一、bug现象描述
- 内核版本: Linux version 4.14.200
- OpenWRT-19.07
- 硬件平台:mtk7621a
在设备树中增加 hua_hand 的io设备,设备树内容如下:
gpio-export {
compatible = "gpio-export";
#size-cells = <0>;
modem-reset {
gpio-export,name = "modem-reset";
gpio-export,output = <1>;
gpios = <&gpio0 16 GPIO_ACTIVE_HIGH>;
};
//增加内容如下
hua-hand {
gpio-export,name = "hua-hand";
gpio-export,direction_may_change = <1>; //采用 direction_may_change 方式,sys/class/gpio/下可用看到io的direction。
gpios = <&gpio0 17 GPIO_ACTIVE_HIGH>;
};
};
二、源码中增加调试信息,日志内容
内核日志如下:
2021-07-23T15:20:58.496 - [ 8.036393] io scheduler noop registered
2021-07-23T15:20:58.512 - [ 8.044040] io scheduler deadline registered (default)
2021-07-23T15:20:58.527 - [ 8.054642] of_phandle_iterator_init, line:1166 , list_name:assigned-clock-parents,list:0.
// gpio 总数为 2 个
2021-07-23T15:20:58.543 - [ 8.071142] of_property_read_string, line:405, dev-name:modem-reset .
2021-07-23T15:20:58.559 - [ 8.083902] of_gpio_export_probe, line:553, name:modem-reset, max gpio num:2
// 注册第一个 gpio 设备名 modem-reset
2021-07-23T15:20:58.574 - [ 8.097918] __of_parse_phandle_with_args, line:1281, np.name:modem-reset, np.type:<NULL>, list_name:gpios, cells_names:#gpio-cells
2021-07-23T15:20:58.590 - [ 8.121402] of_phandle_iterator_init, line:1166 , list_name:gpios,list:-2128588704.
2021-07-23T15:20:58.606 - [ 8.136778] of_phandle_iterator_next, line:1191 , phandle_end:-2128588704,list_end:-2128588692
2021-07-23T15:20:58.621 - [ 8.154064] __of_parse_phandle_with_args, line:1292 , cur_index:0,index:0
2021-07-23T15:20:58.637 - [ 8.167706] of_get_named_gpiod_flags, line:83 , ret:0
2021-07-23T15:20:58.637 - [ 8.177908] of_get_named_gpiod_flags, line:92 , lable:1e000600.gpio
2021-07-23T15:20:58.652 - [ 8.190512] of_get_named_gpiod_flags, line:100 , desc:-1881307904
2021-07-23T15:20:58.668 - [ 8.202785] of_get_named_gpiod_flags, line:108 , desc:-1881307904
2021-07-23T15:20:58.684 - [ 8.215049] of_get_named_gpio_flags, line:123 , desc:-1881307904
//注册第二个 gpio 设备名还是 modem-reset, 而不是 hua_hand 设备名,相同设备名称注册失败。
2021-07-23T15:20:58.699 - [ 8.227308] __of_parse_phandle_with_args, line:1281, np.name:modem-reset, np.type:<NULL>, list_name:gpios, cells_names:#gpio-cells
2021-07-23T15:20:58.715 - [ 8.250704] of_phandle_iterator_init, line:1166 , list_name:gpios,list:-2128588704.
2021-07-23T15:20:58.730 - [ 8.266074] of_phandle_iterator_next, line:1191 , phandle_end:-2128588704,list_end:-2128588692
2021-07-23T15:20:58.746 - [ 8.283356] __of_parse_phandle_with_args, line:1292 , cur_index:0,index:1
2021-07-23T15:20:58.762 - [ 8.297000] __of_parse_phandle_with_args, line:1319 , cur_index:1
2021-07-23T15:20:58.777 - [ 8.309276] of_phandle_iterator_next, line:1191 , phandle_end:-2128588692,list_end:-2128588692
2021-07-23T15:20:58.793 - [ 8.326549] of_get_named_gpiod_flags, line:83 , ret:-2 //错误码 -2
2021-07-23T15:20:58.793 - [ 8.336918] of_get_named_gpio_flags, line:123 , desc:-2
2021-07-23T15:20:58.809 - [ 8.347455] of_gpio_export_probe,line:562, name:modem-reset,gpio number:-2 fail
2021-07-23T15:20:58.824 - [ 8.362279] gpio-export: probe of gpio-export failed with error -2
由此可见在 gpio_export_probe() 函数中,发生注册设备错误。问题的原因是设备重名造成的。
三、源码走读
of_gpio_export_probe 注册程序
static int of_gpio_export_probe(struct platform_device *pdev)
{
for_each_child_of_node(np, cnp) { // 遍历设备链表,读取树节点信息
of_property_read_string(cnp, "gpio-export,name", &name); //检查设备树是否正确
if (!name)
max_gpio = of_gpio_count(cnp); //获取gpio数量,从日志信息中可以看到此处设备总数为 2 个gpio
for (i = 0; i < max_gpio; i++) { //循环注册 GPIO 设备
unsigned flags = 0;
enum of_gpio_flags of_flags;
gpio = of_get_gpio_flags(cnp, i, &of_flags);
if (!gpio_is_valid(gpio))
return gpio;
if (of_flags == OF_GPIO_ACTIVE_LOW)
flags |= GPIOF_ACTIVE_LOW;
if (!of_property_read_u32(cnp, "gpio-export,output", &val))
flags |= val ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW;
else
flags |= GPIOF_IN;
if (devm_gpio_request_one(&pdev->dev, gpio, flags, name ? name : of_node_full_name(np)))
continue;
dmc = of_property_read_bool(cnp, "gpio-export,direction_may_change");
gpio_export_with_name(gpio, dmc, name);
nb++;
}
}
}
省略部分源码内容,通过日志可以看出,程序在注册时hua_hand时,设备名称为 modem-reset,设备名称重复导致注册失败。为什么会出现这个问题呢?
for_each_child_of_node(np, cnp) 源码走读
获取设备信息
/* 此函数负责gpio 树信息扫描, 和内存中IO信息的初始化 */
for_each_child_of_node(np, cnp)
// 此函数采用宏的方式定义,如下:
#define for_each_child_of_node(parent, child) \
for (child = of_get_next_child(parent, NULL); child != NULL; \
child = of_get_next_child(parent, child))
// 函数调用关系如下
of_get_next_child(parent, child));
__of_get_next_child(node, prev);
//最终调用的函数如下
static struct device_node *__of_get_next_child(const struct device_node *node,
struct device_node *prev)
{
struct device_node *next;
if (!node)
return NULL;
next = prev ? prev->sibling : node->child;
for (; next; next = next->sibling)
if (of_node_get(next))
break;
of_node_put(prev); // 获取信息送入next 节点
return next;
}
此函数在遍历设备node节点链表,由此看出 of_gpio_export_probe() 函数,设备的信息是在设备节点遍历时更新,而设备总数为2连续注册,gpio设备节点信息并没有更新。因此设备注册会失败。
四、解决bug方法
源码修改如下
static int of_gpio_export_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct device_node *cnp;
u32 val;
int nb = 0;
for_each_child_of_node(np, cnp) {
const char *name = NULL;
int gpio;
bool dmc;
int max_gpio = 1;
int i;
of_property_read_string(cnp, "gpio-export,name", &name);
if (!name)
max_gpio = of_gpio_count(cnp);
else //增加的码内容,起点
max_gpio = 1;
printk(KERN_ERR "%s, line:%d, name:%s, max gpio num:%d\n", __func__, __LINE__, name, max_gpio);
//增加的码内容,结束
for (i = 0; i < max_gpio; i++) {
unsigned flags = 0;
enum of_gpio_flags of_flags;
gpio = of_get_gpio_flags(cnp, i, &of_flags);
if (!gpio_is_valid(gpio)){
return gpio;
}
if (of_flags == OF_GPIO_ACTIVE_LOW)
flags |= GPIOF_ACTIVE_LOW;
if (!of_property_read_u32(cnp, "gpio-export,output", &val))
flags |= val ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW;
else
flags |= GPIOF_IN;
if (devm_gpio_request_one(&pdev->dev, gpio, flags, name ? name : of_node_full_name(np)))
continue;
dmc = of_property_read_bool(cnp, "gpio-export,direction_may_change");
gpio_export_with_name(gpio, dmc, name);
nb++;
}
}
dev_info(&pdev->dev, "%d gpio(s) exported\n", nb);
return 0;
}
五、验证结果
注册成功日志内容
2021-07-23T16:03:15.124 - [ 8.046305] io scheduler noop registered
2021-07-23T16:03:15.124 - [ 8.053953] io scheduler deadline registered (default)
2021-07-23T16:03:15.155 - [ 8.064460] of_phandle_iterator_init, line:1166 , list_name:assigned-clock-parents,list:0.
//注册 modem-reset io设备
2021-07-23T16:03:15.155 - [ 8.081056] of_property_read_string, line:405, dev-name:modem-reset .
2021-07-23T16:03:15.171 - [ 8.093724] of_gpio_export_probe, line:553, name:modem-reset, max gpio num:1
2021-07-23T16:03:15.202 - [ 8.107721] __of_parse_phandle_with_args, line:1281, np.name:modem-reset, np.type:<NULL>, list_name:gpios, cells_names:#gpio-cells
2021-07-23T16:03:15.218 - [ 8.131224] of_phandle_iterator_init, line:1166 , list_name:gpios,list:-2128588704.
2021-07-23T16:03:15.233 - [ 8.146600] of_phandle_iterator_next, line:1191 , phandle_end:-2128588704,list_end:-2128588692
2021-07-23T16:03:15.249 - [ 8.163886] __of_parse_phandle_with_args, line:1292 , cur_index:0,index:0
2021-07-23T16:03:15.249 - [ 8.177532] of_get_named_gpiod_flags, line:83 , ret:0
//注册 hua_hand io设备
2021-07-23T16:03:15.312 - [ 8.237129] of_property_read_string, line:405, dev-name:hua-hand .
2021-07-23T16:03:15.327 - [ 8.249290] of_gpio_export_probe, line:553, name:hua-hand, max gpio num:1
2021-07-23T16:03:15.358 - [ 8.262756] __of_parse_phandle_with_args, line:1281, np.name:hua-hand, np.type:<NULL>, list_name:gpios, cells_names:#gpio-cells
2021-07-23T16:03:15.374 - [ 8.285748] of_phandle_iterator_init, line:1166 , list_name:gpios,list:-2128588620.
2021-07-23T16:03:15.390 - [ 8.301121] of_phandle_iterator_next, line:1191 , phandle_end:-2128588620,list_end:-2128588608
2021-07-23T16:03:15.405 - [ 8.318408] __of_parse_phandle_with_args, line:1292 , cur_index:0,index:0
2021-07-23T16:03:15.405 - [ 8.332051] of_get_named_gpiod_flags, line:83 , ret:0
// gpio 注册设备总数 2
2021-07-23T16:03:15.468 - [ 8.391613] gpio-export gpio-export: 2 gpio(s) exported
在目标机上采用如下命令查看注册的设备内容。
$ ls /sys/class/gpio/
//可见设备名称
此部分还需要采用内核补丁的方式,制作补丁文件。请参考《OpenWRT 的 linux内核patch方法》
等忙过这段时间,到内核社区看看这个版本bug修复情况,如果有机会也可以为内核贡献点力量。