【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
也有写/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]