ARMv8-A’s CPU 將 interrupt 視為 asynchronous exception。Interrupt 又被分為 IRQ 及 FIQ,在實驗裡僅會用到 IRQ。
Interrupt 分為兩類:
- Core related interrupts
- Core un-related interrupts
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--) {
// }
}
|
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);
}
|
實做一個 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;
}
}
|