总结INPUT子系统设计(重要)

INPUT子系统

一:什么是Input子系统?

(应用场景,用途)

二:怎么设计Input子系统的程序?

(分配一个输入设备——注册一个输入设备——上报输入事件——注销一个输入设备——释放一个输入设备)

三:Input子系统需要知道哪些?

(涉及的重要数据结构(input_dev,input_handle,input_handler),中断的相关知识)

四:一个自带按键驱动分析

linux-4.5/drivers/input/keyboard下的amikbd.c

五:模拟实现案例

(通过echo sysfs接口值,上传到core层)

六:总结

一:什么是Input子系统?

输入设备(如按键,键盘,触摸屏,鼠标,蜂鸣器等)是典型的字符设备,其一般的工作机制是底层在按键,触摸等动作发生时产生一个中断(或驱动通过
timer定时查询),然后cpu通过SPII2C或者外部存储器总线读取键值,坐标等数据,放一个缓冲区,字符设备驱动管理该缓冲区,而驱动的
read()接口让用户可以读取键值,坐标等数据。


Linux中,输入子系统是由输入子系统设备驱动层、输入子系统核心层(Input
Core)和输入子系统事件处理层(Event
Handler)组成。其中设备驱动层提供对硬件各寄存器的读写访问和将底层硬件对用户输入访问的响应转换为标准的输入事件,再通过核心层提交给事件处理
层;而核心层对下提供了设备驱动层的编程接口,对上又提供了事件处理层的编程接口;而事件处理层就为我们用户空间的应用程序提供了统一访问设备的接口和驱
动层提交来的事件处理。这使得我们输入设备的驱动部分不在用关心对设备文件的操作,而是要关心对各硬件寄存器的操作和提交的输入事件。


Linux中,输入子系统作为一个模块存在,向上,为用户层提供接口函数,向下,为驱动层程序提供统一的接口函数。其构建非常灵活,只需要调用一些简单的
函数,就可以将一个输入设备的功能呈现给应用程序。这样,就能够使输入设备的事件通过输入子系统发送给用户层应用程序,用户层应用程序也可以通过输入子系
统通知驱动程序完成某项功能。

Input子系统结构图

二:怎么设计Input子系统的程序?

2.1:分配一个输入设备

struct
input_dev *input_allocate_device*(void);

这个函数的作用就是初始化input_dev结构体,input_dev是非常关键的数据结构,将在下面重点分析。

代码如下表:


struct input_dev
*input_allocate_device(void)

{

struct input_dev *dev;

dev =
kzalloc(sizeof(struct input_dev), GFP_KERNEL); /*分配一个
input_dev 结构体,并初始化为
0*/

if (dev)

{

dev->dev.type =
&input_dev_type; /*初始化设备的类型*/

dev->dev.class =
&input_class;

device_initialize(&dev->dev);

mutex_init(&dev->mutex);
// 初始话互斥锁

spin_lock_init(&dev->event_lock);
// 初始化自旋锁

INIT_LIST_HEAD(&dev->h_list);
//初始化链表

INIT_LIST_HEAD(&dev->node);

__module_get(THIS_MODULE);

}

return dev;

}

分析如下:

该函数返回一个指向
input_dev
类型的指针,该结构体是一个输入设备结构体,包含了输入设备的一些相关信息,如设备支持的按键码、设备的名称、设备支持的事件等。

2.2:注册一个输入设备

int
input_register_device(struct input_dev *dev);

input_register_device()函数注册输入设备结构体,

input_register_device()函数是输入子系统核心(input
core
)提供的函数。该函数将input_dev结构体注册到输入子系统核心中,input_dev结构体必须由前面讲的
input_allocate_device()函数来分配。input_register_device()函数如果注册失败,必须调input_free_device()函数释放分配的空间。如果该函数注册成功,在卸载函数中应该调用
input_unregister_device()函数来注销输入设备结构体。

代码如下:


[cpp] view plain copy

/**

* input_register_device -
register device with input core

* @dev: device to be
registered

*

* This function registers
device with input core. The device must be

* allocated with
input_allocate_device() and all it‘s capabilities

* set up before registering.

* If function fails the
device must be freed with input_free_device().

* Once device has been
successfully registered it can be unregistered

* with
input_unregister_device(); input_free_device() should not be

* called in this case.

*/

int
input_register_device(struct input_dev *dev)

{

static atomic_t input_no =
ATOMIC_INIT(0);

struct input_handler
*handler;

const char *path;

int error;

/* Every input device
generates EV_SYN/SYN_REPORT events. */

__set_bit(EV_SYN,
dev->evbit);

/*

__set_bit()函数设置
input_dev
所支持的事件类型。事件类型由
input_dev
的evbit
成员来表示,在这里将其
EV_SYN
置位,表示设备支持所有的事件。注意,一个设备可以支持一种或者多种事件类型。常用的事件类型如下:

常用的事件类型如下:
1.
#define EV_SYN 0x00 /*表示设备支持所有的事件*/

2. #define EV_KEY 0x01 /*键盘或者按键,表示一个键码*/

3. #define EV_REL 0x02 /*鼠标设备,表示一个相对的光标位置结果*/

4. #define EV_ABS 0x03 /*手写板产生的值,其是一个绝对整数值*/

5. #define EV_MSC 0x04/*其他类型*/

6. #define EV_LED 0x11 /*LED 灯设备*/

7. #define EV_SND 0x12 /*蜂鸣器,输入声音*/

8. #define EV_REP 0x14 /*允许重复按键类型*/

9. #define EV_PWR 0x16 /*电源管理事件*/

*/

/* KEY_RESERVED is not
supposed to be transmitted to userspace. */

__clear_bit(KEY_RESERVED,
dev->keybit);

/* Make sure that bitmasks
not mentioned in dev->evbit are clean. */

input_cleanse_bitmasks(dev);

/*

* If delay and period are
pre-set by the driver, then autorepeating

* is handled by the
driver itself and we don‘t do it in input.c.

*/

init_timer(&dev->timer);

//初始化一个
timer
定时器,这个定时器是为处理重复击键而定义的。

if (!dev->rep[REP_DELAY]
&& !dev->rep[REP_PERIOD]) {

dev->timer.data =
(long) dev;

dev->timer.function
= input_repeat_key;

dev->rep[REP_DELAY]
= 250;

dev->rep[REP_PERIOD]
= 33;

}

//如果dev->rep[REP_DELAY]和dev->rep[REP_PERIOD]没有设值,则将其赋默认值。这主要是处理重复按键的.

if (!dev->getkeycode)

dev->getkeycode =
input_default_getkeycode;

if (!dev->setkeycode)

dev->setkeycode =
input_default_setkeycode;

dev_set_name(&dev->dev,
"input%ld",

(unsigned long)
atomic_inc_return(&input_no) - 1);

/*

//检查
getkeycode()函数和
setkeycode()函数是否被定义,如果没定义,则使用默认的处理函数,这两个函数为

//input_default_getkeycode()和
input_default_setkeycode()。input_default_getkeycode()函数用来得到指定位置的键

//值。input_default_setkeycode()函数用来设置键值。

*/

error =
device_add(&dev->dev);

if (error)

return error;

//使用
device_add()函数将
input_dev
包含的
device
结构注册到
Linux
设备模型中,并可以在
sysfs

//文件系统中表现出来。

path =
kobject_get_path(&dev->dev.kobj, GFP_KERNEL);

printk(KERN_INFO "input:
%s as %s\n",

dev->name ?
dev->name : "Unspecified device", path ? path :
"N/A");

kfree(path);

error =
mutex_lock_interruptible(&input_mutex);

if (error) {

device_del(&dev->dev);

return error;

}

list_add_tail(&dev->node,
&input_dev_list);

//调用
list_add_tail()函数将
input_dev
加入
input_dev_list
链表中,input_dev_list

//表中包含了系统中所有的
input_dev
设备

list_for_each_entry(handler,
&input_handler_list, node)

input_attach_handler(dev,
handler);

//将input
device 挂到input_dev_list链表上.然后,对每一个挂在input_handler_list的handler调用

//input_attach_handler().在这里的情况有好比设备模型中的device和driver的匹配。所有的input
device都挂在

//input_dev_list链上。所有的handler都挂在input_handler_list上。

input_wakeup_procfs_readers();

mutex_unlock(&input_mutex);

return 0;

}

分析如下:

0306行,定义了一些函数中将要用到的局部变量。

第07行,调用__set_bit()函数设置input_dev所支持的事件类型。事件类型由input_dev的evbit成员来表示,在这里将其EV_SYN置位,表示设备支持所有的事件。注意,一个设备可以支持一种或者多种事件类型。常用的事件类型如下:

#define EV_SYN          0x00    /*表示设备支持所有的事件*/ #define EV_KEY          0x01    /*键盘或者按键,表示一个键码*/ #define EV_REL          0x02    /*鼠标设备,表示一个相对的光标位置结果*/ #define EV_ABS          0x03    /*手写板产生的值,其是一个绝对整数值*/ #define EV_MSC          0x04    /*其他类型*/ #define EV_LED          0x11    /*LED灯设备*/ #define EV_SND          0x12    /*蜂鸣器,输入声音*/ #define EV_REP          0x14    /*允许重复按键类型*/ #define EV_PWR          0x16    /*电源管理事件*/

dev_set_name设置input_dev中的device的名字,名字以input0、input1、input2、input3、input4等的形式出现在sysfs文件系统中。

使用device_add()函数将input_dev包含的device结构注册到Linux设备模型中,并可以在sysfs文件系统中表现出来。

打印设备的路径,输出调试信息。

调用list_add_tail()函数将input_dev加入input_dev_list链表中,input_dev_list链表中包含了系统中所有的input_dev设备。

设计到的两个重要函数分析如下表:


/*

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)

{

const struct input_device_id *id;/*输入设备的指针,该结构体表示设备的标识,标识中存储了设备的信息*/

int error;

if (handler->blacklist && input_match_device(handler->blacklist,

dev))/*首先判断 handle 的 blacklist 是否被赋值,如果被赋值,则匹配 blacklist 中的数据跟 dev->id 的数据是否匹配。blacklist 是一个 input_device_id*的类型,其指向 input_device_id的一个表,这个表中存放了驱动程序应该忽略的设备。即使在 id_table 中找到支持的项,也应该忽略这种设备。*/

return -ENODEV;

id = input_match_device(handler, dev);

if (!id)

return -ENODEV;

error = handler->connect(handler, dev, id);/*连接设备和处理函数*/

if (error && error != -ENODEV)

printk(KERN_ERR

"input: failed to attach handler %s to device %s, "

"error: %d\n",

handler->name, kobject_name(&dev->dev.kobj), error);

return error;

}

*/

/*

static const struct input_device_id *input_match_device(struct input_handler *handler,struct input_dev *dev)

{

const struct input_device_id *id;

int i;//声明一个局部变量 i,用于循环。

/*是一个 for 循环,用来匹配 id 和 dev->id 中的信息,只要有一项相同则返回。*/

for (id = handler->id_table; id->flags || id->driver_info; id++) {

/*用 来 匹 配 总 线 类 型 。 id->flags 中 定 义 了 要 匹 配 的 项 , 其 中INPUT_DEVICE_ID_MATCH_BUS 如果没有设置,则比较 input device 和 input handler 的总线类型。*/

if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)

if (id->bustype != dev->id.bustype)

continue;

if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)//匹配设备厂商的信息。

if (id->vendor != dev->id.vendor)

continue;

if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)//分别匹配设备号的信息。

if (id->product != dev->id.product)

continue;

if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)

if (id->version != dev->id.version)

continue;

/*使用 MATCH_BIT 匹配项。如果 id->flags 定义的类型匹配成功,或者 id->flags没有定义,才会进入到 MATCH_BIT 的匹配项。*/

MATCH_BIT(evbit, EV_MAX);

MATCH_BIT(keybit, KEY_MAX);

MATCH_BIT(relbit, REL_MAX);

MATCH_BIT(absbit, ABS_MAX);

MATCH_BIT(mscbit, MSC_MAX);

MATCH_BIT(ledbit, LED_MAX);

MATCH_BIT(sndbit, SND_MAX);

MATCH_BIT(ffbit, FF_MAX);

MATCH_BIT(swbit, SW_MAX);

if (!handler->match || handler->match(handler, dev))

return id;

}

return NULL;

}

*/

2.3:驱动实现-事件支持

Set_bit(EV_KEY,button_dev.evbit)

//Set_bit告诉inout子系统它支持哪些事件

//Struct
input_dev中有两个成员,一个是evbit;一个是keybit;分别用来表示设备所支持的事件类型和按键类型。

事件类型

Linux中输入设备的事件类型有(这里只列出了常用的一些,更多请看linux/input.h中):

EV_SYN
0x00 同步事件

EV_KEY
0x01 按键事件

EV_REL
0x02 相对坐标

EV_ABS
0x03 绝对坐标

EV_MSC
0x04 其它

EV_LED
0x11 LED

EV_SND
0x12 声音

EV_REP
0x14 Repeat

EV_FF
0x15 Force feedback事件

按键类型

当事件类型为EV_KEY时,还需指明按键类型:

BTN_LEFT
鼠标左键

BTN_RIGHT
鼠标右键

BTN_MIDDLE
鼠标中键

BTN_0
数字0键

BTN_1
数字1键

上述set_bit函数实则完成了把EV_KEY赋值到button_dev.evbit

驱动实现-报告事件

//报告指定type,code的输入事件

Void
input_event(struct input_dev
*dev,unsigned int type,unsigned int code,int value);

/*报告键值,code
:
事件的代码,如果事件是ev_key,该代码则为设备的键盘代码。例如鼠标按键代码为0x110~0x116,其中0x110(BTN_LEFT),0x111(BTN_RIGHT),0x112(BTN_MIDDLE)。其它带按摩含义参考include/linux/input.h文件*/

Void
input_report_key(struct
input_dev *dev,unsigned int code,int value);

value
: 事件的值,如果事件的类型是EV_KEY,当按键按下时值为1,松开时为0。

//报告相对坐标

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 此次报告已经结束,能够根据上报的信息往后面处理了*/

Void
input_sync(struct input_dev
*dev);

在触摸屏驱动设计中,一次坐标及按下状态的整个报告过程如下:

Input_report_abs(input_dev,ABS_X,x);//X坐标

Input_report_abs(input_dev,ABS_Y,y);//Y坐标

Input_report_abs(input_dev,ABS_PRESSURE,pres);//压力

input_sync(struct
input_dev *dev);//同步

2.4:释放与注销设备、

Void
input_free_device(struct input_dev *dev);

Void
input_unregister_device(struct input_dev *);

三:Input子系统需要知道哪些?

3.1input_dev代表一个输入设备

结构剖析:


/*

input_dev
结构体剖析

成员说明:

char
*name;

//设备名字,如键盘名字。

char
*phys;

//设备文件节点名,如input/kbd0。

char
*uniq;

//全球唯一的ID号。

struct
input_id id;

//后文作详细介绍。用于匹配事件处理层handler

unsigned
long evbit[NBITS(EV_MAX);]

//该设备驱动所能支持的事件。

//EV_SYN
同步事件

//EV_KEY
键盘事件

//EV_REL
相对坐标事件,用于鼠标

//EV_ABS
绝对坐标事件,用于摇杆

//EV_MSC
其他事件

//EV_LED
LED灯事件

//EV_SND
声音事件

//EV_REP
重复按键事件

//EV_FF
受力事件

//EV_PWR
电源事件

//EV_FF_STATUS
受力状态事件

unsigned
long evbit[BITS_TO_LONGS(EV_CNT)];//用于记录支持的事件类型的位图

unsigned
long keybit[NBITS(KEY_MAX)];

//键值存放表(位图)

unsigned
long relbit[NBITS(REL_MAX)];

//用于存放相对坐标值等

unsigned
long absbit[NBITS(ABS_MAX)];

//用于存放绝对坐标值等

unsigned
long mscbit[NBITS(MSC_MAX)];

//存放其他事件类型

unsigned
long ledbit[NBITS(LED_MAX)];

//存放表示各种状态的LED值

unsigned
long sndbit[NBITS(SND_MAX)];

//存放各种事件的声音

unsigned
long ffbit[NBITS(FF_MAX)];

//存放受力设备的属性

int
ff_effects_max;

//显然与受力效果有关,具体作用还不大清楚。

unsigned
int keycodemax;//支持的按键值的个数

unsigned
int keycodesize;//每个键值的字节数

void *
keycode;//存储按键值的数组首地址

unsigned
int repeat_key;

//存放重复按键时的键值(最近一次的按键值,可用于连击)

struct
timer_list timer;//(自动连击计时器)

//定时器

struct
pm_dev *pm_dev;

//考虑到有些设备可能有电源管理

struct
pt_regs *regs;

//不清楚

int
state;

//显然是表示一个状态,但不清楚具体是谁的状态

int sync;

//最后一次同步后没有新的事件置1

int
abs[ABS_MAX + 1];//当前各个坐标的值

//显然是与绝对坐标有关的,但具体的作用不清楚。

int
rep[REP_MAX + 1];//自动连击的参数

//存放重复按键时的延时,系统依靠这个延时时间来判断重复按键

//rep[0]表示开始要重复按键时的延时时间,即第1个键与第2个键(开始重复按键)之间的延时

//rep[1]此后重复按键之前的延时时间,直到按键抬起

//通俗解释就是,假如我按了一个“a”,并且一直按着,那么在显示出来的第一个a与第二个a之间的时间延时为rep[0],而此后的相邻两个a之间的延时为rep[1]

unsigned
long key[NBITS(KEY_MAX)];//反映当前按键状态的位图

unsigned
long led[NBITS(LED_MAX)];//反映当前led状态的位图

unsigned
long snd[NBITS(SND_MAX)];//反映当前beep状态的位图

int
absmax[ABS_MAX + 1];//记录各个坐标的最大值

int
absmin[ABS_MAX + 1];//记录各个坐标的最小值

int
absfuzz[ABS_MAX + 1];//记录各个坐标的分辨率

int
absflat[ABS_MAX + 1];//记录各个坐标的基准值

int
(*open)(struct input_dev *dev);//打开函数

void
(*close)(struct input_dev *dev);//关闭函数

int
(*accept)(struct input_dev *dev, struct file *file);

int
(*flush)(struct input_dev *dev, struct file *file);//断开连接时冲洗数据

int
(*event)(struct input_dev *dev, unsigned int type, unsigned int
code, int value);

//回调函数

int
(*upload_effect)(struct input_dev *dev, struct ff_effect
*effect);

int
(*erase_effect)(struct input_dev *dev, int effect_id);

//底层与硬件相关的一组操作,若有具体定义,则会在input
core层被调用,具体看input.c。

struct
input_handle *grab;

//该结构会在后文做具体介绍,这个指针用于占用输入设备用,如键盘

struct
list_head h_list;

struct
list_head node;

//h_list链表用于与input_handler相联系

//node链表:设备向输入子系统(input
subsystem)注册后,会将该链表添加到系统维护的一个链表中去,从而系统可以管理这个设备

*/

3.2input_handler事件驱动的主体,每一种处理方式对应一个handler结构体

结构剖析:


事件处理层(event
handler)的核心结构。

头文件:include/linux/input.h

成员说明:

void
*private;

//私有数据指针

void
(*event)(struct input_handle *handle, unsigned int type, unsigned
int code, int value)

//事件处理函数指针。设备驱动报告的事件最终由这个函数来处理。

struct
input_handle * (*connect)(struct input_handler *handler,struct
input_dev *dev,struct input_device_id
*id);//连接handler和input_dev的函数指针

void
(*disconnect)(struct input_handle *handle);//断开链接的函数指针

//event
handler层与input
core层之间一组接口

struct
file_operation *fops;//文件操作结构体

//给应用程序所调用的一组接口

int
minor;

//这个handler可以使用的32个次设备号的最小值

char
*name;

//input_handler的名字

struct
input_device_id *id_table;//可以处理的input_device_ids列表

struct
input_device_id *blacklist;//需要被忽略的列表

//该结构体是对input_id结构体的扩展,从表面上看blacklist为被系统列为黑名单的输入设备列表

struct
list_head h_list;

//用来存放全局handler链表的节点

struct
list_head node;

//h_list链表用于与input_dev相联系

//node链表:事件处理程序向输入子系统(input
subsystem)注册后,会将该链表添加到系统维护的一个链表中去,从而系统可以管理这类事件

3.3
input_handle用来连接input_devinput_handler

结构剖析:


说明:是一个用于关联驱动层input_dev和事件处理层input_handler的中间结构。

头文件:include/linux/input.h

成员说明:

void
*private;

//私有数据指针

int open;

//记录本设备被打开的次数

char
*name;

//input_handle的名字

struct
input_dev *dev;//指着附着的input_dev

struct
input_handler *handler;

//这两个就不解释了,前面都有具体介绍

struct
list_head d_node;

struct
list_head h_node;

//d_node链表用于input_dev链,h_node链表用于input_handler链,有了input_handle,就把相关dev和handler联系起来,相互能容易找到。

3.4input_event事件传送的载体,输入子系统的事件通过这个结构体包装传送给用户空间

结构剖析:


(这个结构体是事件传送的载体,输入子系统的事件都是包装成struct
input_event传给用户空间)

说明:应用程序可通过此结构体获取输入设备事件信息,也就是说,比如在写键盘测试程序时,我们可用这个结构体,再结合ioctl系统调用来获取来自键盘的信息。

头文件:include/linux/input.h

成员说明:

struct
timeval time;

//time是一个时间戳(timestamp),储存着事件发生时的时间记录

__u16
type;

//事件的类型,如EV_KEY,则表示输入事件为键盘事件

__u16
code;

//事件的代码,如果为KEY_1,则表示键盘输入为“1”

__s32
value;//事件值,如坐标的偏移

//用于键盘时,value为0表示按键松开,value为1表示按键按下,value为2表示重复按键

#define
EV_SYN 0x00

#define
EV_KEY 0x01

#define
EV_REL 0x02

#define
EV_ABS 0x03

#define
EV_MSC 0x04

#define
EV_SW 0x05

#define
EV_LED 0x11

#define
EV_SND 0x12

#define
EV_REP 0x14

#define
EV_FF 0x15

#define
EV_PWR 0x16

#define
EV_FF_STATUS 0x17

#define
EV_MAX 0x1f

*/

3.5input_id

结构剖析:


说明:输入设备的一些属性。

头文件:include/linux/input.h

成员说明:

__u16
bustype;

//总线类型,如BUS_PCI、BUS_USB等

__u16
vendor;

//设备生产商

__u16
product;

//产品名字

__u16
version;

//版本号

3.6:中断(事件产生事件通知处理器的方式

详细了解参照LINUX设备驱动程序第十章节介绍。

四:一个自带按键驱动分析


/*

*
Copyright (c) 2000-2001 Vojtech Pavlik

*

* Based
on the work of:

* Hamish
Macdonald

*/

/*

* Amiga
keyboard driver for Linux/m68k

*/

/*

* This
program is free software; you can redistribute it and/or modify

* it
under the terms of the GNU General Public License as published by

* the
Free Software Foundation; either version 2 of the License, or

* (at
your option) any later version.

*

* This
program is distributed in the hope that it will be useful,

* but
WITHOUT ANY WARRANTY; without even the implied warranty of

*
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

* GNU
General Public License for more details.

*

* You
should have received a copy of the GNU General Public License

* along
with this program; if not, write to the Free Software

*
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA

*

* Should
you need to contact me, the author, you can do so either by

* e-mail
- mail your message to <[email protected]>, or by paper mail:

* Vojtech
Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic

*/

#include
<linux/module.h>

#include
<linux/init.h>

#include
<linux/input.h>

#include
<linux/delay.h>

#include
<linux/interrupt.h>

#include
<linux/keyboard.h>

#include
<linux/platform_device.h>

#include
<asm/amigaints.h>

#include
<asm/amigahw.h>

#include
<asm/irq.h>

MODULE_AUTHOR("Vojtech
Pavlik <[email protected]>");

MODULE_DESCRIPTION("Amiga
keyboard driver");

MODULE_LICENSE("GPL");

#ifdef
CONFIG_HW_CONSOLE

static
unsigned char amikbd_keycode[0x78] __initdata = {

[0] =
KEY_GRAVE,

[1] =
KEY_1,

[2] =
KEY_2,

[3] =
KEY_3,

[4] =
KEY_4,

[5] =
KEY_5,

[6] =
KEY_6,

[7] =
KEY_7,

[8] =
KEY_8,

[9] =
KEY_9,

[10] =
KEY_0,

[11] =
KEY_MINUS,

[12] =
KEY_EQUAL,

[13] =
KEY_BACKSLASH,

[15] =
KEY_KP0,

[16] =
KEY_Q,

[17] =
KEY_W,

[18] =
KEY_E,

[19] =
KEY_R,

[20] =
KEY_T,

[21] =
KEY_Y,

[22] =
KEY_U,

[23] =
KEY_I,

[24] =
KEY_O,

[25] =
KEY_P,

[26] =
KEY_LEFTBRACE,

[27] =
KEY_RIGHTBRACE,

[29] =
KEY_KP1,

[30] =
KEY_KP2,

[31] =
KEY_KP3,

[32] =
KEY_A,

[33] =
KEY_S,

[34] =
KEY_D,

[35] =
KEY_F,

[36] =
KEY_G,

[37] =
KEY_H,

[38] =
KEY_J,

[39] =
KEY_K,

[40] =
KEY_L,

[41] =
KEY_SEMICOLON,

[42] =
KEY_APOSTROPHE,

[43] =
KEY_BACKSLASH,

[45] =
KEY_KP4,

[46] =
KEY_KP5,

[47] =
KEY_KP6,

[48] =
KEY_102ND,

[49] =
KEY_Z,

[50] =
KEY_X,

[51] =
KEY_C,

[52] =
KEY_V,

[53] =
KEY_B,

[54] =
KEY_N,

[55] =
KEY_M,

[56] =
KEY_COMMA,

[57] =
KEY_DOT,

[58] =
KEY_SLASH,

[60] =
KEY_KPDOT,

[61] =
KEY_KP7,

[62] =
KEY_KP8,

[63] =
KEY_KP9,

[64] =
KEY_SPACE,

[65] =
KEY_BACKSPACE,

[66] =
KEY_TAB,

[67] =
KEY_KPENTER,

[68] =
KEY_ENTER,

[69] =
KEY_ESC,

[70] =
KEY_DELETE,

[74] =
KEY_KPMINUS,

[76] =
KEY_UP,

[77] =
KEY_DOWN,

[78] =
KEY_RIGHT,

[79] =
KEY_LEFT,

[80] =
KEY_F1,

[81] =
KEY_F2,

[82] =
KEY_F3,

[83] =
KEY_F4,

[84] =
KEY_F5,

[85] =
KEY_F6,

[86] =
KEY_F7,

[87] =
KEY_F8,

[88] =
KEY_F9,

[89] =
KEY_F10,

[90] =
KEY_KPLEFTPAREN,

[91] =
KEY_KPRIGHTPAREN,

[92] =
KEY_KPSLASH,

[93] =
KEY_KPASTERISK,

[94] =
KEY_KPPLUS,

[95] =
KEY_HELP,

[96] =
KEY_LEFTSHIFT,

[97] =
KEY_RIGHTSHIFT,

[98] =
KEY_CAPSLOCK,

[99] =
KEY_LEFTCTRL,

[100] =
KEY_LEFTALT,

[101] =
KEY_RIGHTALT,

[102] =
KEY_LEFTMETA,

[103] =
KEY_RIGHTMETA

};

static
void __init amikbd_init_console_keymaps(void)

{

/* We can
spare 512 bytes on stack for temp_map in init path. */

unsigned
short temp_map[NR_KEYS];

int i,
j;

for (i =
0; i < MAX_NR_KEYMAPS; i++) {

if
(!key_maps[i])

continue;

memset(temp_map,
0, sizeof(temp_map));

for (j =
0; j < 0x78; j++) {

if
(!amikbd_keycode[j])

continue;

temp_map[j]
= key_maps[i][amikbd_keycode[j]];

}

for (j =
0; j < NR_KEYS; j++) {

if
(!temp_map[j])

temp_map[j]
= 0xf200;

}

memcpy(key_maps[i],
temp_map, sizeof(temp_map));

}

}

#else /*
!CONFIG_HW_CONSOLE */

static
inline void amikbd_init_console_keymaps(void) {}

#endif /*
!CONFIG_HW_CONSOLE */

static
const char *amikbd_messages[8] = {

[0] =
KERN_ALERT "amikbd: Ctrl-Amiga-Amiga reset warning!!\n",

[1] =
KERN_WARNING "amikbd: keyboard lost sync\n",

[2] =
KERN_WARNING "amikbd: keyboard buffer overflow\n",

[3] =
KERN_WARNING "amikbd: keyboard controller failure\n",

[4] =
KERN_ERR "amikbd: keyboard selftest failure\n",

[5] =
KERN_INFO "amikbd: initiate power-up key stream\n",

[6] =
KERN_INFO "amikbd: terminate power-up key stream\n",

[7] =
KERN_WARNING "amikbd: keyboard interrupt\n"

};

static
irqreturn_t amikbd_interrupt(int irq, void *data)

{

struct
input_dev *dev = data;

unsigned
char scancode, down;

scancode
= ~ciaa.sdr; /* get and invert scancode (keyboard is active low)
*/

ciaa.cra
|= 0x40; /* switch SP pin to output for handshake */

udelay(85); /*
wait until 85 us have expired */

ciaa.cra
&= ~0x40; /* switch CIA serial port to input mode */

down =
!(scancode & 1); /* lowest bit is release bit */

scancode
>>= 1;

if
(scancode < 0x78) { /* scancodes < 0x78 are keys */

if
(scancode == 98) { /* CapsLock is a toggle switch key on Amiga */

input_report_key(dev,
scancode, 1);

input_report_key(dev,
scancode, 0);

} else
{

input_report_key(dev,
scancode, down);

}

input_sync(dev);

}
else /* scancodes >= 0x78 are error codes */

printk(amikbd_messages[scancode
- 0x78]);

return
IRQ_HANDLED;

}

static int
__init amikbd_probe(struct platform_device *pdev)

{

struct
input_dev *dev;

int i,
err;

dev =
input_allocate_device();

if (!dev)
{

dev_err(&pdev->dev,
"Not enough memory for input device\n");

return
-ENOMEM;

}

dev->name
= pdev->name;

dev->phys
= "amikbd/input0";

dev->id.bustype
= BUS_AMIGA;

dev->id.vendor
= 0x0001;

dev->id.product
= 0x0001;

dev->id.version
= 0x0100;

dev->dev.parent
= &pdev->dev;

dev->evbit[0]
= BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);

for (i =
0; i < 0x78; i++)

set_bit(i,
dev->keybit);

amikbd_init_console_keymaps();

ciaa.cra
&= ~0x41; /* serial data in, turn off TA */

err =
request_irq(IRQ_AMIGA_CIAA_SP, amikbd_interrupt, 0, "amikbd",

dev);

if (err)

goto
fail2;

err =
input_register_device(dev);

if (err)

goto
fail3;

platform_set_drvdata(pdev,
dev);

return
0;

fail3: free_irq(IRQ_AMIGA_CIAA_SP,
dev);

fail2: input_free_device(dev);

return
err;

}

static int
__exit amikbd_remove(struct platform_device *pdev)

{

struct
input_dev *dev = platform_get_drvdata(pdev);

free_irq(IRQ_AMIGA_CIAA_SP,
dev);

input_unregister_device(dev);

return
0;

}

static
struct platform_driver amikbd_driver = {

.remove =
__exit_p(amikbd_remove),

.driver
= {

.name =
"amiga-keyboard",

},

};

module_platform_driver_probe(amikbd_driver,
amikbd_probe);

MODULE_ALIAS("platform:amiga-keyboard");

五:模拟实现案例

代码实例:


#include
<linux/module.h>

#include
<linux/platform_device.h>

#include
<linux/slab.h>

#include
<linux/input.h>

#include
<linux/kthread.h>

#include
<linux/semaphore.h>

#include<linux/delay.h>

struct
v_dev{

struct
platform_device *p_dev;

struct
input_dev *input;

int
ch;

struct
task_struct *run_thread;

struct
semaphore sem;

};

struct
v_dev *vinput_dev = NULL;

static
ssize_t push_ch(struct device *dev, struct device_attribute *attr,

const
char *buf, size_t count)

{

int
ch;

sscanf(buf,"%d",&ch);

vinput_dev->ch
= ch;

//post
信号量

up(&vinput_dev->sem);

return
count;

}

static
ssize_t show_ch(struct device *dev, struct device_attribute *attr,

char
*buf){

return
sprintf(buf,"(%d)\n",vinput_dev->ch);

}

DEVICE_ATTR(push,0666,show_ch,push_ch);

static
int vinput_thread(void *data)

{

int
ch;

struct
v_dev *vinput_dev = (struct v_dev*)data;

struct
semaphore *sema = &(vinput_dev->sem);

printk(KERN_INFO
"vinput thread running\n");

while(1){

//等待信号量

while((down_interruptible(sema))
== -EINTR){} ;

ch
= vinput_dev->ch;

mdelay(3000);

if(ch<0x78)

{

input_report_key(vinput_dev->input,ch,
1);

input_report_key(vinput_dev->input,ch,
0);

}

if(ch==100)

input_report_key(vinput_dev->input,ch,
1);

//
if(ch == ‘m‘){

//
int i=0;

//for(i;i<0x78;i++)

//input_report_key(vinput_dev->input,i,
0); //KEY_M

//
printk("vinput get a ‘m‘,and report a SW_DOCK = 0\n");

//
}

//
else

//
if(ch == ‘n‘){

//i=0;

//for(i;i<0x78;i++)

//
input_report_key(vinput_dev->input,i,1);

//
printk("vinput get a ‘n‘,and report a SW_DOCK = 1\n");

//
}

input_sync(vinput_dev->input);

printk("vinput
thread report\n");

}

return
IRQ_HANDLED;

}

static
int vinput_probe(struct platform_device *pdev)

{

int
ret = -1;

printk("%s
debug \n",__func__);

if(vinput_dev->p_dev
== pdev){

printk("platform
device is same\n");

}

vinput_dev->input
= input_allocate_device();

if(!(vinput_dev->input)){

printk("%s
request input deivce error\n",__func__);

goto
alloc_input;

}

vinput_dev->input->name
= "vinput";

vinput_dev->input->evbit[0]
=BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);// BIT_MASK(EV_MSC);

//
vinput_dev->input->keybit[BIT_WORD(KEY_M)] =
BIT_MASK(KEY_M); //KEY_M

int
i =0;

for
(i ; i < 0x78; i++)

set_bit(i,vinput_dev->input->keybit);

ret
= input_register_device(vinput_dev->input);

if(ret
< 0){

printk("%s
register input device error\n",__func__);

goto
input_register;

}

device_create_file(&pdev->dev,&dev_attr_push);

//初始化信号量,在线程中要用到

sema_init(&(vinput_dev->sem),0);

vinput_dev->run_thread
= kthread_run(vinput_thread,vinput_dev,"vinput_thread");

return
0;

input_register:

input_free_device(vinput_dev->input);

alloc_input:

kfree(vinput_dev);

return
ret;

}

static
struct platform_driver vinput_driver = {

.probe
= vinput_probe,

.driver
= {

.owner
= THIS_MODULE,

.name
= "v_input",

},

};

static
int __init vinput_init(void)

{

int
ret =-1;

printk("%s\n",
__func__);

vinput_dev
= kzalloc(sizeof(struct v_dev),GFP_KERNEL);

if(vinput_dev
== NULL){

printk("%s
alloc memory error\n",__func__);

return
-ENOMEM;

}

vinput_dev->p_dev=
platform_device_register_simple("v_input",-1,NULL,0);

if(!(vinput_dev->p_dev)){

printk("%s
register platform device error\n",__func__);

return
ret;

}

ret
= platform_driver_register(&vinput_driver);

if(ret
< 0){

printk("%s
register driver error\n",__func__);

return
ret;

}

return
0;

}

static
void __exit vinput_exit(void)

{

printk("%s\n",
__func__);

if(vinput_dev->input
!= NULL){

input_unregister_device(vinput_dev->input);

}

printk("%s
debug__1\n",__func__);

if(vinput_dev
!= NULL){

platform_device_unregister(vinput_dev->p_dev);

}

printk("%s
debug__2\n",__func__);

platform_driver_unregister(&vinput_driver);

printk("%s
debug__3\n",__func__);

kfree(vinput_dev);

printk("%s
debug__4\n",__func__);

}

module_init(vinput_init);

module_exit(vinput_exit);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("arch");

时间: 2024-08-03 21:13:43

总结INPUT子系统设计(重要)的相关文章

linux kernel input 子系统分析

Linux 内核为了处理各种不同类型的的输入设备 , 比如说鼠标 , 键盘 , 操纵杆 , 触摸屏 , 设计并实现了一个对上层应用统一的试图的抽象层 , 即是Linux 输入子系统 . 输入子系统的层次结构体如下 从底层到上层 , input 子系统由 设备驱动层 , 核心层 , 以及事件处理层3个部分组成 当一个鼠标移动, 一个按键按下或弹起 , 它都需要从底层设备驱动-->核心层-->事件处理层 -->用户空间 , 层层上报 , 一直到运用程序. 应用这个input  子系统有如下优

Linux input子系统编程、分析与模板

输入设备都有共性:中断驱动+字符IO,基于分层的思想,Linux内核将这些设备的公有的部分提取出来,基于cdev提供接口,设计了输入子系统,所有使用输入子系统构建的设备都使用主设备号13,同时输入子系统也支持自动创建设备文件,这些文件采用阻塞的IO读写方式,被创建在"/dev/input/"下.如下图所示.内核中的输入子系统自底向上分为设备驱动层,输入核心层,事件处理层.由于每种输入的设备上报的事件都各有不同,所以为了应用层能够很好识别上报的事件,内核中也为应用层封装了标准的接口来描述

全网络对Linux input子系统最清晰、详尽的分析

Linux input分析之二:解构input_handler.input_core.input_device 输入输出是用户和产品交互的手段,因此输入驱动开发在Linux驱动开发中很常见.同时,input子系统的分层架构思想在Linux驱动设计中极具代表性和先进性,因此对Linux input子系统进行深入分析很有意义. 本文继续在<Linuxinput子系统分析之一:软件分层>的基础上继续深入研究Linux输入子系统的分层架构思想以及其实现.软件分层探讨的是输入消息从底层硬件到内核.应用层

input子系统——架构、驱动、应用程序

一.input子系统架构 input子系统由驱动层drivers,输入子系统核心层input core,事件处理层event handler组成. 一个输入事件,通过输入设备发给系统如鼠标移动,键盘按键按下等通过device driver->input core(handler->event函数)->event handler->user space的顺序到达用户空间传给应用程序. 一个输出事件,通过系统发给输入设备,通过user space->event handler-&

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

utm_source=tuicool&utm_medium=referral">点击打开链接 将A/B协议这部分单独拿出来说一方面是由于这部分内容是比較easy忽视的.周围大多数用到input子系统的开发者也不甚理解.还有一方面是由于这部分知识一旦扩展到TP(触摸屏Touch Panel)的多点触摸就要与Middleware/Framework一起结合起来看才干全然掌握,复杂性所在. 这里的Middleware/Framework是针对android来说的,本人从事android这

input子系统

以前,看过国嵌关于input子系统的视频课程,说实话,我看完后脑子里很乱,给我的印象好像是input子系统驱动是一个全新的驱动架构,疑惑相当多.前几天在网上,看到有很多人介绍韦东山老师的linux驱动课程很不错,于是,我就买了第二期的视频,看了韦老师讲解的input子系统视频课程后,我完全明白了整个input子系统的工作机制.为了方便以后查阅,对input子系统的整体框架总结如下: 典型的输入设备(如键盘.鼠标)的工作机制都是差不多的,都是在设备有动作时,向CPU产生一个中断,通知它读取相应的数

input子系统 KeyPad-Touch上报数据格式与机制

-----------------------------------------------------------------------本文系本站原创,欢迎转载!转载请注明出处:http://blog.csdn.net/android_huber交流邮箱:[email protected]----------------------------------------------------------------------- linux drive中input子系统上报信息,调用函数

Linux input 子系统应用之按键驱动

硬件平台:s5pv210 软件平台:Linux2.6.35.7 应用程序:inputk2_app.c #include <stdio.h> #include <fcntl.h> #include <linux/input.h> #include <sys/types.h> #include <unistd.h> int main(int argc, char** argv) { int fd; int count; int i = 0; int

input子系统详解2

上一节大概了解了输入子系统的流程 这一节认真追踪一下代码 input.c: input_init(void)函数 1 static int __init input_init(void) 2 { 3 int err; 4 5 err = class_register(&input_class); 6 if (err) { 7 printk(KERN_ERR "input: unable to register input_dev class\n"); 8 return err;