Beware that this post is related to an SDK in maintenance mode
More Info: Consider nRF Connect SDK for new designs

WebUSB on nRF5SDK using a usbd class

Hi there,

in the past year I've been developing a USB bridge.
The last major milestone for this bridge was the support for WebUSB.
For WebUSB to work on Windows WinUSB also need's to be added. This has a nice little side effect. A driver installation for our device isn't needed anymore.
I know there are some examples out there with these two working, but as far as I've seen, they have either used nrf_drv_usbd or the nRF Connect SDK.
Since I've started with the nRF5SDK and a usbd_class implementation, this wasn't a viable way for me.

Yesterday I've finally been able to get it to work.

This ticket isn't a question but more of a guide on how to get BOS descriptors to work with a usbd_class in the nRF5SDK.
This is by no means the best solution but a working one.

So let's get started:

There are 2 files within the SDK that need to be adapted: app_usbd_descriptor.h and app_usbd_core.c.
Additionally a module where all the new requests are handled needs to be added. I've gone with app_usbd_bos_desc.

In app_usbd_descriptor.h 2 things are added:

  1. To the enum app_usbd_descriptor_t  APP_USBD_DESCRIPTOR_BINARY_DEVICE_OBJECT_STORE = 15 get's added so that we can handle BOS descriptors later on
  2. For easier handling of said descriptor the struct app_usbd_descriptor_bos_t get's added, which looks like this:
    typedef struct
    {
        uint8_t  bLength;
        uint8_t  bDescriptorType;
        uint16_t sTotalLength;
        uint8_t  bNumDeviceCaps;
    } app_usbd_descriptor_bos_t;


In app_usbd_core.c the following needs to be done:

  1. Change APP_USBD_CORE_DEVICE_DESCRIPTOR so that the USB version can be changed to 2.1. For a PC to request BOS descriptors the USB version must be greater than 2.0.
    I've done it by adding the define APP_USBD_VERSION_MINOR to the sdk_config.h file which I've set to 1.
       .bcdUSB = APP_USBD_BCD_VER_MAKE(2, APP_USBD_VERSION_MINOR, 0), /* USB BCD version */ 
  2. To the switch in the function setup_device_req_get_descriptor the following case is added:
    case APP_USBD_DESCRIPTOR_BINARY_DEVICE_OBJECT_STORE:
    {
        if(p_setup_ev->setup.wLength.w == 0)
        {
            return NRF_SUCCESS;
        }
    
        uint8_t const * p_bos_dsc = app_usbd_bos_desc_get();
        if (p_bos_dsc == NULL)
        {
            return NRF_ERROR_NOT_SUPPORTED;
        }
    
        return app_usbd_core_setup_rsp(
            &(p_setup_ev->setup),
            p_bos_dsc,
            app_usbd_bos_desc_length(p_bos_dsc));
    }
    

    With this case it is possible to inform the PC about the BOS descriptor including the WebUSB platform capability descriptor and the MSOS 2.0 platform capability descriptor.
  3. Something the SDK doesn't support are vendor specific setup requests. To support these I've changed the setup_device_req_std_handler to the following:
    static ret_code_t setup_device_req_std_handler(app_usbd_class_inst_t const * const p_inst,
                                                   app_usbd_setup_evt_t const * const  p_setup_ev)
    {
        ASSERT(p_inst != NULL);
        ASSERT(p_setup_ev != NULL);
        bool vendor_req = false;
    
        if (APP_USBD_SETUP_REQTYPE_STD != app_usbd_setup_req_typ(p_setup_ev->setup.bmRequestType))
        {
            if(APP_USBD_SETUP_REQTYPE_VENDOR == app_usbd_setup_req_typ(p_setup_ev->setup.bmRequestType))
            {
                vendor_req = true;
            }
            else
                return NRF_ERROR_NOT_SUPPORTED;
        }
    
        if (app_usbd_setup_req_dir(p_setup_ev->setup.bmRequestType) == APP_USBD_SETUP_REQDIR_IN)
        {
            if(vendor_req)
            {
                return app_usbd_vendor_setup(p_inst, p_setup_ev);
            }
            else
            {
                switch (p_setup_ev->setup.bRequest)
                {
                    case APP_USBD_SETUP_STDREQ_GET_STATUS:
                    {
                        return setup_device_req_get_status(p_inst, p_setup_ev);
                    }
                    case APP_USBD_SETUP_STDREQ_GET_DESCRIPTOR:
                    {
                        return setup_device_req_get_descriptor(p_inst, p_setup_ev);
                    }
                    case APP_USBD_SETUP_STDREQ_GET_CONFIGURATION:
                    {
                        return setup_device_req_get_configuration(p_inst, p_setup_ev);
                    }
                    default:
                        /*Not supported*/
                        break;
                }
            }
            return NRF_ERROR_NOT_SUPPORTED;
        }
        else /* APP_USBD_SETUP_REQDIR_OUT */
        {
    ...

Now to handle all these changes the module app_usbd_bos_desc is needed.
The h-file looks like this:

#ifndef APP_USBD_BOS_DESC_H__
#define APP_USBD_BOS_DESC_H__
#include <stdint.h>
#include "app_usbd.h"
#include "app_usbd_core.h"

/**
 * @brief Get bos descriptor.
 *
 * @return BOS descriptor, or NULL if it does not exist.
 * */
uint8_t const * app_usbd_bos_desc_get(void);

/**
 * @brief Get bos length.
 *
 * Function for getting bos length from descriptor (descriptor returned by @ref app_usbd_bos_desc_get).
 *
 * @param[in] p_str BOS descriptor pointer.
 * @return Total descriptor length in bytes.
 */
size_t app_usbd_bos_desc_length(uint8_t const * p_str);

ret_code_t app_usbd_vendor_setup(app_usbd_class_inst_t const * const p_inst,
                        app_usbd_setup_evt_t const * const  p_setup_ev);

#endif

and the c-file like this:

#include "app_usbd_bos_desc.h"

#define NRF_LOG_MODULE_NAME app_usbd_bos_desc
#include "nrf_log.h"
NRF_LOG_MODULE_REGISTER();

#define MSOS20_DESC_LEN (0x1E)
#define BOS_DESC_LEN (0x39)
#define URL_DESC_LEN (0x19)
#define APP_USBD_SETUP_VENDORREQ_GET_MSOS2 0x02
#define APP_USBD_SETUP_VENDORREQ_GET_URL 0x01
#define APP_USBD_SETUP_VENDORREQ_LANDIGPAGE 0x01

uint8_t const m_bos_desc[BOS_DESC_LEN] = { 
  0x05,
  0x0F,
  BOS_DESC_LEN,
  0x00,
  0x02,
  //WebUSB platform capability descriptor
  0x18,
  0x10,
  0x05,
  0x00,
  0x38,
  0xB6,
  0x08,
  0x34,
  0xA9,
  0x09,
  0xA0,
  0x47,
  0x8B,
  0xFD,
  0xA0,
  0x76,
  0x88,
  0x15,
  0xB6,
  0x65,
  0x00,
  0x01,
  APP_USBD_SETUP_VENDORREQ_GET_URL,
  APP_USBD_SETUP_VENDORREQ_LANDIGPAGE,
  //MSOS 2.0 platform capability descriptor
  0x1C,
  0x10,
  0x05,
  0x00,
  0xDF,
  0x60,
  0xDD,
  0xD8,
  0x89,
  0x45,
  0xC7,
  0x4C,
  0x9C,
  0xD2,
  0x65,
  0x9D,
  0x9E,
  0x64,
  0x8A,
  0x9F,
  0x00,
  0x00,
  0x03,
  0x06,
  MSOS20_DESC_LEN,
  0x00,
  APP_USBD_SETUP_VENDORREQ_GET_MSOS2,
  0x00
};

uint8_t const m_msos2_desc[MSOS20_DESC_LEN] = {
  //MOS2.0 descriptor set header
  0x0A,
  0x00,
  0x00,
  0x00,
  0x00,
  0x00,
  0x03,
  0x06,
  MSOS20_DESC_LEN,
  0x00,
  //MOS2.0 compatible ID descriptor
  0x14,
  0x00,
  0x03,
  0x00,
  'W',
  'I',
  'N',
  'U',
  'S',
  'B',
  0x00,
  0x00,
  0x00,
  0x00,
  0x00,
  0x00,
  0x00,
  0x00,
  0x00,
  0x00
};

uint8_t const m_url_desc[URL_DESC_LEN] = {
  //URL descriptor
  URL_DESC_LEN,
  0x03,
  0x01,
  'd',
  'e',
  'v',
  'z',
  'o',
  'n',
  'e',
  '.',
  'n',
  'o',
  'r',
  'd',
  'i',
  'c',
  's',
  'e',
  'm',
  'i',
  '.',
  'c',
  'o',
  'm'
};

uint8_t const * app_usbd_bos_desc_get()
{
  return (uint8_t*) &m_bos_desc;
}

static uint8_t const * app_usbd_bos_desc_get_msos2()
{
  return (uint8_t*) &m_msos2_desc;
}

static uint8_t const * app_usbd_bos_desc_get_url()
{
  return (uint8_t*) &m_url_desc;
}

size_t app_usbd_bos_desc_length(uint8_t const * p_str)
{
  return ((const app_usbd_descriptor_bos_t *)p_str)->sTotalLength;
}

static size_t app_usbd_bos_desc_msos2_length(uint8_t const * p_str)
{
  return MSOS20_DESC_LEN;
}

static size_t app_usbd_bos_desc_url_length(uint8_t const * p_str)
{
  return URL_DESC_LEN;
}

static ret_code_t setup_device_req_get_msos2(
  app_usbd_class_inst_t const * const p_inst,
  app_usbd_setup_evt_t const * const  p_setup_ev)
{
  uint8_t const * p_trans_buff = app_usbd_bos_desc_get_msos2();
  return app_usbd_core_setup_rsp(&p_setup_ev->setup, p_trans_buff, app_usbd_bos_desc_msos2_length(p_trans_buff));
}

static ret_code_t setup_device_req_get_url(
  app_usbd_class_inst_t const * const p_inst,
  app_usbd_setup_evt_t const * const  p_setup_ev)
{
  uint8_t const * p_trans_buff = app_usbd_bos_desc_get_url();
  return app_usbd_core_setup_rsp(&p_setup_ev->setup, p_trans_buff, app_usbd_bos_desc_url_length(p_trans_buff));
}

ret_code_t app_usbd_vendor_setup(
  app_usbd_class_inst_t const * const p_inst,
  app_usbd_setup_evt_t const * const  p_setup_ev)
{
  switch (p_setup_ev->setup.bRequest)
  {
    case APP_USBD_SETUP_VENDORREQ_GET_MSOS2:
    {
      return setup_device_req_get_msos2(p_inst, p_setup_ev);
    }
    case APP_USBD_SETUP_VENDORREQ_GET_URL:
    {
      if(p_setup_ev->setup.wValue.lb == APP_USBD_SETUP_VENDORREQ_LANDIGPAGE)
        return setup_device_req_get_url(p_inst, p_setup_ev);
    }

    default: break;
  }
  return NRF_ERROR_NOT_SUPPORTED;
}

With this WebUSB and WinUSB is working.
If you want to link to a different website you simply change m_url_desc and make sure to change URL_DESC_LEN to the right value (length of the website name + 3).
If you don't want to link to any website set  APP_USBD_SETUP_VENDORREQ_LANDIGPAGE to 0.

I hope that if someone else has this problem this can help him/her.

Finally massive thanks to  for all his tips.

Kind regards,
Johannes

Related