首页 > 其他分享 >驱动(RK3588S)第十二课时:输入子系统(input)

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

时间:2024-09-18 08:51:06浏览次数:3  
标签:struct int unsigned dev 课时 RK3588S input include

目录

学习目标

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

四、目标现象

在这里插入图片描述

标签:struct,int,unsigned,dev,课时,RK3588S,input,include
From: https://blog.csdn.net/2401_83971583/article/details/142209251

相关文章

  • 网站打开提示:”No input file specifed.“
    当你的网站打开时提示“Noinputfilespecified.”,这通常意味着PHP解析器无法找到要解析的文件。这种错误通常发生在以下几个方面:文件路径错误:文件路径不正确或不存在。文件权限问题:文件或目录权限设置不当。Web服务器配置问题:Web服务器配置错误或缺失必要的配置。PHP配置问......
  • inputTranslator 输入翻译小助手
    inputTranslator输入翻译小助手输入翻译助手,你只需要触发热键(例如大键盘右侧的Ctrl),即可轻松编辑框中内容翻译成指定语言。节省你跨软件操作的时间。和异国友人轻松对话。❓解决什么问题?我需要一款输入后翻译的小助手,在聊天中方便我快速翻译回复,不想跨软件操作。但是,目前网上没有一......
  • PbootCMS网站转移后无法打开报错提示“No input file specifed”
    当PBootCMS网站在转移服务器后出现“Noinputfilespecified”的错误提示时,这通常是由于某些配置或文件问题导致的。以下是可能导致此错误的一些原因及解决方法:检查.user.ini文件:检查根目录是否含有.user.ini文件,如果有,请尝试删除该文件,然后重新访问站点。重启Web服务器......
  • 单选和多选在table里的报错问题Blocked aria-hidden on a <input> element because the
    单选在main.js里//table单选报错问题Vue.directive('removeAriaHidden',{bind(el,binding){constariaEls=el.querySelectorAll('.el-radio__original')ariaEls.forEach((item)=>{item.removeAttribute('aria-hidden')......
  • 【Azure Service Bus】创建 ServiceBus 的Terraform脚本报错GetAuthorizationRule: In
    问题描述在使用Terraform部署ServiceBus时候,遇见了如下报错:Error:ErrormakingReadrequestonAzureServiceBusTopicAuthorizationRule:servicebus.TopicsClient#GetAuthorizationRule:Invalidinput:autorest/validation:validationfailed:parameter=authorization......
  • 【Azure Service Bus】创建 ServiceBus 的Terraform脚本报错GetAuthorizationRule: In
    问题描述在使用Terraform部署ServiceBus时候,遇见了如下报错:Error:ErrormakingReadrequestonAzureServiceBusTopicAuthorizationRule:servicebus.TopicsClient#GetAuthorizationRule:Invalidinput:autorest/validation:validationfailed:parameter=authorizat......
  • 2024.9.10 人工智能教育技术学 第一课时搜索引擎
    首先学习了搜索引擎的分类1.全文搜索2.目录式搜索搜索引擎在搜索中的技巧:1.使用减号(减去不需要的东西)减号前记得加空格;可以利用这个功能筛选掉一些广告和不需要的内容2.(1)使用filetype指令搜索格式:关键词+空格+filetype+文件格式(2)site指令搜索格式:关键词+空格+site:+网站(3......
  • 【高级编程】Java流(上)字节流 InputStream OutputStream
    文章目录文件操作流输入流InputStream输出流OutputStream文件操作文件是指相关记录或放在一起的数据的集合。是一种用于存储数据的基本单位,它可以包含各种类型的信息,例如文本、图像、音频或视频。文件在计算机中通常存储在磁盘或其他存储介质上,并且每个文件都有一个......
  • 【SQL数据库技术开发】第34课时-数据库SQL CHECK 约束
    SQL CHECK 约束SQLCHECK约束CHECK约束用于限制列中的值的范围。如果对单个列定义CHECK约束,那么该列只允许特定的值。如果对一个表定义CHECK约束,那么此约束会基于行中其他列的值在特定的列中对值进行限制。CREATETABLE时的SQLCHECK约束下面的SQL在"Per......
  • 【SQL数据库技术开发】第41课时-数据库SQL Date 函数
    SQL Date 函数SQL日期(Dates)当我们处理日期时,最难的任务恐怕是确保所插入的日期的格式,与数据库中日期列的格式相匹配。只要您的数据包含的只是日期部分,运行查询就不会出问题。但是,如果涉及时间部分,情况就有点复杂了。在讨论日期查询的复杂性之前,我们先来看看最重要的......