Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1597128
  • 博文数量: 92
  • 博客积分: 2002
  • 博客等级: 大尉
  • 技术积分: 4717
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-01 17:09
文章分类

全部博文(92)

文章存档

2013年(1)

2012年(6)

2011年(85)

分类: LINUX

2011-01-25 12:18:01

1.PCI设备编号

    每一个PCI device都有其unique PFA(PCI Fcntion Address)
    PFA由 bus number、device number、function number组成。
   
    一条PCI总线支持256个PFA,即支持256个PCI device。
   
    每个PCI芯片都有自己的device number(取决于IDSEL管脚),每个PCI芯片占用8个PFA。
    每个PCI芯片的第一个PCI device的PFA必为8的倍数。
    若PCI device的配置空间中PCI_HEADER_TYPE寄存器的最高bit为1,说明此芯片还有其他PFA,即还有其他device,即当前芯片是 multi-function device.
    Each function on a multi-function device has its own configuration space。
 
   在系统中,每个PCI芯片上的所独有的信号线是:INTA、INTB、INTC、INTD、IDSEL
   每个芯片上的IDSEL需要连到PCI总线中AD[31:11]中的一根,这对应于PCI device PFA的device number

   IDSEL is equivalent to chip select on the CPU side during address phase of  CFG RD\WR command.
   DEVSEL is used in every transaction when the target claims the cycle address to it. It is like saying this cycle in mine, and nobody touches it!

2.PCI配置空间

    每个PCI逻辑设备都有自己的配置空间,里面存储了一些基本信息,生产商,IRQ中断号,还有就是定义了mem空间和io空间的起始地址和大小。



CPU通过两个寄存器访问PCI设备的配置空间:CFG_ADDRCFG_DATA




  对CFG_DATA的操作就是对配置空间相应寄存器的操作,没有专门的bit来制定操作类型r/w。

   PCI配置空间中的BAR(Base Address Register)用来映射PCI设备的寄存器,里面的值是bus地址首地址,至于空间的大小,先向bar中写0xFFFFFFFFF,然后读取,选最低的一位非0的,比如为0x1000,那个空间的大小就为0x1000。
   这里需要注意,当PCI配置成64bit或32bit时,BAR有区别
 
   
3.PCI地址转换

   当CPU访问PCI设备的mem空间和io空间的寄存器时,需要进行地址转换。
MPC8560有5个outbound窗口0~5用来将CPU内部地址转换为PCI总线地址。上电时只有窗口0使能的,其他都是disable状态。每个outbound窗口有如下三类寄存器:
    1.外部PCI总线地址的基址
    2.CPU内部32bit地址的基址(EA)   
    3.窗口属性寄存器,大小、转换类型等等

  outbound窗口转换后(外部PCI总线地址的基址,当使用64bit PCI时会用到第二个扩展寄存器) 

 
outbound窗口转换前地址(CPU内部32bit地址的基址)


outbound窗口属性寄存器



EN:设置此窗口是否使能
RTT/WTT:分别设置此窗口的存取方式(memory或io)
OWS:设置此窗口大小


    同理,当PCI设备访问MPC8560时,有3个inbound窗口1~3用来将PCI总线地址转换为CPU内部地址。和outbound窗口一样,inbound窗口有如下三类寄存器:
    CPU内部32bit地址的基址(EA)   
    外部PCI总线地址的基址
    窗口属性寄存器,大小、转换类型等等



EN:设置此窗口是否使能
PF:设置此窗口是否开启prefetchable特性
TGI:Target Interface,见datasheet P883
RTT/WTT:设置PCI外设访问CPU时的存取方式(snoop L2cache 等等)
IWA:设置此窗口的大小


3.PCI拓扑结构

PCI拓扑结构,有如下例子



    在上图的总线结构中,ethernet设备和pci-pci bridge的资源空间必须要是pci bus0的一个子集
    同理,SCSI和VIDEO同类型资源必须要是pci_bus1的子集。

    CPU访问PCI的过程是这样的(只有一个根总线和pci-pci bridge过滤窗口功能打开的情况):

    1.cpu向pci发出一个I/O请求。
    首先经过根总线.它会判断是否在它的资源范围内.如果在它的范围,它就会丢向总线所在的每一个设备.包括pci bridge. 如果没有在根总线的资源范围,则不会处理这个请求.
    2.如果pci设备判断该地址属于它的资源范围,则处理后发出应答
    3.pci bridge接收到这个请求,它会判断I/O地址是否在它的资源范围内.如果在它的范围,它会把它丢到它的下层子线.
    4.下层总线经过经过相同的处理后,就会找到这个PCI设备了

    一个PCI设备访问其它PCI设备或者其它外设的过程:

    1.首先这个PCI发出一个请求,这个请求会在总线上广播
    2.如果要请求的设备是在同级总线,就会产生应答
    3.请求的设备不是在同层总线,就会进行pci bridge.pci桥判断该请求不在它的范围内(目的地不是它下层的设备),就会将它丢向上层.
    4.这样辗转之后,就能找到对应的设备了


3.PCI枚举过程


通过PCI枚举,CPU知道当前系统上有多少PCI设备,多少根PCI总线,PCI配置空间初始化。

    PCI 总线扫描的原理是从总线 0 扫描到总线 255,对于每条总线,系统都会扫描所有(总线号,设备号,功能号),读出每个设备配置空间的Device ID和Vendor ID寄存器,如果这两个寄存器的值是个无效值(0xFFFF),则说明当前位置上没有设备,接着扫描下一个位置。
    如果是有效值(非0xFFFF),当前位置是个有效的 PCI 设备/桥。进而再读取该设备的 Header Type 寄存器,如果该寄存器为 1,则表示当前设备是 PCI 桥,否则是 PCI 设备。

Register Number:配置空间寄存器偏移量
Function Number:多功能设备有多个功能号
Device Number:设备编号
Bus Number:总线编号




对所有 PCI 总线进行编号
    PCI 桥如何知道它所连接的 PCI 总线情况呢?这就需要对 PCI 桥进行总线编号。前面介绍过 PCI 桥提供了 Primary Bus Number、Secondary Bus Number 和 Subordinate Bus Number 三个寄存器用于标志该桥所连接的 PCI 总线,下面通过一个示例来说明内核对于 PCI 总线是如何进行编号的。



1.系统运行初始,Bus A 为 0,通过上面的 PCI 总线扫描得到连接在 Bus A 上的 PCI 桥(即图中Bridge 1)
2.下面开始设置 Bridge 1 的 Bus 寄存器。将 Primary Bus Number 寄存器设置成 Bus A 的编号,即 0。将 Secondary Bus Number 寄存器设置成 Bus B 的编号,它的值等于(Bus A + 1),也就是 1。由于暂时无法知道该桥所能访问的所有下行总线数目,Subordinate Bus Number 寄存器暂时设置成 0xFF。
3.当扫描完所有 Bus A 上所有(设备号,功能号)后,开始扫描 Bus B,Bus B 的编号在扫描完 Bus A 后已经得到,为 1。Bus B 的扫描方法同步骤(1),先扫描出 Bus B 上的 PCI 桥(即图中的 Bridge 2),然后配置 Primary Bus Number 寄存器为 1,Secondary Bus Number 寄存器为 2,而 Subordinate Bus Number 寄存器依然为 0xFF。
4.Bus B 扫描完后得到 Bus C 的编号,为2。下面开始扫描 Bus C,因为 Bus C 上没有 PCI 桥,于是在扫描完其它(设备号,功能号)后,Bus C 的扫描结束。
5.由于 Bridge 2 所能访问到的最大 Bus 编号是 2,因此重新设置 Bridge 2 的 Subordinate Bus Number 寄存器为 2。
6.由于 Bridge 1 所能访问到的最大 Bus 编号也是 2,因此重新设置 Bridge 1 的 Subordinate Bus Number 寄存器为 2。
7.总线编号结束。

3.Linux内核PCI数据结构


内核(linux-2.6.24) 提供了三类数据结构用以描述 PCI 控制器、PCI 设备以及 PCI 总线。
数据结构关系如下所示


PCI 控制器 用 pci_controller 结构来描述,它有以下几个主要的属性:
index:该属性标志 PCI 控制器的编号。
next:该属性指向下一个 PCI 控制器,通过 next 属性,PCI 控制器可以形成一个单向链表。
first_busno:该属性标志了连接在该控制器上第一条总线的编号。
last_busno:该属性标志了连接在该控制器上最后一条总线的编号。
ops:该属性标志了当前 PCI 控制器所对应的 PCI 配制空间读写操作函数。
io_space:该属性标志了当前 PCI 控制器所支持的 IO 地址空间。
mem_space:该属性标志了当前 PCI 控制器所支持的 Memory 地址区间。
cfg_addr:该属性标志了当前 PCI 控制器发起 Configuration 访问方式时所需要写入的地址空间。
cfg_data:该属性标志了当前 PCI 控制器发起 Configuration 访问方式时所需要读写的数据空间。
bus:该属性标志了当前 PCI 控制器所连接的 PCI 总线,它对应的数据结构是 pci_bus。

PCI 总线 用 pci_bus 结构来描述,它有以下几个主要的属性:
parent:可通过该属性索引到上层 PCI 总线。
self:该属性标志了连接的上行 PCI 桥(对应的数据结构是 pci_dev)。
children:该属性标志了总线连接的所有 PCI 子总线链表。
devices:该属性标志了总线连接的所有 PCI 设备链表。
ops:该属性标志了总线上所有 PCI 设备的配制空间读写操作函数。
number:该属性标志了当前 PCI 总线的编号。
primary:该属性标志了 PCI 上行总线编号。
secondary:该属性标志了 PCI 下行总线编号。
subordinate:该属性标志了能够访问到的最大总线编号。
resource:该属性标志了 Memory/IO 地址空间。

PCI 设备 用 pci_dev 结构来描述,它有以下几个主要的属性:
global_list:Linux 定义了一个全局列表来索引所有PCI 设备,该属性标志了这个全局列表的首指针。
bus:该属性标志了当前设备所在的 PCI 总线(对应的数据结构是 pci_bus)。
devfn:该属性标志了设备编号和功能编号。
vendor:该属性标志了供应商编号。
device:该属性标志了设备编号。
driver:该属性标志了设备对应的驱动代码(对应的数据结构是 pci_driver)。
irq:该属性标志了中断号。
resource:该属性标志了 Memory/IO 地址区间。


当 Linux 内核在做 PCI 初始化工作时,它会根据建立一个由 pci_controller、pci_bus 和 pci_dev 三者组成的一个组织结构图。根据这个结构,软件开发者可以很方便的通过 PCI 控制器索引到每个 PCI 设备或者 PCI 总线。


4.Linux的PCI子系统初始化流程



第一步:Linux分配数据结构pci_contoller,并初始化,包括PCI的mem/io空间范围和访问PCI配置空间所需的handler。
第二步:PCI设备的枚举:扫描系统上所有PCI设备,初始化它们的配置空间。(硬件上的初始化)
第三步:用数据结构将PCI设备信息联系起来,构建PCI树。(软件上的初始化)
第四步:加载PCI设备驱动。


4.1 初始化PCI控制器

pci_controller结构是内核描述PCI子系统信息的数据结构,里面定义了可供PCI设备使用的mem资源和io资源的范围,访问pci设备配置空间的handler等等。
函数调用关系:
start_kernel --> mpc8560ads_setup_arch --> mpc85xx_setup_hose()
此函数分配并初始化了pci_controller

mpc85xx_setup_hose()
    -->pcibios_alloc_controller()                 //分配数据结构pci_controller
    ppc_md.pci_map_irq = mpc85xx_map_irq;    //用来获得pci设备irq号的handler
    -->setup_indirect_pci() //设置pci_controller里访问PCI配置空间的钩子函数
                            //使用ioremap后的CFG_ADDR和CFG_DATA的虚拟地址
    -->mpc85xx_setup_pci1() //设置PCI inbound和outbound窗口寄存器。


PCI子系统的mem和io地址空间范围也在函数mpc85xx_setup_hose()中定义:
    pci_controller *hose_a;
    hose_a->mem_space.start = MPC85XX_PCI1_LOWER_MEM;
    hose_a->mem_space.end = MPC85XX_PCI1_UPPER_MEM;
    hose_a->io_space.start = MPC85XX_PCI1_LOWER_IO;
    hose_a->io_space.end = MPC85XX_PCI1_UPPER_IO;


4.2 PCI枚举过程

目前为止,内核只知道PCI子系统总的mem资源和io资源地址范围。
接下来,内核需要扫描所有PCI设备,把这些资源分配给PCI设备,具体方法参考前面讲的PCI枚举过程。

内核分配mem资源时是从高地址开始分配的。

 mpc85xx_setup_hose()
    -->pciauto_bus_scan()
    for (pci_devfn = 0; pci_devfn < 0xff; pci_devfn++) {    //遍历0号总线上所有PCI设备
        if (读当前设备配置空间PCI_HEADER_TYPE失败)
            continue;                        //当前设备不存在,扫描下一个
        读当前设备配置空间PCI_CLASS_REVISION
        If(当前设备是PCI桥){
            pciauto_setup_bars()            //分配pci_controller的资源的子集
            pciauto_prescan_setup_bridge()      //写PCI桥配置空间 PCI_PRIMARY_BUS
                                                //PCI_SECONDARY_BUS
                                                //PCI_SUBORDINATE_BUS
            pciauto_bus_scan()                        //递归扫描下一条pci bus
            pciauto_postscan_setup_bridge()            //写PCI桥配置空间PCI_SUBORDINATE_BUS
        }
        。。。。。略去
        else {
            //当前设备是PCI设备
            pciauto_setup_bars()
        }
    }

注意:执行完 pciauto_bus_scan后,所有pci设备(包括桥)的配置空间都已经傻瓜式的简单初始化
      但这些桥和设备还没有通过数据结构组织起来,这些工作要在第四步 pcibios_init里来完成


4.3 创建PCI树

上面说了,PCI设备配置空间都初始化差不多了,但是PCI设备还没有通过数据结构组织起来。   

do_initcalls()
    -->pcibios_init()    //所在文件 arch/ppc/kernel/pci.c

pcibios_init()   
    (1):为PCI设备构造数据结构,组织成PCI树
    -->pci_scan_bus(hose->first_busno, hose->ops, hose)    //hose就是上面的pci_controller结构
        -->pci_scan_bus_parented
            -->pci_create_bus        // 建立 PCI bus 0 对应的数据结构,这个bus的资源尚未初始化
            -->pci_scan_child_bus    // 从PCI bus 0 开始扫描生成PCI树,使用了递归
                -->pci_scan_slot
                    -->pci_scan_single_dev
                        -->pci_scan_device()    //创建 pci_dev结构
                            -->pci_setup_device()    //区分桥与设备,分别进行初始化
                                -->pci_read_bases();    //在这才初始化了pci_dev->resource[]

pci_dev->resouce[]中保存的才是cpu internal address(EA),可以对这些地址进行用ioremap,在驱动程序对bar所指位置读写的时候一定要用这个

                           
    (2)给PCI设备分配IRQ号
    -->pci_fixup_irqs()
        //遍历所有pci设备,调用pdev_fixup_irq()
        -->pdev_fixup_irq
            -->ppc_md.pci_swizzle()     //实际调用common_swizzle(),获得pci所在slot编号
            读PCI设备配置空间PCI_INTERRUPT_PIN,获得中断pin编号[1到4]
            之所以是1到4,因为PCI规范里设备最多4个管脚(除第一个外,其他3个仅用于多功能设备)
            -->ppc_md.pci_map_irq() //实际调用mpc85xx_map_irq()
                                    //根据slot,pin 和 pci_irq_table[][4]
                                    //来计算出irq号
            -->pcibios_update_irq(pci_dev,irq)
              //将irq号写入pci设备的配置空间PCI_INTERRUPT_LINE
              //注意,这里寄存器只是用来保存结果,例如把其值8改为9,并不能改变中断号
    (3)PCI结构树有了,现在构建PCI的资源树,有冲突就修改
    -->pcibios_allocate_bus_resource()
        //只考虑pci_bus,形成bus级资源树(并同时check,资源冲突了就修改)
    -->pcibios_allocate_resources()
        //把pci_dev也考虑进去,完成资源树


4.3 加载PCI设备驱动

以e1000 PCI 网卡 82546GB为例,讲解一个PCI驱动的加载过程
驱动里有如下代码:
static struct pci_driver e1000_driver = {
    .name     = e1000_driver_name,
    .id_table = e1000_pci_tbl,
    .probe    = e1000_probe,
    .remove   = __devexit_p(e1000_remove),
#ifdef CONFIG_PM
    /* Power Managment Hooks */
    .suspend  = e1000_suspend,
    .resume   = e1000_resume,
#endif
    .shutdown = e1000_shutdown,
    .err_handler = &e1000_err_handler
};

module_init(e1000_init_module);



do_initcalls()
    -->e1000_init_module
        pci_register_driver(&e1000_driver)
        // e1000_driver.driver.bus = &pci_bus_type;
    -->driver_register(e1000_driver.driver);
    //pci_bus_type.probe() 非空,即调用pci_device_probe()

pci_device_probe(*device)
    -->__pci_device_probe(*pci_driver,*pci_dev)
        -->pci_match_device(*pci_driver,*pci_dev)
        -->pci_call_probe(*pci_driver,*pci_dev,pci_device_id)
            -->drv->probe(*pci_device,*pci_device_id)
            即执行 e1000_probe(*pci_device,*pci_device_id)

接下来就是e1000 PCI 网卡驱动的具体代码。

阅读(31501) | 评论(0) | 转发(11) |
给主人留下些什么吧!~~