当前位置: 首页 > news >正文

【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第五十七章 Linux中断实验

i.MX8MM处理器采用了先进的14LPCFinFET工艺,提供更快的速度和更高的电源效率;四核Cortex-A53,单核Cortex-M4,多达五个内核 ,主频高达1.8GHz,2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT、4G模块、CAN、RS485等接口一应俱全。H264、VP8视频硬编码,H.264、H.265、VP8、VP9视频硬解码,并提供相关历程,支持8路PDM接口、5路SAI接口、2路Speaker。系统支持Android9.0(支持获取root限)Linux4.14.78+Qt5.10.1、Yocto、Ubuntu20、Debian9系统。适用于智能充电桩,物联网,工业控制,医疗,智能交通等,可用于任何通用工业和物联网应用、

【公众号】迅为电子

【粉丝群】258811263(加群获取驱动文档+例程)


第五十七章 Linux中断实验

本章导读

在 Linux 下的驱动实验中,中断是频繁使用的功能,Linux 内核提供了完善的中断框架,我们只需要使

用内核提供的函数,便可以方便的使用中断功能。本章我们就来学习一下如何在 Linux 中使用中断。

57.1章节讲解了Linux中断基础理论知识

57.2章节在iTOP-IMX8MM开发板上以按键中断为例,进行实验。

本章内容对应视频讲解链接(在线观看):

中断基础概念  https://www.bilibili.com/video/BV1Vy4y1B7ta?p=34

设备树中的中断节点以及相关函数  https://www.bilibili.com/video/BV1Vy4y1B7ta?p=35

按键中断实验  https://www.bilibili.com/video/BV1Vy4y1B7ta?p=36

程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\014-Linux中断实验”路径下。

57.1 Linux中断简介

57.1.1 Linux中断介绍

中断是指 CPU 在执行程序的过程中,出现了某些突发事件急待处理,CPU 必须暂停当前程序的执行,

转去处理突发事件,处理完毕后又返回原程序被中断的位置继续执行。由于中断的存在极大的提高了 CPU的运行效率,但是设备的中断会打断内核进程中的正常调度和运行,系统对更高吞吐率的追求势必要求中断服务程序尽量短小精悍。

举例来说,我现在正在厨房做饭,突然电话响了,然后我关火去接电话,接完电话在回去开火继续做饭,这个过程就是中断的一个过程。在这个看似简单的过程中,却涉及到了中断的几个过程,我们一起来看一下:

  • 电话铃声响了:中断请求
  • 我要去接电话:中断响应
  • 我关掉火:保护现场
  • 我接电话的过程:中断处理
  • 接完电话回到厨房开火:恢复现场
  • 继续做饭:中断返回
  • 如果我不接电话:中断屏蔽

为保证系统实时性,中断服务程序必须足够简短,但实际应用中某些时候发生中断时必须处理大量的

事物,这时候如果都在中断服务程序中完成,则会严重降低中断的实时性,基于这个原因,linux 系统提出了一个概念:把中断服务程序分为两部分:顶半部-底半部 。

顶半部(中断上文):完成尽可能少的比较急的功能,它往往只是简单的读取寄存器的中断状态,并清除中断标志后就进行“中断标记”(也就是把底半部处理程序挂到设备的底半部执行队列中)的工作。 顶半部的特点就是响应速度快。

底半部(中断下文):处理中断的剩余大部分任务,可以被新的中断打断。

57.1.2 中断相关函数

linux 中断有专门的中断子系统,其实现原理很复杂,但是驱动开发者不需要知道其实现的具体细节,

只需要知道如何应用该子系统提供的 API 函数来编写中断相关驱动代码即可。

1 获取中断号相关函数

编写驱动的时候需要用到中断号,每一个中断都有中断号,我们用到中断号,中断信息已经写到了设备树里面,因此可以通过 irq_of_parse_and_map 函数从 interupts 属性中提取到对应的设备号,函数原型如下表所示:

函数

unsigned int irq_of_parse_and_map(struct device_node *dev,int index)

dev

设备节点

index

索引号,interrupts 属性可能包含多条中断信息,通过 index 指定要获取的信息。

返回值

中断号

功能

通过 irq_of_parse_and_map 函数从 interupts 属性中提取到对应的设备号

如下表所示:

如果使用 GPIO 的话,可以使用 gpio_to_irq 函数来获取 gpio 对应的中断号,函数原型如下表所 

函数

int gpio_to_irq(unsigned int gpio)

gpio

要获取的 GPIO 编号

返回值

GPIO 对应的中断号

功能

获取GPIO对应的中断号

2 申请中断函数

同GPIO一样,在Linux内核里面,如果我们要使用某个中断也是需要申请的,申请中断我们使用的函数是 request_irq。函数原型如下表所示:

函数

int request_irq( unsigned int irq,irq_handler_t handler,unsigned long flags,const char *name,void *dev)

irq

要申请中断的中断号

handler

中断处理函数,当中断发生以后就会执行此中断处理函数。

flags

中断标志

name

中断名字,设置以后可以在开发板/proc/interrupts 文件中看到对应的中断名字

dev

如果将 flags 设置为 IRQF_SHARED 的话, dev 用来区分不同的中断,一般情况下将 dev 设置为设备结构体,dev 会传递给中断处理函数 irq_handler_t 的第二个参数。

返回值

中断申请成功返回0,其他负值则中断申请失败,如果返回-EBUSY 的话表示中断已经被申请了。

中断标识可以在文件 include/linux/interrupt.h 里面查看所有的中断标志,这里我们介绍几个常用的中断标志,如下图所示: 

标志

功能

IRQF_SHARED

多个设备共享一个中断线,共享的所有中断都必须指定此标志。如果使用共享中断的话,request_irq 函数的 dev 参数就是唯一区分他们的标志。

IRQF_ONESHOT

单次中断,中断执行一次就结束。

IRQF_TRIGGER_NONE

无触发。

IRQF_TRIGGER_RISING

上升沿触发。

IRQF_TRIGGER_FALLING

下降沿触发。

IRQF_TRIGGER_HIGH

高电平触发。

IRQF_TRIGGER_LOW

低电平触发。

3 、free_irq 函数

中断使用完成以后就要通过 free_irq 函数释放掉相应的中断。如果中断不是共享的,那么 free_irq 会

删除中断处理函数并且禁止中断。free_irq 函数原型如下所示

函数

void free_irq(unsigned int irq,void *dev)

irq

要释放的中断

dev

如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数的时候才会被禁止掉。

返回值

功能

释放掉相应的中断

4、中断处理函数

使用 request_irq 函数申请中断的时候需要设置中断处理函数,中断处理函数函数如下表所示:

函数

irqreturn_t (*irq_handler_t) (int, void *)

第一个参数

要中断处理函数要相应的中断号

第二个参数

是一个指向 void 的指针,也就是个通用指针,需要与 request_irq 函数的 dev 参数保持一致。用于区分共享中断的不同设备,dev 也可以指向设备数据结构。

返回值

中断处理函数的返回值为 irqreturn_t 类型

irqreturn_t 类型定义如下所示:

 enum irqreturn {

     IRQ_NONE = (0 << 0),

     IRQ_HANDLED = (1 << 0),

     IRQ_WAKE_THREAD = (1 << 1),

     };

 typedef enum irqreturn irqreturn_t;

可以看出 irqreturn_t 是个枚举类型,一共有三种返回值。一般中断服务函数返回值使用如下形式

return IRQ_RETVAL(IRQ_HANDLED)

5、中断使能和禁止函数

 常用的中断使用和禁止函数如下所示:

void enable_irq(unsigned int irq)

void disable_irq(unsigned int irq)

enable_irq 和 disable_irq 用于使能和禁止指定的中断,irq 就是要禁止的中断号。disable_irq 函数要

等到当前正在执行的中断处理函数执行完才返回,因此使用者需要保证不会产生新的中断,并且确保所有

已经开始执行的中断处理程序已经全部退出。在这种情况下,可以使用另外一个中断禁止函数:

void disable_irq_nosync(unsigned int irq)

disable_irq_nosync 函数调用以后立即返回,不会等待当前中断处理程序执行完毕。

57.1.3 中断上文和中断下文

中断的存在可以极大的提高CPU的运行效率,但是中断会打断内核进程中的正常调度和运行,所以为保证系统实时性,中断服务程序必须足够简短,但实际应用中某些时候发生中断时必须处理大量的事物,这时候如果都在中断服务程序中完成,则会严重降低中断的实时性,基于这个原因,linux 系统提出了一个概念:把中断服务程序分为两部分:中断上文和中断下文。

有些资料中也将顶半部和底半部称为上半部和下半部,都是一个意思。Linux 内核将中断分为顶半部和

底半部的主要目的就是实现中断处理函数的快进快出,那些对时间敏感、执行速度快的操作可以放到中断

处理函数中,也就是顶半部。剩下的所有工作都可以放到底半部去执行,至于哪些代码要在顶半部完成,哪些代码要在底半部完成,并没有严格的要求,要根据实际情况来判断,下面有一些参考点:

① 如果要处理的内容不希望被其他中断打断,那么可以放到上半部。

② 如果要处理的任务对时间敏感,可以放到上半部。

③ 如果要处理的任务与硬件有关,可以放到上半部

④ 除了上述三点以外的其他任务,优先考虑放到下半部。

中断上文:完成尽可能少却比较急的任务,中断上文的特点就是响应速度快。中断下文:处理中断剩余的大量比较耗时间的任务,而且可以被新的中断打断。

举例来说,我现在正在厨房做饭,突然电话响了,然后我关火去接电话,快递员打电话让我下楼去拿快递,接完电话叫我女朋友去下楼拿快递,然后我在回去开火继续做饭,这个过程就是中断上下文。

分析例子:快递员打电话让我下去拿快递,这个事情很紧急,所以要快速处理,这个就是要在中断上文中完成。但是下楼拿快递这个过程非常耗时间,所以叫女朋友去拿快递,这个就是中断下文。下楼拿快递很耗时间,如果我不叫女朋友去帮我拿而是自己拿,等我拿完饭回来我锅里的菜是不是就凉了呀,同理,如果你在中断里面做很耗时间的时间,系统就会崩溃。如果女朋友在去拿快递的过程中,突然口渴了,要去超市买水,所以,中断下半部分是可以被中断打断的。

总之,中断上文越快越好,中断下文可以做比较耗时间的事情,但是不能死循环。Linux中断可以嵌套吗?以前是可以,现在不可以。

57.1.4 设备树中的中断节点

如果一个设备需要用到中断功能,开发人员就需要在设备树中配置好中断属性信息,因为设备树是用来描述硬件信息的,然后Linux内核通过设备树配置的中断属性来配置中断功能。对于中断控制器而言,打开/home/topeet/linux/linux-imx/arch/arm64/boot/dts/freescale/itop8mm-evk.dtsii文件,其中的 gic节点就是 IMX8MM的中断控制器节点,节点内容如下所示:

gic: interrupt-controller@38800000 {compatible = "arm,gic-v3";reg = <0x0 0x38800000 0 0x10000>, /* GIC Dist */<0x0 0x38880000 0 0xC0000>; /* GICR (RD_base + SGI_base) */#interrupt-cells = <3>;interrupt-controller;interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH>;interrupt-parent = <&gic>;};

简单说明:

IMX8MM使用中断控制器是 gic-v3。

gic-v3 是 ARM Generic Interrupt Controller, version 3 的缩写,是一款 ARM 出品的通用中断控制器。

AArch64 SMP 内核通常与 GICv3 搭配使用,GICv3 提供了专用外设中断(PPI),共享外设中断(SPI),软件生成的中断(SGI)和特定于区域的外设中断(LPI)。

#interrupt-cells = <3> 表明 Interrupt client devices 需要用 3个单元才能确定引用的中断,例如 interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH>;。interrupt-controller 节点为空,表示当前节点是中断控制器。

对于 gpio 来说,gpio 节点也可以作为中断控制器,比如 imx6ull.dtsi 文件中的 gpio5 节点内容如下所示:

1 gpio5 : gpio @020ac000{
2     compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
3     reg = <0x020ac000 0x4000>;
4     interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;
5     gpio-controller;
6     #gpio-cells = <2>;
7     interrupt-controller;
8     #interrupt-cells = <2>;
9    };

第 4 行,interrupts 描述中断源信息,对于 gpio5 来说一共有两条信息,中断类型都是 SPI,触发电

平都是 IRQ_TYPE_LEVEL_HIGH。不同之处在于中断源,一个是 74,一个是 75,可以打开参考手册的“Chapter 3 Interrupts and DMA Events”章节,找到表 3-1,有如图所示的内容:

从上图可以看出,GPIO5 一共用了 2 个中断号,一个是 74,一个是 75。其中 74 对应 GPIO5_IO00~GPIO5_IO15 这低 16 个 IO,75 对应 GPIO5_IO16~GPIOI5_IO31 这高 16 位 IO。

第 7 行,interrupt-controller 表明了 gpio5 节点也是个中断控制器,用于控制 gpio5 所有 IO的中断。

第 8 行,将#interrupt-cells 修改为 2。

简单总结下与中断有关的设备树属性信息

  • #interrupt-cells,指定中断源的信息 cells 个数。 
  • interrupt-controller,表示当前节点为中断控制器 
  • interrupts,指定中断号,触发方式等。 
  • interrupt-parent,指定父中断,也就是中断控制器 

中断实际上是非常复杂的,但是作为开发人员,我们只需要关心怎么在设备树中指定中断,怎么在代码中获得中断就可以。其他的事情,比如设备树中的中断控制器,这些都是由原厂的BSP工程师帮我们写好了,我们不需要来修改他。除非将来你有机会去原厂工作,否则我们不会从头开始写一个设备树文件的,分工是非常明确的。我们需要关注的点是怎么在设备树里面描述一个外设的中断节点,我们来看一个例子。

ft5x06_ts@38 {compatible = "edt,edt-ft5x06";reg = <0x38>;pinctrl-names = "defaults";pinctrl-0 = <&pinctrl_ft5x06_int>;interrupt-parent = <&gpio1>;interrupts = <15 2>;status = "okay";
};
pinctrl_ft5x06_int: ft5x06_int {fsl,pins = </*MX8MM_IOMUXC_GPIO1_IO09_GPIO1_IO9               0x159*/MX8MM_IOMUXC_GPIO1_IO15_GPIO1_IO15              0x159MX8MM_IOMUXC_SAI5_RXD2_GPIO3_IO23               0x41>;};

在这个例子中,我们先使用pinctrl和gpio子系统把这个引脚设置为了gpio功能,因为我们在使用中断的时候需要把引脚设置成输入。然后使用interrupt-parent和interrupts属性来描述中断。interrupt-parent的属性值是gpio1,也就是他的要使用gpio1这个中断控制器,为什么是gpio1呢,因为我们的引脚使用的是gpio1里面的io15,所以我们使用的是gpio1这个中断控制器。interrupts属性设置的是中断源,为什么里面是两个cells呢,因为我们在gpio1这个中断控制器里面#interrupt-cells的值为2,如下图所示:

例子中的第一个 cells 的 15表示 GPIO1 组的 15号 IO。2表示下降沿有效。

IRQ_TYPE_EDGE_BOTH 定义在文件 include/linux/irq.h 中,定义如下 

所以我们在设备树里面配置中断的时候只需要两个步骤即可,第一个步骤是把管脚设置为gpio功能。第二个步骤是使用interrupt-parent和interrupts属性来描述中断。

57.2 实验程序编写

我们以iMX8MM开发板为例,,写一个按键中断,按一下按键就让在终端上打印一句话,这个程序是非常简单的,因为他没有涉及到中断下文的编写,只有中断上文。

57.2.1 修改设备树文件

我们修改设备树文件itop8mm-evk.dtsi,路径在源码目录/home/topeet/linux/linux-imx/arch/arm64/boot/dts/freescale/目录下。我们要使用开发板底板上的音量+键进行试验,首先将原来的节点注释掉,如下图所示:

 然后在根节点下,添加以下内容。

注释掉以前修改的内容,如下图所示

然后编译源码,编译源码请参考IMX8MM开发板使用手册。然后重新烧写Linux镜像,接下来我们来检查你编译的设备树文件有没有被加载到系统里面,也就是说查看你添加的节点有没有,如下图所示: 

 从上图可以看到我们添加的节点,我们查看下节点“test”,如下图所示:

从上图可以看出“compatible”属性值是keys。

57.2.2 编写驱动代码

程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\014-Linux中断实验\001”路径下。

我们在Ubuntu的/home/topeet/imx8mm/14/001目录下新建driver.c,将上次编译driver.c的Makefile文件和build.sh拷贝到新建的driver.c同级目录下,修改Makefile为:

obj-m += driver.o
KDIR:=/home/topeet/linux/linux-imx
PWD?=$(shell pwd)
all:make -C $(KDIR) M=$(PWD) modules ARCH=arm64
clean:make -C $(KDIR) M=$(PWD) clean

完整的驱动代码如下面所示:

/** @Author:topeet* @Description: 使用irq_of_parse_and_map函数来获取中断号*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
//定义结构体表示我们的节点
struct device_node *test_device_node;
struct property *test_node_property;
//要申请的中断号
int irq;
// GPIO 编号
int gpio_nu;/*** @description: 中断处理函数test_key* @param {int} irq :要申请的中断号* @param {void} *args :* @return {*}IRQ_HANDLED*/
irqreturn_t test_key(int irq, void *args)
{printk("test_key \n");return IRQ_RETVAL(IRQ_HANDLED);
}
/***************************************************************************************** @brief led_probe : 与设备信息层(设备树)匹配成功后自动执行此函数,* @param inode : 文件索引* @param file  : 文件* @return 成功返回 0           ****************************************************************************************/
int led_probe(struct platform_device *pdev)
{int ret = 0;// 打印匹配成功进入probe函数printk("led_probe\n");test_device_node = of_find_node_by_path("/test");if (test_device_node == NULL){//查找节点失败则打印信息printk("of_find_node_by_path is error \n");return -1;}gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0);if (gpio_nu < 0){printk("of_get_namd_gpio is error \n");return -1;}//设置GPIO为输入模式gpio_direction_input(gpio_nu);//获取GPIO对应的中断号//irq = gpio_to_irq(gpio_nu);irq = irq_of_parse_and_map(test_device_node, 0);printk("irq is %d \n", irq);/*申请中断,irq:中断号名字  test_key:中断处理函数IRQF_TRIGGER_RISING:中断标志,意为上升沿触发"test_key":中断的名字*/ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL);if (ret < 0){printk("request_irq is error \n");return -1;}return 0;
}int led_remove(struct platform_device *pdev)
{printk("led_remove\n");return 0;
}
const struct platform_device_id led_idtable = {.name = "keys",
};
const struct of_device_id of_match_table_test[] = {{.compatible = "keys"},{},
};
struct platform_driver led_driver = {//3. 在led_driver结构体中完成了led_probe和led_remove.probe = led_probe,.remove = led_remove,.driver = {.owner = THIS_MODULE,.name = "led_test",.of_match_table = of_match_table_test},//4 .id_table的优先级要比driver.name的优先级要高,优先与.id_table进行匹配.id_table = &led_idtable};/*** @description: 模块初始化函数* @param {*}* @return {*}*/
static int led_driver_init(void)
{//1.我们看驱动文件要从init函数开始看int ret = 0;//2.在init函数里面注册了platform_driverret = platform_driver_register(&led_driver);if (ret < 0){printk("platform_driver_register error \n");}printk("platform_driver_register ok \n");return 0;
}/*** @description: 模块卸载函数* @param {*}* @return {*}*/
static void led_driver_exit(void)
{free_irq(irq, NULL);platform_driver_unregister(&led_driver);printk("goodbye! \n");
}
module_init(led_driver_init);
module_exit(led_driver_exit);MODULE_LICENSE("GPL");

57.2.3 运行测试

编译驱动代码为驱动模块,编译成功如下图所示:

我们进入共享文件夹,加载驱动模块,如下图所示:

那么我们怎么查看是否申请中断成功了呢?我们可以进入到开发板的/proc/interrupts目录查看,如下图所示:

cat /proc/interrupts | grep test_key

我们按一下底板上的音量+(VOL+),可以看到控制台打印信息,如下图所示: 

我们可以看中断发生了几次,我们可以进入到开发板的/proc/irq/196/spurious 目录下,如下图所示:

cat /proc/irq/196/spurious

 

从上图可知,中断发生了15次,我们可以数一下test_key打印的次数是十五次,说明发生一次中断,会打印一次test_key。

57.2.4 优化方案

上面的例子我们是使用函数gpio_to_irq来获取中断号的,接下来我们通过在设备树文件里面使用属性“interrupt-parent”和“interrupts”来获取中断号,我们修改设备树文件如下图所示:

 

重新编译源码,然后烧写设备树镜像,启动开发板后如下图所示: 

我们检查一下有没有我们添加的节点,如下图所示,出现添加的设备节点test。 

接下来我们改一下驱动driver.c,完整代码如下所示:

程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\014-Linux中断实验\002”路径下。

/** @Author:topeet* @Description: 使用gpio_to_irq函数来获取中断号*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
//定义结构体表示我们的节点
struct device_node *test_device_node;
struct property *test_node_property;
//要申请的中断号
int irq;
// GPIO 编号
int gpio_nu;/*** @description: 中断处理函数test_key* @param {int} irq :要申请的中断号* @param {void} *args :* @return {*}IRQ_HANDLED*/
irqreturn_t test_key(int irq, void *args)
{printk("test_key \n");return IRQ_HANDLED;
}
/***************************************************************************************** @brief led_probe : 与设备信息层(设备树)匹配成功后自动执行此函数,* @param inode : 文件索引* @param file  : 文件* @return 成功返回 0           ****************************************************************************************/
int led_probe(struct platform_device *pdev)
{int ret = 0;// 打印匹配成功进入probe函数printk("led_probe\n");test_device_node = of_find_node_by_path("/test");if (test_device_node == NULL){//查找节点失败则打印信息printk("of_find_node_by_path is error \n");return -1;}gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0);if (gpio_nu < 0){printk("of_get_namd_gpio is error \n");return -1;}//设置GPIO为输入模式gpio_direction_input(gpio_nu);//获取GPIO对应的中断号// irq = gpio_to_irq(gpio_nu);irq =irq_of_parse_and_map(test_device_node,0);printk("irq is %d \n", irq);/*申请中断,irq:中断号名字  test_key:中断处理函数IRQF_TRIGGER_RISING:中断标志,意为上升沿触发"test_key":中断的名字*/ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL);if (ret < 0){printk("request_irq is error \n");return -1;}return 0;
}int led_remove(struct platform_device *pdev)
{printk("led_remove\n");return 0;
}
const struct platform_device_id led_idtable = {.name = "keys",
};
const struct of_device_id of_match_table_test[] = {{.compatible = "keys"},{},
};
struct platform_driver led_driver = {//3. 在led_driver结构体中完成了led_probe和led_remove.probe = led_probe,.remove = led_remove,.driver = {.owner = THIS_MODULE,.name = "led_test",.of_match_table = of_match_table_test},//4 .id_table的优先级要比driver.name的优先级要高,优先与.id_table进行匹配.id_table = &led_idtable};/*** @description: 模块初始化函数* @param {*}* @return {*}*/
static int led_driver_init(void)
{//1.我们看驱动文件要从init函数开始看int ret = 0;//2.在init函数里面注册了platform_driverret = platform_driver_register(&led_driver);if (ret < 0){printk("platform_driver_register error \n");}printk("platform_driver_register ok \n");return 0;
}/*** @description: 模块卸载函数* @param {*}* @return {*}*/
static void led_driver_exit(void)
{free_irq(irq, NULL);platform_driver_unregister(&led_driver);printk("gooodbye! \n");
}
module_init(led_driver_init);
module_exit(led_driver_exit);MODULE_LICENSE("GPL");

 编译驱动代码为驱动模块,如下图所示:

编译成功加载驱动,可以成功获得irq号,如下图所示: 

 我们按一下底板上的音量+(VOL+),可以看到控制台打印信息,如下图所示:

 

我们可以看中断发生了几次,我们可以进入到开发板的/proc/irq/196/spurious 目录下,如下图所示:

cat /proc/irq/196/spurious

 

从上图可知,中断发生了9次,我们可以数一下test_key打印的次数是9次,说明发生一次中断,会打印一次test_key。 

相关文章:

【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第五十七章 Linux中断实验

i.MX8MM处理器采用了先进的14LPCFinFET工艺&#xff0c;提供更快的速度和更高的电源效率;四核Cortex-A53&#xff0c;单核Cortex-M4&#xff0c;多达五个内核 &#xff0c;主频高达1.8GHz&#xff0c;2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT…...

每日一题~961div2A+B+C(阅读题,思维,数学log)

A 题意&#xff1a;给你 n*n 的表格和k 个筹码。每个格子上至多放一个 问至少占据多少对角线。 显然&#xff0c;要先 格数的多的格子去放。 n n-1 n-2 …1 只有n 的是一个&#xff08;主对角线&#xff09;&#xff0c;其他的是两个。 #include <bits/stdc.h> using na…...

Fireflyrk3288 ubuntu18.04添加Qt开发环境、安装mysql-server

1、创建一台同版本的ubuntu18.04的虚拟机 2、下载rk3288_ubuntu_18.04_armhf_ext4_v2.04_20201125-1538_DESKTOP.img 3、创建空img镜像容器 dd if/dev/zero ofubuntu_rootfs.img bs1M count102404、将该容器格式化成ext4文件系统 mkfs.ext4 ubuntu_rootfs.img5、将该镜像文件…...

简化mybatis @Select IN条件的编写

最近从JPA切换到Mybatis&#xff0c;使用无XML配置&#xff0c;Select注解直接写到interface上&#xff0c;发现IN条件的编写相当麻烦。 一般得写成这样&#xff1a; Select({"<script>","SELECT *", "FROM blog","WHERE id IN&quo…...

Windows图形界面(GUI)-MFC-C/C++ - Control

公开视频 -> 链接点击跳转公开课程博客首页 -> ​​​链接点击跳转博客主页 目录 Control 资源编辑器 添加控件 设置控件属性 添加控件变量 添加消息处理 处理控件事件 控件焦点顺序 Control 资源编辑器 资源编辑器&#xff1a;用于可视化地编辑对话框和控件。…...

SQL Server数据库安全:策略制定与实践指南

SQL Server数据库安全&#xff1a;策略制定与实践指南 在当今数字化时代&#xff0c;数据安全是每个组织的核心关注点。SQL Server作为广泛使用的关系型数据库管理系统&#xff0c;提供了一套强大的安全特性来保护存储的数据。制定有效的数据库安全策略是确保数据完整性、可用…...

Spring Boot入门指南:留言板

一.留言板 1.输⼊留⾔信息,点击提交.后端把数据存储起来. 2.⻚⾯展⽰输⼊的表⽩墙的信息 规范&#xff1a; 1.写一个类MessageInfo对象&#xff0c;添加构造方法 虽然有快捷键&#xff0c;但是还是不够偷懒 项目添加Lombok。 Lombok是⼀个Java⼯具库&#xff0c;通过添加注…...

Docker 中安装和配置带用户名和密码保护的 Elasticsearch

在 Docker 中安装和配置带用户名和密码保护的 Elasticsearch 需要以下步骤。Elasticsearch 的安全功能&#xff08;包括基本身份验证&#xff09;在默认情况下是启用的&#xff0c;但在某些版本中可能需要手动配置。以下是详细步骤&#xff0c;包括如何设置用户名和密码。 1. …...

面试官:说说JVM内存调优及内存结构

1. JVM简介 JVM&#xff08;Java虚拟机&#xff09;是运行Java程序的平台&#xff0c;它使得Java能够跨平台运行。JVM负责内存的自动分配和回收&#xff0c;减轻了程序员的负担。 2. JVM内存结构 运行时数据区是JVM中最重要的部分&#xff0c;包含多个内存区域&#xff1a; …...

Ansible的脚本-----playbook剧本【下】

目录 实战演练六&#xff1a;tags 模块 实战演练七&#xff1a;Templates 模块 实战演练六&#xff1a;tags 模块 可以在一个playbook中为某个或某些任务定义“标签”&#xff0c;在执行此playbook时通过ansible-playbook命令使用--tags选项能实现仅运行指定的tasks。 playboo…...

Mysql开启远程控制简化版,亲测有效

首先关闭防火墙 改表法 打开上图的CMD&#xff0c;输入密码进入&#xff0c;然后输入一下指令 1.use mysql; 2.update user set host % where user root;//更新root用户的权限&#xff0c;允许任何主机连接 3.FLUSH PRIVILEGES;//刷新权限&#xff0c;使更改生效 具体参考…...

【MQTT协议与IoT通信】MQTT协议的使用和管理

MQTT协议与IoT通信&#xff1a;MQTT协议的使用和管理 目录 引言MQTT协议概述 什么是MQTTMQTT的工作原理 MQTT协议的关键特性 轻量级与高效性发布/订阅模式质量服务等级(QoS)持久会话安全性 MQTT协议的使用方法 设置MQTT Broker连接MQTT Client发布消息订阅主题断开连接 MQTT协…...

根据题意写出完整的css,html和js代码【购物车模块页面及功能实现】

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…...

AWS免费层之后:了解和管理您的云服务成本

Amazon Web Services (AWS) 为新用户提供了12个月的免费层服务&#xff0c;这是许多人开始使用云服务的绝佳方式。但是&#xff0c;当这一年结束后&#xff0c;您的AWS使用会如何变化&#xff1f;我们九河云通过本文将探讨免费层结束后的AWS成本情况&#xff0c;以及如何有效管…...

Linux定时同步系统时间到硬件时间

Linux定时同步系统时间到硬件时间 1. 系统时间、软件时间 系统时间 &#xff08;System Time&#xff09;&#xff1a; 一般说来就是我们执行 date命令看到的时间&#xff0c;linux系统下所有的时间调 用&#xff08;除了直接访问硬件时间的命令&#xff09;都是使用的这个时…...

网络编程——wireshark抓包、tcp粘包

目录 一、前言 1.1 什么是粘包 1.2 为什么UDP不会粘包 二、编写程序 文件树 客户端程序 服务器程序 tcp程序 头文件 makefile 三、 实验现象 四、改进实验 五、小作业 一、前言 最近在做网络芯片的驱动&#xff0c;验证功能的时候需要借助wireshark这个工具&…...

el-table合计行更新问题

说明&#xff1a;在使用el-table自带的底部合计功能时&#xff0c;初始界面不会显示合计内容 解决方案&#xff1a;使用 doLayout()方法 updated() {this.$nextTick(() > {this.$refs[inventorySumTable].doLayout();});},完整代码&#xff1a; // show-summary&#xff1a…...

ChatGPT:数据库不符合第二范式示例

ChatGPT&#xff1a;数据库不符合第二范式示例 这张图片为什么不符合数据库第二范式 这个表格不符合数据库第二范式&#xff08;2NF&#xff09;的原因如下&#xff1a; 1. 数据库第二范式&#xff08;2NF&#xff09;定义 第二范式要求一个数据库表格在满足第一范式&#xf…...

27、美国国家冰雪中心(NSIDC)海冰密集度月数据下载与处理

文章目录 一、前言二、数据下载三、使用Ponply查看数据结构四、代码一、前言 处理美国国家冰雪中心(NSIDC)的海冰密集度月度数据时,坐标转换是一个重要的步骤。NSIDC提供的数据通常采用极地球面坐标系,需要将其转换为常用的地理坐标系(如经纬度)以便进行分析和可视化。 坐…...

vite环境下使用bootstrap

环境 nodejs 18 pnpm 初始化 pnpm init pnpm add -D vite --registry http://registry.npm.taobao.org pnpm add bootstrap popperjs/core --registry http://registry.npm.taobao.org pnpm add -D sass --registry http://registry.npm.taobao.org新建vite.config.js cons…...

oracle与MySQL数据库之间数据同步的技术要点

Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异&#xff0c;它们的数据同步要求既要保持数据的准确性和一致性&#xff0c;又要处理好性能问题。以下是一些主要的技术要点&#xff1a; 数据结构差异 数据类型差异&#xff…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级

在互联网的快速发展中&#xff0c;高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司&#xff0c;近期做出了一个重大技术决策&#xff1a;弃用长期使用的 Nginx&#xff0c;转而采用其内部开发…...

VTK如何让部分单位不可见

最近遇到一个需求&#xff0c;需要让一个vtkDataSet中的部分单元不可见&#xff0c;查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行&#xff0c;是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示&#xff0c;主要是最后一个参数&#xff0c;透明度…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用

1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...

用docker来安装部署freeswitch记录

今天刚才测试一个callcenter的项目&#xff0c;所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...

今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存

文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...

【 java 虚拟机知识 第一篇 】

目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...

Oracle11g安装包

Oracle 11g安装包 适用于windows系统&#xff0c;64位 下载路径 oracle 11g 安装包...

Appium下载安装配置保姆教程(图文详解)

目录 一、Appium软件介绍 1.特点 2.工作原理 3.应用场景 二、环境准备 安装 Node.js 安装 Appium 安装 JDK 安装 Android SDK 安装Python及依赖包 三、安装教程 1.Node.js安装 1.1.下载Node 1.2.安装程序 1.3.配置npm仓储和缓存 1.4. 配置环境 1.5.测试Node.j…...

【汇编逆向系列】六、函数调用包含多个参数之多个整型-参数压栈顺序,rcx,rdx,r8,r9寄存器

从本章节开始&#xff0c;进入到函数有多个参数的情况&#xff0c;前面几个章节中介绍了整型和浮点型使用了不同的寄存器在进行函数传参&#xff0c;ECX是整型的第一个参数的寄存器&#xff0c;那么多个参数的情况下函数如何传参&#xff0c;下面展开介绍参数为整型时候的几种情…...