一、 USB驱动程序之概念介绍(第十七课/第一节)
现象:
在WINDOWS下,把USB设备接到PC机上:
- 右下角会弹出"发现什么USB新设备"。如发现"android phone"。
- 跳出一个对话框,提示安装驱动程序。
问1:既然还没有"驱动程序",为何能知道是"android phone"?
答1:Windows里已经有了USB的总线驱动程序,接入USB设备后,是"总线驱动程序"知道你是"android phone"。提示你安装的是"设备驱动程序"。 USB总线驱动程序负责:识别USB设备,给USB设备找到对应的驱动程序。
问2:USB设备种类非常多,为什么一接入电脑,就能识别出来?
答2:PC和USB设备都得遵守一些规范。比如:USB设备接入电脑后,PC机就会发出"你是什么?" USB设备就必须回答"我是xxx"。 USB总线驱动程序会发出某些命令想获取设备信息(描述符),USB设备必须返回(描述符)给PC机。
问3:PC机上接有非常多的USB设备,怎么分辨它们?
答3:USB接口只有四根线:5V,GND,D-,D+ 每一个USB设备接入PC机时,USB总线驱动程序都会给它分配一个编号,接在USB总线上的每一个USB设备都有自己的编号(地址),PC机想访问某个USB设备时,发出的命令都含有对应的编号(地址)。
问4:USB设备刚接入PC机时,还没有编号,那么PC机怎么把"分配的编号"告诉它?
答4:新接入的USB设备的默认编号是0,在未分配新编号前,PC机使用0编号和它通信。
问5:为什么一接入USB设备,PC机就能发现它?
答5:PC机的USB口内部,D-和D+都接有15K的下拉电阻,未接USB设备时为低电平;USB设备的USB口内部,D-或D+接有1.5K的上拉电阻。当USB设备一接入PC机,就会把PC的USB口的D-或D+拉高,从硬件的角度通知PC机有新设备接入
其他概念:
一、 USB是主从结构的: 所有的USB传输,都是从USB主机这方发起,USB设备没有"主动"通知USB主机的能力。 例:USB鼠标滑动一下立刻产生数据,但是它没有能力通知PC机来读数据,只能被动的等待PC机来读取。 二、 USB的传输类型: a.控制传输:可靠,实时,比如:USB设备的识别过程 b.批量传输:可靠,时间没有保证,比如:U盘(从PC机上删除U盘时有时快有时慢) c.中断传输:可靠,实时,比如:USB鼠标。(只是借助了中断的概念,实质是用USB主机查询方式来实现实时性,不断读USB鼠标。USB设备并不能发起中断,USB设备并没有发起通知给USB主机的能力。) d.实时传输:不可靠,实时,比如:USB摄像头。(数据的不可靠可以接受,如果要可靠,可能因为某一帧发送错误时会因可靠机制来重传,就会带来滞后感。) 三、 USB传输的对象:端点(endpoint) 我们说"读U盘"、"写U盘",可以细化为:把数据写到U盘的端点1,从U盘的端点2里读出数据;除了端点0外,每一个端点只支持一个方向的数据传输。端点0用于控制传输,既能输入也能输出。 四、 每一个端点都有(只有一个)传输类型,传输方向 五、 术语里、程序里说的输入(IN)、输出(OUT)都是基于USB主机的立场说的。比如鼠标的数据是从鼠标传到PC机,对应的端点称为"输入端点"。(对于USB主机来说这是输入)。 六、 USB总线驱动程序的作用: a. 识别USB设备。 b. 查找并安装对应的设备驱动程序。 c. 提供USB读写函数。(只是提供读写函数,数据的含义不知道。就像"送信人"一样,不了解数据含义。USB设备驱动程序知道数据含义。)
USB驱动程序框架:
二、 USB驱动程序之USB总线驱动程序(第十七课/第二节)
USB主机控制器有三种规范:1.UHCI;2.OHCI;3.EHCI
UHCI:intel 适用于(USB1.1)低速(1.5Mbps)/(USB2.0)全速(12Mbps) (当说到USB2.0还得分全速和高速) OHCI:microsoft 适用低速/全速 EHICI: 适用于高速(480Mbps)
intel做芯片的,用UHCI这套规范做出来的主机控制器硬件功能强大,软件实现的就简单点;相反,microsoft做软件的,用OHCI这套规范做出来的硬件会差一点,但是软件会复杂点。
把USB设备(手机)接到开发板上,查看输出信息:
拔掉
这里的(full speed)会不可靠,速度的种类可能是"%s",所以在内核驱动目录下搜索"USB device using"
HUB 的概念:
每一个USB主机控制器都自带了一个USB HUB,HUB上接有USB设备,可以认为HUB是一个特殊的USB设备
在搜索到的(hub.c)文件中找到打印的这条语句
查看该语句在哪个函数里
hub端口初始化,看看这个函数在哪儿被调用
端口连接变化,这个函数又在哪儿被调用
hub事件函数又是在哪儿被调用
hub线程,它是一个内核线程,平时是休眠的,当有事情发生会被唤醒,那么被谁来唤醒呢
搜索一下谁来从这个队列把它唤醒
这个(kick_khubd)又是被谁调用
注意:这个中断是主机控制器里注册的中断,不是USB设备的中断
当接上USB设备,就会使得D+或者D-有低电平变成高电平,硬件上就感知到了有USB设备接入,主机控制器里就会产生中断。
在中断函数里面一路执行下来,最终就会执行到(hub_port_init),在函数里面就会打印出发现新的USB设备
根据USB驱动总线的作用,来看看是怎么告诉USB设备的
从(hub_port_connet_change)函数开始分析
分配一个usb_device结构体
这个结构体的总线类型是"usb_bus_type"
紧接着分配编号(地址)
然后进入(hub_port_init)函数,设置地址并获取描述符
设置地址
获取描述符
描述符的信息可以在(include\linux\usb\Ch9.h)看到,Ch9表示USB规范协议的第九章
设备描述符:每一个硬件都有USB设备描述符,我可以发出某些命令得到这些USB设备的设备描述符。一个设备可能会有多种配置
配置描述符
接口描述符:逻辑上的设备
每个配置下面可能有多个接口,也可能只有一个。比如:一个USB声卡,它硬件上只有一个,但逻辑上可能会有两个功能(录音、播放)。这样就分成了两个逻辑设备,用两个"接口描述符"分别描述逻辑设备"录音"和"播放"。
端点描述符
每一个硬件都有一个设备描述符,一个设备下面可能有多个配置,一个配置下面可能有多个接口,接口就是逻辑设备,我们写驱动程序时就是给接口(逻辑设备)写的,所以一个USB硬件可能会多个驱动程序。一个接口里面有多个端点,端点描述符会表示一次性会传输多大的数据,它的端点编号,它的方向,它的传输类型这些信息。
我们把这些描述符信息通通读出来之后就可对这个USB设备了如指掌了,就可以根据这些信息找到驱动程序了
问:为什么只获得8个字节呢?
答:因为还不知道你这个端点0一次性传输多少数据。
再次获得设备描述符
到这里(hub_port_init)函数就结束了。再返回上一级函数(hub_port_connet_change),看看还有哪些工作,调用(usb_new_devie)函数
在(usb_get_configuration)函数里把所有的描述符都读出来,并解析他们
最后跳到(usb_new_device)函数里执行(device_add)
(device_add)函数会到总线设备驱动模型上添加USB设备。把设备放到总线的dev链表,从总线的driver链表里取出driver用总线的match函数通过id_table一一比较,若能匹配成功,则调用driver中的"probe"函数。
总结一下这个过程:
当接上一个USB设备时,就会产生由硬件产生一个中断(hub_irq),在中断里会分配一个编号(choose_address),然后把这个地址告诉USB设备(hub_set_address),接着发出各种命令获取USB设备的"设备描述符"。再然后注册一个device(device_add),这个被注册的device会被放到USB总线(usb_bus_type)的"设备链表",然后从总线的"驱动链表"中取出"usb_driver",把usb_interface和usb_driver的id_table比较,若匹配成功则调用"usb_driver"结构中的probe函数。
三、 USB驱动程序之USB设备驱动程序1简单编写(第十七课/第三节)
USB总线驱动程序,在我们接入USB设备的时候会帮我们构造一个新的 usb_dev,注册到(usb_bus_type)中去,左边这部分内核已经帮我们做好,我们要做的是右边这一块,构造一个 usb_driver 结构体,然后注册到(usb_bus_type)里。在 usb_driver 结构体里有 id_table,表示它能支持哪些设备,当(usb_bus_type)结构中match函数匹配成功之后就会调用 usb_driver 结构中probe函数;拔掉USB设备时就会调用 usb_driver 结构中的disconnet函数。
(usb_bus_type)总线驱动模型只是提供了这样一种框架,可以把左右两边挂钩起来。
目标:
USB鼠标用作按键:(相当于输入子系统)
左键 -- L
右键 -- S
中建 -- Enter
在probe函数做的事情:
1. 分配一个input_dev结构体。 2. 设置,使其产生哪类事件,该类事件的哪些事件。 3. 注册 4.硬件相关操作: 写按键或触摸屏驱动程序时是注册某中断,在按键的中断里读引脚确定是按下还是松开然后读按键值。 在USB鼠标里,是使用USB总线驱动程序提供的读写函数来收发数据。
怎么写USB设备驱动程序?
1. 分配/设置 usb_driver 结构体
.id_table 表示能支持哪些设备
.probe 若id_table能够支持就调用probe函数
.disconnet 拔掉USB设备时调用这个函数
2. 注册
先参考别人的代码:(driver\hid\usbhid\usbmouse.c)
入口函数里面注册一个(usb_driver结构体)
假设能够匹配,调用probe函数
以前的触摸屏驱动是从2440adc控制器里面得到数据,现在的USB设备驱动程序想得到数据,是发USB包。通过(USB主机控制器)来得到那些数据
现在来写出框架:
1th、 打印简单的语句
一、 id_table:
".match_flags":表示要匹配设备描述符中的哪一项。"USB_DEVICE_ID_MATCH_INT_INFO":INT是接口的意思,INT_INFO就是指接口的信息;接口的信息就在接口描述符里。 ".bInterfaceClass":接口描述符里的类 ".bInterfaceSubClass":接口描述符的子类 ".bInterfaceProtocol":接口描述符的协议
只要这个设备的接口描述符里面的类是HID类;子类是BOOT;协议是MOUSE,那么这个id_table就能够支持。
也可以支持厂家ID,设备ID
二、 填充probe函数
一个USB硬件设备可能有多个逻辑设备,这些逻辑设备就是用"usb_interface"结构体表示的。如一块声卡有录音和播放两个逻辑接口,就需要两个驱动程序。
三、 disconnect函数
测试1th:
第一步:make menuconfig 去掉原来的USB鼠标驱动,不然一接上USB鼠标内核就会找到原来的驱动,新注册的驱动就无法加载。
第二步:编译没有USB的内核
第三步:使用没有USB的内核启动
第四步:挂接文件系统,加载驱动程序
第五步:插入,拔出鼠标
2th、 打印厂家ID和设备ID
当我们接入USB设备之后,USB总线驱动程序已经把这些设备描述符信息通通读取出来了。
打印描述符信息
测试2th:
重新加载驱动
插入,拔出鼠标
电脑上的USB鼠标描述符信息
3th、 打印USB鼠标的原始数据
继续分析实例(usbmouse.c)的probe函数
问:怎么知道是否是输入类型端点?
答:每一个端点的端点描述符都有一个属性,表明它的方向。
继续写代码,在自己的代码里面就不用做判断了,就认为它是一个鼠标
按照套路把前三步做完
以前的硬件操作就是去注册中断引脚然后去读取寄存器,现在需要用底层的USB主机控制器提供的读写函数来收发USB数据
A、数据传输三要素:源、目的、长度。
1. 源:USB设备的某个端点。
这个宏(usb_rcvintpipe)就包含了USB设备的地址和哪一个端点(端点地址)
自己的猜想:这一个int类型的pipe变量包含了端点类型、设备地址、端点地址、端点方向,是因为把这4字节拆分为了4部分
PIPE_INTERRUPT:中断类型端点 USB_DIR_IN:端点的方向 devnum:设备地址(USB设备编号) endpoint:端点地址(bEndpointAddress)
2. 目的:从USB设备读数据,读到一个缓冲区。所以要分配一个缓冲区,不能用(kalloc),用(usb_buffer_alloc)来分配,返回一个(void*)的虚拟地址
3. 长度:端点描述符中的最大包大小
B、使用三要素
1. 分配一个urb(usb request block)
2. 使用三要素来设置urb和设置某些标记
USB主机控制器得到数据后,要往某个内存去写(写的内容是查询到USB设备的数据),但是这个主机控制器没那么聪明,需要我们告诉这个主机控制器某个内存的物理地址。
USB设备没有能力产生中断,它是通过USB主机控制器不断的查询,查询到数据后是由USB主机控制器向CUP发出中断申请的。
3. 使用urb;提交 urb
自己猜想:在实例程序中未看到该函数,猜想是注册input_dev的时候会调用该结构里的open函数
C、完成中断函数(USB设备没有中断能力,这个中断请求是USB主机控制器发出的)
D、disconnect函数
测试3th:
重新加载驱动
接上USB鼠标
操作鼠标观察数据
第一个字节:数据"01"里的"1"是bit0,表示左键按下;"02"里的"2"是bit1,表示右键按下;"04"里的"4"是bit2,表示中键按下;"03"里的"3"是bit0和bit1,表示左右键同时按下。 第二个字节:X方向,正值表示往右移,负值表示往左移。 第三个字节:Y方向,往上移是负数,往下移是正数。 第四个字节:滑轮,往前滑是正数,往后滑是负数。
USB鼠标驱动程序-3th
#include <linux/kernel.h> #include <linux/slab.h> #include <linux/module.h> #include <linux/init.h> #include <linux/usb/input.h> #include <linux/hid.h> /* *参考 F:\编程之路\编程之路(Linux)\link_to_term1_and_term2\kernels\linux-2.6.22.6\drivers\hid\usbhid\usbmouse.c */ static struct input_dev* uk_dev; static char* usb_buffer_vir; static dma_addr_t usb_buffer_pys; static struct urb* usb_urb; static int len; static struct usb_device_id usbmouse_id_table [] = { { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT, USB_INTERFACE_PROTOCOL_MOUSE) }, { } /* Terminating entry */ }; /* 当USB主机控制器查询到信息后,保存到buffer然后产生中断 */ static void usbmouse_irq(struct urb *urb) { int i; static int cnt = 0; printk("usb_buffer_vir data cnt: %d\n", cnt++); for(i=0; i<len; i++) { printk("%02x", usb_buffer_vir[i]); } printk("\n"); /* 重新提交urb */ usb_submit_urb(usb_urb, GFP_KERNEL); } static int usbmouse_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct usb_device *dev = interface_to_usbdev(intf); struct usb_host_interface *interface; struct usb_endpoint_descriptor *endpoint; int pipe; interface = intf->cur_altsetting; endpoint = &interface->endpoint[0].desc; /* 1.分配一个input_dev结构体 */ uk_dev = input_allocate_device(); /* 2. 设置 */ /* 2.1 设置能产生哪类事件:按键类、重复类 */ set_bit(EV_KEY, uk_dev->evbit); set_bit(EV_REP, uk_dev->evbit); /* 2.2 设置产生该类的哪些事件:L、S、回车 */ set_bit(KEY_L, uk_dev->keybit); set_bit(KEY_S, uk_dev->keybit); set_bit(KEY_ENTER, uk_dev->keybit); /* 3. 注册 */ input_register_device(uk_dev); /* 4. 硬件相关操作 */ /* 数据传输三要素:源、目的、长度 */ /* 4.1 源*/ pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); /* 4.2 长度 */ len = endpoint->wMaxPacketSize; /* 4.3 目的:分配一个缓冲区 */ usb_buffer_vir = usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buffer_pys); /* 使用三要素 */ /* 1. 分配 usb request block */ usb_urb = usb_alloc_urb(0, GFP_KERNEL); /* 2. 使用三要素来设置urb */ usb_fill_int_urb(usb_urb, dev, pipe, usb_buffer_vir, len, usbmouse_irq, NULL, endpoint->bInterval); usb_urb->transfer_dma = usb_buffer_pys; usb_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; /* 3. 提交urb */ usb_submit_urb(usb_urb, GFP_KERNEL); return 0; } static void usbmouse_disconnect(struct usb_interface *intf) { struct usb_device *dev = interface_to_usbdev(intf); printk("usbmouse is disconnet!\n"); usb_kill_urb(usb_urb); usb_free_urb(usb_urb); usb_buffer_free(dev, len, usb_buffer_vir, usb_buffer_pys); input_unregister_device(uk_dev); input_free_device(uk_dev); } static struct usb_driver usbmouse_as_key_driver = { .name = "usbmouse_as_key", .probe = usbmouse_probe, .disconnect = usbmouse_disconnect, .id_table = usbmouse_id_table, }; static int usbmouse_as_key_init(void) { usb_register(&usbmouse_as_key_driver); return 0; } static void usbmouse_as_key_exit(void) { usb_deregister(&usbmouse_as_key_driver); } module_init(usbmouse_as_key_init); module_exit(usbmouse_as_key_exit); MODULE_LICENSE("GPL");
4th、 用USB鼠标实现左键L,右键S,中键ENTER的功能
通过上述实验,可以知道对于实现按键操作,只需要对usb_buffer_vir的第一个数组作分析即可。
测试4th:
不用拔插,可以只重新加载新驱动
cat /dev/tty1然后按鼠标左右键
hexdump /dev/event0
总结:
根据总线驱动设备模型,这个(usb_bus_type)里面有个".match"函数,左边是由USB总线驱动程序帮我们来发现USB新设备,它会注册一个USB设备并且会从右边的driver链表里面通过总线里面的".match"函数比较左边(usb_interface)结构中的接口与右边(usb_driver)结构中的"id_table",若能吻合,则调用(usb_driver)中的".probe"函数。(usb_bus_type)提供了这套机制。
以前的数据是从中断或者读寄存器得到,现在USB驱动程序中的数据从USB总线提供的读写函数得到。一旦有数据就会保存数据,然后由主机控制器发出中断请求
数据传输三要素:源(端点地址)、目的(分配的缓冲区)、长度(端点描述符里最大包大小),然后构造一个"usb_urb = usb_alloc_urb(0, GFP_KERNEL)",紧接着把三要素填充到"usb_urb",最后要想使用就得提交urb。
<wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">