输入子系统

一、系统分析

1.输入子系统简介

同样的输入子系统也需要输入驱动的框架,好来辨认应用程序要打开的是哪个输入驱动

比如: 鼠标、键盘、游戏手柄等等这些都属于输入设备;这些输入设备的驱动都是通过输入子系统来实现的(当然,这些设备也依赖于usb子系统)

这些输入设备都各有不同,那么输入子系统也就只能实现他们的共性,差异性则由设备驱动来实现。差异性又体现在哪里?

最直观的就表现在这些设备功能上的不同了。对于我们写驱动的人来说在设备驱动中就只要使用输入子系统提供的工具(也就是函数)来完成这些“差异”就行了,其他的则是输入子系统的工作。这个思想不仅存在于输入子系统,其他子系统也是一样(比如:usb子系统、video子系统等)

所以我们先来分析下输入子系统input.c的代码,然后怎么来使用输入子系统(在内核中以input来形容输入子系统)

2.输入子系统初始化分析

打开input.c,位于内核deivers/input,有以下这么两段:

1
2
subsys_initcall(input_init);   //修饰入口函数
module_exit(input_exit); //修饰出口函数

显然输入子系统是作为一个模块存在,我们先来分析下input_int()入口函数

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
static int __init input_init(void)
{
int err;
err = class_register(&input_class); //(1)在/sys/class 里创建一个 input类
if (err) {
printk(KERN_ERR "input: unable to register input_dev class\n");
return err;
}

err = input_proc_init(); //在/proc下面建立相关的文件
if (err)
goto fail1;

err = register_chrdev(INPUT_MAJOR, "input", &input_fops); //(2)注册驱动
if (err) {
printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);
goto fail2;
}

return 0;

fail2:
input_proc_exit();

fail1:
class_unregister(&input_class);

return err;
}
  • (1)这里的input_class变量如下所示:

    1
    2
    3
    4
    5
    struct class input_class={
    .name = "input",
    .release = input_dev_release,
    .uevent = input_dev_uevent,
    }

    当启动一个input子系统的驱动后,可以通过如下命令看到创建了个"input"类 :

    1
    2
    3
    4
    5
    6
    7
    # ls /sys/class/
    graphics misc
    hwmon mmc_host
    i2c-adapter mtd
    input net
    mem ppdev
    #

    为什么这里代码只创建类,没有使用class_device_create()函数在类下面创建驱动设备?

    在下面第8小结会详细讲到,这里简单描述:当注册input子系统的驱动后,才会有驱动设备,此时这里的代码是没有驱动的

  • (2)这里通过register_chrdev创建驱动设备,其中宏INPUT_MAJOR =13,所以创建了一个主设备为13的"input"设备。

    然后我们来看看它的操作结构体input_fops,如下:

    1
    2
    3
    4
    static const struct file_operations input_fops={
    .owner =THIS_MODULE,
    .open =input_open_file,
    };

    只有一个.open函数,比如当我们挂载一个新的input驱动,则内核便会调用该.open函数,其他一些工作肯定是在这个函数中处理的,接下来分析该.open函数

3.input_open_file函数分析

然后进入input_open_file函数(drivers/input/input.c):

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
static int input_open_file(struct inode *inode, struct file *file)
{
struct input_handler *handler = input_table[iminor(inode) >> 5]; // (1)
const struct file_operations *old_fops, *new_fops = NULL;
int err;

if (!handler || !(new_fops = fops_get(handler->fops))) //(2)
return -ENODEV;

if (!new_fops->open) {
fops_put(new_fops);
return -ENODEV;
}

old_fops = file->f_op;
file->f_op = new_fops; //(3)

err = new_fops->open(inode, file); //(4)
if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}

fops_put(old_fops);

return err;
}
  • (1)这里iminor (inode)函数调用了MINOR(inode->i_rdev);读取打开文件(input设备)的子设备号,然后将子设备除以32,找到新挂载的input驱动的数组号,然后放在input_handler *类型的handler中
  • (2)若handler有值,说明挂载有这个驱动,利用fops_get()将handler结构体里的成员file_operations *fops取出赋到新的file_operations *new_fops里面,即此时new_fops指向了新挂载的input驱动的操作函数
  • (3)再将新的file_operations *new_fops赋到file->file_operations *f_op里, 此时input子系统的file_operations就等于新挂载的input驱动的file_operations结构体,实现一个偷天换日的效果.
  • (4)然后调用新挂载的input驱动的*new_fops里面的成员.open函数

4.input_table[]数组的赋值

上面代码的input_table[]数组在初始时是没有值的,所以需要找到他在那里被赋值

在input.c函数(drivers/input/input.c)中搜索input_table,找到它在input_register_handler()函数中被赋值,代码如下:

1
2
3
4
5
6
7
8
int input_register_handler(struct input_handler *handler)
{
... ...
input_table[handler->minor >> 5] = handler; //input_table[]被赋值
... ...
list_add_tail(&handler->node, &input_handler_list); //将这个input_handler放到input_handler_list链表中
... ...
}

这里就是将input_handler *类型的变量handler注册到input_table[]中,然后将handler插入input_handler_list链表中,后面第7.1会讲这个链表

5.input_register_handler的调用

继续来搜索input_register_handler,看看这个函数被谁来调用

通过搜索可以查到,有evdev.c(事件设备),tsdev.c(触摸屏设备),joydev.c(joystick操作杆设备),keyboard.c(键盘设备),mousedev.c(鼠标设备) 等各种设备调用了该函数

大致可以推断,以上各种设备通过input_register_handler函数向input.c注册

这里以evdev.c(事件设备)举例,通过如下函数向input.c注册

1
2
3
4
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler)
}

6.input_handler结构体分析

我们来看看这个evdev_handler变量是什么结构体

1
2
3
4
5
6
7
8
9
static struct input_handler evdev_handler = {
.event = evdev_event,
.connect = evdev_connect, //(4)
.disconnect = evdev_disconnect,
.fops = &evdev_fops, //(1)
.minor = EVDEV_MINOR_BASE, //(2)
.name = "evdev",
.id_table = evdev_ids, //(3)
};
  • (1)**.fops:**文件操作结构体,其中evdev_fops为file_operations结构体,就是自己的写的操作函数,然后赋到.fops中

  • (2)**.minor:**用来存放次设备号,其中EVDEV_MINOR_BASE=64, 这里调用input_register_handler(&evdev_handler)后,由于EVDEV_MINOR_BASE/32=2,所以最终会将evdev_handler存到input_table[2]中

    假设当open打开这个input设备时,就会进入 input_open_file()函数,执行evdev_handler-> evdev_fops -> .open函数,如下所示为evdev_fops结构体的内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    static const struct flle_operations evdev_fops={
    .owner =THIS MODULE,
    .read =evdev_read,
    .write =evdev_write,
    .poll =evdeV_poll,
    .open =evdev_open,
    .release =evdev_release,
    .unlocked_ioctl =evdev_ioctl,
    #ifdef CONFIG_COMPAT
    .compat_ioctl =evdev_ioctl_compat,
    #endif
    .fasync =evdev_fasync,
    .flush =evdev_flush
    };

    故最终会调用到evdev_open函数

  • (3).id_table : 表示能支持哪些输入设备,比如某个驱动设备的input_dev->的id和某个input_handler的id_table相匹配,就会调用.connect连接函数,如下图

  • (4).connect: 连接函数,将设备input_dev和某个input_handler建立连接,如下图

    img

7.input_dev和input_handler及其相关关系

我们先来看看上图的input_register_device()函数,谁负责调用他

搜索input_register_device,发现内核自己就已经注册了很多驱动设备

7.1input_register_device()函数分析

进入input_register_device()函数,代码如下:

1
2
3
4
5
6
7
8
9
int input_register_device(struct input_dev *dev)   //*dev:要注册的驱动设备
{
... ...
list_add_tail(&dev->node, &input_dev_list); //(1)将驱动设备放入input_dev_list链表中
... ...
list_for_each_entry(handler, &input_handler_list, node) //(2)
input_attach_handler(dev, handler); //(3)
... ...
}
  • (1)将要注册的input_dev类型的驱动设备放在input_dev_list链表中
  • (2)其中input_handler_list在前面第4点讲过(下面会再讲),就是存放每个input_handle结构体的链表,然后list_for_each_entry()函数会将每个input_handle从链表中取出,放到handler中,并调用一次input_attach_handler
  • (3)根据要注册的驱动设备和每个input_handle类型的handler的id_table进行判断,能否支持这个input_dev类型的驱动设备,若两者支持便进行连接。

7.2.input_register_handler()函数分析

然后我们在回过头来看注册input_handler的input_register_handler()函数,有:

1
2
3
4
5
6
7
8
9
10
int input_register_handler(struct input_handler *handler)
{
... ...
input_table[handler->minor >> 5] = handler; //(1)input_table[]被赋值
... ...
list_add_tail(&handler->node, &input_handler_list); //(2)将这个input_handler放到input_handler_list链表中
list_for_each_entry(dev,&input_dev_list,node)//(3)
input_attach_handler(dev,handler);//(4)
... ...
}
  • (1)将input_handler *类型的变量handler放入数组input_table[]中
  • (2)将input_handler *类型的变量handler插入input_handler_list链表中
  • (3)list_for_each_entry()函数会将每个input_dev从链表input_dev_list中取出,放到dev中,并调用一次input_attach_handler
  • (4)根据要注册的input_handler类型的handler的id_table和每个input_dev类型的dev进行判断,能否支持这个input_dev类型的驱动设备,若两者支持便进行连接。

所以,不管新添加input_dev还是input_handler,都会进入**input_attach_handler()**判断两者id是否匹配, 若两者匹配便进行连接。

7.3.input_attach_handler()函数分析

我们来看看input_attach_handler()如何实现匹配两者id的

1
2
3
4
5
6
7
8
9
10
11
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
... ...
id = input_match_device(handler->id_table, dev); //根据handler的id_table和dev进行匹配

if (!id) //若不匹配,return退出
return -ENODEV;

error = handler->connect(handler, dev, id); //若匹配,调用handler的connect函数建立连接
... ...
}

若两者匹配成功,就会自动进入input_handler类型变量handler的connect函数建立连接

8.连接的建立

我们还是以evdev.c(事件驱动) 的evdev_handler->connect函数来分析是怎样建立连接的。

首先根据evdev.c中的input_handler类型变量evdev_handler分析,有:

1
2
3
4
5
6
7
8
9
static struct input_handler evdev_handler={
.event=evdev_event,
.connect=evdev_connect,
.disConnect=evdev_disconnect,
.fops=& evdev_fops,
.minor=EVDEV_MINOR_BASE,
.name="evdev",
.id_table=evdev_ids,
};

8.1.evdev_connect()函数分析

evdev_handler的.connect函数是evdev_connect(),代码如下:

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
static int evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id)
{
... ...
for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++); //查找驱动设备的子设备号
if (minor == EVDEV_MINORS) { // EVDEV_MINORS=32,所以该事件下的驱动设备最多存32个,
printk(KERN_ERR "evdev: no more free evdev devices\n");
return -ENFILE; //没找到驱动设备
}
... ...
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); //struct evdev结构体中包含input_handle,这里主要是分配一个input_handle全局结构体(没有r)
... ...
evdev->exist = 1;
evdev->minor = minor;
evdev->handle.dev = dev; //指向input_dev驱动设备
evdev->handle.name = evdev->name;
evdev->handle.handler = handler; //指向input_handler驱动处理结构体
evdev->handle.private = evdev;
sprintf(evdev->name, "event%d", minor); //(1)保存驱动设备名字, event%d
... ...
devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor), //(2) 将主设备号和次设备号转换成dev_t类型
cdev = class_device_create(&input_class, &dev->cdev, devt,dev->cdev.dev, evdev->name); // (3)在input类下创建驱动设备

... ...
error = input_register_handle(&evdev->handle); //(4)注册这个input_handle结构体
... ...
}
  • (1)保存驱动设备名字,名为event%d, 比如下面(键盘驱动)event1: 因为没有设置子设备号,默认从小到大排列,其中event0是表示这个input子系统,所以这个键盘驱动名字就是event1

  • (2)是在保存驱动设备的主次设备号,其中主设备号INPUT_MAJOR=13,因为EVDEV_MINOR_BASE=64,所以此设备号=64+驱动程序本事子设备号, 比如下图(键盘驱动)event1: 主次设备号就是13,65

  • (3)在之前在第2点中就分析了input_class类结构,所以该处会在/sys/class/input类下创建驱动设备event%d,比如下图(键盘驱动)event1:

    1
    2
    3
    4
    5
    6
    7
    8
    # ls -l /dev/event*
    crw-rw---- 1 0 0 13, 64 Jan 1 00:00 /dev/event0(input子系统自带的驱动设备)
    crw-rw---- 1 0 0 13, 65 Jan 1 01:02 /dev/event1(后来安装的键盘驱动设备)
    # ls /sys/class/input/
    event0 event1 input0 input1 mice mouse0 ts0
    |
    V
    键盘与input_handler在连接函数里创建的驱动设备
  • (4)最终会进入input_register_handle()函数来注册,代码在下面

8.2.input_register_handle()函数分析

1
2
3
4
5
6
7
8
9
10
11
12
//error = input_register_handle(&evdev->handle);
int input_register_handle(struct input_handle *handle)
{
struct input_handler *handler = handle->handler; //此处的handler==evdev->handle.handler,即evdev_connect函数传下来的input_handler驱动处理结构体

list_add_tail(&handle->d_node, &handle->dev->h_list); //(1)
list_add_tail(&handle->h_node, &handler->h_list); // (2)

if (handler->start)
handler->start(handle);
return 0;
}
  • (1)此处因为右边参数中handle->dev指向input_dev驱动设备,所以就是将左边参数handle->d_node放入到input_dev驱动设备的h_list链表中,即input_dev驱动设备的h_list就指向input_handle(没有r)结构体handle中的d_node成员

  • (2)同样, input_handler驱动处理结构体的h_list也指向了handle->h_node

    最终如下图所示:

img

两者的.h_list都指向了同一个handle结构体,然后通过.h_list 来找到handle的成员.dev和handler,便能找到对方,便建立了连接

9.按键读取分析

假设当read打开这个input设备时,就会执行evdev_handler结构体中evdev_fops成员中的.read函数,如下所示为evdev_fops结构体的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static const struct flle_operations evdev_fops={
.owner =THIS MODULE,
.read =evdev_read,
.write =evdev_write,
.poll =evdeV poll,
.open =evdev_open,
.release =evdev_release,
.unlocked_ioctl =evdev_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl =evdev_ioctl_compat,
#endif
.fasync =evdev_fasync,
.flush =evdev_flush
};

故最终会调用到evdev_read函数,我们来分析下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static ssize_t evdev_read(struct file *file, char __user *      buffer, size_t count, loff_t *ppos)
{
... ...
/*判断应用层要读取的数据是否正确*/
if (count < evdev_event_size())
return -EINVAL;

/*无数据并且是非阻塞方式打开,则立刻返回*/
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);
... ... //上传数据
}

10.read函数的唤醒

若read函数进入了休眠状态,又是谁来唤醒?

我们搜索这个evdev->wait这个等待队列变量,找到在evdev_event函数中唤醒:

1
2
3
4
5
static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
{
... ...
wake_up_interruptible(&evdev->wait); //有事件触发,便唤醒等待中断
}

其中evdev_event()是evdev.c(事件驱动) 的input_handler结构体中的成员event,如下所示:

1
2
3
4
5
6
7
8
9
static struct input_handler evdev_handler={
.event =evdev_event,
.connect =evdev_connect,
.disconnect =evdev_disconnect,
.fops =& evdev_fops,
.minor =EVDEV_MINOR_BASE,
.name ="evdev",
.id_table =evdev_ids,
};

当有事件发生了,比如对于按键驱动,当有按键按下时,就会进入.event函数中处理事件

11.evdev_event()的调用

分析下,是谁调用evdev_event()这个.event事件驱动函数

应该就是之前分析的input_dev那层调用的

我们来看看内核 gpio_keys_isr()函数代码例子就知道了 (driver/input/keyboard/gpio_key.c)

1
2
3
4
5
6
7
8
static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{
/*获取按键值,赋到state里*/
... ...
/*上报事件*/
input_event(input, type, button->code, !!state);
input_sync(input);//同步信号通知,表示事件发送完毕
}

显然就是通过input_event()来调用.event事件函数,我们来看看:

1
2
3
4
5
6
7
8
9
10
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
struct input_handle *handle;
... ...

/* 通过input_dev->h_list链表找到input_handle驱动处理结构体*/
list_for_each_entry(handle, &dev->h_list, d_node)//这里就是从dev->h_list取出每一项(这里的每一项就是input_handle(没有r)类型的)并执行其对应的open函数
if (handle->open) //如果input_handle之前open过,那么这个就是我们的驱动处理结构体
handle->handler->event(handle, type, code, value); //调用evdev_event()的.event事件函数
}

若之前驱动input_dev和处理input_handler已经通过input_handler 的.connect函数建立起了连接,那么就调用evdev_event()的.event事件函数,如下图所示:

img

12.总结分析

1.注册输入子系统,进入put_init():

1)创建主设备号为13的"input"字符设备

1
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);

2.open打开驱动,进入input_open_file():

1)更新设备的file_oprations

1
file->f_op=fops_get(handler->fops);

2)执行file_oprations->open函数

1
err = new_fops->open(inode, file);

3.注册input_handler,进入input_register_handler():

1)添加到input_table[]处理数组中

1
input_table[handler->minor >> 5] = handler;

2)添加到input_handler_list链表中

1
list_add_tail(&handler->node, &input_handler_list);

3)判断input_dev的id,是否有支持这个驱动的设备

1
2
3
list_for_each_entry(dev, &input_dev_list, node)   //遍历查找input_dev_list链表里所有input_dev

input_attach_handler(dev, handler); //判断两者id,若两者支持便进行连接。

4.注册input_dev,进入input_register_device():

1)放在input_dev_list链表中

1
list_add_tail(&dev->node, &input_dev_list);

2)判断input_handler的id,是否有支持这个设备的驱动

1
2
list_for_each_entry(handler, &input_handler_list, node)  //遍历查找input_handler_list链表里所有input_handler
input_attach_handler(dev, handler); //判断两者id,若两者支持便进行连接。

5.判断input_handler和input_dev的id,进入input_attach_handler():

1)匹配两者id,

1
input_match_device(handler->id_table, dev);        //匹配input_handler和dev的id,不成功退出函数

2)匹配成功调用input_handler ->connect

1
handler->connect(handler, dev, id);              //建立连接

6.建立input_handler和input_dev的连接,进入input_handler->connect():

1)创建全局结构体,通过input_handle结构体连接双方

1
2
3
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);    //创建两者连接的input_handle全局结构体
list_add_tail(&handle->d_node, &handle->dev->h_list); //连接input_dev->h_list
list_add_tail(&handle->h_node, &handler->h_list); // 连接input_handle->h_list

7.有事件发生时,比如按键中断,在中断函数中需要进入input_event()上报事件:

1)找到驱动处理结构体,然后执行input_handler->event()

1
2
3
list_for_each_entry(handle, &dev->h_list, d_node)     // 通过input_dev ->h_list链表找到input_handle驱动处理结构体
if (handle->open) //如果input_handle之前open 过,那么这个就是我们的驱动处理结构体(有可能一个驱动设备在不同情况下有不同的驱动处理方式)
handle->handler->event(handle, type, code, value); //调用evdev_event()的.event事件函数

二、程序分析

**目标:**实现键盘驱动,让开发板的4个按键代表键盘中的L、S、空格键、回车键

1.几个常用结构体和函数

  • 1)input_dev驱动设备结构体中常用成员如下:

    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
    struct input_dev {

    void *private;
    const char *name; //设备名字
    const char *phys; //文件路径,比如 input/buttons
    const char *uniq;
    struct input_id id;


    unsigned long evbit[NBITS(EV_MAX)]; //表示支持哪类事件,常用有以下几种事件(可以多选)
    //EV_SYN 同步事件,当使用input_event()函数后,就要使用这个上报个同步事件
    //EV_KEY 键盘事件
    //EV_REL (relative)相对坐标事件,比如鼠标
    //EV_ABS (absolute)绝对坐标事件,比如摇杆、触摸屏感应
    //EV_MSC 其他事件,功能
    //EV_LED LED灯事件
    //EV_SND (sound)声音事件

    //EV_REP 重复键盘按键事件
    //(内部会定义一个定时器,若有键盘按键事件一直按下/松开,就重复定时,时间一到就上报事件)

    //EV_FF 受力事件
    //EV_PWR 电源事件
    //EV_FF_STATUS 受力状态事件

    unsigned long keybit[NBITS(KEY_MAX)]; //存放支持的键盘按键值
    //键盘变量定义在:include/linux/input.h, 比如: KEY_L(按键L)
    unsigned long relbit[NBITS(REL_MAX)]; //存放支持的相对坐标值
    unsigned long absbit[NBITS(ABS_MAX)]; //存放支持的绝对坐标值
    unsigned long mscbit[NBITS(MSC_MAX)]; //存放支持的其它事件,也就是功能
    unsigned long ledbit[NBITS(LED_MAX)]; //存放支持的各种状态LED
    unsigned long sndbit[NBITS(SND_MAX)]; //存放支持的各种声音
    unsigned long ffbit[NBITS(FF_MAX)]; //存放支持的受力设备
    unsigned long swbit[NBITS(SW_MAX)]; //存放支持的开关功能

    ... ...
    }
  • 2)函数如下:

    1
    struct input_dev *input_allocate_device(void);  //向内核中申请一个input_dev设备,然后返回这个设备
    1
    input_unregister_device(struct input_dev *dev);   //卸载/sys/class/input目录下的input_dev这个类设备, 一般在驱动出口函数写
    1
    input_free_device(struct input_dev *dev);   //释放input_dev这个结构体, 一般在驱动出口函数写
    1
    2
    3
    4
    5
    set_bit(nr,p);                  //设置某个结构体成员p里面的某位等于nr,支持这个功能
    /* 比如:
    set_bit(EV_KEY,buttons_dev->evbit); //设置input_dev结构体buttons_dev->evbit支持EV_KEY
    set_bit(KEY_S,buttons_dev->keybit); //设置input_dev结构体buttons_dev->keybit支持按键”S”
    */
    1
    2
    3
    4
    5
    void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);  //上报事件
    // input_dev *dev :要上报哪个input_dev驱动设备的事件
    // type : 要上报哪类事件, 比如按键事件,则填入: EV_KEY
    // code: 对应的事件里支持的哪个变量,比如按下按键L则填入: KEY_L
    //value:对应的变量里的数值,比如松开按键则填入1,松开按键则填入0
    1
    void input_sync(struct input_dev *dev); //同步事件通知

    这里input_event()函数只是个事件函数,需要input_sync()同步事件函数来通知系统,然后系统才会知道

    其中,input_sync()代码如下:

    1
    2
    3
    4
    static inline void input_sync(struct input_dev *dev)
    {
    input_event(dev, EV_SYN, SYN_REPORT, 0); //就是上报同步事件,告诉内核:input_event()事件执行完毕
    }

2.代码分析

1)向内核申请input_dev结构体

2)设置input_dev的成员

3)注册input_dev 驱动设备

4)初始化定时器和中断

5)写中断服务函数

6)写定时器超时函数

7)在出口函数中 释放中断函数,删除定时器,卸载释放驱动

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
#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 <linux/gpio_keys.h>
#include <asm/gpio.h>


struct input_dev *buttons_dev; // 定义一个input_dev结构体
static struct ping_desc *buttons_id; //保存dev_id,在定时器中用
static struct timer_list buttons_timer; //定时器结构体

struct ping_desc{

unsigned char *name; //中断设备名称
int pin_irq; //按键的外部中断标志位
unsigned int pin; //引脚
unsigned int irq_ctl; //触发中断状态: IRQ_TYPE_EDGE_BOTH
unsigned int button; //dev_id,对应键盘的 L , S, 空格, enter
};

// KEY1 -> L
// KEY2 -> S
// KEY3 -> 空格
// KEY4 -> enter
static struct ping_desc buttons_desc[5]=
{
{"s1", IRQ_EINT0, S3C2410_GPF0, IRQ_TYPE_EDGE_BOTH,KEY_L},
{"s2", IRQ_EINT2, S3C2410_GPF2, IRQ_TYPE_EDGE_BOTH,KEY_S},
{"s3", IRQ_EINT11, S3C2410_GPG3 , IRQ_TYPE_EDGE_BOTH,KEY_SPACE},
{"s4", IRQ_EINT19, S3C2410_GPG11,IRQ_TYPE_EDGE_BOTH,KEY_ENTER},
};



/*5. 写中断服务函数*/
static irqreturn_t buttons_irq (int irq, void *dev_id) //中断服务函数
{
buttons_id=(struct ping_desc *)dev_id; //保存当前的dev_id
mod_timer(&buttons_timer, jiffies+HZ/100 ); //更新定时器值 10ms
return 0;
}



/*6.写定时器超时函数*/
void buttons_timer_function(unsigned long i)
{
int val;
val=s3c2410_gpio_getpin(buttons_id->pin); //获取是什么电平
if(val) //高电平,松开
{
/*上报事件*/
input_event(buttons_dev,EV_KEY,buttons_id->button, 0); //上报EV_KEY类型,button按键,0(没按下)
input_sync(buttons_dev); // 上传同步事件,告诉系统有事件出现
}

else //低电平,按下
{
/*上报事件*/
input_event(buttons_dev, EV_KEY, buttons_id->button, 1); //上报EV_KEY类型,button按键,1(按下)
input_sync(buttons_dev); // 上传同步事件,告诉系统有事件出现
}
}


static int buttons_init(void) //入口函数
{
int i;
buttons_dev=input_allocate_device(); //1.向内核 申请input_dev结构体
/*2.设置input_dev , */
set_bit(EV_KEY,buttons_dev->evbit); //支持键盘事件
set_bit(EV_REP,buttons_dev->evbit); //支持键盘重复按键事件,该事件在input_event上报EV_KEY事件时会自动修改定时器来进行处理

set_bit(KEY_L,buttons_dev->keybit); //支持按键 L
set_bit(KEY_S,buttons_dev->keybit); //支持按键 S
set_bit(KEY_SPACE,buttons_dev->keybit); //支持按键 空格
set_bit(KEY_ENTER,buttons_dev->keybit); //支持按键 enter

/*3.注册input_dev */
input_register_device(buttons_dev);


/*4. 初始化硬件:初始化定时器和中断*/
// KEY1 -> L
// KEY2 -> S
// KEY3 -> 空格
// KEY4 -> enter
init_timer(&buttons_timer);
buttons_timer.function=buttons_timer_function;
add_timer(&buttons_timer);

for(i=0;i<4;i++)
request_irq(buttons_desc[i].pin_irq, buttons_irq, buttons_desc[i].irq_ctl, buttons_desc[i].name, &buttons_desc[i]);

return 0;
}


static int buttons_exit(void) //出口函数
{
/*7.释放中断函数,删除定时器,卸载释放驱动*/
int i;
for(i=0;i<4;i++)
free_irq(buttons_desc[i].pin_irq,&buttons_desc[i]); //释放中断函数

del_timer(&buttons_timer); //删除定时器

input_unregister_device(buttons_dev); //卸载类下的驱动设备
input_free_device(buttons_dev); //释放驱动结构体
return 0;
}

module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL v2");

3.测试运行

挂载键盘驱动后, 如下所示,可以通过 ls -l /dev/event* 命令查看已挂载的设备节点:

1
2
3
# ls -l /dev/event*
crw-rw---- 1 0 0 13, 64 Jan 1 00:00 /dev/event0(自带的触摸屏驱动设备)
crw-rw---- 1 0 0 13, 65 Jan 1 01:02 /dev/event1(后来安装的键盘驱动设备)

上一节输入子系统里分析到:输入子系统的主设备号为13,其中event驱动本身的此设备号是从64开始的,如上所示,内核启动时,会加载自带触摸屏驱动,所以我们的键盘驱动的次设备号=64+1

3.1测试运行有两种,一种是直接打开/dev/tyy1,第二种是使用exec命令

方法1:

1
cat /dev/tty1     //tty1:LCD终端,就会通过tty_io.c来访问键盘驱动,然后打印在tty1终端上

方法2:

1
exec 0</dev/tty1    //利用>将/dev/tty1作为输入传给该shell进程的0号文件(该shell的输入设备),此时的键盘驱动就会直接打印在shell上

3.2 调试

若测试不成功,板子又在QT下进行的:

1)可以使用vi命令,在记事本中按按键试

2)或者删除/etc/init.d/rcS 里面有关QT自启动的命令,然后重启

若板子没在QT下进行,也无法测试成功:

1)可以使用hexdump命令来调试代码

1
2
3
4
5
6
hexdump /dev/event1
秒 微秒 类 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