usb_bus_init来自drivers/usb/core/hcd.c,很显然,它就是初始化struct
usb_bus结构体指针.而这个结构体变量hcd->self的内存已经在刚才为hcd申请内存的时候一并申请了.
688 /**
689 * usb_bus_init - shared initialization code
690 * @bus: the bus structure being initialized
691 *
692 * This code is used to initialize a usb_bus structure, memory for which is
693 * separately managed.
694 */
695 static void usb_bus_init (struct usb_bus *bus)
696 {
697 memset (&bus->devmap, 0, sizeof(struct usb_devmap));
698
699 bus->devnum_next = 1;
700
701 bus->root_hub = NULL;
702 bus->busnum = -1;
703 bus->bandwidth_allocated = 0;
704 bus->bandwidth_int_reqs = 0;
705 bus->bandwidth_isoc_reqs = 0;
706
707 INIT_LIST_HEAD (&bus->bus_list);
708 }
我相信你早已忘记了,当初在hub驱动中我就讲过,devnum_next在总线初始化的时候会被设为1,说的就是这里.现在证明当初我没有忽悠你吧.这里其它的赋值就不多说了,用到的时候我会告诉你的,相信我,这是同志的信任.
回到usb_create_hcd中来,又是几行赋值,飘过.
倒是1511行引起了我的注意,又是可恶的时间机制,init_timer,这个函数我们也见过多次了,usb-storage里见过,hub里见过,斑驳的陌生终于在时间的抚摸下变成了今日的熟悉.这里我们设置的函数是rh_timer_func,而传递给这个函数的参数是hcd.这个函数具体做什么我们走着瞧,不过你放心,咱们这个故事里会多次接触到这个timer,想逃是逃不掉的,躲得过初一躲不过十五.
1515行,INIT_WORK咱们也在hub驱动里见过了,这里这个hcd_resume_work什么时候会被调用咱们也到时候再看.
剩下两行赋值,1518行没啥好说的,struct usb_hcd有一个struct
hc_driver的结构体指针成员,所以就这样把它和咱们这个uhci_driver给联系起来了.而在uhci_driver中我们看到,其中有一个product_desc被赋值为"UHCI
Host Controller",所以这里也赋给hcd->product_desc,因为struct hc_driver和struct
usb_hcd这两个结构体中都有一个成员const char *product_desc,你说这不浪费吗?就一个破字符串,还得保存在两个地方,这些家伙九年制义务教育怎么学的?长此以往,国将不国矣!
至此,usb_create_hcd结束了,返回了这个申请好赋好值的hcd.我们继续回到probe函数中来.
89到124这个if-else的确让我开心了一把,因为if里说的是EHCI和OHCI的情况,else里针对的才是UHCI,鉴于EHCI将由某人来写,而OHCI和UHCI性质一样,我们不会讲,所以这就意味着这个if-else我只要从105行开始看,即直接看UHCI那部分的代码.爽!
不过我们也得知道这里为何要判断HCD_MEMORY,这个宏的意思是表明该HC的寄存器是使用memory的,而没有设置这个flag的HC的寄存器是使用I/O的.这些寄存器俗称I/O端口,或者说I/O
ports,这个I/O端口可以被映射在Memory Space,也可以被映射在I/O
Space.UHCI是属于后者,而EHCI/OHCI属于前者.
这里看上去必须多说几句,否则很难说清楚.以我们家Intel为代表的i386系列处理器中,内存和外部IO是独立编址独立寻址的,于是有一个地址空间叫做内存空间,另有一个地址空间叫做I/O空间.也就是说,从处理器的角度来说,i386提供了一些单独的指令用来访问I/O空间.换言之,访问I/O空间和访问普通的内存得使用不同的指令.而在一些玩嵌入式的处理器中,比如PowerPC,他们家就只使用一个空间,那就是内存空间,那像这种情况,外设的I/O端口的物理地址就被映射到内存地址空间中,这就是传说中的Memory-mapped,内存映射.而我们家那种情况,外设的I/O端口的物理地址就被映射到I/O地址空间中,这就是传说中的I/O-mapped,即I/O映射.
那么EHCI/OHCI呢,它们除了有寄存器以外,还有内存,而它们把这些统统映射到Memory
Space中去,而UHCI只使用寄存器来通信,所以它只需要映射寄存器,即I/O端口,而它的spec规定,它是映射到I/O空间.Linux中I/O
Memory和I/O ports都被视作一种资源,它们分别被记录在/proc/iomem和/proc/ioports中.
所以我们可以在这里看到uhci-hcd,
localhost:~ # cat /proc/ioports
0000-001f : dma1
0020-0021 : pic1
(此处省略若干行)
bca0-bcbf : 0000:00:1d.2
bca0-bcbf : uhci_hcd
bcc0-bcdf : 0000:00:1d.1
bcc0-bcdf : uhci_hcd
bce0-bcff : 0000:00:1d.0
bce0-bcff : uhci_hcd
c000-cfff : PCI Bus #10
cc00-ccff : 0000:10:0d.0
d000-dfff : PCI Bus #0e
dcc0-dcdf : 0000:0e:00.1
dcc0-dcdf : e1000
dce0-dcff : 0000:0e:00.0
dce0-dcff : e1000
e000-efff : PCI Bus #0c
e800-e8ff : 0000:0c:00.1
e800-e8ff : qla2xxx
ec00-ecff : 0000:0c:00.0
ec00-ecff : qla2xxx
fc00-fc0f : 0000:00:1f.1
fc00-fc07 : ide0
而在这里看到ehci-hcd,
localhost:~ # cat /proc/iomem
00000000-0009ffff : System RAM
00000000-00000000 : Crash kernel
(此处省略若干行)
d8000000-d80fffff : PCI Bus #01
d8000000-d80fffff : PCI Bus #02
d80f0000-d80fffff : 0000:02:0e.0
d80f0000-d80fffff : megasas: LSI Logic
d8100000-d81fffff : PCI Bus #0c
d8100000-d813ffff : 0000:0c:00.1
e0000000-efffffff : reserved
f2000000-f7ffffff : PCI Bus #06
f4000000-f7ffffff : PCI Bus #07
f4000000-f7ffffff : PCI Bus #08
f4000000-f7ffffff : PCI Bus #09
f4000000-f5ffffff : 0000:09:00.0
f4000000-f5ffffff : bnx2
f8000000-fbffffff : PCI Bus #04
f8000000-fbffffff : PCI Bus #05
f8000000-f9ffffff : 0000:05:00.0
f8000000-f9ffffff : bnx2
(此处省略若干行)
fca00400-fca007ff : 0000:00:1d.7
fca00400-fca007ff : ehci_hcd
fe000000-ffffffff : reserved
100000000-22fffffff : System RAM
要使用I/O内存首先要申请,然后要映射,而要使用I/O端口首先要申请,或者叫请求,对于I/O端口的请求意思是让内核知道你要访问这个端口,这样内核知道了以后它就不会再让别人也访问这个端口了.毕竟这个世界僧多粥少啊.申请I/O端口的函数是request_region,这个函数来自include/linux/ioport.h,
116 /* Convenience shorthand with allocation */
117 #define request_region(start,n,name) __request_region(&ioport_resource, (start), (n), (name))
118 #define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n), (name))
119 #define rename_region(region, newname) do { (region)->name = (newname); } while (0)
120
121 extern struct resource * __request_region(struct resource *,
122 resource_size_t start,
123 resource_size_t n, const char *name);
这里我们看到的那个request_mem_region是申请I/O内存用的.申请了之后,还需要使用ioremap或者ioremap_nocache函数来映射.
对于request_region,三个参数start,n,name表示你想使用从start开始的size为n的I/O
port资源,name自然就是你的名字了.这三个概念在我们刚才贴出来的cat
/proc/ioports里面显示的很清楚,name就是uhci-hcd.
那么对于uhci-hcd,我们究竟需要请求哪些地址,需要多少空间呢?嗯,又要提到那张上坟图了.PCI设备本身有一堆的地址空间,内存空间和IO空间.那么如何把这些空间映射到总线上来呢?用什么?寄存器.看上坟图中的那几个Base
Address 0,1,2,3,4,5,即每个设备都有6个地址空间,这叫做六个基址寄存器,有的设备还有一个ROM,所以又有一个Expansion
ROM Base Address,它对应第七个区间,或者说区间6,而在上坟图上对应的就叫做扩展ROM基址寄存器.每个寄存器都是四个字节.而我们在include/linux/pci.h中定义了,
227 /*
228 * For PCI devices, the region numbers are assigned this way:
229 *
230 * 0-5 standard PCI regions
231 * 6 expansion ROM
232 * 7-10 bridges: address space assigned to buses behind the bridge
233 */
234
235 #define PCI_ROM_RESOURCE 6
所以在我们的代码中我们看到循环条件就是从0到PCI_ROM_RESOURCE之前,即循环六次,因为有六个区间,区间也叫region.那么这些寄存器究竟取的什么值呢?这就是在PCI总线初始化的时候做的事情了,它会把你每个基址寄存器赋上值,而实际上就是映射于总线上的地址,总线驱动的作用就是让各个设备都需要的地址资源都得到满足,并且没有设备与设备之间的地址发生冲突.PCI总线驱动做了这些之后,我们PCI设备驱动就简单了,在需要使用的时候直接请求即可,正如这里的request_region.那么我们传递给request_region的具体参数是什么呢?
两个函数,pci_resource_start和pci_resource_len,就是去获得一个区间的起始地址和长度,所以我们就很好理解这段代码了.至于109行这个if判断,pci_resource_flags是用来判断一个资源是哪种类型的,include/linux/ioport.h中一共定义了四种资源:
36 #define IORESOURCE_IO 0x00000100 /* Resource type */
37 #define IORESOURCE_MEM 0x00000200
38 #define IORESOURCE_IRQ 0x00000400
39 #define IORESOURCE_DMA 0x00000800
它们是IO,Memory,中断,DMA.对应我们在/proc下看到的ioports,iomem,interrupt,dma四个文件.所以这里的意思就是判断说如果不是IO
Port资源,那么就不予理睬.因为UHCI主机控制器只需要理财I/O
Port.
request_region函数如果成功将返回非NULL,失败了才返回NULL.所以代码的意思就是一旦成功就跳出循环.反之,如果循环都结束了还未能请求到,那就说明出错了.那么你说为何一旦成功就跳出循环?老实说,这个问题足足困扰了我13秒钟,别小看13秒钟,有这么长时间刘翔都已经完成一次110米跨栏了.让spec来告诉你.
看到没有,20-23h,四个字节,这里正好对应UHCI的IO空间基址寄存器.换言之,UHCI就定义了一个基址寄存器,所以我们只需要使用一个基址寄存器就可以映射我们需要的地址了.所以,成功一次我们就可以结束循环了.
又一次,我们回到了usb_hcd_pci_probe中,126行,pci_set_master函数.还是看那张上坟图,注意到第三个寄存器,叫做Command
Reg.,其实就是命令寄存器.让我们用PCI spec来告诉你这个命令寄存器的格局: