一个能思想的人,才真是一个力量无边的人。
Cortex-A7内核只有8个异常中断,这8个异常中断的中断向量表如下:
向量地址 | 终端类型 | 中断模式 |
---|---|---|
0x00 | 复位中断(Rest) | 特权模式(SVC) |
0x04 | 未定义指令中断(Undefined Instruction) | 未定义指令中止模式(Undef) |
0x08 | 软中断(Software Interrupt,SWI) | 特权模式(SVC) |
0x0C | 指令预取中止中断(Prefetch Abort) | 中止模式 |
0x10 | 数据访问中止中断(Data Abort) | 中止模式 |
0x14 | 未使用(Not Used) | 未使用 |
0x18 | IRQ 中断(IRQ Interrupt) | 外部中断模式(IRQ) |
0x1C | FIQ 中断(FIQ Interrupt) | 快速中断模式(FIQ) |
Cortex-A7的中断控制器叫做GIC。【类比STM32的NVIC】GIC可以开关中断,设置中断优先级!Cortex-A7用到的版本是GIC V2,该版本最多支持8个核。GIC 将众多的中断源分为分为三类:SPI、PPI、SGI。我们重点关注SPI(Shared Peripheral Interrupt,共享中断)。那些外部中断都属于 SPI 中断(注意!不是 SPI 总线那个中断) 。比如按键中断、串口中断等等,这些中断所有的 Core 都可以处理,不限定特定 Core。
Cortex-A 内核 CPU 的所有外部中断都属于这个 IRQ 中断,当任意一个外部中断发生的时候都会触发 IRQ 中断。在 IRQ 中断服务函数里面就可以读取指定的寄存器来判断发生的具体是什么中断,所以我们重点关注IRQ中断!
为了区分不同的中断,引入了中断号的概念。ARM的SPI(共享中断)中断号范围是ID32~ID1019,一共998个中断号。像 GPIO 中断、串口中断等这些外部中断都在这里面 ,至于具体到某个 ID 对应哪个中断,那就由半导体厂商根据实际情况去定义了。
比如I.MX6U 的总共使用了 128 个中断 ID(IRQ0~IRQ127),加上前面属于 PPI 和 SGI 的 32 个中断ID,所以I.MX6U 的中断源共有 128+32=160个,这 128 个中断 ID 对应的中断在《 I.MX6ULL 参考手册》的“ 3.2 Cortex A7 interrupts”小节可以看到!截取部分表格如下:
Linux 系统对中断处理的演进,是使用内核线程来处理中断。关于进程和线程的概念,说起来蛮多的,这里仅简单介绍下进程和线程中断中会用到的知识。
Linux 系统中不仅含有硬件中断,也有软件中断。我们一一道来!
Linux中对于硬件产生的中断,称之为“硬件中断”(hard irq)。如按键中断,串口中断,并且每个硬件中断都有对应的处理函数。
我们带着问题来学习软件中断:
include/linux/interrupt.h
NR_SOFTIRQS
为 10softirq_vec
表示,该数组是个全局数组,实现原型如下
static struct softirq_action softirq_vec[NR_SOFTIRQS];
enum
{
HI_SOFTIRQ=0, /* 高优先级软中断 */
TIMER_SOFTIRQ, /* 定时器软中断 */
NET_TX_SOFTIRQ, /* 网络数据发送软中断 */
NET_RX_SOFTIRQ, /* 网络数据接收软中断 */
BLOCK_SOFTIRQ,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ, /* tasklet 软中断 */
SCHED_SOFTIRQ, /* 调度软中断 */
HRTIMER_SOFTIRQ, /* 高精度定时器软中断 */
RCU_SOFTIRQ, /* RCU 软中断 */
NR_SOFTIRQS
};
怎么触发软件中断?
raise_softirq
,简单地理解就是设置 softirq_veq[nr]
的标记位软件中断使用流程?
open_softirq
函数注册对应的软中断处理函数
void open_softirq(int nr, void (*action)(struct softirq_action *))
raise_softirq
函数触发
void raise_softirq(unsigned int nr)
在 Linux 内核中要想使用某个中断是需要申请的,request_irq 函数用于申请中断,request_irq函数可能会导致睡眠,因此不能在中断上下文或者其他禁止睡眠的代码段中使用request_irq 函数。 request_irq 函数会激活(使能)中断,所以不需要我们手动去使能中断,request_irq 函数原型如下:
static inline int __must_check
request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev)
其中参数含义:
标志 | 描述 |
---|---|
RQF_SHARED | 多个设备共享一个中断线,共享的所有中断都必须指定此标志。如果使用共享中断的话, request_irq 函数的 dev 参数就是唯一区分他们的标志。 |
IRQF_ONESHOT | 单次中断,中断执行一次就结束。 |
IRQF_TRIGGER_NONE | 无触发。 |
IRQF_TRIGGER_RISING | 上升沿触发。 |
IRQF_TRIGGER_FALLING | 下降沿触发。 |
IRQF_TRIGGER_HIGH | 高电平触发。 |
IRQF_TRIGGER_LOW | 低电平触发。 |
使用中断的时候需要通过 request_irq 函数申请,使用完成以后就要通过 free_irq 函数释放掉相应的中断。如果中断不是共享的,那么 free_irq 会删除中断处理函数并且禁止中断。 free_irq函数原型如下所示:
void free_irq(unsigned int irq,
void *dev)
其中参数含义:
使用 request_irq 函数申请中断的时候需要设置中断处理函数,中断处理函数格式如下所示:
irqreturn_t (*irq_handler_t) (int, void *)
其中参数含义:
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)
void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq)//要等到当前正在执行的中断处理函数执行完才返回
void disable_irq_nosync(unsigned int irq) //调用以后立即返回,不会等待当前中断处理程序执行完毕
local_irq_enable()
local_irq_disable()
中断嵌套突然暴发,那么栈将越来越大,栈终将耗尽。所以,为了防止这种情况发生,也是为了简单化中断的处理,在 Linux 系统上中断无法嵌套:即当前中断 A 没处理完之前,不会响应另一个中断 B(即使它的优先级更高)。
在单芯片系统中,假设中断处理很慢,那应用程序在这段时间内就无法执行:系统显得很迟顿。在 SMP 系统中,假设中断处理很慢,那么正在处理这个中断的 CPU 上的其他线程也无法执行。在中断的处理过程中,该 CPU 是不能进行进程调度的,所以中断的处理要越快越好,尽早让其他中断能被处理──进程调度靠定时器中断来实现。
但是,处理某个中断要做的事情就是很多,没办法加快。 比如对于按键中断,我们需要等待几十毫秒消除机械抖动。难道要在 handler 中等待吗?对于计算机来说,这可是一个段很长的时间。怎么办?
这时,“下半部”的处理思想便由此产生。
当一个中断要耗费很多时间来处理时,它的坏处是:在这段时间内,其他中断无法被处理。 换句话说, 在这段时间内,系统是关中断的。如果某个中断就是要做那么多事,我们能不能把它拆分成两部分:紧急的、不紧急的?在 handler 函数里只做紧急的事,然后就重新开中断,让系统得以正常运行;那些不紧急的事,以后再处理,处理时是开中断的。
哪些代码属于上半部,哪些代码属于下半部并没有明确的规定,一切根据实际使用情况去判断,以借鉴的参考点:
中断下半部的实现有很多种方法,先来了解两种主要的: tasklet(小任务)、 work queue(工作队列)。
假设我们把中断分为上半部、下半部。 发生中断时,上半部下半部的代码何时、如何被
调用?
当下半部比较耗时但是能忍受,并且它的处理比较简单时,可以用 tasklet 来处理下半部。 tasklet 是使用软件中断来实现。
注:流程图中的问号,是在判断preempt_count是不是等于0?
假设硬件中断 A 的上半部函数为 irq_top_half_A,下半部为 irq_bottom_half_A。使用情景化的分析,才能理解上述代码的精华。
a. 硬件中断 A 处理过程中,没有其他中断发生:
一开始, preempt_count = 0;
上述流程图①~⑨依次执行,上半部、下半部的代码各执行一次。
b. 硬件中断 A 处理过程中,又再次发生了中断 A:
一开始, preempt_count = 0;
执行到第⑥时,一开中断后,中断 A 又再次使得 CPU 跳到中断向量表。
注意:这时 preempt_count 等于 1,并且中断下半部的代码并未执行。
CPU 又从①开始再次执行中断 A 的上半部代码:
在第①步 preempt_count 等于 2;
在第③步 preempt_count 等于 1;
在第④步发现 preempt_count 等于 1,所以直接结束当前第 2 次中断的处理;
注意:重点来了,第 2 次中断发生后,打断了第一次中断的第⑦步处理。当第 2 次中断 A处理完毕, CPU 会继续去执行第⑦步。
可以看到,发生 2 次硬件中断 A 时,它的上半部代码执行了 2 次,但是下半部代码只
执行了一次。所以,同一个中断的上半部、下半部,在执行时是多对一的关系。
可以看到,在第⑦步里,它会去执行中断 A 的下半部,也会去执行中断 B 的下半部。
所以,多个中断的下半部,是汇集在一起处理的。
总结:
tasklet结构体定义如下,
struct tasklet_struct
{
struct tasklet_struct *next; /* 下一个 tasklet */
unsigned long state; /* tasklet 状态 */
atomic_t count; /* 计数器,记录对 tasklet 的引用数 */
void (*func)(unsigned long); /* tasklet 执行的函数 */
unsigned long data; /* 函数 func 的参数 */
};
tasklet使用流程
tasklet_init
初始化tasklet。
DECLARE_TASKLET
合二为一。tasklet_schedule
函数,这样 tasklet 就可以在合适的时间运行。上述提到的三个函数原型如下:
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);
DECLARE_TASKLET(name, func, data)
void tasklet_schedule(struct tasklet_struct *t)
tasklet 使用示例
/* 定义 taselet */
struct tasklet_struct testtasklet;
/* tasklet 处理函数 */
void testtasklet_func(unsigned long data)
{
/* tasklet 具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
......
/* 调度 tasklet */
tasklet_schedule(&testtasklet);
......
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
......
/* 初始化 tasklet */
tasklet_init(&testtasklet, testtasklet_func, data);
/* 注册中断处理函数 */
request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
......
}
工作队列是另外一种下半部执行方式,工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度。因此如果你要推后的工作可以睡眠那么就可以选择工作队列,否则的话就只能选择软中断或 tasklet。
在中断下半部的执行过程中,虽然是开中断的,期间可以处理各类中断。但是毕竟整个中断的处理还没走完,这期间 APP 是无法执行的。假设下半部要执行 1、 2 分钟,在这 1、 2 分钟里 APP 都是无法响应的。
这谁受得了?
所以,如果中断要做的事情实在太耗时,那就不能用软件中断来做,而应该用内核线程来做:在中断上半部唤醒内核线程。内核线程和 APP 都一样竞争执行, APP 有机会执行,系统不会卡顿。
这个内核线程是系统帮我们创建的,一般是 kworker 线程,内核中有很多这样的线程:
kworker 线程要去“工作队列”(work queue)上取出一个一个“工作”(work),来执行它里面的函数。
那我们怎么使用 work、 work queue 呢?
a. 创建 work:
b. 要执行这个函数时,把 work 提交给 work queue 就可以了:
c. 谁来执行 work 中的函数?
d. 谁把 work 提交给 work queue?
总结:
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func; /* 工作队列处理函数 */
};
struct workqueue_struct {
...
};
struct worker {
...
};
上面三个定义可以得出,每一个工作者线程(worker)都有一个工作队列(workqueue_struct)。工作者线程处理自己工作队列中的所有工作!在实际的驱动开发中,我们只需要定义工作(work_struct)即可,关于工作队列和工作者线程我们基本不用去管。
工作队列使用流程
INIT_WORK
初始化work_struct。
DECLARE_WORK
合二为一。schedule_work
函数,这样 work_struct 就可以在合适的时间运行。上述提到的三个函数原型如下:
#define INIT_WORK(_work, _func) //work 表示要初始化的工作, _func 是工作对应的处理函数。
#define DECLARE_WORK(n, f) //n 表示定义的工作(work_struct), f 表示工作对应的处理函数。
bool schedule_work(struct work_struct *work) //work: 要调度的工作
work_struct 使用示例
/* 定义工作(work) */
struct work_struct testwork;
/* work 处理函数 */
void testwork_func_t(struct work_struct *work);
{
/* work 具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
......
/* 调度 work */
schedule_work(&testwork);
......
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
......
/* 初始化 work */
INIT_WORK(&testwork, testwork_func_t);
/* 注册中断处理函数 */
request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
......
}
使用线程来处理中断,并不是什么新鲜事。 使用 work 就可以实现,但是需要定义 work、调用 schedule_work,好麻烦啊。
太懒了太懒了,就这 2 步你们都不愿意做。好,内核是为懒人服务的,再杀出一个函数:
你可以只提供 thread_fn,系统会为这个函数创建一个内核线程。发生中断时,内核线程就会执行这个函数。
说懒是开玩笑,内核开发者也不会那么在乎懒人。
以前用 work 来线程化地处理中断,一个 worker 线程只能由一个 CPU 执行,多个中断的 work 都由同一个 worker 线程来处理,在单 CPU 系统中也只能忍着了。但是在 SMP 系统中,明明有那么多 CPU 空着,你偏偏让多个中断挤在这个 CPU 上?
新技术 threaded irq,为每一个中断都创建一个内核线程;多个中断的内核线程可以分配到多个 CPU 上执行, 这样便提高了执行效率。
能弄清楚上面这个图,对 Linux 中断系统的掌握也基本到位了。最核心的结构体是 irq_desc,之前为了易于理解,我们前面说在 Linux 内核中有一个中断数组,对于每一个硬件中断,都有一个数组项, 这个数组就是 irq_desc 数组。
注意: 如果内核配置了 CONFIG_SPARSE_IRQ,那么它就会用基数树(radix tree)来代替irq_desc 数组。 SPARSE 的意思是“稀疏”,假设大小为 1000 的数组中只用到 2 个数组项,那不是浪费嘛? 所以在中断比较“稀疏”的情况下可以用基数树来代替数组。
irq_desc 结构体在 include/linux/irqdesc.h 中定义,主要内容如下图:
每一个 irq_desc 数组项中都有一个函数: handle_irq, 还有一个 action 链表。 要理解它们,需要先看中断结构图:
关注上图中的A号中断和B号中断位置!
外部设备 1、外部设备 n 共享一个 GPIO 中断 B,多个 GPIO 中断汇聚到 GIC(通用中断控制器)的 A 号中断, GIC 再去中断 CPU。那么软件处理时就是反过来,先读取 GIC 获得中断号 A,再细分出 GPIO 中断 B,最后判断是哪一个外部芯片发生了中断。
所以, 中断的处理函数来源有三:
假设 irq_desc[A].handle_irq 是 XXX_gpio_irq_handler(XXX 指厂家), 这个函数需要读取芯片的 GPIO 控制器,细分发生的是哪一个 GPIO 中断(假设是 B),再去调用 irq_desc[B].handle_irq。
注意: irq_desc[A].handle_irq 细分出中断后 B,调用对应的irq_desc[B].handle_irq。显然中断 A 是 CPU 感受到的顶层的中断, GIC 中断 CPU 时, CPU 读取 GIC 状态得到中断 A。
比如对于 GPIO 模块向 GIC 发出的中断 B, 它的处理函数是irq_desc[B].handle_irq。BSP 开发人员会设置对应的处理函数,一般是 handle_level_irq 或 handle_edge_irq,从名字上看是用来处理电平触发的中断、边沿触发的中断。
注意:导致 GPIO 中断 B 发生的原因很多,可能是外部设备 1,可能是外部设备 n,可能只是某一个设备,也可能是多个设备。所以 irq_desc[B].handle_irq 会调用某个链表里的函数,这些函数由外部设备提供。这些函数自行判断该中断是否自己产生, 若是则处理。
这里说的“外部设备”可能是芯片,也可能总是简单的按键。它们的处理函数由自己驱动程序提供,这是最熟悉这个设备的“人”: 它知道如何判断设备是否发生了中断,如何处理中断。
对于共享中断,比如 GPIO 中断 B, 它的中断来源可能有多个, 每个中断源对应一个中断处理函数。所以 irq_desc[B]中应该有一个链表,存放着多个中断源的处理函数。一旦程序确定发生了 GPIO 中断 B,那么就会从链表里把那些函数取出来,然后一一执行。这个链表就是 action 链表。链表可以在本部分开头的第一张图片看到。
irqaction 结构体在 include/linux/interrupt.h 中定义,主要内容如下图:
当调用 request_irq、 request_threaded_irq 注册中断处理函数时,内核就会构造一个irqaction 结构体。在里面保存 name、 dev_id 等,最重要的是 handler、 thread_fn、 thread。
里面还有一个名为 sedondary 的 irqaction 结构体,它的作用以后再分析。在 reqeust_irq 时可以传入 dev_id, 为何需要 dev_id? 作用有 2:
irq_data 结构体在 include/linux/irq.h 中定义,主要内容如下图:
它就是个中转站,里面有 irq_chip 指针、irq_domain 指针,都是指向别的结构体。
比较有意思的是 irq和hwirq,其中irq 是软件中断号, hwirq 是硬件中断号。 比如上面我们举的例子,在 GPIO 中断 B 是软件中断号,可以找到 irq_desc[B]这个数组项; GPIO 里的第 x 号中断, 这就是 hwirq。
谁来建立 irq、 hwirq 之间的联系呢?由 irq_domain 来建立。 irq_domain 会把本地的hwirq 映射为全局的 irq,什么意思?比如 GPIO 控制器里有第 1 号中断, UART 模块里也有第 1 号中断,这两个“第 1 号中断”是不一样的,它们属于不同的“域”──irq_domain。
irq_domain 结构体在 include/linux/irqdomain.h 中定义,主要内容如下图:
当我们后面从设备树讲起,如何在设备树中指定中断,设备树的中断如何被转换为 irq时, irq_domain 将会起到极大的作为。这里基于入门的解度简单讲讲,在设备树中你会看到这样的属性:
interrupt-parent = <&gpio1>;
interrupts = <5 IRQ_TYPE_EDGE_RISING>;
它表示要使用 gpio1 里的第 5 号中断, hwirq 就是 5。但是我们在驱动中会使用 request_irq(irq, handler)这样的函数来注册中断, irq 是什么?它是软件中断号 ,它应该从“gpio1 的第 5 号中断”转换得来。
谁把 hwirq 转换为 irq?由 gpio1 的相关数据结构,就是 gpio1 对应的 irq_domain 结构体。
irq_domain 结构体中有一个 irq_domain_ops 结构体,里面有各种操作函数,主要是:
① xlate
用来解析设备树的中断属性, 提取出 hwirq、 type 等信息。
② map
把 hwirq 转换为 irq。
irq_chip 结构体在 include/linux/irq.h 中定义,主要内容如下图:
这个结构体跟“chip”即芯片相关,里面各成员的作用在头文件中也列得很清楚, 摘录部分如下:
* @irq_startup: start up the interrupt (defaults to ->enable if NULL)
* @irq_shutdown: shut down the interrupt (defaults to ->disable if NULL)
* @irq_enable: enable the interrupt (defaults to chip->unmask if NULL)
* @irq_disable: disable the interrupt
* @irq_ack: start of a new interrupt
* @irq_mask: mask an interrupt source
* @irq_mask_ack: ack and mask an interrupt source
* @irq_unmask: unmask an interrupt source
* @irq_eoi: end of interrupt
我们在 request_irq 后,并不需要手工去使能中断,原因就是系统调用对应的 irq_chip 里的函数帮我们使能了中断。
我们提供的中断处理函数中,也不需要执行主芯片相关的清中断操作,也是系统帮我们调用 irq_chip 中的相关函数。
但是对于外部设备相关的清中断操作,还是需要我们自己做的。就像上面图里的“外部设备 1“、“外部设备 n”, 外设备千变万化,内核里可没有对应的清除中断操作。
可参考文档:
内核 Documentation\devicetree\bindings\interrupt-controller\interrupts.txt
在硬件上,“中断控制器”只有 GIC 这一个,但是我们在软件上也可以把上图中的“GPIO”称为“中断控制器”。 因为芯片有多个 GPIO 模块,比如 GPIO1、 GPIO2 等等。所以软件上的“中断控制器”就有很多个: GIC、 GPIO1、 GPIO2 等等。
GPIO1 连接到 GIC, GPIO2 连接到 GIC,所以 GPIO1 的父亲是 GIC, GPIO2 的父亲是GIC。
假设 GPIO1 有 32 个中断源,但是它把其中的 16 个汇聚起来向 GIC 发出一个中断,把另外 16 个汇聚起来向 GIC 发出另一个中断。这就意味着 GPIO1 会用到 GIC 的两个中断,会涉及 GIC 里的 2 个 hwirq。
这些层级关系、中断号(hwirq),都会在设备树中有所体现。
在设备树中,中断控制器节点中必须有一个属性: interrupt-controller,表明它是“中断控制器”。还必须有一个属性: #interrupt-cells,表明引用这个中断控制器的话需要多少个 cell。#interrupt-cells 的值一般有如下取值:
第 2 个 cell 的 bits[3:0] 用来表示中断触发类型(trigger type and level flags):
1 = low-to-high edge triggered,上升沿触发
2 = high-to-low edge triggered,下降沿触发
4 = active high level-sensitive,高电平触发
8 = active low level-sensitive,低电平触发
示例如下:
vic: intc@10140000 {
compatible = "arm,versatile-vic";
interrupt-controller;
#interrupt-cells = <1>;
reg = <0x10140000 0x1000>;
};
如果中断控制器有级联关系,下级的中断控制器还需要表明它的“interrupt-parent”是谁,用了 interrupt-parent”中的哪一个“interrupts”。
一个外设,它的中断信号接到哪个“中断控制器”的哪个“中断引脚”,这个中断的触发方式是怎样的?这 3 个问题,在设备树里使用中断时,都要有所体现。
Interrupts 里要用几个 cell,由 interrupt-parent 对应的中断控制器决定。在中断控制器里有“#interrupt-cells”属性,它指明了要用几个 cell 来描述中断。
比如:
2c@7000c000 {
gpioext: gpio-adnp@41 {
compatible = "ad,gpio-adnp";
interrupt-parent = <&gpio>;
interrupts = <160 1>;
gpio-controller;
#gpio-cells = <1>;
interrupt-controller;
#interrupt-cells = <2>;
};
......
};
③ 新写法: interrupts-extended
一个“interrupts-extended”属性就可以既指定“interrupt-parent”,也指定“interrupts”,比如:
interrupts-extended = <&intc1 5 1>, <&intc2 1 0>;
在 arch/arm/boot/dts 目录下可以看到 2 个文件:imx6ull.dtsi、 100ask_imx6ull-14x14.dts,把里面有关中断的部分内容抽取出来。
从设备树反推 IMX6ULL 的中断体系,如下,比之前的框图多了一个“GPC INTC”:
GPC INTC 的英文是: General Power Controller, Interrupt Controller。它提供中断屏蔽、中断状态查询功能,实际上这些功能在 GIC 里也实现了,个人觉得有点多余。除此之外,它还提供唤醒功能,这才是保留它的原因。
简单总结一下与中断有关的设备树属性信息:
之前我们提到过,设备树中的节点有些能被转换为内核里的 platform_device,有些不能,回顾如下:
一个节点能被转换为 platform_device,如果它的设备树里指定了中断属性,那么可以从platform_device 中获得“中断资源”, 函数如下,可以使用下列函数获得 IORESOURCE_IRQ 资源,即中断号:
/*
* platform_get_resource - get a resource for a device
* @dev: platform device
* @type: resource type // 取哪类资源? IORESOURCE_MEM、 IORESOURCE_REG、IORESOURCE_IRQ 等
* @num: resource index // 这类资源中的哪一个?
*/
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num);
对于 I2C 设备节点, I2C 总线驱动在处理设备树里的 I2C 子节点时,也会处理其中的中断信息。一个 I2C 设备会被转换为一个 i2c_client 结构体,中断号会保存在 i2c_client 的 irq成员里,代码如下(drivers/i2c/i2c-core.c):
对于 SPI 设备节点, SPI 总线驱动在处理设备树里的 SPI 子节点时,也会处理其中的中断信息。一个 SPI 设备会被转换为一个 spi_device 结构体,中断号会保存在 spi_device 的 irq成员里,代码如下(drivers/spi/spi.c):
如果你的设备节点既不能转换为 platform_device,它也不是 I2C 设备,不是 SPI 设备,那么在驱动程序中可以自行调用 of_irq_get 函数去解析设备树,得到中断号。
函数从 interupts 属性中提取到对应的设备号
unsigned int irq_of_parse_and_map(struct device_node *dev,
int index)
参考: drivers/input/keyboard/gpio_keys.c
,可以使用 gpio_to_irq 或 gpiod_to_irq 获得中断号。
举例,假设在设备树中有如下节点:
gpio-keys {
compatible = "gpio-keys";
pinctrl-names = "default";
user {
label = "User Button";
gpios = <&gpio5 1 GPIO_ACTIVE_HIGH>;
gpio-key,wakeup;
linux,code = <KEY_1>;
};
};
那么可以使用下面的函数获得引脚和 flag:
button->gpio = of_get_gpio_flags(pp, 0, &flags);
bdata->gpiod = gpio_to_desc(button->gpio);
再去使用 gpiod_to_irq 获得中断号:
irq = gpiod_to_irq(bdata->gpiod);