You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

遍历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端口地址,不是虚拟内存地址。
  • 厂商ID0xFFFF是PCI规范定义的“设备不存在”标志,可用来跳过无效设备。
  • 总线号最大255、设备号最大31、功能号最大7,这是PCI规范的硬性限制。
  • 用户态无法实现:用户态进程默认无I/O端口访问权限,即使通过ioperm()申请权限,也可能受BIOS/UEFI保护,且容易引发系统不稳定,必须用内核态实现。

内容的提问来源于stack exchange,提问作者user2183336

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 03:10:09