author: hjjdebug
date: 2026年 05月 15日 星期五 18:17:21 CST
descrip: descrip: usb 串行口驱动庖丁解牛2: 实现/dev/ttyUSB0设备文件框架
文章目录
- 1. 代码说明
- 2 代码
- 3 测试:
- 4. 带hack代码:
- 5. 测试:
1. 代码说明
由于看pl2303真实驱动表示看不懂,所以才通过一个系列来彻底解剖它的架构.
结合庖丁解牛1来看本博客庖丁解牛2.
用一个宏 module_usb_serial_driver 包装了模块初始化及逆初始化函数.
- 宏的展开
module_usb_serial_driver(serial_drivers, pl2303_id_table);
展开结果:
static int usb_serial_module_init(void) { return usb_serial_register_drivers(serial_drivers, “pl2303”, pl2303_id_table); }
static __inittest(void) { return usb_serial_module_init; }
int init_module(void)attribute((alias(“usb_serial_module_init”)));;
static void usb_serial_module_exit(void) { usb_serial_deregister_drivers(serial_drivers); }
static __exittest(void) { return usb_serial_module_exit; }
void cleanup_module(void)attribute((alias(“usb_serial_module_exit”)));;;
意义: 这个宏包含了2个函数,
一个公开的导出函数init_module, 它实际上就是usb_serial_module_init
另一个公开导出函数cleanup_module, 它实际地址就是usb_serial_module_exit 函数
你可以通过nm pl2303.o 文件得到证实.
第2, 它实现了这2个函数, 分别是调用
usb_serial_register_drivers(serial_drivers, “pl2303”, pl2303_id_table); //调用了底层的注册函数
usb_serial_deregister_drivers(serial_drivers); 调用了底层的注销函数
在驱动中,如果你没有定义probe函数,绑定成功后,它就不调用probe 函数了, 但如果定义了.calc_num_ports,
它会调用这个函数, 返回1,它会创建一个/dev/ttyUSB0, 返回2,它会创建2个文件. /dev/ttyUSB0,/de/ttyUSB1
显然,一个硬件应该只返回1.
- /dev/ttyUSB0 设备节点文件的生成.
如此简单, 你要在driver 变量中定义.calc_num_ports 函数, 返回1就可以了.
不过这个文件节点只是个壳子,还不能正常工作, 打开,读写都不会正常,因为目前还没有函数支撑它.
2 代码
$ cat pl2303.c#include<linux/init.h>#include<linux/module.h>#include<linux/usb.h>#include<linux/tty.h>#include<linux/usb/serial.h>// PL2303 VID/PID#definePL2303_VID0x067B#definePL2303_PID0x2303// 设备匹配表staticconststructusb_device_idpl2303_id_table[]={{USB_DEVICE(PL2303_VID,PL2303_PID)},{}};MODULE_DEVICE_TABLE(usb,pl2303_id_table);// 端口数量计算staticintpl2303_calc_num_ports(structusb_serial*serial,structusb_serial_endpoints*epds){if(!epds->bulk_in||!epds->bulk_out){dev_err(&serial->interface->dev,"缺少批量端点\n");return-ENODEV;}dev_info(&serial->interface->dev,"端点解析完成,单端口PL2303\n");return1;}// 驱动单体定义staticstructusb_serial_driverpl2303_serial_driver={.driver={.name="pl2303-step2",},.id_table=pl2303_id_table,.calc_num_ports=pl2303_calc_num_ports,};// 5.4内核强制要求:驱动指针数组staticstructusb_serial_driver*constserial_drivers[]={&pl2303_serial_driver,NULL};// 官方标准注册module_usb_serial_driver(serial_drivers,pl2303_id_table);MODULE_LICENSE("GPL");MODULE_DESCRIPTION("PL2303 第二步 5.4内核 实现端口计算,实现生成设备文件");3 测试:
拔出USB设备
[ 27.863495] usb 1-1: USB disconnect, device number 2
[ 27.866327] pl2303-step2 ttyUSB0: pl2303-step2 converter now disconnected from ttyUSB0
[ 27.871035] pl2303 1-1:1.0: device disconnected
插入USB设备
[ 46.132395] usb 1-1: new full-speed USB device number 3 using uhci_hcd
[ 46.284119] usb 1-1: New USB device found, idVendor=067b, idProduct=2303, bcdDevice= 2.02
[ 46.288389] usb 1-1: New USB device strings: Mfr=0, Product=0, SerialNumber=0
[ 46.292541] pl2303 1-1:1.0: 端点解析完成,单端口PL2303 **********
[ 46.294571] pl2303 1-1:1.0: pl2303-step2 converter detected
[ 46.296963] usb 1-1: pl2303-step2 converter now attached to ttyUSB0
实验成功.
我们插入usb设备,自动执行注册的pl2303_calc_num_ports 函数, 返回1, 就生成了/dev/ttyUSB0.
我们只打印了一行信息,其它是系统打印的.
老式 PL2303 端点有错位问题
所以, 在端口计数函数里判断是否触发 Hack
设定设备特征标记 quirks, 以此变量来区分是否需要走Hack函数
所谓hack 就是黑客, 所谓quirks, 就是怪僻,嗜好,特征. 为啥加这段代码, 就是因为设备端设计不好,需要驱动端为它擦屁股. 这样才能走顺. 以后慢慢搞清了数据交互,会更清晰.
4. 带hack代码:
$ cat pl2303.c#include<linux/tty.h>#include<linux/init.h>#include<linux/module.h>#include<linux/usb.h>#include<linux/usb/serial.h>// PL2303 基础ID#definePL2303_VID0x067B#definePL2303_PID0x2303// 设备特殊特性标记#definePL2303_QUIRK_ENDPOINT_HACKBIT(0)staticconststructusb_device_idpl2303_id_table[]={{USB_DEVICE(PL2303_VID,PL2303_PID)},{}};MODULE_DEVICE_TABLE(usb,pl2303_id_table);// 私有数据结构体,存放quirks标记structpl2303_private_data{unsignedlongquirks;//quirks, 怪僻, 嗜好. 用这个字节标记是否需要hack等};// 端点Hack核心函数:跨接口抓取中断输入端点, hack 是黑客的意思, 破解的意思,需要修复一些东西staticintpl2303_endpoint_hack(structusb_serial*serial,structusb_serial_endpoints*epds){structusb_device*dev=serial->dev;structusb_interface*intf=serial->interface;structusb_host_interface*iface_desc;structusb_endpoint_descriptor*ep;unsignedinti;pr_info("come to endpoint_hack.");// 如果串行口的接口是接口0,不需要调整,直接返回if(intf==dev->actconfig->interface[0])return0;// 当前不是接口0,才需要去接口0捞端点, 这是pl2303设备描述符混乱造成的,// 我们在这里给他顺过来. 把描述符存入对应中断入地址dev_info(&intf->dev,"启用PL2303端点Hack,跨接口查找中断端点\n");iface_desc=dev->actconfig->interface[0]->cur_altsetting;for(i=0;i<iface_desc->desc.bNumEndpoints;i++){ep=&iface_desc->endpoint[i].desc;if(!usb_endpoint_is_int_in(ep))continue;// 存入中断端点数组if(epds->num_interrupt_in<ARRAY_SIZE(epds->interrupt_in))epds->interrupt_in[epds->num_interrupt_in++]=ep;}return0;}// 计算端口数量,同时判断执行端点补丁staticintpl2303_calc_num_ports(structusb_serial*serial,structusb_serial_endpoints*epds){structpl2303_private_data*priv;unsignedlongquirks=0;// 临时标记启用端点hack,实际可按芯片型号区分quirks|=PL2303_QUIRK_ENDPOINT_HACK;// 分配私有数据并保存标记priv=devm_kzalloc(&serial->interface->dev,sizeof(*priv),GFP_KERNEL);if(!priv)return-ENOMEM;priv->quirks=quirks;usb_set_serial_data(serial,priv);// 满足条件则执行端点hackif(quirks&PL2303_QUIRK_ENDPOINT_HACK)pl2303_endpoint_hack(serial,epds);// 基础端点校验if(!epds->bulk_in||!epds->bulk_out){dev_err(&serial->interface->dev,"缺少批量收发端点\n");return-ENODEV;}dev_info(&serial->interface->dev,"端点解析完成,单USB串行口就绪\n");// 固定返回1 代表1路串行口return1;}// 串口驱动结构体staticstructusb_serial_driverpl2303_serial_driver={.driver={.name="pl2303-step3",},.id_table=pl2303_id_table,.calc_num_ports=pl2303_calc_num_ports,};// 驱动数组 匹配内核参数要求staticstructusb_serial_driver*constserial_drivers[]={&pl2303_serial_driver,NULL};module_usb_serial_driver(serial_drivers,pl2303_id_table);MODULE_LICENSE("GPL");MODULE_DESCRIPTION("PL2303 版本三:端点Hack + 私有quirks标记");5. 测试:
/host # insmod pl2303.ko
[ 3518.926739] usbcore: registered new interface driver pl2303
[ 3518.928630] usbserial: USB Serial support registered for pl2303-step3
[ 3518.930731] come to endpoint_hack. *************************
[ 3518.930734] pl2303 1-1:1.0: 端点解析完成,单USB串行口就绪
[ 3518.933934] pl2303 1-1:1.0: pl2303-step3 converter detected
[ 3518.936012] usb 1-1: pl2303-step3 converter now attached to ttyUSB0
没有打印出 “启用PL2303端点Hack,跨接口查找中断端点”, 说明接口是0,还没有走到其它端口.