`
whotodo
  • 浏览: 167995 次
文章分类
社区版块
存档分类
最新评论

USB驱动分析 +<bus,device,driver三者关系>

 
阅读更多

来自:http://blog.chinaunix.net/uid-20691722-id-3154640.html

=======================================================

这个故事中使用的是2.6.10的内核代码.Linux内核代码目录中, 所有去设备驱动程序有关的代码都在drivers/目录下面,在这个目录中我们用ls命令可以看到很多子目录.

localhost:/usr/src/linux-2.6.10/drivers # ls

Kconfig atm cdrom eisa ide macintosh message net parport s390 tc w1

Makefile base char fc4 ieee1394 mca misc nubus pci sbus telephony zorro

acorn block cpufreq firmware input md mmc oprofile pcmcia scsi usb

acpi bluetooth dio i2c isdn media mtd parisc pnp serial video

其中usb目录包含了所有usb设备的驱动,而usb目录下面又有它自己的子目录,进去看一下,

localhost:/usr/src/linux-2.6.10/drivers # cd usb/

locahost:/usr/src/linux-2.6.10/drivers/usb # ls

Kconfig Makefile README atm class core gadget host image input media misc net serial storage usb-skeleton.c

注意到每一个目录下面都有一个Kconfig文件和一个Makefile,这很重要.稍后会有介绍.

而 我们的故事其实是围绕着drivers/usb/storage这个目录来展开的.实际上这里边的代码清清楚楚地展示了我们日常频繁接触的U盘是如何工作 的,是如何被驱动起来的.但是这个目录里边的冬冬并不是生活在世外桃源,他们总是和外面的世界有着千丝万缕的瓜葛.可以继续进来看一下,

localhost:/usr/src/linux-2.6.10/drivers/usb # cd storage/

localhost:/usr/src/linux-2.6.10/drivers/usb/storage # ls

Kconfig debug.c freecom.c isd200.c protocol.c sddr09.c shuttle_usbat.c unusual_devs.h

Makefile debug.h freecom.h isd200.h protocol.h sddr09.h shuttle_usbat.h usb.c

datafab.c dpcm.c initializers.c jumpshot.c scsiglue.c sddr55.c transport.c usb.h

datafab.h dpcm.h initializers.h jumpshot.h scsiglue.h sddr55.h transport.h

咋一看,着实吓了一跳,用`wc -l *`这个命令统计一下,12076行,晕死...

wc [ -c | -m ] [ -l ] [ -w ] [文件]

或者

wc -k [ -c ] [ -l ] [ -w ] [文件]

注:缺省情况下,wc 命令对 File 参数指定的文件中的行数、字数和字节数进行计数。这个命令将换行符数、字数和字节数写到标准输出并为所有指定的文件保留一个总数。

当使用 File 参数时, wc 命令显示文件名以及请求的计数。如果没有给 File 参数指定一个文件名,wc 命令使用标准输入。

wc 命令受 LANG、LC_ALL、LC_CTYPE 和 LC_MESSAGES 环境变量影响。

wc 命令将一个字看作是被一个空格(如空白和跳格)分隔的非零长度字符串。

-c 统计字节数,除非指定 -k 标志。如果指定 -k 标志,wc 命令统计字符数。

-k 统计字符数。指定 -k 标志等同于指定 -klwc 标志。如果将 -k 标志同其他标志一起使用,那么必须包含 -c 标志。否则,将会忽略 -k 标志。

注:这个标志在将来的发行版中将会撤销。

-l 统计行数。

-m 统计字符数。这个标志不能与 -c 标志一起使用。

-w 统计字数。一个字被定义为由空白、跳格或换行字符分隔的字符串。

但是,也许,生活中总是充满了跌宕起伏.

认真看了一下Makefile和Kconfig之后,心情明显好了许多.

出来混,迟早要还的.

从 前在复旦,混了四年,没有学到任何东西,每天就是逃课,上网,玩游戏,睡觉.毕业的时候,身边的人读研的读研,出国的出国,找工作的吧,去麦肯锡的去麦肯 锡,去IBM的去IBM.而自己却一无所长,没有任何技能,直到这时候才发现那四年欠了很多债,早知今日,何必当初.幸运的是,我还有一张复旦的文凭,依 靠着这张文凭,混进了Intel.然而,工作以后,更是发现当初在校期间没有好好读书其实真是在欠债,当初没学,工作以后还是要学,的确是迟早要还的,逃 是逃不掉的.

毕业的时候,人家跟我说Makefile我完全不知,但是一说Make Love我就来劲了.现在想来依然觉得丢人.

基 本上,Linux内核中每一个目录下边都有一个Makefile,Makefile和Kconfig就像一个城市的地图,地图带领我们去认识一个城市,而 Makefile和Kconfig则可以让我们了解这个目录下面的结构.drivers/usb/storage/目录下边的Makefile内容如下:

#

# Makefile for the USB Mass Storage device drivers.

#

# 15 Aug 2000, Christoph Hellwig

# Rewritten to use lists instead of if-statements.

#

EXTRA_CFLAGS := -Idrivers/scsi

obj-$(CONFIG_USB_STORAGE) += usb-storage.o

usb-storage-obj-$(CONFIG_USB_STORAGE_DEBUG) += debug.o

usb-storage-obj-$(CONFIG_USB_STORAGE_HP8200e) += shuttle_usbat.o

usb-storage-obj-$(CONFIG_USB_STORAGE_SDDR09) += sddr09.o

usb-storage-obj-$(CONFIG_USB_STORAGE_SDDR55) += sddr55.o

usb-storage-obj-$(CONFIG_USB_STORAGE_FREECOM) += freecom.o

usb-storage-obj-$(CONFIG_USB_STORAGE_DPCM) += dpcm.o

usb-storage-obj-$(CONFIG_USB_STORAGE_ISD200) += isd200.o

usb-storage-obj-$(CONFIG_USB_STORAGE_DATAFAB) += datafab.o

usb-storage-obj-$(CONFIG_USB_STORAGE_JUMPSHOT) += jumpshot.o

usb-storage-objs := scsiglue.o protocol.o transport.o usb.o \

initializers.o $(usb-storage-obj-y)

关 于Kconfig文件,在故事的最后会介绍,此刻暂且不表,Kconfig文件比较长,就不贴出来了.但是通过看Kconfig文件,我们可以知道,除了 CONFIG_USB_STORAGE这个编译选项是我们真正需要的以外,别的选项我们都可以不予理睬.比如,关于 CONFIG_USB_STORAGE_DATAFAB,Kconfig文件中有这么一段,

config USB_STORAGE_DATAFAB

bool "Datafab Compact Flash Reader support (EXPERIMENTAL)"

depends on USB_STORAGE && EXPERIMENTAL

help

Support for certain Datafab CompactFlash readers.

Datafab has a web page at .

显 然,这个选项和我们没有关系,首先这是专门针对Datafab公司的产品的,其次CompactFlash reader是一种flash设备,但这显然不是U盘,因为drivers/usb/storage这个目录里边的代码是针对一类设备的,不是某一种特定 的设备,这一类设备就是usb mass storage设备,关于这类设备,有专门的文档进行介绍,有相应的spec,描述这类设备的通信或者物理上电特性上等方面的规范,U盘只是其中的一种, 这种设备使用的通信协议被称为Bulk-Only Transport协议.再比如,关于CONFIG_USB_STORAGE_SDDR55这个选项,Kconfig文件中也有对应的一段,

config USB_STORAGE_SDDR55

bool "SanDisk SDDR-55 SmartMedia support (EXPERIMENTAL)"

depends on USB_STORAGE && EXPERIMENTAL

help

Say Y here to include additional code to support the Sandisk SDDR-55

SmartMedia reader in the USB Mass Storage driver.

很 显然这是SanDisk的产品,并且是针对SM卡的,这也不是U盘,所以我们也都不去理睬了.事实上,很容易确定,只有 CONFIG_USB_STORAGE这个选项是我们真正关心的,而它所对应的模块叫usb-storage,Makefile中最后一行也说了,

usb-storage-objs := scsiglue.o protocol.o transport.o usb.o \

initializers.o $(usb-storage-obj-y)

这 就意味着我们只需要关注的文件就是scsiglue.c,protocol.c,transport.c,usb.c,initializers.c以及 它们同名的.h头文件.再次使用wc -l命令统计一下这几个文件,发现总长度只有3701行,比最初看到的12000多行少了许多,当时信心就倍增.

不 过需要特别注意的是,CONFIG_USB_STORAGE_DEBUG这个编译选项,它不是我们必须的,但是如果真的要自己修改或者调试usb- storage的代码,那么打开这个选项是很有必要的,因为它会负责打印一些调试信息,以后在源代码中我们会看到它的作用.

有一种 感动,叫泪流满面,有一种机制,叫模块机制,十月革命一声炮响,给Linux送来了模块机制.显然,这种模块机制给那些Linux的发烧友们带来了方便, 因为模块机制意味着人们可以把庞大的Linux内核划分为许许多多个小的模块,对于编写设备驱动程序的那帮家伙来说,从此以后他们可以编写设备驱动程序却 不需要把她编译进内核,不用reboot机器,她只是一个模块,当你需要她的时候,你可以把她抱入怀中(insmod),当你不再需要她的时候,你可以把 她一脚踢开,甚至,你可以对她咆哮:"滚吧,贱人!"(rmmod).她不能成为你的手足,只能算你的衣服.

也许在现实世界里不会 这样,但是在Linux的虚拟世界里,确实可以是如此,time and time again,我问自己,模块是否就像现实生活中的一样呢?Linux内核是嫖客,当他需要这个模块的时候,他就把人家揽入怀中,当他不需要人家的时 候,就把别人踢开,而且,模块总是能够逆来顺受,尽管Linux内核会一次次抛弃她,但是每当Linux内核再次需要她的时候,当内核再次执行 insmod的时候,模块依然会尽自己的能力去取悦内核,这是否太可悲了些!记得孔子曾经说过,读懂Linux内核代码不难,难得是读懂Linux内核代 码背后的哲学!难道这就是传说中的藏在Linux代码背后的哲学!天哪!

抛开这见鬼的哲学吧.让我们从一个伟大的例子去认识模块.这就是传说中的"Hello World!",这个梦幻般的名字我们看过无数次了,每一次她出现在眼前,就意味着我们开始接触一种新的计算机语言了,或者,如此刻,开始描述一个新的故事.

请看下面这段代码,她就是Linux下的一个最简单的模块.当你安装这个模块的时候,她会用她特有的语言向你表白,"Hello,world!",千真万 确,她没有说"Honey,I love you!",虽然,她可以这么说,如果你要求她这么说.而后来你卸载了这个模块,你无情抛弃了她,她很伤心,她很绝望,但她没有抱怨,她只是淡淡地 说,"Goodbye,cruel world!"(再见,残酷的世界!)

++++++++++++++++++hello.c++++++++++++++++++++

1 #include /* Needed for the macros */

2 #include /* Needed for all modules */

3 MODULE_LICENSE("Dual BSD/GPL");

4 MODULE_AUTHOR("fudan_abc");

5

6 static int __init hello_init(void)

7 {

8 printk(KERN_ALERT "Hello, world!\n");

9 return 0;

10 }

11

12 static void __exit hello_exit(void)

13 {

14 printk(KERN_ALERT "Goodbye, cruel world\n");

15 }

16

17 module_init(hello_init);

18 module_exit(hello_exit);

++++++++++++++++++++++++++++++++++++++++++++++++

你需要使用module_init()和module_exit(),你可以称她们为函数,不过实际上她们是一些宏(macro),现在你可以不用去知道 她们背后的故事,只需要知道,在Linux Kernel 2.6的世界里,你写的任何一个模块都需要使用她们来初始化或退出,或者说注册以及后来的注销.当你用module_init()为一个模块注册了之后, 在你使用insmod这个命令去安装的时候,module_init()注册的函数将会被执行,而当你用rmmod这个命令去卸载一个模块的时 候,module_exit()注册的函数将会被执行.module_init()被称为驱动程序的初始化入口(driver initialization entry point).

怎么样演示以上代码的运行呢?没错,你需要一个Makefile.

+++++++++++++++++++++Makefile+++++++++++++++++++++++++++

1 # To build modules outside of the kernel tree, we run "make"

2 # in the kernel source tree; the Makefile these then includes this

3 # Makefile once again.

4 # This conditional selects whether we are being included from the

5 # kernel Makefile or not.

6 ifeq ($(KERNELRELEASE),)

7

8 # Assume the source tree is where the running kernel was built

9 # You should set KERNELDIR in the environment if it's elsewhere

10 KERNELDIR ?= /lib/modules/$(shell uname -r)/build

11 # The current directory is passed to sub-makes as argument

12 PWD := $(shell pwd)

13

14 modules:

15 $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

16

17 modules_install:

18 $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install

19

20 clean:

21 rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions

22

23 .PHONY: modules modules_install clean

24

25 else

26 # called from kernel build system: just declare what our modules are

27 obj-m := hello.o

28 endif

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

在lwn上可以找到这个例子,你可以把以上两个文件放在你的某个目录下,然后执行make,也许你不一定能成功,因为LK 2.6要求你编译模块之前,必须先在内核源代码目录下执行make,换言之,你必须先配置过内核,执行过make,然后才能make你自己的模块.原因我 就不细说了,你按着她要求的这么去做就行了.在内核顶层目录make过之后,你就可以在你当前放置Makefile的目录下执行make 了.Ok,make之后你就应该看到一个叫做hello.ko的文件生成了,恭喜你,这就是你将要测试的模块.

执行命令,

#insmod hello.ko

同时在另一个窗口,用命令tail -f /var/log/messages察看日志文件,你会看到

Hello world被打印了出来.

再执行命令,

#rmmod hello.ko

此时,在另一窗口你会看到Goodbye,cruel world!被打印了出来.

到这里,我该恭喜你,因为你已经能够编写Linux内核模块了.这种感觉很美妙,不是吗?你可以嘲笑秦皇汉武略输文采唐宗宋祖稍逊风骚,还可以嘲笑一代天骄成吉思汗只识弯弓射大雕了. 是的,Twins姐姐(s)告诉我们,只要我喜欢,还有什么不可以.

日后我们会看到,2.6内核中,每个模块都是以module_init开始,以module_exit结束.大多数来说没有必要知道这是为什么,记住就可 以了,相信每一个对Linux有一点常识的人都会知道这一点的,对大多数人来说,这就像是1+1为什么等于2一样,就像是两点之间最短的是直线,不需要证 明,如果一定要证明两点之间直线最短,可以扔一块骨头在B点,让一条狗从A点出发,你会发现狗走的是直线,是的,狗都知道,你还能不知道吗?

既然知道了怎么编写一个模块,那么编写设备驱动程序自然也就不难了.我相信,每一个会写模块的人都不会觉得写设备驱动有困难.对自己行不行不确定的话,可以去问一下葛优,他准说:"(神州行),我看行."

真的,我没说假话.写驱动不是什么难事,你完全可以很自信的说,你已经可以写Device Driver了.对,没错,飘柔,就这么自信.

前面说了每一个模块都是以module_init开始,以module_exit结束,那么我们就来看一下U盘的驱动的这个模块.在茫茫人海中,我们很容易找到这个文件:drivers/usb/storage/usb.c,在这个文件中又不难发现下面这段:

/***********************************************************************

1056 * Initialization and registration

1057 ***********************************************************************/

1058

1059 static int __init usb_stor_init(void)

1060 {

1061 int retval;

1062 printk(KERN_INFO "Initializing USB Mass Storage driver...\n");

1063

1064 /* register the driver, return usb_register return code if error */

1065 retval = usb_register(&usb_storage_driver);

1066 if (retval == 0)

1067 printk(KERN_INFO "USB Mass Storage support registered.\n");

1068

1069 return retval;

1070 }

1071

1072 static void __exit usb_stor_exit(void)

1073 {

1074 US_DEBUGP("usb_stor_exit() called\n");

1075

1076 /* Deregister the driver

1077 * This will cause disconnect() to be called for each

1078 * attached unit

1079 */

1080 US_DEBUGP("-- calling usb_deregister()\n");

1081 usb_deregister(&usb_storage_driver) ;

1082 }

1083

1084 module_init(usb_stor_init);

1085 module_exit(usb_stor_exit);

其实,module_init/module_exit只是一个宏,通常写模块的人为了彰显自己的个性,会给自己的初始化函数和注销函数另外起个名字,比 如这里module_init(usb_stor_init)以及module_exit(usb_stor_exit)实际上就是告诉这个世界,真正的 函数是usb_stor_init和usb_stor_exit.这种伎俩在Linux内核代码中屡见不鲜.见多了也就不必大惊小怪了,天要下雨娘要嫁 人,随她去吧.我们下面当然就从usb_stor_init正式开始我们的探索之旅.

看代码之前,我曾经 认真的思考过这么一个问题,我需要关注的仅仅是drivers/usb/storage/目录下面那相关的3000多行代码吗?就是这样几个文件就能让一 个个不同的U盘在Linux下面工作起来吗? 像一开始那样把这个目录比作一个小城的话,也许,城里的月光很漂亮,她能够把人的梦照亮,能够温暖人的心房.但我们真的就能厮守在这个城里,一生一世吗?

很不幸,问题远不是这样简单.外面的世界很精彩,作为U盘,她需要与usb core打交道,需要与scsi core打交道,需要与内存管理单元打交道,还有内核中许许多多其它模块打交道.外面的世界很大,远比我们想象的大.

什么是usb core?她负责实现一些核心的功能,为别的设备驱动程序提供服务,比如申请内存,比如实现一些所有的设备都会需要的公共的函数,事实上,在usb的世界里,一个普通的设备要正常的工作,除了要有设备本身以外,还需要有一个叫做控制器的冬冬,老外把它叫做host controller, 和这个控制器相连接在一起的有另一个咚咚,她叫root hub,hub我们应该不会陌生,在大学里,有的宿舍里网口有限,但是我们这一代人上大学基本上是每人一台电脑,所以网口不够,于是有人会使用hub,让 多个人共用一个网口,这是以太网上的hub,而usb的世界里同样有hub,其实原理是一样的,任何支持usb的电脑不会说只允许你只能一个时刻使用一个 usb设备,比如你插入了u盘,你同样还可以插入usb键盘,还可以再插一个usb鼠标,因为你会发现你的电脑里并不只是一个usb接口.这些口实际上就 是所谓的hub口.而现实中经常是让一个usb控制器和一个hub绑定在一起,专业一点说叫集成,而这个 hub也被称作root hub,换言之,和usb控制器绑定在一起的hub就是系统中最根本的hub,其它的hub可以连接到她这里,然后可以延伸出去,外接别的设备,当然也可 以不用别的hub,让usb设备直接接到root hub上.hub干嘛用的我们知道了,那么usb host controller本身是干什么用的呢?controller,控制器,顾名思义,用于控制,控制什么,控制所有的usb设备的通信.通常计算机的cpu并不是直接和usb设备打交道,而是和控制器打交道,他 要对设备做什么,他会告诉控制器,而不是直接把指令发给设备,然后控制器再去负责处理这件事情,他会去指挥设备执行命令,而cpu就不用管剩下的事情,他 还是该干嘛干嘛去,控制器替他去完成剩下的事情,事情办完了再通知cpu.否则让cpu去盯着每一个设备做每一件事情,那是不现实的,那就好比让一个学院 的院长去盯着我们每一个本科生上课,去管理我们的出勤,只能说,不现实.所以我们就被分成了几个系,通常院长有什么指示直接跟各系领导说就可以了,如果他 要和三个系主任说事情,他即使不把三个人都召集起来开个会,也可以给三个人各打一个电话,打完电话他就忙他自己的事情去了,比如去和他带的女硕士风花雪 月.而三个系主任就会去安排下面的人去执行具体的任务,完了之后他们就会像院长汇报.

所以,Linux内核开发者们,专门写了一些 代码,并美其名曰usb core.时代总在发展,当年胖杨贵妃照样迷死唐明皇,而如今人们欣赏的则是林志玲这样的魔鬼身材.同样,早期的Linux内核,其结构并不是如今天这般 有层次感,远不像今天这般错落有致,那时候drivers/usb/这个目录下边放了很多很多文件,usb core与其他各种设备的驱动程序的代码都堆砌在这里,后来,怎奈世间万千的变幻,总爱把有情的人分两端.于是在drivers/usb/目录下面出来了 一个core目录,就专门放一些核心的代码,比如初始化整个usb系统,初始化root hub,初始化host controller的代码,再后来甚至把host controller相关的代码也单独建了一个目录,叫host目录,这是因为usb host controller随着时代的发展,也开始有了好几种,不再像刚开始那样只有一种,所以呢,设计者们把一些host controller公共的代码仍然留在core目录下,而一些各host controller单独的代码则移到host目录下面让负责各种host controller的人去维护,常见的host controller有三种,分别叫做EHCI,UHCI,OHCI,所以这样,出来了三个概念,usb core,usb host,usb device,即 原本是一家人,却被活生生的分成了两岸三地...的确,现实总是很无奈,然而,心若知道灵犀的方向,哪怕不能够朝夕相伴?没错,usb通信的灵魂就是 usb协议. usb协议将是所有usb设备和usb主机所必须遵循的游戏规则.这种规则也很自然的体现在了代码中.于是,我们需要了解的不仅仅是 drivers/usb/storage/目录下面的冬冬,还得去了解那外面的世界,虽然,只需要了解一点点.

还是回到那个初始化函数吧,usb_stor_init,看了它的代码每一个人的心中都有一种莫名的兴奋,因为它太短了,就那么几行,除了两个printk语句以外,就是一个函数的调用,usb_register.

printk 不用我说,每一个有志青年都该知道,就算没见过printk也该见过printf吧,否则的话,你扪心自问,你对得起谭浩强大哥吗?在谭浩强大哥的带领下 我们学会了用#include->main()->printf()来打印hello,world!从而向全世界展示了我们懂C语言.而 stdio.h就是一个C库,printf是一个函数,来自函数库,可是内核中没有标准C库,所以开发者们自己准备了一些函数,专门用于内核代码中,所以 就出来了一个printk,printk的"k"就是kernel,内核.所以我们只要把它当作printf的兄弟即可,如果感兴趣,可以去研究一下 printk的特点,她和printf多少有些不同,但基本思想是一样的.所以我们就不多讲了,当然驱动程序中所有的printk语句对U盘的工作都没有 什么用,她无非是打出来给我们看的,或者说打印给用户看,或者呢,打印给开发者看,特别是开发者要调试程序的时候,就会很有用.

于是我们更开心了,不用看printk的话,那就只有一个函数调用了,usb_register.这个函数是干嘛的?首先这个函数正是来自usb core.凡是usb设备驱动,都要调用这个函数来向usb core注册,从而让usb core知道有这么一个设备.这就像政府规定,一对夫妻结婚要到相关部门那里去登记是一样的,我们无需知道政府是如何管理的,只需要知道去政府那里登记即可.

这 样,insmod的时候,usb_stor_init这个函数会被调用,初始化就算完成了.于是设备就开始工作了...而当我们rmmod的时 候,usb_stor_exit这个函数会被调用,我们发现,这个函数也很短,我们能看出来,US_DEBUG也就是打印一些咚咚,因此,这里实际上也就 是调用了一个函数usb_deregister(),她和usb_register()是一对,完成了注销的工作,从此设备就从usb core中消失了.于是我们惊人的发现,编写设备驱动竟是如此的简单,驱动程序真的就这么结束了?...

这一切,不禁让人产生了一种幻觉,让人分不清故事从哪里开始,又从哪里结束,一切都太短暂了.仿佛开始在结束的时候开始,而结束却在开始的时候就早已结束.

真的吗?

答案是否定的.孔子已经教育过我们,不光要看懂代码,更要理解代码背后的哲学.

所以我们在继续之前,先来看看这里到底有什么哲学.而这,就是伟大的Linux Kernel 2.6中的统一的设备模型.

我 们并无意去详细介绍2.6中的设备模型,但是不懂设备模型又怎能说自己懂设备驱动呢?读代码的人,写代码的人,都要知道,什么是设备驱动?什么又是设备? 设备和驱动之间究竟是什么关系?设备如何与计算机主机联系起来?我相信在中关村买盗版光盘的哥们儿也能回答这个问题.计算机世界里,设备有很多种类,比如 PCI设备,比如ISA设备,再比如SCSI设备,再比如我们这里的USB设备.为设备联姻的是总线,是他把设备连入了计算机主机.但是与其说设备是嫁给 了计算机主机,倒不如说设备是嫁给了设备驱动程序.很显然,在计算机世界里,无论风里雨里,陪伴着设备的正是驱动程序.

唯一的遗憾是,计算机中的设备和驱动程序的关系却并非如可乐和拉环的关系那样,一对一.然而世上又有多少事情总能如人愿呢.

Linux 设备模型中三个很重要的概念就是总线,设备,驱动.即bus,device,driver,而实际上内核中也定义了这么一些数据结构,他们是struct bus_type,struct device,struct device_driver,这三个重要的数据结构都来自一个地方,include/linux/device.h.我们知道总线有很多种,pci总 线,scsi总线,usb总线,所以我们会看到Linux内核代码中出现 pci_bus_type,scsi_bus_type,usb_bus_type,他们都是struct bus_type类型的变量.而struct bus_type结构中两个非常重要的成员就是struct kset drivers和struct kset devices.kset和另一个叫做kobject正是Linux Kernel 2.6中设备模型的基本元素,但此处我们却不愿多讲,因为暂时不用去认识他们.我们的生命中会遇见许许多多的人和事,但更多的人和事与我们只是擦肩而过, 只是我们生命中的过客而已.在我们人生的电影中,他们也许只有一个镜头,甚至那一个镜头后来也被剪辑掉了.这里我们只需要知道,drivers和 devices的存在,让struct bus_type与两个链表联系了起来,一个是devices的链表,一个是drivers的链表,也就是说,知道一条总线所对应的数据结构,就可以找到这条总线所关联的设备有哪些,又有哪些支持这类设备的驱动程序.

而 要实现这些,就要求每次出现一个设备就要向总线汇报,或者说注册,每次出现一个驱动,也要向总线汇报,或者说注册.比如系统初始化的时候,会扫描连接了哪 些设备,并为每一个设备建立起一个struct device的变量,每一次有一个驱动程序,就要准备一个struct device_driver结构的变量.把这些变量统统加入相应的链表,device插入devices链表,driver插入drivers链表. 这样通过总线就能找到每一个设备,每一个驱动.

然而,假如计算机里只有设备却没有对应的驱动,那么设备无法工作.反 过来,倘若只有驱动却没有设备,驱动也起不了任何作用.在他们遇见彼此之前,双方都如同路埂的野草,一个飘啊飘,一个摇啊摇,谁也不知道未来在哪里,只能 在生命的风里飘摇.于是总线上的两张表里就慢慢的就挂上了那许多孤单的灵魂.devices开始多了,drivers开始多了,他们像是两个来自世 界,devices们彼此取暖,drivers们一起狂欢,但他们有一点是相同的,都只是在等待属于自己的那个另一半.

看代码的我,一直好奇的想知道,他们是否和我们现实中一样,有些人注定是等别人,而有些人是注定被人等的.

struct bus_type中为devices和drivers准备了两个链表, 而代表device的结构体struct device中又有两个成员,struct bus_type *bus和struct device_driver *driver,同样,代表driver的结构体struct device_driver同样有两个成员,struct bus_type *bus和struct list_head devices,struct device和struct device_driver的定义和struct bus_type一样,在include/linux/device.h中.凭 一种男人的直觉,可以知晓,struct device中的bus记录的是这个设备连在哪条总线上,driver记录的是这个设备用的是哪个驱动,反过来,struct device_driver中的bus代表的也是这个驱动属于哪条总线,devices记录的是这个驱动支持的那些设备,没错,是devices(复 数),而不是device(单数),因为一个驱动程序可以支持一个或多个设备,反过来一个设备则只会绑定给一个驱动程序.

于 是我们想知道,关于bus,关于device,关于driver,他们是如何建立联系的呢?换言之,这三个数据结构中的指针是如何被赋值的?绝对不可能发 生的事情是,一旦为一条总线申请了一个struct bus_type的数据结构之后,它就知道它的devices链表和drivers链表会包含哪些东西,这些咚咚一定不会是先天就有的,只能是后天填进来 的.而具体到usb系统,完成这个工作的就是usb core.usb core的代码会进行整个usb系统的初始化,比如申请struct bus_type usb_bus_type,然后会扫描usb总线,看线上连接了哪些usb设备,或者说root hub上连了哪些usb设备,比如说连了一个usb键盘,那么就为它准备一个struct device,根据它的实际情况,为这个struct device赋值,并插入devices链表中来.又比如root hub上连了一个普通的hub,那么除了要为这个hub本身准备一个struct device以外,还得继续扫描看这个hub上是否又连了别的设备,有的话继续重复之前的事情,这样一直进行下去,直到完成整个扫描,最终就把 usb_bus_type中的devices链表给建立了起来.

那么drivers链表呢?这个就不用bus方面主动了,而该由每一个driver本身去bus上面登记,或者说挂牌.具体到usb系统,每一个usb设备的驱动程序都会有一个struct usb_driver结构体,其代码如下,来自include/linux/usb.h

485 /* -------------------------------------------------------------------------- */

486

487 /**

488 * struct usb_driver - identifies USB driver to usbcore

489 * @owner: Pointer to the module owner of this driver; initialize

490 * it using THIS_MODULE.

491 * @name: The driver name should be unique among USB drivers,

492 * and should normally be the same as the module name.

493 * @probe: Called to see if the driver is willing to manage a particular

494 * interface on a device. If it is, probe returns zero and uses

495 * dev_set_drvdata() to associate driver-specific data with the

496 * interface. It may also use usb_set_interface() to specify the

497 * appropriate altsetting. If unwilling to manage the interface,

498 * return a negative errno value.

499 * @disconnect: Called when the interface is no longer accessible, usually

500 * because its device has been (or is being) disconnected or the

501 * driver module is being unloaded.

502 * @ioctl: Used for drivers that want to talk to userspace through

503 * the "usbfs" filesystem. This lets devices provide ways to

504 * expose information to user space regardless of where they

505 * do (or don't) show up otherwise in the filesystem.

506 * @suspend: Called when the device is going to be suspended by the system.

507 * @resume: Called when the device is being resumed by the system.

508 * @id_table: USB drivers use ID table to support hotplugging.

509 * Export this with MODULE_DEVICE_TABLE(usb,...). This must be set

510 * or your driver's probe function will never get called.

511 * @driver: the driver model core driver structure.

512 *

513 * USB drivers must provide a name, probe() and disconnect() methods,

514 * and an id_table. Other driver fields are optional.

515 *

516 * The id_table is used in hotplugging. It holds a set of descriptors,

517 * and specialized data may be associated with each entry. That table

518 * is used by both user and kernel mode hotplugging support.

519 *

520 * The probe() and disconnect() methods are called in a context where

521 * they can sleep, but they should avoid abusing the privilege. Most

522 * work to connect to a device should be done when the device is opened,

523 * and undone at the last close. The disconnect code needs to address

524 * concurrency issues with respect to open() and close() methods, as

525 * well as forcing all pending I/O requests to complete (by unlinking

526 * them as necessary, and blocking until the unlinks complete).

527 */

528 structusb_driver {

529 struct module *owner;

530

531 const char *name;

532

533 int (*probe) (struct usb_interface *intf,

534 const struct usb_device_id *id);

535

536 void (*disconnect) (struct usb_interface *intf);

537

538 int (*ioctl) (struct usb_interface *intf, unsigned int code, void *buf);

539

540 int (*suspend) (struct usb_interface *intf, u32 state);

541 int (*resume) (struct usb_interface *intf);

542

543 const struct usb_device_id *id_table;

544

545 struct device_driver driver;

546 };

547 #define to_usb_driver(d) container_of(d, struct usb_driver, driver)

看似很长一段,实际上也就是注释为主.而此刻我们只需注意到其中的struct device_driver driver这个成员,usb core为每一个设备驱动准备了一个函数,让它把自己的这个struct device_driver driver插入到usb_bus_type中的drivers链表中去.而这个函数正是我们此前看到的usb_register.而与之对应的 usb_deregister所从事的正是与之相反的工作,把这个结构体从drivers链表中删除.可以说,usb core的确是用心良苦,为每一个usb设备驱动做足了功课,正因为如此,作为一个实际的usb设备驱动,它在初始化阶段所要做的事情就很少,很简单了, 直接调用usb_register即可.事实上,没有人是理所当然应该为你做什么的,但usb core这么做了.所以每一个写usb设备驱动的人应该铭记,usb device driver绝不是一个人在工作,在他身后,是usb core所提供的默默无闻又不可或缺的支持.

bus上的两张链表记录了每一个device和driver,那么device和driver这两者之间又是如何联系起来的呢?此刻,必须抛出这样一个问题,先有device还是driver?

很 久很久以前,在那激情燃烧的岁月里,先有的是device,每一个要用的device在计算机启动之前就已经插好了,插放在它应该在的位置上,然后计算机 启动,然后操作系统开始初始化,总线开始扫描设备,每找到一个设备,就为其申请一个struct device结构,并且挂入总线中的devices链表中来,然后每一个驱动程序开始初始化,开始注册其struct device_driver结构,然后它去总线的devices链表中去寻找(遍历),去寻找每一个还没有绑定driver的设备,即struct device中的struct device_driver指针仍为空的设备,然后它会去观察这种设备的特征,看是否是他所支持的设备,如果是,那么调用一个叫做 device_bind_driver的函数,然后他们就结为了秦晋之好.换句话说,把struct device中的struct device_driver driver指向这个driver,而struct device_driver driver把struct device加入他的那张struct list_head devices链表中来.就这样,bus,device,和driver,这三者之间或者说他们中的两两之间,就给联系上了.知道其中之一,就能找到另外 两个.一荣俱荣,一损俱损.

但现在情况变了,在这红莲绽放的日子里,在这樱花伤逝的日子里,出现了一种新的名词,叫热插 拔.device可以在计算机启动以后在插入或者拔出计算机了.因此,很难再说是先有device还是先有driver了.因为都有可能.device可 以在任何时刻出现,而driver也可以在任何时刻被加载,所以,出现的情况就是,每当一个struct device诞生,它就会去bus的drivers链表中寻找自己的另一半,反之,每当一个struct device_driver诞生,它就去bus的devices链表中寻找它的那些设备.如果找到了合适的,那么ok,和之前那种情况一下,调用 device_bind_driver绑定好.如果找不到,没有关系,等待吧,等到昙花再开,等到风景看透,心中相信,这世界上总有一个人是你所等的,只 是还没有遇到而已.

好,继续,事实上,完善这个三角关系,正是每一个设备驱动初始化阶段所完成的重要使命之一.让我们还是回到代码中来,usb_register这个函数调用是调用了,但是传递给他的参数是什么呢?

我们注意到,那句调用是这样子的,

1064 /* register the driver, return usb_register return code if error */

1065 retval = usb_register(&usb_storage_driver);

是的,传递了一个叫做usb_storage_driver的家伙,这是什么?同一文件中,drivers/usb/storage/usb.c:

232 struct usb_driver usb_storage_driver = {

233 .owner = THIS_MODULE,

234 .name = "usb-storage",

235 .probe = storage_probe,

236 .disconnect = storage_disconnect,

237 .id_table = storage_usb_ids,

238 };

可 以看到这里定义了一个struct usb_driver的结构体变量,usb_storage_driver,关于usb_driver我们上节已经说过了,当时主要说的是其中的成员 driver,而眼下要讲的则是另外几个成员.首先,.owner和.name这两个没啥好多说的,owner这玩艺是用来给模块计数的,每个模块都这么 用,赋值总是THIS_MODULE,而name就是这个模块的名字,usb core会处理它,所以如果这个模块正常被加载了的话,使用lsmod命令能看到一个叫做usb-storage的模块名.重点要讲一讲,.probe 和.disconnect以及这个id_table.


得到了该usb_device之后,我们要对我们自定义的usb_skel各个状态跟资源作初始化。这部分工作的任务主要是向usb_skel注册该usb设备的端点。这里可能要补充以下一些关于usb_interface_descriptor的知识,但因为内核源码对该结构体的注释不多,所以只能靠个人猜测。在一个usb_host_interface结构里面有一个usb_interface_descriptor叫做desc的成员,他应该是用于描述该interface的一些属性,其中bNumEndpoints是一个8位(b for byte)的数字,他代表了该接口的端点数。probe然后遍历所有的端点,检查他们的类型跟方向,注册到usb_skel中。
/* set up the endpoint information */
/* use only the first bulk-in and bulk-out endpoints */
iface_desc = interface->cur_altsetting;
for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
endpoint = &iface_desc->endpoint[i].desc;
if (!dev->bulk_in_endpointAddr &&
((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) = = USB_DIR_IN) &&
((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) = = USB_ENDPOINT_XFER_BULK)){
/* we found a bulk in endpoint */
buffer_size = le16_to_cpu(endpoint->wMaxPacketSize);
dev->bulk_in_size = buffer_size;
dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);
if (!dev->bulk_in_buffer) {
err("Could not allocate bulk_in_buffer");
goto error;
}
}
if (!dev->bulk_out_endpointAddr &&
((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK)= =USB_DIR_OUT) &&
((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)= = USB_ENDPOINT_XFER_BULK)){
/* we found a bulk out endpoint */
dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;
}
}
if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) {
err("Could not find both bulk-in and bulk-out endpoints");
goto error;
}
Linux USB驱动框架分析(五)
接下来的工作是向系统注册一些以后会用的的信息。首先我们来说明一下usb_set_intfdata(),他向内核注册一个data,这个data的结构可以是任意的,这段程序向内核注册了一个usb_skel结构,就是我们刚刚看到的被初始化的那个,这个data可以在以后用usb_get_intfdata来得到。
usb_set_intfdata(interface, dev);
retval = usb_register_dev(interface, &skel_class);
然后我们向这个interface注册一个skel_class结构。这个结构又是什么?我们就来看看这到底是个什么东西:
static struct usb_class_driver skel_class= {
.name = "skel%d",
.fops = &skel_fops,
.minor_base = USB_SKEL_MINOR_BASE,
};
它其实是一个系统定义的结构,里面包含了一名字、一个文件操作结构体还有一个次设备号的基准值。事实上它才是定义真正完成对设备IO操作的函数。所以他的核心内容应该是skel_fops。这里补充一些我个人的估计:因为usb设备可以有多个interface,每个interface所定义的IO操作可能不一样,所以向系统注册的usb_class_driver要求注册到某一个interface,而不是device,因此,usb_register_dev的第一个参数才是interface,而第二个参数就是某一个usb_class_driver。通常情况下,linux系统用主设备号来识别某类设备的驱动程序,用次设备号管理识别具体的设备,驱动程序可以依照次设备号来区分不同的设备,所以,这里的次设备好其实是用来管理不同的interface的,但由于这个范例只有一个interface,在代码上无法求证这个猜想。
static struct file_operations skel_fops= {
.owner = THIS_MODULE,
.read = skel_read,
.write = skel_write,
.open = skel_open,
.release = skel_release,
};
这个文件操作结构中定义了对设备的读写、打开、释放(USB设备通常使用这个术语release)。他们都是函数指针,分别指向skel_readskel_writeskel_openskel_release这四个函数,这四个函数应该有开发人员自己实现。
当设备被拔出集线器时,usb子系统会自动地调用disconnect,他做的事情不多,最重要的是注销class_driver(交还次设备号)和interfacedata:
dev = usb_get_intfdata(interface);
usb_set_intfdata(interface, NULL);
/* give back our minor */
usb_deregister_dev(interface, &skel_class);
然后他会用kref_put(&dev->kref, skel_delete)进行清理,kref_put的细节参见前文。
到目前为止,我们已经分析完usb子系统要求的各个主要操作,下一部分我们在讨论一下对USB设备的IO操作。
Linux USB驱动框架分析(六)
说到usb子系统的IO操作,不得不说usb request block,简称urb。事实上,可以打一个这样的比喻,usb总线就像一条高速公路,货物、人流之类的可以看成是系统与设备交互的数据,而urb就可以看成是汽车。在一开始对USB规范细节的介绍,我们就说过USBendpoint4种不同类型,也就是说能在这条高速公路上流动的数据就有四种。但是这对汽车是没有要求的,所以urb可以运载四种数据,不过你要先告诉司机你要运什么,目的地是什么。我们现在就看看struct urb的具体内容。它的内容很多,为了不让我的理解误导各位,大家最好还是看一看内核源码的注释,具体内容参见源码树下include/linux/usb.h
在这里我们重点介绍程序中出现的几个关键字段:
struct usb_device *dev
urb所发送的目标设备。
unsigned int pipe
一个管道号码,该管道记录了目标设备的端点以及管道的类型。每个管道只有一种类型和一个方向,它与他的目标设备的端点相对应,我们可以通过以下几个函数来获得管道号并设置管道类型:
unsigned int usb_sndctrlpipe(struct usb_device *dev, unsigned int endpoint)
把指定USB设备的指定端点设置为一个控制OUT端点。
unsigned int usb_rcvctrlpipe(struct usb_device *dev, unsigned int endpoint)
把指定USB设备的指定端点设置为一个控制IN端点。
unsigned int usb_sndbulkpipe(struct usb_device *dev, unsigned int endpoint)
把指定USB设备的指定端点设置为一个批量OUT端点。
unsigned int usb_rcvbulkpipe(struct usb_device *dev, unsigned int endpoint)
把指定USB设备的指定端点设置为一个批量OUT端点。
unsigned int usb_sndintpipe(struct usb_device *dev, unsigned int endpoint)
把指定USB设备的指定端点设置为一个中断OUT端点。
unsigned int usb_rcvintpipe(struct usb_device *dev, unsigned int endpoint)
把指定USB设备的指定端点设置为一个中断OUT端点。
unsigned int usb_sndisocpipe(struct usb_device *dev, unsigned int endpoint)
把指定USB设备的指定端点设置为一个等时OUT端点。
unsigned int usb_rcvisocpipe(struct usb_device *dev, unsigned int endpoint)
把指定USB设备的指定端点设置为一个等时OUT端点。
unsigned int transfer_flags
当不使用DMA时,应该transfer_flags |= URB_NO_TRANSFER_DMA_MAP(按照代码的理解,希望没有错)。
int status
当一个urb把数据送到设备时,这个urb会由系统返回给驱动程序,并调用驱动程序的urb完成回调函数处理。这时,status记录了这次数据传输的有关状态,例如传送成功与否。成功的话会是0
要能够运货当然首先要有车,所以第一步当然要创建urb
struct urb *usb_alloc_urb(int isoc_packets, int mem_flags);
第一个参数是等时包的数量,如果不是乘载等时包,应该为0,第二个参数与kmalloc的标志相同。
要释放一个urb可以用:
void usb_free_urb(struct urb *urb);
要承载数据,还要告诉司机目的地信息跟要运的货物,对于不同的数据,系统提供了不同的函数,对于中断urb,我们用
void usb_fill_int_urb(struct urb *urb, struct usb_device *dev, unsigned int pipe,
void *transfer_buffer, int buffer_length,
usb_complete_t complete, void *context, int interval);
这里要解释一下,transfer_buffer是一个要送/收的数据的缓冲,buffer_length是它的长度,completeurb完成回调函数的入口,context由用户定义,可能会在回调函数中使用的数据,interval就是urb被调度的间隔。
对于批量urb和控制urb,我们用:
void usb_fill_bulk_urb(struct urb *urb, struct usb_device *dev, unsigned int pipe,
void *transfer_buffer, int buffer_length, usb_complete_t complete,
void *context);
void usb_fill_bulk_urb(struct urb *urb, struct usb_device *dev, unsigned int pipe,
unsigned char* setup_packet,void *transfer_buffer,
int buffer_length, usb_complete_t complete,void *context);
控制包有一个特殊参数setup_packet,它指向即将被发送到端点的设置数据报的数据。
对于等时urb,系统没有专门的fill函数,只能对各urb字段显示赋值。
有了汽车,有了司机,下一步就是要开始运货了,我们可以用下面的函数来提交urb
int usb_submit_urb(struct urb *urb, int mem_flags);
mem_flags有几种:GFP_ATOMICGFP_NOIOGFP_KERNEL,通常在中断上下文环境我们会用GFP_ATOMIC
当我们的卡车运货之后,系统会把它调回来,并调用urb完成回调函数,并把这辆车作为函数传递给驱动程序。我们应该在回调函数里面检查status字段,以确定数据的成功传输与否。下面是用urb来传送数据的细节。
/* initialize the urb properly */
usb_fill_bulk_urb(urb, dev->udev,
usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
buf, writesize, skel_write_bulk_callback, dev);
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
/* send the data out the bulk port */
retval = usb_submit_urb(urb, GFP_KERNEL);
这里skel_write_bulk_callback就是一个完成回调函数,而他做的主要事情就是检查数据传输状态和释放urb
dev = (struct usb_skel *)urb->context;
/* sync/async unlink faults aren't errors */
if (urb->status && !(urb->status = = -ENOENT || urb->status == -ECONNRESET || urb->status = = -ESHUTDOWN)) {
dbg("%s - nonzero write bulk status received: %d", __FUNCTION__, urb->status);
}
/* free up our allocated buffer */
usb_buffer_free(urb->dev, urb->transfer_buffer_length,
urb->transfer_buffer, urb->transfer_dma);
事实上,如果数据的量不大,那么可以不一定用卡车来运货,系统还提供了一种不用urb的传输方式,而usb-skeleton的读操作正是采用这种方式实现:
/* do a blocking bulk read to get data from the device */
retval = usb_bulk_msg(dev->udev,
usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr),
dev->bulk_in_buffer,
min(dev->bulk_in_size, count),
&bytes_read, 10000);
/* if the read was successful, copy the data to userspace */
if (!retval) {
if (copy_to_user(buffer, dev->bulk_in_buffer, bytes_read))
retval = -EFAULT;
else
retval = bytes_read;
}
程序使用了usb_bulk_msg来传送数据,它的原型如下:
int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe,void *data,
int len, int *actual length, int timeout)
这个函数会阻塞等待数据传输完成或者等到超时,data是输入/输出缓冲,len是它的大小,actual length是实际传送的数据大小,timeout是阻塞超时。
对于控制数据,系统提供了另外一个函数,他的原型是:
Int usb_contrl_msg(struct usb_device *dev, unsigned int pipe, __u8 request,
__u8 requesttype, __u16 value, __u16 index, void *data,
__u16 size, int timeout);
request是控制消息的USB请求值、requesttype是控制消息的USB请求类型,value是控制消息的USB消息值,index是控制消息的USB消息索引。具体是什么,暂时不是很清楚,希望大家提供说明。
至此,Linux下的USB驱动框架分析基本完成了。


分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics