作者:Sam (甄峰) [email protected]
在最近的项目中,Sam需要将设备数据解析后以系统键盘鼠标的消息发送出去。选用UInput(关于UInputDriver以及编译,( http://blog.sina.com.cn/s/blog_602f87700100liyk.html)。
当uinput driver已经insmod, 且node 已经建立后。即可使用它们传递系统输入设备消息。
1. 打开UInputDevice:
应用程序:
dev 为 UInput Node名:通常为/dev/uinput。
open(dev, O_WRONLY | O_NDELAY);
此时,在Kernel 层,对应的动作为:
static int uinput_open(struct inode *inode, struct file*file)
参数inode对应的是 主设备为10,子设备为223的node(即位用户态的dev)
参数file对应打开的文件。
动作:
创建了newdev-- uinput_device结构。
newdev->state =UIST_NEW_DEVICE;
file->private_data = newdev;
2. 设置UInputDevice:
ioctl(fd, UI_SET_EVBIT, EV_KEY);
此时,在Kernel 层,对应的动作为:
static long uinput_ioctl(struct file *file, unsigned int cmd,unsigned long arg)
参数file对应打开的文件。
参数cmd 对应用户态ioctl参数2。UI_SET_EVBIT
参数arg对应用户态ioctl参数3。EV_KEY
动作:
2.1 将driver参数传递过来。
udev = file->private_data;
udev->dev 是个input_dev类型数据。 此时,它未初始化。
如果udev->dev为空,则使用uinput_allocate_device(udev);申请input_dev结构
具体到CMD=UI_SET_EVBIT
uinput_set_bit(arg, evbit, EV_MAX);
首先判断newdev->state为UIST_CREATED,则返回错误码。
这就说明:设置bit,需要在create input device之前。
具体动作为:udev->dev->evbit设为EV_KEY.
注意:此处input device的evbit:
一个是evbit.表示设备所支持的动作.
#defineEV_KEY 0x01 // 按键
#defineEV_REL 0x02 // 释放
3.继续设置 Device:
ret = ioctl(fd, UI_SET_RELBIT, REL_X); //鼠标
ret = ioctl(fd, UI_SET_RELBIT, REL_Y);
ret = ioctl(fd, UI_SET_EVBIT, EV_ABS);
ret = ioctl(fd, UI_SET_ABSBIT, ABS_X);
ret = ioctl(fd, UI_SET_ABSBIT, ABS_Y);
ret = ioctl(fd, UI_SET_ABSBIT, ABS_PRESSURE);
同上。设置了Keybit等。
这里就是设置了Input Device关心或者说会产生的消息。
4. 写入设备:
struct uinput_user_dev uinput;
uinput.id.version = 4;
uinput.id.bustype = BUS_USB;
uinput.absmin[ABS_X] = 0;
uinput.absmax[ABS_X] = 65535; //sam 把屏幕设为0-65535
uinput.absmin[ABS_Y] = 0;
uinput.absmax[ABS_Y] = 65535;
uinput.absmin[ABS_PRESSURE] = 0;
uinput.absmax[ABS_PRESSURE] = 0xfff;
ret = write(fd, &uinput, sizeof(uinput));
此时,在Kernel 层,对应的动作为:
此时Device status为UIST_NEW_DEVICE
并将udev->dev 这个input device 具体化。初始化该input_dev。
之后,改变状态:
udev->state =UIST_SETUP_COMPLETE;
5.创建InputDevice:
注意,此处是创建了Input Device。而不是UInput Device。
ioctl(fd, UI_DEV_CREATE);
此时,在Kernel 层,对应的动作为:
input_register_device(udev->dev);//向子系统注册该设备,之后中断时input_event()向子系统报告事件
udev->state = UIST_CREATED;
6. 向InputDevice发送Event:
struct input_event event = {0};
gettimeofday(&event.time,NULL);
event.type = EV_KEY;
event.code = key;
event.value = press ? 1:0;
write(fd, &event,sizeof(event));
此时,在Kernel 层,对应的动作为:
static ssize_t uinput_write(struct file *file, const char __user*buffer, size_t count, loff_t *ppos)
因为此时state为UIST_CREATED
input_event(udev->dev, ev.type, ev.code,ev.value);
发送event.
总结:
使用UInput的步骤为:
1. 打开设备。
2. 使用ioctl() 配置设备。
3. 使用write() 将input device信息设置好。
4. 使用ioctl(UI_DEV_CREATE)创建Input Device。(即使用write设置的)
5. 再使用write() 写入event.
UInput添加的Input Device在/proc的反应:
#cat /proc/bus/input/device
I: Bus=0003 Vendor=0000 Product=0000 Version=0004
N: Name="uinput"
P: Phys=
S: Sysfs=/class/input/input6
H: Handlers=event1 mouse1
B: EV=f
B: KEY=400 0 670000 ffff ffffffff ffffffff ffffffff ffffffffffffffff ffffffff ffffffff
B: REL=3
B: ABS=1000003
解释如下:
Bus=0003 Vendor=0000 Product=0000Version=0004
这是在第一次write时设置的:
uinp.id.version = 4;
uinp.id.bustype = BUS_USB;
struct input_id {
__u16 bustype;
__u16 vendor;
__u16 product;
__u16 version;
};
EV=f
后记:
后来的工作中,Sam又看到Hi3716C中,如何使用Driver将红外遥控器模拟成一个Keyboard.
http://blog.sina.com.cn/s/blog_602f877001019wtx.html
其实原理非常类似. 都需要指出支持什么Type的Event.
也要指出每种Type的Event中又分别支持什么具体值.
然后才是创建Device.
作者:Sam (甄峰) [email protected]
Sam在开发中需要在用户态程序中模拟系统Keyboard,Mouse event. 开始想用之前在2.4Kernel中开发的一个Virtual InputDriver,在改写为2.6Kernel模式后,连续尝试了2个嵌入式平台,发现竟然都缺乏必要的内核符号。突然想起之前在哪看到过InputUser level driver这样的东西。赶快问了问Google老师。发现它就是UInput。(发现TCC8900所使用的IRRemote Controller Driver与Sam很多年前在S3C2440A上开发的IR Remote ControllerDriver做法几乎完全一致,嘿嘿)
赶快以Module形式编译uinput.
先察看drivers/input/misc/Kconfig,看到INPUT_UINPUT(User level driversupport)前提为INPUT_MISC。
于是在make menuconfig中
Device Drivers --->
Input device support --->
[*] Miscellaneousdevices --->
<M> User level driver support
呵呵,现在才理解misc是各种其它非标准设备的意思。
#make
将其编译为uinput.ko
读uinput.c之前,最好先读 driver/char/misc.c
解析misc.c
1. init函数
subsys_initcall(misc_init);
insmod misc时,会调用misc_init()
misc_init()使用比较老的方式创建字符设备driver:
register_chrdev(MISC_MAJOR,"misc",&misc_fops)
主设备号为:MISC_JAJOR=10
driver名字为 misc, 在/proc/device中出现。
driver处理程序为misc_fops.
2. driver处理程序:
static const struct file_operations misc_fops = {
.owner =THIS_MODULE,
.open =misc_open,
};
也就是说:它只赋值了open--misc_open()
换句话说,当用户在使用系统调用open主设备号为10的device时,kernel会最终调用misc_open().
misc_open():
它首先察看全局链表misc_list成员中是否有次设备号与open()参数inode的次设备号相同。
如果有,则将链表中c->fops取出,并用它取代参数2 file中的fops.
并调用c->fops->open(inode, file)
注意:这里非常关键,因为misc是一系列driver的组合。各个driver所做工作完全不同,他们之间使用次设备号区分。
但Kernel只关心主设备号,也就是说,它只会把主设备号对应的file给对应出来。所以在open()时,需要使用次设备号作标记,替换对应driver的file(open()的参数2)的fops.
3.int misc_register(structmiscdevice * misc)
查全局链表misc_list内容。如果需要注册的参数misc的子设备号在链表misc_list中已经存在。则返回busy.
如果注册的参数misc的子设备号为MISC_DYNAMIC_MINOR(255,动态子设备号)。则从misc全局子设备号(misc_minors)中取出一个未用的来使用。并将misc_minors中对应子设备号标记为已用。
建立dev_t (主,次设备号)
并经参数misc加入到misc_list中去。
这样,就很清楚misc的处理了。先使用misc_register()注册一个次设备号对应driver.并将其放入misc_list链表。当用户使用系统调用open主设备号为10的device时,则在misc_list寻找device(也就是node)对应的次设备号fops对应file的对应fops.
解析uinput.c
1. init函数
module_init(uinput_init);
insmod uinput时,会调用uinput_init()
它调用misc_register(&uinput_misc);
请注意参数uinput_misc。
static struct miscdevice uinput_misc = {
.fops =&uinput_fops,
.minor =UINPUT_MINOR, (223)
.name =UINPUT_NAME, (uinput)
};
注册后,当用户使用系统调用open主设备号为10,次设备号为223的node时,则使用uinput_fops取代file->fops.
且同时调用fops->open()
2. open函数:
创建结构体uinput_device 实体,并将file->private_data指向该实体。
struct uinput_device {
struct input_dev *dev;
structmutex mutex;
enum uinput_state state;
wait_queue_head_t waitq;
unsignedchar ready;
unsignedchar head;
unsignedchar tail;
structinput_event buff[UINPUT_BUFFER_SIZE];
structuinput_request *requests[UINPUT_NUM_REQUESTS];
wait_queue_head_t requests_waitq;
spinlock_t requests_lock;
};