Reply
Highlighted
Posts: 66
Registered: ‎09-17-2012

Using Microsoft OS Descriptors with emlib/em_usb

This is not a question, more like a tutorial. I recently figured out something smartish, and figured I'd share with the community.

 

For every new version of Windows, installing unsigned drivers becomes more difficult. However, in many cases, all you want to do is install a driver already provided by Microsoft, and you just need to tell Windows which driver to use. This means you have to write a .inf file, and, unfortunately, this file also needs to be signed.

However, in many cases there is a way around this, which skips the .inf file entirely. Not only does this get rid of warnings or even errors during the driver installation; the whole process becomes completely automatic.

The magical key is Microsoft OS Descriptors (MOD) – a proprietary extension to standard USB descriptors. By adding these extra descriptors to your device, you can inform windows about so called “extended compatibility”, meaning that your device is compatible with a Windows driver which does not conform to a standard USB device class. Arguably the most useful of these extended compatibilities is WinUSB, which is a completely generic USB driver. With WinUSB, your Windows application can issue all kinds of standard requests to the device through standard libraries.

The community has dubbed a device that complies with the MOD protocol a WCID-compliant device, for Windows Compatible ID. The official documentation for MOD is found here: https://msdn.microsoft.com/en-us/windows/hardware/gg463179.aspx

Fortunately, the documentation consists mostly of examples, so it is easy to understand. Unfortunately, the documentation consists mostly of examples, so there is no list of supported extended compatibilities or extended feature descriptors. I have not been able to find such a list.

Now, to the case, how to implement WCID support in an EFM32-device using emlib.

The first thing Windows does in order to detect whether this device is WCID-compliant, is to attempt to read a standard USB string descriptor at index 0xEE. Only if this string descriptor exists, and has the correct signature, will windows continue looking for MODs.

This is the definition of this string descriptor (add to descriptors.c):

STATIC_CONST_STRING_DESC( iWindowsOsDescriptor, 'M','S','F','T','1','0','0', YOUR_VENDOR_CODE);

YOUR_VENDOR_CODE should be some number between 1 and 255.

Then, add this string descriptor last in the string descriptor structure USBDESC_strings (still in descriptors.c):

static const void * const USBDESC_strings[] =
{
  &langID,
  &iManufacturer,
  &iProduct,
  &iSerialNumber,
  &iYourInterfaceStringDescriptor,
  /** Other string descriptors may follow here */
  &iWindowsOsDescriptor //Always keep this last in this stucture. It is treated specially in GetDescriptor()
  	  	        //since it is descriptor number 0xEE by microsoft's definition
};

Emlib automatically gives the strings idices from 0 to numberOfStrings-1. In order to make the index of iWindowsOsDescriptor 0xEE, we need to make a slight change in em_usbdch9.c (this is the only change we will make to the actual library). The change is made to the function GetDescriptor():

case USB_STRING_DESCRIPTOR :
      if ( index < pDev->numberOfStrings )
      {
        USB_StringDescriptor_TypeDef *s;
        s = ((USB_StringDescriptor_TypeDef**)pDev->stringDescriptors)[index];

        data   = s;
        length = s->len;
      }
/*********************** Start Edit *****************************/
      else if (index == 0xEE) //0xEE is the predefined number of the windows os string descriptor
      {
          USB_StringDescriptor_TypeDef *s;
          //OS string descriptor shall always be the last of the strings, therefore numberOfStrings
          s = ((USB_StringDescriptor_TypeDef**)pDev->stringDescriptors)[pDev->numberOfStrings];
          data   = s;
          length = s->len;
      }
/*********************** End Edit *****************************/

Now Windows will read this string descriptor and determine that this device is WCID-compatible. Next, Windows will issue a vendor specific setup request using YOUR_VENDOR_CODE as the request value. Windows expects to get an “extended compatibility ID descriptor” from this request. The structure of this descriptor – which you should add to the file where your setup command callback (the one referenced from callbacks in descriptors.c) resides – is as follows:

#define EXTENDED_COMPATIBILITY_ID 4
#define EXT_COMP_DESC_SIZE sizeof(struct t_ext_comp_desc)
#define NUM_INTERFACES_WITH_EXTENDED_COMPATIBILITY 1

struct extended_compatibility_interface_descriptor {
	uint8_t firstInterfaceNumber; /**< Interface number for which an extended compatibility feature descriptor is defined */
	uint8_t reserved1;
	char compatibleID[8]; /**< String describing the compatible id */
	char subCompatibleID[8]; /**< String describing the sub compatible id */
	uint8_t reserved2[6];
};

const struct t_ext_comp_desc{
	uint32_t length; /**< Size of this struct = 16 + bCount*24 */
	uint16_t bcdVersion; /**< 1.00 -> 0x0100 */
	uint16_t index;	/**< Command index - 0x04 for extended compatibility id */
	uint8_t count;		/**< Number of interfaces for which an extended compatibility feature descriptor is defined */
	uint8_t reserved1[7];
	struct extended_compatibility_interface_descriptor ecid[NUM_INTERFACES_WITH_EXTENDED_COMPATIBILITY];

} __attribute__((packed)) extendedCompatibilityIdFeatureDescriptor =
{
	EXT_COMP_DESC_SIZE,
	0x0100,
	EXTENDED_COMPATIBILITY_ID,
	NUM_INTERFACES_WITH_EXTENDED_COMPATIBILITY,
	{0, 0, 0, 0, 0, 0, 0},

	{//Start of array initialization
		{//Start of struct initialization
			YOUR_INTERFACE_NUMBER_1,
			1,
			"WINUSB\0", /** Other IDs define other compatible functions */
			{0, 0, 0, 0, 0, 0, 0, 0}, /**< Some compatible IDs require this “sub compatible ID”  */
			{0, 0, 0, 0, 0, 0},
		},
		/** You can add more interfaces (or functions with multiple interfaces)
		 * which support extended compatibility here.
		 */
	}
};

Next, we need a way for windows to read this descriptor. Add the following code to your setup command callback:

//Vendor specific IN request - Get windows OS feature descriptor from device
	if ((setup->Type == USB_SETUP_TYPE_VENDOR) &&
			(setup->Direction == USB_SETUP_DIR_IN) && //In to host
			(setup->Recipient == USB_SETUP_RECIPIENT_DEVICE) &&
			(setup->bRequest == YOUR_VENDOR_CODE) &&
			(setup->wIndex == EXTENDED_COMPATIBILITY_ID))

	{
		int length = min(setup->wLength,  EXT_COMP_DESC_SIZE);
		retVal = USBD_Write(0, (void*) &extendedCompatibilityIdFeatureDescriptor, length, NULL);
	}

Now Windows has sufficient information to know that it should install the WinUSB driver for your interface. You can access the device through its VID and PID, and you don’t really have to do anything else. However, you can tell windows about other properties of the device, such as a friendly name and power options, and you can also give your device a GUID. This is especially useful for detecting the device from your windows application. All this is done through so called extended properties descriptors. You can have one extended property descriptor per interface.

The structure of the descriptor is as follows:

#define UNICODE_STRING 1
#define EXTENDED_PROPERTIES_ID 5

/** The "u" signifies a 16-bit character (typically unicode) string - supported by GCC */
#define PROPERTY1_NAME u"Label"
#define PROPERTY1	u"Your friendly device name here"
#define PROPERTY2_NAME u"DeviceInterfaceGUID"
#define PROPERTY2	u"{9B67F81A-BB83-11E5-9438-4FB2531BF5E9}" /** Replace by your GUID */

#define PROPERTY1_DESCRIPTOR_SIZE (14 + sizeof(PROPERTY1_NAME) + sizeof(PROPERTY1))
#define PROPERTY2_DESCRIPTOR_SIZE (14 + sizeof(PROPERTY2_NAME) + sizeof(PROPERTY2))

#define PROPERTIES_DESCRIPTOR_SIZE (10 + PROPERTY1_DESCRIPTOR_SIZE + PROPERTY2_DESCRIPTOR_SIZE)

/** Structure containing the extended OS properties of this device, for automatic recognition in windows */
const struct {
	/*<header>*/
	uint32_t wLength; /**< Size of this struct*/
	uint16_t cdVersion; /**< 1.00 -> 0x0100 */
	uint16_t Index;	/**< 0x05 for extended property */
	uint16_t Count;	/**< Number of properties */
	/*</header>*/

	/*Property 1: Friendly name */
	uint32_t Size1;				 /**< Size of this property descriptor */
	uint32_t PropertyDataType1; /**< Data type of this property */
	uint16_t PropertyNameLength1; /**< Length of the name of this property */
	char16_t PropertyName1[sizeof(PROPERTY1_NAME)/2];		/**< Name of this property */
	uint32_t PropertyDataLength1;  /**< Length the property data*/
	char16_t PropertyData1[sizeof(PROPERTY1)/2]; 		/**< Property data*/

	/*Property 2: GUID */
	uint32_t Size2;				 /**< Size of this property descriptor */
	uint32_t PropertyDataType2; /**< Data type of this property */
	uint16_t PropertyNameLength2; /**< Length of the name of this property */
	char16_t PropertyName2[sizeof(PROPERTY2_NAME)/2];		/**< Name of this property */
	uint32_t PropertyDataLength2;  /**< Length the property data*/
	char16_t PropertyData2[sizeof(PROPERTY2)/2]; 		/**< Property data*/
} __attribute__((packed)) extendedPropertyDescriptor =
{
	/* Header */
	PROPERTIES_DESCRIPTOR_SIZE,
	0x0100,
	EXTENDED_PROPERTIES_ID,
	2, //Number of properties

	/* Friendly name */
	PROPERTY1_DESCRIPTOR_SIZE,
	UNICODE_STRING,
	sizeof(PROPERTY1_NAME),
	PROPERTY1_NAME,
	sizeof(PROPERTY1),
	PROPERTY1,

	/* GUID */
	PROPERTY2_DESCRIPTOR_SIZE,
	UNICODE_STRING,
	sizeof(PROPERTY2_NAME),
	PROPERTY2_NAME,
	sizeof(PROPERTY2),
	PROPERTY2
};

Here I have created the properties “Label” and “DeviceInterfaceGUID”, both described in the official documentation. Unfortunately, the “Label” property seems to only show up in the registry, not in the device manager. There may be other properties to name your device, but I haven’t found more documentation about this.

Finally, for windows to be able to read this descriptor, add the following code to your setup command callback:

//Vendor specific IN request - Get windows OS feature descriptor
	if ((setup->Type == USB_SETUP_TYPE_VENDOR) &&
			(setup->Direction == USB_SETUP_DIR_IN) && //In to host
			(setup->Recipient == USB_SETUP_RECIPIENT_INTERFACE) &&
			(setup->wValue == CLOCKSET_INTERFACE_NUMBER) &&
			(setup->bRequest == CLAMPON_VENDOR_CODE) &&
			((setup->wIndex == EXTENDED_PROPERTIES_ID) ||
//Note: A deficiency in the WinUSB driver makes it impossible to set wIndex to anything
//but the interface number, therefore we also accept wIndex == CLOCKSET_INTERFACE_NUMBER
			 (setup->wIndex == CLOCKSET_INTERFACE_NUMBER)))

	{
		int length = MIN(setup->wLength,  PROPERTIES_DESCRIPTOR_SIZE);
		retVal = USBD_Write(0, (void*) &extendedPropertyDescriptor, length, NULL);
	}

And we are done!

Good luck with your MODs!

Posts: 66
Registered: ‎09-17-2012

Re: Using Microsoft OS Descriptors with emlib/em_usb

I forgot a small but important thing:

I subtracted one from numberOfStrings in the initialization struct. That is, in const USBD_Init_TypeDef usbInitStruct:

 

.numberOfStrings     = (sizeof(USBDESC_strings)/sizeof(void*)) -1, //we don't count the OS descriptor here

Also, I forgot to change the names of my constants in the final code block. CLOCKSET_INTERFACE_NUMBER should be YOUR_INTERFACE_NUMBER_1, and CLAMPON_VENDOR_CODE should be YOUR_VENDOR_CODE.

 

Posts: 2,968
Registered: ‎02-07-2002

Re: Using Microsoft OS Descriptors with emlib/em_usb

Very good write up. I was unaware of this.

 

Still I would recommend anyone who wants to use WinUSB to use libusb. It makes your code portable to others platforms. And to get a libusb driver installed in Windows, try zadig.

Posts: 66
Registered: ‎09-17-2012

Re: Using Microsoft OS Descriptors with emlib/em_usb

Thanks, vanmierlo!

Actually, according to this wiki, you should be able to install libusb or libusbK drivers as well this way. Just replace "WINUSB\0" by "LIBUSB0" or "LIBUSBK" in the extended compatibility ID. I have'nt tried this though.

 

By the way, another hint for testing your MODs:

Windows will only try to read MODs from the device the first time it is connected. If you have added or changed MODs, you will need to delete the following registry entry to force windows to read the MODs again:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\usbflags\vvvvpppprrrr

where vvvv is the vendor id, pppp is the PID, and rrrr is the device release number.

Posts: 66
Registered: ‎09-17-2012

Re: Using Microsoft OS Descriptors with emlib/em_usb

Actually, reading that wiki a bit closer, I see that you will still need to install the libusb driver once using Zadig. But after this single installation, windows should automatically recognize libusb devices with MODs.