Comtrol eCos User Manual
Page 700

Sending Data to the Host
The normal sequence of events is that the USB device driver will update the appropriate hardware registers. At
some point after that the host will attempt to fetch data by transmitting an IN token. Since a transmit operation
is now in progress the peripheral can send a packet of data, and the host will generate an ACK. At this point the
USB hardware will generate an interrupt, and the device driver will service this interrupt and arrange for a DSR
to be called. Isochronous and interrupt transfers involve just a single packet. However, bulk transfers may involve
multiple packets so the device driver has to check whether there is more data to send and set things up for the next
packet. When the device driver DSR detects a complete transfer it will inform higher-level code by invoking the
supplied completion function.
This means that the completion function will normally be invoked by a DSR and not in thread context - although
some USB device drivers may have a different implementation. Therefore the completion function is restricted in
what it can do, in particular it must not make any calls that will or may block such as locking a mutex or allocating
memory. The kernel documentation should be consulted for more details of DSR’s and interrupt handling generally.
It is possible that the completion function will be invoked before
usbs_start_tx_buffer
returns. Such an event
would be unusual because the transfer cannot happen until the next time the host tries to fetch data from this
peripheral, but it may happen if, for example, another interrupt happens and a higher priority thread is scheduled
to run. Also, if the endpoint is currently halted then the completion function will be invoked immediately with
-EAGAIN
: typically this will happen in the current thread rather than in a separate DSR. The completion function
is allowed to start another transfer immediately by calling
usbs_start_tx_buffer
again.
USB device drivers are not expected to perform any locking. It is the responsibility of higher-level code to ensure
that there is only one transmit operation for a given endpoint in progress at any one time. If there are concurrent
calls to
usbs_start_tx_buffer
then the resulting behaviour is undefined. For typical USB applications this does
not present any problems because only piece of code will access a given endpoint at any particular time.
The following code fragment illustrates a very simple use of
usbs_start_tx_buffer
to implement a blocking
transmit, using a semaphore to synchronise between the foreground thread and the DSR. For a simple example like
this no completion data is needed.
static int error_code = 0;
static cyg_sem_t completion_wait;
static void
completion_fn(void* data, int result)
{
error_code = result;
cyg_semaphore_post(&completion_wait);
}
int
blocking_transmit(usbs_tx_endpoint* ep, const unsigned char* buf, int len)
{
error_code = 0;
usbs_start_tx_buffer(ep, buf, len, &completion_fn, NULL);
cyg_semaphore_wait(&completion_wait);
return error_code;
}
There is also a utility function
usbs_start
. This can be used by code that wants to manipulate
directly, specifically the
complete_fn
,
complete_data
,
buffer
and
buffer_size
fields.
usbs_start_tx
just calls a function supplied by the device driver.
596