遍历PCI设备:读取PCI配置地址0xCF8的技术咨询
手动遍历PCI设备的正确内核态实现方式
首先明确:x86架构下的PCI配置空间访问是通过I/O端口而非物理内存地址来操作的,0xCF8是配置地址端口,0xCFC是配置数据端口,你直接把它们当内存地址访问当然会出错——用户态下内存地址空间受保护,内核态下直接解引用这个数值会被当成虚拟地址,而该虚拟地址没有映射到物理I/O端口,所以触发空指针。
核心操作逻辑
- x86平台默认支持PCI的I/O端口访问模式(Type 0),无需额外硬件配置。
- 构造32位配置地址写入0xCF8端口,格式规则:
31位: 固定为1(标识访问PCI配置空间) 23-16位: 总线号(范围0-255) 15-11位: 设备号(范围0-31) 10-8位: 功能号(范围0-7) 7-0位: 寄存器偏移(必须4字节对齐) - 写入地址后,通过0xCFC端口读写对应配置寄存器的数据。
内核态代码示例
以下是极简内核模块代码,遍历所有可能的总线、设备、功能,读取设备的厂商ID和设备ID:
#include <linux/module.h> #include <linux/io.h> #define PCI_CONFIG_ADDR 0xCF8 #define PCI_CONFIG_DATA 0xCFC static int __init pci_scan_init(void) { u32 addr; u16 vendor_id, device_id; int bus, dev, func; // 遍历所有PCI总线、设备、功能 for (bus = 0; bus < 256; bus++) { for (dev = 0; dev < 32; dev++) { for (func = 0; func < 8; func++) { // 构造配置地址,指向厂商ID寄存器(偏移0x00) addr = (1 << 31) | (bus << 16) | (dev << 11) | (func << 8) | 0x00; outl(addr, PCI_CONFIG_ADDR); vendor_id = inw(PCI_CONFIG_DATA); // 厂商ID为0xFFFF表示该设备/功能不存在 if (vendor_id == 0xFFFF) { // 单功能设备的话,后续功能无需遍历 if (func == 0) break; else continue; } device_id = inw(PCI_CONFIG_DATA + 2); printk(KERN_INFO "PCI Dev: Bus %02x Dev %02x Func %01x - Vendor: %04x, Device: %04x\n", bus, dev, func, vendor_id, device_id); } } } return 0; } static void __exit pci_scan_exit(void) { printk(KERN_INFO "PCI scan module exited\n"); } module_init(pci_scan_init); module_exit(pci_scan_exit); MODULE_LICENSE("GPL");
关键注意事项
- 内核态必须使用
inl()/outl()、inw()/outw()这类I/O端口操作函数,不能直接用指针访问0xCF8/0xCFC——这些是I/O端口地址,不是虚拟内存地址。 - 厂商ID
0xFFFF是PCI规范定义的“设备不存在”标志,可用来跳过无效设备。 - 总线号最大255、设备号最大31、功能号最大7,这是PCI规范的硬性限制。
- 用户态无法实现:用户态进程默认无I/O端口访问权限,即使通过
ioperm()申请权限,也可能受BIOS/UEFI保护,且容易引发系统不稳定,必须用内核态实现。
内容的提问来源于stack exchange,提问作者user2183336
相关产品推荐
相关产品推荐

