3
\$\begingroup\$

Edit

I may not have asked the correct questions here. Please do not attempt to answer before I edit again to remove this message.

Summary

I'm developing firmware for a USB device, and I can't get it through the enumeration process. My intent is to setup the device descriptors such that it will be automatically detected by a Windows 7/8/10 host as a Full Speed USB 2.0 WinUSB device with one bulk pipe in each direction.

During enumeration, here's what appears to be happening on the device side for descriptor requests, when connecting to a Windows 10 host:

  1. Get Device Descriptor (USB Standard)
  2. Get Configuration Descriptor (USB Standard)
  3. Get Microsoft OS String Descriptor
  4. Get Device_Qualifier Descriptor (USB Standard - request for full speed info) --> device stalls

Questions

  1. Why is the Windows host sending the Device_Qualifier request?
  2. Is there a way to setup the preceding descriptors so as to prevent the Windows host from sending that request?
  3. If not, what should I send in response to the request?
  4. And how can I add user code to do so? (See details below to understand why this is unclear.)
  5. Since this is low level USB stuff, I thought EE was my best bet. Or should I move this to StackOverflow?

Implementation Details

I'm currently using an STM32F072 Discovery board. I used STM32CubeMX to generate code from v1.7.0 of the STM32CubeF0 firmware library.

Initially, I set the project up in Cube as a USB CDC (Virtual COM Port) device. Then I moved the class library (CDC) files into my application code, renamed them, and changed my Cube design file to just generate code for a classless USB device, so I can modify the CDC class files to make it a WinUSB device, and have Cube not disturb those files.

The handler for descriptor requests is in library code. It handles standard requests itself, but passes non-standard requests over to user code. Regarding question 4 above, it appears that there is no way for me to handle the Device_Qualifier descriptor request without modifying library code.

Below is an excerpt from the handler, found in Middlewares\ST\STM32_USB_Device_Library\Core\Src\usbd_ctlreq.c. Of interest at the moment is the USB_DESC_TYPE_DEVICE_QUALIFIER case in the switch statement, but the entire function is provided for context.

/**
* @brief  USBD_GetDescriptor
*         Handle Get Descriptor requests
* @param  pdev: device instance
* @param  req: usb request
* @retval status
*/
static void USBD_GetDescriptor(USBD_HandleTypeDef *pdev , 
                               USBD_SetupReqTypedef *req)
{
  uint16_t len;
  uint8_t *pbuf;


  switch (req->wValue >> 8)
  { 
#if (USBD_LPM_ENABLED == 1)
  case USB_DESC_TYPE_BOS:
    pbuf = pdev->pDesc->GetBOSDescriptor(pdev->dev_speed, &len);
    break;
#endif    
  case USB_DESC_TYPE_DEVICE:
    pbuf = pdev->pDesc->GetDeviceDescriptor(pdev->dev_speed, &len);
    break;

  case USB_DESC_TYPE_CONFIGURATION:     
    if(pdev->dev_speed == USBD_SPEED_HIGH )   
    {
      pbuf   = (uint8_t *)pdev->pClass->GetHSConfigDescriptor(&len);
      pbuf[1] = USB_DESC_TYPE_CONFIGURATION;
    }
    else
    {
      pbuf   = (uint8_t *)pdev->pClass->GetFSConfigDescriptor(&len);
      pbuf[1] = USB_DESC_TYPE_CONFIGURATION;
    }
    break;

  case USB_DESC_TYPE_STRING:
    switch ((uint8_t)(req->wValue))
    {
    case USBD_IDX_LANGID_STR:
     pbuf = pdev->pDesc->GetLangIDStrDescriptor(pdev->dev_speed, &len);        
      break;

    case USBD_IDX_MFC_STR:
      pbuf = pdev->pDesc->GetManufacturerStrDescriptor(pdev->dev_speed, &len);
      break;

    case USBD_IDX_PRODUCT_STR:
      pbuf = pdev->pDesc->GetProductStrDescriptor(pdev->dev_speed, &len);
      break;

    case USBD_IDX_SERIAL_STR:
      pbuf = pdev->pDesc->GetSerialStrDescriptor(pdev->dev_speed, &len);
      break;

    case USBD_IDX_CONFIG_STR:
      pbuf = pdev->pDesc->GetConfigurationStrDescriptor(pdev->dev_speed, &len);
      break;

    case USBD_IDX_INTERFACE_STR:
      pbuf = pdev->pDesc->GetInterfaceStrDescriptor(pdev->dev_speed, &len);
      break;

    default:
#if (USBD_SUPPORT_USER_STRING == 1)
      pbuf = pdev->pClass->GetUsrStrDescriptor(pdev, (req->wValue) , &len);
      break;
#else      
       USBD_CtlError(pdev , req);
      return;
#endif   
    }
    break;
  case USB_DESC_TYPE_DEVICE_QUALIFIER:                   

    if(pdev->dev_speed == USBD_SPEED_HIGH  )   
    {
      pbuf   = (uint8_t *)pdev->pClass->GetDeviceQualifierDescriptor(&len);
      break;
    }
    else
    {
      USBD_CtlError(pdev , req);
      return;
    } 

  case USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION:
    if(pdev->dev_speed == USBD_SPEED_HIGH  )   
    {
      pbuf   = (uint8_t *)pdev->pClass->GetOtherSpeedConfigDescriptor(&len);
      pbuf[1] = USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION;
      break; 
    }
    else
    {
      USBD_CtlError(pdev , req);
      return;
    }

  default: 
     USBD_CtlError(pdev , req);
    return;
  }

  if((len != 0)&& (req->wLength != 0))
  {

    len = MIN(len , req->wLength);

    USBD_CtlSendData (pdev, 
                      pbuf,
                      len);
  }

}

Since pdev->dev_speed == USBD_SPEED_FULL, it's calling USBD_CtlError(), thus stalling the bus, and aborting the enumeration.

/**
* @brief  USBD_CtlError 
*         Handle USB low level Error
* @param  pdev: device instance
* @param  req: usb request
* @retval None
*/
void USBD_CtlError( USBD_HandleTypeDef *pdev ,
                        USBD_SetupReqTypedef *req)
{
  USBD_LL_StallEP(pdev , 0x80);
  USBD_LL_StallEP(pdev , 0);
}

USB Descriptors

Here are the preceding standard USB descriptors.

Device

/* USB Standard Device Descriptor */
__ALIGN_BEGIN uint8_t USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END =
  {
    0x12,                       /* bLength */
    USB_DESC_TYPE_DEVICE,       /* bDescriptorType*/
    0x00,                       /* bcdUSB */  
    0x02,
    0xFF,                       /* bDeviceClass    - vendor specific */
    0xFF,                       /* bDeviceSubClass - vendor specific */
    0xFF,                       /* bDeviceProtocol - vendor specific */
    USB_MAX_EP0_SIZE,           /* bMaxPacketSize */
    LOBYTE(USBD_VID   ),        /* idVendor */
    HIBYTE(USBD_VID   ),        /* idVendor */
    LOBYTE(USBD_PID_FS),        /* idProduct */
    HIBYTE(USBD_PID_FS),        /* idProduct */
    0x01,                       /* bcdDevice rel. 0.01 */
    0x00,
    0x00,                       /* Index of manufacturer  string - 0x00 means NOT USED */
    0x00,                       /* Index of product       string - 0x00 means NOT USED */
    0x00,                       /* Index of serial number string - 0x00 means NOT USED */
    USBD_MAX_NUM_CONFIGURATION  /* bNumConfigurations*/
  }; 
/* USB_DeviceDescriptor */

Config

/* USB CDC device Configuration Descriptor */
__ALIGN_BEGIN uint8_t USBD_CDC_CfgFSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END =
{
  /*Configuration Descriptor*/
  0x09,   /* bLength: Configuration Descriptor size */
  USB_DESC_TYPE_CONFIGURATION,      /* bDescriptorType: Configuration */
  USB_CDC_CONFIG_DESC_SIZ,                /* wTotalLength:no of returned bytes */
  0x00,
  0x01,   /* bNumInterfaces: 1 interface */
  0x01,   /* bConfigurationValue: Configuration value */
  0x00,   /* iConfiguration: Index of string descriptor describing the configuration */
  0x80,   /* bmAttributes: USB-powered */
  0x96,   /* MaxPower 0x96 = 150 = 300 mA */

  /*---------------------------------------------------------------------------*/

  /*Interface Descriptor */
  0x09,   /* bLength: Interface Descriptor size */
  USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: Interface */
  /* Interface descriptor type */
  0x00,   /* bInterfaceNumber: Number of Interface */
  0x00,   /* bAlternateSetting: Alternate setting */
  0x02,   /* bNumEndpoints: One endpoints used */
  0xFF,   /* bInterfaceClass: Communication Interface Class - vendor-specific */
  0x01,   /* bInterfaceSubClass: arbitrary (vendor's choice b/c bInterfaceClass is vendor-specific) */
  0xFF,   /* bInterfaceProtocol: vendor-specific */
  0x00,   /* iInterface: not used */

//  /*Header Functional Descriptor*/
//  0x05,   /* bLength: Endpoint Descriptor size */
//  0x24,   /* bDescriptorType: CS_INTERFACE */
//  0x00,   /* bDescriptorSubtype: Header Func Desc */
//  0x10,   /* bcdCDC: spec release number */
//  0x01,
//  
//  /*Call Management Functional Descriptor*/
//  0x05,   /* bFunctionLength */
//  0x24,   /* bDescriptorType: CS_INTERFACE */
//  0x01,   /* bDescriptorSubtype: Call Management Func Desc */
//  0x00,   /* bmCapabilities: D0+D1 */
//  0x01,   /* bDataInterface: 1 */
//  
//  /*ACM Functional Descriptor*/
//  0x04,   /* bFunctionLength */
//  0x24,   /* bDescriptorType: CS_INTERFACE */
//  0x02,   /* bDescriptorSubtype: Abstract Control Management desc */
//  0x02,   /* bmCapabilities */
//  
//  /*Union Functional Descriptor*/
//  0x05,   /* bFunctionLength */
//  0x24,   /* bDescriptorType: CS_INTERFACE */
//  0x06,   /* bDescriptorSubtype: Union func desc */
//  0x00,   /* bMasterInterface: Communication class interface */
//  0x01,   /* bSlaveInterface0: Data Class Interface */
//
//  /*Endpoint 2 Descriptor*/
//  0x07,                           /* bLength: Endpoint Descriptor size */
//  USB_DESC_TYPE_ENDPOINT,   /* bDescriptorType: Endpoint */
//  CDC_CMD_EP,                     /* bEndpointAddress */
//  0x03,                           /* bmAttributes: Interrupt */
//  LOBYTE(CDC_CMD_PACKET_SIZE),     /* wMaxPacketSize: */
//  HIBYTE(CDC_CMD_PACKET_SIZE),
//  0x10,                           /* bInterval: */ 
  /*---------------------------------------------------------------------------*/

//  /*Data class interface descriptor*/
//  0x09,   /* bLength: Endpoint Descriptor size */
//  USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: */
//  0x01,   /* bInterfaceNumber: Number of Interface */
//  0x00,   /* bAlternateSetting: Alternate setting */
//  0x02,   /* bNumEndpoints: Two endpoints used */
//  0x0A,   /* bInterfaceClass: CDC */
//  0x00,   /* bInterfaceSubClass: */
//  0x00,   /* bInterfaceProtocol: */
//  0x00,   /* iInterface: */

  /*Endpoint OUT Descriptor*/
  0x07,   /* bLength: Endpoint Descriptor size */
  USB_DESC_TYPE_ENDPOINT,      /* bDescriptorType: Endpoint */
  CDC_OUT_EP,                        /* bEndpointAddress */
  USBD_EP_TYPE_BULK,                 /* bmAttributes: Bulk */
  LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),  /* wMaxPacketSize: */
  HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),
  0x00,                              /* bInterval: ignore for Bulk transfer */

  /*Endpoint IN Descriptor*/
  0x07,   /* bLength: Endpoint Descriptor size */
  USB_DESC_TYPE_ENDPOINT,      /* bDescriptorType: Endpoint */
  CDC_IN_EP,                         /* bEndpointAddress */
  USBD_EP_TYPE_BULK,                    /* bmAttributes: Bulk */
  LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),  /* wMaxPacketSize: */
  HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),
  0x00                               /* bInterval: ignore for Bulk transfer */
};
\$\endgroup\$
  • \$\begingroup\$ This behavior is correct. As your device descriptor announces USB version 2.0, a device qualifier is requested to check if your device supports high speed -- since it \$\endgroup\$ – Bruno Basseto Jan 22 '17 at 17:34
  • \$\begingroup\$ -- since it does not, the device must stall, what is correct. What is strange is that you say it stops enumeration, but it might not... \$\endgroup\$ – Bruno Basseto Jan 22 '17 at 17:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.