点击查看系列文章 =》 Interrupt Pipeline系列文章大纲-CSDN博客
3.3.1 Linux中断的使能与屏蔽
3.3.1.1 中断使能与屏蔽的三重关卡
本章的主题是hard_local_irq_disable(),它是对中断的关闭操作。为了彻底搞清楚中断关闭的机制,这里先对Linux使能与屏蔽中断的API做详细的分析,然后再分析IPIPE做了哪些针对性地改造。这里分析的范围已经超过了hard_local_irq_disable()。
硬件控制器的物理中断发生后,到执行中断处理程序IRQ Handler,这中间要经历3重关卡。
3.3.1.2 第一重关卡IMR
第一重关卡指的是硬件控制器自身的中断屏蔽寄存器IMR。以SPI控制器为例,SPI_IMR[4]为0时代表RX FIFO FULL中断被屏蔽,为1时代表RX FIFO FULL中断被使能。在driver/spi/spi-rockchip.c驱动中,由驱动自行根据需要来管理中断屏蔽位。例如使能RX FIFO FULL中断,则调用:
writel_relaxed(INT_RF_FULL, rs->regs + ROCKCHIP_SPI_IMR);
3.3.1.3 第二重关卡中断控制器的使能bit
第二重关卡指的是中断控制器的使能bit。以GIC V3为例,每个中断号都有一个enable bit,可以通过GICD_ISENABLER<n>寄存器来set-enable使能中断,通过GICD_ICENABLER<n>寄存器来clear-enable屏蔽中断。在kernel/irq/manage.c,定义了如下接口:
//关闭中断,在非中断处理程序中使用,会等待中断处理程序完成
void disable_irq(unsigned int irq)
//关闭中断:在中断处理程序中使用,不会等待,避免自己等待自己造成死锁
void disable_irq_nosync(unsigned int irq)
//使能中断
void enable_irq(unsigned int irq)
以disable_irq为例,来追踪一下是如何修改中断控制器的使能bit的:
disable_irq <kernel/irq/manage.c>
-> __disable_irq_nosync <kernel/irq/manage.c>
-> __disable_irq <kernel/irq/manage.c>
-> irq_disable <kernel/irq/chip.c>
-> __irq_disable <kernel/irq/chip.c>
接下来__irq_disable开始了一顿让人迷惑的操作,下面展开说一下。
第1行,第一个入参struct irq_desc *desc是指向中断描述符的指针,为方便讨论,以下简称中断描述符。第二个入参mask涉及到内核的一个精巧的设计。先看一下这个参数是如何传递下来的。
kernel/irq/chip.c:
void irq_disable(struct irq_desc *desc)
{
__irq_disable(desc, irq_settings_disable_unlazy(desc));
}
kernel/irq/settings.h
static inline bool irq_settings_disable_unlazy(struct irq_desc *desc)
{
return desc->status_use_accessors & _IRQ_DISABLE_UNLAZY;
}
根据上述代码可以知道,mask的值取决于中断描述符是否设置了标志位_IRQ_DISABLE_UNLAZY。那到底是否定义了呢?在分配sturct irq_desc的过程,desc->status_use_accessors是初始化为0的,所以_IRQ_DISABLE_UNLAZY是没有置位的,入参mask的值为0。为了后续方便讨论,把这种默认情况称为UNLAZY模式。
alloc_desc
->desc_set_defaults
-> irq_settings_clr_and_set(desc, ~0, _IRQ_DEFAULT_INIT_FLAGS)
#ifndef ARCH_IRQ_INIT_FLAGS
# define ARCH_IRQ_INIT_FLAGS 0
#endif
#define IRQ_DEFAULT_INIT_FLAGS ARCH_IRQ_INIT_FLAGS
第3行,调用irqd_irq_disabled判断中断描述符是否已经处于IRQD_IRQ_DISABLED。如果是IRQD_IRQ_DISABLED,因为mask默认是0,所以其实什么都不做。如果不是IRQD_IRQ_DISABLED,跳转到第7行。
第7行,调用irq_state_set_disabled,将中断描述符设置为IRQD_IRQ_DISABLED。
第8行,判断中断描述符所在的中断控制器是否定义了回调函数desc->irq_data.chip->irq_disable。以drivers/irqchip/irq-gic-v3.c为例,static struct irq_chip gic_chip并没有定义此回调函数,所以第8行的判断不成立,第9~10行不会执行,直接跳转到第11行。
第11行,因为mask为0,所以判断不成立。
综上所述,在默认的UNLAZY模式下,__irq_disable仅仅设置了一个IRQD_IRQ_DISABLED标记,并没有真正的操作中断控制器的使能bit呀?是的,在调用disable_irq <kernel/irq/manage.c>关闭某个中断号后,如果中断发生了,是会触发中断处理流程的,以GIC V3为例,中断处理流程走到irq_desc->handle_irq->handle_fasteoi_irq<kernel/irq/chip.c>时,会检查IRQD_IRQ_DISABLED标记。如果IRQD_IRQ_DISABLED标记置位了,则调用mask_irq(desc)清空中断控制器的使能bit: desc->irq_data.chip->irq_mask(&desc->irq_data)->gic_mask_irq-> gic_poke_irq(d, GICD_ICENABLER)
kernel/irq/chip.c:
void mask_irq(struct irq_desc *desc)
{
if (irqd_irq_masked(&desc->irq_data))
return;
if (desc->irq_data.chip->irq_mask) {
desc->irq_data.chip->irq_mask(&desc->irq_data);
irq_state_set_masked(desc);
}
}
drivers/irqchip/irq-gic-v3.c:
static struct irq_chip gic_chip = {
.name = "GICv3",
.irq_mask = gic_mask_irq,
.irq_unmask = gic_unmask_irq,
……
}
static void gic_mask_irq(struct irq_data *d)
{
gic_poke_irq(d, GICD_ICENABLER);
}
绕了这么一大圈,好处是啥?调用disable_irq <kernel/irq/manage.c>关闭某个中断号后,是不一定有中断发生的。如果在调用enable_irq <kernel/irq/manage.c>之前,确实如预测的一样,没有中断发生,那么disable_irq就省掉了一次对中断控制器寄存器(使能bit)的操作。
3.3.1.4 第三重关卡
第三重关卡指的是CPU core的异常掩码标志。以ARM64为例,DAIF寄存器用来控制异常是否被屏蔽,具体状态通过PSTATE.DAIF查看。
CPU的每个core的DAIF时独立的,所以对DAIF操作的函数都有local_前缀,并且有两对API:
include/linux/irqflags.h:
//第一对API
#define local_irq_enable() do { raw_local_irq_enable(); } while (0)
#define local_irq_disable() do { raw_local_irq_disable(); } while (0)
//第二对API
#define local_irq_save(flags) \
do { \
raw_local_irq_save(flags); \
} while (0)
#define local_irq_restore(flags) do { raw_local_irq_restore(flags); } while (0)
两对API的区别是什么?一个粗暴,一个温柔。
local_irq_disable()/local_irq_enable是简单粗暴,直接关闭和打开中断。以ARM64为例,最终调用arch_local_irq_disable和arch_local_irq_enable来操作DAIF。
arch/arm64/include/asm/irqflags.h:
static inline void arch_local_irq_enable(void)
{
asm volatile(
"msr daifclr, #2 // arch_local_irq_enable"
:
:
: "memory");
}
static inline void arch_local_irq_disable(void)
{
asm volatile(
"msr daifset, #2 // arch_local_irq_disable"
:
:
: "memory");
}
简单粗暴是要付出代价的。在调用local_irq_disable()之前,DAIF可能已经处于关闭状态了。当配对使用local_irq_enable时,肯定会直接打开DAIF,这不就破坏了原来DAIF关闭的状态了,无法回到当初的状态了。
local_irq_save(flags)和local_irq_restore(flags)就显得很温柔了。local_irq_save先保存DAIF的状态到flags,然后再关闭DAIF。当配对使用local_irq_restore(flags)时,把flags保存的状态恢复到DAIF,而不是简单的打开DAIF,以此来保证恢复当初的状态。
arch/arm64/include/asm/irqflags.h:
static inline unsigned long arch_local_irq_save(void)
{
unsigned long flags;
asm volatile(
"mrs %0, daif // arch_local_irq_save\n"
"msr daifset, #2"
: "=r" (flags)
:
: "memory");
return flags;
}
static inline void arch_local_irq_restore(unsigned long flags)
{
asm volatile(
"msr daif, %0 // arch_local_irq_restore"
:
: "r" (flags)
: "memory");
}
点击查看系列文章 =》 Interrupt Pipeline系列文章大纲-CSDN博客
原创不易,需要大家多多鼓励!您的关注、点赞、收藏就是我的创作动力!
标签:IRQ,中断,irq,屏蔽,disable,3.3,Linux,local,desc From: https://blog.csdn.net/aspirestro/article/details/141678040