Linux 有自己的 input 子系统,可以统一管理鼠标和键盘事件。
基于输入子系统 实现的 uinput 可以方便的在用户空间模拟鼠标和键盘事件。
当然,也可以自己造轮子, 做一个字符设备接收用户输入,根据输入,投递 input 事件。
还有一种方式就是直接 往 evnent 里写入数据, 都可以达到控制鼠标键盘的功能。

本篇文章就是演示直接写入 event 的方法。
linux/input.h中有定义,这个文件还定义了标准按键的编码等

struct input_event {
struct timeval time; //按键时间
__u16 type; //类型,在下面有定义
__u16 code; //要模拟成什么按键
__s32 value;//是按下还是释放
};

code:
事件的代码.如果事件的类型代码是EV_KEY,该代码code为设备键盘代码.代码植0~127为键盘上的按键代码, 0x110~0x116 为鼠标上按键代码,其中0x110(BTN_ LEFT)为鼠标左键,0x111(BTN_RIGHT)为鼠标右键,0x112(BTN_ MIDDLE)为鼠标中键.其它代码含义请参看include/linux /input.h文件. 如果事件的类型代码是EV_REL,code值表示轨迹的类型.如指示鼠标的X轴方向 REL_X (代码为0x00),指示鼠标的Y轴方向REL_Y(代码为0x01),指示鼠标中轮子方向REL_WHEEL(代码为0x08).

type:
EV_KEY,键盘
EV_REL,相对坐标
EV_ABS,绝对坐标

value:
事件的值.如果事件的类型代码是EV_KEY,当按键按下时值为1,松开时值为0;如果事件的类型代码是EV_ REL,value的正数值和负数值分别代表两个不同方向的值.
/*
* Event types
*/

#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 //LED
#define EV_SND 0x12//声音
#define EV_REP 0x14//repeat
#define EV_FF 0x15
#define EV_PWR 0x16
#define EV_FF_STATUS 0x17
#define EV_MAX 0x1f
#define EV_CNT (EV_MAX+1)

下面是一个模拟鼠标和键盘输入的例子:

#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/input.h>
#include <linux/uinput.h>
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

void simulate_key(int fd,int kval)
{
struct input_event event;
event.type = EV_KEY;
event.value = 1;
event.code = kval;

gettimeofday(&event.time,0);
write(fd,&event,sizeof(event)) ;

event.type = EV_SYN;
event.code = SYN_REPORT;
event.value = 0;
write(fd, &event, sizeof(event));

memset(&event, 0, sizeof(event));
gettimeofday(&event.time, NULL);
event.type = EV_KEY;
event.code = kval;
event.value = 0;
write(fd, &event, sizeof(event));
event.type = EV_SYN;
event.code = SYN_REPORT;
event.value = 0;
write(fd, &event, sizeof(event));

}

void simulate_mouse(int fd)
{
struct input_event event;
memset(&event, 0, sizeof(event));
gettimeofday(&event.time, NULL);
event.type = EV_REL;
event.code = REL_X;
event.value = 10;
write(fd, &event, sizeof(event));

event.type = EV_REL;
event.code = REL_Y;
event.value = 10;
write(fd, &event, sizeof(event));

event.type = EV_SYN;
event.code = SYN_REPORT;
event.value = 0;
write(fd, &event, sizeof(event));
}

int main()
{
int fd_kbd;
int fd_mouse;
fd_kbd = open("/dev/input/event1",O_RDWR);
if(fd_kbd<=0){
printf("error open keyboard:/n");
return -1;

}

fd_mouse = open("/dev/input/event2",O_RDWR);
if(fd_mouse<=0){
printf("error open mouse/n");
return -2;
}

int i = 0;
for(i=0; i< 10; i++)
{
simulate_key(fd_kbd, KEY_A + i);
simulate_mouse(fd_mouse);
sleep(1);
}

close(fd_kbd);
}
模拟了鼠标和键盘的输入事件。
关于这里 open 哪个 event , 可以通过 cat /proc/bus/input/devices
I: Bus=0017 Vendor=0001 Product=0001 Version=0100
N: Name="Macintosh mouse button emulation"
P: Phys=
S: Sysfs=/class/input/input0
U: Uniq=
H: Handlers=mouse0 event0
B: EV=7
B: KEY=70000 0 0 0 0 0 0 0 0
B: REL=3

I: Bus=0011 Vendor=0001 Product=0001 Version=ab41
N: Name="AT Translated Set 2 keyboard"
P: Phys=isa0060/serio0/input0
S: Sysfs=/class/input/input1
U: Uniq=
H: Handlers=kbd event1
B: EV=120013
B: KEY=4 2000000 3803078 f800d001 feffffdf ffefffff ffffffff fffffffe
B: MSC=10
B: LED=7

I: Bus=0019 Vendor=0000 Product=0002 Version=0000
N: Name="Power Button (FF)"
P: Phys=LNXPWRBN/button/input0
S: Sysfs=/class/input/input3
U: Uniq=
H: Handlers=kbd event3
B: EV=3
B: KEY=100000 0 0 0

I: Bus=0019 Vendor=0000 Product=0001 Version=0000
N: Name="Power Button (CM)"
P: Phys=PNP0C0C/button/input0
S: Sysfs=/class/input/input4
U: Uniq=
H: Handlers=kbd event4
B: EV=3
B: KEY=100000 0 0 0

I: Bus=0003 Vendor=046d Product=c018 Version=0111
N: Name="Logitech USB Optical Mouse"
P: Phys=usb-0000:00:1d.1-2/input0
S: Sysfs=/class/input/input24
U: Uniq=
H: Handlers=mouse1 event2
B: EV=7
B: KEY=70000 0 0 0 0 0 0 0 0
B: REL=103

我的鼠标是 罗技 的 Logitech USB Optical Mouse, 所以 鼠标是 event2
下面是一个读取 鼠标和键盘事件的例子:
#include <stdio.h>
#include <stdlib.h>
#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

static void show_event(struct input_event* event)
{
printf("%d %d %d/n", event->type, event->code, event->value);

return;
}

int main(int argc, char* argv[])
{
struct input_event event = {{0}, 0};
const char* file_name = argc == 2 ? argv[1] : "/dev/input/event2";

int fd = open(file_name, O_RDWR);


if(fd > 0)
{

while(1)
{
int ret = read(fd, &event, sizeof(event));
if(ret == sizeof(event))
{
show_event(&event);
}
else
{
break;
}
}
close(fd);
}

return 0;
}

很多人对于 如何模拟 CTRL + SPACE 感兴趣, 下面也给个例子,呵呵。
void simulate_ctrl_space(int fd)
{
struct input_event event;

//先发送一个 CTRL 按下去的事件。
event.type = EV_KEY;
event.value = 1;
event.code = KEY_LEFTCTRL;
gettimeofday(&event.time,0);
write(fd,&event,sizeof(event)) ;

event.type = EV_SYN;
event.code = SYN_REPORT;
event.value = 0;
write(fd, &event, sizeof(event));

//先发送一个 SPACE 按下去的事件。
event.type = EV_KEY;
event.value = 1;
event.code = KEY_SPACE;
gettimeofday(&event.time,0);
write(fd,&event,sizeof(event)) ;

//发送一个 释放 SPACE 的事件
memset(&event, 0, sizeof(event));
gettimeofday(&event.time, NULL);
event.type = EV_KEY;
event.code = KEY_SPACE;
event.value = 0;
write(fd, &event, sizeof(event));

event.type = EV_SYN;
event.code = SYN_REPORT;
event.value = 0;
write(fd, &event, sizeof(event));


//发送一个 释放 CTRL 的事件
memset(&event, 0, sizeof(event));
gettimeofday(&event.time, NULL);
event.type = EV_KEY;
event.code = KEY_LEFTCTRL;
event.value = 0;
write(fd, &event, sizeof(event));


event.type = EV_SYN;
event.code = SYN_REPORT;
event.value = 0;
write(fd, &event, sizeof(event));

}



接下来分析一下 uinput 和 linux 的 input 子系统。

linux uinput

本文以 2.6.22.7 的kernel 为基础。
首先 uinput 是一个字符设备, 其次它还是一个 input 设备。另外它可以是一个鼠标或者键盘设备。

从 init 部分说起吧。

static const struct file_operations uinput_fops = {
.owner = THIS_MODULE,
.open = uinput_open,
.release = uinput_release,
.read = uinput_read,
.write = uinput_write,
.poll = uinput_poll,
.unlocked_ioctl = uinput_ioctl,
};

static struct miscdevice uinput_misc = {
.fops = &uinput_fops,
.minor = UINPUT_MINOR,
.name = UINPUT_NAME,
};

static int __init uinput_init(void)
{
return misc_register(&uinput_misc);
}

首先说说 miscdevice, 很方便的东西,对 device 做了简单的包装,
当 misc_register 的时候就完成了 设备的 注册安装一类的东东, 不用自己再操心了。真是懒人的设计阿。
所有的 misc 设备公用同一个主设备号,在 misc_init 中,
static int __init misc_init(void)
{
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *ent;

ent = create_proc_entry("misc", 0, NULL);
if (ent)
ent->proc_fops = &misc_proc_fops;
#endif
misc_class = class_create(THIS_MODULE, "misc");
if (IS_ERR(misc_class))
return PTR_ERR(misc_class);

if (register_chrdev(MISC_MAJOR,"misc",&misc_fops)) {
printk("unable to get major %d for misc devices/n",
MISC_MAJOR);
class_destroy(misc_class);
return -EIO;
}
return 0;
}
register_chrdev 接口真 BT ,
__register_chrdev_region(major, 0, 256, name);
直接占用了 0 到 255 的次设备号,注册 misc 类型设备的时候,直接从里面取就是了。
而在 misc_open 通过设备节点把 file_operations 指向对应的设备驱动上去, 很良好的设计, 呵呵。
有点类似其他总线的设计, 但是只有 device_list, 没有 driver_list, 当然也不需要。
不在 misc 上浪费时间了。 接下来再到 uinput 中去。
从uinput_open 说起吧, uinput_open 其实啥事情都没干。。做了一些简单的初始化工作。

要创建一个 input 设备,我们在调用input_register_device 前需要设置 好input_dev的各种属性。
而设置input_dev的属性在 uinput_setup_device 接口中, 但驱动怎么知道你想模拟什么设备呢?
又需要通过 uinput_ioctl 设置先。 很糟糕的设计。 用户若是不知道这些流程,如何能使用这个模拟驱动?
下面是一个使用 uinput 的用户态程序:

int setup_uinput_device(char *device)
{
// Temporary variable
int i=0;
// Open the input device
//uinp_fd = open("/dev/input/uinput", O_WRONLY | O_NDELAY);
uinp_fd = open(device, O_WRONLY | O_NDELAY);

if (uinp_fd == 0)
{
printf("Unable to open /dev/input/uinput/n");
return -1;
}

memset(&uinp,0,sizeof(uinp)); // Intialize the uInput device to NULL
strncpy(uinp.name, "HID Keyboard Device", strlen("HID Keyboard Device"));
uinp.id.version = 4;
uinp.id.bustype = BUS_USB;
uinp.id.product = 1;
uinp.id.vendor = 1;
// Setup the uinput device
ioctl(uinp_fd, UI_SET_EVBIT, EV_KEY);
ioctl(uinp_fd, UI_SET_EVBIT, EV_REL);
ioctl(uinp_fd, UI_SET_RELBIT, REL_X);
ioctl(uinp_fd, UI_SET_RELBIT, REL_Y);

for (i=0; i < 256; i++) {
ioctl(uinp_fd, UI_SET_KEYBIT, i);
}
ioctl(uinp_fd, UI_SET_KEYBIT, BTN_MOUSE);
ioctl(uinp_fd, UI_SET_KEYBIT, BTN_TOUCH);
ioctl(uinp_fd, UI_SET_KEYBIT, BTN_MOUSE);
ioctl(uinp_fd, UI_SET_KEYBIT, BTN_LEFT);
ioctl(uinp_fd, UI_SET_KEYBIT, BTN_MIDDLE);
ioctl(uinp_fd, UI_SET_KEYBIT, BTN_RIGHT);
ioctl(uinp_fd, UI_SET_KEYBIT, BTN_FORWARD);
ioctl(uinp_fd, UI_SET_KEYBIT, BTN_BACK);

write(uinp_fd, &uinp, sizeof(uinp));
if (ioctl(uinp_fd, UI_DEV_CREATE))
{
printf("Unable to create UINPUT device.");
return -1;
}
return 1;
}

很变态的流程, 先用 ioctl 设置参数(模拟鼠标,键盘), 再 write, 在第一次 write 的时候创建 inputdev,
然后 ioctl 调用 UI_DEV_CREATE 向系统注册 . BT............
今天先写到这里拉。。

浅析linux下键盘设备工作和注册流程

【浅析linux下鼠标驱动的实现】
input_init()=>
=>
class_register(&input_class);注册input类
input_proc_init();创建proc下的目录和文件
register_chrdev(INPUT_MAJOR,"input",&input_fops);注册驱动程序到cdev_map上,以待驱动设备.

drivers/input/keyboard/pxa3xx_keypad.c为我们的keyboard设备,
pxa3xx_keypad_probe=>
request_irq(IRQ_ENHROT,&enhanced_rotary_interrupt,
IRQF_DISABLED,"Enhanced Rotary",(void*)keypad);注册快捷键中断
request_irq(IRQ_KEYPAD,pxa3xx_keypad_interrupt,IRQF_DISABLED,pdev->name,keypad);注册中断
staticirqreturn_t pxa3xx_keypad_interrupt(intirq,void*dev_id)
{
structpxa3xx_keypad*keypad=dev_id;
uint32_tkpc=keypad_readl(KPC);

if(kpc&KPC_MI)
pxa3xx_keypad_scan_matrix(keypad);

if(kpc&KPC_DI)
pxa3xx_keypad_scan_direct(keypad);

returnIRQ_HANDLED;
}
在irq中如果读到了key,那么会直接调用
input_report_key(keypad->input_dev,lookup_matrix_keycode(keypad,row,col),
new_state[col]&(1<<row));
staticinlineunsignedintlookup_matrix_keycode(
structpxa3xx_keypad*keypad,introw,intcol)
{
returnkeypad->matrix_keycodes[(row<<3)+col];
}
input_report_key(structinput_dev*dev,unsignedintcode,intvalue)
dev为input_dev设备,我们的4*4键盘
code为标准PC键盘码值
value为按键动作,为1表示键盘按下,为0表示按键抬起
staticinlinevoidinput_report_key(structinput_dev*dev,unsignedintcode,intvalue)
{
input_event(dev,EV_KEY,code,!!value);
}
voidinput_event(structinput_dev*dev,
unsignedinttype,unsignedintcode,intvalue)
{
unsignedlongflags;

if(is_event_supported(type,dev->evbit,EV_MAX)){
spin_lock_irqsave(&dev->event_lock,flags);
add_input_randomness(type,code,value);//因为按键的存在随机性,所以按键是给系统提供墒随机数的好来源.
input_handle_event(dev,type,code,value);
spin_unlock_irqrestore(&dev->event_lock,flags);
}
}
staticvoidinput_handle_event(structinput_dev*dev,
unsignedinttype,unsignedintcode,intvalue)
{
...
caseEV_KEY:
if(is_event_supported(code,dev->keybit,KEY_MAX)&&
!!test_bit(code,dev->key)!=value){//这次来的是否为新的键值

if(value!=2){
__change_bit(code,dev->key);//通过异或^操作,反转code对应的bitmap,如果value等于2,那么将忽略该按键
if(value)
input_start_autorepeat(dev,code);//键盘按下,那么开启定时检测,这样可以出现连续输入的效果
}

disposition=INPUT_PASS_TO_HANDLERS;
}
break;
...
}
staticvoidinput_start_autorepeat(structinput_dev*dev,intcode)
{
if(test_bit(EV_REP,dev->evbit)&&
dev->rep[REP_PERIOD]&&dev->rep[REP_DELAY]&&
dev->timer.data){
dev->repeat_key=code;
mod_timer(&dev->timer,//重新启动定时器input_repeat_key,时间间隔msecs_to_jiffies(dev->rep[REP_DELAY])
jiffies+msecs_to_jiffies(dev->rep[REP_DELAY]));
}
}

staticvoidinput_repeat_key(unsignedlongdata)
{
structinput_dev*dev=(void*)data;
unsignedlongflags;

spin_lock_irqsave(&dev->event_lock,flags);

if(test_bit(dev->repeat_key,dev->key)&&
is_event_supported(dev->repeat_key,dev->keybit,KEY_MAX)){

input_pass_event(dev,EV_KEY,dev->repeat_key,2);//交给处理按键函数

if(dev->sync){
/*
* Only send SYN_REPORT if we are not in a middle
* of driver parsing a new hardware packet.
* Otherwise assume that the driver will send
* SYN_REPORT once it's done.
*/

input_pass_event(dev,EV_SYN,SYN_REPORT,1);
}

if(dev->rep[REP_PERIOD])
mod_timer(&dev->timer,jiffies+
msecs_to_jiffies(dev->rep[REP_PERIOD]));
}

spin_unlock_irqrestore(&dev->event_lock,flags);
}

input_pass_event=>
handle->handler->event(handle,type,code,value);
就是kbd_handler的kbd_event=>kbd_keycode=>
atomic_notifier_call_chain(&keyboard_notifier_list,KBD_UNICODE,&param)
通知挂在keyboard链上所有等待键盘输入的应用程序,
通过register_keyboard_notifier()函数可以注册到键盘链上【gliethttp.Leith】,


input_dev=input_allocate_device();申请一个input设备空间
input_dev->open=pxa3xx_keypad_open;给这个空间填充方法
input_dev->close=pxa3xx_keypad_close;
input_dev->private=keypad;
set_bit(EV_KEY,input_dev->evbit);//键按下
set_bit(EV_REL,input_dev->evbit);//键释放
pxa3xx_keypad_build_keycode(keypad);//设备键盘映射码
该函数将根据pxa3xx_device_keypad设备下的matrix_key_map进行键控设置,
pxa_set_keypad_info(&jades_keypad_info)=>将jades_keypad_info登记为pdata;
#defineMAX_MATRIX_KEY_NUM(8*8)
matrix_keycodes[MAX_MATRIX_KEY_NUM];表示为8*8键盘
keypad->matrix_keycodes[(row<<3)+col]=code;表示第row行的第col列处按键,代表code编码值,这个为我们内部使用.
set_bit(code,input_dev->keybit);//设置code为我们的键盘对操作系统可用的键盘值
if(pdata->direct_key_num){
for(i=0;i<pdata->direct_key_num;i++){
set_bit(pdata->direct_key_map[i],input_dev->keybit);//快捷键单元
}
}
set_bit(KEY_POWER,input_dev->keybit);//登记电源按键为系统可见按键
input_register_device(input_dev);=>//注册设该备devices_subsys总线上
intinput_register_device(structinput_dev*dev)
{
staticatomic_t input_no=ATOMIC_INIT(0);
structinput_handler*handler;
constchar*path;
interror;

__set_bit(EV_SYN,dev->evbit);

/*
* 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);
if(!dev->rep[REP_DELAY]&&!dev->rep[REP_PERIOD]){
dev->timer.data=(long)dev;
dev->timer.function=input_repeat_key;//消抖处理函数,采用延时消抖
dev->rep[REP_DELAY]=500;//250;
dev->rep[REP_PERIOD]=66;//33;
}

if(!dev->getkeycode)
dev->getkeycode=input_default_getkeycode;

if(!dev->setkeycode)
dev->setkeycode=input_default_setkeycode;
//在/sys/class/input下创建以input0,input1为目录名的input类型设备
snprintf(dev->dev.bus_id,sizeof(dev->dev.bus_id),
"input%ld",(unsignedlong)atomic_inc_return(&input_no)-1);

if(dev->cdev.dev)
dev->dev.parent=dev->cdev.dev;

error=device_add(&dev->dev);//将设备登记到设备总线上,之后将以目录和文件的形式呈现
if(error)
returnerror;

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);
returnerror;
}

list_add_tail(&dev->node,&input_dev_list);
//将设备放到input的链表上,该链表上存放着所有input类型的dev设备对象【gliethttp.Leith】
list_for_each_entry(handler,&input_handler_list,node)
input_attach_handler(dev,handler);
//从input_handler_list驱动链表上尝试匹配,是否有驱动该dev设备的driver驱动,如果有,那么将匹配的驱动绑定给dev设备,来驱动这个dev.
input_wakeup_procfs_readers();

mutex_unlock(&input_mutex);

return0;
}

drivers/char/keyboard.c
kbd_init()=>
input_register_handler(&kbd_handler);注册键盘驱动到input_handler_list链表上

staticintinput_attach_handler(structinput_dev*dev,structinput_handler*handler)
{
conststructinput_device_id*id;
interror;
//看看这个咚咚,是不是在黑名单里,如果在,那么就byebye了
【gliethttp.Leith】
if(handler->blacklist&&input_match_device(handler->blacklist,dev))
return-ENODEV;

id=input_match_device(handler->id_table,dev);
if(!id)
return-ENODEV;

error=handler->connect(handler,dev,id);//ok,找到驱动该dev的driver,那么尝试连接
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);

returnerror;
}

kbd_connect=>input_register_handle=>input_open_device=>pxa3xx_keypad_open配置键盘io口


以下内容转自:http://ericxiao.cublog.cn/

九:evdev的初始化
Evdev的模块初始化函数为evdev_init().代码如下:
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}
它调用了input_register_handler注册了一个handler.
注意到,在这里evdev_handler中定义的minor为EVDEV_MINOR_BASE(64).也就是说evdev_handler所表示的设备文件范围为(13,64)à(13,64+32).
从之前的分析我们知道.匹配成功的关键在于handler中的blacklist和id_talbe. Evdev_handler的id_table定义如下:
static const struct input_device_id evdev_ids[] = {
{ .driver_info = 1 }, /* Matches all devices */
{ }, /* Terminating zero entry */
};
它没有定义flags.也没有定义匹配属性值.这个handler是匹配所有input device的.从前面的分析我们知道.匹配成功之后会调用handler->connect函数.
在Evdev_handler中,该成员函数如下所示:

static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
struct evdev *evdev;
int minor;
int error;

for (minor = 0; minor < EVDEV_MINORS; minor++)
if (!evdev_table[minor])
break;

if (minor == EVDEV_MINORS) {
printk(KERN_ERR "evdev: no more free evdev devices/n");
return -ENFILE;
}
EVDEV_MINORS定义为32.表示evdev_handler所表示的32个设备文件.evdev_talbe是一个struct evdev类型的数组.struct evdev是模块使用的封装结构.在接下来的代码中我们可以看到这个结构的使用.
这一段代码的在evdev_talbe找到为空的那一项.minor就是数组中第一项为空的序号.

evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
if (!evdev)
return -ENOMEM;

INIT_LIST_HEAD(&evdev->client_list);
spin_lock_init(&evdev->client_lock);
mutex_init(&evdev->mutex);
init_waitqueue_head(&evdev->wait);

snprintf(evdev->name, sizeof(evdev->name), "event%d", minor);
evdev->exist = 1;
evdev->minor = minor;

evdev->handle.dev = input_get_device(dev);
evdev->handle.name = evdev->name;
evdev->handle.handler = handler;
evdev->handle.private = evdev;
接 下来,分配了一个evdev结构,并对这个结构进行初始化.在这里我们可以看到,这个结构封装了一个handle结构,这结构与我们之前所讨论的 handler是不相同的.注意有一个字母的差别哦.我们可以把handle看成是handler和input device的信息集合体.在这个结构里集合了匹配成功的handler和input device

strlcpy(evdev->dev.bus_id, evdev->name, sizeof(evdev->dev.bus_id));
evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev);
在这段代码里主要完成evdev封装的device的初始化.注意在这里,使它所属的类指向input_class.这样在/sysfs中创建的设备目录就会在/sys/class/input/下面显示.

error = input_register_handle(&evdev->handle);
if (error)
goto err_free_evdev;
error = evdev_install_chrdev(evdev);
if (error)
goto err_unregister_handle;

error = device_add(&evdev->dev);
if (error)
goto err_cleanup_evdev;

return 0;

err_cleanup_evdev:
evdev_cleanup(evdev);
err_unregister_handle:
input_unregister_handle(&evdev->handle);
err_free_evdev:
put_device(&evdev->dev);
return error;
}
注册handle,如果是成功的,那么调用evdev_install_chrdev将evdev_table的minor项指向evdev. 然后将evdev->device注册到sysfs.如果失败,将进行相关的错误处理.
万事俱备了,但是要接收事件,还得要等”东风”.这个”东风”就是要打开相应的handle.这个打开过程是在文件的open()中完成的.

十:evdev设备结点的open()操作
我们知道.对主设备号为INPUT_MAJOR的设备节点进行操作,会将操作集转换成handler的操作集.在evdev中,这个操作集就是evdev_fops.对应的open函数如下示:
static int evdev_open(struct inode *inode, struct file *file)
{
struct evdev *evdev;
struct evdev_client *client;
int i = iminor(inode) - EVDEV_MINOR_BASE;
int error;

if (i >= EVDEV_MINORS)
return -ENODEV;

error = mutex_lock_interruptible(&evdev_table_mutex);
if (error)
return error;
evdev = evdev_table[i];
if (evdev)
get_device(&evdev->dev);
mutex_unlock(&evdev_table_mutex);

if (!evdev)
return -ENODEV;

client = kzalloc(sizeof(struct evdev_client), GFP_KERNEL);
if (!client) {
error = -ENOMEM;
goto err_put_evdev;
}
spin_lock_init(&client->buffer_lock);
client->evdev = evdev;
evdev_attach_client(evdev, client);

error = evdev_open_device(evdev);
if (error)
goto err_free_client;

file->private_data = client;
return 0;

err_free_client:
evdev_detach_client(evdev, client);
kfree(client);
err_put_evdev:
put_device(&evdev->dev);
return error;
}
iminor(inode) - EVDEV_MINOR_BASE就得到了在evdev_table[ ]中的序号.然后将数组中对应的evdev取出.递增devdev中device的引用计数.
分配并初始化一个client.并将它和evdev关联起来: client->evdev指向它所表示的evdev. 将client挂到evdev->client_list上. 将client赋为file的私有区.
对应handle的打开是在此evdev_open_device()中完成的.代码如下:
static int evdev_open_device(struct evdev *evdev)
{
int retval;

retval = mutex_lock_interruptible(&evdev->mutex);
if (retval)
return retval;

if (!evdev->exist)
retval = -ENODEV;
else if (!evdev->open++) {
retval = input_open_device(&evdev->handle);
if (retval)
evdev->open--;
}

mutex_unlock(&evdev->mutex);
return retval;
}
如果evdev是第一次打开,就会调用input_open_device()打开evdev对应的handle.跟踪一下这个函数:
int input_open_device(struct input_handle *handle)
{
struct input_dev *dev = handle->dev;
int retval;

retval = mutex_lock_interruptible(&dev->mutex);
if (retval)
return retval;

if (dev->going_away) {
retval = -ENODEV;
goto out;
}

handle->open++;

if (!dev->users++ && dev->open)
retval = dev->open(dev);

if (retval) {
dev->users--;
if (!--handle->open) {
/*
* Make sure we are not delivering any more events
* through this handle
*/
synchronize_rcu();
}
}

out:
mutex_unlock(&dev->mutex);
return retval;
}
在这个函数中,我们看到.递增handle的打开计数.如果是第一次打开.则调用input device的open()函数.

十一:evdev的事件处理
经过上面的分析.每当input device上报一个事件时,会将其交给和它匹配的handler的event函数处理.在evdev中.这个event函数对应的代码为:
static void evdev_event(struct input_handle *handle,
unsigned int type, unsigned int code, int value)
{
struct evdev *evdev = handle->private;
struct evdev_client *client;
struct input_event event;

do_gettimeofday(&event.time);
event.type = type;
event.code = code;
event.value = value;

rcu_read_lock();

client = rcu_dereference(evdev->grab);
if (client)
evdev_pass_event(client, &event);
else
list_for_each_entry_rcu(client, &evdev->client_list, node)
evdev_pass_event(client, &event);

rcu_read_unlock();

wake_up_interruptible(&evdev->wait);
}
首先构造一个struct input_event结构.并设备它的type.code,value为处理事件的相关属性.如果该设备被强制设置了handle.则调用如之对应的client.
我们在open的时候分析到.会初始化clinet并将其链入到evdev->client_list. 这样,就可以通过evdev->client_list找到这个client了.
对于找到的第一个client都会调用evdev_pass_event( ).代码如下:
static void evdev_pass_event(struct evdev_client *client,
struct input_event *event)
{
/*
* Interrupts are disabled, just acquire the lock
*/
spin_lock(&client->buffer_lock);
client->buffer[client->head++] = *event;
client->head &= EVDEV_BUFFER_SIZE - 1;
spin_unlock(&client->buffer_lock);

kill_fasync(&client->fasync, SIGIO, POLL_IN);
}
这里的操作很简单.就是将event保存到client->buffer中.而client->head就是当前的数据位置.注意这里是一个环形缓存区.写数据是从client->head写.而读数据则是从client->tail中读.

十二:设备节点的read处理
对于evdev设备节点的read操作都会由evdev_read()完成.它的代码如下:
static ssize_t evdev_read(struct file *file, char __user *buffer,
size_t count, loff_t *ppos)
{
struct evdev_client *client = file->private_data;
struct evdev *evdev = client->evdev;
struct input_event event;
int retval;

if (count < evdev_event_size())
return -EINVAL;

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);
if (retval)
return retval;

if (!evdev->exist)
return -ENODEV;

while (retval + evdev_event_size() <= count &&
evdev_fetch_next_event(client, &event)) {

if (evdev_event_to_user(buffer + retval, &event))
return -EFAULT;

retval += evdev_event_size();
}

return retval;
}
首先,它判断缓存区大小是否足够.在读取数据的情况下,可能当前缓存区内没有数据可读.在这里先睡眠等待缓存区中有数据.如果在睡眠的时候,.条件满足.是不会进行睡眠状态而直接返回的.
然后根据read()提够的缓存区大小.将client中的数据写入到用户空间的缓存区中.
十三:设备节点的写操作
同样.对设备节点的写操作是由evdev_write()完成的.代码如下:

static ssize_t evdev_write(struct file *file, const char __user *buffer,
size_t count, loff_t *ppos)
{
struct evdev_client *client = file->private_data;
struct evdev *evdev = client->evdev;
struct input_event event;
int retval;

retval = mutex_lock_interruptible(&evdev->mutex);
if (retval)
return retval;

if (!evdev->exist) {
retval = -ENODEV;
goto out;
}

while (retval < count) {

if (evdev_event_from_user(buffer + retval, &event)) {
retval = -EFAULT;
goto out;
}

input_inject_event(&evdev->handle,
event.type, event.code, event.value);
retval += evdev_event_size();
}

out:
mutex_unlock(&evdev->mutex);
return retval;
}
首先取得操作设备文件所对应的evdev.
实际上,这里写入设备文件的是一个event结构的数组.我们在之前分析过,这个结构里包含了事件的type.code和event.
将写入设备的event数组取出.然后对每一项调用event_inject_event().
这个函数的操作和input_event()差不多.就是将第一个参数handle转换为输入设备结构.然后这个设备再产生一个事件.
代码如下:
void input_inject_event(struct input_handle *handle,
unsigned int type, unsigned int code, int value)
{
struct input_dev *dev = handle->dev;
struct input_handle *grab;
unsigned long flags;

if (is_event_supported(type, dev->evbit, EV_MAX)) {
spin_lock_irqsave(&dev->event_lock, flags);

rcu_read_lock();
grab = rcu_dereference(dev->grab);
if (!grab || grab == handle)
input_handle_event(dev, type, code, value);
rcu_read_unlock();

spin_unlock_irqrestore(&dev->event_lock, flags);
}
}
我们在这里也可以跟input_event()对比一下,这里设备可以产生任意事件,而不需要和设备所支持的事件类型相匹配.
由此可见.对于写操作而言.就是让与设备文件相关的输入设备产生一个特定的事件.
将上述设备文件的操作过程以图的方式表示如下:


十四:小结
在 这一节点,分析了整个input子系统的架构,各个环节的流程.最后还以evdev为例.将各个流程贯穿在一起.以加深对input子系统的理解.由此也 可以看出.linux设备驱动采用了分层的模式.从最下层的设备模型到设备,驱动,总线再到input子系统最后到input device.这样的分层结构使得最上层的驱动不必关心下层是怎么实现的.而下层驱动又为多种型号同样功能的驱动提供了一个统一的接口.

一:前言在键盘驱动代码分析的笔记中,接触到了input子系统.键盘驱动,键盘驱动将检测到的所有按键都上报给了input子系统。Input子系统 是所有I/O设备驱动的中间层,为上层提供了一个统一的界面。例如,在终端系统中,我们不需要去管有多少个键盘,多少个鼠标。它只要从input子系统中 去取对应的事件(按键,鼠标移位等)就可以了。今天就对input子系统做一个详尽的分析.下面的代码是基于linux kernel 2.6.25.分析的代码主要位于kernel2.6.25/drivers/input下面.二:使用input子系统的例子在内核自带的文档Documentation/input/input-programming.txt中。有一个使用input子系统的例子,并附带相应的说明。以此为例分析如下:#include <linux/input.h>#include <linux/module.h>#include <linux/init.h>#include <asm/irq.h>#include <asm/io.h>static void button_interrupt(int irq, void *dummy, struct pt_regs *fp){ input_report_key(&button_dev, BTN_1, inb(BUTTON_PORT) & 1); input_sync(&button_dev);}static int __init button_init(void){ if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button", NULL)) { printk(KERN_ERR "button.c: Can't allocate irq %d/n", button_irq); return -EBUSY; } button_dev.evbit[0] = BIT(EV_KEY); button_dev.keybit[LONG(BTN_0)] = BIT(BTN_0); input_register_device(&button_dev);}static void __exit button_exit(void){ input_unregister_device(&button_dev); free_irq(BUTTON_IRQ, button_interrupt);}module_init(button_init);module_exit(button_exit);这个示例module代码还是比较简单,在初始化函数里注册了一个中断处理例程。然后注册了一个input device.在中断处理程序里,将接收到的按键上报给input子系统。文档的作者在之后的分析里又对这个module作了优化。主要是在注册中断处理的时序上。在修改过后的代码里,为input device定义了open函数,在open的时候再去注册中断处理例程。具体的信息请自行参考这篇文档。在资料缺乏的情况下,kernel自带的文档就 是剖析kernel相关知识的最好资料.文档的作者还分析了几个api函数。列举如下:1):set_bit(EV_KEY, button_dev.evbit); set_bit(BTN_0, button_dev.keybit);分别用来设置设备所产生的事件以及上报的按键值。Struct iput_dev中有两个成员,一个是evbit.一个是keybit.分别用表示设备所支持的动作和按键类型。2): input_register_device(&button_dev);用来注册一个input device.3): input_report_key()用于给上层上报一个按键动作4): input_sync()用来告诉上层,本次的事件已经完成了.5): NBITS(x) - returns the length of a bitfield array in longs for x bits LONG(x) - returns the index in the array in longs for bit xBIT(x) - returns the index in a long for bit x这几个宏在input子系统中经常用到。上面的英文解释已经很清楚了。三:input设备注册分析.Input设备注册的接口为:input_register_device()。代码如下: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; __set_bit(EV_SYN, dev->evbit); /* * 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); 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; }在前面的分析中曾分析过。Input_device的evbit表示该设备所支持的事件。在这里将其EV_SYN置位,即所有设备都支持这个事 件.如果dev->rep[REP_DELAY]和dev->rep[REP_PERIOD]没有设值,则将其赋默认值。这主要是处理重复按 键的. if (!dev->getkeycode) dev->getkeycode = input_default_getkeycode; if (!dev->setkeycode) dev->setkeycode = input_default_setkeycode; snprintf(dev->dev.bus_id, sizeof(dev->dev.bus_id), "input%ld", (unsigned long) atomic_inc_return(&input_no) - 1); error = device_add(&dev->dev); if (error) return error; 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; }如果input device没有定义getkeycode和setkeycode.则将其赋默认值。还记得在键盘驱动中的分析吗?这两个操作函数就可以用来取键的扫描码 和设置键的扫描码。然后调用device_add()将input_dev中封装的device注册到sysfs list_add_tail(&dev->node, &input_dev_list); list_for_each_entry(handler, &input_handler_list, node) input_attach_handler(dev, handler); input_wakeup_procfs_readers(); mutex_unlock(&input_mutex); return 0;}这里就是重点了。将input device挂到input_dev_list链表上.然后,对每一个挂在input_handler_list的handler调用 input_attach_handler().在这里的情况有好比设备模型中的device和driver的匹配。所有的input device都挂在input_dev_list链上。所有的handle都挂在input_handler_list上。看一下这个匹配的详细过程。匹配是在input_attach_handler()中完成的。代码如下: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)) return -ENODEV; id = input_match_device(handler->id_table, 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;}如果handle的blacklist被赋值。要先匹配blacklist中的数据跟dev->id的数据是否匹配。匹配成功过后再来匹 配handle->id和dev->id中的数据。如果匹配成功,则调用handler->connect().来看一下具体的数据匹配过程,这是在input_match_device()中完成的。代码如下:static const struct input_device_id *input_match_device(const struct input_device_id *id, struct input_dev *dev){ int i; for (; id->flags || id->driver_info; id++) { 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(evbit, EV_MAX); MATCH_BIT(,, 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); return id; } return NULL;}MATCH_BIT宏的定义如下:#define MATCH_BIT(bit, max) / for (i = 0; i < BITS_TO_LONGS(max); i++) / if ((id->bit[i] & dev->bit[i]) != id->bit[i]) / break; / if (i != BITS_TO_LONGS(max)) / continue;由此看到。在id->flags中定义了要匹配的项。定义INPUT_DEVICE_ID_MATCH_BUS。则是要比较input device和input handler的总线类型。 INPUT_DEVICE_ID_MATCH_VENDOR,INPUT_DEVICE_ID_MATCH_PRODUCT,INPUT_DEVICE_ID_MATCH_VERSION 分别要求设备厂商。设备号和设备版本.如果id->flags定义的类型匹配成功。或者是id->flags没有定义,就会进入到MATCH_BIT的匹配项了.从 MATCH_BIT宏的定义可以看出。只有当iput device和input handler的id成员在evbit, keybit,… swbit项相同才会匹配成功。而且匹配的顺序是从evbit, keybit到swbit.只要有一项不同,就会循环到id中的下一项进行比较.简而言之,注册input device的过程就是为input device设置默认值,并将其挂以input_dev_list.与挂载在input_handler_list中的handler相匹配。如果匹配成功,就会调用handler的connect函数.四:handler注册分析Handler注册的接口如下所示:int input_register_handler(struct input_handler *handler){ struct input_dev *dev; int retval; retval = mutex_lock_interruptible(&input_mutex); if (retval) return retval; INIT_LIST_HEAD(&handler->h_list); if (handler->fops != NULL) { if (input_table[handler->minor >> 5]) { retval = -EBUSY; goto out; } input_table[handler->minor >> 5] = handler; } list_add_tail(&handler->node, &input_handler_list); list_for_each_entry(dev, &input_dev_list, node) input_attach_handler(dev, handler); input_wakeup_procfs_readers();out: mutex_unlock(&input_mutex); return retval;}handler->minor表示对应input设备节点的次设备号.以handler->minor右移五位做为索引值插入到input_table[ ]中..之后再来分析input_talbe[ ]的作用.然后将handler挂到input_handler_list中.然后将其与挂在input_dev_list中的input device匹配.这个过程和input device的注册有相似的地方.都是注册到各自的链表,.然后与另外一条链表的对象相匹配.五:handle的注册int input_register_handle(struct input_handle *handle){ struct input_handler *handler = handle->handler; struct input_dev *dev = handle->dev; int error; /* * We take dev->mutex here to prevent race with * input_release_device(). */ error = mutex_lock_interruptible(&dev->mutex); if (error) return error; list_add_tail_rcu(&handle->d_node, &dev->h_list); mutex_unlock(&dev->mutex); synchronize_rcu(); /* * Since we are supposed to be called from ->connect() * which is mutually exclusive with ->disconnect() * we can't be racing with input_unregister_handle() * and so separate lock is not needed here. */ list_add_tail(&handle->h_node, &handler->h_list); if (handler->start) handler->start(handle); return 0;}在这个函数里所做的处理其实很简单.将handle挂到所对应input device的h_list链表上.还将handle挂到对应的handler的hlist链表上.如果handler定义了start函数,将调用之.到这里,我们已经看到了input device, handler和handle是怎么关联起来的了.以图的方式总结如下: 六:event事件的处理 我们在开篇的时候曾以linux kernel文档中自带的代码作分析.提出了几个事件上报的API.这些API其实都是input_event()的封装.代码如下: void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { unsigned long flags; //判断设备是否支持这类事件 if (is_event_supported(type, dev->evbit, EV_MAX)) { spin_lock_irqsave(&dev->event_lock, flags); //利用键盘输入来调整随机数产生器 add_input_randomness(type, code, value); input_handle_event(dev, type, code, value); spin_unlock_irqrestore(&dev->event_lock, flags); } } 首先,先判断设备产生的这个事件是否合法.如果合法,流程转入到input_handle_event()中. 代码如下: static void input_handle_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { int disposition = INPUT_IGNORE_EVENT; switch (type) { case EV_SYN: switch (code) { case SYN_CONFIG: disposition = INPUT_PASS_TO_ALL; break; case SYN_REPORT: if (!dev->sync) { dev->sync = 1; disposition = INPUT_PASS_TO_HANDLERS; } break; } break; case EV_KEY: //判断按键值是否被支持 if (is_event_supported(code, dev->keybit, KEY_MAX) && !!test_bit(code, dev->key) != value) { if (value != 2) { __change_bit(code, dev->key); if (value) input_start_autorepeat(dev, code); } disposition = INPUT_PASS_TO_HANDLERS; } break; case EV_SW: if (is_event_supported(code, dev->swbit, SW_MAX) && !!test_bit(code, dev->sw) != value) { __change_bit(code, dev->sw); disposition = INPUT_PASS_TO_HANDLERS; } break; case EV_ABS: if (is_event_supported(code, dev->absbit, ABS_MAX)) { value = input_defuzz_abs_event(value, dev->abs[code], dev->absfuzz[code]); if (dev->abs[code] != value) { dev->abs[code] = value; disposition = INPUT_PASS_TO_HANDLERS; } } break; case EV_REL: if (is_event_supported(code, dev->relbit, REL_MAX) && value) disposition = INPUT_PASS_TO_HANDLERS; break; case EV_MSC: if (is_event_supported(code, dev->mscbit, MSC_MAX)) disposition = INPUT_PASS_TO_ALL; break; case EV_LED: if (is_event_supported(code, dev->ledbit, LED_MAX) && !!test_bit(code, dev->led) != value) { __change_bit(code, dev->led); disposition = INPUT_PASS_TO_ALL; } break; case EV_SND: if (is_event_supported(code, dev->sndbit, SND_MAX)) { if (!!test_bit(code, dev->snd) != !!value) __change_bit(code, dev->snd); disposition = INPUT_PASS_TO_ALL; } break; case EV_REP: if (code <= REP_MAX && value >= 0 && dev->rep[code] != value) { dev->rep[code] = value; disposition = INPUT_PASS_TO_ALL; } break; case EV_FF: if (value >= 0) disposition = INPUT_PASS_TO_ALL; break; case EV_PWR: disposition = INPUT_PASS_TO_ALL; break; } if (type != EV_SYN) dev->sync = 0; if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event) dev->event(dev, type, code, value); if (disposition & INPUT_PASS_TO_HANDLERS) input_pass_event (dev, type, code, value); } 在这里,我们忽略掉具体事件的处理.到最后,如果该事件需要input device来完成的,就会将disposition设置成INPUT_PASS_TO_DEVICE.如果需要handler来完成的,就将 dispostion设为INPUT_PASS_TO_DEVICE.如果需要两者都参与,将disposition设置为 INPUT_PASS_TO_ALL. 需要输入设备参与的,回调设备的event函数.如果需要handler参与的.调用input_pass_event().代码如下: static void input_pass_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { struct input_handle *handle; rcu_read_lock(); handle = rcu_dereference(dev->grab); if (handle) handle->handler->event(handle, type, code, value); else list_for_each_entry_rcu(handle, &dev->h_list, d_node) if (handle->open) handle->handler->event(handle, type, code, value); rcu_read_unlock(); } 如果input device被强制指定了handler,则调用该handler的event函数. 结合handle注册的分析.我们知道.会将handle挂到input device的h_list链表上. 如果没有为input device强制指定handler.就会遍历input device->h_list上的handle成员.如果该handle被打开,则调用与输入设备对应的handler的event()函数.注 意,只有在handle被打开的情况下才会接收到事件. 另外,输入设备的handler强制设置一般是用带EVIOCGRAB标志的ioctl来完成的.如下是发图的方示总结evnet的处理过程: 我们已经分析了input device,handler和handle的注册过程以及事件的上报和处理.下面以evdev为实例做分析.来贯穿理解一下整个过程. 七:evdev概述 Evdev对应的设备节点一般位于/dev/input/event0 ~ /dev/input/event4.理论上可以对应32个设备节点.分别代表被handler匹配的32个input device. 可以用cat /dev/input/event0.然后移动鼠标或者键盘按键就会有数据输出(两者之间只能选一.因为一个设备文件只能关能一个输入设备).还可以往这个文件里写数据,使其产生特定的事件.这个过程我们之后再详细分析. 为了分析这一过程,必须从input子系统的初始化说起. 八:input子系统的初始化 Input子系统的初始化函数为input_init().代码如下: static int __init input_init(void) { int err; err = class_register(&input_class); if (err) { printk(KERN_ERR "input: unable to register input_dev class/n"); return err; } err = input_proc_init(); if (err) goto fail1; err = register_chrdev(INPUT_MAJOR, "input", &input_fops); if (err) { printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR); goto fail2; } return 0; fail2: input_proc_exit(); fail1: class_unregister(&input_class); return err; } 在这个初始化函数里,先注册了一个名为”input”的类.所有input device都属于这个类.在sysfs中表现就是.所有input device所代表的目录都位于/dev/class/input下面. 然后调用input_proc_init()在/proc下面建立相关的交互文件. 再后调用register_chrdev()注册了主设备号为INPUT_MAJOR(13).次设备号为0~255的字符设备.它的操作指针为input_fops. 在这里,我们看到.所有主设备号13的字符设备的操作最终都会转入到input_fops中.在前面分析的/dev/input/event0~/dev/input/event4的主设备号为13.操作也不例外的落在了input_fops中. Input_fops定义如下: static const struct file_operations input_fops = { .owner = THIS_MODULE, .open = input_open_file, }; 打开文件所对应的操作函数为input_open_file.代码如下示: static int input_open_file(struct inode *inode, struct file *file) { struct input_handler *handler = input_table[iminor(inode) >> 5]; const struct file_operations *old_fops, *new_fops = NULL; int err; /* No load-on-demand here? */ if (!handler || !(new_fops = fops_get(handler->fops))) return -ENODEV; iminor(inode)为打开文件所对应的次设备号.input_table是一个struct input_handler全局数组.在这里.它先设备结点的次设备号右移5位做为索引值到input_table中取对应项.从这里我们也可以看到.一 个handle代表1<<5个设备节点(因为在input_table中取值是以次备号右移5位为索引的.即低5位相同的次备号对应的是同一 个索引).在这里,终于看到了input_talbe大显身手的地方了.input_talbe[ ]中取值和input_talbe[ ]的赋值,这两个过程是相对应的. 在input_table中找到对应的handler之后,就会检验这个handle是否存,是否带有fops文件操作集.如果没有.则返回一个设备不存在的错误. /* * That's _really_ odd. Usually NULL ->open means "nothing special", * not "no device". Oh, well... */ if (!new_fops->open) { fops_put(new_fops); return -ENODEV; } old_fops = file->f_op; file->f_op = new_fops; err = new_fops->open(inode, file); if (err) { fops_put(file->f_op); file->f_op = fops_get(old_fops); } fops_put(old_fops); return err; } 然后将handler中的fops替换掉当前的fops.如果新的fops中有open()函数,则调用它.

浅析linux下鼠标驱动的实现

【浅析linux下键盘设备工作和注册流程】

对于鼠标驱动和前面分析过的键盘驱动都是共用input模型,所以,对于事件上报和处理的方式都没有区别,只是mouse鼠标驱动当上报完dx,dy,left,middle,right之后,需要调用input_sync(),将前面上报的仅仅填充在缓冲区中的数据,通过mousedev_notify_readers()发送给open了的挂接在mousedev->client_list链表上等待获取鼠标信息的client门,鼠标设备和键盘设备类似都是在/dev/input/目录下创建了一个char类型的设备节点,由应用程序使用read或者poll来阻塞调用,对于键盘设备为/dev/input/event0,...,/dev/input/eventx,对于鼠标设备为/dev/input/mouse0,...,/dev/input/mousex,可以使用sudo
cat/dev/input/event0来从终端上截获显示按键的信息,使用sudo cat/dev/input/mouse0来捕捉鼠标的信息.
让我们来看看驱动源码【gliethttp.Leith】:
============drivers/input/mouse/amimouse.c============
input_report_rel(amimouse_dev,REL_X,dx);
input_report_rel(amimouse_dev,REL_Y,dy);

input_report_key(amimouse_dev,BTN_LEFT,ciaa.pra&0x40);
input_report_key(amimouse_dev,BTN_MIDDLE,potgor&0x0100);
input_report_key(amimouse_dev,BTN_RIGHT,potgor&0x0400);

input_sync(amimouse_dev);// 拷贝到open了的每个client的client->packets[16]环形缓冲区,每个应用程序在调用open时, mousedev_open都会调用kzalloc来申请一个独立的mousedev_client结构体,然后将该client挂接到mousedev
->client_list链表,最后由mousedev_notify_readers向mousedev->client_list链表 上挂接的每个client拷贝鼠标信息,最后wake_up唤醒read或poll.


============drivers/input/mousedev.c============
mousedev_read=>mousedev_packet=>如果dx,dy,dz同时都为0,说明鼠标停止了,那么client->ready=0;
mousedev_event(dev,EV_SYN,SYN_REPORT,0)=>mousedev_notify_readers=>如果dx,dy,dz有一个发生了移动或者鼠标按键上一次的按键不同,那么client->ready=1;拷贝数据到mousedev->client_list链表上挂接的每个client的环形缓冲区,最后调用wake_up_interruptible(&mousedev->wait);唤醒因为read或者poll操作而被pending住的应用程序,比如xWindows系统或者MiniGUI系统.

mousedev_write=>mousedev_generate_response=>向client->ps2[6]缓冲区填充数据,有效数据的个数为client->bufsiz,之后执行如下赋值client->buffer=client->bufsiz;让client->buffer等于client->ps2[6]数据缓冲区中有效数据的个数.

staticssize_t mousedev_read(structfile*file,char__user*buffer,
size_tcount,loff_t*ppos)
{
structmousedev_client*client=file->private_data;
structmousedev*mousedev=client->mousedev;
signedchardata[sizeof(client->ps2)];
intretval=0;

if(!client->ready&&!client->buffer&&mousedev->exist&&
(file->f_flags&O_NONBLOCK))
return-EAGAIN;

retval=wait_event_interruptible(mousedev->wait,
!mousedev->exist||client->ready||client->buffer);
//等待条件满足或者信号发生,client->ready和client->buffer都可以在调用wake_up_interruptible(&mousedev->wait)之后,因为为真,而继续往下执行.
if(retval)
returnretval;

if(!mousedev->exist)
return-ENODEV;

spin_lock_irq(&client->packet_lock);//禁止中断

if(!client->buffer&&client->ready){
mousedev_packet(client,client->ps2);
client->buffer=client->bufsiz;
}

if(count>client->buffer)
count=client->buffer;

memcpy(data,client->ps2+client->bufsiz-client->buffer,count);
//所以从这里可以看出,client->bufsiz为ps2[]数组有效数据索引的上限值,
//client->buffer为ps2[]数组索引的下限值
client->buffer-=count;//这样之后,再次执行read时,将会接续该buffer偏移位置继续读取.

spin_unlock_irq(&client->packet_lock);//打开中断

if(copy_to_user(buffer,data,count))//拷贝到用户空间
return-EFAULT;

returncount;
}

对于mouse和keyboard来说poll方法是同时处理多项输入的相当高效的信息处理方法,应用程序可以使用select或者poll甚至epoll来等待多个事件的发生,比如同时等待mouse和key的发生,然后来统一处理【gliethttp.Leith】.
staticunsignedintmousedev_poll(structfile*file,poll_table*wait)
{
structmousedev_client*client=file->private_data;
structmousedev*mousedev=client->mousedev;

poll_wait(file,&mousedev->wait,wait);
return((client->ready||client->buffer)?(POLLIN|POLLRDNORM):0)|
(mousedev->exist?0:(POLLHUP|POLLERR));
}
以上鼠标input事件和键盘的input时间基本一致,最后都是调用input_report_rel()、input_report_key(),不同的是mousedev_event只有当调用input_sync才会发生向client的数据拷贝动作,而键盘的evdev_event的事件处理函数不管是什么信息都会执行如下遍历:
list_for_each_entry_rcu(client,&evdev->client_list,node)
evdev_pass_event(client,&event);
来完成向每个client数据buffer拷贝数据【gliethttp.Leith】.

更多相关文章

  1. Linux PCI/PCI-E设备配置空间读取与修改
  2. mysql创建任务事件
  3. C#的委托事件在winform窗体中实现传值备忘
  4. Android中RecyclerView的item中控件的点击事件添加删除一行、上
  5. android 屏幕触摸事件及处理机制解读
  6. Android事件分发机制(下)
  7. Android 通过读取本地Arp表获取当前局域网内其他设备信息
  8. 关于利用android-serialport-api实现在安卓设备上进行串口通信,附
  9. Android 事件输入系统整体框架

随机推荐

  1. android 判断横竖屏的方法
  2. android之Matrix
  3. Android中的Message类以及Java对象池的实
  4. 2011.09.07——— android zxing 条形码
  5. android 9.0 增加实体按键的按键声音,以及
  6. android 创建快捷方式图标
  7. android源码分享之蓝虫火车票余票查询源
  8. android与webservice通信之中文乱码问题!
  9. android通过shape.xml制作渐变背景
  10. Android listview中使用button解决方法