背景
PCIe设备交互需要基于TLP报文,读取PCIe设备配置空间这些操作在linux内核比较方便,在用户态也有lspci、setpci等工具。如何自己手写一个用户态的PCIe设备,来扫描PCIe设备实现类似lspci的效果,本文是一个简单的实验借助 pciutils 库来扫描并显示系统里 PCI 设备的相关信息。
实现机制
libpci提供的接口来操作,包括分配pci句柄,初始化,扫描、读取信息,详细代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <pci/pci.h>
int main() {
struct pci_access *pci;
struct pci_dev *dev;
pci = pci_alloc();
if (!pci) {
fprintf(stderr, "无法分配 PCI 访问对象\n");
return 1;
}
pci_init(pci);
pci_scan_bus(pci);
for (dev = pci->devices; dev; dev = dev->next) {
pci_fill_info(dev, PCI_FILL_IDENT | PCI_FILL_BASES | PCI_FILL_CLASS);
printf("设备: %02x:%02x.%x\n", dev->bus, dev->dev, dev->func);
printf(" Vendor: 0x%04x (%s)\n", dev->vendor_id,
pci_lookup_name(pci, PCI_LOOKUP_VENDOR, dev->vendor_id, 0));
printf(" Device: 0x%04x (%s)\n", dev->device_id,
pci_lookup_name(pci, PCI_LOOKUP_DEVICE, dev->vendor_id, dev->device_id));
printf(" Class: 0x%06x (%s)\n", dev->device_class,
pci_lookup_name(pci, PCI_LOOKUP_CLASS, dev->device_class, 0));
for (int i = 0; i < 6; i++) {
if (dev->base_addr[i]) {
printf(" BAR%d: 0x%016llx (size=0x%x, type=%s)\n",
i,
(unsigned long long)dev->base_addr[i],
dev->size[i],
(dev->flags[i] & PCI_BASE_ADDRESS_SPACE) ? "Memory" : "I/O");
}
}
printf("------------------------\n");
}
pci_cleanup(pci);
// 根据实际版本选择以下一种释放方式
// pci_free(pci); // 新版本
// pci_free_dev(pci); // 旧版本(如果有该函数)
free(pci); // 直接使用 free()
return 0;
}
主要接口说明:
pci_alloc():创建一个 PCI 访问对象,为后续操作分配必要的资源。
pci_init(pci):对 PCI 访问环境进行初始化,读取配置文件并加载相关驱动。
pci_scan_bus(pci):对系统中的 PCI 总线进行扫描,识别所有连接的 PCI 设备。
pci_fill_info():读取设备的关键信息,如设备 ID、基址寄存器(BARs)以及设备类别等。
pci_lookup_name()将 ID 转换为对应的文本描述。
pci_cleanup(pci):清理 PCI 访问环境。
free(pci):释放 PCI 访问对象所占用的内存。
编译:
yum install pciutils-devel #前置准备
gcc -o pci_info pci_info.c -lpci
实操
安装依赖库:
编译:
执行:可以看到扫描到不同的设备,并且BAR空间也扫描出来了。
综述
用户态实现对PCIe的扫描和读写,以及结合未来使用地址映射,可以做很多有意思的事情,本文仅仅是一个通道的建立。后面结合类似/dev/mem以及资源空间,然后映射到用户态空间可以做不少有意思的事。