2. PCIe设备BAR空间映射与寄存器读写
任务说明
编写一个内核模块,在设备探测时读取 PCIe 设备的 BAR0 基址,用 pci_iomap 将其映射到内核虚拟地址,然后对偏移 0x00 的控制寄存器执行一次“读-写-回读”验证:先读原值,写入一个测试值,再读回确认一致,最后恢复原值。卸载时用 pci_iounmap 解除映射。
关键点
- BAR0 的物理基址通过
pci_resource_start(pdev, 0)获取,长度用pci_resource_len。 - 映射前必须用
pci_request_region声明资源,避免与其他驱动冲突。 - 寄存器访问用
ioread32/iowrite32,不能直接解引用指针。 - 测试值选择要避开只读位:这里用
0xA5A50000并只比较可写位,实际设备需按数据手册确定掩码。
完整代码(pcie_bar_rw.c)
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/io.h>
#define DRV_NAME "pcie_bar_rw"
#define BAR_INDEX 0
#define CTRL_REG_OFF 0x00
#define TEST_VALUE 0xA5A50000u
static int bar_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
void __iomem *bar;
resource_size_t bar_start, bar_len;
u32 orig, readback;
if (pci_enable_device(pdev))
return -ENODEV;
bar_start = pci_resource_start(pdev, BAR_INDEX);
bar_len = pci_resource_len(pdev, BAR_INDEX);
if (!bar_start || !bar_len) {
dev_err(&pdev->dev, "BAR%d not available\n", BAR_INDEX);
pci_disable_device(pdev);
return -ENODEV;
}
if (pci_request_region(pdev, BAR_INDEX, DRV_NAME)) {
dev_err(&pdev->dev, "cannot request BAR%d region\n", BAR_INDEX);
pci_disable_device(pdev);
return -EBUSY;
}
bar = pci_iomap(pdev, BAR_INDEX, bar_len);
if (!bar) {
dev_err(&pdev->dev, "pci_iomap failed\n");
pci_release_region(pdev, BAR_INDEX);
pci_disable_device(pdev);
return -ENOMEM;
}
orig = ioread32(bar + CTRL_REG_OFF);
iowrite32(TEST_VALUE, bar + CTRL_REG_OFF);
readback = ioread32(bar + CTRL_REG_OFF);
iowrite32(orig, bar + CTRL_REG_OFF);
dev_info(&pdev->dev,
"BAR%d phys=0x%llx len=0x%llx orig=0x%08x wrote=0x%08x read=0x%08x %s\n",
BAR_INDEX, (unsigned long long)bar_start,
(unsigned long long)bar_len, orig, TEST_VALUE, readback,
(readback == TEST_VALUE) ? "OK" : "MISMATCH");
pci_set_drvdata(pdev, bar);
return 0;
}
static void bar_remove(struct pci_dev *pdev)
{
void __iomem *bar = pci_get_drvdata(pdev);
if (bar)
pci_iounmap(pdev, bar);
pci_release_region(pdev, BAR_INDEX);
pci_disable_device(pdev);
}
static const struct pci_device_id bar_ids[] = {
{ PCI_DEVICE(0x1234, 0x5678) },
{ }
};
MODULE_DEVICE_TABLE(pci, bar_ids);
static struct pci_driver bar_driver = {
.name = DRV_NAME,
.id_table = bar_ids,
.probe = bar_probe,
.remove = bar_remove,
};
module_pci_driver(bar_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("PCIe BAR0 map and control register read/write test");
构建与运行
# Makefile
obj-m += pcie_bar_rw.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_bar_rw.ko
dmesg | tail -n 5
sudo rmmod pcie_bar_rw
把 bar_ids 中的 0x1234:0x5678 换成目标设备的 Vendor/Device ID(用 lspci -nn 查看)。若设备已被其他驱动占用,需先 rmmod 该驱动或使用 pci-stub 绑定。
验证与判定
- 加载后
dmesg应出现一行BAR0 phys=... len=... orig=... wrote=... read=... OK。 read == wrote且末尾为OK,说明映射成功且控制寄存器可读写。- 若显示
MISMATCH,说明该寄存器存在只读位或写保护,需按数据手册调整TEST_VALUE与比较掩码。 - 卸载后
dmesg无 oops,lsmod中不再有pcie_bar_rw。
常见问题
pci_request_region返回-EBUSY:BAR 已被占用,检查是否有其他驱动绑定。pci_iomap返回 NULL:BAR 长度可能为 0,或设备未正确枚举。- 读回值恒为
0xFFFFFFFF:设备可能未上电或链路未建立,先确认lspci -vv中 BAR 已分配。