往期内容
本专栏往期内容:
- input子系统的框架和重要数据结构详解-CSDN博客
- input device和input handler的注册以及匹配过程解析-CSDN博客
- input device和input handler的注册以及匹配过程解析-CSDN博客
I2C子系统专栏:
- 专栏地址:IIC子系统_憧憬一下的博客-CSDN博客
- 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
– 末篇,有往期内容观看顺序总线和设备树专栏:
- 专栏地址:总线和设备树_憧憬一下的博客-CSDN博客
- 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
– 末篇,有往期内容观看顺序
前言
和之前的驱动程序有点差别(IIC专栏中编写的控制器驱动框架编写一个通用的i2c控制器驱动框架-CSDN博客),在driver中变成注册input_dev,file_operations字符驱程序的创建在input_handler层实现(原本是在platform_driver中实现的:file_operation、设备类的注册),实现了内核驱动程序的上层、中转层、下层的分离
下层驱动中,只需要去编写好设备的驱动程序,在程序中分配、设置、注册input_dev,发生中断时只需要上报中断事件即可,其余的中转层和上层的驱动程序内核已经做好了。
这个在之前对内核提供的源码示例进行讲解的时候也很清晰了,详见本专栏前3章内容。
1. 怎么编写input_dev驱动
这里参考内核提供的gpio_keys.c为例子,input_dev上层
\Linux-4.9.88\drivers\input\keyboard\gpio_keys.c:📎gpio_keys.c
1.1 分配、设置、注册input_dev
这一部分主要是probe完成:
在gpio_keys.c中,添加了自己理解的一点注释,如下:
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; // 输入设备结构体
size_t size; // 计算需要分配的内存大小
int i, error; // 循环计数器和错误码
int wakeup = 0; // 标志位,指示是否支持唤醒功能
// 如果 pdata 为 NULL,则从设备树获取平台数据
if (!pdata) {
pdata = gpio_keys_get_devtree_pdata(dev);
if (IS_ERR(pdata))
return PTR_ERR(pdata); // 返回错误码
}
// 计算 gpio_keys_drvdata 和按钮数据结构的大小
size = sizeof(struct gpio_keys_drvdata) +
pdata->nbuttons * sizeof(struct gpio_button_data);
// 分配驱动私有数据内存
ddata = devm_kzalloc(dev, size, GFP_KERNEL);
if (!ddata) {
dev_err(dev, "failed to allocate state\n"); // 分配失败,输出错误信息
return -ENOMEM; // 返回内存不足错误码
}
// 分配输入设备
input = devm_input_allocate_device(dev);
if (!input) {
dev_err(dev, "failed to allocate input device\n"); // 分配失败,输出错误信息
return -ENOMEM; // 返回内存不足错误码
}
ddata->pdata = pdata; // 保存平台数据指针
ddata->input = input; // 保存输入设备指针
mutex_init(&ddata->disable_lock); // 初始化互斥锁
// 将驱动数据指针与平台设备相关联
platform_set_drvdata(pdev, ddata);
input_set_drvdata(input, ddata); // 将驱动数据与输入设备关联
// 设置输入设备名称和物理路径
input->name = pdata->name ? : pdev->name; // 如果 pdata 中有名称则使用,否则使用平台设备名称
input->phys = "gpio-keys/input0"; // 设置物理路径
input->dev.parent = &pdev->dev; // 设置设备的父设备
input->open = gpio_keys_open; // 设置打开设备的函数
input->close = gpio_keys_close; // 设置关闭设备的函数
// 设置输入设备的 ID
input->id.bustype = BUS_HOST; // 设置总线类型
input->id.vendor = 0x0001; // 设置厂商 ID
input->id.product = 0x0001; // 设置产品 ID
input->id.version = 0x0100; // 设置版本号
// 启用 Linux 输入子系统的自动重复功能
if (pdata->rep)
__set_bit(EV_REP, input->evbit); // 设置 EV_REP 事件位
// 遍历每个按钮并设置
for (i = 0; i < pdata->nbuttons; i++) {
const struct gpio_keys_button *button = &pdata->buttons[i]; // 获取当前按钮信息
struct gpio_button_data *bdata = &ddata->data[i]; // 获取按钮数据
error = gpio_keys_setup_key(pdev, input, bdata, button); // 设置按键,里面包括设置了中断函数
if (error)
return error; // 返回错误码
if (button->wakeup) // 如果按钮支持唤醒功能
wakeup = 1; // 设置唤醒标志
}
// 创建 sysfs 组,用于导出按键和开关
error = sysfs_create_group(&pdev->dev.kobj, &gpio_keys_attr_group);
if (error) {
dev_err(dev, "Unable to export keys/switches, error: %d\n", error); // 输出错误信息
return error; // 返回错误码
}
// 注册输入设备
error = input_register_device(input);
if (error) {
dev_err(dev, "Unable to register input device, error: %d\n", error); // 输出错误信息
goto err_remove_group; // 错误处理,移除 sysfs 组
}
// 初始化唤醒设备功能
device_init_wakeup(&pdev->dev, wakeup);
return 0; // 返回成功
err_remove_group:
// 在错误情况下移除 sysfs 组
sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group);
return error; // 返回错误码
}
可以看出来,分配、设置、注册input_dev,然后去注册中断函数,用于调用中断的input_event函数上报中断事件,尝试写一个:
static struct input_dev *g_input_dev;
static int g_irq;
static irqreturn_t input_dev_demo_isr(int irq, void *dev_id)
{
/* read data */
/* report data */
input_event(g_input_dev, EV_KEY, XX, 0);//通过 input_event() 上报事件,input_sync() 用于同步报告的输入事件。
input_sync(g_input_dev);
return IRQ_HANDLED;
}
static int input_dev_demo_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int error;
struct resource *irq;
/* 从设备树中获取硬件信息 */
/* 分配、设置并注册 input_dev */
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); // 绝对坐标事件
/* 设置 2: 支持的具体事件 */
__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 轴坐标
/* 设置 3: 事件参数 */
input_set_abs_params(g_input_dev, ABS_MT_POSITION_X, 0, 0xffff, 0, 0); // X 坐标范围
input_set_abs_params(g_input_dev, ABS_MT_POSITION_Y, 0, 0xffff, 0, 0); // Y 坐标范围
// 注册输入设备
error = input_register_device(g_input_dev);
/* 硬件操作: 获取中断资源并注册中断 */
irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
g_irq = irq->start;
request_irq(irq->start, input_dev_demo_isr, IRQF_TRIGGER_RISING, "input_dev_demo_irq", NULL);
return 0;
}
1.2 硬件相关操作
- 申请中断
- 在中断服务程序里
- 读取硬件获得数据
- 上报数据
void input_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value);
static inline void input_sync(struct input_dev *dev); // 实质也是 input_event
input子系统中读取流程解析-CSDN博客
具体内容在该章节的 event 处已经讲解过。
2. 代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#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>
#include <linux/workqueue.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/spinlock.h>
/* 定义指向输入设备结构体的指针 */
static struct input_dev *input_device_demo;
/* 用于保存输入设备的中断号 */
static int irq_num;
/* 中断服务程序,处理输入设备的事件 */
static irqreturn_t input_device_demo_isr(int irq, void *dev_id)
{
/* 可在此处添加数据读取和事件处理逻辑 */
/* 向输入子系统报告按键事件 */
input_event(input_device_demo, EV_KEY, KEY_TOUCH, 0);
input_sync(input_device_demo);
return IRQ_HANDLED;
}
/* 分配、配置和注册平台驱动 */
static int input_device_demo_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int error;
struct resource *irq;
/* 从设备树获取硬件信息 */
/* 分配并初始化输入设备结构体 */
input_device_demo = devm_input_allocate_device(dev);
if (!input_device_demo)
return -ENOMEM;
/* 设置设备名称和物理位置 */
input_device_demo->name = "input_device_demo";
input_device_demo->phys = "input_device_demo";
input_device_demo->dev.parent = dev;
/* 设置设备的总线类型和ID */
input_device_demo->id.bustype = BUS_HOST;
input_device_demo->id.vendor = 0x0001;
input_device_demo->id.product = 0x0001;
input_device_demo->id.version = 0x0100;
/* 设置输入设备支持的事件类型 */
__set_bit(EV_KEY, input_device_demo->evbit); // 支持按键事件
__set_bit(EV_ABS, input_device_demo->evbit); // 支持绝对位置事件
/* 设置输入设备支持的具体事件 */
__set_bit(BTN_TOUCH, input_device_demo->keybit); // 支持触摸按键事件
__set_bit(ABS_MT_SLOT, input_device_demo->absbit); // 多点触控槽位事件
__set_bit(ABS_MT_POSITION_X, input_device_demo->absbit); // X轴位置事件
__set_bit(ABS_MT_POSITION_Y, input_device_demo->absbit); // Y轴位置事件
/* 设置具体事件的参数范围,例如X和Y坐标的最小值、最大值等 */
input_set_abs_params(input_device_demo, ABS_MT_POSITION_X, 0, 0xffff, 0, 0);
input_set_abs_params(input_device_demo, ABS_MT_POSITION_Y, 0, 0xffff, 0, 0);
/* 注册输入设备 */
error = input_register_device(input_device_demo);
if (error)
return error;
/* 硬件操作:从设备树获取中断资源并注册中断 */
irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
irq_num = irq->start;
error = request_irq(irq_num, input_device_demo_isr, IRQF_TRIGGER_RISING, "input_device_demo_irq", NULL);
if (error)
input_unregister_device(input_device_demo);
return error;
}
/* 驱动移除函数,释放中断资源并注销输入设备 */
static int input_device_demo_remove(struct platform_device *pdev)
{
free_irq(irq_num, NULL);
input_unregister_device(input_device_demo);
return 0;
}
/* 设备树匹配表,用于匹配设备树中描述的设备 */
static const struct of_device_id input_device_demo_of_match[] = {
{ .compatible = "input,input_device_demo", },
{ },
};
/* 定义平台驱动结构体,指定probe和remove函数 */
static struct platform_driver input_device_demo_driver = {
.probe = input_device_demo_probe,
.remove = input_device_demo_remove,
.driver = {
.name = "input_device_demo",
.of_match_table = input_device_demo_of_match,
}
};
/* 模块初始化函数,注册平台驱动 */
static int __init input_device_demo_init(void)
{
return platform_driver_register(&input_device_demo_driver);
}
/* 模块退出函数,注销平台驱动 */
static void __exit input_device_demo_exit(void)
{
platform_driver_unregister(&input_device_demo_driver);
}
module_init(input_device_demo_init);
module_exit(input_device_demo_exit);
MODULE_LICENSE("GPL");
在以前的驱动程序模板中,platform_driver中不仅注册了file_operation等,也注册了中断处理函数,并且中断处理函数中是直接对中断事件进行处理
但是在内核驱动分层中,platform_input_dev,中断处理函数只上报中断事件给input.c,交由Input.c去调用input_dev对应的input_handler中的函数来处理中断(filter、events、event函数),同时input_handler层提供了app调用的接口函数,如file_operation中的read