2020-09-22 05:06 PM
I'm trying to create a composite HID device, mouse+joystick. I'm using the HID mouse profile as a starting point and then add a second interface. I also used an already existing example as a guide: the Arduino core based on the HAL is maintained by ST, and implements a composite HID device mouse+keyboard, which is then used by the Arduino Mouse and Keyboard libraries https://github.com/stm32duino. For what is worth, I modified that code to implement a mouse+joystick device, and everything works as expected in Arduino. Unfortunately that code is not based on the latest HAL, so I cannot simply swap it.
I then tried to use the same USB description and init function to create my device in a standard STM32CubeIDE project (below). I can see that the device initializes properly, Windows recognizes it as a composite HID device with the right characteristics, and the mouse interface works perfectly. If on the other hand I try to send data via the joystick interface, it stops transmitting. I used USBpcap to capture the traffic on the PC side, and I see that the device sends all the right data, initializes 2 interfaces and then windows sends an interrupt request to get data from interface 1 and 2. Interface 1 (the mouse) sends a packet back (4 bytes, as expected), while interface 2 never responds.
By debugging the HAL code (which it's its own form of punishment, I guess :) I can see that the STM32 seems to get an interrupt, but somehow the USB_ReadDevInEPInterrupt() function doesn't seem to return a valid value when the interrupt is from the second interface. I can see that function returning 1 when called from HAL_PCD_IRQHandler() for the interface 1 IRQ, but never returning 1 for the second interface. SO the payload for interface 2 is never sent, leaving the device in HID_BUSY state, and getting blocked
I'm enclosing the relevant portion of my HID definition, hoping someone can spot the problem or suggest how to further debug it. I have been staring at this for hours with no real progress. The device is FS, and the rest of the code is identical to the standard HID HAL code
(code follows in next post to avoid errors from the forum software)
Solved! Go to Solution.
2020-09-23 04:00 PM
To answer my own question for posterity (it's really unpleasant to search for a topic, find the right thread, but no conclusion), I had to modify also the function USBD_LL_Init() in target/usbd_conf.c as follows. Sooner or later I will post this project under https://github.com/robcazzaro
USBD_StatusTypeDef USBD_LL_Init(USBD_HandleTypeDef *pdev)
{
/* Init USB Ip. */
if (pdev->id == DEVICE_FS) {
/* Link the driver to the stack. */
hpcd_USB_OTG_FS.pData = pdev;
pdev->pData = &hpcd_USB_OTG_FS;
//[removed for clarity, this code doesn't need to be changed]
HAL_PCDEx_SetRxFiFo(&hpcd_USB_OTG_FS, 0x40);
HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 0, 0x40);
HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 1, 0x04); // length of mouse_report
HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 2, 0x08); // length of joystick_report
}
return USBD_OK;
}
2020-09-22 05:08 PM
#define USBD_MAX_NUM_INTERFACES 2U
#define USBD_MAX_NUM_CONFIGURATION 1U
#define USBD_MAX_STR_DESC_SIZ 512U
#define USBD_DEBUG_LEVEL 0U
#define USBD_LPM_ENABLED 0U
#define USBD_SELF_POWERED 1U
#define HID_FS_BINTERVAL 0x08U
/****************************************/
/* #define for FS and HS identification */
#define DEVICE_FS 0
#define DEVICE_HS 1
USBD_ClassTypeDef USBD_HID_COMPOSITE = {
USBD_HID_COMPOSITE_Init,
USBD_HID_COMPOSITE_DeInit,
USBD_HID_COMPOSITE_Setup,
NULL, /* EP0_TxSent */
NULL, /* EP0_RxReady */
USBD_HID_COMPOSITE_DataIn, /* DataIn */
NULL, /* DataOut */
NULL, /* SOF */
NULL,
NULL,
USBD_HID_COMPOSITE_GetHSCfgDesc,
USBD_HID_COMPOSITE_GetFSCfgDesc,
USBD_HID_COMPOSITE_GetOtherSpeedCfgDesc,
USBD_HID_COMPOSITE_GetDeviceQualifierDesc,
};
/* USB HID device FS Configuration Descriptor */
__ALIGN_BEGIN static uint8_t USBD_HID_COMPOSITE_CfgFSDesc[USB_HID_CONFIG_DESC_SIZ] __ALIGN_END = {
0x09, /* bLength: Configuration Descriptor size */
USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */
USB_HID_CONFIG_DESC_SIZ,
/* wTotalLength: Bytes returned */
0x00,
0x02, /* bNumInterfaces: 1 interface */ //2 interfaces Rob!!!
0x01, /* bConfigurationValue: Configuration value */
0x00, /* iConfiguration: Index of string descriptor describing the configuration */
0xC0, /* bmAttributes: bus powered and Support Remote Wake-up */
0x32, /* MaxPower 100 mA: this current is used for detecting Vbus */
/********************************************************************
Mouse
*********************************************************************/
/************** Descriptor of Mouse interface ****************/
/* 09 */
0x09, /* bLength: Interface Descriptor size */
USB_DESC_TYPE_INTERFACE, /* bDescriptorType: Interface descriptor type */
HID_MOUSE_INTERFACE, /* bInterfaceNumber: Number of Interface */
0x00, /* bAlternateSetting: Alternate setting */
0x01, /* bNumEndpoints */
0x03, /* bInterfaceClass: HID */
0x01, /* bInterfaceSubClass : 1=BOOT, 0=no boot */
0x02, /* nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse */
0, /* iInterface: Index of string descriptor */
/******************** Descriptor of Mouse HID ********************/
/* 18 */
0x09, /* bLength: HID Descriptor size */
HID_DESCRIPTOR_TYPE, /* bDescriptorType: HID */
0x11, /* bcdHID: HID Class Spec release number */
0x01,
0x00, /* bCountryCode: Hardware target country */
0x01, /* bNumDescriptors: Number of HID class descriptors to follow */
0x22, /* bDescriptorType */
HID_MOUSE_REPORT_DESC_SIZE, /* wItemLength: Total length of Report descriptor */
0x00,
/******************** Descriptor of Mouse endpoint ********************/
/* 27 */
0x07, /* bLength: Endpoint Descriptor size */
USB_DESC_TYPE_ENDPOINT, /* bDescriptorType:*/
HID_MOUSE_EPIN_ADDR, /* bEndpointAddress: Endpoint Address (IN) */
0x03, /* bmAttributes: Interrupt endpoint */
HID_MOUSE_EPIN_SIZE, /* wMaxPacketSize: 4 Byte max */
0x00,
HID_FS_BINTERVAL, /* bInterval: Polling Interval */
/* 34 */
/********************************************************************
Joystick
*********************************************************************/
/************** Descriptor of HID Joystick interface ****************/
/* 34 */
0x09, /*bLength: Interface Descriptor size*/
USB_DESC_TYPE_INTERFACE,/*bDescriptorType: Interface descriptor type*/
HID_JOYSTICK_INTERFACE, /*bInterfaceNumber: Number of Interface*/
0x00, /*bAlternateSetting: Alternate setting*/
0x01, /*bNumEndpoints*/
0x03, /*bInterfaceClass: HID*/
0x00, /*bInterfaceSubClass : 1=BOOT, 0=no boot*/
0x00, /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/
0x00, /*iInterface: Index of string descriptor*/
/******************** HID Descriptor ********************/
/* 43 */
0x09, /*bLength: HID Descriptor size*/
HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/
0x11, /*bcdHID: HID Class Spec release number*/
0x01,
0x00, /*bCountryCode: Hardware target country*/
0x01, /*bNumDescriptors: Number of HID class descriptors to follow*/
0x22, /*bDescriptorType*/
HID_JOYSTICK_REPORT_DESC_SIZE,/*wItemLength: Total length of Report descriptor*/
0x00,
/******************** Descriptor of Joystick endpoint ********************/
/* 52 */
0x07, /*bLength: Endpoint Descriptor size*/
USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/
HID_JOYSTICK_EPIN_ADDR, /*bEndpointAddress: Endpoint Address (IN)*/
0x03, /*bmAttributes: Interrupt endpoint*/
HID_JOYSTICK_EPIN_SIZE, /*wMaxPacketSize: 8 Byte max */
0x00,
HID_FS_BINTERVAL /*bInterval: Polling Interval*/
/* 59 */
};
/* USB HID device Configuration Descriptor */
__ALIGN_BEGIN static uint8_t USBD_MOUSE_HID_Desc[USB_HID_DESC_SIZ] __ALIGN_END = {
/* 18 */
0x09, /* bLength: HID Descriptor size */
HID_DESCRIPTOR_TYPE, /* bDescriptorType: HID */
0x11, /* bcdHID: HID Class Spec release number */
0x01,
0x00, /* bCountryCode: Hardware target country */
0x01, /* bNumDescriptors: Number of HID class descriptors to follow */
0x22, /* bDescriptorType */
HID_MOUSE_REPORT_DESC_SIZE, /* wItemLength: Total length of Report descriptor */
0x00,
};
/* USB HID device Configuration Descriptor */
__ALIGN_BEGIN static uint8_t USBD_JOYSTICK_HID_Desc[USB_HID_DESC_SIZ] __ALIGN_END = {
0x09, /*bLength: HID Descriptor size*/
HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/
0x11, /*bcdHID: HID Class Spec release number*/
0x01,
0x00, /*bCountryCode: Hardware target country*/
0x01, /*bNumDescriptors: Number of HID class descriptors to follow*/
0x22, /*bDescriptorType*/
HID_JOYSTICK_REPORT_DESC_SIZE,/*wItemLength: Total length of Report descriptor*/
0x00,
};
/* USB Standard Device Descriptor */
__ALIGN_BEGIN static uint8_t USBD_HID_COMPOSITE_DeviceQualifierDesc[USB_LEN_DEV_QUALIFIER_DESC] __ALIGN_END = {
USB_LEN_DEV_QUALIFIER_DESC,
USB_DESC_TYPE_DEVICE_QUALIFIER,
0x00,
0x02,
0x00,
0x00,
0x00,
0x40,
0x01,
0x00,
};
__ALIGN_BEGIN static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE] __ALIGN_END = {
0x05, 0x01,
0x09, 0x02,
0xA1, 0x01,
0x09, 0x01,
0xA1, 0x00,
0x05, 0x09,
0x19, 0x01,
0x29, 0x03,
0x15, 0x00,
0x25, 0x01,
0x95, 0x03,
0x75, 0x01,
0x81, 0x02,
0x95, 0x01,
0x75, 0x05,
0x81, 0x01,
0x05, 0x01,
0x09, 0x30,
0x09, 0x31,
0x09, 0x38,
0x15, 0x81,
0x25, 0x7F,
0x75, 0x08,
0x95, 0x03,
0x81, 0x06,
0xC0, 0x09,
0x3c, 0x05,
0xff, 0x09,
0x01, 0x15,
0x00, 0x25,
0x01, 0x75,
0x01, 0x95,
0x02, 0xb1,
0x22, 0x75,
0x06, 0x95,
0x01, 0xb1,
0x01, 0xc0
};
__ALIGN_BEGIN static uint8_t HID_JOYSTICK_ReportDesc[HID_JOYSTICK_REPORT_DESC_SIZE] __ALIGN_END = {
0x05, 0x01, 0x09, 0x04, 0xA1, 0x01, 0xA1, 0x02, 0x75, 0x08, 0x95, 0x05, 0x15, 0x00, 0x26, 0xFF,
0x00, 0x35, 0x00, 0x46, 0xFF, 0x00, 0x09, 0x30, 0x09, 0x31, 0x09, 0x32, 0x09, 0x32, 0x09, 0x35,
0x81, 0x02, 0x75, 0x04, 0x95, 0x01, 0x25, 0x07, 0x46, 0x3B, 0x01, 0x65, 0x14, 0x09, 0x39, 0x81,
0x42, 0x65, 0x00, 0x75, 0x01, 0x95, 0x0C, 0x25, 0x01, 0x45, 0x01, 0x05, 0x09, 0x19, 0x01, 0x29,
0x0C, 0x81, 0x02, 0x06, 0x00, 0xFF, 0x75, 0x01, 0x95, 0x08, 0x25, 0x01, 0x45, 0x01, 0x09, 0x01,
0x81, 0x02, 0xC0, 0xA1, 0x02, 0x75, 0x08, 0x95, 0x07, 0x46, 0xFF, 0x00, 0x26, 0xFF, 0x00, 0x09,
0x02, 0x91, 0x02, 0xC0, 0xC0
};
2020-09-22 05:09 PM
static uint8_t USBD_HID_COMPOSITE_Init(USBD_HandleTypeDef *pdev, uint8_t cfgidx)
{
UNUSED(cfgidx);
USBD_HID_COMPOSITE_HandleTypeDef *hhid;
hhid = USBD_malloc(sizeof(USBD_HID_COMPOSITE_HandleTypeDef));
if (hhid == NULL)
{
pdev->pClassData = NULL;
return (uint8_t)USBD_EMEM;
}
pdev->pClassData = (void *)hhid;
if (pdev->dev_speed == USBD_SPEED_HIGH)
{
pdev->ep_in[HID_MOUSE_EPIN_ADDR & 0xFU].bInterval = HID_HS_BINTERVAL;
pdev->ep_in[HID_JOYSTICK_EPIN_ADDR & 0xFU].bInterval = HID_HS_BINTERVAL;
}
else /* LOW and FULL-speed endpoints */
{
pdev->ep_in[HID_MOUSE_EPIN_ADDR & 0xFU].bInterval = HID_FS_BINTERVAL;
pdev->ep_in[HID_JOYSTICK_EPIN_ADDR & 0xFU].bInterval = HID_FS_BINTERVAL;
}
/* Open EP IN */
(void)USBD_LL_OpenEP(pdev, HID_MOUSE_EPIN_ADDR, USBD_EP_TYPE_INTR, HID_MOUSE_EPIN_SIZE);
pdev->ep_in[HID_MOUSE_EPIN_ADDR & 0xFU].is_used = 1U;
(void)USBD_LL_OpenEP(pdev, HID_JOYSTICK_EPIN_ADDR, USBD_EP_TYPE_INTR, HID_JOYSTICK_EPIN_SIZE);
pdev->ep_in[HID_JOYSTICK_EPIN_ADDR & 0xFU].is_used = 1U;
hhid->Mousestate = HID_IDLE;
hhid->Joystickstate = HID_IDLE;
return (uint8_t)USBD_OK;
}
static uint8_t USBD_HID_COMPOSITE_Setup(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req)
{
/* Check which interface is targetted by this request */
if ((req->wIndex & 0x00FF) == HID_JOYSTICK_INTERFACE) {
return USBD_HID_JOYSTICK_Setup(pdev, req);
} else {
return USBD_HID_MOUSE_Setup(pdev, req);
}
}
/**
* @brief USBD_HID_MOUSE_Setup
* Handle the HID specific requests
* @param pdev: instance
* @param req: usb requests
* @retval status
*/
static uint8_t USBD_HID_MOUSE_Setup(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req)
{
USBD_HID_COMPOSITE_HandleTypeDef *hhid = (USBD_HID_COMPOSITE_HandleTypeDef *)pdev->pClassData;
USBD_StatusTypeDef ret = USBD_OK;
uint16_t len;
uint8_t *pbuf;
uint16_t status_info = 0U;
switch (req->bmRequest & USB_REQ_TYPE_MASK)
{
case USB_REQ_TYPE_CLASS :
switch (req->bRequest)
{
case HID_REQ_SET_PROTOCOL:
hhid->Protocol = (uint8_t)(req->wValue);
break;
case HID_REQ_GET_PROTOCOL:
(void)USBD_CtlSendData(pdev, (uint8_t *)&hhid->Protocol, 1U);
break;
case HID_REQ_SET_IDLE:
hhid->IdleState = (uint8_t)(req->wValue >> 8);
break;
case HID_REQ_GET_IDLE:
(void)USBD_CtlSendData(pdev, (uint8_t *)&hhid->IdleState, 1U);
break;
default:
USBD_CtlError(pdev, req);
ret = USBD_FAIL;
break;
}
break;
case USB_REQ_TYPE_STANDARD:
switch (req->bRequest)
{
case USB_REQ_GET_STATUS:
if (pdev->dev_state == USBD_STATE_CONFIGURED)
{
(void)USBD_CtlSendData(pdev, (uint8_t *)&status_info, 2U);
}
else
{
USBD_CtlError(pdev, req);
ret = USBD_FAIL;
}
break;
case USB_REQ_GET_DESCRIPTOR:
if ((req->wValue >> 8) == HID_REPORT_DESC)
{
len = MIN(HID_MOUSE_REPORT_DESC_SIZE, req->wLength);
pbuf = HID_MOUSE_ReportDesc;
}
else if ((req->wValue >> 8) == HID_DESCRIPTOR_TYPE)
{
pbuf = USBD_MOUSE_HID_Desc;
len = MIN(USB_HID_DESC_SIZ, req->wLength);
}
else
{
USBD_CtlError(pdev, req);
ret = USBD_FAIL;
break;
}
(void)USBD_CtlSendData(pdev, pbuf, len);
break;
case USB_REQ_GET_INTERFACE :
if (pdev->dev_state == USBD_STATE_CONFIGURED)
{
(void)USBD_CtlSendData(pdev, (uint8_t *)&hhid->AltSetting, 1U);
}
else
{
USBD_CtlError(pdev, req);
ret = USBD_FAIL;
}
break;
case USB_REQ_SET_INTERFACE:
if (pdev->dev_state == USBD_STATE_CONFIGURED)
{
hhid->AltSetting = (uint8_t)(req->wValue);
}
else
{
USBD_CtlError(pdev, req);
ret = USBD_FAIL;
}
break;
case USB_REQ_CLEAR_FEATURE:
break;
default:
USBD_CtlError(pdev, req);
ret = USBD_FAIL;
break;
}
break;
default:
USBD_CtlError(pdev, req);
ret = USBD_FAIL;
break;
}
return (uint8_t)ret;
}
2020-09-22 05:10 PM
static uint8_t USBD_HID_JOYSTICK_Setup(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req)
{
USBD_HID_COMPOSITE_HandleTypeDef *hhid = (USBD_HID_COMPOSITE_HandleTypeDef *)pdev->pClassData;
USBD_StatusTypeDef ret = USBD_OK;
uint16_t len;
uint8_t *pbuf;
uint16_t status_info = 0U;
switch (req->bmRequest & USB_REQ_TYPE_MASK)
{
case USB_REQ_TYPE_CLASS :
switch (req->bRequest)
{
case HID_REQ_SET_PROTOCOL:
hhid->Protocol = (uint8_t)(req->wValue);
break;
case HID_REQ_GET_PROTOCOL:
(void)USBD_CtlSendData(pdev, (uint8_t *)&hhid->Protocol, 1U);
break;
case HID_REQ_SET_IDLE:
hhid->IdleState = (uint8_t)(req->wValue >> 8);
break;
case HID_REQ_GET_IDLE:
(void)USBD_CtlSendData(pdev, (uint8_t *)&hhid->IdleState, 1U);
break;
default:
USBD_CtlError(pdev, req);
ret = USBD_FAIL;
break;
}
break;
case USB_REQ_TYPE_STANDARD:
switch (req->bRequest)
{
case USB_REQ_GET_STATUS:
if (pdev->dev_state == USBD_STATE_CONFIGURED)
{
(void)USBD_CtlSendData(pdev, (uint8_t *)&status_info, 2U);
}
else
{
USBD_CtlError(pdev, req);
ret = USBD_FAIL;
}
break;
case USB_REQ_GET_DESCRIPTOR:
if ((req->wValue >> 8) == HID_REPORT_DESC)
{
len = MIN(HID_JOYSTICK_REPORT_DESC_SIZE, req->wLength);
pbuf = HID_JOYSTICK_ReportDesc;
}
else if ((req->wValue >> 8) == HID_DESCRIPTOR_TYPE)
{
pbuf = USBD_JOYSTICK_HID_Desc;
len = MIN(USB_HID_DESC_SIZ, req->wLength);
}
else
{
USBD_CtlError(pdev, req);
ret = USBD_FAIL;
break;
}
(void)USBD_CtlSendData(pdev, pbuf, len);
break;
case USB_REQ_GET_INTERFACE :
if (pdev->dev_state == USBD_STATE_CONFIGURED)
{
(void)USBD_CtlSendData(pdev, (uint8_t *)&hhid->AltSetting, 1U);
}
else
{
USBD_CtlError(pdev, req);
ret = USBD_FAIL;
}
break;
case USB_REQ_SET_INTERFACE:
if (pdev->dev_state == USBD_STATE_CONFIGURED)
{
hhid->AltSetting = (uint8_t)(req->wValue);
}
else
{
USBD_CtlError(pdev, req);
ret = USBD_FAIL;
}
break;
case USB_REQ_CLEAR_FEATURE:
break;
default:
USBD_CtlError(pdev, req);
ret = USBD_FAIL;
break;
}
break;
default:
USBD_CtlError(pdev, req);
ret = USBD_FAIL;
break;
}
return (uint8_t)ret;
}
uint8_t USBD_HID_MOUSE_SendReport(USBD_HandleTypeDef *pdev, uint8_t *report, uint16_t len)
{
USBD_HID_COMPOSITE_HandleTypeDef *hhid = (USBD_HID_COMPOSITE_HandleTypeDef *)pdev->pClassData;
if (pdev->dev_state == USBD_STATE_CONFIGURED)
{
if (hhid->Mousestate == HID_IDLE)
{
hhid->Mousestate = HID_BUSY;
(void)USBD_LL_Transmit(pdev, HID_MOUSE_EPIN_ADDR, report, len);
}
}
return (uint8_t)USBD_OK;
}
uint8_t USBD_HID_JOYSTICK_SendReport(USBD_HandleTypeDef *pdev, uint8_t *report, uint16_t len)
{
USBD_HID_COMPOSITE_HandleTypeDef *hhid = (USBD_HID_COMPOSITE_HandleTypeDef *)pdev->pClassData;
if (pdev->dev_state == USBD_STATE_CONFIGURED)
{
if (hhid->Joystickstate == HID_IDLE)
{
hhid->Joystickstate = HID_BUSY;
(void)USBD_LL_Transmit(pdev, HID_JOYSTICK_EPIN_ADDR, report, len);
}
}
return (uint8_t)USBD_OK;
}
2020-09-23 04:00 PM
To answer my own question for posterity (it's really unpleasant to search for a topic, find the right thread, but no conclusion), I had to modify also the function USBD_LL_Init() in target/usbd_conf.c as follows. Sooner or later I will post this project under https://github.com/robcazzaro
USBD_StatusTypeDef USBD_LL_Init(USBD_HandleTypeDef *pdev)
{
/* Init USB Ip. */
if (pdev->id == DEVICE_FS) {
/* Link the driver to the stack. */
hpcd_USB_OTG_FS.pData = pdev;
pdev->pData = &hpcd_USB_OTG_FS;
//[removed for clarity, this code doesn't need to be changed]
HAL_PCDEx_SetRxFiFo(&hpcd_USB_OTG_FS, 0x40);
HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 0, 0x40);
HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 1, 0x04); // length of mouse_report
HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 2, 0x08); // length of joystick_report
}
return USBD_OK;
}
2023-09-26 01:39 AM
So many developers have suffered the frustration of the incomplete ST USB support. It makes no sense that ST haven't sorted out a multi-device (composite) USB driver for their MCUs, and I think in this regards developers have failed in making enough of a fuss to push ST to sort this out once and for all.
2024-02-12 03:08 AM
I noticed that the complete solution isn't among your github repositories, as mentioned in your 23rd September post. It would be a great help to many (including me!) if you were able to, if you wouldn't mind. Many thanks
2024-02-12 04:16 AM
It would be even better if ST Microelectronics could deliver the update composite USB driver they've been promising since 2008!