Hotplug,直译就是热插拔。在OpenWrt中,无论何时一个设备从系统中增删,都产生一个“热插拔事件”。
hotplug机制是linux内核提供的一种消息通知机制,主要用来实现内核态事件向用户态传递。OpenWrt下的ubus也类似。ubus是用户态的,而hotplug是内核态的,其功能都是用来消息传递。
插拔一个设备,我们一般要作两方面的开发。
- 内核侧,底层驱动mapping相应的中断,然后处理中断及上报事件
- 用户侧,上层应用收到内核上报的事件,根据需求做不同的处理
从上述可以看出,我们的定制开发主要涉及两方面,内核驱动相关中断/事件的上报;用户层相关应用的对中断/事件的处理。目前经过多年的演进,这两方面的开发也趋于简单。
- 针对板子设计创建或者修改DTS文件
- 针对不同的设备在/etc/hotplug.d或者/etc/rc.button下添加或者修改设备事件处理脚本
下面以WPS为例,描述整个过程,
1) DTS文件
内核侧使用DTS,不用改代码,直接修改文件就可以根据自己的板子定义不同GPIO pin以及其他属性。下面是
gpio-keys {
compatible = "gpio-keys";
autorepeat;
btn_reset {
label = "btn,reset";
linux,code = <KEY_RESTART>;
gpios = <&gpio0 8 GPIO_ACTIVE_LOW>;
};
btn_wps {
label = "btn,wps"; //按键标签
linux,code = <KEY_WPS_BUTTON>; // 按键对应的Linux输入子系统的键值代码
gpios = <&gpio3 7 GPIO_ACTIVE_LOW>; //指定GPIO端口和引脚编号
};
btn_wifi {
label = "btn,wifi";
linux,code = <KEY_WLAN>;
gpios = <&gpio3 3 GPIO_ACTIVE_LOW>;
};
};
2)gpiobutton-hotplug库
在OpenWrt系统中,gpio-button-hotplug库用于简化和优化基于通用输入输出(GPIO)的按钮事件处理。它提供了一种机制,允许开发者方便地在内核级别捕获来自物理按键的事件。这种处理机制不仅增加了按键事件处理的灵活性和可靠性,还减少了开发者在底层硬件交互方面的工作量。
在gpio-button-hotplug库,两种主要的按键状态检测方式:轮询(Polling)和中断(Interrupt)。
中断方式:
- 实现方式:中断方式依赖于GPIO引脚能够产生硬件中断。当按键状态发生变化时,如被按下或释放,硬件中断会被触发,从而立即通知系统处理按键事件。
- 适用场合:这种方式适用于对按键响应时间有严格要求的应用,如紧急停止按钮或高交互性的控制按键。
KEY_WPS_BUTTON //gpio-button-hotplug/src/gpio-button-hotplug.c
gpio_keys_probe //interupt process way
gpio_keys_button_probe //get platform data
gpio_keys_irq_work_func //delay debounce time then process this irq
gpio_keys_handle_button
button_hotplug_create_event //create an event
button_hotplug_work
button_hotplug_fill_event //construct message content
broadcast_uevent //send uevent to user space
netlink_broadcast //lib/kobject_uevent.c
轮询方式:
- 实现方式:在轮询模式下,系统定期检查每个按键的状态。这种方式不依赖于硬件中断,因此对于无法生成硬件中断的GPIO按键状态的检测非常适用。
- 适用场合:轮询方式通常用于那些对实时性要求不高的场合,或是在硬件资源有限的情况下,如简单的用户界面交互。
gpio_keys_polled_probe //poll process way
gpio_keys_button_probe
gpio_keys_polled_poll
gpio_keys_handle_button //create an event and send to user space via uevent
gpio_keys_polled_queue_work //delay poll interval
在gpio-button-hotplug库,内核使用netlink socket以广播的方式把uevent事件发送给用户层。uevent 事件是以json格式的附带信息内容。事件包含的信息一般包含HOME ,PATH,SUBSYSTEM,ACTION,BUTTON,SEEN,SEQNUM等。
具体事件内容填写见button_hotplug_fill_event。下面是一个例子,
- procd: rule_handle_command(360): Message:
- procd: rule_handle_command(362): HOME=/
- procd: rule_handle_command(362): PATH=/sbin:/bin:/usr/sbin:/usr/bin
- procd: rule_handle_command(362): SUBSYSTEM=button
- procd: rule_handle_command(362): ACTION=pressed
- procd: rule_handle_command(362): BUTTON=wps
- procd: rule_handle_command(362): SEEN=862
- procd: rule_handle_command(362): SEQNUM=593
3)Procd hotplug
procd/plug/hotplug.c中, 创建一个 PF_NETLINK 套接字来监听内核 netlink_broadcast() 发出的 uevent. 收到 uevent 之后, 再根据 /etc/hotplug.json 里的描述定位到对应的执行函数来处理。
通常情况下,/etc/hotplug.json会调用/sbin/hotplug-call来处理uevent,它根据uevent的$SUBSYSTEM变量来分别调用/etc/hotplug.d下不同目录中的脚本
在/etc/hotplug.d目录中,您会发现一些目录block、iface、net和ntp。当触发器事件触发时,procd 将按字母顺序执行该触发器目录中的所有脚本。/sbin/hotplug-call脚本会完成这部分工作,依次执行相应目录下的所有脚本
目录 描述
block 块设备事件:设备连接/断开
button: 默认情况下不创建,看到/etc/rc.button代替
dhcp DHCP相关事件
firewall 防火墙相关事件
iface: LAN / WAN /etc。连接/断开
neigh 邻居发现
net 网络相关事件
ntp 时间同步事件:时间步长、时间服务器层变化
tftp TFTP相关事件
USB USB 设备,如 3g 调制解调器和 tty*
比如/etc/hotplug.d/iface下有4个脚本,会按顺序执行这4个脚本。
root@AONT:/etc/hotplug.d/iface# ls
00-netstate 05-led_inet 10-rgwstack 20-firewall
对于button,则执行 /etc/rc.button/ 下的 %BUTTON% 脚本来处理. 每个button对应不同的处理脚本。
以wps为例,hotplug.json内容如下,最终会调用/etc/rc.button/wps
[ "if",
[ "and",
[ "has", "BUTTON" ],
[ "eq", "SUBSYSTEM", "button" ]
],
[ "button", "/etc/rc.button/%BUTTON%" ]
],
代码走读,
procd初始化的时候将hotplug运行起来,procd_state_next-->state_enter-->hotplug
hotplug 会创建PF_NETLINK socket,接收NETLINK_KOBJECT_UEVENT。将这个socket加入uloop,其callback是hotplug_handler
hotplug_handler 会接收uevent事件,并解析json 格式的事件报文。然后调用json_script_run
json_script_run根据etc/hotplug.json中预先定义的JSON内容匹配条件,定位到对应的执行函数。
handlers[] = {
[HANDLER_MKDEV] = {
.name = "makedev",
.atomic = 1,
.handler = handle_makedev,
},
[HANDLER_RM] = {
.name = "rm",
.atomic = 1,
.handler = handle_rm,
},
[HANDLER_EXEC] = {
.name = "exec",
.handler = handle_exec,
},
[HANDLER_BUTTON] = {
.name = "button",
.handler = handle_exec, //执行相应的脚本
.start = handle_button_start,
.complete = handle_button_complete,
},
[HANDLER_FW] = {
.name = "load-firmware",
.handler = handle_firmware,
},
[HANDLER_START_CONSOLE] = {
.name = "start-console",
.handler = handle_start_console,
},
};
rule_handle_command
handle_exec
handle_makedev
handle_rm
handle_firmware
handle_start_console
这些函数的入参是(struct blob_attr *msg, struct blob_attr *data),其中msg表示来自kernel JSON uevent;data表示来自hotplug.json中匹配的规则。
参考文献:
openwrt热插拔HotPlug_openwrt hotplug-CSDN博客
openwrt hotplug_openwrt uevent事件-CSDN博客
基础指南:在OpenWrt中使用GPIO按键 - 知乎 (zhihu.com)