20.PCI代码导读-6.PCI配置空间访问-《计算机知识》

admin 2025-11-02 22:26:04 系统网络 来源:ZONE.CI 全球网 0 阅读模式
  • 配置空间访问专栏
    • Intel关于配置空间访问描述
    • IO端口访问
    • ECAM访问
      • MCFG访问机制
      • ECAM是如何初始化的
    • 内核启动后读写配置空间接口

    配置空间访问专栏

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

    Intel关于配置空间访问描述

    在X86架构上有关于这部分的描述:10th Generation Intel® Core™ Processors, Datasheet Volume 1 of 2 中 P29页描述:image.pngPCI 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 位:总线编号。 // bus15: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 // 这里假设匹配到Intel的e7520设备
    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);
    01-shell脚本介绍-《shell脚本》 系统网络

    01-shell脚本介绍-《shell脚本》

    一、shell脚本是什么二、为什么要学shell,而不是其他计算机语言三、学习这门课程的优势四、学了能干什么五、学习什么内容六、学习的技巧七、成长路径八、学习环
    评论:0   参与:  17