linux中的input设备(转)
转自:http://blog.csdn.net/lmm670/article/details/6080998
用过linux的哥们都知道,linux所有的设备都是以文件的形式实现的,要访问一个设备,我们只需要以open、read、write的形式对设备的进行操作就可以了。在linux系统的/dev目录下,罗列了当前系统支持的所有设备。运行 ls /dev一下,着实吓了一大跳,
[root@localhost ~]# ls /dev
adsp full midi ram9 tty15 tty42 ttyS3
agpgart fuse mixer ramdisk tty16 tty43 urandom
audio hpet net random tty17 tty44 usbdev1.1_ep00
bsg hvc0 null root tty18 tty45 usbdev1.1_ep81
bus hvc1 nvram rtc tty19 tty46 usbmon0
cdrom hvc2 oldmem scd0 tty2 tty47 usbmon1
console hvc3 parport0 sda tty20 tty48 vcs
core hvc4 parport1 sda1 tty21 tty49 vcs1
disk hvc5 parport2 sda2 tty22 tty5 vcs2
dmmidi hvc6 parport3 sda3 tty23 tty50 vcs3
dsp hvc7 port sequencer tty24 tty51 vcs4
fd initctl ppp sequencer2 tty25 tty52 vcs5
fd0 input ptmx sg0 tty26 tty53 vcs6
fd0u1040 kmsg pts sg1 tty27 tty54 vcs7
fd0u1120 log ram shm tty28 tty55 vcs8
fd0u1440 loop0 ram0 snapshot tty29 tty56 vcsa
fd0u1600 loop1 ram1 snd tty3 tty57 vcsa1
fd0u1680 loop2 ram10 sr0 tty30 tty58 vcsa2
fd0u1722 loop3 ram11 stderr tty31 tty59 vcsa3
fd0u1743 loop4 ram12 stdin tty32 tty6 vcsa4
fd0u1760 loop5 ram13 stdout tty33 tty60 vcsa5
fd0u1840 loop6 ram14 systty tty34 tty61 vcsa6
fd0u1920 loop7 ram15 tty tty35 tty62 vcsa7
fd0u360 lp0 ram2 tty0 tty36 tty63 vcsa8
fd0u720 lp1 ram3 tty1 tty37 tty7 X0R
fd0u800 lp2 ram4 tty10 tty38 tty8 XOR
fd0u820 lp3 ram5 tty11 tty39 tty9 zero
fd0u830 MAKEDEV ram6 tty12 tty4 ttyS0
floppy mapper ram7 tty13 tty40 ttyS1
floppy-fd0 mem ram8 tty14 tty41 ttyS2
这么多设备那么管理起来是不是很麻烦,linux内核的开发者智商自然在你我之上,他们把所有的这些设备归为三大类,即平时我们熟悉的字符设备、块设备、网络设备。运行 ls –l /dev(这里我只取一部分显示信息)
crw-rw----+ 1 root root 14, 12 12-16 00:57 adsp
crw------- 1 root root 10, 175 12-16 00:57 agpgart
crw-rw----+ 1 root root 14, 4 12-16 00:57 audio
drwxr-xr-x 2 root root 80 12-16 00:57 bsg
drwxr-xr-x 3 root root 60 12-16 00:57 bus
lrwxrwxrwx 1 root root 3 12-16 00:57 cdrom -> sr0
crw------- 1 lmm670 root 5, 1 12-16 00:57 console
lrwxrwxrwx 1 root root 11 12-16 00:57 core -> /proc/kcore
drwxr-xr-x 5 root root 100 12-16 00:57 disk
crw-rw---- 1 root root 14, 9 12-16 00:57 dmmidi
crw-rw----+ 1 root root 14, 3 12-16 00:57 dsp
lrwxrwxrwx 1 root root 13 12-16 00:57 fd -> /proc/self/fd
brw-r----- 1 root floppy 2, 0 12-16 00:57 fd0
brw-r----- 1 root floppy 2, 84 12-16 00:57 fd0u1040
brw-r----- 1 root floppy 2, 88 12-16 00:57 fd0u1120
brw-r----- 1 root floppy 2, 28 12-16 00:57 fd0u1440
brw-r----- 1 root floppy 2, 124 12-16 00:57 fd0u1600
brw-r----- 1 root floppy 2, 44 12-16 00:57 fd0u1680
brw-r----- 1 root floppy 2, 60 12-16 00:57 fd0u1722
brw-r----- 1 root floppy 2, 76 12-16 00:57 fd0u1743
brw-r----- 1 root floppy 2, 96 12-16 00:57 fd0u1760
brw-r----- 1 root floppy 2, 116 12-16 00:57 fd0u1840
brw-r----- 1 root floppy 2, 100 12-16 00:57 fd0u1920
brw-r----- 1 root floppy 2, 12 12-16 00:57 fd0u360
brw-r----- 1 root floppy 2, 16 12-16 00:57 fd0u720
brw-r----- 1 root floppy 2, 120 12-16 00:57 fd0u800
brw-r----- 1 root floppy 2, 52 12-16 00:57 fd0u820
brw-r----- 1 root floppy 2, 68 12-16 00:57 fd0u830
lrwxrwxrwx 1 root root 3 12-16 00:57 floppy -> fd0
lrwxrwxrwx 1 root root 3 12-16 00:57 floppy-fd0 -> fd0
crw-rw-rw- 1 root root 1, 7 12-16 00:57 full
crw-rw---- 1 root fuse 10, 229 12-16 00:57 fuse
crw-rw---- 1 root root 10, 228 12-16 00:57 hpet
crw-rw---- 1 root uucp 229, 0 12-16 00:57 hvc0
crw-rw---- 1 root uucp 229, 1 12-16 00:57 hvc1
crw-rw---- 1 root uucp 229, 2 12-16 00:57 hvc2
crw-rw---- 1 root uucp 229, 3 12-16 00:57 hvc3
crw-rw---- 1 root uucp 229, 4 12-16 00:57 hvc4
crw-rw---- 1 root uucp 229, 5 12-16 00:57 hvc5
crw-rw---- 1 root uucp 229, 6 12-16 00:57 hvc6
crw-rw---- 1 root uucp 229, 7 12-16 00:57 hvc7
prw------- 1 root root 0 12-16 00:58 initctl
drwxr-xr-x 3 root root 200 12-16 00:57 input
crw-rw---- 1 root root 1, 11 12-16 00:57 kmsg
srw-rw-rw- 1 root root 0 12-16 00:58 log
brw-r----- 1 root disk 7, 0 12-16 00:57 loop0
大家可以看到,每一行的第一个字母,代表着此文件的类型。c表示字符设备,b表示块设备,s表示网络设备,细心的哥们会问,不是说只有三类设备吗,怎么还有其他类型开头的呢?比如d、l等等。对不起,这里讲的是文件类型,d表示是一个目录文件,l表示一个链接文件。至于这三者之间的区别,我就不在这啰嗦了,Google一下一大堆。我要强调的是,无论上面三个设备中的任何一种设备,要想在linux实现它的设备驱动,首先要对它进行一系列的初始化工作,然后需给它提供一个设备操作集合(或者更简单一点理解:接口函数),用来提供给我们的上层程序进行访问,比如open,read,write等等。要不然,我要你这个设备驱动干嘛。在字符设备驱动中,我们的操作集函数是这样的
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
};
网名为“卖血去上网”的兄弟要说了,写一个驱动接口函数怎么这么复杂,要提供这么多接口函数。其实不然,一般的设备驱动接口函数并不需要把上面的所有都实现,只需要实现里面的一些:比如我们这里:
static const struct file_operations evdev_fops = {
.owner = THIS_MODULE,
.read = evdev_read,
.write = evdev_write,
.poll = evdev_poll,
.open = evdev_open,
.release = evdev_release,
.unlocked_ioctl = evdev_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = evdev_ioctl_compat,
#endif
.fasync = evdev_fasync,
.flush = evdev_flush
};
兄弟们注意了,这个结构体就是前面那个结构体的实现,类似于c++中的类和对象。结构中若干个等式中,我们最终要实现的是右边的那些函数。这样做的目的大家都清楚:实现统一接口,增加程序的可移植性。上层代码每次open evdev这个设备的时候最终都会通过file_operations落实到我们的evdev_open函数。鲁迅先生曾说过:驱动代码写起来其实并不难,当接口函数多了就变难了。说了一大推好像被忽悠了,怎么没提到一点关于input设备的信息,关于input设备我现在只提一句,input设备的接口函数,linux内核已经为我们写好了。毕竟时代在进步,内核在更新,鲁先生的话也可以改一下了:设备驱动的实现本身很难,自从有了input设备子系统,就变得不难了(当然只针对input设备)。到底何谓input设备呢?
究竟何谓input设备,相信武汉跳蚤市场上卖宠物小狗的大妈都能一口答出来,你能不知道么?对,就是我们传说中的输入设备。说到输入设备,相信用过电脑的兄弟都不会陌生了,即按键、鼠标、键盘、等一系列需要我们用户“动手”产生信息,然后丢给我们聪明绝顶的pc来处理的设备。前面说了,linux内核input子系统中已经实现了input设备的接口函数,这使得我们工作量大大的减轻了。我们以akm8973芯片(用于智能手机指南针的主功能芯片,实际上就一电子罗盘)为例,来简单看一下写一个input设备我们需要做的工作。
首先,在驱动模块加载函数中申请一个input设备,并告知input子系统它支持哪些事件,如下所示:
akm->input_dev = input_allocate_device();
set_bit(EV_ABS, akm->input_dev->evbit);
input_set_abs_params(akm->input_dev, ABS_RX, 0, 23040, 0, 0);
input_set_abs_params(akm->input_dev, ABS_RY, -11520, 11520, 0, 0);
input_set_abs_params(akm->input_dev, ABS_RZ, -5760, 5760, 0, 0);
input_set_abs_params(akm->input_dev, ABS_THROTTLE, -30, 85, 0, 0);
input_set_abs_params(akm->input_dev, ABS_RUDDER, 0, 3, 0, 0);
input_set_abs_params(akm->input_dev, ABS_HAT0X, -2048, 2032, 0, 0);
input_set_abs_params(akm->input_dev, ABS_HAT0Y, -2048, 2032, 0, 0);
input_set_abs_params(akm->input_dev, ABS_BRAKE, -2048, 2032, 0, 0);
以上这些都是为让input子系统支持的某些参数而设置的,EV_ABS表示支持绝对值坐标,后面都是针对这些坐标的一些参数访问范围设置。至于为什么这样设置,我们继续往下走,到后面我们就明白了。
接着,在驱动模块函数中注册输入设备:
err = input_register_device(akm->input_dev);
然后,报告发生的一些事件以及对应的坐标。
input_report_abs(data->input_dev, ABS_RX, rbuf[0]);
input_report_abs(data->input_dev, ABS_RY, rbuf[1]);
input_report_abs(data->input_dev, ABS_RZ, rbuf[2]);
对应的三个方向的坐标值就被驱动记录下来了。
深入里面跟踪一下:
static inline void input_set_abs_params(struct input_dev *dev, int axis, int min, int max, int fuzz, int flat)
{
dev->absmin[axis] = min;
dev->absmax[axis] = max;
dev->absfuzz[axis] = fuzz;
dev->absflat[axis] = flat;
dev->absbit[BIT_WORD(axis)] |= BIT_MASK(axis);
}
这个函数用来干嘛的呢?这个留到以后讲,不过你得多个心眼,后面用得到的。
添加一个input设备,我们要做的工作就这些了。接下来我们就可以通过input内核子系统提供的接口函数来处理这些坐标值,把把他们传到用户空间。
看完这些,让我想起来了食堂门前那些招行的办信用卡活动。办一个信用卡送一个U盾。没办法,现在的社会什么事情都要收费,连上厕所也得给点小费,不给钱不让进,哪怕你当众尿裤子。所以更别说我们如此有技术含量的保护大家网上购物安全的U盾了。去银行办一个U盾,至少得花个50大洋以上。所以很多像我这样的哥们,毫不犹豫的办了一个信用卡。反正就填几张表,然后就可以免费获得一个u盾,多好啊。网名为“唐伯虎点蚊香”的兄弟马上发话了:“这不就和我们的input设备那个一样的吗,我们这些比较懒的家伙为了避免去完善那些复杂的设备接口函数集,所以干脆把它注册成一个input设备,所以你就得先申请它,注册它等一系列预备工作,(就如我们为了u盾而填的那些表格)做好这些之后,我们就实行鲁迅先生的拿来主义,直接使用input子系统的的接口函数”。
不过不是什么设备都可以注册成inpunt设备的。就好比一兄弟随便拿了一张纸,画一只小鸡,然后头上加个光圈,就号称是唐伯虎的名画“神鸟凤凰图”,然后递给招行的工作人员说“我表格填好了,给我来一个U盾”,人家会以为这哥们肯定刚从精神病院出来的。
从前一节来看,在linux内核中添加一个input设备变得很简单了。我们再也不必须去动手写那些该死的接口函数了。可是你有没有想过,是谁让我们的工作变得这么简单了呢?答案是linux内核中的input core。她总是那么痴情,默默地不求回报地为你做许许多多的事情,在你背后默默的支持你爱着你。是的,你所想到的大多数事情,我们的input core都已经为你做好。除了感动,我们还能说什么呢?(input core对应的实体在linux内核源码目录linux-2.6.29/drivers/input/input.c文件)
在正式接触我们可爱的input core之前,有必要了解一下几个重要的结构体,这几个结构体是我们这个故事的主体。
第一个数据结构 struct input_dev。悟性高的哥们马上就会想到,它就是我们input 设备在linux内核中的模拟,即里面记录了一个input设备的所有信息。定义于linux-2.6.29/include/linux/input.h中
struct input_dev {
const char *name;
const char *phys;
const char *uniq;
struct input_id id;
unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
unsigned long relbit[BITS_TO_LONGS(REL_CNT)];
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];
unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
unsigned long sndbit[BITS_TO_LONGS(SND_