mirror of
https://github.com/plctlab/riscv-operating-system-mooc.git
synced 2025-11-16 12:34:47 +00:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48d1d6ebaf | ||
|
|
eb61470e22 | ||
|
|
0e16685c97 | ||
|
|
a982ea7317 | ||
|
|
7df4ae5a19 | ||
|
|
0cacbd6a22 | ||
|
|
2fcd517c78 | ||
|
|
fd1d098906 | ||
|
|
1976f03d28 | ||
|
|
492cac5ba9 | ||
|
|
a2617cffa6 | ||
|
|
992b0ba91c | ||
|
|
4698c95063 | ||
|
|
c0e6ce15f0 | ||
|
|
0446eb2b9d | ||
|
|
27723d09ed | ||
|
|
2474692c8b | ||
|
|
457713c30e | ||
|
|
a94fb0cd58 | ||
|
|
6d2c046c67 | ||
|
|
f77939f1e8 | ||
|
|
c674ffc9bc | ||
|
|
744226d09e | ||
|
|
cec375a5e6 | ||
|
|
ae10d3e698 | ||
|
|
ca2629bebc |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,4 +1,4 @@
|
||||
*.o
|
||||
*.bin
|
||||
*.elf
|
||||
|
||||
code/os/*/out/
|
||||
|
||||
30
README_zh.md
30
README_zh.md
@@ -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 学习群**
|
||||
|
||||

|
||||
**“课程群 2”**, 群号: **799027025**,或者扫码加入,见下:
|
||||
|
||||
- **微信学习群**,请添加微信 fangzhang1024 (标注 【汪辰老师】)加入。
|
||||

|
||||
|
||||
***注意!!!:“课程群 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
|
||||
|
||||
@@ -20,7 +20,7 @@ stop:
|
||||
nop # just for demo effect
|
||||
|
||||
stack_start:
|
||||
.rept 10
|
||||
.rept 12
|
||||
.word 0
|
||||
.endr
|
||||
stack_end:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -17,7 +17,7 @@ stop:
|
||||
nop # just for demo effect
|
||||
|
||||
stack_start:
|
||||
.rept 10
|
||||
.rept 12
|
||||
.word 0
|
||||
.endr
|
||||
stack_end:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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__ */
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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__ */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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__ */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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__ */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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__ */
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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__ */
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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__ */
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user