首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

编写一个简单的Iinput_dev框架

  • 25-03-04 14:01
  • 3369
  • 7977
blog.csdn.net

往期内容

本专栏往期内容:

  1. input子系统的框架和重要数据结构详解-CSDN博客
  2. input device和input handler的注册以及匹配过程解析-CSDN博客
  3. input device和input handler的注册以及匹配过程解析-CSDN博客

I2C子系统专栏:

  1. 专栏地址:IIC子系统_憧憬一下的博客-CSDN博客
  2. 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
    – 末篇,有往期内容观看顺序

总线和设备树专栏:

  1. 专栏地址:总线和设备树_憧憬一下的博客-CSDN博客
  2. 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
    – 末篇,有往期内容观看顺序

前言

img
img

和之前的驱动程序有点差别(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完成:

img

在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;  // 返回错误码
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96

可以看出来,分配、设置、注册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
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

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
  • 1
  • 2
  • 3
  • 4

input子系统中读取流程解析-CSDN博客
具体内容在该章节的 event 处已经讲解过。

2. 代码

img

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/* 定义指向输入设备结构体的指针 */
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");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132

img

在以前的驱动程序模板中,platform_driver中不仅注册了file_operation等,也注册了中断处理函数,并且中断处理函数中是直接对中断事件进行处理

但是在内核驱动分层中,platform_input_dev,中断处理函数只上报中断事件给input.c,交由Input.c去调用input_dev对应的input_handler中的函数来处理中断(filter、events、event函数),同时input_handler层提供了app调用的接口函数,如file_operation中的read

注:本文转载自blog.csdn.net的憧憬一下的文章"https://blog.csdn.net/caiji0169/article/details/143261794"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top