Exception Vector Table

當中斷發生時,CPU 會根據目前的 exception level 查找對應的中斷向量表 (VBAR_ELx)。知道向量表在哪後,就可以根據中斷發生的類型執行對應的操作。

中斷發生的時機,可以是在 EL0 主動執行 svc trap 到 EL1 (Synchronous),或是被 timer interrupt (IRQ) 打到,也會從 EL0 轉換到 EL1。

Exception Mechanism

當中斷發生時,會有以下流程:

  1. 將目前的 PSTAT 儲存在 SPSR_ELx
  2. 將 returned address 儲存在 ELR_ELx
  3. 關閉 interrupt (PSTATE.{D,A,I,F} are set to 1)
  4. 若 exception 是 Synchronous exception 或是 SError interrupt,將 exception syndrome 保存在 ESR_ELx
  5. 轉換 EL 到目標的 EL,pc 指到對應的中斷向量表

當中斷處理結束,執行 eret 回到中斷發生前的狀態:

  1. pc 變回先前在 ELR_ELx 儲存的值
  2. PSTATE 變回先前在 SPSR_ELx 儲存的值

More details: Stack Pointer

當處在不同的 exception level 時,sp 這個暫存器會被當作是該 level sp 的別稱 (SP_ELx 的別稱)。可以使用 SPSel 在不同的 exception level 共享 SP_EL0,但是通常不建議這樣做。

  • 對於 EL0,僅能使用 SP_EL0 (簡稱為 EL0t)
  • 對於 EL1,可以選擇使用 SP_EL0 (簡稱為 EL1t),或者使用 SP_EL1 (簡稱為 EL1h)
  • 對於 EL2,可以選擇使用 SP_EL0 (簡稱為 EL2t),或者使用 SP_EL2 (簡稱為 EL2h)
  • 對於 EL3,可以選擇使用 SP_EL0 (簡稱為 EL3t),或者使用 SP_EL3 (簡稱為 EL3h)

t: thread, h: handler

ELxt 與 ELxh 的區別在於是不是使用該 exception level 的 sp,t 代表使用 SP_EL0,h 代表使用該 exception level 自己的 sp

Exception Level Switch 設定 SPSR_EL1SPSR_EL2 時,也會設定該 exception level 的 stack pointer (M[3:0] 欄位):

  1. 開機時處於 EL2,將 SPSR_EL2.M[3:0] 設定為 EL1h,執行 eret 時回到 EL1 後,sp 即為 SP_EL1
  2. 在處於 EL1 設定 EL0 時,將 SPSR_EL1.M[3:0] 設定為 EL0t,執行 eret 時回到 EL0 後,sp 即為 SP_EL0

Interrupts vs Exceptions

In ARM.v8 architecture, interrupts are part of a more general term: exceptions. There are 4 types of exceptions:

  • Synchronous exception
    • caused by the currently executed instruction
  • IRQ (Interrupt Request)
    • always asynchronous, which means that they have nothing to do with the currently executed instruction
    • they are always not generated by the processor itself, but by external hardware
  • FIQ (Fast Interrupt Request)
    • for the purpose of prioritizing exceptions
  • SError (System Error)
    • asynchronous and are generated by external hardware
    • always indicates some error condition

中斷向量表

定義 Vector Table,總共會有 16 個 Exception Handler 需要實作:4 種類型,每種類型都有 4 種不同的 exception handler (Sync, IRQ, FIQ, SError),以 EL1 的 Vector Table 為例:

  • EL0_32
    • VBAR_EL1 + 0x600
  • EL0_64
    • VBAR_EL1 + 0x400
  • EL1h (SPSel = 1)
    • VBAR_EL1 + 0x200
  • EL1t (SPSel = 0)
    • VBAR_EL1 + 0x0

注意每個 exception handler 需要對齊 0x80

exception_table.h

先定義一下 invalid 的編號,當有還沒有實作的 exception handler 被呼叫到時,會將編號當作參數傳入某個 function 進行錯誤輸出

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#ifndef VECTOR_TABLE_H
#define VECTOR_TABLE_H

#define SYNC_INVALID_EL1t       0
#define IRQ_INVALID_EL1t        1
#define FIQ_INVALID_EL1t        2
#define ERROR_INVALID_EL1t      3

#define SYNC_INVALID_EL1h       4
#define IRQ_INVALID_EL1h        5
#define FIQ_INVALID_EL1h        6
#define ERROR_INVALID_EL1h      7

#define SYNC_INVALID_EL0_64     8
#define IRQ_INVALID_EL0_64      9
#define FIQ_INVALID_EL0_64      10
#define ERROR_INVALID_EL0_64    11

#define SYNC_INVALID_EL0_32     12
#define IRQ_INVALID_EL0_32      13
#define FIQ_INVALID_EL0_32      14
#define ERROR_INVALID_EL0_32    15

#endif

exception_table.S

在進到 Exception Handler 時需要進行 context saving (kernel_entrykernel_exit 這兩個 macro),避免 Exception Handler 覆寫先前的 Register 狀態。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include "exception_table.h"

    .macro handle_invalid_entry type
    kernel_entry
    mov x0, #\type
    mrs x1, esr_el1
    mrs x2, elr_el1
    bl  show_exception_status
    b   not_implemented
    .endm

    .macro ventry label
    .align 7  // entry size is 0x80, .align will pad 0
    b   \label
    .endm

    .macro kernel_entry
    sub sp, sp, #272
    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]
    str x30, [sp, #16 * 15]

    mrs x22, elr_el1
    mrs x23, spsr_el1
    stp x22, x23, [sp, #16 * 16]
    .endm

    .macro kernel_exit
    ldp x22, x23, [sp, #16 * 16]
    msr elr_el1, x22
    msr spsr_el1, x23

    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 x30, [sp, #16 * 15]
    add sp, sp, #272
    eret
    .endm

目前只實作了 EL1h 與 EL0_64 的 Synchronous Interrupt (for system call) 及 IRQ Interrupt (for timer interrupt) 的 exception handler,當觸發時會跳到用 C 的 code。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
.section ".text"

.align 11 // vector table should be aligned to 0x800
.global exception_table
exception_table:
    ventry sync_invalid_el1t       // Synchronous EL1t
    ventry irq_invalid_el1t        // IRQ EL1t
    ventry fiq_invalid_el1t        // FIQ EL1t
    ventry error_invalid_el1t      // Error EL1t

    ventry sync_exc_handler        // Synchronous EL1h
    ventry irq_exc_handler         // IRQ EL1h
    ventry fiq_invalid_el1h        // FIQ EL1h
    ventry error_invalid_el1h      // Error EL1h

    ventry sync_exc_handler        // Synchronous 64-bit EL0
    ventry irq_exc_handler         // IRQ 64-bit EL0
    ventry fiq_invalid_el0_64      // FIQ 64-bit EL0
    ventry error_invalid_el0_64    // Error 64-bit EL0

    ventry sync_invalid_el0_32     // Synchronous 32-bit EL0
    ventry irq_invalid_el0_32      // IRQ 32-bit EL0
    ventry fiq_invalid_el0_32      // FIQ 32-bit EL0
    ventry error_invalid_el0_32    // Error 32-bit EL0

sync_invalid_el1t:
    handle_invalid_entry  SYNC_INVALID_EL1t

irq_invalid_el1t:
    handle_invalid_entry  IRQ_INVALID_EL1t

fiq_invalid_el1t:
    handle_invalid_entry  FIQ_INVALID_EL1t

error_invalid_el1t:
    handle_invalid_entry  ERROR_INVALID_EL1t

sync_invalid_el1h:
    handle_invalid_entry  SYNC_INVALID_EL1h

fiq_invalid_el1h:
    handle_invalid_entry  FIQ_INVALID_EL1h

error_invalid_el1h:
    handle_invalid_entry  ERROR_INVALID_EL1h

sync_invalid_el0_64:
    handle_invalid_entry  SYNC_INVALID_EL0_64

irq_invalid_el0_64:
    handle_invalid_entry  IRQ_INVALID_EL0_64

fiq_invalid_el0_64:
    handle_invalid_entry  FIQ_INVALID_EL0_64

error_invalid_el0_64:
    handle_invalid_entry  ERROR_INVALID_EL0_64

sync_invalid_el0_32:
    handle_invalid_entry  SYNC_INVALID_EL0_32

irq_invalid_el0_32:
    handle_invalid_entry  IRQ_INVALID_EL0_32

fiq_invalid_el0_32:
    handle_invalid_entry  FIQ_INVALID_EL0_32

error_invalid_el0_32:
    handle_invalid_entry  ERROR_INVALID_EL0_32

sync_exc_handler:
    kernel_entry
    mrs x0, esr_el1
    mrs x1, elr_el1
    bl  sync_exc_router
    kernel_exit

irq_exc_handler:
    kernel_entry
    bl  irq_exc_router
    kernel_exit

start.S

在初始化到 EL1 時,將向量表的記憶體位址載入到 VBAR_EL1

1
2
adr     x1, exception_table
msr     vbar_el1, x1

panic.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include "uart0.h"

const char *entry_error_messages[] = {
    "SYNC_INVALID_EL1t",
    "IRQ_INVALID_EL1t",
    "FIQ_INVALID_EL1t",
    "ERROR_INVALID_EL1T",

    "SYNC_INVALID_EL1h",
    "IRQ_INVALID_EL1h",
    "FIQ_INVALID_EL1h",
    "ERROR_INVALID_EL1h",

    "SYNC_INVALID_EL0_64",
    "IRQ_INVALID_EL0_64",
    "FIQ_INVALID_EL0_64",
    "ERROR_INVALID_EL0_64",

    "SYNC_INVALID_EL0_32",
    "IRQ_INVALID_EL0_32",
    "FIQ_INVALID_EL0_32",
    "ERROR_INVALID_EL0_32",
};

void not_implemented() {
    uart_printf("kenel panic because of not implemented function...\n");
    while (1);
}

void show_exception_status(int type, unsigned long esr, unsigned long address) {
    uart_printf("%s, ESR: 0x%x, address: 0x%x\n", entry_error_messages[type], esr, address);
    uart_printf("Exception class (EC) 0x%x\n", (esr >> 26) & 0b111111);
    uart_printf("Instruction specific syndrome (ISS) 0x%x\n", esr & 0xFFFFFF);
}

當發生 Exception 時怎麼知道會跳到哪個 level?

The Exception level that execution changes to, or remains in, on taking an exception, is called the target Exception level of the exception and:

  • Every exception type has a target Exception level that is either:
    • Implicit in the nature of the exception.
    • Defined by configuration bits in the System registers.
  • An exception cannot target the EL0 Exception level.

https://developer.arm.com/documentation/ddi0488/d/programmers-model/armv8-architecture-concepts/exception-levels

Synchronous exception 通常都有訂好 target exception level (e.g. svc: EL0 -> EL1, hvc: EL1 -> EL2)。

IRQ 可以通過設置不同 Register 來進行調整 (e.g. 設定 HCR_EL2.TGE 可以將 EL1 發生的 exception route 到 EL2),在實驗中我們都假設是由 EL1 來進行處理。

Reference

https://s-matyukevich.github.io/raspberry-pi-os/docs/lesson03/linux/low_level-exception_handling.html

延伸閱讀

ARM64的启动过程之(六):异常向量表的设定

comments powered by Disqus