hotplug為linux的一個熱拔插系統(tǒng)盒至,在很多應用都有用到椒功,如網(wǎng)口的拔插华烟,USB的拔插心墅,按鍵的觸發(fā)...非常廣泛的應用酿矢,下面舉幾個例子來進行理解這個過程
1.gpio-button-hotplug(內(nèi)核層發(fā)送hotplug到procd)
gpio-button-hotplug為kernel的一個package榨乎,位于package/kernel/gpio-button-hotplug/
要把CONFIG_PACKAGE_kmod-gpio-button-hotplug=y
選項打開
1.1驅動
"gpio-keys"和"gpio-keys-polled"就是兩個platform設備,如下:
static struct platform_driver gpio_keys_driver = {
.probe = gpio_keys_probe,
.remove = gpio_keys_remove,
.driver = {
.name = "gpio-keys",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(gpio_keys_of_match),
},
};
static struct platform_driver gpio_keys_polled_driver = {
.probe = gpio_keys_polled_probe,
.remove = gpio_keys_remove,
.driver = {
.name = "gpio-keys-polled",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(gpio_keys_polled_of_match),
},
};
static int __init gpio_button_init(void)
{
int ret;
ret = platform_driver_register(&gpio_keys_driver);
if (ret)
return ret;
ret = platform_driver_register(&gpio_keys_polled_driver);
if (ret)
platform_driver_unregister(&gpio_keys_driver);
return ret;
}
static void __exit gpio_button_exit(void)
{
platform_driver_unregister(&gpio_keys_driver);
platform_driver_unregister(&gpio_keys_polled_driver);
}
module_init(gpio_button_init);
module_exit(gpio_button_exit);
與以前的做法一樣瘫筐,device和driver蜜暑,driver這邊有了,device要么arch里面要么dts里面策肝。
看下DTS里面的配置
gpio-keys-polled {
compatible = "gpio-keys-polled";
#address-cells = <1>;
#size-cells = <0>;
poll-interval = <20>;
power {
label = "power";
gpios = <&gpio0 24 1>; //GPIO24 line is low, key is pressed
linux,code = <116>; //KEY_POWER
};
reset {
label = "reset";
gpios = <&gpio1 11 1>; //GPIO43 line is low, key is pressed
linux,code = <0x198>; //KEY_RESTART
};
};
上面的這些參數(shù)数冬,在gpio_keys_get_devtree_pdata
函數(shù)里面都會進行解析判斷格式是否正確钧排,錯誤在啟動注冊時就會直接提示錯誤信息。
另一種就是在arch里面注冊,
在zkernel/3.10.49/arch/mips/mtk/dev-gpio-buttons.c里面提供了注冊接口蚂子,調(diào)用即可绅这。
#define ZRMT7621_KEYS_POLL_INTERVAL 20
#define ZRMT7621_KEYS_DEBOUNCE_INTERVAL (3 * ZRMT7621_KEYS_POLL_INTERVAL)
static struct gpio_keys_button zrmt7621_gpio_buttons[] __initdata = {
{
.desc = "reset",
.type = EV_KEY,
.code = KEY_RESTART,
.debounce_interval = ZRMT7621_KEYS_DEBOUNCE_INTERVAL,
.gpio = ZRMT7621_GPIO_BUTTON_RESET,
.active_low = 1,
},
};
ramips_register_gpio_buttons(-1, ZRMT7621_KEYS_POLL_INTERVAL,
ARRAY_SIZE(zrmt7621_gpio_buttons),
zrmt7621_gpio_buttons);
在上面的button結構體里面要定義好對應的type和code赔桌,
這邊故意將linux,code里面的數(shù)值寫成數(shù)值瓢剿,是為了讓我們看到更底層的定義,在gpio-button-hotplug.c
里面有如下定義帘睦,
static struct bh_map button_map[] = {
BH_MAP(BTN_0, "BTN_0"),
BH_MAP(BTN_1, "BTN_1"),
BH_MAP(BTN_2, "BTN_2"),
BH_MAP(BTN_3, "BTN_3"),
BH_MAP(BTN_4, "BTN_4"),
BH_MAP(BTN_5, "BTN_5"),
BH_MAP(BTN_6, "BTN_6"),
BH_MAP(BTN_7, "BTN_7"),
BH_MAP(BTN_8, "BTN_8"),
BH_MAP(BTN_9, "BTN_9"),
BH_MAP(KEY_BRIGHTNESS_ZERO, "brightness_zero"),
BH_MAP(KEY_CONFIG, "config"),
BH_MAP(KEY_COPY, "copy"),
BH_MAP(KEY_EJECTCD, "eject"),
BH_MAP(KEY_HELP, "help"),
BH_MAP(KEY_LIGHTS_TOGGLE, "lights_toggle"),
BH_MAP(KEY_PHONE, "phone"),
BH_MAP(KEY_POWER, "power"),
BH_MAP(KEY_RESTART, "reset"),
BH_MAP(KEY_RFKILL, "rfkill"),
BH_MAP(KEY_VIDEO, "video"),
BH_MAP(KEY_WIMAX, "wwan"),
BH_MAP(KEY_WLAN, "wlan"),
BH_MAP(KEY_WPS_BUTTON, "wps"),
};
在include/dt-bindings/input/linux-event-codes.h
里面有如下定義袍患,所以就知道最終的數(shù)值了。
#define KEY_RESTART 0x198
#define KEY_INSERT 110
#define KEY_DELETE 111
#define KEY_MACRO 112
#define KEY_MUTE 113
#define KEY_VOLUMEDOWN 114
#define KEY_VOLUMEUP 115
#define KEY_POWER 116 /* SC System Power Down */
#define KEY_KPEQUAL 117
#define KEY_KPPLUSMINUS 118
#define KEY_PAUSE 119
#define KEY_SCALE 120 /* AL Compiz Scale (Expose) */
驅動加載成功則會有如下信息:
root@zihome:/sys/devices/platform/gpio-keys-polled# ls
driver modalias subsystem uevent
1.2 key的應用層處理
當按鍵時竣付,則觸發(fā)button_hotplug_event
函數(shù)(gpio-button-hotplug.c):調(diào)用button_hotplug_create_event
產(chǎn)生uevent事件诡延,調(diào)用button_hotplug_fill_even
填充事件(JSON格式),并最終調(diào)用broadcast_uevent
發(fā)出uevent廣播信息古胆,后由內(nèi)核netlink_broadcast
函數(shù)(linux-3.10.49/net/netlink/af_netlink.c)
netlink的實現(xiàn)原理可以看下面這篇文件的介紹肆良,為socket通信,內(nèi)核發(fā)出socket廣播逸绎,上層應用(procd)只需要監(jiān)聽這個socket事件即可惹恃。
https://blog.csdn.net/Swallow_he/article/details/84073545
上述廣播,被procd進程中的hotplug_handler (procd/plug/hotplug.c) 收到棺牧,并根據(jù)etc/hotplug.json中預先定義的JSON內(nèi)容匹配條件巫糙,定位到對應的執(zhí)行函數(shù),具體為:
[ "if",
[ "and",
[ "has", "BUTTON" ],
[ "eq", "SUBSYSTEM", "button" ],
],
[ "exec", "/etc/rc.button/%BUTTON%" ]
],
最終會執(zhí)行/etc/rc.button/里面的對應的腳本颊乘,如reset/power参淹,腳本的名字要跟button_map結構里面的一致。
root@LEDE:/# cat etc/rc.button/power
#!/bin/sh
[ "${ACTION}" = "released" ] || exit 0
exec /sbin/poweroff
return 0
root@LEDE:/# cat etc/rc.button/reset
#!/bin/sh
. /lib/functions.sh
OVERLAY="$( grep ' /overlay ' /proc/mounts )"
case "$ACTION" in
pressed)
[ -z "$OVERLAY" ] && return 0
return 5
;;
timeout)
. /etc/diag.sh
set_state failsafe
;;
released)
if [ "$SEEN" -lt 1 ]
then
echo "REBOOT" > /dev/console
sync
reboot
elif [ "$SEEN" -gt 5 -a -n "$OVERLAY" ]
then
echo "FACTORY RESET" > /dev/console
jffs2reset -y && reboot &
fi
;;
esac
return 0
2.WAN口網(wǎng)線是否插入檢測(phy內(nèi)核發(fā)出)
內(nèi)核檢測到WAN口變化后會創(chuàng)建hotplug消息(broadcast_uevent)乏悄,發(fā)送給procd浙值,再轉發(fā)到對應的模塊
static void phy_hotplug_work(struct work_struct *work)
{
struct bh_event *event = container_of(work, struct bh_event, work);
int ret = 0;
event->skb = alloc_skb(BH_SKB_SIZE, GFP_KERNEL);
if (!event->skb)
goto out_free_event;
ret = bh_event_add_var(event, 0, "%s@", event->action);
if (ret)
goto out_free_skb;
ret = phy_hotplug_fill_event(event);
if (ret)
goto out_free_skb;
if (event->type) {
printk(KERN_NOTICE "phy: port%u %s(irq)\n", event->port_num, event->action);
} else {
printk(KERN_NOTICE "phy: port%u %s(dev)\n", event->port_num, event->action);
}
NETLINK_CB(event->skb).dst_group = 1;
broadcast_uevent(event->skb, 0, 1, GFP_KERNEL);
out_free_skb:
if (ret) {
kfree_skb(event->skb);
}
out_free_event:
kfree(event);
}
發(fā)出后就會觸發(fā)以下腳本,在腳本里面添加我們需要的內(nèi)容
vim /etc/hotplug.d/phy/00-wan
case "$wan_ifname" in
"eth"*)
if [ "$wan_port" = "$PORTNUM" ]; then
logger -t "phy" "$PORTNUM $ACTION"
mkdir -p /tmp/status >/dev/null 2>&1
case "$ACTION" in
"linkup")
echo "1" >/tmp/status/wan_port_status
ubus call zboard set_wan "{\"status\":1,\"port\":$wan_port}"
dhcp_handle_up "wan" "$wan_ifname"
;;
"linkdown")
echo "0" >/tmp/status/wan_port_status
ubus call zboard set_wan "{\"status\":0,\"port\":$wan_port}"
dhcp_handle_down "wan" "$wan_ifname"
;;
esac
phy_hotplug "wan" $ACTION
fi
;;
*)
if [ "$wan_port" = "$PORTNUM" ]; then
logger -t "phy" "$PORTNUM $ACTION for wisp"
mkdir -p /tmp/status >/dev/null 2>&1
case "$ACTION" in
"linkup")
echo "1" >/tmp/status/wan_port_status
ubus call zboard set_wan "{\"status\":1,\"port\":$wan_port}"
;;
"linkdown")
echo "0" >/tmp/status/wan_port_status
ubus call zboard set_wan "{\"status\":0,\"port\":$wan_port}"
;;
esac
fi
;;
esac
3.網(wǎng)絡檢測添加LED閃爍(/sbin/hotplug-call)
3.1 zdetect網(wǎng)絡檢測模塊
在zrouter/zpackages/zihome/utils/zdetect/src/zdetect.c里面會發(fā)送hotplug event當網(wǎng)絡變化的時候:
static void inet_hotplug(const char* action)
{
char *argv[3];
int pid;
pid = fork();
if (pid < 0) {
dbg_printf(MSG_INFO, "hotplug_event fork failed!");
return;
} else if (pid == 0) {
setenv("ACTION", action, 1);
argv[0] = HOTPLUG_PATH;
argv[1] = "inet";
argv[2] = NULL;
execvp(argv[0], argv);
exit(127);
}
}
3.2 收到inet模塊hotplug消息后
14.07/package/base-files/files/etc/hotplug.d/inet/00-inet
#!/bin/sh
case "$ACTION" in
"online")
logger -t "inet" "detect online"
zihome_led yellow on
;;
"offline")
logger -t "inet" "detect offline"
zihome_led yellow 1000 1000
;;
*) ;;
esac
3.3 調(diào)用led執(zhí)行腳本
14.07/package/base-files/files/sbin/zihome_led
#!/bin/sh
. /lib/functions/leds.sh
led_path="zihome:""$1"
led
4.iface(netifd)
每次網(wǎng)絡接口啟動(up)或者關閉(down)的時候檩小,所有在/etc/hotplug.d/iface/目錄中的腳本都會以字母順序執(zhí)行开呐。根據(jù)一個不成文的規(guī)則,會在每個腳本的前面加上一個數(shù)字前綴來設置正確的運行順序。這就是為什么腳本名稱都像:/etc/hotplug.d/iface/<nn>-<scriptname>的原因负蚊。
變量名稱 | 描述 |
---|---|
ACTION | "ifup" 或者 "ifdown" |
INTERFACE | 網(wǎng)絡接口的名稱神妹,如"wan" |
DEVICE | 物理設備的名稱颓哮,如"br-lan" |
https://blog.csdn.net/vivianliulu/article/details/79629836
static void call_hotplug(void)
{
const char *device = NULL;
if (list_empty(&pending))
return;
current = list_first_entry(&pending, struct interface, hotplug_list);
current_ev = current->hotplug_ev;
list_del_init(¤t->hotplug_list);
if ((current_ev == IFEV_UP || current_ev == IFEV_UPDATE) && current->l3_dev.dev)
device = current->l3_dev.dev->ifname;
D(SYSTEM, "Call hotplug handler for interface '%s', event '%s' (%s)\n",
current->name, eventnames[current_ev], device ? device : "none");
run_cmd(current->name, device, current_ev, current->updated);
}
static void run_cmd(const char *ifname, const char *device, enum interface_event event,
enum interface_update_flags updated)
{
char *argv[3];
int pid;
pid = fork();
if (pid < 0)
return task_complete(NULL, -1);
if (pid > 0) {
task.pid = pid;
uloop_process_add(&task);
return;
}
setenv("ACTION", eventnames[event], 1);
setenv("INTERFACE", ifname, 1);
if (device)
setenv("DEVICE", device, 1);
if (event == IFEV_UPDATE) {
if (updated & IUF_ADDRESS)
setenv("IFUPDATE_ADDRESSES", "1", 1);
if (updated & IUF_ROUTE)
setenv("IFUPDATE_ROUTES", "1", 1);
if (updated & IUF_PREFIX)
setenv("IFUPDATE_PREFIXES", "1", 1);
if (updated & IUF_DATA)
setenv("IFUPDATE_DATA", "1", 1);
}
argv[0] = hotplug_cmd_path;
argv[1] = "iface";
argv[2] = NULL;
execvp(argv[0], argv);
exit(127);
}