输入子系统概念介绍

 

更多文档:http://pan.baidu.com/s/1jGzEzbw

 

输入子系统在内核中的位置:/driver/input

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,

};

问:怎么读按键?

input_open_file

      struct input_handler *handler;

      handler = input_table[iminor(inode) >> 5];

      new_fops = fops_get(handler->fops)

      file->f_op = new_fops;//这样当调用read时,就调用了具体设备驱动的read

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

App: read ---> … --->  file->f_op->read

input_table数组由谁构造?(由于intput_table是一个static类型数组,可以在当前文件看到它是如何构建的)

     int input_register_handler(struct input_handler *handler)

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

其中函数input_register_handler会被具体的设备驱动程序调用,如:evdev.c、joydev.c 、keyboard.c 、mousedev.c、rfkill-input.c。

注册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函数建立"连接"

怎么建立连接?(以evdev.c为例)
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;
   input_dev->h_list      = &input_handle;


evdev_connect
    evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); // 分配一个input_handle
   
    // 设置
    evdev->handle.dev = dev;  // 指向左边的input_dev
    evdev->handle.name = evdev->name;
    evdev->handle.handler = handler;  // 指向右边的input_handler
    evdev->handle.private = evdev;
   
    // 注册
    error = input_register_handle(&evdev->handle);

 

怎么读按键?(以evdev.c为例)
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.c为例,在当前文件搜索evdev->wait)
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是在/drivers/input/input.c实现的
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);

第13课第1节 输入子系统概念介绍_P.wmv_snapshot_47.27_[2014.05.03_09.47.57]_副本

说明:

drivers/input/input.c构成了输入子系统的核心层。除了核心外,输入子系统分为硬件相关部分和纯软件handler部分,这两个都要调用核心层提供的函数接口,如硬件部分调用核心层提供的input_register_device完成注册,纯软件handler部分调用核心提供的input_register_handler完成注册。

不管是硬件部分还是软件部分先注册,他们都通过input_attach_handler(dev, handler)去匹配相适应的对方,如果匹配到,会调用handler提供的connect函数(在connect函数中会完成设备结点的注册,而在注册),在connect函数中会创建一个handle的结构体,这个结构体同时包含了input_dev和input_handler两个部分的信息,然后会把handle分别跟input_dev和input_handler关联起来,这样input_handler或者input_dev都可以通过handle找到对方。

以读为例,当调用open时,会调到input_handler提供的open函数,调用read时,会调到handler提供的read函数,因为在open时file->f_op被“调包”了。调用read,如果没有数据,read可能阻塞,它的唤醒是在硬件相关部分的中断处理函数中通过调用核心层提供的input_event完成的。核心层的input_event会调用到handler提供的XXX_event函数,在xxx_event函数中完成唤醒操作。

 

怎么写符合输入子系统框架的驱动程序?
1. 分配一个input_dev结构体
2. 设置
3. 注册
4. 硬件相关的代码,比如在中断服务程序里上报事件 
           
struct input_dev {

    void *private;

    const char *name;
    const char *phys;
    const char *uniq;
    struct input_id id;

    unsigned long evbit[NBITS(EV_MAX)];   // 表示能产生哪类事件
    unsigned long keybit[NBITS(KEY_MAX)]; // 表示能产生哪些按键
    unsigned long relbit[NBITS(REL_MAX)]; // 表示能产生哪些相对位移事件, x,y,滚轮
    unsigned long absbit[NBITS(ABS_MAX)]; // 表示能产生哪些绝对位移事件, x,y
    unsigned long mscbit[NBITS(MSC_MAX)];
    unsigned long ledbit[NBITS(LED_MAX)];
    unsigned long sndbit[NBITS(SND_MAX)];
    unsigned long ffbit[NBITS(FF_MAX)];
    unsigned long swbit[NBITS(SW_MAX)];

测试:
1.
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

2. 如果没有启动QT:
cat /dev/tty1
按:s2,s3,s4
就可以得到ls

或者:
exec 0</dev/tty1
然后可以使用按键来输入


3. 如果已经启动了QT:
可以点开记事本
然后按:s2,s3,s4

下面是buttons.c,可以在tq2440上运行:

 
/* 参考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>
#include <mach/regs-gpio.h>
 
struct pin_desc{
    int irq;
    char *name;
    unsigned int pin;
    unsigned int key_val;
};
 
struct pin_desc pins_desc[4] = {
        {IRQ_EINT1,  "K1", S3C2410_GPF1,   KEY_L},
        {IRQ_EINT4,  "K2", S3C2410_GPF4,   KEY_S},
        {IRQ_EINT2, "K3", S3C2410_GPF2,   KEY_ENTER},
        {IRQ_EINT0, "K4",  S3C2410_GPF0, 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);//pindesc->key_val表示键值
        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);// 映射成字符 'l'
    set_bit(KEY_S, buttons_dev->keybit); // 映射成字符 's'
    set_bit(KEY_ENTER, buttons_dev->keybit);  // 映射成 回车键
    set_bit(KEY_LEFTSHIFT, buttons_dev->keybit);  //映射成shift键
 
    /* 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 < 4; 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 < 4; 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");
 
 
 

下面是Makefile的内容:

KERN_DIR = /home/pengdl/tq2440/kernel/origin/linux-2.6.30.4
 
all:
    make -C $(KERN_DIR) M=`pwd` modules 
 
clean:
    make -C $(KERN_DIR) M=`pwd` modules clean
    rm -rf modules.order
 
obj-m    += buttons.o
posted @ 2014-05-02 22:28  摩斯电码  阅读(690)  评论(0编辑  收藏  举报