相关资料
现在基本上所有设备都使用PCIE,但PCIE是PCI的拓展,所以基础的PCI知识仍然必要,这里重点强调两者共同部分。
参考资料:
- PCIe扫盲系列博文连载目录篇
- 深入PCI与PCIe之二:软件篇
- 《Linux 那些事之我是PCI》
- 《PCI+Express体系结构》
- 《Linux设备开发详解》 第11章
- 《PCI Express Base_r5_1》
- PCI-SIG
相关书籍下载方式:
链接: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配置导入到配置空间;
驱动软件是如何读取配置空间
// Linux中pci读写配置空间系列函数
#include <linux/pci.h>
inline int pci_read_config_byte(struct pci_dev *dev, int where, u8 *val);
inline int pci_read_config_word(struct pci_dev *dev, int where, u16 *val);
inline int pci_read_config_dword(struct pci_dev *dev, int where, u32 *val);
inline int pci_write_config_byte(struct pci_dev *dev, int where, u8 val);
inline int pci_write_config_word(struct pci_dev *dev, int where, u16 val);
inline int pci_write_config_dword(struct pci_dev *dev, int where, u32 val);
// 注:跟踪这部分代码,实际上调用的是对应总线的读写操作接口,对应的BDF寻址;
int pci_bus_read_config_byte(struct pci_bus *bus, unsigned int devfn,
int where, u8 *val);
int pci_bus_read_config_word(struct pci_bus *bus, unsigned int devfn,
int where, u16 *val);
int pci_bus_read_config_dword(struct pci_bus *bus, unsigned int devfn,
int where, u32 *val);
int pci_bus_write_config_byte(struct pci_bus *bus, unsigned int devfn,
int where, u8 val);
int pci_bus_write_config_word(struct pci_bus *bus, unsigned int devfn,
int where, u16 val);
int pci_bus_write_config_dword(struct pci_bus *bus, unsigned int devfn,
int where, u32 val);
// 实际调用IO端口进行访问,详情参考《PCI代码导读》
baiy@test:~$ sudo cat /proc/ioports | grep "PCI conf"
0cf8-0cff : PCI conf1
// arch/x86/direct.c中
#define PCI_CONF1_ADDRESS(bus, devfn, reg) \
(0x80000000 | ((reg & 0xF00) << 16) | (bus << 16) \
| (devfn << 8) | (reg & 0xFC))
outl(PCI_CONF1_ADDRESS(bus, devfn, reg), 0xCF8); // 配置地址
u32 value = inl(0xCFC); // 读取配置
Linux下通过lspci查看pci详细信息
baiy:workspace$ sudo lspci -s 00:02.0 -vvv
[sudo] password for baiy:
00:02.0 VGA compatible controller: Intel Corporation Device 3ea5 (rev 01) (prog-if 00 [VGA controller])
Subsystem: Intel Corporation Device 2074
Control: I/O+ Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B- DisINTx+
Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-
Latency: 0, Cache Line Size: 64 bytes
Interrupt: pin A routed to IRQ 132
Region 0: Memory at 90000000 (64-bit, non-prefetchable) [size=16M]
Region 2: Memory at 80000000 (64-bit, prefetchable) [size=256M]
baiy:workspace$ sudo lspci -s 00:02.0 -xxx
00:02.0 VGA compatible controller: Intel Corporation Device 3ea5 (rev 01)
00: 86 80 a5 3e 07 04 10 00 01 00 00 03 10 00 00 00
10: 04 00 00 90 00 00 00 00 0c 00 00 80 00 00 00 00
20: 01 30 00 00 00 00 00 00 00 00 00 00 86 80 74 20
30: 00 00 00 00 40 00 00 00 00 00 00 00 00 01 00 00
...
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 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空间详解
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用来表示设备需要占用的地址空间大小。
// 内核解析 bar 空间的函数
static inline unsigned long decode_bar(struct pci_dev *dev, u32 bar)
{
u32 mem_type;
unsigned long flags;
if ((bar & PCI_BASE_ADDRESS_SPACE) == PCI_BASE_ADDRESS_SPACE_IO) {
flags = bar & ~PCI_BASE_ADDRESS_IO_MASK;
flags |= IORESOURCE_IO;
return flags;
}
flags = bar & ~PCI_BASE_ADDRESS_MEM_MASK;
flags |= IORESOURCE_MEM;
if (flags & PCI_BASE_ADDRESS_MEM_PREFETCH)
flags |= IORESOURCE_PREFETCH;
mem_type = bar & PCI_BASE_ADDRESS_MEM_TYPE_MASK;
switch (mem_type) {
case PCI_BASE_ADDRESS_MEM_TYPE_32:
break;
case PCI_BASE_ADDRESS_MEM_TYPE_1M:
/* 1M mem BAR treated as 32-bit BAR */
break;
case PCI_BASE_ADDRESS_MEM_TYPE_64:
flags |= IORESOURCE_MEM_64;
break;
default:
/* mem unknown type treated as 32-bit BAR */
break;
}
return flags;
}
所以我们软件在开发时,需要读取下该配置空间的bit[2]是否为1。如果为1,那么当前设备使用64位地址空间,这也就是上边 lspci 看到的:
Region 0: Memory at 90000000 (64-bit, non-prefetchable) [size=16M]
Region 2: Memory at 80000000 (64-bit, prefetchable) [size=256M]
10: 04 00 00 90 00 00 00 00 0c 00 00 80 00 00 00 00
# 0x90000004,然后判断04的低4为是0b0100,那么使用64位 内存空间,且不支持预期
# 0x8000000c,然后判断0c的低4位时0b1100,那么使用64位 内存空间,且支持预期
32-bit Memory Address Space Request
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
IO Address Space Request
每一组BAR空间存放了:PCI设备映射的物理内存对应的PCI总线地址,资源信息,长度信息;
比如:00 00 00 90 ,则总线地址是90000000,flag是0x04、0,
如果获取长度信息?写入0xFFFFFFFF,然后读取即可获取;
pci_write_config_dword(struct pci_dev *dev, int where, 0xFFFFFFFF);
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寄存器
// bit[1:0] 在使能设备时打开,也可通过sysfs下enable直接使能
int pci_enable_device(struct pci_dev *dev)
{
return pci_enable_device_flags(dev, IORESOURCE_MEM | IORESOURCE_IO);
}
// bit[2] pci_set_master使用
static void __pci_set_master(struct pci_dev *dev, bool enable)
{
u16 old_cmd, cmd;
pci_read_config_word(dev, PCI_COMMAND, &old_cmd);
if (enable)
cmd = old_cmd | PCI_COMMAND_MASTER;
else
cmd = old_cmd & ~PCI_COMMAND_MASTER;
...
}
// bit[10] 使能中断
void pci_intx(struct pci_dev *pdev, int enable)
{
u16 pci_command, new;
pci_read_config_word(pdev, PCI_COMMAND, &pci_command);
if (enable)
new = pci_command & ~PCI_COMMAND_INTX_DISABLE;
else
new = pci_command | PCI_COMMAND_INTX_DISABLE;
pci_write_config_word(pdev, PCI_COMMAND, new);
}
EXPORT_SYMBOL_GPL(pci_intx);
status
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的算法
详情参考《PCI代码导读》