天天看点

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结构体的定义:

input子系统驱动编写(按键)
  • 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      

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

  • 执行​

    ​cat /dev/tty1​

  • 开发板上按下:S2、S3、S4
  • 可以看到:ls
input子系统驱动编写(按键)

或者

  • 执行​

    ​exec 0</dev/tty1​

  • 可以使用开发板上的键盘来输入ls命令
input子系统驱动编写(按键)

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;

  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);

继续阅读