专栏目录:专栏目录传送门
前面我们提到,在正式进入gic_handle_irq之前,汇编层已经将处理器中的通用寄存器,SP,PSTATE等保存进了regs中。然后C代码中的el1_interrupt还会做一些中断前的简单处理。最新的代码已经将EL0和EL1中的FIQ和IRQ中断处理移动到了C代码中。
el1_interrupt
向DAIF域写入DAIF_PROCCTX_NOIRQ,SError屏蔽、IRQ屏蔽。enter_el1_irq_or_nmi
的作用是从内核模式进入用户模式时更新一些状态,例如锁依赖、RCU和跟踪状态。
static void noinstr el1_interrupt(struct pt_regs *regs,
void (*handler)(struct pt_regs *))
{
write_sysreg(DAIF_PROCCTX_NOIRQ, daif);
enter_el1_irq_or_nmi(regs);
do_interrupt_handler(regs, handler);
/*
* Note: thread_info::preempt_count includes both thread_info::count
* and thread_info::need_resched, and is not equivalent to
* preempt_count().
*/
if (IS_ENABLED(CONFIG_PREEMPTION) &&
READ_ONCE(current_thread_info()->preempt_count) == 0)
arm64_preempt_schedule_irq();
exit_el1_irq_or_nmi(regs);
}
然后do_interrupt_handler
将regs传给gic_handle_irq
函数。
gic_handle_irq
1.do_read_iar
通过读取iar寄存器返回中断ID,然后判读中断ID的合法性。
2.检查是否支持NMI(非屏蔽中断),并读取RPR(运行优先级寄存器)的值。如果RPR的值等于GICD_INT_RPR_PRI(GICD_INT_NMI_PRI)
,则调用gic_handle_nmi(irqnr, regs)
函数来处理NMI。
3.检查是否启用了GIC(通用中断控制器)的优先级屏蔽功能。如果启用了,则调用gic_pmr_mask_irqs()
函数来屏蔽中断,并调用gic_arch_enable_irqs()
函数来启用中断。
4.gic_complete_ack
将中断ID写入ICC_EOIR1_EL1寄存器来停止这个中断,然后下一步调用中断处理函数handle_domain_irq
。
static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
u32 irqnr;
irqnr = do_read_iar(regs);
/* Check for special IDs first */
if ((irqnr >= 1020 && irqnr <= 1023))
return;
if (gic_supports_nmi() &&
unlikely(gic_read_rpr() == GICD_INT_RPR_PRI(GICD_INT_NMI_PRI))) {
gic_handle_nmi(irqnr, regs);
return;
}
if (gic_prio_masking_enabled()) {
gic_pmr_mask_irqs();
gic_arch_enable_irqs();
}
gic_complete_ack(irqnr);
if (handle_domain_irq(gic_data.domain, irqnr, regs)) {
WARN_ONCE(true, "Unexpected interrupt received!\n");
gic_deactivate_unhandled(irqnr);
}
}
handle_domain_irq
传入绑定了gic_irq_domain_ops
的irq_domain
,中断ID和中断上下文regs。
该函数首先调用set_irq_regs
函数读取当前CPU的__irq_regs
值并将其存储在局部变量old_regs
,写入中断上下文regs。接下来,它调用irq_resolve_mapping
函数来获取与给定的硬件中断号hwirq对应的中断描述符irq_desc。成功获取到中断描述符后,则调用handle_irq_desc
函数处理该中断描述符。最后,调用irq_exit
函数并使用之前保存的值调用set_irq_regs
函数恢复寄存器状态。
int handle_domain_irq(struct irq_domain *domain,
unsigned int hwirq, struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
struct irq_desc *desc;
int ret = 0;
irq_enter();
/* The irqdomain code provides boundary checks */
desc = irq_resolve_mapping(domain, hwirq);
if (likely(desc))
handle_irq_desc(desc);
else
ret = -EINVAL;
irq_exit();
set_irq_regs(old_regs);
return ret;
}
handle_irq_desc
获取irq_desc中的irq_data,然后检查当前是否不在中断上下文中且需要强制执行中断上下文。然后处理irq_desc,执行handle_irq函数。这个irq_desc->handle_irq
函数由gic_irq_domain_map->irq_domain_set_info
设置,SGI/PPI/EPPI对应handle_percpu_devid_irq
。SPI/ESPI/LPI对应handle_fasteoi_irq
。
int handle_irq_desc(struct irq_desc *desc)
{
struct irq_data *data;
data = irq_desc_get_irq_data(desc);
if (WARN_ON_ONCE(!in_irq() && handle_enforce_irqctx(data)))
return -EPERM;
generic_handle_irq_desc(desc);
return 0;
}
最终handle_fasteoi_irq
会调用action->handler
来执行中断处理函数,这个中断处理函数就是我们在驱动中通过request_threaded_irq
设置的irq_default_primary_handler
,这个直接返回IRQ_WAKE_THREAD
来唤醒线程处理函数。又或者是ads7846_hard_irq
获取触摸的状态来选择返回IRQ_WAKE_THREAD
还是IRQ_HANDLED
。本质上handler的作用就是唤醒中断线程,执行线程函数再处理中断。
知识点
IAR寄存器
在ARMv8-A架构中,IAR(Interrupt Acknowledge Register,中断确认寄存器)用于处理中断请求并提供中断号。当一个外部中断被触发时,处理器会通过中断控制器和IAR来确定中断号。IAR寄存器有两个,分别为IAR_EL1和IAR_EL2,分别用于在EL1和EL2级别中处理中断。IAR寄存器是只读的,每个中断号对应一个唯一的IAR值。在处理完中断后,处理器会将相应的IAR寄存器更新为0来确认该中断已被处理。
IAR寄存器是一个32位的寄存器,包括以下几个位:
- [31:10]:保留位
- [9:0]:中断向量号
其中,[9:0] 位用于保存当前正在处理的中断向量号,可以表示0-1023范围内的中断向量号。
RPR寄存器
在 GIC v3中,RPR 寄存器(Running Priority Register)存储当前正在运行的最高优先级中断的中断号。当当前运行的中断处理程序退出时,GIC 会使用该寄存器中的值来确定下一个要执行的中断处理程序。
RPR寄存器位定义如下:
其中,RPR0到RPRn表示各个优先级的保留寄存器,用于保存当前该优先级下的最高中断优先级,取值范围为0到255。PMHE表示Priority Mask Hint Enable,用于表示当前优先级是否被屏蔽,取值0或1。其他位为保留位。
ICC_EOIR1_EL1寄存器
在 ARMv8-A 架构中,ICC_EOIR1_EL1 (Interrupt Controller Control End Of Interrupt Register 1, EL1) 是一个 64 位的寄存器,用于完成中断的结束操作。该寄存器允许内核结束中断处理并通知 GICv3 中断控制器中的相应中断已被处理完毕,以允许下一个中断在该中断的后面立即传递到 CPU。
ICC_EOIR1_EL1 寄存器由以下字段组成:
- [63:32] : Reserved,保留字段,读取时返回未定义的值。
- [31:0] : EOIINTID,表示中断号。写入此字段后,GICv3 中断控制器将中断状态寄存器 (ICR) 中相应中断的 EOImode 置为 1,表示该中断处理已完成。
需要注意的是,这个寄存器只能在 EL1 权限级别使用,而在其他权限级别不能直接访问。同时,该寄存器只有在使用中断时才有意义,在没有中断发生时,该寄存器的值未定义。
ICR
在GICv3架构中,有多个ICR(Interrupt Control Register)寄存器,分别用于处理不同级别的中断。其中,ICR_EL1用于处理EL1级别的中断,ICR_EL2用于处理EL2级别的中断,ICR_EL3用于处理EL3级别的中断。
ICR_ELx寄存器的结构与功能相似,主要用于发送中断信号给对应的CPU。ICR_ELx寄存器的位域定义如下:
- [31:24] :Reserved
- [23:16] :Priority,表示中断优先级,取值范围0~255。
- [15] :Reserved
- [14] :Group,表示中断分组,0表示Group0,1表示Group1。
- [13:10] :TargetList,表示中断目标CPU列表,其中每个bit对应一个CPU,1表示该中断需要发送给对应的CPU,0表示不需要发送给对应的CPU。如果在分配中断给CPU之前,这些位被写入1,则该中断会被发送给相应的CPU。如果写入的值为0,则该CPU不会收到该中断。
- [9:5] :Reserved
- [4:0] :InterruptID,表示中断ID,用于指定要发送的中断的ID。
当某个CPU请求中断处理时,GICv3控制器将相应的中断号和目标CPU的信息填充到ICR_ELx寄存器中,然后发出中断信号,目标CPU将从中断服务例程(ISR)中处理该中断。处理完成后,目标CPU使用ICC_EOIR1_ELx寄存器来发送中断结束信号(EOI)给GICv3控制器,以告知中断处理已完成。