如何在内核空间直接读取CDC协议模拟TTY的USB stick数据
CDC模拟串口USB Stick内核空间直接读取数据方案
实现思路
无需完全从零编写全新USB驱动,优先基于内核原生cdc_acm驱动做定制修改,是成本最低、兼容性最高的方案,仅在设备存在私有CDC扩展的场景下考虑独立编写驱动。
方案1:修改内核cdc_acm驱动导出内核态数据
内核自带的drivers/usb/class/cdc-acm.c驱动已经完整实现了CDC ACM类设备的枚举、URB收发、串口协议解析逻辑,只需在数据接收路径插入自定义留存逻辑即可:
- 找到驱动中
acm_rx_bulk函数,该函数是USB批量端点接收数据的回调入口,所有设备上传的原始数据都会先进入该函数处理 - 在函数将数据拷贝到tty层缓存之前添加你的自定义逻辑,示例修改如下:
// 原有代码:获取URB实际接收数据长度 int count = urb->actual_length; if (count == 0) goto exit; /* 新增自定义逻辑开始 */ unsigned long flags; // 提前初始化自旋锁保护自定义缓存区,防止并发访问冲突 spin_lock_irqsave(&custom_buffer_lock, flags); // 将数据拷贝到你预先分配的内核虚拟地址缓存区 memcpy(custom_kernel_buffer, urb->transfer_buffer, min(count, CUSTOM_BUF_MAX_LEN)); // 可按需添加时间戳标记、数据预处理逻辑 spin_unlock_irqrestore(&custom_buffer_lock, flags); /* 新增自定义逻辑结束 */ // 原有代码:将数据送入tty层,不影响原有/dev/ttyACM*设备的用户态访问能力 tty_insert_flip_string(&acm->port, urb->transfer_buffer, count); tty_flip_buffer_push(&acm->port);
- 如果需要其他内核模块调用该数据,可导出访问接口:
// 内核态数据读接口 int read_cdc_stick_data(u8 *out_buf, int read_len) { unsigned long flags; int copy_len; spin_lock_irqsave(&custom_buffer_lock, flags); copy_len = min(read_len, CUSTOM_BUF_MAX_LEN); memcpy(out_buf, custom_kernel_buffer, copy_len); spin_unlock_irqrestore(&custom_buffer_lock, flags); return copy_len; } EXPORT_SYMBOL_GPL(read_cdc_stick_data);
- 最后修改驱动的
id_table仅匹配你的设备VID/PID,避免影响其他CDC ACM设备,重新编译模块替换系统原有cdc_acm模块即可生效。
方案2:独立编写自定义USB CDC驱动
如果不希望修改公共内核驱动,可独立编写专属驱动:
- 驱动初始化时调用
usb_register注册USB驱动,在id_table中填写你的设备VID、PID作为匹配规则 - 在
probe回调中完成端点查找、接收URB初始化,提交URB到USB核心 - 在URB接收回调中直接处理设备上传数据,存入自定义缓存区并导出访问接口
- 该方案完全不依赖
cdc_acm驱动,可自主控制全流程,适合有高实时性、特殊安全要求的场景。
关键注意事项
- URB回调函数运行在原子上下文,不能调用任何可能休眠的函数,内存分配必须使用
GFP_ATOMIC标记 - 多核心并发访问共享缓存区必须加锁,避免数据错乱
- 所有内存拷贝操作必须做边界检查,避免越界访问导致内核崩溃
内容的提问来源于stack exchange,提问作者ThomasS
相关产品推荐
相关产品推荐

