【北京迅为】《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。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/50684.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AI绘画:艺术与科技融合的新篇章

随着人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;AI绘画作为一种新兴的艺术形式&#xff0c;正逐步改变着传统艺术创作的格局。从早期的简单模仿到如今的个性化创作&#xff0c;AI绘画不仅提升了艺术创作的效率和质量&#xff0c;还开辟了全新的应用场景和商…

C++模板——泛型编程

目录 1. 什么是泛型编程 2. 函数模板 2.1 定义格式 2.2 实例化及原理 2.3 参数匹配原则 3. 类模板 3.1 定义格式 3.2 实例化 4. 非类型模板参数 5. 模板的特化 5.1 概念 5.2 函数模板和类模板特化 6. 模板的分离编译 1. 什么是泛型编程 如何实现一个通用的加…

【Java算法专场】二分查找(下)

目录 山脉数组的峰顶索引 算法分析 算法步骤 算法代码 算法示例 寻找峰值 算法分析 算法步骤 算法代码 算法示例 寻找旋转排序数组中的最小值 算法分析 算法步骤 算法代码 算法示例 点名 算法分析 算法步骤 算法代码 算法示例 山脉数组的峰顶索引 …

TCP/IP协议(全的一b)应用层,数据链层,传输层,网络层,以及面试题

目录 TCP/IP协议介绍 协议是什么,有什么作用? 网络协议为什么要分层 TCP/IP五层网络协议每层的作用 应⽤层 DNS的作用及原理 DNS工作流程 数据链路层 以太⽹帧格式 MAC地址的作用 ARP协议的作⽤ ARP协议的工作流程 MTU以及MTU对 IP / UD / TCP 协议的影响 传输层…

数据结构之判断二叉树是否为搜索树(C/C++实现)

文章目录 判断二叉树是否为搜索树方法一&#xff1a;递归法方法二&#xff1a;中序遍历法总结 二叉树是一种非常常见的数据结构&#xff0c;它在计算机科学中有着广泛的应用。二叉搜索树&#xff08;Binary Search Tree&#xff0c;简称BST&#xff09;是二叉树的一种特殊形式&…

自动化测试--WebDriver API

1. 元素定位方法 通过 ID 定位&#xff1a;如果元素具有唯一的 ID 属性&#xff0c;可以使用 findElement(By.id("elementId")) 方法来定位元素。通过 Name 定位&#xff1a;使用 findElement(By.name("elementName")) 来查找具有指定名称的元素。通过 Cl…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 5键键盘(100分) - 三语言AC题解(Python/Java/Cpp)

🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 🍿 最新华为OD机试D卷目录,全、新、准,题目覆盖率达 95% 以上,支持题目在线…

Linux常用操作

软件安装 CentOS 系统使用&#xff1a; yum [install remove search] [-y] 软件名称 install 安装 remove 卸载 search 搜索 -y &#xff0c;自动确认 Ubuntu 系统使用 apt [install remove search] [-y] 软件名称 install 安装 remove 卸载 search 搜索 -y…

【北京迅为】《i.MX8MM嵌入式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…

前端播放rtsp视频流(最后使用WebRtc)

前端播放rtsp视频流&#xff08;最后使用WebRtc&#xff09; 前言&#xff1a; ​ 项目需要将实验室里的摄像头画面引入到前端页面中&#xff0c;故对目前常见的几种方法进行了尝试&#xff0c;虽然过程坎坷但结局是好的。 一些尝试&#xff1a; RTSPtoWebRtc工具 由于RTSPt…

opengl 写一个3D立方体——计算机图形学编程 第4章 管理3D图形数据 笔记

计算机图形学编程&#xff08;使用OpenGL和C&#xff09; 第4章 管理3D图形数据 笔记 数据处理 想要绘制一个对象&#xff0c;它的顶点数据需要发送给顶点着色器。通常会把顶点数据在C端放入 一个缓冲区&#xff0c;并把这个缓冲区和着色器中声明的顶点属性相关联。 初始化立…

力扣 二分查找

二分查找基础篇。 题目 class Solution {public int searchInsert(int[] nums, int target) {int l 0, r nums.length - 1;while(l < r) {int mid l((r-l)>>1);//(lr)/2if(nums[mid]<target)lmid1;else rmid-1;}return l;//处理边界&#xff0c;设定数组的左半…

21 Python常用内置函数——zip()

zip() 函数用来把多个可迭代对象中的元素压缩到一起&#xff0c;返回一个可迭代的 zip 对象&#xff0c;其中每个元素都是包含原来的多个可迭代对象对应位置上元素的元组&#xff0c;最终结果中包含的元素个数取决于所有参数序列或可迭代对象中最短的那个。 可以这样理解这个函…

论文阅读——Design of Environmental backscatter tag antenna for 5G Internet of things

文章目录 摘要一、背景二、系统模型三、天线设计A. 指标B. 天线结构描述C. 天线结构优化D. 天线结构确定 四、仿真结果总结 论文来源&#xff1a;https://ieeexplore.ieee.org/document/9379395 摘要 文章针对传统设备识别在电力物联网场景中存在的可靠性低和读取距离不足的问…

Java智慧养老养老护理帮忙代办陪诊陪护小程序系统源码

&#x1f31f;智慧养老新风尚&#xff0c;护理代办陪诊小程序来帮忙✨ &#x1f3e1;【开篇&#xff1a;关爱老人&#xff0c;从智慧养老开始】&#x1f3e1; 随着社会的进步&#xff0c;智慧养老已成为新时代孝心的体现。面对忙碌的生活节奏&#xff0c;如何更好地照顾家中长…

【北京迅为】《i.MX8MM嵌入式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…

电力系统 | 发电、输电、变电、配电、用电介绍 | 一度电从电厂发出来到用户终端需要经历哪些环节 | 变电站建在哪里

文章目录 一、一度电从电厂发出来到用户终端需要经历哪些环节&#xff1f;二、发电、变电、输电、配售电和用电过程介绍三、变电站建在哪里&#xff1f; 一、一度电从电厂发出来到用户终端需要经历哪些环节&#xff1f; 电力系统是由发电、变电、输电、配售电和用电等环节组成的…

leetcode106. 从中序与后序遍历序列构造二叉树,力扣105姊妹题

leetcode106. 从中序与后序遍历序列构造二叉树 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 示例 1: 输入&#xff1a;inorder [9,3,15,20,7…

活动报名小程序

#活动报名工具# # 活动报名小程序 ## 项目简介 一款通用的活动报名工具&#xff0c;包含活动展示&#xff0c;微信支付&#xff0c;订单管理&#xff0c;分享评价等功能。 品客聚精彩&#xff0c;有你才精彩&#xff01;不只有线下活动还可以进行线上裂变活动。 …

UE4-构建光照后导入的静态网格体变黑

当我们将我们的静态网格体导入到项目当中的时候&#xff0c;此时我们进行重新构建光照&#xff0c;我们在从新构建完光照后&#xff0c;会发现我们的静态网格体全部变黑了&#xff0c;此时是因为没有设置光照贴图分辨率和坐标索引引起的。 将General Settings中的L…