驱动(RK3588S)第十课时:linux中断和等待队列

学习目标

1.编写按键中断的驱动代码
2.使用等待队列去优化按键驱动代码

一、按键中断

1、linux中断概念

所谓中断就是打断 CPU 正在处理的事情,然后跳转到中断处理函数里去运行,然后处理完了中断服务函数里的功能之后,他要再次回到之前运行的位置接着运行。其实中断对于 CPU 来书他就是一个异常,在内核里异常也分为不同的类型。之所以使用中断,就是为了增加CPU的运行效率。
CPU 它是有七中模式:
1、用户模式(Usr):用于正常执行程序;
2、快速中断模式(FIQ):用于高速数据传输;
3、外部中断模式(IRQ):用于通常的中断处理;
4、管理模式(svc):操作系统使用的保护模式;
5、数据访问终止模式(abt):当数据或指令预取终止时进入该模式,可用于虚拟存储以及存储保护。
6、系统模式(sys):运行具有特权的操作系统任务;
7、未定义指令中止模式(und):当未定义的指令执行时进入该模式,可用于支持硬件
但是在发生中断的时候的是不是 CPU 就立马跳转到你的中断服务函数里运行了呢?
肯定不是的,他要保存当前运行的一些环境变量并进行压栈,目的就是为了等 CPU处理完中断之后,知道接下来 CPU 要从哪里开始继续运行下面的内容。

2、什么是中断服务函数

简单来说中断服务函数就是发生中断的时候要执行的函数也就是触发中断时要执行的功能函数。单片机/CPU 他是通过NVIC来管理中断的,但是ARM系列是通过GIC来管理中断的。一般来说中断分为两部分,上半部分只能快进快出,下一部分可以进行输出等一系列复杂运算。

3、中断分类

SGI是软件触发中断,它的中断号 0-15保留用于 SGI 的中断号
PPI是私有外设中断,这是由单个 CPU 核私有的外设生成的。PPI 的中断号为 16-31
SPI是享外设中断(SPI,Shared Peripheral Interrupt)这是由外设生成的,中断控制器可以将其路由到多个核。其中断号为 32-1020。SPI 用于从整个系统可访问的各种外围设备发出中断信号。

4、中断的状态

① 非活动状态(Inactive),这意味着该中断未触发。
② 挂起(Pending),这意味着中断源已被触发,但正在等待 CPU 核处理。待处理的中断要通过转发到 CPU 接口单元,然后再由 CPU 接口单元转发到内核。
③ 活动(Active),描述了一个已被内核接收并正在处理的中断。
④ 活动和挂起(Active and pending)描述了一种情况,其中 CPU 核正在为中断服务,而 GIC 又收到来自同一源的中断。

5、设备树树中的中断interrupts 属性

interrupt-controller 一个空属性用来声明这个 node 接收中断信号,即这个 node是一个中断控制器。 #interrupt-cells,是中断控制器节点的属性,用来标识这个控制器需要几个单位做中断描述符,用来描述子节点中"interrupts"属性使用了父节点中的interrupts 属性的具体的哪个值。 一般,如果父节点的该属性的值是 3,则子节点的interrupts 一个 cell 的三个 32bits 整数值分别为:<中断域 中断 触发方式>,如果父节点的该属性是 2,则是<中断 触发方式> interrupt-parent,标识此设备节点属于哪一个中断控制器,如果没有设置这个属性,会自动依附父节点的 interrupts。interrupt-parent 属性用于指定中断父节点是可以被继承.
自己添加的按键中断的设备节点
在这里插入图片描述
在这里插入图片描述

6、中断的函数

1、注册中断的资源在使用设备树的时候就使用这个函数

函数原型: int devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,unsigned long irqflags, const char *devname, void *dev_id)
函数头文件:#include <linux/interrupt.h>
函数参数:dev:设备信息的结构体 — 在探测函数里获取
irq:获取得到的中断编号
handler:中断服务器函数
中断服务函数的原型irqreturn_t (*irq_handler_t)(int irq, void *date);
参数:irq :哪一个中断号发生了中断
date:注册函数最后一个参数传递了数值dev
irqflags:中断的触发方式
IRQ_TYPE_NONE ---- 默认触发方式
IRQ_TYPE_EDGE_RISING — 上升沿触发
IRQ_TYPE_EDGE_FALLING — 下降沿触发
IRQ_TYPE_EDGE_BOTH — 上升沿和下降沿都触发
IRQ_TYPE_LEVEL_HIGH — 高电平触发
IRQ_TYPE_LEVEL_LOW — 低电平触发
devname:中断的名字 — 无所谓
dev_id:你给中断服务函数传递的参数
如果你不传递你就写 NULL
函数返回值:成功返回 0 失败负数

2、释放注册的中断资源

函数原型:void devm_free_irq(struct device *dev, unsigned int irq, void *dev_id)
函数头文件:#include <linux/interrupt.h>
函数参数:irq:获取的中断编号
dev_id:就是你注册函数最后一个参数写什么,这里就写什么
函数返回值:无

3、使能或失能

函数功能:使能中断
函数原型:void enable_irq(unsigned int irq)
函数头文件:#include <linux/interrupt.h>
函数参数:irq:获取的中断编号
函数返回值:无
函数功能:失能
函数原型:void disable_irq(unsigned int irq)
函数头文件:#include <linux/interrupt.h>
函数参数:irq:获取的中断编号
函数返回值:无
一般情况下内核已经打开或者关闭,所以这两个函数我们一般不用。

4、从设备树上去获取中断资源

函数原型:int platform_get_irq(struct platform_device *dev,unsigned int num)
函数头文件:#include <linux/platform_device.h>
函数参数:dev:探测函数的参数
num:获取中断资源的编号 — 就是第几个资源
函数返回值:成功返回 获取得到的中断编号 失败返回负数

二、等待队列

1、等待队列的概念

Linux 中的等待队列是为了提供在进程直接的同步机制而提出,尤其是在资源抢占的情况下,这里的同步指的是进程直接是按照一定的先后顺序执行的,就是条件等待,在特点的事情没有发生的时候,进程会把自己加入到事件的队列当中,主动放弃对 CPU 的控制权,给其他的进程使用,大大的减少 CPU 的工作量,等到条件成立了,有内核去主动的唤醒等待的进程。其实等待队列他具是条件等待,就是不满足条件我就阻塞等待(睡眠),当条件满足
了,就有内核去主动去唤醒等待的进行。在内核内部实现的机制是链表。
那么内核怎么去管理睡眠等待的进程的呢,他实际上是搞了一个等待队列头,用来管理等待队列里的事件。
所谓的等待和唤醒就是
等待:就是不满足就睡眠阻塞
唤醒:满足一定条件了,就唤醒等待的进行开始工作。
比如 key 按键,按键没有按下那就阻塞等待,当按键按下了,就有内核去主动的唤醒刚才去按键状态的进行,让他在去读。
阻塞和非阻塞的区别:
阻塞:阻塞就目前没有满足一定提交,就阻塞等待,比如 scanf
非阻塞:就是不管条件是否满足,只要你调用了这个函数他就一定会给你返回一个结果。

2、等待队列函数

1、创建等待队列的头

static DECLARE_WAIT_QUEUE_HEAD(name);
name:你定义的等待队列的头 — 就是管理等待队列里的事件的

2、不满足就阻塞等待,加入到等待队列里睡眠

函数原型:wait_event_interruptible(wq, condition);
函数头文件:#include <linux/sched.h>
函数参数:wq:就是定义的等待队列头
condition:他是等待队列的条件
0 是休眠 非 0 就不休眠
宏定义,无返回值
在这里插入图片描述

3、条件满足就唤醒等待队列里的进程

函数原型:wake_up_interruptible(&name);
函数头文件:#include <linux/wait.h>
函数参数:name:就是定义的等待队列头,这里是需要加&
函数返回值:无

三、任务代码

1、linux中断

底层内核:

#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>
int key[2]={0};
int keyirq[2]= {0};
int key_value = 0;
struct platform_device *xyddev = NULL;
int beep_value[2] = {0};
dev_t dev;
int i;
struct cdev mydev;
struct class *myclass=NULL;
 irqreturn_t mykey_handler (int irq, void *date)
 {
 	if(gpio_get_value(key[0])==0)
		{
			key_value = 1;
		}
	return 0;
 }
int myled_open (struct inode *inode, struct file *fp)
{
	int ret=0;
	printk("myled open 正确打开\n");
	keyirq[0]=platform_get_irq(xyddev,0);//获得到的中断编号
	//keyirq[1]=platform_get_irq(xyddev,1);//获得到的中断编号
	//for(i=0;i<=1;i++)
	//{
	//	printk("keyirq[%d]:%d\n",i,keyirq[i]);
	//}
	printk("keyirq[0]:%d\n",keyirq[0]);
	ret=devm_request_irq(&xyddev->dev,keyirq[0],mykey_handler,IRQ_TYPE_EDGE_FALLING,"xyd-key",NULL);
	return 0;
}

int myled_close (struct inode *inode, struct file *fp)
{
	printk("myled close 关闭正确\n");
	devm_free_irq(&xyddev->dev,keyirq[0],NULL);
	return 0;
}
ssize_t mykey_read (struct file *fp, char __user *buf, size_t size, loff_t *offset)
{
	unsigned long ret=0;
	ret=copy_to_user(buf,&key_value,4);
	if(ret<0)
	{
		printk("copy_to_user 错误\n");
		return -1;
	}
	key_value=0;
	return 0;
}
ssize_t mykey_write (struct file *fp, const char __user *buf, size_t size, loff_t *offset)
{	
	return 0;
}
struct file_operations myfops={
	.open = myled_open,
	.release = myled_close,
	.read = mykey_read,
	.write = mykey_write,
};

int myled_probe(struct platform_device *pdev)
{
	printk("探测函数:设备端和驱动端匹配成功\n");
	xyddev=pdev;
	//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]);	
	alloc_chrdev_region(&dev,0,1,"led");//动态申请设备号  linux2.6或杂项类型
	cdev_init(&mydev,&myfops);//初始化核心结构体
	cdev_add(&mydev,dev,1);//向内核去申请 linux2.6 字符设备
	myclass=class_create(THIS_MODULE,"class_key");//创建类
	if(myclass == NULL)
	{
		printk("class_create error\n");
		printk("class_create 类创建失败\n");
		return -1;
	}
	device_create(myclass,NULL,dev,NULL,"mykey");//自动创建设备节点
 	return 0;
}

int myled_remove (struct platform_device *pdev)
{
	printk("移除函数成功\n");
	device_destroy(myclass,dev);//销毁设备节点  在/dev/name ---device_create
	class_destroy(myclass);//销毁类 --class_create
	cdev_del(&mydev);//释放申请的字符设备  --cdev_add
	unregister_chrdev_region(dev,1);//释放申请的设备号 ---alloc_chrdev_region
	gpio_free(key[0]);// 释放 gpio 口资源 ----gpio_request
	gpio_free(key[1]);
	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>
int main(int argc,char *argv[])
{
	int fd = 0;
	int key_value=0;
	fd = open("/dev/mykey",O_RDWR); // --- 底层的open函数
	while(1)
		{
			read(fd,&key_value,4);
			if(key_value==1)
			{
				printf("第%d个按键按下\n",key_value);
			}
			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

2、等待队列

内核底层:

#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>
int key[2]={0};
int keyirq[2]= {0};
int key_value = 0;
struct platform_device *xyddev = NULL;
int beep_value[2] = {0};
static DECLARE_WAIT_QUEUE_HEAD(key_wait);
int condition = 0;
dev_t dev;
int i;
struct cdev mydev;
struct class *myclass=NULL;
 irqreturn_t mykey_handler (int irq, void *date)
 {
 	if(gpio_get_value(key[0])==0)
		{
			key_value = 1;
			condition = 1;
			wake_up_interruptible(&key_wait);
		}
	return 0;
 }
int myled_open (struct inode *inode, struct file *fp)
{
	int ret=0;
	printk("myled open 正确打开\n");
	keyirq[0]=platform_get_irq(xyddev,0);//获得到的中断编号
	//keyirq[1]=platform_get_irq(xyddev,1);//获得到的中断编号
	//for(i=0;i<=1;i++)
	//{
	//	printk("keyirq[%d]:%d\n",i,keyirq[i]);
	//}
	printk("keyirq[0]:%d\n",keyirq[0]);
	ret=devm_request_irq(&xyddev->dev,keyirq[0],mykey_handler,IRQ_TYPE_EDGE_FALLING,"xyd-key",NULL);
	return 0;
}

int myled_close (struct inode *inode, struct file *fp)
{
	printk("myled close 关闭正确\n");
	devm_free_irq(&xyddev->dev,keyirq[0],NULL);
	return 0;
}
ssize_t mykey_read (struct file *fp, char __user *buf, size_t size, loff_t *offset)
{
	unsigned long ret=0;
	if(!condition)
		{//只要能进来就说明按键没有按下
			if(fp->f_flags &O_NONBLOCK)
			{
				printk("打开文件方式使用的是非阻塞");
				return -1;
			}
		wait_event_interruptible(key_wait, condition);//这里就会把当前的进程加入等待队列里去睡眠
		}
		else
		{
			ret=copy_to_user(buf,&key_value,4);
			if(ret<0)
			{
				printk("copy_to_user 错误\n");
				return -1;
			}
			key_value=0;
			condition=0;
		}
	

	return 0;
}
ssize_t mykey_write (struct file *fp, const char __user *buf, size_t size, loff_t *offset)
{	
	return 0;
}
struct file_operations myfops={
	.open = myled_open,
	.release = myled_close,
	.read = mykey_read,
	.write = mykey_write,
};

int myled_probe(struct platform_device *pdev)
{
	printk("探测函数:设备端和驱动端匹配成功\n");
	xyddev=pdev;
	//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]);	
	alloc_chrdev_region(&dev,0,1,"led");//动态申请设备号  linux2.6或杂项类型
	cdev_init(&mydev,&myfops);//初始化核心结构体
	cdev_add(&mydev,dev,1);//向内核去申请 linux2.6 字符设备
	myclass=class_create(THIS_MODULE,"class_key");//创建类
	if(myclass == NULL)
	{
		printk("class_create error\n");
		printk("class_create 类创建失败\n");
		return -1;
	}
	device_create(myclass,NULL,dev,NULL,"mykey");//自动创建设备节点
 	return 0;
}

int myled_remove (struct platform_device *pdev)
{
	printk("移除函数成功\n");
	device_destroy(myclass,dev);//销毁设备节点  在/dev/name ---device_create
	class_destroy(myclass);//销毁类 --class_create
	cdev_del(&mydev);//释放申请的字符设备  --cdev_add
	unregister_chrdev_region(dev,1);//释放申请的设备号 ---alloc_chrdev_region
	gpio_free(key[0]);// 释放 gpio 口资源 ----gpio_request
	gpio_free(key[1]);
	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>
int main(int argc,char *argv[])
{
	int fd = 0;
	int key_value=0;
	fd = open("/dev/mykey",O_RDWR); // --- 底层的open函数
	while(1)
		{
			read(fd,&key_value,4);
			if(key_value==1)
			{
				printf("第%d个按键按下\n",key_value);
			}
			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

四、烧录现象

1、linux中断

在这里插入图片描述
在这里插入图片描述

2、等待队列

在这里插入图片描述
在这里插入图片描述

五、附录

烧入开发板的内核镜像:

// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2021 Rockchip Electronics Co., Ltd.
*
*/
/dts-v1/;
#include "rk3588s-evb4-lp4x-yyt.dtsi"
#include "rk3588-linux.dtsi"
/ {
		model = "Rockchip RK3588S EVB4 LP4X V10 Board";
		compatible = "rockchip,rk3588s-evb4-lp4x-v10", "rockchip,rk3588";
		xydled: xyd_leds {
			compatible = "xyd-led";
			leds-gpios = <&gpio0 RK_PC5 GPIO_ACTIVE_HIGH>,
		 					<&gpio0 RK_PC6 GPIO_ACTIVE_HIGH>;
		status = "okay";
	};
	xydkey: xyd_keys {
		compatible = "xyd-key";
		keys-gpios = <&gpio1 RK_PA7 GPIO_ACTIVE_LOW>,
					 <&gpio1 RK_PB1 GPIO_ACTIVE_LOW>;
		interrupt-parent=<&gpio1>; 
		interrupts = <RK_PA7 IRQ_TYPE_LEVEL_LOW>,
					<RK_PB1 IRQ_TYPE_LEVEL_LOW>;
		status = "okay";
	};
	xydbeep: xyd_beep {
		compatible = "xyd-beep";
		beeps-gpios = <&gpio1 RK_PA4 GPIO_ACTIVE_HIGH>;
		status = "okay";
	};
	xydinfo: xyd_device {
		compatible = "xyd-device";
		devices-gpios = <&gpio0 RK_PC5 GPIO_ACTIVE_HIGH>,
		 				<&gpio0 RK_PC6 GPIO_ACTIVE_HIGH>,
						 <&gpio1 RK_PA7 GPIO_ACTIVE_LOW>,
						 <&gpio1 RK_PB1 GPIO_ACTIVE_LOW>,
						 <&gpio1 RK_PA4 GPIO_ACTIVE_HIGH>;
		status = "okay";
	};
	xydnod: xyd_devs {
		compatible = "xyd-devs";
		xydled1: xyd_leds {
		leds-gpios = <&gpio0 RK_PC5 GPIO_ACTIVE_HIGH>,
					 <&gpio0 RK_PC6 GPIO_ACTIVE_HIGH>;
		status = "okay";
	};
	xydkey1: xyd_keys {
		keys-gpios = <&gpio1 RK_PA7 GPIO_ACTIVE_LOW>,
		 				<&gpio1 RK_PB1 GPIO_ACTIVE_LOW>;
		status = "okay";
	};
	xydbeep1: xyd_beep {
			beeps-gpios = <&gpio1 RK_PA4 GPIO_ACTIVE_HIGH>;
			status = "okay";
		};
	};
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值