Openwrt按键检测分析-窥探Linux内核与用户空间通讯机制netlink使用
首先看一下Openwrt系統中關于按鍵功能的使用和修改,以18.06版本為例
按鍵功能實現在腳本中, 比如18.06/package/base-files/files/etc/rc.button/reset
#!/bin/sh. /lib/functions.shOVERLAY="$( grep ' /overlay ' /proc/mounts )"case "$ACTION" in pressed)[ -z "$OVERLAY" ] && return 0return 5 ;; timeout). /etc/diag.shset_state failsafe ;; released)if [ "$SEEN" -lt 1 ]thenecho "REBOOT" > /dev/consolesyncrebootelif [ "$SEEN" -ge 5 -a -n "$OVERLAY" ]thenecho "FACTORY RESET" > /dev/consolejffs2reset -y && reboot &fi ;; esacreturn 0主要有2個參數, ACTION和SEEN,分別代表按鍵動作(按下/抬起)和按鍵持續時間
關于按鍵GPIO的修改位于dts中,比如target/linux/ramips/dts/GL-MT300N-V2.dts
gpio-keys {compatible = "gpio-keys-polled";#address-cells = <1>;#size-cells = <0>;poll-interval = <20>;vccin {label = "BTN_0";gpios = <&gpio0 6 0>;linux,code = <BTN_0>;};electricity {label = "BTN_1";gpios = <&gpio0 11 0>;linux,code = <BTN_1>;};reset {label = "reset";gpios = <&gpio1 7 GPIO_ACTIVE_LOW>;linux,code = <KEY_RESTART>;};};poll-interval = <20>; 表示輪詢檢測,防抖時間
label = "reset"; 代表功能實現腳本的名稱,對應18.06/package/base-files/files/etc/rc.button/reset
gpios = <&gpio1 7 GPIO_ACTIVE_LOW>; 表示使用GPIO1分組,第7個引腳; 低電平生效
linux,code = <KEY_RESTART>; 按鍵事件代碼,對于linux標準輸入輸出系統,參考Linux內核頭文件input/input.h
compatible = "gpio-keys-polled"; 表示加載驅動gpio-keys-polled
位于18.06/package/kernel/gpio-button-hotplug/src/gpio-button-hotplug.c
它具體實現主要內容如下:
1. 注冊2種類型驅動,輪詢檢測和中斷檢測
2. 使用netlink與用戶空間通訊
3. 自定義上報事件內容, 比如前面功能實現腳本中用到的ACTION以及SEEN等
分析內核驅動,首先要看全局結構體定義,這幾乎是分析linux所有驅動的共性
2個主要結構
struct gpio_keys_platform_data *pdata; 滿足linux內核驅動模型, platform總線驅動設備描述結構,主要存放的是dts中關于設備的描述信息
struct gpio_keys_button_dev *bdev;? 驅動私有描述結構
其二者關系為,
1. 取出pdata信息初始化bdev
2. 把bdev設置為pdata的私有數據,與其他驅動接口同步,platform_set_drvdata(pdev, bdev);
以上的操作為linux驅動的標準執行過程,有興趣的同學可以深入研究下Linux內核驅動模型
對于驅動來說,最重要的是驅動自己的私有結構,即gpio_keys_button_dev
struct gpio_keys_button_dev {int polled; //是否輪詢struct delayed_work work; //用于輪詢時的循環檢測struct device *dev; //設備,來自pdevstruct gpio_keys_platform_data *pdata; //platform總線設備描述struct gpio_keys_button_data data[0];//事件上報描述結構 };事件描述gpio_keys_button_data
struct gpio_keys_button_data {struct delayed_work work; //執行上報動作struct bh_priv bh;int last_state; //狀態記錄int count; //按鍵計時int threshold; //防抖域值int can_sleep; //是否支持睡眠struct gpio_keys_button *b; //中斷設備描述 };介紹完驅動結構描述,下面看其使用
首先看輪詢檢測:
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),}, };主要實現函數gpio_keys_polled_probe
static int gpio_keys_polled_probe(struct platform_device *pdev) {struct gpio_keys_platform_data *pdata;struct gpio_keys_button_dev *bdev;int ret;int i;//從platform總線設備描述gpio_keys_platform_data獲取信息, //初始化驅動私有結構gpio_keys_button_devret = gpio_keys_button_probe(pdev, &bdev, 1);if (ret)return ret;//初始化工作隊列INIT_DELAYED_WORK(&bdev->work, gpio_keys_polled_poll);pdata = bdev->pdata;if (pdata->enable)pdata->enable(bdev->dev);for (i = 0; i < pdata->nbuttons; i++) //逐個檢測button狀態,準備上報事件數據gpio_keys_button_datagpio_keys_polled_check_state(&bdev->data[i]);//循環檢測button狀態gpio_keys_polled_queue_work(bdev);return ret; }具體事件上報位于button_hotplug_create_event
static int button_hotplug_create_event(const char *name, unsigned int type,unsigned long seen, int pressed) {struct bh_event *event;BH_DBG("create event, name=%s, seen=%lu, pressed=%d\n",name, seen, pressed);event = kzalloc(sizeof(*event), GFP_KERNEL);if (!event)return -ENOMEM;// 填充事件信息event->name = name;event->type = type;event->seen = seen;event->action = pressed ? "pressed" : "released";// 在工作隊列中上報事件INIT_WORK(&event->work, (void *)(void *)button_hotplug_work);schedule_work(&event->work);return 0; }上報事件的netlink實現button_hotplug_work
static void button_hotplug_work(struct work_struct *work) {struct bh_event *event = container_of(work, struct bh_event, work);int ret = 0;//分配skbevent->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 = button_hotplug_fill_event(event);if (ret)goto out_free_skb;//發送netlink消息NETLINK_CB(event->skb).dst_group = 1;broadcast_uevent(event->skb, 0, 1, GFP_KERNEL);out_free_skb:if (ret) {BH_ERR("work error %d\n", ret);kfree_skb(event->skb);}out_free_event:kfree(event); }整個流程總結: 周期循環調用gpio_keys_polled_probe檢測按鍵狀態,滿足條件發送netlink消息
gpio_keys_polled_probe
????????--> gpio_keys_polled_check_state
????????????????--> button_hotplug_create_event
????????????????????????-->button_hotplug_work
????????????????????????????????--> broadcast_uevent
有了以上的分析基礎,中斷檢測流程就變得簡單了
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),}, };主要流程如下:
gpio_keys_probe
? --> devm_request_threaded_irq? 注冊中斷
??? --> button_handle_irq 中斷處理函數
??????? --> button_hotplug_create_event
??????????? -->button_hotplug_work
??????????????? -->broadcast_uevent
關于Netlink
Netlink套接字是用以實現用戶進程與內核進程通信的一種特殊的進程間通信(IPC) ,也是網絡應用程序與內核通信的最常用的接口。
在Linux 內核中,使用netlink 進行應用與內核通信的應用有很多,如
- 路由 daemon(NETLINK_ROUTE)
- 用戶態 socket 協議(NETLINK_USERSOCK)
- 防火墻(NETLINK_FIREWALL)
- netfilter 子系統(NETLINK_NETFILTER)
- 內核事件向用戶態通知(NETLINK_KOBJECT_UEVENT)
- 通用netlink(NETLINK_GENERIC)
Netlink 是一種在內核與用戶應用間進行雙向數據傳輸的非常好的方式,用戶態應用使用標準的 socket API 就可以使用 netlink 提供的強大功能,內核態需要使用專門的內核 API 來使用 netlink。
Openwrt系統中netlink消息接收,位于procd-2018-03-28-dfb68f85/plug/hotplug.c
procd是openwrt系統的init進程,負責內核netlink消息接收處理,watchdog以及執行一些循環任務等等
void hotplug(char *rules) {struct sockaddr_nl nls = {};int nlbufsize = 512 * 1024;rule_file = strdup(rules);nls.nl_family = AF_NETLINK;nls.nl_pid = getpid();nls.nl_groups = -1;//創建netlink socketif ((hotplug_fd.fd = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT)) == -1) {ERROR("Failed to open hotplug socket: %m\n");exit(1);}if (bind(hotplug_fd.fd, (void *)&nls, sizeof(struct sockaddr_nl))) {ERROR("Failed to bind hotplug socket: %m\n");exit(1);}if (setsockopt(hotplug_fd.fd, SOL_SOCKET, SO_RCVBUFFORCE, &nlbufsize, sizeof(nlbufsize)))ERROR("Failed to resize receive buffer: %m\n");json_script_init(&jctx);queue_proc.cb = queue_proc_cb; //循環接收netlink消息uloop_fd_add(&hotplug_fd, ULOOP_READ); }調用功能實現腳本時機, procd收到netlink消息后會根據配置文件來搜尋相關功能實現腳本
?按鍵事件的配置文件為 procd/etc/hotplug.json
[ "if",[ "and",[ "has", "BUTTON" ],[ "eq", "SUBSYSTEM", "button" ]],[ "button", "/etc/rc.button/%BUTTON%" ]],? 可解讀為: procd收到來自BUTTON的消息,最終執行/etc/rc.button/下的腳本,腳本名稱為事件中的button字段取值, 在驅動中以數組方式定義,如下:
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"), };BH_MAP第一個參數BTN_0或KEY_POWER對應input子系統的input.h中標準定義;
第二個參數"BTN_0"或"reset"對應/etc/rc.button/下的腳本名稱
至此,openwrt按鍵檢測整個過程分析完畢
最后總結:
1. Openwrt系統按鍵內核驅動分為兩種, 循環檢測和中斷檢測, 在dts中配置
2. 按鍵驅動通過netlink方式發送按鍵事件到用戶空間
3. 在用戶空間, init進程procd統一接收處理來自內核的netlink消息,同時根據配置文件,調用/etc/rc.button/下的腳本, 此處需注意腳本的可執行權限
4. dts中關于按鍵的配置, linux,code為input子系統標準事件定義,即input.h中的定義
總結
以上是生活随笔為你收集整理的Openwrt按键检测分析-窥探Linux内核与用户空间通讯机制netlink使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ARM GIC简介与Linux中断处理分
- 下一篇: Linux Mutex机制与死锁分析