Linux设备驱动之gpio-keys

Linux设备驱动之gpio-keys

前两个章节介绍了Linux字符设备和platform设备的注册,他们都是比较基础的,让大家理解Linux内核的设备驱动是如何注册、使用的。但在工作中,个人认为完全手写一个字符设备驱动的机会比较少,更多的都是基于前人的代码修修补补过三年。在内核驱动中,更多的会基于platform设备进行具体设备驱动的注册与使用,下面以内核原生的gpio-keys为例,向大家介绍一个简单的按键驱动是如何配置、工作的。

dts配置

前面有简单的介绍,设备树用于描述板端硬件信息,配合驱动进行使用的,Linux内核原生的gpio-keys驱动支持的dts如下:

	gpio-keys {compatible = "gpio-keys";	/* 用于匹配内核gpio-keys驱动 */autorepeat;					/* 标记是否自动重复该按键,想想常按的情况 */up {label = "GPIO Key UP";	/* 按键的标签 */linux,code = <103>;		/* 按键的键值,理解为键盘上的字母类 */gpios = <&gpio1 0 1>;	/* 使用的是哪个gpio */};down {label = "GPIO Key DOWN";linux,code = <108>;interrupts = <1 IRQ_TYPE_LEVEL_HIGH 7>;	/* 中断配置 */};};

上面介绍到的dts配置,最后在驱动端都会进行解析,使用,那么驱动又具体是如何使用的呢,继续。

gpio-keys的platform驱动

Linux内核基本都可以看到类似以下的代码:

[drivers/input/keyboard/gpio_keys.c]/* 设备驱动匹配信息 */
static const struct of_device_id gpio_keys_of_match[] = {{ .compatible = "gpio-keys", },{ },
};
MODULE_DEVICE_TABLE(of, gpio_keys_of_match);static struct platform_driver gpio_keys_device_driver = {.probe          = gpio_keys_probe,	/* platform设备驱动匹配了,则调用probe函数 */.shutdown       = gpio_keys_shutdown,.driver         = {.name   = "gpio-keys",.pm     = &gpio_keys_pm_ops,.of_match_table = gpio_keys_of_match,.dev_groups     = gpio_keys_groups,}
};static int __init gpio_keys_init(void)
{/* 注册platform driver */return platform_driver_register(&gpio_keys_device_driver);
}static void __exit gpio_keys_exit(void)
{platform_driver_unregister(&gpio_keys_device_driver);
}/* late_initcall与modeule_init的宏作用一样,* 都是将函数添加到驱动段,方便开机启动加载驱动,* late_initcall与modeule_init的差异是加载* 的优先级不一致*/
late_initcall(gpio_keys_init);
module_exit(gpio_keys_exit);

在platform和设备树章节已经介绍,当设备驱动匹配时,将会调用到probe函数。下面看看gpio-keys的probe是怎样获取dts信息的。PS:dts是怎么解析的,platform device是什么时候注册的,后面其他章节我们再进行介绍。

/** Handlers for alternative sources of platform_data*//** Translate properties into platform_data*/
static struct gpio_keys_platform_data *
gpio_keys_get_devtree_pdata(struct device *dev)
{struct gpio_keys_platform_data *pdata;struct gpio_keys_button *button;struct fwnode_handle *child;int nbuttons;/* 查看当前的gpio-keys节点有多少个子节点,每个节点代表一个按键 */nbuttons = device_get_child_node_count(dev);if (nbuttons == 0)return ERR_PTR(-ENODEV);pdata = devm_kzalloc(dev,sizeof(*pdata) + nbuttons * sizeof(*button),GFP_KERNEL);if (!pdata)return ERR_PTR(-ENOMEM);button = (struct gpio_keys_button *)(pdata + 1);pdata->buttons = button;pdata->nbuttons = nbuttons;/* 是否自动重复 */pdata->rep = device_property_read_bool(dev, "autorepeat");/* 按键的标签 */device_property_read_string(dev, "label", &pdata->name);device_for_each_child_node(dev, child) {if (is_of_node(child))button->irq =irq_of_parse_and_map(to_of_node(child), 0);/* 按键码值 */if (fwnode_property_read_u32(child, "linux,code",&button->code)) {dev_err(dev, "Button without keycode\n");fwnode_handle_put(child);return ERR_PTR(-EINVAL);}fwnode_property_read_string(child, "label", &button->desc);/* 输入的类型,一般设置为KEY */if (fwnode_property_read_u32(child, "linux,input-type",&button->type))button->type = EV_KEY;/* 该按键是否设置为唤醒源 */button->wakeup =fwnode_property_read_bool(child, "wakeup-source") ||/* legacy name */fwnode_property_read_bool(child, "gpio-key,wakeup");/* 唤醒的状态 */fwnode_property_read_u32(child, "wakeup-event-action",&button->wakeup_event_action);/* 是否可休眠 */button->can_disable =fwnode_property_read_bool(child, "linux,can-disable");/* 按键去抖配置 */if (fwnode_property_read_u32(child, "debounce-interval",&button->debounce_interval))button->debounce_interval = 5;button++;}return pdata;
}

在上面的代码可以看到,就是一个解析dts的操作,gpio_keys_get_devtree_pdata()将会进行以下操作:

  1. 确认gpio-keys节点存在多少个子节点,每个子节点代表一种按键功能;
  2. 获取”autorepeat“字段的信息赋值到pdata->rep,这个变量决定着是否允许重复发送事件;
  3. 获取各button的中断号、事件代码code、label、输入的类型、唤醒等信息;

之后驱动会依据这些信息进行按键驱动的适配以及操作。下面通过gpio-keys驱动学习Linux input子系统。

input子系统

Linux input子系统是linux内核用于管理各种输入设备的部分,内核将给用户导出一套固定的硬件无关的input API,供用户空间程序使用。
input系统分为三块:input core、input drivers和event handles。数据传输从底层硬件到input driver,再经过input core到event handles,最后到达用户空间。

input core

input子系统的core代码主要是input.c,该文件集成模块,模块的注册函数实现如下:

static int __init input_init(void)
{int err;/* 注册input类 */err = class_register(&input_class);if (err) {pr_err("unable to register input_dev class\n");return err;}/* 在/proc创建 bus/input/devices handlers */err = input_proc_init();if (err)goto fail1;/* 注册input字符设备 */err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),INPUT_MAX_CHAR_DEVICES, "input");if (err) {pr_err("unable to register char major %d", INPUT_MAJOR);goto fail2;}return 0;fail2:	input_proc_exit();fail1:	class_unregister(&input_class);return err;
}

在input.c是input core的核心文件,input core主要是承上启下,为input drivers提供输入设备注册和操作接口,如input_register_device()函数;通知event handles对事件进行处理;在/proc下产生相应的设备信息。input core将会负责将input drivers和event handles联通,具体是如何完成这个操作的呢,继续看input drivers和event handles。

相关结构体

[linux/input.h]
/** The event structure itself*/struct input_event {struct timeval time;	/* 输入事件时间 */__u16 type;			/* 类型 */__u16 code;			/* 事件代码 [linux/input-event-codes.h] */__s32 value;			/* 事件值(当type为EV_KEY时,value:0表示按键抬起,1表示按键按下) */
};[linux/input-event-codes.h]
/** Event types*/#define EV_SYN                  0x00	/* 所有input设备都具备的同步事件,主要是与client同步事件队列 */
#define EV_KEY                  0x01	/* 按键 */
#define EV_REL                  0x02	/* 鼠标事件 相对坐标 */
#define EV_ABS                  0x03	/* 手写板事件 绝对坐标 */
#define EV_MSC                  0x04	/* 其他类型 */
#define EV_SW                   0x05	/* 开关状态事件 */
#define EV_LED                  0x11	/* LED事件 */
#define EV_SND                  0x12	/* 音频事件 */
#define EV_REP                  0x14	/* 用于指定自动重复事件 */
#define EV_FF                   0x15	/* 用于初始化具有力反馈功能的设备并使该设备反馈 */
#define EV_PWR                  0x16	/* 电源管理 */
#define EV_FF_STATUS            0x17	/* 用于接收力反馈设备状态 */
#define EV_MAX                  0x1f
#define EV_CNT                  (EV_MAX+1)

gpio_keys_probe()

继续回到gpio_keys_probe()函数,从dts中获取到button信息之后,将会通过调用devm_input_allocate_device()([input.c])函数创建input_dev设备。接着是通过gpio_keys_setup_key()函数,为各个button申请相应的GPIO、中断资源等。最后,通过input_register_device()函数,注册input设备。至此,可以看到input drivers注册完成。

input handle

input handle的相关操作在evdev.c实现,该文件恰好自己构成一个模块,先分析模块的注册函数,注册函数只是调用了input_register_handler()([input.c]),该函数主要是为系统中的输入设备注册一个新的输入处理程序,并将其附加到与该处理程序兼容的所有输入设备上。该模块将在input dev注册时,将会通过input core完成与input dev完成相应的connect,当event发生时,又将会处理相应的event事件并上报。

input core、input drivers和event handles三块的简单介绍如上,下面,我们将带着问题去阅读代码,进一步了解Linux input子系统。

FAQ

input core是如何知道,当前有多少输入设备,而这些输入设备又分别是支持什么类型的事件?

在介绍上面的input drivers的时候就有提到,在driver的probe函数中,将会通过input_register_device()函数注册input device,下面来分析一下该函数的实现。

int input_register_device(struct input_dev *dev)
{/* 检查input dev支持的事件类型,注册device等 */...error = mutex_lock_interruptible(&input_mutex);if (error)goto err_device_del;/* 将该input device添加到input_dev_list链表 */list_add_tail(&dev->node, &input_dev_list);/* 在linux input子系统中,一个input device的输入事件* 将会发送到系统中所有的event handles,所以这里从保存* event handles的全局链表input_handler_list中,逐个获* 取event handles添加input device */list_for_each_entry(handler, &input_handler_list, node)input_attach_handler(dev, handler);/* 当注册input device成功,将会通过该函数唤醒profs的poll线程 */input_wakeup_procfs_readers();mutex_unlock(&input_mutex);...
}

然后我们再回到这个问题,input core是如何知道有多少的input devices,显然,是在通过input_register_device()注册input device的时候,同时会把input device添加到全局链表input_dev_list[input.c]中,这样,input core通过枚举input_dev_list链表,就可以得到相应的input device。
还剩一个问题,这些input device支持的事件类型和事件代码,又是从哪里填充的呢?
上面在介绍input device的时候,我们是以gpio-keys为例,同样的,在这里我们继续以它为例,也即gpio_keys.c。
在input device的probe()函数中,如上面介绍,将会通过gpio_keys_get_devtree_pdata()函数从dts中获取button的相应信息,接着通过gpio_keys_setup_key()将上面获取得到的信息填充到input dev,比如通过input_set_capability()函数设置input dev支持的事件类型等。
这样,linux内核就知道,当前的input dev支持什么事件type以及code。

当发生输入事件时,信息又是如何传递到应用层(用户空间)?

继续以gpio-keys作为input dev例子进行这个问题的解答。
回到gpio_keys_setup_key()函数,在该函数中,获取gpio之后,将会申请相应的中断,同时设置中断函数,中断函数有两个,分别是gpio_keys_gpio_isr()和gpio_keys_irq_isr(),将会根据gpio的信息相应的选择其中一个,以gpio_keys_gpio_isr()中断函数,当相应的gpio中断信号到来时,系统将会调用该函数,而在该函数中,又将存在以下代码:

static irqreturn_t gpio_keys_gpio_isr(int irq, void *dev_id)
{...mod_delayed_work(system_wq,&bdata->work,msecs_to_jiffies(bdata->software_debounce));...

同时的,在gpio_keys_setup_key()函数中,是这样初始化bdata->work,所以,在中断函数中,设置延迟一段时间执行gpio_keys_gpio_work_func()函数。

	INIT_DELAYED_WORK(&bdata->work, gpio_keys_gpio_work_func);

而gpio_keys_gpio_work_func()函数也很简单,主要是通过gpio_keys_gpio_report_event()函数报告相应的事件信息。

static void gpio_keys_gpio_work_func(struct work_struct *work)
{struct gpio_button_data *bdata =container_of(work, struct gpio_button_data, work.work);gpio_keys_gpio_report_event(bdata);if (bdata->button->wakeup)pm_relax(bdata->input->dev.parent);
}static void gpio_keys_gpio_report_event(struct gpio_button_data *bdata)
{const struct gpio_keys_button *button = bdata->button;struct input_dev *input = bdata->input;unsigned int type = button->type ?: EV_KEY;int state;/* 获取gpio的状态,是高电平还是低电平 */state = gpiod_get_value_cansleep(bdata->gpiod);if (state < 0) {dev_err(input->dev.parent,"failed to get gpio state: %d\n", state);return;}/* 通过input_event()函数报告input事件 */if (type == EV_ABS) {if (state)input_event(input, type, button->code, button->value);} else {input_event(input, type, *bdata->code, state);}/* 同步事件 */input_sync(input);
}void input_event(struct input_dev *dev,unsigned int type, unsigned int code, int value)
{unsigned long flags;/* 先检查input dev是否支持该type,这个dev->evbit是在调用input_set_capability()时设置的 */if (is_event_supported(type, dev->evbit, EV_MAX)) {spin_lock_irqsave(&dev->event_lock, flags);input_handle_event(dev, type, code, value);spin_unlock_irqrestore(&dev->event_lock, flags);}
}

而在input_handle_event()函数中,将会先通过input_get_disposition()函数回去事件的相应信息。

#define INPUT_IGNORE_EVENT	0		/* 忽略该事件 */
#define INPUT_PASS_TO_HANDLERS	1	/* input handles处理该事件 */
#define INPUT_PASS_TO_DEVICE	2	/* input device处理该事件 */
#define INPUT_SLOT		4
#define INPUT_FLUSH		8
#define INPUT_PASS_TO_ALL	(INPUT_PASS_TO_HANDLERS | INPUT_PASS_TO_DEVICE)

gpio-keys中,事件是由handles处理,所以在input_handle_event()函数中,将会将事件的信息填充到input dev的vals数组。至此,回到gpio_keys_gpio_report_event()函数,将事件信息填充到input dev的相应结构体之后,最后将会调用input_sync(input)函数同步事件。

static inline void input_sync(struct input_dev *dev)
{input_event(dev, EV_SYN, SYN_REPORT, 0);
}

通过input_sync()函数的代码我们可以知道,最终是发送EV_SYN事件,code为SYN_REPORT,这样的一个事件代码,将会在input_handle_event()函数中调用input_pass_values()函数,激发input handles处理事件。
而在input_pass_values()函数中,重要的也是以下部分:

static void input_pass_values(struct input_dev *dev,struct input_value *vals, unsigned int count)
{.../* 一般的,handle会为空,所以执行else部分代码 */handle = rcu_dereference(dev->grab);if (handle) {count = input_to_handler(handle, vals, count);} else {/* 从input dev的dev->h_list链表获取event handles,* 上面就有提到,一个input dev的事件,将会发送到所有的handles */list_for_each_entry_rcu(handle, &dev->h_list, d_node)if (handle->open) {count = input_to_handler(handle, vals, count);if (!count)break;}}...
}

代码跟踪到这里,又多了一个疑问:dev->h_list这个链表是什么时候填充的?
在input_register_device()函数中,通过调用input_attach_handler()函数,将event handles添加到dev->h_list,函数调用流程如下:

	input_register_device()input_attach_handler()handler->connect(handler, dev, id)evdev_connect()input_register_handle()list_add_rcu(&handle->d_node, &dev->h_list)/list_add_tail_rcu(&handle->d_node, &dev->h_list)

了解到dev->h_list链表的填充过程之后,继续回到input_pass_values()函数,在该函数中,将会针对enable的event handle调用input_to_handler()函数,而在input_to_handler()函数重要的是调用handler的events函数—evdev_events()。

/** Pass incoming events to all connected clients.*/
static void evdev_events(struct input_handle *handle,const struct input_value *vals, unsigned int count)
{...client = rcu_dereference(evdev->grab);if (client)evdev_pass_values(client, vals, count, ev_time);else/* 主要是通过client_list链表获取handle client进行事件处理,* 可以将client理解为一个用户层的接收者,在open event时创建 */list_for_each_entry_rcu(client, &evdev->client_list, node)evdev_pass_values(client, vals, count, ev_time);...
}

在evdev_pass_values()函数中,将会通过__pass_event()将event信息填充到client的buffer缓冲区,如果code是SYN_REPORT,将会调用kill_fasync(&client->fasync, SIGIO, POLL_IN)异步通知应用层,这样,input event传递到应用层,接着,用户程序就可以通过read函数读取event的详细信息。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/83001.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

你的第1个 Unity 游戏!!!

简介 首先新建一个方块添加重力!!!总结首先 首先,你要先打开 U n i t y Unity Unity

Linux 挂载

挂载需要挂载源和挂载点 虚拟机本身就有的挂源 添加硬件 重启虚拟机 操作程序 sudo fdisk -l //以管理员权限查看电脑硬盘使用情况sudo mkfs.ext4 /dev/sdb //以管理员身份格式化硬盘sudo mkdir guazai //创建挂载文件夹 sudo mount /dev/sdb/guazai //将挂载源接上挂载点 s…

嵌入式C语言知识复习和提高

文章目录 前言基础知识main函数防BUG注释&#xff08;重要&#xff09;关键字标识符命名&#xff08;驼峰命名&#xff09;常量类型变量printf1.输出不同类型数据2.输出不同宽度数据3.不同类型数据长度归类 scanf函数运算符sizeof&#xff08;运算符&#xff0c;优先级2&#x…

欧拉操作系统在线安装mysql8数据库并用navicat premium远程连接

网上太多安装教程&#xff0c;但是没有一个教程能够一站式解决安装问题&#xff0c;而我不一样&#xff0c;我写的每一个博客&#xff0c;都是我自己试验过很多次&#xff0c;能够确保一站式解决&#xff0c;才会发上去&#xff0c;希望能够帮助到大家 第一步&#xff1a;升级…

Java手写背包问题算法应用拓展案例

Java手写背包问题算法应用拓展案例 1. 0-1背包问题 实际案例&#xff1a;购物问题 假设你是一个购物爱好者&#xff0c;你去商场购物&#xff0c;商场里有很多商品&#xff0c;每个商品有自己的重量和价值。你只有一个背包&#xff0c;它的容量是有限的。你希望在购物过程中…

恒合仓库 - 仓库管理系统搭建

仓库管理系统搭建 文章目录 仓库管理系统搭建一、项目介绍1.1 项目描述1.2 技术选型1.3 模块划分 二、搭建前端环境三、搭建后端环境3.1 创建springboot项目 - 划分包层次3.2 导入依赖3.3 启动类配置3.4.配置文件配置 一、项目介绍 1.1 项目描述 项目搭球为满足日益扩大的仓库…

Matlab图像处理-区域描述

一旦一幅图像的目标区域被确定&#xff0c;我们往往用一套描述子来表示其特性。选择区域描述子的动机不单纯为了减少在区域中原始数据的数量&#xff0c;而且也应有利于区别带有不同特性的区域。因此&#xff0c;当目标区域有大小、旋转、平移等方面的变化时&#xff0c;针对这…

2023 亲测好用版VScode配置文件

tasks.json {"tasks": [{"type": "cppbuild","label": "g++",// 调试会话开始前执行的任务,一般为编译程序,c++为g++, c为gcc 和launch中preLaunchTask保持一致// "command": "D:/Users/Downloads/ming…

力扣 -- 394. 字符串解码

解题方法&#xff1a; 参考代码&#xff1a; class Solution{ public:string decodeString(string s){stack<string> sst;stack<int> dst;//防止字符串栈为空的时候再追加字符串到栈顶元素sst.push("");int n s.size();int i 0;while(i<n)//最好不…

高级运维学习(九)块存储、文件系统存储和对象存储的实现

块存储基础 块设备存取数据时&#xff0c;可以一次存取很多。字符设备只能是字符流 [rootceph1 ~]# ll /dev/sda brw-rw---- 1 root disk 8, 0 Dec 12 13:15 /dev/sda # b表示block&#xff0c;块设备[rootceph1 ~]# ll /dev/tty crw-rw-rw- 1 root tty 5, 0 Dec 12 13:31 /d…

工业机器人仿真参考

最近有一些朋友看到我做的关于Unity3d仿真机器人的项目&#xff0c;本次我在平台做以分享&#xff0c;希望的朋友或者有需要在此基础做开发的可以参考下。 开发工具&#xff1a; 下位机&#xff1a;Unity3D 上位机&#xff1a;Visual Studio 机械臂模型&#xff1a;TH6-QKM…

从零开始:使用Python创建GUI驱动的简易国际象棋游戏

第一部分&#xff1a;国际象棋的基础 1. 介绍 国际象棋&#xff0c;一个古老而又充满策略的游戏&#xff0c;历经数世纪的发展&#xff0c;至今仍然广受喜爱。那么&#xff0c;如何使用Python来创建一个简单的国际象棋游戏&#xff0c;并给它加上一个图形界面(GUI)呢? 这篇文…

Python图像处理初探:Pillow库的基础使用

图像处理在许多领域都有广泛的应用&#xff0c;包括计算机视觉&#xff0c;机器学习&#xff0c;人工智能&#xff0c;网页开发等。Pillow库是Python中最流行的图像处理库之一&#xff0c;它是PIL&#xff08;Python Imaging Library&#xff09;的一个分支&#xff0c;提供了丰…

高云FPGA系列教程(7):ARM GPIO外部中断

文章目录 [toc]GPIO中断简介FPGA配置常用函数MCU程序设计工程下载 本文是高云FPGA系列教程的第7篇文章。 本篇文章介绍片上ARM Cortex-M3硬核处理器GPIO外部的使用&#xff0c;演示按键中断方式来控制LED亮灭&#xff0c;基于TangNano 4K开发板。 参考文档&#xff1a;Gowin_E…

Android 下的usb框架及功能点

有关USB android框架的链接 http://blog.sina.com.cn/s/articlelist_1627432177_0_1.html ICS4.0下Framework层的usb框架 Android 下的usb主要工作还是在android的framework层。主要有以下几个文件&#xff1a; 1.1UsbDeviceManager.java/高 主要完成功能切换及状态的更新&a…

L1-027 出租

一、题目再现 下面是新浪微博上曾经很火的一张图&#xff1a; 一时间网上一片求救声&#xff0c;急问这个怎么破。其实这段代码很简单&#xff0c;index数组就是arr数组的下标&#xff0c;index[0]2 对应 arr[2]1&#xff0c;index[1]0 对应 arr[0]8&#xff0c;index[2]3 对应…

消息队列(五):虚拟主机设计

虚拟主机存在的意义 一个虚拟主机类似于 MySQL 的 database&#xff0c;把交换机&#xff0c;队列&#xff0c;绑定&#xff0c;消息....进⾏逻辑上的隔离&#xff0c;⼀个服务器可以有多 个虚拟主机&#xff0c;此处我们项⽬就设计了⼀个虚拟主机&#xff08;VirtualHost&…

一步一步详细介绍如何使用 Mediapipe 创建 Snapchat/Instagram 的过滤器--提供完整的实现源码

介绍 既然 Snapchat 和 Instagram 提供的滤镜可以让你在几秒钟内随心所欲地看起来狂野、异国情调或美丽,为什么还要投资化妆品或时尚服装和眼镜呢? 也许您只想闲逛并变成女巫或圣诞老人,或者也许只是通过在脸上添加滤镜来变成您最喜欢的虚构人物。有数百个这样的过滤器,…

GO-日志分析

GO-日志分析 log包简介 Go提供了logger包来做日志记录。使用方式如下所示 package mainimport ("log""os" )func main() {// 创建一个新的日志文件.默认是stdOutfile, err : os.Create("app.log")if err ! nil {log.Fatal(err)}defer file.Cl…

源码编译Qt 5.15.9+msvc2019

官方文档里给出了详细步骤&#xff1a; Building Qt Sources Building Qt 5 from Git (Wiki) 注&#xff1a;本文基于windows11vs2019x64qt5.15.9&#xff0c;不编译Qt WebEngine 归纳总结如下&#xff1a; 准备阶段 Qt for Windows - Requirements 安装python&#xff0c;…