现在的位置: 首页 > 综合 > 正文

闲聊linux中的input设备

2013年05月24日 ⁄ 综合 ⁄ 共 13503字 ⁄ 字号 评论关闭

 用过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_CNT)];

       unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];

       unsigned long swbit[BITS_TO_LONGS(SW_CNT)];

 

       unsigned int keycodemax;

       unsigned int keycodesize;

       void *keycode;

       int (*setkeycode)(struct input_dev *dev, int scancode, int keycode);

       int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode);

 

       struct ff_device *ff;

 

       unsigned int repeat_key;

       struct timer_list timer;

 

       int sync;

 

       int abs[ABS_MAX + 1];

       int rep[REP_MAX + 1];

 

       unsigned long key[BITS_TO_LONGS(KEY_CNT)];

       unsigned long led[BITS_TO_LONGS(LED_CNT)];

       unsigned long snd[BITS_TO_LONGS(SND_CNT)];

       unsigned long sw[BITS_TO_LONGS(SW_CNT)];

 

       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 (*flush)(struct input_dev *dev, struct file *file);

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

 

       struct input_handle *grab;

 

       spinlock_t event_lock;

       struct mutex mutex;

 

       unsigned int users;

       int going_away;

 

       struct device dev;

 

       struct list_head  h_list;

       struct list_head  node;

};
复制代码

 

很强大的一个结构体,因为她把所有的input设备的信息都考虑到了,真的是很无私。不过对于我们的akm驱动来说只需要关注几个小细节,结构中的加粗部分。unsigned longevbit[BITS_TO_LONGS(EV_CNT)]表示此input设备支持的事件,比如前面的第二节中的set_bit(EV_ABS, akm->input_dev->evbit)设置input_dev->evbit中的相应位让它支持绝对值坐标。类似的还有以下这些事件:EV_KEY -按键, EV_REL -相对坐标EV_ABS -绝对坐标,EV_LED -  LED,EV_FF- 力反馈。unsigned
long absbit[BITS_TO_LONGS(ABS_CNT)];设置相应的位以支持某一类绝对值坐标。比如第二节中的input_set_abs_params(akm->input_dev, ABS_RX, 0, 23040, 0, 0);它的函数体如下:

复制代码
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);

}
复制代码

 

表示支持绝对值x坐标,并设置它在坐标系中的最大值和最小值,以及干扰值和平焊位置等。

struct list_head h_list;表示的是和该设备相关的所有input handle的结构体链表(input handle为何物下文马上会讲到)。struct list_head node;所有input设备组成的链表结构(后面将会知道它对应于input_dev_list)。

 

Ok 马上进入第二个结构体struct input_handler(还是来自linux-2.6.29/include/linux/input.h)

复制代码
struct input_handler {

 

       void *private;

 

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

       int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);

       void (*disconnect)(struct input_handle *handle);

       void (*start)(struct input_handle *handle);

 

       const struct file_operations *fops;

       int minor;

       const char *name;

 

       const struct input_device_id *id_table;

       const struct input_device_id *blacklist;

 

       struct list_head  h_list;

       struct list_head  node;

};
复制代码

 

顾名思义:用来处理input设备的一个结构体。struct list_head h_list表示的是和该设备相关的所有input handle的结构体链表和前面那个一样;struct list_head       node所有input_handle组成的结构体连链表(后面将会知道它对应于input_handler_list)。每一个input设备在注册时,他都会遍历input_handler_list链表上的每一个input_handler,去找寻他心中的那个她,同理每一个input_handler在注册时,他也会去input_dev_list上找寻那个属于他的她。有时候事情往往不会那么尽如人意,当input_handler还没出生时,你这个input_dev就一直在那等吧,等到天荒地老,等到海枯石烂。最后来一句,我等到花儿也谢了,你丫到底还来不来啊。注意这里的input_handler和input_dev并不是一一对应的关系,有时一个input_handler对应好几个input_dev。于是乎,作为看代码的我就在想,linux内核开发者的思想怎么这么不单纯呢,这不明摆着教育我们搞一夫多妻制吗,不过管怎样,还是得记住公司的企业文化,本分点。如果你丫说你同时拥有两个马子,我将会无情的向你抛出那句话:“出来混,迟早要还的!”。

前面多次提到那个input_handle(注意区别input_handler),她到底是何方神圣。好吧,就让我们来一层一层揭开她那神秘的面纱,当你第一次看到她完完全全展现在你面前时,那时候你的满足感和兴奋度和她的害羞度是成正比的。

同样来自linux-2.6.29/include/linux/input.h

复制代码
struct input_handle {

 

       void *private;

 

       int open;

       const char *name;

 

       struct input_dev *dev;

       struct input_handler *handler;

 

       struct list_head  d_node;

       struct list_head  h_node;

};
复制代码

 

怎么啦?是不是很失望,原来就这么回事啊。嗯,没错,兄弟,就这么回事。人往往都这样,得到了某样东西,想想觉得就那么回事,没得到呢,那叫一个好奇,那叫一个盼望。好了既然看到她的庐山真面目了,就坦然面对她,作为一个负责的男人,我还是来好好研究一下。

Input_Handle其实也好理解,它就是input_dev和 input_handler粘合剂,通过Input_Handle这么一搅和,input_dev就和 input_handler发生了点关系,至于什么样的关系我们后文将会知道的一清二楚。struct input_dev *dev对应的input设备。struct input_handler *handler对应该设备的handler。struct list_head d_node和struct list_head h_node则分别关联上前面两个结构体中的struct
list_head h_list。

 

对应input core,前面我一直在夸她的好,对于一个大家都不认识的家伙,我这样说她,是不是显的特虚,好了,为了证明她并不是那么的虚,我不得不拿出前面第二节中出现过的两行代码来看看:

akm->input_dev = input_allocate_device();

err = input_register_device(akm->input_dev);

没有错,这正是我们要把akm实现为一个input 设备的仅有的几行代码中的两行。

第一行,申请一个input设备:在内核中分配相应的内存空间,并初始化它。

第二行,把这个input设备注册到linux内核中,从此这个设备在内核中生根发芽,快乐幸福的和他的handler过着属于自己的小日子(虽然handler不一定属于她一个人,不过她不在乎)。

作为一个男人,我还是得负责任为我们的input core说明一下,input_allocate_device()和input_register_device();都来自我们的 input core。现在知道她的伟大了吧。你看看,我们写一个input设备驱动本来就那么几行代码,而这仅有的几行代码中还调用了来自内核的函数。我不得不说,input core ,你真给力。

好了,我们先来研究一下第一个函数 input_allocate_device()。(linux内核源码目录linux-2.6.29/drivers/input/input.c文件中)

复制代码
struct input_dev *input_allocate_device(void)

{

1 struct input_dev *dev;

2 dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);

3 if (dev) {

4 dev->dev.type = &input_dev_type;

5 dev->dev.class = &input_class;

6 device_initialize(&dev->dev);

7 mutex_init(&dev->mutex);

8 spin_lock_init(&dev->event_lock);

9 INIT_LIST_HEAD(&dev->h_list);

10 INIT_LIST_HEAD(&dev->node);

抱歉!评论已关闭.