diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..387fd41 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.o +*.bin +*.elf + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..236c04e --- /dev/null +++ b/LICENSE @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2021, plctlab +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6d09b8f --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +SECTIONS = \ + code/asm \ + code/os \ + +.DEFAULT_GOAL := all +all : + @echo "begin compile ALL exercises for assembly samples ......................." + for dir in $(SECTIONS); do $(MAKE) -C $$dir || exit "$$?"; done + @echo "compile ALL exercises finished successfully! ......" + +.PHONY : clean +clean: + for dir in $(SECTIONS); do $(MAKE) -C $$dir clean || exit "$$?"; done + +.PHONY : slides +slides: + rm -f ./slides/*.pdf + soffice --headless --convert-to pdf:writer_pdf_Export --outdir ./slides ./docs/ppts/*.pptx + diff --git a/README.md b/README.md index 52e11c0..28d95c4 100644 --- a/README.md +++ b/README.md @@ -1 +1,77 @@ -# riscv-operating-system-mooc +**Step by step, learn to develop an operating system on RISC-V** + + + +- [Introduction](#introduction) +- [Operating environment](#operating-environment) +- [Construction and usage](#construction-and-usage) +- [References](#references) + + +# Introduction + +This course is used to teach and demonstrate how to write a simple operating system kernel for the RISC-V platform from scratch. Released under the BSD 2-Clause license (For details, please read the [LICENSE file](./LICENSE) under the root directory of this repository). + +# Operating environment + +All demo codes have been verified under the following equipment environment: + +``` +$ lsb_release -a +No LSB modules are available. +Distributor ID: Ubuntu +Description: Ubuntu 20.04.2 LTS +Release: 20.04 +Codename: focal +$ uname -r +5.8.0-45-generic +``` + +There may be dependent libraries that need to be installed manually. If you are prompted that other libraries and dependencies are missing during the operation, please install them by yourself according to the prompts. + +``` +$ sudo apt update +$ sudo apt install build-essential git gitk vim libfdt-dev libsdl2-dev +``` + +The experiment requires some running tools, pre-compiled binary files have been provided, the specific installation steps are described as follows: + +First, create a working directory, and then enter the directory. + +``` +$ mkdir $HOME/ws +$ cd $HOME/ws +``` + +Download the development tool package `tools.tar.gz`, the download address is:。 + +After downloading, copy the file to `$HOME/ws` and unzip it. + +``` +$ tar xzf tools.tar.gz +``` + +Add the following path to `$HOME/.bashrc` +``` +export PATH="$PATH:$HOME/ws/tools/gcc/bin:$HOME/ws/tools/qemu/bin" +``` + +Re-import `$HOME/.bashrc` or restart the system to make the configuration effective. + +# Construction and usage + +- make:Compile and build +- make run:Start qemu and run +- make debug:Start debugging +- make code:Disassemble to view binary code +- make clean:cleanup + +For specific use, please refer to the Makefile under the specific sub-project. + +# References + +The design of this course refers to the following network resources, thank you :) + +- The Adventures of OS: +- mini-riscv-os: +- Xv6, a simple Unix-like teaching operating system: diff --git a/README_zh.md b/README_zh.md new file mode 100644 index 0000000..2711505 --- /dev/null +++ b/README_zh.md @@ -0,0 +1,77 @@ +**循序渐进,学习开发一个 RISC-V 上的操作系统** + + + +- [简介](#简介) +- [运行环境](#运行环境) +- [构建和使用说明](#构建和使用说明) +- [参考文献](#参考文献) + + +# 简介 + +本课程用于教学演示如何从零开始为 RISC-V 平台编写一个简单的操作系统内核。采用 BSD 2-Clause 许可证发布(具体请阅读本仓库根目录下的 [LICENSE 文件](./LICENSE))。 + +# 运行环境 + +所有演示代码在以下设备环境下验证通过: + +``` +$ lsb_release -a +No LSB modules are available. +Distributor ID: Ubuntu +Description: Ubuntu 20.04.2 LTS +Release: 20.04 +Codename: focal +$ uname -r +5.8.0-45-generic +``` + +有可能需要手动安装的依赖库,如果运行过程中提示缺少其他的库和依赖,请按照提示自行安装。 + +``` +$ sudo apt update +$ sudo apt install build-essential git gitk vim libfdt-dev libsdl2-dev +``` + +实验需要一些运行工具,已经提供预先编译好的二进制文件,具体安装步骤描述如下: + +首先,创建一个工作目录,然后进入该目录。 + +``` +$ mkdir $HOME/ws +$ cd $HOME/ws +``` + +下载开发工具软件包 `tools.tar.gz`,下载地址为:。 + +下载完毕后将该文件拷贝到 `$HOME/ws` 下并解压。 + +``` +$ tar xzf tools.tar.gz +``` + +将以下路径加入 `$HOME/.bashrc` +``` +export PATH="$PATH:$HOME/ws/tools/gcc/bin:$HOME/ws/tools/qemu/bin" +``` + +重新导入 `$HOME/.bashrc` 或者重启系统使配置生效即可。 + +# 构建和使用说明 + +- make:编译构建 +- make run:启动 qemu 并运行 +- make debug:启动调试 +- make code:反汇编查看二进制代码 +- make clean:清理 + +具体使用请参考具体子项目下的 Makefile 文件。 + +# 参考文献 + +本课程的设计参考了如下网络资源,在此表示感谢 :) + +- The Adventures of OS: +- mini-riscv-os: +- Xv6, a simple Unix-like teaching operating system: diff --git a/code/asm/Makefile b/code/asm/Makefile new file mode 100644 index 0000000..199fb45 --- /dev/null +++ b/code/asm/Makefile @@ -0,0 +1,63 @@ +SECTIONS_Arithmetic = \ + add \ + sub \ + addi \ + subi \ + neg \ + nop \ + mv \ + lui \ + li + +SECTIONS_Logical = \ + and \ + andi \ + not + +SECTIONS_Shifting = \ + slli \ + srli \ + srai + +SECTIONS_Load_Store = \ + lb \ + lbu \ + lw \ + sb \ + auipc \ + la + +SECTIONS_Branch = \ + bne + +SECTIONS_Jump = \ + jalr \ + +SECTIONS_CallingConventions = \ + cc_leaf \ + cc_nested \ + +SECTIONS_others = \ + asm2c \ + c2asm + +SECTIONS = \ + _first \ + $(SECTIONS_Arithmetic) \ + $(SECTIONS_Logical) \ + $(SECTIONS_Shifting) \ + $(SECTIONS_Load_Store) \ + $(SECTIONS_Branch) \ + $(SECTIONS_Jump) \ + $(SECTIONS_CallingConventions) \ + $(SECTIONS_others) + +.DEFAULT_GOAL := all +all : + @echo "begin compile ALL exercises for assembly samples ......................." + for dir in $(SECTIONS); do $(MAKE) -C $$dir || exit "$$?"; done + @echo "compile ALL exercises finished successfully! ......" + +.PHONY : clean +clean: + for dir in $(SECTIONS); do $(MAKE) -C $$dir clean || exit "$$?"; done diff --git a/code/asm/_first/Makefile b/code/asm/_first/Makefile new file mode 120000 index 0000000..b421268 --- /dev/null +++ b/code/asm/_first/Makefile @@ -0,0 +1 @@ +../build.mk \ No newline at end of file diff --git a/code/asm/_first/test.s b/code/asm/_first/test.s new file mode 100644 index 0000000..c2b8c65 --- /dev/null +++ b/code/asm/_first/test.s @@ -0,0 +1,10 @@ +# First RISC-V Assemble Sample + .text # Define beginning of text section + .global _start # Define entry _start +_start: # Label, not really required + li x6, 5 # Load register x6 with the value 5 + li x7, 4 # Load register x7 with the value 4 + add x5, x6, x7 # Add x6 and x7 and store result in x5 +stop: j stop # Infinite loop to stop execution + .end # End of file + diff --git a/code/asm/add/Makefile b/code/asm/add/Makefile new file mode 120000 index 0000000..b421268 --- /dev/null +++ b/code/asm/add/Makefile @@ -0,0 +1 @@ +../build.mk \ No newline at end of file diff --git a/code/asm/add/test.s b/code/asm/add/test.s new file mode 100644 index 0000000..a1aa746 --- /dev/null +++ b/code/asm/add/test.s @@ -0,0 +1,19 @@ +# Add +# Format: +# ADD RD, RS1, RS2 +# Description: +# The contents of RS1 is added to the contents of RS2 and the result is +# placed in RD. + + .text # Define beginning of text section + .global _start # Define entry _start + +_start: # Label, not really required + li x6, 1 # x6 = 1 + li x7, 2 # x7 = 2 + add x5, x6, x7 # x5 = x6 + x7 + +stop: + j stop # Infinite loop to stop execution + + .end # End of file diff --git a/code/asm/addi/Makefile b/code/asm/addi/Makefile new file mode 120000 index 0000000..b421268 --- /dev/null +++ b/code/asm/addi/Makefile @@ -0,0 +1 @@ +../build.mk \ No newline at end of file diff --git a/code/asm/addi/test.s b/code/asm/addi/test.s new file mode 100644 index 0000000..ac7927f --- /dev/null +++ b/code/asm/addi/test.s @@ -0,0 +1,18 @@ +# Add Immediate +# Format: +# ADDI RD, RS1, IMM +# Description: +# The immediate value (a sign-extended 12-bit value, i.e., -2,048 .. +2,047) +# is added to the contents of RS1 and the result is placed in RD. + + .text # Define beginning of text section + .global _start # Define entry _start + +_start: # Label, not really required + li x6, 2 # x6 = 2 + addi x5, x6, 1 # x5 = x6 + 1 + +stop: + j stop # Infinite loop to stop execution + + .end # End of file diff --git a/code/asm/and/Makefile b/code/asm/and/Makefile new file mode 120000 index 0000000..b421268 --- /dev/null +++ b/code/asm/and/Makefile @@ -0,0 +1 @@ +../build.mk \ No newline at end of file diff --git a/code/asm/and/test.s b/code/asm/and/test.s new file mode 100644 index 0000000..4e4285b --- /dev/null +++ b/code/asm/and/test.s @@ -0,0 +1,18 @@ +# And +# Format: +# AND RD, RS1, RS2 +# The contents of RS1 is logically ANDed with the contents of RS2 and the +# result is placed in RD. + + .text # Define beginning of text section + .global _start # Define entry _start + +_start: # Label, not really required + li x6, 0x10 # x6 = b1000-0000 + li x7, 0x01 # x7 = b0000-0001 + and x5, x6, x7 # x5 = x6 & x7 + +stop: + j stop # Infinite loop to stop execution + + .end # End of file diff --git a/code/asm/andi/Makefile b/code/asm/andi/Makefile new file mode 120000 index 0000000..b421268 --- /dev/null +++ b/code/asm/andi/Makefile @@ -0,0 +1 @@ +../build.mk \ No newline at end of file diff --git a/code/asm/andi/test.s b/code/asm/andi/test.s new file mode 100644 index 0000000..a7bf8ac --- /dev/null +++ b/code/asm/andi/test.s @@ -0,0 +1,18 @@ +# And Immediate +# Format: +# ANDI RD, RS1, IMM +# Description: +# The immediate value (a sign-extended 12-bit value, i.e., -2,048 .. +2,047) +# is logically ANDed with the contents of RD1 and the result is placed in RD. + + .text # Define beginning of text section + .global _start # Define entry _start + +_start: # Label, not really required + li x6, 0x10 # x6 = b1000-0000 + andi x5, x6, 0x01 # x5 = x6 & 0x01 + +stop: + j stop # Infinite loop to stop execution + + .end # End of file diff --git a/code/asm/asm2c/Makefile b/code/asm/asm2c/Makefile new file mode 100644 index 0000000..98897ac --- /dev/null +++ b/code/asm/asm2c/Makefile @@ -0,0 +1,7 @@ +EXEC = test + +SRC = $(EXEC).s $(EXEC).c + +GDBINIT = ./gdbinit + +include ../rule.mk \ No newline at end of file diff --git a/code/asm/asm2c/gdbinit b/code/asm/asm2c/gdbinit new file mode 100644 index 0000000..52a7362 --- /dev/null +++ b/code/asm/asm2c/gdbinit @@ -0,0 +1,9 @@ +display/z $sp +display/z $ra +display/z $a0 +display/z $a1 + +set disassemble-next-line on +b _start +target remote : 1234 +c diff --git a/code/asm/asm2c/test.c b/code/asm/asm2c/test.c new file mode 100644 index 0000000..5b11a6f --- /dev/null +++ b/code/asm/asm2c/test.c @@ -0,0 +1,5 @@ +int foo(int a, int b) +{ + int sum = a + b; + return sum; +} \ No newline at end of file diff --git a/code/asm/asm2c/test.s b/code/asm/asm2c/test.s new file mode 100644 index 0000000..3682726 --- /dev/null +++ b/code/asm/asm2c/test.s @@ -0,0 +1,23 @@ +# ASM call C + + .text # Define beginning of text section + .global _start # Define entry _start + .global foo # + +_start: # Label, not really required + la sp, stack_end # prepare stack for calling functions + + li a0, 1 + li a1, 2 + call foo + +stop: + j stop # Infinite loop to stop execution + +stack_start: + .rept 10 + .word 0 + .endr +stack_end: + + .end # End of file diff --git a/code/asm/auipc/Makefile b/code/asm/auipc/Makefile new file mode 120000 index 0000000..b421268 --- /dev/null +++ b/code/asm/auipc/Makefile @@ -0,0 +1 @@ +../build.mk \ No newline at end of file diff --git a/code/asm/auipc/test.s b/code/asm/auipc/test.s new file mode 100644 index 0000000..a89f7f1 --- /dev/null +++ b/code/asm/auipc/test.s @@ -0,0 +1,23 @@ +# Add Upper Immediate to PC +# Format: +# AUIPC RD, IMM +# Description: +# AUIPC is used to build pc-relative addresses and uses the U-type format. +# AUIPC forms a 32-bit offset from the 20-bit U-immediate, filling in the +# lowest 12 bits with zeros, adds this offset to the address of the AUIPC +# instruction, then places the result in register RD. +# Note: +# The current PC can be obtained by setting the U-immediate to 0. + + .text # Define beginning of text section + .global _start # Define entry _start + +_start: # Label, not really required + auipc x6, 0 # x6 = PC + auipc x5, 0x12345 # x5 = PC + (0x12345 << 12) + + +stop: + j stop # Infinite loop to stop execution + + .end # End of file diff --git a/code/asm/bne/Makefile b/code/asm/bne/Makefile new file mode 120000 index 0000000..b421268 --- /dev/null +++ b/code/asm/bne/Makefile @@ -0,0 +1 @@ +../build.mk \ No newline at end of file diff --git a/code/asm/bne/test.s b/code/asm/bne/test.s new file mode 100644 index 0000000..340c3ac --- /dev/null +++ b/code/asm/bne/test.s @@ -0,0 +1,26 @@ +# Branch if Not Equal +# Format: +# BNE RS1, RS2, IMM +# Description: +# The contents of RS1 is compared to the contents of RS2. If not equal, +# control jumps to a PC-relative target address. +# Note: +# When programming, we just provide label instead of immediate value, and +# leave linker to provide the final immediate value. + + .text # Define beginning of text section + .global _start # Define entry _start + +_start: # Label, not really required + # i = 0 + # while (i < 5) i++; + li x5, 0 + li x6, 5 +loop: + addi x5, x5, 1 + bne x5, x6, loop + +stop: + j stop # Infinite loop to stop execution + + .end # End of file diff --git a/code/asm/build.mk b/code/asm/build.mk new file mode 100644 index 0000000..d41190a --- /dev/null +++ b/code/asm/build.mk @@ -0,0 +1,7 @@ +EXEC = test + +SRC = $(EXEC).s + +GDBINIT = ../gdbinit + +include ../rule.mk diff --git a/code/asm/c2asm/Makefile b/code/asm/c2asm/Makefile new file mode 100644 index 0000000..98897ac --- /dev/null +++ b/code/asm/c2asm/Makefile @@ -0,0 +1,7 @@ +EXEC = test + +SRC = $(EXEC).s $(EXEC).c + +GDBINIT = ./gdbinit + +include ../rule.mk \ No newline at end of file diff --git a/code/asm/c2asm/gdbinit b/code/asm/c2asm/gdbinit new file mode 100644 index 0000000..4b099c5 --- /dev/null +++ b/code/asm/c2asm/gdbinit @@ -0,0 +1,11 @@ +display/z $sp +display/z $ra +display/z $s0 +display/z $a0 +display/z $a1 +display/z $a4 +display/z $a5 +set disassemble-next-line on +b _start +target remote : 1234 +c diff --git a/code/asm/c2asm/test.c b/code/asm/c2asm/test.c new file mode 100644 index 0000000..3c55290 --- /dev/null +++ b/code/asm/c2asm/test.c @@ -0,0 +1,11 @@ +int foo(int a, int b) +{ + int sum; + asm volatile( + "nop\n" + "add %0, %1, %2" + :"=r"(sum) + :"r"(a), "r"(b) + ); + return sum; +} \ No newline at end of file diff --git a/code/asm/c2asm/test.s b/code/asm/c2asm/test.s new file mode 100644 index 0000000..dd0e20c --- /dev/null +++ b/code/asm/c2asm/test.s @@ -0,0 +1,23 @@ +# C all ASM + + .text # Define beginning of text section + .global _start # Define entry _start + .global foo # + +_start: # Label, not really required + la sp, stack_end # prepare stack for calling functions + + li a0, 1 + li a1, 2 + call foo + +stop: + j stop # Infinite loop to stop execution + +stack_start: + .rept 10 + .word 0 + .endr +stack_end: + + .end # End of file diff --git a/code/asm/cc_leaf/Makefile b/code/asm/cc_leaf/Makefile new file mode 100644 index 0000000..de79511 --- /dev/null +++ b/code/asm/cc_leaf/Makefile @@ -0,0 +1,7 @@ +EXEC = test + +SRC = $(EXEC).s + +GDBINIT = ./gdbinit + +include ../rule.mk \ No newline at end of file diff --git a/code/asm/cc_leaf/gdbinit b/code/asm/cc_leaf/gdbinit new file mode 100644 index 0000000..fcf609e --- /dev/null +++ b/code/asm/cc_leaf/gdbinit @@ -0,0 +1,11 @@ +display/z $sp +display/z $ra +display/z $a0 +display/z $s0 +display/z $s1 +display/z $s2 + +set disassemble-next-line on +b _start +target remote : 1234 +c diff --git a/code/asm/cc_leaf/test.s b/code/asm/cc_leaf/test.s new file mode 100644 index 0000000..a5a194f --- /dev/null +++ b/code/asm/cc_leaf/test.s @@ -0,0 +1,48 @@ +# Calling Convention +# Demo to create a leaf routine + + .text # Define beginning of text section + .global _start # Define entry _start + +_start: # Label, not really required + la sp, stack_end # prepare stack for calling functions + + li a0, 3 + call square + + # the time return here, a0 should stores the result +stop: + j stop # Infinite loop to stop execution + +# int square(int num) +square: + # prologue + addi sp, sp, -8 + sw s0, 0(sp) + sw s1, 4(sp) + + # `mul a0, a0, a0` should be fine, + # programing as below just to demo we can contine use the stack + mv s0, a0 + mul s1, s0, s0 + mv a0, s1 + + # epilogue + lw s0, 0(sp) + lw s1, 4(sp) + addi sp, sp, 8 + + ret + + # add nop here just for demo in gdb + nop + + # allocate stack space +stack_start: + .rept 10 + .word 0 + .endr +stack_end: + + .end # End of file + diff --git a/code/asm/cc_nested/Makefile b/code/asm/cc_nested/Makefile new file mode 100644 index 0000000..de79511 --- /dev/null +++ b/code/asm/cc_nested/Makefile @@ -0,0 +1,7 @@ +EXEC = test + +SRC = $(EXEC).s + +GDBINIT = ./gdbinit + +include ../rule.mk \ No newline at end of file diff --git a/code/asm/cc_nested/gdbinit b/code/asm/cc_nested/gdbinit new file mode 100644 index 0000000..05d6a45 --- /dev/null +++ b/code/asm/cc_nested/gdbinit @@ -0,0 +1,13 @@ +display/z $sp +display/z $ra +display/z $a0 +display/z $a1 +display/z $s0 +display/z $s1 +display/z $s2 + + +set disassemble-next-line on +b _start +target remote : 1234 +c diff --git a/code/asm/cc_nested/test.s b/code/asm/cc_nested/test.s new file mode 100644 index 0000000..e3329cc --- /dev/null +++ b/code/asm/cc_nested/test.s @@ -0,0 +1,83 @@ +# Calling Convention +# Demo how to write nested routunes + + .text # Define beginning of text section + .global _start # Define entry _start + +_start: # Label, not really required + la sp, stack_end # prepare stack for calling functions + + # aa_bb(3, 4); + li a0, 3 + li a1, 4 + call aa_bb + +stop: + j stop # Infinite loop to stop execution + +# int aa_bb(int a, int b) +# return a^2 + b^2 +aa_bb: + # prologue + addi sp, sp, -16 + sw s0, 0(sp) + sw s1, 4(sp) + sw s2, 8(sp) + sw ra, 12(sp) + + # cp and store the input params + mv s0, a0 + mv s1, a1 + + # sum will be stored in s2 and is initialized as zero + li s2, 0 + + mv a0, s0 + jal square + add s2, s2, a0 + + mv a0, s1 + jal square + add s2, s2, a0 + + mv a0, s2 + + # epilogue + lw s0, 0(sp) + lw s1, 4(sp) + lw s2, 8(sp) + lw ra, 12(sp) + addi sp, sp, 16 + ret + +# int square(int num) +square: + # prologue + addi sp, sp, -8 + sw s0, 0(sp) + sw s1, 4(sp) + + # `mul a0, a0, a0` should be fine, + # programing as below just to demo we can contine use the stack + mv s0, a0 + mul s1, s0, s0 + mv a0, s1 + + # epilogue + lw s0, 0(sp) + lw s1, 4(sp) + addi sp, sp, 8 + + ret + + # add nop here just for demo in gdb + nop + + # allocate stack space +stack_start: + .rept 10 + .word 0 + .endr +stack_end: + + .end # End of file diff --git a/code/asm/gdbinit b/code/asm/gdbinit new file mode 100644 index 0000000..0f5c443 --- /dev/null +++ b/code/asm/gdbinit @@ -0,0 +1,8 @@ +display/z $x5 +display/z $x6 +display/z $x7 + +set disassemble-next-line on +b _start +target remote : 1234 +c diff --git a/code/asm/jalr/Makefile b/code/asm/jalr/Makefile new file mode 120000 index 0000000..b421268 --- /dev/null +++ b/code/asm/jalr/Makefile @@ -0,0 +1 @@ +../build.mk \ No newline at end of file diff --git a/code/asm/jalr/test.s b/code/asm/jalr/test.s new file mode 100644 index 0000000..b072b8d --- /dev/null +++ b/code/asm/jalr/test.s @@ -0,0 +1,44 @@ +# Jump And Link (Short-Distance CALL) +# Format: +# JAL RD, IMM +# Description: +# This instruction is used to call a subroutine (i.e., function). +# The jump and link (JAL) instruction uses the J-type format, where the +# immediate (20 bits width) encodes a signed offset in multiples of 2 bytes. +# The offset is sign-extended and added to the address of the jump +# instruction to form the jump target address. JAL can therefore target +# a ±1 MiB range. +# JAL stores the address of the instruction following the jump (pc+4) into +# register RD. +# Note: +# When programming, we just provide label instead of immediate value, and +# leave linker to provide the final immediate value. +# +# Jump And Link Register +# Format: +# JALR RD, RS1, IMM +# Description: +# This instruction is used to call a subroutine (i.e., function). +# The indirect jump instruction JALR (jump and link register) uses the +# I-type encoding. The target address is obtained by adding the +# sign-extended 12-bit I-immediate to the register RS1, then setting +# the least-significant bit of the result to zero. JALR can therefore target +# a ±1 KiB range, relative to the address in RS1. +# The address of the instruction following the jump(pc+4) is written to register RD. + + .text # Define beginning of text section + .global _start # Define entry _start + +_start: + li x6, 1 + li x7, 2 + jal x5, sum # call sum, return address is saved in x5 + +stop: + j stop # Infinite loop to stop execution + +sum: + add x6, x6, x7 # x6 = x6 + x7 + jalr x0, 0(x5) # return + + .end # End of file diff --git a/code/asm/la/Makefile b/code/asm/la/Makefile new file mode 120000 index 0000000..b421268 --- /dev/null +++ b/code/asm/la/Makefile @@ -0,0 +1 @@ +../build.mk \ No newline at end of file diff --git a/code/asm/la/test.s b/code/asm/la/test.s new file mode 100644 index 0000000..5c65d7e --- /dev/null +++ b/code/asm/la/test.s @@ -0,0 +1,29 @@ +# Load Address +# Format: +# LA RD, Address +# Description: +# The address of some memory location is copied into RD. +# +# LA is a pseudoinstruction, and is assembled to a sequence of two +# instructions to achieve the same effect. +# AUIPC RD, Upper-20 +# ADDI RD, RD, Lower-12 +# +# The "Address" can refer to any location within the 32-bit memory space. +# The address is converted to a PC-relative address, with an offset of +# 32 bits. This offset is then broken into two pieces: a upper 20-bit +# piece and a lower 12-bit piece. + + .text # Define beginning of text section + .global _start # Define entry _start + +_start: # Label, not really required + la x5, _start # x6 = PC + jr x5 + +stop: + j stop # Infinite loop to stop execution + +exit: + + .end # End of file diff --git a/code/asm/lb/Makefile b/code/asm/lb/Makefile new file mode 120000 index 0000000..b421268 --- /dev/null +++ b/code/asm/lb/Makefile @@ -0,0 +1 @@ +../build.mk \ No newline at end of file diff --git a/code/asm/lb/test.s b/code/asm/lb/test.s new file mode 100644 index 0000000..84d7a5e --- /dev/null +++ b/code/asm/lb/test.s @@ -0,0 +1,28 @@ +# Load Byte (Signed) +# Format: +# LB RD, IMM(RS1) +# Description: +# An 8-bit value is fetched from memory and moved into register RD. The +# memory address is formed by adding the offset(IMM) to the contents of RS1. +# The 8-bit value is sign-extended to the full length of the register. +# Note: +# Due to IMM is 12 bits width, the target location given by the +# offset(IMM) must be within the range of -2,048 .. 2,047 relative to the +# value in RS1. + + .text # Define beginning of text section + .global _start # Define entry _start + +_start: # Label, not really required + la x5, _array # char *x5 = &(array[0]) + lb x6, 0(x5) # char x6 = *x5 + lb x7, 1(x5) # char x7 = *(x5 + 1) +stop: + j stop # Infinite loop to stop execution + +_array: + .byte 0x11 + .byte 0xff + + .end # End of file + diff --git a/code/asm/lbu/Makefile b/code/asm/lbu/Makefile new file mode 120000 index 0000000..b421268 --- /dev/null +++ b/code/asm/lbu/Makefile @@ -0,0 +1 @@ +../build.mk \ No newline at end of file diff --git a/code/asm/lbu/test.s b/code/asm/lbu/test.s new file mode 100644 index 0000000..77a2278 --- /dev/null +++ b/code/asm/lbu/test.s @@ -0,0 +1,28 @@ +# Load Byte (Unsigned) +# Format: +# LBU RD, IMM(RS1) +# Description: +# An 8-bit value is fetched from memory and moved into register RD. The +# memory address is formed by adding the offset(IMM) to the contents of RS1. +# The 8-bit value is zero-extended to the full length of the register. +# Note: +# Due to IMM is 12 bits width, the target location given by the +# offset(IMM) must be within the range of -2,048 .. 2,047 relative to the +# value in RS1. + + .text # Define beginning of text section + .global _start # Define entry _start + +_start: # Label, not really required + la x5, _array # unsigned char *x5 = &(array[0]) + lbu x6, 0(x5) # unsigned x6 = *x5 + lbu x7, 1(x5) # unsigned x7 = *(x5 + 1) +stop: + j stop # Infinite loop to stop execution + +_array: + .byte 0x11 + .byte 0xff + + .end # End of file + diff --git a/code/asm/li/Makefile b/code/asm/li/Makefile new file mode 120000 index 0000000..b421268 --- /dev/null +++ b/code/asm/li/Makefile @@ -0,0 +1 @@ +../build.mk \ No newline at end of file diff --git a/code/asm/li/test.s b/code/asm/li/test.s new file mode 100644 index 0000000..d6fbb4a --- /dev/null +++ b/code/asm/li/test.s @@ -0,0 +1,44 @@ +# Load Immediate +# Format: +# LI RD, IMM +# Description: +# The immediate value (which can be any 32-bit value) is copied into RD. +# LI is a pseudoinstruction, and is assembled differently depending on +# the actual value present. +# +# If the immediate value is in the range of -2,048 .. +2,047, then it can +# be assembled identically to: +# ADDI RD, x0, IMM +# +# If the immediate value is not within the range of -2,048 .. +2,047 but +# is within the range of a 32-bit number (i.e., -2,147,483,648 .. +2,147,483,647) +# then it can be assembled using this two-instruction sequence: +# LUI RD, Upper-20 +# ADDI RD, RD, Lower-12 +# where "Upper-20" represents the uppermost 20 bits of the value +# and "Lower-12" represents the least significant 12-bits of the value. +# Note that, due to the immediate operand to the addi has its +# most-significant-bit set to 1 then it will have the effect of +# subtracting 1 from the operand in the lui instruction. + + .text # Define beginning of text section + .global _start # Define entry _start + +_start: # Label, not really required + li x5, 0x80 # imm is in the range of [-2,048, +2,047] + addi x5, x0, 0x80 # these two instructions assemble into the same thing! + + li x6, 0x12345001 # imm is NOT in the range of [-2,048, +2,047] + # and the most-significant-bit of "lower-12" is 0 + lui x6, 0x12345 # these two instructions assemble into the same thing! + addi x6, x6, 0x001 + + li x7, 0x12345800 # imm is NOT in the range of [-2,048, +2,047] + # and the most-significant-bit of "lower-12" is 1 + lui x7, 0x12346 # these two instructions assemble into the same thing! + addi x7, x7, -0x800 + +stop: + j stop # Infinite loop to stop execution + + .end # End of file diff --git a/code/asm/lui/Makefile b/code/asm/lui/Makefile new file mode 120000 index 0000000..b421268 --- /dev/null +++ b/code/asm/lui/Makefile @@ -0,0 +1 @@ +../build.mk \ No newline at end of file diff --git a/code/asm/lui/test.s b/code/asm/lui/test.s new file mode 100644 index 0000000..b49777a --- /dev/null +++ b/code/asm/lui/test.s @@ -0,0 +1,19 @@ +# Load Upper Immediate +# Format: +# LUI RD, IMM +# Description: +# The instruction contains a 20-bit immediate value. This value is placed +# in the leftmost (i.e., upper, most significant) 20 bits of the register +# RD and the rightmost (i.e., lower, least significant) 12-bits are set +# to zero. + + .text # Define beginning of text section + .global _start # Define entry _start + +_start: # Label, not really required + lui x5, 0x12345 # int x5 = 0x12345 << 12 + addi x5, x5, 0x678 # x5 = x5 + 0x678 +stop: + j stop # Infinite loop to stop execution + + .end # End of file diff --git a/code/asm/lw/Makefile b/code/asm/lw/Makefile new file mode 120000 index 0000000..b421268 --- /dev/null +++ b/code/asm/lw/Makefile @@ -0,0 +1 @@ +../build.mk \ No newline at end of file diff --git a/code/asm/lw/test.s b/code/asm/lw/test.s new file mode 100644 index 0000000..a0d52bc --- /dev/null +++ b/code/asm/lw/test.s @@ -0,0 +1,31 @@ +# Load Byte (Signed) +# Format: +# LW RD, IMM(RS1) +# Description: +# An 32-bit value is fetched from memory and moved into register RD. The +# memory address is formed by adding the offset(IMM) to the contents of RS1. +# Note: +# Due to IMM is 12 bits width, the target location given by the +# offset(IMM) must be within the range of -2,048 .. 2,047 relative to the +# value in RS1. +# In a machine with 32-bit registers(rv32), neither sign-extension nor +# zero-extension is necessary for value that is already 32 bits wide. +# Therefore the "signed load" instruction (LW) does the same thing as the +# "unsigned load" instruction(LWU), making LWU redundant. + + .text # Define beginning of text section + .global _start # Define entry _start + +_start: # Label, not really required + la x5, _array # int *x5 = &(array[0]) + lw x6, 0(x5) # int x6 = *x5 + lw x7, 4(x5) # int x7 = *(x5 + 1) +stop: + j stop # Infinite loop to stop execution + +_array: + .word 0x11111111 + .word 0xffffffff + + .end # End of file + diff --git a/code/asm/mv/Makefile b/code/asm/mv/Makefile new file mode 120000 index 0000000..b421268 --- /dev/null +++ b/code/asm/mv/Makefile @@ -0,0 +1 @@ +../build.mk \ No newline at end of file diff --git a/code/asm/mv/test.s b/code/asm/mv/test.s new file mode 100644 index 0000000..56878bc --- /dev/null +++ b/code/asm/mv/test.s @@ -0,0 +1,20 @@ +# Move (Register to Register) +# Format: +# MV RD, RS +# Description: +# The contents of RS is copied into RD. +# MV is a pseudoinstruction, and is assembled identically to: +# ADDI RD, RS, 0 +# + + .text # Define beginning of text section + .global _start # Define entry _start + +_start: # Label, not really required + li x6, 30 # x6 = 30 + mv x5, x6 # x5 = x6 + addi x5, x6, 0 # these two instructions assemble into the same thing! +stop: + j stop # Infinite loop to stop execution + + .end # End of file diff --git a/code/asm/neg/Makefile b/code/asm/neg/Makefile new file mode 120000 index 0000000..b421268 --- /dev/null +++ b/code/asm/neg/Makefile @@ -0,0 +1 @@ +../build.mk \ No newline at end of file diff --git a/code/asm/neg/test.s b/code/asm/neg/test.s new file mode 100644 index 0000000..4726fdb --- /dev/null +++ b/code/asm/neg/test.s @@ -0,0 +1,21 @@ +# Negate +# Format: +# NEG RD, RS +# Description: +# The contents of RS is arithmetically negated and the result is placed in RD. +# NEG is a pseudoinstruction, and is assembled identically to: +# SUB RD, x0, RS +# + + .text # Define beginning of text section + .global _start # Define entry _start + +_start: # Label, not really required + li x6, 1 # x6 = 1 + neg x5, x6 # x5 = -x6 + sub x5, x0, x6 # these two instructions assemble into the same thing! + +stop: + j stop # Infinite loop to stop execution + + .end # End of file diff --git a/code/asm/nop/Makefile b/code/asm/nop/Makefile new file mode 120000 index 0000000..b421268 --- /dev/null +++ b/code/asm/nop/Makefile @@ -0,0 +1 @@ +../build.mk \ No newline at end of file diff --git a/code/asm/nop/test.s b/code/asm/nop/test.s new file mode 100644 index 0000000..36ecb89 --- /dev/null +++ b/code/asm/nop/test.s @@ -0,0 +1,20 @@ +# Nop +# Format: +# NOP +# Description: +# This instruction has no effect. +# NOP is a pseudoinstruction, and is assembled identically to: +# ADDI x0, x0, 0 +# + + .text # Define beginning of text section + .global _start # Define entry _start + +_start: # Label, not really required + nop # do nothing and has no effect on system + addi x0, x0, 0 # these two instructions assemble into the same thing! + +stop: + j stop # Infinite loop to stop execution + + .end # End of file diff --git a/code/asm/not/Makefile b/code/asm/not/Makefile new file mode 120000 index 0000000..b421268 --- /dev/null +++ b/code/asm/not/Makefile @@ -0,0 +1 @@ +../build.mk \ No newline at end of file diff --git a/code/asm/not/test.s b/code/asm/not/test.s new file mode 100644 index 0000000..f709f67 --- /dev/null +++ b/code/asm/not/test.s @@ -0,0 +1,19 @@ +# Not +# Format: +# NOT RD, RS +# The contents of RS is fetched and each of the bits is flipped. The resulting +# value is copied into RD. +# NEG is a pseudoinstruction, and is assembled identically to: +# XORI RD, RS, -1 // Note that -1 is 0xFFFFFFFF + + .text # Define beginning of text section + .global _start # Define entry _start + +_start: # Label, not really required + li x6, 0xffff0000 # x6 = 0xffff0000 + not x5, x6 # x5 = ~x6 + xori x5, x6, -1 # these two instructions assemble into the same thing! +stop: + j stop # Infinite loop to stop execution + + .end # End of file diff --git a/code/asm/rule.mk b/code/asm/rule.mk new file mode 100644 index 0000000..77bb62f --- /dev/null +++ b/code/asm/rule.mk @@ -0,0 +1,38 @@ +CROSS_COMPILE = riscv64-unknown-elf- +CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall + +QEMU = qemu-system-riscv32 +QFLAGS = -nographic -smp 1 -machine virt -bios none + +GDB = ${CROSS_COMPILE}gdb + +.DEFAULT_GOAL := all +all: + @$(CROSS_COMPILE)gcc $(CFLAGS) ${SRC} -Ttext=0x80000000 -o $(EXEC).elf + @$(CROSS_COMPILE)objcopy -O binary $(EXEC).elf $(EXEC).bin + +.PHONY : run +run: all + @echo "Press Ctrl-A and then X to exit QEMU" + @echo "------------------------------------" + @echo "No output, please run 'make debug' to see details" + @$(QEMU) $(QFLAGS) -kernel ./$(EXEC).elf + +.PHONY : debug +debug: all + @echo "Press Ctrl-C and then input 'quit' to exit GDB and QEMU" + @echo "-------------------------------------------------------" + @$(QEMU) $(QFLAGS) -kernel $(EXEC).elf -s -S & + @$(GDB) $(EXEC).elf -q -x ${GDBINIT} + +.PHONY : code +code: all + @$(CROSS_COMPILE)objdump -S $(EXEC).elf | less + +.PHONY : hex +hex: all + @hexdump -C $(EXEC).bin + +.PHONY : clean +clean: + rm -rf *.o *.bin *.elf diff --git a/code/asm/sb/Makefile b/code/asm/sb/Makefile new file mode 120000 index 0000000..b421268 --- /dev/null +++ b/code/asm/sb/Makefile @@ -0,0 +1 @@ +../build.mk \ No newline at end of file diff --git a/code/asm/sb/test.s b/code/asm/sb/test.s new file mode 100644 index 0000000..c7097d1 --- /dev/null +++ b/code/asm/sb/test.s @@ -0,0 +1,28 @@ +# Store Byte +# Format: +# SB RS2, IMM(RS1) +# Description: +# An 8-bit value is copied from register RS2 to memory. The upper (more +# significant) bits in RS2 are ignored. The memory address is formed by +# adding the offset(IMM) to the contents of RS1. +# Note: +# Due to IMM is 12 bits width, the target location given by the +# offset(IMM) must be within the range of -2,048 .. 2,047 relative to the +# value in RS1. + + .text # Define beginning of text section + .global _start # Define entry _start + +_start: # Label, not really required + li x6, 0xffffffab # int x6 = 0xffffffab + la x5, _array # array[0] = (char)x6 + sb x6, 0(x5) +stop: + j stop # Infinite loop to stop execution + +_array: + .byte 0x00 + .byte 0x00 + + .end # End of file + diff --git a/code/asm/slli/Makefile b/code/asm/slli/Makefile new file mode 120000 index 0000000..b421268 --- /dev/null +++ b/code/asm/slli/Makefile @@ -0,0 +1 @@ +../build.mk \ No newline at end of file diff --git a/code/asm/slli/test.s b/code/asm/slli/test.s new file mode 100644 index 0000000..b0f4e54 --- /dev/null +++ b/code/asm/slli/test.s @@ -0,0 +1,21 @@ +# Shift Left Logical Immediate +# Format: +# SLLI RD, RS1, IMM +# Description: +# The immediate value determines the number of bits to shift. The contents +# of RS1 is shifted left that many bits and the result is placed in RD. +# The bits shifted in are filled with zero. +# For 32-bit machines, the shift amount must be within 0..31, 0 means no +# shifting is done. + + .text # Define beginning of text section + .global _start # Define entry _start + +_start: # Label, not really required + li x6, 1 # x6 = 1 + slli x5, x6, 3 # x5 = x6 << 3 + +stop: + j stop # Infinite loop to stop execution + + .end # End of file diff --git a/code/asm/srai/Makefile b/code/asm/srai/Makefile new file mode 120000 index 0000000..b421268 --- /dev/null +++ b/code/asm/srai/Makefile @@ -0,0 +1 @@ +../build.mk \ No newline at end of file diff --git a/code/asm/srai/test.c b/code/asm/srai/test.c new file mode 100644 index 0000000..4253c82 --- /dev/null +++ b/code/asm/srai/test.c @@ -0,0 +1,9 @@ +// for signed int, shift right is assembled to srai + +// riscv64-unknown-elf-gcc -march=rv32ima -mabi=ilp32 -c -g test.c -o test.o +// riscv64-unknown-elf-objdump -S test.o +void foo() +{ + int i = 0x80000000; + i = i >> 4; +} \ No newline at end of file diff --git a/code/asm/srai/test.s b/code/asm/srai/test.s new file mode 100644 index 0000000..9390063 --- /dev/null +++ b/code/asm/srai/test.s @@ -0,0 +1,23 @@ +# Shift Right Arithmetic Immediate +# Format: +# SLLI RD, RS1, IMM +# Description: +# The immediate value determines the number of bits to shift. The contents of +# RS1 is shifted right that many bits and the result is placed in RD. The shift +# is “arithmetic”, i.e., the sign bit is repeatedly shifted in on the +# most-significant end. +# Comment: +# In C, for signed integer, >> is shift right with arithmetic. + + .text # Define beginning of text section + .global _start # Define entry _start + +_start: # Label, not really required +# li x6, 0x80 # x6 = 0b1000-0000 + li x6, 0x80000000 # x6 = 0b1000-0000-0000-0000-0000-0000-0000-0000 + srai x5, x6, 4 # x5 = x6 >> 3 + +stop: + j stop # Infinite loop to stop execution + + .end # End of file diff --git a/code/asm/srli/Makefile b/code/asm/srli/Makefile new file mode 120000 index 0000000..b421268 --- /dev/null +++ b/code/asm/srli/Makefile @@ -0,0 +1 @@ +../build.mk \ No newline at end of file diff --git a/code/asm/srli/test.c b/code/asm/srli/test.c new file mode 100644 index 0000000..a88e108 --- /dev/null +++ b/code/asm/srli/test.c @@ -0,0 +1,9 @@ +// for unsigned int, shift right is assembled to srli + +// riscv64-unknown-elf-gcc -march=rv32ima -mabi=ilp32 -c -g test.c -o test.o +// riscv64-unknown-elf-objdump -S test.o +void foo() +{ + unsigned int i = 0x80000000; + i = i >> 4; +} \ No newline at end of file diff --git a/code/asm/srli/test.s b/code/asm/srli/test.s new file mode 100644 index 0000000..fe8fb15 --- /dev/null +++ b/code/asm/srli/test.s @@ -0,0 +1,21 @@ +# Shift Right Logical Immediate +# Format: +# SRLI RD, RS1, IMM +# Description: +# The immediate value determines the number of bits to shift. The contents +# of RS1 is shifted right that many bits and the result is placed in RD. +# The bits shifted in on the most-significant end are filled with zero. +# For 32-bit machines, the shift amount must be within 0..31, 0 means no +# shifting is done. + + .text # Define beginning of text section + .global _start # Define entry _start + +_start: # Label, not really required + li x6, 0x80000000 # x6 = 0x80000000 + srli x5, x6, 3 # x5 = x6 >> 3 + +stop: + j stop # Infinite loop to stop execution + + .end # End of file diff --git a/code/asm/sub/Makefile b/code/asm/sub/Makefile new file mode 120000 index 0000000..b421268 --- /dev/null +++ b/code/asm/sub/Makefile @@ -0,0 +1 @@ +../build.mk \ No newline at end of file diff --git a/code/asm/sub/test.s b/code/asm/sub/test.s new file mode 100644 index 0000000..0a56a78 --- /dev/null +++ b/code/asm/sub/test.s @@ -0,0 +1,19 @@ +# Substract +# Format: +# SUB RD, RS1, RS2 +# Description: +# The contents of RS2 is subtracted from the contents of RS1 and the result +# is placed in RD. + + .text # Define beginning of text section + .global _start # Define entry _start + +_start: # Label, not really required + li x6, -1 # x6 = -1 + li x7, -2 # x7 = -2 + sub x5, x6, x7 # x5 = x6 - x7 + +stop: + j stop # Infinite loop to stop execution + + .end # End of file diff --git a/code/asm/subi/Makefile b/code/asm/subi/Makefile new file mode 120000 index 0000000..b421268 --- /dev/null +++ b/code/asm/subi/Makefile @@ -0,0 +1 @@ +../build.mk \ No newline at end of file diff --git a/code/asm/subi/test.s b/code/asm/subi/test.s new file mode 100644 index 0000000..4c217aa --- /dev/null +++ b/code/asm/subi/test.s @@ -0,0 +1,16 @@ +# Substract Immediate +# Description: +# There is no “subtract immediate” instruction because subtraction is +# equivalent to adding a negative value of immediate. + + .text # Define beginning of text section + .global _start # Define entry _start + +_start: # Label, not really required + li x6, 30 # x5 = 1 + addi x5, x6, -20 # x5 = x6 - 20 + +stop: + j stop # Infinite loop to stop execution + + .end # End of file diff --git a/code/os/00-bootstrap/Makefile b/code/os/00-bootstrap/Makefile new file mode 100644 index 0000000..ab35e5c --- /dev/null +++ b/code/os/00-bootstrap/Makefile @@ -0,0 +1,55 @@ +CROSS_COMPILE = riscv64-unknown-elf- +CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall + +QEMU = qemu-system-riscv32 +QFLAGS = -nographic -smp 1 -machine virt -bios none + +GDB = ${CROSS_COMPILE}gdb +CC = ${CROSS_COMPILE}gcc +OBJCOPY = ${CROSS_COMPILE}objcopy +OBJDUMP = ${CROSS_COMPILE}objdump + +SRCS_ASM = \ + start.S \ + +SRCS_C = \ + kernel.c \ + +OBJS = $(SRCS_ASM:.S=.o) +OBJS += $(SRCS_C:.c=.o) + +.DEFAULT_GOAL := all +all: os.elf + +# start.o must be the first in dependency! +os.elf: ${OBJS} + ${CC} $(CFLAGS) -Ttext=0x80000000 -o os.elf $^ + ${OBJCOPY} -O binary os.elf os.bin + +%.o : %.c + ${CC} ${CFLAGS} -c -o $@ $< + +%.o : %.S + ${CC} ${CFLAGS} -c -o $@ $< + +run: all + @${QEMU} -M ? | grep virt >/dev/null || exit + @echo "Press Ctrl-A and then X to exit QEMU" + @echo "------------------------------------" + @${QEMU} ${QFLAGS} -kernel os.elf + +.PHONY : debug +debug: all + @echo "Press Ctrl-C and then input 'quit' to exit GDB and QEMU" + @echo "-------------------------------------------------------" + @${QEMU} ${QFLAGS} -kernel os.elf -s -S & + @${GDB} os.elf -q -x ../gdbinit + +.PHONY : code +code: all + @${OBJDUMP} -S os.elf | less + +.PHONY : clean +clean: + rm -rf *.o *.bin *.elf + diff --git a/code/os/00-bootstrap/kernel.c b/code/os/00-bootstrap/kernel.c new file mode 100644 index 0000000..2cdfe38 --- /dev/null +++ b/code/os/00-bootstrap/kernel.c @@ -0,0 +1,5 @@ +void start_kernel(void) +{ + while (1) {}; // stop here! +} + diff --git a/code/os/00-bootstrap/platform.h b/code/os/00-bootstrap/platform.h new file mode 100644 index 0000000..2a78efa --- /dev/null +++ b/code/os/00-bootstrap/platform.h @@ -0,0 +1,15 @@ +#ifndef __PLATFORM_H__ +#define __PLATFORM_H__ + +/* + * QEMU RISC-V Virt machine with 16550a UART and VirtIO MMIO + */ + +/* + * maximum number of CPUs + * see https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h + * #define VIRT_CPUS_MAX 8 + */ +#define MAXNUM_CPU 8 + +#endif /* __PLATFORM_H__ */ diff --git a/code/os/00-bootstrap/start.S b/code/os/00-bootstrap/start.S new file mode 100644 index 0000000..eb37676 --- /dev/null +++ b/code/os/00-bootstrap/start.S @@ -0,0 +1,31 @@ +#include "platform.h" + + .equ STACK_SIZE, 8192 + + .global _start + + .text +_start: + # park harts with id != 0 + csrr t0, mhartid # read current hart id + mv tp, t0 # keep CPU's hartid in its tp for later usage. + bnez t0, park # if we're not on the hart 0 + # we park the hart + # Setup stacks, the stack grows from bottom to top, so we put the + # stack pointer to the very end of the stack range. + slli t0, t0, 10 # shift left the hart id by 1024 + la sp, stacks + STACK_SIZE # set the initial stack pointer + # to the end of the stack space + add sp, sp, t0 # move the current hart stack pointer + # to its place in the stack space + + j start_kernel # hart 0 jump to c + +park: + wfi + j park + +stacks: + .skip STACK_SIZE * MAXNUM_CPU # allocate space for the harts stacks + + .end # End of file diff --git a/code/os/01-helloRVOS/Makefile b/code/os/01-helloRVOS/Makefile new file mode 100644 index 0000000..c6fa85d --- /dev/null +++ b/code/os/01-helloRVOS/Makefile @@ -0,0 +1,56 @@ +CROSS_COMPILE = riscv64-unknown-elf- +CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall + +QEMU = qemu-system-riscv32 +QFLAGS = -nographic -smp 1 -machine virt -bios none + +GDB = ${CROSS_COMPILE}gdb +CC = ${CROSS_COMPILE}gcc +OBJCOPY = ${CROSS_COMPILE}objcopy +OBJDUMP = ${CROSS_COMPILE}objdump + +SRCS_ASM = \ + start.S \ + +SRCS_C = \ + kernel.c \ + uart.c \ + +OBJS = $(SRCS_ASM:.S=.o) +OBJS += $(SRCS_C:.c=.o) + +.DEFAULT_GOAL := all +all: os.elf + +# start.o must be the first in dependency! +os.elf: ${OBJS} + ${CC} $(CFLAGS) -Ttext=0x80000000 -o os.elf $^ + ${OBJCOPY} -O binary os.elf os.bin + +%.o : %.c + ${CC} ${CFLAGS} -c -o $@ $< + +%.o : %.S + ${CC} ${CFLAGS} -c -o $@ $< + +run: all + @${QEMU} -M ? | grep virt >/dev/null || exit + @echo "Press Ctrl-A and then X to exit QEMU" + @echo "------------------------------------" + @${QEMU} ${QFLAGS} -kernel os.elf + +.PHONY : debug +debug: all + @echo "Press Ctrl-C and then input 'quit' to exit GDB and QEMU" + @echo "-------------------------------------------------------" + @${QEMU} ${QFLAGS} -kernel os.elf -s -S & + @${GDB} os.elf -q -x ../gdbinit + +.PHONY : code +code: all + @${OBJDUMP} -S os.elf | less + +.PHONY : clean +clean: + rm -rf *.o *.bin *.elf + diff --git a/code/os/01-helloRVOS/kernel.c b/code/os/01-helloRVOS/kernel.c new file mode 100644 index 0000000..2218ede --- /dev/null +++ b/code/os/01-helloRVOS/kernel.c @@ -0,0 +1,11 @@ +extern void uart_init(void); +extern void uart_puts(char *s); + +void start_kernel(void) +{ + uart_init(); + uart_puts("Hello, RVOS!\n"); + + while (1) {}; // stop here! +} + diff --git a/code/os/01-helloRVOS/platform.h b/code/os/01-helloRVOS/platform.h new file mode 100644 index 0000000..c5367c6 --- /dev/null +++ b/code/os/01-helloRVOS/platform.h @@ -0,0 +1,29 @@ +#ifndef __PLATFORM_H__ +#define __PLATFORM_H__ + +/* + * QEMU RISC-V Virt machine with 16550a UART and VirtIO MMIO + */ + +/* + * maximum number of CPUs + * see https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h + * #define VIRT_CPUS_MAX 8 + */ +#define MAXNUM_CPU 8 + +/* + * MemoryMap + * see https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c, virt_memmap[] + * 0x00001000 -- boot ROM, provided by qemu + * 0x02000000 -- CLINT + * 0x0C000000 -- PLIC + * 0x10000000 -- UART0 + * 0x10001000 -- virtio disk + * 0x80000000 -- boot ROM jumps here in machine mode, where we load our kernel + */ + +/* This machine puts UART registers here in physical memory. */ +#define UART0 0x10000000L + +#endif /* __PLATFORM_H__ */ diff --git a/code/os/01-helloRVOS/start.S b/code/os/01-helloRVOS/start.S new file mode 100644 index 0000000..eb37676 --- /dev/null +++ b/code/os/01-helloRVOS/start.S @@ -0,0 +1,31 @@ +#include "platform.h" + + .equ STACK_SIZE, 8192 + + .global _start + + .text +_start: + # park harts with id != 0 + csrr t0, mhartid # read current hart id + mv tp, t0 # keep CPU's hartid in its tp for later usage. + bnez t0, park # if we're not on the hart 0 + # we park the hart + # Setup stacks, the stack grows from bottom to top, so we put the + # stack pointer to the very end of the stack range. + slli t0, t0, 10 # shift left the hart id by 1024 + la sp, stacks + STACK_SIZE # set the initial stack pointer + # to the end of the stack space + add sp, sp, t0 # move the current hart stack pointer + # to its place in the stack space + + j start_kernel # hart 0 jump to c + +park: + wfi + j park + +stacks: + .skip STACK_SIZE * MAXNUM_CPU # allocate space for the harts stacks + + .end # End of file diff --git a/code/os/01-helloRVOS/types.h b/code/os/01-helloRVOS/types.h new file mode 100644 index 0000000..e2a8b8e --- /dev/null +++ b/code/os/01-helloRVOS/types.h @@ -0,0 +1,9 @@ +#ifndef __TYPES_H__ +#define __TYPES_H__ + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long long uint64_t; + +#endif /* __TYPES_H__ */ \ No newline at end of file diff --git a/code/os/01-helloRVOS/uart.c b/code/os/01-helloRVOS/uart.c new file mode 100644 index 0000000..a4945a2 --- /dev/null +++ b/code/os/01-helloRVOS/uart.c @@ -0,0 +1,120 @@ +#include "types.h" +#include "platform.h" + +/* + * The UART control registers are memory-mapped at address UART0. + * This macro returns the address of one of the registers. + */ +#define UART_REG(reg) ((volatile uint8_t *)(UART0 + reg)) + +/* + * Reference + * [1]: TECHNICAL DATA ON 16550, http://byterunner.com/16550.html + */ + +/* + * UART control registers map. see [1] "PROGRAMMING TABLE" + * note some are reused by multiple functions + * 0 (write mode): THR/DLL + * 1 (write mode): IER/DLM + */ +#define RHR 0 // Receive Holding Register (read mode) +#define THR 0 // Transmit Holding Register (write mode) +#define DLL 0 // LSB of Divisor Latch (write mode) +#define IER 1 // Interrupt Enable Register (write mode) +#define DLM 1 // MSB of Divisor Latch (write mode) +#define FCR 2 // FIFO Control Register (write mode) +#define ISR 2 // Interrupt Status Register (read mode) +#define LCR 3 // Line Control Register +#define MCR 4 // Modem Control Register +#define LSR 5 // Line Status Register +#define MSR 6 // Modem Status Register +#define SPR 7 // ScratchPad Register + +/* + * POWER UP DEFAULTS + * IER = 0: TX/RX holding register interrupts are bith disabled + * ISR = 1: no interrupt penting + * LCR = 0 + * MCR = 0 + * LSR = 60 HEX + * MSR = BITS 0-3 = 0, BITS 4-7 = inputs + * FCR = 0 + * TX = High + * OP1 = High + * OP2 = High + * RTS = High + * DTR = High + * RXRDY = High + * TXRDY = Low + * INT = Low + */ + +/* + * LINE STATUS REGISTER (LSR) + * LSR BIT 0: + * 0 = no data in receive holding register or FIFO. + * 1 = data has been receive and saved in the receive holding register or FIFO. + * ...... + * LSR BIT 5: + * 0 = transmit holding register is full. 16550 will not accept any data for transmission. + * 1 = transmitter hold register (or FIFO) is empty. CPU can load the next character. + * ...... + */ +#define LSR_RX_READY (1 << 0) +#define LSR_TX_IDLE (1 << 5) + +#define uart_read_reg(reg) (*(UART_REG(reg))) +#define uart_write_reg(reg, v) (*(UART_REG(reg)) = (v)) + +void uart_init() +{ + /* disable interrupts. */ + uart_write_reg(IER, 0x00); + + /* + * Setting baud rate. Just a demo here if we care about the divisor, + * but for our purpose [QEMU-virt], this doesn't really do anything. + * + * Notice that the divisor register DLL (divisor latch least) and DLM (divisor + * latch most) have the same base address as the receiver/transmitter and the + * interrupt enable register. To change what the base address points to, we + * open the "divisor latch" by writing 1 into the Divisor Latch Access Bit + * (DLAB), which is bit index 7 of the Line Control Register (LCR). + * + * Regarding the baud rate value, see [1] "BAUD RATE GENERATOR PROGRAMMING TABLE". + * We use 38.4K when 1.8432 MHZ crystal, so the corresponding value is 3. + * And due to the divisor register is two bytes (16 bits), so we need to + * split the value of 3(0x0003) into two bytes, DLL stores the low byte, + * DLM stores the high byte. + */ + uint8_t lcr = uart_read_reg(LCR); + uart_write_reg(LCR, lcr | (1 << 7)); + uart_write_reg(DLL, 0x03); + uart_write_reg(DLM, 0x00); + + /* + * Continue setting the asynchronous data communication format. + * - number of the word length: 8 bits + * - number of stop bits:1 bit when word length is 8 bits + * - no parity + * - no break control + * - disabled baud latch + */ + lcr = 0; + uart_write_reg(LCR, lcr | (3 << 0)); +} + +int uart_putc(char ch) +{ + while ((uart_read_reg(LSR) & LSR_TX_IDLE) == 0); + return uart_write_reg(THR, ch); +} + +void uart_puts(char *s) +{ + while (*s) { + uart_putc(*s++); + } +} + diff --git a/code/os/02-memanagement/Makefile b/code/os/02-memanagement/Makefile new file mode 100644 index 0000000..7f89179 --- /dev/null +++ b/code/os/02-memanagement/Makefile @@ -0,0 +1,59 @@ +CROSS_COMPILE = riscv64-unknown-elf- +CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall + +QEMU = qemu-system-riscv32 +QFLAGS = -nographic -smp 1 -machine virt -bios none + +GDB = ${CROSS_COMPILE}gdb +CC = ${CROSS_COMPILE}gcc +OBJCOPY = ${CROSS_COMPILE}objcopy +OBJDUMP = ${CROSS_COMPILE}objdump + +SRCS_ASM = \ + start.S \ + mem.S \ + +SRCS_C = \ + kernel.c \ + uart.c \ + printf.c \ + page.c \ + +OBJS = $(SRCS_ASM:.S=.o) +OBJS += $(SRCS_C:.c=.o) + +.DEFAULT_GOAL := all +all: os.elf + +# start.o must be the first in dependency! +os.elf: ${OBJS} + ${CC} $(CFLAGS) -T os.ld -o os.elf $^ + ${OBJCOPY} -O binary os.elf os.bin + +%.o : %.c + ${CC} ${CFLAGS} -c -o $@ $< + +%.o : %.S + ${CC} ${CFLAGS} -c -o $@ $< + +run: all + @${QEMU} -M ? | grep virt >/dev/null || exit + @echo "Press Ctrl-A and then X to exit QEMU" + @echo "------------------------------------" + @${QEMU} ${QFLAGS} -kernel os.elf + +.PHONY : debug +debug: all + @echo "Press Ctrl-C and then input 'quit' to exit GDB and QEMU" + @echo "-------------------------------------------------------" + @${QEMU} ${QFLAGS} -kernel os.elf -s -S & + @${GDB} os.elf -q -x ../gdbinit + +.PHONY : code +code: all + @${OBJDUMP} -S os.elf | less + +.PHONY : clean +clean: + rm -rf *.o *.bin *.elf + diff --git a/code/os/02-memanagement/kernel.c b/code/os/02-memanagement/kernel.c new file mode 100644 index 0000000..1a99e4c --- /dev/null +++ b/code/os/02-memanagement/kernel.c @@ -0,0 +1,19 @@ +#include "os.h" + +/* + * Following functions SHOULD be called ONLY ONE time here, + * so just declared here ONCE and NOT included in file os.h. + */ +extern void uart_init(void); +extern void page_init(void); + +void start_kernel(void) +{ + uart_init(); + uart_puts("Hello, RVOS!\n"); + + page_init(); + + while (1) {}; // stop here! +} + diff --git a/code/os/02-memanagement/mem.S b/code/os/02-memanagement/mem.S new file mode 100644 index 0000000..93ba0d6 --- /dev/null +++ b/code/os/02-memanagement/mem.S @@ -0,0 +1,30 @@ +.section .rodata +.global HEAP_START +HEAP_START: .word _heap_start + +.global HEAP_SIZE +HEAP_SIZE: .word _heap_size + +.global TEXT_START +TEXT_START: .word _text_start + +.global TEXT_END +TEXT_END: .word _text_end + +.global DATA_START +DATA_START: .word _data_start + +.global DATA_END +DATA_END: .word _data_end + +.global RODATA_START +RODATA_START: .word _rodata_start + +.global RODATA_END +RODATA_END: .word _rodata_end + +.global BSS_START +BSS_START: .word _bss_start + +.global BSS_END +BSS_END: .word _bss_end diff --git a/code/os/02-memanagement/os.h b/code/os/02-memanagement/os.h new file mode 100644 index 0000000..1908af4 --- /dev/null +++ b/code/os/02-memanagement/os.h @@ -0,0 +1,22 @@ +#ifndef __OS_H__ +#define __OS_H__ + +#include "types.h" +#include "platform.h" + +#include +#include + +/* uart */ +extern int uart_putc(char ch); +extern void uart_puts(char *s); + +/* printf */ +extern int printf(const char* s, ...); +extern void panic(char *s); + +/* memory management */ +extern void *page_alloc(int npages); +extern void page_free(void *p); + +#endif /* __OS_H__ */ diff --git a/code/os/02-memanagement/os.ld b/code/os/02-memanagement/os.ld new file mode 100644 index 0000000..a75cd93 --- /dev/null +++ b/code/os/02-memanagement/os.ld @@ -0,0 +1,136 @@ +/* + * rvos.ld + * Linker script for outputting to RVOS + */ + +/* + * https://sourceware.org/binutils/docs/ld/Miscellaneous-Commands.html + * OUTPUT_ARCH command specifies a particular output machine architecture. + * "riscv" is the name of the architecture for both 64-bit and 32-bit + * RISC-V target. We will further refine this by using -march=rv32ima + * and -mabi=ilp32 when calling gcc. + */ +OUTPUT_ARCH( "riscv" ) + +/* + * https://sourceware.org/binutils/docs/ld/Entry-Point.html + * ENTRY command is used to set the "entry point", which is the first instruction + * to execute in a program. + * The argument of ENTRY command is a symbol name, here is "_start" which is + * defined in start.S. + */ +ENTRY( _start ) + +/* + * https://sourceware.org/binutils/docs/ld/MEMORY.html + * The MEMORY command describes the location and size of blocks of memory in + * the target. + * The syntax for MEMORY is: + * MEMORY + * { + * name [(attr)] : ORIGIN = origin, LENGTH = len + * ...... + * } + * Each line defines a memory region. + * Each memory region must have a distinct name within the MEMORY command. Here + * we only define one region named as "ram". + * The "attr" string is an optional list of attributes that specify whether to + * use a particular memory region for an input section which is not explicitly + * mapped in the linker script. Here we assign 'w' (writeable), 'x' (executable), + * and 'a' (allocatable). We use '!' to invert 'r' (read-only) and + * 'i' (initialized). + * The "ORIGIN" is used to set the start address of the memory region. Here we + * place it right at the beginning of 0x8000_0000 because this is where the + * QEMU-virt machine will start executing. + * Finally LENGTH = 128M tells the linker that we have 128 megabyte of RAM. + * The linker will double check this to make sure everything can fit. + */ +MEMORY +{ + ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M +} + +/* + * https://sourceware.org/binutils/docs/ld/SECTIONS.html + * The SECTIONS command tells the linker how to map input sections into output + * sections, and how to place the output sections in memory. + * The format of the SECTIONS command is: + * SECTIONS + * { + * sections-command + * sections-command + * ...... + * } + * + * Each sections-command may of be one of the following: + * (1) an ENTRY command + * (2) a symbol assignment + * (3) an output section description + * (4) an overlay description + * We here only demo (2) & (3). + * + * We use PROVIDE command to define symbols. + * https://sourceware.org/binutils/docs/ld/PROVIDE.html + * The PROVIDE keyword may be used to define a symbol. + * The syntax is PROVIDE(symbol = expression). + * Such symbols as "_text_start", "_text_end" ... will be used in mem.S. + * Notice the period '.' tells the linker to set symbol(e.g. _text_start) to + * the CURRENT location ('.' = current memory location). This current memory + * location moves as we add things. + */ +SECTIONS +{ + /* + * We are going to layout all text sections in .text output section, + * starting with .text. The asterisk("*") in front of the + * parentheses means to match the .text section of ANY object file. + */ + .text : { + PROVIDE(_text_start = .); + *(.text .text.*) + PROVIDE(_text_end = .); + } >ram + + .rodata : { + PROVIDE(_rodata_start = .); + *(.rodata .rodata.*) + PROVIDE(_rodata_end = .); + } >ram + + .data : { + /* + * . = ALIGN(4096) tells the linker to align the current memory + * location to 4096 bytes. This will insert padding bytes until + * current location becomes aligned on 4096-byte boundary. + * This is because our paging system's resolution is 4,096 bytes. + */ + . = ALIGN(4096); + PROVIDE(_data_start = .); + /* + * sdata and data are essentially the same thing. We do not need + * to distinguish sdata from data. + */ + *(.sdata .sdata.*) + *(.data .data.*) + PROVIDE(_data_end = .); + } >ram + + .bss :{ + /* + * https://sourceware.org/binutils/docs/ld/Input-Section-Common.html + * In most cases, common symbols in input files will be placed + * in the ‘.bss’ section in the output file. + */ + PROVIDE(_bss_start = .); + *(.sbss .sbss.*) + *(.bss .bss.*) + *(COMMON) + PROVIDE(_bss_end = .); + } >ram + + PROVIDE(_memory_start = ORIGIN(ram)); + PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram)); + + PROVIDE(_heap_start = _bss_end); + PROVIDE(_heap_size = _memory_end - _heap_start); +} diff --git a/code/os/02-memanagement/page.c b/code/os/02-memanagement/page.c new file mode 100644 index 0000000..addf097 --- /dev/null +++ b/code/os/02-memanagement/page.c @@ -0,0 +1,189 @@ +#include "os.h" + +/* + * Following global vars are defined in mem.S + */ +extern uint32_t TEXT_START; +extern uint32_t TEXT_END; +extern uint32_t DATA_START; +extern uint32_t DATA_END; +extern uint32_t RODATA_START; +extern uint32_t RODATA_END; +extern uint32_t BSS_START; +extern uint32_t BSS_END; +extern uint32_t HEAP_START; +extern uint32_t HEAP_SIZE; + +/* + * _alloc_start points to the actual start address of heap pool + * _alloc_end points to the actual end address of heap pool + * _num_pages holds the actual max number of pages we can allocate. + */ +static uint32_t _alloc_start = 0; +static uint32_t _alloc_end = 0; +static uint32_t _num_pages = 0; + +#define PAGE_SIZE 4096 +#define PAGE_ORDER 12 + +#define PAGE_TAKEN (uint8_t)(1 << 0) +#define PAGE_LAST (uint8_t)(1 << 1) + +/* + * Page Descriptor + * flags: + * - bit 0: flag if this page is taken(allocated) + * - bit 1: flag if this page is the last page of the memory block allocated + */ +struct Page { + uint8_t flags; +}; + +static inline void _clear(struct Page *page) +{ + page->flags = 0; +} + +static inline int _is_free(struct Page *page) +{ + if (page->flags & PAGE_TAKEN) { + return 0; + } else { + return 1; + } +} + +static inline void _set_flag(struct Page *page, uint8_t flags) +{ + page->flags |= flags; +} + +static inline int _is_last(struct Page *page) +{ + if (page->flags & PAGE_LAST) { + return 1; + } else { + return 0; + } +} + +/* + * align the address to the border of page(4K) + */ +static inline uint32_t _align_page(uint32_t address) +{ + uint32_t order = (1 << PAGE_ORDER) - 1; + return (address + order) & (~order); +} + +void page_init() +{ + /* + * We reserved 8 Page (8 x 4096) to hold the Page structures. + * It should be enough to manage at most 128 MB (8 x 4096 x 4096) + */ + _num_pages = (HEAP_SIZE / PAGE_SIZE) - 8; + printf("HEAP_START = %x, HEAP_SIZE = %x, num of pages = %d\n", HEAP_START, HEAP_SIZE, _num_pages); + + struct Page *page = (struct Page *)HEAP_START; + for (int i = 0; i < _num_pages; i++) { + _clear(page); + page++; + } + + _alloc_start = _align_page(HEAP_START + 8 * PAGE_SIZE); + _alloc_end = _alloc_start + (PAGE_SIZE * _num_pages); + + printf("TEXT: 0x%x -> 0x%x\n", TEXT_START, TEXT_END); + printf("RODATA: 0x%x -> 0x%x\n", RODATA_START, RODATA_END); + printf("DATA: 0x%x -> 0x%x\n", DATA_START, DATA_END); + printf("BSS: 0x%x -> 0x%x\n", BSS_START, BSS_END); + printf("HEAP: 0x%x -> 0x%x\n", _alloc_start, _alloc_end); +} + +/* + * Allocate a memory block which is composed of contiguous physical pages + * - npages: the number of PAGE_SIZE pages to allocate + */ +void *page_alloc(int npages) +{ + /* Note we are searching the page descriptor bitmaps. */ + int found = 0; + struct Page *page_i = (struct Page *)HEAP_START; + for (int i = 0; i < (_num_pages - npages); i++) { + if (_is_free(page_i)) { + found = 1; + /* + * meet a free page, continue to check if following + * (npages - 1) pages are also unallocated. + */ + struct Page *page_j = page_i; + for (int j = i; j < (i + npages); j++) { + if (!_is_free(page_j)) { + found = 0; + break; + } + page_j++; + } + /* + * get a memory block which is good enough for us, + * take housekeeping, then return the actual start + * address of the first page of this memory block + */ + if (found) { + struct Page *page_k = page_i; + for (int k = i; k < (i + npages); k++) { + _set_flag(page_k, PAGE_TAKEN); + page_k++; + } + page_k--; + _set_flag(page_k, PAGE_LAST); + return (void *)(_alloc_start + i * PAGE_SIZE); + } + } + page_i++; + } + return NULL; +} + +/* + * Free the memory block + * - p: start address of the memory block + */ +void page_free(void *p) +{ + /* + * Assert (TBD) if p is invalid + */ + if (!p || (uint32_t)p >= _alloc_end) { + return; + } + /* get the first page descriptor of this memory block */ + struct Page *page = (struct Page *)HEAP_START; + page += ((uint32_t)p - _alloc_start)/ PAGE_SIZE; + /* loop and clear all the page descriptors of the memory block */ + while (!_is_free(page)) { + if (_is_last(page)) { + _clear(page); + break; + } else { + _clear(page); + page++;; + } + } +} + +void page_test() +{ + void *p = page_alloc(2); + printf("p = 0x%x\n", p); + //page_free(p); + + void *p2 = page_alloc(7); + printf("p2 = 0x%x\n", p2); + page_free(p2); + + void *p3 = page_alloc(4); + printf("p3 = 0x%x\n", p3); +} + diff --git a/code/os/02-memanagement/platform.h b/code/os/02-memanagement/platform.h new file mode 100644 index 0000000..c5367c6 --- /dev/null +++ b/code/os/02-memanagement/platform.h @@ -0,0 +1,29 @@ +#ifndef __PLATFORM_H__ +#define __PLATFORM_H__ + +/* + * QEMU RISC-V Virt machine with 16550a UART and VirtIO MMIO + */ + +/* + * maximum number of CPUs + * see https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h + * #define VIRT_CPUS_MAX 8 + */ +#define MAXNUM_CPU 8 + +/* + * MemoryMap + * see https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c, virt_memmap[] + * 0x00001000 -- boot ROM, provided by qemu + * 0x02000000 -- CLINT + * 0x0C000000 -- PLIC + * 0x10000000 -- UART0 + * 0x10001000 -- virtio disk + * 0x80000000 -- boot ROM jumps here in machine mode, where we load our kernel + */ + +/* This machine puts UART registers here in physical memory. */ +#define UART0 0x10000000L + +#endif /* __PLATFORM_H__ */ diff --git a/code/os/02-memanagement/printf.c b/code/os/02-memanagement/printf.c new file mode 100644 index 0000000..93ace5b --- /dev/null +++ b/code/os/02-memanagement/printf.c @@ -0,0 +1,138 @@ +#include "os.h" + +/* + * ref: https://github.com/cccriscv/mini-riscv-os/blob/master/05-Preemptive/lib.c + */ + +static int _vsnprintf(char * out, size_t n, const char* s, va_list vl) +{ + int format = 0; + int longarg = 0; + size_t pos = 0; + for (; *s; s++) { + if (format) { + switch(*s) { + case 'l': { + longarg = 1; + break; + } + case 'p': { + longarg = 1; + if (out && pos < n) { + out[pos] = '0'; + } + pos++; + if (out && pos < n) { + out[pos] = 'x'; + } + pos++; + } + case 'x': { + long num = longarg ? va_arg(vl, long) : va_arg(vl, int); + int hexdigits = 2*(longarg ? sizeof(long) : sizeof(int))-1; + for(int i = hexdigits; i >= 0; i--) { + int d = (num >> (4*i)) & 0xF; + if (out && pos < n) { + out[pos] = (d < 10 ? '0'+d : 'a'+d-10); + } + pos++; + } + longarg = 0; + format = 0; + break; + } + case 'd': { + long num = longarg ? va_arg(vl, long) : va_arg(vl, int); + if (num < 0) { + num = -num; + if (out && pos < n) { + out[pos] = '-'; + } + pos++; + } + long digits = 1; + for (long nn = num; nn /= 10; digits++); + for (int i = digits-1; i >= 0; i--) { + if (out && pos + i < n) { + out[pos + i] = '0' + (num % 10); + } + num /= 10; + } + pos += digits; + longarg = 0; + format = 0; + break; + } + case 's': { + const char* s2 = va_arg(vl, const char*); + while (*s2) { + if (out && pos < n) { + out[pos] = *s2; + } + pos++; + s2++; + } + longarg = 0; + format = 0; + break; + } + case 'c': { + if (out && pos < n) { + out[pos] = (char)va_arg(vl,int); + } + pos++; + longarg = 0; + format = 0; + break; + } + default: + break; + } + } else if (*s == '%') { + format = 1; + } else { + if (out && pos < n) { + out[pos] = *s; + } + pos++; + } + } + if (out && pos < n) { + out[pos] = 0; + } else if (out && n) { + out[n-1] = 0; + } + return pos; +} + +static char out_buf[1000]; // buffer for _vprintf() + +static int _vprintf(const char* s, va_list vl) +{ + int res = _vsnprintf(NULL, -1, s, vl); + if (res+1 >= sizeof(out_buf)) { + uart_puts("error: output string size overflow\n"); + while(1) {} + } + _vsnprintf(out_buf, res + 1, s, vl); + uart_puts(out_buf); + return res; +} + +int printf(const char* s, ...) +{ + int res = 0; + va_list vl; + va_start(vl, s); + res = _vprintf(s, vl); + va_end(vl); + return res; +} + +void panic(char *s) +{ + printf("panic: "); + printf(s); + printf("\n"); + while(1){}; +} diff --git a/code/os/02-memanagement/start.S b/code/os/02-memanagement/start.S new file mode 100644 index 0000000..6747e7c --- /dev/null +++ b/code/os/02-memanagement/start.S @@ -0,0 +1,41 @@ +#include "platform.h" + + .equ STACK_SIZE, 8192 + + .global _start + + .text +_start: + # park harts with id != 0 + csrr t0, mhartid # read current hart id + mv tp, t0 # keep CPU's hartid in its tp for later usage. + bnez t0, park # if we're not on the hart 0 + # we park the hart + + # Set all bytes in the BSS section to zero. + la a0, _bss_start + la a1, _bss_end + bgeu a0, a1, 2f +1: + sw zero, (a0) + addi a0, a0, 4 + bltu a0, a1, 1b +2: + # Setup stacks, the stack grows from bottom to top, so we put the + # stack pointer to the very end of the stack range. + slli t0, t0, 10 # shift left the hart id by 1024 + la sp, stacks + STACK_SIZE # set the initial stack pointer + # to the end of the stack space + add sp, sp, t0 # move the current hart stack pointer + # to its place in the stack space + + j start_kernel # hart 0 jump to c + +park: + wfi + j park + +stacks: + .skip STACK_SIZE * MAXNUM_CPU # allocate space for the harts stacks + + .end # End of file diff --git a/code/os/02-memanagement/types.h b/code/os/02-memanagement/types.h new file mode 100644 index 0000000..e2a8b8e --- /dev/null +++ b/code/os/02-memanagement/types.h @@ -0,0 +1,9 @@ +#ifndef __TYPES_H__ +#define __TYPES_H__ + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long long uint64_t; + +#endif /* __TYPES_H__ */ \ No newline at end of file diff --git a/code/os/02-memanagement/uart.c b/code/os/02-memanagement/uart.c new file mode 100644 index 0000000..db80aeb --- /dev/null +++ b/code/os/02-memanagement/uart.c @@ -0,0 +1,119 @@ +#include "os.h" + +/* + * The UART control registers are memory-mapped at address UART0. + * This macro returns the address of one of the registers. + */ +#define UART_REG(reg) ((volatile uint8_t *)(UART0 + reg)) + +/* + * Reference + * [1]: TECHNICAL DATA ON 16550, http://byterunner.com/16550.html + */ + +/* + * UART control registers map. see [1] "PROGRAMMING TABLE" + * note some are reused by multiple functions + * 0 (write mode): THR/DLL + * 1 (write mode): IER/DLM + */ +#define RHR 0 // Receive Holding Register (read mode) +#define THR 0 // Transmit Holding Register (write mode) +#define DLL 0 // LSB of Divisor Latch (write mode) +#define IER 1 // Interrupt Enable Register (write mode) +#define DLM 1 // MSB of Divisor Latch (write mode) +#define FCR 2 // FIFO Control Register (write mode) +#define ISR 2 // Interrupt Status Register (read mode) +#define LCR 3 // Line Control Register +#define MCR 4 // Modem Control Register +#define LSR 5 // Line Status Register +#define MSR 6 // Modem Status Register +#define SPR 7 // ScratchPad Register + +/* + * POWER UP DEFAULTS + * IER = 0: TX/RX holding register interrupts are bith disabled + * ISR = 1: no interrupt penting + * LCR = 0 + * MCR = 0 + * LSR = 60 HEX + * MSR = BITS 0-3 = 0, BITS 4-7 = inputs + * FCR = 0 + * TX = High + * OP1 = High + * OP2 = High + * RTS = High + * DTR = High + * RXRDY = High + * TXRDY = Low + * INT = Low + */ + +/* + * LINE STATUS REGISTER (LSR) + * LSR BIT 0: + * 0 = no data in receive holding register or FIFO. + * 1 = data has been receive and saved in the receive holding register or FIFO. + * ...... + * LSR BIT 5: + * 0 = transmit holding register is full. 16550 will not accept any data for transmission. + * 1 = transmitter hold register (or FIFO) is empty. CPU can load the next character. + * ...... + */ +#define LSR_RX_READY (1 << 0) +#define LSR_TX_IDLE (1 << 5) + +#define uart_read_reg(reg) (*(UART_REG(reg))) +#define uart_write_reg(reg, v) (*(UART_REG(reg)) = (v)) + +void uart_init() +{ + /* disable interrupts. */ + uart_write_reg(IER, 0x00); + + /* + * Setting baud rate. Just a demo here if we care about the divisor, + * but for our purpose [QEMU-virt], this doesn't really do anything. + * + * Notice that the divisor register DLL (divisor latch least) and DLM (divisor + * latch most) have the same base address as the receiver/transmitter and the + * interrupt enable register. To change what the base address points to, we + * open the "divisor latch" by writing 1 into the Divisor Latch Access Bit + * (DLAB), which is bit index 7 of the Line Control Register (LCR). + * + * Regarding the baud rate value, see [1] "BAUD RATE GENERATOR PROGRAMMING TABLE". + * We use 38.4K when 1.8432 MHZ crystal, so the corresponding value is 3. + * And due to the divisor register is two bytes (16 bits), so we need to + * split the value of 3(0x0003) into two bytes, DLL stores the low byte, + * DLM stores the high byte. + */ + uint8_t lcr = uart_read_reg(LCR); + uart_write_reg(LCR, lcr | (1 << 7)); + uart_write_reg(DLL, 0x03); + uart_write_reg(DLM, 0x00); + + /* + * Continue setting the asynchronous data communication format. + * - number of the word length: 8 bits + * - number of stop bits:1 bit when word length is 8 bits + * - no parity + * - no break control + * - disabled baud latch + */ + lcr = 0; + uart_write_reg(LCR, lcr | (3 << 0)); +} + +int uart_putc(char ch) +{ + while ((uart_read_reg(LSR) & LSR_TX_IDLE) == 0); + return uart_write_reg(THR, ch); +} + +void uart_puts(char *s) +{ + while (*s) { + uart_putc(*s++); + } +} + diff --git a/code/os/03-contextswitch/Makefile b/code/os/03-contextswitch/Makefile new file mode 100644 index 0000000..073f35a --- /dev/null +++ b/code/os/03-contextswitch/Makefile @@ -0,0 +1,61 @@ +CROSS_COMPILE = riscv64-unknown-elf- +CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall + +QEMU = qemu-system-riscv32 +QFLAGS = -nographic -smp 1 -machine virt -bios none + +GDB = ${CROSS_COMPILE}gdb +CC = ${CROSS_COMPILE}gcc +OBJCOPY = ${CROSS_COMPILE}objcopy +OBJDUMP = ${CROSS_COMPILE}objdump + +SRCS_ASM = \ + start.S \ + mem.S \ + entry.S \ + +SRCS_C = \ + kernel.c \ + uart.c \ + printf.c \ + page.c \ + sched.c \ + +OBJS = $(SRCS_ASM:.S=.o) +OBJS += $(SRCS_C:.c=.o) + +.DEFAULT_GOAL := all +all: os.elf + +# start.o must be the first in dependency! +os.elf: ${OBJS} + ${CC} $(CFLAGS) -T os.ld -o os.elf $^ + ${OBJCOPY} -O binary os.elf os.bin + +%.o : %.c + ${CC} ${CFLAGS} -c -o $@ $< + +%.o : %.S + ${CC} ${CFLAGS} -c -o $@ $< + +run: all + @${QEMU} -M ? | grep virt >/dev/null || exit + @echo "Press Ctrl-A and then X to exit QEMU" + @echo "------------------------------------" + @${QEMU} ${QFLAGS} -kernel os.elf + +.PHONY : debug +debug: all + @echo "Press Ctrl-C and then input 'quit' to exit GDB and QEMU" + @echo "-------------------------------------------------------" + @${QEMU} ${QFLAGS} -kernel os.elf -s -S & + @${GDB} os.elf -q -x ../gdbinit + +.PHONY : code +code: all + @${OBJDUMP} -S os.elf | less + +.PHONY : clean +clean: + rm -rf *.o *.bin *.elf + diff --git a/code/os/03-contextswitch/entry.S b/code/os/03-contextswitch/entry.S new file mode 100644 index 0000000..658d9d1 --- /dev/null +++ b/code/os/03-contextswitch/entry.S @@ -0,0 +1,106 @@ +# save all General-Purpose(GP) registers to context +# struct context *base = &ctx_task; +# base->ra = ra; +# ...... +.macro reg_save base + sw ra, 0(\base) + sw sp, 4(\base) + sw gp, 8(\base) + sw tp, 12(\base) + sw t0, 16(\base) + sw t1, 20(\base) + sw t2, 24(\base) + sw s0, 28(\base) + sw s1, 32(\base) + sw a0, 36(\base) + sw a1, 40(\base) + sw a2, 44(\base) + sw a3, 48(\base) + sw a4, 52(\base) + sw a5, 56(\base) + sw a6, 60(\base) + sw a7, 64(\base) + sw s2, 68(\base) + sw s3, 72(\base) + sw s4, 76(\base) + sw s5, 80(\base) + sw s6, 84(\base) + sw s7, 88(\base) + sw s8, 92(\base) + sw s9, 96(\base) + sw s10, 100(\base) + sw s11, 104(\base) + sw t3, 108(\base) + sw t4, 112(\base) + sw t5, 116(\base) + sw t6, 120(\base) +.endm + +# restore all General-Purpose(GP) registers from the context +# struct context *base = &ctx_task; +# ra = base->ra; +# ...... +.macro reg_restore base + lw ra, 0(\base) + lw sp, 4(\base) + lw gp, 8(\base) + lw tp, 12(\base) + lw t0, 16(\base) + lw t1, 20(\base) + lw t2, 24(\base) + lw s0, 28(\base) + lw s1, 32(\base) + lw a0, 36(\base) + lw a1, 40(\base) + lw a2, 44(\base) + lw a3, 48(\base) + lw a4, 52(\base) + lw a5, 56(\base) + lw a6, 60(\base) + lw a7, 64(\base) + lw s2, 68(\base) + lw s3, 72(\base) + lw s4, 76(\base) + lw s5, 80(\base) + lw s6, 84(\base) + lw s7, 88(\base) + lw s8, 92(\base) + lw s9, 96(\base) + lw s10, 100(\base) + lw s11, 104(\base) + lw t3, 108(\base) + lw t4, 112(\base) + lw t5, 116(\base) + lw t6, 120(\base) +.endm + +# Something to note about save/restore: +# - We use mscratch to hold a pointer to context of previous task +# - We use t6 as the 'base' for reg_save/reg_restore, because it is the +# very bottom register (x31) and would not be overwritten during loading. + +.text + +# void switch_to(struct context *next); +# a0: pointer to the context of the next task +.globl switch_to +.align 4 +switch_to: + csrrw t6, mscratch, t6 # swap t6 and mscratch + beqz t6, 1f # Notice: previous task may be NULL + reg_save t6 # save context of prev task + +1: + # switch mscratch to point to the context of the next task + csrw mscratch, a0 + + # Restore all GP registers + # Use t6 to point to the context of the new task + mv t6, a0 + reg_restore t6 + + # Do actual context switching. + ret + +.end + diff --git a/code/os/03-contextswitch/kernel.c b/code/os/03-contextswitch/kernel.c new file mode 100644 index 0000000..e71f6c4 --- /dev/null +++ b/code/os/03-contextswitch/kernel.c @@ -0,0 +1,26 @@ +#include "os.h" + +/* + * Following functions SHOULD be called ONLY ONE time here, + * so just declared here ONCE and NOT included in file os.h. + */ +extern void uart_init(void); +extern void page_init(void); +extern void sched_init(void); +extern void schedule(void); + +void start_kernel(void) +{ + uart_init(); + uart_puts("Hello, RVOS!\n"); + + page_init(); + + sched_init(); + + schedule(); + + uart_puts("Would not go here!\n"); + while (1) {}; // stop here! +} + diff --git a/code/os/03-contextswitch/mem.S b/code/os/03-contextswitch/mem.S new file mode 100644 index 0000000..93ba0d6 --- /dev/null +++ b/code/os/03-contextswitch/mem.S @@ -0,0 +1,30 @@ +.section .rodata +.global HEAP_START +HEAP_START: .word _heap_start + +.global HEAP_SIZE +HEAP_SIZE: .word _heap_size + +.global TEXT_START +TEXT_START: .word _text_start + +.global TEXT_END +TEXT_END: .word _text_end + +.global DATA_START +DATA_START: .word _data_start + +.global DATA_END +DATA_END: .word _data_end + +.global RODATA_START +RODATA_START: .word _rodata_start + +.global RODATA_END +RODATA_END: .word _rodata_end + +.global BSS_START +BSS_START: .word _bss_start + +.global BSS_END +BSS_END: .word _bss_end diff --git a/code/os/03-contextswitch/os.h b/code/os/03-contextswitch/os.h new file mode 100644 index 0000000..474d868 --- /dev/null +++ b/code/os/03-contextswitch/os.h @@ -0,0 +1,61 @@ +#ifndef __OS_H__ +#define __OS_H__ + +#include "types.h" +#include "platform.h" + +#include +#include + +/* uart */ +extern int uart_putc(char ch); +extern void uart_puts(char *s); + +/* printf */ +extern int printf(const char* s, ...); +extern void panic(char *s); + +/* memory management */ +extern void *page_alloc(int npages); +extern void page_free(void *p); + +/* task management */ +struct context { + /* ignore x0 */ + reg_t ra; + reg_t sp; + reg_t gp; + reg_t tp; + reg_t t0; + reg_t t1; + reg_t t2; + reg_t s0; + reg_t s1; + reg_t a0; + reg_t a1; + reg_t a2; + reg_t a3; + reg_t a4; + reg_t a5; + reg_t a6; + reg_t a7; + reg_t s2; + reg_t s3; + reg_t s4; + reg_t s5; + reg_t s6; + reg_t s7; + reg_t s8; + reg_t s9; + reg_t s10; + reg_t s11; + reg_t t3; + reg_t t4; + reg_t t5; + reg_t t6; +}; + +extern int task_create(void (*task)(void)); +extern void task_delay(volatile int count); + +#endif /* __OS_H__ */ diff --git a/code/os/03-contextswitch/os.ld b/code/os/03-contextswitch/os.ld new file mode 100644 index 0000000..a75cd93 --- /dev/null +++ b/code/os/03-contextswitch/os.ld @@ -0,0 +1,136 @@ +/* + * rvos.ld + * Linker script for outputting to RVOS + */ + +/* + * https://sourceware.org/binutils/docs/ld/Miscellaneous-Commands.html + * OUTPUT_ARCH command specifies a particular output machine architecture. + * "riscv" is the name of the architecture for both 64-bit and 32-bit + * RISC-V target. We will further refine this by using -march=rv32ima + * and -mabi=ilp32 when calling gcc. + */ +OUTPUT_ARCH( "riscv" ) + +/* + * https://sourceware.org/binutils/docs/ld/Entry-Point.html + * ENTRY command is used to set the "entry point", which is the first instruction + * to execute in a program. + * The argument of ENTRY command is a symbol name, here is "_start" which is + * defined in start.S. + */ +ENTRY( _start ) + +/* + * https://sourceware.org/binutils/docs/ld/MEMORY.html + * The MEMORY command describes the location and size of blocks of memory in + * the target. + * The syntax for MEMORY is: + * MEMORY + * { + * name [(attr)] : ORIGIN = origin, LENGTH = len + * ...... + * } + * Each line defines a memory region. + * Each memory region must have a distinct name within the MEMORY command. Here + * we only define one region named as "ram". + * The "attr" string is an optional list of attributes that specify whether to + * use a particular memory region for an input section which is not explicitly + * mapped in the linker script. Here we assign 'w' (writeable), 'x' (executable), + * and 'a' (allocatable). We use '!' to invert 'r' (read-only) and + * 'i' (initialized). + * The "ORIGIN" is used to set the start address of the memory region. Here we + * place it right at the beginning of 0x8000_0000 because this is where the + * QEMU-virt machine will start executing. + * Finally LENGTH = 128M tells the linker that we have 128 megabyte of RAM. + * The linker will double check this to make sure everything can fit. + */ +MEMORY +{ + ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M +} + +/* + * https://sourceware.org/binutils/docs/ld/SECTIONS.html + * The SECTIONS command tells the linker how to map input sections into output + * sections, and how to place the output sections in memory. + * The format of the SECTIONS command is: + * SECTIONS + * { + * sections-command + * sections-command + * ...... + * } + * + * Each sections-command may of be one of the following: + * (1) an ENTRY command + * (2) a symbol assignment + * (3) an output section description + * (4) an overlay description + * We here only demo (2) & (3). + * + * We use PROVIDE command to define symbols. + * https://sourceware.org/binutils/docs/ld/PROVIDE.html + * The PROVIDE keyword may be used to define a symbol. + * The syntax is PROVIDE(symbol = expression). + * Such symbols as "_text_start", "_text_end" ... will be used in mem.S. + * Notice the period '.' tells the linker to set symbol(e.g. _text_start) to + * the CURRENT location ('.' = current memory location). This current memory + * location moves as we add things. + */ +SECTIONS +{ + /* + * We are going to layout all text sections in .text output section, + * starting with .text. The asterisk("*") in front of the + * parentheses means to match the .text section of ANY object file. + */ + .text : { + PROVIDE(_text_start = .); + *(.text .text.*) + PROVIDE(_text_end = .); + } >ram + + .rodata : { + PROVIDE(_rodata_start = .); + *(.rodata .rodata.*) + PROVIDE(_rodata_end = .); + } >ram + + .data : { + /* + * . = ALIGN(4096) tells the linker to align the current memory + * location to 4096 bytes. This will insert padding bytes until + * current location becomes aligned on 4096-byte boundary. + * This is because our paging system's resolution is 4,096 bytes. + */ + . = ALIGN(4096); + PROVIDE(_data_start = .); + /* + * sdata and data are essentially the same thing. We do not need + * to distinguish sdata from data. + */ + *(.sdata .sdata.*) + *(.data .data.*) + PROVIDE(_data_end = .); + } >ram + + .bss :{ + /* + * https://sourceware.org/binutils/docs/ld/Input-Section-Common.html + * In most cases, common symbols in input files will be placed + * in the ‘.bss’ section in the output file. + */ + PROVIDE(_bss_start = .); + *(.sbss .sbss.*) + *(.bss .bss.*) + *(COMMON) + PROVIDE(_bss_end = .); + } >ram + + PROVIDE(_memory_start = ORIGIN(ram)); + PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram)); + + PROVIDE(_heap_start = _bss_end); + PROVIDE(_heap_size = _memory_end - _heap_start); +} diff --git a/code/os/03-contextswitch/page.c b/code/os/03-contextswitch/page.c new file mode 100644 index 0000000..addf097 --- /dev/null +++ b/code/os/03-contextswitch/page.c @@ -0,0 +1,189 @@ +#include "os.h" + +/* + * Following global vars are defined in mem.S + */ +extern uint32_t TEXT_START; +extern uint32_t TEXT_END; +extern uint32_t DATA_START; +extern uint32_t DATA_END; +extern uint32_t RODATA_START; +extern uint32_t RODATA_END; +extern uint32_t BSS_START; +extern uint32_t BSS_END; +extern uint32_t HEAP_START; +extern uint32_t HEAP_SIZE; + +/* + * _alloc_start points to the actual start address of heap pool + * _alloc_end points to the actual end address of heap pool + * _num_pages holds the actual max number of pages we can allocate. + */ +static uint32_t _alloc_start = 0; +static uint32_t _alloc_end = 0; +static uint32_t _num_pages = 0; + +#define PAGE_SIZE 4096 +#define PAGE_ORDER 12 + +#define PAGE_TAKEN (uint8_t)(1 << 0) +#define PAGE_LAST (uint8_t)(1 << 1) + +/* + * Page Descriptor + * flags: + * - bit 0: flag if this page is taken(allocated) + * - bit 1: flag if this page is the last page of the memory block allocated + */ +struct Page { + uint8_t flags; +}; + +static inline void _clear(struct Page *page) +{ + page->flags = 0; +} + +static inline int _is_free(struct Page *page) +{ + if (page->flags & PAGE_TAKEN) { + return 0; + } else { + return 1; + } +} + +static inline void _set_flag(struct Page *page, uint8_t flags) +{ + page->flags |= flags; +} + +static inline int _is_last(struct Page *page) +{ + if (page->flags & PAGE_LAST) { + return 1; + } else { + return 0; + } +} + +/* + * align the address to the border of page(4K) + */ +static inline uint32_t _align_page(uint32_t address) +{ + uint32_t order = (1 << PAGE_ORDER) - 1; + return (address + order) & (~order); +} + +void page_init() +{ + /* + * We reserved 8 Page (8 x 4096) to hold the Page structures. + * It should be enough to manage at most 128 MB (8 x 4096 x 4096) + */ + _num_pages = (HEAP_SIZE / PAGE_SIZE) - 8; + printf("HEAP_START = %x, HEAP_SIZE = %x, num of pages = %d\n", HEAP_START, HEAP_SIZE, _num_pages); + + struct Page *page = (struct Page *)HEAP_START; + for (int i = 0; i < _num_pages; i++) { + _clear(page); + page++; + } + + _alloc_start = _align_page(HEAP_START + 8 * PAGE_SIZE); + _alloc_end = _alloc_start + (PAGE_SIZE * _num_pages); + + printf("TEXT: 0x%x -> 0x%x\n", TEXT_START, TEXT_END); + printf("RODATA: 0x%x -> 0x%x\n", RODATA_START, RODATA_END); + printf("DATA: 0x%x -> 0x%x\n", DATA_START, DATA_END); + printf("BSS: 0x%x -> 0x%x\n", BSS_START, BSS_END); + printf("HEAP: 0x%x -> 0x%x\n", _alloc_start, _alloc_end); +} + +/* + * Allocate a memory block which is composed of contiguous physical pages + * - npages: the number of PAGE_SIZE pages to allocate + */ +void *page_alloc(int npages) +{ + /* Note we are searching the page descriptor bitmaps. */ + int found = 0; + struct Page *page_i = (struct Page *)HEAP_START; + for (int i = 0; i < (_num_pages - npages); i++) { + if (_is_free(page_i)) { + found = 1; + /* + * meet a free page, continue to check if following + * (npages - 1) pages are also unallocated. + */ + struct Page *page_j = page_i; + for (int j = i; j < (i + npages); j++) { + if (!_is_free(page_j)) { + found = 0; + break; + } + page_j++; + } + /* + * get a memory block which is good enough for us, + * take housekeeping, then return the actual start + * address of the first page of this memory block + */ + if (found) { + struct Page *page_k = page_i; + for (int k = i; k < (i + npages); k++) { + _set_flag(page_k, PAGE_TAKEN); + page_k++; + } + page_k--; + _set_flag(page_k, PAGE_LAST); + return (void *)(_alloc_start + i * PAGE_SIZE); + } + } + page_i++; + } + return NULL; +} + +/* + * Free the memory block + * - p: start address of the memory block + */ +void page_free(void *p) +{ + /* + * Assert (TBD) if p is invalid + */ + if (!p || (uint32_t)p >= _alloc_end) { + return; + } + /* get the first page descriptor of this memory block */ + struct Page *page = (struct Page *)HEAP_START; + page += ((uint32_t)p - _alloc_start)/ PAGE_SIZE; + /* loop and clear all the page descriptors of the memory block */ + while (!_is_free(page)) { + if (_is_last(page)) { + _clear(page); + break; + } else { + _clear(page); + page++;; + } + } +} + +void page_test() +{ + void *p = page_alloc(2); + printf("p = 0x%x\n", p); + //page_free(p); + + void *p2 = page_alloc(7); + printf("p2 = 0x%x\n", p2); + page_free(p2); + + void *p3 = page_alloc(4); + printf("p3 = 0x%x\n", p3); +} + diff --git a/code/os/03-contextswitch/platform.h b/code/os/03-contextswitch/platform.h new file mode 100644 index 0000000..c5367c6 --- /dev/null +++ b/code/os/03-contextswitch/platform.h @@ -0,0 +1,29 @@ +#ifndef __PLATFORM_H__ +#define __PLATFORM_H__ + +/* + * QEMU RISC-V Virt machine with 16550a UART and VirtIO MMIO + */ + +/* + * maximum number of CPUs + * see https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h + * #define VIRT_CPUS_MAX 8 + */ +#define MAXNUM_CPU 8 + +/* + * MemoryMap + * see https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c, virt_memmap[] + * 0x00001000 -- boot ROM, provided by qemu + * 0x02000000 -- CLINT + * 0x0C000000 -- PLIC + * 0x10000000 -- UART0 + * 0x10001000 -- virtio disk + * 0x80000000 -- boot ROM jumps here in machine mode, where we load our kernel + */ + +/* This machine puts UART registers here in physical memory. */ +#define UART0 0x10000000L + +#endif /* __PLATFORM_H__ */ diff --git a/code/os/03-contextswitch/printf.c b/code/os/03-contextswitch/printf.c new file mode 100644 index 0000000..93ace5b --- /dev/null +++ b/code/os/03-contextswitch/printf.c @@ -0,0 +1,138 @@ +#include "os.h" + +/* + * ref: https://github.com/cccriscv/mini-riscv-os/blob/master/05-Preemptive/lib.c + */ + +static int _vsnprintf(char * out, size_t n, const char* s, va_list vl) +{ + int format = 0; + int longarg = 0; + size_t pos = 0; + for (; *s; s++) { + if (format) { + switch(*s) { + case 'l': { + longarg = 1; + break; + } + case 'p': { + longarg = 1; + if (out && pos < n) { + out[pos] = '0'; + } + pos++; + if (out && pos < n) { + out[pos] = 'x'; + } + pos++; + } + case 'x': { + long num = longarg ? va_arg(vl, long) : va_arg(vl, int); + int hexdigits = 2*(longarg ? sizeof(long) : sizeof(int))-1; + for(int i = hexdigits; i >= 0; i--) { + int d = (num >> (4*i)) & 0xF; + if (out && pos < n) { + out[pos] = (d < 10 ? '0'+d : 'a'+d-10); + } + pos++; + } + longarg = 0; + format = 0; + break; + } + case 'd': { + long num = longarg ? va_arg(vl, long) : va_arg(vl, int); + if (num < 0) { + num = -num; + if (out && pos < n) { + out[pos] = '-'; + } + pos++; + } + long digits = 1; + for (long nn = num; nn /= 10; digits++); + for (int i = digits-1; i >= 0; i--) { + if (out && pos + i < n) { + out[pos + i] = '0' + (num % 10); + } + num /= 10; + } + pos += digits; + longarg = 0; + format = 0; + break; + } + case 's': { + const char* s2 = va_arg(vl, const char*); + while (*s2) { + if (out && pos < n) { + out[pos] = *s2; + } + pos++; + s2++; + } + longarg = 0; + format = 0; + break; + } + case 'c': { + if (out && pos < n) { + out[pos] = (char)va_arg(vl,int); + } + pos++; + longarg = 0; + format = 0; + break; + } + default: + break; + } + } else if (*s == '%') { + format = 1; + } else { + if (out && pos < n) { + out[pos] = *s; + } + pos++; + } + } + if (out && pos < n) { + out[pos] = 0; + } else if (out && n) { + out[n-1] = 0; + } + return pos; +} + +static char out_buf[1000]; // buffer for _vprintf() + +static int _vprintf(const char* s, va_list vl) +{ + int res = _vsnprintf(NULL, -1, s, vl); + if (res+1 >= sizeof(out_buf)) { + uart_puts("error: output string size overflow\n"); + while(1) {} + } + _vsnprintf(out_buf, res + 1, s, vl); + uart_puts(out_buf); + return res; +} + +int printf(const char* s, ...) +{ + int res = 0; + va_list vl; + va_start(vl, s); + res = _vprintf(s, vl); + va_end(vl); + return res; +} + +void panic(char *s) +{ + printf("panic: "); + printf(s); + printf("\n"); + while(1){}; +} diff --git a/code/os/03-contextswitch/sched.c b/code/os/03-contextswitch/sched.c new file mode 100644 index 0000000..b2933d0 --- /dev/null +++ b/code/os/03-contextswitch/sched.c @@ -0,0 +1,48 @@ +#include "os.h" + +/* defined in entry.S */ +extern void switch_to(struct context *next); + +#define STACK_SIZE 1024 +uint8_t task_stack[STACK_SIZE]; +struct context ctx_task; + +static void w_mscratch(reg_t x) +{ + asm volatile("csrw mscratch, %0" : : "r" (x)); +} + +void user_task0(void); +void sched_init() +{ + w_mscratch(0); + + ctx_task.sp = (reg_t) &task_stack[STACK_SIZE - 1]; + ctx_task.ra = (reg_t) user_task0; +} + +void schedule() +{ + struct context *next = &ctx_task; + switch_to(next); +} + +/* + * a very rough implementaion, just to consume the cpu + */ +void task_delay(volatile int count) +{ + count *= 50000; + while (count--); +} + + +void user_task0(void) +{ + uart_puts("Task 0: Created!\n"); + while (1) { + uart_puts("Task 0: Running...\n"); + task_delay(1000); + } +} + diff --git a/code/os/03-contextswitch/start.S b/code/os/03-contextswitch/start.S new file mode 100644 index 0000000..6747e7c --- /dev/null +++ b/code/os/03-contextswitch/start.S @@ -0,0 +1,41 @@ +#include "platform.h" + + .equ STACK_SIZE, 8192 + + .global _start + + .text +_start: + # park harts with id != 0 + csrr t0, mhartid # read current hart id + mv tp, t0 # keep CPU's hartid in its tp for later usage. + bnez t0, park # if we're not on the hart 0 + # we park the hart + + # Set all bytes in the BSS section to zero. + la a0, _bss_start + la a1, _bss_end + bgeu a0, a1, 2f +1: + sw zero, (a0) + addi a0, a0, 4 + bltu a0, a1, 1b +2: + # Setup stacks, the stack grows from bottom to top, so we put the + # stack pointer to the very end of the stack range. + slli t0, t0, 10 # shift left the hart id by 1024 + la sp, stacks + STACK_SIZE # set the initial stack pointer + # to the end of the stack space + add sp, sp, t0 # move the current hart stack pointer + # to its place in the stack space + + j start_kernel # hart 0 jump to c + +park: + wfi + j park + +stacks: + .skip STACK_SIZE * MAXNUM_CPU # allocate space for the harts stacks + + .end # End of file diff --git a/code/os/03-contextswitch/types.h b/code/os/03-contextswitch/types.h new file mode 100644 index 0000000..5051639 --- /dev/null +++ b/code/os/03-contextswitch/types.h @@ -0,0 +1,14 @@ +#ifndef __TYPES_H__ +#define __TYPES_H__ + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long long uint64_t; + +/* + * RISCV32: register is 32bits width + */ +typedef uint32_t reg_t; + +#endif /* __TYPES_H__ */ diff --git a/code/os/03-contextswitch/uart.c b/code/os/03-contextswitch/uart.c new file mode 100644 index 0000000..db80aeb --- /dev/null +++ b/code/os/03-contextswitch/uart.c @@ -0,0 +1,119 @@ +#include "os.h" + +/* + * The UART control registers are memory-mapped at address UART0. + * This macro returns the address of one of the registers. + */ +#define UART_REG(reg) ((volatile uint8_t *)(UART0 + reg)) + +/* + * Reference + * [1]: TECHNICAL DATA ON 16550, http://byterunner.com/16550.html + */ + +/* + * UART control registers map. see [1] "PROGRAMMING TABLE" + * note some are reused by multiple functions + * 0 (write mode): THR/DLL + * 1 (write mode): IER/DLM + */ +#define RHR 0 // Receive Holding Register (read mode) +#define THR 0 // Transmit Holding Register (write mode) +#define DLL 0 // LSB of Divisor Latch (write mode) +#define IER 1 // Interrupt Enable Register (write mode) +#define DLM 1 // MSB of Divisor Latch (write mode) +#define FCR 2 // FIFO Control Register (write mode) +#define ISR 2 // Interrupt Status Register (read mode) +#define LCR 3 // Line Control Register +#define MCR 4 // Modem Control Register +#define LSR 5 // Line Status Register +#define MSR 6 // Modem Status Register +#define SPR 7 // ScratchPad Register + +/* + * POWER UP DEFAULTS + * IER = 0: TX/RX holding register interrupts are bith disabled + * ISR = 1: no interrupt penting + * LCR = 0 + * MCR = 0 + * LSR = 60 HEX + * MSR = BITS 0-3 = 0, BITS 4-7 = inputs + * FCR = 0 + * TX = High + * OP1 = High + * OP2 = High + * RTS = High + * DTR = High + * RXRDY = High + * TXRDY = Low + * INT = Low + */ + +/* + * LINE STATUS REGISTER (LSR) + * LSR BIT 0: + * 0 = no data in receive holding register or FIFO. + * 1 = data has been receive and saved in the receive holding register or FIFO. + * ...... + * LSR BIT 5: + * 0 = transmit holding register is full. 16550 will not accept any data for transmission. + * 1 = transmitter hold register (or FIFO) is empty. CPU can load the next character. + * ...... + */ +#define LSR_RX_READY (1 << 0) +#define LSR_TX_IDLE (1 << 5) + +#define uart_read_reg(reg) (*(UART_REG(reg))) +#define uart_write_reg(reg, v) (*(UART_REG(reg)) = (v)) + +void uart_init() +{ + /* disable interrupts. */ + uart_write_reg(IER, 0x00); + + /* + * Setting baud rate. Just a demo here if we care about the divisor, + * but for our purpose [QEMU-virt], this doesn't really do anything. + * + * Notice that the divisor register DLL (divisor latch least) and DLM (divisor + * latch most) have the same base address as the receiver/transmitter and the + * interrupt enable register. To change what the base address points to, we + * open the "divisor latch" by writing 1 into the Divisor Latch Access Bit + * (DLAB), which is bit index 7 of the Line Control Register (LCR). + * + * Regarding the baud rate value, see [1] "BAUD RATE GENERATOR PROGRAMMING TABLE". + * We use 38.4K when 1.8432 MHZ crystal, so the corresponding value is 3. + * And due to the divisor register is two bytes (16 bits), so we need to + * split the value of 3(0x0003) into two bytes, DLL stores the low byte, + * DLM stores the high byte. + */ + uint8_t lcr = uart_read_reg(LCR); + uart_write_reg(LCR, lcr | (1 << 7)); + uart_write_reg(DLL, 0x03); + uart_write_reg(DLM, 0x00); + + /* + * Continue setting the asynchronous data communication format. + * - number of the word length: 8 bits + * - number of stop bits:1 bit when word length is 8 bits + * - no parity + * - no break control + * - disabled baud latch + */ + lcr = 0; + uart_write_reg(LCR, lcr | (3 << 0)); +} + +int uart_putc(char ch) +{ + while ((uart_read_reg(LSR) & LSR_TX_IDLE) == 0); + return uart_write_reg(THR, ch); +} + +void uart_puts(char *s) +{ + while (*s) { + uart_putc(*s++); + } +} + diff --git a/code/os/04-multitask/Makefile b/code/os/04-multitask/Makefile new file mode 100644 index 0000000..f3abea5 --- /dev/null +++ b/code/os/04-multitask/Makefile @@ -0,0 +1,62 @@ +CROSS_COMPILE = riscv64-unknown-elf- +CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall + +QEMU = qemu-system-riscv32 +QFLAGS = -nographic -smp 1 -machine virt -bios none + +GDB = ${CROSS_COMPILE}gdb +CC = ${CROSS_COMPILE}gcc +OBJCOPY = ${CROSS_COMPILE}objcopy +OBJDUMP = ${CROSS_COMPILE}objdump + +SRCS_ASM = \ + start.S \ + mem.S \ + entry.S \ + +SRCS_C = \ + kernel.c \ + uart.c \ + printf.c \ + page.c \ + sched.c \ + user.c \ + +OBJS = $(SRCS_ASM:.S=.o) +OBJS += $(SRCS_C:.c=.o) + +.DEFAULT_GOAL := all +all: os.elf + +# start.o must be the first in dependency! +os.elf: ${OBJS} + ${CC} $(CFLAGS) -T os.ld -o os.elf $^ + ${OBJCOPY} -O binary os.elf os.bin + +%.o : %.c + ${CC} ${CFLAGS} -c -o $@ $< + +%.o : %.S + ${CC} ${CFLAGS} -c -o $@ $< + +run: all + @${QEMU} -M ? | grep virt >/dev/null || exit + @echo "Press Ctrl-A and then X to exit QEMU" + @echo "------------------------------------" + @${QEMU} ${QFLAGS} -kernel os.elf + +.PHONY : debug +debug: all + @echo "Press Ctrl-C and then input 'quit' to exit GDB and QEMU" + @echo "-------------------------------------------------------" + @${QEMU} ${QFLAGS} -kernel os.elf -s -S & + @${GDB} os.elf -q -x ../gdbinit + +.PHONY : code +code: all + @${OBJDUMP} -S os.elf | less + +.PHONY : clean +clean: + rm -rf *.o *.bin *.elf + diff --git a/code/os/04-multitask/entry.S b/code/os/04-multitask/entry.S new file mode 100644 index 0000000..658d9d1 --- /dev/null +++ b/code/os/04-multitask/entry.S @@ -0,0 +1,106 @@ +# save all General-Purpose(GP) registers to context +# struct context *base = &ctx_task; +# base->ra = ra; +# ...... +.macro reg_save base + sw ra, 0(\base) + sw sp, 4(\base) + sw gp, 8(\base) + sw tp, 12(\base) + sw t0, 16(\base) + sw t1, 20(\base) + sw t2, 24(\base) + sw s0, 28(\base) + sw s1, 32(\base) + sw a0, 36(\base) + sw a1, 40(\base) + sw a2, 44(\base) + sw a3, 48(\base) + sw a4, 52(\base) + sw a5, 56(\base) + sw a6, 60(\base) + sw a7, 64(\base) + sw s2, 68(\base) + sw s3, 72(\base) + sw s4, 76(\base) + sw s5, 80(\base) + sw s6, 84(\base) + sw s7, 88(\base) + sw s8, 92(\base) + sw s9, 96(\base) + sw s10, 100(\base) + sw s11, 104(\base) + sw t3, 108(\base) + sw t4, 112(\base) + sw t5, 116(\base) + sw t6, 120(\base) +.endm + +# restore all General-Purpose(GP) registers from the context +# struct context *base = &ctx_task; +# ra = base->ra; +# ...... +.macro reg_restore base + lw ra, 0(\base) + lw sp, 4(\base) + lw gp, 8(\base) + lw tp, 12(\base) + lw t0, 16(\base) + lw t1, 20(\base) + lw t2, 24(\base) + lw s0, 28(\base) + lw s1, 32(\base) + lw a0, 36(\base) + lw a1, 40(\base) + lw a2, 44(\base) + lw a3, 48(\base) + lw a4, 52(\base) + lw a5, 56(\base) + lw a6, 60(\base) + lw a7, 64(\base) + lw s2, 68(\base) + lw s3, 72(\base) + lw s4, 76(\base) + lw s5, 80(\base) + lw s6, 84(\base) + lw s7, 88(\base) + lw s8, 92(\base) + lw s9, 96(\base) + lw s10, 100(\base) + lw s11, 104(\base) + lw t3, 108(\base) + lw t4, 112(\base) + lw t5, 116(\base) + lw t6, 120(\base) +.endm + +# Something to note about save/restore: +# - We use mscratch to hold a pointer to context of previous task +# - We use t6 as the 'base' for reg_save/reg_restore, because it is the +# very bottom register (x31) and would not be overwritten during loading. + +.text + +# void switch_to(struct context *next); +# a0: pointer to the context of the next task +.globl switch_to +.align 4 +switch_to: + csrrw t6, mscratch, t6 # swap t6 and mscratch + beqz t6, 1f # Notice: previous task may be NULL + reg_save t6 # save context of prev task + +1: + # switch mscratch to point to the context of the next task + csrw mscratch, a0 + + # Restore all GP registers + # Use t6 to point to the context of the new task + mv t6, a0 + reg_restore t6 + + # Do actual context switching. + ret + +.end + diff --git a/code/os/04-multitask/kernel.c b/code/os/04-multitask/kernel.c new file mode 100644 index 0000000..24263c1 --- /dev/null +++ b/code/os/04-multitask/kernel.c @@ -0,0 +1,29 @@ +#include "os.h" + +/* + * Following functions SHOULD be called ONLY ONE time here, + * so just declared here ONCE and NOT included in file os.h. + */ +extern void uart_init(void); +extern void page_init(void); +extern void sched_init(void); +extern void schedule(void); +extern void os_main(void); + +void start_kernel(void) +{ + uart_init(); + uart_puts("Hello, RVOS!\n"); + + page_init(); + + sched_init(); + + os_main(); + + schedule(); + + uart_puts("Would not go here!\n"); + while (1) {}; // stop here! +} + diff --git a/code/os/04-multitask/mem.S b/code/os/04-multitask/mem.S new file mode 100644 index 0000000..93ba0d6 --- /dev/null +++ b/code/os/04-multitask/mem.S @@ -0,0 +1,30 @@ +.section .rodata +.global HEAP_START +HEAP_START: .word _heap_start + +.global HEAP_SIZE +HEAP_SIZE: .word _heap_size + +.global TEXT_START +TEXT_START: .word _text_start + +.global TEXT_END +TEXT_END: .word _text_end + +.global DATA_START +DATA_START: .word _data_start + +.global DATA_END +DATA_END: .word _data_end + +.global RODATA_START +RODATA_START: .word _rodata_start + +.global RODATA_END +RODATA_END: .word _rodata_end + +.global BSS_START +BSS_START: .word _bss_start + +.global BSS_END +BSS_END: .word _bss_end diff --git a/code/os/04-multitask/os.h b/code/os/04-multitask/os.h new file mode 100644 index 0000000..50817cf --- /dev/null +++ b/code/os/04-multitask/os.h @@ -0,0 +1,62 @@ +#ifndef __OS_H__ +#define __OS_H__ + +#include "types.h" +#include "platform.h" + +#include +#include + +/* uart */ +extern int uart_putc(char ch); +extern void uart_puts(char *s); + +/* printf */ +extern int printf(const char* s, ...); +extern void panic(char *s); + +/* memory management */ +extern void *page_alloc(int npages); +extern void page_free(void *p); + +/* task management */ +struct context { + /* ignore x0 */ + reg_t ra; + reg_t sp; + reg_t gp; + reg_t tp; + reg_t t0; + reg_t t1; + reg_t t2; + reg_t s0; + reg_t s1; + reg_t a0; + reg_t a1; + reg_t a2; + reg_t a3; + reg_t a4; + reg_t a5; + reg_t a6; + reg_t a7; + reg_t s2; + reg_t s3; + reg_t s4; + reg_t s5; + reg_t s6; + reg_t s7; + reg_t s8; + reg_t s9; + reg_t s10; + reg_t s11; + reg_t t3; + reg_t t4; + reg_t t5; + reg_t t6; +}; + +extern int task_create(void (*task)(void)); +extern void task_delay(volatile int count); +extern void task_yield(); + +#endif /* __OS_H__ */ diff --git a/code/os/04-multitask/os.ld b/code/os/04-multitask/os.ld new file mode 100644 index 0000000..a75cd93 --- /dev/null +++ b/code/os/04-multitask/os.ld @@ -0,0 +1,136 @@ +/* + * rvos.ld + * Linker script for outputting to RVOS + */ + +/* + * https://sourceware.org/binutils/docs/ld/Miscellaneous-Commands.html + * OUTPUT_ARCH command specifies a particular output machine architecture. + * "riscv" is the name of the architecture for both 64-bit and 32-bit + * RISC-V target. We will further refine this by using -march=rv32ima + * and -mabi=ilp32 when calling gcc. + */ +OUTPUT_ARCH( "riscv" ) + +/* + * https://sourceware.org/binutils/docs/ld/Entry-Point.html + * ENTRY command is used to set the "entry point", which is the first instruction + * to execute in a program. + * The argument of ENTRY command is a symbol name, here is "_start" which is + * defined in start.S. + */ +ENTRY( _start ) + +/* + * https://sourceware.org/binutils/docs/ld/MEMORY.html + * The MEMORY command describes the location and size of blocks of memory in + * the target. + * The syntax for MEMORY is: + * MEMORY + * { + * name [(attr)] : ORIGIN = origin, LENGTH = len + * ...... + * } + * Each line defines a memory region. + * Each memory region must have a distinct name within the MEMORY command. Here + * we only define one region named as "ram". + * The "attr" string is an optional list of attributes that specify whether to + * use a particular memory region for an input section which is not explicitly + * mapped in the linker script. Here we assign 'w' (writeable), 'x' (executable), + * and 'a' (allocatable). We use '!' to invert 'r' (read-only) and + * 'i' (initialized). + * The "ORIGIN" is used to set the start address of the memory region. Here we + * place it right at the beginning of 0x8000_0000 because this is where the + * QEMU-virt machine will start executing. + * Finally LENGTH = 128M tells the linker that we have 128 megabyte of RAM. + * The linker will double check this to make sure everything can fit. + */ +MEMORY +{ + ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M +} + +/* + * https://sourceware.org/binutils/docs/ld/SECTIONS.html + * The SECTIONS command tells the linker how to map input sections into output + * sections, and how to place the output sections in memory. + * The format of the SECTIONS command is: + * SECTIONS + * { + * sections-command + * sections-command + * ...... + * } + * + * Each sections-command may of be one of the following: + * (1) an ENTRY command + * (2) a symbol assignment + * (3) an output section description + * (4) an overlay description + * We here only demo (2) & (3). + * + * We use PROVIDE command to define symbols. + * https://sourceware.org/binutils/docs/ld/PROVIDE.html + * The PROVIDE keyword may be used to define a symbol. + * The syntax is PROVIDE(symbol = expression). + * Such symbols as "_text_start", "_text_end" ... will be used in mem.S. + * Notice the period '.' tells the linker to set symbol(e.g. _text_start) to + * the CURRENT location ('.' = current memory location). This current memory + * location moves as we add things. + */ +SECTIONS +{ + /* + * We are going to layout all text sections in .text output section, + * starting with .text. The asterisk("*") in front of the + * parentheses means to match the .text section of ANY object file. + */ + .text : { + PROVIDE(_text_start = .); + *(.text .text.*) + PROVIDE(_text_end = .); + } >ram + + .rodata : { + PROVIDE(_rodata_start = .); + *(.rodata .rodata.*) + PROVIDE(_rodata_end = .); + } >ram + + .data : { + /* + * . = ALIGN(4096) tells the linker to align the current memory + * location to 4096 bytes. This will insert padding bytes until + * current location becomes aligned on 4096-byte boundary. + * This is because our paging system's resolution is 4,096 bytes. + */ + . = ALIGN(4096); + PROVIDE(_data_start = .); + /* + * sdata and data are essentially the same thing. We do not need + * to distinguish sdata from data. + */ + *(.sdata .sdata.*) + *(.data .data.*) + PROVIDE(_data_end = .); + } >ram + + .bss :{ + /* + * https://sourceware.org/binutils/docs/ld/Input-Section-Common.html + * In most cases, common symbols in input files will be placed + * in the ‘.bss’ section in the output file. + */ + PROVIDE(_bss_start = .); + *(.sbss .sbss.*) + *(.bss .bss.*) + *(COMMON) + PROVIDE(_bss_end = .); + } >ram + + PROVIDE(_memory_start = ORIGIN(ram)); + PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram)); + + PROVIDE(_heap_start = _bss_end); + PROVIDE(_heap_size = _memory_end - _heap_start); +} diff --git a/code/os/04-multitask/page.c b/code/os/04-multitask/page.c new file mode 100644 index 0000000..addf097 --- /dev/null +++ b/code/os/04-multitask/page.c @@ -0,0 +1,189 @@ +#include "os.h" + +/* + * Following global vars are defined in mem.S + */ +extern uint32_t TEXT_START; +extern uint32_t TEXT_END; +extern uint32_t DATA_START; +extern uint32_t DATA_END; +extern uint32_t RODATA_START; +extern uint32_t RODATA_END; +extern uint32_t BSS_START; +extern uint32_t BSS_END; +extern uint32_t HEAP_START; +extern uint32_t HEAP_SIZE; + +/* + * _alloc_start points to the actual start address of heap pool + * _alloc_end points to the actual end address of heap pool + * _num_pages holds the actual max number of pages we can allocate. + */ +static uint32_t _alloc_start = 0; +static uint32_t _alloc_end = 0; +static uint32_t _num_pages = 0; + +#define PAGE_SIZE 4096 +#define PAGE_ORDER 12 + +#define PAGE_TAKEN (uint8_t)(1 << 0) +#define PAGE_LAST (uint8_t)(1 << 1) + +/* + * Page Descriptor + * flags: + * - bit 0: flag if this page is taken(allocated) + * - bit 1: flag if this page is the last page of the memory block allocated + */ +struct Page { + uint8_t flags; +}; + +static inline void _clear(struct Page *page) +{ + page->flags = 0; +} + +static inline int _is_free(struct Page *page) +{ + if (page->flags & PAGE_TAKEN) { + return 0; + } else { + return 1; + } +} + +static inline void _set_flag(struct Page *page, uint8_t flags) +{ + page->flags |= flags; +} + +static inline int _is_last(struct Page *page) +{ + if (page->flags & PAGE_LAST) { + return 1; + } else { + return 0; + } +} + +/* + * align the address to the border of page(4K) + */ +static inline uint32_t _align_page(uint32_t address) +{ + uint32_t order = (1 << PAGE_ORDER) - 1; + return (address + order) & (~order); +} + +void page_init() +{ + /* + * We reserved 8 Page (8 x 4096) to hold the Page structures. + * It should be enough to manage at most 128 MB (8 x 4096 x 4096) + */ + _num_pages = (HEAP_SIZE / PAGE_SIZE) - 8; + printf("HEAP_START = %x, HEAP_SIZE = %x, num of pages = %d\n", HEAP_START, HEAP_SIZE, _num_pages); + + struct Page *page = (struct Page *)HEAP_START; + for (int i = 0; i < _num_pages; i++) { + _clear(page); + page++; + } + + _alloc_start = _align_page(HEAP_START + 8 * PAGE_SIZE); + _alloc_end = _alloc_start + (PAGE_SIZE * _num_pages); + + printf("TEXT: 0x%x -> 0x%x\n", TEXT_START, TEXT_END); + printf("RODATA: 0x%x -> 0x%x\n", RODATA_START, RODATA_END); + printf("DATA: 0x%x -> 0x%x\n", DATA_START, DATA_END); + printf("BSS: 0x%x -> 0x%x\n", BSS_START, BSS_END); + printf("HEAP: 0x%x -> 0x%x\n", _alloc_start, _alloc_end); +} + +/* + * Allocate a memory block which is composed of contiguous physical pages + * - npages: the number of PAGE_SIZE pages to allocate + */ +void *page_alloc(int npages) +{ + /* Note we are searching the page descriptor bitmaps. */ + int found = 0; + struct Page *page_i = (struct Page *)HEAP_START; + for (int i = 0; i < (_num_pages - npages); i++) { + if (_is_free(page_i)) { + found = 1; + /* + * meet a free page, continue to check if following + * (npages - 1) pages are also unallocated. + */ + struct Page *page_j = page_i; + for (int j = i; j < (i + npages); j++) { + if (!_is_free(page_j)) { + found = 0; + break; + } + page_j++; + } + /* + * get a memory block which is good enough for us, + * take housekeeping, then return the actual start + * address of the first page of this memory block + */ + if (found) { + struct Page *page_k = page_i; + for (int k = i; k < (i + npages); k++) { + _set_flag(page_k, PAGE_TAKEN); + page_k++; + } + page_k--; + _set_flag(page_k, PAGE_LAST); + return (void *)(_alloc_start + i * PAGE_SIZE); + } + } + page_i++; + } + return NULL; +} + +/* + * Free the memory block + * - p: start address of the memory block + */ +void page_free(void *p) +{ + /* + * Assert (TBD) if p is invalid + */ + if (!p || (uint32_t)p >= _alloc_end) { + return; + } + /* get the first page descriptor of this memory block */ + struct Page *page = (struct Page *)HEAP_START; + page += ((uint32_t)p - _alloc_start)/ PAGE_SIZE; + /* loop and clear all the page descriptors of the memory block */ + while (!_is_free(page)) { + if (_is_last(page)) { + _clear(page); + break; + } else { + _clear(page); + page++;; + } + } +} + +void page_test() +{ + void *p = page_alloc(2); + printf("p = 0x%x\n", p); + //page_free(p); + + void *p2 = page_alloc(7); + printf("p2 = 0x%x\n", p2); + page_free(p2); + + void *p3 = page_alloc(4); + printf("p3 = 0x%x\n", p3); +} + diff --git a/code/os/04-multitask/platform.h b/code/os/04-multitask/platform.h new file mode 100644 index 0000000..c5367c6 --- /dev/null +++ b/code/os/04-multitask/platform.h @@ -0,0 +1,29 @@ +#ifndef __PLATFORM_H__ +#define __PLATFORM_H__ + +/* + * QEMU RISC-V Virt machine with 16550a UART and VirtIO MMIO + */ + +/* + * maximum number of CPUs + * see https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h + * #define VIRT_CPUS_MAX 8 + */ +#define MAXNUM_CPU 8 + +/* + * MemoryMap + * see https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c, virt_memmap[] + * 0x00001000 -- boot ROM, provided by qemu + * 0x02000000 -- CLINT + * 0x0C000000 -- PLIC + * 0x10000000 -- UART0 + * 0x10001000 -- virtio disk + * 0x80000000 -- boot ROM jumps here in machine mode, where we load our kernel + */ + +/* This machine puts UART registers here in physical memory. */ +#define UART0 0x10000000L + +#endif /* __PLATFORM_H__ */ diff --git a/code/os/04-multitask/printf.c b/code/os/04-multitask/printf.c new file mode 100644 index 0000000..93ace5b --- /dev/null +++ b/code/os/04-multitask/printf.c @@ -0,0 +1,138 @@ +#include "os.h" + +/* + * ref: https://github.com/cccriscv/mini-riscv-os/blob/master/05-Preemptive/lib.c + */ + +static int _vsnprintf(char * out, size_t n, const char* s, va_list vl) +{ + int format = 0; + int longarg = 0; + size_t pos = 0; + for (; *s; s++) { + if (format) { + switch(*s) { + case 'l': { + longarg = 1; + break; + } + case 'p': { + longarg = 1; + if (out && pos < n) { + out[pos] = '0'; + } + pos++; + if (out && pos < n) { + out[pos] = 'x'; + } + pos++; + } + case 'x': { + long num = longarg ? va_arg(vl, long) : va_arg(vl, int); + int hexdigits = 2*(longarg ? sizeof(long) : sizeof(int))-1; + for(int i = hexdigits; i >= 0; i--) { + int d = (num >> (4*i)) & 0xF; + if (out && pos < n) { + out[pos] = (d < 10 ? '0'+d : 'a'+d-10); + } + pos++; + } + longarg = 0; + format = 0; + break; + } + case 'd': { + long num = longarg ? va_arg(vl, long) : va_arg(vl, int); + if (num < 0) { + num = -num; + if (out && pos < n) { + out[pos] = '-'; + } + pos++; + } + long digits = 1; + for (long nn = num; nn /= 10; digits++); + for (int i = digits-1; i >= 0; i--) { + if (out && pos + i < n) { + out[pos + i] = '0' + (num % 10); + } + num /= 10; + } + pos += digits; + longarg = 0; + format = 0; + break; + } + case 's': { + const char* s2 = va_arg(vl, const char*); + while (*s2) { + if (out && pos < n) { + out[pos] = *s2; + } + pos++; + s2++; + } + longarg = 0; + format = 0; + break; + } + case 'c': { + if (out && pos < n) { + out[pos] = (char)va_arg(vl,int); + } + pos++; + longarg = 0; + format = 0; + break; + } + default: + break; + } + } else if (*s == '%') { + format = 1; + } else { + if (out && pos < n) { + out[pos] = *s; + } + pos++; + } + } + if (out && pos < n) { + out[pos] = 0; + } else if (out && n) { + out[n-1] = 0; + } + return pos; +} + +static char out_buf[1000]; // buffer for _vprintf() + +static int _vprintf(const char* s, va_list vl) +{ + int res = _vsnprintf(NULL, -1, s, vl); + if (res+1 >= sizeof(out_buf)) { + uart_puts("error: output string size overflow\n"); + while(1) {} + } + _vsnprintf(out_buf, res + 1, s, vl); + uart_puts(out_buf); + return res; +} + +int printf(const char* s, ...) +{ + int res = 0; + va_list vl; + va_start(vl, s); + res = _vprintf(s, vl); + va_end(vl); + return res; +} + +void panic(char *s) +{ + printf("panic: "); + printf(s); + printf("\n"); + while(1){}; +} diff --git a/code/os/04-multitask/sched.c b/code/os/04-multitask/sched.c new file mode 100644 index 0000000..85259df --- /dev/null +++ b/code/os/04-multitask/sched.c @@ -0,0 +1,81 @@ +#include "os.h" + +/* defined in entry.S */ +extern void switch_to(struct context *next); + +#define MAX_TASKS 10 +#define STACK_SIZE 1024 +uint8_t task_stack[MAX_TASKS][STACK_SIZE]; +struct context ctx_tasks[MAX_TASKS]; + +/* + * _top is used to mark the max available position of ctx_tasks + * _current is used to point to the context of current task + */ +static int _top = 0; +static int _current = -1; + +static void w_mscratch(reg_t x) +{ + asm volatile("csrw mscratch, %0" : : "r" (x)); +} + +void sched_init() +{ + w_mscratch(0); +} + +/* + * implment a simple cycle FIFO schedular + */ +void schedule() +{ + if (_top <= 0) { + panic("Num of task should be greater than zero!"); + return; + } + + _current = (_current + 1) % _top; + struct context *next = &(ctx_tasks[_current]); + switch_to(next); +} + +/* + * DESCRIPTION + * Create a task. + * - start_routin: task routune entry + * RETURN VALUE + * 0: success + * -1: if error occured + */ +int task_create(void (*start_routin)(void)) +{ + if (_top < MAX_TASKS) { + ctx_tasks[_top].sp = (reg_t) &task_stack[_top][STACK_SIZE - 1]; + ctx_tasks[_top].ra = (reg_t) start_routin; + _top++; + return 0; + } else { + return -1; + } +} + +/* + * DESCRIPTION + * task_yield() causes the calling task to relinquish the CPU and a new + * task gets to run. + */ +void task_yield() +{ + schedule(); +} + +/* + * a very rough implementaion, just to consume the cpu + */ +void task_delay(volatile int count) +{ + count *= 50000; + while (count--); +} + diff --git a/code/os/04-multitask/start.S b/code/os/04-multitask/start.S new file mode 100644 index 0000000..6747e7c --- /dev/null +++ b/code/os/04-multitask/start.S @@ -0,0 +1,41 @@ +#include "platform.h" + + .equ STACK_SIZE, 8192 + + .global _start + + .text +_start: + # park harts with id != 0 + csrr t0, mhartid # read current hart id + mv tp, t0 # keep CPU's hartid in its tp for later usage. + bnez t0, park # if we're not on the hart 0 + # we park the hart + + # Set all bytes in the BSS section to zero. + la a0, _bss_start + la a1, _bss_end + bgeu a0, a1, 2f +1: + sw zero, (a0) + addi a0, a0, 4 + bltu a0, a1, 1b +2: + # Setup stacks, the stack grows from bottom to top, so we put the + # stack pointer to the very end of the stack range. + slli t0, t0, 10 # shift left the hart id by 1024 + la sp, stacks + STACK_SIZE # set the initial stack pointer + # to the end of the stack space + add sp, sp, t0 # move the current hart stack pointer + # to its place in the stack space + + j start_kernel # hart 0 jump to c + +park: + wfi + j park + +stacks: + .skip STACK_SIZE * MAXNUM_CPU # allocate space for the harts stacks + + .end # End of file diff --git a/code/os/04-multitask/types.h b/code/os/04-multitask/types.h new file mode 100644 index 0000000..5051639 --- /dev/null +++ b/code/os/04-multitask/types.h @@ -0,0 +1,14 @@ +#ifndef __TYPES_H__ +#define __TYPES_H__ + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long long uint64_t; + +/* + * RISCV32: register is 32bits width + */ +typedef uint32_t reg_t; + +#endif /* __TYPES_H__ */ diff --git a/code/os/04-multitask/uart.c b/code/os/04-multitask/uart.c new file mode 100644 index 0000000..db80aeb --- /dev/null +++ b/code/os/04-multitask/uart.c @@ -0,0 +1,119 @@ +#include "os.h" + +/* + * The UART control registers are memory-mapped at address UART0. + * This macro returns the address of one of the registers. + */ +#define UART_REG(reg) ((volatile uint8_t *)(UART0 + reg)) + +/* + * Reference + * [1]: TECHNICAL DATA ON 16550, http://byterunner.com/16550.html + */ + +/* + * UART control registers map. see [1] "PROGRAMMING TABLE" + * note some are reused by multiple functions + * 0 (write mode): THR/DLL + * 1 (write mode): IER/DLM + */ +#define RHR 0 // Receive Holding Register (read mode) +#define THR 0 // Transmit Holding Register (write mode) +#define DLL 0 // LSB of Divisor Latch (write mode) +#define IER 1 // Interrupt Enable Register (write mode) +#define DLM 1 // MSB of Divisor Latch (write mode) +#define FCR 2 // FIFO Control Register (write mode) +#define ISR 2 // Interrupt Status Register (read mode) +#define LCR 3 // Line Control Register +#define MCR 4 // Modem Control Register +#define LSR 5 // Line Status Register +#define MSR 6 // Modem Status Register +#define SPR 7 // ScratchPad Register + +/* + * POWER UP DEFAULTS + * IER = 0: TX/RX holding register interrupts are bith disabled + * ISR = 1: no interrupt penting + * LCR = 0 + * MCR = 0 + * LSR = 60 HEX + * MSR = BITS 0-3 = 0, BITS 4-7 = inputs + * FCR = 0 + * TX = High + * OP1 = High + * OP2 = High + * RTS = High + * DTR = High + * RXRDY = High + * TXRDY = Low + * INT = Low + */ + +/* + * LINE STATUS REGISTER (LSR) + * LSR BIT 0: + * 0 = no data in receive holding register or FIFO. + * 1 = data has been receive and saved in the receive holding register or FIFO. + * ...... + * LSR BIT 5: + * 0 = transmit holding register is full. 16550 will not accept any data for transmission. + * 1 = transmitter hold register (or FIFO) is empty. CPU can load the next character. + * ...... + */ +#define LSR_RX_READY (1 << 0) +#define LSR_TX_IDLE (1 << 5) + +#define uart_read_reg(reg) (*(UART_REG(reg))) +#define uart_write_reg(reg, v) (*(UART_REG(reg)) = (v)) + +void uart_init() +{ + /* disable interrupts. */ + uart_write_reg(IER, 0x00); + + /* + * Setting baud rate. Just a demo here if we care about the divisor, + * but for our purpose [QEMU-virt], this doesn't really do anything. + * + * Notice that the divisor register DLL (divisor latch least) and DLM (divisor + * latch most) have the same base address as the receiver/transmitter and the + * interrupt enable register. To change what the base address points to, we + * open the "divisor latch" by writing 1 into the Divisor Latch Access Bit + * (DLAB), which is bit index 7 of the Line Control Register (LCR). + * + * Regarding the baud rate value, see [1] "BAUD RATE GENERATOR PROGRAMMING TABLE". + * We use 38.4K when 1.8432 MHZ crystal, so the corresponding value is 3. + * And due to the divisor register is two bytes (16 bits), so we need to + * split the value of 3(0x0003) into two bytes, DLL stores the low byte, + * DLM stores the high byte. + */ + uint8_t lcr = uart_read_reg(LCR); + uart_write_reg(LCR, lcr | (1 << 7)); + uart_write_reg(DLL, 0x03); + uart_write_reg(DLM, 0x00); + + /* + * Continue setting the asynchronous data communication format. + * - number of the word length: 8 bits + * - number of stop bits:1 bit when word length is 8 bits + * - no parity + * - no break control + * - disabled baud latch + */ + lcr = 0; + uart_write_reg(LCR, lcr | (3 << 0)); +} + +int uart_putc(char ch) +{ + while ((uart_read_reg(LSR) & LSR_TX_IDLE) == 0); + return uart_write_reg(THR, ch); +} + +void uart_puts(char *s) +{ + while (*s) { + uart_putc(*s++); + } +} + diff --git a/code/os/04-multitask/user.c b/code/os/04-multitask/user.c new file mode 100644 index 0000000..6c9484d --- /dev/null +++ b/code/os/04-multitask/user.c @@ -0,0 +1,31 @@ +#include "os.h" + +#define DELAY 1000 + +void user_task0(void) +{ + uart_puts("Task 0: Created!\n"); + while (1) { + uart_puts("Task 0: Running...\n"); + task_delay(DELAY); + task_yield(); + } +} + +void user_task1(void) +{ + uart_puts("Task 1: Created!\n"); + while (1) { + uart_puts("Task 1: Running...\n"); + task_delay(DELAY); + task_yield(); + } +} + +/* NOTICE: DON'T LOOP INFINITELY IN main() */ +void os_main(void) +{ + task_create(user_task0); + task_create(user_task1); +} + diff --git a/code/os/05-traps/Makefile b/code/os/05-traps/Makefile new file mode 100644 index 0000000..620ba67 --- /dev/null +++ b/code/os/05-traps/Makefile @@ -0,0 +1,63 @@ +CROSS_COMPILE = riscv64-unknown-elf- +CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall + +QEMU = qemu-system-riscv32 +QFLAGS = -nographic -smp 1 -machine virt -bios none + +GDB = ${CROSS_COMPILE}gdb +CC = ${CROSS_COMPILE}gcc +OBJCOPY = ${CROSS_COMPILE}objcopy +OBJDUMP = ${CROSS_COMPILE}objdump + +SRCS_ASM = \ + start.S \ + mem.S \ + entry.S \ + +SRCS_C = \ + kernel.c \ + uart.c \ + printf.c \ + page.c \ + sched.c \ + user.c \ + trap.c \ + +OBJS = $(SRCS_ASM:.S=.o) +OBJS += $(SRCS_C:.c=.o) + +.DEFAULT_GOAL := all +all: os.elf + +# start.o must be the first in dependency! +os.elf: ${OBJS} + ${CC} $(CFLAGS) -T os.ld -o os.elf $^ + ${OBJCOPY} -O binary os.elf os.bin + +%.o : %.c + ${CC} ${CFLAGS} -c -o $@ $< + +%.o : %.S + ${CC} ${CFLAGS} -c -o $@ $< + +run: all + @${QEMU} -M ? | grep virt >/dev/null || exit + @echo "Press Ctrl-A and then X to exit QEMU" + @echo "------------------------------------" + @${QEMU} ${QFLAGS} -kernel os.elf + +.PHONY : debug +debug: all + @echo "Press Ctrl-C and then input 'quit' to exit GDB and QEMU" + @echo "-------------------------------------------------------" + @${QEMU} ${QFLAGS} -kernel os.elf -s -S & + @${GDB} os.elf -q -x ../gdbinit + +.PHONY : code +code: all + @${OBJDUMP} -S os.elf | less + +.PHONY : clean +clean: + rm -rf *.o *.bin *.elf + diff --git a/code/os/05-traps/entry.S b/code/os/05-traps/entry.S new file mode 100644 index 0000000..ed0fca2 --- /dev/null +++ b/code/os/05-traps/entry.S @@ -0,0 +1,131 @@ +# save all General-Purpose(GP) registers to context +# struct context *base = &ctx_task; +# base->ra = ra; +# ...... +.macro reg_save base + sw ra, 0(\base) + sw sp, 4(\base) + sw gp, 8(\base) + sw tp, 12(\base) + sw t0, 16(\base) + sw t1, 20(\base) + sw t2, 24(\base) + sw s0, 28(\base) + sw s1, 32(\base) + sw a0, 36(\base) + sw a1, 40(\base) + sw a2, 44(\base) + sw a3, 48(\base) + sw a4, 52(\base) + sw a5, 56(\base) + sw a6, 60(\base) + sw a7, 64(\base) + sw s2, 68(\base) + sw s3, 72(\base) + sw s4, 76(\base) + sw s5, 80(\base) + sw s6, 84(\base) + sw s7, 88(\base) + sw s8, 92(\base) + sw s9, 96(\base) + sw s10, 100(\base) + sw s11, 104(\base) + sw t3, 108(\base) + sw t4, 112(\base) + sw t5, 116(\base) + sw t6, 120(\base) +.endm + +# restore all General-Purpose(GP) registers from the context +# struct context *base = &ctx_task; +# ra = base->ra; +# ...... +.macro reg_restore base + lw ra, 0(\base) + lw sp, 4(\base) + lw gp, 8(\base) + lw tp, 12(\base) + lw t0, 16(\base) + lw t1, 20(\base) + lw t2, 24(\base) + lw s0, 28(\base) + lw s1, 32(\base) + lw a0, 36(\base) + lw a1, 40(\base) + lw a2, 44(\base) + lw a3, 48(\base) + lw a4, 52(\base) + lw a5, 56(\base) + lw a6, 60(\base) + lw a7, 64(\base) + lw s2, 68(\base) + lw s3, 72(\base) + lw s4, 76(\base) + lw s5, 80(\base) + lw s6, 84(\base) + lw s7, 88(\base) + lw s8, 92(\base) + lw s9, 96(\base) + lw s10, 100(\base) + lw s11, 104(\base) + lw t3, 108(\base) + lw t4, 112(\base) + lw t5, 116(\base) + lw t6, 120(\base) +.endm + +# Something to note about save/restore: +# - We use mscratch to hold a pointer to context of previous task +# - We use t6 as the 'base' for reg_save/reg_restore, because it is the +# very bottom register (x31) and would not be overwritten during loading. + +.text + +# interrupts and exceptions while in machine mode come here. +.globl trap_vector +# the trap vector base address must always be aligned on a 4-byte boundary +.align 4 +trap_vector: + # save context(registers). + csrrw t6, mscratch, t6 # swap t6 and mscratch + reg_save t6 + csrw mscratch, t6 + + # call the C trap handler in trap.c + csrr a0, mepc + csrr a1, mcause + call trap_handler + + # trap_handler will return the return address via a0. + csrw mepc, a0 + + # restore context(registers). + csrr t6, mscratch + reg_restore t6 + + # return to whatever we were doing before trap. + mret + +# void switch_to(struct context *next); +# a0: pointer to the context of the next task +.globl switch_to +.align 4 +switch_to: + csrrw t6, mscratch, t6 # swap t6 and mscratch + beqz t6, 1f # Notice: previous task may be NULL + reg_save t6 # save context of prev task + +1: + # switch mscratch to point to the context of the next task + csrw mscratch, a0 + + # Restore all GP registers + # Use t6 to point to the context of the new task + mv t6, a0 + reg_restore t6 + + # Do actual context switching. + ret + +.end + diff --git a/code/os/05-traps/kernel.c b/code/os/05-traps/kernel.c new file mode 100644 index 0000000..0cf8532 --- /dev/null +++ b/code/os/05-traps/kernel.c @@ -0,0 +1,32 @@ +#include "os.h" + +/* + * Following functions SHOULD be called ONLY ONE time here, + * so just declared here ONCE and NOT included in file os.h. + */ +extern void uart_init(void); +extern void page_init(void); +extern void sched_init(void); +extern void schedule(void); +extern void os_main(void); +extern void trap_init(void); + +void start_kernel(void) +{ + uart_init(); + uart_puts("Hello, RVOS!\n"); + + page_init(); + + trap_init(); + + sched_init(); + + os_main(); + + schedule(); + + uart_puts("Would not go here!\n"); + while (1) {}; // stop here! +} + diff --git a/code/os/05-traps/mem.S b/code/os/05-traps/mem.S new file mode 100644 index 0000000..93ba0d6 --- /dev/null +++ b/code/os/05-traps/mem.S @@ -0,0 +1,30 @@ +.section .rodata +.global HEAP_START +HEAP_START: .word _heap_start + +.global HEAP_SIZE +HEAP_SIZE: .word _heap_size + +.global TEXT_START +TEXT_START: .word _text_start + +.global TEXT_END +TEXT_END: .word _text_end + +.global DATA_START +DATA_START: .word _data_start + +.global DATA_END +DATA_END: .word _data_end + +.global RODATA_START +RODATA_START: .word _rodata_start + +.global RODATA_END +RODATA_END: .word _rodata_end + +.global BSS_START +BSS_START: .word _bss_start + +.global BSS_END +BSS_END: .word _bss_end diff --git a/code/os/05-traps/os.h b/code/os/05-traps/os.h new file mode 100644 index 0000000..b8d114e --- /dev/null +++ b/code/os/05-traps/os.h @@ -0,0 +1,63 @@ +#ifndef __OS_H__ +#define __OS_H__ + +#include "types.h" +#include "riscv.h" +#include "platform.h" + +#include +#include + +/* uart */ +extern int uart_putc(char ch); +extern void uart_puts(char *s); + +/* printf */ +extern int printf(const char* s, ...); +extern void panic(char *s); + +/* memory management */ +extern void *page_alloc(int npages); +extern void page_free(void *p); + +/* task management */ +struct context { + /* ignore x0 */ + reg_t ra; + reg_t sp; + reg_t gp; + reg_t tp; + reg_t t0; + reg_t t1; + reg_t t2; + reg_t s0; + reg_t s1; + reg_t a0; + reg_t a1; + reg_t a2; + reg_t a3; + reg_t a4; + reg_t a5; + reg_t a6; + reg_t a7; + reg_t s2; + reg_t s3; + reg_t s4; + reg_t s5; + reg_t s6; + reg_t s7; + reg_t s8; + reg_t s9; + reg_t s10; + reg_t s11; + reg_t t3; + reg_t t4; + reg_t t5; + reg_t t6; +}; + +extern int task_create(void (*task)(void)); +extern void task_delay(volatile int count); +extern void task_yield(); + +#endif /* __OS_H__ */ diff --git a/code/os/05-traps/os.ld b/code/os/05-traps/os.ld new file mode 100644 index 0000000..a75cd93 --- /dev/null +++ b/code/os/05-traps/os.ld @@ -0,0 +1,136 @@ +/* + * rvos.ld + * Linker script for outputting to RVOS + */ + +/* + * https://sourceware.org/binutils/docs/ld/Miscellaneous-Commands.html + * OUTPUT_ARCH command specifies a particular output machine architecture. + * "riscv" is the name of the architecture for both 64-bit and 32-bit + * RISC-V target. We will further refine this by using -march=rv32ima + * and -mabi=ilp32 when calling gcc. + */ +OUTPUT_ARCH( "riscv" ) + +/* + * https://sourceware.org/binutils/docs/ld/Entry-Point.html + * ENTRY command is used to set the "entry point", which is the first instruction + * to execute in a program. + * The argument of ENTRY command is a symbol name, here is "_start" which is + * defined in start.S. + */ +ENTRY( _start ) + +/* + * https://sourceware.org/binutils/docs/ld/MEMORY.html + * The MEMORY command describes the location and size of blocks of memory in + * the target. + * The syntax for MEMORY is: + * MEMORY + * { + * name [(attr)] : ORIGIN = origin, LENGTH = len + * ...... + * } + * Each line defines a memory region. + * Each memory region must have a distinct name within the MEMORY command. Here + * we only define one region named as "ram". + * The "attr" string is an optional list of attributes that specify whether to + * use a particular memory region for an input section which is not explicitly + * mapped in the linker script. Here we assign 'w' (writeable), 'x' (executable), + * and 'a' (allocatable). We use '!' to invert 'r' (read-only) and + * 'i' (initialized). + * The "ORIGIN" is used to set the start address of the memory region. Here we + * place it right at the beginning of 0x8000_0000 because this is where the + * QEMU-virt machine will start executing. + * Finally LENGTH = 128M tells the linker that we have 128 megabyte of RAM. + * The linker will double check this to make sure everything can fit. + */ +MEMORY +{ + ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M +} + +/* + * https://sourceware.org/binutils/docs/ld/SECTIONS.html + * The SECTIONS command tells the linker how to map input sections into output + * sections, and how to place the output sections in memory. + * The format of the SECTIONS command is: + * SECTIONS + * { + * sections-command + * sections-command + * ...... + * } + * + * Each sections-command may of be one of the following: + * (1) an ENTRY command + * (2) a symbol assignment + * (3) an output section description + * (4) an overlay description + * We here only demo (2) & (3). + * + * We use PROVIDE command to define symbols. + * https://sourceware.org/binutils/docs/ld/PROVIDE.html + * The PROVIDE keyword may be used to define a symbol. + * The syntax is PROVIDE(symbol = expression). + * Such symbols as "_text_start", "_text_end" ... will be used in mem.S. + * Notice the period '.' tells the linker to set symbol(e.g. _text_start) to + * the CURRENT location ('.' = current memory location). This current memory + * location moves as we add things. + */ +SECTIONS +{ + /* + * We are going to layout all text sections in .text output section, + * starting with .text. The asterisk("*") in front of the + * parentheses means to match the .text section of ANY object file. + */ + .text : { + PROVIDE(_text_start = .); + *(.text .text.*) + PROVIDE(_text_end = .); + } >ram + + .rodata : { + PROVIDE(_rodata_start = .); + *(.rodata .rodata.*) + PROVIDE(_rodata_end = .); + } >ram + + .data : { + /* + * . = ALIGN(4096) tells the linker to align the current memory + * location to 4096 bytes. This will insert padding bytes until + * current location becomes aligned on 4096-byte boundary. + * This is because our paging system's resolution is 4,096 bytes. + */ + . = ALIGN(4096); + PROVIDE(_data_start = .); + /* + * sdata and data are essentially the same thing. We do not need + * to distinguish sdata from data. + */ + *(.sdata .sdata.*) + *(.data .data.*) + PROVIDE(_data_end = .); + } >ram + + .bss :{ + /* + * https://sourceware.org/binutils/docs/ld/Input-Section-Common.html + * In most cases, common symbols in input files will be placed + * in the ‘.bss’ section in the output file. + */ + PROVIDE(_bss_start = .); + *(.sbss .sbss.*) + *(.bss .bss.*) + *(COMMON) + PROVIDE(_bss_end = .); + } >ram + + PROVIDE(_memory_start = ORIGIN(ram)); + PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram)); + + PROVIDE(_heap_start = _bss_end); + PROVIDE(_heap_size = _memory_end - _heap_start); +} diff --git a/code/os/05-traps/page.c b/code/os/05-traps/page.c new file mode 100644 index 0000000..addf097 --- /dev/null +++ b/code/os/05-traps/page.c @@ -0,0 +1,189 @@ +#include "os.h" + +/* + * Following global vars are defined in mem.S + */ +extern uint32_t TEXT_START; +extern uint32_t TEXT_END; +extern uint32_t DATA_START; +extern uint32_t DATA_END; +extern uint32_t RODATA_START; +extern uint32_t RODATA_END; +extern uint32_t BSS_START; +extern uint32_t BSS_END; +extern uint32_t HEAP_START; +extern uint32_t HEAP_SIZE; + +/* + * _alloc_start points to the actual start address of heap pool + * _alloc_end points to the actual end address of heap pool + * _num_pages holds the actual max number of pages we can allocate. + */ +static uint32_t _alloc_start = 0; +static uint32_t _alloc_end = 0; +static uint32_t _num_pages = 0; + +#define PAGE_SIZE 4096 +#define PAGE_ORDER 12 + +#define PAGE_TAKEN (uint8_t)(1 << 0) +#define PAGE_LAST (uint8_t)(1 << 1) + +/* + * Page Descriptor + * flags: + * - bit 0: flag if this page is taken(allocated) + * - bit 1: flag if this page is the last page of the memory block allocated + */ +struct Page { + uint8_t flags; +}; + +static inline void _clear(struct Page *page) +{ + page->flags = 0; +} + +static inline int _is_free(struct Page *page) +{ + if (page->flags & PAGE_TAKEN) { + return 0; + } else { + return 1; + } +} + +static inline void _set_flag(struct Page *page, uint8_t flags) +{ + page->flags |= flags; +} + +static inline int _is_last(struct Page *page) +{ + if (page->flags & PAGE_LAST) { + return 1; + } else { + return 0; + } +} + +/* + * align the address to the border of page(4K) + */ +static inline uint32_t _align_page(uint32_t address) +{ + uint32_t order = (1 << PAGE_ORDER) - 1; + return (address + order) & (~order); +} + +void page_init() +{ + /* + * We reserved 8 Page (8 x 4096) to hold the Page structures. + * It should be enough to manage at most 128 MB (8 x 4096 x 4096) + */ + _num_pages = (HEAP_SIZE / PAGE_SIZE) - 8; + printf("HEAP_START = %x, HEAP_SIZE = %x, num of pages = %d\n", HEAP_START, HEAP_SIZE, _num_pages); + + struct Page *page = (struct Page *)HEAP_START; + for (int i = 0; i < _num_pages; i++) { + _clear(page); + page++; + } + + _alloc_start = _align_page(HEAP_START + 8 * PAGE_SIZE); + _alloc_end = _alloc_start + (PAGE_SIZE * _num_pages); + + printf("TEXT: 0x%x -> 0x%x\n", TEXT_START, TEXT_END); + printf("RODATA: 0x%x -> 0x%x\n", RODATA_START, RODATA_END); + printf("DATA: 0x%x -> 0x%x\n", DATA_START, DATA_END); + printf("BSS: 0x%x -> 0x%x\n", BSS_START, BSS_END); + printf("HEAP: 0x%x -> 0x%x\n", _alloc_start, _alloc_end); +} + +/* + * Allocate a memory block which is composed of contiguous physical pages + * - npages: the number of PAGE_SIZE pages to allocate + */ +void *page_alloc(int npages) +{ + /* Note we are searching the page descriptor bitmaps. */ + int found = 0; + struct Page *page_i = (struct Page *)HEAP_START; + for (int i = 0; i < (_num_pages - npages); i++) { + if (_is_free(page_i)) { + found = 1; + /* + * meet a free page, continue to check if following + * (npages - 1) pages are also unallocated. + */ + struct Page *page_j = page_i; + for (int j = i; j < (i + npages); j++) { + if (!_is_free(page_j)) { + found = 0; + break; + } + page_j++; + } + /* + * get a memory block which is good enough for us, + * take housekeeping, then return the actual start + * address of the first page of this memory block + */ + if (found) { + struct Page *page_k = page_i; + for (int k = i; k < (i + npages); k++) { + _set_flag(page_k, PAGE_TAKEN); + page_k++; + } + page_k--; + _set_flag(page_k, PAGE_LAST); + return (void *)(_alloc_start + i * PAGE_SIZE); + } + } + page_i++; + } + return NULL; +} + +/* + * Free the memory block + * - p: start address of the memory block + */ +void page_free(void *p) +{ + /* + * Assert (TBD) if p is invalid + */ + if (!p || (uint32_t)p >= _alloc_end) { + return; + } + /* get the first page descriptor of this memory block */ + struct Page *page = (struct Page *)HEAP_START; + page += ((uint32_t)p - _alloc_start)/ PAGE_SIZE; + /* loop and clear all the page descriptors of the memory block */ + while (!_is_free(page)) { + if (_is_last(page)) { + _clear(page); + break; + } else { + _clear(page); + page++;; + } + } +} + +void page_test() +{ + void *p = page_alloc(2); + printf("p = 0x%x\n", p); + //page_free(p); + + void *p2 = page_alloc(7); + printf("p2 = 0x%x\n", p2); + page_free(p2); + + void *p3 = page_alloc(4); + printf("p3 = 0x%x\n", p3); +} + diff --git a/code/os/05-traps/platform.h b/code/os/05-traps/platform.h new file mode 100644 index 0000000..c5367c6 --- /dev/null +++ b/code/os/05-traps/platform.h @@ -0,0 +1,29 @@ +#ifndef __PLATFORM_H__ +#define __PLATFORM_H__ + +/* + * QEMU RISC-V Virt machine with 16550a UART and VirtIO MMIO + */ + +/* + * maximum number of CPUs + * see https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h + * #define VIRT_CPUS_MAX 8 + */ +#define MAXNUM_CPU 8 + +/* + * MemoryMap + * see https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c, virt_memmap[] + * 0x00001000 -- boot ROM, provided by qemu + * 0x02000000 -- CLINT + * 0x0C000000 -- PLIC + * 0x10000000 -- UART0 + * 0x10001000 -- virtio disk + * 0x80000000 -- boot ROM jumps here in machine mode, where we load our kernel + */ + +/* This machine puts UART registers here in physical memory. */ +#define UART0 0x10000000L + +#endif /* __PLATFORM_H__ */ diff --git a/code/os/05-traps/printf.c b/code/os/05-traps/printf.c new file mode 100644 index 0000000..93ace5b --- /dev/null +++ b/code/os/05-traps/printf.c @@ -0,0 +1,138 @@ +#include "os.h" + +/* + * ref: https://github.com/cccriscv/mini-riscv-os/blob/master/05-Preemptive/lib.c + */ + +static int _vsnprintf(char * out, size_t n, const char* s, va_list vl) +{ + int format = 0; + int longarg = 0; + size_t pos = 0; + for (; *s; s++) { + if (format) { + switch(*s) { + case 'l': { + longarg = 1; + break; + } + case 'p': { + longarg = 1; + if (out && pos < n) { + out[pos] = '0'; + } + pos++; + if (out && pos < n) { + out[pos] = 'x'; + } + pos++; + } + case 'x': { + long num = longarg ? va_arg(vl, long) : va_arg(vl, int); + int hexdigits = 2*(longarg ? sizeof(long) : sizeof(int))-1; + for(int i = hexdigits; i >= 0; i--) { + int d = (num >> (4*i)) & 0xF; + if (out && pos < n) { + out[pos] = (d < 10 ? '0'+d : 'a'+d-10); + } + pos++; + } + longarg = 0; + format = 0; + break; + } + case 'd': { + long num = longarg ? va_arg(vl, long) : va_arg(vl, int); + if (num < 0) { + num = -num; + if (out && pos < n) { + out[pos] = '-'; + } + pos++; + } + long digits = 1; + for (long nn = num; nn /= 10; digits++); + for (int i = digits-1; i >= 0; i--) { + if (out && pos + i < n) { + out[pos + i] = '0' + (num % 10); + } + num /= 10; + } + pos += digits; + longarg = 0; + format = 0; + break; + } + case 's': { + const char* s2 = va_arg(vl, const char*); + while (*s2) { + if (out && pos < n) { + out[pos] = *s2; + } + pos++; + s2++; + } + longarg = 0; + format = 0; + break; + } + case 'c': { + if (out && pos < n) { + out[pos] = (char)va_arg(vl,int); + } + pos++; + longarg = 0; + format = 0; + break; + } + default: + break; + } + } else if (*s == '%') { + format = 1; + } else { + if (out && pos < n) { + out[pos] = *s; + } + pos++; + } + } + if (out && pos < n) { + out[pos] = 0; + } else if (out && n) { + out[n-1] = 0; + } + return pos; +} + +static char out_buf[1000]; // buffer for _vprintf() + +static int _vprintf(const char* s, va_list vl) +{ + int res = _vsnprintf(NULL, -1, s, vl); + if (res+1 >= sizeof(out_buf)) { + uart_puts("error: output string size overflow\n"); + while(1) {} + } + _vsnprintf(out_buf, res + 1, s, vl); + uart_puts(out_buf); + return res; +} + +int printf(const char* s, ...) +{ + int res = 0; + va_list vl; + va_start(vl, s); + res = _vprintf(s, vl); + va_end(vl); + return res; +} + +void panic(char *s) +{ + printf("panic: "); + printf(s); + printf("\n"); + while(1){}; +} diff --git a/code/os/05-traps/riscv.h b/code/os/05-traps/riscv.h new file mode 100644 index 0000000..e5bd491 --- /dev/null +++ b/code/os/05-traps/riscv.h @@ -0,0 +1,102 @@ +#ifndef __RISCV_H__ +#define __RISCV_H__ + +#include "types.h" + +/* + * ref: https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/riscv.h + */ + +static inline reg_t r_tp() +{ + reg_t x; + asm volatile("mv %0, tp" : "=r" (x) ); + return x; +} + +/* which hart (core) is this? */ +static inline reg_t r_mhartid() +{ + reg_t x; + asm volatile("csrr %0, mhartid" : "=r" (x) ); + return x; +} + +/* Machine Status Register, mstatus */ +#define MSTATUS_MPP (3 << 11) +#define MSTATUS_SPP (1 << 8) + +#define MSTATUS_MPIE (1 << 7) +#define MSTATUS_SPIE (1 << 5) +#define MSTATUS_UPIE (1 << 4) + +#define MSTATUS_MIE (1 << 3) +#define MSTATUS_SIE (1 << 1) +#define MSTATUS_UIE (1 << 0) + +static inline reg_t r_mstatus() +{ + reg_t x; + asm volatile("csrr %0, mstatus" : "=r" (x) ); + return x; +} + +static inline void w_mstatus(reg_t x) +{ + asm volatile("csrw mstatus, %0" : : "r" (x)); +} + +/* + * machine exception program counter, holds the + * instruction address to which a return from + * exception will go. + */ +static inline void w_mepc(reg_t x) +{ + asm volatile("csrw mepc, %0" : : "r" (x)); +} + +static inline reg_t r_mepc() +{ + reg_t x; + asm volatile("csrr %0, mepc" : "=r" (x)); + return x; +} + +/* Machine Scratch register, for early trap handler */ +static inline void w_mscratch(reg_t x) +{ + asm volatile("csrw mscratch, %0" : : "r" (x)); +} + +/* Machine-mode interrupt vector */ +static inline void w_mtvec(reg_t x) +{ + asm volatile("csrw mtvec, %0" : : "r" (x)); +} + +/* Machine-mode Interrupt Enable */ +#define MIE_MEIE (1 << 11) // external +#define MIE_MTIE (1 << 7) // timer +#define MIE_MSIE (1 << 3) // software + +static inline reg_t r_mie() +{ + reg_t x; + asm volatile("csrr %0, mie" : "=r" (x) ); + return x; +} + +static inline void w_mie(reg_t x) +{ + asm volatile("csrw mie, %0" : : "r" (x)); +} + +static inline reg_t r_mcause() +{ + reg_t x; + asm volatile("csrr %0, mcause" : "=r" (x) ); + return x; +} + +#endif /* __RISCV_H__ */ diff --git a/code/os/05-traps/sched.c b/code/os/05-traps/sched.c new file mode 100644 index 0000000..12a9995 --- /dev/null +++ b/code/os/05-traps/sched.c @@ -0,0 +1,76 @@ +#include "os.h" + +/* defined in entry.S */ +extern void switch_to(struct context *next); + +#define MAX_TASKS 10 +#define STACK_SIZE 1024 +uint8_t task_stack[MAX_TASKS][STACK_SIZE]; +struct context ctx_tasks[MAX_TASKS]; + +/* + * _top is used to mark the max available position of ctx_tasks + * _current is used to point to the context of current task + */ +static int _top = 0; +static int _current = -1; + +void sched_init() +{ + w_mscratch(0); +} + +/* + * implment a simple cycle FIFO schedular + */ +void schedule() +{ + if (_top <= 0) { + panic("Num of task should be greater than zero!"); + return; + } + + _current = (_current + 1) % _top; + struct context *next = &(ctx_tasks[_current]); + switch_to(next); +} + +/* + * DESCRIPTION + * Create a task. + * - start_routin: task routune entry + * RETURN VALUE + * 0: success + * -1: if error occured + */ +int task_create(void (*start_routin)(void)) +{ + if (_top < MAX_TASKS) { + ctx_tasks[_top].sp = (reg_t) &task_stack[_top][STACK_SIZE - 1]; + ctx_tasks[_top].ra = (reg_t) start_routin; + _top++; + return 0; + } else { + return -1; + } +} + +/* + * DESCRIPTION + * task_yield() causes the calling task to relinquish the CPU and a new + * task gets to run. + */ +void task_yield() +{ + schedule(); +} + +/* + * a very rough implementaion, just to consume the cpu + */ +void task_delay(volatile int count) +{ + count *= 50000; + while (count--); +} + diff --git a/code/os/05-traps/start.S b/code/os/05-traps/start.S new file mode 100644 index 0000000..6747e7c --- /dev/null +++ b/code/os/05-traps/start.S @@ -0,0 +1,41 @@ +#include "platform.h" + + .equ STACK_SIZE, 8192 + + .global _start + + .text +_start: + # park harts with id != 0 + csrr t0, mhartid # read current hart id + mv tp, t0 # keep CPU's hartid in its tp for later usage. + bnez t0, park # if we're not on the hart 0 + # we park the hart + + # Set all bytes in the BSS section to zero. + la a0, _bss_start + la a1, _bss_end + bgeu a0, a1, 2f +1: + sw zero, (a0) + addi a0, a0, 4 + bltu a0, a1, 1b +2: + # Setup stacks, the stack grows from bottom to top, so we put the + # stack pointer to the very end of the stack range. + slli t0, t0, 10 # shift left the hart id by 1024 + la sp, stacks + STACK_SIZE # set the initial stack pointer + # to the end of the stack space + add sp, sp, t0 # move the current hart stack pointer + # to its place in the stack space + + j start_kernel # hart 0 jump to c + +park: + wfi + j park + +stacks: + .skip STACK_SIZE * MAXNUM_CPU # allocate space for the harts stacks + + .end # End of file diff --git a/code/os/05-traps/trap.c b/code/os/05-traps/trap.c new file mode 100644 index 0000000..46f603d --- /dev/null +++ b/code/os/05-traps/trap.c @@ -0,0 +1,60 @@ +#include "os.h" + +extern void trap_vector(void); + +void trap_init() +{ + /* + * set the trap-vector base-address for machine-mode + */ + w_mtvec((reg_t)trap_vector); +} + +reg_t trap_handler(reg_t epc, reg_t cause) +{ + reg_t return_pc = epc; + reg_t cause_code = cause & 0xfff; + + if (cause & 0x80000000) { + /* Asynchronous trap - interrupt */ + switch (cause_code) { + case 3: + uart_puts("software interruption!\n"); + break; + case 7: + uart_puts("timer interruption!\n"); + break; + case 11: + uart_puts("external interruption!\n"); + break; + default: + uart_puts("unknown async exception!\n"); + break; + } + } else { + /* Synchronous trap - exception */ + printf("Sync exceptions!, code = %d\n", cause_code); + panic("OOPS! What can I do!"); + //return_pc += 4; + } + + return return_pc; +} + +void trap_test() +{ + /* + * Synchronous exception code = 7 + * Store/AMO access fault + */ + *(int *)0x00000000 = 100; + + /* + * Synchronous exception code = 5 + * Load access fault + */ + //int a = *(int *)0x00000000; + + uart_puts("Yeah! I'm return back from trap!\n"); +} + diff --git a/code/os/05-traps/types.h b/code/os/05-traps/types.h new file mode 100644 index 0000000..5051639 --- /dev/null +++ b/code/os/05-traps/types.h @@ -0,0 +1,14 @@ +#ifndef __TYPES_H__ +#define __TYPES_H__ + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long long uint64_t; + +/* + * RISCV32: register is 32bits width + */ +typedef uint32_t reg_t; + +#endif /* __TYPES_H__ */ diff --git a/code/os/05-traps/uart.c b/code/os/05-traps/uart.c new file mode 100644 index 0000000..db80aeb --- /dev/null +++ b/code/os/05-traps/uart.c @@ -0,0 +1,119 @@ +#include "os.h" + +/* + * The UART control registers are memory-mapped at address UART0. + * This macro returns the address of one of the registers. + */ +#define UART_REG(reg) ((volatile uint8_t *)(UART0 + reg)) + +/* + * Reference + * [1]: TECHNICAL DATA ON 16550, http://byterunner.com/16550.html + */ + +/* + * UART control registers map. see [1] "PROGRAMMING TABLE" + * note some are reused by multiple functions + * 0 (write mode): THR/DLL + * 1 (write mode): IER/DLM + */ +#define RHR 0 // Receive Holding Register (read mode) +#define THR 0 // Transmit Holding Register (write mode) +#define DLL 0 // LSB of Divisor Latch (write mode) +#define IER 1 // Interrupt Enable Register (write mode) +#define DLM 1 // MSB of Divisor Latch (write mode) +#define FCR 2 // FIFO Control Register (write mode) +#define ISR 2 // Interrupt Status Register (read mode) +#define LCR 3 // Line Control Register +#define MCR 4 // Modem Control Register +#define LSR 5 // Line Status Register +#define MSR 6 // Modem Status Register +#define SPR 7 // ScratchPad Register + +/* + * POWER UP DEFAULTS + * IER = 0: TX/RX holding register interrupts are bith disabled + * ISR = 1: no interrupt penting + * LCR = 0 + * MCR = 0 + * LSR = 60 HEX + * MSR = BITS 0-3 = 0, BITS 4-7 = inputs + * FCR = 0 + * TX = High + * OP1 = High + * OP2 = High + * RTS = High + * DTR = High + * RXRDY = High + * TXRDY = Low + * INT = Low + */ + +/* + * LINE STATUS REGISTER (LSR) + * LSR BIT 0: + * 0 = no data in receive holding register or FIFO. + * 1 = data has been receive and saved in the receive holding register or FIFO. + * ...... + * LSR BIT 5: + * 0 = transmit holding register is full. 16550 will not accept any data for transmission. + * 1 = transmitter hold register (or FIFO) is empty. CPU can load the next character. + * ...... + */ +#define LSR_RX_READY (1 << 0) +#define LSR_TX_IDLE (1 << 5) + +#define uart_read_reg(reg) (*(UART_REG(reg))) +#define uart_write_reg(reg, v) (*(UART_REG(reg)) = (v)) + +void uart_init() +{ + /* disable interrupts. */ + uart_write_reg(IER, 0x00); + + /* + * Setting baud rate. Just a demo here if we care about the divisor, + * but for our purpose [QEMU-virt], this doesn't really do anything. + * + * Notice that the divisor register DLL (divisor latch least) and DLM (divisor + * latch most) have the same base address as the receiver/transmitter and the + * interrupt enable register. To change what the base address points to, we + * open the "divisor latch" by writing 1 into the Divisor Latch Access Bit + * (DLAB), which is bit index 7 of the Line Control Register (LCR). + * + * Regarding the baud rate value, see [1] "BAUD RATE GENERATOR PROGRAMMING TABLE". + * We use 38.4K when 1.8432 MHZ crystal, so the corresponding value is 3. + * And due to the divisor register is two bytes (16 bits), so we need to + * split the value of 3(0x0003) into two bytes, DLL stores the low byte, + * DLM stores the high byte. + */ + uint8_t lcr = uart_read_reg(LCR); + uart_write_reg(LCR, lcr | (1 << 7)); + uart_write_reg(DLL, 0x03); + uart_write_reg(DLM, 0x00); + + /* + * Continue setting the asynchronous data communication format. + * - number of the word length: 8 bits + * - number of stop bits:1 bit when word length is 8 bits + * - no parity + * - no break control + * - disabled baud latch + */ + lcr = 0; + uart_write_reg(LCR, lcr | (3 << 0)); +} + +int uart_putc(char ch) +{ + while ((uart_read_reg(LSR) & LSR_TX_IDLE) == 0); + return uart_write_reg(THR, ch); +} + +void uart_puts(char *s) +{ + while (*s) { + uart_putc(*s++); + } +} + diff --git a/code/os/05-traps/user.c b/code/os/05-traps/user.c new file mode 100644 index 0000000..53bfb3f --- /dev/null +++ b/code/os/05-traps/user.c @@ -0,0 +1,36 @@ +#include "os.h" + +#define DELAY 1000 + +extern void trap_test(void); + +void user_task0(void) +{ + uart_puts("Task 0: Created!\n"); + while (1) { + uart_puts("Task 0: Running...\n"); + + trap_test(); + + task_delay(DELAY); + task_yield(); + } +} + +void user_task1(void) +{ + uart_puts("Task 1: Created!\n"); + while (1) { + uart_puts("Task 1: Running...\n"); + task_delay(DELAY); + task_yield(); + } +} + +/* NOTICE: DON'T LOOP INFINITELY IN main() */ +void os_main(void) +{ + task_create(user_task0); + task_create(user_task1); +} + diff --git a/code/os/06-interrupts/Makefile b/code/os/06-interrupts/Makefile new file mode 100644 index 0000000..88e6ead --- /dev/null +++ b/code/os/06-interrupts/Makefile @@ -0,0 +1,64 @@ +CROSS_COMPILE = riscv64-unknown-elf- +CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall + +QEMU = qemu-system-riscv32 +QFLAGS = -nographic -smp 1 -machine virt -bios none + +GDB = ${CROSS_COMPILE}gdb +CC = ${CROSS_COMPILE}gcc +OBJCOPY = ${CROSS_COMPILE}objcopy +OBJDUMP = ${CROSS_COMPILE}objdump + +SRCS_ASM = \ + start.S \ + mem.S \ + entry.S \ + +SRCS_C = \ + kernel.c \ + uart.c \ + printf.c \ + page.c \ + sched.c \ + user.c \ + trap.c \ + plic.c \ + +OBJS = $(SRCS_ASM:.S=.o) +OBJS += $(SRCS_C:.c=.o) + +.DEFAULT_GOAL := all +all: os.elf + +# start.o must be the first in dependency! +os.elf: ${OBJS} + ${CC} $(CFLAGS) -T os.ld -o os.elf $^ + ${OBJCOPY} -O binary os.elf os.bin + +%.o : %.c + ${CC} ${CFLAGS} -c -o $@ $< + +%.o : %.S + ${CC} ${CFLAGS} -c -o $@ $< + +run: all + @${QEMU} -M ? | grep virt >/dev/null || exit + @echo "Press Ctrl-A and then X to exit QEMU" + @echo "------------------------------------" + @${QEMU} ${QFLAGS} -kernel os.elf + +.PHONY : debug +debug: all + @echo "Press Ctrl-C and then input 'quit' to exit GDB and QEMU" + @echo "-------------------------------------------------------" + @${QEMU} ${QFLAGS} -kernel os.elf -s -S & + @${GDB} os.elf -q -x ../gdbinit + +.PHONY : code +code: all + @${OBJDUMP} -S os.elf | less + +.PHONY : clean +clean: + rm -rf *.o *.bin *.elf + diff --git a/code/os/06-interrupts/entry.S b/code/os/06-interrupts/entry.S new file mode 100644 index 0000000..ed0fca2 --- /dev/null +++ b/code/os/06-interrupts/entry.S @@ -0,0 +1,131 @@ +# save all General-Purpose(GP) registers to context +# struct context *base = &ctx_task; +# base->ra = ra; +# ...... +.macro reg_save base + sw ra, 0(\base) + sw sp, 4(\base) + sw gp, 8(\base) + sw tp, 12(\base) + sw t0, 16(\base) + sw t1, 20(\base) + sw t2, 24(\base) + sw s0, 28(\base) + sw s1, 32(\base) + sw a0, 36(\base) + sw a1, 40(\base) + sw a2, 44(\base) + sw a3, 48(\base) + sw a4, 52(\base) + sw a5, 56(\base) + sw a6, 60(\base) + sw a7, 64(\base) + sw s2, 68(\base) + sw s3, 72(\base) + sw s4, 76(\base) + sw s5, 80(\base) + sw s6, 84(\base) + sw s7, 88(\base) + sw s8, 92(\base) + sw s9, 96(\base) + sw s10, 100(\base) + sw s11, 104(\base) + sw t3, 108(\base) + sw t4, 112(\base) + sw t5, 116(\base) + sw t6, 120(\base) +.endm + +# restore all General-Purpose(GP) registers from the context +# struct context *base = &ctx_task; +# ra = base->ra; +# ...... +.macro reg_restore base + lw ra, 0(\base) + lw sp, 4(\base) + lw gp, 8(\base) + lw tp, 12(\base) + lw t0, 16(\base) + lw t1, 20(\base) + lw t2, 24(\base) + lw s0, 28(\base) + lw s1, 32(\base) + lw a0, 36(\base) + lw a1, 40(\base) + lw a2, 44(\base) + lw a3, 48(\base) + lw a4, 52(\base) + lw a5, 56(\base) + lw a6, 60(\base) + lw a7, 64(\base) + lw s2, 68(\base) + lw s3, 72(\base) + lw s4, 76(\base) + lw s5, 80(\base) + lw s6, 84(\base) + lw s7, 88(\base) + lw s8, 92(\base) + lw s9, 96(\base) + lw s10, 100(\base) + lw s11, 104(\base) + lw t3, 108(\base) + lw t4, 112(\base) + lw t5, 116(\base) + lw t6, 120(\base) +.endm + +# Something to note about save/restore: +# - We use mscratch to hold a pointer to context of previous task +# - We use t6 as the 'base' for reg_save/reg_restore, because it is the +# very bottom register (x31) and would not be overwritten during loading. + +.text + +# interrupts and exceptions while in machine mode come here. +.globl trap_vector +# the trap vector base address must always be aligned on a 4-byte boundary +.align 4 +trap_vector: + # save context(registers). + csrrw t6, mscratch, t6 # swap t6 and mscratch + reg_save t6 + csrw mscratch, t6 + + # call the C trap handler in trap.c + csrr a0, mepc + csrr a1, mcause + call trap_handler + + # trap_handler will return the return address via a0. + csrw mepc, a0 + + # restore context(registers). + csrr t6, mscratch + reg_restore t6 + + # return to whatever we were doing before trap. + mret + +# void switch_to(struct context *next); +# a0: pointer to the context of the next task +.globl switch_to +.align 4 +switch_to: + csrrw t6, mscratch, t6 # swap t6 and mscratch + beqz t6, 1f # Notice: previous task may be NULL + reg_save t6 # save context of prev task + +1: + # switch mscratch to point to the context of the next task + csrw mscratch, a0 + + # Restore all GP registers + # Use t6 to point to the context of the new task + mv t6, a0 + reg_restore t6 + + # Do actual context switching. + ret + +.end + diff --git a/code/os/06-interrupts/kernel.c b/code/os/06-interrupts/kernel.c new file mode 100644 index 0000000..7731ef3 --- /dev/null +++ b/code/os/06-interrupts/kernel.c @@ -0,0 +1,35 @@ +#include "os.h" + +/* + * Following functions SHOULD be called ONLY ONE time here, + * so just declared here ONCE and NOT included in file os.h. + */ +extern void uart_init(void); +extern void page_init(void); +extern void sched_init(void); +extern void schedule(void); +extern void os_main(void); +extern void trap_init(void); +extern void plic_init(void); + +void start_kernel(void) +{ + uart_init(); + uart_puts("Hello, RVOS!\n"); + + page_init(); + + trap_init(); + + plic_init(); + + sched_init(); + + os_main(); + + schedule(); + + uart_puts("Would not go here!\n"); + while (1) {}; // stop here! +} + diff --git a/code/os/06-interrupts/mem.S b/code/os/06-interrupts/mem.S new file mode 100644 index 0000000..93ba0d6 --- /dev/null +++ b/code/os/06-interrupts/mem.S @@ -0,0 +1,30 @@ +.section .rodata +.global HEAP_START +HEAP_START: .word _heap_start + +.global HEAP_SIZE +HEAP_SIZE: .word _heap_size + +.global TEXT_START +TEXT_START: .word _text_start + +.global TEXT_END +TEXT_END: .word _text_end + +.global DATA_START +DATA_START: .word _data_start + +.global DATA_END +DATA_END: .word _data_end + +.global RODATA_START +RODATA_START: .word _rodata_start + +.global RODATA_END +RODATA_END: .word _rodata_end + +.global BSS_START +BSS_START: .word _bss_start + +.global BSS_END +BSS_END: .word _bss_end diff --git a/code/os/06-interrupts/os.h b/code/os/06-interrupts/os.h new file mode 100644 index 0000000..fb99051 --- /dev/null +++ b/code/os/06-interrupts/os.h @@ -0,0 +1,69 @@ +#ifndef __OS_H__ +#define __OS_H__ + +#include "types.h" +#include "riscv.h" +#include "platform.h" + +#include +#include + +/* uart */ +extern int uart_putc(char ch); +extern void uart_puts(char *s); +extern int uart_getc(void); + +/* printf */ +extern int printf(const char* s, ...); +extern void panic(char *s); + +/* memory management */ +extern void *page_alloc(int npages); +extern void page_free(void *p); + +/* task management */ +struct context { + /* ignore x0 */ + reg_t ra; + reg_t sp; + reg_t gp; + reg_t tp; + reg_t t0; + reg_t t1; + reg_t t2; + reg_t s0; + reg_t s1; + reg_t a0; + reg_t a1; + reg_t a2; + reg_t a3; + reg_t a4; + reg_t a5; + reg_t a6; + reg_t a7; + reg_t s2; + reg_t s3; + reg_t s4; + reg_t s5; + reg_t s6; + reg_t s7; + reg_t s8; + reg_t s9; + reg_t s10; + reg_t s11; + reg_t t3; + reg_t t4; + reg_t t5; + reg_t t6; +}; + +extern int task_create(void (*task)(void)); +extern void task_delay(volatile int count); +extern void task_yield(); + +/* plic */ +extern int plic_claim(void); +extern void plic_complete(int irq); + + +#endif /* __OS_H__ */ diff --git a/code/os/06-interrupts/os.ld b/code/os/06-interrupts/os.ld new file mode 100644 index 0000000..a75cd93 --- /dev/null +++ b/code/os/06-interrupts/os.ld @@ -0,0 +1,136 @@ +/* + * rvos.ld + * Linker script for outputting to RVOS + */ + +/* + * https://sourceware.org/binutils/docs/ld/Miscellaneous-Commands.html + * OUTPUT_ARCH command specifies a particular output machine architecture. + * "riscv" is the name of the architecture for both 64-bit and 32-bit + * RISC-V target. We will further refine this by using -march=rv32ima + * and -mabi=ilp32 when calling gcc. + */ +OUTPUT_ARCH( "riscv" ) + +/* + * https://sourceware.org/binutils/docs/ld/Entry-Point.html + * ENTRY command is used to set the "entry point", which is the first instruction + * to execute in a program. + * The argument of ENTRY command is a symbol name, here is "_start" which is + * defined in start.S. + */ +ENTRY( _start ) + +/* + * https://sourceware.org/binutils/docs/ld/MEMORY.html + * The MEMORY command describes the location and size of blocks of memory in + * the target. + * The syntax for MEMORY is: + * MEMORY + * { + * name [(attr)] : ORIGIN = origin, LENGTH = len + * ...... + * } + * Each line defines a memory region. + * Each memory region must have a distinct name within the MEMORY command. Here + * we only define one region named as "ram". + * The "attr" string is an optional list of attributes that specify whether to + * use a particular memory region for an input section which is not explicitly + * mapped in the linker script. Here we assign 'w' (writeable), 'x' (executable), + * and 'a' (allocatable). We use '!' to invert 'r' (read-only) and + * 'i' (initialized). + * The "ORIGIN" is used to set the start address of the memory region. Here we + * place it right at the beginning of 0x8000_0000 because this is where the + * QEMU-virt machine will start executing. + * Finally LENGTH = 128M tells the linker that we have 128 megabyte of RAM. + * The linker will double check this to make sure everything can fit. + */ +MEMORY +{ + ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M +} + +/* + * https://sourceware.org/binutils/docs/ld/SECTIONS.html + * The SECTIONS command tells the linker how to map input sections into output + * sections, and how to place the output sections in memory. + * The format of the SECTIONS command is: + * SECTIONS + * { + * sections-command + * sections-command + * ...... + * } + * + * Each sections-command may of be one of the following: + * (1) an ENTRY command + * (2) a symbol assignment + * (3) an output section description + * (4) an overlay description + * We here only demo (2) & (3). + * + * We use PROVIDE command to define symbols. + * https://sourceware.org/binutils/docs/ld/PROVIDE.html + * The PROVIDE keyword may be used to define a symbol. + * The syntax is PROVIDE(symbol = expression). + * Such symbols as "_text_start", "_text_end" ... will be used in mem.S. + * Notice the period '.' tells the linker to set symbol(e.g. _text_start) to + * the CURRENT location ('.' = current memory location). This current memory + * location moves as we add things. + */ +SECTIONS +{ + /* + * We are going to layout all text sections in .text output section, + * starting with .text. The asterisk("*") in front of the + * parentheses means to match the .text section of ANY object file. + */ + .text : { + PROVIDE(_text_start = .); + *(.text .text.*) + PROVIDE(_text_end = .); + } >ram + + .rodata : { + PROVIDE(_rodata_start = .); + *(.rodata .rodata.*) + PROVIDE(_rodata_end = .); + } >ram + + .data : { + /* + * . = ALIGN(4096) tells the linker to align the current memory + * location to 4096 bytes. This will insert padding bytes until + * current location becomes aligned on 4096-byte boundary. + * This is because our paging system's resolution is 4,096 bytes. + */ + . = ALIGN(4096); + PROVIDE(_data_start = .); + /* + * sdata and data are essentially the same thing. We do not need + * to distinguish sdata from data. + */ + *(.sdata .sdata.*) + *(.data .data.*) + PROVIDE(_data_end = .); + } >ram + + .bss :{ + /* + * https://sourceware.org/binutils/docs/ld/Input-Section-Common.html + * In most cases, common symbols in input files will be placed + * in the ‘.bss’ section in the output file. + */ + PROVIDE(_bss_start = .); + *(.sbss .sbss.*) + *(.bss .bss.*) + *(COMMON) + PROVIDE(_bss_end = .); + } >ram + + PROVIDE(_memory_start = ORIGIN(ram)); + PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram)); + + PROVIDE(_heap_start = _bss_end); + PROVIDE(_heap_size = _memory_end - _heap_start); +} diff --git a/code/os/06-interrupts/page.c b/code/os/06-interrupts/page.c new file mode 100644 index 0000000..addf097 --- /dev/null +++ b/code/os/06-interrupts/page.c @@ -0,0 +1,189 @@ +#include "os.h" + +/* + * Following global vars are defined in mem.S + */ +extern uint32_t TEXT_START; +extern uint32_t TEXT_END; +extern uint32_t DATA_START; +extern uint32_t DATA_END; +extern uint32_t RODATA_START; +extern uint32_t RODATA_END; +extern uint32_t BSS_START; +extern uint32_t BSS_END; +extern uint32_t HEAP_START; +extern uint32_t HEAP_SIZE; + +/* + * _alloc_start points to the actual start address of heap pool + * _alloc_end points to the actual end address of heap pool + * _num_pages holds the actual max number of pages we can allocate. + */ +static uint32_t _alloc_start = 0; +static uint32_t _alloc_end = 0; +static uint32_t _num_pages = 0; + +#define PAGE_SIZE 4096 +#define PAGE_ORDER 12 + +#define PAGE_TAKEN (uint8_t)(1 << 0) +#define PAGE_LAST (uint8_t)(1 << 1) + +/* + * Page Descriptor + * flags: + * - bit 0: flag if this page is taken(allocated) + * - bit 1: flag if this page is the last page of the memory block allocated + */ +struct Page { + uint8_t flags; +}; + +static inline void _clear(struct Page *page) +{ + page->flags = 0; +} + +static inline int _is_free(struct Page *page) +{ + if (page->flags & PAGE_TAKEN) { + return 0; + } else { + return 1; + } +} + +static inline void _set_flag(struct Page *page, uint8_t flags) +{ + page->flags |= flags; +} + +static inline int _is_last(struct Page *page) +{ + if (page->flags & PAGE_LAST) { + return 1; + } else { + return 0; + } +} + +/* + * align the address to the border of page(4K) + */ +static inline uint32_t _align_page(uint32_t address) +{ + uint32_t order = (1 << PAGE_ORDER) - 1; + return (address + order) & (~order); +} + +void page_init() +{ + /* + * We reserved 8 Page (8 x 4096) to hold the Page structures. + * It should be enough to manage at most 128 MB (8 x 4096 x 4096) + */ + _num_pages = (HEAP_SIZE / PAGE_SIZE) - 8; + printf("HEAP_START = %x, HEAP_SIZE = %x, num of pages = %d\n", HEAP_START, HEAP_SIZE, _num_pages); + + struct Page *page = (struct Page *)HEAP_START; + for (int i = 0; i < _num_pages; i++) { + _clear(page); + page++; + } + + _alloc_start = _align_page(HEAP_START + 8 * PAGE_SIZE); + _alloc_end = _alloc_start + (PAGE_SIZE * _num_pages); + + printf("TEXT: 0x%x -> 0x%x\n", TEXT_START, TEXT_END); + printf("RODATA: 0x%x -> 0x%x\n", RODATA_START, RODATA_END); + printf("DATA: 0x%x -> 0x%x\n", DATA_START, DATA_END); + printf("BSS: 0x%x -> 0x%x\n", BSS_START, BSS_END); + printf("HEAP: 0x%x -> 0x%x\n", _alloc_start, _alloc_end); +} + +/* + * Allocate a memory block which is composed of contiguous physical pages + * - npages: the number of PAGE_SIZE pages to allocate + */ +void *page_alloc(int npages) +{ + /* Note we are searching the page descriptor bitmaps. */ + int found = 0; + struct Page *page_i = (struct Page *)HEAP_START; + for (int i = 0; i < (_num_pages - npages); i++) { + if (_is_free(page_i)) { + found = 1; + /* + * meet a free page, continue to check if following + * (npages - 1) pages are also unallocated. + */ + struct Page *page_j = page_i; + for (int j = i; j < (i + npages); j++) { + if (!_is_free(page_j)) { + found = 0; + break; + } + page_j++; + } + /* + * get a memory block which is good enough for us, + * take housekeeping, then return the actual start + * address of the first page of this memory block + */ + if (found) { + struct Page *page_k = page_i; + for (int k = i; k < (i + npages); k++) { + _set_flag(page_k, PAGE_TAKEN); + page_k++; + } + page_k--; + _set_flag(page_k, PAGE_LAST); + return (void *)(_alloc_start + i * PAGE_SIZE); + } + } + page_i++; + } + return NULL; +} + +/* + * Free the memory block + * - p: start address of the memory block + */ +void page_free(void *p) +{ + /* + * Assert (TBD) if p is invalid + */ + if (!p || (uint32_t)p >= _alloc_end) { + return; + } + /* get the first page descriptor of this memory block */ + struct Page *page = (struct Page *)HEAP_START; + page += ((uint32_t)p - _alloc_start)/ PAGE_SIZE; + /* loop and clear all the page descriptors of the memory block */ + while (!_is_free(page)) { + if (_is_last(page)) { + _clear(page); + break; + } else { + _clear(page); + page++;; + } + } +} + +void page_test() +{ + void *p = page_alloc(2); + printf("p = 0x%x\n", p); + //page_free(p); + + void *p2 = page_alloc(7); + printf("p2 = 0x%x\n", p2); + page_free(p2); + + void *p3 = page_alloc(4); + printf("p3 = 0x%x\n", p3); +} + diff --git a/code/os/06-interrupts/platform.h b/code/os/06-interrupts/platform.h new file mode 100644 index 0000000..3b714f9 --- /dev/null +++ b/code/os/06-interrupts/platform.h @@ -0,0 +1,63 @@ +#ifndef __PLATFORM_H__ +#define __PLATFORM_H__ + +/* + * QEMU RISC-V Virt machine with 16550a UART and VirtIO MMIO + */ + +/* + * maximum number of CPUs + * see https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h + * #define VIRT_CPUS_MAX 8 + */ +#define MAXNUM_CPU 8 + +/* + * MemoryMap + * see https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c, virt_memmap[] + * 0x00001000 -- boot ROM, provided by qemu + * 0x02000000 -- CLINT + * 0x0C000000 -- PLIC + * 0x10000000 -- UART0 + * 0x10001000 -- virtio disk + * 0x80000000 -- boot ROM jumps here in machine mode, where we load our kernel + */ + +/* This machine puts UART registers here in physical memory. */ +#define UART0 0x10000000L + +/* + * UART0 interrupt source + * see https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h + * enum { + * UART0_IRQ = 10, + * ...... + * }; + */ +#define UART0_IRQ 10 + +/* + * This machine puts platform-level interrupt controller (PLIC) here. + * Here only list PLIC registers in Machine mode. + * see https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h + * #define VIRT_PLIC_HART_CONFIG "MS" + * #define VIRT_PLIC_NUM_SOURCES 127 + * #define VIRT_PLIC_NUM_PRIORITIES 7 + * #define VIRT_PLIC_PRIORITY_BASE 0x04 + * #define VIRT_PLIC_PENDING_BASE 0x1000 + * #define VIRT_PLIC_ENABLE_BASE 0x2000 + * #define VIRT_PLIC_ENABLE_STRIDE 0x80 + * #define VIRT_PLIC_CONTEXT_BASE 0x200000 + * #define VIRT_PLIC_CONTEXT_STRIDE 0x1000 + * #define VIRT_PLIC_SIZE(__num_context) \ + * (VIRT_PLIC_CONTEXT_BASE + (__num_context) * VIRT_PLIC_CONTEXT_STRIDE) + */ +#define PLIC_BASE 0x0c000000L +#define PLIC_PRIORITY(id) (PLIC_BASE + (id) * 4) +#define PLIC_PENDING(id) (PLIC_BASE + 0x1000 + ((id) / 32)) +#define PLIC_MENABLE(hart) (PLIC_BASE + 0x2000 + (hart) * 0x80) +#define PLIC_MTHRESHOLD(hart) (PLIC_BASE + 0x200000 + (hart) * 0x1000) +#define PLIC_MCLAIM(hart) (PLIC_BASE + 0x200004 + (hart) * 0x1000) +#define PLIC_MCOMPLETE(hart) (PLIC_BASE + 0x200004 + (hart) * 0x1000) + +#endif /* __PLATFORM_H__ */ diff --git a/code/os/06-interrupts/plic.c b/code/os/06-interrupts/plic.c new file mode 100644 index 0000000..5354240 --- /dev/null +++ b/code/os/06-interrupts/plic.c @@ -0,0 +1,81 @@ +#include "os.h" + +void plic_init(void) +{ + int hart = r_tp(); + + /* + * Set priority for UART0. + * + * Each PLIC interrupt source can be assigned a priority by writing + * to its 32-bit memory-mapped priority register. + * The QEMU-virt (the same as FU540-C000) supports 7 levels of priority. + * A priority value of 0 is reserved to mean "never interrupt" and + * effectively disables the interrupt. + * Priority 1 is the lowest active priority, and priority 7 is the highest. + * Ties between global interrupts of the same priority are broken by + * the Interrupt ID; interrupts with the lowest ID have the highest + * effective priority. + */ + *(uint32_t*)PLIC_PRIORITY(UART0_IRQ) = 1; + + /* + * Enable UART0 + * + * Each global interrupt can be enabled by setting the corresponding + * bit in the enables registers. + */ + *(uint32_t*)PLIC_MENABLE(hart)= (1 << UART0_IRQ); + + /* + * Set priority threshold for UART0. + * + * PLIC will mask all interrupts of a priority less than or equal to threshold. + * Maximum threshold is 7. + * For example, a threshold value of zero permits all interrupts with + * non-zero priority, whereas a value of 7 masks all interrupts. + * Notice, the threshold is global for PLIC, not for each interrupt source. + */ + *(uint32_t*)PLIC_MTHRESHOLD(hart) = 0; + + /* enable machine-mode external interrupts. */ + w_mie(r_mie() | MIE_MEIE); + + /* enable machine-mode global interrupts. */ + w_mstatus(r_mstatus() | MSTATUS_MIE); +} + +/* + * DESCRIPTION: + * Query the PLIC what interrupt we should serve. + * Perform an interrupt claim by reading the claim register, which + * returns the ID of the highest-priority pending interrupt or zero if there + * is no pending interrupt. + * A successful claim also atomically clears the corresponding pending bit + * on the interrupt source. + * RETURN VALUE: + * the ID of the highest-priority pending interrupt or zero if there + * is no pending interrupt. + */ +int plic_claim(void) +{ + int hart = r_tp(); + int irq = *(uint32_t*)PLIC_MCLAIM(hart); + return irq; +} + +/* + * DESCRIPTION: + * Writing the interrupt ID it received from the claim (irq) to the + * complete register would signal the PLIC we've served this IRQ. + * The PLIC does not check whether the completion ID is the same as the + * last claim ID for that target. If the completion ID does not match an + * interrupt source that is currently enabled for the target, the completion + * is silently ignored. + * RETURN VALUE: none + */ +void plic_complete(int irq) +{ + int hart = r_tp(); + *(uint32_t*)PLIC_MCOMPLETE(hart) = irq; +} diff --git a/code/os/06-interrupts/printf.c b/code/os/06-interrupts/printf.c new file mode 100644 index 0000000..93ace5b --- /dev/null +++ b/code/os/06-interrupts/printf.c @@ -0,0 +1,138 @@ +#include "os.h" + +/* + * ref: https://github.com/cccriscv/mini-riscv-os/blob/master/05-Preemptive/lib.c + */ + +static int _vsnprintf(char * out, size_t n, const char* s, va_list vl) +{ + int format = 0; + int longarg = 0; + size_t pos = 0; + for (; *s; s++) { + if (format) { + switch(*s) { + case 'l': { + longarg = 1; + break; + } + case 'p': { + longarg = 1; + if (out && pos < n) { + out[pos] = '0'; + } + pos++; + if (out && pos < n) { + out[pos] = 'x'; + } + pos++; + } + case 'x': { + long num = longarg ? va_arg(vl, long) : va_arg(vl, int); + int hexdigits = 2*(longarg ? sizeof(long) : sizeof(int))-1; + for(int i = hexdigits; i >= 0; i--) { + int d = (num >> (4*i)) & 0xF; + if (out && pos < n) { + out[pos] = (d < 10 ? '0'+d : 'a'+d-10); + } + pos++; + } + longarg = 0; + format = 0; + break; + } + case 'd': { + long num = longarg ? va_arg(vl, long) : va_arg(vl, int); + if (num < 0) { + num = -num; + if (out && pos < n) { + out[pos] = '-'; + } + pos++; + } + long digits = 1; + for (long nn = num; nn /= 10; digits++); + for (int i = digits-1; i >= 0; i--) { + if (out && pos + i < n) { + out[pos + i] = '0' + (num % 10); + } + num /= 10; + } + pos += digits; + longarg = 0; + format = 0; + break; + } + case 's': { + const char* s2 = va_arg(vl, const char*); + while (*s2) { + if (out && pos < n) { + out[pos] = *s2; + } + pos++; + s2++; + } + longarg = 0; + format = 0; + break; + } + case 'c': { + if (out && pos < n) { + out[pos] = (char)va_arg(vl,int); + } + pos++; + longarg = 0; + format = 0; + break; + } + default: + break; + } + } else if (*s == '%') { + format = 1; + } else { + if (out && pos < n) { + out[pos] = *s; + } + pos++; + } + } + if (out && pos < n) { + out[pos] = 0; + } else if (out && n) { + out[n-1] = 0; + } + return pos; +} + +static char out_buf[1000]; // buffer for _vprintf() + +static int _vprintf(const char* s, va_list vl) +{ + int res = _vsnprintf(NULL, -1, s, vl); + if (res+1 >= sizeof(out_buf)) { + uart_puts("error: output string size overflow\n"); + while(1) {} + } + _vsnprintf(out_buf, res + 1, s, vl); + uart_puts(out_buf); + return res; +} + +int printf(const char* s, ...) +{ + int res = 0; + va_list vl; + va_start(vl, s); + res = _vprintf(s, vl); + va_end(vl); + return res; +} + +void panic(char *s) +{ + printf("panic: "); + printf(s); + printf("\n"); + while(1){}; +} diff --git a/code/os/06-interrupts/riscv.h b/code/os/06-interrupts/riscv.h new file mode 100644 index 0000000..e5bd491 --- /dev/null +++ b/code/os/06-interrupts/riscv.h @@ -0,0 +1,102 @@ +#ifndef __RISCV_H__ +#define __RISCV_H__ + +#include "types.h" + +/* + * ref: https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/riscv.h + */ + +static inline reg_t r_tp() +{ + reg_t x; + asm volatile("mv %0, tp" : "=r" (x) ); + return x; +} + +/* which hart (core) is this? */ +static inline reg_t r_mhartid() +{ + reg_t x; + asm volatile("csrr %0, mhartid" : "=r" (x) ); + return x; +} + +/* Machine Status Register, mstatus */ +#define MSTATUS_MPP (3 << 11) +#define MSTATUS_SPP (1 << 8) + +#define MSTATUS_MPIE (1 << 7) +#define MSTATUS_SPIE (1 << 5) +#define MSTATUS_UPIE (1 << 4) + +#define MSTATUS_MIE (1 << 3) +#define MSTATUS_SIE (1 << 1) +#define MSTATUS_UIE (1 << 0) + +static inline reg_t r_mstatus() +{ + reg_t x; + asm volatile("csrr %0, mstatus" : "=r" (x) ); + return x; +} + +static inline void w_mstatus(reg_t x) +{ + asm volatile("csrw mstatus, %0" : : "r" (x)); +} + +/* + * machine exception program counter, holds the + * instruction address to which a return from + * exception will go. + */ +static inline void w_mepc(reg_t x) +{ + asm volatile("csrw mepc, %0" : : "r" (x)); +} + +static inline reg_t r_mepc() +{ + reg_t x; + asm volatile("csrr %0, mepc" : "=r" (x)); + return x; +} + +/* Machine Scratch register, for early trap handler */ +static inline void w_mscratch(reg_t x) +{ + asm volatile("csrw mscratch, %0" : : "r" (x)); +} + +/* Machine-mode interrupt vector */ +static inline void w_mtvec(reg_t x) +{ + asm volatile("csrw mtvec, %0" : : "r" (x)); +} + +/* Machine-mode Interrupt Enable */ +#define MIE_MEIE (1 << 11) // external +#define MIE_MTIE (1 << 7) // timer +#define MIE_MSIE (1 << 3) // software + +static inline reg_t r_mie() +{ + reg_t x; + asm volatile("csrr %0, mie" : "=r" (x) ); + return x; +} + +static inline void w_mie(reg_t x) +{ + asm volatile("csrw mie, %0" : : "r" (x)); +} + +static inline reg_t r_mcause() +{ + reg_t x; + asm volatile("csrr %0, mcause" : "=r" (x) ); + return x; +} + +#endif /* __RISCV_H__ */ diff --git a/code/os/06-interrupts/sched.c b/code/os/06-interrupts/sched.c new file mode 100644 index 0000000..12a9995 --- /dev/null +++ b/code/os/06-interrupts/sched.c @@ -0,0 +1,76 @@ +#include "os.h" + +/* defined in entry.S */ +extern void switch_to(struct context *next); + +#define MAX_TASKS 10 +#define STACK_SIZE 1024 +uint8_t task_stack[MAX_TASKS][STACK_SIZE]; +struct context ctx_tasks[MAX_TASKS]; + +/* + * _top is used to mark the max available position of ctx_tasks + * _current is used to point to the context of current task + */ +static int _top = 0; +static int _current = -1; + +void sched_init() +{ + w_mscratch(0); +} + +/* + * implment a simple cycle FIFO schedular + */ +void schedule() +{ + if (_top <= 0) { + panic("Num of task should be greater than zero!"); + return; + } + + _current = (_current + 1) % _top; + struct context *next = &(ctx_tasks[_current]); + switch_to(next); +} + +/* + * DESCRIPTION + * Create a task. + * - start_routin: task routune entry + * RETURN VALUE + * 0: success + * -1: if error occured + */ +int task_create(void (*start_routin)(void)) +{ + if (_top < MAX_TASKS) { + ctx_tasks[_top].sp = (reg_t) &task_stack[_top][STACK_SIZE - 1]; + ctx_tasks[_top].ra = (reg_t) start_routin; + _top++; + return 0; + } else { + return -1; + } +} + +/* + * DESCRIPTION + * task_yield() causes the calling task to relinquish the CPU and a new + * task gets to run. + */ +void task_yield() +{ + schedule(); +} + +/* + * a very rough implementaion, just to consume the cpu + */ +void task_delay(volatile int count) +{ + count *= 50000; + while (count--); +} + diff --git a/code/os/06-interrupts/start.S b/code/os/06-interrupts/start.S new file mode 100644 index 0000000..6747e7c --- /dev/null +++ b/code/os/06-interrupts/start.S @@ -0,0 +1,41 @@ +#include "platform.h" + + .equ STACK_SIZE, 8192 + + .global _start + + .text +_start: + # park harts with id != 0 + csrr t0, mhartid # read current hart id + mv tp, t0 # keep CPU's hartid in its tp for later usage. + bnez t0, park # if we're not on the hart 0 + # we park the hart + + # Set all bytes in the BSS section to zero. + la a0, _bss_start + la a1, _bss_end + bgeu a0, a1, 2f +1: + sw zero, (a0) + addi a0, a0, 4 + bltu a0, a1, 1b +2: + # Setup stacks, the stack grows from bottom to top, so we put the + # stack pointer to the very end of the stack range. + slli t0, t0, 10 # shift left the hart id by 1024 + la sp, stacks + STACK_SIZE # set the initial stack pointer + # to the end of the stack space + add sp, sp, t0 # move the current hart stack pointer + # to its place in the stack space + + j start_kernel # hart 0 jump to c + +park: + wfi + j park + +stacks: + .skip STACK_SIZE * MAXNUM_CPU # allocate space for the harts stacks + + .end # End of file diff --git a/code/os/06-interrupts/trap.c b/code/os/06-interrupts/trap.c new file mode 100644 index 0000000..b419523 --- /dev/null +++ b/code/os/06-interrupts/trap.c @@ -0,0 +1,77 @@ +#include "os.h" + +extern void trap_vector(void); +extern void uart_isr(void); + +void trap_init() +{ + /* + * set the trap-vector base-address for machine-mode + */ + w_mtvec((reg_t)trap_vector); +} + +void external_interrupt_handler() +{ + int irq = plic_claim(); + + if (irq == UART0_IRQ){ + uart_isr(); + } else if (irq) { + printf("unexpected interrupt irq = %d\n", irq); + } + + if (irq) { + plic_complete(irq); + } +} + +reg_t trap_handler(reg_t epc, reg_t cause) +{ + reg_t return_pc = epc; + reg_t cause_code = cause & 0xfff; + + if (cause & 0x80000000) { + /* Asynchronous trap - interrupt */ + switch (cause_code) { + case 3: + uart_puts("software interruption!\n"); + break; + case 7: + uart_puts("timer interruption!\n"); + break; + case 11: + uart_puts("external interruption!\n"); + external_interrupt_handler(); + break; + default: + uart_puts("unknown async exception!\n"); + break; + } + } else { + /* Synchronous trap - exception */ + printf("Sync exceptions!, code = %d\n", cause_code); + panic("OOPS! What can I do!"); + //return_pc += 4; + } + + return return_pc; +} + +void trap_test() +{ + /* + * Synchronous exception code = 7 + * Store/AMO access fault + */ + *(int *)0x00000000 = 100; + + /* + * Synchronous exception code = 5 + * Load access fault + */ + //int a = *(int *)0x00000000; + + uart_puts("Yeah! I'm return back from trap!\n"); +} + diff --git a/code/os/06-interrupts/types.h b/code/os/06-interrupts/types.h new file mode 100644 index 0000000..5051639 --- /dev/null +++ b/code/os/06-interrupts/types.h @@ -0,0 +1,14 @@ +#ifndef __TYPES_H__ +#define __TYPES_H__ + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long long uint64_t; + +/* + * RISCV32: register is 32bits width + */ +typedef uint32_t reg_t; + +#endif /* __TYPES_H__ */ diff --git a/code/os/06-interrupts/uart.c b/code/os/06-interrupts/uart.c new file mode 100644 index 0000000..2905331 --- /dev/null +++ b/code/os/06-interrupts/uart.c @@ -0,0 +1,149 @@ +#include "os.h" + +/* + * The UART control registers are memory-mapped at address UART0. + * This macro returns the address of one of the registers. + */ +#define UART_REG(reg) ((volatile uint8_t *)(UART0 + reg)) + +/* + * Reference + * [1]: TECHNICAL DATA ON 16550, http://byterunner.com/16550.html + */ + +/* + * UART control registers map. see [1] "PROGRAMMING TABLE" + * note some are reused by multiple functions + * 0 (write mode): THR/DLL + * 1 (write mode): IER/DLM + */ +#define RHR 0 // Receive Holding Register (read mode) +#define THR 0 // Transmit Holding Register (write mode) +#define DLL 0 // LSB of Divisor Latch (write mode) +#define IER 1 // Interrupt Enable Register (write mode) +#define DLM 1 // MSB of Divisor Latch (write mode) +#define FCR 2 // FIFO Control Register (write mode) +#define ISR 2 // Interrupt Status Register (read mode) +#define LCR 3 // Line Control Register +#define MCR 4 // Modem Control Register +#define LSR 5 // Line Status Register +#define MSR 6 // Modem Status Register +#define SPR 7 // ScratchPad Register + +/* + * POWER UP DEFAULTS + * IER = 0: TX/RX holding register interrupts are bith disabled + * ISR = 1: no interrupt penting + * LCR = 0 + * MCR = 0 + * LSR = 60 HEX + * MSR = BITS 0-3 = 0, BITS 4-7 = inputs + * FCR = 0 + * TX = High + * OP1 = High + * OP2 = High + * RTS = High + * DTR = High + * RXRDY = High + * TXRDY = Low + * INT = Low + */ + +/* + * LINE STATUS REGISTER (LSR) + * LSR BIT 0: + * 0 = no data in receive holding register or FIFO. + * 1 = data has been receive and saved in the receive holding register or FIFO. + * ...... + * LSR BIT 5: + * 0 = transmit holding register is full. 16550 will not accept any data for transmission. + * 1 = transmitter hold register (or FIFO) is empty. CPU can load the next character. + * ...... + */ +#define LSR_RX_READY (1 << 0) +#define LSR_TX_IDLE (1 << 5) + +#define uart_read_reg(reg) (*(UART_REG(reg))) +#define uart_write_reg(reg, v) (*(UART_REG(reg)) = (v)) + +void uart_init() +{ + /* disable interrupts. */ + uart_write_reg(IER, 0x00); + + /* + * Setting baud rate. Just a demo here if we care about the divisor, + * but for our purpose [QEMU-virt], this doesn't really do anything. + * + * Notice that the divisor register DLL (divisor latch least) and DLM (divisor + * latch most) have the same base address as the receiver/transmitter and the + * interrupt enable register. To change what the base address points to, we + * open the "divisor latch" by writing 1 into the Divisor Latch Access Bit + * (DLAB), which is bit index 7 of the Line Control Register (LCR). + * + * Regarding the baud rate value, see [1] "BAUD RATE GENERATOR PROGRAMMING TABLE". + * We use 38.4K when 1.8432 MHZ crystal, so the corresponding value is 3. + * And due to the divisor register is two bytes (16 bits), so we need to + * split the value of 3(0x0003) into two bytes, DLL stores the low byte, + * DLM stores the high byte. + */ + uint8_t lcr = uart_read_reg(LCR); + uart_write_reg(LCR, lcr | (1 << 7)); + uart_write_reg(DLL, 0x03); + uart_write_reg(DLM, 0x00); + + /* + * Continue setting the asynchronous data communication format. + * - number of the word length: 8 bits + * - number of stop bits:1 bit when word length is 8 bits + * - no parity + * - no break control + * - disabled baud latch + */ + lcr = 0; + uart_write_reg(LCR, lcr | (3 << 0)); + + /* + * enable receive interrupts. + */ + uint8_t ier = uart_read_reg(IER); + uart_write_reg(IER, ier | (1 << 0)); +} + +int uart_putc(char ch) +{ + while ((uart_read_reg(LSR) & LSR_TX_IDLE) == 0); + return uart_write_reg(THR, ch); +} + +void uart_puts(char *s) +{ + while (*s) { + uart_putc(*s++); + } +} + +int uart_getc(void) +{ + if (uart_read_reg(LSR) & LSR_RX_READY){ + return uart_read_reg(RHR); + } else { + return -1; + } +} + +/* + * handle a uart interrupt, raised because input has arrived, called from trap.c. + */ +void uart_isr(void) +{ + while (1) { + int c = uart_getc(); + if (c == -1) { + break; + } else { + uart_putc((char)c); + uart_putc('\n'); + } + } +} diff --git a/code/os/06-interrupts/user.c b/code/os/06-interrupts/user.c new file mode 100644 index 0000000..6c9484d --- /dev/null +++ b/code/os/06-interrupts/user.c @@ -0,0 +1,31 @@ +#include "os.h" + +#define DELAY 1000 + +void user_task0(void) +{ + uart_puts("Task 0: Created!\n"); + while (1) { + uart_puts("Task 0: Running...\n"); + task_delay(DELAY); + task_yield(); + } +} + +void user_task1(void) +{ + uart_puts("Task 1: Created!\n"); + while (1) { + uart_puts("Task 1: Running...\n"); + task_delay(DELAY); + task_yield(); + } +} + +/* NOTICE: DON'T LOOP INFINITELY IN main() */ +void os_main(void) +{ + task_create(user_task0); + task_create(user_task1); +} + diff --git a/code/os/07-hwtimer/Makefile b/code/os/07-hwtimer/Makefile new file mode 100644 index 0000000..7a65c62 --- /dev/null +++ b/code/os/07-hwtimer/Makefile @@ -0,0 +1,65 @@ +CROSS_COMPILE = riscv64-unknown-elf- +CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall + +QEMU = qemu-system-riscv32 +QFLAGS = -nographic -smp 1 -machine virt -bios none + +GDB = ${CROSS_COMPILE}gdb +CC = ${CROSS_COMPILE}gcc +OBJCOPY = ${CROSS_COMPILE}objcopy +OBJDUMP = ${CROSS_COMPILE}objdump + +SRCS_ASM = \ + start.S \ + mem.S \ + entry.S \ + +SRCS_C = \ + kernel.c \ + uart.c \ + printf.c \ + page.c \ + sched.c \ + user.c \ + trap.c \ + plic.c \ + timer.c \ + +OBJS = $(SRCS_ASM:.S=.o) +OBJS += $(SRCS_C:.c=.o) + +.DEFAULT_GOAL := all +all: os.elf + +# start.o must be the first in dependency! +os.elf: ${OBJS} + ${CC} $(CFLAGS) -T os.ld -o os.elf $^ + ${OBJCOPY} -O binary os.elf os.bin + +%.o : %.c + ${CC} ${CFLAGS} -c -o $@ $< + +%.o : %.S + ${CC} ${CFLAGS} -c -o $@ $< + +run: all + @${QEMU} -M ? | grep virt >/dev/null || exit + @echo "Press Ctrl-A and then X to exit QEMU" + @echo "------------------------------------" + @${QEMU} ${QFLAGS} -kernel os.elf + +.PHONY : debug +debug: all + @echo "Press Ctrl-C and then input 'quit' to exit GDB and QEMU" + @echo "-------------------------------------------------------" + @${QEMU} ${QFLAGS} -kernel os.elf -s -S & + @${GDB} os.elf -q -x ../gdbinit + +.PHONY : code +code: all + @${OBJDUMP} -S os.elf | less + +.PHONY : clean +clean: + rm -rf *.o *.bin *.elf + diff --git a/code/os/07-hwtimer/entry.S b/code/os/07-hwtimer/entry.S new file mode 100644 index 0000000..ed0fca2 --- /dev/null +++ b/code/os/07-hwtimer/entry.S @@ -0,0 +1,131 @@ +# save all General-Purpose(GP) registers to context +# struct context *base = &ctx_task; +# base->ra = ra; +# ...... +.macro reg_save base + sw ra, 0(\base) + sw sp, 4(\base) + sw gp, 8(\base) + sw tp, 12(\base) + sw t0, 16(\base) + sw t1, 20(\base) + sw t2, 24(\base) + sw s0, 28(\base) + sw s1, 32(\base) + sw a0, 36(\base) + sw a1, 40(\base) + sw a2, 44(\base) + sw a3, 48(\base) + sw a4, 52(\base) + sw a5, 56(\base) + sw a6, 60(\base) + sw a7, 64(\base) + sw s2, 68(\base) + sw s3, 72(\base) + sw s4, 76(\base) + sw s5, 80(\base) + sw s6, 84(\base) + sw s7, 88(\base) + sw s8, 92(\base) + sw s9, 96(\base) + sw s10, 100(\base) + sw s11, 104(\base) + sw t3, 108(\base) + sw t4, 112(\base) + sw t5, 116(\base) + sw t6, 120(\base) +.endm + +# restore all General-Purpose(GP) registers from the context +# struct context *base = &ctx_task; +# ra = base->ra; +# ...... +.macro reg_restore base + lw ra, 0(\base) + lw sp, 4(\base) + lw gp, 8(\base) + lw tp, 12(\base) + lw t0, 16(\base) + lw t1, 20(\base) + lw t2, 24(\base) + lw s0, 28(\base) + lw s1, 32(\base) + lw a0, 36(\base) + lw a1, 40(\base) + lw a2, 44(\base) + lw a3, 48(\base) + lw a4, 52(\base) + lw a5, 56(\base) + lw a6, 60(\base) + lw a7, 64(\base) + lw s2, 68(\base) + lw s3, 72(\base) + lw s4, 76(\base) + lw s5, 80(\base) + lw s6, 84(\base) + lw s7, 88(\base) + lw s8, 92(\base) + lw s9, 96(\base) + lw s10, 100(\base) + lw s11, 104(\base) + lw t3, 108(\base) + lw t4, 112(\base) + lw t5, 116(\base) + lw t6, 120(\base) +.endm + +# Something to note about save/restore: +# - We use mscratch to hold a pointer to context of previous task +# - We use t6 as the 'base' for reg_save/reg_restore, because it is the +# very bottom register (x31) and would not be overwritten during loading. + +.text + +# interrupts and exceptions while in machine mode come here. +.globl trap_vector +# the trap vector base address must always be aligned on a 4-byte boundary +.align 4 +trap_vector: + # save context(registers). + csrrw t6, mscratch, t6 # swap t6 and mscratch + reg_save t6 + csrw mscratch, t6 + + # call the C trap handler in trap.c + csrr a0, mepc + csrr a1, mcause + call trap_handler + + # trap_handler will return the return address via a0. + csrw mepc, a0 + + # restore context(registers). + csrr t6, mscratch + reg_restore t6 + + # return to whatever we were doing before trap. + mret + +# void switch_to(struct context *next); +# a0: pointer to the context of the next task +.globl switch_to +.align 4 +switch_to: + csrrw t6, mscratch, t6 # swap t6 and mscratch + beqz t6, 1f # Notice: previous task may be NULL + reg_save t6 # save context of prev task + +1: + # switch mscratch to point to the context of the next task + csrw mscratch, a0 + + # Restore all GP registers + # Use t6 to point to the context of the new task + mv t6, a0 + reg_restore t6 + + # Do actual context switching. + ret + +.end + diff --git a/code/os/07-hwtimer/kernel.c b/code/os/07-hwtimer/kernel.c new file mode 100644 index 0000000..b4e8096 --- /dev/null +++ b/code/os/07-hwtimer/kernel.c @@ -0,0 +1,38 @@ +#include "os.h" + +/* + * Following functions SHOULD be called ONLY ONE time here, + * so just declared here ONCE and NOT included in file os.h. + */ +extern void uart_init(void); +extern void page_init(void); +extern void sched_init(void); +extern void schedule(void); +extern void os_main(void); +extern void trap_init(void); +extern void plic_init(void); +extern void timer_init(void); + +void start_kernel(void) +{ + uart_init(); + uart_puts("Hello, RVOS!\n"); + + page_init(); + + trap_init(); + + plic_init(); + + timer_init(); + + sched_init(); + + os_main(); + + schedule(); + + uart_puts("Would not go here!\n"); + while (1) {}; // stop here! +} + diff --git a/code/os/07-hwtimer/mem.S b/code/os/07-hwtimer/mem.S new file mode 100644 index 0000000..93ba0d6 --- /dev/null +++ b/code/os/07-hwtimer/mem.S @@ -0,0 +1,30 @@ +.section .rodata +.global HEAP_START +HEAP_START: .word _heap_start + +.global HEAP_SIZE +HEAP_SIZE: .word _heap_size + +.global TEXT_START +TEXT_START: .word _text_start + +.global TEXT_END +TEXT_END: .word _text_end + +.global DATA_START +DATA_START: .word _data_start + +.global DATA_END +DATA_END: .word _data_end + +.global RODATA_START +RODATA_START: .word _rodata_start + +.global RODATA_END +RODATA_END: .word _rodata_end + +.global BSS_START +BSS_START: .word _bss_start + +.global BSS_END +BSS_END: .word _bss_end diff --git a/code/os/07-hwtimer/os.h b/code/os/07-hwtimer/os.h new file mode 100644 index 0000000..fb99051 --- /dev/null +++ b/code/os/07-hwtimer/os.h @@ -0,0 +1,69 @@ +#ifndef __OS_H__ +#define __OS_H__ + +#include "types.h" +#include "riscv.h" +#include "platform.h" + +#include +#include + +/* uart */ +extern int uart_putc(char ch); +extern void uart_puts(char *s); +extern int uart_getc(void); + +/* printf */ +extern int printf(const char* s, ...); +extern void panic(char *s); + +/* memory management */ +extern void *page_alloc(int npages); +extern void page_free(void *p); + +/* task management */ +struct context { + /* ignore x0 */ + reg_t ra; + reg_t sp; + reg_t gp; + reg_t tp; + reg_t t0; + reg_t t1; + reg_t t2; + reg_t s0; + reg_t s1; + reg_t a0; + reg_t a1; + reg_t a2; + reg_t a3; + reg_t a4; + reg_t a5; + reg_t a6; + reg_t a7; + reg_t s2; + reg_t s3; + reg_t s4; + reg_t s5; + reg_t s6; + reg_t s7; + reg_t s8; + reg_t s9; + reg_t s10; + reg_t s11; + reg_t t3; + reg_t t4; + reg_t t5; + reg_t t6; +}; + +extern int task_create(void (*task)(void)); +extern void task_delay(volatile int count); +extern void task_yield(); + +/* plic */ +extern int plic_claim(void); +extern void plic_complete(int irq); + + +#endif /* __OS_H__ */ diff --git a/code/os/07-hwtimer/os.ld b/code/os/07-hwtimer/os.ld new file mode 100644 index 0000000..a75cd93 --- /dev/null +++ b/code/os/07-hwtimer/os.ld @@ -0,0 +1,136 @@ +/* + * rvos.ld + * Linker script for outputting to RVOS + */ + +/* + * https://sourceware.org/binutils/docs/ld/Miscellaneous-Commands.html + * OUTPUT_ARCH command specifies a particular output machine architecture. + * "riscv" is the name of the architecture for both 64-bit and 32-bit + * RISC-V target. We will further refine this by using -march=rv32ima + * and -mabi=ilp32 when calling gcc. + */ +OUTPUT_ARCH( "riscv" ) + +/* + * https://sourceware.org/binutils/docs/ld/Entry-Point.html + * ENTRY command is used to set the "entry point", which is the first instruction + * to execute in a program. + * The argument of ENTRY command is a symbol name, here is "_start" which is + * defined in start.S. + */ +ENTRY( _start ) + +/* + * https://sourceware.org/binutils/docs/ld/MEMORY.html + * The MEMORY command describes the location and size of blocks of memory in + * the target. + * The syntax for MEMORY is: + * MEMORY + * { + * name [(attr)] : ORIGIN = origin, LENGTH = len + * ...... + * } + * Each line defines a memory region. + * Each memory region must have a distinct name within the MEMORY command. Here + * we only define one region named as "ram". + * The "attr" string is an optional list of attributes that specify whether to + * use a particular memory region for an input section which is not explicitly + * mapped in the linker script. Here we assign 'w' (writeable), 'x' (executable), + * and 'a' (allocatable). We use '!' to invert 'r' (read-only) and + * 'i' (initialized). + * The "ORIGIN" is used to set the start address of the memory region. Here we + * place it right at the beginning of 0x8000_0000 because this is where the + * QEMU-virt machine will start executing. + * Finally LENGTH = 128M tells the linker that we have 128 megabyte of RAM. + * The linker will double check this to make sure everything can fit. + */ +MEMORY +{ + ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M +} + +/* + * https://sourceware.org/binutils/docs/ld/SECTIONS.html + * The SECTIONS command tells the linker how to map input sections into output + * sections, and how to place the output sections in memory. + * The format of the SECTIONS command is: + * SECTIONS + * { + * sections-command + * sections-command + * ...... + * } + * + * Each sections-command may of be one of the following: + * (1) an ENTRY command + * (2) a symbol assignment + * (3) an output section description + * (4) an overlay description + * We here only demo (2) & (3). + * + * We use PROVIDE command to define symbols. + * https://sourceware.org/binutils/docs/ld/PROVIDE.html + * The PROVIDE keyword may be used to define a symbol. + * The syntax is PROVIDE(symbol = expression). + * Such symbols as "_text_start", "_text_end" ... will be used in mem.S. + * Notice the period '.' tells the linker to set symbol(e.g. _text_start) to + * the CURRENT location ('.' = current memory location). This current memory + * location moves as we add things. + */ +SECTIONS +{ + /* + * We are going to layout all text sections in .text output section, + * starting with .text. The asterisk("*") in front of the + * parentheses means to match the .text section of ANY object file. + */ + .text : { + PROVIDE(_text_start = .); + *(.text .text.*) + PROVIDE(_text_end = .); + } >ram + + .rodata : { + PROVIDE(_rodata_start = .); + *(.rodata .rodata.*) + PROVIDE(_rodata_end = .); + } >ram + + .data : { + /* + * . = ALIGN(4096) tells the linker to align the current memory + * location to 4096 bytes. This will insert padding bytes until + * current location becomes aligned on 4096-byte boundary. + * This is because our paging system's resolution is 4,096 bytes. + */ + . = ALIGN(4096); + PROVIDE(_data_start = .); + /* + * sdata and data are essentially the same thing. We do not need + * to distinguish sdata from data. + */ + *(.sdata .sdata.*) + *(.data .data.*) + PROVIDE(_data_end = .); + } >ram + + .bss :{ + /* + * https://sourceware.org/binutils/docs/ld/Input-Section-Common.html + * In most cases, common symbols in input files will be placed + * in the ‘.bss’ section in the output file. + */ + PROVIDE(_bss_start = .); + *(.sbss .sbss.*) + *(.bss .bss.*) + *(COMMON) + PROVIDE(_bss_end = .); + } >ram + + PROVIDE(_memory_start = ORIGIN(ram)); + PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram)); + + PROVIDE(_heap_start = _bss_end); + PROVIDE(_heap_size = _memory_end - _heap_start); +} diff --git a/code/os/07-hwtimer/page.c b/code/os/07-hwtimer/page.c new file mode 100644 index 0000000..addf097 --- /dev/null +++ b/code/os/07-hwtimer/page.c @@ -0,0 +1,189 @@ +#include "os.h" + +/* + * Following global vars are defined in mem.S + */ +extern uint32_t TEXT_START; +extern uint32_t TEXT_END; +extern uint32_t DATA_START; +extern uint32_t DATA_END; +extern uint32_t RODATA_START; +extern uint32_t RODATA_END; +extern uint32_t BSS_START; +extern uint32_t BSS_END; +extern uint32_t HEAP_START; +extern uint32_t HEAP_SIZE; + +/* + * _alloc_start points to the actual start address of heap pool + * _alloc_end points to the actual end address of heap pool + * _num_pages holds the actual max number of pages we can allocate. + */ +static uint32_t _alloc_start = 0; +static uint32_t _alloc_end = 0; +static uint32_t _num_pages = 0; + +#define PAGE_SIZE 4096 +#define PAGE_ORDER 12 + +#define PAGE_TAKEN (uint8_t)(1 << 0) +#define PAGE_LAST (uint8_t)(1 << 1) + +/* + * Page Descriptor + * flags: + * - bit 0: flag if this page is taken(allocated) + * - bit 1: flag if this page is the last page of the memory block allocated + */ +struct Page { + uint8_t flags; +}; + +static inline void _clear(struct Page *page) +{ + page->flags = 0; +} + +static inline int _is_free(struct Page *page) +{ + if (page->flags & PAGE_TAKEN) { + return 0; + } else { + return 1; + } +} + +static inline void _set_flag(struct Page *page, uint8_t flags) +{ + page->flags |= flags; +} + +static inline int _is_last(struct Page *page) +{ + if (page->flags & PAGE_LAST) { + return 1; + } else { + return 0; + } +} + +/* + * align the address to the border of page(4K) + */ +static inline uint32_t _align_page(uint32_t address) +{ + uint32_t order = (1 << PAGE_ORDER) - 1; + return (address + order) & (~order); +} + +void page_init() +{ + /* + * We reserved 8 Page (8 x 4096) to hold the Page structures. + * It should be enough to manage at most 128 MB (8 x 4096 x 4096) + */ + _num_pages = (HEAP_SIZE / PAGE_SIZE) - 8; + printf("HEAP_START = %x, HEAP_SIZE = %x, num of pages = %d\n", HEAP_START, HEAP_SIZE, _num_pages); + + struct Page *page = (struct Page *)HEAP_START; + for (int i = 0; i < _num_pages; i++) { + _clear(page); + page++; + } + + _alloc_start = _align_page(HEAP_START + 8 * PAGE_SIZE); + _alloc_end = _alloc_start + (PAGE_SIZE * _num_pages); + + printf("TEXT: 0x%x -> 0x%x\n", TEXT_START, TEXT_END); + printf("RODATA: 0x%x -> 0x%x\n", RODATA_START, RODATA_END); + printf("DATA: 0x%x -> 0x%x\n", DATA_START, DATA_END); + printf("BSS: 0x%x -> 0x%x\n", BSS_START, BSS_END); + printf("HEAP: 0x%x -> 0x%x\n", _alloc_start, _alloc_end); +} + +/* + * Allocate a memory block which is composed of contiguous physical pages + * - npages: the number of PAGE_SIZE pages to allocate + */ +void *page_alloc(int npages) +{ + /* Note we are searching the page descriptor bitmaps. */ + int found = 0; + struct Page *page_i = (struct Page *)HEAP_START; + for (int i = 0; i < (_num_pages - npages); i++) { + if (_is_free(page_i)) { + found = 1; + /* + * meet a free page, continue to check if following + * (npages - 1) pages are also unallocated. + */ + struct Page *page_j = page_i; + for (int j = i; j < (i + npages); j++) { + if (!_is_free(page_j)) { + found = 0; + break; + } + page_j++; + } + /* + * get a memory block which is good enough for us, + * take housekeeping, then return the actual start + * address of the first page of this memory block + */ + if (found) { + struct Page *page_k = page_i; + for (int k = i; k < (i + npages); k++) { + _set_flag(page_k, PAGE_TAKEN); + page_k++; + } + page_k--; + _set_flag(page_k, PAGE_LAST); + return (void *)(_alloc_start + i * PAGE_SIZE); + } + } + page_i++; + } + return NULL; +} + +/* + * Free the memory block + * - p: start address of the memory block + */ +void page_free(void *p) +{ + /* + * Assert (TBD) if p is invalid + */ + if (!p || (uint32_t)p >= _alloc_end) { + return; + } + /* get the first page descriptor of this memory block */ + struct Page *page = (struct Page *)HEAP_START; + page += ((uint32_t)p - _alloc_start)/ PAGE_SIZE; + /* loop and clear all the page descriptors of the memory block */ + while (!_is_free(page)) { + if (_is_last(page)) { + _clear(page); + break; + } else { + _clear(page); + page++;; + } + } +} + +void page_test() +{ + void *p = page_alloc(2); + printf("p = 0x%x\n", p); + //page_free(p); + + void *p2 = page_alloc(7); + printf("p2 = 0x%x\n", p2); + page_free(p2); + + void *p3 = page_alloc(4); + printf("p3 = 0x%x\n", p3); +} + diff --git a/code/os/07-hwtimer/platform.h b/code/os/07-hwtimer/platform.h new file mode 100644 index 0000000..3319f4f --- /dev/null +++ b/code/os/07-hwtimer/platform.h @@ -0,0 +1,97 @@ +#ifndef __PLATFORM_H__ +#define __PLATFORM_H__ + +/* + * QEMU RISC-V Virt machine with 16550a UART and VirtIO MMIO + */ + +/* + * maximum number of CPUs + * see https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h + * #define VIRT_CPUS_MAX 8 + */ +#define MAXNUM_CPU 8 + +/* + * MemoryMap + * see https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c, virt_memmap[] + * 0x00001000 -- boot ROM, provided by qemu + * 0x02000000 -- CLINT + * 0x0C000000 -- PLIC + * 0x10000000 -- UART0 + * 0x10001000 -- virtio disk + * 0x80000000 -- boot ROM jumps here in machine mode, where we load our kernel + */ + +/* This machine puts UART registers here in physical memory. */ +#define UART0 0x10000000L + +/* + * UART0 interrupt source + * see https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h + * enum { + * UART0_IRQ = 10, + * ...... + * }; + */ +#define UART0_IRQ 10 + +/* + * This machine puts platform-level interrupt controller (PLIC) here. + * Here only list PLIC registers in Machine mode. + * see https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h + * #define VIRT_PLIC_HART_CONFIG "MS" + * #define VIRT_PLIC_NUM_SOURCES 127 + * #define VIRT_PLIC_NUM_PRIORITIES 7 + * #define VIRT_PLIC_PRIORITY_BASE 0x04 + * #define VIRT_PLIC_PENDING_BASE 0x1000 + * #define VIRT_PLIC_ENABLE_BASE 0x2000 + * #define VIRT_PLIC_ENABLE_STRIDE 0x80 + * #define VIRT_PLIC_CONTEXT_BASE 0x200000 + * #define VIRT_PLIC_CONTEXT_STRIDE 0x1000 + * #define VIRT_PLIC_SIZE(__num_context) \ + * (VIRT_PLIC_CONTEXT_BASE + (__num_context) * VIRT_PLIC_CONTEXT_STRIDE) + */ +#define PLIC_BASE 0x0c000000L +#define PLIC_PRIORITY(id) (PLIC_BASE + (id) * 4) +#define PLIC_PENDING(id) (PLIC_BASE + 0x1000 + ((id) / 32)) +#define PLIC_MENABLE(hart) (PLIC_BASE + 0x2000 + (hart) * 0x80) +#define PLIC_MTHRESHOLD(hart) (PLIC_BASE + 0x200000 + (hart) * 0x1000) +#define PLIC_MCLAIM(hart) (PLIC_BASE + 0x200004 + (hart) * 0x1000) +#define PLIC_MCOMPLETE(hart) (PLIC_BASE + 0x200004 + (hart) * 0x1000) + + /* + * The Core Local INTerruptor (CLINT) block holds memory-mapped control and + * status registers associated with software and timer interrupts. + * QEMU-virt reuses sifive configuration for CLINT. + * see https://gitee.com/qemu/qemu/blob/master/include/hw/riscv/sifive_clint.h + * enum { + * SIFIVE_SIP_BASE = 0x0, + * SIFIVE_TIMECMP_BASE = 0x4000, + * SIFIVE_TIME_BASE = 0xBFF8 + * }; + * + * enum { + * SIFIVE_CLINT_TIMEBASE_FREQ = 10000000 + * }; + * + * Notice: + * The machine-level MSIP bit of mip register are written by accesses to + * memory-mapped control registers, which are used by remote harts to provide + * machine-mode interprocessor interrupts. + * For QEMU-virt machine, Each msip register is a 32-bit wide WARL register + * where the upper 31 bits are tied to 0. The least significant bit is + * reflected in the MSIP bit of the mip CSR. We can write msip to generate + * machine-mode software interrupts. A pending machine-level software + * interrupt can be cleared by writing 0 to the MSIP bit in mip. + * On reset, each msip register is cleared to zero. + */ +#define CLINT_BASE 0x2000000L +#define CLINT_MSIP(hartid) (CLINT_BASE + 4 * (hartid)) +#define CLINT_MTIMECMP(hartid) (CLINT_BASE + 0x4000 + 8 * (hartid)) +#define CLINT_MTIME (CLINT_BASE + 0xBFF8) // cycles since boot. + +/* 10000000 ticks per-second */ +#define CLINT_TIMEBASE_FREQ 10000000 + +#endif /* __PLATFORM_H__ */ diff --git a/code/os/07-hwtimer/plic.c b/code/os/07-hwtimer/plic.c new file mode 100644 index 0000000..5354240 --- /dev/null +++ b/code/os/07-hwtimer/plic.c @@ -0,0 +1,81 @@ +#include "os.h" + +void plic_init(void) +{ + int hart = r_tp(); + + /* + * Set priority for UART0. + * + * Each PLIC interrupt source can be assigned a priority by writing + * to its 32-bit memory-mapped priority register. + * The QEMU-virt (the same as FU540-C000) supports 7 levels of priority. + * A priority value of 0 is reserved to mean "never interrupt" and + * effectively disables the interrupt. + * Priority 1 is the lowest active priority, and priority 7 is the highest. + * Ties between global interrupts of the same priority are broken by + * the Interrupt ID; interrupts with the lowest ID have the highest + * effective priority. + */ + *(uint32_t*)PLIC_PRIORITY(UART0_IRQ) = 1; + + /* + * Enable UART0 + * + * Each global interrupt can be enabled by setting the corresponding + * bit in the enables registers. + */ + *(uint32_t*)PLIC_MENABLE(hart)= (1 << UART0_IRQ); + + /* + * Set priority threshold for UART0. + * + * PLIC will mask all interrupts of a priority less than or equal to threshold. + * Maximum threshold is 7. + * For example, a threshold value of zero permits all interrupts with + * non-zero priority, whereas a value of 7 masks all interrupts. + * Notice, the threshold is global for PLIC, not for each interrupt source. + */ + *(uint32_t*)PLIC_MTHRESHOLD(hart) = 0; + + /* enable machine-mode external interrupts. */ + w_mie(r_mie() | MIE_MEIE); + + /* enable machine-mode global interrupts. */ + w_mstatus(r_mstatus() | MSTATUS_MIE); +} + +/* + * DESCRIPTION: + * Query the PLIC what interrupt we should serve. + * Perform an interrupt claim by reading the claim register, which + * returns the ID of the highest-priority pending interrupt or zero if there + * is no pending interrupt. + * A successful claim also atomically clears the corresponding pending bit + * on the interrupt source. + * RETURN VALUE: + * the ID of the highest-priority pending interrupt or zero if there + * is no pending interrupt. + */ +int plic_claim(void) +{ + int hart = r_tp(); + int irq = *(uint32_t*)PLIC_MCLAIM(hart); + return irq; +} + +/* + * DESCRIPTION: + * Writing the interrupt ID it received from the claim (irq) to the + * complete register would signal the PLIC we've served this IRQ. + * The PLIC does not check whether the completion ID is the same as the + * last claim ID for that target. If the completion ID does not match an + * interrupt source that is currently enabled for the target, the completion + * is silently ignored. + * RETURN VALUE: none + */ +void plic_complete(int irq) +{ + int hart = r_tp(); + *(uint32_t*)PLIC_MCOMPLETE(hart) = irq; +} diff --git a/code/os/07-hwtimer/printf.c b/code/os/07-hwtimer/printf.c new file mode 100644 index 0000000..93ace5b --- /dev/null +++ b/code/os/07-hwtimer/printf.c @@ -0,0 +1,138 @@ +#include "os.h" + +/* + * ref: https://github.com/cccriscv/mini-riscv-os/blob/master/05-Preemptive/lib.c + */ + +static int _vsnprintf(char * out, size_t n, const char* s, va_list vl) +{ + int format = 0; + int longarg = 0; + size_t pos = 0; + for (; *s; s++) { + if (format) { + switch(*s) { + case 'l': { + longarg = 1; + break; + } + case 'p': { + longarg = 1; + if (out && pos < n) { + out[pos] = '0'; + } + pos++; + if (out && pos < n) { + out[pos] = 'x'; + } + pos++; + } + case 'x': { + long num = longarg ? va_arg(vl, long) : va_arg(vl, int); + int hexdigits = 2*(longarg ? sizeof(long) : sizeof(int))-1; + for(int i = hexdigits; i >= 0; i--) { + int d = (num >> (4*i)) & 0xF; + if (out && pos < n) { + out[pos] = (d < 10 ? '0'+d : 'a'+d-10); + } + pos++; + } + longarg = 0; + format = 0; + break; + } + case 'd': { + long num = longarg ? va_arg(vl, long) : va_arg(vl, int); + if (num < 0) { + num = -num; + if (out && pos < n) { + out[pos] = '-'; + } + pos++; + } + long digits = 1; + for (long nn = num; nn /= 10; digits++); + for (int i = digits-1; i >= 0; i--) { + if (out && pos + i < n) { + out[pos + i] = '0' + (num % 10); + } + num /= 10; + } + pos += digits; + longarg = 0; + format = 0; + break; + } + case 's': { + const char* s2 = va_arg(vl, const char*); + while (*s2) { + if (out && pos < n) { + out[pos] = *s2; + } + pos++; + s2++; + } + longarg = 0; + format = 0; + break; + } + case 'c': { + if (out && pos < n) { + out[pos] = (char)va_arg(vl,int); + } + pos++; + longarg = 0; + format = 0; + break; + } + default: + break; + } + } else if (*s == '%') { + format = 1; + } else { + if (out && pos < n) { + out[pos] = *s; + } + pos++; + } + } + if (out && pos < n) { + out[pos] = 0; + } else if (out && n) { + out[n-1] = 0; + } + return pos; +} + +static char out_buf[1000]; // buffer for _vprintf() + +static int _vprintf(const char* s, va_list vl) +{ + int res = _vsnprintf(NULL, -1, s, vl); + if (res+1 >= sizeof(out_buf)) { + uart_puts("error: output string size overflow\n"); + while(1) {} + } + _vsnprintf(out_buf, res + 1, s, vl); + uart_puts(out_buf); + return res; +} + +int printf(const char* s, ...) +{ + int res = 0; + va_list vl; + va_start(vl, s); + res = _vprintf(s, vl); + va_end(vl); + return res; +} + +void panic(char *s) +{ + printf("panic: "); + printf(s); + printf("\n"); + while(1){}; +} diff --git a/code/os/07-hwtimer/riscv.h b/code/os/07-hwtimer/riscv.h new file mode 100644 index 0000000..e5bd491 --- /dev/null +++ b/code/os/07-hwtimer/riscv.h @@ -0,0 +1,102 @@ +#ifndef __RISCV_H__ +#define __RISCV_H__ + +#include "types.h" + +/* + * ref: https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/riscv.h + */ + +static inline reg_t r_tp() +{ + reg_t x; + asm volatile("mv %0, tp" : "=r" (x) ); + return x; +} + +/* which hart (core) is this? */ +static inline reg_t r_mhartid() +{ + reg_t x; + asm volatile("csrr %0, mhartid" : "=r" (x) ); + return x; +} + +/* Machine Status Register, mstatus */ +#define MSTATUS_MPP (3 << 11) +#define MSTATUS_SPP (1 << 8) + +#define MSTATUS_MPIE (1 << 7) +#define MSTATUS_SPIE (1 << 5) +#define MSTATUS_UPIE (1 << 4) + +#define MSTATUS_MIE (1 << 3) +#define MSTATUS_SIE (1 << 1) +#define MSTATUS_UIE (1 << 0) + +static inline reg_t r_mstatus() +{ + reg_t x; + asm volatile("csrr %0, mstatus" : "=r" (x) ); + return x; +} + +static inline void w_mstatus(reg_t x) +{ + asm volatile("csrw mstatus, %0" : : "r" (x)); +} + +/* + * machine exception program counter, holds the + * instruction address to which a return from + * exception will go. + */ +static inline void w_mepc(reg_t x) +{ + asm volatile("csrw mepc, %0" : : "r" (x)); +} + +static inline reg_t r_mepc() +{ + reg_t x; + asm volatile("csrr %0, mepc" : "=r" (x)); + return x; +} + +/* Machine Scratch register, for early trap handler */ +static inline void w_mscratch(reg_t x) +{ + asm volatile("csrw mscratch, %0" : : "r" (x)); +} + +/* Machine-mode interrupt vector */ +static inline void w_mtvec(reg_t x) +{ + asm volatile("csrw mtvec, %0" : : "r" (x)); +} + +/* Machine-mode Interrupt Enable */ +#define MIE_MEIE (1 << 11) // external +#define MIE_MTIE (1 << 7) // timer +#define MIE_MSIE (1 << 3) // software + +static inline reg_t r_mie() +{ + reg_t x; + asm volatile("csrr %0, mie" : "=r" (x) ); + return x; +} + +static inline void w_mie(reg_t x) +{ + asm volatile("csrw mie, %0" : : "r" (x)); +} + +static inline reg_t r_mcause() +{ + reg_t x; + asm volatile("csrr %0, mcause" : "=r" (x) ); + return x; +} + +#endif /* __RISCV_H__ */ diff --git a/code/os/07-hwtimer/sched.c b/code/os/07-hwtimer/sched.c new file mode 100644 index 0000000..12a9995 --- /dev/null +++ b/code/os/07-hwtimer/sched.c @@ -0,0 +1,76 @@ +#include "os.h" + +/* defined in entry.S */ +extern void switch_to(struct context *next); + +#define MAX_TASKS 10 +#define STACK_SIZE 1024 +uint8_t task_stack[MAX_TASKS][STACK_SIZE]; +struct context ctx_tasks[MAX_TASKS]; + +/* + * _top is used to mark the max available position of ctx_tasks + * _current is used to point to the context of current task + */ +static int _top = 0; +static int _current = -1; + +void sched_init() +{ + w_mscratch(0); +} + +/* + * implment a simple cycle FIFO schedular + */ +void schedule() +{ + if (_top <= 0) { + panic("Num of task should be greater than zero!"); + return; + } + + _current = (_current + 1) % _top; + struct context *next = &(ctx_tasks[_current]); + switch_to(next); +} + +/* + * DESCRIPTION + * Create a task. + * - start_routin: task routune entry + * RETURN VALUE + * 0: success + * -1: if error occured + */ +int task_create(void (*start_routin)(void)) +{ + if (_top < MAX_TASKS) { + ctx_tasks[_top].sp = (reg_t) &task_stack[_top][STACK_SIZE - 1]; + ctx_tasks[_top].ra = (reg_t) start_routin; + _top++; + return 0; + } else { + return -1; + } +} + +/* + * DESCRIPTION + * task_yield() causes the calling task to relinquish the CPU and a new + * task gets to run. + */ +void task_yield() +{ + schedule(); +} + +/* + * a very rough implementaion, just to consume the cpu + */ +void task_delay(volatile int count) +{ + count *= 50000; + while (count--); +} + diff --git a/code/os/07-hwtimer/start.S b/code/os/07-hwtimer/start.S new file mode 100644 index 0000000..6747e7c --- /dev/null +++ b/code/os/07-hwtimer/start.S @@ -0,0 +1,41 @@ +#include "platform.h" + + .equ STACK_SIZE, 8192 + + .global _start + + .text +_start: + # park harts with id != 0 + csrr t0, mhartid # read current hart id + mv tp, t0 # keep CPU's hartid in its tp for later usage. + bnez t0, park # if we're not on the hart 0 + # we park the hart + + # Set all bytes in the BSS section to zero. + la a0, _bss_start + la a1, _bss_end + bgeu a0, a1, 2f +1: + sw zero, (a0) + addi a0, a0, 4 + bltu a0, a1, 1b +2: + # Setup stacks, the stack grows from bottom to top, so we put the + # stack pointer to the very end of the stack range. + slli t0, t0, 10 # shift left the hart id by 1024 + la sp, stacks + STACK_SIZE # set the initial stack pointer + # to the end of the stack space + add sp, sp, t0 # move the current hart stack pointer + # to its place in the stack space + + j start_kernel # hart 0 jump to c + +park: + wfi + j park + +stacks: + .skip STACK_SIZE * MAXNUM_CPU # allocate space for the harts stacks + + .end # End of file diff --git a/code/os/07-hwtimer/timer.c b/code/os/07-hwtimer/timer.c new file mode 100644 index 0000000..76f9ab1 --- /dev/null +++ b/code/os/07-hwtimer/timer.c @@ -0,0 +1,38 @@ +#include "os.h" + +/* interval ~= 1s */ +#define TIMER_INTERVAL CLINT_TIMEBASE_FREQ + +static uint32_t _tick = 0; + +/* load timer interval(in ticks) for next timer interrupt.*/ +void timer_load(int interval) +{ + /* each CPU has a separate source of timer interrupts. */ + int id = r_mhartid(); + + *(uint64_t*)CLINT_MTIMECMP(id) = *(uint64_t*)CLINT_MTIME + interval; +} + +void timer_init() +{ + /* + * On reset, mtime is cleared to zero, but the mtimecmp registers + * are not reset. So we have to init the mtimecmp manually. + */ + timer_load(TIMER_INTERVAL); + + /* enable machine-mode timer interrupts. */ + w_mie(r_mie() | MIE_MTIE); + + /* enable machine-mode global interrupts. */ + w_mstatus(r_mstatus() | MSTATUS_MIE); +} + +void timer_handler() +{ + _tick++; + printf("tick: %d\n", _tick); + + timer_load(TIMER_INTERVAL); +} diff --git a/code/os/07-hwtimer/trap.c b/code/os/07-hwtimer/trap.c new file mode 100644 index 0000000..c42b0a8 --- /dev/null +++ b/code/os/07-hwtimer/trap.c @@ -0,0 +1,79 @@ +#include "os.h" + +extern void trap_vector(void); +extern void uart_isr(void); +extern void timer_handler(void); + +void trap_init() +{ + /* + * set the trap-vector base-address for machine-mode + */ + w_mtvec((reg_t)trap_vector); +} + +void external_interrupt_handler() +{ + int irq = plic_claim(); + + if (irq == UART0_IRQ){ + uart_isr(); + } else if (irq) { + printf("unexpected interrupt irq = %d\n", irq); + } + + if (irq) { + plic_complete(irq); + } +} + +reg_t trap_handler(reg_t epc, reg_t cause) +{ + reg_t return_pc = epc; + reg_t cause_code = cause & 0xfff; + + if (cause & 0x80000000) { + /* Asynchronous trap - interrupt */ + switch (cause_code) { + case 3: + uart_puts("software interruption!\n"); + break; + case 7: + uart_puts("timer interruption!\n"); + timer_handler(); + break; + case 11: + uart_puts("external interruption!\n"); + external_interrupt_handler(); + break; + default: + uart_puts("unknown async exception!\n"); + break; + } + } else { + /* Synchronous trap - exception */ + printf("Sync exceptions!, code = %d\n", cause_code); + panic("OOPS! What can I do!"); + //return_pc += 4; + } + + return return_pc; +} + +void trap_test() +{ + /* + * Synchronous exception code = 7 + * Store/AMO access fault + */ + *(int *)0x00000000 = 100; + + /* + * Synchronous exception code = 5 + * Load access fault + */ + //int a = *(int *)0x00000000; + + uart_puts("Yeah! I'm return back from trap!\n"); +} + diff --git a/code/os/07-hwtimer/types.h b/code/os/07-hwtimer/types.h new file mode 100644 index 0000000..5051639 --- /dev/null +++ b/code/os/07-hwtimer/types.h @@ -0,0 +1,14 @@ +#ifndef __TYPES_H__ +#define __TYPES_H__ + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long long uint64_t; + +/* + * RISCV32: register is 32bits width + */ +typedef uint32_t reg_t; + +#endif /* __TYPES_H__ */ diff --git a/code/os/07-hwtimer/uart.c b/code/os/07-hwtimer/uart.c new file mode 100644 index 0000000..2905331 --- /dev/null +++ b/code/os/07-hwtimer/uart.c @@ -0,0 +1,149 @@ +#include "os.h" + +/* + * The UART control registers are memory-mapped at address UART0. + * This macro returns the address of one of the registers. + */ +#define UART_REG(reg) ((volatile uint8_t *)(UART0 + reg)) + +/* + * Reference + * [1]: TECHNICAL DATA ON 16550, http://byterunner.com/16550.html + */ + +/* + * UART control registers map. see [1] "PROGRAMMING TABLE" + * note some are reused by multiple functions + * 0 (write mode): THR/DLL + * 1 (write mode): IER/DLM + */ +#define RHR 0 // Receive Holding Register (read mode) +#define THR 0 // Transmit Holding Register (write mode) +#define DLL 0 // LSB of Divisor Latch (write mode) +#define IER 1 // Interrupt Enable Register (write mode) +#define DLM 1 // MSB of Divisor Latch (write mode) +#define FCR 2 // FIFO Control Register (write mode) +#define ISR 2 // Interrupt Status Register (read mode) +#define LCR 3 // Line Control Register +#define MCR 4 // Modem Control Register +#define LSR 5 // Line Status Register +#define MSR 6 // Modem Status Register +#define SPR 7 // ScratchPad Register + +/* + * POWER UP DEFAULTS + * IER = 0: TX/RX holding register interrupts are bith disabled + * ISR = 1: no interrupt penting + * LCR = 0 + * MCR = 0 + * LSR = 60 HEX + * MSR = BITS 0-3 = 0, BITS 4-7 = inputs + * FCR = 0 + * TX = High + * OP1 = High + * OP2 = High + * RTS = High + * DTR = High + * RXRDY = High + * TXRDY = Low + * INT = Low + */ + +/* + * LINE STATUS REGISTER (LSR) + * LSR BIT 0: + * 0 = no data in receive holding register or FIFO. + * 1 = data has been receive and saved in the receive holding register or FIFO. + * ...... + * LSR BIT 5: + * 0 = transmit holding register is full. 16550 will not accept any data for transmission. + * 1 = transmitter hold register (or FIFO) is empty. CPU can load the next character. + * ...... + */ +#define LSR_RX_READY (1 << 0) +#define LSR_TX_IDLE (1 << 5) + +#define uart_read_reg(reg) (*(UART_REG(reg))) +#define uart_write_reg(reg, v) (*(UART_REG(reg)) = (v)) + +void uart_init() +{ + /* disable interrupts. */ + uart_write_reg(IER, 0x00); + + /* + * Setting baud rate. Just a demo here if we care about the divisor, + * but for our purpose [QEMU-virt], this doesn't really do anything. + * + * Notice that the divisor register DLL (divisor latch least) and DLM (divisor + * latch most) have the same base address as the receiver/transmitter and the + * interrupt enable register. To change what the base address points to, we + * open the "divisor latch" by writing 1 into the Divisor Latch Access Bit + * (DLAB), which is bit index 7 of the Line Control Register (LCR). + * + * Regarding the baud rate value, see [1] "BAUD RATE GENERATOR PROGRAMMING TABLE". + * We use 38.4K when 1.8432 MHZ crystal, so the corresponding value is 3. + * And due to the divisor register is two bytes (16 bits), so we need to + * split the value of 3(0x0003) into two bytes, DLL stores the low byte, + * DLM stores the high byte. + */ + uint8_t lcr = uart_read_reg(LCR); + uart_write_reg(LCR, lcr | (1 << 7)); + uart_write_reg(DLL, 0x03); + uart_write_reg(DLM, 0x00); + + /* + * Continue setting the asynchronous data communication format. + * - number of the word length: 8 bits + * - number of stop bits:1 bit when word length is 8 bits + * - no parity + * - no break control + * - disabled baud latch + */ + lcr = 0; + uart_write_reg(LCR, lcr | (3 << 0)); + + /* + * enable receive interrupts. + */ + uint8_t ier = uart_read_reg(IER); + uart_write_reg(IER, ier | (1 << 0)); +} + +int uart_putc(char ch) +{ + while ((uart_read_reg(LSR) & LSR_TX_IDLE) == 0); + return uart_write_reg(THR, ch); +} + +void uart_puts(char *s) +{ + while (*s) { + uart_putc(*s++); + } +} + +int uart_getc(void) +{ + if (uart_read_reg(LSR) & LSR_RX_READY){ + return uart_read_reg(RHR); + } else { + return -1; + } +} + +/* + * handle a uart interrupt, raised because input has arrived, called from trap.c. + */ +void uart_isr(void) +{ + while (1) { + int c = uart_getc(); + if (c == -1) { + break; + } else { + uart_putc((char)c); + uart_putc('\n'); + } + } +} diff --git a/code/os/07-hwtimer/user.c b/code/os/07-hwtimer/user.c new file mode 100644 index 0000000..6c9484d --- /dev/null +++ b/code/os/07-hwtimer/user.c @@ -0,0 +1,31 @@ +#include "os.h" + +#define DELAY 1000 + +void user_task0(void) +{ + uart_puts("Task 0: Created!\n"); + while (1) { + uart_puts("Task 0: Running...\n"); + task_delay(DELAY); + task_yield(); + } +} + +void user_task1(void) +{ + uart_puts("Task 1: Created!\n"); + while (1) { + uart_puts("Task 1: Running...\n"); + task_delay(DELAY); + task_yield(); + } +} + +/* NOTICE: DON'T LOOP INFINITELY IN main() */ +void os_main(void) +{ + task_create(user_task0); + task_create(user_task1); +} + diff --git a/code/os/08-preemptive/Makefile b/code/os/08-preemptive/Makefile new file mode 100644 index 0000000..7a65c62 --- /dev/null +++ b/code/os/08-preemptive/Makefile @@ -0,0 +1,65 @@ +CROSS_COMPILE = riscv64-unknown-elf- +CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall + +QEMU = qemu-system-riscv32 +QFLAGS = -nographic -smp 1 -machine virt -bios none + +GDB = ${CROSS_COMPILE}gdb +CC = ${CROSS_COMPILE}gcc +OBJCOPY = ${CROSS_COMPILE}objcopy +OBJDUMP = ${CROSS_COMPILE}objdump + +SRCS_ASM = \ + start.S \ + mem.S \ + entry.S \ + +SRCS_C = \ + kernel.c \ + uart.c \ + printf.c \ + page.c \ + sched.c \ + user.c \ + trap.c \ + plic.c \ + timer.c \ + +OBJS = $(SRCS_ASM:.S=.o) +OBJS += $(SRCS_C:.c=.o) + +.DEFAULT_GOAL := all +all: os.elf + +# start.o must be the first in dependency! +os.elf: ${OBJS} + ${CC} $(CFLAGS) -T os.ld -o os.elf $^ + ${OBJCOPY} -O binary os.elf os.bin + +%.o : %.c + ${CC} ${CFLAGS} -c -o $@ $< + +%.o : %.S + ${CC} ${CFLAGS} -c -o $@ $< + +run: all + @${QEMU} -M ? | grep virt >/dev/null || exit + @echo "Press Ctrl-A and then X to exit QEMU" + @echo "------------------------------------" + @${QEMU} ${QFLAGS} -kernel os.elf + +.PHONY : debug +debug: all + @echo "Press Ctrl-C and then input 'quit' to exit GDB and QEMU" + @echo "-------------------------------------------------------" + @${QEMU} ${QFLAGS} -kernel os.elf -s -S & + @${GDB} os.elf -q -x ../gdbinit + +.PHONY : code +code: all + @${OBJDUMP} -S os.elf | less + +.PHONY : clean +clean: + rm -rf *.o *.bin *.elf + diff --git a/code/os/08-preemptive/entry.S b/code/os/08-preemptive/entry.S new file mode 100644 index 0000000..146c75d --- /dev/null +++ b/code/os/08-preemptive/entry.S @@ -0,0 +1,134 @@ +# save all General-Purpose(GP) registers to context +# struct context *base = &ctx_task; +# base->ra = ra; +# ...... +.macro reg_save base + sw ra, 0(\base) + sw sp, 4(\base) + sw gp, 8(\base) + sw tp, 12(\base) + sw t0, 16(\base) + sw t1, 20(\base) + sw t2, 24(\base) + sw s0, 28(\base) + sw s1, 32(\base) + sw a0, 36(\base) + sw a1, 40(\base) + sw a2, 44(\base) + sw a3, 48(\base) + sw a4, 52(\base) + sw a5, 56(\base) + sw a6, 60(\base) + sw a7, 64(\base) + sw s2, 68(\base) + sw s3, 72(\base) + sw s4, 76(\base) + sw s5, 80(\base) + sw s6, 84(\base) + sw s7, 88(\base) + sw s8, 92(\base) + sw s9, 96(\base) + sw s10, 100(\base) + sw s11, 104(\base) + sw t3, 108(\base) + sw t4, 112(\base) + sw t5, 116(\base) + sw t6, 120(\base) +.endm + +# restore all General-Purpose(GP) registers from the context +# struct context *base = &ctx_task; +# ra = base->ra; +# ...... +.macro reg_restore base + lw ra, 0(\base) + lw sp, 4(\base) + lw gp, 8(\base) + lw tp, 12(\base) + lw t0, 16(\base) + lw t1, 20(\base) + lw t2, 24(\base) + lw s0, 28(\base) + lw s1, 32(\base) + lw a0, 36(\base) + lw a1, 40(\base) + lw a2, 44(\base) + lw a3, 48(\base) + lw a4, 52(\base) + lw a5, 56(\base) + lw a6, 60(\base) + lw a7, 64(\base) + lw s2, 68(\base) + lw s3, 72(\base) + lw s4, 76(\base) + lw s5, 80(\base) + lw s6, 84(\base) + lw s7, 88(\base) + lw s8, 92(\base) + lw s9, 96(\base) + lw s10, 100(\base) + lw s11, 104(\base) + lw t3, 108(\base) + lw t4, 112(\base) + lw t5, 116(\base) + lw t6, 120(\base) +.endm + +# Something to note about save/restore: +# - We use mscratch to hold a pointer to context of previous task +# - We use t6 as the 'base' for reg_save/reg_restore, because it is the +# very bottom register (x31) and would not be overwritten during loading. + +.text + +# interrupts and exceptions while in machine mode come here. +.globl trap_vector +# the trap vector base address must always be aligned on a 4-byte boundary +.align 4 +trap_vector: + # save context(registers). + csrrw t6, mscratch, t6 # swap t6 and mscratch + reg_save t6 + csrw mscratch, t6 + + # save mepc to context of current task + csrr a0, mepc + sw a0, 124(t6) + + # call the C trap handler in trap.c + csrr a0, mepc + csrr a1, mcause + call trap_handler + + # trap_handler will return the return address via a0. + csrw mepc, a0 + + # restore context(registers). + csrr t6, mscratch + reg_restore t6 + + # return to whatever we were doing before trap. + mret + +# void switch_to(struct context *next); +# a0: pointer to the context of the next task +.globl switch_to +.align 4 +switch_to: + # switch mscratch to point to the context of the next task + csrw mscratch, a0 + # set mepc to the pc of the next task + lw a1, 124(a0) + csrw mepc, a1 + + # Restore all GP registers + # Use t6 to point to the context of the new task + mv t6, a0 + reg_restore t6 + + # Do actual context switching. + # Notice this will enable global interrupt + mret + +.end + diff --git a/code/os/08-preemptive/kernel.c b/code/os/08-preemptive/kernel.c new file mode 100644 index 0000000..b4e8096 --- /dev/null +++ b/code/os/08-preemptive/kernel.c @@ -0,0 +1,38 @@ +#include "os.h" + +/* + * Following functions SHOULD be called ONLY ONE time here, + * so just declared here ONCE and NOT included in file os.h. + */ +extern void uart_init(void); +extern void page_init(void); +extern void sched_init(void); +extern void schedule(void); +extern void os_main(void); +extern void trap_init(void); +extern void plic_init(void); +extern void timer_init(void); + +void start_kernel(void) +{ + uart_init(); + uart_puts("Hello, RVOS!\n"); + + page_init(); + + trap_init(); + + plic_init(); + + timer_init(); + + sched_init(); + + os_main(); + + schedule(); + + uart_puts("Would not go here!\n"); + while (1) {}; // stop here! +} + diff --git a/code/os/08-preemptive/mem.S b/code/os/08-preemptive/mem.S new file mode 100644 index 0000000..93ba0d6 --- /dev/null +++ b/code/os/08-preemptive/mem.S @@ -0,0 +1,30 @@ +.section .rodata +.global HEAP_START +HEAP_START: .word _heap_start + +.global HEAP_SIZE +HEAP_SIZE: .word _heap_size + +.global TEXT_START +TEXT_START: .word _text_start + +.global TEXT_END +TEXT_END: .word _text_end + +.global DATA_START +DATA_START: .word _data_start + +.global DATA_END +DATA_END: .word _data_end + +.global RODATA_START +RODATA_START: .word _rodata_start + +.global RODATA_END +RODATA_END: .word _rodata_end + +.global BSS_START +BSS_START: .word _bss_start + +.global BSS_END +BSS_END: .word _bss_end diff --git a/code/os/08-preemptive/os.h b/code/os/08-preemptive/os.h new file mode 100644 index 0000000..d190d66 --- /dev/null +++ b/code/os/08-preemptive/os.h @@ -0,0 +1,73 @@ +#ifndef __OS_H__ +#define __OS_H__ + +#include "types.h" +#include "riscv.h" +#include "platform.h" + +#include +#include + +/* uart */ +extern int uart_putc(char ch); +extern void uart_puts(char *s); +extern int uart_getc(void); + +/* printf */ +extern int printf(const char* s, ...); +extern void panic(char *s); + +/* memory management */ +extern void *page_alloc(int npages); +extern void page_free(void *p); + +/* task management */ +struct context { + /* ignore x0 */ + reg_t ra; + reg_t sp; + reg_t gp; + reg_t tp; + reg_t t0; + reg_t t1; + reg_t t2; + reg_t s0; + reg_t s1; + reg_t a0; + reg_t a1; + reg_t a2; + reg_t a3; + reg_t a4; + reg_t a5; + reg_t a6; + reg_t a7; + reg_t s2; + reg_t s3; + reg_t s4; + reg_t s5; + reg_t s6; + reg_t s7; + reg_t s8; + reg_t s9; + reg_t s10; + reg_t s11; + reg_t t3; + reg_t t4; + reg_t t5; + reg_t t6; + // upon is trap frame + + // save the pc to run in next schedule cycle + reg_t pc; // offset: 31 *4 = 124 +}; + +extern int task_create(void (*task)(void)); +extern void task_delay(volatile int count); +extern void task_yield(); + +/* plic */ +extern int plic_claim(void); +extern void plic_complete(int irq); + + +#endif /* __OS_H__ */ diff --git a/code/os/08-preemptive/os.ld b/code/os/08-preemptive/os.ld new file mode 100644 index 0000000..a75cd93 --- /dev/null +++ b/code/os/08-preemptive/os.ld @@ -0,0 +1,136 @@ +/* + * rvos.ld + * Linker script for outputting to RVOS + */ + +/* + * https://sourceware.org/binutils/docs/ld/Miscellaneous-Commands.html + * OUTPUT_ARCH command specifies a particular output machine architecture. + * "riscv" is the name of the architecture for both 64-bit and 32-bit + * RISC-V target. We will further refine this by using -march=rv32ima + * and -mabi=ilp32 when calling gcc. + */ +OUTPUT_ARCH( "riscv" ) + +/* + * https://sourceware.org/binutils/docs/ld/Entry-Point.html + * ENTRY command is used to set the "entry point", which is the first instruction + * to execute in a program. + * The argument of ENTRY command is a symbol name, here is "_start" which is + * defined in start.S. + */ +ENTRY( _start ) + +/* + * https://sourceware.org/binutils/docs/ld/MEMORY.html + * The MEMORY command describes the location and size of blocks of memory in + * the target. + * The syntax for MEMORY is: + * MEMORY + * { + * name [(attr)] : ORIGIN = origin, LENGTH = len + * ...... + * } + * Each line defines a memory region. + * Each memory region must have a distinct name within the MEMORY command. Here + * we only define one region named as "ram". + * The "attr" string is an optional list of attributes that specify whether to + * use a particular memory region for an input section which is not explicitly + * mapped in the linker script. Here we assign 'w' (writeable), 'x' (executable), + * and 'a' (allocatable). We use '!' to invert 'r' (read-only) and + * 'i' (initialized). + * The "ORIGIN" is used to set the start address of the memory region. Here we + * place it right at the beginning of 0x8000_0000 because this is where the + * QEMU-virt machine will start executing. + * Finally LENGTH = 128M tells the linker that we have 128 megabyte of RAM. + * The linker will double check this to make sure everything can fit. + */ +MEMORY +{ + ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M +} + +/* + * https://sourceware.org/binutils/docs/ld/SECTIONS.html + * The SECTIONS command tells the linker how to map input sections into output + * sections, and how to place the output sections in memory. + * The format of the SECTIONS command is: + * SECTIONS + * { + * sections-command + * sections-command + * ...... + * } + * + * Each sections-command may of be one of the following: + * (1) an ENTRY command + * (2) a symbol assignment + * (3) an output section description + * (4) an overlay description + * We here only demo (2) & (3). + * + * We use PROVIDE command to define symbols. + * https://sourceware.org/binutils/docs/ld/PROVIDE.html + * The PROVIDE keyword may be used to define a symbol. + * The syntax is PROVIDE(symbol = expression). + * Such symbols as "_text_start", "_text_end" ... will be used in mem.S. + * Notice the period '.' tells the linker to set symbol(e.g. _text_start) to + * the CURRENT location ('.' = current memory location). This current memory + * location moves as we add things. + */ +SECTIONS +{ + /* + * We are going to layout all text sections in .text output section, + * starting with .text. The asterisk("*") in front of the + * parentheses means to match the .text section of ANY object file. + */ + .text : { + PROVIDE(_text_start = .); + *(.text .text.*) + PROVIDE(_text_end = .); + } >ram + + .rodata : { + PROVIDE(_rodata_start = .); + *(.rodata .rodata.*) + PROVIDE(_rodata_end = .); + } >ram + + .data : { + /* + * . = ALIGN(4096) tells the linker to align the current memory + * location to 4096 bytes. This will insert padding bytes until + * current location becomes aligned on 4096-byte boundary. + * This is because our paging system's resolution is 4,096 bytes. + */ + . = ALIGN(4096); + PROVIDE(_data_start = .); + /* + * sdata and data are essentially the same thing. We do not need + * to distinguish sdata from data. + */ + *(.sdata .sdata.*) + *(.data .data.*) + PROVIDE(_data_end = .); + } >ram + + .bss :{ + /* + * https://sourceware.org/binutils/docs/ld/Input-Section-Common.html + * In most cases, common symbols in input files will be placed + * in the ‘.bss’ section in the output file. + */ + PROVIDE(_bss_start = .); + *(.sbss .sbss.*) + *(.bss .bss.*) + *(COMMON) + PROVIDE(_bss_end = .); + } >ram + + PROVIDE(_memory_start = ORIGIN(ram)); + PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram)); + + PROVIDE(_heap_start = _bss_end); + PROVIDE(_heap_size = _memory_end - _heap_start); +} diff --git a/code/os/08-preemptive/page.c b/code/os/08-preemptive/page.c new file mode 100644 index 0000000..addf097 --- /dev/null +++ b/code/os/08-preemptive/page.c @@ -0,0 +1,189 @@ +#include "os.h" + +/* + * Following global vars are defined in mem.S + */ +extern uint32_t TEXT_START; +extern uint32_t TEXT_END; +extern uint32_t DATA_START; +extern uint32_t DATA_END; +extern uint32_t RODATA_START; +extern uint32_t RODATA_END; +extern uint32_t BSS_START; +extern uint32_t BSS_END; +extern uint32_t HEAP_START; +extern uint32_t HEAP_SIZE; + +/* + * _alloc_start points to the actual start address of heap pool + * _alloc_end points to the actual end address of heap pool + * _num_pages holds the actual max number of pages we can allocate. + */ +static uint32_t _alloc_start = 0; +static uint32_t _alloc_end = 0; +static uint32_t _num_pages = 0; + +#define PAGE_SIZE 4096 +#define PAGE_ORDER 12 + +#define PAGE_TAKEN (uint8_t)(1 << 0) +#define PAGE_LAST (uint8_t)(1 << 1) + +/* + * Page Descriptor + * flags: + * - bit 0: flag if this page is taken(allocated) + * - bit 1: flag if this page is the last page of the memory block allocated + */ +struct Page { + uint8_t flags; +}; + +static inline void _clear(struct Page *page) +{ + page->flags = 0; +} + +static inline int _is_free(struct Page *page) +{ + if (page->flags & PAGE_TAKEN) { + return 0; + } else { + return 1; + } +} + +static inline void _set_flag(struct Page *page, uint8_t flags) +{ + page->flags |= flags; +} + +static inline int _is_last(struct Page *page) +{ + if (page->flags & PAGE_LAST) { + return 1; + } else { + return 0; + } +} + +/* + * align the address to the border of page(4K) + */ +static inline uint32_t _align_page(uint32_t address) +{ + uint32_t order = (1 << PAGE_ORDER) - 1; + return (address + order) & (~order); +} + +void page_init() +{ + /* + * We reserved 8 Page (8 x 4096) to hold the Page structures. + * It should be enough to manage at most 128 MB (8 x 4096 x 4096) + */ + _num_pages = (HEAP_SIZE / PAGE_SIZE) - 8; + printf("HEAP_START = %x, HEAP_SIZE = %x, num of pages = %d\n", HEAP_START, HEAP_SIZE, _num_pages); + + struct Page *page = (struct Page *)HEAP_START; + for (int i = 0; i < _num_pages; i++) { + _clear(page); + page++; + } + + _alloc_start = _align_page(HEAP_START + 8 * PAGE_SIZE); + _alloc_end = _alloc_start + (PAGE_SIZE * _num_pages); + + printf("TEXT: 0x%x -> 0x%x\n", TEXT_START, TEXT_END); + printf("RODATA: 0x%x -> 0x%x\n", RODATA_START, RODATA_END); + printf("DATA: 0x%x -> 0x%x\n", DATA_START, DATA_END); + printf("BSS: 0x%x -> 0x%x\n", BSS_START, BSS_END); + printf("HEAP: 0x%x -> 0x%x\n", _alloc_start, _alloc_end); +} + +/* + * Allocate a memory block which is composed of contiguous physical pages + * - npages: the number of PAGE_SIZE pages to allocate + */ +void *page_alloc(int npages) +{ + /* Note we are searching the page descriptor bitmaps. */ + int found = 0; + struct Page *page_i = (struct Page *)HEAP_START; + for (int i = 0; i < (_num_pages - npages); i++) { + if (_is_free(page_i)) { + found = 1; + /* + * meet a free page, continue to check if following + * (npages - 1) pages are also unallocated. + */ + struct Page *page_j = page_i; + for (int j = i; j < (i + npages); j++) { + if (!_is_free(page_j)) { + found = 0; + break; + } + page_j++; + } + /* + * get a memory block which is good enough for us, + * take housekeeping, then return the actual start + * address of the first page of this memory block + */ + if (found) { + struct Page *page_k = page_i; + for (int k = i; k < (i + npages); k++) { + _set_flag(page_k, PAGE_TAKEN); + page_k++; + } + page_k--; + _set_flag(page_k, PAGE_LAST); + return (void *)(_alloc_start + i * PAGE_SIZE); + } + } + page_i++; + } + return NULL; +} + +/* + * Free the memory block + * - p: start address of the memory block + */ +void page_free(void *p) +{ + /* + * Assert (TBD) if p is invalid + */ + if (!p || (uint32_t)p >= _alloc_end) { + return; + } + /* get the first page descriptor of this memory block */ + struct Page *page = (struct Page *)HEAP_START; + page += ((uint32_t)p - _alloc_start)/ PAGE_SIZE; + /* loop and clear all the page descriptors of the memory block */ + while (!_is_free(page)) { + if (_is_last(page)) { + _clear(page); + break; + } else { + _clear(page); + page++;; + } + } +} + +void page_test() +{ + void *p = page_alloc(2); + printf("p = 0x%x\n", p); + //page_free(p); + + void *p2 = page_alloc(7); + printf("p2 = 0x%x\n", p2); + page_free(p2); + + void *p3 = page_alloc(4); + printf("p3 = 0x%x\n", p3); +} + diff --git a/code/os/08-preemptive/platform.h b/code/os/08-preemptive/platform.h new file mode 100644 index 0000000..3319f4f --- /dev/null +++ b/code/os/08-preemptive/platform.h @@ -0,0 +1,97 @@ +#ifndef __PLATFORM_H__ +#define __PLATFORM_H__ + +/* + * QEMU RISC-V Virt machine with 16550a UART and VirtIO MMIO + */ + +/* + * maximum number of CPUs + * see https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h + * #define VIRT_CPUS_MAX 8 + */ +#define MAXNUM_CPU 8 + +/* + * MemoryMap + * see https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c, virt_memmap[] + * 0x00001000 -- boot ROM, provided by qemu + * 0x02000000 -- CLINT + * 0x0C000000 -- PLIC + * 0x10000000 -- UART0 + * 0x10001000 -- virtio disk + * 0x80000000 -- boot ROM jumps here in machine mode, where we load our kernel + */ + +/* This machine puts UART registers here in physical memory. */ +#define UART0 0x10000000L + +/* + * UART0 interrupt source + * see https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h + * enum { + * UART0_IRQ = 10, + * ...... + * }; + */ +#define UART0_IRQ 10 + +/* + * This machine puts platform-level interrupt controller (PLIC) here. + * Here only list PLIC registers in Machine mode. + * see https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h + * #define VIRT_PLIC_HART_CONFIG "MS" + * #define VIRT_PLIC_NUM_SOURCES 127 + * #define VIRT_PLIC_NUM_PRIORITIES 7 + * #define VIRT_PLIC_PRIORITY_BASE 0x04 + * #define VIRT_PLIC_PENDING_BASE 0x1000 + * #define VIRT_PLIC_ENABLE_BASE 0x2000 + * #define VIRT_PLIC_ENABLE_STRIDE 0x80 + * #define VIRT_PLIC_CONTEXT_BASE 0x200000 + * #define VIRT_PLIC_CONTEXT_STRIDE 0x1000 + * #define VIRT_PLIC_SIZE(__num_context) \ + * (VIRT_PLIC_CONTEXT_BASE + (__num_context) * VIRT_PLIC_CONTEXT_STRIDE) + */ +#define PLIC_BASE 0x0c000000L +#define PLIC_PRIORITY(id) (PLIC_BASE + (id) * 4) +#define PLIC_PENDING(id) (PLIC_BASE + 0x1000 + ((id) / 32)) +#define PLIC_MENABLE(hart) (PLIC_BASE + 0x2000 + (hart) * 0x80) +#define PLIC_MTHRESHOLD(hart) (PLIC_BASE + 0x200000 + (hart) * 0x1000) +#define PLIC_MCLAIM(hart) (PLIC_BASE + 0x200004 + (hart) * 0x1000) +#define PLIC_MCOMPLETE(hart) (PLIC_BASE + 0x200004 + (hart) * 0x1000) + + /* + * The Core Local INTerruptor (CLINT) block holds memory-mapped control and + * status registers associated with software and timer interrupts. + * QEMU-virt reuses sifive configuration for CLINT. + * see https://gitee.com/qemu/qemu/blob/master/include/hw/riscv/sifive_clint.h + * enum { + * SIFIVE_SIP_BASE = 0x0, + * SIFIVE_TIMECMP_BASE = 0x4000, + * SIFIVE_TIME_BASE = 0xBFF8 + * }; + * + * enum { + * SIFIVE_CLINT_TIMEBASE_FREQ = 10000000 + * }; + * + * Notice: + * The machine-level MSIP bit of mip register are written by accesses to + * memory-mapped control registers, which are used by remote harts to provide + * machine-mode interprocessor interrupts. + * For QEMU-virt machine, Each msip register is a 32-bit wide WARL register + * where the upper 31 bits are tied to 0. The least significant bit is + * reflected in the MSIP bit of the mip CSR. We can write msip to generate + * machine-mode software interrupts. A pending machine-level software + * interrupt can be cleared by writing 0 to the MSIP bit in mip. + * On reset, each msip register is cleared to zero. + */ +#define CLINT_BASE 0x2000000L +#define CLINT_MSIP(hartid) (CLINT_BASE + 4 * (hartid)) +#define CLINT_MTIMECMP(hartid) (CLINT_BASE + 0x4000 + 8 * (hartid)) +#define CLINT_MTIME (CLINT_BASE + 0xBFF8) // cycles since boot. + +/* 10000000 ticks per-second */ +#define CLINT_TIMEBASE_FREQ 10000000 + +#endif /* __PLATFORM_H__ */ diff --git a/code/os/08-preemptive/plic.c b/code/os/08-preemptive/plic.c new file mode 100644 index 0000000..c28a8d1 --- /dev/null +++ b/code/os/08-preemptive/plic.c @@ -0,0 +1,78 @@ +#include "os.h" + +void plic_init(void) +{ + int hart = r_tp(); + + /* + * Set priority for UART0. + * + * Each PLIC interrupt source can be assigned a priority by writing + * to its 32-bit memory-mapped priority register. + * The QEMU-virt (the same as FU540-C000) supports 7 levels of priority. + * A priority value of 0 is reserved to mean "never interrupt" and + * effectively disables the interrupt. + * Priority 1 is the lowest active priority, and priority 7 is the highest. + * Ties between global interrupts of the same priority are broken by + * the Interrupt ID; interrupts with the lowest ID have the highest + * effective priority. + */ + *(uint32_t*)PLIC_PRIORITY(UART0_IRQ) = 1; + + /* + * Enable UART0 + * + * Each global interrupt can be enabled by setting the corresponding + * bit in the enables registers. + */ + *(uint32_t*)PLIC_MENABLE(hart)= (1 << UART0_IRQ); + + /* + * Set priority threshold for UART0. + * + * PLIC will mask all interrupts of a priority less than or equal to threshold. + * Maximum threshold is 7. + * For example, a threshold value of zero permits all interrupts with + * non-zero priority, whereas a value of 7 masks all interrupts. + * Notice, the threshold is global for PLIC, not for each interrupt source. + */ + *(uint32_t*)PLIC_MTHRESHOLD(hart) = 0; + + /* enable machine-mode external interrupts. */ + w_mie(r_mie() | MIE_MEIE); +} + +/* + * DESCRIPTION: + * Query the PLIC what interrupt we should serve. + * Perform an interrupt claim by reading the claim register, which + * returns the ID of the highest-priority pending interrupt or zero if there + * is no pending interrupt. + * A successful claim also atomically clears the corresponding pending bit + * on the interrupt source. + * RETURN VALUE: + * the ID of the highest-priority pending interrupt or zero if there + * is no pending interrupt. + */ +int plic_claim(void) +{ + int hart = r_tp(); + int irq = *(uint32_t*)PLIC_MCLAIM(hart); + return irq; +} + +/* + * DESCRIPTION: + * Writing the interrupt ID it received from the claim (irq) to the + * complete register would signal the PLIC we've served this IRQ. + * The PLIC does not check whether the completion ID is the same as the + * last claim ID for that target. If the completion ID does not match an + * interrupt source that is currently enabled for the target, the completion + * is silently ignored. + * RETURN VALUE: none + */ +void plic_complete(int irq) +{ + int hart = r_tp(); + *(uint32_t*)PLIC_MCOMPLETE(hart) = irq; +} diff --git a/code/os/08-preemptive/printf.c b/code/os/08-preemptive/printf.c new file mode 100644 index 0000000..93ace5b --- /dev/null +++ b/code/os/08-preemptive/printf.c @@ -0,0 +1,138 @@ +#include "os.h" + +/* + * ref: https://github.com/cccriscv/mini-riscv-os/blob/master/05-Preemptive/lib.c + */ + +static int _vsnprintf(char * out, size_t n, const char* s, va_list vl) +{ + int format = 0; + int longarg = 0; + size_t pos = 0; + for (; *s; s++) { + if (format) { + switch(*s) { + case 'l': { + longarg = 1; + break; + } + case 'p': { + longarg = 1; + if (out && pos < n) { + out[pos] = '0'; + } + pos++; + if (out && pos < n) { + out[pos] = 'x'; + } + pos++; + } + case 'x': { + long num = longarg ? va_arg(vl, long) : va_arg(vl, int); + int hexdigits = 2*(longarg ? sizeof(long) : sizeof(int))-1; + for(int i = hexdigits; i >= 0; i--) { + int d = (num >> (4*i)) & 0xF; + if (out && pos < n) { + out[pos] = (d < 10 ? '0'+d : 'a'+d-10); + } + pos++; + } + longarg = 0; + format = 0; + break; + } + case 'd': { + long num = longarg ? va_arg(vl, long) : va_arg(vl, int); + if (num < 0) { + num = -num; + if (out && pos < n) { + out[pos] = '-'; + } + pos++; + } + long digits = 1; + for (long nn = num; nn /= 10; digits++); + for (int i = digits-1; i >= 0; i--) { + if (out && pos + i < n) { + out[pos + i] = '0' + (num % 10); + } + num /= 10; + } + pos += digits; + longarg = 0; + format = 0; + break; + } + case 's': { + const char* s2 = va_arg(vl, const char*); + while (*s2) { + if (out && pos < n) { + out[pos] = *s2; + } + pos++; + s2++; + } + longarg = 0; + format = 0; + break; + } + case 'c': { + if (out && pos < n) { + out[pos] = (char)va_arg(vl,int); + } + pos++; + longarg = 0; + format = 0; + break; + } + default: + break; + } + } else if (*s == '%') { + format = 1; + } else { + if (out && pos < n) { + out[pos] = *s; + } + pos++; + } + } + if (out && pos < n) { + out[pos] = 0; + } else if (out && n) { + out[n-1] = 0; + } + return pos; +} + +static char out_buf[1000]; // buffer for _vprintf() + +static int _vprintf(const char* s, va_list vl) +{ + int res = _vsnprintf(NULL, -1, s, vl); + if (res+1 >= sizeof(out_buf)) { + uart_puts("error: output string size overflow\n"); + while(1) {} + } + _vsnprintf(out_buf, res + 1, s, vl); + uart_puts(out_buf); + return res; +} + +int printf(const char* s, ...) +{ + int res = 0; + va_list vl; + va_start(vl, s); + res = _vprintf(s, vl); + va_end(vl); + return res; +} + +void panic(char *s) +{ + printf("panic: "); + printf(s); + printf("\n"); + while(1){}; +} diff --git a/code/os/08-preemptive/riscv.h b/code/os/08-preemptive/riscv.h new file mode 100644 index 0000000..e5bd491 --- /dev/null +++ b/code/os/08-preemptive/riscv.h @@ -0,0 +1,102 @@ +#ifndef __RISCV_H__ +#define __RISCV_H__ + +#include "types.h" + +/* + * ref: https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/riscv.h + */ + +static inline reg_t r_tp() +{ + reg_t x; + asm volatile("mv %0, tp" : "=r" (x) ); + return x; +} + +/* which hart (core) is this? */ +static inline reg_t r_mhartid() +{ + reg_t x; + asm volatile("csrr %0, mhartid" : "=r" (x) ); + return x; +} + +/* Machine Status Register, mstatus */ +#define MSTATUS_MPP (3 << 11) +#define MSTATUS_SPP (1 << 8) + +#define MSTATUS_MPIE (1 << 7) +#define MSTATUS_SPIE (1 << 5) +#define MSTATUS_UPIE (1 << 4) + +#define MSTATUS_MIE (1 << 3) +#define MSTATUS_SIE (1 << 1) +#define MSTATUS_UIE (1 << 0) + +static inline reg_t r_mstatus() +{ + reg_t x; + asm volatile("csrr %0, mstatus" : "=r" (x) ); + return x; +} + +static inline void w_mstatus(reg_t x) +{ + asm volatile("csrw mstatus, %0" : : "r" (x)); +} + +/* + * machine exception program counter, holds the + * instruction address to which a return from + * exception will go. + */ +static inline void w_mepc(reg_t x) +{ + asm volatile("csrw mepc, %0" : : "r" (x)); +} + +static inline reg_t r_mepc() +{ + reg_t x; + asm volatile("csrr %0, mepc" : "=r" (x)); + return x; +} + +/* Machine Scratch register, for early trap handler */ +static inline void w_mscratch(reg_t x) +{ + asm volatile("csrw mscratch, %0" : : "r" (x)); +} + +/* Machine-mode interrupt vector */ +static inline void w_mtvec(reg_t x) +{ + asm volatile("csrw mtvec, %0" : : "r" (x)); +} + +/* Machine-mode Interrupt Enable */ +#define MIE_MEIE (1 << 11) // external +#define MIE_MTIE (1 << 7) // timer +#define MIE_MSIE (1 << 3) // software + +static inline reg_t r_mie() +{ + reg_t x; + asm volatile("csrr %0, mie" : "=r" (x) ); + return x; +} + +static inline void w_mie(reg_t x) +{ + asm volatile("csrw mie, %0" : : "r" (x)); +} + +static inline reg_t r_mcause() +{ + reg_t x; + asm volatile("csrr %0, mcause" : "=r" (x) ); + return x; +} + +#endif /* __RISCV_H__ */ diff --git a/code/os/08-preemptive/sched.c b/code/os/08-preemptive/sched.c new file mode 100644 index 0000000..c612a3a --- /dev/null +++ b/code/os/08-preemptive/sched.c @@ -0,0 +1,81 @@ +#include "os.h" + +/* defined in entry.S */ +extern void switch_to(struct context *next); + +#define MAX_TASKS 10 +#define STACK_SIZE 1024 +uint8_t task_stack[MAX_TASKS][STACK_SIZE]; +struct context ctx_tasks[MAX_TASKS]; + +/* + * _top is used to mark the max available position of ctx_tasks + * _current is used to point to the context of current task + */ +static int _top = 0; +static int _current = -1; + +void sched_init() +{ + w_mscratch(0); + + /* enable machine-mode software interrupts. */ + w_mie(r_mie() | MIE_MSIE); +} + +/* + * implment a simple cycle FIFO schedular + */ +void schedule() +{ + if (_top <= 0) { + panic("Num of task should be greater than zero!"); + return; + } + + _current = (_current + 1) % _top; + struct context *next = &(ctx_tasks[_current]); + switch_to(next); +} + +/* + * DESCRIPTION + * Create a task. + * - start_routin: task routune entry + * RETURN VALUE + * 0: success + * -1: if error occured + */ +int task_create(void (*start_routin)(void)) +{ + if (_top < MAX_TASKS) { + ctx_tasks[_top].sp = (reg_t) &task_stack[_top][STACK_SIZE - 1]; + ctx_tasks[_top].pc = (reg_t) start_routin; + _top++; + return 0; + } else { + return -1; + } +} + +/* + * DESCRIPTION + * task_yield() causes the calling task to relinquish the CPU and a new + * task gets to run. + */ +void task_yield() +{ + /* trigger a machine-level software interrupt */ + int id = r_mhartid(); + *(uint32_t*)CLINT_MSIP(id) = 1; +} + +/* + * a very rough implementaion, just to consume the cpu + */ +void task_delay(volatile int count) +{ + count *= 50000; + while (count--); +} + diff --git a/code/os/08-preemptive/start.S b/code/os/08-preemptive/start.S new file mode 100644 index 0000000..e49c063 --- /dev/null +++ b/code/os/08-preemptive/start.S @@ -0,0 +1,51 @@ +#include "platform.h" + + .equ STACK_SIZE, 8192 + + .global _start + + .text +_start: + # park harts with id != 0 + csrr t0, mhartid # read current hart id + mv tp, t0 # keep CPU's hartid in its tp for later usage. + bnez t0, park # if we're not on the hart 0 + # we park the hart + + # Set all bytes in the BSS section to zero. + la a0, _bss_start + la a1, _bss_end + bgeu a0, a1, 2f +1: + sw zero, (a0) + addi a0, a0, 4 + bltu a0, a1, 1b +2: + # Setup stacks, the stack grows from bottom to top, so we put the + # stack pointer to the very end of the stack range. + slli t0, t0, 10 # shift left the hart id by 1024 + la sp, stacks + STACK_SIZE # set the initial stack pointer + # to the end of the stack space + add sp, sp, t0 # move the current hart stack pointer + # to its place in the stack space + + # At the end of start_kernel, schedule() will call MRET to switch + # to the first task, so we parepare the mstatus here. + # Set mstatus.MPIE to 1, so MRET will enable the interrupt. + # Set mstatus.MPP to 3, so we still run in Machine mode after MRET. + # Notice: default mstatus is 0 + li t0, 3 << 11 | 1 << 7 + csrr a1, mstatus + or t0, t0, a1 + csrw mstatus, t0 + + j start_kernel # hart 0 jump to c + +park: + wfi + j park + +stacks: + .skip STACK_SIZE * MAXNUM_CPU # allocate space for the harts stacks + + .end # End of file diff --git a/code/os/08-preemptive/timer.c b/code/os/08-preemptive/timer.c new file mode 100644 index 0000000..f769ee3 --- /dev/null +++ b/code/os/08-preemptive/timer.c @@ -0,0 +1,39 @@ +#include "os.h" + +extern void schedule(void); + +/* interval ~= 1s */ +#define TIMER_INTERVAL CLINT_TIMEBASE_FREQ + +static uint32_t _tick = 0; + +/* load timer interval(in ticks) for next timer interrupt.*/ +void timer_load(int interval) +{ + /* each CPU has a separate source of timer interrupts. */ + int id = r_mhartid(); + + *(uint64_t*)CLINT_MTIMECMP(id) = *(uint64_t*)CLINT_MTIME + interval; +} + +void timer_init() +{ + /* + * On reset, mtime is cleared to zero, but the mtimecmp registers + * are not reset. So we have to init the mtimecmp manually. + */ + timer_load(TIMER_INTERVAL); + + /* enable machine-mode timer interrupts. */ + w_mie(r_mie() | MIE_MTIE); +} + +void timer_handler() +{ + _tick++; + printf("tick: %d\n", _tick); + + timer_load(TIMER_INTERVAL); + + schedule(); +} diff --git a/code/os/08-preemptive/trap.c b/code/os/08-preemptive/trap.c new file mode 100644 index 0000000..b3fa9cc --- /dev/null +++ b/code/os/08-preemptive/trap.c @@ -0,0 +1,89 @@ +#include "os.h" + +extern void trap_vector(void); +extern void uart_isr(void); +extern void timer_handler(void); +extern void schedule(void); + +void trap_init() +{ + /* + * set the trap-vector base-address for machine-mode + */ + w_mtvec((reg_t)trap_vector); +} + +void external_interrupt_handler() +{ + int irq = plic_claim(); + + if (irq == UART0_IRQ){ + uart_isr(); + } else if (irq) { + printf("unexpected interrupt irq = %d\n", irq); + } + + if (irq) { + plic_complete(irq); + } +} + +reg_t trap_handler(reg_t epc, reg_t cause) +{ + reg_t return_pc = epc; + reg_t cause_code = cause & 0xfff; + + if (cause & 0x80000000) { + /* Asynchronous trap - interrupt */ + switch (cause_code) { + case 3: + uart_puts("software interruption!\n"); + /* + * acknowledge the software interrupt by clearing + * the MSIP bit in mip. + */ + int id = r_mhartid(); + *(uint32_t*)CLINT_MSIP(id) = 0; + + schedule(); + + break; + case 7: + uart_puts("timer interruption!\n"); + timer_handler(); + break; + case 11: + uart_puts("external interruption!\n"); + external_interrupt_handler(); + break; + default: + uart_puts("unknown async exception!\n"); + break; + } + } else { + /* Synchronous trap - exception */ + printf("Sync exceptions!, code = %d\n", cause_code); + panic("OOPS! What can I do!"); + //return_pc += 4; + } + + return return_pc; +} + +void trap_test() +{ + /* + * Synchronous exception code = 7 + * Store/AMO access fault + */ + *(int *)0x00000000 = 100; + + /* + * Synchronous exception code = 5 + * Load access fault + */ + //int a = *(int *)0x00000000; + + uart_puts("Yeah! I'm return back from trap!\n"); +} + diff --git a/code/os/08-preemptive/types.h b/code/os/08-preemptive/types.h new file mode 100644 index 0000000..5051639 --- /dev/null +++ b/code/os/08-preemptive/types.h @@ -0,0 +1,14 @@ +#ifndef __TYPES_H__ +#define __TYPES_H__ + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long long uint64_t; + +/* + * RISCV32: register is 32bits width + */ +typedef uint32_t reg_t; + +#endif /* __TYPES_H__ */ diff --git a/code/os/08-preemptive/uart.c b/code/os/08-preemptive/uart.c new file mode 100644 index 0000000..2905331 --- /dev/null +++ b/code/os/08-preemptive/uart.c @@ -0,0 +1,149 @@ +#include "os.h" + +/* + * The UART control registers are memory-mapped at address UART0. + * This macro returns the address of one of the registers. + */ +#define UART_REG(reg) ((volatile uint8_t *)(UART0 + reg)) + +/* + * Reference + * [1]: TECHNICAL DATA ON 16550, http://byterunner.com/16550.html + */ + +/* + * UART control registers map. see [1] "PROGRAMMING TABLE" + * note some are reused by multiple functions + * 0 (write mode): THR/DLL + * 1 (write mode): IER/DLM + */ +#define RHR 0 // Receive Holding Register (read mode) +#define THR 0 // Transmit Holding Register (write mode) +#define DLL 0 // LSB of Divisor Latch (write mode) +#define IER 1 // Interrupt Enable Register (write mode) +#define DLM 1 // MSB of Divisor Latch (write mode) +#define FCR 2 // FIFO Control Register (write mode) +#define ISR 2 // Interrupt Status Register (read mode) +#define LCR 3 // Line Control Register +#define MCR 4 // Modem Control Register +#define LSR 5 // Line Status Register +#define MSR 6 // Modem Status Register +#define SPR 7 // ScratchPad Register + +/* + * POWER UP DEFAULTS + * IER = 0: TX/RX holding register interrupts are bith disabled + * ISR = 1: no interrupt penting + * LCR = 0 + * MCR = 0 + * LSR = 60 HEX + * MSR = BITS 0-3 = 0, BITS 4-7 = inputs + * FCR = 0 + * TX = High + * OP1 = High + * OP2 = High + * RTS = High + * DTR = High + * RXRDY = High + * TXRDY = Low + * INT = Low + */ + +/* + * LINE STATUS REGISTER (LSR) + * LSR BIT 0: + * 0 = no data in receive holding register or FIFO. + * 1 = data has been receive and saved in the receive holding register or FIFO. + * ...... + * LSR BIT 5: + * 0 = transmit holding register is full. 16550 will not accept any data for transmission. + * 1 = transmitter hold register (or FIFO) is empty. CPU can load the next character. + * ...... + */ +#define LSR_RX_READY (1 << 0) +#define LSR_TX_IDLE (1 << 5) + +#define uart_read_reg(reg) (*(UART_REG(reg))) +#define uart_write_reg(reg, v) (*(UART_REG(reg)) = (v)) + +void uart_init() +{ + /* disable interrupts. */ + uart_write_reg(IER, 0x00); + + /* + * Setting baud rate. Just a demo here if we care about the divisor, + * but for our purpose [QEMU-virt], this doesn't really do anything. + * + * Notice that the divisor register DLL (divisor latch least) and DLM (divisor + * latch most) have the same base address as the receiver/transmitter and the + * interrupt enable register. To change what the base address points to, we + * open the "divisor latch" by writing 1 into the Divisor Latch Access Bit + * (DLAB), which is bit index 7 of the Line Control Register (LCR). + * + * Regarding the baud rate value, see [1] "BAUD RATE GENERATOR PROGRAMMING TABLE". + * We use 38.4K when 1.8432 MHZ crystal, so the corresponding value is 3. + * And due to the divisor register is two bytes (16 bits), so we need to + * split the value of 3(0x0003) into two bytes, DLL stores the low byte, + * DLM stores the high byte. + */ + uint8_t lcr = uart_read_reg(LCR); + uart_write_reg(LCR, lcr | (1 << 7)); + uart_write_reg(DLL, 0x03); + uart_write_reg(DLM, 0x00); + + /* + * Continue setting the asynchronous data communication format. + * - number of the word length: 8 bits + * - number of stop bits:1 bit when word length is 8 bits + * - no parity + * - no break control + * - disabled baud latch + */ + lcr = 0; + uart_write_reg(LCR, lcr | (3 << 0)); + + /* + * enable receive interrupts. + */ + uint8_t ier = uart_read_reg(IER); + uart_write_reg(IER, ier | (1 << 0)); +} + +int uart_putc(char ch) +{ + while ((uart_read_reg(LSR) & LSR_TX_IDLE) == 0); + return uart_write_reg(THR, ch); +} + +void uart_puts(char *s) +{ + while (*s) { + uart_putc(*s++); + } +} + +int uart_getc(void) +{ + if (uart_read_reg(LSR) & LSR_RX_READY){ + return uart_read_reg(RHR); + } else { + return -1; + } +} + +/* + * handle a uart interrupt, raised because input has arrived, called from trap.c. + */ +void uart_isr(void) +{ + while (1) { + int c = uart_getc(); + if (c == -1) { + break; + } else { + uart_putc((char)c); + uart_putc('\n'); + } + } +} diff --git a/code/os/08-preemptive/user.c b/code/os/08-preemptive/user.c new file mode 100644 index 0000000..e2d9851 --- /dev/null +++ b/code/os/08-preemptive/user.c @@ -0,0 +1,32 @@ +#include "os.h" + +#define DELAY 1000 + +void user_task0(void) +{ + uart_puts("Task 0: Created!\n"); + + task_yield(); + uart_puts("Task 0: I'm back!\n"); + while (1) { + uart_puts("Task 0: Running...\n"); + task_delay(DELAY); + } +} + +void user_task1(void) +{ + uart_puts("Task 1: Created!\n"); + while (1) { + uart_puts("Task 1: Running...\n"); + task_delay(DELAY); + } +} + +/* NOTICE: DON'T LOOP INFINITELY IN main() */ +void os_main(void) +{ + task_create(user_task0); + task_create(user_task1); +} + diff --git a/code/os/09-lock/Makefile b/code/os/09-lock/Makefile new file mode 100644 index 0000000..90296dd --- /dev/null +++ b/code/os/09-lock/Makefile @@ -0,0 +1,66 @@ +CROSS_COMPILE = riscv64-unknown-elf- +CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall + +QEMU = qemu-system-riscv32 +QFLAGS = -nographic -smp 1 -machine virt -bios none + +GDB = ${CROSS_COMPILE}gdb +CC = ${CROSS_COMPILE}gcc +OBJCOPY = ${CROSS_COMPILE}objcopy +OBJDUMP = ${CROSS_COMPILE}objdump + +SRCS_ASM = \ + start.S \ + mem.S \ + entry.S \ + +SRCS_C = \ + kernel.c \ + uart.c \ + printf.c \ + page.c \ + sched.c \ + user.c \ + trap.c \ + plic.c \ + timer.c \ + lock.c \ + +OBJS = $(SRCS_ASM:.S=.o) +OBJS += $(SRCS_C:.c=.o) + +.DEFAULT_GOAL := all +all: os.elf + +# start.o must be the first in dependency! +os.elf: ${OBJS} + ${CC} $(CFLAGS) -T os.ld -o os.elf $^ + ${OBJCOPY} -O binary os.elf os.bin + +%.o : %.c + ${CC} ${CFLAGS} -c -o $@ $< + +%.o : %.S + ${CC} ${CFLAGS} -c -o $@ $< + +run: all + @${QEMU} -M ? | grep virt >/dev/null || exit + @echo "Press Ctrl-A and then X to exit QEMU" + @echo "------------------------------------" + @${QEMU} ${QFLAGS} -kernel os.elf + +.PHONY : debug +debug: all + @echo "Press Ctrl-C and then input 'quit' to exit GDB and QEMU" + @echo "-------------------------------------------------------" + @${QEMU} ${QFLAGS} -kernel os.elf -s -S & + @${GDB} os.elf -q -x ../gdbinit + +.PHONY : code +code: all + @${OBJDUMP} -S os.elf | less + +.PHONY : clean +clean: + rm -rf *.o *.bin *.elf + diff --git a/code/os/09-lock/entry.S b/code/os/09-lock/entry.S new file mode 100644 index 0000000..146c75d --- /dev/null +++ b/code/os/09-lock/entry.S @@ -0,0 +1,134 @@ +# save all General-Purpose(GP) registers to context +# struct context *base = &ctx_task; +# base->ra = ra; +# ...... +.macro reg_save base + sw ra, 0(\base) + sw sp, 4(\base) + sw gp, 8(\base) + sw tp, 12(\base) + sw t0, 16(\base) + sw t1, 20(\base) + sw t2, 24(\base) + sw s0, 28(\base) + sw s1, 32(\base) + sw a0, 36(\base) + sw a1, 40(\base) + sw a2, 44(\base) + sw a3, 48(\base) + sw a4, 52(\base) + sw a5, 56(\base) + sw a6, 60(\base) + sw a7, 64(\base) + sw s2, 68(\base) + sw s3, 72(\base) + sw s4, 76(\base) + sw s5, 80(\base) + sw s6, 84(\base) + sw s7, 88(\base) + sw s8, 92(\base) + sw s9, 96(\base) + sw s10, 100(\base) + sw s11, 104(\base) + sw t3, 108(\base) + sw t4, 112(\base) + sw t5, 116(\base) + sw t6, 120(\base) +.endm + +# restore all General-Purpose(GP) registers from the context +# struct context *base = &ctx_task; +# ra = base->ra; +# ...... +.macro reg_restore base + lw ra, 0(\base) + lw sp, 4(\base) + lw gp, 8(\base) + lw tp, 12(\base) + lw t0, 16(\base) + lw t1, 20(\base) + lw t2, 24(\base) + lw s0, 28(\base) + lw s1, 32(\base) + lw a0, 36(\base) + lw a1, 40(\base) + lw a2, 44(\base) + lw a3, 48(\base) + lw a4, 52(\base) + lw a5, 56(\base) + lw a6, 60(\base) + lw a7, 64(\base) + lw s2, 68(\base) + lw s3, 72(\base) + lw s4, 76(\base) + lw s5, 80(\base) + lw s6, 84(\base) + lw s7, 88(\base) + lw s8, 92(\base) + lw s9, 96(\base) + lw s10, 100(\base) + lw s11, 104(\base) + lw t3, 108(\base) + lw t4, 112(\base) + lw t5, 116(\base) + lw t6, 120(\base) +.endm + +# Something to note about save/restore: +# - We use mscratch to hold a pointer to context of previous task +# - We use t6 as the 'base' for reg_save/reg_restore, because it is the +# very bottom register (x31) and would not be overwritten during loading. + +.text + +# interrupts and exceptions while in machine mode come here. +.globl trap_vector +# the trap vector base address must always be aligned on a 4-byte boundary +.align 4 +trap_vector: + # save context(registers). + csrrw t6, mscratch, t6 # swap t6 and mscratch + reg_save t6 + csrw mscratch, t6 + + # save mepc to context of current task + csrr a0, mepc + sw a0, 124(t6) + + # call the C trap handler in trap.c + csrr a0, mepc + csrr a1, mcause + call trap_handler + + # trap_handler will return the return address via a0. + csrw mepc, a0 + + # restore context(registers). + csrr t6, mscratch + reg_restore t6 + + # return to whatever we were doing before trap. + mret + +# void switch_to(struct context *next); +# a0: pointer to the context of the next task +.globl switch_to +.align 4 +switch_to: + # switch mscratch to point to the context of the next task + csrw mscratch, a0 + # set mepc to the pc of the next task + lw a1, 124(a0) + csrw mepc, a1 + + # Restore all GP registers + # Use t6 to point to the context of the new task + mv t6, a0 + reg_restore t6 + + # Do actual context switching. + # Notice this will enable global interrupt + mret + +.end + diff --git a/code/os/09-lock/kernel.c b/code/os/09-lock/kernel.c new file mode 100644 index 0000000..b4e8096 --- /dev/null +++ b/code/os/09-lock/kernel.c @@ -0,0 +1,38 @@ +#include "os.h" + +/* + * Following functions SHOULD be called ONLY ONE time here, + * so just declared here ONCE and NOT included in file os.h. + */ +extern void uart_init(void); +extern void page_init(void); +extern void sched_init(void); +extern void schedule(void); +extern void os_main(void); +extern void trap_init(void); +extern void plic_init(void); +extern void timer_init(void); + +void start_kernel(void) +{ + uart_init(); + uart_puts("Hello, RVOS!\n"); + + page_init(); + + trap_init(); + + plic_init(); + + timer_init(); + + sched_init(); + + os_main(); + + schedule(); + + uart_puts("Would not go here!\n"); + while (1) {}; // stop here! +} + diff --git a/code/os/09-lock/lock.c b/code/os/09-lock/lock.c new file mode 100644 index 0000000..0866fc9 --- /dev/null +++ b/code/os/09-lock/lock.c @@ -0,0 +1,13 @@ +#include "os.h" + +int spin_lock() +{ + w_mstatus(r_mstatus() & ~MSTATUS_MIE); + return 0; +} + +int spin_unlock() +{ + w_mstatus(r_mstatus() | MSTATUS_MIE); + return 0; +} diff --git a/code/os/09-lock/mem.S b/code/os/09-lock/mem.S new file mode 100644 index 0000000..93ba0d6 --- /dev/null +++ b/code/os/09-lock/mem.S @@ -0,0 +1,30 @@ +.section .rodata +.global HEAP_START +HEAP_START: .word _heap_start + +.global HEAP_SIZE +HEAP_SIZE: .word _heap_size + +.global TEXT_START +TEXT_START: .word _text_start + +.global TEXT_END +TEXT_END: .word _text_end + +.global DATA_START +DATA_START: .word _data_start + +.global DATA_END +DATA_END: .word _data_end + +.global RODATA_START +RODATA_START: .word _rodata_start + +.global RODATA_END +RODATA_END: .word _rodata_end + +.global BSS_START +BSS_START: .word _bss_start + +.global BSS_END +BSS_END: .word _bss_end diff --git a/code/os/09-lock/os.h b/code/os/09-lock/os.h new file mode 100644 index 0000000..3100fc2 --- /dev/null +++ b/code/os/09-lock/os.h @@ -0,0 +1,76 @@ +#ifndef __OS_H__ +#define __OS_H__ + +#include "types.h" +#include "riscv.h" +#include "platform.h" + +#include +#include + +/* uart */ +extern int uart_putc(char ch); +extern void uart_puts(char *s); +extern int uart_getc(void); + +/* printf */ +extern int printf(const char* s, ...); +extern void panic(char *s); + +/* memory management */ +extern void *page_alloc(int npages); +extern void page_free(void *p); + +/* task management */ +struct context { + /* ignore x0 */ + reg_t ra; + reg_t sp; + reg_t gp; + reg_t tp; + reg_t t0; + reg_t t1; + reg_t t2; + reg_t s0; + reg_t s1; + reg_t a0; + reg_t a1; + reg_t a2; + reg_t a3; + reg_t a4; + reg_t a5; + reg_t a6; + reg_t a7; + reg_t s2; + reg_t s3; + reg_t s4; + reg_t s5; + reg_t s6; + reg_t s7; + reg_t s8; + reg_t s9; + reg_t s10; + reg_t s11; + reg_t t3; + reg_t t4; + reg_t t5; + reg_t t6; + // upon is trap frame + + // save the pc to run in next schedule cycle + reg_t pc; // offset: 31 *4 = 124 +}; + +extern int task_create(void (*task)(void)); +extern void task_delay(volatile int count); +extern void task_yield(); + +/* plic */ +extern int plic_claim(void); +extern void plic_complete(int irq); + +/* lock */ +extern int spin_lock(void); +extern int spin_unlock(void); + +#endif /* __OS_H__ */ diff --git a/code/os/09-lock/os.ld b/code/os/09-lock/os.ld new file mode 100644 index 0000000..a75cd93 --- /dev/null +++ b/code/os/09-lock/os.ld @@ -0,0 +1,136 @@ +/* + * rvos.ld + * Linker script for outputting to RVOS + */ + +/* + * https://sourceware.org/binutils/docs/ld/Miscellaneous-Commands.html + * OUTPUT_ARCH command specifies a particular output machine architecture. + * "riscv" is the name of the architecture for both 64-bit and 32-bit + * RISC-V target. We will further refine this by using -march=rv32ima + * and -mabi=ilp32 when calling gcc. + */ +OUTPUT_ARCH( "riscv" ) + +/* + * https://sourceware.org/binutils/docs/ld/Entry-Point.html + * ENTRY command is used to set the "entry point", which is the first instruction + * to execute in a program. + * The argument of ENTRY command is a symbol name, here is "_start" which is + * defined in start.S. + */ +ENTRY( _start ) + +/* + * https://sourceware.org/binutils/docs/ld/MEMORY.html + * The MEMORY command describes the location and size of blocks of memory in + * the target. + * The syntax for MEMORY is: + * MEMORY + * { + * name [(attr)] : ORIGIN = origin, LENGTH = len + * ...... + * } + * Each line defines a memory region. + * Each memory region must have a distinct name within the MEMORY command. Here + * we only define one region named as "ram". + * The "attr" string is an optional list of attributes that specify whether to + * use a particular memory region for an input section which is not explicitly + * mapped in the linker script. Here we assign 'w' (writeable), 'x' (executable), + * and 'a' (allocatable). We use '!' to invert 'r' (read-only) and + * 'i' (initialized). + * The "ORIGIN" is used to set the start address of the memory region. Here we + * place it right at the beginning of 0x8000_0000 because this is where the + * QEMU-virt machine will start executing. + * Finally LENGTH = 128M tells the linker that we have 128 megabyte of RAM. + * The linker will double check this to make sure everything can fit. + */ +MEMORY +{ + ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M +} + +/* + * https://sourceware.org/binutils/docs/ld/SECTIONS.html + * The SECTIONS command tells the linker how to map input sections into output + * sections, and how to place the output sections in memory. + * The format of the SECTIONS command is: + * SECTIONS + * { + * sections-command + * sections-command + * ...... + * } + * + * Each sections-command may of be one of the following: + * (1) an ENTRY command + * (2) a symbol assignment + * (3) an output section description + * (4) an overlay description + * We here only demo (2) & (3). + * + * We use PROVIDE command to define symbols. + * https://sourceware.org/binutils/docs/ld/PROVIDE.html + * The PROVIDE keyword may be used to define a symbol. + * The syntax is PROVIDE(symbol = expression). + * Such symbols as "_text_start", "_text_end" ... will be used in mem.S. + * Notice the period '.' tells the linker to set symbol(e.g. _text_start) to + * the CURRENT location ('.' = current memory location). This current memory + * location moves as we add things. + */ +SECTIONS +{ + /* + * We are going to layout all text sections in .text output section, + * starting with .text. The asterisk("*") in front of the + * parentheses means to match the .text section of ANY object file. + */ + .text : { + PROVIDE(_text_start = .); + *(.text .text.*) + PROVIDE(_text_end = .); + } >ram + + .rodata : { + PROVIDE(_rodata_start = .); + *(.rodata .rodata.*) + PROVIDE(_rodata_end = .); + } >ram + + .data : { + /* + * . = ALIGN(4096) tells the linker to align the current memory + * location to 4096 bytes. This will insert padding bytes until + * current location becomes aligned on 4096-byte boundary. + * This is because our paging system's resolution is 4,096 bytes. + */ + . = ALIGN(4096); + PROVIDE(_data_start = .); + /* + * sdata and data are essentially the same thing. We do not need + * to distinguish sdata from data. + */ + *(.sdata .sdata.*) + *(.data .data.*) + PROVIDE(_data_end = .); + } >ram + + .bss :{ + /* + * https://sourceware.org/binutils/docs/ld/Input-Section-Common.html + * In most cases, common symbols in input files will be placed + * in the ‘.bss’ section in the output file. + */ + PROVIDE(_bss_start = .); + *(.sbss .sbss.*) + *(.bss .bss.*) + *(COMMON) + PROVIDE(_bss_end = .); + } >ram + + PROVIDE(_memory_start = ORIGIN(ram)); + PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram)); + + PROVIDE(_heap_start = _bss_end); + PROVIDE(_heap_size = _memory_end - _heap_start); +} diff --git a/code/os/09-lock/page.c b/code/os/09-lock/page.c new file mode 100644 index 0000000..addf097 --- /dev/null +++ b/code/os/09-lock/page.c @@ -0,0 +1,189 @@ +#include "os.h" + +/* + * Following global vars are defined in mem.S + */ +extern uint32_t TEXT_START; +extern uint32_t TEXT_END; +extern uint32_t DATA_START; +extern uint32_t DATA_END; +extern uint32_t RODATA_START; +extern uint32_t RODATA_END; +extern uint32_t BSS_START; +extern uint32_t BSS_END; +extern uint32_t HEAP_START; +extern uint32_t HEAP_SIZE; + +/* + * _alloc_start points to the actual start address of heap pool + * _alloc_end points to the actual end address of heap pool + * _num_pages holds the actual max number of pages we can allocate. + */ +static uint32_t _alloc_start = 0; +static uint32_t _alloc_end = 0; +static uint32_t _num_pages = 0; + +#define PAGE_SIZE 4096 +#define PAGE_ORDER 12 + +#define PAGE_TAKEN (uint8_t)(1 << 0) +#define PAGE_LAST (uint8_t)(1 << 1) + +/* + * Page Descriptor + * flags: + * - bit 0: flag if this page is taken(allocated) + * - bit 1: flag if this page is the last page of the memory block allocated + */ +struct Page { + uint8_t flags; +}; + +static inline void _clear(struct Page *page) +{ + page->flags = 0; +} + +static inline int _is_free(struct Page *page) +{ + if (page->flags & PAGE_TAKEN) { + return 0; + } else { + return 1; + } +} + +static inline void _set_flag(struct Page *page, uint8_t flags) +{ + page->flags |= flags; +} + +static inline int _is_last(struct Page *page) +{ + if (page->flags & PAGE_LAST) { + return 1; + } else { + return 0; + } +} + +/* + * align the address to the border of page(4K) + */ +static inline uint32_t _align_page(uint32_t address) +{ + uint32_t order = (1 << PAGE_ORDER) - 1; + return (address + order) & (~order); +} + +void page_init() +{ + /* + * We reserved 8 Page (8 x 4096) to hold the Page structures. + * It should be enough to manage at most 128 MB (8 x 4096 x 4096) + */ + _num_pages = (HEAP_SIZE / PAGE_SIZE) - 8; + printf("HEAP_START = %x, HEAP_SIZE = %x, num of pages = %d\n", HEAP_START, HEAP_SIZE, _num_pages); + + struct Page *page = (struct Page *)HEAP_START; + for (int i = 0; i < _num_pages; i++) { + _clear(page); + page++; + } + + _alloc_start = _align_page(HEAP_START + 8 * PAGE_SIZE); + _alloc_end = _alloc_start + (PAGE_SIZE * _num_pages); + + printf("TEXT: 0x%x -> 0x%x\n", TEXT_START, TEXT_END); + printf("RODATA: 0x%x -> 0x%x\n", RODATA_START, RODATA_END); + printf("DATA: 0x%x -> 0x%x\n", DATA_START, DATA_END); + printf("BSS: 0x%x -> 0x%x\n", BSS_START, BSS_END); + printf("HEAP: 0x%x -> 0x%x\n", _alloc_start, _alloc_end); +} + +/* + * Allocate a memory block which is composed of contiguous physical pages + * - npages: the number of PAGE_SIZE pages to allocate + */ +void *page_alloc(int npages) +{ + /* Note we are searching the page descriptor bitmaps. */ + int found = 0; + struct Page *page_i = (struct Page *)HEAP_START; + for (int i = 0; i < (_num_pages - npages); i++) { + if (_is_free(page_i)) { + found = 1; + /* + * meet a free page, continue to check if following + * (npages - 1) pages are also unallocated. + */ + struct Page *page_j = page_i; + for (int j = i; j < (i + npages); j++) { + if (!_is_free(page_j)) { + found = 0; + break; + } + page_j++; + } + /* + * get a memory block which is good enough for us, + * take housekeeping, then return the actual start + * address of the first page of this memory block + */ + if (found) { + struct Page *page_k = page_i; + for (int k = i; k < (i + npages); k++) { + _set_flag(page_k, PAGE_TAKEN); + page_k++; + } + page_k--; + _set_flag(page_k, PAGE_LAST); + return (void *)(_alloc_start + i * PAGE_SIZE); + } + } + page_i++; + } + return NULL; +} + +/* + * Free the memory block + * - p: start address of the memory block + */ +void page_free(void *p) +{ + /* + * Assert (TBD) if p is invalid + */ + if (!p || (uint32_t)p >= _alloc_end) { + return; + } + /* get the first page descriptor of this memory block */ + struct Page *page = (struct Page *)HEAP_START; + page += ((uint32_t)p - _alloc_start)/ PAGE_SIZE; + /* loop and clear all the page descriptors of the memory block */ + while (!_is_free(page)) { + if (_is_last(page)) { + _clear(page); + break; + } else { + _clear(page); + page++;; + } + } +} + +void page_test() +{ + void *p = page_alloc(2); + printf("p = 0x%x\n", p); + //page_free(p); + + void *p2 = page_alloc(7); + printf("p2 = 0x%x\n", p2); + page_free(p2); + + void *p3 = page_alloc(4); + printf("p3 = 0x%x\n", p3); +} + diff --git a/code/os/09-lock/platform.h b/code/os/09-lock/platform.h new file mode 100644 index 0000000..3319f4f --- /dev/null +++ b/code/os/09-lock/platform.h @@ -0,0 +1,97 @@ +#ifndef __PLATFORM_H__ +#define __PLATFORM_H__ + +/* + * QEMU RISC-V Virt machine with 16550a UART and VirtIO MMIO + */ + +/* + * maximum number of CPUs + * see https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h + * #define VIRT_CPUS_MAX 8 + */ +#define MAXNUM_CPU 8 + +/* + * MemoryMap + * see https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c, virt_memmap[] + * 0x00001000 -- boot ROM, provided by qemu + * 0x02000000 -- CLINT + * 0x0C000000 -- PLIC + * 0x10000000 -- UART0 + * 0x10001000 -- virtio disk + * 0x80000000 -- boot ROM jumps here in machine mode, where we load our kernel + */ + +/* This machine puts UART registers here in physical memory. */ +#define UART0 0x10000000L + +/* + * UART0 interrupt source + * see https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h + * enum { + * UART0_IRQ = 10, + * ...... + * }; + */ +#define UART0_IRQ 10 + +/* + * This machine puts platform-level interrupt controller (PLIC) here. + * Here only list PLIC registers in Machine mode. + * see https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h + * #define VIRT_PLIC_HART_CONFIG "MS" + * #define VIRT_PLIC_NUM_SOURCES 127 + * #define VIRT_PLIC_NUM_PRIORITIES 7 + * #define VIRT_PLIC_PRIORITY_BASE 0x04 + * #define VIRT_PLIC_PENDING_BASE 0x1000 + * #define VIRT_PLIC_ENABLE_BASE 0x2000 + * #define VIRT_PLIC_ENABLE_STRIDE 0x80 + * #define VIRT_PLIC_CONTEXT_BASE 0x200000 + * #define VIRT_PLIC_CONTEXT_STRIDE 0x1000 + * #define VIRT_PLIC_SIZE(__num_context) \ + * (VIRT_PLIC_CONTEXT_BASE + (__num_context) * VIRT_PLIC_CONTEXT_STRIDE) + */ +#define PLIC_BASE 0x0c000000L +#define PLIC_PRIORITY(id) (PLIC_BASE + (id) * 4) +#define PLIC_PENDING(id) (PLIC_BASE + 0x1000 + ((id) / 32)) +#define PLIC_MENABLE(hart) (PLIC_BASE + 0x2000 + (hart) * 0x80) +#define PLIC_MTHRESHOLD(hart) (PLIC_BASE + 0x200000 + (hart) * 0x1000) +#define PLIC_MCLAIM(hart) (PLIC_BASE + 0x200004 + (hart) * 0x1000) +#define PLIC_MCOMPLETE(hart) (PLIC_BASE + 0x200004 + (hart) * 0x1000) + + /* + * The Core Local INTerruptor (CLINT) block holds memory-mapped control and + * status registers associated with software and timer interrupts. + * QEMU-virt reuses sifive configuration for CLINT. + * see https://gitee.com/qemu/qemu/blob/master/include/hw/riscv/sifive_clint.h + * enum { + * SIFIVE_SIP_BASE = 0x0, + * SIFIVE_TIMECMP_BASE = 0x4000, + * SIFIVE_TIME_BASE = 0xBFF8 + * }; + * + * enum { + * SIFIVE_CLINT_TIMEBASE_FREQ = 10000000 + * }; + * + * Notice: + * The machine-level MSIP bit of mip register are written by accesses to + * memory-mapped control registers, which are used by remote harts to provide + * machine-mode interprocessor interrupts. + * For QEMU-virt machine, Each msip register is a 32-bit wide WARL register + * where the upper 31 bits are tied to 0. The least significant bit is + * reflected in the MSIP bit of the mip CSR. We can write msip to generate + * machine-mode software interrupts. A pending machine-level software + * interrupt can be cleared by writing 0 to the MSIP bit in mip. + * On reset, each msip register is cleared to zero. + */ +#define CLINT_BASE 0x2000000L +#define CLINT_MSIP(hartid) (CLINT_BASE + 4 * (hartid)) +#define CLINT_MTIMECMP(hartid) (CLINT_BASE + 0x4000 + 8 * (hartid)) +#define CLINT_MTIME (CLINT_BASE + 0xBFF8) // cycles since boot. + +/* 10000000 ticks per-second */ +#define CLINT_TIMEBASE_FREQ 10000000 + +#endif /* __PLATFORM_H__ */ diff --git a/code/os/09-lock/plic.c b/code/os/09-lock/plic.c new file mode 100644 index 0000000..c28a8d1 --- /dev/null +++ b/code/os/09-lock/plic.c @@ -0,0 +1,78 @@ +#include "os.h" + +void plic_init(void) +{ + int hart = r_tp(); + + /* + * Set priority for UART0. + * + * Each PLIC interrupt source can be assigned a priority by writing + * to its 32-bit memory-mapped priority register. + * The QEMU-virt (the same as FU540-C000) supports 7 levels of priority. + * A priority value of 0 is reserved to mean "never interrupt" and + * effectively disables the interrupt. + * Priority 1 is the lowest active priority, and priority 7 is the highest. + * Ties between global interrupts of the same priority are broken by + * the Interrupt ID; interrupts with the lowest ID have the highest + * effective priority. + */ + *(uint32_t*)PLIC_PRIORITY(UART0_IRQ) = 1; + + /* + * Enable UART0 + * + * Each global interrupt can be enabled by setting the corresponding + * bit in the enables registers. + */ + *(uint32_t*)PLIC_MENABLE(hart)= (1 << UART0_IRQ); + + /* + * Set priority threshold for UART0. + * + * PLIC will mask all interrupts of a priority less than or equal to threshold. + * Maximum threshold is 7. + * For example, a threshold value of zero permits all interrupts with + * non-zero priority, whereas a value of 7 masks all interrupts. + * Notice, the threshold is global for PLIC, not for each interrupt source. + */ + *(uint32_t*)PLIC_MTHRESHOLD(hart) = 0; + + /* enable machine-mode external interrupts. */ + w_mie(r_mie() | MIE_MEIE); +} + +/* + * DESCRIPTION: + * Query the PLIC what interrupt we should serve. + * Perform an interrupt claim by reading the claim register, which + * returns the ID of the highest-priority pending interrupt or zero if there + * is no pending interrupt. + * A successful claim also atomically clears the corresponding pending bit + * on the interrupt source. + * RETURN VALUE: + * the ID of the highest-priority pending interrupt or zero if there + * is no pending interrupt. + */ +int plic_claim(void) +{ + int hart = r_tp(); + int irq = *(uint32_t*)PLIC_MCLAIM(hart); + return irq; +} + +/* + * DESCRIPTION: + * Writing the interrupt ID it received from the claim (irq) to the + * complete register would signal the PLIC we've served this IRQ. + * The PLIC does not check whether the completion ID is the same as the + * last claim ID for that target. If the completion ID does not match an + * interrupt source that is currently enabled for the target, the completion + * is silently ignored. + * RETURN VALUE: none + */ +void plic_complete(int irq) +{ + int hart = r_tp(); + *(uint32_t*)PLIC_MCOMPLETE(hart) = irq; +} diff --git a/code/os/09-lock/printf.c b/code/os/09-lock/printf.c new file mode 100644 index 0000000..93ace5b --- /dev/null +++ b/code/os/09-lock/printf.c @@ -0,0 +1,138 @@ +#include "os.h" + +/* + * ref: https://github.com/cccriscv/mini-riscv-os/blob/master/05-Preemptive/lib.c + */ + +static int _vsnprintf(char * out, size_t n, const char* s, va_list vl) +{ + int format = 0; + int longarg = 0; + size_t pos = 0; + for (; *s; s++) { + if (format) { + switch(*s) { + case 'l': { + longarg = 1; + break; + } + case 'p': { + longarg = 1; + if (out && pos < n) { + out[pos] = '0'; + } + pos++; + if (out && pos < n) { + out[pos] = 'x'; + } + pos++; + } + case 'x': { + long num = longarg ? va_arg(vl, long) : va_arg(vl, int); + int hexdigits = 2*(longarg ? sizeof(long) : sizeof(int))-1; + for(int i = hexdigits; i >= 0; i--) { + int d = (num >> (4*i)) & 0xF; + if (out && pos < n) { + out[pos] = (d < 10 ? '0'+d : 'a'+d-10); + } + pos++; + } + longarg = 0; + format = 0; + break; + } + case 'd': { + long num = longarg ? va_arg(vl, long) : va_arg(vl, int); + if (num < 0) { + num = -num; + if (out && pos < n) { + out[pos] = '-'; + } + pos++; + } + long digits = 1; + for (long nn = num; nn /= 10; digits++); + for (int i = digits-1; i >= 0; i--) { + if (out && pos + i < n) { + out[pos + i] = '0' + (num % 10); + } + num /= 10; + } + pos += digits; + longarg = 0; + format = 0; + break; + } + case 's': { + const char* s2 = va_arg(vl, const char*); + while (*s2) { + if (out && pos < n) { + out[pos] = *s2; + } + pos++; + s2++; + } + longarg = 0; + format = 0; + break; + } + case 'c': { + if (out && pos < n) { + out[pos] = (char)va_arg(vl,int); + } + pos++; + longarg = 0; + format = 0; + break; + } + default: + break; + } + } else if (*s == '%') { + format = 1; + } else { + if (out && pos < n) { + out[pos] = *s; + } + pos++; + } + } + if (out && pos < n) { + out[pos] = 0; + } else if (out && n) { + out[n-1] = 0; + } + return pos; +} + +static char out_buf[1000]; // buffer for _vprintf() + +static int _vprintf(const char* s, va_list vl) +{ + int res = _vsnprintf(NULL, -1, s, vl); + if (res+1 >= sizeof(out_buf)) { + uart_puts("error: output string size overflow\n"); + while(1) {} + } + _vsnprintf(out_buf, res + 1, s, vl); + uart_puts(out_buf); + return res; +} + +int printf(const char* s, ...) +{ + int res = 0; + va_list vl; + va_start(vl, s); + res = _vprintf(s, vl); + va_end(vl); + return res; +} + +void panic(char *s) +{ + printf("panic: "); + printf(s); + printf("\n"); + while(1){}; +} diff --git a/code/os/09-lock/riscv.h b/code/os/09-lock/riscv.h new file mode 100644 index 0000000..e5bd491 --- /dev/null +++ b/code/os/09-lock/riscv.h @@ -0,0 +1,102 @@ +#ifndef __RISCV_H__ +#define __RISCV_H__ + +#include "types.h" + +/* + * ref: https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/riscv.h + */ + +static inline reg_t r_tp() +{ + reg_t x; + asm volatile("mv %0, tp" : "=r" (x) ); + return x; +} + +/* which hart (core) is this? */ +static inline reg_t r_mhartid() +{ + reg_t x; + asm volatile("csrr %0, mhartid" : "=r" (x) ); + return x; +} + +/* Machine Status Register, mstatus */ +#define MSTATUS_MPP (3 << 11) +#define MSTATUS_SPP (1 << 8) + +#define MSTATUS_MPIE (1 << 7) +#define MSTATUS_SPIE (1 << 5) +#define MSTATUS_UPIE (1 << 4) + +#define MSTATUS_MIE (1 << 3) +#define MSTATUS_SIE (1 << 1) +#define MSTATUS_UIE (1 << 0) + +static inline reg_t r_mstatus() +{ + reg_t x; + asm volatile("csrr %0, mstatus" : "=r" (x) ); + return x; +} + +static inline void w_mstatus(reg_t x) +{ + asm volatile("csrw mstatus, %0" : : "r" (x)); +} + +/* + * machine exception program counter, holds the + * instruction address to which a return from + * exception will go. + */ +static inline void w_mepc(reg_t x) +{ + asm volatile("csrw mepc, %0" : : "r" (x)); +} + +static inline reg_t r_mepc() +{ + reg_t x; + asm volatile("csrr %0, mepc" : "=r" (x)); + return x; +} + +/* Machine Scratch register, for early trap handler */ +static inline void w_mscratch(reg_t x) +{ + asm volatile("csrw mscratch, %0" : : "r" (x)); +} + +/* Machine-mode interrupt vector */ +static inline void w_mtvec(reg_t x) +{ + asm volatile("csrw mtvec, %0" : : "r" (x)); +} + +/* Machine-mode Interrupt Enable */ +#define MIE_MEIE (1 << 11) // external +#define MIE_MTIE (1 << 7) // timer +#define MIE_MSIE (1 << 3) // software + +static inline reg_t r_mie() +{ + reg_t x; + asm volatile("csrr %0, mie" : "=r" (x) ); + return x; +} + +static inline void w_mie(reg_t x) +{ + asm volatile("csrw mie, %0" : : "r" (x)); +} + +static inline reg_t r_mcause() +{ + reg_t x; + asm volatile("csrr %0, mcause" : "=r" (x) ); + return x; +} + +#endif /* __RISCV_H__ */ diff --git a/code/os/09-lock/sched.c b/code/os/09-lock/sched.c new file mode 100644 index 0000000..c612a3a --- /dev/null +++ b/code/os/09-lock/sched.c @@ -0,0 +1,81 @@ +#include "os.h" + +/* defined in entry.S */ +extern void switch_to(struct context *next); + +#define MAX_TASKS 10 +#define STACK_SIZE 1024 +uint8_t task_stack[MAX_TASKS][STACK_SIZE]; +struct context ctx_tasks[MAX_TASKS]; + +/* + * _top is used to mark the max available position of ctx_tasks + * _current is used to point to the context of current task + */ +static int _top = 0; +static int _current = -1; + +void sched_init() +{ + w_mscratch(0); + + /* enable machine-mode software interrupts. */ + w_mie(r_mie() | MIE_MSIE); +} + +/* + * implment a simple cycle FIFO schedular + */ +void schedule() +{ + if (_top <= 0) { + panic("Num of task should be greater than zero!"); + return; + } + + _current = (_current + 1) % _top; + struct context *next = &(ctx_tasks[_current]); + switch_to(next); +} + +/* + * DESCRIPTION + * Create a task. + * - start_routin: task routune entry + * RETURN VALUE + * 0: success + * -1: if error occured + */ +int task_create(void (*start_routin)(void)) +{ + if (_top < MAX_TASKS) { + ctx_tasks[_top].sp = (reg_t) &task_stack[_top][STACK_SIZE - 1]; + ctx_tasks[_top].pc = (reg_t) start_routin; + _top++; + return 0; + } else { + return -1; + } +} + +/* + * DESCRIPTION + * task_yield() causes the calling task to relinquish the CPU and a new + * task gets to run. + */ +void task_yield() +{ + /* trigger a machine-level software interrupt */ + int id = r_mhartid(); + *(uint32_t*)CLINT_MSIP(id) = 1; +} + +/* + * a very rough implementaion, just to consume the cpu + */ +void task_delay(volatile int count) +{ + count *= 50000; + while (count--); +} + diff --git a/code/os/09-lock/start.S b/code/os/09-lock/start.S new file mode 100644 index 0000000..e49c063 --- /dev/null +++ b/code/os/09-lock/start.S @@ -0,0 +1,51 @@ +#include "platform.h" + + .equ STACK_SIZE, 8192 + + .global _start + + .text +_start: + # park harts with id != 0 + csrr t0, mhartid # read current hart id + mv tp, t0 # keep CPU's hartid in its tp for later usage. + bnez t0, park # if we're not on the hart 0 + # we park the hart + + # Set all bytes in the BSS section to zero. + la a0, _bss_start + la a1, _bss_end + bgeu a0, a1, 2f +1: + sw zero, (a0) + addi a0, a0, 4 + bltu a0, a1, 1b +2: + # Setup stacks, the stack grows from bottom to top, so we put the + # stack pointer to the very end of the stack range. + slli t0, t0, 10 # shift left the hart id by 1024 + la sp, stacks + STACK_SIZE # set the initial stack pointer + # to the end of the stack space + add sp, sp, t0 # move the current hart stack pointer + # to its place in the stack space + + # At the end of start_kernel, schedule() will call MRET to switch + # to the first task, so we parepare the mstatus here. + # Set mstatus.MPIE to 1, so MRET will enable the interrupt. + # Set mstatus.MPP to 3, so we still run in Machine mode after MRET. + # Notice: default mstatus is 0 + li t0, 3 << 11 | 1 << 7 + csrr a1, mstatus + or t0, t0, a1 + csrw mstatus, t0 + + j start_kernel # hart 0 jump to c + +park: + wfi + j park + +stacks: + .skip STACK_SIZE * MAXNUM_CPU # allocate space for the harts stacks + + .end # End of file diff --git a/code/os/09-lock/timer.c b/code/os/09-lock/timer.c new file mode 100644 index 0000000..f769ee3 --- /dev/null +++ b/code/os/09-lock/timer.c @@ -0,0 +1,39 @@ +#include "os.h" + +extern void schedule(void); + +/* interval ~= 1s */ +#define TIMER_INTERVAL CLINT_TIMEBASE_FREQ + +static uint32_t _tick = 0; + +/* load timer interval(in ticks) for next timer interrupt.*/ +void timer_load(int interval) +{ + /* each CPU has a separate source of timer interrupts. */ + int id = r_mhartid(); + + *(uint64_t*)CLINT_MTIMECMP(id) = *(uint64_t*)CLINT_MTIME + interval; +} + +void timer_init() +{ + /* + * On reset, mtime is cleared to zero, but the mtimecmp registers + * are not reset. So we have to init the mtimecmp manually. + */ + timer_load(TIMER_INTERVAL); + + /* enable machine-mode timer interrupts. */ + w_mie(r_mie() | MIE_MTIE); +} + +void timer_handler() +{ + _tick++; + printf("tick: %d\n", _tick); + + timer_load(TIMER_INTERVAL); + + schedule(); +} diff --git a/code/os/09-lock/trap.c b/code/os/09-lock/trap.c new file mode 100644 index 0000000..b3fa9cc --- /dev/null +++ b/code/os/09-lock/trap.c @@ -0,0 +1,89 @@ +#include "os.h" + +extern void trap_vector(void); +extern void uart_isr(void); +extern void timer_handler(void); +extern void schedule(void); + +void trap_init() +{ + /* + * set the trap-vector base-address for machine-mode + */ + w_mtvec((reg_t)trap_vector); +} + +void external_interrupt_handler() +{ + int irq = plic_claim(); + + if (irq == UART0_IRQ){ + uart_isr(); + } else if (irq) { + printf("unexpected interrupt irq = %d\n", irq); + } + + if (irq) { + plic_complete(irq); + } +} + +reg_t trap_handler(reg_t epc, reg_t cause) +{ + reg_t return_pc = epc; + reg_t cause_code = cause & 0xfff; + + if (cause & 0x80000000) { + /* Asynchronous trap - interrupt */ + switch (cause_code) { + case 3: + uart_puts("software interruption!\n"); + /* + * acknowledge the software interrupt by clearing + * the MSIP bit in mip. + */ + int id = r_mhartid(); + *(uint32_t*)CLINT_MSIP(id) = 0; + + schedule(); + + break; + case 7: + uart_puts("timer interruption!\n"); + timer_handler(); + break; + case 11: + uart_puts("external interruption!\n"); + external_interrupt_handler(); + break; + default: + uart_puts("unknown async exception!\n"); + break; + } + } else { + /* Synchronous trap - exception */ + printf("Sync exceptions!, code = %d\n", cause_code); + panic("OOPS! What can I do!"); + //return_pc += 4; + } + + return return_pc; +} + +void trap_test() +{ + /* + * Synchronous exception code = 7 + * Store/AMO access fault + */ + *(int *)0x00000000 = 100; + + /* + * Synchronous exception code = 5 + * Load access fault + */ + //int a = *(int *)0x00000000; + + uart_puts("Yeah! I'm return back from trap!\n"); +} + diff --git a/code/os/09-lock/types.h b/code/os/09-lock/types.h new file mode 100644 index 0000000..5051639 --- /dev/null +++ b/code/os/09-lock/types.h @@ -0,0 +1,14 @@ +#ifndef __TYPES_H__ +#define __TYPES_H__ + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long long uint64_t; + +/* + * RISCV32: register is 32bits width + */ +typedef uint32_t reg_t; + +#endif /* __TYPES_H__ */ diff --git a/code/os/09-lock/uart.c b/code/os/09-lock/uart.c new file mode 100644 index 0000000..2905331 --- /dev/null +++ b/code/os/09-lock/uart.c @@ -0,0 +1,149 @@ +#include "os.h" + +/* + * The UART control registers are memory-mapped at address UART0. + * This macro returns the address of one of the registers. + */ +#define UART_REG(reg) ((volatile uint8_t *)(UART0 + reg)) + +/* + * Reference + * [1]: TECHNICAL DATA ON 16550, http://byterunner.com/16550.html + */ + +/* + * UART control registers map. see [1] "PROGRAMMING TABLE" + * note some are reused by multiple functions + * 0 (write mode): THR/DLL + * 1 (write mode): IER/DLM + */ +#define RHR 0 // Receive Holding Register (read mode) +#define THR 0 // Transmit Holding Register (write mode) +#define DLL 0 // LSB of Divisor Latch (write mode) +#define IER 1 // Interrupt Enable Register (write mode) +#define DLM 1 // MSB of Divisor Latch (write mode) +#define FCR 2 // FIFO Control Register (write mode) +#define ISR 2 // Interrupt Status Register (read mode) +#define LCR 3 // Line Control Register +#define MCR 4 // Modem Control Register +#define LSR 5 // Line Status Register +#define MSR 6 // Modem Status Register +#define SPR 7 // ScratchPad Register + +/* + * POWER UP DEFAULTS + * IER = 0: TX/RX holding register interrupts are bith disabled + * ISR = 1: no interrupt penting + * LCR = 0 + * MCR = 0 + * LSR = 60 HEX + * MSR = BITS 0-3 = 0, BITS 4-7 = inputs + * FCR = 0 + * TX = High + * OP1 = High + * OP2 = High + * RTS = High + * DTR = High + * RXRDY = High + * TXRDY = Low + * INT = Low + */ + +/* + * LINE STATUS REGISTER (LSR) + * LSR BIT 0: + * 0 = no data in receive holding register or FIFO. + * 1 = data has been receive and saved in the receive holding register or FIFO. + * ...... + * LSR BIT 5: + * 0 = transmit holding register is full. 16550 will not accept any data for transmission. + * 1 = transmitter hold register (or FIFO) is empty. CPU can load the next character. + * ...... + */ +#define LSR_RX_READY (1 << 0) +#define LSR_TX_IDLE (1 << 5) + +#define uart_read_reg(reg) (*(UART_REG(reg))) +#define uart_write_reg(reg, v) (*(UART_REG(reg)) = (v)) + +void uart_init() +{ + /* disable interrupts. */ + uart_write_reg(IER, 0x00); + + /* + * Setting baud rate. Just a demo here if we care about the divisor, + * but for our purpose [QEMU-virt], this doesn't really do anything. + * + * Notice that the divisor register DLL (divisor latch least) and DLM (divisor + * latch most) have the same base address as the receiver/transmitter and the + * interrupt enable register. To change what the base address points to, we + * open the "divisor latch" by writing 1 into the Divisor Latch Access Bit + * (DLAB), which is bit index 7 of the Line Control Register (LCR). + * + * Regarding the baud rate value, see [1] "BAUD RATE GENERATOR PROGRAMMING TABLE". + * We use 38.4K when 1.8432 MHZ crystal, so the corresponding value is 3. + * And due to the divisor register is two bytes (16 bits), so we need to + * split the value of 3(0x0003) into two bytes, DLL stores the low byte, + * DLM stores the high byte. + */ + uint8_t lcr = uart_read_reg(LCR); + uart_write_reg(LCR, lcr | (1 << 7)); + uart_write_reg(DLL, 0x03); + uart_write_reg(DLM, 0x00); + + /* + * Continue setting the asynchronous data communication format. + * - number of the word length: 8 bits + * - number of stop bits:1 bit when word length is 8 bits + * - no parity + * - no break control + * - disabled baud latch + */ + lcr = 0; + uart_write_reg(LCR, lcr | (3 << 0)); + + /* + * enable receive interrupts. + */ + uint8_t ier = uart_read_reg(IER); + uart_write_reg(IER, ier | (1 << 0)); +} + +int uart_putc(char ch) +{ + while ((uart_read_reg(LSR) & LSR_TX_IDLE) == 0); + return uart_write_reg(THR, ch); +} + +void uart_puts(char *s) +{ + while (*s) { + uart_putc(*s++); + } +} + +int uart_getc(void) +{ + if (uart_read_reg(LSR) & LSR_RX_READY){ + return uart_read_reg(RHR); + } else { + return -1; + } +} + +/* + * handle a uart interrupt, raised because input has arrived, called from trap.c. + */ +void uart_isr(void) +{ + while (1) { + int c = uart_getc(); + if (c == -1) { + break; + } else { + uart_putc((char)c); + uart_putc('\n'); + } + } +} diff --git a/code/os/09-lock/user.c b/code/os/09-lock/user.c new file mode 100644 index 0000000..a56ed25 --- /dev/null +++ b/code/os/09-lock/user.c @@ -0,0 +1,45 @@ +#include "os.h" + +#define DELAY 4000 + +#define USE_LOCK + +void user_task0(void) +{ + uart_puts("Task 0: Created!\n"); + while (1) { +#ifdef USE_LOCK + spin_lock(); +#endif + uart_puts("Task 0: Begin ... \n"); + for (int i = 0; i < 5; i++) { + uart_puts("Task 0: Running... \n"); + task_delay(DELAY); + } + uart_puts("Task 0: End ... \n"); +#ifdef USE_LOCK + spin_unlock(); +#endif + } +} + +void user_task1(void) +{ + uart_puts("Task 1: Created!\n"); + while (1) { + uart_puts("Task 1: Begin ... \n"); + for (int i = 0; i < 5; i++) { + uart_puts("Task 1: Running... \n"); + task_delay(DELAY); + } + uart_puts("Task 1: End ... \n"); + } +} + +/* NOTICE: DON'T LOOP INFINITELY IN main() */ +void os_main(void) +{ + task_create(user_task0); + task_create(user_task1); +} + diff --git a/code/os/10-swtimer/Makefile b/code/os/10-swtimer/Makefile new file mode 100644 index 0000000..90296dd --- /dev/null +++ b/code/os/10-swtimer/Makefile @@ -0,0 +1,66 @@ +CROSS_COMPILE = riscv64-unknown-elf- +CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall + +QEMU = qemu-system-riscv32 +QFLAGS = -nographic -smp 1 -machine virt -bios none + +GDB = ${CROSS_COMPILE}gdb +CC = ${CROSS_COMPILE}gcc +OBJCOPY = ${CROSS_COMPILE}objcopy +OBJDUMP = ${CROSS_COMPILE}objdump + +SRCS_ASM = \ + start.S \ + mem.S \ + entry.S \ + +SRCS_C = \ + kernel.c \ + uart.c \ + printf.c \ + page.c \ + sched.c \ + user.c \ + trap.c \ + plic.c \ + timer.c \ + lock.c \ + +OBJS = $(SRCS_ASM:.S=.o) +OBJS += $(SRCS_C:.c=.o) + +.DEFAULT_GOAL := all +all: os.elf + +# start.o must be the first in dependency! +os.elf: ${OBJS} + ${CC} $(CFLAGS) -T os.ld -o os.elf $^ + ${OBJCOPY} -O binary os.elf os.bin + +%.o : %.c + ${CC} ${CFLAGS} -c -o $@ $< + +%.o : %.S + ${CC} ${CFLAGS} -c -o $@ $< + +run: all + @${QEMU} -M ? | grep virt >/dev/null || exit + @echo "Press Ctrl-A and then X to exit QEMU" + @echo "------------------------------------" + @${QEMU} ${QFLAGS} -kernel os.elf + +.PHONY : debug +debug: all + @echo "Press Ctrl-C and then input 'quit' to exit GDB and QEMU" + @echo "-------------------------------------------------------" + @${QEMU} ${QFLAGS} -kernel os.elf -s -S & + @${GDB} os.elf -q -x ../gdbinit + +.PHONY : code +code: all + @${OBJDUMP} -S os.elf | less + +.PHONY : clean +clean: + rm -rf *.o *.bin *.elf + diff --git a/code/os/10-swtimer/entry.S b/code/os/10-swtimer/entry.S new file mode 100644 index 0000000..146c75d --- /dev/null +++ b/code/os/10-swtimer/entry.S @@ -0,0 +1,134 @@ +# save all General-Purpose(GP) registers to context +# struct context *base = &ctx_task; +# base->ra = ra; +# ...... +.macro reg_save base + sw ra, 0(\base) + sw sp, 4(\base) + sw gp, 8(\base) + sw tp, 12(\base) + sw t0, 16(\base) + sw t1, 20(\base) + sw t2, 24(\base) + sw s0, 28(\base) + sw s1, 32(\base) + sw a0, 36(\base) + sw a1, 40(\base) + sw a2, 44(\base) + sw a3, 48(\base) + sw a4, 52(\base) + sw a5, 56(\base) + sw a6, 60(\base) + sw a7, 64(\base) + sw s2, 68(\base) + sw s3, 72(\base) + sw s4, 76(\base) + sw s5, 80(\base) + sw s6, 84(\base) + sw s7, 88(\base) + sw s8, 92(\base) + sw s9, 96(\base) + sw s10, 100(\base) + sw s11, 104(\base) + sw t3, 108(\base) + sw t4, 112(\base) + sw t5, 116(\base) + sw t6, 120(\base) +.endm + +# restore all General-Purpose(GP) registers from the context +# struct context *base = &ctx_task; +# ra = base->ra; +# ...... +.macro reg_restore base + lw ra, 0(\base) + lw sp, 4(\base) + lw gp, 8(\base) + lw tp, 12(\base) + lw t0, 16(\base) + lw t1, 20(\base) + lw t2, 24(\base) + lw s0, 28(\base) + lw s1, 32(\base) + lw a0, 36(\base) + lw a1, 40(\base) + lw a2, 44(\base) + lw a3, 48(\base) + lw a4, 52(\base) + lw a5, 56(\base) + lw a6, 60(\base) + lw a7, 64(\base) + lw s2, 68(\base) + lw s3, 72(\base) + lw s4, 76(\base) + lw s5, 80(\base) + lw s6, 84(\base) + lw s7, 88(\base) + lw s8, 92(\base) + lw s9, 96(\base) + lw s10, 100(\base) + lw s11, 104(\base) + lw t3, 108(\base) + lw t4, 112(\base) + lw t5, 116(\base) + lw t6, 120(\base) +.endm + +# Something to note about save/restore: +# - We use mscratch to hold a pointer to context of previous task +# - We use t6 as the 'base' for reg_save/reg_restore, because it is the +# very bottom register (x31) and would not be overwritten during loading. + +.text + +# interrupts and exceptions while in machine mode come here. +.globl trap_vector +# the trap vector base address must always be aligned on a 4-byte boundary +.align 4 +trap_vector: + # save context(registers). + csrrw t6, mscratch, t6 # swap t6 and mscratch + reg_save t6 + csrw mscratch, t6 + + # save mepc to context of current task + csrr a0, mepc + sw a0, 124(t6) + + # call the C trap handler in trap.c + csrr a0, mepc + csrr a1, mcause + call trap_handler + + # trap_handler will return the return address via a0. + csrw mepc, a0 + + # restore context(registers). + csrr t6, mscratch + reg_restore t6 + + # return to whatever we were doing before trap. + mret + +# void switch_to(struct context *next); +# a0: pointer to the context of the next task +.globl switch_to +.align 4 +switch_to: + # switch mscratch to point to the context of the next task + csrw mscratch, a0 + # set mepc to the pc of the next task + lw a1, 124(a0) + csrw mepc, a1 + + # Restore all GP registers + # Use t6 to point to the context of the new task + mv t6, a0 + reg_restore t6 + + # Do actual context switching. + # Notice this will enable global interrupt + mret + +.end + diff --git a/code/os/10-swtimer/kernel.c b/code/os/10-swtimer/kernel.c new file mode 100644 index 0000000..b4e8096 --- /dev/null +++ b/code/os/10-swtimer/kernel.c @@ -0,0 +1,38 @@ +#include "os.h" + +/* + * Following functions SHOULD be called ONLY ONE time here, + * so just declared here ONCE and NOT included in file os.h. + */ +extern void uart_init(void); +extern void page_init(void); +extern void sched_init(void); +extern void schedule(void); +extern void os_main(void); +extern void trap_init(void); +extern void plic_init(void); +extern void timer_init(void); + +void start_kernel(void) +{ + uart_init(); + uart_puts("Hello, RVOS!\n"); + + page_init(); + + trap_init(); + + plic_init(); + + timer_init(); + + sched_init(); + + os_main(); + + schedule(); + + uart_puts("Would not go here!\n"); + while (1) {}; // stop here! +} + diff --git a/code/os/10-swtimer/lock.c b/code/os/10-swtimer/lock.c new file mode 100644 index 0000000..0866fc9 --- /dev/null +++ b/code/os/10-swtimer/lock.c @@ -0,0 +1,13 @@ +#include "os.h" + +int spin_lock() +{ + w_mstatus(r_mstatus() & ~MSTATUS_MIE); + return 0; +} + +int spin_unlock() +{ + w_mstatus(r_mstatus() | MSTATUS_MIE); + return 0; +} diff --git a/code/os/10-swtimer/mem.S b/code/os/10-swtimer/mem.S new file mode 100644 index 0000000..93ba0d6 --- /dev/null +++ b/code/os/10-swtimer/mem.S @@ -0,0 +1,30 @@ +.section .rodata +.global HEAP_START +HEAP_START: .word _heap_start + +.global HEAP_SIZE +HEAP_SIZE: .word _heap_size + +.global TEXT_START +TEXT_START: .word _text_start + +.global TEXT_END +TEXT_END: .word _text_end + +.global DATA_START +DATA_START: .word _data_start + +.global DATA_END +DATA_END: .word _data_end + +.global RODATA_START +RODATA_START: .word _rodata_start + +.global RODATA_END +RODATA_END: .word _rodata_end + +.global BSS_START +BSS_START: .word _bss_start + +.global BSS_END +BSS_END: .word _bss_end diff --git a/code/os/10-swtimer/os.h b/code/os/10-swtimer/os.h new file mode 100644 index 0000000..5ee343c --- /dev/null +++ b/code/os/10-swtimer/os.h @@ -0,0 +1,85 @@ +#ifndef __OS_H__ +#define __OS_H__ + +#include "types.h" +#include "riscv.h" +#include "platform.h" + +#include +#include + +/* uart */ +extern int uart_putc(char ch); +extern void uart_puts(char *s); +extern int uart_getc(void); + +/* printf */ +extern int printf(const char* s, ...); +extern void panic(char *s); + +/* memory management */ +extern void *page_alloc(int npages); +extern void page_free(void *p); + +/* task management */ +struct context { + /* ignore x0 */ + reg_t ra; + reg_t sp; + reg_t gp; + reg_t tp; + reg_t t0; + reg_t t1; + reg_t t2; + reg_t s0; + reg_t s1; + reg_t a0; + reg_t a1; + reg_t a2; + reg_t a3; + reg_t a4; + reg_t a5; + reg_t a6; + reg_t a7; + reg_t s2; + reg_t s3; + reg_t s4; + reg_t s5; + reg_t s6; + reg_t s7; + reg_t s8; + reg_t s9; + reg_t s10; + reg_t s11; + reg_t t3; + reg_t t4; + reg_t t5; + reg_t t6; + // upon is trap frame + + // save the pc to run in next schedule cycle + reg_t pc; // offset: 31 *4 = 124 +}; + +extern int task_create(void (*task)(void)); +extern void task_delay(volatile int count); +extern void task_yield(); + +/* plic */ +extern int plic_claim(void); +extern void plic_complete(int irq); + +/* lock */ +extern int spin_lock(void); +extern int spin_unlock(void); + +/* software timer */ +struct timer { + void (*func)(void *arg); + void *arg; + uint32_t timeout_tick; +}; +extern struct timer *timer_create(void (*handler)(void *arg), void *arg, uint32_t timeout); +extern void timer_delete(struct timer *timer); + +#endif /* __OS_H__ */ diff --git a/code/os/10-swtimer/os.ld b/code/os/10-swtimer/os.ld new file mode 100644 index 0000000..a75cd93 --- /dev/null +++ b/code/os/10-swtimer/os.ld @@ -0,0 +1,136 @@ +/* + * rvos.ld + * Linker script for outputting to RVOS + */ + +/* + * https://sourceware.org/binutils/docs/ld/Miscellaneous-Commands.html + * OUTPUT_ARCH command specifies a particular output machine architecture. + * "riscv" is the name of the architecture for both 64-bit and 32-bit + * RISC-V target. We will further refine this by using -march=rv32ima + * and -mabi=ilp32 when calling gcc. + */ +OUTPUT_ARCH( "riscv" ) + +/* + * https://sourceware.org/binutils/docs/ld/Entry-Point.html + * ENTRY command is used to set the "entry point", which is the first instruction + * to execute in a program. + * The argument of ENTRY command is a symbol name, here is "_start" which is + * defined in start.S. + */ +ENTRY( _start ) + +/* + * https://sourceware.org/binutils/docs/ld/MEMORY.html + * The MEMORY command describes the location and size of blocks of memory in + * the target. + * The syntax for MEMORY is: + * MEMORY + * { + * name [(attr)] : ORIGIN = origin, LENGTH = len + * ...... + * } + * Each line defines a memory region. + * Each memory region must have a distinct name within the MEMORY command. Here + * we only define one region named as "ram". + * The "attr" string is an optional list of attributes that specify whether to + * use a particular memory region for an input section which is not explicitly + * mapped in the linker script. Here we assign 'w' (writeable), 'x' (executable), + * and 'a' (allocatable). We use '!' to invert 'r' (read-only) and + * 'i' (initialized). + * The "ORIGIN" is used to set the start address of the memory region. Here we + * place it right at the beginning of 0x8000_0000 because this is where the + * QEMU-virt machine will start executing. + * Finally LENGTH = 128M tells the linker that we have 128 megabyte of RAM. + * The linker will double check this to make sure everything can fit. + */ +MEMORY +{ + ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M +} + +/* + * https://sourceware.org/binutils/docs/ld/SECTIONS.html + * The SECTIONS command tells the linker how to map input sections into output + * sections, and how to place the output sections in memory. + * The format of the SECTIONS command is: + * SECTIONS + * { + * sections-command + * sections-command + * ...... + * } + * + * Each sections-command may of be one of the following: + * (1) an ENTRY command + * (2) a symbol assignment + * (3) an output section description + * (4) an overlay description + * We here only demo (2) & (3). + * + * We use PROVIDE command to define symbols. + * https://sourceware.org/binutils/docs/ld/PROVIDE.html + * The PROVIDE keyword may be used to define a symbol. + * The syntax is PROVIDE(symbol = expression). + * Such symbols as "_text_start", "_text_end" ... will be used in mem.S. + * Notice the period '.' tells the linker to set symbol(e.g. _text_start) to + * the CURRENT location ('.' = current memory location). This current memory + * location moves as we add things. + */ +SECTIONS +{ + /* + * We are going to layout all text sections in .text output section, + * starting with .text. The asterisk("*") in front of the + * parentheses means to match the .text section of ANY object file. + */ + .text : { + PROVIDE(_text_start = .); + *(.text .text.*) + PROVIDE(_text_end = .); + } >ram + + .rodata : { + PROVIDE(_rodata_start = .); + *(.rodata .rodata.*) + PROVIDE(_rodata_end = .); + } >ram + + .data : { + /* + * . = ALIGN(4096) tells the linker to align the current memory + * location to 4096 bytes. This will insert padding bytes until + * current location becomes aligned on 4096-byte boundary. + * This is because our paging system's resolution is 4,096 bytes. + */ + . = ALIGN(4096); + PROVIDE(_data_start = .); + /* + * sdata and data are essentially the same thing. We do not need + * to distinguish sdata from data. + */ + *(.sdata .sdata.*) + *(.data .data.*) + PROVIDE(_data_end = .); + } >ram + + .bss :{ + /* + * https://sourceware.org/binutils/docs/ld/Input-Section-Common.html + * In most cases, common symbols in input files will be placed + * in the ‘.bss’ section in the output file. + */ + PROVIDE(_bss_start = .); + *(.sbss .sbss.*) + *(.bss .bss.*) + *(COMMON) + PROVIDE(_bss_end = .); + } >ram + + PROVIDE(_memory_start = ORIGIN(ram)); + PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram)); + + PROVIDE(_heap_start = _bss_end); + PROVIDE(_heap_size = _memory_end - _heap_start); +} diff --git a/code/os/10-swtimer/page.c b/code/os/10-swtimer/page.c new file mode 100644 index 0000000..addf097 --- /dev/null +++ b/code/os/10-swtimer/page.c @@ -0,0 +1,189 @@ +#include "os.h" + +/* + * Following global vars are defined in mem.S + */ +extern uint32_t TEXT_START; +extern uint32_t TEXT_END; +extern uint32_t DATA_START; +extern uint32_t DATA_END; +extern uint32_t RODATA_START; +extern uint32_t RODATA_END; +extern uint32_t BSS_START; +extern uint32_t BSS_END; +extern uint32_t HEAP_START; +extern uint32_t HEAP_SIZE; + +/* + * _alloc_start points to the actual start address of heap pool + * _alloc_end points to the actual end address of heap pool + * _num_pages holds the actual max number of pages we can allocate. + */ +static uint32_t _alloc_start = 0; +static uint32_t _alloc_end = 0; +static uint32_t _num_pages = 0; + +#define PAGE_SIZE 4096 +#define PAGE_ORDER 12 + +#define PAGE_TAKEN (uint8_t)(1 << 0) +#define PAGE_LAST (uint8_t)(1 << 1) + +/* + * Page Descriptor + * flags: + * - bit 0: flag if this page is taken(allocated) + * - bit 1: flag if this page is the last page of the memory block allocated + */ +struct Page { + uint8_t flags; +}; + +static inline void _clear(struct Page *page) +{ + page->flags = 0; +} + +static inline int _is_free(struct Page *page) +{ + if (page->flags & PAGE_TAKEN) { + return 0; + } else { + return 1; + } +} + +static inline void _set_flag(struct Page *page, uint8_t flags) +{ + page->flags |= flags; +} + +static inline int _is_last(struct Page *page) +{ + if (page->flags & PAGE_LAST) { + return 1; + } else { + return 0; + } +} + +/* + * align the address to the border of page(4K) + */ +static inline uint32_t _align_page(uint32_t address) +{ + uint32_t order = (1 << PAGE_ORDER) - 1; + return (address + order) & (~order); +} + +void page_init() +{ + /* + * We reserved 8 Page (8 x 4096) to hold the Page structures. + * It should be enough to manage at most 128 MB (8 x 4096 x 4096) + */ + _num_pages = (HEAP_SIZE / PAGE_SIZE) - 8; + printf("HEAP_START = %x, HEAP_SIZE = %x, num of pages = %d\n", HEAP_START, HEAP_SIZE, _num_pages); + + struct Page *page = (struct Page *)HEAP_START; + for (int i = 0; i < _num_pages; i++) { + _clear(page); + page++; + } + + _alloc_start = _align_page(HEAP_START + 8 * PAGE_SIZE); + _alloc_end = _alloc_start + (PAGE_SIZE * _num_pages); + + printf("TEXT: 0x%x -> 0x%x\n", TEXT_START, TEXT_END); + printf("RODATA: 0x%x -> 0x%x\n", RODATA_START, RODATA_END); + printf("DATA: 0x%x -> 0x%x\n", DATA_START, DATA_END); + printf("BSS: 0x%x -> 0x%x\n", BSS_START, BSS_END); + printf("HEAP: 0x%x -> 0x%x\n", _alloc_start, _alloc_end); +} + +/* + * Allocate a memory block which is composed of contiguous physical pages + * - npages: the number of PAGE_SIZE pages to allocate + */ +void *page_alloc(int npages) +{ + /* Note we are searching the page descriptor bitmaps. */ + int found = 0; + struct Page *page_i = (struct Page *)HEAP_START; + for (int i = 0; i < (_num_pages - npages); i++) { + if (_is_free(page_i)) { + found = 1; + /* + * meet a free page, continue to check if following + * (npages - 1) pages are also unallocated. + */ + struct Page *page_j = page_i; + for (int j = i; j < (i + npages); j++) { + if (!_is_free(page_j)) { + found = 0; + break; + } + page_j++; + } + /* + * get a memory block which is good enough for us, + * take housekeeping, then return the actual start + * address of the first page of this memory block + */ + if (found) { + struct Page *page_k = page_i; + for (int k = i; k < (i + npages); k++) { + _set_flag(page_k, PAGE_TAKEN); + page_k++; + } + page_k--; + _set_flag(page_k, PAGE_LAST); + return (void *)(_alloc_start + i * PAGE_SIZE); + } + } + page_i++; + } + return NULL; +} + +/* + * Free the memory block + * - p: start address of the memory block + */ +void page_free(void *p) +{ + /* + * Assert (TBD) if p is invalid + */ + if (!p || (uint32_t)p >= _alloc_end) { + return; + } + /* get the first page descriptor of this memory block */ + struct Page *page = (struct Page *)HEAP_START; + page += ((uint32_t)p - _alloc_start)/ PAGE_SIZE; + /* loop and clear all the page descriptors of the memory block */ + while (!_is_free(page)) { + if (_is_last(page)) { + _clear(page); + break; + } else { + _clear(page); + page++;; + } + } +} + +void page_test() +{ + void *p = page_alloc(2); + printf("p = 0x%x\n", p); + //page_free(p); + + void *p2 = page_alloc(7); + printf("p2 = 0x%x\n", p2); + page_free(p2); + + void *p3 = page_alloc(4); + printf("p3 = 0x%x\n", p3); +} + diff --git a/code/os/10-swtimer/platform.h b/code/os/10-swtimer/platform.h new file mode 100644 index 0000000..3319f4f --- /dev/null +++ b/code/os/10-swtimer/platform.h @@ -0,0 +1,97 @@ +#ifndef __PLATFORM_H__ +#define __PLATFORM_H__ + +/* + * QEMU RISC-V Virt machine with 16550a UART and VirtIO MMIO + */ + +/* + * maximum number of CPUs + * see https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h + * #define VIRT_CPUS_MAX 8 + */ +#define MAXNUM_CPU 8 + +/* + * MemoryMap + * see https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c, virt_memmap[] + * 0x00001000 -- boot ROM, provided by qemu + * 0x02000000 -- CLINT + * 0x0C000000 -- PLIC + * 0x10000000 -- UART0 + * 0x10001000 -- virtio disk + * 0x80000000 -- boot ROM jumps here in machine mode, where we load our kernel + */ + +/* This machine puts UART registers here in physical memory. */ +#define UART0 0x10000000L + +/* + * UART0 interrupt source + * see https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h + * enum { + * UART0_IRQ = 10, + * ...... + * }; + */ +#define UART0_IRQ 10 + +/* + * This machine puts platform-level interrupt controller (PLIC) here. + * Here only list PLIC registers in Machine mode. + * see https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h + * #define VIRT_PLIC_HART_CONFIG "MS" + * #define VIRT_PLIC_NUM_SOURCES 127 + * #define VIRT_PLIC_NUM_PRIORITIES 7 + * #define VIRT_PLIC_PRIORITY_BASE 0x04 + * #define VIRT_PLIC_PENDING_BASE 0x1000 + * #define VIRT_PLIC_ENABLE_BASE 0x2000 + * #define VIRT_PLIC_ENABLE_STRIDE 0x80 + * #define VIRT_PLIC_CONTEXT_BASE 0x200000 + * #define VIRT_PLIC_CONTEXT_STRIDE 0x1000 + * #define VIRT_PLIC_SIZE(__num_context) \ + * (VIRT_PLIC_CONTEXT_BASE + (__num_context) * VIRT_PLIC_CONTEXT_STRIDE) + */ +#define PLIC_BASE 0x0c000000L +#define PLIC_PRIORITY(id) (PLIC_BASE + (id) * 4) +#define PLIC_PENDING(id) (PLIC_BASE + 0x1000 + ((id) / 32)) +#define PLIC_MENABLE(hart) (PLIC_BASE + 0x2000 + (hart) * 0x80) +#define PLIC_MTHRESHOLD(hart) (PLIC_BASE + 0x200000 + (hart) * 0x1000) +#define PLIC_MCLAIM(hart) (PLIC_BASE + 0x200004 + (hart) * 0x1000) +#define PLIC_MCOMPLETE(hart) (PLIC_BASE + 0x200004 + (hart) * 0x1000) + + /* + * The Core Local INTerruptor (CLINT) block holds memory-mapped control and + * status registers associated with software and timer interrupts. + * QEMU-virt reuses sifive configuration for CLINT. + * see https://gitee.com/qemu/qemu/blob/master/include/hw/riscv/sifive_clint.h + * enum { + * SIFIVE_SIP_BASE = 0x0, + * SIFIVE_TIMECMP_BASE = 0x4000, + * SIFIVE_TIME_BASE = 0xBFF8 + * }; + * + * enum { + * SIFIVE_CLINT_TIMEBASE_FREQ = 10000000 + * }; + * + * Notice: + * The machine-level MSIP bit of mip register are written by accesses to + * memory-mapped control registers, which are used by remote harts to provide + * machine-mode interprocessor interrupts. + * For QEMU-virt machine, Each msip register is a 32-bit wide WARL register + * where the upper 31 bits are tied to 0. The least significant bit is + * reflected in the MSIP bit of the mip CSR. We can write msip to generate + * machine-mode software interrupts. A pending machine-level software + * interrupt can be cleared by writing 0 to the MSIP bit in mip. + * On reset, each msip register is cleared to zero. + */ +#define CLINT_BASE 0x2000000L +#define CLINT_MSIP(hartid) (CLINT_BASE + 4 * (hartid)) +#define CLINT_MTIMECMP(hartid) (CLINT_BASE + 0x4000 + 8 * (hartid)) +#define CLINT_MTIME (CLINT_BASE + 0xBFF8) // cycles since boot. + +/* 10000000 ticks per-second */ +#define CLINT_TIMEBASE_FREQ 10000000 + +#endif /* __PLATFORM_H__ */ diff --git a/code/os/10-swtimer/plic.c b/code/os/10-swtimer/plic.c new file mode 100644 index 0000000..c28a8d1 --- /dev/null +++ b/code/os/10-swtimer/plic.c @@ -0,0 +1,78 @@ +#include "os.h" + +void plic_init(void) +{ + int hart = r_tp(); + + /* + * Set priority for UART0. + * + * Each PLIC interrupt source can be assigned a priority by writing + * to its 32-bit memory-mapped priority register. + * The QEMU-virt (the same as FU540-C000) supports 7 levels of priority. + * A priority value of 0 is reserved to mean "never interrupt" and + * effectively disables the interrupt. + * Priority 1 is the lowest active priority, and priority 7 is the highest. + * Ties between global interrupts of the same priority are broken by + * the Interrupt ID; interrupts with the lowest ID have the highest + * effective priority. + */ + *(uint32_t*)PLIC_PRIORITY(UART0_IRQ) = 1; + + /* + * Enable UART0 + * + * Each global interrupt can be enabled by setting the corresponding + * bit in the enables registers. + */ + *(uint32_t*)PLIC_MENABLE(hart)= (1 << UART0_IRQ); + + /* + * Set priority threshold for UART0. + * + * PLIC will mask all interrupts of a priority less than or equal to threshold. + * Maximum threshold is 7. + * For example, a threshold value of zero permits all interrupts with + * non-zero priority, whereas a value of 7 masks all interrupts. + * Notice, the threshold is global for PLIC, not for each interrupt source. + */ + *(uint32_t*)PLIC_MTHRESHOLD(hart) = 0; + + /* enable machine-mode external interrupts. */ + w_mie(r_mie() | MIE_MEIE); +} + +/* + * DESCRIPTION: + * Query the PLIC what interrupt we should serve. + * Perform an interrupt claim by reading the claim register, which + * returns the ID of the highest-priority pending interrupt or zero if there + * is no pending interrupt. + * A successful claim also atomically clears the corresponding pending bit + * on the interrupt source. + * RETURN VALUE: + * the ID of the highest-priority pending interrupt or zero if there + * is no pending interrupt. + */ +int plic_claim(void) +{ + int hart = r_tp(); + int irq = *(uint32_t*)PLIC_MCLAIM(hart); + return irq; +} + +/* + * DESCRIPTION: + * Writing the interrupt ID it received from the claim (irq) to the + * complete register would signal the PLIC we've served this IRQ. + * The PLIC does not check whether the completion ID is the same as the + * last claim ID for that target. If the completion ID does not match an + * interrupt source that is currently enabled for the target, the completion + * is silently ignored. + * RETURN VALUE: none + */ +void plic_complete(int irq) +{ + int hart = r_tp(); + *(uint32_t*)PLIC_MCOMPLETE(hart) = irq; +} diff --git a/code/os/10-swtimer/printf.c b/code/os/10-swtimer/printf.c new file mode 100644 index 0000000..93ace5b --- /dev/null +++ b/code/os/10-swtimer/printf.c @@ -0,0 +1,138 @@ +#include "os.h" + +/* + * ref: https://github.com/cccriscv/mini-riscv-os/blob/master/05-Preemptive/lib.c + */ + +static int _vsnprintf(char * out, size_t n, const char* s, va_list vl) +{ + int format = 0; + int longarg = 0; + size_t pos = 0; + for (; *s; s++) { + if (format) { + switch(*s) { + case 'l': { + longarg = 1; + break; + } + case 'p': { + longarg = 1; + if (out && pos < n) { + out[pos] = '0'; + } + pos++; + if (out && pos < n) { + out[pos] = 'x'; + } + pos++; + } + case 'x': { + long num = longarg ? va_arg(vl, long) : va_arg(vl, int); + int hexdigits = 2*(longarg ? sizeof(long) : sizeof(int))-1; + for(int i = hexdigits; i >= 0; i--) { + int d = (num >> (4*i)) & 0xF; + if (out && pos < n) { + out[pos] = (d < 10 ? '0'+d : 'a'+d-10); + } + pos++; + } + longarg = 0; + format = 0; + break; + } + case 'd': { + long num = longarg ? va_arg(vl, long) : va_arg(vl, int); + if (num < 0) { + num = -num; + if (out && pos < n) { + out[pos] = '-'; + } + pos++; + } + long digits = 1; + for (long nn = num; nn /= 10; digits++); + for (int i = digits-1; i >= 0; i--) { + if (out && pos + i < n) { + out[pos + i] = '0' + (num % 10); + } + num /= 10; + } + pos += digits; + longarg = 0; + format = 0; + break; + } + case 's': { + const char* s2 = va_arg(vl, const char*); + while (*s2) { + if (out && pos < n) { + out[pos] = *s2; + } + pos++; + s2++; + } + longarg = 0; + format = 0; + break; + } + case 'c': { + if (out && pos < n) { + out[pos] = (char)va_arg(vl,int); + } + pos++; + longarg = 0; + format = 0; + break; + } + default: + break; + } + } else if (*s == '%') { + format = 1; + } else { + if (out && pos < n) { + out[pos] = *s; + } + pos++; + } + } + if (out && pos < n) { + out[pos] = 0; + } else if (out && n) { + out[n-1] = 0; + } + return pos; +} + +static char out_buf[1000]; // buffer for _vprintf() + +static int _vprintf(const char* s, va_list vl) +{ + int res = _vsnprintf(NULL, -1, s, vl); + if (res+1 >= sizeof(out_buf)) { + uart_puts("error: output string size overflow\n"); + while(1) {} + } + _vsnprintf(out_buf, res + 1, s, vl); + uart_puts(out_buf); + return res; +} + +int printf(const char* s, ...) +{ + int res = 0; + va_list vl; + va_start(vl, s); + res = _vprintf(s, vl); + va_end(vl); + return res; +} + +void panic(char *s) +{ + printf("panic: "); + printf(s); + printf("\n"); + while(1){}; +} diff --git a/code/os/10-swtimer/riscv.h b/code/os/10-swtimer/riscv.h new file mode 100644 index 0000000..e5bd491 --- /dev/null +++ b/code/os/10-swtimer/riscv.h @@ -0,0 +1,102 @@ +#ifndef __RISCV_H__ +#define __RISCV_H__ + +#include "types.h" + +/* + * ref: https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/riscv.h + */ + +static inline reg_t r_tp() +{ + reg_t x; + asm volatile("mv %0, tp" : "=r" (x) ); + return x; +} + +/* which hart (core) is this? */ +static inline reg_t r_mhartid() +{ + reg_t x; + asm volatile("csrr %0, mhartid" : "=r" (x) ); + return x; +} + +/* Machine Status Register, mstatus */ +#define MSTATUS_MPP (3 << 11) +#define MSTATUS_SPP (1 << 8) + +#define MSTATUS_MPIE (1 << 7) +#define MSTATUS_SPIE (1 << 5) +#define MSTATUS_UPIE (1 << 4) + +#define MSTATUS_MIE (1 << 3) +#define MSTATUS_SIE (1 << 1) +#define MSTATUS_UIE (1 << 0) + +static inline reg_t r_mstatus() +{ + reg_t x; + asm volatile("csrr %0, mstatus" : "=r" (x) ); + return x; +} + +static inline void w_mstatus(reg_t x) +{ + asm volatile("csrw mstatus, %0" : : "r" (x)); +} + +/* + * machine exception program counter, holds the + * instruction address to which a return from + * exception will go. + */ +static inline void w_mepc(reg_t x) +{ + asm volatile("csrw mepc, %0" : : "r" (x)); +} + +static inline reg_t r_mepc() +{ + reg_t x; + asm volatile("csrr %0, mepc" : "=r" (x)); + return x; +} + +/* Machine Scratch register, for early trap handler */ +static inline void w_mscratch(reg_t x) +{ + asm volatile("csrw mscratch, %0" : : "r" (x)); +} + +/* Machine-mode interrupt vector */ +static inline void w_mtvec(reg_t x) +{ + asm volatile("csrw mtvec, %0" : : "r" (x)); +} + +/* Machine-mode Interrupt Enable */ +#define MIE_MEIE (1 << 11) // external +#define MIE_MTIE (1 << 7) // timer +#define MIE_MSIE (1 << 3) // software + +static inline reg_t r_mie() +{ + reg_t x; + asm volatile("csrr %0, mie" : "=r" (x) ); + return x; +} + +static inline void w_mie(reg_t x) +{ + asm volatile("csrw mie, %0" : : "r" (x)); +} + +static inline reg_t r_mcause() +{ + reg_t x; + asm volatile("csrr %0, mcause" : "=r" (x) ); + return x; +} + +#endif /* __RISCV_H__ */ diff --git a/code/os/10-swtimer/sched.c b/code/os/10-swtimer/sched.c new file mode 100644 index 0000000..c612a3a --- /dev/null +++ b/code/os/10-swtimer/sched.c @@ -0,0 +1,81 @@ +#include "os.h" + +/* defined in entry.S */ +extern void switch_to(struct context *next); + +#define MAX_TASKS 10 +#define STACK_SIZE 1024 +uint8_t task_stack[MAX_TASKS][STACK_SIZE]; +struct context ctx_tasks[MAX_TASKS]; + +/* + * _top is used to mark the max available position of ctx_tasks + * _current is used to point to the context of current task + */ +static int _top = 0; +static int _current = -1; + +void sched_init() +{ + w_mscratch(0); + + /* enable machine-mode software interrupts. */ + w_mie(r_mie() | MIE_MSIE); +} + +/* + * implment a simple cycle FIFO schedular + */ +void schedule() +{ + if (_top <= 0) { + panic("Num of task should be greater than zero!"); + return; + } + + _current = (_current + 1) % _top; + struct context *next = &(ctx_tasks[_current]); + switch_to(next); +} + +/* + * DESCRIPTION + * Create a task. + * - start_routin: task routune entry + * RETURN VALUE + * 0: success + * -1: if error occured + */ +int task_create(void (*start_routin)(void)) +{ + if (_top < MAX_TASKS) { + ctx_tasks[_top].sp = (reg_t) &task_stack[_top][STACK_SIZE - 1]; + ctx_tasks[_top].pc = (reg_t) start_routin; + _top++; + return 0; + } else { + return -1; + } +} + +/* + * DESCRIPTION + * task_yield() causes the calling task to relinquish the CPU and a new + * task gets to run. + */ +void task_yield() +{ + /* trigger a machine-level software interrupt */ + int id = r_mhartid(); + *(uint32_t*)CLINT_MSIP(id) = 1; +} + +/* + * a very rough implementaion, just to consume the cpu + */ +void task_delay(volatile int count) +{ + count *= 50000; + while (count--); +} + diff --git a/code/os/10-swtimer/start.S b/code/os/10-swtimer/start.S new file mode 100644 index 0000000..e49c063 --- /dev/null +++ b/code/os/10-swtimer/start.S @@ -0,0 +1,51 @@ +#include "platform.h" + + .equ STACK_SIZE, 8192 + + .global _start + + .text +_start: + # park harts with id != 0 + csrr t0, mhartid # read current hart id + mv tp, t0 # keep CPU's hartid in its tp for later usage. + bnez t0, park # if we're not on the hart 0 + # we park the hart + + # Set all bytes in the BSS section to zero. + la a0, _bss_start + la a1, _bss_end + bgeu a0, a1, 2f +1: + sw zero, (a0) + addi a0, a0, 4 + bltu a0, a1, 1b +2: + # Setup stacks, the stack grows from bottom to top, so we put the + # stack pointer to the very end of the stack range. + slli t0, t0, 10 # shift left the hart id by 1024 + la sp, stacks + STACK_SIZE # set the initial stack pointer + # to the end of the stack space + add sp, sp, t0 # move the current hart stack pointer + # to its place in the stack space + + # At the end of start_kernel, schedule() will call MRET to switch + # to the first task, so we parepare the mstatus here. + # Set mstatus.MPIE to 1, so MRET will enable the interrupt. + # Set mstatus.MPP to 3, so we still run in Machine mode after MRET. + # Notice: default mstatus is 0 + li t0, 3 << 11 | 1 << 7 + csrr a1, mstatus + or t0, t0, a1 + csrw mstatus, t0 + + j start_kernel # hart 0 jump to c + +park: + wfi + j park + +stacks: + .skip STACK_SIZE * MAXNUM_CPU # allocate space for the harts stacks + + .end # End of file diff --git a/code/os/10-swtimer/timer.c b/code/os/10-swtimer/timer.c new file mode 100644 index 0000000..5ba2e49 --- /dev/null +++ b/code/os/10-swtimer/timer.c @@ -0,0 +1,119 @@ +#include "os.h" + +extern void schedule(void); + +/* interval ~= 1s */ +#define TIMER_INTERVAL CLINT_TIMEBASE_FREQ + +static uint32_t _tick = 0; + +#define MAX_TIMER 10 +static struct timer timer_list[MAX_TIMER]; + +/* load timer interval(in ticks) for next timer interrupt.*/ +void timer_load(int interval) +{ + /* each CPU has a separate source of timer interrupts. */ + int id = r_mhartid(); + + *(uint64_t*)CLINT_MTIMECMP(id) = *(uint64_t*)CLINT_MTIME + interval; +} + +void timer_init() +{ + struct timer *t = &(timer_list[0]); + for (int i = 0; i < MAX_TIMER; i++) { + t->func = NULL; /* use .func to flag if the item is used */ + t->arg = NULL; + t++; + } + + /* + * On reset, mtime is cleared to zero, but the mtimecmp registers + * are not reset. So we have to init the mtimecmp manually. + */ + timer_load(TIMER_INTERVAL); + + /* enable machine-mode timer interrupts. */ + w_mie(r_mie() | MIE_MTIE); +} + +struct timer *timer_create(void (*handler)(void *arg), void *arg, uint32_t timeout) +{ + /* TBD: params should be checked more, but now we just simplify this */ + if (NULL == handler || 0 == timeout) { + return NULL; + } + + /* use lock to protect the shared timer_list between multiple tasks */ + spin_lock(); + + struct timer *t = &(timer_list[0]); + for (int i = 0; i < MAX_TIMER; i++) { + if (NULL == t->func) { + break; + } + t++; + } + if (NULL != t->func) { + spin_unlock(); + return NULL; + } + + t->func = handler; + t->arg = arg; + t->timeout_tick = _tick + timeout; + + spin_unlock(); + + return t; +} + +void timer_delete(struct timer *timer) +{ + spin_lock(); + + struct timer *t = &(timer_list[0]); + for (int i = 0; i < MAX_TIMER; i++) { + if (t == timer) { + t->func = NULL; + t->arg = NULL; + break; + } + t++; + } + + spin_unlock(); +} + +/* this routine should be called in interrupt context (interrupt is disabled) */ +static inline void timer_check() +{ + struct timer *t = &(timer_list[0]); + for (int i = 0; i < MAX_TIMER; i++) { + if (NULL != t->func) { + if (_tick >= t->timeout_tick) { + t->func(t->arg); + + /* once time, just delete it after timeout */ + t->func = NULL; + t->arg = NULL; + + break; + } + } + t++; + } +} + +void timer_handler() +{ + _tick++; + printf("tick: %d\n", _tick); + + timer_check(); + + timer_load(TIMER_INTERVAL); + + schedule(); +} diff --git a/code/os/10-swtimer/trap.c b/code/os/10-swtimer/trap.c new file mode 100644 index 0000000..b3fa9cc --- /dev/null +++ b/code/os/10-swtimer/trap.c @@ -0,0 +1,89 @@ +#include "os.h" + +extern void trap_vector(void); +extern void uart_isr(void); +extern void timer_handler(void); +extern void schedule(void); + +void trap_init() +{ + /* + * set the trap-vector base-address for machine-mode + */ + w_mtvec((reg_t)trap_vector); +} + +void external_interrupt_handler() +{ + int irq = plic_claim(); + + if (irq == UART0_IRQ){ + uart_isr(); + } else if (irq) { + printf("unexpected interrupt irq = %d\n", irq); + } + + if (irq) { + plic_complete(irq); + } +} + +reg_t trap_handler(reg_t epc, reg_t cause) +{ + reg_t return_pc = epc; + reg_t cause_code = cause & 0xfff; + + if (cause & 0x80000000) { + /* Asynchronous trap - interrupt */ + switch (cause_code) { + case 3: + uart_puts("software interruption!\n"); + /* + * acknowledge the software interrupt by clearing + * the MSIP bit in mip. + */ + int id = r_mhartid(); + *(uint32_t*)CLINT_MSIP(id) = 0; + + schedule(); + + break; + case 7: + uart_puts("timer interruption!\n"); + timer_handler(); + break; + case 11: + uart_puts("external interruption!\n"); + external_interrupt_handler(); + break; + default: + uart_puts("unknown async exception!\n"); + break; + } + } else { + /* Synchronous trap - exception */ + printf("Sync exceptions!, code = %d\n", cause_code); + panic("OOPS! What can I do!"); + //return_pc += 4; + } + + return return_pc; +} + +void trap_test() +{ + /* + * Synchronous exception code = 7 + * Store/AMO access fault + */ + *(int *)0x00000000 = 100; + + /* + * Synchronous exception code = 5 + * Load access fault + */ + //int a = *(int *)0x00000000; + + uart_puts("Yeah! I'm return back from trap!\n"); +} + diff --git a/code/os/10-swtimer/types.h b/code/os/10-swtimer/types.h new file mode 100644 index 0000000..5051639 --- /dev/null +++ b/code/os/10-swtimer/types.h @@ -0,0 +1,14 @@ +#ifndef __TYPES_H__ +#define __TYPES_H__ + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long long uint64_t; + +/* + * RISCV32: register is 32bits width + */ +typedef uint32_t reg_t; + +#endif /* __TYPES_H__ */ diff --git a/code/os/10-swtimer/uart.c b/code/os/10-swtimer/uart.c new file mode 100644 index 0000000..2905331 --- /dev/null +++ b/code/os/10-swtimer/uart.c @@ -0,0 +1,149 @@ +#include "os.h" + +/* + * The UART control registers are memory-mapped at address UART0. + * This macro returns the address of one of the registers. + */ +#define UART_REG(reg) ((volatile uint8_t *)(UART0 + reg)) + +/* + * Reference + * [1]: TECHNICAL DATA ON 16550, http://byterunner.com/16550.html + */ + +/* + * UART control registers map. see [1] "PROGRAMMING TABLE" + * note some are reused by multiple functions + * 0 (write mode): THR/DLL + * 1 (write mode): IER/DLM + */ +#define RHR 0 // Receive Holding Register (read mode) +#define THR 0 // Transmit Holding Register (write mode) +#define DLL 0 // LSB of Divisor Latch (write mode) +#define IER 1 // Interrupt Enable Register (write mode) +#define DLM 1 // MSB of Divisor Latch (write mode) +#define FCR 2 // FIFO Control Register (write mode) +#define ISR 2 // Interrupt Status Register (read mode) +#define LCR 3 // Line Control Register +#define MCR 4 // Modem Control Register +#define LSR 5 // Line Status Register +#define MSR 6 // Modem Status Register +#define SPR 7 // ScratchPad Register + +/* + * POWER UP DEFAULTS + * IER = 0: TX/RX holding register interrupts are bith disabled + * ISR = 1: no interrupt penting + * LCR = 0 + * MCR = 0 + * LSR = 60 HEX + * MSR = BITS 0-3 = 0, BITS 4-7 = inputs + * FCR = 0 + * TX = High + * OP1 = High + * OP2 = High + * RTS = High + * DTR = High + * RXRDY = High + * TXRDY = Low + * INT = Low + */ + +/* + * LINE STATUS REGISTER (LSR) + * LSR BIT 0: + * 0 = no data in receive holding register or FIFO. + * 1 = data has been receive and saved in the receive holding register or FIFO. + * ...... + * LSR BIT 5: + * 0 = transmit holding register is full. 16550 will not accept any data for transmission. + * 1 = transmitter hold register (or FIFO) is empty. CPU can load the next character. + * ...... + */ +#define LSR_RX_READY (1 << 0) +#define LSR_TX_IDLE (1 << 5) + +#define uart_read_reg(reg) (*(UART_REG(reg))) +#define uart_write_reg(reg, v) (*(UART_REG(reg)) = (v)) + +void uart_init() +{ + /* disable interrupts. */ + uart_write_reg(IER, 0x00); + + /* + * Setting baud rate. Just a demo here if we care about the divisor, + * but for our purpose [QEMU-virt], this doesn't really do anything. + * + * Notice that the divisor register DLL (divisor latch least) and DLM (divisor + * latch most) have the same base address as the receiver/transmitter and the + * interrupt enable register. To change what the base address points to, we + * open the "divisor latch" by writing 1 into the Divisor Latch Access Bit + * (DLAB), which is bit index 7 of the Line Control Register (LCR). + * + * Regarding the baud rate value, see [1] "BAUD RATE GENERATOR PROGRAMMING TABLE". + * We use 38.4K when 1.8432 MHZ crystal, so the corresponding value is 3. + * And due to the divisor register is two bytes (16 bits), so we need to + * split the value of 3(0x0003) into two bytes, DLL stores the low byte, + * DLM stores the high byte. + */ + uint8_t lcr = uart_read_reg(LCR); + uart_write_reg(LCR, lcr | (1 << 7)); + uart_write_reg(DLL, 0x03); + uart_write_reg(DLM, 0x00); + + /* + * Continue setting the asynchronous data communication format. + * - number of the word length: 8 bits + * - number of stop bits:1 bit when word length is 8 bits + * - no parity + * - no break control + * - disabled baud latch + */ + lcr = 0; + uart_write_reg(LCR, lcr | (3 << 0)); + + /* + * enable receive interrupts. + */ + uint8_t ier = uart_read_reg(IER); + uart_write_reg(IER, ier | (1 << 0)); +} + +int uart_putc(char ch) +{ + while ((uart_read_reg(LSR) & LSR_TX_IDLE) == 0); + return uart_write_reg(THR, ch); +} + +void uart_puts(char *s) +{ + while (*s) { + uart_putc(*s++); + } +} + +int uart_getc(void) +{ + if (uart_read_reg(LSR) & LSR_RX_READY){ + return uart_read_reg(RHR); + } else { + return -1; + } +} + +/* + * handle a uart interrupt, raised because input has arrived, called from trap.c. + */ +void uart_isr(void) +{ + while (1) { + int c = uart_getc(); + if (c == -1) { + break; + } else { + uart_putc((char)c); + uart_putc('\n'); + } + } +} diff --git a/code/os/10-swtimer/user.c b/code/os/10-swtimer/user.c new file mode 100644 index 0000000..73665de --- /dev/null +++ b/code/os/10-swtimer/user.c @@ -0,0 +1,61 @@ +#include "os.h" + +#define DELAY 4000 + +struct userdata { + int counter; + char *str; +}; + +/* Jack must be global */ +struct userdata person = {0, "Jack"}; + +void timer_func(void *arg) +{ + if (NULL == arg) + return; + + struct userdata *param = (struct userdata *)arg; + + param->counter++; + printf("======> TIMEOUT: %s: %d\n", param->str, param->counter); +} + +void user_task0(void) +{ + uart_puts("Task 0: Created!\n"); + + struct timer *t1 = timer_create(timer_func, &person, 3); + if (NULL == t1) { + printf("timer_create() failed!\n"); + } + struct timer *t2 = timer_create(timer_func, &person, 5); + if (NULL == t2) { + printf("timer_create() failed!\n"); + } + struct timer *t3 = timer_create(timer_func, &person, 7); + if (NULL == t3) { + printf("timer_create() failed!\n"); + } + while (1) { + uart_puts("Task 0: Running... \n"); + task_delay(DELAY); + } +} + +void user_task1(void) +{ + uart_puts("Task 1: Created!\n"); + while (1) { + uart_puts("Task 1: Running... \n"); + task_delay(DELAY); + } +} + +/* NOTICE: DON'T LOOP INFINITELY IN main() */ +void os_main(void) +{ + task_create(user_task0); + task_create(user_task1); +} + diff --git a/code/os/11-syscall/Makefile b/code/os/11-syscall/Makefile new file mode 100644 index 0000000..232dbfd --- /dev/null +++ b/code/os/11-syscall/Makefile @@ -0,0 +1,75 @@ +SYSCALL = y + +CROSS_COMPILE = riscv64-unknown-elf- +CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall + +ifeq ($(SYSCALL), y) +CFLAGS += -D CONFIG_SYSCALL +endif + + +QEMU = qemu-system-riscv32 +QFLAGS = -nographic -smp 1 -machine virt -bios none + +GDB = ${CROSS_COMPILE}gdb +CC = ${CROSS_COMPILE}gcc +OBJCOPY = ${CROSS_COMPILE}objcopy +OBJDUMP = ${CROSS_COMPILE}objdump + +SRCS_ASM = \ + start.S \ + mem.S \ + entry.S \ + usys.S + +SRCS_C = \ + kernel.c \ + uart.c \ + printf.c \ + page.c \ + sched.c \ + user.c \ + trap.c \ + plic.c \ + timer.c \ + lock.c \ + syscall.c + +OBJS = $(SRCS_ASM:.S=.o) +OBJS += $(SRCS_C:.c=.o) + +.DEFAULT_GOAL := all +all: os.elf + +# start.o must be the first in dependency! +os.elf: ${OBJS} + ${CC} $(CFLAGS) -T os.ld -o os.elf $^ + ${OBJCOPY} -O binary os.elf os.bin + +%.o : %.c + ${CC} ${CFLAGS} -c -o $@ $< + +%.o : %.S + ${CC} ${CFLAGS} -c -o $@ $< + +run: all + @${QEMU} -M ? | grep virt >/dev/null || exit + @echo "Press Ctrl-A and then X to exit QEMU" + @echo "------------------------------------" + @${QEMU} ${QFLAGS} -kernel os.elf + +.PHONY : debug +debug: all + @echo "Press Ctrl-C and then input 'quit' to exit GDB and QEMU" + @echo "-------------------------------------------------------" + @${QEMU} ${QFLAGS} -kernel os.elf -s -S & + @${GDB} os.elf -q -x ../gdbinit + +.PHONY : code +code: all + @${OBJDUMP} -S os.elf | less + +.PHONY : clean +clean: + rm -rf *.o *.bin *.elf + diff --git a/code/os/11-syscall/entry.S b/code/os/11-syscall/entry.S new file mode 100644 index 0000000..7df6646 --- /dev/null +++ b/code/os/11-syscall/entry.S @@ -0,0 +1,135 @@ +# save all General-Purpose(GP) registers to context +# struct context *base = &ctx_task; +# base->ra = ra; +# ...... +.macro reg_save base + sw ra, 0(\base) + sw sp, 4(\base) + sw gp, 8(\base) + sw tp, 12(\base) + sw t0, 16(\base) + sw t1, 20(\base) + sw t2, 24(\base) + sw s0, 28(\base) + sw s1, 32(\base) + sw a0, 36(\base) + sw a1, 40(\base) + sw a2, 44(\base) + sw a3, 48(\base) + sw a4, 52(\base) + sw a5, 56(\base) + sw a6, 60(\base) + sw a7, 64(\base) + sw s2, 68(\base) + sw s3, 72(\base) + sw s4, 76(\base) + sw s5, 80(\base) + sw s6, 84(\base) + sw s7, 88(\base) + sw s8, 92(\base) + sw s9, 96(\base) + sw s10, 100(\base) + sw s11, 104(\base) + sw t3, 108(\base) + sw t4, 112(\base) + sw t5, 116(\base) + sw t6, 120(\base) +.endm + +# restore all General-Purpose(GP) registers from the context +# struct context *base = &ctx_task; +# ra = base->ra; +# ...... +.macro reg_restore base + lw ra, 0(\base) + lw sp, 4(\base) + lw gp, 8(\base) + lw tp, 12(\base) + lw t0, 16(\base) + lw t1, 20(\base) + lw t2, 24(\base) + lw s0, 28(\base) + lw s1, 32(\base) + lw a0, 36(\base) + lw a1, 40(\base) + lw a2, 44(\base) + lw a3, 48(\base) + lw a4, 52(\base) + lw a5, 56(\base) + lw a6, 60(\base) + lw a7, 64(\base) + lw s2, 68(\base) + lw s3, 72(\base) + lw s4, 76(\base) + lw s5, 80(\base) + lw s6, 84(\base) + lw s7, 88(\base) + lw s8, 92(\base) + lw s9, 96(\base) + lw s10, 100(\base) + lw s11, 104(\base) + lw t3, 108(\base) + lw t4, 112(\base) + lw t5, 116(\base) + lw t6, 120(\base) +.endm + +# Something to note about save/restore: +# - We use mscratch to hold a pointer to context of previous task +# - We use t6 as the 'base' for reg_save/reg_restore, because it is the +# very bottom register (x31) and would not be overwritten during loading. + +.text + +# interrupts and exceptions while in machine mode come here. +.globl trap_vector +# the trap vector base address must always be aligned on a 4-byte boundary +.align 4 +trap_vector: + # save context(registers). + csrrw t6, mscratch, t6 # swap t6 and mscratch + reg_save t6 + csrw mscratch, t6 + + # save mepc to context of current task + csrr a0, mepc + sw a0, 124(t6) + + # call the C trap handler in trap.c + csrr a0, mepc + csrr a1, mcause + csrr a2, mscratch + call trap_handler + + # trap_handler will return the return address via a0. + csrw mepc, a0 + + # restore context(registers). + csrr t6, mscratch + reg_restore t6 + + # return to whatever we were doing before trap. + mret + +# void switch_to(struct context *next); +# a0: pointer to the context of the next task +.globl switch_to +.align 4 +switch_to: + # switch mscratch to point to the context of the next task + csrw mscratch, a0 + # set mepc to the pc of the next task + lw a1, 124(a0) + csrw mepc, a1 + + # Restore all GP registers + # Use t6 to point to the context of the new task + mv t6, a0 + reg_restore t6 + + # Do actual context switching. + # Notice this will enable global interrupt + mret + +.end + diff --git a/code/os/11-syscall/kernel.c b/code/os/11-syscall/kernel.c new file mode 100644 index 0000000..b4e8096 --- /dev/null +++ b/code/os/11-syscall/kernel.c @@ -0,0 +1,38 @@ +#include "os.h" + +/* + * Following functions SHOULD be called ONLY ONE time here, + * so just declared here ONCE and NOT included in file os.h. + */ +extern void uart_init(void); +extern void page_init(void); +extern void sched_init(void); +extern void schedule(void); +extern void os_main(void); +extern void trap_init(void); +extern void plic_init(void); +extern void timer_init(void); + +void start_kernel(void) +{ + uart_init(); + uart_puts("Hello, RVOS!\n"); + + page_init(); + + trap_init(); + + plic_init(); + + timer_init(); + + sched_init(); + + os_main(); + + schedule(); + + uart_puts("Would not go here!\n"); + while (1) {}; // stop here! +} + diff --git a/code/os/11-syscall/lock.c b/code/os/11-syscall/lock.c new file mode 100644 index 0000000..0866fc9 --- /dev/null +++ b/code/os/11-syscall/lock.c @@ -0,0 +1,13 @@ +#include "os.h" + +int spin_lock() +{ + w_mstatus(r_mstatus() & ~MSTATUS_MIE); + return 0; +} + +int spin_unlock() +{ + w_mstatus(r_mstatus() | MSTATUS_MIE); + return 0; +} diff --git a/code/os/11-syscall/mem.S b/code/os/11-syscall/mem.S new file mode 100644 index 0000000..93ba0d6 --- /dev/null +++ b/code/os/11-syscall/mem.S @@ -0,0 +1,30 @@ +.section .rodata +.global HEAP_START +HEAP_START: .word _heap_start + +.global HEAP_SIZE +HEAP_SIZE: .word _heap_size + +.global TEXT_START +TEXT_START: .word _text_start + +.global TEXT_END +TEXT_END: .word _text_end + +.global DATA_START +DATA_START: .word _data_start + +.global DATA_END +DATA_END: .word _data_end + +.global RODATA_START +RODATA_START: .word _rodata_start + +.global RODATA_END +RODATA_END: .word _rodata_end + +.global BSS_START +BSS_START: .word _bss_start + +.global BSS_END +BSS_END: .word _bss_end diff --git a/code/os/11-syscall/os.h b/code/os/11-syscall/os.h new file mode 100644 index 0000000..5ee343c --- /dev/null +++ b/code/os/11-syscall/os.h @@ -0,0 +1,85 @@ +#ifndef __OS_H__ +#define __OS_H__ + +#include "types.h" +#include "riscv.h" +#include "platform.h" + +#include +#include + +/* uart */ +extern int uart_putc(char ch); +extern void uart_puts(char *s); +extern int uart_getc(void); + +/* printf */ +extern int printf(const char* s, ...); +extern void panic(char *s); + +/* memory management */ +extern void *page_alloc(int npages); +extern void page_free(void *p); + +/* task management */ +struct context { + /* ignore x0 */ + reg_t ra; + reg_t sp; + reg_t gp; + reg_t tp; + reg_t t0; + reg_t t1; + reg_t t2; + reg_t s0; + reg_t s1; + reg_t a0; + reg_t a1; + reg_t a2; + reg_t a3; + reg_t a4; + reg_t a5; + reg_t a6; + reg_t a7; + reg_t s2; + reg_t s3; + reg_t s4; + reg_t s5; + reg_t s6; + reg_t s7; + reg_t s8; + reg_t s9; + reg_t s10; + reg_t s11; + reg_t t3; + reg_t t4; + reg_t t5; + reg_t t6; + // upon is trap frame + + // save the pc to run in next schedule cycle + reg_t pc; // offset: 31 *4 = 124 +}; + +extern int task_create(void (*task)(void)); +extern void task_delay(volatile int count); +extern void task_yield(); + +/* plic */ +extern int plic_claim(void); +extern void plic_complete(int irq); + +/* lock */ +extern int spin_lock(void); +extern int spin_unlock(void); + +/* software timer */ +struct timer { + void (*func)(void *arg); + void *arg; + uint32_t timeout_tick; +}; +extern struct timer *timer_create(void (*handler)(void *arg), void *arg, uint32_t timeout); +extern void timer_delete(struct timer *timer); + +#endif /* __OS_H__ */ diff --git a/code/os/11-syscall/os.ld b/code/os/11-syscall/os.ld new file mode 100644 index 0000000..a75cd93 --- /dev/null +++ b/code/os/11-syscall/os.ld @@ -0,0 +1,136 @@ +/* + * rvos.ld + * Linker script for outputting to RVOS + */ + +/* + * https://sourceware.org/binutils/docs/ld/Miscellaneous-Commands.html + * OUTPUT_ARCH command specifies a particular output machine architecture. + * "riscv" is the name of the architecture for both 64-bit and 32-bit + * RISC-V target. We will further refine this by using -march=rv32ima + * and -mabi=ilp32 when calling gcc. + */ +OUTPUT_ARCH( "riscv" ) + +/* + * https://sourceware.org/binutils/docs/ld/Entry-Point.html + * ENTRY command is used to set the "entry point", which is the first instruction + * to execute in a program. + * The argument of ENTRY command is a symbol name, here is "_start" which is + * defined in start.S. + */ +ENTRY( _start ) + +/* + * https://sourceware.org/binutils/docs/ld/MEMORY.html + * The MEMORY command describes the location and size of blocks of memory in + * the target. + * The syntax for MEMORY is: + * MEMORY + * { + * name [(attr)] : ORIGIN = origin, LENGTH = len + * ...... + * } + * Each line defines a memory region. + * Each memory region must have a distinct name within the MEMORY command. Here + * we only define one region named as "ram". + * The "attr" string is an optional list of attributes that specify whether to + * use a particular memory region for an input section which is not explicitly + * mapped in the linker script. Here we assign 'w' (writeable), 'x' (executable), + * and 'a' (allocatable). We use '!' to invert 'r' (read-only) and + * 'i' (initialized). + * The "ORIGIN" is used to set the start address of the memory region. Here we + * place it right at the beginning of 0x8000_0000 because this is where the + * QEMU-virt machine will start executing. + * Finally LENGTH = 128M tells the linker that we have 128 megabyte of RAM. + * The linker will double check this to make sure everything can fit. + */ +MEMORY +{ + ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M +} + +/* + * https://sourceware.org/binutils/docs/ld/SECTIONS.html + * The SECTIONS command tells the linker how to map input sections into output + * sections, and how to place the output sections in memory. + * The format of the SECTIONS command is: + * SECTIONS + * { + * sections-command + * sections-command + * ...... + * } + * + * Each sections-command may of be one of the following: + * (1) an ENTRY command + * (2) a symbol assignment + * (3) an output section description + * (4) an overlay description + * We here only demo (2) & (3). + * + * We use PROVIDE command to define symbols. + * https://sourceware.org/binutils/docs/ld/PROVIDE.html + * The PROVIDE keyword may be used to define a symbol. + * The syntax is PROVIDE(symbol = expression). + * Such symbols as "_text_start", "_text_end" ... will be used in mem.S. + * Notice the period '.' tells the linker to set symbol(e.g. _text_start) to + * the CURRENT location ('.' = current memory location). This current memory + * location moves as we add things. + */ +SECTIONS +{ + /* + * We are going to layout all text sections in .text output section, + * starting with .text. The asterisk("*") in front of the + * parentheses means to match the .text section of ANY object file. + */ + .text : { + PROVIDE(_text_start = .); + *(.text .text.*) + PROVIDE(_text_end = .); + } >ram + + .rodata : { + PROVIDE(_rodata_start = .); + *(.rodata .rodata.*) + PROVIDE(_rodata_end = .); + } >ram + + .data : { + /* + * . = ALIGN(4096) tells the linker to align the current memory + * location to 4096 bytes. This will insert padding bytes until + * current location becomes aligned on 4096-byte boundary. + * This is because our paging system's resolution is 4,096 bytes. + */ + . = ALIGN(4096); + PROVIDE(_data_start = .); + /* + * sdata and data are essentially the same thing. We do not need + * to distinguish sdata from data. + */ + *(.sdata .sdata.*) + *(.data .data.*) + PROVIDE(_data_end = .); + } >ram + + .bss :{ + /* + * https://sourceware.org/binutils/docs/ld/Input-Section-Common.html + * In most cases, common symbols in input files will be placed + * in the ‘.bss’ section in the output file. + */ + PROVIDE(_bss_start = .); + *(.sbss .sbss.*) + *(.bss .bss.*) + *(COMMON) + PROVIDE(_bss_end = .); + } >ram + + PROVIDE(_memory_start = ORIGIN(ram)); + PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram)); + + PROVIDE(_heap_start = _bss_end); + PROVIDE(_heap_size = _memory_end - _heap_start); +} diff --git a/code/os/11-syscall/page.c b/code/os/11-syscall/page.c new file mode 100644 index 0000000..addf097 --- /dev/null +++ b/code/os/11-syscall/page.c @@ -0,0 +1,189 @@ +#include "os.h" + +/* + * Following global vars are defined in mem.S + */ +extern uint32_t TEXT_START; +extern uint32_t TEXT_END; +extern uint32_t DATA_START; +extern uint32_t DATA_END; +extern uint32_t RODATA_START; +extern uint32_t RODATA_END; +extern uint32_t BSS_START; +extern uint32_t BSS_END; +extern uint32_t HEAP_START; +extern uint32_t HEAP_SIZE; + +/* + * _alloc_start points to the actual start address of heap pool + * _alloc_end points to the actual end address of heap pool + * _num_pages holds the actual max number of pages we can allocate. + */ +static uint32_t _alloc_start = 0; +static uint32_t _alloc_end = 0; +static uint32_t _num_pages = 0; + +#define PAGE_SIZE 4096 +#define PAGE_ORDER 12 + +#define PAGE_TAKEN (uint8_t)(1 << 0) +#define PAGE_LAST (uint8_t)(1 << 1) + +/* + * Page Descriptor + * flags: + * - bit 0: flag if this page is taken(allocated) + * - bit 1: flag if this page is the last page of the memory block allocated + */ +struct Page { + uint8_t flags; +}; + +static inline void _clear(struct Page *page) +{ + page->flags = 0; +} + +static inline int _is_free(struct Page *page) +{ + if (page->flags & PAGE_TAKEN) { + return 0; + } else { + return 1; + } +} + +static inline void _set_flag(struct Page *page, uint8_t flags) +{ + page->flags |= flags; +} + +static inline int _is_last(struct Page *page) +{ + if (page->flags & PAGE_LAST) { + return 1; + } else { + return 0; + } +} + +/* + * align the address to the border of page(4K) + */ +static inline uint32_t _align_page(uint32_t address) +{ + uint32_t order = (1 << PAGE_ORDER) - 1; + return (address + order) & (~order); +} + +void page_init() +{ + /* + * We reserved 8 Page (8 x 4096) to hold the Page structures. + * It should be enough to manage at most 128 MB (8 x 4096 x 4096) + */ + _num_pages = (HEAP_SIZE / PAGE_SIZE) - 8; + printf("HEAP_START = %x, HEAP_SIZE = %x, num of pages = %d\n", HEAP_START, HEAP_SIZE, _num_pages); + + struct Page *page = (struct Page *)HEAP_START; + for (int i = 0; i < _num_pages; i++) { + _clear(page); + page++; + } + + _alloc_start = _align_page(HEAP_START + 8 * PAGE_SIZE); + _alloc_end = _alloc_start + (PAGE_SIZE * _num_pages); + + printf("TEXT: 0x%x -> 0x%x\n", TEXT_START, TEXT_END); + printf("RODATA: 0x%x -> 0x%x\n", RODATA_START, RODATA_END); + printf("DATA: 0x%x -> 0x%x\n", DATA_START, DATA_END); + printf("BSS: 0x%x -> 0x%x\n", BSS_START, BSS_END); + printf("HEAP: 0x%x -> 0x%x\n", _alloc_start, _alloc_end); +} + +/* + * Allocate a memory block which is composed of contiguous physical pages + * - npages: the number of PAGE_SIZE pages to allocate + */ +void *page_alloc(int npages) +{ + /* Note we are searching the page descriptor bitmaps. */ + int found = 0; + struct Page *page_i = (struct Page *)HEAP_START; + for (int i = 0; i < (_num_pages - npages); i++) { + if (_is_free(page_i)) { + found = 1; + /* + * meet a free page, continue to check if following + * (npages - 1) pages are also unallocated. + */ + struct Page *page_j = page_i; + for (int j = i; j < (i + npages); j++) { + if (!_is_free(page_j)) { + found = 0; + break; + } + page_j++; + } + /* + * get a memory block which is good enough for us, + * take housekeeping, then return the actual start + * address of the first page of this memory block + */ + if (found) { + struct Page *page_k = page_i; + for (int k = i; k < (i + npages); k++) { + _set_flag(page_k, PAGE_TAKEN); + page_k++; + } + page_k--; + _set_flag(page_k, PAGE_LAST); + return (void *)(_alloc_start + i * PAGE_SIZE); + } + } + page_i++; + } + return NULL; +} + +/* + * Free the memory block + * - p: start address of the memory block + */ +void page_free(void *p) +{ + /* + * Assert (TBD) if p is invalid + */ + if (!p || (uint32_t)p >= _alloc_end) { + return; + } + /* get the first page descriptor of this memory block */ + struct Page *page = (struct Page *)HEAP_START; + page += ((uint32_t)p - _alloc_start)/ PAGE_SIZE; + /* loop and clear all the page descriptors of the memory block */ + while (!_is_free(page)) { + if (_is_last(page)) { + _clear(page); + break; + } else { + _clear(page); + page++;; + } + } +} + +void page_test() +{ + void *p = page_alloc(2); + printf("p = 0x%x\n", p); + //page_free(p); + + void *p2 = page_alloc(7); + printf("p2 = 0x%x\n", p2); + page_free(p2); + + void *p3 = page_alloc(4); + printf("p3 = 0x%x\n", p3); +} + diff --git a/code/os/11-syscall/platform.h b/code/os/11-syscall/platform.h new file mode 100644 index 0000000..3319f4f --- /dev/null +++ b/code/os/11-syscall/platform.h @@ -0,0 +1,97 @@ +#ifndef __PLATFORM_H__ +#define __PLATFORM_H__ + +/* + * QEMU RISC-V Virt machine with 16550a UART and VirtIO MMIO + */ + +/* + * maximum number of CPUs + * see https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h + * #define VIRT_CPUS_MAX 8 + */ +#define MAXNUM_CPU 8 + +/* + * MemoryMap + * see https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c, virt_memmap[] + * 0x00001000 -- boot ROM, provided by qemu + * 0x02000000 -- CLINT + * 0x0C000000 -- PLIC + * 0x10000000 -- UART0 + * 0x10001000 -- virtio disk + * 0x80000000 -- boot ROM jumps here in machine mode, where we load our kernel + */ + +/* This machine puts UART registers here in physical memory. */ +#define UART0 0x10000000L + +/* + * UART0 interrupt source + * see https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h + * enum { + * UART0_IRQ = 10, + * ...... + * }; + */ +#define UART0_IRQ 10 + +/* + * This machine puts platform-level interrupt controller (PLIC) here. + * Here only list PLIC registers in Machine mode. + * see https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h + * #define VIRT_PLIC_HART_CONFIG "MS" + * #define VIRT_PLIC_NUM_SOURCES 127 + * #define VIRT_PLIC_NUM_PRIORITIES 7 + * #define VIRT_PLIC_PRIORITY_BASE 0x04 + * #define VIRT_PLIC_PENDING_BASE 0x1000 + * #define VIRT_PLIC_ENABLE_BASE 0x2000 + * #define VIRT_PLIC_ENABLE_STRIDE 0x80 + * #define VIRT_PLIC_CONTEXT_BASE 0x200000 + * #define VIRT_PLIC_CONTEXT_STRIDE 0x1000 + * #define VIRT_PLIC_SIZE(__num_context) \ + * (VIRT_PLIC_CONTEXT_BASE + (__num_context) * VIRT_PLIC_CONTEXT_STRIDE) + */ +#define PLIC_BASE 0x0c000000L +#define PLIC_PRIORITY(id) (PLIC_BASE + (id) * 4) +#define PLIC_PENDING(id) (PLIC_BASE + 0x1000 + ((id) / 32)) +#define PLIC_MENABLE(hart) (PLIC_BASE + 0x2000 + (hart) * 0x80) +#define PLIC_MTHRESHOLD(hart) (PLIC_BASE + 0x200000 + (hart) * 0x1000) +#define PLIC_MCLAIM(hart) (PLIC_BASE + 0x200004 + (hart) * 0x1000) +#define PLIC_MCOMPLETE(hart) (PLIC_BASE + 0x200004 + (hart) * 0x1000) + + /* + * The Core Local INTerruptor (CLINT) block holds memory-mapped control and + * status registers associated with software and timer interrupts. + * QEMU-virt reuses sifive configuration for CLINT. + * see https://gitee.com/qemu/qemu/blob/master/include/hw/riscv/sifive_clint.h + * enum { + * SIFIVE_SIP_BASE = 0x0, + * SIFIVE_TIMECMP_BASE = 0x4000, + * SIFIVE_TIME_BASE = 0xBFF8 + * }; + * + * enum { + * SIFIVE_CLINT_TIMEBASE_FREQ = 10000000 + * }; + * + * Notice: + * The machine-level MSIP bit of mip register are written by accesses to + * memory-mapped control registers, which are used by remote harts to provide + * machine-mode interprocessor interrupts. + * For QEMU-virt machine, Each msip register is a 32-bit wide WARL register + * where the upper 31 bits are tied to 0. The least significant bit is + * reflected in the MSIP bit of the mip CSR. We can write msip to generate + * machine-mode software interrupts. A pending machine-level software + * interrupt can be cleared by writing 0 to the MSIP bit in mip. + * On reset, each msip register is cleared to zero. + */ +#define CLINT_BASE 0x2000000L +#define CLINT_MSIP(hartid) (CLINT_BASE + 4 * (hartid)) +#define CLINT_MTIMECMP(hartid) (CLINT_BASE + 0x4000 + 8 * (hartid)) +#define CLINT_MTIME (CLINT_BASE + 0xBFF8) // cycles since boot. + +/* 10000000 ticks per-second */ +#define CLINT_TIMEBASE_FREQ 10000000 + +#endif /* __PLATFORM_H__ */ diff --git a/code/os/11-syscall/plic.c b/code/os/11-syscall/plic.c new file mode 100644 index 0000000..c28a8d1 --- /dev/null +++ b/code/os/11-syscall/plic.c @@ -0,0 +1,78 @@ +#include "os.h" + +void plic_init(void) +{ + int hart = r_tp(); + + /* + * Set priority for UART0. + * + * Each PLIC interrupt source can be assigned a priority by writing + * to its 32-bit memory-mapped priority register. + * The QEMU-virt (the same as FU540-C000) supports 7 levels of priority. + * A priority value of 0 is reserved to mean "never interrupt" and + * effectively disables the interrupt. + * Priority 1 is the lowest active priority, and priority 7 is the highest. + * Ties between global interrupts of the same priority are broken by + * the Interrupt ID; interrupts with the lowest ID have the highest + * effective priority. + */ + *(uint32_t*)PLIC_PRIORITY(UART0_IRQ) = 1; + + /* + * Enable UART0 + * + * Each global interrupt can be enabled by setting the corresponding + * bit in the enables registers. + */ + *(uint32_t*)PLIC_MENABLE(hart)= (1 << UART0_IRQ); + + /* + * Set priority threshold for UART0. + * + * PLIC will mask all interrupts of a priority less than or equal to threshold. + * Maximum threshold is 7. + * For example, a threshold value of zero permits all interrupts with + * non-zero priority, whereas a value of 7 masks all interrupts. + * Notice, the threshold is global for PLIC, not for each interrupt source. + */ + *(uint32_t*)PLIC_MTHRESHOLD(hart) = 0; + + /* enable machine-mode external interrupts. */ + w_mie(r_mie() | MIE_MEIE); +} + +/* + * DESCRIPTION: + * Query the PLIC what interrupt we should serve. + * Perform an interrupt claim by reading the claim register, which + * returns the ID of the highest-priority pending interrupt or zero if there + * is no pending interrupt. + * A successful claim also atomically clears the corresponding pending bit + * on the interrupt source. + * RETURN VALUE: + * the ID of the highest-priority pending interrupt or zero if there + * is no pending interrupt. + */ +int plic_claim(void) +{ + int hart = r_tp(); + int irq = *(uint32_t*)PLIC_MCLAIM(hart); + return irq; +} + +/* + * DESCRIPTION: + * Writing the interrupt ID it received from the claim (irq) to the + * complete register would signal the PLIC we've served this IRQ. + * The PLIC does not check whether the completion ID is the same as the + * last claim ID for that target. If the completion ID does not match an + * interrupt source that is currently enabled for the target, the completion + * is silently ignored. + * RETURN VALUE: none + */ +void plic_complete(int irq) +{ + int hart = r_tp(); + *(uint32_t*)PLIC_MCOMPLETE(hart) = irq; +} diff --git a/code/os/11-syscall/printf.c b/code/os/11-syscall/printf.c new file mode 100644 index 0000000..93ace5b --- /dev/null +++ b/code/os/11-syscall/printf.c @@ -0,0 +1,138 @@ +#include "os.h" + +/* + * ref: https://github.com/cccriscv/mini-riscv-os/blob/master/05-Preemptive/lib.c + */ + +static int _vsnprintf(char * out, size_t n, const char* s, va_list vl) +{ + int format = 0; + int longarg = 0; + size_t pos = 0; + for (; *s; s++) { + if (format) { + switch(*s) { + case 'l': { + longarg = 1; + break; + } + case 'p': { + longarg = 1; + if (out && pos < n) { + out[pos] = '0'; + } + pos++; + if (out && pos < n) { + out[pos] = 'x'; + } + pos++; + } + case 'x': { + long num = longarg ? va_arg(vl, long) : va_arg(vl, int); + int hexdigits = 2*(longarg ? sizeof(long) : sizeof(int))-1; + for(int i = hexdigits; i >= 0; i--) { + int d = (num >> (4*i)) & 0xF; + if (out && pos < n) { + out[pos] = (d < 10 ? '0'+d : 'a'+d-10); + } + pos++; + } + longarg = 0; + format = 0; + break; + } + case 'd': { + long num = longarg ? va_arg(vl, long) : va_arg(vl, int); + if (num < 0) { + num = -num; + if (out && pos < n) { + out[pos] = '-'; + } + pos++; + } + long digits = 1; + for (long nn = num; nn /= 10; digits++); + for (int i = digits-1; i >= 0; i--) { + if (out && pos + i < n) { + out[pos + i] = '0' + (num % 10); + } + num /= 10; + } + pos += digits; + longarg = 0; + format = 0; + break; + } + case 's': { + const char* s2 = va_arg(vl, const char*); + while (*s2) { + if (out && pos < n) { + out[pos] = *s2; + } + pos++; + s2++; + } + longarg = 0; + format = 0; + break; + } + case 'c': { + if (out && pos < n) { + out[pos] = (char)va_arg(vl,int); + } + pos++; + longarg = 0; + format = 0; + break; + } + default: + break; + } + } else if (*s == '%') { + format = 1; + } else { + if (out && pos < n) { + out[pos] = *s; + } + pos++; + } + } + if (out && pos < n) { + out[pos] = 0; + } else if (out && n) { + out[n-1] = 0; + } + return pos; +} + +static char out_buf[1000]; // buffer for _vprintf() + +static int _vprintf(const char* s, va_list vl) +{ + int res = _vsnprintf(NULL, -1, s, vl); + if (res+1 >= sizeof(out_buf)) { + uart_puts("error: output string size overflow\n"); + while(1) {} + } + _vsnprintf(out_buf, res + 1, s, vl); + uart_puts(out_buf); + return res; +} + +int printf(const char* s, ...) +{ + int res = 0; + va_list vl; + va_start(vl, s); + res = _vprintf(s, vl); + va_end(vl); + return res; +} + +void panic(char *s) +{ + printf("panic: "); + printf(s); + printf("\n"); + while(1){}; +} diff --git a/code/os/11-syscall/riscv.h b/code/os/11-syscall/riscv.h new file mode 100644 index 0000000..e5bd491 --- /dev/null +++ b/code/os/11-syscall/riscv.h @@ -0,0 +1,102 @@ +#ifndef __RISCV_H__ +#define __RISCV_H__ + +#include "types.h" + +/* + * ref: https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/riscv.h + */ + +static inline reg_t r_tp() +{ + reg_t x; + asm volatile("mv %0, tp" : "=r" (x) ); + return x; +} + +/* which hart (core) is this? */ +static inline reg_t r_mhartid() +{ + reg_t x; + asm volatile("csrr %0, mhartid" : "=r" (x) ); + return x; +} + +/* Machine Status Register, mstatus */ +#define MSTATUS_MPP (3 << 11) +#define MSTATUS_SPP (1 << 8) + +#define MSTATUS_MPIE (1 << 7) +#define MSTATUS_SPIE (1 << 5) +#define MSTATUS_UPIE (1 << 4) + +#define MSTATUS_MIE (1 << 3) +#define MSTATUS_SIE (1 << 1) +#define MSTATUS_UIE (1 << 0) + +static inline reg_t r_mstatus() +{ + reg_t x; + asm volatile("csrr %0, mstatus" : "=r" (x) ); + return x; +} + +static inline void w_mstatus(reg_t x) +{ + asm volatile("csrw mstatus, %0" : : "r" (x)); +} + +/* + * machine exception program counter, holds the + * instruction address to which a return from + * exception will go. + */ +static inline void w_mepc(reg_t x) +{ + asm volatile("csrw mepc, %0" : : "r" (x)); +} + +static inline reg_t r_mepc() +{ + reg_t x; + asm volatile("csrr %0, mepc" : "=r" (x)); + return x; +} + +/* Machine Scratch register, for early trap handler */ +static inline void w_mscratch(reg_t x) +{ + asm volatile("csrw mscratch, %0" : : "r" (x)); +} + +/* Machine-mode interrupt vector */ +static inline void w_mtvec(reg_t x) +{ + asm volatile("csrw mtvec, %0" : : "r" (x)); +} + +/* Machine-mode Interrupt Enable */ +#define MIE_MEIE (1 << 11) // external +#define MIE_MTIE (1 << 7) // timer +#define MIE_MSIE (1 << 3) // software + +static inline reg_t r_mie() +{ + reg_t x; + asm volatile("csrr %0, mie" : "=r" (x) ); + return x; +} + +static inline void w_mie(reg_t x) +{ + asm volatile("csrw mie, %0" : : "r" (x)); +} + +static inline reg_t r_mcause() +{ + reg_t x; + asm volatile("csrr %0, mcause" : "=r" (x) ); + return x; +} + +#endif /* __RISCV_H__ */ diff --git a/code/os/11-syscall/sched.c b/code/os/11-syscall/sched.c new file mode 100644 index 0000000..c612a3a --- /dev/null +++ b/code/os/11-syscall/sched.c @@ -0,0 +1,81 @@ +#include "os.h" + +/* defined in entry.S */ +extern void switch_to(struct context *next); + +#define MAX_TASKS 10 +#define STACK_SIZE 1024 +uint8_t task_stack[MAX_TASKS][STACK_SIZE]; +struct context ctx_tasks[MAX_TASKS]; + +/* + * _top is used to mark the max available position of ctx_tasks + * _current is used to point to the context of current task + */ +static int _top = 0; +static int _current = -1; + +void sched_init() +{ + w_mscratch(0); + + /* enable machine-mode software interrupts. */ + w_mie(r_mie() | MIE_MSIE); +} + +/* + * implment a simple cycle FIFO schedular + */ +void schedule() +{ + if (_top <= 0) { + panic("Num of task should be greater than zero!"); + return; + } + + _current = (_current + 1) % _top; + struct context *next = &(ctx_tasks[_current]); + switch_to(next); +} + +/* + * DESCRIPTION + * Create a task. + * - start_routin: task routune entry + * RETURN VALUE + * 0: success + * -1: if error occured + */ +int task_create(void (*start_routin)(void)) +{ + if (_top < MAX_TASKS) { + ctx_tasks[_top].sp = (reg_t) &task_stack[_top][STACK_SIZE - 1]; + ctx_tasks[_top].pc = (reg_t) start_routin; + _top++; + return 0; + } else { + return -1; + } +} + +/* + * DESCRIPTION + * task_yield() causes the calling task to relinquish the CPU and a new + * task gets to run. + */ +void task_yield() +{ + /* trigger a machine-level software interrupt */ + int id = r_mhartid(); + *(uint32_t*)CLINT_MSIP(id) = 1; +} + +/* + * a very rough implementaion, just to consume the cpu + */ +void task_delay(volatile int count) +{ + count *= 50000; + while (count--); +} + diff --git a/code/os/11-syscall/start.S b/code/os/11-syscall/start.S new file mode 100644 index 0000000..6711538 --- /dev/null +++ b/code/os/11-syscall/start.S @@ -0,0 +1,53 @@ +#include "platform.h" + + .equ STACK_SIZE, 8192 + + .global _start + + .text +_start: + # park harts with id != 0 + csrr t0, mhartid # read current hart id + mv tp, t0 # keep CPU's hartid in its tp for later usage. + bnez t0, park # if we're not on the hart 0 + # we park the hart + + # Set all bytes in the BSS section to zero. + la a0, _bss_start + la a1, _bss_end + bgeu a0, a1, 2f +1: + sw zero, (a0) + addi a0, a0, 4 + bltu a0, a1, 1b +2: + # Setup stacks, the stack grows from bottom to top, so we put the + # stack pointer to the very end of the stack range. + slli t0, t0, 10 # shift left the hart id by 1024 + la sp, stacks + STACK_SIZE # set the initial stack pointer + # to the end of the stack space + add sp, sp, t0 # move the current hart stack pointer + # to its place in the stack space + +#ifndef CONFIG_SYSCALL + # At the end of start_kernel, schedule() will call MRET to switch + # to the first task, so we parepare the mstatus here. + # Set mstatus.MPIE to 1, so MRET will enable the interrupt. + # Set mstatus.MPP to 3, so we still run in Machine mode after MRET. + # Notice: default mstatus is 0 + li t0, 3 << 11 | 1 << 7 + csrr a1, mstatus + or t0, t0, a1 + csrw mstatus, t0 +#endif + + j start_kernel # hart 0 jump to c + +park: + wfi + j park + +stacks: + .skip STACK_SIZE * MAXNUM_CPU # allocate space for the harts stacks + + .end # End of file diff --git a/code/os/11-syscall/syscall.c b/code/os/11-syscall/syscall.c new file mode 100644 index 0000000..641ffed --- /dev/null +++ b/code/os/11-syscall/syscall.c @@ -0,0 +1,29 @@ +#include "os.h" +#include "syscall.h" + +int sys_gethid(unsigned int *ptr_hid) +{ + printf("--> sys_gethid, arg0 = 0x%x\n", ptr_hid); + if (ptr_hid == NULL) { + return -1; + } else { + *ptr_hid = r_mhartid(); + return 0; + } +} + +void do_syscall(struct context *cxt) +{ + uint32_t syscall_num = cxt->a7; + + switch (syscall_num) { + case SYS_gethid: + cxt->a0 = sys_gethid((unsigned int *)(cxt->a0)); + break; + default: + printf("Unknown syscall no: %d\n", syscall_num); + cxt->a0 = -1; + } + + return; +} \ No newline at end of file diff --git a/code/os/11-syscall/syscall.h b/code/os/11-syscall/syscall.h new file mode 100644 index 0000000..8861778 --- /dev/null +++ b/code/os/11-syscall/syscall.h @@ -0,0 +1,2 @@ +// System call numbers +#define SYS_gethid 1 diff --git a/code/os/11-syscall/timer.c b/code/os/11-syscall/timer.c new file mode 100644 index 0000000..5ba2e49 --- /dev/null +++ b/code/os/11-syscall/timer.c @@ -0,0 +1,119 @@ +#include "os.h" + +extern void schedule(void); + +/* interval ~= 1s */ +#define TIMER_INTERVAL CLINT_TIMEBASE_FREQ + +static uint32_t _tick = 0; + +#define MAX_TIMER 10 +static struct timer timer_list[MAX_TIMER]; + +/* load timer interval(in ticks) for next timer interrupt.*/ +void timer_load(int interval) +{ + /* each CPU has a separate source of timer interrupts. */ + int id = r_mhartid(); + + *(uint64_t*)CLINT_MTIMECMP(id) = *(uint64_t*)CLINT_MTIME + interval; +} + +void timer_init() +{ + struct timer *t = &(timer_list[0]); + for (int i = 0; i < MAX_TIMER; i++) { + t->func = NULL; /* use .func to flag if the item is used */ + t->arg = NULL; + t++; + } + + /* + * On reset, mtime is cleared to zero, but the mtimecmp registers + * are not reset. So we have to init the mtimecmp manually. + */ + timer_load(TIMER_INTERVAL); + + /* enable machine-mode timer interrupts. */ + w_mie(r_mie() | MIE_MTIE); +} + +struct timer *timer_create(void (*handler)(void *arg), void *arg, uint32_t timeout) +{ + /* TBD: params should be checked more, but now we just simplify this */ + if (NULL == handler || 0 == timeout) { + return NULL; + } + + /* use lock to protect the shared timer_list between multiple tasks */ + spin_lock(); + + struct timer *t = &(timer_list[0]); + for (int i = 0; i < MAX_TIMER; i++) { + if (NULL == t->func) { + break; + } + t++; + } + if (NULL != t->func) { + spin_unlock(); + return NULL; + } + + t->func = handler; + t->arg = arg; + t->timeout_tick = _tick + timeout; + + spin_unlock(); + + return t; +} + +void timer_delete(struct timer *timer) +{ + spin_lock(); + + struct timer *t = &(timer_list[0]); + for (int i = 0; i < MAX_TIMER; i++) { + if (t == timer) { + t->func = NULL; + t->arg = NULL; + break; + } + t++; + } + + spin_unlock(); +} + +/* this routine should be called in interrupt context (interrupt is disabled) */ +static inline void timer_check() +{ + struct timer *t = &(timer_list[0]); + for (int i = 0; i < MAX_TIMER; i++) { + if (NULL != t->func) { + if (_tick >= t->timeout_tick) { + t->func(t->arg); + + /* once time, just delete it after timeout */ + t->func = NULL; + t->arg = NULL; + + break; + } + } + t++; + } +} + +void timer_handler() +{ + _tick++; + printf("tick: %d\n", _tick); + + timer_check(); + + timer_load(TIMER_INTERVAL); + + schedule(); +} diff --git a/code/os/11-syscall/trap.c b/code/os/11-syscall/trap.c new file mode 100644 index 0000000..d4777e5 --- /dev/null +++ b/code/os/11-syscall/trap.c @@ -0,0 +1,98 @@ +#include "os.h" + +extern void trap_vector(void); +extern void uart_isr(void); +extern void timer_handler(void); +extern void schedule(void); +extern void do_syscall(struct context *cxt); + +void trap_init() +{ + /* + * set the trap-vector base-address for machine-mode + */ + w_mtvec((reg_t)trap_vector); +} + +void external_interrupt_handler() +{ + int irq = plic_claim(); + + if (irq == UART0_IRQ){ + uart_isr(); + } else if (irq) { + printf("unexpected interrupt irq = %d\n", irq); + } + + if (irq) { + plic_complete(irq); + } +} + +reg_t trap_handler(reg_t epc, reg_t cause, struct context *cxt) +{ + reg_t return_pc = epc; + reg_t cause_code = cause & 0xfff; + + if (cause & 0x80000000) { + /* Asynchronous trap - interrupt */ + switch (cause_code) { + case 3: + uart_puts("software interruption!\n"); + /* + * acknowledge the software interrupt by clearing + * the MSIP bit in mip. + */ + int id = r_mhartid(); + *(uint32_t*)CLINT_MSIP(id) = 0; + + schedule(); + + break; + case 7: + uart_puts("timer interruption!\n"); + timer_handler(); + break; + case 11: + uart_puts("external interruption!\n"); + external_interrupt_handler(); + break; + default: + uart_puts("unknown async exception!\n"); + break; + } + } else { + /* Synchronous trap - exception */ + printf("Sync exceptions!, code = %d\n", cause_code); + switch (cause_code) { + case 8: + uart_puts("System call from U-mode!\n"); + do_syscall(cxt); + return_pc += 4; + break; + default: + panic("OOPS! What can I do!"); + //return_pc += 4; + } + } + + return return_pc; +} + +void trap_test() +{ + /* + * Synchronous exception code = 7 + * Store/AMO access fault + */ + *(int *)0x00000000 = 100; + + /* + * Synchronous exception code = 5 + * Load access fault + */ + //int a = *(int *)0x00000000; + + uart_puts("Yeah! I'm return back from trap!\n"); +} + diff --git a/code/os/11-syscall/types.h b/code/os/11-syscall/types.h new file mode 100644 index 0000000..5051639 --- /dev/null +++ b/code/os/11-syscall/types.h @@ -0,0 +1,14 @@ +#ifndef __TYPES_H__ +#define __TYPES_H__ + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long long uint64_t; + +/* + * RISCV32: register is 32bits width + */ +typedef uint32_t reg_t; + +#endif /* __TYPES_H__ */ diff --git a/code/os/11-syscall/uart.c b/code/os/11-syscall/uart.c new file mode 100644 index 0000000..2905331 --- /dev/null +++ b/code/os/11-syscall/uart.c @@ -0,0 +1,149 @@ +#include "os.h" + +/* + * The UART control registers are memory-mapped at address UART0. + * This macro returns the address of one of the registers. + */ +#define UART_REG(reg) ((volatile uint8_t *)(UART0 + reg)) + +/* + * Reference + * [1]: TECHNICAL DATA ON 16550, http://byterunner.com/16550.html + */ + +/* + * UART control registers map. see [1] "PROGRAMMING TABLE" + * note some are reused by multiple functions + * 0 (write mode): THR/DLL + * 1 (write mode): IER/DLM + */ +#define RHR 0 // Receive Holding Register (read mode) +#define THR 0 // Transmit Holding Register (write mode) +#define DLL 0 // LSB of Divisor Latch (write mode) +#define IER 1 // Interrupt Enable Register (write mode) +#define DLM 1 // MSB of Divisor Latch (write mode) +#define FCR 2 // FIFO Control Register (write mode) +#define ISR 2 // Interrupt Status Register (read mode) +#define LCR 3 // Line Control Register +#define MCR 4 // Modem Control Register +#define LSR 5 // Line Status Register +#define MSR 6 // Modem Status Register +#define SPR 7 // ScratchPad Register + +/* + * POWER UP DEFAULTS + * IER = 0: TX/RX holding register interrupts are bith disabled + * ISR = 1: no interrupt penting + * LCR = 0 + * MCR = 0 + * LSR = 60 HEX + * MSR = BITS 0-3 = 0, BITS 4-7 = inputs + * FCR = 0 + * TX = High + * OP1 = High + * OP2 = High + * RTS = High + * DTR = High + * RXRDY = High + * TXRDY = Low + * INT = Low + */ + +/* + * LINE STATUS REGISTER (LSR) + * LSR BIT 0: + * 0 = no data in receive holding register or FIFO. + * 1 = data has been receive and saved in the receive holding register or FIFO. + * ...... + * LSR BIT 5: + * 0 = transmit holding register is full. 16550 will not accept any data for transmission. + * 1 = transmitter hold register (or FIFO) is empty. CPU can load the next character. + * ...... + */ +#define LSR_RX_READY (1 << 0) +#define LSR_TX_IDLE (1 << 5) + +#define uart_read_reg(reg) (*(UART_REG(reg))) +#define uart_write_reg(reg, v) (*(UART_REG(reg)) = (v)) + +void uart_init() +{ + /* disable interrupts. */ + uart_write_reg(IER, 0x00); + + /* + * Setting baud rate. Just a demo here if we care about the divisor, + * but for our purpose [QEMU-virt], this doesn't really do anything. + * + * Notice that the divisor register DLL (divisor latch least) and DLM (divisor + * latch most) have the same base address as the receiver/transmitter and the + * interrupt enable register. To change what the base address points to, we + * open the "divisor latch" by writing 1 into the Divisor Latch Access Bit + * (DLAB), which is bit index 7 of the Line Control Register (LCR). + * + * Regarding the baud rate value, see [1] "BAUD RATE GENERATOR PROGRAMMING TABLE". + * We use 38.4K when 1.8432 MHZ crystal, so the corresponding value is 3. + * And due to the divisor register is two bytes (16 bits), so we need to + * split the value of 3(0x0003) into two bytes, DLL stores the low byte, + * DLM stores the high byte. + */ + uint8_t lcr = uart_read_reg(LCR); + uart_write_reg(LCR, lcr | (1 << 7)); + uart_write_reg(DLL, 0x03); + uart_write_reg(DLM, 0x00); + + /* + * Continue setting the asynchronous data communication format. + * - number of the word length: 8 bits + * - number of stop bits:1 bit when word length is 8 bits + * - no parity + * - no break control + * - disabled baud latch + */ + lcr = 0; + uart_write_reg(LCR, lcr | (3 << 0)); + + /* + * enable receive interrupts. + */ + uint8_t ier = uart_read_reg(IER); + uart_write_reg(IER, ier | (1 << 0)); +} + +int uart_putc(char ch) +{ + while ((uart_read_reg(LSR) & LSR_TX_IDLE) == 0); + return uart_write_reg(THR, ch); +} + +void uart_puts(char *s) +{ + while (*s) { + uart_putc(*s++); + } +} + +int uart_getc(void) +{ + if (uart_read_reg(LSR) & LSR_RX_READY){ + return uart_read_reg(RHR); + } else { + return -1; + } +} + +/* + * handle a uart interrupt, raised because input has arrived, called from trap.c. + */ +void uart_isr(void) +{ + while (1) { + int c = uart_getc(); + if (c == -1) { + break; + } else { + uart_putc((char)c); + uart_putc('\n'); + } + } +} diff --git a/code/os/11-syscall/user.c b/code/os/11-syscall/user.c new file mode 100644 index 0000000..88c4cac --- /dev/null +++ b/code/os/11-syscall/user.c @@ -0,0 +1,52 @@ +#include "os.h" + +#include "user_api.h" + +#define DELAY 4000 + +void user_task0(void) +{ + uart_puts("Task 0: Created!\n"); + + unsigned int hid = -1; + + /* + * if syscall is supported, this will trigger exception, + * code = 2 (Illegal instruction) + */ + //hid = r_mhartid(); + //printf("hart id is %d\n", hid); + +#ifdef CONFIG_SYSCALL + int ret = -1; + ret = gethid(&hid); + //ret = gethid(NULL); + if (!ret) { + printf("system call returned!, hart id is %d\n", hid); + } else { + printf("gethid() failed, return: %d\n", ret); + } +#endif + + while (1){ + uart_puts("Task 0: Running... \n"); + task_delay(DELAY); + } +} + +void user_task1(void) +{ + uart_puts("Task 1: Created!\n"); + while (1) { + uart_puts("Task 1: Running... \n"); + task_delay(DELAY); + } +} + +/* NOTICE: DON'T LOOP INFINITELY IN main() */ +void os_main(void) +{ + task_create(user_task0); + task_create(user_task1); +} + diff --git a/code/os/11-syscall/user_api.h b/code/os/11-syscall/user_api.h new file mode 100644 index 0000000..116081d --- /dev/null +++ b/code/os/11-syscall/user_api.h @@ -0,0 +1,7 @@ +#ifndef __USER_API_H__ +#define __USER_API_H__ + +/* user mode syscall APIs */ +extern int gethid(unsigned int *hid); + +#endif /* __USER_API_H__ */ diff --git a/code/os/11-syscall/usys.S b/code/os/11-syscall/usys.S new file mode 100644 index 0000000..1bd15dc --- /dev/null +++ b/code/os/11-syscall/usys.S @@ -0,0 +1,7 @@ +#include "syscall.h" + +.global gethid +gethid: + li a7, SYS_gethid + ecall + ret diff --git a/code/os/Makefile b/code/os/Makefile new file mode 100644 index 0000000..401b6df --- /dev/null +++ b/code/os/Makefile @@ -0,0 +1,23 @@ +SECTIONS = \ + 00-bootstrap \ + 01-helloRVOS \ + 02-memanagement \ + 03-contextswitch \ + 04-multitask \ + 05-traps \ + 06-interrupts \ + 07-hwtimer \ + 08-preemptive \ + 09-lock \ + 10-swtimer \ + 11-syscall \ + +.DEFAULT_GOAL := all +all : + @echo "begin compile ALL exercises for assembly samples ......................." + for dir in $(SECTIONS); do $(MAKE) -C $$dir || exit "$$?"; done + @echo "compile ALL exercises finished successfully! ......" + +.PHONY : clean +clean: + for dir in $(SECTIONS); do $(MAKE) -C $$dir clean || exit "$$?"; done diff --git a/code/os/gdbinit b/code/os/gdbinit new file mode 100644 index 0000000..14343a5 --- /dev/null +++ b/code/os/gdbinit @@ -0,0 +1,4 @@ +set disassemble-next-line on +b _start +target remote : 1234 +c diff --git a/refs/AN-0491.pdf b/refs/AN-0491.pdf new file mode 100644 index 0000000..ca6f569 Binary files /dev/null and b/refs/AN-0491.pdf differ diff --git a/refs/FU540-C000-v1.0.pdf b/refs/FU540-C000-v1.0.pdf new file mode 100644 index 0000000..1a8cc69 Binary files /dev/null and b/refs/FU540-C000-v1.0.pdf differ diff --git a/refs/RISC-V-Reader-Chinese-v2p1.pdf b/refs/RISC-V-Reader-Chinese-v2p1.pdf new file mode 100644 index 0000000..bc732bc Binary files /dev/null and b/refs/RISC-V-Reader-Chinese-v2p1.pdf differ diff --git a/refs/riscv-privileged-20190608-1.pdf b/refs/riscv-privileged-20190608-1.pdf new file mode 100644 index 0000000..ef15e68 Binary files /dev/null and b/refs/riscv-privileged-20190608-1.pdf differ diff --git a/refs/riscv-spec-20191213.pdf b/refs/riscv-spec-20191213.pdf new file mode 100644 index 0000000..533c1cb Binary files /dev/null and b/refs/riscv-spec-20191213.pdf differ diff --git a/refs/td16550.pdf b/refs/td16550.pdf new file mode 100644 index 0000000..ed76335 Binary files /dev/null and b/refs/td16550.pdf differ diff --git a/slides/ch00-introduction.pdf b/slides/ch00-introduction.pdf new file mode 100644 index 0000000..df98701 Binary files /dev/null and b/slides/ch00-introduction.pdf differ diff --git a/slides/ch01-compter-system-overview.pdf b/slides/ch01-compter-system-overview.pdf new file mode 100644 index 0000000..df14034 Binary files /dev/null and b/slides/ch01-compter-system-overview.pdf differ diff --git a/slides/ch02-riscv-isa-introduction.pdf b/slides/ch02-riscv-isa-introduction.pdf new file mode 100644 index 0000000..c3c617b Binary files /dev/null and b/slides/ch02-riscv-isa-introduction.pdf differ diff --git a/slides/ch03-compile-link.pdf b/slides/ch03-compile-link.pdf new file mode 100644 index 0000000..6b7d979 Binary files /dev/null and b/slides/ch03-compile-link.pdf differ diff --git a/slides/ch04-embedded-development.pdf b/slides/ch04-embedded-development.pdf new file mode 100644 index 0000000..0e81af4 Binary files /dev/null and b/slides/ch04-embedded-development.pdf differ diff --git a/slides/ch05-assemble-programming.pdf b/slides/ch05-assemble-programming.pdf new file mode 100644 index 0000000..ce89e03 Binary files /dev/null and b/slides/ch05-assemble-programming.pdf differ diff --git a/slides/ch06-rvos.pdf b/slides/ch06-rvos.pdf new file mode 100644 index 0000000..a8fffeb Binary files /dev/null and b/slides/ch06-rvos.pdf differ diff --git a/slides/ch07-hello-rvos.pdf b/slides/ch07-hello-rvos.pdf new file mode 100644 index 0000000..e2fb619 Binary files /dev/null and b/slides/ch07-hello-rvos.pdf differ diff --git a/slides/ch08-memory-management.pdf b/slides/ch08-memory-management.pdf new file mode 100644 index 0000000..c015ee4 Binary files /dev/null and b/slides/ch08-memory-management.pdf differ diff --git a/slides/ch09-context-switch.pdf b/slides/ch09-context-switch.pdf new file mode 100644 index 0000000..99ab9b5 Binary files /dev/null and b/slides/ch09-context-switch.pdf differ diff --git a/slides/ch10-trap-exception.pdf b/slides/ch10-trap-exception.pdf new file mode 100644 index 0000000..ab478b8 Binary files /dev/null and b/slides/ch10-trap-exception.pdf differ diff --git a/slides/ch11-external-interrupt.pdf b/slides/ch11-external-interrupt.pdf new file mode 100644 index 0000000..4f43519 Binary files /dev/null and b/slides/ch11-external-interrupt.pdf differ diff --git a/slides/ch12-hardware-timer.pdf b/slides/ch12-hardware-timer.pdf new file mode 100644 index 0000000..dfeebec Binary files /dev/null and b/slides/ch12-hardware-timer.pdf differ diff --git a/slides/ch13-preemptive.pdf b/slides/ch13-preemptive.pdf new file mode 100644 index 0000000..58531c2 Binary files /dev/null and b/slides/ch13-preemptive.pdf differ diff --git a/slides/ch14-lock.pdf b/slides/ch14-lock.pdf new file mode 100644 index 0000000..da21e00 Binary files /dev/null and b/slides/ch14-lock.pdf differ diff --git a/slides/ch15-software-timer.pdf b/slides/ch15-software-timer.pdf new file mode 100644 index 0000000..99080d9 Binary files /dev/null and b/slides/ch15-software-timer.pdf differ diff --git a/slides/ch16-syscall.pdf b/slides/ch16-syscall.pdf new file mode 100644 index 0000000..42f910c Binary files /dev/null and b/slides/ch16-syscall.pdf differ diff --git a/slides/exercises.pdf b/slides/exercises.pdf new file mode 100644 index 0000000..a9fedb4 Binary files /dev/null and b/slides/exercises.pdf differ