26 Commits
v0.9.2 ... main

Author SHA1 Message Date
6eanut
48d1d6ebaf VIRT_PLIC_PRIORITY_BASE 0x04->0x00 2025-04-28 17:10:11 +08:00
Chen Wang
eb61470e22 Improve memory/page init
Align heap start and caculate number of reserved pages
according to the length of ram available.

See
https://gitee.com/unicornx/riscv-operating-system-mooc/issues/I9LNCF.

Signed-off-by: Chen Wang <unicorn_wang@outlook.com>
2024-06-06 10:13:52 +08:00
Chen Wang
0e16685c97 add ignore of output folers for os examples
Signed-off-by: Chen Wang <unicorn_wang@outlook.com>
2024-06-05 21:02:39 +08:00
Chen Wang
a982ea7317 update reamdme_zh, add info about porting rvos
Signed-off-by: Chen Wang <wangchen20@iscas.ac.cn>
2024-04-11 07:46:05 +08:00
Chen Wang
7df4ae5a19 optimize the uart get process
Also remove the while in uart_isr. Generally unlimited loop in interrupt
handler is not recommended.

Signed-off-by: Wang Chen <unicorn_wang@outlook.com>
2024-03-28 09:08:10 +08:00
Wang Chen
0cacbd6a22 remove qq group 1 from the readme.
Signed-off-by: Wang Chen <wangchen20@iscas.ac.cn>
2024-03-27 16:44:14 +08:00
Wang Chen
2fcd517c78 Use %p to print pointer
We use %ld to print mcause.code to compatilbe with rv64 and it do
no harm for rv32.

Signed-off-by: Wang Chen <wangchen20@iscas.ac.cn>
2024-03-27 14:50:35 +08:00
Wang Chen
fd1d098906 Encapsulate some types related to cpu word length.
The purpose is to facilitate porting to RV64.

Signed-off-by: Wang Chen <wangchen20@iscas.ac.cn>
2024-03-27 14:30:53 +08:00
Wang Chen
1976f03d28 move all output to out folder
Now after build the source directories are clean.

Signed-off-by: Wang Chen <wangchen20@iscas.ac.cn>
2024-03-27 09:07:06 +08:00
Wang Chen
492cac5ba9 add defines.mk
Signed-off-by: Wang Chen <wangchen20@iscas.ac.cn>
2024-03-27 08:22:05 +08:00
Wang Chen
a2617cffa6 create common.mk for os projects
Makefiles of rvos projects contains too many duplicated contents.
Cleanup and move it into a new common.mk file for os only, this will
not touch asm samples.

Finally the common..mk which was used for both asm & os is removed.

Signed-off-by: Wang Chen <wangchen20@iscas.ac.cn>
2024-03-26 09:01:03 +08:00
Wang Chen
992b0ba91c fixed bug for syscall example
The original logic of setting mstatus has problem.
The or directive cannot set .MPP to 0.

Optimize the original code and use csrs and csrc instead.

Note we cannot assume the default value of mstatus is zero.
rvos may not be the first one to run on the system/virt.
It just so lucky that the initial value of mstatus is zero
on QEMU/virt.

Signed-off-by: Wang Chen <wangchen20@iscas.ac.cn>
2024-03-25 21:10:40 +08:00
Wang Chen
4698c95063 fixed PLIC_MENABLE issue
Learn from https://github.com/LiuJiLan/RVOS_On_VisionFive2.
Thanks.

Signed-off-by: Wang Chen <wangchen20@iscas.ac.cn>
2024-03-06 18:32:20 +08:00
Wang Chen
c0e6ce15f0 added QQ group 2
Signed-off-by: Wang Chen <wangchen20@iscas.ac.cn>
2024-02-01 19:44:45 +08:00
Wang Chen
0446eb2b9d fixed bug #I85M48
https://gitee.com/unicornx/riscv-operating-system-mooc/issues/I85M48

Signed-off-by:  Wang Chen <wangchen20@iscas.ac.cn>
2024-01-06 09:41:30 +08:00
Chen Wang
27723d09ed Compatible support for new gcc
With gcc version >= 11.1.0, to support new ISA spec changes, which
moved some instructions from the I extension to the Zicsr and Zifencei
extensions, we have to explicitly specify Zicsr and Zifencei via -march.
But it is not required for old gcc versions.

To cope with both cases, we use rv32g instead of rv32ima.
"g" = "imafd". RVOS doesn't use "f" & "d", and we also don't want "c".

We use "g" to just to make life easy, otherwise we may have to intriduce
some mechanism to judge and differ the version of gcc used.

Also updated some comments to move to rv32g and don't involve words such
as "rv32ima".

Signed-off-by: Chen Wang <wangchen20@iscas.ac.cn>
2023-12-19 08:26:42 +08:00
Hunter
2474692c8b Fix uses of .align
The GNU assembler responds to .align in a platform-dependent way. For a
use of '.align x', gas will align to 'x' bytes for some platforms,
but '2^x' bytes for other platforms including RISC-V. We are currently
reserving too much space with .align, so correct them by switching to
the more predictable .balign directive.

Signed-off-by: Hunter <>
2023-12-19 07:51:43 +08:00
Wang Chen
457713c30e Don't set MPIE explicitly
Because according to ISA specification: interrupts for M-mode, which
is higher than U-mode, are always globally enabled regardless of the
setting of the global MIE bit.
We don't set mstatus.MPIE to 1 explicitly to avoid misleading people
into thinking it is must-haveto-do.

Signed-off-by: Wang Chen <wangchen20@iscas.ac.cn>
Co-authored-by: LiuJiLan <ldc31415926@126.com>
2023-06-18 09:11:19 +08:00
Wang Chen
a94fb0cd58 stack pointer aligment
Following the standard RISC-V calling convention, make sure
the stack pointer sp is always 16-byte aligned.

Fixed an issue, make sure the sp of task point to the bottom
of the stack, while originally we waste one byte (forgive my
stupid ~~~).

Signed-off-by: Wang Chen <wangchen20@iscas.ac.cn>
2023-06-17 22:12:59 +08:00
Wang Chen
6d2c046c67 don't save/retore gp&tp
Signed-off-by: Wang Chen <wangchen20@iscas.ac.cn>
Co-authored-by: LiuJiLan <ldc31415926@126.com>
2023-06-17 16:14:18 +08:00
Wang Chen
f77939f1e8 improved code comments about restore/save context of task
Signed-off-by: Wang Chen <wangchen20@iscas.ac.cn>
2023-02-12 13:23:20 +08:00
Wang Chen
c674ffc9bc optimize page alloc
Signed-off-by: ablechen <17895010372@163.com>
Signed-off-by: Wang Chen <wangchen20@iscas.ac.cn>
2022-11-23 10:52:43 +08:00
Wang Chen
744226d09e improved comments
Signed-off-by: ablechen <17895010372@163.com>
Signed-off-by: Wang Chen <wangchen20@iscas.ac.cn>
2022-11-23 10:06:37 +08:00
Chen Wang
cec375a5e6 fixed some bugs
- https://gitee.com/unicornx/riscv-operating-system-mooc/issues/I55JRV
- https://gitee.com/unicornx/riscv-operating-system-mooc/issues/I4UUHU
- https://gitee.com/unicornx/riscv-operating-system-mooc/issues/I4D6N9

Signed-off-by: Chen Wang <wangchen20@iscas.ac.cn>
2022-06-12 16:37:03 +08:00
Wang Chen
ae10d3e698 fixed some minor issues.
- https://gitee.com/unicornx/riscv-operating-system-mooc/issues/I4QLTP
- https://gitee.com/unicornx/riscv-operating-system-mooc/issues/I4PJTQ
- https://gitee.com/unicornx/riscv-operating-system-mooc/issues/I49VW5

Signed-off-by: Wang Chen <wangchen20@iscas.ac.cn>
2022-01-27 11:06:44 +08:00
Wang Chen
ca2629bebc I493SN:fix bugs introduced by I441IC 2021-09-08 08:38:58 +08:00
147 changed files with 2181 additions and 1988 deletions

2
.gitignore vendored
View File

@@ -1,4 +1,4 @@
*.o
*.bin
*.elf
code/os/*/out/

View File

@@ -5,24 +5,34 @@
- [1. 简介](#1-简介)
- [2. 运行环境](#2-运行环境)
- [3. 构建和使用说明](#3-构建和使用说明)
- [4. 参考文献](#4-参考文献)
- [4. 有关 RVOS 的移植](#4-有关-rvos-的移植)
- [5. 参考文献](#5-参考文献)
<!-- /TOC -->
# 1. 简介
本课程用于教学演示如何从零开始为 RISC-V 平台编写一个简单的操作系统内核。采用 BSD 2-Clause 许可证发布(具体请阅读本仓库根目录下的 [LICENSE 文件](./LICENSE))。
本课程自 2021 年春季首次发布后的课程内容勘误表为本仓库根目录下的 `errata.pdf` 文件,在学习之前请先自行阅读。
如果您有任何的问题或者发现任何可疑的 bug请通过 [Gitee 的 issue 跟踪系统](https://gitee.com/unicornx/riscv-operating-system-mooc/issues) 给我们提出报告,我们将第一时间检查并在系统中回复您。
**本课程的配套教学视频在线播放地址**: <https://www.bilibili.com/video/BV1Q5411w7z5>
欢迎加入本课程的 **学习群**,边学习边讨论:
- **QQ 学习群**,群号 976125506或者扫码加入见下
- **QQ 学习群**
![](./qq-group.png)
**“课程群 2”**, 群号: **799027025**,或者扫码加入,见下:
- **微信学习群**,请添加微信 fangzhang1024 (标注 【汪辰老师】)加入。
![](./qq-group-2.png)
***注意!!!:“课程群 1” 已满,请大家加入 “课程群 2”。***
- **微信学习群**
请添加微信 fangzhang1024 (标注 【汪辰老师】)加入。
# 2. 运行环境
@@ -61,10 +71,20 @@ $ sudo apt install build-essential gcc make perl dkms git gcc-riscv64-unknown-el
具体使用请参考具体子项目下的 Makefile 文件。
# 4. 参考文献
# 4. 有关 RVOS 的移植
可以参考这篇文章: [《将 RVOS 移植到 MilkV-Duo 上》][1],介绍了如何将 RVOS 从 32 位扩展为 64 位以及如何将其移植到一款真正的硬件Milk-V Duo上。
也可以看 [这里,收集了网友将本课程代码移植到各种硬件的活动记录][2]。
# 5. 参考文献
本课程的设计参考了如下网络资源,在此表示感谢 :)
- 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>
[1]: https://zhuanlan.zhihu.com/p/691697875
[2]: https://gitee.com/unicornx/riscv-operating-system-mooc/issues/I64EEQ

View File

@@ -20,7 +20,7 @@ stop:
nop # just for demo effect
stack_start:
.rept 10
.rept 12
.word 0
.endr
stack_end:

View File

@@ -1,3 +1,6 @@
# Note: The Makefile for each project is just a symbol-link to the build.mk
# under the "asm" folder.
EXEC = test
SRC = ${EXEC}.s

View File

@@ -17,7 +17,7 @@ stop:
nop # just for demo effect
stack_start:
.rept 10
.rept 12
.word 0
.endr
stack_end:

View File

@@ -1,59 +1,59 @@
# Calling Convention
# Demo to create a leaf routine
#
# void _start()
# {
# // calling leaf routine
# square(3);
# }
#
# int square(int num)
# {
# return num * num;
# }
.text # Define beginning of text section
.global _start # Define entry _start
_start:
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
# Calling Convention
# Demo to create a leaf routine
#
# void _start()
# {
# // calling leaf routine
# square(3);
# }
#
# int square(int num)
# {
# return num * num;
# }
.text # Define beginning of text section
.global _start # Define entry _start
_start:
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 12
.word 0
.endr
stack_end:
.end # End of file

View File

@@ -1,99 +1,99 @@
# Calling Convention
# Demo how to write nested routines
#
# void _start()
# {
# // calling nested routine
# aa_bb(3, 4);
# }
#
# int aa_bb(int a, int b)
# {
# return square(a) + square(b);
# }
#
# int square(int num)
# {
# return num * num;
# }
.text # Define beginning of text section
.global _start # Define entry _start
_start:
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
# Calling Convention
# Demo how to write nested routines
#
# void _start()
# {
# // calling nested routine
# aa_bb(3, 4);
# }
#
# int aa_bb(int a, int b)
# {
# return square(a) + square(b);
# }
#
# int square(int num)
# {
# return num * num;
# }
.text # Define beginning of text section
.global _start # Define entry _start
_start:
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 12
.word 0
.endr
stack_end:
.end # End of file

View File

@@ -1,4 +1,15 @@
include ../../common.mk
# This file will be included by the build.mk.
CROSS_COMPILE = riscv64-unknown-elf-
CFLAGS = -nostdlib -fno-builtin -march=rv32g -mabi=ilp32 -g -Wall
QEMU = qemu-system-riscv32
QFLAGS = -nographic -smp 1 -machine virt -bios none
GDB = gdb-multiarch
CC = ${CROSS_COMPILE}gcc
OBJCOPY = ${CROSS_COMPILE}objcopy
OBJDUMP = ${CROSS_COMPILE}objdump
.DEFAULT_GOAL := all
all:

View File

@@ -1,6 +1,6 @@
// 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-gcc -march=rv32g -mabi=ilp32 -c -g test.c -o test.o
// riscv64-unknown-elf-objdump -S test.o
void foo()
{

View File

@@ -1,23 +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:
# 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
# Shift Right Arithmetic Immediate
# Format:
# SRAI 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:
# li x6, 0x80 # x6 = 0b1000-0000
li x6, 0x80000000 # x6 = 0b1000-0000-0000-0000-0000-0000-0000-0000
srai x5, x6, 4 # x5 = x6 >> 4
stop:
j stop # Infinite loop to stop execution
.end # End of file

View File

@@ -1,6 +1,6 @@
// 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-gcc -march=rv32g -mabi=ilp32 -c -g test.c -o test.o
// riscv64-unknown-elf-objdump -S test.o
void foo()
{

View File

@@ -1,10 +0,0 @@
CROSS_COMPILE = riscv64-unknown-elf-
CFLAGS = -nostdlib -fno-builtin -march=rv32ima -mabi=ilp32 -g -Wall
QEMU = qemu-system-riscv32
QFLAGS = -nographic -smp 1 -machine virt -bios none
GDB = gdb-multiarch
CC = ${CROSS_COMPILE}gcc
OBJCOPY = ${CROSS_COMPILE}objcopy
OBJDUMP = ${CROSS_COMPILE}objdump

View File

@@ -1,4 +1,4 @@
include ../../common.mk
USE_LINKER_SCRIPT = false
SRCS_ASM = \
start.S \
@@ -6,41 +6,4 @@ SRCS_ASM = \
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
include ../common.mk

View File

@@ -26,6 +26,9 @@ park:
wfi
j park
# In the standard RISC-V calling convention, the stack pointer sp
# is always 16-byte aligned.
.balign 16
stacks:
.skip STACK_SIZE * MAXNUM_CPU # allocate space for all the harts stacks

View File

@@ -1,4 +1,4 @@
include ../../common.mk
USE_LINKER_SCRIPT = false
SRCS_ASM = \
start.S \
@@ -7,41 +7,4 @@ 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
include ../common.mk

View File

@@ -5,7 +5,7 @@
* 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
@@ -14,7 +14,7 @@
/*
* MemoryMap
* see https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c, virt_memmap[]
* see https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c, virt_memmap[]
* 0x00001000 -- boot ROM, provided by qemu
* 0x02000000 -- CLINT
* 0x0C000000 -- PLIC

View File

@@ -26,6 +26,9 @@ park:
wfi
j park
# In the standard RISC-V calling convention, the stack pointer sp
# is always 16-byte aligned.
.balign 16
stacks:
.skip STACK_SIZE * MAXNUM_CPU # allocate space for all the harts stacks

View File

@@ -33,7 +33,7 @@
/*
* POWER UP DEFAULTS
* IER = 0: TX/RX holding register interrupts are bith disabled
* IER = 0: TX/RX holding register interrupts are both disabled
* ISR = 1: no interrupt penting
* LCR = 0
* MCR = 0

View File

@@ -1,5 +1,3 @@
include ../../common.mk
SRCS_ASM = \
start.S \
mem.S \
@@ -10,41 +8,4 @@ SRCS_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
include ../common.mk

View File

@@ -1,30 +1,32 @@
#define SIZE_PTR .word
.section .rodata
.global HEAP_START
HEAP_START: .word _heap_start
HEAP_START: SIZE_PTR _heap_start
.global HEAP_SIZE
HEAP_SIZE: .word _heap_size
HEAP_SIZE: SIZE_PTR _heap_size
.global TEXT_START
TEXT_START: .word _text_start
TEXT_START: SIZE_PTR _text_start
.global TEXT_END
TEXT_END: .word _text_end
TEXT_END: SIZE_PTR _text_end
.global DATA_START
DATA_START: .word _data_start
DATA_START: SIZE_PTR _data_start
.global DATA_END
DATA_END: .word _data_end
DATA_END: SIZE_PTR _data_end
.global RODATA_START
RODATA_START: .word _rodata_start
RODATA_START: SIZE_PTR _rodata_start
.global RODATA_END
RODATA_END: .word _rodata_end
RODATA_END: SIZE_PTR _rodata_end
.global BSS_START
BSS_START: .word _bss_start
BSS_START: SIZE_PTR _bss_start
.global BSS_END
BSS_END: .word _bss_end
BSS_END: SIZE_PTR _bss_end

View File

@@ -3,12 +3,14 @@
* Linker script for outputting to RVOS
*/
#include "platform.h"
/*
* 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.
* RISC-V target. We will further refine this by using -march
* and -mabi when calling gcc.
*/
OUTPUT_ARCH( "riscv" )
@@ -47,7 +49,7 @@ ENTRY( _start )
*/
MEMORY
{
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = LENGTH_RAM
}
/*

View File

@@ -3,24 +3,24 @@
/*
* 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;
extern ptr_t TEXT_START;
extern ptr_t TEXT_END;
extern ptr_t DATA_START;
extern ptr_t DATA_END;
extern ptr_t RODATA_START;
extern ptr_t RODATA_END;
extern ptr_t BSS_START;
extern ptr_t BSS_END;
extern ptr_t HEAP_START;
extern ptr_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 ptr_t _alloc_start = 0;
static ptr_t _alloc_end = 0;
static uint32_t _num_pages = 0;
#define PAGE_SIZE 4096
@@ -70,35 +70,65 @@ static inline int _is_last(struct Page *page)
/*
* align the address to the border of page(4K)
*/
static inline uint32_t _align_page(uint32_t address)
static inline ptr_t _align_page(ptr_t address)
{
uint32_t order = (1 << PAGE_ORDER) - 1;
ptr_t order = (1 << PAGE_ORDER) - 1;
return (address + order) & (~order);
}
/*
* ______________________________HEAP_SIZE_______________________________
* / ___num_reserved_pages___ ______________num_pages______________ \
* / / \ / \ \
* |---|<--Page-->|<--Page-->|...|<--Page-->|<--Page-->|......|<--Page-->|---|
* A A A A A
* | | | | |
* | | | | _memory_end
* | | | |
* | _heap_start_aligned _alloc_start _alloc_end
* HEAP_START(BSS_END)
*
* Note: _alloc_end may equal to _memory_end.
*/
void page_init()
{
ptr_t _heap_start_aligned = _align_page(HEAP_START);
/*
* 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)
* We reserved some Pages to hold the Page structures.
* The number of reserved pages depends on the LENGTH_RAM.
* For simplicity, the space we reserve here is just an approximation,
* assuming that it can accommodate the maximum LENGTH_RAM.
* We assume LENGTH_RAM should not be too small, ideally no less
* than 16M (i.e. PAGE_SIZE * PAGE_SIZE).
*/
_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);
uint32_t num_reserved_pages = LENGTH_RAM / (PAGE_SIZE * PAGE_SIZE);
_num_pages = (HEAP_SIZE - (_heap_start_aligned - HEAP_START))/ PAGE_SIZE - num_reserved_pages;
printf("HEAP_START = %p(aligned to %p), HEAP_SIZE = 0x%lx,\n"
"num of reserved pages = %d, num of pages to be allocated for heap = %d\n",
HEAP_START, _heap_start_aligned, HEAP_SIZE,
num_reserved_pages, _num_pages);
/*
* We use HEAP_START, not _heap_start_aligned as begin address for
* allocating struct Page, because we have no requirement of alignment
* for position of struct Page.
*/
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_start = _heap_start_aligned + num_reserved_pages * 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);
printf("TEXT: %p -> %p\n", TEXT_START, TEXT_END);
printf("RODATA: %p -> %p\n", RODATA_START, RODATA_END);
printf("DATA: %p -> %p\n", DATA_START, DATA_END);
printf("BSS: %p -> %p\n", BSS_START, BSS_END);
printf("HEAP: %p -> %p\n", _alloc_start, _alloc_end);
}
/*
@@ -110,15 +140,15 @@ 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++) {
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++) {
struct Page *page_j = page_i + 1;
for (int j = i + 1; j < (i + npages); j++) {
if (!_is_free(page_j)) {
found = 0;
break;
@@ -155,12 +185,12 @@ void page_free(void *p)
/*
* Assert (TBD) if p is invalid
*/
if (!p || (uint32_t)p >= _alloc_end) {
if (!p || (ptr_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;
page += ((ptr_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)) {
@@ -176,14 +206,14 @@ void page_free(void *p)
void page_test()
{
void *p = page_alloc(2);
printf("p = 0x%x\n", p);
printf("p = %p\n", p);
//page_free(p);
void *p2 = page_alloc(7);
printf("p2 = 0x%x\n", p2);
printf("p2 = %p\n", p2);
page_free(p2);
void *p3 = page_alloc(4);
printf("p3 = 0x%x\n", p3);
printf("p3 = %p\n", p3);
}

View File

@@ -5,16 +5,19 @@
* 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
/* used in os.ld */
#define LENGTH_RAM 128*1024*1024
/*
* MemoryMap
* see https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c, virt_memmap[]
* see https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c, virt_memmap[]
* 0x00001000 -- boot ROM, provided by qemu
* 0x02000000 -- CLINT
* 0x0C000000 -- PLIC

View File

@@ -36,6 +36,9 @@ park:
wfi
j park
# In the standard RISC-V calling convention, the stack pointer sp
# is always 16-byte aligned.
.balign 16
stacks:
.skip STACK_SIZE * MAXNUM_CPU # allocate space for all the harts stacks

View File

@@ -6,4 +6,6 @@ typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
typedef uint32_t ptr_t;
#endif /* __TYPES_H__ */

View File

@@ -32,7 +32,7 @@
/*
* POWER UP DEFAULTS
* IER = 0: TX/RX holding register interrupts are bith disabled
* IER = 0: TX/RX holding register interrupts are both disabled
* ISR = 1: no interrupt penting
* LCR = 0
* MCR = 0

View File

@@ -1,5 +1,3 @@
include ../../common.mk
SRCS_ASM = \
start.S \
mem.S \
@@ -12,41 +10,4 @@ SRCS_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
include ../common.mk

View File

@@ -1,102 +1,116 @@
# save all General-Purpose(GP) registers to context
#define LOAD lw
#define STORE sw
#define SIZE_REG 4
# Save all General-Purpose(GP) registers to context.
# struct context *base = &ctx_task;
# base->ra = ra;
# ......
# These GP registers to be saved don't include gp
# and tp, because they are not caller-saved or
# callee-saved. These two registers are often used
# for special purpose. For example, in RVOS, 'tp'
# (aka "thread pointer") is used to store hartid,
# which is a global value and would not be changed
# during context-switch.
.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)
STORE ra, 0*SIZE_REG(\base)
STORE sp, 1*SIZE_REG(\base)
STORE t0, 4*SIZE_REG(\base)
STORE t1, 5*SIZE_REG(\base)
STORE t2, 6*SIZE_REG(\base)
STORE s0, 7*SIZE_REG(\base)
STORE s1, 8*SIZE_REG(\base)
STORE a0, 9*SIZE_REG(\base)
STORE a1, 10*SIZE_REG(\base)
STORE a2, 11*SIZE_REG(\base)
STORE a3, 12*SIZE_REG(\base)
STORE a4, 13*SIZE_REG(\base)
STORE a5, 14*SIZE_REG(\base)
STORE a6, 15*SIZE_REG(\base)
STORE a7, 16*SIZE_REG(\base)
STORE s2, 17*SIZE_REG(\base)
STORE s3, 18*SIZE_REG(\base)
STORE s4, 19*SIZE_REG(\base)
STORE s5, 20*SIZE_REG(\base)
STORE s6, 21*SIZE_REG(\base)
STORE s7, 22*SIZE_REG(\base)
STORE s8, 23*SIZE_REG(\base)
STORE s9, 24*SIZE_REG(\base)
STORE s10, 25*SIZE_REG(\base)
STORE s11, 26*SIZE_REG(\base)
STORE t3, 27*SIZE_REG(\base)
STORE t4, 28*SIZE_REG(\base)
STORE t5, 29*SIZE_REG(\base)
# we don't save t6 here, due to we have used
# it as base, we have to save t6 in an extra step
# outside of reg_save
.endm
# restore all General-Purpose(GP) registers from the context
# except gp & tp.
# 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)
LOAD ra, 0*SIZE_REG(\base)
LOAD sp, 1*SIZE_REG(\base)
LOAD t0, 4*SIZE_REG(\base)
LOAD t1, 5*SIZE_REG(\base)
LOAD t2, 6*SIZE_REG(\base)
LOAD s0, 7*SIZE_REG(\base)
LOAD s1, 8*SIZE_REG(\base)
LOAD a0, 9*SIZE_REG(\base)
LOAD a1, 10*SIZE_REG(\base)
LOAD a2, 11*SIZE_REG(\base)
LOAD a3, 12*SIZE_REG(\base)
LOAD a4, 13*SIZE_REG(\base)
LOAD a5, 14*SIZE_REG(\base)
LOAD a6, 15*SIZE_REG(\base)
LOAD a7, 16*SIZE_REG(\base)
LOAD s2, 17*SIZE_REG(\base)
LOAD s3, 18*SIZE_REG(\base)
LOAD s4, 19*SIZE_REG(\base)
LOAD s5, 20*SIZE_REG(\base)
LOAD s6, 21*SIZE_REG(\base)
LOAD s7, 22*SIZE_REG(\base)
LOAD s8, 23*SIZE_REG(\base)
LOAD s9, 24*SIZE_REG(\base)
LOAD s10, 25*SIZE_REG(\base)
LOAD s11, 26*SIZE_REG(\base)
LOAD t3, 27*SIZE_REG(\base)
LOAD t4, 28*SIZE_REG(\base)
LOAD t5, 29*SIZE_REG(\base)
LOAD t6, 30*SIZE_REG(\base)
.endm
# Something to note about save/restore:
# - We use mscratch to hold a pointer to context of previous task
# - We use mscratch to hold a pointer to context of current 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.
# Note: CSRs(mscratch) can not be used as 'base' due to load/restore
# instruction only accept general purpose registers.
.text
# void switch_to(struct context *next);
# a0: pointer to the context of the next task
.globl switch_to
.align 4
.balign 4
switch_to:
csrrw t6, mscratch, t6 # swap t6 and mscratch
beqz t6, 1f # Notice: previous task may be NULL
beqz t6, 1f # Note: the first time switch_to() is
# called, mscratch is initialized as zero
# (in sched_init()), which makes t6 zero,
# and that's the special case we have to
# handle with t6
reg_save t6 # save context of prev task
# Save the actual t6 register, which we swapped into
# mscratch
mv t5, t6 # t5 points to the context of current task
csrr t6, mscratch # read t6 back from mscratch
sw t6, 120(t5) # save t6 with t5 as base
mv t5, t6 # t5 points to the context of current task
csrr t6, mscratch # read t6 back from mscratch
STORE t6, 30*SIZE_REG(t5) # save t6 with t5 as base
1:
# switch mscratch to point to the context of the next task

View File

@@ -1,30 +1,32 @@
#define SIZE_PTR .word
.section .rodata
.global HEAP_START
HEAP_START: .word _heap_start
HEAP_START: SIZE_PTR _heap_start
.global HEAP_SIZE
HEAP_SIZE: .word _heap_size
HEAP_SIZE: SIZE_PTR _heap_size
.global TEXT_START
TEXT_START: .word _text_start
TEXT_START: SIZE_PTR _text_start
.global TEXT_END
TEXT_END: .word _text_end
TEXT_END: SIZE_PTR _text_end
.global DATA_START
DATA_START: .word _data_start
DATA_START: SIZE_PTR _data_start
.global DATA_END
DATA_END: .word _data_end
DATA_END: SIZE_PTR _data_end
.global RODATA_START
RODATA_START: .word _rodata_start
RODATA_START: SIZE_PTR _rodata_start
.global RODATA_END
RODATA_END: .word _rodata_end
RODATA_END: SIZE_PTR _rodata_end
.global BSS_START
BSS_START: .word _bss_start
BSS_START: SIZE_PTR _bss_start
.global BSS_END
BSS_END: .word _bss_end
BSS_END: SIZE_PTR _bss_end

View File

@@ -3,12 +3,14 @@
* Linker script for outputting to RVOS
*/
#include "platform.h"
/*
* 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.
* RISC-V target. We will further refine this by using -march
* and -mabi when calling gcc.
*/
OUTPUT_ARCH( "riscv" )
@@ -47,7 +49,7 @@ ENTRY( _start )
*/
MEMORY
{
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = LENGTH_RAM
}
/*

View File

@@ -3,24 +3,24 @@
/*
* 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;
extern ptr_t TEXT_START;
extern ptr_t TEXT_END;
extern ptr_t DATA_START;
extern ptr_t DATA_END;
extern ptr_t RODATA_START;
extern ptr_t RODATA_END;
extern ptr_t BSS_START;
extern ptr_t BSS_END;
extern ptr_t HEAP_START;
extern ptr_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 ptr_t _alloc_start = 0;
static ptr_t _alloc_end = 0;
static uint32_t _num_pages = 0;
#define PAGE_SIZE 4096
@@ -70,35 +70,65 @@ static inline int _is_last(struct Page *page)
/*
* align the address to the border of page(4K)
*/
static inline uint32_t _align_page(uint32_t address)
static inline ptr_t _align_page(ptr_t address)
{
uint32_t order = (1 << PAGE_ORDER) - 1;
ptr_t order = (1 << PAGE_ORDER) - 1;
return (address + order) & (~order);
}
/*
* ______________________________HEAP_SIZE_______________________________
* / ___num_reserved_pages___ ______________num_pages______________ \
* / / \ / \ \
* |---|<--Page-->|<--Page-->|...|<--Page-->|<--Page-->|......|<--Page-->|---|
* A A A A A
* | | | | |
* | | | | _memory_end
* | | | |
* | _heap_start_aligned _alloc_start _alloc_end
* HEAP_START(BSS_END)
*
* Note: _alloc_end may equal to _memory_end.
*/
void page_init()
{
ptr_t _heap_start_aligned = _align_page(HEAP_START);
/*
* 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)
* We reserved some Pages to hold the Page structures.
* The number of reserved pages depends on the LENGTH_RAM.
* For simplicity, the space we reserve here is just an approximation,
* assuming that it can accommodate the maximum LENGTH_RAM.
* We assume LENGTH_RAM should not be too small, ideally no less
* than 16M (i.e. PAGE_SIZE * PAGE_SIZE).
*/
_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);
uint32_t num_reserved_pages = LENGTH_RAM / (PAGE_SIZE * PAGE_SIZE);
_num_pages = (HEAP_SIZE - (_heap_start_aligned - HEAP_START))/ PAGE_SIZE - num_reserved_pages;
printf("HEAP_START = %p(aligned to %p), HEAP_SIZE = 0x%lx,\n"
"num of reserved pages = %d, num of pages to be allocated for heap = %d\n",
HEAP_START, _heap_start_aligned, HEAP_SIZE,
num_reserved_pages, _num_pages);
/*
* We use HEAP_START, not _heap_start_aligned as begin address for
* allocating struct Page, because we have no requirement of alignment
* for position of struct Page.
*/
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_start = _heap_start_aligned + num_reserved_pages * 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);
printf("TEXT: %p -> %p\n", TEXT_START, TEXT_END);
printf("RODATA: %p -> %p\n", RODATA_START, RODATA_END);
printf("DATA: %p -> %p\n", DATA_START, DATA_END);
printf("BSS: %p -> %p\n", BSS_START, BSS_END);
printf("HEAP: %p -> %p\n", _alloc_start, _alloc_end);
}
/*
@@ -110,15 +140,15 @@ 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++) {
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++) {
struct Page *page_j = page_i + 1;
for (int j = i + 1; j < (i + npages); j++) {
if (!_is_free(page_j)) {
found = 0;
break;
@@ -155,12 +185,12 @@ void page_free(void *p)
/*
* Assert (TBD) if p is invalid
*/
if (!p || (uint32_t)p >= _alloc_end) {
if (!p || (ptr_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;
page += ((ptr_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)) {
@@ -176,14 +206,14 @@ void page_free(void *p)
void page_test()
{
void *p = page_alloc(2);
printf("p = 0x%x\n", p);
printf("p = %p\n", p);
//page_free(p);
void *p2 = page_alloc(7);
printf("p2 = 0x%x\n", p2);
printf("p2 = %p\n", p2);
page_free(p2);
void *p3 = page_alloc(4);
printf("p3 = 0x%x\n", p3);
printf("p3 = %p\n", p3);
}

View File

@@ -5,16 +5,19 @@
* 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
/* used in os.ld */
#define LENGTH_RAM 128*1024*1024
/*
* MemoryMap
* see https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c, virt_memmap[]
* see https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c, virt_memmap[]
* 0x00001000 -- boot ROM, provided by qemu
* 0x02000000 -- CLINT
* 0x0C000000 -- PLIC

View File

@@ -4,7 +4,11 @@
extern void switch_to(struct context *next);
#define STACK_SIZE 1024
uint8_t task_stack[STACK_SIZE];
/*
* In the standard RISC-V calling convention, the stack pointer sp
* is always 16-byte aligned.
*/
uint8_t __attribute__((aligned(16))) task_stack[STACK_SIZE];
struct context ctx_task;
static void w_mscratch(reg_t x)
@@ -17,7 +21,7 @@ void sched_init()
{
w_mscratch(0);
ctx_task.sp = (reg_t) &task_stack[STACK_SIZE - 1];
ctx_task.sp = (reg_t) &task_stack[STACK_SIZE];
ctx_task.ra = (reg_t) user_task0;
}

View File

@@ -36,6 +36,9 @@ park:
wfi
j park
# In the standard RISC-V calling convention, the stack pointer sp
# is always 16-byte aligned.
.balign 16
stacks:
.skip STACK_SIZE * MAXNUM_CPU # allocate space for all the harts stacks

View File

@@ -7,8 +7,9 @@ typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
/*
* RISCV32: register is 32bits width
*/
* Register Width
*/
typedef uint32_t reg_t;
typedef uint32_t ptr_t;
#endif /* __TYPES_H__ */

View File

@@ -32,7 +32,7 @@
/*
* POWER UP DEFAULTS
* IER = 0: TX/RX holding register interrupts are bith disabled
* IER = 0: TX/RX holding register interrupts are both disabled
* ISR = 1: no interrupt penting
* LCR = 0
* MCR = 0

View File

@@ -1,5 +1,3 @@
include ../../common.mk
SRCS_ASM = \
start.S \
mem.S \
@@ -13,41 +11,4 @@ SRCS_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
include ../common.mk

View File

@@ -1,102 +1,116 @@
# save all General-Purpose(GP) registers to context
#define LOAD lw
#define STORE sw
#define SIZE_REG 4
# Save all General-Purpose(GP) registers to context.
# struct context *base = &ctx_task;
# base->ra = ra;
# ......
# These GP registers to be saved don't include gp
# and tp, because they are not caller-saved or
# callee-saved. These two registers are often used
# for special purpose. For example, in RVOS, 'tp'
# (aka "thread pointer") is used to store hartid,
# which is a global value and would not be changed
# during context-switch.
.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)
STORE ra, 0*SIZE_REG(\base)
STORE sp, 1*SIZE_REG(\base)
STORE t0, 4*SIZE_REG(\base)
STORE t1, 5*SIZE_REG(\base)
STORE t2, 6*SIZE_REG(\base)
STORE s0, 7*SIZE_REG(\base)
STORE s1, 8*SIZE_REG(\base)
STORE a0, 9*SIZE_REG(\base)
STORE a1, 10*SIZE_REG(\base)
STORE a2, 11*SIZE_REG(\base)
STORE a3, 12*SIZE_REG(\base)
STORE a4, 13*SIZE_REG(\base)
STORE a5, 14*SIZE_REG(\base)
STORE a6, 15*SIZE_REG(\base)
STORE a7, 16*SIZE_REG(\base)
STORE s2, 17*SIZE_REG(\base)
STORE s3, 18*SIZE_REG(\base)
STORE s4, 19*SIZE_REG(\base)
STORE s5, 20*SIZE_REG(\base)
STORE s6, 21*SIZE_REG(\base)
STORE s7, 22*SIZE_REG(\base)
STORE s8, 23*SIZE_REG(\base)
STORE s9, 24*SIZE_REG(\base)
STORE s10, 25*SIZE_REG(\base)
STORE s11, 26*SIZE_REG(\base)
STORE t3, 27*SIZE_REG(\base)
STORE t4, 28*SIZE_REG(\base)
STORE t5, 29*SIZE_REG(\base)
# we don't save t6 here, due to we have used
# it as base, we have to save t6 in an extra step
# outside of reg_save
.endm
# restore all General-Purpose(GP) registers from the context
# except gp & tp.
# 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)
LOAD ra, 0*SIZE_REG(\base)
LOAD sp, 1*SIZE_REG(\base)
LOAD t0, 4*SIZE_REG(\base)
LOAD t1, 5*SIZE_REG(\base)
LOAD t2, 6*SIZE_REG(\base)
LOAD s0, 7*SIZE_REG(\base)
LOAD s1, 8*SIZE_REG(\base)
LOAD a0, 9*SIZE_REG(\base)
LOAD a1, 10*SIZE_REG(\base)
LOAD a2, 11*SIZE_REG(\base)
LOAD a3, 12*SIZE_REG(\base)
LOAD a4, 13*SIZE_REG(\base)
LOAD a5, 14*SIZE_REG(\base)
LOAD a6, 15*SIZE_REG(\base)
LOAD a7, 16*SIZE_REG(\base)
LOAD s2, 17*SIZE_REG(\base)
LOAD s3, 18*SIZE_REG(\base)
LOAD s4, 19*SIZE_REG(\base)
LOAD s5, 20*SIZE_REG(\base)
LOAD s6, 21*SIZE_REG(\base)
LOAD s7, 22*SIZE_REG(\base)
LOAD s8, 23*SIZE_REG(\base)
LOAD s9, 24*SIZE_REG(\base)
LOAD s10, 25*SIZE_REG(\base)
LOAD s11, 26*SIZE_REG(\base)
LOAD t3, 27*SIZE_REG(\base)
LOAD t4, 28*SIZE_REG(\base)
LOAD t5, 29*SIZE_REG(\base)
LOAD t6, 30*SIZE_REG(\base)
.endm
# Something to note about save/restore:
# - We use mscratch to hold a pointer to context of previous task
# - We use mscratch to hold a pointer to context of current 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.
# Note: CSRs(mscratch) can not be used as 'base' due to load/restore
# instruction only accept general purpose registers.
.text
# void switch_to(struct context *next);
# a0: pointer to the context of the next task
.globl switch_to
.align 4
.balign 4
switch_to:
csrrw t6, mscratch, t6 # swap t6 and mscratch
beqz t6, 1f # Notice: previous task may be NULL
beqz t6, 1f # Note: the first time switch_to() is
# called, mscratch is initialized as zero
# (in sched_init()), which makes t6 zero,
# and that's the special case we have to
# handle with t6
reg_save t6 # save context of prev task
# Save the actual t6 register, which we swapped into
# mscratch
mv t5, t6 # t5 points to the context of current task
csrr t6, mscratch # read t6 back from mscratch
sw t6, 120(t5) # save t6 with t5 as base
mv t5, t6 # t5 points to the context of current task
csrr t6, mscratch # read t6 back from mscratch
STORE t6, 30*SIZE_REG(t5) # save t6 with t5 as base
1:
# switch mscratch to point to the context of the next task

View File

@@ -1,30 +1,32 @@
#define SIZE_PTR .word
.section .rodata
.global HEAP_START
HEAP_START: .word _heap_start
HEAP_START: SIZE_PTR _heap_start
.global HEAP_SIZE
HEAP_SIZE: .word _heap_size
HEAP_SIZE: SIZE_PTR _heap_size
.global TEXT_START
TEXT_START: .word _text_start
TEXT_START: SIZE_PTR _text_start
.global TEXT_END
TEXT_END: .word _text_end
TEXT_END: SIZE_PTR _text_end
.global DATA_START
DATA_START: .word _data_start
DATA_START: SIZE_PTR _data_start
.global DATA_END
DATA_END: .word _data_end
DATA_END: SIZE_PTR _data_end
.global RODATA_START
RODATA_START: .word _rodata_start
RODATA_START: SIZE_PTR _rodata_start
.global RODATA_END
RODATA_END: .word _rodata_end
RODATA_END: SIZE_PTR _rodata_end
.global BSS_START
BSS_START: .word _bss_start
BSS_START: SIZE_PTR _bss_start
.global BSS_END
BSS_END: .word _bss_end
BSS_END: SIZE_PTR _bss_end

View File

@@ -3,12 +3,14 @@
* Linker script for outputting to RVOS
*/
#include "platform.h"
/*
* 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.
* RISC-V target. We will further refine this by using -march
* and -mabi when calling gcc.
*/
OUTPUT_ARCH( "riscv" )
@@ -47,7 +49,7 @@ ENTRY( _start )
*/
MEMORY
{
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = LENGTH_RAM
}
/*

View File

@@ -3,24 +3,24 @@
/*
* 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;
extern ptr_t TEXT_START;
extern ptr_t TEXT_END;
extern ptr_t DATA_START;
extern ptr_t DATA_END;
extern ptr_t RODATA_START;
extern ptr_t RODATA_END;
extern ptr_t BSS_START;
extern ptr_t BSS_END;
extern ptr_t HEAP_START;
extern ptr_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 ptr_t _alloc_start = 0;
static ptr_t _alloc_end = 0;
static uint32_t _num_pages = 0;
#define PAGE_SIZE 4096
@@ -70,35 +70,65 @@ static inline int _is_last(struct Page *page)
/*
* align the address to the border of page(4K)
*/
static inline uint32_t _align_page(uint32_t address)
static inline ptr_t _align_page(ptr_t address)
{
uint32_t order = (1 << PAGE_ORDER) - 1;
ptr_t order = (1 << PAGE_ORDER) - 1;
return (address + order) & (~order);
}
/*
* ______________________________HEAP_SIZE_______________________________
* / ___num_reserved_pages___ ______________num_pages______________ \
* / / \ / \ \
* |---|<--Page-->|<--Page-->|...|<--Page-->|<--Page-->|......|<--Page-->|---|
* A A A A A
* | | | | |
* | | | | _memory_end
* | | | |
* | _heap_start_aligned _alloc_start _alloc_end
* HEAP_START(BSS_END)
*
* Note: _alloc_end may equal to _memory_end.
*/
void page_init()
{
ptr_t _heap_start_aligned = _align_page(HEAP_START);
/*
* 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)
* We reserved some Pages to hold the Page structures.
* The number of reserved pages depends on the LENGTH_RAM.
* For simplicity, the space we reserve here is just an approximation,
* assuming that it can accommodate the maximum LENGTH_RAM.
* We assume LENGTH_RAM should not be too small, ideally no less
* than 16M (i.e. PAGE_SIZE * PAGE_SIZE).
*/
_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);
uint32_t num_reserved_pages = LENGTH_RAM / (PAGE_SIZE * PAGE_SIZE);
_num_pages = (HEAP_SIZE - (_heap_start_aligned - HEAP_START))/ PAGE_SIZE - num_reserved_pages;
printf("HEAP_START = %p(aligned to %p), HEAP_SIZE = 0x%lx,\n"
"num of reserved pages = %d, num of pages to be allocated for heap = %d\n",
HEAP_START, _heap_start_aligned, HEAP_SIZE,
num_reserved_pages, _num_pages);
/*
* We use HEAP_START, not _heap_start_aligned as begin address for
* allocating struct Page, because we have no requirement of alignment
* for position of struct Page.
*/
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_start = _heap_start_aligned + num_reserved_pages * 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);
printf("TEXT: %p -> %p\n", TEXT_START, TEXT_END);
printf("RODATA: %p -> %p\n", RODATA_START, RODATA_END);
printf("DATA: %p -> %p\n", DATA_START, DATA_END);
printf("BSS: %p -> %p\n", BSS_START, BSS_END);
printf("HEAP: %p -> %p\n", _alloc_start, _alloc_end);
}
/*
@@ -110,15 +140,15 @@ 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++) {
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++) {
struct Page *page_j = page_i + 1;
for (int j = i + 1; j < (i + npages); j++) {
if (!_is_free(page_j)) {
found = 0;
break;
@@ -155,12 +185,12 @@ void page_free(void *p)
/*
* Assert (TBD) if p is invalid
*/
if (!p || (uint32_t)p >= _alloc_end) {
if (!p || (ptr_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;
page += ((ptr_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)) {
@@ -176,14 +206,14 @@ void page_free(void *p)
void page_test()
{
void *p = page_alloc(2);
printf("p = 0x%x\n", p);
printf("p = %p\n", p);
//page_free(p);
void *p2 = page_alloc(7);
printf("p2 = 0x%x\n", p2);
printf("p2 = %p\n", p2);
page_free(p2);
void *p3 = page_alloc(4);
printf("p3 = 0x%x\n", p3);
printf("p3 = %p\n", p3);
}

View File

@@ -5,16 +5,19 @@
* 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
/* used in os.ld */
#define LENGTH_RAM 128*1024*1024
/*
* MemoryMap
* see https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c, virt_memmap[]
* see https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c, virt_memmap[]
* 0x00001000 -- boot ROM, provided by qemu
* 0x02000000 -- CLINT
* 0x0C000000 -- PLIC

View File

@@ -5,7 +5,11 @@ extern void switch_to(struct context *next);
#define MAX_TASKS 10
#define STACK_SIZE 1024
uint8_t task_stack[MAX_TASKS][STACK_SIZE];
/*
* In the standard RISC-V calling convention, the stack pointer sp
* is always 16-byte aligned.
*/
uint8_t __attribute__((aligned(16))) task_stack[MAX_TASKS][STACK_SIZE];
struct context ctx_tasks[MAX_TASKS];
/*
@@ -43,7 +47,7 @@ void schedule()
/*
* DESCRIPTION
* Create a task.
* - start_routin: task routune entry
* - start_routin: task routine entry
* RETURN VALUE
* 0: success
* -1: if error occured
@@ -51,7 +55,7 @@ void schedule()
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].sp = (reg_t) &task_stack[_top][STACK_SIZE];
ctx_tasks[_top].ra = (reg_t) start_routin;
_top++;
return 0;

View File

@@ -36,6 +36,9 @@ park:
wfi
j park
# In the standard RISC-V calling convention, the stack pointer sp
# is always 16-byte aligned.
.balign 16
stacks:
.skip STACK_SIZE * MAXNUM_CPU # allocate space for all the harts stacks

View File

@@ -7,8 +7,9 @@ typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
/*
* RISCV32: register is 32bits width
*/
* Register Width
*/
typedef uint32_t reg_t;
typedef uint32_t ptr_t;
#endif /* __TYPES_H__ */

View File

@@ -32,7 +32,7 @@
/*
* POWER UP DEFAULTS
* IER = 0: TX/RX holding register interrupts are bith disabled
* IER = 0: TX/RX holding register interrupts are both disabled
* ISR = 1: no interrupt penting
* LCR = 0
* MCR = 0

View File

@@ -1,5 +1,3 @@
include ../../common.mk
SRCS_ASM = \
start.S \
mem.S \
@@ -14,41 +12,4 @@ SRCS_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
include ../common.mk

View File

@@ -1,92 +1,102 @@
# save all General-Purpose(GP) registers to context
#define LOAD lw
#define STORE sw
#define SIZE_REG 4
# Save all General-Purpose(GP) registers to context.
# struct context *base = &ctx_task;
# base->ra = ra;
# ......
# These GP registers to be saved don't include gp
# and tp, because they are not caller-saved or
# callee-saved. These two registers are often used
# for special purpose. For example, in RVOS, 'tp'
# (aka "thread pointer") is used to store hartid,
# which is a global value and would not be changed
# during context-switch.
.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)
STORE ra, 0*SIZE_REG(\base)
STORE sp, 1*SIZE_REG(\base)
STORE t0, 4*SIZE_REG(\base)
STORE t1, 5*SIZE_REG(\base)
STORE t2, 6*SIZE_REG(\base)
STORE s0, 7*SIZE_REG(\base)
STORE s1, 8*SIZE_REG(\base)
STORE a0, 9*SIZE_REG(\base)
STORE a1, 10*SIZE_REG(\base)
STORE a2, 11*SIZE_REG(\base)
STORE a3, 12*SIZE_REG(\base)
STORE a4, 13*SIZE_REG(\base)
STORE a5, 14*SIZE_REG(\base)
STORE a6, 15*SIZE_REG(\base)
STORE a7, 16*SIZE_REG(\base)
STORE s2, 17*SIZE_REG(\base)
STORE s3, 18*SIZE_REG(\base)
STORE s4, 19*SIZE_REG(\base)
STORE s5, 20*SIZE_REG(\base)
STORE s6, 21*SIZE_REG(\base)
STORE s7, 22*SIZE_REG(\base)
STORE s8, 23*SIZE_REG(\base)
STORE s9, 24*SIZE_REG(\base)
STORE s10, 25*SIZE_REG(\base)
STORE s11, 26*SIZE_REG(\base)
STORE t3, 27*SIZE_REG(\base)
STORE t4, 28*SIZE_REG(\base)
STORE t5, 29*SIZE_REG(\base)
# we don't save t6 here, due to we have used
# it as base, we have to save t6 in an extra step
# outside of reg_save
.endm
# restore all General-Purpose(GP) registers from the context
# except gp & tp.
# 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)
LOAD ra, 0*SIZE_REG(\base)
LOAD sp, 1*SIZE_REG(\base)
LOAD t0, 4*SIZE_REG(\base)
LOAD t1, 5*SIZE_REG(\base)
LOAD t2, 6*SIZE_REG(\base)
LOAD s0, 7*SIZE_REG(\base)
LOAD s1, 8*SIZE_REG(\base)
LOAD a0, 9*SIZE_REG(\base)
LOAD a1, 10*SIZE_REG(\base)
LOAD a2, 11*SIZE_REG(\base)
LOAD a3, 12*SIZE_REG(\base)
LOAD a4, 13*SIZE_REG(\base)
LOAD a5, 14*SIZE_REG(\base)
LOAD a6, 15*SIZE_REG(\base)
LOAD a7, 16*SIZE_REG(\base)
LOAD s2, 17*SIZE_REG(\base)
LOAD s3, 18*SIZE_REG(\base)
LOAD s4, 19*SIZE_REG(\base)
LOAD s5, 20*SIZE_REG(\base)
LOAD s6, 21*SIZE_REG(\base)
LOAD s7, 22*SIZE_REG(\base)
LOAD s8, 23*SIZE_REG(\base)
LOAD s9, 24*SIZE_REG(\base)
LOAD s10, 25*SIZE_REG(\base)
LOAD s11, 26*SIZE_REG(\base)
LOAD t3, 27*SIZE_REG(\base)
LOAD t4, 28*SIZE_REG(\base)
LOAD t5, 29*SIZE_REG(\base)
LOAD t6, 30*SIZE_REG(\base)
.endm
# Something to note about save/restore:
# - We use mscratch to hold a pointer to context of previous task
# - We use mscratch to hold a pointer to context of current 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.
# Note: CSRs(mscratch) can not be used as 'base' due to load/restore
# instruction only accept general purpose registers.
.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
.balign 4
trap_vector:
# save context(registers).
csrrw t6, mscratch, t6 # swap t6 and mscratch
@@ -94,9 +104,9 @@ trap_vector:
# Save the actual t6 register, which we swapped into
# mscratch
mv t5, t6 # t5 points to the context of current task
csrr t6, mscratch # read t6 back from mscratch
sw t6, 120(t5) # save t6 with t5 as base
mv t5, t6 # t5 points to the context of current task
csrr t6, mscratch # read t6 back from mscratch
STORE t6, 30*SIZE_REG(t5) # save t6 with t5 as base
# Restore the context pointer into mscratch
csrw mscratch, t5
@@ -119,17 +129,21 @@ trap_vector:
# void switch_to(struct context *next);
# a0: pointer to the context of the next task
.globl switch_to
.align 4
.balign 4
switch_to:
csrrw t6, mscratch, t6 # swap t6 and mscratch
beqz t6, 1f # Notice: previous task may be NULL
beqz t6, 1f # Note: the first time switch_to() is
# called, mscratch is initialized as zero
# (in sched_init()), which makes t6 zero,
# and that's the special case we have to
# handle with t6
reg_save t6 # save context of prev task
# Save the actual t6 register, which we swapped into
# mscratch
mv t5, t6 # t5 points to the context of current task
csrr t6, mscratch # read t6 back from mscratch
sw t6, 120(t5) # save t6 with t5 as base
mv t5, t6 # t5 points to the context of current task
csrr t6, mscratch # read t6 back from mscratch
STORE t6, 30*SIZE_REG(t5) # save t6 with t5 as base
1:
# switch mscratch to point to the context of the next task

View File

@@ -1,30 +1,32 @@
#define SIZE_PTR .word
.section .rodata
.global HEAP_START
HEAP_START: .word _heap_start
HEAP_START: SIZE_PTR _heap_start
.global HEAP_SIZE
HEAP_SIZE: .word _heap_size
HEAP_SIZE: SIZE_PTR _heap_size
.global TEXT_START
TEXT_START: .word _text_start
TEXT_START: SIZE_PTR _text_start
.global TEXT_END
TEXT_END: .word _text_end
TEXT_END: SIZE_PTR _text_end
.global DATA_START
DATA_START: .word _data_start
DATA_START: SIZE_PTR _data_start
.global DATA_END
DATA_END: .word _data_end
DATA_END: SIZE_PTR _data_end
.global RODATA_START
RODATA_START: .word _rodata_start
RODATA_START: SIZE_PTR _rodata_start
.global RODATA_END
RODATA_END: .word _rodata_end
RODATA_END: SIZE_PTR _rodata_end
.global BSS_START
BSS_START: .word _bss_start
BSS_START: SIZE_PTR _bss_start
.global BSS_END
BSS_END: .word _bss_end
BSS_END: SIZE_PTR _bss_end

View File

@@ -3,12 +3,14 @@
* Linker script for outputting to RVOS
*/
#include "platform.h"
/*
* 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.
* RISC-V target. We will further refine this by using -march
* and -mabi when calling gcc.
*/
OUTPUT_ARCH( "riscv" )
@@ -47,7 +49,7 @@ ENTRY( _start )
*/
MEMORY
{
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = LENGTH_RAM
}
/*

View File

@@ -3,24 +3,24 @@
/*
* 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;
extern ptr_t TEXT_START;
extern ptr_t TEXT_END;
extern ptr_t DATA_START;
extern ptr_t DATA_END;
extern ptr_t RODATA_START;
extern ptr_t RODATA_END;
extern ptr_t BSS_START;
extern ptr_t BSS_END;
extern ptr_t HEAP_START;
extern ptr_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 ptr_t _alloc_start = 0;
static ptr_t _alloc_end = 0;
static uint32_t _num_pages = 0;
#define PAGE_SIZE 4096
@@ -70,35 +70,65 @@ static inline int _is_last(struct Page *page)
/*
* align the address to the border of page(4K)
*/
static inline uint32_t _align_page(uint32_t address)
static inline ptr_t _align_page(ptr_t address)
{
uint32_t order = (1 << PAGE_ORDER) - 1;
ptr_t order = (1 << PAGE_ORDER) - 1;
return (address + order) & (~order);
}
/*
* ______________________________HEAP_SIZE_______________________________
* / ___num_reserved_pages___ ______________num_pages______________ \
* / / \ / \ \
* |---|<--Page-->|<--Page-->|...|<--Page-->|<--Page-->|......|<--Page-->|---|
* A A A A A
* | | | | |
* | | | | _memory_end
* | | | |
* | _heap_start_aligned _alloc_start _alloc_end
* HEAP_START(BSS_END)
*
* Note: _alloc_end may equal to _memory_end.
*/
void page_init()
{
ptr_t _heap_start_aligned = _align_page(HEAP_START);
/*
* 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)
* We reserved some Pages to hold the Page structures.
* The number of reserved pages depends on the LENGTH_RAM.
* For simplicity, the space we reserve here is just an approximation,
* assuming that it can accommodate the maximum LENGTH_RAM.
* We assume LENGTH_RAM should not be too small, ideally no less
* than 16M (i.e. PAGE_SIZE * PAGE_SIZE).
*/
_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);
uint32_t num_reserved_pages = LENGTH_RAM / (PAGE_SIZE * PAGE_SIZE);
_num_pages = (HEAP_SIZE - (_heap_start_aligned - HEAP_START))/ PAGE_SIZE - num_reserved_pages;
printf("HEAP_START = %p(aligned to %p), HEAP_SIZE = 0x%lx,\n"
"num of reserved pages = %d, num of pages to be allocated for heap = %d\n",
HEAP_START, _heap_start_aligned, HEAP_SIZE,
num_reserved_pages, _num_pages);
/*
* We use HEAP_START, not _heap_start_aligned as begin address for
* allocating struct Page, because we have no requirement of alignment
* for position of struct Page.
*/
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_start = _heap_start_aligned + num_reserved_pages * 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);
printf("TEXT: %p -> %p\n", TEXT_START, TEXT_END);
printf("RODATA: %p -> %p\n", RODATA_START, RODATA_END);
printf("DATA: %p -> %p\n", DATA_START, DATA_END);
printf("BSS: %p -> %p\n", BSS_START, BSS_END);
printf("HEAP: %p -> %p\n", _alloc_start, _alloc_end);
}
/*
@@ -110,15 +140,15 @@ 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++) {
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++) {
struct Page *page_j = page_i + 1;
for (int j = i + 1; j < (i + npages); j++) {
if (!_is_free(page_j)) {
found = 0;
break;
@@ -155,12 +185,12 @@ void page_free(void *p)
/*
* Assert (TBD) if p is invalid
*/
if (!p || (uint32_t)p >= _alloc_end) {
if (!p || (ptr_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;
page += ((ptr_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)) {
@@ -176,14 +206,14 @@ void page_free(void *p)
void page_test()
{
void *p = page_alloc(2);
printf("p = 0x%x\n", p);
printf("p = %p\n", p);
//page_free(p);
void *p2 = page_alloc(7);
printf("p2 = 0x%x\n", p2);
printf("p2 = %p\n", p2);
page_free(p2);
void *p3 = page_alloc(4);
printf("p3 = 0x%x\n", p3);
printf("p3 = %p\n", p3);
}

View File

@@ -5,16 +5,19 @@
* 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
/* used in os.ld */
#define LENGTH_RAM 128*1024*1024
/*
* MemoryMap
* see https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c, virt_memmap[]
* see https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c, virt_memmap[]
* 0x00001000 -- boot ROM, provided by qemu
* 0x02000000 -- CLINT
* 0x0C000000 -- PLIC

View File

@@ -92,6 +92,10 @@ static inline void w_mie(reg_t x)
asm volatile("csrw mie, %0" : : "r" (x));
}
/* Machine-mode Cause Masks */
#define MCAUSE_MASK_INTERRUPT (reg_t)0x80000000
#define MCAUSE_MASK_ECODE (reg_t)0x7FFFFFFF
static inline reg_t r_mcause()
{
reg_t x;

View File

@@ -5,7 +5,11 @@ extern void switch_to(struct context *next);
#define MAX_TASKS 10
#define STACK_SIZE 1024
uint8_t task_stack[MAX_TASKS][STACK_SIZE];
/*
* In the standard RISC-V calling convention, the stack pointer sp
* is always 16-byte aligned.
*/
uint8_t __attribute__((aligned(16))) task_stack[MAX_TASKS][STACK_SIZE];
struct context ctx_tasks[MAX_TASKS];
/*
@@ -38,7 +42,7 @@ void schedule()
/*
* DESCRIPTION
* Create a task.
* - start_routin: task routune entry
* - start_routin: task routine entry
* RETURN VALUE
* 0: success
* -1: if error occured
@@ -46,7 +50,7 @@ void schedule()
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].sp = (reg_t) &task_stack[_top][STACK_SIZE];
ctx_tasks[_top].ra = (reg_t) start_routin;
_top++;
return 0;

View File

@@ -36,6 +36,9 @@ park:
wfi
j park
# In the standard RISC-V calling convention, the stack pointer sp
# is always 16-byte aligned.
.balign 16
stacks:
.skip STACK_SIZE * MAXNUM_CPU # allocate space for all the harts stacks

View File

@@ -13,9 +13,9 @@ void trap_init()
reg_t trap_handler(reg_t epc, reg_t cause)
{
reg_t return_pc = epc;
reg_t cause_code = cause & 0xfff;
reg_t cause_code = cause & MCAUSE_MASK_ECODE;
if (cause & 0x80000000) {
if (cause & MCAUSE_MASK_INTERRUPT) {
/* Asynchronous trap - interrupt */
switch (cause_code) {
case 3:
@@ -28,12 +28,12 @@ reg_t trap_handler(reg_t epc, reg_t cause)
uart_puts("external interruption!\n");
break;
default:
uart_puts("unknown async exception!\n");
printf("Unknown async exception! Code = %ld\n", cause_code);
break;
}
} else {
/* Synchronous trap - exception */
printf("Sync exceptions!, code = %d\n", cause_code);
printf("Sync exceptions! Code = %ld\n", cause_code);
panic("OOPS! What can I do!");
//return_pc += 4;
}

View File

@@ -7,8 +7,9 @@ typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
/*
* RISCV32: register is 32bits width
*/
* Register Width
*/
typedef uint32_t reg_t;
typedef uint32_t ptr_t;
#endif /* __TYPES_H__ */

View File

@@ -32,7 +32,7 @@
/*
* POWER UP DEFAULTS
* IER = 0: TX/RX holding register interrupts are bith disabled
* IER = 0: TX/RX holding register interrupts are both disabled
* ISR = 1: no interrupt penting
* LCR = 0
* MCR = 0

View File

@@ -1,5 +1,3 @@
include ../../common.mk
SRCS_ASM = \
start.S \
mem.S \
@@ -15,41 +13,4 @@ SRCS_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
include ../common.mk

View File

@@ -1,92 +1,102 @@
# save all General-Purpose(GP) registers to context
#define LOAD lw
#define STORE sw
#define SIZE_REG 4
# Save all General-Purpose(GP) registers to context.
# struct context *base = &ctx_task;
# base->ra = ra;
# ......
# These GP registers to be saved don't include gp
# and tp, because they are not caller-saved or
# callee-saved. These two registers are often used
# for special purpose. For example, in RVOS, 'tp'
# (aka "thread pointer") is used to store hartid,
# which is a global value and would not be changed
# during context-switch.
.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)
STORE ra, 0*SIZE_REG(\base)
STORE sp, 1*SIZE_REG(\base)
STORE t0, 4*SIZE_REG(\base)
STORE t1, 5*SIZE_REG(\base)
STORE t2, 6*SIZE_REG(\base)
STORE s0, 7*SIZE_REG(\base)
STORE s1, 8*SIZE_REG(\base)
STORE a0, 9*SIZE_REG(\base)
STORE a1, 10*SIZE_REG(\base)
STORE a2, 11*SIZE_REG(\base)
STORE a3, 12*SIZE_REG(\base)
STORE a4, 13*SIZE_REG(\base)
STORE a5, 14*SIZE_REG(\base)
STORE a6, 15*SIZE_REG(\base)
STORE a7, 16*SIZE_REG(\base)
STORE s2, 17*SIZE_REG(\base)
STORE s3, 18*SIZE_REG(\base)
STORE s4, 19*SIZE_REG(\base)
STORE s5, 20*SIZE_REG(\base)
STORE s6, 21*SIZE_REG(\base)
STORE s7, 22*SIZE_REG(\base)
STORE s8, 23*SIZE_REG(\base)
STORE s9, 24*SIZE_REG(\base)
STORE s10, 25*SIZE_REG(\base)
STORE s11, 26*SIZE_REG(\base)
STORE t3, 27*SIZE_REG(\base)
STORE t4, 28*SIZE_REG(\base)
STORE t5, 29*SIZE_REG(\base)
# we don't save t6 here, due to we have used
# it as base, we have to save t6 in an extra step
# outside of reg_save
.endm
# restore all General-Purpose(GP) registers from the context
# except gp & tp.
# 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)
LOAD ra, 0*SIZE_REG(\base)
LOAD sp, 1*SIZE_REG(\base)
LOAD t0, 4*SIZE_REG(\base)
LOAD t1, 5*SIZE_REG(\base)
LOAD t2, 6*SIZE_REG(\base)
LOAD s0, 7*SIZE_REG(\base)
LOAD s1, 8*SIZE_REG(\base)
LOAD a0, 9*SIZE_REG(\base)
LOAD a1, 10*SIZE_REG(\base)
LOAD a2, 11*SIZE_REG(\base)
LOAD a3, 12*SIZE_REG(\base)
LOAD a4, 13*SIZE_REG(\base)
LOAD a5, 14*SIZE_REG(\base)
LOAD a6, 15*SIZE_REG(\base)
LOAD a7, 16*SIZE_REG(\base)
LOAD s2, 17*SIZE_REG(\base)
LOAD s3, 18*SIZE_REG(\base)
LOAD s4, 19*SIZE_REG(\base)
LOAD s5, 20*SIZE_REG(\base)
LOAD s6, 21*SIZE_REG(\base)
LOAD s7, 22*SIZE_REG(\base)
LOAD s8, 23*SIZE_REG(\base)
LOAD s9, 24*SIZE_REG(\base)
LOAD s10, 25*SIZE_REG(\base)
LOAD s11, 26*SIZE_REG(\base)
LOAD t3, 27*SIZE_REG(\base)
LOAD t4, 28*SIZE_REG(\base)
LOAD t5, 29*SIZE_REG(\base)
LOAD t6, 30*SIZE_REG(\base)
.endm
# Something to note about save/restore:
# - We use mscratch to hold a pointer to context of previous task
# - We use mscratch to hold a pointer to context of current 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.
# Note: CSRs(mscratch) can not be used as 'base' due to load/restore
# instruction only accept general purpose registers.
.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
.balign 4
trap_vector:
# save context(registers).
csrrw t6, mscratch, t6 # swap t6 and mscratch
@@ -94,9 +104,9 @@ trap_vector:
# Save the actual t6 register, which we swapped into
# mscratch
mv t5, t6 # t5 points to the context of current task
csrr t6, mscratch # read t6 back from mscratch
sw t6, 120(t5) # save t6 with t5 as base
mv t5, t6 # t5 points to the context of current task
csrr t6, mscratch # read t6 back from mscratch
STORE t6, 30*SIZE_REG(t5) # save t6 with t5 as base
# Restore the context pointer into mscratch
csrw mscratch, t5
@@ -119,17 +129,21 @@ trap_vector:
# void switch_to(struct context *next);
# a0: pointer to the context of the next task
.globl switch_to
.align 4
.balign 4
switch_to:
csrrw t6, mscratch, t6 # swap t6 and mscratch
beqz t6, 1f # Notice: previous task may be NULL
beqz t6, 1f # Note: the first time switch_to() is
# called, mscratch is initialized as zero
# (in sched_init()), which makes t6 zero,
# and that's the special case we have to
# handle with t6
reg_save t6 # save context of prev task
# Save the actual t6 register, which we swapped into
# mscratch
mv t5, t6 # t5 points to the context of current task
csrr t6, mscratch # read t6 back from mscratch
sw t6, 120(t5) # save t6 with t5 as base
mv t5, t6 # t5 points to the context of current task
csrr t6, mscratch # read t6 back from mscratch
STORE t6, 30*SIZE_REG(t5) # save t6 with t5 as base
1:
# switch mscratch to point to the context of the next task

View File

@@ -1,30 +1,32 @@
#define SIZE_PTR .word
.section .rodata
.global HEAP_START
HEAP_START: .word _heap_start
HEAP_START: SIZE_PTR _heap_start
.global HEAP_SIZE
HEAP_SIZE: .word _heap_size
HEAP_SIZE: SIZE_PTR _heap_size
.global TEXT_START
TEXT_START: .word _text_start
TEXT_START: SIZE_PTR _text_start
.global TEXT_END
TEXT_END: .word _text_end
TEXT_END: SIZE_PTR _text_end
.global DATA_START
DATA_START: .word _data_start
DATA_START: SIZE_PTR _data_start
.global DATA_END
DATA_END: .word _data_end
DATA_END: SIZE_PTR _data_end
.global RODATA_START
RODATA_START: .word _rodata_start
RODATA_START: SIZE_PTR _rodata_start
.global RODATA_END
RODATA_END: .word _rodata_end
RODATA_END: SIZE_PTR _rodata_end
.global BSS_START
BSS_START: .word _bss_start
BSS_START: SIZE_PTR _bss_start
.global BSS_END
BSS_END: .word _bss_end
BSS_END: SIZE_PTR _bss_end

View File

@@ -3,12 +3,14 @@
* Linker script for outputting to RVOS
*/
#include "platform.h"
/*
* 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.
* RISC-V target. We will further refine this by using -march
* and -mabi when calling gcc.
*/
OUTPUT_ARCH( "riscv" )
@@ -47,7 +49,7 @@ ENTRY( _start )
*/
MEMORY
{
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = LENGTH_RAM
}
/*

View File

@@ -3,24 +3,24 @@
/*
* 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;
extern ptr_t TEXT_START;
extern ptr_t TEXT_END;
extern ptr_t DATA_START;
extern ptr_t DATA_END;
extern ptr_t RODATA_START;
extern ptr_t RODATA_END;
extern ptr_t BSS_START;
extern ptr_t BSS_END;
extern ptr_t HEAP_START;
extern ptr_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 ptr_t _alloc_start = 0;
static ptr_t _alloc_end = 0;
static uint32_t _num_pages = 0;
#define PAGE_SIZE 4096
@@ -70,35 +70,65 @@ static inline int _is_last(struct Page *page)
/*
* align the address to the border of page(4K)
*/
static inline uint32_t _align_page(uint32_t address)
static inline ptr_t _align_page(ptr_t address)
{
uint32_t order = (1 << PAGE_ORDER) - 1;
ptr_t order = (1 << PAGE_ORDER) - 1;
return (address + order) & (~order);
}
/*
* ______________________________HEAP_SIZE_______________________________
* / ___num_reserved_pages___ ______________num_pages______________ \
* / / \ / \ \
* |---|<--Page-->|<--Page-->|...|<--Page-->|<--Page-->|......|<--Page-->|---|
* A A A A A
* | | | | |
* | | | | _memory_end
* | | | |
* | _heap_start_aligned _alloc_start _alloc_end
* HEAP_START(BSS_END)
*
* Note: _alloc_end may equal to _memory_end.
*/
void page_init()
{
ptr_t _heap_start_aligned = _align_page(HEAP_START);
/*
* 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)
* We reserved some Pages to hold the Page structures.
* The number of reserved pages depends on the LENGTH_RAM.
* For simplicity, the space we reserve here is just an approximation,
* assuming that it can accommodate the maximum LENGTH_RAM.
* We assume LENGTH_RAM should not be too small, ideally no less
* than 16M (i.e. PAGE_SIZE * PAGE_SIZE).
*/
_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);
uint32_t num_reserved_pages = LENGTH_RAM / (PAGE_SIZE * PAGE_SIZE);
_num_pages = (HEAP_SIZE - (_heap_start_aligned - HEAP_START))/ PAGE_SIZE - num_reserved_pages;
printf("HEAP_START = %p(aligned to %p), HEAP_SIZE = 0x%lx,\n"
"num of reserved pages = %d, num of pages to be allocated for heap = %d\n",
HEAP_START, _heap_start_aligned, HEAP_SIZE,
num_reserved_pages, _num_pages);
/*
* We use HEAP_START, not _heap_start_aligned as begin address for
* allocating struct Page, because we have no requirement of alignment
* for position of struct Page.
*/
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_start = _heap_start_aligned + num_reserved_pages * 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);
printf("TEXT: %p -> %p\n", TEXT_START, TEXT_END);
printf("RODATA: %p -> %p\n", RODATA_START, RODATA_END);
printf("DATA: %p -> %p\n", DATA_START, DATA_END);
printf("BSS: %p -> %p\n", BSS_START, BSS_END);
printf("HEAP: %p -> %p\n", _alloc_start, _alloc_end);
}
/*
@@ -110,15 +140,15 @@ 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++) {
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++) {
struct Page *page_j = page_i + 1;
for (int j = i + 1; j < (i + npages); j++) {
if (!_is_free(page_j)) {
found = 0;
break;
@@ -155,12 +185,12 @@ void page_free(void *p)
/*
* Assert (TBD) if p is invalid
*/
if (!p || (uint32_t)p >= _alloc_end) {
if (!p || (ptr_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;
page += ((ptr_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)) {
@@ -176,14 +206,14 @@ void page_free(void *p)
void page_test()
{
void *p = page_alloc(2);
printf("p = 0x%x\n", p);
printf("p = %p\n", p);
//page_free(p);
void *p2 = page_alloc(7);
printf("p2 = 0x%x\n", p2);
printf("p2 = %p\n", p2);
page_free(p2);
void *p3 = page_alloc(4);
printf("p3 = 0x%x\n", p3);
printf("p3 = %p\n", p3);
}

View File

@@ -12,6 +12,9 @@
*/
#define MAXNUM_CPU 8
/* used in os.ld */
#define LENGTH_RAM 128*1024*1024
/*
* MemoryMap
* see https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c, virt_memmap[]
@@ -43,7 +46,7 @@
* #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_PRIORITY_BASE 0x00
* #define VIRT_PLIC_PENDING_BASE 0x1000
* #define VIRT_PLIC_ENABLE_BASE 0x2000
* #define VIRT_PLIC_ENABLE_STRIDE 0x80
@@ -55,7 +58,7 @@
#define PLIC_BASE 0x0c000000L
#define PLIC_PRIORITY(id) (PLIC_BASE + (id) * 4)
#define PLIC_PENDING(id) (PLIC_BASE + 0x1000 + ((id) / 32) * 4)
#define PLIC_MENABLE(hart) (PLIC_BASE + 0x2000 + (hart) * 0x80)
#define PLIC_MENABLE(hart, id) (PLIC_BASE + 0x2000 + (hart) * 0x80 + ((id) / 32) * 4)
#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)

View File

@@ -25,7 +25,7 @@ void plic_init(void)
* Each global interrupt can be enabled by setting the corresponding
* bit in the enables registers.
*/
*(uint32_t*)PLIC_MENABLE(hart)= (1 << UART0_IRQ);
*(uint32_t*)PLIC_MENABLE(hart, UART0_IRQ)= (1 << (UART0_IRQ % 32));
/*
* Set priority threshold for UART0.

View File

@@ -92,6 +92,10 @@ static inline void w_mie(reg_t x)
asm volatile("csrw mie, %0" : : "r" (x));
}
/* Machine-mode Cause Masks */
#define MCAUSE_MASK_INTERRUPT (reg_t)0x80000000
#define MCAUSE_MASK_ECODE (reg_t)0x7FFFFFFF
static inline reg_t r_mcause()
{
reg_t x;

View File

@@ -5,7 +5,11 @@ extern void switch_to(struct context *next);
#define MAX_TASKS 10
#define STACK_SIZE 1024
uint8_t task_stack[MAX_TASKS][STACK_SIZE];
/*
* In the standard RISC-V calling convention, the stack pointer sp
* is always 16-byte aligned.
*/
uint8_t __attribute__((aligned(16))) task_stack[MAX_TASKS][STACK_SIZE];
struct context ctx_tasks[MAX_TASKS];
/*
@@ -38,7 +42,7 @@ void schedule()
/*
* DESCRIPTION
* Create a task.
* - start_routin: task routune entry
* - start_routin: task routine entry
* RETURN VALUE
* 0: success
* -1: if error occured
@@ -46,7 +50,7 @@ void schedule()
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].sp = (reg_t) &task_stack[_top][STACK_SIZE];
ctx_tasks[_top].ra = (reg_t) start_routin;
_top++;
return 0;

View File

@@ -36,6 +36,9 @@ park:
wfi
j park
# In the standard RISC-V calling convention, the stack pointer sp
# is always 16-byte aligned.
.balign 16
stacks:
.skip STACK_SIZE * MAXNUM_CPU # allocate space for all the harts stacks

View File

@@ -29,9 +29,9 @@ void external_interrupt_handler()
reg_t trap_handler(reg_t epc, reg_t cause)
{
reg_t return_pc = epc;
reg_t cause_code = cause & 0xfff;
reg_t cause_code = cause & MCAUSE_MASK_ECODE;
if (cause & 0x80000000) {
if (cause & MCAUSE_MASK_INTERRUPT) {
/* Asynchronous trap - interrupt */
switch (cause_code) {
case 3:
@@ -45,12 +45,12 @@ reg_t trap_handler(reg_t epc, reg_t cause)
external_interrupt_handler();
break;
default:
uart_puts("unknown async exception!\n");
printf("Unknown async exception! Code = %ld\n", cause_code);
break;
}
} else {
/* Synchronous trap - exception */
printf("Sync exceptions!, code = %d\n", cause_code);
printf("Sync exceptions! Code = %ld\n", cause_code);
panic("OOPS! What can I do!");
//return_pc += 4;
}

View File

@@ -7,8 +7,9 @@ typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
/*
* RISCV32: register is 32bits width
*/
* Register Width
*/
typedef uint32_t reg_t;
typedef uint32_t ptr_t;
#endif /* __TYPES_H__ */

View File

@@ -32,7 +32,7 @@
/*
* POWER UP DEFAULTS
* IER = 0: TX/RX holding register interrupts are bith disabled
* IER = 0: TX/RX holding register interrupts are both disabled
* ISR = 1: no interrupt penting
* LCR = 0
* MCR = 0
@@ -125,11 +125,9 @@ void uart_puts(char *s)
int uart_getc(void)
{
if (uart_read_reg(LSR) & LSR_RX_READY){
return uart_read_reg(RHR);
} else {
return -1;
}
while (0 == (uart_read_reg(LSR) & LSR_RX_READY))
;
return uart_read_reg(RHR);
}
/*
@@ -137,13 +135,7 @@ int uart_getc(void)
*/
void uart_isr(void)
{
while (1) {
int c = uart_getc();
if (c == -1) {
break;
} else {
uart_putc((char)c);
uart_putc('\n');
}
}
uart_putc((char)uart_getc());
/* add a new line just to look better */
uart_putc('\n');
}

View File

@@ -1,5 +1,3 @@
include ../../common.mk
SRCS_ASM = \
start.S \
mem.S \
@@ -16,41 +14,4 @@ SRCS_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
include ../common.mk

View File

@@ -1,92 +1,102 @@
# save all General-Purpose(GP) registers to context
#define LOAD lw
#define STORE sw
#define SIZE_REG 4
# Save all General-Purpose(GP) registers to context.
# struct context *base = &ctx_task;
# base->ra = ra;
# ......
# These GP registers to be saved don't include gp
# and tp, because they are not caller-saved or
# callee-saved. These two registers are often used
# for special purpose. For example, in RVOS, 'tp'
# (aka "thread pointer") is used to store hartid,
# which is a global value and would not be changed
# during context-switch.
.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)
STORE ra, 0*SIZE_REG(\base)
STORE sp, 1*SIZE_REG(\base)
STORE t0, 4*SIZE_REG(\base)
STORE t1, 5*SIZE_REG(\base)
STORE t2, 6*SIZE_REG(\base)
STORE s0, 7*SIZE_REG(\base)
STORE s1, 8*SIZE_REG(\base)
STORE a0, 9*SIZE_REG(\base)
STORE a1, 10*SIZE_REG(\base)
STORE a2, 11*SIZE_REG(\base)
STORE a3, 12*SIZE_REG(\base)
STORE a4, 13*SIZE_REG(\base)
STORE a5, 14*SIZE_REG(\base)
STORE a6, 15*SIZE_REG(\base)
STORE a7, 16*SIZE_REG(\base)
STORE s2, 17*SIZE_REG(\base)
STORE s3, 18*SIZE_REG(\base)
STORE s4, 19*SIZE_REG(\base)
STORE s5, 20*SIZE_REG(\base)
STORE s6, 21*SIZE_REG(\base)
STORE s7, 22*SIZE_REG(\base)
STORE s8, 23*SIZE_REG(\base)
STORE s9, 24*SIZE_REG(\base)
STORE s10, 25*SIZE_REG(\base)
STORE s11, 26*SIZE_REG(\base)
STORE t3, 27*SIZE_REG(\base)
STORE t4, 28*SIZE_REG(\base)
STORE t5, 29*SIZE_REG(\base)
# we don't save t6 here, due to we have used
# it as base, we have to save t6 in an extra step
# outside of reg_save
.endm
# restore all General-Purpose(GP) registers from the context
# except gp & tp.
# 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)
LOAD ra, 0*SIZE_REG(\base)
LOAD sp, 1*SIZE_REG(\base)
LOAD t0, 4*SIZE_REG(\base)
LOAD t1, 5*SIZE_REG(\base)
LOAD t2, 6*SIZE_REG(\base)
LOAD s0, 7*SIZE_REG(\base)
LOAD s1, 8*SIZE_REG(\base)
LOAD a0, 9*SIZE_REG(\base)
LOAD a1, 10*SIZE_REG(\base)
LOAD a2, 11*SIZE_REG(\base)
LOAD a3, 12*SIZE_REG(\base)
LOAD a4, 13*SIZE_REG(\base)
LOAD a5, 14*SIZE_REG(\base)
LOAD a6, 15*SIZE_REG(\base)
LOAD a7, 16*SIZE_REG(\base)
LOAD s2, 17*SIZE_REG(\base)
LOAD s3, 18*SIZE_REG(\base)
LOAD s4, 19*SIZE_REG(\base)
LOAD s5, 20*SIZE_REG(\base)
LOAD s6, 21*SIZE_REG(\base)
LOAD s7, 22*SIZE_REG(\base)
LOAD s8, 23*SIZE_REG(\base)
LOAD s9, 24*SIZE_REG(\base)
LOAD s10, 25*SIZE_REG(\base)
LOAD s11, 26*SIZE_REG(\base)
LOAD t3, 27*SIZE_REG(\base)
LOAD t4, 28*SIZE_REG(\base)
LOAD t5, 29*SIZE_REG(\base)
LOAD t6, 30*SIZE_REG(\base)
.endm
# Something to note about save/restore:
# - We use mscratch to hold a pointer to context of previous task
# - We use mscratch to hold a pointer to context of current 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.
# Note: CSRs(mscratch) can not be used as 'base' due to load/restore
# instruction only accept general purpose registers.
.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
.balign 4
trap_vector:
# save context(registers).
csrrw t6, mscratch, t6 # swap t6 and mscratch
@@ -94,9 +104,9 @@ trap_vector:
# Save the actual t6 register, which we swapped into
# mscratch
mv t5, t6 # t5 points to the context of current task
csrr t6, mscratch # read t6 back from mscratch
sw t6, 120(t5) # save t6 with t5 as base
mv t5, t6 # t5 points to the context of current task
csrr t6, mscratch # read t6 back from mscratch
STORE t6, 30*SIZE_REG(t5) # save t6 with t5 as base
# Restore the context pointer into mscratch
csrw mscratch, t5
@@ -119,17 +129,21 @@ trap_vector:
# void switch_to(struct context *next);
# a0: pointer to the context of the next task
.globl switch_to
.align 4
.balign 4
switch_to:
csrrw t6, mscratch, t6 # swap t6 and mscratch
beqz t6, 1f # Notice: previous task may be NULL
beqz t6, 1f # Note: the first time switch_to() is
# called, mscratch is initialized as zero
# (in sched_init()), which makes t6 zero,
# and that's the special case we have to
# handle with t6
reg_save t6 # save context of prev task
# Save the actual t6 register, which we swapped into
# mscratch
mv t5, t6 # t5 points to the context of current task
csrr t6, mscratch # read t6 back from mscratch
sw t6, 120(t5) # save t6 with t5 as base
mv t5, t6 # t5 points to the context of current task
csrr t6, mscratch # read t6 back from mscratch
STORE t6, 30*SIZE_REG(t5) # save t6 with t5 as base
1:
# switch mscratch to point to the context of the next task

View File

@@ -1,30 +1,32 @@
#define SIZE_PTR .word
.section .rodata
.global HEAP_START
HEAP_START: .word _heap_start
HEAP_START: SIZE_PTR _heap_start
.global HEAP_SIZE
HEAP_SIZE: .word _heap_size
HEAP_SIZE: SIZE_PTR _heap_size
.global TEXT_START
TEXT_START: .word _text_start
TEXT_START: SIZE_PTR _text_start
.global TEXT_END
TEXT_END: .word _text_end
TEXT_END: SIZE_PTR _text_end
.global DATA_START
DATA_START: .word _data_start
DATA_START: SIZE_PTR _data_start
.global DATA_END
DATA_END: .word _data_end
DATA_END: SIZE_PTR _data_end
.global RODATA_START
RODATA_START: .word _rodata_start
RODATA_START: SIZE_PTR _rodata_start
.global RODATA_END
RODATA_END: .word _rodata_end
RODATA_END: SIZE_PTR _rodata_end
.global BSS_START
BSS_START: .word _bss_start
BSS_START: SIZE_PTR _bss_start
.global BSS_END
BSS_END: .word _bss_end
BSS_END: SIZE_PTR _bss_end

View File

@@ -3,12 +3,14 @@
* Linker script for outputting to RVOS
*/
#include "platform.h"
/*
* 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.
* RISC-V target. We will further refine this by using -march
* and -mabi when calling gcc.
*/
OUTPUT_ARCH( "riscv" )
@@ -47,7 +49,7 @@ ENTRY( _start )
*/
MEMORY
{
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = LENGTH_RAM
}
/*

View File

@@ -3,24 +3,24 @@
/*
* 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;
extern ptr_t TEXT_START;
extern ptr_t TEXT_END;
extern ptr_t DATA_START;
extern ptr_t DATA_END;
extern ptr_t RODATA_START;
extern ptr_t RODATA_END;
extern ptr_t BSS_START;
extern ptr_t BSS_END;
extern ptr_t HEAP_START;
extern ptr_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 ptr_t _alloc_start = 0;
static ptr_t _alloc_end = 0;
static uint32_t _num_pages = 0;
#define PAGE_SIZE 4096
@@ -70,35 +70,65 @@ static inline int _is_last(struct Page *page)
/*
* align the address to the border of page(4K)
*/
static inline uint32_t _align_page(uint32_t address)
static inline ptr_t _align_page(ptr_t address)
{
uint32_t order = (1 << PAGE_ORDER) - 1;
ptr_t order = (1 << PAGE_ORDER) - 1;
return (address + order) & (~order);
}
/*
* ______________________________HEAP_SIZE_______________________________
* / ___num_reserved_pages___ ______________num_pages______________ \
* / / \ / \ \
* |---|<--Page-->|<--Page-->|...|<--Page-->|<--Page-->|......|<--Page-->|---|
* A A A A A
* | | | | |
* | | | | _memory_end
* | | | |
* | _heap_start_aligned _alloc_start _alloc_end
* HEAP_START(BSS_END)
*
* Note: _alloc_end may equal to _memory_end.
*/
void page_init()
{
ptr_t _heap_start_aligned = _align_page(HEAP_START);
/*
* 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)
* We reserved some Pages to hold the Page structures.
* The number of reserved pages depends on the LENGTH_RAM.
* For simplicity, the space we reserve here is just an approximation,
* assuming that it can accommodate the maximum LENGTH_RAM.
* We assume LENGTH_RAM should not be too small, ideally no less
* than 16M (i.e. PAGE_SIZE * PAGE_SIZE).
*/
_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);
uint32_t num_reserved_pages = LENGTH_RAM / (PAGE_SIZE * PAGE_SIZE);
_num_pages = (HEAP_SIZE - (_heap_start_aligned - HEAP_START))/ PAGE_SIZE - num_reserved_pages;
printf("HEAP_START = %p(aligned to %p), HEAP_SIZE = 0x%lx,\n"
"num of reserved pages = %d, num of pages to be allocated for heap = %d\n",
HEAP_START, _heap_start_aligned, HEAP_SIZE,
num_reserved_pages, _num_pages);
/*
* We use HEAP_START, not _heap_start_aligned as begin address for
* allocating struct Page, because we have no requirement of alignment
* for position of struct Page.
*/
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_start = _heap_start_aligned + num_reserved_pages * 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);
printf("TEXT: %p -> %p\n", TEXT_START, TEXT_END);
printf("RODATA: %p -> %p\n", RODATA_START, RODATA_END);
printf("DATA: %p -> %p\n", DATA_START, DATA_END);
printf("BSS: %p -> %p\n", BSS_START, BSS_END);
printf("HEAP: %p -> %p\n", _alloc_start, _alloc_end);
}
/*
@@ -110,15 +140,15 @@ 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++) {
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++) {
struct Page *page_j = page_i + 1;
for (int j = i + 1; j < (i + npages); j++) {
if (!_is_free(page_j)) {
found = 0;
break;
@@ -155,12 +185,12 @@ void page_free(void *p)
/*
* Assert (TBD) if p is invalid
*/
if (!p || (uint32_t)p >= _alloc_end) {
if (!p || (ptr_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;
page += ((ptr_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)) {
@@ -176,14 +206,14 @@ void page_free(void *p)
void page_test()
{
void *p = page_alloc(2);
printf("p = 0x%x\n", p);
printf("p = %p\n", p);
//page_free(p);
void *p2 = page_alloc(7);
printf("p2 = 0x%x\n", p2);
printf("p2 = %p\n", p2);
page_free(p2);
void *p3 = page_alloc(4);
printf("p3 = 0x%x\n", p3);
printf("p3 = %p\n", p3);
}

View File

@@ -12,6 +12,9 @@
*/
#define MAXNUM_CPU 8
/* used in os.ld */
#define LENGTH_RAM 128*1024*1024
/*
* MemoryMap
* see https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c, virt_memmap[]
@@ -43,7 +46,7 @@
* #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_PRIORITY_BASE 0x00
* #define VIRT_PLIC_PENDING_BASE 0x1000
* #define VIRT_PLIC_ENABLE_BASE 0x2000
* #define VIRT_PLIC_ENABLE_STRIDE 0x80
@@ -55,7 +58,7 @@
#define PLIC_BASE 0x0c000000L
#define PLIC_PRIORITY(id) (PLIC_BASE + (id) * 4)
#define PLIC_PENDING(id) (PLIC_BASE + 0x1000 + ((id) / 32) * 4)
#define PLIC_MENABLE(hart) (PLIC_BASE + 0x2000 + (hart) * 0x80)
#define PLIC_MENABLE(hart, id) (PLIC_BASE + 0x2000 + (hart) * 0x80 + ((id) / 32) * 4)
#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)

View File

@@ -25,7 +25,7 @@ void plic_init(void)
* Each global interrupt can be enabled by setting the corresponding
* bit in the enables registers.
*/
*(uint32_t*)PLIC_MENABLE(hart)= (1 << UART0_IRQ);
*(uint32_t*)PLIC_MENABLE(hart, UART0_IRQ)= (1 << (UART0_IRQ % 32));
/*
* Set priority threshold for UART0.

View File

@@ -92,6 +92,10 @@ static inline void w_mie(reg_t x)
asm volatile("csrw mie, %0" : : "r" (x));
}
/* Machine-mode Cause Masks */
#define MCAUSE_MASK_INTERRUPT (reg_t)0x80000000
#define MCAUSE_MASK_ECODE (reg_t)0x7FFFFFFF
static inline reg_t r_mcause()
{
reg_t x;

View File

@@ -5,7 +5,11 @@ extern void switch_to(struct context *next);
#define MAX_TASKS 10
#define STACK_SIZE 1024
uint8_t task_stack[MAX_TASKS][STACK_SIZE];
/*
* In the standard RISC-V calling convention, the stack pointer sp
* is always 16-byte aligned.
*/
uint8_t __attribute__((aligned(16))) task_stack[MAX_TASKS][STACK_SIZE];
struct context ctx_tasks[MAX_TASKS];
/*
@@ -38,7 +42,7 @@ void schedule()
/*
* DESCRIPTION
* Create a task.
* - start_routin: task routune entry
* - start_routin: task routine entry
* RETURN VALUE
* 0: success
* -1: if error occured
@@ -46,7 +50,7 @@ void schedule()
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].sp = (reg_t) &task_stack[_top][STACK_SIZE];
ctx_tasks[_top].ra = (reg_t) start_routin;
_top++;
return 0;

View File

@@ -36,6 +36,9 @@ park:
wfi
j park
# In the standard RISC-V calling convention, the stack pointer sp
# is always 16-byte aligned.
.balign 16
stacks:
.skip STACK_SIZE * MAXNUM_CPU # allocate space for all the harts stacks

View File

@@ -30,9 +30,9 @@ void external_interrupt_handler()
reg_t trap_handler(reg_t epc, reg_t cause)
{
reg_t return_pc = epc;
reg_t cause_code = cause & 0xfff;
reg_t cause_code = cause & MCAUSE_MASK_ECODE;
if (cause & 0x80000000) {
if (cause & MCAUSE_MASK_INTERRUPT) {
/* Asynchronous trap - interrupt */
switch (cause_code) {
case 3:
@@ -47,12 +47,12 @@ reg_t trap_handler(reg_t epc, reg_t cause)
external_interrupt_handler();
break;
default:
uart_puts("unknown async exception!\n");
printf("Unknown async exception! Code = %ld\n", cause_code);
break;
}
} else {
/* Synchronous trap - exception */
printf("Sync exceptions!, code = %d\n", cause_code);
printf("Sync exceptions! Code = %ld\n", cause_code);
panic("OOPS! What can I do!");
//return_pc += 4;
}

View File

@@ -7,8 +7,9 @@ typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
/*
* RISCV32: register is 32bits width
*/
* Register Width
*/
typedef uint32_t reg_t;
typedef uint32_t ptr_t;
#endif /* __TYPES_H__ */

View File

@@ -32,7 +32,7 @@
/*
* POWER UP DEFAULTS
* IER = 0: TX/RX holding register interrupts are bith disabled
* IER = 0: TX/RX holding register interrupts are both disabled
* ISR = 1: no interrupt penting
* LCR = 0
* MCR = 0
@@ -125,11 +125,9 @@ void uart_puts(char *s)
int uart_getc(void)
{
if (uart_read_reg(LSR) & LSR_RX_READY){
return uart_read_reg(RHR);
} else {
return -1;
}
while (0 == (uart_read_reg(LSR) & LSR_RX_READY))
;
return uart_read_reg(RHR);
}
/*
@@ -137,13 +135,7 @@ int uart_getc(void)
*/
void uart_isr(void)
{
while (1) {
int c = uart_getc();
if (c == -1) {
break;
} else {
uart_putc((char)c);
uart_putc('\n');
}
}
uart_putc((char)uart_getc());
/* add a new line just to look better */
uart_putc('\n');
}

View File

@@ -1,5 +1,3 @@
include ../../common.mk
SRCS_ASM = \
start.S \
mem.S \
@@ -16,41 +14,4 @@ SRCS_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
include ../common.mk

View File

@@ -1,92 +1,102 @@
# save all General-Purpose(GP) registers to context
#define LOAD lw
#define STORE sw
#define SIZE_REG 4
# Save all General-Purpose(GP) registers to context.
# struct context *base = &ctx_task;
# base->ra = ra;
# ......
# These GP registers to be saved don't include gp
# and tp, because they are not caller-saved or
# callee-saved. These two registers are often used
# for special purpose. For example, in RVOS, 'tp'
# (aka "thread pointer") is used to store hartid,
# which is a global value and would not be changed
# during context-switch.
.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)
STORE ra, 0*SIZE_REG(\base)
STORE sp, 1*SIZE_REG(\base)
STORE t0, 4*SIZE_REG(\base)
STORE t1, 5*SIZE_REG(\base)
STORE t2, 6*SIZE_REG(\base)
STORE s0, 7*SIZE_REG(\base)
STORE s1, 8*SIZE_REG(\base)
STORE a0, 9*SIZE_REG(\base)
STORE a1, 10*SIZE_REG(\base)
STORE a2, 11*SIZE_REG(\base)
STORE a3, 12*SIZE_REG(\base)
STORE a4, 13*SIZE_REG(\base)
STORE a5, 14*SIZE_REG(\base)
STORE a6, 15*SIZE_REG(\base)
STORE a7, 16*SIZE_REG(\base)
STORE s2, 17*SIZE_REG(\base)
STORE s3, 18*SIZE_REG(\base)
STORE s4, 19*SIZE_REG(\base)
STORE s5, 20*SIZE_REG(\base)
STORE s6, 21*SIZE_REG(\base)
STORE s7, 22*SIZE_REG(\base)
STORE s8, 23*SIZE_REG(\base)
STORE s9, 24*SIZE_REG(\base)
STORE s10, 25*SIZE_REG(\base)
STORE s11, 26*SIZE_REG(\base)
STORE t3, 27*SIZE_REG(\base)
STORE t4, 28*SIZE_REG(\base)
STORE t5, 29*SIZE_REG(\base)
# we don't save t6 here, due to we have used
# it as base, we have to save t6 in an extra step
# outside of reg_save
.endm
# restore all General-Purpose(GP) registers from the context
# except gp & tp.
# 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)
LOAD ra, 0*SIZE_REG(\base)
LOAD sp, 1*SIZE_REG(\base)
LOAD t0, 4*SIZE_REG(\base)
LOAD t1, 5*SIZE_REG(\base)
LOAD t2, 6*SIZE_REG(\base)
LOAD s0, 7*SIZE_REG(\base)
LOAD s1, 8*SIZE_REG(\base)
LOAD a0, 9*SIZE_REG(\base)
LOAD a1, 10*SIZE_REG(\base)
LOAD a2, 11*SIZE_REG(\base)
LOAD a3, 12*SIZE_REG(\base)
LOAD a4, 13*SIZE_REG(\base)
LOAD a5, 14*SIZE_REG(\base)
LOAD a6, 15*SIZE_REG(\base)
LOAD a7, 16*SIZE_REG(\base)
LOAD s2, 17*SIZE_REG(\base)
LOAD s3, 18*SIZE_REG(\base)
LOAD s4, 19*SIZE_REG(\base)
LOAD s5, 20*SIZE_REG(\base)
LOAD s6, 21*SIZE_REG(\base)
LOAD s7, 22*SIZE_REG(\base)
LOAD s8, 23*SIZE_REG(\base)
LOAD s9, 24*SIZE_REG(\base)
LOAD s10, 25*SIZE_REG(\base)
LOAD s11, 26*SIZE_REG(\base)
LOAD t3, 27*SIZE_REG(\base)
LOAD t4, 28*SIZE_REG(\base)
LOAD t5, 29*SIZE_REG(\base)
LOAD t6, 30*SIZE_REG(\base)
.endm
# Something to note about save/restore:
# - We use mscratch to hold a pointer to context of previous task
# - We use mscratch to hold a pointer to context of current 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.
# Note: CSRs(mscratch) can not be used as 'base' due to load/restore
# instruction only accept general purpose registers.
.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
.balign 4
trap_vector:
# save context(registers).
csrrw t6, mscratch, t6 # swap t6 and mscratch
@@ -94,13 +104,13 @@ trap_vector:
# Save the actual t6 register, which we swapped into
# mscratch
mv t5, t6 # t5 points to the context of current task
csrr t6, mscratch # read t6 back from mscratch
sw t6, 120(t5) # save t6 with t5 as base
mv t5, t6 # t5 points to the context of current task
csrr t6, mscratch # read t6 back from mscratch
STORE t6, 30*SIZE_REG(t5) # save t6 with t5 as base
# save mepc to context of current task
csrr a0, mepc
sw a0, 124(t5)
STORE a0, 31*SIZE_REG(t5)
# Restore the context pointer into mscratch
csrw mscratch, t5
@@ -123,12 +133,12 @@ trap_vector:
# void switch_to(struct context *next);
# a0: pointer to the context of the next task
.globl switch_to
.align 4
.balign 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)
LOAD a1, 31*SIZE_REG(a0)
csrw mepc, a1
# Restore all GP registers

View File

@@ -1,30 +1,32 @@
#define SIZE_PTR .word
.section .rodata
.global HEAP_START
HEAP_START: .word _heap_start
HEAP_START: SIZE_PTR _heap_start
.global HEAP_SIZE
HEAP_SIZE: .word _heap_size
HEAP_SIZE: SIZE_PTR _heap_size
.global TEXT_START
TEXT_START: .word _text_start
TEXT_START: SIZE_PTR _text_start
.global TEXT_END
TEXT_END: .word _text_end
TEXT_END: SIZE_PTR _text_end
.global DATA_START
DATA_START: .word _data_start
DATA_START: SIZE_PTR _data_start
.global DATA_END
DATA_END: .word _data_end
DATA_END: SIZE_PTR _data_end
.global RODATA_START
RODATA_START: .word _rodata_start
RODATA_START: SIZE_PTR _rodata_start
.global RODATA_END
RODATA_END: .word _rodata_end
RODATA_END: SIZE_PTR _rodata_end
.global BSS_START
BSS_START: .word _bss_start
BSS_START: SIZE_PTR _bss_start
.global BSS_END
BSS_END: .word _bss_end
BSS_END: SIZE_PTR _bss_end

View File

@@ -58,7 +58,7 @@ struct context {
// upon is trap frame
// save the pc to run in next schedule cycle
reg_t pc; // offset: 31 *4 = 124
reg_t pc; // offset: 31 * sizeof(reg_t)
};
extern int task_create(void (*task)(void));

View File

@@ -3,12 +3,14 @@
* Linker script for outputting to RVOS
*/
#include "platform.h"
/*
* 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.
* RISC-V target. We will further refine this by using -march
* and -mabi when calling gcc.
*/
OUTPUT_ARCH( "riscv" )
@@ -47,7 +49,7 @@ ENTRY( _start )
*/
MEMORY
{
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = LENGTH_RAM
}
/*

View File

@@ -3,24 +3,24 @@
/*
* 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;
extern ptr_t TEXT_START;
extern ptr_t TEXT_END;
extern ptr_t DATA_START;
extern ptr_t DATA_END;
extern ptr_t RODATA_START;
extern ptr_t RODATA_END;
extern ptr_t BSS_START;
extern ptr_t BSS_END;
extern ptr_t HEAP_START;
extern ptr_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 ptr_t _alloc_start = 0;
static ptr_t _alloc_end = 0;
static uint32_t _num_pages = 0;
#define PAGE_SIZE 4096
@@ -70,35 +70,65 @@ static inline int _is_last(struct Page *page)
/*
* align the address to the border of page(4K)
*/
static inline uint32_t _align_page(uint32_t address)
static inline ptr_t _align_page(ptr_t address)
{
uint32_t order = (1 << PAGE_ORDER) - 1;
ptr_t order = (1 << PAGE_ORDER) - 1;
return (address + order) & (~order);
}
/*
* ______________________________HEAP_SIZE_______________________________
* / ___num_reserved_pages___ ______________num_pages______________ \
* / / \ / \ \
* |---|<--Page-->|<--Page-->|...|<--Page-->|<--Page-->|......|<--Page-->|---|
* A A A A A
* | | | | |
* | | | | _memory_end
* | | | |
* | _heap_start_aligned _alloc_start _alloc_end
* HEAP_START(BSS_END)
*
* Note: _alloc_end may equal to _memory_end.
*/
void page_init()
{
ptr_t _heap_start_aligned = _align_page(HEAP_START);
/*
* 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)
* We reserved some Pages to hold the Page structures.
* The number of reserved pages depends on the LENGTH_RAM.
* For simplicity, the space we reserve here is just an approximation,
* assuming that it can accommodate the maximum LENGTH_RAM.
* We assume LENGTH_RAM should not be too small, ideally no less
* than 16M (i.e. PAGE_SIZE * PAGE_SIZE).
*/
_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);
uint32_t num_reserved_pages = LENGTH_RAM / (PAGE_SIZE * PAGE_SIZE);
_num_pages = (HEAP_SIZE - (_heap_start_aligned - HEAP_START))/ PAGE_SIZE - num_reserved_pages;
printf("HEAP_START = %p(aligned to %p), HEAP_SIZE = 0x%lx,\n"
"num of reserved pages = %d, num of pages to be allocated for heap = %d\n",
HEAP_START, _heap_start_aligned, HEAP_SIZE,
num_reserved_pages, _num_pages);
/*
* We use HEAP_START, not _heap_start_aligned as begin address for
* allocating struct Page, because we have no requirement of alignment
* for position of struct Page.
*/
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_start = _heap_start_aligned + num_reserved_pages * 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);
printf("TEXT: %p -> %p\n", TEXT_START, TEXT_END);
printf("RODATA: %p -> %p\n", RODATA_START, RODATA_END);
printf("DATA: %p -> %p\n", DATA_START, DATA_END);
printf("BSS: %p -> %p\n", BSS_START, BSS_END);
printf("HEAP: %p -> %p\n", _alloc_start, _alloc_end);
}
/*
@@ -110,15 +140,15 @@ 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++) {
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++) {
struct Page *page_j = page_i + 1;
for (int j = i + 1; j < (i + npages); j++) {
if (!_is_free(page_j)) {
found = 0;
break;
@@ -155,12 +185,12 @@ void page_free(void *p)
/*
* Assert (TBD) if p is invalid
*/
if (!p || (uint32_t)p >= _alloc_end) {
if (!p || (ptr_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;
page += ((ptr_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)) {
@@ -176,14 +206,14 @@ void page_free(void *p)
void page_test()
{
void *p = page_alloc(2);
printf("p = 0x%x\n", p);
printf("p = %p\n", p);
//page_free(p);
void *p2 = page_alloc(7);
printf("p2 = 0x%x\n", p2);
printf("p2 = %p\n", p2);
page_free(p2);
void *p3 = page_alloc(4);
printf("p3 = 0x%x\n", p3);
printf("p3 = %p\n", p3);
}

View File

@@ -12,6 +12,9 @@
*/
#define MAXNUM_CPU 8
/* used in os.ld */
#define LENGTH_RAM 128*1024*1024
/*
* MemoryMap
* see https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c, virt_memmap[]
@@ -43,7 +46,7 @@
* #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_PRIORITY_BASE 0x00
* #define VIRT_PLIC_PENDING_BASE 0x1000
* #define VIRT_PLIC_ENABLE_BASE 0x2000
* #define VIRT_PLIC_ENABLE_STRIDE 0x80
@@ -55,7 +58,7 @@
#define PLIC_BASE 0x0c000000L
#define PLIC_PRIORITY(id) (PLIC_BASE + (id) * 4)
#define PLIC_PENDING(id) (PLIC_BASE + 0x1000 + ((id) / 32) * 4)
#define PLIC_MENABLE(hart) (PLIC_BASE + 0x2000 + (hart) * 0x80)
#define PLIC_MENABLE(hart, id) (PLIC_BASE + 0x2000 + (hart) * 0x80 + ((id) / 32) * 4)
#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)

View File

@@ -25,7 +25,7 @@ void plic_init(void)
* Each global interrupt can be enabled by setting the corresponding
* bit in the enables registers.
*/
*(uint32_t*)PLIC_MENABLE(hart)= (1 << UART0_IRQ);
*(uint32_t*)PLIC_MENABLE(hart, UART0_IRQ)= (1 << (UART0_IRQ % 32));
/*
* Set priority threshold for UART0.

View File

@@ -92,6 +92,10 @@ static inline void w_mie(reg_t x)
asm volatile("csrw mie, %0" : : "r" (x));
}
/* Machine-mode Cause Masks */
#define MCAUSE_MASK_INTERRUPT (reg_t)0x80000000
#define MCAUSE_MASK_ECODE (reg_t)0x7FFFFFFF
static inline reg_t r_mcause()
{
reg_t x;

View File

@@ -5,7 +5,11 @@ extern void switch_to(struct context *next);
#define MAX_TASKS 10
#define STACK_SIZE 1024
uint8_t task_stack[MAX_TASKS][STACK_SIZE];
/*
* In the standard RISC-V calling convention, the stack pointer sp
* is always 16-byte aligned.
*/
uint8_t __attribute__((aligned(16))) task_stack[MAX_TASKS][STACK_SIZE];
struct context ctx_tasks[MAX_TASKS];
/*
@@ -41,7 +45,7 @@ void schedule()
/*
* DESCRIPTION
* Create a task.
* - start_routin: task routune entry
* - start_routin: task routine entry
* RETURN VALUE
* 0: success
* -1: if error occured
@@ -49,7 +53,7 @@ void schedule()
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].sp = (reg_t) &task_stack[_top][STACK_SIZE];
ctx_tasks[_top].pc = (reg_t) start_routin;
_top++;
return 0;

View File

@@ -32,13 +32,12 @@ _start:
# At the end of start_kernel, schedule() will call MRET to switch
# to the first task, so we parepare the mstatus here.
# Notice: default mstatus is 0
# Notice: It is best not to assume that the initial value of mstatus is
# zero.
# Set mstatus.MPP to 3, so we still run in Machine mode after MRET.
# Set mstatus.MPIE to 1, so MRET will enable the interrupt.
li t0, 3 << 11 | 1 << 7
csrr a1, mstatus
or t0, t0, a1
csrw mstatus, t0
csrs mstatus, t0
j start_kernel # hart 0 jump to c
@@ -46,6 +45,9 @@ park:
wfi
j park
# In the standard RISC-V calling convention, the stack pointer sp
# is always 16-byte aligned.
.balign 16
stacks:
.skip STACK_SIZE * MAXNUM_CPU # allocate space for all the harts stacks

View File

@@ -31,9 +31,9 @@ void external_interrupt_handler()
reg_t trap_handler(reg_t epc, reg_t cause)
{
reg_t return_pc = epc;
reg_t cause_code = cause & 0xfff;
reg_t cause_code = cause & MCAUSE_MASK_ECODE;
if (cause & 0x80000000) {
if (cause & MCAUSE_MASK_INTERRUPT) {
/* Asynchronous trap - interrupt */
switch (cause_code) {
case 3:
@@ -57,12 +57,12 @@ reg_t trap_handler(reg_t epc, reg_t cause)
external_interrupt_handler();
break;
default:
uart_puts("unknown async exception!\n");
printf("Unknown async exception! Code = %ld\n", cause_code);
break;
}
} else {
/* Synchronous trap - exception */
printf("Sync exceptions!, code = %d\n", cause_code);
printf("Sync exceptions! Code = %ld\n", cause_code);
panic("OOPS! What can I do!");
//return_pc += 4;
}

View File

@@ -7,8 +7,9 @@ typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
/*
* RISCV32: register is 32bits width
*/
* Register Width
*/
typedef uint32_t reg_t;
typedef uint32_t ptr_t;
#endif /* __TYPES_H__ */

View File

@@ -32,7 +32,7 @@
/*
* POWER UP DEFAULTS
* IER = 0: TX/RX holding register interrupts are bith disabled
* IER = 0: TX/RX holding register interrupts are both disabled
* ISR = 1: no interrupt penting
* LCR = 0
* MCR = 0
@@ -125,11 +125,9 @@ void uart_puts(char *s)
int uart_getc(void)
{
if (uart_read_reg(LSR) & LSR_RX_READY){
return uart_read_reg(RHR);
} else {
return -1;
}
while (0 == (uart_read_reg(LSR) & LSR_RX_READY))
;
return uart_read_reg(RHR);
}
/*
@@ -137,13 +135,7 @@ int uart_getc(void)
*/
void uart_isr(void)
{
while (1) {
int c = uart_getc();
if (c == -1) {
break;
} else {
uart_putc((char)c);
uart_putc('\n');
}
}
uart_putc((char)uart_getc());
/* add a new line just to look better */
uart_putc('\n');
}

View File

@@ -1,5 +1,3 @@
include ../../common.mk
SRCS_ASM = \
start.S \
mem.S \
@@ -17,41 +15,4 @@ SRCS_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
include ../common.mk

View File

@@ -1,92 +1,102 @@
# save all General-Purpose(GP) registers to context
#define LOAD lw
#define STORE sw
#define SIZE_REG 4
# Save all General-Purpose(GP) registers to context.
# struct context *base = &ctx_task;
# base->ra = ra;
# ......
# These GP registers to be saved don't include gp
# and tp, because they are not caller-saved or
# callee-saved. These two registers are often used
# for special purpose. For example, in RVOS, 'tp'
# (aka "thread pointer") is used to store hartid,
# which is a global value and would not be changed
# during context-switch.
.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)
STORE ra, 0*SIZE_REG(\base)
STORE sp, 1*SIZE_REG(\base)
STORE t0, 4*SIZE_REG(\base)
STORE t1, 5*SIZE_REG(\base)
STORE t2, 6*SIZE_REG(\base)
STORE s0, 7*SIZE_REG(\base)
STORE s1, 8*SIZE_REG(\base)
STORE a0, 9*SIZE_REG(\base)
STORE a1, 10*SIZE_REG(\base)
STORE a2, 11*SIZE_REG(\base)
STORE a3, 12*SIZE_REG(\base)
STORE a4, 13*SIZE_REG(\base)
STORE a5, 14*SIZE_REG(\base)
STORE a6, 15*SIZE_REG(\base)
STORE a7, 16*SIZE_REG(\base)
STORE s2, 17*SIZE_REG(\base)
STORE s3, 18*SIZE_REG(\base)
STORE s4, 19*SIZE_REG(\base)
STORE s5, 20*SIZE_REG(\base)
STORE s6, 21*SIZE_REG(\base)
STORE s7, 22*SIZE_REG(\base)
STORE s8, 23*SIZE_REG(\base)
STORE s9, 24*SIZE_REG(\base)
STORE s10, 25*SIZE_REG(\base)
STORE s11, 26*SIZE_REG(\base)
STORE t3, 27*SIZE_REG(\base)
STORE t4, 28*SIZE_REG(\base)
STORE t5, 29*SIZE_REG(\base)
# we don't save t6 here, due to we have used
# it as base, we have to save t6 in an extra step
# outside of reg_save
.endm
# restore all General-Purpose(GP) registers from the context
# except gp & tp.
# 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)
LOAD ra, 0*SIZE_REG(\base)
LOAD sp, 1*SIZE_REG(\base)
LOAD t0, 4*SIZE_REG(\base)
LOAD t1, 5*SIZE_REG(\base)
LOAD t2, 6*SIZE_REG(\base)
LOAD s0, 7*SIZE_REG(\base)
LOAD s1, 8*SIZE_REG(\base)
LOAD a0, 9*SIZE_REG(\base)
LOAD a1, 10*SIZE_REG(\base)
LOAD a2, 11*SIZE_REG(\base)
LOAD a3, 12*SIZE_REG(\base)
LOAD a4, 13*SIZE_REG(\base)
LOAD a5, 14*SIZE_REG(\base)
LOAD a6, 15*SIZE_REG(\base)
LOAD a7, 16*SIZE_REG(\base)
LOAD s2, 17*SIZE_REG(\base)
LOAD s3, 18*SIZE_REG(\base)
LOAD s4, 19*SIZE_REG(\base)
LOAD s5, 20*SIZE_REG(\base)
LOAD s6, 21*SIZE_REG(\base)
LOAD s7, 22*SIZE_REG(\base)
LOAD s8, 23*SIZE_REG(\base)
LOAD s9, 24*SIZE_REG(\base)
LOAD s10, 25*SIZE_REG(\base)
LOAD s11, 26*SIZE_REG(\base)
LOAD t3, 27*SIZE_REG(\base)
LOAD t4, 28*SIZE_REG(\base)
LOAD t5, 29*SIZE_REG(\base)
LOAD t6, 30*SIZE_REG(\base)
.endm
# Something to note about save/restore:
# - We use mscratch to hold a pointer to context of previous task
# - We use mscratch to hold a pointer to context of current 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.
# Note: CSRs(mscratch) can not be used as 'base' due to load/restore
# instruction only accept general purpose registers.
.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
.balign 4
trap_vector:
# save context(registers).
csrrw t6, mscratch, t6 # swap t6 and mscratch
@@ -94,13 +104,13 @@ trap_vector:
# Save the actual t6 register, which we swapped into
# mscratch
mv t5, t6 # t5 points to the context of current task
csrr t6, mscratch # read t6 back from mscratch
sw t6, 120(t5) # save t6 with t5 as base
mv t5, t6 # t5 points to the context of current task
csrr t6, mscratch # read t6 back from mscratch
STORE t6, 30*SIZE_REG(t5) # save t6 with t5 as base
# save mepc to context of current task
csrr a0, mepc
sw a0, 124(t5)
STORE a0, 31*SIZE_REG(t5)
# Restore the context pointer into mscratch
csrw mscratch, t5
@@ -123,12 +133,12 @@ trap_vector:
# void switch_to(struct context *next);
# a0: pointer to the context of the next task
.globl switch_to
.align 4
.balign 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)
LOAD a1, 31*SIZE_REG(a0)
csrw mepc, a1
# Restore all GP registers

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