标签:
INPUT子系统
一:什么是Input子系统?
(应用场景,用途)
二:怎么设计Input子系统的程序?
(分配一个输入设备——注册一个输入设备——上报输入事件——注销一个输入设备——释放一个输入设备)
三:Input子系统需要知道哪些?
(涉及的重要数据结构(input_dev,input_handle,input_handler),中断的相关知识)
四:一个自带按键驱动分析
(linux-4.5/drivers/input/keyboard下的amikbd.c )
五:模拟实现案例
(通过echo sysfs接口值,上传到core层)
六:总结
一:什么是Input子系统?
输入设备(如按键,键盘,触摸屏,鼠标,蜂鸣器等)是典型的字符设备,其一般的工作机制是底层在按键,触摸等动作发生时产生一个中断(或驱动通过 timer定时查询),然后cpu通过SPI,I2C或者外部存储器总线读取键值,坐标等数据,放一个缓冲区,字符设备驱动管理该缓冲区,而驱动的 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 置位,表示设备支持所有的事件。注意,一个设备可以支持一种或者多种事件类型。常用的事件类型如下: 常用的事件类型如下: 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; } |
分析如下:
第03~06行,定义了一些函数中将要用到的局部变量。
第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.1:input_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.2:input_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_dev和input_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.4:input_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.5:input_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 <vojtech@ucw.cz>, 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 <vojtech@ucw.cz>"); 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"); |
标签:
原文地址:http://www.cnblogs.com/oracleloyal/p/5438220.html