一、自定义驱动的局限性与兼容性需求
对于一些输入设备,可以“自定义框架”—— 自行实现字符设备注册、环形缓冲区、读写逻辑。这种方案仅适用于公司内部闭环场景:
- 若驱动仅由公司内部使用,且应用程序也由自身开发,无需考虑外部兼容性,完全可以采用此方案;
- 但在通用场景下(如 Ubuntu 系统中用遥控器控制 PPT 翻页),现成应用程序(如 PPT 软件)不会适配 自定义设备节点 —— 应用程序开发时,不可能预知后续新增的驱动节点。
核心矛盾:不是外部应用适配我们的驱动,而是我们的驱动需适配通用应用的访问逻辑。无论是按键、摄像头还是屏幕,Linux 驱动开发的核心思路是学习通用框架,基于框架开发,确保驱动能被现有应用程序识别。
二、Linux 驱动的核心思想:分离与分层
Linux 系统中,“分离” 与 “分层” 是贯穿各类驱动框架的核心设计:
- 分离:将驱动分为 “固定通用代码”(与硬件无关)和 “硬件相关代码”,降低耦合;
- 分层:通过核心层(如总线、输入核心层)衔接硬件与应用,统一数据交互接口。
以之前的 “总线设备驱动模型” 为例,已体现 “分离” 思想 —— 将代码拆分为通用总线逻辑和硬件设备逻辑。而输入系统(针对鼠标、键盘、遥控器等输入设备)则进一步强化了这一思想,构建更完善的分层架构:
- 输入核心层:类似 “总线”,负责衔接硬件与应用,提供统一的事件处理接口;
- 处理层:包含通用事件处理函数(与硬件无关),如按键事件解析、坐标转换;
- 设备层:与硬件强相关,负责读取硬件数据(如中断解析红外编码),并通过核心层上报事件。
三、输入系统的设备抽象:提炼输入设备共性
输入系统以 “面向对象思想” 抽象各类输入设备(鼠标、键盘、触摸屏、红外遥控器),提炼共性特性,统一事件模型:
1. 输入设备的共性特性
| | |
| | 相对 X/Y 轴位移、左键 / 右键 / 滚轮按键 |
| | 绝对 X/Y 轴坐标(屏幕固定区域)、触摸 “按下 / 松开” 按键 |
| | |
| | |
2. 输入设备的核心描述维度
驱动需明确描述两类信息,确保输入核心层识别设备能力:
- 设备能产生的 “事件类型”(如 EV_KEY:按键事件、EV_REL:相对位移事件、EV_ABS:绝对位移事件);
- 某类事件下能产生的 “具体事件”(如 EV_KEY 下的 “电源键编码”“音量 + 键编码”)。
四、输入系统驱动实现:三步核心流程
基于输入子系统开发驱动(以HS0038红外遥控为例),核心流程为 “分配input_dev结构体→设置结构体→注册结构体”,无需重复实现字符设备的 open/read 逻辑(输入核心层已封装)。
1. 步骤 1:分配 input_dev 结构体
input_dev 是输入系统描述设备的核心结构体,需通过内核提供的接口分配:
#include<linux/input.h>// 输入系统头文件
staticstructinput_dev *hs0038_input_dev;// 定义input_dev指针
// 在probe函数中分配结构体
hs0038_input_dev = input_allocate_device();
if (!hs0038_input_dev) {
return -ENOMEM; // 分配失败返回错误
}
2. 步骤 2:设置 input_dev 结构体
核心是描述设备的 “事件能力”—— 明确能产生的事件类型及具体事件:
// 1. 设置设备名称(用于识别设备)
hs0038_input_dev->name = "hs0038_ir";
// 2. 标记设备能产生的事件类型:EV_KEY(按键事件)、EV_REP(重复事件)
__set_bit(EV_KEY, hs0038_input_dev->evbit); // 支持按键事件
__set_bit(EV_REP, hs0038_input_dev->evbit); // 支持重复事件(长按按键)
// 3. 标记设备能产生的具体按键事件:简化处理,支持所有按键(实际需按遥控器编码适配)
int i;
for (i = 0; i < KEY_MAX; i++) {
__set_bit(i, hs0038_input_dev->keybit); // 标记支持所有按键编码
}
- 说明:
__set_bit是内核宏,用于设置结构体中的位掩码(evbit:事件类型掩码,keybit:按键编码掩码);实际开发中需根据遥控器的实际按键编码,只标记支持的按键,避免冗余。
3. 步骤 3:注册 input_dev 结构体
将设置好的结构体注册到输入核心层,由核心层自动创建设备节点(如/dev/input/event1):
// 在probe函数中注册
int ret = input_register_device(hs0038_input_dev);
if (ret) {
input_free_device(hs0038_input_dev); // 注册失败,释放结构体
return ret;
}
// 在remove函数中注销(资源释放)
input_unregister_device(hs0038_input_dev);
input_free_device(hs0038_input_dev);
五、中断数据处理:事件上报
输入系统无需自行实现 read 函数 —— 应用程序通过读取/dev/input/eventX节点获取事件,驱动只需在 “解析到红外数据后”,通过输入核心层上报事件即可:
1. 事件上报逻辑
在中断服务程序(ISR)的 “解析成功” 分支中,上报按键事件(按下 + 松开):
// 解析到的红外按键编码(如key_code = parsed_data)
unsignedint key_code;
// 1. 上报“按键按下”事件:type=EV_KEY,code=按键编码,value=1(按下)
input_event(hs0038_input_dev, EV_KEY, key_code, 1);
// 2. 上报“同步”事件:通知应用程序,当前批次事件已完成
input_sync(hs0038_input_dev);
// 3. (可选)上报“按键松开”事件:模拟按键回弹,value=0(松开)
msleep(20); // 延时模拟按键按下时长
input_event(hs0038_input_dev, EV_KEY, key_code, 0);
input_sync(hs0038_input_dev);
- 关键函数:
input_event用于上报具体事件,input_sync用于同步事件(确保应用程序完整接收事件批次)。
六、驱动编译与功能验证
1. 编译注意事项
- Makefile 配置:无需额外修改(与字符设备驱动一致,确保内核路径正确)。
2. 功能验证步骤
- 装载新驱动:
insmod 05-hs0038-input.ko; - 查看设备节点:
ls /dev/input/,确认新增eventX节点(如event1); - 读取事件数据:
hexdump /dev/input/event1,按下遥控器按键,观察输出数据(虽显示为十六进制,但可确认事件已上报)。
七、输入系统的优势总结
相比自定义字符设备驱动,输入系统的核心优势在于 “兼容性” 与 “简化开发”:
- 兼容性:驱动适配通用
/dev/input/eventX节点,可被所有支持输入系统的应用程序识别(如 PPT 软件、系统设置); - 简化开发:无需自行实现字符设备的 open/read/write 逻辑、数据处理 —— 输入核心层已封装所有通用逻辑,驱动只需关注 “硬件数据读取” 与 “事件上报”;
- 分层解耦:硬件相关代码(中断解析、事件上报)与通用逻辑(节点创建、数据交互)完全分离,便于维护与移植。