驱动(RK3588S)第十二课时:输入子系统(input)

学习目标

1.利用设备树+输入子系统实现按键驱动代码的编写并实现编写应用层去获取输入子系统传递的按键信息。

一、输入子系统

1、输入子系统的概念

所谓的输入子系统指定是内核把所有的输入设备给做了一个架构,相当于封装了一个子系统,专门就是处理所有的输入设备的。比如你现在写一个按键驱动或者是在写一个键盘或者是鼠标的驱动,这三个代码的底层驱动你都需要重复去写 read write open close,这样就会感觉你的代码显得非常的冗余,这些设备其实都是可以归结于一类,都是 input 设备,就是 CPU 输入内容。那么能不能把这些重复的代码都给他摘出来统一去管理呢?目前生产键盘的厂商是不是很多,牌子是不是很多,你有没有发现,你不管是那一个厂商生产的键盘你插到同一个电脑上,去按同一个字母按键,都输入相同的内容。这里的原型就是因为操作系统里的内核已经对这些输入设备做了统一的管理,就是所谓的输入子系统。

2、输入子系统的框架

输入子系统他是分为了四个层,具体是:
设备驱动层:他就是直接和硬件打交道的,这一个是需要你驱动开发工程师去写的。
核心层:核心层是有内核开发的人员去编写的,这里不需要你驱动开发工程师去写,就给你底层留好了相关的函数接口。
事件分发层:他也是不需要你驱动开发工程师去编写的,他也是有内核驱动打开工程师去编写的,同样他也给应用层去分配你所需要的设备的节点 /dev/input/eventx。
用户层:他也是你驱动开放工程师自己编写的,就是所谓的用户调用内核去驱动。你在编写输入设备的时候,这里的核心层和事件分发层是不需要你编写的,它们四部分共同组成了一个输入子系统。

3、输入子系统节点在 Linux 操作系统中的位置

在这里插入图片描述
这以上都是属于输入子系统的节点

4、让事件同步的目的

上报“同步事件”的目的是为了确保数据能够及时从内核空间传递到应用层。在我们内核上报数据时,上报的数据包会存储在内核空间的一个 buf 缓冲区(f 数组)中,而不是立即传递到应用层。如果没有“同步事件”,应用层无法立即读取这些数据。只有当缓冲区被填满时,内核才会将所有数据一次性传递到应用层。这会导致数据延迟,甚至可能导致应用层无法实时处理事件。如,我们按下按键 1 时,在应用层读取不到数据包。按下按键 2时,也读取到…当我们按下一堆按键时,这时 buff 被存满,之前被存储的数据包一次性被读了出来,这是我们不想看到的结果,所以我们需要每上报完一次据,就发送一次同步事件。

二、输入子系统相关API函数

1、注册输入设备

函数原型::int input_register_device(struct input_dev *dev);
函数头文件:#include <linux/input.h>
函数参数:dev:定义的核心结构体
函数返回值:成功返回 0 失败返回负数

2、定义的核心结构体

struct input_dev {
const char *name;//输入设备的名字 – 自己取名
const char *phys;
const char *uniq;
struct input_id id;
unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
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 hint_events_per_packet;
unsigned int keycodemax;//支持的按键值的个数
unsigned int keycodesize;//每个键值的字节数
void *keycode;//存放按键值的数组首地址
int (*setkeycode)(struct input_dev *dev,
const struct input_keymap_entry *ke,
unsigned int *old_keycode);
int (*getkeycode)(struct input_dev *dev,
struct input_keymap_entry *ke);
struct ff_device *ff;
struct input_dev_poller *poller;
unsigned int repeat_key;//最近一次键值,用于连击
struct timer_list timer;//自动连击计时器
int rep[REP_CNT];
struct input_mt *mt;
struct input_absinfo *absinfo;
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 (*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 __rcu *grab;
spinlock_t event_lock;
struct mutex mutex;
unsigned int users;
bool going_away;
struct device dev;
struct list_head h_list;
struct list_head node;
unsigned int num_vals;
unsigned int max_vals;
struct input_value *vals;
bool devres_managed;
ktime_t timestamp[INPUT_CLK_MAX];
};
比如我要填写键值和事件
Set_bit(Nr, addr)
Nr:你要填写事件类型/具体的键值
Addr:你给和结构体里的那一个成员进行赋值,比如我要给事件类型进行赋值。
Set_bit(EV_KEY,xxx->evbit) — 他是支持按键事件
Set_bit(EV_REP,xxx->evbit) — 他是支持重复事件
Set_bit(KEY_1,xxx->keybit) — 支持的按键的数值

3、注销申请的输入设备的资源

函数原型:void input_unregister_device(struct input_dev *dev);
函数头文件:#include <linux/input.h>
函数参数:dev:定义的核心结构体
函数返回值:无

4、上报事件到应用层

函数原型:void input_event(struct input_dev *dev,unsigned int type,unsigned int code,int value);
函数头文件:#include <linux/input.h>
函数参数:dev:定义的核心结构体
Type:发生事件的类型
Code:上报按键的集体的数值
Value:按键的状态
1 — 按下 0 — 松开
函数返回值:无

5、上报同步事件

函数原型:void input_sync(struct input_dev *dev);
函数头文件:#include <linux/input.h>
函数参数:dev:定义的核心结构体
函数返回值:无

6、应用层所用的核心结构体

#include <linux/input.h>
struct input_event {
struct timeval time;//本次上报的时间戳
__u16 type;//本次事件上报的类型,EV_KEY
__u16 code;//具体数值,如果是 EV_KEY 事件,则是哪个按键,如 KEY_1.
__s32 value;//和 code 相关的标志,如果是 EV_KEY 按键事件,表示按键状态是按下还是松
开,1 表示按下,0 表示松开。
}

三、目标代码

内核底层:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/gpio.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/poll.h>
#include <linux/input.h>
int key[2]={0};
int i;
int keyirq[2]= {0};
struct input_dev *mydev = NULL;

irqreturn_t mykey_handler (int irq, void *date)
 {
 	printk("按键1触发中断\n");
 	if(gpio_get_value(key[0])==0)//这里代表按键按下了
		{
			input_event(mydev,EV_KEY,KEY_1,1);
		}
	else
		{//这里就按键松开了
			input_event(mydev,EV_KEY,KEY_1,0);
		}
	input_sync(mydev);//不管你按键是按下了还是松开了都要同步
	return 0;
 }
 irqreturn_t myykey_handler (int irq, void *date)
 {
 	printk("按键2触发中断\n");
 	if(gpio_get_value(key[1])==0)//这里代表按键按下了
		{
			input_event(mydev,EV_KEY,KEY_2,1);
		}
	else
		{//这里就按键松开了
			input_event(mydev,EV_KEY,KEY_2,0);
		}
	input_sync(mydev);//不管你按键是按下了还是松开了都要同步
 	
	return 0;
 }


int myled_probe(struct platform_device *pdev)
{
	int ret = 0;
	int ret1=0;
	int ret2=0;
	printk("探测函数:设备端和驱动端匹配成功\n");
	mydev = input_allocate_device();
	if(mydev == NULL)
	{
		printk("input allocate device error\n");
		return -1;
	}
	mydev->name = "xydkey_input";
	set_bit(EV_KEY,mydev->evbit);//支持的按键事件
	set_bit(EV_REP,mydev->evbit);//支持的重复事件
	set_bit(KEY_1,mydev->keybit);//具体的键值
	set_bit(KEY_2,mydev->keybit);
	ret = input_register_device(mydev);
	//led[0] led[1]返回的是gpio编口号
	key[0]=of_get_named_gpio(pdev->dev.of_node,"keys-gpios",0);//获得设备树的属性
 	key[1]=of_get_named_gpio(pdev->dev.of_node,"keys-gpios",1);
 	gpio_request(key[0], "key1 pa7");//21  申请你要使用 gpio 口的资源
	gpio_request(key[1], "key2 pb1");//22
	gpio_direction_input(key[0]);//配置 gpio 口的工作模式
	gpio_direction_input(key[1]);
	keyirq[0]=platform_get_irq(pdev,0);//获得到的中断编号
	keyirq[1]=platform_get_irq(pdev,1);//获得到的中断资源编号
	for(i=0;i<=1;i++)
	{
		printk("keyirq[%d]:%d\n",i,keyirq[i]);
	}
	ret1=devm_request_irq(&pdev->dev,keyirq[0],mykey_handler,IRQ_TYPE_EDGE_BOTH,"xyd-key",NULL);
	ret2=devm_request_irq(&pdev->dev,keyirq[1],myykey_handler,IRQ_TYPE_EDGE_BOTH,"xyd-key",NULL);
 	return 0;
}

int myled_remove (struct platform_device *pdev)
{
	printk("移除函数成功\n");
	devm_free_irq(&pdev->dev,keyirq[0],NULL);
	devm_free_irq(&pdev->dev,keyirq[1],NULL);
	gpio_free(key[0]);// 释放 gpio 口资源 ----gpio_request
	gpio_free(key[1]);
	input_unregister_device(mydev);
	input_free_device(mydev);
	return 0;
}
struct of_device_id	mydev_node={
	.compatible="xyd-key",
};

struct platform_driver drv={
	.probe = myled_probe,
	.remove = myled_remove,
	.driver = {
		.name = "xyd-key",//与设备端必须保持一致
		.of_match_table = &mydev_node,
	},
};
static int __init myled_init(void)
{	
	platform_driver_register(&drv);
	return 0;
}
static void __exit myled_exit(void)
{
	platform_driver_unregister(&drv);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");

应用层:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
int main(int argc,char *argv[])
{
	int fd = 0;
	int key_value=0;
	struct input_event myinput_event;
	fd= open(argv[1],O_RDWR); // --- 底层的open函数
	if (fd < 0)
	{
		printf("can't open!\n");
	}
	while(1)
		{
			read(fd,&myinput_event,sizeof(myinput_event));	
			if(myinput_event.type == EV_KEY)
			{
				if(myinput_event.code== KEY_1)
				{
					if(myinput_event.value == 1)
					{
						printf("%d按键按下\n",myinput_event.code);
					}
					if(myinput_event.value == 0)
					{
						printf("%d按键松开\n",myinput_event.code);
					}
				}
				if(myinput_event.code== KEY_2)
				{
					if(myinput_event.value == 1)
					{
						printf("%d按键按下\n",myinput_event.code);
					}
					if(myinput_event.value == 0)
					{
						printf("%d按键松开\n",myinput_event.code);
					}
				}
			}
			usleep(500000);
		}
	close(fd);//底层的close	
	return 0;
}

编译:

obj-m += led_driver.o #最终生成模块的名字就是 led.ko    
KDIR:=/home/stephen/RK3588S/kernel  #他就是你现在rk3588s里内核的路径 
    
CROSS_COMPILE_FLAG=/home/stephen/RK3588S/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-
    #这是你的交叉编译器路径 --- 这里你也要替换成你自己的交叉编译工具的路径
all:
	make -C $(KDIR) M=$(PWD) modules ARCH=arm64 CROSS_COMPILE=$(CROSS_COMPILE_FLAG)
	aarch64-none-linux-gnu-gcc app.c -o app
    #调用内核层 Makefile 编译目标为 modules->模块 文件在当前路径
    # 架构  ARCH=arm64 
clean:
	rm -f  *.o *.mod.o *.mod.c *.symvers *.markers *.order app  *.mod  *.ko

四、目标现象

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值