讲述usb设备插入到设备创建过程的源码分析,以及以中断传输方式的鼠标驱动为例,讲述USB驱动实现流程。
USB设备创建过程
当外部插入USB设备后,系统都做了哪些工作?总的来说就是:插入USB设备后,USB控制识别到USB设备,会向系统触发一中断,在中断函数中唤醒休眠线程,在线程中获取USB设备信息,并创建USB设备。
该过程可参考hub.c 文件。USB控制器触发中断入口函数:hub_irq。整个调用流程如下:
->hub_irq //产生中断(由USB控制器程序完成中断触发)
->kick_khubd(hub);
->wake_up(&khubd_wait); //唤醒khubd_wait,khubd线程继续执行
hub_thread 循环线程里执行如下:
->hub_thread
->wait_event_freezable(khubd_wait, !list_empty(&hub_event_list) //khubd_wait列队被唤醒,开始往下执行↓
->hub_events //遍历获取hub集线器上的ubs device
-> hub_port_connect_change(hub, i, portstatus, portchange); 锁定这个usbhub
->udev = usb_alloc_dev(hdev, hdev->bus, port1); 申请分配一个USB设备udev,
->dev->dev.type = &usb_device_type;指定总线为usb_device_type(方便后续match用)
->choose_devnum(udev);获取一个空余的USB编号(地址)
->status = hub_port_init(hub, udev, port1, i);
-> hub_set_address(udev, devnum);把编号地址写进usb (udev->devnum)
->usb_get_device_descriptor(udev, 8); 获取该硬件的设备描述符
->usb_new_device(udev);
->usb_enumerate_device(udev); 获取并解析设备/接口描述符等信息
-> device_add(&udev->dev); add设备。把该设备添加到USB总线
从上述的流程中可以看到,内核获取了USB几个关键参数:设备描述符;配置描述符;接口描述符等。这几个信息的结构体从属关系位于内核里ch9.h:
一个USB设备描述符:(包含厂家ID 信息,device ID,配置描述个数…) usb_device_descriptor
-》配置描述(属性,接口个数,配置信息等…) usb_config_descriptor
-》 接口描述(类/子类信息,协议,端口数量,接口含义 :如一个Usb同时有录音,播放…) usb_interface_descriptor
(如USB鼠标接口描述的类为:人体工学设备类HID,子类:1,协议:鼠标)
-》 端点信息(输入/输出,实时,传输类型,包大小,消息长度…) usb_endpoint_descriptor
最后执行的device_add,会将dev会放入usb总线的dev链表,然后调用总线usb_device_match函数,遍历比较各USB驱动的idtable,匹配成功后则会调用对应的usb driver驱动的probe函数(这个过程是linux系统中常用的总线 -设备 - 驱动原理)
驱动的idtable匹配方法有2种类型来匹配:
- 方法一:通过接口描述:USB_INTERFACE_INFO(类,子类,协议) 完成device匹配
- 方法二:通过厂家ID 和device ID:USB_DEVICE(厂家ID ,device ID) 完成device匹配
USB驱动编写(中断传输方式)
USB设备的整个创建过程以及总线匹配都是系统自动完成的,因此需要用户做的仅是对应USB设备驱动的编写。
比如:一个鼠标类型的USB设备。
-
插入设备后会匹配到对应驱动,而后调用probe函数,probe函数需要初始化:注册需要产生的input事件;Urb(USB数据传输的载体)的分配以及初始化;绑定urb通信的中断函数。
-
当外部有USB数据输入后,USB控制器收到数据会自动触发urb通信的中断函数。在中断函数里接收urb数据并解析,完成需要的操作(如点击,移动等事件上报),因此该中断函数是USB功能实现的重要主体。
-
当USB设备拔出后会调用disconnect,主要做一些移除,注销,释放操作。
因此驱动涉及:probe;中断函数;disconnect函数,三部分实现架构大致如下:
(具体可参考usbmouse.c范例)
probe(struct usb_interface *intf, const struct usb_device_id *id)函数:
->
//根据接口获取USB设备
struct usb_device *dev = interface_to_usbdev(intf);
//根据接口获取端点
interface = intf->cur_altsetting;
endpoint = &interface->endpoint[0].desc;
/*input 事件3要素:申请,配置,注册*/
input_dev = input_allocate_device(); //申请设备
__set_bit(EV_KEY,input_dev->evbit); //配置事件类型:按键
__set_bit(EV_REL,input_dev->evbit); //配置事件类型:位移
__set_bit(REL_X,input_dev->absbit); //配置具体事件:x方向相对位移
...
input_register_device(input_dev); //注册事件
/*定义数据传输3要素:端点长度,源地址,目的地址*/
len=endpoint->wMaxPacketSize; //自定义数据长度,这里定义为端点最大包大小
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); //端点源地址
vir_addr=usb_alloc_coherent(dev, len, GFP_ATOMIC, &dma_phy_addr); //目的地址,申请一段连续物理内存作为目的地址,长度为len
//申请urb - USB REQUEST BLOCK(定义数据传输产生中断函数)可以理解urb为usb数据通信载体
urb_t =usb_alloc_urb(0, GFP_KERNEL);//申请URB
usb_fill_int_urb(urb_t, dev, pipe, NULL, //填充urb,中断传输方式
(maxp > 8 ? 8 : maxp),
test_irq_function, NULL, endpoint->bInterval); //中断函数为test_irq_function
即:USB数据传输到USB控制器,USB控制器会实时扫描数据,有数据会触发一中断,NULL为中断传入参数
usb_submit_urb(urb_t, GFP_KERNEL);//提交urb
===========================================================================================
test_irq_function urb(struct urb *urb) 中断函数(即:USB数据传输到USB控制器,USB控制器会实时扫描数据,有数据会触发中断)
->
data=urb->context->data
input_report_key(dev, BTN_LEFT, data[0] & 0x01); //解析数据,上报事件(BTN_LEFT:左键)
//input_report_rel(dev, REL_X, data[1]);//位移上报
...
input_sync(dev); //事件同步
usb_submit_urb(urb, GFP_ATOMIC); //提交urb
===========================================================================================
disconnect(struct usb_interface *intf)函数:(USB设备拔出后调用)
->
usb_kill_urb(urb_t);
input_unregister_device(input_dev);
usb_free_urb(urb_t);
usb_free_coherent(interface_to_usbdev(intf), len, vir_addr, &dma_phy_addr);
上述驱动讲述的是USB鼠标类型驱动实现,采用的中断传输方式,结合input事件实现。USB还涉及储存,音视频等类型,其传输和实现方式各有区别。因此需要了解如下几种传输方式的概念:
- 中断: 传输固定少量数据,单向输入的:鼠标,键盘等,如上就是中断传输方式。
- 控制:控制传输是双向传输,数据量通常较小。
- 批量:通常用于USB存储器,大块数据传输。
- 等时: 也是批量数据传输,但确保时效性,不保证数据是否到达,如:音视频设备。
本文主要介绍中断传输方式的实现 ,其他传输方式在USB驱动里实现框架类似。