1
0
Fork 0
forked from nuttx/nuttx-update

NuttX: USB Composite and DFU drivers: Add support for Microsoft OS descriptors.

These Microsoft-only descriptors help in loading the correct driver on Windows.
They are especially helpful to give libusb access to a custom device without
having to manually configure/install WinUSB driver.

With this change DFU interface works automatically on
Windows 10 with dfu-util 0.9 and libusb 1.0.22. On Windows 7
it still appears to need driver installation.
This commit is contained in:
Petteri Aimonen 2018-11-02 18:12:07 +02:00
parent 1f8bd33a5d
commit a37c0c4cba
5 changed files with 313 additions and 3 deletions

View file

@ -190,6 +190,19 @@ config COMPOSITE_VERSIONNO
default 0x1010
---help---
Interface version number.
config COMPOSITE_MSFT_OS_DESCRIPTORS
bool "Add support for Microsoft OS Descriptors"
default n
---help---
Microsoft Windows cannot always automatically determine appropriate
drivers for different interfaces of a USB composite device. There is
a vendor-specific mechanism called "Microsoft OS Descriptors" that
allows the interface to provide further ID code to help with driver
loading. See https://msdn.microsoft.com/en-us/windows/hardware/gg463179
Enabling this feature in composite driver will pass these requests
onwards to the interface drivers.
endif
config PL2303
@ -724,6 +737,40 @@ menuconfig DFU
a host application to send DFU_DETACH request and to cause the device
to reboot into a bootloader mode.
if DFU
config DFU_MSFT_OS_DESCRIPTORS
bool "Microsoft OS descriptor support"
default n
depends on COMPOSITE_MSFT_OS_DESCRIPTORS
---help---
Enabling this option will cause the DFU driver to return "WINUSB" as
the compatible ID of the DFU interface. This will automatically load
the appropriate driver for use with e.g. libusb and dfu-util.
Note that as of 2018 there are some issues with libusb and
composite devices, you may need a patched version:
https://sourceforge.net/p/libusb/mailman/message/36304399/
config DFU_INTERFACE_NAME
string "DFU interface string"
default "DFU interface"
---help---
String to assign as a name for the DFU interface.
if DFU_MSFT_OS_DESCRIPTORS
config DFU_INTERFACE_GUID
string "DFU interface GUID"
default "{8FE6D4D7-49DD-41E7-9486-49AFC6BFE475}"
---help---
DeviceInterfaceGUID to use for DFU interface in Microsoft OS descriptors.
Actual value does not matter for libusb, but if using WinUSB API directly
you can request your device by this GUID.
endif # DFU_MSFT_OS_DESCRIPTORS
endif # DFU
menuconfig NET_CDCECM
bool "CDC-ECM Ethernet-over-USB"
default n

View file

@ -192,6 +192,90 @@ static int composite_classsetup(FAR struct composite_dev_s *priv,
return ret;
}
/****************************************************************************
* Name: composite_msftdescriptor
*
* Description:
* Assemble the Microsoft OS descriptor from the COMPATIBLE_ID's given
* in each device's composite_devdesc_s.
*
****************************************************************************/
#ifdef CONFIG_COMPOSITE_MSFT_OS_DESCRIPTORS
static int composite_msftdescriptor(FAR struct composite_dev_s *priv,
FAR struct usbdev_s *dev,
FAR const struct usb_ctrlreq_s *ctrl, FAR struct usbdev_req_s *ctrl_rsp, FAR bool *dispatched)
{
if (ctrl->index[0] == MSFTOSDESC_INDEX_FUNCTION)
{
/* Function descriptor is common to whole device */
int i;
FAR struct usb_msft_os_feature_desc_s *response = (FAR struct usb_msft_os_feature_desc_s*)ctrl_rsp->buf;
memset(response, 0, sizeof(*response));
for (i = 0; i < priv->ndevices; i++)
{
if (priv->device[i].compdesc.msft_compatible_id[0] != 0)
{
FAR struct usb_msft_os_function_desc_s *func = &response->function[response->count];
memset(func, 0, sizeof(*func));
func->firstif = priv->device[i].compdesc.devinfo.ifnobase;
func->nifs = priv->device[i].compdesc.devinfo.ninterfaces;
memcpy(func->compatible_id, priv->device[i].compdesc.msft_compatible_id, sizeof(func->compatible_id));
memcpy(func->sub_id, priv->device[i].compdesc.msft_sub_id, sizeof(func->sub_id));
response->count++;
}
}
if (response->count > 0)
{
size_t total_len = sizeof(struct usb_msft_os_feature_desc_s) + (response->count - 1) * sizeof(struct usb_msft_os_function_desc_s);
response->len[0] = (total_len >> 0) & 0xFF;
response->len[1] = (total_len >> 8) & 0xFF;
response->len[2] = (total_len >> 16) & 0xFF;
response->len[3] = (total_len >> 24) & 0xFF;
response->version[1] = 0x01;
response->index[0] = MSFTOSDESC_INDEX_FUNCTION;
return total_len;
}
else
{
return 0;
}
}
else if (ctrl->index[0] == MSFTOSDESC_INDEX_EXTPROP || ctrl->index[0] == ctrl->value[0])
{
/* Extended properties are per-interface, pass the request to subdevice.
* NOTE: The documentation in OS_Desc_Ext_Prop.docx seems a bit incorrect here,
* the interface is in ctrl->value low byte.
* Also WinUSB driver has limitation that index[0] will not be correct if
* trying to read descriptors using e.g. libusb xusb.exe.
*/
int i;
int ret = -ENOTSUP;
uint8_t interface = ctrl->value[0];
for (i = 0; i < priv->ndevices; i++)
{
if (interface >= priv->device[i].compdesc.devinfo.ifnobase &&
interface < (priv->device[i].compdesc.devinfo.ifnobase +
priv->device[i].compdesc.devinfo.ninterfaces))
{
ret = CLASS_SETUP(priv->device[i].dev, dev, ctrl, NULL, 0);
*dispatched = true;
break;
}
}
return ret;
}
else
{
return -ENOTSUP;
}
}
#endif
/****************************************************************************
* Name: composite_allocreq
*
@ -490,6 +574,20 @@ static int composite_setup(FAR struct usbdevclass_driver_s *driver,
{
ret = composite_mkstrdesc(strid, buf);
}
#ifdef CONFIG_COMPOSITE_MSFT_OS_DESCRIPTORS
else if (strid == USB_REQ_GETMSFTOSDESCRIPTOR)
{
/* Note: Windows has a habit of caching this response, so if you want to enable/disable
* it you'll usually need to change the device serial number afterwards. */
static const uint8_t msft_response[16] = {
'M',0,'S',0,'F',0,'T',0,'1',0,'0',0,'0',0,0xEE,0
};
buf->len = 18;
buf->type = USB_DESC_TYPE_STRING;
memcpy(buf->data, msft_response, 16);
ret = buf->len;
}
#endif
else
{
int i;
@ -573,6 +671,14 @@ static int composite_setup(FAR struct usbdevclass_driver_s *driver,
break;
}
}
#ifdef CONFIG_COMPOSITE_MSFT_OS_DESCRIPTORS
else if (ctrl->req == USB_REQ_GETMSFTOSDESCRIPTOR &&
(ctrl->type & USB_REQ_DIR_MASK) == USB_REQ_DIR_IN &&
(ctrl->type & USB_REQ_TYPE_MASK) == USB_REQ_TYPE_VENDOR)
{
ret = composite_msftdescriptor(priv, dev, ctrl, ctrlreq, &dispatched);
}
#endif
else
{
uint8_t recipient;

View file

@ -70,6 +70,12 @@
#define USB_REQ_DFU_DETACH 0
#define USB_REQ_DFU_GETSTATUS 3
#ifdef CONFIG_DFU_MSFT_OS_DESCRIPTORS
#define DFU_MAX_DESCRIPTOR_LEN 256
#else
#define DFU_MAX_DESCRIPTOR_LEN sizeof(struct dfu_cfgdesc_s)
#endif
/****************************************************************************
* Private Types
****************************************************************************/
@ -232,15 +238,91 @@ static int16_t usbclass_mkcfgdesc(FAR uint8_t *buf,
*dest = g_dfu_cfgdesc;
dest->ifdesc.ifno += devinfo->ifnobase;
dest->ifdesc.iif = devinfo->strbase;
return sizeof(g_dfu_cfgdesc);
}
static int convert_to_utf16(FAR uint8_t *dest, FAR const char *src)
{
int bytes = 0;
while (*src)
{
*dest++ = *src++;
*dest++ = 0x00;
bytes += 2;
}
return bytes;
}
static int usbclass_mkstrdesc(uint8_t id, FAR struct usb_strdesc_s *strdesc)
{
return -EINVAL;
FAR const char *str;
if (id == 0)
{
str = CONFIG_DFU_INTERFACE_NAME;
}
else
{
return -EINVAL;
}
strdesc->len = 2 + convert_to_utf16(strdesc->data, str);
strdesc->type = USB_DESC_TYPE_STRING;
return strdesc->len;
}
#ifdef CONFIG_DFU_MSFT_OS_DESCRIPTORS
static int dfu_make_msft_extprop_desc(FAR uint8_t *buf)
{
FAR const char *propname = "DeviceInterfaceGUIDs";
FAR const char *propval = CONFIG_DFU_INTERFACE_GUID;
FAR struct usb_msft_os_extprop_hdr_s *hdr = (FAR struct usb_msft_os_extprop_hdr_s*)buf;
FAR uint8_t *payload = buf + sizeof(struct usb_msft_os_extprop_hdr_s);
int namelen, valuelen, proplen, totallen;
namelen = strlen(propname) * 2 + 2;
valuelen = strlen(propval) * 2 + 4;
proplen = 14 + namelen + valuelen;
totallen = sizeof(struct usb_msft_os_extprop_hdr_s) + proplen;
memset(buf, 0, totallen);
hdr->len[0] = LSBYTE(totallen);
hdr->len[1] = MSBYTE(totallen);
hdr->version[1] = 0x01;
hdr->index[0] = MSFTOSDESC_INDEX_EXTPROP;
hdr->count[0] = 1;
*payload++ = LSBYTE(proplen); // dwSize
*payload++ = MSBYTE(proplen);
*payload++ = 0;
*payload++ = 0;
*payload++ = 7; // dwPropertyDataType = REG_MULTI_SZ
*payload++ = 0;
*payload++ = 0;
*payload++ = 0;
*payload++ = LSBYTE(namelen); // wPropertyNameLength
*payload++ = MSBYTE(namelen);
payload += convert_to_utf16(payload, propname); // bPropertyName
*payload++ = 0; // Null terminator
*payload++ = 0;
*payload++= LSBYTE(valuelen); // dwPropertyDataLength
*payload++= MSBYTE(valuelen);
*payload++ = 0;
*payload++ = 0;
payload += convert_to_utf16(payload, propval);
*payload++ = 0; // Null terminator for string
*payload++ = 0;
*payload++ = 0; // Null terminator for array
*payload++ = 0;
return totallen;
}
#endif
static void dfu_workqueue_callback(void *arg)
{
usbdev_dfu_activate_bootloader();
@ -312,6 +394,12 @@ static int usbclass_setup(FAR struct usbdevclass_driver_s *driver,
ret = sizeof(struct dfu_getstatus_response_s);
}
}
#ifdef CONFIG_DFU_MSFT_OS_DESCRIPTORS
else if (ctrl->req == USB_REQ_GETMSFTOSDESCRIPTOR)
{
ret = dfu_make_msft_extprop_desc(ctrlreq->buf);
}
#endif
/* Respond to the setup command if data was returned. On an error return
* value (ret < 0), the USB driver will stall.
@ -337,7 +425,7 @@ static int usbclass_bind(FAR struct usbdevclass_driver_s *driver,
{
FAR struct dfu_driver_s *priv = (FAR struct dfu_driver_s *)driver;
priv->ctrlreq = usbclass_allocreq(dev->ep0, sizeof(g_dfu_cfgdesc));
priv->ctrlreq = usbclass_allocreq(dev->ep0, DFU_MAX_DESCRIPTOR_LEN);
if (priv->ctrlreq == NULL)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_ALLOCCTRLREQ), 0);
@ -427,8 +515,12 @@ void usbdev_dfu_get_composite_devdesc(struct composite_devdesc_s *dev)
dev->configid = 0;
dev->cfgdescsize = sizeof(g_dfu_cfgdesc);
dev->devinfo.ninterfaces = 1;
dev->devinfo.nstrings = 0;
dev->devinfo.nstrings = 1;
dev->devinfo.nendpoints = 0;
#ifdef CONFIG_DFU_MSFT_OS_DESCRIPTORS
memcpy(dev->msft_compatible_id, "WINUSB", 6);
#endif
}

View file

@ -259,6 +259,11 @@
#define USB_MAX_DEVICES (127)
/* Microsoft OS Descriptor specific values */
#define USB_REQ_GETMSFTOSDESCRIPTOR (0xEE)
#define MSFTOSDESC_INDEX_FUNCTION 4
#define MSFTOSDESC_INDEX_EXTPROP 5
/************************************************************************************
* Public Types
************************************************************************************/
@ -417,6 +422,61 @@ struct usb_iaddesc_s
};
#define USB_SIZEOF_IADDESC 8
/* Microsoft OS function descriptor.
* This can be used to request a specific driver (such as WINUSB) to be loaded
* on Windows. Unlike other descriptors, it is requested by a special request
* USB_REQ_GETMSFTOSDESCRIPTOR.
* More details: https://msdn.microsoft.com/en-us/windows/hardware/gg463179
* And excellent explanation: https://github.com/pbatard/libwdi/wiki/WCID-Devices
*
* The device will have exactly one "Extended Compat ID Feature Descriptor",
* which may contain multiple "Function Descriptors" associated with different
* interfaces.
*/
struct usb_msft_os_function_desc_s
{
uint8_t firstif; /* Index of first associated interface */
uint8_t nifs; /* Number of associated interfaces */
uint8_t compatible_id[8]; /* COMPATIBLE_ID of the driver to load */
uint8_t sub_id[8]; /* SUB_COMPATIBLE_ID of the driver */
uint8_t reserved[6];
};
struct usb_msft_os_feature_desc_s
{
uint8_t len[4]; /* Descriptor length */
uint8_t version[2]; /* Descriptor syntax version, 0x0100 */
uint8_t index[2]; /* Set to 4 for "extended compat ID descriptors" */
uint8_t count; /* Number of function sections */
uint8_t reserved[7];
struct usb_msft_os_function_desc_s function[1];
};
/* Microsoft OS extended property descriptor.
* This can be used to set specific registry values, such as interface GUID for
* a device. It is requested per-interface by special request USB_REQ_GETMSFTOSDESCRIPTOR.
*
* The interface will have one extended properties descriptor, which may contain
* multiple properties inside it.
*/
struct usb_msft_os_extprop_hdr_s
{
uint8_t len[4]; /* Descriptor length */
uint8_t version[2]; /* Descriptor syntax version, 0x0100 */
uint8_t index[2]; /* Set to 5 for "extended property descriptors" */
uint8_t count[2]; /* Number of property sections */
/* The properties are appended after the header and follow this format:
* uint8_t prop_len[4];
* uint8_t data_type[4];
* uint8_t name_len[2];
* uint8_t name[name_len];
* uint8_t data_len[4];
* uint8_t data[data_len];
*/
};
/************************************************************************************
* Public Data
************************************************************************************/

View file

@ -234,6 +234,11 @@ struct composite_devdesc_s
int cfgdescsize; /* The size of the config descriptor */
int minor;
#ifdef CONFIG_COMPOSITE_MSFT_OS_DESCRIPTORS
uint8_t msft_compatible_id[8];
uint8_t msft_sub_id[8];
#endif
struct usbdev_devinfo_s devinfo;
};
#endif