第二章 驱动加载与初始化故障:那些让我头疼的启动问题
驱动加载失败,说白了就是GPU还没开始干活就罢工了。这类问题在嵌入式开发中特别常见,我这些年调试过的板子,十块里有七八块都栽在这个环节。今天咱们就来聊聊驱动模块加载失败、设备树匹配错误、Firmware加载超时这几个老大难问题。
2.1 驱动模块加载失败
先说说驱动模块加载失败。这个问题其实分两种:一种是模块根本加载不进去,另一种是加载进去了但初始化到一半就崩了。
典型现象:insmod 或 modprobe 后,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」——这个我见得更多。驱动里调用了某个内核函数,但内核没导出这个符号。为什么会这样?可能是内核配置没开对应的功能,也可能是驱动版本太老。
/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 "——多了个空格!就这一个空格,折腾了我整整两天。
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 综合排查流程
遇到驱动加载初始化问题,我一般按这个顺序排查:
- 看 dmesg——这是第一手信息,错误原因基本都在这里
- 确认内核配置——检查
/boot/config-$(uname -r),看 GPU 相关选项是否开启 - 检查设备树——反编译设备树,确认节点和驱动匹配
- 验证 firmware——确认文件存在、路径正确、md5sum 一致
- 硬件确认——量电压、看时钟、测复位——虽然这是硬件工程师的活,但有时候驱动加载失败就是硬件没准备好
你想想看,这些步骤走下来,90%的问题都能定位到。剩下的10%,嗯,那就得靠经验和运气了。我记得有一次,驱动加载失败是因为电源管理芯片的时序不对,GPU 上电晚了那么几毫秒,导致初始化时寄存器读出来全是 0xFF。这种问题,光看软件日志是看不出来的。
好了,这一章的内容就到这里。驱动加载初始化是GPU驱动调试的第一道坎,跨过去之后,后面的事情就好办多了。下一章咱们聊聊运行时故障,那才是真正考验功力的时候。