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

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

其中,设备层部分的代码跟具体的输入设备相关,由驱动工程师来具体实现,负责监测并上报具体的输入事件。核心层起承上启下的作用,接受设备层上传的输入事件,然后转发给事件处理层。事件处理层则处理核心层上报的输入事件,负责字符设备驱动那一套,对用户空间提供访问接口。系统框架图如下:
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)非阻塞模式下,有无上报事件都直接返回,不休眠。阻塞模式下,有事件则读取事件并返回,无事件则休眠直到有事件上报。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Linux GPIO子系统是一个用于控制嵌入式系统中通用输入/输出(GPIO)的软件子系统。它提供了一种标准的接,使得应用程序可以通过文件系统接来访问GPIO。这个子系统可以用于控制各种设备,例如LED、按钮、传感器等等。如果你需要更多的信息,可以查看Linux内核文档。 ### 回答2: Linux GPIO子系统是一种用于管理通用输入输出(GPIO)引脚的软件层。GPIO引脚是一种通用可编程引脚,可以在嵌入式系统中用来通过读取输入或设置输出与外部设备进行通信。 Linux GPIO子系统负责将底层硬件 GPIO 引脚的操作抽象为文件系统的接,使开发者可以通过读写文件的方式来访问和控制 GPIO 引脚。通过该子系统,可以实现对 GPIO 引脚的配置、读取和写入等操作,以满足不同应用下对 GPIO 的需求。 Linux GPIO子系统的核心是GPIO驱动程序,它与底层硬件层进行交互,完成对GPIO引脚的操作。驱动程序将GPIO引脚映射到内存,通过读写该内存地址即可对引脚进行操作。用户通过访问特定目录下的文件来和引脚进行交互,例如将引脚配置为输入模式、输出模式,以及读取或写入引脚的状态。 通过Linux GPIO子系统开发者可以方便地进行GPIO引脚的控制。可以根据不同的应用需求,灵活配置引脚的输入输出模式,监听引脚上的状态变化,并根据需要对其他外设进行控制。 总之,Linux GPIO子系统开发者提供了便捷的接,使得在嵌入式系统中使用GPIO引脚更加简单和灵活。它允许开发者通过读写文件的方式访问和控制GPIO引脚,满足各种不同嵌入式应用对GPIO的需求。 ### 回答3: LinuxGPIO(General Purpose Input/Output)子系统是通过软件对硬件上的通用输入/输出引脚进行控制的一种机制。它使得开发者可以利用这些GPIO引脚实现各种功能,比如控制LED灯、读取外部传感器的数据等。 LinuxGPIO子系统提供了许多功能和接来管理和操作GPIO。首先,它使用sysfs文件系统来组织GPIO资源的目录树,并通过文件的方式来读取和写入GPIO的状态。在/sys/class/gpio目录下,每个GPIO引脚都会有一个对应的目录,在该目录中的文件可以用于配置GPIO的方向(输入或输出)、读取和写入GPIO的电平状态。开发者可以使用命令行工具或者编程语言(如Python、C等)来操作这些文件,从而控制GPIO引脚的行为。 其次,LinuxGPIO子系统还提供了设备树(Device Tree)来描述硬件平台上的GPIO资源。设备树是一种描述硬件的数据结构,在启动时通过设备树绑定机制将设备树中定义的GPIO资源与内核驱动程序关联起来。这样,开发者就可以通过调用相应的驱动程序来控制GPIO引脚,而不需要手动操作sysfs文件系统。 此外,LinuxGPIO子系统还支持中断机制,可以让GPIO引脚在特定事件发生时触发中断。通过注册中断处理函数,开发者可以实现对GPIO输入信号的快速响应,提高系统的实时性。 总之,LinuxGPIO子系统开发者提供了一种方便且灵活的方式来控制硬件上的GPIO引脚。通过sysfs文件系统或设备树,开发者可以轻松地配置、读取和控制GPIO的状态,从而实现各种功能和应用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值