之所以说uhci_hcd_init有技术含量,并不是说它包含多么精巧的算法,包含多么复杂的数据结构.而是因为这其中涉及了很多东西.首先924行,usb_disable涉及了Linux中的内核参数的概念.928行的kmalloc和936行的kmem_cache_create涉及了Linux内核中内存申请的问题,931行debugfs_create_dir则涉及到了文件系统,一个虚拟的文件系统debugfs,而941行pci_register_driver则涉及到了Linux中pci设备驱动程序的注册.
这么多东西往这里一堆,其复杂程度立马就上来了.在这个共和国58周年的喜庆日子里,我是多么希望我们的伟大祖国繁荣昌盛啊!同时,我又是多么希望这个函数有且只有921那么一行printk语句啊!
第一,内核参数,什么是内核参数?看一下你的grub文件,
title SUSE Linux Enterprise Server 10 (kdb enabled)
kernel (hd0,2)/boot/vmlinuz-2.6.22.1-test root=/dev/hda3 resume=/dev/hda2 splash=silent showopts
initrd /boot/initrd-2.6.22.1-test
kernel那行的都是内核参数,比如root,比如resume,比如splash,比如showopts.其中root=代表的是你的Root文件系统的位置,resume=代表的是你用来做software suspend恢复的那个分区,而usb子系统也准备了这么一个参数,其名字就叫做nousb.所以你可以往这一行后面加上nousb,这就意味着你的系统不需要支持usb,或者换一种说法,你把usb子系统给disable掉了.nousb默认为0,你设置了它就为1.而usb_disabled返回的就是nousb的值.我们在drivers/usb/core/usb.c中也能看到这个函数:
852 /*
853 * for external read access to <nousb>
854 */
855 int usb_disabled(void)
856 {
857 return nousb;
858 }
第二,申请内存的两个函数kmalloc和kmem_cache_create.对于kmalloc,我们早已不陌生,而kmem_cache_create,呵呵,传说中的Slab现身了.传统上,kmem_cache_create是Slab分配器的接口函数,用于创建一个cache,你要是不知道什么是cache的话,你就把它当作内存池,而这里创建了一个cache之后,以后你就可以用另一个函数kmem_cache_zalloc来申请内存,使用kmem_cache_free来释放内存,而当你玩腻了之后,你可以使用kmem_cache_destroy来彻底释放这个内存池.这其中一个很重要的特点是每次你用kmem_cache_zalloc申请的内存大小是一样的,这正是你在kmem_cache_create中的第二个参数所指定的,比如这里的具体情况就是sizeof(struct urb_priv),即以后你看到你用kmem_cache_zalloc申请的内存总是这么大,而这里kmem_cache_create的返回值就是创建好的那个cache.这里返回值被赋给了uhci_up_cachep,它是一个struct kmem_cache的结构体指针.所以以后你使用kmem_cache_zalloc的时候只要把这个uhci_up_cachep作为参数即可.然后你就能得到你想要的内存.对于kmem_cache_free和kmem_cache_destroy也一样.
对这些函数最简单的理解方法就是,比如你去沃尔玛超市,你需要装东西,超市里给你提供了篮子,你可以把你需要的东西装在篮子里,白痴都知道超市里的篮子数量是有限的,但是基本上你不会碰到说你去超市发现篮子不够的情况,这是因为沃尔玛在开张之前就准备了足够多的篮子,比如它订做了一个仓库的篮子,每个篮子都一样大.即一开始沃尔玛方就调用了kmem_cache_create做了一池的篮子,而你每次去就是使用kmem_cache_zalloc去拿一个篮子即可,而当你付款之后你要离开了,你又调用kmem_cache_free去归还篮子,每个人都这样做的话,你下次去了你要用篮子你又可以kmem_cache_zalloc再拿一个.而一旦哪天沃尔玛宣武门分店连续亏损,店子做不下去了,它就可以调用kmem_cache_destroy把篮子全都毁掉,当然更形象的例子是它把篮子转移到别的店去,比如知春路分店,那么从整个沃尔玛公司来看,可以供来装东西的容器总容积还是没有变.正如你的计算机总的内存是不会变的.假如公司将来又打算在国贸开一家分店,那么它可以再次调用kmem_cache_create.而对你来说,你并不需要知道一池内存到底有多少,就像你永远不用知道沃尔玛知春路店究竟有多少个篮子一样.
好了,第三个,debugfs_create_dir,传说中的debugfs也现身了.很多事情都是早已注定的,原本以为写设备驱动的只要懂一些硬件规范就可以了,后来终于在眼泪中明白,有些人一旦写代码就会越写越复杂.如今的内核代码早已不像那时候了那么单纯了,记得奶茶刘若英也在专门为此而唱了一首歌,叫做后来,歌中感叹,
后来我总算学会了如何去看代码,
可惜那简单的代码早已远去消失在人海,
后来终于在眼泪中明白,
有些代码一旦错过就不再.
那时候的代码,为什么就能那样简单,
而又是为什么,人年少时,没有好好的看代码,
在这相似的深夜里你是否一样也在静静追悔感伤,
如果当时我们能不那么贪玩,现在也不那么遗憾.
遗憾归遗憾,代码还是要看.以前我一直以为,Linux中PCI子系统和USB子系统的掌门人Greg同志只是一个花拳绣腿的家伙,只是每天忙着到处演讲,开会,而不干正经事,后来我发现,其实不是的,Greg其实还是干了很多有意义的事情,我不得不承认,Greg是条汉子!debugfs就是他开发的,这是一个虚拟的文件系统,Greg同志在2.6.11中把它引入的,专门用于输出调试信息,这个文件系统默认是被挂载在/sys/kernel/debug下面,比如
localhost:~ # mount
/dev/hda3 on / type reiserfs (rw,acl,user_xattr)
proc on /proc type proc (rw)
sysfs on /sys type sysfs (rw)
debugfs on /sys/kernel/debug type debugfs (rw)
udev on /dev type tmpfs (rw)
devpts on /dev/pts type devpts (rw,mode=0620,gid=5)
这个文件系统是专门为开发者准备的,在配置内核的时候可以选择编译进去也可以选择不编译进去.其对应的Kconfig文件是lib/Kconfig.debug.
50 config DEBUG_FS
51 bool "Debug Filesystem"
52 depends on SYSFS
53 help
54 debugfs is a virtual file system that kernel developers use to put
55 debugging files into. Enable this option to be able to read and
56 write to these files.
57
58 If unsure, say N.
很有意思的是这个文件系统居然依赖于另一个文件系统,sysfs.如果你用make menuconfig命令编译内核的话,你可以在Kernel hacking下面找到它,就叫DEBUG_FS.我们不去深入研究这个文件系统,但是对于它的接口函数是有必要了解一下的.
首先第一个就是这里这个debugfs_create_dir,这很简单,就是创建一个目录.像这里这么一行的作用就是在/sys/kernel/debug下面创建一个叫做uhci的目录,比如你加载了uhci-hcd这个模块的话你就能像我一样看到uhci这么一个目录:
localhost:/usr/src/linux-2.6.22.1 # ls /sys/kernel/debug/
kprobes uhci
这个函数的返回值是文件系统里最经典的一个结构体指针,struct dentry的指针,而这里我们把返回值赋给了uhci_debugfs_root,后者正是一个struct dentry指针,它被定义于drivers/usb/host/uhci-debug.c中,
20 static struct dentry *uhci_debugfs_root;
显然这个指针对我们uhci-hcd这个模块来说是到处都可以引用的.而从此之后要在uhci目录下创建文件就是用debugfs_create_file这个函数,我们将会在uhci_start函数中见到,到时候再说.而以后删除这个目录的任务就在uhci_hcd_cleanup中,它只要调用debugfs_remove函数即可.至于在这个目录下创建什么文件,具体有什么用,只能到时候再来看,现在时机尚未成熟,很多术语尚未交待.
现在剩下第四个问题,pci_register_driver,其实一路走来的兄弟们应该多少有点感觉,虽然我们没见过这个函数,但是我们能感觉出它和我们当初的那个usb_register_driver是一个性质的,一个是注册usb驱动,一个是注册pci驱动.这里的参数uhci_pci_driver是我们要关注的.
894 static const struct pci_device_id uhci_pci_ids[] = { {
895 /* handle any USB UHCI controller */
896 PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_UHCI, ~0),
897 .driver_data = (unsigned long) &uhci_driver,
898 }, { /* end: all zeroes */ }
899 };
900
901 MODULE_DEVICE_TABLE(pci, uhci_pci_ids);
902
903 static struct pci_driver uhci_pci_driver = {
904 .name = (char *)hcd_name,
905 .id_table = uhci_pci_ids,
906
907 .probe = usb_hcd_pci_probe,
908 .remove = usb_hcd_pci_remove,
909 .shutdown = uhci_shutdown,
910
911 #ifdef CONFIG_PM
912 .suspend = usb_hcd_pci_suspend,
913 .resume = usb_hcd_pci_resume,
914 #endif /* PM */
915 };
跟我们走过usb-storage的同志们应该是一看就知道怎么回事了吧?尤其是那个uhci_pci_ids,这张表怎么看怎么觉得似曾相识.没错,它的作用正如我们当初那张storage_usb_ids.所以我就不多说了.这里PCI_CLASS_SERIAL_USB_UHCI是0x0c0300,03表示类别为03,这代表USB,而00代表UHCI,如果是OHCI,就是0x0c0310,而EHCI则是0x0c0320.而最前面两位的0c呢?PCI spec中专门有一节叫Class Code,其中就有如下一张图:
<shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 414.75pt; HEIGHT: 158.25pt" type="#_x0000_t75"><imagedata o:title="" src="file:///C:/DOCUME~1/JASON_~1/LOCALS~1/Temp/msohtml1/01/clip_image001.emz"></imagedata></shape>
可以看出,PCI spec规定了,0c代表所有的串行总线控制器,其中03为USB.
所以我们不难知道,我们真正的故事将从哪个函数开始.老规矩,probe函数,即usb_hcd_pci_probe.在讲这个函数之前我们把这个超级简单的uhci_hcd_cleanup也给贴出来.
961 static void __exit uhci_hcd_cleanup(void)
962 {
963 pci_unregister_driver(&uhci_pci_driver);
964 kmem_cache_destroy(uhci_up_cachep);
965 debugfs_remove(uhci_debugfs_root);
966 kfree(errbuf);
967 }
我相信,看完我们这一节的你,不需要我多讲这个函数了吧,如果我再多讲两句,恐怕你就会用<<疯狂的石头>>里的那句经典台词说我不仅是侮辱你的人格,更是侮辱你的智商了.
总之吧,一个模块就是这样,你写一个注册函数,写一个注销函数,就ok了.我们出来行走江湖的,最重要是讲一个利字!Linux中模块机制的便利就是,当你想用它的时候你可以用modprobe或者insmod加载它,当你不想用它的时候,你可以用rmmod卸载它.加载就是注册,卸载就是注销,就像银行里的开户和销户,但至少这种服务你可以随意享受,不像工商银行那样乱收费,而且收费的时候说要和国际接轨,服务的时候却说要有中国特色,存款利率说要和国际接轨,贷款利率却又说要有中国特色.赚钱的时候告诉国人说自己是海外上市企业,然后将大把大把从国人腰包里搜刮来的银子奉献给国外的同行,赔钱的时候告诉国人说,自己是全民所有制企业,财政部就手忙脚乱拿着纳税人的银子给他们家动辄几千亿的坏账买单…