现在的位置: 首页 > 综合 > 正文

IO端口和IO内存

2013年07月05日 ⁄ 综合 ⁄ 共 6199字 ⁄ 字号 评论关闭

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_driverstruct
usb_hcd
这两个结构体中都有一个成员const char *product_desc,你说这不浪费吗?就一个破字符串,还得保存在两个地方,这些家伙九年制义务教育怎么学的?长此以往,国将不国矣!

至此,usb_create_hcd结束了,返回了这个申请好赋好值的hcd.我们继续回到probe函数中来.

89124这个if-else的确让我开心了一把,因为if里说的是EHCIOHCI的情况,else里针对的才是UHCI,鉴于EHCI将由某人来写,OHCIUHCI性质一样,我们不会讲,所以这就意味着这个if-else我只要从105行开始看,即直接看UHCI那部分的代码.!

不过我们也得知道这里为何要判断HCD_MEMORY,这个宏的意思是表明该HC的寄存器是使用memory,而没有设置这个flagHC的寄存器是使用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空间.LinuxI/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开始的sizenI/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

所以在我们的代码中我们看到循环条件就是从0PCI_ROM_RESOURCE之前,即循环六次,因为有六个区间,区间也叫region.那么这些寄存器究竟取的什么值呢?这就是在PCI总线初始化的时候做的事情了,它会把你每个基址寄存器赋上值,而实际上就是映射于总线上的地址,总线驱动的作用就是让各个设备都需要的地址资源都得到满足,并且没有设备与设备之间的地址发生冲突.PCI总线驱动做了这些之后,我们PCI设备驱动就简单了,在需要使用的时候直接请求即可,正如这里的request_region.那么我们传递给request_region的具体参数是什么呢?

两个函数,pci_resource_startpci_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,四个字节,这里正好对应UHCIIO空间基址寄存器.换言之,UHCI就定义了一个基址寄存器,所以我们只需要使用一个基址寄存器就可以映射我们需要的地址了.所以,成功一次我们就可以结束循环了.

又一次,我们回到了usb_hcd_pci_probe,126,pci_set_master函数.还是看那张上坟图,注意到第三个寄存器,叫做Command
Reg.,
其实就是命令寄存器.让我们用PCI spec来告诉你这个命令寄存器的格局:

抱歉!评论已关闭.