This commit is contained in:
ccckmit
2020-11-14 11:31:33 +08:00
commit 5eec97bef9
69 changed files with 2775 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
*~
*.o
*.elf
*.bin
*.list
*.swp
*.bak
bak

20
01-HelloOs/Makefile Normal file
View File

@@ -0,0 +1,20 @@
CC = riscv64-unknown-elf-gcc
CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32
QEMU = qemu-system-riscv32
QFLAGS = -nographic -smp 4 -machine virt -bios none
OBJDUMP = riscv64-unknown-elf-objdump
all: os.elf
os.elf: start.s os.c
$(CC) $(CFLAGS) -T os.ld -o os.elf $^
qemu: $(TARGET)
@qemu-system-riscv32 -M ? | grep virt >/dev/null || exit
@echo "Press Ctrl-A and then X to exit QEMU"
$(QEMU) $(QFLAGS) -kernel os.elf
clean:
rm -f *.elf

19
01-HelloOs/README.md Normal file
View File

@@ -0,0 +1,19 @@
# 01-Hello OS
## Build & Run
```
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/01-HelloOs (master)
$ make clean
rm -f *.elf
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/01-HelloOs (master)
$ make
riscv64-unknown-elf-gcc -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -T os.ld -o os.elf start.s os.c
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/01-HelloOs (master)
$ make qemu
Press Ctrl-A and then X to exit QEMU
qemu-system-riscv32 -nographic -smp 4 -machine virt -bios none -kernel os.elf
Hello OS!
```

22
01-HelloOs/os.c Normal file
View File

@@ -0,0 +1,22 @@
#include <stdint.h>
// ref: https://www.activexperts.com/serial-port-component/tutorials/uart/
#define UART 0x10000000
#define UART_THR (uint8_t*)(UART+0x00) // THR:transmitter holding register
#define UART_LSR (uint8_t*)(UART+0x05) // LSR:line status register
#define UART_LSR_EMPTY_MASK 0x40 // LSR Bit 6: Transmitter empty; both the THR and LSR are empty
int lib_putc(char ch) {
while ((*UART_LSR & UART_LSR_EMPTY_MASK) == 0);
return *UART_THR = ch;
}
void lib_puts(char *s) {
while (*s) lib_putc(*s++);
}
int os_main(void)
{
lib_puts("Hello OS!\n");
return 0;
}

46
01-HelloOs/os.ld Normal file
View File

@@ -0,0 +1,46 @@
OUTPUT_ARCH( "riscv" )
ENTRY( _start )
MEMORY
{
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M
}
PHDRS
{
text PT_LOAD;
data PT_LOAD;
bss PT_LOAD;
}
SECTIONS
{
.text : {
PROVIDE(_text_start = .);
*(.text.init) *(.text .text.*)
PROVIDE(_text_end = .);
} >ram AT>ram :text
.rodata : {
PROVIDE(_rodata_start = .);
*(.rodata .rodata.*)
PROVIDE(_rodata_end = .);
} >ram AT>ram :text
.data : {
. = ALIGN(4096);
PROVIDE(_data_start = .);
*(.sdata .sdata.*) *(.data .data.*)
PROVIDE(_data_end = .);
} >ram AT>ram :data
.bss :{
PROVIDE(_bss_start = .);
*(.sbss .sbss.*) *(.bss .bss.*)
PROVIDE(_bss_end = .);
} >ram AT>ram :bss
PROVIDE(_memory_start = ORIGIN(ram));
PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram));
}

26
01-HelloOs/start.s Normal file
View File

@@ -0,0 +1,26 @@
.equ STACK_SIZE, 8192
.global _start
_start:
# setup stacks per hart
csrr t0, mhartid # read current hart id
slli t0, t0, 10 # shift left the hart id by 1024
la sp, stacks + STACK_SIZE # set the initial stack pointer
# to the end of the stack space
add sp, sp, t0 # move the current hart stack pointer
# to its place in the stack space
# park harts with id != 0
csrr a0, mhartid # read current hart id
bnez a0, park # if we're not on the hart 0
# we park the hart
j os_main # hart 0 jump to c
park:
wfi
j park
stacks:
.skip STACK_SIZE * 4 # allocate space for the harts stacks

20
02-ContextSwitch/Makefile Normal file
View File

@@ -0,0 +1,20 @@
CC = riscv64-unknown-elf-gcc
CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32
QEMU = qemu-system-riscv32
QFLAGS = -nographic -smp 4 -machine virt -bios none
OBJDUMP = riscv64-unknown-elf-objdump
all: os.elf
os.elf: start.s sys.s lib.c os.c
$(CC) $(CFLAGS) -T os.ld -o os.elf $^
qemu: $(TARGET)
@qemu-system-riscv32 -M ? | grep virt >/dev/null || exit
@echo "Press Ctrl-A and then X to exit QEMU"
$(QEMU) $(QFLAGS) -kernel os.elf
clean:
rm -f *.elf

View File

@@ -0,0 +1,21 @@
# 03-ContextSwitch
## Build & Run
```
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/03-ContextSwitch (master)
$ make clean
rm -f *.elf
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/03-ContextSwitch (master)
$ make
riscv64-unknown-elf-gcc -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -T os.ld -o os.elf start.s sys.s lib.c os.c
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/03-ContextSwitch (master)
$ make qemu
Press Ctrl-A and then X to exit QEMU
qemu-system-riscv32 -nographic -smp 4 -machine virt -bios none -kernel os.elf
OS start
Task0: Context Switch Success !
QEMU: Terminated
```

16
02-ContextSwitch/lib.c Normal file
View File

@@ -0,0 +1,16 @@
#include "lib.h"
void lib_delay(volatile int count)
{
count *= 50000;
while (count--);
}
int lib_putc(char ch) {
while ((*UART_LSR & UART_LSR_EMPTY_MASK) == 0);
return *UART_THR = ch;
}
void lib_puts(char *s) {
while (*s) lib_putc(*s++);
}

12
02-ContextSwitch/lib.h Normal file
View File

@@ -0,0 +1,12 @@
#ifndef __LIB_H__
#define __LIB_H__
#include "riscv.h"
#include <stddef.h>
#include <stdarg.h>
extern void lib_delay(volatile int count);
extern int lib_putc(char ch);
extern void lib_puts(char *s);
#endif

24
02-ContextSwitch/os.c Normal file
View File

@@ -0,0 +1,24 @@
#include "os.h"
#define STACK_SIZE 1024
uint8_t task0_stack[STACK_SIZE];
struct context ctx_os;
struct context ctx_task;
extern void sys_switch();
void user_task0(void)
{
lib_puts("Task0: Context Switch Success !\n");
while (1) {} // stop here.
}
int os_main(void)
{
lib_puts("OS start\n");
ctx_task.ra = (reg_t) user_task0;
ctx_task.sp = (reg_t) &task0_stack[STACK_SIZE-1];
sys_switch(&ctx_os, &ctx_task);
return 0;
}

9
02-ContextSwitch/os.h Normal file
View File

@@ -0,0 +1,9 @@
#ifndef __OS_H__
#define __OS_H__
#include "riscv.h"
#include "lib.h"
extern int os_main(void);
#endif

46
02-ContextSwitch/os.ld Normal file
View File

@@ -0,0 +1,46 @@
OUTPUT_ARCH( "riscv" )
ENTRY( _start )
MEMORY
{
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M
}
PHDRS
{
text PT_LOAD;
data PT_LOAD;
bss PT_LOAD;
}
SECTIONS
{
.text : {
PROVIDE(_text_start = .);
*(.text.init) *(.text .text.*)
PROVIDE(_text_end = .);
} >ram AT>ram :text
.rodata : {
PROVIDE(_rodata_start = .);
*(.rodata .rodata.*)
PROVIDE(_rodata_end = .);
} >ram AT>ram :text
.data : {
. = ALIGN(4096);
PROVIDE(_data_start = .);
*(.sdata .sdata.*) *(.data .data.*)
PROVIDE(_data_end = .);
} >ram AT>ram :data
.bss :{
PROVIDE(_bss_start = .);
*(.sbss .sbss.*) *(.bss .bss.*)
PROVIDE(_bss_end = .);
} >ram AT>ram :bss
PROVIDE(_memory_start = ORIGIN(ram));
PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram));
}

35
02-ContextSwitch/riscv.h Normal file
View File

@@ -0,0 +1,35 @@
#ifndef __RISCV_H__
#define __RISCV_H__
#include <stdint.h>
#define reg_t uint32_t // RISCV32: register is 32bits
// define reg_t as uint64_t // RISCV64: register is 64bits
// ref: https://www.activexperts.com/serial-port-component/tutorials/uart/
#define UART 0x10000000
#define UART_THR (uint8_t*)(UART+0x00) // THR:transmitter holding register
#define UART_LSR (uint8_t*)(UART+0x05) // LSR:line status register
#define UART_LSR_EMPTY_MASK 0x40 // LSR Bit 6: Transmitter empty; both the THR and LSR are empty
// Saved registers for kernel context switches.
struct context {
reg_t ra;
reg_t sp;
// callee-saved
reg_t s0;
reg_t s1;
reg_t s2;
reg_t s3;
reg_t s4;
reg_t s5;
reg_t s6;
reg_t s7;
reg_t s8;
reg_t s9;
reg_t s10;
reg_t s11;
};
#endif

26
02-ContextSwitch/start.s Normal file
View File

@@ -0,0 +1,26 @@
.equ STACK_SIZE, 8192
.global _start
_start:
# setup stacks per hart
csrr t0, mhartid # read current hart id
slli t0, t0, 10 # shift left the hart id by 1024
la sp, stacks + STACK_SIZE # set the initial stack pointer
# to the end of the stack space
add sp, sp, t0 # move the current hart stack pointer
# to its place in the stack space
# park harts with id != 0
csrr a0, mhartid # read current hart id
bnez a0, park # if we're not on the hart 0
# we park the hart
j os_main # hart 0 jump to c
park:
wfi
j park
stacks:
.skip STACK_SIZE * 4 # allocate space for the harts stacks

8
02-ContextSwitch/sys.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef __SYS_H__
#define __SYS_H__
#include "riscv.h"
extern void sys_timer();
extern void sys_switch(struct context *ctx_old, struct context *ctx_new);
#endif

52
02-ContextSwitch/sys.s Normal file
View File

@@ -0,0 +1,52 @@
# This Code derived from xv6-riscv (64bit)
# -- https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/swtch.S
# ============ MACRO ==================
.macro ctx_save base
sw ra, 0(\base)
sw sp, 4(\base)
sw s0, 8(\base)
sw s1, 12(\base)
sw s2, 16(\base)
sw s3, 20(\base)
sw s4, 24(\base)
sw s5, 28(\base)
sw s6, 32(\base)
sw s7, 36(\base)
sw s8, 40(\base)
sw s9, 44(\base)
sw s10, 48(\base)
sw s11, 52(\base)
.endm
.macro ctx_load base
lw ra, 0(\base)
lw sp, 4(\base)
lw s0, 8(\base)
lw s1, 12(\base)
lw s2, 16(\base)
lw s3, 20(\base)
lw s4, 24(\base)
lw s5, 28(\base)
lw s6, 32(\base)
lw s7, 36(\base)
lw s8, 40(\base)
lw s9, 44(\base)
lw s10, 48(\base)
lw s11, 52(\base)
.endm
# ============ Macro END ==================
# Context switch
#
# void sys_switch(struct context *old, struct context *new);
#
# Save current registers in old. Load from new.
.globl sys_switch
.align 4
sys_switch:
ctx_save a0 # a0 => struct context *old
ctx_load a1 # a1 => struct context *new
ret # pc=ra; swtch to new task (new->ra)

20
03-MultiTasking/Makefile Normal file
View File

@@ -0,0 +1,20 @@
CC = riscv64-unknown-elf-gcc
CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32
QEMU = qemu-system-riscv32
QFLAGS = -nographic -smp 4 -machine virt -bios none
OBJDUMP = riscv64-unknown-elf-objdump
all: os.elf
os.elf: start.s sys.s lib.c task.c os.c user.c
$(CC) $(CFLAGS) -T os.ld -o os.elf $^
qemu: $(TARGET)
@qemu-system-riscv32 -M ? | grep virt >/dev/null || exit
@echo "Press Ctrl-A and then X to exit QEMU"
$(QEMU) $(QFLAGS) -kernel os.elf
clean:
rm -f *.elf

56
03-MultiTasking/README.md Normal file
View File

@@ -0,0 +1,56 @@
# 04-MultiTasking
## Build & Run
```
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/04-MultiTasking (master)
$ make clean
rm -f *.elf
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/04-MultiTasking (master)
$ make
riscv64-unknown-elf-gcc -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -T os.ld -o os.elf start.s sys.s lib.c task.c os.c user.c
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/04-MultiTasking (master)
$ make qemu
Press Ctrl-A and then X to exit QEMU
qemu-system-riscv32 -nographic -smp 4 -machine virt -bios none -kernel os.elf
OS start
OS: Activate next task
Task0: Created!
Task0: Now, return to kernel mode
OS: Back to OS
OS: Activate next task
Task1: Created!
Task1: Now, return to kernel mode
OS: Back to OS
OS: Activate next task
Task0: Running...
OS: Back to OS
OS: Activate next task
Task1: Running...
OS: Back to OS
OS: Activate next task
Task0: Running...
OS: Back to OS
OS: Activate next task
Task1: Running...
OS: Back to OS
OS: Activate next task
Task0: Running...
OS: Back to OS
OS: Activate next task
Task1: Running...
OS: Back to OS
OS: Activate next task
Task0: Running...
QEMU: Terminated
```

16
03-MultiTasking/lib.c Normal file
View File

@@ -0,0 +1,16 @@
#include "lib.h"
void lib_delay(volatile int count)
{
count *= 50000;
while (count--);
}
int lib_putc(char ch) {
while ((*UART_LSR & UART_LSR_EMPTY_MASK) == 0);
return *UART_THR = ch;
}
void lib_puts(char *s) {
while (*s) lib_putc(*s++);
}

12
03-MultiTasking/lib.h Normal file
View File

@@ -0,0 +1,12 @@
#ifndef __LIB_H__
#define __LIB_H__
#include "riscv.h"
#include <stddef.h>
#include <stdarg.h>
extern void lib_delay(volatile int count);
extern int lib_putc(char ch);
extern void lib_puts(char *s);
#endif

26
03-MultiTasking/os.c Normal file
View File

@@ -0,0 +1,26 @@
#include "os.h"
void os_kernel() {
task_os();
}
void os_start() {
lib_puts("OS start\n");
user_init();
}
int os_main(void)
{
os_start();
int current_task = 0;
while (1) {
lib_puts("OS: Activate next task\n");
task_go(current_task);
lib_puts("OS: Back to OS\n");
current_task = (current_task + 1) % taskTop; // Round Robin Scheduling
lib_puts("\n");
}
return 0;
}

12
03-MultiTasking/os.h Normal file
View File

@@ -0,0 +1,12 @@
#ifndef __OS_H__
#define __OS_H__
#include "riscv.h"
#include "lib.h"
#include "task.h"
extern void user_init();
extern void os_kernel();
extern int os_main(void);
#endif

46
03-MultiTasking/os.ld Normal file
View File

@@ -0,0 +1,46 @@
OUTPUT_ARCH( "riscv" )
ENTRY( _start )
MEMORY
{
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M
}
PHDRS
{
text PT_LOAD;
data PT_LOAD;
bss PT_LOAD;
}
SECTIONS
{
.text : {
PROVIDE(_text_start = .);
*(.text.init) *(.text .text.*)
PROVIDE(_text_end = .);
} >ram AT>ram :text
.rodata : {
PROVIDE(_rodata_start = .);
*(.rodata .rodata.*)
PROVIDE(_rodata_end = .);
} >ram AT>ram :text
.data : {
. = ALIGN(4096);
PROVIDE(_data_start = .);
*(.sdata .sdata.*) *(.data .data.*)
PROVIDE(_data_end = .);
} >ram AT>ram :data
.bss :{
PROVIDE(_bss_start = .);
*(.sbss .sbss.*) *(.bss .bss.*)
PROVIDE(_bss_end = .);
} >ram AT>ram :bss
PROVIDE(_memory_start = ORIGIN(ram));
PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram));
}

35
03-MultiTasking/riscv.h Normal file
View File

@@ -0,0 +1,35 @@
#ifndef __RISCV_H__
#define __RISCV_H__
#include <stdint.h>
#define reg_t uint32_t // RISCV32: register is 32bits
// define reg_t as uint64_t // RISCV64: register is 64bits
// ref: https://www.activexperts.com/serial-port-component/tutorials/uart/
#define UART 0x10000000
#define UART_THR (uint8_t*)(UART+0x00) // THR:transmitter holding register
#define UART_LSR (uint8_t*)(UART+0x05) // LSR:line status register
#define UART_LSR_EMPTY_MASK 0x40 // LSR Bit 6: Transmitter empty; both the THR and LSR are empty
// Saved registers for kernel context switches.
struct context {
reg_t ra;
reg_t sp;
// callee-saved
reg_t s0;
reg_t s1;
reg_t s2;
reg_t s3;
reg_t s4;
reg_t s5;
reg_t s6;
reg_t s7;
reg_t s8;
reg_t s9;
reg_t s10;
reg_t s11;
};
#endif

26
03-MultiTasking/start.s Normal file
View File

@@ -0,0 +1,26 @@
.equ STACK_SIZE, 8192
.global _start
_start:
# setup stacks per hart
csrr t0, mhartid # read current hart id
slli t0, t0, 10 # shift left the hart id by 1024
la sp, stacks + STACK_SIZE # set the initial stack pointer
# to the end of the stack space
add sp, sp, t0 # move the current hart stack pointer
# to its place in the stack space
# park harts with id != 0
csrr a0, mhartid # read current hart id
bnez a0, park # if we're not on the hart 0
# we park the hart
j os_main # hart 0 jump to c
park:
wfi
j park
stacks:
.skip STACK_SIZE * 4 # allocate space for the harts stacks

8
03-MultiTasking/sys.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef __SYS_H__
#define __SYS_H__
#include "riscv.h"
extern void sys_timer();
extern void sys_switch(struct context *ctx_old, struct context *ctx_new);
#endif

52
03-MultiTasking/sys.s Normal file
View File

@@ -0,0 +1,52 @@
# This Code derived from xv6-riscv (64bit)
# -- https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/swtch.S
# ============ MACRO ==================
.macro ctx_save base
sw ra, 0(\base)
sw sp, 4(\base)
sw s0, 8(\base)
sw s1, 12(\base)
sw s2, 16(\base)
sw s3, 20(\base)
sw s4, 24(\base)
sw s5, 28(\base)
sw s6, 32(\base)
sw s7, 36(\base)
sw s8, 40(\base)
sw s9, 44(\base)
sw s10, 48(\base)
sw s11, 52(\base)
.endm
.macro ctx_load base
lw ra, 0(\base)
lw sp, 4(\base)
lw s0, 8(\base)
lw s1, 12(\base)
lw s2, 16(\base)
lw s3, 20(\base)
lw s4, 24(\base)
lw s5, 28(\base)
lw s6, 32(\base)
lw s7, 36(\base)
lw s8, 40(\base)
lw s9, 44(\base)
lw s10, 48(\base)
lw s11, 52(\base)
.endm
# ============ Macro END ==================
# Context switch
#
# void sys_switch(struct context *old, struct context *new);
#
# Save current registers in old. Load from new.
.globl sys_switch
.align 4
sys_switch:
ctx_save a0 # a0 => struct context *old
ctx_load a1 # a1 => struct context *new
ret # pc=ra; swtch to new task (new->ra)

30
03-MultiTasking/task.c Normal file
View File

@@ -0,0 +1,30 @@
#include "task.h"
#include "lib.h"
uint8_t task_stack[MAX_TASK][STACK_SIZE];
struct context ctx_os;
struct context ctx_tasks[MAX_TASK];
struct context *ctx_now;
int taskTop=0; // total number of task
// create a new task
int task_create(void (*task)(void))
{
int i=taskTop++;
ctx_tasks[i].ra = (reg_t) task;
ctx_tasks[i].sp = (reg_t) &task_stack[i][STACK_SIZE-1];
return i;
}
// switch to task[i]
void task_go(int i) {
ctx_now = &ctx_tasks[i];
sys_switch(&ctx_os, &ctx_tasks[i]);
}
// switch back to os
void task_os() {
struct context *ctx = ctx_now;
ctx_now = &ctx_os;
sys_switch(ctx, &ctx_os);
}

16
03-MultiTasking/task.h Normal file
View File

@@ -0,0 +1,16 @@
#ifndef __TASK_H__
#define __TASK_H__
#include "riscv.h"
#include "sys.h"
#define MAX_TASK 10
#define STACK_SIZE 1024
extern int taskTop;
extern int task_create(void (*task)(void));
extern void task_go(int i);
extern void task_os();
#endif

30
03-MultiTasking/user.c Normal file
View File

@@ -0,0 +1,30 @@
#include "os.h"
void user_task0(void)
{
lib_puts("Task0: Created!\n");
lib_puts("Task0: Now, return to kernel mode\n");
os_kernel();
while (1) {
lib_puts("Task0: Running...\n");
lib_delay(1000);
os_kernel();
}
}
void user_task1(void)
{
lib_puts("Task1: Created!\n");
lib_puts("Task1: Now, return to kernel mode\n");
os_kernel();
while (1) {
lib_puts("Task1: Running...\n");
lib_delay(1000);
os_kernel();
}
}
void user_init() {
task_create(&user_task0);
task_create(&user_task1);
}

View File

@@ -0,0 +1,20 @@
CC = riscv64-unknown-elf-gcc
CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32
QEMU = qemu-system-riscv32
QFLAGS = -nographic -smp 4 -machine virt -bios none
OBJDUMP = riscv64-unknown-elf-objdump
all: os.elf
os.elf: start.s sys.s lib.c timer.c os.c
$(CC) $(CFLAGS) -T os.ld -o os.elf $^
qemu: $(TARGET)
@qemu-system-riscv32 -M ? | grep virt >/dev/null || exit
@echo "Press Ctrl-A and then X to exit QEMU"
$(QEMU) $(QFLAGS) -kernel os.elf
clean:
rm -f *.elf

View File

@@ -0,0 +1,25 @@
# 04-TimerInterrupt
## Build & Run
```
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/04-TimerInterrupt (master)
$ make clean
rm -f *.elf
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/04-TimerInterrupt (master)
$ make
riscv64-unknown-elf-gcc -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -T os.ld -o os.elf start.s sys.s lib.c timer.c os.c
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/04-TimerInterrupt (master)
$ make qemu
Press Ctrl-A and then X to exit QEMU
qemu-system-riscv32 -nographic -smp 4 -machine virt -bios none -kernel os.elf
OS start
timer_handler: 1
timer_handler: 2
timer_handler: 3
timer_handler: 4
timer_handler: 5
QEMU: Terminated
```

146
04-TimerInterrupt/lib.c Normal file
View File

@@ -0,0 +1,146 @@
#include "lib.h"
void lib_delay(volatile int count)
{
count *= 50000;
while (count--);
}
int lib_putc(char ch) {
while ((*UART_LSR & UART_LSR_EMPTY_MASK) == 0);
return *UART_THR = ch;
}
void lib_puts(char *s) {
while (*s) lib_putc(*s++);
}
int lib_vsnprintf(char * out, size_t n, const char* s, va_list vl)
{
int format = 0;
int longarg = 0;
size_t pos = 0;
for( ; *s; s++) {
if (format) {
switch(*s) {
case 'l': {
longarg = 1;
break;
}
case 'p': {
longarg = 1;
if (out && pos < n) {
out[pos] = '0';
}
pos++;
if (out && pos < n) {
out[pos] = 'x';
}
pos++;
}
case 'x': {
long num = longarg ? va_arg(vl, long) : va_arg(vl, int);
int hexdigits = 2*(longarg ? sizeof(long) : sizeof(int))-1;
for(int i = hexdigits; i >= 0; i--) {
int d = (num >> (4*i)) & 0xF;
if (out && pos < n) {
out[pos] = (d < 10 ? '0'+d : 'a'+d-10);
}
pos++;
}
longarg = 0;
format = 0;
break;
}
case 'd': {
long num = longarg ? va_arg(vl, long) : va_arg(vl, int);
if (num < 0) {
num = -num;
if (out && pos < n) {
out[pos] = '-';
}
pos++;
}
long digits = 1;
for (long nn = num; nn /= 10; digits++)
;
for (int i = digits-1; i >= 0; i--) {
if (out && pos + i < n) {
out[pos + i] = '0' + (num % 10);
}
num /= 10;
}
pos += digits;
longarg = 0;
format = 0;
break;
}
case 's': {
const char* s2 = va_arg(vl, const char*);
while (*s2) {
if (out && pos < n) {
out[pos] = *s2;
}
pos++;
s2++;
}
longarg = 0;
format = 0;
break;
}
case 'c': {
if (out && pos < n) {
out[pos] = (char)va_arg(vl,int);
}
pos++;
longarg = 0;
format = 0;
break;
}
default:
break;
}
}
else if(*s == '%') {
format = 1;
}
else {
if (out && pos < n) {
out[pos] = *s;
}
pos++;
}
}
if (out && pos < n) {
out[pos] = 0;
}
else if (out && n) {
out[n-1] = 0;
}
return pos;
}
static char out_buf[1000]; // buffer for lib_vprintf()
int lib_vprintf(const char* s, va_list vl)
{
int res = lib_vsnprintf(NULL, -1, s, vl);
if (res+1 >= sizeof(out_buf)) {
lib_puts("error: lib_vprintf() output string size overflow\n");
while(1) {}
}
lib_vsnprintf(out_buf, res + 1, s, vl);
lib_puts(out_buf);
return res;
}
int lib_printf(const char* s, ...)
{
int res = 0;
va_list vl;
va_start(vl, s);
res = lib_vprintf(s, vl);
va_end(vl);
return res;
}

17
04-TimerInterrupt/lib.h Normal file
View File

@@ -0,0 +1,17 @@
#ifndef __LIB_H__
#define __LIB_H__
#include "riscv.h"
#include <stddef.h>
#include <stdarg.h>
#define lib_error(...) { lib_printf(__VA_ARGS__); while(1) {} } }
extern void lib_delay(volatile int count);
extern int lib_putc(char ch);
extern void lib_puts(char *s);
extern int lib_printf(const char* s, ...);
extern int lib_vprintf(const char* s, va_list vl);
extern int lib_vsnprintf(char * out, size_t n, const char* s, va_list vl);
#endif

15
04-TimerInterrupt/os.c Normal file
View File

@@ -0,0 +1,15 @@
#include "os.h"
void os_start() {
lib_puts("OS start\n");
// user_init();
timer_init(); // start timer interrupt ...
}
int os_main(void)
{
os_start();
while (1) {} // stop here !
return 0;
}

12
04-TimerInterrupt/os.h Normal file
View File

@@ -0,0 +1,12 @@
#ifndef __OS_H__
#define __OS_H__
#include "riscv.h"
#include "lib.h"
#include "timer.h"
extern void user_init();
extern void os_kernel();
extern int os_main(void);
#endif

46
04-TimerInterrupt/os.ld Normal file
View File

@@ -0,0 +1,46 @@
OUTPUT_ARCH( "riscv" )
ENTRY( _start )
MEMORY
{
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M
}
PHDRS
{
text PT_LOAD;
data PT_LOAD;
bss PT_LOAD;
}
SECTIONS
{
.text : {
PROVIDE(_text_start = .);
*(.text.init) *(.text .text.*)
PROVIDE(_text_end = .);
} >ram AT>ram :text
.rodata : {
PROVIDE(_rodata_start = .);
*(.rodata .rodata.*)
PROVIDE(_rodata_end = .);
} >ram AT>ram :text
.data : {
. = ALIGN(4096);
PROVIDE(_data_start = .);
*(.sdata .sdata.*) *(.data .data.*)
PROVIDE(_data_end = .);
} >ram AT>ram :data
.bss :{
PROVIDE(_bss_start = .);
*(.sbss .sbss.*) *(.bss .bss.*)
PROVIDE(_bss_end = .);
} >ram AT>ram :bss
PROVIDE(_memory_start = ORIGIN(ram));
PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram));
}

116
04-TimerInterrupt/riscv.h Normal file
View File

@@ -0,0 +1,116 @@
#ifndef __RISCV_H__
#define __RISCV_H__
#include <stdint.h>
#define reg_t uint32_t // RISCV32: register is 32bits
// define reg_t as uint64_t // RISCV64: register is 64bits
// ref: https://www.activexperts.com/serial-port-component/tutorials/uart/
#define UART 0x10000000
#define UART_THR (uint8_t*)(UART+0x00) // THR:transmitter holding register
#define UART_LSR (uint8_t*)(UART+0x05) // LSR:line status register
#define UART_LSR_EMPTY_MASK 0x40 // LSR Bit 6: Transmitter empty; both the THR and LSR are empty
// Saved registers for kernel context switches.
struct context {
reg_t ra;
reg_t sp;
// callee-saved
reg_t s0;
reg_t s1;
reg_t s2;
reg_t s3;
reg_t s4;
reg_t s5;
reg_t s6;
reg_t s7;
reg_t s8;
reg_t s9;
reg_t s10;
reg_t s11;
};
// ref: https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/riscv.h
//
// local interrupt controller, which contains the timer.
// ================== Timer Interrput ====================
#define NCPU 8 // maximum number of CPUs
#define CLINT 0x2000000
#define CLINT_MTIMECMP(hartid) (CLINT + 0x4000 + 4*(hartid))
#define CLINT_MTIME (CLINT + 0xBFF8) // cycles since boot.
// which hart (core) is this?
static inline reg_t r_mhartid()
{
reg_t x;
asm volatile("csrr %0, mhartid" : "=r" (x) );
return x;
}
// Machine Status Register, mstatus
#define MSTATUS_MPP_MASK (3 << 11) // previous mode.
#define MSTATUS_MPP_M (3 << 11)
#define MSTATUS_MPP_S (1 << 11)
#define MSTATUS_MPP_U (0 << 11)
#define MSTATUS_MIE (1 << 3) // machine-mode interrupt enable.
static inline reg_t r_mstatus()
{
reg_t x;
asm volatile("csrr %0, mstatus" : "=r" (x) );
return x;
}
static inline void w_mstatus(reg_t x)
{
asm volatile("csrw mstatus, %0" : : "r" (x));
}
// machine exception program counter, holds the
// instruction address to which a return from
// exception will go.
static inline void w_mepc(reg_t x)
{
asm volatile("csrw mepc, %0" : : "r" (x));
}
static inline reg_t r_mepc()
{
reg_t x;
asm volatile("csrr %0, mepc" : "=r" (x));
return x;
}
// Machine Scratch register, for early trap handler
static inline void w_mscratch(reg_t x)
{
asm volatile("csrw mscratch, %0" : : "r" (x));
}
// Machine-mode interrupt vector
static inline void w_mtvec(reg_t x)
{
asm volatile("csrw mtvec, %0" : : "r" (x));
}
// Machine-mode Interrupt Enable
#define MIE_MEIE (1 << 11) // external
#define MIE_MTIE (1 << 7) // timer
#define MIE_MSIE (1 << 3) // software
static inline reg_t r_mie()
{
reg_t x;
asm volatile("csrr %0, mie" : "=r" (x) );
return x;
}
static inline void w_mie(reg_t x)
{
asm volatile("csrw mie, %0" : : "r" (x));
}
#endif

26
04-TimerInterrupt/start.s Normal file
View File

@@ -0,0 +1,26 @@
.equ STACK_SIZE, 8192
.global _start
_start:
# setup stacks per hart
csrr t0, mhartid # read current hart id
slli t0, t0, 10 # shift left the hart id by 1024
la sp, stacks + STACK_SIZE # set the initial stack pointer
# to the end of the stack space
add sp, sp, t0 # move the current hart stack pointer
# to its place in the stack space
# park harts with id != 0
csrr a0, mhartid # read current hart id
bnez a0, park # if we're not on the hart 0
# we park the hart
j os_main # hart 0 jump to c
park:
wfi
j park
stacks:
.skip STACK_SIZE * 4 # allocate space for the harts stacks

8
04-TimerInterrupt/sys.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef __SYS_H__
#define __SYS_H__
#include "riscv.h"
extern void sys_timer();
extern void sys_switch(struct context *ctx_old, struct context *ctx_new);
#endif

163
04-TimerInterrupt/sys.s Normal file
View File

@@ -0,0 +1,163 @@
# This Code derived from xv6-riscv (64bit)
# -- https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/swtch.S
# ============ MACRO ==================
.macro ctx_save base
sw ra, 0(\base)
sw sp, 4(\base)
sw s0, 8(\base)
sw s1, 12(\base)
sw s2, 16(\base)
sw s3, 20(\base)
sw s4, 24(\base)
sw s5, 28(\base)
sw s6, 32(\base)
sw s7, 36(\base)
sw s8, 40(\base)
sw s9, 44(\base)
sw s10, 48(\base)
sw s11, 52(\base)
.endm
.macro ctx_load base
lw ra, 0(\base)
lw sp, 4(\base)
lw s0, 8(\base)
lw s1, 12(\base)
lw s2, 16(\base)
lw s3, 20(\base)
lw s4, 24(\base)
lw s5, 28(\base)
lw s6, 32(\base)
lw s7, 36(\base)
lw s8, 40(\base)
lw s9, 44(\base)
lw s10, 48(\base)
lw s11, 52(\base)
.endm
.macro reg_save base
# save the registers.
sw ra, 0(\base)
sw sp, 4(\base)
sw gp, 8(\base)
sw tp, 12(\base)
sw t0, 16(\base)
sw t1, 20(\base)
sw t2, 24(\base)
sw s0, 28(\base)
sw s1, 32(\base)
sw a0, 36(\base)
sw a1, 40(\base)
sw a2, 44(\base)
sw a3, 48(\base)
sw a4, 52(\base)
sw a5, 56(\base)
sw a6, 60(\base)
sw a7, 64(\base)
sw s2, 68(\base)
sw s3, 72(\base)
sw s4, 76(\base)
sw s5, 80(\base)
sw s6, 84(\base)
sw s7, 88(\base)
sw s8, 92(\base)
sw s9, 96(\base)
sw s10, 100(\base)
sw s11, 104(\base)
sw t3, 108(\base)
sw t4, 112(\base)
sw t5, 116(\base)
sw t6, 120(\base)
.endm
.macro reg_load base
# restore registers.
lw ra, 0(\base)
lw sp, 4(\base)
lw gp, 8(\base)
# not this, in case we moved CPUs: lw tp, 12(\base)
lw t0, 16(\base)
lw t1, 20(\base)
lw t2, 24(\base)
lw s0, 28(\base)
lw s1, 32(\base)
lw a0, 36(\base)
lw a1, 40(\base)
lw a2, 44(\base)
lw a3, 48(\base)
lw a4, 52(\base)
lw a5, 56(\base)
lw a6, 60(\base)
lw a7, 64(\base)
lw s2, 68(\base)
lw s3, 72(\base)
lw s4, 76(\base)
lw s5, 80(\base)
lw s6, 84(\base)
lw s7, 88(\base)
lw s8, 92(\base)
lw s9, 96(\base)
lw s10, 100(\base)
lw s11, 104(\base)
lw t3, 108(\base)
lw t4, 112(\base)
lw t5, 116(\base)
lw t6, 120(\base)
.endm
# ============ Macro END ==================
# Context switch
#
# void sys_switch(struct context *old, struct context *new);
#
# Save current registers in old. Load from new.
.globl sys_switch
.align 4
sys_switch:
ctx_save a0 # a0 => struct context *old
ctx_load a1 # a1 => struct context *new
ret # pc=ra; swtch to new task (new->ra)
.globl sys_kernel
.align 4
sys_kernel:
addi sp, sp, -128
reg_save sp
call timer_handler # context switch ...
reg_load sp
addi sp, sp, 128
jr a7 # jump to a7=mepc , return to timer break point
.globl sys_timer
.align 4
sys_timer:
# timer_init() has set up the memory that mscratch points to:
# scratch[0,4,8] : register save area.
# scratch[12] : address of CLINT's MTIMECMP register.
# scratch[16] : desired interval between interrupts.
csrrw a0, mscratch, a0 # exchange(mscratch,a0)
sw a1, 0(a0)
sw a2, 4(a0)
sw a3, 8(a0)
# schedule the next timer interrupt
# by adding interval to mtimecmp.
lw a1, 12(a0) # CLINT_MTIMECMP(hart)
lw a2, 16(a0) # interval
lw a3, 0(a1) # a3 = CLINT_MTIMECMP(hart)
add a3, a3, a2 # a3 += interval
sw a3, 0(a1) # CLINT_MTIMECMP(hart) = a3
csrr a7, mepc # a7 = mepc, for sys_kernel jump back to interrupted point
la a1, sys_kernel # mepc = sys_kernel
csrw mepc, a1 # mret : will jump to sys_kernel
lw a3, 8(a0)
lw a2, 4(a0)
lw a1, 0(a0)
csrrw a0, mscratch, a0 # exchange(mscratch,a0)
mret # jump to mepc (=sys_kernel)

41
04-TimerInterrupt/timer.c Normal file
View File

@@ -0,0 +1,41 @@
#include "timer.h"
extern void os_kernel();
// a scratch area per CPU for machine-mode timer interrupts.
reg_t timer_scratch[NCPU][5];
void timer_init()
{
// each CPU has a separate source of timer interrupts.
int id = r_mhartid();
// ask the CLINT for a timer interrupt.
int interval = 10000000; // cycles; about 1 second in qemu.
*(reg_t*)CLINT_MTIMECMP(id) = *(reg_t*)CLINT_MTIME + interval;
// prepare information in scratch[] for timervec.
// scratch[0..2] : space for timervec to save registers.
// scratch[3] : address of CLINT MTIMECMP register.
// scratch[4] : desired interval (in cycles) between timer interrupts.
reg_t *scratch = &timer_scratch[id][0];
scratch[3] = CLINT_MTIMECMP(id);
scratch[4] = interval;
w_mscratch((reg_t)scratch);
// set the machine-mode trap handler.
w_mtvec((reg_t)sys_timer);
// enable machine-mode interrupts.
w_mstatus(r_mstatus() | MSTATUS_MIE);
// enable machine-mode timer interrupts.
w_mie(r_mie() | MIE_MTIE);
}
static int timer_count = 0;
void timer_handler() {
lib_printf("timer_handler: %d\n", ++timer_count);
// os_kernel();
}

11
04-TimerInterrupt/timer.h Normal file
View File

@@ -0,0 +1,11 @@
#ifndef __TIMER_H__
#define __TIMER_H__
#include "riscv.h"
#include "sys.h"
#include "lib.h"
extern void timer_handler();
extern void timer_init();
#endif

20
05-Preemptive/Makefile Normal file
View File

@@ -0,0 +1,20 @@
CC = riscv64-unknown-elf-gcc
CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32
QEMU = qemu-system-riscv32
QFLAGS = -nographic -smp 4 -machine virt -bios none
OBJDUMP = riscv64-unknown-elf-objdump
all: os.elf
os.elf: start.s sys.s lib.c timer.c task.c os.c user.c
$(CC) $(CFLAGS) -T os.ld -o os.elf $^
qemu: $(TARGET)
@qemu-system-riscv32 -M ? | grep virt >/dev/null || exit
@echo "Press Ctrl-A and then X to exit QEMU"
$(QEMU) $(QFLAGS) -kernel os.elf
clean:
rm -f *.elf

68
05-Preemptive/README.md Normal file
View File

@@ -0,0 +1,68 @@
# 05-Preemptive
## Build & Run
```
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/05-Preemptive (master)
$ make clean
rm -f *.elf
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/05-Preemptive (master)
$ make
riscv64-unknown-elf-gcc -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -T os.ld -o os.elf start.s sys.s lib.c timer.c task.c os.c user.c
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/05-Preemptive (master)
$ make qemu
Press Ctrl-A and then X to exit QEMU
qemu-system-riscv32 -nographic -smp 4 -machine virt -bios none -kernel os.elf
OS start
OS: Activate next task
Task0: Created!
Task0: Now, return to kernel mode
OS: Back to OS
OS: Activate next task
Task1: Created!
Task1: Now, return to kernel mode
OS: Back to OS
OS: Activate next task
Task0: Running...
Task0: Running...
Task0: Running...
timer_handler: 1
OS: Back to OS
OS: Activate next task
Task1: Running...
Task1: Running...
Task1: Running...
timer_handler: 2
OS: Back to OS
OS: Activate next task
Task0: Running...
Task0: Running...
Task0: Running...
timer_handler: 3
OS: Back to OS
OS: Activate next task
Task1: Running...
Task1: Running...
Task1: Running...
timer_handler: 4
OS: Back to OS
OS: Activate next task
Task0: Running...
Task0: Running...
Task0: Running...
timer_handler: 5
OS: Back to OS
OS: Activate next task
Task1: Running...
Task1: Running...
QEMU: Terminated
```

146
05-Preemptive/lib.c Normal file
View File

@@ -0,0 +1,146 @@
#include "lib.h"
void lib_delay(volatile int count)
{
count *= 50000;
while (count--);
}
int lib_putc(char ch) {
while ((*UART_LSR & UART_LSR_EMPTY_MASK) == 0);
return *UART_THR = ch;
}
void lib_puts(char *s) {
while (*s) lib_putc(*s++);
}
int lib_vsnprintf(char * out, size_t n, const char* s, va_list vl)
{
int format = 0;
int longarg = 0;
size_t pos = 0;
for( ; *s; s++) {
if (format) {
switch(*s) {
case 'l': {
longarg = 1;
break;
}
case 'p': {
longarg = 1;
if (out && pos < n) {
out[pos] = '0';
}
pos++;
if (out && pos < n) {
out[pos] = 'x';
}
pos++;
}
case 'x': {
long num = longarg ? va_arg(vl, long) : va_arg(vl, int);
int hexdigits = 2*(longarg ? sizeof(long) : sizeof(int))-1;
for(int i = hexdigits; i >= 0; i--) {
int d = (num >> (4*i)) & 0xF;
if (out && pos < n) {
out[pos] = (d < 10 ? '0'+d : 'a'+d-10);
}
pos++;
}
longarg = 0;
format = 0;
break;
}
case 'd': {
long num = longarg ? va_arg(vl, long) : va_arg(vl, int);
if (num < 0) {
num = -num;
if (out && pos < n) {
out[pos] = '-';
}
pos++;
}
long digits = 1;
for (long nn = num; nn /= 10; digits++)
;
for (int i = digits-1; i >= 0; i--) {
if (out && pos + i < n) {
out[pos + i] = '0' + (num % 10);
}
num /= 10;
}
pos += digits;
longarg = 0;
format = 0;
break;
}
case 's': {
const char* s2 = va_arg(vl, const char*);
while (*s2) {
if (out && pos < n) {
out[pos] = *s2;
}
pos++;
s2++;
}
longarg = 0;
format = 0;
break;
}
case 'c': {
if (out && pos < n) {
out[pos] = (char)va_arg(vl,int);
}
pos++;
longarg = 0;
format = 0;
break;
}
default:
break;
}
}
else if(*s == '%') {
format = 1;
}
else {
if (out && pos < n) {
out[pos] = *s;
}
pos++;
}
}
if (out && pos < n) {
out[pos] = 0;
}
else if (out && n) {
out[n-1] = 0;
}
return pos;
}
static char out_buf[1000]; // buffer for lib_vprintf()
int lib_vprintf(const char* s, va_list vl)
{
int res = lib_vsnprintf(NULL, -1, s, vl);
if (res+1 >= sizeof(out_buf)) {
lib_puts("error: lib_vprintf() output string size overflow\n");
while(1) {}
}
lib_vsnprintf(out_buf, res + 1, s, vl);
lib_puts(out_buf);
return res;
}
int lib_printf(const char* s, ...)
{
int res = 0;
va_list vl;
va_start(vl, s);
res = lib_vprintf(s, vl);
va_end(vl);
return res;
}

17
05-Preemptive/lib.h Normal file
View File

@@ -0,0 +1,17 @@
#ifndef __LIB_H__
#define __LIB_H__
#include "riscv.h"
#include <stddef.h>
#include <stdarg.h>
#define lib_error(...) { lib_printf(__VA_ARGS__); while(1) {} } }
extern void lib_delay(volatile int count);
extern int lib_putc(char ch);
extern void lib_puts(char *s);
extern int lib_printf(const char* s, ...);
extern int lib_vprintf(const char* s, va_list vl);
extern int lib_vsnprintf(char * out, size_t n, const char* s, va_list vl);
#endif

27
05-Preemptive/os.c Normal file
View File

@@ -0,0 +1,27 @@
#include "os.h"
void os_kernel() {
task_os();
}
void os_start() {
lib_puts("OS start\n");
user_init();
timer_init(); // start timer interrupt ...
}
int os_main(void)
{
os_start();
int current_task = 0;
while (1) {
lib_puts("OS: Activate next task\n");
task_go(current_task);
lib_puts("OS: Back to OS\n");
current_task = (current_task + 1) % taskTop; // Round Robin Scheduling
lib_puts("\n");
}
return 0;
}

13
05-Preemptive/os.h Normal file
View File

@@ -0,0 +1,13 @@
#ifndef __OS_H__
#define __OS_H__
#include "riscv.h"
#include "lib.h"
#include "task.h"
#include "timer.h"
extern void user_init();
extern void os_kernel();
extern int os_main(void);
#endif

46
05-Preemptive/os.ld Normal file
View File

@@ -0,0 +1,46 @@
OUTPUT_ARCH( "riscv" )
ENTRY( _start )
MEMORY
{
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M
}
PHDRS
{
text PT_LOAD;
data PT_LOAD;
bss PT_LOAD;
}
SECTIONS
{
.text : {
PROVIDE(_text_start = .);
*(.text.init) *(.text .text.*)
PROVIDE(_text_end = .);
} >ram AT>ram :text
.rodata : {
PROVIDE(_rodata_start = .);
*(.rodata .rodata.*)
PROVIDE(_rodata_end = .);
} >ram AT>ram :text
.data : {
. = ALIGN(4096);
PROVIDE(_data_start = .);
*(.sdata .sdata.*) *(.data .data.*)
PROVIDE(_data_end = .);
} >ram AT>ram :data
.bss :{
PROVIDE(_bss_start = .);
*(.sbss .sbss.*) *(.bss .bss.*)
PROVIDE(_bss_end = .);
} >ram AT>ram :bss
PROVIDE(_memory_start = ORIGIN(ram));
PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram));
}

116
05-Preemptive/riscv.h Normal file
View File

@@ -0,0 +1,116 @@
#ifndef __RISCV_H__
#define __RISCV_H__
#include <stdint.h>
#define reg_t uint32_t // RISCV32: register is 32bits
// define reg_t as uint64_t // RISCV64: register is 64bits
// ref: https://www.activexperts.com/serial-port-component/tutorials/uart/
#define UART 0x10000000
#define UART_THR (uint8_t*)(UART+0x00) // THR:transmitter holding register
#define UART_LSR (uint8_t*)(UART+0x05) // LSR:line status register
#define UART_LSR_EMPTY_MASK 0x40 // LSR Bit 6: Transmitter empty; both the THR and LSR are empty
// Saved registers for kernel context switches.
struct context {
reg_t ra;
reg_t sp;
// callee-saved
reg_t s0;
reg_t s1;
reg_t s2;
reg_t s3;
reg_t s4;
reg_t s5;
reg_t s6;
reg_t s7;
reg_t s8;
reg_t s9;
reg_t s10;
reg_t s11;
};
// ref: https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/riscv.h
//
// local interrupt controller, which contains the timer.
// ================== Timer Interrput ====================
#define NCPU 8 // maximum number of CPUs
#define CLINT 0x2000000
#define CLINT_MTIMECMP(hartid) (CLINT + 0x4000 + 4*(hartid))
#define CLINT_MTIME (CLINT + 0xBFF8) // cycles since boot.
// which hart (core) is this?
static inline reg_t r_mhartid()
{
reg_t x;
asm volatile("csrr %0, mhartid" : "=r" (x) );
return x;
}
// Machine Status Register, mstatus
#define MSTATUS_MPP_MASK (3 << 11) // previous mode.
#define MSTATUS_MPP_M (3 << 11)
#define MSTATUS_MPP_S (1 << 11)
#define MSTATUS_MPP_U (0 << 11)
#define MSTATUS_MIE (1 << 3) // machine-mode interrupt enable.
static inline reg_t r_mstatus()
{
reg_t x;
asm volatile("csrr %0, mstatus" : "=r" (x) );
return x;
}
static inline void w_mstatus(reg_t x)
{
asm volatile("csrw mstatus, %0" : : "r" (x));
}
// machine exception program counter, holds the
// instruction address to which a return from
// exception will go.
static inline void w_mepc(reg_t x)
{
asm volatile("csrw mepc, %0" : : "r" (x));
}
static inline reg_t r_mepc()
{
reg_t x;
asm volatile("csrr %0, mepc" : "=r" (x));
return x;
}
// Machine Scratch register, for early trap handler
static inline void w_mscratch(reg_t x)
{
asm volatile("csrw mscratch, %0" : : "r" (x));
}
// Machine-mode interrupt vector
static inline void w_mtvec(reg_t x)
{
asm volatile("csrw mtvec, %0" : : "r" (x));
}
// Machine-mode Interrupt Enable
#define MIE_MEIE (1 << 11) // external
#define MIE_MTIE (1 << 7) // timer
#define MIE_MSIE (1 << 3) // software
static inline reg_t r_mie()
{
reg_t x;
asm volatile("csrr %0, mie" : "=r" (x) );
return x;
}
static inline void w_mie(reg_t x)
{
asm volatile("csrw mie, %0" : : "r" (x));
}
#endif

26
05-Preemptive/start.s Normal file
View File

@@ -0,0 +1,26 @@
.equ STACK_SIZE, 8192
.global _start
_start:
# setup stacks per hart
csrr t0, mhartid # read current hart id
slli t0, t0, 10 # shift left the hart id by 1024
la sp, stacks + STACK_SIZE # set the initial stack pointer
# to the end of the stack space
add sp, sp, t0 # move the current hart stack pointer
# to its place in the stack space
# park harts with id != 0
csrr a0, mhartid # read current hart id
bnez a0, park # if we're not on the hart 0
# we park the hart
j os_main # hart 0 jump to c
park:
wfi
j park
stacks:
.skip STACK_SIZE * 4 # allocate space for the harts stacks

8
05-Preemptive/sys.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef __SYS_H__
#define __SYS_H__
#include "riscv.h"
extern void sys_timer();
extern void sys_switch(struct context *ctx_old, struct context *ctx_new);
#endif

163
05-Preemptive/sys.s Normal file
View File

@@ -0,0 +1,163 @@
# This Code derived from xv6-riscv (64bit)
# -- https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/swtch.S
# ============ MACRO ==================
.macro ctx_save base
sw ra, 0(\base)
sw sp, 4(\base)
sw s0, 8(\base)
sw s1, 12(\base)
sw s2, 16(\base)
sw s3, 20(\base)
sw s4, 24(\base)
sw s5, 28(\base)
sw s6, 32(\base)
sw s7, 36(\base)
sw s8, 40(\base)
sw s9, 44(\base)
sw s10, 48(\base)
sw s11, 52(\base)
.endm
.macro ctx_load base
lw ra, 0(\base)
lw sp, 4(\base)
lw s0, 8(\base)
lw s1, 12(\base)
lw s2, 16(\base)
lw s3, 20(\base)
lw s4, 24(\base)
lw s5, 28(\base)
lw s6, 32(\base)
lw s7, 36(\base)
lw s8, 40(\base)
lw s9, 44(\base)
lw s10, 48(\base)
lw s11, 52(\base)
.endm
.macro reg_save base
# save the registers.
sw ra, 0(\base)
sw sp, 4(\base)
sw gp, 8(\base)
sw tp, 12(\base)
sw t0, 16(\base)
sw t1, 20(\base)
sw t2, 24(\base)
sw s0, 28(\base)
sw s1, 32(\base)
sw a0, 36(\base)
sw a1, 40(\base)
sw a2, 44(\base)
sw a3, 48(\base)
sw a4, 52(\base)
sw a5, 56(\base)
sw a6, 60(\base)
sw a7, 64(\base)
sw s2, 68(\base)
sw s3, 72(\base)
sw s4, 76(\base)
sw s5, 80(\base)
sw s6, 84(\base)
sw s7, 88(\base)
sw s8, 92(\base)
sw s9, 96(\base)
sw s10, 100(\base)
sw s11, 104(\base)
sw t3, 108(\base)
sw t4, 112(\base)
sw t5, 116(\base)
sw t6, 120(\base)
.endm
.macro reg_load base
# restore registers.
lw ra, 0(\base)
lw sp, 4(\base)
lw gp, 8(\base)
# not this, in case we moved CPUs: lw tp, 12(\base)
lw t0, 16(\base)
lw t1, 20(\base)
lw t2, 24(\base)
lw s0, 28(\base)
lw s1, 32(\base)
lw a0, 36(\base)
lw a1, 40(\base)
lw a2, 44(\base)
lw a3, 48(\base)
lw a4, 52(\base)
lw a5, 56(\base)
lw a6, 60(\base)
lw a7, 64(\base)
lw s2, 68(\base)
lw s3, 72(\base)
lw s4, 76(\base)
lw s5, 80(\base)
lw s6, 84(\base)
lw s7, 88(\base)
lw s8, 92(\base)
lw s9, 96(\base)
lw s10, 100(\base)
lw s11, 104(\base)
lw t3, 108(\base)
lw t4, 112(\base)
lw t5, 116(\base)
lw t6, 120(\base)
.endm
# ============ Macro END ==================
# Context switch
#
# void sys_switch(struct context *old, struct context *new);
#
# Save current registers in old. Load from new.
.globl sys_switch
.align 4
sys_switch:
ctx_save a0 # a0 => struct context *old
ctx_load a1 # a1 => struct context *new
ret # pc=ra; swtch to new task (new->ra)
.globl sys_kernel
.align 4
sys_kernel:
addi sp, sp, -128
reg_save sp
call timer_handler # context switch ...
reg_load sp
addi sp, sp, 128
jr a7 # jump to a7=mepc , return to timer break point
.globl sys_timer
.align 4
sys_timer:
# timer_init() has set up the memory that mscratch points to:
# scratch[0,4,8] : register save area.
# scratch[12] : address of CLINT's MTIMECMP register.
# scratch[16] : desired interval between interrupts.
csrrw a0, mscratch, a0 # exchange(mscratch,a0)
sw a1, 0(a0)
sw a2, 4(a0)
sw a3, 8(a0)
# schedule the next timer interrupt
# by adding interval to mtimecmp.
lw a1, 12(a0) # CLINT_MTIMECMP(hart)
lw a2, 16(a0) # interval
lw a3, 0(a1) # a3 = CLINT_MTIMECMP(hart)
add a3, a3, a2 # a3 += interval
sw a3, 0(a1) # CLINT_MTIMECMP(hart) = a3
csrr a7, mepc # a7 = mepc, for sys_kernel jump back to interrupted point
la a1, sys_kernel # mepc = sys_kernel
csrw mepc, a1 # mret : will jump to sys_kernel
lw a3, 8(a0)
lw a2, 4(a0)
lw a1, 0(a0)
csrrw a0, mscratch, a0 # exchange(mscratch,a0)
mret # jump to mepc (=sys_kernel)

30
05-Preemptive/task.c Normal file
View File

@@ -0,0 +1,30 @@
#include "task.h"
#include "lib.h"
uint8_t task_stack[MAX_TASK][STACK_SIZE];
struct context ctx_os;
struct context ctx_tasks[MAX_TASK];
struct context *ctx_now;
int taskTop=0; // total number of task
// create a new task
int task_create(void (*task)(void))
{
int i=taskTop++;
ctx_tasks[i].ra = (reg_t) task;
ctx_tasks[i].sp = (reg_t) &task_stack[i][STACK_SIZE-1];
return i;
}
// switch to task[i]
void task_go(int i) {
ctx_now = &ctx_tasks[i];
sys_switch(&ctx_os, &ctx_tasks[i]);
}
// switch back to os
void task_os() {
struct context *ctx = ctx_now;
ctx_now = &ctx_os;
sys_switch(ctx, &ctx_os);
}

16
05-Preemptive/task.h Normal file
View File

@@ -0,0 +1,16 @@
#ifndef __TASK_H__
#define __TASK_H__
#include "riscv.h"
#include "sys.h"
#define MAX_TASK 10
#define STACK_SIZE 1024
extern int taskTop;
extern int task_create(void (*task)(void));
extern void task_go(int i);
extern void task_os();
#endif

42
05-Preemptive/timer.c Normal file
View File

@@ -0,0 +1,42 @@
#include "timer.h"
extern void os_kernel();
// a scratch area per CPU for machine-mode timer interrupts.
reg_t timer_scratch[NCPU][5];
void timer_init()
{
// each CPU has a separate source of timer interrupts.
int id = r_mhartid();
// ask the CLINT for a timer interrupt.
// int interval = 1000000; // cycles; about 1/10th second in qemu.
int interval = 20000000; // cycles; about 2 second in qemu.
*(reg_t*)CLINT_MTIMECMP(id) = *(reg_t*)CLINT_MTIME + interval;
// prepare information in scratch[] for timervec.
// scratch[0..2] : space for timervec to save registers.
// scratch[3] : address of CLINT MTIMECMP register.
// scratch[4] : desired interval (in cycles) between timer interrupts.
reg_t *scratch = &timer_scratch[id][0];
scratch[3] = CLINT_MTIMECMP(id);
scratch[4] = interval;
w_mscratch((reg_t)scratch);
// set the machine-mode trap handler.
w_mtvec((reg_t)sys_timer);
// enable machine-mode interrupts.
w_mstatus(r_mstatus() | MSTATUS_MIE);
// enable machine-mode timer interrupts.
w_mie(r_mie() | MIE_MTIE);
}
static int timer_count = 0;
void timer_handler() {
lib_printf("timer_handler: %d\n", ++timer_count);
os_kernel();
}

12
05-Preemptive/timer.h Normal file
View File

@@ -0,0 +1,12 @@
#ifndef __TIMER_H__
#define __TIMER_H__
#include "riscv.h"
#include "sys.h"
#include "lib.h"
#include "task.h"
extern void timer_handler();
extern void timer_init();
#endif

30
05-Preemptive/user.c Normal file
View File

@@ -0,0 +1,30 @@
#include "os.h"
void user_task0(void)
{
lib_puts("Task0: Created!\n");
lib_puts("Task0: Now, return to kernel mode\n");
os_kernel();
while (1) {
lib_puts("Task0: Running...\n");
lib_delay(1000);
// os_kernel();
}
}
void user_task1(void)
{
lib_puts("Task1: Created!\n");
lib_puts("Task1: Now, return to kernel mode\n");
os_kernel();
while (1) {
lib_puts("Task1: Running...\n");
lib_delay(1000);
// os_kernel();
}
}
void user_init() {
task_create(&user_task0);
task_create(&user_task1);
}

5
AUTHORS Normal file
View File

@@ -0,0 +1,5 @@
mini-riscv-os is written by:
Chung-Chen Chen <ccckmit@gmail.com>
Copyrighted by:
National Quemoy University, Taiwan

26
LICENSE Normal file
View File

@@ -0,0 +1,26 @@
mini-riscv-os is freely redistributable under the two-clause BSD License:
Copyright (C) 2015-2018 National Quemoy University, Taiwan.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.

54
README.md Normal file
View File

@@ -0,0 +1,54 @@
# mini-riscv-os
Build a minimal multi-tasking OS kernel for RISC-V from scratch
Mini-riscv-os was inspired by [jserv](https://github.com/jserv)'s [mini-arm-os](https://github.com/jserv/mini-arm-os) project.
However, [ccckmit](https://github.com/ccckmit) rewrite the project for RISC-V, and run on Win10 instead of Linux.
## Prerequisites
* Win10 : [git-bash](https://git-scm.com/download/win) + [FreedomStudio](https://www.sifive.com/software)
After download and extract the FreedomStudio for windows. You have to set the system PATH to the folder of `riscv64-unknown-elf-gcc/bin` and `riscv-qemu/bin`. For example, I set PATH to the following folders.
```
D:\install\FreedomStudio-2020-06-3-win64\SiFive\riscv64-unknown-elf-gcc-8.3.0-2020.04.1\bin
D:\install\FreedomStudio-2020-06-3-win64\SiFive\riscv-qemu-4.2.0-2020.04.0\bin
```
And you should start your git-bash to build the project. (It works for me in vscode bash terminal)
## Steps
* `01-HelloOs`
- Enable UART to print trivial greetings
* `02-ContextSwitch`
- Basic switch from OS to user task
* `03-Multitasking`
- Two user tasks are interatively switching
* `04-TimerInterrupt`
- Enable SysTick for future scheduler implementation
* `05-Preemptive`
- Basic preemptive scheduling
## Building and Verification
* Changes the current working directory to the specified one and then
```
make
make qemu
```
## Licensing
`mini-riscv-os` is freely redistributable under the two-clause BSD License.
Use of this source code is governed by a BSD-style license that can be found
in the `LICENSE` file.
## Reference
* [Adventures in RISC-V](https://matrix89.github.io/writes/writes/experiments-in-riscv/)
* [Xv6, a simple Unix-like teaching operating system](https://pdos.csail.mit.edu/6.828/2020/xv6.html)

178
doc/Background.md Normal file
View File

@@ -0,0 +1,178 @@
# 背景知識
file:///D:/ccc109/sp/10-riscv/pdf/RISC-V-Reader-Chinese-v2p1.pdf
101 頁
有三种标准的中断源:软件、时钟和外部来源。软件中断通过向内存映射寄存器中存数来触发,并通常用于由一个 hart 中断另一个 hart在其他架构中称为处理器间中断机制。当 hart 的时间比较器(一个名为 mtimecmp 的内存映射寄存器大于实时计数器mtime 时,会触发时钟中断。。外部中断由平台级中断控制器(大多数外部设备连接到这个中断控制器)引发。不同的硬件平台具有不同的内存映射并且需要中断控制器的不同特性,因此用于发出和消除这些中断的机制因平台而异。所有 RISC-V 系统的共同问题是如何处理异常和屏蔽中断,这是下一节的主题。
10.3 机器模式下的异常处理
八个控制状态寄存器CSR是机器模式下异常处理的必要部分
* mtvecMachine Trap Vector它保存发生异常时处理器需要跳转到的地址。
* mepcMachine Exception PC它指向发生异常的指令。
* mcauseMachine Exception Cause它指示发生异常的种类。
* mieMachine Interrupt Enable它指出处理器目前能处理和必须忽略的中断。
* mipMachine Interrupt Pending它列出目前正准备处理的中断。
* mtvalMachine Trap Value它保存了陷入trap的附加信息地址例外中出错的地址、发生非法指令例外的指令本身对于其他异常它的值为 0。
* mscratchMachine Scratch它暂时存放一个字大小的数据。
* mstatusMachine Status它保存全局中断使能以及许多其他的状态如图10.4 所示。
处理器在 M 模式下运行时,只有在全局中断使能位 mstatus.MIE 置 1 时才会产生中断.此外,每个中断在控制状态寄存器 mie 中都有自己的使能位。这些位在 mie 中的位置,对应于图 10.3 中的中断代码。例如mie[7]对应于 M 模式中的时钟中断。控制状态寄存器mip具有相同的布局并且它指示当前待处理的中断。将所有三个控制状态寄存器合在一起考虑如果 status.MIE = 1mie[7] = 1且 mip[7] = 1则可以处理机器的时钟中断。
当一个 hart 发生异常时,硬件自动经历如下的状态转换:
* 异常指令的 PC 被保存在 mepc 中PC 被设置为 mtvec。对于同步异常mepc 指向导致异常的指令;对于中断,它指向中断处理后应该恢复执行的位置。)
* mepc = PC; PC = mtvec
* 根据异常来源设置 mcause如图 10.3 所示),并将 mtval 设置为出错的地址或 者其它适用于特定异常的信息字。
* 把控制状态寄存器 mstatus 中的 MIE 位置零以禁用中断,并把先前的 MIE 值保留到 MPIE 中。
* 发生异常之前的权限模式保留在 mstatus 的 MPP 域中再把权限模式更改为M。图 10.5 显示了 MPP 域的编码(如果处理器仅实现 M 模式,则有效地跳过这个步骤)。
为避免覆盖整数寄存器中的内容,中断处理程序先在最开始用 mscratch 和整数寄存器(例如 a0中的值交换。通常软件会让 mscratch 包含指向附加临时内存空间的指针,处理程序用该指针来保存其主体中将会用到的整数寄存器。在主体执行之后,中断程序会恢复它保存到内存中的寄存器,然后再次使用 mscratch 和 a0 交换,将两个寄存器恢复到它们在发生异常之前的值。
最后,处理程序用 mret 指令M 模式特有的指令返回。mret 将 PC 设置为 mepc通过将 mstatus 的 MPIE 域复制到 MIE 来恢复之前的中断使能设置,并将权限模式设置为 mstatus 的 MPP 域中的值。这基本是前一段中描述的逆操作。
> mret: PC=mepc; mstatus:MIE=MPIE;
图 10.6 展示了遵循此模式的基本时钟中断处理程序的 RISC-V 汇编代码。它只对时间比较器执行了递增操作,然后继续执行之前的任务。更实际的时钟中断处理程序可能会调用调度程序,从而在任务之间切换。它是非抢占的,因此在处理程序的过程中中断会被禁用。不考虑这些限制条件的话,它就是一个只有一页的 RISC-V 中断处理程序的完整示例!
![](img/InterruptHandler.png)
图 10.6;简单的 RISC-V 时钟中断处理程序代码。代码中假定了全局中断已通过置位 mstatus.MIE 启
用;时钟中断已通过置位 mie[7]启用mtvec CSR 已设置为此处理程序的入口地址;而且 mscratch
CSR 已经设置为有 16 个字节用于保存寄存器的临时空间的地址。第一部分保存了五个寄存器,把 a0 保存在 mscratch 中a1 到 a4 保存在内存中。然后它检查 mcause 来读取异常的类别:如果 mcause<0 则是中断反之则是同步异常如果是中断就检查 mcause 的低位是否等于 7如果是就是 M 模式的时钟中断如果确定是时钟中断就给时间比较器加上 1000 个时钟周期于是下一个时钟中断会发生在大约 1000 个时钟周期之后最后一段恢复了 a0 a4 mscratch然后用 mret 指令返回
默认情况下发生所有异常不论在什么权限模式下的时候控制权都会被移交到M 模式的异常处理程序但是 Unix 系统中的大多数例外都应该进行 S 模式下的系统调用M 模式的异常处理程序可以将异常重新导向 S 模式但这些额外的操作会减慢大多数异常的处理速度因此RISC-V 提供了一种异常委托机制通过该机制可以选择性地将中断和同步异常交给 S 模式处理而完全绕过 M 模式
midelegMachine Interrupt Delegation机器中断委托CSR 控制将哪些中断委托给 S模式 mip mie 一样mideleg 中的每个位对应于图 10.3 中相同的异常例如mideleg[5]对应于 S 模式的时钟中断如果把它置位S 模式的时钟中断将会移交 S 模式的异常处理程序而不是 M 模式的异常处理程序
委托给 S 模式的任何中断都可以被 S 模式的软件屏蔽sieSupervisor InterruptEnable监管者中断使能 sipSupervisor Interrupt Pending监管者中断待处理CSR是 S 模式的控制状态寄存器他们是 mie mip 的子集它们有着和 M 模式下相同的布局但在 sie sip 中只有与由 mideleg 委托的中断对应的位才能读写那些没有被委派的中断对应的位始终为零
M 模式还可以通过 medeleg CSR 将同步异常委托给 S 模式该机制类似于刚才提到的中断委托 medeleg 中的位对应的不再是中断而是图 10.3 中的同步异常编码例如置上 medeleg[15]便会把 store page faultstore 过程中出现的缺页委托给 S 模式
请注意无论委派设置是怎样的发生异常时控制权都不会移交给权限更低的模式
M 模式下发生的异常总是在 M 模式下处理 S 模式下发生的异常根据具体的委派设置可能由 M 模式或 S 模式处理但永远不会由 U 模式处理
S 模式有几个异常处理 CSRsepcstvecscausesscratchstval sstatus它们执行与 10.2 中描述的 M 模式 CSR 相同的功能 10.9 显示了 sstatus 寄存器的布局
监管者异常返回指令 sret mret 的行为相同但它作用于 S 模式的异常处理 CSR而不是 M 模式的 CSR
S 模式处理例外的行为已和 M 模式非常相似如果 hart 接受了异常并且把它委派给了S 模式则硬件会原子地经历几个类似的状态转换其中用到了 S 模式而不是 M 模式的CSR
* 发生例外的指令的 PC 被存入 sepc PC 被设置为 stvec
* scause 按图 10.3 根据异常类型设置stval 被设置成出错的地址或者其它特定异常的信息字
* sstatus CSR 中的 SIE 置零屏蔽中断 SIE 之前的值被保存在 SPIE
* 发生例外时的权限模式被保存在 sstatus SPP 然后设置当前模式为 S 模式
##
* https://github.com/RISCV-on-Microsemi-FPGA/RVBM-BootLoader/blob/master/src/riscv_hal/startup.S
* https://github.com/RISCV-on-Microsemi-FPGA/RVBM-BootLoader/blob/master/src/riscv_hal/entry.S
* https://github.com/RISCV-on-Microsemi-FPGA/RVBM-BootLoader/blob/master/src/interrupts.c
```cpp
/******************************************************************************
* RISC-V interrupt handler for machine timer interrupts.
*****************************************************************************/
void handle_m_timer_interrupt(){
clear_csr(mie, MIP_MTIP);
add_10ms_to_mtimecmp();
SysTick_Handler();
// Re-enable the timer interrupt.
set_csr(mie, MIP_MTIP);
}
```
```cpp
/*------------------------------------------------------------------------------
* Count the number of elapsed milliseconds (SysTick_Handler is called every
* 10mS so the resolution will be 10ms). Rolls over every 49 days or so...
*
* Should be safe to read g_10ms_count from elsewhere.
*/
void SysTick_Handler(void)
{
g_10ms_count += 10;
/*
* For neatness, if we roll over, reset cleanly back to 0 so the count
* always goes up in proper 10s.
*/
if(g_10ms_count < 10)
g_10ms_count = 0;
}
```
```cpp
uintptr_t handle_trap(uintptr_t mcause, uintptr_t epc)
{
if (0){
// External Machine-Level Interrupt from PLIC
}else if ((mcause & MCAUSE_INT) && ((mcause & MCAUSE_CAUSE) == IRQ_M_EXT)) {
handle_m_ext_interrupt();
}else if ((mcause & MCAUSE_INT) && ((mcause & MCAUSE_CAUSE) == IRQ_M_TIMER)) {
handle_m_timer_interrupt();
}
else{
write(1, "trap\n", 5);
_exit(1 + mcause);
}
return epc;
}
```
start.s
```s
trap_entry:
addi sp, sp, -32*REGBYTES
...
csrr a0, mcause
csrr a1, mepc
mv a2, sp
jal handle_trap
csrw mepc, a0
# Remain in M-mode after mret
li t0, MSTATUS_MPP
csrs mstatus, t0
...
addi sp, sp, 32*REGBYTES
mret
```
* https://github.com/d0iasm/rvemu/blob/master/src/cpu.rs
```rust
// mret
// "The RISC-V Reader" book says:
// "Returns from a machine-mode exception handler. Sets the pc to
// CSRs[mepc], the privilege mode to CSRs[mstatus].MPP,
// CSRs[mstatus].MIE to CSRs[mstatus].MPIE, and CSRs[mstatus].MPIE
// to 1; and, if user mode is supported, sets CSRs[mstatus].MPP to 0".
```
* RISC-V-Reader-Chinese-v2p1.pdf
* mret ExceptionReturn(Machine)
* 机器模式异常返回(Machine-mode Exception Return). R-type, RV32I and RV64I 特权架构从机器模式异常处理程序返回 pc 设置为 CSRs[mepc], 将特权级设置成 CSRs[mstatus].MPP, CSRs[mstatus].MIE 置成 CSRs[mstatus].MPIE, 并且将 CSRs[mstatus].MPIE 1;并且如果支持用户模式则将 CSR [mstatus].MPP 设置为 0
```cpp
// set the machine-mode trap handler.
w_mtvec((reg_t)sys_timer);
```
```cpp
// Machine-mode interrupt vector
static inline void w_mtvec(reg_t x)
{
asm volatile("csrw mtvec, %0" : : "r" (x));
}
```

83
doc/Threads.md Normal file
View File

@@ -0,0 +1,83 @@
像是 07-thread 裏使用了 tcb_t
```cpp
/* Thread Control Block */
typedef struct {
void *stack;
void *orig_stack;
uint8_t in_use;
} tcb_t;
static tcb_t tasks[MAX_TASKS];
static int lastTask;
```
還有用了一大堆組合語言
```cpp
void __attribute__((naked)) thread_start()
{
lastTask = 0;
/* Reset APSR before context switch.
* Make sure we have a _clean_ PSR for the task.
*/
asm volatile("mov r0, #0\n"
"msr APSR_nzcvq, r0\n");
/* To bridge the variable in C and the register in ASM,
* move the task's stack pointer address into r0.
* http://www.ethernut.de/en/documents/arm-inline-asm.html
*/
asm volatile("mov r0, %0\n" : : "r" (tasks[lastTask].stack));
asm volatile("msr psp, r0\n"
"mov r0, #3\n"
"msr control, r0\n"
"isb\n");
/* This is how we simulate stack handling that pendsv_handler
* does. Thread_create sets 17 entries in stack, and the 9
* entries we pop here will be pushed back in pendsv_handler
* in the same order.
*
*
* pop {r4-r11, lr}
* ldr r0, [sp]
* stack
* offset -------
* | 16 | <- Reset value of PSR
* -------
* | 15 | <- Task entry
* -------
* | 14 | <- LR for task
* -------
* | ... | register
* ------- -------
* | 9 | <- Task argument ----> | r0 |
* psp after pop--< -------
* | 8 | <- EXC_RETURN ----> | lr |
* ------- -------
* | 7 | | r11 |
* ------- -------
* | ... | | ... |
* ------- -------
* | 0 | | r4 |
* psp -> ------- -------
*
* Instead of "pop {r0}", use "ldr r0, [sp]" to ensure consistent
* with the way how PendSV saves _old_ context[1].
*/
asm volatile("pop {r4-r11, lr}\n"
"ldr r0, [sp]\n");
/* Okay, we are ready to run first task, get address from
* stack[15]. We just pop 9 register so #24 comes from
* (15 - 9) * sizeof(entry of sp) = 6 * 4.
*/
asm volatile("ldr pc, [sp, #24]\n");
/* Never reach here */
while(1);
}
```

5
doc/Uart.md Normal file
View File

@@ -0,0 +1,5 @@
# Uart
* https://www.activexperts.com/serial-port-component/tutorials/uart/
* https://www.maxlinear.com/Files/Documents/Intro_To_UARTs.pdf
* https://twilco.github.io/riscv-from-scratch/2019/07/08/riscv-from-scratch-3.html

101
doc/freeRtosRef.md Normal file
View File

@@ -0,0 +1,101 @@
# FreeRTOS
* https://github.com/illustris/FreeRTOS-RISCV/blob/master/Source/portable/GCC/RISCV/port.c
```cpp
/*-----------------------------------------------------------*/
void vPortSysTickHandler( void )
{
prvSetNextTimerInterrupt();
/* Increment the RTOS tick. */
if( xTaskIncrementTick() != pdFALSE )
{
vTaskSwitchContext();
}
}
/*-----------------------------------------------------------*/
```
* https://github.com/illustris/FreeRTOS-RISCV/blob/master/Source/portable/GCC/RISCV/portasm.S
```s
vPortYield:
/*
* This routine can be called from outside of interrupt handler. This means
* interrupts may be enabled at this point. This is probably okay for registers and
* stack. However, "mepc" will be overwritten by the interrupt handler if a timer
* interrupt happens during the yield. To avoid this, prevent interrupts before starting.
* The write to mstatus in the restore context routine will enable interrupts after the
* mret. A more fine-grain lock may be possible.
*/
csrci mstatus, 8
portSAVE_CONTEXT
portSAVE_RA
jal vTaskSwitchContext
portRESTORE_CONTEXT
```
* https://github.com/illustris/FreeRTOS-RISCV/blob/master/Source/tasks.c
```cpp
void vTaskSwitchContext( void )
{
if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
{
/* The scheduler is currently suspended - do not allow a context
switch. */
xYieldPending = pdTRUE;
}
else
{
xYieldPending = pdFALSE;
traceTASK_SWITCHED_OUT();
#if ( configGENERATE_RUN_TIME_STATS == 1 )
{
#ifdef portALT_GET_RUN_TIME_COUNTER_VALUE
portALT_GET_RUN_TIME_COUNTER_VALUE( ulTotalRunTime );
#else
ulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE();
#endif
/* Add the amount of time the task has been running to the
accumulated time so far. The time the task started running was
stored in ulTaskSwitchedInTime. Note that there is no overflow
protection here so count values are only valid until the timer
overflows. The guard against negative values is to protect
against suspect run time stat counter implementations - which
are provided by the application, not the kernel. */
if( ulTotalRunTime > ulTaskSwitchedInTime )
{
pxCurrentTCB->ulRunTimeCounter += ( ulTotalRunTime - ulTaskSwitchedInTime );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
ulTaskSwitchedInTime = ulTotalRunTime;
}
#endif /* configGENERATE_RUN_TIME_STATS */
/* Check for stack overflow, if configured. */
taskCHECK_FOR_STACK_OVERFLOW();
/* Select a new task to run using either the generic C or port
optimised asm code. */
taskSELECT_HIGHEST_PRIORITY_TASK();
traceTASK_SWITCHED_IN();
#if ( configUSE_NEWLIB_REENTRANT == 1 )
{
/* Switch Newlib's _impure_ptr variable to point to the _reent
structure specific to this task. */
_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
}
#endif /* configUSE_NEWLIB_REENTRANT */
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

101
doc/xv6ref.md Normal file
View File

@@ -0,0 +1,101 @@
# xv6 參考
当 xv6 内核在 CPU 上执行时,可能会发生两种类型的陷阱:异常和设备中断。上一节概述了 CPU 对此类陷阱的响应。
当内核执行时,它指向汇编代码 kernelvec (kernel/kernelve.S10)。由于xv6已经在内核中因此kernelvec可以依赖于将`satp`设置为内核页表并依赖于引用有效内核堆栈的堆栈指针。kernelvec保存所有寄存器这样我们最终可以恢复中断的代码而不会干扰它。
***
kernelvec将寄存器保存在中断的内核线程的堆栈上这是有意义的因为寄存器值属于该线程。如果陷阱导致切换到不同的线程这一点尤其重要 - 在这种情况下,陷阱实际上会返回到新线程的堆栈上,将被中断线程的已保存寄存器安全地留在其堆栈上。
***
kernelvec在保存寄存器后跳转到kerneltrap(kernel/trap.c134)。kerneltrap为两种类型的陷阱做好准备设备中断和异常。它调用sdevintr(kernel/trap.c177)来检查和处理前者。如果陷阱不是设备中断,那么它就是一个异常,如果它发生在内核中,那总是一个致命的错误。
## supervisor mode & mret
File: start.c
```cpp
// entry.S jumps here in machine mode on stack0.
void
start()
{
// set M Previous Privilege mode to Supervisor, for mret.
unsigned long x = r_mstatus();
x &= ~MSTATUS_MPP_MASK;
x |= MSTATUS_MPP_S;
w_mstatus(x);
// set M Exception Program Counter to main, for mret.
// requires gcc -mcmodel=medany
w_mepc((uint64)main);
// disable paging for now.
w_satp(0);
// delegate all interrupts and exceptions to supervisor mode.
w_medeleg(0xffff);
w_mideleg(0xffff);
// ask for clock interrupts.
timerinit();
// keep each CPU's hartid in its tp register, for cpuid().
int id = r_mhartid();
w_tp(id);
// switch to supervisor mode and jump to main().
asm volatile("mret");
}
```
File: main.c
```cpp
#include "types.h"
#include "param.h"
#include "memlayout.h"
#include "riscv.h"
#include "defs.h"
volatile static int started = 0;
// start() jumps here in supervisor mode on all CPUs.
void
main()
{
if(cpuid() == 0){
consoleinit();
printfinit();
printf("\n");
printf("xv6 kernel is booting\n");
printf("\n");
kinit(); // physical page allocator
kvminit(); // create kernel page table
kvminithart(); // turn on paging
procinit(); // process table
trapinit(); // trap vectors
trapinithart(); // install kernel trap vector
plicinit(); // set up interrupt controller
plicinithart(); // ask PLIC for device interrupts
binit(); // buffer cache
iinit(); // inode cache
fileinit(); // file table
virtio_disk_init(); // emulated hard disk
userinit(); // first user process
__sync_synchronize();
started = 1;
} else {
while(started == 0)
;
__sync_synchronize();
printf("hart %d starting\n", cpuid());
kvminithart(); // turn on paging
trapinithart(); // install kernel trap vector
plicinithart(); // ask PLIC for device interrupts
}
scheduler();
}
```