├── .DS_Store ├── .gitignore ├── 01-HelloOs ├── Makefile ├── README.md ├── os.c ├── os.ld └── start.s ├── 02-ContextSwitch ├── Makefile ├── README.md ├── lib.c ├── lib.h ├── os.c ├── os.h ├── os.ld ├── riscv.h ├── start.s ├── sys.h └── sys.s ├── 03-MultiTasking ├── Makefile ├── README.md ├── lib.c ├── lib.h ├── os.c ├── os.h ├── os.ld ├── riscv.h ├── start.s ├── sys.h ├── sys.s ├── task.c ├── task.h └── user.c ├── 04-TimerInterrupt ├── Makefile ├── README.md ├── lib.c ├── lib.h ├── os.c ├── os.h ├── os.ld ├── riscv.h ├── start.s ├── sys.h ├── sys.s ├── timer.c └── timer.h ├── 05-Preemptive ├── Makefile ├── README.md ├── lib.c ├── lib.h ├── os.c ├── os.h ├── os.ld ├── riscv.h ├── start.s ├── sys.h ├── sys.s ├── task.c ├── task.h ├── timer.c ├── timer.h ├── trap.c └── user.c ├── 06-Spinlock ├── Makefile ├── README.md ├── gdbinit ├── lib.c ├── lib.h ├── lock.c ├── os.c ├── os.h ├── os.ld ├── riscv.h ├── start.s ├── sys.h ├── sys.s ├── task.c ├── task.h ├── timer.c ├── timer.h ├── trap.c └── user.c ├── 07-ExterInterrupt ├── Makefile ├── README.md ├── gdbinit ├── lib.c ├── lib.h ├── lock.c ├── os.c ├── os.h ├── os.ld ├── plic.c ├── riscv.h ├── start.s ├── sys.h ├── sys.s ├── task.c ├── task.h ├── timer.c ├── timer.h ├── trap.c └── user.c ├── 08-BlockDeviceDriver ├── Makefile ├── README.md ├── gdbinit ├── lib.c ├── lib.h ├── lock.c ├── os.c ├── os.h ├── os.ld ├── plic.c ├── riscv.h ├── start.s ├── string.c ├── string.h ├── sys.h ├── sys.s ├── task.c ├── task.h ├── timer.c ├── timer.h ├── trap.c ├── types.h ├── user.c ├── virtio.c └── virtio.h ├── 09-MemoryAllocator ├── Makefile ├── README.md ├── gdbinit ├── include │ ├── lib.h │ ├── os.h │ ├── riscv.h │ ├── string.h │ ├── sys.h │ ├── task.h │ ├── timer.h │ ├── types.h │ └── virtio.h ├── os.ld └── src │ ├── alloc.c │ ├── lib.c │ ├── lock.c │ ├── mem.s │ ├── os.c │ ├── plic.c │ ├── start.s │ ├── string.c │ ├── sys.s │ ├── task.c │ ├── timer.c │ ├── trap.c │ ├── user.c │ └── virtio.c ├── 10-SystemCall ├── .DS_Store ├── Makefile ├── README.md ├── gdbinit ├── include │ ├── .DS_Store │ ├── lib.h │ ├── os.h │ ├── riscv.h │ ├── string.h │ ├── sys.h │ ├── task.h │ ├── timer.h │ ├── types.h │ ├── user_api.h │ └── virtio.h ├── os.ld └── src │ ├── .DS_Store │ ├── alloc.c │ ├── lib.c │ ├── lock.c │ ├── mem.s │ ├── os.c │ ├── plic.c │ ├── start.s │ ├── string.c │ ├── sys.s │ ├── syscall.c │ ├── task.c │ ├── timer.c │ ├── trap.c │ ├── user.c │ ├── usys.s │ └── virtio.c ├── A1-Input ├── Makefile ├── README.md ├── linux.md ├── os.c ├── os.ld └── start.s ├── AUTHORS ├── LICENSE ├── README.md ├── bug.md ├── doc ├── ref │ ├── Background.md │ ├── InterruptHandler.png │ ├── Threads.md │ ├── Uart.md │ ├── freeRtosRef.md │ ├── seL4.md │ └── xv6ref.md └── tw │ ├── 01-HelloOs.md │ ├── 02-ContextSwitch.md │ ├── 03-MultiTasking.md │ ├── 04-TimerInterrupt.md │ ├── 05-Preemptive.md │ ├── 06-Spinlock.md │ ├── 07-ExterInterrupt.md │ ├── 08-BlockDeviceDriver.md │ ├── 09-MemoryAllocator.md │ └── README.md └── logo.png /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccriscv/mini-riscv-os/70443b0ab3bf186c47b42127a0ffe31ad417fb91/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.o 3 | *.elf 4 | *.bin 5 | *.list 6 | *.swp 7 | *.bak 8 | bak 9 | .vscode 10 | *.img 11 | *.dsk 12 | 13 | -------------------------------------------------------------------------------- /01-HelloOs/Makefile: -------------------------------------------------------------------------------- 1 | CC = riscv64-unknown-elf-gcc 2 | CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 3 | 4 | QEMU = qemu-system-riscv32 5 | QFLAGS = -nographic -smp 4 -machine virt -bios none 6 | 7 | OBJDUMP = riscv64-unknown-elf-objdump 8 | 9 | all: os.elf 10 | 11 | os.elf: start.s os.c 12 | $(CC) $(CFLAGS) -T os.ld -o os.elf $^ 13 | 14 | qemu: $(TARGET) 15 | @qemu-system-riscv32 -M ? | grep virt >/dev/null || exit 16 | @echo "Press Ctrl-A and then X to exit QEMU" 17 | $(QEMU) $(QFLAGS) -kernel os.elf 18 | 19 | clean: 20 | rm -f *.elf 21 | -------------------------------------------------------------------------------- /01-HelloOs/README.md: -------------------------------------------------------------------------------- 1 | # 01-Hello OS 2 | 3 | ## Build & Run 4 | 5 | ```sh 6 | user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/01-HelloOs (master) 7 | $ make clean 8 | rm -f *.elf 9 | 10 | user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/01-HelloOs (master) 11 | $ make 12 | riscv64-unknown-elf-gcc -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -T os.ld -o os.elf start.s os.c 13 | 14 | user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/01-HelloOs (master) 15 | $ make qemu 16 | Press Ctrl-A and then X to exit QEMU 17 | qemu-system-riscv32 -nographic -smp 4 -machine virt -bios none -kernel os.elf 18 | Hello OS! 19 | ``` 20 | -------------------------------------------------------------------------------- /01-HelloOs/os.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define UART 0x10000000 4 | #define UART_THR (uint8_t*)(UART+0x00) // THR:transmitter holding register 5 | #define UART_LSR (uint8_t*)(UART+0x05) // LSR:line status register 6 | #define UART_LSR_EMPTY_MASK 0x40 // LSR Bit 6: Transmitter empty; both the THR and LSR are empty 7 | 8 | int lib_putc(char ch) { 9 | while ((*UART_LSR & UART_LSR_EMPTY_MASK) == 0); 10 | return *UART_THR = ch; 11 | } 12 | 13 | void lib_puts(char *s) { 14 | while (*s) lib_putc(*s++); 15 | } 16 | 17 | int os_main(void) 18 | { 19 | lib_puts("Hello OS!\n"); 20 | while (1) {} 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /01-HelloOs/os.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_ARCH( "riscv" ) 2 | 3 | ENTRY( _start ) 4 | 5 | MEMORY 6 | { 7 | ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M 8 | } 9 | 10 | PHDRS 11 | { 12 | text PT_LOAD; 13 | data PT_LOAD; 14 | bss PT_LOAD; 15 | } 16 | 17 | SECTIONS 18 | { 19 | .text : { 20 | PROVIDE(_text_start = .); 21 | *(.text.init) *(.text .text.*) 22 | PROVIDE(_text_end = .); 23 | } >ram AT>ram :text 24 | 25 | .rodata : { 26 | PROVIDE(_rodata_start = .); 27 | *(.rodata .rodata.*) 28 | PROVIDE(_rodata_end = .); 29 | } >ram AT>ram :text 30 | 31 | .data : { 32 | . = ALIGN(4096); 33 | PROVIDE(_data_start = .); 34 | *(.sdata .sdata.*) *(.data .data.*) 35 | PROVIDE(_data_end = .); 36 | } >ram AT>ram :data 37 | 38 | .bss :{ 39 | PROVIDE(_bss_start = .); 40 | *(.sbss .sbss.*) *(.bss .bss.*) 41 | PROVIDE(_bss_end = .); 42 | } >ram AT>ram :bss 43 | 44 | PROVIDE(_memory_start = ORIGIN(ram)); 45 | PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram)); 46 | } 47 | -------------------------------------------------------------------------------- /01-HelloOs/start.s: -------------------------------------------------------------------------------- 1 | .equ STACK_SIZE, 8192 2 | 3 | .global _start 4 | 5 | _start: 6 | csrr a0, mhartid # 讀取核心代號 7 | bnez a0, park # 若不是 0 號核心,跳到 park 停止 8 | la sp, stacks + STACK_SIZE # 0 號核心設定堆疊 9 | j os_main # 0 號核心跳到主程式 os_main 10 | 11 | park: 12 | wfi 13 | j park 14 | 15 | stacks: 16 | .skip STACK_SIZE # 分配堆疊空間 17 | -------------------------------------------------------------------------------- /02-ContextSwitch/Makefile: -------------------------------------------------------------------------------- 1 | CC = riscv64-unknown-elf-gcc 2 | CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 3 | 4 | QEMU = qemu-system-riscv32 5 | QFLAGS = -nographic -smp 4 -machine virt -bios none 6 | 7 | OBJDUMP = riscv64-unknown-elf-objdump 8 | 9 | all: os.elf 10 | 11 | os.elf: start.s sys.s lib.c os.c 12 | $(CC) $(CFLAGS) -T os.ld -o os.elf $^ 13 | 14 | qemu: $(TARGET) 15 | @qemu-system-riscv32 -M ? | grep virt >/dev/null || exit 16 | @echo "Press Ctrl-A and then X to exit QEMU" 17 | $(QEMU) $(QFLAGS) -kernel os.elf 18 | 19 | clean: 20 | rm -f *.elf 21 | -------------------------------------------------------------------------------- /02-ContextSwitch/README.md: -------------------------------------------------------------------------------- 1 | # 02-ContextSwitch 2 | 3 | ## Build & Run 4 | 5 | ```sh 6 | user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/02-ContextSwitch (master) 7 | $ make clean 8 | rm -f *.elf 9 | 10 | user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/02-ContextSwitch (master) 11 | $ make 12 | 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 13 | 14 | user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/02-ContextSwitch (master) 15 | $ make qemu 16 | Press Ctrl-A and then X to exit QEMU 17 | qemu-system-riscv32 -nographic -smp 4 -machine virt -bios none -kernel os.elf 18 | OS start 19 | Task0: Context Switch Success ! 20 | QEMU: Terminated 21 | ``` 22 | -------------------------------------------------------------------------------- /02-ContextSwitch/lib.c: -------------------------------------------------------------------------------- 1 | #include "lib.h" 2 | 3 | void lib_delay(volatile int count) 4 | { 5 | count *= 50000; 6 | while (count--); 7 | } 8 | 9 | int lib_putc(char ch) { 10 | while ((*UART_LSR & UART_LSR_EMPTY_MASK) == 0); 11 | return *UART_THR = ch; 12 | } 13 | 14 | void lib_puts(char *s) { 15 | while (*s) lib_putc(*s++); 16 | } 17 | -------------------------------------------------------------------------------- /02-ContextSwitch/lib.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIB_H__ 2 | #define __LIB_H__ 3 | 4 | #include "riscv.h" 5 | #include 6 | #include 7 | 8 | extern void lib_delay(volatile int count); 9 | extern int lib_putc(char ch); 10 | extern void lib_puts(char *s); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /02-ContextSwitch/os.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | 3 | #define STACK_SIZE 1024 4 | reg_t task0_stack[STACK_SIZE]; 5 | struct context ctx_os; 6 | struct context ctx_task; 7 | 8 | extern void sys_switch(); 9 | 10 | void user_task0(void) 11 | { 12 | lib_puts("Task0: Context Switch Success !\n"); 13 | while (1) {} // stop here. 14 | } 15 | 16 | int os_main(void) 17 | { 18 | lib_puts("OS start\n"); 19 | ctx_task.ra = (reg_t) user_task0; 20 | ctx_task.sp = (reg_t) &task0_stack[STACK_SIZE-1]; 21 | sys_switch(&ctx_os, &ctx_task); 22 | return 0; 23 | } 24 | 25 | -------------------------------------------------------------------------------- /02-ContextSwitch/os.h: -------------------------------------------------------------------------------- 1 | #ifndef __OS_H__ 2 | #define __OS_H__ 3 | 4 | #include "riscv.h" 5 | #include "lib.h" 6 | 7 | extern int os_main(void); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /02-ContextSwitch/os.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_ARCH( "riscv" ) 2 | 3 | ENTRY( _start ) 4 | 5 | MEMORY 6 | { 7 | ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M 8 | } 9 | 10 | PHDRS 11 | { 12 | text PT_LOAD; 13 | data PT_LOAD; 14 | bss PT_LOAD; 15 | } 16 | 17 | SECTIONS 18 | { 19 | .text : { 20 | PROVIDE(_text_start = .); 21 | *(.text.init) *(.text .text.*) 22 | PROVIDE(_text_end = .); 23 | } >ram AT>ram :text 24 | 25 | .rodata : { 26 | PROVIDE(_rodata_start = .); 27 | *(.rodata .rodata.*) 28 | PROVIDE(_rodata_end = .); 29 | } >ram AT>ram :text 30 | 31 | .data : { 32 | . = ALIGN(4096); 33 | PROVIDE(_data_start = .); 34 | *(.sdata .sdata.*) *(.data .data.*) 35 | PROVIDE(_data_end = .); 36 | } >ram AT>ram :data 37 | 38 | .bss :{ 39 | PROVIDE(_bss_start = .); 40 | *(.sbss .sbss.*) *(.bss .bss.*) 41 | PROVIDE(_bss_end = .); 42 | } >ram AT>ram :bss 43 | 44 | PROVIDE(_memory_start = ORIGIN(ram)); 45 | PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram)); 46 | } 47 | -------------------------------------------------------------------------------- /02-ContextSwitch/riscv.h: -------------------------------------------------------------------------------- 1 | #ifndef __RISCV_H__ 2 | #define __RISCV_H__ 3 | 4 | #include 5 | 6 | #define reg_t uint32_t // RISCV32: register is 32bits 7 | // define reg_t as uint64_t // RISCV64: register is 64bits 8 | 9 | // ref: https://www.activexperts.com/serial-port-component/tutorials/uart/ 10 | #define UART 0x10000000 11 | #define UART_THR (uint8_t*)(UART+0x00) // THR:transmitter holding register 12 | #define UART_LSR (uint8_t*)(UART+0x05) // LSR:line status register 13 | #define UART_LSR_EMPTY_MASK 0x40 // LSR Bit 6: Transmitter empty; both the THR and LSR are empty 14 | 15 | // Saved registers for kernel context switches. 16 | struct context { 17 | reg_t ra; 18 | reg_t sp; 19 | 20 | // callee-saved 21 | reg_t s0; 22 | reg_t s1; 23 | reg_t s2; 24 | reg_t s3; 25 | reg_t s4; 26 | reg_t s5; 27 | reg_t s6; 28 | reg_t s7; 29 | reg_t s8; 30 | reg_t s9; 31 | reg_t s10; 32 | reg_t s11; 33 | }; 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /02-ContextSwitch/start.s: -------------------------------------------------------------------------------- 1 | .equ STACK_SIZE, 8192 2 | 3 | .global _start 4 | 5 | _start: 6 | csrr a0, mhartid # 讀取核心代號 7 | bnez a0, park # 若不是 0 號核心,跳到 park 停止 8 | la sp, stacks + STACK_SIZE # 0 號核心設定堆疊 9 | j os_main # 0 號核心跳到主程式 os_main 10 | 11 | park: 12 | wfi 13 | j park 14 | 15 | stacks: 16 | .skip STACK_SIZE # 分配堆疊空間 17 | -------------------------------------------------------------------------------- /02-ContextSwitch/sys.h: -------------------------------------------------------------------------------- 1 | #ifndef __SYS_H__ 2 | #define __SYS_H__ 3 | 4 | #include "riscv.h" 5 | extern void sys_timer(); 6 | extern void sys_switch(struct context *ctx_old, struct context *ctx_new); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /02-ContextSwitch/sys.s: -------------------------------------------------------------------------------- 1 | # This Code derived from xv6-riscv (64bit) 2 | # -- https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/swtch.S 3 | 4 | # ============ MACRO ================== 5 | .macro ctx_save base 6 | sw ra, 0(\base) 7 | sw sp, 4(\base) 8 | sw s0, 8(\base) 9 | sw s1, 12(\base) 10 | sw s2, 16(\base) 11 | sw s3, 20(\base) 12 | sw s4, 24(\base) 13 | sw s5, 28(\base) 14 | sw s6, 32(\base) 15 | sw s7, 36(\base) 16 | sw s8, 40(\base) 17 | sw s9, 44(\base) 18 | sw s10, 48(\base) 19 | sw s11, 52(\base) 20 | .endm 21 | 22 | .macro ctx_load base 23 | lw ra, 0(\base) 24 | lw sp, 4(\base) 25 | lw s0, 8(\base) 26 | lw s1, 12(\base) 27 | lw s2, 16(\base) 28 | lw s3, 20(\base) 29 | lw s4, 24(\base) 30 | lw s5, 28(\base) 31 | lw s6, 32(\base) 32 | lw s7, 36(\base) 33 | lw s8, 40(\base) 34 | lw s9, 44(\base) 35 | lw s10, 48(\base) 36 | lw s11, 52(\base) 37 | .endm 38 | 39 | # ============ Macro END ================== 40 | 41 | # Context switch 42 | # 43 | # void sys_switch(struct context *old, struct context *new); 44 | # 45 | # Save current registers in old. Load from new. 46 | 47 | .globl sys_switch 48 | .align 4 49 | sys_switch: 50 | ctx_save a0 # a0 => struct context *old 51 | ctx_load a1 # a1 => struct context *new 52 | ret # pc=ra; swtch to new task (new->ra) 53 | -------------------------------------------------------------------------------- /03-MultiTasking/Makefile: -------------------------------------------------------------------------------- 1 | CC = riscv64-unknown-elf-gcc 2 | CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 3 | 4 | QEMU = qemu-system-riscv32 5 | QFLAGS = -nographic -smp 4 -machine virt -bios none 6 | 7 | OBJDUMP = riscv64-unknown-elf-objdump 8 | 9 | all: os.elf 10 | 11 | os.elf: start.s sys.s lib.c task.c os.c user.c 12 | $(CC) $(CFLAGS) -T os.ld -o os.elf $^ 13 | 14 | qemu: $(TARGET) 15 | @qemu-system-riscv32 -M ? | grep virt >/dev/null || exit 16 | @echo "Press Ctrl-A and then X to exit QEMU" 17 | $(QEMU) $(QFLAGS) -kernel os.elf 18 | 19 | clean: 20 | rm -f *.elf 21 | -------------------------------------------------------------------------------- /03-MultiTasking/README.md: -------------------------------------------------------------------------------- 1 | # 03-MultiTasking 2 | 3 | ## Build & Run 4 | 5 | ```sh 6 | user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/03-MultiTasking 7 | (master) 8 | $ make clean 9 | rm -f *.elf 10 | 11 | user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/03-MultiTasking 12 | (master) 13 | $ make 14 | riscv64-unknown-elf-gcc -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima 15 | -mabi=ilp32 -T os.ld -o os.elf start.s sys.s lib.c task.c os.c user.c 16 | 17 | user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/03-MultiTasking 18 | (master) 19 | $ make qemu 20 | Press Ctrl-A and then X to exit QEMU 21 | qemu-system-riscv32 -nographic -smp 4 -machine virt -bios none -kernel os.elf 22 | OS start 23 | OS: Activate next task 24 | Task0: Created! 25 | Task0: Now, return to kernel mode 26 | OS: Back to OS 27 | 28 | OS: Activate next task 29 | Task1: Created! 30 | Task1: Now, return to kernel mode 31 | OS: Back to OS 32 | 33 | OS: Activate next task 34 | Task0: Running... 35 | OS: Back to OS 36 | 37 | OS: Activate next task 38 | Task1: Running... 39 | OS: Back to OS 40 | 41 | OS: Activate next task 42 | Task0: Running... 43 | OS: Back to OS 44 | 45 | OS: Activate next task 46 | Task1: Running... 47 | OS: Back to OS 48 | 49 | OS: Activate next task 50 | Task0: Running... 51 | OS: Back to OS 52 | 53 | OS: Activate next task 54 | Task1: Running... 55 | OS: Back to OS 56 | 57 | OS: Activate next task 58 | Task0: Running... 59 | QEMU: Terminated 60 | ``` 61 | -------------------------------------------------------------------------------- /03-MultiTasking/lib.c: -------------------------------------------------------------------------------- 1 | #include "lib.h" 2 | 3 | void lib_delay(volatile int count) 4 | { 5 | count *= 50000; 6 | while (count--); 7 | } 8 | 9 | int lib_putc(char ch) { 10 | while ((*UART_LSR & UART_LSR_EMPTY_MASK) == 0); 11 | return *UART_THR = ch; 12 | } 13 | 14 | void lib_puts(char *s) { 15 | while (*s) lib_putc(*s++); 16 | } 17 | -------------------------------------------------------------------------------- /03-MultiTasking/lib.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIB_H__ 2 | #define __LIB_H__ 3 | 4 | #include "riscv.h" 5 | #include 6 | #include 7 | 8 | extern void lib_delay(volatile int count); 9 | extern int lib_putc(char ch); 10 | extern void lib_puts(char *s); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /03-MultiTasking/os.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | 3 | void os_kernel() { 4 | task_os(); 5 | } 6 | 7 | void os_start() { 8 | lib_puts("OS start\n"); 9 | user_init(); 10 | } 11 | 12 | int os_main(void) 13 | { 14 | os_start(); 15 | 16 | int current_task = 0; 17 | while (1) { 18 | lib_puts("OS: Activate next task\n"); 19 | task_go(current_task); 20 | lib_puts("OS: Back to OS\n"); 21 | current_task = (current_task + 1) % taskTop; // Round Robin Scheduling 22 | lib_puts("\n"); 23 | } 24 | return 0; 25 | } 26 | 27 | -------------------------------------------------------------------------------- /03-MultiTasking/os.h: -------------------------------------------------------------------------------- 1 | #ifndef __OS_H__ 2 | #define __OS_H__ 3 | 4 | #include "riscv.h" 5 | #include "lib.h" 6 | #include "task.h" 7 | 8 | extern void user_init(); 9 | extern void os_kernel(); 10 | extern int os_main(void); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /03-MultiTasking/os.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_ARCH( "riscv" ) 2 | 3 | ENTRY( _start ) 4 | 5 | MEMORY 6 | { 7 | ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M 8 | } 9 | 10 | PHDRS 11 | { 12 | text PT_LOAD; 13 | data PT_LOAD; 14 | bss PT_LOAD; 15 | } 16 | 17 | SECTIONS 18 | { 19 | .text : { 20 | PROVIDE(_text_start = .); 21 | *(.text.init) *(.text .text.*) 22 | PROVIDE(_text_end = .); 23 | } >ram AT>ram :text 24 | 25 | .rodata : { 26 | PROVIDE(_rodata_start = .); 27 | *(.rodata .rodata.*) 28 | PROVIDE(_rodata_end = .); 29 | } >ram AT>ram :text 30 | 31 | .data : { 32 | . = ALIGN(4096); 33 | PROVIDE(_data_start = .); 34 | *(.sdata .sdata.*) *(.data .data.*) 35 | PROVIDE(_data_end = .); 36 | } >ram AT>ram :data 37 | 38 | .bss :{ 39 | PROVIDE(_bss_start = .); 40 | *(.sbss .sbss.*) *(.bss .bss.*) 41 | PROVIDE(_bss_end = .); 42 | } >ram AT>ram :bss 43 | 44 | PROVIDE(_memory_start = ORIGIN(ram)); 45 | PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram)); 46 | } 47 | -------------------------------------------------------------------------------- /03-MultiTasking/riscv.h: -------------------------------------------------------------------------------- 1 | #ifndef __RISCV_H__ 2 | #define __RISCV_H__ 3 | 4 | #include 5 | 6 | #define reg_t uint32_t // RISCV32: register is 32bits 7 | // define reg_t as uint64_t // RISCV64: register is 64bits 8 | 9 | // ref: https://www.activexperts.com/serial-port-component/tutorials/uart/ 10 | #define UART 0x10000000 11 | #define UART_THR (uint8_t*)(UART+0x00) // THR:transmitter holding register 12 | #define UART_LSR (uint8_t*)(UART+0x05) // LSR:line status register 13 | #define UART_LSR_EMPTY_MASK 0x40 // LSR Bit 6: Transmitter empty; both the THR and LSR are empty 14 | 15 | // Saved registers for kernel context switches. 16 | struct context { 17 | reg_t ra; 18 | reg_t sp; 19 | 20 | // callee-saved 21 | reg_t s0; 22 | reg_t s1; 23 | reg_t s2; 24 | reg_t s3; 25 | reg_t s4; 26 | reg_t s5; 27 | reg_t s6; 28 | reg_t s7; 29 | reg_t s8; 30 | reg_t s9; 31 | reg_t s10; 32 | reg_t s11; 33 | }; 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /03-MultiTasking/start.s: -------------------------------------------------------------------------------- 1 | .equ STACK_SIZE, 8192 2 | 3 | .global _start 4 | 5 | _start: 6 | csrr a0, mhartid # 讀取核心代號 7 | bnez a0, park # 若不是 0 號核心,跳到 park 停止 8 | la sp, stacks + STACK_SIZE # 0 號核心設定堆疊 9 | j os_main # 0 號核心跳到主程式 os_main 10 | 11 | park: 12 | wfi 13 | j park 14 | 15 | stacks: 16 | .skip STACK_SIZE # 分配堆疊空間 17 | -------------------------------------------------------------------------------- /03-MultiTasking/sys.h: -------------------------------------------------------------------------------- 1 | #ifndef __SYS_H__ 2 | #define __SYS_H__ 3 | 4 | #include "riscv.h" 5 | extern void sys_timer(); 6 | extern void sys_switch(struct context *ctx_old, struct context *ctx_new); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /03-MultiTasking/sys.s: -------------------------------------------------------------------------------- 1 | # This Code derived from xv6-riscv (64bit) 2 | # -- https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/swtch.S 3 | 4 | # ============ MACRO ================== 5 | .macro ctx_save base 6 | sw ra, 0(\base) 7 | sw sp, 4(\base) 8 | sw s0, 8(\base) 9 | sw s1, 12(\base) 10 | sw s2, 16(\base) 11 | sw s3, 20(\base) 12 | sw s4, 24(\base) 13 | sw s5, 28(\base) 14 | sw s6, 32(\base) 15 | sw s7, 36(\base) 16 | sw s8, 40(\base) 17 | sw s9, 44(\base) 18 | sw s10, 48(\base) 19 | sw s11, 52(\base) 20 | .endm 21 | 22 | .macro ctx_load base 23 | lw ra, 0(\base) 24 | lw sp, 4(\base) 25 | lw s0, 8(\base) 26 | lw s1, 12(\base) 27 | lw s2, 16(\base) 28 | lw s3, 20(\base) 29 | lw s4, 24(\base) 30 | lw s5, 28(\base) 31 | lw s6, 32(\base) 32 | lw s7, 36(\base) 33 | lw s8, 40(\base) 34 | lw s9, 44(\base) 35 | lw s10, 48(\base) 36 | lw s11, 52(\base) 37 | .endm 38 | 39 | # ============ Macro END ================== 40 | 41 | # Context switch 42 | # 43 | # void sys_switch(struct context *old, struct context *new); 44 | # 45 | # Save current registers in old. Load from new. 46 | 47 | .globl sys_switch 48 | .align 4 49 | sys_switch: 50 | ctx_save a0 # a0 => struct context *old 51 | ctx_load a1 # a1 => struct context *new 52 | ret # pc=ra; swtch to new task (new->ra) 53 | -------------------------------------------------------------------------------- /03-MultiTasking/task.c: -------------------------------------------------------------------------------- 1 | #include "task.h" 2 | #include "lib.h" 3 | 4 | uint8_t task_stack[MAX_TASK][STACK_SIZE]; 5 | struct context ctx_os; 6 | struct context ctx_tasks[MAX_TASK]; 7 | struct context *ctx_now; 8 | int taskTop=0; // total number of task 9 | 10 | // create a new task 11 | int task_create(void (*task)(void)) 12 | { 13 | int i=taskTop++; 14 | ctx_tasks[i].ra = (reg_t) task; 15 | ctx_tasks[i].sp = (reg_t) &task_stack[i][STACK_SIZE-1]; 16 | return i; 17 | } 18 | 19 | // switch to task[i] 20 | void task_go(int i) { 21 | ctx_now = &ctx_tasks[i]; 22 | sys_switch(&ctx_os, &ctx_tasks[i]); 23 | } 24 | 25 | // switch back to os 26 | void task_os() { 27 | struct context *ctx = ctx_now; 28 | ctx_now = &ctx_os; 29 | sys_switch(ctx, &ctx_os); 30 | } 31 | -------------------------------------------------------------------------------- /03-MultiTasking/task.h: -------------------------------------------------------------------------------- 1 | #ifndef __TASK_H__ 2 | #define __TASK_H__ 3 | 4 | #include "riscv.h" 5 | #include "sys.h" 6 | 7 | #define MAX_TASK 10 8 | #define STACK_SIZE 1024 9 | 10 | extern int taskTop; 11 | 12 | extern int task_create(void (*task)(void)); 13 | extern void task_go(int i); 14 | extern void task_os(); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /03-MultiTasking/user.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | 3 | void user_task0(void) 4 | { 5 | lib_puts("Task0: Created!\n"); 6 | lib_puts("Task0: Now, return to kernel mode\n"); 7 | os_kernel(); 8 | while (1) { 9 | lib_puts("Task0: Running...\n"); 10 | lib_delay(1000); 11 | os_kernel(); 12 | } 13 | } 14 | 15 | void user_task1(void) 16 | { 17 | lib_puts("Task1: Created!\n"); 18 | lib_puts("Task1: Now, return to kernel mode\n"); 19 | os_kernel(); 20 | while (1) { 21 | lib_puts("Task1: Running...\n"); 22 | lib_delay(1000); 23 | os_kernel(); 24 | } 25 | } 26 | 27 | void user_init() { 28 | task_create(&user_task0); 29 | task_create(&user_task1); 30 | } 31 | -------------------------------------------------------------------------------- /04-TimerInterrupt/Makefile: -------------------------------------------------------------------------------- 1 | CC = riscv64-unknown-elf-gcc 2 | CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 3 | 4 | QEMU = qemu-system-riscv32 5 | QFLAGS = -nographic -smp 4 -machine virt -bios none 6 | 7 | OBJDUMP = riscv64-unknown-elf-objdump 8 | 9 | all: os.elf 10 | 11 | os.elf: start.s sys.s lib.c timer.c os.c 12 | $(CC) $(CFLAGS) -T os.ld -o os.elf $^ 13 | 14 | qemu: $(TARGET) 15 | @qemu-system-riscv32 -M ? | grep virt >/dev/null || exit 16 | @echo "Press Ctrl-A and then X to exit QEMU" 17 | $(QEMU) $(QFLAGS) -kernel os.elf 18 | 19 | clean: 20 | rm -f *.elf 21 | -------------------------------------------------------------------------------- /04-TimerInterrupt/README.md: -------------------------------------------------------------------------------- 1 | # 04-TimerInterrupt 2 | 3 | ## Build & Run 4 | 5 | ```sh 6 | $ make 7 | 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 8 | $ make qemu 9 | Press Ctrl-A and then X to exit QEMU 10 | qemu-system-riscv32 -nographic -smp 4 -machine virt -bios none -kernel os.elf 11 | OS start 12 | timer_handler: 1 13 | timer_handler: 2 14 | timer_handler: 3 15 | timer_handler: 4 16 | timer_handler: 5 17 | QEMU: Terminated 18 | ``` 19 | -------------------------------------------------------------------------------- /04-TimerInterrupt/lib.c: -------------------------------------------------------------------------------- 1 | #include "lib.h" 2 | 3 | void lib_delay(volatile int count) 4 | { 5 | count *= 50000; 6 | while (count--); 7 | } 8 | 9 | int lib_putc(char ch) { 10 | while ((*UART_LSR & UART_LSR_EMPTY_MASK) == 0); 11 | return *UART_THR = ch; 12 | } 13 | 14 | void lib_puts(char *s) { 15 | while (*s) lib_putc(*s++); 16 | } 17 | 18 | 19 | int lib_vsnprintf(char * out, size_t n, const char* s, va_list vl) 20 | { 21 | int format = 0; 22 | int longarg = 0; 23 | size_t pos = 0; 24 | for( ; *s; s++) { 25 | if (format) { 26 | switch(*s) { 27 | case 'l': { 28 | longarg = 1; 29 | break; 30 | } 31 | case 'p': { 32 | longarg = 1; 33 | if (out && pos < n) { 34 | out[pos] = '0'; 35 | } 36 | pos++; 37 | if (out && pos < n) { 38 | out[pos] = 'x'; 39 | } 40 | pos++; 41 | } 42 | case 'x': { 43 | long num = longarg ? va_arg(vl, long) : va_arg(vl, int); 44 | int hexdigits = 2*(longarg ? sizeof(long) : sizeof(int))-1; 45 | for(int i = hexdigits; i >= 0; i--) { 46 | int d = (num >> (4*i)) & 0xF; 47 | if (out && pos < n) { 48 | out[pos] = (d < 10 ? '0'+d : 'a'+d-10); 49 | } 50 | pos++; 51 | } 52 | longarg = 0; 53 | format = 0; 54 | break; 55 | } 56 | case 'd': { 57 | long num = longarg ? va_arg(vl, long) : va_arg(vl, int); 58 | if (num < 0) { 59 | num = -num; 60 | if (out && pos < n) { 61 | out[pos] = '-'; 62 | } 63 | pos++; 64 | } 65 | long digits = 1; 66 | for (long nn = num; nn /= 10; digits++) 67 | ; 68 | for (int i = digits-1; i >= 0; i--) { 69 | if (out && pos + i < n) { 70 | out[pos + i] = '0' + (num % 10); 71 | } 72 | num /= 10; 73 | } 74 | pos += digits; 75 | longarg = 0; 76 | format = 0; 77 | break; 78 | } 79 | case 's': { 80 | const char* s2 = va_arg(vl, const char*); 81 | while (*s2) { 82 | if (out && pos < n) { 83 | out[pos] = *s2; 84 | } 85 | pos++; 86 | s2++; 87 | } 88 | longarg = 0; 89 | format = 0; 90 | break; 91 | } 92 | case 'c': { 93 | if (out && pos < n) { 94 | out[pos] = (char)va_arg(vl,int); 95 | } 96 | pos++; 97 | longarg = 0; 98 | format = 0; 99 | break; 100 | } 101 | default: 102 | break; 103 | } 104 | } 105 | else if(*s == '%') { 106 | format = 1; 107 | } 108 | else { 109 | if (out && pos < n) { 110 | out[pos] = *s; 111 | } 112 | pos++; 113 | } 114 | } 115 | if (out && pos < n) { 116 | out[pos] = 0; 117 | } 118 | else if (out && n) { 119 | out[n-1] = 0; 120 | } 121 | return pos; 122 | } 123 | 124 | static char out_buf[1000]; // buffer for lib_vprintf() 125 | 126 | int lib_vprintf(const char* s, va_list vl) 127 | { 128 | int res = lib_vsnprintf(NULL, -1, s, vl); 129 | if (res+1 >= sizeof(out_buf)) { 130 | lib_puts("error: lib_vprintf() output string size overflow\n"); 131 | while(1) {} 132 | } 133 | lib_vsnprintf(out_buf, res + 1, s, vl); 134 | lib_puts(out_buf); 135 | return res; 136 | } 137 | 138 | int lib_printf(const char* s, ...) 139 | { 140 | int res = 0; 141 | va_list vl; 142 | va_start(vl, s); 143 | res = lib_vprintf(s, vl); 144 | va_end(vl); 145 | return res; 146 | } 147 | -------------------------------------------------------------------------------- /04-TimerInterrupt/lib.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIB_H__ 2 | #define __LIB_H__ 3 | 4 | #include "riscv.h" 5 | #include 6 | #include 7 | 8 | #define lib_error(...) { lib_printf(__VA_ARGS__); while(1) {} } } 9 | 10 | extern void lib_delay(volatile int count); 11 | extern int lib_putc(char ch); 12 | extern void lib_puts(char *s); 13 | extern int lib_printf(const char* s, ...); 14 | extern int lib_vprintf(const char* s, va_list vl); 15 | extern int lib_vsnprintf(char * out, size_t n, const char* s, va_list vl); 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /04-TimerInterrupt/os.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | 3 | int os_main(void) 4 | { 5 | lib_puts("OS start\n"); 6 | timer_init(); // start timer interrupt ... 7 | while (1) {} // os : do nothing, just loop! 8 | return 0; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /04-TimerInterrupt/os.h: -------------------------------------------------------------------------------- 1 | #ifndef __OS_H__ 2 | #define __OS_H__ 3 | 4 | #include "riscv.h" 5 | #include "lib.h" 6 | #include "timer.h" 7 | 8 | extern void os_loop(void); 9 | extern int os_main(void); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /04-TimerInterrupt/os.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_ARCH( "riscv" ) 2 | 3 | ENTRY( _start ) 4 | 5 | MEMORY 6 | { 7 | ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M 8 | } 9 | 10 | PHDRS 11 | { 12 | text PT_LOAD; 13 | data PT_LOAD; 14 | bss PT_LOAD; 15 | } 16 | 17 | SECTIONS 18 | { 19 | .text : { 20 | PROVIDE(_text_start = .); 21 | *(.text.init) *(.text .text.*) 22 | PROVIDE(_text_end = .); 23 | } >ram AT>ram :text 24 | 25 | .rodata : { 26 | PROVIDE(_rodata_start = .); 27 | *(.rodata .rodata.*) 28 | PROVIDE(_rodata_end = .); 29 | } >ram AT>ram :text 30 | 31 | .data : { 32 | . = ALIGN(4096); 33 | PROVIDE(_data_start = .); 34 | *(.sdata .sdata.*) *(.data .data.*) 35 | PROVIDE(_data_end = .); 36 | } >ram AT>ram :data 37 | 38 | .bss :{ 39 | PROVIDE(_bss_start = .); 40 | *(.sbss .sbss.*) *(.bss .bss.*) 41 | PROVIDE(_bss_end = .); 42 | } >ram AT>ram :bss 43 | 44 | PROVIDE(_memory_start = ORIGIN(ram)); 45 | PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram)); 46 | } 47 | -------------------------------------------------------------------------------- /04-TimerInterrupt/riscv.h: -------------------------------------------------------------------------------- 1 | #ifndef __RISCV_H__ 2 | #define __RISCV_H__ 3 | 4 | #include 5 | 6 | #define reg_t uint32_t // RISCV32: register is 32bits 7 | // define reg_t as uint64_t // RISCV64: register is 64bits 8 | 9 | // ref: https://www.activexperts.com/serial-port-component/tutorials/uart/ 10 | #define UART 0x10000000 11 | #define UART_THR (uint8_t*)(UART+0x00) // THR:transmitter holding register 12 | #define UART_LSR (uint8_t*)(UART+0x05) // LSR:line status register 13 | #define UART_LSR_EMPTY_MASK 0x40 // LSR Bit 6: Transmitter empty; both the THR and LSR are empty 14 | 15 | // Saved registers for kernel context switches. 16 | struct context { 17 | reg_t ra; 18 | reg_t sp; 19 | 20 | // callee-saved 21 | reg_t s0; 22 | reg_t s1; 23 | reg_t s2; 24 | reg_t s3; 25 | reg_t s4; 26 | reg_t s5; 27 | reg_t s6; 28 | reg_t s7; 29 | reg_t s8; 30 | reg_t s9; 31 | reg_t s10; 32 | reg_t s11; 33 | }; 34 | 35 | // ref: https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/riscv.h 36 | // 37 | // local interrupt controller, which contains the timer. 38 | // ================== Timer Interrput ==================== 39 | 40 | #define NCPU 8 // maximum number of CPUs 41 | #define CLINT 0x2000000 42 | #define CLINT_MTIMECMP(hartid) (CLINT + 0x4000 + 4*(hartid)) 43 | #define CLINT_MTIME (CLINT + 0xBFF8) // cycles since boot. 44 | 45 | // which hart (core) is this? 46 | static inline reg_t r_mhartid() 47 | { 48 | reg_t x; 49 | asm volatile("csrr %0, mhartid" : "=r" (x) ); 50 | return x; 51 | } 52 | 53 | // Machine Status Register, mstatus 54 | #define MSTATUS_MPP_MASK (3 << 11) // previous mode. 55 | #define MSTATUS_MPP_M (3 << 11) 56 | #define MSTATUS_MPP_S (1 << 11) 57 | #define MSTATUS_MPP_U (0 << 11) 58 | #define MSTATUS_MIE (1 << 3) // machine-mode interrupt enable. 59 | 60 | static inline reg_t r_mstatus() 61 | { 62 | reg_t x; 63 | asm volatile("csrr %0, mstatus" : "=r" (x) ); 64 | return x; 65 | } 66 | 67 | static inline void w_mstatus(reg_t x) 68 | { 69 | asm volatile("csrw mstatus, %0" : : "r" (x)); 70 | } 71 | 72 | // machine exception program counter, holds the 73 | // instruction address to which a return from 74 | // exception will go. 75 | static inline void w_mepc(reg_t x) 76 | { 77 | asm volatile("csrw mepc, %0" : : "r" (x)); 78 | } 79 | 80 | static inline reg_t r_mepc() 81 | { 82 | reg_t x; 83 | asm volatile("csrr %0, mepc" : "=r" (x)); 84 | return x; 85 | } 86 | 87 | // Machine Scratch register, for early trap handler 88 | static inline void w_mscratch(reg_t x) 89 | { 90 | asm volatile("csrw mscratch, %0" : : "r" (x)); 91 | } 92 | 93 | // Machine-mode interrupt vector 94 | static inline void w_mtvec(reg_t x) 95 | { 96 | asm volatile("csrw mtvec, %0" : : "r" (x)); 97 | } 98 | 99 | // Machine-mode Interrupt Enable 100 | #define MIE_MEIE (1 << 11) // external 101 | #define MIE_MTIE (1 << 7) // timer 102 | #define MIE_MSIE (1 << 3) // software 103 | 104 | static inline reg_t r_mie() 105 | { 106 | reg_t x; 107 | asm volatile("csrr %0, mie" : "=r" (x) ); 108 | return x; 109 | } 110 | 111 | static inline void w_mie(reg_t x) 112 | { 113 | asm volatile("csrw mie, %0" : : "r" (x)); 114 | } 115 | 116 | #endif 117 | -------------------------------------------------------------------------------- /04-TimerInterrupt/start.s: -------------------------------------------------------------------------------- 1 | .equ STACK_SIZE, 8192 2 | 3 | .global _start 4 | 5 | _start: 6 | csrr a0, mhartid # 讀取核心代號 7 | bnez a0, park # 若不是 0 號核心,跳到 park 停止 8 | la sp, stacks + STACK_SIZE # 0 號核心設定堆疊 9 | j os_main # 0 號核心跳到主程式 os_main 10 | 11 | park: 12 | wfi 13 | j park 14 | 15 | stacks: 16 | .skip STACK_SIZE # 分配堆疊空間 17 | -------------------------------------------------------------------------------- /04-TimerInterrupt/sys.h: -------------------------------------------------------------------------------- 1 | #ifndef __SYS_H__ 2 | #define __SYS_H__ 3 | 4 | #include "riscv.h" 5 | extern void sys_timer(); 6 | extern void sys_switch(struct context *ctx_old, struct context *ctx_new); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /04-TimerInterrupt/sys.s: -------------------------------------------------------------------------------- 1 | # This Code derived from xv6-riscv (64bit) 2 | # -- https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/swtch.S 3 | 4 | .globl sys_timer 5 | .align 4 6 | sys_timer: 7 | # call the C timer_handler(reg_t epc, reg_t cause) 8 | csrr a0, mepc 9 | csrr a1, mcause 10 | call timer_handler 11 | 12 | # timer_handler will return the return address via a0. 13 | csrw mepc, a0 14 | 15 | mret # back to interrupt location (pc=mepc) 16 | -------------------------------------------------------------------------------- /04-TimerInterrupt/timer.c: -------------------------------------------------------------------------------- 1 | #include "timer.h" 2 | 3 | #define interval 10000000 // cycles; about 1 second in qemu. 4 | 5 | void timer_init() 6 | { 7 | // each CPU has a separate source of timer interrupts. 8 | int id = r_mhartid(); 9 | 10 | // ask the CLINT for a timer interrupt. 11 | *(reg_t*)CLINT_MTIMECMP(id) = *(reg_t*)CLINT_MTIME + interval; 12 | 13 | // set the machine-mode trap handler. 14 | w_mtvec((reg_t)sys_timer); 15 | 16 | // enable machine-mode interrupts. 17 | w_mstatus(r_mstatus() | MSTATUS_MIE); 18 | 19 | // enable machine-mode timer interrupts. 20 | w_mie(r_mie() | MIE_MTIE); 21 | } 22 | 23 | static int timer_count = 0; 24 | 25 | reg_t timer_handler(reg_t epc, reg_t cause) 26 | { 27 | reg_t return_pc = epc; 28 | // disable machine-mode timer interrupts. 29 | w_mie(~((~r_mie()) | (1 << 7))); 30 | lib_printf("timer_handler: %d\n", ++timer_count); 31 | int id = r_mhartid(); 32 | *(reg_t *)CLINT_MTIMECMP(id) = *(reg_t *)CLINT_MTIME + interval; 33 | // enable machine-mode timer interrupts. 34 | w_mie(r_mie() | MIE_MTIE); 35 | return return_pc; 36 | } 37 | -------------------------------------------------------------------------------- /04-TimerInterrupt/timer.h: -------------------------------------------------------------------------------- 1 | #ifndef __TIMER_H__ 2 | #define __TIMER_H__ 3 | 4 | #include "riscv.h" 5 | #include "sys.h" 6 | #include "lib.h" 7 | 8 | extern reg_t timer_handler(reg_t epc, reg_t cause); 9 | extern void timer_init(); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /05-Preemptive/Makefile: -------------------------------------------------------------------------------- 1 | CC = riscv64-unknown-elf-gcc 2 | CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 3 | 4 | OBJ = \ 5 | start.s \ 6 | sys.s \ 7 | lib.c \ 8 | timer.c \ 9 | task.c \ 10 | os.c \ 11 | user.c \ 12 | trap.c 13 | 14 | QEMU = qemu-system-riscv32 15 | QFLAGS = -nographic -smp 4 -machine virt -bios none 16 | 17 | OBJDUMP = riscv64-unknown-elf-objdump 18 | 19 | all: os.elf 20 | 21 | os.elf: $(OBJ) 22 | $(CC) $(CFLAGS) -T os.ld -o os.elf $^ 23 | 24 | qemu: $(TARGET) 25 | @qemu-system-riscv32 -M ? | grep virt >/dev/null || exit 26 | @echo "Press Ctrl-A and then X to exit QEMU" 27 | $(QEMU) $(QFLAGS) -kernel os.elf 28 | 29 | clean: 30 | rm -f *.elf 31 | -------------------------------------------------------------------------------- /05-Preemptive/README.md: -------------------------------------------------------------------------------- 1 | # 05-Preemptive 2 | 3 | ## Build & Run 4 | 5 | ```sh 6 | user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/05-Preemptive (master) 7 | $ make clean 8 | rm -f *.elf 9 | 10 | user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/05-Preemptive (master) 11 | $ make 12 | 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 13 | 14 | user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/05-Preemptive (master) 15 | $ make qemu 16 | Press Ctrl-A and then X to exit QEMU 17 | qemu-system-riscv32 -nographic -smp 4 -machine virt -bios none -kernel os.elf 18 | OS start 19 | OS: Activate next task 20 | Task0: Created! 21 | Task0: Now, return to kernel mode 22 | OS: Back to OS 23 | 24 | OS: Activate next task 25 | Task1: Created! 26 | Task1: Now, return to kernel mode 27 | OS: Back to OS 28 | 29 | OS: Activate next task 30 | Task0: Running... 31 | Task0: Running... 32 | Task0: Running... 33 | timer_handler: 1 34 | OS: Back to OS 35 | 36 | OS: Activate next task 37 | Task1: Running... 38 | Task1: Running... 39 | Task1: Running... 40 | timer_handler: 2 41 | OS: Back to OS 42 | 43 | OS: Activate next task 44 | Task0: Running... 45 | Task0: Running... 46 | Task0: Running... 47 | timer_handler: 3 48 | OS: Back to OS 49 | 50 | OS: Activate next task 51 | Task1: Running... 52 | Task1: Running... 53 | Task1: Running... 54 | timer_handler: 4 55 | OS: Back to OS 56 | 57 | OS: Activate next task 58 | Task0: Running... 59 | Task0: Running... 60 | Task0: Running... 61 | timer_handler: 5 62 | OS: Back to OS 63 | 64 | OS: Activate next task 65 | Task1: Running... 66 | Task1: Running... 67 | QEMU: Terminated 68 | ``` 69 | -------------------------------------------------------------------------------- /05-Preemptive/lib.c: -------------------------------------------------------------------------------- 1 | #include "lib.h" 2 | 3 | void lib_delay(volatile int count) 4 | { 5 | count *= 50000; 6 | while (count--); 7 | } 8 | 9 | int lib_putc(char ch) { 10 | while ((*UART_LSR & UART_LSR_EMPTY_MASK) == 0); 11 | return *UART_THR = ch; 12 | } 13 | 14 | void lib_puts(char *s) { 15 | while (*s) lib_putc(*s++); 16 | } 17 | 18 | 19 | int lib_vsnprintf(char * out, size_t n, const char* s, va_list vl) 20 | { 21 | int format = 0; 22 | int longarg = 0; 23 | size_t pos = 0; 24 | for( ; *s; s++) { 25 | if (format) { 26 | switch(*s) { 27 | case 'l': { 28 | longarg = 1; 29 | break; 30 | } 31 | case 'p': { 32 | longarg = 1; 33 | if (out && pos < n) { 34 | out[pos] = '0'; 35 | } 36 | pos++; 37 | if (out && pos < n) { 38 | out[pos] = 'x'; 39 | } 40 | pos++; 41 | } 42 | case 'x': { 43 | long num = longarg ? va_arg(vl, long) : va_arg(vl, int); 44 | int hexdigits = 2*(longarg ? sizeof(long) : sizeof(int))-1; 45 | for(int i = hexdigits; i >= 0; i--) { 46 | int d = (num >> (4*i)) & 0xF; 47 | if (out && pos < n) { 48 | out[pos] = (d < 10 ? '0'+d : 'a'+d-10); 49 | } 50 | pos++; 51 | } 52 | longarg = 0; 53 | format = 0; 54 | break; 55 | } 56 | case 'd': { 57 | long num = longarg ? va_arg(vl, long) : va_arg(vl, int); 58 | if (num < 0) { 59 | num = -num; 60 | if (out && pos < n) { 61 | out[pos] = '-'; 62 | } 63 | pos++; 64 | } 65 | long digits = 1; 66 | for (long nn = num; nn /= 10; digits++) 67 | ; 68 | for (int i = digits-1; i >= 0; i--) { 69 | if (out && pos + i < n) { 70 | out[pos + i] = '0' + (num % 10); 71 | } 72 | num /= 10; 73 | } 74 | pos += digits; 75 | longarg = 0; 76 | format = 0; 77 | break; 78 | } 79 | case 's': { 80 | const char* s2 = va_arg(vl, const char*); 81 | while (*s2) { 82 | if (out && pos < n) { 83 | out[pos] = *s2; 84 | } 85 | pos++; 86 | s2++; 87 | } 88 | longarg = 0; 89 | format = 0; 90 | break; 91 | } 92 | case 'c': { 93 | if (out && pos < n) { 94 | out[pos] = (char)va_arg(vl,int); 95 | } 96 | pos++; 97 | longarg = 0; 98 | format = 0; 99 | break; 100 | } 101 | default: 102 | break; 103 | } 104 | } 105 | else if(*s == '%') { 106 | format = 1; 107 | } 108 | else { 109 | if (out && pos < n) { 110 | out[pos] = *s; 111 | } 112 | pos++; 113 | } 114 | } 115 | if (out && pos < n) { 116 | out[pos] = 0; 117 | } 118 | else if (out && n) { 119 | out[n-1] = 0; 120 | } 121 | return pos; 122 | } 123 | 124 | static char out_buf[1000]; // buffer for lib_vprintf() 125 | 126 | int lib_vprintf(const char* s, va_list vl) 127 | { 128 | int res = lib_vsnprintf(NULL, -1, s, vl); 129 | if (res+1 >= sizeof(out_buf)) { 130 | lib_puts("error: lib_vprintf() output string size overflow\n"); 131 | while(1) {} 132 | } 133 | lib_vsnprintf(out_buf, res + 1, s, vl); 134 | lib_puts(out_buf); 135 | return res; 136 | } 137 | 138 | int lib_printf(const char* s, ...) 139 | { 140 | int res = 0; 141 | va_list vl; 142 | va_start(vl, s); 143 | res = lib_vprintf(s, vl); 144 | va_end(vl); 145 | return res; 146 | } 147 | -------------------------------------------------------------------------------- /05-Preemptive/lib.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIB_H__ 2 | #define __LIB_H__ 3 | 4 | #include "riscv.h" 5 | #include 6 | #include 7 | 8 | #define lib_error(...) { lib_printf(__VA_ARGS__); while(1) {} } } 9 | 10 | extern void lib_delay(volatile int count); 11 | extern int lib_putc(char ch); 12 | extern void lib_puts(char *s); 13 | extern int lib_printf(const char* s, ...); 14 | extern int lib_vprintf(const char* s, va_list vl); 15 | extern int lib_vsnprintf(char * out, size_t n, const char* s, va_list vl); 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /05-Preemptive/os.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | 3 | extern void trap_init(void); 4 | 5 | void os_kernel() 6 | { 7 | task_os(); 8 | } 9 | 10 | void os_start() 11 | { 12 | lib_puts("OS start\n"); 13 | user_init(); 14 | trap_init(); 15 | timer_init(); // start timer interrupt ... 16 | } 17 | 18 | int os_main(void) 19 | { 20 | os_start(); 21 | 22 | int current_task = 0; 23 | while (1) 24 | { 25 | lib_puts("OS: Activate next task\n"); 26 | task_go(current_task); 27 | lib_puts("OS: Back to OS\n"); 28 | current_task = (current_task + 1) % taskTop; // Round Robin Scheduling 29 | lib_puts("\n"); 30 | } 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /05-Preemptive/os.h: -------------------------------------------------------------------------------- 1 | #ifndef __OS_H__ 2 | #define __OS_H__ 3 | 4 | #include "riscv.h" 5 | #include "lib.h" 6 | #include "task.h" 7 | #include "timer.h" 8 | 9 | extern void user_init(); 10 | extern void os_kernel(); 11 | extern int os_main(void); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /05-Preemptive/os.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_ARCH( "riscv" ) 2 | 3 | ENTRY( _start ) 4 | 5 | MEMORY 6 | { 7 | ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M 8 | } 9 | 10 | PHDRS 11 | { 12 | text PT_LOAD; 13 | data PT_LOAD; 14 | bss PT_LOAD; 15 | } 16 | 17 | SECTIONS 18 | { 19 | .text : { 20 | PROVIDE(_text_start = .); 21 | *(.text.init) *(.text .text.*) 22 | PROVIDE(_text_end = .); 23 | } >ram AT>ram :text 24 | 25 | .rodata : { 26 | PROVIDE(_rodata_start = .); 27 | *(.rodata .rodata.*) 28 | PROVIDE(_rodata_end = .); 29 | } >ram AT>ram :text 30 | 31 | .data : { 32 | . = ALIGN(4096); 33 | PROVIDE(_data_start = .); 34 | *(.sdata .sdata.*) *(.data .data.*) 35 | PROVIDE(_data_end = .); 36 | } >ram AT>ram :data 37 | 38 | .bss :{ 39 | PROVIDE(_bss_start = .); 40 | *(.sbss .sbss.*) *(.bss .bss.*) 41 | PROVIDE(_bss_end = .); 42 | } >ram AT>ram :bss 43 | 44 | PROVIDE(_memory_start = ORIGIN(ram)); 45 | PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram)); 46 | } 47 | -------------------------------------------------------------------------------- /05-Preemptive/riscv.h: -------------------------------------------------------------------------------- 1 | #ifndef __RISCV_H__ 2 | #define __RISCV_H__ 3 | 4 | #include 5 | 6 | #define reg_t uint32_t // RISCV32: register is 32bits 7 | // define reg_t as uint64_t // RISCV64: register is 64bits 8 | 9 | // ref: https://www.activexperts.com/serial-port-component/tutorials/uart/ 10 | #define UART 0x10000000 11 | #define UART_THR (uint8_t*)(UART+0x00) // THR:transmitter holding register 12 | #define UART_LSR (uint8_t*)(UART+0x05) // LSR:line status register 13 | #define UART_LSR_EMPTY_MASK 0x40 // LSR Bit 6: Transmitter empty; both the THR and LSR are empty 14 | 15 | // Saved registers for kernel context switches. 16 | struct context { 17 | reg_t ra; 18 | reg_t sp; 19 | 20 | // callee-saved 21 | reg_t s0; 22 | reg_t s1; 23 | reg_t s2; 24 | reg_t s3; 25 | reg_t s4; 26 | reg_t s5; 27 | reg_t s6; 28 | reg_t s7; 29 | reg_t s8; 30 | reg_t s9; 31 | reg_t s10; 32 | reg_t s11; 33 | }; 34 | 35 | // ref: https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/riscv.h 36 | // 37 | // local interrupt controller, which contains the timer. 38 | // ================== Timer Interrput ==================== 39 | 40 | #define NCPU 8 // maximum number of CPUs 41 | #define CLINT 0x2000000 42 | #define CLINT_MTIMECMP(hartid) (CLINT + 0x4000 + 4*(hartid)) 43 | #define CLINT_MTIME (CLINT + 0xBFF8) // cycles since boot. 44 | 45 | // which hart (core) is this? 46 | static inline reg_t r_mhartid() 47 | { 48 | reg_t x; 49 | asm volatile("csrr %0, mhartid" : "=r" (x) ); 50 | return x; 51 | } 52 | 53 | // Machine Status Register, mstatus 54 | #define MSTATUS_MPP_MASK (3 << 11) // previous mode. 55 | #define MSTATUS_MPP_M (3 << 11) 56 | #define MSTATUS_MPP_S (1 << 11) 57 | #define MSTATUS_MPP_U (0 << 11) 58 | #define MSTATUS_MIE (1 << 3) // machine-mode interrupt enable. 59 | 60 | static inline reg_t r_mstatus() 61 | { 62 | reg_t x; 63 | asm volatile("csrr %0, mstatus" : "=r" (x) ); 64 | return x; 65 | } 66 | 67 | static inline void w_mstatus(reg_t x) 68 | { 69 | asm volatile("csrw mstatus, %0" : : "r" (x)); 70 | } 71 | 72 | // machine exception program counter, holds the 73 | // instruction address to which a return from 74 | // exception will go. 75 | static inline void w_mepc(reg_t x) 76 | { 77 | asm volatile("csrw mepc, %0" : : "r" (x)); 78 | } 79 | 80 | static inline reg_t r_mepc() 81 | { 82 | reg_t x; 83 | asm volatile("csrr %0, mepc" : "=r" (x)); 84 | return x; 85 | } 86 | 87 | // Machine Scratch register, for early trap handler 88 | static inline void w_mscratch(reg_t x) 89 | { 90 | asm volatile("csrw mscratch, %0" : : "r" (x)); 91 | } 92 | 93 | // Machine-mode interrupt vector 94 | static inline void w_mtvec(reg_t x) 95 | { 96 | asm volatile("csrw mtvec, %0" : : "r" (x)); 97 | } 98 | 99 | // Machine-mode Interrupt Enable 100 | #define MIE_MEIE (1 << 11) // external 101 | #define MIE_MTIE (1 << 7) // timer 102 | #define MIE_MSIE (1 << 3) // software 103 | 104 | static inline reg_t r_mie() 105 | { 106 | reg_t x; 107 | asm volatile("csrr %0, mie" : "=r" (x) ); 108 | return x; 109 | } 110 | 111 | static inline void w_mie(reg_t x) 112 | { 113 | asm volatile("csrw mie, %0" : : "r" (x)); 114 | } 115 | 116 | #endif 117 | -------------------------------------------------------------------------------- /05-Preemptive/start.s: -------------------------------------------------------------------------------- 1 | .equ STACK_SIZE, 8192 2 | 3 | .global _start 4 | 5 | _start: 6 | csrr a0, mhartid # 讀取核心代號 7 | bnez a0, park # 若不是 0 號核心,跳到 park 停止 8 | la sp, stacks + STACK_SIZE # 0 號核心設定堆疊 9 | j os_main # 0 號核心跳到主程式 os_main 10 | 11 | park: 12 | wfi 13 | j park 14 | 15 | stacks: 16 | .skip STACK_SIZE # 分配堆疊空間 17 | -------------------------------------------------------------------------------- /05-Preemptive/sys.h: -------------------------------------------------------------------------------- 1 | #ifndef __SYS_H__ 2 | #define __SYS_H__ 3 | 4 | #include "riscv.h" 5 | extern void sys_timer(); 6 | extern void sys_switch(struct context *ctx_old, struct context *ctx_new); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /05-Preemptive/sys.s: -------------------------------------------------------------------------------- 1 | # This Code derived from xv6-riscv (64bit) 2 | # -- https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/swtch.S 3 | 4 | # ============ MACRO ================== 5 | .macro ctx_save base 6 | sw ra, 0(\base) 7 | sw sp, 4(\base) 8 | sw s0, 8(\base) 9 | sw s1, 12(\base) 10 | sw s2, 16(\base) 11 | sw s3, 20(\base) 12 | sw s4, 24(\base) 13 | sw s5, 28(\base) 14 | sw s6, 32(\base) 15 | sw s7, 36(\base) 16 | sw s8, 40(\base) 17 | sw s9, 44(\base) 18 | sw s10, 48(\base) 19 | sw s11, 52(\base) 20 | .endm 21 | 22 | .macro ctx_load base 23 | lw ra, 0(\base) 24 | lw sp, 4(\base) 25 | lw s0, 8(\base) 26 | lw s1, 12(\base) 27 | lw s2, 16(\base) 28 | lw s3, 20(\base) 29 | lw s4, 24(\base) 30 | lw s5, 28(\base) 31 | lw s6, 32(\base) 32 | lw s7, 36(\base) 33 | lw s8, 40(\base) 34 | lw s9, 44(\base) 35 | lw s10, 48(\base) 36 | lw s11, 52(\base) 37 | .endm 38 | 39 | .macro reg_save base 40 | # save the registers. 41 | sw ra, 0(\base) 42 | sw sp, 4(\base) 43 | sw gp, 8(\base) 44 | sw tp, 12(\base) 45 | sw t0, 16(\base) 46 | sw t1, 20(\base) 47 | sw t2, 24(\base) 48 | sw s0, 28(\base) 49 | sw s1, 32(\base) 50 | sw a0, 36(\base) 51 | sw a1, 40(\base) 52 | sw a2, 44(\base) 53 | sw a3, 48(\base) 54 | sw a4, 52(\base) 55 | sw a5, 56(\base) 56 | sw a6, 60(\base) 57 | sw a7, 64(\base) 58 | sw s2, 68(\base) 59 | sw s3, 72(\base) 60 | sw s4, 76(\base) 61 | sw s5, 80(\base) 62 | sw s6, 84(\base) 63 | sw s7, 88(\base) 64 | sw s8, 92(\base) 65 | sw s9, 96(\base) 66 | sw s10, 100(\base) 67 | sw s11, 104(\base) 68 | sw t3, 108(\base) 69 | sw t4, 112(\base) 70 | sw t5, 116(\base) 71 | sw t6, 120(\base) 72 | .endm 73 | 74 | .macro reg_load base 75 | # restore registers. 76 | lw ra, 0(\base) 77 | lw sp, 4(\base) 78 | lw gp, 8(\base) 79 | # not this, in case we moved CPUs: lw tp, 12(\base) 80 | lw t0, 16(\base) 81 | lw t1, 20(\base) 82 | lw t2, 24(\base) 83 | lw s0, 28(\base) 84 | lw s1, 32(\base) 85 | lw a0, 36(\base) 86 | lw a1, 40(\base) 87 | lw a2, 44(\base) 88 | lw a3, 48(\base) 89 | lw a4, 52(\base) 90 | lw a5, 56(\base) 91 | lw a6, 60(\base) 92 | lw a7, 64(\base) 93 | lw s2, 68(\base) 94 | lw s3, 72(\base) 95 | lw s4, 76(\base) 96 | lw s5, 80(\base) 97 | lw s6, 84(\base) 98 | lw s7, 88(\base) 99 | lw s8, 92(\base) 100 | lw s9, 96(\base) 101 | lw s10, 100(\base) 102 | lw s11, 104(\base) 103 | lw t3, 108(\base) 104 | lw t4, 112(\base) 105 | lw t5, 116(\base) 106 | lw t6, 120(\base) 107 | .endm 108 | # ============ Macro END ================== 109 | 110 | # Context switch 111 | # 112 | # void sys_switch(struct context *old, struct context *new); 113 | # 114 | # Save current registers in old. Load from new. 115 | 116 | .globl sys_switch 117 | .align 4 118 | sys_switch: 119 | 120 | ctx_save a0 # a0 => struct context *old 121 | ctx_load a1 # a1 => struct context *new 122 | ret # pc=ra; swtch to new task (new->ra) 123 | 124 | 125 | .globl trap_vector 126 | # the trap vector base address must always be aligned on a 4-byte boundary 127 | .align 4 128 | trap_vector: 129 | # save context(registers). 130 | csrrw t6, mscratch, t6 # swap t6 and mscratch 131 | reg_save t6 132 | csrw mscratch, t6 133 | # call the C trap handler in trap.c 134 | csrr a0, mepc 135 | csrr a1, mcause 136 | call trap_handler 137 | 138 | # trap_handler will return the return address via a0. 139 | csrw mepc, a0 140 | 141 | # load context(registers). 142 | csrr t6, mscratch 143 | reg_load t6 144 | mret 145 | 146 | -------------------------------------------------------------------------------- /05-Preemptive/task.c: -------------------------------------------------------------------------------- 1 | #include "task.h" 2 | #include "lib.h" 3 | 4 | uint8_t task_stack[MAX_TASK][STACK_SIZE]; 5 | struct context ctx_os; 6 | struct context ctx_tasks[MAX_TASK]; 7 | struct context *ctx_now; 8 | int taskTop=0; // total number of task 9 | 10 | // create a new task 11 | int task_create(void (*task)(void)) 12 | { 13 | int i=taskTop++; 14 | ctx_tasks[i].ra = (reg_t) task; 15 | ctx_tasks[i].sp = (reg_t) &task_stack[i][STACK_SIZE-1]; 16 | return i; 17 | } 18 | 19 | // switch to task[i] 20 | void task_go(int i) { 21 | ctx_now = &ctx_tasks[i]; 22 | sys_switch(&ctx_os, &ctx_tasks[i]); 23 | } 24 | 25 | // switch back to os 26 | void task_os() { 27 | struct context *ctx = ctx_now; 28 | ctx_now = &ctx_os; 29 | sys_switch(ctx, &ctx_os); 30 | } 31 | -------------------------------------------------------------------------------- /05-Preemptive/task.h: -------------------------------------------------------------------------------- 1 | #ifndef __TASK_H__ 2 | #define __TASK_H__ 3 | 4 | #include "riscv.h" 5 | #include "sys.h" 6 | 7 | #define MAX_TASK 10 8 | #define STACK_SIZE 1024 9 | 10 | extern int taskTop; 11 | 12 | extern int task_create(void (*task)(void)); 13 | extern void task_go(int i); 14 | extern void task_os(); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /05-Preemptive/timer.c: -------------------------------------------------------------------------------- 1 | #include "timer.h" 2 | 3 | // a scratch area per CPU for machine-mode timer interrupts. 4 | reg_t timer_scratch[NCPU][5]; 5 | 6 | #define interval 20000000 // cycles; about 2 second in qemu. 7 | 8 | void timer_init() 9 | { 10 | // each CPU has a separate source of timer interrupts. 11 | int id = r_mhartid(); 12 | 13 | // ask the CLINT for a timer interrupt. 14 | // int interval = 1000000; // cycles; about 1/10th second in qemu. 15 | 16 | *(reg_t *)CLINT_MTIMECMP(id) = *(reg_t *)CLINT_MTIME + interval; 17 | 18 | // prepare information in scratch[] for timervec. 19 | // scratch[0..2] : space for timervec to save registers. 20 | // scratch[3] : address of CLINT MTIMECMP register. 21 | // scratch[4] : desired interval (in cycles) between timer interrupts. 22 | reg_t *scratch = &timer_scratch[id][0]; 23 | scratch[3] = CLINT_MTIMECMP(id); 24 | scratch[4] = interval; 25 | w_mscratch((reg_t)scratch); 26 | 27 | // enable machine-mode timer interrupts. 28 | w_mie(r_mie() | MIE_MTIE); 29 | } 30 | 31 | static int timer_count = 0; 32 | 33 | void timer_handler() 34 | { 35 | lib_printf("timer_handler: %d\n", ++timer_count); 36 | int id = r_mhartid(); 37 | *(reg_t *)CLINT_MTIMECMP(id) = *(reg_t *)CLINT_MTIME + interval; 38 | } 39 | -------------------------------------------------------------------------------- /05-Preemptive/timer.h: -------------------------------------------------------------------------------- 1 | #ifndef __TIMER_H__ 2 | #define __TIMER_H__ 3 | 4 | #include "riscv.h" 5 | #include "sys.h" 6 | #include "lib.h" 7 | #include "task.h" 8 | 9 | extern void timer_handler(); 10 | extern void timer_init(); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /05-Preemptive/trap.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | extern void trap_vector(); 3 | 4 | void trap_init() 5 | { 6 | // set the machine-mode trap handler. 7 | w_mtvec((reg_t)trap_vector); 8 | 9 | // enable machine-mode interrupts. 10 | w_mstatus(r_mstatus() | MSTATUS_MIE); 11 | } 12 | 13 | reg_t trap_handler(reg_t epc, reg_t cause) 14 | { 15 | reg_t return_pc = epc; 16 | reg_t cause_code = cause & 0xfff; 17 | 18 | if (cause & 0x80000000) 19 | { 20 | /* Asynchronous trap - interrupt */ 21 | switch (cause_code) 22 | { 23 | case 3: 24 | lib_puts("software interruption!\n"); 25 | break; 26 | case 7: 27 | lib_puts("timer interruption!\n"); 28 | // disable machine-mode timer interrupts. 29 | w_mie(~((~r_mie()) | (1 << 7))); 30 | timer_handler(); 31 | return_pc = (reg_t)&os_kernel; 32 | // enable machine-mode timer interrupts. 33 | w_mie(r_mie() | MIE_MTIE); 34 | break; 35 | case 11: 36 | lib_puts("external interruption!\n"); 37 | break; 38 | default: 39 | lib_puts("unknown async exception!\n"); 40 | break; 41 | } 42 | } 43 | else 44 | { 45 | /* Synchronous trap - exception */ 46 | lib_puts("Sync exceptions!\n"); 47 | while (1) 48 | { 49 | /* code */ 50 | } 51 | } 52 | return return_pc; 53 | } 54 | -------------------------------------------------------------------------------- /05-Preemptive/user.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | 3 | void user_task0(void) 4 | { 5 | lib_puts("Task0: Created!\n"); 6 | while (1) 7 | { 8 | lib_puts("Task0: Running...\n"); 9 | lib_delay(1000); 10 | } 11 | } 12 | 13 | void user_task1(void) 14 | { 15 | lib_puts("Task1: Created!\n"); 16 | while (1) 17 | { 18 | lib_puts("Task1: Running...\n"); 19 | lib_delay(1000); 20 | } 21 | } 22 | 23 | void user_init() 24 | { 25 | task_create(&user_task0); 26 | task_create(&user_task1); 27 | } 28 | -------------------------------------------------------------------------------- /06-Spinlock/Makefile: -------------------------------------------------------------------------------- 1 | CC = riscv64-unknown-elf-gcc 2 | CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 3 | GDB = riscv64-unknown-elf-gdb 4 | 5 | OBJ = \ 6 | start.s \ 7 | sys.s \ 8 | lib.c \ 9 | timer.c \ 10 | task.c \ 11 | os.c \ 12 | user.c \ 13 | trap.c \ 14 | lock.c 15 | 16 | QEMU = qemu-system-riscv32 17 | QFLAGS = -nographic -smp 4 -machine virt -bios none 18 | 19 | OBJDUMP = riscv64-unknown-elf-objdump 20 | 21 | all: os.elf 22 | 23 | os.elf: $(OBJ) 24 | $(CC) $(CFLAGS) -g -Wall -T os.ld -o os.elf $^ 25 | 26 | qemu: $(TARGET) 27 | @qemu-system-riscv32 -M ? | grep virt >/dev/null || exit 28 | @echo "Press Ctrl-A and then X to exit QEMU" 29 | $(QEMU) $(QFLAGS) -kernel os.elf 30 | 31 | clean: 32 | rm -f *.elf 33 | 34 | .PHONY : debug 35 | debug: all 36 | @echo "Press Ctrl-C and then input 'quit' to exit GDB and QEMU" 37 | @echo "-------------------------------------------------------" 38 | @${QEMU} ${QFLAGS} -kernel os.elf -s -S & 39 | @${GDB} os.elf -q -x ./gdbinit -------------------------------------------------------------------------------- /06-Spinlock/README.md: -------------------------------------------------------------------------------- 1 | # 06-Spinlock 2 | 3 | ## Build & Run 4 | 5 | ```sh 6 | IAN@DESKTOP-9AEMEPL MINGW64 ~/Desktop/mini-riscv-os/06-Spinlock (feat/spinlock) 7 | $ make 8 | riscv64-unknown-elf-gcc -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall -T os.ld -o os.elf start.s sys.s lib.c timer.c task.c os.c user.c trap.c lock.c 9 | 10 | IAN@DESKTOP-9AEMEPL MINGW64 ~/Desktop/mini-riscv-os/06-Spinlock (feat/spinlock) 11 | $ make qemu 12 | Press Ctrl-A and then X to exit QEMU 13 | qemu-system-riscv32 -nographic -smp 4 -machine virt -bios none -kernel os.elf 14 | OS start 15 | OS: Activate next task 16 | Task0: Created! 17 | Task0: Running... 18 | Task0: Running... 19 | Task0: Running... 20 | Task0: Running... 21 | Task0: Running... 22 | Task0: Running... 23 | Task0: Running... 24 | Task0: Running... 25 | Task0: Running... 26 | Task0: Running... 27 | Task0: Running... 28 | Task0: Running... 29 | Task0: Running... 30 | Task0: Running... 31 | timer interruption! 32 | timer_handler: 1 33 | OS: Back to OS 34 | 35 | OS: Activate next task 36 | Task1: Created! 37 | Task1: Running... 38 | Task1: Running... 39 | Task1: Running... 40 | Task1: Running... 41 | Task1: Running... 42 | Task1: Running... 43 | Task1: Running... 44 | Task1: Running... 45 | Task1: Running... 46 | Task1: Running... 47 | Task1: Running... 48 | Task1: Running... 49 | Task1: Running... 50 | Task1: Running... 51 | timer interruption! 52 | timer_handler: 2 53 | OS: Back to OS 54 | 55 | OS: Activate next task 56 | Task2: Created! 57 | The value of shared_var is: 550 58 | The value of shared_var is: 600 59 | The value of shared_var is: 650 60 | The value of shared_var is: 700 61 | The value of shared_var is: 750 62 | The value of shared_var is: 800 63 | The value of shared_var is: 850 64 | The value of shared_var is: 900 65 | The value of shared_var is: 950 66 | The value of shared_var is: 1000 67 | The value of shared_var is: 1050 68 | The value of shared_var is: 1100 69 | The value of shared_var is: 1150 70 | The value of shared_var is: 1200 71 | The value of shared_var is: 1250 72 | The value of shared_var is: 1300 73 | The value of shared_var is: 1350 74 | The value of shared_var is: 1400 75 | The value of shared_var is: 1450 76 | The value of shared_var is: 1500 77 | The value of shared_var is: 1550 78 | The value of shared_var is: 1600 79 | timer interruption! 80 | timer_handler: 3 81 | OS: Back to OS 82 | 83 | OS: Activate next task 84 | Task0: Running... 85 | Task0: Running... 86 | Task0: Running... 87 | Task0: Running... 88 | Task0: Running... 89 | Task0: Running... 90 | Task0: Running... 91 | Task0: Running... 92 | Task0: Running... 93 | Task0: Running... 94 | Task0: Running... 95 | Task0: Running... 96 | Task0: Running... 97 | Task0: Running... 98 | timer interruption! 99 | timer_handler: 4 100 | OS: Back to OS 101 | QEMU: Terminated 102 | ``` 103 | 104 | ## Debug mode 105 | 106 | ```sh 107 | make debug 108 | riscv64-unknown-elf-gcc -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall -T os.ld -o os.elf start.s sys.s lib.c timer.c task.c os.c user.c trap.c lock.c 109 | Press Ctrl-C and then input 'quit' to exit GDB and QEMU 110 | ------------------------------------------------------- 111 | Reading symbols from os.elf... 112 | Breakpoint 1 at 0x80000000: file start.s, line 7. 113 | 0x00001000 in ?? () 114 | => 0x00001000: 97 02 00 00 auipc t0,0x0 115 | 116 | Thread 1 hit Breakpoint 1, _start () at start.s:7 117 | 7 csrr t0, mhartid # read current hart id 118 | => 0x80000000 <_start+0>: f3 22 40 f1 csrr t0,mhartid 119 | (gdb) 120 | ``` 121 | 122 | ### set the breakpoint 123 | 124 | You can set the breakpoint in any c file: 125 | 126 | ```sh 127 | (gdb) b trap.c:27 128 | Breakpoint 2 at 0x80008f78: file trap.c, line 27. 129 | (gdb) 130 | ``` 131 | 132 | As the example above, when process running on trap.c, line 27 (Timer Interrupt). 133 | The process will be suspended automatically until you press the key `c` (continue) or `s` (step). 134 | -------------------------------------------------------------------------------- /06-Spinlock/gdbinit: -------------------------------------------------------------------------------- 1 | set disassemble-next-line on 2 | b _start 3 | target remote : 1234 4 | c -------------------------------------------------------------------------------- /06-Spinlock/lib.c: -------------------------------------------------------------------------------- 1 | #include "lib.h" 2 | 3 | void lib_delay(volatile int count) 4 | { 5 | count *= 50000; 6 | while (count--); 7 | } 8 | 9 | int lib_putc(char ch) { 10 | while ((*UART_LSR & UART_LSR_EMPTY_MASK) == 0); 11 | return *UART_THR = ch; 12 | } 13 | 14 | void lib_puts(char *s) { 15 | while (*s) lib_putc(*s++); 16 | } 17 | 18 | 19 | int lib_vsnprintf(char * out, size_t n, const char* s, va_list vl) 20 | { 21 | int format = 0; 22 | int longarg = 0; 23 | size_t pos = 0; 24 | for( ; *s; s++) { 25 | if (format) { 26 | switch(*s) { 27 | case 'l': { 28 | longarg = 1; 29 | break; 30 | } 31 | case 'p': { 32 | longarg = 1; 33 | if (out && pos < n) { 34 | out[pos] = '0'; 35 | } 36 | pos++; 37 | if (out && pos < n) { 38 | out[pos] = 'x'; 39 | } 40 | pos++; 41 | } 42 | case 'x': { 43 | long num = longarg ? va_arg(vl, long) : va_arg(vl, int); 44 | int hexdigits = 2*(longarg ? sizeof(long) : sizeof(int))-1; 45 | for(int i = hexdigits; i >= 0; i--) { 46 | int d = (num >> (4*i)) & 0xF; 47 | if (out && pos < n) { 48 | out[pos] = (d < 10 ? '0'+d : 'a'+d-10); 49 | } 50 | pos++; 51 | } 52 | longarg = 0; 53 | format = 0; 54 | break; 55 | } 56 | case 'd': { 57 | long num = longarg ? va_arg(vl, long) : va_arg(vl, int); 58 | if (num < 0) { 59 | num = -num; 60 | if (out && pos < n) { 61 | out[pos] = '-'; 62 | } 63 | pos++; 64 | } 65 | long digits = 1; 66 | for (long nn = num; nn /= 10; digits++) 67 | ; 68 | for (int i = digits-1; i >= 0; i--) { 69 | if (out && pos + i < n) { 70 | out[pos + i] = '0' + (num % 10); 71 | } 72 | num /= 10; 73 | } 74 | pos += digits; 75 | longarg = 0; 76 | format = 0; 77 | break; 78 | } 79 | case 's': { 80 | const char* s2 = va_arg(vl, const char*); 81 | while (*s2) { 82 | if (out && pos < n) { 83 | out[pos] = *s2; 84 | } 85 | pos++; 86 | s2++; 87 | } 88 | longarg = 0; 89 | format = 0; 90 | break; 91 | } 92 | case 'c': { 93 | if (out && pos < n) { 94 | out[pos] = (char)va_arg(vl,int); 95 | } 96 | pos++; 97 | longarg = 0; 98 | format = 0; 99 | break; 100 | } 101 | default: 102 | break; 103 | } 104 | } 105 | else if(*s == '%') { 106 | format = 1; 107 | } 108 | else { 109 | if (out && pos < n) { 110 | out[pos] = *s; 111 | } 112 | pos++; 113 | } 114 | } 115 | if (out && pos < n) { 116 | out[pos] = 0; 117 | } 118 | else if (out && n) { 119 | out[n-1] = 0; 120 | } 121 | return pos; 122 | } 123 | 124 | static char out_buf[1000]; // buffer for lib_vprintf() 125 | 126 | int lib_vprintf(const char* s, va_list vl) 127 | { 128 | int res = lib_vsnprintf(NULL, -1, s, vl); 129 | if (res+1 >= sizeof(out_buf)) { 130 | lib_puts("error: lib_vprintf() output string size overflow\n"); 131 | while(1) {} 132 | } 133 | lib_vsnprintf(out_buf, res + 1, s, vl); 134 | lib_puts(out_buf); 135 | return res; 136 | } 137 | 138 | int lib_printf(const char* s, ...) 139 | { 140 | int res = 0; 141 | va_list vl; 142 | va_start(vl, s); 143 | res = lib_vprintf(s, vl); 144 | va_end(vl); 145 | return res; 146 | } 147 | -------------------------------------------------------------------------------- /06-Spinlock/lib.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIB_H__ 2 | #define __LIB_H__ 3 | 4 | #include "riscv.h" 5 | #include 6 | #include 7 | 8 | #define lib_error(...) { lib_printf(__VA_ARGS__); while(1) {} } } 9 | 10 | extern void lib_delay(volatile int count); 11 | extern int lib_putc(char ch); 12 | extern void lib_puts(char *s); 13 | extern int lib_printf(const char* s, ...); 14 | extern int lib_vprintf(const char* s, va_list vl); 15 | extern int lib_vsnprintf(char * out, size_t n, const char* s, va_list vl); 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /06-Spinlock/lock.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | 3 | void lock_init(lock_t *lock) 4 | { 5 | lock->locked = 0; 6 | } 7 | 8 | void lock_acquire(lock_t *lock) 9 | { 10 | for (;;) 11 | { 12 | if (!atomic_swap(lock)) 13 | { 14 | break; 15 | } 16 | } 17 | } 18 | 19 | void lock_free(lock_t *lock) 20 | { 21 | lock->locked = 0; 22 | } 23 | 24 | void basic_lock() 25 | { 26 | w_mstatus(r_mstatus() & ~MSTATUS_MIE); 27 | } 28 | 29 | void basic_unlock() 30 | { 31 | w_mstatus(r_mstatus() | MSTATUS_MIE); 32 | } 33 | -------------------------------------------------------------------------------- /06-Spinlock/os.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | 3 | extern void trap_init(void); 4 | 5 | void os_kernel() 6 | { 7 | task_os(); 8 | } 9 | 10 | void os_start() 11 | { 12 | lib_puts("OS start\n"); 13 | user_init(); 14 | trap_init(); 15 | timer_init(); // start timer interrupt ... 16 | } 17 | 18 | int os_main(void) 19 | { 20 | os_start(); 21 | 22 | int current_task = 0; 23 | while (1) 24 | { 25 | lib_puts("OS: Activate next task\n"); 26 | task_go(current_task); 27 | lib_puts("OS: Back to OS\n"); 28 | current_task = (current_task + 1) % taskTop; // Round Robin Scheduling 29 | lib_puts("\n"); 30 | } 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /06-Spinlock/os.h: -------------------------------------------------------------------------------- 1 | #ifndef __OS_H__ 2 | #define __OS_H__ 3 | 4 | #include "riscv.h" 5 | #include "lib.h" 6 | #include "task.h" 7 | #include "timer.h" 8 | 9 | extern void user_init(); 10 | extern void os_kernel(); 11 | extern int os_main(void); 12 | extern void basic_lock(); 13 | extern void basic_unlock(); 14 | 15 | typedef struct lock 16 | { 17 | volatile int locked; 18 | } lock_t; 19 | 20 | extern int atomic_swap(lock_t *); 21 | 22 | extern void lock_init(lock_t *lock); 23 | 24 | extern void lock_acquire(lock_t *lock); 25 | 26 | extern void lock_free(lock_t *lock); 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /06-Spinlock/os.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_ARCH( "riscv" ) 2 | 3 | ENTRY( _start ) 4 | 5 | MEMORY 6 | { 7 | ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M 8 | } 9 | 10 | PHDRS 11 | { 12 | text PT_LOAD; 13 | data PT_LOAD; 14 | bss PT_LOAD; 15 | } 16 | 17 | SECTIONS 18 | { 19 | .text : { 20 | PROVIDE(_text_start = .); 21 | *(.text.init) *(.text .text.*) 22 | PROVIDE(_text_end = .); 23 | } >ram AT>ram :text 24 | 25 | .rodata : { 26 | PROVIDE(_rodata_start = .); 27 | *(.rodata .rodata.*) 28 | PROVIDE(_rodata_end = .); 29 | } >ram AT>ram :text 30 | 31 | .data : { 32 | . = ALIGN(4096); 33 | PROVIDE(_data_start = .); 34 | *(.sdata .sdata.*) *(.data .data.*) 35 | PROVIDE(_data_end = .); 36 | } >ram AT>ram :data 37 | 38 | .bss :{ 39 | PROVIDE(_bss_start = .); 40 | *(.sbss .sbss.*) *(.bss .bss.*) 41 | PROVIDE(_bss_end = .); 42 | } >ram AT>ram :bss 43 | 44 | PROVIDE(_memory_start = ORIGIN(ram)); 45 | PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram)); 46 | } 47 | -------------------------------------------------------------------------------- /06-Spinlock/riscv.h: -------------------------------------------------------------------------------- 1 | #ifndef __RISCV_H__ 2 | #define __RISCV_H__ 3 | 4 | #include 5 | 6 | #define reg_t uint32_t // RISCV32: register is 32bits 7 | // define reg_t as uint64_t // RISCV64: register is 64bits 8 | 9 | // ref: https://www.activexperts.com/serial-port-component/tutorials/uart/ 10 | #define UART 0x10000000 11 | #define UART_THR (uint8_t*)(UART+0x00) // THR:transmitter holding register 12 | #define UART_LSR (uint8_t*)(UART+0x05) // LSR:line status register 13 | #define UART_LSR_EMPTY_MASK 0x40 // LSR Bit 6: Transmitter empty; both the THR and LSR are empty 14 | 15 | // Saved registers for kernel context switches. 16 | struct context { 17 | reg_t ra; 18 | reg_t sp; 19 | 20 | // callee-saved 21 | reg_t s0; 22 | reg_t s1; 23 | reg_t s2; 24 | reg_t s3; 25 | reg_t s4; 26 | reg_t s5; 27 | reg_t s6; 28 | reg_t s7; 29 | reg_t s8; 30 | reg_t s9; 31 | reg_t s10; 32 | reg_t s11; 33 | }; 34 | 35 | // ref: https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/riscv.h 36 | // 37 | // local interrupt controller, which contains the timer. 38 | // ================== Timer Interrput ==================== 39 | 40 | #define NCPU 8 // maximum number of CPUs 41 | #define CLINT 0x2000000 42 | #define CLINT_MTIMECMP(hartid) (CLINT + 0x4000 + 4*(hartid)) 43 | #define CLINT_MTIME (CLINT + 0xBFF8) // cycles since boot. 44 | 45 | // which hart (core) is this? 46 | static inline reg_t r_mhartid() 47 | { 48 | reg_t x; 49 | asm volatile("csrr %0, mhartid" : "=r" (x) ); 50 | return x; 51 | } 52 | 53 | // Machine Status Register, mstatus 54 | #define MSTATUS_MPP_MASK (3 << 11) // previous mode. 55 | #define MSTATUS_MPP_M (3 << 11) 56 | #define MSTATUS_MPP_S (1 << 11) 57 | #define MSTATUS_MPP_U (0 << 11) 58 | #define MSTATUS_MIE (1 << 3) // machine-mode interrupt enable. 59 | 60 | static inline reg_t r_mstatus() 61 | { 62 | reg_t x; 63 | asm volatile("csrr %0, mstatus" : "=r" (x) ); 64 | return x; 65 | } 66 | 67 | static inline void w_mstatus(reg_t x) 68 | { 69 | asm volatile("csrw mstatus, %0" : : "r" (x)); 70 | } 71 | 72 | // machine exception program counter, holds the 73 | // instruction address to which a return from 74 | // exception will go. 75 | static inline void w_mepc(reg_t x) 76 | { 77 | asm volatile("csrw mepc, %0" : : "r" (x)); 78 | } 79 | 80 | static inline reg_t r_mepc() 81 | { 82 | reg_t x; 83 | asm volatile("csrr %0, mepc" : "=r" (x)); 84 | return x; 85 | } 86 | 87 | // Machine Scratch register, for early trap handler 88 | static inline void w_mscratch(reg_t x) 89 | { 90 | asm volatile("csrw mscratch, %0" : : "r" (x)); 91 | } 92 | 93 | // Machine-mode interrupt vector 94 | static inline void w_mtvec(reg_t x) 95 | { 96 | asm volatile("csrw mtvec, %0" : : "r" (x)); 97 | } 98 | 99 | // Machine-mode Interrupt Enable 100 | #define MIE_MEIE (1 << 11) // external 101 | #define MIE_MTIE (1 << 7) // timer 102 | #define MIE_MSIE (1 << 3) // software 103 | 104 | static inline reg_t r_mie() 105 | { 106 | reg_t x; 107 | asm volatile("csrr %0, mie" : "=r" (x) ); 108 | return x; 109 | } 110 | 111 | static inline void w_mie(reg_t x) 112 | { 113 | asm volatile("csrw mie, %0" : : "r" (x)); 114 | } 115 | 116 | #endif 117 | -------------------------------------------------------------------------------- /06-Spinlock/start.s: -------------------------------------------------------------------------------- 1 | .equ STACK_SIZE, 8192 2 | 3 | .global _start 4 | 5 | _start: 6 | csrr a0, mhartid # 讀取核心代號 7 | bnez a0, park # 若不是 0 號核心,跳到 park 停止 8 | la sp, stacks + STACK_SIZE # 0 號核心設定堆疊 9 | j os_main # 0 號核心跳到主程式 os_main 10 | 11 | park: 12 | wfi 13 | j park 14 | 15 | stacks: 16 | .skip STACK_SIZE # 分配堆疊空間 17 | -------------------------------------------------------------------------------- /06-Spinlock/sys.h: -------------------------------------------------------------------------------- 1 | #ifndef __SYS_H__ 2 | #define __SYS_H__ 3 | 4 | #include "riscv.h" 5 | extern void sys_timer(); 6 | extern void sys_switch(struct context *ctx_old, struct context *ctx_new); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /06-Spinlock/sys.s: -------------------------------------------------------------------------------- 1 | # This Code derived from xv6-riscv (64bit) 2 | # -- https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/swtch.S 3 | 4 | # ============ MACRO ================== 5 | .macro ctx_save base 6 | sw ra, 0(\base) 7 | sw sp, 4(\base) 8 | sw s0, 8(\base) 9 | sw s1, 12(\base) 10 | sw s2, 16(\base) 11 | sw s3, 20(\base) 12 | sw s4, 24(\base) 13 | sw s5, 28(\base) 14 | sw s6, 32(\base) 15 | sw s7, 36(\base) 16 | sw s8, 40(\base) 17 | sw s9, 44(\base) 18 | sw s10, 48(\base) 19 | sw s11, 52(\base) 20 | .endm 21 | 22 | .macro ctx_load base 23 | lw ra, 0(\base) 24 | lw sp, 4(\base) 25 | lw s0, 8(\base) 26 | lw s1, 12(\base) 27 | lw s2, 16(\base) 28 | lw s3, 20(\base) 29 | lw s4, 24(\base) 30 | lw s5, 28(\base) 31 | lw s6, 32(\base) 32 | lw s7, 36(\base) 33 | lw s8, 40(\base) 34 | lw s9, 44(\base) 35 | lw s10, 48(\base) 36 | lw s11, 52(\base) 37 | .endm 38 | 39 | .macro reg_save base 40 | # save the registers. 41 | sw ra, 0(\base) 42 | sw sp, 4(\base) 43 | sw gp, 8(\base) 44 | sw tp, 12(\base) 45 | sw t0, 16(\base) 46 | sw t1, 20(\base) 47 | sw t2, 24(\base) 48 | sw s0, 28(\base) 49 | sw s1, 32(\base) 50 | sw a0, 36(\base) 51 | sw a1, 40(\base) 52 | sw a2, 44(\base) 53 | sw a3, 48(\base) 54 | sw a4, 52(\base) 55 | sw a5, 56(\base) 56 | sw a6, 60(\base) 57 | sw a7, 64(\base) 58 | sw s2, 68(\base) 59 | sw s3, 72(\base) 60 | sw s4, 76(\base) 61 | sw s5, 80(\base) 62 | sw s6, 84(\base) 63 | sw s7, 88(\base) 64 | sw s8, 92(\base) 65 | sw s9, 96(\base) 66 | sw s10, 100(\base) 67 | sw s11, 104(\base) 68 | sw t3, 108(\base) 69 | sw t4, 112(\base) 70 | sw t5, 116(\base) 71 | sw t6, 120(\base) 72 | .endm 73 | 74 | .macro reg_load base 75 | # restore registers. 76 | lw ra, 0(\base) 77 | lw sp, 4(\base) 78 | lw gp, 8(\base) 79 | # not this, in case we moved CPUs: lw tp, 12(\base) 80 | lw t0, 16(\base) 81 | lw t1, 20(\base) 82 | lw t2, 24(\base) 83 | lw s0, 28(\base) 84 | lw s1, 32(\base) 85 | lw a0, 36(\base) 86 | lw a1, 40(\base) 87 | lw a2, 44(\base) 88 | lw a3, 48(\base) 89 | lw a4, 52(\base) 90 | lw a5, 56(\base) 91 | lw a6, 60(\base) 92 | lw a7, 64(\base) 93 | lw s2, 68(\base) 94 | lw s3, 72(\base) 95 | lw s4, 76(\base) 96 | lw s5, 80(\base) 97 | lw s6, 84(\base) 98 | lw s7, 88(\base) 99 | lw s8, 92(\base) 100 | lw s9, 96(\base) 101 | lw s10, 100(\base) 102 | lw s11, 104(\base) 103 | lw t3, 108(\base) 104 | lw t4, 112(\base) 105 | lw t5, 116(\base) 106 | lw t6, 120(\base) 107 | .endm 108 | # ============ Macro END ================== 109 | 110 | # Context switch 111 | # 112 | # void sys_switch(struct context *old, struct context *new); 113 | # 114 | # Save current registers in old. Load from new. 115 | 116 | .globl sys_switch 117 | .align 4 118 | sys_switch: 119 | 120 | ctx_save a0 # a0 => struct context *old 121 | ctx_load a1 # a1 => struct context *new 122 | ret # pc=ra; swtch to new task (new->ra) 123 | 124 | .globl atomic_swap 125 | .align 4 126 | atomic_swap: 127 | li a5, 1 128 | amoswap.w.aq a5, a5, 0(a0) 129 | mv a0, a5 130 | ret 131 | 132 | .globl trap_vector 133 | # the trap vector base address must always be aligned on a 4-byte boundary 134 | .align 4 135 | trap_vector: 136 | # save context(registers). 137 | csrrw t6, mscratch, t6 # swap t6 and mscratch 138 | reg_save t6 139 | csrw mscratch, t6 140 | # call the C trap handler in trap.c 141 | csrr a0, mepc 142 | csrr a1, mcause 143 | call trap_handler 144 | 145 | # trap_handler will return the return address via a0. 146 | csrw mepc, a0 147 | 148 | # load context(registers). 149 | csrr t6, mscratch 150 | reg_load t6 151 | mret 152 | 153 | -------------------------------------------------------------------------------- /06-Spinlock/task.c: -------------------------------------------------------------------------------- 1 | #include "task.h" 2 | #include "lib.h" 3 | 4 | uint8_t task_stack[MAX_TASK][STACK_SIZE]; 5 | struct context ctx_os; 6 | struct context ctx_tasks[MAX_TASK]; 7 | struct context *ctx_now; 8 | int taskTop = 0; // total number of task 9 | 10 | // create a new task 11 | int task_create(void (*task)(void)) 12 | { 13 | int i = taskTop++; 14 | ctx_tasks[i].ra = (reg_t)task; 15 | ctx_tasks[i].sp = (reg_t)&task_stack[i][STACK_SIZE - 1]; 16 | return i; 17 | } 18 | 19 | // switch to task[i] 20 | void task_go(int i) 21 | { 22 | ctx_now = &ctx_tasks[i]; 23 | sys_switch(&ctx_os, &ctx_tasks[i]); 24 | } 25 | 26 | // switch back to os 27 | void task_os() 28 | { 29 | struct context *ctx = ctx_now; 30 | ctx_now = &ctx_os; 31 | sys_switch(ctx, &ctx_os); 32 | } 33 | -------------------------------------------------------------------------------- /06-Spinlock/task.h: -------------------------------------------------------------------------------- 1 | #ifndef __TASK_H__ 2 | #define __TASK_H__ 3 | 4 | #include "riscv.h" 5 | #include "sys.h" 6 | 7 | #define MAX_TASK 10 8 | #define STACK_SIZE 1024 9 | 10 | extern int taskTop; 11 | 12 | extern int task_create(void (*task)(void)); 13 | extern void task_go(int i); 14 | extern void task_os(); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /06-Spinlock/timer.c: -------------------------------------------------------------------------------- 1 | #include "timer.h" 2 | 3 | // a scratch area per CPU for machine-mode timer interrupts. 4 | reg_t timer_scratch[NCPU][5]; 5 | 6 | #define interval 20000000 // cycles; about 2 second in qemu. 7 | 8 | void timer_init() 9 | { 10 | // each CPU has a separate source of timer interrupts. 11 | int id = r_mhartid(); 12 | 13 | // ask the CLINT for a timer interrupt. 14 | // int interval = 1000000; // cycles; about 1/10th second in qemu. 15 | 16 | *(reg_t *)CLINT_MTIMECMP(id) = *(reg_t *)CLINT_MTIME + interval; 17 | 18 | // prepare information in scratch[] for timervec. 19 | // scratch[0..2] : space for timervec to save registers. 20 | // scratch[3] : address of CLINT MTIMECMP register. 21 | // scratch[4] : desired interval (in cycles) between timer interrupts. 22 | reg_t *scratch = &timer_scratch[id][0]; 23 | scratch[3] = CLINT_MTIMECMP(id); 24 | scratch[4] = interval; 25 | w_mscratch((reg_t)scratch); 26 | 27 | // enable machine-mode timer interrupts. 28 | w_mie(r_mie() | MIE_MTIE); 29 | } 30 | 31 | static int timer_count = 0; 32 | 33 | void timer_handler() 34 | { 35 | lib_printf("timer_handler: %d\n", ++timer_count); 36 | int id = r_mhartid(); 37 | *(reg_t *)CLINT_MTIMECMP(id) = *(reg_t *)CLINT_MTIME + interval; 38 | } 39 | -------------------------------------------------------------------------------- /06-Spinlock/timer.h: -------------------------------------------------------------------------------- 1 | #ifndef __TIMER_H__ 2 | #define __TIMER_H__ 3 | 4 | #include "riscv.h" 5 | #include "sys.h" 6 | #include "lib.h" 7 | #include "task.h" 8 | 9 | extern void timer_handler(); 10 | extern void timer_init(); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /06-Spinlock/trap.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | extern void trap_vector(); 3 | 4 | void trap_init() 5 | { 6 | // set the machine-mode trap handler. 7 | w_mtvec((reg_t)trap_vector); 8 | 9 | // enable machine-mode interrupts. 10 | w_mstatus(r_mstatus() | MSTATUS_MIE); 11 | } 12 | 13 | reg_t trap_handler(reg_t epc, reg_t cause) 14 | { 15 | reg_t return_pc = epc; 16 | reg_t cause_code = cause & 0xfff; 17 | 18 | if (cause & 0x80000000) 19 | { 20 | /* Asynchronous trap - interrupt */ 21 | switch (cause_code) 22 | { 23 | case 3: 24 | lib_puts("software interruption!\n"); 25 | break; 26 | case 7: 27 | lib_puts("timer interruption!\n"); 28 | // disable machine-mode timer interrupts. 29 | w_mie(~((~r_mie()) | (1 << 7))); 30 | timer_handler(); 31 | return_pc = (reg_t)&os_kernel; 32 | // enable machine-mode timer interrupts. 33 | w_mie(r_mie() | MIE_MTIE); 34 | break; 35 | case 11: 36 | lib_puts("external interruption!\n"); 37 | break; 38 | default: 39 | lib_puts("unknown async exception!\n"); 40 | break; 41 | } 42 | } 43 | else 44 | { 45 | /* Synchronous trap - exception */ 46 | lib_puts("Sync exceptions!\n"); 47 | while (1) 48 | { 49 | /* code */ 50 | } 51 | } 52 | return return_pc; 53 | } 54 | -------------------------------------------------------------------------------- /06-Spinlock/user.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | 3 | int shared_var = 500; 4 | 5 | lock_t lock; 6 | 7 | void user_task0(void) 8 | { 9 | lib_puts("Task0: Created!\n"); 10 | while (1) 11 | { 12 | lib_puts("Task0: Running...\n"); 13 | lib_delay(1000); 14 | } 15 | } 16 | 17 | void user_task1(void) 18 | { 19 | lib_puts("Task1: Created!\n"); 20 | while (1) 21 | { 22 | lib_puts("Task1: Running...\n"); 23 | lib_delay(1000); 24 | } 25 | } 26 | 27 | void user_task2(void) 28 | { 29 | lib_puts("Task2: Created!\n"); 30 | while (1) 31 | { 32 | for (int i = 0; i < 50; i++) 33 | { 34 | lock_acquire(&lock); 35 | shared_var++; 36 | lock_free(&lock); 37 | lib_delay(100); 38 | } 39 | lib_printf("The value of shared_var is: %d \n", shared_var); 40 | } 41 | } 42 | 43 | void user_task3(void) 44 | { 45 | lib_puts("Task3: Created!\n"); 46 | while (1) 47 | { 48 | lib_puts("Tryin to get the lock... \n"); 49 | lock_acquire(&lock); 50 | lib_puts("Get the lock!\n"); 51 | lock_free(&lock); 52 | lib_delay(1000); 53 | } 54 | } 55 | 56 | void user_init() 57 | { 58 | lock_init(&lock); 59 | task_create(&user_task0); 60 | task_create(&user_task1); 61 | task_create(&user_task2); 62 | task_create(&user_task3); 63 | } 64 | -------------------------------------------------------------------------------- /07-ExterInterrupt/Makefile: -------------------------------------------------------------------------------- 1 | CC = riscv64-unknown-elf-gcc 2 | CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall 3 | GDB = riscv64-unknown-elf-gdb 4 | 5 | OBJ = \ 6 | start.s \ 7 | sys.s \ 8 | lib.c \ 9 | timer.c \ 10 | task.c \ 11 | os.c \ 12 | user.c \ 13 | trap.c \ 14 | lock.c \ 15 | plic.c 16 | 17 | QEMU = qemu-system-riscv32 18 | QFLAGS = -nographic -smp 4 -machine virt -bios none 19 | 20 | OBJDUMP = riscv64-unknown-elf-objdump 21 | 22 | all: os.elf 23 | 24 | os.elf: $(OBJ) 25 | $(CC) $(CFLAGS) -T os.ld -o os.elf $^ 26 | 27 | qemu: $(TARGET) 28 | @qemu-system-riscv32 -M ? | grep virt >/dev/null || exit 29 | @echo "Press Ctrl-A and then X to exit QEMU" 30 | $(QEMU) $(QFLAGS) -kernel os.elf 31 | 32 | clean: 33 | rm -f *.elf 34 | 35 | .PHONY : debug 36 | debug: all 37 | @echo "Press Ctrl-C and then input 'quit' to exit GDB and QEMU" 38 | @echo "-------------------------------------------------------" 39 | @${QEMU} ${QFLAGS} -kernel os.elf -s -S & 40 | @${GDB} os.elf -q -x ./gdbinit -------------------------------------------------------------------------------- /07-ExterInterrupt/README.md: -------------------------------------------------------------------------------- 1 | # 07-ExternInterrupt 2 | 3 | ## Build & Run 4 | 5 | ```sh 6 | IAN@DESKTOP-9AEMEPL MINGW64 ~/Desktop/mini-riscv-os/07-ExterInterrupt (feat/getchar) 7 | $ make 8 | riscv64-unknown-elf-gcc -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall -T os.ld -o os.elf start.s sys.s lib.c timer.c task.c os.c user.c trap.c lock.c plic.c 9 | 10 | IAN@DESKTOP-9AEMEPL MINGW64 ~/Desktop/mini-riscv-os/07-ExterInterrupt (feat/getchar) 11 | $ make qemu 12 | Press Ctrl-A and then X to exit QEMU 13 | qemu-system-riscv32 -nographic -smp 4 -machine virt -bios none -kernel os.elf 14 | OS start 15 | OS: Activate next task 16 | Task0: Created! 17 | Task0: Running... 18 | Task0: Running... 19 | Task0: Running... 20 | Task0: Running... 21 | Task0: Running... 22 | external interruption! 23 | j 24 | Task0: Running... 25 | Task0: Running... 26 | external interruption! 27 | k 28 | Task0: Running... 29 | Task0: Running... 30 | Task0: Running... 31 | external interruption! 32 | j 33 | Task0: Running... 34 | external interruption! 35 | k 36 | external interruption! 37 | j 38 | Task0: Running... 39 | timer interruption! 40 | timer_handler: 1 41 | OS: Back to OS 42 | QEMU: Terminated 43 | ``` 44 | 45 | ## Debug mode 46 | 47 | ```sh 48 | make debug 49 | riscv64-unknown-elf-gcc -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall -T os.ld -o os.elf start.s sys.s lib.c timer.c task.c os.c user.c trap.c lock.c 50 | Press Ctrl-C and then input 'quit' to exit GDB and QEMU 51 | ------------------------------------------------------- 52 | Reading symbols from os.elf... 53 | Breakpoint 1 at 0x80000000: file start.s, line 7. 54 | 0x00001000 in ?? () 55 | => 0x00001000: 97 02 00 00 auipc t0,0x0 56 | 57 | Thread 1 hit Breakpoint 1, _start () at start.s:7 58 | 7 csrr t0, mhartid # read current hart id 59 | => 0x80000000 <_start+0>: f3 22 40 f1 csrr t0,mhartid 60 | (gdb) 61 | ``` 62 | 63 | ### set the breakpoint 64 | 65 | You can set the breakpoint in any c file: 66 | 67 | ```sh 68 | (gdb) b trap.c:27 69 | Breakpoint 2 at 0x80008f78: file trap.c, line 27. 70 | (gdb) 71 | ``` 72 | 73 | As the example above, when process running on trap.c, line 27 (Timer Interrupt). 74 | The process will be suspended automatically until you press the key `c` (continue) or `s` (step). 75 | -------------------------------------------------------------------------------- /07-ExterInterrupt/gdbinit: -------------------------------------------------------------------------------- 1 | set disassemble-next-line on 2 | b _start 3 | target remote : 1234 4 | c -------------------------------------------------------------------------------- /07-ExterInterrupt/lib.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIB_H__ 2 | #define __LIB_H__ 3 | 4 | #include "riscv.h" 5 | #include 6 | #include 7 | 8 | #define lib_error(...) \ 9 | { \ 10 | lib_printf(__VA_ARGS__); \ 11 | while (1) \ 12 | { \ 13 | } \ 14 | } \ 15 | } 16 | 17 | extern char *lib_gets(char *); 18 | extern void uart_init(); 19 | extern void lib_isr(void); 20 | extern int lib_getc(void); 21 | extern void lib_delay(volatile int count); 22 | extern int lib_putc(char ch); 23 | extern void lib_puts(char *s); 24 | extern int lib_printf(const char *s, ...); 25 | extern int lib_vprintf(const char *s, va_list vl); 26 | extern int lib_vsnprintf(char *out, size_t n, const char *s, va_list vl); 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /07-ExterInterrupt/lock.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | 3 | void lock_init(lock_t *lock) 4 | { 5 | lock->locked = 0; 6 | } 7 | 8 | void lock_acquire(lock_t *lock) 9 | { 10 | for (;;) 11 | { 12 | if (!atomic_swap(lock)) 13 | { 14 | break; 15 | } 16 | } 17 | } 18 | 19 | void lock_free(lock_t *lock) 20 | { 21 | lock->locked = 0; 22 | } 23 | 24 | void basic_lock() 25 | { 26 | w_mstatus(r_mstatus() & ~MSTATUS_MIE); 27 | } 28 | 29 | void basic_unlock() 30 | { 31 | w_mstatus(r_mstatus() | MSTATUS_MIE); 32 | } 33 | -------------------------------------------------------------------------------- /07-ExterInterrupt/os.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | 3 | extern void trap_init(void); 4 | 5 | void panic(char *s) 6 | { 7 | lib_puts(s); 8 | for (;;) 9 | { 10 | } 11 | } 12 | 13 | void os_kernel() 14 | { 15 | task_os(); 16 | } 17 | 18 | void os_start() 19 | { 20 | uart_init(); 21 | lib_puts("OS start\n"); 22 | user_init(); 23 | trap_init(); 24 | plic_init(); 25 | timer_init(); // start timer interrupt ... 26 | } 27 | 28 | int os_main(void) 29 | { 30 | os_start(); 31 | 32 | int current_task = 0; 33 | while (1) 34 | { 35 | lib_puts("OS: Activate next task\n"); 36 | task_go(current_task); 37 | lib_puts("OS: Back to OS\n"); 38 | current_task = (current_task + 1) % taskTop; // Round Robin Scheduling 39 | lib_puts("\n"); 40 | } 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /07-ExterInterrupt/os.h: -------------------------------------------------------------------------------- 1 | #ifndef __OS_H__ 2 | #define __OS_H__ 3 | 4 | #include "riscv.h" 5 | #include "lib.h" 6 | #include "task.h" 7 | #include "timer.h" 8 | 9 | extern void panic(char *); 10 | extern void user_init(); 11 | extern void os_kernel(); 12 | extern int os_main(void); 13 | 14 | // PLIC 15 | extern void plic_init(); 16 | extern int plic_claim(); 17 | extern void plic_complete(int); 18 | 19 | // lock 20 | extern void basic_lock(); 21 | extern void basic_unlock(); 22 | 23 | typedef struct lock 24 | { 25 | volatile int locked; 26 | } lock_t; 27 | 28 | extern int atomic_swap(lock_t *); 29 | 30 | extern void lock_init(lock_t *lock); 31 | 32 | extern void lock_acquire(lock_t *lock); 33 | 34 | extern void lock_free(lock_t *lock); 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /07-ExterInterrupt/os.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_ARCH( "riscv" ) 2 | 3 | ENTRY( _start ) 4 | 5 | MEMORY 6 | { 7 | ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M 8 | } 9 | 10 | PHDRS 11 | { 12 | text PT_LOAD; 13 | data PT_LOAD; 14 | bss PT_LOAD; 15 | } 16 | 17 | SECTIONS 18 | { 19 | .text : { 20 | PROVIDE(_text_start = .); 21 | *(.text.init) *(.text .text.*) 22 | PROVIDE(_text_end = .); 23 | } >ram AT>ram :text 24 | 25 | .rodata : { 26 | PROVIDE(_rodata_start = .); 27 | *(.rodata .rodata.*) 28 | PROVIDE(_rodata_end = .); 29 | } >ram AT>ram :text 30 | 31 | .data : { 32 | . = ALIGN(4096); 33 | PROVIDE(_data_start = .); 34 | *(.sdata .sdata.*) *(.data .data.*) 35 | PROVIDE(_data_end = .); 36 | } >ram AT>ram :data 37 | 38 | .bss :{ 39 | PROVIDE(_bss_start = .); 40 | *(.sbss .sbss.*) *(.bss .bss.*) 41 | PROVIDE(_bss_end = .); 42 | } >ram AT>ram :bss 43 | 44 | PROVIDE(_memory_start = ORIGIN(ram)); 45 | PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram)); 46 | } 47 | -------------------------------------------------------------------------------- /07-ExterInterrupt/plic.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | 3 | // ref: https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h 4 | // Intro: https://github.com/ianchen0119/AwesomeCS/wiki/2-5-RISC-V::%E4%B8%AD%E6%96%B7%E8%88%87%E7%95%B0%E5%B8%B8%E8%99%95%E7%90%86----PLIC-%E4%BB%8B%E7%B4%B9 5 | #define PLIC_BASE 0x0c000000L 6 | #define PLIC_PRIORITY(id) (PLIC_BASE + (id)*4) 7 | #define PLIC_PENDING(id) (PLIC_BASE + 0x1000 + ((id) / 32)) 8 | #define PLIC_MENABLE(hart) (PLIC_BASE + 0x2000 + (hart)*0x80) 9 | #define PLIC_MTHRESHOLD(hart) (PLIC_BASE + 0x200000 + (hart)*0x1000) 10 | #define PLIC_MCLAIM(hart) (PLIC_BASE + 0x200004 + (hart)*0x1000) 11 | #define PLIC_MCOMPLETE(hart) (PLIC_BASE + 0x200004 + (hart)*0x1000) 12 | 13 | void plic_init() 14 | { 15 | int hart = r_tp(); 16 | // QEMU Virt machine support 7 priority (1 - 7), 17 | // The "0" is reserved, and the lowest priority is "1". 18 | *(uint32_t *)PLIC_PRIORITY(UART0_IRQ) = 1; 19 | 20 | /* Enable UART0 */ 21 | *(uint32_t *)PLIC_MENABLE(hart) = (1 << UART0_IRQ); 22 | 23 | /* Set priority threshold for UART0. */ 24 | 25 | *(uint32_t *)PLIC_MTHRESHOLD(hart) = 0; 26 | 27 | /* enable machine-mode external interrupts. */ 28 | w_mie(r_mie() | MIE_MEIE); 29 | 30 | // enable machine-mode interrupts. 31 | w_mstatus(r_mstatus() | MSTATUS_MIE); 32 | } 33 | 34 | int plic_claim() 35 | { 36 | int hart = r_tp(); 37 | int irq = *(uint32_t *)PLIC_MCLAIM(hart); 38 | return irq; 39 | } 40 | 41 | void plic_complete(int irq) 42 | { 43 | int hart = r_tp(); 44 | *(uint32_t *)PLIC_MCOMPLETE(hart) = irq; 45 | } -------------------------------------------------------------------------------- /07-ExterInterrupt/riscv.h: -------------------------------------------------------------------------------- 1 | #ifndef __RISCV_H__ 2 | #define __RISCV_H__ 3 | 4 | #include 5 | 6 | #define reg_t uint32_t // RISCV32: register is 32bits 7 | // define reg_t as uint64_t // RISCV64: register is 64bits 8 | 9 | // ref: https://www.activexperts.com/serial-port-component/tutorials/uart/ 10 | #define UART 0x10000000L 11 | #define UART_THR (volatile uint8_t *)(UART + 0x00) // THR:transmitter holding register 12 | #define UART_RHR (volatile uint8_t *)(UART + 0x00) // RHR:Receive holding register 13 | #define UART_DLL (volatile uint8_t *)(UART + 0x00) // LSB of Divisor Latch (write mode) 14 | #define UART_DLM (volatile uint8_t *)(UART + 0x01) // MSB of Divisor Latch (write mode) 15 | #define UART_IER (volatile uint8_t *)(UART + 0x01) // Interrupt Enable Register 16 | #define UART_LCR (volatile uint8_t *)(UART + 0x03) // Line Control Register 17 | #define UART_LSR (volatile uint8_t *)(UART + 0x05) // LSR:line status register 18 | #define UART_LSR_EMPTY_MASK 0x40 // LSR Bit 6: Transmitter empty; both the THR and LSR are empty 19 | 20 | #define UART_REGR(reg) (*(reg)) 21 | #define UART_REGW(reg, v) ((*reg) = (v)) 22 | 23 | // ref: https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h 24 | // enum { 25 | // UART0_IRQ = 10, 26 | // RTC_IRQ = 11, 27 | // VIRTIO_IRQ = 1, /* 1 to 8 */ 28 | // VIRTIO_COUNT = 8, 29 | // PCIE_IRQ = 0x20, /* 32 to 35 */ 30 | // VIRTIO_NDEV = 0x35 /* Arbitrary maximum number of interrupts */ 31 | // }; 32 | #define UART0_IRQ 10 33 | #define VIRTIO_IRQ 1 34 | 35 | // Saved registers for kernel context switches. 36 | struct context 37 | { 38 | reg_t ra; 39 | reg_t sp; 40 | 41 | // callee-saved 42 | reg_t s0; 43 | reg_t s1; 44 | reg_t s2; 45 | reg_t s3; 46 | reg_t s4; 47 | reg_t s5; 48 | reg_t s6; 49 | reg_t s7; 50 | reg_t s8; 51 | reg_t s9; 52 | reg_t s10; 53 | reg_t s11; 54 | }; 55 | 56 | // ref: https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/riscv.h 57 | // 58 | // local interrupt controller, which contains the timer. 59 | // ================== Timer Interrput ==================== 60 | 61 | #define NCPU 8 // maximum number of CPUs 62 | #define CLINT 0x2000000 63 | #define CLINT_MTIMECMP(hartid) (CLINT + 0x4000 + 4 * (hartid)) 64 | #define CLINT_MTIME (CLINT + 0xBFF8) // cycles since boot. 65 | 66 | static inline reg_t r_tp() 67 | { 68 | reg_t x; 69 | asm volatile("mv %0, tp" 70 | : "=r"(x)); 71 | return x; 72 | } 73 | 74 | // which hart (core) is this? 75 | 76 | static inline reg_t r_mhartid() 77 | { 78 | reg_t x; 79 | asm volatile("csrr %0, mhartid" 80 | : "=r"(x)); 81 | return x; 82 | } 83 | 84 | // Machine Status Register, mstatus 85 | #define MSTATUS_MPP_MASK (3 << 11) // previous mode. 86 | #define MSTATUS_MPP_M (3 << 11) 87 | #define MSTATUS_MPP_S (1 << 11) 88 | #define MSTATUS_MPP_U (0 << 11) 89 | #define MSTATUS_MIE (1 << 3) // machine-mode interrupt enable. 90 | 91 | static inline reg_t r_mstatus() 92 | { 93 | reg_t x; 94 | asm volatile("csrr %0, mstatus" 95 | : "=r"(x)); 96 | return x; 97 | } 98 | 99 | static inline void w_mstatus(reg_t x) 100 | { 101 | asm volatile("csrw mstatus, %0" 102 | : 103 | : "r"(x)); 104 | } 105 | 106 | // machine exception program counter, holds the 107 | // instruction address to which a return from 108 | // exception will go. 109 | static inline void w_mepc(reg_t x) 110 | { 111 | asm volatile("csrw mepc, %0" 112 | : 113 | : "r"(x)); 114 | } 115 | 116 | static inline reg_t r_mepc() 117 | { 118 | reg_t x; 119 | asm volatile("csrr %0, mepc" 120 | : "=r"(x)); 121 | return x; 122 | } 123 | 124 | // Machine Scratch register, for early trap handler 125 | static inline void w_mscratch(reg_t x) 126 | { 127 | asm volatile("csrw mscratch, %0" 128 | : 129 | : "r"(x)); 130 | } 131 | 132 | // Machine-mode interrupt vector 133 | static inline void w_mtvec(reg_t x) 134 | { 135 | asm volatile("csrw mtvec, %0" 136 | : 137 | : "r"(x)); 138 | } 139 | 140 | // Machine-mode Interrupt Enable 141 | #define MIE_MEIE (1 << 11) // external 142 | #define MIE_MTIE (1 << 7) // timer 143 | #define MIE_MSIE (1 << 3) // software 144 | 145 | static inline reg_t r_mie() 146 | { 147 | reg_t x; 148 | asm volatile("csrr %0, mie" 149 | : "=r"(x)); 150 | return x; 151 | } 152 | 153 | static inline void w_mie(reg_t x) 154 | { 155 | asm volatile("csrw mie, %0" 156 | : 157 | : "r"(x)); 158 | } 159 | 160 | #endif 161 | -------------------------------------------------------------------------------- /07-ExterInterrupt/start.s: -------------------------------------------------------------------------------- 1 | .equ STACK_SIZE, 8192 2 | 3 | .global _start 4 | 5 | _start: 6 | csrr a0, mhartid # 讀取核心代號 7 | bnez a0, park # 若不是 0 號核心,跳到 park 停止 8 | la sp, stacks + STACK_SIZE # 0 號核心設定堆疊 9 | j os_main # 0 號核心跳到主程式 os_main 10 | 11 | park: 12 | wfi 13 | j park 14 | 15 | stacks: 16 | .skip STACK_SIZE # 分配堆疊空間 17 | -------------------------------------------------------------------------------- /07-ExterInterrupt/sys.h: -------------------------------------------------------------------------------- 1 | #ifndef __SYS_H__ 2 | #define __SYS_H__ 3 | 4 | #include "riscv.h" 5 | extern void sys_timer(); 6 | extern void sys_switch(struct context *ctx_old, struct context *ctx_new); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /07-ExterInterrupt/sys.s: -------------------------------------------------------------------------------- 1 | # This Code derived from xv6-riscv (64bit) 2 | # -- https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/swtch.S 3 | 4 | # ============ MACRO ================== 5 | .macro ctx_save base 6 | sw ra, 0(\base) 7 | sw sp, 4(\base) 8 | sw s0, 8(\base) 9 | sw s1, 12(\base) 10 | sw s2, 16(\base) 11 | sw s3, 20(\base) 12 | sw s4, 24(\base) 13 | sw s5, 28(\base) 14 | sw s6, 32(\base) 15 | sw s7, 36(\base) 16 | sw s8, 40(\base) 17 | sw s9, 44(\base) 18 | sw s10, 48(\base) 19 | sw s11, 52(\base) 20 | .endm 21 | 22 | .macro ctx_load base 23 | lw ra, 0(\base) 24 | lw sp, 4(\base) 25 | lw s0, 8(\base) 26 | lw s1, 12(\base) 27 | lw s2, 16(\base) 28 | lw s3, 20(\base) 29 | lw s4, 24(\base) 30 | lw s5, 28(\base) 31 | lw s6, 32(\base) 32 | lw s7, 36(\base) 33 | lw s8, 40(\base) 34 | lw s9, 44(\base) 35 | lw s10, 48(\base) 36 | lw s11, 52(\base) 37 | .endm 38 | 39 | .macro reg_save base 40 | # save the registers. 41 | sw ra, 0(\base) 42 | sw sp, 4(\base) 43 | sw gp, 8(\base) 44 | sw tp, 12(\base) 45 | sw t0, 16(\base) 46 | sw t1, 20(\base) 47 | sw t2, 24(\base) 48 | sw s0, 28(\base) 49 | sw s1, 32(\base) 50 | sw a0, 36(\base) 51 | sw a1, 40(\base) 52 | sw a2, 44(\base) 53 | sw a3, 48(\base) 54 | sw a4, 52(\base) 55 | sw a5, 56(\base) 56 | sw a6, 60(\base) 57 | sw a7, 64(\base) 58 | sw s2, 68(\base) 59 | sw s3, 72(\base) 60 | sw s4, 76(\base) 61 | sw s5, 80(\base) 62 | sw s6, 84(\base) 63 | sw s7, 88(\base) 64 | sw s8, 92(\base) 65 | sw s9, 96(\base) 66 | sw s10, 100(\base) 67 | sw s11, 104(\base) 68 | sw t3, 108(\base) 69 | sw t4, 112(\base) 70 | sw t5, 116(\base) 71 | sw t6, 120(\base) 72 | .endm 73 | 74 | .macro reg_load base 75 | # restore registers. 76 | lw ra, 0(\base) 77 | lw sp, 4(\base) 78 | lw gp, 8(\base) 79 | # not this, in case we moved CPUs: lw tp, 12(\base) 80 | lw t0, 16(\base) 81 | lw t1, 20(\base) 82 | lw t2, 24(\base) 83 | lw s0, 28(\base) 84 | lw s1, 32(\base) 85 | lw a0, 36(\base) 86 | lw a1, 40(\base) 87 | lw a2, 44(\base) 88 | lw a3, 48(\base) 89 | lw a4, 52(\base) 90 | lw a5, 56(\base) 91 | lw a6, 60(\base) 92 | lw a7, 64(\base) 93 | lw s2, 68(\base) 94 | lw s3, 72(\base) 95 | lw s4, 76(\base) 96 | lw s5, 80(\base) 97 | lw s6, 84(\base) 98 | lw s7, 88(\base) 99 | lw s8, 92(\base) 100 | lw s9, 96(\base) 101 | lw s10, 100(\base) 102 | lw s11, 104(\base) 103 | lw t3, 108(\base) 104 | lw t4, 112(\base) 105 | lw t5, 116(\base) 106 | lw t6, 120(\base) 107 | .endm 108 | # ============ Macro END ================== 109 | 110 | # Context switch 111 | # 112 | # void sys_switch(struct context *old, struct context *new); 113 | # 114 | # Save current registers in old. Load from new. 115 | 116 | .globl sys_switch 117 | .align 4 118 | sys_switch: 119 | 120 | ctx_save a0 # a0 => struct context *old 121 | ctx_load a1 # a1 => struct context *new 122 | ret # pc=ra; swtch to new task (new->ra) 123 | 124 | .globl atomic_swap 125 | .align 4 126 | atomic_swap: 127 | li a5, 1 128 | amoswap.w.aq a5, a5, 0(a0) 129 | mv a0, a5 130 | ret 131 | 132 | .globl trap_vector 133 | # the trap vector base address must always be aligned on a 4-byte boundary 134 | .align 4 135 | trap_vector: 136 | # save context(registers). 137 | csrrw t6, mscratch, t6 # swap t6 and mscratch 138 | reg_save t6 139 | csrw mscratch, t6 140 | # call the C trap handler in trap.c 141 | csrr a0, mepc 142 | csrr a1, mcause 143 | call trap_handler 144 | 145 | # trap_handler will return the return address via a0. 146 | csrw mepc, a0 147 | 148 | # load context(registers). 149 | csrr t6, mscratch 150 | reg_load t6 151 | mret 152 | 153 | -------------------------------------------------------------------------------- /07-ExterInterrupt/task.c: -------------------------------------------------------------------------------- 1 | #include "task.h" 2 | #include "lib.h" 3 | 4 | uint8_t task_stack[MAX_TASK][STACK_SIZE]; 5 | struct context ctx_os; 6 | struct context ctx_tasks[MAX_TASK]; 7 | struct context *ctx_now; 8 | int taskTop = 0; // total number of task 9 | 10 | // create a new task 11 | int task_create(void (*task)(void)) 12 | { 13 | int i = taskTop++; 14 | ctx_tasks[i].ra = (reg_t)task; 15 | ctx_tasks[i].sp = (reg_t)&task_stack[i][STACK_SIZE - 1]; 16 | return i; 17 | } 18 | 19 | // switch to task[i] 20 | void task_go(int i) 21 | { 22 | ctx_now = &ctx_tasks[i]; 23 | sys_switch(&ctx_os, &ctx_tasks[i]); 24 | } 25 | 26 | // switch back to os 27 | void task_os() 28 | { 29 | struct context *ctx = ctx_now; 30 | ctx_now = &ctx_os; 31 | sys_switch(ctx, &ctx_os); 32 | } 33 | -------------------------------------------------------------------------------- /07-ExterInterrupt/task.h: -------------------------------------------------------------------------------- 1 | #ifndef __TASK_H__ 2 | #define __TASK_H__ 3 | 4 | #include "riscv.h" 5 | #include "sys.h" 6 | 7 | #define MAX_TASK 10 8 | #define STACK_SIZE 1024 9 | 10 | extern int taskTop; 11 | 12 | extern int task_create(void (*task)(void)); 13 | extern void task_go(int i); 14 | extern void task_os(); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /07-ExterInterrupt/timer.c: -------------------------------------------------------------------------------- 1 | #include "timer.h" 2 | 3 | // a scratch area per CPU for machine-mode timer interrupts. 4 | reg_t timer_scratch[NCPU][5]; 5 | 6 | #define interval 20000000 // cycles; about 2 second in qemu. 7 | 8 | void timer_init() 9 | { 10 | // each CPU has a separate source of timer interrupts. 11 | int id = r_mhartid(); 12 | 13 | // ask the CLINT for a timer interrupt. 14 | // int interval = 1000000; // cycles; about 1/10th second in qemu. 15 | 16 | *(reg_t *)CLINT_MTIMECMP(id) = *(reg_t *)CLINT_MTIME + interval; 17 | 18 | // prepare information in scratch[] for timervec. 19 | // scratch[0..2] : space for timervec to save registers. 20 | // scratch[3] : address of CLINT MTIMECMP register. 21 | // scratch[4] : desired interval (in cycles) between timer interrupts. 22 | reg_t *scratch = &timer_scratch[id][0]; 23 | scratch[3] = CLINT_MTIMECMP(id); 24 | scratch[4] = interval; 25 | w_mscratch((reg_t)scratch); 26 | 27 | // enable machine-mode timer interrupts. 28 | w_mie(r_mie() | MIE_MTIE); 29 | } 30 | 31 | static int timer_count = 0; 32 | 33 | void timer_handler() 34 | { 35 | lib_printf("timer_handler: %d\n", ++timer_count); 36 | int id = r_mhartid(); 37 | *(reg_t *)CLINT_MTIMECMP(id) = *(reg_t *)CLINT_MTIME + interval; 38 | } 39 | -------------------------------------------------------------------------------- /07-ExterInterrupt/timer.h: -------------------------------------------------------------------------------- 1 | #ifndef __TIMER_H__ 2 | #define __TIMER_H__ 3 | 4 | #include "riscv.h" 5 | #include "sys.h" 6 | #include "lib.h" 7 | #include "task.h" 8 | 9 | extern void timer_handler(); 10 | extern void timer_init(); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /07-ExterInterrupt/trap.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | extern void trap_vector(); 3 | 4 | void trap_init() 5 | { 6 | // set the machine-mode trap handler. 7 | w_mtvec((reg_t)trap_vector); 8 | } 9 | 10 | void external_handler() 11 | { 12 | int irq = plic_claim(); 13 | if (irq == UART0_IRQ) 14 | { 15 | lib_isr(); 16 | } 17 | else if (irq) 18 | { 19 | lib_printf("unexpected interrupt irq = %d\n", irq); 20 | } 21 | 22 | if (irq) 23 | { 24 | plic_complete(irq); 25 | } 26 | } 27 | 28 | reg_t trap_handler(reg_t epc, reg_t cause) 29 | { 30 | reg_t return_pc = epc; 31 | reg_t cause_code = cause & 0xfff; 32 | 33 | if (cause & 0x80000000) 34 | { 35 | /* Asynchronous trap - interrupt */ 36 | switch (cause_code) 37 | { 38 | case 3: 39 | lib_puts("software interruption!\n"); 40 | break; 41 | case 7: 42 | lib_puts("timer interruption!\n"); 43 | // disable machine-mode timer interrupts. 44 | w_mie(r_mie() & ~(1 << 7)); 45 | timer_handler(); 46 | return_pc = (reg_t)&os_kernel; 47 | // enable machine-mode timer interrupts. 48 | w_mie(r_mie() | MIE_MTIE); 49 | break; 50 | case 11: 51 | lib_puts("external interruption!\n"); 52 | external_handler(); 53 | break; 54 | default: 55 | lib_puts("unknown async exception!\n"); 56 | break; 57 | } 58 | } 59 | else 60 | { 61 | /* Synchronous trap - exception */ 62 | lib_puts("Sync exceptions!\n"); 63 | while (1) 64 | { 65 | /* code */ 66 | } 67 | } 68 | return return_pc; 69 | } 70 | -------------------------------------------------------------------------------- /07-ExterInterrupt/user.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | 3 | int shared_var = 500; 4 | 5 | lock_t lock; 6 | 7 | void user_task0(void) 8 | { 9 | lib_puts("Task0: Created!\n"); 10 | while (1) 11 | { 12 | lib_puts("Task0: Running...\n"); 13 | lib_delay(1000); 14 | } 15 | } 16 | 17 | void user_task1(void) 18 | { 19 | lib_puts("Task1: Created!\n"); 20 | while (1) 21 | { 22 | lib_puts("Task1: Running...\n"); 23 | lib_delay(1000); 24 | } 25 | } 26 | 27 | void user_task2(void) 28 | { 29 | lib_puts("Task2: Created!\n"); 30 | while (1) 31 | { 32 | for (int i = 0; i < 50; i++) 33 | { 34 | lock_acquire(&lock); 35 | shared_var++; 36 | lock_free(&lock); 37 | lib_delay(100); 38 | } 39 | lib_printf("The value of shared_var is: %d \n", shared_var); 40 | } 41 | } 42 | 43 | void user_task3(void) 44 | { 45 | lib_puts("Task3: Created!\n"); 46 | while (1) 47 | { 48 | lib_puts("Trying to get the lock... \n"); 49 | lock_acquire(&lock); 50 | lib_puts("Get the lock!\n"); 51 | lock_free(&lock); 52 | lib_delay(1000); 53 | } 54 | } 55 | 56 | void user_init() 57 | { 58 | lock_init(&lock); 59 | task_create(&user_task0); 60 | task_create(&user_task1); 61 | task_create(&user_task2); 62 | task_create(&user_task3); 63 | } 64 | -------------------------------------------------------------------------------- /08-BlockDeviceDriver/Makefile: -------------------------------------------------------------------------------- 1 | CC = riscv64-unknown-elf-gcc 2 | CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall -w 3 | GDB = riscv64-unknown-elf-gdb 4 | 5 | OBJ = \ 6 | start.s \ 7 | sys.s \ 8 | lib.c \ 9 | timer.c \ 10 | task.c \ 11 | os.c \ 12 | user.c \ 13 | trap.c \ 14 | lock.c \ 15 | plic.c \ 16 | virtio.c \ 17 | string.c 18 | 19 | QEMU = qemu-system-riscv32 20 | QFLAGS = -nographic -smp 4 -machine virt -bios none 21 | QFLAGS += -drive if=none,format=raw,file=hdd.dsk,id=x0 22 | QFLAGS += -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 23 | 24 | OBJDUMP = riscv64-unknown-elf-objdump 25 | 26 | all: clean os.elf hdd.dsk qemu 27 | 28 | test: clean os.elf qemu 29 | 30 | os.elf: $(OBJ) 31 | $(CC) $(CFLAGS) -T os.ld -o os.elf $^ 32 | 33 | qemu: $(TARGET) hdd.dsk 34 | @qemu-system-riscv32 -M ? | grep virt >/dev/null || exit 35 | @echo "Press Ctrl-A and then X to exit QEMU" 36 | $(QEMU) $(QFLAGS) -kernel os.elf 37 | 38 | clean: 39 | rm -f *.elf *.img 40 | 41 | hdd.dsk: 42 | dd if=/dev/urandom of=hdd.dsk bs=1M count=32 43 | 44 | .PHONY : debug 45 | debug: clean os.elf hdd.dsk 46 | @echo "Press Ctrl-C and then input 'quit' to exit GDB and QEMU" 47 | @echo "-------------------------------------------------------" 48 | @${QEMU} ${QFLAGS} -kernel os.elf -s -S & 49 | @${GDB} os.elf -q -x ./gdbinit -------------------------------------------------------------------------------- /08-BlockDeviceDriver/README.md: -------------------------------------------------------------------------------- 1 | # 08-BlockDeviceDriver 2 | 3 | ## Build & Run 4 | 5 | ```sh 6 | IAN@DESKTOP-9AEMEPL MINGW64 ~/Desktop/mini-riscv-os/08-BlockDeviceDriver (feat/block_driver) 7 | $ make all 8 | rm -f *.elf *.img 9 | riscv64-unknown-elf-gcc -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall -w -T os.ld -o os.elf start.s sys.s lib.c timer.c task.c os.c user.c trap.c lock.c plic.c virtio.c string.c 10 | Press Ctrl-A and then X to exit QEMU 11 | qemu-system-riscv32 -nographic -smp 4 -machine virt -bios none -drive if=none,format=raw,file=hdd.dsk,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 -kernel os.elf 12 | OS start 13 | Disk init work is success! 14 | buffer init... 15 | block read... 16 | Virtio IRQ 17 | 000000fd 18 | 000000af 19 | 000000f8 20 | 000000ab 21 | 00000088 22 | 00000042 23 | 000000cc 24 | 00000017 25 | 00000022 26 | 0000008e 27 | 28 | OS: Activate next task 29 | Task0: Created! 30 | Task0: Running... 31 | Task0: Running... 32 | Task0: Running... 33 | Task0: Running... 34 | QEMU: Terminated 35 | ``` 36 | 37 | ## Debug mode 38 | 39 | ```sh 40 | make debug 41 | riscv64-unknown-elf-gcc -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall -T os.ld -o os.elf start.s sys.s lib.c timer.c task.c os.c user.c trap.c lock.c 42 | Press Ctrl-C and then input 'quit' to exit GDB and QEMU 43 | ------------------------------------------------------- 44 | Reading symbols from os.elf... 45 | Breakpoint 1 at 0x80000000: file start.s, line 7. 46 | 0x00001000 in ?? () 47 | => 0x00001000: 97 02 00 00 auipc t0,0x0 48 | 49 | Thread 1 hit Breakpoint 1, _start () at start.s:7 50 | 7 csrr t0, mhartid # read current hart id 51 | => 0x80000000 <_start+0>: f3 22 40 f1 csrr t0,mhartid 52 | (gdb) 53 | ``` 54 | 55 | ### set the breakpoint 56 | 57 | You can set the breakpoint in any c file: 58 | 59 | ```sh 60 | (gdb) b trap.c:27 61 | Breakpoint 2 at 0x80008f78: file trap.c, line 27. 62 | (gdb) 63 | ``` 64 | 65 | As the example above, when process running on trap.c, line 27 (Timer Interrupt). 66 | The process will be suspended automatically until you press the key `c` (continue) or `s` (step). 67 | -------------------------------------------------------------------------------- /08-BlockDeviceDriver/gdbinit: -------------------------------------------------------------------------------- 1 | set disassemble-next-line on 2 | b _start 3 | target remote : 1234 4 | c -------------------------------------------------------------------------------- /08-BlockDeviceDriver/lib.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIB_H__ 2 | #define __LIB_H__ 3 | 4 | #include "riscv.h" 5 | #include 6 | #include 7 | 8 | #define lib_error(...) \ 9 | { \ 10 | lib_printf(__VA_ARGS__); \ 11 | while (1) \ 12 | { \ 13 | } \ 14 | } \ 15 | } 16 | 17 | extern char *lib_gets(char *); 18 | extern void uart_init(); 19 | extern void lib_isr(void); 20 | extern int lib_getc(void); 21 | extern void lib_delay(volatile int count); 22 | extern int lib_putc(char ch); 23 | extern void lib_puts(char *s); 24 | extern int lib_printf(const char *s, ...); 25 | extern int lib_vprintf(const char *s, va_list vl); 26 | extern int lib_vsnprintf(char *out, size_t n, const char *s, va_list vl); 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /08-BlockDeviceDriver/lock.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | 3 | void lock_init(lock_t *lock) 4 | { 5 | lock->locked = 0; 6 | } 7 | 8 | void lock_acquire(lock_t *lock) 9 | { 10 | for (;;) 11 | { 12 | if (!atomic_swap(lock)) 13 | { 14 | break; 15 | } 16 | } 17 | } 18 | 19 | void lock_free(lock_t *lock) 20 | { 21 | lock->locked = 0; 22 | } 23 | 24 | void basic_lock() 25 | { 26 | w_mstatus(r_mstatus() & ~MSTATUS_MIE); 27 | } 28 | 29 | void basic_unlock() 30 | { 31 | w_mstatus(r_mstatus() | MSTATUS_MIE); 32 | } 33 | -------------------------------------------------------------------------------- /08-BlockDeviceDriver/os.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | 3 | extern void trap_init(void); 4 | 5 | void panic(char *s) 6 | { 7 | lib_puts(s); 8 | for (;;) 9 | { 10 | } 11 | } 12 | 13 | void os_kernel() 14 | { 15 | task_os(); 16 | } 17 | 18 | void disk_read() 19 | { 20 | virtio_tester(0); 21 | } 22 | 23 | void os_start() 24 | { 25 | uart_init(); 26 | lib_puts("OS start\n"); 27 | user_init(); 28 | trap_init(); 29 | plic_init(); 30 | virtio_disk_init(); 31 | timer_init(); // start timer interrupt ... 32 | } 33 | 34 | int os_main(void) 35 | { 36 | os_start(); 37 | disk_read(); 38 | int current_task = 0; 39 | while (1) 40 | { 41 | lib_puts("OS: Activate next task\n"); 42 | task_go(current_task); 43 | lib_puts("OS: Back to OS\n"); 44 | current_task = (current_task + 1) % taskTop; // Round Robin Scheduling 45 | lib_puts("\n"); 46 | } 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /08-BlockDeviceDriver/os.h: -------------------------------------------------------------------------------- 1 | #ifndef __OS_H__ 2 | #define __OS_H__ 3 | 4 | #include "riscv.h" 5 | #include "lib.h" 6 | #include "task.h" 7 | #include "timer.h" 8 | #include "string.h" 9 | 10 | extern void panic(char *); 11 | extern void user_init(); 12 | extern void os_kernel(); 13 | extern int os_main(void); 14 | 15 | // PLIC 16 | extern void plic_init(); 17 | extern int plic_claim(); 18 | extern void plic_complete(int); 19 | 20 | // lock 21 | extern void basic_lock(); 22 | extern void basic_unlock(); 23 | 24 | typedef struct lock 25 | { 26 | volatile int locked; 27 | } lock_t; 28 | 29 | extern int atomic_swap(lock_t *); 30 | 31 | extern void lock_init(lock_t *lock); 32 | 33 | extern void lock_acquire(lock_t *lock); 34 | 35 | extern void lock_free(lock_t *lock); 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /08-BlockDeviceDriver/os.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_ARCH( "riscv" ) 2 | 3 | ENTRY( _start ) 4 | 5 | MEMORY 6 | { 7 | ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M 8 | } 9 | 10 | PHDRS 11 | { 12 | text PT_LOAD; 13 | data PT_LOAD; 14 | bss PT_LOAD; 15 | } 16 | 17 | SECTIONS 18 | { 19 | .text : { 20 | PROVIDE(_text_start = .); 21 | *(.text.init) *(.text .text.*) 22 | PROVIDE(_text_end = .); 23 | } >ram AT>ram :text 24 | 25 | .rodata : { 26 | PROVIDE(_rodata_start = .); 27 | *(.rodata .rodata.*) 28 | PROVIDE(_rodata_end = .); 29 | } >ram AT>ram :text 30 | 31 | .data : { 32 | . = ALIGN(4096); 33 | PROVIDE(_data_start = .); 34 | *(.sdata .sdata.*) *(.data .data.*) 35 | PROVIDE(_data_end = .); 36 | } >ram AT>ram :data 37 | 38 | .bss :{ 39 | PROVIDE(_bss_start = .); 40 | *(.sbss .sbss.*) *(.bss .bss.*) 41 | PROVIDE(_bss_end = .); 42 | } >ram AT>ram :bss 43 | 44 | PROVIDE(_memory_start = ORIGIN(ram)); 45 | PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram)); 46 | } 47 | -------------------------------------------------------------------------------- /08-BlockDeviceDriver/plic.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | 3 | // ref: https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h 4 | // Intro: https://github.com/ianchen0119/AwesomeCS/wiki/2-5-RISC-V::%E4%B8%AD%E6%96%B7%E8%88%87%E7%95%B0%E5%B8%B8%E8%99%95%E7%90%86----PLIC-%E4%BB%8B%E7%B4%B9 5 | #define PLIC_BASE 0x0c000000L 6 | #define PLIC_PRIORITY(id) (PLIC_BASE + (id)*4) 7 | #define PLIC_PENDING(id) (PLIC_BASE + 0x1000 + ((id) / 32)) 8 | #define PLIC_MENABLE(hart) (PLIC_BASE + 0x2000 + (hart)*0x80) 9 | #define PLIC_MTHRESHOLD(hart) (PLIC_BASE + 0x200000 + (hart)*0x1000) 10 | #define PLIC_MCLAIM(hart) (PLIC_BASE + 0x200004 + (hart)*0x1000) 11 | #define PLIC_MCOMPLETE(hart) (PLIC_BASE + 0x200004 + (hart)*0x1000) 12 | 13 | void plic_init() 14 | { 15 | int hart = r_tp(); 16 | // QEMU Virt machine support 7 priority (1 - 7), 17 | // The "0" is reserved, and the lowest priority is "1". 18 | 19 | *(uint32_t *)PLIC_PRIORITY(UART0_IRQ) = 1; 20 | *(uint32_t *)PLIC_PRIORITY(VIRTIO_IRQ) = 1; 21 | 22 | /* Enable UART0 and VIRTIO */ 23 | 24 | *(uint32_t *)PLIC_MENABLE(hart) = (1 << UART0_IRQ) | (1 << VIRTIO_IRQ); 25 | 26 | /* Set priority threshold for UART0. */ 27 | 28 | *(uint32_t *)PLIC_MTHRESHOLD(hart) = 0; 29 | 30 | /* enable machine-mode external interrupts. */ 31 | w_mie(r_mie() | MIE_MEIE); 32 | 33 | // enable machine-mode interrupts. 34 | w_mstatus(r_mstatus() | MSTATUS_MIE); 35 | } 36 | 37 | int plic_claim() 38 | { 39 | int hart = r_tp(); 40 | int irq = *(uint32_t *)PLIC_MCLAIM(hart); 41 | return irq; 42 | } 43 | 44 | void plic_complete(int irq) 45 | { 46 | int hart = r_tp(); 47 | *(uint32_t *)PLIC_MCOMPLETE(hart) = irq; 48 | } -------------------------------------------------------------------------------- /08-BlockDeviceDriver/riscv.h: -------------------------------------------------------------------------------- 1 | #ifndef __RISCV_H__ 2 | #define __RISCV_H__ 3 | 4 | #include 5 | 6 | #define reg_t uint32_t // RISCV32: register is 32bits 7 | // define reg_t as uint64_t // RISCV64: register is 64bits 8 | 9 | #define PGSIZE 4096 // bytes per page 10 | 11 | // ref: https://www.activexperts.com/serial-port-component/tutorials/uart/ 12 | #define UART 0x10000000L 13 | #define UART_THR (volatile uint8_t *)(UART + 0x00) // THR:transmitter holding register 14 | #define UART_RHR (volatile uint8_t *)(UART + 0x00) // RHR:Receive holding register 15 | #define UART_DLL (volatile uint8_t *)(UART + 0x00) // LSB of Divisor Latch (write mode) 16 | #define UART_DLM (volatile uint8_t *)(UART + 0x01) // MSB of Divisor Latch (write mode) 17 | #define UART_IER (volatile uint8_t *)(UART + 0x01) // Interrupt Enable Register 18 | #define UART_LCR (volatile uint8_t *)(UART + 0x03) // Line Control Register 19 | #define UART_LSR (volatile uint8_t *)(UART + 0x05) // LSR:line status register 20 | #define UART_LSR_EMPTY_MASK 0x40 // LSR Bit 6: Transmitter empty; both the THR and LSR are empty 21 | 22 | #define UART_REGR(reg) (*(reg)) 23 | #define UART_REGW(reg, v) ((*reg) = (v)) 24 | 25 | // ref: https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h 26 | // enum { 27 | // UART0_IRQ = 10, 28 | // RTC_IRQ = 11, 29 | // VIRTIO_IRQ = 1, /* 1 to 8 */ 30 | // VIRTIO_COUNT = 8, 31 | // PCIE_IRQ = 0x20, /* 32 to 35 */ 32 | // VIRTIO_NDEV = 0x35 /* Arbitrary maximum number of interrupts */ 33 | // }; 34 | #define UART0_IRQ 10 35 | #define VIRTIO_IRQ 1 36 | 37 | // Saved registers for kernel context switches. 38 | struct context 39 | { 40 | reg_t ra; 41 | reg_t sp; 42 | 43 | // callee-saved 44 | reg_t s0; 45 | reg_t s1; 46 | reg_t s2; 47 | reg_t s3; 48 | reg_t s4; 49 | reg_t s5; 50 | reg_t s6; 51 | reg_t s7; 52 | reg_t s8; 53 | reg_t s9; 54 | reg_t s10; 55 | reg_t s11; 56 | }; 57 | 58 | // ref: https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/riscv.h 59 | // 60 | // local interrupt controller, which contains the timer. 61 | // ================== Timer Interrput ==================== 62 | 63 | #define NCPU 8 // maximum number of CPUs 64 | #define CLINT 0x2000000 65 | #define CLINT_MTIMECMP(hartid) (CLINT + 0x4000 + 4 * (hartid)) 66 | #define CLINT_MTIME (CLINT + 0xBFF8) // cycles since boot. 67 | 68 | static inline reg_t r_tp() 69 | { 70 | reg_t x; 71 | asm volatile("mv %0, tp" 72 | : "=r"(x)); 73 | return x; 74 | } 75 | 76 | // which hart (core) is this? 77 | 78 | static inline reg_t r_mhartid() 79 | { 80 | reg_t x; 81 | asm volatile("csrr %0, mhartid" 82 | : "=r"(x)); 83 | return x; 84 | } 85 | 86 | // Machine Status Register, mstatus 87 | #define MSTATUS_MPP_MASK (3L << 11) // previous mode. 88 | #define MSTATUS_MPP_M (3L << 11) 89 | #define MSTATUS_MPP_S (1L << 11) 90 | #define MSTATUS_MPP_U (0L << 11) 91 | #define MSTATUS_MIE (1L << 3) // machine-mode interrupt enable. 92 | 93 | static inline reg_t r_mstatus() 94 | { 95 | reg_t x; 96 | asm volatile("csrr %0, mstatus" 97 | : "=r"(x)); 98 | return x; 99 | } 100 | 101 | static inline void w_mstatus(reg_t x) 102 | { 103 | asm volatile("csrw mstatus, %0" 104 | : 105 | : "r"(x)); 106 | } 107 | 108 | // machine exception program counter, holds the 109 | // instruction address to which a return from 110 | // exception will go. 111 | static inline void w_mepc(reg_t x) 112 | { 113 | asm volatile("csrw mepc, %0" 114 | : 115 | : "r"(x)); 116 | } 117 | 118 | static inline reg_t r_mepc() 119 | { 120 | reg_t x; 121 | asm volatile("csrr %0, mepc" 122 | : "=r"(x)); 123 | return x; 124 | } 125 | 126 | // Machine Scratch register, for early trap handler 127 | static inline void w_mscratch(reg_t x) 128 | { 129 | asm volatile("csrw mscratch, %0" 130 | : 131 | : "r"(x)); 132 | } 133 | 134 | // Machine-mode interrupt vector 135 | static inline void w_mtvec(reg_t x) 136 | { 137 | asm volatile("csrw mtvec, %0" 138 | : 139 | : "r"(x)); 140 | } 141 | 142 | // Machine-mode Interrupt Enable 143 | #define MIE_MEIE (1L << 11) // external 144 | #define MIE_MTIE (1L << 7) // timer 145 | #define MIE_MSIE (1L << 3) // software 146 | 147 | static inline reg_t r_mie() 148 | { 149 | reg_t x; 150 | asm volatile("csrr %0, mie" 151 | : "=r"(x)); 152 | return x; 153 | } 154 | 155 | static inline void w_mie(reg_t x) 156 | { 157 | asm volatile("csrw mie, %0" 158 | : 159 | : "r"(x)); 160 | } 161 | 162 | #endif 163 | -------------------------------------------------------------------------------- /08-BlockDeviceDriver/start.s: -------------------------------------------------------------------------------- 1 | .equ STACK_SIZE, 8192 2 | 3 | .global _start 4 | 5 | _start: 6 | csrr a0, mhartid # 讀取核心代號 7 | bnez a0, park # 若不是 0 號核心,跳到 park 停止 8 | la sp, stacks + STACK_SIZE # 0 號核心設定堆疊 9 | j os_main # 0 號核心跳到主程式 os_main 10 | 11 | park: 12 | wfi 13 | j park 14 | 15 | stacks: 16 | .skip STACK_SIZE # 分配堆疊空間 17 | -------------------------------------------------------------------------------- /08-BlockDeviceDriver/string.c: -------------------------------------------------------------------------------- 1 | #include "string.h" 2 | 3 | void *memset(void *dst, int c, unsigned int n) 4 | { 5 | char *cdst = (char *)dst; 6 | int i; 7 | for (i = 0; i < n; i++) 8 | { 9 | cdst[i] = c; 10 | } 11 | return dst; 12 | } 13 | 14 | void * 15 | memcpy(void *dst, const void *src, unsigned int n) 16 | { 17 | return memmove(dst, src, n); 18 | } 19 | 20 | void * 21 | memmove(void *dst, const void *src, unsigned int n) 22 | { 23 | const char *s; 24 | char *d; 25 | 26 | s = src; 27 | d = dst; 28 | if (s < d && s + n > d) 29 | { 30 | s += n; 31 | d += n; 32 | while (n-- > 0) 33 | *--d = *--s; 34 | } 35 | else 36 | while (n-- > 0) 37 | *d++ = *s++; 38 | 39 | return dst; 40 | } -------------------------------------------------------------------------------- /08-BlockDeviceDriver/string.h: -------------------------------------------------------------------------------- 1 | #ifndef __STRING_H__ 2 | #define __STRING_H__ 3 | 4 | void *memset(void *, int, unsigned int); 5 | void * 6 | memcpy(void *dst, const void *src, unsigned int n); 7 | void * 8 | memmove(void *dst, const void *src, unsigned int n); 9 | 10 | #endif -------------------------------------------------------------------------------- /08-BlockDeviceDriver/sys.h: -------------------------------------------------------------------------------- 1 | #ifndef __SYS_H__ 2 | #define __SYS_H__ 3 | 4 | #include "riscv.h" 5 | extern void sys_timer(); 6 | extern void sys_switch(struct context *ctx_old, struct context *ctx_new); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /08-BlockDeviceDriver/sys.s: -------------------------------------------------------------------------------- 1 | # This Code derived from xv6-riscv (64bit) 2 | # -- https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/swtch.S 3 | 4 | # ============ MACRO ================== 5 | .macro ctx_save base 6 | sw ra, 0(\base) 7 | sw sp, 4(\base) 8 | sw s0, 8(\base) 9 | sw s1, 12(\base) 10 | sw s2, 16(\base) 11 | sw s3, 20(\base) 12 | sw s4, 24(\base) 13 | sw s5, 28(\base) 14 | sw s6, 32(\base) 15 | sw s7, 36(\base) 16 | sw s8, 40(\base) 17 | sw s9, 44(\base) 18 | sw s10, 48(\base) 19 | sw s11, 52(\base) 20 | .endm 21 | 22 | .macro ctx_load base 23 | lw ra, 0(\base) 24 | lw sp, 4(\base) 25 | lw s0, 8(\base) 26 | lw s1, 12(\base) 27 | lw s2, 16(\base) 28 | lw s3, 20(\base) 29 | lw s4, 24(\base) 30 | lw s5, 28(\base) 31 | lw s6, 32(\base) 32 | lw s7, 36(\base) 33 | lw s8, 40(\base) 34 | lw s9, 44(\base) 35 | lw s10, 48(\base) 36 | lw s11, 52(\base) 37 | .endm 38 | 39 | .macro reg_save base 40 | # save the registers. 41 | sw ra, 0(\base) 42 | sw sp, 4(\base) 43 | sw gp, 8(\base) 44 | sw tp, 12(\base) 45 | sw t0, 16(\base) 46 | sw t1, 20(\base) 47 | sw t2, 24(\base) 48 | sw s0, 28(\base) 49 | sw s1, 32(\base) 50 | sw a0, 36(\base) 51 | sw a1, 40(\base) 52 | sw a2, 44(\base) 53 | sw a3, 48(\base) 54 | sw a4, 52(\base) 55 | sw a5, 56(\base) 56 | sw a6, 60(\base) 57 | sw a7, 64(\base) 58 | sw s2, 68(\base) 59 | sw s3, 72(\base) 60 | sw s4, 76(\base) 61 | sw s5, 80(\base) 62 | sw s6, 84(\base) 63 | sw s7, 88(\base) 64 | sw s8, 92(\base) 65 | sw s9, 96(\base) 66 | sw s10, 100(\base) 67 | sw s11, 104(\base) 68 | sw t3, 108(\base) 69 | sw t4, 112(\base) 70 | sw t5, 116(\base) 71 | sw t6, 120(\base) 72 | .endm 73 | 74 | .macro reg_load base 75 | # restore registers. 76 | lw ra, 0(\base) 77 | lw sp, 4(\base) 78 | lw gp, 8(\base) 79 | # not this, in case we moved CPUs: lw tp, 12(\base) 80 | lw t0, 16(\base) 81 | lw t1, 20(\base) 82 | lw t2, 24(\base) 83 | lw s0, 28(\base) 84 | lw s1, 32(\base) 85 | lw a0, 36(\base) 86 | lw a1, 40(\base) 87 | lw a2, 44(\base) 88 | lw a3, 48(\base) 89 | lw a4, 52(\base) 90 | lw a5, 56(\base) 91 | lw a6, 60(\base) 92 | lw a7, 64(\base) 93 | lw s2, 68(\base) 94 | lw s3, 72(\base) 95 | lw s4, 76(\base) 96 | lw s5, 80(\base) 97 | lw s6, 84(\base) 98 | lw s7, 88(\base) 99 | lw s8, 92(\base) 100 | lw s9, 96(\base) 101 | lw s10, 100(\base) 102 | lw s11, 104(\base) 103 | lw t3, 108(\base) 104 | lw t4, 112(\base) 105 | lw t5, 116(\base) 106 | lw t6, 120(\base) 107 | .endm 108 | # ============ Macro END ================== 109 | 110 | # Context switch 111 | # 112 | # void sys_switch(struct context *old, struct context *new); 113 | # 114 | # Save current registers in old. Load from new. 115 | 116 | .globl sys_switch 117 | .align 4 118 | sys_switch: 119 | 120 | ctx_save a0 # a0 => struct context *old 121 | ctx_load a1 # a1 => struct context *new 122 | ret # pc=ra; swtch to new task (new->ra) 123 | 124 | .globl atomic_swap 125 | .align 4 126 | atomic_swap: 127 | li a5, 1 128 | amoswap.w.aq a5, a5, 0(a0) 129 | mv a0, a5 130 | ret 131 | 132 | .globl trap_vector 133 | # the trap vector base address must always be aligned on a 4-byte boundary 134 | .align 4 135 | trap_vector: 136 | # save context(registers). 137 | csrrw t6, mscratch, t6 # swap t6 and mscratch 138 | reg_save t6 139 | csrw mscratch, t6 140 | # call the C trap handler in trap.c 141 | csrr a0, mepc 142 | csrr a1, mcause 143 | call trap_handler 144 | 145 | # trap_handler will return the return address via a0. 146 | csrw mepc, a0 147 | 148 | # load context(registers). 149 | csrr t6, mscratch 150 | reg_load t6 151 | mret 152 | 153 | -------------------------------------------------------------------------------- /08-BlockDeviceDriver/task.c: -------------------------------------------------------------------------------- 1 | #include "task.h" 2 | #include "lib.h" 3 | 4 | uint8_t task_stack[MAX_TASK][STACK_SIZE]; 5 | struct context ctx_os; 6 | struct context ctx_tasks[MAX_TASK]; 7 | struct context *ctx_now; 8 | int taskTop = 0; // total number of task 9 | 10 | // create a new task 11 | int task_create(void (*task)(void)) 12 | { 13 | int i = taskTop++; 14 | ctx_tasks[i].ra = (reg_t)task; 15 | ctx_tasks[i].sp = (reg_t)&task_stack[i][STACK_SIZE - 1]; 16 | return i; 17 | } 18 | 19 | // switch to task[i] 20 | void task_go(int i) 21 | { 22 | ctx_now = &ctx_tasks[i]; 23 | sys_switch(&ctx_os, &ctx_tasks[i]); 24 | } 25 | 26 | // switch back to os 27 | void task_os() 28 | { 29 | struct context *ctx = ctx_now; 30 | ctx_now = &ctx_os; 31 | sys_switch(ctx, &ctx_os); 32 | } 33 | -------------------------------------------------------------------------------- /08-BlockDeviceDriver/task.h: -------------------------------------------------------------------------------- 1 | #ifndef __TASK_H__ 2 | #define __TASK_H__ 3 | 4 | #include "riscv.h" 5 | #include "sys.h" 6 | 7 | #define MAX_TASK 10 8 | #define STACK_SIZE 1024 9 | 10 | extern int taskTop; 11 | 12 | extern int task_create(void (*task)(void)); 13 | extern void task_go(int i); 14 | extern void task_os(); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /08-BlockDeviceDriver/timer.c: -------------------------------------------------------------------------------- 1 | #include "timer.h" 2 | 3 | // a scratch area per CPU for machine-mode timer interrupts. 4 | reg_t timer_scratch[NCPU][5]; 5 | 6 | #define interval 20000000 // cycles; about 2 second in qemu. 7 | 8 | void timer_init() 9 | { 10 | // each CPU has a separate source of timer interrupts. 11 | int id = r_mhartid(); 12 | 13 | // ask the CLINT for a timer interrupt. 14 | // int interval = 1000000; // cycles; about 1/10th second in qemu. 15 | 16 | *(reg_t *)CLINT_MTIMECMP(id) = *(reg_t *)CLINT_MTIME + interval; 17 | 18 | // prepare information in scratch[] for timervec. 19 | // scratch[0..2] : space for timervec to save registers. 20 | // scratch[3] : address of CLINT MTIMECMP register. 21 | // scratch[4] : desired interval (in cycles) between timer interrupts. 22 | reg_t *scratch = &timer_scratch[id][0]; 23 | scratch[3] = CLINT_MTIMECMP(id); 24 | scratch[4] = interval; 25 | w_mscratch((reg_t)scratch); 26 | 27 | // enable machine-mode timer interrupts. 28 | w_mie(r_mie() | MIE_MTIE); 29 | } 30 | 31 | static int timer_count = 0; 32 | 33 | void timer_handler() 34 | { 35 | lib_printf("timer_handler: %d\n", ++timer_count); 36 | int id = r_mhartid(); 37 | *(reg_t *)CLINT_MTIMECMP(id) = *(reg_t *)CLINT_MTIME + interval; 38 | } 39 | -------------------------------------------------------------------------------- /08-BlockDeviceDriver/timer.h: -------------------------------------------------------------------------------- 1 | #ifndef __TIMER_H__ 2 | #define __TIMER_H__ 3 | 4 | #include "riscv.h" 5 | #include "sys.h" 6 | #include "lib.h" 7 | #include "task.h" 8 | 9 | extern void timer_handler(); 10 | extern void timer_init(); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /08-BlockDeviceDriver/trap.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | extern void trap_vector(); 3 | extern void virtio_disk_isr(); 4 | void trap_init() 5 | { 6 | // set the machine-mode trap handler. 7 | w_mtvec((reg_t)trap_vector); 8 | } 9 | 10 | void external_handler() 11 | { 12 | int irq = plic_claim(); 13 | if (irq == UART0_IRQ) 14 | { 15 | lib_isr(); 16 | } 17 | else if (irq == VIRTIO_IRQ) 18 | { 19 | lib_puts("Virtio IRQ\n"); 20 | virtio_disk_isr(); 21 | } 22 | else if (irq) 23 | { 24 | lib_printf("unexpected interrupt irq = %d\n", irq); 25 | } 26 | 27 | if (irq) 28 | { 29 | plic_complete(irq); 30 | } 31 | } 32 | 33 | reg_t trap_handler(reg_t epc, reg_t cause) 34 | { 35 | reg_t return_pc = epc; 36 | reg_t cause_code = cause & 0xfff; 37 | 38 | if (cause & 0x80000000) 39 | { 40 | /* Asynchronous trap - interrupt */ 41 | switch (cause_code) 42 | { 43 | case 3: 44 | /* software interruption */ 45 | break; 46 | case 7: 47 | /* timer interruption */ 48 | // disable machine-mode timer interrupts. 49 | w_mie(r_mie() & ~(1 << 7)); 50 | timer_handler(); 51 | return_pc = (reg_t)&os_kernel; 52 | // enable machine-mode timer interrupts. 53 | w_mie(r_mie() | MIE_MTIE); 54 | break; 55 | case 11: 56 | /* external interruption */ 57 | external_handler(); 58 | break; 59 | default: 60 | lib_puts("unknown async exception!\n"); 61 | break; 62 | } 63 | } 64 | else 65 | { 66 | switch (cause_code) 67 | { 68 | case 2: 69 | lib_puts("Illegal instruction!\n"); 70 | break; 71 | case 5: 72 | lib_puts("Fault load!\n"); 73 | break; 74 | case 7: 75 | lib_puts("Fault store!\n"); 76 | break; 77 | case 11: 78 | lib_puts("Machine mode ecall!\n"); 79 | break; 80 | default: 81 | /* Synchronous trap - exception */ 82 | lib_printf("Sync exceptions! cause code: %d\n", cause_code); 83 | break; 84 | } 85 | for (;;) 86 | { 87 | /* code */ 88 | } 89 | } 90 | return return_pc; 91 | } 92 | -------------------------------------------------------------------------------- /08-BlockDeviceDriver/types.h: -------------------------------------------------------------------------------- 1 | #ifndef __TYPES_H__ 2 | #define __TYPES_H__ 3 | 4 | typedef unsigned int uint; 5 | typedef unsigned short ushort; 6 | typedef unsigned char uchar; 7 | 8 | typedef unsigned char uint8; 9 | typedef unsigned short uint16; 10 | typedef unsigned int uint32; 11 | typedef unsigned long long uint64; 12 | 13 | #endif -------------------------------------------------------------------------------- /08-BlockDeviceDriver/user.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | 3 | int shared_var = 500; 4 | 5 | lock_t lock; 6 | 7 | void user_task0(void) 8 | { 9 | lib_puts("Task0: Created!\n"); 10 | while (1) 11 | { 12 | lib_puts("Task0: Running...\n"); 13 | lib_delay(1000); 14 | } 15 | } 16 | 17 | void user_task1(void) 18 | { 19 | lib_puts("Task1: Created!\n"); 20 | while (1) 21 | { 22 | lib_puts("Task1: Running...\n"); 23 | lib_delay(1000); 24 | } 25 | } 26 | 27 | void user_task2(void) 28 | { 29 | lib_puts("Task2: Created!\n"); 30 | while (1) 31 | { 32 | for (int i = 0; i < 50; i++) 33 | { 34 | lock_acquire(&lock); 35 | shared_var++; 36 | lock_free(&lock); 37 | lib_delay(100); 38 | } 39 | lib_printf("The value of shared_var is: %d \n", shared_var); 40 | } 41 | } 42 | 43 | void user_task3(void) 44 | { 45 | lib_puts("Task3: Created!\n"); 46 | while (1) 47 | { 48 | lib_puts("Trying to get the lock... \n"); 49 | lock_acquire(&lock); 50 | lib_puts("Get the lock!\n"); 51 | lock_free(&lock); 52 | lib_delay(1000); 53 | } 54 | } 55 | 56 | void user_init() 57 | { 58 | // lock_init(&lock); 59 | task_create(&user_task0); 60 | task_create(&user_task1); 61 | // task_create(&user_task2); 62 | // task_create(&user_task3); 63 | } 64 | -------------------------------------------------------------------------------- /09-MemoryAllocator/Makefile: -------------------------------------------------------------------------------- 1 | CC = riscv64-unknown-elf-gcc 2 | CFLAGS = -I./include -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall -w 3 | GDB = riscv64-unknown-elf-gdb 4 | SOURCE = src/ 5 | 6 | OBJ = \ 7 | $(SOURCE)start.s \ 8 | $(SOURCE)sys.s \ 9 | $(SOURCE)mem.s \ 10 | $(SOURCE)lib.c \ 11 | $(SOURCE)timer.c \ 12 | $(SOURCE)task.c \ 13 | $(SOURCE)os.c \ 14 | $(SOURCE)user.c \ 15 | $(SOURCE)trap.c \ 16 | $(SOURCE)lock.c \ 17 | $(SOURCE)plic.c \ 18 | $(SOURCE)virtio.c \ 19 | $(SOURCE)string.c \ 20 | $(SOURCE)alloc.c 21 | 22 | QEMU = qemu-system-riscv32 23 | QFLAGS = -nographic -smp 4 -machine virt -bios none 24 | QFLAGS += -drive if=none,format=raw,file=hdd.dsk,id=x0 25 | QFLAGS += -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 26 | 27 | OBJDUMP = riscv64-unknown-elf-objdump 28 | 29 | all: clean os.elf hdd.dsk qemu 30 | 31 | test: clean os.elf qemu 32 | 33 | os.elf: $(OBJ) 34 | $(CC) $(CFLAGS) -T os.ld -o os.elf $^ 35 | 36 | qemu: $(TARGET) hdd.dsk 37 | @qemu-system-riscv32 -M ? | grep virt >/dev/null || exit 38 | @echo "Press Ctrl-A and then X to exit QEMU" 39 | $(QEMU) $(QFLAGS) -kernel os.elf 40 | 41 | clean: 42 | rm -f *.elf *.img 43 | 44 | hdd.dsk: 45 | dd if=/dev/urandom of=hdd.dsk bs=1M count=32 46 | 47 | .PHONY : debug 48 | debug: clean os.elf hdd.dsk 49 | @echo "Press Ctrl-C and then input 'quit' to exit GDB and QEMU" 50 | @echo "-------------------------------------------------------" 51 | @${QEMU} ${QFLAGS} -kernel os.elf -s -S & 52 | @${GDB} os.elf -q -x ./gdbinit -------------------------------------------------------------------------------- /09-MemoryAllocator/README.md: -------------------------------------------------------------------------------- 1 | # 09-MemoryAllocator 2 | 3 | ## Build & Run 4 | 5 | ```sh 6 | IAN@DESKTOP-9AEMEPL MINGW64 ~/Desktop/mini-riscv-os/09-MemoryAllocator (feat/memoryAlloc) 7 | $ make all 8 | rm -f *.elf *.img 9 | riscv64-unknown-elf-gcc -I./include -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall -w -T os.ld -o os.elf src/start.s src/sys.s src/mem.s src/lib.c src/timer.c src/task.c src/os.c src/user.c src/trap.c src/lock.c src/plic.c src/virtio.c src/string.c src/alloc.c 10 | Press Ctrl-A and then X to exit QEMU 11 | qemu-system-riscv32 -nographic -smp 4 -machine virt -bios none -drive if=none,format=raw,file=hdd.dsk,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 -kernel os.elf 12 | HEAP_START = 8001100c, HEAP_SIZE = 07feeff4, num of pages = 521967 13 | TEXT: 0x80000000 -> 0x8000ac78 14 | RODATA: 0x8000ac78 -> 0x8000b09f 15 | DATA: 0x8000c000 -> 0x8000c004 16 | BSS: 0x8000d000 -> 0x8001100c 17 | HEAP: 0x80091100 -> 0x88000000 18 | OS start 19 | Disk init work is success! 20 | buffer init... 21 | block read... 22 | Virtio IRQ 23 | 000000fd 24 | 000000af 25 | 000000f8 26 | 000000ab 27 | 00000088 28 | 00000042 29 | 000000cc 30 | 00000017 31 | 00000022 32 | 0000008e 33 | 34 | p = 0x80091700 35 | p2 = 0x80091300 36 | p3 = 0x80091100 37 | OS: Activate next task 38 | Task0: Created! 39 | Task0: Running... 40 | Task0: Running... 41 | Task0: Running... 42 | Task0: Running... 43 | Task0: Running... 44 | Task0: Running... 45 | Task0: Running... 46 | Task0: Running... 47 | Task0: Running... 48 | Task0: Running... 49 | Task0: Running... 50 | Task0: Running... 51 | QEMU: Terminated 52 | ``` 53 | 54 | ## Debug mode 55 | 56 | ```sh 57 | make debug 58 | riscv64-unknown-elf-gcc -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall -T os.ld -o os.elf start.s sys.s lib.c timer.c task.c os.c user.c trap.c lock.c 59 | Press Ctrl-C and then input 'quit' to exit GDB and QEMU 60 | ------------------------------------------------------- 61 | Reading symbols from os.elf... 62 | Breakpoint 1 at 0x80000000: file start.s, line 7. 63 | 0x00001000 in ?? () 64 | => 0x00001000: 97 02 00 00 auipc t0,0x0 65 | 66 | Thread 1 hit Breakpoint 1, _start () at start.s:7 67 | 7 csrr t0, mhartid # read current hart id 68 | => 0x80000000 <_start+0>: f3 22 40 f1 csrr t0,mhartid 69 | (gdb) 70 | ``` 71 | 72 | ### set the breakpoint 73 | 74 | You can set the breakpoint in any c file: 75 | 76 | ```sh 77 | (gdb) b trap.c:27 78 | Breakpoint 2 at 0x80008f78: file trap.c, line 27. 79 | (gdb) 80 | ``` 81 | 82 | As the example above, when process running on trap.c, line 27 (Timer Interrupt). 83 | The process will be suspended automatically until you press the key `c` (continue) or `s` (step). 84 | -------------------------------------------------------------------------------- /09-MemoryAllocator/gdbinit: -------------------------------------------------------------------------------- 1 | set disassemble-next-line on 2 | b _start 3 | target remote : 1234 4 | c -------------------------------------------------------------------------------- /09-MemoryAllocator/include/lib.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIB_H__ 2 | #define __LIB_H__ 3 | 4 | #include "riscv.h" 5 | #include 6 | #include 7 | 8 | #define lib_error(...) \ 9 | { \ 10 | lib_printf(__VA_ARGS__); \ 11 | while (1) \ 12 | { \ 13 | } \ 14 | } \ 15 | } 16 | 17 | extern char *lib_gets(char *); 18 | extern void uart_init(); 19 | extern void lib_isr(void); 20 | extern int lib_getc(void); 21 | extern void lib_delay(volatile int count); 22 | extern int lib_putc(char ch); 23 | extern void lib_puts(char *s); 24 | extern int lib_printf(const char *s, ...); 25 | extern int lib_vprintf(const char *s, va_list vl); 26 | extern int lib_vsnprintf(char *out, size_t n, const char *s, va_list vl); 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /09-MemoryAllocator/include/os.h: -------------------------------------------------------------------------------- 1 | #ifndef __OS_H__ 2 | #define __OS_H__ 3 | 4 | #include "riscv.h" 5 | #include "lib.h" 6 | #include "task.h" 7 | #include "timer.h" 8 | #include "string.h" 9 | 10 | extern void panic(char *); 11 | extern void user_init(); 12 | extern void os_kernel(); 13 | extern int os_main(void); 14 | 15 | // PLIC 16 | extern void plic_init(); 17 | extern int plic_claim(); 18 | extern void plic_complete(int); 19 | 20 | // lock 21 | extern void basic_lock(); 22 | extern void basic_unlock(); 23 | 24 | typedef struct lock 25 | { 26 | volatile int locked; 27 | } lock_t; 28 | 29 | extern int atomic_swap(lock_t *); 30 | 31 | extern void lock_init(lock_t *lock); 32 | 33 | extern void lock_acquire(lock_t *lock); 34 | 35 | extern void lock_free(lock_t *lock); 36 | 37 | // memory allocator 38 | 39 | extern void *malloc(size_t size); 40 | extern void free(void *p); 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /09-MemoryAllocator/include/riscv.h: -------------------------------------------------------------------------------- 1 | #ifndef __RISCV_H__ 2 | #define __RISCV_H__ 3 | 4 | #include 5 | 6 | #define reg_t uint32_t // RISCV32: register is 32bits 7 | // define reg_t as uint64_t // RISCV64: register is 64bits 8 | 9 | #define PGSIZE 4096 // bytes per page 10 | 11 | // ref: https://www.activexperts.com/serial-port-component/tutorials/uart/ 12 | #define UART 0x10000000L 13 | #define UART_THR (volatile uint8_t *)(UART + 0x00) // THR:transmitter holding register 14 | #define UART_RHR (volatile uint8_t *)(UART + 0x00) // RHR:Receive holding register 15 | #define UART_DLL (volatile uint8_t *)(UART + 0x00) // LSB of Divisor Latch (write mode) 16 | #define UART_DLM (volatile uint8_t *)(UART + 0x01) // MSB of Divisor Latch (write mode) 17 | #define UART_IER (volatile uint8_t *)(UART + 0x01) // Interrupt Enable Register 18 | #define UART_LCR (volatile uint8_t *)(UART + 0x03) // Line Control Register 19 | #define UART_LSR (volatile uint8_t *)(UART + 0x05) // LSR:line status register 20 | #define UART_LSR_EMPTY_MASK 0x40 // LSR Bit 6: Transmitter empty; both the THR and LSR are empty 21 | 22 | #define UART_REGR(reg) (*(reg)) 23 | #define UART_REGW(reg, v) ((*reg) = (v)) 24 | 25 | // ref: https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h 26 | // enum { 27 | // UART0_IRQ = 10, 28 | // RTC_IRQ = 11, 29 | // VIRTIO_IRQ = 1, /* 1 to 8 */ 30 | // VIRTIO_COUNT = 8, 31 | // PCIE_IRQ = 0x20, /* 32 to 35 */ 32 | // VIRTIO_NDEV = 0x35 /* Arbitrary maximum number of interrupts */ 33 | // }; 34 | #define UART0_IRQ 10 35 | #define VIRTIO_IRQ 1 36 | 37 | // Saved registers for kernel context switches. 38 | struct context 39 | { 40 | reg_t ra; 41 | reg_t sp; 42 | 43 | // callee-saved 44 | reg_t s0; 45 | reg_t s1; 46 | reg_t s2; 47 | reg_t s3; 48 | reg_t s4; 49 | reg_t s5; 50 | reg_t s6; 51 | reg_t s7; 52 | reg_t s8; 53 | reg_t s9; 54 | reg_t s10; 55 | reg_t s11; 56 | }; 57 | 58 | // ref: https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/riscv.h 59 | // 60 | // local interrupt controller, which contains the timer. 61 | // ================== Timer Interrput ==================== 62 | 63 | #define NCPU 8 // maximum number of CPUs 64 | #define CLINT 0x2000000 65 | #define CLINT_MTIMECMP(hartid) (CLINT + 0x4000 + 4 * (hartid)) 66 | #define CLINT_MTIME (CLINT + 0xBFF8) // cycles since boot. 67 | 68 | static inline reg_t r_tp() 69 | { 70 | reg_t x; 71 | asm volatile("mv %0, tp" 72 | : "=r"(x)); 73 | return x; 74 | } 75 | 76 | // which hart (core) is this? 77 | 78 | static inline reg_t r_mhartid() 79 | { 80 | reg_t x; 81 | asm volatile("csrr %0, mhartid" 82 | : "=r"(x)); 83 | return x; 84 | } 85 | 86 | // Machine Status Register, mstatus 87 | #define MSTATUS_MPP_MASK (3L << 11) // previous mode. 88 | #define MSTATUS_MPP_M (3L << 11) 89 | #define MSTATUS_MPP_S (1L << 11) 90 | #define MSTATUS_MPP_U (0L << 11) 91 | #define MSTATUS_MIE (1L << 3) // machine-mode interrupt enable. 92 | 93 | static inline reg_t r_mstatus() 94 | { 95 | reg_t x; 96 | asm volatile("csrr %0, mstatus" 97 | : "=r"(x)); 98 | return x; 99 | } 100 | 101 | static inline void w_mstatus(reg_t x) 102 | { 103 | asm volatile("csrw mstatus, %0" 104 | : 105 | : "r"(x)); 106 | } 107 | 108 | // machine exception program counter, holds the 109 | // instruction address to which a return from 110 | // exception will go. 111 | static inline void w_mepc(reg_t x) 112 | { 113 | asm volatile("csrw mepc, %0" 114 | : 115 | : "r"(x)); 116 | } 117 | 118 | static inline reg_t r_mepc() 119 | { 120 | reg_t x; 121 | asm volatile("csrr %0, mepc" 122 | : "=r"(x)); 123 | return x; 124 | } 125 | 126 | // Machine Scratch register, for early trap handler 127 | static inline void w_mscratch(reg_t x) 128 | { 129 | asm volatile("csrw mscratch, %0" 130 | : 131 | : "r"(x)); 132 | } 133 | 134 | // Machine-mode interrupt vector 135 | static inline void w_mtvec(reg_t x) 136 | { 137 | asm volatile("csrw mtvec, %0" 138 | : 139 | : "r"(x)); 140 | } 141 | 142 | // Machine-mode Interrupt Enable 143 | #define MIE_MEIE (1L << 11) // external 144 | #define MIE_MTIE (1L << 7) // timer 145 | #define MIE_MSIE (1L << 3) // software 146 | 147 | static inline reg_t r_mie() 148 | { 149 | reg_t x; 150 | asm volatile("csrr %0, mie" 151 | : "=r"(x)); 152 | return x; 153 | } 154 | 155 | static inline void w_mie(reg_t x) 156 | { 157 | asm volatile("csrw mie, %0" 158 | : 159 | : "r"(x)); 160 | } 161 | 162 | #endif 163 | -------------------------------------------------------------------------------- /09-MemoryAllocator/include/string.h: -------------------------------------------------------------------------------- 1 | #ifndef __STRING_H__ 2 | #define __STRING_H__ 3 | 4 | void *memset(void *, int, unsigned int); 5 | void * 6 | memcpy(void *dst, const void *src, unsigned int n); 7 | void * 8 | memmove(void *dst, const void *src, unsigned int n); 9 | 10 | #endif -------------------------------------------------------------------------------- /09-MemoryAllocator/include/sys.h: -------------------------------------------------------------------------------- 1 | #ifndef __SYS_H__ 2 | #define __SYS_H__ 3 | 4 | #include "riscv.h" 5 | extern void sys_timer(); 6 | extern void sys_switch(struct context *ctx_old, struct context *ctx_new); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /09-MemoryAllocator/include/task.h: -------------------------------------------------------------------------------- 1 | #ifndef __TASK_H__ 2 | #define __TASK_H__ 3 | 4 | #include "riscv.h" 5 | #include "sys.h" 6 | 7 | #define MAX_TASK 10 8 | #define STACK_SIZE 1024 9 | 10 | extern int taskTop; 11 | 12 | extern int task_create(void (*task)(void)); 13 | extern void task_go(int i); 14 | extern void task_os(); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /09-MemoryAllocator/include/timer.h: -------------------------------------------------------------------------------- 1 | #ifndef __TIMER_H__ 2 | #define __TIMER_H__ 3 | 4 | #include "riscv.h" 5 | #include "sys.h" 6 | #include "lib.h" 7 | #include "task.h" 8 | 9 | extern void timer_handler(); 10 | extern void timer_init(); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /09-MemoryAllocator/include/types.h: -------------------------------------------------------------------------------- 1 | #ifndef __TYPES_H__ 2 | #define __TYPES_H__ 3 | 4 | typedef unsigned int uint; 5 | typedef unsigned short ushort; 6 | typedef unsigned char uchar; 7 | 8 | typedef unsigned char uint8; 9 | typedef unsigned short uint16; 10 | typedef unsigned int uint32; 11 | typedef unsigned long long uint64; 12 | 13 | #endif -------------------------------------------------------------------------------- /09-MemoryAllocator/os.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_ARCH( "riscv" ) 2 | 3 | ENTRY( _start ) 4 | 5 | MEMORY 6 | { 7 | ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M 8 | } 9 | 10 | PHDRS 11 | { 12 | text PT_LOAD; 13 | data PT_LOAD; 14 | bss PT_LOAD; 15 | } 16 | 17 | SECTIONS 18 | { 19 | .text : { 20 | PROVIDE(_text_start = .); 21 | *(.text.init) *(.text .text.*) 22 | PROVIDE(_text_end = .); 23 | } >ram AT>ram :text 24 | 25 | .rodata : { 26 | PROVIDE(_rodata_start = .); 27 | *(.rodata .rodata.*) 28 | PROVIDE(_rodata_end = .); 29 | } >ram AT>ram :text 30 | 31 | .data : { 32 | . = ALIGN(4096); 33 | PROVIDE(_data_start = .); 34 | *(.sdata .sdata.*) *(.data .data.*) 35 | PROVIDE(_data_end = .); 36 | } >ram AT>ram :data 37 | 38 | .bss :{ 39 | PROVIDE(_bss_start = .); 40 | *(.sbss .sbss.*) *(.bss .bss.*) 41 | PROVIDE(_bss_end = .); 42 | } >ram AT>ram :bss 43 | 44 | PROVIDE(_memory_start = ORIGIN(ram)); 45 | PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram)); 46 | 47 | PROVIDE(_heap_start = _bss_end); 48 | PROVIDE(_heap_size = _memory_end - _heap_start); 49 | } 50 | -------------------------------------------------------------------------------- /09-MemoryAllocator/src/lock.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | 3 | void lock_init(lock_t *lock) 4 | { 5 | lock->locked = 0; 6 | } 7 | 8 | void lock_acquire(lock_t *lock) 9 | { 10 | for (;;) 11 | { 12 | if (!atomic_swap(lock)) 13 | { 14 | break; 15 | } 16 | } 17 | } 18 | 19 | void lock_free(lock_t *lock) 20 | { 21 | lock->locked = 0; 22 | } 23 | 24 | void basic_lock() 25 | { 26 | w_mstatus(r_mstatus() & ~MSTATUS_MIE); 27 | } 28 | 29 | void basic_unlock() 30 | { 31 | w_mstatus(r_mstatus() | MSTATUS_MIE); 32 | } 33 | -------------------------------------------------------------------------------- /09-MemoryAllocator/src/mem.s: -------------------------------------------------------------------------------- 1 | .section .rodata 2 | .global HEAP_START 3 | HEAP_START: .word _heap_start 4 | 5 | .global HEAP_SIZE 6 | HEAP_SIZE: .word _heap_size 7 | 8 | .global TEXT_START 9 | TEXT_START: .word _text_start 10 | 11 | .global TEXT_END 12 | TEXT_END: .word _text_end 13 | 14 | .global DATA_START 15 | DATA_START: .word _data_start 16 | 17 | .global DATA_END 18 | DATA_END: .word _data_end 19 | 20 | .global RODATA_START 21 | RODATA_START: .word _rodata_start 22 | 23 | .global RODATA_END 24 | RODATA_END: .word _rodata_end 25 | 26 | .global BSS_START 27 | BSS_START: .word _bss_start 28 | 29 | .global BSS_END 30 | BSS_END: .word _bss_end -------------------------------------------------------------------------------- /09-MemoryAllocator/src/os.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | 3 | extern void page_init(void); 4 | 5 | extern void trap_init(void); 6 | 7 | void panic(char *s) 8 | { 9 | lib_puts(s); 10 | for (;;) 11 | { 12 | } 13 | } 14 | 15 | void os_kernel() 16 | { 17 | task_os(); 18 | } 19 | 20 | void disk_read() 21 | { 22 | virtio_tester(0); 23 | } 24 | 25 | void os_start() 26 | { 27 | uart_init(); 28 | page_init(); 29 | lib_puts("OS start\n"); 30 | user_init(); 31 | trap_init(); 32 | plic_init(); 33 | virtio_disk_init(); 34 | timer_init(); // start timer interrupt ... 35 | } 36 | 37 | int os_main(void) 38 | { 39 | os_start(); 40 | disk_read(); 41 | page_test(); 42 | int current_task = 0; 43 | while (1) 44 | { 45 | lib_puts("OS: Activate next task\n"); 46 | task_go(current_task); 47 | lib_puts("OS: Back to OS\n"); 48 | current_task = (current_task + 1) % taskTop; // Round Robin Scheduling 49 | lib_puts("\n"); 50 | } 51 | return 0; 52 | } 53 | -------------------------------------------------------------------------------- /09-MemoryAllocator/src/plic.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | 3 | // ref: https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h 4 | // Intro: https://github.com/ianchen0119/AwesomeCS/wiki/2-5-RISC-V::%E4%B8%AD%E6%96%B7%E8%88%87%E7%95%B0%E5%B8%B8%E8%99%95%E7%90%86----PLIC-%E4%BB%8B%E7%B4%B9 5 | #define PLIC_BASE 0x0c000000L 6 | #define PLIC_PRIORITY(id) (PLIC_BASE + (id)*4) 7 | #define PLIC_PENDING(id) (PLIC_BASE + 0x1000 + ((id) / 32)) 8 | #define PLIC_MENABLE(hart) (PLIC_BASE + 0x2000 + (hart)*0x80) 9 | #define PLIC_MTHRESHOLD(hart) (PLIC_BASE + 0x200000 + (hart)*0x1000) 10 | #define PLIC_MCLAIM(hart) (PLIC_BASE + 0x200004 + (hart)*0x1000) 11 | #define PLIC_MCOMPLETE(hart) (PLIC_BASE + 0x200004 + (hart)*0x1000) 12 | 13 | void plic_init() 14 | { 15 | int hart = r_tp(); 16 | // QEMU Virt machine support 7 priority (1 - 7), 17 | // The "0" is reserved, and the lowest priority is "1". 18 | 19 | *(uint32_t *)PLIC_PRIORITY(UART0_IRQ) = 1; 20 | *(uint32_t *)PLIC_PRIORITY(VIRTIO_IRQ) = 1; 21 | 22 | /* Enable UART0 and VIRTIO */ 23 | 24 | *(uint32_t *)PLIC_MENABLE(hart) = (1 << UART0_IRQ) | (1 << VIRTIO_IRQ); 25 | 26 | /* Set priority threshold for UART0. */ 27 | 28 | *(uint32_t *)PLIC_MTHRESHOLD(hart) = 0; 29 | 30 | /* enable machine-mode external interrupts. */ 31 | w_mie(r_mie() | MIE_MEIE); 32 | 33 | // enable machine-mode interrupts. 34 | w_mstatus(r_mstatus() | MSTATUS_MIE); 35 | } 36 | 37 | int plic_claim() 38 | { 39 | int hart = r_tp(); 40 | int irq = *(uint32_t *)PLIC_MCLAIM(hart); 41 | return irq; 42 | } 43 | 44 | void plic_complete(int irq) 45 | { 46 | int hart = r_tp(); 47 | *(uint32_t *)PLIC_MCOMPLETE(hart) = irq; 48 | } -------------------------------------------------------------------------------- /09-MemoryAllocator/src/start.s: -------------------------------------------------------------------------------- 1 | .equ STACK_SIZE, 8192 2 | 3 | .global _start 4 | 5 | _start: 6 | csrr a0, mhartid # 讀取核心代號 7 | bnez a0, park # 若不是 0 號核心,跳到 park 停止 8 | la sp, stacks + STACK_SIZE # 0 號核心設定堆疊 9 | j os_main # 0 號核心跳到主程式 os_main 10 | 11 | park: 12 | wfi 13 | j park 14 | 15 | stacks: 16 | .skip STACK_SIZE # 分配堆疊空間 17 | -------------------------------------------------------------------------------- /09-MemoryAllocator/src/string.c: -------------------------------------------------------------------------------- 1 | #include "string.h" 2 | 3 | void *memset(void *dst, int c, unsigned int n) 4 | { 5 | char *cdst = (char *)dst; 6 | int i; 7 | for (i = 0; i < n; i++) 8 | { 9 | cdst[i] = c; 10 | } 11 | return dst; 12 | } 13 | 14 | void * 15 | memcpy(void *dst, const void *src, unsigned int n) 16 | { 17 | return memmove(dst, src, n); 18 | } 19 | 20 | void * 21 | memmove(void *dst, const void *src, unsigned int n) 22 | { 23 | const char *s; 24 | char *d; 25 | 26 | s = src; 27 | d = dst; 28 | if (s < d && s + n > d) 29 | { 30 | s += n; 31 | d += n; 32 | while (n-- > 0) 33 | *--d = *--s; 34 | } 35 | else 36 | while (n-- > 0) 37 | *d++ = *s++; 38 | 39 | return dst; 40 | } -------------------------------------------------------------------------------- /09-MemoryAllocator/src/sys.s: -------------------------------------------------------------------------------- 1 | # This Code derived from xv6-riscv (64bit) 2 | # -- https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/swtch.S 3 | 4 | # ============ MACRO ================== 5 | .macro ctx_save base 6 | sw ra, 0(\base) 7 | sw sp, 4(\base) 8 | sw s0, 8(\base) 9 | sw s1, 12(\base) 10 | sw s2, 16(\base) 11 | sw s3, 20(\base) 12 | sw s4, 24(\base) 13 | sw s5, 28(\base) 14 | sw s6, 32(\base) 15 | sw s7, 36(\base) 16 | sw s8, 40(\base) 17 | sw s9, 44(\base) 18 | sw s10, 48(\base) 19 | sw s11, 52(\base) 20 | .endm 21 | 22 | .macro ctx_load base 23 | lw ra, 0(\base) 24 | lw sp, 4(\base) 25 | lw s0, 8(\base) 26 | lw s1, 12(\base) 27 | lw s2, 16(\base) 28 | lw s3, 20(\base) 29 | lw s4, 24(\base) 30 | lw s5, 28(\base) 31 | lw s6, 32(\base) 32 | lw s7, 36(\base) 33 | lw s8, 40(\base) 34 | lw s9, 44(\base) 35 | lw s10, 48(\base) 36 | lw s11, 52(\base) 37 | .endm 38 | 39 | .macro reg_save base 40 | # save the registers. 41 | sw ra, 0(\base) 42 | sw sp, 4(\base) 43 | sw gp, 8(\base) 44 | sw tp, 12(\base) 45 | sw t0, 16(\base) 46 | sw t1, 20(\base) 47 | sw t2, 24(\base) 48 | sw s0, 28(\base) 49 | sw s1, 32(\base) 50 | sw a0, 36(\base) 51 | sw a1, 40(\base) 52 | sw a2, 44(\base) 53 | sw a3, 48(\base) 54 | sw a4, 52(\base) 55 | sw a5, 56(\base) 56 | sw a6, 60(\base) 57 | sw a7, 64(\base) 58 | sw s2, 68(\base) 59 | sw s3, 72(\base) 60 | sw s4, 76(\base) 61 | sw s5, 80(\base) 62 | sw s6, 84(\base) 63 | sw s7, 88(\base) 64 | sw s8, 92(\base) 65 | sw s9, 96(\base) 66 | sw s10, 100(\base) 67 | sw s11, 104(\base) 68 | sw t3, 108(\base) 69 | sw t4, 112(\base) 70 | sw t5, 116(\base) 71 | sw t6, 120(\base) 72 | .endm 73 | 74 | .macro reg_load base 75 | # restore registers. 76 | lw ra, 0(\base) 77 | lw sp, 4(\base) 78 | lw gp, 8(\base) 79 | # not this, in case we moved CPUs: lw tp, 12(\base) 80 | lw t0, 16(\base) 81 | lw t1, 20(\base) 82 | lw t2, 24(\base) 83 | lw s0, 28(\base) 84 | lw s1, 32(\base) 85 | lw a0, 36(\base) 86 | lw a1, 40(\base) 87 | lw a2, 44(\base) 88 | lw a3, 48(\base) 89 | lw a4, 52(\base) 90 | lw a5, 56(\base) 91 | lw a6, 60(\base) 92 | lw a7, 64(\base) 93 | lw s2, 68(\base) 94 | lw s3, 72(\base) 95 | lw s4, 76(\base) 96 | lw s5, 80(\base) 97 | lw s6, 84(\base) 98 | lw s7, 88(\base) 99 | lw s8, 92(\base) 100 | lw s9, 96(\base) 101 | lw s10, 100(\base) 102 | lw s11, 104(\base) 103 | lw t3, 108(\base) 104 | lw t4, 112(\base) 105 | lw t5, 116(\base) 106 | lw t6, 120(\base) 107 | .endm 108 | # ============ Macro END ================== 109 | 110 | # Context switch 111 | # 112 | # void sys_switch(struct context *old, struct context *new); 113 | # 114 | # Save current registers in old. Load from new. 115 | 116 | .globl sys_switch 117 | .align 4 118 | sys_switch: 119 | 120 | ctx_save a0 # a0 => struct context *old 121 | ctx_load a1 # a1 => struct context *new 122 | ret # pc=ra; swtch to new task (new->ra) 123 | 124 | .globl atomic_swap 125 | .align 4 126 | atomic_swap: 127 | li a5, 1 128 | amoswap.w.aq a5, a5, 0(a0) 129 | mv a0, a5 130 | ret 131 | 132 | .globl trap_vector 133 | # the trap vector base address must always be aligned on a 4-byte boundary 134 | .align 4 135 | trap_vector: 136 | # save context(registers). 137 | csrrw t6, mscratch, t6 # swap t6 and mscratch 138 | reg_save t6 139 | csrw mscratch, t6 140 | # call the C trap handler in trap.c 141 | csrr a0, mepc 142 | csrr a1, mcause 143 | call trap_handler 144 | 145 | # trap_handler will return the return address via a0. 146 | csrw mepc, a0 147 | 148 | # load context(registers). 149 | csrr t6, mscratch 150 | reg_load t6 151 | mret 152 | 153 | -------------------------------------------------------------------------------- /09-MemoryAllocator/src/task.c: -------------------------------------------------------------------------------- 1 | #include "task.h" 2 | #include "lib.h" 3 | 4 | uint8_t task_stack[MAX_TASK][STACK_SIZE]; 5 | struct context ctx_os; 6 | struct context ctx_tasks[MAX_TASK]; 7 | struct context *ctx_now; 8 | int taskTop = 0; // total number of task 9 | 10 | // create a new task 11 | int task_create(void (*task)(void)) 12 | { 13 | int i = taskTop++; 14 | ctx_tasks[i].ra = (reg_t)task; 15 | ctx_tasks[i].sp = (reg_t)&task_stack[i][STACK_SIZE - 1]; 16 | return i; 17 | } 18 | 19 | // switch to task[i] 20 | void task_go(int i) 21 | { 22 | ctx_now = &ctx_tasks[i]; 23 | sys_switch(&ctx_os, &ctx_tasks[i]); 24 | } 25 | 26 | // switch back to os 27 | void task_os() 28 | { 29 | struct context *ctx = ctx_now; 30 | ctx_now = &ctx_os; 31 | sys_switch(ctx, &ctx_os); 32 | } 33 | -------------------------------------------------------------------------------- /09-MemoryAllocator/src/timer.c: -------------------------------------------------------------------------------- 1 | #include "timer.h" 2 | 3 | // a scratch area per CPU for machine-mode timer interrupts. 4 | reg_t timer_scratch[NCPU][5]; 5 | 6 | #define interval 20000000 // cycles; about 2 second in qemu. 7 | 8 | void timer_init() 9 | { 10 | // each CPU has a separate source of timer interrupts. 11 | int id = r_mhartid(); 12 | 13 | // ask the CLINT for a timer interrupt. 14 | // int interval = 1000000; // cycles; about 1/10th second in qemu. 15 | 16 | *(reg_t *)CLINT_MTIMECMP(id) = *(reg_t *)CLINT_MTIME + interval; 17 | 18 | // prepare information in scratch[] for timervec. 19 | // scratch[0..2] : space for timervec to save registers. 20 | // scratch[3] : address of CLINT MTIMECMP register. 21 | // scratch[4] : desired interval (in cycles) between timer interrupts. 22 | reg_t *scratch = &timer_scratch[id][0]; 23 | scratch[3] = CLINT_MTIMECMP(id); 24 | scratch[4] = interval; 25 | w_mscratch((reg_t)scratch); 26 | 27 | // enable machine-mode timer interrupts. 28 | w_mie(r_mie() | MIE_MTIE); 29 | } 30 | 31 | static int timer_count = 0; 32 | 33 | void timer_handler() 34 | { 35 | lib_printf("timer_handler: %d\n", ++timer_count); 36 | int id = r_mhartid(); 37 | *(reg_t *)CLINT_MTIMECMP(id) = *(reg_t *)CLINT_MTIME + interval; 38 | } 39 | -------------------------------------------------------------------------------- /09-MemoryAllocator/src/trap.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | extern void trap_vector(); 3 | extern void virtio_disk_isr(); 4 | void trap_init() 5 | { 6 | // set the machine-mode trap handler. 7 | w_mtvec((reg_t)trap_vector); 8 | } 9 | 10 | void external_handler() 11 | { 12 | int irq = plic_claim(); 13 | if (irq == UART0_IRQ) 14 | { 15 | lib_isr(); 16 | } 17 | else if (irq == VIRTIO_IRQ) 18 | { 19 | lib_puts("Virtio IRQ\n"); 20 | virtio_disk_isr(); 21 | } 22 | else if (irq) 23 | { 24 | lib_printf("unexpected interrupt irq = %d\n", irq); 25 | } 26 | 27 | if (irq) 28 | { 29 | plic_complete(irq); 30 | } 31 | } 32 | 33 | reg_t trap_handler(reg_t epc, reg_t cause) 34 | { 35 | reg_t return_pc = epc; 36 | reg_t cause_code = cause & 0xfff; 37 | 38 | if (cause & 0x80000000) 39 | { 40 | /* Asynchronous trap - interrupt */ 41 | switch (cause_code) 42 | { 43 | case 3: 44 | /* software interruption */ 45 | break; 46 | case 7: 47 | /* timer interruption */ 48 | // disable machine-mode timer interrupts. 49 | w_mie(r_mie() & ~(1 << 7)); 50 | timer_handler(); 51 | return_pc = (reg_t)&os_kernel; 52 | // enable machine-mode timer interrupts. 53 | w_mie(r_mie() | MIE_MTIE); 54 | break; 55 | case 11: 56 | /* external interruption */ 57 | external_handler(); 58 | break; 59 | default: 60 | lib_puts("unknown async exception!\n"); 61 | break; 62 | } 63 | } 64 | else 65 | { 66 | switch (cause_code) 67 | { 68 | case 2: 69 | lib_puts("Illegal instruction!\n"); 70 | break; 71 | case 5: 72 | lib_puts("Fault load!\n"); 73 | break; 74 | case 7: 75 | lib_puts("Fault store!\n"); 76 | break; 77 | case 11: 78 | lib_puts("Machine mode ecall!\n"); 79 | break; 80 | default: 81 | /* Synchronous trap - exception */ 82 | lib_printf("Sync exceptions! cause code: %d\n", cause_code); 83 | break; 84 | } 85 | for (;;) 86 | { 87 | /* code */ 88 | } 89 | } 90 | return return_pc; 91 | } 92 | -------------------------------------------------------------------------------- /09-MemoryAllocator/src/user.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | 3 | int shared_var = 500; 4 | 5 | lock_t lock; 6 | 7 | void user_task0(void) 8 | { 9 | lib_puts("Task0: Created!\n"); 10 | while (1) 11 | { 12 | lib_puts("Task0: Running...\n"); 13 | lib_delay(1000); 14 | } 15 | } 16 | 17 | void user_task1(void) 18 | { 19 | lib_puts("Task1: Created!\n"); 20 | while (1) 21 | { 22 | lib_puts("Task1: Running...\n"); 23 | lib_delay(1000); 24 | } 25 | } 26 | 27 | void user_task2(void) 28 | { 29 | lib_puts("Task2: Created!\n"); 30 | while (1) 31 | { 32 | for (int i = 0; i < 50; i++) 33 | { 34 | lock_acquire(&lock); 35 | shared_var++; 36 | lock_free(&lock); 37 | lib_delay(100); 38 | } 39 | lib_printf("The value of shared_var is: %d \n", shared_var); 40 | } 41 | } 42 | 43 | void user_task3(void) 44 | { 45 | lib_puts("Task3: Created!\n"); 46 | while (1) 47 | { 48 | lib_puts("Trying to get the lock... \n"); 49 | lock_acquire(&lock); 50 | lib_puts("Get the lock!\n"); 51 | lock_free(&lock); 52 | lib_delay(1000); 53 | } 54 | } 55 | 56 | void user_init() 57 | { 58 | task_create(&user_task0); 59 | task_create(&user_task1); 60 | } 61 | -------------------------------------------------------------------------------- /10-SystemCall/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccriscv/mini-riscv-os/70443b0ab3bf186c47b42127a0ffe31ad417fb91/10-SystemCall/.DS_Store -------------------------------------------------------------------------------- /10-SystemCall/Makefile: -------------------------------------------------------------------------------- 1 | CC = riscv64-unknown-elf-gcc 2 | CFLAGS = -I./include -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall -w -D CONFIG_SYSCALL 3 | GDB = riscv64-unknown-elf-gdb 4 | SOURCE = src/ 5 | 6 | OBJ = \ 7 | $(SOURCE)start.s \ 8 | $(SOURCE)sys.s \ 9 | $(SOURCE)mem.s \ 10 | $(SOURCE)lib.c \ 11 | $(SOURCE)timer.c \ 12 | $(SOURCE)os.c \ 13 | $(SOURCE)task.c \ 14 | $(SOURCE)user.c \ 15 | $(SOURCE)trap.c \ 16 | $(SOURCE)lock.c \ 17 | $(SOURCE)plic.c \ 18 | $(SOURCE)virtio.c \ 19 | $(SOURCE)string.c \ 20 | $(SOURCE)alloc.c \ 21 | $(SOURCE)syscall.c \ 22 | $(SOURCE)usys.s \ 23 | 24 | QEMU = qemu-system-riscv32 25 | QFLAGS = -nographic -smp 4 -machine virt -bios none 26 | QFLAGS += -drive if=none,format=raw,file=hdd.dsk,id=x0 27 | QFLAGS += -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 28 | 29 | OBJDUMP = riscv64-unknown-elf-objdump 30 | 31 | all: clean os.elf hdd.dsk qemu 32 | 33 | test: clean os.elf qemu 34 | 35 | os.elf: $(OBJ) 36 | $(CC) $(CFLAGS) -T os.ld -o os.elf $^ 37 | 38 | qemu: $(TARGET) hdd.dsk 39 | @qemu-system-riscv32 -M ? | grep virt >/dev/null || exit 40 | @echo "Press Ctrl-A and then X to exit QEMU" 41 | $(QEMU) $(QFLAGS) -kernel os.elf 42 | 43 | clean: 44 | rm -f *.elf *.img 45 | 46 | hdd.dsk: 47 | dd if=/dev/urandom of=hdd.dsk bs=1048576 count=32 48 | 49 | .PHONY : debug 50 | debug: clean os.elf hdd.dsk 51 | @echo "Press Ctrl-C and then input 'quit' to exit GDB and QEMU" 52 | @echo "-------------------------------------------------------" 53 | @${QEMU} ${QFLAGS} -kernel os.elf -s -S & 54 | @${GDB} os.elf -q -x ./gdbinit 55 | -------------------------------------------------------------------------------- /10-SystemCall/README.md: -------------------------------------------------------------------------------- 1 | # 10-SystemCall 2 | 3 | ## Build & Run 4 | 5 | ``` 6 | austin@AustindeMacBook-Air   ~/Desktop/riscv/austin362667/mini-riscv-os/10-SystemCall    master  INSERT  make all    3309  0.75G   1.52   10:34:23  7 | rm -f *.elf *.img 8 | riscv64-unknown-elf-gcc -I./include -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall -w -D CONFIG_SYSCALL -T os.ld -o os.elf src/start.s src/sys.s src/mem.s src/lib.c src/timer.c src/os.c src/task.c src/user.c src/trap.c src/lock.c src/plic.c src/virtio.c src/string.c src/alloc.c src/syscall.c src/usys.s 9 | Press Ctrl-A and then X to exit QEMU 10 | qemu-system-riscv32 -nographic -smp 4 -machine virt -bios none -drive if=none,format=raw,file=hdd.dsk,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 -kernel os.elf 11 | HEAP_START = 8001500c, HEAP_SIZE = 07feaff4, num of pages = 521903 12 | TEXT: 0x80000000 -> 0x8000af2c 13 | RODATA: 0x8000af2c -> 0x8000b490 14 | DATA: 0x8000c000 -> 0x8000c004 15 | BSS: 0x8000d000 -> 0x8001500c 16 | HEAP: 0x80095100 -> 0x88000000 17 | OS start 18 | Disk init work is success! 19 | buffer init... 20 | block read... 21 | Virtio IRQ 22 | 00000050 23 | 0000002f 24 | 00000009 25 | 00000039 26 | 0000009f 27 | 000000ce 28 | 00000037 29 | 0000003d 30 | 000000df 31 | 0000003a 32 | 33 | p = 0x80095100 34 | p2 = 0x80095500 35 | p3 = 0x80095700 36 | OS: Activate next task 37 | Task0: Created! 38 | Task0: Running... 39 | Task0: Running... 40 | Task0: Running... 41 | Task0: Running... 42 | Task0: Running... 43 | Task0: Running... 44 | Task0: Running... 45 | Task0: Running... 46 | Task0: Running... 47 | Task0: Running... 48 | Task0: Running... 49 | Task0: Running... 50 | Task0: Running... 51 | Task0: Running... 52 | Task0: Running... 53 | Task0: Running... 54 | Task0: Running... 55 | Task0: Running... 56 | timer_handler: 1 57 | OS: Back to OS 58 | 59 | OS: Activate next task 60 | Task4: Created! 61 | Environment call from M-mode! 62 | syscall_num: 1 63 | --> sys_gethid, arg0 = 0x8000dc7f 64 | ptr_hid != NULL 65 | system call returned!, hart id is 0 66 | Task4: Running... 67 | Task4: Running... 68 | Task4: Running... 69 | Task4: Running... 70 | Task4: Running... 71 | Task4: Running... 72 | Task4: Running... 73 | Task4: Running... 74 | Task4: Running... 75 | Task4: Running... 76 | Task4: Running... 77 | Task4: Running... 78 | Task4: Running... 79 | Task4: Running... 80 | Task4: Running... 81 | Task4: Running... 82 | Task4: Running... 83 | Task4: Running... 84 | Task4: Running... 85 | timer_handler: 2 86 | OS: Back to OS 87 | 88 | OS: Activate next task 89 | Task0: Running... 90 | Task0: Running... 91 | Task0: Running... 92 | Task0: Running... 93 | Task0: Running... 94 | Task0: Running... 95 | Task0: Running... 96 | Task0: Running... 97 | Task0: Running... 98 | QEMU: Terminated 99 | ``` 100 | -------------------------------------------------------------------------------- /10-SystemCall/gdbinit: -------------------------------------------------------------------------------- 1 | set disassemble-next-line on 2 | b _start 3 | target remote : 1234 4 | c -------------------------------------------------------------------------------- /10-SystemCall/include/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccriscv/mini-riscv-os/70443b0ab3bf186c47b42127a0ffe31ad417fb91/10-SystemCall/include/.DS_Store -------------------------------------------------------------------------------- /10-SystemCall/include/lib.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIB_H__ 2 | #define __LIB_H__ 3 | 4 | #include "riscv.h" 5 | #include 6 | #include 7 | 8 | #define lib_error(...) \ 9 | { \ 10 | lib_printf(__VA_ARGS__); \ 11 | while (1) \ 12 | { \ 13 | } \ 14 | } \ 15 | } 16 | 17 | extern char *lib_gets(char *); 18 | extern void uart_init(); 19 | extern void lib_isr(void); 20 | extern int lib_getc(void); 21 | extern void lib_delay(volatile int count); 22 | extern int lib_putc(char ch); 23 | extern void lib_puts(char *s); 24 | extern int lib_printf(const char *s, ...); 25 | extern int lib_vprintf(const char *s, va_list vl); 26 | extern int lib_vsnprintf(char *out, size_t n, const char *s, va_list vl); 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /10-SystemCall/include/os.h: -------------------------------------------------------------------------------- 1 | #ifndef __OS_H__ 2 | #define __OS_H__ 3 | 4 | #include "riscv.h" 5 | #include "lib.h" 6 | #include "task.h" 7 | #include "timer.h" 8 | #include "string.h" 9 | 10 | extern void panic(char *); 11 | extern void user_init(); 12 | extern void os_kernel(); 13 | extern int os_main(void); 14 | 15 | // PLIC 16 | extern void plic_init(); 17 | extern int plic_claim(); 18 | extern void plic_complete(int); 19 | 20 | // lock 21 | extern void basic_lock(); 22 | extern void basic_unlock(); 23 | 24 | typedef struct lock 25 | { 26 | volatile int locked; 27 | } lock_t; 28 | 29 | extern int atomic_swap(lock_t *); 30 | 31 | extern void lock_init(lock_t *lock); 32 | 33 | extern void lock_acquire(lock_t *lock); 34 | 35 | extern void lock_free(lock_t *lock); 36 | 37 | // memory allocator 38 | 39 | extern void *malloc(size_t size); 40 | extern void free(void *p); 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /10-SystemCall/include/string.h: -------------------------------------------------------------------------------- 1 | #ifndef __STRING_H__ 2 | #define __STRING_H__ 3 | 4 | void *memset(void *, int, unsigned int); 5 | void * 6 | memcpy(void *dst, const void *src, unsigned int n); 7 | void * 8 | memmove(void *dst, const void *src, unsigned int n); 9 | 10 | #endif -------------------------------------------------------------------------------- /10-SystemCall/include/sys.h: -------------------------------------------------------------------------------- 1 | #ifndef __SYS_H__ 2 | #define __SYS_H__ 3 | 4 | #include "riscv.h" 5 | extern void sys_timer(); 6 | extern void sys_switch(struct context *ctx_old, struct context *ctx_new); 7 | extern void switch_to(struct context *ctx_next); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /10-SystemCall/include/task.h: -------------------------------------------------------------------------------- 1 | #ifndef __TASK_H__ 2 | #define __TASK_H__ 3 | 4 | #include "riscv.h" 5 | #include "sys.h" 6 | 7 | #define MAX_TASK 10 8 | #define STACK_SIZE 1024 9 | 10 | extern int taskTop; 11 | 12 | extern int task_create(void (*task)(void)); 13 | extern void task_go(int i); 14 | extern void task_os(); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /10-SystemCall/include/timer.h: -------------------------------------------------------------------------------- 1 | #ifndef __TIMER_H__ 2 | #define __TIMER_H__ 3 | 4 | #include "riscv.h" 5 | #include "sys.h" 6 | #include "lib.h" 7 | #include "task.h" 8 | 9 | extern void timer_handler(); 10 | extern void timer_init(); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /10-SystemCall/include/types.h: -------------------------------------------------------------------------------- 1 | #ifndef __TYPES_H__ 2 | #define __TYPES_H__ 3 | 4 | typedef unsigned int uint; 5 | typedef unsigned short ushort; 6 | typedef unsigned char uchar; 7 | 8 | typedef unsigned char uint8; 9 | typedef unsigned short uint16; 10 | typedef unsigned int uint32; 11 | typedef unsigned long long uint64; 12 | 13 | #endif -------------------------------------------------------------------------------- /10-SystemCall/include/user_api.h: -------------------------------------------------------------------------------- 1 | #ifndef __USER_API_H__ 2 | #define __USER_API_H__ 3 | 4 | /* user mode syscall APIs */ 5 | extern int gethid(unsigned int *hid); 6 | 7 | #endif /* __USER_API_H__ */ 8 | -------------------------------------------------------------------------------- /10-SystemCall/os.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_ARCH( "riscv" ) 2 | 3 | ENTRY( _start ) 4 | 5 | MEMORY 6 | { 7 | ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M 8 | } 9 | 10 | PHDRS 11 | { 12 | text PT_LOAD; 13 | data PT_LOAD; 14 | bss PT_LOAD; 15 | } 16 | 17 | SECTIONS 18 | { 19 | .text : { 20 | PROVIDE(_text_start = .); 21 | *(.text.init) *(.text .text.*) 22 | PROVIDE(_text_end = .); 23 | } >ram AT>ram :text 24 | 25 | .rodata : { 26 | PROVIDE(_rodata_start = .); 27 | *(.rodata .rodata.*) 28 | PROVIDE(_rodata_end = .); 29 | } >ram AT>ram :text 30 | 31 | .data : { 32 | . = ALIGN(4096); 33 | PROVIDE(_data_start = .); 34 | *(.sdata .sdata.*) *(.data .data.*) 35 | PROVIDE(_data_end = .); 36 | } >ram AT>ram :data 37 | 38 | .bss :{ 39 | PROVIDE(_bss_start = .); 40 | *(.sbss .sbss.*) *(.bss .bss.*) 41 | PROVIDE(_bss_end = .); 42 | } >ram AT>ram :bss 43 | 44 | PROVIDE(_memory_start = ORIGIN(ram)); 45 | PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram)); 46 | 47 | PROVIDE(_heap_start = _bss_end); 48 | PROVIDE(_heap_size = _memory_end - _heap_start); 49 | } 50 | -------------------------------------------------------------------------------- /10-SystemCall/src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccriscv/mini-riscv-os/70443b0ab3bf186c47b42127a0ffe31ad417fb91/10-SystemCall/src/.DS_Store -------------------------------------------------------------------------------- /10-SystemCall/src/lock.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | 3 | void lock_init(lock_t *lock) 4 | { 5 | lock->locked = 0; 6 | } 7 | 8 | void lock_acquire(lock_t *lock) 9 | { 10 | for (;;) 11 | { 12 | if (!atomic_swap(lock)) 13 | { 14 | break; 15 | } 16 | } 17 | } 18 | 19 | void lock_free(lock_t *lock) 20 | { 21 | lock->locked = 0; 22 | } 23 | 24 | void basic_lock() 25 | { 26 | w_mstatus(r_mstatus() & ~MSTATUS_MIE); 27 | } 28 | 29 | void basic_unlock() 30 | { 31 | w_mstatus(r_mstatus() | MSTATUS_MIE); 32 | } 33 | -------------------------------------------------------------------------------- /10-SystemCall/src/mem.s: -------------------------------------------------------------------------------- 1 | .section .rodata 2 | .global HEAP_START 3 | HEAP_START: .word _heap_start 4 | 5 | .global HEAP_SIZE 6 | HEAP_SIZE: .word _heap_size 7 | 8 | .global TEXT_START 9 | TEXT_START: .word _text_start 10 | 11 | .global TEXT_END 12 | TEXT_END: .word _text_end 13 | 14 | .global DATA_START 15 | DATA_START: .word _data_start 16 | 17 | .global DATA_END 18 | DATA_END: .word _data_end 19 | 20 | .global RODATA_START 21 | RODATA_START: .word _rodata_start 22 | 23 | .global RODATA_END 24 | RODATA_END: .word _rodata_end 25 | 26 | .global BSS_START 27 | BSS_START: .word _bss_start 28 | 29 | .global BSS_END 30 | BSS_END: .word _bss_end -------------------------------------------------------------------------------- /10-SystemCall/src/os.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | #include "riscv.h" 3 | extern void page_init(void); 4 | extern void trap_init(void); 5 | 6 | void panic(char *s) 7 | { 8 | lib_puts(s); 9 | for (;;) 10 | { 11 | } 12 | } 13 | 14 | void os_kernel() 15 | { 16 | task_os(); 17 | } 18 | 19 | void disk_read() 20 | { 21 | virtio_tester(0); 22 | } 23 | 24 | void os_start() 25 | { 26 | uart_init(); 27 | page_init(); 28 | lib_puts("OS start\n"); 29 | user_init(); 30 | trap_init(); 31 | plic_init(); 32 | virtio_disk_init(); 33 | timer_init(); // start timer interrupt ... 34 | } 35 | 36 | int os_main(void) 37 | { 38 | os_start(); 39 | disk_read(); 40 | page_test(); 41 | int current_task = 0; 42 | 43 | while (1) 44 | { 45 | lib_puts("OS: Activate next task\n"); 46 | task_go(current_task); 47 | lib_puts("OS: Back to OS\n"); 48 | current_task = (current_task + 1) % taskTop; // Round Robin Scheduling 49 | lib_puts("\n"); 50 | } 51 | return 0; 52 | } 53 | -------------------------------------------------------------------------------- /10-SystemCall/src/plic.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | 3 | // ref: https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h 4 | // Intro: https://github.com/ianchen0119/AwesomeCS/wiki/2-5-RISC-V::%E4%B8%AD%E6%96%B7%E8%88%87%E7%95%B0%E5%B8%B8%E8%99%95%E7%90%86----PLIC-%E4%BB%8B%E7%B4%B9 5 | #define PLIC_BASE 0x0c000000L 6 | #define PLIC_PRIORITY(id) (PLIC_BASE + (id)*4) 7 | #define PLIC_PENDING(id) (PLIC_BASE + 0x1000 + ((id) / 32)) 8 | #define PLIC_MENABLE(hart) (PLIC_BASE + 0x2000 + (hart)*0x80) 9 | #define PLIC_MTHRESHOLD(hart) (PLIC_BASE + 0x200000 + (hart)*0x1000) 10 | #define PLIC_MCLAIM(hart) (PLIC_BASE + 0x200004 + (hart)*0x1000) 11 | #define PLIC_MCOMPLETE(hart) (PLIC_BASE + 0x200004 + (hart)*0x1000) 12 | 13 | void plic_init() 14 | { 15 | int hart = r_tp(); 16 | // QEMU Virt machine support 7 priority (1 - 7), 17 | // The "0" is reserved, and the lowest priority is "1". 18 | 19 | *(uint32_t *)PLIC_PRIORITY(UART0_IRQ) = 1; 20 | *(uint32_t *)PLIC_PRIORITY(VIRTIO_IRQ) = 1; 21 | 22 | /* Enable UART0 and VIRTIO */ 23 | 24 | *(uint32_t *)PLIC_MENABLE(hart) = (1 << UART0_IRQ) | (1 << VIRTIO_IRQ); 25 | 26 | /* Set priority threshold for UART0. */ 27 | 28 | *(uint32_t *)PLIC_MTHRESHOLD(hart) = 0; 29 | 30 | /* enable machine-mode external interrupts. */ 31 | w_mie(r_mie() | MIE_MEIE); 32 | 33 | // enable machine-mode interrupts. 34 | w_mstatus(r_mstatus() | MSTATUS_MIE); 35 | } 36 | 37 | int plic_claim() 38 | { 39 | int hart = r_tp(); 40 | int irq = *(uint32_t *)PLIC_MCLAIM(hart); 41 | return irq; 42 | } 43 | 44 | void plic_complete(int irq) 45 | { 46 | int hart = r_tp(); 47 | *(uint32_t *)PLIC_MCOMPLETE(hart) = irq; 48 | } -------------------------------------------------------------------------------- /10-SystemCall/src/start.s: -------------------------------------------------------------------------------- 1 | .equ STACK_SIZE, 8192 2 | 3 | .global _start 4 | 5 | _start: 6 | csrr a0, mhartid # 讀取核心代號 7 | bnez a0, park # 若不是 0 號核心,跳到 park 停止 8 | la sp, stacks + STACK_SIZE # 0 號核心設定堆疊 9 | j os_main # 0 號核心跳到主程式 os_main 10 | 11 | park: 12 | wfi 13 | j park 14 | 15 | stacks: 16 | .skip STACK_SIZE # 分配堆疊空間 17 | -------------------------------------------------------------------------------- /10-SystemCall/src/string.c: -------------------------------------------------------------------------------- 1 | #include "string.h" 2 | 3 | void *memset(void *dst, int c, unsigned int n) 4 | { 5 | char *cdst = (char *)dst; 6 | int i; 7 | for (i = 0; i < n; i++) 8 | { 9 | cdst[i] = c; 10 | } 11 | return dst; 12 | } 13 | 14 | void * 15 | memcpy(void *dst, const void *src, unsigned int n) 16 | { 17 | return memmove(dst, src, n); 18 | } 19 | 20 | void * 21 | memmove(void *dst, const void *src, unsigned int n) 22 | { 23 | const char *s; 24 | char *d; 25 | 26 | s = src; 27 | d = dst; 28 | if (s < d && s + n > d) 29 | { 30 | s += n; 31 | d += n; 32 | while (n-- > 0) 33 | *--d = *--s; 34 | } 35 | else 36 | while (n-- > 0) 37 | *d++ = *s++; 38 | 39 | return dst; 40 | } -------------------------------------------------------------------------------- /10-SystemCall/src/syscall.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | 3 | int sys_gethid(unsigned int *ptr_hid) 4 | { 5 | lib_printf("--> sys_gethid, arg0 = 0x%x\n", ptr_hid); 6 | if (ptr_hid == NULL) { 7 | lib_printf("ptr_hid == NULL\n"); 8 | return -1; 9 | } else { 10 | lib_printf("ptr_hid != NULL\n"); 11 | *ptr_hid = r_mhartid(); 12 | return 0; 13 | } 14 | } 15 | 16 | void do_syscall(struct context *ctx) 17 | { 18 | uint32_t syscall_num = ctx->a7; 19 | lib_printf("syscall_num: %d\n", syscall_num); 20 | switch (syscall_num) { 21 | case 1: 22 | ctx->a0 = sys_gethid((unsigned int *)(ctx->a0)); 23 | break; 24 | default: 25 | lib_printf("Unknown syscall no: %d\n", syscall_num); 26 | ctx->a0 = -1; 27 | } 28 | 29 | return; 30 | } 31 | -------------------------------------------------------------------------------- /10-SystemCall/src/task.c: -------------------------------------------------------------------------------- 1 | #include "task.h" 2 | #include "lib.h" 3 | 4 | uint8_t task_stack[MAX_TASK][STACK_SIZE]; 5 | struct context ctx_os; 6 | struct context ctx_tasks[MAX_TASK]; 7 | struct context *ctx_now; 8 | int taskTop = 0; // total number of task 9 | 10 | // create a new task 11 | int task_create(void (*task)(void)) 12 | { 13 | int i = taskTop++; 14 | ctx_tasks[i].ra = (reg_t)task; 15 | // ctx_tasks[i].pc = (reg_t)task; 16 | ctx_tasks[i].sp = (reg_t)&task_stack[i][STACK_SIZE - 1]; 17 | return i; 18 | } 19 | 20 | // switch to task[i] 21 | void task_go(int i) 22 | { 23 | ctx_now = &ctx_tasks[i]; 24 | // switch_to(ctx_now); 25 | sys_switch(&ctx_os, &ctx_tasks[i]); 26 | } 27 | 28 | // switch back to os 29 | void task_os() 30 | { 31 | struct context *ctx = ctx_now; 32 | ctx_now = &ctx_os; 33 | // switch_to(&ctx_os); 34 | sys_switch(ctx, &ctx_os); 35 | } 36 | -------------------------------------------------------------------------------- /10-SystemCall/src/timer.c: -------------------------------------------------------------------------------- 1 | #include "timer.h" 2 | 3 | // a scratch area per CPU for machine-mode timer interrupts. 4 | reg_t timer_scratch[NCPU][5]; 5 | 6 | #define interval 20000000 // cycles; about 2 second in qemu. 7 | 8 | void timer_init() 9 | { 10 | // each CPU has a separate source of timer interrupts. 11 | int id = r_mhartid(); 12 | 13 | // ask the CLINT for a timer interrupt. 14 | // int interval = 1000000; // cycles; about 1/10th second in qemu. 15 | 16 | *(reg_t *)CLINT_MTIMECMP(id) = *(reg_t *)CLINT_MTIME + interval; 17 | 18 | // prepare information in scratch[] for timervec. 19 | // scratch[0..2] : space for timervec to save registers. 20 | // scratch[3] : address of CLINT MTIMECMP register. 21 | // scratch[4] : desired interval (in cycles) between timer interrupts. 22 | reg_t *scratch = &timer_scratch[id][0]; 23 | scratch[3] = CLINT_MTIMECMP(id); 24 | scratch[4] = interval; 25 | w_mscratch((reg_t)scratch); 26 | 27 | // enable machine-mode timer interrupts. 28 | w_mie(r_mie() | MIE_MTIE); 29 | } 30 | 31 | static int timer_count = 0; 32 | 33 | void timer_handler() 34 | { 35 | lib_printf("timer_handler: %d\n", ++timer_count); 36 | int id = r_mhartid(); 37 | //lib_printf("hid: %d\n", id); 38 | *(reg_t *)CLINT_MTIMECMP(id) = *(reg_t *)CLINT_MTIME + interval; 39 | } 40 | -------------------------------------------------------------------------------- /10-SystemCall/src/trap.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | #include "riscv.h" 3 | extern void trap_vector(); 4 | extern void virtio_disk_isr(); 5 | extern void do_syscall(struct context *ctx); 6 | 7 | void trap_init() 8 | { 9 | // set the machine-mode trap handler. 10 | w_mtvec((reg_t)trap_vector); 11 | } 12 | 13 | void external_handler() 14 | { 15 | int irq = plic_claim(); 16 | if (irq == UART0_IRQ) 17 | { 18 | lib_isr(); 19 | } 20 | else if (irq == VIRTIO_IRQ) 21 | { 22 | lib_puts("Virtio IRQ\n"); 23 | virtio_disk_isr(); 24 | } 25 | else if (irq) 26 | { 27 | lib_printf("unexpected interrupt irq = %d\n", irq); 28 | } 29 | 30 | if (irq) 31 | { 32 | plic_complete(irq); 33 | } 34 | } 35 | 36 | reg_t trap_handler(reg_t epc, reg_t cause, struct context *ctx) 37 | { 38 | reg_t return_pc = epc; 39 | reg_t cause_code = cause & 0xfff; 40 | 41 | if (cause & 0x80000000) 42 | { 43 | /* Asynchronous trap - interrupt */ 44 | switch (cause_code) 45 | { 46 | case 3: 47 | /* software interruption */ 48 | break; 49 | case 7: 50 | /* timer interruption */ 51 | // disable machine-mode timer interrupts. 52 | w_mie(r_mie() & ~(1 << 7)); 53 | timer_handler(); 54 | return_pc = (reg_t)&os_kernel; 55 | // enable machine-mode timer interrupts. 56 | w_mie(r_mie() | MIE_MTIE); 57 | break; 58 | case 11: 59 | /* external interruption */ 60 | external_handler(); 61 | break; 62 | default: 63 | lib_puts("unknown async exception!\n"); 64 | break; 65 | } 66 | } 67 | else 68 | { 69 | switch (cause_code) 70 | { 71 | case 2: 72 | lib_puts("Illegal instruction!\n"); 73 | break; 74 | case 5: 75 | lib_puts("Fault load!\n"); 76 | break; 77 | case 7: 78 | lib_puts("Fault store!\n"); 79 | break; 80 | case 8: 81 | lib_puts("Environment call from U-mode!\n"); 82 | do_syscall(ctx); 83 | return_pc += 4; 84 | break; 85 | case 11: 86 | lib_puts("Environment call from M-mode!\n"); 87 | do_syscall(ctx); 88 | return_pc += 4; 89 | break; 90 | default: 91 | /* Synchronous trap - exception */ 92 | lib_printf("Sync exceptions! cause code: %d\n", cause_code); 93 | break; 94 | } 95 | // for (;;) 96 | // { 97 | /* code */ 98 | // } 99 | } 100 | return return_pc; 101 | } 102 | -------------------------------------------------------------------------------- /10-SystemCall/src/user.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | #include "user_api.h" 3 | 4 | int shared_var = 500; 5 | 6 | lock_t lock; 7 | 8 | void user_task0(void) 9 | { 10 | lib_puts("Task0: Created!\n"); 11 | while (1) 12 | { 13 | lib_puts("Task0: Running...\n"); 14 | lib_delay(1000); 15 | } 16 | } 17 | 18 | void user_task1(void) 19 | { 20 | lib_puts("Task1: Created!\n"); 21 | while (1) 22 | { 23 | lib_puts("Task1: Running...\n"); 24 | lib_delay(1000); 25 | } 26 | } 27 | 28 | void user_task2(void) 29 | { 30 | lib_puts("Task2: Created!\n"); 31 | while (1) 32 | { 33 | for (int i = 0; i < 50; i++) 34 | { 35 | lock_acquire(&lock); 36 | shared_var++; 37 | lock_free(&lock); 38 | lib_delay(100); 39 | } 40 | lib_printf("The value of shared_var is: %d \n", shared_var); 41 | } 42 | } 43 | 44 | void user_task3(void) 45 | { 46 | lib_puts("Task3: Created!\n"); 47 | while (1) 48 | { 49 | lib_puts("Trying to get the lock... \n"); 50 | lock_acquire(&lock); 51 | lib_puts("Get the lock!\n"); 52 | lock_free(&lock); 53 | lib_delay(1000); 54 | } 55 | } 56 | 57 | void user_task4(void) 58 | { 59 | lib_puts("Task4: Created!\n"); 60 | unsigned int hid = -1; 61 | 62 | /* 63 | * if syscall is supported, this will trigger exception, 64 | * code = 2 (Illegal instruction) 65 | */ 66 | // hid = r_mhartid(); 67 | // lib_printf("hart id is %d\n", hid); 68 | 69 | // perform system call from the user mode 70 | int ret = -1; 71 | ret = gethid(&hid); 72 | // ret = gethid(NULL); 73 | if (!ret) { 74 | lib_printf("system call returned!, hart id is %d\n", hid); 75 | } else { 76 | lib_printf("gethid() failed, return: %d\n", ret); 77 | } 78 | 79 | while (1) 80 | { 81 | lib_puts("Task4: Running...\n"); 82 | lib_delay(1000); 83 | } 84 | } 85 | 86 | void user_init() 87 | { 88 | task_create(&user_task0); 89 | task_create(&user_task4); 90 | // task_create(&user_task1); 91 | } 92 | -------------------------------------------------------------------------------- /10-SystemCall/src/usys.s: -------------------------------------------------------------------------------- 1 | .global gethid 2 | gethid: 3 | li a7, 1 4 | ecall 5 | ret 6 | 7 | -------------------------------------------------------------------------------- /A1-Input/Makefile: -------------------------------------------------------------------------------- 1 | CC = riscv64-unknown-elf-gcc 2 | CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 3 | 4 | QEMU = qemu-system-riscv32 5 | QFLAGS = -nographic -smp 4 -machine virt -bios none 6 | 7 | OBJDUMP = riscv64-unknown-elf-objdump 8 | 9 | all: os.elf 10 | 11 | os.elf: start.s os.c 12 | $(CC) $(CFLAGS) -T os.ld -o os.elf $^ 13 | 14 | qemu: $(TARGET) 15 | @qemu-system-riscv32 -M ? | grep virt >/dev/null || exit 16 | @echo "Press Ctrl-A and then X to exit QEMU" 17 | $(QEMU) $(QFLAGS) -kernel os.elf 18 | 19 | clean: 20 | rm -f *.elf 21 | -------------------------------------------------------------------------------- /A1-Input/README.md: -------------------------------------------------------------------------------- 1 | # A1-Input 2 | 3 | ## Build & Run 4 | 5 | ```sh 6 | $ make clean 7 | rm -f *.elf 8 | $ make 9 | riscv64-unknown-elf-gcc -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -T os.ld -o os.elf start.s os.c 10 | $ make qemu 11 | Press Ctrl-A and then X to exit QEMU 12 | qemu-system-riscv32 -nographic -smp 4 -machine virt -bios none -kernel os.elf 13 | type something:hello 14 | you type:hello 15 | QEMU: Terminated 16 | ``` 17 | -------------------------------------------------------------------------------- /A1-Input/linux.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ``` 4 | guest@localhost:~/sp/10-riscv/03-mini-riscv-os/01-HelloOs$ ls 5 | 01-HelloOs--嵌入式RISC-V輸出入程式.md Makefile os.c os.ld run.md start.s 6 | guest@localhost:~/sp/10-riscv/03-mini-riscv-os/01-HelloOs$ make 7 | riscv64-unknown-elf-gcc -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -T os.ld -o os.elf start.s os.c 8 | guest@localhost:~/sp/10-riscv/03-mini-riscv-os/01-HelloOs$ make qemu 9 | Press Ctrl-A and then X to exit QEMU 10 | qemu-system-riscv32 -nographic -smp 4 -machine virt -bios none -kernel os.elf 11 | Hello OS! 12 | QEMU: Terminated 13 | ``` -------------------------------------------------------------------------------- /A1-Input/os.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define UART 0x10000000 // RISC-V 的 virt 虛擬機,其 UART 映射區起始位址為 0x10000000 4 | #define UART_THR (uint8_t*)(UART+0x00) // THR:transmitter holding register 傳送暫存器 5 | #define UART_RHR (uint8_t*)(UART+0x00) // THR:transmitter holding register 傳送暫存器 6 | #define UART_LSR (uint8_t*)(UART+0x05) // LSR:line status register 輸出狀態暫存器 7 | #define UART_LSR_RX_READY 0x01 // input is waiting to be read from RHR 8 | #define UART_LSR_EMPTY_MASK 0x40 // LSR Bit 6: 當 LSR 的第六位元為 1 時,代表傳送區為空的,可以傳了 (Transmitter empty; both the THR and LSR are empty) 9 | 10 | void lib_putc(char ch) { // 透過 UART 傳送字元 ch 給宿主機印出 11 | while ((*UART_LSR & UART_LSR_EMPTY_MASK) == 0); // 一直等到 UART LSR 傳送區為空,可以傳送了 12 | *UART_THR = ch; // 將字元 ch 發送出去 13 | } 14 | 15 | void lib_puts(char *s) { // 印出字串到宿主機。 16 | while (*s) lib_putc(*s++); 17 | } 18 | 19 | char lib_getc() { 20 | while ((*UART_LSR & UART_LSR_RX_READY) == 0); // 一直等到 UART RX_READY 有收到資料 21 | return *UART_RHR; // 傳回收到的字元 22 | } 23 | 24 | int lib_gets(char *s) { 25 | char *p = s; 26 | while (1) { 27 | char ch = lib_getc(); 28 | lib_putc(ch); 29 | if (ch == '\n' || ch == '\r') break; 30 | *p++ = ch; 31 | } 32 | *p = '\0'; 33 | return p-s; 34 | } 35 | 36 | int os_main(void) 37 | { 38 | lib_puts("type something:"); 39 | char str[100]; 40 | lib_gets(str); 41 | lib_puts("\r\nyou type:"); 42 | lib_puts(str); 43 | lib_puts("\r\n"); 44 | while (1) {} // 讓主程式卡在這裡,用無窮迴圈停止! 45 | return 0; 46 | } 47 | -------------------------------------------------------------------------------- /A1-Input/os.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_ARCH( "riscv" ) /* 處理器架構為 RISCV */ 2 | 3 | ENTRY( _start ) /* 進入點從 _start 開始執行 */ 4 | 5 | MEMORY 6 | { 7 | ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M /* RAM 從 0x80000000 開始,共 128M BYTES */ 8 | } 9 | 10 | PHDRS 11 | { 12 | text PT_LOAD; /* text 段要載入*/ /* PT_LOAD (1) Indicates that this program header describes a segment to be loaded from the file. 參考 -- https://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_node/ld_23.html */ 13 | data PT_LOAD; /* data 段要載入*/ 14 | bss PT_LOAD; /* bss 段要載入*/ 15 | } 16 | 17 | SECTIONS 18 | { 19 | .text : { 20 | PROVIDE(_text_start = .); /* 設定 _text_start 為 .text 段開頭位址。 */ 21 | *(.text.init) *(.text .text.*) /* 將所有程式碼放在這段。 */ 22 | PROVIDE(_text_end = .); /* 設定 _text_end 為 .text 段開頭位址。 */ 23 | } >ram AT>ram :text 24 | 25 | .rodata : { /* 唯讀資料段 */ 26 | PROVIDE(_rodata_start = .); 27 | *(.rodata .rodata.*) 28 | PROVIDE(_rodata_end = .); 29 | } >ram AT>ram :text 30 | 31 | .data : { /* 資料段 */ 32 | . = ALIGN(4096); 33 | PROVIDE(_data_start = .); 34 | *(.sdata .sdata.*) *(.data .data.*) 35 | PROVIDE(_data_end = .); 36 | } >ram AT>ram :data 37 | 38 | .bss :{ /* 未初始化資料段 */ 39 | PROVIDE(_bss_start = .); 40 | *(.sbss .sbss.*) *(.bss .bss.*) 41 | PROVIDE(_bss_end = .); 42 | } >ram AT>ram :bss 43 | 44 | PROVIDE(_memory_start = ORIGIN(ram)); 45 | PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram)); 46 | } 47 | -------------------------------------------------------------------------------- /A1-Input/start.s: -------------------------------------------------------------------------------- 1 | .equ STACK_SIZE, 8192 2 | 3 | .global _start 4 | 5 | _start: 6 | csrr a0, mhartid # 讀取核心代號 7 | bnez a0, park # 若不是 0 號核心,跳到 park 停止 8 | la sp, stacks + STACK_SIZE # 0 號核心設定堆疊 9 | j os_main # 0 號核心跳到主程式 os_main 10 | 11 | park: 12 | wfi 13 | j park 14 | 15 | stacks: 16 | .skip STACK_SIZE # 分配堆疊空間 17 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | mini-riscv-os is written by: 2 | Chung-Chen Chen 3 | 4 | Other contributors: 5 | Ian Chen 6 | 7 | Copyrighted by: 8 | National Quemoy University, Taiwan 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | mini-riscv-os is freely redistributable under the two-clause BSD License: 2 | 3 | Copyright (C) 2015-2018 National Quemoy University, Taiwan. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions 8 | are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright notice, 11 | this list of conditions and the following disclaimer. 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE 20 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 26 | THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [中文版說明文件](doc/tw) 2 | 3 | # mini-riscv-os 4 | 5 | 6 | 7 | Build a minimal multi-tasking OS kernel for RISC-V from scratch 8 | 9 | Mini-riscv-os was inspired by [jserv](https://github.com/jserv)'s [mini-arm-os](https://github.com/jserv/mini-arm-os) project. 10 | 11 | However, [ccckmit](https://github.com/ccckmit) rewrite the project for RISC-V, and run on Win10 instead of Linux. 12 | 13 | ## Build & Run on Windows 10 14 | 15 | - [git-bash](https://git-scm.com/download/win) 16 | - [FreedomStudio](https://www.sifive.com/software) 17 | 18 | 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. 19 | 20 | ``` 21 | D:\install\FreedomStudio-2020-06-3-win64\SiFive\riscv64-unknown-elf-gcc-8.3.0-2020.04.1\bin 22 | 23 | D:\install\FreedomStudio-2020-06-3-win64\SiFive\riscv-qemu-4.2.0-2020.04.0\bin 24 | ``` 25 | 26 | And you should start your git-bash to build the project. (It works for me in vscode bash terminal) 27 | 28 | ## Steps 29 | 30 | - [01-HelloOs](01-HelloOs) 31 | - Enable UART to print trivial greetings 32 | - [02-ContextSwitch](02-ContextSwitch) 33 | - Basic switch from OS to user task 34 | - [03-MultiTasking](03-MultiTasking) 35 | - Two user tasks are interactively switching 36 | - [04-TimerInterrupt](04-TimerInterrupt) 37 | - Enable SysTick for future scheduler implementation 38 | - [05-Preemptive](05-Preemptive) 39 | - Basic preemptive scheduling 40 | - [06-Spinlock](06-Spinlock) 41 | - Lock implementation to protect critical sections 42 | - [07-ExterInterrupt](07-ExterInterrupt) 43 | - Learning PLIC & external interruption 44 | - [08-BlockDeviceDriver](08-BlockDeviceDriver) 45 | - Learning VirtIO Protocol & Device driver implementation 46 | - [09-MemoryAllocator](09-MemoryAllocator) 47 | - Understanding how to write the linker script & how the heap works 48 | - [10-SystemCall](10-SystemCall) 49 | - Invoking a mini ecall from machine mode. 50 | ## Building and Verification 51 | 52 | - Changes the current working directory to the specified one and then 53 | 54 | ``` 55 | make 56 | make qemu 57 | ``` 58 | 59 | ## Licensing 60 | 61 | `mini-riscv-os` is freely redistributable under the two-clause BSD License. 62 | Use of this source code is governed by a BSD-style license that can be found 63 | in the `LICENSE` file. 64 | 65 | ## Reference 66 | 67 | - [Adventures in RISC-V](https://matrix89.github.io/writes/writes/experiments-in-riscv/) 68 | - [Xv6, a simple Unix-like teaching operating system](https://pdos.csail.mit.edu/6.828/2020/xv6.html) 69 | - [Basics of programming a UART](https://www.activexperts.com/serial-port-component/tutorials/uart/) 70 | - [QEMU RISC-V Virt Machine Platform](https://github.com/riscv/opensbi/blob/master/docs/platform/qemu_virt.md) 71 | -------------------------------------------------------------------------------- /bug.md: -------------------------------------------------------------------------------- 1 | 所有 start.s 中的 .equ STACK_SIZE, 8192 應該都要改為 .equ STACK_SIZE, 1024 2 | 3 | 因為 後面的 slli t0, t0, 10 # shift left the hart id by 1024 4 | 5 | 這兩個要一致,所以不是把 STACK_SIZE 改為 8192, 就是把 slli t0, t0, 10 的 10 改為 13 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /doc/ref/InterruptHandler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccriscv/mini-riscv-os/70443b0ab3bf186c47b42127a0ffe31ad417fb91/doc/ref/InterruptHandler.png -------------------------------------------------------------------------------- /doc/ref/Threads.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 像是 07-thread 裏使用了 tcb_t 5 | 6 | ```cpp 7 | /* Thread Control Block */ 8 | typedef struct { 9 | void *stack; 10 | void *orig_stack; 11 | uint8_t in_use; 12 | } tcb_t; 13 | 14 | static tcb_t tasks[MAX_TASKS]; 15 | static int lastTask; 16 | 17 | ``` 18 | 19 | 還有用了一大堆組合語言 20 | 21 | ```cpp 22 | void __attribute__((naked)) thread_start() 23 | { 24 | lastTask = 0; 25 | 26 | /* Reset APSR before context switch. 27 | * Make sure we have a _clean_ PSR for the task. 28 | */ 29 | asm volatile("mov r0, #0\n" 30 | "msr APSR_nzcvq, r0\n"); 31 | /* To bridge the variable in C and the register in ASM, 32 | * move the task's stack pointer address into r0. 33 | * http://www.ethernut.de/en/documents/arm-inline-asm.html 34 | */ 35 | asm volatile("mov r0, %0\n" : : "r" (tasks[lastTask].stack)); 36 | asm volatile("msr psp, r0\n" 37 | "mov r0, #3\n" 38 | "msr control, r0\n" 39 | "isb\n"); 40 | /* This is how we simulate stack handling that pendsv_handler 41 | * does. Thread_create sets 17 entries in stack, and the 9 42 | * entries we pop here will be pushed back in pendsv_handler 43 | * in the same order. 44 | * 45 | * 46 | * pop {r4-r11, lr} 47 | * ldr r0, [sp] 48 | * stack 49 | * offset ------- 50 | * | 16 | <- Reset value of PSR 51 | * ------- 52 | * | 15 | <- Task entry 53 | * ------- 54 | * | 14 | <- LR for task 55 | * ------- 56 | * | ... | register 57 | * ------- ------- 58 | * | 9 | <- Task argument ----> | r0 | 59 | * psp after pop--< ------- 60 | * | 8 | <- EXC_RETURN ----> | lr | 61 | * ------- ------- 62 | * | 7 | | r11 | 63 | * ------- ------- 64 | * | ... | | ... | 65 | * ------- ------- 66 | * | 0 | | r4 | 67 | * psp -> ------- ------- 68 | * 69 | * Instead of "pop {r0}", use "ldr r0, [sp]" to ensure consistent 70 | * with the way how PendSV saves _old_ context[1]. 71 | */ 72 | asm volatile("pop {r4-r11, lr}\n" 73 | "ldr r0, [sp]\n"); 74 | /* Okay, we are ready to run first task, get address from 75 | * stack[15]. We just pop 9 register so #24 comes from 76 | * (15 - 9) * sizeof(entry of sp) = 6 * 4. 77 | */ 78 | asm volatile("ldr pc, [sp, #24]\n"); 79 | 80 | /* Never reach here */ 81 | while(1); 82 | } 83 | ``` -------------------------------------------------------------------------------- /doc/ref/Uart.md: -------------------------------------------------------------------------------- 1 | # Uart 2 | 3 | * https://www.activexperts.com/serial-port-component/tutorials/uart/ 4 | * https://www.maxlinear.com/Files/Documents/Intro_To_UARTs.pdf 5 | * https://twilco.github.io/riscv-from-scratch/2019/07/08/riscv-from-scratch-3.html 6 | -------------------------------------------------------------------------------- /doc/ref/freeRtosRef.md: -------------------------------------------------------------------------------- 1 | # FreeRTOS 2 | 3 | * https://github.com/illustris/FreeRTOS-RISCV/blob/master/Source/portable/GCC/RISCV/port.c 4 | 5 | ```cpp 6 | /*-----------------------------------------------------------*/ 7 | 8 | void vPortSysTickHandler( void ) 9 | { 10 | prvSetNextTimerInterrupt(); 11 | 12 | /* Increment the RTOS tick. */ 13 | if( xTaskIncrementTick() != pdFALSE ) 14 | { 15 | vTaskSwitchContext(); 16 | } 17 | } 18 | /*-----------------------------------------------------------*/ 19 | ``` 20 | 21 | * https://github.com/illustris/FreeRTOS-RISCV/blob/master/Source/portable/GCC/RISCV/portasm.S 22 | 23 | ```s 24 | vPortYield: 25 | /* 26 | * This routine can be called from outside of interrupt handler. This means 27 | * interrupts may be enabled at this point. This is probably okay for registers and 28 | * stack. However, "mepc" will be overwritten by the interrupt handler if a timer 29 | * interrupt happens during the yield. To avoid this, prevent interrupts before starting. 30 | * The write to mstatus in the restore context routine will enable interrupts after the 31 | * mret. A more fine-grain lock may be possible. 32 | */ 33 | csrci mstatus, 8 34 | 35 | portSAVE_CONTEXT 36 | portSAVE_RA 37 | jal vTaskSwitchContext 38 | portRESTORE_CONTEXT 39 | ``` 40 | 41 | * https://github.com/illustris/FreeRTOS-RISCV/blob/master/Source/tasks.c 42 | 43 | ```cpp 44 | void vTaskSwitchContext( void ) 45 | { 46 | if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE ) 47 | { 48 | /* The scheduler is currently suspended - do not allow a context 49 | switch. */ 50 | xYieldPending = pdTRUE; 51 | } 52 | else 53 | { 54 | xYieldPending = pdFALSE; 55 | traceTASK_SWITCHED_OUT(); 56 | 57 | #if ( configGENERATE_RUN_TIME_STATS == 1 ) 58 | { 59 | #ifdef portALT_GET_RUN_TIME_COUNTER_VALUE 60 | portALT_GET_RUN_TIME_COUNTER_VALUE( ulTotalRunTime ); 61 | #else 62 | ulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE(); 63 | #endif 64 | 65 | /* Add the amount of time the task has been running to the 66 | accumulated time so far. The time the task started running was 67 | stored in ulTaskSwitchedInTime. Note that there is no overflow 68 | protection here so count values are only valid until the timer 69 | overflows. The guard against negative values is to protect 70 | against suspect run time stat counter implementations - which 71 | are provided by the application, not the kernel. */ 72 | if( ulTotalRunTime > ulTaskSwitchedInTime ) 73 | { 74 | pxCurrentTCB->ulRunTimeCounter += ( ulTotalRunTime - ulTaskSwitchedInTime ); 75 | } 76 | else 77 | { 78 | mtCOVERAGE_TEST_MARKER(); 79 | } 80 | ulTaskSwitchedInTime = ulTotalRunTime; 81 | } 82 | #endif /* configGENERATE_RUN_TIME_STATS */ 83 | 84 | /* Check for stack overflow, if configured. */ 85 | taskCHECK_FOR_STACK_OVERFLOW(); 86 | 87 | /* Select a new task to run using either the generic C or port 88 | optimised asm code. */ 89 | taskSELECT_HIGHEST_PRIORITY_TASK(); 90 | traceTASK_SWITCHED_IN(); 91 | 92 | #if ( configUSE_NEWLIB_REENTRANT == 1 ) 93 | { 94 | /* Switch Newlib's _impure_ptr variable to point to the _reent 95 | structure specific to this task. */ 96 | _impure_ptr = &( pxCurrentTCB->xNewLib_reent ); 97 | } 98 | #endif /* configUSE_NEWLIB_REENTRANT */ 99 | } 100 | } 101 | ``` 102 | -------------------------------------------------------------------------------- /doc/ref/seL4.md: -------------------------------------------------------------------------------- 1 | # seL4 2 | 3 | * https://github.com/seL4/seL4/blob/master/src/arch/riscv/traps.S 4 | -------------------------------------------------------------------------------- /doc/ref/xv6ref.md: -------------------------------------------------------------------------------- 1 | # xv6 參考 2 | 3 | 当 xv6 内核在 CPU 上执行时,可能会发生两种类型的陷阱:异常和设备中断。上一节概述了 CPU 对此类陷阱的响应。 4 | 5 | 当内核执行时,它指向汇编代码 kernelvec (kernel/kernelve.S:10)。由于xv6已经在内核中,因此kernelvec可以依赖于将`satp`设置为内核页表,并依赖于引用有效内核堆栈的堆栈指针。kernelvec保存所有寄存器,这样我们最终可以恢复中断的代码,而不会干扰它。 6 | 7 | 8 | *** 9 | kernelvec将寄存器保存在中断的内核线程的堆栈上,这是有意义的,因为寄存器值属于该线程。如果陷阱导致切换到不同的线程,这一点尤其重要 - 在这种情况下,陷阱实际上会返回到新线程的堆栈上,将被中断线程的已保存寄存器安全地留在其堆栈上。 10 | *** 11 | 12 | kernelvec在保存寄存器后跳转到kerneltrap(kernel/trap.c:134)。kerneltrap为两种类型的陷阱做好准备:设备中断和异常。它调用sdevintr(kernel/trap.c:177)来检查和处理前者。如果陷阱不是设备中断,那么它就是一个异常,如果它发生在内核中,那总是一个致命的错误。 13 | 14 | ## supervisor mode & mret 15 | 16 | File: start.c 17 | 18 | ```cpp 19 | // entry.S jumps here in machine mode on stack0. 20 | void 21 | start() 22 | { 23 | // set M Previous Privilege mode to Supervisor, for mret. 24 | unsigned long x = r_mstatus(); 25 | x &= ~MSTATUS_MPP_MASK; 26 | x |= MSTATUS_MPP_S; 27 | w_mstatus(x); 28 | 29 | // set M Exception Program Counter to main, for mret. 30 | // requires gcc -mcmodel=medany 31 | w_mepc((uint64)main); 32 | 33 | // disable paging for now. 34 | w_satp(0); 35 | 36 | // delegate all interrupts and exceptions to supervisor mode. 37 | w_medeleg(0xffff); 38 | w_mideleg(0xffff); 39 | 40 | // ask for clock interrupts. 41 | timerinit(); 42 | 43 | // keep each CPU's hartid in its tp register, for cpuid(). 44 | int id = r_mhartid(); 45 | w_tp(id); 46 | 47 | // switch to supervisor mode and jump to main(). 48 | asm volatile("mret"); 49 | } 50 | 51 | ``` 52 | 53 | File: main.c 54 | 55 | ```cpp 56 | #include "types.h" 57 | #include "param.h" 58 | #include "memlayout.h" 59 | #include "riscv.h" 60 | #include "defs.h" 61 | 62 | volatile static int started = 0; 63 | 64 | // start() jumps here in supervisor mode on all CPUs. 65 | void 66 | main() 67 | { 68 | if(cpuid() == 0){ 69 | consoleinit(); 70 | printfinit(); 71 | printf("\n"); 72 | printf("xv6 kernel is booting\n"); 73 | printf("\n"); 74 | kinit(); // physical page allocator 75 | kvminit(); // create kernel page table 76 | kvminithart(); // turn on paging 77 | procinit(); // process table 78 | trapinit(); // trap vectors 79 | trapinithart(); // install kernel trap vector 80 | plicinit(); // set up interrupt controller 81 | plicinithart(); // ask PLIC for device interrupts 82 | binit(); // buffer cache 83 | iinit(); // inode cache 84 | fileinit(); // file table 85 | virtio_disk_init(); // emulated hard disk 86 | userinit(); // first user process 87 | __sync_synchronize(); 88 | started = 1; 89 | } else { 90 | while(started == 0) 91 | ; 92 | __sync_synchronize(); 93 | printf("hart %d starting\n", cpuid()); 94 | kvminithart(); // turn on paging 95 | trapinithart(); // install kernel trap vector 96 | plicinithart(); // ask PLIC for device interrupts 97 | } 98 | 99 | scheduler(); 100 | } 101 | ``` -------------------------------------------------------------------------------- /doc/tw/06-Spinlock.md: -------------------------------------------------------------------------------- 1 | # 06-Spinlock -- RISC-V 的嵌入式作業系統 2 | 3 | ## Spinlock 介紹 4 | 5 | Spinlock 中文稱做自旋鎖,透過名稱我們就能大概大概猜到 Spinlock 的功用。與 Mutex 相同, Spinlock 可以用來保護 Critical section ,如果執行緒沒有獲取鎖,則會進入迴圈直到獲得上鎖的資格,因此叫做自旋鎖。 6 | 7 | ### 原子操作 8 | 9 | 原子操作可以確保一個操作在完成前不會被其他操作打斷,以 RISC-V 為例,它提供了 RV32A Instruction set ,該指令集都是原子操作 (Atomic)。 10 | 11 | ![](https://img2018.cnblogs.com/blog/361409/201810/361409-20181029191919995-84497985.png) 12 | 13 | 為了避免在同時間有多個 Spinlock 對相同的記憶體做存取,在 Spinlock 實現上會使用到原子操作以確保正確上鎖。 14 | 15 | > 其實不單單是 Spinlock ,互斥鎖在實現上同樣需要 Atomic operation 。 16 | 17 | ### 用 C 語言打造簡單的 Spinlock 18 | 19 | 考慮以下程式碼: 20 | 21 | ```cpp 22 | typedef struct spinlock{ 23 | volatile uint lock; 24 | } spinlock_t; 25 | void lock(spinlock_t *lock){ 26 | while(xchg(lock−>lock, 1) != 0); 27 | } 28 | void unlock(spinlock_t *lock){ 29 | lock->lock = 0; 30 | } 31 | ``` 32 | 33 | 透過範例程式碼,可以注意到幾點: 34 | 35 | - lock 的 `volatile` 關鍵字 36 | 使用 `volatile` 關鍵字會讓編譯器知道該變數可能會在不可預期的情況下被存取,所以不要將該變數的指令做優化以避免將結果存在 Register 中,而是直接寫進記憶體。 37 | - lock 函式 38 | [`xchg(a,b)`](https://zh.m.wikibooks.org/zh-hant/X86%E7%B5%84%E5%90%88%E8%AA%9E%E8%A8%80/%E5%9F%BA%E6%9C%AC%E6%8C%87%E4%BB%A4%E9%9B%86/IA32%E6%8C%87%E4%BB%A4:xchg) 可以將 a, b 兩個變數的內容對調,並且該函式為原子操作,當 lock 值不為 0 時,執行緒便會不停的自旋等待,直到 lock 為 0 (也就是可供上鎖)為止。 39 | - unlock 函式 40 | 由於同時間只會有一個執行緒獲得鎖,所以在解鎖時不怕會有搶占存取的問題。也因為這樣,範例就沒有使用原子操作了。 41 | 42 | ## mini-riscv-os 中的自旋鎖 43 | 44 | ### basic lock 45 | 46 | 首先,由於 mini-riscv-os 是屬於 Single hart 的作業系統,除了使用原子操作以外,其實還有一個非常簡單的作法可以做到 Lock 的效果: 47 | 48 | ```cpp 49 | void basic_lock() 50 | { 51 | w_mstatus(r_mstatus() & ~MSTATUS_MIE); 52 | } 53 | 54 | void basic_unlock() 55 | { 56 | w_mstatus(r_mstatus() | MSTATUS_MIE); 57 | } 58 | ``` 59 | 60 | 在 [lock.c] 中,我們實作了非常簡單的鎖,當我們在程式中呼叫 `basic_lock()` 時,系統的 machine mode 中斷機制會被關閉,如此一來,我們就可以確保不會有其他程式存取到 Shared memory ,避免了 Race condition 的發生。 61 | 62 | ### spinlock 63 | 64 | 上面的 lock 有一個明顯的缺陷: **當獲取鎖的程式一直沒有釋放鎖,整個系統都會被 Block** ,為了確保作業系統還是能維持多工的機制,我們勢必要實作更複雜的鎖: 65 | 66 | - [os.h] 67 | - [lock.c] 68 | - [sys.s] 69 | 70 | ```cpp 71 | typedef struct lock 72 | { 73 | volatile int locked; 74 | } lock_t; 75 | 76 | void lock_init(lock_t *lock) 77 | { 78 | lock->locked = 0; 79 | } 80 | 81 | void lock_acquire(lock_t *lock) 82 | { 83 | for (;;) 84 | { 85 | if (!atomic_swap(lock)) 86 | { 87 | break; 88 | } 89 | } 90 | } 91 | 92 | void lock_free(lock_t *lock) 93 | { 94 | lock->locked = 0; 95 | } 96 | ``` 97 | 98 | 其實上面的程式碼與前面介紹 Spinlock 的範例基本上一致,我們在系統中實作時只需要處理一個比較麻煩的問題,也就是實現原子性的交換動作 `atomic_swap()`: 99 | 100 | ```assembly= 101 | .globl atomic_swap 102 | .align 4 103 | atomic_swap: 104 | li a5, 1 105 | amoswap.w.aq a5, a5, 0(a0) 106 | mv a0, a5 107 | ret 108 | ``` 109 | 110 | 在上面的程式中,我們將 lock 結構中的 locked 讀入,與數值 `1` 做交換,最後再回傳暫存器 `a5` 的內容。 111 | 進一步歸納程式的執行結果,我們可以得出兩個 Case: 112 | 113 | 1. 成功獲取鎖 114 | 當 `lock->locked` 為 `0` 時,經過 `amoswap.w.aq` 進行交換以後, `lock->locked` 值為 `1` ,回傳值 (Value of a5) 為 `0`: 115 | 116 | ```cpp 117 | void lock_acquire(lock_t *lock) 118 | { 119 | for (;;) 120 | { 121 | if (!atomic_swap(lock)) 122 | { 123 | break; 124 | } 125 | } 126 | } 127 | ``` 128 | 129 | 當回傳值為 `0` , `lock_acquire()` 就會順利跳出無窮迴圈,進入到 Critical sections 執行。 2. 沒有獲取鎖 130 | 反之,則繼續在無窮迴圈中嘗試獲得鎖。 131 | 132 | ## 延伸閱讀 133 | 134 | 如果對 `Race Condition` 、 `Critical sections` 、 `Mutex` 有興趣,可以閱讀 [AwesomeCS Wiki](https://github.com/ianchen0119/AwesomeCS/wiki) 中的並行程式設計一節。 135 | -------------------------------------------------------------------------------- /doc/tw/README.md: -------------------------------------------------------------------------------- 1 | # mini-riscv-os -- 一步一步自製 RISC-V 處理器上的嵌入式作業系統 2 | 3 | ## 簡介 4 | 5 | mini-riscv-os 是仿照 [jserv](https://github.com/jserv) 的 [mini-arm-os](https://github.com/jserv/mini-arm-os) 所做出來的專案。 6 | 7 | 陳鍾誠 [ccckmit](https://github.com/ccckmit) 參考 mini-arm-os 專案,改寫成 RISC-V 處理器的 mini-riscv-os,然後在 Windows 10 作業系統中編譯並執行 。 8 | 9 | ## 安裝指引 10 | 11 | 目前只在 windows 10 平台中建置並測試,請安裝 git bash 與 [FreedomStudio](https://www.sifive.com/software)。 12 | 13 | 下載 FreedomStudio 的 windows 版後,請將 `riscv64-unknown-elf-gcc/bin` 和 `riscv-qemu/bin` 加入系統路徑 PATH 設到 中。 14 | 15 | 舉例而言,在我的電腦中,我將下列兩個資料夾加入 PATH 中。 16 | 17 | ``` 18 | D:\install\FreedomStudio-2020-06-3-win64\SiFive\riscv64-unknown-elf-gcc-8.3.0-2020.04.1\bin 19 | 20 | D:\install\FreedomStudio-2020-06-3-win64\SiFive\riscv-qemu-4.2.0-2020.04.0\bin 21 | ``` 22 | 23 | 然後請用 git bash 來建置編譯本課程的專案。我是在 vscode (Visual Studio Code) 中開終端機 Select Default Shell 設成 bash ,然後編譯這些專案的。 24 | 25 | ## 自製 RISC-V 嵌入式作業系統課程 26 | 27 | * [01-HelloOs](01-HelloOs.md) 28 | - 使用 UART 印出 `"Hello OS!"` 訊息 29 | * [02-ContextSwitch](02-ContextSwitch.md) 30 | - 從 OS 切到第一個使用者行程 (user task) 31 | * [03-MultiTasking](03-MultiTasking.md) 32 | - 可以在作業系統與兩個行程之間切換 (協同式多工,非強制切換 Non Preemptive) 33 | * [04-TimerInterrupt](04-TimerInterrupt.md) 34 | - 示範如何引發時間中斷 (大約每秒一次) 35 | * [05-Preemptive](05-Preemptive.md) 36 | - 透過時間中斷強制切換的 Preemptive 作業系統。 37 | 38 | ## 建置方法 39 | 40 | * 先切到對應資料夾,例如 `cd 01-HelloOs`,然後執行下列指令。 41 | 42 | ``` 43 | make 44 | make qemu 45 | ``` 46 | 47 | 如果你沒有 make,請嘗試下列方式: 48 | 49 | 1. 用 choco 安裝 make 50 | * [choco install make](https://chocolatey.org/packages/make) 51 | * 當然在此之前得先 [安裝好 choco](https://chocolatey.org/install) 52 | 2. 或者下載 CodeBlocks ,然後加入 `CodeBlocks\MinGW\bin` 到 PATH 中。 53 | * 例如我的系統裡 make 是在 `C:\Program Files (x86)\CodeBlocks\MinGW\bin` 54 | 55 | ## 授權聲明 56 | 57 | `mini-riscv-os` 在遵守 BSD 授權協議的情況下,可以被自由的複製、散布與修改,詳細授權聲明請看 [LICENSE](../../LICENSE)。 58 | 59 | ## 參考文獻 60 | 61 | * [Adventures in RISC-V](https://matrix89.github.io/writes/writes/experiments-in-riscv/) 62 | * [Xv6, a simple Unix-like teaching operating system](https://pdos.csail.mit.edu/6.828/2020/xv6.html) 63 | * [Basics of programming a UART](https://www.activexperts.com/serial-port-component/tutorials/uart/) -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccriscv/mini-riscv-os/70443b0ab3bf186c47b42127a0ffe31ad417fb91/logo.png --------------------------------------------------------------------------------