1. Arm Linux 中断子系统
外设(中断源)、中断控制器、CPU
普通外设驱动、Linux kernel通用中断处理模块(硬件无关代码)、CPU架构相关处理、中断控制器驱动代码
图1.1 Linux 中断子系统分层图
IRQ和FIQ
irq_desc:系统中每一个irq都对应着一个irq_desc结构
irq_data:中断相关数据信息
irq_chip:中断控制器数据结构
Irqaction:中断响应链表,当一个irq被触发时,内核会遍历该链表,调用action结构中的回调handler或者激活其中的中断线程
图1.2 Irq各个数据结构关系图
generic_handle_irq();
irq_to_desc();
irq_set_chip();
irq_set_chained_handler();
handle_simple_irq();
handle_level_irq(); //电平中断流控处理程序
handle_edge_irq(); //边沿触发中断流控处理程序
handle_fasteoi_irq(); //需要eoi的中断处理器使用的中断流控处理程序
handle_percpu_irq(); //该irq只有单个cpu响应时使用的流控处理程序
enable_irq();
disable_irq();
disable_irq_nosync();
request_threaded_irq();
irq_set_affinity();
Start_kernel()--->setup_arch()--->early_trap_init()
Start_kernel()--->early_irq_init()<初始化irq_desc结构体>---->arch_early_irq_init()
Start_kernel()--->init_IRQ()--->machine_desc()--->init_irq()--->irqchip_init()--->
of_irq_init(__irqchip_begin)
__irqchip_begin就是内核irq chip table的首地址,这个table也就保存了kernel支持的所有的中断控制器的驱动代码初始化函数和DT compatible string的对应关系。
l 保存进程(或中断)上下文(各寄存器
l R14保存PC值
l CPSR值存入SPSR,同时修改CPSR中工作模式为中断模式、中断屏蔽位置1
l PC赋值为异常向量表中的相应中断地址
外部中断产生--->CPU保存上下文--->asm_do_IRQ()--->handle_IRQ()--->generic_handle_irq()
--->generic_handle_irq_desc()--->desc->handle_irq()--->
handle_level_irq--->handle_irq_event()--->action->handler /
handle_edge_irq--->ack_irq()--->handle_irq_event()--->action->handler / ……
图2.1 GIC400原理框图
输入: SGI(Software-generated Interrupts),软件中断(ID0~ID15)
PPI(Private Peripheral Interrupt),CPU私有中断(ID16~ID31)
SPI(Shared Peripheral Interrupt),CPU间共有中断(ID32~ID1019)
注: GIC400(arm_v7)只支持480个SPI,GIC600(arm_v8)支持960个SPI
图2.2 GIC-V2的内部逻辑图
输入:
LPI(Locality-specific Peripheral Interrupts ),基于消息的中断
SGI(Software-generated Interrupts),软件中断(ID0~ID15)
PPI(Private Peripheral Interrupt),CPU私有中断(ID16~ID31)
SPI(Shared Peripheral Interrupt),CPU间共有中断(ID32~ID1019)
图2.3 GIC logical partitioning with an ITS
Ø v2最大支持8个PE;
Ø v3支持更多(GIC-600•Supports up to 512 processor cores/threads per chip and up to 16 chips in a multi-chip configuration;GIC-500•Supports up to 128 cores within a maximum of 32 clusters using affinity-level routing);
Ø v3改变了interrupt routing机制为affinity routing(需要在GICD.CTLR中enable),增加Redistributer组件来实现
to align interrupt handling with ARMv8 Exception model
Ø Group 0 中断期望在EL3处理
Ø Secure Group1 期望在Secure EL1处理
Ø Non-Secure Group 1 physical interrupt expected handled at Non-secure EL2 for virtualization, or Non-secure EL1 for non-virtualization
Ø GICv3 Interrupt grouping support:
Ø Config to be Group0, Seure Group1, Non-secure Group1
Ø Group0->PE by FIQ
Ø Group1 ->PE by IRQ, in their own Security State
Ø Unified scheme for handling priority of Group0 and Group1
软件控制方法,将中断转换为物理中断或者虚拟中断
Ø allows software to control how interrupt translated into Physical (v3/v4) or Virtual (v4) interrupt
Ø 软件控制ITS通过command 接口和associated table-based structures in memory
Ø ITS输出为LPIs,一种message-based Interrupt
Ø 新的Interrupt类型,extends Interrupt ID space
Ø optional。如果要实现,需要ITS支持
Ø modified context of SGI
Ø no includes identity of source PE.
Ø 之前的SGI是在支持legacy operation的GIC中才可用
Ø add new regs in Distributer to support setting and clearing of message-based SPIs
Ø 使用system register instruction进行控制
Ø all are memory mapped
Ø 需要在对应EL的ICC_SRE.SRE中使能
Ø support for direct injection of virtual interrupts to a VM, without involving Hypervisor.
Ø system at least needs one ITS that translate interrupts into LPIs.
arm_v7a~arm_v8,中断机制引入一个新概念“Exception level”,用于整合之前架构中的processor mode和privilege level相关的功能,同时提供了完整的“security model”和“virtualization框架”的支持。
Arm_v8将Exception level分为4个等级:EL0~EL3,ELn的privilege随着n的增大而增大。类似地,可以将EL0归属于non-privilege level,EL1/2/3属于privilege level。如下图:
ARMv8-a Exception level有关的说明如下:
1)首先需要注意的是,AArch64中,已经没有User、SVC、ABT等处理器模式的概念,但ARMv8需要向前兼容,在AArch32中,就把这些处理器模式map到了4个Exception level。
2)Application位于特权等级最低的EL0,Guest OS(Linux kernel、window等)位于EL1,提供虚拟化支持的Hypervisor位于EL2(可以不实现),提供Security支持的Seurity Monitor位于EL3(可以不实现)。
3)只有在异常发生时(或者异常处理返回时),才能切换Exception level(这也是Exception level的命名原因,为了处理异常)。当异常发生时,有两种选择,停留在当前的EL,或者跳转到更高的EL,EL不能降级。同样,异常处理返回时,也有两种选择,停留在当前EL,或者调到更低的EL。
Ø 异步异常:
CPU CORE外部产生,IRQ、FIQ、ERROR
Ø 同步异常:
CPU内部产生如:未定义的指令、data abort、prefetch instruction abort、SP未对齐异常、debug exception及SVC/HVC/SMC指令
x0~x30共计31个;
每个exception level都有自己的stack pointer register,名字是SP_ELx,当然,前提是处理器支持该EL。对于EL0,只能使用SP_EL0,EL1~EL3,除了可以使用自己的SP_ELx,还可以选择使用SP_EL0;
当发生异常的时候,PE总是把当前cpu的状态保存在SPSR寄存器中,该寄存器有三个,分别是SPSR_EL1,SPSR_EL2,SPSR_EL3,异常迁移到哪一个exception level就使用哪一个SPSR。在返回异常现场的时候,可以使用SPSR_ELx来恢复PE的状态;
当发生异常的时候,PE把一个适当的返回地址保存在ELR(Exception Link Register)寄存器中,该寄存器有三个,分别是ELR_EL1,ELR_EL2,ELR_EL3,异常迁移到哪一个exception level就使用哪一个ELR。在返回异常现场的时候,可以使用ELR_ELx来恢复PC值;
各个exception level的Vector Base Address Register (VBAR)寄存器,该寄存器保存了各个exception level的异常向量表的基地址。该寄存器有三个,分别是VBAR_EL1,VBAR_EL2,VBAR_EL3。
具体的exception handler是通过vector base address + offset得到,offset的定义如下表所示:
Exception offset 对照表
exception level迁移情况 |
Synchronous exception的offset值 |
IRQ和vIRQ exception的offset值 |
FIQ和vFIQ exception的offset值 |
SError和vSError exception的offset值 |
同级exception level迁移,使用SP_EL0。例如EL1迁移到EL1 |
0x000 |
0x080 |
0x100 |
0x180 |
同级exception level迁移,使用SP_ELx。例如EL1迁移到EL1 |
0x200 |
0x280 |
0x300 |
0x380 |
ELx迁移到ELy,其中y>x并且ELx处于AArch64状态 |
0x400 |
0x480 |
0x500 |
0x580 |
ELx迁移到ELy,其中y>x并且ELx处于AArch32状态 |
0x600 |
0x680 |
0x700 |
0x780 |
Arm64架构中的中断向量表包含16个entry,这16个entry分为4组,每组包含4个entry,每组中的4个entry分别对应4种类型的异常:
1.Synchronous Aborts
2.IRQ
3.FIQ
4.SError
如下为Linux-4.13.8版本EL1异常向量表:
4个组的分类根据发生异常时是否发生异常级别切换、和使用的堆栈指针来区别。分别对应于如下4组(参照上面“Exception offset 对照表”来理解):
Ø 1.异常发生在当前级别且使用SP_EL0(EL0级别对应的堆栈指针),即发生异常时不发生异常级别切换,可以简单理解为异常发生在内核态(EL1),且使用EL0级别对应的SP。 这种情况在Linux内核中未进行实质处理,直接进入bad_mode()流程。
Ø 2.异常发生在当前级别且使用SP_ELx(ELx级别对应的堆栈指针,x可能为1、2、3),即发生异常时不发生异常级别切换,可以简单理解为异常发生在内核态(EL1),且使用EL1级别对应的SP。 这是比较常见的场景。
Ø 3.异常发生在更低级别且在异常处理时使用AArch64模式。 可以简单理解为异常发生在用户态,且进入内核处理异常时,使用的是AArch64执行模式(非AArch32模式)。 这也是比较常见的场景。
Ø 4.异常发生在更低级别且在异常处理时使用AArch32模式。 可以简单理解为异常发生在用户态,且进入内核处理异常时,使用的是AArch32执行模式(非AArch64模式)。 这中场景基本未做处理。
start_kernel()--->init_IRQ()--->irqchip_init()--->of_irq_init(__irqchip_of_table)
__irqchip_begin就是内核irq chip table的首地址,这个table也就保存了kernel支持的所有的中断控制器的驱动代码初始化函数和DT compatible string的对应关系。
IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init);将gic_v3的驱动绑定为gic_of_init。
el0_irq/el1_irq--->irq_handler--->handle_arch_irq(handle_arch_irq为全局回调函数)
在gic_of_init--->gic_init_bases--->set_handle_irq(gic_handle_irq)中将gic_handle_irq传给了handle_arch_irq;
gic_handle_irq--->handle_domain_irq--->__handle_domain_irq--->generic_handle_irq--->
generic_handle_irq_desc--->desc->handle_irq(desc)<根据具体irq_desc注册的回调函数处理>
注1:目前内核中实现的异步异常处理包括:EL0->EL1及EL1->EL1的IRQ中断处理;其他exception level的IRQ及FIQ未处理。参照Linux-4.13.8版本arch\arm64\kernel\entry.S
注2:中断子系统中初始化就没有拷贝向量表这一步了,因为在arm64系统里有个专门的寄存器存放每个EL的基地址:VBAR_ELx
参见kern\arm\64\ivt.S
参见kern\arm\64\cpu-arm-64.cpp
.Lirq_entry [各寄存器入栈;保存跳转返回信息地址程序链接寄存器LR(X30)]
.Lirq_entry: sub sp, sp, #EF_SIZE //EF_SIZE = 38 * 8,让sp指向栈底 save_gprs //保存通用寄存器 mrs x3, EL(ELR) //#define EL(x) x##_el1,保存ELR_EL1寄存器 mrs x4, EL(SPSR) //保存SPSR_EL1状态寄存器 stp x3, x4, [sp, #EF_PC] //保存ELR和SPSR到第36和第37个栈地址(0开始) mov x0, sp //此时sp扔指向栈底,因为前面操作未加“!” str x0, [sp, #EF_KSP] //sp寄存器值保存在第32个地址 adr x30, .Lret_from_exception //LR(X30)保存异常返回地址 b irq_handler //跳转到具体中断处理函数
|
.macro save_gprs no_78=0 //保存xzr+x0~x31共32个寄存器 stp xzr, x0, [sp] stp x1, x2, [sp, #16] stp x3, x4, [sp, #32] stp x5, x6, [sp, #48] .if \no_78 == 0 stp x7, x8, [sp, #64] .endif stp x9, x10, [sp, #80] stp x11, x12, [sp, #96] stp x13, x14, [sp, #112] stp x15, x16, [sp, #128] stp x17, x18, [sp, #144] stp x19, x20, [sp, #160] stp x21, x22, [sp, #176] stp x23, x24, [sp, #192] stp x25, x26, [sp, #208] stp x27, x28, [sp, #224] stp x29, x30, [sp, #240] .endm |
.Lret_from_exception: //中断返回 ldr x0, [sp, #EF_KSP] //中断栈指针load到x0(此时仍然是栈底) __return_from_user_invoke: ldr x1, [x0, #EF_ERETW] //EF_ERETW = 0,将sp指针的值(地址)load到x1 cbnz x1, 1f //if(x1 != 0) goto 1f(f表示forward,向前找标号1) mov sp, x0 //还原栈指针 fast_ret_from_irq: mrs x5, daif //DAIF寄存器load到x5 cmp x5, #0x3c0 //0x3c0表示屏蔽所有中断 b.eq 99f //向前找标号99用99f,向后找用99b brk 0x400 //soft break point 99: load_eret_state sp //恢复ELR和SPSR寄存器 restore_gprs //恢复x0~x31共31个寄存器 add sp, sp, #EF_SIZE //将sp置于栈顶 Eret //中断返回
1: str xzr, [x0, #EF_ERETW] // reset continuation br x1 //handle continuation in x1 |
栈增长方向:低-->高
class Gic : public Irq_chip_gen
寄存器初始化:
Gic::hw_nr_irqs() //通过GICD_TYPER寄存器,获取gic允许的最大中断数
Gic::has_sec_ext() //通过GICD_TYPER寄存器,获取gic对Security Extensions的支持情况
Gic::pcpu_to_sgi() //
Gic::softint_cpu() //通过GICD_SGIR寄存器,设置软中断的目的CPU列表
Gic::softint_bcast() //通过GICD_SGIR寄存器,设置软中断过滤方式(详见GIC_V2文档)
Gic::pmr() //通过GICC_PMR寄存器,设置允许的最低优先级
Gic::gicc_enable() //通过GICC_CTRL寄存器,设置CPU interface使能
Gic::gicd_enable() //通过GICD_CTRL寄存器,设置Distributor使能
Gic::gicd_init_prio() //通过GICD_IPRIORITYR寄存器,初始化设置中断优先级
Gic::gicd_init_regs() //通过GICD_ICENABLER寄存器,初始化设置中断清除寄存器
Gic::cpu_init() //调用Gic::gicc_enable()使能CPU interface,另外if(!resume),还要进行 Distributor各个寄存器的初始化。
Gic::init_ap() //调用Gic::cpu_init()
Gic::init() //
.调用Gic::gicd_init_prio()初始化设置中断优先级
.调用Gic::gicd_init_regs()初始化设置中断清除寄存器
.初始化设置中断所属分组
.初始化中断的响应CPU为空
.调用Gic::gicd_enable(),设置Distributor使能
Gic::Gic() //构造函数。调用父类init函数申请资源;调用Gic::init()初始化
提供的硬件方法:
Gic::disable_locked() //提供特定中断disable功能
Gic::enable_locked() //提供特定中断enable功能
Gic::acknowledge_locked() //中断结束通知
Gic::mask() //调用Gic::disable_locked()
Gic::ack() //调用Gic::acknowledge_locked()
Gic::mask_and_ack() //调用disable_locked、acknowledge_locked
Gic::unmask() //调用Gic::enable_locked()
Gic::set_mode() //设置中断触发模式(电平触发<高、低>、沿触发<上升、下降>)
Gic::is_edge_triggered() //是否为边沿触发
Gic::hit() //中断处理,调用IRQ_CHIP的handle_irq方法处理
Gic::cascade_hit() //级联处理,调用子类的hit方法
Gic::pending() //获取中断号
Gic::set_cpu() //
Gic::set_pending_irq() //触发中断(软件中断)
class Irq_chip_gen : public Irq_chip_icu
提供的方法:
Irq_chip_gen::init() //为指定数目的中断分配空间
Irq_chip_gen::irq() //根据pin返回对应Irq_base指针
Irq_chip_gen::alloc() //调用父类bind方法绑定pin与Irq_base
Irq_chip_gen::unbind() //对irq进行解绑
Irq_chip_gen::reserve() //Irq_base数组对应pin置1
class Irq_chip_icu : public Irq_chip //中断控制器接口类,纯虚类
class Irq_chip //中断控制器基类
class Irq_base //中断处理器基类
class Upstream_irq //主要用于级联回复ack
class Irq_mgr //中断管理类。用于全局中断号到对应chip&pin映射关系的管理。
struct Irq // 封装一个chip&pin对
Irq chip() //虚函数,在子类Irq_mgr_single_chip中实现,用于返回一个Irq对
class Irq : public Irq_base //Hardware interrupts
Irq::get_irq_opcode() //获取操作码,详见L4_msg_tag定义
Irq::dispatch_irq_proto() //
class Irq_sender : public Kobject_h
//IRQ Kobject to send IPC messages to a receiving thread
Irq_sender::alloc //Bind a receiver to this device interrupt
Irq_sender::free //Release an interrupt
Irq_sender::Irq_sender() //默认初始化中断处理为电平触发
Irq_sender::switch_mode() //切换边沿触发
Irq_sender::destroy
Irq_sender::consume() //
Irq_sender::transfer_msg //
Irq_sender::modify_label
Irq_sender::handle_remote_hit
Irq_sender::send() //send msg
Irq_sender::_hit_level_irq
Irq_sender::hit_level_irq
Irq_sender::_hit_edge_irq
Irq_sender::hit_edge_irq
Irq_sender::sys_attach
Irq_sender::sys_detach()
Irq_sender::kinvoke
class Irq_muxer : public Kobject_h
//IRQ Kobject to broadcast IRQs to multilep other IRQ objects
Irq_muxer::unmask() //
Irq_muxer::mask() //
Irq_muxer::unbind //
Irq_muxer::mask_and_ack //
Irq_muxer::handle() //
Irq_muxer::destroy() //
Irq_muxer::sys_attach //
Irq_muxer::kinvoke