USB device stack

USB Vendor and Product identifiers

The USB Vendor ID for the Zephyr project is 0x2FE3. The default USB Product ID for the Zephyr project is 0x100. The USB bcdDevice Device Release Number represents the Zephyr kernel major and minor versions as a binary coded decimal value. When a vendor integrates the Zephyr USB subsystem into a product, the vendor must use the USB Vendor and Product ID assigned to them. A vendor integrating the Zephyr USB subsystem in a product must not use the Vendor ID of the Zephyr project.

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.

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.

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.

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 USB loopback application:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
struct usb_loopback_config {
	struct usb_if_descriptor if0;
	struct usb_ep_descriptor if0_out_ep;
	struct usb_ep_descriptor if0_in_ep;
} __packed;

USBD_CLASS_DESCR_DEFINE(primary) struct usb_loopback_config loopback_cfg = {
	/* Interface descriptor 0 */
	.if0 = {
		.bLength = sizeof(struct usb_if_descriptor),
		.bDescriptorType = USB_INTERFACE_DESC,
		.bInterfaceNumber = 0,
		.bAlternateSetting = 0,
		.bNumEndpoints = 2,
		.bInterfaceClass = CUSTOM_CLASS,
		.bInterfaceSubClass = 0,
		.bInterfaceProtocol = 0,
		.iInterface = 0,
	},

	/* Data Endpoint OUT */
	.if0_out_ep = {
		.bLength = sizeof(struct usb_ep_descriptor),
		.bDescriptorType = USB_ENDPOINT_DESC,
		.bEndpointAddress = LOOPBACK_OUT_EP_ADDR,
		.bmAttributes = USB_DC_EP_BULK,
		.wMaxPacketSize = sys_cpu_to_le16(CONFIG_LOOPBACK_BULK_EP_MPS),
		.bInterval = 0x00,
	},

	/* Data Endpoint IN */
	.if0_in_ep = {
		.bLength = sizeof(struct usb_ep_descriptor),
		.bDescriptorType = USB_ENDPOINT_DESC,
		.bEndpointAddress = LOOPBACK_IN_EP_ADDR,
		.bmAttributes = USB_DC_EP_BULK,
		.wMaxPacketSize = sys_cpu_to_le16(CONFIG_LOOPBACK_BULK_EP_MPS),
		.bInterval = 0x00,
	},
};

Endpoint configuration:

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

USB Device configuration structure:

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

For the Composite USB Device configuration is done by composite layer, otherwise:

1
2
3
4
5
	ret = usb_set_config(&loopback_config);
	if (ret < 0) {
		LOG_ERR("Failed to config USB");
		return ret;
	}

To enable the USB device for host/device connection:

1
2
3
4
5
	ret = usb_enable(&loopback_config);
	if (ret < 0) {
		LOG_ERR("Failed to enable USB");
		return ret;
	}

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
static int loopback_vendor_handler(struct usb_setup_packet *setup,
				   s32_t *len, u8_t **data)
{
	LOG_DBG("Class request: bRequest 0x%x bmRequestType 0x%x len %d",
		setup->bRequest, setup->bmRequestType, *len);

	if (REQTYPE_GET_RECIP(setup->bmRequestType) != REQTYPE_RECIP_DEVICE) {
		return -ENOTSUP;
	}

	if (REQTYPE_GET_DIR(setup->bmRequestType) == REQTYPE_DIR_TO_DEVICE &&
	    setup->bRequest == 0x5b) {
		LOG_DBG("Host-to-Device, data %p", *data);
		return 0;
	}

	if ((REQTYPE_GET_DIR(setup->bmRequestType) == REQTYPE_DIR_TO_HOST) &&
	    (setup->bRequest == 0x5c)) {
		if (setup->wLength > sizeof(loopback_buf)) {
			return -ENOTSUP;
		}

		*data = loopback_buf;
		*len = setup->wLength;
		LOG_DBG("Device-to-Host, wLength %d, data %p",
			setup->wLength, *data);
		return 0;
	}

	return -ENOTSUP;
}

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

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.

Further reading

More information on the stack and its usage can be found in the following subsections: