USBD ROM Stack  2.0
ROM based USB device stack
Defining USB Descriptors

A USB device provides information about itself in data structures called USB descriptors. This section provides information about various descriptors that a USB device should provide to host during enumeration process.

Defining USB Descriptors

The host obtains descriptors from an attached device by sending various standard control requests (GET_DESCRIPTOR requests) to the default endpoint. Those requests specify the type of descriptor to retrieve. In response to such requests, the device sends descriptors that include information about the device, its configurations, interfaces and the related endpoints. Device descriptors contain information about the whole device. Configuration descriptors contain information about each device configuration. String descriptors contain Unicode text strings. The USB ROM stack handles all these standard requests eliminating the complexity from user application, as long as the user application provides the proper descriptor arrays to stack initialization routine.

Every USB device exposes a device descriptor that indicates the device's class information, vendor and product identifiers, and number of configurations. Each configuration exposes its configuration descriptor that indicates number of interfaces and power characteristics. Each interface exposes an interface descriptor for each of its alternate settings that contain information about the class and the number of endpoints. Each endpoint within each interface exposes endpoint descriptors that indicate the endpoint type and the maximum packet size. User application should provide these descriptors to the ROM stack for proper handling of standard requests.

Defining USB Device Descriptor

The device descriptor contains information about a USB device as a whole. The pointer to the memory containing device descriptor should be provided by application through device_desc field of USB_CORE_DESCS_T structure, whose address is passed to stack initialization routine usbapi->hw->init(). The user application can create an instance of _USB_DEVICE_DESCRIPTOR structure in global scope (defined in memory accessible by USB bus master) and pass the address of the instance in device_desc field. Or the application can define the descriptor as character array as shown below:

/* USB Standard Device Descriptor */
ALIGNED(4) const uint8_t USB_DeviceDescriptor[] =
{
USB_DEVICE_DESC_SIZE, /* bLength */
USB_DEVICE_DESCRIPTOR_TYPE, /* bDescriptorType */
WBVAL(0x0200), /* 2.00 */ /* bcdUSB */
0x00, /* bDeviceClass */
0x00, /* bDeviceSubClass */
0x00, /* bDeviceProtocol */
USB_MAX_PACKET0, /* bMaxPacketSize */
WBVAL(0x1FC9), /* idVendor: Vendor ID (assigned by the USB-IF) */
WBVAL(0x0001), /* idProduct Product ID (assigned by the manufacturer)*/
WBVAL(0x0100), /* 1.00 */ /* bcdDevice */
0x01, /* iManufacturer */
0x02, /* iProduct */
0x03, /* iSerialNumber */
0x01 /* bNumConfigurations */
};

Defining USB Device Qualifier Descriptor

The device_qualifier descriptor describes information about a high-speed capable device that would change if the device were operating at the other speed (see _USB_DEVICE_QUALIFIER_DESCRIPTOR structure). For example, if the device is currently operating at full-speed, the device_qualifier returns information about how it would operate at high-speed and vice-versa. The pointer to the memory containing device qualifier descriptor should be provided by application through device_qualifier field of USB_CORE_DESCS_T structure, whose address is passed to stack initialization routine usbapi->hw->init().

Note
If application is implementing full-speed only device this field should be set to 0. And also the field high_speed_desc and full_speed_desc should point to full-speed configuration descriptor array.
/* USB Device Qualifier */
ALIGNED(4) const uint8_t USB_DeviceQualifier[] = {
USB_DEVICE_QUALI_SIZE, /* bLength */
USB_DEVICE_QUALIFIER_DESCRIPTOR_TYPE, /* bDescriptorType */
WBVAL(0x0200), /* 2.00 */ /* bcdUSB */
0x00, /* bDeviceClass */
0x00, /* bDeviceSubClass */
0x00, /* bDeviceProtocol */
USB_MAX_PACKET0, /* bMaxPacketSize0 */
0x01, /* bNumOtherSpeedConfigurations */
0x00 /* bReserved */
};

Defining USB Configuration Descriptors Array

A USB device exposes its capabilities in the form of a series of interfaces called a USB configuration. A USB configuration is described in a configuration descriptor (see _USB_CONFIGURATION_DESCRIPTOR structure). A configuration descriptor contains information about the configuration and its interfaces, alternate settings, and their endpoints. Each interface descriptor or alternate setting is described in a _USB_INTERFACE_DESCRIPTOR structure. In a configuration, each interface descriptor is followed in memory by all of the endpoint descriptors for the interface and alternate setting. Each endpoint descriptor is stored in a _USB_ENDPOINT_DESCRIPTOR structure.

The ROM stack assumes that all the descriptors associated with the configuration are arranged as a consecutive byte array in memory. The address to this array is passed to the stack through full_speed_desc and high_speed_desc field of USB_CORE_DESCS_T structure, whose address is passed to usbapi->hw->init() routine. The following diagram illustrates how configuration information should be laid out in memory.


config_des.png


The following example shows the configuration descriptor for the USB Mass storage class device:

/* All Descriptors (Configuration, Interface, Endpoint, Class, Vendor */
ALIGNED(4) uint8_t USB_FsConfigDescriptor[] = {
/* Configuration 1 */
USB_CONFIGUARTION_DESC_SIZE, /* bLength. Set to 0x09. */
USB_CONFIGURATION_DESCRIPTOR_TYPE, /* bDescriptorType. Set to 0x02.*/
WBVAL( /* wTotalLength: contains total length of data returned for this configuration. Includes the combined length of all descriptors (configuration, interface, endpoint, and class- or vendor-specific) returned for this configuration. */
1*USB_CONFIGUARTION_DESC_SIZE +
1*USB_INTERFACE_DESC_SIZE +
2*USB_ENDPOINT_DESC_SIZE
),
0x01, /* bNumInterfaces: total number of interfaces the device supports */
0x01, /* bConfigurationValue: This field indicates the index for the configuration defined in the firmware of the device. The host functional driver uses that index value to select an active configuration. */
0x00, /* iConfiguration */
USB_CONFIG_SELF_POWERED, /* bmAttributes: contains a bitmask that indicates whether the configuration supports the remote wake-up feature, and whether the device is bus-powered or self-powered. */
USB_CONFIG_POWER_MA(100), /* bMaxPower: specifies the maximum power (in milliamp units) that the device can draw from the host, when the device is bus-powered. */
/* Interface 0, Alternate Setting 0, MSC Class */
USB_INTERFACE_DESC_SIZE, /* bLength */
USB_INTERFACE_DESCRIPTOR_TYPE, /* bDescriptorType */
0x00, /* bInterfaceNumber */
0x00, /* bAlternateSetting */
0x02, /* bNumEndpoints */
USB_DEVICE_CLASS_STORAGE, /* bInterfaceClass */
MSC_SUBCLASS_SCSI, /* bInterfaceSubClass */
MSC_PROTOCOL_BULK_ONLY, /* bInterfaceProtocol */
0x04, /* iInterface: Index to string descriptor containing interface description. */
/* Bulk In Endpoint */
USB_ENDPOINT_DESC_SIZE, /* bLength */
USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType */
MSC_EP_IN, /* bEndpointAddress */
USB_ENDPOINT_TYPE_BULK, /* bmAttributes */
WBVAL(64), /* wMaxPacketSize */
0, /* bInterval */
/* Bulk Out Endpoint */
USB_ENDPOINT_DESC_SIZE, /* bLength */
USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType */
MSC_EP_OUT, /* bEndpointAddress */
USB_ENDPOINT_TYPE_BULK, /* bmAttributes */
WBVAL(64), /* wMaxPacketSize */
0, /* bInterval */
/* Terminator */
0 /* bLength: Indicates to ROM stack the end of descriptor array. */
};
Note
For devices implementing multiple configurations the second configuration descriptors should follow immediately after the first configuration descriptors with NULL descriptor at the end of the second configuration array.

Defining USB Other Speed Configuration

The other_speed_configuration describes a configuration of a high-speed capable device if it were operating at its other possible speed. The structure _USB_OTHER_SPEED_CONFIGURATION is identical to a configuration descriptor. Except the bDescriptorType field which is set to 0x07. To optimize application data memory usage the ROM stack reuses the meory pointed by full_speed_desc and high_speed_desc fields when reporting other speed configuration descriptor.

Note
The stack updates bDescriptorType field before sending the descriptor data to host. Hence the applications should have the configuration descriptors in read-write memory area such as ISRAM and not declare the descriptor array with const qualifier.

Defining USB Binary Device Object Store (BOS) Descriptor

The USB 3.0 and USB 2.0 LPM specifications define a new USB descriptor called the Binary Device Object Store (BOS). The BOS descriptor returns a set of device-level capability descriptors for the USB device. For a USB device, which reports a bcdUSB value greater than 0x0200 in their device descriptor, the host sends Get BOS Descriptor request to the device. The device should prepare for a BOS descriptor once LPM is enabled.

The BOS descriptor contains:

Defining USB Device Capability Descriptor

For a USB device, which reports a bcdUSB value greater than 0x0200 in their device descriptor, the host sends Get BOS Descriptor request to the device. The device should report both BOS descriptor and Device Capability Descriptor(s) information back to the host.

The Device Capability descriptor contains:

Defining USB String Descriptor

Device, configuration, and interface descriptors may contain references to string descriptors. String descriptors are referenced by their one-based index number. A string descriptor contains one or more Unicode strings; each string is a translation of the others into another language.

A string descriptor contains:

String index zero for all languages should return a string descriptor that contains an array of two-byte LANGID codes supported by the device. Current implementation of ROM stack assumes single language support. For applications using US English strings this descriptor should be:

0x04, /* bLength */
0x03, /* bDescriptorType */
WBVAL(0x0409), /* wLANGID: US English */

The ROM stack assumes that all USB strings referenced in various descriptors are provided to stack as a single character array containing multiple string descriptors. ROM stack traverses to the next descriptor in array by adding the value of bLength field to current index. Hence it is important to construct this descriptor array properly with bLength fields reflecting the exact size of its string descriptor.

/* USB String Descriptor (optional) */
ALIGNED(4) uint8_t USB_StringDescriptor[] =
{
/* Index 0x00: LANGID Codes */
0x04, /* bLength */
USB_STRING_DESCRIPTOR_TYPE, /* bDescriptorType */
WBVAL(0x0409), /* wLANGID : US English */
/* Index 0x01: Manufacturer */
(2*2 + 2), /* bLength (3 Char + Type + length) */
USB_STRING_DESCRIPTOR_TYPE, /* bDescriptorType */
'N', 0,
'X', 0,
'P', 0,
/* Index 0x02: Product */
(20*2 + 2), /* bLength */
USB_STRING_DESCRIPTOR_TYPE, /* bDescriptorType */
'N', 0,
'X', 0,
'P', 0,
' ', 0,
/* Index 0x03: Serial Number */
(13*2 + 2), /* bLength (13 Char + Type + length)*/
USB_STRING_DESCRIPTOR_TYPE, /* bDescriptorType */
'A', 0,
'B', 0,
'C', 0,
'D', 0,
'1', 0,
'2', 0,
'3', 0,
'4', 0,
'5', 0,
'6', 0,
'7', 0,
'8', 0,
'9', 0,
};