ARM中断架构
中断也是异常模式的一种,当外设触发中断时,处理器会切换到特定的异常模式进行处理,而这部分代码都是架构相关的;ARM64的代码位于arch/arm64/kernel/entry.S。
ARM64处理器有四个异常级别Exception Level:0~3,EL0级对应用户态程序,EL1级对应操作系统内核态,EL2级对应Hypervisor,EL3级对应Secure Monitor;
异常触发时,处理器进行切换,并且跳转到异常向量表开始执行,针对中断异常,最终会跳转到irq_handler中;
中断触发,处理器去异常向量表找到对应的入口,比如EL0的中断跳转到el0_irq处,EL1则跳转到el1_irq处;
在GIC驱动中,会调用set_handle_irq接口来设置handle_arch_irq的函数指针,让它指向gic_handle_irq,因此中断触发的时候会跳转到gic_handle_irq处执行;gic_handle_irq函数处理时,分为两种情况,一种是外设触发的中断,硬件中断号在16 ~ 1020之间,一种是软件触发的中断,用于处理器之间的交互,硬件中断号在16以内;
外设触发中断后,根据irq domain去查找对应的Linux IRQ中断号,进而得到中断描述符irq_desc,最终也就能调用到外设的中断处理函数了;
gic_of_init()
static int __init gic_of_init(struct device_node *node, struct device_node *parent) { void __iomem *cpu_base; void __iomem *dist_base; u32 percpu_offset; int irq; if (WARN_ON(!node)) return -ENODEV;[] dist_base = of_iomap(node, 0);//映射GIC Distributor的寄存器地址空间 WARN(!dist_base, "unable to map gic dist registers\n"); cpu_base = of_iomap(node, 1);//映射GIC CPU interface的寄存器地址空间 WARN(!cpu_base, "unable to map gic cpu registers\n"); if (of_property_read_u32(node, "cpu-offset", &percpu_offset))//处理cpu-offset属性。 percpu_offset = 0; gic_init_bases(gic_cnt, -1, dist_base, cpu_base, percpu_offset, node);//主处理过程,后面详述 if (!gic_cnt) gic_init_physaddr(node);//对于不支持big.LITTLE switcher(CONFIG_BL_SWITCHER)的系统,该函数为空。 if (parent) {//处理interrupt级联 irq = irq_of_parse_and_map(node, 0);//解析second GIC的interrupts属性,并进行mapping,返回IRQ number gic_cascade_irq(gic_cnt, irq); } if (IS_ENABLED(CONFIG_ARM_GIC_V2M)) gicv2m_of_init(node, gic_data[gic_cnt].domain); gic_cnt++; return 0; }
gic_init_bases()函数
void __init gic_init_bases(unsigned int gic_nr, int irq_start, void __iomem *dist_base, void __iomem *cpu_base, u32 percpu_offset, struct device_node *node) { irq_hw_number_t hwirq_base; struct gic_chip_data *gic; int gic_irqs, irq_base, i; BUG_ON(gic_nr >= MAX_GIC_NR); gic = &gic_data[gic_nr]; #ifdef CONFIG_GIC_NON_BANKED if (percpu_offset) { /* Frankein-GIC without banked registers... */ unsigned int cpu; gic->dist_base.percpu_base = alloc_percpu(void __iomem *); gic->cpu_base.percpu_base = alloc_percpu(void __iomem *); if (WARN_ON(!gic->dist_base.percpu_base || !gic->cpu_base.percpu_base)) { free_percpu(gic->dist_base.percpu_base); free_percpu(gic->cpu_base.percpu_base); return; } for_each_possible_cpu(cpu) { u32 mpidr = cpu_logical_map(cpu); u32 core_id = MPIDR_AFFINITY_LEVEL(mpidr, 0); unsigned long offset = percpu_offset * core_id; *per_cpu_ptr(gic->dist_base.percpu_base, cpu) = dist_base + offset; *per_cpu_ptr(gic->cpu_base.percpu_base, cpu) = cpu_base + offset; } gic_set_base_accessor(gic, gic_get_percpu_base); } else #endif { /* Normal, sane GIC... */ WARN(percpu_offset, "GIC_NON_BANKED not enabled, ignoring %08x offset!", percpu_offset); gic->dist_base.common_base = dist_base; gic->cpu_base.common_base = cpu_base; gic_set_base_accessor(gic, gic_get_common_base); } /* * Initialize the CPU interface map to all CPUs. * It will be refined as each CPU probes its ID. */ for (i = 0; i < NR_GIC_CPU_IF; i++) gic_cpu_map[i] = 0xff; /* * Find out how many interrupts are supported. * The GIC only supports up to 1020 interrupt sources. */ /* 读取GIC 的最大支持的中断数目,从 GIC_DIST_CTR 寄存器(这是V1版本的寄存器 名字,V2中是GICD_TYPER,Interrupt Controller Type Register,)的低五位 ITLinesNumber获取的。如果ITLinesNumber等于N,那么最大支持的中断数目是 32(N+1)。此外,GIC规范规定最大的中断数目不能超过1020,1020-1023是有特 别用户的interrupt ID。 */ gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f; gic_irqs = (gic_irqs + 1) * 32; if (gic_irqs > 1020) gic_irqs = 1020; gic->gic_irqs = gic_irqs; /* 分配一个 irq_domain 的结构,一个 irq_domain 代表了一个 GIC 控制器 */ if (node) { /* DT case */ gic->domain = irq_domain_add_linear(node, gic_irqs, &gic_irq_domain_hierarchy_ops, gic); } else { /* Non-DT case */ /* * For primary GICs, skip over SGIs. * For secondary GICs, skip over PPIs, too. */ if (gic_nr == 0 && (irq_start & 31) > 0) { hwirq_base = 16; if (irq_start != -1) irq_start = (irq_start & ~31) + 16; } else { hwirq_base = 32; } gic_irqs -= hwirq_base; /* calculate # of irqs to allocate */ irq_base = irq_alloc_descs(irq_start, 16, gic_irqs, numa_node_id()); if (IS_ERR_VALUE(irq_base)) { WARN(1, "Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n", irq_start); irq_base = irq_start; } gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base, hwirq_base, &gic_irq_domain_ops, gic); } if (WARN_ON(!gic->domain)) return; if (gic_nr == 0) {//只对root GIC操作,因为设定callback、注册Notifier只需要一次就OK了 #ifdef CONFIG_SMP set_smp_cross_call(gic_raise_softirq); register_cpu_notifier(&gic_cpu_notifier); #endif set_handle_irq(gic_handle_irq); } gic_dist_init(gic);//GIC Distributer 部分初始化 gic_cpu_init(gic);//GIC CPU Interface 部分初始化 gic_pm_init(gic);//GIC PM 部分初始化 }
gic_handle_irq()
static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs) { u64 irqnr; do { irqnr = gic_read_iar();//读取gic寄存器并获取hwirq中断号 if (likely(irqnr > 15 && irqnr < 1020) || irqnr >= 8192) { int err; err = handle_domain_irq(gic_data.domain, irqnr, regs);//外设触发的中断 if (err) { WARN_ONCE(true, "Unexpected interrupt received!\n"); gic_write_eoir(irqnr); } continue; } if (irqnr < 16) {//软件触发的中断SGI gic_write_eoir(irqnr); #ifdef CONFIG_SMP handle_IPI(irqnr, regs);//处理核间交互 #else WARN_ONCE(true, "Unexpected SGI received!\n"); #endif continue; } } while (irqnr != ICC_IAR1_EL1_SPURIOUS); }
__handle_domain_irq()
int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq, bool lookup, struct pt_regs *regs) { struct pt_regs *old_regs = set_irq_regs(regs); unsigned int irq = hwirq; int ret = 0; irq_enter();//进入中断上下文 #ifdef CONFIG_IRQ_DOMAIN if (lookup) irq = irq_find_mapping(domain, hwirq);//根据hwirq去查找linux中断号 #endif /* * Some hardware gives randomly wrong interrupts. Rather * than crashing, do something sensible. */ if (unlikely(!irq || irq >= nr_irqs)) { ack_bad_irq(irq); ret = -EINVAL; } else { generic_handle_irq(irq); /* 根据linux中断号找到对应的中断描述符,从而执行中断服务函数 */ } irq_exit();//退出中断上下文 set_irq_regs(old_regs); return ret; }