Linux Input子系统5(基于Linux6.6)---输入设备驱动层介绍
一、输入设备驱动层
在 Linux 内核中,输入设备驱动(Input Device Driver)是用于管理和处理来自硬件输入设备(如键盘、鼠标、触摸屏、游戏控制器等)的事件的模块。输入设备驱动层通过抽象化硬件设备和用户空间应用程序之间的交互,提供了统一的接口来处理各种类型的输入事件。
1.1、输入设备驱动层的作用
输入设备驱动层的主要目的是接收来自硬件设备的输入事件,处理这些事件,并将其转发到上层应用程序(如 X Window 系统、Wayland 或其他用户空间应用)进行进一步的处理。这一层的关键在于如何标准化硬件输入的事件,使得不同硬件设备的行为尽可能一致。
1.2、输入设备框架的组成
Linux 的输入子系统通过一个统一的框架来支持各种输入设备。这个框架包括以下几个重要的部分:
-
输入设备(Input Devices)
- 每一个物理输入设备(如键盘、鼠标等)在 Linux 中都被表示为一个输入设备对象。这些设备对象提供了用于与设备交互的接口。
-
输入事件(Input Events)
- 输入事件是设备发送到内核的原始数据,通常是某个设备的状态变化。例如,按下某个键或移动鼠标时,都会生成对应的事件。
- 事件类型通常包括按键(key events)、坐标(motion events)、鼠标按钮(button events)、触摸事件等。
-
事件类型和事件代码
- 每个事件由两部分组成:事件类型和事件代码。事件类型指定了事件的种类,如按键、坐标等,事件代码则是具体的事件,如某个特定的按键或坐标位置。
-
输入设备驱动(Input Device Driver)
- 输入设备驱动程序是连接输入设备硬件和 Linux 内核的桥梁。它们负责管理硬件输入设备,捕获输入事件,处理这些事件,并将其传递给上层系统。
-
输入核心(Input Core)
- Linux 内核中的输入核心负责协调所有输入设备的操作。它提供了设备注册、事件分发和事件处理等功能。
-
输入子系统接口(Input Subsystem API)
- 通过这一层,驱动程序和用户空间程序之间可以交互。它提供了一些 API,允许驱动程序注册输入设备,传递事件,和设置设备的属性等。
1.3、输入子系统的工作流程
-
设备注册
- 每个输入设备(如键盘、鼠标、触摸屏等)都需要通过
input_register_device()
函数注册到输入子系统。注册之后,设备会被分配一个设备对象,并且会被链接到输入子系统。
- 每个输入设备(如键盘、鼠标、触摸屏等)都需要通过
-
int input_register_device(struct input_dev *dev);
其中,
input_dev
是一个结构体,代表输入设备的状态和特性(如设备名称、事件类型、输入事件处理函数等)。 -
事件处理
- 当输入设备发生事件时,驱动程序会通过
input_event()
函数将事件数据传递给输入核心。
- 当输入设备发生事件时,驱动程序会通过
-
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
type
:事件类型(例如,按键、鼠标移动等)。code
:事件代码(例如,键盘按键的具体编号、鼠标移动的坐标等)。value
:事件的值(例如,按键的状态:按下或松开,鼠标的移动量等)。
-
事件分发
- 输入核心会根据事件类型和代码将事件分发给适当的上层处理程序,通常是用户空间的事件处理程序。输入核心通过事件队列将事件传递给上层应用,如图形界面系统(X11、Wayland)或其他应用程序。
-
输入设备注销
- 当输入设备不再需要时,驱动程序会通过
input_unregister_device()
函数注销设备,释放相关资源。 -
void input_unregister_device(struct input_dev *dev);
- 当输入设备不再需要时,驱动程序会通过
1.4、设备和事件类型
输入设备可以产生不同类型的事件,这些事件通常分为以下几类:
-
按键事件(Key events):
- 用于表示按键的状态变化(按下或释放)。例如键盘上的按键,或者某些触摸屏的触控事件。
事件类型:
EV_KEY
:按键事件类型。- 事件代码:键盘上各个键的编号(例如,
KEY_A
、KEY_SPACE
)。
-
鼠标和坐标事件(Pointer / Motion events):
- 用于表示鼠标、触摸屏、触摸板等设备的运动、点击等行为。
事件类型:
EV_REL
:相对运动事件类型(例如,鼠标的水平和垂直移动)。EV_ABS
:绝对位置事件类型(例如,触摸屏的触摸坐标)。EV_MSC
:鼠标滚轮等附加事件。
事件代码:
REL_X
、REL_Y
:鼠标在水平和垂直方向上的相对移动。ABS_MT_SLOT
、ABS_MT_POSITION_X
、ABS_MT_POSITION_Y
:触摸屏多点触控事件。
-
音量/灯光事件(Miscellaneous events):
- 用于表示设备的特殊事件,如灯光开关、音量控制等。
事件类型:
EV_MSC
:杂项事件类型。
-
同步事件(SYN events):
- 用于同步多个事件流,标记一组事件的开始和结束。
事件类型:
EV_SYN
:同步事件类型。SYN_REPORT
:报告事件的结束,通知内核处理完当前的一组事件。
1.5、输入设备的注册与配置
设备的注册过程包括为设备分配内存、初始化设备属性和事件处理等。例如,初始化一个键盘设备时,通常会设置以下内容:
- 设备类型(
EV_KEY
)。 - 事件代码(按键事件的具体代码,如
KEY_A
、KEY_B
等)。 - 事件处理函数,用于接收并处理输入事件。
- 注册设备名,通常会与
/dev/input
下的设备文件关联。
1.6、输入设备驱动开发示例
这是一个简化的输入设备驱动注册示例,展示了如何在内核中注册一个简单的输入设备:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/input.h>
#include <linux/init.h>
static struct input_dev *my_input_dev;
static int __init my_input_driver_init(void)
{
int error;
// 分配输入设备
my_input_dev = input_allocate_device();
if (!my_input_dev)
return -ENOMEM;
// 设置设备类型和事件类型
my_input_dev->evbit[0] = BIT(EV_KEY); // 按键事件
my_input_dev->keybit[BIT_WORD(KEY_A)] = BIT(KEY_A); // 键盘按键 A
// 注册输入设备
error = input_register_device(my_input_dev);
if (error) {
pr_err("Failed to register input device\n");
input_free_device(my_input_dev);
return error;
}
pr_info("Input device registered\n");
return 0;
}
static void __exit my_input_driver_exit(void)
{
// 注销输入设备
input_unregister_device(my_input_dev);
pr_info("Input device unregistered\n");
}
module_init(my_input_driver_init);
module_exit(my_input_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Author Name");
MODULE_DESCRIPTION("Simple Input Driver");
主要函数说明:
input_allocate_device()
:分配一个新的输入设备对象。input_register_device()
:注册设备。input_unregister_device()
:注销设备。input_free_device()
:释放设备对象。
#include <linux/input.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/gpio.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/irqreturn.h> /* 中断函数的返回值用到 */
#include <mach/irqs.h> /* 中断号用到 */
#include <linux/interrupt.h> /* 中断req和free用到 */
#define BUTTON_LIFT_IRQ IRQ_EINT2 /* 我的板子是接到外部中断2了 */
static struct input_dev *button_dev;
static irqreturn_t button_interrupt(int irq, void *dummy)
{
/* 得到按键状态,并上报给响应的上层程序,下一小节我分析具体的上报流程 */
input_report_key(button_dev, KEY_LEFT, !gpio_get_value(S5PV210_GPH0(2)));
/* 上报一个同步包 */
input_sync(button_dev);
return IRQ_HANDLED;
}
static int __init button_init(void)
{
int error;
int ret;
/* 申请gpio */
ret = gpio_request(S5PV210_GPH0(2), "GPH0");
if(ret)
{
printk("button-x210: request gpio GPH0(2) fail");
error = -EIO;
goto err_io;
}
/* 设置gpio模式 */
s3c_gpio_setpull(S5PV210_GPH0(2), S3C_GPIO_PULL_UP); //上拉
s3c_gpio_cfgpin(S5PV210_GPH0(2), S3C_GPIO_SFN(0xf)); //外部中断
/* 请求并注册一个irq中断,上升沿和下降沿触发 */
if (request_irq(BUTTON_LIFT_IRQ, button_interrupt,IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "button_left", NULL)) {
printk(KERN_ERR "button.c: Can't allocate irq %d\n", BUTTON_LIFT_IRQ);
error = -EBUSY;
goto err_req_irq;
}
/* 申请一个input_dev并初始化里面的通用变量 */
button_dev = input_allocate_device();
if (!button_dev) {
printk(KERN_ERR "button.c: Not enough memory\n");
error = -ENOMEM;
goto err_free_irq;
}
/* 设置按键事件和键值 */
button_dev->evbit[0] = BIT_MASK(EV_KEY);
button_dev->keybit[BIT_WORD(KEY_LEFT)] = BIT_MASK(KEY_LEFT);
/* 注册这个input_dev */
error = input_register_device(button_dev);
if (error) {
printk(KERN_ERR "button.c: Failed to register device\n");
goto err_free_dev;
}
return 0;
err_free_dev:
input_free_device(button_dev);
err_free_irq:
free_irq(BUTTON_LIFT_IRQ, button_interrupt);
err_req_irq:
gpio_free(S5PV210_GPH0(2));
err_io:
return error;
}
/* 卸载时要吧申请的资源释放掉 */
static void __exit button_exit(void)
{
input_unregister_device(button_dev);
free_irq(BUTTON_LIFT_IRQ, NULL); /* 这里必须是NULL,参考模版有问题(应该是文档很久没更新了,具体原因我在后面章节分析) */
gpio_free(S5PV210_GPH0(2));
}
module_init(button_init);
module_exit(button_exit);
MODULE_LICENSE("GPL");