arm_linux中断子系统

1. Arm Linux 中断子系统

1.1. 中断硬件系统3个组成部分:

外设(中断源)、中断控制器、CPU

1.2. Linux中断子系统4个部分:

普通外设驱动、Linux kernel通用中断处理模块(硬件无关代码)、CPU架构相关处理、中断控制器驱动代码

 

1.1 Linux 中断子系统分层图

 

1.3. 两种中断请求(ARM):

IRQFIQ

1.4. 几个重要数据结构

irq_desc:系统中每一个irq都对应着一个irq_desc结构

irq_data:中断相关数据信息

irq_chip:中断控制器数据结构

Irqaction:中断响应链表,当一个irq被触发时,内核会遍历该链表,调用action结构中的回调handler或者激活其中的中断线程

 

 

1.2 Irq各个数据结构关系图

 

1.5. 数据结构的管理辅助函数

generic_handle_irq();

irq_to_desc();

irq_set_chip();

irq_set_chained_handler();

1.6. 中断处理标准函数

handle_simple_irq();

handle_level_irq(); //电平中断流控处理程序

handle_edge_irq(); //边沿触发中断流控处理程序

handle_fasteoi_irq(); //需要eoi的中断处理器使用的中断流控处理程序

handle_percpu_irq(); //irq只有单个cpu响应时使用的流控处理程序

1.7. 提供给驱动的API

enable_irq();

disable_irq();

disable_irq_nosync();

request_threaded_irq();

irq_set_affinity();

1.8. 中断子系统初始化:

中断向量表拷贝

Start_kernel()--->setup_arch()--->early_trap_init()

irq_desc结构体分配资源、填充默认值

Start_kernel()--->early_irq_init()<初始化irq_desc结构体>---->arch_early_irq_init()

初始化irq_desc、注册中断控制器驱动

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的对应关系。

1.9. 中断发生时,CPU硬件动作

保存进程(或中断)上下文(各寄存器<R0~R15>入栈

l R14保存PC

l CPSR值存入SPSR,同时修改CPSR中工作模式为中断模式、中断屏蔽位置1

l PC赋值为异常向量表中的相应中断地址

1.10. 中断处理流程:

外部中断产生--->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. GIC_V2V3ARM64中断机制

2.1. ARM中断控制器 GICV2):

 

图2.1 GIC400原理框图

输入: SGISoftware-generated Interrupts),软件中断(ID0~ID15

PPIPrivate Peripheral Interrupt),CPU私有中断(ID16~ID31

SPIShared Peripheral Interrupt),CPU间共有中断(ID32~ID1019

注: GIC400(arm_v7)只支持480SPIGIC600arm_v8)支持960SPI

 

图2.2 GIC-V2的内部逻辑图

2.2. GIC-V3支持ARMv8架构

输入:

LPI(Locality-specific Peripheral Interrupts ),基于消息的中断

SGISoftware-generated Interrupts),软件中断(ID0~ID15)

PPIPrivate Peripheral Interrupt),CPU私有中断(ID16~ID31

SPIShared Peripheral Interrupt),CPU间共有中断(ID32~ID1019

 

图2.3  GIC logical partitioning with an ITS

 

2.3. GIC v3基于v2的提升

2.3.1.GIC scalability

Ø v2最大支持8PE;

Ø v3支持更多(GIC-600Supports up to 512 processor cores/threads per chip and up to 16 chips in a multi-chip configuration;GIC-500Supports up to 128 cores within a maximum of 32 clusters using affinity-level routing);

Ø v3改变了interrupt routing机制为affinity routing(需要在GICD.CTLRenable),增加Redistributer组件来实现

2.3.2.Interrupt Grouping:

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   virtualizationor 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

2.3.3.Interrupt Translation Service(ITS)

软件控制方法,将中断转换为物理中断或者虚拟中断

Ø 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

2.3.4.LPIsLocality-specific Peripheral Interrupts)

Ø 新的Interrupt类型,extends Interrupt ID space

Ø optional。如果要实现,需要ITS支持

2.3.5.SGIs(Software Generate Interrupts)

Ø modified context of SGI

Ø no includes identity of source PE.

Ø 之前的SGI是在支持legacy operation的GIC中才可用

2.3.6.SPIs(Shared Peripheral Interrupts)

Ø add new regs in Distributer to support setting and clearing of message-based SPIs

2.3.7.System Register Interface

Ø 使用system register instruction进行控制

Ø all are memory mapped

Ø 需要在对应ELICC_SRE.SRE中使能

2.4. GIC v4 improvement

Ø support for direct injection of virtual interrupts to a VM, without involving Hypervisor.

Ø system at least needs one ITS that translate interrupts into LPIs.


3. ARM_V8(arm64)中断机制

3.1. Exception level

arm_v7a~arm_v8,中断机制引入一个新概念“Exception level”,用于整合之前架构中的processor modeprivilege level相关的功能,同时提供了完整的“security model”和“virtualization框架”的支持。

Arm_v8Exception level分为4个等级:EL0~EL3ELnprivilege随着n的增大而增大。类似地,可以将EL0归属于non-privilege levelEL1/2/3属于privilege level。如下图:

 

ARMv8-a Exception level有关的说明如下:

1)首先需要注意的是,AArch64中,已经没有UserSVCABT等处理器模式的概念,但ARMv8需要向前兼容,在AArch32中,就把这些处理器模式map到了4Exception level

2Application位于特权等级最低的EL0Guest OSLinux kernelwindow等)位于EL1,提供虚拟化支持的Hypervisor位于EL2(可以不实现),提供Security支持的Seurity Monitor位于EL3(可以不实现)。

3)只有在异常发生时(或者异常处理返回时),才能切换Exception level(这也是Exception level的命名原因,为了处理异常)。当异常发生时,有两种选择,停留在当前的EL,或者跳转到更高的ELEL不能降级。同样,异常处理返回时,也有两种选择,停留在当前EL,或者调到更低的EL

3.2. 异步异常和同步异常

Ø 异步异常:

CPU CORE外部产生,IRQFIQERROR

Ø 同步异常:

CPU内部产生如:未定义的指令、data abortprefetch instruction abortSP未对齐异常、debug exceptionSVC/HVC/SMC指令

3.3. 涉及的寄存器

3.3.1.通用寄存器:

x0x30共计31个;

3.3.2.SP寄存器:

每个exception level都有自己的stack pointer register,名字是SP_ELx,当然,前提是处理器支持该EL。对于EL0,只能使用SP_EL0EL1~EL3,除了可以使用自己的SP_ELx,还可以选择使用SP_EL0

3.3.3.SPSR寄存器:

当发生异常的时候,PE总是把当前cpu的状态保存在SPSR寄存器中,该寄存器有三个,分别是SPSR_EL1SPSR_EL2SPSR_EL3,异常迁移到哪一个exception level就使用哪一个SPSR。在返回异常现场的时候,可以使用SPSR_ELx来恢复PE的状态;

3.3.4.PC寄存器:

当发生异常的时候,PE把一个适当的返回地址保存在ELRException Link Register)寄存器中,该寄存器有三个,分别是ELR_EL1ELR_EL2ELR_EL3,异常迁移到哪一个exception level就使用哪一个ELR。在返回异常现场的时候,可以使用ELR_ELx来恢复PC值;

3.3.5.VBAR寄存器:

各个exception levelVector Base Address Register (VBAR)寄存器,该寄存器保存了各个exception level的异常向量表的基地址。该寄存器有三个,分别是VBAR_EL1VBAR_EL2VBAR_EL3

具体的exception handler是通过vector base address offset得到,offset的定义如下表所示:

Exception offset 对照表

exception level迁移情况

Synchronous exceptionoffset

IRQvIRQ exceptionoffset

FIQvFIQ exceptionoffset

SErrorvSError exceptionoffset

同级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

3.4. 中断向量表

Arm64架构中的中断向量表包含16entry,这16entry分为4组,每组包含4entry,每组中的4entry分别对应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可能为123),即发生异常时不发生异常级别切换,可以简单理解为异常发生在内核态(EL1),且使用EL1级别对应的SP。 这是比较常见的场景。

Ø 3.异常发生在更低级别且在异常处理时使用AArch64模式。 可以简单理解为异常发生在用户态,且进入内核处理异常时,使用的是AArch64执行模式(AArch32模式)。 这也是比较常见的场景。

Ø 4.异常发生在更低级别且在异常处理时使用AArch32模式。 可以简单理解为异常发生在用户态,且进入内核处理异常时,使用的是AArch32执行模式(AArch64模式)。 这中场景基本未做处理。

3.5. 中断控制器驱动注册:

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

3.6. IRQ中断处理流程:

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->EL1EL1->EL1IRQ中断处理;其他exception levelIRQFIQ未处理。参照Linux-4.13.8版本arch\arm64\kernel\entry.S

 

2中断子系统中初始化就没有拷贝向量表这一步了,因为在arm64系统里有个专门的寄存器存放每个EL的基地址:VBAR_ELx

4. Fiasco 17.09 arm64中断机制分析

4.1. 中断向量表:

4.1.1.定义:

参见kern\arm\64\ivt.S

 

4.1.2.地址赋值VBAR寄存器:

参见kern\arm\64\cpu-arm-64.cpp

 

4.2. IRQ中断处理函数:

.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]   //保存ELRSPSR到第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~x3132个寄存器

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]     //中断栈指针loadx0(此时仍然是栈底)

__return_from_user_invoke:

ldr x1, [x0, #EF_ERETW]  //EF_ERETW = 0,sp指针的值(地址)loadx1

cbnz x1, 1f                //if(x1 != 0) goto 1f(f表示forward,向前找标号1)

mov sp, x0                //还原栈指针

fast_ret_from_irq:

mrs x5, daif               //DAIF寄存器loadx5

cmp x5, #0x3c0            //0x3c0表示屏蔽所有中断

b.eq 99f                  //向前找标号9999f向后找用99b

brk 0x400                //soft break point

99:

load_eret_state sp          //恢复ELRSPSR寄存器

restore_gprs              //恢复x0~x3131个寄存器

add sp, sp, #EF_SIZE      //sp置于栈顶

Eret                     //中断返回

1: str xzr, [x0, #EF_ERETW] // reset continuation

br x1                 //handle continuation in x1

 

栈增长方向:低-->

4.3. 中断控制器GICV2:

class Gic : public Irq_chip_gen

寄存器初始化:

Gic::hw_nr_irqs() //通过GICD_TYPER寄存器,获取gic允许的最大中断数

Gic::has_sec_ext() //通过GICD_TYPER寄存器,获取gicSecurity 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_lockedacknowledge_locked

Gic::unmask() //调用Gic::enable_locked()

Gic::set_mode() //设置中断触发模式(电平触发<高、低>、沿触发<上升、下降>

Gic::is_edge_triggered() //是否为边沿触发

Gic::hit() //中断处理,调用IRQ_CHIPhandle_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方法绑定pinIrq_base

Irq_chip_gen::unbind() //irq进行解绑

Irq_chip_gen::reserve() //Irq_base数组对应pin1

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_sender, Irq>, public Ipc_sender<Irq_sender>

//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_muxer, Irq>, private Irq_chip

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

猜你喜欢

转载自blog.csdn.net/gaojy19881225/article/details/80019103