




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、通用USB设备驱动源码分析Author:aaron前段时间写了篇<qualcomm usb modem驱动小结>的文章, 描述了自己如何为高通的一个usb modem设备写驱动的过程, 最近发现实际上可以使用linux自带的一个叫usbserial的模块作为这个modem的驱动并能良好的工作, 所以写了这片文章来详细的分析下usbserial模块的源码().应该来说, 对于那些仅仅是用USB来通信, 在上层可看作tty设备, 不属于任何USB设备类型, 没有什么流控等的普通USB设备来说都可以使用
2、这个驱动来作为设备驱动程序.下面就来对这样一种通用的驱动程序来进行详细的分析. 不对之处敬请指正! 为了能让usbserail模块支持我的设备, 我必须在命令行上输入如下命令:sudo modprobe usbserial vendor=0x12d1 product=0x1003该命令用特权用户来加载usbserial模块,并把该模块依赖的模块一并加载进系统, 同时它还设置了usbserial的两个参数: vendor, product, 很显然这两个参数是厂商ID和设备ID, 而作用就是用于匹配设备.首先, 当
3、然是要知道usbserial模块由哪些文件编译而成, 这样才能有目的性的去分析其代码. 而要知道其组成当然是去其目录下看Makefile了, 它位于内核源码目录下的./drivers/usb/serial/下./drivers/usb/serial/Makefile:# Makefile for the USB serial device drivers.# # Object file lists. obj-$(CONFIG_USB_SERIAL) &
4、#160; += usbserial.o #编译内核时如何编译该模块 usbserial-obj-$(CONFIG_USB_SERIAL_CONSOLE) += console.ousbserial-obj-$(CONFIG_USB_EZUSB) += ezus
5、b.o usbserial-objs := usb-serial.o generic.o bus.o $(usbserial-obj-y) #OK, 就是usbserial模块的组成了. obj-$(CONFIG_USB_SERIAL_AIRPRIME) += airprime.oobj-$(CONFIG_USB_SERIAL_ANYDATA) &
6、#160; += anydata.o.我们重点看的是usb-serial.c, generic.c, bus.c 在看源码之前我们先说说该模块的原理及整体结构:很简单跟应用层交互的是一个tty设备, 也就是说该模块把USB设备映射成一个tty设备(即在/dev/目录下为该USB设备创建一个tty设备文件), 然后用于可以用minicom之类的串口工具来打开这个设备, 并同设备端的设备通信.对于发送过程: tty设备文件在获取了用户要求发送的
7、数据之后传递到下层usbserial模块的核心层,而该核心层就是将数据打包成USB格式的数据并由USB通信发送到设备端去,对于接收过程: usbserial模块会在该设备打开时就启动一个urb在那等待设备端发数据过来, 收到数据后就push到上层tty设备的缓冲中去, 而tty设备在收到数据后就会给用户,或直接显示在minicom之类的工具上.usb-serial.c 就是usbserial模块的核心, 它主要用来接收设备端发来的数据并传送到上层, 同时也接收来自上层应用的数据,并组装成urb包发送给设备.generic.c 对特定设
8、备单独的操作,相当于是设备自己的驱动程序, 由于很多设备具有通用性, 所以对于没有特殊要求的设备都可以使用这个驱动来作为自己设备的驱动程序. 它有两个参数vendor和product,上面提过了.bus.c 每个usb驱动和设备都必须要归入某一条总线上, 即都是归属于某条总线的,只有这样系统才能从特定一条总线开始找到每个驱动和设备并为他们匹配. 这个文件就是用来模拟一条总线, 而usbserial的每个驱动和设备都会注册到这条总线上来. 好了,是时候分析usbserial模块了. 我们知道当把
9、一个模块加载进系统时会调用这个模块里的一个由module_init()声明的一个初始化函数. usbserial当然也不另外,usb-serial.c:module_init(usb_serial_init); module_exit(usb_serial_exit);没错加载时调用的就是: usb_serial_init().usb-serial.c:struct tty_driver *usb_serial_tty_driver;static int _init usb_serial_init(void)
10、60; int i; int result; /创建一个tty_driver对象, 对应的就是tty设备的驱动. usb_serial_tty_driver = alloc_tty_driver(SERIAL_TTY_MINORS);
11、160; if (!usb_serial_tty_driver) return -ENOMEM; /* Initialize our global data */ for (i = 0; i < SERIAL_TTY_MINORS; +i)
12、 serial_tablei = NULL; /该模块共支持SERIAL_TTY_MINORS个该类型设备. result = bus_register(&usb_serial_bus_type);
13、160; /注册这条serial bus. if (result) err("%s - registering bus driver failed", _FUNCTION_);
14、 goto exit_bus; /初始化tty_driver对象 usb_serial_tty_driver->owner = THIS_MODULE; usb_serial_tty_driver->driver_name
15、= "usbserial" usb_serial_tty_driver->devfs_name = "usb/tts/" usb_serial_tty_driver->name = "ttyUSB" /tty设备文件名以这个开头,后加0,1,2,3,.
16、0; usb_serial_tty_driver->major = SERIAL_TTY_MAJOR; /主设备号 usb_serial_tty_driver->minor_start = 0; usb_serial_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; /设备类型&
17、#160; usb_serial_tty_driver->subtype = SERIAL_TYPE_NORMAL; usb_serial_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS; usb_serial_tty_driver->init_termios = tty
18、_std_termios; usb_serial_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; /赋值tty设备的操作集合,即应用层调用open时最终会调到serial_ops->open里面 tty_set_operations(usb_s
19、erial_tty_driver, &serial_ops); result = tty_register_driver(usb_serial_tty_driver); /注册这个tty驱动 if (result)
20、; err("%s - tty_register_driver failed", _FUNCTION_); goto exit_reg_driver; /* register the USB driver */
21、160; result = usb_register(&usb_serial_driver); /注册一个usb驱动 if (result < 0) err("%s - usb_register failed", _FUNC
22、TION_); goto exit_tty; /* register the generic driver, if we should */ result = usb_ser
23、ial_generic_register(debug); /注册generic驱动程序 if (result < 0) err("%s - registering generic driver failed", _FUNCTION_);
24、60; goto exit_generic; info(DRIVER_DESC); return result; /失败时候的一些反向操作exit_generic:
25、 usb_deregister(&usb_serial_driver); exit_tty: tty_unregister_driver(usb_serial_tty_driver); exit_reg_driver: bus_unregister(&usb_serial_bus_type); exit_bus:
26、60; err ("%s - returning with error %d", _FUNCTION_, result); put_tty_driver(usb_serial_tty_driver); return result;该函数先创建并初始化好了一个tty_driver的对象, 并把该对象注册进系统, 该对象就是tty设备的驱动程序,
27、60;后面我们会看到他是如何于具体tty设备绑定在一起的.usb_serial.c:static struct tty_operations serial_ops = .open = serial_open, .close =
28、60; serial_close, .write = serial_write, .write_room = serial_wri
29、te_room, .ioctl = serial_ioctl, .set_termios = serial_set_termios,
30、0;.throttle = serial_throttle, .unthrottle = serial_unthrottle, .break_ctl =
31、 serial_break, .chars_in_buffer = serial_chars_in_buffer, .read_proc = serial_read_proc,&
32、#160; .tiocmget = serial_tiocmget, .tiocmset = serial_tiocmset,;这个就是tty设备文件对应的操作方法
33、集合, 例如, 应用层调用open函数来打开该设备文件时将最终会走到serial_open里面.usb_serial_init() 还注册了一条总线: usb_serial_bus_type, 这样当有设备连上系统时, 该总线上的驱动就有机会去匹配这个设备. 后面我们会看到generic的驱动就是注册在该总线上的.bus.c:struct bus_type usb_serial_bus_type = .name = &
34、#160; "usb-serial", .match = usb_serial_device_match, /在设备匹配时会调用 .probe = usb_serial_device_probe,
35、 .remove = usb_serial_device_remove,;关于设备匹配过程(probe)可以参考我的另一篇文章.usb_serial_init() 在最后usb_serial_generic_register(debug)来注册generic驱动.generic.c:int usb_serial_generic_register (int _debug) int retval = 0;
36、 debug = _debug;#ifdef CONFIG_USB_SERIAL_GENERIC generic_device_ids0.idVendor = vendor; /保存厂商ID generic_device_ids0.idProduct = product; /保存产品I
37、D generic_device_ids0.match_flags = USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_PRODUCT; /匹配类型 /* register our generic driver with ourselves */
38、; retval = usb_serial_register (&usb_serial_generic_device); /注册驱动 if (retval) goto exit; retval = usb_regist
39、er(&generic_driver); /注册驱动 if (retval) usb_serial_deregister(&usb_serial_generic_device);exit:#endif
40、60;return retval;该函数首先保存了命令通过命令行设备的vendor,product 用于以后设备匹配, 由此我们知道该驱动可以动态支持设备匹配. 接着该函数注册了usb_serial_generic_device驱动.generic.c:struct usb_serial_driver usb_serial_generic_device = .driver =
41、; .owner = THIS_MODULE, .name = "generic", ,
42、; .id_table = generic_device_ids, /匹配用的设备列表, 支持动态匹配 .num_interrupt_in = NUM_DONT_CARE,
43、0;.num_bulk_in = NUM_DONT_CARE, .num_bulk_out = NUM_DONT_CARE, .num_ports = &
44、#160; 1, .shutdown = usb_serial_generic_shutdown,;Usb-serial.c:int usb_serial_register(struct usb_serial_driver *driver) int
45、 retval; fixup_generic(driver); /为driver赋上默认的操作函数 if (!driver->description) driver->description = dr
46、iver->; /* Add this device to our list of devices */ list_add(&driver->driver_list, &usb_serial_driver_list); /加入驱动列表 retva
47、l = usb_serial_bus_register(driver); /把该驱动注册进usb serial bus下 if (retval) err("problem %d when registering driver %s", retval, driver->description
48、); list_del(&driver->driver_list); else info
49、("USB Serial support registered for %s", driver->description); return retval;其中的fixup_generic()函数仅仅是为driver赋上默认的操作函数.Usb-serial.c:#define set_to_generic_if_null(type, function)
50、0; do &
51、#160; if (!type->function) &
52、#160; type->function = usb_serial_generic_#fun
53、ction; dbg("Had to override the " #function
54、160; " usb serial operation with the generic one.");
55、; while (0)static
56、void fixup_generic(struct usb_serial_driver *device) set_to_generic_if_null(device, open); set_to_generic_if_null(device, write); set_to_generic_if_null(device, close);
57、60; set_to_generic_if_null(device, write_room); set_to_generic_if_null(device, chars_in_buffer); set_to_generic_if_null(device, read_bulk_callback);
58、;set_to_generic_if_null(device, write_bulk_callback); set_to_generic_if_null(device, shutdown);即通过上面的usb_serial_register()函数后usb_serial_generic_device的函数集为:usb_serial_generic_device.open = usb_serial_generic_open;usb_serial_generic_device.close = usb_serial_g
59、eneric_close.驱动usb_serial_generic_device将是以后操作tty设备的主要函数.我们会在后面分析. bus.c:int usb_serial_bus_register(struct usb_serial_driver *driver) int retval; driver->driver.bus = &usb_serial_bus_type;
60、; /注册到该bus下 retval = driver_register(&driver->driver); return retval; 最后usb_serial_generic_register()函数注册了一个generic_driver驱动.generic.c:static struct usb_driver generic_driver =
61、 .name = "usbserial_generic", .probe = generic_probe, /匹配函数 .disconnect = usb_ser
62、ial_disconnect, .id_table = generic_serial_ids, /匹配用的设备列表 .no_dynamic_id = 1, /不支持动态匹配;整个初始化过程, 乍
63、一看一下子注册了几个驱动程序, 几个驱动列表, 有的支持动态匹配有的不支持, 感觉很复杂, 其实注册generic_driver驱动主要是为了注册一个generic_probe函数, 而该函数将会在设备连上系统后被调用以来匹配设备. 除此之外该驱动没什么用, 而在这个初始化函数中把vendor,product都保存在了generic_device_ids里, 因此可以肯定以后的匹配将用这个设备列表,而不是generic_serial_ids, 说的更直白些generic_serial_ids其实根本也没什
64、么用. 真正有用的是usb_serial_generic_device驱动, generic.c:static int generic_probe(struct usb_interface *interface, const stru
65、ct usb_device_id *id) const struct usb_device_id *id_pattern; id_pattern = usb_match_id(interface, generic_device_ids); /设备匹配 if (id_pattern !=
66、 NULL) return usb_serial_probe(interface, id); /进一步匹配 return -ENODEV;如果接入系统的设备的vendor和product与我们驱动支持的设备列表匹配则调用usb_serial_probe来进一步匹配.usb_serial_probe函数比较长, 我们一段
67、段的来看usb-serial.c:int usb_serial_probe(struct usb_interface *interface, const struct usb_device_id *id) &
68、#160; struct usb_device *dev = interface_to_usbdev (interface); struct usb_serial *serial = NULL; struct usb_serial_port *port; struct usb_host_interface *iface_desc;
69、 struct usb_endpoint_descriptor *endpoint; struct usb_endpoint_descriptor *interrupt_in_endpointMAX_NUM_PORTS; struct usb_endpoint_descriptor *interrupt_out_endpointMAX_NUM_PORTS;
70、0; struct usb_endpoint_descriptor *bulk_in_endpointMAX_NUM_PORTS; struct usb_endpoint_descriptor *bulk_out_endpointMAX_NUM_PORTS; struct usb_serial_driver *type = NULL;
71、60; int retval; int minor; int buffer_size; int i; int num_interrupt_in = 0; &
72、#160;int num_interrupt_out = 0; int num_bulk_in = 0; int num_bulk_out = 0; int num_ports = 0; int max_endpoints;
73、 type = search_serial_device(interface); /获取该设备匹配的驱动 if (!type) dbg("none matched");
74、; return -ENODEV; .首先是找到合适的驱动程序.usb-serial.c:static struct usb_serial_driver *search_serial_device(struct usb_interface *iface) struct list_head *p;
75、; const struct usb_device_id *id; struct usb_serial_driver *t; /* Check if the usb id matches a known device */ list_for_each(p, &usb_serial_driver_list)
76、160; t = list_entry(p, struct usb_serial_driver, driver_list); id = usb_match_id(iface, t->id_table); /看设备列表是否匹配
77、160; if (id != NULL) dbg("descriptor matches");
78、0; return t; /返回匹配的驱动 return NULL
79、;实际上这边的匹配和generic_probe里的匹配重复了, 因为他们的匹配的设备列表是同一个, 这边主要是为了得到匹配的驱动程序, 根据上面的代码分析我们可以知道这里匹配的驱动是usb_serial_generic_device.接着看usb_serial_probe()usb-serial.c:. serial = create_serial (dev, interface, type); /为该设备创建一个usb_serial对象
80、 if (!serial) dev_err(&interface->dev, "%s - out of memoryn", _FUNCTION_);
81、return -ENOMEM; /* if this device type has a probe function, call it */ if (type->probe) /从上面分析的代码可知这里的probe函数没有赋值
82、; const struct usb_device_id *id; if (!try_module_get(type->driver.owner)
83、60; dev_err(&interface->dev, "module get failed, exitingn"); kfree (serial);
84、; return -EIO; id = usb_match_id(interface, type-&
85、gt;id_table); retval = type->probe(serial, id); module_put(type->driver.owner); &
86、#160; if (retval) dbg ("sub driver rejected device");
87、0; kfree (serial); return retval;
88、; .这段代码可知, 主要是创建一个usb_serial的对象, 用于保存该设备的详细信息, 一般的驱动程序都会为自己匹配的设备创建一个描用于描述该设备的对象. 在以后的所有操作中如读写等都会直接从这个对象里获取相应的信息.usb-serial.c:static struct usb_serial * create_serial (struct usb_device *dev, &
89、#160; struct usb_interface *interface,
90、; struct usb_serial_driver *driver) struct usb_serial *serial; serial = kmalloc (sizeof (*serial),
91、GFP_KERNEL); /闯将该对象 if (!serial) dev_err(&dev->dev, "%s - out of memoryn", _FUNCTION_);
92、 return NULL; /初始化该对象 memset (serial, 0, sizeof(*serial); serial->dev = usb_get_dev(dev); /增加dev的引用计数
93、60; serial->type = driver; serial->interface = interface; kref_init(&serial->kref); return serial;这个函数就是用来创建usb
94、_serial对象的,并把相关信息保存在里面.继续看usb_serial_probe()usb-serial.c:. /* descriptor matches, let's find the endpoints needed */ /* check out the endpoints */ /查找该设备使用的endpoint的描述符, 并检查是否正确
95、60; iface_desc = interface->cur_altsetting; /接口描述符 for (i = 0; i < iface_desc->desc.bNumEndpoints; +i) endpoint = &i
96、face_desc->endpointi.desc; /端点描述符 if (endpoint->bEndpointAddress & 0x80) &&
97、; (endpoint->bmAttributes & 3) = 0x02) /* we found a bulk in endpoint */
98、/bulk in 的端点 dbg("found bulk in on endpoint %d", i);
99、 bulk_in_endpointnum_bulk_in = endpoint; +num_bulk_in;
100、160; if (endpoint->bEndpointAddress & 0x80) = 0x00) && (endpoint->bmAttributes & 3) = 0x02)
101、160; /* we found a bulk out endpoint */ /bulk out的端点
102、0;dbg("found bulk out on endpoint %d", i); bulk_out_endpointnum_bulk_out = endpoint;
103、60; +num_bulk_out; &
104、#160;if (endpoint->bEndpointAddress & 0x80) && (endpoint->bmAttributes & 3) = 0x03)
105、60; /* we found a interrupt in endpoint */ /中断 in 端点 dbg("found interrupt in on endpoint %d", i);
106、 interrupt_in_endpointnum_interrupt_in = endpoint;
107、;+num_interrupt_in; if (endpoint->bEndpointAddress & 0x80) = 0x00) && &
108、#160; (endpoint->bmAttributes & 3) = 0x03) /* we found an interrupt out endpoint */ /中断 out 端点
109、0; dbg("found interrupt out on endpoint %d", i);
110、60;interrupt_out_endpointnum_interrupt_out = endpoint; +num_interrupt_out;
111、; .该段代码主要是获取该设备使用的各个类型及方向的端点描述府, 并保存起来, 关于端点的类型与方向可以参考USB的规范.继续看usb_serial_probe()usb-serial.c:.#if defined(CONFIG_USB_SERIAL_PL2303) | defined(CONFIG_USB_SERIAL_PL2303_MODULE) /* BEGIN HORRIBLE HACK FOR PL2303 */ &
112、#160; /* this is needed due to the looney way its endpoints are set up */ if (le16_to_cpu(dev->descriptor.idVendor) = PL2303_VENDOR_ID) && (le16_to_cpu(de
113、v->descriptor.idProduct) = PL2303_PRODUCT_ID) | (le16_to_cpu(dev->descriptor.idVendor) = ATEN_VENDOR_ID) && (le16_to_cpu(dev->descriptor.idProduct)
114、 = ATEN_PRODUCT_ID) if (interface != dev->actconfig->interface0) /* check out the en
115、dpoints of the other interface*/ iface_desc = dev->actconfig->interface0->cur_altsetting;
116、; for (i = 0; i < iface_desc->desc.bNumEndpoints; +i) endpoint = &iface_des
117、c->endpointi.desc; if (endpoint->bEndpointAddress & 0x80) && &
118、#160; (endpoint->bmAttributes & 3) = 0x03)
119、; /* we found a interrupt in endpoint */
120、60; dbg("found interrupt in for Prolific device on separate interface");
121、60; interrupt_in_endpointnum_interrupt_in = endpoint; +num_interrupt
122、_in;
123、160; /* Now make sure the PL-2303 is configured correctly.
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 宝鸡市麟游县2025-2026学年数学三年级第一学期期末统考试题含解析
- 教育信息化时代的教学变革
- 口腔颌面部肿瘤的预防
- 精通2025年经济法的考生指南试题及答案
- 公共关系学在社会影响中的应用试题及答案
- 现代工程经济质量控制试题及答案
- 银行信用状况证明书(8篇)
- 互联网电商仓储管理系统协议
- 2025年工程项目管理人力资源试题及答案
- 游戏平台使用权转让合同
- DB11∕T 854-2023 占道作业交通安全设施设置技术要求
- 国家职业技术技能标准 6-30-99-00 工业机器人系统操作员 人社厅发2020108号
- 大数据导论(计科2103-4)学习通超星期末考试答案章节答案2024年
- 小儿常见出疹性疾病皮疹图谱和治疗课件
- 女生穿搭技巧学习通超星期末考试答案章节答案2024年
- 《地方导游基础知识》7.3 青海 地方导游基础知识-题库及答案
- 小学美术人教版六年级上册 教案-点的集合
- 浙江省金华市义乌市东阳市2024年小升初英语试卷( 含笔试解析无听力原文无音频)
- 2024年“燃气安全我知道”知识竞赛考试题库-下(判断、填空题)
- 感觉统合教育指导师理论考试复习题库(含答案)
- 《进一步规范管理燃煤自备电厂工作方案》发改体改〔2021〕1624号
评论
0/150
提交评论