3. 驱动开发环境搭建:内核源码树、交叉编译工具链、QEMU模拟PCIe设备

在开始编写和调试PCIe驱动之前,必须搭建一个可复现、可控制的开发环境。本章将指导你完成三个核心组件的配置:内核源码树(用于编译驱动模块)、交叉编译工具链(用于目标架构编译)以及QEMU模拟器(用于模拟PCIe设备并运行驱动)。一个标准化的环境能大幅减少“环境不一致”导致的调试干扰。

环境概览:本课程采用 x86_64 主机作为开发机,目标架构为 aarch64(ARM64),使用 QEMU 的 virt 平台模拟 PCIe 总线与设备。所有操作均在 Ubuntu 22.04 LTS 上验证。

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 目标机之间的数据流与依赖关系:

PCIe 驱动开发环境架构图 PCIe 驱动开发环境架构 主机 (x86_64) Ubuntu 22.04 GCC / 编辑器 / 调试器 QEMU 模拟器 交叉编译工具链 aarch64-linux-gnu-gcc aarch64-linux-gnu-ld aarch64-linux-gnu-objdump 内核源码树 Linux 6.1.76 ARM64 defconfig PCIe 驱动模块 调用 编译 QEMU 目标机 (aarch64) virt 平台 / Cortex-A72 PCIe 总线 + 根端口 edu 设备 / pci-testdev 加载驱动 .ko 并调试 Image + 模块 QEMU 启动 工作流程: ① 主机编写驱动源码 → ② 交叉编译链 + 内核源码树编译生成 .ko → ③ 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 显示驱动初始化信息
最佳实践:将上述验证步骤写入一个 shell 脚本 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 指令或内核内联汇编。