USB composite CDC and MSC configuration

The file CdcMscConfig.fth contains a USB configuration for a composite Communications Device Class (CDC) and Mass Storage Class (MSC) application. For CDC and MSC only applications see CdcConfig.fth and MscConfig.fth.

Running composite devices on Windows without using a custom driver for Windows can be problematic. Windows 7, Vista and XP SP3 are fine, but XP SP2 is not. For XP, either upgrade to SP3 or install hotfixes. It seems you need:

We installed SP3 for testing and had to copy the new version of usbser.sys manually from SP3.cab. Use Windows search and copy the new version to replace the one in Windows\System32\Drivers.

The trick for composite devices is to use an Interface Aggregation Descriptor (IAD) for each instance of any class with more than one interface, e.g. CDC. Read the source code for the gory details!

You also need to take care with the start up code for composite devices. USB device start up consists of three steps.

  1. Initialise data buffers, some of which may be in UDATA section, and so will not be initialised by the default MPE initialisation code. This code should also be part of the USB reset action for the class or interface.
  2. The primary initialisation of the USB hardware and code is performed by InitUSB.
  3. Then perform the USB bus connection process using USBconnect.

Your start up code for a composite device then consists of

  initA initB ... InitUSB USBConnect

Versions of this stack from November 2010 onwards include in UsbEP0.fth the word startUSBsys which performs these operations.

Primary Configuration

Note that interface numbers must be a contiguous set starting at 0.

0 equ dbgUHW?   \ -- flag
Set true to get debug information from the USB hardware layer. Some of the debug output must be done using polled serial drivers.

0 equ dbgCore?  \ -- flag
Set true to get debug information from the USB core layer. Some of the debug output must be done using polled serial drivers.

0 equ UsbPowered?       \ -- 0/1 ; bus powered?
Set to one if bus powered.

1 equ UsbDMA?           \ -- 0/1 ; uses DMA?
Set to one if any endpoints other than EP0 use DMA. At the moment we assume that EP0 does not use DMA.

#64 equ /maxpacket0     \ -- u
Maximum size of packets on endpoint 0.

#64 equ /maxpacket      \ -- u
Maximum size of packets on endpoints other than 0.

#32 equ #EPs    \ -- u
Number of endpoints we are are going to deal with. On a 32 bit CPU, there are 32 bits per word. We need separate bits for IN and OUT, so there's a practical maximum here of 32, and USB supports 16 endpoints, each with IN and OUT capability. Endpoint 0 is always IN and OUT capable.

4 equ #USBifs   \ -- u
Number of interfaces we can expand to eventually.

0 equ HID?      \ -- flag
True if a HID device, e.g. mouse, is to be supported.

1 equ CDC?      \ -- flag
True if a communications device class (CDC) is to be supported. The most common of these is a USB serial port.

1 equ MSC?      \ -- flag
True if a mass storage device is to be supported.

0 equ AUDIO?    \ -- flag
True if an audio device is to be supported.

resetUIFs
Force the interface numbers to start at zero.

NextUIF: CdcCIf#        \ -- b
Interface number of the CDC control interface.

NextUIF: CdcDIf#        \ -- b
Interface number of the CDC data interface.

$81 equ CDCIinEP        \ -- ep
Optional Interrupt IN event notification endpoint (CIF).

$85 equ CDCBinEP        \ -- ep
CDC Bulk IN endpoint (DIF).

$05 equ CDCBoutEP       \ -- ep
CDC Bulk OUT endpoint (DIF).

1 equ dbgCDC?           \ -- flag
set non-zero to get debug information from the CDC driver.

CDCIinEP EP>mask equ CDCIinEPMask       \ -- mask
Core layer bit mask for the CDC Interrupt IN endpoint. Used to avoid run-time calculation.

CDCBinEP EP>mask equ CDCBinEPMask       \ -- mask
Core layer bit mask for the CDC bulk IN endpoint. Used to avoid run-time calculation.

CDCBoutEP EP>mask equ CDCBoutEPMask     \ -- mask
Core layer bit mask for the CDC bulk OUT endpoint. Used to avoid run-time calculation.

NextUIF: MscIf#         \ -- b
Interface number of the mass storage class.

$82 equ MscBinEP        \ -- ep
MSC Bulk IN endpoint.

$02 equ MscBoutEP       \ -- ep
MSC Bulk OUT endpoint.

0 equ dbgMSC?           \ -- flag
set non-zero to get debug information from the MSC driver.

MscBinEP EP>mask equ MscBinEPMask       \ -- mask
Core layer bit mask for the MSC bulk IN endpoint. Used to avoid run-time calculation.

MscBoutEP EP>mask equ MscBoutEPMask     \ -- mask
Core layer bit mask for the MSC bulk OUT endpoint. Used to avoid run-time calculation.

USB Descriptors

This section contains the USB descriptors. There is a descriptor for the device, for its configuration, and for various strings. There are various other descriptors, both general and class class specific. Their use is absurdly badly documented and the actual requirement as to which are required is often operating system dependent. If it works with Linux, it will (probably) work with everything else.

Strings are accessed using a "string index". Apart from zero, which has a predefined meaning to define the language, string indices (1..255) are defined by the application. Here the indices are the offset in bytes from the start of a string table. If you need more than 255 bytes of string descriptors, change the index to be the offset into a table of string descriptor addresses.

Create CfgDesc  \ -- addr
The configuration descriptor has a primary configuration, three interfaces, and five endpoints. If there is more than one configuration, additional ones must follow the first one.

create StrDesc  \ -- addr
String descriptor and following strings.

create DevDescDefault   \ -- addr
The device descriptor for Windows and Linux.

create DevDescOSX       \ -- addr
The device descriptor for OSX.

0 value DevDescReqLen   \ -- u
Holds request length of first device descriptor after reset. It is used to auto-select the device descriptor

:noname 0 to DevDescReqLen  ; AtUSBreset
Performed at USB reset.

: DevDesc       \ -- addr
Return the address of the device descriptor. This word uses a kluge to provide operation on Windows, Linux, and versions of OS X before 10.5.6 which recognises the IAD descriptor.

Serial number formatting

If the word unit# is defined, it should return a 32 bit unit serial number. This is used by the USB code to return a serial number to the host. If the word unit# has not been defined, a default fixed serial number is returned.

#34 buffer: UTFbuf      \ -- addr
Holds a string descriptor with up to 16 little-endian Unicode 16-bit characters.

: usbSerialNum  \ -- caddr len
Returns the default device USB serial number if unit# is not defined.

: usbSerialNum  \ -- caddr len
Returns the default device USB serial number if unit# is not defined.

: USBStr@       \ index -- caddr len
Return the USB string descriptor corresponding to the given string index. By convention the following order is used:

0

language identifier - do not change

1

serial number

2

product

3

manufacturer

If you configure your products with serial numbers extracted from non-USB strings, e.g. MAC numbers, modify this word to perform the string extraction as required.