Lab3: Question and Elective
Question 1: Change svc instruction to brk (breakpoint) instruction. See the difference in ELR_EL2(return address). Explain why there is a difference.
asm volatile("svc #1");1
2
3
4Type: 0
Exception return address 0x81FDC
Exception class (EC) 0x15 (0b010101)
Instruction specific synfrome (ISS) 0x1asm volatile("brk #1");1
2
3
4Type: 0
Exception return address 0x81FD8
Exception class (EC) 0x3C (0b111100)
Instruction specific synfrome (ISS) 0x1
svc 是返回 caller 的下一行指令,brk 是停留在原地
Question 2: Do you need to save floating point SIMD registers in ISRs? Why or why not.
在 start.S 內我們操作 CPACR_EL1 允許在 EL0 及 EL1 進行操作 floating pointer 及 SIMD register 而不被 trap,在核心內為了效能不使用 floating point 及 SIMD。後續會實作 process 的 content switch,理論上進入 ISR 應該存下這些 register 防止其他 process 改動到。
1 | // Do not cause trap when using Advanced SIMD and floating point register on EL0 & EL1 |
Question 3: What will happen if you don’t clear peripherals’ interrupt signal?
中斷會一直觸發
Elective 1: Pick another timer and implement its handler.
BCM2837 為 4 顆 ARMv8-A 架構的 Cortex-A53 組成,每個 core 有 4 個 timer 可用
| 位置 | 名稱 | 理論上 qemu 可模擬 | 實際上 qemu 6.2.0可模擬 | raspibery pi3b+ | pending register |
|---|---|---|---|---|---|
| GPU peripheral | system timer | ❌ | ✅ | IRQ_PENDING_1 |
|
| GPU peripheral | arm timer | ❌ | ❌ | IRQ_BASIC_PENDING |
|
| local peripheral | local timer | ✅ | ✅ | COREn_IRQ_SRC |
|
| local peripheral | core timer | ✅ | ✅ | COREn_IRQ_SRC |
Interrupt handler
1 | void irq_handler(unsigned long esr, unsigned long elr) |
Core timer
Reference: CNTP_CTL_EL0, CNTP_TVAL_EL0, Counter-timer Physical Timer TimerValue register
On a write of this register, CNTP_CVAL_EL0 is set to (CNTPCT_EL0 + TimerValue), where TimerValue is treated as a signed 32-bit integer.
the timer condition is met when (CNTPCT_EL0 - CNTP_CVAL_EL0) is greater than or equal to zero.
If
CNTP_CTL_EL0.IMASKis 0, an interrupt is generated.
[
CNTP_CTL_EL0](https://developer.arm.com/documentation/ddi0595/2020-12/AArch64-Registers/CNTP-CTL-EL0--Counter-timer-Physical-Timer-Control-register?lang=en).ISTATUS****is set to 1.
1 |
|
Elective 2: Implement ISR for either mini UART or PL011 UART.
對照下方資料可得到完整 PL011 使用資訊
UARTRXINTR
receive interrupt 觸發的條件為任一下列事件
- FIFO 啟用,receive FIFO 達到 trigger level,直到清除中斷或是讀取資料到低於 trigger level。
- FIFO 未啟用,一接收到資料馬上觸發中斷,直到清除中斷或是讀取資料。
UARTTXINTR
transmit interrupt 觸發的條件為任一下列事件
- FIFO 啟用,transmit FIFO 等於或低於 trigger level,直到清除中斷或是寫入資料到高於 trigger level。
- FIFO 未啟用,在位置上沒有資料要寫入馬上觸發中斷,直到清除中斷或是寫入資料。
更新 transmit FIFO 的方式為在啟用 UART 和 interrupt 前或後,向 transmit FIFO 寫入資料。
transmit interrupt 發生在狀態改變時,也就是當啟用時 transmit FIFO 無資料中斷是不觸發的。只有當寫入後的資料離開 transmit FIFO 該處且 FIFO 為空時觸發。
新增 ring buffer 來處理 uart interrupt,同時改寫原有的 read, write,以下就各實作進行介紹。
Ring buffer
uart 中斷處理 :依據 status 決定資料要往 RX QUEUE 送或是從 TX QUEUE 拉出。
1 | void uart_handler() |
RX QUEUE 與 TX QUEUE 分別各以 ring buffer 進行實作,設計上 ring buffer 資料結構除了儲存資料的 buf[] 外,以 head 表示要讀取的資料位置,tail 表示要寫入檔案的位置。
1 | typedef struct ringbuf_t { |
限制 buf[] 大小為 2 的冪次方,這樣在將 head, tail 累加時,處理邊界問題只需要用 and。
1 | void ringbuf_init(ringbuf_t *rb) |
由於初始化時 head=tail=0,我將 head 和 tail 相等作為判斷 empty。
那如何判斷 full? 試想進到 queue 的資料越來越多,而卻不進行讀取(head=0),tail 變化為 head - 2, head - 1, head…,由於 head==tail 已作為判斷 empty,退而求其次以 (head - 1)==tail 作為 full 條件。
1 | bool ringbuf_is_empty(const ringbuf_t *rb) |
這樣做的後果是若一開始初始化 ring buffer 大小為 PL011_RINGBUFF_SIZE,滿載時 tail 永遠在 head 之後,可使用的空間為 PL011_RINGBUFF_SIZE-1,雖說空間無法完全使用,但隨著 buffer size 增加,這點差異是微不足道的。
read/write
對應 glibc 的 read, write,回傳已讀取/寫入位元組。採用 non-blocking 的方式實作,由於 buffer 不是隨時可讀取/寫入,回傳值可能與預期有差,使用此函式務必檢查回傳值是否符合預期。
1 | ssize_t uart_read(ringbuf_t *rb, void *dst, size_t count) |
putc, getc
基於 read, write 的單一字元操作,會等到 1 byte 字元順利讀取/寫入再回傳,回傳的型態為 int 而非 char,int 負數的部份是保留給 EOF 及其他 errno 使用。
getc, putc會處理 ‘\n’,getc_raw 用來讀取 kernel image 不對字元做任何操作。
1 | int uart_getc() |
fputs, fgets
基於 getc, putc 的寫入/讀取字串操作,回傳型態仿造 glibc。fgets 正確回傳 char,錯誤回傳 null pointer,錯誤情況包含傳入的 buffer size 無法除了 null byte 的其他字元。fputs 以 null bytes 做為寫入停止的標記,並回傳寫入的字元數。
1 | char *uart_fgets(char *buf, size_t len) |