设备树覆盖后Linux模块加载失败问题解析及方案
2024-12-24 21:20:09
设备树覆盖应用后 Linux 内核模块无法加载
在嵌入式Linux系统开发中,经常需要动态配置硬件,设备树覆盖 (device-tree overlay) 提供了一种灵活的方式。它允许在运行时修改设备的信息,无需重新编译整个内核。利用configfs
可以实现这一功能,通过修改配置项动态地应用设备树覆盖。一种常见的使用场景是将驱动模块和设备树配置关联起来。即:应用包含新设备信息的覆盖时,对应的驱动模块应当自动加载。但实践中,可能会遇到这样的问题:设备树覆盖应用后,驱动模块并没有自动加载,需要手动modprobe
才能工作。 这给开发者带来了困扰,需要仔细分析其背后的原因。
模块加载时序与依赖问题
内核模块的加载并非随意发生,依赖于内核中的多种机制。 udev
在设备树改变时会被触发,这看似应该直接加载驱动,实际上加载行为并非直接的udev行为。udev
会传递事件通知,促使其他子系统或脚本采取行动。设备树被覆盖应用后,内核会检测到新的设备,寻找匹配的驱动程序,驱动程序的匹配涉及modules.alias
和modules.order
。如果匹配机制存在问题,即使modules.alias
中定义了 compatible
字符串, 内核也不会加载。问题可能出在内核模块加载依赖的初始化次序中,设备树节点定义出现在模块初始化前,也会导致无法自动加载,或加载失败,出现如下现象:
modprobe sample-driver
可以正常加载。这表明模块本身没有问题,加载器本身能够找到模块并且正确解析。modules.alias
中有compatible
条目。表明内核有能力将当前节点和当前模块建立匹配。
解决思路与实践方案
要解决这个问题,需要深入了解模块加载机制和设备树管理的相关知识,需要一些更具体的调整手段,以下方案可供参考。
方案一: 强制重新加载驱动
一种比较直接的解决办法是:在应用设备树覆盖后,显式触发模块的重新扫描和加载。这通过使用udev
机制来达成。我们可以创建一个udev
规则,当设备树相关的设备被更新时,卸载所有与该 compatible
属性相关的所有模块,然后再通过modprobe加载它们。这种方案虽然简单,却需要配置udev
,维护多个相关的 udev
文件。 考虑到此文章是解决特定问题而不是一个通用的解决方案,为了控制复杂性,我们可以用脚本模拟此流程。
- 代码示例:
#!/bin/bash
MOD_NAME="sample-driver" #模块名称
COMPAT="sample-compat-string" #兼容字符串, 需要根据设备树定义修改
# 卸载所有使用该 compatible 字符串的模块
for mod in $(find /sys/module -name 'holders' | while read -r holders ;do grep "$COMPAT" "$holders"| awk '{print $1}'; done ); do
modprobe -r "$mod";
done
modprobe $MOD_NAME; #加载指定模块
- 操作步骤:
- 保存上述脚本为
load_module.sh
, 修改为实际的模块名称以及设备树覆盖文件中定义的兼容性字符串。 - 添加可执行权限:
chmod +x load_module.sh
。 - 在应用
configfs
配置后,运行这个脚本:sudo ./load_module.sh
。
- 保存上述脚本为
该脚本的原理为,首先利用find
找到所有正在使用的设备模块,然后根据他们的名字进行过滤,找出当前设备树overlay 需要使用compat string。最后强制卸载它们。 再次执行modprobe
,此时modprobe 根据最新配置再次匹配需要的驱动,确保能够正确加载模块。
方案二: 修改驱动的模块初始化次序
有些时候问题出在模块初始化时序上,可以在模块的初始化阶段设置模块在稍后时间被加载,从而确保其他设备信息已经完全初始化,此时使用 module init call.
- 代码示例: (以下示例需要修改你的驱动代码)
#include <linux/module.h>
#include <linux/init.h>
#include <linux/iio/iio.h>
#include <linux/of.h>
/* 定义 compatible string */
#define COMPATIBLE "sample-compat-string"
/*
* 这里假设 module 初始化的工作可以晚一点进行。如果一些全局变量需要在 early init 使用,
* 这样的做法则会带来问题。你需要自己仔细评估当前module 的生命周期
*/
static int __init sample_driver_late_init(void){
struct device_node *np;
np = of_find_compatible_node(NULL, NULL, COMPATIBLE);
if (np) {
pr_info("Found the device node by compatible");
} else {
pr_info("Did not found any device node by compatible %s\n", COMPATIBLE);
}
// 在此处加载iio 驱动相关模块,调用相应的api进行注册,略去
return 0;
}
module_init(sample_driver_late_init);
static void __exit sample_driver_exit(void)
{
//退出程序,相关模块注销。
return ;
}
module_exit(sample_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Sample Driver for Device-tree Overlay Issue");
MODULE_COMPATIBLE(COMPATIBLE);
- 操作步骤
- 根据设备树定义修改兼容性字符串(宏
COMPATIBLE
的值) - 编译此模块。注意重新编译驱动可能需要更新内核模块缓存或者再次运行
depmod
命令。 - 将新的内核模块安装到对应的模块目录中
/lib/modules/<version>/kernel/drivers/iio/dac/sample-driver.ko
。 - 应用
configfs
配置。
方案二,调整模块加载时间需要深入修改驱动本身,对驱动设计的依赖度较高,因此你需要结合你自身的情况选择。
额外提示
- 确认
modules.order
文件内容是否包含对应的模块路径。 - 尝试在
modprobe
中加入-vvv
参数进行详细日志查看, 能够获得模块加载更多的相关信息。 例如:modprobe -vvv sample-driver
.
选择方案时,请根据自身情况权衡不同方案的复杂性,找到最适合你项目的解决方案。 良好的调试习惯和详尽的日志分析可以帮助定位并解决此类问题。