# 3. External Interrupt 與 System Call
## Keyboard Event
修改太多東西了,請直接到 GitHub 上看原始碼,這邊僅擷取片段。
### 新增 uart.h
```
#include "uart.h"
#define LSR_RX_READY (1 << 0)
#define EOF 0
void uart_init()
{
/* disable interrupts */
UART_REGW(UART_IER, 0x00);
/* Baud rate setting */
uint8_t lcr = UART_REGR(UART_LCR);
UART_REGW(UART_LCR, lcr | (1 << 7));
UART_REGW(UART_DLL, 0x03);
UART_REGW(UART_DLM, 0x00);
lcr = 0;
UART_REGW(UART_LCR, lcr | (3 << 0));
uint8_t ier = UART_REGR(UART_IER);
UART_REGW(UART_IER, ier | (1 << 0));
}
void putchar(char c) {
*(char *)UART = c;
}
int getchar(void) {
if (*UART_LSR & LSR_RX_READY)
{
return *UART_RHR == '\r' ? '\n' : *UART_RHR;
}
else
{
return -1;
}
}
```
### 新增 plic.h
```c=
#include "riscv.h"
#define UART0_IRQ 10
#define VIRTIO_IRQ 1
#define PLIC_BASE 0x0c000000L
#define PLIC_PRIORITY(id) (PLIC_BASE + (id)*4)
#define PLIC_PENDING(id) (PLIC_BASE + 0x1000 + ((id) / 32))
#define PLIC_MENABLE(hart) (PLIC_BASE + 0x2000 + (hart)*0x80)
#define PLIC_MTHRESHOLD(hart) (PLIC_BASE + 0x200000 + (hart)*0x1000)
#define PLIC_MCLAIM(hart) (PLIC_BASE + 0x200004 + (hart)*0x1000)
#define PLIC_MCOMPLETE(hart) (PLIC_BASE + 0x200004 + (hart)*0x1000)
void plic_init();
int plic_claim();
void plic_complete(int irq);
```
### 新增 plic.c
```c=
#include "plic.h"
void plic_init() {
int hart = r_tp();
printf("hart = %d\n", hart);
// The "0" is reserved, and the lowest priority is "1".
*(uint32_t *)PLIC_PRIORITY(UART0_IRQ) = 1;
/* Enable UART0 */
*(uint32_t *)PLIC_MENABLE(hart) |= (1 << UART0_IRQ);
/* Set priority threshold for UART0. */
*(uint32_t *)PLIC_MTHRESHOLD(hart) = 0;
}
int plic_claim() {
int hart = r_tp();
int irq = *(uint32_t *)PLIC_MCLAIM(hart);
return irq;
}
void plic_complete(int irq) {
int hart = r_tp();
*(uint32_t *)PLIC_MCOMPLETE(hart) = irq;
}
```
### 修改 trap.c
```c=
#include "trap.h"
void trap_init() {
// set the machine-mode trap handler.
w_mtvec((reg_t)trap_vector);
// enable machine-mode external interrupts.
w_mie(r_mie() | MIE_MEIE);
// enable machine-mode timer interrupts.
w_mie(r_mie() | MIE_MTIE);
// enable machine-mode interrupts.
w_mstatus(r_mstatus() | MSTATUS_MIE);
}
void external_handler() {
int irq = plic_claim();
if (irq == UART0_IRQ) {
for (;;) {
int c = getchar();
if (c == -1)
{
break;
}
else
{
printf("USER INPUT %c\n", (char)c);
}
}
} else if (irq) {
printf("unexpected interrupt irq = %d\n", irq);
}
if (irq) {
plic_complete(irq);
}
}
reg_t trap_handler(reg_t epc, reg_t cause) {
reg_t return_pc = epc;
reg_t cause_code = cause & 0xfff;
/* printf("0x%x", cause); */
if (cause & 0x8000000000000000LL)
{
/* Asynchronous trap - interrupt */
switch (cause_code) {
case 3:
printf("software interruption!\n");
break;
case 7:
/* printf("timer interruption!\n"); */
// disable machine-mode timer interrupts.
w_mie(~((~r_mie()) | (1 << 7)));
timer_handler();
// enable machine-mode timer interrupts.
w_mie(r_mie() | MIE_MTIE);
break;
case 11:
external_handler();
break;
default:
printf("unknown async exception!\n");
break;
}
} else {
/* Synchronous trap - exception */
printf("Sync exceptions: ");
switch (cause_code) {
case 2:
printf("Illegal instruction!\n");
break;
case 5:
printf("Fault load!\n");
break;
case 7:
printf("Fault store!\n");
break;
default:
/* Synchronous trap - exception */
printf("Sync exceptions! cause code: %d\n", cause_code);
break;
}
/* return_pc += 2; */
while(1) {}
}
return return_pc;
}
```
### 修改 os.c
```c=
#include "printf.h"
#include "mem.h"
#include "time.h"
#include "trap.h"
#include "plic.h"
void delay(volatile int count) {
count *= 50000;
while (count--) {
asm volatile ("nop");
}
}
int os_main(void) {
uart_init();
page_init();
trap_init();
timer_init();
plic_init();
printf("OS start\n");
printf("test %c\n", 'a');
while(1) {
printf("OS Loop\n");
delay(10000);
};
return 0;
}
```
### 執行結果如下

## 製作 sys_call:malloc 和 free
### 新增 usys.h
```c=
extern void* malloc(size_t size);
extern int free(void *ptr);
```
提供一個高級的使用方式。
### 新增 usys.s
實作這些 functions:
```riscv=
.global sys_malloc
sys_malloc:
li a7, 1
ecall
ret
.global sys_free
sys_free:
li a7, 2
ecall
ret
```
邏輯很簡單,把 sys_call 編號壓入 a7,然後執行 `ecall`,他會引發一個 trap,於是我們要修改 sys.s 捕捉這個 a7。
### 修改 sys.s
```riscv=
.global trap_vector
trap_vector:
# Save the context
addi sp, sp, -256
reg_save sp
# call the C timer_handler(reg_t epc, reg_t cause)
csrr a0, mepc
csrr a1, mcause
mv a2, sp
call trap_handler
# timer_handler will return the return address via a0.
csrw mepc, a0
# Restore the context
reg_load sp
addi sp, sp, 256
mret # back to interrupt location (pc=mepc)
```
我們新增了一行 `mv a2, sp`,這是做什麽的呢?就是把當前的 context 傳入 trap_handler,為的就是讀取那個 a7。
### 修改 trap.c
```c=
reg_t trap_handler(reg_t epc, reg_t cause, struct context *ctx) {
reg_t return_pc = epc;
reg_t cause_code = cause & 0xfff;
/* printf("0x%x", cause); */
if (cause & 0x8000000000000000LL)
{
/* Asynchronous trap - interrupt */
switch (cause_code) {
case 3:
printf("software interruption!\n");
break;
case 7:
// disable machine-mode timer interrupts.
w_mie(~((~r_mie()) | (1 << 7)));
timer_handler();
// enable machine-mode timer interrupts.
w_mie(r_mie() | MIE_MTIE);
break;
case 11:
external_handler();
break;
default:
printf("unknown async exception!\n");
break;
}
} else {
/* Synchronous trap - exception */
switch (cause_code) {
case 2:
printf("Illegal instruction!\n");
break;
case 5:
printf("Fault load!\n");
break;
case 7:
printf("Fault store!\n");
break;
case 8:
/* printf("Environment call from U-mode!\n"); */
do_syscall(ctx);
return_pc += 4;
break;
case 11:
/* printf("Environment call from M-mode!\n"); */
do_syscall(ctx);
return_pc += 4;
break;
default:
/* Synchronous trap - exception */
printf("Sync exceptions! cause code: %d\n", cause_code);
break;
}
/* return_pc += 2; */
}
return return_pc;
}
```
核心在此:
```c=
case 8:
/* printf("Environment call from U-mode!\n"); */
do_syscall(ctx);
return_pc += 4;
break;
case 11:
/* printf("Environment call from M-mode!\n"); */
do_syscall(ctx);
return_pc += 4;
break;
```
### 新增 syscall.c 實作 do_syscall
```c=
#include "mem.h"
#include "sys.h"
void do_syscall(struct context *ctx) {
uint32_t syscall_num = ctx->a7;
/* printf("syscall_num: %d\n", syscall_num); */
switch (syscall_num) {
case 1:
/* printf("parameter = %d\n", ctx -> a0); */
ctx->a0 = malloc(ctx->a0);
break;
case 2:
ctx->a0 = free(ctx->a0);
break;
default:
printf("Unknown syscall no: %d\n", syscall_num);
ctx->a0 = -1;
}
}
```
### 修改 os.c
```c=
#include "printf.h"
#include "mem.h"
#include "time.h"
#include "trap.h"
#include "plic.h"
#include "usys.h"
void delay(volatile int count) {
count *= 50000;
while (count--) {
asm volatile ("nop");
}
}
void boot() {
uart_init();
page_init();
trap_init();
timer_init();
plic_init();
printf("OS start\n");
}
int os_main(void) {
boot();
while(1) {
printf("OS Loop\n");
sys_free(sys_malloc(4096 * 3));
delay(10000);
};
return 0;
}
```
使用者只需要 `#include "usys.h"` 就可以使用我們的 `sys_malloc` 和 `sys_free` 了。
### 目錄結構

## Spinlock
這樣子的程式其實有問題,如果我們在執行 malloc 當中進行了 context switch,他是有可能導致一塊相同的記憶體被分配到不同的兩隻 process,因此我們需要引入 lock 的概念來解決這個問題。
### 創建 lock.h
```c=
typedef struct lock
{
volatile int locked;
} lock_t;
extern int atomic_swap(lock_t *);
void lock_init(lock_t *lock);
void lock_acquire(lock_t *lock);
void lock_free(lock_t *lock);
```
### 創建 lock.c
```c=
#include "lock.h"
void lock_init(lock_t *lock) {
lock->locked = 0;
}
void lock_acquire(lock_t *lock) {
for (;;) {
if (!atomic_swap(lock)) {
break;
}
}
}
void lock_free(lock_t *lock) {
lock->locked = 0;
}
```
### 修改 sys.s
新增:
```riscv=
.global atomic_swap
.align 4
atomic_swap:
li a5, 1
amoswap.w.aq a5, a5, 0(a0)
mv a0, a5
ret
```
### 修改 mem.c
```c=
#include "mem.h"
lock_t lock;
void printMEM(int x) {
lock_acquire(&lock);
...
lock_free(&lock);
printf("|\n");
}
void* malloc(size_t size) {
lock_acquire(&lock);
int N = (size + PAGE_SIZE - 1) / PAGE_SIZE; // ceil
struct Page *page = (struct Page *)HEAP_START;
for(int i = 0; i < _num_pages; i++, page++) {
...
lock_free(&lock);
...
}
lock_free(&lock);
return 0x0;
}
```