USB device stack

The USB device stack is a hardware independent interface between USB device controller driver and USB device class drivers or customer applications. It is a port of the LPCUSB device stack and has been modified and expanded over time. It provides the following functionalities:

  • Uses the APIs provided by the device controller drivers to interact with the USB device controller.

  • 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 is described in include/zephyr/usb/usb_device.h

The device stack has few limitations with which it is not possible to support more than one controller instance at runtime, and only one USB device configuration is supported.

Supported USB classes:

  • USB Audio (experimental)

  • USB CDC ACM

  • USB CDC ECM

  • USB CDC EEM

  • RNDIS

  • USB MSC

  • USB DFU

  • Bluetooth HCI over USB

  • USB HID class

List of samples for different purposes. CDC ACM and HID samples have configuration overlays for composite configuration.

Implementing a non-standard USB class

The configuration of USB Device is done in the stack layer.

The following structures and callbacks need to be defined:

  • Part of USB Descriptor table

  • USB Endpoint configuration table

  • USB Device configuration structure

  • Endpoint callbacks

  • Optionally class, vendor and custom handlers

For example, for the USB loopback application:

 1struct usb_loopback_config {
 2	struct usb_if_descriptor if0;
 3	struct usb_ep_descriptor if0_out_ep;
 4	struct usb_ep_descriptor if0_in_ep;
 5} __packed;
 6
 7USBD_CLASS_DESCR_DEFINE(primary, 0) struct usb_loopback_config loopback_cfg = {
 8	/* Interface descriptor 0 */
 9	.if0 = {
10		.bLength = sizeof(struct usb_if_descriptor),
11		.bDescriptorType = USB_DESC_INTERFACE,
12		.bInterfaceNumber = 0,
13		.bAlternateSetting = 0,
14		.bNumEndpoints = 2,
15		.bInterfaceClass = USB_BCC_VENDOR,
16		.bInterfaceSubClass = 0,
17		.bInterfaceProtocol = 0,
18		.iInterface = 0,
19	},
20
21	/* Data Endpoint OUT */
22	.if0_out_ep = {
23		.bLength = sizeof(struct usb_ep_descriptor),
24		.bDescriptorType = USB_DESC_ENDPOINT,
25		.bEndpointAddress = LOOPBACK_OUT_EP_ADDR,
26		.bmAttributes = USB_DC_EP_BULK,
27		.wMaxPacketSize = sys_cpu_to_le16(CONFIG_LOOPBACK_BULK_EP_MPS),
28		.bInterval = 0x00,
29	},
30
31	/* Data Endpoint IN */
32	.if0_in_ep = {
33		.bLength = sizeof(struct usb_ep_descriptor),
34		.bDescriptorType = USB_DESC_ENDPOINT,
35		.bEndpointAddress = LOOPBACK_IN_EP_ADDR,
36		.bmAttributes = USB_DC_EP_BULK,
37		.wMaxPacketSize = sys_cpu_to_le16(CONFIG_LOOPBACK_BULK_EP_MPS),
38		.bInterval = 0x00,
39	},
40};

Endpoint configuration:

 1static struct usb_ep_cfg_data ep_cfg[] = {
 2	{
 3		.ep_cb = loopback_out_cb,
 4		.ep_addr = LOOPBACK_OUT_EP_ADDR,
 5	},
 6	{
 7		.ep_cb = loopback_in_cb,
 8		.ep_addr = LOOPBACK_IN_EP_ADDR,
 9	},
10};

USB Device configuration structure:

 1USBD_DEFINE_CFG_DATA(loopback_config) = {
 2	.usb_device_description = NULL,
 3	.interface_config = loopback_interface_config,
 4	.interface_descriptor = &loopback_cfg.if0,
 5	.cb_usb_status = loopback_status_cb,
 6	.interface = {
 7		.class_handler = NULL,
 8		.custom_handler = NULL,
 9		.vendor_handler = loopback_vendor_handler,
10	},
11	.num_endpoints = ARRAY_SIZE(ep_cfg),
12	.endpoint = ep_cfg,
13};

The vendor device requests are forwarded by the USB stack core driver to the class driver through the registered vendor handler.

For the loopback class driver, loopback_vendor_handler() processes the vendor requests:

 1static int loopback_vendor_handler(struct usb_setup_packet *setup,
 2				   int32_t *len, uint8_t **data)
 3{
 4	LOG_DBG("Class request: bRequest 0x%x bmRequestType 0x%x len %d",
 5		setup->bRequest, setup->bmRequestType, *len);
 6
 7	if (setup->RequestType.recipient != USB_REQTYPE_RECIPIENT_DEVICE) {
 8		return -ENOTSUP;
 9	}
10
11	if (usb_reqtype_is_to_device(setup) &&
12	    setup->bRequest == 0x5b) {
13		LOG_DBG("Host-to-Device, data %p", *data);
14		/*
15		 * Copy request data in loopback_buf buffer and reuse
16		 * it later in control device-to-host transfer.
17		 */
18		memcpy(loopback_buf, *data,
19		       MIN(sizeof(loopback_buf), setup->wLength));
20		return 0;
21	}
22
23	if ((usb_reqtype_is_to_host(setup)) &&
24	    (setup->bRequest == 0x5c)) {
25		LOG_DBG("Device-to-Host, wLength %d, data %p",
26			setup->wLength, *data);
27		*data = loopback_buf;
28		*len = MIN(sizeof(loopback_buf), setup->wLength);
29		return 0;
30	}
31
32	return -ENOTSUP;
33}

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

USB Vendor and Product identifiers

The USB Vendor ID for the Zephyr project is 0x2FE3. This USB Vendor ID must not be used when a vendor integrates Zephyr USB device support into its own product.

Each USB sample has its own unique Product ID. The USB maintainer, if one is assigned, or otherwise the Zephyr Technical Steering Committee, may allocate other USB Product IDs based on well-motivated and documented requests.

When adding a new sample, add a new entry in samples/subsys/usb/usb_pid.Kconfig and a Kconfig file inside your sample subdirectory. The following Product IDs are currently used:

  • CONFIG_USB_PID_CDC_ACM_SAMPLE

  • CONFIG_USB_PID_CDC_ACM_COMPOSITE_SAMPLE

  • CONFIG_USB_PID_HID_CDC_SAMPLE

  • CONFIG_USB_PID_CONSOLE_SAMPLE

  • CONFIG_USB_PID_DFU_SAMPLE

  • CONFIG_USB_PID_HID_SAMPLE

  • CONFIG_USB_PID_HID_MOUSE_SAMPLE

  • CONFIG_USB_PID_MASS_SAMPLE

  • CONFIG_USB_PID_TESTUSB_SAMPLE

  • CONFIG_USB_PID_WEBUSB_SAMPLE

  • CONFIG_USB_PID_BLE_HCI_H4_SAMPLE

The USB device descriptor field bcdDevice (Device Release Number) represents the Zephyr kernel major and minor versions as a binary coded decimal value.

API reference

There are two ways to transmit data, using the ‘low’ level read/write API or the ‘high’ level transfer API.

Low level API

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 write. When data is received, the registered endpoint callback is called. usb_read() should be used for retrieving the received data. 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).

High level API

The usb_transfer method can be used to transfer data to/from the host. The transfer API will automatically split the data transmission into one or more USB transaction(s), depending endpoint max packet size. The class driver does not have to implement endpoint callback and should set this callback to the generic usb_transfer_ep_callback.

group _usb_device_core_api

USB Device Core Layer API.

Defines

USB_TRANS_READ
USB_TRANS_WRITE
USB_TRANS_NO_ZLP

Typedefs

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

Callback function signature for the USB Endpoint status.

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

Callback function signature for class specific requests.

Function which handles Class specific requests corresponding to an interface number specified in the device descriptor table. 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.

typedef void (*usb_interface_config)(struct usb_desc_header *head, uint8_t bInterfaceNumber)

Function for interface runtime configuration.

typedef void (*usb_transfer_callback)(uint8_t ep, int tsize, void *priv)

Callback function signature for transfer completion.

Functions

int usb_set_config(const uint8_t *usb_descriptor)

Configure USB controller.

Function to configure USB controller. Configuration parameters must be valid or an error is returned

Parameters
  • usb_descriptor[in] USB descriptor table

Returns

0 on success, negative errno code on fail

int usb_deconfig(void)

Deconfigure USB controller.

This function returns the USB device to it’s initial state

Returns

0 on success, negative errno code on fail

int usb_enable(usb_dc_status_callback status_cb)

Enable the USB subsystem and associated hardware.

This function initializes the USB core subsystem and enables the corresponding hardware so that it can begin transmitting and receiving on the USB bus, as well as generating interrupts.

Class-specific initialization and registration must be performed by the user before invoking this, so that any data or events on the bus are processed correctly by the associated class handling code.

Parameters
  • status_cb[in] Callback registered by user to notify about USB device controller state.

Returns

0 on success, negative errno code on fail.

int usb_disable(void)

Disable the USB device.

Function to disable the USB device. Upon success, the specified USB interface is clock gated in hardware, it is no longer capable of generating interrupts.

Returns

0 on success, negative errno code on fail

int usb_write(uint8_t ep, const uint8_t *data, uint32_t data_len, uint32_t *bytes_ret)

Write data to the specified endpoint.

Function to write data to the specified endpoint. The supplied usb_ep_callback will be called when transmission is done.

Parameters
  • ep[in] Endpoint address corresponding to the one listed in the device configuration table

  • data[in] Pointer to data to write

  • data_len[in] Length of data requested to write. This may be zero for a zero length status packet.

  • bytes_ret[out] Bytes written to the EP FIFO. This value may be NULL if the application expects all bytes to be written

Returns

0 on success, negative errno code on fail

int usb_read(uint8_t ep, uint8_t *data, uint32_t max_data_len, uint32_t *ret_bytes)

Read data from the specified endpoint.

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.

Parameters
  • ep[in] Endpoint address corresponding to the one listed in the device configuration table

  • data[in] Pointer to data buffer to write to

  • max_data_len[in] Max length of data to read

  • ret_bytes[out] Number of bytes read. If data is NULL and max_data_len is 0 the number of bytes available for read is returned.

Returns

0 on success, negative errno code on fail

int usb_ep_set_stall(uint8_t ep)

Set STALL condition on the specified endpoint.

This function is called by USB device class handler code to set stall condition on endpoint.

Parameters
  • ep[in] Endpoint address corresponding to the one listed in the device configuration table

Returns

0 on success, negative errno code on fail

int usb_ep_clear_stall(uint8_t ep)

Clears STALL condition on the specified endpoint.

This function is called by USB device class handler code to clear stall condition on endpoint.

Parameters
  • ep[in] Endpoint address corresponding to the one listed in the device configuration table

Returns

0 on success, negative errno code on fail

int usb_ep_read_wait(uint8_t ep, uint8_t *data, uint32_t max_data_len, uint32_t *read_bytes)

Read data from the specified endpoint.

This is similar to usb_ep_read, the difference being that, it doesn’t clear the endpoint NAKs so that the consumer is not bogged down by further upcalls till he is done with the processing of the data. The caller should reactivate ep by invoking usb_ep_read_continue() do so.

Parameters
  • ep[in] Endpoint address corresponding to the one listed in the device configuration table

  • data[in] pointer to data buffer to write to

  • max_data_len[in] max length of data to read

  • read_bytes[out] Number of bytes read. If data is NULL and max_data_len is 0 the number of bytes available for read should be returned.

Returns

0 on success, negative errno code on fail.

int usb_ep_read_continue(uint8_t ep)

Continue reading data from the endpoint.

Clear the endpoint NAK and enable the endpoint to accept more data from the host. Usually called after usb_ep_read_wait() when the consumer is fine to accept more data. Thus these calls together acts as flow control mechanism.

Parameters
  • ep[in] Endpoint address corresponding to the one listed in the device configuration table

Returns

0 on success, negative errno code on fail.

void usb_transfer_ep_callback(uint8_t ep, enum usb_dc_ep_cb_status_code)

Transfer management endpoint callback.

If a USB class driver wants to use high-level transfer functions, driver needs to register this callback as usb endpoint callback.

int usb_transfer(uint8_t ep, uint8_t *data, size_t dlen, unsigned int flags, usb_transfer_callback cb, void *priv)

Start a transfer.

Start a usb transfer to/from the data buffer. This function is asynchronous and can be executed in IRQ context. The provided callback will be called on transfer completion (or error) in thread context.

Parameters
  • ep[in] Endpoint address corresponding to the one listed in the device configuration table

  • data[in] Pointer to data buffer to write-to/read-from

  • dlen[in] Size of data buffer

  • flags[in] Transfer flags (USB_TRANS_READ, USB_TRANS_WRITE…)

  • cb[in] Function called on transfer completion/failure

  • priv[in] Data passed back to the transfer completion callback

Returns

0 on success, negative errno code on fail.

int usb_transfer_sync(uint8_t ep, uint8_t *data, size_t dlen, unsigned int flags)

Start a transfer and block-wait for completion.

Synchronous version of usb_transfer, wait for transfer completion before returning.

Parameters
  • ep[in] Endpoint address corresponding to the one listed in the device configuration table

  • data[in] Pointer to data buffer to write-to/read-from

  • dlen[in] Size of data buffer

  • flags[in] Transfer flags

Returns

number of bytes transferred on success, negative errno code on fail.

void usb_cancel_transfer(uint8_t ep)

Cancel any ongoing transfer on the specified endpoint.

Parameters
  • ep[in] Endpoint address corresponding to the one listed in the device configuration table

void usb_cancel_transfers(void)

Cancel all ongoing transfers.

bool usb_transfer_is_busy(uint8_t ep)

Check that transfer is ongoing for the endpoint.

Parameters
  • ep[in] Endpoint address corresponding to the one listed in the device configuration table

Returns

true if transfer is ongoing, false otherwise.

int usb_wakeup_request(void)

Start the USB remote wakeup procedure.

Function to request a remote wakeup. This feature must be enabled in configuration, otherwise it will always return -ENOTSUP error.

Returns

0 on success, negative errno code on fail, i.e. when the bus is already active.

bool usb_get_remote_wakeup_status(void)

Get status of the USB remote wakeup feature.

Returns

true if remote wakeup has been enabled by the host, false otherwise.

struct usb_ep_cfg_data
#include <usb_device.h>

USB Endpoint Configuration.

This structure contains configuration for the endpoint.

Public Members

usb_ep_callback ep_cb

Callback function for notification of data received and available to application or transmit done, NULL if callback not required by application code

uint8_t ep_addr

The number associated with the EP in the device configuration structure IN EP = 0x80 | <endpoint number> OUT EP = 0x00 | <endpoint number>

struct usb_interface_cfg_data
#include <usb_device.h>

USB Interface Configuration.

This structure contains USB interface configuration.

Public Members

usb_request_handler class_handler

Handler for USB Class specific Control (EP 0) communications

usb_request_handler vendor_handler

Handler for USB Vendor specific commands

usb_request_handler 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. return 0 on success, -EINVAL if the request has not been handled by the custom handler and instead needs to be handled by the core USB stack. Any other error code to denote failure within the custom handler.

struct usb_cfg_data
#include <usb_device.h>

USB device configuration.

The Application instantiates this with given parameters added using the “usb_set_config” function. Once this function is called changes to this structure will result in undefined behavior. This structure may only be updated after calls to usb_deconfig

Public Members

const uint8_t *usb_device_description

USB device description, see http://www.beyondlogic.org/usbnutshell/usb5.shtml#DeviceDescriptors

void *interface_descriptor

Pointer to interface descriptor

usb_interface_config interface_config

Function for interface runtime configuration

void (*cb_usb_status)(struct usb_cfg_data *cfg, enum usb_dc_status_code cb_status, const uint8_t *param)

Callback to be notified on USB connection status change

struct usb_interface_cfg_data interface

USB interface (Class) handler and storage space

uint8_t num_endpoints

Number of individual endpoints in the device configuration

struct usb_ep_cfg_data *endpoint

Pointer to an array of endpoint structs of length equal to the number of EP associated with the device description, not including control endpoints