输入驱动知识点
1> 逻辑简单,功能简单的输入的实现 --就有限个按键的输入实现
这种情况直接使用基本驱动框架来实现,正常情况下和输入相关的驱动都需要通过input子系统来实现
代码实现流程:
1. 设备树 在设备树文件中定义pinctrl+key的设备节点 --确保定义的pin/gpio信息的唯一性
2. 驱动文件
a. 声明驱动信息(author,GPL协议)
b. 加载驱动 (module_init)
c. 实现驱动加载函数
1. 定义全局设备信息结构体变量
2. 原子操作 (原子变量就是按键值,对读取到的按键值进行保护)
3. 注册设备号
4. 初始化cdev (将设备号和fops通过cdev建立关系)
5. 创建设备类
6. 创建设备文件
7. 硬件初始化
获取设备节点
获取GPIO编号
gpio操作
8. 实现fops
open() ==>将设备结构体信息保存在私有数据中
read() ==>从私有数据中取出设备信息结构体,读取key对应gpio的值,然后通过copy_to_user()上报给应用层
close()==>不需要什么操作。。
9. 错误集中处理 goto实现
d. 实现驱动卸载函数
直接将前面错误集中处理的代码拿过来就可以
一句话: 后申请的先释放
3. 应用文件
1. 打开设备文件 open()
2. 循环读取按键值 read()
3. 关闭设备文件 close()
2> input子系统简介
输入设备有哪些? 按键/鼠标/键盘/触摸屏 ; 输入设备本质上还是字符设备,所以我们前面使用字符设备的基本框架实现了读取按键的值
linux专门做了一个叫做input子系统的框架来处理输入事件
input子系统框架: 用户只需要在驱动中上报读取到的输入事件即可,系统会在input_handle层自动处理这些事件,然后将按键信息组装成input_event结构体传递给用户层
什么是按键信息?
==>
不同的输入设备所代表的按键信息含义不同,对于鼠标/触摸屏来说就是按键坐标和按键释放被按下,对于键盘来说那就是什么键被按下
input子系统框架
硬件输入设备: 按键,鼠标,键盘,TS
内核空间: 驱动层,核心层,事件层 ==>这是关键
用户空间: 操作设备文件
驱动代码分层 我们关心这个就可以了
在input_device层 驱动层(自己写代码实现) —获取数据,上报数据给input_core 硬件初始化操作
a. 申请一个input_dev对象来代表这个硬件输入设备
b. 初始化这个input_dev对象(叫什么,什么输入事件,什么按键,按键的值…)
c. 在加载驱动时将input设备注册到内核中
e. 硬件初始化
获取设备节点
因为这个节点下面可能定义了非常多的按键的gpio信息,这些按键我们都要使用,故需要使用循环来处理
获取gpio编号
设置中断处理函数所需要的参数值
获取中断号
设置中断处理函数
设置触发方式
设置中断名称 (会在某个文件下记录)
设置传递该中断函数的参数
gpio子系统的api操作
初始化系统定时器
f. 完成中断处理函数(启动定时器,实现按键消逗)
g. 实现定时器的超时函数(获取按键值,上报按键值,这里不在是直接上报该用户层,而是上报给input_core层)
h. 在卸载驱动时,删除定时器,注销中断,释放input_dev
在input_core层 核心层 (系统已经帮忙搞定) —承上启下,将input_device层中上报的数据,在传递给input_handle层
申请设备号,创建设备类
知识点: 在申请设备号时,使用的是手动申请,并且主设备号设置为13,设备号名称为“input”,故input设备的主设备号一定是13
在创建设备类时,我们设置了设备类的名称为"input",故会在/sys/class目录下新增一个名为"input’的文件
在input_handle层 事件层 (系统已帮忙搞定) ---和用户层交换,将core层中传递进来的input_event数据上报给用户层
创建设备文件 ,实现fops来和用户层交换
3> 代码流程分析
1. 驱动层 代码见 keyinput.c文件 这个文件需要我们自己实现,目的就是将硬件设备抽象为一个input_dev对象,然后将他注册到input_core
例如我们要实现:实时监视一个按键的状态,当这个按键的状态发生改变就上报当前按键的状态
大致思路: 按键状态发生变化,产生中断,在中断中开启定时器,定时器定时超时,产生超时函数,在超时函数中读取按键的状态,并将按键状态上报给input_core层
a. 创建一个input_dev对象
b. 初始化input_dev对象(该设备的名称,要上报的事件类型,按键键码,按键的值)
c. 将input_dev对象注册到input_core中
d. 硬件初始化
获取设备树中定义的那些信息,要监视的按键的设备节点,GPIO编号,设置按键值,中断名称,中断参数,注册中断处理函数,gpio操作) 关键点:如果要监视多个按键,这里直接循环处理即可
初始化定时器 使用定时器是为了实现按键消抖
e. 错误处理
知识点:
1. a,b,c 3点是input子系统中的专属步骤,只要使用了input子系统这3个步骤就必须要有
2. 什么是input_dev对象 成员的含义直接将将前面的注释翻译即可
struct input_dev {
const char *name; //表示输入设备的名称 名称用于区别设备
/*
evbit:表示输入事件类型
输入事件类型包括如下
/*
* Event types
*/
#define EV_SYN 0x00 //表示设备支持所有的事件
#define EV_KEY 0x01 //按键事件 就表示一个按键的状态的事件 键盘/按键 表示一个键
#define EV_REL 0x02 //相对坐标事件 鼠标设备
#define EV_ABS 0x03 //绝对坐标事件 TS
#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
... ...
*/
unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
/*
下面的这些刚好就是前面那些输入事件对应的值存放的地方 ,注意这里不是值,而只是存放值的地方
以keybit为例: 它只是存放了按键的状态值,并不就是按键的状态值 类似于数组和成员的关系,按键状态的值是成员,它是数组
*/
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
unsigned long relbit[BITS_TO_LONGS(REL_CNT)];
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];
unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
... ...
};
3. 注册input_dev对象到哪里去? //其实这里就进入了input_core层 因为input_register_device函数是在input_core中实现的 ********** 这里开始就是input_core层了
/*
input_register_device -注册设备到输入核心
这个函数用于输入核心注册设备,使用这个函数的前提:设备必须是通过input_allocate_device()产生和它的所有功能(设置上报的事件类型,键码)注册前已经做好准备。
如果函数失败,必须使用input_free_device()释放设备。
一旦设备已成功注册,它可以取消注册,取消注册使用input_unregister_device();input_free_device()不应该是在这种情况(取消注册)下被调用。
*/
int input_register_device(struct input_dev *dev)
{
struct input_devres *devres = NULL;
struct input_handler *handler;
unsigned int packet_size;
const char *path;
int error;
... ...
/* Every input device generates EV_SYN/SYN_REPORT events. 每个输入设备都会生成EV_SYN/SYN_REPORT事件。*/
/*
个人理解:
将evbit的EV_SYN置1: 表示支持所有的事件
*/
__set_bit(EV_SYN, dev->evbit);
/* KEY_RESERVED is not supposed to be transmitted to userspace. KEY_RESERVED不应该被传输到用户空间*/
/*
个人理解:
就是不管你设不设置上报KEY_RESERVED按键的值,反正系统是不允许你上报的
*/
__clear_bit(KEY_RESERVED, dev->keybit);
/* Make sure that bitmasks not mentioned in dev->evbit are clean. 确保dev->evbit中没有提到的位掩码是干净的*/
/*
个人理解:
根据前面设置的需要上报的事件类型信息,将那些没有设置的事件类型清0,因为在前面通过EV_SYN=1,所有时=事件类型都打开了
*/
input_cleanse_bitmasks(dev);
|
static void input_cleanse_bitmasks(struct input_dev *dev)
{
INPUT_CLEANSE_BITMASK(dev, KEY, key);
|
#define INPUT_CLEANSE_BITMASK(dev, type, bits) \
do { \
/*
判断参数dev(要注册的input_dev对象)中的每个事件类型的值是否为0,如果为0就将dev中的对应事件类型清0
##表示连接符
EV_##type == EV_type变量 == EV_KEY
*/
if (!test_bit(EV_##type, dev->evbit)) \
memset(dev->bits##bit, 0, \
sizeof(dev->bits##bit)); \
} while (0)
INPUT_CLEANSE_BITMASK(dev, REL, rel);
INPUT_CLEANSE_BITMASK(dev, ABS, abs);
INPUT_CLEANSE_BITMASK(dev, MSC, msc);
INPUT_CLEANSE_BITMASK(dev, LED, led);
INPUT_CLEANSE_BITMASK(dev, SND, snd);
INPUT_CLEANSE_BITMASK(dev, FF, ff);
INPUT_CLEANSE_BITMASK(dev, SW, sw);
}
/*
个人理解;
设置每个包最大包含的event数量
dev->max_vals == struct input_dev下的max_vals成员 unsigned int max_vals; ==>在帧排队中的最大值 驱动上报给input_core的一次数据看作为一帧数据,在一帧数据中可以包含多个event(一帧数据以上报SYN结束)
*/
packet_size = input_estimate_events_per_packet(dev);
if (dev->hint_events_per_packet < packet_size)
dev->hint_events_per_packet = packet_size;
dev->max_vals = dev->hint_events_per_packet + 2;
/*
申请dev->vals内存空间 struct input_dev下的struct input_value *vals成员
*/
dev->vals = kcalloc(dev->max_vals, sizeof(*dev->vals), GFP_KERNEL);
if (!dev->vals) {
error = -ENOMEM;
goto err_devres_free;
}
/*
* If delay and period are pre-set by the driver, then autorepeating
* is handled by the driver itself and we don't do it in input.c.
如果延时和周期由驱动器预先设定,则自动重复
*是由驱动本身处理的,我们没有在input.c中这样做。
*/
if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
dev->timer.data = (long) dev;
dev->timer.function = input_repeat_key;
dev->rep[REP_DELAY] = 250;
dev->rep[REP_PERIOD] = 33;
}
/*
个人理解:
获取按键值的方法为:input_default_getkeycode函数
设置按键值的方法为:input_default_setkeycode函数
*/
if (!dev->getkeycode)
dev->getkeycode = input_default_getkeycode;
if (!dev->setkeycode)
dev->setkeycode = input_default_setkeycode;
/*
个人理解: 非常重要 ****************
将input_dev包含的device结构注册到linux设备模型中,并可以在sysfs文件系统中表现出来
驱动分层理念: device bus driver
这里就是将input_dev这个device注册到对应的input总线中的device链表中,后面会匹配bus中的另外一个driver链表,匹配成功就会调用驱动
思路:
注册设备: 先将设备添加到对应bus(input总线)中的device链表中,然后去bus中的driver链表中寻找匹配的驱动,如果匹配成功那设备就驱动起来了,如果没有匹配成功的driver那就继续等待 input子系统
注册驱动: 先将驱动添加到对应的bus(platform总线)中driver链表中,然后去bus的device链表中寻找匹配的设备,匹配成功设备就驱动起来了
学习网站: https://blog.csdn.net/yiyeguzhou100/article/details/72637948
https://blog.csdn.net/leesagacious/article/details/50221227
*/
error = device_add(&dev->dev);
if (error)
goto err_free_vals;
/*
个人理解:
生成并返回设备的路径,调用者必须使用kfree()来释放结果
因为它的代码中有path = kzalloc(len,gfp_mask)
*/
path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
pr_info("%s as %s\n",
dev->name ? dev->name : "Unspecified device",
path ? path : "N/A");
kfree(path);
/*
获取互斥锁,这个锁可以被信号打断
*/
error = mutex_lock_interruptible(&input_mutex);
if (error)
goto err_device_del;
/*
个人理解:
在input_bus中维护了两条链表
device链表: input_dev_list链表,该链表中包含了系统中所有的input_dev设备
driver链表: input_handle_list链表,该链表中包含了系统中所有的input_handle驱动
这里就是将要注册的input_dev对象添加到device链表中
*/
list_add_tail(&dev->node, &input_dev_list);
·
· /*
个人理解: ******* 非常重要
遍历input_handler_list链表,看看有没有与我们要注册的input_dev匹配的input_handle
input_attach_handler: 匹配input_dev和input_handle
*/
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
/*
唤醒等待队列input_devices_poll_wait()
*/
input_wakeup_procfs_readers();
/*
个人理解:
释放互斥锁
*/
mutex_unlock(&input_mutex);
if (dev->devres_managed) {
dev_dbg(dev->dev.parent, "%s: registering %s with devres.\n",
__func__, dev_name(&dev->dev));
devres_add(dev->dev.parent, devres);
}
return 0;
err_device_del:
device_del(&dev->dev);
err_free_vals:
kfree(dev->vals);
dev->vals = NULL;
err_devres_free:
devres_free(devres);
return error;
}
总结一下 input_register_device函数的主要作用:
将input_dev注册到input总线中的input_dev_list链表(device链表)中,然后遍历input总线的driver链表,寻找匹配的input_handler驱动
4. input_attach_handler函数分析 input中设备和驱动的匹配过程,已经匹配成功之后干什么
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
const struct input_device_id *id;
int error;
/*
个人理解:
input_match_device函数在判断这两个东西是否匹配
*/
id = input_match_device(handler, dev);
if (!id)
return -ENODEV;
/*
个人理解:
这是匹配成功之后要执行的代码,connect表示匹配成功就会调用input_handler下的connect方法
这个过程和platform平台的匹配流程非常相似 connect函数类似于platform中的probe函数
*/
error = handler->connect(handler, dev, id);
if (error && error != -ENODEV)
pr_err("failed to attach handler %s to device %s, error: %d\n",
handler->name, kobject_name(&dev->dev.kobj), error);
return error;
}
5. input_match_device函数分析 了解input_dev是如何匹配input_handler的
static const struct input_device_id *input_match_device(struct input_handler *handler,
struct input_dev *dev)
{
const struct input_device_id *id;
for (id = handler->id_table; id->flags || id->driver_info; id++) {
if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
if (id->bustype != dev->id.bustype)
continue;
//代码分析 后面有结合例子分析
dev->id.bustype //dev->id = struct input_id id
|
struct input_id {
__u16 bustype; //总线类型
__u16 vendor;
__u16 product;
__u16 version;
};
//先确保struct input_id中的4个成员是否相同
if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
if (id->vendor != dev->id.vendor)
continue;
if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
if (id->product != dev->id.product)
continue;
if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
if (id->version != dev->id.version)
continue;
if (!bitmap_subset(id->evbit, dev->evbit, EV_MAX))
continue;
if (!bitmap_subset(id->keybit, dev->keybit, KEY_MAX))
continue;
if (!bitmap_subset(id->relbit, dev->relbit, REL_MAX))
continue;
if (!bitmap_subset(id->absbit, dev->absbit, ABS_MAX))
continue;
if (!bitmap_subset(id->mscbit, dev->mscbit, MSC_MAX))
continue;
if (!bitmap_subset(id->ledbit, dev->ledbit, LED_MAX))
continue;
if (!bitmap_subset(id->sndbit, dev->sndbit, SND_MAX))
continue;
if (!bitmap_subset(id->ffbit, dev->ffbit, FF_MAX))
continue;
if (!bitmap_subset(id->swbit, dev->swbit, SW_MAX))
continue;
if (!handler->match || handler->match(handler, dev))
return id;
}
return NULL;
}
6. 实际开发工程中 input_device层的代码通常上由platform来实现的 *******重点中的重点
下面以内核下 input/keyboard/gpio_keys.c这个代码为input_device层驱动
input/input.c 为input_core层驱动
input/evdev.c 为input_handle层驱动为例来分析input子系统
input_device层 gpio_keys.c文件分析
late_initcall(gpio_keys_init);
==>
static int __init gpio_keys_init(void)
{
return platform_driver_register(&gpio_keys_device_driver);
}
==> 将platform_driver驱动对象注册到平台总线中
static struct platform_driver gpio_keys_device_driver = {
.probe = gpio_keys_probe,
.remove = gpio_keys_remove,
.driver = {
.name = "gpio-keys",
.pm = &gpio_keys_pm_ops,
.of_match_table = of_match_ptr(gpio_keys_of_match),
}
};
static const struct of_device_id gpio_keys_of_match[] = {
{ .compatible = "gpio-keys", },
{ },
};
==> 故只需要在设备树文件中定义要驱动key的compatible属性值为"gpio-keys"即可,然后将gpio_keys.c这个文件添加到内核即可
设备树文件:
gpio-keys {
compatible = "gpio-keys";
#address-cells = <1>;
#size-cells = <0>;
autorepeat;
key0 {
label = "GPIO Key Enter";
linux,code = <KEY_ENTER>;
/* gpios = <&gpio1 18 GPIO_ACTIVE_LOW>; */
};
};
操作Kconfig文件将gpio_keys.c文件添加到内核中
make menuconfig
选中该文件对应的配置 [*]
确认是否添加成功: 查看.config文件中是否有CONFIG_KEYBOARD_GPIO=y 这一行代码
==> 匹配成功调用probe函数
static int gpio_keys_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);
struct gpio_keys_drvdata *ddata;
struct input_dev *input; //声明一个input_dev变量
size_t size;
int i, error;
int wakeup = 0;
... ...
input = devm_input_allocate_device(dev); //申请一个input_dev对象的内存空间 +devm前缀说明用完后不需要我们手动去释放该内存,系统会帮忙自动是否
if (!input) {
dev_err(dev, "failed to allocate input device\n");
return -ENOMEM;
}
... ...
platform_set_drvdata(pdev, ddata); //将pdev信息保存在ddate中 该函数通常在probe中执行,在remove中执行platform_get_drvdata函数
input_set_drvdata(input, ddata);
input->name = pdata->name ? : pdev->name; //这里就开始初始化input_dev对象中的成员
... ...
input->id.bustype = BUS_HOST; //这里就是input_dev与input_handle匹配时需要匹配的4个选项
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100;
/* Enable auto repeat feature of Linux input subsystem */
if (pdata->rep) //设置事件类型为重复事件
__set_bit(EV_REP, input->evbit);
... ...
error = input_register_device(input); //这里就是将input_dev注册到input_bus中的device链表中
if (error) {
dev_err(dev, "Unable to register input device, error: %d\n",
error);
goto err_remove_group;
}
device_init_wakeup(&pdev->dev, wakeup);
return 0;
err_remove_group:
sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group);
return error;
}
input_core层input.c文件分析 其实在前面分析input_register_device函数时就是在分析input_core层
subsys_initcall(input_init); //加载函数
==>
static int __init input_init(void)
{
int err;
err = class_register(&input_class); //******* 创建设备类
==> class类 class_create函数 == 先定义一个class类然后在用class_register函数注册它
struct class input_class = {
.name = "input",
.devnode = input_devnode,
};
if (err) {
pr_err("unable to register input_dev class\n");
return err;
}
err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0), //******* 注册设备号,INPUT_MAJOR=13,故input设备的主设备号都是13
INPUT_MAX_CHAR_DEVICES, "input");
... ...
}
==> 然后就是在input_device层中调用input_register_device函数时
我将要注册的input_dev添加到device链表中,然后去driver链表中寻找匹配的input_handler对象
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
==> 这时候就开始和input_handler层开始有联系了
==>input_dev和input_handler开始匹配,前面已经分析过了,匹配函数为input_match_device,匹配成功之后调用执行input_handler中的connect函数, **** 这里就过度到input_handler层了
学习网站: https://blog.csdn.net/lickylin/article/details/106449162
input_handle层 evdev.c文件 是linux系统中input_handler层的通用文件
module_init(evdev_init);
==>
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler); //将input_handler对象注册到input_core层中的driver链表中
}
==>
static struct input_handler evdev_handler = {
.event = evdev_event,
.events = evdev_events,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.legacy_minors = true,
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
.id_table = evdev_ids,
};
//匹配表
static const struct input_device_id evdev_ids[] = {
{ .driver_info = 1 }, /* Matches all devices */ //这样设置,就会匹配任意的input_device(即任何注册到input_bus中的input_device都会与之匹配),这就是为什么它是通用input_handle文件了
{ }, /* Terminating zero entry */
};
==> 故前面注册的input_dev会与evdev.c中注册的input_handler匹配从而调用了 input_handler中的connect()
connect函数的作用是: 类似于pribe函数,这里是在connect函数中进行字符设备注册的操作,和就行input_dev和input_handler的绑定操作
/*
* Create new evdev device. Note that input core serializes calls
* to connect and disconnect.
创建新的evdev设备。注意input core序列化调用 连接和断开连接。
*/
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
struct evdev *evdev;
int minor;
int dev_no;
int error;
/*
非常关键 获取一个未被使用的evdev设备的次设备号
input子系统创建的字符设备文件一定为: /dev/input/eventX (X=0,1,...,实际上就是次设备号的值)
input子系统创建的设备号的主设备号都是13(input_core层中定义的),故这里只需要分配一个未被使用的次设备号即可
*/
minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);
if (minor < 0) {
error = minor;
pr_err("failed to reserve new minor: %d\n", error);
return error;
}
/*
申请一个evdev对象
*/
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
if (!evdev) {
error = -ENOMEM;
goto err_free_minor;
}
INIT_LIST_HEAD(&evdev->client_list);
spin_lock_init(&evdev->client_lock);
mutex_init(&evdev->mutex);
init_waitqueue_head(&evdev->wait);
evdev->exist = true;
dev_no = minor;
/* Normalize device number if it falls into legacy range */
if (dev_no < EVDEV_MINOR_BASE + EVDEV_MINORS)
dev_no -= EVDEV_MINOR_BASE;
dev_set_name(&evdev->dev, "event%d", dev_no); //这里就设置了设备文件的名称格式为 "eventX类型",具体体现就是会在/dev/input目录下创建一个eventX文件来作为该设备的字符设备文件 ***创建设备文件
... ...
evdev->dev.devt = MKDEV(INPUT_MAJOR, minor); //在这里定义了匹配成功之后,该input设备的设备号 ***** 非常关键
... ...
error = input_register_handle(&evdev->handle); //注册input_handle
if (error)
goto err_free_evdev;
/*
熟悉的cdev操作,将设备号和fops建立联系 ***实现fops操作那应该也在input_handler实现
*/
cdev_init(&evdev->cdev, &evdev_fops);
evdev->cdev.kobj.parent = &evdev->dev.kobj;
error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);
... ...
}
总结一下connect函数的操作:
创建input_handle,并设置input_handle与input_handler、input_dev的关联,同时完成input_handle的注册;然后创建cdev,并完成字符设备的注册(设备号,cdev,fops的联系)
static const struct file_operations evdev_fops = {
.owner = THIS_MODULE,
.read = evdev_read,
... ...
};
以read为例
static ssize_t evdev_read(struct file *file, char __user *buffer,
size_t count, loff_t *ppos)
{
... ...
while (read + input_event_size() <= count &&
evdev_fetch_next_event(client, &event)) {
if (input_event_to_user(buffer + read, &event)) //*** 这里就是通过内核调用将数据上报给应用层了
return -EFAULT;
read += input_event_size();
}
}
return read;
}
7. 应用层是如何获取到上报数据的? 大概分析
1>打开设备文件
fd = open(argv[1],O_RDWR); //argv[1] == /dev/input/event1 (值如何确认? 观察在我们加载了驱动之后/dev/input下新增了那个文件,那个文件就是驱动对应的设备文件)
==> 反正经过一系列调用最终会调用input_handler层中定义的fops下的open函数 (至于调用)
static int evdev_open(struct inode *inode, struct file *file)
{
struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev);
unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev);
unsigned int size = sizeof(struct evdev_client) +
bufsize * sizeof(struct input_event);
struct evdev_client *client;
int error;
client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN); //每当一个应用程序打开evdev对应的字符设备文件(/dev/input/eventX),就会创建一个evdev_client类型的变量
if (!client)
client = vzalloc(size);
if (!client)
return -ENOMEM;
client->bufsize = bufsize; //evdev_client变量中存在一个buffer缓冲区来存储底层上报的event数据
... ...
file->private_data = client; //将evdev_client和文件描述符的私有数据建立关系
nonseekable_open(inode, file);
return 0;
}
当input_device层有事件上报时,则将该事件广播到evdev->client_list链表上所有evdev_client的buffer中,这样每一个打开的文件描述符均可以读取到该事件(这种事件分发是广播的机制)
还是看这个网站中的信息吧: https://blog.csdn.net/lickylin/article/details/106449162
学习网站:https://blog.csdn.net/lickylin/article/details/105851233 (好,顶)