blogernice

导航

USB驱动学习笔记

这 个礼拜由于突然被安排进了一个项目,所以终止了在进行当中的USB驱动的培训。作为自己学习的总结,也希望能为有想法要往这个方向发展但暂时还没有向这个 方向发展的同学们提供一些微薄的帮助。这个项目可能要写bootloader或者sd卡/camera驱动,反正到时学会了再回来写篇笔记,希望大家喜 欢:)

开 始写这篇文章之前感觉能写得比较好,可开始写了发现写这样的一篇东西有难度啊,又想不要象规范那样累赘,有想表达出自己的意思,甚为复杂啊。暂且估计 下,一般没有任何USB基础的人可能根本看不懂这篇文章,如果先能把USB 1.1 spec过一遍再来看,可能效果就会好很多。还有点就是,毕竟没有参与过实际的USB的项目开发,其中很可能有些概念我的理解也是错的,所以欢迎大家指出 错误,补充不足。

参考书籍:
USB 1.1 spec -- www.usb.org可下载
UHCI/OHCI/EHCI spec -- www.usb.org可下载
LINUX设备驱动程序(第三版)-- 中国电力出版社
LINUX内核源代码情景分析 -- 浙江大学出版社
PDIUSBD12 USB固件编程与驱动开发 -- 北京航空航天大学出版社
P.S.世面上能见到的有关USB的书我看过6.7成了,我发现大多只是把USB spec的英文内容翻译成中文,刚上手的时候对英文不是母语的我们有些帮助,但过了段时间之后就会发现这些书空洞无物了。上面那3本里则是有些自己的内容,还是值得一看的。

推荐软件:
usbhound 5.0 -- http://bbs.zndev.com/htm_data/9/0404/64787.html可下载,用于抓获UBS通信时的包。
linux kernel -- http://lxr.linux.no/source/可在线看,linux的USB驱动代码可从这里获得。
Platform builder 5.0 -- Windows CE开发工具,很难下到,但BT上应该有,有所有Windows CE的USB驱动的代码。
P.S.由于没有仔细接触过Windows PC上的驱动(WDM),所以不太清楚DDK里面的内容,我以下的内容也只会涉及Windows CE和linux。

1.USB是主从结构的。
如何确定主从:这个是由USB接口的芯片决定的,也就是由硬件决定的,而不象以太网中的c/s那样,由软件决定。
举个例子:我们的PC主板上提供的USB接口类型决定了PC机在USB通信中永远是主(host)端,而不能做从(device)端。而U盘里面的USB芯片也决定了U盘只能做为从(device)端。
注意:但是在USB2.0的规范中出现了OTG的概念,可以让设备即能做host,又能做device,但这个方面我只是知道概念,对于具体的内容还不熟悉。

2.USB通信都是由host端发起的。
概念描述:也就是说,必须由host先向device发送请求,然后由device作出响应。device没有主动发起通信的能力。

3.device端是通过endpoint来进行通信的。
概念描述:USB的规范上,以及很多书籍上都有endpoint的概念,但是我感觉说得都不清楚,在这里我想尽力能描述得清楚些。首先,endpoint 的物理存在的东西,是在device芯片上存在的东西。你可以把USB的endpoint想象成切了片的连藕,那一个个孔就是endpoint,只有通过 endpoint才能传输数据。USB规范指出,必须有endpoint0来进行基本的配置操作。

4.USB的host端的驱动
USB的host端的驱动是很麻烦的,在windows ce和linux下虽然代码不一样,但是实现的基本结构相同。
USB client driver
|
USB driver/USB core
|
host control driver
|
host control
|
cable
就是这个5层的结构。
cable就是我们用的USB连接线,当然,要是距离足够短,也可以完全不需要,而且这个和软件上的实现完全没有关系。

host control就是我上面说过的USB芯片里的一种,也就是用于host端的芯片。所以最下面2层完全是硬件上决定的,和软件无关,是硬件设计人员决定的。而上面的3层就是具体的USB驱动的实现了。

host control driver看名字大家就应该知道了,是对host control的驱动。这里要说下,到现在为止,USB的host control分为3种,分别是UHCI,OHCI,EHCI,这3种不同的host control在硬件的实现上是不同的。EHCI用于USB2.0的规范,而UHCI和OHCI用于USB1.1的规范。一般在PC机上的1.1的接口都 是UHCI的,但是一般嵌入式CPU上的USB接口则是OHCI比较常见。UHCI在硬件复杂度上不如OHCI,所以造成的后果是UHCI对应的软件驱动 会比OHCI的麻烦些。USB的数据其中最终都是通过host control来发送的。这些不同的host control发送数据的方法其实也满类似的。一般是按照spec来定义一些结构体,当然,结构体里每个字段的含义也必须严格按照spec里的定义。这1 个结构体就当是一个数据包。然后,把一个个的数据包连接成链表的形式,再把链表头的地址赋给host control的某个特定的寄存器,这样,host control就会根据这个寄存器里的地址找到要发送的数据,然后把这些数据组装成USB通信协议中规定的格式,再发送出去。(对于UHCI, 《LINUX内核源代码情景分析》里面有比较详细的分析,不过代码是linux2.4的,和现在的2.6还是有满大不同的)。所以,如果在硬件平台确定的 情况下一般开发host control driver的机会很少,而且开发host control driver确实是一件很有难度的事。

再上面一层,USB driver/USB core(Windows ce里叫USB driver,linux里叫USB core,只是名字不一样),这层主要实现了USB规范中的4种传输(中断,同步,控制,大块,具体可参考USB 1.1 spec),其实也就是把host control driver里的功能更集中得向上抽象了一层。这层是操作系统提供的,是不需要驱动开发人员来修改的,他是用来对最上层的client driver屏蔽掉host control的不同。

最上层的USB client driver,才是我们一般情况下讲的USB鼠标驱动啊,U盘驱动啊,数码相机驱动啊。这里就要用到上面讲的device的endpoint了。首先, USB client driver的任务是确定设备的特性,并对设备进行配置,使之进入正常的可工作状态。而这些配置必须用控制传输的方式通过device的 endpoint0来进行。(对于这些操作,一般叫做枚举,整个操作的过程可通过bushound这个软件截获,,或者在《PDIUSBD12 USB固件编程与驱动开发》中也有教详细的描述。个人感觉,理解枚举的每一个步骤有利于理解usb包发送的格式)。在完成了对设备的配置之后,设备就可以 正常工作了。这时,endpoint0的职责就就完成了。接下去,所有的通信则是同过endpoint1,endpoint2,endpoint3... 等endpoint进行了。endpoint1,endpoint2,endpoint3...不象endpoint0,他们在地位上是没有区别的。

下面拿linux里的USB鼠标驱动做个列子,因为这是我发现的最为简单的USB client驱动了,也是我暂时基本能完全看懂的一个驱动,呵呵。(http: //lxr.linux.no/source/drivers/usb/input/usbmouse.c)在完成了枚举之后,驱动程序和USB鼠标之间 通过endpoint1进行通信。驱动调用usb_submit_urb(mouse->irq, GFP_KERNEL),mouse->irq是一个URB(至于URB是什么,可参考linux device driver 3),简单得可以理解为要发送给鼠标的数据。usb_submit_urb也就是把数据包提交给host control,让其把数据包发送出去。而在usb_submit_urb之前,usb_fill_int_urb(mouse->irq, dev,pipe,mouse->data,(maxp > 8 ? 8 : maxp),usb_mouse_irq, mouse, endpoint->bInterval),其中乱七八糟的参数暂时不要关心,要关心的是usb_mouse_irq。这是一个函数地址,起到回调 作用。也就是驱动通过usb_submit_urb把数据发送出去。当收到鼠标的正确回应之后,linux会自动去调用usb_mouse_irq来读取 鼠标传回来的值,从而进行具体的响应。当usb_submit_urb把请求发给鼠标之后,他就进行等待。鼠标收到这个请求之后就返回信息。这里又要涉及 到一个麻烦的东西,就是USB规范里对不同的设备又分了n多的类,每个不同的类又有不同的规范与之对应。鼠标对应的是HID规范。规范里指出,鼠标如果在 任何按键或者位置上没有变化的情况下,是不需要发送数据给host端的。所以,如果你的鼠标不动,鼠标只会发送一个nak标志(也就是拒绝请求)给 host端。而host端收到nak标志后会把刚才的数据进行重发。这里就有一个概念了:USB host control对于usb device是基于轮巡的机制,而USB host control对于CPU则是基于中断的机制。这个概念以前捆扰了我一段时间。就这样,USB host control对鼠标不断请求,这个请求的间隔是很短的,可能只有10ms,当鼠标发生了事件之后,鼠标会发送数据回host,这是USB host control中断通知CPU,于是usb_mouse_irq被调用。在usb_mouse_irq里,就可以读取鼠标发回的数据。当读完之后,驱动再 次调用usb_submit_urb发出请求,就这么一直重复下去,一个USB鼠标的驱动程序也就完成了。

要是U盘的驱动,和鼠标的驱动有很大的不同。因为U盘属于mass storage类,所以必须遵循USB mass storage协议规范。USB mass storage比USB HID复杂很多,而且还要设计SCSI的命令,由于培训到这里就结束了,所以没有学下去。谁有兴趣可以去学下,感觉mass storage是很实用的技术的,数码相机,U盘,mp3...反正是不胜枚举啊。

5.USB device驱动
也就是一般说的固件编程。device端的驱动相对host端的要简单很多。因为USB规范规定所有通信必须是由host发起的,所以device的驱动 程序主要就是对 host的请求作出响应。这里就要强烈推荐《PDIUSBD12 USB固件编程与驱动开发》了,这里面的内容足够我们品位一段时间了。一般的做法是在进入了main之后除了初始化硬件之外什么也干,等着中断的发生。当 USB芯片收到从host端来的数据之后,就会发生中断,进入中断处理。通过读取USB芯片上寄存器的值,可知道是哪个endpoint来了数据,然后就 读取数据进行响应。USB host发送过来的可能是标准的请求,也可能是类的请求,有可能是厂商的请求,也可能只是数据。关于标准的请求参见USB 1.1 spec chapter 9,里面有很详细的定义。

后记:暂时就写这么多了,以后感觉哪里不足再加吧,写了那么多,手酸了...这里有点要提醒新手们,学习的主要方法就是文档,代码,实验。这里的文档不是 指书,而是官方文档。学驱动不比学C++,学JAVA,满天的经典书籍,很多时间只能靠自己去摸索,自己去体味没有注释的代码。

posted on 2018-10-25 17:39  blogernice  阅读(427)  评论(0编辑  收藏  举报