内核配置


内核版本:Linux version 4.9.56

make ARCH=arm64 menuconfig
Device Drivers  --->
	Input device support  --->     
		[*]   Keyboards  --->
			<*>   GPIO Buttons

配置文件


sys_config.fex
;----------------------------------------------------------------------------------
;gpio-keys parameters
;----------------------------------------------------------------------------------
[gpio-keys]
compatible = "gpio-keys";

[gpio-keys/up]
linux,code = 103
linux,input-type = 1
gpios = port:PH11<6><default><default><default>

[gpio-keys/down]
linux,code = 108
linux,input-type = 1
gpios = port:PH08<0><default><default><default>

[gpio-keys/enter]
linux,code = 28
linux,input-type = 1
gpios = port:PH10<6><default><default><default>

内存越界


日志信息

[    2.868360] of_get_named_gpiod_flags: parsed 'gpios' property of node '/soc@01c00000/gpio-keys/up[0]' - status (0)
[    2.868366] of_get_gpio_flags button->gpio:235...
[    2.868393] of_get_named_gpiod_flags: parsed 'gpios' property of node '/soc@01c00000/gpio-keys/down[0]' - status (0)
[    2.868396] of_get_gpio_flags button->gpio:232...
[    2.868417] of_get_named_gpiod_flags: parsed 'gpios' property of node '/soc@01c00000/gpio-keys/enter[0]' - status (0)
[    2.868420] of_get_gpio_flags button->gpio:234...
[    2.869024] input: gpio-keys as /devices/platform/soc/gpio-keys/input/input3
[    2.869369] Kernel panic - not syncing: stack-protector: Kernel stack is corrupted in: ffffff8008600ce0
[    2.869369] 
[    2.869380] CPU: 1 PID: 1 Comm: swapper/0 Not tainted 4.9.56 #165
[    2.869383] Hardware name: sun50iw1 (DT)
[    2.869388] Call trace:
[    2.869409] [<ffffff800808a7d4>] dump_backtrace+0x0/0x22c
[    2.869417] [<ffffff800808aa24>] show_stack+0x24/0x30
[    2.869430] [<ffffff80083c9a64>] dump_stack+0x8c/0xb0
[    2.869438] [<ffffff80081a5960>] panic+0x14c/0x298
[    2.869450] [<ffffff80080a19d0>] print_tainted+0x0/0xa8
[    2.869463] [<ffffff8008600ce0>] gpio_keys_probe+0x6d0/0x804
[    2.869474] [<ffffff80084b3e9c>] platform_drv_probe+0x60/0xac
[    2.869481] [<ffffff80084b1a6c>] driver_probe_device+0x1b8/0x3d4
[    2.869485] SMP: stopping secondary CPUs
[    2.877351] Kernel Offset: disabled
[    2.877354] Memory Limit: none

解决补丁

diff --git a/drivers/input/keyboard/gpio_keys.c b/drivers/input/keyboard/gpio_keys.c
old mode 100644
new mode 100755
index 9b8079c..04e1580
--- a/drivers/input/keyboard/gpio_keys.c
+++ b/drivers/input/keyboard/gpio_keys.c
@@ -32,6 +32,7 @@
 #include <linux/of_gpio.h>
 #include <linux/of_irq.h>
 #include <linux/spinlock.h>
+#include <linux/sunxi-gpio.h>
 
 struct gpio_button_data {
        const struct gpio_keys_button *button;
@@ -664,11 +665,11 @@ static void gpio_keys_close(struct input_dev *input)
 
        i = 0;
        for_each_available_child_of_node(node, pp) {
-               enum of_gpio_flags flags;
+               struct gpio_config gpio_flags;
 
                button = &pdata->buttons[i++];
 
-               button->gpio = of_get_gpio_flags(pp, 0, &flags);
+               button->gpio = of_get_gpio_flags(pp, 0, (enum of_gpio_flags *)&gpio_flags);
                if (button->gpio < 0) {
                        error = button->gpio;
                        if (error != -ENOENT) {
@@ -679,7 +680,7 @@ static void gpio_keys_close(struct input_dev *input)
                                return ERR_PTR(error);
                        }
                } else {
-                       button->active_low = flags & OF_GPIO_ACTIVE_LOW;
+                       button->active_low = gpio_flags.data & OF_GPIO_ACTIVE_LOW;
                }
 
                button->irq = irq_of_parse_and_map(pp, 0);

原因分析

  1. gpio_keys 驱动使用 of_get_gpio_flags() 获取 dts 里面 gpio 配置信息。
  2. 但是 of_get_gpio_flags() 传入 enum of_gpio_flags 类型来获取配置信息。
  3. of_get_gpio_flags() 的最终实现由具体的 SOC 厂商实现,这里是全志厂商实现。
  4. 实现的函数为:drivers/pinctrl/sunxi/pinctrl-sunxi.c --> sunxi_pinctrl_gpio_of_xlate()。
  5. 在 sunxi_pinctrl_gpio_of_xlate() 却是通过强制转换 struct gpio_config 类型存储 gpio 配置信息。
  6. enum of_gpio_flags 占用 4 字节,而 struct gpio_config 占用 20 字节,出现内存越界操作的问题。
// include/linux/of_gpio.h
enum of_gpio_flags {		
	OF_GPIO_ACTIVE_LOW = 0x1,
	OF_GPIO_SINGLE_ENDED = 0x2,
}; // 4Byte

// include/linux/sunxi-gpio.h
struct gpio_config {
	u32	gpio;
	u32	mul_sel;
	u32	pull;
	u32	drv_level;
	u32	data;
}; // 20Byte

// drivers/pinctrl/sunxi/pinctrl-sunxi.c
static int sunxi_pinctrl_gpio_of_xlate(struct gpio_chip *gc,
				const struct of_phandle_args *gpiospec,
				u32 *flags)
{
	struct gpio_config *config;
	int pin, base;

	base = PINS_PER_BANK * gpiospec->args[0];
	pin = base + gpiospec->args[1];
	pin = pin - gc->base;
	if (pin > gc->ngpio)
		return -EINVAL;

	if (flags) {
		// 问题出在这个条件下面的赋值语句
		// 传进来的是 enum of_gpio_flags,只有 4Byte 
		// 结果使用的 struct gpio_config,却有 20Byte 
		config = (struct gpio_config *)flags;
		config->gpio = base + gpiospec->args[1];
		config->mul_sel = gpiospec->args[2];
		config->drv_level = gpiospec->args[3];
		config->pull = gpiospec->args[4];
		config->data = gpiospec->args[5];
	}

	return pin;
}