文章目录
- 前言
- 一、输入子系统
- 1. 子系统的引入
- 2. 组成部分
- 3. 事件处理流程
- 4. 相关数据结构
- 二、程序编写
- 1. 相关API函数
- 1.1 input_allocate_device ( )
- 1.2 input_free_device ( )
- 1.3 input_register_device ( )
- 1.4 input_unregister_device ( )
- 1.5 input_event ( )
- 1.6 input_report_key ( )和input_sync ( )
- 2. probe函数的编写
- 3. remove函数
- 4. 按键消抖
前言
Linux 的 input 子系统是一个用于处理和管理输入设备(例如键盘、鼠标、触摸屏、游戏控制器等)的框架。它的作用是将硬件输入设备产生的原始输入数据转换成系统可以识别和使用的输入事件,并将这些事件传递给用户空间的应用程序。
一、输入子系统
1. 子系统的引入
引入子系统是操作系统设计中一个重要的实践,它有助于管理复杂性、提高可扩展性和维护性。子系统将操作系统的不同功能模块化,每个子系统专注于特定的功能。例如,input子系统专门处理各种输入设备的数据。这种模块化设计可以简化内核的复杂性,使得每个子系统更易于开发、测试和维护。
子系统提供标准化的接口和框架,简化了驱动程序的开发。开发人员只需关注如何与具体设备交互,而不必关心数据如何传递到用户空间。
这种模块化和标准化设计有助于增强系统的稳定性。每个子系统都是相对独立的模块,出问题时可以单独调试和修复,而不影响其他部分。例如,input子系统出问题时,主要影响输入设备,而不会波及网络、存储等其他子系统。
2. 组成部分
linux为了统一各个输入设备,将输入子系统分为了Drivers(驱动层)、Input Core(输入子系统核心层)、Handlers(事件处理层)三部分:
Drivers
设备驱动程序负责与实际的硬件设备进行交互,将设备的原始输入数据传递给 input core。每种输入设备(如键盘、鼠标)通常都有相应的驱动程序。
Input Core
这是 input 子系统的核心部分,为Drivers提供了规范及接口并通知Handlers对事件进行处理。负责管理输入设备和事件,处理设备注册、事件分发等工作。
Handlers
事件处理程序负责接收来自 input core 的输入事件,并将其传递给用户空间的应用程序或其他内核子系统。常见的事件处理程序包括键盘事件处理器、鼠标事件处理器等。
3. 事件处理流程
- 驱动程序检测到输入事件:
当输入设备(如键盘或鼠标)产生输入数据时,设备驱动程序会检测到这些事件。 - 驱动程序生成输入事件:
驱动程序将这些原始数据转换成标准的 input 事件结构,并将其传递给 input core。 - Input core 分发事件:
input core 接收到驱动程序传递的事件后,将其分发给相应的事件处理程序。 - 事件处理程序处理事件:
事件处理程序(如键盘或鼠标事件处理程序)接收到事件后,将其转换成用户空间可以理解的格式,并通过 input 事件接口传递给用户空间的应用程序。 - 用户空间应用程序接收事件:
用户空间的应用程序通过读取 /dev/input/eventX 设备文件来接收和处理这些输入事件。
4. 相关数据结构
我们可以将所有输入设备的输入信息将被抽象成以下结构体:
//输入事件
struct input_event{struct timeval time;//事件产生的时间__u16 type; //输入设备的类型,鼠标、键盘、触摸屏__u16 code; //设备类型的不同其含义也不同,如其类型是按键表示为按键号__s16 value; //设备类型的不同其含义也不同,如其类型是按键表示为按键值
}
我们可以利用struct input_dev结构体来代表一个具体的输入设备, 后面将会根据具体的设备来初始化这个结构体。
struct input_dev {const char *name; //提供给用户的输入设备的名称const char *phys; //提供给编程者的设备节点的名称const char *uniq; //指定唯一的ID号struct input_id id; //输入设备标识IDunsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; //指定设备支持的事件类型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)];/*----------以下结构体成员省略----------------*/
};
- evbit:用于指定支持的事件类型,这要根据实际输入设备能够产生的事件来选择,可选选项如下所示。
输入子系统事件类型 | 取值 | 描述 |
---|---|---|
EV_SYN | 0x00 | 同步事件 |
EV_KEY | 0x01 | 用于描述键盘、按钮或其他类似按键的设备 |
EV_REL | 0x02 | 用于描述相对位置变化,例如鼠标移动 |
EV_ABS | 0x03 | 用于描述绝对位置变化,例如触摸屏的触点坐标 |
EV_MSC | 0x04 | 其他事件类型 |
EV_SW | 0x05 | 用于描述二进制开关类型的设备,例如拨码开关 |
EV_LED | 0x11 | 用于 LED 事件,表示 LED 灯的状态 |
EV_SND | 0x12 | 用于声音事件,表示声音的播放相关事件 |
EV_REP | 0x14 | 用于重复事件,表示键盘重复发送事件 |
EV_FF | 0x15 | 用于力反馈事件,表示力反馈设备的输出事件 |
EV_PWR | 0x16 | 用于电源事件,表示电源状态变化 |
EV_FF_STATUS | 0x17 | 用于力反馈状态事件,表示力反馈设备的状态变化 |
EV_MAX | 0x1f | 输入事件类型的最大值 |
EV_CNT | (EV_MAX+1) | 输入事件类型的数量 |
- keybit:记录支持的键值,“键值”在程序中用于区分不同的按键,可选“键值”如下所示。
按键键值 | 描述 |
---|---|
KEY_RESERVED | 0 |
KEY_ESC | 1 |
KEY_1 | 2 |
KEY_2 | 3 |
KEY_3 | 4 |
KEY_4 | 5 |
二、程序编写
1. 相关API函数
1.1 input_allocate_device ( )
//申请input_dev结构体
struct input_dev *input_allocate_device(void);
- 参数:无
- 返回值:申请到的 input_dev。
1.2 input_free_device ( )
//释放掉申请到的input_dev
void input_free_device(struct input_dev *dev);
- 参数
- dev:需要释放的input_dev。
- 返回值:无
1.3 input_register_device ( )
//初始化input_dev
int input_register_device(struct input_dev *dev);
- 参数
- dev:要注册的 input_dev 。
- 返回值:0,input_dev 注册成功;负值,input_dev 注册失败
1.4 input_unregister_device ( )
//注销input_dev
void input_unregister_device(struct input_dev *dev);
- 参数
- dev:要注销的 input_dev
- 返回值:无
1.5 input_event ( )
//上报事件
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
- 参数:
- dev:指定输设备(input_dev结构体)。
- type:事件类型。
- code:编码。
- value:指定事件的值。
- 返回值: 无
1.6 input_report_key ( )和input_sync ( )
//发送上报结束事件
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{input_event(dev, EV_KEY, code, !!value);
}
//上报按键事件
static inline void input_sync(struct input_dev *dev)
{input_event(dev, EV_SYN, SYN_REPORT, 0);
}
2. probe函数的编写
本次实验采用平台设备的写法,考虑到在input子系统中,我们不需要进行设备号的申请和类的创建,这里便不进行module_init和module_exti的编写。代码如下(示例):
//这里还用到了上章讲到的tasklet和work软中断,在宏定义通过更改DEFER_TEST实现切换
static int button_probe(struct platform_device *pdev)
{struct button_data *priv;struct gpio_desc *gpiod;struct input_dev *i_dev;int ret;pr_info("button_probe\n");priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);if (!priv)return -ENOMEM;i_dev = input_allocate_device();if (!i_dev) {devm_kfree(&pdev->dev, priv);return -ENOMEM;}i_dev->open = btn_open;i_dev->close = btn_close;i_dev->name = "key input";i_dev->dev.parent = &pdev->dev;priv->button_input_dev = i_dev;priv->pdev = pdev;set_bit(EV_KEY, i_dev->evbit);set_bit(KEY_1, i_dev->keybit);gpiod = gpiod_get(&pdev->dev, "button", GPIOD_IN);if (IS_ERR(gpiod)) {ret = PTR_ERR(gpiod);devm_kfree(&pdev->dev, priv);input_free_device(i_dev);return ret;}priv->irq = gpiod_to_irq(gpiod);priv->button_input_gpiod = gpiod;ret = input_register_device(priv->button_input_dev);if (ret) {pr_err("Failed to register input device\n");gpiod_put(priv->button_input_gpiod);devm_kfree(&pdev->dev, priv);input_free_device(i_dev);return ret;}platform_set_drvdata(pdev, priv);// 初始化去抖动工作INIT_DELAYED_WORK(&priv->debounce_work, button_debounce_work);ret = request_any_context_irq(priv->irq, button_input_irq_handler, IRQF_TRIGGER_FALLING, "input-button", priv);if (ret < 0) {dev_err(&pdev->dev, "Request GPIO IRQ failed\n");input_unregister_device(priv->button_input_dev);gpiod_put(priv->button_input_gpiod);devm_kfree(&pdev->dev, priv);return ret;}#if (DEFER_TEST == 0)tasklet_init(&button_tasklet, button_tasklet_handler, 0);
#elif (DEFER_TEST==1)/*初始化button_work*/INIT_WORK(&button_work, button_work_hander);
#endifreturn 0;
}
}
3. remove函数
static int button_remove(struct platform_device *pdev)
{struct button_data *priv = platform_get_drvdata(pdev);#if (DEFER_TEST == 0)tasklet_kill(&button_tasklet);
#endifcancel_delayed_work_sync(&priv->debounce_work);free_irq(priv->irq, priv);input_unregister_device(priv->button_input_dev);gpiod_put(priv->button_input_gpiod);devm_kfree(&pdev->dev, priv);return 0;
}
4. 按键消抖
//按键消抖
static void button_debounce_work(struct work_struct *work)
{struct button_data *priv = container_of(work, struct button_data, debounce_work.work);int button_status;button_status = gpiod_get_value(priv->button_input_gpiod);input_report_key(priv->button_input_dev, KEY_1, button_status);input_sync(priv->button_input_dev);
}//中断服务函数
static irqreturn_t button_input_irq_handler(int irq, void *dev_id)
{struct button_data *priv = dev_id;// 调试信息:记录去抖动工作被执行pr_info("Debounce work executed\n");// 调度去抖动工作schedule_delayed_work(&priv->debounce_work, msecs_to_jiffies(DEBOUNCE_DELAY_MS));#if (DEFER_TEST==0)tasklet_schedule(&button_tasklet);
#elif (DEFER_TEST==1)schedule_work(&button_work); //触发工作
#endif return IRQ_HANDLED;
}
免责声明:本文参考了野火的部分资料,仅供学习参考使用,若有侵权或勘误请联系笔者。