11-Openwrt hotplug system

作者: Creator_Ly | 来源:发表于2019-07-13 17:03 被阅读2次

    hotplug为linux的一个热拔插系统,在很多应用都有用到,如网口的拔插,USB的拔插,按键的触发...非常广泛的应用,下面举几个例子来进行理解这个过程

    1.gpio-button-hotplug(内核层发送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
        };
    };
    

    上面的这些参数,在gpio_keys_get_devtree_pdata函数里面都会进行解析判断格式是否正确,错误在启动注册时就会直接提示错误信息。

    另一种就是在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)\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);
    }
    

    发出后就会触发以下脚本,在脚本里面添加我们需要的内容

    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)或者关闭(down)的时候,所有在/etc/hotplug.d/iface/目录中的脚本都会以字母顺序执行。根据一个不成文的规则,会在每个脚本的前面加上一个数字前缀来设置正确的运行顺序。这就是为什么脚本名称都像:/etc/hotplug.d/iface/<nn>-<scriptname>的原因。

    变量名称 描述
    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)\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);
    }
    

    相关文章

      网友评论

        本文标题:11-Openwrt hotplug system

        本文链接:https://www.haomeiwen.com/subject/klemkctx.html