├── .gitignore ├── .spike.cfg ├── LICENSE.txt ├── Makefile ├── README.md ├── hostfs_root ├── bin │ ├── app_cat │ ├── app_echo │ ├── app_ls │ ├── app_mkdir │ ├── app_shell │ └── app_touch └── shellrc ├── kernel ├── config.h ├── elf.c ├── elf.h ├── hostfs.c ├── hostfs.h ├── kernel.c ├── kernel.lds ├── machine │ ├── mentry.S │ ├── minit.c │ ├── mtrap.c │ └── mtrap_vector.S ├── memlayout.h ├── pmm.c ├── pmm.h ├── proc_file.c ├── proc_file.h ├── process.c ├── process.h ├── ramdev.c ├── ramdev.h ├── rfs.c ├── rfs.h ├── riscv.h ├── sched.c ├── sched.h ├── strap.c ├── strap.h ├── strap_vector.S ├── syscall.c ├── syscall.h ├── vfs.c ├── vfs.h ├── vmm.c └── vmm.h ├── 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_cat.c ├── app_echo.c ├── app_ls.c ├── app_mkdir.c ├── app_shell.c ├── app_touch.c ├── user_lib.c └── user_lib.h └── util ├── functions.h ├── hash_table.c ├── hash_table.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 | Guo Li (2925441676@qq.com), 8 | Huazhong University of Science and Technology 9 | Liang Shi (lshi@cs.ecnu.edu.cn), 10 | Longshan Xu (1981888213@qq.com), 11 | Yangxue Ou (3386215144@qq.com), 12 | East China Normal University 13 | 14 | Permission is hereby granted, free of charge, to any person obtaining 15 | a copy of this software and associated documentation files (the 16 | "Software"), to deal in the Software without restriction, including 17 | without limitation the rights to use, copy, modify, merge, publish, 18 | distribute, sublicense, and/or sell copies of the Software, and to 19 | permit persons to whom the Software is furnished to do so, subject to 20 | the following conditions: 21 | 22 | * The above copyright notice and this permission notice shall be 23 | included in all copies or substantial portions of the Software. 24 | 25 | * Neither the name of the Huazhong University of Science and Technology 26 | nor the names of its contributors may be used to endorse or promote 27 | products derived from this software without specific prior written 28 | permission. 29 | 30 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 31 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 32 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 33 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 34 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 35 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 36 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 37 | -------------------------------------------------------------------------------- /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 | HOSTFS_ROOT := hostfs_root 18 | ifneq (,) 19 | march := -march= 20 | is_32bit := $(findstring 32,$(march)) 21 | mabi := -mabi=$(if $(is_32bit),ilp32,lp64) 22 | endif 23 | 24 | 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) 25 | COMPILE := $(CC) -MMD -MP $(CFLAGS) $(SPROJS_INCLUDE) 26 | 27 | #--------------------- utils ----------------------- 28 | UTIL_CPPS := util/*.c 29 | 30 | UTIL_CPPS := $(wildcard $(UTIL_CPPS)) 31 | UTIL_OBJS := $(addprefix $(OBJ_DIR)/, $(patsubst %.c,%.o,$(UTIL_CPPS))) 32 | 33 | 34 | UTIL_LIB := $(OBJ_DIR)/util.a 35 | 36 | #--------------------- kernel ----------------------- 37 | KERNEL_LDS := kernel/kernel.lds 38 | KERNEL_CPPS := \ 39 | kernel/*.c \ 40 | kernel/machine/*.c \ 41 | kernel/util/*.c 42 | 43 | KERNEL_ASMS := \ 44 | kernel/*.S \ 45 | kernel/machine/*.S \ 46 | kernel/util/*.S 47 | 48 | KERNEL_CPPS := $(wildcard $(KERNEL_CPPS)) 49 | KERNEL_ASMS := $(wildcard $(KERNEL_ASMS)) 50 | KERNEL_OBJS := $(addprefix $(OBJ_DIR)/, $(patsubst %.c,%.o,$(KERNEL_CPPS))) 51 | KERNEL_OBJS += $(addprefix $(OBJ_DIR)/, $(patsubst %.S,%.o,$(KERNEL_ASMS))) 52 | 53 | KERNEL_TARGET = $(OBJ_DIR)/riscv-pke 54 | 55 | 56 | #--------------------- spike interface library ----------------------- 57 | SPIKE_INF_CPPS := spike_interface/*.c 58 | 59 | SPIKE_INF_CPPS := $(wildcard $(SPIKE_INF_CPPS)) 60 | SPIKE_INF_OBJS := $(addprefix $(OBJ_DIR)/, $(patsubst %.c,%.o,$(SPIKE_INF_CPPS))) 61 | 62 | 63 | SPIKE_INF_LIB := $(OBJ_DIR)/spike_interface.a 64 | 65 | 66 | #--------------------- user ----------------------- 67 | USER_CPPS := user/app_shell.c user/user_lib.c 68 | 69 | USER_OBJS := $(addprefix $(OBJ_DIR)/, $(patsubst %.c,%.o,$(USER_CPPS))) 70 | 71 | USER_TARGET := $(HOSTFS_ROOT)/bin/app_shell 72 | 73 | USER_E_CPPS := user/app_ls.c user/user_lib.c 74 | 75 | USER_E_OBJS := $(addprefix $(OBJ_DIR)/, $(patsubst %.c,%.o,$(USER_E_CPPS))) 76 | 77 | USER_E_TARGET := $(HOSTFS_ROOT)/bin/app_ls 78 | 79 | USER_M_CPPS := user/app_mkdir.c user/user_lib.c 80 | 81 | USER_M_OBJS := $(addprefix $(OBJ_DIR)/, $(patsubst %.c,%.o,$(USER_M_CPPS))) 82 | 83 | USER_M_TARGET := $(HOSTFS_ROOT)/bin/app_mkdir 84 | 85 | USER_T_CPPS := user/app_touch.c user/user_lib.c 86 | 87 | USER_T_OBJS := $(addprefix $(OBJ_DIR)/, $(patsubst %.c,%.o,$(USER_T_CPPS))) 88 | 89 | USER_T_TARGET := $(HOSTFS_ROOT)/bin/app_touch 90 | 91 | USER_C_CPPS := user/app_cat.c user/user_lib.c 92 | 93 | USER_C_OBJS := $(addprefix $(OBJ_DIR)/, $(patsubst %.c,%.o,$(USER_C_CPPS))) 94 | 95 | USER_C_TARGET := $(HOSTFS_ROOT)/bin/app_cat 96 | 97 | USER_O_CPPS := user/app_echo.c user/user_lib.c 98 | 99 | USER_O_OBJS := $(addprefix $(OBJ_DIR)/, $(patsubst %.c,%.o,$(USER_O_CPPS))) 100 | 101 | USER_O_TARGET := $(HOSTFS_ROOT)/bin/app_echo 102 | #------------------------targets------------------------ 103 | $(OBJ_DIR): 104 | @-mkdir -p $(OBJ_DIR) 105 | @-mkdir -p $(dir $(UTIL_OBJS)) 106 | @-mkdir -p $(dir $(SPIKE_INF_OBJS)) 107 | @-mkdir -p $(dir $(KERNEL_OBJS)) 108 | @-mkdir -p $(dir $(USER_OBJS)) 109 | @-mkdir -p $(dir $(USER_E_OBJS)) 110 | @-mkdir -p $(dir $(USER_M_OBJS)) 111 | @-mkdir -p $(dir $(USER_T_OBJS)) 112 | @-mkdir -p $(dir $(USER_C_OBJS)) 113 | @-mkdir -p $(dir $(USER_O_OBJS)) 114 | 115 | $(OBJ_DIR)/%.o : %.c 116 | @echo "compiling" $< 117 | @$(COMPILE) -c $< -o $@ 118 | 119 | $(OBJ_DIR)/%.o : %.S 120 | @echo "compiling" $< 121 | @$(COMPILE) -c $< -o $@ 122 | 123 | $(UTIL_LIB): $(OBJ_DIR) $(UTIL_OBJS) 124 | @echo "linking " $@ ... 125 | @$(AR) -rcs $@ $(UTIL_OBJS) 126 | @echo "Util lib has been build into" \"$@\" 127 | 128 | $(SPIKE_INF_LIB): $(OBJ_DIR) $(UTIL_OBJS) $(SPIKE_INF_OBJS) 129 | @echo "linking " $@ ... 130 | @$(AR) -rcs $@ $(SPIKE_INF_OBJS) $(UTIL_OBJS) 131 | @echo "Spike lib has been build into" \"$@\" 132 | 133 | $(KERNEL_TARGET): $(OBJ_DIR) $(UTIL_LIB) $(SPIKE_INF_LIB) $(KERNEL_OBJS) $(KERNEL_LDS) 134 | @echo "linking" $@ ... 135 | @$(COMPILE) $(KERNEL_OBJS) $(UTIL_LIB) $(SPIKE_INF_LIB) -o $@ -T $(KERNEL_LDS) 136 | @echo "PKE core has been built into" \"$@\" 137 | 138 | $(USER_TARGET): $(OBJ_DIR) $(UTIL_LIB) $(USER_OBJS) 139 | @echo "linking" $@ ... 140 | -@mkdir -p $(HOSTFS_ROOT)/bin 141 | @$(COMPILE) --entry=main $(USER_OBJS) $(UTIL_LIB) -o $@ 142 | @echo "User app has been built into" \"$@\" 143 | @cp $@ $(OBJ_DIR) 144 | 145 | $(USER_E_TARGET): $(OBJ_DIR) $(UTIL_LIB) $(USER_E_OBJS) 146 | @echo "linking" $@ ... 147 | -@mkdir -p $(HOSTFS_ROOT)/bin 148 | @$(COMPILE) --entry=main $(USER_E_OBJS) $(UTIL_LIB) -o $@ 149 | @echo "User app has been built into" \"$@\" 150 | 151 | $(USER_M_TARGET): $(OBJ_DIR) $(UTIL_LIB) $(USER_M_OBJS) 152 | @echo "linking" $@ ... 153 | -@mkdir -p $(HOSTFS_ROOT)/bin 154 | @$(COMPILE) --entry=main $(USER_M_OBJS) $(UTIL_LIB) -o $@ 155 | @echo "User app has been built into" \"$@\" 156 | 157 | $(USER_T_TARGET): $(OBJ_DIR) $(UTIL_LIB) $(USER_T_OBJS) 158 | @echo "linking" $@ ... 159 | -@mkdir -p $(HOSTFS_ROOT)/bin 160 | @$(COMPILE) --entry=main $(USER_T_OBJS) $(UTIL_LIB) -o $@ 161 | @echo "User app has been built into" \"$@\" 162 | 163 | $(USER_C_TARGET): $(OBJ_DIR) $(UTIL_LIB) $(USER_C_OBJS) 164 | @echo "linking" $@ ... 165 | -@mkdir -p $(HOSTFS_ROOT)/bin 166 | @$(COMPILE) --entry=main $(USER_C_OBJS) $(UTIL_LIB) -o $@ 167 | @echo "User app has been built into" \"$@\" 168 | 169 | $(USER_O_TARGET): $(OBJ_DIR) $(UTIL_LIB) $(USER_O_OBJS) 170 | @echo "linking" $@ ... 171 | -@mkdir -p $(HOSTFS_ROOT)/bin 172 | @$(COMPILE) --entry=main $(USER_O_OBJS) $(UTIL_LIB) -o $@ 173 | @echo "User app has been built into" \"$@\" 174 | 175 | -include $(wildcard $(OBJ_DIR)/*/*.d) 176 | -include $(wildcard $(OBJ_DIR)/*/*/*.d) 177 | 178 | .DEFAULT_GOAL := $(all) 179 | 180 | all: $(KERNEL_TARGET) $(USER_TARGET) $(USER_E_TARGET) $(USER_M_TARGET) $(USER_T_TARGET) $(USER_C_TARGET) $(USER_O_TARGET) 181 | .PHONY:all 182 | 183 | run: $(KERNEL_TARGET) $(USER_TARGET) $(USER_E_TARGET) $(USER_M_TARGET) $(USER_T_TARGET) $(USER_C_TARGET) $(USER_O_TARGET) 184 | @echo "********************HUST PKE********************" 185 | spike $(KERNEL_TARGET) /bin/app_shell 186 | 187 | # need openocd! 188 | gdb:$(KERNEL_TARGET) $(USER_TARGET) 189 | spike --rbb-port=9824 -H $(KERNEL_TARGET) $(USER_TARGET) & 190 | @sleep 1 191 | openocd -f ./.spike.cfg & 192 | @sleep 1 193 | riscv64-unknown-elf-gdb -command=./.gdbinit 194 | 195 | # clean gdb. need openocd! 196 | gdb_clean: 197 | @-kill -9 $$(lsof -i:9824 -t) 198 | @-kill -9 $$(lsof -i:3333 -t) 199 | @sleep 1 200 | 201 | objdump: 202 | riscv64-unknown-elf-objdump -d $(KERNEL_TARGET) > $(OBJ_DIR)/kernel_dump 203 | riscv64-unknown-elf-objdump -d $(USER_TARGET) > $(OBJ_DIR)/user_dump 204 | 205 | cscope: 206 | find ./ -name "*.c" > cscope.files 207 | find ./ -name "*.h" >> cscope.files 208 | find ./ -name "*.S" >> cscope.files 209 | find ./ -name "*.lds" >> cscope.files 210 | cscope -bqk 211 | 212 | format: 213 | @python ./format.py ./ 214 | 215 | clean: 216 | rm -fr ${OBJ_DIR} ${HOSTFS_ROOT}/bin 217 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## about riscv-pke (Proxy Kernel for Education, a.k.a. PKE) ## 2 | ---------- 3 | 4 | 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. 5 | 6 | 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. 7 | 8 | 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: 9 | 10 | Lab1(3 basic labs+2 challenge labs): traps (syscalls), exceptions and interrupts (IRQs in Intel terminology). 11 | 12 | Lab2 (3 basic labs+2 challenge labs): memory management. 13 | 14 | Lab3 (3 basic labs+2 challenge labs): processes. 15 | 16 | Lab4 (3 basic labs): device and file (conducted on a PYNQ FPGA board + an Arduino toy car). 17 | 18 | The experiments in the REPO may be different (with more actual labs) from the above list with the passing of time. 19 | 20 | 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.)*. 21 | 22 | 23 | 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. 24 | 25 | 26 | 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. 27 | 28 | 29 | 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! 30 | 31 | 32 | Environment configuration 33 | ---------- 34 | 35 | **1. Install Operating system (Virtual machine or Windows Subversion Linux)** 36 | 37 | (preferred) Ubuntu 16.04LTS or higher, 64-bit 38 | 39 | **2. Install tools for building cross-compiler and emluator** 40 | 41 | ```bash 42 | $ sudo apt-get install autoconf automake autotools-dev curl python3 libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex 43 | ``` 44 | 45 | **3. Install RISC-V cross-compiler** 46 | 47 | ```bash 48 | $ export RISCV=/path-to-install-RISCV-toolchains 49 | $ git clone --recursive https://github.com/riscv/riscv-gnu-toolchain.git 50 | $ cd riscv-gnu-toolchain 51 | $ ./configure --prefix=$RISCV 52 | $ make -j$(nproc) 53 | $ sudo make install 54 | ``` 55 | 56 | 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. 57 | 58 | **4. Install emulator (Spike)** 59 | 60 | ```bash 61 | $ sudo apt-get install device-tree-compiler 62 | $ git clone https://github.com/riscv/riscv-isa-sim.git 63 | $ cd riscv-isa-sim 64 | $ mkdir build 65 | $ cd build 66 | $ ../configure --prefix=$RISCV 67 | $ make -j$(nproc) 68 | $ sudo make install 69 | ``` 70 | 71 | After this step, you should find executables like spike, spike-dasm in your */path-to-install-RISCV-toolchains/bin* directory. 72 | 73 | **5. Clone PKE REPO** 74 | 75 | ```bash 76 | $ git clone https://github.com/MrShawCode/riscv-pke.git 77 | ``` 78 | 79 | After this step, you will have the pke directory containing the PKE labs. 80 | 81 | **6. Build/Run PKE** 82 | 83 | ```bash 84 | $ make [run] 85 | ``` 86 | 87 | **7. (optional) Install OpenOCD for debugging** 88 | 89 | ```bash 90 | $ git clone https://github.com/riscv/riscv-openocd.git 91 | $ cd openocd 92 | $ ./bootstrap (when building from the git repository) 93 | $ ./configure --prefix=$RISCV 94 | $ make -j$(nproc) 95 | $ sudo make install 96 | ``` 97 | 98 | After installing OpenOCD, you can debug the PKE kernel. Simply use following command: 99 | 100 | ```bash 101 | $ make gdb 102 | ``` 103 | 104 | Start the first lab 105 | ---------- 106 | 107 | In this lab, we are going to learn the basic priciples of trap (also called as the **syscall** in many textbooks). 108 | 109 | 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). 110 | 111 | 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*). 112 | 113 | 114 | #### Code structure of Lab1_1 115 | ---------- 116 | The structure of Lab1_1 is listed in the following: 117 | 118 | . 119 | ├── LICENSE.txt 120 | ├── Makefile 121 | ├── README.md 122 | ├── .spike.cfg 123 | ├── kernel 124 | │   ├── config.h 125 | │   ├── elf.c 126 | │   ├── elf.h 127 | │   ├── kernel.c 128 | │   ├── kernel.lds 129 | │   ├── machine 130 | │   │   ├── mentry.S 131 | │   │   └── minit.c 132 | │   ├── process.c 133 | │   ├── process.h 134 | │   ├── riscv.h 135 | │   ├── strap.c 136 | │   ├── strap.h 137 | │   ├── strap_vector.S 138 | │   ├── syscall.c 139 | │   └── syscall.h 140 | ├── spike_interface 141 | │   ├── atomic.h 142 | │   ├── dts_parse.c 143 | │   ├── dts_parse.h 144 | │   ├── spike_file.c 145 | │   ├── spike_file.h 146 | │   ├── spike_htif.c 147 | │   ├── spike_htif.h 148 | │   ├── spike_memory.c 149 | │   ├── spike_memory.h 150 | │   ├── spike_utils.c 151 | │   └── spike_utils.h 152 | ├── user 153 | │   ├── app_helloworld.c 154 | │   ├── user.lds 155 | │   ├── user_lib.c 156 | │   └── user_lib.h 157 | └── util 158 | ├── functions.h 159 | ├── load_store.S 160 | ├── snprintf.c 161 | ├── snprintf.h 162 | ├── string.c 163 | ├── string.h 164 | └── types.h 165 | 166 | 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. 167 | 168 | 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*: 169 | 170 | ```C 171 | 1 /* 172 | 2 * Below is the given application for lab1_1. 173 | 3 * 174 | 4 * You can build this app (as well as our PKE OS kernel) by command: 175 | 5 * $ make 176 | 6 * 177 | 7 * Or run this app (with the support from PKE OS kernel) by command: 178 | 8 * $ make run 179 | 9 */ 180 | 10 181 | 11 #include "user_lib.h" 182 | 12 183 | 13 int main(void) { 184 | 14 printu("Hello world!\n"); 185 | 15 186 | 16 exit(0); 187 | 17 } 188 | ``` 189 | 190 | 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. 191 | 192 | The prototype and implementation of *printu* can be found in *user/user.h* and *user/do_print.c* respectively. 193 | 194 | Switching to next stage 195 | ---------- 196 | 197 | 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: 198 | 199 | ```bash 200 | $ git commit -a -m "your comments to lab1_1" 201 | ``` 202 | 203 | then, switch to next lab (lab1_2) by: 204 | 205 | ```bash 206 | $ git checkout lab1_2_exception 207 | ``` 208 | 209 | and merge your solution in previous lab by: 210 | 211 | ```bash 212 | $ git merge lab1_1_syscall -m "continue lab1_2" 213 | ``` 214 | 215 | After all these, you can proceed to work on lab1_2. 216 | 217 | **Note**: Never merge challenge labs, such as lab1_challenge1_backtrace, lab2_challenge1_pagefaults, etc. 218 | 219 | 220 | 221 | That's all. Hope you enjoy! 222 | 223 | -------------------------------------------------------------------------------- /hostfs_root/bin/app_cat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuwenbo0/hust-os/2df1f7b6eed3c74dbf43ed59e5f317414d8033bc/hostfs_root/bin/app_cat -------------------------------------------------------------------------------- /hostfs_root/bin/app_echo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuwenbo0/hust-os/2df1f7b6eed3c74dbf43ed59e5f317414d8033bc/hostfs_root/bin/app_echo -------------------------------------------------------------------------------- /hostfs_root/bin/app_ls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuwenbo0/hust-os/2df1f7b6eed3c74dbf43ed59e5f317414d8033bc/hostfs_root/bin/app_ls -------------------------------------------------------------------------------- /hostfs_root/bin/app_mkdir: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuwenbo0/hust-os/2df1f7b6eed3c74dbf43ed59e5f317414d8033bc/hostfs_root/bin/app_mkdir -------------------------------------------------------------------------------- /hostfs_root/bin/app_shell: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuwenbo0/hust-os/2df1f7b6eed3c74dbf43ed59e5f317414d8033bc/hostfs_root/bin/app_shell -------------------------------------------------------------------------------- /hostfs_root/bin/app_touch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuwenbo0/hust-os/2df1f7b6eed3c74dbf43ed59e5f317414d8033bc/hostfs_root/bin/app_touch -------------------------------------------------------------------------------- /hostfs_root/shellrc: -------------------------------------------------------------------------------- 1 | /bin/app_mkdir /RAMDISK0/sub_dir 2 | /bin/app_touch /RAMDISK0/sub_dir/ramfile1 3 | /bin/app_touch /RAMDISK0/sub_dir/ramfile2 4 | /bin/app_echo /RAMDISK0/sub_dir/ramfile1 5 | /bin/app_cat /RAMDISK0/sub_dir/ramfile1 6 | /bin/app_ls /RAMDISK0/sub_dir 7 | /bin/app_ls /RAMDISK0 8 | END END 9 | -------------------------------------------------------------------------------- /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 | //interval of timer interrupt. added @lab1_3 8 | #define TIMER_INTERVAL 1000000 9 | 10 | // the maximum memory space that PKE is allowed to manage. added @lab2_1 11 | #define PKE_MAX_ALLOWABLE_RAM 128 * 1024 * 1024 12 | 13 | // the ending physical address that PKE observes. added @lab2_1 14 | #define PHYS_TOP (DRAM_BASE + PKE_MAX_ALLOWABLE_RAM) 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /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 "vmm.h" 10 | #include "pmm.h" 11 | #include "vfs.h" 12 | #include "spike_interface/spike_utils.h" 13 | typedef struct elf_info_t { 14 | struct file *f; 15 | process *p; 16 | } elf_info; 17 | 18 | // 19 | // the implementation of allocater. allocates memory space for later segment loading. 20 | // this allocater is heavily modified @lab2_1, where we do NOT work in bare mode. 21 | // 22 | static void *elf_alloc_mb(elf_ctx *ctx, uint64 elf_pa, uint64 elf_va, uint64 size) { 23 | elf_info *msg = (elf_info *)ctx->info; 24 | // we assume that size of proram segment is smaller than a page. 25 | pte_t *pte = page_walk(msg->p->pagetable, elf_va, 0); 26 | if(pte != 0 && *pte & PTE_V) return (void *)PTE2PA(*pte); 27 | kassert(size < PGSIZE); 28 | void *pa = alloc_page(); 29 | if (pa == 0) panic("uvmalloc mem alloc falied\n"); 30 | 31 | memset((void *)pa, 0, PGSIZE); 32 | user_vm_map((pagetable_t)msg->p->pagetable, elf_va, PGSIZE, (uint64)pa, 33 | prot_to_type(PROT_WRITE | PROT_READ | PROT_EXEC, 1)); 34 | 35 | return pa; 36 | } 37 | 38 | // 39 | // actual file reading, using the vfs file interface. 40 | // 41 | static uint64 elf_fpread(elf_ctx *ctx, void *dest, uint64 nb, uint64 offset) { 42 | elf_info *msg = (elf_info *)ctx->info; 43 | vfs_lseek(msg->f, offset, SEEK_SET); 44 | return vfs_read(msg->f, dest, nb); 45 | } 46 | 47 | // 48 | // init elf_ctx, a data structure that loads the elf. 49 | // 50 | elf_status elf_init(elf_ctx *ctx, void *info) { 51 | ctx->info = info; 52 | 53 | // load the elf header 54 | if (elf_fpread(ctx, &ctx->ehdr, sizeof(ctx->ehdr), 0) != sizeof(ctx->ehdr)) return EL_EIO; 55 | 56 | // check the signature (magic value) of the elf 57 | if (ctx->ehdr.magic != ELF_MAGIC) return EL_NOTELF; 58 | 59 | return EL_OK; 60 | } 61 | 62 | // 63 | // load the elf segments to memory regions. 64 | // 65 | elf_status elf_load(elf_ctx *ctx) { 66 | // elf_prog_header structure is defined in kernel/elf.h 67 | elf_prog_header ph_addr; 68 | int i, off; 69 | 70 | // traverse the elf program segment headers 71 | for (i = 0, off = ctx->ehdr.phoff; i < ctx->ehdr.phnum; i++, off += sizeof(ph_addr)) { 72 | // read segment headers 73 | if (elf_fpread(ctx, (void *)&ph_addr, sizeof(ph_addr), off) != sizeof(ph_addr)) return EL_EIO; 74 | 75 | if (ph_addr.type != ELF_PROG_LOAD) continue; 76 | if (ph_addr.memsz < ph_addr.filesz) return EL_ERR; 77 | if (ph_addr.vaddr + ph_addr.memsz < ph_addr.vaddr) return EL_ERR; 78 | 79 | // allocate memory block before elf loading 80 | void *dest = elf_alloc_mb(ctx, ph_addr.vaddr, ph_addr.vaddr, ph_addr.memsz); 81 | 82 | // actual loading 83 | if (elf_fpread(ctx, dest, ph_addr.memsz, ph_addr.off) != ph_addr.memsz) 84 | return EL_EIO; 85 | 86 | // record the vm region in proc->mapped_info. added @lab3_1 87 | int j; 88 | for( j=0; jinfo))->p)->mapped_info[j].va == 0x0 ) break; 90 | 91 | ((process*)(((elf_info*)(ctx->info))->p))->mapped_info[j].va = ph_addr.vaddr; 92 | ((process*)(((elf_info*)(ctx->info))->p))->mapped_info[j].npages = 1; 93 | 94 | // SEGMENT_READABLE, SEGMENT_EXECUTABLE, SEGMENT_WRITABLE are defined in kernel/elf.h 95 | if( ph_addr.flags == (SEGMENT_READABLE|SEGMENT_EXECUTABLE) ){ 96 | ((process*)(((elf_info*)(ctx->info))->p))->mapped_info[j].seg_type = CODE_SEGMENT; 97 | sprint( "CODE_SEGMENT added at mapped info offset:%d\n", j ); 98 | }else if ( ph_addr.flags == (SEGMENT_READABLE|SEGMENT_WRITABLE) ){ 99 | ((process*)(((elf_info*)(ctx->info))->p))->mapped_info[j].seg_type = DATA_SEGMENT; 100 | sprint( "DATA_SEGMENT added at mapped info offset:%d\n", j ); 101 | }else 102 | panic( "unknown program segment encountered, segment flag:%d.\n", ph_addr.flags ); 103 | 104 | ((process*)(((elf_info*)(ctx->info))->p))->total_mapped_region ++; 105 | } 106 | 107 | return EL_OK; 108 | } 109 | 110 | // 111 | // load the elf of user application, by using the spike file interface. 112 | // 113 | void load_bincode_from_host_elf(process *p, char *filename) { 114 | sprint("Application: %s\n", filename); 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 = vfs_open(filename, O_RDONLY); 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 vfs file 137 | vfs_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 | // segment types, attributes of elf_prog_header_t.flags 29 | #define SEGMENT_READABLE 0x4 30 | #define SEGMENT_EXECUTABLE 0x1 31 | #define SEGMENT_WRITABLE 0x2 32 | 33 | // Program segment header. 34 | typedef struct elf_prog_header_t { 35 | uint32 type; /* Segment type */ 36 | uint32 flags; /* Segment flags */ 37 | uint64 off; /* Segment file offset */ 38 | uint64 vaddr; /* Segment virtual address */ 39 | uint64 paddr; /* Segment physical address */ 40 | uint64 filesz; /* Segment size in file */ 41 | uint64 memsz; /* Segment size in memory */ 42 | uint64 align; /* Segment alignment */ 43 | } elf_prog_header; 44 | 45 | #define ELF_MAGIC 0x464C457FU // "\x7FELF" in little endian 46 | #define ELF_PROG_LOAD 1 47 | 48 | typedef enum elf_status_t { 49 | EL_OK = 0, 50 | 51 | EL_EIO, 52 | EL_ENOMEM, 53 | EL_NOTELF, 54 | EL_ERR, 55 | 56 | } elf_status; 57 | 58 | typedef struct elf_ctx_t { 59 | void *info; 60 | elf_header ehdr; 61 | } elf_ctx; 62 | 63 | elf_status elf_init(elf_ctx *ctx, void *info); 64 | elf_status elf_load(elf_ctx *ctx); 65 | 66 | void load_bincode_from_host_elf(process *p, char *filename); 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /kernel/hostfs.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Interface functions between VFS and host-fs. added @lab4_1. 3 | */ 4 | #include "hostfs.h" 5 | 6 | #include "pmm.h" 7 | #include "spike_interface/spike_file.h" 8 | #include "spike_interface/spike_utils.h" 9 | #include "util/string.h" 10 | #include "util/types.h" 11 | #include "vfs.h" 12 | 13 | /**** host-fs vinode interface ****/ 14 | const struct vinode_ops hostfs_i_ops = { 15 | .viop_read = hostfs_read, 16 | .viop_write = hostfs_write, 17 | .viop_create = hostfs_create, 18 | .viop_lseek = hostfs_lseek, 19 | .viop_lookup = hostfs_lookup, 20 | 21 | .viop_hook_open = hostfs_hook_open, 22 | .viop_hook_close = hostfs_hook_close, 23 | 24 | .viop_write_back_vinode = hostfs_write_back_vinode, 25 | 26 | // not implemented 27 | .viop_link = hostfs_link, 28 | .viop_unlink = hostfs_unlink, 29 | .viop_readdir = hostfs_readdir, 30 | .viop_mkdir = hostfs_mkdir, 31 | }; 32 | 33 | /**** hostfs utility functions ****/ 34 | // 35 | // append hostfs to the fs list. 36 | // 37 | int register_hostfs() { 38 | struct file_system_type *fs_type = (struct file_system_type *)alloc_page(); 39 | fs_type->type_num = HOSTFS_TYPE; 40 | fs_type->get_superblock = hostfs_get_superblock; 41 | 42 | for (int i = 0; i < MAX_SUPPORTED_FS; i++) { 43 | if (fs_list[i] == NULL) { 44 | fs_list[i] = fs_type; 45 | return 0; 46 | } 47 | } 48 | return -1; 49 | } 50 | 51 | // 52 | // append new device under "name" to vfs_dev_list. 53 | // 54 | struct device *init_host_device(char *name) { 55 | // find rfs in registered fs list 56 | struct file_system_type *fs_type = NULL; 57 | for (int i = 0; i < MAX_SUPPORTED_FS; i++) { 58 | if (fs_list[i] != NULL && fs_list[i]->type_num == HOSTFS_TYPE) { 59 | fs_type = fs_list[i]; 60 | break; 61 | } 62 | } 63 | if (!fs_type) 64 | panic("init_host_device: No HOSTFS file system found!\n"); 65 | 66 | // allocate a vfs device 67 | struct device *device = (struct device *)alloc_page(); 68 | // set the device name and index 69 | strcpy(device->dev_name, name); 70 | // we only support one host-fs device 71 | device->dev_id = 0; 72 | device->fs_type = fs_type; 73 | 74 | // add the device to the vfs device list 75 | for (int i = 0; i < MAX_VFS_DEV; i++) { 76 | if (vfs_dev_list[i] == NULL) { 77 | vfs_dev_list[i] = device; 78 | break; 79 | } 80 | } 81 | 82 | return device; 83 | } 84 | 85 | // 86 | // recursive call to assemble a path. 87 | // 88 | void path_backtrack(char *path, struct dentry *dentry) { 89 | if (dentry->parent == NULL) { 90 | return; 91 | } 92 | path_backtrack(path, dentry->parent); 93 | strcat(path, "/"); 94 | strcat(path, dentry->name); 95 | } 96 | 97 | // 98 | // obtain the absolute path for "dentry", from root to file. 99 | // 100 | void get_path_string(char *path, struct dentry *dentry) { 101 | strcpy(path, H_ROOT_DIR); 102 | path_backtrack(path, dentry); 103 | } 104 | 105 | // 106 | // allocate a vfs inode for an host fs file. 107 | // 108 | struct vinode *hostfs_alloc_vinode(struct super_block *sb) { 109 | struct vinode *vinode = default_alloc_vinode(sb); 110 | vinode->inum = -1; 111 | vinode->i_fs_info = NULL; 112 | vinode->i_ops = &hostfs_i_ops; 113 | return vinode; 114 | } 115 | 116 | int hostfs_write_back_vinode(struct vinode *vinode) { return 0; } 117 | 118 | // 119 | // populate the vfs inode of an hostfs file, according to its stats. 120 | // 121 | int hostfs_update_vinode(struct vinode *vinode) { 122 | spike_file_t *f = vinode->i_fs_info; 123 | if ((int64)f < 0) { // is a direntry 124 | vinode->type = H_DIR; 125 | return -1; 126 | } 127 | 128 | struct stat stat; 129 | spike_file_stat(f, &stat); 130 | 131 | vinode->inum = stat.st_ino; 132 | vinode->size = stat.st_size; 133 | vinode->nlinks = stat.st_nlink; 134 | vinode->blocks = stat.st_blocks; 135 | 136 | if (S_ISDIR(stat.st_mode)) { 137 | vinode->type = H_DIR; 138 | } else if (S_ISREG(stat.st_mode)) { 139 | vinode->type = H_FILE; 140 | } else { 141 | sprint("hostfs_lookup:unknown file type!"); 142 | return -1; 143 | } 144 | 145 | return 0; 146 | } 147 | 148 | /**** vfs-host-fs interface functions ****/ 149 | // 150 | // read a hostfs file. 151 | // 152 | ssize_t hostfs_read(struct vinode *f_inode, char *r_buf, ssize_t len, 153 | int *offset) { 154 | spike_file_t *pf = (spike_file_t *)f_inode->i_fs_info; 155 | if (pf < 0) { 156 | sprint("hostfs_read: invalid file handle!\n"); 157 | return -1; 158 | } 159 | int read_len = spike_file_read(pf, r_buf, len); 160 | // obtain current offset 161 | *offset = spike_file_lseek(pf, 0, 1); 162 | return read_len; 163 | } 164 | 165 | // 166 | // write a hostfs file. 167 | // 168 | ssize_t hostfs_write(struct vinode *f_inode, const char *w_buf, ssize_t len, 169 | int *offset) { 170 | spike_file_t *pf = (spike_file_t *)f_inode->i_fs_info; 171 | if (pf < 0) { 172 | sprint("hostfs_write: invalid file handle!\n"); 173 | return -1; 174 | } 175 | int write_len = spike_file_write(pf, w_buf, len); 176 | // obtain current offset 177 | *offset = spike_file_lseek(pf, 0, 1); 178 | return write_len; 179 | } 180 | 181 | // 182 | // lookup a hostfs file, and establish its vfs inode in PKE vfs. 183 | // 184 | struct vinode *hostfs_lookup(struct vinode *parent, struct dentry *sub_dentry) { 185 | // get complete path string 186 | char path[MAX_PATH_LEN]; 187 | get_path_string(path, sub_dentry); 188 | 189 | spike_file_t *f = spike_file_open(path, O_RDWR, 0); 190 | 191 | struct vinode *child_inode = hostfs_alloc_vinode(parent->sb); 192 | child_inode->i_fs_info = f; 193 | hostfs_update_vinode(child_inode); 194 | 195 | child_inode->ref = 0; 196 | return child_inode; 197 | } 198 | 199 | // 200 | // creates a hostfs file, and establish its vfs inode. 201 | // 202 | struct vinode *hostfs_create(struct vinode *parent, struct dentry *sub_dentry) { 203 | char path[MAX_PATH_LEN]; 204 | get_path_string(path, sub_dentry); 205 | 206 | spike_file_t *f = spike_file_open(path, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); 207 | if ((int64)f < 0) { 208 | sprint("hostfs_create cannot create the given file.\n"); 209 | return NULL; 210 | } 211 | 212 | struct vinode *new_inode = hostfs_alloc_vinode(parent->sb); 213 | new_inode->i_fs_info = f; 214 | 215 | if (hostfs_update_vinode(new_inode) != 0) return NULL; 216 | 217 | new_inode->ref = 0; 218 | return new_inode; 219 | } 220 | 221 | // 222 | // reposition read/write file offset 223 | // 224 | int hostfs_lseek(struct vinode *f_inode, ssize_t new_offset, int whence, 225 | int *offset) { 226 | spike_file_t *f = (spike_file_t *)f_inode->i_fs_info; 227 | if (f < 0) { 228 | sprint("hostfs_lseek: invalid file handle!\n"); 229 | return -1; 230 | } 231 | 232 | *offset = spike_file_lseek(f, new_offset, whence); 233 | if (*offset >= 0) 234 | return 0; 235 | return -1; 236 | } 237 | 238 | int hostfs_link(struct vinode *parent, struct dentry *sub_dentry, 239 | struct vinode *link_node) { 240 | panic("hostfs_link not implemented!\n"); 241 | return -1; 242 | } 243 | 244 | int hostfs_unlink(struct vinode *parent, struct dentry *sub_dentry, struct vinode *unlink_node) { 245 | panic("hostfs_unlink not implemented!\n"); 246 | return -1; 247 | } 248 | 249 | int hostfs_readdir(struct vinode *dir_vinode, struct dir *dir, int *offset) { 250 | panic("hostfs_readdir not implemented!\n"); 251 | return -1; 252 | } 253 | 254 | struct vinode *hostfs_mkdir(struct vinode *parent, struct dentry *sub_dentry) { 255 | panic("hostfs_mkdir not implemented!\n"); 256 | return NULL; 257 | } 258 | 259 | /**** vfs-hostfs hook interface functions ****/ 260 | // 261 | // open a hostfs file (after having its vfs inode). 262 | // 263 | int hostfs_hook_open(struct vinode *f_inode, struct dentry *f_dentry) { 264 | if (f_inode->i_fs_info != NULL) return 0; 265 | 266 | char path[MAX_PATH_LEN]; 267 | get_path_string(path, f_dentry); 268 | spike_file_t *f = spike_file_open(path, O_RDWR, 0); 269 | if ((int64)f < 0) { 270 | sprint("hostfs_hook_open cannot open the given file.\n"); 271 | return -1; 272 | } 273 | 274 | f_inode->i_fs_info = f; 275 | return 0; 276 | } 277 | 278 | // 279 | // close a hostfs file. 280 | // 281 | int hostfs_hook_close(struct vinode *f_inode, struct dentry *dentry) { 282 | spike_file_t *f = (spike_file_t *)f_inode->i_fs_info; 283 | spike_file_close(f); 284 | return 0; 285 | } 286 | 287 | /**** vfs-hostfs file system type interface functions ****/ 288 | struct super_block *hostfs_get_superblock(struct device *dev) { 289 | // set the data for the vfs super block 290 | struct super_block *sb = alloc_page(); 291 | sb->s_dev = dev; 292 | 293 | struct vinode *root_inode = hostfs_alloc_vinode(sb); 294 | root_inode->type = H_DIR; 295 | 296 | struct dentry *root_dentry = alloc_vfs_dentry("/", root_inode, NULL); 297 | sb->s_root = root_dentry; 298 | 299 | return sb; 300 | } 301 | -------------------------------------------------------------------------------- /kernel/hostfs.h: -------------------------------------------------------------------------------- 1 | #ifndef _HOSTFS_H_ 2 | #define _HOSTFS_H_ 3 | #include "vfs.h" 4 | 5 | #define HOSTFS_TYPE 1 6 | 7 | // dinode type 8 | #define H_FILE FILE_I 9 | #define H_DIR DIR_I 10 | 11 | // root directory 12 | #define H_ROOT_DIR "./hostfs_root" 13 | 14 | // hostfs utility functin declarations 15 | int register_hostfs(); 16 | struct device *init_host_device(char *name); 17 | void get_path_string(char *path, struct dentry *dentry); 18 | struct vinode *hostfs_alloc_vinode(struct super_block *sb); 19 | int hostfs_write_back_vinode(struct vinode *vinode); 20 | int hostfs_update_vinode(struct vinode *vinode); 21 | 22 | // hostfs interface function declarations 23 | ssize_t hostfs_read(struct vinode *f_inode, char *r_buf, ssize_t len, 24 | int *offset); 25 | ssize_t hostfs_write(struct vinode *f_inode, const char *w_buf, ssize_t len, 26 | int *offset); 27 | struct vinode *hostfs_lookup(struct vinode *parent, struct dentry *sub_dentry); 28 | struct vinode *hostfs_create(struct vinode *parent, struct dentry *sub_dentry); 29 | int hostfs_lseek(struct vinode *f_inode, ssize_t new_offset, int whence, 30 | int *offset); 31 | int hostfs_link(struct vinode *parent, struct dentry *sub_dentry, struct vinode *link_node); 32 | int hostfs_unlink(struct vinode *parent, struct dentry *sub_dentry, struct vinode *unlink_node); 33 | int hostfs_hook_open(struct vinode *f_inode, struct dentry *f_dentry); 34 | int hostfs_hook_close(struct vinode *f_inode, struct dentry *dentry); 35 | int hostfs_readdir(struct vinode *dir_vinode, struct dir *dir, int *offset); 36 | struct vinode *hostfs_mkdir(struct vinode *parent, struct dentry *sub_dentry); 37 | struct super_block *hostfs_get_superblock(struct device *dev); 38 | 39 | extern const struct vinode_ops hostfs_node_ops; 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /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 | #include "pmm.h" 10 | #include "vmm.h" 11 | #include "sched.h" 12 | #include "memlayout.h" 13 | #include "spike_interface/spike_utils.h" 14 | #include "util/types.h" 15 | #include "vfs.h" 16 | #include "rfs.h" 17 | #include "ramdev.h" 18 | 19 | // 20 | // trap_sec_start points to the beginning of S-mode trap segment (i.e., the entry point of 21 | // S-mode trap vector). added @lab2_1 22 | // 23 | extern char trap_sec_start[]; 24 | 25 | // 26 | // turn on paging. added @lab2_1 27 | // 28 | void enable_paging() { 29 | // write the pointer to kernel page (table) directory into the CSR of "satp". 30 | write_csr(satp, MAKE_SATP(g_kernel_pagetable)); 31 | 32 | // refresh tlb to invalidate its content. 33 | flush_tlb(); 34 | } 35 | 36 | typedef union { 37 | uint64 buf[MAX_CMDLINE_ARGS]; 38 | char *argv[MAX_CMDLINE_ARGS]; 39 | } arg_buf; 40 | 41 | // 42 | // returns the number (should be 1) of string(s) after PKE kernel in command line. 43 | // and store the string(s) in arg_bug_msg. 44 | // 45 | static size_t parse_args(arg_buf *arg_bug_msg) { 46 | // HTIFSYS_getmainvars frontend call reads command arguments to (input) *arg_bug_msg 47 | long r = frontend_syscall(HTIFSYS_getmainvars, (uint64)arg_bug_msg, 48 | sizeof(*arg_bug_msg), 0, 0, 0, 0, 0); 49 | kassert(r == 0); 50 | 51 | size_t pk_argc = arg_bug_msg->buf[0]; 52 | uint64 *pk_argv = &arg_bug_msg->buf[1]; 53 | 54 | int arg = 1; // skip the PKE OS kernel string, leave behind only the application name 55 | for (size_t i = 0; arg + i < pk_argc; i++) 56 | arg_bug_msg->argv[i] = (char *)(uintptr_t)pk_argv[arg + i]; 57 | 58 | //returns the number of strings after PKE kernel in command line 59 | return pk_argc - arg; 60 | } 61 | 62 | // 63 | // load the elf, and construct a "process" (with only a trapframe). 64 | // load_bincode_from_host_elf is defined in elf.c 65 | // 66 | process* load_user_program() { 67 | process* proc; 68 | 69 | proc = alloc_process(); 70 | sprint("User application is loading.\n"); 71 | 72 | arg_buf arg_bug_msg; 73 | 74 | // retrieve command line arguements 75 | size_t argc = parse_args(&arg_bug_msg); 76 | if (!argc) panic("You need to specify the application program!\n"); 77 | 78 | load_bincode_from_host_elf(proc, arg_bug_msg.argv[0]); 79 | return proc; 80 | } 81 | 82 | // 83 | // s_start: S-mode entry point of riscv-pke OS kernel. 84 | // 85 | int s_start(void) { 86 | sprint("Enter supervisor mode...\n"); 87 | // in the beginning, we use Bare mode (direct) memory mapping as in lab1. 88 | // but now, we are going to switch to the paging mode @lab2_1. 89 | // note, the code still works in Bare mode when calling pmm_init() and kern_vm_init(). 90 | write_csr(satp, 0); 91 | 92 | // init phisical memory manager 93 | pmm_init(); 94 | 95 | // build the kernel page table 96 | kern_vm_init(); 97 | 98 | // now, switch to paging mode by turning on paging (SV39) 99 | enable_paging(); 100 | // the code now formally works in paging mode, meaning the page table is now in use. 101 | sprint("kernel page table is on \n"); 102 | 103 | // added @lab3_1 104 | init_proc_pool(); 105 | 106 | // init file system, added @lab4_1 107 | fs_init(); 108 | 109 | sprint("Switch to user mode...\n"); 110 | // the application code (elf) is first loaded into memory, and then put into execution 111 | // added @lab3_1 112 | insert_to_ready_queue( load_user_program() ); 113 | schedule(); 114 | 115 | // we should never reach here. 116 | return 0; 117 | } 118 | -------------------------------------------------------------------------------- /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 | // M-mode trap entry point, added @lab1_2 23 | extern void mtrapvec(); 24 | 25 | // htif is defined in spike_interface/spike_htif.c, marks the availability of HTIF 26 | extern uint64 htif; 27 | // g_mem_size is defined in spike_interface/spike_memory.c, size of the emulated memory 28 | extern uint64 g_mem_size; 29 | // struct riscv_regs is define in kernel/riscv.h, and g_itrframe is used to save 30 | // registers when interrupt hapens in M mode. added @lab1_2 31 | riscv_regs g_itrframe; 32 | 33 | // 34 | // get the information of HTIF (calling interface) and the emulated memory by 35 | // parsing the Device Tree Blog (DTB, actually DTS) stored in memory. 36 | // 37 | // the role of DTB is similar to that of Device Address Resolution Table (DART) 38 | // in Intel series CPUs. it records the details of devices and memory of the 39 | // platform simulated using Spike. 40 | // 41 | void init_dtb(uint64 dtb) { 42 | // defined in spike_interface/spike_htif.c, enabling Host-Target InterFace (HTIF) 43 | query_htif(dtb); 44 | if (htif) sprint("HTIF is available!\r\n"); 45 | 46 | // defined in spike_interface/spike_memory.c, obtain information about emulated memory 47 | query_mem(dtb); 48 | sprint("(Emulated) memory size: %ld MB\n", g_mem_size >> 20); 49 | } 50 | 51 | // 52 | // delegate (almost all) interrupts and most exceptions to S-mode. 53 | // after delegation, syscalls will handled by the PKE OS kernel running in S-mode. 54 | // 55 | static void delegate_traps() { 56 | // supports_extension macro is defined in kernel/riscv.h 57 | if (!supports_extension('S')) { 58 | // confirm that our processor supports supervisor mode. abort if it does not. 59 | sprint("S mode is not supported.\n"); 60 | return; 61 | } 62 | 63 | // macros used in following two statements are defined in kernel/riscv.h 64 | uintptr_t interrupts = MIP_SSIP | MIP_STIP | MIP_SEIP; 65 | uintptr_t exceptions = (1U << CAUSE_MISALIGNED_FETCH) | (1U << CAUSE_FETCH_PAGE_FAULT) | 66 | (1U << CAUSE_BREAKPOINT) | (1U << CAUSE_LOAD_PAGE_FAULT) | 67 | (1U << CAUSE_STORE_PAGE_FAULT) | (1U << CAUSE_USER_ECALL); 68 | 69 | // writes 64-bit values (interrupts and exceptions) to 'mideleg' and 'medeleg' (two 70 | // priviledged registers of RV64G machine) respectively. 71 | // 72 | // write_csr and read_csr are macros defined in kernel/riscv.h 73 | write_csr(mideleg, interrupts); 74 | write_csr(medeleg, exceptions); 75 | assert(read_csr(mideleg) == interrupts); 76 | assert(read_csr(medeleg) == exceptions); 77 | } 78 | 79 | // 80 | // enabling timer interrupt (irq) in Machine mode. added @lab1_3 81 | // 82 | void timerinit(uintptr_t hartid) { 83 | // fire timer irq after TIMER_INTERVAL from now. 84 | *(uint64*)CLINT_MTIMECMP(hartid) = *(uint64*)CLINT_MTIME + TIMER_INTERVAL; 85 | 86 | // enable machine-mode timer irq in MIE (Machine Interrupt Enable) csr. 87 | write_csr(mie, read_csr(mie) | MIE_MTIE); 88 | } 89 | 90 | // 91 | // m_start: machine mode C entry point. 92 | // 93 | void m_start(uintptr_t hartid, uintptr_t dtb) { 94 | // init the spike file interface (stdin,stdout,stderr) 95 | // functions with "spike_" prefix are all defined in codes under spike_interface/, 96 | // sprint is also defined in spike_interface/spike_utils.c 97 | spike_file_init(); 98 | sprint("In m_start, hartid:%d\n", hartid); 99 | 100 | // init HTIF (Host-Target InterFace) and memory by using the Device Table Blob (DTB) 101 | // init_dtb() is defined above. 102 | init_dtb(dtb); 103 | 104 | // save the address of trap frame for interrupt in M mode to "mscratch". added @lab1_2 105 | write_csr(mscratch, &g_itrframe); 106 | 107 | // set previous privilege mode to S (Supervisor), and will enter S mode after 'mret' 108 | // write_csr is a macro defined in kernel/riscv.h 109 | write_csr(mstatus, ((read_csr(mstatus) & ~MSTATUS_MPP_MASK) | MSTATUS_MPP_S)); 110 | 111 | // set M Exception Program Counter to sstart, for mret (requires gcc -mcmodel=medany) 112 | write_csr(mepc, (uint64)s_start); 113 | 114 | // setup trap handling vector for machine mode. added @lab1_2 115 | write_csr(mtvec, (uint64)mtrapvec); 116 | 117 | // enable machine-mode interrupts. added @lab1_3 118 | write_csr(mstatus, read_csr(mstatus) | MSTATUS_MIE); 119 | 120 | // delegate all interrupts and exceptions to supervisor mode. 121 | // delegate_traps() is defined above. 122 | delegate_traps(); 123 | 124 | // also enables interrupt handling in supervisor mode. added @lab1_3 125 | write_csr(sie, read_csr(sie) | SIE_SEIE | SIE_STIE | SIE_SSIE); 126 | 127 | // init timing. added @lab1_3 128 | timerinit(hartid); 129 | 130 | // switch to supervisor mode (S mode) and jump to s_start(), i.e., set pc to mepc 131 | asm volatile("mret"); 132 | } 133 | -------------------------------------------------------------------------------- /kernel/machine/mtrap.c: -------------------------------------------------------------------------------- 1 | #include "kernel/riscv.h" 2 | #include "kernel/process.h" 3 | #include "spike_interface/spike_utils.h" 4 | 5 | static void handle_instruction_access_fault() { panic("Instruction access fault!"); } 6 | 7 | static void handle_load_access_fault() { panic("Load access fault!"); } 8 | 9 | static void handle_store_access_fault() { panic("Store/AMO access fault!"); } 10 | 11 | static void handle_illegal_instruction() { panic("Illegal instruction!"); } 12 | 13 | static void handle_misaligned_load() { panic("Misaligned Load!"); } 14 | 15 | static void handle_misaligned_store() { panic("Misaligned AMO!"); } 16 | 17 | // added @lab1_3 18 | static void handle_timer() { 19 | int cpuid = 0; 20 | // setup the timer fired at next time (TIMER_INTERVAL from now) 21 | *(uint64*)CLINT_MTIMECMP(cpuid) = *(uint64*)CLINT_MTIMECMP(cpuid) + TIMER_INTERVAL; 22 | 23 | // setup a soft interrupt in sip (S-mode Interrupt Pending) to be handled in S-mode 24 | write_csr(sip, SIP_SSIP); 25 | } 26 | 27 | // 28 | // handle_mtrap calls a handling function according to the type of a machine mode interrupt (trap). 29 | // 30 | void handle_mtrap() { 31 | uint64 mcause = read_csr(mcause); 32 | switch (mcause) { 33 | case CAUSE_MTIMER: 34 | handle_timer(); 35 | break; 36 | case CAUSE_FETCH_ACCESS: 37 | handle_instruction_access_fault(); 38 | break; 39 | case CAUSE_LOAD_ACCESS: 40 | handle_load_access_fault(); 41 | case CAUSE_STORE_ACCESS: 42 | handle_store_access_fault(); 43 | break; 44 | case CAUSE_ILLEGAL_INSTRUCTION: 45 | // TODO (lab1_2): call handle_illegal_instruction to implement illegal instruction 46 | // interception, and finish lab1_2. 47 | //panic( "call handle_illegal_instruction to accomplish illegal instruction interception for lab1_2.\n" ); 48 | handle_illegal_instruction(); 49 | break; 50 | case CAUSE_MISALIGNED_LOAD: 51 | handle_misaligned_load(); 52 | break; 53 | case CAUSE_MISALIGNED_STORE: 54 | handle_misaligned_store(); 55 | break; 56 | 57 | default: 58 | sprint("machine trap(): unexpected mscause %p\n", mcause); 59 | sprint(" mepc=%p mtval=%p\n", read_csr(mepc), read_csr(mtval)); 60 | panic( "unexpected exception happened in M-mode.\n" ); 61 | break; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /kernel/machine/mtrap_vector.S: -------------------------------------------------------------------------------- 1 | #include "util/load_store.S" 2 | 3 | # 4 | # M-mode trap entry point 5 | # 6 | .globl mtrapvec 7 | .align 4 8 | mtrapvec: 9 | # mscratch -> g_itrframe (cf. kernel/machine/minit.c line 94) 10 | # swap a0 and mscratch, so that a0 points to interrupt frame, 11 | # i.e., [a0] = &g_itrframe 12 | csrrw a0, mscratch, a0 13 | 14 | # save the registers in g_itrframe 15 | addi t6, a0, 0 16 | store_all_registers 17 | # save the original content of a0 in g_itrframe 18 | csrr t0, mscratch 19 | sd t0, 72(a0) 20 | 21 | # switch stack (to use stack0) for the rest of machine mode 22 | # trap handling. 23 | la sp, stack0 24 | li a3, 4096 25 | csrr a4, mhartid 26 | addi a4, a4, 1 27 | mul a3, a3, a4 28 | add sp, sp, a3 29 | 30 | # pointing mscratch back to g_itrframe 31 | csrw mscratch, a0 32 | 33 | # call machine mode trap handling function 34 | call handle_mtrap 35 | 36 | # restore all registers, come back to the status before entering 37 | # machine mode handling. 38 | csrr t6, mscratch 39 | restore_all_registers 40 | 41 | mret 42 | -------------------------------------------------------------------------------- /kernel/memlayout.h: -------------------------------------------------------------------------------- 1 | #ifndef _MEMLAYOUT_H 2 | #define _MEMLAYOUT_H 3 | #include "riscv.h" 4 | 5 | // RISC-V machine places its physical memory above DRAM_BASE 6 | #define DRAM_BASE 0x80000000 7 | 8 | // the beginning virtual address of PKE kernel 9 | #define KERN_BASE 0x80000000 10 | 11 | // default stack size 12 | #define STACK_SIZE 4096 13 | 14 | // virtual address of stack top of user process 15 | #define USER_STACK_TOP 0x7ffff000 16 | 17 | // start virtual address (4MB) of our simple heap. added @lab2_2 18 | #define USER_FREE_ADDRESS_START 0x00000000 + PGSIZE * 1024 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /kernel/pmm.c: -------------------------------------------------------------------------------- 1 | #include "pmm.h" 2 | #include "util/functions.h" 3 | #include "riscv.h" 4 | #include "config.h" 5 | #include "util/string.h" 6 | #include "memlayout.h" 7 | #include "spike_interface/spike_utils.h" 8 | 9 | // _end is defined in kernel/kernel.lds, it marks the ending (virtual) address of PKE kernel 10 | extern char _end[]; 11 | // g_mem_size is defined in spike_interface/spike_memory.c, it indicates the size of our 12 | // (emulated) spike machine. g_mem_size's value is obtained when initializing HTIF. 13 | extern uint64 g_mem_size; 14 | 15 | static uint64 free_mem_start_addr; //beginning address of free memory 16 | static uint64 free_mem_end_addr; //end address of free memory (not included) 17 | 18 | typedef struct node { 19 | struct node *next; 20 | } list_node; 21 | 22 | // g_free_mem_list is the head of the list of free physical memory pages 23 | static list_node g_free_mem_list; 24 | 25 | // 26 | // actually creates the freepage list. each page occupies 4KB (PGSIZE), i.e., small page. 27 | // PGSIZE is defined in kernel/riscv.h, ROUNDUP is defined in util/functions.h. 28 | // 29 | static void create_freepage_list(uint64 start, uint64 end) { 30 | g_free_mem_list.next = 0; 31 | for (uint64 p = ROUNDUP(start, PGSIZE); p + PGSIZE < end; p += PGSIZE) 32 | free_page( (void *)p ); 33 | } 34 | 35 | // 36 | // place a physical page at *pa to the free list of g_free_mem_list (to reclaim the page) 37 | // 38 | void free_page(void *pa) { 39 | if (((uint64)pa % PGSIZE) != 0 || (uint64)pa < free_mem_start_addr || (uint64)pa >= free_mem_end_addr) 40 | panic("free_page 0x%lx \n", pa); 41 | 42 | // insert a physical page to g_free_mem_list 43 | list_node *n = (list_node *)pa; 44 | n->next = g_free_mem_list.next; 45 | g_free_mem_list.next = n; 46 | } 47 | 48 | // 49 | // takes the first free page from g_free_mem_list, and returns (allocates) it. 50 | // Allocates only ONE page! 51 | // 52 | void *alloc_page(void) { 53 | list_node *n = g_free_mem_list.next; 54 | if (n) g_free_mem_list.next = n->next; 55 | 56 | return (void *)n; 57 | } 58 | 59 | // 60 | // pmm_init() establishes the list of free physical pages according to available 61 | // physical memory space. 62 | // 63 | void pmm_init() { 64 | // start of kernel program segment 65 | uint64 g_kernel_start = KERN_BASE; 66 | uint64 g_kernel_end = (uint64)&_end; 67 | 68 | uint64 pke_kernel_size = g_kernel_end - g_kernel_start; 69 | sprint("PKE kernel start 0x%lx, PKE kernel end: 0x%lx, PKE kernel size: 0x%lx .\n", 70 | g_kernel_start, g_kernel_end, pke_kernel_size); 71 | 72 | // free memory starts from the end of PKE kernel and must be page-aligined 73 | free_mem_start_addr = ROUNDUP(g_kernel_end , PGSIZE); 74 | 75 | // recompute g_mem_size to limit the physical memory space that our riscv-pke kernel 76 | // needs to manage 77 | g_mem_size = MIN(PKE_MAX_ALLOWABLE_RAM, g_mem_size); 78 | if( g_mem_size < pke_kernel_size ) 79 | panic( "Error when recomputing physical memory size (g_mem_size).\n" ); 80 | 81 | free_mem_end_addr = g_mem_size + DRAM_BASE; 82 | sprint("free physical memory address: [0x%lx, 0x%lx] \n", free_mem_start_addr, 83 | free_mem_end_addr - 1); 84 | 85 | sprint("kernel memory manager is initializing ...\n"); 86 | // create the list of free pages 87 | create_freepage_list(free_mem_start_addr, free_mem_end_addr); 88 | } 89 | -------------------------------------------------------------------------------- /kernel/pmm.h: -------------------------------------------------------------------------------- 1 | #ifndef _PMM_H_ 2 | #define _PMM_H_ 3 | 4 | // Initialize phisical memeory manager 5 | void pmm_init(); 6 | // Allocate a free phisical page 7 | void* alloc_page(); 8 | // Free an allocated page 9 | void free_page(void* pa); 10 | 11 | #endif -------------------------------------------------------------------------------- /kernel/proc_file.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Interface functions between file system and kernel/processes. added @lab4_1 3 | */ 4 | 5 | #include "proc_file.h" 6 | 7 | #include "hostfs.h" 8 | #include "pmm.h" 9 | #include "process.h" 10 | #include "ramdev.h" 11 | #include "rfs.h" 12 | #include "riscv.h" 13 | #include "spike_interface/spike_file.h" 14 | #include "spike_interface/spike_utils.h" 15 | #include "util/functions.h" 16 | #include "util/string.h" 17 | 18 | // 19 | // initialize file system 20 | // 21 | void fs_init(void) { 22 | // initialize the vfs 23 | vfs_init(); 24 | 25 | // register hostfs and mount it as the root 26 | if( register_hostfs() < 0 ) panic( "fs_init: cannot register hostfs.\n" ); 27 | struct device *hostdev = init_host_device("HOSTDEV"); 28 | vfs_mount("HOSTDEV", MOUNT_AS_ROOT); 29 | 30 | // register and mount rfs 31 | if( register_rfs() < 0 ) panic( "fs_init: cannot register rfs.\n" ); 32 | struct device *ramdisk0 = init_rfs_device("RAMDISK0"); 33 | rfs_format_dev(ramdisk0); 34 | vfs_mount("RAMDISK0", MOUNT_DEFAULT); 35 | } 36 | 37 | // 38 | // initialize a proc_file_management data structure for a process. 39 | // return the pointer to the page containing the data structure. 40 | // 41 | proc_file_management *init_proc_file_management(void) { 42 | proc_file_management *pfiles = (proc_file_management *)alloc_page(); 43 | pfiles->cwd = vfs_root_dentry; // by default, cwd is the root 44 | pfiles->nfiles = 0; 45 | 46 | for (int fd = 0; fd < MAX_FILES; ++fd) 47 | pfiles->opened_files[fd].status = FD_NONE; 48 | 49 | sprint("FS: created a file management struct for a process.\n"); 50 | return pfiles; 51 | } 52 | 53 | // 54 | // reclaim the open-file management data structure of a process. 55 | // note: this function is not used as PKE does not actually reclaim a process. 56 | // 57 | void reclaim_proc_file_management(proc_file_management *pfiles) { 58 | free_page(pfiles); 59 | return; 60 | } 61 | 62 | // 63 | // get an opened file from proc->opened_file array. 64 | // return: the pointer to the opened file structure. 65 | // 66 | struct file *get_opened_file(int fd) { 67 | struct file *pfile = NULL; 68 | 69 | // browse opened file list to locate the fd 70 | for (int i = 0; i < MAX_FILES; ++i) { 71 | pfile = &(current->pfiles->opened_files[i]); // file entry 72 | if (i == fd) break; 73 | } 74 | if (pfile == NULL) panic("do_read: invalid fd!\n"); 75 | return pfile; 76 | } 77 | 78 | // 79 | // open a file named as "pathname" with the permission of "flags". 80 | // return: -1 on failure; non-zero file-descriptor on success. 81 | // 82 | int do_open(char *pathname, int flags) { 83 | struct file *opened_file = NULL; 84 | if ((opened_file = vfs_open(pathname, flags)) == NULL) return -1; 85 | 86 | int fd = 0; 87 | if (current->pfiles->nfiles >= MAX_FILES) { 88 | panic("do_open: no file entry for current process!\n"); 89 | } 90 | struct file *pfile; 91 | for (fd = 0; fd < MAX_FILES; ++fd) { 92 | pfile = &(current->pfiles->opened_files[fd]); 93 | if (pfile->status == FD_NONE) break; 94 | } 95 | 96 | // initialize this file structure 97 | memcpy(pfile, opened_file, sizeof(struct file)); 98 | 99 | ++current->pfiles->nfiles; 100 | return fd; 101 | } 102 | 103 | // 104 | // read content of a file ("fd") into "buf" for "count". 105 | // return: actual length of data read from the file. 106 | // 107 | int do_read(int fd, char *buf, uint64 count) { 108 | struct file *pfile = get_opened_file(fd); 109 | 110 | if (pfile->readable == 0) panic("do_read: no readable file!\n"); 111 | 112 | char buffer[count + 1]; 113 | int len = vfs_read(pfile, buffer, count); 114 | buffer[count] = '\0'; 115 | strcpy(buf, buffer); 116 | return len; 117 | } 118 | 119 | // 120 | // write content ("buf") whose length is "count" to a file "fd". 121 | // return: actual length of data written to the file. 122 | // 123 | int do_write(int fd, char *buf, uint64 count) { 124 | struct file *pfile = get_opened_file(fd); 125 | 126 | if (pfile->writable == 0) panic("do_write: cannot write file!\n"); 127 | 128 | int len = vfs_write(pfile, buf, count); 129 | return len; 130 | } 131 | 132 | // 133 | // reposition the file offset 134 | // 135 | int do_lseek(int fd, int offset, int whence) { 136 | struct file *pfile = get_opened_file(fd); 137 | return vfs_lseek(pfile, offset, whence); 138 | } 139 | 140 | // 141 | // read the vinode information 142 | // 143 | int do_stat(int fd, struct istat *istat) { 144 | struct file *pfile = get_opened_file(fd); 145 | return vfs_stat(pfile, istat); 146 | } 147 | 148 | // 149 | // read the inode information on the disk 150 | // 151 | int do_disk_stat(int fd, struct istat *istat) { 152 | struct file *pfile = get_opened_file(fd); 153 | return vfs_disk_stat(pfile, istat); 154 | } 155 | 156 | // 157 | // close a file 158 | // 159 | int do_close(int fd) { 160 | struct file *pfile = get_opened_file(fd); 161 | return vfs_close(pfile); 162 | } 163 | 164 | // 165 | // open a directory 166 | // return: the fd of the directory file 167 | // 168 | int do_opendir(char *pathname) { 169 | struct file *opened_file = NULL; 170 | if ((opened_file = vfs_opendir(pathname)) == NULL) return -1; 171 | 172 | int fd = 0; 173 | struct file *pfile; 174 | for (fd = 0; fd < MAX_FILES; ++fd) { 175 | pfile = &(current->pfiles->opened_files[fd]); 176 | if (pfile->status == FD_NONE) break; 177 | } 178 | if (pfile->status != FD_NONE) // no free entry 179 | panic("do_opendir: no file entry for current process!\n"); 180 | 181 | // initialize this file structure 182 | memcpy(pfile, opened_file, sizeof(struct file)); 183 | 184 | ++current->pfiles->nfiles; 185 | return fd; 186 | } 187 | 188 | // 189 | // read a directory entry 190 | // 191 | int do_readdir(int fd, struct dir *dir) { 192 | struct file *pfile = get_opened_file(fd); 193 | return vfs_readdir(pfile, dir); 194 | } 195 | 196 | // 197 | // make a new directory 198 | // 199 | int do_mkdir(char *pathname) { 200 | return vfs_mkdir(pathname); 201 | } 202 | 203 | // 204 | // close a directory 205 | // 206 | int do_closedir(int fd) { 207 | struct file *pfile = get_opened_file(fd); 208 | return vfs_closedir(pfile); 209 | } 210 | 211 | // 212 | // create hard link to a file 213 | // 214 | int do_link(char *oldpath, char *newpath) { 215 | return vfs_link(oldpath, newpath); 216 | } 217 | 218 | // 219 | // remove a hard link to a file 220 | // 221 | int do_unlink(char *path) { 222 | return vfs_unlink(path); 223 | } 224 | -------------------------------------------------------------------------------- /kernel/proc_file.h: -------------------------------------------------------------------------------- 1 | #ifndef _PROC_FILE_H_ 2 | #define _PROC_FILE_H_ 3 | 4 | #include "spike_interface/spike_file.h" 5 | #include "util/types.h" 6 | #include "vfs.h" 7 | 8 | // 9 | // file operations 10 | // 11 | int do_open(char *pathname, int flags); 12 | int do_read(int fd, char *buf, uint64 count); 13 | int do_write(int fd, char *buf, uint64 count); 14 | int do_lseek(int fd, int offset, int whence); 15 | int do_stat(int fd, struct istat *istat); 16 | int do_disk_stat(int fd, struct istat *istat); 17 | int do_close(int fd); 18 | 19 | int do_opendir(char *pathname); 20 | int do_readdir(int fd, struct dir *dir); 21 | int do_mkdir(char *pathname); 22 | int do_closedir(int fd); 23 | 24 | int do_link(char *oldpath, char *newpath); 25 | int do_unlink(char *path); 26 | 27 | void fs_init(void); 28 | 29 | // data structure that manages all openned files in a PCB 30 | typedef struct proc_file_management_t { 31 | struct dentry *cwd; // vfs dentry of current working directory 32 | struct file opened_files[MAX_FILES]; // opened files array 33 | int nfiles; // the number of files opened by a process 34 | } proc_file_management; 35 | 36 | proc_file_management *init_proc_file_management(void); 37 | 38 | void reclaim_proc_file_management(proc_file_management *pfiles); 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /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 | #include "vmm.h" 16 | #include "pmm.h" 17 | #include "memlayout.h" 18 | #include "sched.h" 19 | #include "spike_interface/spike_utils.h" 20 | 21 | //Two functions defined in kernel/usertrap.S 22 | extern char smode_trap_vector[]; 23 | extern void return_to_user(trapframe *, uint64 satp); 24 | 25 | // trap_sec_start points to the beginning of S-mode trap segment (i.e., the entry point 26 | // of S-mode trap vector). 27 | extern char trap_sec_start[]; 28 | 29 | // process pool. added @lab3_1 30 | process procs[NPROC]; 31 | 32 | // current points to the currently running user-mode application. 33 | process* current = NULL; 34 | 35 | // 36 | // switch to a user-mode process 37 | // 38 | void switch_to(process* proc) { 39 | assert(proc); 40 | current = proc; 41 | 42 | // write the smode_trap_vector (64-bit func. address) defined in kernel/strap_vector.S 43 | // to the stvec privilege register, such that trap handler pointed by smode_trap_vector 44 | // will be triggered when an interrupt occurs in S mode. 45 | write_csr(stvec, (uint64)smode_trap_vector); 46 | 47 | // set up trapframe values (in process structure) that smode_trap_vector will need when 48 | // the process next re-enters the kernel. 49 | proc->trapframe->kernel_sp = proc->kstack; // process's kernel stack 50 | proc->trapframe->kernel_satp = read_csr(satp); // kernel page table 51 | proc->trapframe->kernel_trap = (uint64)smode_trap_handler; 52 | 53 | // SSTATUS_SPP and SSTATUS_SPIE are defined in kernel/riscv.h 54 | // set S Previous Privilege mode (the SSTATUS_SPP bit in sstatus register) to User mode. 55 | unsigned long x = read_csr(sstatus); 56 | x &= ~SSTATUS_SPP; // clear SPP to 0 for user mode 57 | x |= SSTATUS_SPIE; // enable interrupts in user mode 58 | 59 | // write x back to 'sstatus' register to enable interrupts, and sret destination mode. 60 | write_csr(sstatus, x); 61 | 62 | // set S Exception Program Counter (sepc register) to the elf entry pc. 63 | write_csr(sepc, proc->trapframe->epc); 64 | 65 | // make user page table. macro MAKE_SATP is defined in kernel/riscv.h. added @lab2_1 66 | uint64 user_satp = MAKE_SATP(proc->pagetable); 67 | 68 | // return_to_user() is defined in kernel/strap_vector.S. switch to user mode with sret. 69 | // note, return_to_user takes two parameters @ and after lab2_1. 70 | return_to_user(proc->trapframe, user_satp); 71 | } 72 | 73 | // 74 | // initialize process pool (the procs[] array). added @lab3_1 75 | // 76 | void init_proc_pool() { 77 | memset( procs, 0, sizeof(process)*NPROC ); 78 | 79 | for (int i = 0; i < NPROC; ++i) { 80 | procs[i].status = FREE; 81 | procs[i].pid = i; 82 | } 83 | } 84 | 85 | // 86 | // allocate an empty process, init its vm space. returns the pointer to 87 | // process strcuture. added @lab3_1 88 | // 89 | process* alloc_process() { 90 | // locate the first usable process structure 91 | int i; 92 | 93 | for( i=0; i=NPROC ){ 97 | panic( "cannot find any free process structure.\n" ); 98 | return 0; 99 | } 100 | 101 | // init proc[i]'s vm space 102 | procs[i].trapframe = (trapframe *)alloc_page(); //trapframe, used to save context 103 | memset(procs[i].trapframe, 0, sizeof(trapframe)); 104 | 105 | // page directory 106 | procs[i].pagetable = (pagetable_t)alloc_page(); 107 | memset((void *)procs[i].pagetable, 0, PGSIZE); 108 | 109 | procs[i].kstack = (uint64)alloc_page() + PGSIZE; //user kernel stack top 110 | uint64 user_stack = (uint64)alloc_page(); //phisical address of user stack bottom 111 | procs[i].trapframe->regs.sp = USER_STACK_TOP; //virtual address of user stack top 112 | 113 | // allocates a page to record memory regions (segments) 114 | procs[i].mapped_info = (mapped_region*)alloc_page(); 115 | memset( procs[i].mapped_info, 0, PGSIZE ); 116 | 117 | // map user stack in userspace 118 | user_vm_map((pagetable_t)procs[i].pagetable, USER_STACK_TOP - PGSIZE, PGSIZE, 119 | user_stack, prot_to_type(PROT_WRITE | PROT_READ, 1)); 120 | procs[i].mapped_info[STACK_SEGMENT].va = USER_STACK_TOP - PGSIZE; 121 | procs[i].mapped_info[STACK_SEGMENT].npages = 1; 122 | procs[i].mapped_info[STACK_SEGMENT].seg_type = STACK_SEGMENT; 123 | 124 | // map trapframe in user space (direct mapping as in kernel space). 125 | user_vm_map((pagetable_t)procs[i].pagetable, (uint64)procs[i].trapframe, PGSIZE, 126 | (uint64)procs[i].trapframe, prot_to_type(PROT_WRITE | PROT_READ, 0)); 127 | procs[i].mapped_info[CONTEXT_SEGMENT].va = (uint64)procs[i].trapframe; 128 | procs[i].mapped_info[CONTEXT_SEGMENT].npages = 1; 129 | procs[i].mapped_info[CONTEXT_SEGMENT].seg_type = CONTEXT_SEGMENT; 130 | 131 | // map S-mode trap vector section in user space (direct mapping as in kernel space) 132 | // we assume that the size of usertrap.S is smaller than a page. 133 | user_vm_map((pagetable_t)procs[i].pagetable, (uint64)trap_sec_start, PGSIZE, 134 | (uint64)trap_sec_start, prot_to_type(PROT_READ | PROT_EXEC, 0)); 135 | procs[i].mapped_info[SYSTEM_SEGMENT].va = (uint64)trap_sec_start; 136 | procs[i].mapped_info[SYSTEM_SEGMENT].npages = 1; 137 | procs[i].mapped_info[SYSTEM_SEGMENT].seg_type = SYSTEM_SEGMENT; 138 | 139 | sprint("in alloc_proc. user frame 0x%lx, user stack 0x%lx, user kstack 0x%lx \n", 140 | procs[i].trapframe, procs[i].trapframe->regs.sp, procs[i].kstack); 141 | 142 | // initialize the process's heap manager 143 | procs[i].user_heap.heap_top = USER_FREE_ADDRESS_START; 144 | procs[i].user_heap.heap_bottom = USER_FREE_ADDRESS_START; 145 | procs[i].user_heap.free_pages_count = 0; 146 | 147 | // map user heap in userspace 148 | procs[i].mapped_info[HEAP_SEGMENT].va = USER_FREE_ADDRESS_START; 149 | procs[i].mapped_info[HEAP_SEGMENT].npages = 0; // no pages are mapped to heap yet. 150 | procs[i].mapped_info[HEAP_SEGMENT].seg_type = HEAP_SEGMENT; 151 | 152 | procs[i].total_mapped_region = 4; 153 | 154 | // initialize files_struct 155 | procs[i].pfiles = init_proc_file_management(); 156 | sprint("in alloc_proc. build proc_file_management successfully.\n"); 157 | 158 | // return after initialization. 159 | return &procs[i]; 160 | } 161 | 162 | // 163 | // reclaim a process. added @lab3_1 164 | // 165 | int free_process( process* proc ) { 166 | // we set the status to ZOMBIE, but cannot destruct its vm space immediately. 167 | // since proc can be current process, and its user kernel stack is currently in use! 168 | // but for proxy kernel, it (memory leaking) may NOT be a really serious issue, 169 | // as it is different from regular OS, which needs to run 7x24. 170 | proc->status = ZOMBIE; 171 | 172 | return 0; 173 | } 174 | 175 | // 176 | // implements fork syscal in kernel. added @lab3_1 177 | // basic idea here is to first allocate an empty process (child), then duplicate the 178 | // context and data segments of parent process to the child, and lastly, map other 179 | // segments (code, system) of the parent to child. the stack segment remains unchanged 180 | // for the child. 181 | // 182 | int do_fork( process* parent) 183 | { 184 | sprint( "will fork a child from parent %d.\n", parent->pid ); 185 | process* child = alloc_process(); 186 | 187 | for( int i=0; itotal_mapped_region; i++ ){ 188 | // browse parent's vm space, and copy its trapframe and data segments, 189 | // map its code segment. 190 | switch( parent->mapped_info[i].seg_type ){ 191 | case CONTEXT_SEGMENT: 192 | *child->trapframe = *parent->trapframe; 193 | break; 194 | case STACK_SEGMENT: 195 | memcpy( (void*)lookup_pa(child->pagetable, child->mapped_info[STACK_SEGMENT].va), 196 | (void*)lookup_pa(parent->pagetable, parent->mapped_info[i].va), PGSIZE ); 197 | break; 198 | case HEAP_SEGMENT: 199 | // build a same heap for child process. 200 | 201 | // convert free_pages_address into a filter to skip reclaimed blocks in the heap 202 | // when mapping the heap blocks 203 | { int free_block_filter[MAX_HEAP_PAGES]; 204 | memset(free_block_filter, 0, MAX_HEAP_PAGES); 205 | uint64 heap_bottom = parent->user_heap.heap_bottom; 206 | for (int i = 0; i < parent->user_heap.free_pages_count; i++) { 207 | int index = (parent->user_heap.free_pages_address[i] - heap_bottom) / PGSIZE; 208 | free_block_filter[index] = 1; 209 | } 210 | 211 | // copy and map the heap blocks 212 | for (uint64 heap_block = current->user_heap.heap_bottom; 213 | heap_block < current->user_heap.heap_top; heap_block += PGSIZE) { 214 | if (free_block_filter[(heap_block - heap_bottom) / PGSIZE]) // skip free blocks 215 | continue; 216 | 217 | void* child_pa = alloc_page(); 218 | memcpy(child_pa, (void*)lookup_pa(parent->pagetable, heap_block), PGSIZE); 219 | user_vm_map((pagetable_t)child->pagetable, heap_block, PGSIZE, (uint64)child_pa, 220 | prot_to_type(PROT_WRITE | PROT_READ, 1)); 221 | } 222 | 223 | child->mapped_info[HEAP_SEGMENT].npages = parent->mapped_info[HEAP_SEGMENT].npages; 224 | 225 | // copy the heap manager from parent to child 226 | memcpy((void*)&child->user_heap, (void*)&parent->user_heap, sizeof(parent->user_heap)); 227 | break; 228 | } 229 | case CODE_SEGMENT: 230 | // TODO (lab3_1): implment the mapping of child code segment to parent's 231 | // code segment. 232 | // hint: the virtual address mapping of code segment is tracked in mapped_info 233 | // page of parent's process structure. use the information in mapped_info to 234 | // retrieve the virtual to physical mapping of code segment. 235 | // after having the mapping information, just map the corresponding virtual 236 | // address region of child to the physical pages that actually store the code 237 | // segment of parent process. 238 | // DO NOT COPY THE PHYSICAL PAGES, JUST MAP THEM. 239 | //panic( "You need to implement the code segment mapping of child in lab3_1.\n" ); 240 | child->mapped_info[CODE_SEGMENT].va = parent->mapped_info[CODE_SEGMENT].va; 241 | user_vm_map((pagetable_t)child->pagetable, child->mapped_info[CODE_SEGMENT].va, PGSIZE, 242 | lookup_pa(parent->pagetable, parent->mapped_info[CODE_SEGMENT].va),prot_to_type(PROT_EXEC | PROT_READ, 1)); 243 | // after mapping, register the vm region (do not delete codes below!) 244 | child->mapped_info[child->total_mapped_region].va = parent->mapped_info[i].va; 245 | child->mapped_info[child->total_mapped_region].npages = 246 | parent->mapped_info[i].npages; 247 | child->mapped_info[child->total_mapped_region].seg_type = CODE_SEGMENT; 248 | child->total_mapped_region++; 249 | sprint("do_fork map code segment at pa:%lx of parent to child at va:%lx.\n", lookup_pa(parent->pagetable, parent->mapped_info[CODE_SEGMENT].va), child->mapped_info[CODE_SEGMENT].va); 250 | break; 251 | } 252 | } 253 | 254 | child->status = READY; 255 | child->trapframe->regs.a0 = 0; 256 | child->parent = parent; 257 | insert_to_ready_queue( child ); 258 | 259 | return child->pid; 260 | } 261 | 262 | void empty_process(process* proc){ 263 | // init proc[i]'s vm space 264 | proc->trapframe = (trapframe *)alloc_page(); //trapframe, used to save context 265 | memset(proc->trapframe, 0, sizeof(trapframe)); 266 | 267 | // page directory 268 | proc->pagetable = (pagetable_t)alloc_page(); 269 | memset((void *)proc->pagetable, 0, PGSIZE); 270 | 271 | proc->kstack = (uint64)alloc_page() + PGSIZE; //user kernel stack top 272 | uint64 user_stack = (uint64)alloc_page(); //phisical address of user stack bottom 273 | proc->trapframe->regs.sp = USER_STACK_TOP; //virtual address of user stack top 274 | 275 | // allocates a page to record memory regions (segments) 276 | proc->mapped_info = (mapped_region*)alloc_page(); 277 | memset( proc->mapped_info, 0, PGSIZE ); 278 | 279 | // map user stack in userspace 280 | user_vm_map((pagetable_t)proc->pagetable, USER_STACK_TOP - PGSIZE, PGSIZE, 281 | user_stack, prot_to_type(PROT_WRITE | PROT_READ, 1)); 282 | proc->mapped_info[STACK_SEGMENT].va = USER_STACK_TOP - PGSIZE; 283 | proc->mapped_info[STACK_SEGMENT].npages = 1; 284 | proc->mapped_info[STACK_SEGMENT].seg_type = STACK_SEGMENT; 285 | 286 | // map trapframe in user space (direct mapping as in kernel space). 287 | user_vm_map((pagetable_t)proc->pagetable, (uint64)proc->trapframe, PGSIZE, 288 | (uint64)proc->trapframe, prot_to_type(PROT_WRITE | PROT_READ, 0)); 289 | proc->mapped_info[CONTEXT_SEGMENT].va = (uint64)proc->trapframe; 290 | proc->mapped_info[CONTEXT_SEGMENT].npages = 1; 291 | proc->mapped_info[CONTEXT_SEGMENT].seg_type = CONTEXT_SEGMENT; 292 | 293 | // map S-mode trap vector section in user space (direct mapping as in kernel space) 294 | // we assume that the size of usertrap.S is smaller than a page. 295 | user_vm_map((pagetable_t)proc->pagetable, (uint64)trap_sec_start, PGSIZE, 296 | (uint64)trap_sec_start, prot_to_type(PROT_READ | PROT_EXEC, 0)); 297 | proc->mapped_info[SYSTEM_SEGMENT].va = (uint64)trap_sec_start; 298 | proc->mapped_info[SYSTEM_SEGMENT].npages = 1; 299 | proc->mapped_info[SYSTEM_SEGMENT].seg_type = SYSTEM_SEGMENT; 300 | 301 | // initialize the process's heap manager 302 | proc->user_heap.heap_top = USER_FREE_ADDRESS_START; 303 | proc->user_heap.heap_bottom = USER_FREE_ADDRESS_START; 304 | proc->user_heap.free_pages_count = 0; 305 | 306 | // map user heap in userspace 307 | proc->mapped_info[HEAP_SEGMENT].va = USER_FREE_ADDRESS_START; 308 | proc->mapped_info[HEAP_SEGMENT].npages = 0; // no pages are mapped to heap yet. 309 | proc->mapped_info[HEAP_SEGMENT].seg_type = HEAP_SEGMENT; 310 | 311 | proc->total_mapped_region = 4; 312 | 313 | // initialize files_struct 314 | proc->pfiles = (proc_file_management *)alloc_page(); 315 | proc->pfiles->cwd = vfs_root_dentry; // by default, cwd is the root 316 | proc->pfiles->nfiles = 0; 317 | 318 | for (int fd = 0; fd < MAX_FILES; ++fd) 319 | proc->pfiles->opened_files[fd].status = FD_NONE; 320 | } 321 | 322 | void do_exec(char *fn, char *para) { 323 | empty_process(current); 324 | load_bincode_from_host_elf(current, fn); 325 | 326 | uint64 sp = (uint64)current->trapframe->regs.sp; // get the virtual address of stack 327 | sp -= strlen(para) + 1; 328 | sp -= sp % 8; // align sizeof(uint64) = 8 329 | memcpy((void *)user_va_to_pa(current->pagetable, (void *)sp), (void *)para, strlen(para) + 1); 330 | sp -= 8; 331 | *((uint64 *)user_va_to_pa(current->pagetable, (void *)sp)) = sp + 8; 332 | // sprint("%x\n", sp); 333 | // sprint("%x\n", *((uint64 *)user_va_to_pa(current->pagetable, (void *)sp))); 334 | // sprint("%x\n", user_va_to_pa(current->pagetable, (void *)*((uint64 *)user_va_to_pa(current->pagetable, (void *)sp)))); 335 | // sprint("%s\n", user_va_to_pa(current->pagetable, (void *)*((uint64 *)user_va_to_pa(current->pagetable, (void *)sp)))); 336 | current->trapframe->regs.sp = sp; 337 | current->trapframe->regs.a0 = (uint64)1; 338 | current->trapframe->regs.a1 = sp; 339 | 340 | switch_to(current); 341 | } -------------------------------------------------------------------------------- /kernel/process.h: -------------------------------------------------------------------------------- 1 | #ifndef _PROC_H_ 2 | #define _PROC_H_ 3 | 4 | #include "riscv.h" 5 | #include "proc_file.h" 6 | 7 | typedef struct trapframe_t { 8 | // space to store context (all common registers) 9 | /* offset:0 */ riscv_regs regs; 10 | 11 | // process's "user kernel" stack 12 | /* offset:248 */ uint64 kernel_sp; 13 | // pointer to smode_trap_handler 14 | /* offset:256 */ uint64 kernel_trap; 15 | // saved user process counter 16 | /* offset:264 */ uint64 epc; 17 | 18 | // kernel page table. added @lab2_1 19 | /* offset:272 */ uint64 kernel_satp; 20 | }trapframe; 21 | 22 | // riscv-pke kernel supports at most 32 processes 23 | #define NPROC 32 24 | // maximum number of pages in a process's heap 25 | #define MAX_HEAP_PAGES 32 26 | 27 | // possible status of a process 28 | enum proc_status { 29 | FREE, // unused state 30 | READY, // ready state 31 | RUNNING, // currently running 32 | BLOCKED, // waiting for something 33 | ZOMBIE, // terminated but not reclaimed yet 34 | }; 35 | 36 | // types of a segment 37 | enum segment_type { 38 | STACK_SEGMENT = 0, // runtime stack segment 39 | CONTEXT_SEGMENT, // trapframe segment 40 | SYSTEM_SEGMENT, // system segment 41 | HEAP_SEGMENT, // runtime heap segment 42 | CODE_SEGMENT, // ELF segment 43 | DATA_SEGMENT, // ELF segment 44 | }; 45 | 46 | // the VM regions mapped to a user process 47 | typedef struct mapped_region { 48 | uint64 va; // mapped virtual address 49 | uint32 npages; // mapping_info is unused if npages == 0 50 | uint32 seg_type; // segment type, one of the segment_types 51 | } mapped_region; 52 | 53 | typedef struct process_heap_manager { 54 | // points to the last free page in our simple heap. 55 | uint64 heap_top; 56 | // points to the bottom of our simple heap. 57 | uint64 heap_bottom; 58 | 59 | // the address of free pages in the heap 60 | uint64 free_pages_address[MAX_HEAP_PAGES]; 61 | // the number of free pages in the heap 62 | uint32 free_pages_count; 63 | }process_heap_manager; 64 | 65 | // the extremely simple definition of process, used for begining labs of PKE 66 | typedef struct process_t { 67 | // pointing to the stack used in trap handling. 68 | uint64 kstack; 69 | // user page table 70 | pagetable_t pagetable; 71 | // trapframe storing the context of a (User mode) process. 72 | trapframe* trapframe; 73 | 74 | // points to a page that contains mapped_regions. below are added @lab3_1 75 | mapped_region *mapped_info; 76 | // next free mapped region in mapped_info 77 | int total_mapped_region; 78 | 79 | // heap management 80 | process_heap_manager user_heap; 81 | 82 | // process id 83 | uint64 pid; 84 | // process status 85 | int status; 86 | // parent process 87 | struct process_t *parent; 88 | // next queue element 89 | struct process_t *queue_next; 90 | 91 | // accounting. added @lab3_3 92 | int tick_count; 93 | 94 | // file system. added @lab4_1 95 | proc_file_management *pfiles; 96 | }process; 97 | 98 | // switch to run user app 99 | void switch_to(process*); 100 | 101 | // initialize process pool (the procs[] array) 102 | void init_proc_pool(); 103 | // allocate an empty process, init its vm space. returns its pid 104 | process* alloc_process(); 105 | // reclaim a process, destruct its vm space and free physical pages. 106 | int free_process( process* proc ); 107 | // fork a child from parent 108 | int do_fork(process* parent); 109 | 110 | void do_exec(char *fn, char *para); 111 | // current running process 112 | extern process* current; 113 | 114 | #endif 115 | -------------------------------------------------------------------------------- /kernel/ramdev.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Utility functions operating the devices. support only RAM disk device. added @lab4_1. 3 | */ 4 | 5 | #include "ramdev.h" 6 | #include "vfs.h" 7 | #include "pmm.h" 8 | #include "riscv.h" 9 | #include "util/types.h" 10 | #include "util/string.h" 11 | #include "spike_interface/spike_utils.h" 12 | #include "rfs.h" 13 | 14 | struct rfs_device *rfs_device_list[MAX_RAMDISK_COUNT]; 15 | 16 | // 17 | // write the content stored in "buff" to the "blkno"^th block of disk. 18 | // 19 | int ramdisk_write(struct rfs_device *rfs_device, int blkno){ 20 | if ( blkno < 0 || blkno >= RAMDISK_BLOCK_COUNT ) 21 | panic("ramdisk_write: write block No %d out of range!\n", blkno); 22 | void * dst = (void *)((uint64)rfs_device->d_address + blkno * RAMDISK_BLOCK_SIZE); 23 | memcpy(dst, rfs_device->iobuffer, RAMDISK_BLOCK_SIZE); 24 | return 0; 25 | } 26 | 27 | // 28 | // read the "blkno"^th block from the RAM disk and store its content into buffer. 29 | // 30 | int ramdisk_read(struct rfs_device *rfs_device, int blkno){ 31 | if ( blkno < 0 || blkno >= RAMDISK_BLOCK_COUNT ) 32 | panic("ramdisk_read: read block No out of range!\n"); 33 | void * src = (void *)((uint64)rfs_device->d_address + blkno * RAMDISK_BLOCK_SIZE); 34 | memcpy(rfs_device->iobuffer, src, RAMDISK_BLOCK_SIZE); 35 | return 0; 36 | } 37 | 38 | // 39 | // alloc RAMDISK_BLOCK_COUNT continuous pages (blocks) for the RAM Disk 40 | // setup an vfs node, initialize RAM disk device, and attach the device with the vfs node. 41 | // 42 | struct device *init_rfs_device(const char *dev_name) { 43 | // find rfs in registered fs list 44 | struct file_system_type *fs_type = NULL; 45 | for (int i = 0; i < MAX_SUPPORTED_FS; i++) { 46 | if (fs_list[i] != NULL && fs_list[i]->type_num == RFS_TYPE) { 47 | fs_type = fs_list[i]; 48 | break; 49 | } 50 | } 51 | if (!fs_type) { 52 | panic("No RFS file system found!\n"); 53 | } 54 | 55 | // alloc blocks for the RAM Disk 56 | void *curr_addr = NULL; 57 | void *last_addr = NULL; 58 | void *ramdisk_addr = NULL; 59 | for ( int i = 0; i < RAMDISK_BLOCK_COUNT; ++ i ){ 60 | last_addr = curr_addr; 61 | curr_addr = alloc_page(); 62 | if ( last_addr != NULL && last_addr - curr_addr != PGSIZE ){ 63 | panic("RAM Disk0: address is discontinuous!\n"); 64 | } 65 | } 66 | ramdisk_addr = curr_addr; 67 | 68 | // find a free rfs device 69 | struct rfs_device **rfs_device = NULL; 70 | int device_id = 0; 71 | for (int i = 0; i < MAX_RAMDISK_COUNT; i++) { 72 | if (rfs_device_list[i] == NULL) { 73 | rfs_device = &rfs_device_list[i]; 74 | device_id = i; 75 | break; 76 | } 77 | } 78 | if (!rfs_device) { 79 | panic("RAM Disk0: no free device!\n"); 80 | } 81 | 82 | *rfs_device = (struct rfs_device *)alloc_page(); 83 | (*rfs_device)->d_blocks = RAMDISK_BLOCK_COUNT; 84 | (*rfs_device)->d_blocksize = RAMDISK_BLOCK_SIZE; 85 | (*rfs_device)->d_write = ramdisk_write; 86 | (*rfs_device)->d_read = ramdisk_read; 87 | (*rfs_device)->d_address = ramdisk_addr; 88 | (*rfs_device)->iobuffer = alloc_page(); 89 | 90 | // allocate a vfs device 91 | struct device * device = (struct device *)alloc_page(); 92 | // set the device name and index 93 | strcpy(device->dev_name, dev_name); 94 | device->dev_id = device_id; 95 | device->fs_type = fs_type; 96 | 97 | // add the device to the vfs device list 98 | for(int i = 0; i < MAX_VFS_DEV; i++) { 99 | if (vfs_dev_list[i] == NULL) { 100 | vfs_dev_list[i] = device; 101 | break; 102 | } 103 | } 104 | 105 | sprint("%s: base address of %s is: %p\n",dev_name, dev_name, ramdisk_addr); 106 | return device; 107 | } -------------------------------------------------------------------------------- /kernel/ramdev.h: -------------------------------------------------------------------------------- 1 | #ifndef _RAMDEV_H_ 2 | #define _RAMDEV_H_ 3 | 4 | #include "riscv.h" 5 | #include "util/types.h" 6 | 7 | #define RAMDISK_BLOCK_COUNT 128 8 | #define RAMDISK_BLOCK_SIZE PGSIZE 9 | 10 | #define MAX_RAMDISK_COUNT 10 11 | 12 | #define RAMDISK_FREE 0 13 | #define RAMDISK_USED 1 14 | 15 | struct rfs_device { 16 | void *d_address; // the ramdisk base address 17 | int d_blocks; // the number of blocks of the device 18 | int d_blocksize; // the blocksize (bytes) per block 19 | void *iobuffer; // iobuffer for write/read 20 | int (*d_write)(struct rfs_device *rdev, int blkno); // device write funtion 21 | int (*d_read)(struct rfs_device *rdev, int blkno); // device read funtion 22 | }; 23 | 24 | #define dop_write(rdev, blkno) ((rdev)->d_write(rdev, blkno)) 25 | #define dop_read(rdev, blkno) ((rdev)->d_read(rdev, blkno)) 26 | 27 | struct device *init_rfs_device(const char *dev_name); 28 | struct rfs_device *alloc_rfs_device(void); 29 | 30 | extern struct rfs_device *rfs_device_list[MAX_RAMDISK_COUNT]; 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /kernel/rfs.h: -------------------------------------------------------------------------------- 1 | #ifndef _RFS_H_ 2 | #define _RFS_H_ 3 | 4 | #include "ramdev.h" 5 | #include "riscv.h" 6 | #include "util/types.h" 7 | #include "vfs.h" 8 | 9 | #define RFS_TYPE 0 10 | #define RFS_MAGIC 0xBEAF 11 | #define RFS_BLKSIZE PGSIZE 12 | #define RFS_INODESIZE 128 // block size must be divisible by this value 13 | #define RFS_MAX_INODE_BLKNUM 10 14 | #define RFS_MAX_FILE_NAME_LEN 28 15 | #define RFS_DIRECT_BLKNUM DIRECT_BLKNUM 16 | 17 | // rfs block offset 18 | #define RFS_BLK_OFFSET_SUPER 0 19 | #define RFS_BLK_OFFSET_INODE 1 20 | #define RFS_BLK_OFFSET_BITMAP 11 21 | #define RFS_BLK_OFFSET_FREE 12 22 | 23 | // dinode type 24 | #define R_FILE FILE_I 25 | #define R_DIR DIR_I 26 | #define R_FREE 2 27 | 28 | // file system super block 29 | struct rfs_superblock { 30 | int magic; // magic number of the 31 | int size; // size of file system image (blocks) 32 | int nblocks; // number of data blocks 33 | int ninodes; // number of inodes. 34 | }; 35 | 36 | // disk inode 37 | struct rfs_dinode { 38 | int size; // size of the file (in bytes) 39 | int type; // one of R_FREE, R_FILE, R_DIR 40 | int nlinks; // number of hard links to this file 41 | int blocks; // number of blocks 42 | int addrs[RFS_DIRECT_BLKNUM]; // direct blocks 43 | }; 44 | 45 | // directory entry 46 | struct rfs_direntry { 47 | int inum; // inode number 48 | char name[RFS_MAX_FILE_NAME_LEN]; // file name 49 | }; 50 | 51 | // directory memory cache (used by opendir/readdir/closedir) 52 | struct rfs_dir_cache { 53 | int block_count; 54 | struct rfs_direntry *dir_base_addr; 55 | }; 56 | 57 | // rfs utility functin declarations 58 | int register_rfs(); 59 | int rfs_format_dev(struct device *dev); 60 | 61 | int rfs_r1block(struct rfs_device *rfs_dev, int n_block); 62 | int rfs_w1block(struct rfs_device *rfs_dev, int n_block); 63 | struct rfs_dinode *rfs_read_dinode(struct rfs_device *rdev, int n_inode); 64 | int rfs_write_dinode(struct rfs_device *rdev, const struct rfs_dinode *dinode, 65 | int n_inode); 66 | int rfs_alloc_block(struct super_block *sb); 67 | int rfs_free_block(struct super_block *sb, int block_num); 68 | int rfs_add_direntry(struct vinode *dir, const char *name, int inum); 69 | 70 | struct vinode *rfs_alloc_vinode(struct super_block *sb); 71 | int rfs_write_back_vinode(struct vinode *vinode); 72 | int rfs_update_vinode(struct vinode *vinode); 73 | 74 | // rfs interface function declarations 75 | ssize_t rfs_read(struct vinode *f_inode, char *r_buf, ssize_t len, int *offset); 76 | ssize_t rfs_write(struct vinode *f_inode, const char *w_buf, ssize_t len, 77 | int *offset); 78 | struct vinode *rfs_lookup(struct vinode *parent, struct dentry *sub_dentry); 79 | struct vinode *rfs_create(struct vinode *parent, struct dentry *sub_dentry); 80 | int rfs_lseek(struct vinode *f_inode, ssize_t new_offset, int whence, int *offset); 81 | int rfs_disk_stat(struct vinode *vinode, struct istat *istat); 82 | int rfs_link(struct vinode *parent, struct dentry *sub_dentry, struct vinode *link_node); 83 | int rfs_unlink(struct vinode *parent, struct dentry *sub_dentry, struct vinode *unlink_vinode); 84 | 85 | int rfs_hook_opendir(struct vinode *dir_vinode, struct dentry *dentry); 86 | int rfs_hook_closedir(struct vinode *dir_vinode, struct dentry *dentry); 87 | int rfs_readdir(struct vinode *dir_vinode, struct dir *dir, int *offset); 88 | struct vinode *rfs_mkdir(struct vinode *parent, struct dentry *sub_dentry); 89 | 90 | struct super_block *rfs_get_superblock(struct device *dev); 91 | 92 | extern const struct vinode_ops rfs_i_ops; 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /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 | // irqs (interrupts). added @lab1_3 56 | #define CAUSE_MTIMER 0x8000000000000007 57 | #define CAUSE_MTIMER_S_TRAP 0x8000000000000001 58 | 59 | //Supervisor interrupt-pending register 60 | #define SIP_SSIP (1L << 1) 61 | 62 | // core local interruptor (CLINT), which contains the timer. 63 | #define CLINT 0x2000000L 64 | #define CLINT_MTIMECMP(hartid) (CLINT + 0x4000 + 8 * (hartid)) 65 | #define CLINT_MTIME (CLINT + 0xBFF8) // cycles since boot. 66 | 67 | // fields of sstatus, the Supervisor mode Status register 68 | #define SSTATUS_SPP (1L << 8) // Previous mode, 1=Supervisor, 0=User 69 | #define SSTATUS_SPIE (1L << 5) // Supervisor Previous Interrupt Enable 70 | #define SSTATUS_UPIE (1L << 4) // User Previous Interrupt Enable 71 | #define SSTATUS_SIE (1L << 1) // Supervisor Interrupt Enable 72 | #define SSTATUS_UIE (1L << 0) // User Interrupt Enable 73 | #define SSTATUS_SUM 0x00040000 74 | #define SSTATUS_FS 0x00006000 75 | 76 | // Supervisor Interrupt Enable 77 | #define SIE_SEIE (1L << 9) // external 78 | #define SIE_STIE (1L << 5) // timer 79 | #define SIE_SSIE (1L << 1) // software 80 | 81 | // Machine-mode Interrupt Enable 82 | #define MIE_MEIE (1L << 11) // external 83 | #define MIE_MTIE (1L << 7) // timer 84 | #define MIE_MSIE (1L << 3) // software 85 | 86 | #define read_const_csr(reg) \ 87 | ({ \ 88 | unsigned long __tmp; \ 89 | asm("csrr %0, " #reg : "=r"(__tmp)); \ 90 | __tmp; \ 91 | }) 92 | 93 | static inline int supports_extension(char ext) { 94 | return read_const_csr(misa) & (1 << (ext - 'A')); 95 | } 96 | 97 | #define read_csr(reg) \ 98 | ({ \ 99 | unsigned long __tmp; \ 100 | asm volatile("csrr %0, " #reg : "=r"(__tmp)); \ 101 | __tmp; \ 102 | }) 103 | 104 | #define write_csr(reg, val) ({ asm volatile("csrw " #reg ", %0" ::"rK"(val)); }) 105 | 106 | #define swap_csr(reg, val) \ 107 | ({ \ 108 | unsigned long __tmp; \ 109 | asm volatile("csrrw %0, " #reg ", %1" : "=r"(__tmp) : "rK"(val)); \ 110 | __tmp; \ 111 | }) 112 | 113 | #define set_csr(reg, bit) \ 114 | ({ \ 115 | unsigned long __tmp; \ 116 | asm volatile("csrrs %0, " #reg ", %1" : "=r"(__tmp) : "rK"(bit)); \ 117 | __tmp; \ 118 | }) 119 | 120 | // enable device interrupts 121 | static inline void intr_on(void) { write_csr(sstatus, read_csr(sstatus) | SSTATUS_SIE); } 122 | 123 | // disable device interrupts 124 | static inline void intr_off(void) { write_csr(sstatus, read_csr(sstatus) & ~SSTATUS_SIE); } 125 | 126 | // are device interrupts enabled? 127 | static inline int is_intr_enable(void) { 128 | // uint64 x = r_sstatus(); 129 | uint64 x = read_csr(sstatus); 130 | return (x & SSTATUS_SIE) != 0; 131 | } 132 | 133 | // read sp, the stack pointer 134 | static inline uint64 read_sp(void) { 135 | uint64 x; 136 | asm volatile("mv %0, sp" : "=r"(x)); 137 | return x; 138 | } 139 | 140 | // read tp, the thread pointer, holding hartid (core number), the index into cpus[]. 141 | static inline uint64 read_tp(void) { 142 | uint64 x; 143 | asm volatile("mv %0, tp" : "=r"(x)); 144 | return x; 145 | } 146 | 147 | // write tp, the thread pointer, holding hartid (core number), the index into cpus[]. 148 | static inline void write_tp(uint64 x) { asm volatile("mv tp, %0" : : "r"(x)); } 149 | 150 | typedef struct riscv_regs_t { 151 | /* 0 */ uint64 ra; 152 | /* 8 */ uint64 sp; 153 | /* 16 */ uint64 gp; 154 | /* 24 */ uint64 tp; 155 | /* 32 */ uint64 t0; 156 | /* 40 */ uint64 t1; 157 | /* 48 */ uint64 t2; 158 | /* 56 */ uint64 s0; 159 | /* 64 */ uint64 s1; 160 | /* 72 */ uint64 a0; 161 | /* 80 */ uint64 a1; 162 | /* 88 */ uint64 a2; 163 | /* 96 */ uint64 a3; 164 | /* 104 */ uint64 a4; 165 | /* 112 */ uint64 a5; 166 | /* 120 */ uint64 a6; 167 | /* 128 */ uint64 a7; 168 | /* 136 */ uint64 s2; 169 | /* 144 */ uint64 s3; 170 | /* 152 */ uint64 s4; 171 | /* 160 */ uint64 s5; 172 | /* 168 */ uint64 s6; 173 | /* 176 */ uint64 s7; 174 | /* 184 */ uint64 s8; 175 | /* 192 */ uint64 s9; 176 | /* 196 */ uint64 s10; 177 | /* 208 */ uint64 s11; 178 | /* 216 */ uint64 t3; 179 | /* 224 */ uint64 t4; 180 | /* 232 */ uint64 t5; 181 | /* 240 */ uint64 t6; 182 | }riscv_regs; 183 | 184 | // following lines are added @lab2_1 185 | static inline void flush_tlb(void) { asm volatile("sfence.vma zero, zero"); } 186 | #define PGSIZE 4096 // bytes per page 187 | #define PGSHIFT 12 // offset bits within a page 188 | 189 | // use riscv's sv39 page table scheme. 190 | #define SATP_SV39 (8L << 60) 191 | #define MAKE_SATP(pagetable) (SATP_SV39 | (((uint64)pagetable) >> 12)) 192 | 193 | #define PTE_V (1L << 0) // valid 194 | #define PTE_R (1L << 1) // readable 195 | #define PTE_W (1L << 2) // writable 196 | #define PTE_X (1L << 3) // executable 197 | #define PTE_U (1L << 4) // 1->user can access, 0->otherwise 198 | #define PTE_G (1L << 5) // global 199 | #define PTE_A (1L << 6) // accessed 200 | #define PTE_D (1L << 7) // dirty 201 | 202 | // shift a physical address to the right place for a PTE. 203 | #define PA2PTE(pa) ((((uint64)pa) >> 12) << 10) 204 | 205 | // convert a pte content into its corresponding physical address 206 | #define PTE2PA(pte) (((pte) >> 10) << 12) 207 | 208 | // extract the property bits of a pte 209 | #define PTE_FLAGS(pte) ((pte)&0x3FF) 210 | 211 | // extract the three 9-bit page table indices from a virtual address. 212 | #define PXMASK 0x1FF // 9 bits 213 | 214 | #define PXSHIFT(level) (PGSHIFT + (9 * (level))) 215 | #define PX(level, va) ((((uint64)(va)) >> PXSHIFT(level)) & PXMASK) 216 | 217 | // one beyond the highest possible virtual address. 218 | // MAXVA is actually one bit less than the max allowed by 219 | // Sv39, to avoid having to sign-extend virtual addresses 220 | // that have the high bit set. 221 | #define MAXVA (1L << (9 + 9 + 9 + 12 - 1)) 222 | 223 | typedef uint64 pte_t; 224 | typedef uint64 *pagetable_t; // 512 PTEs 225 | 226 | #endif 227 | -------------------------------------------------------------------------------- /kernel/sched.c: -------------------------------------------------------------------------------- 1 | /* 2 | * implementing the scheduler 3 | */ 4 | 5 | #include "sched.h" 6 | #include "spike_interface/spike_utils.h" 7 | 8 | process* ready_queue_head = NULL; 9 | 10 | // 11 | // insert a process, proc, into the END of ready queue. 12 | // 13 | void insert_to_ready_queue( process* proc ) { 14 | sprint( "going to insert process %d to ready queue.\n", proc->pid ); 15 | // if the queue is empty in the beginning 16 | if( ready_queue_head == NULL ){ 17 | proc->status = READY; 18 | proc->queue_next = NULL; 19 | ready_queue_head = proc; 20 | return; 21 | } 22 | 23 | // ready queue is not empty 24 | process *p; 25 | // browse the ready queue to see if proc is already in-queue 26 | for( p=ready_queue_head; p->queue_next!=NULL; p=p->queue_next ) 27 | if( p == proc ) return; //already in queue 28 | 29 | // p points to the last element of the ready queue 30 | if( p==proc ) return; 31 | p->queue_next = proc; 32 | proc->status = READY; 33 | proc->queue_next = NULL; 34 | 35 | return; 36 | } 37 | 38 | // 39 | // choose a proc from the ready queue, and put it to run. 40 | // note: schedule() does not take care of previous current process. If the current 41 | // process is still runnable, you should place it into the ready queue (by calling 42 | // ready_queue_insert), and then call schedule(). 43 | // 44 | extern process procs[NPROC]; 45 | void schedule() { 46 | if ( !ready_queue_head ){ 47 | // by default, if there are no ready process, and all processes are in the status of 48 | // FREE and ZOMBIE, we should shutdown the emulated RISC-V machine. 49 | int should_shutdown = 1; 50 | 51 | for( int i=0; istatus == READY ); 68 | ready_queue_head = ready_queue_head->queue_next; 69 | 70 | current->status = RUNNING; 71 | sprint( "going to schedule process %d to run.\n", current->pid ); 72 | switch_to( current ); 73 | } 74 | -------------------------------------------------------------------------------- /kernel/sched.h: -------------------------------------------------------------------------------- 1 | #ifndef _SCHED_H_ 2 | #define _SCHED_H_ 3 | 4 | #include "process.h" 5 | 6 | //length of a time slice, in number of ticks 7 | #define TIME_SLICE_LEN 2 8 | 9 | void insert_to_ready_queue( process* proc ); 10 | void schedule(); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /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 | #include "pmm.h" 10 | #include "vmm.h" 11 | #include "sched.h" 12 | #include "util/functions.h" 13 | #include "memlayout.h" 14 | #include "spike_interface/spike_utils.h" 15 | 16 | // 17 | // handling the syscalls. will call do_syscall() defined in kernel/syscall.c 18 | // 19 | static void handle_syscall(trapframe *tf) { 20 | // tf->epc points to the address that our computer will jump to after the trap handling. 21 | // for a syscall, we should return to the NEXT instruction after its handling. 22 | // in RV64G, each instruction occupies exactly 32 bits (i.e., 4 Bytes) 23 | tf->epc += 4; 24 | 25 | // TODO (lab1_1): remove the panic call below, and call do_syscall (defined in 26 | // kernel/syscall.c) to conduct real operations of the kernel side for a syscall. 27 | // IMPORTANT: return value should be returned to user app, or else, you will encounter 28 | // problems in later experiments! 29 | //panic( "call do_syscall to accomplish the syscall and lab1_1 here.\n" ); 30 | uint64 syscall_num = (tf->regs).a0; 31 | (tf->regs).a0 = do_syscall(syscall_num, 32 | (tf->regs).a1, 33 | (tf->regs).a2, 34 | (tf->regs).a3, 35 | (tf->regs).a4, 36 | (tf->regs).a5, 37 | (tf->regs).a6, 38 | (tf->regs).a7); 39 | } 40 | 41 | // 42 | // global variable that store the recorded "ticks". added @lab1_3 43 | static uint64 g_ticks = 0; 44 | // 45 | // added @lab1_3 46 | // 47 | void handle_mtimer_trap() { 48 | sprint("Ticks %d\n", g_ticks); 49 | // TODO (lab1_3): increase g_ticks to record this "tick", and then clear the "SIP" 50 | // field in sip register. 51 | // hint: use write_csr to disable the SIP_SSIP bit in sip. 52 | //panic( "lab1_3: increase g_ticks by one, and clear SIP field in sip register.\n" ); 53 | g_ticks+=1; 54 | write_csr(sip, 0L << 1); 55 | } 56 | 57 | // 58 | // the page fault handler. added @lab2_3. parameters: 59 | // sepc: the pc when fault happens; 60 | // stval: the virtual address that causes pagefault when being accessed. 61 | // 62 | void handle_user_page_fault(uint64 mcause, uint64 sepc, uint64 stval) { 63 | sprint("handle_page_fault: %lx\n", stval); 64 | switch (mcause) { 65 | case CAUSE_STORE_PAGE_FAULT: 66 | // TODO (lab2_3): implement the operations that solve the page fault to 67 | // dynamically increase application stack. 68 | // hint: first allocate a new physical page, and then, maps the new page to the 69 | // virtual address that causes the page fault. 70 | //panic( "You need to implement the operations that actually handle the page fault in lab2_3.\n" ); 71 | if(stval < USER_STACK_TOP && stval > (USER_STACK_TOP - 20 * STACK_SIZE)){ 72 | void* pa = alloc_page(); 73 | user_vm_map((pagetable_t)current->pagetable,ROUNDDOWN(stval,PGSIZE), PGSIZE, (uint64)pa,prot_to_type(PROT_WRITE | PROT_READ, 1)); 74 | } 75 | else panic("In page fault illegal va.\n"); 76 | break; 77 | default: 78 | panic("unknown page fault.\n"); 79 | break; 80 | } 81 | } 82 | 83 | // 84 | // implements round-robin scheduling. added @lab3_3 85 | // 86 | void rrsched() { 87 | // TODO (lab3_3): implements round-robin scheduling. 88 | // hint: increase the tick_count member of current process by one, if it is bigger than 89 | // TIME_SLICE_LEN (means it has consumed its time slice), change its status into READY, 90 | // place it in the rear of ready queue, and finally schedule next process to run. 91 | //panic( "You need to further implement the timer handling in lab3_3.\n" ); 92 | if(++current->tick_count >= TIME_SLICE_LEN){ 93 | current->tick_count = 0; 94 | current->status = READY; 95 | insert_to_ready_queue(current); 96 | schedule(); 97 | } 98 | } 99 | 100 | // 101 | // kernel/smode_trap.S will pass control to smode_trap_handler, when a trap happens 102 | // in S-mode. 103 | // 104 | void smode_trap_handler(void) { 105 | // make sure we are in User mode before entering the trap handling. 106 | // we will consider other previous case in lab1_3 (interrupt). 107 | if ((read_csr(sstatus) & SSTATUS_SPP) != 0) panic("usertrap: not from user mode"); 108 | 109 | assert(current); 110 | // save user process counter. 111 | current->trapframe->epc = read_csr(sepc); 112 | 113 | // if the cause of trap is syscall from user application. 114 | // read_csr() and CAUSE_USER_ECALL are macros defined in kernel/riscv.h 115 | uint64 cause = read_csr(scause); 116 | 117 | // use switch-case instead of if-else, as there are many cases since lab2_3. 118 | switch (cause) { 119 | case CAUSE_USER_ECALL: 120 | handle_syscall(current->trapframe); 121 | break; 122 | case CAUSE_MTIMER_S_TRAP: 123 | handle_mtimer_trap(); 124 | // invoke round-robin scheduler. added @lab3_3 125 | rrsched(); 126 | break; 127 | case CAUSE_STORE_PAGE_FAULT: 128 | case CAUSE_LOAD_PAGE_FAULT: 129 | // the address of missing page is stored in stval 130 | // call handle_user_page_fault to process page faults 131 | handle_user_page_fault(cause, read_csr(sepc), read_csr(stval)); 132 | break; 133 | default: 134 | sprint("smode_trap_handler(): unexpected scause %p\n", read_csr(scause)); 135 | sprint(" sepc=%p stval=%p\n", read_csr(sepc), read_csr(stval)); 136 | panic( "unexpected exception happened.\n" ); 137 | break; 138 | } 139 | // sprint("dir = %s\n", lookup_pa(current->pagetable, current->trapframe->regs.sp)); 140 | // continue (come back to) the execution of current process. 141 | switch_to(current); 142 | } 143 | -------------------------------------------------------------------------------- /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 | # restore kernel page table from p->trapframe->kernel_satp. added @lab2_1 41 | ld t1, 272(a0) 42 | csrw satp, t1 43 | sfence.vma zero, zero 44 | 45 | # jump to smode_trap_handler() that is defined in kernel/trap.c 46 | jr t0 47 | 48 | # 49 | # return from Supervisor mode to User mode, transition is made by using a trapframe, 50 | # which stores the context of a user application. 51 | # return_to_user() takes one parameter, i.e., the pointer (a0 register) pointing to a 52 | # trapframe (defined in kernel/process.h) of the process. 53 | # 54 | .globl return_to_user 55 | return_to_user: 56 | # a0: TRAPFRAME 57 | # a1: user page table, for satp. 58 | 59 | # switch to the user page table. added @lab2_1 60 | csrw satp, a1 61 | sfence.vma zero, zero 62 | 63 | # [sscratch]=[a0], save a0 in sscratch, so sscratch points to a trapframe now. 64 | csrw sscratch, a0 65 | 66 | # let [t6]=[a0] 67 | addi t6, a0, 0 68 | 69 | # restore_all_registers is a assembly macro defined in util/load_store.S. 70 | # the macro restores all registers from trapframe started from [t6] to all general 71 | # purpose registers, so as to resort the execution of a process. 72 | restore_all_registers 73 | 74 | # return to user mode and user pc. 75 | sret 76 | -------------------------------------------------------------------------------- /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 | #include "pmm.h" 14 | #include "vmm.h" 15 | #include "sched.h" 16 | #include "proc_file.h" 17 | 18 | #include "spike_interface/spike_utils.h" 19 | 20 | extern process procs[NPROC]; 21 | // 22 | // implement the SYS_user_print syscall 23 | // 24 | ssize_t sys_user_print(const char* buf, size_t n) { 25 | // buf is now an address in user space of the given app's user stack, 26 | // so we have to transfer it into phisical address (kernel is running in direct mapping). 27 | assert( current ); 28 | char* pa = (char*)user_va_to_pa((pagetable_t)(current->pagetable), (void*)buf); 29 | sprint(pa); 30 | return 0; 31 | } 32 | 33 | // 34 | // implement the SYS_user_exit syscall 35 | // 36 | ssize_t sys_user_exit(uint64 code) { 37 | sprint("User exit with code:%d.\n", code); 38 | // reclaim the current process, and reschedule. added @lab3_1 39 | free_process( current ); 40 | for( int i=NPROC; i>=0; i-- ) 41 | if( procs[i].status == BLOCKED && current->parent == &procs[i]){ 42 | procs[i].status = READY; 43 | insert_to_ready_queue( &procs[i] ); 44 | } 45 | schedule(); 46 | return 0; 47 | } 48 | 49 | // 50 | // maybe, the simplest implementation of malloc in the world ... added @lab2_2 51 | // 52 | uint64 sys_user_allocate_page() { 53 | void* pa = alloc_page(); 54 | uint64 va; 55 | // if there are previously reclaimed pages, use them first (this does not change the 56 | // size of the heap) 57 | if (current->user_heap.free_pages_count > 0) { 58 | va = current->user_heap.free_pages_address[--current->user_heap.free_pages_count]; 59 | assert(va < current->user_heap.heap_top); 60 | } else { 61 | // otherwise, allocate a new page (this increases the size of the heap by one page) 62 | va = current->user_heap.heap_top; 63 | current->user_heap.heap_top += PGSIZE; 64 | 65 | current->mapped_info[HEAP_SEGMENT].npages++; 66 | } 67 | user_vm_map((pagetable_t)current->pagetable, va, PGSIZE, (uint64)pa, 68 | prot_to_type(PROT_WRITE | PROT_READ, 1)); 69 | 70 | return va; 71 | } 72 | 73 | // 74 | // reclaim a page, indicated by "va". added @lab2_2 75 | // 76 | uint64 sys_user_free_page(uint64 va) { 77 | user_vm_unmap((pagetable_t)current->pagetable, va, PGSIZE, 1); 78 | // add the reclaimed page to the free page list 79 | current->user_heap.free_pages_address[current->user_heap.free_pages_count++] = va; 80 | return 0; 81 | } 82 | 83 | // 84 | // kerenl entry point of naive_fork 85 | // 86 | ssize_t sys_user_fork() { 87 | sprint("User call fork.\n"); 88 | return do_fork( current ); 89 | } 90 | 91 | // 92 | // kerenl entry point of yield. added @lab3_2 93 | // 94 | ssize_t sys_user_yield() { 95 | // TODO (lab3_2): implment the syscall of yield. 96 | // hint: the functionality of yield is to give up the processor. therefore, 97 | // we should set the status of currently running process to READY, insert it in 98 | // the rear of ready queue, and finally, schedule a READY process to run. 99 | //panic( "You need to implement the yield syscall in lab3_2.\n" ); 100 | current->status = READY; 101 | insert_to_ready_queue(current); 102 | schedule(); 103 | return 0; 104 | } 105 | 106 | ssize_t sys_user_wait(uint64 pid) { 107 | process* parent = current; 108 | if(pid == -1){ 109 | //wait any child process exit and return its pid 110 | for (int i = 0; i < NPROC; ++i) { 111 | if(procs[i].status == ZOMBIE && procs[i].parent == parent){ 112 | return i; 113 | } 114 | } 115 | //sprint("wait for any process\n"); 116 | parent->status = BLOCKED; 117 | schedule(); 118 | 119 | }else if(pid > 0){ 120 | //wait the child process exit and return its pid 121 | if(procs[pid].parent == parent){ 122 | if(procs[pid].status == ZOMBIE){ 123 | return pid; 124 | } 125 | //sprint("wait for process %d\n",pid); 126 | parent->status = BLOCKED; 127 | schedule(); 128 | } 129 | else return -1; 130 | } 131 | return -1; 132 | } 133 | 134 | // 135 | // open file 136 | // 137 | ssize_t sys_user_open(char *pathva, int flags) { 138 | char* pathpa = (char*)user_va_to_pa((pagetable_t)(current->pagetable), pathva); 139 | // sprint("%s\n", pathpa); 140 | return do_open(pathpa, flags); 141 | } 142 | 143 | // 144 | // read file 145 | // 146 | ssize_t sys_user_read(int fd, char *bufva, uint64 count) { 147 | int i = 0; 148 | while (i < count) { // count can be greater than page size 149 | uint64 addr = (uint64)bufva + i; 150 | uint64 pa = lookup_pa((pagetable_t)current->pagetable, addr); 151 | uint64 off = addr - ROUNDDOWN(addr, PGSIZE); 152 | uint64 len = count - i < PGSIZE - off ? count - i : PGSIZE - off; 153 | uint64 r = do_read(fd, (char *)pa + off, len); 154 | i += r; if (r < len) return i; 155 | } 156 | return count; 157 | } 158 | 159 | // 160 | // write file 161 | // 162 | ssize_t sys_user_write(int fd, char *bufva, uint64 count) { 163 | int i = 0; 164 | while (i < count) { // count can be greater than page size 165 | uint64 addr = (uint64)bufva + i; 166 | uint64 pa = lookup_pa((pagetable_t)current->pagetable, addr); 167 | uint64 off = addr - ROUNDDOWN(addr, PGSIZE); 168 | uint64 len = count - i < PGSIZE - off ? count - i : PGSIZE - off; 169 | uint64 r = do_write(fd, (char *)pa + off, len); 170 | i += r; if (r < len) return i; 171 | } 172 | return count; 173 | } 174 | 175 | // 176 | // lseek file 177 | // 178 | ssize_t sys_user_lseek(int fd, int offset, int whence) { 179 | return do_lseek(fd, offset, whence); 180 | } 181 | 182 | // 183 | // read vinode 184 | // 185 | ssize_t sys_user_stat(int fd, struct istat *istat) { 186 | struct istat * pistat = (struct istat *)user_va_to_pa((pagetable_t)(current->pagetable), istat); 187 | return do_stat(fd, pistat); 188 | } 189 | 190 | // 191 | // read disk inode 192 | // 193 | ssize_t sys_user_disk_stat(int fd, struct istat *istat) { 194 | struct istat * pistat = (struct istat *)user_va_to_pa((pagetable_t)(current->pagetable), istat); 195 | return do_disk_stat(fd, pistat); 196 | } 197 | 198 | // 199 | // close file 200 | // 201 | ssize_t sys_user_close(int fd) { 202 | return do_close(fd); 203 | } 204 | 205 | // 206 | // lib call to opendir 207 | // 208 | ssize_t sys_user_opendir(char * pathva){ 209 | char * pathpa = (char*)user_va_to_pa((pagetable_t)(current->pagetable), pathva); 210 | // sprint("!!!%s\n", pathpa); 211 | return do_opendir(pathpa); 212 | } 213 | 214 | // 215 | // lib call to readdir 216 | // 217 | ssize_t sys_user_readdir(int fd, struct dir *vdir){ 218 | struct dir * pdir = (struct dir *)user_va_to_pa((pagetable_t)(current->pagetable), vdir); 219 | // sprint("!!!%s\n", pdir->name); 220 | return do_readdir(fd, pdir); 221 | } 222 | 223 | // 224 | // lib call to mkdir 225 | // 226 | ssize_t sys_user_mkdir(char * pathva){ 227 | char * pathpa = (char*)user_va_to_pa((pagetable_t)(current->pagetable), pathva); 228 | // sprint("%s\n", pathpa); 229 | return do_mkdir(pathpa); 230 | } 231 | 232 | // 233 | // lib call to closedir 234 | // 235 | ssize_t sys_user_closedir(int fd){ 236 | return do_closedir(fd); 237 | } 238 | 239 | // 240 | // lib call to link 241 | // 242 | ssize_t sys_user_link(char * vfn1, char * vfn2){ 243 | char * pfn1 = (char*)user_va_to_pa((pagetable_t)(current->pagetable), (void*)vfn1); 244 | char * pfn2 = (char*)user_va_to_pa((pagetable_t)(current->pagetable), (void*)vfn2); 245 | return do_link(pfn1, pfn2); 246 | } 247 | 248 | // 249 | // lib call to unlink 250 | // 251 | ssize_t sys_user_unlink(char * vfn){ 252 | char * pfn = (char*)user_va_to_pa((pagetable_t)(current->pagetable), (void*)vfn); 253 | return do_unlink(pfn); 254 | } 255 | 256 | ssize_t sys_user_exec(char * fn, char * para){ 257 | char* fn_pa = (char*)user_va_to_pa((pagetable_t)(current->pagetable), fn); 258 | char* para_pa = (char*)user_va_to_pa((pagetable_t)(current->pagetable), para); 259 | do_exec(fn_pa, para_pa); 260 | return -1; 261 | } 262 | // 263 | // [a0]: the syscall number; [a1] ... [a7]: arguments to the syscalls. 264 | // returns the code of success, (e.g., 0 means success, fail for otherwise) 265 | // 266 | long do_syscall(long a0, long a1, long a2, long a3, long a4, long a5, long a6, long a7) { 267 | switch (a0) { 268 | case SYS_user_print: 269 | return sys_user_print((const char*)a1, a2); 270 | case SYS_user_exit: 271 | return sys_user_exit(a1); 272 | // added @lab2_2 273 | case SYS_user_allocate_page: 274 | return sys_user_allocate_page(); 275 | case SYS_user_free_page: 276 | return sys_user_free_page(a1); 277 | case SYS_user_fork: 278 | return sys_user_fork(); 279 | case SYS_user_yield: 280 | return sys_user_yield(); 281 | case SYS_user_wait: 282 | return sys_user_wait(a1); 283 | // added @lab4_1 284 | case SYS_user_open: 285 | return sys_user_open((char *)a1, a2); 286 | case SYS_user_read: 287 | return sys_user_read(a1, (char *)a2, a3); 288 | case SYS_user_write: 289 | return sys_user_write(a1, (char *)a2, a3); 290 | case SYS_user_lseek: 291 | return sys_user_lseek(a1, a2, a3); 292 | case SYS_user_stat: 293 | return sys_user_stat(a1, (struct istat *)a2); 294 | case SYS_user_disk_stat: 295 | return sys_user_disk_stat(a1, (struct istat *)a2); 296 | case SYS_user_close: 297 | return sys_user_close(a1); 298 | // added @lab4_2 299 | case SYS_user_opendir: 300 | return sys_user_opendir((char *)a1); 301 | case SYS_user_readdir: 302 | return sys_user_readdir(a1, (struct dir *)a2); 303 | case SYS_user_mkdir: 304 | return sys_user_mkdir((char *)a1); 305 | case SYS_user_closedir: 306 | return sys_user_closedir(a1); 307 | // added @lab4_3 308 | case SYS_user_link: 309 | return sys_user_link((char *)a1, (char *)a2); 310 | case SYS_user_unlink: 311 | return sys_user_unlink((char *)a1); 312 | case SYS_user_exec: 313 | return sys_user_exec((char *)a1, (char *)a2); 314 | default: 315 | panic("Unknown syscall %ld \n", a0); 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /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 | // added @lab2_2 12 | #define SYS_user_allocate_page (SYS_user_base + 2) 13 | #define SYS_user_free_page (SYS_user_base + 3) 14 | // added @lab3_1 15 | #define SYS_user_fork (SYS_user_base + 4) 16 | #define SYS_user_yield (SYS_user_base + 5) 17 | #define SYS_user_wait (SYS_user_base + 6) 18 | // added @lab4_1 19 | #define SYS_user_open (SYS_user_base + 17) 20 | #define SYS_user_read (SYS_user_base + 18) 21 | #define SYS_user_write (SYS_user_base + 19) 22 | #define SYS_user_lseek (SYS_user_base + 20) 23 | #define SYS_user_stat (SYS_user_base + 21) 24 | #define SYS_user_disk_stat (SYS_user_base + 22) 25 | #define SYS_user_close (SYS_user_base + 23) 26 | // added @lab4_2 27 | #define SYS_user_opendir (SYS_user_base + 24) 28 | #define SYS_user_readdir (SYS_user_base + 25) 29 | #define SYS_user_mkdir (SYS_user_base + 26) 30 | #define SYS_user_closedir (SYS_user_base + 27) 31 | // added @lab4_3 32 | #define SYS_user_link (SYS_user_base + 28) 33 | #define SYS_user_unlink (SYS_user_base + 29) 34 | #define SYS_user_exec (SYS_user_base + 30) 35 | 36 | long do_syscall(long a0, long a1, long a2, long a3, long a4, long a5, long a6, long a7); 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /kernel/vfs.h: -------------------------------------------------------------------------------- 1 | #ifndef _VFS_H_ 2 | #define _VFS_H_ 3 | 4 | #include "util/types.h" 5 | 6 | #define MAX_VFS_DEV 10 // the maximum number of vfs_dev_list 7 | #define MAX_DENTRY_NAME_LEN 30 // the maximum length of dentry name 8 | #define MAX_DEVICE_NAME_LEN 30 // the maximum length of device name 9 | #define MAX_MOUNTS 10 // the maximum number of mounts 10 | #define MAX_DENTRY_HASH_SIZE 100 // the maximum size of dentry hash table 11 | #define MAX_PATH_LEN 30 // the maximum length of path 12 | #define MAX_SUPPORTED_FS 10 // the maximum number of supported file systems 13 | 14 | #define DIRECT_BLKNUM 10 // the number of direct blocks 15 | 16 | /**** vfs initialization function ****/ 17 | int vfs_init(); 18 | 19 | /**** vfs interfaces ****/ 20 | 21 | // device interfaces 22 | struct super_block *vfs_mount(const char *dev_name, int mnt_type); 23 | 24 | // file interfaces 25 | struct file *vfs_open(const char *path, int flags); 26 | ssize_t vfs_read(struct file *file, char *buf, size_t count); 27 | ssize_t vfs_write(struct file *file, const char *buf, size_t count); 28 | ssize_t vfs_lseek(struct file *file, ssize_t offset, int whence); 29 | int vfs_stat(struct file *file, struct istat *istat); 30 | int vfs_disk_stat(struct file *file, struct istat *istat); 31 | int vfs_link(const char *oldpath, const char *newpath); 32 | int vfs_unlink(const char *path); 33 | int vfs_close(struct file *file); 34 | 35 | // directory interfaces 36 | struct file *vfs_opendir(const char *path); 37 | int vfs_readdir(struct file *file, struct dir *dir); 38 | int vfs_mkdir(const char *path); 39 | int vfs_closedir(struct file *file); 40 | 41 | /**** vfs abstract object types ****/ 42 | // system root direntry 43 | extern struct dentry *vfs_root_dentry; 44 | 45 | // vfs abstract dentry 46 | struct dentry { 47 | char name[MAX_DENTRY_NAME_LEN]; 48 | int d_ref; 49 | struct vinode *dentry_inode; 50 | struct dentry *parent; 51 | struct super_block *sb; 52 | }; 53 | 54 | 55 | // dentry constructor and destructor 56 | struct dentry *alloc_vfs_dentry(const char *name, struct vinode *inode, 57 | struct dentry *parent); 58 | int free_vfs_dentry(struct dentry *dentry); 59 | 60 | // ** dentry hash table ** 61 | extern struct hash_table dentry_hash_table; 62 | 63 | // dentry hash table key type 64 | struct dentry_key { 65 | struct dentry *parent; 66 | char *name; 67 | }; 68 | 69 | // generic hash table method implementation 70 | int dentry_hash_equal(void *key1, void *key2); 71 | size_t dentry_hash_func(void *key); 72 | 73 | // dentry hash table interface 74 | struct dentry *hash_get_dentry(struct dentry *parent, char *name); 75 | int hash_put_dentry(struct dentry *dentry); 76 | int hash_erase_dentry(struct dentry *dentry); 77 | 78 | // data structure of an openned file 79 | struct file { 80 | int status; 81 | int readable; 82 | int writable; 83 | int offset; 84 | struct dentry *f_dentry; 85 | }; 86 | 87 | // file constructor and destructor(use free_page to destruct) 88 | struct file *alloc_vfs_file(struct dentry *dentry, int readable, int writable, 89 | int offset); 90 | 91 | // abstract device entry in vfs_dev_list 92 | struct device { 93 | char dev_name[MAX_DEVICE_NAME_LEN]; // the name of the device 94 | int dev_id; // the id of the device (the meaning of an id is interpreted by 95 | // the specific file system, all we need to know is that it is 96 | // a unique identifier) 97 | struct file_system_type *fs_type; // the file system type in the device 98 | }; 99 | 100 | // device list in vfs layer 101 | extern struct device *vfs_dev_list[MAX_VFS_DEV]; 102 | 103 | // supported file system types 104 | struct file_system_type { 105 | int type_num; // the number of the file system type 106 | struct super_block *(*get_superblock)(struct device *dev); 107 | }; 108 | 109 | extern struct file_system_type *fs_list[MAX_SUPPORTED_FS]; 110 | 111 | // general-purpose super_block structure 112 | struct super_block { 113 | int magic; // magic number of the file system 114 | int size; // size of file system image (blocks) 115 | int nblocks; // number of data blocks 116 | int ninodes; // number of inodes. 117 | struct dentry *s_root; // root dentry of inode 118 | struct device *s_dev; // device of the superblock 119 | void *s_fs_info; // filesystem-specific info. for rfs, it points bitmap 120 | }; 121 | 122 | // abstract vfs inode 123 | struct vinode { 124 | int inum; // inode number of the disk inode 125 | int ref; // reference count 126 | int size; // size of the file (in bytes) 127 | int type; // one of FILE_I, DIR_I 128 | int nlinks; // number of hard links to this file 129 | int blocks; // number of blocks 130 | int addrs[DIRECT_BLKNUM]; // direct blocks 131 | void *i_fs_info; // filesystem-specific info (see s_fs_info) 132 | struct super_block *sb; // super block of the vfs inode 133 | const struct vinode_ops *i_ops; // vfs inode operations 134 | }; 135 | 136 | struct vinode_ops { 137 | // file operations 138 | ssize_t (*viop_read)(struct vinode *node, char *buf, ssize_t len, 139 | int *offset); 140 | ssize_t (*viop_write)(struct vinode *node, const char *buf, ssize_t len, 141 | int *offset); 142 | struct vinode *(*viop_create)(struct vinode *parent, struct dentry *sub_dentry); 143 | int (*viop_lseek)(struct vinode *node, ssize_t new_off, int whence, int *off); 144 | int (*viop_disk_stat)(struct vinode *node, struct istat *istat); 145 | int (*viop_link)(struct vinode *parent, struct dentry *sub_dentry, 146 | struct vinode *link_node); 147 | int (*viop_unlink)(struct vinode *parent, struct dentry *sub_dentry, 148 | struct vinode *unlink_node); 149 | struct vinode *(*viop_lookup)(struct vinode *parent, 150 | struct dentry *sub_dentry); 151 | 152 | // directory operations 153 | int (*viop_readdir)(struct vinode *dir_vinode, struct dir *dir, int *offset); 154 | struct vinode *(*viop_mkdir)(struct vinode *parent, struct dentry *sub_dentry); 155 | 156 | // write back inode to disk 157 | int (*viop_write_back_vinode)(struct vinode *node); 158 | 159 | // hook functions 160 | // In the vfs layer, we do not assume that hook functions will do anything, 161 | // but simply call them (when they are defined) at the appropriate time. 162 | // Hook functions exist because the fs layer may need to do some additional 163 | // operations (such as allocating additional data structures) at some critical 164 | // times. 165 | int (*viop_hook_open)(struct vinode *node, struct dentry *dentry); 166 | int (*viop_hook_close)(struct vinode *node, struct dentry *dentry); 167 | int (*viop_hook_opendir)(struct vinode *node, struct dentry *dentry); 168 | int (*viop_hook_closedir)(struct vinode *node, struct dentry *dentry); 169 | }; 170 | 171 | // vinode operation interface 172 | // the implementation depends on the vinode type and the specific file system 173 | 174 | // virtual file system inode interfaces 175 | #define viop_read(node, buf, len, offset) (node->i_ops->viop_read(node, buf, len, offset)) 176 | #define viop_write(node, buf, len, offset) (node->i_ops->viop_write(node, buf, len, offset)) 177 | #define viop_create(node, name) (node->i_ops->viop_create(node, name)) 178 | #define viop_lseek(node, new_off, whence, off) (node->i_ops->viop_lseek(node, new_off, whence, off)) 179 | #define viop_disk_stat(node, istat) (node->i_ops->viop_disk_stat(node, istat)) 180 | #define viop_link(node, name, link_node) (node->i_ops->viop_link(node, name, link_node)) 181 | #define viop_unlink(node, name, unlink_node) (node->i_ops->viop_unlink(node, name, unlink_node)) 182 | #define viop_lookup(parent, sub_dentry) (parent->i_ops->viop_lookup(parent, sub_dentry)) 183 | #define viop_readdir(dir_vinode, dir, offset) (dir_vinode->i_ops->viop_readdir(dir_vinode, dir, offset)) 184 | #define viop_mkdir(dir, sub_dentry) (dir->i_ops->viop_mkdir(dir, sub_dentry)) 185 | #define viop_write_back_vinode(node) (node->i_ops->viop_write_back_vinode(node)) 186 | 187 | // vinode hash table 188 | extern struct hash_table vinode_hash_table; 189 | 190 | // vinode hash table key type 191 | struct vinode_key { 192 | int inum; 193 | struct super_block *sb; 194 | }; 195 | 196 | // generic hash table method implementation 197 | int vinode_hash_equal(void *key1, void *key2); 198 | size_t vinode_hash_func(void *key); 199 | 200 | // vinode hash table interface 201 | struct vinode *hash_get_vinode(struct super_block *sb, int inum); 202 | int hash_put_vinode(struct vinode *vinode); 203 | int hash_erase_vinode(struct vinode *vinode); 204 | 205 | // other utility functions 206 | struct vinode *default_alloc_vinode(struct super_block *sb); 207 | struct dentry *lookup_final_dentry(const char *path, struct dentry **parent, 208 | char *miss_name); 209 | void get_base_name(const char *path, char *base_name); 210 | 211 | #endif 212 | -------------------------------------------------------------------------------- /kernel/vmm.c: -------------------------------------------------------------------------------- 1 | /* 2 | * virtual address mapping related functions. 3 | */ 4 | 5 | #include "vmm.h" 6 | #include "riscv.h" 7 | #include "pmm.h" 8 | #include "util/types.h" 9 | #include "memlayout.h" 10 | #include "util/string.h" 11 | #include "spike_interface/spike_utils.h" 12 | #include "util/functions.h" 13 | 14 | /* --- utility functions for virtual address mapping --- */ 15 | // 16 | // establish mapping of virtual address [va, va+size] to phyiscal address [pa, pa+size] 17 | // with the permission of "perm". 18 | // 19 | int map_pages(pagetable_t page_dir, uint64 va, uint64 size, uint64 pa, int perm) { 20 | uint64 first, last; 21 | pte_t *pte; 22 | 23 | for (first = ROUNDDOWN(va, PGSIZE), last = ROUNDDOWN(va + size - 1, PGSIZE); 24 | first <= last; first += PGSIZE, pa += PGSIZE) { 25 | if ((pte = page_walk(page_dir, first, 1)) == 0) return -1; 26 | if (*pte & PTE_V) 27 | panic("map_pages fails on mapping va (0x%lx) to pa (0x%lx)", first, pa); 28 | *pte = PA2PTE(pa) | perm | PTE_V; 29 | } 30 | return 0; 31 | } 32 | 33 | // 34 | // convert permission code to permission types of PTE 35 | // 36 | uint64 prot_to_type(int prot, int user) { 37 | uint64 perm = 0; 38 | if (prot & PROT_READ) perm |= PTE_R | PTE_A; 39 | if (prot & PROT_WRITE) perm |= PTE_W | PTE_D; 40 | if (prot & PROT_EXEC) perm |= PTE_X | PTE_A; 41 | if (perm == 0) perm = PTE_R; 42 | if (user) perm |= PTE_U; 43 | return perm; 44 | } 45 | 46 | // 47 | // traverse the page table (starting from page_dir) to find the corresponding pte of va. 48 | // returns: PTE (page table entry) pointing to va. 49 | // 50 | pte_t *page_walk(pagetable_t page_dir, uint64 va, int alloc) { 51 | if (va >= MAXVA) panic("page_walk"); 52 | 53 | // starting from the page directory 54 | pagetable_t pt = page_dir; 55 | 56 | // traverse from page directory to page table. 57 | // as we use risc-v sv39 paging scheme, there will be 3 layers: page dir, 58 | // page medium dir, and page table. 59 | for (int level = 2; level > 0; level--) { 60 | // macro "PX" gets the PTE index in page table of current level 61 | // "pte" points to the entry of current level 62 | pte_t *pte = pt + PX(level, va); 63 | 64 | // now, we need to know if above pte is valid (established mapping to a phyiscal page) 65 | // or not. 66 | if (*pte & PTE_V) { //PTE valid 67 | // phisical address of pagetable of next level 68 | pt = (pagetable_t)PTE2PA(*pte); 69 | } else { //PTE invalid (not exist). 70 | // allocate a page (to be the new pagetable), if alloc == 1 71 | if( alloc && ((pt = (pte_t *)alloc_page(1)) != 0) ){ 72 | memset(pt, 0, PGSIZE); 73 | // writes the physical address of newly allocated page to pte, to establish the 74 | // page table tree. 75 | *pte = PA2PTE(pt) | PTE_V; 76 | }else //returns NULL, if alloc == 0, or no more physical page remains 77 | return 0; 78 | } 79 | } 80 | 81 | // return a PTE which contains phisical address of a page 82 | return pt + PX(0, va); 83 | } 84 | 85 | // 86 | // look up a virtual page address, return the physical page address or 0 if not mapped. 87 | // 88 | uint64 lookup_pa(pagetable_t pagetable, uint64 va) { 89 | pte_t *pte; 90 | uint64 pa; 91 | 92 | if (va >= MAXVA) return 0; 93 | 94 | pte = page_walk(pagetable, va, 0); 95 | if (pte == 0 || (*pte & PTE_V) == 0 || ((*pte & PTE_R) == 0 && (*pte & PTE_W) == 0)) 96 | return 0; 97 | pa = PTE2PA(*pte); 98 | 99 | return pa; 100 | } 101 | 102 | /* --- kernel page table part --- */ 103 | // _etext is defined in kernel.lds, it points to the address after text and rodata segments. 104 | extern char _etext[]; 105 | 106 | // pointer to kernel page director 107 | pagetable_t g_kernel_pagetable; 108 | 109 | // 110 | // maps virtual address [va, va+sz] to [pa, pa+sz] (for kernel). 111 | // 112 | void kern_vm_map(pagetable_t page_dir, uint64 va, uint64 pa, uint64 sz, int perm) { 113 | // map_pages is defined in kernel/vmm.c 114 | if (map_pages(page_dir, va, sz, pa, perm) != 0) panic("kern_vm_map"); 115 | } 116 | 117 | // 118 | // kern_vm_init() constructs the kernel page table. 119 | // 120 | void kern_vm_init(void) { 121 | // pagetable_t is defined in kernel/riscv.h. it's actually uint64* 122 | pagetable_t t_page_dir; 123 | 124 | // allocate a page (t_page_dir) to be the page directory for kernel. alloc_page is defined in kernel/pmm.c 125 | t_page_dir = (pagetable_t)alloc_page(); 126 | // memset is defined in util/string.c 127 | memset(t_page_dir, 0, PGSIZE); 128 | 129 | // map virtual address [KERN_BASE, _etext] to physical address [DRAM_BASE, DRAM_BASE+(_etext - KERN_BASE)], 130 | // to maintain (direct) text section kernel address mapping. 131 | kern_vm_map(t_page_dir, KERN_BASE, DRAM_BASE, (uint64)_etext - KERN_BASE, 132 | prot_to_type(PROT_READ | PROT_EXEC, 0)); 133 | 134 | sprint("KERN_BASE 0x%lx\n", lookup_pa(t_page_dir, KERN_BASE)); 135 | 136 | // also (direct) map remaining address space, to make them accessable from kernel. 137 | // this is important when kernel needs to access the memory content of user's app 138 | // without copying pages between kernel and user spaces. 139 | kern_vm_map(t_page_dir, (uint64)_etext, (uint64)_etext, PHYS_TOP - (uint64)_etext, 140 | prot_to_type(PROT_READ | PROT_WRITE, 0)); 141 | 142 | sprint("physical address of _etext is: 0x%lx\n", lookup_pa(t_page_dir, (uint64)_etext)); 143 | 144 | g_kernel_pagetable = t_page_dir; 145 | } 146 | 147 | /* --- user page table part --- */ 148 | // 149 | // convert and return the corresponding physical address of a virtual address (va) of 150 | // application. 151 | // 152 | void *user_va_to_pa(pagetable_t page_dir, void *va) { 153 | // TODO (lab2_1): implement user_va_to_pa to convert a given user virtual address "va" 154 | // to its corresponding physical address, i.e., "pa". To do it, we need to walk 155 | // through the page table, starting from its directory "page_dir", to locate the PTE 156 | // that maps "va". If found, returns the "pa" by using: 157 | // pa = PYHS_ADDR(PTE) + (va & (1<pid ); 211 | for( int i=0; itotal_mapped_region; i++ ){ 212 | sprint( "-va:%lx, npage:%d, ", proc->mapped_info[i].va, proc->mapped_info[i].npages); 213 | switch(proc->mapped_info[i].seg_type){ 214 | case CODE_SEGMENT: sprint( "type: CODE SEGMENT" ); break; 215 | case DATA_SEGMENT: sprint( "type: DATA SEGMENT" ); break; 216 | case STACK_SEGMENT: sprint( "type: STACK SEGMENT" ); break; 217 | case CONTEXT_SEGMENT: sprint( "type: TRAPFRAME SEGMENT" ); break; 218 | case SYSTEM_SEGMENT: sprint( "type: USER KERNEL STACK SEGMENT" ); break; 219 | } 220 | sprint( ", mapped to pa:%lx\n", lookup_pa(proc->pagetable, proc->mapped_info[i].va) ); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /kernel/vmm.h: -------------------------------------------------------------------------------- 1 | #ifndef _VMM_H_ 2 | #define _VMM_H_ 3 | 4 | #include "riscv.h" 5 | #include "process.h" 6 | 7 | /* --- utility functions for virtual address mapping --- */ 8 | int map_pages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm); 9 | // permission codes. 10 | enum VMPermision { 11 | PROT_NONE = 0, 12 | PROT_READ = 1, 13 | PROT_WRITE = 2, 14 | PROT_EXEC = 4, 15 | }; 16 | 17 | uint64 prot_to_type(int prot, int user); 18 | pte_t *page_walk(pagetable_t pagetable, uint64 va, int alloc); 19 | uint64 lookup_pa(pagetable_t pagetable, uint64 va); 20 | 21 | /* --- kernel page table --- */ 22 | // pointer to kernel page directory 23 | extern pagetable_t g_kernel_pagetable; 24 | 25 | void kern_vm_map(pagetable_t page_dir, uint64 va, uint64 pa, uint64 sz, int perm); 26 | 27 | // Initialize the kernel pagetable 28 | void kern_vm_init(void); 29 | 30 | /* --- user page table --- */ 31 | void *user_va_to_pa(pagetable_t page_dir, void *va); 32 | void user_vm_map(pagetable_t page_dir, uint64 va, uint64 size, uint64 pa, int perm); 33 | void user_vm_unmap(pagetable_t page_dir, uint64 va, uint64 size, int free); 34 | void print_proc_vmspace(process* proc); 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /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 | static spike_file_t* spike_fds[MAX_FDS]; 18 | spike_file_t spike_files[MAX_FILES] = {[0 ... MAX_FILES - 1] = {-1, 0}}; 19 | 20 | void copy_stat(struct stat* dest_va, struct frontend_stat* src) { 21 | struct stat* dest = (struct stat*)dest_va; 22 | dest->st_dev = src->dev; 23 | dest->st_ino = src->ino; 24 | dest->st_mode = src->mode; 25 | dest->st_nlink = src->nlink; 26 | dest->st_uid = src->uid; 27 | dest->st_gid = src->gid; 28 | dest->st_rdev = src->rdev; 29 | dest->st_size = src->size; 30 | dest->st_blksize = src->blksize; 31 | dest->st_blocks = src->blocks; 32 | dest->st_atime = src->atime; 33 | dest->st_mtime = src->mtime; 34 | dest->st_ctime = src->ctime; 35 | } 36 | 37 | int spike_file_stat(spike_file_t* f, struct stat* s) { 38 | struct frontend_stat buf; 39 | uint64 pa = (uint64)&buf; 40 | long ret = frontend_syscall(HTIFSYS_fstat, f->kfd, (uint64)&buf, 0, 0, 0, 0, 0); 41 | copy_stat(s, &buf); 42 | return ret; 43 | } 44 | 45 | int spike_file_close(spike_file_t* f) { 46 | if (!f) return -1; 47 | spike_file_t* old = atomic_cas(&spike_fds[f->kfd], f, 0); 48 | spike_file_decref(f); 49 | if (old != f) return -1; 50 | spike_file_decref(f); 51 | return 0; 52 | } 53 | 54 | void spike_file_decref(spike_file_t* f) { 55 | if (atomic_add(&f->refcnt, -1) == 2) { 56 | int kfd = f->kfd; 57 | mb(); 58 | atomic_set(&f->refcnt, 0); 59 | 60 | frontend_syscall(HTIFSYS_close, kfd, 0, 0, 0, 0, 0, 0); 61 | } 62 | } 63 | 64 | void spike_file_incref(spike_file_t* f) { 65 | long prev = atomic_add(&f->refcnt, 1); 66 | kassert(prev > 0); 67 | } 68 | 69 | ssize_t spike_file_write(spike_file_t* f, const void* buf, size_t size) { 70 | return frontend_syscall(HTIFSYS_write, f->kfd, (uint64)buf, size, 0, 0, 0, 0); 71 | } 72 | 73 | static spike_file_t* spike_file_get_free(void) { 74 | for (spike_file_t* f = spike_files; f < spike_files + MAX_FILES; f++) 75 | if (atomic_read(&f->refcnt) == 0 && atomic_cas(&f->refcnt, 0, INIT_FILE_REF) == 0) 76 | return f; 77 | return NULL; 78 | } 79 | 80 | int spike_file_dup(spike_file_t* f) { 81 | for (int i = 0; i < MAX_FDS; i++) { 82 | if (atomic_cas(&spike_fds[i], 0, f) == 0) { 83 | spike_file_incref(f); 84 | return i; 85 | } 86 | } 87 | return -1; 88 | } 89 | 90 | void spike_file_init(void) { 91 | // create stdin, stdout, stderr and FDs 0-2 92 | for (int i = 0; i < 3; i++) { 93 | spike_file_t* f = spike_file_get_free(); 94 | f->kfd = i; 95 | spike_file_dup(f); 96 | } 97 | } 98 | 99 | spike_file_t* spike_file_openat(int dirfd, const char* fn, int flags, int mode) { 100 | spike_file_t* f = spike_file_get_free(); 101 | if (f == NULL) return ERR_PTR(-ENOMEM); 102 | 103 | size_t fn_size = strlen(fn) + 1; 104 | long ret = frontend_syscall(HTIFSYS_openat, dirfd, (uint64)fn, fn_size, flags, mode, 0, 0); 105 | if (ret >= 0) { 106 | f->kfd = ret; 107 | return f; 108 | } else { 109 | spike_file_decref(f); 110 | return ERR_PTR(ret); 111 | } 112 | } 113 | 114 | spike_file_t* spike_file_open(const char* fn, int flags, int mode) { 115 | return spike_file_openat(AT_FDCWD, fn, flags, mode); 116 | } 117 | 118 | ssize_t spike_file_pread(spike_file_t* f, void* buf, size_t size, off_t offset) { 119 | return frontend_syscall(HTIFSYS_pread, f->kfd, (uint64)buf, size, offset, 0, 0, 0); 120 | } 121 | 122 | ssize_t spike_file_read(spike_file_t* f, void* buf, size_t size) { 123 | return frontend_syscall(HTIFSYS_read, f->kfd, (uint64)buf, size, 0, 0, 0, 0); 124 | } 125 | 126 | ssize_t spike_file_lseek(spike_file_t* f, size_t ptr, int dir) { 127 | return frontend_syscall(HTIFSYS_lseek, f->kfd, ptr, dir, 0, 0, 0, 0); 128 | } 129 | 130 | spike_file_t* spike_file_get(int fd) { 131 | spike_file_t* f; 132 | if (fd < 0 || fd >= MAX_FDS || (f = atomic_read(&spike_fds[fd])) == NULL) 133 | return 0; 134 | 135 | long old_cnt; 136 | do { 137 | old_cnt = atomic_read(&f->refcnt); 138 | if (old_cnt == 0) 139 | return 0; 140 | } while (atomic_cas(&f->refcnt, old_cnt, old_cnt+1) != old_cnt); 141 | 142 | return f; 143 | } 144 | -------------------------------------------------------------------------------- /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_t { 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 MAX_FILES 128 17 | #define MAX_FDS 128 18 | 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 | spike_file_t* spike_file_get(int fd); 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /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_cat.c: -------------------------------------------------------------------------------- 1 | #include "user_lib.h" 2 | #include "util/string.h" 3 | #include "util/types.h" 4 | 5 | int main(int argc, char *argv[]) { 6 | int fd; 7 | int MAXBUF = 512; 8 | char buf[MAXBUF]; 9 | char *filename = argv[0]; 10 | 11 | printu("\n======== cat command ========\n"); 12 | printu("cat: %s\n", filename); 13 | 14 | fd = open(filename, O_RDWR); 15 | printu("file descriptor fd: %d\n", fd); 16 | 17 | read_u(fd, buf, MAXBUF); 18 | printu("read content: \n%s\n", buf); 19 | close(fd); 20 | 21 | exit(0); 22 | return 0; 23 | } 24 | -------------------------------------------------------------------------------- /user/app_echo.c: -------------------------------------------------------------------------------- 1 | #include "user_lib.h" 2 | #include "util/string.h" 3 | #include "util/types.h" 4 | 5 | int main(int argc, char *argv[]) { 6 | int fd; 7 | char str[] = "hello world"; 8 | char *filename = argv[0]; 9 | printu("\n======== echo command ========\n"); 10 | printu("echo: %s\n", filename); 11 | 12 | fd = open(filename, O_RDWR | O_CREAT); 13 | printu("file descriptor fd: %d\n", fd); 14 | 15 | write_u(fd, str, strlen(str)); 16 | printu("write content: \n%s\n", str); 17 | close(fd); 18 | exit(0); 19 | return 0; 20 | } 21 | -------------------------------------------------------------------------------- /user/app_ls.c: -------------------------------------------------------------------------------- 1 | #include "user_lib.h" 2 | #include "util/string.h" 3 | #include "util/types.h" 4 | 5 | int main(int argc, char *argv[]) { 6 | char *path = argv[0]; 7 | int dir_fd = opendir_u(path); 8 | printu("---------- ls command -----------\n"); 9 | printu("ls \"%s\":\n", path); 10 | printu("[name] [inode_num]\n"); 11 | struct dir dir; 12 | int width = 20; 13 | while(readdir_u(dir_fd, &dir) == 0) { 14 | // we do not have %ms :( 15 | char name[width + 1]; 16 | memset(name, ' ', width + 1); 17 | name[width] = '\0'; 18 | if (strlen(dir.name) < width) { 19 | strcpy(name, dir.name); 20 | name[strlen(dir.name)] = ' '; 21 | printu("%s %d\n", name, dir.inum); 22 | } 23 | else 24 | printu("%s %d\n", dir.name, dir.inum); 25 | } 26 | printu("------------------------------\n"); 27 | closedir_u(dir_fd); 28 | 29 | exit(0); 30 | return 0; 31 | } -------------------------------------------------------------------------------- /user/app_mkdir.c: -------------------------------------------------------------------------------- 1 | #include "user_lib.h" 2 | #include "util/string.h" 3 | #include "util/types.h" 4 | 5 | 6 | int main(int argc, char *argv[]) { 7 | char *new_dir = argv[0]; 8 | // printu("%s\n", argv[0]); 9 | printu("\n======== mkdir command ========\n"); 10 | int ret = mkdir_u(new_dir); 11 | // printu("%d\n", ret); 12 | printu("mkdir: %s\n", new_dir); 13 | exit(0); 14 | return 0; 15 | } 16 | -------------------------------------------------------------------------------- /user/app_shell.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This app starts a very simple shell and executes some simple commands. 3 | * The commands are stored in the hostfs_root/shellrc 4 | * The shell loads the file and executes the command line by line. 5 | */ 6 | #include "user_lib.h" 7 | #include "string.h" 8 | #include "util/types.h" 9 | 10 | int main(int argc, char *argv[]) { 11 | printu("\n======== Shell Start ========\n\n"); 12 | int fd; 13 | int MAXBUF = 1024; 14 | char buf[MAXBUF]; 15 | char *token; 16 | char delim[3] = " \n"; 17 | fd = open("/shellrc", O_RDONLY); 18 | 19 | read_u(fd, buf, MAXBUF); 20 | close(fd); 21 | char *command = naive_malloc(); 22 | char *para = naive_malloc(); 23 | int start = 0; 24 | while (1) 25 | { 26 | if(!start) { 27 | token = strtok(buf, delim); 28 | start = 1; 29 | } 30 | else 31 | token = strtok(NULL, delim); 32 | strcpy(command, token); 33 | token = strtok(NULL, delim); 34 | strcpy(para, token); 35 | if(strcmp(command, "END") == 0 && strcmp(para, "END") == 0) 36 | break; 37 | printu("Next command: %s %s\n\n", command, para); 38 | printu("==========Command Start============\n\n"); 39 | int pid = fork(); 40 | if(pid == 0) { 41 | int ret = exec(command, para); 42 | if (ret == -1) 43 | printu("exec failed!\n"); 44 | } 45 | else 46 | { 47 | wait(pid); 48 | printu("==========Command End============\n\n"); 49 | } 50 | } 51 | exit(0); 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /user/app_touch.c: -------------------------------------------------------------------------------- 1 | #include "user_lib.h" 2 | #include "util/string.h" 3 | #include "util/types.h" 4 | 5 | int main(int argc, char *argv[]) { 6 | int fd; 7 | char *filename = argv[0]; 8 | printu("\n======== touch command ========\n"); 9 | printu("touch: %s\n", filename); 10 | 11 | fd = open(filename, O_CREAT); 12 | printu("file descriptor fd: %d\n", fd); 13 | 14 | close(fd); 15 | exit(0); 16 | return 0; 17 | } 18 | -------------------------------------------------------------------------------- /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 | uint64 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 | 53 | // 54 | // lib call to naive_malloc 55 | // 56 | void* naive_malloc() { 57 | return (void*)do_user_call(SYS_user_allocate_page, 0, 0, 0, 0, 0, 0, 0); 58 | } 59 | 60 | // 61 | // lib call to naive_free 62 | // 63 | void naive_free(void* va) { 64 | do_user_call(SYS_user_free_page, (uint64)va, 0, 0, 0, 0, 0, 0); 65 | } 66 | 67 | // 68 | // lib call to naive_fork 69 | int fork() { 70 | return do_user_call(SYS_user_fork, 0, 0, 0, 0, 0, 0, 0); 71 | } 72 | 73 | // 74 | // lib call to yield 75 | // 76 | void yield() { 77 | do_user_call(SYS_user_yield, 0, 0, 0, 0, 0, 0, 0); 78 | } 79 | 80 | int wait(int pid) { 81 | return do_user_call(SYS_user_wait, pid, 0, 0, 0, 0, 0, 0); 82 | } 83 | 84 | int exec(const char *fn, const char *para){ 85 | return do_user_call(SYS_user_exec, (uint64)fn, (uint64)para, 0, 0, 0, 0, 0); 86 | } 87 | 88 | // 89 | // lib call to open 90 | // 91 | int open(const char *pathname, int flags) { 92 | return do_user_call(SYS_user_open, (uint64)pathname, flags, 0, 0, 0, 0, 0); 93 | } 94 | 95 | // 96 | // lib call to read 97 | // 98 | int read_u(int fd, void * buf, uint64 count){ 99 | return do_user_call(SYS_user_read, fd, (uint64)buf, count, 0, 0, 0, 0); 100 | } 101 | 102 | // 103 | // lib call to write 104 | // 105 | int write_u(int fd, void *buf, uint64 count) { 106 | return do_user_call(SYS_user_write, fd, (uint64)buf, count, 0, 0, 0, 0); 107 | } 108 | 109 | // 110 | // lib call to seek 111 | // 112 | int lseek_u(int fd, int offset, int whence) { 113 | return do_user_call(SYS_user_lseek, fd, offset, whence, 0, 0, 0, 0); 114 | } 115 | 116 | // 117 | // lib call to read file information 118 | // 119 | int stat_u(int fd, struct istat *istat) { 120 | return do_user_call(SYS_user_stat, fd, (uint64)istat, 0, 0, 0, 0, 0); 121 | } 122 | 123 | // 124 | // lib call to read file information from disk 125 | // 126 | int disk_stat_u(int fd, struct istat *istat) { 127 | return do_user_call(SYS_user_disk_stat, fd, (uint64)istat, 0, 0, 0, 0, 0); 128 | } 129 | 130 | // 131 | // lib call to open dir 132 | // 133 | int opendir_u(const char *dirname) { 134 | return do_user_call(SYS_user_opendir, (uint64)dirname, 0, 0, 0, 0, 0, 0); 135 | } 136 | 137 | // 138 | // lib call to read dir 139 | // 140 | int readdir_u(int fd, struct dir *dir) { 141 | return do_user_call(SYS_user_readdir, fd, (uint64)dir, 0, 0, 0, 0, 0); 142 | } 143 | 144 | // 145 | // lib call to make dir 146 | // 147 | int mkdir_u(const char *pathname) { 148 | return do_user_call(SYS_user_mkdir, (uint64)pathname, 0, 0, 0, 0, 0, 0); 149 | } 150 | 151 | // 152 | // lib call to close dir 153 | // 154 | int closedir_u(int fd) { 155 | return do_user_call(SYS_user_closedir, fd, 0, 0, 0, 0, 0, 0); 156 | } 157 | 158 | // 159 | // lib call to link 160 | // 161 | int link_u(const char *fn1, const char *fn2){ 162 | return do_user_call(SYS_user_link, (uint64)fn1, (uint64)fn2, 0, 0, 0, 0, 0); 163 | } 164 | 165 | // 166 | // lib call to unlink 167 | // 168 | int unlink_u(const char *fn){ 169 | return do_user_call(SYS_user_unlink, (uint64)fn, 0, 0, 0, 0, 0, 0); 170 | } 171 | 172 | // 173 | // lib call to close 174 | // 175 | int close(int fd) { 176 | return do_user_call(SYS_user_close, fd, 0, 0, 0, 0, 0, 0); 177 | } 178 | -------------------------------------------------------------------------------- /user/user_lib.h: -------------------------------------------------------------------------------- 1 | /* 2 | * header file to be used by applications. 3 | */ 4 | 5 | #ifndef _USER_LIB_H_ 6 | #define _USER_LIB_H_ 7 | #include "util/types.h" 8 | #include "kernel/proc_file.h" 9 | 10 | int printu(const char *s, ...); 11 | int exit(int code); 12 | void* naive_malloc(); 13 | void naive_free(void* va); 14 | int fork(); 15 | void yield(); 16 | int wait(int pid); 17 | int exec(const char *fn, const char *para); 18 | 19 | // added @ lab4_1 20 | int open(const char *pathname, int flags); 21 | int read_u(int fd, void *buf, uint64 count); 22 | int write_u(int fd, void *buf, uint64 count); 23 | int lseek_u(int fd, int offset, int whence); 24 | int stat_u(int fd, struct istat *istat); 25 | int disk_stat_u(int fd, struct istat *istat); 26 | int close(int fd); 27 | 28 | // added @ lab4_2 29 | int opendir_u(const char *pathname); 30 | int readdir_u(int fd, struct dir *dir); 31 | int mkdir_u(const char *pathname); 32 | int closedir_u(int fd); 33 | 34 | // added @ lab4_3 35 | int link_u(const char *fn1, const char *fn2); 36 | int unlink_u(const char *fn); 37 | 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /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/hash_table.c: -------------------------------------------------------------------------------- 1 | #include "util/hash_table.h" 2 | #include "util/types.h" 3 | #include "kernel/pmm.h" 4 | 5 | static int default_equal(void *key1, void *key2) { return key1 == key2; } 6 | 7 | static int default_put(struct hash_table *hash_table, void *key, void *value) { 8 | struct hash_node *node = (struct hash_node *)alloc_page(); 9 | if (hash_table->virtual_hash_get(hash_table, key) != NULL) return -1; 10 | node->key = key; 11 | node->value = value; 12 | 13 | size_t index = hash_table->virtual_hash_func(key); 14 | struct hash_node *head = hash_table->head + index; 15 | 16 | node->next = head->next; 17 | head->next = node; 18 | return 0; 19 | } 20 | 21 | static void *defalut_get(struct hash_table *hash_table, void *key) { 22 | size_t index = hash_table->virtual_hash_func(key); 23 | struct hash_node *head = hash_table->head + index; 24 | struct hash_node *node = head->next; 25 | while (node) { 26 | if (hash_table->virtual_hash_equal(node->key, key)) return node->value; 27 | node = node->next; 28 | } 29 | return NULL; 30 | } 31 | 32 | static int default_erase(struct hash_table *hash_table, void *key) { 33 | size_t index = hash_table->virtual_hash_func(key); 34 | struct hash_node *head = hash_table->head + index; 35 | while (head->next && !hash_table->virtual_hash_equal(head->next->key, key)) 36 | head = head->next; 37 | if (head->next) { 38 | struct hash_node *node = head->next; 39 | head->next = node->next; 40 | free_page(node); 41 | return 0; 42 | } else 43 | return -1; 44 | } 45 | 46 | int hash_table_init(struct hash_table *list, 47 | int (*equal)(void *key1, void *key2), 48 | size_t (*func)(void *key), 49 | int (*put)(struct hash_table *hash_table, void *key, void *value), 50 | void *(*get)(struct hash_table *hash_table, void *key), 51 | int (*erase)(struct hash_table *hash_table, void *key)) { 52 | for (int i = 0; i < HASH_TABLE_SIZE; i++) list->head[i].next = NULL; 53 | if (func == NULL) return -1; 54 | list->virtual_hash_func = func; 55 | list->virtual_hash_equal = equal ? equal : default_equal; 56 | list->virtual_hash_put = put ? put : default_put; 57 | list->virtual_hash_get = get ? get : defalut_get; 58 | list->virtual_hash_erase = erase ? erase : default_erase; 59 | return 0; 60 | } -------------------------------------------------------------------------------- /util/hash_table.h: -------------------------------------------------------------------------------- 1 | #ifndef _HASH_TABLE_H 2 | #define _HASH_TABLE_H 3 | #include "util/types.h" 4 | 5 | #define HASH_TABLE_SIZE 128 6 | 7 | struct hash_node { 8 | struct hash_node *next; 9 | void *key; 10 | void *value; 11 | }; 12 | 13 | // this is a generic hash linked table for KERNEL SPACE 14 | struct hash_table { 15 | struct hash_node head[HASH_TABLE_SIZE]; 16 | int (*virtual_hash_equal)(void *key1, void *key2); 17 | size_t (*virtual_hash_func)(void *key); 18 | int (*virtual_hash_put)(struct hash_table *hash_table, void *key, void *value); 19 | void *(*virtual_hash_get)(struct hash_table *hash_table, void *key); 20 | int (*virtual_hash_erase)(struct hash_table *hash_table, void *key); 21 | }; 22 | 23 | int hash_table_init(struct hash_table *list, int (*virtual_hash_equal)(void *key1, void *key2), 24 | size_t (*virtual_hash_func)(void *key), 25 | int (*virtual_hash_put)(struct hash_table *hash_table, void *key, void *value), 26 | void *(*virtual_hash_get)(struct hash_table *hash_table, void *key), 27 | int (*virtual_hash_erase)(struct hash_table *hash_table, void *key)); 28 | 29 | #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 "string.h" 4 | 5 | #include 6 | #include 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 | char *strchr(const char *p, int ch) 66 | { 67 | char c; 68 | c = ch; 69 | for (;; ++p) { 70 | if (*p == c) 71 | return ((char *)p); 72 | if (*p == '\0') 73 | return (NULL); 74 | } 75 | } 76 | 77 | char* strtok(char* str, const char* delim) { 78 | static char* current; 79 | if (str != NULL) current = str; 80 | if (current == NULL) return NULL; 81 | 82 | char* start = current; 83 | while (*start != '\0' && strchr(delim, *start) != NULL) start++; 84 | 85 | if (*start == '\0') { 86 | current = NULL; 87 | return current; 88 | } 89 | 90 | char* end = start; 91 | while (*end != '\0' && strchr(delim, *end) == NULL) end++; 92 | 93 | if (*end != '\0') { 94 | *end = '\0'; 95 | current = end + 1; 96 | } else 97 | current = NULL; 98 | return start; 99 | } 100 | 101 | char *strcat(char *dst, const char *src) { 102 | strcpy(dst + strlen(dst), src); 103 | return dst; 104 | } 105 | 106 | long atol(const char* str) { 107 | long res = 0; 108 | int sign = 0; 109 | 110 | while (*str == ' ') str++; 111 | 112 | if (*str == '-' || *str == '+') { 113 | sign = *str == '-'; 114 | str++; 115 | } 116 | 117 | while (*str) { 118 | res *= 10; 119 | res += *str++ - '0'; 120 | } 121 | 122 | return sign ? -res : res; 123 | } 124 | 125 | void* memmove(void* dst, const void* src, size_t n) { 126 | const char* s; 127 | char* d; 128 | 129 | s = src; 130 | d = dst; 131 | if (s < d && s + n > d) { 132 | s += n; 133 | d += n; 134 | while (n-- > 0) *--d = *--s; 135 | } else 136 | while (n-- > 0) *d++ = *s++; 137 | 138 | return dst; 139 | } 140 | 141 | // Like strncpy but guaranteed to NUL-terminate. 142 | char* safestrcpy(char* s, const char* t, int n) { 143 | char* os; 144 | 145 | os = s; 146 | if (n <= 0) return os; 147 | while (--n > 0 && (*s++ = *t++) != 0) 148 | ; 149 | *s = 0; 150 | return os; 151 | } 152 | -------------------------------------------------------------------------------- /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 | char *strchr(const char *p, int ch); 12 | char *strtok(char* str, const char* delim); 13 | char *strcat(char *dst, const char *src); 14 | long atol(const char* str); 15 | void *memmove(void* dst, const void* src, size_t n); 16 | char *safestrcpy(char* s, const char* t, int n); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /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 | 20 | #define NULL ((void *)0) 21 | #define TRUE 1 22 | #define FALSE 0 23 | 24 | #define LSEEK_SET 0 25 | #define LSEEK_CUR 1 26 | 27 | #define MOUNT_DEFAULT 0 28 | #define MOUNT_AS_ROOT 1 29 | 30 | #define FILE_I 0 31 | #define DIR_I 1 32 | 33 | #define MASK_FILEMODE 0x003 34 | 35 | #define O_RDONLY 00 // read-only access 36 | #define O_WRONLY 01 // write-only access 37 | #define O_RDWR 02 // read-write 38 | #define O_CREAT 0100 // create 39 | 40 | #define FD_NONE 0 41 | #define FD_OPENED 1 42 | 43 | #define MAX_FILE_NAME_LEN 32 44 | 45 | struct dir { 46 | char name[MAX_FILE_NAME_LEN]; 47 | int inum; 48 | }; 49 | 50 | struct istat { 51 | int st_inum; 52 | int st_size; 53 | int st_type; 54 | int st_nlinks; 55 | int st_blocks; 56 | }; 57 | 58 | #endif 59 | --------------------------------------------------------------------------------