//==========================================================================
//
//      usb.c
//
//      RedBoot stream handler for usb protocol
//
//==========================================================================
// ####ECOSGPLCOPYRIGHTBEGIN####                                            
// -------------------------------------------                              
// This file is part of eCos, the Embedded Configurable Operating System.   
// Copyright (C) 2007 Free Software Foundation, Inc.                        
// Copyright (C) 2007 eCosCentric Limited                                   
//
// eCos is free software; you can redistribute it and/or modify it under    
// the terms of the GNU General Public License as published by the Free     
// Software Foundation; either version 2 or (at your option) any later      
// version.                                                                 
//
// eCos is distributed in the hope that it will be useful, but WITHOUT      
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or    
// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License    
// for more details.                                                        
//
// You should have received a copy of the GNU General Public License        
// along with eCos; if not, write to the Free Software Foundation, Inc.,    
// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.            
//
// As a special exception, if other files instantiate templates or use      
// macros or inline functions from this file, or you compile this file      
// and link it with other works to produce a work based on this file,       
// this file does not by itself cause the resulting work to be covered by   
// the GNU General Public License. However the source code for this file    
// must still be made available in accordance with section (3) of the GNU   
// General Public License v2.                                               
//
// This exception does not invalidate any other reasons why a work based    
// on this file might be covered by the GNU General Public License.         
// -------------------------------------------                              
// ####ECOSGPLCOPYRIGHTEND####                                              
//==========================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):    PK
// Contributors: 
// Date:         2006-08-11
// Purpose:      
// Description:  
//              
// This code is part of RedBoot (tm).
//
//####DESCRIPTIONEND####
//
//==========================================================================

#include <redboot.h>
#include <cyg/io/io.h>  // handle and cyg_io_lookup

//==========================================================================
// Definitions

#define ERROR_INCORRECT_FILE_SIZE -1
#define ERROR_RX_FAILED -2
#define ERROR_TOO_MANY_DATA -3
#define TIMEOUT (20*1000)

static unsigned long image_size;

static unsigned long bytes_to_transfer;

static unsigned long nbr_stored_bytes;

static unsigned char  stored_buf[CYGNUM_REDBOOT_GETC_BUFFER + 64];

static unsigned char  received_buf[64];

static unsigned long nbr_received;

static unsigned char data_received;

void usb_stream_close(int *err);

//==========================================================================
// USB definitions from USB driver

#include <cyg/io/usb/usbs.h>

extern usbs_control_endpoint CYGDAT_REDBOOT_USBS_CONTROL_ENDPOINT;

extern usbs_enumeration_data CYGDAT_REDBOOT_USBS_ENUMERATION_DATA;

extern usbs_rx_endpoint CYGDAT_REDBOOT_USBS_RX_ENDPOINT;

// The following are shorthand to make the code more readable.
#define control_endpoint CYGDAT_REDBOOT_USBS_CONTROL_ENDPOINT
#define enum_data        CYGDAT_REDBOOT_USBS_ENUMERATION_DATA
#define rx_endpoint      CYGDAT_REDBOOT_USBS_RX_ENDPOINT
#define cat2_(__a,__b)   __a ## __b
#define cat_(__a,__b)    cat2_(__a,__b)

extern void cat_( CYGDAT_REDBOOT_USBS_CONTROL_ENDPOINT, _stop)(usbs_control_endpoint* endpoint);


//==========================================================================


void completion_fn_rx_endpoint (void* data, int result)
{
    //result (si positif) contient le nombre d'octets lus réelement par le driver !!!
    nbr_received = result;
    data_received = 1;
}


//==========================================================================
// Called from redboot_getc_init
//retourne < 0 si erreur
//info ne contient aucune données utiles

int usb_stream_open(connection_info_t *info, int *err)
{
    unsigned int i = 0;

#if 0
 for( i = 0; i < 20; i++ )
    {
        int j;
        for (j = 0;  j < 1000;  j++)
            CYGACC_CALL_IF_DELAY_US((cyg_int32)1000);

        diag_printf("Tick %d\n",i);
    }
#endif
    //initialisation
    nbr_stored_bytes = 0;

    control_endpoint.enumeration_data = &enum_data;

    usbs_start(&control_endpoint);   //return void
    diag_printf("USB core started\n");

    while(control_endpoint.state != USBS_STATE_CONFIGURED)
    {
        (*control_endpoint.poll_fn)(&control_endpoint) ;
    }

    diag_printf("USB core configured\n");

    //flag
    data_received = 0;

    //initialisation si jamais la taille du paquet USB reçu est < 4
    received_buf[0] = 0;
    received_buf[1] = 0;
    received_buf[2] = 0;
    received_buf[3] = 0;

    //appel driver
    usbs_start_rx_buffer(     //return void
        &rx_endpoint,
        received_buf,
        64,
        completion_fn_rx_endpoint,
        NULL
        );

    diag_printf("Start download app in next %ds\n", TIMEOUT/1000 );
    while( data_received    == 0)
    {
        CYGACC_CALL_IF_DELAY_US(1000);
        if ((i % 1000) == 0) diag_printf(".");
//        CYGACC_CALL_IF_DELAY_US((cyg_int32)1000000);
//        diag_printf("%d\n", i);
        
        if (i++ == TIMEOUT)
        {
            diag_printf("\n");
            usb_stream_close(0);
            return -1;
        }
        //attente active
        (*control_endpoint.poll_fn)(&control_endpoint) ;
    }

    if (nbr_received <= 0 || nbr_received > 4)
    {
        *err = ERROR_INCORRECT_FILE_SIZE;
        usb_stream_close(0);
        return -1;
    }


    //lecture de la taille du fichier
    image_size = (unsigned long)received_buf[0];
    image_size |= (unsigned long)received_buf[1]<<8;
    image_size |= (unsigned long)received_buf[2]<<16;
    image_size |= (unsigned long)received_buf[3]<<24;

    bytes_to_transfer = image_size;

    diag_printf("usb_stream_open: size of file = 0x%lX\n", image_size);

    return 1;
}


//==========================================================================

void usb_stream_close(int *err)
{
    diag_printf("USB core stopped\n");
    cat_( CYGDAT_REDBOOT_USBS_CONTROL_ENDPOINT, _stop)(&control_endpoint);
}

//==========================================================================

void usb_stream_terminate(bool abort, int (*getc)(void))
{

}

//==========================================================================
//
//on retourne le nombre d'octets lus
//si ce nombre est > 0 et < size, la commande load en déduira qu'il s'agit du dernier buffer
//si ce nombre vaut 0, la commande load se terminera
//si ce nombre est < 0, cela indique une erreur

int usb_stream_read(char *buf, int size, int *err)
{
    unsigned int i, j;

    if (size < 0)
    {
        return -1; //error
    }
    else if (size == 0)
    {
        return 0;
    }
    else if (size <= nbr_stored_bytes)
    {
        memcpy(buf, stored_buf, size); //copie stored_buf dans buf

        for(i=size,j=0 ; i<nbr_stored_bytes ; i++,j++)
        {
            stored_buf[j] = stored_buf[i];
        }
        nbr_stored_bytes -= size;
        bytes_to_transfer -= size;
        return size;
    }
    else if (bytes_to_transfer == nbr_stored_bytes)
    {
        //la fin !!!
        memcpy(buf, stored_buf, bytes_to_transfer);
        return bytes_to_transfer;
    }
    else if (bytes_to_transfer < nbr_stored_bytes)
    {
        *err = ERROR_TOO_MANY_DATA;
        return -1;
    }
    else
    {
        data_received = 0;

        usbs_start_rx_buffer(  //return void
            &rx_endpoint,
            received_buf,
            64,
            completion_fn_rx_endpoint,
            NULL
            );

        while( data_received    == 0)
        {
            (*control_endpoint.poll_fn)(&control_endpoint) ;
        }

        if (nbr_received > 0)
        {
            for(i=0,j=nbr_stored_bytes ; i<nbr_received; i++,j++)
            {
                stored_buf[j] = received_buf[i];
            }
            nbr_stored_bytes += nbr_received;
            return usb_stream_read(buf, size, err) ;
        }
        else
        {
            *err = ERROR_RX_FAILED;
            return -1;
        }
    }
}

//==========================================================================

char* usb_error(int err)
{
    switch (err)
    {
    case ERROR_INCORRECT_FILE_SIZE:
        return "The first USB packet must contain between 1 and 4 bytes to indicate the size of the file";
        break;
    case ERROR_RX_FAILED:
        return "The call to usbs_start_rx_buffer failed when receiving the contents of the file";
        break;
    case ERROR_TOO_MANY_DATA:
        return "Too much data was received";
        break;
    default:
        return "Unknown error";
        break;
    }
}

//==========================================================================
//
// RedBoot interface
//

GETC_IO_FUNCS(usb_io, usb_stream_open, usb_stream_close, usb_stream_terminate, usb_stream_read, usb_error);
RedBoot_load(usb, usb_io, true, false, 0);  //name, functions, verbose, filename, mode

//==========================================================================
// End of usb.c

