背景
Linux内核态操作PCIe配置空间非常方便,接口也比较成熟。本文简单记录一个打印bar空间的函数,入参是标准的pci_dev。打印PCI设备所有BAR(Base Address Register)空间信息。该函数会遍历并打印每个BAR的基址、大小和类型等信息。
代码细节
#include <linux/pci.h>
#include <linux/device.h>
/**
* print_pci_bar_info - 打印PCI设备的所有BAR信息
* @pdev: PCI设备指针
*
* 该函数遍历PCI设备的所有BAR(0-5),并打印每个BAR的相关信息,
* 包括基址、大小和类型(内存或I/O)。
*/
void print_pci_bar_info(struct pci_dev *pdev)
{
int i;
resource_size_t start, size;
const char *type_str;
if (!pdev) {
dev_err(NULL, "Invalid PCI device pointer\n");
return;
}
dev_info(&pdev->dev, "PCI device %04x:%02x %02x.%d BAR information:\n",
pdev->vendor, pdev->device, pdev->bus->number,
PCI_SLOT(pdev->devfn));
for (i = 0; i < 6; i++) {
start = pci_resource_start(pdev, i);
size = pci_resource_len(pdev, i);
if (start == 0 && size == 0) {
dev_info(&pdev->dev, " BAR %d: Not used\n", i);
continue;
}
if (pci_resource_flags(pdev, i) & IORESOURCE_IO)
type_str = "I/O";
else if (pci_resource_flags(pdev, i) & IORESOURCE_MEM)
type_str = "Memory";
else
type_str = "Unknown";
dev_info(&pdev->dev, " BAR %d: %s at 0x%llx, size 0x%llx (%llu bytes)\n",
i, type_str,
(unsigned long long)start,
(unsigned long long)size,
(unsigned long long)size);
}
}
函数说明:
- 该函数接收一个struct pci_dev指针作为参数
- 遍历PCI设备的6个BAR寄存器(索引0-5)
- 对于每个BAR,获取其起始地址、长度和标志
- 根据标志判断BAR类型(内存或I/O)
- 使用dev_info打印规范化的BAR信息
- 会正确处理未使用的BAR(起始地址和长度均为0的情况)
关键接口:
pci_resource_start 获取资源启示地址,函数内部实现是直接读取的struct中resource的数组,并且根据barid来的
pci_resource_len 资源大小,是根据end - start算出来的
pci_resource_flags 自动读取标志
dev_info 能够直接读取pcie的device的信息
输出示例:
PCI device 8086:1234 BAR information:
BAR 0: Memory at 0xf0000000, size 0x1000000 (16777216 bytes)
BAR 1: Not used
BAR 2: I/O at 0x00003000, size 0x00000100 (256 bytes)
BAR 3-5: Not used
实践
本文实验是放到了Mellanox OFED网卡驱动中,由于依赖较多不放在这里,仅做金丹展示,其他驱动类似,只需要传入pdev接口,并且pdev入参是系统传过来的。是PCIe驱动程序被PCIe框架调用的时候根据pci table匹配到的pcie的device。可以直接使用。
效果:
使用它lspci查看bar空间效果相同:
综述
本文整体围绕打印 PCI 设备 BAR 空间信息函数的实现、关键接口、应用场景和运行效果进行了介绍 ,提供了在 Linux 内核态打印 PCI 设备 BAR 空间信息的方法。核心关键就是pdev数据结构存储了很多信息,可以直接用内核接口访问,方便后期快速取用和进一步迭代