initial versioin

This commit is contained in:
Wang Chen
2021-03-31 14:32:02 +08:00
committed by unicornx
parent 8ad78e0e0a
commit ad15280f3a
277 changed files with 15119 additions and 1 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
*.o
*.bin
*.elf

25
LICENSE Normal file
View File

@@ -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.

19
Makefile Normal file
View File

@@ -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

View File

@@ -1 +1,77 @@
# riscv-operating-system-mooc **Step by step, learn to develop an operating system on RISC-V**
<!-- TOC -->
- [Introduction](#introduction)
- [Operating environment](#operating-environment)
- [Construction and usage](#construction-and-usage)
- [References](#references)
<!-- /TOC -->
# 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:<https://share.weiyun.com/nyTqAGKh>
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
- makeCompile and build
- make runStart qemu and run
- make debugStart debugging
- make codeDisassemble to view binary code
- make cleancleanup
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<https://osblog.stephenmarz.com/index.html>
- mini-riscv-os: <https://github.com/cccriscv/mini-riscv-os>
- Xv6, a simple Unix-like teaching operating system<https://pdos.csail.mit.edu/6.828/2020/xv6.html>

77
README_zh.md Normal file
View File

@@ -0,0 +1,77 @@
**循序渐进,学习开发一个 RISC-V 上的操作系统**
<!-- TOC -->
- [简介](#简介)
- [运行环境](#运行环境)
- [构建和使用说明](#构建和使用说明)
- [参考文献](#参考文献)
<!-- /TOC -->
# 简介
本课程用于教学演示如何从零开始为 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`,下载地址为:<https://share.weiyun.com/nyTqAGKh>
下载完毕后将该文件拷贝到 `$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<https://osblog.stephenmarz.com/index.html>
- mini-riscv-os: <https://github.com/cccriscv/mini-riscv-os>
- Xv6, a simple Unix-like teaching operating system<https://pdos.csail.mit.edu/6.828/2020/xv6.html>

63
code/asm/Makefile Normal file
View File

@@ -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

1
code/asm/_first/Makefile Symbolic link
View File

@@ -0,0 +1 @@
../build.mk

10
code/asm/_first/test.s Normal file
View File

@@ -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

1
code/asm/add/Makefile Symbolic link
View File

@@ -0,0 +1 @@
../build.mk

19
code/asm/add/test.s Normal file
View File

@@ -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

1
code/asm/addi/Makefile Symbolic link
View File

@@ -0,0 +1 @@
../build.mk

18
code/asm/addi/test.s Normal file
View File

@@ -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

1
code/asm/and/Makefile Symbolic link
View File

@@ -0,0 +1 @@
../build.mk

18
code/asm/and/test.s Normal file
View File

@@ -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

1
code/asm/andi/Makefile Symbolic link
View File

@@ -0,0 +1 @@
../build.mk

18
code/asm/andi/test.s Normal file
View File

@@ -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

7
code/asm/asm2c/Makefile Normal file
View File

@@ -0,0 +1,7 @@
EXEC = test
SRC = $(EXEC).s $(EXEC).c
GDBINIT = ./gdbinit
include ../rule.mk

9
code/asm/asm2c/gdbinit Normal file
View File

@@ -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

5
code/asm/asm2c/test.c Normal file
View File

@@ -0,0 +1,5 @@
int foo(int a, int b)
{
int sum = a + b;
return sum;
}

23
code/asm/asm2c/test.s Normal file
View File

@@ -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

1
code/asm/auipc/Makefile Symbolic link
View File

@@ -0,0 +1 @@
../build.mk

23
code/asm/auipc/test.s Normal file
View File

@@ -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

1
code/asm/bne/Makefile Symbolic link
View File

@@ -0,0 +1 @@
../build.mk

26
code/asm/bne/test.s Normal file
View File

@@ -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

7
code/asm/build.mk Normal file
View File

@@ -0,0 +1,7 @@
EXEC = test
SRC = $(EXEC).s
GDBINIT = ../gdbinit
include ../rule.mk

7
code/asm/c2asm/Makefile Normal file
View File

@@ -0,0 +1,7 @@
EXEC = test
SRC = $(EXEC).s $(EXEC).c
GDBINIT = ./gdbinit
include ../rule.mk

11
code/asm/c2asm/gdbinit Normal file
View File

@@ -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

11
code/asm/c2asm/test.c Normal file
View File

@@ -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;
}

23
code/asm/c2asm/test.s Normal file
View File

@@ -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

View File

@@ -0,0 +1,7 @@
EXEC = test
SRC = $(EXEC).s
GDBINIT = ./gdbinit
include ../rule.mk

11
code/asm/cc_leaf/gdbinit Normal file
View File

@@ -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

48
code/asm/cc_leaf/test.s Normal file
View File

@@ -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

View File

@@ -0,0 +1,7 @@
EXEC = test
SRC = $(EXEC).s
GDBINIT = ./gdbinit
include ../rule.mk

View File

@@ -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

83
code/asm/cc_nested/test.s Normal file
View File

@@ -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

8
code/asm/gdbinit Normal file
View File

@@ -0,0 +1,8 @@
display/z $x5
display/z $x6
display/z $x7
set disassemble-next-line on
b _start
target remote : 1234
c

1
code/asm/jalr/Makefile Symbolic link
View File

@@ -0,0 +1 @@
../build.mk

44
code/asm/jalr/test.s Normal file
View File

@@ -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

1
code/asm/la/Makefile Symbolic link
View File

@@ -0,0 +1 @@
../build.mk

29
code/asm/la/test.s Normal file
View File

@@ -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

1
code/asm/lb/Makefile Symbolic link
View File

@@ -0,0 +1 @@
../build.mk

28
code/asm/lb/test.s Normal file
View File

@@ -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

1
code/asm/lbu/Makefile Symbolic link
View File

@@ -0,0 +1 @@
../build.mk

28
code/asm/lbu/test.s Normal file
View File

@@ -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

1
code/asm/li/Makefile Symbolic link
View File

@@ -0,0 +1 @@
../build.mk

44
code/asm/li/test.s Normal file
View File

@@ -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

1
code/asm/lui/Makefile Symbolic link
View File

@@ -0,0 +1 @@
../build.mk

19
code/asm/lui/test.s Normal file
View File

@@ -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

1
code/asm/lw/Makefile Symbolic link
View File

@@ -0,0 +1 @@
../build.mk

31
code/asm/lw/test.s Normal file
View File

@@ -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

1
code/asm/mv/Makefile Symbolic link
View File

@@ -0,0 +1 @@
../build.mk

20
code/asm/mv/test.s Normal file
View File

@@ -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

1
code/asm/neg/Makefile Symbolic link
View File

@@ -0,0 +1 @@
../build.mk

21
code/asm/neg/test.s Normal file
View File

@@ -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

1
code/asm/nop/Makefile Symbolic link
View File

@@ -0,0 +1 @@
../build.mk

20
code/asm/nop/test.s Normal file
View File

@@ -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

1
code/asm/not/Makefile Symbolic link
View File

@@ -0,0 +1 @@
../build.mk

19
code/asm/not/test.s Normal file
View File

@@ -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

38
code/asm/rule.mk Normal file
View File

@@ -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

1
code/asm/sb/Makefile Symbolic link
View File

@@ -0,0 +1 @@
../build.mk

28
code/asm/sb/test.s Normal file
View File

@@ -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

1
code/asm/slli/Makefile Symbolic link
View File

@@ -0,0 +1 @@
../build.mk

21
code/asm/slli/test.s Normal file
View File

@@ -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

1
code/asm/srai/Makefile Symbolic link
View File

@@ -0,0 +1 @@
../build.mk

9
code/asm/srai/test.c Normal file
View File

@@ -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;
}

23
code/asm/srai/test.s Normal file
View File

@@ -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

1
code/asm/srli/Makefile Symbolic link
View File

@@ -0,0 +1 @@
../build.mk

9
code/asm/srli/test.c Normal file
View File

@@ -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;
}

21
code/asm/srli/test.s Normal file
View File

@@ -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

1
code/asm/sub/Makefile Symbolic link
View File

@@ -0,0 +1 @@
../build.mk

19
code/asm/sub/test.s Normal file
View File

@@ -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

1
code/asm/subi/Makefile Symbolic link
View File

@@ -0,0 +1 @@
../build.mk

16
code/asm/subi/test.s Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,5 @@
void start_kernel(void)
{
while (1) {}; // stop here!
}

View File

@@ -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__ */

View File

@@ -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

View File

@@ -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

View File

@@ -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!
}

View File

@@ -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__ */

View File

@@ -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

View File

@@ -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__ */

120
code/os/01-helloRVOS/uart.c Normal file
View File

@@ -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 bits1 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++);
}
}

View File

@@ -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

View File

@@ -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!
}

View File

@@ -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

View File

@@ -0,0 +1,22 @@
#ifndef __OS_H__
#define __OS_H__
#include "types.h"
#include "platform.h"
#include <stddef.h>
#include <stdarg.h>
/* 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__ */

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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__ */

View File

@@ -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){};
}

View File

@@ -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

View File

@@ -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__ */

View File

@@ -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 bits1 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++);
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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!
}

View File

@@ -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

View File

@@ -0,0 +1,61 @@
#ifndef __OS_H__
#define __OS_H__
#include "types.h"
#include "platform.h"
#include <stddef.h>
#include <stdarg.h>
/* 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__ */

View File

@@ -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);
}

Some files were not shown because too many files have changed in this diff Show More