How Do You Design USB Composite Device Descriptors for Multiple Interfaces?
Last updated 10 July 2026 · 8 min read
Direct Answer
A USB composite device presents multiple independent interfaces — for example a virtual serial port (CDC-ACM) alongside a HID interface, or a mass storage interface alongside a custom vendor interface — from a single physical USB device with one VID/PID. The host must be able to tell which interfaces belong together as one logical function and route each to the correct class driver, which single-interface descriptors don't provide for automatically. Two mechanisms make this possible: setting the device descriptor's class/subclass/protocol to the special Interface Association Descriptor (IAD) values (0xEF/0x02/0x01) so the host looks for IADs describing which consecutive interfaces form one function, and correctly assigning sequential, non-overlapping interface numbers and endpoint addresses across every interface in the configuration descriptor. Getting either wrong is the most common cause of a composite device where individual interfaces enumerate but the wrong driver binds, or where only one of the expected interfaces appears in the OS.
Detailed Explanation
Most USB firmware examples start from a single-interface device — a CDC-ACM virtual COM port, or a HID device, on its own. Real products frequently need more than one interface from the same physical USB connection: a virtual serial port for logging or configuration alongside a HID interface for a control surface, or a mass-storage interface for firmware update files alongside a vendor-specific interface for the product's own control protocol. This is a composite device, and it introduces two descriptor-design concerns that don't exist for a single-interface device: telling the host which interfaces belong together as one function, and making sure interface and endpoint numbering doesn't collide across the whole device. For the enumeration sequence and general descriptor troubleshooting this builds on, see why is my USB device failing enumeration?
Why Composite Devices Need More Than Just Multiple Interfaces
A USB configuration descriptor can already declare multiple interfaces — that much is standard USB, not composite-specific. The problem composite devices actually solve is grouping: some device classes, most notably CDC-ACM, require two separate USB interfaces (a Communication Class interface for control/status and a Data Class interface for the actual byte stream) that only function correctly as a pair. Nothing in a plain configuration descriptor tells the host "interfaces 0 and 1 are not two independent things — they are one logical CDC function." Without that information, a host's driver-binding logic can attach drivers to each interface independently, which for CDC produces a broken virtual serial port even though both interfaces technically enumerated.
The Interface Association Descriptor (IAD)
The Interface Association Descriptor, added via a USB-IF Engineering Change Notice to USB 2.0, solves exactly this problem. It's a short descriptor placed in the configuration descriptor immediately before the first interface it describes, declaring: starting at interface number N, the next M consecutive interfaces are one function, with a specified class/subclass/protocol for the function as a whole.
To use IADs correctly:
- Set the device descriptor's
bDeviceClassto 0xEF (Miscellaneous),bDeviceSubClassto 0x02, andbDeviceProtocolto 0x01. This specific combination is the signal to the host that it should look for IADs in the configuration descriptor rather than relying solely on the device-level class fields — using a plainbDeviceClassof 0x00 (defer to interface descriptors) alongside IADs is a common and confusing mistake that leads to inconsistent driver binding across host operating systems. - Place one IAD immediately before each multi-interface function's first interface descriptor. For a device with a CDC-ACM function (2 interfaces) followed by a HID function (1 interface), the descriptor order is: IAD for the CDC function → CDC Communication interface → CDC Data interface → HID interface. The HID interface, being single-interface, does not need its own IAD, though some implementations add one for consistency and it is not incorrect to do so.
- Set
bFirstInterfaceandbInterfaceCountcorrectly in each IAD —bFirstInterfacemust match the interface number of the immediately following interface descriptor, andbInterfaceCountmust equal the actual number of consecutive interfaces belonging to that function. A mismatch here is a common cause of a composite device where one function enumerates correctly and the adjacent one silently fails driver binding.
Interface and Endpoint Numbering Across the Whole Device
Every interface in a composite device's configuration descriptor needs a unique bInterfaceNumber, assigned sequentially starting from 0 across the entire device, not restarting per function. Every endpoint (other than endpoint 0, shared for control transfers) needs a unique endpoint address within its direction (IN or OUT) across the entire device — reusing endpoint 1 IN in two different interfaces without deliberately intending endpoint sharing (which composite devices generally don't do) will cause one interface's data to silently interfere with the other's.
This numbering has to be planned across the whole descriptor set, particularly when composing a device from vendor SDK example code for each class — copying a standalone CDC example's descriptor block and a standalone HID example's descriptor block into the same device typically requires renumbering both interfaces and endpoints, since each example assumes it's the only interface in the device and starts numbering from 0 and endpoint 1.
String Descriptors for Interface-Level Identification
Each interface can optionally carry its own iInterface string descriptor index, which some hosts display when listing the device's individual functions (for example, Windows Device Manager showing separate entries per interface under one composite device). While not required for correct enumeration, distinct interface strings make a composite device meaningfully easier to identify and support in the field, especially once a product ships with more than one or two interfaces.
Practical Examples
An industrial data logger exposes a CDC-ACM virtual serial port (for a configuration/diagnostics terminal) and a vendor-specific bulk interface (for high-throughput logged-data download by the manufacturer's own PC application). The device descriptor uses the 0xEF/0x02/0x01 IAD signature; an IAD precedes the two-interface CDC function; the vendor-specific interface (class 0xFF) follows as a single, un-associated interface with its own bulk IN/OUT endpoint pair. On Windows, the CDC function binds automatically to the in-box usbser.sys driver, while the vendor interface requires a Microsoft OS Descriptor pointing Windows at the in-box WinUSB driver so the manufacturer's PC application can access it without shipping a custom signed driver.
A wearable device combines a HID interface (for a proprietary low-latency sensor-streaming protocol implemented as a vendor-defined HID report, avoiding the need for any custom driver at all since HID has universal OS support) with a DFU interface for firmware updates over USB. Both are single-interface functions and neither strictly requires an IAD, but the firmware still must ensure the two interfaces use non-overlapping interface numbers and endpoint addresses, and that SET_CONFIGURATION correctly activates both simultaneously as part of the same configuration rather than treating them as alternate configurations the host would have to choose between.
Design Considerations
- Validate composite descriptors against Windows first, since its composite driver (
usbccgp.sys) is the strictest of the three major desktop USB stacks about IAD correctness and device-class-field consistency — a descriptor set that enumerates cleanly on Linux or macOS can still fail driver binding on Windows. - Plan interface and endpoint numbering across the whole device up front, especially when combining descriptor blocks copied from separate single-function vendor SDK examples — each example typically assumes it owns interface 0 and endpoint 1, and both need renumbering to coexist.
- Use WinUSB via a Microsoft OS Descriptor for any vendor-specific interface, rather than shipping a custom signed kernel driver, unless there's a specific reason the interface needs kernel-level access.
- Keep
wTotalLengthin the configuration descriptor consistent with the full set of interfaces, IADs, and endpoints — a composite device's total descriptor length is easy to get wrong when combining descriptor blocks from multiple sources, and the resulting truncation shows up as a generic descriptor-parsing failure that's hard to trace back to the specific missing bytes. - Zeus Design's embedded firmware team develops composite USB device firmware — combining CDC, HID, mass storage, and vendor-specific interfaces — for embedded products.
Common Mistakes
- Omitting the Interface Association Descriptor for a multi-interface function like CDC-ACM. Without it, drivers can bind to the Communication and Data interfaces independently and incorrectly, producing a virtual serial port that doesn't work even though both interfaces technically enumerated.
- Leaving
bDeviceClassat 0x00 while also including IADs, or setting it incorrectly instead of the required 0xEF/0x02/0x01 signature — this inconsistency causes unpredictable, host-dependent driver-binding behaviour rather than a clean failure that's easy to diagnose. - Reusing interface numbers or endpoint addresses when combining descriptor blocks from separate single-function examples. This is the single most common composite-device bug when assembling a new device from existing vendor sample code for each class.
- Getting
bFirstInterfaceorbInterfaceCountwrong in an IAD. A mismatch here typically causes one function in the composite device to work while an adjacent one fails driver binding, which is confusing to debug because the failing function's own descriptors may be entirely correct in isolation. - Assuming a descriptor set validated on Linux or macOS is complete. Test composite device driver binding on Windows specifically before considering the descriptor design finished, given how much stricter its composite driver is about descriptor correctness.
Frequently Asked Questions
- Do I always need an Interface Association Descriptor for a composite device?
- Not always — it depends on whether any of the device's functions are themselves multi-interface. A single-interface HID device combined with a single-interface mass storage interface doesn't strictly require an IAD, because each interface is self-contained and the host can bind a driver to each independently based on its own class/subclass/protocol fields. An IAD becomes mandatory when one logical function spans multiple interfaces that must be treated as a unit — the standard example is CDC-ACM, where the Communication Class interface (AT-command-style control) and the Data Class interface (the actual serial data) are two separate USB interfaces that only make sense together. Without an IAD telling the host these two interfaces are one function, drivers can bind to them independently and incorrectly, or Windows can fail to load the correct composite CDC driver at all.
- Why does Windows sometimes fail to recognise a composite device that works fine on Linux and macOS?
- Windows' built-in USB class drivers (usbccgp.sys, the generic composite device driver) are comparatively strict about descriptor correctness — a missing IAD, an incorrect bDeviceClass at the device level (it must be 0xEF for IAD-based composite devices, not 0x00), or a malformed CDC functional descriptor set will often cause Windows to fail driver binding for one or more interfaces even though the interfaces enumerate correctly at the protocol level. Linux and macOS USB stacks are frequently more permissive about minor descriptor irregularities and will bind drivers that Windows rejects. Always validate a composite device against Windows specifically before assuming the descriptor set is complete, since it's the strictest of the three major host stacks in practice.
- Can I add a custom vendor-specific interface alongside standard USB classes like CDC or HID?
- Yes, and it's a common pattern — for example, a device exposing a CDC-ACM virtual serial port for logging plus a vendor-specific interface for a proprietary control protocol used only by the manufacturer's own configuration tool. The vendor-specific interface uses class code 0xFF (vendor-specific) and requires either a custom driver (WinUSB is the standard modern approach on Windows, avoiding a signed kernel driver) or a INF file directing Windows to bind the generic WinUSB driver to that specific interface via a Microsoft OS Descriptor. The standard-class interfaces (CDC, HID) still bind to their normal in-box drivers independently, since interface numbering and IADs let each interface's class binding be resolved separately within the same composite device.
References
Related Questions
Why Is My USB Device Failing Enumeration?
USB enumeration failures usually trace to one of five causes: descriptor errors, VID/PID conflicts, clock accuracy, D+ pull-up timing, or power draw.
What Is USB?
USB is a host-device protocol for PC connectivity and power delivery. Learn about descriptors, CDC-ACM, HID, DFU classes, and Full Speed vs High Speed.
What Is the Difference Between USB 2.0, USB 3.x, and USB4?
USB 2.0, USB 3.x, and USB4 compared: speed tiers, connector evolution, and which version and PHY to target for an embedded design.
How Do You Implement USB Power Delivery in an Embedded Product?
How to implement USB Power Delivery in an embedded product: PD controller ICs, PDO negotiation, voltage/current profiles, and sink-side design.
How Do You Use the STM32 DFU Bootloader to Flash Firmware?
The STM32 DFU bootloader lets you flash firmware over USB without a debug probe. Learn how to enter DFU mode, use dfu-util, and fix common detection issues.
How Do You Use the nRF52840 USB Port and Update Firmware Over DFU?
Use the nRF52840 native USB port in Zephyr NCS: CDC ACM serial, USB DFU class setup, MCUboot dual-slot partitioning, image signing, and DFU trigger workflow.