3.3.1 Linux中断的使能与屏蔽

点击查看系列文章 =》 Interrupt Pipeline系列文章大纲-CSDN博客

        3.3 第一行之hard_local_irq_disable() 

                3.3.1 Linux中断的使能与屏蔽

                        3.3.1.1 中断使能与屏蔽的三重关卡
                        3.3.1.2 第一重关卡IMR
                        3.3.1.3 第二重关卡中断控制器的使能bit
                        3.3.1.4 第三重关卡CPU core异常掩码标志

                3.3.2 IPIPE对Linux中断使能与屏蔽的改造

                        3.3.2.1 第一重关卡IMR的改造
                        3.3.2.2 第二重关卡中断控制器的使能bit的改造
                        3.3.2.3 第三重关卡的改造

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博客

原创不易,需要大家多多鼓励!您的关注、点赞、收藏就是我的创作动力!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值