零、几个问题
(1)为什么有输入子系统:
针对输入设备的多样性和输入事件的多样性,内核通过输入子系统来实现输入设备的驱动、输入事件的提交及对输入事件的读取,并有统一的命名规则。
(2)分层分离结构
input子系统是对不同类型的输入设备进行统一处理的驱动程序。一个输入事件,如按键,按照:
驱动层-->系统核心层-->事件处理层-->用户空间层
的顺序达到用户空间并传递给应用程序的。
input子系统组成
- 驱动层
- 核心层
- 事件处理层
(3)几个重要结构体
input_dev:物理输入设备结构体,包含设备信息。存在input_dev_list链表中
input_handler:事件处理结构体,实现事件处理逻辑。存在input_handler_list链表中
input_handle:建立input_dev与input_handler之间关系。input_handle与input_dev、input_handler之间关系的链表。
内核中,有很多写好的框架、例子等,理解这些框架的流程,把其头文件包含进来。调用系统框架的函数,来实现驱动等。以前自己写,只能是自己的人用,现在用内核现成的写好的,来修改。
(4)input子系统我们需要做什么?
系统核心层input、事件处理层handler已经被系统做好了,内核启动的时候会加载相关驱动。我们需要做的是注册设备层的驱动,注册过程被核心层知道后,会产生设备dev和handler处理者之间的连接,我们要做的就是按照input子系统的框架,写出对应的设备驱动。
(5)架构图
Linux输入子系统(Input Subsystem):
在Linux中,输入子系统是由输入子系统设备驱动层、输入子系统核心层(Input Core)和输入子系统事件处理层(Event Handler)组成。其中设备驱动层提供对硬件各寄存器的读写访问和将底层硬件对用户输入访问的响应转换为标准的输入事件,再通过核心层提交给事件处理层;而核心层对下提供了设备驱动层的编程接口,对上又提供了事件处理层的编程接口;而事件处理层就为我们用户空间的应用程序提供了统一访问设备的接口和驱动层提交来的事件处理。所以这使得我们输入设备的驱动部分不在用关心对设备文件的操作,而是要关心对各硬件寄存器的操作和提交的输入事件。下面用图形来描述一下这三者的关系吧!
一、input子系统分析
1、以前我们写驱动
- register_chrdev
- file_operations
- module_init
- module_exit
缺点:不能用在现成的应用程序,别人的应用程序不可以用。别人可能打开tty,scanf,如何适应这种情况?
2、使用现成的驱动:输入子系统
输入子系统框架,也有以上的几点,只不过是别人写好的,我们只需要修改。
框架分析如下:Input.c (drivers\input)核心层。
(1)看驱动程序从入口函数开始看:subsys_initcall(input_init);--》static int __init input_init(void)
{
int err;
。。。
err = class_register(&input_class);
。。。
}
#define INPUT_MAJOR13 可以看出输入子系统的主设备号都是13
(2)查看file_operations 结构体
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};
static int input_open_file(struct inode *inode, struct file *file)
{
。。。
//从input_table中获得一个input_handler结构体。input_handler结构体里面存有file_operations结构体,后面会介绍。
struct input_handler *handler = input_table[iminor(inode) >> 5];
//定义两个file_operations指针变量
const struct file_operations *old_fops, *new_fops = NULL;
if (!handler || !(new_fops =fops_get(handler->fops)))
return -ENODEV;
old_fops = file->f_op;
file->f_op = new_fops;
err = new_fops->open(inode, file);
。。。
}
这个特殊的open中,根据打开文件的次设备号得到一个input_handler,从input_handler中得到应用程序的file_operations,进行打开。
(3)input_table在哪呢?
在input_register_handler中构造了这个数组项。
int input_register_handler(struct input_handler *handler){。。
input_table[handler->minor >> 5] = handler;
}
(4)input_register_handler被谁调用呢?内核中搜索
(一些通用的设备函数调用,系统写好的处理函数)input_register_handler被input的下层调用。
因此是被下一层调用。
(5)以Evdev.c (drivers\input)为例
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}
调用input_register_handler向上核心层注册(6)evdev_handler是一个input_handler结构体被存到input_table中。
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,
};
input_register_handler(&evdev_handler);
.fops = &evdev_fops evdev_fops里面有各种函数 如open
evdev_fops中.minor = EVDEV_MINOR_BASE,=64
因此input_register_handler中input_table[handler->minor >> 5] = handler; =input_table[2]=handler
evdev 64右移5放到input_table的第二项input_table[2]
evdev_handler是一个input_handler结构体。
假设应用程序要读:
(7)handler 是纯软件,事件处理层(EventHandler)
struct input_handler evdev_handler中有一个id_table,
.id_table = evdev_ids,表示这个hander能支持哪些设备。
(8)还有一层叫设备驱动层。
看一下这个hander
.id_table= evdev_ids, 表示这个hander能支持哪些设备。
能支持会调用.connect = evdev_connect,函数 ,建立一个连接.
(9)搜索下谁会调用 input_register_device,很多按键 鼠标 触摸屏等
注册输入设备:
看一下input_register_device做什么
list_add_tail(&dev->node,&input_dev_list);
会把一个结构体放入链表input_dev_list中
list_for_each_entry(handler,&input_handler_list, node)
input_attach_handler(dev,handler);
对链表里的每个input_handler(注册的时候也会放入链表),都调用input_attach_handler(dev, handler);来根据input_handler的id_table来判断能不能支持这个input_device输入设备。
(10)注册input_register_handler的时候做什么?
放入数组
input_table[handler->minor >> 5] =handler;
放入链表
list_add_tail(&handler->node,&input_handler_list);
对每个input_dev调用input_attach_handler,来根据input_handler的id_table来判断能不能支持这个输入设备。
list_for_each_entry(dev,&input_dev_list, node)
input_attach_handler(dev,handler);
很对称 无论先加载右边Hander还是 左边的设备,都可以调用input_attach_handler。
(11)来看一下input_attach_handler做什么?
根据handler的id_table和输入设备dev看看能不能匹配,可以的话调用handler 的connect函数
id= input_match_device(handler->id_table, dev);
if(!id)
return-ENODEV;
error= handler->connect(handler, dev, id);
if(error && error != -ENODEV)
注册input_dev或input_handler时,会两两比较左边的input_dev和右边的input_handler,
根据input_handler的id_table判断这个input_handler能否支持这个input_dev,
如果能支持,则调用input_handler的connect函数建立"连接",每个handler都有自己不同方式。
(12)怎么建立连接?看一看evdev的connect函数
1. 分配一个input_handle结构体
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
error = input_register_handle(&evdev->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);
int input_register_handle(struct input_handle *handle)
{
struct input_handler *handler = handle->handler;
list_add_tail(&handle->d_node, &handle->dev->h_list);
list_add_tail(&handle->h_node, &handler->h_list);
if (handler->start)
handler->start(handle);
return 0;
}
(13)怎么读按键?
app: read
--------------------------
.......新的结构体的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事件处理函数,唤醒应用程序。设备出发,hander处理。
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, unsignedint type, unsigned int code, int value)
structinput_handle *handle;
list_for_each_entry(handle,&dev->h_list, d_node)
if(handle->open)
handle->handler->event(handle,type, code, value);
二、按键input子系统代码实现
1.参考Gpio_keys.c (drivers\input\keyboard)
(1)以前写驱动程序步骤:
应用程序:open、read、write。。。
驱动:drv_opne、drv_read、drv_write。。。
然后有一个结构体,涵盖了各个方法,注册、入口、出口
(2)input.c别人写的,也包括:
主设备号:13
file_opreations结构体:只有open函数(中转作用,找到某个hander 用里面的file_opreations)
注册函数:regist_chrdev(13,。。)
入口函数:
出口函数:
(3)怎么写符合输入子系统框架的驱动程序?
- 分配一个input_dev结构体
- 设置
- 注册
- 硬件相关的代码,比如在中断服务程序里上报事件
static int buttons_init(void)
static void buttons_exit(void)
module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL");
(5)分配一个input_dev结构体
static struct input_dev *buttons_dev;
/* 1. 分配一个input_dev结构体 */
buttons_dev = input_allocate_device();
(6)设置结构体的属性参数
/* 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);
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)]; //表示能产生哪些按键L、S
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 longswbit[NBITS(SW_MAX)];
(7)注册
/* 3. 注册 */
input_register_device(buttons_dev);
会把buttons_dev放入链表中 与id_table 右边一一比较,如果能够支持,执行connect
Connect中建立一个input_hande结构体,里面dev指向左边,hander指向右边
(8)硬件相关的操作:定时器初始化、申请中断等
/* 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, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]);
}
硬件有数据产生时,调用input_event 上报时间(上报事件核心)
/* 松开 : 最后一个参数: 0-松开, 1-按下 */
input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);
input_sync(buttons_dev);
input_event做了什么呢?
从输入设备的h_list里面找出handle,从handle得到handler,调用它的event函数。event做什么?
记录按键值-->发信号--》唤醒程序。
因为之前read时没数据会休眠:
evdev_read--》evdev_event_to_user--》读到input_event-->
struct input_event {
struct timeval time; 时间
__u16 type; 类别(按键类、相对位移、绝对位移)
__u16 code;那个位置位置
__s32 value;
};//这个结构体可以支持所有的输入事件。
总结:
输入系统分为上下两层
上面是input.c:里面有regist_chrdev,比较简单里面file_opreations里面只有open函数,里面有中转过程,根据打开设备节点的次设备号从input_table找到一个handler,调用里面的open函数,并且把file_opreations指向handler里面的file_opreations,要读写的时候,调用新的file_opreations里面的读写函数。
input_table由谁构建呢?。。
read按键的过程:
read导致handler里面的read被调用,读没有数据休眠。谁来唤醒,是evdev_event来唤醒,是被input_dev层掉用。因为中断中调用input_event,导致event被调用。
2.驱动代码实现
/* 参考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 <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_GPF0, KEY_L},
{IRQ_EINT2, "S3", S3C2410_GPF2, KEY_S},
{IRQ_EINT11, "S4", S3C2410_GPG3, KEY_ENTER},
{IRQ_EINT19, "S5", S3C2410_GPG11, 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 < 4; i++)
{
request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, 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");
3.结果测试
需要杀死QT系统
4、input子系统要点说明
1、几个重要的数据结构:
(1)input_dev、input_handler、input_handle。
input_dev:物理输入设备的基本数据结构,设备相关的基本信息。
input_handler:事件处理结构体,定义处理事件的逻辑。
input_handle:用来创建input_dev与input_handler之间关系的结构体。