• 【分析笔记】全志平台 gpio_wdt 驱动应用和 stack crash 解决


    使用说明


    第一次遇到看门狗芯片是通过切换电平信号来喂狗,如 SGM706 芯片,之前也比较少会用到看门狗芯片。原本打算参考 sunxi-wdt.c 的框架,利用定时器自己写一个,无意中发现内核已经有 gpio_wdt.c 驱动程序,其原理也是通过内核定时器实现喂狗。因其使用了 of_get_gpio_flags() 接口获取 GPIO 信息,和 gpio-keys.c 驱动一样,该接口存在内存越界的问题,需要略作修改才能使用。

    内核配置


    内核版本:Linux 4.9

    make ARCH=arm64 menuconfig

    Device Drivers  --->
    	[*] Watchdog Timer Support  --->
    		<*>   LED Support for GPIO connected LEDs 
    			<*>   Watchdog device controlled through GPIO-line
    
    • 1
    • 2
    • 3
    • 4

    配置文件


    sys_config.fex

    全志平台便捷方式配置,也可以使用通用的 dts 配置方式

    ;----------------------------------------------------------------------------------
    ;gpio-wdt parameters
    ;compatible: 匹配平台驱动
    ;hw_algo: 清除看门狗计数方式:切换方式(toggle)或脉冲方式(level)
    ;hw_margin_ms: 看门狗电路会触发复位的最长时间(毫秒),不能小于 2 或者大于 65535
    ;always-running: 如果看门狗不能关闭,使能后驱动默认会自动喂狗,在应用层调用 STOP 接口不会执行关闭
    ;gpios: 连接看门狗芯片 WDI 的引脚
    ;----------------------------------------------------------------------------------
    [wdt-gpio]
    compatible = "linux,wdt-gpio"
    hw_algo = "level"
    hw_margin_ms = 1600
    always-running = "true"
    gpios = port:PL12<1><default><default><default>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    配置选项说明:linux-4.9\Documentation\devicetree\bindings\watchdog\gpio_wdt.txt

    * GPIO-controlled Watchdog
    
    Required Properties:
    - compatible: Should contain "linux,wdt-gpio".
    - gpios: From common gpio binding; gpio connection to WDT reset pin.
    - hw_algo: The algorithm used by the driver. Should be one of the
      following values:
      - toggle: Either a high-to-low or a low-to-high transition clears
        the WDT counter. The watchdog timer is disabled when GPIO is
        left floating or connected to a three-state buffer.
      - level: Low or high level starts counting WDT timeout,
        the opposite level disables the WDT. Active level is determined
        by the GPIO flags.
    - hw_margin_ms: Maximum time to reset watchdog circuit (milliseconds).
    
    Optional Properties:
    - always-running: If the watchdog timer cannot be disabled, add this flag to
      have the driver keep toggling the signal without a client. It will only cease
      to toggle the signal when the device is open and the timeout elapsed.
    
    Example:
    	watchdog: watchdog {
    		/* ADM706 */
    		compatible = "linux,wdt-gpio";
    		gpios = <&gpio3 9 GPIO_ACTIVE_LOW>;
    		hw_algo = "toggle";
    		hw_margin_ms = <1600>;
    	};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    内存越界


    日志信息

    [    3.727333] sunxi-wdt 1c20ca0.watchdog: Watchdog enabled (timeout=16 sec, nowayout=0)
    [    3.736874] of_get_named_gpiod_flags: parsed 'gpios' property of node '/soc@01c00000/wdt-gpio[0]' - status (0)
    [    3.748392] Kernel panic - not syncing: stack-protector: Kernel stack is corrupted in: ffffff8008655044
    [    3.748392] 
    [    3.760564] CPU: 2 PID: 1 Comm: swapper/0 Not tainted 4.9.56 #179
    [    3.767382] Hardware name: sun50iw1 (DT)
    [    3.771771] Call trace:
    [    3.774522] [] dump_backtrace+0x0/0x22c
    [    3.780566] [] show_stack+0x24/0x30
    [    3.786226] [] dump_stack+0x8c/0xb0
    [    3.791880] [] panic+0x14c/0x298
    [    3.797247] [] print_tainted+0x0/0xa8
    [    3.803099] [] gpio_wdt_probe+0x230/0x258
    [    3.809338] [] platform_drv_probe+0x60/0xac
    [    3.815770] [] driver_probe_device+0x1b8/0x3d4
    [    3.822493] [] __driver_attach+0x94/0x108
    [    3.828729] [] bus_for_each_dev+0x88/0xc8
    [    3.834964] [] driver_attach+0x30/0x3c
    [    3.840908] [] bus_add_driver+0xf8/0x24c
    [    3.847046] [] driver_register+0x9c/0xe8
    [    3.853186] [] __platform_driver_register+0x5c/0x68
    [    3.860400] [] gpio_wdt_driver_init+0x18/0x20
    [    3.867026] [] do_one_initcall+0xb0/0x14c
    [    3.873267] [] kernel_init_freeable+0x14c/0x1e8
    [    3.880093] [] kernel_init+0x18/0x104
    [    3.885941] [] ret_from_fork+0x10/0x40
    [    3.891884] SMP: stopping secondary CPUs
    [    3.896276] Kernel Offset: disabled
    [    3.900178] Memory Limit: none
    [    3.909173] Rebooting in 5 seconds..
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    解决补丁

    --- linux-4.9\drivers\watchdog\gpio_wdt.c	2022-09-13 17:51:57.000000000 +0800
    +++ linux-4.9\drivers\watchdog\gpio_wdt.c	2022-09-13 17:51:37.000000000 +0800
    @@ -12,12 +12,13 @@
     #include 
     #include 
     #include 
     #include 
     #include 
     #include 
    +#include <linux/sunxi-gpio.h>
     
     #define SOFT_TIMEOUT_MIN	1
     #define SOFT_TIMEOUT_DEF	60
     #define SOFT_TIMEOUT_MAX	0xffff
     
     enum {
    @@ -138,29 +139,29 @@
     	.set_timeout	= gpio_wdt_set_timeout,
     };
     
     static int gpio_wdt_probe(struct platform_device *pdev)
     {
     	struct gpio_wdt_priv *priv;
    -	enum of_gpio_flags flags;
    +	struct gpio_config gpio_flags;
     	unsigned int hw_margin;
     	unsigned long f = 0;
     	const char *algo;
     	int ret;
     
     	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
     	if (!priv)
     		return -ENOMEM;
     
     	platform_set_drvdata(pdev, priv);
     
    -	priv->gpio = of_get_gpio_flags(pdev->dev.of_node, 0, &flags);
    +	priv->gpio = of_get_gpio_flags(pdev->dev.of_node, 0, (enum of_gpio_flags *)&gpio_flags);
     	if (!gpio_is_valid(priv->gpio))
     		return priv->gpio;
     
    -	priv->active_low = flags & OF_GPIO_ACTIVE_LOW;
    +	priv->active_low = gpio_flags.data & OF_GPIO_ACTIVE_LOW;
     
     	ret = of_property_read_string(pdev->dev.of_node, "hw_algo", &algo);
     	if (ret)
     		return ret;
     	if (!strcmp(algo, "toggle")) {
     		priv->hw_algo = HW_ALGO_TOGGLE;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    原因分析

    1. gpio_wdt 驱动使用 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
  • 相关阅读:
    【数据结构】面试OJ题——时间复杂度
    01-国产MCU兆易GD32实现矩阵按键扫描
    GCC 参数详解
    常用的一些vscode前端插件
    【LeetCode】49. 字母异位词分组
    什么是网段
    python——列表(数组)
    [RoarCTF 2019]Simple Upload
    电商数仓项目中各层的表
    java技术专家面试指南80问【java学习+面试宝典】(九)
  • 原文地址:https://blog.csdn.net/lovemengx/article/details/126900961