3. 驱动开发环境搭建:内核源码树、交叉编译工具链、QEMU模拟PCIe设备
在开始编写和调试PCIe驱动之前,必须搭建一个可复现、可控制的开发环境。本章将指导你完成三个核心组件的配置:内核源码树(用于编译驱动模块)、交叉编译工具链(用于目标架构编译)以及QEMU模拟器(用于模拟PCIe设备并运行驱动)。一个标准化的环境能大幅减少“环境不一致”导致的调试干扰。
3.1 内核源码树准备
驱动模块必须与目标内核版本完全匹配。我们采用独立内核源码树的方式,不依赖宿主机的内核头文件。
3.1.1 下载与解压
# 以 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
3.1.2 配置内核(针对 QEMU aarch64)
使用 QEMU 推荐的 defconfig 并启用 PCIe 相关选项:
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
# 启用 PCIe 热插拔与虚拟化支持
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig
在 menuconfig 中确保以下选项被选中(标记为 <*> 或 <M>):
| 配置项 | 路径 | 说明 |
|---|---|---|
CONFIG_PCI |
Bus support → PCI support | PCI 总线核心支持 |
CONFIG_PCI_MSI |
Bus support → PCI → MSI/MSI-X support | 消息信号中断,PCIe 必备 |
CONFIG_PCIEPORTBUS |
Bus support → PCI → PCI Express Port Bus support | PCIe 端口驱动 |
CONFIG_PCI_IOV |
Bus support → PCI → PCI IOV support | 单根虚拟化(SR-IOV) |
CONFIG_VIRTIO_PCI |
Device Drivers → Virtio drivers → PCI driver for virtio devices | QEMU 虚拟 PCIe 设备后端 |
3.1.3 编译内核与模块
# 编译内核镜像
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- Image -j$(nproc)
# 编译内核模块
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- modules -j$(nproc)
# 安装模块到临时根文件系统目录
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- INSTALL_MOD_PATH=../rootfs modules_install
arch/arm64/boot/ 下生成 Image 文件,这是 QEMU 启动所需的内核镜像。模块安装在 ../rootfs/lib/modules/6.1.76/ 下。
3.2 交叉编译工具链
由于目标架构是 aarch64,我们需要在 x86_64 主机上使用交叉编译器。推荐使用 Linaro 或 ARM 官方提供的 GCC 工具链。
3.2.1 安装工具链
# 方法一:apt 安装(Ubuntu 官方源)
sudo apt install gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu
# 方法二:下载 ARM 官方预编译工具链(版本更新)
wget https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x86_64-aarch64-none-linux-gnu.tar.xz
tar -xf arm-gnu-toolchain-13.2.rel1-x86_64-aarch64-none-linux-gnu.tar.xz
export PATH=$PATH:$(pwd)/arm-gnu-toolchain-13.2.rel1-x86_64-aarch64-none-linux-gnu/bin
3.2.2 验证工具链
aarch64-linux-gnu-gcc --version
# 输出示例:aarch64-linux-gnu-gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
3.2.3 编译一个独立驱动模块
以下 Makefile 展示了如何利用内核源码树编译外部模块:
# Makefile (放在驱动源码目录)
obj-m := pcie_sample.o
KERNEL_DIR ?= /path/to/linux-6.1.76
CROSS_COMPILE ?= aarch64-linux-gnu-
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
# 编译命令
make KERNEL_DIR=~/linux-6.1.76
编译成功后,会生成 pcie_sample.ko,这是目标架构的 ARM64 内核模块。
3.3 QEMU 模拟 PCIe 设备
QEMU 的 virt 平台原生支持 PCIe 总线,并允许通过命令行添加自定义 PCIe 设备。我们使用 QEMU 的 -device 参数来模拟一个简单的 PCIe 端点设备。
3.3.1 安装 QEMU
sudo apt install qemu-system-arm qemu-utils
3.3.2 创建根文件系统
使用 Buildroot 或直接使用 Ubuntu Base 创建一个最小的 aarch64 根文件系统。这里以 Buildroot 为例:
# 下载 Buildroot
wget https://buildroot.org/downloads/buildroot-2024.02.tar.gz
tar -xf buildroot-2024.02.tar.gz
cd buildroot-2024.02
# 配置 aarch64
make qemu_aarch64_virt_defconfig
make -j$(nproc)
# 输出文件在 output/images/ 下:rootfs.ext4, Image, qemu-system-aarch64 启动脚本
3.3.3 启动 QEMU 并添加 PCIe 设备
QEMU 提供了 pci-shpc(标准热插拔控制器)和 pcie-root-port 来模拟 PCIe 拓扑。我们通过 -device 添加一个自定义 PCIe 设备:
qemu-system-aarch64 \
-machine virt,gic-version=3 \
-cpu cortex-a72 \
-smp 4 \
-m 2G \
-kernel Image \
-drive file=rootfs.ext4,format=raw,if=none,id=hd0 \
-device virtio-blk-device,drive=hd0 \
-append "root=/dev/vda console=ttyAMA0" \
-nographic \
-device pcie-root-port,id=pcie.1,bus=pcie.0,addr=1c.0,slot=1 \
-device pci-shpc,id=shpc0,bus=pcie.1 \
-device edu,bus=pcie.1,addr=00.0 \
-device pci-testdev,bus=pcie.1,addr=01.0
参数说明:
pcie-root-port:创建一个 PCIe 根端口,挂载在总线pcie.0上。pci-shpc:标准热插拔控制器,用于模拟 PCIe 热插拔。edu:QEMU 自带的 PCI 教育设备(edu),支持 DMA、MSI、BAR 操作,非常适合驱动开发测试。pci-testdev:另一个测试设备,用于验证 PCI 配置空间访问。
edu 设备不是真正的 PCIe 设备,而是 PCI 设备。在 QEMU 中,PCI 设备可以挂载在 PCIe 总线上,但无法使用 PCIe 独有特性(如 AER、DPC)。如需测试纯 PCIe 设备,可使用 vfio-pci 直通或自定义 QEMU 设备模型。
3.3.4 在 QEMU 中验证 PCIe 设备
启动 QEMU 后,登录根文件系统,使用 lspci 查看设备:
# lspci
00:00.0 Host bridge: Intel Corporation 440FX - 82441FX PMC [Natoma] (rev 02)
00:01.0 ISA bridge: Intel Corporation 82371SB PIIX3 ISA [Natoma/Triton II]
00:1c.0 PCI bridge: Red Hat, Inc. QEMU PCIe Root port
01:00.0 PCI bridge: Red Hat, Inc. QEMU PCIe Root port
02:00.0 Unclassified device [00ff]: Device 1234:11e8 (rev 10) # edu 设备
02:01.0 Unclassified device [00ff]: Device 1234:11e9 (rev 10) # pci-testdev
使用 lspci -vvv -s 02:00.0 查看详细配置空间,确认 BAR 地址、MSI 能力等。
3.4 开发环境架构总览
下图展示了主机、交叉编译工具链、内核源码树与 QEMU 目标机之间的数据流与依赖关系:
3.5 环境验证清单
完成搭建后,使用以下清单逐项验证环境正确性:
| 检查项 | 验证命令 | 预期结果 |
|---|---|---|
| 交叉编译器可用 | aarch64-linux-gnu-gcc --version |
显示版本信息,无错误 |
| 内核可编译 | make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- Image |
生成 arch/arm64/boot/Image |
| 外部模块可编译 | make -C $(KERNEL_DIR) M=$(PWD) modules |
生成 .ko 文件,无未定义符号 |
| QEMU 可启动 | 执行 QEMU 启动命令 | 进入 shell,uname -a 显示 6.1.76 |
| PCIe 设备可见 | lspci |
列出 edu 设备 (1234:11e8) |
| 驱动可加载 | insmod pcie_sample.ko |
dmesg 显示驱动初始化信息 |
env_check.sh,每次搭建新环境时自动运行,确保所有组件一致。后续章节的调试案例均基于此环境。
3.6 常见问题与解决
- 编译模块时提示
missing kernel header:确保KERNEL_DIR指向已编译的内源码树,且执行过make modules_prepare。 - QEMU 启动后无法识别 PCIe 设备:检查
-machine virt是否启用 PCIe(默认启用),并确认-device pcie-root-port的bus参数正确。 - 模块加载失败:
Invalid module format:内核版本或配置不匹配。重新编译模块,确保KERNEL_DIR与 QEMU 运行的内核完全一致。 - 交叉编译工具链版本过旧:推荐使用 GCC 11+,旧版本可能不支持某些 ARM64 指令或内核内联汇编。