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