7 Linux usb驱动框架分析
现象:将USB设备接入PC,PC右下角上会弹出"发现xx新设备",例如"发现andriod phone"若PC上没有安装该设备的驱动程序,则会弹出对话框提示"安装驱动程序"。
问1:没有安装设备的驱动程序之前,为什么PC还能发现andriod phone设备呢?
答1:windows系统中已经安装了USB的"总线驱动程序",是"总线驱动程序"发现了新的设备,而提示我们安装的是"设备驱动程序"。
问2:USB设备种类繁多,为何一接入电脑,就可以识别出来?
答2:PC与USB设备都要遵循一些规范。
比如:USB设备插入PC机后,PC会发出"你是什么"?
USB设备回答"我是xxx",且回答的语言必须是中文。
USB总线驱动程序会发出某些命令获取设备的信息(描述符)。
USB设备必须返回"描述符"给PC。
问3:PC机上接有很多的USB设备,如何区分这些设备?
USB接口上有四条线:5V、GND、D+、D-
答3:每一个USB设备在接入到总线上后,USB驱动程序都会给它分配一个固定的编号。
接在USB总线上的USB设备都拥有属于自己的编号(地址),PC发送含有对应USB设备的编号(地址)的命令进行访问。
问4:新接入的USB设备还没有编号,PC如何将总线"分配的编号"告诉对应的USB设备?
答4:新接入的USB设备默认的编号是0,在未分配新的编号前,PC机使用0编号和设备进行通信。
问5:为什么设备一插入PC机,就能被PC机发现了?
答5:PC机的USB口内部,D+和D-都接有15k的下拉电阻,未接入USB设备时为低电平;
USB设备的USB口内部,D+或者D-接有1.5k的上拉电阻,他一接入PC,就会把PC的USB口的D+或者D-拉高,从而通过硬件通知PC有新的USB 设备接入。
USB概念简介
- USB是主从结构
所有的USB传输,都是从USB主机这方发起,USB设备方不能主动发起通信。
- USB的传输类型
a、控制传输:可靠的,时间有保证,比如:USB设备的识别过程
b、批量传输:可靠,不实时,时间没有保证,比如:U盘
c、中断传输(不断的查询实现实时性):可靠,实时,比如:USB鼠标
d、实时传输:不可靠,实时,比如:USB摄像头
- USB传输的对象:端点(endpoint)
端点0用于控制传输,既能输出也能输入。
除了端口0外,每一个端点只支持一个方向的数据传输。
- 每一个端点都有传输类型,传输方向。
- 术语或者程序中的"输入"、"输出"都是针对USB主机的立场说的。
linux内核中USB架构
如下图所示,linux内核中USB驱动采用一种分层架构,由USB总线驱动和USB设备驱动构成。其中,USB总线驱动程序的作用主要包括:a 、识别USB设备;b、查找并安装对应的设备驱动程序;c、提供USB读写函数。
OHCI(Open Host Controller Interface):由Compaq,Microsoft和National Semiconductor创立,支持USB1.1的标准,硬件复杂,软件相对简单,主要用于非x86的USB,如扩展卡、嵌入式开发板的USB主控。
UHCI(Universal Host Controller Interface):Intel主导的USB1.0、USB1.1的接口标准,与OHCI不兼容,其硬件较为简单,软件则比较复杂。
EHCI(Enhanced Host Controller Interface),是Intel主导的USB2.0的接口标准。提供USB2.0的高速功能。
xHCI(eXtensible Host Controller Interface),是最新最火的USB3.0的接口标准,它在速度、节能、虚拟化等方面都比前面3种有了较大的提高。xHCI支持所有种类速度的USB设备(USB 3.0 SuperSpeed,USB 2.0 Low-,Full-,and High-speed,USB 1.1 Low- and Full-speed)。
USB驱动程序框架分析
往开发板上插入一个U盘,借助系统打印出的信息,分析Linux内核系统中USB总线驱动程序是如何运行的。
插上u盘后,终端输出的信息。
拔出u盘后
选择"usb 1-1: new full speed USB device using s3c2410-ohci and address 2"中部分信息,使用grep命令在内核中进行查找,搜索结果如下。
找到drivers\usb\core\hub.c文件,发现是在hub_port_init函数中打印的上述信息。继续搜索,最后列出函数的调用关系如下:
hub_irq
kick_khubd
->wake_up(&khubd_wait); 唤醒khubd_wait
hub_thread
hub_events
wait_event_interruptible (khubd_wait,..) 等待khubd_wait事件
hub_port_connect_change
usb_alloc_dev
dev->dev.bus = &usb_bus_type;
…
choose_address 为新的USB设备分配一个编号(地址)
hub_port_init
-> hub_port_reset
-> hub_set_address 为新的USB设备设置分配的地址编号
-> usb_get_device_descriptor 获取USB的设备描述符
usb_new_device
usb_get_configuration 获取并解析USB设备的配置描述符
device_add 将新的dev放入到usb_bus_ type的dev链表中,
再从usb_bus_ type的driver链表中取出drv进行一一比较,
若匹配成功,则可以调用drv的probe函数。
有关USB设备描述符,请参考:https://www.cnblogs.com/beijiqie1104/p/11725775.html。
进入hub_port_connect_change函数。
- static void hub_port_connect_change(struct usb_hub *hub, int port1,
- u16 portstatus, u16 portchange)
- {
- struct usb_device *hdev = hub->hdev;
- struct device *hub_dev = hub->intfdev;
- ...
- udev = usb_alloc_dev(hdev, hdev->bus, port1);
- ...
- choose_address(udev);
- ...
- status = hub_port_init(hub, udev, port1, i);
- ...
- status = usb_new_device(udev);
- ...
- }
代码第7行,申请一个usb_dev,并将其挂接到usb_bus上。
代码第9行,为usb_dev分配一个地址编号。
代码第11行,初始化hub port端口。
代码第13行,创建一个新的usb device。
查看usb_alloc_dev函数。
- struct usb_device *
- usb_alloc_dev(struct usb_device *parent, struct usb_bus *bus, unsigned port1)
- {
- struct usb_device *dev;
- dev = kzalloc(sizeof(*dev), GFP_KERNEL); //申请usb_dev结构体空间
- ...
- device_initialize(&dev->dev);
- dev->dev.bus = &usb_bus_type; //将设备的总线指向usb_bus_type
- dev->dev.type = &usb_device_type; //设备的类型为usb_device_type
- ...
- return dev;
- }
代码中第6行,分配了一个usb_dev结构体。
代码第10行,将结构体usb_dev的dev.bus指向usb_bus_type。其中usb_bus_type是linux内核中bus_type的一种,与之前的平台总线platform_bus_type类似,具体的定义如下:
其中usb_device_match函数用于实现device与drvier的匹配。
- static int usb_device_match(struct device *dev, struct device_driver *drv)
- {
- /* devices and interfaces are handled separately */
- if (is_usb_device(dev)) { //是否是usb设备
- /* interface drivers never match devices */
- if (!is_usb_device_driver(drv))
- return 0;
- /* TODO: Add real matching code */
- return 1;
- } else { //USB的接口或者驱动drv
- struct usb_interface *intf;
- struct usb_driver *usb_drv;
- const struct usb_device_id *id;
- /* device drivers never match interfaces */
- if (is_usb_device_driver(drv))
- return 0;
- intf = to_usb_interface(dev); //获取usb的接口
- usb_drv = to_usb_driver(drv); //获取usb的驱动
- id = usb_match_id(intf, usb_drv->id_table); //匹配usb驱动的id
- if (id)
- return 1;
- id = usb_match_dynamic_id(intf, usb_drv);
- if (id)
- return 1;
- }
- return 0;
- }
代码第11行,将dev.type指向usb_device_type。其中usb_device_type的定义如下:
紧接着来看看hub_port_connect_change函数中的choose_address函数。
- static void choose_address(struct usb_device *udev)
- {
- int devnum;
- struct usb_bus *bus = udev->bus;
- /* If khubd ever becomes multithreaded, this will need a lock */
- /* Try to allocate the next devnum beginning at bus->devnum_next. */
- devnum = find_next_zero_bit(bus->devmap.devicemap, 128,
- bus->devnum_next);
- if (devnum >= 128)
- devnum = find_next_zero_bit(bus->devmap.devicemap, 128, 1);
- bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1);
- if (devnum < 128) {
- set_bit(devnum, bus->devmap.devicemap);
- udev->devnum = devnum;
- }
- }
代码第9行,在bus->devnum_next到128之间,查找一个非0的编号。
代码第11行,当编号大于等于128的时候,从1开头处开始查找。
代码第14行,设置下次查找的起始位置。
hub_port_init函数
- static int
- hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
- int retry_counter)
- {
- ...
- buf = kmalloc(GET_DESCRIPTOR_BUFSIZE, GFP_NOIO);
- ...
- retval = hub_port_reset(hub, port1, udev, delay);
- ...
- retval = hub_set_address(udev);
- ...
- retval = usb_get_device_descriptor(udev, 8);
- ...
- retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);
- ...
- }
代码第10行,设置usb device的地址编号。
代码第12行,获取设备描述符的前8个字节,这8个字节是USB设备描述符的固定字节数,先将8个字节数据读出后,在解析后续需要再重新读取的字节数。
代码第14行,再次获取完整的设备描述符信息。
usb_new_device函数如下:
- int usb_new_device(struct usb_device *udev)
- {
- ...
- err = usb_get_configuration(udev); //获取USB设备的配置描述符
- ...
- err = device_add(&udev->dev); //将设备加入到usb device设备链表中
- ...
- }
代码第4行,获取设备的配置描述符,其中usb_get_configuration函数定义如下:
- int usb_get_configuration(struct usb_device *dev)
- {
- ...
- int ncfg = dev->descriptor.bNumConfigurations; //获取设备配置描述符的个数
- ...
- for (cfgno = 0; cfgno < ncfg; cfgno++) { //循环
- ...
- result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
- buffer, USB_DT_CONFIG_SIZE); //获取配置描述符的前九个字节
- ...
- length = max((int) le16_to_cpu(desc->wTotalLength),
- USB_DT_CONFIG_SIZE); //获取设备配置描述符的长度
- ...
- result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
- bigbuffer, length); //取出完整的配置描述符
- ...
- result = usb_parse_configuration(&dev->dev, cfgno,
- &dev->config[cfgno], bigbuffer, length); //解析配置描述符
- ...
- }
- }
接着来看看device_add函数。
- int device_add(struct device *dev)
- {
- ...
- dev = get_device(dev); //获取设备
- ...
- if ((error = bus_add_device(dev))) //将设备添加到总线上的device链表中
- ...
- bus_attach_device(dev);
- ...
- }
总结:
linux内核中USB的驱动分为USB总线驱动和USB设备驱动两部分。系统中hub_thread在没有USB连接的时候,处于睡眠状态,一旦主机控制器检测到USB设备插入的时候,会产生hub_irq中断,唤醒hub_thread线程。然后会创建新的USB设备,分配地址编号,获取设备的描述符信息,将其放入到设备链表中,并和drv链表中的id_table进行匹配,若匹配成功则调用drv的probe函数。框架中已经将总线驱动完成,我们需要做的是编写usb的设备驱动,重点就是probe函数。