commit 5eec97bef957200db55cf2092cd3dd65376a57d9 Author: ccckmit Date: Sat Nov 14 11:31:33 2020 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0eedd1f --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*~ +*.o +*.elf +*.bin +*.list +*.swp +*.bak +bak + diff --git a/01-HelloOs/Makefile b/01-HelloOs/Makefile new file mode 100644 index 0000000..02f0d22 --- /dev/null +++ b/01-HelloOs/Makefile @@ -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 diff --git a/01-HelloOs/README.md b/01-HelloOs/README.md new file mode 100644 index 0000000..01cc50c --- /dev/null +++ b/01-HelloOs/README.md @@ -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! +``` diff --git a/01-HelloOs/os.c b/01-HelloOs/os.c new file mode 100644 index 0000000..6a51dc9 --- /dev/null +++ b/01-HelloOs/os.c @@ -0,0 +1,22 @@ +#include + +// 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; +} diff --git a/01-HelloOs/os.ld b/01-HelloOs/os.ld new file mode 100644 index 0000000..9c1a7df --- /dev/null +++ b/01-HelloOs/os.ld @@ -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)); +} diff --git a/01-HelloOs/start.s b/01-HelloOs/start.s new file mode 100644 index 0000000..aec0240 --- /dev/null +++ b/01-HelloOs/start.s @@ -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 diff --git a/02-ContextSwitch/Makefile b/02-ContextSwitch/Makefile new file mode 100644 index 0000000..8ba6c3c --- /dev/null +++ b/02-ContextSwitch/Makefile @@ -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 diff --git a/02-ContextSwitch/README.md b/02-ContextSwitch/README.md new file mode 100644 index 0000000..db1e0dc --- /dev/null +++ b/02-ContextSwitch/README.md @@ -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 +``` diff --git a/02-ContextSwitch/lib.c b/02-ContextSwitch/lib.c new file mode 100644 index 0000000..6d05c4e --- /dev/null +++ b/02-ContextSwitch/lib.c @@ -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++); +} diff --git a/02-ContextSwitch/lib.h b/02-ContextSwitch/lib.h new file mode 100644 index 0000000..cdcb753 --- /dev/null +++ b/02-ContextSwitch/lib.h @@ -0,0 +1,12 @@ +#ifndef __LIB_H__ +#define __LIB_H__ + +#include "riscv.h" +#include +#include + +extern void lib_delay(volatile int count); +extern int lib_putc(char ch); +extern void lib_puts(char *s); + +#endif diff --git a/02-ContextSwitch/os.c b/02-ContextSwitch/os.c new file mode 100644 index 0000000..d2f7d81 --- /dev/null +++ b/02-ContextSwitch/os.c @@ -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; +} + diff --git a/02-ContextSwitch/os.h b/02-ContextSwitch/os.h new file mode 100644 index 0000000..3c308d7 --- /dev/null +++ b/02-ContextSwitch/os.h @@ -0,0 +1,9 @@ +#ifndef __OS_H__ +#define __OS_H__ + +#include "riscv.h" +#include "lib.h" + +extern int os_main(void); + +#endif diff --git a/02-ContextSwitch/os.ld b/02-ContextSwitch/os.ld new file mode 100644 index 0000000..9c1a7df --- /dev/null +++ b/02-ContextSwitch/os.ld @@ -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)); +} diff --git a/02-ContextSwitch/riscv.h b/02-ContextSwitch/riscv.h new file mode 100644 index 0000000..d5772ac --- /dev/null +++ b/02-ContextSwitch/riscv.h @@ -0,0 +1,35 @@ +#ifndef __RISCV_H__ +#define __RISCV_H__ + +#include + +#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 diff --git a/02-ContextSwitch/start.s b/02-ContextSwitch/start.s new file mode 100644 index 0000000..aec0240 --- /dev/null +++ b/02-ContextSwitch/start.s @@ -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 diff --git a/02-ContextSwitch/sys.h b/02-ContextSwitch/sys.h new file mode 100644 index 0000000..4355ab3 --- /dev/null +++ b/02-ContextSwitch/sys.h @@ -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 diff --git a/02-ContextSwitch/sys.s b/02-ContextSwitch/sys.s new file mode 100644 index 0000000..3aab89b --- /dev/null +++ b/02-ContextSwitch/sys.s @@ -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) diff --git a/03-MultiTasking/Makefile b/03-MultiTasking/Makefile new file mode 100644 index 0000000..9d97198 --- /dev/null +++ b/03-MultiTasking/Makefile @@ -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 diff --git a/03-MultiTasking/README.md b/03-MultiTasking/README.md new file mode 100644 index 0000000..38dd11c --- /dev/null +++ b/03-MultiTasking/README.md @@ -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 +``` diff --git a/03-MultiTasking/lib.c b/03-MultiTasking/lib.c new file mode 100644 index 0000000..6d05c4e --- /dev/null +++ b/03-MultiTasking/lib.c @@ -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++); +} diff --git a/03-MultiTasking/lib.h b/03-MultiTasking/lib.h new file mode 100644 index 0000000..cdcb753 --- /dev/null +++ b/03-MultiTasking/lib.h @@ -0,0 +1,12 @@ +#ifndef __LIB_H__ +#define __LIB_H__ + +#include "riscv.h" +#include +#include + +extern void lib_delay(volatile int count); +extern int lib_putc(char ch); +extern void lib_puts(char *s); + +#endif diff --git a/03-MultiTasking/os.c b/03-MultiTasking/os.c new file mode 100644 index 0000000..f0c0228 --- /dev/null +++ b/03-MultiTasking/os.c @@ -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; +} + diff --git a/03-MultiTasking/os.h b/03-MultiTasking/os.h new file mode 100644 index 0000000..355a523 --- /dev/null +++ b/03-MultiTasking/os.h @@ -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 diff --git a/03-MultiTasking/os.ld b/03-MultiTasking/os.ld new file mode 100644 index 0000000..9c1a7df --- /dev/null +++ b/03-MultiTasking/os.ld @@ -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)); +} diff --git a/03-MultiTasking/riscv.h b/03-MultiTasking/riscv.h new file mode 100644 index 0000000..d5772ac --- /dev/null +++ b/03-MultiTasking/riscv.h @@ -0,0 +1,35 @@ +#ifndef __RISCV_H__ +#define __RISCV_H__ + +#include + +#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 diff --git a/03-MultiTasking/start.s b/03-MultiTasking/start.s new file mode 100644 index 0000000..aec0240 --- /dev/null +++ b/03-MultiTasking/start.s @@ -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 diff --git a/03-MultiTasking/sys.h b/03-MultiTasking/sys.h new file mode 100644 index 0000000..4355ab3 --- /dev/null +++ b/03-MultiTasking/sys.h @@ -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 diff --git a/03-MultiTasking/sys.s b/03-MultiTasking/sys.s new file mode 100644 index 0000000..3aab89b --- /dev/null +++ b/03-MultiTasking/sys.s @@ -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) diff --git a/03-MultiTasking/task.c b/03-MultiTasking/task.c new file mode 100644 index 0000000..3316b00 --- /dev/null +++ b/03-MultiTasking/task.c @@ -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); +} diff --git a/03-MultiTasking/task.h b/03-MultiTasking/task.h new file mode 100644 index 0000000..1ac7e39 --- /dev/null +++ b/03-MultiTasking/task.h @@ -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 diff --git a/03-MultiTasking/user.c b/03-MultiTasking/user.c new file mode 100644 index 0000000..5dfdad6 --- /dev/null +++ b/03-MultiTasking/user.c @@ -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); +} diff --git a/04-TimerInterrupt/Makefile b/04-TimerInterrupt/Makefile new file mode 100644 index 0000000..b1977eb --- /dev/null +++ b/04-TimerInterrupt/Makefile @@ -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 diff --git a/04-TimerInterrupt/README.md b/04-TimerInterrupt/README.md new file mode 100644 index 0000000..93887ac --- /dev/null +++ b/04-TimerInterrupt/README.md @@ -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 +``` diff --git a/04-TimerInterrupt/lib.c b/04-TimerInterrupt/lib.c new file mode 100644 index 0000000..3538b9d --- /dev/null +++ b/04-TimerInterrupt/lib.c @@ -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; +} diff --git a/04-TimerInterrupt/lib.h b/04-TimerInterrupt/lib.h new file mode 100644 index 0000000..e338c8e --- /dev/null +++ b/04-TimerInterrupt/lib.h @@ -0,0 +1,17 @@ +#ifndef __LIB_H__ +#define __LIB_H__ + +#include "riscv.h" +#include +#include + +#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 diff --git a/04-TimerInterrupt/os.c b/04-TimerInterrupt/os.c new file mode 100644 index 0000000..eb57075 --- /dev/null +++ b/04-TimerInterrupt/os.c @@ -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; +} + diff --git a/04-TimerInterrupt/os.h b/04-TimerInterrupt/os.h new file mode 100644 index 0000000..4058445 --- /dev/null +++ b/04-TimerInterrupt/os.h @@ -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 diff --git a/04-TimerInterrupt/os.ld b/04-TimerInterrupt/os.ld new file mode 100644 index 0000000..9c1a7df --- /dev/null +++ b/04-TimerInterrupt/os.ld @@ -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)); +} diff --git a/04-TimerInterrupt/riscv.h b/04-TimerInterrupt/riscv.h new file mode 100644 index 0000000..27277bb --- /dev/null +++ b/04-TimerInterrupt/riscv.h @@ -0,0 +1,116 @@ +#ifndef __RISCV_H__ +#define __RISCV_H__ + +#include + +#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 diff --git a/04-TimerInterrupt/start.s b/04-TimerInterrupt/start.s new file mode 100644 index 0000000..aec0240 --- /dev/null +++ b/04-TimerInterrupt/start.s @@ -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 diff --git a/04-TimerInterrupt/sys.h b/04-TimerInterrupt/sys.h new file mode 100644 index 0000000..4355ab3 --- /dev/null +++ b/04-TimerInterrupt/sys.h @@ -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 diff --git a/04-TimerInterrupt/sys.s b/04-TimerInterrupt/sys.s new file mode 100644 index 0000000..cf0fc4c --- /dev/null +++ b/04-TimerInterrupt/sys.s @@ -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) diff --git a/04-TimerInterrupt/timer.c b/04-TimerInterrupt/timer.c new file mode 100644 index 0000000..ed34982 --- /dev/null +++ b/04-TimerInterrupt/timer.c @@ -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(); +} diff --git a/04-TimerInterrupt/timer.h b/04-TimerInterrupt/timer.h new file mode 100644 index 0000000..73b9f3a --- /dev/null +++ b/04-TimerInterrupt/timer.h @@ -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 diff --git a/05-Preemptive/Makefile b/05-Preemptive/Makefile new file mode 100644 index 0000000..778958d --- /dev/null +++ b/05-Preemptive/Makefile @@ -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 diff --git a/05-Preemptive/README.md b/05-Preemptive/README.md new file mode 100644 index 0000000..fe2ec46 --- /dev/null +++ b/05-Preemptive/README.md @@ -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 +``` diff --git a/05-Preemptive/lib.c b/05-Preemptive/lib.c new file mode 100644 index 0000000..3538b9d --- /dev/null +++ b/05-Preemptive/lib.c @@ -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; +} diff --git a/05-Preemptive/lib.h b/05-Preemptive/lib.h new file mode 100644 index 0000000..e338c8e --- /dev/null +++ b/05-Preemptive/lib.h @@ -0,0 +1,17 @@ +#ifndef __LIB_H__ +#define __LIB_H__ + +#include "riscv.h" +#include +#include + +#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 diff --git a/05-Preemptive/os.c b/05-Preemptive/os.c new file mode 100644 index 0000000..be5b1b2 --- /dev/null +++ b/05-Preemptive/os.c @@ -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; +} + diff --git a/05-Preemptive/os.h b/05-Preemptive/os.h new file mode 100644 index 0000000..2c802d9 --- /dev/null +++ b/05-Preemptive/os.h @@ -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 diff --git a/05-Preemptive/os.ld b/05-Preemptive/os.ld new file mode 100644 index 0000000..9c1a7df --- /dev/null +++ b/05-Preemptive/os.ld @@ -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)); +} diff --git a/05-Preemptive/riscv.h b/05-Preemptive/riscv.h new file mode 100644 index 0000000..27277bb --- /dev/null +++ b/05-Preemptive/riscv.h @@ -0,0 +1,116 @@ +#ifndef __RISCV_H__ +#define __RISCV_H__ + +#include + +#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 diff --git a/05-Preemptive/start.s b/05-Preemptive/start.s new file mode 100644 index 0000000..aec0240 --- /dev/null +++ b/05-Preemptive/start.s @@ -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 diff --git a/05-Preemptive/sys.h b/05-Preemptive/sys.h new file mode 100644 index 0000000..4355ab3 --- /dev/null +++ b/05-Preemptive/sys.h @@ -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 diff --git a/05-Preemptive/sys.s b/05-Preemptive/sys.s new file mode 100644 index 0000000..cf0fc4c --- /dev/null +++ b/05-Preemptive/sys.s @@ -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) diff --git a/05-Preemptive/task.c b/05-Preemptive/task.c new file mode 100644 index 0000000..3316b00 --- /dev/null +++ b/05-Preemptive/task.c @@ -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); +} diff --git a/05-Preemptive/task.h b/05-Preemptive/task.h new file mode 100644 index 0000000..1ac7e39 --- /dev/null +++ b/05-Preemptive/task.h @@ -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 diff --git a/05-Preemptive/timer.c b/05-Preemptive/timer.c new file mode 100644 index 0000000..d77a134 --- /dev/null +++ b/05-Preemptive/timer.c @@ -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(); +} diff --git a/05-Preemptive/timer.h b/05-Preemptive/timer.h new file mode 100644 index 0000000..b971c37 --- /dev/null +++ b/05-Preemptive/timer.h @@ -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 diff --git a/05-Preemptive/user.c b/05-Preemptive/user.c new file mode 100644 index 0000000..353d98a --- /dev/null +++ b/05-Preemptive/user.c @@ -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); +} diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..d5aa51a --- /dev/null +++ b/AUTHORS @@ -0,0 +1,5 @@ +mini-riscv-os is written by: + Chung-Chen Chen + +Copyrighted by: + National Quemoy University, Taiwan diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..dd6e6f3 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a1fe130 --- /dev/null +++ b/README.md @@ -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) diff --git a/doc/Background.md b/doc/Background.md new file mode 100644 index 0000000..07119f1 --- /dev/null +++ b/doc/Background.md @@ -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)是机器模式下异常处理的必要部分: + +* mtvec(Machine Trap Vector)它保存发生异常时处理器需要跳转到的地址。 +* mepc(Machine Exception PC)它指向发生异常的指令。 +* mcause(Machine Exception Cause)它指示发生异常的种类。 +* mie(Machine Interrupt Enable)它指出处理器目前能处理和必须忽略的中断。 +* mip(Machine Interrupt Pending)它列出目前正准备处理的中断。 +* mtval(Machine Trap Value)它保存了陷入(trap)的附加信息:地址例外中出错的地址、发生非法指令例外的指令本身,对于其他异常,它的值为 0。 +* mscratch(Machine Scratch)它暂时存放一个字大小的数据。 +* mstatus(Machine Status)它保存全局中断使能,以及许多其他的状态,如图10.4 所示。 + +处理器在 M 模式下运行时,只有在全局中断使能位 mstatus.MIE 置 1 时才会产生中断.此外,每个中断在控制状态寄存器 mie 中都有自己的使能位。这些位在 mie 中的位置,对应于图 10.3 中的中断代码。例如,mie[7]对应于 M 模式中的时钟中断。控制状态寄存器mip具有相同的布局,并且它指示当前待处理的中断。将所有三个控制状态寄存器合在一起考虑,如果 status.MIE = 1,mie[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 模式。 + +mideleg(Machine Interrupt Delegation,机器中断委托)CSR 控制将哪些中断委托给 S模式。与 mip 和 mie 一样,mideleg 中的每个位对应于图 10.3 中相同的异常。例如,mideleg[5]对应于 S 模式的时钟中断,如果把它置位,S 模式的时钟中断将会移交 S 模式的异常处理程序,而不是 M 模式的异常处理程序。 + +委托给 S 模式的任何中断都可以被 S 模式的软件屏蔽。sie(Supervisor InterruptEnable,监管者中断使能)和 sip(Supervisor Interrupt Pending,监管者中断待处理)CSR是 S 模式的控制状态寄存器,他们是 mie 和 mip 的子集。它们有着和 M 模式下相同的布局,但在 sie 和 sip 中只有与由 mideleg 委托的中断对应的位才能读写。那些没有被委派的中断对应的位始终为零。 + +M 模式还可以通过 medeleg CSR 将同步异常委托给 S 模式。该机制类似于刚才提到的中断委托,但 medeleg 中的位对应的不再是中断,而是图 10.3 中的同步异常编码。例如,置上 medeleg[15]便会把 store page fault(store 过程中出现的缺页)委托给 S 模式。 + +请注意,无论委派设置是怎样的,发生异常时控制权都不会移交给权限更低的模式。 + +在 M 模式下发生的异常总是在 M 模式下处理。在 S 模式下发生的异常,根据具体的委派设置,可能由 M 模式或 S 模式处理,但永远不会由 U 模式处理。 + +S 模式有几个异常处理 CSR:sepc、stvec、scause、sscratch、stval 和 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)); +} +``` + diff --git a/doc/Threads.md b/doc/Threads.md new file mode 100644 index 0000000..91a0582 --- /dev/null +++ b/doc/Threads.md @@ -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); +} +``` \ No newline at end of file diff --git a/doc/Uart.md b/doc/Uart.md new file mode 100644 index 0000000..f7519b8 --- /dev/null +++ b/doc/Uart.md @@ -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 diff --git a/doc/freeRtosRef.md b/doc/freeRtosRef.md new file mode 100644 index 0000000..005fe75 --- /dev/null +++ b/doc/freeRtosRef.md @@ -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 */ + } +} +``` diff --git a/doc/img/InterruptHandler.png b/doc/img/InterruptHandler.png new file mode 100644 index 0000000..dfefc37 Binary files /dev/null and b/doc/img/InterruptHandler.png differ diff --git a/doc/xv6ref.md b/doc/xv6ref.md new file mode 100644 index 0000000..bc6886f --- /dev/null +++ b/doc/xv6ref.md @@ -0,0 +1,101 @@ +# xv6 參考 + +当 xv6 内核在 CPU 上执行时,可能会发生两种类型的陷阱:异常和设备中断。上一节概述了 CPU 对此类陷阱的响应。 + +当内核执行时,它指向汇编代码 kernelvec (kernel/kernelve.S:10)。由于xv6已经在内核中,因此kernelvec可以依赖于将`satp`设置为内核页表,并依赖于引用有效内核堆栈的堆栈指针。kernelvec保存所有寄存器,这样我们最终可以恢复中断的代码,而不会干扰它。 + + +*** +kernelvec将寄存器保存在中断的内核线程的堆栈上,这是有意义的,因为寄存器值属于该线程。如果陷阱导致切换到不同的线程,这一点尤其重要 - 在这种情况下,陷阱实际上会返回到新线程的堆栈上,将被中断线程的已保存寄存器安全地留在其堆栈上。 +*** + +kernelvec在保存寄存器后跳转到kerneltrap(kernel/trap.c:134)。kerneltrap为两种类型的陷阱做好准备:设备中断和异常。它调用sdevintr(kernel/trap.c:177)来检查和处理前者。如果陷阱不是设备中断,那么它就是一个异常,如果它发生在内核中,那总是一个致命的错误。 + +## 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(); +} +``` \ No newline at end of file