Asynchronous Exception Handler
ARMv8-A’s CPU 將 interrupt 視為 asynchronous exception。Interrupt 又被分為 IRQ 及 FIQ,在實驗裡僅會用到 IRQ。
Interrupt 分為兩類:
- Core related interrupts
- Core un-related interrupts
Core related interrupts 會綁定指定的 core,大多數的 interrupt 都來自於 core 本身,例如 core timer interrupt。有以下幾種:
- Four timer interrupts (64-bit timer)
- One performance monitor interrupts
- Four Mailbox interrupts
Core un-related interrupts 不綁定 core,可以由任意 core 處理,通常來自周邊元件,例如 UART 或 system timer interrupt 等,有以下幾種:
- GPU IRQ
- GPU FIQ
- Local timer interrupt
- AXI error
在中斷向量表上被呼叫的 function,用來導向不同周邊的 interrupt handler。
ARM Interrupt Registers
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| void irq_enable() {
asm volatile("msr daifclr, #2");
}
void irq_exc_router() {
unsigned int irq_basic_pending = *IRQ_BASIC_PENDING;
unsigned int core0_intr_src = *CORE0_INTR_SRC;
// GPU IRQ 57: UART Interrupt
if (irq_basic_pending & (1 << 19)) {
uart_intr_handler();
}
// ARM Core Timer Interrupt
else if (core0_intr_src & (1 << 1)) {
arm_core_timer_intr_handler();
}
// ARM Local Timer Interrupt
else if (core0_intr_src & (1 << 11)) {
arm_local_timer_intr_handler();
}
}
|
timer 是測試 interrupt 最簡單的方式,rpi 有 4 種 timer:
- system timer
- arm side timer
- arm local timer
- arm core timer
我只玩了 Core timer 及 Local timer 兩種。
4 個 Core 會共享同一個 clock source,但彼此設定互相獨立:
- CNTP_CTL_EL0 (Counter-timer Physical Timer Control register)
- CNTPCT_EL0 (Counter-timer Physical Count register)
- CNTP_CVAL_EL0 (Counter-timer Physical Timer CompareValue register)
- 當 CNTPCT_EL0 >= CNTP_CVAL_EL0 時會觸發 Interrupt
- CNTP_TVAL_EL0 (Counter-timer Physical Timer TimerValue register)
- 當寫入 TVAL 時,其實是將 CVAL 設為 CNTPCT_EL0 加上寫入 TVAL 的值
Core timers interrupts control:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| void arm_core_timer_enable() {
// enable timer
register unsigned int enable = 1;
asm volatile("msr cntp_ctl_el0, %0" : : "r"(enable));
// set expired time
register unsigned int expire_period = CORE_TIMER_EXPRIED_PERIOD;
asm volatile("msr cntp_tval_el0, %0" : : "r"(expire_period));
// enable timer interrupt
*CORE0_TIMER_IRQ_CTRL |= 1 << 1;
}
void arm_core_timer_disable() {
// disable timer
register unsigned int enable = 0;
asm volatile("msr cntp_ctl_el0, %0" : : "r"(enable));
// disable timer interrupt
*CORE0_TIMER_IRQ_CTRL &= !(1 << 1);
}
|
1
2
3
4
5
6
7
8
9
10
| void arm_core_timer_intr_handler() {
register unsigned int expire_period = CORE_TIMER_EXPRIED_PERIOD;
asm volatile("msr cntp_tval_el0, %0" : : "r"(expire_period));
uart_printf("Core timer interrupt, jiffies %d\n", ++arm_core_timer_jiffies);
// bottom half simulation
// irq_enable();
// unsigned long long x = 100000000000;
// while (x--) {
// }
}
|
ARM Local Timer Registers
1
2
3
4
5
6
7
8
9
| void arm_local_timer_enable() {
unsigned int flag = 0x30000000; // enable timer and interrupt.
unsigned int reload = 0xfffffff / 10; // 0.14 Hz * 10
*LOCAL_TIMER_CTRL = flag | reload;
}
void arm_local_timer_disable() {
*LOCAL_TIMER_CTRL &= !(0b11 << 28); // disable timer and interrupt.
}
|
1
2
3
4
| void arm_local_timer_intr_handler() {
*LOCAL_TIMER_IRQ_CLR = 0b11 << 30; // clear interrupt
uart_printf("Local timer interrupt, jiffies %d\n", ++arm_local_timer_jiffies);
}
|
UART 可以透過不同 polling 來查看目前狀態,但是這樣會浪費很多 CPU 資源,透過 Interrupt 來進行傳輸或讀取可以提升效能。
PL011 UART Registers
實做一個 queue library,並使用 queue 保存 read_buffer 與 write_buffer。
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
| #include "queue.h"
void queue_init(struct queue* q, int size) {
q->front = 0;
q->rear = 0;
q->size = size;
}
int queue_empty(struct queue* q) {
return q->front == q->rear;
}
int queue_full(struct queue* q) {
return q->front == (q->rear + 1) % q->size;
}
void queue_push(struct queue* q, char val) {
if (queue_full(q)) return; // drop if full
q->buf[q->rear] = val;
q->rear = (q->rear + 1) % q->size;
}
char queue_pop(struct queue* q) {
if (queue_empty(q)) return '\0';
char elmt = q->buf[q->front];
q->front = (q->front + 1) % q->size;
return elmt;
}
|
修改一下前次 Lab 的 code,寫入時先丟進 write_buffer,並在之後的 exception handler 消化。讀取時讀取 read_buffer,若有內容就拿出來。
- 設定
UART0_IMSC
在 UART Controller 上打開 TX, RX 的 Interrupt - 設定
IRQ_ENABLE_2
的第 57 號位置 (uart_int) 在 Interrupt Controller 開啟 UART Interrupt
1
2
3
4
5
6
7
| void uart_init() {
...
/* Setup Interrupt */
*UART0_IMSC = 0b11 << 4; // Enable Tx, Rx Interrupt
*IRQ_ENABLE_2 |= 1 << 25; // Enable UART Interrupt
...
}
|
1
2
3
4
5
6
7
| char uart_read() {
while (queue_empty(&read_buf)) {
asm volatile ("nop");
}
char r = queue_pop(&read_buf);
return r == '\r' ? '\n' : r;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| void uart_write(char c) {
if (*UART0_FR & 0x80) { // TX FIFO Empty
// trigger interrupt by sending one character
if (queue_empty(&write_buf)) {
*UART0_DR = c;
}
else {
queue_push(&write_buf, c);
*UART0_DR = queue_pop(&write_buf);
}
}
else {
queue_push(&write_buf, c); // push to write queue, drop if buffer full
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| void uart_intr_handler() {
if (*UART0_MIS & 0x10) { // UARTTXINTR
while (!(*UART0_FR & 0x10)) { // RX FIFO not empty
char r = (char)(*UART0_DR);
queue_push(&read_buf, r);
}
*UART0_ICR = 1 << 4;
}
else if (*UART0_MIS & 0x20) { // UARTRTINTR
while (!queue_empty(&write_buf)) { // flush buffer to TX
while (*UART0_FR & 0x20) { // TX FIFO is full
asm volatile("nop");
}
*UART0_DR = queue_pop(&write_buf);
}
*UART0_ICR = 2 << 4;
}
}
|
https://grasslab.github.io/osdi/en/hardware/uart.html
qemu has a bug on PL011 UART’s transmit interrupt. According to manual, interrupt is set if transmit buffer is empty. But in qemu, you need to send your first byte then the MIS register would function well.
Generic Timer signals
The Generic Timer can schedule events and trigger interrupts based on an incrementing counter value.
成大資工 wiki
About the Generic Timer