1. PCIe设备枚举与DMA能力探测

PCIe设备枚举与DMA能力探测 学习卡片

任务说明

编写一个内核模块,遍历 PCI 总线定位目标 PCIe 设备,读取配置空间中的 BAR 与 Capabilities,判断设备是否具备 DMA 能力,并通过 dmesg 输出关键信息。目标设备通过模块参数 vendor/device 指定,默认匹配 Intel 82599(8086:10fb)。

关键点

  • 用 pci_get_device() 按厂商/设备 ID 定位设备,用 pci_dev_put() 释放引用。
  • BAR 用 pci_resource_start/len/flags 读取,IORESOURCE_MEM 表示内存映射 BAR。
  • DMA 能力判定:设备存在内存 BAR 且配置空间含 PCI_CAP_ID_MSI 或 PCI_CAP_ID_MSIX(中断/DMA 相关能力),并检查 PCI_COMMAND_MASTER 是否可置位。
  • 遍历 Capabilities 链表用 pci_find_capability() 与 pci_read_config_byte()。
模块加载 pci_get_device 定位设备 读取 BAR 遍历 Cap DMA 能力判定 MEM BAR + MSI/MSI-X dmesg 输出

完整代码(pcie_probe.c)

#include <linux/module.h>
#include <linux/pci.h>
#include <linux/init.h>

static int vendor = 0x8086;
static int device = 0x10fb;
module_param(vendor, int, 0444);
module_param(device, int, 0444);
MODULE_PARM_DESC(vendor, "PCI vendor ID");
MODULE_PARM_DESC(device, "PCI device ID");

static void dump_bars(struct pci_dev *pdev)
{
    int i;
    for (i = 0; i < PCI_STD_NUM_BARS; i++) {
        resource_size_t start = pci_resource_start(pdev, i);
        resource_size_t len   = pci_resource_len(pdev, i);
        unsigned long flags   = pci_resource_flags(pdev, i);
        if (!len)
            continue;
        pr_info("pcie_probe: BAR%d start=%pa len=%pa %s\n",
                i, &start, &len,
                (flags & IORESOURCE_MEM) ? "MEM" : "IO");
    }
}

static void dump_caps(struct pci_dev *pdev)
{
    int pos = 0;
    u8 cap_id;
    while ((pos = pci_find_next_capability(pdev, pos, 0)) > 0) {
        pci_read_config_byte(pdev, pos, &cap_id);
        pr_info("pcie_probe: cap id=0x%02x at 0x%02x\n", cap_id, pos);
    }
}

static int __init pcie_probe_init(void)
{
    struct pci_dev *pdev;
    u16 cmd;
    bool has_mem_bar = false, has_msi = false;
    int i;

    pdev = pci_get_device(vendor, device, NULL);
    if (!pdev) {
        pr_info("pcie_probe: device %04x:%04x not found\n", vendor, device);
        return -ENODEV;
    }

    pr_info("pcie_probe: found %s at %s\n", pci_name(pdev), pci_name(pdev));

    for (i = 0; i < PCI_STD_NUM_BARS; i++) {
        if (pci_resource_len(pdev, i) &&
            (pci_resource_flags(pdev, i) & IORESOURCE_MEM)) {
            has_mem_bar = true;
            break;
        }
    }

    if (pci_find_capability(pdev, PCI_CAP_ID_MSI) ||
        pci_find_capability(pdev, PCI_CAP_ID_MSIX))
        has_msi = true;

    dump_bars(pdev);
    dump_caps(pdev);

    pci_read_config_word(pdev, PCI_COMMAND, &cmd);
    pr_info("pcie_probe: COMMAND=0x%04x MEM_BAR=%d MSI=%d DMA_CAPABLE=%d\n",
            cmd, has_mem_bar, has_msi, has_mem_bar && has_msi);

    pci_dev_put(pdev);
    return 0;
}

static void __exit pcie_probe_exit(void)
{
    pr_info("pcie_probe: unloaded\n");
}

module_init(pcie_probe_init);
module_exit(pcie_probe_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("PCIe enumeration and DMA capability probe");

Makefile

obj-m += pcie_probe.o
KDIR ?= /lib/modules/$(shell uname -r)/build
all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean

运行与验证

make
sudo insmod pcie_probe.ko
dmesg | tail -n 30
sudo rmmod pcie_probe

指定其他设备:sudo insmod pcie_probe.ko vendor=0x10de device=0x1c82。

通过条件:dmesg 中出现 found 行、至少一条 BARn ... MEM 行、若干 cap id= 行,以及 DMA_CAPABLE=1(设备确有内存 BAR 且带 MSI/MSI-X 时)。若设备不存在则输出 not found 并返回 -ENODEV,属预期行为。