配置空间访问专栏

参考:《PCI Express Base_r5_1》 7.2 章节
**

Intel关于配置空间访问描述

在X86架构上有关于这部分的描述:
10th Generation Intel® Core™ Processors, Datasheet Volume 1 of 2 中 P29页描述:
image.png
PCI Express *将配置空间扩展到每个设备/功能4K字节。

PCI Express 配置空间分为 **一个PCI兼容区域(就是前256个字节组成)和 一个扩展的PCI Express 区域(就是 0x100-0xFFF)**。

PCI前256字节配置空间:可以通过 PCI规范中定义的机制(就是 通过 0cf8-0cff : PCI conf1 两个ioport通过BDF来寻址访问 )使用PCI Express 增强配置机制(ECAM-* PCI Express Enhanced Configuration Access Mechanism**)访问机制来访问PCI兼容区域

PCI的0x100-0xFFF的ECAM访问,使用ioremap去访问PCI Express区域,这个属于硬件支持,基地址从ACPI来获取到

PCI Express 主机桥,将内存映射的PCI Express 配置空间访问从主机处理器转换为PCI Express 配置周期。为了保持与PCI配置寻址机制的兼容性,建议系统软件仅使用32位操作(32位对齐)访问增强的配置空间。有关PCI兼容和PCI Express 增强配置机制和事务规则的详细信息,请参阅《 PCI Express基本规范》。

IO端口访问

  1. request_region(0xCF8, 8, "PCI conf1")

X86使用0xCF8(bus+device+),0xCFC作为IO端口来访问PCI,对PCI主桥校验

  1. baiy@ubuntu:output$ sudo cat /proc/ioports | grep PCI
  2. 0000-0cf7 : PCI Bus 0000:00
  3. 0cf8-0cff : PCI conf1 # 访问基础配置空间
  4. ......

这就是PCI基础配置空间读写的真实方法:

  1. inno@DEV-005:~$ sudo cat /proc/ioports | grep "PCI conf"
  2. 0cf8-0cff : PCI conf1
  3. // arch/x86/direct.c
  4. #define PCI_CONF1_ADDRESS(bus, devfn, reg) \
  5. (0x80000000 | ((reg & 0xF00) << 16) | (bus << 16) \
  6. | (devfn << 8) | (reg & 0xFC))
  7. outl(PCI_CONF1_ADDRESS(bus, devfn, reg), 0xCF8); // 配置地址
  8. u32 value = inl(0xCFC); // 读取配置

CONFIG_ADDRESS寄存器格式:
31 位:Enabled位。
23:16 位:总线编号。 // bus
15:11 位:设备编号。 // devfn[7:3]
10: 8 位:功能编号。 // devfn[2:0]
7: 2 位:配置空间寄存器编号。 // 配置空间偏移地址, 注:因为是32位端口,所以4字节访问。 // 所以支持0x00-0xFF的配置空间访问
1: 0 位:恒为“00”。这是因为CF8h、CFCh端口是32位端口。

ECAM访问

MCFG访问机制

上节问题:IO端口访问配置空间偏移地址只有8-bit,所以只能访问配置空间0x00-0xFF地址,但PCIE可支持4K个地址偏移,怎么办? 

PCI访问0x100-0xFFF是通过MCFG作为基地址映射来访问的,那么MCFG怎么查看?
PCIE ECAM机制 和 《PCI Express Base_r5_1》 7.2 章节 中描述:

ECAM是访问PCIe配置空间一种机制,PCIe配置空间大小是4k 4kbyte寄存器地址空间,需要12bit bit 0~bit11 Function Number bit 12~bit 14 Device Number bit 15~bit 19 Bus Number bit 20~bit 27 如何访问一个PCIe设备的配置空间呢 比如ECAM 基地址是0xd0000000 devmem 0xd0000000就是访问00:00.0 设备偏移0寄存器,就是Device ID和Vendor ID devmem 0xd0100000就是访问01:00.0 设备偏移0寄存器

image.png

那么,这个ECAM基地址是什么?不同PCIE设备的ECAM基地址怎么看?

方式一:可以尝试将ACPI表打开查看下:
注:打印dmar的acpi表信息(详细可参考 ACPI)

  1. sudo apt-get install -y iasl acpica-tools
  2. mkdir -p testacpi && cd testacpi
  3. acpidump > acpidump.out # 将ACPI表二进制打印到文件
  4. acpixtract -a acpidump.out # 解析acpi表,生成各个dat文件
  5. iasl -d mcfg.dat # iasl会解析acpi 二进制表,生成xxx.dsl描述文件
  6. cat mcfg.dsl # 可以查看mcfg的配置文件

image.png
方式二:通过/proc/iomem查看
比如Intel,我这里看到的是 0xE000_0000, Start BusNum=00, End BusNum=ff, 所以所有总线的ECAM都在这个空间,按照ECAM地址空间依次偏移即可。

  1. cat /proc/iomem | grep e0000000
  2. e0000000-eFFFFFFF PCI MMCONFIG [bus 00 - ff]

方式三:启动信息描述
在系统启动过程中,有这么一句打印:

  1. 比如这个ECAM的基地址是0xe0000000
  2. [ 0.111732] PCI: MMCONFIG for domain 0000 [bus 00-ff] at [mem 0xe0000000-0xefffffff] (base 0xe0000000)
  3. [ 0.111734] PCI: MMCONFIG at [mem 0xe0000000-0xefffffff] reserved in E820

ECAM是如何初始化的

第一阶段:ECAM初始化
在系统启动前期,有个打印:

  1. [ 0.149685] PCI: MMCONFIG for domain 0000 [bus 00-3f] at [mem 0xf8000000-0xfbffffff] (base 0xf8000000)
  2. [ 0.149685] PCI: MMCONFIG at [mem 0xf8000000-0xfbffffff] reserved in E820
  1. // init.c:45:arch_initcall(pci_arch_init);
  2. pci_arch_init
  3. if (!(pci_probe & PCI_PROBE_NOEARLY))
  4. pci_mmcfg_early_init(); // 这里会初始化ECAM

这个 pci_mmcfg_early_init 就是前期扫描mmcfg资源的
原理:

  1. 扫描系统主桥,判断系统主桥的Device 和 Vendor ID是否与 pci_mmcfg_probes 中的匹配,进入对应的初始化流程。
  2. 在初始化流程里,检测MCFG的配置,来获取MCFG的起始地址 以及 bus范围
  1. pci_mmcfg_early_init // S:\linux-git\arch\x86\pci\mmconfig-shared.c
  2. pci_mmcfg_check_hostbridge() // 这里会扫描 pci_mmcfg_probes 数组,来匹配所有的MMCFG硬件
  3. raw_pci_ops->read(0, bus, devfn, 0, 4, &l); // raw_pci_ops = &pci_direct_conf1; 读取对应设备的vendorID,判断是否与pci_mmcfg_probes一致
  4. pci_mmcfg_e7520 // 这里假设匹配到Intele7520设备
  5. pci_mmcfg_e7520
  6. raw_pci_ops->read(0, 0, PCI_DEVFN(0, 0), 0xce, 2, &win); // 获取ECAM的基地址
  7. pci_mmconfig_add(0, 0, 255, win << 16) // 将基地址添加到资源内,也就是 内核前期的打印信息

第二阶段:主桥映射

从ACPI获取MCFG基地址,并映射

  1. pci_mmcfg_late_init();
  2. // #define ACPI_SIG_MCFG "MCFG" /* PCI Memory Mapped Configuration table */
  3. acpi_table_parse(ACPI_SIG_MCFG, pci_mcfg_parse); // "ACPI中关于MCFG的描述"
  1. 流程回顾
  1. pci_acpi_scan_root // 主桥信息struct pci_root_info 和 struct pci_sysdata 初始化
  2. -> acpi_pci_root_create // ECAM初始化,主桥资源初始化
  3. -> if (ops->init_info && ops->init_info(info)) // ECAM 初始化 init_info = pci_acpi_root_init_info
  4. -> setup_mcfg_map
  1. 主桥映射mcfg地址空间,并初始化接口

  1. unsigned int pci_probe = PCI_PROBE_BIOS | PCI_PROBE_CONF1 | PCI_PROBE_CONF2 |
  2. PCI_PROBE_MMCONF;
  3. ops->init_info(info)
  4. pci_acpi_root_init_info
  5. setup_mcfg_map(ci);
  6. pci_mmconfig_insert(dev, seg, info->start_bus, info->end_bus,
  7. root->mcfg_addr);
  8. // 分配MCFG的 struct pci_mmcfg_region !!! 这里只分配主桥管理的BUS总线的资源,因为1个主桥并不一定全部0x00-0XFF的PCI总线
  9. cfg = pci_mmconfig_alloc(seg, start, end, addr);
  10. pci_mmcfg_check_reserved(dev, cfg, 0); // 检测主桥对MMC的预留信息(非重点)
  11. insert_resource_conflict(&iomem_resource, &cfg->res); // __insert_resource ( 非重点)
  12. pci_mmcfg_arch_map(cfg)
  13. cfg->virt = mcfg_ioremap(cfg); // !!! 映射mcfg空间
  14. list_add_sorted(cfg); // !!! pci_mmcfg_list 存放了全局所有主桥下的ECAM配置
  15. // 启动打印: PCI: MMCONFIG for domain 0000 [bus 00-ff] at [mem 0x80000000-0x8fffffff] (base 0x80000000)
  16. // PCI: MMCONFIG at [mem 0x80000000-0x8fffffff] reserved in E820 这两个地址就是前边看的acpi的地址
  17. raw_pci_ext_ops = &pci_mmcfg; // 这就是访问PCI 扩展空间的ECAM方式。

第三阶段:提供接口

内核启动后读写配置空间接口

  1. inline int pci_read_config_byte(struct pci_dev *dev, int where, u8 *val);
  2. inline int pci_read_config_word(struct pci_dev *dev, int where, u16 *val);
  3. inline int pci_read_config_dword(struct pci_dev *dev, int where, u32 *val);
  4. inline int pci_write_config_byte(struct pci_dev *dev, int where, u8 val);
  5. inline int pci_write_config_word(struct pci_dev *dev, int where, u16 val);
  6. inline int pci_write_config_dword(struct pci_dev *dev, int where, u32 val);
  7. raw_pci_ops = &pci_direct_conf1
  8. raw_pci_ext_ops = &pci_mmcfg;
  9. int raw_pci_read(unsigned int domain, unsigned int bus, unsigned int devfn,
  10. int reg, int len, u32 *val)
  11. {
  12. if (domain == 0 && reg < 256 && raw_pci_ops)
  13. return raw_pci_ops->read(domain, bus, devfn, reg, len, val); // 0x00-0xff 使用io端口读写
  14. if (raw_pci_ext_ops)
  15. return raw_pci_ext_ops->read(domain, bus, devfn, reg, len, val); // 0x100-0xfff 使用ecam方式读写
  16. return -EINVAL;
  17. }
  18. int raw_pci_write(unsigned int domain, unsigned int bus, unsigned int devfn,
  19. int reg, int len, u32 val)
  20. {
  21. if (domain == 0 && reg < 256 && raw_pci_ops)
  22. return raw_pci_ops->write(domain, bus, devfn, reg, len, val);
  23. if (raw_pci_ext_ops)
  24. return raw_pci_ext_ops->write(domain, bus, devfn, reg, len, val);
  25. return -EINVAL;
  26. }
  1. ECAM读写需要先将配置空间 map到本地,然后进行读写即可
  2. int pci_generic_config_read32(struct pci_bus *bus, unsigned int devfn,
  3. int where, int size, u32 *val)
  4. {
  5. void __iomem *addr;
  6. addr = bus->ops->map_bus(bus, devfn, where & ~0x3);
  7. if (!addr) {
  8. *val = ~0;
  9. return PCIBIOS_DEVICE_NOT_FOUND;
  10. }
  11. *val = readl(addr); // writel(val, addr);
  12. if (size <= 2)
  13. *val = (*val >> (8 * (where & 3))) & ((1 << (size * 8)) - 1);
  14. return PCIBIOS_SUCCESSFUL;
  15. }
  16. /* ECAM ops for 32-bit access only (non-compliant) */
  17. struct pci_ecam_ops pci_32b_ops = {
  18. .bus_shift = 20,
  19. .pci_ops = {
  20. .map_bus = pci_ecam_map_bus,
  21. .read = pci_generic_config_read32,
  22. .write = pci_generic_config_write32,
  23. }
  24. };
  25. 这里主要看 map_bus
  26. struct pci_config_window *cfg = bus->sysdata;
  27. void __iomem *base = cfg->win + (busn << cfg->ops->bus_shift);