【ZYNQ Linux开发】gpio子系统相关驱动先于Xgpio注册完成而加载失败的问题分析与探究
1 问题描述
最近在开发过程中,想把一个涉及按键的驱动编译进内核中,让内核在启动时,自动加载,但是 Linux 系统启动以后吗,尝试使用驱动发现并无 key 设备,但在Linux系统完全启动后,通过insmod命令去加载对应代码编译出的模块 .ko 文件,却能够成功加载。查看内核的启动日志,结果如下(中间的那一条打印是后续打印时添加的):
对应驱动初始化部分的代码如下(只保留关键部分):
struct device_node *nd;
const char *str;
int ret;printk("mykey_init succeed in kernel!\n");/* 获取key节点 */
nd = of_find_node_by_path("/key");
if(NULL == nd) {printk(KERN_ERR "key: Failed to get key node\n");return -EINVAL;
}/* 读取status属性 */
ret = of_property_read_string(nd, "status", &str);
if(!ret) {if (strcmp(str, "okay"))return -EINVAL;
}/* 获取compatible属性值并进行匹配 */
ret = of_property_read_string(nd, "compatible", &str);
if(ret)return ret;if (strcmp(str, "my_key")) {printk(KERN_ERR "key: Compatible match failed\n");return -EINVAL;
}/* 获取设备树中的key-gpio属性,得到按键的GPIO编号 */
key.key_gpio = of_get_named_gpio(nd, "key-gpio", 0);
printk(KERN_INFO "key: Got GPIO num = %d\n", key.key_gpio); // 打印实际获取的值
if(!gpio_is_valid(key.key_gpio)) {printk(KERN_ERR "key: Failed to get key-gpio\n");return -EINVAL;
}
说明从设备树获取gpio子系统的相关信息出了问题。
2 问题分析与探究
由以上代码,结合在 uboot 中使用 boot.scr 成功加载了设备树文件,并有以下日志记录:
[ 0.000000] efi: Getting EFI parameters from FDT:
可以分析得知,设备树确实在 uboot 中成功加载。但错误信息的打印发生在获取设备树中的 key-gpio 属性,而前面获取 key 节点、读取 status 和 compatible 属性均没有问题,可以进一步说明不是设备树的问题。
进而,本着哪里出错,就把对应地方的错误返回值打印出来的原则,在驱动中添加了对应的打印。得到的打印结果也如 1 中所示,为 -517,代表的是宏EPROBE_DEFER,说明用于设备驱动探测过程中的依赖资源未就绪。而我这里所使用的资源,便是 GPIO 资源。
这时,继续分析系统日志,发现:
系统对于驱动的加载发生在 3.255697s,而 XGPIO 控制器注册完成却是发生在 3.310812s,也就是在驱动加载之后!
进一步佐证了错误码 -517 (EPROBE_DEFER)打印的问题。
既然如此,那就考虑怎么把驱动的加载放在 XGPIO 控制器注册之后!
在此之前,我在驱动中对于驱动入口函数使用以下形式进行声明:
module_init(mykey_init);
那么如果换成以下形式:
late_initcall(mykey_init);
使用最迟的初始化呢?很遗憾,问题仍然存在。
那再尝试了一个 Deepseeker 提供的方法:在驱动中加入阻塞判断,在确保所有设备都 probe:
/* 获取设备树中的key-gpio属性,得到按键的GPIO编号 */
key.key_gpio = of_get_named_gpio(nd, "key-gpio", 0);
printk(KERN_INFO "key: Got GPIO num = %d\n", key.key_gpio); // 打印实际获取的值
if (key.key_gpio == -EPROBE_DEFER) {wait_for_device_probe();key.key_gpio = of_get_named_gpio(nd, "key-gpio", 0); // 重试
}if(!gpio_is_valid(key.key_gpio)) {printk(KERN_ERR "key: Failed to get key-gpio\n");return -EINVAL;
}
但也没有效果。调试到这,似乎就陷入了死局,**难道必须要编译为 .ko 模块且启动后用命令加载才能成功地加载驱动?**作为非常成熟的系统,Linux必不可能是这个样子。
没有思路,就从头开始分析,一步步看看我是如何把对应的驱动代码放进内核源码,再进而编译进内核的。
1,首先,由于是与 GPIO 相关的代码,我直接就把它放在了 内核源码根目录/drivers/gpio 中:
2,然后就是修改这个文件夹的 Makefile,在尾部添加:
obj-$(CONFIG_GPIO_ASYNCNOTI) += asyncnoti.o
3,最后在这个文件夹的 Kconfig 中添加:
config GPIO_ASYNCNOTItristate "GPIO Async notice kernel test"default yhelpSay Y to enable the async notice driver for kernel test.
对比其他人的方法,仔细去核对了一下步骤和内容,都没有发现有什么问题。
这时突发奇想,因为我的驱动用到了 GPIO 子系统,我才把它放在了 /gpio 中,但有没有一种可能,对于Linux系统来说,它会先加载这里的所有驱动,最后再初始化 Xgpio 呢?如果是这样,那肯定我们前面所有的把驱动加载的时候延后的尝试都是徒劳的!
直接实验一下就好了,这次,我把驱动源文件放在了/drivers/leds 中,采用与上面相同的方法编译内核,再使用板子启动。接着,就成功加载了!
想想也是够离谱的,不过,还没有完全结束。这时我的驱动,初始化宏还是使用了 late_initcall ,也还保留了上面的阻塞操作确保所有 probe 完成,那如果把它们都改回最初状态(使用 module_init 声明,不使用阻塞等待)呢?结果发现,也是没有问题的。
那到底是哪个起了作用呢?经过测试,用wait_for_device_probe(); 等待所有设备匹配完成是不必要的,但是对驱动的入口函数声明为最晚初始化是必须的。
3总结
通过以上分析,问题的关键就很明了了。就跟我前面的猜测一样,对于Linux系统来说,它会先加载 drivers/gpio 所有驱动,最后再初始化 Xgpio!
具体的解决方法为:
1,而我们的驱动如果涉及 gpio 子系统,则不要把源文件放到 drivers/gpio 这个文件夹中。
2,在驱动的入口函数声明采用最晚初始化:
late_initcall(mykey_init);