Linux那些事儿之我是UHCI(4)IO内存和IO端口

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 MemoryI/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来告诉你这个命令寄存器的格局:

看到其中有一位叫做Bus Master了么?没错,就是那个Bit 2.用毛德操先生的话说就是PCI设备要进行DMA操作就得具有竞争成为总线主的能力.而这个Bus Master位就是用来打开或关闭PCI设备竞争成为总线主的能力的.在完成PCI总线的初始化时,所有PCI设备的DMA功能都是关闭的,所以这里要调用pci_set_master启用USB主机控制器竞争成为总线主的能力.就是说PCI设备有没有这么一种能力是可以设置的.

USB spec 2.010.2.9节在讲到USB Host Interface的时候,说了USB HC是应该具备这种能力的:

The Host Controller provides a high-speed bus-mastering interface to and from main system memory. The physical transfer between memory and the USB wire is performed automatically by the Host Controller.

同时在UHCI spec里面我们也能找到这么一句话,For the implementation example in this document, the Host Controller is a PCI device. PCI Bus master capability in the Host Controller permits high performance data transfers to system memory.

所以我们需要启用这种能力.

不过你要问了,究竟什么是PCIBus Master?从我们读电子的人的角度来看,连接到PCI总线上的设备有两种,即主控设备和目标设备.或者说一个是master设备,一个是target-only设备,用我们专业的术语来看,这两者最直接的区别就是target-only最少需要47pin,master最少需要49pin,就是说它们所必须支持的总线信号就是不一样的.PCI设备如果是以一个target-only的方式工作,那么它就完全是在主机的CPU的控制之下工作,比如设备接收到某一个外部事件,然后中断主机,然后主机CPU读写设备,这样设备就可以工作了.这样的设备也被一小撮人称为slave设备,或者说从设备.这就是典型的奴才型的设备,主说什么就是什么,完全没有自己的见解.master类型的设备就比这个要复杂了,master设备能够不在主机CPU的干预下访问主机的地址空间,包括主存和其它PCI设备,很显然,DMA就属于这种情况,即不需要主机CPU干涉的情况下USB主机控制器通过DMA直接读写内存.所以我们需要打开这种能力.不过PCI总线在同一时刻只能供一对设备完成传输.至于有了竞争力能不能竞争得到Master,那就得看人品了.上天给了你一幅天使的面孔和魔鬼的身材,但你能不能成为明星就得看造化了,当然只要你遵守圈中的潜规则,你离成功就不远了. 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值