11-Openwrt hotplug system

31561 ワード

hotplugはlinuxの1つのホットスワップシステムで、多くの応用で役に立ちます.例えば、ネットポートのスワップ、USBのスワップ、ボタンのトリガ...非常に広範な応用で、以下にいくつかの例を挙げてこの過程を理解します.

1.gpio-button-hotplug(カーネル層がprocdにhotplugを送信)


gpio-button-hotplugはkernelのpackageであり、package/kernel/gpio-button-hotplug/に位置する.CONFIG_PACKAGE_kmod-gpio-button-hotplug=yオプションを開くには
1.1駆動
「gpio-keys」と「gpio-keys-polled」は、次の2つの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
    };
};

上記のパラメータは、gpio_keys_get_devtree_pdata関数で解析的にフォーマットが正しいかどうかを判断し、エラーは登録開始時にエラー情報を直接提示します.
もう1つはarchに登録しzkernel/3.10.49/arch/mips/mtk/dev-gpio-buttons.cには登録インタフェースが提供されており、呼び出せばいいです.
#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の中の数値を数値に書いたのは、より下位の定義を見せるためで、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には次のような定義があるので、最終的な数値がわかります.
#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のアプリケーション層処理
ボタンを押すとbutton_hotplug_event関数(gpio-button-hotplug.c):button_hotplug_create_eventを呼び出してueventイベントを生成し、button_hotplug_fill_evenを呼び出してイベント(JSON形式)を充填し、最終的にbroadcast_ueventを呼び出してueventブロードキャスト情報を発行し、カーネルnetlink_broadcast関数(linux-3.10.49/net/netlink/af_netlink.c)
Netlinkの実現原理は,次の文書の紹介を見ることができ,socket通信のためにカーネルがsocketブロードキャストを発行し,上位アプリケーション(procd)はこのsocketイベントを傍受するだけでよい.https://blog.csdn.net/Swallow_he/article/details/84073545
上記ブロードキャストは、procdプロセスにおけるhotplug_handler(procd/plug/hotplug.c)が受け取り、etc/hotplug.jsonで予め定義されたJSONコンテンツマッチング条件は、対応する実行関数に位置付けられ、具体的には:
[ "if",
        [ "and",
                [ "has", "BUTTON" ],
                [ "eq", "SUBSYSTEM", "button" ],
        ],
        [ "exec", "/etc/rc.button/%BUTTON%" ]
],

最終的に/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ポートケーブルが挿入されているかどうかを検査する(phyカーネル発行)


カーネルがWANポートの変化を検出するとhotplugメッセージ(broadcast_uevent)が作成され、procdに送信され、対応するモジュールに転送されます.
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)
", event->port_num, event->action); } else { printk(KERN_NOTICE "phy: port%u %s(dev)
", 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); }

発行後、次のスクリプトがトリガーされ、必要なコンテンツがスクリプトに追加されます.
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.ネットワーク検出LED点滅追加(/sbin/hotplug-call)


3.1 zdetectネットワーク検出モジュール
zrouter/zpackages/zihome/utils/zdetect/src/zdetect.cではhotplug eventがネットワークが変化したときに送信されます.
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 led実行スクリプトを呼び出す
14.07/package/base-files/files/sbin/zihome_led
#!/bin/sh

. /lib/functions/leds.sh

led_path="zihome:""$1"

led

4.iface(netifd)


ネットワークインタフェースが起動(up)または閉じるたびに/etc/hotplug.d/iface/ディレクトリのスクリプトはアルファベット順に実行されます.不文律に従って、各スクリプトの前に数字の接頭辞を付けて、正しい実行順序を設定します.スクリプト名が/etc/hotplugに似ている理由です.d/iface/-の理由.
変数名
説明
ACTION
「ifup」または「ifdown」
INTERFACE
「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(&current->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)
", 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);
}