didi1. 框架分析
1. 1 字符设备驱动程序框架
怎么编写字符设备驱动程序?
-
确定主设备号
-
创建file_operations结构体
-
在里面填充drv_open/drv_read/drv_ioctl等函数
-
-
注册file_operations结构体
-
register_chrdev(major, &fops, name)
-
-
谁调用register_chrdev?在入口函数调用
-
有入口自然就有出口
-
在出口函数unregister_chrdev
-
-
辅助函数(帮助系统自动创建设备节点)
-
class_create
-
device_create
-
1.2 Input子系统框架
1.
1.3 Input子系统内部实现
1.3.1 重要结构体
左边的input_dev表示输入设备
右边的input_handler表示"处理程序"
匹配之后使用input_handle保存2者之间的联系
设备获取、上报数据:input_event
1.3.2 注册流程演示
-
注册input_dev:input_register_device
-
把input_dev放入input.c的input_dev_list链表
-
对于input.c的input_handler_list链表中的每一个input_handler,一一比较
-
如果匹配则调用input_handler.connect
-
-
注册input_handler: input_register_handler
-
把input_dev放入input.c的input_handler_list链表
-
对于input.c的input_dev_list链表中的每一个input_dev,一一比较
-
如果匹配则调用input_handler.connect
-
-
怎么判断input_dev和input_handler是否匹配
-
input_handler中有一个id_table,表示它支持哪类设备
-
input_handler还有一个match函数,可以用来进行复杂的判断
-
match函数可以省略
-
判断是否匹配流程
-
如果没有match函数:input_dev跟id_table比较即可
-
如果有match函数:input_dev先跟id_table比较,成功后再用match函数再次判断
-
-
-
匹配后做的事情
-
调用input_handler.connect
-
创建/注册一个input_handle,把input_dev, input_handler放在里面
-
-
注册字符设备驱动程序
1.3.3 读取一个数据的流程演示
-
APP调用open函数打开
/dev/input/event0
-
在驱动程序evdev_open里,创建一个evdev_client,表示一个"客户"
-
-
APP调用read/poll读取、等待数据
-
没有数据时休眠:
wait_event_interruptible(evdev->wait, ...)
-
-
点击、操作输入设备,产生中断
-
在中断服务程序里
-
从硬件读取到数据
-
使用以下函数上报数据
-
-
input_event做什么?
-
从dev->h_list中取出input_handle,从input_handle取出input_handler
-
优先调用input_handler->filter来处理
-
如果没有input_handler->filter或者没处理成功
-
调用input_handler->events
-
没有input_handler->events的话,调用input_handler->event
-
-
-
以evdev.c为例
-
它有evdev_events:用来处理多个事件
-
也有evdev_event:实质还是调用evdev_events
-
唤醒"客户":wake_up_interruptible(&evdev->wait);
-
2. 编写input_dev驱动框架
2.1 分配/设置/注册input_dev
2.2 硬件相关的操作
-
申请中断
-
在中断服务程序里
-
读取硬件获得数据
-
上报数据
-
2.3 编程 代码分析
-
在设备树里创建一个节点(设备树文件)
-
指定硬件资源等信息
-
-
编译一个plartform_driver驱动(驱动.c文件)
-
在probe函数里
-
从设备树获得资源
-
分配/设置/注册input_dev
-
硬件相关的操作
-
request_irq等
-
-
-
驱动程序所依赖的一些头文件
定义结构体指针,中断线(指针),中断服务
probe函数:从设备树获取硬件信息,分配并设置输入设备
1.设置输入设备ID
2.设置支持的事件类型
3.设置具体事件
4.设置绝对值参数
在Linux输入子系统中,设置绝对值参数(absolute axis values)的作用是定义输入设备绝对轴的特性,如屏幕坐标、滑条位置等。这些参数告诉内核如何解释从输入设备接收到的原始数据。
5.注册输入设备
硬件操作,获取IRQ资源并注册中断服务程序
// 平台设备探测函数
static int input_dev_demo_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int error;
struct resource *irq;
// 从设备树获取硬件信息
// ...
// 分配并设置输入设备
g_input_dev = devm_input_allocate_device(dev);
g_input_dev->name = "input_dev_demo";
g_input_dev->phys = "input_dev_demo";
g_input_dev->dev.parent = dev;
// 设置输入设备ID
g_input_dev->id.bustype = BUS_HOST;
g_input_dev->id.vendor = 0x0001;
g_input_dev->id.product = 0x0001;
g_input_dev->id.version = 0x0100;
// 设置支持的事件类型
__set_bit(EV_KEY, g_input_dev->evbit); // 按键事件
__set_bit(EV_ABS, g_input_dev->evbit); // 绝对值事件,用于触摸屏等
// 设置具体的事件
__set_bit(BTN_TOUCH, g_input_dev->keybit); // 触摸按键
__set_bit(ABS_MT_SLOT, g_input_dev->absbit); // 触摸屏槽位
__set_bit(ABS_MT_POSITION_X, g_input_dev->absbit); // 触摸屏X坐标
__set_bit(ABS_MT_POSITION_Y, g_input_dev->absbit); // 触摸屏Y坐标
// 设置绝对值参数
input_set_abs_params(g_input_dev, ABS_MT_POSITION_X, 0, 0xffff, 0, 0);
input_set_abs_params(g_input_dev, ABS_MT_POSITION_Y, 0, 0xffff, 0, 0);
// 注册输入设备
error = input_register_device(g_input_dev);
// 硬件操作,获取IRQ资源并注册中断服务程序
irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
g_irq = irq->start;
request_irq(g_irq, input_dev_demo_isr, IRQF_TRIGGER_RISING, "input_dev_demo_irq", NULL);
return 0;
}
定义 平台设备移除函数,设备树匹配表
定义平台驱动结构体
在Linux内核中,平台驱动结构体(platform_driver
)是用于定义和注册平台设备驱动程序的一种机制。平台设备通常用于嵌入式系统或固定硬件,其中设备的配置和资源在编译时就已经确定。
定义模块初始化函数,模块退出函数3.
3. 编写触摸屏驱动程序_基于QEMU
-
request_irq
-
在中断处理函数里
-
上报按下、松开的事件
-
-
如果触摸屏被按下,启动定时器
-
如果触摸屏被松开,取消定时器
依赖的头文件和定时时间
#include <linux/module.h> // 模块化编程支持
#include <linux/init.h> // 模块初始化和清理宏
#include <linux/fs.h> // 文件系统支持
#include <linux/interrupt.h> // 中断处理
#include <linux/irq.h> // IRQ支持
#include <linux/sched.h> // 调度程序支持
#include <linux/pm.h> // 电源管理
#include <linux/slab.h> // 内存分配
#include <linux/sysctl.h> // 系统控制接口
#include <linux/proc_fs.h> // 进程文件系统
#include <linux/delay.h> // 延时函数
#include <linux/platform_device.h> // 平台设备支持
#include <linux/input.h> // 输入子系统
#include <linux/gpio_keys.h> // GPIO按键支持
#include <linux/workqueue.h> // 工作队列
#include <linux/gpio.h> // GPIO支持
#include <linux/gpio/consumer.h> // GPIO消费者
#include <linux/of.h> // 设备树支持
#include <linux/of_irq.h> // 设备树中的中断支持
#include <linux/of_gpio.h> // 设备树中的GPIO支持
#include <linux/spinlock.h> // 自旋锁
#define TOUCHSCREEN_POLL_TIME_MS 10 // 触摸屏轮询时间(毫秒)
定义触摸屏控制器结构体
定义定时器中断处理函数
中断服务程序 :读取数据,报告数据(发送具体数据,开始定时器,取消定时器(如果松手),结束时候告诉内核中断已经被处理)
/ 定义触摸屏控制器结构体
struct qemu_ts_con {
volatile unsigned int pressure; // 触摸压力
volatile unsigned int x; // X坐标
volatile unsigned int y; // Y坐标
volatile unsigned int clean; // 清洁标志
};
static struct input_dev *g_input_dev; // 全局输入设备结构体指针
static int g_irq; // 全局IRQ线
static struct qemu_ts_con *ts_con; // 触摸屏控制器
struct timer_list ts_timer; // 定时器
// 定时器中断处理函数
static void ts_irq_timer(unsigned long _data)
{
if (ts_con->pressure) // 如果有触摸压力
{
input_event(g_input_dev, EV_ABS, ABS_X, ts_con->x); // 报告X坐标
input_event(g_input_dev, EV_ABS, ABS_Y, ts_con->y); // 报告Y坐标
input_sync(g_input_dev); // 同步事件
// 重新设置定时器
mod_timer(&ts_timer,
jiffies + msecs_to_jiffies(TOUCHSCREEN_POLL_TIME_MS));
}
}
// 中断服务程序
static irqreturn_t input_dev_demo_isr(int irq, void *dev_id)
{
/* 读取数据 */
/* 报告数据 */
if (ts_con->pressure)
{
input_event(g_input_dev, EV_ABS, ABS_X, ts_con->x); // 报告X坐标
input_event(g_input_dev, EV_ABS, ABS_Y, ts_con->y); // 报告Y坐标
input_event(g_input_dev, EV_KEY, BTN_TOUCH, 1); // 报告触摸按键
input_sync(g_input_dev); // 同步事件
/* 开始定时器 */
mod_timer(&ts_timer,
jiffies + msecs_to_jiffies(TOUCHSCREEN_POLL_TIME_MS));
}
else
{
input_event(g_input_dev, EV_KEY, BTN_TOUCH, 0); // 报告触摸释放
input_sync(g_input_dev); // 同步事件
/* 取消定时器 */
del_timer_sync(&ts_timer);
}
return IRQ_HANDLED; // 告诉内核该中断已经被处理
}
初始化变量;
从设备树获取GPIO信息;
分配并设置输入设备:
1.哪种类型的事件?
2. 哪些事件?
3.时间参数?;
硬件操作:获取内存资源,并将其映射与物理地址绑定
初始化并设置定时器
// 平台设备探测函数
static int input_dev_demo_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int error;
struct resource *io;
int gpio;
printk(KERN_INFO "%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
gpio = of_get_gpio(pdev->dev.of_node, 0); // 从设备树获取GPIO
/* 分配并设置输入设备 */
g_input_dev = devm_input_allocate_device(dev);
g_input_dev->name = "input_dev_demo";
g_input_dev->phys = "input_dev_demo";
g_input_dev->dev.parent = dev;
g_input_dev->id.bustype = BUS_HOST;
g_input_dev->id.vendor = 0x0001;
g_input_dev->id.product = 0x0001;
g_input_dev->id.version = 0x0100;
/* 设置1: 哪种类型的事件?*/
__set_bit(EV_KEY, g_input_dev->evbit); // 按键事件
__set_bit(EV_ABS, g_input_dev->evbit); // 绝对值事件
__set_bit(INPUT_PROP_DIRECT, g_input_dev->propbit); // 直接属性
/* 设置2: 哪些事件?*/
__set_bit(BTN_TOUCH, g_input_dev->keybit); // 触摸按键
__set_bit(ABS_X, g_input_dev->absbit); // X坐标
__set_bit(ABS_Y, g_input_dev->absbit); // Y坐标
/* 设置3: 事件参数?*/
input_set_abs_params(g_input_dev, ABS_X, 0, 0xffff, 0, 0); // 设置X坐标参数
input_set_abs_params(g_input_dev, ABS_Y, 0, 0xffff, 0, 0); // 设置Y坐标参数
error = input_register_device(g_input_dev); // 注册输入设备
/* 硬件操作 */
io = platform_get_resource(pdev, IORESOURCE_MEM, 0); // 获取内存资源
ts_con = ioremap(io->start, io->end - io->start + 1); // 映射内存资源
g_irq = gpio_to_irq(gpio); // 将GPIO转换为IRQ
error = request_irq(g_irq, input_dev_demo_isr, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "input_dev_demo_irq", NULL); // 请求IRQ
setup_timer(&ts_timer, // 初始化定时器
ts_irq_timer, (unsigned long)NULL);
return 0;
}
平台设备移除函数,设备树匹配表,
平台驱动结构体:探测函数(从设备树获取硬件信息。分配设置注册inputdev(驱动结构体),中断硬件相关消息),移除函数,驱动信息(驱动名称和设备树匹配表)
模块初始化和退出函数。
// 平台设备移除函数
static int input_dev_demo_remove(struct platform_device *pdev)
{
del_timer_sync(&ts_timer); // 删除定时器
iounmap(ts_con); // 取消映射内存资源
free_irq(g_irq, NULL); // 释放IRQ资源
input_unregister_device(g_input_dev); // 注销输入设备
return 0;
}
// 设备树匹配表
static const struct of_device_id input_dev_demo_of_match[] = {
{ .compatible = "100ask,input_dev_demo", },
{ },
};
// 平台驱动结构体
static struct platform_driver input_dev_demo_driver = {
.probe = input_dev_demo_probe, // 探测函数
.remove = input_dev_demo_remove, // 移除函数
.driver = {
.name = "input_dev_demo", // 驱动名称
.of_match_table = input_dev_demo_of_match, // 设备树匹配表
}
};
// 模块初始化函数
static int __init input_dev_demo_init(void)
{
printk(KERN_INFO "%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
return platform_driver_register(&input_dev_demo_driver);
}
// 模块退出函数
static void __exit input_dev_demo_exit(void)
{
platform_driver_unregister(&input_dev_demo_driver);
}
module_init(input_dev_demo_init); // 注册初始化函数
module_exit(input_dev_demo_exit); // 注册退出函数
MODULE_LICENSE("GPL"); // 模块许可证
-
/
:设备树的根节点。 -
input_dev_demo
:设备节点,它是一个自定义的输入设备演示节点。 -
compatible
:字符串属性,用于标识设备的兼容信息。内核通过这个属性来确定应该使用哪个驱动程序来驱动这个设备。"100ask,input_dev_demo"
是一个假设的兼容字符串,实际使用时应替换为正确的值。 -
reg
:内存资源属性,它定义了设备的内存映射区域。<0x021B4000 16>
表示设备使用的起始地址是0x021B4000
,长度是16
个字节。 -
//interrupt-parent = <&gpio1>;
:被注释掉的属性,如果设备使用GPIO作为中断源,这个属性应该指向GPIO控制器的节点。 -
//interrupts = <5 IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING>;
:同样被注释掉的属性,如果设备使用GPIO中断,这里定义了中断的类型和编号。IRQ_TYPE_EDGE_FALLING
和IRQ_TYPE_EDGE_RISING
表示中断类型是边沿触发,5
是GPIO的中断号。 -
gpios
:GPIO属性,用于定义设备使用的GPIO引脚。<&gpio1 5 1>
表示GPIO控制器gpio1
的第5
个引脚被用作输入设备,1
表示GPIO引脚的配置(例如,作为中断引脚)。
设备树中的注释部分(以//
开头)提供了额外的配置示例,但这些行在当前配置中被忽略了。如果设备确实需要通过GPIO中断来工作,你需要取消注释并可能修改这些行,以匹配你的硬件配置。
在实际使用中,你需要根据实际的硬件设计来调整设备树中的属性,确保它们正确地反映了你的硬件配置。一旦设备树被编译并加载到内核中,内核将使用这些信息来自动配置和初始化硬件设备
/ {
input_dev_demo {
compatible = "100ask,input_dev_demo";
reg = <0x021B4000 16>;
//interrupt-parent = <&gpio1>;
//interrupts = <5 IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING>;
gpios = <&gpio1 5 1>;
};
};
4. GPIO按键驱动分析与使用
4.1 驱动程序框架
组件说明:
-
input.c:
- 这通常指的是内核源代码中处理输入子系统的C语言文件。它包含定义输入设备如何工作的代码。
-
platform_bus_type:
- 这是一个内核中的总线类型,用于将平台设备连接到内核的设备模型。平台总线允许驱动程序通过设备树或ACPI表等机制来发现和配置设备。
-
input_dev:
- 代表一个输入设备的结构体(通常是
struct input_dev
),它包含了输入设备的所有相关信息,如设备名称、类型、按键映射、事件处理函数等。
- 代表一个输入设备的结构体(通常是
-
input_handler:
- 这是一个处理输入事件的内核组件,它负责将输入事件传递给注册的处理器或驱动程序。
-
probe:
- 这是一个在设备驱动程序中常见的函数,用于初始化和配置设备。在这个上下文中,它可能指的是平台驱动程序的
probe
函数,该函数在设备被发现时被调用。
- 这是一个在设备驱动程序中常见的函数,用于初始化和配置设备。在这个上下文中,它可能指的是平台驱动程序的
-
设备树:
- 设备树(Device Tree)是一个数据结构,用于描述硬件的配置。它允许内核在启动时自动发现硬件并加载相应的驱动程序。
-
platform_device:
- 这是一个内核中用于表示平台设备的抽象。它包含了设备的资源和配置信息,通常通过设备树或ACPI表定义。
-
platform_driver:
- 这是一个内核中的驱动程序结构体,用于定义如何探测、初始化和管理平台设备。它通常包含
probe
、remove
和shutdown
等回调函数。
- 这是一个内核中的驱动程序结构体,用于定义如何探测、初始化和管理平台设备。它通常包含
关系说明:
-
platform_bus_type 和 platform_device 之间的关系:
- 平台设备通过平台总线连接到内核的设备模型。
-
platform_device 和 platform_driver 之间的关系:
- 平台驱动程序负责管理平台设备。驱动程序的
probe
函数在设备被发现时被调用,用于初始化设备。
- 平台驱动程序负责管理平台设备。驱动程序的
-
input_dev 和 input_handler 之间的关系:
- 输入设备通过输入处理器与内核的输入子系统交互。输入处理器负责处理输入事件并传递给相应的驱动程序。
-
probe 函数的作用:
- 在设备探测过程中,
probe
函数会分配、设置和注册input_dev
结构体。这涉及到初始化设备、设置事件类型和按键映射,以及注册设备到内核的输入子系统。
- 在设备探测过程中,
图示流程:
- 设备树定义了platform_device的配置。
- 内核通过platform_bus_type发现platform_device。
- platform_driver的probe函数被调用,执行以下步骤:
- 分配和初始化input_dev结构体。
- 设置input_dev的属性,如名称、物理路径、事件类型和按键映射。
- 注册input_dev到内核的输入子系统。
- input_handler处理来自input_dev的输入事件。
这个图示提供了一个高层次的视图,展示了Linux内核中输入设备驱动程序的组件和它们之间的交互方式。
4.2 设备树讲解
属性:
-
必备:
compatible = "gpio-keys";
-
可选:
-
autorepeat
: 表示自动重复,按下按键不松开,驱动会自动重复上报按键值
-
-
对于每一个GPIO按键,都是一个子节点,有这些属性:
-
gpios
:使用哪个GPIO -
interrupts
:对应的中断 -
linux,code
:对应的按键值 -
注意:
gpios
和interrupts
至少要保留一个,不能都省略 -
debounce-interval
: 消除抖动的间隔,单位:ms,默认是5ms
-
gpio-keys {
compatible = "gpio-keys";
pinctrl-names = "default";
user1 {
label = "User1 Button";
gpios = <&gpio5 1 GPIO_ACTIVE_LOW>;
gpio-key,wakeup;
linux,code = <KEY_1>;
};
user2 {
label = "User2 Button";
gpios = <&gpio4 14 GPIO_ACTIVE_LOW>;
gpio-key,wakeup;
linux,code = <KEY_2>;
};
};
4.3 gpio_keys.c驱动程序分析
4.3.1 套路
-
根据设备树获得硬件信息:哪个GPIO、对于什么按键
-
分配/设置/注册input_dev结构体
-
request_irq: 在中断处理函数中确定按键值、上报按键值
-
有两种IRQ函数
-
gpio_keys_gpio_isr
:设备树中的用gpios
来描述用到的引脚 -
gpio_keys_irq_isr
:设备树中的用interrupts
来描述用到的引脚
-
4.3.2 gpio_keys_gpio_isr分析
理想状况是:按下、松开按键,各产生一次中断,也只产生一次中断。 但是对于机械开关,它的金属弹片会反复震动。GPIO电平会反复变化,最后才稳定。一般是几十毫秒才会稳定。 如果不处理抖动的话,用户只操作一次按键,会发生多次中断,驱动程序可能会上报多个数据。
怎么处理按键抖动?
-
在按键中断程序中,可以循环判断几十亳秒,发现电平稳定之后再上报
-
使用定时器
显然第1种方法太耗时,违背“中断要尽快处理”的原则,你的系统会很卡。
怎么使用定时器?看下图:
核心在于:在GPIO中断中并不立刻记录按键值,而是修改定时器超时时间,10ms后再处理。
如果10ms内又发生了GPIO中断,那就认为是抖动,这时再次修改超时时间为10ms。
只有10ms之内再无GPIO中断发生,那么定时器的函数才会被调用。
在定时器函数中上报按键值。
4.3.3 gpio_keys_irq_isr分析
有个变量key_pressed,用来表示当前按键状态:初始值是false,表示按键没有被按下。
-
发生中断
-
上报"按下的值":
input_event(input, EV_KEY, button->code, 1); input_sync(input);
-
如果不延迟(!bdata->release_delay)
-
马上上报"松开的值":
input_event(input, EV_KEY, button->code, 0); input_sync(input);
-
如果延迟(bdata->release_delay)
-
启动定时器,过若干毫秒再上报"松开的值"
-
-
所以,使用gpio_keys_irq_isr时,一次中断就会导致上报2个事件:按下、松开
-
缺点:无法准确判断一个按键确实已经被松开了
4.3.4 实验步骤
1.设置工具链;
2.配置内核;(QEMU的内核里已经配置了GPIO按键的设备树,只需要编译出gpio_keys驱动程序即可。 配置内核:执行make menuconfig
)
3.编译驱动
4.启动QEMU
5.挂载NFS、实验
5. I2C接口触摸屏驱动分析
图片展示了一个简化的流程图,描述了在Linux内核中,I2C输入设备驱动程序的组件和它们之间的交互方式。下面是对这个图的分析:
组件说明:
-
input.c:
- 这可能指的是内核中处理输入子系统的源代码文件。
-
i2c_bus_type:
- 表示I2C总线类型,用于I2C设备通信。
-
input_dev:
- 表示输入设备的结构体,包含了输入设备的所有相关信息。
-
(input_handler):
- 表示输入处理器,用于处理来自输入设备的事件。
-
i2c_client:
- 表示I2C设备的客户端结构体,代表I2C总线上的一个设备。
-
i2c_driver:
- 表示I2C设备的驱动程序,负责管理I2C设备。
-
设备树:
- 用于在嵌入式Linux系统中描述硬件配置的数据结构。
流程说明:
-
图中展示了I2C设备驱动程序与输入设备驱动程序的交互过程。
-
设备树:
- 包含了I2C设备和输入设备的配置信息。
-
i2c_bus_type 和 i2c_client:
- I2C总线类型用于连接I2C设备。每个I2C设备在系统中都是一个i2c_client实例。
-
i2c_driver:
- 负责探测和初始化I2C设备。它包含一个probe函数,当I2C设备被探测到时,这个函数会被调用。
-
比较和匹配:
- 如果i2c_driver中的probe函数发现设备匹配(例如,通过检查设备地址或ID),它将为该设备分配资源并进行初始化。
-
probe:
- 当一个I2C设备被探测到,并且i2c_driver的probe函数确定它管理这个设备时,这个函数会被调用。
-
分配/设置/注册input_dev:
- 在probe函数中,i2c_driver将为输入设备分配和设置input_dev结构体,然后注册到内核的输入子系统中。
-
(input_handler):
- 一旦input_dev被注册,它将与一个输入处理器关联,该处理器会处理来自input_dev的事件。
图解流程:
- 设备树定义了I2C设备的配置。
- i2c_bus_type 用于I2C设备之间的通信。
- I2C设备在内核中表示为 i2c_client。
- 当I2C设备被探测到时,内核查找合适的 i2c_driver。
- i2c_driver 的 probe 函数被调用,如果设备匹配:
- 为输入设备分配和设置 input_dev。
- 注册 input_dev 到内核的输入子系统。
- input_dev 与 (input_handler) 关联,以便处理输入事件。
这个图示提供了一个高层次的视图,展示了Linux内核中I2C输入设备驱动程序的组件和它们之间的交互方式。实际的驱动程序开发会涉及更多的细节,如错误处理、资源管理、电源管理等。
这张图片描述了Linux系统中输入设备驱动的架构和数据流,从硬件层到用户空间的交互过程。以下是核心内容的整理:
用户空间 (User Space)
- 应用程序 (APP):
- 应用程序可以直接访问输入设备的节点,例如
/dev/input/event0
、/dev/input/event1
等。 - 应用程序可以通过特定的库,如
ts lib
或libinput
,来使用输入设备。
- 应用程序可以直接访问输入设备的节点,例如
内核空间 (Kernel Space)
-
文件操作结构体 (struct file_operations):
evdev_fops
提供了一组函数,用于处理用户空间对输入设备的文件操作。- 包括
open
、release
、read
、write
、ioctl
、poll
等操作。
-
Input Handler:
- 负责承上启下,接收来自硬件的输入事件,并将其转换为标准的输入事件向上汇报。
evdev.c
文件包含了处理输入事件的核心代码。
-
Input Core:
- 位于内核空间,接收来自底层硬件的输入事件,并转发给上层的处理器(handler)。
input.c
文件包含了输入核心的实现。
-
Input Device:
- 硬件相关的驱动,从硬件获取数据。
- 使用
input_dev
结构体表示输入设备,并转换为标准的输入事件向上汇报。
硬件 (Hardware)
- 键盘/GPIO键/鼠标/触摸屏:
- 这些是输入设备的实例,它们通过硬件驱动与内核空间交互。
用户空间与内核空间的交互
- open: 打开输入设备文件。
- ioctl: 控制输入设备的行为。
- poll: 等待输入事件发生。
- read: 读取输入事件数据。
- release: 释放输入设备文件。
- write: 可选,某些输入设备支持写操作。
- fasync: 异步通知。
- flush: 清除待处理的输入事件。
- no_llseek: 输入设备文件不支持寻址。
内核空间内部组件
-
evdev_fops:
- 为用户空间提供访问输入设备的接口。
- 处理来自用户空间的请求,如读取输入事件。
-
evdev_open:
- 打开设备时调用,初始化设备。
-
evdev_release:
- 释放设备时调用,执行清理工作。
-
evdev_read:
- 读取输入事件数据。
-
evdev_write:
- 可选,某些情况下写入数据到设备。
-
evdev_ioctl:
- 执行输入设备的控制命令。
-
evdev_poll:
- 实现轮询机制,等待输入事件发生。
-
evdev_fasync:
- 异步通知处理。
-
evdev_flush:
- 清除待处理的输入事件。
-
no_llseek:
- 输入设备文件不支持常规的寻址操作。
总结
这张图片展示了Linux输入系统的工作流程,从物理硬件获取输入数据,通过硬件驱动转换为标准事件,然后通过内核的输入子系统处理这些事件,最终提供给用户空间的应用程序使用。这个过程涉及到内核的多个层次和组件,以及它们之间的交互方式。