第二章 驱动加载与初始化故障:那些让我头疼的启动问题

驱动加载失败,说白了就是GPU还没开始干活就罢工了。这类问题在嵌入式开发中特别常见,我这些年调试过的板子,十块里有七八块都栽在这个环节。今天咱们就来聊聊驱动模块加载失败、设备树匹配错误、Firmware加载超时这几个老大难问题。

2.1 驱动模块加载失败

先说说驱动模块加载失败。这个问题其实分两种:一种是模块根本加载不进去,另一种是加载进去了但初始化到一半就崩了。

典型现象insmodmodprobe 后,dmesg 里出现类似这样的错误:

[   12.345678] gpu: disagrees about version of symbol module_layout
[   12.345679] gpu: Unknown symbol drm_gem_object_lookup (err -2)
[   12.345680] gpu: loading out-of-tree module taints kernel.

嗯,这里要注意。第一个错误「disagrees about version」——说白了就是内核版本不匹配。我遇到过最坑的一次,客户拿了个3.10内核的驱动,硬要往4.19内核上装,结果就是各种符号版本对不上。

排查思路

  • 先确认内核版本:uname -r
  • 检查驱动编译时的内核头文件版本是否一致
  • /lib/modules/$(uname -r)/build 是否存在

第二个错误「Unknown symbol」——这个我见得更多。驱动里调用了某个内核函数,但内核没导出这个符号。为什么会这样?可能是内核配置没开对应的功能,也可能是驱动版本太老。

我的小技巧:遇到 Unknown symbol,先查 /proc/kallsyms 看看这个符号到底在不在内核里。如果不在,八成是内核配置问题。

2.2 设备树匹配错误

设备树匹配错误,这个坑我踩过好几次。嵌入式GPU驱动通常通过设备树(Device Tree)来匹配硬件。驱动里会写一个 of_match_table,内核根据这个表去和设备树里的节点匹配。

举个例子,驱动里可能是这样写的:

static const struct of_device_id gpu_of_match[] = {
    { .compatible = "vendor,gpu-v1" },
    { .compatible = "vendor,gpu-v2" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, gpu_of_match);

static struct platform_driver gpu_driver = {
    .probe  = gpu_probe,
    .driver = {
        .name = "gpu",
        .of_match_table = gpu_of_match,
    },
};

而设备树里可能是这样:

gpu: gpu@12340000 {
    compatible = "vendor,gpu-v1";
    reg = <0x0 0x12340000 0x0 0x10000>;
    interrupts = <0 42 4>;
};

匹配不上,最常见的原因就是字符串写错了。我曾经排查过一个案子,驱动里写的是 "vendor,gpu-v1",设备树里写的是 "vendor,gpu-v1 "——多了个空格!就这一个空格,折腾了我整整两天。

避坑指南:我曾经因为设备树里 compatible 字符串大小写不一致,导致驱动加载失败。建议用 dtc -I dtb -O dts 反编译设备树,肉眼对比字符串。

其他常见原因

  • 设备树节点 status = "disabled"——这个我见过好几次,明明硬件好好的,设备树里给禁用了
  • reg 地址或大小写错了——导致驱动 probe 时 ioremap 失败
  • 中断号配置错误——probe 时 request_irq 失败

2.3 Firmware加载超时

Firmware加载超时,这个问题在GPU驱动里特别常见。GPU通常需要加载一个微码(firmware)才能正常工作。加载过程一般是这样的:

static int gpu_load_firmware(struct gpu_device *gdev)
{
    const struct firmware *fw;
    int ret;

    ret = request_firmware(&fw, "gpu_fw.bin", &gdev->dev);
    if (ret) {
        dev_err(&gdev->dev, "Failed to load firmware: %d\n", ret);
        return ret;
    }

    // 将 firmware 写入 GPU 的 SRAM
    gpu_write_fw_to_sram(gdev, fw->data, fw->size);

    release_firmware(fw);
    return 0;
}

超时通常发生在 request_firmware 这一步。为什么?

原因分析

  • firmware 文件不存在或路径不对——内核默认在 /lib/firmware 下找
  • 文件系统还没挂载——如果根文件系统在 SD 卡或网络文件系统上,加载时机太早就会超时
  • firmware 文件损坏——我遇到过下载过程中断导致文件不完整的情况

我个人习惯在调试阶段,先把 firmware 编译进内核(用 CONFIG_EXTRA_FIRMWARE),这样就不用担心文件系统的问题了。

调试技巧:在 request_firmware 前后加打印,看超时具体发生在哪一步。也可以用 firmware_class 的调试接口:

echo 1 > /sys/module/firmware_class/parameters/debug

2.4 综合排查流程

遇到驱动加载初始化问题,我一般按这个顺序排查:

  1. 看 dmesg——这是第一手信息,错误原因基本都在这里
  2. 确认内核配置——检查 /boot/config-$(uname -r),看 GPU 相关选项是否开启
  3. 检查设备树——反编译设备树,确认节点和驱动匹配
  4. 验证 firmware——确认文件存在、路径正确、md5sum 一致
  5. 硬件确认——量电压、看时钟、测复位——虽然这是硬件工程师的活,但有时候驱动加载失败就是硬件没准备好

你想想看,这些步骤走下来,90%的问题都能定位到。剩下的10%,嗯,那就得靠经验和运气了。我记得有一次,驱动加载失败是因为电源管理芯片的时序不对,GPU 上电晚了那么几毫秒,导致初始化时寄存器读出来全是 0xFF。这种问题,光看软件日志是看不出来的。

我的建议:调试驱动加载问题时,别只盯着软件。有时候硬件的一个小问题,在软件层面会表现出完全不同的症状。多和硬件工程师聊聊,往往能少走很多弯路。

好了,这一章的内容就到这里。驱动加载初始化是GPU驱动调试的第一道坎,跨过去之后,后面的事情就好办多了。下一章咱们聊聊运行时故障,那才是真正考验功力的时候。