input子系统驱动

框架分析

核心层

文件为:/drivers/input/input.c;

首先找到入口函数为static int __init input_init(void),在该函数中主要做了如下几件事:

  1. 创建了一个input_class类;
  2. 初始化/proc/input路径下的文件;
  3. 使用register_chrdev()注册了一个名为input_fops的file_operations结构体变量,且主设备号为13(INPUT_MAJOR)

通过注册的file_operations中可发现只有一个.open函数,指向的是input_open_file函数,进入该函数中可发现如下语句:

struct input_handler *handler = input_table[iminor(inode) >> 5];
new_fops = fops_get(handler->fops);
err = new_fops->open(inode, file);
  1. 根据子设备号,从数组input_table[]中拿到对应的一个数据类型为struct input_handler *的handler;
  2. 将这个handler中指向的fops赋值给new_fops;
  3. 打开new_fops中的open()函数;

上述分析可看出,在input.c中的input_fops结构中的.open只是一个“中转”的作用,最终还是会用到“input_table[]”;

问题:那么input_table[]数组是由谁构建的?

input_table[]数组的定义是“static struct input_handler *input_table[8];”,是一个文件内部使用的数组,搜索查看,可以找到如下两个函数:

int input_register_handler(struct input_handler *handler);
void input_unregister_handler(struct input_handler *handler);

在input_register_handler()中可以看到:

if (handler->fops != NULL) {
if (input_table[handler->minor >> 5])
return -EBUSY;
/* 放入数组中 */
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_register_handler()是谁在调用,可以找到/drivers/input/evdev.c(以此为例分析)文件下:

static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}

在入口函数evdev_init()中调用了input_register_handler()接口进行注册,以上便是具体设备与核心层(input.c)的层次调用关系;

注册的evdev_handler是一个"struct input_handler"结构体,具体定义如下:

static struct input_handler evdev_handler = {
.event = evdev_event,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.fops = &evdev_fops,
.minor = EVDEV_MINOR_BASE, /* 子设备号为64 */
.name = "evdev",
.id_table = evdev_ids, /* 表示这个evdev_handler可以支持哪些“输入设备” */
};

其中.fops指向的evdev_fops,里面指定了open、read、write等的操作函数,具体定义如下:

static const struct file_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
};

同时在input.c这个文件中可以找到这个函数:

int input_register_device(struct input_dev *dev);

并且有如下操作:

/* 放入链表中 */
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);

从上面分析可得出,不管是先注册加载“handler”,还是注册加载“device”,最后都会调用input_attach_handler();

分析input_attach_handler中的代码可以看出:在注册input_dev或input_handler时,会两两比较input_dev和input_handler,根据input_handler的id_table判断这个input_handler能否支持这个input_dev,如果支持,则调用input_handler中的connect函数建立“连接“,因为是调用input_handler中的connect函数,所以不同的handler可能有自己不同的连接方式;

最后可以得出,input子系统整体框架中,一边是向核心层“input.c”注册handler,这一层代表“软件”,还有另一边向核心层“input.c”注册device,这一层代表“硬件”;

代码分析

下面的代码是使用input子系统框架实现了按键输入检测的驱动程序

/*
* jz2440 buttons driver
**/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <linux/input.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h> static struct input_dev *buttons_dev;
static struct timer_list buttons_timer; /* 该变量不能直接定义为指针变量,否则在init_timer()中传入使用时,因为传入的是空指针系统报错 */ static struct button_irq_desc{
int irq; /* 中断号 */
unsigned long flags; /* 中断标志,用来定义中断的触发方式 */
char *name; /* 中断名称 */
unsigned int pin; /* 引脚 */
unsigned int key_val;/* 键值 */
}; static struct button_irq_desc button_irqs[] = {
{IRQ_EINT0, IRQT_BOTHEDGE, "KEY1", S3C2410_GPF0, KEY_L}, /* S2 */
{IRQ_EINT2, IRQT_BOTHEDGE, "KEY2", S3C2410_GPF2, KEY_S}, /* S3 */
{IRQ_EINT11, IRQT_BOTHEDGE, "KEY3", S3C2410_GPG3, KEY_ENTER}, /* S4 */
{IRQ_EINT19, IRQT_BOTHEDGE, "KEY4", S3C2410_GPG11, KEY_LEFTSHIFT}, /* S5 */
}; static struct button_irq_desc *pin_desc; static irqreturn_t buttons_interrupt(int irq, void *dev_id)
{
pin_desc = (struct button_irq_desc *)dev_id; /* 10ms后启动定时器 */
mod_timer(&buttons_timer, jiffies+HZ/100); return IRQ_RETVAL(IRQ_HANDLED);
} static void buttons_timer_function(unsigned long data)
{
unsigned int pinval; if (!pin_desc)
return; /* 获取对应引脚的值 */
pinval = s3c2410_gpio_getpin(pin_desc->pin); if (pinval)
{
/* 松开 : 最后一个参数: 0-松开, 1-按下 */
input_event(buttons_dev, EV_KEY, pin_desc->key_val, 0);
input_sync(buttons_dev);
}
else
{
/* 按下 */
input_event(buttons_dev, EV_KEY, pin_desc->key_val, 1);
input_sync(buttons_dev);
}
} static int __init s3c2440_buttons_init(void)
{
int error, i; /* 1. 分配一个input_dev结构体 */
buttons_dev = input_allocate_device();
if (!buttons_dev)
return -ENOMEM; /* 2. 设置 */
/* 设置能产生哪类事件 */
set_bit(EV_KEY, buttons_dev->evbit); /* 可以产生按键类事件 */
set_bit(EV_REP, buttons_dev->evbit); /* 可以产生重复类事件 */ /* 设置能产生这类事件中的哪些事件:L, S, ENTER, LEFTSHIFT */
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. 注册 */
error = input_register_device(buttons_dev);
if (error) {
printk(KERN_ERR "Unable to register buttons input device\n");
goto fail;
} /* 4. 硬件相关的操作 */
/* 注册一个定时器用于按键消抖 */
init_timer(&buttons_timer);
buttons_timer.function = buttons_timer_function;
add_timer(&buttons_timer); /* 申请中断 */
for(i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++){
request_irq(button_irqs[i].irq, buttons_interrupt, button_irqs[i].flags, button_irqs[i].name, &button_irqs[i]);
} return 0; fail:
input_free_device(buttons_dev); return error;
} static void __exit s3c2440_buttons_exit(void)
{
int i; /* 释放中断 */
for(i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++){
free_irq(button_irqs[i].irq, &button_irqs[i]);
} /* 删除定时器 */
del_timer(&buttons_timer); input_unregister_device(buttons_dev); input_free_device(buttons_dev);
} module_init(s3c2440_buttons_init);
module_exit(s3c2440_buttons_exit); MODULE_LICENSE("GPL");
MODULE_AUTHOR("jason.tian");
MODULE_VERSION("0.0.1");
MODULE_DESCRIPTION("S3C2440 buttons Driver");

按照上述代码总结如何写一个符合input子系统驱动框架的驱动程序,步骤如下:

  1. 分配一个input_dev结构体(使用input_allocate_device()函数);

  2. 对input_dev结构体成员进行设置;

    • 设置能产生哪类事件,例如:

      set_bit(EV_KEY, buttons_dev->evbit); /* 可以产生按键类事件 */
      set_bit(EV_REP, buttons_dev->evbit); /* 可以产生重复类事件 */
    • 设置能产生这类事件中的哪些事件,例如:

      /* 设置能产生这类事件中的哪些事件:L, S, ENTER, LEFTSHIFT */
      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()函数注册);

  4. 硬件相关的操作,比如在中断服务程序中使用input_event()函数进行上报事件,并使用input_sync()函数产生一个同步信号;

当装载按键驱动模块后,可以使用如下方法测试:

  1. cat /dev/tty1 或者 exec 0</dev/tty1,然后使用按键输入;

  2. hexdump /dev/event1 (具体查看是哪个event,ls -l /dev/event*);

    hexdump 是16进制显示open后的/dev/event1,显示的数据格式如下:

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

使用内核中的gpio_key.c

代码位于:/drivers/input/keyboard/gpio_keys.c

该文件是内核中自带的一个基于input子系统架构并且符合“总线、设备、驱动”模型的通用GPIO按键驱动,所以需要自己编写一个具有设备信息的platform_device文件;

下面分别是driver文件和device文件,其中gpio_keys.c中的内容有部分优化,因为在原有代码实测中发现,按键的抖动对按键输入有影响,所以增加了一个定时器用于按键消抖,具体代码如下:

/* 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 <linux/gpio_keys.h>
#include <asm/gpio.h> static struct platform_device *pdev_buttons;
static struct timer_list buttons_timer;
static int buttons_irq; static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{
pdev_buttons = dev_id;
buttons_irq = irq; /* 10ms后启动定时器 */
mod_timer(&buttons_timer, jiffies+HZ/100); return IRQ_HANDLED;
} static void buttons_timer_function(unsigned long data)
{
int i; struct gpio_keys_platform_data *pdata = pdev_buttons->dev.platform_data;
struct input_dev *input = platform_get_drvdata(pdev_buttons); for (i = 0; i < pdata->nbuttons; i++) {
struct gpio_keys_button *button = &pdata->buttons[i];
int gpio = button->gpio; if (buttons_irq == gpio_to_irq(gpio)) {
unsigned int type = button->type ?: EV_KEY;
int state = (gpio_get_value(gpio) ? 1 : 0) ^ button->active_low; input_event(input, type, button->code, !!state);
input_sync(input);
}
}
} static int __devinit gpio_keys_probe(struct platform_device *pdev)
{
struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;
struct input_dev *input;
int i, error; input = input_allocate_device();
if (!input)
return -ENOMEM; platform_set_drvdata(pdev, input); input->evbit[0] = BIT(EV_KEY); input->name = pdev->name;
input->phys = "gpio-keys/input0";
input->dev.parent = &pdev->dev; input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100; for (i = 0; i < pdata->nbuttons; i++) {
struct gpio_keys_button *button = &pdata->buttons[i];
int irq = gpio_to_irq(button->gpio);
unsigned int type = button->type ?: EV_KEY; set_irq_type(irq, IRQ_TYPE_EDGE_BOTH);
error = request_irq(irq, gpio_keys_isr, IRQF_SAMPLE_RANDOM,
button->desc ? button->desc : "gpio_keys",
pdev);
if (error) {
printk(KERN_ERR "gpio-keys: unable to claim irq %d; error %d\n",
irq, error);
goto fail;
} input_set_capability(input, type, button->code);
} error = input_register_device(input);
if (error) {
printk(KERN_ERR "Unable to register gpio-keys input device\n");
goto fail;
} /* 注册一个定时器用于按键消抖 */
init_timer(&buttons_timer);
buttons_timer.function = buttons_timer_function;
add_timer(&buttons_timer); return 0; fail:
for (i = i - 1; i >= 0; i--)
free_irq(gpio_to_irq(pdata->buttons[i].gpio), pdev); input_free_device(input); return error;
} static int __devexit gpio_keys_remove(struct platform_device *pdev)
{
struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;
struct input_dev *input = platform_get_drvdata(pdev);
int i; for (i = 0; i < pdata->nbuttons; i++) {
int irq = gpio_to_irq(pdata->buttons[i].gpio);
free_irq(irq, pdev);
} input_unregister_device(input); return 0;
} struct platform_driver gpio_keys_device_driver = {
.probe = gpio_keys_probe,
.remove = __devexit_p(gpio_keys_remove),
.driver = {
.name = "gpio-keys",
}
}; static int __init gpio_keys_init(void)
{
return platform_driver_register(&gpio_keys_device_driver);
} static void __exit gpio_keys_exit(void)
{
platform_driver_unregister(&gpio_keys_device_driver);
} module_init(gpio_keys_init);
module_exit(gpio_keys_exit); MODULE_LICENSE("GPL");
MODULE_AUTHOR("Phil Blundell <pb@handhelds.org>");
MODULE_DESCRIPTION("Keyboard driver for CPU GPIOs");
/* platform_button_device.c */
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/timer.h>
#include <linux/init.h>
#include <linux/serial_core.h>
#include <linux/platform_device.h>
#include <linux/gpio_keys.h>
#include <linux/input.h>
#include <asm/arch/regs-gpio.h> static struct gpio_keys_button jz2440_buttons[] = {
{
.code = KEY_L,
.gpio = S3C2410_GPF0, /* 最终会通过gpio转换为对应的中断号 */
.active_low = 1,
.desc = "l",
.type = EV_KEY, /* 可以不用设置,最后在驱动代码中都强制设置为EV_KEY */
},
{
.code = KEY_S,
.gpio = S3C2410_GPF2,
.active_low = 1,
.desc = "s",
.type = EV_KEY,
},
{
.code = KEY_ENTER,
.gpio = S3C2410_GPG3,
.active_low = 1,
.desc = "enter",
.type = EV_KEY,
},
{
.code = KEY_LEFTSHIFT,
.gpio = S3C2410_GPG11,
.active_low = 1,
.desc = "left-shift",
.type = EV_KEY,
}
}; static struct gpio_keys_platform_data jz2440_buttons_data = {
.buttons = jz2440_buttons,
.nbuttons = ARRAY_SIZE(jz2440_buttons),
}; static struct platform_device button_dev = {
.name = "gpio-keys",
.id = -1,
.dev = {
.platform_data = &jz2440_buttons_data,
},
}; static int button_dev_init(void)
{
platform_device_register(&button_dev);
return 0;
} static void button_dev_exit(void)
{
platform_device_unregister(&button_dev);
} module_init(button_dev_init);
module_exit(button_dev_exit);
MODULE_LICENSE("GPL");

input子系统驱动的更多相关文章

  1. input子系统驱动学习之中的一个

        刚開始学习linux这门课就被分配编写一个设备的input子系统驱动.这对我的确有点困难.只是实际的操作中发现困难远比我想象的要大的多.本以为依照老师课上的步骤就行非常快的完毕这项任务.后来发 ...

  2. 鼠标驱动之-sys节点-input子系统

    首先需要了解sys节点和linux驱动编程的知识,在linux内核<linux/>下有着对应的实现.本例实现创建sys节点,外围程序通过input子系统控制鼠标位置. 第一步编写驱动代码, ...

  3. Android驱动之 Linux Input子系统之TP——A/B(Slot)协议

    将A/B协议这部分单独拿出来说一方面是因为这部分内容是比较容易忽视的,周围大多数用到input子系统的开发人员也不甚理解:另一方面是由于这部分知识一旦扩展到TP(触摸屏Touch Panel)的多点触 ...

  4. 基于input子系统的sensor驱动调试(一)

    要想弄明白世界的本质,就要追根溯源:代码也是一样的道理: 最近调试几个sensor驱动,alps sensor驱动.compass sensor驱动.G-sensor驱动都是一样的架构: 一.基于in ...

  5. 基于input子系统的sensor驱动调试(二)

    继上一篇:http://www.cnblogs.com/linhaostudy/p/8303628.html#_label1_1 一.驱动流程解析: 1.模块加载: static struct of_ ...

  6. input子系统学习笔记六 按键驱动实例分析下【转】

    转自:http://blog.chinaunix.net/uid-20776117-id-3212095.html 本文接着input子系统学习笔记五 按键驱动实例分析上接续分析这个按键驱动实例! i ...

  7. 【Linux高级驱动】input子系统框架

    [1.input子系统框架(drivers\input)] 如何得出某个驱动所遵循的框架?    1) 通过网络搜索    2) 自己想办法跟内核代码!         2.1 定位此驱动是属于哪种类 ...

  8. 【Linux高级驱动】input子系统框架【转】

    转自:http://www.cnblogs.com/lcw/p/3802617.html [1.input子系统框架(drivers\input)] 如何得出某个驱动所遵循的框架?    1) 通过网 ...

  9. 【驱动】input子系统整体流程全面分析(触摸屏驱动为例)【转】

    转自:http://www.cnblogs.com/lcw/p/3294356.html input输入子系统整体流程 input子系统在内核中的实现,包括输入子系统(Input Core),事件处理 ...

随机推荐

  1. 学习SpringBoot整合SSM三大框架源码之SpringBoot

    Spring Boot源码剖析 一.Spring Boot 项目的启动入口流程分析 Spring Boot项目的启动入口main线程上有一个@SpringBootApplication( @Confi ...

  2. 《精通并发与Netty》学习笔记(06 - Apache Thrift使用简介)

    一.概述 Apache Thrift 是 Facebook 实现的一种高效的.支持多种编程语言的远程服务调用的框架.Thrift是由Facebook开发的,并在2008年捐给了Apache基金会,成为 ...

  3. 关于sws安全助手企业政府版的停止维护以及无法购买(官方已公开永久可用免费序列号并将软件开源)

    sws安全助手企业政府版官方公布的永久可用系列号:XGVPP-NMH47-7TTHJ-W3FW7-8HV2C 安装程序官网下载地址:https://swssoftwareshare.gitee.io/ ...

  4. Docker跨主机网络实践

    Docker使用中网络管理是最麻烦的,在项目初始化前期就需要进行合理的规划,如果在比较理想的单主机的网络通信是比较简单的,但如果涉及到跨主机的网络就需要使用docker自带的overlay netwo ...

  5. MongoTemplate 使用 _id 查询

    MongoTemplate mt; DBObject lisi = new BasicDBObject(); lisi.put("_id", new BasicDBObject(& ...

  6. 【ARM-Linux开发】 pkg-config的用法

    pkg-config 是一个提供从源代码中编译软件时查询已安装的库时使用的统一接口的计算机软件.pkg-config原本是设计用于Linux的,但现在在各个版本的BSD.windows.Mac OS ...

  7. 34.TCP非阻塞连接及套接字异常处理丶端口扫描脚本

    TCP非阻塞及套接字异常处理: TCP阻塞套接字异常捕获: 套接字创建失败,8000 socket.error 客户端连接错误: ConnectionRefusedError socket.gaier ...

  8. url-pattern / 与/* 的区别

    其中/和/*的区别:< url-pattern>/</url-pattern> 会匹配到/login这样的路径型url 不会匹配到模式为*.jsp这样的后缀型url,即:*.j ...

  9. SQL SERVER MONTH函数

    定义: MONTH函数返回指定日期的月的部分 语法: MONTH(date) 参数: ①date参数是合法的日期表达式. 返回值: int型数据 例:  声明:本文是本人查阅网上及书籍等各种资料,再加 ...

  10. 记录一次 hadoop yarn resourceManager无故切换的故障

    某日 收到告警 线上集群rm切换 观察resourcemanager 日志报错如下 这行不明显 再看看其他日志报错 在 app attempt_removed 时候发生了空指针错误 break; ca ...