零、几个问题
(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 (driversinput)核心层。
(1)看驱动程序从入口函数开始看:subsys_initcall(input_init);--》
static int __init input_init(void)
{
int err;
。。。
err = class_register(&input_class);
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
。。。
}
#define INPUT_MAJOR13 可以看出输入子系统的主设备号都是13
(2)查看file_operations 结构体
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};
只有一个open函数, 这个open肯定做了某些特殊事情。
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 (driversinput)为例
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 ={undefined
.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 (driversinputkeyboard)
(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结构体
设置
注册
硬件相关的代码,比如在中断服务程序里上报事件
(4)参考Gpio_keys.c (driversinputkeyboard),拷贝里面的头文件
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)];
上一篇:09-S3C2440驱动学习(三)嵌入式linux-platform平台总线驱动程序及分离分层构建驱动框架
下一篇:07-S3C2440驱动学习(一)嵌入式linux字符设备驱动-查询+中断+引入poll机制的按键驱动程序
推荐阅读





推荐帖子
- 汇编与C相兼容编程求助
- 本帖最后由wensir于2016-5-3019:29编辑 #include<iom8v.h> unsignedcharbyte[200]={0};//初始化100个ram单元 voidTestASM(void); voidmain(void) { TestASM(); while(1) { if(byte[5]==100)//取第六个单元数据 { PORTB=~PORTB; } } } 以
-
wensir
Microchip MCU
- iTOP3399开发板Android应用开发环境搭建-安装AndroidStudio(一)
- 配套资料在网盘资料的“iTOP-3399开发资料汇总(不含光盘资料)\06_iTOP-3399开发板Androidstudio\安装Androidstudio” Androidstudio是一个Android集成开发工具,基于IntelliJIDEA,AndroidStudio提供了集成的Android开发工具用于开发和调试。本文档将带领大家安装AndroidStudio4.0.1版本。
-
遥寄山川
ARM技术
- 如何用tinymix调试WM8960 音频驱动?
- RT,用过的来说一下,偶要多学习。 如何用tinymix调试WM8960音频驱动?
-
Wince.Android
嵌入式系统
- 485电路集(。sch+程序)
- 各种各样的485电路集485电路集(。sch+程序)
-
tonytong
单片机
- 简单的数值比较,谁能告诉我这是为什么
- 谁能告诉我这是为什么?明明“chipNumb”值是0x02,而且CHIP_NUMB是常量0x02,还返回FALSE? 1.JPG (6.25KB) 下载次数:3 2010-8-1915:32 2.JPG (39.49KB) 下载次数:2 2010-8-1915:32 简单的数值比较,谁能告诉我这是为什么
-
flamingo315
stm32/stm8
- 内核添加串口设备uart3-uart5失败
- A8核心板,内核版本4.1.18,初始串口设备只有ttyO0-ttyO2三个,现在要使用ttyO5串口,在内核添加了uart3,uart4,uart5设备树修改如下:内核启动报错信息:.034815]omap_uart48022000.serial:nowakeirqforuart1 [1.040692]of_get_named_gpiod_flags:can'tparse'rts-gpio'propertyofnode
-
别叫你哥许鲜森
Linux与安卓
实战 培训 开发板 精华推荐
- 利用DS18B20测温,并通过LCD1602 显示 proteus仿真 +DS1302时钟模块
- 51单片机外接ADC0808制作简易电压表
- 利用定时器测量方波频率(P3^4口)
- OK6410A 开发板 (八) 105 linux-5.11 OK6410A 进程通信机制2 共享内存
- OK6410A 开发板 (八) 106 linux-5.11 OK6410A tmpfs 文件系统
- OK6410A 开发板 (八) 107 linux-5.11 OK6410A devtmpfs 文件系统
- OK6410A 开发板 (八) 108 linux-5.11 OK6410A ramfs 文件系统
- OK6410A 开发板 (八) 109 linux-5.11 OK6410A tmpfs 文件系统 CONFIG_SHMEM=n & CONFIG_TMPFS=n
- OK6410A 开发板 (八) 110 linux-5.11 OK6410A 从流程去熟悉文件系统1-挂载
最新视频课程更多
- 和风暖阳,我与春天有个约会!
- 好消息!返现+抽奖,双重好礼助力openmv技术学习!
- 高性能 i.MX RT 处理器助力智能节点无需联网实现机器学习
- 有奖直播|第三代 TI C2000™ 新特性资源更新
- 下载有礼:一起初探5G,赢氮化镓(GaN)充电器、柔性墨水屏等精美礼品
- 端午节芯币兑换礼品专场礼品专场
- 了解ADI电网管理、能源计量方案,答题赢Kindle、《新概念模拟电路》【世健的ADI之路主题游 能源站】
- 直播【英飞凌应用于变频家电和中小功率工业变频控制领域的产品:iMOTION™】
- 报名有礼:【TI C2000在实时控制系统中的新特性】网络直播诚邀您参与!
- 双 11 拯救行动:用开发板治愈单身狗之忧桑