input子系统驱动编写(按键)

之前已经分析过了编写一个驱动程序,主要有以下几个步骤:

  1. 自己设定或由系统自动分配驱动设备的主设备号;
  2. 编写设备操作函数(drv_open、drv_read、drv_write、drv_close等);
  3. 构造file_operations结构体,将上一步编写的操作函数赋值给结构体内的.open、.read、.write、.poll、.fasync
  4. 注册驱动程序,调用register_chrdev(major, name, fops);
  5. 编写入口函数和出口函数。

但输入子系统驱动框架将以上步骤分开了,它是由设备层、核心层、事件层共同组成的。其中核心层提供一些设备层与事件层公用的函数,比如说注册函数、反注册函数、事件到来的处理函数等等,完成了驱动程序编写的第1-4步。但在input框架的file_operations中,只含有一个open函数,正是通过它调用特定input_handler结构体,其里面包含有根据不同次设备号映射到不同的输入设备大类的**.fops**;事件处理层其实在Linux内核里面已经帮我们写好了很多有关的事件;而设备层就是我们要新添加到输入系统的具体设备相关的程序了。

在分析输入子系统框架时,我们已经知道内核对不同类型的输入设备已经抽象出了不同的handler进行处理,device层实现纯硬件的操作,我们可以根据所实现驱动的功能对device层进行设计,主要是内容是当检测有效输入发送,调用input_event函数向handler层上报结果即可

1、编写符合输入子系统框架的驱动程序步骤

  1. 分配一个input_dev结构体,调用input_allocate_device()或者devm_input_allocate_device(struct input_dev*)实现;
  2. 编写函数,设置input_dev结构体,选择input设备的事件类型(调用set_bit()函数实现);
  3. 注册input_dev结构体,调用input_register_device(struct input_dev*)函数实现;
  4. 编写硬件相关代码:注册中断处理函数,比如键盘设备需要编写按键的抬起、放下,触摸屏设备需要编写按下、抬起、绝对移动,鼠标设备需要编写单击、抬起、相对移动,并且需要在必要的时候提交硬件数据(键值/坐标/状态等等),即上报输入事件。
  5. 当提交输入设备产生的输入事件之后,需要调用void input_sync(struct input_dev *dev)函数来通知输入子系统,以处理设备产生的完整事件。

Linux输入子系统支持的事件类型:(在include/linux/input.h中)

EV_SYN     0x00    同步事件
EV_KEY     0x01    按键事件
EV_REL     0x02    相对坐标(如:鼠标移动,报告相对最后一次位置的偏移)
EV_ABS     0x03    绝对坐标(如:触摸屏或操作杆,报告绝对的坐标位置)
EV_MSC     0x04    其它
EV_SW      0x05    开关
EV_LED     0x11    按键/设备灯
EV_SND     0x12    声音/警报
EV_REP     0x14    重复
EV_FF      0x15    力反馈
EV_PWR    0x16    电源
EV_FF_STATUS    0x17   力反馈状态
EV_MAX    0x1f    事件类型最大个数和提供位掩码支持

Linux输入子系统提供的设备驱动层上报输入事件的函数:(在include/linux/input.h中)

void input_event(struct input_dev* dev, unsigned int type, unsigned int code, int value); //上报指定的type、code的输入事件

void input_report_key(struct input_dev *dev, unsigned int code, int value);	//上报按键事件

void input_report_rel(struct input_dev *dev, unsigned int code, int value);	//上报相对坐标事件

void input_report_abs(struct input_dev *dev, unsigned int code, int value);	//上报绝对坐标事件

2、编写符合input系统框架的按键驱动程序

现在我们开始使用内核的输入子系统框架,将自己编写驱动程序融入其中,编写更通用的按键驱动程序。这里以JZ2440开发板上的4个按键作为输入子系统的按键,我们编写的驱动程序实现将开发板上的4个按键分别映射成键盘上不同键值,代表键盘上的字母L、S和Enter(回车)。这几个值是在include\linux\input.h中被定义的。

2.1 编写入口函数、出口函数,搭建好框架

/* 参考drivers\input\keyboard\gpio_keys.c */
#include <linux/module.h>
#include <linux/version.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/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>

#include <asm/gpio.h>
#include <asm/io.h>

static int buttons_init(void)
{
    /* 1.分配一个input_dev结构体。 */
    
    /* 2.设置input_dev结构体。 */
    
    /* 3.注册input_dev结构体。 */
    
    /* 4.硬件相关的操作。 */
}

static void buttons_exit(vod)
{
    
}

module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL");
2.1.1 分配一个input_dev结构体
/* 以下结构体定义位于函数外,属于全局变量*/
static struct input_dev *buttons_dev;

/*以下语句位于buttons_init函数中*/
buttons_dev = input_allocate_device();
if (!buttons_dev)
    return -ENOMEM;
2.1.2 设置input_dev结构体

先看以下Input_dev结构体的定义:

image-20210714230647641

  • evbit:用来设置该设备能产生哪类事件类型(在第1节有具体描述输入子系统所支持的所有事件类型),在此我们选择按键事件EV_KEY和EV_REP,代码如下:

    • set_bit(EV_KEY, buttons_input->evbit);	//按键事件
      set_bit(EV_REP, buttons_input->evbit);	//重复事件(长按按键会重复输入按键值)
      
  • keybit:用来设置该设备能产生哪些按键值,在此我们设置本节开头说的键盘上的L、S、左shift、enter(为了可以在开发板上执行ls命令)。按键码定义在include\linux\input.h中,截取其中一小部分:

    • #define KEY_ENTER        28//enter的按键码
      #define KEY_S            31//S的按键码
      #define KEY_L            38//L的按键码
      #define KEY_LEFTSHIFT    42//leftshift的按键码
      

      设置输入事件类型的哪一种按键的代码:

      set_bit(KEY_L, buttons_input->keybit);
      set_bit(KEY_S, buttons_input->keybit);
      set_bit(KEY_ENTER, buttons_input->keybit);
      set_bit(KEY_LEFTSHIFT, buttons_input->keybit);
      
  • relbit:表示能产生哪些相对位移事件, 例如滚轮

  • absbit:表示能产生哪些绝对位移事件。

2.1.3 注册input_dev结构体

注册的功能其实就是将当前设备buttons_dev放到input_dev链表中去,然后和事件层的evdev_handler逐个比较(通过比较id和id.table[]),如果匹配,则会调用evdev_handler中的.connect函数,产生一个handle结构体(只含有.handler和.dev,一个指向设备,一个指向handler),将当前设备与handler建立起关联,将当前dev及其匹配的handler放到各自的.h_list中。代码如下:

input_register_device(buttons_input);//注册设备驱动
2.1.4 硬件相关的操作
  • 定义各按键描述结构体
/* 以下2个结构体定义位于函数外,属于全局变量*/
struct pin_desc{
  int irq;
  char *name;
  unsigned int pin;
  unsigned int key_val;
};
 
struct pin_desc pins_desc[4] = {
  {IRQ_EINT0,  "S2", S3C2410_GPF(0),   KEY_L},
  {IRQ_EINT2,  "S3", S3C2410_GPF(2),   KEY_S},
  {IRQ_EINT11, "S4", S3C2410_GPG(3),   KEY_ENTER},
};
  • 初始化定时器(防抖动)
/* 以下结构体定义位于函数外,属于全局变量*/
static struct timer_list buttons_timer;	//定义一个定时器

/*以下语句位于buttons_init函数中*/
init_timer(&buttons_timer);	//初始化定时器
buttons_timer.function = buttons_timer_function;//定义超时处理函数
add_timer(&buttons_timer);	//定义超时时间
  • 为每个按键注册中断
/*以下语句位于buttons_init函数中*/
for (i = 0; i < 3; i++)	注册中断
{
	request_irq(pins_desc[i].irq, buttons_irq, (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING), pins_desc[i].name, &pins_desc[i]);
}
  • 所以整体buttons_init函数的代码如下:

static int buttons_init(void)
{
	int i;
	
	/* 1. 分配一个input_dev结构体 */
	buttons_dev = input_allocate_device();;

	/* 2. 设置 */
	/* 2.1 能产生哪类事件 */
	set_bit(EV_KEY, buttons_dev->evbit);
	set_bit(EV_REP, buttons_dev->evbit);
	
	/* 2.2 能产生这类操作里的哪些事件: L,S,ENTER,LEFTSHIT */
	set_bit(KEY_L, buttons_dev->keybit);
	set_bit(KEY_S, buttons_dev->keybit);
	set_bit(KEY_ENTER, buttons_dev->keybit);
	
	/* 3. 注册 */
	input_register_device(buttons_dev);
	
	/* 4. 硬件相关的操作 */
	init_timer(&buttons_timer);
	buttons_timer.function = buttons_timer_function;
	add_timer(&buttons_timer);
	
	for (i = 0; i < 3; i++)
	{
		request_irq(pins_desc[i].irq, buttons_irq, (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING), pins_desc[i].name, &pins_desc[i]);
	}
	
	return 0;
}

  • 编写出口函数
static void buttons_exit(void)
{
	int i;
	for (i = 0; i < 3; i++)
	{
		free_irq(pins_desc[i].irq, &pins_desc[i]);
	}

	del_timer(&buttons_timer);
	input_unregister_device(buttons_dev);
	input_free_device(buttons_dev);	
}

2.2 编写按键的中断处理函数

/* 以下结构体指针定义位于函数外,属于全局变量*/
static struct pin_desc *irq_pd;

static irqreturn_t buttons_irq(int irq, void *dev_id)
{
	/* 10ms后启动定时器 */
	irq_pd = (struct pin_desc *)dev_id;
	mod_timer(&buttons_timer, jiffies+HZ/100);
	return IRQ_RETVAL(IRQ_HANDLED);
}

2.3 编写定时器超时处理函数

当有按键数据产生时,调用input_event函数来上报事件,该函数会从input_dev链表中的.h_list找到对应的.handler,并调用里面的.event函数。


static void buttons_timer_function(unsigned long data)
{
	struct pin_desc * pindesc = irq_pd;
	unsigned int pinval;

	if (!pindesc)
		return;
	
	pinval = s3c2410_gpio_getpin(pindesc->pin);

	if (pinval)
	{
		/* 松开 : 最后一个参数: 0-松开, 1-按下 */
		input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);
		input_sync(buttons_dev);	//上报同步事件
	}
	else
	{
		/* 按下 */
		input_event(buttons_dev, EV_KEY, pindesc->key_val, 1);
		input_sync(buttons_dev);
	}
}

2.4 整体代码buttons.c

/* 参考drivers\input\keyboard\gpio_keys.c */
/* 运行于JZ2440开发板、Linux3.4.2内核、 gcc4.3.2*/
#include <linux/module.h>
#include <linux/version.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/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>

#include <asm/gpio.h>
#include <asm/io.h>
//#include <asm/arch/regs-gpio.h>

struct pin_desc{
	int irq;
	char *name;
	unsigned int pin;
	unsigned int key_val;
};

struct pin_desc pins_desc[4] = {
	{IRQ_EINT0,  "S2", S3C2410_GPF(0),   KEY_L},
	{IRQ_EINT2,  "S3", S3C2410_GPF(2),   KEY_S},
	{IRQ_EINT11, "S4", S3C2410_GPG(3),   KEY_ENTER},
//	{IRQ_EINT19, "S5",  S3C2410_GPG(11), KEY_LEFTSHIFT},
};

static struct input_dev *buttons_dev;
static struct pin_desc *irq_pd;
static struct timer_list buttons_timer;

static irqreturn_t buttons_irq(int irq, void *dev_id)
{
	/* 10ms后启动定时器 */
	irq_pd = (struct pin_desc *)dev_id;
	mod_timer(&buttons_timer, jiffies+HZ/100);
	return IRQ_RETVAL(IRQ_HANDLED);
}

static void buttons_timer_function(unsigned long data)
{
	struct pin_desc * pindesc = irq_pd;
	unsigned int pinval;

	if (!pindesc)
		return;
	
	pinval = s3c2410_gpio_getpin(pindesc->pin);

	if (pinval)
	{
		/* 松开 : 最后一个参数: 0-松开, 1-按下 */
		input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);
		input_sync(buttons_dev);
	}
	else
	{
		/* 按下 */
		input_event(buttons_dev, EV_KEY, pindesc->key_val, 1);
		input_sync(buttons_dev);
	}
}

static int buttons_init(void)
{
	int i;
	
	/* 1. 分配一个input_dev结构体 */
	buttons_dev = input_allocate_device();;

	/* 2. 设置 */
	/* 2.1 能产生哪类事件 */
	set_bit(EV_KEY, buttons_dev->evbit);
	set_bit(EV_REP, buttons_dev->evbit);
	
	/* 2.2 能产生这类操作里的哪些事件: L,S,ENTER,LEFTSHIT */
	set_bit(KEY_L, buttons_dev->keybit);
	set_bit(KEY_S, buttons_dev->keybit);
	set_bit(KEY_ENTER, buttons_dev->keybit);
	//set_bit(KEY_LEFTSHIFT, buttons_dev->keybit);

	/* 3. 注册 */
	input_register_device(buttons_dev);
	
	/* 4. 硬件相关的操作 */
	init_timer(&buttons_timer);
	buttons_timer.function = buttons_timer_function;
	add_timer(&buttons_timer);
	
	for (i = 0; i < 3; i++)
	{
		request_irq(pins_desc[i].irq, buttons_irq, (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING), pins_desc[i].name, &pins_desc[i]);
	}
	
	return 0;
}

static void buttons_exit(void)
{
	int i;
	for (i = 0; i < 3; i++)
	{
		free_irq(pins_desc[i].irq, &pins_desc[i]);
	}

	del_timer(&buttons_timer);
	input_unregister_device(buttons_dev);
	input_free_device(buttons_dev);	
}

module_init(buttons_init);

module_exit(buttons_exit);

MODULE_LICENSE("GPL");

3、测试

3.1 利用hexdump测试

hexdump /dev/event1	//等价于执行`open /dev/event1; read();`
     秒        微秒    类  code    value
0000000 0bb2 0000 0e48 000c 0001 0026 0001 0000
0000010 0bb2 0000 0e54 000c 0000 0000 0000 0000
0000020 0bb2 0000 5815 000e 0001 0026 0000 0000
0000030 0bb2 0000 581f 000e 0000 0000 0000 0000

3.2 如果当前没有启动QT,则按如下方法:

  • 执行cat /dev/tty1
  • 开发板上按下:S2、S3、S4
  • 可以看到:ls

image-20210717121010117

或者

  • 执行exec 0</dev/tty1
  • 可以使用开发板上的键盘来输入ls命令

image-20210717120914031

3.3 如果已经启动了QT

  • 先打开记事本
  • 然后按键S2、S3、S4
  • 可以看到记事本上显示ls

4、小结

  • 打开输入设备后发生了什么?

    • drivers/input/input.c:
      • input_init > err = register_chrdev(INPUT_MAJOR, “input”, &input_fops);
    • static const struct file_operations input_fops = {
      .owner = THIS_MODULE,
      .open = input_open_file,
      };
  • 怎么读按键?

    • intput_open_file
      • struct input_handler *handler = input_table[iminor(inode) >> 5];
      • new_fops = fops_get(handler->fops) // =>&evdev_fops
      • file->f_op = new_fops;
      • err = new_fops->open(inode, file);
    • APP: read > … >file->f_op->read
  • input_table数组由谁构造?

    • input_register_handler
  • 如何注册input_handler?

    • input_register_handler
      • input_table[handler->minor >> 5] = handler; // 放入数组
      • list_add_tail(&handler->node, &input_handler_list); // 放入链表
      • // 对于每个input_dev,调用input_attach_handler
      • list_for_each_entry(dev, &input_dev_list, node)
        • input_attach_handler(dev, handler); // 根据input_handler的id_table判断能否支持这个input_dev
  • 如何 注册输入设备:

    • input_register_device

      • list_add_tail(&dev->node, &input_dev_list); // 放入链表
      • // 对于每一个input_handler,都调用input_attach_handler
        list_for_each_entry(handler, &input_handler_list, node)
        • input_attach_handler(dev, handler); // 根据input_handler的id_table判断能否支持这个input_dev

      input_attach_handler

      • id = input_match_device(handler->id_table, dev);
      • error = handler->connect(handler, dev, id);

      注册input_dev或input_handler时,会两两比较左边的input_dev和右边的input_handler,
      根据input_handler的id_table判断这个input_handler能否支持这个input_dev,
      如果能支持,则调用input_handler的connect函数建立"连接"

输入子系统驱动

  • 怎么建立连接?
  1. 分配一个input_handle结构体
  2. input_handle.dev = input_dev; // 指向左边的input_dev
    input_handle.handler = input_handler; // 指向右边的input_handler
  3. 注册:
    input_handler->h_list = &input_handle;
    inpu_dev->h_list = &input_handle;

evdev_connect

  1. evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); // 分配一个input_handle
  2. // 设置
    evdev->handle.dev = dev; // 指向左边的input_dev
    evdev->handle.name = evdev->name;
    evdev->handle.handler = handler; // 指向右边的input_handler
    evdev->handle.private = evdev;
  3. // 注册
    error = input_register_handle(&evdev->handle);
  • 怎么读按键?

    • APP应用程序执行: read

    • 。。。。

    • evdev_read

      • // 无数据并且是非阻塞方式打开,则立刻返回
        if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))
        return -EAGAIN;

        // 否则休眠
        retval = wait_event_interruptible(evdev->wait,
        client->head != client->tail || !evdev->exist);

    • 谁来唤醒?
      evdev_event
      wake_up_interruptible(&evdev->wait);

    • evdev_event被谁调用?
      猜:应该是硬件相关的代码,input_dev那层调用的
      在设备的中断服务程序里,确定事件是什么,然后调用相应的input_handler的event处理函数
      gpio_keys_isr

      • // 上报事件
        input_event(input, type, button->code, !!state);
        input_sync(input);
      • input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
        • struct input_handle *handle;
        • list_for_each_entry(handle, &dev->h_list, d_node)
          if (handle->open)
          handle->handler->event(handle, type, code, value);
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
在开发输入子系统设备驱动时,通常遵循以下步骤: 1. 包含必要的头文件:在驱动程序的源文件中,你需要包含一些必要的头文件,例如`linux/input.h`、`linux/module.h`、`linux/init.h`等。 2. 定义驱动模块:使用`module_init`宏定义一个初始化函数,用来加载驱动程序。例如: ```c static int __init myinput_init(void) { // 驱动初始化逻辑 return 0; } module_init(myinput_init); ``` 3. 注册输入设备:在初始化函数中,你需要创建一个输入设备并进行注册。你可以使用`input_allocate_device`函数分配一个输入设备结构体,并设置一些属性,例如设备名称、支持的事件类型等。然后,使用`input_register_device`函数注册输入设备。例如: ```c static int __init myinput_init(void) { struct input_dev *myinput_dev; myinput_dev = input_allocate_device(); if (!myinput_dev) { pr_err("Failed to allocate input device\n"); return -ENOMEM; } // 设置设备名称、支持的事件类型等 input_register_device(myinput_dev); return 0; } ``` 4. 处理输入事件:注册完输入设备后,你需要实现一个中断处理函数或者定时器处理函数,用来处理输入事件。当触发输入事件时,驱动程序会调用该函数进行处理。你可以使用`input_report_*`系列函数上报输入事件,例如鼠标移动、按键按下等。例如: ```c static irqreturn_t myinput_interrupt(int irq, void *dev_id) { // 处理输入事件的逻辑 input_report_key(myinput_dev, KEY_A, 1); // 模拟按下 A 键 input_sync(myinput_dev); // 同步输入事件 return IRQ_HANDLED; } ``` 这只是一个简单的示例,实际的输入子系统设备驱动可能还需要处理更多的细节和特定的硬件接口。更详细的编写方法和实现细节可以参考Linux内核源码中的驱动示例和相关文档。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Leon_George

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值