Chinaunix首页 | 论坛 | 博客
  • 博客访问: 829154
  • 博文数量: 281
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 2770
  • 用 户 组: 普通用户
  • 注册时间: 2009-08-02 19:45
个人简介

邮箱:zhuimengcanyang@163.com 痴爱嵌入式技术的蜗牛

文章分类
文章存档

2020年(1)

2018年(1)

2017年(56)

2016年(72)

2015年(151)

分类: 嵌入式

2015-08-24 10:59:04

目标

之前我们写的驱动都是自己构造的,那些设备文件是自己定义的,只有自己知道。
怎么写一个驱动程序,可以跟linux无缝结合呢,这就需要输入子系统了。




介绍

输入子系统由驱动层、输入子系统核心、事件处理层三部分组成。
一个输入事件,如鼠标移动、键盘按下等通过Driver->Inputcore->Event handler->userspace的顺序到达用户控件的应用程序。





驱动层:
将底层的硬件输入转化为统一事件形式,向输入核心(
Input Core)汇报。

输入子系统核心:
承上启下。为驱动层提供输入设备注册与操作接口,如:input_register_device;通知事件处理层对事件进行处理;在/proc下产生相应的设备信息。

事件处理层:
主要是和用户空间交互。(Linux中在用户空间将所有的设备都当初文件来处理,由于在一般的驱动程序中都有提供fops接口,以及在/dev下生成相应的设备文件nod这些操作在输入子系统中由事件处理层完成【在handler中定义,下面会讲有一个移花接木过程。】)

 

设备描述:

input_dev结构

实现设备驱动核心工作是:向系统报告按键、触摸屏等输入事件(event,通过input_event结构描述),不再需要关心文件操作接口。驱动报告事件经过inputCoreEventhandler到达用户空间。

/*
 * The event structure itself
 */
struct input_event {
    struct timeval time;
    __u16 type;
    __u16 code;
    __s32 value;
};

注册输入设备函数:

int input_register_device(struct input_dev *dev)

注销输入设备函数:

void input_unregister_device(struct input_dev *dev)


驱动实现——初始化(事件支持)

set_bit()告诉input输入子系统支持哪些事件,哪些按键。例如:

set_bit(EV_KEY, button_dev->evbit)  (其中button_devstruct input_dev *类型)


struct input_dev中有两个成员为:

evbit:

事件类型(包括EV_RST,EV_REL,EV_MSC,EV_KEY,EV_ABS,EV_REP等)

keybit:

按键类型(当事件类型为EV_KEY时包括BTN_LEFT,BTN_0,BTN_1,BTN_MIDDLE等)


驱动实现——报告事件:

用于报告EV_KEY,EV_REL,EV_ABS事件的函数分别为
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)

这几个函数最终都是调用函数:void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);

驱动实现——报告结束:

input_sync()同步用于告诉input core子系统报告结束。


实例:触摸屏设备驱动中,一次点击的整个报告过程如下:

input_reprot_abs(input_dev,ABS_X,x);   //x坐标

input_reprot_abs(input_dev,ABS_Y,y);   // y坐标

input_reprot_abs(input_dev,ABS_PRESSURE,1);

input_sync(input_dev);//同步结束



源码分析

1. 先看核心层: drivers/input/input.c:
入口函数: input_init
里面做了两件事:(1)err = class_register(&input_class);                        // 注册一个类
             (2)err = register_chrdev(INPUT_MAJOR, "input", &input_fops);  // 注册字符设备
和以前自己写的区别是:没有在类下注册一个设备。猜测:应该是有设备注册的时候,才开始在类下注册设备的,在后面肯定会有在类下添加设备的函数。

input_init > err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
这里主设备号: INPUT_MAJOR = 13 , 由内核自己定义的。启动开发板,可以查看/proc/devices. 可以看到输入子系统驱动,主设备号为13.

static const struct file_operations input_fops = {
 .owner = THIS_MODULE,
 .open = input_open_file,
};


问:
看到上面的
file_operations结构有疑问:为什么只有.open函数呢??那如何进行读操作呢??

input_open_file

     struct input_handler *handler = input_table[iminor(inode) >> 5];  // 从这个数组中获取handler
     new_fops = fops_get(handler->fops)  //  =>&evdev_fops   从handler中获取fops结构体
     file->f_op = new_fops;              //  移花接木,将新的fops作为当前
     err = new_fops->open(inode, file);  //  用新的fops的open函数打开设备文件

 读操作怎么实现呢??调用新的fops的read函数。所以这里的input_fops只是起了一个桥梁的作用。

 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


注册input_device:

注册输入设备:
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

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函数
   // 分配

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

 


怎么读按键?
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)  // 遍历所有input_dev中的handle,如果handle打开过,则调用handler->event
  if (handle->open)
   handle->handler->event(handle, type, code, value);

对应evdev设备,handler定义:

点击(此处)折叠或打开

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


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


驱动代码

点击(此处)折叠或打开

  1. /* 参考drivers\input\keyboard\gpio_keys.c */

  2. #include <linux/module.h>
  3. #include <linux/version.h>

  4. #include <linux/init.h>
  5. #include <linux/fs.h>
  6. #include <linux/interrupt.h>
  7. #include <linux/irq.h>
  8. #include <linux/sched.h>
  9. #include <linux/pm.h>
  10. #include <linux/sysctl.h>
  11. #include <linux/proc_fs.h>
  12. #include <linux/delay.h>
  13. #include <linux/platform_device.h>
  14. #include <linux/input.h>
  15. #include <linux/irq.h>

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

  19. struct pin_desc{
  20.     int irq;
  21.     char *name;
  22.     unsigned int pin;
  23.     unsigned int key_val;
  24. };

  25. struct pin_desc pins_desc[4] = {
  26.     {IRQ_EINT0, "S2", S3C2410_GPF0, KEY_L},
  27.     {IRQ_EINT2, "S3", S3C2410_GPF2, KEY_S},
  28.     {IRQ_EINT11, "S4", S3C2410_GPG3, KEY_ENTER},
  29.     {IRQ_EINT19, "S5", S3C2410_GPG11, KEY_LEFTSHIFT},
  30. };

  31. static struct input_dev *buttons_dev;
  32. static struct pin_desc *irq_pd;
  33. static struct timer_list buttons_timer;

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

  41. static void buttons_timer_function(unsigned long data)
  42. {
  43.     struct pin_desc * pindesc = irq_pd;
  44.     unsigned int pinval;

  45.     if (!pindesc)
  46.         return;
  47.     
  48.     pinval = s3c2410_gpio_getpin(pindesc->pin);

  49.     if (pinval)
  50.     {
  51.         /* 松开 : 最后一个参数: 0-松开, 1-按下 */
  52.         input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);
  53.         input_sync(buttons_dev);
  54.     }
  55.     else
  56.     {
  57.         /* 按下 */
  58.         input_event(buttons_dev, EV_KEY, pindesc->key_val, 1);
  59.         input_sync(buttons_dev);
  60.     }
  61. }

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

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

  78.     /* 3. 注册 */
  79.     input_register_device(buttons_dev);
  80.     
  81.     /* 4. 硬件相关的操作 */
  82.     init_timer(&buttons_timer);
  83.     buttons_timer.function = buttons_timer_function;
  84.     add_timer(&buttons_timer);
  85.     
  86.     for (i = 0; i < 4; i++)
  87.     {
  88.         request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]);
  89.     }
  90.     
  91.     return 0;
  92. }

  93. static void buttons_exit(void)
  94. {
  95.     int i;
  96.     for (i = 0; i < 4; i++)
  97.     {
  98.         free_irq(pins_desc[i].irq, &pins_desc[i]);
  99.     }

  100.     del_timer(&buttons_timer); // 卸载定时器
  101.     input_unregister_device(buttons_dev); // 卸载注册的设备
  102.     input_free_device(buttons_dev);     // 释放初始化的设备内存。
  103. }

  104. module_init(buttons_init);

  105. module_exit(buttons_exit);

  106. MODULE_LICENSE("GPL");

测试:

编译代码成buttons.ko,加载驱动。

# insmod buttons.ko
input: Unspecified device as /class/input/input1
# 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 00:34 /dev/event1
# rmmod buttons
# lsmod
Module                  Size  Used by    Not tainted
# ls -l /dev/event*
crw-rw----    1 0        0         13,  64 Jan  1 00:00 /dev/event0


1. hexdump /dev/event1 
    (这个命令将会:打开文件open(/dev/event1), 读取read(), )

读取函数将调用:
    evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
        evdev_event_to_user(char __user *buffer, const struct input_event *event)
            copy_to_user(buffer, event, sizeof(struct input_event))  // 读取的是:struct input_event

struct input_event结构体:
/*
 * The event structure itself
 */
struct input_event {
    struct timeval time;
    __u16 type;
    __u16 code;
    __s32 value;
};

struct timeval {
    time_t        tv_sec;        /* seconds */
    suseconds_t    tv_usec;    /* microseconds */
};



           秒        微秒     类  code   value


                                                                         低字节     高字节
0000000 0bb2 0000 0e48 000c 0001 0026 0001    0000   // 按下S2键, KEY_L, KEY_L = 38 = 0x26; 按下按键为 1.
0000010 0bb2 0000 0e54 000c 0000 0000 0000    0000
                            类:EV_KEY 在文件中定义为: #define EV_KEY  0x01

00008a0 0c87 0000 3757 000b 0001 001f 0001    0000   // 按下S3键, KEY_S, KEY_S = 31 = 0x1f; 按下按键为 1.
00008b0 0c87 0000 3762 000b 0000 0000 0000    0000



2. 如果没有启动QT:
cat /dev/tty1
按:s2,s3,s4
就可以得到ls (按下s2, s3, 到按下s4的时候,在LCD上显示“ls”字符串)。

或者:
exec 0
然后可以使用按键来测试。(按下s2,s3,s4键,可以键入ls命令)

# cat /dev/tty1
ss
ls

# exec 0
# ls
Makefile
Module.symvers
buttons.c
buttons.ko
buttons.mod.c
buttons.mod.o
buttons.o

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


输入子系统架构图




阅读(414) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~