Openwrt按键检测分析-窥探Linux内核与用户空间通讯机制netlink使用

2023-11-17

首先看一下Openwrt系统中关于按键功能的使用和修改,以18.06版本为例

按键功能实现在脚本中, 比如18.06/package/base-files/files/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" -ge 5 -a -n "$OVERLAY" ]
	then
		echo "FACTORY RESET" > /dev/console
		jffs2reset -y && reboot &
	fi
;;
esac

return 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;  //设备,来自pdev
	struct 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_dev
	ret = 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_data
		gpio_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;

//分配skb
	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 = 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 socket
	if ((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中的定义

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Openwrt按键检测分析-窥探Linux内核与用户空间通讯机制netlink使用 的相关文章

  • 如何阅读shell命令的源代码?

    我想阅读编写linux命令的实际源代码 我已经获得了一些使用它们的经验 现在我认为是时候与我的机器进行更深层次的交互了 我在这里找到了一些命令http directory fsf org wiki GNU http directory fs
  • 编写多个mysql脚本

    是否可以在复合脚本中包含其他 mysql 脚本 理想情况下 我不想为包含的脚本创建存储过程 对于较大的项目 我想分层维护几个较小的脚本 然后根据需要组合它们 但现在 我很乐意学习如何包含其他脚本 source是一个内置命令 您可以在 MyS
  • 如何使用ffmpeg重叠和合并多个音频文件?

    我正在尝试将多个音频文件合并到一个文件中 但我可以使用以下命令来连接 而不是连接 ffmpeg v debug i file1 wav i file2 wav i file3 wav filter complex 0 0 concat n
  • 使用脚本自动输入 SSH 密码

    我需要创建一个自动向 OpenSSH 输入密码的脚本ssh client 假设我需要通过 SSH 进入myname somehost用密码a1234b 我已经尝试过 bin myssh sh ssh myname somehost a123
  • 如何在 Linux x86_64 上模拟 iret

    我正在编写一个基于 Intel VT 的调试器 由于当 NMI Exiting 1 时 iret 指令在 vmx guest 中的性能发生了变化 所以我应该自己处理vmx主机中的NMI 否则 guest会出现nmi可重入错误 我查了英特尔手
  • Windows 与 Linux 文本文件读取

    问题是 我最近从 Windows 切换到 Ubuntu 我的一些用于分析数据文件的 python 脚本给了我错误 我不确定如何正确解决 我当前仪器的数据文件输出如下 Header 有关仪器等的各种信息 Data 状态 代码 温度 字段等 0
  • Linux 中的电源管理通知

    在基于 Linux 的系统中 我们可以使用哪些方法 最简单的方法 来获取电源状态更改的通知 例如 当计算机进入睡眠 休眠状态等时 我需要这个主要是为了在睡眠前保留某些状态 当然 在计算机唤醒后恢复该状态 您只需配置即可获得所有这些事件acp
  • 如何从 Linux 的 shell 中删除所有以 ._ 开头的文件?

    确实如标题所示 我已将许多文件从 Mac 复制到 Raspberry Pi 这导致了许多以前缀开头的多余文件 我想删除以以下开头的文件夹中的每个文件 我该怎么做 尝试类似的方法 cd path to directory rm rf 或者 如
  • 在 shell 脚本中查找和替换

    是否可以使用 shell 在文件中搜索然后替换值 当我安装服务时 我希望能够在配置文件中搜索变量 然后在该值中替换 插入我自己的设置 当然 您可以使用 sed 或 awk 来完成此操作 sed 示例 sed i s Andrew James
  • 如何在 Linux 主机上的 docker 容器中挂载目录 [重复]

    这个问题在这里已经有答案了 我想将一个目录从 docker 容器挂载到本地文件系统 该目录是网站根目录 我需要能够使用任何编辑器在本地计算机上编辑它 我知道我可以跑docker run v local path container path
  • “grep -q”的意义是什么

    我正在阅读 grep 手册页 并遇到了 q 选项 它告诉 grep 不向标准输出写入任何内容 如果发现任何匹配 即使检测到错误 也立即以零状态退出 我不明白为什么这可能是理想或有用的行为 在一个程序中 其原因似乎是从标准输入读取 处理 写入
  • Linux 上的 Python 3.6 tkinter 窗口图标错误

    我正在从 Python GUI 编程手册 学习 Python GUI 某项任务要求我通过将以下代码添加到我的配方中来更改窗口图标 Change the main windows icon win iconbitmap r C Python3
  • python:numpy 运行脚本两次

    当我将 numpy 导入到 python 脚本中时 该脚本会执行两次 有人可以告诉我如何阻止这种情况 因为我的脚本中的所有内容都需要两倍的时间 这是一个例子 usr bin python2 from numpy import print t
  • 无法仅在控制台中启动 androidstudio

    你好 我的问题是下一个 我下载了Android Studio如果我去 路径 android studio bin 我执行studio sh 我收到以下错误 No JDK found Please validate either STUDIO
  • 操作系统什么时候清除进程的内存

    进程在某些操作系统上成功或异常终止 操作系统何时决定擦除分配给该进程的内存 数据 代码等 在退出时或当它想为新进程分配内存时 这个清除内存分配过程在所有操作系统 winXP Win7 linux Mac 上都相同吗 据我了解 页表具有该进程
  • numpy 未定义符号:PyFPE_jbuf

    我正在尝试使用一百万首歌曲数据集 为此我必须安装 python 表 numpy cython hdf5 numexpr 等 昨天我设法安装了我需要的所有内容 在使用 hdf5 遇到一些麻烦之后 我下载了预编译的二进制包并将它们保存在我的 b
  • 如何在我的 AWS EC2 实例上安装特定字体?

    我有一个在 AWS EC2 Amazon Linux Elastic Beanstalk 实例上运行的 Python 应用程序 该实例需要某些特定字体才能生成输出 并且想知道如何在部署或实例启动过程中安装它们 我的代码在本地计算机 OS X
  • gentoo crontab:为什么这个简单的 crontab 不起作用?

    我使用 GENTOO 发行版 crontab e 35 12 root php5 home www cron php 当我手动运行时 php5 php5 home www cron php 这有效 它向我发送了一封电子邮件 然后我检查日期
  • git在Windows和Linux之间切换后强制刷新索引

    我有一个Windows和Linux共享的磁盘分区 格式 NTFS 它包含一个 git 存储库 约 6 7 GB 如果我只使用Windows or 只使用Linux操作 git 存储库一切正常 但是每次切换系统的时候git status命令将
  • 从多线程程序中调用 system()

    我们正在开发一个用 C 编写的多线程内存消耗应用程序 我们必须执行大量的 shellscript linux 命令 并获取返回码 读完之后article http www linuxprogrammingblog com threads a

随机推荐

  • Android Studio获取系统级签名方式

    android sharedUserId android uid system 系统签名 通过sharedUserId 拥有同一个User id的多个APK可以配置成运行在同一个进程中 那么把程序的UID配成android uid syst
  • CAS与ABA问题

    在JDK 5之前Java语言是靠synchronized关键字保证同步的 这会导致有锁机制存在以下问题 1 在多线程竞争下 加锁 释放锁会导致比较多的上下文切换和调度延时 引起性能问题 2 一个线程持有锁会导致其它所有需要此锁的线程挂起 3
  • Python爬虫到入门只需要三个月

    如何入门Python 为了能够帮助大家更轻松的学好Python开发 Python爬数据 Python数据分析等相关理论知识 给大家共同分享自己一套Python学习生活资料 文章最后面的有附属的相关资料 无论你是大牛还是小白 是想转行还是想入
  • C语言-扫雷游戏程序设计

    文章目录 一 问题要求 1 问题描述 2 程序的功能 二 基本要求 1 要求分析 2 需求分析 四 设计概要 1 程序的设计概要 2 程序的主要流程 1 设置棋盘 2 布地雷 五 用户说明 六 测试结果 1 运行结果说明 2 测试结论 七
  • 区块链基本加密概念

    什么是区块链 目前狭义就任务就是一个超级账本 区块链可以用来做什么 可以用来无障碍的置换 既然是用来交易的 那么我们就要有一个地址存放我的资产 地址 举例比特资产地址 一个比特币地址由两部分组成 一部分是公钥哈希值经过Base58check
  • 面试:Spring&SpringMVC&Mybatis 面试必备面试题

    Spring SpringMVC Mybatis常见面试题 历史文章 多线程史上最全面试题 持续更新中 dubbo zookeeper55道高频面试题 附加答案 SpringCloud SpringBoot经典面试题 附加答案 Spring
  • linux进程snprintf函数功能,linux 之 snprintf函数用法

    int snprintf char restrict buf size t n const char restrict format 函数说明 最多从源串中拷贝n 1个字符到目标串中 然后再在后面加一个0 所以如果目标串的大小为n 的话 将
  • MapReduce实现TopN的效果

    1 背景 最近在学习Hadoop的MapReduce 此处记录一下如何实现 TopN 的效果 以及在MapReduce中如何实现 自定义分组 2 需求 我们有一份数据 数据中存在如下3个字段 订单编号 订单项和订单项价格 输出的数据 需求如
  • 最近邻检索(NN)和近似最近邻(ANN)检索

    文章目录 1 最近邻检索 Nearest Neighbor Search 1 1 概述 1 2 应用领域 2 最近邻检索的发展 2 1 精确检索 2 2 近似检索 参考文献 1 最近邻检索 Nearest Neighbor Search 1
  • 解决导入aliyun-sdk-vod-upload后仍报错的问题

    在手动下载阿里云的aliyun sdk vod upload jar包并执行mvn install install file DgroupId com aliyun DartifactId aliyun sdk vod upload Dve
  • STM32 Freertos 添加 外部sram heap_5.c

    1 添加外部SRAM 初始化 2 添加heap 5 c 3 初始化heap 5 c 外部堆栈 Define the start address and size of the two RAM regions not used by the
  • MYSQL基础命令及添加用户及权限操作

    用root管理打开mysql 1 连接数据库 mysql u root p或者 mysql h192 168 222 132 uroot p通过ip远程连接数据库 mysql h host u user p password P port
  • 啊哈C语言——安装啊哈C编译器

    安装啊哈C编译器 首先访问啊哈C语言编译器的官网www ahacpp com 下载啊哈 C语言编译器安装包 Windows 单击下载之后会跳转到腾讯微云 选中文件复选框 然后单击保存微云 这个时候会弹出登录框 选择自己喜欢的登录方式 然后再
  • 华为OD机试 - 全量和已占用字符集(Java)

    题目描述 给定两个字符集合 一个是全量字符集 一个是已占用字符集 已占用字符集中的字符不能再使用 要求输出剩余可用字符集 输入描述 输入一个字符串 一定包含 前为全量字符集 后的为已占用字符集 已占用字符集中的字符一定是全量字符集中的字符
  • drop mysql,MySQL删除大表更快的DROP TABLE办法

    本文内容遵从CC版权协议 可以随意转载 但必须以超链接形式标明文章原始出处和作者信息及版权声明网址 http www penglixun com tech database mysql fast drop table use hard li
  • 【Python】植物大战僵尸-基于pygame模块-part2

    文章目录 版本介绍 一 种子栏类 SeedBank py 1 成员变量 2 方法 展示种子栏 二 主游戏类 MainGame py 1 成员变量 2 加载项 3 主事件项 三 最终效果 版本介绍 继上篇内容开发 该版本为ver1 1 后续版
  • go 进阶 请求代理相关: 二. ReverseProxy 基础讲解

    目录 一 ReverseProxy 基础 ReverseProxy 中提供了哪些功能 ReverseProxy 结构详解 ReverseProxy实现代理的简单示例 1 NewSingleHostReverseProxy 函数源码解释 2
  • 元宇宙大投资 & 元宇宙通证

    元宇宙大投资 元宇宙大投资 1 备战元宇宙大浪潮 元宇宙大投资 2 抓紧元宇宙本质 元宇宙大投资 3 决胜元宇宙投资 元宇宙大投资 4 元宇宙的终局 生物与数字的融合 元宇宙大投资 6 元宇宙中国之崛起 元宇宙大投资 7 全球投资脉络下的元
  • Linux 下搭建 Kafka 环境

    安装步骤 准备软件目录 mkdir datalake 上传之前下载好的安装包到 datalake 目录下 jdk 8u181 linux x64 gz kafka 2 11 2 1 0 tgz zookeeper 3 4 5 tar gz
  • Openwrt按键检测分析-窥探Linux内核与用户空间通讯机制netlink使用

    首先看一下Openwrt系统中关于按键功能的使用和修改 以18 06版本为例 按键功能实现在脚本中 比如18 06 package base files files etc rc button reset bin sh lib functi