相关资料

现在基本上所有设备都使用PCIE,但PCIE是PCI的拓展,所以基础的PCI知识仍然必要,这里重点强调两者共同部分。

参考资料:

相关书籍下载方式:
链接:https://pan.baidu.com/s/1zzWWt9ujVTr9oJSaJNP_mA
提取码:ahax

测试工具

windows测试工具:

Linux测试工具

  • lspci

Inbound:PCI域訪问存储器域 Outbound:存储器域訪问PCI域

RC訪问EP: RC存储器域->outbound->RC PCI域->EP PCI域->inbound->EP存储器域 EP訪问RC:EP存储器域->outbound->EP PCI域->RC PCI域->inbound->RC存储器域

Out即出去,发起訪问的一側,须要进行outbound,去訪问对端 In即进来,被訪问的一側,须要进行inbound,使得对端能够訪问

EP訪问RC演示样例(蓝色箭头): (1)首先,EP须要配置outbound,RC须要inbound(一般RC端不用配),这样就建立了EP端0x20000000到RC端0x50000000的映射 (2)在RC端改动0x50000000的内容,EP端能够看到对应的变化。从EP端读/写0x20000000和从RC端读/写0x50000000,结果是一样的

RC訪问EP演示样例(黑色箭头): (1)首先,RC端须要配置outbound(一般内核中配好),EP端须要inbound(0x5b000000 inbound到BAR2),这样就建立了RC端0x20100000(BAR2)到EP端0x5b000000的映射 (2)在EP端改动0x5b000000内存的内容,在RC端0x20100000能够看到对应的变化,从RC端读/写0x20100000和从EP端读/写0x5b000000,结果是一样的

PCI配置空间(软件重点)

因为PCI配置空间0x00-0x3F是必须的有的,0x40-0xFF可选支持,所以这里重点介绍0x00-0x3F的信息

PCI Agent(Type0)配置空间

PCIE设备通常将配置信息存储在EEPROM中,上电后会将EEPROM配置导入到配置空间;
image.png

驱动软件是如何读取配置空间

  1. // Linux中pci读写配置空间系列函数
  2. #include <linux/pci.h>
  3. inline int pci_read_config_byte(struct pci_dev *dev, int where, u8 *val);
  4. inline int pci_read_config_word(struct pci_dev *dev, int where, u16 *val);
  5. inline int pci_read_config_dword(struct pci_dev *dev, int where, u32 *val);
  6. inline int pci_write_config_byte(struct pci_dev *dev, int where, u8 val);
  7. inline int pci_write_config_word(struct pci_dev *dev, int where, u16 val);
  8. inline int pci_write_config_dword(struct pci_dev *dev, int where, u32 val);
  9. // 注:跟踪这部分代码,实际上调用的是对应总线的读写操作接口,对应的BDF寻址;
  10. int pci_bus_read_config_byte(struct pci_bus *bus, unsigned int devfn,
  11. int where, u8 *val);
  12. int pci_bus_read_config_word(struct pci_bus *bus, unsigned int devfn,
  13. int where, u16 *val);
  14. int pci_bus_read_config_dword(struct pci_bus *bus, unsigned int devfn,
  15. int where, u32 *val);
  16. int pci_bus_write_config_byte(struct pci_bus *bus, unsigned int devfn,
  17. int where, u8 val);
  18. int pci_bus_write_config_word(struct pci_bus *bus, unsigned int devfn,
  19. int where, u16 val);
  20. int pci_bus_write_config_dword(struct pci_bus *bus, unsigned int devfn,
  21. int where, u32 val);
  22. // 实际调用IO端口进行访问,详情参考《PCI代码导读》
  23. baiy@test:~$ sudo cat /proc/ioports | grep "PCI conf"
  24. 0cf8-0cff : PCI conf1
  25. // arch/x86/direct.c中
  26. #define PCI_CONF1_ADDRESS(bus, devfn, reg) \
  27. (0x80000000 | ((reg & 0xF00) << 16) | (bus << 16) \
  28. | (devfn << 8) | (reg & 0xFC))
  29. outl(PCI_CONF1_ADDRESS(bus, devfn, reg), 0xCF8); // 配置地址
  30. u32 value = inl(0xCFC); // 读取配置

Linux下通过lspci查看pci详细信息

  1. baiy:workspace$ sudo lspci -s 00:02.0 -vvv
  2. [sudo] password for baiy:
  3. 00:02.0 VGA compatible controller: Intel Corporation Device 3ea5 (rev 01) (prog-if 00 [VGA controller])
  4. Subsystem: Intel Corporation Device 2074
  5. Control: I/O+ Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B- DisINTx+
  6. Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-
  7. Latency: 0, Cache Line Size: 64 bytes
  8. Interrupt: pin A routed to IRQ 132
  9. Region 0: Memory at 90000000 (64-bit, non-prefetchable) [size=16M]
  10. Region 2: Memory at 80000000 (64-bit, prefetchable) [size=256M]
  11. baiy:workspace$ sudo lspci -s 00:02.0 -xxx
  12. 00:02.0 VGA compatible controller: Intel Corporation Device 3ea5 (rev 01)
  13. 00: 86 80 a5 3e 07 04 10 00 01 00 00 03 10 00 00 00
  14. 10: 04 00 00 90 00 00 00 00 0c 00 00 80 00 00 00 00
  15. 20: 01 30 00 00 00 00 00 00 00 00 00 00 86 80 74 20
  16. 30: 00 00 00 00 40 00 00 00 00 00 00 00 00 01 00 00
  17. ...

ID相关(只读)

Device ID和Vendor ID: 没啥说的,厂家编号,系统软件只读,FPGA工程可配置。0xFFFF代表ID无效;
RevisionID:PCI设备的版本号;
SubsystemID和Subsystem VendorID:用户可在同一个FPGA上配置不同功能,使用这部分ID进行区分;

ClassCode 设备类型

ClassCode[23:0]由三部分组成:

  • Base ClassCode:设备分类
  • Sub ClassCode:设备类型细分
  • Interface ClassCode:编程接口

参考:PCI ClassCode表

PCI BAR0~5空间详解(重点)

基础部分:PCIe扫盲——基地址寄存器(BAR)详解
FPGA例化时,会将使用的BAR空间地址映射到真实的PCI设备的物理内存上
每一个设备最多有 6组32位bar空间/3组64位bar空间 +1组option Rom空间,用户可选用哪些空间可用;
注:需要特别注意的是,软件对BAR的检测与操作(Evaluating)必须是顺序执行的,即先BAR0,然后BAR1,……,直到BAR5。当软件检测到那些被硬件设置为全0的BAR,则认为这个BAR没有被使用。

如何判断PCI/PCIE使用了32位bar空间,还是64位bar空间
参考:PCI bar空间详解
image.png
bit0:表示设备寄存器是映射到memory(0)还是IO(1)空间。
bit1: reserved 0
bit2: 在base adress register for Memory 中0表示32位地址空间,1表示64位地址空间。
bit3:在memory BAR中用来表示该设备是否允许prefetch,1表示可以预取,0表示不可以预取。
其余的bit用来表示设备需要占用的地址空间大小。

  1. // 内核解析 bar 空间的函数
  2. static inline unsigned long decode_bar(struct pci_dev *dev, u32 bar)
  3. {
  4. u32 mem_type;
  5. unsigned long flags;
  6. if ((bar & PCI_BASE_ADDRESS_SPACE) == PCI_BASE_ADDRESS_SPACE_IO) {
  7. flags = bar & ~PCI_BASE_ADDRESS_IO_MASK;
  8. flags |= IORESOURCE_IO;
  9. return flags;
  10. }
  11. flags = bar & ~PCI_BASE_ADDRESS_MEM_MASK;
  12. flags |= IORESOURCE_MEM;
  13. if (flags & PCI_BASE_ADDRESS_MEM_PREFETCH)
  14. flags |= IORESOURCE_PREFETCH;
  15. mem_type = bar & PCI_BASE_ADDRESS_MEM_TYPE_MASK;
  16. switch (mem_type) {
  17. case PCI_BASE_ADDRESS_MEM_TYPE_32:
  18. break;
  19. case PCI_BASE_ADDRESS_MEM_TYPE_1M:
  20. /* 1M mem BAR treated as 32-bit BAR */
  21. break;
  22. case PCI_BASE_ADDRESS_MEM_TYPE_64:
  23. flags |= IORESOURCE_MEM_64;
  24. break;
  25. default:
  26. /* mem unknown type treated as 32-bit BAR */
  27. break;
  28. }
  29. return flags;
  30. }

所以我们软件在开发时,需要读取下该配置空间的bit[2]是否为1。如果为1,那么当前设备使用64位地址空间,这也就是上边 lspci 看到的:

  1. Region 0: Memory at 90000000 (64-bit, non-prefetchable) [size=16M]
  2. Region 2: Memory at 80000000 (64-bit, prefetchable) [size=256M]
  3. 10: 04 00 00 90 00 00 00 00 0c 00 00 80 00 00 00 00
  4. # 0x90000004,然后判断04的低4为是0b0100,那么使用64位 内存空间,且不支持预期
  5. # 0x8000000c,然后判断0c的低4位时0b1100,那么使用64位 内存空间,且支持预期

32-bit Memory Address Space Request
image.png
Step1:如图中(1)所示,未初始化的BAR的低比特(11~4)都是0,高比特(31~12)都是不确定的值。所谓初始化,就是系统(软件)向整个BAR都写1,来确定BAR的可操作的最低位是哪一位。当前可操作的最低位为12,因此当前BAR可申请的(最小)地址空间大小为4KB(2^12)。如果可操作的最低位为20,则该BAR可申请的(最小)地址空间大小为1MB(2^20)。

Step2:完成初始化(写1操作)之后,软件便开始读取BAR的值,来确定每一个BAR对应的地址空间大小和类型。其中操作的类型一般由最低四位所决定,具体如上图右侧部分所示。

Step3:最后一步是,软件向BAR的高比特写入地址空间的起始地址(Start Address)。如图中所示,为0xF9000000。

当然,这三部分由操作系统启动后执行深度优先扫描算法去检测的时候,来获取PCI的资源信息。

64-bit Memory Address Space Request
64MB P-MMIO地址空间的例子,由于采用的是64-bit的地址,因此需要两个BAR
image.png

IO Address Space Request
image.png

每一组BAR空间存放了:PCI设备映射的物理内存对应的PCI总线地址,资源信息,长度信息;
比如:00 00 00 90 ,则总线地址是90000000,flag是0x04、0,

如果获取长度信息?写入0xFFFFFFFF,然后读取即可获取;

  1. pci_write_config_dword(struct pci_dev *dev, int where, 0xFFFFFFFF);
  2. pci_read_config_dword(struct pci_dev *dev, int where, u32 *val);

注:64位需要相邻的两个BAR地址和长度进行拼接,详情参考 《PCI代码导读

系统软件在Linux系统启动后,有两个空间:PCI总线空间存储器域空间,且 只有映射到存储器域的空间 才可以被系统软件访问到。


混淆点
pci_read_dconfig() 获取到的是pci总线域的物理地址;
pci_reasource_start()获取到的是BAR空间在存储器域的物理地址;
ioremap(存储器域物理地址,SIZE)
所以系统软件常用:
void vaddr = ioremap(pci_resource_start(), SIZE); 获取虚拟地址后,像访问内存一样访问PCI存储域空间;*

OptionRom/Expansion Rom

在运行操作系统之前,有些设备有些 预初始化代码,只有在执行后才能使用;比如键盘鼠标,显卡等设备。OptionRom记录了PCIE设备存储这段ROM代码的地址;详情参考《PCIE OptionRom》;

capabilities pointer

PCI设备可选,PCIE设备必须,存访拓展功能信息;PCIE章节会详解;

Command寄存器

image.pngimage.png

  1. // bit[1:0] 在使能设备时打开,也可通过sysfs下enable直接使能
  2. int pci_enable_device(struct pci_dev *dev)
  3. {
  4. return pci_enable_device_flags(dev, IORESOURCE_MEM | IORESOURCE_IO);
  5. }
  6. // bit[2] pci_set_master使用
  7. static void __pci_set_master(struct pci_dev *dev, bool enable)
  8. {
  9. u16 old_cmd, cmd;
  10. pci_read_config_word(dev, PCI_COMMAND, &old_cmd);
  11. if (enable)
  12. cmd = old_cmd | PCI_COMMAND_MASTER;
  13. else
  14. cmd = old_cmd & ~PCI_COMMAND_MASTER;
  15. ...
  16. }
  17. // bit[10] 使能中断
  18. void pci_intx(struct pci_dev *pdev, int enable)
  19. {
  20. u16 pci_command, new;
  21. pci_read_config_word(pdev, PCI_COMMAND, &pci_command);
  22. if (enable)
  23. new = pci_command & ~PCI_COMMAND_INTX_DISABLE;
  24. else
  25. new = pci_command | PCI_COMMAND_INTX_DISABLE;
  26. pci_write_config_word(pdev, PCI_COMMAND, new);
  27. }
  28. EXPORT_SYMBOL_GPL(pci_intx);

status

image.png
image.png
image.png

BDF寻址和分配过程

基础-BDF与配置空间
基础-PCI和PCIE的扫描
PCIe总线中的每一个功能(Function)都有一个唯一的标识符与之对应。这个标识符就是BDF(Bus,Device,Function)
详情参考:《PCI+Express体系结构》 P57

BDF描述:16bit = BUS Number 8 bit + Device Number 5bit +Func Number + 3bit
每个PCIE设备支持1-8个功能(至少有一个),其中每个功能都有独立的配置空间。

由于一个pcie 最多有256 bus 最大32个device,每个device最多8个function。每个funciton 又占用4KB的空间,因此上电的时候需要为每个pcie准备256×32×8×4KB=256MB。这即使PCIe著名的256MB的算法

image.png
详情参考《PCI代码导读》