Hi, I've just started the learning process on QEMU, and am trying to create a USB webcam virtual device (targetting the Linux UVC driver). So far, I'm able to create the device, plug it in (thanks to those involved in qdev), and inspect the descriptor.
The problem is building the descriptor, and particularly how to do an Interface Association Descriptor. This is covered in Section 9.6.4 of the USB 3.0 spec, but there is a better description in the Intel IAD whitepaper (www.usb.org/developers/whitepapers/iadclasscode_r10.pdf). The short version is that IAD is an extra descriptor type that appears before a group (two or more) interface descriptors, that explains which interface descriptors make up a virtual device. So it could look like: Config Desc IAD#0 Iface#0 Iface#1 Iface#2 IAD#1 Iface#3 Iface#4 [Check the diagram in the Intel IAD whitepaper if that makes no sense] I've managed to make the USB descriptor code produce my descriptor, but the change has (at least) two problems I'd appreciate suggestions on how to fix: 1. I can only do the simple case where there is 0 or 1 IADs. 2. The change impacts on every USB configuration descriptor, even though IAD isn't very common. Here is what I'm currently doing in any case (not complete - I haven't converted over all the existing device to have the .niad = 0 bit, since I got a bad feeling about it. Not sure what the problem is - it just feels a bit wrong). Suggestions? Brad --- a/hw/usb-desc.c +++ b/hw/usb-desc.c @@ -91,6 +91,15 @@ int usb_desc_config(const USBDescConfig *conf, uint8_t *dest, size_t len) dest[0x08] = conf->bMaxPower; wTotalLength += bLength; + /* handle Interface Association Descriptor, if present */ + if (conf->niad != 0) { + rc = usb_desc_iad(conf->iad, dest + wTotalLength, len - wTotalLength); + if (rc < 0) { + return rc; + } + wTotalLength += rc; + } + count = conf->nif ? conf->nif : conf->bNumInterfaces; for (i = 0; i < count; i++) { rc = usb_desc_iface(conf->ifs + i, dest + wTotalLength, len - wTotalLength); @@ -105,6 +114,26 @@ int usb_desc_config(const USBDescConfig *conf, uint8_t *dest, size_t len) return wTotalLength; } +int usb_desc_iad(const USBDescIfaceAssoc *iad, uint8_t *dest, size_t len) +{ + uint8_t bLength = 0x08; + + if (len < bLength) { + return -1; + } + + dest[0x00] = bLength; + dest[0x01] = USB_DT_INTERFACE_ASSOC; + dest[0x02] = iad->bFirstInterface; + dest[0x03] = iad->bInterfaceCount; + dest[0x04] = iad->bFunctionClass; + dest[0x05] = iad->bFunctionSubClass; + dest[0x06] = iad->bFunctionProtocol; + dest[0x07] = iad->iFunction; + + return bLength; +} + int usb_desc_iface(const USBDescIface *iface, uint8_t *dest, size_t len) { uint8_t bLength = 0x09; diff --git a/hw/usb-desc.h b/hw/usb-desc.h index ac734ab..cf5f794 100644 --- a/hw/usb-desc.h +++ b/hw/usb-desc.h @@ -30,10 +30,22 @@ struct USBDescConfig { uint8_t bmAttributes; uint8_t bMaxPower; + uint8_t niad; /* 0 or 1 for now */ + const USBDescIfaceAssoc *iad; + uint8_t nif; const USBDescIface *ifs; }; +struct USBDescIfaceAssoc { + uint8_t bFirstInterface; + uint8_t bInterfaceCount; + uint8_t bFunctionClass; + uint8_t bFunctionSubClass; + uint8_t bFunctionProtocol; + uint8_t iFunction; +}; + struct USBDescIface { uint8_t bInterfaceNumber; uint8_t bAlternateSetting; @@ -75,6 +87,7 @@ int usb_desc_device(const USBDescID *id, const USBDescDevice *dev, int usb_desc_device_qualifier(const USBDescDevice *dev, uint8_t *dest, size_t len); int usb_desc_config(const USBDescConfig *conf, uint8_t *dest, size_t len); +int usb_desc_iad(const USBDescIfaceAssoc *iad, uint8_t *dest, size_t len); int usb_desc_iface(const USBDescIface *iface, uint8_t *dest, size_t len); int usb_desc_endpoint(const USBDescEndpoint *ep, uint8_t *dest, size_t len); int usb_desc_other(const USBDescOther *desc, uint8_t *dest, size_t len); diff --git a/hw/usb-hid.c b/hw/usb-hid.c index c25362c..acbcddc 100644 --- a/hw/usb-hid.c +++ b/hw/usb-hid.c @@ -211,6 +211,7 @@ static const USBDescDevice desc_device_mouse = { .iConfiguration = STR_CONFIG_MOUSE, .bmAttributes = 0xa0, .bMaxPower = 50, + .niad = 0, .ifs = &desc_iface_mouse, }, }, @@ -227,6 +228,7 @@ static const USBDescDevice desc_device_tablet = { .iConfiguration = STR_CONFIG_TABLET, .bmAttributes = 0xa0, .bMaxPower = 50, + .niad = 0, .ifs = &desc_iface_tablet, }, }, @@ -243,6 +245,7 @@ static const USBDescDevice desc_device_keyboard = { .iConfiguration = STR_CONFIG_KEYBOARD, .bmAttributes = 0xa0, .bMaxPower = 50, + .niad = 0, .ifs = &desc_iface_keyboard, }, }, diff --git a/hw/usb-hub.c b/hw/usb-hub.c index 3dd31ba..bc06a01 100644 --- a/hw/usb-hub.c +++ b/hw/usb-hub.c @@ -119,6 +119,7 @@ static const USBDescDevice desc_device_hub = { .bNumInterfaces = 1, .bConfigurationValue = 1, .bmAttributes = 0xe0, + .niad = 0, .ifs = &desc_iface_hub, }, }, diff --git a/hw/usb.h b/hw/usb.h index d3d755d..b1e8879 100644 --- a/hw/usb.h +++ b/hw/usb.h @@ -124,6 +124,7 @@ #define USB_DT_ENDPOINT 0x05 #define USB_DT_DEVICE_QUALIFIER 0x06 #define USB_DT_OTHER_SPEED_CONFIG 0x07 +#define USB_DT_INTERFACE_ASSOC 0x0B #define USB_ENDPOINT_XFER_CONTROL 0 #define USB_ENDPOINT_XFER_ISOC 1 @@ -140,6 +141,7 @@ typedef struct USBDesc USBDesc; typedef struct USBDescID USBDescID; typedef struct USBDescDevice USBDescDevice; typedef struct USBDescConfig USBDescConfig; +typedef struct USBDescIfaceAssoc USBDescIfaceAssoc; typedef struct USBDescIface USBDescIface; typedef struct USBDescEndpoint USBDescEndpoint; typedef struct USBDescOther USBDescOther;