Understanding the CDC ACM Driver in Linux: Bridging USB Serial Devices to TTY Interfaces

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:

  • Interface Class: 0x02 (Communications Device Class).
  • Interface Subclass: 0x02 (Abstract Control Model).
  • Protocol: Typically 0x00 (no specific protocol).

The Linux CDC ACM driver (drivers/usb/class/cdc-acm.c) binds to devices in two ways:

  1. Explicit VID/PID Entries: For devices with known quirks or non-standard behavior.
  2. Class/Subclass Matching: For any device declaring itself as CDC ACM-compliant.

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():

  1. Parse USB Descriptors: Identify control/data interfaces and endpoints.
  2. Allocate ACM Structure: Stores device context (URBs, buffers, TTY port).
  3. Set Up URBs:Control URB: Handles modem signals (DTR/RTS).Read/Write URBs: Manage data transfer.
  4. Register TTY Device: Creates /dev/ttyACM* nodes.

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)

  1. Read URBs are submitted asynchronously to receive data.
  2. On completion, acm_read_bulk_callback() pushes data to the TTY layer:tty_insert_flip_string(&acm->port, data, len); tty_flip_buffer_push(&acm->port);

Writing Data (Host → Device)

  1. User-space writes to /dev/ttyACM* are copied to a write buffer.
  2. The buffer is sent via a write URB:acm_start_wb(acm, wb);

Control Requests

  • Line Coding: Baud rate, parity, and stop bits are set via acm_set_line().
  • Modem Signals: DTR/RTS are controlled via acm_set_control().


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:

  • NO_UNION_NORMAL: Missing union descriptor.
  • DISABLE_ECHO: Suppress initial echo.
  • SINGLE_RX_URB: Use one URB for reads (buggy firmware).


6. Debugging and Tools

  • Kernel Logs: Use dmesg to view probe/disconnect events.
  • USB Descriptors: Inspect with lsusb -vd <VID>:<PID>.
  • TTY Configuration: Set baud rate with stty or termios.


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:

  1. USB CDC Specification
  2. Linux Kernel Documentation (cdc-acm)
  3. drivers/usb/class/cdc-acm.c in the Linux kernel source.

要查看或添加评论,请登录

David Zhu的更多文章