版权声明:本文为卫伟学习总结文章,转载请注明出处!
导读:Linux输入子系统由驱动层、输入子系统核心层、事件处理层三部分组成。一个输入事件,如鼠标移动、键盘按下等通过Driver->Inputcore->Event handler->userspace的顺序到达用户控件的应用程序。
驱动层
将底层的硬件输入转化为统一事件形式,向输入核心层(Input Core)汇报。
输入子系统核心层
- 为驱动层提供输入设备注册与操作接口,如:input_register_device;
- 通知事件处理层对事件进行处理; 在/Proc下产生相应的设备信息。
事件处理层
主要是和用户空间交互(Linux在用户空间将所有的设备都当作文件处理,由于在一般的驱动程序中都有提供fops接口,以及在/dev下生成相应的设备文件nod,这些操作在输入子系统中由事件处理层完成)。
设备描述
input_dev结构是实现设备驱动核心工作:向系统报告按键、触摸屏等输入事件(event,通过input_event结构描述),不再需要关心文件操作接口。驱动报告事件经过inputCore和Eventhandler到达用户空间。
注册输入设备函数:
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_dev是struct input_dev类型)。
struct input_dev中有两个成员为
- evbit事件类型(包括EV_RST,EV_REL,EV_MSC,EV_KEY,EV_ABS,EV_REP等)。
- eybit按键类型(当事件类型为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)
驱动实现——报告结束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);//同步结束
input.c文件分析
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 = input_table[iminor(inode) >> 5];
new_fops = fops_get(handler->fops) // =>&evdev_fops
file->f_op = new_fops;
err = new_fops->open(inode, file);
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
// 放入链表
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函数建立"连接"。
怎么建立连接?
- 分配一个input_handle结构体;
- input_handle.dev = input_dev; // 指向左边的input_dev
input_handle.handler = input_handler; // 指向右边的input_handler - 注册: 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);
怎么读按键?
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)
if (handle->open)
handle->handler->event(handle, type, code, value);
怎么写符合输入子系统框架的驱动程序?
- 向内核申请input_dev结构体;
- 设置input_dev的成员;
- 注册input_dev 驱动设备;
- 初始化定时器和中断;
- 写中断服务函数;
- 写定时器超时函数;
- 在出口函数中 释放中断函数,删除定时器,卸载释放驱动。
具体代码如下(都加了注释):
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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); //支持键盘重复按事件
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");
测试运行
挂载键盘驱动后, 如下图,可以通过 ls -l /dev/event* 命令查看已挂载的设备节点:
测试运行有两种,一种是直接打开/dev/tyy1,第二种是使用exec命令
- cat /dev/tty1 //tty1:LCD终端,就会通过tty_io.c来访问键盘驱动,然后打印在tty1终端上
- exec 0