禁止/开启中断
软件可以禁止中断,使处理器不再响应任何中断请求,但是不可屏蔽中断(NMI,Non Maskable Interrupt)例外。禁止中断在需要临界区保护的场景下非常重要,避免中断打断正在执行的代码。
禁止中断的接口如下。
(1)local_irq_disable()。
(2)local_irq_save(flags):首先把中断状态保存在参数flags中,然后禁止中断。
这两个接口只能禁止当前处理器的中断,不能禁止其他处理器的中断。禁止中断以后,处理器不再响应中断请求。
开启中断的接口如下。
(1)local_irq_enable()。
(2)local irq_restore(flags):恢复本处理器的中断状态。
local_irq_disable()和local_irq_enable()不能嵌套使用,local_irq_save(flags)和local_irq_restore(flags)可以嵌套使用。
禁止中断
ARM64架构禁止中断的函数local_irq_disable()如下:
local_irq_disable() -> raw_local_irq_disable() -> arch_local_irq_disable()
c
#define local_irq_disable() do { raw_local_irq_disable(); } while (0)
#define raw_local_irq_disable() arch_local_irq_disable()
c
static inline void arch_local_irq_disable(void)
{
asm volatile(
"msr daifset, #2 // arch_local_irq_disable"
:
:
: "memory");
}
arch_local_irq_disable(),通过汇编指令禁止中断;
- msr指令:
• msr 是 ARM 汇编中的指令,表示 "Move to Special Register"。
• 它将立即数或寄存器的值写入特定的系统寄存器。
- daifset:
• daif 是一个包含中断屏蔽位的寄存器,具体表示:
• D (Debug Mask): 调试异常屏蔽位。
• A (SError Mask): 同步错误异常屏蔽位。
• I (IRQ Mask): 普通中断屏蔽位。
• F (FIQ Mask): 快速中断屏蔽位。
• daifset 是 daif 的一个操作子集,用于设置(屏蔽)指定的中断类型。
- #2:
• #2 表示屏蔽普通中断(IRQ)。
• 屏蔽操作会将 I 位设置为 1,从而禁止普通中断。
- memoryclobber:
• 通知编译器汇编指令可能对内存产生副作用,确保在此指令前后的内存访问不会被重排序。
开启中断
ARM64架构开启中断的函数local_irq_enable()如下:
raw_local_irq_enable() -> raw_local_irq_enable() -> arch_local_irq_enable()
c
#define local_irq_enable() do { raw_local_irq_enable(); } while (0)
#define raw_local_irq_enable() arch_local_irq_enable()
c
static inline void arch_local_irq_enable(void)
{
asm volatile(
"msr daifclr, #2 // arch_local_irq_enable"
:
:
: "memory");
}
将daif寄存器中断I 位设置为 0,从而使能普通中断。
禁止/开启单个中断
软件可以禁止某个外围设备的中断,中断控制器不会把该设备发送的中断转发给处理器。
禁止中断
禁止单个中断的函数如下:
c
/**
* disable_irq - disable an irq and wait for completion
* @irq: Interrupt to disable
*
* Disable the selected interrupt line. Enables and Disables are
* nested.
* This function waits for any pending IRQ handlers for this interrupt
* to complete before returning. If you use this function while
* holding a resource the IRQ handler may need you will deadlock.
*
* This function may be called - with care - from IRQ context.
*/
void disable_irq(unsigned int irq)
{
if (!__disable_irq_nosync(irq))
synchronize_irq(irq);
}
此函数调用 __disable_irq_nosync() 函数,并将中断号 (irq) 作为参数传入。此函数用于禁用指定的中断,而不必等待任何现有的中断处理程序完成。首先找到中断描述符(irq_desc),之后对irq_data进行set_mask操作。
如果 __disable_irq_nosync() 函数的返回值为 0,表示中断已成功禁用。在这种情况下,将调用 synchronize_irq() 函数来确保在返回之前,已禁用的中断的任何挂起的中断处理程序都已完成。
如果 __disable_irq_nosync() 函数的返回值不为 0,表示中断无法被禁用。在这种情况下,不会执行任何进一步的操作。
disable_irq() 函数为用户提供了一个易于使用的接口,用于禁用中断,并确保在返回之前完成所有挂起的中断处理程序。
开启中断
开启单个中断函数如下:
c
/**
* enable_irq - enable handling of an irq
* @irq: Interrupt to enable
*
* Undoes the effect of one call to disable_irq(). If this
* matches the last disable, processing of interrupts on this
* IRQ line is re-enabled.
*
* This function may be called from IRQ context only when
* desc->irq_data.chip->bus_lock and desc->chip->bus_sync_unlock are NULL !
*/
void enable_irq(unsigned int irq)
{
unsigned long flags;
struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, IRQ_GET_DESC_CHECK_GLOBAL);
if (!desc)
return;
if (WARN(!desc->irq_data.chip,
KERN_ERR "enable_irq before setup/request_irq: irq %u\n", irq))
goto out;
__enable_irq(desc);
out:
irq_put_desc_busunlock(desc, flags);
}
GIC控制器实现
对于ARM64架构的GIC控制器,如果需要开启硬件中断n,那么设置分发器的寄存器GICD_ISENABLERn(Interrupt Set-Enable Register);如果需需要禁止硬件中断n,那么设置分发器的寄存器GICD_ICENABLERn (Interrupt Clear-EnableRegister)。
假设某个外围设备的硬件中断号是n,当这个外围设备发送中断给分发器的时候,只有在分发器上开启了硬件中断n,分发器才会把硬件中断n转发给处理器。
以下是GIC控制器禁止/启用中断,设置分发器寄存器的代码:
c
static void gic_poke_irq(struct irq_data *d, u32 offset)
{
u32 mask = 1 << (gic_irq(d) % 32);
writel_relaxed(mask, gic_dist_base(d) + offset + (gic_irq(d) / 32) * 4);
}
static void gic_mask_irq(struct irq_data *d)
{
gic_poke_irq(d, GIC_DIST_ENABLE_CLEAR);
}
static void gic_unmask_irq(struct irq_data *d)
{
gic_poke_irq(d, GIC_DIST_ENABLE_SET);
}
参考资料
- Professional Linux Kernel Architecture,Wolfgang Mauerer
- Linux内核深度解析,余华兵
- Linux设备驱动开发详解,宋宝华