做人呢,最紧要就系开心啦

ARM64基础12:ARM64的异常模式及异常向量表

1,155次阅读
一条评论

1.ARM64 的异常等级

ARM64 包含 4 个异常等级:
EL0:非特权模式,常用来跑应用程序;
EL1:特权模式,常用来跑内核;
EL2:虚拟化监控程序,例如 hypervisor;
EL3:安全模式,例如 secure monitor;

2. 同步异常和异步异常

同步异常是由正在运行的指令,或指令运行的结果,出错造成的异常;而异步异常则不必由运行的指令造成,可以在程序运行中的任意时刻 (异步) 发生。

同步异常 包括:
1. 系统调用,svc, hvc, SMC 等;
2.MMU 引发的异常;
3.SP 和 PC 对齐检查;
4. 未分配的指令;

异步异常
IRQ 中断;
FIQ 中断;
SError

注:在官方手册 D1.12 有详细列出那些是同步异常;

3. 异常入口

当异常发生时刻:
a.CPU 硬件做了哪些事情
(1) PSTATE 保存到 SPSR_ELx;
(2) 返回地址保存到 ELR_ELx;
(3) PSTATE 寄存器里的 DAIF 域都设置为 1,相当于把调试异常、系统错误关闭;
(4)更新 ESR_ELx 寄存器,该寄存器包含同步异常发生的原因;
(5)切换到对应的 EL,然后跳转到异常向量表里执行;

b. 操作系统做哪些事情
(1)根据异常发生的类型,跳转到合适的异常向量表
(2)异常向量表的每个表项,保存有一个异常处理的跳转函数,跳转到对应的异常处理函数处理异常

4 异常的返回

操作系统执行一条 eret 语句;
从 ELR_ELx 寄存器恢复 PC 指针;
从 SPSR_ELx 寄存器恢复处理器的状态;

5. 异常返回地址

返回地址的两个寄存器:
x30: 子函数的返回地址,使用 ret 指令来返回;
ELR_Elx: 异常返回地址,使用 eret 返回;

ELR_ELx 保存了异常返回地址
(1)对于异步异常,它是中断发生时的下一条指令,或没有执行的第一条指令;
(2)对于不是 system call 的同步异常,它是触发同步异常的那一条指令;
(3)对于 system call, 它是 svc 指令的下一条指令;

6. 异常处理的路由

(1)异常发生时,异常处理可以在当前或更高 EL,EL0 不能处理异常;
(2)同步异常是可以在当前 EL 处理的,比如在 EL1 里发生的同步异常;
(3)对于异步异常,可以路由到 EL1/EL2/EL3 处理,需要配置 HCR 以及 SCR 相关寄存器;

路由方法:

1. 选择异常级别:

当异常发生时,PC 可以有三个基地址 VBAR_EL1、VBAR_EL3、VBAR_EL1(secure)供选择;路由规则如下:
ARM64 基础 12:ARM64 的异常模式及异常向量表

举两例来说明下:
第一个红色框的内容表示:在此种配置 (安全模式) 下,EL0 和 EL1 状态下产生的异步异常,会导致 CPU 进入 EL1。
第一个红色框的内容表示:在此种配置 (非安全模式) 下,EL0 和 EL1 状态下产生的异步异常,会导致 CPU 进入 EL1,而 EL2 状态下产生的异常,不会导致 exception level 切换。

Linux 内核 只支持 EL0 和 EL1,EL0 对应用户态,EL1 对应内核态,当 CPU 运行在用户态时,产生的异步异常会导致 CPU 切换到 EL1,当 CPU 运行在内核态时,产生的异步异常不会导致 exception level 的切换。
这里配置为非安全模式 vbar_el1:

//set vector table addr to vbar
    ldr x10, =vectors
    msr vbar_el1, x10
    isb

2. 选择异常级别相关的偏移:

配置好基地址后,再根据下表配置异常向量表的偏移地址
ARM64 基础 12:ARM64 的异常模式及异常向量表
说明:
(1)实际上有四张表,每张表有四个异常入口,分别对应同步异常,IRQ,FIQ 和出错异常
(2)每一个异常入口占用 0x80 bytes(不同于 ARMv7 之前的 4bytes)空间,也就是说,每一个异常入口可以放置多条指令,而不仅仅是一条跳转指令。
四张表类型
(1)如果发生异常并不会导致 exception level 切换,并且使用的栈指针是 SP_EL0,那么使用第一张异常向量表。
(2)如果发生异常并不会导致 exception level 切换,并且使用的栈指针是 SP_EL1/2/3,那么使用第二张异常向量表。
(3)如果发生异常会导致 exception level 切换,并且比目的 exception level 低一级的 exception level 运行在 AARCH64 模式,那么使用第三张异常向量表。
(4)如果发生异常会导致 exception level 切换,并且比目的 exception level 低一级的 exception level 运行在 AARCH32 模式,那么使用第四张异常向量表。

在 Linux 中,用户态 EL0, 内核态 EL1, 结合上面路由规则,可得到如下结论:
(1)第一章异常向量表,用不到;因为 EL0 使用 SP_EL0,但发生 EL0 异常会 routing 到 EL1;
(2)第二张表,用于 CPU 运行在 EL1 即内核态,发生异常时,exception level 不发生切换;
(3)第三张表用于 CPU 运行在 EL0 即用户态的 AARCH32 模式时,发生异常;
(4)第四张表,用于 CPU 运行在 EL0 即用户态的 AARCH64 时,发生异常;
ARM64 基础 12:ARM64 的异常模式及异常向量表

3. 填充异常向量表

根据以上规则,填充异常向量表如下,根据实际发生的不同异常类型,跳转到对应的处理函数;

/*
 * Vector Table
 *
 * ARM64 的异常向量表一共占用 2048 个字节
 * 分成 4 组,每组 4 个表项,每个表项占 128 字节
 * 参见 ARMv8 spec v8.6 第 D1.10 节
 * align 11 表示 2048 字节对齐
 */
.align 11
.global vectors
vectors:
    /* Current EL with SP0
       当前系统运行在 EL1 时使用 EL0 的栈指针 SP
       这是一种异常错误的类型
     */
    vtentry el1_sync_invalid
    vtentry el1_irq_invalid
    vtentry el1_fiq_invalid
    vtentry el1_error_invalid

    /* Current EL with SPx
       当前系统运行在 EL1 时使用 EL1 的栈指针 SP
       这说明系统在内核态发生了异常

       Note: 我们暂时只实现 IRQ 中断
     */
    vtentry el1_sync_invalid
    vtentry el1_irq
    vtentry el1_fiq_invalid
    vtentry el1_error_invalid

    /* Lower EL using AArch64
       在用户态的 aarch64 的程序发生了异常
     */
    vtentry el0_sync_invalid
    vtentry el0_irq_invalid
    vtentry el0_fiq_invalid
    vtentry el0_error_invalid

    /* Lower EL using AArch32
       在用户态的 aarch32 的程序发生了异常
     */
    vtentry el0_sync_invalid
    vtentry el0_irq_invalid
    vtentry el0_fiq_invalid
    vtentry el0_error_invalid

el1_sync_invalid:
    //inv_entry 1, BAD_SYNC
    kernel_entry
    mov x0, sp
    mov x1, 0
    mrs x2, esr_el1
    bl bad_mode
    kernel_exit

el1_irq_invalid:
    inv_entry 1, BAD_IRQ
el1_fiq_invalid:
    inv_entry 1, BAD_FIQ
el1_error_invalid:
    inv_entry 1, BAD_ERROR
el0_sync_invalid:
    inv_entry 0, BAD_SYNC
el0_irq_invalid:
    inv_entry 0, BAD_IRQ
el0_fiq_invalid:
    inv_entry 0, BAD_FIQ
el0_error_invalid:
    inv_entry 0, BAD_ERROR

7. 栈的选择

(1)每个异常登记 EL 都有对应的栈指针寄存器 SP:SP_EL0, SP_EL1, SP_EL2, SP_EL3;
(2)栈必须 16 字节对齐,硬件可以检测栈指针对齐;
(3)当异常发生时,跳转到目标异常等级时,硬件会自动选择 SP_ELx;
(3)操作系统负责分配和保证,每个异常等级 EL 对应的栈,是可用的

8. 异常处理的执行模式

(1)异常发生时,切换到更高级别 EL,这个 EL 运行在哪个模式?
HCR_EL2.RW 记录 EL1 要运行的哪个模式,1:aarch64, 0:aarch32;
(2)异常发生后,执行模式可以改变;
一个 aarch32 模式运行的应用程序,异常出现后,可以在 aarch64 模式处理异常;

9. 异常返回的执行模式

从一个异常返回时,SPSR 寄存器记录了:
(1)返回到哪个 EL?SPSR.M[3:0]
(2)返回目标 EL 的执行模式?SPSR.M[4],1 表示 aarch32,0 表示 aarch64;

案例 1: 树莓派开机默认运行在 EL2 模式, 请切换到 EL1 模式, 切换后, 打印出 EL 的值为 1;

提示: 从 EL2 切换到 EL1,需要做一下事情:
(1)设置 HCR_EL2 寄存器,Bit31 位设置为 1,表示 EL1 要运行在 aarch64 模式;
(2)设置 SCTRL_EL1 寄存器,设置小端,关闭 MMU;
(3)设置 SPSR_EL2 寄存器,设置切换模式 M 域为 EL1h, 另外关闭所有 DAIF;
(4)设置异常返回寄存器 elr_el2, 将返回到 EL1 时执行的函数;
(5) 调用 eret;

其中 (3)(4) 是一个典型的一场返回操作
主要实现代码如下:

master:
    /* init uart and print string */
    bl __init_uart

    mrs x5, CurrentEL
    cmp x5, #CurrentEL_EL3
    b.eq el3_entry
    b el2_entry

el2_entry:
    bl print_el
    /*
     * set arm64 from EL2 to EL1, need to do list
     * 1.set HCR_EL2, bit31=1?aarch64:aarch32;
     * 2.set SCTLR_EL1, big/little endian and disable MMU;
     * 3.set SPSR_EL2, set M filed to EL1h, and disable all DAIF;
     * 4.set ELR_EL2, return to el1_entry;
     */

    /* The execution state for EL1 is AArch64 */
    ldr x0, =HCR_HOST_NVHE_FLAGS
    msr hcr_el2, x0

    /* set big/little endian, disable MMU*/
    ldr x0, = SCTLR_VALUE_MMU_DISABLED
    msr sctlr_el1, x0

    /*
     * 1.set M_field to EL1h;
     * 2.disable all DAIF;
     */
    ldr x0, = SPSR_EL1
    msr spsr_el2, x0

    /*
     * set return function
     */
    adr x0, _el1_entry
    msr elr_el2, x0
    eret

el3_entry:
    eret

.globl _el1_entry
_el1_entry:
    bl print_el

    adr x0, _bss
    adr x1, _ebss
    sub x1, x1, x0
    bl memzero

    mov sp, #LOW_MEMORY 
    bl  kernel_main
    b   proc_hang       // should never come here

单步调式,观察 PC 寄存器值变化如下:
ARM64 基础 12:ARM64 的异常模式及异常向量表
执行 eret 后,CPU 自动从 ELR_el2 获取返回地址,继续执行
ARM64 基础 12:ARM64 的异常模式及异常向量表

10 异常向量表

(1)每个异常等级 EL 都有自己的异常向量表,EL0 除外;
(2)异常向量表的基地址需要设置到 VBAR_ELx 寄存器中;
(3)VBAR_EL1 寄存器必须以 2KB 对齐;
(4)每个表项可存放 32 条指令,一共 128 字节
(5)大的异常类型分类有 4 类,分别是同步 /IRQ/FIQ/Serror;

ARM64 的异常向量表格式如下

地址偏移(基地址为 VBAR_ELn) 异常类型 描述
+0x000 同步 Current EL with SP0
+0x080 IRQ/vIRQ Current EL with SP0
+0x100 FIQ/vFIQ Current EL with SP0
+0x180 SError/vSError Current EL with SP0
+0x200 同步 Current EL with SP1
+0x280 IRQ/vIRQ Current EL with SP1
+0x300 FIQ/vFIQ Current EL with SP1
+0x380 SError/vSError Current EL with SP1
+0x400 同步 Current EL with SP2
+0x480 IRQ/vIRQ Current EL with SP2
+0x500 FIQ/vFIQ Current EL with SP2
+0x580 SError/vSError Current EL with SP2
+0x600 同步 Current EL with SP3
+0x680 IRQ/vIRQ Current EL with SP3
+0x700 FIQ/vFIQ Current EL with SP3
+0x780 SError/vSError Current EL with SP3

内核里的异常向量表实现arch/arm64/kernel/entry.S

/*
 * Exception vectors.
 */
    .pushsection ".entry.text", "ax"

    .align  11
SYM_CODE_START(vectors)
    kernel_ventry   1, sync_invalid         // Synchronous EL1t
    kernel_ventry   1, irq_invalid          // IRQ EL1t
    kernel_ventry   1, fiq_invalid          // FIQ EL1t
    kernel_ventry   1, error_invalid        // Error EL1t

    kernel_ventry   1, sync             // Synchronous EL1h
    kernel_ventry   1, irq              // IRQ EL1h
    kernel_ventry   1, fiq              // FIQ EL1h
    kernel_ventry   1, error            // Error EL1h

    kernel_ventry   0, sync             // Synchronous 64-bit EL0
    kernel_ventry   0, irq              // IRQ 64-bit EL0
    kernel_ventry   0, fiq              // FIQ 64-bit EL0
    kernel_ventry   0, error            // Error 64-bit EL0

#ifdef CONFIG_COMPAT
    kernel_ventry   0, sync_compat, 32      // Synchronous 32-bit EL0
    kernel_ventry   0, irq_compat, 32       // IRQ 32-bit EL0
    kernel_ventry   0, fiq_compat, 32       // FIQ 32-bit EL0
    kernel_ventry   0, error_compat, 32     // Error 32-bit EL0
#else
    kernel_ventry   0, sync_invalid, 32     // Synchronous 32-bit EL0
    kernel_ventry   0, irq_invalid, 32      // IRQ 32-bit EL0
    kernel_ventry   0, fiq_invalid, 32      // FIQ 32-bit EL0
    kernel_ventry   0, error_invalid, 32        // Error 32-bit EL0
#endif
SYM_CODE_END(vectors)

kernel_ventry 宏定义实现:

/*
 * Bad Abort numbers
 *-----------------
 */
#define BAD_SYNC    0
#define BAD_IRQ     1
#define BAD_FIQ     2
#define BAD_ERROR   3

    .macro kernel_ventry, el, label, regsize = 64
    .align 7
    #...
    sub sp, sp, #PT_REGS_SIZE
    #...
    b   el\()\el\()_\label
    .endm

案例,实现一个简单的异常向量表,用一个数据对齐异常,PC 指针的跳转;


#define BAD_SYNC        0
#define BAD_IRQ         1
#define BAD_FIQ         2
#define BAD_ERROR       3

/*
   处理无效的异常向量
 */
    .macro inv_entry el, reason
    //kernel_entry el
    mov x0, sp
    mov x1, #\reason
    mrs x2, esr_el1
    b bad_mode
    .endm

/*
   vector table entry
   每个表项是 128 字节,align 7 表示 128 字节对齐
 */
    .macro vtentry label
    .align 7
    b \label
    .endm

/*
 * Vector Table
 *
 * ARM64 的异常向量表一共占用 2048 个字节
 * 分成 4 组,每组 4 个表项,每个表项占 128 字节
 * 参见 ARMv8 spec v8.6 第 D1.10 节
 * align 11 表示 2048 字节对齐
 */
.align 11
.global vectors
vectors:
    /* Current EL with SP0
       当前系统运行在 EL1 时使用 EL0 的栈指针 SP
       这是一种异常错误的类型
     */
    vtentry el1_sync_invalid
    vtentry el1_irq_invalid
    vtentry el1_fiq_invalid
    vtentry el1_error_invalid

    /* Current EL with SPx
       当前系统运行在 EL1 时使用 EL1 的栈指针 SP
       这说明系统在内核态发生了异常

       Note: 我们暂时只实现 IRQ 中断
     */
    vtentry el1_sync_invalid
    vtentry el1_irq_invalid
    vtentry el1_fiq_invalid
    vtentry el1_error_invalid

    /* Lower EL using AArch64
       在用户态的 aarch64 的程序发生了异常
     */
    vtentry el0_sync_invalid
    vtentry el0_irq_invalid
    vtentry el0_fiq_invalid
    vtentry el0_error_invalid

    /* Lower EL using AArch32
       在用户态的 aarch32 的程序发生了异常
     */
    vtentry el0_sync_invalid
    vtentry el0_irq_invalid
    vtentry el0_fiq_invalid
    vtentry el0_error_invalid

el1_sync_invalid:
    inv_entry 1, BAD_SYNC
el1_irq_invalid:
    inv_entry 1, BAD_IRQ
el1_fiq_invalid:
    inv_entry 1, BAD_FIQ
el1_error_invalid:
    inv_entry 1, BAD_ERROR
el0_sync_invalid:
    inv_entry 0, BAD_SYNC
el0_irq_invalid:
    inv_entry 0, BAD_IRQ
el0_fiq_invalid:
    inv_entry 0, BAD_FIQ
el0_error_invalid:
    inv_entry 0, BAD_ERROR

string_test:
    .string "t"

.global trigger_alignment
trigger_alignment:
    ldr x0, =0x80002
    ldr x1, [x0]
    ret

单步调试如下
ARM64 基础 12:ARM64 的异常模式及异常向量表
异常执行完后,返回
ARM64 基础 12:ARM64 的异常模式及异常向量表
串口打印:

qemu-system-aarch64 -machine raspi4 -nographic -kernel benos.bin
booting at EL2
booting at EL1
printk init done
<0x800880> func_c
image layout:
  .text.boot: 0x00080000 - 0x000800d8 (216 B)
  .text: 0x000800d8 - 0x0008329c (12740 B)
  .rodata: 0x0008329c - 0x000834ee (594 B)
  .data: 0x000834ee - 0x00083828 (826 B)
  .bss: 0x000838d8 - 0x000a3ce8 (132112 B)
Bad mode for Sync Abort handler detected, far:0x0 esr:0x2000000
test and:p=0x2
test or:p=0x3
test andnot:p=0x1
el=1

11 同步异常的解析

查看异常综合信息寄存器:ESR_ELx

ARM64 基础 12:ARM64 的异常模式及异常向量表
当异常发生时,在异常向量表处,对应处理函数里,读取 ESR 的 EC 域,根据 EC 域分类,进一步解析 ISS 域具体异常原因:
ARM64 基础 12:ARM64 的异常模式及异常向量表

失效地址寄存器 FAR

FAR 寄存器保存发生异常时刻的虚拟地址;
异常解析部分代码:

static const char * const bad_mode_handler[] = {
      "Sync Abort",
      "IRQ",
      "FIQ",
      "SError"
};

static const char *data_fault_code[] = {[0] = "Address size fault, level0",
    [1] = "Address size fault, level1",
    [2] = "Address size fault, level2",
    [3] = "Address size fault, level3",
    [4] = "Translation fault, level0",
    [5] = "Translation fault, level1",
    [6] = "Translation fault, level2",
    [7] = "Translation fault, level3",
    [9] = "Access flag fault, level1",
    [10] = "Access flag fault, level2",
    [11] = "Access flag fault, level3",
    [13] = "Permission fault, level1",
    [14] = "Permission fault, level2",
    [15] = "Permission fault, level3",
    [0x21] = "Alignment fault",
    [0x35] = "Unsupported Exclusive or Atomic access",
};

static const char *esr_get_dfsc_string(unsigned int esr)
{return data_fault_code[esr & 0x3f];
}

static const char *esr_class_str[] = {[0 ... ESR_ELx_EC_MAX]      = "UNRECOGNIZED EC",
    [ESR_ELx_EC_UNKNOWN]        = "Unknown/Uncategorized",
    [ESR_ELx_EC_WFx]        = "WFI/WFE",
    [ESR_ELx_EC_CP15_32]        = "CP15 MCR/MRC",
    [ESR_ELx_EC_CP15_64]        = "CP15 MCRR/MRRC",
    [ESR_ELx_EC_CP14_MR]        = "CP14 MCR/MRC",
    [ESR_ELx_EC_CP14_LS]        = "CP14 LDC/STC",
    [ESR_ELx_EC_FP_ASIMD]       = "ASIMD",
    [ESR_ELx_EC_CP10_ID]        = "CP10 MRC/VMRS",
    [ESR_ELx_EC_CP14_64]        = "CP14 MCRR/MRRC",
    [ESR_ELx_EC_ILL]        = "PSTATE.IL",
    [ESR_ELx_EC_SVC32]      = "SVC (AArch32)",
    [ESR_ELx_EC_HVC32]      = "HVC (AArch32)",
    [ESR_ELx_EC_SMC32]      = "SMC (AArch32)",
    [ESR_ELx_EC_SVC64]      = "SVC (AArch64)",
    [ESR_ELx_EC_HVC64]      = "HVC (AArch64)",
    [ESR_ELx_EC_SMC64]      = "SMC (AArch64)",
    [ESR_ELx_EC_SYS64]      = "MSR/MRS (AArch64)",
    [ESR_ELx_EC_IMP_DEF]        = "EL3 IMP DEF",
    [ESR_ELx_EC_IABT_LOW]       = "IABT (lower EL)",
    [ESR_ELx_EC_IABT_CUR]       = "IABT (current EL)",
    [ESR_ELx_EC_PC_ALIGN]       = "PC Alignment",
    [ESR_ELx_EC_DABT_LOW]       = "DABT (lower EL)",
    [ESR_ELx_EC_DABT_CUR]       = "DABT (current EL)",
    [ESR_ELx_EC_SP_ALIGN]       = "SP Alignment",
    [ESR_ELx_EC_FP_EXC32]       = "FP (AArch32)",
    [ESR_ELx_EC_FP_EXC64]       = "FP (AArch64)",
    [ESR_ELx_EC_SERROR]     = "SError",
    [ESR_ELx_EC_BREAKPT_LOW]    = "Breakpoint (lower EL)",
    [ESR_ELx_EC_BREAKPT_CUR]    = "Breakpoint (current EL)",
    [ESR_ELx_EC_SOFTSTP_LOW]    = "Software Step (lower EL)",
    [ESR_ELx_EC_SOFTSTP_CUR]    = "Software Step (current EL)",
    [ESR_ELx_EC_WATCHPT_LOW]    = "Watchpoint (lower EL)",
    [ESR_ELx_EC_WATCHPT_CUR]    = "Watchpoint (current EL)",
    [ESR_ELx_EC_BKPT32]     = "BKPT (AArch32)",
    [ESR_ELx_EC_VECTOR32]       = "Vector catch (AArch32)",
    [ESR_ELx_EC_BRK64]      = "BRK (AArch64)",
};

static const char *esr_get_class_string(unsigned int esr)
{return esr_class_str[esr >> ESR_ELx_EC_SHIFT];
}

void parse_esr(unsigned int esr)
{unsigned int ec = ESR_ELx_EC(esr);

    printk("ESR info:\n");
    printk("  ESR = 0x%08x\n", esr);
    printk("  Exception class = %s, IL = %u bits\n", esr_get_class_string(esr), (esr & ESR_ELx_IL) ? 32 : 16);

    if (ec == ESR_ELx_EC_DABT_LOW || ec == ESR_ELx_EC_DABT_CUR) {printk("  Data abort:\n");
        if ((esr & ESR_ELx_ISV)) {printk("  Access size = %u byte(s)\n", 1U << ((esr & ESR_ELx_SAS) >> ESR_ELx_SAS_SHIFT));
            printk("  SSE = %lu, SRT = %lu\n", (esr & ESR_ELx_SSE) >> ESR_ELx_SSE_SHIFT, (esr & ESR_ELx_SRT_MASK) >> ESR_ELx_SRT_SHIFT);
            printk("  SF = %lu, AR = %lu\n", (esr & ESR_ELx_SF) >> ESR_ELx_SF_SHIFT, (esr & ESR_ELx_AR) >> ESR_ELx_AR_SHIFT);
        }

        printk("  SET = %lu, FnV = %lu\n", (esr >> ESR_ELx_SET_SHIFT) & 3, (esr >> ESR_ELx_FnV_SHIFT) & 1);
        printk("  EA = %lu, S1PTW = %lu\n", (esr >> ESR_ELx_EA_SHIFT) & 1, (esr >> ESR_ELx_S1PTW_SHIFT) & 1);
        printk("  CM = %lu, WnR = %lu\n", (esr & ESR_ELx_CM) >> ESR_ELx_CM_SHIFT, (esr & ESR_ELx_WNR) >> ESR_ELx_WNR_SHIFT);
        printk("  DFSC = %s\n", esr_get_dfsc_string(esr));
    }
}

void bad_mode(struct pt_regs *regs, int reason, unsigned int esr)
{
    printk("Bad mode for %s handler detected, far:0x%x esr:0x%x- %s\n",
                      bad_mode_handler[reason], read_sysreg(far_el1),
                      esr, esr_get_class_string(esr));
    parse_esr(esr);  

}

正文完
 
admin
版权声明:本站原创文章,由 admin 2021-11-02发表,共计11652字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(一条评论)