├── .gitignore ├── .spike.cfg ├── LICENSE.txt ├── Makefile ├── README.md ├── kernel ├── config.h ├── elf.c ├── elf.h ├── kernel.c ├── kernel.lds ├── machine │ ├── mentry.S │ └── minit.c ├── process.c ├── process.h ├── riscv.h ├── strap.c ├── strap.h ├── strap_vector.S ├── syscall.c └── syscall.h ├── preview.png ├── spike_interface ├── atomic.h ├── dts_parse.c ├── dts_parse.h ├── spike_file.c ├── spike_file.h ├── spike_htif.c ├── spike_htif.h ├── spike_memory.c ├── spike_memory.h ├── spike_utils.c └── spike_utils.h ├── user ├── app_helloworld.c ├── user.lds ├── user_lib.c └── user_lib.h └── util ├── functions.h ├── load_store.S ├── snprintf.c ├── snprintf.h ├── string.c ├── string.h └── types.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.out 3 | *.swp 4 | tags 5 | cscope* 6 | *dump 7 | rename.py 8 | format.py 9 | obj/ 10 | .VSCodeCounter/ 11 | .vscode/ 12 | pke_out.txt 13 | -------------------------------------------------------------------------------- /.spike.cfg: -------------------------------------------------------------------------------- 1 | adapter driver remote_bitbang 2 | remote_bitbang_host localhost 3 | remote_bitbang_port 9824 4 | 5 | set _CHIPNAME riscv 6 | jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x10e31913 7 | 8 | set _TARGETNAME $_CHIPNAME.cpu 9 | target create $_TARGETNAME riscv -chain-position $_TARGETNAME 10 | 11 | gdb_report_data_abort enable 12 | 13 | init 14 | halt 15 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | ========================================================================== 2 | Copyright License 3 | ========================================================================== 4 | The PKE software is: 5 | 6 | Copyright (c) 2021, Zhiyuan Shao (zyshao@hust.edu.cn), 7 | Yi Gui (gy163email@163.com), 8 | Yan Jiao (773709579@qq.com), 9 | Huazhong University of Science and Technology 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining 12 | a copy of this software and associated documentation files (the 13 | "Software"), to deal in the Software without restriction, including 14 | without limitation the rights to use, copy, modify, merge, publish, 15 | distribute, sublicense, and/or sell copies of the Software, and to 16 | permit persons to whom the Software is furnished to do so, subject to 17 | the following conditions: 18 | 19 | * The above copyright notice and this permission notice shall be 20 | included in all copies or substantial portions of the Software. 21 | 22 | * Neither the name of the Huazhong University of Science and Technology 23 | nor the names of its contributors may be used to endorse or promote 24 | products derived from this software without specific prior written 25 | permission. 26 | 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 28 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 29 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 30 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 31 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 32 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 33 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 34 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # we assume that the utilities from RISC-V cross-compiler (i.e., riscv64-unknown-elf-gcc and etc.) 2 | # are in your system PATH. To check if your environment satisfies this requirement, simple use 3 | # `which` command as follows: 4 | # $ which riscv64-unknown-elf-gcc 5 | # if you have an output path, your environment satisfy our requirement. 6 | 7 | # --------------------- macros -------------------------- 8 | CROSS_PREFIX := riscv64-unknown-elf- 9 | CC := $(CROSS_PREFIX)gcc 10 | AR := $(CROSS_PREFIX)ar 11 | RANLIB := $(CROSS_PREFIX)ranlib 12 | 13 | SRC_DIR := . 14 | OBJ_DIR := obj 15 | SPROJS_INCLUDE := -I. 16 | 17 | ifneq (,) 18 | march := -march= 19 | is_32bit := $(findstring 32,$(march)) 20 | mabi := -mabi=$(if $(is_32bit),ilp32,lp64) 21 | endif 22 | 23 | CFLAGS := -Wall -Werror -fno-builtin -nostdlib -D__NO_INLINE__ -mcmodel=medany -g -Og -std=gnu99 -Wno-unused -Wno-attributes -fno-delete-null-pointer-checks -fno-PIE $(march) 24 | COMPILE := $(CC) -MMD -MP $(CFLAGS) $(SPROJS_INCLUDE) 25 | 26 | #--------------------- utils ----------------------- 27 | UTIL_CPPS := util/*.c 28 | 29 | UTIL_CPPS := $(wildcard $(UTIL_CPPS)) 30 | UTIL_OBJS := $(addprefix $(OBJ_DIR)/, $(patsubst %.c,%.o,$(UTIL_CPPS))) 31 | 32 | 33 | UTIL_LIB := $(OBJ_DIR)/util.a 34 | 35 | #--------------------- kernel ----------------------- 36 | KERNEL_LDS := kernel/kernel.lds 37 | KERNEL_CPPS := \ 38 | kernel/*.c \ 39 | kernel/machine/*.c \ 40 | kernel/util/*.c 41 | 42 | KERNEL_ASMS := \ 43 | kernel/*.S \ 44 | kernel/machine/*.S \ 45 | kernel/util/*.S 46 | 47 | KERNEL_CPPS := $(wildcard $(KERNEL_CPPS)) 48 | KERNEL_ASMS := $(wildcard $(KERNEL_ASMS)) 49 | KERNEL_OBJS := $(addprefix $(OBJ_DIR)/, $(patsubst %.c,%.o,$(KERNEL_CPPS))) 50 | KERNEL_OBJS += $(addprefix $(OBJ_DIR)/, $(patsubst %.S,%.o,$(KERNEL_ASMS))) 51 | 52 | KERNEL_TARGET = $(OBJ_DIR)/riscv-pke 53 | 54 | 55 | #--------------------- spike interface library ----------------------- 56 | SPIKE_INF_CPPS := spike_interface/*.c 57 | 58 | SPIKE_INF_CPPS := $(wildcard $(SPIKE_INF_CPPS)) 59 | SPIKE_INF_OBJS := $(addprefix $(OBJ_DIR)/, $(patsubst %.c,%.o,$(SPIKE_INF_CPPS))) 60 | 61 | 62 | SPIKE_INF_LIB := $(OBJ_DIR)/spike_interface.a 63 | 64 | 65 | #--------------------- user ----------------------- 66 | USER_LDS := user/user.lds 67 | USER_CPPS := user/*.c 68 | 69 | USER_CPPS := $(wildcard $(USER_CPPS)) 70 | USER_OBJS := $(addprefix $(OBJ_DIR)/, $(patsubst %.c,%.o,$(USER_CPPS))) 71 | 72 | USER_TARGET := $(OBJ_DIR)/app_helloworld 73 | 74 | #------------------------targets------------------------ 75 | $(OBJ_DIR): 76 | @-mkdir -p $(OBJ_DIR) 77 | @-mkdir -p $(dir $(UTIL_OBJS)) 78 | @-mkdir -p $(dir $(SPIKE_INF_OBJS)) 79 | @-mkdir -p $(dir $(KERNEL_OBJS)) 80 | @-mkdir -p $(dir $(USER_OBJS)) 81 | 82 | $(OBJ_DIR)/%.o : %.c 83 | @echo "compiling" $< 84 | @$(COMPILE) -c $< -o $@ 85 | 86 | $(OBJ_DIR)/%.o : %.S 87 | @echo "compiling" $< 88 | @$(COMPILE) -c $< -o $@ 89 | 90 | $(UTIL_LIB): $(OBJ_DIR) $(UTIL_OBJS) 91 | @echo "linking " $@ ... 92 | @$(AR) -rcs $@ $(UTIL_OBJS) 93 | @echo "Util lib has been build into" \"$@\" 94 | 95 | $(SPIKE_INF_LIB): $(OBJ_DIR) $(UTIL_OBJS) $(SPIKE_INF_OBJS) 96 | @echo "linking " $@ ... 97 | @$(AR) -rcs $@ $(SPIKE_INF_OBJS) $(UTIL_OBJS) 98 | @echo "Spike lib has been build into" \"$@\" 99 | 100 | $(KERNEL_TARGET): $(OBJ_DIR) $(UTIL_LIB) $(SPIKE_INF_LIB) $(KERNEL_OBJS) $(KERNEL_LDS) 101 | @echo "linking" $@ ... 102 | @$(COMPILE) $(KERNEL_OBJS) $(UTIL_LIB) $(SPIKE_INF_LIB) -o $@ -T $(KERNEL_LDS) 103 | @echo "PKE core has been built into" \"$@\" 104 | 105 | $(USER_TARGET): $(OBJ_DIR) $(UTIL_LIB) $(USER_OBJS) $(USER_LDS) 106 | @echo "linking" $@ ... 107 | @$(COMPILE) $(USER_OBJS) $(UTIL_LIB) -o $@ -T $(USER_LDS) 108 | @echo "User app has been built into" \"$@\" 109 | 110 | -include $(wildcard $(OBJ_DIR)/*/*.d) 111 | -include $(wildcard $(OBJ_DIR)/*/*/*.d) 112 | 113 | .DEFAULT_GOAL := $(all) 114 | 115 | all: $(KERNEL_TARGET) $(USER_TARGET) 116 | .PHONY:all 117 | 118 | run: $(KERNEL_TARGET) $(USER_TARGET) 119 | @echo "********************HUST PKE********************" 120 | spike $(KERNEL_TARGET) $(USER_TARGET) 121 | 122 | # need openocd! 123 | gdb:$(KERNEL_TARGET) $(USER_TARGET) 124 | spike --rbb-port=9824 -H $(KERNEL_TARGET) $(USER_TARGET) & 125 | @sleep 1 126 | openocd -f ./.spike.cfg & 127 | @sleep 1 128 | riscv64-unknown-elf-gdb -command=./.gdbinit 129 | 130 | # clean gdb. need openocd! 131 | gdb_clean: 132 | @-kill -9 $$(lsof -i:9824 -t) 133 | @-kill -9 $$(lsof -i:3333 -t) 134 | @sleep 1 135 | 136 | objdump: 137 | riscv64-unknown-elf-objdump -d $(KERNEL_TARGET) > $(OBJ_DIR)/kernel_dump 138 | riscv64-unknown-elf-objdump -d $(USER_TARGET) > $(OBJ_DIR)/user_dump 139 | 140 | cscope: 141 | find ./ -name "*.c" > cscope.files 142 | find ./ -name "*.h" >> cscope.files 143 | find ./ -name "*.S" >> cscope.files 144 | find ./ -name "*.lds" >> cscope.files 145 | cscope -bqk 146 | 147 | format: 148 | @python ./format.py ./ 149 | 150 | clean: 151 | rm -fr ${OBJ_DIR} 152 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Preface for HUSTers 2 | ---------- 3 | 4 | ### source 5 | 6 | This project comes from the repository [华中科技大学操作系统团队 / riscv-pke](https://gitee.com/hustos/riscv-pke). 7 | 8 | ### structure 9 | 10 | As you can see in **Branches**, each branch is corresponding to a level with its concise description. 11 | 12 | ![tutu](preview.png) 13 | 14 | ### usage 15 | 16 | This is **NOT** a local runnable package, cuz I haven't done any compile works in it. Just regard it as an answerbook of checkpoints in *Educoder* online assessment. If you want to run this project locally, please read on and find your way in **official README**. 17 | 18 | ### caution 19 | 20 | This preface will only be displayed in default branch. 21 | 22 | ---------- 23 | ## about riscv-pke (Proxy Kernel for Education, a.k.a. PKE) ## 24 | ---------- 25 | 26 | Documents in Chinese can be found [here](https://gitee.com/hustos/pke-doc). There is still no dedicated documents in English yet, but the in-line comments in our codes as well as the self-explaining names for variables and functions will help on your journey of PKE. 27 | 28 | PKE is an open source project (see [LICENSE.txt](./LICENSE.txt) for license information) for the educational purpose of the Operating System Engineering/Computer System Engineering courses, given to undergraduate students majored in CS (Computer Science) or EECS ( Electrical Engineering and Computer Science) in universities. 29 | 30 | PKE provides a series of labs that cover the engineering-side knowledge points of the Operating System as well as some of Computer Organization/Architecture, including: 31 | 32 | Lab1(3 basic labs+2 challenge labs): traps (syscalls), exceptions and interrupts (IRQs in Intel terminology). 33 | 34 | Lab2 (3 basic labs+2 challenge labs): memory management. 35 | 36 | Lab3 (3 basic labs+2 challenge labs): processes. 37 | 38 | Lab4 (3 basic labs): device and file (conducted on a PYNQ FPGA board + an Arduino toy car). 39 | 40 | The experiments in the REPO may be different (with more actual labs) from the above list with the passing of time. 41 | 42 | From the angle of education on Operating System Engineering, different from many famous OS educational projects (like [xv6](https://pdos.csail.mit.edu/6.828/2020/xv6.html) (JOS when earlier) used in MIT 6.828 and [ucore](https://github.com/oscourse-tsinghua/ucore-rv) taught in Tsinghua University) that use complete or near-complete OS kernels containing almost everything like process management, file systems and many other modules, *PKE is **NOT** a complete OS kernel (actually, PKE never intends to be one of them.)*. 43 | 44 | 45 | PKE is built around the idea of Proxy Kernel (proposed in [PK](https://github.com/riscv/riscv-pk), an open source project of the RISC-V software ecology), that emphasizes to construct a "just-enough" OS kernel for a given application. With such an idea, we design a series of labs in PKE that gradually "upgrades" the OS kernel by giving a set of applications, from simple to complex. During the upgradations, you can learn more and more sophisticated ideas of modern operating systems, and more importantly, play with them by following the labs, one after another. 46 | 47 | 48 | In each lab, PKE starts with an application (placed in the *./user/* folder, with the "app_" prefix) and an *incomplete* proxy OS kernel. During the lab, you need to 1) understand the interaction between application and proxy OS kernel (sometimes, also the RISC-V machine emulated by using [Spike](https://github.com/riscv/riscv-isa-sim), or an FPGA board with a soft RISC-V core); 2) follow the code from the given application to the OS kernel based on the understanding; 3) complete the proxy OS kernel to make the application (or the system) to execute correctly and smoothly. 49 | 50 | 51 | In the labs of PKE, we tried our best to control and minimize the code scale of each lab, hoping to help you to stay focus on the key components of Operating System, and minimize the efforts at the same time. [Contact us](mailto:zyshao@hust.edu.cn) if you have further suggestions on reducing the code scale, thanks in advance! 52 | 53 | 54 | Environment configuration 55 | ---------- 56 | 57 | **1. Install Operating system (Virtual machine or Windows Subversion Linux)** 58 | 59 | (preferred) Ubuntu 16.04LTS or higher, 64-bit 60 | 61 | **2. Install tools for building cross-compiler and emluator** 62 | 63 | ```bash 64 | $ sudo apt-get install autoconf automake autotools-dev curl python3 libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex 65 | ``` 66 | 67 | **3. Install RISC-V cross-compiler** 68 | 69 | ```bash 70 | $ export RISCV=/path-to-install-RISCV-toolchains 71 | $ git clone --recursive https://github.com/riscv/riscv-gnu-toolchain.git 72 | $ cd riscv-gnu-toolchain 73 | $ ./configure --prefix=$RISCV 74 | $ make -j$(nproc) 75 | $ sudo make install 76 | ``` 77 | 78 | In above commands, *$(nproc)* stands for the number of threads you want to invoke during building. Generelly, *$(nproc)* should equal to the number of cores that your computer have. After this step, you should find executables, like riscv64-unknown-elf-gcc, riscv64-unknown-elf-gdb, riscv64-unknown-elf-objdump, and many others (with the name prefix of "riscv64-unknown-elf-") in your */path-to-install-RISCV-toolchains/bin* directory. 79 | 80 | **4. Install emulator (Spike)** 81 | 82 | ```bash 83 | $ sudo apt-get install device-tree-compiler 84 | $ git clone https://github.com/riscv/riscv-isa-sim.git 85 | $ cd riscv-isa-sim 86 | $ mkdir build 87 | $ cd build 88 | $ ../configure --prefix=$RISCV 89 | $ make -j$(nproc) 90 | $ sudo make install 91 | ``` 92 | 93 | After this step, you should find executables like spike, spike-dasm in your */path-to-install-RISCV-toolchains/bin* directory. 94 | 95 | **5. Clone PKE REPO** 96 | 97 | ```bash 98 | $ git clone https://github.com/MrShawCode/riscv-pke.git 99 | ``` 100 | 101 | After this step, you will have the pke directory containing the PKE labs. 102 | 103 | **6. Build/Run PKE** 104 | 105 | ```bash 106 | $ make [run] 107 | ``` 108 | 109 | **7. (optional) Install OpenOCD for debugging** 110 | 111 | ```bash 112 | $ git clone https://github.com/riscv/riscv-openocd.git 113 | $ cd openocd 114 | $ ./bootstrap (when building from the git repository) 115 | $ ./configure --prefix=$RISCV 116 | $ make -j$(nproc) 117 | $ sudo make install 118 | ``` 119 | 120 | After installing OpenOCD, you can debug the PKE kernel. Simply use following command: 121 | 122 | ```bash 123 | $ make gdb 124 | ``` 125 | 126 | Start the first lab 127 | ---------- 128 | 129 | In this lab, we are going to learn the basic priciples of trap (also called as the **syscall** in many textbooks). 130 | 131 | A trap (for example, *printf* that is in our daily use) is generally issued by an application, and evetually handled by the kernel. It is very important for an OS to provide such facility, since applications running in less priviledged modes (e.g., User-mode in RISC-V) need to frequently perform legal operations like I/Os that require to be conducted in higher priviledge modes (e.g., Supervisor or Machine modes in RISC-V). 132 | 133 | Lab1_1 gives an application in "user/lab1_1_helloworld.c", whose main() function calls a function *printu* that has the same functionality as *printf*, but under a slightly different name. *printu* is defined in "user/do_print.c", and actually invokes the trap by the *ecall* instruction (see the inline assemblies in the function of *do_user_print*). 134 | 135 | 136 | #### Code structure of Lab1_1 137 | ---------- 138 | The structure of Lab1_1 is listed in the following: 139 | 140 | . 141 | ├── LICENSE.txt 142 | ├── Makefile 143 | ├── README.md 144 | ├── .spike.cfg 145 | ├── kernel 146 | │   ├── config.h 147 | │   ├── elf.c 148 | │   ├── elf.h 149 | │   ├── kernel.c 150 | │   ├── kernel.lds 151 | │   ├── machine 152 | │   │   ├── mentry.S 153 | │   │   └── minit.c 154 | │   ├── process.c 155 | │   ├── process.h 156 | │   ├── riscv.h 157 | │   ├── strap.c 158 | │   ├── strap.h 159 | │   ├── strap_vector.S 160 | │   ├── syscall.c 161 | │   └── syscall.h 162 | ├── spike_interface 163 | │   ├── atomic.h 164 | │   ├── dts_parse.c 165 | │   ├── dts_parse.h 166 | │   ├── spike_file.c 167 | │   ├── spike_file.h 168 | │   ├── spike_htif.c 169 | │   ├── spike_htif.h 170 | │   ├── spike_memory.c 171 | │   ├── spike_memory.h 172 | │   ├── spike_utils.c 173 | │   └── spike_utils.h 174 | ├── user 175 | │   ├── app_helloworld.c 176 | │   ├── user.lds 177 | │   ├── user_lib.c 178 | │   └── user_lib.h 179 | └── util 180 | ├── functions.h 181 | ├── load_store.S 182 | ├── snprintf.c 183 | ├── snprintf.h 184 | ├── string.c 185 | ├── string.h 186 | └── types.h 187 | 188 | The root directory mainly contains the documents (i.e., the md files), the license text and importantly, the make file (named as *Makefile*). The *kernel* sub-directory contains the OS kernel, while the *user* sub-directory contains the given application (in *app_helloworld.c*) as well as the source-code files containing the supporting routings, which should be placed in the user library in full-pledged OS like Linux. 189 | 190 | To understand the PKE OS kernel (of Lab1_1) and accomplish the lab, you should start from the given application. Therefore, we start the tourism from *user/app_helloworld.c*: 191 | 192 | ```C 193 | 1 /* 194 | 2 * Below is the given application for lab1_1. 195 | 3 * 196 | 4 * You can build this app (as well as our PKE OS kernel) by command: 197 | 5 * $ make 198 | 6 * 199 | 7 * Or run this app (with the support from PKE OS kernel) by command: 200 | 8 * $ make run 201 | 9 */ 202 | 10 203 | 11 #include "user_lib.h" 204 | 12 205 | 13 int main(void) { 206 | 14 printu("Hello world!\n"); 207 | 15 208 | 16 exit(0); 209 | 17 } 210 | ``` 211 | 212 | From the code, we can observe that there is a newly defined function called *printu*, whose functionality equals to *printf* of our daily use. The reason we define a new function instead of using *printf* is that *printf* is already defined in the newlib of the RISC-V cross-compiling tool chain. 213 | 214 | The prototype and implementation of *printu* can be found in *user/user.h* and *user/do_print.c* respectively. 215 | 216 | Switching to next stage 217 | ---------- 218 | 219 | After having finished a lab (and committed your solution), you can continue the practicing of following labs. For example, after finishing lab1_1, you can commit your solution by: 220 | 221 | ```bash 222 | $ git commit -a -m "your comments to lab1_1" 223 | ``` 224 | 225 | then, switch to next lab (lab1_2) by: 226 | 227 | ```bash 228 | $ git checkout lab1_2_exception 229 | ``` 230 | 231 | and merge your solution in previous lab by: 232 | 233 | ```bash 234 | $ git merge lab1_1_syscall -m "continue lab1_2" 235 | ``` 236 | 237 | After all these, you can proceed to work on lab1_2. 238 | 239 | **Note**: Never merge challenge labs, such as lab1_challenge1_backtrace, lab2_challenge1_pagefaults, etc. 240 | 241 | 242 | 243 | That's all. Hope you enjoy! 244 | 245 | -------------------------------------------------------------------------------- /kernel/config.h: -------------------------------------------------------------------------------- 1 | #ifndef _CONFIG_H_ 2 | #define _CONFIG_H_ 3 | 4 | // we use only one HART (cpu) in fundamental experiments 5 | #define NCPU 1 6 | 7 | #define DRAM_BASE 0x80000000 8 | 9 | /* we use fixed physical (also logical) addresses for the stacks and trap frames as in 10 | Bare memory-mapping mode */ 11 | // user stack top 12 | #define USER_STACK 0x81100000 13 | 14 | // the stack used by PKE kernel when a syscall happens 15 | #define USER_KSTACK 0x81200000 16 | 17 | // the trap frame used to assemble the user "process" 18 | #define USER_TRAP_FRAME 0x81300000 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /kernel/elf.c: -------------------------------------------------------------------------------- 1 | /* 2 | * routines that scan and load a (host) Executable and Linkable Format (ELF) file 3 | * into the (emulated) memory. 4 | */ 5 | 6 | #include "elf.h" 7 | #include "string.h" 8 | #include "riscv.h" 9 | #include "spike_interface/spike_utils.h" 10 | 11 | typedef struct elf_info_t { 12 | spike_file_t *f; 13 | process *p; 14 | } elf_info; 15 | 16 | // 17 | // the implementation of allocater. allocates memory space for later segment loading 18 | // 19 | static void *elf_alloc_mb(elf_ctx *ctx, uint64 elf_pa, uint64 elf_va, uint64 size) { 20 | // directly returns the virtual address as we are in the Bare mode in lab1_x 21 | return (void *)elf_va; 22 | } 23 | 24 | // 25 | // actual file reading, using the spike file interface. 26 | // 27 | static uint64 elf_fpread(elf_ctx *ctx, void *dest, uint64 nb, uint64 offset) { 28 | elf_info *msg = (elf_info *)ctx->info; 29 | // call spike file utility to load the content of elf file into memory. 30 | // spike_file_pread will read the elf file (msg->f) from offset to memory (indicated by 31 | // *dest) for nb bytes. 32 | return spike_file_pread(msg->f, dest, nb, offset); 33 | } 34 | 35 | // 36 | // init elf_ctx, a data structure that loads the elf. 37 | // 38 | elf_status elf_init(elf_ctx *ctx, void *info) { 39 | ctx->info = info; 40 | 41 | // load the elf header 42 | if (elf_fpread(ctx, &ctx->ehdr, sizeof(ctx->ehdr), 0) != sizeof(ctx->ehdr)) return EL_EIO; 43 | 44 | // check the signature (magic value) of the elf 45 | if (ctx->ehdr.magic != ELF_MAGIC) return EL_NOTELF; 46 | 47 | return EL_OK; 48 | } 49 | 50 | // 51 | // load the elf segments to memory regions as we are in Bare mode in lab1 52 | // 53 | elf_status elf_load(elf_ctx *ctx) { 54 | // elf_prog_header structure is defined in kernel/elf.h 55 | elf_prog_header ph_addr; 56 | int i, off; 57 | 58 | // traverse the elf program segment headers 59 | for (i = 0, off = ctx->ehdr.phoff; i < ctx->ehdr.phnum; i++, off += sizeof(ph_addr)) { 60 | // read segment headers 61 | if (elf_fpread(ctx, (void *)&ph_addr, sizeof(ph_addr), off) != sizeof(ph_addr)) return EL_EIO; 62 | 63 | if (ph_addr.type != ELF_PROG_LOAD) continue; 64 | if (ph_addr.memsz < ph_addr.filesz) return EL_ERR; 65 | if (ph_addr.vaddr + ph_addr.memsz < ph_addr.vaddr) return EL_ERR; 66 | 67 | // allocate memory block before elf loading 68 | void *dest = elf_alloc_mb(ctx, ph_addr.vaddr, ph_addr.vaddr, ph_addr.memsz); 69 | 70 | // actual loading 71 | if (elf_fpread(ctx, dest, ph_addr.memsz, ph_addr.off) != ph_addr.memsz) 72 | return EL_EIO; 73 | } 74 | 75 | return EL_OK; 76 | } 77 | 78 | typedef union { 79 | uint64 buf[MAX_CMDLINE_ARGS]; 80 | char *argv[MAX_CMDLINE_ARGS]; 81 | } arg_buf; 82 | 83 | // 84 | // returns the number (should be 1) of string(s) after PKE kernel in command line. 85 | // and store the string(s) in arg_bug_msg. 86 | // 87 | static size_t parse_args(arg_buf *arg_bug_msg) { 88 | // HTIFSYS_getmainvars frontend call reads command arguments to (input) *arg_bug_msg 89 | long r = frontend_syscall(HTIFSYS_getmainvars, (uint64)arg_bug_msg, 90 | sizeof(*arg_bug_msg), 0, 0, 0, 0, 0); 91 | kassert(r == 0); 92 | 93 | size_t pk_argc = arg_bug_msg->buf[0]; 94 | uint64 *pk_argv = &arg_bug_msg->buf[1]; 95 | 96 | int arg = 1; // skip the PKE OS kernel string, leave behind only the application name 97 | for (size_t i = 0; arg + i < pk_argc; i++) 98 | arg_bug_msg->argv[i] = (char *)(uintptr_t)pk_argv[arg + i]; 99 | 100 | //returns the number of strings after PKE kernel in command line 101 | return pk_argc - arg; 102 | } 103 | 104 | // 105 | // load the elf of user application, by using the spike file interface. 106 | // 107 | void load_bincode_from_host_elf(process *p) { 108 | arg_buf arg_bug_msg; 109 | 110 | // retrieve command line arguements 111 | size_t argc = parse_args(&arg_bug_msg); 112 | if (!argc) panic("You need to specify the application program!\n"); 113 | 114 | sprint("Application: %s\n", arg_bug_msg.argv[0]); 115 | 116 | //elf loading. elf_ctx is defined in kernel/elf.h, used to track the loading process. 117 | elf_ctx elfloader; 118 | // elf_info is defined above, used to tie the elf file and its corresponding process. 119 | elf_info info; 120 | 121 | info.f = spike_file_open(arg_bug_msg.argv[0], O_RDONLY, 0); 122 | info.p = p; 123 | // IS_ERR_VALUE is a macro defined in spike_interface/spike_htif.h 124 | if (IS_ERR_VALUE(info.f)) panic("Fail on openning the input application program.\n"); 125 | 126 | // init elfloader context. elf_init() is defined above. 127 | if (elf_init(&elfloader, &info) != EL_OK) 128 | panic("fail to init elfloader.\n"); 129 | 130 | // load elf. elf_load() is defined above. 131 | if (elf_load(&elfloader) != EL_OK) panic("Fail on loading elf.\n"); 132 | 133 | // entry (virtual, also physical in lab1_x) address 134 | p->trapframe->epc = elfloader.ehdr.entry; 135 | 136 | // close the host spike file 137 | spike_file_close( info.f ); 138 | 139 | sprint("Application program entry point (virtual address): 0x%lx\n", p->trapframe->epc); 140 | } 141 | -------------------------------------------------------------------------------- /kernel/elf.h: -------------------------------------------------------------------------------- 1 | #ifndef _ELF_H_ 2 | #define _ELF_H_ 3 | 4 | #include "util/types.h" 5 | #include "process.h" 6 | 7 | #define MAX_CMDLINE_ARGS 64 8 | 9 | // elf header structure 10 | typedef struct elf_header_t { 11 | uint32 magic; 12 | uint8 elf[12]; 13 | uint16 type; /* Object file type */ 14 | uint16 machine; /* Architecture */ 15 | uint32 version; /* Object file version */ 16 | uint64 entry; /* Entry point virtual address */ 17 | uint64 phoff; /* Program header table file offset */ 18 | uint64 shoff; /* Section header table file offset */ 19 | uint32 flags; /* Processor-specific flags */ 20 | uint16 ehsize; /* ELF header size in bytes */ 21 | uint16 phentsize; /* Program header table entry size */ 22 | uint16 phnum; /* Program header table entry count */ 23 | uint16 shentsize; /* Section header table entry size */ 24 | uint16 shnum; /* Section header table entry count */ 25 | uint16 shstrndx; /* Section header string table index */ 26 | } elf_header; 27 | 28 | // Program segment header. 29 | typedef struct elf_prog_header_t { 30 | uint32 type; /* Segment type */ 31 | uint32 flags; /* Segment flags */ 32 | uint64 off; /* Segment file offset */ 33 | uint64 vaddr; /* Segment virtual address */ 34 | uint64 paddr; /* Segment physical address */ 35 | uint64 filesz; /* Segment size in file */ 36 | uint64 memsz; /* Segment size in memory */ 37 | uint64 align; /* Segment alignment */ 38 | } elf_prog_header; 39 | 40 | #define ELF_MAGIC 0x464C457FU // "\x7FELF" in little endian 41 | #define ELF_PROG_LOAD 1 42 | 43 | typedef enum elf_status_t { 44 | EL_OK = 0, 45 | 46 | EL_EIO, 47 | EL_ENOMEM, 48 | EL_NOTELF, 49 | EL_ERR, 50 | 51 | } elf_status; 52 | 53 | typedef struct elf_ctx_t { 54 | void *info; 55 | elf_header ehdr; 56 | } elf_ctx; 57 | 58 | elf_status elf_init(elf_ctx *ctx, void *info); 59 | elf_status elf_load(elf_ctx *ctx); 60 | 61 | void load_bincode_from_host_elf(process *p); 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /kernel/kernel.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Supervisor-mode startup codes 3 | */ 4 | 5 | #include "riscv.h" 6 | #include "string.h" 7 | #include "elf.h" 8 | #include "process.h" 9 | 10 | #include "spike_interface/spike_utils.h" 11 | 12 | // process is a structure defined in kernel/process.h 13 | process user_app; 14 | 15 | // 16 | // load the elf, and construct a "process" (with only a trapframe). 17 | // load_bincode_from_host_elf is defined in elf.c 18 | // 19 | void load_user_program(process *proc) { 20 | // USER_TRAP_FRAME is a physical address defined in kernel/config.h 21 | proc->trapframe = (trapframe *)USER_TRAP_FRAME; 22 | memset(proc->trapframe, 0, sizeof(trapframe)); 23 | // USER_KSTACK is also a physical address defined in kernel/config.h 24 | proc->kstack = USER_KSTACK; 25 | proc->trapframe->regs.sp = USER_STACK; 26 | 27 | // load_bincode_from_host_elf() is defined in kernel/elf.c 28 | load_bincode_from_host_elf(proc); 29 | } 30 | 31 | // 32 | // s_start: S-mode entry point of riscv-pke OS kernel. 33 | // 34 | int s_start(void) { 35 | sprint("Enter supervisor mode...\n"); 36 | // Note: we use direct (i.e., Bare mode) for memory mapping in lab1. 37 | // which means: Virtual Address = Physical Address 38 | // therefore, we need to set satp to be 0 for now. we will enable paging in lab2_x. 39 | // 40 | // write_csr is a macro defined in kernel/riscv.h 41 | write_csr(satp, 0); 42 | 43 | // the application code (elf) is first loaded into memory, and then put into execution 44 | load_user_program(&user_app); 45 | 46 | sprint("Switch to user mode...\n"); 47 | // switch_to() is defined in kernel/process.c 48 | switch_to(&user_app); 49 | 50 | // we should never reach here. 51 | return 0; 52 | } 53 | -------------------------------------------------------------------------------- /kernel/kernel.lds: -------------------------------------------------------------------------------- 1 | /* See LICENSE for license details. */ 2 | 3 | OUTPUT_ARCH( "riscv" ) 4 | 5 | ENTRY( _mentry ) 6 | 7 | SECTIONS 8 | { 9 | 10 | /*--------------------------------------------------------------------*/ 11 | /* Code and read-only segment */ 12 | /*--------------------------------------------------------------------*/ 13 | 14 | /* Begining of code and text segment, starts from DRAM_BASE to be effective before enabling paging */ 15 | . = 0x80000000; 16 | _ftext = .; 17 | 18 | /* text: Program code section */ 19 | .text : 20 | { 21 | *(.text) 22 | *(.text.*) 23 | *(.gnu.linkonce.t.*) 24 | . = ALIGN(0x1000); 25 | 26 | _trap_sec_start = .; 27 | *(trapsec) 28 | . = ALIGN(0x1000); 29 | /* ASSERT(. - _trap_sec_start == 0x1000, "error: trap section larger than one page"); */ 30 | } 31 | 32 | /* rodata: Read-only data */ 33 | .rodata : 34 | { 35 | *(.rdata) 36 | *(.rodata) 37 | *(.rodata.*) 38 | *(.gnu.linkonce.r.*) 39 | } 40 | 41 | /* End of code and read-only segment */ 42 | . = ALIGN(0x1000); 43 | _etext = .; 44 | 45 | /*--------------------------------------------------------------------*/ 46 | /* HTIF, isolated onto separate page */ 47 | /*--------------------------------------------------------------------*/ 48 | .htif : 49 | { 50 | PROVIDE( __htif_base = . ); 51 | *(.htif) 52 | } 53 | . = ALIGN(0x1000); 54 | 55 | /*--------------------------------------------------------------------*/ 56 | /* Initialized data segment */ 57 | /*--------------------------------------------------------------------*/ 58 | 59 | /* Start of initialized data segment */ 60 | . = ALIGN(16); 61 | _fdata = .; 62 | 63 | /* data: Writable data */ 64 | .data : 65 | { 66 | *(.data) 67 | *(.data.*) 68 | *(.srodata*) 69 | *(.gnu.linkonce.d.*) 70 | *(.comment) 71 | } 72 | 73 | /* End of initialized data segment */ 74 | . = ALIGN(16); 75 | _edata = .; 76 | 77 | /*--------------------------------------------------------------------*/ 78 | /* Uninitialized data segment */ 79 | /*--------------------------------------------------------------------*/ 80 | 81 | /* Start of uninitialized data segment */ 82 | . = .; 83 | _fbss = .; 84 | 85 | /* sbss: Uninitialized writeable small data section */ 86 | . = .; 87 | 88 | /* bss: Uninitialized writeable data section */ 89 | . = .; 90 | _bss_start = .; 91 | .bss : 92 | { 93 | *(.bss) 94 | *(.bss.*) 95 | *(.sbss*) 96 | *(.gnu.linkonce.b.*) 97 | *(COMMON) 98 | } 99 | 100 | . = ALIGN(0x1000); 101 | _end = .; 102 | } 103 | -------------------------------------------------------------------------------- /kernel/machine/mentry.S: -------------------------------------------------------------------------------- 1 | # 2 | # _mentry is the entry point of riscv-pke OS kernel. 3 | # 4 | # !Important (for your understanding) 5 | # Before entering _mentry, two argument registers, i.e., a0(x10) and a1(x11), are set by 6 | # our emulator (i.e., spike). 7 | # [a0] = processor ID (in the context of RISC-V, a processor is called as a HART, i.e., 8 | # Hardware Thread). 9 | # [a1] = pointer to the DTS (i.e., Device Tree String), which is stored in the memory of 10 | # RISC-V guest computer emulated by spike. 11 | # 12 | 13 | .globl _mentry 14 | _mentry: 15 | # [mscratch] = 0; mscratch points the stack bottom of machine mode computer 16 | csrw mscratch, x0 17 | 18 | # following codes allocate a 4096-byte stack for each HART, although we use only 19 | # ONE HART in this lab. 20 | la sp, stack0 # stack0 is statically defined in kernel/machine/minit.c 21 | li a3, 4096 # 4096-byte stack 22 | csrr a4, mhartid # [mhartid] = core ID 23 | addi a4, a4, 1 24 | mul a3, a3, a4 25 | add sp, sp, a3 # re-arrange the stack points so that they don't overlap 26 | 27 | # jump to mstart(), i.e., machine state start function in kernel/machine/minit.c 28 | call m_start 29 | -------------------------------------------------------------------------------- /kernel/machine/minit.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Machine-mode C startup codes 3 | */ 4 | 5 | #include "util/types.h" 6 | #include "kernel/riscv.h" 7 | #include "kernel/config.h" 8 | #include "spike_interface/spike_utils.h" 9 | 10 | // 11 | // global variables are placed in the .data section. 12 | // stack0 is the privilege mode stack(s) of the proxy kernel on CPU(s) 13 | // allocates 4KB stack space for each processor (hart) 14 | // 15 | // NCPU is defined to be 1 in kernel/config.h, as we consider only one HART in basic 16 | // labs. 17 | // 18 | __attribute__((aligned(16))) char stack0[4096 * NCPU]; 19 | 20 | // sstart() is the supervisor state entry point defined in kernel/kernel.c 21 | extern void s_start(); 22 | 23 | // htif is defined in spike_interface/spike_htif.c, marks the availability of HTIF 24 | extern uint64 htif; 25 | // g_mem_size is defined in spike_interface/spike_memory.c, size of the emulated memory 26 | extern uint64 g_mem_size; 27 | 28 | // 29 | // get the information of HTIF (calling interface) and the emulated memory by 30 | // parsing the Device Tree Blog (DTB, actually DTS) stored in memory. 31 | // 32 | // the role of DTB is similar to that of Device Address Resolution Table (DART) 33 | // in Intel series CPUs. it records the details of devices and memory of the 34 | // platform simulated using Spike. 35 | // 36 | void init_dtb(uint64 dtb) { 37 | // defined in spike_interface/spike_htif.c, enabling Host-Target InterFace (HTIF) 38 | query_htif(dtb); 39 | if (htif) sprint("HTIF is available!\r\n"); 40 | 41 | // defined in spike_interface/spike_memory.c, obtain information about emulated memory 42 | query_mem(dtb); 43 | sprint("(Emulated) memory size: %ld MB\n", g_mem_size >> 20); 44 | } 45 | 46 | // 47 | // delegate (almost all) interrupts and most exceptions to S-mode. 48 | // after delegation, syscalls will handled by the PKE OS kernel running in S-mode. 49 | // 50 | static void delegate_traps() { 51 | // supports_extension macro is defined in kernel/riscv.h 52 | if (!supports_extension('S')) { 53 | // confirm that our processor supports supervisor mode. abort if it does not. 54 | sprint("S mode is not supported.\n"); 55 | return; 56 | } 57 | 58 | // macros used in following two statements are defined in kernel/riscv.h 59 | uintptr_t interrupts = MIP_SSIP | MIP_STIP | MIP_SEIP; 60 | uintptr_t exceptions = (1U << CAUSE_MISALIGNED_FETCH) | (1U << CAUSE_FETCH_PAGE_FAULT) | 61 | (1U << CAUSE_BREAKPOINT) | (1U << CAUSE_LOAD_PAGE_FAULT) | 62 | (1U << CAUSE_STORE_PAGE_FAULT) | (1U << CAUSE_USER_ECALL); 63 | 64 | // writes 64-bit values (interrupts and exceptions) to 'mideleg' and 'medeleg' (two 65 | // priviledged registers of RV64G machine) respectively. 66 | // 67 | // write_csr and read_csr are macros defined in kernel/riscv.h 68 | write_csr(mideleg, interrupts); 69 | write_csr(medeleg, exceptions); 70 | assert(read_csr(mideleg) == interrupts); 71 | assert(read_csr(medeleg) == exceptions); 72 | } 73 | 74 | // 75 | // m_start: machine mode C entry point. 76 | // 77 | void m_start(uintptr_t hartid, uintptr_t dtb) { 78 | // init the spike file interface (stdin,stdout,stderr) 79 | // functions with "spike_" prefix are all defined in codes under spike_interface/, 80 | // sprint is also defined in spike_interface/spike_utils.c 81 | spike_file_init(); 82 | sprint("In m_start, hartid:%d\n", hartid); 83 | 84 | // init HTIF (Host-Target InterFace) and memory by using the Device Table Blob (DTB) 85 | // init_dtb() is defined above. 86 | init_dtb(dtb); 87 | 88 | // set previous privilege mode to S (Supervisor), and will enter S mode after 'mret' 89 | // write_csr is a macro defined in kernel/riscv.h 90 | write_csr(mstatus, ((read_csr(mstatus) & ~MSTATUS_MPP_MASK) | MSTATUS_MPP_S)); 91 | 92 | // set M Exception Program Counter to sstart, for mret (requires gcc -mcmodel=medany) 93 | write_csr(mepc, (uint64)s_start); 94 | 95 | // delegate all interrupts and exceptions to supervisor mode. 96 | // delegate_traps() is defined above. 97 | delegate_traps(); 98 | 99 | // switch to supervisor mode (S mode) and jump to s_start(), i.e., set pc to mepc 100 | asm volatile("mret"); 101 | } 102 | -------------------------------------------------------------------------------- /kernel/process.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Utility functions for process management. 3 | * 4 | * Note: in Lab1, only one process (i.e., our user application) exists. Therefore, 5 | * PKE OS at this stage will set "current" to the loaded user application, and also 6 | * switch to the old "current" process after trap handling. 7 | */ 8 | 9 | #include "riscv.h" 10 | #include "strap.h" 11 | #include "config.h" 12 | #include "process.h" 13 | #include "elf.h" 14 | #include "string.h" 15 | 16 | #include "spike_interface/spike_utils.h" 17 | 18 | //Two functions defined in kernel/usertrap.S 19 | extern char smode_trap_vector[]; 20 | extern void return_to_user(trapframe*); 21 | 22 | // current points to the currently running user-mode application. 23 | process* current = NULL; 24 | 25 | // 26 | // switch to a user-mode process 27 | // 28 | void switch_to(process* proc) { 29 | assert(proc); 30 | current = proc; 31 | 32 | // write the smode_trap_vector (64-bit func. address) defined in kernel/strap_vector.S 33 | // to the stvec privilege register, such that trap handler pointed by smode_trap_vector 34 | // will be triggered when an interrupt occurs in S mode. 35 | write_csr(stvec, (uint64)smode_trap_vector); 36 | 37 | // set up trapframe values (in process structure) that smode_trap_vector will need when 38 | // the process next re-enters the kernel. 39 | proc->trapframe->kernel_sp = proc->kstack; // process's kernel stack 40 | proc->trapframe->kernel_trap = (uint64)smode_trap_handler; 41 | 42 | // SSTATUS_SPP and SSTATUS_SPIE are defined in kernel/riscv.h 43 | // set S Previous Privilege mode (the SSTATUS_SPP bit in sstatus register) to User mode. 44 | unsigned long x = read_csr(sstatus); 45 | x &= ~SSTATUS_SPP; // clear SPP to 0 for user mode 46 | x |= SSTATUS_SPIE; // enable interrupts in user mode 47 | 48 | // write x back to 'sstatus' register to enable interrupts, and sret destination mode. 49 | write_csr(sstatus, x); 50 | 51 | // set S Exception Program Counter (sepc register) to the elf entry pc. 52 | write_csr(sepc, proc->trapframe->epc); 53 | 54 | // return_to_user() is defined in kernel/strap_vector.S. switch to user mode with sret. 55 | return_to_user(proc->trapframe); 56 | } 57 | -------------------------------------------------------------------------------- /kernel/process.h: -------------------------------------------------------------------------------- 1 | #ifndef _PROC_H_ 2 | #define _PROC_H_ 3 | 4 | #include "riscv.h" 5 | 6 | typedef struct trapframe_t { 7 | // space to store context (all common registers) 8 | /* offset:0 */ riscv_regs regs; 9 | 10 | // process's "user kernel" stack 11 | /* offset:248 */ uint64 kernel_sp; 12 | // pointer to smode_trap_handler 13 | /* offset:256 */ uint64 kernel_trap; 14 | // saved user process counter 15 | /* offset:264 */ uint64 epc; 16 | }trapframe; 17 | 18 | // the extremely simple definition of process, used for begining labs of PKE 19 | typedef struct process_t { 20 | // pointing to the stack used in trap handling. 21 | uint64 kstack; 22 | // trapframe storing the context of a (User mode) process. 23 | trapframe* trapframe; 24 | }process; 25 | 26 | void switch_to(process*); 27 | 28 | extern process* current; 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /kernel/riscv.h: -------------------------------------------------------------------------------- 1 | #ifndef _RISCV_H_ 2 | #define _RISCV_H_ 3 | 4 | #include "util/types.h" 5 | #include "config.h" 6 | 7 | // fields of mstatus, the Machine mode Status register 8 | #define MSTATUS_MPP_MASK (3L << 11) // previous mode mask 9 | #define MSTATUS_MPP_M (3L << 11) // machine mode (m-mode) 10 | #define MSTATUS_MPP_S (1L << 11) // supervisor mode (s-mode) 11 | #define MSTATUS_MPP_U (0L << 11) // user mode (u-mode) 12 | #define MSTATUS_MIE (1L << 3) // machine-mode interrupt enable 13 | #define MSTATUS_MPIE (1L << 7) // preserve MIE bit 14 | 15 | // values of mcause, the Machine Cause register 16 | #define IRQ_S_EXT 9 // s-mode external interrupt 17 | #define IRQ_S_TIMER 5 // s-mode timer interrupt 18 | #define IRQ_S_SOFT 1 // s-mode software interrupt 19 | #define IRQ_M_SOFT 3 // m-mode software interrupt 20 | 21 | // fields of mip, the Machine Interrupt Pending register 22 | #define MIP_SEIP (1 << IRQ_S_EXT) // s-mode external interrupt pending 23 | #define MIP_SSIP (1 << IRQ_S_SOFT) // s-mode software interrupt pending 24 | #define MIP_STIP (1 << IRQ_S_TIMER) // s-mode timer interrupt pending 25 | #define MIP_MSIP (1 << IRQ_M_SOFT) // m-mode software interrupt pending 26 | 27 | // pysical memory protection choices 28 | #define PMP_R 0x01 29 | #define PMP_W 0x02 30 | #define PMP_X 0x04 31 | #define PMP_A 0x18 32 | #define PMP_L 0x80 33 | #define PMP_SHIFT 2 34 | 35 | #define PMP_TOR 0x08 36 | #define PMP_NA4 0x10 37 | #define PMP_NAPOT 0x18 38 | 39 | // exceptions 40 | #define CAUSE_MISALIGNED_FETCH 0x0 // Instruction address misaligned 41 | #define CAUSE_FETCH_ACCESS 0x1 // Instruction access fault 42 | #define CAUSE_ILLEGAL_INSTRUCTION 0x2 // Illegal Instruction 43 | #define CAUSE_BREAKPOINT 0x3 // Breakpoint 44 | #define CAUSE_MISALIGNED_LOAD 0x4 // Load address misaligned 45 | #define CAUSE_LOAD_ACCESS 0x5 // Load access fault 46 | #define CAUSE_MISALIGNED_STORE 0x6 // Store/AMO address misaligned 47 | #define CAUSE_STORE_ACCESS 0x7 // Store/AMO access fault 48 | #define CAUSE_USER_ECALL 0x8 // Environment call from U-mode 49 | #define CAUSE_SUPERVISOR_ECALL 0x9 // Environment call from S-mode 50 | #define CAUSE_MACHINE_ECALL 0xb // Environment call from M-mode 51 | #define CAUSE_FETCH_PAGE_FAULT 0xc // Instruction page fault 52 | #define CAUSE_LOAD_PAGE_FAULT 0xd // Load page fault 53 | #define CAUSE_STORE_PAGE_FAULT 0xf // Store/AMO page fault 54 | 55 | // fields of sstatus, the Supervisor mode Status register 56 | #define SSTATUS_SPP (1L << 8) // Previous mode, 1=Supervisor, 0=User 57 | #define SSTATUS_SPIE (1L << 5) // Supervisor Previous Interrupt Enable 58 | #define SSTATUS_UPIE (1L << 4) // User Previous Interrupt Enable 59 | #define SSTATUS_SIE (1L << 1) // Supervisor Interrupt Enable 60 | #define SSTATUS_UIE (1L << 0) // User Interrupt Enable 61 | #define SSTATUS_SUM 0x00040000 62 | #define SSTATUS_FS 0x00006000 63 | 64 | // Supervisor Interrupt Enable 65 | #define SIE_SEIE (1L << 9) // external 66 | #define SIE_STIE (1L << 5) // timer 67 | #define SIE_SSIE (1L << 1) // software 68 | 69 | // Machine-mode Interrupt Enable 70 | #define MIE_MEIE (1L << 11) // external 71 | #define MIE_MTIE (1L << 7) // timer 72 | #define MIE_MSIE (1L << 3) // software 73 | 74 | #define read_const_csr(reg) \ 75 | ({ \ 76 | unsigned long __tmp; \ 77 | asm("csrr %0, " #reg : "=r"(__tmp)); \ 78 | __tmp; \ 79 | }) 80 | 81 | static inline int supports_extension(char ext) { 82 | return read_const_csr(misa) & (1 << (ext - 'A')); 83 | } 84 | 85 | #define read_csr(reg) \ 86 | ({ \ 87 | unsigned long __tmp; \ 88 | asm volatile("csrr %0, " #reg : "=r"(__tmp)); \ 89 | __tmp; \ 90 | }) 91 | 92 | #define write_csr(reg, val) ({ asm volatile("csrw " #reg ", %0" ::"rK"(val)); }) 93 | 94 | #define swap_csr(reg, val) \ 95 | ({ \ 96 | unsigned long __tmp; \ 97 | asm volatile("csrrw %0, " #reg ", %1" : "=r"(__tmp) : "rK"(val)); \ 98 | __tmp; \ 99 | }) 100 | 101 | #define set_csr(reg, bit) \ 102 | ({ \ 103 | unsigned long __tmp; \ 104 | asm volatile("csrrs %0, " #reg ", %1" : "=r"(__tmp) : "rK"(bit)); \ 105 | __tmp; \ 106 | }) 107 | 108 | // enable device interrupts 109 | static inline void intr_on(void) { write_csr(sstatus, read_csr(sstatus) | SSTATUS_SIE); } 110 | 111 | // disable device interrupts 112 | static inline void intr_off(void) { write_csr(sstatus, read_csr(sstatus) & ~SSTATUS_SIE); } 113 | 114 | // are device interrupts enabled? 115 | static inline int is_intr_enable(void) { 116 | // uint64 x = r_sstatus(); 117 | uint64 x = read_csr(sstatus); 118 | return (x & SSTATUS_SIE) != 0; 119 | } 120 | 121 | // read sp, the stack pointer 122 | static inline uint64 read_sp(void) { 123 | uint64 x; 124 | asm volatile("mv %0, sp" : "=r"(x)); 125 | return x; 126 | } 127 | 128 | // read tp, the thread pointer, holding hartid (core number), the index into cpus[]. 129 | static inline uint64 read_tp(void) { 130 | uint64 x; 131 | asm volatile("mv %0, tp" : "=r"(x)); 132 | return x; 133 | } 134 | 135 | // write tp, the thread pointer, holding hartid (core number), the index into cpus[]. 136 | static inline void write_tp(uint64 x) { asm volatile("mv tp, %0" : : "r"(x)); } 137 | 138 | typedef struct riscv_regs_t { 139 | /* 0 */ uint64 ra; 140 | /* 8 */ uint64 sp; 141 | /* 16 */ uint64 gp; 142 | /* 24 */ uint64 tp; 143 | /* 32 */ uint64 t0; 144 | /* 40 */ uint64 t1; 145 | /* 48 */ uint64 t2; 146 | /* 56 */ uint64 s0; 147 | /* 64 */ uint64 s1; 148 | /* 72 */ uint64 a0; 149 | /* 80 */ uint64 a1; 150 | /* 88 */ uint64 a2; 151 | /* 96 */ uint64 a3; 152 | /* 104 */ uint64 a4; 153 | /* 112 */ uint64 a5; 154 | /* 120 */ uint64 a6; 155 | /* 128 */ uint64 a7; 156 | /* 136 */ uint64 s2; 157 | /* 144 */ uint64 s3; 158 | /* 152 */ uint64 s4; 159 | /* 160 */ uint64 s5; 160 | /* 168 */ uint64 s6; 161 | /* 176 */ uint64 s7; 162 | /* 184 */ uint64 s8; 163 | /* 192 */ uint64 s9; 164 | /* 196 */ uint64 s10; 165 | /* 208 */ uint64 s11; 166 | /* 216 */ uint64 t3; 167 | /* 224 */ uint64 t4; 168 | /* 232 */ uint64 t5; 169 | /* 240 */ uint64 t6; 170 | }riscv_regs; 171 | 172 | #endif 173 | -------------------------------------------------------------------------------- /kernel/strap.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Utility functions for trap handling in Supervisor mode. 3 | */ 4 | 5 | #include "riscv.h" 6 | #include "process.h" 7 | #include "strap.h" 8 | #include "syscall.h" 9 | 10 | #include "spike_interface/spike_utils.h" 11 | 12 | // 13 | // handling the syscalls. will call do_syscall() defined in kernel/syscall.c 14 | // 15 | static void handle_syscall(trapframe *tf) { 16 | // tf->epc points to the address that our computer will jump to after the trap handling. 17 | // for a syscall, we should return to the NEXT instruction after its handling. 18 | // in RV64G, each instruction occupies exactly 32 bits (i.e., 4 Bytes) 19 | tf->epc += 4; 20 | 21 | // TODO (lab1_1): remove the panic call below, and call do_syscall (defined in 22 | // kernel/syscall.c) to conduct real operations of the kernel side for a syscall. 23 | // IMPORTANT: return value should be returned to user app, or else, you will encounter 24 | // problems in later experiments! 25 | //panic( "call do_syscall to accomplish the syscall and lab1_1 here.\n" ); 26 | tf->regs.a0 = do_syscall(tf->regs.a0,tf->regs.a1,tf->regs.a2,tf->regs.a3,tf->regs.a4,tf->regs.a5,tf->regs.a6,tf->regs.a7); 27 | } 28 | 29 | // 30 | // kernel/smode_trap.S will pass control to smode_trap_handler, when a trap happens 31 | // in S-mode. 32 | // 33 | void smode_trap_handler(void) { 34 | // make sure we are in User mode before entering the trap handling. 35 | // we will consider other previous case in lab1_3 (interrupt). 36 | if ((read_csr(sstatus) & SSTATUS_SPP) != 0) panic("usertrap: not from user mode"); 37 | 38 | assert(current); 39 | // save user process counter. 40 | current->trapframe->epc = read_csr(sepc); 41 | 42 | // if the cause of trap is syscall from user application. 43 | // read_csr() and CAUSE_USER_ECALL are macros defined in kernel/riscv.h 44 | if (read_csr(scause) == CAUSE_USER_ECALL) { 45 | handle_syscall(current->trapframe); 46 | } else { 47 | sprint("smode_trap_handler(): unexpected scause %p\n", read_csr(scause)); 48 | sprint(" sepc=%p stval=%p\n", read_csr(sepc), read_csr(stval)); 49 | panic( "unexpected exception happened.\n" ); 50 | } 51 | 52 | // continue (come back to) the execution of current process. 53 | switch_to(current); 54 | } 55 | -------------------------------------------------------------------------------- /kernel/strap.h: -------------------------------------------------------------------------------- 1 | #ifndef _STRAP_H_ 2 | #define _STRAP_H_ 3 | 4 | void smode_trap_handler(void); 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /kernel/strap_vector.S: -------------------------------------------------------------------------------- 1 | .section trapsec 2 | .globl trap_sec_start 3 | trap_sec_start: 4 | 5 | #include "util/load_store.S" 6 | 7 | # 8 | # When a trap (e.g., a syscall from User mode in this lab) happens and the computer 9 | # enters the Supervisor mode, the computer will continue to execute the following 10 | # function (smode_trap_vector) to actually handle the trap. 11 | # 12 | # NOTE: sscratch points to the trapframe of current process before entering 13 | # smode_trap_vector. It is done by reture_to_user function (defined below) when 14 | # scheduling a user-mode application to run. 15 | # 16 | .globl smode_trap_vector 17 | .align 4 18 | smode_trap_vector: 19 | # swap a0 and sscratch, so that points a0 to the trapframe of current process 20 | csrrw a0, sscratch, a0 21 | 22 | # save the context (user registers) of current process in its trapframe. 23 | addi t6, a0 , 0 24 | 25 | # store_all_registers is a macro defined in util/load_store.S, it stores contents 26 | # of all general purpose registers into a piece of memory started from [t6]. 27 | store_all_registers 28 | 29 | # come back to save a0 register before entering trap handling in trapframe 30 | # [t0]=[sscratch] 31 | csrr t0, sscratch 32 | sd t0, 72(a0) 33 | 34 | # use the "user kernel" stack (whose pointer stored in p->trapframe->kernel_sp) 35 | ld sp, 248(a0) 36 | 37 | # load the address of smode_trap_handler() from p->trapframe->kernel_trap 38 | ld t0, 256(a0) 39 | 40 | # jump to smode_trap_handler() that is defined in kernel/trap.c 41 | jr t0 42 | 43 | # 44 | # return from Supervisor mode to User mode, transition is made by using a trapframe, 45 | # which stores the context of a user application. 46 | # return_to_user() takes one parameter, i.e., the pointer (a0 register) pointing to a 47 | # trapframe (defined in kernel/process.h) of the process. 48 | # 49 | .globl return_to_user 50 | return_to_user: 51 | # [sscratch]=[a0], save a0 in sscratch, so sscratch points to a trapframe now. 52 | csrw sscratch, a0 53 | 54 | # let [t6]=[a0] 55 | addi t6, a0, 0 56 | 57 | # restore_all_registers is a assembly macro defined in util/load_store.S. 58 | # the macro restores all registers from trapframe started from [t6] to all general 59 | # purpose registers, so as to resort the execution of a process. 60 | restore_all_registers 61 | 62 | # return to user mode and user pc. 63 | sret 64 | -------------------------------------------------------------------------------- /kernel/syscall.c: -------------------------------------------------------------------------------- 1 | /* 2 | * contains the implementation of all syscalls. 3 | */ 4 | 5 | #include 6 | #include 7 | 8 | #include "util/types.h" 9 | #include "syscall.h" 10 | #include "string.h" 11 | #include "process.h" 12 | #include "util/functions.h" 13 | 14 | #include "spike_interface/spike_utils.h" 15 | 16 | // 17 | // implement the SYS_user_print syscall 18 | // 19 | ssize_t sys_user_print(const char* buf, size_t n) { 20 | sprint(buf); 21 | return 0; 22 | } 23 | 24 | // 25 | // implement the SYS_user_exit syscall 26 | // 27 | ssize_t sys_user_exit(uint64 code) { 28 | sprint("User exit with code:%d.\n", code); 29 | // in lab1, PKE considers only one app (one process). 30 | // therefore, shutdown the system when the app calls exit() 31 | shutdown(code); 32 | } 33 | 34 | // 35 | // [a0]: the syscall number; [a1] ... [a7]: arguments to the syscalls. 36 | // returns the code of success, (e.g., 0 means success, fail for otherwise) 37 | // 38 | long do_syscall(long a0, long a1, long a2, long a3, long a4, long a5, long a6, long a7) { 39 | switch (a0) { 40 | case SYS_user_print: 41 | return sys_user_print((const char*)a1, a2); 42 | case SYS_user_exit: 43 | return sys_user_exit(a1); 44 | default: 45 | panic("Unknown syscall %ld \n", a0); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /kernel/syscall.h: -------------------------------------------------------------------------------- 1 | /* 2 | * define the syscall numbers of PKE OS kernel. 3 | */ 4 | #ifndef _SYSCALL_H_ 5 | #define _SYSCALL_H_ 6 | 7 | // syscalls of PKE OS kernel. append below if adding new syscalls. 8 | #define SYS_user_base 64 9 | #define SYS_user_print (SYS_user_base + 0) 10 | #define SYS_user_exit (SYS_user_base + 1) 11 | 12 | long do_syscall(long a0, long a1, long a2, long a3, long a4, long a5, long a6, long a7); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darzz0/HUST-operating-system-lab/25c44e29bb5823920ec0a7d0f034ac3d32d8f82b/preview.png -------------------------------------------------------------------------------- /spike_interface/atomic.h: -------------------------------------------------------------------------------- 1 | // See LICENSE for license details. 2 | // borrowed from https://github.com/riscv/riscv-pk: 3 | // machine/atomic.h 4 | 5 | #ifndef _RISCV_ATOMIC_H_ 6 | #define _RISCV_ATOMIC_H_ 7 | 8 | // Currently, interrupts are always disabled in M-mode. 9 | // todo: for PKE, wo turn on irq in lab_1_3_timer, so wo have to implement these two functions. 10 | #define disable_irqsave() (0) 11 | #define enable_irqrestore(flags) ((void)(flags)) 12 | 13 | typedef struct { 14 | int lock; 15 | // For debugging: 16 | char* name; // Name of lock. 17 | struct cpu* cpu; // The cpu holding the lock. 18 | } spinlock_t; 19 | 20 | #define SPINLOCK_INIT \ 21 | { 0 } 22 | 23 | #define mb() asm volatile("fence" ::: "memory") 24 | #define atomic_set(ptr, val) (*(volatile typeof(*(ptr))*)(ptr) = val) 25 | #define atomic_read(ptr) (*(volatile typeof(*(ptr))*)(ptr)) 26 | 27 | #define atomic_binop(ptr, inc, op) \ 28 | ({ \ 29 | long flags = disable_irqsave(); \ 30 | typeof(*(ptr)) res = atomic_read(ptr); \ 31 | atomic_set(ptr, op); \ 32 | enable_irqrestore(flags); \ 33 | res; \ 34 | }) 35 | #define atomic_add(ptr, inc) atomic_binop(ptr, inc, res + (inc)) 36 | #define atomic_or(ptr, inc) atomic_binop(ptr, inc, res | (inc)) 37 | #define atomic_swap(ptr, inc) atomic_binop(ptr, inc, (inc)) 38 | #define atomic_cas(ptr, cmp, swp) \ 39 | ({ \ 40 | long flags = disable_irqsave(); \ 41 | typeof(*(ptr)) res = *(volatile typeof(*(ptr))*)(ptr); \ 42 | if (res == (cmp)) *(volatile typeof(ptr))(ptr) = (swp); \ 43 | enable_irqrestore(flags); \ 44 | res; \ 45 | }) 46 | 47 | static inline int spinlock_trylock(spinlock_t* lock) { 48 | int res = atomic_swap(&lock->lock, -1); 49 | mb(); 50 | return res; 51 | } 52 | 53 | static inline void spinlock_lock(spinlock_t* lock) { 54 | do { 55 | while (atomic_read(&lock->lock)) 56 | ; 57 | } while (spinlock_trylock(lock)); 58 | } 59 | 60 | static inline void spinlock_unlock(spinlock_t* lock) { 61 | mb(); 62 | atomic_set(&lock->lock, 0); 63 | } 64 | 65 | static inline long spinlock_lock_irqsave(spinlock_t* lock) { 66 | long flags = disable_irqsave(); 67 | spinlock_lock(lock); 68 | return flags; 69 | } 70 | 71 | static inline void spinlock_unlock_irqrestore(spinlock_t* lock, long flags) { 72 | spinlock_unlock(lock); 73 | enable_irqrestore(flags); 74 | } 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /spike_interface/dts_parse.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Utility functions scanning the Flattened Device Tree (FDT), stored in DTS (Device Tree String). 3 | * 4 | * codes are borrowed from riscv-pk (https://github.com/riscv/riscv-pk) 5 | */ 6 | 7 | #include "dts_parse.h" 8 | #include "spike_interface/spike_utils.h" 9 | #include "string.h" 10 | 11 | static inline uint32 bswap(uint32 x) { 12 | uint32 y = (x & 0x00FF00FF) << 8 | (x & 0xFF00FF00) >> 8; 13 | uint32 z = (y & 0x0000FFFF) << 16 | (y & 0xFFFF0000) >> 16; 14 | return z; 15 | } 16 | 17 | static uint32 *fdt_scan_helper(uint32 *lex, const char *strings, struct fdt_scan_node *node, 18 | const struct fdt_cb *cb) { 19 | struct fdt_scan_node child; 20 | struct fdt_scan_prop prop; 21 | int last = 0; 22 | 23 | child.parent = node; 24 | // these are the default cell counts, as per the FDT spec 25 | child.address_cells = 2; 26 | child.size_cells = 1; 27 | prop.node = node; 28 | 29 | while (1) { 30 | switch (bswap(lex[0])) { 31 | case FDT_NOP: { 32 | lex += 1; 33 | break; 34 | } 35 | case FDT_PROP: { 36 | assert(!last); 37 | prop.name = strings + bswap(lex[2]); 38 | prop.len = bswap(lex[1]); 39 | prop.value = lex + 3; 40 | if (node && !strcmp(prop.name, "#address-cells")) { 41 | node->address_cells = bswap(lex[3]); 42 | } 43 | if (node && !strcmp(prop.name, "#size-cells")) { 44 | node->size_cells = bswap(lex[3]); 45 | } 46 | lex += 3 + (prop.len + 3) / 4; 47 | cb->prop(&prop, cb->extra); 48 | break; 49 | } 50 | case FDT_BEGIN_NODE: { 51 | uint32 *lex_next; 52 | if (!last && node && cb->done) cb->done(node, cb->extra); 53 | last = 1; 54 | child.name = (const char *)(lex + 1); 55 | if (cb->open) cb->open(&child, cb->extra); 56 | lex_next = fdt_scan_helper(lex + 2 + strlen(child.name) / 4, strings, &child, cb); 57 | if (cb->close && cb->close(&child, cb->extra) == -1) 58 | while (lex != lex_next) *lex++ = bswap(FDT_NOP); 59 | lex = lex_next; 60 | break; 61 | } 62 | case FDT_END_NODE: { 63 | if (!last && node && cb->done) cb->done(node, cb->extra); 64 | return lex + 1; 65 | } 66 | default: { // FDT_END 67 | if (!last && node && cb->done) cb->done(node, cb->extra); 68 | return lex; 69 | } 70 | } 71 | } 72 | } 73 | 74 | const uint32 *fdt_get_address(const struct fdt_scan_node *node, const uint32 *value, 75 | uint64 *result) { 76 | *result = 0; 77 | for (int cells = node->address_cells; cells > 0; --cells) 78 | *result = (*result << 32) + bswap(*value++); 79 | return value; 80 | } 81 | 82 | const uint32 *fdt_get_size(const struct fdt_scan_node *node, const uint32 *value, uint64 *result) { 83 | *result = 0; 84 | for (int cells = node->size_cells; cells > 0; --cells) 85 | *result = (*result << 32) + bswap(*value++); 86 | return value; 87 | } 88 | 89 | void fdt_scan(uint64 fdt, const struct fdt_cb *cb) { 90 | struct fdt_header *header = (struct fdt_header *)fdt; 91 | 92 | // Only process FDT that we understand 93 | if (bswap(header->magic) != FDT_MAGIC || bswap(header->last_comp_version) > FDT_VERSION) return; 94 | 95 | const char *strings = (const char *)(fdt + bswap(header->off_dt_strings)); 96 | uint32 *lex = (uint32 *)(fdt + bswap(header->off_dt_struct)); 97 | 98 | fdt_scan_helper(lex, strings, 0, cb); 99 | } 100 | -------------------------------------------------------------------------------- /spike_interface/dts_parse.h: -------------------------------------------------------------------------------- 1 | #ifndef _DT_PARSE_H_ 2 | #define _DT_PARSE_H_ 3 | 4 | #include "util/types.h" 5 | 6 | #define FDT_MAGIC 0xd00dfeed 7 | #define FDT_VERSION 17 8 | 9 | struct fdt_header { 10 | uint32 magic; 11 | uint32 totalsize; 12 | uint32 off_dt_struct; 13 | uint32 off_dt_strings; 14 | uint32 off_mem_rsvmap; 15 | uint32 version; 16 | uint32 last_comp_version; /* <= 17 */ 17 | uint32 boot_cpuid_phys; 18 | uint32 size_dt_strings; 19 | uint32 size_dt_struct; 20 | }; 21 | 22 | #define FDT_BEGIN_NODE 1 23 | #define FDT_END_NODE 2 24 | #define FDT_PROP 3 25 | #define FDT_NOP 4 26 | #define FDT_END 9 27 | 28 | struct fdt_scan_node { 29 | const struct fdt_scan_node *parent; 30 | const char *name; 31 | int address_cells; 32 | int size_cells; 33 | }; 34 | 35 | struct fdt_scan_prop { 36 | const struct fdt_scan_node *node; 37 | const char *name; 38 | uint32 *value; 39 | int len; // in bytes of value 40 | }; 41 | 42 | struct fdt_cb { 43 | void (*open)(const struct fdt_scan_node *node, void *extra); 44 | void (*prop)(const struct fdt_scan_prop *prop, void *extra); 45 | void (*done)(const struct fdt_scan_node *node, 46 | void *extra); // last property was seen 47 | int (*close)(const struct fdt_scan_node *node, 48 | void *extra); // -1 => delete the node + children 49 | void *extra; 50 | }; 51 | 52 | // Scan the contents of FDT 53 | void fdt_scan(uint64 fdt, const struct fdt_cb *cb); 54 | uint32 fdt_size(uint64 fdt); 55 | 56 | // Extract fields 57 | const uint32 *fdt_get_address(const struct fdt_scan_node *node, const uint32 *base, uint64 *value); 58 | const uint32 *fdt_get_size(const struct fdt_scan_node *node, const uint32 *base, uint64 *value); 59 | int fdt_string_list_index(const struct fdt_scan_prop *prop, 60 | const char *str); // -1 if not found 61 | #endif 62 | -------------------------------------------------------------------------------- /spike_interface/spike_file.c: -------------------------------------------------------------------------------- 1 | /* 2 | * accessing host files by using the Spike interface. 3 | * 4 | * PKE OS needs to access the host file duing its execution to conduct ELF (application) loading. 5 | * 6 | * codes are borrowed from riscv-pk (https://github.com/riscv/riscv-pk) 7 | */ 8 | 9 | #include "spike_file.h" 10 | #include "spike_htif.h" 11 | #include "atomic.h" 12 | #include "string.h" 13 | #include "util/functions.h" 14 | #include "spike_interface/spike_utils.h" 15 | //#include "../kernel/config.h" 16 | 17 | #define MAX_FILES 128 18 | #define MAX_FDS 128 19 | static spike_file_t* spike_fds[MAX_FDS]; 20 | spike_file_t spike_files[MAX_FILES] = {[0 ... MAX_FILES - 1] = {-1, 0}}; 21 | 22 | void copy_stat(struct stat* dest_va, struct frontend_stat* src) { 23 | struct stat* dest = (struct stat*)dest_va; 24 | dest->st_dev = src->dev; 25 | dest->st_ino = src->ino; 26 | dest->st_mode = src->mode; 27 | dest->st_nlink = src->nlink; 28 | dest->st_uid = src->uid; 29 | dest->st_gid = src->gid; 30 | dest->st_rdev = src->rdev; 31 | dest->st_size = src->size; 32 | dest->st_blksize = src->blksize; 33 | dest->st_blocks = src->blocks; 34 | dest->st_atime = src->atime; 35 | dest->st_mtime = src->mtime; 36 | dest->st_ctime = src->ctime; 37 | } 38 | 39 | int spike_file_stat(spike_file_t* f, struct stat* s) { 40 | struct frontend_stat buf; 41 | uint64 pa = (uint64)&buf; 42 | long ret = frontend_syscall(HTIFSYS_fstat, f->kfd, (uint64)&buf, 0, 0, 0, 0, 0); 43 | copy_stat(s, &buf); 44 | return ret; 45 | } 46 | 47 | int spike_file_close(spike_file_t* f) { 48 | if (!f) return -1; 49 | spike_file_t* old = atomic_cas(&spike_fds[f->kfd], f, 0); 50 | spike_file_decref(f); 51 | if (old != f) return -1; 52 | spike_file_decref(f); 53 | return 0; 54 | } 55 | 56 | void spike_file_decref(spike_file_t* f) { 57 | if (atomic_add(&f->refcnt, -1) == 2) { 58 | int kfd = f->kfd; 59 | mb(); 60 | atomic_set(&f->refcnt, 0); 61 | 62 | frontend_syscall(HTIFSYS_close, kfd, 0, 0, 0, 0, 0, 0); 63 | } 64 | } 65 | 66 | void spike_file_incref(spike_file_t* f) { 67 | long prev = atomic_add(&f->refcnt, 1); 68 | kassert(prev > 0); 69 | } 70 | 71 | ssize_t spike_file_write(spike_file_t* f, const void* buf, size_t size) { 72 | return frontend_syscall(HTIFSYS_write, f->kfd, (uint64)buf, size, 0, 0, 0, 0); 73 | } 74 | 75 | static spike_file_t* spike_file_get_free(void) { 76 | for (spike_file_t* f = spike_files; f < spike_files + MAX_FILES; f++) 77 | if (atomic_read(&f->refcnt) == 0 && atomic_cas(&f->refcnt, 0, INIT_FILE_REF) == 0) 78 | return f; 79 | return NULL; 80 | } 81 | 82 | int spike_file_dup(spike_file_t* f) { 83 | for (int i = 0; i < MAX_FDS; i++) { 84 | if (atomic_cas(&spike_fds[i], 0, f) == 0) { 85 | spike_file_incref(f); 86 | return i; 87 | } 88 | } 89 | return -1; 90 | } 91 | 92 | void spike_file_init(void) { 93 | // create stdin, stdout, stderr and FDs 0-2 94 | for (int i = 0; i < 3; i++) { 95 | spike_file_t* f = spike_file_get_free(); 96 | f->kfd = i; 97 | spike_file_dup(f); 98 | } 99 | } 100 | 101 | spike_file_t* spike_file_openat(int dirfd, const char* fn, int flags, int mode) { 102 | spike_file_t* f = spike_file_get_free(); 103 | if (f == NULL) return ERR_PTR(-ENOMEM); 104 | 105 | size_t fn_size = strlen(fn) + 1; 106 | long ret = frontend_syscall(HTIFSYS_openat, dirfd, (uint64)fn, fn_size, flags, mode, 0, 0); 107 | if (ret >= 0) { 108 | f->kfd = ret; 109 | return f; 110 | } else { 111 | spike_file_decref(f); 112 | return ERR_PTR(ret); 113 | } 114 | } 115 | 116 | spike_file_t* spike_file_open(const char* fn, int flags, int mode) { 117 | return spike_file_openat(AT_FDCWD, fn, flags, mode); 118 | } 119 | 120 | ssize_t spike_file_pread(spike_file_t* f, void* buf, size_t size, off_t offset) { 121 | return frontend_syscall(HTIFSYS_pread, f->kfd, (uint64)buf, size, offset, 0, 0, 0); 122 | } 123 | 124 | ssize_t spike_file_read(spike_file_t* f, void* buf, size_t size) { 125 | return frontend_syscall(HTIFSYS_read, f->kfd, (uint64)buf, size, 0, 0, 0, 0); 126 | } 127 | 128 | ssize_t spike_file_lseek(spike_file_t* f, size_t ptr, int dir) { 129 | return frontend_syscall(HTIFSYS_lseek, f->kfd, ptr, dir, 0, 0, 0, 0); 130 | } 131 | -------------------------------------------------------------------------------- /spike_interface/spike_file.h: -------------------------------------------------------------------------------- 1 | #ifndef _SPIKE_FILE_H_ 2 | #define _SPIKE_FILE_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include "util/types.h" 8 | 9 | typedef struct file { 10 | int kfd; // file descriptor of the host file 11 | uint32 refcnt; 12 | } spike_file_t; 13 | 14 | extern spike_file_t spike_files[]; 15 | 16 | #define O_RDONLY 00 17 | #define O_WRONLY 01 18 | #define O_RDWR 02 19 | #define ENOMEM 12 /* Out of memory */ 20 | 21 | #define stdin (spike_files + 0) 22 | #define stdout (spike_files + 1) 23 | #define stderr (spike_files + 2) 24 | 25 | #define INIT_FILE_REF 3 26 | 27 | struct frontend_stat { 28 | uint64 dev; 29 | uint64 ino; 30 | uint32 mode; 31 | uint32 nlink; 32 | uint32 uid; 33 | uint32 gid; 34 | uint64 rdev; 35 | uint64 __pad1; 36 | uint64 size; 37 | uint32 blksize; 38 | uint32 __pad2; 39 | uint64 blocks; 40 | uint64 atime; 41 | uint64 __pad3; 42 | uint64 mtime; 43 | uint64 __pad4; 44 | uint64 ctime; 45 | uint64 __pad5; 46 | uint32 __unused4; 47 | uint32 __unused5; 48 | }; 49 | 50 | void copy_stat(struct stat* dest, struct frontend_stat* src); 51 | spike_file_t* spike_file_open(const char* fn, int flags, int mode); 52 | int spike_file_close(spike_file_t* f); 53 | spike_file_t* spike_file_openat(int dirfd, const char* fn, int flags, int mode); 54 | ssize_t spike_file_lseek(spike_file_t* f, size_t ptr, int dir); 55 | ssize_t spike_file_read(spike_file_t* f, void* buf, size_t size); 56 | ssize_t spike_file_pread(spike_file_t* f, void* buf, size_t n, off_t off); 57 | ssize_t spike_file_write(spike_file_t* f, const void* buf, size_t n); 58 | void spike_file_decref(spike_file_t* f); 59 | void spike_file_init(void); 60 | int spike_file_dup(spike_file_t* f); 61 | int spike_file_truncate(spike_file_t* f, off_t len); 62 | int spike_file_stat(spike_file_t* f, struct stat* s); 63 | 64 | #endif 65 | -------------------------------------------------------------------------------- /spike_interface/spike_htif.c: -------------------------------------------------------------------------------- 1 | /* 2 | * HTIF (Host-Target InterFace) scanning. 3 | * output: the availability of HTIF (indicated by "uint64 htif") 4 | * 5 | * HTIF is a powerful utility provided by the underlying emulator, i.e., Spike. 6 | * with HTIF, target environment (i.e., the RISC-V machine we use) can leverage 7 | * the power (e.g., read spike_files, print strings to screen and many others) of host 8 | * at ease (by issueing HTIF syscalls to the hosts via htif_syscall). 9 | * 10 | * codes are borrowed from riscv-pk (https://github.com/riscv/riscv-pk) 11 | */ 12 | 13 | #include "util/types.h" 14 | #include "spike_htif.h" 15 | #include "atomic.h" 16 | #include "spike_interface/spike_utils.h" 17 | #include "dts_parse.h" 18 | #include "string.h" 19 | 20 | uint64 htif; //is Spike HTIF avaiable? initially 0 (false) 21 | 22 | /////////////////////////// Spike HTIF discovering ////////////////////////////// 23 | struct htif_scan { 24 | int compat; 25 | }; 26 | 27 | static void htif_open(const struct fdt_scan_node *node, void *extra) { 28 | struct htif_scan *scan = (struct htif_scan *)extra; 29 | memset(scan, 0, sizeof(*scan)); 30 | } 31 | 32 | static void htif_prop(const struct fdt_scan_prop *prop, void *extra) { 33 | struct htif_scan *scan = (struct htif_scan *)extra; 34 | if (!strcmp(prop->name, "compatible") && !strcmp((const char *)prop->value, "ucb,htif0")) { 35 | scan->compat = 1; 36 | } 37 | } 38 | 39 | static void htif_done(const struct fdt_scan_node *node, void *extra) { 40 | struct htif_scan *scan = (struct htif_scan *)extra; 41 | if (!scan->compat) return; 42 | 43 | htif = 1; 44 | } 45 | 46 | // scanning the HTIF 47 | void query_htif(uint64 fdt) { 48 | struct fdt_cb cb; 49 | struct htif_scan scan; 50 | 51 | memset(&cb, 0, sizeof(cb)); 52 | cb.open = htif_open; 53 | cb.prop = htif_prop; 54 | cb.done = htif_done; 55 | cb.extra = &scan; 56 | 57 | fdt_scan(fdt, &cb); 58 | } 59 | 60 | ///////////////////////// Spike HTIF basic operations ////////////////////////// 61 | volatile uint64_t tohost __attribute__((section(".htif"))); 62 | volatile uint64_t fromhost __attribute__((section(".htif"))); 63 | //__htif_base marks the beginning of .htif section (defined in kernel/kernel.lds) 64 | extern uint64_t __htif_base; 65 | 66 | #define TOHOST(base_int) (uint64_t *)(base_int + TOHOST_OFFSET) 67 | #define FROMHOST(base_int) (uint64_t *)(base_int + FROMHOST_OFFSET) 68 | 69 | #define TOHOST_OFFSET ((uint64)tohost - (uint64)__htif_base) 70 | #define FROMHOST_OFFSET ((uint64)fromhost - (uint64)__htif_base) 71 | 72 | volatile int htif_console_buf; 73 | static spinlock_t htif_lock = SPINLOCK_INIT; 74 | 75 | static void __check_fromhost(void) { 76 | uint64_t fh = fromhost; 77 | if (!fh) return; 78 | fromhost = 0; 79 | 80 | // this should be from the console 81 | assert(FROMHOST_DEV(fh) == 1); 82 | switch (FROMHOST_CMD(fh)) { 83 | case 0: 84 | htif_console_buf = 1 + (uint8_t)FROMHOST_DATA(fh); 85 | break; 86 | case 1: 87 | break; 88 | default: 89 | assert(0); 90 | } 91 | } 92 | 93 | static void __set_tohost(uint64 dev, uint64 cmd, uint64 data) { 94 | while (tohost) __check_fromhost(); 95 | tohost = TOHOST_CMD(dev, cmd, data); 96 | } 97 | 98 | static void do_tohost_fromhost(uint64 dev, uint64 cmd, uint64 data) { 99 | spinlock_lock(&htif_lock); 100 | __set_tohost(dev, cmd, data); 101 | 102 | while (1) { 103 | uint64_t fh = fromhost; 104 | if (fh) { 105 | if (FROMHOST_DEV(fh) == dev && FROMHOST_CMD(fh) == cmd) { 106 | fromhost = 0; 107 | break; 108 | } 109 | __check_fromhost(); 110 | } 111 | } 112 | spinlock_unlock(&htif_lock); 113 | } 114 | 115 | ///////////////////// Encapsulated Spike HTIF functionalities ////////////////// 116 | void htif_syscall(uint64 arg) { do_tohost_fromhost(0, 0, arg); } 117 | 118 | // htif fuctionalities 119 | void htif_console_putchar(uint8_t ch) { 120 | #if __riscv_xlen == 32 121 | // HTIF devices are not supported on RV32, so proxy a write system call 122 | volatile uint64_t magic_mem[8]; 123 | magic_mem[0] = HTIFSYS_write; 124 | magic_mem[1] = 1; 125 | magic_mem[2] = (uint64)&ch; 126 | magic_mem[3] = 1; 127 | do_tohost_fromhost(0, 0, (uint64)magic_mem); 128 | #else 129 | spinlock_lock(&htif_lock); 130 | __set_tohost(1, 1, ch); 131 | spinlock_unlock(&htif_lock); 132 | #endif 133 | } 134 | 135 | int htif_console_getchar(void) { 136 | #if __riscv_xlen == 32 137 | // HTIF devices are not supported on RV32 138 | return -1; 139 | #endif 140 | 141 | spinlock_lock(&htif_lock); 142 | __check_fromhost(); 143 | int ch = htif_console_buf; 144 | if (ch >= 0) { 145 | htif_console_buf = -1; 146 | __set_tohost(1, 0, 0); 147 | } 148 | spinlock_unlock(&htif_lock); 149 | 150 | return ch - 1; 151 | } 152 | 153 | void htif_poweroff(void) { 154 | while (1) { 155 | fromhost = 0; 156 | tohost = 1; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /spike_interface/spike_htif.h: -------------------------------------------------------------------------------- 1 | #ifndef _SPIKE_HTIF_H_ 2 | #define _SPIKE_HTIF_H_ 3 | 4 | #include 5 | #include "util/types.h" 6 | 7 | #if __riscv_xlen == 64 8 | #define TOHOST_CMD(dev, cmd, payload) \ 9 | (((uint64_t)(dev) << 56) | ((uint64_t)(cmd) << 48) | (uint64_t)(payload)) 10 | #else 11 | #define TOHOST_CMD(dev, cmd, payload) \ 12 | ({ \ 13 | if ((dev) || (cmd)) __builtin_trap(); \ 14 | (payload); \ 15 | }) 16 | #endif 17 | #define FROMHOST_DEV(fromhost_value) ((uint64_t)(fromhost_value) >> 56) 18 | #define FROMHOST_CMD(fromhost_value) ((uint64_t)(fromhost_value) << 8 >> 56) 19 | #define FROMHOST_DATA(fromhost_value) ((uint64_t)(fromhost_value) << 16 >> 16) 20 | 21 | // HTIF Syscalls 22 | #define HTIFSYS_init_memsize 81 23 | #define HTIFSYS_sema_down 82 24 | #define HTIFSYS_sema_up 83 25 | #define HTIFSYS_exit 93 26 | #define HTIFSYS_exit_group 94 27 | #define HTIFSYS_getpid 172 28 | #define HTIFSYS_kill 129 29 | #define HTIFSYS_read 63 30 | #define HTIFSYS_write 64 31 | #define HTIFSYS_openat 56 32 | #define HTIFSYS_close 57 33 | #define HTIFSYS_lseek 62 34 | #define HTIFSYS_brk 214 35 | #define HTIFSYS_linkat 37 36 | #define HTIFSYS_unlinkat 35 37 | #define HTIFSYS_wait 3 38 | #define HTIFSYS_mkdirat 34 39 | #define HTIFSYS_renameat 38 40 | #define HTIFSYS_chdir 49 41 | #define HTIFSYS_getcwd 17 42 | #define HTIFSYS_fstat 80 43 | #define HTIFSYS_fstatat 79 44 | #define HTIFSYS_faccessat 48 45 | #define HTIFSYS_pread 67 46 | #define HTIFSYS_pwrite 68 47 | #define HTIFSYS_uname 160 48 | #define HTIFSYS_fork 170 49 | #define HTIFSYS_wait 3 50 | #define HTIFSYS_getuid 174 51 | #define HTIFSYS_geteuid 175 52 | #define HTIFSYS_getgid 176 53 | #define HTIFSYS_getegid 177 54 | #define HTIFSYS_mmap 222 55 | #define HTIFSYS_munmap 215 56 | #define HTIFSYS_mremap 216 57 | #define HTIFSYS_mprotect 226 58 | #define HTIFSYS_prlimit64 261 59 | #define HTIFSYS_getmainvars 2011 60 | #define HTIFSYS_rt_sigaction 134 61 | #define HTIFSYS_writev 66 62 | #define HTIFSYS_gettimeofday 169 63 | #define HTIFSYS_times 153 64 | #define HTIFSYS_fcntl 25 65 | #define HTIFSYS_ftruncate 46 66 | #define HTIFSYS_getdents 61 67 | #define HTIFSYS_dup 23 68 | #define HTIFSYS_readlinkat 78 69 | #define HTIFSYS_rt_sigprocmask 135 70 | #define HTIFSYS_ioctl 29 71 | #define HTIFSYS_getrlimit 163 72 | #define HTIFSYS_setrlimit 164 73 | #define HTIFSYS_getrusage 165 74 | #define HTIFSYS_clock_gettime 113 75 | #define HTIFSYS_set_tid_address 96 76 | #define HTIFSYS_set_robust_list 99 77 | #define HTIFSYS_madvise 233 78 | 79 | #define HTIFSYS_open 1024 80 | #define HTIFSYS_link 1025 81 | #define HTIFSYS_unlink 1026 82 | #define HTIFSYS_mkdir 1030 83 | #define HTIFSYS_access 1033 84 | #define HTIFSYS_stat 1038 85 | #define HTIFSYS_lstat 1039 86 | #define HTIFSYS_time 1062 87 | 88 | #define IS_ERR_VALUE(x) ((unsigned long)(x) >= (unsigned long)-4096) 89 | #define ERR_PTR(x) ((void*)(long)(x)) 90 | #define PTR_ERR(x) ((long)(x)) 91 | 92 | #define AT_FDCWD -100 93 | 94 | extern uint64 htif; 95 | void query_htif(uint64 dtb); 96 | 97 | // Spike HTIF functionalities 98 | void htif_syscall(uint64); 99 | 100 | void htif_console_putchar(uint8_t); 101 | int htif_console_getchar(); 102 | void htif_poweroff() __attribute__((noreturn)); 103 | 104 | #endif 105 | -------------------------------------------------------------------------------- /spike_interface/spike_memory.c: -------------------------------------------------------------------------------- 1 | /* 2 | * scanning the emulated memory from the DTS (Device Tree String). 3 | * output: the availability and the size (stored in "uint64 g_mem_size") of emulated memory. 4 | * 5 | * codes are borrowed from riscv-pk (https://github.com/riscv/riscv-pk) 6 | */ 7 | #include "dts_parse.h" 8 | #include "spike_interface/spike_utils.h" 9 | #include "string.h" 10 | 11 | uint64 g_mem_size; 12 | 13 | struct mem_scan { 14 | int memory; 15 | const uint32 *reg_value; 16 | int reg_len; 17 | }; 18 | 19 | static void mem_open(const struct fdt_scan_node *node, void *extra) { 20 | struct mem_scan *scan = (struct mem_scan *)extra; 21 | memset(scan, 0, sizeof(*scan)); 22 | } 23 | 24 | static void mem_prop(const struct fdt_scan_prop *prop, void *extra) { 25 | struct mem_scan *scan = (struct mem_scan *)extra; 26 | if (!strcmp(prop->name, "device_type") && !strcmp((const char *)prop->value, "memory")) { 27 | scan->memory = 1; 28 | } else if (!strcmp(prop->name, "reg")) { 29 | scan->reg_value = prop->value; 30 | scan->reg_len = prop->len; 31 | } 32 | } 33 | 34 | static void mem_done(const struct fdt_scan_node *node, void *extra) { 35 | struct mem_scan *scan = (struct mem_scan *)extra; 36 | const uint32 *value = scan->reg_value; 37 | const uint32 *end = value + scan->reg_len / 4; 38 | uint64 self = (uint64)mem_done; 39 | 40 | if (!scan->memory) return; 41 | assert(scan->reg_value && scan->reg_len % 4 == 0); 42 | 43 | while (end - value > 0) { 44 | uint64 base, size; 45 | value = fdt_get_address(node->parent, value, &base); 46 | value = fdt_get_size(node->parent, value, &size); 47 | if (base <= self && self <= base + size) { 48 | g_mem_size = size; 49 | } 50 | } 51 | assert(end == value); 52 | } 53 | 54 | // scanning the emulated memory 55 | void query_mem(uint64 fdt) { 56 | struct fdt_cb cb; 57 | struct mem_scan scan; 58 | 59 | memset(&cb, 0, sizeof(cb)); 60 | cb.open = mem_open; 61 | cb.prop = mem_prop; 62 | cb.done = mem_done; 63 | cb.extra = &scan; 64 | 65 | g_mem_size = 0; 66 | fdt_scan(fdt, &cb); 67 | assert(g_mem_size > 0); 68 | } 69 | -------------------------------------------------------------------------------- /spike_interface/spike_memory.h: -------------------------------------------------------------------------------- 1 | #ifndef _SPIKE_MEMORY_H_ 2 | #define _SPIKE_MEMORY_H_ 3 | 4 | #include "util/types.h" 5 | void query_mem(uint64 fdt); 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /spike_interface/spike_utils.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Utilities implemented by using the Spike HTIF. 3 | * 4 | * codes are borrowed from riscv-pk (https://github.com/riscv/riscv-pk) 5 | */ 6 | 7 | #include "atomic.h" 8 | #include "spike_htif.h" 9 | #include "util/functions.h" 10 | #include "util/snprintf.h" 11 | #include "spike_utils.h" 12 | #include "spike_file.h" 13 | 14 | //============= encapsulating htif syscalls, invoking Spike functions ============= 15 | long frontend_syscall(long n, uint64 a0, uint64 a1, uint64 a2, uint64 a3, uint64 a4, 16 | uint64 a5, uint64 a6) { 17 | static volatile uint64 magic_mem[8]; 18 | 19 | static spinlock_t lock = SPINLOCK_INIT; 20 | spinlock_lock(&lock); 21 | 22 | magic_mem[0] = n; 23 | magic_mem[1] = a0; 24 | magic_mem[2] = a1; 25 | magic_mem[3] = a2; 26 | magic_mem[4] = a3; 27 | magic_mem[5] = a4; 28 | magic_mem[6] = a5; 29 | magic_mem[7] = a6; 30 | 31 | htif_syscall((uintptr_t)magic_mem); 32 | 33 | long ret = magic_mem[0]; 34 | 35 | spinlock_unlock(&lock); 36 | return ret; 37 | } 38 | 39 | //=============== Spike-assisted printf, output string to terminal =============== 40 | static uintptr_t mcall_console_putchar(uint8 ch) { 41 | if (htif) { 42 | htif_console_putchar(ch); 43 | } 44 | return 0; 45 | } 46 | 47 | void vprintk(const char* s, va_list vl) { 48 | char out[256]; 49 | int res = vsnprintf(out, sizeof(out), s, vl); 50 | //you need spike_file_init before this call 51 | spike_file_write(stderr, out, res < sizeof(out) ? res : sizeof(out)); 52 | } 53 | 54 | void printk(const char* s, ...) { 55 | va_list vl; 56 | va_start(vl, s); 57 | 58 | vprintk(s, vl); 59 | 60 | va_end(vl); 61 | } 62 | 63 | void putstring(const char* s) { 64 | while (*s) mcall_console_putchar(*s++); 65 | } 66 | 67 | void vprintm(const char* s, va_list vl) { 68 | char buf[256]; 69 | vsnprintf(buf, sizeof buf, s, vl); 70 | putstring(buf); 71 | } 72 | 73 | void sprint(const char* s, ...) { 74 | va_list vl; 75 | va_start(vl, s); 76 | 77 | vprintk(s, vl); 78 | 79 | va_end(vl); 80 | } 81 | 82 | //=============== Spike-assisted termination, panic and assert =============== 83 | void poweroff(uint16_t code) { 84 | assert(htif); 85 | sprint("Power off\r\n"); 86 | if (htif) { 87 | htif_poweroff(); 88 | } else { 89 | // we consider only one HART case in PKE experiments. May extend this later. 90 | // send_ipi_many(0, IPI_HALT); 91 | while (1) { 92 | asm volatile("wfi\n"); 93 | } 94 | } 95 | } 96 | 97 | void shutdown(int code) { 98 | sprint("System is shutting down with exit code %d.\n", code); 99 | frontend_syscall(HTIFSYS_exit, code, 0, 0, 0, 0, 0, 0); 100 | while (1) 101 | ; 102 | } 103 | 104 | void do_panic(const char* s, ...) { 105 | va_list vl; 106 | va_start(vl, s); 107 | 108 | sprint(s, vl); 109 | shutdown(-1); 110 | 111 | va_end(vl); 112 | } 113 | 114 | void kassert_fail(const char* s) { 115 | register uintptr_t ra asm("ra"); 116 | do_panic("assertion failed @ %p: %s\n", ra, s); 117 | // sprint("assertion failed @ %p: %s\n", ra, s); 118 | shutdown(-1); 119 | } 120 | -------------------------------------------------------------------------------- /spike_interface/spike_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef _SPIKE_UTILS_H_ 2 | #define _SPIKE_UTILS_H_ 3 | 4 | #include "util/types.h" 5 | #include "spike_file.h" 6 | #include "spike_memory.h" 7 | #include "spike_htif.h" 8 | 9 | long frontend_syscall(long n, uint64 a0, uint64 a1, uint64 a2, uint64 a3, uint64 a4, uint64 a5, 10 | uint64 a6); 11 | 12 | void poweroff(uint16 code) __attribute((noreturn)); 13 | void sprint(const char* s, ...); 14 | void putstring(const char* s); 15 | void shutdown(int) __attribute__((noreturn)); 16 | 17 | #define assert(x) \ 18 | ({ \ 19 | if (!(x)) die("assertion failed: %s", #x); \ 20 | }) 21 | #define die(str, ...) \ 22 | ({ \ 23 | sprint("%s:%d: " str "\n", __FILE__, __LINE__, ##__VA_ARGS__); \ 24 | poweroff(-1); \ 25 | }) 26 | 27 | void do_panic(const char* s, ...) __attribute__((noreturn)); 28 | void kassert_fail(const char* s) __attribute__((noreturn)); 29 | 30 | //void shutdown(int code); 31 | 32 | #define panic(s, ...) \ 33 | do { \ 34 | do_panic(s "\n", ##__VA_ARGS__); \ 35 | } while (0) 36 | #define kassert(cond) \ 37 | do { \ 38 | if (!(cond)) kassert_fail("" #cond); \ 39 | } while (0) 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /user/app_helloworld.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Below is the given application for lab1_1. 3 | * 4 | * You can build this app (as well as our PKE OS kernel) by command: 5 | * $ make 6 | * 7 | * Or run this app (with the support from PKE OS kernel) by command: 8 | * $ make run 9 | */ 10 | 11 | #include "user_lib.h" 12 | 13 | int main(void) { 14 | printu("Hello world!\n"); 15 | 16 | exit(0); 17 | } 18 | -------------------------------------------------------------------------------- /user/user.lds: -------------------------------------------------------------------------------- 1 | OUTPUT_ARCH( "riscv" ) 2 | 3 | ENTRY(main) 4 | 5 | SECTIONS 6 | { 7 | . = 0x81000000; 8 | . = ALIGN(0x1000); 9 | .text : { *(.text) } 10 | . = ALIGN(16); 11 | .data : { *(.data) } 12 | . = ALIGN(16); 13 | .bss : { *(.bss) } 14 | } 15 | -------------------------------------------------------------------------------- /user/user_lib.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The supporting library for applications. 3 | * Actually, supporting routines for applications are catalogued as the user 4 | * library. we don't do that in PKE to make the relationship between application 5 | * and user library more straightforward. 6 | */ 7 | 8 | #include "user_lib.h" 9 | #include "util/types.h" 10 | #include "util/snprintf.h" 11 | #include "kernel/syscall.h" 12 | 13 | int do_user_call(uint64 sysnum, uint64 a1, uint64 a2, uint64 a3, uint64 a4, uint64 a5, uint64 a6, 14 | uint64 a7) { 15 | int ret; 16 | 17 | // before invoking the syscall, arguments of do_user_call are already loaded into the argument 18 | // registers (a0-a7) of our (emulated) risc-v machine. 19 | asm volatile( 20 | "ecall\n" 21 | "sw a0, %0" // returns a 32-bit value 22 | : "=m"(ret) 23 | : 24 | : "memory"); 25 | 26 | return ret; 27 | } 28 | 29 | // 30 | // printu() supports user/lab1_1_helloworld.c 31 | // 32 | int printu(const char* s, ...) { 33 | va_list vl; 34 | va_start(vl, s); 35 | 36 | char out[256]; // fixed buffer size. 37 | int res = vsnprintf(out, sizeof(out), s, vl); 38 | va_end(vl); 39 | const char* buf = out; 40 | size_t n = res < sizeof(out) ? res : sizeof(out); 41 | 42 | // make a syscall to implement the required functionality. 43 | return do_user_call(SYS_user_print, (uint64)buf, n, 0, 0, 0, 0, 0); 44 | } 45 | 46 | // 47 | // applications need to call exit to quit execution. 48 | // 49 | int exit(int code) { 50 | return do_user_call(SYS_user_exit, code, 0, 0, 0, 0, 0, 0); 51 | } 52 | -------------------------------------------------------------------------------- /user/user_lib.h: -------------------------------------------------------------------------------- 1 | /* 2 | * header file to be used by applications. 3 | */ 4 | 5 | int printu(const char *s, ...); 6 | int exit(int code); 7 | -------------------------------------------------------------------------------- /util/functions.h: -------------------------------------------------------------------------------- 1 | #ifndef _FUNCTIONS_H_ 2 | #define _FUNCTIONS_H_ 3 | 4 | #define ROUNDUP(a, b) ((((a)-1) / (b) + 1) * (b)) 5 | #define ROUNDDOWN(a, b) ((a) / (b) * (b)) 6 | 7 | #define MAX(a, b) ((a) > (b) ? (a) : (b)) 8 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 9 | #define likely(x) __builtin_expect((x), 1) 10 | #define unlikely(x) __builtin_expect((x), 0) 11 | 12 | #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) 13 | 14 | char* safestrcpy(char*, const char*, int); 15 | 16 | #endif -------------------------------------------------------------------------------- /util/load_store.S: -------------------------------------------------------------------------------- 1 | .align 4 2 | .globl store_all_registers 3 | //use t6 to store all 4 | .macro store_all_registers 5 | sd ra, 0(t6) 6 | sd sp, 8(t6) 7 | sd gp, 16(t6) 8 | sd tp, 24(t6) 9 | sd t0, 32(t6) 10 | sd t1, 40(t6) 11 | sd t2, 48(t6) 12 | sd s0, 56(t6) 13 | sd s1, 64(t6) 14 | sd a0, 72(t6) 15 | sd a1, 80(t6) 16 | sd a2, 88(t6) 17 | sd a3, 96(t6) 18 | sd a4, 104(t6) 19 | sd a5, 112(t6) 20 | sd a6, 120(t6) 21 | sd a7, 128(t6) 22 | sd s2, 136(t6) 23 | sd s3, 144(t6) 24 | sd s4, 152(t6) 25 | sd s5, 160(t6) 26 | sd s6, 168(t6) 27 | sd s7, 176(t6) 28 | sd s8, 184(t6) 29 | sd s9, 192(t6) 30 | sd s10, 200(t6) 31 | sd s11, 208(t6) 32 | sd t3, 216(t6) 33 | sd t4, 224(t6) 34 | sd t5, 232(t6) 35 | sd t6, 240(t6) 36 | //ld t6, 0 37 | .endm 38 | 39 | //use t6 to restore all because it's the last one. 40 | .globl restore_all_registers 41 | .macro restore_all_registers 42 | ld ra, 0(t6) 43 | ld sp, 8(t6) 44 | ld gp, 16(t6) 45 | ld tp, 24(t6) 46 | ld t0, 32(t6) 47 | ld t1, 40(t6) 48 | ld t2, 48(t6) 49 | ld s0, 56(t6) 50 | ld s1, 64(t6) 51 | ld a0, 72(t6) 52 | ld a1, 80(t6) 53 | ld a2, 88(t6) 54 | ld a3, 96(t6) 55 | ld a4, 104(t6) 56 | ld a5, 112(t6) 57 | ld a6, 120(t6) 58 | ld a7, 128(t6) 59 | ld s2, 136(t6) 60 | ld s3, 144(t6) 61 | ld s4, 152(t6) 62 | ld s5, 160(t6) 63 | ld s6, 168(t6) 64 | ld s7, 176(t6) 65 | ld s8, 184(t6) 66 | ld s9, 192(t6) 67 | ld s10, 200(t6) 68 | ld s11, 208(t6) 69 | ld t3, 216(t6) 70 | ld t4, 224(t6) 71 | ld t5, 232(t6) 72 | ld t6, 240(t6) 73 | .endm -------------------------------------------------------------------------------- /util/snprintf.c: -------------------------------------------------------------------------------- 1 | /* 2 | * vsnprintf() is borrowed from pk. 3 | */ 4 | 5 | //#include 6 | //#include 7 | //#include 8 | 9 | #include "util/snprintf.h" 10 | 11 | int32 vsnprintf(char* out, size_t n, const char* s, va_list vl) { 12 | bool format = FALSE; 13 | bool longarg = FALSE; 14 | size_t pos = 0; 15 | 16 | for (; *s; s++) { 17 | if (format) { 18 | switch (*s) { 19 | case 'l': 20 | longarg = TRUE; 21 | break; 22 | case 'p': 23 | longarg = TRUE; 24 | if (++pos < n) out[pos - 1] = '0'; 25 | if (++pos < n) out[pos - 1] = 'x'; 26 | case 'x': { 27 | long num = longarg ? va_arg(vl, long) : va_arg(vl, int); 28 | for (int i = 2 * (longarg ? sizeof(long) : sizeof(int)) - 1; i >= 0; i--) { 29 | int d = (num >> (4 * i)) & 0xF; 30 | if (++pos < n) out[pos - 1] = (d < 10 ? '0' + d : 'a' + d - 10); 31 | } 32 | longarg = FALSE; 33 | format = FALSE; 34 | break; 35 | } 36 | case 'd': { 37 | long num = longarg ? va_arg(vl, long) : va_arg(vl, int); 38 | if (num < 0) { 39 | num = -num; 40 | if (++pos < n) out[pos - 1] = '-'; 41 | } 42 | long digits = 1; 43 | for (long nn = num; nn /= 10; digits++) 44 | ; 45 | for (int i = digits - 1; i >= 0; i--) { 46 | if (pos + i + 1 < n) out[pos + i] = '0' + (num % 10); 47 | num /= 10; 48 | } 49 | pos += digits; 50 | longarg = FALSE; 51 | format = FALSE; 52 | break; 53 | } 54 | case 's': { 55 | const char* s2 = va_arg(vl, const char*); 56 | while (*s2) { 57 | if (++pos < n) out[pos - 1] = *s2; 58 | s2++; 59 | } 60 | longarg = FALSE; 61 | format = FALSE; 62 | break; 63 | } 64 | case 'c': { 65 | if (++pos < n) out[pos - 1] = (char)va_arg(vl, int); 66 | longarg = FALSE; 67 | format = FALSE; 68 | break; 69 | } 70 | default: 71 | break; 72 | } 73 | } else if (*s == '%') 74 | format = TRUE; 75 | else if (++pos < n) 76 | out[pos - 1] = *s; 77 | } 78 | if (pos < n) 79 | out[pos] = 0; 80 | else if (n) 81 | out[n - 1] = 0; 82 | return pos; 83 | } 84 | -------------------------------------------------------------------------------- /util/snprintf.h: -------------------------------------------------------------------------------- 1 | // borrowed from https://github.com/riscv/riscv-pk : util/snprintf.c 2 | #ifndef _SNPRINTF_H 3 | #define _SNPRINTF_H 4 | 5 | #include 6 | 7 | #include "util/types.h" 8 | 9 | int vsnprintf(char* out, size_t n, const char* s, va_list vl); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /util/string.c: -------------------------------------------------------------------------------- 1 | // See LICENSE for license details. 2 | 3 | #include 4 | #include 5 | 6 | #include "string.h" 7 | 8 | void* memcpy(void* dest, const void* src, size_t len) { 9 | const char* s = src; 10 | char* d = dest; 11 | 12 | if ((((uintptr_t)dest | (uintptr_t)src) & (sizeof(uintptr_t) - 1)) == 0) { 13 | while ((void*)d < (dest + len - (sizeof(uintptr_t) - 1))) { 14 | *(uintptr_t*)d = *(const uintptr_t*)s; 15 | d += sizeof(uintptr_t); 16 | s += sizeof(uintptr_t); 17 | } 18 | } 19 | 20 | while (d < (char*)(dest + len)) *d++ = *s++; 21 | 22 | return dest; 23 | } 24 | 25 | void* memset(void* dest, int byte, size_t len) { 26 | if ((((uintptr_t)dest | len) & (sizeof(uintptr_t) - 1)) == 0) { 27 | uintptr_t word = byte & 0xFF; 28 | word |= word << 8; 29 | word |= word << 16; 30 | word |= word << 16 << 16; 31 | 32 | uintptr_t* d = dest; 33 | while (d < (uintptr_t*)(dest + len)) *d++ = word; 34 | } else { 35 | char* d = dest; 36 | while (d < (char*)(dest + len)) *d++ = byte; 37 | } 38 | return dest; 39 | } 40 | 41 | size_t strlen(const char* s) { 42 | const char* p = s; 43 | while (*p) p++; 44 | return p - s; 45 | } 46 | 47 | int strcmp(const char* s1, const char* s2) { 48 | unsigned char c1, c2; 49 | 50 | do { 51 | c1 = *s1++; 52 | c2 = *s2++; 53 | } while (c1 != 0 && c1 == c2); 54 | 55 | return c1 - c2; 56 | } 57 | 58 | char* strcpy(char* dest, const char* src) { 59 | char* d = dest; 60 | while ((*d++ = *src++)) 61 | ; 62 | return dest; 63 | } 64 | 65 | long atol(const char* str) { 66 | long res = 0; 67 | int sign = 0; 68 | 69 | while (*str == ' ') str++; 70 | 71 | if (*str == '-' || *str == '+') { 72 | sign = *str == '-'; 73 | str++; 74 | } 75 | 76 | while (*str) { 77 | res *= 10; 78 | res += *str++ - '0'; 79 | } 80 | 81 | return sign ? -res : res; 82 | } 83 | 84 | void* memmove(void* dst, const void* src, size_t n) { 85 | const char* s; 86 | char* d; 87 | 88 | s = src; 89 | d = dst; 90 | if (s < d && s + n > d) { 91 | s += n; 92 | d += n; 93 | while (n-- > 0) *--d = *--s; 94 | } else 95 | while (n-- > 0) *d++ = *s++; 96 | 97 | return dst; 98 | } 99 | 100 | // Like strncpy but guaranteed to NUL-terminate. 101 | char* safestrcpy(char* s, const char* t, int n) { 102 | char* os; 103 | 104 | os = s; 105 | if (n <= 0) return os; 106 | while (--n > 0 && (*s++ = *t++) != 0) 107 | ; 108 | *s = 0; 109 | return os; 110 | } -------------------------------------------------------------------------------- /util/string.h: -------------------------------------------------------------------------------- 1 | #ifndef _STRING_H 2 | #define _STRING_H 3 | 4 | #include 5 | 6 | void* memcpy(void* dest, const void* src, size_t len); 7 | void* memset(void* dest, int byte, size_t len); 8 | size_t strlen(const char* s); 9 | int strcmp(const char* s1, const char* s2); 10 | char* strcpy(char* dest, const char* src); 11 | long atol(const char* str); 12 | void* memmove(void* dst, const void* src, size_t n); 13 | char* safestrcpy(char* s, const char* t, int n); 14 | 15 | #endif -------------------------------------------------------------------------------- /util/types.h: -------------------------------------------------------------------------------- 1 | #ifndef _TYPES_H_ 2 | #define _TYPES_H_ 3 | 4 | typedef unsigned char uint8; 5 | typedef unsigned short uint16; 6 | typedef unsigned int uint32; 7 | typedef unsigned long long uint64; 8 | 9 | typedef signed char int8; 10 | typedef signed short int16; 11 | typedef signed int int32; 12 | typedef signed long long int64; 13 | 14 | typedef int bool; 15 | 16 | typedef signed long ssize_t; 17 | typedef unsigned long size_t; 18 | 19 | #define NULL ((void *)0) 20 | #define TRUE 1 21 | #define FALSE 0 22 | 23 | #endif 24 | --------------------------------------------------------------------------------