设为首页 加入收藏

TOP

Linux usb子系统(一) _写一个usb鼠标驱动
2017-04-07 10:26:28 】 浏览:8068
Tags:Linux usb 子系统 一个 鼠标 驱动

USB总线是一种典型的热插拔的总线标准,由于其优异的性能几乎成为了当下大小设备中的标配。
USB的驱动可以分为3类:SoC的USB控制器的驱动,主机端USB设备的驱动,设备上的USB Gadget驱动,通常,对于USB这种标准化的设备,内核已经将主机控制器的驱动编写好了,设备上的Gadget驱动通常只运行固件程序而不是基于Linux, 所以驱动工程师的主要工作就是编写主机端的USB设备驱动。


下图表示了Linux中USB子系统的框架结构,和i2c一样,USB子系统也可分为三层:**设备驱动层--USB核心--控制器驱动层*


作为热插拔总线, USB和非热插拔总线最大的区别就是总线无法事前获知设备的信息以及设备何时被插入或拔出,所以也就不能使用任意一种形式将设备信息事前写入内核。
为了解决由于热插拔引起的设备识别问题,USB总线通过枚举的方式来获取一个接入总线的USB设备的设备信息——一个由device->config->interface->endpoint逐级描述的设备,基于分离的思想,USB子系统中设计了一组结构来描述这几个维度的设备信息,相比之下,i2c总线只要一个i2c_client即可描述一个设备.


USB总线上的所有通信都是由主机发起的,所以本质上,USB都是采用轮询的方式进行的。USB总线会使用轮询的方式不断检测总线上是否有设备接入,如果有设备接入相应的D+D-就会有电平变化。然后总线就会按照USB规定的协议与设备进行通信,设备将存储在自身的设备信息依次交给主机,主机将这些信息按照4层模型组织起来。上报到内核,内核中的USB子系统再去匹配相应的驱动,USB设备驱动是面向interface这一层次的信息的


作为一种高度标准化的设备, 虽然USB本身十分复杂, 但是内核已经为我们完成了相当多的工作, 下述的常用设备驱动在内核中已经实现了。很多时候, 驱动的难度不是看设备的复杂程度, 而是看标准化程度


基于分离的思想,USB子系统也提供了描述一个USB设备的结构,只不过基于USB协议,完整描述一个USB设备信息需要9个结构,这些结构中,前4个用来描述一个USB设备的硬件信息,即设备本身的信息,这些信息是写入到设备的eeprom的,在任何USB主机中看到的都一样,这些信息可以使用lsusb -v命令来查看; 后5个描述一个USB设备的软件信息,即除了硬件信息之外,Linux为了管理一个USB设备还要封装一些信息,是OS-specific的信息; USB设备硬件信息和软件信息的关系类似于中断子系统中的硬件中断和内核中断,只不过更复杂一点。


首先说的是那9个描述设备信息的结构, 其中的硬件信息是相互独立的, 分别使用 这些结构在内核"include/uapi/linux/usbch9.h"有定义, 我就不贴代码了


下面这个就是与驱动直接匹配的描述


endpoint是USB设备IO的基本单元


每一个硬件信息对象都包含在一个软件信息对象中, 而软件信息对象是层层包含的, 所以虽然驱动是基于interface描述的, 但是我们可以使用"list_entry()"很容易的向上找到config和device描述, 使用其中的域很容易的找到endpoint描述, 这9个描述设备的结构关系如下图所示:


与platform或i2c总线不同的是,usb总线不允许设备发起通信,所以作为设备驱动, 只有将需要的"材料"准备好经由核心层提交到usb控制器驱动,让控制器驱动带着这些"材料"去轮询设备并将应答带回。这些"材料"就是urb和相应的注册参数。当usb_driver和usb设备匹配上后,我们准备一个urb对象通过usb_fill_int_urb()/_bulk_/_control_注册到总线,并在合适的时机通过usb_submit_urb向控制器驱动发出发送这个urb对象的命令,总线的控制器驱动收到我们的发送命令之后,会根据我们注册时设置的周期不断向匹配的设备发送请求,如果子设备响应了我们的请求,控制器驱动就会将我们注册的urb对象填充好,并回调我们的注册函数。
对于海量存储USB设备,如果要让设备正常工作, 除了这些流程,还需要加上对缓存区的管理,这部分我们下篇说。


初始化并注册一个中断urb。函数原型如下:


这个函数参数比较多, urb表示我们要注册的urb对象; dev表示这个urb对象的目的设备; pipe表示读写管道, 使用usb_sndintpipe()和usb_rcvintpipe()获取; transfer_buffer表示传递数据的缓冲区首地址;buffer_length表示缓冲区长度;complete_fn表示如果我们发出的urb有了回应, 就回调这个函数; context是回调函数的参数, 由用户定义, 相当于request_irq中的void *dev; interval就是发送周期, 核心层会以这个参数为周期通过usb控制器驱动轮询设备,


urb和xxx一样,要用内核分配函数,其中会做一些初始化的工作


初始化并注册一个海量存储urb


初始化并注册一个控制urb


通知内核发送urb对象


注册一个usb_driver到内核


注销一个usb_driver


内核提供了如下的宏来构造一个usb_device_id对象, 其实也就是对usb_device_id中的不同域进行了填充, 由于设备的差异性, 不同的USB设备会上报不同的设备信息, 但无论上报哪些信息, 一定属于下面这些宏的一种封装. 可以先使用lsusb -v查看设备的硬件信息, 再根据其提供的硬件信息确定id_table编写相应的驱动


下面是内核"drdrivers/hid/usbhid/usbmouse.c"中的ib_table填写方式, 可以看出, 不仅构造使用了宏, 由于USB鼠标是标准设备, 它的属性值也有标准的宏来标识


内核"drivers/hid/usbhid/usbmouse.c"就是一个usb鼠标的驱动, 这里我根据自己的理解写了一个, 采用的是"usb中断设备驱动+input子系统"框架


】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Linux i2c子系统(四) _从i2c-s3c2.. 下一篇Linux块设备IO子系统(一) _驱动模..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目