USB device stack

The USB device stack is split into three layers:
  • USB Device Controller drivers (hardware dependent)
  • USB device core driver (hardware independent)
  • USB device class drivers (hardware independent)

USB device controller drivers

The Device Controller Driver Layer implements the low level control routines to deal directly with the hardware. All device controller drivers should implement the APIs described in file usb_dc.h. This allows the integration of new USB device controllers to be done without changing the upper layers. For now only Quark SE USB device controller (Designware IP) is supported.

Structures

struct usb_dc_ep_cfg_data {
   uint8_t ep_addr;
   uint16_t ep_mps;
   enum usb_dc_ep_type ep_type;
};
Structure containing the USB endpoint configuration.
  • ep_addr: endpoint address, the number associated with the EP in the device configuration structure. IN EP = 0x80 | <endpoint number>. OUT EP = 0x00 | <endpoint number>
  • ep_mps: Endpoint max packet size.
  • ep_type: Endpoint type, may be Bulk, Interrupt or Control. Isochronous endpoints are not supported for now.
enum usb_dc_status_code {
   USB_DC_ERROR,
   USB_DC_RESET,
   USB_DC_CONNECTED,
   USB_DC_CONFIGURED,
   USB_DC_DISCONNECTED,
   USB_DC_SUSPEND,
   USB_DC_RESUME,
   USB_DC_UNKNOWN
};
Status codes reported by the registered device status callback.
  • USB_DC_ERROR: USB error reported by the controller.
  • USB_DC_RESET: USB reset.
  • USB_DC_CONNECTED: USB connection established - hardware enumeration is completed.
  • USB_DC_CONFIGURED: USB configuration done.
  • USB_DC_DISCONNECTED: USB connection lost.
  • USB_DC_SUSPEND: USB connection suspended by the HOST.
  • USB_DC_RESUME: USB connection resumed by the HOST.
  • USB_DC_UNKNOWN: Initial USB connection status.
enum usb_dc_ep_cb_status_code {
   USB_DC_EP_SETUP,
   USB_DC_EP_DATA_OUT,
   USB_DC_EP_DATA_IN,
};
Status Codes reported by the registered endpoint callback.
  • USB_DC_EP_SETUP: SETUP packet received.
  • USB_DC_EP_DATA_OUT: Out transaction on this endpoint. Data is available for read.
  • USB_DC_EP_DATA_IN: In transaction done on this endpoint.

APIs

The following APIs are provided by the device controller driver:

usb_dc_attach()
This function attaches USB for device connection. Upon success, the USB PLL is enabled, and the USB device is now capable of transmitting and receiving on the USB bus and of generating interrupts.
usb_dc_detach()
This function detaches the USB device. Upon success the USB hardware PLL is powered down and USB communication is disabled.
usb_dc_reset()
This function returns the USB device to it’s initial state.
usb_dc_set_address()
This function sets USB device address.
usb_dc_set_status_callback()
This function sets USB device controller status callback. The registered callback is used to report changes in the status of the device controller. The status code are described by the usb_dc_status_code enumeration.
usb_dc_ep_configure()
This function configures an endpoint. usb_dc_ep_cfg_data structure provides the endpoint configuration parameters: endpoint address, endpoint maximum packet size and endpoint type.
usb_dc_ep_set_stall()
This function sets stall condition for the selected endpoint.
usb_dc_ep_clear_stall()
This functions clears stall condition for the selected endpoint
usb_dc_ep_is_stalled()
This function check if selected endpoint is stalled.
usb_dc_ep_halt()
This function halts the selected endpoint
usb_dc_ep_enable()
This function enables the selected endpoint. Upon success interrupts are enabled for the corresponding endpoint and the endpoint is ready for transmitting/receiving data.
usb_dc_ep_disable()
This function disables the selected endpoint. Upon success interrupts are disabled for the corresponding endpoint and the endpoint is no longer able for transmitting/receiving data.
usb_dc_ep_flush()
This function flushes the FIFOs for the selected endpoint.
usb_dc_ep_write()
This function writes data to the specified endpoint. The supplied usb_ep_callback function will be called when data is transmitted out.
usb_dc_ep_read()
This function is called by the Endpoint handler function, after an OUT interrupt has been received for that EP. The application must only call this function through the supplied usb_ep_callback function.
usb_dc_ep_set_callback()
This function sets callback function for notification of data received and available to application or transmit done on the selected endpoint. The callback status code is described by usb_dc_ep_cb_status_code.

USB device core layer

The USB Device core layer is a hardware independent interface between USB device controller driver and USB device class drivers or customer applications. It’s a port of the LPCUSB device stack. It provides the following functionalities:

  • Responds to standard device requests and returns standard descriptors, essentially handling ‘Chapter 9’ processing, specifically the standard device requests in table 9-3 from the universal serial bus specification revision 2.0.
  • Provides a programming interface to be used by USB device classes or customer applications. The APIs are described in the usb_device.h file.
  • Uses the APIs provided by the device controller drivers to interact with the USB device controller.

Structures

typedef void (*usb_status_callback)(enum usb_dc_status_code status_code);

Callback function signature for the device status.

typedef void (*usb_ep_callback)(uint8_t ep,
   enum usb_dc_ep_cb_status_code cb_status);

Callback function signature for the USB Endpoint.

typedef int (*usb_request_handler) (struct usb_setup_packet *setup,
   int *transfer_len, uint8_t **payload_data);

Callback function signature for class specific requests. For host to device direction the ‘len’ and ‘payload_data’ contain the length of the received data and the pointer to the received data respectively. For device to host class requests, ‘len’ and ‘payload_data’ should be set by the callback function with the length and the address of the data to be transmitted buffer respectively.

struct usb_ep_cfg_data {
   usb_ep_callback ep_cb;
   uint8_t ep_addr;
};
This structure contains configuration for a certain endpoint.
  • ep_cb: callback function for notification of data received and available to application or transmit done, NULL if callback not required by application code.
  • ep_addr: endpoint address. The number associated with the EP in the device configuration structure.
struct usb_interface_cfg_data {
   usb_request_handler class_handler;
   usb_request_handler custom_handler;
   uint8_t *payload_data;
};
This structure contains USB interface configuration.
  • class_handler: handler for USB Class specific Control (EP 0) communications.
  • custom_handler: the custom request handler gets a first chance at handling the request before it is handed over to the ‘chapter 9’ request handler.
  • payload_data: this data area, allocated by the application, is used to store class specific command data and must be large enough to store the largest payload associated with the largest supported Class’ command set.
struct usb_cfg_data {
   const uint8_t *usb_device_description;
   usb_status_callback cb_usb_status;
   struct usb_interface_cfg_data interface;
   uint8_t num_endpoints;
   struct usb_ep_cfg_data *endpoint;
};
This structure contains USB device configuration.
  • usb_device_description: USB device description, see http://www.beyondlogic.org/usbnutshell/usb5.shtml#DeviceDescriptors
  • cb_usb_status: callback to be notified on USB connection status change
  • interface: USB class handlers and storage space.
  • num_endpoints: number of individual endpoints in the device configuration
  • endpoint: pointer to an array of endpoint configuration structures (usb_cfg_data) of length equal to the number of EP associated with the device description, not including control endpoints.

The class drivers instantiates this with given parameters using the “usb_set_config” function.

APIs

usb_set_config()
This function configures USB device.
usb_deconfig()
This function returns the USB device back to it’s initial state
usb_enable()
This function enable USB for host/device connection. Upon success, the USB module is no longer clock gated in hardware, it is now capable of transmitting and receiving on the USB bus and of generating interrupts.
usb_disable()
This function disables the USB device. Upon success, the USB module clock is gated in hardware and it is no longer capable of generating interrupts.
usb_write()
write data to the specified endpoint. The supplied usb_ep_callback will be called when transmission is done.
usb_read()
This function is called by the endpoint handler function after an OUT interrupt has been received for that EP. The application must only call this function through the supplied usb_ep_callback function.

USB device class drivers

To initialize the device class driver instance the USB device class driver should call usb_set_config() passing as parameter the instance’s configuration structure.

For example, for CDC_ACM sample application:

static const uint8_t cdc_acm_usb_description[] = {
   /* Device descriptor */
   USB_DEVICE_DESC_SIZE,           /* Descriptor size */
   USB_DEVICE_DESC,                /* Descriptor type */
   LOW_BYTE(USB_1_1),
   HIGH_BYTE(USB_1_1),             /* USB version in BCD format */
   COMMUNICATION_DEVICE_CLASS,     /* Class */
   0x00,                           /* SubClass - Interface specific */
   0x00,                           /* Protocol - Interface specific */
   MAX_PACKET_SIZE_EP0,            /* Max Packet Size */
   LOW_BYTE(VENDOR_ID),
   HIGH_BYTE(VENDOR_ID),           /* Vendor Id */
   LOW_BYTE(CDC_PRODUCT_ID),
   HIGH_BYTE(CDC_PRODUCT_ID),      /* Product Id */
   LOW_BYTE(BCDDEVICE_RELNUM),
   HIGH_BYTE(BCDDEVICE_RELNUM),    /* Device Release Number */
   0x01,                           /* Index of Manufacturer String Descriptor */
   0x02,                           /* Index of Product String Descriptor */
   0x03,                           /* Index of Serial Number String Descriptor */
   CDC_NUM_CONF,                   /* Number of Possible Configuration */

   /* Configuration descriptor */
   USB_CONFIGURATION_DESC_SIZE,    /* Descriptor size */
   USB_CONFIGURATION_DESC,         /* Descriptor type */
   LOW_BYTE(CDC_CONF_SIZE),
   HIGH_BYTE(CDC_CONF_SIZE),       /* Total length in bytes of data returned */
   CDC_NUM_ITF,                    /* Number of interfaces */
   0x01,                           /* Configuration value */
   0x00,                           /* Index of the Configuration string */
   USB_CONFIGURATION_ATTRIBUTES,   /* Attributes */
   MAX_LOW_POWER,                  /* Max power consumption */

   /* Interface descriptor */
   USB_INTERFACE_DESC_SIZE,        /* Descriptor size */
   USB_INTERFACE_DESC,             /* Descriptor type */
   0x00,                           /* Interface index */
   0x00,                           /* Alternate setting */
   CDC1_NUM_EP,                    /* Number of Endpoints */
   COMMUNICATION_DEVICE_CLASS,     /* Class */
   ACM_SUBCLASS,                   /* SubClass */
   V25TER_PROTOCOL,                /* Protocol */
   0x00,                           /* Index of the Interface String Descriptor */

   /* Header Functional Descriptor */
   USB_HFUNC_DESC_SIZE,            /* Descriptor size */
   CS_INTERFACE,                   /* Descriptor type */
   USB_HFUNC_SUBDESC,              /* Descriptor SubType */
   LOW_BYTE(USB_1_1),
   HIGH_BYTE(USB_1_1),             /* CDC Device Release Number */

   /* Call Management Functional Descriptor */
   USB_CMFUNC_DESC_SIZE,           /* Descriptor size */
   CS_INTERFACE,                   /* Descriptor type */
   USB_CMFUNC_SUBDESC,             /* Descriptor SubType */
   0x00,                           /* Capabilities */
   0x01,                           /* Data Interface */

   /* ACM Functional Descriptor */
   USB_ACMFUNC_DESC_SIZE,          /* Descriptor size */
   CS_INTERFACE,                   /* Descriptor type */
   USB_ACMFUNC_SUBDESC,            /* Descriptor SubType */
   /* Capabilities - Device supports the request combination of:
    *        Set_Line_Coding,
    *        Set_Control_Line_State,
    *        Get_Line_Coding
    *        and the notification Serial_State
    */
   0x02,

   /* Union Functional Descriptor */
   USB_UFUNC_DESC_SIZE,            /* Descriptor size */
   CS_INTERFACE,                   /* Descriptor type */
   USB_UFUNC_SUBDESC,              /* Descriptor SubType */
   0x00,                           /* Master Interface */
   0x01,                           /* Slave Interface */

   /* Endpoint INT */
   USB_ENDPOINT_DESC_SIZE,         /* Descriptor size */
   USB_ENDPOINT_DESC,              /* Descriptor type */
   CDC_ENDP_INT,                   /* Endpoint address */
   USB_DC_EP_INTERRUPT,            /* Attributes */
   LOW_BYTE(CDC_INTERRUPT_EP_MPS),
   HIGH_BYTE(CDC_INTERRUPT_EP_MPS),/* Max packet size */
   0x0A,                           /* Interval */

   /* Interface descriptor */
   USB_INTERFACE_DESC_SIZE,        /* Descriptor size */
   USB_INTERFACE_DESC,             /* Descriptor type */
   0x01,                           /* Interface index */
   0x00,                           /* Alternate setting */
   CDC2_NUM_EP,                    /* Number of Endpoints */
   COMMUNICATION_DEVICE_CLASS_DATA,/* Class */
   0x00,                           /* SubClass */
   0x00,                           /* Protocol */
   0x00,                           /* Index of the Interface String Descriptor */

   /* First Endpoint IN */
   USB_ENDPOINT_DESC_SIZE,         /* Descriptor size */
   USB_ENDPOINT_DESC,              /* Descriptor type */
   CDC_ENDP_IN,                    /* Endpoint address */
   USB_DC_EP_BULK,                 /* Attributes */
   LOW_BYTE(CDC_BULK_EP_MPS),
   HIGH_BYTE(CDC_BULK_EP_MPS),     /* Max packet size */
   0x00,                           /* Interval */

   /* Second Endpoint OUT */
   USB_ENDPOINT_DESC_SIZE,         /* Descriptor size */
   USB_ENDPOINT_DESC,              /* Descriptor type */
   CDC_ENDP_OUT,                   /* Endpoint address */
   USB_DC_EP_BULK,                 /* Attributes */
   LOW_BYTE(CDC_BULK_EP_MPS),
   HIGH_BYTE(CDC_BULK_EP_MPS),     /* Max packet size */
   0x00,                           /* Interval */

   /* String descriptor language, only one, so min size 4 bytes.
    * 0x0409 English(US) language code used
    */
   USB_STRING_DESC_SIZE,           /* Descriptor size */
   USB_STRING_DESC,                /* Descriptor type */
   0x09,
   0x04,
   /* Manufacturer String Descriptor "Intel" */
   0x0C,
   USB_STRING_DESC,
   'I', 0, 'n', 0, 't', 0, 'e', 0, 'l', 0,
   /* Product String Descriptor "CDC-ACM" */
   0x10,
   USB_STRING_DESC,
   'C', 0, 'D', 0, 'C', 0, '-', 0, 'A', 0, 'C', 0, 'M', 0,
   /* Serial Number String Descriptor "00.01" */
   0x0C,
   USB_STRING_DESC,
   '0', 0, '0', 0, '.', 0, '0', 0, '1', 0,
};
static struct usb_ep_cfg_data cdc_acm_ep_data[] = {
   {
      .ep_cb = cdc_acm_int_in,
      .ep_addr = CDC_ENDP_INT
   },
   {
      .ep_cb = cdc_acm_bulk_out,
      .ep_addr = CDC_ENDP_OUT
   },
   {
      .ep_cb = cdc_acm_bulk_in,
      .ep_addr = CDC_ENDP_IN
   }
};
static struct usb_cfg_data cdc_acm_config = {
   .usb_device_description = cdc_acm_usb_description,
   .cb_usb_status = cdc_acm_dev_status_cb,
   .interface = {
   .class_handler = cdc_acm_class_handle_req,
   .custom_handler = NULL,
   .payload_data = NULL,
   },
   .num_endpoints = CDC1_NUM_EP + CDC2_NUM_EP,
   .endpoint = cdc_acm_ep_data
};
ret = usb_set_config(&cdc_acm_config);
if (ret < 0) {
   DBG("Failed to config USB\n");
   return ret;
}

To enable the USB device for host/device connection:

ret = usb_enable(&cdc_acm_config);
if (ret < 0) {
   DBG("Failed to enable USB\n");
   return ret;
}

The class device requests are forwarded by the USB stack core driver to the class driver through the registered class handler. For the CDC ACM sample class driver, ‘cdc_acm_class_handle_req’ processes the SET_LINE_CODING, CDC_SET_CONTROL_LINE_STATE and CDC_GET_LINE_CODING class requests:

int cdc_acm_class_handle_req(struct usb_setup_packet *pSetup,
      int32_t *len, uint8_t **data)
{
   struct cdc_acm_dev_data_t * const dev_data = DEV_DATA(cdc_acm_dev);

   switch (pSetup->bRequest) {
   case CDC_SET_LINE_CODING:
      memcpy(&dev_data->line_coding, *data, sizeof(dev_data->line_coding));
      DBG("\nCDC_SET_LINE_CODING %d %d %d %d\n",
         sys_le32_to_cpu(dev_data->line_coding.dwDTERate),
         dev_data->line_coding.bCharFormat,
         dev_data->line_coding.bParityType,
         dev_data->line_coding.bDataBits);
   break;

   case CDC_SET_CONTROL_LINE_STATE:
      dev_data->line_state = (uint8_t)sys_le16_to_cpu(pSetup->wValue);
      DBG("CDC_SET_CONTROL_LINE_STATE 0x%x\n", dev_data->line_state);
         break;

   case CDC_GET_LINE_CODING:
      *data = (uint8_t *)(&dev_data->line_coding);
      *len = sizeof(dev_data->line_coding);
      DBG("\nCDC_GET_LINE_CODING %d %d %d %d\n",
      sys_le32_to_cpu(dev_data->line_coding.dwDTERate),
         dev_data->line_coding.bCharFormat,
         dev_data->line_coding.bParityType,
         dev_data->line_coding.bDataBits);
         break;

   default:
      DBG("CDC ACM request 0x%x, value 0x%x\n",
         pSetup->bRequest, pSetup->wValue);
         return -EINVAL;
   }

   return 0;
}

The class driver should wait for the USB_DC_CONFIGURED device status code before transmitting any data.

To transmit data to the host, the class driver should call usb_write(). Upon completion the registered endpoint callback will be called. Before sending another packet the class driver should wait for the completion of the previous transfer.

When data is received, the registered endpoint callback is called. usb_read() should be used for retrieving the received data. It must always be called through the registered endpoint callback. For CDC ACM sample driver this happens via the OUT bulk endpoint handler (cdc_acm_bulk_out) mentioned in the endpoint array (cdc_acm_ep_data).

Only CDC ACM and DFU class driver examples are provided for now.