Asynchronous Exception Handler

ARMv8-A’s CPU 將 interrupt 視為 asynchronous exception。Interrupt 又被分為 IRQ 及 FIQ,在實驗裡僅會用到 IRQ。

Interrupt Routing

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

IRQ Exception Router

在中斷向量表上被呼叫的 function,用來導向不同周邊的 interrupt handler。

ARM Interrupt Registers

exception.c

 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

timer 是測試 interrupt 最簡單的方式,rpi 有 4 種 timer:

  • system timer
  • arm side timer
  • arm local timer
  • arm core timer

我只玩了 Core timer 及 Local timer 兩種。

ARM Core 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:

timer.c

 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);
}

exception.c

 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

ARM Local Timer Registers

timer.c

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.
}

exception.c

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);
}

PL011 UART Interrupt

UART 可以透過不同 polling 來查看目前狀態,但是這樣會浪費很多 CPU 資源,透過 Interrupt 來進行傳輸或讀取可以提升效能。

PL011 UART Registers

queue.c

實做一個 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;
}

uart0.c

修改一下前次 Lab 的 code,寫入時先丟進 write_buffer,並在之後的 exception handler 消化。讀取時讀取 read_buffer,若有內容就拿出來。

uart_init

  • 設定 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
    ...
}

uart_read

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;
}

uart_write

 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
    }
}

exception.c

 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;
    }
}

Warning

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

comments powered by Disqus