Linux Kernel 5.14 arm64异常向量表解读-中断处理解读
★★★ 个人博客导读首页—点击此处 ★★★
.
说明:
在默认情况下,本文讲述的都是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异常向量表解读-中断处理解读相关推荐
- optee3.14中的异常向量表解读--中断处理解读
optee3.14中的异常向量表.VBAR_EL1.中断实现的介绍 ★★★ 个人博客导读首页-点击此处 ★★★ . 说明: 在默认情况下,本文讲述的都是ARMV8-aarch64架构,optee3.1 ...
- [armv8-arch64]linux kernel 5.9的异常量表介绍(irq,fiq,sync,svc)
在entry.S中,定义了异常向量表,从代码中我们可以知道以下信息: 该表的基地址在vectors处(在开机的时候,会将其写入到vbar_el1中) 这个表以".align 11" ...
- 【linux kernel】基于ARM64分析linux内核的链接脚本vmlinux.lds.S
文章目录 一.导读 二.链接器是什么 三.链接脚本 四.linux内核的链接脚本 4-1 头文件包含描述 4-2 参数设置和宏定义描述 4-3 SECTIONS内容分析 五.linux内核的" ...
- [ARM异常]-linux中(aarch/aarch64)异常向量表介绍
文章目录 1.ARM的异常向量表基地址寄存器--VBAR 1.1.armv8 : VBAR寄存器 1.2.armv7 : VBAR寄存器 2.ARM的异常向量表的定义 2.1 armv8 :异常向量表 ...
- 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内核系列的一系列新更新. 这些新内核在他们之前发布的一 ...
- 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 ...
- 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 ...
- Linux内核异常向量表在哪,ARM64的启动过程之(六):异常向量表的设定
ARM64的启动过程之(六):异常向量表的设定 作者:linuxer 发布于:2015-11-24 18:22 分类:ARMv8A Arch 一.前言 本文主要描述了4.1.10内核初始化过程中如何初 ...
- 在linux、optee、ATF中的中断异常向量表
目录 1.在linux中的异常向量表 (1).arm64的异常向量表-(irq,fiq,svc......) (2).arm32的异常向量表-(irq,fiq,swi......) 2.在optee中 ...
最新文章
- Android自定义控件系列之基础篇
- Android 手机卫士--自定义组合控件构件布局结构
- Today:基于 Electron 和 Vue.js 的 GTD 应用
- FreeBsdb FAMP Lamp环境
- 玩转Google开源C++单元测试框架Google Test系列(gtest)之七 - 深入解析gtest
- springboot介绍_Spring Boot 主类及目录结构介绍!
- APP技巧:电脑登录微信,要删除这5个文件!否则别人能查看聊天记录
- DUMP文件分析4:栈溢出
- jQuery 插件写法
- excel用警员姓名查找警号信息
- 多域名环境,页面获取url的一种方案
- java table 内容居中_JTable内容居中显示 | 学步园
- myeclipse安装maven
- Excel如何从身份证号码中提取性别
- c语言与西门子plc通讯,西门子PLC四种核心通讯方式汇总学习
- 国内社交网络信息开放平台汇总
- 梯度消失和爆炸原因以及解决方法
- 在Linux 中安装cmus 用命令行中玩转音乐库
- 行列式运算法则 矩阵的运算及其运算规则:
- IGV变异可视化设置要点
热门文章
- python兼容性怎么样_Python与exe的兼容性
- 邀请参加活动的邀请函_圣诞节点灯仪式活动邀请函制作
- 如何定期按时完成数据中心的测试?
- HighNewTech:重磅!来自深度学习的三位大牛Yoshua、Hinton、LeCun荣获2018年图灵奖
- 成功解决gensim\matutils.py:737: FutureWarning: Conversion of the second argument of issubdtype from `int
- 成功解决AttributeError: 'map' object has no attribute 'items'
- DL之AF:机器学习/深度学习中常用的激活函数(sigmoid、softmax等)简介、应用、计算图实现、代码实现详细攻略
- 报错引发的版本对应——tensorflow+keras+python版本对应(全)
- jenkins的安装
- mysqil操作数据库