★★★ 个人博客导读首页—点击此处 ★★★
.
说明:
在默认情况下,本文讲述的都是ARMV8-aarch64架构,linux kernel 5.14

文章目录

  • 1、armv8-aarch64的异常向量表介绍
  • 2、armv8的VBAR_ELx寄存器
  • 3、Linux Kernel arm64中断向量表的定义
  • 4、Linux Kernel arm64设置中断向量表的基地址
  • 5、kernel_ventry宏的介绍
  • 6、未实现的异常向量: elx_yyy_invalid
  • 7、el1_irq的介绍 - 跳转到注册的handler函数
  • 7、handle_domain_irq
  • 8、关于中断级联的介绍

1、armv8-aarch64的异常向量表介绍


我们可以看出,实际上有四组表,每组表有四个异常入口,分别对应同步异常,IRQ,FIQ和serror。

  • 如果发生异常后并没有exception level切换,并且发生异常之前使用的栈指针是SP_EL0,那么使用第一组异常向量表。
  • 如果发生异常后并没有exception level切换,并且发生异常之前使用的栈指针是SP_EL1/2/3,那么使用第二组异常向量表。
  • 如果发生异常导致了exception level切换,并且发生异常之前的exception
    level运行在AARCH64模式,那么使用第三组异常向量表。
  • 如果发生异常导致了exception level切换,并且发生异常之前的exception
    level运行在AARCH32模式,那么使用第四组异常向量表。

另外我们还可以看到的一点是,每一个异常入口不再仅仅占用4bytes的空间,而是占用0x80 bytes空间,也就是说,每一个异常入口可以放置多条指令,而不仅仅是一条跳转指令

2、armv8的VBAR_ELx寄存器

armv8定义了VBAR_EL1、VBAR_EL2、VBAR_EL3三个基地址寄存器

思考:

1、VBAR_EL1、VBAR_EL2、VBAR_EL3写入的基地址,是物理地址还是虚拟地址?
2、基地址不再放0x00000000的位置吗?
3、异常向量表中,没有reset offset了?
4、异常向量表中的每一个offset为啥是0x80(128)地址空间? 以前是多少?
5、VBAR_ELx中,为啥末尾11个bit是reserved?

3、Linux Kernel arm64中断向量表的定义

(linux/arch/arm64/kernel/entry.S)/** Exception vectors.*/.pushsection ".entry.text", "ax".align  11
SYM_CODE_START(vectors)kernel_ventry    1, sync_invalid         // Synchronous EL1tkernel_ventry    1, irq_invalid          // IRQ EL1tkernel_ventry    1, fiq_invalid          // FIQ EL1tkernel_ventry    1, error_invalid        // Error EL1tkernel_ventry  1, sync             // Synchronous EL1hkernel_ventry    1, irq              // IRQ EL1hkernel_ventry    1, fiq              // FIQ EL1hkernel_ventry    1, error            // Error EL1hkernel_ventry  0, sync             // Synchronous 64-bit EL0kernel_ventry  0, irq              // IRQ 64-bit EL0kernel_ventry  0, fiq              // FIQ 64-bit EL0kernel_ventry  0, error            // Error 64-bit EL0#ifdef CONFIG_COMPATkernel_ventry    0, sync_compat, 32      // Synchronous 32-bit EL0kernel_ventry  0, irq_compat, 32       // IRQ 32-bit EL0kernel_ventry  0, fiq_compat, 32       // FIQ 32-bit EL0kernel_ventry  0, error_compat, 32     // Error 32-bit EL0
#elsekernel_ventry  0, sync_invalid, 32     // Synchronous 32-bit EL0kernel_ventry  0, irq_invalid, 32      // IRQ 32-bit EL0kernel_ventry  0, fiq_invalid, 32      // FIQ 32-bit EL0kernel_ventry  0, error_invalid, 32        // Error 32-bit EL0
#endif
SYM_CODE_END(vectors)

思考:

1、这里有没有按照armv8定义的异常向量表排列?不是每一个offset只有128bytes地址空间吗,如何做到的?
2、Linux Kernel arm64体系中不是没有实现FIQ吗,这里为何实现了?
3、第一组异常向量为何没有实现?

4、Linux Kernel arm64设置中断向量表的基地址

(linux/arch/arm64/kernel/head.S)SYM_FUNC_START_LOCAL(__primary_switched)adrp x4, init_thread_unionadd    sp, x4, #THREAD_SIZEadr_l   x5, init_taskmsr    sp_el0, x5          // Save thread_infoadr_l    x8, vectors         // load VBAR_EL1 with virtualmsr    vbar_el1, x8            // vector table addressisb......b   start_kernel
SYM_FUNC_END(__primary_switched)

思考:

1、设置VBAR_EL1,如果系统系统里有8个ARM Core,那么8个Core都需要设置吗,分别如何设置的?

5、kernel_ventry宏的介绍

(linux/arch/arm64/kernel/entry.S).macro kernel_ventry, el, label, regsize = 64.align 7
#ifdef CONFIG_UNMAP_KERNEL_AT_EL0.if    \el == 0
alternative_if ARM64_UNMAP_KERNEL_AT_EL0.if \regsize == 64mrs x30, tpidrro_el0msr tpidrro_el0, xzr.elsemov    x30, xzr.endif
alternative_else_nop_endif.endif
#endifsub   sp, sp, #PT_REGS_SIZE
#ifdef CONFIG_VMAP_STACK/** Test whether the SP has overflowed, without corrupting a GPR.* Task and IRQ stacks are aligned so that SP & (1 << THREAD_SHIFT)* should always be zero.*/add  sp, sp, x0          // sp' = sp + x0sub  x0, sp, x0          // x0' = sp' - x0 = (sp + x0) - x0 = sptbnz   x0, #THREAD_SHIFT, 0fsub    x0, sp, x0          // x0'' = sp' - x0' = (sp + x0) - sp = x0sub    sp, sp, x0          // sp'' = sp' - x0 = (sp + x0) - x0 = spb    el\()\el\()_\label0:/** Either we've just detected an overflow, or we've taken an exception* while on the overflow stack. Either way, we won't return to* userspace, and can clobber EL0 registers to free up GPRs.*//* Stash the original SP (minus PT_REGS_SIZE) in tpidr_el0. */msr   tpidr_el0, x0/* Recover the original x0 value and stash it in tpidrro_el0 */sub x0, sp, x0msr   tpidrro_el0, x0/* Switch to the overflow stack */adr_this_cpu sp, overflow_stack + OVERFLOW_STACK_SIZE, x0/** Check whether we were already on the overflow stack. This may happen* after panic() re-enables interrupts.*/mrs  x0, tpidr_el0           // sp of interrupted contextsub x0, sp, x0          // delta with top of overflow stacktst  x0, #~(OVERFLOW_STACK_SIZE - 1) // within range?b.ne    __bad_stack         // no? -> bad stack pointer/* We were already on the overflow stack. Restore sp/x0 and carry on. */sub   sp, sp, x0mrs   x0, tpidrro_el0
#endifb el\()\el\()_\label.endm

注意.align=7,说明该段代码是以2^7=128字节对其的,这和向量表中每一个offset的大小是一致的
代码看似非常复杂,其实最终跳转到了b el\()\el\()_\label, 翻译一下,其实就是跳转到了如下这样的函数中

el1_sync_invalid
el1_irq_invalid
el1_fiq_invalid
el1_error_invalidel1_sync
el1_irq
el1_fiq
el1_error       el0_sync
el0_irq
el0_fiq
el0_error

6、未实现的异常向量: elx_yyy_invalid

未实现的向量定义为了elx_yyy_invalid函数, 该invalid函数其实也是一种实现,它最终调用了panic函数
例如el1_irq_invalid的Flow : el1_irq_invalid --> bl bad_mode --> panic(“bad mode”)

SYM_CODE_START_LOCAL(el1_irq_invalid)inv_entry 1, BAD_IRQ
SYM_CODE_END(el1_irq_invalid)/** Bad Abort numbers*-----------------*/
#define BAD_SYNC    0
#define BAD_IRQ     1
#define BAD_FIQ     2
#define BAD_ERROR   3/** Invalid mode handlers*/.macro  inv_entry, el, reason, regsize = 64kernel_entry \el, \regsizemov   x0, spmov   x1, #\reasonmrs x2, esr_el1bl   bad_modeASM_BUG().endm
 /** bad_mode handles the impossible case in the exception vector. This is always* fatal.*/asmlinkage void notrace bad_mode(struct pt_regs *regs, int reason, unsigned int esr){arm64_enter_nmi(regs);console_verbose();pr_crit("Bad mode in %s handler detected on CPU%d, code 0x%08x -- %s\n",handler[reason], smp_processor_id(), esr,esr_get_class_string(esr));__show_regs(regs);local_daif_mask();panic("bad mode");}

7、el1_irq的介绍 - 跳转到注册的handler函数

抛开事务看本质,el1_interrupt_handler handle_arch_irq其实就是调用handle_arch_irq, 而handle_arch_irq指向irq-gic-v3.c中定义的handler函数

 .align  6
SYM_CODE_START_LOCAL_NOALIGN(el1_irq)kernel_entry 1el1_interrupt_handler handle_arch_irqkernel_exit 1
SYM_CODE_END(el1_irq)

这里我们就不再深究kernel_entry和kernel_exit,它俩里面干得事情非常多。当前我们需要了解,一个是保存general purpose寄存器,一个是恢复就可以了。

.macro   kernel_entry, el, regsize = 64
.if \regsize == 32
mov w0, w0              // zero upper 32 bits of x0
.endif
stp x0, x1, [sp, #16 * 0]
stp x2, x3, [sp, #16 * 1]
stp x4, x5, [sp, #16 * 2]
stp x6, x7, [sp, #16 * 3]
stp x8, x9, [sp, #16 * 4]
stp x10, x11, [sp, #16 * 5]
stp x12, x13, [sp, #16 * 6]
stp x14, x15, [sp, #16 * 7]
stp x16, x17, [sp, #16 * 8]
stp x18, x19, [sp, #16 * 9]
stp x20, x21, [sp, #16 * 10]
stp x22, x23, [sp, #16 * 11]
stp x24, x25, [sp, #16 * 12]
stp x26, x27, [sp, #16 * 13]
stp x28, x29, [sp, #16 * 14]
......
.macro   kernel_exit, el
......
msr elr_el1, x21            // set up the return data
msr spsr_el1, x22
ldp x0, x1, [sp, #16 * 0]
ldp x2, x3, [sp, #16 * 1]
ldp x4, x5, [sp, #16 * 2]
ldp x6, x7, [sp, #16 * 3]
ldp x8, x9, [sp, #16 * 4]
ldp x10, x11, [sp, #16 * 5]
ldp x12, x13, [sp, #16 * 6]
ldp x14, x15, [sp, #16 * 7]
ldp x16, x17, [sp, #16 * 8]
ldp x18, x19, [sp, #16 * 9]
ldp x20, x21, [sp, #16 * 10]
ldp x22, x23, [sp, #16 * 11]
ldp x24, x25, [sp, #16 * 12]
ldp x26, x27, [sp, #16 * 13]
ldp x28, x29, [sp, #16 * 14]
ldr lr, [sp, #S_LR]
add sp, sp, #PT_REGS_SIZE       // restore sp
......



我们再来剖析gic_handle_irq()函数,其实就是涉及gic的读写了,从gic中读取硬件中断号,然后调用handle_domain_irq函数,找到相匹配的中断hander函数,然后回调。

(linux/drivers/irqchip/irq-gic-v3.c)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_NMI_PRI)) {gic_handle_nmi(irqnr, regs);return;}if (gic_prio_masking_enabled()) {gic_pmr_mask_irqs();gic_arch_enable_irqs();}if (static_branch_likely(&supports_deactivate_key))gic_write_eoir(irqnr);elseisb();if (handle_domain_irq(gic_data.domain, irqnr, regs)) {WARN_ONCE(true, "Unexpected interrupt received!\n");gic_deactivate_unhandled(irqnr);}
}


另外注意一点,在Linux Kernel5.0之后,gic中的handler处理函数,发生了一些细微的变化,如下所示:

7、handle_domain_irq

补充IRQ Domain介绍
在linux kernel中,我们使用下面两个ID来标识一个来自外设的中断:

1、IRQ number。CPU需要为每一个外设中断编号,我们称之IRQ Number。这个IRQ number是一个虚拟的interrupt ID,和硬件无关,仅仅是被CPU用来标识一个外设中断。

2、HW interrupt ID。对于interrupt controller而言,它收集了多个外设的interrupt request line并向上传递,因此,interrupt controller需要对外设中断进行编码。Interrupt controller用HW interrupt ID来标识外设的中断。在interrupt controller级联的情况下,仅仅用HW interrupt ID已经不能唯一标识一个外设中断,还需要知道该HW interrupt ID所属的interrupt controller(HW interrupt ID在不同的Interrupt controller上是会重复编码的)。

这样,CPU和interrupt controller在标识中断上就有了一些不同的概念,但是,对于驱动工程师而言,我们和CPU视角是一样的,我们只希望得到一个IRQ number,而不关系具体是那个interrupt controller上的那个HW interrupt ID。这样一个好处是在中断相关的硬件发生变化的时候,驱动软件不需要修改。因此,linux kernel中的中断子系统需要提供一个将HW interrupt ID映射到IRQ number上来的机制…

(本段转载自:http://www.wowotech.net/linux_kenrel/irq-domain.html)

思考:

1、上文提到"在interrupt controller级联的情况下", 为什么会有中断级联,一个gic控制器可以连接好几千个中断难道还不够吗?

handle_domain_irq的处理流程如下所示,最终是调用到了我们request_irq注册的中断处理函数.

8、关于中断级联的介绍

这也是我想不通的地方,一个gic控制器可以连接好几千个中断难道还不够吗? 也许是为了SOC方便设计。例如某平台(mt6785)就使用到了级联的方式

/ {model = "MT6785";compatible = "mediatek,MT6785";interrupt-parent = <&sysirq>;#address-cells = <2>;#size-cells = <2>;gic: interrupt-controller {compatible = "arm,gic-v3";#interrupt-cells = <3>;#address-cells = <2>;#size-cells = <2>;#redistributor-regions = <1>;interrupt-parent = <&gic>;interrupt-controller;reg = <0 0x0c000000 0 0x40000>, // distributor<0 0x0c040000 0 0x200000>,// redistributor<0 0x0c53a650 0 0x50>; // INT_POLinterrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH>;};sysirq: intpol-controller@0 {compatible = "mediatek,mt6577-sysirq";interrupt-controller;#interrupt-cells = <3>;interrupt-parent = <&gic>;reg = <0 0x0c53a650 0 0x50>;};pio: pinctrl {compatible = "mediatek,mt6785-pinctrl";reg_bases = <&gpio>,<&iocfg_rm>,<&iocfg_br>,<&iocfg_bl>,<&iocfg_lb>,<&iocfg_rt>,<&iocfg_lt>,<&iocfg_tl>;reg_base_eint = <&eint>;pins-are-numbered;gpio-controller;gpio-ranges = <&pio 0 0 210>;#gpio-cells = <2>;interrupt-controller;#interrupt-cells = <4>;interrupts = <GIC_SPI 204 IRQ_TYPE_LEVEL_HIGH>;};/* Trustonic Mobicore SW IRQ number 121 = 32 + 89 */mobicore {compatible = "trustonic,mobicore";interrupts = <GIC_SPI 89 IRQ_TYPE_EDGE_RISING>;};/* Microtrust SW IRQ number 91(123) ~ 95(127) & 331(363) */utos {compatible = "microtrust,utos";interrupts = <GIC_SPI 91 IRQ_TYPE_EDGE_RISING>,<GIC_SPI 92 IRQ_TYPE_EDGE_RISING>;};
  • interrupts : 一个计算机系统中大量设备都是通过中断请求CPU服务的,所以设备节点就需要在指定中断号。常用的属性;
  • interrupt-controller : 一个空属性用来声明这个node接收中断,即一个node是一个中断控制器;
  • #interrupt-cells,是中断控制器节点的属性,用来标识这个控制器需要几个单位做中断描述符,用来描述子节点"interrupts"属性使用了父节点中的interrupt属性的具体哪个值;一般,如果父节点的该属性的值为3,则子节点的interrupts一个cell的三个32bits的整数值分别为:<中断域 中断 触发方式>,如果父节点的该属性为2,则是<中断 触发方式> interrupt-parent,标识此设备节点属于哪一个中断控制器,如果没有设置这个属性,会自动依附父节点的;
    注意在dts中#不是注释的意思,#也是一个有效的字符

另外例如我们再看mobicore和utos node时,在该node没有interrupt-parent属性,那么认为其父节点/就是其父节点,如果在父节点下依然没有interrupt-parent属性,那么还是继续再往上一级去寻找父节点。在父节点下(本示例为/)找到interrupt-parent属性。该属性引用的标签为sysirq。 所以mobicore、utos中的interrupt连接的sysirq,而不是直接连的gic。


欢迎添加微信、微信群,多多交流

Linux Kernel 5.14 arm64异常向量表解读-中断处理解读相关推荐

  1. optee3.14中的异常向量表解读--中断处理解读

    optee3.14中的异常向量表.VBAR_EL1.中断实现的介绍 ★★★ 个人博客导读首页-点击此处 ★★★ . 说明: 在默认情况下,本文讲述的都是ARMV8-aarch64架构,optee3.1 ...

  2. [armv8-arch64]linux kernel 5.9的异常量表介绍(irq,fiq,sync,svc)

    在entry.S中,定义了异常向量表,从代码中我们可以知道以下信息: 该表的基地址在vectors处(在开机的时候,会将其写入到vbar_el1中) 这个表以".align 11" ...

  3. 【linux kernel】基于ARM64分析linux内核的链接脚本vmlinux.lds.S

    文章目录 一.导读 二.链接器是什么 三.链接脚本 四.linux内核的链接脚本 4-1 头文件包含描述 4-2 参数设置和宏定义描述 4-3 SECTIONS内容分析 五.linux内核的" ...

  4. [ARM异常]-linux中(aarch/aarch64)异常向量表介绍

    文章目录 1.ARM的异常向量表基地址寄存器--VBAR 1.1.armv8 : VBAR寄存器 1.2.armv7 : VBAR寄存器 2.ARM的异常向量表的定义 2.1 armv8 :异常向量表 ...

  5. linux4.14内核,Linux内核4.14.14,4.9.77,4.4.112和3.18.92更新发布

    导读 正如所承诺的,Linux内核维护者Greg Kroah-Hartman今天发布了针对长期支持的Linux 4.14,4.9,4.4和3.18内核系列的一系列新更新. 这些新内核在他们之前发布的一 ...

  6. linux内核3.14.4,Linux内核4.14.14,4.9.77,4.4.112和3.18.92更新发布

    原标题:Linux内核4.14.14,4.9.77,4.4.112和3.18.92更新发布 导读 正如所承诺的,Linux内核维护者Greg Kroah-Hartman今天发布了针对长期支持的Linu ...

  7. linux内核漏洞分类,blog/linux kernel double-free类型漏洞的利用.md at master · snorez/blog · GitHub...

    对linux kernel double-free类型漏洞的较通用利用方法 update Wed Nov 29 16:39:01 HKT 2017 linux kernel 4.14 released ...

  8. Linux内核异常向量表在哪,ARM64的启动过程之(六):异常向量表的设定

    ARM64的启动过程之(六):异常向量表的设定 作者:linuxer 发布于:2015-11-24 18:22 分类:ARMv8A Arch 一.前言 本文主要描述了4.1.10内核初始化过程中如何初 ...

  9. 在linux、optee、ATF中的中断异常向量表

    目录 1.在linux中的异常向量表 (1).arm64的异常向量表-(irq,fiq,svc......) (2).arm32的异常向量表-(irq,fiq,swi......) 2.在optee中 ...

最新文章

  1. Android自定义控件系列之基础篇
  2. Android 手机卫士--自定义组合控件构件布局结构
  3. Today:基于 Electron 和 Vue.js 的 GTD 应用
  4. FreeBsdb FAMP Lamp环境
  5. 玩转Google开源C++单元测试框架Google Test系列(gtest)之七 - 深入解析gtest
  6. springboot介绍_Spring Boot 主类及目录结构介绍!
  7. APP技巧:电脑登录微信,要删除这5个文件!否则别人能查看聊天记录
  8. DUMP文件分析4:栈溢出
  9. jQuery 插件写法
  10. excel用警员姓名查找警号信息
  11. 多域名环境,页面获取url的一种方案
  12. java table 内容居中_JTable内容居中显示 | 学步园
  13. myeclipse安装maven
  14. Excel如何从身份证号码中提取性别
  15. c语言与西门子plc通讯,西门子PLC四种核心通讯方式汇总学习
  16. 国内社交网络信息开放平台汇总
  17. 梯度消失和爆炸原因以及解决方法
  18. 在Linux 中安装cmus 用命令行中玩转音乐库
  19. 行列式运算法则 矩阵的运算及其运算规则:
  20. IGV变异可视化设置要点

热门文章

  1. python兼容性怎么样_Python与exe的兼容性
  2. 邀请参加活动的邀请函_圣诞节点灯仪式活动邀请函制作
  3. 如何定期按时完成数据中心的测试?
  4. HighNewTech:重磅!来自深度学习的三位大牛Yoshua、Hinton、LeCun荣获2018年图灵奖
  5. 成功解决gensim\matutils.py:737: FutureWarning: Conversion of the second argument of issubdtype from `int
  6. 成功解决AttributeError: 'map' object has no attribute 'items'
  7. DL之AF:机器学习/深度学习中常用的激活函数(sigmoid、softmax等)简介、应用、计算图实现、代码实现详细攻略
  8. 报错引发的版本对应——tensorflow+keras+python版本对应(全)
  9. jenkins的安装
  10. mysqil操作数据库