灯的抢脚见过了。今天看外部看门狗:软件卡死时它负责把整机拉起来,缺一行配置会变成隔几分钟静默重启。

桌面起来了,什么都没做,大约三分钟整板复位。串口没有 panic,没有 sysrq,没有 reboot: Restarting system。U-Boot 又从头跑。这种「静默复位」不是内核内部 dw_wdt 的 44 秒,是底板上那颗 MAX6369 在拉 RESET。

MAX6369 是外部芯片。WDI 接到 GPIO0_C2。内核要用 gpio-wdt 按芯片要求的方式翻转这根脚,芯片才认为「还活着」。设备树里 必须 有 hw_algo = "toggle"。缺这一行,gpio_wdt_probe() 读属性失败,返回 -EINVAL,也就是 -22,驱动不绑定,没人喂 WDI。芯片按自己的超时把整板复位。这块板拨码默认档,现场测到大约 188 秒

拨码拨到「接入看门狗」,故意把应用挂死,超过 4 分钟不复位,才说明内核在喂。关拨码等于把芯片电气上拿掉,再挂 10 分钟不复位也不能证明驱动好。

产品 rk3568_evb

device/board/rk/rk3568_evb/kernel/rk3568-evb-linux.dts

官方流程图是「打开设备 → 设超时 → 周期喂狗 → 关掉」。外部这颗在 always-running 下,内核 probe 成功就开始喂,用户态不必打开 /dev/watchdog。图上的 open/ioctl 路径留给内部 dw_wdt,或者留给你故意去掉 always-running、改成用户态守护的演示。


1. 外部芯片和内部控制器不是同一条狗

RK3568 内部有 dw_wdt,会出 /dev/watchdog0 一类节点,超时常见 44 秒,而且常常被 watchdog_service 喂着。你看到 /dev/watchdog 存在,不能说明 MAX6369 在工作。

外部 MAX6369:

内核 gpio-wdt ──toggle──► GPIO0_C2 (WDI)
                             │
                        MAX6369
                             │ RESET 拉 SoC

特征:

  • 整板复位,dmesg 来不及留下「我要重启了」
  • 间隔大约 188 秒(随拨码超时档变,不是 dw_wdt 的 44)
  • dmesg | grep gpio-wdt 若有 probe ... error -22,就是没绑上
hdc shell "ls -l /dev/watchdog*"
hdc shell "cat /sys/class/watchdog/watchdog*/identity"
hdc shell "dmesg | grep -iE 'gpio-wdt|wdt-gpio|watchdog'"

identity 里能看到 GPIO Watchdog 才是这颗。dw_wdt 是另一条。板上可能同时有三个节点 watchdogwatchdog0watchdog1,按 identity 认,不要按序号猜。

拨码在底板,丝印常是 WDT ON/OFF。关掉之后 WDI 悬空或芯片不参与复位,软件怎么配都不会 188 秒死一次。验收必须拨到开。

Linux 编号:GPIO0_C2 = 0×32 + 16 + 2 = 18

hdc shell "cat /sys/kernel/debug/gpio" | grep gpio-18

绑上之后 consumer 是 wdt-gpio 或 gpio-wdt


2. 驱动认什么属性

drivers/watchdog/gpio_wdt.c probe 开头:

ret = of_property_read_string(np, "hw_algo", &algo);
if (ret)
   return ret;
if (!strcmp(algo, "toggle")) {
   priv->hw_algo = HW_ALGO_TOGGLE;
   gflags = GPIOD_IN;
} else if (!strcmp(algo, "level")) {
   priv->hw_algo = HW_ALGO_LEVEL;
   gflags = GPIOD_OUT_LOW;
} else {
   return -EINVAL;
}

of_property_read_string 找不到属性就返回 -EINVAL(-22)。dmesg:

gpio-wdt: probe of wdt-gpio failed with error -22

MAX6369 的 WDI 是 边沿复位计时:每次高低跳变清零内部计数器。对应算法是 toggle,不是 level(level 是拉高一小段再拉低,给另一类芯片)。toggle 下 GPIO 极性几乎无所谓,驱动自己翻 priv->state

hw_margin_ms 是硬件超时的安全边距。驱动把它填进 max_hw_heartbeat_ms。内核看门狗核心会在这个时间之内调用 ping。本板写 600:大约每 300~600 ms toggle 一次 WDI,远小于 188 秒,足够。

always-running 是布尔属性。probe 末尾:

priv->always_running = of_property_read_bool(np, "always-running");
...
if (priv->always_running)
   gpio_wdt_start(&priv->wdd);

写成 always-enabled 驱动不认,等于没开,用户态若不 open /dev/watchdog,仍然没人喂——表现又回到 188 秒复位。属性名必须是 always-running

ping 对 toggle:

case HW_ALGO_TOGGLE:
   priv->state = !priv->state;
   gpiod_set_value_cansleep(priv->gpiod, priv->state);
   break;

内核在喂的时候,示波器看 GPIO0_C2 是方波,周期约几百毫秒。没有方波:要么没 probe,要么拨码把 WDI 隔开了。

stop 在 always-running 时不会把脚改成输入去停喂,只把 WDOG_HW_RUNNING 再置上。所以用户态 close /dev/watchdog 也停不掉这颗,这是属性的本意。

内核配置:

CONFIG_WATCHDOG=y
CONFIG_GPIO_WATCHDOG=y
CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED=y

HANDLE_BOOT_ENABLED 让启动早期也维持喂,避免刚进内核、用户态还没起来的那一段被芯片咬死。


3. 设备树

wdt_gpio: wdt-gpio {
   compatible = "linux,wdt-gpio";
   gpios = <&gpio0 RK_PC2 GPIO_ACTIVE_LOW>;
   hw_algo = "toggle";
   hw_margin_ms = <600>;
   always-running;
};

&pinctrl {
   ext_wdt {
       ext_wdt_wdi: ext-wdt-wdi {
           rockchip,pins = <0 RK_PC2 RK_FUNC_GPIO &pcfg_pull_up>;
       };
   };
};

WDI = GPIO0_C2。pinctrl 强制 GPIO,不要停在别的复用。SET 脚(超时档)有的板接到 GPIO4_C0,由拨码或硬件电阻决定超时,软件不必去 bit-bang SET,除非你要改档。

compatible 必须是 linux,wdt-gpio。写成 gpio-wdt 对不上 of_device_id

改完刷 resource + boot_linux。

hdc shell "cat /proc/partitions"
hdc shell "dd if=/data/resource.img of=/dev/block/mmcblk1p4 bs=4M conv=fsync"
hdc shell "dd if=/data/boot_linux.img of=/dev/block/mmcblk1p5 bs=4M conv=fsync"

of= 必须 /dev/block/...


4. 怎么证明内核在喂

拨码打开。不要杀 init,不要 reboot -f。只要桌面挂着,超过 4 分钟 不复位,外部芯片就被喂着。188 秒那一档如果没人喂,四分钟之内一定已经死过一回。

对照实验(会复位,只在你能接受重启时做):

  1. 临时把节点 status = "disabled",刷进去,拨码开,等 188 秒级复位——证明芯片和拨码是活的。
  2. 再打开节点并带齐三个属性,同样拨码,挂 4 分钟不死——证明驱动在喂。

不要用「关拨码」当对照,那只能证明拨码能把芯片拿掉。

看方波:逻辑分析仪或示波器探 GPIO0_C2,toggle 在跳。没有仪器时,4 分钟挂机就是仪器。

hdc shell "cat /sys/class/watchdog/watchdog*/identity"
hdc shell "cat /sys/class/watchdog/watchdog*/state"

不要 在 always-running 已经生效时再写一个用户态 while sleep; ioctl KEEPALIVE 抢同一颗,除非你拿掉 always-running 做「杀守护就复位」的演示。两套同时喂也无害,但验收说不清是谁在喂。

内部 dw_wdt 被 watchdog_service 喂着,44 秒不对齐现场 188 秒。排查静默复位时先问间隔:不到一分钟偏内部,三分多钟偏 MAX6369。


5. 完整 C:只查询,不抢喂

always-running 下用户态打开 /dev/watchdog 要小心 nowayout:有的配置 close 而不写 V 会被当成停喂。查询用 sysfs,不要随便 open。

/* wdt_info.c
* armv7-unknown-linux-ohos-clang --target=arm-linux-ohos \
*   --sysroot=${OHOS_NATIVE}/sysroot \
*   -o wdt_info wdt_info.c \
*   -Wl,--dynamic-linker=/system/lib/ld-musl-arm.so.1
*
* hdc file send wdt_info /data/wdt_info
* hdc shell "chmod 755 /data/wdt_info; /data/wdt_info"
*/
#include <stdio.h>
#include <string.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>

static void dump_file(const char *path)
{
   char buf[128];
   int fd = open(path, O_RDONLY);
   int n;
   if (fd < 0) {
       return;
   }
   n = (int)read(fd, buf, sizeof(buf) - 1);
   close(fd);
   if (n > 0) {
       buf[n] = 0;
       printf("  %s = %s", path, buf);
       if (buf[n - 1] != '\n') {
           putchar('\n');
       }
   }
}

int main(void)
{
   DIR *d = opendir("/sys/class/watchdog");
   struct dirent *e;
   int gpio_wdt = 0;
   if (!d) {
       fprintf(stderr, "没有 /sys/class/watchdog。CONFIG_WATCHDOG?\n");
       return 1;
   }
   while ((e = readdir(d)) != NULL) {
       char p[192], id[64];
       int fd, n;
       if (e->d_name[0] == '.') {
           continue;
       }
       snprintf(p, sizeof(p), "/sys/class/watchdog/%s/identity", e->d_name);
       fd = open(p, O_RDONLY);
       if (fd < 0) {
           continue;
       }
       n = (int)read(fd, id, sizeof(id) - 1);
       close(fd);
       if (n <= 0) {
           continue;
       }
       id[n] = 0;
       printf("[%s] %s", e->d_name, id);
       if (strstr(id, "GPIO")) {
           gpio_wdt = 1;
           snprintf(p, sizeof(p), "/sys/class/watchdog/%s/state", e->d_name);
           dump_file(p);
           snprintf(p, sizeof(p), "/sys/class/watchdog/%s/timeout", e->d_name);
           dump_file(p);
       }
   }
   closedir(d);
   if (!gpio_wdt) {
       fprintf(stderr,
               "没有 identity=GPIO Watchdog。\n"
               "dmesg 搜 gpio-wdt 和 error -22,补 hw_algo=toggle\n");
       return 2;
   }
   printf("拨码打开,挂机 >4 min 不复位 = 内核在喂 WDI。\n");
   printf("约 188s 就重启 = 没喂上,不是 dw_wdt 的 44s。\n");
   return 0;
}

若要做「停喂就复位」演示,去掉 dts 的 always-running,用户态周期 ioctl(fd, WDIOC_KEEPALIVE, 0),正常退出前 write(fd, "V", 1)。X 用 identity 对上 GPIO 的那一个。杀这个进程后,MAX6369 按拨码超时复位。always-running 还在时杀用户态进程不会复位。


6. ArkTS 页面只做状态,不负责喂狗

import evbio from 'libevbio.so';

@Entry
@Component
struct GpioWdt {
 @State id: string = '';
 @State hint: string = '拨码打开。内核 always-running 在喂,页面不 ioctl。';

 build() {
   Column() {
     Text('MAX6369 / gpio-wdt').fontSize(22).fontWeight(FontWeight.Bold)
     Text('identity: ' + this.id).margin({ top: 12 }).fontSize(16)
     Text(this.hint).margin({ top: 8 }).fontSize(14)
     Button('刷新').onClick(() => { this.refresh(); }).margin({ top: 16 })
   }.padding(20)
 }

 refresh() {
   let found: string = '';
   for (let i = 0; i < 4; i++) {
     let base: string = '/sys/class/watchdog/watchdog' + i.toString();
     let s: string = evbio.readText(base + '/identity');
     if (s.indexOf('GPIO') >= 0) {
       found = s.replace('\n', '');
       break;
     }
   }
   if (found.length === 0) {
     this.id = '(没有 GPIO Watchdog)';
     this.hint = 'probe -22 的话补 hw_algo=toggle,属性名是 always-running 不是 always-enabled';
   } else {
     this.id = found;
     this.hint = '拨码开 + 挂机超过 4 分钟不复位,即内核在喂。约 188 秒就重启是没喂上。';
   }
 }

 aboutToAppear() {
   this.refresh();
 }
}

页面上不要放「开始喂狗」按钮,除非 dts 已经拿掉 always-running。否则按钮按下去你以为是应用在救机器,其实一直是内核在救。


7. 现象对照

现象原因处理
~188 s 静默复位gpio-wdt 没绑,芯片自己计时补 `hw_algo="toggle"`
probe error -22缺 hw_algo,或写成了别的字符串只能是 toggle 或 level;本芯片用 toggle
有 /dev/watchdog 仍 188 s 复位那是 dw_wdt,不是这颗identity 找 GPIO Watchdog
写了 always-enabled 仍复位驱动认 always-running改属性名
关拨码就不复位芯片没接入验收必须拨开
挂 4 min 不复位内核在喂通过
用户态守护杀掉才复位你拿掉了 always-running演示路径,和默认交付不同
44 s 复位且有 panic/log内部 dw_wdt / 别的原因不要和 MAX6369 混
flowchart TD
 A["约 188s 静默复位"] --> B{dmesg gpio-wdt -22?}
 B -->|是| C[补 hw_algo=toggle]
 B -->|否, 有 GPIO Watchdog| D{拨码开了?}
 D -->|否| E[拨开再验]
 D -->|是仍复位| F[always-running 写成了 always-enabled?]
 F --> G[改属性名, 刷 resource]
 B -->|否, 只有 dw_wdt| H[节点 compatible / GPIO0_C2 / CONFIG_GPIO_WATCHDOG]

外部看门狗靠 linux,wdt-gpio + hw_algo=toggle + hw_margin_ms=600 + always-running。缺算法就 -22,没人喂,大约 188 秒硬复位。拨码打开、挂机超过 4 分钟还活着,内核就在喂。


系列第 27 篇 · 芯片:瑞芯微 RK3568 · OpenHarmony 4.1(API 11) · Linux 5.10

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐