USB Device Driver Functional Model
The USB device controller can operate in distinct modes while transferring data. This section describes the common sequence of operations for the receive and transmit API functions in the device driver, highlighting potential differences when the controller is operating on FIFO or DMA mode. While there are some controllers that are strictly FIFO-based or DMA-based, there are controllers that can operate in both modes depending on hardware characteristics. For this type of controller, the device driver will employ the appropriate sequence of operations depending, for example, on the endpoint type.
Device Synchronous Receive
The device synchronous receive operation is initiated by the calls: USBD_BulkRx()
, USBD_CtrlRx()
, and USBD_IntrRx()
. The Figure - Device Synchronous Receive Diagram shows an overview of the device synchronous receive operation.
(1) The upper layer functions (USBD_BulkRx()
, USBD_CtrlRx()
, and USBD_IntrRx()
) lock the endpoint and call USBD_EP_Rx()
.
(2) In USBD_EP_RX()
, USBD_DrvEP_RxStart()
is invoked.
On DMA-based controllers, this device driver API is responsible for queuing a receive transfer. The queued receive transfer does not need to satisfy the whole requested transfer length in one single transaction. If multiple transfers are queued only the last queued transfer must be signaled to the USB device stack. This is required since the USB device stack iterates through the receive process until all requested data or a short packet has been received. This function must also return the maximum amount of bytes that will be received. Typically this value will be the lowest value between the maximum transfer size and the amount of bytes requested by the core.
On FIFO-based controllers, this device driver API is responsible for enabling data to be received into the endpoint FIFO, including any related ISR’s. The function must return the maximum amount of bytes that will be received. Typically this value will be the lowest value between the FIFO size and the amount of bytes requested by the core.
(3) While data is being received, the device synchronous receive operation waits on the device receive signal, during which the endpoint is unlocked.
(4) The USB device controller triggers an interrupt request when it is finished receiving the data. This invokes the USB device driver interrupt service routine (ISR) handler, directly or indirectly, depending on the architecture.
(5) Inside the USB device driver ISR handler, the type of interrupt request is determined to be a receive interrupt. USBD_EP_RxCmpl()
is called to unblock the device receive signal. The endpoint is re-locked.
(6) The device receive operation reaches the USBD_EP_Rx()
, which internally calls USBD_DrvEP_Rx()
.
On DMA-based controllers, this device driver API is responsible for de-queuing the completed receive transfer and returning the amount of data received. In case the DMA-based controller requires the buffered data to be placed in a dedicated USB memory region, the buffered data must be transferred into the application buffer area.
On FIFO-based controllers, this device driver API is responsible for reading the amount of data received by copying it into the application buffer area and returning the data back to its caller.
(7) The device receive operation iterates through the process until the amount of data received matches the amount requested, or a short packet is received. The endpoint is unlocked.
Device Asynchronous Receive
The device asynchronous receive operation is initiated by the calls: USBD_BulkRxAsync()
, USBD_IntrRxAsync()
and
. Figure - Device Asynchronous Receive Diagram shows an overview of the device asynchronous receive operation.USBD_IsocRxAsync()
(1) The upper layer functions (USBD_BulkRxAsync()
, USBD_IntrRxAsync()
and
) lock the endpoint and call USBD_IsocRxAsync()
USBD_EP_Rx()
, passing a receive complete callback function as an argument.
(2) In USBD_EP_Rx()
, the USBD_DrvEP_RxStart()
function is invoked in the same way as for the synchronous operation.
On DMA-based controllers, this device driver API is responsible for queuing a receive transfer. The queued receive transfer does not need to satisfy the whole requested transfer length in one single transaction. If multiple transfers are queued only the last queued transfer must be signaled to the USB device stack. This is required since the USB device stack iterates through the receive process until all requested data or a short packet has been received. This function must also return the maximum amount of bytes that will be received. Typically this value will be the lowest value between the maximum transfer size and the amount of bytes requested by the core.
On FIFO-based controllers, this device driver API is responsible for enabling data to be received into the endpoint FIFO, including any related ISR’s. The function must return the maximum amount of bytes that will be received. Typically this value will be the lowest value between the FIFO size and the amount of bytes requested by the core.
In both cases, no more transfers can be queued on that endpoint if the maximum amount of bytes that can be received is lower than the amount requested by the application. For example, if the application wishes to receive 1000 bytes but that the driver can only receive up to 512 bytes per transfer, USBD_DrvEP_RxStart()
will return 512 as the maximum number of bytes that could be received. In this case, no other transfer can be queued on that endpoint at this moment. When this first transfer completes, another transfer of 488 (1000 - 512) bytes will be queued. Since the driver will return 488 as the maximum byte it can receive (the amount requested), it would be possible to queue another transfer at that moment.
(3) The transfer is added to the endpoint transfer list, if possible. That is, the driver must be able to queue the transfer too, there must not be a synchronous transfer currently in progress on that same endpoint and there must not be a partial asynchronous transfer queued on that endpoint (see note #2). The call to USBD_EP_Rx()
immediately returns (with the appropriate error value, if any) to the application (without blocking) and the endpoint is unlocked while data is being received.
(4) The USB device controller triggers an interrupt request when it is finished receiving the data. This invokes the USB device driver interrupt service routine (ISR) handler, directly or indirectly, depending on the architecture.
(5) Inside the USB device driver ISR handler, the type of interrupt request is determined to be a receive interrupt. USBD_EP_RxCmpl()
is called to queue the core event for the endpoint that had its transfer completed.
(6) The core task de-queues the core event indicating a completed transfer.
(7) The core task invokes USBD_EP_XferAsyncProcess()
, which locks the endpoint and gets the first completed transfer in the endpoint transfer list.
(8) The core task internally calls USBD_DrvEP_Rx()
for the completed transfer.
On DMA-based controllers, this device driver API is responsible for de-queuing the completed receive transfer and returning the amount of data received. In case the DMA-based controller requires the buffered data to be placed in a dedicated USB memory region, the buffered data must be transferred into the application buffer area.
On FIFO-based controllers, this device driver API is responsible for reading the amount of data received by copying it into the application buffer area and returning the data back to its caller.
(9) If the overall amount of data received is less than the amount requested and the current transfer is not a short packet, USBD_DrvEP_RxStart()
is called (see note #2) to request the remaining data. Endpoint is unlocked afterwards.
(10) The receive operation finishes when the amount of data received matches the amount requested, or a short packet is received. The endpoint is unlocked and the receive complete callback is invoked to notify the application about the completion of the process.
Device Synchronous Transmit
The device synchronous transmit operation is initiated by the calls: USBD_BulkTx()
, USBD_CtrlTx()
, and USBD_IntrTx()
. Figure - Device Synchronous Transmit Diagram shows an overview of the device synchronous transmit operation.
(1) The upper layer functions (USBD_BulkTx()
, USBD_CtrlTx()
, and USBD_IntrTx()
) lock the endpoint and call USBD_EP_Tx()
.
(2) In
,USBD_EP_Tx()
USBD_DrvEP_Tx()
is invoked.
On DMA-based controllers, this device driver API is responsible for preparing the transmit transfer/descriptor and returning the amount of data to transmit. In case the DMA-based controller requires the buffered data to be placed in a dedicated USB memory region, the contents of the application buffer area must be transferred into the dedicated memory region.
On FIFO-based controllers, this device driver API is responsible for writing the amount of data to transfer into the FIFO and returning the amount of data to transmit.
(3) The USBD_DrvEP_TxStart()
API starts the transmit process.
On DMA-based controllers, this device driver API is responsible for queuing the DMA transmit descriptor and enabling DMA transmit complete ISR’s.
On FIFO-based controllers, this device driver API is responsible for enabling transmit complete ISR’s.
(4) While data is being transmitted, the device synchronous transmit operation waits on the device transmit signal, during which the endpoint is unlocked.
(5) The USB device controller triggers an interrupt request when it is finished transmitting the data. This invokes the USB device driver interrupt service routine (ISR) handler, directly or indirectly, depending on the architecture.
(6) Inside the USB device driver ISR handler, the type of interrupt request is determined as a transmit interrupt. USBD_EP_TxCmpl()
is called to unblock the device transmit signal. The endpoint is re-locked.
On DMA-based controllers, the transmit transfer is de-queued from a list of completed transfers.
(7) The device transmit operation iterates through the process until the amount of data transmitted matches the requested amount. The endpoint is unlocked.
Device Asynchronous Transmit
The device asynchronous transmit operation is initiated by the calls: USBD_BulkTxAsync()
, USBD_IntrTxAsync()
and USBD_IsocTxAsync()
. Figure - Device Asynchronous Transmit Diagram shows an overview of the device asynchronous transmit operation.
(1) The upper layer functions (USBD_BulkTxAsync()
, USBD_IntrTxAsync()
and
) lock the endpoint call USBD_IsocTxAsync()
USBD_EP_Tx()
passing a transmit complete callback function as an argument.
(2) In USBD_EP_Tx()
, the USBD_DrvEP_Tx()
function is invoked in the same way as for the synchronous operation.
On DMA-based controllers, this device driver API is responsible for preparing the transmit transfer/descriptor and returning the amount of data to transmit. In case the DMA-based controller requires the buffered data to be placed in a dedicated USB memory region, the contents of the application buffer area must be transferred into the dedicated memory region.
On FIFO-based controllers, this device driver API is responsible for writing the amount of data to transfer into the FIFO and returning the amount of data to transmit.
In both cases, no more transfers can be queued on that endpoint if the maximum amount of bytes that can be transmitted is lower than the amount requested by the application. For example, if the application wishes to transmit 1000 bytes but that the driver can only transmit up to 512 bytes per transfer, USBD_DrvEP_Tx()
will return 512 as the maximum number of bytes that could be transmitted. In this case, no other transfer can be queued on that endpoint at this moment. When this first transfer completes, another transfer of 488 (1000 - 512) bytes will be queued. Since the driver will return 488 as the maximum byte it can transmit (the amount requested), it would be possible to queue another transfer at that moment.
(3) The USBD_DrvEP_TxStart()
API starts the transmit process.
On DMA-based controllers, this device driver API is responsible for queuing the DMA transmit descriptor and enabling DMA transmit complete ISR’s.
On FIFO-based controllers, this device driver API is responsible for enabling transmit complete ISR’s.
(4) The transfer is added to the endpoint transfer list, if possible. That is, the driver must be able to queue the transfer too, there must not be a synchronous transfer currently in progress on that same endpoint and there must not be a partial asynchronous transfer queued on that endpoint (see note #2). The call to USBD_EP_Tx()
returns immediately to the application (without blocking) and the endpoint is unlocked while data is being transmitted.
(5) The USB device controller triggers an interrupt request when it is finished transmitting the data. This invokes the USB device driver interrupt service routine (ISR) handler, directly or indirectly, depending on the architecture.
(6) Inside the USB device driver ISR handler, the type of interrupt request is determined as a transmit interrupt. USBD_EP_TxCmpl()
is called to queue the endpoint that had its transfer completed.
On DMA-based controllers, the transmit transfer is de-queued from the list of completed transfers.
(7) The core task de-queues the core event indicating a completed transfer.
(8) The core task invokes USBD_EP_XferAsyncProcess()
, which locks the endpoint and gets the first completed transfer in the endpoint transfer list.
(9) If the overall amount of data transmitted is less than the amount requested, USBD_DrvEP_Tx()
and USBD_DrvEP_TxStart()
are called to transmit the remaining amount of data. Endpoint is then unlocked.
(10) The device transmit operation finishes when the amount of data transmitted matches the amount requested. The endpoints is unlocked and the transmit complete callback is invoked to notify the application about the completion of the process.
Device Set Address
The device set address operation is performed by the setup transfer handler when a SET_ADDRESS
request is received. Figure - Device Set Address Diagram shows an overview of the device set address operation.
(1) Once the arguments of the setup request are validated, USBD_DrvAddrSet()
is called to inform the device driver layer of the new address. For controllers that have hardware assistance in setting the device address after the status stage, this device driver API is used to configure the device address and enable the transition after the status stage. For controllers that activate the device address as soon as configured, this device driver API should not perform any action.
(2) The setup request status stage is transmitted to acknowledge the address change.
(3) After the status stage, the USBD_DrvAddrEn()
is called to inform the device driver layer to enable the new device address. For controllers that activate the device address as soon as configured, this device driver API is responsible for setting and enabling the new device address. For controllers that have hardware assistance in setting the device address after the status stage, this device driver API should not perform any action, since USBD_DrvAddrSet()
has already taken care of setting the new device.