专栏目录:专栏目录传送门


前面我们提到,在正式进入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_opsirq_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控制器,以告知中断处理已完成。

04-14 04:26