2. PCIe驱动开发环境搭建:内核源码树准备、QEMU虚拟PCIe设备模拟、交叉编译工具链配置与模块加载基础

PCIe驱动开发与普通用户态程序开发有着本质区别:驱动代码必须与特定版本的内核源码树绑定编译,运行环境依赖真实的或虚拟的PCIe硬件,调试过程涉及内核态与硬件寄存器的直接交互。本章将系统搭建一套完整的PCIe驱动开发环境,涵盖内核源码树准备、QEMU虚拟PCIe设备模拟、交叉编译工具链配置以及模块加载验证四个核心环节。

环境概览: 本课程采用 x86_64 主机 + QEMU 模拟 aarch64 目标机的典型交叉开发模式。主机用于编译和调试,QEMU 虚拟机作为目标运行环境,通过虚拟 PCIe 设备提供硬件交互接口。

2.1 内核源码树准备

内核源码树是驱动编译的“头文件仓库”和“符号表来源”。驱动模块必须与目标运行内核的源码树版本完全一致,否则会出现 modprobe: ERROR 或内核崩溃。

2.1.1 获取内核源码

推荐从 kernel.org 获取长期支持(LTS)版本。本课程使用 Linux 6.1 LTS 作为演示版本。

# 下载并解压内核源码
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.76.tar.xz
tar -xf linux-6.1.76.tar.xz
cd linux-6.1.76

2.1.2 配置与编译内核

为了支持 PCIe 驱动开发和 QEMU 虚拟化,需要启用以下关键配置项:

配置项 说明 推荐值
CONFIG_PCI PCI 总线支持 y
CONFIG_PCI_MSI MSI/MSI-X 中断支持 y
CONFIG_PCIEPORTBUS PCIe 端口总线驱动 y
CONFIG_PCI_DEBUG PCI 调试信息输出 y
CONFIG_MODULES 内核模块加载支持 y
CONFIG_PCI_IOV SR-IOV 虚拟化支持(进阶) m
# 生成默认配置(基于当前架构)
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig

# 手动调整 PCI 相关配置
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig
# 进入 Device Drivers → PCI support,确保上述选项开启

# 编译内核(使用多核加速)
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc)
常见陷阱: 如果目标内核启用了 CONFIG_MODULE_SIG(模块签名验证),编译出的模块必须签名才能加载。开发阶段建议临时关闭该选项,或使用 make modules_install 安装后通过 modprobe --force 加载。

2.2 QEMU虚拟PCIe设备模拟

QEMU 提供了强大的设备模拟能力,可以创建虚拟 PCIe 控制器和设备,使驱动开发无需真实硬件即可进行功能验证。

2.2.1 安装支持PCIe的QEMU

# 安装完整版 QEMU(包含 PCIe 模拟支持)
sudo apt install qemu-system-arm qemu-efi-aarch64

# 验证 PCIe 支持
qemu-system-aarch64 -machine help | grep virt
# 输出应包含 'virt' 机器类型,该类型默认包含 PCIe 控制器

2.2.2 创建虚拟PCIe设备

QEMU 支持通过 -device 参数添加多种 PCIe 设备。本课程使用 edu 设备(pci- edu),这是一个专为教学设计的 PCIe 功能模拟设备,包含 BAR 空间、MSI 中断、DMA 等典型 PCIe 特性。

# 启动包含 edu 设备的虚拟机
qemu-system-aarch64 \
  -machine virt,gic-version=3 \
  -cpu cortex-a72 \
  -smp 4 \
  -m 4G \
  -kernel arch/arm64/boot/Image \
  -initrd /path/to/initrd.img \
  -append "console=ttyAMA0 root=/dev/vda" \
  -device pci- edu \
  -nographic
edu 设备 BAR 布局:
  • BAR0: 4KB MMIO 空间,用于控制寄存器访问
  • BAR1: 1MB MMIO 空间,用于 DMA 缓冲区
  • BAR2: 32-bit 非预取内存空间
  • 中断: 支持 MSI 和 INTx

2.2.3 验证设备枚举

在虚拟机内部执行以下命令,确认 PCIe 设备已被内核正确枚举:

# 查看所有 PCIe 设备
lspci -vvv

# 预期输出包含 edu 设备
# 00:04.0 Unclassified device [00ff]: Device 1234:11e8 (rev 10)
#   Subsystem: Device 1af4:1100
#   Control: I/O+ Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B- DisINTx-
#   Interrupt: pin A routed to IRQ 32
#   Region 0: Memory at 4010000000 (64-bit, prefetchable) [size=4K]
#   Region 1: Memory at 4010001000 (64-bit, prefetchable) [size=1M]
#   Capabilities: [40] MSI: Enable- Count=1/1 Maskable- 64bit+
QEMU 虚拟 PCIe 设备架构 QEMU 用户态进程 QEMU PCIe 控制器 (GICv3 + ECAM) PCIe 总线 edu 设备 (Vendor: 0x1234, Device: 0x11e8) BAR0: 4KB MMIO 控制寄存器 BAR1: 1MB MMIO DMA 缓冲区 BAR2: 32-bit 非预取 扩展内存 MSI 中断控制器 GICv3 ITS 中断路由 虚拟 CPU Cortex-A72 x4 💡 驱动通过 MMIO 访问 BAR 空间,通过 MSI 接收中断,QEMU 在用户态模拟所有 PCIe 事务

2.3 交叉编译工具链配置

交叉编译是指在 x86_64 主机上编译出能在 aarch64 目标机上运行的驱动模块。工具链的选择直接影响编译效率和兼容性。

2.3.1 安装交叉编译工具链

# 安装 GNU 交叉编译工具链
sudo apt install gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu

# 验证安装
aarch64-linux-gnu-gcc --version
# 输出示例:aarch64-linux-gnu-gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0

2.3.2 编写 Makefile 模板

驱动模块的 Makefile 需要指定内核源码树路径和交叉编译前缀:

# Makefile - PCIe 驱动模块编译模板
obj-m := pcie_edu.o
pcie_edu-objs := main.o probe.o irq.o dma.o

KERNEL_DIR ?= /home/user/linux-6.1.76
CROSS_COMPILE ?= aarch64-linux-gnu-
CC := $(CROSS_COMPILE)gcc

all:
    $(MAKE) -C $(KERNEL_DIR) M=$(PWD) ARCH=arm64 CROSS_COMPILE=$(CROSS_COMPILE) modules

clean:
    $(MAKE) -C $(KERNEL_DIR) M=$(PWD) ARCH=arm64 CROSS_COMPILE=$(CROSS_COMPILE) clean

install:
    $(MAKE) -C $(KERNEL_DIR) M=$(PWD) ARCH=arm64 CROSS_COMPILE=$(CROSS_COMPILE) modules_install

2.3.3 编译与验证

# 编译驱动模块
make

# 检查生成的目标文件
file pcie_edu.ko
# 输出:pcie_edu.ko: ELF 64-bit LSB relocatable, ARM aarch64, version 1 (SYSV), BuildID[sha1]=..., not stripped

# 查看模块符号表
aarch64-linux-gnu-nm pcie_edu.ko | grep pci
# 应包含 pci_register_driver、pci_enable_device 等内核符号引用
编译加速技巧: 使用 make -j$(nproc) 并行编译。如果内核源码树已编译过,驱动模块编译通常只需几秒钟。首次编译内核时建议使用 make -j$(nproc) ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- Image modules 一次性编译内核和模块。

2.4 模块加载基础

模块加载是驱动开发的“最后一公里”。在 QEMU 虚拟机中加载编译好的 .ko 文件,验证 probe 函数是否被正确调用。

2.4.1 传输模块到虚拟机

通过 QEMU 的虚拟串口或网络共享目录传输模块文件:

# 方法一:使用 virtio-serial 通道
# QEMU 启动参数添加 -chardev socket,path=/tmp/guest.sock,server=on,wait=off,id=char0 -device virtio-serial -device virtserialport,chardev=char0

# 方法二:使用 9p 共享目录(推荐)
# QEMU 启动参数添加 -virtfs local,path=/home/user/modules,security_model=none,mount_tag=modshare
# 虚拟机内挂载:mount -t 9p -o trans=virtio modshare /mnt

2.4.2 加载与卸载模块

# 加载模块
insmod /mnt/pcie_edu.ko

# 查看内核日志
dmesg | tail -20
# 预期输出:
# [  123.456789] pcie_edu: loading out-of-tree module taints kernel.
# [  123.456901] pcie_edu: probe called for device 0000:00:04.0
# [  123.457012] pcie_edu: BAR0 at 0x4010000000, size 4096
# [  123.457123] pcie_edu: BAR1 at 0x4010001000, size 1048576
# [  123.457234] pcie_edu: MSI enabled, IRQ number 32

# 验证设备文件
ls -l /dev/pcie_edu*
# 预期:crw------- 1 root root 240, 0 Jan 15 10:00 /dev/pcie_edu

# 卸载模块
rmmod pcie_edu
dmesg | tail -5
# 预期输出:
# [  456.789012] pcie_edu: remove called for device 0000:00:04.0
# [  456.789123] pcie_edu: module unloaded

2.4.3 模块加载故障排查表

错误现象 可能原因 解决方案
insmod: ERROR: could not insert module 内核版本不匹配 检查 modinfo pcie_edu.ko 的 vermagic 是否与 uname -r 一致
Unknown symbol pci_register_driver 内核符号表未导出 确认内核配置 CONFIG_PCI=y,且模块依赖的符号已 EXPORT_SYMBOL
probe 未调用 设备 ID 表不匹配 检查 pci_device_id 表中的 vendor/device 是否与 lspci -n 输出一致
段错误 / NULL 指针 ioremap 失败或 BAR 地址无效 在 probe 中检查 pci_resource_start() 返回值是否为 0
modprobe: FATAL: Module not found 模块未安装到标准路径 使用 insmod /full/path/to/pcie_edu.ko 或运行 depmod 更新依赖
驱动模块加载与初始化流程 insmod 用户态命令 module_init 入口函数 pci_register_driver 注册驱动 probe 回调 probe 函数内部执行步骤 pci_enable_device pci_request_regions ioremap BAR0 pci_alloc_irq_vectors request_irq device_create

2.5 环境验证清单

完成上述步骤后,使用以下清单确认环境就绪:

  • 内核源码树: make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -C /path/to/kernel M=$(pwd) modules 编译无错误
  • QEMU 虚拟设备: 虚拟机内 lspci 能正确显示 edu 设备,且 cat /proc/iomem 包含 BAR 地址范围
  • 交叉编译: file pcie_edu.ko 输出为 ELF 64-bit LSB relocatable, ARM aarch64
  • 模块加载: insmod 后 dmesg 显示 probe 成功,ls /dev/pcie_edu 存在设备节点
  • 模块卸载: rmmod 后 dmesg 显示 remove 回调,设备节点消失
开发环境速查表:
组件 版本/路径 验证命令
主机内核 6.1.76 (x86_64) uname -r
目标内核 6.1.76 (aarch64) 虚拟机内 uname -r
交叉编译器 aarch64-linux-gnu-gcc 11.4.0 aarch64-linux-gnu-gcc --version
QEMU qemu-system-aarch64 8.2.0 qemu-system-aarch64 --version
edu 设备 Vendor 0x1234, Device 0x11e8 虚拟机内 lspci -n | grep 1234