今天在分析系统led灯闪烁状态机制。这里记录一下相关的笔记和流程。
和led相关的部分从2个流程来看:
流程1:系统启动时,由openwrt系统脚本设置LED状态。
流程2:系统启动后,由应用行为控制LED灯状态。
依赖两部分,首先是DTS中的GPIO定义,第二部分是相关内核模块加载。
例如 xxx.dts 文件中定义了GPIO和默认状态。
aliases {
led-boot = &led_power;
led-failsafe = &led_power;
led-running = &led_power;
led-upgrade = &led_power;
};
leds {
compatible = "gpio-leds";
led@6 {
label = "green:wifi2";
gpios = <&pca9555 6 GPIO_ACTIVE_LOW>;
default-state = "off";
};
led@7 {
label = "green:wifi5";
gpios = <&pca9555 7 GPIO_ACTIVE_LOW>;
default-state = "off";
};
led@10 {
label = "green:cloud";
gpios = <&pca9555 8 GPIO_ACTIVE_LOW>;
default-state = "off";
};
led_power: led@11{
label = "orange:sys";
gpios = <&pca9555 9 GPIO_ACTIVE_LOW>;
default-state = "off";
};
};
package的定义在 openwrt/package/kernel/linux/modules/leds.mk 中
kmod-leds-gpio
kmod-ledtrig-activity
kmod-ledtrig-transient
这里列举一下kmod的信息:
kmod-leds-gpio 的模块信息:
define KernelPackage/leds-gpio
SUBMENU:=$(LEDS_MENU)
TITLE:=GPIO LED support
DEPENDS:= @GPIO_SUPPORT
KCONFIG:=CONFIG_LEDS_GPIO
FILES:=$(LINUX_DIR)/drivers/leds/leds-gpio.ko
AUTOLOAD:=$(call AutoLoad,60,leds-gpio,1)
endef
define KernelPackage/leds-gpio/description
Kernel module for LEDs on GPIO lines
endef
$(eval $(call KernelPackage,leds-gpio))
kmod-ledtrig-activity 的模块信息:
LED_TRIGGER_DIR=$(LINUX_DIR)/drivers/leds/trigger
define KernelPackage/ledtrig-activity
SUBMENU:=$(LEDS_MENU)
TITLE:=LED Activity Trigger
KCONFIG:=CONFIG_LEDS_TRIGGER_ACTIVITY
FILES:=$(LED_TRIGGER_DIR)/ledtrig-activity.ko
AUTOLOAD:=$(call AutoLoad,50,ledtrig-activity)
endef
define KernelPackage/ledtrig-activity/description
Kernel module that allows LEDs to blink based on system load
endef
$(eval $(call KernelPackage,ledtrig-activity))
kmod-ledtrig-transient 的模块信息:
define KernelPackage/ledtrig-transient
SUBMENU:=$(LEDS_MENU)
TITLE:=LED Transient Trigger
KCONFIG:=CONFIG_LEDS_TRIGGER_TRANSIENT
FILES:=$(LED_TRIGGER_DIR)/ledtrig-transient.ko
AUTOLOAD:=$(call AutoLoad,50,ledtrig-transient,1)
endef
define KernelPackage/ledtrig-transient/description
Kernel module that allows LEDs one time activation of a transient state.
endef
$(eval $(call KernelPackage,ledtrig-transient))
在系统启动阶段,led的状态是由哪部分代码和功能去做的控制呢,我们接下来做一下梳理。
比如我当前的系统,SYS LED灯是个双色灯,其中绿色是硬件默认点亮,红色的灯是需要GPIO控制的,默认拉高,所以,如果需要点亮的话,设置GPIO为低电平(GPIO_ACTIVE_LOW)即可。
在进入kernel之后,因为硬件设计的原因,LED 绿色灯是常亮的,此时红色灯未点亮。
当系统启动进入procd管理阶段,红色 LED灯开始闪烁。
当系统启动项/etc/rc.d/S96LED 启动脚本执行后,红色LED灯熄灭,默认绿色LED常亮。
当系统升级时,红色LED灯闪烁。
接下来分析一下启动过程:
kernel引导启动后,在kernel代码 init/main.c文件中:
static int __ref kernel_init(void *unused)
{
int ret;
// ...
/* init= 方式传递启动项参数到 execute_command */
if (execute_command) {
ret = run_init_process(execute_command);
if (!ret)
return 0;
}
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh"))
return 0;
}
老版本的openwrt系统,会有patch替换到启动脚本为 /etc/preinit ,但是我当前分析的系统 openwrt 21已经是使用默认的 /sbin/init 了。
/sbin/init 进程分析
OpenWrt 启动顺序_preinit
再细化一下 /etc/preinit 执行的动作
/etc/diag.sh 文件中封装了设置 led_state 的设置函数。
然后再看一下调用的位置:
1./lib/preinit/10_indicate_failsafe 文件中:
针对failsafe模式会有一个状态设置:
set_state failsafe
2./lib/preinit/50_indicate_regular_preinit 文件中:
set_state preinit_regular
3./lib/preinit/10_indicate_preinit 文件中:
set_state preinit
4./lib/upgrade/common.sh 文件中:
set_state upgrade
5./etc/init.d/done 文件中:
set_state done
6./etc/init.d/led 文件中:
set_state done
7./etc/rc.button/reset 文件中
set_state failsafe
设置时操作的led灯信息:
boot="$(get_dt_led boot)"
failsafe="$(get_dt_led failsafe)"
running="$(get_dt_led running)"
upgrade="$(get_dt_led upgrade)"
/lib/functions/leds.sh 文件中实现函数:
get_dt_led_path() {
local ledpath
local basepath="/proc/device-tree"
local nodepath="$basepath/aliases/led-$1"
[ -f "$nodepath" ] && ledpath=$(cat "$nodepath")
[ -n "$ledpath" ] && ledpath="$basepath$ledpath"
echo "$ledpath"
}
get_dt_led() {
local label
local ledpath=$(get_dt_led_path $1)
[ -n "$ledpath" ] && \
label=$(cat "$ledpath/label" 2>/dev/null) || \
label=$(cat "$ledpath/chan-name" 2>/dev/null) || \
label=$(basename "$ledpath")
echo "$label"
}
我这边看了下我的设备中,相关路径下的led灯定义:
cat /proc/device-tree/aliases/led-boot
/leds/led@11
cat /proc/device-tree/aliases/led-running
/leds/led@11
cat /proc/device-tree/aliases/led-upgrade
/leds/led@11
cat /proc/device-tree/aliases/led-failsafe
/leds/led@11
这一块的LED灯定义是参考 dts aliases中配置,实际都是指向 sys led灯。
各种状态的不同点只是在于LED灯闪烁频率不一样等等。
所以在procd启动到/etc/init.d/led启动之前这段时间内。LED灯的控制主要是由 /lib/preinit/ 目录下相关脚本控制设置,然后是 /etc/init.d/done 。
三个配置文件,一个是 /tmp/board.json, 一个是 /etc/board.json, 另一个是 /etc/config/system
设备配置文件的生成有三种途径:
1./etc/board.d/目录下脚本
2./bin/config_generate 脚本 (/etc/init.d/boot 调用)
3.uci-defaults目录下脚本 (/etc/init.d/boot 调用)
针对/etc/board.d/目录下脚本,会有2个调用处。
第一个是
/lib/preinit/10_indicate_preinit -> preinit_ip -> preinit_config_board -> /bin/board_detect /tmp/board.json -> 调用执行 /etc/board.d/ 目录下的可执行文件,生成配置信息。 -> /etc/board.d/01_leds
第二个是
/etc/rc.d/S10boot -> /bin/config_generate -> /bin/board_detect /etc/board.json -> 调用执行 /etc/board.d/ 目录下的可执行文件,生成配置信息。 -> /etc/board.d/01_leds
/etc/board.d/01_leds 文件内容
#!/bin/sh
. /lib/functions/uci-defaults.sh
board_config_update
board=$(board_name)
case "$board" in
xx,xxxx)
ucidef_set_led_default "system" "SYSTEM" "orange:sys" "0"
ucidef_set_led_wlan "wlan5g" "WLAN5G" "green:wifi5" "phy0tx"
ucidef_set_led_wlan "wlan2g" "WLAN2G" "green:wifi2" "phy1tx"
ucidef_set_led_default "cloud" "CLOUD" "green:cloud" "0"
;;
esac
board_config_flush
exit 0
我看了下 board_config_update、ucidef_set_led_xxx 、 board_config_flush 等函数实现主要是生成json 格式内容。
具体的函数实现可以看一下 /lib/functions/uci-defaults.sh 脚本内实现方法。
前面生成了 /etc/board.json文件之后,在 /bin/config_generate 文件中生成system 配置文件:
if [ ! -s /etc/config/system ]; then
touch /etc/config/system
generate_static_system
json_get_keys keys rssimon
for key in $keys; do generate_rssimon $key; done
json_get_keys keys gpioswitch
for key in $keys; do generate_gpioswitch $key; done
json_get_keys keys led
for key in $keys; do generate_led $key; done
fi
generate_led 函数内实现了 led参数的 设置。
最终生成的 /etc/config/system 配置文件中led等信息如下:
config led 'led_system'
option name 'SYSTEM'
option sysfs 'orange:sys'
option default '0'
系统启动后,控制LED灯状态改变有以下几种情况:
1.设置LED灯默认状态
/etc/init.d/led
2.升级过程中设置LED灯状态
在/lib/upgrade/common.sh 文件中set_state upgrade
3.按键超时
/etc/rc.button/reset 文件中set_state failsafe