|
本帖最后由 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/
- failsafe power 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 erasesize name
- 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
复制代码
|
+10
|