sunsili 发表于 2024-1-11 17:16:13

【OpenWRT】详解 OpenWRT RESET按键 键盘响应逻辑

本帖最后由 sunsili 于 2024-1-11 17:17 编辑

详解 OpenWRT RESET按键 键盘响应逻辑


OpenWrt 按键处理逻辑采用 hotplug 事件方式进行管理,reset按键,用来进行重启或者恢复出厂操作。热插拔事件流程:内核具有检测 键盘守护程序,gpio_button_hotplug 模块,源码位于 openwrt/package/kernel/gpio-button-hotplug 文件夹下,系统启动时加载键盘检测模块,就驻留到内核。

内核gpio_button_hotplug 模块检测到键盘后,通过netlink-kobject方式发送到用户空间,用户空间 ubusd 系统获得到事件后,执行键盘响应的回调函数,响应键盘。
本文直接分析用户空间程序处理逻辑,不分析内核键盘模块程序、请读者自行参考netlink框架和gpio_button_hotplug.c文件分析。

RESET按键的操作对应为:
单击 - 重启设备
长按 5秒以上 – 恢复出厂设置
当然,这些操作可以通过配置etc/rc.button/reset脚本处理方法,重新定义按键功能。为什么修改rese脚本就能够重新定义呢?
接下来逐步分析,揭晓 openWRT的按键管理逻辑。

hotplug 事件注册
在 《详解 OpenWRT系统框架基础软件模块之 procd》篇,分析过procd线程启动时,调用 openWRT的state_enter() 状态机函数,此函数中,在系统 early_init 阶段,调用 hotplug() 函数注册RPC服务响应程序。源码如下:
static void state_enter(void)
{
      char ubus_cmd[] = "/sbin/ubusd";
      switch (state) {
      case STATE_EARLY:
                LOG("- early -\n");
                watchdog_init(0);
                hotplug("/etc/hotplug.json");   //热插拔函数初始化,hotplug.json文件内容非常关键
                procd_coldplug();                              //冷插拔函数初始化
                break;

      case STATE_UBUS:
                // try to reopen incase the wdt was not available before coldplug

                watchdog_init(0);
                set_stdio("console");
                LOG("- ubus -\n");
                procd_connect_ubus();
                service_start_early("ubus", ubus_cmd);
                break;

      case STATE_INIT:
                LOG("- init -\n");
                procd_inittab();
                procd_inittab_run("respawn");
                procd_inittab_run("askconsole");
                procd_inittab_run("askfirst");
                procd_inittab_run("sysinit");
                // switch to syslog log channel
                ulog_open(ULOG_SYSLOG, LOG_DAEMON, "procd");
                break;

      case STATE_RUNNING:
                LOG("- init complete -\n");
                procd_inittab_run("respawnlate");                procd_inittab_run("askconsolelate");

                break;

..... 省略部分源码

      default:
                ERROR("Unhandled state %d\n", state);

                return;
      };
}
文件 /etc/hotplug.json 内容
[

      [ "case", "ACTION", {

                "add": [

                        [ "if",

                              [ "and",

                                        [ "has", "MAJOR" ],

                                        [ "has", "MINOR" ]

                              ],

                              [

                                        [ "if",

                                                [ "eq", "DEVNAME",

                                                      [ "null", "full", "ptmx", "zero", "tty", "net", "random", "urandom" ]

                                                ],

                                                [

                                                      [ "makedev", "/dev/%DEVNAME%", "0666" ],

                                                      [ "return" ]
                                                ]
                                        ],
                                        [ "if",
                                                [ "regex", "DEVNAME", "^snd" ],
                                                [ "makedev", "/dev/%DEVNAME%", "0660", "audio" ]
                                        ],
                                        [ "if",
                                                [ "regex", "DEVNAME", "^tty" ],
                                                [ "makedev", "/dev/%DEVNAME%", "0660", "dialout" ]
                                        ],
                                        [ "if",
                                                [ "has", "DEVNAME" ],
                                                [ "makedev", "/dev/%DEVNAME%", "0600" ]
                                        ]
                              ]
                        ],
                        [ "if",
                              [ "has", "FIRMWARE" ],
                              [
                                        [ "exec", "/sbin/hotplug-call", "%SUBSYSTEM%" ],
                                        [ "load-firmware", "/lib/firmware" ],
                                        [ "return" ]
                              ]
                        ]
                ],
                "remove" : [
                        [ "if",
                              [ "and",
                                        [ "has", "DEVNAME" ],
                                        [ "has", "MAJOR" ],
                                        [ "has", "MINOR" ]

                              ],
                              [ "rm", "/dev/%DEVNAME%" ]
                        ]
                ]
      } ],
      [ "if",
                [ "and",                                                                              //键盘事件
                        [ "has", "BUTTON" ],
                        [ "eq", "SUBSYSTEM", "button" ]
                ],
                [ "button", "/etc/rc.button/%BUTTON%" ]                        // 键盘事件执行的脚本位置

      ],
      [ "if",
                [ "and",                                                                              // usb串口事件
                        [ "eq", "SUBSYSTEM", "usb-serial" ],
                        [ "regex", "DEVNAME",
                              [ "^ttyUSB", "^ttyACM" ]
                        ]
                ],
                [ "exec", "/sbin/hotplug-call", "tty" ],
                [ "if",
                        [ "isdir", "/etc/hotplug.d/%SUBSYSTEM%" ],
                        [ "exec", "/sbin/hotplug-call", "%SUBSYSTEM%" ]
                ]
      ]
]
由此文件看到 [ “button”, “/etc/rc.button/%BUTTON%” ] 内容,也就是说键盘热插拔事件发送到用户空间后,会触发程序执行此文件夹下的脚本。

OpenWRT 系统中的按键处理方法如下:
在 etc/rc.button/ 文件夹下有热拔插事件响应脚本
按键热拔插事件、触发etc/rc.button/ 对应的按键响应脚本
系统按键响应逻辑是,执行响应脚本文件,用户可以直接修改脚本文件,来修订键盘处理响应逻辑。
查看 rc.button/
内容如下
root@LEDE:/# ls /etc/rc.button/
failsafepower   reset   rfkill
响应脚本内容,分别如下:

电源按键
/# cat etc/rc.button/power       // 电源off执行内容

#!/bin/sh

[ "${ACTION}" = "released" ] || exit 0
exec /sbin/poweroff

return 0

安全模式
/# cat etc/rc.button/failsafe         // 进入安全模式

#!/bin/sh
[ "${TYPE}" = "switch" ] || echo ${BUTTON} > /tmp/failsafe_button

return 0
重点看 复位按键 的脚本

root@LEDE:/# cat etc/rc.button/reset //复位按键处理
#!/bin/sh
. /lib/functions.sh
OVERLAY="$( grep ' /overlay ' /proc/mounts )"
case "$ACTION" in
pressed)                                                                // 内核模块gpio_button_hotplug 发送事件类型pressed
      [ -z "$OVERLAY" ] && return 0
      return 5
;;
timeout)
      . /etc/diag.sh
      set_state failsafe
;;
released)                                                                // 内核模块gpio_button_hotplug回发送事件类型released,
                                                                              // 并携带按键时间SEEN变量值,因此脚本判定此变量的时间值
      if [ "$SEEN" -lt 1 ]                                                // 小于 1s , 执行reboot指令
      then
                echo "REBOOT" > /dev/console
                sync
                reboot                                                               
      elif [ "$SEEN" -gt 5 -a -n "$OVERLAY" ]                // 按键超出 5s , 就执行 jffs2reset -y && reboot
      then
                echo "FACTORY RESET" > /dev/console
                jffs2reset -y && reboot &                     
      fi
;;
esac
return 0

至此,我们揭开reset按键功能、openWRT系统是处理的全过程。接下来重点分析恢复出厂功能是如何实现的,
下面内容的重点是文件系统知识,特别是 overlay 文件系统相关知识。关于 overlay 文件系统原理,推荐阅读 《深入理解 overlayfs (一、原理)》。

reset 处理逻辑分析
reset 按键超出 5s , 就执行 jffs2reset -y && reboot & 命令,我们进一步分析 jffs2reset 命令。

执行 jffs2reset -y 结果如下
root@ixeRouter:~# jffs2reset -y                                                                                                                              

/dev/mtdblock6 is mounted as /overlay, only erasing files

查看分区
root@ixeRouter:~# cat /proc/mtd                                                                                                                              

dev:    size   erasesizename

mtd0: 00030000 00010000 "bootloader"

mtd1: 00010000 00010000 "config"

mtd2: 00010000 00010000 "factory"

mtd3: 01fb0000 00010000 "firmware"

mtd4: 0020670f 00010000 "kernel"

mtd5: 01da98f1 00010000 "rootfs"

mtd6: 00640000 00010000 "rootfs_data"
得知,mtd6中的内容为 rootfs_data, 也就是路由器设备配置相关内容被擦除,
系统启动后、恢复到出厂镜像配置状态。

jffs2reset 源码分析
查看 fstools 文件夹下的 CMakeList.txt文件内容,摘录部分内容如下:
ADD_EXECUTABLE(mount_root mount_root.c) TARGET_LINK_LIBRARIES(mount_root fstools) INSTALL(TARGETS mount_root RUNTIME DESTINATION sbin)

find_library(json NAMES json-c json)



ADD_EXECUTABLE(blockd blockd.c) TARGET_LINK_LIBRARIES(blockd fstools ubus blobmsg_json ${json}) INSTALL(TARGETS blockd RUNTIME DESTINATION sbin)

ADD_EXECUTABLE(block block.c probe.c probe-libblkid.c) IF(DEFINED CMAKE_UBIFS_EXTROOT) ADD_DEFINITIONS(-DUBIFS_EXTROOT)

      TARGET_LINK_LIBRARIES(block blkid-tiny dl uci ubox ubus blobmsg_json ubi-utils ${json}) ELSE(DEFINED CMAKE_UBIFS_EXTROOT) TARGET_LINK_LIBRARIES(block blkid-tiny dl uci ubox ubus blobmsg_json ${json}) ENDIF(DEFINED CMAKE_UBIFS_EXTROOT) INSTALL(TARGETS block RUNTIME DESTINATION sbin)



# jffs2reset 命令内容对应的源码文件

ADD_EXECUTABLE(jffs2reset jffs2reset.c) TARGET_LINK_LIBRARIES(jffs2reset fstools) INSTALL(TARGETS jffs2reset RUNTIME DESTINATION sbin)



ADD_EXECUTABLE(snapshot_tool snapshot.c) TARGET_LINK_LIBRARIES(snapshot_tool fstools) INSTALL(TARGETS snapshot_tool RUNTIME DESTINATION sbin)

ADD_EXECUTABLE(ubi ubi.c) TARGET_LINK_LIBRARIES(ubi ubi-utils ubox) INSTALL(TARGETS ubi RUNTIME DESTINATION sbin)
jffs2reset.c 源码文件内容
如下:
int main(int argc, char **argv)
{
      struct volume *v;
      int ch, yes = 0, reset = 0;

      while ((ch = getopt(argc, argv, "yr")) != -1) {
                switch(ch) {
                case 'y':
                        yes = 1;
                        break;
                case 'r':
                        reset = 1;
                        break;

                }
      }

      if (!yes && ask_user())
                return -1;

      /*
         * TODO: Currently this only checks if kernel supports OverlayFS. We
         * should check if there is a mount point using it with rootfs_data
         * as upperdir.
         */

      if (find_filesystem("overlay")) {
                ULOG_ERR("overlayfs not supported by kernel\n");
                return -1;
      }
      v = volume_find("rootfs_data");                              // 查找分区 rootfs_data

      if (!v) {
                ULOG_ERR("MTD partition 'rootfs_data' not found\n");
                return -1;
      }

      volume_init(v);
      if (!strcmp(*argv, "jffs2mark"))
                return jffs2_mark(v);
      return jffs2_reset(v, reset);                              // 执行 jffs2_reset
}
jffs2_reset 函数内容
如下
static int jffs2_reset(struct volume *v, int reset)
{
      char *mp;

      mp = find_mount_point(v->blk, 1);

      if (mp) {
                ULOG_INFO("%s is mounted as %s, only erasing files\n", v->blk, mp);
                fs_state_set("/overlay", FS_STATE_PENDING);
                overlay_delete(mp, false);                                                                //delete overlay分区内容
                mount(mp, "/", NULL, MS_REMOUNT, 0);
      } else {
                ULOG_INFO("%s is not mounted\n", v->blk);
                return jffs2_mark(v);
      }

      if (reset) {
                sync();
                sleep(2);

                reboot(RB_AUTOBOOT);

                while (1)
                        ;
      }

      return 0;
}
路由器系统文件构成
root@ixeRouter:~# cat /proc/filesystems                                                                                                                     

nodev   sysfs

nodev   rootfs

nodev   ramfs

nodev   bdev

nodev   proc

nodev   tmpfs

nodev   debugfs

nodev   tracefs

nodev   sockfs

nodev   bpf

nodev   pipefs

// 路由器文件系统构成

nodev   devpts

      squashfs      //文件系统

nodev   jffs2

nodev   overlay                //文件系统

nodev   ubifs

sunsili 发表于 2024-1-26 21:11:47

也有写/etc/config/system里的

config button
      option button 'BTN_3'
      option action 'released'
      option handler '/root/resetparam && reboot'
      option min '1'
      option max '3'

config button
      option button 'BTN_3'
      option action 'released'
      option handler '/root/resetall && reboot'
      option min '5'
      option max '30'


页: [1]
查看完整版本: 【OpenWRT】详解 OpenWRT RESET按键 键盘响应逻辑