ARMLinux内核Input输入子系统浅析.docx
《ARMLinux内核Input输入子系统浅析.docx》由会员分享,可在线阅读,更多相关《ARMLinux内核Input输入子系统浅析.docx(37页珍藏版)》请在冰点文库上搜索。
![ARMLinux内核Input输入子系统浅析.docx](https://file1.bingdoc.com/fileroot1/2023-5/23/69ab213c-6855-4525-a8fd-766475e416ea/69ab213c-6855-4525-a8fd-766475e416ea1.gif)
ARMLinux内核Input输入子系统浅析
--以触摸屏驱动为例
第一章、了解linuxinput子系统
Linux输入设备总类繁杂,常见的包括有按键、键盘、触摸屏、鼠标、摇杆等等,他们本身就是字符设备,而linux内核将这些设备的共同性抽象出来,简化驱动开发建立了一个input子系统。
子系统共分为三层,如图1所示。
图1 input输入子系统
驱动层和硬件相关,直接捕捉和获取硬件设备的数据信息等(包括触摸屏被按下、按下位置、鼠标移动、键盘按下等等),然后将数据信息报告到核心层。
核心层负责连接驱动层和事件处理层,设备驱动(devicedriver)和处理程序(handler)的注册需要通过核心层来完成,核心层接收来自驱动层的数据信息,并将数据信息选择对应的handler去处理,最终handler将数据复制到用户空间。
先了解三个定义在/linux/input.h下重要的结构体input_dev、input_handler、input_handle。
structinput_dev{
void*private;
constchar*name;
constchar*phys;
constchar*uniq;
structinput_idid; //与input_handler匹配用的id
unsignedlongevbit[NBITS(EV_MAX)]; //设备支持的事件类型
unsignedlongkeybit[NBITS(KEY_MAX)]; //按键事件支持的子事件类型
unsignedlongrelbit[NBITS(REL_MAX)];
unsignedlongabsbit[NBITS(ABS_MAX)]; //绝对坐标事件支持的子事件类型
unsignedlongmscbit[NBITS(MSC_MAX)];
unsignedlongledbit[NBITS(LED_MAX)];
unsignedlongsndbit[NBITS(SND_MAX)];
unsignedlongffbit[NBITS(FF_MAX)];
unsigned long swbit[NBITS(SW_MAX)];
intff_effects_max;
unsignedintkeycodemax;
unsignedintkeycodesize;
void*keycode;
unsignedintrepeat_key;
structtimer_listtimer;
structpt_regs*regs;
intstate;
intsync;
intabs[ABS_MAX+1];
intrep[REP_MAX+1];
unsignedlongkey[NBITS(KEY_MAX)];
unsignedlongled[NBITS(LED_MAX)];
unsignedlongsnd[NBITS(SND_MAX)];
unsigned long sw[NBITS(SW_MAX)];
intabsmax[ABS_MAX+1]; //绝对坐标事件的最大键值
intabsmin[ABS_MAX+1]; //绝对坐标事件的最小键值
intabsfuzz[ABS_MAX+1];
intabsflat[ABS_MAX+1];
int(*open)(structinput_dev*dev);
void(*close)(structinput_dev*dev);
int(*accept)(structinput_dev*dev,structfile*file);
int(*flush)(structinput_dev*dev,structfile*file);
int(*event)(structinput_dev*dev,unsignedinttype,unsignedintcode,intvalue);
int(*upload_effect)(structinput_dev*dev,structff_effect*effect);
int(*erase_effect)(structinput_dev*dev,inteffect_id);
structinput_handle*grab; //当前占有该设备的handle
structmutexmutex; /*serializes open andcloseoperations*/
unsignedintusers; //打开该设备的用户量
structclass_devicecdev;
structdevice*dev; /*willberemovedsoon*/
intdynalloc; /*temporarily*/
structlist_head h_list; //该链表头用于链接该设备所关联的input_handle
structlist_head node; //该链表头用于将设备链接到input_dev_list
};
Input_dev是一个很强大的结构体,它把所有的input设备(触摸屏、键盘、鼠标等)的信息都考虑到了,对于触摸屏来说只用到它里面的一部分而已,尤其是加粗的部分,注意该结构体中最后两行定义的两个list_head结构体,list_head在/linux/list.h中有定义,深入跟踪
structlist_head{
structlist_head*next,*prev;
};
该结构体内部并没有定义数据而只定义了两个指向本身结构体的指针,预先说明一下,所有的input device在注册后会加入一个input_dev_list(输入设备链表),所有的eventhandler在注册后会加入一个input_handler_list(输入处理程序链表),这里的list_head主要的作用是作为input_dev_list和input_handler_list的一个节点来保存地址。
Input_dev_list和input_handler_list之间的对应关系由input_handle结构体桥接,具体后面说明。
structinput_handler{
void*private;
void(*event)(structinput_handle*handle,unsignedinttype,unsignedintcode,intvalue);
structinput_handle*(*connect)(structinput_handler*handler,structinput_dev*dev,structinput_device_id*id);
void(*disconnect)(structinput_handle*handle);
conststructfile_operations*fops; //提供给用户对设备操作的函数指针
intminor;
char*name;
structinput_device_id*id_table; //与input_dev匹配用的id
structinput_device_id*blacklist; //标记的黑名单
structlist_head h_list; //用于链接和该handler相关的handle
structlist_head node; //用于将该handler链入input_handler_list
};
input_handler顾名思义,它是用来处理input_dev的一个结构体,相关的处理函数在结构里内部都有定义,最后两行定义的list_head结构体作用同input_dev所定义的一样,这里不再说明。
注:
input_device_id结构体在/linux/mod_devicetable.h中有定义
structinput_handle{
void*private;
int open; //记录设备打开次数
char*name;
structinput_dev*dev; //指向所属的input_dev
structinput_handler*handler; //指向所属的input_handler
structlist_head d_node; //用于链入所指向的input_dev的handle链表
structlist_head h_node; //用于链入所指向的input_handler的handle链表
};
可以看到input_handle中拥有指向input_dev和input_handler的指针,即input_handle是用来关联input_dev和input_handler。
为什么用input_handle来关联input_dev和input_handler而不将input_dev和input_handler直接对应呢?
因为一个device可以对应多个handler,而一个handler也可处理多个device。
就如一个触摸屏设备可以对应eventhandler也可以对应tsevehandler。
input_dev、input_handler、input_handle的关系如下图2所示。
图2 input_dev,input_handler,input_handle关系图
第二章、input device的注册
Input device的注册实际上仅仅只有几行代码,因为在input.c中已经将大量的代码封装好了,主需要调用几个关键的函数就能完成对inputdevice的注册。
在xxx_ts.c中预先定义全局变量structinput_dev tsdev;然后进入到初始化函数
staticint__initxxx_probe(structplatform_device*pdev)
{
…
if(!
(tsdev=input_allocate_device()))
{
printk(KERN_ERR"tsdev:
notenoughmemory\n");
err=-ENOMEM;
gotofail;
}
…
tsdev->name="xxxTouchScreen"; //xxx为芯片型号
tsdev->phys="xxx/event0";
tsdev->id.bustype=BUS_HOST; //设备id,用于匹配handler的id
tsdev->id.vendor =0x0005;
tsdev->id.product=0x0001;
tsdev->id.version=0x0100;
tsdev->open =xxx_open;
tsdev->close =xxx_close;
tsdev->evbit[0]=BIT(EV_KEY)|BIT(EV_ABS)|BIT(EV_SYN); //设置支持的事件类型
tsdev->keybit[LONG(BTN_TOUCH)]=BIT(BTN_TOUCH);
input_set_abs_params(tsdev,ABS_X,0,0x400,0,0); //限定绝对坐标X的取值范围
input_set_abs_params(tsdev,ABS_Y,0,0x400,0,0); //同上
input_set_abs_params(tsdev,ABS_PRESSURE,0,1000,0,0); //触摸屏压力值范围
…
If(input_register_device(tsdev)==error) //注册设备
gotofail;
…
fail:
input_free_device(tsdev);
printk(“tsprobefailed\n”);
returnerr;
}
先看该函数中的tsdev=input_allocate_device(),深入最终,该函数在input.c中有定义
structinput_dev*input_allocate_device(void)
{
structinput_dev*dev;
dev=kzalloc(sizeof(structinput_dev),GFP_KERNEL);
if(dev){
dev->dynalloc=1;
dev->cdev.class=&input_class;
class_device_initialize(&dev->cdev);
INIT_LIST_HEAD(&dev->h_list);
INIT_LIST_HEAD(&dev->node);
}
returndev;
}
学过C语言应该都知道malloc函数(开辟内存空间),而这里的kzalloc也类似于C语言中的malloc一样,是linux内核空间分配内存函数,后面的GFP_KERNEL标志意为常规的内存分配,更多的分配标志可参阅先关资料(我也不懂)。
再回到前面的函数中来,接着后面的代码为对tsdev结构体中的成员进行赋值初始化,赋值完成后就要开始进入主题:
注册设备了,进入到函数input_register_device(tsdev),该函数在input.c中有定义。
intinput_register_device(structinput_dev*dev)
{
staticatomic_tinput_no=ATOMIC_INIT(0); //定义原子变量,禁止线程并发访问
structinput_handle*handle; //定义一些变量备后文使用
structinput_handler*handler;
structinput_device_id*id;
constchar*path;
interror;
if(!
dev->dynalloc){
printk(KERN_WARNING"input:
device%sisstaticallyallocated,willnotregister\n"
"Pleaseconverttoinput_allocate_device()orcontact ***********************\n",
dev->name?
dev->name:
"");
return-EINVAL;
}
mutex_init(&dev->mutex); //互斥锁初始化,防止临界区代码被并发访问
set_bit(EV_SYN,dev->evbit); //设置支持同步事件,input设备全部默认支持同步事件
/*
*Ifdelayandperiodarepre-setbythedriver,thenautorepeating
*ishandledbythedriveritselfandwedon'tdoitininput.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;
}
INIT_LIST_HEAD(&dev->h_list); //初始化需要关联的handle链表头
list_add_tail(&dev->node,&input_dev_list); //将设备添加到input_dev_list中
dev->cdev.class=&input_class;
snprintf(dev->cdev.class_id,sizeof(dev->cdev.class_id),
"input%ld",(unsigned long)atomic_inc_return(&input_no)-1);
error=class_device_add(&dev->cdev);
if(error)
returnerror;
error=sysfs_create_group(&dev->cdev.kobj,&input_dev_attr_group);
if(error)
gotofail1;
error=sysfs_create_group(&dev->cdev.kobj,&input_dev_id_attr_group);
if(error)
gotofail2;
error=sysfs_create_group(&dev->cdev.kobj,&input_dev_caps_attr_group);
if(error)
gotofail3;
__module_get(THIS_MODULE);
path=kobject_get_path(&dev->cdev.kobj,GFP_KERNEL);
printk(KERN_INFO"input:
%sas%s\n",
dev->name?
dev->name:
"Unspecifieddevice",path?
path:
"N/A");
kfree(path);
/*** 遍历input_handler_list上全部的handler,寻找与该设备匹配的handler ***/
list_for_each_entry(handler,&input_handler_list,node)
if(!
handler->blacklist||!
input_match_device(handler->blacklist,dev))
if((id=input_match_device(handler->id_table,dev)))
if((handle=handler->connect(handler,dev,id)))
input_link_handle(handle);
input_wakeup_procfs_readers();
return0;
fail3:
sysfs_remove_group(&dev->cdev.kobj,&input_dev_id_attr_group);
fail2:
sysfs_remove_group(&dev->cdev.kobj,&input_dev_attr_group);
fail1:
class_device_del(&dev->cdev);
returnerror;
}
先看函数中前面代码加粗的部分mutex_init(&dev->mutex),与互斥锁相关的东西(我也不太懂),set_bit(EV_SYN,dev->evbit)设置支持同步事件,linux的input子系统默认