天天看点

uboot中PCIe驱动程序说明

针对之前分析uboot中PCIe部分的代码,这里进行简要的PCIE体会说明,回头再整理一下格式 1.     概论

这边的说明只针对Uboot中P1020开发板的PCIE部分。简要说明功能和注意问题。

2.     PCIe相关基本概念

Host主桥:HOST 主桥与主存储器控制器在同一级总线上,其主要功能是隔离处理器系统的存储器域与处理器系统的PCI总线域。PCI 设备可以方便地通过 HOST 主桥访问主存储器。一个CPU可以有多个HOST主桥,一个主桥代表一个PCI总线域。

通过PCI桥(bridge)可以划分多个PCI总线域。而PCI设备(EP)则是挂载在各个PCI总线域的终结。

如下图:

uboot中PCIe驱动程序说明

PCI总线域:如同上图中列出的PCI域x,从上向下按树形一一级分。每个域可以通过bus号进行区分。

存储器域:对应PCI总线域,存储器域就是处理器系统的地址空间。

映射空间:由于隔离就存在将存储器域和PCI总线域映射的需要。在PPC中就采用Outbound和Inbound两个寄存器,负责保存这两个域之间的映射关系。

Outbound:将存储器域的地址转换为 PCI。用于host处理器对pci总线上设备进行读写。就是配置outbound映射关系后,host处理器就可以访问PCI总线设备。

Inbound:将 PCI 总线域的地址转换为存储器域的地址。用于PCI总线上的设备对host处理器的读写访问。要pci设备能访问host处理器的某段存储器空间,则需要配置inbound寄存器。

配置空间:按照PCI标准,任何PCI设备不管是桥还是Ep,都有一套标准的配置空间,配置空间中包含许多寄存器,这些是用来存放ID、指令、基地址等。其中桥和设备的配置空间结构不一样,需要区别对待。

访问配置空间:访问配置空间的方法每种HOST主桥会不一样。而P1020中的访问方式是通过往寄存器PEXx_PEX_CONFIG_ADDR写入要访问配置空间寄存器的偏移量,然后再对PEXx_PEX_CONFIG_DATA寄存器进行读写操作,来完成对配置空间中指定寄存器的读写操作。

PCIe总线则是串行总线,只是按照PCI标准指定的串行总线,所以它建立的是一对一的连接,也就是如果要负载一个设备,就需要host主桥和设备通过一个串行总线连接,根据负载能力可以分为X1、X2或X4等等。P1020中一个host最多连4个,也就是X4,但是这个跟硬件设计分配有关,首先就需要设置p1020中的serdes,分配lane通道来提供串行连接。

LAW:是p1020的本地访问窗口,用于做本地地址映射。在配置PCI总线时需要分配一段映射空间供给PCI设备的读写访问。

3.     说明

3.1.     主要文件说明

PCI的主文件:

fsl_pci_init.c文件是飞思卡尔PCI控制器的主要配置文件,PCI的初始化函数等就在该函数中。

pci.c文件是PCI配置的公用文件,用于所有PCI驱动,主要包括PCI设备的扫描、设备查找等。

pci_auto.c文件主要用于PCI自动扫描和PCI设备配置。包括飞思卡尔PCI控制扫描前和扫描后的配置函数等。

pci_indirect.c文件存放对PCI设备的配置空间中寄存器进行读写操作的函数。

cmd_pci.c文件是存放pci查看指令函数的文件,用户通过shell输入相关指令来查看PCIe设备信息。

PCI配置相关文件:

fsl_law.c文件用来设置LAW的文件,在PCIe配置时需要设置LAW,之前要进行law初始化,查看law的使用情况进行相关标志编号等。

p1021_serdes.c文件用来设置serde的文件,对相应的PCIe设置lane通道,在P1020开发板中对PCIe串口只提供了一个lane通道。

PCI头文件:

主要定义相关的宏定义、数据类型定义、大小端IO访问的函数等。

移植时用到的代码结构图

\PCIE

│  cmd_pci.c

│  command.h

│  e500.h

│  fsl_law.c

│  fsl_law.h

│  fsl_pci_init.c

│  law.c

│  mpc85xx.h

│  p1021_serdes.c

│  p1020rdb.h

│  pci.c

│  pci.h

│  pci_auto.c

│  pci_ids.h

│  pci_indirect.c

│ 

├─asm

│      bitops.h

│      byteorder.h

│      config.h

│      config_mpc85xx.h

│      fsl_law.h

│      fsl_pci.h

│      fsl_serdes.h

│      immap_85xx.h

│      io.h

│      processor.h

│      types.h

│ 

└─linux

        compiler-gcc.h

        compiler-gcc4.h

        compiler.h

        config.h

3.2.     结构体说明

PCIE主桥控制器的配置结构体如下:

struct pci_controller {

     struct pci_controller *next;

     int first_busno;

     int last_busno;

。。。。。

     int indirect_type;

     struct pci_region regions[MAX_PCI_REGIONS];

     int region_count;

     struct pci_config_table *config_table;

     void (*fixup_irq)(struct pci_controller *, pci_dev_t);

     。。。。。。

     struct pci_region *pci_mem, *pci_io, *pci_prefetch;

。。。。。。};

first_busno和last_busno分别存储在该host主桥的bus号和所挂最下一级设备的bus号。pci_controller结构体中的struct pci_region regions用来存放IO空间、存储空间、预读空间等,在outbound和inbound 的配置时有用到。region_count指示配置的region个数。

而*pci_mem, *pci_io, *pci_prefetch结构体指针则是在PCI设备扫描时为了根据outbound自动配置PCI设备的BAR地址而定义的。在pciauto_config_init函数中会过去之前regions来设置各个特定区域的参数。

config_table在P1020中没有用到。其他不太重要的参数用省略号略过。

struct pci_region结构体如下:

struct pci_region {

     pci_addr_t bus_start;    

     phys_addr_t phys_start;    

     pci_size_t size;    

     unsigned long flags;    

     pci_addr_t bus_lower;};

因为在一个处理器系统中,并不是所有存储器空间都可以被 PCI 设备访问,只有在 PCI总线域中有映像的存储器空间才能被 PCI 设备访问。该结构体就是用来配置映射空间的。

bus_start是指CPU存储域的起始地址,phys_start是指PCI设备物理空间的起始地址。Size是指映射的空间大小。flags是用来指示映射空间的功能,是用于设备IO、MEM还是预读。bus_lower是在自动扫描配置时会用到,初始配置时等于bus_start,如果扫描设备用到了某空间,比如IO空间或MEM空间,则bus_lower=bus_start+used_size,used_size是扫描所有设备使用该空间的总大小。

3.3.     主程序流程图说明

uboot中PCIe驱动程序说明

Uboot中主流程如上图,下面将按照几个大模块分别进行说明。

3.4.     主程序详细说明

3.4.1.     PCIe初始化前奏

因为配置PCIe牵涉到LAW配置等,所以在初始化PCIe之前需要对LAW相关进行初始化。

serdes部分:执行fsl_serdes_init ()函数来初始化。其实serdes的配置主要跟硬件连接有关,看serdes的4个lane都分给了谁,根据I/O port selection中的设置,将serdes的分配结果映射到serdes1_prtcl_map变量中。这样在PCIe配置之前先判断serdes有没有分配一个lane给PCIe控制器,如果没有,就没有做后面PCIe配置的必要了。然后根据I/O port selection中的设置,对相应的serdes进行默认配置。

LAW部分:执行init_laws()函数来初始化,这里主要是查看12个LAW的使能情况,对已经使能的LAW寄存器在used_laws变量中进行标记。然后按照law.c文件中的law table对需要配置的相关law寄存器进行配置。

在执行PCIe配置前一定要先执行这两项的初始化。

3.4.2.     PCIe控制器的配置

3.4.2.1.     初始配置和bus号分配

P1020有两个独立的host主桥控制器,分管两个PCI域。在配置之前先判断serdes是否使能了PCIe,使能了才能继续。同时将系统提供的MEM基地址和空间大小,以及IO基地址和空间大小,放置到相应fsl_pci_info结构体中,用于配置对应PCIe控制器的LAW寄存器。

这边要注意的是对P1020中所有PCI的总线号的分配,这边对bus号分配是按从0开始顺序分配的(其实可以按照任意号开始,这边默认从0开始)。

比如P1020中包含pcie1和pcie2,其实属于两个host主桥(host-to-pci桥),相当于两个独立的域,而uboot中的pcie是从0开始编号的。如果按照连续配置两个PCIE1和PCIE2两个桥的时候,扫描后bus编号如下:

uboot中PCIe驱动程序说明

这边只考虑每个host控制器配置X1的情况,好像Uboot中只支持单分支的扫描,实际在X1中扫描的结果也和上图类似。

对于Host主桥通过pci_controller结构体保存信息,在结构体中的first_busno记录RC的bus号。

由于P1020的PCIe控制器可以配置成RC或EP(这种配置是由host/agent引脚配置信号LWE_B[1]/LA[18:19]确定哪种模式,说白了就是硬件决定是什么模式),所以在UBoot的驱动中调用fsl_is_pci_agent函数判断该控制器被配置成那种类型。在该函数中通过判断Cap ID寄存器来判断是否是PCIe接口,然后再读相关寄存器看是设置为何种模式。这里Cap ID寄存器在配置空间的0x4C位置,P1020中PCIE控制器有两个capabilities寄存器,先cap point指向0x44,然后链表指向0x4C。

3.4.2.2.     outbound和inbound配置

在PCIe控制器中的初始化主要在fsl_pci_init函数中。首先是outbound和inbound的配置。Outbound的地址转换寄存器配置时,由于POWBAR0 寄存器作为缺省窗口,所以从POWBAR1寄存器开始配置,然而Inbound的地址转换寄存器配置时,则是从PIWBAR2或PIWBAR3倒序配置的。

而在uboot中对地址、空间大小等配置都是先放置到pci_region结构体中,然后由pci_region设置到对应寄存器中。在开发板中测试得到一般的pci_region配置如下图:

uboot中PCIe驱动程序说明

注意PCSRBAR设置中,配置1M空间,做了防止outbound和inbound重合的操作。包括在fsl_pci_setup_inbound_windows中设置inbound也是这样,虽然pcie本身可以支持outbound和inbound重合,不过该程序是针对pci/pcie,所以做了防止inbound重合的措施。

Uboot中pci_region结构体里,phy_start和bus_start一般设成一样,作为一一映射。在inbound配置中,这两个地址都默认为0。

3.4.2.3.     查看链路状态和扫描前配置空间设置

配置完outbound和inbound后,就是清除error寄存器然后使能错误中断异常寄存器。

如果定义CONFIG_FSL_PCIE_DISABLE_ASPM宏,则设置寄存器关闭ASPM(激活状态电源管理)功能。

之后查看link传输状态机观察设备连接的状态,只有在L0状态以上才表示有正常连接,如果有设备连接则继续,如果没有则返回,完成初始化配置。同时初始化时如果检测link状态机在active状态0,表示errata,需要reset(不过在P1020中这个pdb_stat软件复位没有该寄存器,所以段reset代码无效)。

有设备连接的话,还将对PCIe控制器进行prescan配置(调用pciauto_prescan_setup_bridge函数),对HOST主桥的配置空间进行设置,主要包括PCI_COMMAND,主总线、二级总线、下级总线数目等,以及RC主桥的IO、MEM等基地址。

3.4.2.4.     PCIe设备扫描和自动配置

初始化时可以通过定义CONFIG_PCI_NOSCAN宏,不进行PCI扫描和自动配置。

这里默认使用初始化时扫描。

在fsl_pci_init函数中调用pci_hose_scan_bus,首先从HOST主桥的下一级bus号开始扫描。如果是桥,则配置完cmd、BAR0~1等参数后,再递归调用pci_hose_scan_bus,继续向下扫描。Busno加1。通过判断vendorID和HeaderType来判断是否有设备挂载。

PCIe下级设备是从主总线一级级向下扫,一级级增加bus号。参考上节的图。

如果添加或删除设备后重新扫描busno可能变化。热插拔后需要重新调用pci_hose_scan(struct pci_controller *hose)函数重新扫描分配BAR地址。

从代码可以看出,uboot中pcie的自动扫描只能进行单分支扫描,就是针对X1的情况,若X2或X4之类的情况,则不行。uboot中的pci_hose_scan用嵌套扫描,如果是桥再在桥下进行扫描。同一桥下的设备通过device号进行区分。

扫描后再调用pciauto_postscan_setup_bridge函数进行配置,这里主要是根据挂接的PCIe设备得到使用的地址空间大小并最大bus号。IO、MEM当前使用的高位地址放置在对应pci_region的bus_lower中。

3.5.     结束及测试

扫描配置完后,重新清除error寄存器位。再设置配置ready等寄存器之后,整个PCIe初始化基本就完成了。

这时候可以通过pciinfo指令查看总线号下的设备配置参数等,还可以获得对应设备的BAR,对该地址进行读写。

在测试时可以通过CPU对设备映射的地址进行读写操作,在FPGA开发板的PCIE设备中通过CPU反复读写测试,发现正常。不过对于PCI设备访问CPU的测试没能进行。

pci