Linux + arm 中断系统理解

本文深入探讨ARM中断框架,详细分析了ARM处理器的中断处理机制,包括CPU、中断控制器(GIC)及外设中断源的工作原理。重点讲解了imx6q的GIC(V2版本)中断控制器的内部结构,中断信号分类(PPI和SPI),以及Linux内核下GIC的初始化和中断处理流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

arm中断框架:
在这里插入图片描述
我把中断系统的硬件组成分为三个部分,CPU、中断控制器、外设中断源。imx6q的Interrupt Controller为GIC,支持多CPU Core。
在这里插入图片描述
【1】首先来看CPU 目标架构相关的的中断处理
中断向量表
arch/arm/kernel/entry-armv.S中
.section .vectors, “ax”, %progbits
__vectors_start:
W(b) vector_rst
W(b) vector_und
W(ldr) pc, __vectors_start + 0x1000
W(b) vector_pabt
W(b) vector_dabt
W(b) vector_addrexcptn
W(b) vector_irq //中断发生时跳转的地址
W(b) vector_fiq
.data

关于vector_irq的定义分析到跳转流程如下
.macro vector_stub, name, mode, correction=0
.align 5
vector_\name:
.if \correction
sub lr, lr, #\correction
.endif
vector_stub irq, IRQ_MODE, 4
.long __irq_usr @ 0 (USR_26 / USR_32)
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.long __irq_svc @ 3 (SVC_26 / SVC_32)
.long __irq_invalid @ 4
.long __irq_invalid @ 5
.long __irq_invalid @ 6
.long __irq_invalid @ 7
.long __irq_invalid @ 8
.long __irq_invalid @ 9
.long __irq_invalid @ a
.long __irq_invalid @ b
.long __irq_invalid @ c
.long __irq_invalid @ d
.long __irq_invalid @ e
.long __irq_invalid @ f
从上面的定义可以看到,arm下只有usr状态的中断和svc状态的中断,继续分析
__irq_usr 和 __irq_svc的定义。

__irq_usr:
usr_entry ----->保存现场
kuser_cmpxchg_check
irq_handler ---->处理中断
get_thread_info tsk
mov why, #0
b ret_to_user_from_irq —>恢复现场
UNWIND(.fnend )
ENDPROC(__irq_usr)
__irq_svc:
svc_entry —>保存现场
irq_handler ---->处理中断
#ifdef CONFIG_PREEMPT
get_thread_info tsk
ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
ldr r0, [tsk, #TI_FLAGS] @ get flags
teq r8, #0 @ if preempt count != 0
movne r0, #0 @ force flags to 0
tst r0, #_TIF_NEED_RESCHED
blne svc_preempt
#endif
svc_exit r5, irq = 1 @ return from exception 恢复现场
UNWIND(.fnend )
ENDPROC(__irq_svc)

继续跟踪irq_handler 的处理
.macro irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
ldr r1, =handle_arch_irq
mov r0, sp
adr lr, BSYM(9997f)
ldr pc, [r1]
#else
arch_irq_handler_default
#endif
最终跳转到handle_arch_irq,在arch/arm/kernel/irq.c中使用set_handle_irq设置handle_arch_irq的函数指针
void __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
if (handle_arch_irq)
return;
handle_arch_irq = handle_irq;
}

继续跟踪,在imx6q目标架构下,driver/irqchip/irq-gic.c中调用set_handle_irq。设置中断处理函数入口。
static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
u32 irqstat, irqnr;
struct gic_chip_data *gic = &gic_data[0];
void __iomem *cpu_base = gic_data_cpu_base(gic);
do {
irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
irqnr = irqstat & GICC_IAR_INT_ID_MASK;
if (likely(irqnr > 15 && irqnr < 1021)) {
handle_domain_irq(gic->domain, irqnr, regs);
continue;
}
if (irqnr < 16) {
writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
#ifdef CONFIG_SMP
handle_IPI(irqnr, regs);
#endif
continue;
}
break;
} while (1);
}
cpu架构相关的中断代码暂时跟踪到这里,继续跟踪基本上是和imx6q片上寄存器设置相关的代码,需要熟悉imx6q的架构和寄存器设置等内容。
【2】再看ARM 的GIC中断控制器相关分析
GIC的全称为Generic Interrupt Controller,是ARM公司提供的通用的中断控制器。目前共有V1 V2 V3 V4 四个版本。imx6q使用的为V2版本。Linux内核中关于GIC中断控制器的代码目录为 drivers/irqchip/irq-gic.c 和 drivers/irqchip/irq-common.c
先补充一下GIC中断控制器的几个知识点:
GIC中断控制器中的中断输入信号,分为两种,分别是PPI和SPI, PPI即Private Peripheral Interrupt,该类型的中断信号为CPU私有的中断信号,每个CPU都有其特定的PPI信号输入。对于IMX6Q使用的Cortex-A9结构,她的PPI中断信号包括5根:
a>.nLEGACYIRQ, interrupt ID=31,nLEGACYIRQ可以直接连到对应CPU的nIRQCPU信号线上,这种情况下,该CPU不参与其他属于该CPU的PPI以及SPI中断的响应,只是特别的为这一根中断线服务。
b>.nLEGACYFRQ, interrupt ID=28,功能同nLEGACYIRQ
c>.Private timer, interrupt ID=29
d>.Watch Dog Timer, interrupt ID=30
e>.Global Time, interrupt ID=27
SPI,即Shared Peripheral Interrupt,所有CPU之间共享的中断,通过寄存器GICD_TYPER可以设置SPI的个数。GIC支持多少个SPI中断,其输入信号就有多少个SPI的request signal。

从代码driver/irqchip/irq-gic.c中的gic_handle_irq可以看出,ID0-ID15,为CPU的PPI中断ID,ID16-ID1020为SPI中断ID。

Linux GIC源码分析
driver的加载入口:
IRQCHIP_DECLARE(cortex_a9_gic, “arm,cortex-a9-gic”, gic_of_init);
展开IRQCHIP_DECLARE 宏
#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)
#define OF_DECLARE_2(table, name, compat, fn)
_OF_DECLARE(table, name, compat, fn, of_init_fn_2)
#define _OF_DECLARE(table, name, compat, fn, fn_type)
static const struct of_device_id _of_table##name
__used section(##table##_of_table)
= { .compatible = compat,
.data = (fn == (fn_type)NULL) ? fn : fn }
最终初始化了一个struct of_decice_id的静态常量,并存储在_irqchip_of_table_section中。在内核启动过程中根据设备树种的内容匹配当前芯片的of_decice_id,进行驱动的加载过程。


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 分发器的寄存器地址
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))
percpu_offset = 0; //处理cpu-offset属性
//gic_init_bases是GIC的主要处理过程
gic_init_bases(gic_cnt, -1, dist_base, cpu_base, percpu_offset, node);
if (!gic_cnt)
gic_init_physaddr(node);
//处理interrupt级联关系
if (parent) {
irq = irq_of_parse_and_map(node, 0);//解析second GIC的interrupt属性并进行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;
}

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];
/

* 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_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;
/gic_irqs保存了GIC支持的最大中断数量,从GICD_TYPER寄存器中的低五位读出,如果读出值为N, 那么最大支持的中断数目是32(N+1)。GIC规定的最大中断数据不超过1020
/
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_nr表示GIC_NUMBER, 0表示root GIC,hwirq表示硬件中断id,并不是GIC上的每个中断id都需要映射到Linux 中断系统中的id。例如软件中断SGI,用于cpu之间的通信,没有必要进行 中断ID的映射。hwirq_base 表示GIC上需要进行id map的base,16表示忽略掉16个SGI。对于系统
中的其他GIC,其PPI也没有必要进行mapping,因此hwirq_base=32
/
gic_irqs -= hwirq_base; /
calculate # of irqs to allocate */
/*从gic_irqs 中减去那些不需要mapping的interrupt ID /
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;
}
/分配中断描述符 如果irq_start大于0,那么说明是指定IRQ number的分配,这种情况,irq_start=-1
不用指定IRQ NUMBER。让其自动搜索,参数2是起始搜索的IRQ NUMBER. gic_irqs则指明了要分配的
irq_number 的数目。
/
gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base,
hwirq_base, &gic_irq_domain_ops, gic);
/向系统中注册一个irq_domain的数据结构,关于irq_domain 后面专门分析/
}
if (WARN_ON(!gic->domain))
return;
if (gic_nr == 0) {
#ifdef CONFIG_SMP
set_smp_cross_call(gic_raise_softirq);
/设定一个多个cpu直接通信的callback函数。当一个cpu core上的软件控制行为需要传递到其他的cpu上时,
就会调用这个函数set_smp_cross_call,对于GIC,这里实际上触发IPI中断
/
register_cpu_notifier(&gic_cpu_notifier);
/注册cpu的通知事件,当cpu 状态发生变化时,GIC driver需要接收到这些事件,并对GIC 的
cpu interface进行设定
/
#endif
set_handle_irq(gic_handle_irq);
/设定所有中断的处理函数入口/
}
gic_dist_init(gic);
gic_cpu_init(gic);
gic_pm_init(gic);
/

GIC的电源初始化,主要分配两个per cpu的内存。这些内存在系统进行sleep状态时保存PPI的寄存器状态信息,在resume的时候,写回寄存器。对于root GIC,
需要注册一个和电源管理的时间通知事件回掉函数。
*/
}

static void __init gic_dist_init(struct gic_chip_data *gic)
{
unsigned int i;
u32 cpumask;
unsigned int gic_irqs = gic->gic_irqs; //GIC支持的IRQ数量
void __iomem base = gic_data_dist_base(gic); //获取GIC Distributor分发器基地址
writel_relaxed(GICD_DISABLE, base + GIC_DIST_CTRL);
/

GIC_DIST_CTRL寄存器用来控制全局中断向CPU Interface分发,写入0表示不向CPU Interface发送中断请求信号
即为关闭全部中断请求。
/
/

* Set all global interrupts to this CPU only.
*/
cpumask = gic_get_cpumask(gic);
cpumask |= cpumask << 8;
cpumask |= cpumask << 16;
for (i = 32; i < gic_irqs; i += 4)
/设定每个SPI类型的中断都是只送达该CPU/
writel_relaxed(cpumask, base + GIC_DIST_TARGET + i * 4 / 4);
/配置GIC Distributor的其他寄存器/
gic_dist_config(base, gic_irqs, NULL);
writel_relaxed(GICD_ENABLE, base + GIC_DIST_CTRL);
}

static void gic_cpu_init(struct gic_chip_data *gic)
{
void __iomem *dist_base = gic_data_dist_base(gic); //获取 GIC Distrbutor的基地址
void __iomem base = gic_data_cpu_base(gic); //获取 GIC CPU Interface的基地址
unsigned int cpu_mask, cpu = smp_processor_id(); //获取CPU的逻辑ID
int i;
/

* Get what the GIC says our CPU mask is.
/
BUG_ON(cpu >= NR_GIC_CPU_IF);
cpu_mask = gic_get_cpumask(gic); //获取cpu_mask
gic_cpu_map[cpu] = cpu_mask;
/

* Clear our mask from the other map entries in case they’re
* still undefined.
*/
for (i = 0; i < NR_GIC_CPU_IF; i++)
if (i != cpu)
gic_cpu_map[i] &= ~cpu_mask;
/设定SGI PPI的初始值/
gic_cpu_config(dist_base, NULL);
//让所有的interrupt interface都可以送达CPU
writel_relaxed(GICC_INT_PRI_THRESHOLD, base + GIC_CPU_PRIMASK);
//设定cpu interface的control register,enable group-0的中断,disable group-1的中断,group 0的interrupt source
//触发irq中断,而非fiq中断
gic_cpu_if_up();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值