2、驱动开发环境搭建:交叉编译工具链配置、Linux内核源码获取与编译、设备树(Device Tree)中GPU节点的配置、根文件系统集成驱动模块。

好,咱们直接进入正题。驱动开发,说白了就是让硬件在操作系统里“活”起来。但嵌入式环境跟你的PC不一样,你不能直接在开发板上写代码、编译——性能不够,工具链也不全。所以,交叉编译是第一步。

2.1 交叉编译工具链配置

我个人习惯用Linaro提供的GCC工具链,稳定,社区支持也好。你想想看,如果你的目标芯片是ARM Cortex-A系列,那你就需要arm-linux-gnueabihf-这套前缀的工具。

安装步骤其实不复杂:

  1. 下载对应架构的交叉编译器(比如gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf.tar.xz)。
  2. 解压到/opt/目录下。
  3. bin目录加入PATH环境变量。
export PATH=/opt/gcc-arm-9.2/bin:$PATH
export CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH=arm

我在项目中遇到过一个问题:工具链版本太新,导致内核编译报错。嗯,这里要注意,内核版本和工具链版本要匹配。比如Linux 4.19内核,用gcc 9.x系列就挺稳。太新的gcc 12.x可能会引入一些语法警告,甚至直接编译失败。

避坑指南: 我曾经因为偷懒,直接用系统自带的gcc去编译ARM内核,结果链接阶段报了一堆“relocation truncated to fit”错误。后来才发现,必须用专用的交叉编译器,不能混用。

2.2 Linux内核源码获取与编译

内核源码怎么拿?我建议直接从官方主线拉取,或者用芯片厂商提供的BSP包。比如树莓派就用raspberrypi/linux这个仓库。

git clone --depth=1 -b rpi-4.19.y https://github.com/raspberrypi/linux.git
cd linux
make bcm2709_defconfig
make -j4 zImage modules dtbs

这里bcm2709_defconfig是树莓派3的默认配置。你换成自己的板子,比如全志H3就用sun8i_defconfig。编译的时候加-j4,利用多核加速,不然等得你怀疑人生。

编译完成后,你会得到三个关键产物:

  • zImage:压缩后的内核镜像
  • *.ko:内核模块文件
  • *.dtb:设备树二进制文件

说白了,这三样东西就是驱动运行的“三驾马车”。少一个,你的GPU都跑不起来。

2.3 设备树(Device Tree)中GPU节点的配置

设备树,我刚开始接触时觉得它很玄乎。其实就是用文本描述硬件信息,告诉内核“我这里有个GPU,它挂在哪条总线上,中断号是多少,寄存器地址在哪”。

以Mali-400 GPU为例,设备树节点大概长这样:

gpu: gpu@1c40000 {
    compatible = "arm,mali-400", "arm,mali-utgard";
    reg = <0x01c40000 0x10000>;
    interrupts = <GIC_SPI 97 IRQ_TYPE_LEVEL_HIGH>,
                 <GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>,
                 <GIC_SPI 99 IRQ_TYPE_LEVEL_HIGH>,
                 <GIC_SPI 100 IRQ_TYPE_LEVEL_HIGH>;
    interrupt-names = "IRQGP", "IRQGPMMU", "IRQPP0", "IRQPPMMU0";
    clocks = <>;
    clock-names = "bus", "core";
};

这里有几个关键点:

  • compatible:驱动匹配的关键字符串,驱动里会用它来绑定设备。
  • reg:GPU的寄存器基地址和范围。别写错了,否则内核访问不到硬件。
  • interrupts:GPU的中断号。我见过有人把中断号写反,结果GPU一干活就死机。
小技巧: 调试设备树时,可以用dtc -I dtb -O dts反编译dtb文件,看看实际加载的配置对不对。我曾经靠这招抓到一个“地址偏移量少写了一个0”的bug。

2.4 根文件系统集成驱动模块

驱动编译成模块(.ko文件)后,怎么让它开机自动加载?你得把它放到根文件系统里。

我常用的方法是:

  1. 编译内核模块:make modules_install INSTALL_MOD_PATH=./rootfs
  2. 这会把所有模块安装到rootfs/lib/modules/$(KERNEL_VERSION)/目录下。
  3. 然后打包成根文件系统镜像,比如用mkfs.ext4或者genimage工具。

如果你用的是Buildroot或Yocto,那就更简单了。直接在配置里把GPU驱动选上,它们会自动帮你集成。

但手动集成时,有个坑:模块依赖关系。比如Mali驱动依赖drmdma-buf,你得确保这些模块也一起打包进去。否则insmod时会报“Unknown symbol”。

depmod -a -b ./rootfs $(KERNEL_VERSION)

运行depmod会生成modules.dep文件,告诉内核模块之间的依赖顺序。这一步很多人会忘,结果模块加载失败,还以为是驱动代码写错了。

总结一下: 环境搭建是驱动开发的地基。交叉编译工具链、内核源码、设备树、根文件系统,这四个环节环环相扣。任何一个地方出问题,你的GPU驱动都跑不起来。我建议你每做完一步,都验证一下——比如编译完内核先试试能不能启动,设备树改完先反编译看看语法对不对。别等到最后一步才发现问题,那排查起来就头大了。

好,这一章就到这里。下一章我们聊聊GPU驱动的核心数据结构,以及如何注册一个platform driver。到时候见。