抑郁症健康,内容丰富有趣,生活中的好帮手!
抑郁症健康 > 八 INPUT子系统和内核自带的GPIO按键驱动

八 INPUT子系统和内核自带的GPIO按键驱动

时间:2021-12-13 03:47:32

相关推荐

Input子系统驱动框架 = 设备层 + 核心层 + 事件处理层

其中,设备层部分的代码跟具体的输入设备相关,由驱动工程师来具体实现,负责监测并上报具体的输入事件。核心层起承上启下的作用,接受设备层上传的输入事件,然后转发给事件处理层。事件处理层则处理核心层上报的输入事件,负责字符设备驱动那一套,对用户空间提供访问接口。系统框架图如下:

Input子系统的实现也借鉴了总线技术,核心层作为联系设备层和事件处理层的桥梁,可以将它类比为一条总线。总线下面有一条设备链表和事件处理链表。调用input_register_device()向核心层注册输入设备时,把input_dev挂到设备链表下;调用input_register_handler()向核心层注册事件处理handler时,把input_handler挂到事件处理链表下。

注册输入设备或事件处理handler时,设备链表和事件处理链表会一一进行匹配,如果设备和事件处理handler匹配上,则input_handler里面的connet函数会被调用。在connet函数里面,匹配上的双方被放入一个input_handle的结构体里面(.dev.handler成员),并且该input_handle结构体被分别放入input_devinput_handlerh_list链表里里面,这样通过input_dev可以找到与之匹配的事件处理handler,通过input_handler也可以找到与之匹配的设备。除此之外,connet函数还向内核注册一个字符设备,为用户提供驱动访问的接口。

当我们调用input_event()来上报事件时,该函数就通过input_dev来找到对应的input_handler,按照优先级顺序选择调用其中的filter,events,event方法来处理数据。

使用Input子系统编写输入设备驱动的思路:

由于核心层和事件处理层内核都已经做好了,所以我们只需要关心设备层

1)构造设备树

2)借助platform总线,在probe函数里面构造struct input_dev, 然后调用input_register_device(),将设备注册到Input子系统

3] 调用input_register_handler(),将事件处理hanler注册到Input子系统(这一步不是必须的,因为内核里面有一个通用的事件处理handler(位于evdev.c),这个handler在内核启动的时候已经注册,可以匹配所有的input_dev。也就是说,对每个input_dev上报的事件,至少都会有一个handler进行处理。)

如何调试:

使用ls /dev/input/*命令能查看有哪些输入设备节点。

使用cat /proc/bus/input/devices命令能查看输入设备节点的详细信息,各个字母表示含义如下:

I 对应input_dev结构体里的id成员N 输入设备名称P 输入设备物理信息,比如input/ts表示触摸屏H 表示使用的核心事件层类型B 表示位图信息S 表示现在sysfs中的位置

接口

分配并初始化/释放一个input_dev结构体

struct input_dev *devm_input_allocate_device(struct device *dev);struct input_dev *input_allocate_device(void);void input_free_device(struct input_dev *dev);

注册/注销输入设备

int input_register_device(struct input_dev *dev);void input_unregister_device(struct input_dev *dev);

设置输入设备支持的事件类型/编号

__set_bit(nr, vaddr);void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code);

上报输入事件

void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);static inline void input_report_key(struct input_dev *dev, unsigned int code, int value);static inline void input_report_key(struct input_dev *dev, unsigned int code, int value);static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value);static inline void input_sync(struct input_dev *dev);

注意,上报数据时并不是直接上报,而是先放到一个循环队列里面,当上报SYNC同步事件时,才表示输入数据上报完成。

使用Input子系统实现的内核按键驱动

设备树

gpio_keys {compatible = "gpio-keys";#address-cells = <1>;#size-cells = <0>;autorepeat;button@21 {label = "GPIO Key UP";linux,code = <103>;gpios = <&gpio1 0 1>;};button@22 {label = "GPIO Key DOWN";linux,code = <108>;interrupts = <1 IRQ_TYPE_LEVEL_HIGH 7>; };}

几点说明:

1)外层使用一个gpio_keys的节点,表示一个按键设备,其compatible属性为"gpio-keys",表示使用内核提供的gpio按键驱动。

2)因为一个按键设备可能有多个按键(比如键盘),所以用子节点表示各个按键,这些按键使用GPIO引脚与主控相连。

3)父节点设置属性autorepeat,表示为按键上报长按事件。

4)使用gpios属性和interrupts属性都可以使用中断,但是驱动对两种形式的处理不同,因为gpios方式能根据引脚电平判断按键是按下还是弹起,推荐使用这种形式。

5)当使用gpios属性指定中断时,使用的中断处理函数为gpio_keys_gpio_isr(),内核work函数为gpio_keys_gpio_work_func()

6) 子节点里可以通过设置debounce-interval属性,来为按键设置消抖延时时间,如果没有设置则驱动默认使用5ms

7) 消抖会优先使用gpiolib提供的引脚硬件消抖,如果gpiolib没有实现硬件消抖,则使用定时器消抖

GPIO按键驱动的内核配置路径

-> Device Drivers->Input device support->Generic input layer->Keyboards<*> GPIO Buttons

关于按键长按的几点说明

1)如果input_register_device()注册的input_dev设置了rep成员的REP_DELAY或REP_PERIOD值,则input子系统核心认为设备驱动(我们)将自行上报长按事件,核心层则不会进行上报。

2)如果input_dev没有设置rep成员的REP_DELAY和REP_PERIOD值,则input子系统核心会为设备设置一个定时器用来自动上报长按事件,并将devrep[REP_DELAY]设置为250ms,将devrep[REP_PERIOD]设置为33ms,这两个参数是内核头文件里的宏定义,不可通过设备树配置。

3)长按支持的实现过程如下:

3.1)input_register_device()里面初始化了一个定时器:dev->timer.data = (long) dev;dev->timer.function = input_repeat_key;dev->rep[REP_DELAY] = 250;//250ms后开始上报长按事件dev->rep[REP_PERIOD] = 33;//每间隔33ms上报一次长按事件3.2)最底层的按键驱动只需要负责消抖,并且上报按下和弹起事件。3.3)当按键调用input_event()上报按下事件时,如果按键开启了长按AUTO_REPEATE标志,则会调用input_start_autorepeat(),该函数做了如下事情:3.3.1)设置产生长按的CODE,dev->repeat_key = code;3.3.1)启动定时器dev->timer,定时时间为dev->rep[REP_DELAY]毫秒3.3.3) 定时时间到后,执行input_repeat_key(); 3.3.4) input_repeat_key()函数会构造一个长按事件 + 同步事件,上报后再次启动定时器,定时rep[REP_PERIOD]毫秒,如此反复上报长按事件,直到按键松开时删除该定时器3.4)当按键调用input_event()上报松开事件时,如果按键开启了长按AUTO_REPEATE标志,则会调用input_stop_autorepeat(),该函数删除自动上报定时器:del_timer(&dev->timer);

关于read的几点说明

1)input子系统核心字符设备驱动相关的代码源文件为evdev.c

2)应用打开输入设备(比如/dev/input/event0)的时候,驱动的open函数会为其分配一个evdev_client的结构体,用来保存设备句柄,以及缓存该设备上报的事件。所以,open()之前的按键事件将不会被缓存,也不会被应用获取到。

3)当应用调用read函数读取输入设备时,驱动的read函数会从fd句柄对应的evdev_client结构体里面取出缓存的事件(struct input_event),返回给应用层。

4)驱动的read函数会根据应用层提供的buffer的大小,使用一个while()循环尽量多地返回上报事件,直到buffer不足以再多放一个事件。

5)非阻塞模式下,有无上报事件都直接返回,不休眠。阻塞模式下,有事件则读取事件并返回,无事件则休眠直到有事件上报。

如果觉得《八 INPUT子系统和内核自带的GPIO按键驱动》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。