Understanding the CDC ACM Driver in Linux: Bridging USB Serial Devices to TTY Interfaces
Introduction to CDC ACM
The Communication Device Class Abstract Control Model (CDC ACM) is a USB protocol standard that allows devices like modems, serial adapters, and debugging tools to communicate with a host system over USB as virtual serial ports. In Linux, the cdc_acm driver is responsible for detecting these devices, creating /dev/ttyACM* interfaces, and managing data flow between the hardware and user-space applications.
This article explores how the CDC ACM driver works in the Linux kernel, from device detection to TTY integration, using real-world examples and kernel code snippets.
1. Device Identification: How CDC ACM Devices Are Recognized
USB Descriptors and Matching
USB devices declare their capabilities through descriptors. For CDC ACM devices, critical descriptors include:
The Linux CDC ACM driver (drivers/usb/class/cdc-acm.c) binds to devices in two ways:
Example:
// Generic CDC ACM interface match (no VID/PID required)
{
USB_INTERFACE_INFO(
USB_CLASS_COMM, // Class 0x02
USB_CDC_SUBCLASS_ACM, // Subclass 0x02
USB_CDC_PROTO_NONE // Protocol 0x00
)
}
This entry matches any device with CDC ACM-compliant interfaces, even if its VID/PID isn’t explicitly listed in acm_ids.
Case Study: WCH CH55x Adapter
The device with idVendor=1a86 (QinHeng Electronics) and idProduct=55de (a multi-protocol USB-UART adapter) was detected despite not being in acm_ids because its interface descriptors correctly identified it as CDC ACM:
bInterfaceClass 0x02 (Communications)
bInterfaceSubClass 0x02 (ACM)
2. Driver Initialization and Probing
Module Initialization
The driver registers itself with the USB subsystem and allocates a TTY driver:
static int __init acm_init(void) {
// Allocate TTY driver
acm_tty_driver = tty_alloc_driver(...);
// Register USB driver
usb_register(&acm_driver);
}
Probing the Device
When a USB device is plugged in, the kernel calls acm_probe():
Key Data Structures:
struct acm {
struct tty_port port; // TTY port
struct urb *ctrlurb; // Control URB
struct urb *read_urbs[]; // Read URBs
struct acm_wb wb[]; // Write buffers
};
3. Data Flow: From USB to TTY
Reading Data (Device → Host)
Writing Data (Host → Device)
Control Requests
4. TTY Integration
The CDC ACM driver exposes USB devices as standard TTY ports, enabling compatibility with terminal emulators (e.g., screen, minicom) and serial libraries.
TTY Operations
static const struct tty_operations acm_ops = {
.open = acm_tty_open,
.close = acm_tty_close,
.write = acm_tty_write,
.ioctl = acm_tty_ioctl, // Handle termios settings
.tiocmget = acm_tty_tiocmget, // Get modem status
};
Real-World Example
The WCH adapter creates two TTY devices (ttyACM0 and ttyACM1) because it exposes two CDC ACM interfaces (e.g., UART and JTAG).
Workflow (Step-by-Step)
Here's how the kernel detects and configures your USB-to-serial device:
5. Handling Non-Standard Devices
Some devices require quirks due to non-compliant descriptors. These are handled via acm_ids entries:
{ USB_DEVICE(0x1a86, 0x55de), .driver_info = NO_UNION_NORMAL },
Common quirks include:
6. Debugging and Tools
Conclusion
The CDC ACM driver is a critical component in Linux for enabling USB-based serial communication. By leveraging USB class codes and flexible URB management, it supports a wide range of devices, from standard modems to specialized multi-protocol adapters. Understanding its internals helps developers debug issues, add quirks for non-compliant hardware, and integrate custom USB serial solutions seamlessly into the Linux ecosystem.
Further Reading: