配置空间访问专栏
参考:《PCI Express Base_r5_1》 7.2 章节
**
Intel关于配置空间访问描述
在X86架构上有关于这部分的描述:
10th Generation Intel® Core™ Processors, Datasheet Volume 1 of 2 中 P29页描述:
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端口访问
request_region(0xCF8, 8, "PCI conf1")
X86使用0xCF8(bus+device+),0xCFC作为IO端口来访问PCI,对PCI主桥校验
baiy@ubuntu:output$ sudo cat /proc/ioports | grep PCI
0000-0cf7 : PCI Bus 0000:00
0cf8-0cff : PCI conf1 # 访问基础配置空间
......
这就是PCI基础配置空间读写的真实方法:
inno@DEV-005:~$ 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); // 读取配置
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寄存器
那么,这个ECAM基地址是什么?不同PCIE设备的ECAM基地址怎么看?
方式一:可以尝试将ACPI表打开查看下:
注:打印dmar的acpi表信息(详细可参考 ACPI)
sudo apt-get install -y iasl acpica-tools
mkdir -p testacpi && cd testacpi
acpidump > acpidump.out # 将ACPI表二进制打印到文件
acpixtract -a acpidump.out # 解析acpi表,生成各个dat文件
iasl -d mcfg.dat # iasl会解析acpi 二进制表,生成xxx.dsl描述文件
cat mcfg.dsl # 可以查看mcfg的配置文件
方式二:通过/proc/iomem查看
比如Intel,我这里看到的是 0xE000_0000, Start BusNum=00, End BusNum=ff, 所以所有总线的ECAM都在这个空间,按照ECAM地址空间依次偏移即可。
cat /proc/iomem | grep e0000000
e0000000-eFFFFFFF PCI MMCONFIG [bus 00 - ff]
方式三:启动信息描述
在系统启动过程中,有这么一句打印:
比如这个ECAM的基地址是0xe0000000
[ 0.111732] PCI: MMCONFIG for domain 0000 [bus 00-ff] at [mem 0xe0000000-0xefffffff] (base 0xe0000000)
[ 0.111734] PCI: MMCONFIG at [mem 0xe0000000-0xefffffff] reserved in E820
ECAM是如何初始化的
第一阶段:ECAM初始化
在系统启动前期,有个打印:
[ 0.149685] PCI: MMCONFIG for domain 0000 [bus 00-3f] at [mem 0xf8000000-0xfbffffff] (base 0xf8000000)
[ 0.149685] PCI: MMCONFIG at [mem 0xf8000000-0xfbffffff] reserved in E820
// init.c:45:arch_initcall(pci_arch_init);
pci_arch_init
if (!(pci_probe & PCI_PROBE_NOEARLY))
pci_mmcfg_early_init(); // 这里会初始化ECAM,
这个 pci_mmcfg_early_init 就是前期扫描mmcfg资源的
原理:
- 扫描系统主桥,判断系统主桥的Device 和 Vendor ID是否与 pci_mmcfg_probes 中的匹配,进入对应的初始化流程。
- 在初始化流程里,检测MCFG的配置,来获取MCFG的起始地址 以及 bus范围
pci_mmcfg_early_init // S:\linux-git\arch\x86\pci\mmconfig-shared.c
pci_mmcfg_check_hostbridge() // 这里会扫描 pci_mmcfg_probes 数组,来匹配所有的MMCFG硬件
raw_pci_ops->read(0, bus, devfn, 0, 4, &l); // raw_pci_ops = &pci_direct_conf1; 读取对应设备的vendorID,判断是否与pci_mmcfg_probes一致
pci_mmcfg_e7520 // 这里假设匹配到Intel的e7520设备
pci_mmcfg_e7520
raw_pci_ops->read(0, 0, PCI_DEVFN(0, 0), 0xce, 2, &win); // 获取ECAM的基地址
pci_mmconfig_add(0, 0, 255, win << 16) // 将基地址添加到资源内,也就是 内核前期的打印信息
第二阶段:主桥映射
从ACPI获取MCFG基地址,并映射
pci_mmcfg_late_init();
// #define ACPI_SIG_MCFG "MCFG" /* PCI Memory Mapped Configuration table */
acpi_table_parse(ACPI_SIG_MCFG, pci_mcfg_parse); // "ACPI中关于MCFG的描述"
流程回顾
pci_acpi_scan_root // 主桥信息struct pci_root_info 和 struct pci_sysdata 初始化
-> acpi_pci_root_create // ECAM初始化,主桥资源初始化
-> if (ops->init_info && ops->init_info(info)) // ECAM 初始化 init_info = pci_acpi_root_init_info
-> setup_mcfg_map
主桥映射mcfg地址空间,并初始化接口
unsigned int pci_probe = PCI_PROBE_BIOS | PCI_PROBE_CONF1 | PCI_PROBE_CONF2 |
PCI_PROBE_MMCONF;
ops->init_info(info)
pci_acpi_root_init_info
setup_mcfg_map(ci);
pci_mmconfig_insert(dev, seg, info->start_bus, info->end_bus,
root->mcfg_addr);
// 分配MCFG的 struct pci_mmcfg_region !!! 这里只分配主桥管理的BUS总线的资源,因为1个主桥并不一定全部0x00-0XFF的PCI总线
cfg = pci_mmconfig_alloc(seg, start, end, addr);
pci_mmcfg_check_reserved(dev, cfg, 0); // 检测主桥对MMC的预留信息(非重点)
insert_resource_conflict(&iomem_resource, &cfg->res); // __insert_resource ( 非重点)
pci_mmcfg_arch_map(cfg)
cfg->virt = mcfg_ioremap(cfg); // !!! 映射mcfg空间
list_add_sorted(cfg); // !!! pci_mmcfg_list 存放了全局所有主桥下的ECAM配置
// 启动打印: PCI: MMCONFIG for domain 0000 [bus 00-ff] at [mem 0x80000000-0x8fffffff] (base 0x80000000)
// PCI: MMCONFIG at [mem 0x80000000-0x8fffffff] reserved in E820 这两个地址就是前边看的acpi的地址
raw_pci_ext_ops = &pci_mmcfg; // 这就是访问PCI 扩展空间的ECAM方式。
第三阶段:提供接口
内核启动后读写配置空间接口
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);
raw_pci_ops = &pci_direct_conf1
raw_pci_ext_ops = &pci_mmcfg;
int raw_pci_read(unsigned int domain, unsigned int bus, unsigned int devfn,
int reg, int len, u32 *val)
{
if (domain == 0 && reg < 256 && raw_pci_ops)
return raw_pci_ops->read(domain, bus, devfn, reg, len, val); // 0x00-0xff 使用io端口读写
if (raw_pci_ext_ops)
return raw_pci_ext_ops->read(domain, bus, devfn, reg, len, val); // 0x100-0xfff 使用ecam方式读写
return -EINVAL;
}
int raw_pci_write(unsigned int domain, unsigned int bus, unsigned int devfn,
int reg, int len, u32 val)
{
if (domain == 0 && reg < 256 && raw_pci_ops)
return raw_pci_ops->write(domain, bus, devfn, reg, len, val);
if (raw_pci_ext_ops)
return raw_pci_ext_ops->write(domain, bus, devfn, reg, len, val);
return -EINVAL;
}
ECAM读写需要先将配置空间 map到本地,然后进行读写即可
int pci_generic_config_read32(struct pci_bus *bus, unsigned int devfn,
int where, int size, u32 *val)
{
void __iomem *addr;
addr = bus->ops->map_bus(bus, devfn, where & ~0x3);
if (!addr) {
*val = ~0;
return PCIBIOS_DEVICE_NOT_FOUND;
}
*val = readl(addr); // writel(val, addr);
if (size <= 2)
*val = (*val >> (8 * (where & 3))) & ((1 << (size * 8)) - 1);
return PCIBIOS_SUCCESSFUL;
}
/* ECAM ops for 32-bit access only (non-compliant) */
struct pci_ecam_ops pci_32b_ops = {
.bus_shift = 20,
.pci_ops = {
.map_bus = pci_ecam_map_bus,
.read = pci_generic_config_read32,
.write = pci_generic_config_write32,
}
};
这里主要看 map_bus
struct pci_config_window *cfg = bus->sysdata;
void __iomem *base = cfg->win + (busn << cfg->ops->bus_shift);