概述
在VBAR_ELx寄存器中存放着异常向量基地址
;/*********************************************************************************************************
; 关中断并设置异常向量表
;*********************************************************************************************************/
MSR DAIFSET , #2
ADRP X0 , vector
ADD X0 , X0 , #:lo12:vector
MSR VBAR_EL1 , X0
vector 是 定义的函数。
;/*********************************************************************************************************
; 向量表定义
;*********************************************************************************************************/
MACRO_DEF(VENTRY label)
.balign 128
B label
MACRO_END()
.balign 2048
FUNC_DEF(vector)
VENTRY(archEL1SyncInvalidEntry) ;/* Sync EL1t */
VENTRY(archEL1IrqInvalidEntry) ;/* IRQ EL1t */
VENTRY(archEL1FiqInvalidEntry) ;/* FIQ EL1t */
VENTRY(archEL1ErrInvalidEntry) ;/* Error EL1t */
VENTRY(archEL1SyncExcEntry) ;/* Sync EL1h */
VENTRY(archEL1IrqEntry) ;/* IRQ EL1h */
VENTRY(archEL1FiqInvalidEntry) ;/* FIQ EL1h */
VENTRY(archEL1ErrInvalidEntry) ;/* Error EL1h */
VENTRY(archEL2AArch64SyncExcEntry) ;/* Sync EL0 AArch64 */
VENTRY(archEL2AArch64IrqEntry) ;/* IRQ EL0 AArch64 */
VENTRY(archEL2AArch64FiqInvalidEntry) ;/* FIQ EL0 AArch64 */
VENTRY(archEL2AArch64ErrInvalidEntry) ;/* Error EL0 AArch64 */
VENTRY(archEL2AArch32SyncExcEntry) ;/* Sync EL0 AArch32 */
VENTRY(archEL2AArch32IrqEntry) ;/* IRQ EL0 AArch32 */
VENTRY(archEL2AArch32FiqInvalidEntry) ;/* FIQ EL0 AArch32 */
VENTRY(archEL2AArch32ErrInvalidEntry) ;/* Error EL0 AArch32 */
FUNC_END()
FILE_END()
VENTRY 是定义的宏以128 字节对齐。 异常向量表必须是2K对齐,因为VBAR_ELx 后低12位不保存。
为什么以128字节对齐是因为arm定义的异常entry偏移。armv8中分同步异常和异步异常。IRQ,FIQ,SError 为异步异常
。什么是异步异常(asynchronous exception)和同步异常(synchronous exception)可以参考ARMV8异常处理,arm64启动-异常向量表。 下图文档中的介绍的偏移。为什么sync和error都是无效的可以参考ARM64的启动过程之(六):异常向量表的设定给的介绍。
在SylixOS中最终无效的中断都会跳转到 ARCH_INVALID_EXC_ENTRY 这个宏定义
;/*********************************************************************************************************
; Invalid 异常入口
;*********************************************************************************************************/
MACRO_DEF(ARCH_INVALID_EXC_ENTRY type)
;/*
; * 使用异常临时栈, 并在异常临时栈开辟临时上下文保存区, 将 volatile 寄存器保存到临时上下文保存区
; * SP 指向异常临时栈
; */
EXC_SAVE_VOLATILE
BL API_InterEnter
;/*
; * 如果不是第一次进入中断, 跳转
; */
CMP X0 , #1
BNE 1f
;/*
; * 获得当前任务 TCB 的 ARCH_REG_CTX 地址
; */
BL API_ThreadTcbInter ;/* get current tcb */
;/*
; * 拷贝 volatile 寄存器到当前任务 TCB 的 ARCH_REG_CTX 里
; */
EXC_COPY_VOLATILE
;/*
; * 保存 non volatile 寄存器到当前任务 TCB 的 ARCH_REG_CTX 里
; */
EXC_SAVE_NON_VOLATILE
MOV X18, X0
;/*
; * 第一次进入中断: 获得当前 CPU 中断堆栈栈顶, 并设置 SP
; */
BL API_InterStackBaseGet
MOV SP , X0
MOV X0 , X18
2:
;/*
; * archInvalidExcHandle()
; */
MRS X1 , ESR_EL1
MOV X2 , type
BL archInvalidExcHandle
;/*
; * 来到这里, 说明发生了中断嵌套
; */
MOV X18 , SP
RESTORE_BIG_REG_CTX ;/* 恢复所有寄存器 */
1:
;/*
; * 不是第一次进入中断
; */
LDR X0 , [SP, #XSP_OFFSET] ;/* 获取异常前 SP */
SUB X0 , X0 , ARCH_REG_CTX_SIZE ;/* 在异常堆栈开辟上下文保存区 */
;/*
; * 拷贝 volatile 寄存器到异常堆栈里的上下文保存区
; */
EXC_COPY_VOLATILE
;/*
; * 保存 non volatile 寄存器到异常堆栈里
; */
EXC_SAVE_NON_VOLATILE
MOV SP , X0 ;/* 使用异常堆栈 */
B 2b
MACRO_END()
EXC_SAVE_VOLATILE 首先保存上下文寄存器。
MACRO_DEF(EXC_SAVE_VOLATILE)
MRS X18 , TPIDR_EL1 ;/* 读出异常临时堆栈地址 */
SUB X18 , X18 , ARCH_REG_CTX_SIZE ;/* 在临时堆栈开辟上下文保存区 */
STP X0 , X1 , [X18 , #XGREG_OFFSET(0)]
STP X2 , X3 , [X18 , #XGREG_OFFSET(2)]
STP X4 , X5 , [X18 , #XGREG_OFFSET(4)]
STP X6 , X7 , [X18 , #XGREG_OFFSET(6)]
STP X8 , X9 , [X18 , #XGREG_OFFSET(8)]
STP X10 , X11 , [X18 , #XGREG_OFFSET(10)]
STP X12 , X13 , [X18 , #XGREG_OFFSET(12)]
STP X14 , X15 , [X18 , #XGREG_OFFSET(14)]
STP X16 , X17 , [X18 , #XGREG_OFFSET(16)]
STP X29 , LR , [X18 , #XGREG_OFFSET(29)]
MRS X2 , SPSR_EL1
STR X2 , [X18, #XPSTATE_OFFSET] ;/* 保存 PSTATE */
MOV X2 , SP
STR X2 , [X18, #XSP_OFFSET] ;/* 保存 SP 指针 */
MRS X2 , ELR_EL1
STR X2 , [X18, #XPC_OFFSET]
MOV SP , X18
MACRO_END()
TPIDR_EL1 是线程ID寄存器,在SylixOS中使用这个寄存器保存了异常临时栈堆栈的地址。MSR指令将指针加载到X18寄存器,然后向下减,开辟出一个块保存上下文呢的区域。 STP 指令时同时将两个寄存器的值写入到存储中。LR 就是x30 寄存器,首先将x0 - x17寄存器保存外加x29 和x30。然后保存状态寄存器SPSR。因为系统时运行在EL1层所以当前操作的都是EL1的寄存器。然后保存栈指针寄存器SP。保存异常链接寄存器。然后更新了栈指针寄存器指向增加了上下文后的栈指针位置。 BL API_InterEnter 跳转到内核中断入口函数API_InterEnter。
ULONG API_InterEnter (ARCH_REG_T reg0,
ARCH_REG_T reg1,
ARCH_REG_T reg2,
ARCH_REG_T reg3)
{
PLW_CLASS_CPU pcpu;
pcpu = LW_CPU_GET_CUR();
pcpu->CPU_ulInterNesting++;
#if !defined(__SYLIXOS_ARM_ARCH_M__) || (LW_CFG_CORTEX_M_SVC_SWITCH > 0)
archIntCtxSaveReg(pcpu, reg0, reg1, reg2, reg3);
#endif
#if (LW_CFG_CPU_FPU_EN > 0) && (LW_CFG_INTER_FPU > 0)
if (LW_KERN_FPU_EN_GET()) { /* 中断状态允许使用浮点运算 */
__fpuInterEnter(pcpu);
}
#endif /* LW_CFG_CPU_FPU_EN > 0 */
/* LW_CFG_INTER_FPU > 0 */
#if (LW_CFG_CPU_DSP_EN > 0) && (LW_CFG_INTER_DSP > 0)
if (LW_KERN_DSP_EN_GET()) { /* 中断状态允许使用 DSP */
__dspInterEnter(pcpu);
}
#endif /* LW_CFG_CPU_DSP_EN > 0 */
/* LW_CFG_INTER_DSP > 0 */
return (pcpu->CPU_ulInterNesting);
}
API_InterEnter主要时当前cpu ID 中断嵌套基数。获取cpu ID。 获取当前cpu ID 使用如下函数
FUNC_DEF(archMpCur)
MRS X0 , TPIDR_EL1
LDR X0 , [X0 , #CPUID_OFFSET]
RET
FUNC_END()
也是从堆栈中获得,这里时启动时系统将从 MPIDR_EL1 寄存器获取的物理cpu信息存放在栈中。 具体可以参考另一篇文章文章链接
CMP X0 , #1 BNE 1f 这两句比对是不是头一次进中断,如果不是第一次跳转到 标签1处执行。如果是第一次继续向下执行。然后跳转到API_ThreadTcbInter函数,获取当前中断线程的tcp。
PLW_CLASS_TCB API_ThreadTcbInter (VOID)
{
PLW_CLASS_TCB ptcbCur;
LW_TCB_GET_CUR(ptcbCur);
return (ptcbCur);
}
此函数也是从堆栈中获取cpu id 然后从对用的cpu结构中获取tcp。由于返回值tcp 指针存放在x0寄存器中。所以调用EXC_COPY_VOLATILE 保存寄存器到tcp控制块中时直接使用x0作为基地址。
;/*********************************************************************************************************
; 拷贝 volatile 寄存器(参数 X0: 目的 ARCH_REG_CTX 地址, 参数 SP: 源 ARCH_REG_CTX 地址)
;*********************************************************************************************************/
MACRO_DEF(EXC_COPY_VOLATILE)
LDP X9 , X10 , [SP , #XGREG_OFFSET(0)]
STP X9 , X10 , [X0 , #XGREG_OFFSET(0)]
LDP X9 , X10 , [SP , #XGREG_OFFSET(2)]
STP X9 , X10 , [X0 , #XGREG_OFFSET(2)]
LDP X9 , X10 , [SP , #XGREG_OFFSET(4)]
STP X9 , X10 , [X0 , #XGREG_OFFSET(4)]
LDP X9 , X10 , [SP , #XGREG_OFFSET(6)]
STP X9 , X10 , [X0 , #XGREG_OFFSET(6)]
LDP X9 , X10 , [SP , #XGREG_OFFSET(8)]
STP X9 , X10 , [X0 , #XGREG_OFFSET(8)]
LDP X9 , X10 , [SP , #XGREG_OFFSET(10)]
STP X9 , X10 , [X0 , #XGREG_OFFSET(10)]
LDP X9 , X10 , [SP , #XGREG_OFFSET(12)]
STP X9 , X10 , [X0 , #XGREG_OFFSET(12)]
LDP X9 , X10 , [SP , #XGREG_OFFSET(14)]
STP X9 , X10 , [X0 , #XGREG_OFFSET(14)]
LDP X9 , X10 , [SP , #XGREG_OFFSET(16)]
STP X9 , X10 , [X0 , #XGREG_OFFSET(16)]
LDP X9 , X10 , [SP , #XGREG_OFFSET(29)] ;/* X29, LR */
STP X9 , X10 , [X0 , #XGREG_OFFSET(29)]
LDP X9 , X10 , [SP , #XPC_OFFSET] ;/* PC, SP */
STP X9 , X10 , [X0 , #XPC_OFFSET]
LDR X9 , [SP , #XPSTATE_OFFSET] ;/* PSTATE */
STR X9 , [X0 , #XPSTATE_OFFSET]
MACRO_END()
在线程控制块的头部就是寄存器上下问定义,所以可以直接以x0写入
typedef struct __lw_tcb {
ARCH_REG_CTX TCB_archRegCtx; /* 寄存器上下文 */
在保存完后调用了EXC_SAVE_NON_VOLATILE
MACRO_DEF(EXC_SAVE_NON_VOLATILE)
MOV X1 , #0
STR X1 , [X0, #CTX_TYPE_OFFSET] ;/* 设置为大上下文 */
STP X19 , X20 , [X0 , #XGREG_OFFSET(19)]
STP X21 , X22 , [X0 , #XGREG_OFFSET(21)]
STP X23 , X24 , [X0 , #XGREG_OFFSET(23)]
STP X25 , X26 , [X0 , #XGREG_OFFSET(25)]
STP X27 , X28 , [X0 , #XGREG_OFFSET(27)]
MACRO_END()
将剩下的寄存器保存。有个特别的地方,ARCH_REG_CTX 结构体的定义,第一个位置时定义了叫小上下文的变量。所以XGREG_OFFSET定义时是n+1
#define XGREG_OFFSET(n) (ARCH_REG_SIZE * (n + 1))
ESR_EL1 寄存器包含了一些当前一些信息。
ESR_EL1 内容和 当前中断的类型传入到 archInvalidExcHandle函数。archInvalidExcHandle函数主要是打印一些必要的信息,然后重启系统。
在无效中断里其实只有archInvalidExcHandle有作用,其他函数并没有实际意义,只是SylixOS 中断框架是先保存一些上下文环境。
;/*********************************************************************************************************
; 同步异常入口
;*********************************************************************************************************/
FUNC_DEF(archEL1SyncExcEntry)
;/*
; * 使用异常临时栈, 并在异常临时栈开辟临时上下文保存区, 将 volatile 寄存器保存到临时上下文保存区
; * SP 指向异常临时栈
; */
EXC_SAVE_VOLATILE
BL API_InterEnter
;/*
; * 如果不是第一次进入中断, 跳转
; */
CMP X0 , #1
BNE 1f
;/*
; * 获得当前任务 TCB 的 ARCH_REG_CTX 地址
; */
BL API_ThreadTcbInter ;/* get current tcb */
;/*
; * 拷贝 volatile 寄存器到当前任务 TCB 的 ARCH_REG_CTX 里
; */
EXC_COPY_VOLATILE
;/*
; * 保存 non volatile 寄存器到当前任务 TCB 的 ARCH_REG_CTX 里
; */
EXC_SAVE_NON_VOLATILE
MOV X18, X0
;/*
; * 第一次进入中断: 获得当前 CPU 中断堆栈栈顶, 并设置 SP
; */
BL API_InterStackBaseGet
MOV SP , X0
MOV X0 , X18
2:
;/*
; * archSyncExcHandle()
; */
MRS X1 , ESR_EL1
BL archSyncExcHandle
;/*
; * API_InterExit()
; * 如果没有发生中断嵌套, 则 API_InterExit 会调用 archIntCtxLoad 函数
; */
BL API_InterExit
;/*
; * 来到这里, 说明发生了中断嵌套
; */
MOV X18 , SP
RESTORE_BIG_REG_CTX ;/* 恢复所有寄存器 */
1:
;/*
; * 不是第一次进入中断
; */
LDR X0 , [SP, #XSP_OFFSET] ;/* 获取异常前 SP */
SUB X0 , X0 , ARCH_REG_CTX_SIZE ;/* 在异常堆栈开辟上下文保存区 */
;/*
; * 拷贝 volatile 寄存器到异常堆栈里的上下文保存区
; */
EXC_COPY_VOLATILE
;/*
; * 保存 non volatile 寄存器到异常堆栈里
; */
EXC_SAVE_NON_VOLATILE
MOV SP , X0 ;/* 使用异常堆栈 */
B 2b
FUNC_END()
看同步异常入口,同步异常主要是指令执行产生的异常。首先也是保存当前上下文寄存器到临时栈,然后判断是第几次进入中断,如果是第一次进入中断获取当前cpu 的tcp将寄存器拷贝到tcp中保存。然后通过API_InterStackBaseGet函数获得当前 CPU 中断堆栈栈顶, 并设置 SP。在SylixOS每个核都有一个中断堆栈,在这里获取当前CPU中断堆栈是和SylixOS在中断中检测是否需要调度有关以及中断服务程序有关,中断服务程序执行使用CPU对应的中断堆栈。 然后调用archSyncExcHandle 函数,此时archSyncExcHandle函数参数是x0,x0保存的是当前tcp的指针。所以archSyncExcHandle函数第一个参数是是tcp指针,tcp结构题第一个就是上下文结构体,所以把上下文传入进来。第二个参数是x1 寄存器。寄存器中是ESR寄存器的值。根据ESR寄存器,在archSyncExcHandle函数中进行判断并做了相应的处理。
然后调用API_InterExit 函数,在API_InterExit函数中会将中断嵌套计数减1.同时在此函数中调用一个对系统调度很重要的函数__KERNEL_SCHED_INT 。此函数会查找当前系统是否有需要调用的任务。如果有会在此进行调用,最后在API_InterExit调用了archIntCtxLoad函数,进行任务加载。
;/*********************************************************************************************************
; 中断返回时, 线程装载
; 参数为当前 CPU 控制块, 即 X0 为当前 CPU 控制块指针
;*********************************************************************************************************/
FUNC_DEF(archIntCtxLoad)
LDR X18 , [X0] ;/* 获取当前 TCB 的 REG_CTX 地址*/
LDR X9 , [X18, #CTX_TYPE_OFFSET] ;/* 获得上下文类型 */
CMP X9 , #0
B.NE _RestoreSmallCtx
RESTORE_BIG_REG_CTX ;/* 恢复大寄存器上下文 */
FUNC_END()
通过x0 获取当前的tcp。CTX_TYPE_OFFSET 当时在保存上下文寄存器的值是赋值为0,所以这里会跳转到RESTORE_BIG_REG_CTX这执行。不执行BNE 那条指令。
MACRO_DEF(RESTORE_BIG_REG_CTX)
LDR X1 , [X18 , #XSP_OFFSET] ;/* 恢复 SP 指针 */
MOV SP , X1
RESTORE_PSTATE ;/* 恢复 PSTATE */
LDR X1 , [X18 , #XPC_OFFSET] ;/* 恢复 PC 到 ELR_EL1 */
MSR ELR_EL1 , X1
LDP X0 , X1 , [X18 , #XGREG_OFFSET(0)]
LDP X2 , X3 , [X18 , #XGREG_OFFSET(2)]
LDP X4 , X5 , [X18 , #XGREG_OFFSET(4)]
LDP X6 , X7 , [X18 , #XGREG_OFFSET(6)]
LDP X8 , X9 , [X18 , #XGREG_OFFSET(8)]
LDP X10 , X11 , [X18 , #XGREG_OFFSET(10)]
LDP X12 , X13 , [X18 , #XGREG_OFFSET(12)]
LDP X14 , X15 , [X18 , #XGREG_OFFSET(14)]
LDP X16 , X17 , [X18 , #XGREG_OFFSET(16)]
LDP X19 , X20 , [X18 , #XGREG_OFFSET(19)]
LDP X21 , X22 , [X18 , #XGREG_OFFSET(21)]
LDP X23 , X24 , [X18 , #XGREG_OFFSET(23)]
LDP X25 , X26 , [X18 , #XGREG_OFFSET(25)]
LDP X27 , X28 , [X18 , #XGREG_OFFSET(27)]
LDP X29 , LR , [X18 , #XGREG_OFFSET(29)]
LDR X18 , [X18 , #XGREG_OFFSET(18)]
ERET
MACRO_END()
根据函数内容是将tcp中的上下文回复到寄存器中。如果有调度的进程,这里是把该任务tcp中的上下文寄存器恢复到arm 寄存器中。如果没有还是原来的tcp的上下文寄存器恢复回去。
不讨论中断嵌套情况,下面看进入到archEL1IrqEntry 函数,这也是普通外部中断进入的函数。
;/*********************************************************************************************************
; 中断入口
;*********************************************************************************************************/
FUNC_DEF(archEL1IrqEntry)
;/*
; * 使用异常临时栈, 并在异常临时栈开辟临时上下文保存区, 将 volatile 寄存器保存到临时上下文保存区
; * SP 指向异常临时栈
; */
EXC_SAVE_VOLATILE
BL API_InterEnter
;/*
; * 如果不是第一次进入中断, 跳转
; */
CMP X0 , #1
BNE 1f
;/*
; * 获得当前任务 TCB 的 ARCH_REG_CTX 地址
; */
BL API_ThreadTcbInter ;/* get current tcb */
;/*
; * 拷贝 volatile 寄存器到当前任务 TCB 的 ARCH_REG_CTX 里
; */
EXC_COPY_VOLATILE
;/*
; * 保存 non volatile 寄存器到当前任务 TCB 的 ARCH_REG_CTX 里
; */
EXC_SAVE_NON_VOLATILE
;/*
; * 第一次进入中断: 获得当前 CPU 中断堆栈栈顶, 并设置 SP
; */
BL API_InterStackBaseGet
MOV SP , X0
2:
;/*
; * bspIntHandle()
; */
BL bspIntHandle
;/*
; * API_InterExit()
; * 如果没有发生中断嵌套, 则 API_InterExit 会调用 archIntCtxLoad 函数
; */
BL API_InterExit
;/*
; * 来到这里, 说明发生了中断嵌套
; */
MOV X18 , SP
RESTORE_BIG_REG_CTX ;/* 恢复所有寄存器 */
1:
;/*
; * 不是第一次进入中断
; */
LDR X0 , [SP, #XSP_OFFSET] ;/* 获取异常前 SP */
SUB X0 , X0 , ARCH_REG_CTX_SIZE ;/* 在异常堆栈开辟上下文保存区 */
;/*
; * 拷贝 volatile 寄存器到异常堆栈里的上下文保存区
; */
EXC_COPY_VOLATILE
;/*
; * 保存 non volatile 寄存器到异常堆栈里
; */
EXC_SAVE_NON_VOLATILE
MOV SP , X0 ;/* 使用异常堆栈 */
B 2b
FUNC_END()
和archEL1SyncExcEntry 有区别的是调用了bspIntHandle函数。 这个函数是bsp函数,需要在板级支持包中实现。
VOID bspIntHandle (VOID)
{
REGISTER UINT32 uiAck = armGicIrqReadAck();
REGISTER UINT32 uiSourceCpu = (uiAck >> 10) & 0x7;
REGISTER UINT32 uiVector = uiAck & 0x1FF;
(VOID)uiSourceCpu;
archIntHandle((ULONG)uiVector, LW_FALSE);
armGicIrqWriteDone(uiAck);
}
上面是H6的 bspIntHandle函数。这里主要是调用archIntHandle函数,不同的芯片需要在这里获取中断向量号uiVectror传递给archIntHandle函数。
irqret = API_InterVectorIsr(ulVector); /* 调用中断服务程序 */
KN_INT_DISABLE(); /* 禁能中断 */
在archIntHandle主要是上面两个函数,一个调用中断服务程序,一个是禁能中断。API_InterVectorIsr开始查找中断号绑定的函数,然后执行。
最后
以上就是怕孤独樱桃为你收集整理的SylixOS arm64 异常向量表的全部内容,希望文章能够帮你解决SylixOS arm64 异常向量表所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复