分享

USB驱动分析(一)

 Jaylongor 2014-06-11

这个故事中使用的是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 <
hch@infradead.org>
# 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 <http://www./>.

显然,这个选项和我们没有关系,首先这是专门针对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 <linux/init.h>  /* Needed for the macros */
      2 #include <linux/module.h> /* 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_initusb_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<stdio.h>->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(单数),因为一个驱动程序可以支持一个或多个设备,反过来一个设备则只会绑定给一个驱动程序.

  1. 于是我们想知道,关于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 struct usb_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上的两张链表记录了每一个devicedriver,那么devicedriver这两者之间又是如何联系起来的呢?此刻,必须抛出这样一个问题,先有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 driverstruct 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.

 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多