Showing preview only (867K chars total). Download the full file or copy to clipboard to get everything.
Repository: sgmarz/osblog
Branch: master
Commit: 76715da537e8
Files: 198
Total size: 811.8 KB
Directory structure:
gitextract_71jihpoi/
├── LICENSE
├── README.md
├── assembly/
│ ├── .gitignore
│ ├── intrin.S
│ └── intrin.c
└── risc_v/
├── .cargo/
│ └── config
├── .gitignore
├── BUILD.md
├── Cargo.toml
├── chapters/
│ ├── ch0/
│ │ ├── .build.config
│ │ ├── .cargo/
│ │ │ └── config
│ │ ├── Cargo.toml
│ │ ├── Makefile
│ │ ├── do.sh
│ │ └── virt.lds
│ ├── ch1/
│ │ ├── .cargo/
│ │ │ └── config
│ │ ├── .gitignore
│ │ ├── Cargo.toml
│ │ ├── Makefile
│ │ ├── make_hdd.sh
│ │ └── src/
│ │ ├── asm/
│ │ │ ├── boot.S
│ │ │ └── trap.S
│ │ ├── lds/
│ │ │ └── virt.lds
│ │ └── lib.rs
│ ├── ch2/
│ │ ├── .cargo/
│ │ │ └── config
│ │ ├── .gitignore
│ │ ├── Cargo.toml
│ │ ├── Makefile
│ │ ├── make_hdd.sh
│ │ └── src/
│ │ ├── asm/
│ │ │ ├── boot.S
│ │ │ └── trap.S
│ │ ├── lds/
│ │ │ └── virt.lds
│ │ ├── lib.rs
│ │ └── uart.rs
│ ├── ch3/
│ │ ├── .cargo/
│ │ │ └── config
│ │ ├── .gitignore
│ │ ├── Cargo.toml
│ │ ├── Makefile
│ │ ├── make_hdd.sh
│ │ └── src/
│ │ ├── asm/
│ │ │ ├── boot.S
│ │ │ ├── mem.S
│ │ │ └── trap.S
│ │ ├── kmem.rs
│ │ ├── lds/
│ │ │ └── virt.lds
│ │ ├── lib.rs
│ │ ├── page.rs
│ │ └── uart.rs
│ ├── ch4/
│ │ ├── .cargo/
│ │ │ └── config
│ │ ├── .gitignore
│ │ ├── Cargo.toml
│ │ ├── Makefile
│ │ ├── make_hdd.sh
│ │ └── src/
│ │ ├── asm/
│ │ │ ├── boot.S
│ │ │ ├── mem.S
│ │ │ └── trap.S
│ │ ├── cpu.rs
│ │ ├── kmem.rs
│ │ ├── lds/
│ │ │ └── virt.lds
│ │ ├── lib.rs
│ │ ├── page.rs
│ │ ├── trap.rs
│ │ └── uart.rs
│ ├── ch5/
│ │ ├── .cargo/
│ │ │ └── config
│ │ ├── .gitignore
│ │ ├── Cargo.toml
│ │ ├── Makefile
│ │ ├── make_hdd.sh
│ │ └── src/
│ │ ├── asm/
│ │ │ ├── boot.S
│ │ │ ├── mem.S
│ │ │ └── trap.S
│ │ ├── cpu.rs
│ │ ├── kmem.rs
│ │ ├── lds/
│ │ │ └── virt.lds
│ │ ├── lib.rs
│ │ ├── page.rs
│ │ ├── plic.rs
│ │ ├── trap.rs
│ │ └── uart.rs
│ ├── ch6/
│ │ ├── .cargo/
│ │ │ └── config
│ │ ├── .gitignore
│ │ ├── Cargo.toml
│ │ ├── Makefile
│ │ ├── make_hdd.sh
│ │ └── src/
│ │ ├── asm/
│ │ │ ├── boot.S
│ │ │ ├── mem.S
│ │ │ └── trap.S
│ │ ├── cpu.rs
│ │ ├── kmem.rs
│ │ ├── lds/
│ │ │ └── virt.lds
│ │ ├── lib.rs
│ │ ├── page.rs
│ │ ├── plic.rs
│ │ ├── process.rs
│ │ ├── trap.rs
│ │ └── uart.rs
│ ├── ch7/
│ │ ├── .cargo/
│ │ │ └── config
│ │ ├── .gitignore
│ │ ├── Cargo.toml
│ │ ├── Makefile
│ │ ├── make_hdd.sh
│ │ └── src/
│ │ ├── asm/
│ │ │ ├── boot.S
│ │ │ ├── mem.S
│ │ │ └── trap.S
│ │ ├── cpu.rs
│ │ ├── kmem.rs
│ │ ├── lds/
│ │ │ └── virt.lds
│ │ ├── lib.rs
│ │ ├── page.rs
│ │ ├── plic.rs
│ │ ├── process.rs
│ │ ├── sched.rs
│ │ ├── syscall.rs
│ │ ├── trap.rs
│ │ └── uart.rs
│ ├── ch8/
│ │ ├── .cargo/
│ │ │ └── config
│ │ ├── .gitignore
│ │ ├── Cargo.toml
│ │ ├── Makefile
│ │ ├── make_hdd.sh
│ │ └── src/
│ │ ├── asm/
│ │ │ ├── boot.S
│ │ │ ├── mem.S
│ │ │ └── trap.S
│ │ ├── cpu.rs
│ │ ├── kmem.rs
│ │ ├── lds/
│ │ │ └── virt.lds
│ │ ├── lib.rs
│ │ ├── page.rs
│ │ ├── plic.rs
│ │ ├── process.rs
│ │ ├── sched.rs
│ │ ├── syscall.rs
│ │ ├── trap.rs
│ │ └── uart.rs
│ └── ch9/
│ ├── .cargo/
│ │ └── config
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── Makefile
│ ├── make_hdd.sh
│ └── src/
│ ├── asm/
│ │ ├── boot.S
│ │ ├── mem.S
│ │ └── trap.S
│ ├── block.rs
│ ├── cpu.rs
│ ├── kmem.rs
│ ├── lds/
│ │ └── virt.lds
│ ├── lib.rs
│ ├── page.rs
│ ├── plic.rs
│ ├── process.rs
│ ├── rng.rs
│ ├── sched.rs
│ ├── syscall.rs
│ ├── trap.rs
│ ├── uart.rs
│ └── virtio.rs
├── src/
│ ├── asm/
│ │ ├── boot.S
│ │ ├── mem.S
│ │ └── trap.S
│ ├── assembly.rs
│ ├── block.rs
│ ├── buffer.rs
│ ├── console.rs
│ ├── cpu.rs
│ ├── elf.rs
│ ├── fs.rs
│ ├── gpu.rs
│ ├── input.rs
│ ├── kmem.rs
│ ├── lds/
│ │ └── virt.lds
│ ├── lock.rs
│ ├── main.rs
│ ├── page.rs
│ ├── plic.rs
│ ├── process.rs
│ ├── rng.rs
│ ├── sched.rs
│ ├── syscall.rs
│ ├── test.rs
│ ├── trap.rs
│ ├── uart.rs
│ ├── vfs.rs
│ └── virtio.rs
└── userspace/
├── .gitignore
├── Makefile
├── fb.cpp
├── helloworld.cpp
├── input-event-codes.h
├── shell.cpp
├── sleepy.cpp
├── startlib/
│ ├── .gitignore
│ ├── Makefile
│ ├── linker.lds
│ ├── printf.cpp
│ ├── printf.h
│ ├── start.S
│ ├── syscall.S
│ └── syscall.h
└── upload.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2019 Stephen Marz
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.
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.
================================================
FILE: README.md
================================================
# osblog
The Adventures of OS
# RISC-V OS in Rust
- risc_v/src - contains RISC-V OS in Rust
- risc_v/src/asm - contains assembly portions
- risc_v/userspace - contains C++ userspace programs
================================================
FILE: assembly/.gitignore
================================================
*
!*.S
!*.c
!.gitignore
!Makefile
================================================
FILE: assembly/intrin.S
================================================
.intel_syntax noprefix
.section .text
.global calc_asm
calc_asm:
# rdi rsi rdx rcx
movupd xmm0, [rsi + 0]
movupd xmm1, [rsi + 16]
movupd xmm2, [rsi + 32]
movupd xmm3, [rsi + 48]
movupd xmm4, [rdx]
mulps xmm0, xmm4
mulps xmm1, xmm4
mulps xmm2, xmm4
mulps xmm3, xmm4
haddps xmm0, xmm0
haddps xmm0, xmm0
haddps xmm1, xmm1
haddps xmm1, xmm1
haddps xmm2, xmm2
haddps xmm2, xmm2
haddps xmm3, xmm3
haddps xmm3, xmm3
movss [rdi + 0], xmm0
movss [rdi + 4], xmm1
movss [rdi + 8], xmm2
movss [rdi + 12], xmm3
ret
================================================
FILE: assembly/intrin.c
================================================
#include <stdio.h>
#include <pmmintrin.h>
void calc_intrin(float result[], float matrix[], float vector[]);
void calc_asm(float result[], float matrix[], float vector[]);
int main() {
int row, col;
float vec[] = {1.0, 10.0, 100.0, 1000.0};
float mat[] = {2.0, 0.0, 0.0, 0.0,
0.0, 2.2, 0.0, 0.0,
0.0, 0.0, 22.2, 0.0,
0.0, 0.0, 0.0, 22.22};
float result[4];
calc_intrin(result, mat, vec);
printf("%5.3f %5.3f %5.3f %5.3f\n", result[0], result[1], result[2], result[3]);
calc_asm(result, mat, vec);
printf("%5.3f %5.3f %5.3f %5.3f\n", result[0], result[1], result[2], result[3]);
return 0;
}
void calc_intrin(float result[], float matrix[], float vector[])
{
int row;
__m128 vec = _mm_loadu_ps(vector);
for (row = 0;row < 4;row++) {
__m128 rowvec = _mm_loadu_ps(&matrix[row * 4]);
__m128 rowvec2 = _mm_mul_ps(vec, rowvec);
__m128 rowvec3 = _mm_hadd_ps(rowvec2, rowvec2);
__m128 rowvec4 = _mm_hadd_ps(rowvec3, rowvec3);
_mm_store_ss(&result[row], rowvec4);
}
}
================================================
FILE: risc_v/.cargo/config
================================================
[build]
target = "riscv64gc-unknown-none-elf"
rustflags = ['-Clink-arg=-Tsrc/lds/virt.lds']
[target.riscv64gc-unknown-none-elf]
runner = "qemu-system-riscv64 -machine virt -cpu rv64 -d guest_errors,unimp -smp 4 -m 128M -drive if=none,format=raw,file=hdd.dsk,id=foo -device virtio-blk-device,scsi=off,drive=foo -serial mon:stdio -bios none -device virtio-rng-device -device virtio-gpu-device -device virtio-net-device -device virtio-tablet-device -device virtio-keyboard-device -kernel "
================================================
FILE: risc_v/.gitignore
================================================
os.elf
target/*
Cargo.lock
hdd.dsk
================================================
FILE: risc_v/BUILD.md
================================================
# PREREQS
You will need to install the riscv64gc target using rustup as well as cargo-binutils using cargo.
* rustup target add riscv64gc-unknown-none-elf
* cargo install cargo-binutils
# BUILDING
Edit .cargo/config to match your host's configuration. The runner will execute when you type `cargo run`.
Type `cargo build` to start the build process.
Type `cargo run` to run using the runner provided in .cargo/config
# RELEASE BUILDS
Release builds turn on the optimizer and make it run much quicker. To run release builds, add `--release` as a parameter to cargo:
* cargo build --release
* cargo run --release
# HARD DRIVE FILE
To run this as I have it configured, you'll need a hard drive file called hdd.dsk in this directory. You can create an empty
one by typing the following.
* fallocate -l 32M hdd.dsk
================================================
FILE: risc_v/Cargo.toml
================================================
[package]
name = "sos"
version = "0.1.0"
authors = ["Stephen Marz <stephen.marz@utk.edu>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[profile.dev]
opt-level = 0
lto = false
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
[dependencies]
================================================
FILE: risc_v/chapters/ch0/.build.config
================================================
USE_SUDO="sudo"
JOBS="10"
LINUX_VER="git"
BUILD_ROOT="${PWD}"
TOOLCHAIN_ROOT=""
BUILD_BINUTILS="${BUILD_ROOT}/build-binutils"
BUILD_GCC_S1="${BUILD_ROOT}/build-gcc-s1"
BUILD_GCC_S2="${BUILD_ROOT}/build-gcc-s2"
BUILD_GLIBC_S1="${BUILD_ROOT}/build-glibc-s1"
BUILD_GLIBC_S2="${BUILD_ROOT}/build-glibc-s2"
BUILD_QEMU="${BUILD_ROOT}/build-qemu"
ARCH="riscv"
BITS="64"
TAG="_1"
ABI="lp64"
ISA="rv64g"
TARGET="${ARCH}${BITS}-unknown-linux-gnu"
LIB_HEADER="linux.h"
BUILD_LINUX_ARCH=$ARCH
BUILD_LINUX="${BUILD_ROOT}/linux-${LINUX_VER}"
BUILD_LINUX_HEADERS="${BUILD_ROOT}/build-${TARGET}-linux-headers"
PREFIX="${TOOLCHAIN_ROOT}/opt/${ARCH}${BITS}${TAG}"
SYSROOT="${PREFIX}/sysroot"
PATH="${PREFIX}/bin:${PATH}"
================================================
FILE: risc_v/chapters/ch0/.cargo/config
================================================
[build]
target = "riscv64gc-unknown-none-elf"
[target.riscv64gc-unknown-none-elf]
linker = "riscv64-unknown-linux-gnu-gcc"
================================================
FILE: risc_v/chapters/ch0/Cargo.toml
================================================
[package]
name = "sos"
version = "0.1.0"
authors = ["Stephen Marz <stephen.marz@utk.edu>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["staticlib"]
[dependencies]
================================================
FILE: risc_v/chapters/ch0/Makefile
================================================
#####
## BUILD
#####
CC=riscv64-unknown-linux-gnu-g++
CFLAGS=-Wall -Wextra -pedantic -Wextra -O0 -g -std=c++17
CFLAGS+=-static -ffreestanding -nostdlib -fno-rtti -fno-exceptions
CFLAGS+=-march=rv64gc -mabi=lp64
INCLUDES=
LINKER_SCRIPT=-Tsrc/lds/virt.lds
TYPE=debug
RUST_TARGET=./target/riscv64gc-unknown-none-elf/$(TYPE)
LIBS=-L$(RUST_TARGET)
SOURCES_ASM=$(wildcard src/asm/*.S)
LIB=-lsos -lgcc
OUT=os.elf
#####
## QEMU
#####
QEMU=qemu-system-riscv64
MACH=virt
CPU=rv64
CPUS=4
MEM=128M
DRIVE=hdd.dsk
all:
cargo build
$(CC) $(CFLAGS) $(LINKER_SCRIPT) $(INCLUDES) -o $(OUT) $(SOURCES_ASM) $(LIBS) $(LIB)
run: all
$(QEMU) -machine $(MACH) -cpu $(CPU) -smp $(CPUS) -m $(MEM) -nographic -serial mon:stdio -bios none -kernel $(OUT) -drive if=none,format=raw,file=$(DRIVE),id=foo -device virtio-blk-device,scsi=off,drive=foo
.PHONY: clean
clean:
cargo clean
rm -f $(OUT)
================================================
FILE: risc_v/chapters/ch0/do.sh
================================================
#!/bin/bash
# For building cross compilers
# Use this at your own risk!
# I make no warranties or guarantees with this script!
# Stephen Marz
# 15 Jan 2018
. ./.build.config
if [ $# -eq 0 ]; then
echo "Must provide a number"
echo "0 - Binutils"
echo "1 - GCC Stage 1"
echo "2 - Linux Headers"
echo "3 - GLIBC Headers"
echo "4 - GLIBC"
echo "5 - GCC Stage 2"
echo "6 - QEMU"
echo "7 - Libs and Links"
echo "Add 90 if you just want to build that one stage"
echo "99 - Clean"
exit 99
else
ARG=$1
fi
#Build BINUTILS
if [ $ARG -le 0 -o $ARG -eq 90 ]; then
echo "+-+-+-+ BINUTILS +-+-+-+"
mkdir -p ${BUILD_BINUTILS}
cd ${BUILD_BINUTILS}
${BUILD_ROOT}/binutils-gdb/configure --target=${TARGET} --prefix=${PREFIX} --with-sysroot=${SYSROOT} --disable-multilib --disable-werror --disable-nls --with-expat=yes --enable-gdb > ${BUILD_ROOT}/binutils.log 2>&1
if [ $? -ne 0 ]; then
echo "Error configuring BINUTILS"
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~"
cat ${BUILD_ROOT}/binutils.log
exit 1
fi
cd ${BUILD_ROOT}
make -C ${BUILD_BINUTILS} -j${JOBS} >> ${BUILD_ROOT}/binutils.log 2>&1
if [ $? -ne 0 ]; then
echo "Error building BINUTILS"
echo "~~~~~~~~~~~~~~~~~~~~~~~"
cat ${BUILD_ROOT}/binutils.log
exit 1
fi
${USE_SUDO} make -C ${BUILD_BINUTILS} install >> ${BUILD_ROOT}/binutils.log 2>&1
if [ $? -ne 0 ]; then
echo "Error installing BINUTILS"
echo "~~~~~~~~~~~~~~~~~~~~~~~~~"
cat ${BUILD_ROOT}/binutils.log
exit 1
fi
fi
#Build GCC Stage 1
if [ $ARG -le 1 -o $ARG -eq 91 ]; then
echo "+-+-+-+ GCC STAGE 1 +-+-+-+"
sed -i "s|\"/lib/ld-linux-${ARCH}|\"${SYSROOT}/lib/ld-linux-${ARCH}|" ${BUILD_ROOT}/gcc/gcc/config/${ARCH}/${LIB_HEADER}
mkdir -p ${BUILD_GCC_S1}
cd ${BUILD_GCC_S1}
${BUILD_ROOT}/gcc/configure --target=${TARGET} --prefix=${PREFIX} --with-sysroot=${SYSROOT} --with-newlib --without-headers --disable-shared --disable-threads --with-system-zlib --enable-tls --enable-languages=c --disable-libatomic --disable-libmudflap --disable-libssp --disable-libquadmath --disable-libgomp --disable-nls --disable-bootstrap --enable-checking=yes --disable-multilib --with-abi=${ABI} --with-arch=${ISA} > ${BUILD_ROOT}/gccs1.log 2>&1
if [ $? -ne 0 ]; then
echo "Error configuring GCC stage 1"
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
cat ${BUILD_ROOT}/gccs1.log
exit 2
fi
cd ${BUILD_ROOT}
make -j${JOBS} -C ${BUILD_GCC_S1} >> ${BUILD_ROOT}/gccs1.log 2>&1
if [ $? -ne 0 ]; then
echo "Error building GCC stage 1"
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~"
cat ${BUILD_ROOT}/gccs1.log
exit 2
fi
${USE_SUDO} make -C ${BUILD_GCC_S1} install >> ${BUILD_ROOT}/gccs1.log 2>&1
if [ $? -ne 0 ]; then
echo "Error installing GCC stage 1"
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
cat ${BUILD_ROOT}/gccs1.log
exit 2
fi
fi
#Build Linux Headers
if [ $ARG -le 2 -o $ARG -eq 92 ]; then
echo "+-+-+-+ LINUX HEADERS +-+-+-+"
if [ ! -x ${BUILD_ROOT}/linux-${LINUX_VER} ]; then
tar xf ${BUILD_ROOT}/linux-${LINUX_VER}.tar.xz -C ${BUILD_ROOT} > ${BUILD_ROOT}/linhdr.log 2>&1
fi
if [ $? -ne 0 ]; then
echo "Error unpacking Linux Headers"
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
cat ${BUILD_ROOT}/linhdr.log
exit 3
fi
make ARCH=${BUILD_LINUX_ARCH} INSTALL_HDR_PATH=${BUILD_LINUX_HEADERS} -C ${BUILD_LINUX} defconfig >> ${BUILD_ROOT}/linhdr.log 2>&1
if [ $? -ne 0 ]; then
echo "Error configuring Linux Headers"
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
cat ${BUILD_ROOT}/linhdr.log
exit 3
fi
make ARCH=${BUILD_LINUX_ARCH} INSTALL_HDR_PATH=${BUILD_LINUX_HEADERS} -C ${BUILD_LINUX} headers_install >> ${BUILD_ROOT}/linhdr.log 2>&1
if [ $? -ne 0 ]; then
echo "Error installing Linux Headers"
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
cat ${BUILD_ROOT}/linhdr.log
exit 3
fi
fi
if [ $ARG -le 3 -o $ARG -eq 93 ]; then
#Build GLIBC Headers
echo "+-+-+-+ GLIBC HEADERS +-+-+-+"
mkdir -p ${BUILD_GLIBC_S1}
cd ${BUILD_GLIBC_S1}
${BUILD_ROOT}/glibc/configure --host=${TARGET} --prefix=${SYSROOT}/usr --enable-shared --with-headers=${BUILD_LINUX_HEADERS}/include --disable-multilib --enable-kernel=3.0.0 > ${BUILD_ROOT}/glibchdr.log 2>&1
if [ $? -ne 0 ]; then
echo "Error configuring GLIBC headers"
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
cat ${BUILD_ROOT}/glibchdr.log
exit 4
fi
cd ${BUILD_ROOT}
${USE_SUDO} make -C ${BUILD_GLIBC_S1} install-headers >> ${BUILD_ROOT}/glibchdr.log 2>&1
if [ $? -ne 0 ]; then
echo "Error installing GLIBC headers"
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
cat ${BUILD_ROOT}/glibchdr.log
exit 4
fi
${USE_SUDO} cp -a ${BUILD_LINUX_HEADERS}/include/* ${SYSROOT}/usr/include/ >> ${BUILD_ROOT}/glibchdr.log 2>&1
if [ $? -ne 0 ]; then
echo "Error copying include files"
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~"
cat ${BUILD_ROOT}/glibchdr.log
exit 4
fi
fi
if [ $ARG -le 4 -o $ARG -eq 94 ]; then
#Build GLIBC
echo "+-+-+-+ GLIBC +-+-+-+"
mkdir -p ${BUILD_GLIBC_S2}
cd ${BUILD_GLIBC_S2}
${BUILD_ROOT}/glibc/configure --host=${TARGET} --prefix=/usr --disable-werror --enable-tls --disable-nls --enable-shared --enable-obsolete-rpc --with-headers=${SYSROOT}/usr/include --disable-multilib --enable-kernel=3.0.0 > ${BUILD_ROOT}/glibc.log 2>&1
if [ $? -ne 0 ]; then
echo "Error configuring GLIBC"
echo "~~~~~~~~~~~~~~~~~~~~~~~"
cat ${BUILD_ROOT}/glibc.log
exit 5
fi
cd ${BUILD_ROOT}
make -C ${BUILD_GLIBC_S2} -j${JOBS} >> ${BUILD_ROOT}/glibc.log 2>&1
if [ $? -ne 0 ]; then
echo "Error building GLIBC"
echo "~~~~~~~~~~~~~~~~~~~~"
cat ${BUILD_ROOT}/glibc.log
exit 5
fi
${USE_SUDO} make -C ${BUILD_GLIBC_S2} install install_root=${SYSROOT} >> ${BUILD_ROOT}/glibc.log 2>&1
if [ $? -ne 0 ]; then
echo "Error installing GLIBC"
echo "~~~~~~~~~~~~~~~~~~~~~~"
cat ${BUILD_ROOT}/glibc.log
exit 5
fi
${USE_SUDO} ln -s ${SYSROOT}/lib64 ${SYSROOT}/lib
fi
if [ $ARG -le 5 -o $ARG -eq 95 ]; then
#Build GCC Stage 2
echo "+-+-+-+ GCC STAGE 2 +-+-+-+"
mkdir -p ${BUILD_GCC_S2}
cd ${BUILD_GCC_S2}
${BUILD_ROOT}/gcc/configure --target=${TARGET} --prefix=${PREFIX} --with-sysroot=${SYSROOT} --with-system-zlib --enable-shared --enable-tls --enable-languages=c,c++ --disable-libmudflap --disable-libssp --disable-libquadmath --disable-nls --disable-bootstrap --disable-multilib --enable-checking=yes --with-abi=${ABI} > ${BUILD_ROOT}/gccs2.log 2>&1
if [ $? -ne 0 ]; then
echo "Error configuring GCC stage 2"
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
cat ${BUILD_ROOT}/gccs2.log
exit 6
fi
cd ${BUILD_ROOT}
make -C ${BUILD_GCC_S2} -j${JOBS} >> ${BUILD_ROOT}/gccs2.log 2>&1
if [ $? -ne 0 ]; then
echo "Error building GCC stage 2"
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~"
cat ${BUILD_ROOT}/gccs2.log
exit 6
fi
${USE_SUDO} make -C ${BUILD_GCC_S2} install >> ${BUILD_ROOT}/gccs2.log 2>&1
if [ $? -ne 0 ]; then
echo "Error installing GCC stage 2"
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
cat ${BUILD_ROOT}/gccs2.log
exit 6
fi
${USE_SUDO} cp -a ${PREFIX}/${TARGET}/lib* ${SYSROOT}
if [ $? -ne 0 ]; then
echo "Error copying libraries"
echo "~~~~~~~~~~~~~~~~~~~~~~~"
exit 6
fi
fi
if [ $ARG -le 6 -o $ARG -eq 96 ]; then
#Build QEMU
echo "+-+-+-+ QEMU +-+-+-+"
mkdir -p ${BUILD_QEMU}
cd ${BUILD_QEMU}
${BUILD_ROOT}/qemu/configure --prefix=${PREFIX} --interp-prefix=${SYSROOT} --target-list=riscv32-linux-user,riscv32-softmmu,${ARCH}${BITS}-linux-user,${ARCH}${BITS}-softmmu --enable-jemalloc --disable-werror > ${BUILD_ROOT}/qemu.log 2>&1
if [ $? -ne 0 ]; then
echo "Error configuring QEMU"
echo "~~~~~~~~~~~~~~~~~~~~~~"
cat ${BUILD_ROOT}/qemu.log
exit 7
fi
cd ${BUILD_ROOT}
make -C ${BUILD_QEMU} -j${JOBS} >> ${BUILD_ROOT}/qemu.log 2>&1
if [ $? -ne 0 ]; then
echo "Error building QEMU"
echo "~~~~~~~~~~~~~~~~~~~"
cat ${BUILD_ROOT}/qemu.log
exit 7
fi
${USE_SUDO} make -C ${BUILD_QEMU} install >> ${BUILD_ROOT}/qemu.log 2>&1
if [ $? -ne 0 ]; then
echo "Error installing QEMU"
echo "~~~~~~~~~~~~~~~~~~~~~"
cat ${BUILD_ROOT}/qemu.log
exit 7
fi
fi
if [ $ARG -le 7 -o $ARG -eq 97 ]; then
#Make Symlinks
echo "+-+-+-+ SYMLINKS +-+-+-+"
${USE_SUDO} ln -s ${PREFIX}/bin/${TARGET}-gcc ${PREFIX}/bin/${ARCH}${BITS}-gcc
${USE_SUDO} ln -s ${PREFIX}/bin/${TARGET}-g++ ${PREFIX}/bin/${ARCH}${BITS}-g++
${USE_SUDO} ln -s ${PREFIX}/bin/${TARGET}-objdump ${PREFIX}/bin/${ARCH}${BITS}-objdump
${USE_SUDO} ln -s ${PREFIX}/bin/${TARGET}-gdb ${PREFIX}/bin/${ARCH}${BITS}-gdb
#Copy Libraries
echo "+-+-+-+ COPY LIBRARIES +-+-+-+"
${USE_SUDO} cp -a ${SYSROOT}/lib/* ${SYSROOT}/usr/lib${BITS}/${ABI}/
fi
if [ $ARG -eq 99 ]; then
echo "+-+-+-+ CLEANING +-+-+-+"
${USE_SUDO} rm -fr ${BUILD_BINUTILS}
${USE_SUDO} rm -fr ${BUILD_GCC_S1}
${USE_SUDO} rm -fr ${BUILD_LINUX_HEADERS}
${USE_SUDO} rm -fr ${BUILD_GLIBC_S1}
${USE_SUDO} rm -fr ${BUILD_GLIBC_S2}
${USE_SUDO} rm -fr ${BUILD_GCC_S2}
${USE_SUDO} rm -fr ${BUILD_QEMU}
rm -fr *.log
fi
echo "+-+-+-+ !! DONE !! +-+-+-+"
================================================
FILE: risc_v/chapters/ch0/virt.lds
================================================
/*
virt.lds
Linker script for outputting to RISC-V QEMU "virt" machine.
Stephen Marz
6 October 2019
*/
/*
riscv is the name of the architecture that the linker understands
for any RISC-V target (64-bit or 32-bit).
We will further refine this by using -mabi=lp64 and -march=rv64gc
*/
OUTPUT_ARCH( "riscv" )
/*
We're setting our entry point to a symbol
called _start which is inside of boot.S. This
essentially stores the address of _start as the
"entry point", or where CPU instructions should start
executing.
In the rest of this script, we are going to place _start
right at the beginning of 0x8000_0000 because this is where
the virtual machine and many RISC-V boards will start executing.
*/
ENTRY( _start )
/*
The MEMORY section will explain that we have "ram" that contains
a section that is 'w' (writeable), 'x' (executable), and 'a' (allocatable).
We use '!' to invert 'r' (read-only) and 'i' (initialized). We don't want
our memory to be read-only, and we're stating that it is NOT initialized
at the beginning.
The ORIGIN is the memory address 0x8000_0000. If we look at the virt
spec or the specification for the RISC-V HiFive Unleashed, this is the
starting memory address for our code.
Side note: There might be other boot ROMs at different addresses, but
their job is to get to this point.
Finally LENGTH = 128M tells the linker that we have 128 megabyte of RAM.
The linker will double check this to make sure everything can fit.
The HiFive Unleashed has a lot more RAM than this, but for the virtual
machine, I went with 128M since I think that's enough RAM for now.
We can provide other pieces of memory, such as QSPI, or ROM, but we're
telling the linker script here that we have one pool of RAM.
*/
MEMORY
{
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M
}
/*
PHDRS is short for "program headers", which we specify three here:
text - CPU instructions (executable sections)
data - Global, initialized variables
bss - Global, uninitialized variables (all will be set to 0 by boot.S)
The command PT_LOAD tells the linker that these sections will be loaded
from the file into memory.
We can actually stuff all of these into a single program header, but by
splitting it up into three, we can actually use the other PT_* commands
such as PT_DYNAMIC, PT_INTERP, PT_NULL to tell the linker where to find
additional information.
However, for our purposes, every section will be loaded from the program
headers.
*/
PHDRS
{
text PT_LOAD;
data PT_LOAD;
bss PT_LOAD;
}
/*
We are now going to organize the memory based on which
section it is in. In assembly, we can change the section
with the ".section" directive. However, in C++ and Rust,
CPU instructions go into text, global constants go into
rodata, global initialized variables go into data, and
global uninitialized variables go into bss.
*/
SECTIONS
{
/*
The first part of our RAM layout will be the text section.
Since our CPU instructions are here, and our memory starts at
0x8000_0000, we need our entry point to line up here.
*/
.text : {
/*
PROVIDE allows me to access a symbol called _text_start so
I know where the text section starts in the operating system.
This should not move, but it is here for convenience.
The period '.' tells the linker to set _text_start to the
CURRENT location ('.' = current memory location). This current
memory location moves as we add things.
*/
PROVIDE(_text_start = .);
/*
We are going to layout all text sections here, starting with
.text.init. The asterisk in front of the parentheses means to match
the .text.init section of ANY object file. Otherwise, we can specify
which object file should contain the .text.init section, for example,
boot.o(.text.init) would specifically put the .text.init section of
our bootloader here.
Because we might want to change the name of our files, we'll leave it
with a *.
Inside the parentheses is the name of the section. I created my own
called .text.init to make 100% sure that the _start is put right at the
beginning. The linker will lay this out in the order it receives it:
.text.init first
all .text sections next
any .text.* sections last
.text.* means to match anything after .text. If we didn't already specify
.text.init, this would've matched here. The assembler and linker can place
things in "special" text sections, so we match any we might come across here.
*/
*(.text.init) *(.text .text.*)
/*
Again, with PROVIDE, we're providing a readable symbol called _text_end, which is
set to the memory address AFTER .text.init, .text, and .text.*'s have been added.
*/
PROVIDE(_text_end = .);
/*
The portion after the right brace is in an odd format. However, this is telling the
linker what memory portion to put it in. We labeled our RAM, ram, with the constraints
that it is writeable, allocatable, and executable. The linker will make sure with this
that we can do all of those things.
>ram - This just tells the linker script to put this entire section (.text) into the
ram region of memory. To my knowledge, the '>' does not mean "greater than". Instead,
it is a symbol to let the linker know we want to put this in ram.
AT>ram - This sets the LMA (load memory address) region to the same thing. LMA is the final
translation of a VMA (virtual memory address). With this linker script, we're loading
everything into its physical location. We'll let the kernel copy and sort out the
virtual memory. That's why >ram and AT>ram are continually the same thing.
:text - This tells the linker script to put this into the :text program header. We've only
defined three: text, data, and bss. In this case, we're telling the linker script
to go into the text section.
*/
} >ram AT>ram :text
/*
The global pointer allows the linker to position global variables and constants into
independent positions relative to the gp (global pointer) register. The globals start
after the text sections and are only relevant to the rodata, data, and bss sections.
*/
PROVIDE(_global_pointer = .);
/*
Most compilers create a rodata (read only data) section for global constants. However,
we're going to place ours in the text section. We can actually put this in :data, but
since the .text section is read-only, we can place it there.
NOTE: This doesn't actually do anything, yet. The actual "protection" cannot be done
at link time. Instead, when we program the memory management unit (MMU), we will be
able to choose which bits (R=read, W=write, X=execute) we want each memory segment
to be able to do.
*/
.rodata : {
PROVIDE(_rodata_start = .);
*(.rodata .rodata.*)
PROVIDE(_rodata_end = .);
/*
Again, we're placing the rodata section in the memory segment "ram" and we're putting
it in the :text program header. We don't have one for rodata anyway.
*/
} >ram AT>ram :text
.data : {
/*
. = ALIGN(4096) tells the linker to align the current memory location (which is
0x8000_0000 + text section + rodata section) to 4096 bytes. This is because our paging
system's resolution is 4,096 bytes or 4 KiB.
*/
. = ALIGN(4096);
PROVIDE(_data_start = .);
/*
sdata and data are essentially the same thing. However, compilers usually use the
sdata sections for shorter, quicker loading sections. So, usually critical data
is loaded there. However, we're loading all of this in one fell swoop.
So, we're looking to put all of the following sections under the umbrella .data:
.sdata
.sdata.[anything]
.data
.data.[anything]
...in that order.
*/
*(.sdata .sdata.*) *(.data .data.*)
PROVIDE(_data_end = .);
} >ram AT>ram :data
.bss : {
PROVIDE(_bss_start = .);
*(.sbss .sbss.*) *(.bss .bss.*)
PROVIDE(_bss_end = .);
} >ram AT>ram :bss
/*
The following will be helpful when we allocate the kernel stack (_stack) and
determine where the heap begnis and ends (_heap_start and _heap_start + _heap_size)/
When we do memory allocation, we can use these symbols.
We use the symbols instead of hard-coding an address because this is a floating target.
As we add code, the heap moves farther down the memory and gets shorter.
_memory_start will be set to 0x8000_0000 here. We use ORIGIN(ram) so that it will take
whatever we set the origin of ram to. Otherwise, we'd have to change it more than once
if we ever stray away from 0x8000_0000 as our entry point.
*/
PROVIDE(_memory_start = ORIGIN(ram));
/*
Our kernel stack starts at the end of the bss segment (_bss_end). However, we're allocating
0x80000 bytes (524 KiB) to our kernel stack. This should be PLENTY of space. The reason
we add the memory is because the stack grows from higher memory to lower memory (bottom to top).
Therefore we set the stack at the very bottom of its allocated slot.
When we go to allocate from the stack, we'll subtract the number of bytes we need.
*/
PROVIDE(_stack = _bss_end + 0x80000);
PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram));
/*
Finally, our heap starts right after the kernel stack. This heap will be used mainly
to dole out memory for user-space applications. However, in some circumstances, it will
be used for kernel memory as well.
We don't align here because we let the kernel determine how it wants to do this.
*/
PROVIDE(_heap_start = _stack);
PROVIDE(_heap_size = _memory_end - _stack);
}
================================================
FILE: risc_v/chapters/ch1/.cargo/config
================================================
[build]
target = "riscv64gc-unknown-none-elf"
[target.riscv64gc-unknown-none-elf]
linker = "riscv64-unknown-linux-gnu-gcc"
================================================
FILE: risc_v/chapters/ch1/.gitignore
================================================
os.elf
target/*
Cargo.lock
hdd.dsk
================================================
FILE: risc_v/chapters/ch1/Cargo.toml
================================================
[package]
name = "sos"
version = "0.1.0"
authors = ["Stephen Marz <stephen.marz@utk.edu>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["staticlib"]
[dependencies]
================================================
FILE: risc_v/chapters/ch1/Makefile
================================================
#####
## BUILD
#####
CC=riscv64-unknown-linux-gnu-g++
CFLAGS=-Wall -Wextra -pedantic -Wextra -O0 -g -std=c++17
CFLAGS+=-static -ffreestanding -nostdlib -fno-rtti -fno-exceptions
CFLAGS+=-march=rv64gc -mabi=lp64
INCLUDES=
LINKER_SCRIPT=-Tsrc/lds/virt.lds
TYPE=debug
RUST_TARGET=./target/riscv64gc-unknown-none-elf/$(TYPE)
LIBS=-L$(RUST_TARGET)
SOURCES_ASM=$(wildcard src/asm/*.S)
LIB=-lsos -lgcc
OUT=os.elf
#####
## QEMU
#####
QEMU=qemu-system-riscv64
MACH=virt
CPU=rv64
CPUS=4
MEM=128M
DRIVE=hdd.dsk
all:
cargo build
$(CC) $(CFLAGS) $(LINKER_SCRIPT) $(INCLUDES) -o $(OUT) $(SOURCES_ASM) $(LIBS) $(LIB)
run: all
$(QEMU) -machine $(MACH) -cpu $(CPU) -smp $(CPUS) -m $(MEM) -nographic -serial mon:stdio -bios none -kernel $(OUT) -drive if=none,format=raw,file=$(DRIVE),id=foo -device virtio-blk-device,scsi=off,drive=foo
.PHONY: clean
clean:
cargo clean
rm -f $(OUT)
================================================
FILE: risc_v/chapters/ch1/make_hdd.sh
================================================
#!/bin/sh
dd if=/dev/zero of=hdd.dsk bs=1M count=32
================================================
FILE: risc_v/chapters/ch1/src/asm/boot.S
================================================
# boot.S
# bootloader for SoS
# Stephen Marz
# 8 February 2019
# Disable generation of compressed instructions.
.option norvc
# Define a .data section.
.section .data
# Define a .text.init section.
.section .text.init
# Execution starts here.
.global _start
_start:
# Any hardware threads (hart) that are not bootstrapping
# need to wait for an IPI
csrr t0, mhartid
bnez t0, 3f
# SATP should be zero, but let's make sure
csrw satp, zero
# Disable linker instruction relaxation for the `la` instruction below.
# This disallows the assembler from assuming that `gp` is already initialized.
# This causes the value stored in `gp` to be calculated from `pc`.
.option push
.option norelax
la gp, _global_pointer
.option pop
# Set all bytes in the BSS section to zero.
la a0, _bss_start
la a1, _bss_end
bgeu a0, a1, 2f
1:
sd zero, (a0)
addi a0, a0, 8
bltu a0, a1, 1b
2:
# Control registers, set the stack, mstatus, mepc,
# and mtvec to return to the main function.
# li t5, 0xffff;
# csrw medeleg, t5
# csrw mideleg, t5
la sp, _stack
# Setting `mstatus` register:
# 0b11 << 11: Machine's previous protection mode is 3 (MPP=3).
# 1 << 7 : Machine's previous interrupt-enable bit is 1 (MPIE=1).
# 1 << 3 : Machine's interrupt-enable bit is 1 (MIE=1).
li t0, (0b11 << 11) | (1 << 7) | (1 << 3)
csrw mstatus, t0
# Machine's exception program counter (MEPC) is set to `kmain`.
la t1, kmain
csrw mepc, t1
# Machine's trap vector base address is set to `asm_trap_vector`.
la t2, asm_trap_vector
csrw mtvec, t2
# Setting Machine's interrupt-enable bits (`mie` register):
# 1 << 3 : Machine's M-mode software interrupt-enable bit is 1 (MSIE=1).
# 1 << 7 : Machine's timer interrupt-enable bit is 1 (MTIE=1).
# 1 << 11: Machine's external interrupt-enable bit is 1 (MEIE=1).
li t3, (1 << 3) | (1 << 7) | (1 << 11)
csrw mie, t3
# Set the return address to infinitely wait for interrupts.
la ra, 4f
# We use mret here so that the mstatus register is properly updated.
mret
3:
# Parked harts go here. We need to set these
# to only awaken if it receives a software interrupt,
# which we're going to call the SIPI (Software Intra-Processor Interrupt).
# We only use these to run user-space programs, although this may
# change.
4:
wfi
j 4b
================================================
FILE: risc_v/chapters/ch1/src/asm/trap.S
================================================
# trap.S
# In the future our trap vector will go here.
.global asm_trap_vector
# This will be our trap vector when we start
# handling interrupts.
asm_trap_vector:
mret
================================================
FILE: risc_v/chapters/ch1/src/lds/virt.lds
================================================
OUTPUT_ARCH( "riscv" )
ENTRY( _start )
MEMORY
{
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M
}
PHDRS
{
text PT_LOAD;
data PT_LOAD;
bss PT_LOAD;
}
SECTIONS
{
.text : {
PROVIDE(_text_start = .);
*(.text.init) *(.text .text.*)
PROVIDE(_text_end = .);
} >ram AT>ram :text
PROVIDE(_global_pointer = .);
.rodata : {
PROVIDE(_rodata_start = .);
*(.rodata .rodata.*)
PROVIDE(_rodata_end = .);
} >ram AT>ram :text
.data : {
. = ALIGN(4096);
PROVIDE(_data_start = .);
*(.sdata .sdata.*) *(.data .data.*)
PROVIDE(_data_end = .);
} >ram AT>ram :data
.bss :{
PROVIDE(_bss_start = .);
*(.sbss .sbss.*) *(.bss .bss.*)
PROVIDE(_bss_end = .);
} >ram AT>ram :bss
PROVIDE(_memory_start = ORIGIN(ram));
PROVIDE(_stack = _bss_end + 0x80000);
PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram));
PROVIDE(_heap_start = _stack);
PROVIDE(_heap_size = _memory_end - _stack);
}
================================================
FILE: risc_v/chapters/ch1/src/lib.rs
================================================
// Steve Operating System
// Stephen Marz
// 21 Sep 2019
#![no_std]
#![feature(panic_info_message,asm)]
// ///////////////////////////////////
// / RUST MACROS
// ///////////////////////////////////
#[macro_export]
macro_rules! print
{
($($args:tt)+) => ({
});
}
#[macro_export]
macro_rules! println
{
() => ({
print!("\r\n")
});
($fmt:expr) => ({
print!(concat!($fmt, "\r\n"))
});
($fmt:expr, $($args:tt)+) => ({
print!(concat!($fmt, "\r\n"), $($args)+)
});
}
// ///////////////////////////////////
// / LANGUAGE STRUCTURES / FUNCTIONS
// ///////////////////////////////////
#[no_mangle]
extern "C" fn eh_personality() {}
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
print!("Aborting: ");
if let Some(p) = info.location() {
println!(
"line {}, file {}: {}",
p.line(),
p.file(),
info.message().unwrap()
);
}
else {
println!("no information available.");
}
abort();
}
#[no_mangle]
extern "C"
fn abort() -> ! {
loop {
unsafe {
asm!("wfi"::::"volatile");
}
}
}
// ///////////////////////////////////
// / CONSTANTS
// ///////////////////////////////////
// ///////////////////////////////////
// / ENTRY POINT
// ///////////////////////////////////
#[no_mangle]
extern "C"
fn kmain() {
// Main should initialize all sub-systems and get
// ready to start scheduling. The last thing this
// should do is start the timer.
}
// ///////////////////////////////////
// / RUST MODULES
// ///////////////////////////////////
================================================
FILE: risc_v/chapters/ch2/.cargo/config
================================================
[build]
target = "riscv64gc-unknown-none-elf"
[target.riscv64gc-unknown-none-elf]
linker = "riscv64-unknown-linux-gnu-gcc"
================================================
FILE: risc_v/chapters/ch2/.gitignore
================================================
os.elf
target/*
Cargo.lock
hdd.dsk
================================================
FILE: risc_v/chapters/ch2/Cargo.toml
================================================
[package]
name = "sos"
version = "0.1.0"
authors = ["Stephen Marz <stephen.marz@utk.edu>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["staticlib"]
[dependencies]
================================================
FILE: risc_v/chapters/ch2/Makefile
================================================
#####
## BUILD
#####
CC=riscv64-unknown-linux-gnu-g++
CFLAGS=-Wall -Wextra -pedantic -Wextra -O0 -g -std=c++17
CFLAGS+=-static -ffreestanding -nostdlib -fno-rtti -fno-exceptions
CFLAGS+=-march=rv64gc -mabi=lp64
INCLUDES=
LINKER_SCRIPT=-Tsrc/lds/virt.lds
TYPE=debug
RUST_TARGET=./target/riscv64gc-unknown-none-elf/$(TYPE)
LIBS=-L$(RUST_TARGET)
SOURCES_ASM=$(wildcard src/asm/*.S)
LIB=-lsos -lgcc
OUT=os.elf
#####
## QEMU
#####
QEMU=qemu-system-riscv64
MACH=virt
CPU=rv64
CPUS=4
MEM=128M
DRIVE=hdd.dsk
all:
cargo build
$(CC) $(CFLAGS) $(LINKER_SCRIPT) $(INCLUDES) -o $(OUT) $(SOURCES_ASM) $(LIBS) $(LIB)
run: all
$(QEMU) -machine $(MACH) -cpu $(CPU) -smp $(CPUS) -m $(MEM) -nographic -serial mon:stdio -bios none -kernel $(OUT) -drive if=none,format=raw,file=$(DRIVE),id=foo -device virtio-blk-device,scsi=off,drive=foo
.PHONY: clean
clean:
cargo clean
rm -f $(OUT)
================================================
FILE: risc_v/chapters/ch2/make_hdd.sh
================================================
#!/bin/sh
dd if=/dev/zero of=hdd.dsk bs=1M count=32
================================================
FILE: risc_v/chapters/ch2/src/asm/boot.S
================================================
# boot.S
# bootloader for SoS
# Stephen Marz
# 8 February 2019
.option norvc
.section .data
.section .text.init
.global _start
_start:
# Any hardware threads (hart) that are not bootstrapping
# need to wait for an IPI
csrr t0, mhartid
bnez t0, 3f
# SATP should be zero, but let's make sure
csrw satp, zero
.option push
.option norelax
la gp, _global_pointer
.option pop
# The BSS section is expected to be zero
la a0, _bss_start
la a1, _bss_end
bgeu a0, a1, 2f
1:
sd zero, (a0)
addi a0, a0, 8
bltu a0, a1, 1b
2:
# Control registers, set the stack, mstatus, mepc,
# and mtvec to return to the main function.
# li t5, 0xffff;
# csrw medeleg, t5
# csrw mideleg, t5
la sp, _stack
# We use mret here so that the mstatus register
# is properly updated.
li t0, (0b11 << 11) | (1 << 7) | (1 << 3)
csrw mstatus, t0
la t1, kmain
csrw mepc, t1
la t2, asm_trap_vector
csrw mtvec, t2
li t3, (1 << 3) | (1 << 7) | (1 << 11)
csrw mie, t3
la ra, 4f
mret
3:
# Parked harts go here. We need to set these
# to only awaken if it receives a software interrupt,
# which we're going to call the SIPI (Software Intra-Processor Interrupt).
# We only use these to run user-space programs, although this may
# change.
4:
wfi
j 4b
================================================
FILE: risc_v/chapters/ch2/src/asm/trap.S
================================================
# trap.S
# In the future our trap vector will go here.
.global asm_trap_vector
# This will be our trap vector when we start
# handling interrupts.
asm_trap_vector:
mret
================================================
FILE: risc_v/chapters/ch2/src/lds/virt.lds
================================================
/*
virt.lds
Linker script for outputting to RISC-V QEMU "virt" machine.
Stephen Marz
6 October 2019
*/
/*
riscv is the name of the architecture that the linker understands
for any RISC-V target (64-bit or 32-bit).
We will further refine this by using -mabi=lp64 and -march=rv64gc
*/
OUTPUT_ARCH( "riscv" )
/*
We're setting our entry point to a symbol
called _start which is inside of boot.S. This
essentially stores the address of _start as the
"entry point", or where CPU instructions should start
executing.
In the rest of this script, we are going to place _start
right at the beginning of 0x8000_0000 because this is where
the virtual machine and many RISC-V boards will start executing.
*/
ENTRY( _start )
/*
The MEMORY section will explain that we have "ram" that contains
a section that is 'w' (writeable), 'x' (executable), and 'a' (allocatable).
We use '!' to invert 'r' (read-only) and 'i' (initialized). We don't want
our memory to be read-only, and we're stating that it is NOT initialized
at the beginning.
The ORIGIN is the memory address 0x8000_0000. If we look at the virt
spec or the specification for the RISC-V HiFive Unleashed, this is the
starting memory address for our code.
Side note: There might be other boot ROMs at different addresses, but
their job is to get to this point.
Finally LENGTH = 128M tells the linker that we have 128 megabyte of RAM.
The linker will double check this to make sure everything can fit.
The HiFive Unleashed has a lot more RAM than this, but for the virtual
machine, I went with 128M since I think that's enough RAM for now.
We can provide other pieces of memory, such as QSPI, or ROM, but we're
telling the linker script here that we have one pool of RAM.
*/
MEMORY
{
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M
}
/*
PHDRS is short for "program headers", which we specify three here:
text - CPU instructions (executable sections)
data - Global, initialized variables
bss - Global, uninitialized variables (all will be set to 0 by boot.S)
The command PT_LOAD tells the linker that these sections will be loaded
from the file into memory.
We can actually stuff all of these into a single program header, but by
splitting it up into three, we can actually use the other PT_* commands
such as PT_DYNAMIC, PT_INTERP, PT_NULL to tell the linker where to find
additional information.
However, for our purposes, every section will be loaded from the program
headers.
*/
PHDRS
{
text PT_LOAD;
data PT_LOAD;
bss PT_LOAD;
}
/*
We are now going to organize the memory based on which
section it is in. In assembly, we can change the section
with the ".section" directive. However, in C++ and Rust,
CPU instructions go into text, global constants go into
rodata, global initialized variables go into data, and
global uninitialized variables go into bss.
*/
SECTIONS
{
/*
The first part of our RAM layout will be the text section.
Since our CPU instructions are here, and our memory starts at
0x8000_0000, we need our entry point to line up here.
*/
.text : {
/*
PROVIDE allows me to access a symbol called _text_start so
I know where the text section starts in the operating system.
This should not move, but it is here for convenience.
The period '.' tells the linker to set _text_start to the
CURRENT location ('.' = current memory location). This current
memory location moves as we add things.
*/
PROVIDE(_text_start = .);
/*
We are going to layout all text sections here, starting with
.text.init. The asterisk in front of the parentheses means to match
the .text.init section of ANY object file. Otherwise, we can specify
which object file should contain the .text.init section, for example,
boot.o(.text.init) would specifically put the .text.init section of
our bootloader here.
Because we might want to change the name of our files, we'll leave it
with a *.
Inside the parentheses is the name of the section. I created my own
called .text.init to make 100% sure that the _start is put right at the
beginning. The linker will lay this out in the order it receives it:
.text.init first
all .text sections next
any .text.* sections last
.text.* means to match anything after .text. If we didn't already specify
.text.init, this would've matched here. The assembler and linker can place
things in "special" text sections, so we match any we might come across here.
*/
*(.text.init) *(.text .text.*)
/*
Again, with PROVIDE, we're providing a readable symbol called _text_end, which is
set to the memory address AFTER .text.init, .text, and .text.*'s have been added.
*/
PROVIDE(_text_end = .);
/*
The portion after the right brace is in an odd format. However, this is telling the
linker what memory portion to put it in. We labeled our RAM, ram, with the constraints
that it is writeable, allocatable, and executable. The linker will make sure with this
that we can do all of those things.
>ram - This just tells the linker script to put this entire section (.text) into the
ram region of memory. To my knowledge, the '>' does not mean "greater than". Instead,
it is a symbol to let the linker know we want to put this in ram.
AT>ram - This sets the LMA (load memory address) region to the same thing. LMA is the final
translation of a VMA (virtual memory address). With this linker script, we're loading
everything into its physical location. We'll let the kernel copy and sort out the
virtual memory. That's why >ram and AT>ram are continually the same thing.
:text - This tells the linker script to put this into the :text program header. We've only
defined three: text, data, and bss. In this case, we're telling the linker script
to go into the text section.
*/
} >ram AT>ram :text
/*
The global pointer allows the linker to position global variables and constants into
independent positions relative to the gp (global pointer) register. The globals start
after the text sections and are only relevant to the rodata, data, and bss sections.
*/
PROVIDE(_global_pointer = .);
/*
Most compilers create a rodata (read only data) section for global constants. However,
we're going to place ours in the text section. We can actually put this in :data, but
since the .text section is read-only, we can place it there.
NOTE: This doesn't actually do anything, yet. The actual "protection" cannot be done
at link time. Instead, when we program the memory management unit (MMU), we will be
able to choose which bits (R=read, W=write, X=execute) we want each memory segment
to be able to do.
*/
.rodata : {
PROVIDE(_rodata_start = .);
*(.rodata .rodata.*)
PROVIDE(_rodata_end = .);
/*
Again, we're placing the rodata section in the memory segment "ram" and we're putting
it in the :text program header. We don't have one for rodata anyway.
*/
} >ram AT>ram :text
.data : {
/*
. = ALIGN(4096) tells the linker to align the current memory location (which is
0x8000_0000 + text section + rodata section) to 4096 bytes. This is because our paging
system's resolution is 4,096 bytes or 4 KiB.
*/
. = ALIGN(4096);
PROVIDE(_data_start = .);
/*
sdata and data are essentially the same thing. However, compilers usually use the
sdata sections for shorter, quicker loading sections. So, usually critical data
is loaded there. However, we're loading all of this in one fell swoop.
So, we're looking to put all of the following sections under the umbrella .data:
.sdata
.sdata.[anything]
.data
.data.[anything]
...in that order.
*/
*(.sdata .sdata.*) *(.data .data.*)
PROVIDE(_data_end = .);
} >ram AT>ram :data
.bss : {
PROVIDE(_bss_start = .);
*(.sbss .sbss.*) *(.bss .bss.*)
PROVIDE(_bss_end = .);
} >ram AT>ram :bss
/*
The following will be helpful when we allocate the kernel stack (_stack) and
determine where the heap begnis and ends (_heap_start and _heap_start + _heap_size)/
When we do memory allocation, we can use these symbols.
We use the symbols instead of hard-coding an address because this is a floating target.
As we add code, the heap moves farther down the memory and gets shorter.
_memory_start will be set to 0x8000_0000 here. We use ORIGIN(ram) so that it will take
whatever we set the origin of ram to. Otherwise, we'd have to change it more than once
if we ever stray away from 0x8000_0000 as our entry point.
*/
PROVIDE(_memory_start = ORIGIN(ram));
/*
Our kernel stack starts at the end of the bss segment (_bss_end). However, we're allocating
0x80000 bytes (524 KiB) to our kernel stack. This should be PLENTY of space. The reason
we add the memory is because the stack grows from higher memory to lower memory (bottom to top).
Therefore we set the stack at the very bottom of its allocated slot.
When we go to allocate from the stack, we'll subtract the number of bytes we need.
*/
PROVIDE(_stack = _bss_end + 0x80000);
PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram));
/*
Finally, our heap starts right after the kernel stack. This heap will be used mainly
to dole out memory for user-space applications. However, in some circumstances, it will
be used for kernel memory as well.
We don't align here because we let the kernel determine how it wants to do this.
*/
PROVIDE(_heap_start = _stack);
PROVIDE(_heap_size = _memory_end - _stack);
}
================================================
FILE: risc_v/chapters/ch2/src/lib.rs
================================================
// Steve Operating System
// Stephen Marz
// 21 Sep 2019
#![no_std]
#![feature(panic_info_message,asm)]
// ///////////////////////////////////
// / RUST MACROS
// ///////////////////////////////////
#[macro_export]
macro_rules! print
{
($($args:tt)+) => ({
use core::fmt::Write;
let _ = write!(crate::uart::Uart::new(0x1000_0000), $($args)+);
});
}
#[macro_export]
macro_rules! println
{
() => ({
print!("\r\n")
});
($fmt:expr) => ({
print!(concat!($fmt, "\r\n"))
});
($fmt:expr, $($args:tt)+) => ({
print!(concat!($fmt, "\r\n"), $($args)+)
});
}
// ///////////////////////////////////
// / LANGUAGE STRUCTURES / FUNCTIONS
// ///////////////////////////////////
#[no_mangle]
extern "C" fn eh_personality() {}
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
print!("Aborting: ");
if let Some(p) = info.location() {
println!(
"line {}, file {}: {}",
p.line(),
p.file(),
info.message().unwrap()
);
}
else {
println!("no information available.");
}
abort();
}
#[no_mangle]
extern "C"
fn abort() -> ! {
loop {
unsafe {
asm!("wfi"::::"volatile");
}
}
}
// ///////////////////////////////////
// / CONSTANTS
// ///////////////////////////////////
// ///////////////////////////////////
// / ENTRY POINT
// ///////////////////////////////////
#[no_mangle]
extern "C"
fn kmain() {
// Main should initialize all sub-systems and get
// ready to start scheduling. The last thing this
// should do is start the timer.
// Let's try using our newly minted UART by initializing it first.
// The UART is sitting at MMIO address 0x1000_0000, so for testing
// now, lets connect to it and see if we can initialize it and write
// to it.
let mut my_uart = uart::Uart::new(0x1000_0000);
my_uart.init();
// Now test println! macro!
println!("This is my operating system!");
println!("I'm so awesome. If you start typing something, I'll show you what you typed!");
// Now see if we can read stuff:
// Usually we can use #[test] modules in Rust, but it would convolute the
// task at hand. So, we'll just add testing snippets.
loop {
if let Some(c) = my_uart.get() {
match c {
8 => {
// This is a backspace, so we essentially have
// to write a space and backup again:
print!("{}{}{}", 8 as char, ' ', 8 as char);
},
10 | 13 => {
// Newline or carriage-return
println!();
},
0x1b => {
// Those familiar with ANSI escape sequences
// knows that this is one of them. The next
// thing we should get is the left bracket [
// These are multi-byte sequences, so we can take
// a chance and get from UART ourselves.
// Later, we'll button this up.
if let Some(next_byte) = my_uart.get() {
if next_byte == 91 {
// This is a right bracket! We're on our way!
if let Some(b) = my_uart.get() {
match b as char {
'A' => {
println!("That's the up arrow!");
},
'B' => {
println!("That's the down arrow!");
},
'C' => {
println!("That's the right arrow!");
},
'D' => {
println!("That's the left arrow!");
},
_ => {
println!("That's something else.....");
}
}
}
}
}
},
_ => {
print!("{}", c as char);
}
}
}
}
}
// ///////////////////////////////////
// / RUST MODULES
// ///////////////////////////////////
pub mod uart;
================================================
FILE: risc_v/chapters/ch2/src/uart.rs
================================================
// uart.rs
// UART routines and driver
use core::convert::TryInto;
use core::fmt::Write;
use core::fmt::Error;
pub struct Uart {
base_address: usize,
}
impl Write for Uart {
fn write_str(&mut self, out: &str) -> Result<(), Error> {
for c in out.bytes() {
self.put(c);
}
Ok(())
}
}
impl Uart {
pub fn new(base_address: usize) -> Self {
Uart {
base_address
}
}
pub fn init(&mut self) {
let ptr = self.base_address as *mut u8;
unsafe {
// First, set the word length, which
// are bits 0 and 1 of the line control register (LCR)
// which is at base_address + 3
// We can easily write the value 3 here or 0b11, but I'm
// extending it so that it is clear we're setting two individual
// fields
// Word 0 Word 1
// ~~~~~~ ~~~~~~
ptr.add(3).write_volatile((1 << 0) | (1 << 1));
// Now, enable the FIFO, which is bit index 0 of the FIFO
// control register (FCR at offset 2).
// Again, we can just write 1 here, but when we use left shift,
// it's easier to see that we're trying to write bit index #0.
ptr.add(2).write_volatile(1 << 0);
// Enable receiver buffer interrupts, which is at bit index
// 0 of the interrupt enable register (IER at offset 1).
ptr.add(1).write_volatile(1 << 0);
// If we cared about the divisor, the code below would set the divisor
// from a global clock rate of 22.729 MHz (22,729,000 cycles per second)
// to a signaling rate of 2400 (BAUD). We usually have much faster signalling
// rates nowadays, but this demonstrates what the divisor actually does.
// The formula given in the NS16500A specification for calculating the divisor
// is:
// divisor = ceil( (clock_hz) / (baud_sps x 16) )
// So, we substitute our values and get:
// divisor = ceil( 22_729_000 / (2400 x 16) )
// divisor = ceil( 22_729_000 / 38_400 )
// divisor = ceil( 591.901 ) = 592
// The divisor register is two bytes (16 bits), so we need to split the value
// 592 into two bytes. Typically, we would calculate this based on measuring
// the clock rate, but again, for our purposes [qemu], this doesn't really do
// anything.
let divisor: u16 = 592;
let divisor_least: u8 = (divisor & 0xff).try_into().unwrap();
let divisor_most: u8 = (divisor >> 8).try_into().unwrap();
// Notice that the divisor register DLL (divisor latch least) and DLM (divisor latch most)
// have the same base address as the receiver/transmitter and the interrupt enable register.
// To change what the base address points to, we open the "divisor latch" by writing 1 into
// the Divisor Latch Access Bit (DLAB), which is bit index 7 of the Line Control Register (LCR)
// which is at base_address + 3.
let lcr = ptr.add(3).read_volatile();
ptr.add(3).write_volatile(lcr | 1 << 7);
// Now, base addresses 0 and 1 point to DLL and DLM, respectively.
// Put the lower 8 bits of the divisor into DLL
ptr.add(0).write_volatile(divisor_least);
ptr.add(1).write_volatile(divisor_most);
// Now that we've written the divisor, we never have to touch this again. In hardware, this
// will divide the global clock (22.729 MHz) into one suitable for 2,400 signals per second.
// So, to once again get access to the RBR/THR/IER registers, we need to close the DLAB bit
// by clearing it to 0. Here, we just restore the original value of lcr.
ptr.add(3).write_volatile(lcr);
}
}
pub fn put(&mut self, c: u8) {
let ptr = self.base_address as *mut u8;
unsafe {
ptr.add(0).write_volatile(c);
}
}
pub fn get(&mut self) -> Option<u8> {
let ptr = self.base_address as *mut u8;
unsafe {
if ptr.add(5).read_volatile() & 1 == 0 {
// The DR bit is 0, meaning no data
None
}
else {
// The DR bit is 1, meaning data!
Some(ptr.add(0).read_volatile())
}
}
}
}
================================================
FILE: risc_v/chapters/ch3/.cargo/config
================================================
[build]
target = "riscv64gc-unknown-none-elf"
[target.riscv64gc-unknown-none-elf]
linker = "riscv64-unknown-linux-gnu-gcc"
================================================
FILE: risc_v/chapters/ch3/.gitignore
================================================
os.elf
target/*
Cargo.lock
hdd.dsk
================================================
FILE: risc_v/chapters/ch3/Cargo.toml
================================================
[package]
name = "sos"
version = "0.1.0"
authors = ["Stephen Marz <stephen.marz@utk.edu>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["staticlib"]
[dependencies]
================================================
FILE: risc_v/chapters/ch3/Makefile
================================================
#####
## BUILD
#####
CC=riscv64-unknown-linux-gnu-gcc
CFLAGS=-Wall -Wextra -pedantic -Wextra -O0 -g
CFLAGS+=-static -ffreestanding -nostdlib -fno-rtti -fno-exceptions
CFLAGS+=-march=rv64gc -mabi=lp64
INCLUDES=
LINKER_SCRIPT=-Tsrc/lds/virt.lds
TYPE=debug
RUST_TARGET=./target/riscv64gc-unknown-none-elf/$(TYPE)
LIBS=-L$(RUST_TARGET)
SOURCES_ASM=$(wildcard src/asm/*.S)
LIB=-lsos -lgcc
OUT=os.elf
#####
## QEMU
#####
QEMU=qemu-system-riscv64
MACH=virt
CPU=rv64
CPUS=4
MEM=128M
DISK=hdd.dsk
# DRIVE= -drive if=none,format=raw,file=$(DISK),id=foo -device virtio-blk-device,scsi=off,drive=foo
DRIVE=
all:
cargo build
$(CC) $(CFLAGS) $(LINKER_SCRIPT) $(INCLUDES) -o $(OUT) $(SOURCES_ASM) $(LIBS) $(LIB)
run: all
$(QEMU) -machine $(MACH) -cpu $(CPU) -smp $(CPUS) -m $(MEM) $(DRIVE) -nographic -serial mon:stdio -bios none -kernel $(OUT)
.PHONY: clean
clean:
cargo clean
rm -f $(OUT)
================================================
FILE: risc_v/chapters/ch3/make_hdd.sh
================================================
#!/bin/sh
dd if=/dev/zero of=hdd.dsk bs=1M count=32
================================================
FILE: risc_v/chapters/ch3/src/asm/boot.S
================================================
# boot.S
# bootloader for SoS
# Stephen Marz
# 8 February 2019
# Disable generation of compressed instructions.
.option norvc
# Define a .data section.
.section .data
# Define a .text.init section.
.section .text.init
# Execution starts here.
.global _start
_start:
# Any hardware threads (hart) that are not bootstrapping
# need to wait for an IPI
csrr t0, mhartid
bnez t0, 3f
# SATP should be zero, but let's make sure
csrw satp, zero
# Disable linker instruction relaxation for the `la` instruction below.
# This disallows the assembler from assuming that `gp` is already initialized.
# This causes the value stored in `gp` to be calculated from `pc`.
.option push
.option norelax
la gp, _global_pointer
.option pop
# Set all bytes in the BSS section to zero.
la a0, _bss_start
la a1, _bss_end
bgeu a0, a1, 2f
1:
sd zero, (a0)
addi a0, a0, 8
bltu a0, a1, 1b
2:
# Control registers, set the stack, mstatus, mepc,
# and mtvec to return to the main function.
# li t5, 0xffff;
# csrw medeleg, t5
# csrw mideleg, t5
# The stack grows from bottom to top, so we put the stack pointer
# to the very end of the stack range.
la sp, _stack_end
# Setting `mstatus` register:
# 0b11 << 11: Machine's previous protection mode is 3 (MPP=3).
li t0, 0b11 << 11
csrw mstatus, t0
# Machine's exception program counter (MEPC) is set to `kinit`.
la t1, kinit
csrw mepc, t1
# Machine's trap vector base address is set to `asm_trap_vector`.
la t2, asm_trap_vector
csrw mtvec, t2
# Set the return address to get us into supervisor mode
la ra, 2f
# We use mret here so that the mstatus register is properly updated.
mret
2:
# We set the return address (ra above) to this label. When kinit() is finished
# in Rust, it will return here.
# Setting `sstatus` (supervisor status) register:
# 1 << 8 : Supervisor's previous protection mode is 1 (SPP=1 [Supervisor]).
# 1 << 5 : Supervisor's previous interrupt-enable bit is 1 (SPIE=1 [Enabled]).
# 1 << 1 : Supervisor's interrupt-enable bit will be set to 1 after sret.
# We set the "previous" bits because the sret will write the current bits
# with the previous bits.
li t0, (1 << 8) | (1 << 5)
csrw sstatus, t0
la t1, kmain
csrw sepc, t1
# Setting `mideleg` (machine interrupt delegate) register:
# 1 << 1 : Software interrupt delegated to supervisor mode
# 1 << 5 : Timer interrupt delegated to supervisor mode
# 1 << 9 : External interrupt delegated to supervisor mode
# By default all traps (interrupts or exceptions) automatically
# cause an elevation to the machine privilege mode (mode 3).
# When we delegate, we're telling the CPU to only elevate to
# the supervisor privilege mode (mode 1)
li t2, (1 << 1) | (1 << 5) | (1 << 9)
csrw mideleg, t2
# Setting `sie` (supervisor interrupt enable) register:
# This register takes the same bits as mideleg
# 1 << 1 : Supervisor software interrupt enable (SSIE=1 [Enabled])
# 1 << 5 : Supervisor timer interrupt enable (STIE=1 [Enabled])
# 1 << 9 : Supervisor external interrupt enable (SEIE=1 [Enabled])
csrw sie, t2
# Setting `stvec` (supervisor trap vector) register:
# Essentially this is a function pointer, but the last two bits can be 00 or 01
# 00 : All exceptions set pc to BASE
# 01 : Asynchronous interrupts set pc to BASE + 4 x scause
la t3, asm_trap_vector
csrw stvec, t3
# kinit() is required to return back the SATP value (including MODE) via a0
csrw satp, a0
# Force the CPU to take our SATP register.
# To be efficient, if the address space identifier (ASID) portion of SATP is already
# in cache, it will just grab whatever's in cache. However, that means if we've updated
# it in memory, it will be the old table. So, sfence.vma will ensure that the MMU always
# grabs a fresh copy of the SATP register and associated tables.
sfence.vma
# sret will put us in supervisor mode and re-enable interrupts
sret
3:
# Parked harts go here. We need to set these
# to only awaken if it receives a software interrupt,
# which we're going to call the SIPI (Software Intra-Processor Interrupt).
# We call the SIPI by writing the software interrupt into the Core Local Interruptor (CLINT)
# Which is calculated by: base_address + hart * 4
# where base address is 0x0200_0000 (MMIO CLINT base address)
# We only use additional harts to run user-space programs, although this may
# change.
4:
wfi
j 4b
================================================
FILE: risc_v/chapters/ch3/src/asm/mem.S
================================================
// mem.S
// Importation of linker symbols
.section .rodata
.global HEAP_START
HEAP_START: .dword _heap_start
.global HEAP_SIZE
HEAP_SIZE: .dword _heap_size
.global TEXT_START
TEXT_START: .dword _text_start
.global TEXT_END
TEXT_END: .dword _text_end
.global DATA_START
DATA_START: .dword _data_start
.global DATA_END
DATA_END: .dword _data_end
.global RODATA_START
RODATA_START: .dword _rodata_start
.global RODATA_END
RODATA_END: .dword _rodata_end
.global BSS_START
BSS_START: .dword _bss_start
.global BSS_END
BSS_END: .dword _bss_end
.global KERNEL_STACK_START
KERNEL_STACK_START: .dword _stack_start
.global KERNEL_STACK_END
KERNEL_STACK_END: .dword _stack_end
.section .data
.global KERNEL_TABLE
KERNEL_TABLE: .dword 0
================================================
FILE: risc_v/chapters/ch3/src/asm/trap.S
================================================
# trap.S
# In the future our trap vector will go here.
.global asm_trap_vector
# This will be our trap vector when we start
# handling interrupts.
asm_trap_vector:
csrr a0, mtval
wfi
j asm_trap_vector
================================================
FILE: risc_v/chapters/ch3/src/kmem.rs
================================================
// kmem.rs
// Sub-page level: malloc-like allocation system
// Stephen Marz
// 7 October 2019
use crate::page::{align_val, zalloc, Table, PAGE_SIZE};
use core::{mem::size_of, ptr::null_mut};
#[repr(usize)]
enum AllocListFlags {
Taken = 1 << 63,
}
impl AllocListFlags {
pub fn val(self) -> usize {
self as usize
}
}
struct AllocList {
pub flags_size: usize,
}
impl AllocList {
pub fn is_taken(&self) -> bool {
self.flags_size & AllocListFlags::Taken.val() != 0
}
pub fn is_free(&self) -> bool {
!self.is_taken()
}
pub fn set_taken(&mut self) {
self.flags_size |= AllocListFlags::Taken.val();
}
pub fn set_free(&mut self) {
self.flags_size &= !AllocListFlags::Taken.val();
}
pub fn set_size(&mut self, sz: usize) {
let k = self.is_taken();
self.flags_size = sz & !AllocListFlags::Taken.val();
if k {
self.flags_size |= AllocListFlags::Taken.val();
}
}
pub fn get_size(&self) -> usize {
self.flags_size & !AllocListFlags::Taken.val()
}
}
// This is the head of the allocation. We start here when
// we search for a free memory location.
static mut KMEM_HEAD: *mut AllocList = null_mut();
// In the future, we will have on-demand pages
// so, we need to keep track of our memory footprint to
// see if we actually need to allocate more.
static mut KMEM_ALLOC: usize = 0;
static mut KMEM_PAGE_TABLE: *mut Table = null_mut();
// These functions are safe helpers around an unsafe
// operation.
pub fn get_head() -> *mut u8 {
unsafe { KMEM_HEAD as *mut u8 }
}
pub fn get_page_table() -> *mut Table {
unsafe { KMEM_PAGE_TABLE as *mut Table }
}
pub fn get_num_allocations() -> usize {
unsafe { KMEM_ALLOC }
}
/// Initialize kernel's memory
/// This is not to be used to allocate memory
/// for user processes. If that's the case, use
/// alloc/dealloc from the page crate.
pub fn init() {
unsafe {
// Allocate 64 kernel pages (64 * 4096 = 262 KiB)
let k_alloc = zalloc(64);
assert!(!k_alloc.is_null());
KMEM_ALLOC = 64;
KMEM_HEAD = k_alloc as *mut AllocList;
(*KMEM_HEAD).set_free();
(*KMEM_HEAD).set_size(KMEM_ALLOC * PAGE_SIZE);
KMEM_PAGE_TABLE = zalloc(1) as *mut Table;
}
}
/// Allocate sub-page level allocation based on bytes and zero the memory
pub fn kzmalloc(sz: usize) -> *mut u8 {
let size = align_val(sz, 3);
let ret = kmalloc(size);
if !ret.is_null() {
for i in 0..size {
unsafe {
(*ret.add(i)) = 0;
}
}
}
ret
}
/// Allocate sub-page level allocation based on bytes
pub fn kmalloc(sz: usize) -> *mut u8 {
unsafe {
let size = align_val(sz, 3) + size_of::<AllocList>();
let mut head = KMEM_HEAD;
// .add() uses pointer arithmetic, so we type-cast into a u8
// so that we multiply by an absolute size (KMEM_ALLOC *
// PAGE_SIZE).
let tail = (KMEM_HEAD as *mut u8).add(KMEM_ALLOC * PAGE_SIZE)
as *mut AllocList;
while head < tail {
if (*head).is_free() && size <= (*head).get_size() {
let chunk_size = (*head).get_size();
let rem = chunk_size - size;
(*head).set_taken();
if rem > size_of::<AllocList>() {
let next = (head as *mut u8).add(size)
as *mut AllocList;
// There is space remaining here.
(*next).set_free();
(*next).set_size(rem);
(*head).set_size(size);
}
else {
// If we get here, take the entire chunk
(*head).set_size(chunk_size);
}
return head.add(1) as *mut u8;
}
else {
// If we get here, what we saw wasn't a free
// chunk, move on to the next.
head = (head as *mut u8).add((*head).get_size())
as *mut AllocList;
}
}
}
// If we get here, we didn't find any free chunks--i.e. there isn't
// enough memory for this. TODO: Add on-demand page allocation.
null_mut()
}
/// Free a sub-page level allocation
pub fn kfree(ptr: *mut u8) {
unsafe {
if !ptr.is_null() {
let p = (ptr as *mut AllocList).offset(-1);
if (*p).is_taken() {
(*p).set_free();
}
// After we free, see if we can combine adjacent free
// spots to see if we can reduce fragmentation.
coalesce();
}
}
}
/// Merge smaller chunks into a bigger chunk
pub fn coalesce() {
unsafe {
let mut head = KMEM_HEAD;
let tail = (KMEM_HEAD as *mut u8).add(KMEM_ALLOC * PAGE_SIZE)
as *mut AllocList;
while head < tail {
let next = (head as *mut u8).add((*head).get_size())
as *mut AllocList;
if (*head).get_size() == 0 {
// If this happens, then we have a bad heap
// (double free or something). However, that
// will cause an infinite loop since the next
// pointer will never move beyond the current
// location.
break;
}
else if next >= tail {
// We calculated the next by using the size
// given as get_size(), however this could push
// us past the tail. In that case, the size is
// wrong, hence we break and stop doing what we
// need to do.
break;
}
else if (*head).is_free() && (*next).is_free() {
// This means we have adjacent blocks needing to
// be freed. So, we combine them into one
// allocation.
(*head).set_size(
(*head).get_size()
+ (*next).get_size(),
);
}
// If we get here, we might've moved. Recalculate new
// head.
head = (head as *mut u8).add((*head).get_size())
as *mut AllocList;
}
}
}
/// For debugging purposes, print the kmem table
pub fn print_table() {
unsafe {
let mut head = KMEM_HEAD;
let tail = (KMEM_HEAD as *mut u8).add(KMEM_ALLOC * PAGE_SIZE)
as *mut AllocList;
while head < tail {
println!(
"{:p}: Length = {:<10} Taken = {}",
head,
(*head).get_size(),
(*head).is_taken()
);
head = (head as *mut u8).add((*head).get_size())
as *mut AllocList;
}
}
}
// ///////////////////////////////////
// / GLOBAL ALLOCATOR
// ///////////////////////////////////
// The global allocator allows us to use the data structures
// in the core library, such as a linked list or B-tree.
// We want to use these sparingly since we have a coarse-grained
// allocator.
use core::alloc::{GlobalAlloc, Layout};
// The global allocator is a static constant to a global allocator
// structure. We don't need any members because we're using this
// structure just to implement alloc and dealloc.
struct OsGlobalAlloc;
unsafe impl GlobalAlloc for OsGlobalAlloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
// We align to the next page size so that when
// we divide by PAGE_SIZE, we get exactly the number
// of pages necessary.
kzmalloc(layout.size())
}
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
// We ignore layout since our allocator uses ptr_start -> last
// to determine the span of an allocation.
kfree(ptr);
}
}
#[global_allocator]
/// Technically, we don't need the {} at the end, but it
/// reveals that we're creating a new structure and not just
/// copying a value.
static GA: OsGlobalAlloc = OsGlobalAlloc {};
#[alloc_error_handler]
/// If for some reason alloc() in the global allocator gets null_mut(),
/// then we come here. This is a divergent function, so we call panic to
/// let the tester know what's going on.
pub fn alloc_error(l: Layout) -> ! {
panic!(
"Allocator failed to allocate {} bytes with {}-byte alignment.",
l.size(),
l.align()
);
}
================================================
FILE: risc_v/chapters/ch3/src/lds/virt.lds
================================================
/*
virt.lds
Linker script for outputting to RISC-V QEMU "virt" machine.
Stephen Marz
6 October 2019
*/
/*
riscv is the name of the architecture that the linker understands
for any RISC-V target (64-bit or 32-bit).
We will further refine this by using -mabi=lp64 and -march=rv64gc
*/
OUTPUT_ARCH( "riscv" )
/*
We're setting our entry point to a symbol
called _start which is inside of boot.S. This
essentially stores the address of _start as the
"entry point", or where CPU instructions should start
executing.
In the rest of this script, we are going to place _start
right at the beginning of 0x8000_0000 because this is where
the virtual machine and many RISC-V boards will start executing.
*/
ENTRY( _start )
/*
The MEMORY section will explain that we have "ram" that contains
a section that is 'w' (writeable), 'x' (executable), and 'a' (allocatable).
We use '!' to invert 'r' (read-only) and 'i' (initialized). We don't want
our memory to be read-only, and we're stating that it is NOT initialized
at the beginning.
The ORIGIN is the memory address 0x8000_0000. If we look at the virt
spec or the specification for the RISC-V HiFive Unleashed, this is the
starting memory address for our code.
Side note: There might be other boot ROMs at different addresses, but
their job is to get to this point.
Finally LENGTH = 128M tells the linker that we have 128 megabyte of RAM.
The linker will double check this to make sure everything can fit.
The HiFive Unleashed has a lot more RAM than this, but for the virtual
machine, I went with 128M since I think that's enough RAM for now.
We can provide other pieces of memory, such as QSPI, or ROM, but we're
telling the linker script here that we have one pool of RAM.
*/
MEMORY
{
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M
}
/*
PHDRS is short for "program headers", which we specify three here:
text - CPU instructions (executable sections)
data - Global, initialized variables
bss - Global, uninitialized variables (all will be set to 0 by boot.S)
The command PT_LOAD tells the linker that these sections will be loaded
from the file into memory.
We can actually stuff all of these into a single program header, but by
splitting it up into three, we can actually use the other PT_* commands
such as PT_DYNAMIC, PT_INTERP, PT_NULL to tell the linker where to find
additional information.
However, for our purposes, every section will be loaded from the program
headers.
*/
PHDRS
{
text PT_LOAD;
data PT_LOAD;
bss PT_LOAD;
}
/*
We are now going to organize the memory based on which
section it is in. In assembly, we can change the section
with the ".section" directive. However, in C++ and Rust,
CPU instructions go into text, global constants go into
rodata, global initialized variables go into data, and
global uninitialized variables go into bss.
*/
SECTIONS
{
/*
The first part of our RAM layout will be the text section.
Since our CPU instructions are here, and our memory starts at
0x8000_0000, we need our entry point to line up here.
*/
.text : {
/*
PROVIDE allows me to access a symbol called _text_start so
I know where the text section starts in the operating system.
This should not move, but it is here for convenience.
The period '.' tells the linker to set _text_start to the
CURRENT location ('.' = current memory location). This current
memory location moves as we add things.
*/
PROVIDE(_text_start = .);
/*
We are going to layout all text sections here, starting with
.text.init. The asterisk in front of the parentheses means to match
the .text.init section of ANY object file. Otherwise, we can specify
which object file should contain the .text.init section, for example,
boot.o(.text.init) would specifically put the .text.init section of
our bootloader here.
Because we might want to change the name of our files, we'll leave it
with a *.
Inside the parentheses is the name of the section. I created my own
called .text.init to make 100% sure that the _start is put right at the
beginning. The linker will lay this out in the order it receives it:
.text.init first
all .text sections next
any .text.* sections last
.text.* means to match anything after .text. If we didn't already specify
.text.init, this would've matched here. The assembler and linker can place
things in "special" text sections, so we match any we might come across here.
*/
*(.text.init) *(.text .text.*)
/*
Again, with PROVIDE, we're providing a readable symbol called _text_end, which is
set to the memory address AFTER .text.init, .text, and .text.*'s have been added.
*/
PROVIDE(_text_end = .);
/*
The portion after the right brace is in an odd format. However, this is telling the
linker what memory portion to put it in. We labeled our RAM, ram, with the constraints
that it is writeable, allocatable, and executable. The linker will make sure with this
that we can do all of those things.
>ram - This just tells the linker script to put this entire section (.text) into the
ram region of memory. To my knowledge, the '>' does not mean "greater than". Instead,
it is a symbol to let the linker know we want to put this in ram.
AT>ram - This sets the LMA (load memory address) region to the same thing. LMA is the final
translation of a VMA (virtual memory address). With this linker script, we're loading
everything into its physical location. We'll let the kernel copy and sort out the
virtual memory. That's why >ram and AT>ram are continually the same thing.
:text - This tells the linker script to put this into the :text program header. We've only
defined three: text, data, and bss. In this case, we're telling the linker script
to go into the text section.
*/
} >ram AT>ram :text
/*
The global pointer allows the linker to position global variables and constants into
independent positions relative to the gp (global pointer) register. The globals start
after the text sections and are only relevant to the rodata, data, and bss sections.
*/
PROVIDE(_global_pointer = .);
/*
Most compilers create a rodata (read only data) section for global constants. However,
we're going to place ours in the text section. We can actually put this in :data, but
since the .text section is read-only, we can place it there.
NOTE: This doesn't actually do anything, yet. The actual "protection" cannot be done
at link time. Instead, when we program the memory management unit (MMU), we will be
able to choose which bits (R=read, W=write, X=execute) we want each memory segment
to be able to do.
*/
.rodata : {
PROVIDE(_rodata_start = .);
*(.rodata .rodata.*)
PROVIDE(_rodata_end = .);
/*
Again, we're placing the rodata section in the memory segment "ram" and we're putting
it in the :text program header. We don't have one for rodata anyway.
*/
} >ram AT>ram :text
.data : {
/*
. = ALIGN(4096) tells the linker to align the current memory location (which is
0x8000_0000 + text section + rodata section) to 4096 bytes. This is because our paging
system's resolution is 4,096 bytes or 4 KiB.
*/
. = ALIGN(4096);
PROVIDE(_data_start = .);
/*
sdata and data are essentially the same thing. However, compilers usually use the
sdata sections for shorter, quicker loading sections. So, usually critical data
is loaded there. However, we're loading all of this in one fell swoop.
So, we're looking to put all of the following sections under the umbrella .data:
.sdata
.sdata.[anything]
.data
.data.[anything]
...in that order.
*/
*(.sdata .sdata.*) *(.data .data.*)
PROVIDE(_data_end = .);
} >ram AT>ram :data
.bss : {
PROVIDE(_bss_start = .);
*(.sbss .sbss.*) *(.bss .bss.*)
PROVIDE(_bss_end = .);
} >ram AT>ram :bss
/*
The following will be helpful when we allocate the kernel stack (_stack) and
determine where the heap begnis and ends (_heap_start and _heap_start + _heap_size)/
When we do memory allocation, we can use these symbols.
We use the symbols instead of hard-coding an address because this is a floating target.
As we add code, the heap moves farther down the memory and gets shorter.
_memory_start will be set to 0x8000_0000 here. We use ORIGIN(ram) so that it will take
whatever we set the origin of ram to. Otherwise, we'd have to change it more than once
if we ever stray away from 0x8000_0000 as our entry point.
*/
PROVIDE(_memory_start = ORIGIN(ram));
/*
Our kernel stack starts at the end of the bss segment (_bss_end). However, we're allocating
0x80000 bytes (524 KiB) to our kernel stack. This should be PLENTY of space. The reason
we add the memory is because the stack grows from higher memory to lower memory (bottom to top).
Therefore we set the stack at the very bottom of its allocated slot.
When we go to allocate from the stack, we'll subtract the number of bytes we need.
*/
PROVIDE(_stack_start = _bss_end);
PROVIDE(_stack_end = _stack_start + 0x8000);
PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram));
/*
Finally, our heap starts right after the kernel stack. This heap will be used mainly
to dole out memory for user-space applications. However, in some circumstances, it will
be used for kernel memory as well.
We don't align here because we let the kernel determine how it wants to do this.
*/
PROVIDE(_heap_start = _stack_end);
PROVIDE(_heap_size = _memory_end - _heap_start);
}
================================================
FILE: risc_v/chapters/ch3/src/lib.rs
================================================
// Steve Operating System
// Stephen Marz
// 21 Sep 2019
#![no_std]
#![feature(panic_info_message,
asm,
allocator_api,
alloc_error_handler,
alloc_prelude,
const_raw_ptr_to_usize_cast)]
#[macro_use]
extern crate alloc;
// This is experimental and requires alloc_prelude as a feature
use alloc::prelude::v1::*;
// ///////////////////////////////////
// / RUST MACROS
// ///////////////////////////////////
#[macro_export]
macro_rules! print
{
($($args:tt)+) => ({
use core::fmt::Write;
let _ = write!(crate::uart::Uart::new(0x1000_0000), $($args)+);
});
}
#[macro_export]
macro_rules! println
{
() => ({
print!("\r\n")
});
($fmt:expr) => ({
print!(concat!($fmt, "\r\n"))
});
($fmt:expr, $($args:tt)+) => ({
print!(concat!($fmt, "\r\n"), $($args)+)
});
}
// ///////////////////////////////////
// / LANGUAGE STRUCTURES / FUNCTIONS
// ///////////////////////////////////
#[no_mangle]
extern "C" fn eh_personality() {}
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
print!("Aborting: ");
if let Some(p) = info.location() {
println!(
"line {}, file {}: {}",
p.line(),
p.file(),
info.message().unwrap()
);
}
else {
println!("no information available.");
}
abort();
}
#[no_mangle]
extern "C" fn abort() -> ! {
loop {
unsafe {
asm!("wfi"::::"volatile");
}
}
}
// ///////////////////////////////////
// / CONSTANTS
// ///////////////////////////////////
// const STR_Y: &str = "\x1b[38;2;79;221;13m✓\x1b[m";
// const STR_N: &str = "\x1b[38;2;221;41;13m✘\x1b[m";
// The following symbols come from asm/mem.S. We can use
// the symbols directly, but the address of the symbols
// themselves are their values, which can cause issues.
// Instead, I created doubleword values in mem.S in the .rodata and .data
// sections.
extern "C" {
static TEXT_START: usize;
static TEXT_END: usize;
static DATA_START: usize;
static DATA_END: usize;
static RODATA_START: usize;
static RODATA_END: usize;
static BSS_START: usize;
static BSS_END: usize;
static KERNEL_STACK_START: usize;
static KERNEL_STACK_END: usize;
static HEAP_START: usize;
static HEAP_SIZE: usize;
static mut KERNEL_TABLE: usize;
}
/// Identity map range
/// Takes a contiguous allocation of memory and maps it using PAGE_SIZE
/// This assumes that start <= end
pub fn id_map_range(root: &mut page::Table,
start: usize,
end: usize,
bits: i64)
{
let mut memaddr = start & !(page::PAGE_SIZE - 1);
let num_kb_pages = (page::align_val(end, 12)
- memaddr)
/ page::PAGE_SIZE;
// I named this num_kb_pages for future expansion when
// I decide to allow for GiB (2^30) and 2MiB (2^21) page
// sizes. However, the overlapping memory regions are causing
// nightmares.
for _ in 0..num_kb_pages {
page::map(root, memaddr, memaddr, bits, 0);
memaddr += 1 << 12;
}
}
// ///////////////////////////////////
// / ENTRY POINT
// ///////////////////////////////////
#[no_mangle]
extern "C" fn kinit() -> usize {
// We created kinit, which runs in super-duper mode
// 3 called "machine mode".
// The job of kinit() is to get us into supervisor mode
// as soon as possible.
// Interrupts are disabled for the duration of kinit()
uart::Uart::new(0x1000_0000).init();
page::init();
kmem::init();
// Map heap allocations
let root_ptr = kmem::get_page_table();
let root_u = root_ptr as usize;
let mut root = unsafe { root_ptr.as_mut().unwrap() };
let kheap_head = kmem::get_head() as usize;
let total_pages = kmem::get_num_allocations();
println!();
println!();
unsafe {
println!("TEXT: 0x{:x} -> 0x{:x}", TEXT_START, TEXT_END);
println!("RODATA: 0x{:x} -> 0x{:x}", RODATA_START, RODATA_END);
println!("DATA: 0x{:x} -> 0x{:x}", DATA_START, DATA_END);
println!("BSS: 0x{:x} -> 0x{:x}", BSS_START, BSS_END);
println!("STACK: 0x{:x} -> 0x{:x}", KERNEL_STACK_START, KERNEL_STACK_END);
println!("HEAP: 0x{:x} -> 0x{:x}", kheap_head, kheap_head + total_pages * 4096);
}
id_map_range(
&mut root,
kheap_head,
kheap_head + total_pages * 4096,
page::EntryBits::ReadWrite.val(),
);
unsafe {
// Map heap descriptors
let num_pages = HEAP_SIZE / page::PAGE_SIZE;
id_map_range(&mut root,
HEAP_START,
HEAP_START + num_pages,
page::EntryBits::ReadWrite.val()
);
// Map executable section
id_map_range(
&mut root,
TEXT_START,
TEXT_END,
page::EntryBits::ReadExecute.val(),
);
// Map rodata section
// We put the ROdata section into the text section, so they can
// potentially overlap however, we only care that it's read
// only.
id_map_range(
&mut root,
RODATA_START,
RODATA_END,
page::EntryBits::ReadExecute.val(),
);
// Map data section
id_map_range(
&mut root,
DATA_START,
DATA_END,
page::EntryBits::ReadWrite.val(),
);
// Map bss section
id_map_range(
&mut root,
BSS_START,
BSS_END,
page::EntryBits::ReadWrite.val(),
);
// Map kernel stack
id_map_range(
&mut root,
KERNEL_STACK_START,
KERNEL_STACK_END,
page::EntryBits::ReadWrite.val(),
);
}
// UART
page::map(
&mut root,
0x1000_0000,
0x1000_0000,
page::EntryBits::ReadWrite.val(),
0
);
// CLINT
// -> MSIP
page::map(
&mut root,
0x0200_0000,
0x0200_0000,
page::EntryBits::ReadWrite.val(),
0
);
// -> MTIMECMP
page::map(
&mut root,
0x0200_b000,
0x0200_b000,
page::EntryBits::ReadWrite.val(),
0
);
// -> MTIME
page::map(
&mut root,
0x0200_c000,
0x0200_c000,
page::EntryBits::ReadWrite.val(),
0
);
// PLIC
id_map_range(
&mut root,
0x0c00_0000,
0x0c00_2000,
page::EntryBits::ReadWrite.val(),
);
id_map_range(
&mut root,
0x0c20_0000,
0x0c20_8000,
page::EntryBits::ReadWrite.val(),
);
page::print_page_allocations();
// The following shows how we're going to walk to translate a virtual
// address into a physical address. We will use this whenever a user
// space application requires services. Since the user space application
// only knows virtual addresses, we have to translate silently behind
// the scenes.
let p = 0x8005_7000 as usize;
let m = page::virt_to_phys(&root, p).unwrap_or(0);
println!("Walk 0x{:x} = 0x{:x}", p, m);
// When we return from here, we'll go back to boot.S and switch into
// supervisor mode We will return the SATP register to be written when
// we return. root_u is the root page table's address. When stored into
// the SATP register, this is divided by 4 KiB (right shift by 12 bits).
// We enable the MMU by setting mode 8. Bits 63, 62, 61, 60 determine
// the mode.
// 0 = Bare (no translation)
// 8 = Sv39
// 9 = Sv48
unsafe {
// We have to store the kernel's table. The tables will be moved back
// and forth between the kernel's table and user applicatons' tables.
KERNEL_TABLE = root_u;
}
// table / 4096 Sv39 mode
(root_u >> 12) | (8 << 60)
}
#[no_mangle]
extern "C" fn kmain() {
// kmain() starts in supervisor mode. So, we should have the trap
// vector setup and the MMU turned on when we get here.
// We initialized my_uart in machine mode under kinit for debugging
// prints, but this just grabs a pointer to it.
let mut my_uart = uart::Uart::new(0x1000_0000);
// Create a new scope so that we can test the global allocator and
// deallocator
{
// We have the global allocator, so let's see if that works!
let k = Box::<u32>::new(100);
println!("Boxed value = {}", *k);
kmem::print_table();
// The following comes from the Rust documentation:
// some bytes, in a vector
let sparkle_heart = vec![240, 159, 146, 150];
// We know these bytes are valid, so we'll use `unwrap()`.
let sparkle_heart = String::from_utf8(sparkle_heart).unwrap();
println!("String = {}", sparkle_heart);
}
// If we get here, the Box, vec, and String should all be freed since
// they go out of scope. This calls their "Drop" trait.
// Now see if we can read stuff:
// Usually we can use #[test] modules in Rust, but it would convolute
// the task at hand, and it requires us to create the testing harness
// since the embedded testing system is part of the "std" library.
loop {
if let Some(c) = my_uart.get() {
match c {
8 => {
// This is a backspace, so we
// essentially have to write a space and
// backup again:
print!("{} {}", 8 as char, 8 as char);
},
10 | 13 => {
// Newline or carriage-return
println!();
},
_ => {
print!("{}", c as char);
},
}
}
}
}
// ///////////////////////////////////
// / RUST MODULES
// ///////////////////////////////////
pub mod kmem;
pub mod page;
pub mod uart;
================================================
FILE: risc_v/chapters/ch3/src/page.rs
================================================
// page.rs
// Memory routines
// Stephen Marz
// 6 October 2019
use core::{mem::size_of, ptr::null_mut};
// ////////////////////////////////
// // Allocation routines
// ////////////////////////////////
extern "C" {
static HEAP_START: usize;
static HEAP_SIZE: usize;
}
// We will use ALLOC_START to mark the start of the actual
// memory we can dish out.
static mut ALLOC_START: usize = 0;
const PAGE_ORDER: usize = 12;
pub const PAGE_SIZE: usize = 1 << 12;
/// Align (set to a multiple of some power of two)
/// This takes an order which is the exponent to 2^order
/// Therefore, all alignments must be made as a power of two.
/// This function always rounds up.
pub const fn align_val(val: usize, order: usize) -> usize {
let o = (1usize << order) - 1;
(val + o) & !o
}
#[repr(u8)]
pub enum PageBits {
Empty = 0,
Taken = 1 << 0,
Last = 1 << 1,
}
impl PageBits {
// We convert PageBits to a u8 a lot, so this is
// for convenience.
pub fn val(self) -> u8 {
self as u8
}
}
// Each page is described by the Page structure. Linux does this
// as well, where each 4096-byte chunk of memory has a structure
// associated with it. However, there structure is much larger.
pub struct Page {
flags: u8,
}
impl Page {
// If this page has been marked as the final allocation,
// this function returns true. Otherwise, it returns false.
pub fn is_last(&self) -> bool {
if self.flags & PageBits::Last.val() != 0 {
true
}
else {
false
}
}
// If the page is marked as being taken (allocated), then
// this function returns true. Otherwise, it returns false.
pub fn is_taken(&self) -> bool {
if self.flags & PageBits::Taken.val() != 0 {
true
}
else {
false
}
}
// This is the opposite of is_taken().
pub fn is_free(&self) -> bool {
!self.is_taken()
}
// Clear the Page structure and all associated allocations.
pub fn clear(&mut self) {
self.flags = PageBits::Empty.val();
}
// Set a certain flag. We ran into trouble here since PageBits
// is an enumeration and we haven't implemented the BitOr Trait
// on it.
pub fn set_flag(&mut self, flag: PageBits) {
self.flags |= flag.val();
}
pub fn clear_flag(&mut self, flag: PageBits) {
self.flags &= !(flag.val());
}
}
/// Initialize the allocation system. There are several ways that we can
/// implement the page allocator:
/// 1. Free list (singly linked list where it starts at the first free
/// allocation) 2. Bookkeeping list (structure contains a taken and length)
/// 3. Allocate one Page structure per 4096 bytes (this is what I chose)
/// 4. Others
pub fn init() {
unsafe {
let num_pages = HEAP_SIZE / PAGE_SIZE;
let ptr = HEAP_START as *mut Page;
// Clear all pages to make sure that they aren't accidentally
// taken
for i in 0..num_pages {
(*ptr.add(i)).clear();
}
// Determine where the actual useful memory starts. This will be
// after all Page structures. We also must align the ALLOC_START
// to a page-boundary (PAGE_SIZE = 4096). ALLOC_START =
// (HEAP_START + num_pages * size_of::<Page>() + PAGE_SIZE - 1)
// & !(PAGE_SIZE - 1);
ALLOC_START = align_val(
HEAP_START
+ num_pages * size_of::<Page,>(),
PAGE_ORDER,
);
}
}
/// Allocate a page or multiple pages
/// pages: the number of PAGE_SIZE pages to allocate
pub fn alloc(pages: usize) -> *mut u8 {
// We have to find a contiguous allocation of pages
assert!(pages > 0);
unsafe {
// We create a Page structure for each page on the heap. We
// actually might have more since HEAP_SIZE moves and so does
// the size of our structure, but we'll only waste a few bytes.
let num_pages = HEAP_SIZE / PAGE_SIZE;
let ptr = HEAP_START as *mut Page;
for i in 0..num_pages - pages {
let mut found = false;
// Check to see if this Page is free. If so, we have our
// first candidate memory address.
if (*ptr.add(i)).is_free() {
// It was FREE! Yay!
found = true;
for j in i..i + pages {
// Now check to see if we have a
// contiguous allocation for all of the
// request pages. If not, we should
// check somewhere else.
if (*ptr.add(j)).is_taken() {
found = false;
break;
}
}
}
// We've checked to see if there are enough contiguous
// pages to form what we need. If we couldn't, found
// will be false, otherwise it will be true, which means
// we've found valid memory we can allocate.
if found {
for k in i..i + pages - 1 {
(*ptr.add(k)).set_flag(PageBits::Taken);
}
// The marker for the last page is
// PageBits::Last This lets us know when we've
// hit the end of this particular allocation.
(*ptr.add(i+pages-1)).set_flag(PageBits::Taken);
(*ptr.add(i+pages-1)).set_flag(PageBits::Last);
// The Page structures themselves aren't the
// useful memory. Instead, there is 1 Page
// structure per 4096 bytes starting at
// ALLOC_START.
return (ALLOC_START + PAGE_SIZE * i)
as *mut u8;
}
}
}
// If we get here, that means that no contiguous allocation was
// found.
null_mut()
}
/// Allocate and zero a page or multiple pages
/// pages: the number of pages to allocate
/// Each page is PAGE_SIZE which is calculated as 1 << PAGE_ORDER
/// On RISC-V, this typically will be 4,096 bytes.
pub fn zalloc(pages: usize) -> *mut u8 {
// Allocate and zero a page.
// First, let's get the allocation
let ret = alloc(pages);
if !ret.is_null() {
let size = (PAGE_SIZE * pages) / 8;
let big_ptr = ret as *mut u64;
for i in 0..size {
// We use big_ptr so that we can force an
// sd (store doubleword) instruction rather than
// the sb. This means 8x fewer stores than before.
// Typically we have to be concerned about remaining
// bytes, but fortunately 4096 % 8 = 0, so we
// won't have any remaining bytes.
unsafe {
(*big_ptr.add(i)) = 0;
}
}
}
ret
}
/// Deallocate a page by its pointer
/// The way we've structured this, it will automatically coalesce
/// contiguous pages.
pub fn dealloc(ptr: *mut u8) {
// Make sure we don't try to free a null pointer.
assert!(!ptr.is_null());
unsafe {
let addr =
HEAP_START + (ptr as usize - ALLOC_START) / PAGE_SIZE;
// Make sure that the address makes sense. The address we
// calculate here is the page structure, not the HEAP address!
assert!(addr >= HEAP_START && addr < HEAP_START + HEAP_SIZE);
let mut p = addr as *mut Page;
// Keep clearing pages until we hit the last page.
while (*p).is_taken() && !(*p).is_last() {
(*p).clear();
p = p.add(1);
}
// If the following assertion fails, it is most likely
// caused by a double-free.
assert!(
(*p).is_last() == true,
"Possible double-free detected! (Not taken found \
before last)"
);
// If we get here, we've taken care of all previous pages and
// we are on the last page.
(*p).clear();
}
}
/// Print all page allocations
/// This is mainly used for debugging.
pub fn print_page_allocations() {
unsafe {
let num_pages = HEAP_SIZE / PAGE_SIZE;
let mut beg = HEAP_START as *const Page;
let end = beg.add(num_pages);
let alloc_beg = ALLOC_START;
let alloc_end = ALLOC_START + num_pages * PAGE_SIZE;
println!();
println!(
"PAGE ALLOCATION TABLE\nMETA: {:p} -> {:p}\nPHYS: \
0x{:x} -> 0x{:x}",
beg, end, alloc_beg, alloc_end
);
println!("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
let mut num = 0;
while beg < end {
if (*beg).is_taken() {
let start = beg as usize;
let memaddr = ALLOC_START
+ (start - HEAP_START)
* PAGE_SIZE;
print!("0x{:x} => ", memaddr);
loop {
num += 1;
if (*beg).is_last() {
let end = beg as usize;
let memaddr = ALLOC_START
+ (end
- HEAP_START)
* PAGE_SIZE
+ PAGE_SIZE - 1;
print!(
"0x{:x}: {:>3} page(s)",
memaddr,
(end - start + 1)
);
println!(".");
break;
}
beg = beg.add(1);
}
}
beg = beg.add(1);
}
println!("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
println!(
"Allocated: {:>5} pages ({:>9} bytes).",
num,
num * PAGE_SIZE
);
println!(
"Free : {:>5} pages ({:>9} bytes).",
num_pages - num,
(num_pages - num) * PAGE_SIZE
);
println!();
}
}
// ////////////////////////////////
// // MMU Routines
// ////////////////////////////////
// Represent (repr) our entry bits as
// unsigned 64-bit integers.
#[repr(i64)]
#[derive(Copy, Clone)]
pub enum EntryBits {
None = 0,
Valid = 1 << 0,
Read = 1 << 1,
Write = 1 << 2,
Execute = 1 << 3,
User = 1 << 4,
Global = 1 << 5,
Access = 1 << 6,
Dirty = 1 << 7,
// Convenience combinations
ReadWrite = 1 << 1 | 1 << 2,
ReadExecute = 1 << 1 | 1 << 3,
ReadWriteExecute = 1 << 1 | 1 << 2 | 1 << 3,
// User Convenience Combinations
UserReadWrite = 1 << 1 | 1 << 2 | 1 << 4,
UserReadExecute = 1 << 1 | 1 << 3 | 1 << 4,
UserReadWriteExecute = 1 << 1 | 1 << 2 | 1 << 3 | 1 << 4,
}
// Helper functions to convert the enumeration
// into an i64, which is what our page table
// entries will be.
impl EntryBits {
pub fn val(self) -> i64 {
self as i64
}
}
// A single entry. We're using an i64 so that
// this will sign-extend rather than zero-extend
// since RISC-V requires that the reserved sections
// take on the most significant bit.
pub struct Entry {
pub entry: i64,
}
// The Entry structure describes one of the 512 entries per table, which is
// described in the RISC-V privileged spec Figure 4.18.
impl Entry {
pub fn is_valid(&self) -> bool {
self.get_entry() & EntryBits::Valid.val() != 0
}
// The first bit (bit index #0) is the V bit for
// valid.
pub fn is_invalid(&self) -> bool {
!self.is_valid()
}
// A leaf has one or more RWX bits set
pub fn is_leaf(&self) -> bool {
self.get_entry() & 0xe != 0
}
pub fn is_branch(&self) -> bool {
!self.is_leaf()
}
pub fn set_entry(&mut self, entry: i64) {
self.entry = entry;
}
pub fn get_entry(&self) -> i64 {
self.entry
}
}
// Table represents a single table, which contains 512 (2^9), 64-bit entries.
pub struct Table {
pub entries: [Entry; 512],
}
impl Table {
pub fn len() -> usize {
512
}
}
/// Map a virtual address to a physical address using 4096-byte page
/// size.
/// root: a mutable reference to the root Table
/// vaddr: The virtual address to map
/// paddr: The physical address to map
/// bits: An OR'd bitset containing the bits the leaf should have.
/// The bits should contain only the following:
/// Read, Write, Execute, User, and/or Global
/// The bits MUST include one or more of the following:
/// Read, Write, Execute
/// The valid bit automatically gets added.
pub fn map(root: &mut Table, vaddr: usize, paddr: usize, bits: i64, level: usize) {
// Make sure that Read, Write, or Execute have been provided
// otherwise, we'll leak memory and always create a page fault.
assert!(bits & 0xe != 0);
// Extract out each VPN from the virtual address
// On the virtual address, each VPN is exactly 9 bits,
// which is why we use the mask 0x1ff = 0b1_1111_1111 (9 bits)
let vpn = [
// VPN[0] = vaddr[20:12]
(vaddr >> 12) & 0x1ff,
// VPN[1] = vaddr[29:21]
(vaddr >> 21) & 0x1ff,
// VPN[2] = vaddr[38:30]
(vaddr >> 30) & 0x1ff,
];
// Just like the virtual address, extract the physical address
// numbers (PPN). However, PPN[2] is different in that it stores
// 26 bits instead of 9. Therefore, we use,
// 0x3ff_ffff = 0b11_1111_1111_1111_1111_1111_1111 (26 bits).
let ppn = [
// PPN[0] = paddr[20:12]
(paddr >> 12) & 0x1ff,
// PPN[1] = paddr[29:21]
(paddr >> 21) & 0x1ff,
// PPN[2] = paddr[55:30]
(paddr >> 30) & 0x3ff_ffff,
];
// We will use this as a floating reference so that we can set
// individual entries as we walk the table.
let mut v = &mut root.entries[vpn[2]];
// Now, we're going to traverse the page table and set the bits
// properly. We expect the root to be valid, however we're required to
// create anything beyond the root.
// In Rust, we create a range iterator using the .. operator.
// The .rev() will reverse the iteration since we need to start with
// VPN[2] The .. operator is inclusive on start but exclusive on end.
// So, (0..2) will iterate 0 and 1.
for i in (level..2).rev() {
if !v.is_valid() {
// Allocate a page
let page = zalloc(1);
// The page is already aligned by 4,096, so store it
// directly The page is stored in the entry shifted
// right by 2 places.
v.set_entry(
(page as i64 >> 2)
| EntryBits::Valid.val(),
);
}
let entry = ((v.get_entry() & !0x3ff) << 2) as *mut Entry;
v = unsafe { entry.add(vpn[i]).as_mut().unwrap() };
}
// When we get here, we should be at VPN[0] and v should be pointing to
// our entry.
// The entry structure is Figure 4.18 in the RISC-V Privileged
// Specification
let entry = (ppn[2] << 28) as i64 | // PPN[2] = [53:28]
(ppn[1] << 19) as i64 | // PPN[1] = [27:19]
(ppn[0] << 10) as i64 | // PPN[0] = [18:10]
bits | // Specified bits, such as User, Read, Write, etc
EntryBits::Valid.val(); // Valid bit
// Set the entry. V should be set to the correct pointer by the loop
// above.
v.set_entry(entry);
}
/// Unmaps and frees all memory associated with a table.
/// root: The root table to start freeing.
/// NOTE: This does NOT free root directly. This must be
/// freed manually.
/// The reason we don't free the root is because it is
/// usually embedded into the Process structure.
pub fn unmap(root: &mut Table) {
// Start with level 2
for lv2 in 0..Table::len() {
let ref entry_lv2 = root.entries[lv2];
if entry_lv2.is_valid() && entry_lv2.is_branch() {
// This is a valid entry, so drill down and free.
let memaddr_lv1 = (entry_lv2.get_entry() & !0x3ff) << 2;
let table_lv1 = unsafe {
// Make table_lv1 a mutable reference instead of a pointer.
(memaddr_lv1 as *mut Table).as_mut().unwrap()
};
for lv1 in 0..Table::len() {
let ref entry_lv1 = table_lv1.entries[lv1];
if entry_lv1.is_valid() && entry_lv1.is_branch()
{
let memaddr_lv0 = (entry_lv1.get_entry()
& !0x3ff) << 2;
// The next level is level 0, which
// cannot have branches, therefore,
// we free here.
dealloc(memaddr_lv0 as *mut u8);
}
}
dealloc(memaddr_lv1 as *mut u8);
}
}
}
/// Walk the page table to convert a virtual address to a
/// physical address.
/// If a page fault would occur, this returns None
/// Otherwise, it returns Some with the physical address.
pub fn virt_to_phys(root: &Table, vaddr: usize) -> Option<usize> {
// Walk the page table pointed to by root
let vpn = [
// VPN[0] = vaddr[20:12]
(vaddr >> 12) & 0x1ff,
// VPN[1] = vaddr[29:21]
(vaddr >> 21) & 0x1ff,
// VPN[2] = vaddr[38:30]
(vaddr >> 30) & 0x1ff,
];
let mut v = &root.entries[vpn[2]];
for i in (0..=2).rev() {
if v.is_invalid() {
// This is an invalid entry, page fault.
break;
}
else if v.is_leaf() {
// According to RISC-V, a leaf can be at any level.
// The offset mask masks off the PPN. Each PPN is 9
// bits and they start at bit #12. So, our formula
// 12 + i * 9
let off_mask = (1 << (12 + i * 9)) - 1;
let vaddr_pgoff = vaddr & off_mask;
let addr = ((v.get_entry() << 2) as usize) & !off_mask;
return Some(addr | vaddr_pgoff);
}
// Set v to the next entry which is pointed to by this
// entry. However, the address was shifted right by 2 places
// when stored in the page table entry, so we shift it left
// to get it back into place.
let entry = ((v.get_entry() & !0x3ff) << 2) as *const Entry;
// We do i - 1 here, however we should get None or Some() above
// before we do 0 - 1 = -1.
v = unsafe { entry.add(vpn[i - 1]).as_ref().unwrap() };
}
// If we get here, we've exhausted all valid tables and haven't
// found a leaf.
None
}
================================================
FILE: risc_v/chapters/ch3/src/uart.rs
================================================
// uart.rs
// UART routines and driver
use core::convert::TryInto;
use core::fmt::Write;
use core::fmt::Error;
pub struct Uart {
base_address: usize,
}
impl Write for Uart {
fn write_str(&mut self, out: &str) -> Result<(), Error> {
for c in out.bytes() {
self.put(c);
}
Ok(())
}
}
impl Uart {
pub fn new(base_address: usize) -> Self {
Uart {
base_address
}
}
pub fn init(&mut self) {
let ptr = self.base_address as *mut u8;
unsafe {
// First, set the word length, which
// are bits 0 and 1 of the line control register (LCR)
// which is at base_address + 3
// We can easily write the value 3 here or 0b11, but I'm
// extending it so that it is clear we're setting two individual
// fields
// Word 0 Word 1
// ~~~~~~ ~~~~~~
let lcr: u8 = (1 << 0) | (1 << 1);
ptr.add(3).write_volatile(lcr);
// Now, enable the FIFO, which is bit index 0 of the FIFO
// control register (FCR at offset 2).
// Again, we can just write 1 here, but when we use left shift,
// it's easier to see that we're trying to write bit index #0.
ptr.add(2).write_volatile(1 << 0);
// Enable receiver buffer interrupts, which is at bit index
// 0 of the interrupt enable register (IER at offset 1).
ptr.add(1).write_volatile(1 << 0);
// If we cared about the divisor, the code below would set the divisor
// from a global clock rate of 22.729 MHz (22,729,000 cycles per second)
// to a signaling rate of 2400 (BAUD). We usually have much faster signalling
// rates nowadays, but this demonstrates what the divisor actually does.
// The formula given in the NS16500A specification for calculating the divisor
// is:
// divisor = ceil( (clock_hz) / (baud_sps x 16) )
// So, we substitute our values and get:
// divisor = ceil( 22_729_000 / (2400 x 16) )
// divisor = ceil( 22_729_000 / 38_400 )
// divisor = ceil( 591.901 ) = 592
// The divisor register is two bytes (16 bits), so we need to split the value
// 592 into two bytes. Typically, we would calculate this based on measuring
// the clock rate, but again, for our purposes [qemu], this doesn't really do
// anything.
let divisor: u16 = 592;
let divisor_least: u8 = (divisor & 0xff).try_into().unwrap();
let divisor_most: u8 = (divisor >> 8).try_into().unwrap();
// Notice that the divisor register DLL (divisor latch least) and DLM (divisor latch most)
// have the same base address as the receiver/transmitter and the interrupt enable register.
// To change what the base address points to, we open the "divisor latch" by writing 1 into
// the Divisor Latch Access Bit (DLAB), which is bit index 7 of the Line Control Register (LCR)
// which is at base_address + 3.
ptr.add(3).write_volatile(lcr | 1 << 7);
// Now, base addresses 0 and 1 point to DLL and DLM, respectively.
// Put the lower 8 bits of the divisor into DLL
ptr.add(0).write_volatile(divisor_least);
ptr.add(1).write_volatile(divisor_most);
// Now that we've written the divisor, we never have to touch this again. In hardware, this
// will divide the global clock (22.729 MHz) into one suitable for 2,400 signals per second.
// So, to once again get access to the RBR/THR/IER registers, we need to close the DLAB bit
// by clearing it to 0.
ptr.add(3).write_volatile(lcr);
}
}
pub fn put(&mut self, c: u8) {
let ptr = self.base_address as *mut u8;
unsafe {
ptr.add(0).write_volatile(c);
}
}
pub fn get(&mut self) -> Option<u8> {
let ptr = self.base_address as *mut u8;
unsafe {
if ptr.add(5).read_volatile() & 1 == 0 {
// The DR bit is 0, meaning no data
None
}
else {
// The DR bit is 1, meaning data!
Some(ptr.add(0).read_volatile())
}
}
}
}
================================================
FILE: risc_v/chapters/ch4/.cargo/config
================================================
[build]
target = "riscv64gc-unknown-none-elf"
[target.riscv64gc-unknown-none-elf]
linker = "riscv64-unknown-linux-gnu-gcc"
================================================
FILE: risc_v/chapters/ch4/.gitignore
================================================
os.elf
target/*
Cargo.lock
hdd.dsk
================================================
FILE: risc_v/chapters/ch4/Cargo.toml
================================================
[package]
name = "sos"
version = "0.1.0"
authors = ["Stephen Marz <stephen.marz@utk.edu>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["staticlib"]
[dependencies]
================================================
FILE: risc_v/chapters/ch4/Makefile
================================================
#####
## BUILD
#####
CC=riscv64-unknown-linux-gnu-gcc
CFLAGS=-Wall -Wextra -pedantic -Wextra -O0 -g
CFLAGS+=-static -ffreestanding -nostdlib -fno-rtti -fno-exceptions
CFLAGS+=-march=rv64gc -mabi=lp64
INCLUDES=
LINKER_SCRIPT=-Tsrc/lds/virt.lds
TYPE=debug
RUST_TARGET=./target/riscv64gc-unknown-none-elf/$(TYPE)
LIBS=-L$(RUST_TARGET)
SOURCES_ASM=$(wildcard src/asm/*.S)
LIB=-lsos -lgcc
OUT=os.elf
#####
## QEMU
#####
QEMU=qemu-system-riscv64
MACH=virt
CPU=rv64
CPUS=4
MEM=128M
DISK=hdd.dsk
# DRIVE= -drive if=none,format=raw,file=$(DISK),id=foo -device virtio-blk-device,scsi=off,drive=foo
DRIVE=
all:
cargo build
$(CC) $(CFLAGS) $(LINKER_SCRIPT) $(INCLUDES) -o $(OUT) $(SOURCES_ASM) $(LIBS) $(LIB)
run: all
$(QEMU) -machine $(MACH) -cpu $(CPU) -smp $(CPUS) -m $(MEM) $(DRIVE) -nographic -serial mon:stdio -bios none -kernel $(OUT)
.PHONY: clean
clean:
cargo clean
rm -f $(OUT)
================================================
FILE: risc_v/chapters/ch4/make_hdd.sh
================================================
#!/bin/sh
dd if=/dev/zero of=hdd.dsk bs=1M count=32
================================================
FILE: risc_v/chapters/ch4/src/asm/boot.S
================================================
# boot.S
# bootloader for SoS
# Stephen Marz
# 8 February 2019
# Disable generation of compressed instructions.
.option norvc
# Define a .text.init section. The .text.init is put at the
# starting address so that the entry _start is put at the RISC-V
# address 0x8000_0000.
.section .text.init
# Execution starts here.
.global _start
_start:
# Disable linker instruction relaxation for the `la` instruction below.
# This disallows the assembler from assuming that `gp` is already initialized.
# This causes the value stored in `gp` to be calculated from `pc`.
# The job of the global pointer is to give the linker the ability to address
# memory relative to GP instead of as an absolute address.
.option push
.option norelax
la gp, _global_pointer
.option pop
# SATP should be zero, but let's make sure. Each HART has its own
# SATP register.
csrw satp, zero
# Any hardware threads (hart) that are not bootstrapping
# need to wait for an IPI
csrr t0, mhartid
bnez t0, 3f
# Set all bytes in the BSS section to zero.
la a0, _bss_start
la a1, _bss_end
bgeu a0, a1, 2f
1:
sd zero, (a0)
addi a0, a0, 8
bltu a0, a1, 1b
2:
# The stack grows from bottom to top, so we put the stack pointer
# to the very end of the stack range.
la sp, _stack_end
# Setting `mstatus` register:
# 0b01 << 11: Machine's previous protection mode is 2 (MPP=2).
li t0, 0b11 << 11
csrw mstatus, t0
# Do not allow interrupts while running kinit
csrw mie, zero
# Machine's exception program counter (MEPC) is set to `kinit`.
la t1, kinit
csrw mepc, t1
# Set the return address to get us into supervisor mode
la ra, 2f
# We use mret here so that the mstatus register is properly updated.
mret
2:
# We set the return address (ra above) to this label. When kinit() is finished
# in Rust, it will return here.
# Setting `mstatus` (supervisor status) register:
# 0b01 << 11 : Previous protection mode is 1 (MPP=01 [Supervisor]).
# 1 << 7 : Previous machine interrupt-enable bit is 1 (MPIE=1 [Enabled])
# 1 << 5 : Previous interrupt-enable bit is 1 (SPIE=1 [Enabled]).
# We set the "previous" bits because the mret will write the current bits
# with the previous bits.
li t0, (0b01 << 11) | (1 << 7) | (1 << 5)
csrw mstatus, t0
# Machine's trap vector base address is set to `m_trap_vector`, for
# "machine" trap vector.
la t2, m_trap_vector
csrw mtvec, t2
# Setting `stvec` (supervisor trap vector) register:
# Essentially this is a function pointer, but the last two bits can be 00 or 01
# 00 : All exceptions set pc to BASE
# 01 : Asynchronous interrupts set pc to BASE + 4 x scause
# la t3, s_trap_vector
# csrw stvec, t3
# Jump to kmain. We put the MPP = 01 for supervisor mode, so after
# mret, we will jump to kmain in supervisor mode.
la t1, kmain
csrw mepc, t1
# Setting `sie` (supervisor interrupt enable) register:
# This register takes the same bits as mideleg
# 1 << 1 : Supervisor software interrupt enable (SSIE=1 [Enabled])
# 1 << 5 : Supervisor timer interrupt enable (STIE=1 [Enabled])
# 1 << 9 : Supervisor external interrupt enable (SEIE=1 [Enabled])
# 0xaaa = MEIP/SEIP and MTIP/STIP and MSIP/SSIP
li t2, 0x888
csrw mie, t2
mret
3:
# Parked harts go here. We need to set these
# to only awaken if it receives a software interrupt,
# which we're going to call the SIPI (Software Intra-Processor Interrupt).
# We call the SIPI by writing the software interrupt into the Core Local Interruptor (CLINT)
# Which is calculated by: base_address + hart * 4
# where base address is 0x0200_0000 (MMIO CLINT base address)
# We only use additional harts to run user-space programs, although this may
# change.
# We divide up the stack so the harts aren't clobbering one another.
la sp, _stack_end
li t0, 0x10000
csrr a0, mhartid
mul t0, t0, a0
sub sp, sp, t0
# The parked harts will be put into machine mode with interrupts enabled.
li t0, 0b11 << 11 | (1 << 7)
csrw mstatus, t0
# Allow for MSIP (Software interrupt). We will write the MSIP from hart #0 to
# awaken these parked harts.
li t3, (1 << 3)
csrw mie, t3
# Machine's exception program counter (MEPC) is set to the Rust initialization
# code and waiting loop.
la t1, kinit_hart
csrw mepc, t1
# Machine's trap vector base address is set to `m_trap_vector`, for
# "machine" trap vector. The Rust initialization routines will give each
# hart its own trap frame. We can use the same trap function and distinguish
# between each hart by looking at the trap frame.
la t2, m_trap_vector
csrw mtvec, t2
# Whenever our hart is done initializing, we want it to return to the waiting
# loop, which is just below mret.
la ra, 4f
# We use mret here so that the mstatus register is properly updated.
mret
4:
# wfi = wait for interrupt. This is a hint to the harts to shut everything needed
# down. However, the RISC-V specification allows for wfi to do nothing. Anyway,
# with QEMU, this will save some CPU!
wfi
j 4b
================================================
FILE: risc_v/chapters/ch4/src/asm/mem.S
================================================
// mem.S
// Importation of linker symbols
.section .rodata
.global HEAP_START
HEAP_START: .dword _heap_start
.global HEAP_SIZE
HEAP_SIZE: .dword _heap_size
.global TEXT_START
TEXT_START: .dword _text_start
.global TEXT_END
TEXT_END: .dword _text_end
.global DATA_START
DATA_START: .dword _data_start
.global DATA_END
DATA_END: .dword _data_end
.global RODATA_START
RODATA_START: .dword _rodata_start
.global RODATA_END
RODATA_END: .dword _rodata_end
.global BSS_START
BSS_START: .dword _bss_start
.global BSS_END
BSS_END: .dword _bss_end
.global KERNEL_STACK_START
KERNEL_STACK_START: .dword _stack_start
.global KERNEL_STACK_END
KERNEL_STACK_END: .dword _stack_end
================================================
FILE: risc_v/chapters/ch4/src/asm/trap.S
================================================
# trap.S
# Trap handler and global context
# Steve Operating System
# Stephen Marz
# 24 February 2019
.option norvc
.altmacro
.set NUM_GP_REGS, 32 # Number of registers per context
.set NUM_FP_REGS, 32
.set REG_SIZE, 8 # Register size (in bytes)
.set MAX_CPUS, 8 # Maximum number of CPUs
# Use macros for saving and restoring multiple registers
.macro save_gp i, basereg=t6
sd x\i, ((\i)*REG_SIZE)(\basereg)
.endm
.macro load_gp i, basereg=t6
ld x\i, ((\i)*REG_SIZE)(\basereg)
.endm
.macro save_fp i, basereg=t6
fsd f\i, ((NUM_GP_REGS+(\i))*REG_SIZE)(\basereg)
.endm
.macro load_fp i, basereg=t6
fld f\i, ((NUM_GP_REGS+(\i))*REG_SIZE)(\basereg)
.endm
.section .text
.global m_trap_vector
# This must be aligned by 4 since the last two bits
# of the mtvec register do not contribute to the address
# of this vector.
.align 4
m_trap_vector:
# All registers are volatile here, we need to save them
# before we do anything.
csrrw t6, mscratch, t6
# csrrw will atomically swap t6 into mscratch and the old
# value of mscratch into t6. This is nice because we just
# switched values and didn't destroy anything -- all atomically!
# in cpu.rs we have a structure of:
# 32 gp regs 0
# 32 fp regs 256
# SATP register 512
# Trap stack 520
# CPU HARTID 528
# We use t6 as the temporary register because it is the very
# bottom register (x31)
.set i, 1
.rept 30
save_gp %i
.set i, i+1
.endr
# Save the actual t6 register, which we swapped into
# mscratch
mv t5, t6
csrr t6, mscratch
save_gp 31, t5
# Restore the kernel trap frame into mscratch
csrw mscratch, t5
# Get ready to go into Rust (trap.rs)
# We don't want to write into the user's stack or whomever
# messed with us here.
csrr a0, mepc
csrr a1, mtval
csrr a2, mcause
csrr a3, mhartid
csrr a4, mstatus
mv a5, t5
ld sp, 520(a5)
call m_trap
# When we get here, we've returned from m_trap, restore registers
# and return.
# m_trap will return the return address via a0.
csrw mepc, a0
# Now load the trap frame back into t6
csrr t6, mscratch
# Restore all GP registers
.set i, 1
.rept 31
load_gp %i
.set i, i+1
.endr
# Since we ran this loop 31 times starting with i = 1,
# the last one loaded t6 back to its original value.
mret
.global make_syscall
make_syscall:
ecall
ret
================================================
FILE: risc_v/chapters/ch4/src/cpu.rs
================================================
// cpu.rs
// CPU and CPU-related routines
// Also contains the kernel's trap frame
// Stephen Marz
// 14 October 2019
use core::ptr::null_mut;
#[repr(usize)]
pub enum SatpMode {
Off = 0,
Sv39 = 8,
Sv48 = 9,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct TrapFrame {
pub regs: [usize; 32], // 0 - 255
pub fregs: [usize; 32], // 256 - 511
pub satp: usize, // 512 - 519
pub trap_stack: *mut u8, // 520
pub hartid: usize, // 528
}
impl TrapFrame {
pub const fn zero() -> Self {
TrapFrame { regs: [0; 32],
fregs: [0; 32],
satp: 0,
trap_stack: null_mut(),
hartid: 0, }
}
}
pub static mut KERNEL_TRAP_FRAME: [TrapFrame; 8] =
[TrapFrame::zero(); 8];
pub const fn build_satp(mode: SatpMode, asid: usize, addr: usize) -> usize {
(mode as usize) << 60
| (asid & 0xffff) << 44
| (addr >> 12) & 0xff_ffff_ffff
}
pub fn mhartid_read() -> usize {
unsafe {
let rval;
asm!("csrr $0, mhartid" :"=r"(rval));
rval
}
}
pub fn mstatus_write(val: usize) {
unsafe {
asm!("csrw mstatus, $0" ::"r"(val));
}
}
pub fn mstatus_read() -> usize {
unsafe {
let rval;
asm!("csrr $0, mstatus":"=r"(rval));
rval
}
}
pub fn stvec_write(val: usize) {
unsafe {
asm!("csrw stvec, $0" ::"r"(val));
}
}
pub fn stvec_read() -> usize {
unsafe {
let rval;
asm!("csrr $0, stvec" :"=r"(rval));
rval
}
}
pub fn mscratch_write(val: usize) {
unsafe {
asm!("csrw mscratch, $0" ::"r"(val));
}
}
pub fn mscratch_read() -> usize {
unsafe {
let rval;
asm!("csrr $0, mscratch" : "=r"(rval));
rval
}
}
pub fn mscratch_swap(to: usize) -> usize {
unsafe {
let from;
asm!("csrrw $0, mscratch, $1" : "=r"(from) : "r"(to));
from
}
}
pub fn sscratch_write(val: usize) {
unsafe {
asm!("csrw sscratch, $0" ::"r"(val));
}
}
pub fn sscratch_read() -> usize {
unsafe {
let rval;
asm!("csrr $0, sscratch" : "=r"(rval));
rval
}
}
pub fn sscratch_swap(to: usize) -> usize {
unsafe {
let from;
asm!("csrrw $0, sscratch, $1" : "=r"(from) : "r"(to));
from
}
}
pub fn sepc_write(val: usize) {
unsafe {
asm!("csrw sepc, $0" :: "r"(val));
}
}
pub fn sepc_read() -> usize {
unsafe {
let rval;
asm!("csrr $0, sepc" :"=r"(rval));
rval
}
}
pub fn satp_write(val: usize) {
unsafe {
asm!("csrw satp, $0" :: "r"(val));
}
}
pub fn satp_read() -> usize {
unsafe {
let rval;
asm!("csrr $0, satp" :"=r"(rval));
rval
}
}
pub fn satp_fence(vaddr: usize, asid: usize) {
unsafe {
asm!("sfence.vma $0, $1" :: "r"(vaddr), "r"(asid));
}
}
pub fn satp_fence_asid(asid: usize) {
unsafe {
asm!("sfence.vma zero, $0" :: "r"(asid));
}
}
================================================
FILE: risc_v/chapters/ch4/src/kmem.rs
================================================
// kmem.rs
// Sub-page level: malloc-like allocation system
// Stephen Marz
// 7 October 2019
use crate::page::{align_val, zalloc, Table, PAGE_SIZE};
use core::{mem::size_of, ptr::null_mut};
#[repr(usize)]
enum AllocListFlags {
Taken = 1 << 63,
}
impl AllocListFlags {
pub fn val(self) -> usize {
self as usize
}
}
struct AllocList {
pub flags_size: usize,
}
impl AllocList {
pub fn is_taken(&self) -> bool {
self.flags_size & AllocListFlags::Taken.val() != 0
}
pub fn is_free(&self) -> bool {
!self.is_taken()
}
pub fn set_taken(&mut self) {
self.flags_size |= AllocListFlags::Taken.val();
}
pub fn set_free(&mut self) {
self.flags_size &= !AllocListFlags::Taken.val();
}
pub fn set_size(&mut self, sz: usize) {
let k = self.is_taken();
self.flags_size = sz & !AllocListFlags::Taken.val();
if k {
self.flags_size |= AllocListFlags::Taken.val();
}
}
pub fn get_size(&self) -> usize {
self.flags_size & !AllocListFlags::Taken.val()
}
}
// This is the head of the allocation. We start here when
// we search for a free memory location.
static mut KMEM_HEAD: *mut AllocList = null_mut();
// In the future, we will have on-demand pages
// so, we need to keep track of our memory footprint to
// see if we actually need to allocate more.
static mut KMEM_ALLOC: usize = 0;
static mut KMEM_PAGE_TABLE: *mut Table = null_mut();
// These functions are safe helpers around an unsafe
// operation.
pub fn get_head() -> *mut u8 {
unsafe { KMEM_HEAD as *mut u8 }
}
pub fn get_page_table() -> *mut Table {
unsafe { KMEM_PAGE_TABLE as *mut Table }
}
pub fn get_num_allocations() -> usize {
unsafe { KMEM_ALLOC }
}
/// Initialize kernel's memory
/// This is not to be used to allocate memory
/// for user processes. If that's the case, use
/// alloc/dealloc from the page crate.
pub fn init() {
unsafe {
// Allocate kernel pages (KMEM_ALLOC)
KMEM_ALLOC = 512;
let k_alloc = zalloc(KMEM_ALLOC);
assert!(!k_alloc.is_null());
KMEM_HEAD = k_alloc as *mut AllocList;
(*KMEM_HEAD).set_free();
(*KMEM_HEAD).set_size(KMEM_ALLOC * PAGE_SIZE);
KMEM_PAGE_TABLE = zalloc(1) as *mut Table;
}
}
/// Allocate sub-page level allocation based on bytes and zero the memory
pub fn kzmalloc(sz: usize) -> *mut u8 {
let size = align_val(sz, 3);
let ret = kmalloc(size);
if !ret.is_null() {
for i in 0..size {
unsafe {
(*ret.add(i)) = 0;
}
}
}
ret
}
/// Allocate sub-page level allocation based on bytes
pub fn kmalloc(sz: usize) -> *mut u8 {
unsafe {
let size = align_val(sz, 3) + size_of::<AllocList>();
let mut head = KMEM_HEAD;
// .add() uses pointer arithmetic, so we type-cast into a u8
// so that we multiply by an absolute size (KMEM_ALLOC *
// PAGE_SIZE).
let tail = (KMEM_HEAD as *mut u8).add(KMEM_ALLOC * PAGE_SIZE)
as *mut AllocList;
while head < tail {
if (*head).is_free() && size <= (*head).get_size() {
let chunk_size = (*head).get_size();
let rem = chunk_size - size;
(*head).set_taken();
if rem > size_of::<AllocList>() {
let next = (head as *mut u8).add(size)
as *mut AllocList;
// There is space remaining here.
(*next).set_free();
(*next).set_size(rem);
(*head).set_size(size);
}
else {
// If we get here, take the entire chunk
(*head).set_size(chunk_size);
}
return head.add(1) as *mut u8;
}
else {
// If we get here, what we saw wasn't a free
// chunk, move on to the next.
head = (head as *mut u8).add((*head).get_size())
as *mut AllocList;
}
}
}
// If we get here, we didn't find any free chunks--i.e. there isn't
// enough memory for this. TODO: Add on-demand page allocation.
null_mut()
}
/// Free a sub-page level allocation
pub fn kfree(ptr: *mut u8) {
unsafe {
if !ptr.is_null() {
let p = (ptr as *mut AllocList).offset(-1);
if (*p).is_taken() {
(*p).set_free();
}
// After we free, see if we can combine adjacent free
// spots to see if we can reduce fragmentation.
coalesce();
}
}
}
/// Merge smaller chunks into a bigger chunk
pub fn coalesce() {
unsafe {
let mut head = KMEM_HEAD;
let tail = (KMEM_HEAD as *mut u8).add(KMEM_ALLOC * PAGE_SIZE)
as *mut AllocList;
while head < tail {
let next = (head as *mut u8).add((*head).get_size())
as *mut AllocList;
if (*head).get_size() == 0 {
// If this happens, then we have a bad heap
// (double free or something). However, that
// will cause an infinite loop since the next
// pointer will never move beyond the current
// location.
break;
}
else if next >= tail {
// We calculated the next by using the size
// given as get_size(), however this could push
// us past the tail. In that case, the size is
// wrong, hence we break and stop doing what we
// need to do.
break;
}
else if (*head).is_free() && (*next).is_free() {
// This means we have adjacent blocks needing to
// be freed. So, we combine them into one
// allocation.
(*head).set_size(
(*head).get_size()
+ (*next).get_size(),
);
}
// If we get here, we might've moved. Recalculate new
// head.
head = (head as *mut u8).add((*head).get_size())
as *mut AllocList;
}
}
}
/// For debugging purposes, print the kmem table
pub fn print_table() {
unsafe {
let mut head = KMEM_HEAD;
let tail = (KMEM_HEAD as *mut u8).add(KMEM_ALLOC * PAGE_SIZE)
as *mut AllocList;
while head < tail {
println!(
"{:p}: Length = {:<10} Taken = {}",
head,
(*head).get_size(),
(*head).is_taken()
);
head = (head as *mut u8).add((*head).get_size())
as *mut AllocList;
}
}
}
// ///////////////////////////////////
// / GLOBAL ALLOCATOR
// ///////////////////////////////////
// The global allocator allows us to use the data structures
// in the core library, such as a linked list or B-tree.
// We want to use these sparingly since we have a coarse-grained
// allocator.
use core::alloc::{GlobalAlloc, Layout};
// The global allocator is a static constant to a global allocator
// structure. We don't need any members because we're using this
// structure just to implement alloc and dealloc.
struct OsGlobalAlloc;
unsafe impl GlobalAlloc for OsGlobalAlloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
// We align to the next page size so that when
// we divide by PAGE_SIZE, we get exactly the number
// of pages necessary.
kzmalloc(layout.size())
}
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
// We ignore layout since our allocator uses ptr_start -> last
// to determine the span of an allocation.
kfree(ptr);
}
}
#[global_allocator]
/// Technically, we don't need the {} at the end, but it
/// reveals that we're creating a new structure and not just
/// copying a value.
static GA: OsGlobalAlloc = OsGlobalAlloc {};
#[alloc_error_handler]
/// If for some reason alloc() in the global allocator gets null_mut(),
/// then we come here. This is a divergent function, so we call panic to
/// let the tester know what's going on.
pub fn alloc_error(l: Layout) -> ! {
panic!(
"Allocator failed to allocate {} bytes with {}-byte alignment.",
l.size(),
l.align()
);
}
================================================
FILE: risc_v/chapters/ch4/src/lds/virt.lds
================================================
/*
virt.lds
Linker script for outputting to RISC-V QEMU "virt" machine.
Stephen Marz
6 October 2019
*/
/*
riscv is the name of the architecture that the linker understands
for any RISC-V target (64-bit or 32-bit).
We will further refine this by using -mabi=lp64 and -march=rv64gc
*/
OUTPUT_ARCH( "riscv" )
/*
We're setting our entry point to a symbol
called _start which is inside of boot.S. This
essentially stores the address of _start as the
"entry point", or where CPU instructions should start
executing.
In the rest of this script, we are going to place _start
right at the beginning of 0x8000_0000 because this is where
the virtual machine and many RISC-V boards will start executing.
*/
ENTRY( _start )
/*
The MEMORY section will explain that we have "ram" that contains
a section that is 'w' (writeable), 'x' (executable), and 'a' (allocatable).
We use '!' to invert 'r' (read-only) and 'i' (initialized). We don't want
our memory to be read-only, and we're stating that it is NOT initialized
at the beginning.
The ORIGIN is the memory address 0x8000_0000. If we look at the virt
spec or the specification for the RISC-V HiFive Unleashed, this is the
starting memory address for our code.
Side note: There might be other boot ROMs at different addresses, but
their job is to get to this point.
Finally LENGTH = 128M tells the linker that we have 128 megabyte of RAM.
The linker will double check this to make sure everything can fit.
The HiFive Unleashed has a lot more RAM than this, but for the virtual
machine, I went with 128M since I think that's enough RAM for now.
We can provide other pieces of memory, such as QSPI, or ROM, but we're
telling the linker script here that we have one pool of RAM.
*/
MEMORY
{
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M
}
/*
PHDRS is short for "program headers", which we specify three here:
text - CPU instructions (executable sections)
data - Global, initialized variables
bss - Global, uninitialized variables (all will be set to 0 by boot.S)
The command PT_LOAD tells the linker that these sections will be loaded
from the file into memory.
We can actually stuff all of these into a single program header, but by
splitting it up into three, we can actually use the other PT_* commands
such as PT_DYNAMIC, PT_INTERP, PT_NULL to tell the linker where to find
additional information.
However, for our purposes, every section will be loaded from the program
headers.
*/
PHDRS
{
text PT_LOAD;
data PT_LOAD;
bss PT_LOAD;
}
/*
We are now going to organize the memory based on which
section it is in. In assembly, we can change the section
with the ".section" directive. However, in C++ and Rust,
CPU instructions go into text, global constants go into
rodata, global initialized variables go into data, and
global uninitialized variables go into bss.
*/
SECTIONS
{
/*
The first part of our RAM layout will be the text section.
Since our CPU instructions are here, and our memory starts at
0x8000_0000, we need our entry point to line up here.
*/
.text : {
/*
PROVIDE allows me to access a symbol called _text_start so
I know where the text section starts in the operating system.
This should not move, but it is here for convenience.
The period '.' tells the linker to set _text_start to the
CURRENT location ('.' = current memory location). This current
memory location moves as we add things.
*/
PROVIDE(_text_start = .);
/*
We are going to layout all text sections here, starting with
.text.init. The asterisk in front of the parentheses means to match
the .text.init section of ANY object file. Otherwise, we can specify
which object file should contain the .text.init section, for example,
boot.o(.text.init) would specifically put the .text.init section of
our bootloader here.
Because we might want to change the name of our files, we'll leave it
with a *.
Inside the parentheses is the name of the section. I created my own
called .text.init to make 100% sure that the _start is put right at the
beginning. The linker will lay this out in the order it receives it:
.text.init first
all .text sections next
any .text.* sections last
.text.* means to match anything after .text. If we didn't already specify
.text.init, this would've matched here. The assembler and linker can place
things in "special" text sections, so we match any we might come across here.
*/
*(.text.init) *(.text .text.*)
/*
Again, with PROVIDE, we're providing a readable symbol called _text_end, which is
set to the memory address AFTER .text.init, .text, and .text.*'s have been added.
*/
PROVIDE(_text_end = .);
/*
The portion after the right brace is in an odd format. However, this is telling the
linker what memory portion to put it in. We labeled our RAM, ram, with the constraints
that it is writeable, allocatable, and executable. The linker will make sure with this
that we can do all of those things.
>ram - This just tells the linker script to put this entire section (.text) into the
ram region of memory. To my knowledge, the '>' does not mean "greater than". Instead,
it is a symbol to let the linker know we want to put this in ram.
AT>ram - This sets the LMA (load memory address) region to the same thing. LMA is the final
translation of a VMA (virtual memory address). With this linker script, we're loading
everything into its physical location. We'll let the kernel copy and sort out the
virtual memory. That's why >ram and AT>ram are continually the same thing.
:text - This tells the linker script to put this into the :text program header. We've only
defined three: text, data, and bss. In this case, we're telling the linker script
to go into the text section.
*/
} >ram AT>ram :text
/*
The global pointer allows the linker to position global variables and constants into
independent positions relative to the gp (global pointer) register. The globals start
after the text sections and are only relevant to the rodata, data, and bss sections.
*/
PROVIDE(_global_pointer = .);
/*
Most compilers create a rodata (read only data) section for global constants. However,
we're going to place ours in the text section. We can actually put this in :data, but
since the .text section is read-only, we can place it there.
NOTE: This doesn't actually do anything, yet. The actual "protection" cannot be done
at link time. Instead, when we program the memory management unit (MMU), we will be
able to choose which bits (R=read, W=write, X=execute) we want each memory segment
to be able to do.
*/
.rodata : {
PROVIDE(_rodata_start = .);
*(.rodata .rodata.*)
PROVIDE(_rodata_end = .);
/*
Again, we're placing the rodata section in the memory segment "ram" and we're putting
it in the :text program header. We don't have one for rodata anyway.
*/
} >ram AT>ram :text
.data : {
/*
. = ALIGN(4096) tells the linker to align the current memory location (which is
0x8000_0000 + text section + rodata section) to 4096 bytes. This is because our paging
system's resolution is 4,096 bytes or 4 KiB.
*/
. = ALIGN(4096);
PROVIDE(_data_start = .);
/*
sdata and data are essentially the same thing. However, compilers usually use the
sdata sections for shorter, quicker loading sections. So, usually critical data
is loaded there. However, we're loading all of this in one fell swoop.
So, we're looking to put all of the following sections under the umbrella .data:
.sdata
.sdata.[anything]
.data
.data.[anything]
...in that order.
*/
*(.sdata .sdata.*) *(.data .data.*)
PROVIDE(_data_end = .);
} >ram AT>ram :data
.bss : {
PROVIDE(_bss_start = .);
*(.sbss .sbss.*) *(.bss .bss.*)
PROVIDE(_bss_end = .);
} >ram AT>ram :bss
/*
The following will be helpful when we allocate the kernel stack (_stack) and
determine where the heap begnis and ends (_heap_start and _heap_start + _heap_size)/
When we do memory allocation, we can use these symbols.
We use the symbols instead of hard-coding an address because this is a floating target.
As we add code, the heap moves farther down the memory and gets shorter.
_memory_start will be set to 0x8000_0000 here. We use ORIGIN(ram) so that it will take
whatever we set the origin of ram to. Otherwise, we'd have to change it more than once
if we ever stray away from 0x8000_0000 as our entry point.
*/
PROVIDE(_memory_start = ORIGIN(ram));
/*
Our kernel stack starts at the end of the bss segment (_bss_end). However, we're allocating
0x80000 bytes (524 KiB) to our kernel stack. This should be PLENTY of space. The reason
we add the memory is because the stack grows from higher memory to lower memory (bottom to top).
Therefore we set the stack at the very bottom of its allocated slot.
When we go to allocate from the stack, we'll subtract the number of bytes we need.
*/
PROVIDE(_stack_start = _bss_end);
PROVIDE(_stack_end = _stack_start + 0x8000);
PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram));
/*
Finally, our heap starts right after the kernel stack. This heap will be used mainly
to dole out memory for user-space applications. However, in some circumstances, it will
be used for kernel memory as well.
We don't align here because we let the kernel determine how it wants to do this.
*/
PROVIDE(_heap_start = _stack_end);
PROVIDE(_heap_size = _memory_end - _heap_start);
}
================================================
FILE: risc_v/chapters/ch4/src/lib.rs
================================================
// Steve Operating System
// Stephen Marz
// 21 Sep 2019
#![no_std]
#![feature(panic_info_message,
asm,
allocator_api,
alloc_error_handler,
alloc_prelude,
const_raw_ptr_to_usize_cast)]
#[macro_use]
extern crate alloc;
// This is experimental and requires alloc_prelude as a feature
use alloc::prelude::v1::*;
// ///////////////////////////////////
// / RUST MACROS
// ///////////////////////////////////
#[macro_export]
macro_rules! print
{
($($args:tt)+) => ({
use core::fmt::Write;
let _ = write!(crate::uart::Uart::new(0x1000_0000), $($args)+);
});
}
#[macro_export]
macro_rules! println
{
() => ({
print!("\r\n")
});
($fmt:expr) => ({
print!(concat!($fmt, "\r\n"))
});
($fmt:expr, $($args:tt)+) => ({
print!(concat!($fmt, "\r\n"), $($args)+)
});
}
// ///////////////////////////////////
// / LANGUAGE STRUCTURES / FUNCTIONS
// ///////////////////////////////////
#[no_mangle]
extern "C" fn eh_personality() {}
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
print!("Aborting: ");
if let Some(p) = info.location() {
println!(
"line {}, file {}: {}",
p.line(),
p.file(),
info.message().unwrap()
);
}
else {
println!("no information available.");
}
abort();
}
#[no_mangle]
extern "C" fn abort() -> ! {
loop {
unsafe {
asm!("wfi"::::"volatile");
}
}
}
// ///////////////////////////////////
// / CONSTANTS
// ///////////////////////////////////
// const STR_Y: &str = "\x1b[38;2;79;221;13m✓\x1b[m";
// const STR_N: &str = "\x1b[38;2;221;41;13m✘\x1b[m";
// The following symbols come from asm/mem.S. We can use
// the symbols directly, but the address of the symbols
// themselves are their values, which can cause issues.
// Instead, I created doubleword values in mem.S in the .rodata and .data
// sections.
extern "C" {
static TEXT_START: usize;
static TEXT_END: usize;
static DATA_START: usize;
static DATA_END: usize;
static RODATA_START: usize;
static RODATA_END: usize;
static BSS_START: usize;
static BSS_END: usize;
static KERNEL_STACK_START: usize;
static KERNEL_STACK_END: usize;
static HEAP_START: usize;
static HEAP_SIZE: usize;
}
/// Identity map range
/// Takes a contiguous allocation of memory and maps it using PAGE_SIZE
/// This assumes that start <= end
pub fn id_map_range(root: &mut page::Table,
start: usize,
end: usize,
bits: i64)
{
let mut memaddr = start & !(page::PAGE_SIZE - 1);
let num_kb_pages =
(page::align_val(end, 12) - memaddr) / page::PAGE_SIZE;
// I named this num_kb_pages for future expansion when
// I decide to allow for GiB (2^30) and 2MiB (2^21) page
// sizes. However, the overlapping memory regions are causing
// nightmares.
for _ in 0..num_kb_pages {
page::map(root, memaddr, memaddr, bits, 0);
memaddr += 1 << 12;
}
}
// ///////////////////////////////////
// / ENTRY POINT
// ///////////////////////////////////
#[no_mangle]
extern "C" fn kinit() {
// We created kinit, which runs in super-duper mode
// 3 called "machine mode".
// The job of kinit() is to get us into supervisor mode
// as soon as possible.
// Interrupts are disabled for the duration of kinit()
uart::Uart::new(0x1000_0000).init();
page::init();
kmem::init();
// Map heap allocations
let root_ptr = kmem::get_page_table();
let root_u = root_ptr as usize;
let mut root = unsafe { root_ptr.as_mut().unwrap() };
let kheap_head = kmem::get_head() as usize;
let total_pages = kmem::get_num_allocations();
println!();
println!();
unsafe {
println!("TEXT: 0x{:x} -> 0x{:x}", TEXT_START, TEXT_END);
println!("RODATA: 0x{:x} -> 0x{:x}", RODATA_START, RODATA_END);
println!("DATA: 0x{:x} -> 0x{:x}", DATA_START, DATA_END);
println!("BSS: 0x{:x} -> 0x{:x}", BSS_START, BSS_END);
println!(
"STACK: 0x{:x} -> 0x{:x}",
KERNEL_STACK_START, KERNEL_STACK_END
);
println!(
"HEAP: 0x{:x} -> 0x{:x}",
kheap_head,
kheap_head + total_pages * page::PAGE_SIZE
);
}
id_map_range(
&mut root,
kheap_head,
kheap_head + total_pages * page::PAGE_SIZE,
page::EntryBits::ReadWrite.val(),
);
// Using statics is inherently unsafe.
unsafe {
// Map heap descriptors
let num_pages = HEAP_SIZE / page::PAGE_SIZE;
id_map_range(
&mut root,
HEAP_START,
HEAP_START + num_pages,
page::EntryBits::ReadWrite.val(),
);
// Map executable section
id_map_range(
&mut root,
TEXT_START,
TEXT_END,
page::EntryBits::ReadExecute.val(),
);
// Map rodata section
// We put the ROdata section into the text section, so they can
// potentially overlap however, we only care that it's read
// only.
id_map_range(
&mut root,
RODATA_START,
RODATA_END,
page::EntryBits::ReadExecute.val(),
);
// Map data section
id_map_range(
&mut root,
DATA_START,
DATA_END,
page::EntryBits::ReadWrite.val(),
);
// Map bss section
id_map_range(
&mut root,
BSS_START,
BSS_END,
page::EntryBits::ReadWrite.val(),
);
// Map kernel stack
id_map_range(
&mut root,
KERNEL_STACK_START,
KERNEL_STACK_END,
page::EntryBits::ReadWrite.val(),
);
}
// UART
id_map_range(
&mut root,
0x1000_0000,
0x1000_0100,
page::EntryBits::ReadWrite.val(),
);
// CLINT
// -> MSIP
id_map_range(
&mut root,
0x0200_0000,
0x0200_ffff,
page::EntryBits::ReadWrite.val(),
);
// PLIC
id_map_range(
&mut root,
0x0c00_0000,
0x0c00_2000,
page::EntryBits::ReadWrite.val(),
);
id_map_range(
&mut root,
0x0c20_0000,
0x0c20_8000,
page::EntryBits::ReadWrite.val(),
);
// When we return from here, we'll go back to boot.S and switch into
// supervisor mode We will return the SATP register to be written when
// we return. root_u is the root page table's address. When stored into
// the SATP register, this is divided by 4 KiB (right shift by 12 bits).
// We enable the MMU by setting mode 8. Bits 63, 62, 61, 60 determine
// the mode.
// 0 = Bare (no translation)
// 8 = Sv39
// 9 = Sv48
// build_satp has these parameters: mode, asid, page table address.
let satp_value = cpu::build_satp(cpu::SatpMode::Sv39, 0, root_u);
unsafe {
// We have to store the kernel's table. The tables will be moved
// back and forth between the kernel's table and user
// applicatons' tables. Note that we're writing the physical address
// of the trap frame.
cpu::mscratch_write(
(&mut cpu::KERNEL_TRAP_FRAME[0]
as *mut cpu::TrapFrame)
as usize,
);
cpu::sscratch_write(cpu::mscratch_read());
cpu::KERNEL_TRAP_FRAME[0].satp = satp_value;
// Move the stack pointer to the very bottom. The stack is
// actually in a non-mapped page. The stack is decrement-before
// push and increment after pop. Therefore, the stack will be
// allocated (decremented) before it is stored.
cpu::KERNEL_TRAP_FRAME[0].trap_stack =
page::zalloc(1).add(page::PAGE_SIZE);
id_map_range(
&mut root,
cpu::KERNEL_TRAP_FRAME[0].trap_stack
.sub(page::PAGE_SIZE,)
as usize,
cpu::KERNEL_TRAP_FRAME[0].trap_stack as usize,
page::EntryBits::ReadWrite.val(),
);
// The trap frame itself is stored in the mscratch register.
id_map_range(
&mut root,
cpu::mscratch_read(),
cpu::mscratch_read()
+ core::mem::size_of::<cpu::TrapFrame,>(),
page::EntryBits::ReadWrite.val(),
);
page::print_page_allocations();
let p = cpu::KERNEL_TRAP_FRAME[0].trap_stack as usize - 1;
let m = page::virt_to_phys(&root, p).unwrap_or(0);
println!("Walk 0x{:x} = 0x{:x}", p, m);
}
// The following shows how we're going to walk to translate a virtual
// address into a physical address. We will use this whenever a user
// space application requires services. Since the user space application
// only knows virtual addresses, we have to translate silently behind
// the scenes.
println!("Setting 0x{:x}", satp_value);
println!("Scratch reg = 0x{:x}", cpu::mscratch_read());
cpu::satp_write(satp_value);
cpu::satp_fence_asid(0);
}
#[no_mangle]
extern "C" fn kinit_hart(hartid: usize) {
// All non-0 harts initialize here.
unsafe {
// We have to store the kernel's table. The tables will be moved
// back and forth between the kernel's table and user
// applicatons' tables.
cpu::mscratch_write(
(&mut cpu::KERNEL_TRAP_FRAME[hartid]
as *mut cpu::TrapFrame)
as usize,
);
// Copy the same mscratch over to the supervisor version of the
// same register.
cpu::sscratch_write(cpu::mscratch_read());
cpu::KERNEL_TRAP_FRAME[hartid].hartid = hartid;
// We can't do the following until zalloc() is locked, but we
// don't have locks, yet :( cpu::KERNEL_TRAP_FRAME[hartid].satp
// = cpu::KERNEL_TRAP_FRAME[0].satp;
// cpu::KERNEL_TRAP_FRAME[hartid].trap_stack = page::zalloc(1);
}
}
#[no_mangle]
extern "C" fn kmain() {
// kmain() starts in supervisor mode. So, we should have the trap
// vector setup and the MMU turned on when we get here.
// We initialized my_uart in machine mode under kinit for debugging
// prints, but this just grabs a pointer to it.
let mut my_uart = uart::Uart::new(0x1000_0000);
// Create a new scope so that we can test the global allocator and
// deallocator
{
// We have the global allocator, so let's see if that works!
let k = Box::<u32>::new(100);
println!("Boxed value = {}", *k);
// The following comes from the Rust documentation:
// some bytes, in a vector
let sparkle_heart = vec![240, 159, 146, 150];
// We know these bytes are valid, so we'll use `unwrap()`.
// This will MOVE the vector.
let sparkle_heart = String::from_utf8(sparkle_heart).unwrap();
println!("String = {}", sparkle_heart);
println!("\n\nAllocations of a box, vector, and string");
kmem::print_table();
}
println!("\n\nEverything should now be free:");
kmem::print_table();
unsafe {
// Set the next machine timer to fire.
let mtimecmp = 0x0200_4000 as *mut u64;
let mtime = 0x0200_bff8 as *const u64;
// The frequency given by QEMU is 10_000_000 Hz, so this sets
// the next interrupt to fire one second from now.
mtimecmp.write_volatile(mtime.read_volatile() + 10_000_000);
// Let's cause a page fault and see what happens. This should trap
// to m_trap under trap.rs
let v = 0x0 as *mut u64;
v.write_volatile(0);
}
// If we get here, the Box, vec, and String should all be freed since
// they go out of scope. This calls their "Drop" trait.
// Now see if we can read stuff:
// Usually we can use #[test] modules in Rust, but it would convolute
// the task at hand, and it requires us to create the testing harness
// since the embedded testing system is part of the "std" library.
loop {
if let Some(c) = my_uart.get() {
match c {
8 => {
// This is a backspace, so we
// essentially have to write a space and
// backup again:
print!("{} {}", 8 as char, 8 as char);
},
10 | 13 => {
// Newline or carriage-return
println!();
},
_ => {
print!("{}", c as char);
},
}
}
}
}
// ///////////////////////////////////
// / RUST MODULES
// ///////////////////////////////////
pub mod cpu;
pub mod kmem;
pub mod page;
pub mod trap;
pub mod uart;
================================================
FILE: risc_v/chapters/ch4/src/page.rs
================================================
// page.rs
// Memory routines
// Stephen Marz
// 6 October 2019
use core::{mem::size_of, ptr::null_mut};
// ////////////////////////////////
// // Allocation routines
// ////////////////////////////////
extern "C" {
static HEAP_START: usize;
static HEAP_SIZE: usize;
}
// We will use ALLOC_START to mark the start of the actual
// memory we can dish out.
static mut ALLOC_START: usize = 0;
const PAGE_ORDER: usize = 12;
pub const PAGE_SIZE: usize = 1 << 12;
/// Align (set to a multiple of some power of two)
/// This takes an order which is the exponent to 2^order
/// Therefore, all alignments must be made as a power of two.
/// This function always rounds up.
pub const fn align_val(val: usize, order: usize) -> usize {
let o = (1usize << order) - 1;
(val + o) & !o
}
#[repr(u8)]
pub enum PageBits {
Empty = 0,
Taken = 1 << 0,
Last = 1 << 1,
}
impl PageBits {
// We convert PageBits to a u8 a lot, so this is
// for convenience.
pub fn val(self) -> u8 {
self as u8
}
}
// Each page is described by the Page structure. Linux does this
// as well, where each 4096-byte chunk of memory has a structure
// associated with it. However, there structure is much larger.
pub struct Page {
flags: u8,
}
impl Page {
// If this page has been marked as the final allocation,
// this function returns true. Otherwise, it returns false.
pub fn is_last(&self) -> bool {
if self.flags & PageBits::Last.val() != 0 {
true
}
else {
false
}
}
// If the page is marked as being taken (allocated), then
// this function returns true. Otherwise, it returns false.
pub fn is_taken(&self) -> bool {
if self.flags & PageBits::Taken.val() != 0 {
true
}
else {
false
}
}
// This is the opposite of is_taken().
pub fn is_free(&self) -> bool {
!self.is_taken()
}
// Clear the Page structure and all associated allocations.
pub fn clear(&mut self) {
self.flags = PageBits::Empty.val();
}
// Set a certain flag. We ran into trouble here since PageBits
// is an enumeration and we haven't implemented the BitOr Trait
// on it.
pub fn set_flag(&mut self, flag: PageBits) {
self.flags |= flag.val();
}
pub fn clear_flag(&mut self, flag: PageBits) {
self.flags &= !(flag.val());
}
}
/// Initialize the allocation system. There are several ways that we can
/// implement the page allocator:
/// 1. Free list (singly linked list where it starts at the first free
/// allocation) 2. Bookkeeping list (structure contains a taken and length)
/// 3. Allocate one Page structure per 4096 bytes (this is what I chose)
/// 4. Others
pub fn init() {
unsafe {
// let desc_per_page = PAGE_SIZE / size_of::<Page>();
let num_pages = HEAP_SIZE / PAGE_SIZE;
// let num_desc_pages = num_pages / desc_per_page;
let ptr = HEAP_START as *mut Page;
// Clear all pages to make sure that they aren't accidentally
// taken
for i in 0..num_pages {
(*ptr.add(i)).clear();
}
// Determine where the actual useful memory starts. This will be
// after all Page structures. We also must align the ALLOC_START
// to a page-boundary (PAGE_SIZE = 4096). ALLOC_START =
// (HEAP_START + num_pages * size_of::<Page>() + PAGE_SIZE - 1)
// & !(PAGE_SIZE - 1);
ALLOC_START = align_val(
HEAP_START
+ num_pages * size_of::<Page>(),
PAGE_ORDER,
);
}
}
/// Allocate a page or multiple pages
/// pages: the number of PAGE_SIZE pages to allocate
pub fn alloc(pages: usize) -> *mut u8 {
// We have to find a contiguous allocation of pages
assert!(pages > 0);
unsafe {
// We create a Page structure for each page on the heap. We
// actually might have more since HEAP_SIZE moves and so does
// the size of our structure, but we'll only waste a few bytes.
let num_pages = HEAP_SIZE / PAGE_SIZE;
let ptr = HEAP_START as *mut Page;
for i in 0..num_pages - pages {
let mut found = false;
// Check to see if this Page is free. If so, we have our
// first candidate memory address.
if (*ptr.add(i)).is_free() {
// It was FREE! Yay!
found = true;
for j in i..i + pages {
// Now check to see if we have a
// contiguous allocation for all of the
// request pages. If not, we should
// check somewhere else.
if (*ptr.add(j)).is_taken() {
found = false;
break;
}
}
}
// We've checked to see if there are enough contiguous
// pages to form what we need. If we couldn't, found
// will be false, otherwise it will be true, which means
// we've found valid memory we can allocate.
if found {
for k in i..i + pages - 1 {
(*ptr.add(k)).set_flag(PageBits::Taken);
}
// The marker for the last page is
// PageBits::Last This lets us know when we've
// hit the end of this particular allocation.
(*ptr.add(i+pages-1)).set_flag(PageBits::Taken);
(*ptr.add(i+pages-1)).set_flag(PageBits::Last);
// The Page structures themselves aren't the
// useful memory. Instead, there is 1 Page
// structure per 4096 bytes starting at
// ALLOC_START.
return (ALLOC_START + PAGE_SIZE * i)
as *mut u8;
}
}
}
// If we get here, that means that no contiguous allocation was
// found.
null_mut()
}
/// Allocate and zero a page or multiple pages
/// pages: the number of pages to allocate
/// Each page is PAGE_SIZE which is calculated as 1 << PAGE_ORDER
/// On RISC-V, this typically will be 4,096 bytes.
pub fn zalloc(pages: usize) -> *mut u8 {
// Allocate and zero a page.
// First, let's get the allocation
let ret = alloc(pages);
if !ret.is_null() {
let size = (PAGE_SIZE * pages) / 8;
let big_ptr = ret as *mut u64;
for i in 0..size {
// We use big_ptr so that we can force an
// sd (store doubleword) instruction rather than
// the sb. This means 8x fewer stores than before.
// Typically we have to be concerned about remaining
// bytes, but fortunately 4096 % 8 = 0, so we
// won't have any remaining bytes.
unsafe {
(*big_ptr.add(i)) = 0;
}
}
}
ret
}
/// Deallocate a page by its pointer
/// The way we've structured this, it will automatically coalesce
/// contiguous pages.
pub fn dealloc(ptr: *mut u8) {
// Make sure we don't try to free a null pointer.
assert!(!ptr.is_null());
unsafe {
let addr =
HEAP_START + (ptr as usize - ALLOC_START) / PAGE_SIZE;
// Make sure that the address makes sense. The address we
// calculate here is the page structure, not the HEAP address!
assert!(addr >= HEAP_START && addr < HEAP_START + HEAP_SIZE);
let mut p = addr as *mut Page;
// Keep clearing pages until we hit the last page.
while (*p).is_taken() && !(*p).is_last() {
(*p).clear();
p = p.add(1);
}
// If the following assertion fails, it is most likely
// caused by a double-free.
assert!(
(*p).is_last() == true,
"Possible double-free detected! (Not taken found \
before last)"
);
// If we get here, we've taken care of all previous pages and
// we are on the last page.
(*p).clear();
}
}
/// Print all page allocations
/// This is mainly used for debugging.
pub fn print_page_allocations() {
unsafe {
let num_pages = (HEAP_SIZE - (ALLOC_START - HEAP_START)) / PAGE_SIZE;
let mut beg = HEAP_START as *const Page;
let end = beg.add(num_pages);
let alloc_beg = ALLOC_START;
let alloc_end = ALLOC_START + num_pages * PAGE_SIZE;
println!();
println!(
"PAGE ALLOCATION TABLE\nMETA: {:p} -> {:p}\nPHYS: \
0x{:x} -> 0x{:x}",
beg, end, alloc_beg, alloc_end
);
println!("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
let mut num = 0;
while beg < end {
if (*beg).is_taken() {
let start = beg as usize;
let memaddr = ALLOC_START
+ (start - HEAP_START)
* PAGE_SIZE;
print!("0x{:x} => ", memaddr);
loop {
num += 1;
if (*beg).is_last() {
let end = beg as usize;
let memaddr = ALLOC_START
+ (end
- HEAP_START)
* PAGE_SIZE
+ PAGE_SIZE - 1;
print!(
"0x{:x}: {:>3} page(s)",
memaddr,
(end - start + 1)
);
println!(".");
break;
}
beg = beg.add(1);
}
}
beg = beg.add(1);
}
println!("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
println!(
"Allocated: {:>6} pages ({:>10} bytes).",
num,
num * PAGE_SIZE
);
println!(
"Free : {:>6} pages ({:>10} bytes).",
num_pages - num,
(num_pages - num) * PAGE_SIZE
);
println!();
}
}
// ////////////////////////////////
// // MMU Routines
// ////////////////////////////////
// Represent (repr) our entry bits as
// unsigned 64-bit integers.
#[repr(i64)]
#[derive(Copy, Clone)]
pub enum EntryBits {
None = 0,
Valid = 1 << 0,
Read = 1 << 1,
Write = 1 << 2,
Execute = 1 << 3,
User = 1 << 4,
Global = 1 << 5,
Access = 1 << 6,
Dirty = 1 << 7,
// Convenience combinations
ReadWrite = 1 << 1 | 1 << 2,
ReadExecute = 1 << 1 | 1 << 3,
ReadWriteExecute = 1 << 1 | 1 << 2 | 1 << 3,
// User Convenience Combinations
UserReadWrite = 1 << 1 | 1 << 2 | 1 << 4,
UserReadExecute = 1 << 1 | 1 << 3 | 1 << 4,
UserReadWriteExecute = 1 << 1 | 1 << 2 | 1 << 3 | 1 << 4,
}
// Helper functions to convert the enumeration
// into an i64, which is what our page table
// entries will be.
impl EntryBits {
pub fn val(self) -> i64 {
self as i64
}
}
// A single entry. We're using an i64 so that
// this will sign-extend rather than zero-extend
// since RISC-V requires that the reserved sections
// take on the most significant bit.
pub struct Entry {
pub entry: i64,
}
// The Entry structure describes one of the 512 entries per table, which is
// described in the RISC-V privileged spec Figure 4.18.
impl Entry {
pub fn is_valid(&self) -> bool {
self.get_entry() & EntryBits::Valid.val() != 0
}
// The first bit (bit index #0) is the V bit for
// valid.
pub fn is_invalid(&self) -> bool {
!self.is_valid()
}
// A leaf has one or more RWX bits set
pub fn is_leaf(&self) -> bool {
self.get_entry() & 0xe != 0
}
pub fn is_branch(&self) -> bool {
!self.is_leaf()
}
pub fn set_entry(&mut self, entry: i64) {
self.entry = entry;
}
pub fn get_entry(&self) -> i64 {
self.entry
}
}
// Table represents a single table, which contains 512 (2^9), 64-bit entries.
pub struct Table {
pub entries: [Entry; 512],
}
impl Table {
pub fn len() -> usize {
512
}
}
/// Map a virtual address to a physical address using 4096-byte page
/// size.
/// root: a mutable reference to the root Table
/// vaddr: The virtual address to map
/// paddr: The physical address to map
/// bits: An OR'd bitset containing the bits the leaf should have.
/// The bits should contain only the following:
/// Read, Write, Execute, User, and/or Global
/// The bits MUST include one or more of the following:
/// Read, Write, Execute
/// The valid bit automatically gets added.
pub fn map(root: &mut Table,
vaddr: usize,
paddr: usize,
bits: i64,
level: usize)
{
// Make sure that Read, Write, or Execute have been provided
// otherwise, we'll leak memory and always create a page fault.
assert!(bits & 0xe != 0);
// Extract out each VPN from the virtual address
// On the virtual address, each VPN is exactly 9 bits,
// which is why we use the mask 0x1ff = 0b1_1111_1111 (9 bits)
let vpn = [
// VPN[0] = vaddr[20:12]
(vaddr >> 12) & 0x1ff,
// VPN[1] = vaddr[29:21]
(vaddr >> 21) & 0x1ff,
// VPN[2] = vaddr[38:30]
(vaddr >> 30) & 0x1ff,
];
// Just like the virtual address, extract the physical address
// numbers (PPN). However, PPN[2] is different in that it stores
// 26 bits instead of 9. Therefore, we use,
// 0x3ff_ffff = 0b11_1111_1111_1111_1111_1111_1111 (26 bits).
let ppn = [
// PPN[0] = paddr[20:12]
(paddr >> 12) & 0x1ff,
// PPN[1] = paddr[29:21]
(paddr >> 21) & 0x1ff,
// PPN[2] = paddr[55:30]
(paddr >> 30) & 0x3ff_ffff,
];
// We will use this as a floating reference so that we can set
// individual entries as we walk the table.
let mut v = &mut root.entries[vpn[2]];
// Now, we're going to traverse the page table and set the bits
// properly. We expect the root to be valid, however we're required to
// create anything beyond the root.
// In Rust, we create a range iterator using the .. operator.
// The .rev() will reverse the iteration since we need to start with
// VPN[2] The .. operator is inclusive on start but exclusive on end.
// So, (0..2) will iterate 0 and 1.
for i in (level..2).rev() {
if !v.is_valid() {
// Allocate a page
let page = zalloc(1);
// The page is already aligned by 4,096, so store it
// directly The page is stored in the entry shifted
// right by 2 places.
v.set_entry(
(page as i64 >> 2)
| EntryBits::Valid.val(),
);
}
let entry = ((v.get_entry() & !0x3ff) << 2) as *mut Entry;
v = unsafe { entry.add(vpn[i]).as_mut().unwrap() };
}
// When we get here, we should be at VPN[0] and v should be pointing to
// our entry.
// The entry structure is Figure 4.18 in the RISC-V Privileged
// Specification
let entry = (ppn[2] << 28) as i64 | // PPN[2] = [53:28]
(ppn[1] << 19) as i64 | // PPN[1] = [27:19]
(ppn[0] << 10) as i64 | // PPN[0] = [18:10]
bits | // Specified bits, such as User, Read, Write, etc
EntryBits::Valid.val() | // Valid bit
EntryBits::Dirty.val() | // Some machines require this to =1
EntryBits::Access.val() // Just like dirty, some machines require this
;
// Set the entry. V should be set to the correct pointer by the loop
// above.
v.set_entry(entry);
}
/// Unmaps and frees all memory associated with a table.
/// root: The root table to start freeing.
/// NOTE: This does NOT free root directly. This must be
/// freed manually.
/// The reason we don't free the root is because it is
/// usually embedded into the Process structure.
pub fn unmap(root: &mut Table) {
// Start with level 2
for lv2 in 0..Table::len() {
let ref entry_lv2 = root.entries[lv2];
if entry_lv2.is_valid() && entry_lv2.is_branch() {
// This is a valid entry, so drill down and free.
let memaddr_lv1 = (entry_lv2.get_entry() & !0x3ff) << 2;
let table_lv1 = unsafe {
// Make table_lv1 a mutable reference instead of
// a pointer.
(memaddr_lv1 as *mut Table).as_mut().unwrap()
};
for lv1 in 0..Table::len() {
let ref entry_lv1 = table_lv1.entries[lv1];
if entry_lv1.is_valid() && entry_lv1.is_branch()
{
let memaddr_lv0 = (entry_lv1.get_entry()
& !0x3ff) << 2;
// The next level is level 0, which
// cannot have branches, therefore,
// we free here.
dealloc(memaddr_lv0 as *mut u8);
}
}
dealloc(memaddr_lv1 as *mut u8);
}
}
}
/// Walk the page table to convert a virtual address to a
/// physical address.
/// If a page fault would occur, this returns None
/// Otherwise, it returns Some with the physical address.
pub fn virt_to_phys(root: &Table, vaddr: usize) -> Option<usize> {
// Walk the page table pointed to by root
let vpn = [
// VPN[0] = vaddr[20:12]
(vaddr >> 12) & 0x1ff,
// VPN[1] = vaddr[29:21]
(vaddr >> 21) & 0x1ff,
// VPN[2] = vaddr[38:30]
(vaddr >> 30) & 0x1ff,
];
let mut v = &root.entries[vpn[2]];
for i in (0..=2).rev() {
if v.is_invalid() {
// This is an invalid entry, page fault.
break;
}
else if v.is_leaf() {
// According to RISC-V, a leaf can be at any level.
// The offset mask masks off the PPN. Each PPN is 9
// bits and they start at bit #12. So, our formula
// 12 + i * 9
let off_mask = (1 << (12 + i * 9)) - 1;
let vaddr_pgoff = vaddr & off_mask;
let addr = ((v.get_entry() << 2) as usize) & !off_mask;
return Some(addr | vaddr_pgoff);
}
// Set v to the next entry which is pointed to by this
// entry. However, the address was shifted right by 2 places
// when stored in the page table entry, so we shift it left
// to get it back into place.
let entry = ((v.get_entry() & !0x3ff) << 2) as *const Entry;
// We do i - 1 here, however we should get None or Some() above
// before we do 0 - 1 = -1.
v = unsafe { entry.add(vpn[i - 1]).as_ref().unwrap() };
}
// If we get here, we've exhausted all valid tables and haven't
// found a leaf.
None
}
================================================
FILE: risc_v/chapters/ch4/src/trap.rs
================================================
// trap.rs
// Trap routines
// Stephen Marz
// 10 October 2019
use crate::cpu::TrapFrame;
#[no_mangle]
extern "C" fn m_trap(epc: usize,
tval: usize,
cause: usize,
hart: usize,
status: usize,
frame: &mut TrapFrame)
-> usize
{
// We're going to handle all traps in machine mode. RISC-V lets
// us delegate to supervisor mode, but switching out SATP (virtual memory)
// gets hairy.
let is_async = {
if cause >> 63 & 1 == 1 {
true
}
else {
false
}
};
// The cause contains the type of trap (sync, async) as well as the cause
// number. So, here we narrow down just the cause number.
let cause_num = cause & 0xfff;
let mut return_pc = epc;
if is_async {
// Asynchronous trap
match cause_num {
3 => {
// Machine software
println!("Machine software interrupt CPU#{}", hart);
},
7 => unsafe {
// Machine timer
let mtimecmp = 0x0200_4000 as *mut u64;
let mtime = 0x0200_bff8 as *const u64;
// The frequency given by QEMU is 10_000_000 Hz, so this sets
// the next interrupt to fire one second from now.
mtimecmp.write_volatile(mtime.read_volatile() + 10_000_000);
},
11 => {
// Machine external (interrupt from Platform Interrupt Controller (PLIC))
println!("Machine external interrupt CPU#{}", hart);
},
_ => {
panic!("Unhandled async trap CPU#{} -> {}\n", hart, cause_num);
}
}
}
else {
// Synchronous trap
match cause_num {
2 => {
// Illegal instruction
panic!("Illegal instruction CPU#{} -> 0x{:08x}: 0x{:08x}\n", hart, epc, tval);
},
8 => {
// Environment (system) call from User mode
println!("E-call from User mode! CPU#{} -> 0x{:08x}", hart, epc);
return_pc += 4;
},
9 => {
// Environment (system) call from Supervisor mode
println!("E-call from Supervisor mode! CPU#{} -> 0x{:08x}", hart, epc);
return_pc += 4;
},
11 => {
// Environment (system) call from Machine mode
panic!("E-call from Machine mode! CPU#{} -> 0x{:08x}\n", hart, epc);
},
// Page faults
12 => {
// Instruction page fault
println!("Instruction page fault CPU#{} -> 0x{:08x}: 0x{:08x}", hart, epc, tval);
return_pc += 4;
},
13 => {
// Load page fault
println!("Load page fault CPU#{} -> 0x{:08x}: 0x{:08x}", hart, epc, tval);
return_pc += 4;
},
15 => {
// Store page fault
println!("Store page fault CPU#{} -> 0x{:08x}: 0x{:08x}", hart, epc, tval);
return_pc += 4;
},
_ => {
panic!("Unhandled sync trap CPU#{} -> {}\n", hart, cause_num);
}
}
};
// Finally, return the updated program counter
return_pc
}
================================================
FILE: risc_v/chapters/ch4/src/uart.rs
================================================
// uart.rs
// UART routines and driver
use core::{convert::TryInto,
fmt::{Error, Write}};
pub struct Uart {
base_address: usize,
}
impl Write for Uart {
fn write_str(&mut self, out: &str) -> Result<(), Error> {
for c in out.bytes() {
self.put(c);
}
Ok(())
}
}
impl Uart {
pub fn new(base_address: usize) -> Self {
Uart { base_address }
}
pub fn init(&mut self) {
let ptr = self.base_address as *mut u8;
unsafe {
// First, set the word length, which
// are bits 0 and 1 of the line control register (LCR)
// which is at base_address + 3
// We can easily write the value 3 here or 0b11, but I'm
// extending it so that it is clear we're setting two
// individual fields
// Word 0 Word 1
// ~~~~~~ ~~~~~~
let lcr: u8 = (1 << 0) | (1 << 1);
ptr.add(3).write_volatile(lcr);
// Now, enable the FIFO, which is bit index 0 of the
// FIFO control register (FCR at offset 2).
// Again, we can just write 1 here, but when we use left
// shift, it's easier to see that we're trying to write
// bit index #0.
ptr.add(2).write_volatile(1 << 0);
// Enable receiver buffer interrupts, which is at bit
// index 0 of the interrupt enable register (IER at
// offset 1).
ptr.add(1).write_volatile(1 << 0);
// If we cared about the divisor, the code below would
// set the divisor from a global clock rate of 22.729
// MHz (22,729,000 cycles per second) to a signaling
// rate of 2400 (BAUD). We usually have much faster
// signalling rates nowadays, but this demonstrates what
// the divisor actually does. The formula given in the
// NS16500A specification for calculating the divisor
// is:
// divisor = ceil( (clock_hz) / (baud_sps x 16) )
// So, we substitute our values and get:
// divisor = ceil( 22_729_000 / (2400 x 16) )
// divisor = ceil( 22_729_000 / 38_400 )
// divisor = ceil( 591.901 ) = 592
// The divisor register is two bytes (16 bits), so we
// need to split the value 592 into two bytes.
// Typically, we would calculate this based on measuring
// the clock rate, but again, for our purposes [qemu],
// this doesn't really do anything.
let divisor: u16 = 592;
let divisor_least: u8 =
(divisor & 0xff).try_into().unwrap();
let divisor_most: u8 =
(divisor >> 8).try_into().unwrap();
// Notice that the divisor register DLL (divisor latch
// least) and DLM (divisor latch most) have the same
// base address as the receiver/transmitter and the
// interrupt enable register. To change what the base
// address points to, we open the "divisor latch" by
// writing 1 into the Divisor Latch Access Bit (DLAB),
// which is bit index 7 of the Line Control Register
// (LCR) which is at base_address + 3.
ptr.add(3).write_volatile(lcr | 1 << 7);
// Now, base addresses 0 and 1 point to DLL and DLM,
// respectively. Put the lower 8 bits of the divisor
// into DLL
ptr.add(0).write_volatile(divisor_least);
ptr.add(1).write_volatile(divisor_most);
// Now that we've written the divisor, we never have to
// touch this again. In hardware, this will divide the
// global clock (22.729 MHz) into one suitable for 2,400
// signals per second. So, to once again get access to
// the RBR/THR/IER registers, we need to close the DLAB
// bit by clearing it to 0.
ptr.add(3).write_volatile(lcr);
}
}
pub fn put(&mut self, c: u8) {
let ptr = self.base_address as *mut u8;
unsafe {
ptr.add(0).write_volatile(c);
}
}
pub fn get(&mut self) -> Option<u8> {
let ptr = self.base_address as *mut u8;
unsafe {
if ptr.add(5).read_volatile() & 1 == 0 {
// The DR bit is 0, meaning no data
None
}
else {
// The DR bit is 1, meaning data!
Some(ptr.add(0).read_volatile())
}
}
}
}
================================================
FILE: risc_v/chapters/ch5/.cargo/config
================================================
[build]
target = "riscv64gc-unknown-none-elf"
[target.riscv64gc-unknown-none-elf]
linker = "riscv64-unknown-linux-gnu-gcc"
================================================
FILE: risc_v/chapters/ch5/.gitignore
================================================
os.elf
target/*
Cargo.lock
hdd.dsk
================================================
FILE: risc_v/chapters/ch5/Cargo.toml
================================================
[package]
name = "sos"
version = "0.1.0"
authors = ["Stephen Marz <stephen.marz@utk.edu>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["staticlib"]
[dependencies]
================================================
FILE: risc_v/chapters/ch5/Makefile
================================================
#####
## BUILD
#####
CC=riscv64-unknown-linux-gnu-gcc
CFLAGS=-Wall -Wextra -pedantic -Wextra -O0 -g
CFLAGS+=-static -ffreestanding -nostdlib -fno-rtti -fno-exceptions
CFLAGS+=-march=rv64gc -mabi=lp64
INCLUDES=
LINKER_SCRIPT=-Tsrc/lds/virt.lds
TYPE=debug
RUST_TARGET=./target/riscv64gc-unknown-none-elf/$(TYPE)
LIBS=-L$(RUST_TARGET)
SOURCES_ASM=$(wildcard src/asm/*.S)
LIB=-lsos -lgcc
OUT=os.elf
#####
## QEMU
#####
QEMU=qemu-system-riscv64
MACH=virt
CPU=rv64
CPUS=4
MEM=128M
DISK=hdd.dsk
# DRIVE= -drive if=none,format=raw,file=$(DISK),id=foo -device virtio-blk-device,scsi=off,drive=foo
DRIVE=
all:
cargo build
$(CC) $(CFLAGS) $(LINKER_SCRIPT) $(INCLUDES) -o $(OUT) $(SOURCES_ASM) $(LIBS) $(LIB)
run: all
$(QEMU) -machine $(MACH) -cpu $(CPU) -smp $(CPUS) -m $(MEM) $(DRIVE) -nographic -serial mon:stdio -bios none -kernel $(OUT)
.PHONY: clean
clean:
cargo clean
rm -f $(OUT)
================================================
FILE: risc_v/chapters/ch5/make_hdd.sh
================================================
#!/bin/sh
dd if=/dev/zero of=hdd.dsk bs=1M count=32
================================================
FILE: risc_v/chapters/ch5/src/asm/boot.S
================================================
# boot.S
# bootloader for SoS
# Stephen Marz
# 8 February 2019
# Disable generation of compressed instructions.
.option norvc
# Define a .text.init section. The .text.init is put at the
# starting address so that the entry _start is put at the RISC-V
# address 0x8000_0000.
.section .text.init
# Execution starts here.
.global _start
_start:
# Disable linker instruction relaxation for the `la` instruction below.
# This disallows the assembler from assuming that `gp` is already initialized.
# This causes the value stored in `gp` to be calculated from `pc`.
# The job of the global pointer is to give the linker the ability to address
# memory relative to GP instead of as an absolute address.
.option push
.option norelax
la gp, _global_pointer
.option pop
# SATP should be zero, but let's make sure. Each HART has its own
# SATP register.
csrw satp, zero
# Any hardware threads (hart) that are not bootstrapping
# need to wait for an IPI
csrr t0, mhartid
bnez t0, 3f
# Set all bytes in the BSS section to zero.
la a0, _bss_start
la a1, _bss_end
bgeu a0, a1, 2f
1:
sd zero, (a0)
addi a0, a0, 8
bltu a0, a1, 1b
2:
# The stack grows from bottom to top, so we put the stack pointer
# to the very end of the stack range.
la sp, _stack_end
# Setting `mstatus` register:
# 0b01 << 11: Machine's previous protection mode is 2 (MPP=2).
li t0, 0b11 << 11
csrw mstatus, t0
# Do not allow interrupts while running kinit
csrw mie, zero
# Machine's exception program counter (MEPC) is set to `kinit`.
la t1, kinit
csrw mepc, t1
# Set the return address to get us into supervisor mode
la ra, 2f
# We use mret here so that the mstatus register is properly updated.
mret
2:
# We set the return address (ra above) to this label. When kinit() is finished
# in Rust, it will return here.
# Setting `mstatus` (supervisor status) register:
# 0b01 << 11 : Previous protection mode is 1 (MPP=01 [Supervisor]).
# 1 << 7 : Previous machine interrupt-enable bit is 1 (MPIE=1 [Enabled])
# 1 << 5 : Previous interrupt-enable bit is 1 (SPIE=1 [Enabled]).
# We set the "previous" bits because the mret will write the current bits
# with the previous bits.
li t0, (0b01 << 11) | (1 << 7) | (1 << 5)
csrw mstatus, t0
# Machine's trap vector base address is set to `m_trap_vector`, for
# "machine" trap vector.
la t2, m_trap_vector
csrw mtvec, t2
# Setting `stvec` (supervisor trap vector) register:
# Essentially this is a function pointer, but the last two bits can be 00 or 01
# 00 : All exceptions set pc to BASE
# 01 : Asynchronous interrupts set pc to BASE + 4 x scause
# la t3, s_trap_vector
# csrw stvec, t3
# Jump to kmain. We put the MPP = 01 for supervisor mode, so after
# mret, we will jump to kmain in supervisor mode.
la t1, kmain
csrw mepc, t1
# Setting `sie` (supervisor interrupt enable) register:
# This register takes the same bits as mideleg
# 1 << 1 : Supervisor software interrupt enable (SSIE=1 [Enabled])
# 1 << 5 : Supervisor timer interrupt enable (STIE=1 [Enabled])
# 1 << 9 : Supervisor external interrupt enable (SEIE=1 [Enabled])
# 0xaaa = MEIP/SEIP and MTIP/STIP and MSIP/SSIP
li t2, 0xaaa
csrw mie, t2
la ra, 4f
mret
3:
# Parked harts go here. We need to set these
# to only awaken if it receives a software interrupt,
# which we're going to call the SIPI (Software Intra-Processor Interrupt).
# We call the SIPI by writing the software interrupt into the Core Local Interruptor (CLINT)
# Which is calculated by: base_address + hart * 4
# where base address is 0x0200_0000 (MMIO CLINT base address)
# We only use additional harts to run user-space programs, although this may
# change.
# We divide up the stack so the harts aren't clobbering one another.
la sp, _stack_end
li t0, 0x10000
csrr a0, mhartid
mul t0, t0, a0
sub sp, sp, t0
# The parked harts will be put into machine mode with interrupts enabled.
li t0, 0b11 << 11 | (1 << 7)
csrw mstatus, t0
# Allow for MSIP (Software interrupt). We will write the MSIP from hart #0 to
# awaken these parked harts.
li t3, (1 << 3)
csrw mie, t3
# Machine's exception program counter (MEPC) is set to the Rust initialization
# code and waiting loop.
la t1, kinit_hart
csrw mepc, t1
# Machine's trap vector base address is set to `m_trap_vector`, for
# "machine" trap vector. The Rust initialization routines will give each
# hart its own trap frame. We can use the same trap function and distinguish
# between each hart by looking at the trap frame.
la t2, m_trap_vector
csrw mtvec, t2
# Whenever our hart is done initializing, we want it to return to the waiting
# loop, which is just below mret.
la ra, 4f
# We use mret here so that the mstatus register is properly updated.
mret
4:
# wfi = wait for interrupt. This is a hint to the harts to shut everything needed
# down. However, the RISC-V specification allows for wfi to do nothing. Anyway,
# with QEMU, this will save some CPU!
wfi
j 4b
================================================
FILE: risc_v/chapters/ch5/src/asm/mem.S
================================================
// mem.S
// Importation of linker symbols
.section .rodata
.global HEAP_START
HEAP_START: .dword _heap_start
.global HEAP_SIZE
HEAP_SIZE: .dword _heap_size
.global TEXT_START
TEXT_START: .dword _text_start
.global TEXT_END
TEXT_END: .dword _text_end
.global DATA_START
DATA_START: .dword _data_start
.global DATA_END
DATA_END: .dword _data_end
.global RODATA_START
RODATA_START: .dword _rodata_start
.global RODATA_END
RODATA_END: .dword _rodata_end
.global BSS_START
BSS_START: .dword _bss_start
.global BSS_END
BSS_END: .dword _bss_end
.global KERNEL_STACK_START
KERNEL_STACK_START: .dword _stack_start
.global KERNEL_STACK_END
KERNEL_STACK_END: .dword _stack_end
================================================
FILE: risc_v/chapters/ch5/src/asm/trap.S
================================================
# trap.S
# Trap handler and global context
# Steve Operating System
# Stephen Marz
# 24 February 2019
.option norvc
.altmacro
.set NUM_GP_REGS, 32 # Number of registers per context
.set NUM_FP_REGS, 32
.set REG_SIZE, 8 # Register size (in bytes)
.set MAX_CPUS, 8 # Maximum number of CPUs
# Use macros for saving and restoring multiple registers
.macro save_gp i, basereg=t6
sd x\i, ((\i)*REG_SIZE)(\basereg)
.endm
.macro load_gp i, basereg=t6
ld x\i, ((\i)*REG_SIZE)(\basereg)
.endm
.macro save_fp i, basereg=t6
fsd f\i, ((NUM_GP_REGS+(\i))*REG_SIZE)(\basereg)
.endm
.macro load_fp i, basereg=t6
fld f\i, ((NUM_GP_REGS+(\i))*REG_SIZE)(\basereg)
.endm
.section .text
.global m_trap_vector
# This must be aligned by 4 since the last two bits
# of the mtvec register do not contribute to the address
# of this vector.
.align 4
m_trap_vector:
# All registers are volatile here, we need to save them
# before we do anything.
csrrw t6, mscratch, t6
# csrrw will atomically swap t6 into mscratch and the old
# value of mscratch into t6. This is nice because we just
# switched values and didn't destroy anything -- all atomically!
# in cpu.rs we have a structure of:
# 32 gp regs 0
# 32 fp regs 256
# SATP register 512
# Trap stack 520
# CPU HARTID 528
# We use t6 as the temporary register because it is the very
# bottom register (x31)
.set i, 1
.rept 30
save_gp %i
.set i, i+1
.endr
# Save the actual t6 register, which we swapped into
# mscratch
mv t5, t6
csrr t6, mscratch
save_gp 31, t5
# Restore the kernel trap frame into mscratch
csrw mscratch, t5
# Get ready to go into Rust (trap.rs)
# We don't want to write into the user's stack or whomever
# messed with us here.
csrr a0, mepc
csrr a1, mtval
csrr a2, mcause
csrr a3, mhartid
csrr a4, mstatus
mv a5, t5
ld sp, 520(a5)
call m_trap
# When we get here, we've returned from m_trap, restore registers
# and return.
# m_trap will return the return address via a0.
csrw mepc, a0
# Now load the trap frame back into t6
csrr t6, mscratch
# Restore all GP registers
.set i, 1
.rept 31
load_gp %i
.set i, i+1
.endr
# Since we ran this loop 31 times starting with i = 1,
# the last one loaded t6 back to its original value.
mret
.global make_syscall
make_syscall:
ecall
ret
================================================
FILE: risc_v/chapters/ch5/src/cpu.rs
================================================
// cpu.rs
// CPU and CPU-related routines
// Also contains the kernel's trap frame
// Stephen Marz
// 14 October 2019
use core::ptr::null_mut;
/// In 64-bit mode, we're given three different modes for the MMU:
/// 0 - The MMU is off -- no protection and no translation PA = VA
/// 8 - This is Sv39 mode -- 39-bit virtual addresses
/// 9 - This is Sv48 mode -- 48-bit virtual addresses
#[repr(usize)]
pub enum SatpMode {
Off = 0,
Sv39 = 8,
Sv48 = 9,
}
/// The trap frame is set into a structure
/// and packed into each hart's mscratch register.
/// This allows for quick reference and full
/// context switch handling.
#[repr(C)]
#[derive(Clone, Copy)]
pub struct TrapFrame {
pub regs: [usize; 32], // 0 - 255
pub fregs: [usize; 32], // 256 - 511
pub satp: usize, // 512 - 519
pub trap_stack: *mut u8, // 520
pub hartid: usize, // 528
}
/// Rust requires that we initialize our structures
/// because of the move semantics. What'll happen below
/// is Rust will construct a new TrapFrame and move it
/// out of the zero() function below. Rust contains two
/// different "selfs" where self can refer to the object
/// in memory or Self (capital S) which refers to the
/// data type of the structure. In the case below, this
/// is TrapFrame.
impl TrapFrame {
pub const fn zero() -> Self {
TrapFrame { regs: [0; 32],
fregs: [0; 32],
satp: 0,
trap_stack: null_mut(),
hartid: 0, }
}
}
/// The global kernel trap frame stores 8 separate
/// frames -- one per CPU hart. We will switch these
/// in and out and store a dormant trap frame with
/// the process itself.
pub static mut KERNEL_TRAP_FRAME: [TrapFrame; 8] =
[TrapFrame::zero(); 8];
/// The SATP register contains three fields: mode, address space id, and
/// the first level table address (level 2 for Sv39). This function
/// helps make the 64-bit register contents based on those three
/// fields.
pub const fn build_satp(mode: SatpMode, asid: usize, addr: usize) -> usize {
(mode as usize) << 60
| (asid & 0xffff) << 44
| (addr >> 12) & 0xff_ffff_ffff
}
pub fn mhartid_read() -> usize {
unsafe {
let rval;
asm!("csrr $0, mhartid" :"=r"(rval));
rval
}
}
pub fn mie_read() -> usize {
unsafe {
let rval;
asm!("csrr $0, mie" :"=r"(rval));
rval
}
}
pub fn mie_write(val: usize) {
unsafe {
asm!("csrw mie, $0" :: "r"(val));
}
}
pub fn mstatus_write(val: usize) {
unsafe {
asm!("csrw mstatus, $0" ::"r"(val));
}
}
pub fn mstatus_read() -> usize {
unsafe {
let rval;
asm!("csrr $0, mstatus":"=r"(rval));
rval
}
}
pub fn stvec_write(val: usize) {
unsafe {
asm!("csrw stvec, $0" ::"r"(val));
}
}
pub fn stvec_read() -> usize {
unsafe {
let rval;
asm!("csrr $0, stvec" :"=r"(rval));
rval
}
}
pub fn mscratch_write(val: usize) {
unsafe {
asm!("csrw mscratch, $0" ::"r"(val));
}
}
pub fn mscratch_read() -> usize {
unsafe {
let rval;
asm!("csrr $0, mscratch" : "=r"(rval));
rval
}
}
pub fn mscratch_swap(to: usize) -> usize {
unsafe {
let from;
asm!("csrrw $0, mscratch, $1" : "=r"(from) : "r"(to));
from
}
}
pub fn sscratch_write(val: usize) {
unsafe {
asm!("csrw sscratch, $0" ::"r"(val));
}
}
pub fn sscratch_read() -> usize {
unsafe {
let rval;
asm!("csrr $0, sscratch" : "=r"(rval));
rval
}
}
pub fn sscratch_swap(to: usize) -> usize {
unsafe {
let from;
asm!("csrrw $0, sscratch, $1" : "=r"(from) : "r"(to));
from
}
}
pub fn sepc_write(val: usize) {
unsafe {
asm!("csrw sepc, $0" :: "r"(val));
}
}
pub fn sepc_read() -> usize {
unsafe {
let rval;
asm!("csrr $0, sepc" :"=r"(rval));
rval
}
}
pub fn satp_write(val: usize) {
unsafe {
asm!("csrw satp, $0" :: "r"(val));
}
}
pub fn satp_read() -> usize {
unsafe {
let rval;
asm!("csrr $0, satp" :"=r"(rval));
rval
}
}
/// Take a hammer to the page tables and synchronize
/// all of them. This essentially flushes the entire
/// TLB.
pub fn satp_fence(vaddr: usize, asid: usize) {
unsafe {
asm!("sfence.vma $0, $1" :: "r"(vaddr), "r"(asid));
}
}
/// Synchronize based on the address space identifier
/// This allows us to fence a particular process rather
/// than the entire TLB.
/// The RISC-V documentation calls this a TLB flush +.
/// Since there are other memory routines involved, they
/// didn't call it a TLB flush, but it is much like
/// Intel/AMD's invtlb [] instruction.
pub fn satp_fence_asid(asid: usize) {
unsafe {
asm!("sfence.vma zero, $0" :: "r"(asid));
}
}
================================================
FILE: risc_v/chapters/ch5/src/kmem.rs
================================================
// kmem.rs
// Sub-page level: malloc-like allocation system
// Stephen Marz
// 7 October 2019
use crate::page::{align_val, zalloc, Table, PAGE_SIZE};
use core::{mem::size_of, ptr::null_mut};
#[repr(usize)]
enum AllocListFlags {
Taken = 1 << 63,
}
impl AllocListFlags {
pub fn val(self) -> usize {
self as usize
}
}
struct AllocList {
pub flags_size: usize,
}
impl AllocList {
pub fn is_taken(&self) -> bool {
self.flags_size & AllocListFlags::Taken.val() != 0
}
pub fn is_free(&self) -> bool {
!self.is_taken()
}
pub fn set_taken(&mut self) {
self.flags_size |= AllocListFlags::Taken.val();
}
pub fn set_free(&mut self) {
self.flags_size &= !AllocListFlags::Taken.val();
}
pub fn set_size(&mut self, sz: usize) {
let k = self.is_taken();
self.flags_size = sz & !AllocListFlags::Taken.val();
if k {
self.flags_size |= AllocListFlags::Taken.val();
}
}
pub fn get_size(&self) -> usize {
self.flags_size & !AllocListFlags::Taken.val()
}
}
// This is the head of the allocation. We start here when
// we search for a free memory location.
static mut KMEM_HEAD: *mut AllocList = null_mut();
// In the future, we will have on-demand pages
// so, we need to keep track of our memory footprint to
// see if we actually need to allocate more.
static mut KMEM_ALLOC: usize = 0;
static mut KMEM_PAGE_TABLE: *mut Table = null_mut();
// These functions are safe helpers around an unsafe
// operation.
pub fn get_head() -> *mut u8 {
unsafe { KMEM_HEAD as *mut u8 }
}
pub fn get_page_table() -> *mut Table {
unsafe { KMEM_PAGE_TABLE as *mut Table }
}
pub fn get_num_allocations() -> usize {
unsafe { KMEM_ALLOC }
}
/// Initialize kernel's memory
/// This is not to be used to allocate memory
/// for user processes. If that's the case, use
/// alloc/dealloc from the page crate.
pub fn init() {
unsafe {
// Allocate kernel pages (KMEM_ALLOC)
KMEM_ALLOC = 512;
let k_alloc = zalloc(KMEM_ALLOC);
assert!(!k_alloc.is_null());
KMEM_HEAD = k_alloc as *mut AllocList;
(*KMEM_HEAD).set_free();
(*KMEM_HEAD).set_size(KMEM_ALLOC * PAGE_SIZE);
KMEM_PAGE_TABLE = zalloc(1) as *mut Table;
}
}
/// Allocate sub-page level allocation based on bytes and zero the memory
pub fn kzmalloc(sz: usize) -> *mut u8 {
let size = align_val(sz, 3);
let ret = kmalloc(size);
if !ret.is_null() {
for i in 0..size {
unsafe {
(*ret.add(i)) = 0;
}
}
}
ret
}
/// Allocate sub-page level allocation based on bytes
pub fn kmalloc(sz: usize) -> *mut u8 {
unsafe {
let size = align_val(sz, 3) + size_of::<AllocList>();
let mut head = KMEM_HEAD;
// .add() uses pointer arithmetic, so we type-cast into a u8
// so that we multiply by an absolute size (KMEM_ALLOC *
// PAGE_SIZE).
let tail = (KMEM_HEAD as *mut u8).add(KMEM_ALLOC * PAGE_SIZE)
as *mut AllocList;
while head < tail {
if (*head).is_free() && size <= (*head).get_size() {
let chunk_size = (*head).get_size();
let rem = chunk_size - size;
(*head).set_taken();
if rem > size_of::<AllocList>() {
let next = (head as *mut u8).add(size)
as *mut AllocList;
// There is space remaining here.
(*next).set_free();
(*next).set_size(rem);
(*head).set_size(size);
}
else {
// If we get here, take the entire chunk
(*head).set_size(chunk_size);
}
return head.add(1) as *mut u8;
}
else {
// If we get here, what we saw wasn't a free
// chunk, move on to the next.
head = (head as *mut u8).add((*head).get_size())
as *mut AllocList;
}
}
}
// If we get here, we didn't find any free chunks--i.e. there isn't
// enough memory for this. TODO: Add on-demand page allocation.
null_mut()
}
/// Free a sub-page level allocation
pub fn kfree(ptr: *mut u8) {
unsafe {
if !ptr.is_null() {
let p = (ptr as *mut AllocList).offset(-1);
if (*p).is_taken() {
(*p).set_free();
}
// After we free, see if we can combine adjacent free
// spots to see if we can reduce fragmentation.
coalesce();
}
}
}
/// Merge smaller chunks into a bigger chunk
pub fn coalesce() {
unsafe {
let mut head = KMEM_HEAD;
let tail = (KMEM_HEAD as *mut u8).add(KMEM_ALLOC * PAGE_SIZE)
as *mut AllocList;
while head < tail {
let next = (head as *mut u8).add((*head).get_size())
as *mut AllocList;
if (*head).get_size() == 0 {
// If this happens, then we have a bad heap
// (double free or something). However, that
// will cause an infinite loop since the next
// pointer will never move beyond the current
// location.
break;
}
else if next >= tail {
// We calculated the next by using the size
// given as get_size(), however this could push
// us past the tail. In that case, the size is
// wrong, hence we break and stop doing what we
// need to do.
break;
}
else if (*head).is_free() && (*next).is_free() {
// This means we have adjacent blocks needing to
// be freed. So, we combine them into one
// allocation.
(*head).set_size(
(*head).get_size()
+ (*next).get_size(),
);
}
// If we get here, we might've moved. Recalculate new
// head.
head = (head as *mut u8).add((*head).get_size())
as *mut AllocList;
}
}
}
/// For debugging purposes, print the kmem table
pub fn print_table() {
unsafe {
let mut head = KMEM_HEAD;
let tail = (KMEM_HEAD as *mut u8).add(KMEM_ALLOC * PAGE_SIZE)
as *mut AllocList;
while head < tail {
println!(
"{:p}: Length = {:<10} Taken = {}",
head,
(*head).get_size(),
(*head).is_taken()
);
head = (head as *mut u8).add((*head).get_size())
as *mut AllocList;
}
}
}
// ///////////////////////////////////
// / GLOBAL ALLOCATOR
// ///////////////////////////////////
// The global allocator allows us to use the data structures
// in the core library, such as a linked list or B-tree.
// We want to use these sparingly since we have a coarse-grained
// allocator.
use core::alloc::{GlobalAlloc, Layout};
// The global allocator is a static constant to a global allocator
// structure. We don't need any members because we're using this
// structure just to implement alloc and dealloc.
struct OsGlobalAlloc;
unsafe impl GlobalAlloc for OsGlobalAlloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
// We align to the next page size so that when
// we divide by PAGE_SIZE, we get exactly the number
// of pages necessary.
kzmalloc(layout.size())
}
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
// We ignore layout since our allocator uses ptr_start -> last
// to determine the span of an allocation.
kfree(ptr);
}
}
#[global_allocator]
/// Technically, we don't need the {} at the end, but it
/// reveals that we're creating a new structure and not just
/// copying a value.
static GA: OsGlobalAlloc = OsGlobalAlloc {};
#[alloc_error_handler]
/// If for some reason alloc() in the global allocator gets null_mut(),
/// then we come here. This is a divergent function, so we call panic to
/// let the tester know what's going on.
pub fn alloc_error(l: Layout) -> ! {
panic!(
"Allocator failed to allocate {} bytes with {}-byte alignment.",
l.size(),
l.align()
);
}
================================================
FILE: risc_v/chapters/ch5/src/lds/virt.lds
================================================
/*
virt.lds
Linker script fo
gitextract_71jihpoi/
├── LICENSE
├── README.md
├── assembly/
│ ├── .gitignore
│ ├── intrin.S
│ └── intrin.c
└── risc_v/
├── .cargo/
│ └── config
├── .gitignore
├── BUILD.md
├── Cargo.toml
├── chapters/
│ ├── ch0/
│ │ ├── .build.config
│ │ ├── .cargo/
│ │ │ └── config
│ │ ├── Cargo.toml
│ │ ├── Makefile
│ │ ├── do.sh
│ │ └── virt.lds
│ ├── ch1/
│ │ ├── .cargo/
│ │ │ └── config
│ │ ├── .gitignore
│ │ ├── Cargo.toml
│ │ ├── Makefile
│ │ ├── make_hdd.sh
│ │ └── src/
│ │ ├── asm/
│ │ │ ├── boot.S
│ │ │ └── trap.S
│ │ ├── lds/
│ │ │ └── virt.lds
│ │ └── lib.rs
│ ├── ch2/
│ │ ├── .cargo/
│ │ │ └── config
│ │ ├── .gitignore
│ │ ├── Cargo.toml
│ │ ├── Makefile
│ │ ├── make_hdd.sh
│ │ └── src/
│ │ ├── asm/
│ │ │ ├── boot.S
│ │ │ └── trap.S
│ │ ├── lds/
│ │ │ └── virt.lds
│ │ ├── lib.rs
│ │ └── uart.rs
│ ├── ch3/
│ │ ├── .cargo/
│ │ │ └── config
│ │ ├── .gitignore
│ │ ├── Cargo.toml
│ │ ├── Makefile
│ │ ├── make_hdd.sh
│ │ └── src/
│ │ ├── asm/
│ │ │ ├── boot.S
│ │ │ ├── mem.S
│ │ │ └── trap.S
│ │ ├── kmem.rs
│ │ ├── lds/
│ │ │ └── virt.lds
│ │ ├── lib.rs
│ │ ├── page.rs
│ │ └── uart.rs
│ ├── ch4/
│ │ ├── .cargo/
│ │ │ └── config
│ │ ├── .gitignore
│ │ ├── Cargo.toml
│ │ ├── Makefile
│ │ ├── make_hdd.sh
│ │ └── src/
│ │ ├── asm/
│ │ │ ├── boot.S
│ │ │ ├── mem.S
│ │ │ └── trap.S
│ │ ├── cpu.rs
│ │ ├── kmem.rs
│ │ ├── lds/
│ │ │ └── virt.lds
│ │ ├── lib.rs
│ │ ├── page.rs
│ │ ├── trap.rs
│ │ └── uart.rs
│ ├── ch5/
│ │ ├── .cargo/
│ │ │ └── config
│ │ ├── .gitignore
│ │ ├── Cargo.toml
│ │ ├── Makefile
│ │ ├── make_hdd.sh
│ │ └── src/
│ │ ├── asm/
│ │ │ ├── boot.S
│ │ │ ├── mem.S
│ │ │ └── trap.S
│ │ ├── cpu.rs
│ │ ├── kmem.rs
│ │ ├── lds/
│ │ │ └── virt.lds
│ │ ├── lib.rs
│ │ ├── page.rs
│ │ ├── plic.rs
│ │ ├── trap.rs
│ │ └── uart.rs
│ ├── ch6/
│ │ ├── .cargo/
│ │ │ └── config
│ │ ├── .gitignore
│ │ ├── Cargo.toml
│ │ ├── Makefile
│ │ ├── make_hdd.sh
│ │ └── src/
│ │ ├── asm/
│ │ │ ├── boot.S
│ │ │ ├── mem.S
│ │ │ └── trap.S
│ │ ├── cpu.rs
│ │ ├── kmem.rs
│ │ ├── lds/
│ │ │ └── virt.lds
│ │ ├── lib.rs
│ │ ├── page.rs
│ │ ├── plic.rs
│ │ ├── process.rs
│ │ ├── trap.rs
│ │ └── uart.rs
│ ├── ch7/
│ │ ├── .cargo/
│ │ │ └── config
│ │ ├── .gitignore
│ │ ├── Cargo.toml
│ │ ├── Makefile
│ │ ├── make_hdd.sh
│ │ └── src/
│ │ ├── asm/
│ │ │ ├── boot.S
│ │ │ ├── mem.S
│ │ │ └── trap.S
│ │ ├── cpu.rs
│ │ ├── kmem.rs
│ │ ├── lds/
│ │ │ └── virt.lds
│ │ ├── lib.rs
│ │ ├── page.rs
│ │ ├── plic.rs
│ │ ├── process.rs
│ │ ├── sched.rs
│ │ ├── syscall.rs
│ │ ├── trap.rs
│ │ └── uart.rs
│ ├── ch8/
│ │ ├── .cargo/
│ │ │ └── config
│ │ ├── .gitignore
│ │ ├── Cargo.toml
│ │ ├── Makefile
│ │ ├── make_hdd.sh
│ │ └── src/
│ │ ├── asm/
│ │ │ ├── boot.S
│ │ │ ├── mem.S
│ │ │ └── trap.S
│ │ ├── cpu.rs
│ │ ├── kmem.rs
│ │ ├── lds/
│ │ │ └── virt.lds
│ │ ├── lib.rs
│ │ ├── page.rs
│ │ ├── plic.rs
│ │ ├── process.rs
│ │ ├── sched.rs
│ │ ├── syscall.rs
│ │ ├── trap.rs
│ │ └── uart.rs
│ └── ch9/
│ ├── .cargo/
│ │ └── config
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── Makefile
│ ├── make_hdd.sh
│ └── src/
│ ├── asm/
│ │ ├── boot.S
│ │ ├── mem.S
│ │ └── trap.S
│ ├── block.rs
│ ├── cpu.rs
│ ├── kmem.rs
│ ├── lds/
│ │ └── virt.lds
│ ├── lib.rs
│ ├── page.rs
│ ├── plic.rs
│ ├── process.rs
│ ├── rng.rs
│ ├── sched.rs
│ ├── syscall.rs
│ ├── trap.rs
│ ├── uart.rs
│ └── virtio.rs
├── src/
│ ├── asm/
│ │ ├── boot.S
│ │ ├── mem.S
│ │ └── trap.S
│ ├── assembly.rs
│ ├── block.rs
│ ├── buffer.rs
│ ├── console.rs
│ ├── cpu.rs
│ ├── elf.rs
│ ├── fs.rs
│ ├── gpu.rs
│ ├── input.rs
│ ├── kmem.rs
│ ├── lds/
│ │ └── virt.lds
│ ├── lock.rs
│ ├── main.rs
│ ├── page.rs
│ ├── plic.rs
│ ├── process.rs
│ ├── rng.rs
│ ├── sched.rs
│ ├── syscall.rs
│ ├── test.rs
│ ├── trap.rs
│ ├── uart.rs
│ ├── vfs.rs
│ └── virtio.rs
└── userspace/
├── .gitignore
├── Makefile
├── fb.cpp
├── helloworld.cpp
├── input-event-codes.h
├── shell.cpp
├── sleepy.cpp
├── startlib/
│ ├── .gitignore
│ ├── Makefile
│ ├── linker.lds
│ ├── printf.cpp
│ ├── printf.h
│ ├── start.S
│ ├── syscall.S
│ └── syscall.h
└── upload.sh
SYMBOL INDEX (1210 symbols across 88 files)
FILE: assembly/intrin.c
function main (line 7) | int main() {
function calc_intrin (line 29) | void calc_intrin(float result[], float matrix[], float vector[])
FILE: risc_v/chapters/ch1/src/lib.rs
function eh_personality (line 35) | extern "C" fn eh_personality() {}
function panic (line 37) | fn panic(info: &core::panic::PanicInfo) -> ! {
function abort (line 53) | extern "C"
function kmain (line 70) | extern "C"
FILE: risc_v/chapters/ch2/src/lib.rs
function eh_personality (line 36) | extern "C" fn eh_personality() {}
function panic (line 39) | fn panic(info: &core::panic::PanicInfo) -> ! {
function abort (line 55) | extern "C"
function kmain (line 72) | extern "C"
FILE: risc_v/chapters/ch2/src/uart.rs
type Uart (line 8) | pub struct Uart {
method new (line 22) | pub fn new(base_address: usize) -> Self {
method init (line 28) | pub fn init(&mut self) {
method put (line 92) | pub fn put(&mut self, c: u8) {
method get (line 99) | pub fn get(&mut self) -> Option<u8> {
method write_str (line 13) | fn write_str(&mut self, out: &str) -> Result<(), Error> {
FILE: risc_v/chapters/ch3/src/kmem.rs
type AllocListFlags (line 10) | enum AllocListFlags {
method val (line 14) | pub fn val(self) -> usize {
type AllocList (line 19) | struct AllocList {
method is_taken (line 23) | pub fn is_taken(&self) -> bool {
method is_free (line 27) | pub fn is_free(&self) -> bool {
method set_taken (line 31) | pub fn set_taken(&mut self) {
method set_free (line 35) | pub fn set_free(&mut self) {
method set_size (line 39) | pub fn set_size(&mut self, sz: usize) {
method get_size (line 47) | pub fn get_size(&self) -> usize {
function get_head (line 64) | pub fn get_head() -> *mut u8 {
function get_page_table (line 68) | pub fn get_page_table() -> *mut Table {
function get_num_allocations (line 72) | pub fn get_num_allocations() -> usize {
function init (line 80) | pub fn init() {
function kzmalloc (line 94) | pub fn kzmalloc(sz: usize) -> *mut u8 {
function kmalloc (line 109) | pub fn kmalloc(sz: usize) -> *mut u8 {
function kfree (line 152) | pub fn kfree(ptr: *mut u8) {
function coalesce (line 167) | pub fn coalesce() {
function print_table (line 210) | pub fn print_table() {
type OsGlobalAlloc (line 241) | struct OsGlobalAlloc;
method alloc (line 244) | unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
method dealloc (line 251) | unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
function alloc_error (line 268) | pub fn alloc_error(l: Layout) -> ! {
FILE: risc_v/chapters/ch3/src/lib.rs
function eh_personality (line 46) | extern "C" fn eh_personality() {}
function panic (line 49) | fn panic(info: &core::panic::PanicInfo) -> ! {
function abort (line 65) | extern "C" fn abort() -> ! {
function id_map_range (line 102) | pub fn id_map_range(root: &mut page::Table,
function kinit (line 125) | extern "C" fn kinit() -> usize {
function kmain (line 280) | extern "C" fn kmain() {
FILE: risc_v/chapters/ch3/src/page.rs
constant PAGE_ORDER (line 19) | const PAGE_ORDER: usize = 12;
constant PAGE_SIZE (line 20) | pub const PAGE_SIZE: usize = 1 << 12;
function align_val (line 26) | pub const fn align_val(val: usize, order: usize) -> usize {
type PageBits (line 32) | pub enum PageBits {
method val (line 41) | pub fn val(self) -> u8 {
type Page (line 49) | pub struct Page {
method is_last (line 56) | pub fn is_last(&self) -> bool {
method is_taken (line 67) | pub fn is_taken(&self) -> bool {
method is_free (line 77) | pub fn is_free(&self) -> bool {
method clear (line 82) | pub fn clear(&mut self) {
method set_flag (line 89) | pub fn set_flag(&mut self, flag: PageBits) {
method clear_flag (line 93) | pub fn clear_flag(&mut self, flag: PageBits) {
function init (line 104) | pub fn init() {
function alloc (line 128) | pub fn alloc(pages: usize) -> *mut u8 {
function zalloc (line 187) | pub fn zalloc(pages: usize) -> *mut u8 {
function dealloc (line 212) | pub fn dealloc(ptr: *mut u8) {
function print_page_allocations (line 242) | pub fn print_page_allocations() {
type EntryBits (line 309) | pub enum EntryBits {
method val (line 335) | pub fn val(self) -> i64 {
type Entry (line 344) | pub struct Entry {
method is_valid (line 351) | pub fn is_valid(&self) -> bool {
method is_invalid (line 357) | pub fn is_invalid(&self) -> bool {
method is_leaf (line 362) | pub fn is_leaf(&self) -> bool {
method is_branch (line 366) | pub fn is_branch(&self) -> bool {
method set_entry (line 370) | pub fn set_entry(&mut self, entry: i64) {
method get_entry (line 374) | pub fn get_entry(&self) -> i64 {
type Table (line 380) | pub struct Table {
method len (line 385) | pub fn len() -> usize {
function map (line 401) | pub fn map(root: &mut Table, vaddr: usize, paddr: usize, bits: i64, leve...
function unmap (line 474) | pub fn unmap(root: &mut Table) {
function virt_to_phys (line 506) | pub fn virt_to_phys(root: &Table, vaddr: usize) -> Option<usize> {
FILE: risc_v/chapters/ch3/src/uart.rs
type Uart (line 8) | pub struct Uart {
method new (line 22) | pub fn new(base_address: usize) -> Self {
method init (line 28) | pub fn init(&mut self) {
method put (line 92) | pub fn put(&mut self, c: u8) {
method get (line 99) | pub fn get(&mut self) -> Option<u8> {
method write_str (line 13) | fn write_str(&mut self, out: &str) -> Result<(), Error> {
FILE: risc_v/chapters/ch4/src/cpu.rs
type SatpMode (line 10) | pub enum SatpMode {
type TrapFrame (line 18) | pub struct TrapFrame {
method zero (line 27) | pub const fn zero() -> Self {
function build_satp (line 39) | pub const fn build_satp(mode: SatpMode, asid: usize, addr: usize) -> usi...
function mhartid_read (line 45) | pub fn mhartid_read() -> usize {
function mstatus_write (line 53) | pub fn mstatus_write(val: usize) {
function mstatus_read (line 59) | pub fn mstatus_read() -> usize {
function stvec_write (line 67) | pub fn stvec_write(val: usize) {
function stvec_read (line 73) | pub fn stvec_read() -> usize {
function mscratch_write (line 81) | pub fn mscratch_write(val: usize) {
function mscratch_read (line 87) | pub fn mscratch_read() -> usize {
function mscratch_swap (line 95) | pub fn mscratch_swap(to: usize) -> usize {
function sscratch_write (line 103) | pub fn sscratch_write(val: usize) {
function sscratch_read (line 109) | pub fn sscratch_read() -> usize {
function sscratch_swap (line 117) | pub fn sscratch_swap(to: usize) -> usize {
function sepc_write (line 125) | pub fn sepc_write(val: usize) {
function sepc_read (line 131) | pub fn sepc_read() -> usize {
function satp_write (line 139) | pub fn satp_write(val: usize) {
function satp_read (line 145) | pub fn satp_read() -> usize {
function satp_fence (line 153) | pub fn satp_fence(vaddr: usize, asid: usize) {
function satp_fence_asid (line 159) | pub fn satp_fence_asid(asid: usize) {
FILE: risc_v/chapters/ch4/src/kmem.rs
type AllocListFlags (line 10) | enum AllocListFlags {
method val (line 14) | pub fn val(self) -> usize {
type AllocList (line 19) | struct AllocList {
method is_taken (line 23) | pub fn is_taken(&self) -> bool {
method is_free (line 27) | pub fn is_free(&self) -> bool {
method set_taken (line 31) | pub fn set_taken(&mut self) {
method set_free (line 35) | pub fn set_free(&mut self) {
method set_size (line 39) | pub fn set_size(&mut self, sz: usize) {
method get_size (line 47) | pub fn get_size(&self) -> usize {
function get_head (line 63) | pub fn get_head() -> *mut u8 {
function get_page_table (line 67) | pub fn get_page_table() -> *mut Table {
function get_num_allocations (line 71) | pub fn get_num_allocations() -> usize {
function init (line 79) | pub fn init() {
function kzmalloc (line 93) | pub fn kzmalloc(sz: usize) -> *mut u8 {
function kmalloc (line 108) | pub fn kmalloc(sz: usize) -> *mut u8 {
function kfree (line 151) | pub fn kfree(ptr: *mut u8) {
function coalesce (line 166) | pub fn coalesce() {
function print_table (line 209) | pub fn print_table() {
type OsGlobalAlloc (line 240) | struct OsGlobalAlloc;
method alloc (line 243) | unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
method dealloc (line 250) | unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
function alloc_error (line 267) | pub fn alloc_error(l: Layout) -> ! {
FILE: risc_v/chapters/ch4/src/lib.rs
function eh_personality (line 46) | extern "C" fn eh_personality() {}
function panic (line 49) | fn panic(info: &core::panic::PanicInfo) -> ! {
function abort (line 65) | extern "C" fn abort() -> ! {
function id_map_range (line 101) | pub fn id_map_range(root: &mut page::Table,
function kinit (line 123) | extern "C" fn kinit() {
function kinit_hart (line 303) | extern "C" fn kinit_hart(hartid: usize) {
function kmain (line 326) | extern "C" fn kmain() {
FILE: risc_v/chapters/ch4/src/page.rs
constant PAGE_ORDER (line 19) | const PAGE_ORDER: usize = 12;
constant PAGE_SIZE (line 20) | pub const PAGE_SIZE: usize = 1 << 12;
function align_val (line 26) | pub const fn align_val(val: usize, order: usize) -> usize {
type PageBits (line 32) | pub enum PageBits {
method val (line 41) | pub fn val(self) -> u8 {
type Page (line 49) | pub struct Page {
method is_last (line 56) | pub fn is_last(&self) -> bool {
method is_taken (line 67) | pub fn is_taken(&self) -> bool {
method is_free (line 77) | pub fn is_free(&self) -> bool {
method clear (line 82) | pub fn clear(&mut self) {
method set_flag (line 89) | pub fn set_flag(&mut self, flag: PageBits) {
method clear_flag (line 93) | pub fn clear_flag(&mut self, flag: PageBits) {
function init (line 104) | pub fn init() {
function alloc (line 130) | pub fn alloc(pages: usize) -> *mut u8 {
function zalloc (line 189) | pub fn zalloc(pages: usize) -> *mut u8 {
function dealloc (line 214) | pub fn dealloc(ptr: *mut u8) {
function print_page_allocations (line 244) | pub fn print_page_allocations() {
type EntryBits (line 311) | pub enum EntryBits {
method val (line 337) | pub fn val(self) -> i64 {
type Entry (line 346) | pub struct Entry {
method is_valid (line 353) | pub fn is_valid(&self) -> bool {
method is_invalid (line 359) | pub fn is_invalid(&self) -> bool {
method is_leaf (line 364) | pub fn is_leaf(&self) -> bool {
method is_branch (line 368) | pub fn is_branch(&self) -> bool {
method set_entry (line 372) | pub fn set_entry(&mut self, entry: i64) {
method get_entry (line 376) | pub fn get_entry(&self) -> i64 {
type Table (line 382) | pub struct Table {
method len (line 387) | pub fn len() -> usize {
function map (line 403) | pub fn map(root: &mut Table,
function unmap (line 484) | pub fn unmap(root: &mut Table) {
function virt_to_phys (line 517) | pub fn virt_to_phys(root: &Table, vaddr: usize) -> Option<usize> {
FILE: risc_v/chapters/ch4/src/trap.rs
function m_trap (line 9) | extern "C" fn m_trap(epc: usize,
FILE: risc_v/chapters/ch4/src/uart.rs
type Uart (line 7) | pub struct Uart {
method new (line 21) | pub fn new(base_address: usize) -> Self {
method init (line 25) | pub fn init(&mut self) {
method put (line 102) | pub fn put(&mut self, c: u8) {
method get (line 109) | pub fn get(&mut self) -> Option<u8> {
method write_str (line 12) | fn write_str(&mut self, out: &str) -> Result<(), Error> {
FILE: risc_v/chapters/ch5/src/cpu.rs
type SatpMode (line 14) | pub enum SatpMode {
type TrapFrame (line 26) | pub struct TrapFrame {
method zero (line 43) | pub const fn zero() -> Self {
function build_satp (line 63) | pub const fn build_satp(mode: SatpMode, asid: usize, addr: usize) -> usi...
function mhartid_read (line 69) | pub fn mhartid_read() -> usize {
function mie_read (line 76) | pub fn mie_read() -> usize {
function mie_write (line 84) | pub fn mie_write(val: usize) {
function mstatus_write (line 90) | pub fn mstatus_write(val: usize) {
function mstatus_read (line 96) | pub fn mstatus_read() -> usize {
function stvec_write (line 104) | pub fn stvec_write(val: usize) {
function stvec_read (line 110) | pub fn stvec_read() -> usize {
function mscratch_write (line 118) | pub fn mscratch_write(val: usize) {
function mscratch_read (line 124) | pub fn mscratch_read() -> usize {
function mscratch_swap (line 132) | pub fn mscratch_swap(to: usize) -> usize {
function sscratch_write (line 140) | pub fn sscratch_write(val: usize) {
function sscratch_read (line 146) | pub fn sscratch_read() -> usize {
function sscratch_swap (line 154) | pub fn sscratch_swap(to: usize) -> usize {
function sepc_write (line 162) | pub fn sepc_write(val: usize) {
function sepc_read (line 168) | pub fn sepc_read() -> usize {
function satp_write (line 176) | pub fn satp_write(val: usize) {
function satp_read (line 182) | pub fn satp_read() -> usize {
function satp_fence (line 193) | pub fn satp_fence(vaddr: usize, asid: usize) {
function satp_fence_asid (line 206) | pub fn satp_fence_asid(asid: usize) {
FILE: risc_v/chapters/ch5/src/kmem.rs
type AllocListFlags (line 10) | enum AllocListFlags {
method val (line 14) | pub fn val(self) -> usize {
type AllocList (line 19) | struct AllocList {
method is_taken (line 23) | pub fn is_taken(&self) -> bool {
method is_free (line 27) | pub fn is_free(&self) -> bool {
method set_taken (line 31) | pub fn set_taken(&mut self) {
method set_free (line 35) | pub fn set_free(&mut self) {
method set_size (line 39) | pub fn set_size(&mut self, sz: usize) {
method get_size (line 47) | pub fn get_size(&self) -> usize {
function get_head (line 63) | pub fn get_head() -> *mut u8 {
function get_page_table (line 67) | pub fn get_page_table() -> *mut Table {
function get_num_allocations (line 71) | pub fn get_num_allocations() -> usize {
function init (line 79) | pub fn init() {
function kzmalloc (line 93) | pub fn kzmalloc(sz: usize) -> *mut u8 {
function kmalloc (line 108) | pub fn kmalloc(sz: usize) -> *mut u8 {
function kfree (line 151) | pub fn kfree(ptr: *mut u8) {
function coalesce (line 166) | pub fn coalesce() {
function print_table (line 209) | pub fn print_table() {
type OsGlobalAlloc (line 240) | struct OsGlobalAlloc;
method alloc (line 243) | unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
method dealloc (line 250) | unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
function alloc_error (line 267) | pub fn alloc_error(l: Layout) -> ! {
FILE: risc_v/chapters/ch5/src/lib.rs
function eh_personality (line 46) | extern "C" fn eh_personality() {}
function panic (line 49) | fn panic(info: &core::panic::PanicInfo) -> ! {
function abort (line 65) | extern "C" fn abort() -> ! {
function id_map_range (line 101) | pub fn id_map_range(root: &mut page::Table,
function kinit (line 123) | extern "C" fn kinit() {
function kinit_hart (line 303) | extern "C" fn kinit_hart(hartid: usize) {
function kmain (line 326) | extern "C" fn kmain() {
FILE: risc_v/chapters/ch5/src/page.rs
constant PAGE_ORDER (line 19) | const PAGE_ORDER: usize = 12;
constant PAGE_SIZE (line 20) | pub const PAGE_SIZE: usize = 1 << 12;
function align_val (line 26) | pub const fn align_val(val: usize, order: usize) -> usize {
type PageBits (line 32) | pub enum PageBits {
method val (line 41) | pub fn val(self) -> u8 {
type Page (line 49) | pub struct Page {
method is_last (line 56) | pub fn is_last(&self) -> bool {
method is_taken (line 67) | pub fn is_taken(&self) -> bool {
method is_free (line 77) | pub fn is_free(&self) -> bool {
method clear (line 82) | pub fn clear(&mut self) {
method set_flag (line 89) | pub fn set_flag(&mut self, flag: PageBits) {
method clear_flag (line 93) | pub fn clear_flag(&mut self, flag: PageBits) {
function init (line 104) | pub fn init() {
function alloc (line 130) | pub fn alloc(pages: usize) -> *mut u8 {
function zalloc (line 189) | pub fn zalloc(pages: usize) -> *mut u8 {
function dealloc (line 214) | pub fn dealloc(ptr: *mut u8) {
function print_page_allocations (line 244) | pub fn print_page_allocations() {
type EntryBits (line 311) | pub enum EntryBits {
method val (line 337) | pub fn val(self) -> i64 {
type Entry (line 346) | pub struct Entry {
method is_valid (line 353) | pub fn is_valid(&self) -> bool {
method is_invalid (line 359) | pub fn is_invalid(&self) -> bool {
method is_leaf (line 364) | pub fn is_leaf(&self) -> bool {
method is_branch (line 368) | pub fn is_branch(&self) -> bool {
method set_entry (line 372) | pub fn set_entry(&mut self, entry: i64) {
method get_entry (line 376) | pub fn get_entry(&self) -> i64 {
type Table (line 382) | pub struct Table {
method len (line 387) | pub fn len() -> usize {
function map (line 403) | pub fn map(root: &mut Table,
function unmap (line 484) | pub fn unmap(root: &mut Table) {
function virt_to_phys (line 517) | pub fn virt_to_phys(root: &Table, vaddr: usize) -> Option<usize> {
FILE: risc_v/chapters/ch5/src/plic.rs
constant PLIC_PRIORITY (line 6) | const PLIC_PRIORITY: usize = 0x0c00_0000;
constant PLIC_PENDING (line 7) | const PLIC_PENDING: usize = 0x0c00_1000;
constant PLIC_INT_ENABLE (line 8) | const PLIC_INT_ENABLE: usize = 0x0c00_2000;
constant PLIC_THRESHOLD (line 9) | const PLIC_THRESHOLD: usize = 0x0c20_0000;
constant PLIC_CLAIM (line 10) | const PLIC_CLAIM: usize = 0x0c20_0004;
function next (line 30) | pub fn next() -> Option<u32> {
function complete (line 50) | pub fn complete(id: u32) {
function set_threshold (line 64) | pub fn set_threshold(tsh: u8) {
function is_pending (line 76) | pub fn is_pending(id: u32) -> bool {
function enable (line 87) | pub fn enable(id: u32) {
function set_priority (line 101) | pub fn set_priority(id: u32, prio: u8) {
FILE: risc_v/chapters/ch5/src/trap.rs
function m_trap (line 14) | extern "C" fn m_trap(epc: usize,
FILE: risc_v/chapters/ch5/src/uart.rs
type Uart (line 7) | pub struct Uart {
method new (line 21) | pub fn new(base_address: usize) -> Self {
method init (line 25) | pub fn init(&mut self) {
method put (line 102) | pub fn put(&mut self, c: u8) {
method get (line 109) | pub fn get(&mut self) -> Option<u8> {
method write_str (line 12) | fn write_str(&mut self, out: &str) -> Result<(), Error> {
FILE: risc_v/chapters/ch6/src/cpu.rs
type SatpMode (line 14) | pub enum SatpMode {
type TrapFrame (line 26) | pub struct TrapFrame {
method zero (line 43) | pub const fn zero() -> Self {
function build_satp (line 63) | pub const fn build_satp(mode: SatpMode, asid: usize, addr: usize) -> usi...
function mhartid_read (line 69) | pub fn mhartid_read() -> usize {
function mie_read (line 76) | pub fn mie_read() -> usize {
function mie_write (line 84) | pub fn mie_write(val: usize) {
function mstatus_write (line 90) | pub fn mstatus_write(val: usize) {
function mstatus_read (line 96) | pub fn mstatus_read() -> usize {
function stvec_write (line 104) | pub fn stvec_write(val: usize) {
function stvec_read (line 110) | pub fn stvec_read() -> usize {
function mscratch_write (line 118) | pub fn mscratch_write(val: usize) {
function mscratch_read (line 124) | pub fn mscratch_read() -> usize {
function mscratch_swap (line 132) | pub fn mscratch_swap(to: usize) -> usize {
function sscratch_write (line 140) | pub fn sscratch_write(val: usize) {
function sscratch_read (line 146) | pub fn sscratch_read() -> usize {
function sscratch_swap (line 154) | pub fn sscratch_swap(to: usize) -> usize {
function sepc_write (line 162) | pub fn sepc_write(val: usize) {
function sepc_read (line 168) | pub fn sepc_read() -> usize {
function satp_write (line 176) | pub fn satp_write(val: usize) {
function satp_read (line 182) | pub fn satp_read() -> usize {
function satp_fence (line 193) | pub fn satp_fence(vaddr: usize, asid: usize) {
function satp_fence_asid (line 206) | pub fn satp_fence_asid(asid: usize) {
FILE: risc_v/chapters/ch6/src/kmem.rs
type AllocListFlags (line 10) | enum AllocListFlags {
method val (line 14) | pub fn val(self) -> usize {
type AllocList (line 19) | struct AllocList {
method is_taken (line 23) | pub fn is_taken(&self) -> bool {
method is_free (line 27) | pub fn is_free(&self) -> bool {
method set_taken (line 31) | pub fn set_taken(&mut self) {
method set_free (line 35) | pub fn set_free(&mut self) {
method set_size (line 39) | pub fn set_size(&mut self, sz: usize) {
method get_size (line 47) | pub fn get_size(&self) -> usize {
function get_head (line 63) | pub fn get_head() -> *mut u8 {
function get_page_table (line 67) | pub fn get_page_table() -> *mut Table {
function get_num_allocations (line 71) | pub fn get_num_allocations() -> usize {
function init (line 79) | pub fn init() {
function kzmalloc (line 93) | pub fn kzmalloc(sz: usize) -> *mut u8 {
function kmalloc (line 108) | pub fn kmalloc(sz: usize) -> *mut u8 {
function kfree (line 151) | pub fn kfree(ptr: *mut u8) {
function coalesce (line 166) | pub fn coalesce() {
function print_table (line 209) | pub fn print_table() {
type OsGlobalAlloc (line 240) | struct OsGlobalAlloc;
method alloc (line 243) | unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
method dealloc (line 250) | unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
function alloc_error (line 267) | pub fn alloc_error(l: Layout) -> ! {
FILE: risc_v/chapters/ch6/src/lib.rs
function eh_personality (line 46) | extern "C" fn eh_personality() {}
function panic (line 49) | fn panic(info: &core::panic::PanicInfo) -> ! {
function abort (line 65) | extern "C" fn abort() -> ! {
function id_map_range (line 103) | pub fn id_map_range(root: &mut page::Table,
function kinit (line 125) | extern "C" fn kinit() -> usize {
function kinit_hart (line 154) | extern "C" fn kinit_hart(hartid: usize) {
FILE: risc_v/chapters/ch6/src/page.rs
constant PAGE_ORDER (line 19) | const PAGE_ORDER: usize = 12;
constant PAGE_SIZE (line 20) | pub const PAGE_SIZE: usize = 1 << 12;
function align_val (line 26) | pub const fn align_val(val: usize, order: usize) -> usize {
type PageBits (line 32) | pub enum PageBits {
method val (line 41) | pub fn val(self) -> u8 {
type Page (line 49) | pub struct Page {
method is_last (line 56) | pub fn is_last(&self) -> bool {
method is_taken (line 67) | pub fn is_taken(&self) -> bool {
method is_free (line 77) | pub fn is_free(&self) -> bool {
method clear (line 82) | pub fn clear(&mut self) {
method set_flag (line 89) | pub fn set_flag(&mut self, flag: PageBits) {
method clear_flag (line 93) | pub fn clear_flag(&mut self, flag: PageBits) {
function init (line 104) | pub fn init() {
function alloc (line 130) | pub fn alloc(pages: usize) -> *mut u8 {
function zalloc (line 189) | pub fn zalloc(pages: usize) -> *mut u8 {
function dealloc (line 214) | pub fn dealloc(ptr: *mut u8) {
function print_page_allocations (line 244) | pub fn print_page_allocations() {
type EntryBits (line 311) | pub enum EntryBits {
method val (line 337) | pub fn val(self) -> i64 {
type Entry (line 346) | pub struct Entry {
method is_valid (line 353) | pub fn is_valid(&self) -> bool {
method is_invalid (line 359) | pub fn is_invalid(&self) -> bool {
method is_leaf (line 364) | pub fn is_leaf(&self) -> bool {
method is_branch (line 368) | pub fn is_branch(&self) -> bool {
method set_entry (line 372) | pub fn set_entry(&mut self, entry: i64) {
method get_entry (line 376) | pub fn get_entry(&self) -> i64 {
type Table (line 382) | pub struct Table {
method len (line 387) | pub fn len() -> usize {
function map (line 403) | pub fn map(root: &mut Table,
function unmap (line 484) | pub fn unmap(root: &mut Table) {
function virt_to_phys (line 517) | pub fn virt_to_phys(root: &Table, vaddr: usize) -> Option<usize> {
FILE: risc_v/chapters/ch6/src/plic.rs
constant PLIC_PRIORITY (line 6) | const PLIC_PRIORITY: usize = 0x0c00_0000;
constant PLIC_PENDING (line 7) | const PLIC_PENDING: usize = 0x0c00_1000;
constant PLIC_INT_ENABLE (line 8) | const PLIC_INT_ENABLE: usize = 0x0c00_2000;
constant PLIC_THRESHOLD (line 9) | const PLIC_THRESHOLD: usize = 0x0c20_0000;
constant PLIC_CLAIM (line 10) | const PLIC_CLAIM: usize = 0x0c20_0004;
function next (line 30) | pub fn next() -> Option<u32> {
function complete (line 50) | pub fn complete(id: u32) {
function set_threshold (line 64) | pub fn set_threshold(tsh: u8) {
function is_pending (line 76) | pub fn is_pending(id: u32) -> bool {
function enable (line 87) | pub fn enable(id: u32) {
function set_priority (line 101) | pub fn set_priority(id: u32, prio: u8) {
FILE: risc_v/chapters/ch6/src/process.rs
constant STACK_PAGES (line 24) | const STACK_PAGES: usize = 2;
constant STACK_ADDR (line 27) | const STACK_ADDR: usize = 0xf_0000_0000;
constant PROCESS_STARTING_ADDR (line 29) | const PROCESS_STARTING_ADDR: usize = 0x2000_0000;
function init_process (line 47) | fn init_process() {
function add_process_default (line 56) | pub fn add_process_default(pr: fn()) {
function init (line 86) | pub fn init() -> usize {
type ProcessState (line 120) | pub enum ProcessState {
type Process (line 133) | pub struct Process {
method new_default (line 144) | pub fn new_default(func: fn()) -> Self {
method drop (line 210) | fn drop(&mut self) {
type ProcessData (line 228) | pub struct ProcessData {
method zero (line 236) | pub fn zero() -> Self {
FILE: risc_v/chapters/ch6/src/trap.rs
function m_trap (line 14) | extern "C" fn m_trap(epc: usize,
FILE: risc_v/chapters/ch6/src/uart.rs
type Uart (line 7) | pub struct Uart {
method new (line 21) | pub fn new(base_address: usize) -> Self {
method init (line 25) | pub fn init(&mut self) {
method put (line 102) | pub fn put(&mut self, c: u8) {
method get (line 116) | pub fn get(&mut self) -> Option<u8> {
method write_str (line 12) | fn write_str(&mut self, out: &str) -> Result<(), Error> {
FILE: risc_v/chapters/ch7/src/cpu.rs
type SatpMode (line 14) | pub enum SatpMode {
type TrapFrame (line 26) | pub struct TrapFrame {
method zero (line 43) | pub const fn zero() -> Self {
function build_satp (line 63) | pub const fn build_satp(mode: SatpMode, asid: usize, addr: usize) -> usi...
function mhartid_read (line 69) | pub fn mhartid_read() -> usize {
function mie_read (line 76) | pub fn mie_read() -> usize {
function mie_write (line 84) | pub fn mie_write(val: usize) {
function mstatus_write (line 90) | pub fn mstatus_write(val: usize) {
function mstatus_read (line 96) | pub fn mstatus_read() -> usize {
function stvec_write (line 104) | pub fn stvec_write(val: usize) {
function stvec_read (line 110) | pub fn stvec_read() -> usize {
function mscratch_write (line 118) | pub fn mscratch_write(val: usize) {
function mscratch_read (line 124) | pub fn mscratch_read() -> usize {
function mscratch_swap (line 132) | pub fn mscratch_swap(to: usize) -> usize {
function sscratch_write (line 140) | pub fn sscratch_write(val: usize) {
function sscratch_read (line 146) | pub fn sscratch_read() -> usize {
function sscratch_swap (line 154) | pub fn sscratch_swap(to: usize) -> usize {
function sepc_write (line 162) | pub fn sepc_write(val: usize) {
function sepc_read (line 168) | pub fn sepc_read() -> usize {
function satp_write (line 176) | pub fn satp_write(val: usize) {
function satp_read (line 182) | pub fn satp_read() -> usize {
function satp_fence (line 193) | pub fn satp_fence(vaddr: usize, asid: usize) {
function satp_fence_asid (line 206) | pub fn satp_fence_asid(asid: usize) {
FILE: risc_v/chapters/ch7/src/kmem.rs
type AllocListFlags (line 10) | enum AllocListFlags {
method val (line 14) | pub fn val(self) -> usize {
type AllocList (line 19) | struct AllocList {
method is_taken (line 23) | pub fn is_taken(&self) -> bool {
method is_free (line 27) | pub fn is_free(&self) -> bool {
method set_taken (line 31) | pub fn set_taken(&mut self) {
method set_free (line 35) | pub fn set_free(&mut self) {
method set_size (line 39) | pub fn set_size(&mut self, sz: usize) {
method get_size (line 47) | pub fn get_size(&self) -> usize {
function get_head (line 63) | pub fn get_head() -> *mut u8 {
function get_page_table (line 67) | pub fn get_page_table() -> *mut Table {
function get_num_allocations (line 71) | pub fn get_num_allocations() -> usize {
function init (line 79) | pub fn init() {
function kzmalloc (line 93) | pub fn kzmalloc(sz: usize) -> *mut u8 {
function kmalloc (line 108) | pub fn kmalloc(sz: usize) -> *mut u8 {
function kfree (line 151) | pub fn kfree(ptr: *mut u8) {
function coalesce (line 166) | pub fn coalesce() {
function print_table (line 209) | pub fn print_table() {
type OsGlobalAlloc (line 240) | struct OsGlobalAlloc;
method alloc (line 243) | unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
method dealloc (line 250) | unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
function alloc_error (line 267) | pub fn alloc_error(l: Layout) -> ! {
FILE: risc_v/chapters/ch7/src/lib.rs
function eh_personality (line 46) | extern "C" fn eh_personality() {}
function panic (line 49) | fn panic(info: &core::panic::PanicInfo) -> ! {
function abort (line 65) | extern "C" fn abort() -> ! {
function id_map_range (line 103) | pub fn id_map_range(root: &mut page::Table,
function kinit (line 125) | extern "C" fn kinit() -> usize {
function kinit_hart (line 154) | extern "C" fn kinit_hart(hartid: usize) {
FILE: risc_v/chapters/ch7/src/page.rs
constant PAGE_ORDER (line 19) | const PAGE_ORDER: usize = 12;
constant PAGE_SIZE (line 20) | pub const PAGE_SIZE: usize = 1 << 12;
function align_val (line 26) | pub const fn align_val(val: usize, order: usize) -> usize {
type PageBits (line 32) | pub enum PageBits {
method val (line 41) | pub fn val(self) -> u8 {
type Page (line 49) | pub struct Page {
method is_last (line 56) | pub fn is_last(&self) -> bool {
method is_taken (line 67) | pub fn is_taken(&self) -> bool {
method is_free (line 77) | pub fn is_free(&self) -> bool {
method clear (line 82) | pub fn clear(&mut self) {
method set_flag (line 89) | pub fn set_flag(&mut self, flag: PageBits) {
method clear_flag (line 93) | pub fn clear_flag(&mut self, flag: PageBits) {
function init (line 104) | pub fn init() {
function alloc (line 130) | pub fn alloc(pages: usize) -> *mut u8 {
function zalloc (line 189) | pub fn zalloc(pages: usize) -> *mut u8 {
function dealloc (line 214) | pub fn dealloc(ptr: *mut u8) {
function print_page_allocations (line 244) | pub fn print_page_allocations() {
type EntryBits (line 311) | pub enum EntryBits {
method val (line 337) | pub fn val(self) -> i64 {
type Entry (line 346) | pub struct Entry {
method is_valid (line 353) | pub fn is_valid(&self) -> bool {
method is_invalid (line 359) | pub fn is_invalid(&self) -> bool {
method is_leaf (line 364) | pub fn is_leaf(&self) -> bool {
method is_branch (line 368) | pub fn is_branch(&self) -> bool {
method set_entry (line 372) | pub fn set_entry(&mut self, entry: i64) {
method get_entry (line 376) | pub fn get_entry(&self) -> i64 {
type Table (line 382) | pub struct Table {
method len (line 387) | pub fn len() -> usize {
function map (line 403) | pub fn map(root: &mut Table,
function unmap (line 484) | pub fn unmap(root: &mut Table) {
function virt_to_phys (line 517) | pub fn virt_to_phys(root: &Table, vaddr: usize) -> Option<usize> {
FILE: risc_v/chapters/ch7/src/plic.rs
constant PLIC_PRIORITY (line 6) | const PLIC_PRIORITY: usize = 0x0c00_0000;
constant PLIC_PENDING (line 7) | const PLIC_PENDING: usize = 0x0c00_1000;
constant PLIC_INT_ENABLE (line 8) | const PLIC_INT_ENABLE: usize = 0x0c00_2000;
constant PLIC_THRESHOLD (line 9) | const PLIC_THRESHOLD: usize = 0x0c20_0000;
constant PLIC_CLAIM (line 10) | const PLIC_CLAIM: usize = 0x0c20_0004;
function next (line 30) | pub fn next() -> Option<u32> {
function complete (line 50) | pub fn complete(id: u32) {
function set_threshold (line 64) | pub fn set_threshold(tsh: u8) {
function is_pending (line 76) | pub fn is_pending(id: u32) -> bool {
function enable (line 87) | pub fn enable(id: u32) {
function set_priority (line 101) | pub fn set_priority(id: u32, prio: u8) {
FILE: risc_v/chapters/ch7/src/process.rs
constant STACK_PAGES (line 24) | const STACK_PAGES: usize = 2;
constant STACK_ADDR (line 27) | const STACK_ADDR: usize = 0xf_0000_0000;
constant PROCESS_STARTING_ADDR (line 29) | const PROCESS_STARTING_ADDR: usize = 0x2000_0000;
function init_process (line 47) | fn init_process() {
function add_process_default (line 56) | pub fn add_process_default(pr: fn()) {
function init (line 86) | pub fn init() -> usize {
type ProcessState (line 120) | pub enum ProcessState {
type Process (line 133) | pub struct Process {
method new_default (line 144) | pub fn new_default(func: fn()) -> Self {
method drop (line 210) | fn drop(&mut self) {
type ProcessData (line 228) | pub struct ProcessData {
method zero (line 236) | pub fn zero() -> Self {
FILE: risc_v/chapters/ch7/src/sched.rs
function schedule (line 9) | pub fn schedule() {
function switch_to (line 13) | pub fn switch_to(from: &mut Process, to: &mut Process) {
FILE: risc_v/chapters/ch7/src/syscall.rs
function do_syscall (line 8) | pub fn do_syscall(mepc: usize, frame: *mut TrapFrame) -> usize {
FILE: risc_v/chapters/ch7/src/trap.rs
function m_trap (line 15) | extern "C" fn m_trap(epc: usize,
FILE: risc_v/chapters/ch7/src/uart.rs
type Uart (line 7) | pub struct Uart {
method new (line 21) | pub fn new(base_address: usize) -> Self {
method init (line 25) | pub fn init(&mut self) {
method put (line 102) | pub fn put(&mut self, c: u8) {
method get (line 109) | pub fn get(&mut self) -> Option<u8> {
method write_str (line 12) | fn write_str(&mut self, out: &str) -> Result<(), Error> {
FILE: risc_v/chapters/ch8/src/cpu.rs
type SatpMode (line 14) | pub enum SatpMode {
type TrapFrame (line 26) | pub struct TrapFrame {
method zero (line 43) | pub const fn zero() -> Self {
function build_satp (line 63) | pub const fn build_satp(mode: SatpMode, asid: usize, addr: usize) -> usi...
function mhartid_read (line 69) | pub fn mhartid_read() -> usize {
function mie_read (line 76) | pub fn mie_read() -> usize {
function mie_write (line 84) | pub fn mie_write(val: usize) {
function mstatus_write (line 90) | pub fn mstatus_write(val: usize) {
function mstatus_read (line 96) | pub fn mstatus_read() -> usize {
function stvec_write (line 104) | pub fn stvec_write(val: usize) {
function stvec_read (line 110) | pub fn stvec_read() -> usize {
function mscratch_write (line 118) | pub fn mscratch_write(val: usize) {
function mscratch_read (line 124) | pub fn mscratch_read() -> usize {
function mscratch_swap (line 132) | pub fn mscratch_swap(to: usize) -> usize {
function sscratch_write (line 140) | pub fn sscratch_write(val: usize) {
function sscratch_read (line 146) | pub fn sscratch_read() -> usize {
function sscratch_swap (line 154) | pub fn sscratch_swap(to: usize) -> usize {
function mepc_write (line 162) | pub fn mepc_write(val: usize) {
function mepc_read (line 168) | pub fn mepc_read() -> usize {
function sepc_write (line 176) | pub fn sepc_write(val: usize) {
function sepc_read (line 182) | pub fn sepc_read() -> usize {
function satp_write (line 190) | pub fn satp_write(val: usize) {
function satp_read (line 196) | pub fn satp_read() -> usize {
function satp_fence (line 207) | pub fn satp_fence(vaddr: usize, asid: usize) {
function satp_fence_asid (line 220) | pub fn satp_fence_asid(asid: usize) {
FILE: risc_v/chapters/ch8/src/kmem.rs
type AllocListFlags (line 10) | enum AllocListFlags {
method val (line 14) | pub fn val(self) -> usize {
type AllocList (line 19) | struct AllocList {
method is_taken (line 23) | pub fn is_taken(&self) -> bool {
method is_free (line 27) | pub fn is_free(&self) -> bool {
method set_taken (line 31) | pub fn set_taken(&mut self) {
method set_free (line 35) | pub fn set_free(&mut self) {
method set_size (line 39) | pub fn set_size(&mut self, sz: usize) {
method get_size (line 47) | pub fn get_size(&self) -> usize {
function get_head (line 63) | pub fn get_head() -> *mut u8 {
function get_page_table (line 67) | pub fn get_page_table() -> *mut Table {
function get_num_allocations (line 71) | pub fn get_num_allocations() -> usize {
function init (line 79) | pub fn init() {
function kzmalloc (line 93) | pub fn kzmalloc(sz: usize) -> *mut u8 {
function kmalloc (line 108) | pub fn kmalloc(sz: usize) -> *mut u8 {
function kfree (line 151) | pub fn kfree(ptr: *mut u8) {
function coalesce (line 166) | pub fn coalesce() {
function print_table (line 209) | pub fn print_table() {
type OsGlobalAlloc (line 240) | struct OsGlobalAlloc;
method alloc (line 243) | unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
method dealloc (line 250) | unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
function alloc_error (line 267) | pub fn alloc_error(l: Layout) -> ! {
FILE: risc_v/chapters/ch8/src/lib.rs
function eh_personality (line 46) | extern "C" fn eh_personality() {}
function panic (line 49) | fn panic(info: &core::panic::PanicInfo) -> ! {
function abort (line 65) | extern "C" fn abort() -> ! {
function id_map_range (line 103) | pub fn id_map_range(root: &mut page::Table,
function switch_to_user (line 122) | fn switch_to_user(frame: usize, mepc: usize, satp: usize) -> !;
function kinit (line 128) | extern "C" fn kinit() {
function kinit_hart (line 160) | extern "C" fn kinit_hart(hartid: usize) {
FILE: risc_v/chapters/ch8/src/page.rs
constant PAGE_ORDER (line 19) | const PAGE_ORDER: usize = 12;
constant PAGE_SIZE (line 20) | pub const PAGE_SIZE: usize = 1 << 12;
function align_val (line 26) | pub const fn align_val(val: usize, order: usize) -> usize {
type PageBits (line 32) | pub enum PageBits {
method val (line 41) | pub fn val(self) -> u8 {
type Page (line 49) | pub struct Page {
method is_last (line 56) | pub fn is_last(&self) -> bool {
method is_taken (line 67) | pub fn is_taken(&self) -> bool {
method is_free (line 77) | pub fn is_free(&self) -> bool {
method clear (line 82) | pub fn clear(&mut self) {
method set_flag (line 89) | pub fn set_flag(&mut self, flag: PageBits) {
method clear_flag (line 93) | pub fn clear_flag(&mut self, flag: PageBits) {
function init (line 104) | pub fn init() {
function alloc (line 130) | pub fn alloc(pages: usize) -> *mut u8 {
function zalloc (line 189) | pub fn zalloc(pages: usize) -> *mut u8 {
function dealloc (line 214) | pub fn dealloc(ptr: *mut u8) {
function print_page_allocations (line 244) | pub fn print_page_allocations() {
type EntryBits (line 311) | pub enum EntryBits {
method val (line 337) | pub fn val(self) -> i64 {
type Entry (line 346) | pub struct Entry {
method is_valid (line 353) | pub fn is_valid(&self) -> bool {
method is_invalid (line 359) | pub fn is_invalid(&self) -> bool {
method is_leaf (line 364) | pub fn is_leaf(&self) -> bool {
method is_branch (line 368) | pub fn is_branch(&self) -> bool {
method set_entry (line 372) | pub fn set_entry(&mut self, entry: i64) {
method get_entry (line 376) | pub fn get_entry(&self) -> i64 {
type Table (line 382) | pub struct Table {
method len (line 387) | pub fn len() -> usize {
function map (line 403) | pub fn map(root: &mut Table,
function unmap (line 484) | pub fn unmap(root: &mut Table) {
function virt_to_phys (line 517) | pub fn virt_to_phys(root: &Table, vaddr: usize) -> Option<usize> {
FILE: risc_v/chapters/ch8/src/plic.rs
constant PLIC_PRIORITY (line 6) | const PLIC_PRIORITY: usize = 0x0c00_0000;
constant PLIC_PENDING (line 7) | const PLIC_PENDING: usize = 0x0c00_1000;
constant PLIC_INT_ENABLE (line 8) | const PLIC_INT_ENABLE: usize = 0x0c00_2000;
constant PLIC_THRESHOLD (line 9) | const PLIC_THRESHOLD: usize = 0x0c20_0000;
constant PLIC_CLAIM (line 10) | const PLIC_CLAIM: usize = 0x0c20_0004;
function next (line 30) | pub fn next() -> Option<u32> {
function complete (line 50) | pub fn complete(id: u32) {
function set_threshold (line 64) | pub fn set_threshold(tsh: u8) {
function is_pending (line 76) | pub fn is_pending(id: u32) -> bool {
function enable (line 87) | pub fn enable(id: u32) {
function set_priority (line 101) | pub fn set_priority(id: u32, prio: u8) {
FILE: risc_v/chapters/ch8/src/process.rs
constant STACK_PAGES (line 19) | const STACK_PAGES: usize = 2;
constant STACK_ADDR (line 22) | const STACK_ADDR: usize = 0x1_0000_0000;
constant PROCESS_STARTING_ADDR (line 24) | const PROCESS_STARTING_ADDR: usize = 0x8000_0000;
function make_syscall (line 41) | fn make_syscall(a: usize) -> usize;
function init_process (line 46) | fn init_process() {
function add_process_default (line 64) | pub fn add_process_default(pr: fn()) {
function init (line 94) | pub fn init() -> usize {
type ProcessState (line 123) | pub enum ProcessState {
type Process (line 136) | pub struct Process {
method get_frame_address (line 148) | pub fn get_frame_address(&self) -> usize {
method get_program_counter (line 151) | pub fn get_program_counter(&self) -> usize {
method get_table_address (line 154) | pub fn get_table_address(&self) -> usize {
method get_state (line 157) | pub fn get_state(&self) -> &ProcessState {
method get_pid (line 160) | pub fn get_pid(&self) -> u16 {
method get_sleep_until (line 163) | pub fn get_sleep_until(&self) -> usize {
method new_default (line 166) | pub fn new_default(func: fn()) -> Self {
method drop (line 240) | fn drop(&mut self) {
type ProcessData (line 258) | pub struct ProcessData {
method zero (line 266) | pub fn zero() -> Self {
FILE: risc_v/chapters/ch8/src/sched.rs
function schedule (line 8) | pub fn schedule() -> (usize, usize, usize) {
FILE: risc_v/chapters/ch8/src/syscall.rs
function do_syscall (line 8) | pub fn do_syscall(mepc: usize, frame: *mut TrapFrame) -> usize {
FILE: risc_v/chapters/ch8/src/trap.rs
function switch_to_user (line 12) | fn switch_to_user(frame: usize, mepc: usize, satp: usize) -> !;
function m_trap (line 20) | extern "C" fn m_trap(epc: usize,
FILE: risc_v/chapters/ch8/src/uart.rs
type Uart (line 7) | pub struct Uart {
method new (line 21) | pub fn new(base_address: usize) -> Self {
method init (line 25) | pub fn init(&mut self) {
method put (line 102) | pub fn put(&mut self, c: u8) {
method get (line 109) | pub fn get(&mut self) -> Option<u8> {
method write_str (line 12) | fn write_str(&mut self, out: &str) -> Result<(), Error> {
FILE: risc_v/chapters/ch9/src/block.rs
type Geometry (line 13) | pub struct Geometry {
type Topology (line 20) | pub struct Topology {
type Config (line 33) | pub struct Config {
type Header (line 60) | pub struct Header {
type Data (line 67) | pub struct Data {
type Status (line 72) | pub struct Status {
type Request (line 77) | pub struct Request {
type BlockDevice (line 89) | pub struct BlockDevice {
constant VIRTIO_BLK_T_IN (line 98) | pub const VIRTIO_BLK_T_IN: u32 = 0;
constant VIRTIO_BLK_T_OUT (line 99) | pub const VIRTIO_BLK_T_OUT: u32 = 1;
constant VIRTIO_BLK_T_FLUSH (line 100) | pub const VIRTIO_BLK_T_FLUSH: u32 = 4;
constant VIRTIO_BLK_T_DISCARD (line 101) | pub const VIRTIO_BLK_T_DISCARD: u32 = 11;
constant VIRTIO_BLK_T_WRITE_ZEROES (line 102) | pub const VIRTIO_BLK_T_WRITE_ZEROES: u32 = 13;
constant VIRTIO_BLK_S_OK (line 105) | pub const VIRTIO_BLK_S_OK: u8 = 0;
constant VIRTIO_BLK_S_IOERR (line 106) | pub const VIRTIO_BLK_S_IOERR: u8 = 1;
constant VIRTIO_BLK_S_UNSUPP (line 107) | pub const VIRTIO_BLK_S_UNSUPP: u8 = 2;
constant VIRTIO_BLK_F_SIZE_MAX (line 110) | pub const VIRTIO_BLK_F_SIZE_MAX: u32 = 1;
constant VIRTIO_BLK_F_SEG_MAX (line 111) | pub const VIRTIO_BLK_F_SEG_MAX: u32 = 2;
constant VIRTIO_BLK_F_GEOMETRY (line 112) | pub const VIRTIO_BLK_F_GEOMETRY: u32 = 4;
constant VIRTIO_BLK_F_RO (line 113) | pub const VIRTIO_BLK_F_RO: u32 = 5;
constant VIRTIO_BLK_F_BLK_SIZE (line 114) | pub const VIRTIO_BLK_F_BLK_SIZE: u32 = 6;
constant VIRTIO_BLK_F_FLUSH (line 115) | pub const VIRTIO_BLK_F_FLUSH: u32 = 9;
constant VIRTIO_BLK_F_TOPOLOGY (line 116) | pub const VIRTIO_BLK_F_TOPOLOGY: u32 = 10;
constant VIRTIO_BLK_F_CONFIG_WCE (line 117) | pub const VIRTIO_BLK_F_CONFIG_WCE: u32 = 11;
constant VIRTIO_BLK_F_DISCARD (line 118) | pub const VIRTIO_BLK_F_DISCARD: u32 = 13;
constant VIRTIO_BLK_F_WRITE_ZEROES (line 119) | pub const VIRTIO_BLK_F_WRITE_ZEROES: u32 = 14;
function setup_block_device (line 128) | pub fn setup_block_device(ptr: *mut u32) -> bool {
function fill_next_descriptor (line 226) | pub fn fill_next_descriptor(bd: &mut BlockDevice, desc: Descriptor) -> u...
function block_op (line 250) | pub fn block_op(dev: usize, buffer: *mut u8, size: u32, offset: u64, wri...
function read (line 308) | pub fn read(dev: usize, buffer: *mut u8, size: u32, offset: u64) {
function write (line 312) | pub fn write(dev: usize, buffer: *mut u8, size: u32, offset: u64) {
function pending (line 319) | pub fn pending(bd: &mut BlockDevice) {
function handle_interrupt (line 337) | pub fn handle_interrupt(idx: usize) {
FILE: risc_v/chapters/ch9/src/cpu.rs
constant FREQ (line 8) | pub const FREQ: u64 = 10_000_000;
constant CONTEXT_SWITCH_TIME (line 10) | pub const CONTEXT_SWITCH_TIME: u64 = FREQ / 250;
type SatpMode (line 17) | pub enum SatpMode {
type TrapFrame (line 29) | pub struct TrapFrame {
method new (line 47) | pub const fn new() -> Self {
function build_satp (line 62) | pub const fn build_satp(mode: SatpMode, asid: usize, addr: usize) -> usi...
function mhartid_read (line 68) | pub fn mhartid_read() -> usize {
function mie_read (line 75) | pub fn mie_read() -> usize {
function mie_write (line 83) | pub fn mie_write(val: usize) {
function mstatus_write (line 89) | pub fn mstatus_write(val: usize) {
function mstatus_read (line 95) | pub fn mstatus_read() -> usize {
function stvec_write (line 103) | pub fn stvec_write(val: usize) {
function stvec_read (line 109) | pub fn stvec_read() -> usize {
function mscratch_write (line 117) | pub fn mscratch_write(val: usize) {
function mscratch_read (line 123) | pub fn mscratch_read() -> usize {
function mscratch_swap (line 131) | pub fn mscratch_swap(to: usize) -> usize {
function sscratch_write (line 139) | pub fn sscratch_write(val: usize) {
function sscratch_read (line 145) | pub fn sscratch_read() -> usize {
function sscratch_swap (line 153) | pub fn sscratch_swap(to: usize) -> usize {
function mepc_write (line 161) | pub fn mepc_write(val: usize) {
function mepc_read (line 167) | pub fn mepc_read() -> usize {
function sepc_write (line 175) | pub fn sepc_write(val: usize) {
function sepc_read (line 181) | pub fn sepc_read() -> usize {
function satp_write (line 189) | pub fn satp_write(val: usize) {
function satp_read (line 195) | pub fn satp_read() -> usize {
function satp_fence (line 206) | pub fn satp_fence(vaddr: usize, asid: usize) {
function satp_fence_asid (line 219) | pub fn satp_fence_asid(asid: usize) {
FILE: risc_v/chapters/ch9/src/kmem.rs
type AllocListFlags (line 10) | enum AllocListFlags {
method val (line 14) | pub fn val(self) -> usize {
type AllocList (line 19) | struct AllocList {
method is_taken (line 23) | pub fn is_taken(&self) -> bool {
method is_free (line 27) | pub fn is_free(&self) -> bool {
method set_taken (line 31) | pub fn set_taken(&mut self) {
method set_free (line 35) | pub fn set_free(&mut self) {
method set_size (line 39) | pub fn set_size(&mut self, sz: usize) {
method get_size (line 47) | pub fn get_size(&self) -> usize {
function get_head (line 63) | pub fn get_head() -> *mut u8 {
function get_page_table (line 67) | pub fn get_page_table() -> *mut Table {
function get_num_allocations (line 71) | pub fn get_num_allocations() -> usize {
function init (line 79) | pub fn init() {
function kzmalloc (line 93) | pub fn kzmalloc(sz: usize) -> *mut u8 {
function kmalloc (line 108) | pub fn kmalloc(sz: usize) -> *mut u8 {
function kfree (line 151) | pub fn kfree(ptr: *mut u8) {
function coalesce (line 166) | pub fn coalesce() {
function print_table (line 209) | pub fn print_table() {
type OsGlobalAlloc (line 240) | struct OsGlobalAlloc;
method alloc (line 243) | unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
method dealloc (line 250) | unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
function alloc_error (line 267) | pub fn alloc_error(l: Layout) -> ! {
FILE: risc_v/chapters/ch9/src/lib.rs
function eh_personality (line 46) | extern "C" fn eh_personality() {}
function panic (line 49) | fn panic(info: &core::panic::PanicInfo) -> ! {
function abort (line 65) | extern "C" fn abort() -> ! {
function id_map_range (line 101) | pub fn id_map_range(root: &mut page::Table,
function switch_to_user (line 120) | fn switch_to_user(frame: usize) -> !;
function rust_switch_to_user (line 122) | fn rust_switch_to_user(frame: usize) -> ! {
function kinit (line 131) | extern "C" fn kinit() {
function kinit_hart (line 197) | extern "C" fn kinit_hart(_hartid: usize) {
FILE: risc_v/chapters/ch9/src/page.rs
constant PAGE_ORDER (line 19) | const PAGE_ORDER: usize = 12;
constant PAGE_SIZE (line 20) | pub const PAGE_SIZE: usize = 1 << 12;
function align_val (line 26) | pub const fn align_val(val: usize, order: usize) -> usize {
type PageBits (line 32) | pub enum PageBits {
method val (line 41) | pub fn val(self) -> u8 {
type Page (line 49) | pub struct Page {
method is_last (line 56) | pub fn is_last(&self) -> bool {
method is_taken (line 67) | pub fn is_taken(&self) -> bool {
method is_free (line 77) | pub fn is_free(&self) -> bool {
method clear (line 82) | pub fn clear(&mut self) {
method set_flag (line 89) | pub fn set_flag(&mut self, flag: PageBits) {
method clear_flag (line 93) | pub fn clear_flag(&mut self, flag: PageBits) {
function init (line 104) | pub fn init() {
function alloc (line 130) | pub fn alloc(pages: usize) -> *mut u8 {
function zalloc (line 189) | pub fn zalloc(pages: usize) -> *mut u8 {
function dealloc (line 214) | pub fn dealloc(ptr: *mut u8) {
function print_page_allocations (line 244) | pub fn print_page_allocations() {
type EntryBits (line 311) | pub enum EntryBits {
method val (line 337) | pub fn val(self) -> i64 {
type Entry (line 346) | pub struct Entry {
method is_valid (line 353) | pub fn is_valid(&self) -> bool {
method is_invalid (line 359) | pub fn is_invalid(&self) -> bool {
method is_leaf (line 364) | pub fn is_leaf(&self) -> bool {
method is_branch (line 368) | pub fn is_branch(&self) -> bool {
method set_entry (line 372) | pub fn set_entry(&mut self, entry: i64) {
method get_entry (line 376) | pub fn get_entry(&self) -> i64 {
type Table (line 382) | pub struct Table {
method len (line 387) | pub fn len() -> usize {
function map (line 403) | pub fn map(root: &mut Table,
function unmap (line 484) | pub fn unmap(root: &mut Table) {
function virt_to_phys (line 517) | pub fn virt_to_phys(root: &Table, vaddr: usize) -> Option<usize> {
FILE: risc_v/chapters/ch9/src/plic.rs
constant PLIC_PRIORITY (line 9) | const PLIC_PRIORITY: usize = 0x0c00_0000;
constant PLIC_PENDING (line 10) | const PLIC_PENDING: usize = 0x0c00_1000;
constant PLIC_INT_ENABLE (line 11) | const PLIC_INT_ENABLE: usize = 0x0c00_2000;
constant PLIC_THRESHOLD (line 12) | const PLIC_THRESHOLD: usize = 0x0c20_0000;
constant PLIC_CLAIM (line 13) | const PLIC_CLAIM: usize = 0x0c20_0004;
function next (line 33) | pub fn next() -> Option<u32> {
function complete (line 53) | pub fn complete(id: u32) {
function set_threshold (line 67) | pub fn set_threshold(tsh: u8) {
function is_pending (line 79) | pub fn is_pending(id: u32) -> bool {
function enable (line 90) | pub fn enable(id: u32) {
function set_priority (line 104) | pub fn set_priority(id: u32, prio: u8) {
function handle_interrupt (line 116) | pub fn handle_interrupt() {
FILE: risc_v/chapters/ch9/src/process.rs
constant STACK_PAGES (line 19) | const STACK_PAGES: usize = 2;
constant STACK_ADDR (line 22) | const STACK_ADDR: usize = 0x1_0000_0000;
function make_syscall (line 42) | fn make_syscall(a: usize) -> usize;
function init_process (line 47) | fn init_process() {
function add_process_default (line 65) | pub fn add_process_default(pr: fn()) {
function init (line 95) | pub fn init() -> usize {
type ProcessState (line 123) | pub enum ProcessState {
type Process (line 136) | pub struct Process {
method get_frame_address (line 147) | pub fn get_frame_address(&self) -> usize {
method get_program_counter (line 150) | pub fn get_program_counter(&self) -> usize {
method get_table_address (line 153) | pub fn get_table_address(&self) -> usize {
method get_state (line 156) | pub fn get_state(&self) -> &ProcessState {
method get_pid (line 159) | pub fn get_pid(&self) -> u16 {
method get_sleep_until (line 162) | pub fn get_sleep_until(&self) -> usize {
method new_default (line 165) | pub fn new_default(func: fn()) -> Self {
method drop (line 241) | fn drop(&mut self) {
type ProcessData (line 262) | pub struct ProcessData {
method zero (line 270) | pub fn zero() -> Self {
FILE: risc_v/chapters/ch9/src/rng.rs
function setup_entropy_device (line 6) | pub fn setup_entropy_device(_ptr: *mut u32) -> bool {
FILE: risc_v/chapters/ch9/src/sched.rs
function schedule (line 8) | pub fn schedule() -> usize {
FILE: risc_v/chapters/ch9/src/syscall.rs
function do_syscall (line 8) | pub fn do_syscall(mepc: usize, frame: *mut TrapFrame) -> usize {
FILE: risc_v/chapters/ch9/src/trap.rs
function m_trap (line 17) | extern "C" fn m_trap(epc: usize,
constant MMIO_MTIMECMP (line 128) | pub const MMIO_MTIMECMP: *mut u64 = 0x0200_4000usize as *mut u64;
constant MMIO_MTIME (line 129) | pub const MMIO_MTIME: *const u64 = 0x0200_BFF8 as *const u64;
function schedule_next_context_switch (line 131) | pub fn schedule_next_context_switch(qm: u16) {
FILE: risc_v/chapters/ch9/src/uart.rs
type Uart (line 7) | pub struct Uart {
method new (line 21) | pub fn new(base_address: usize) -> Self {
method init (line 25) | pub fn init(&mut self) {
method put (line 102) | pub fn put(&mut self, c: u8) {
method get (line 109) | pub fn get(&mut self) -> Option<u8> {
method write_str (line 12) | fn write_str(&mut self, out: &str) -> Result<(), Error> {
FILE: risc_v/chapters/ch9/src/virtio.rs
constant VIRTIO_DESC_F_NEXT (line 14) | pub const VIRTIO_DESC_F_NEXT: u16 = 1;
constant VIRTIO_DESC_F_WRITE (line 15) | pub const VIRTIO_DESC_F_WRITE: u16 = 2;
constant VIRTIO_DESC_F_INDIRECT (line 16) | pub const VIRTIO_DESC_F_INDIRECT: u16 = 4;
constant VIRTIO_AVAIL_F_NO_INTERRUPT (line 18) | pub const VIRTIO_AVAIL_F_NO_INTERRUPT: u16 = 1;
constant VIRTIO_USED_F_NO_NOTIFY (line 20) | pub const VIRTIO_USED_F_NO_NOTIFY: u16 = 1;
constant VIRTIO_RING_SIZE (line 25) | pub const VIRTIO_RING_SIZE: usize = 1 << 7;
type Descriptor (line 35) | pub struct Descriptor {
type Available (line 43) | pub struct Available {
type UsedElem (line 51) | pub struct UsedElem {
type Used (line 57) | pub struct Used {
type Queue (line 65) | pub struct Queue {
type MmioOffsets (line 77) | pub enum MmioOffsets {
method val (line 116) | pub fn val(self) -> usize {
method scaled (line 120) | pub fn scaled(self, scale: usize) -> usize {
method scale32 (line 124) | pub fn scale32(self) -> usize {
type DeviceTypes (line 100) | pub enum DeviceTypes {
type StatusField (line 129) | pub enum StatusField {
method val (line 141) | pub fn val(self) -> usize {
method val32 (line 145) | pub fn val32(self) -> u32 {
method test (line 149) | pub fn test(sf: u32, bit: StatusField) -> bool {
method is_failed (line 153) | pub fn is_failed(sf: u32) -> bool {
method needs_reset (line 157) | pub fn needs_reset(sf: u32) -> bool {
method driver_ok (line 161) | pub fn driver_ok(sf: u32) -> bool {
method features_ok (line 165) | pub fn features_ok(sf: u32) -> bool {
constant MMIO_VIRTIO_START (line 173) | pub const MMIO_VIRTIO_START: usize = 0x1000_1000;
constant MMIO_VIRTIO_END (line 174) | pub const MMIO_VIRTIO_END: usize = 0x1000_8000;
constant MMIO_VIRTIO_STRIDE (line 175) | pub const MMIO_VIRTIO_STRIDE: usize = 0x1000;
constant MMIO_VIRTIO_MAGIC (line 176) | pub const MMIO_VIRTIO_MAGIC: u32 = 0x74_72_69_76;
type VirtioDevice (line 183) | pub struct VirtioDevice {
method new (line 188) | pub const fn new() -> Self {
method new_with (line 192) | pub const fn new_with(devtype: DeviceTypes) -> Self {
function probe (line 201) | pub fn probe() {
function setup_network_device (line 292) | pub fn setup_network_device(_ptr: *mut u32) -> bool {
function setup_gpu_device (line 296) | pub fn setup_gpu_device(_ptr: *mut u32) -> bool {
function setup_input_device (line 300) | pub fn setup_input_device(_ptr: *mut u32) -> bool {
function handle_interrupt (line 308) | pub fn handle_interrupt(interrupt: u32) {
FILE: risc_v/src/block.rs
type Geometry (line 22) | pub struct Geometry {
type Topology (line 29) | pub struct Topology {
type Config (line 42) | pub struct Config {
type Header (line 69) | pub struct Header {
type Data (line 76) | pub struct Data {
type Status (line 81) | pub struct Status {
type Request (line 86) | pub struct Request {
type BlockDevice (line 105) | pub struct BlockDevice {
constant VIRTIO_BLK_T_IN (line 114) | pub const VIRTIO_BLK_T_IN: u32 = 0;
constant VIRTIO_BLK_T_OUT (line 115) | pub const VIRTIO_BLK_T_OUT: u32 = 1;
constant VIRTIO_BLK_T_FLUSH (line 116) | pub const VIRTIO_BLK_T_FLUSH: u32 = 4;
constant VIRTIO_BLK_T_DISCARD (line 117) | pub const VIRTIO_BLK_T_DISCARD: u32 = 11;
constant VIRTIO_BLK_T_WRITE_ZEROES (line 118) | pub const VIRTIO_BLK_T_WRITE_ZEROES: u32 = 13;
constant VIRTIO_BLK_S_OK (line 121) | pub const VIRTIO_BLK_S_OK: u8 = 0;
constant VIRTIO_BLK_S_IOERR (line 122) | pub const VIRTIO_BLK_S_IOERR: u8 = 1;
constant VIRTIO_BLK_S_UNSUPP (line 123) | pub const VIRTIO_BLK_S_UNSUPP: u8 = 2;
constant VIRTIO_BLK_F_SIZE_MAX (line 126) | pub const VIRTIO_BLK_F_SIZE_MAX: u32 = 1;
constant VIRTIO_BLK_F_SEG_MAX (line 127) | pub const VIRTIO_BLK_F_SEG_MAX: u32 = 2;
constant VIRTIO_BLK_F_GEOMETRY (line 128) | pub const VIRTIO_BLK_F_GEOMETRY: u32 = 4;
constant VIRTIO_BLK_F_RO (line 129) | pub const VIRTIO_BLK_F_RO: u32 = 5;
constant VIRTIO_BLK_F_BLK_SIZE (line 130) | pub const VIRTIO_BLK_F_BLK_SIZE: u32 = 6;
constant VIRTIO_BLK_F_FLUSH (line 131) | pub const VIRTIO_BLK_F_FLUSH: u32 = 9;
constant VIRTIO_BLK_F_TOPOLOGY (line 132) | pub const VIRTIO_BLK_F_TOPOLOGY: u32 = 10;
constant VIRTIO_BLK_F_CONFIG_WCE (line 133) | pub const VIRTIO_BLK_F_CONFIG_WCE: u32 = 11;
constant VIRTIO_BLK_F_DISCARD (line 134) | pub const VIRTIO_BLK_F_DISCARD: u32 = 13;
constant VIRTIO_BLK_F_WRITE_ZEROES (line 135) | pub const VIRTIO_BLK_F_WRITE_ZEROES: u32 = 14;
type BlockErrors (line 138) | pub enum BlockErrors {
function setup_block_device (line 153) | pub fn setup_block_device(ptr: *mut u32) -> bool {
function fill_next_descriptor (line 266) | pub fn fill_next_descriptor(bd: &mut BlockDevice, desc: Descriptor) -> u...
function block_op (line 294) | pub fn block_op(dev: usize,
function read (line 384) | pub fn read(dev: usize,
function write (line 393) | pub fn write(dev: usize,
function pending (line 405) | pub fn pending(bd: &mut BlockDevice) {
function handle_interrupt (line 437) | pub fn handle_interrupt(idx: usize) {
type ProcArgs (line 454) | struct ProcArgs {
function read_proc (line 463) | fn read_proc(args_addr: usize) {
function process_read (line 477) | pub fn process_read(pid: u16,
function write_proc (line 500) | fn write_proc(args_addr: usize) {
function process_write (line 514) | pub fn process_write(pid: u16,
FILE: risc_v/src/buffer.rs
type Buffer (line 12) | pub struct Buffer {
method new (line 18) | pub fn new(sz: usize) -> Self {
method get_mut (line 25) | pub fn get_mut(&mut self) -> *mut u8 {
method get (line 29) | pub fn get(&self) -> *const u8 {
method len (line 33) | pub fn len(&self) -> usize {
type Output (line 45) | type Output = u8;
method index (line 46) | fn index(&self, idx: usize) -> &Self::Output {
method index_mut (line 54) | fn index_mut(&mut self, idx: usize) -> &mut Self::Output {
method default (line 39) | fn default() -> Self {
method clone (line 63) | fn clone(&self) -> Self {
method drop (line 78) | fn drop(&mut self) {
FILE: risc_v/src/console.rs
constant DEFAULT_OUT_BUFFER_SIZE (line 16) | pub const DEFAULT_OUT_BUFFER_SIZE: usize = 10_000;
constant DEFAULT_IN_BUFFER_SIZE (line 17) | pub const DEFAULT_IN_BUFFER_SIZE: usize = 1_000;
function init (line 21) | pub fn init() {
function push_stdout (line 30) | pub fn push_stdout(c: u8) {
function pop_stdout (line 43) | pub fn pop_stdout() -> u8 {
function push_stdin (line 56) | pub fn push_stdin(c: u8) {
function pop_stdin (line 78) | pub fn pop_stdin() -> u8 {
function push_queue (line 91) | pub fn push_queue(pid: u16) {
FILE: risc_v/src/cpu.rs
constant FREQ (line 8) | pub const FREQ: u64 = 10_000_000;
constant CONTEXT_SWITCH_TIME (line 10) | pub const CONTEXT_SWITCH_TIME: u64 = FREQ / 500;
type SatpMode (line 17) | pub enum SatpMode {
type CpuMode (line 24) | pub enum CpuMode {
type Registers (line 31) | pub enum Registers {
function gp (line 66) | pub const fn gp(r: Registers) -> usize {
type FRegisters (line 72) | pub enum FRegisters {
type TrapFrame (line 114) | pub struct TrapFrame {
method new (line 134) | pub const fn new() -> Self {
function build_satp (line 150) | pub const fn build_satp(mode: SatpMode, asid: usize, addr: usize) -> usi...
function mhartid_read (line 156) | pub fn mhartid_read() -> usize {
function mie_read (line 163) | pub fn mie_read() -> usize {
function mie_write (line 171) | pub fn mie_write(val: usize) {
function mstatus_write (line 177) | pub fn mstatus_write(val: usize) {
function mstatus_read (line 183) | pub fn mstatus_read() -> usize {
function stvec_write (line 191) | pub fn stvec_write(val: usize) {
function stvec_read (line 197) | pub fn stvec_read() -> usize {
function mscratch_write (line 205) | pub fn mscratch_write(val: usize) {
function mscratch_read (line 211) | pub fn mscratch_read() -> usize {
function mscratch_swap (line 219) | pub fn mscratch_swap(to: usize) -> usize {
function sscratch_write (line 227) | pub fn sscratch_write(val: usize) {
function sscratch_read (line 233) | pub fn sscratch_read() -> usize {
function sscratch_swap (line 241) | pub fn sscratch_swap(to: usize) -> usize {
function mepc_write (line 249) | pub fn mepc_write(val: usize) {
function mepc_read (line 255) | pub fn mepc_read() -> usize {
function sepc_write (line 263) | pub fn sepc_write(val: usize) {
function sepc_read (line 269) | pub fn sepc_read() -> usize {
function satp_write (line 277) | pub fn satp_write(val: usize) {
function satp_read (line 283) | pub fn satp_read() -> usize {
function satp_fence (line 294) | pub fn satp_fence(vaddr: usize, asid: usize) {
function satp_fence_asid (line 307) | pub fn satp_fence_asid(asid: usize) {
constant MMIO_MTIME (line 313) | const MMIO_MTIME: *const u64 = 0x0200_BFF8 as *const u64;
function get_mtime (line 315) | pub fn get_mtime() -> usize {
function memcpy (line 320) | pub unsafe fn memcpy(dest: *mut u8, src: *const u8, bytes: usize) {
function dump_registers (line 337) | pub fn dump_registers(frame: *const TrapFrame) {
FILE: risc_v/src/elf.rs
constant MAGIC (line 13) | pub const MAGIC: u32 = 0x464c_457f;
type Header (line 18) | pub struct Header {
type ProgramHeader (line 43) | pub struct ProgramHeader {
constant TYPE_EXEC (line 54) | pub const TYPE_EXEC: u16 = 2;
constant PROG_READ (line 56) | pub const PROG_READ: u32 = 4;
constant PROG_WRITE (line 57) | pub const PROG_WRITE: u32 = 2;
constant PROG_EXECUTE (line 58) | pub const PROG_EXECUTE: u32 = 1;
constant MACHINE_RISCV (line 60) | pub const MACHINE_RISCV: u16 = 0xf3;
constant PH_SEG_TYPE_NULL (line 61) | pub const PH_SEG_TYPE_NULL: u32 = 0;
constant PH_SEG_TYPE_LOAD (line 62) | pub const PH_SEG_TYPE_LOAD: u32 = 1;
constant PH_SEG_TYPE_DYNAMIC (line 63) | pub const PH_SEG_TYPE_DYNAMIC: u32 = 2;
constant PH_SEG_TYPE_INTERP (line 64) | pub const PH_SEG_TYPE_INTERP: u32 = 3;
constant PH_SEG_TYPE_NOTE (line 65) | pub const PH_SEG_TYPE_NOTE: u32 = 4;
type Program (line 67) | pub struct Program {
type LoadErrors (line 72) | pub enum LoadErrors {
type File (line 79) | pub struct File {
method load (line 85) | pub fn load(buffer: &Buffer) -> Result<Self, LoadErrors> {
method load_proc (line 133) | pub fn load_proc(buffer: &Buffer) -> Result<Process, LoadErrors> {
FILE: risc_v/src/fs.rs
constant MAGIC (line 14) | pub const MAGIC: u16 = 0x4d5a;
constant BLOCK_SIZE (line 15) | pub const BLOCK_SIZE: u32 = 1024;
constant NUM_IPTRS (line 16) | pub const NUM_IPTRS: usize = BLOCK_SIZE as usize / 4;
constant S_IFDIR (line 17) | pub const S_IFDIR: u16 = 0o040_000;
constant S_IFREG (line 18) | pub const S_IFREG: u16 = 0o100_000;
type SuperBlock (line 23) | pub struct SuperBlock {
type Inode (line 46) | pub struct Inode {
type DirEntry (line 63) | pub struct DirEntry {
type MinixFileSystem (line 69) | pub struct MinixFileSystem;
method get_inode (line 80) | pub fn get_inode(bdev: usize, inode_num: u32) -> Option<Inode> {
method cache_at (line 129) | fn cache_at(btm: &mut BTreeMap<String, Inode>, cwd: &String, inode_num...
method init (line 170) | pub fn init(bdev: usize) {
method open (line 189) | pub fn open(bdev: usize, path: &str) -> Result<Inode, FsError> {
method read (line 208) | pub fn read(bdev: usize, inode: &Inode, buffer: *mut u8, size: u32, of...
method write (line 415) | pub fn write(&mut self, _desc: &Inode, _buffer: *const u8, _offset: u3...
method stat (line 419) | pub fn stat(&self, inode: &Inode) -> Stat {
function syc_read (line 430) | fn syc_read(bdev: usize, buffer: *mut u8, size: u32, offset: u32) -> u8 {
type ProcArgs (line 437) | struct ProcArgs {
function read_proc (line 447) | fn read_proc(args_addr: usize) {
function process_read (line 471) | pub fn process_read(pid: u16, dev: usize, node: u32, buffer: *mut u8, si...
type Stat (line 488) | pub struct Stat {
type FsError (line 495) | pub enum FsError {
FILE: risc_v/src/gpu.rs
constant F_VIRGL (line 14) | const F_VIRGL: u32 = 0;
constant F_EDID (line 15) | const F_EDID: u32 = 1;
constant EVENT_DISPLAY (line 16) | const EVENT_DISPLAY: u32 = 1 << 0;
type Config (line 18) | struct Config {
type CtrlType (line 28) | enum CtrlType {
constant FLAG_FENCE (line 59) | const FLAG_FENCE: u32 = 1 << 0;
type CtrlHeader (line 61) | struct CtrlHeader {
constant MAX_SCANOUTS (line 69) | const MAX_SCANOUTS: usize = 16;
type Rect (line 72) | pub struct Rect {
method new (line 80) | pub const fn new(x: u32, y: u32, width: u32, height: u32) -> Self {
type DisplayOne (line 87) | struct DisplayOne {
type RespDisplayInfo (line 94) | struct RespDisplayInfo {
type GetEdid (line 99) | struct GetEdid {
type RespEdid (line 105) | struct RespEdid {
type Formats (line 112) | enum Formats {
type ResourceCreate2d (line 124) | struct ResourceCreate2d {
type ResourceUnref (line 132) | struct ResourceUnref {
type SetScanout (line 138) | struct SetScanout {
type ResourceFlush (line 145) | struct ResourceFlush {
type TransferToHost2d (line 153) | struct TransferToHost2d {
type AttachBacking (line 161) | struct AttachBacking {
type MemEntry (line 168) | struct MemEntry {
type DetachBacking (line 175) | struct DetachBacking {
type CursorPos (line 181) | struct CursorPos {
type UpdateCursor (line 189) | struct UpdateCursor {
type Pixel (line 199) | pub struct Pixel {
method new (line 206) | pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
type Request (line 215) | struct Request<RqT, RpT> {
function new (line 221) | pub fn new(request: RqT) -> *mut Self {
type Request3 (line 231) | struct Request3<RqT, RmT, RpT> {
function new (line 238) | pub fn new(request: RqT, meminfo: RmT) -> *mut Self {
type Device (line 249) | pub struct Device {
method new (line 260) | pub const fn new() -> Self {
method get_framebuffer (line 270) | pub fn get_framebuffer(&self) -> *mut Pixel {
method get_width (line 273) | pub fn get_width(&self) -> u32 {
method get_height (line 276) | pub fn get_height(&self) -> u32 {
function fill_rect (line 292) | pub fn fill_rect(dev: &mut Device, rect: Rect, color: Pixel) {
function stroke_rect (line 303) | pub fn stroke_rect(dev: &mut Device, rect: Rect, color: Pixel, size: u32) {
function init (line 336) | pub fn init(gdev: usize) {
function transfer (line 546) | pub fn transfer(gdev: usize, x: u32, y: u32, width: u32, height: u32) {
function setup_gpu_device (line 628) | pub fn setup_gpu_device(ptr: *mut u32) -> bool {
function pending (line 732) | pub fn pending(dev: &mut Device) {
function handle_interrupt (line 751) | pub fn handle_interrupt(idx: usize) {
FILE: risc_v/src/input.rs
constant EVENT_BUFFER_ELEMENTS (line 16) | const EVENT_BUFFER_ELEMENTS: usize = 64;
type InputType (line 18) | pub enum InputType {
type Event (line 26) | pub struct Event {
type ConfigSelect (line 33) | pub enum ConfigSelect {
type AbsInfo (line 44) | pub struct AbsInfo {
type DevIds (line 53) | pub struct DevIds {
type Config (line 71) | pub struct Config {
type EventType (line 81) | pub enum EventType {
constant EVENT_SIZE (line 97) | const EVENT_SIZE: usize = size_of::<Event>();
type Device (line 99) | pub struct Device {
function setup_input_device (line 119) | pub fn setup_input_device(ptr: *mut u32) -> bool {
function repopulate_event (line 242) | unsafe fn repopulate_event(dev: &mut Device, buffer: usize) {
function pending (line 257) | fn pending(dev: &mut Device) {
function handle_interrupt (line 300) | pub fn handle_interrupt(idx: usize) {
FILE: risc_v/src/kmem.rs
type AllocListFlags (line 10) | enum AllocListFlags {
method val (line 14) | pub fn val(self) -> usize {
type AllocList (line 19) | struct AllocList {
method is_taken (line 23) | pub fn is_taken(&self) -> bool {
method is_free (line 27) | pub fn is_free(&self) -> bool {
method set_taken (line 31) | pub fn set_taken(&mut self) {
method set_free (line 35) | pub fn set_free(&mut self) {
method set_size (line 39) | pub fn set_size(&mut self, sz: usize) {
method get_size (line 47) | pub fn get_size(&self) -> usize {
function get_head (line 63) | pub fn get_head() -> *mut u8 {
function get_page_table (line 67) | pub fn get_page_table() -> *mut Table {
function get_num_allocations (line 71) | pub fn get_num_allocations() -> usize {
function init (line 79) | pub fn init() {
function kzmalloc (line 93) | pub fn kzmalloc(sz: usize) -> *mut u8 {
function kmalloc (line 108) | pub fn kmalloc(sz: usize) -> *mut u8 {
function kfree (line 151) | pub fn kfree(ptr: *mut u8) {
function coalesce (line 166) | pub fn coalesce() {
function print_table (line 209) | pub fn print_table() {
type OsGlobalAlloc (line 240) | struct OsGlobalAlloc;
method alloc (line 243) | unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
method dealloc (line 250) | unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
function alloc_error (line 267) | pub fn alloc_error(l: Layout) -> ! {
FILE: risc_v/src/lock.rs
constant DEFAULT_LOCK_SLEEP (line 8) | pub const DEFAULT_LOCK_SLEEP: usize = 10000;
type MutexState (line 10) | pub enum MutexState {
type Mutex (line 16) | pub struct Mutex {
method new (line 21) | pub const fn new() -> Self {
method val (line 25) | pub fn val(&'a self) -> &'a MutexState {
method try_lock (line 30) | pub fn try_lock(&mut self) -> bool {
method sleep_lock (line 45) | pub fn sleep_lock(&mut self) {
method spin_lock (line 52) | pub fn spin_lock(&mut self) {
method unlock (line 57) | pub fn unlock(&mut self) {
FILE: risc_v/src/main.rs
function eh_personality (line 16) | extern fn eh_personality() {}
function panic (line 53) | fn panic(info: &core::panic::PanicInfo) -> ! {
function abort (line 69) | extern "C" fn abort() -> ! {
function switch_to_user (line 78) | fn switch_to_user(frame: usize) -> !;
function rust_switch_to_user (line 85) | fn rust_switch_to_user(frame: usize) -> ! {
function kinit (line 94) | extern "C" fn kinit() {
function kinit_hart (line 124) | extern "C" fn kinit_hart(_hartid: usize) {
FILE: risc_v/src/page.rs
constant PAGE_ORDER (line 19) | const PAGE_ORDER: usize = 12;
constant PAGE_SIZE (line 20) | pub const PAGE_SIZE: usize = 1 << 12;
function align_val (line 26) | pub const fn align_val(val: usize, order: usize) -> usize {
type PageBits (line 32) | pub enum PageBits {
method val (line 41) | pub fn val(self) -> u8 {
type Page (line 49) | pub struct Page {
method is_last (line 56) | pub fn is_last(&self) -> bool {
method is_taken (line 62) | pub fn is_taken(&self) -> bool {
method is_free (line 67) | pub fn is_free(&self) -> bool {
method clear (line 72) | pub fn clear(&mut self) {
method set_flag (line 79) | pub fn set_flag(&mut self, flag: PageBits) {
method clear_flag (line 83) | pub fn clear_flag(&mut self, flag: PageBits) {
function init (line 94) | pub fn init() {
function alloc (line 120) | pub fn alloc(pages: usize) -> *mut u8 {
function zalloc (line 179) | pub fn zalloc(pages: usize) -> *mut u8 {
function dealloc (line 204) | pub fn dealloc(ptr: *mut u8) {
function print_page_allocations (line 236) | pub fn print_page_allocations() {
type EntryBits (line 303) | pub enum EntryBits {
method val (line 329) | pub fn val(self) -> usize {
type Entry (line 335) | pub struct Entry {
method is_valid (line 342) | pub fn is_valid(&self) -> bool {
method is_invalid (line 348) | pub fn is_invalid(&self) -> bool {
method is_leaf (line 353) | pub fn is_leaf(&self) -> bool {
method is_branch (line 357) | pub fn is_branch(&self) -> bool {
method set_entry (line 361) | pub fn set_entry(&mut self, entry: usize) {
method get_entry (line 365) | pub fn get_entry(&self) -> usize {
type Table (line 371) | pub struct Table {
method len (line 376) | pub fn len() -> usize {
function map (line 392) | pub fn map(root: &mut Table,
function unmap (line 473) | pub fn unmap(root: &mut Table) {
function virt_to_phys (line 506) | pub fn virt_to_phys(root: &Table, vaddr: usize) -> Option<usize> {
FILE: risc_v/src/plic.rs
constant PLIC_PRIORITY (line 9) | const PLIC_PRIORITY: usize = 0x0c00_0000;
constant PLIC_PENDING (line 10) | const PLIC_PENDING: usize = 0x0c00_1000;
constant PLIC_INT_ENABLE (line 11) | const PLIC_INT_ENABLE: usize = 0x0c00_2000;
constant PLIC_THRESHOLD (line 12) | const PLIC_THRESHOLD: usize = 0x0c20_0000;
constant PLIC_CLAIM (line 13) | const PLIC_CLAIM: usize = 0x0c20_0004;
function next (line 33) | pub fn next() -> Option<u32> {
function complete (line 53) | pub fn complete(id: u32) {
function set_threshold (line 67) | pub fn set_threshold(tsh: u8) {
function is_pending (line 79) | pub fn is_pending(id: u32) -> bool {
function enable (line 90) | pub fn enable(id: u32) {
function set_priority (line 104) | pub fn set_priority(id: u32, prio: u8) {
function handle_interrupt (line 116) | pub fn handle_interrupt() {
FILE: risc_v/src/process.rs
constant STACK_PAGES (line 22) | pub const STACK_PAGES: usize = 35;
constant STACK_ADDR (line 25) | pub const STACK_ADDR: usize = 0x1_0000_0000;
constant PROCESS_STARTING_ADDR (line 28) | pub const PROCESS_STARTING_ADDR: usize = 0x2000_0000;
function set_running (line 52) | pub fn set_running(pid: u16) -> bool {
function set_waiting (line 77) | pub fn set_waiting(pid: u16) -> bool {
function set_sleeping (line 100) | pub fn set_sleeping(pid: u16, duration: usize) -> bool {
function delete_process (line 125) | pub fn delete_process(pid: u16) {
function get_by_pid (line 147) | pub unsafe fn get_by_pid(pid: u16) -> *mut Process {
function init_process (line 163) | fn init_process() {
function add_kernel_process (line 177) | pub fn add_kernel_process(func: fn()) -> u16 {
function ra_delete_proc (line 254) | fn ra_delete_proc() {
function add_kernel_process_args (line 261) | pub fn add_kernel_process_args(func: fn(args_ptr: usize), args: usize) -...
function init (line 340) | pub fn init() -> usize {
type ProcessState (line 370) | pub enum ProcessState {
type Process (line 377) | pub struct Process {
method drop (line 392) | fn drop(&mut self) {
type Descriptor (line 416) | pub enum Descriptor {
type ProcessData (line 433) | pub struct ProcessData {
method new (line 444) | pub fn new() -> Self {
FILE: risc_v/src/rng.rs
type EntropyDevice (line 13) | pub struct EntropyDevice {
method new (line 20) | pub const fn new() -> Self {
function setup_entropy_device (line 39) | pub fn setup_entropy_device(ptr: *mut u32) -> bool {
function get_random (line 134) | pub fn get_random() -> u64 {
FILE: risc_v/src/sched.rs
function schedule (line 9) | pub fn schedule() -> usize {
FILE: risc_v/src/syscall.rs
function do_syscall (line 25) | pub unsafe fn do_syscall(mepc: usize, frame: *mut TrapFrame) {
function make_syscall (line 429) | fn make_syscall(sysno: usize, arg0: usize, arg1: usize, arg2: usize, arg...
function do_make_syscall (line 432) | fn do_make_syscall(sysno: usize, arg0: usize, arg1: usize, arg2: usize, ...
function syscall_yield (line 436) | pub fn syscall_yield() {
function syscall_exit (line 440) | pub fn syscall_exit() {
function syscall_execv (line 444) | pub fn syscall_execv(path: *const u8, argv: usize) -> usize {
function syscall_fs_read (line 448) | pub fn syscall_fs_read(dev: usize, inode: u32, buffer: *mut u8, size: u3...
function syscall_block_read (line 452) | pub fn syscall_block_read(dev: usize, buffer: *mut u8, size: u32, offset...
function syscall_sleep (line 456) | pub fn syscall_sleep(duration: usize) {
function syscall_get_pid (line 460) | pub fn syscall_get_pid() -> u16 {
function exec_func (line 466) | fn exec_func(args: usize) {
FILE: risc_v/src/test.rs
function test (line 6) | pub fn test() {
FILE: risc_v/src/trap.rs
function m_trap (line 18) | extern "C" fn m_trap(epc: usize,
constant MMIO_MTIMECMP (line 146) | pub const MMIO_MTIMECMP: *mut u64 = 0x0200_4000usize as *mut u64;
constant MMIO_MTIME (line 147) | pub const MMIO_MTIME: *const u64 = 0x0200_BFF8 as *const u64;
function schedule_next_context_switch (line 149) | pub fn schedule_next_context_switch(qm: u16) {
FILE: risc_v/src/uart.rs
type Uart (line 8) | pub struct Uart {
method new (line 22) | pub fn new(base_address: usize) -> Self {
method init (line 26) | pub fn init(&mut self) {
method put (line 103) | pub fn put(&mut self, c: u8) {
method get (line 110) | pub fn get(&mut self) -> Option<u8> {
method write_str (line 13) | fn write_str(&mut self, out: &str) -> Result<(), Error> {
function handle_interrupt (line 125) | pub fn handle_interrupt() {
FILE: risc_v/src/virtio.rs
constant VIRTIO_F_RING_INDIRECT_DESC (line 16) | pub const VIRTIO_F_RING_INDIRECT_DESC: u32 = 28;
constant VIRTIO_F_RING_EVENT_IDX (line 17) | pub const VIRTIO_F_RING_EVENT_IDX: u32 = 29;
constant VIRTIO_F_VERSION_1 (line 18) | pub const VIRTIO_F_VERSION_1: u32 = 32;
constant VIRTIO_DESC_F_NEXT (line 20) | pub const VIRTIO_DESC_F_NEXT: u16 = 1;
constant VIRTIO_DESC_F_WRITE (line 21) | pub const VIRTIO_DESC_F_WRITE: u16 = 2;
constant VIRTIO_DESC_F_INDIRECT (line 22) | pub const VIRTIO_DESC_F_INDIRECT: u16 = 4;
constant VIRTIO_AVAIL_F_NO_INTERRUPT (line 24) | pub const VIRTIO_AVAIL_F_NO_INTERRUPT: u16 = 1;
constant VIRTIO_USED_F_NO_NOTIFY (line 26) | pub const VIRTIO_USED_F_NO_NOTIFY: u16 = 1;
constant VIRTIO_RING_SIZE (line 31) | pub const VIRTIO_RING_SIZE: usize = 1 << 7;
type Descriptor (line 41) | pub struct Descriptor {
type Available (line 49) | pub struct Available {
type UsedElem (line 57) | pub struct UsedElem {
type Used (line 63) | pub struct Used {
type Queue (line 71) | pub struct Queue {
type MmioOffsets (line 83) | pub enum MmioOffsets {
method val (line 154) | pub fn val(self) -> usize {
method scaled (line 158) | pub fn scaled(self, scale: usize) -> usize {
method scale32 (line 162) | pub fn scale32(self) -> usize {
type MmioDevice (line 108) | pub struct MmioDevice {
type DeviceTypes (line 138) | pub enum DeviceTypes {
type StatusField (line 168) | pub enum StatusField {
method val (line 180) | pub fn val(self) -> usize {
method val32 (line 184) | pub fn val32(self) -> u32 {
method test (line 188) | pub fn test(sf: u32, bit: StatusField) -> bool {
method is_failed (line 192) | pub fn is_failed(sf: u32) -> bool {
method needs_reset (line 196) | pub fn needs_reset(sf: u32) -> bool {
method driver_ok (line 200) | pub fn driver_ok(sf: u32) -> bool {
method features_ok (line 204) | pub fn features_ok(sf: u32) -> bool {
constant MMIO_VIRTIO_START (line 212) | pub const MMIO_VIRTIO_START: usize = 0x1000_1000;
constant MMIO_VIRTIO_END (line 213) | pub const MMIO_VIRTIO_END: usize = 0x1000_8000;
constant MMIO_VIRTIO_STRIDE (line 214) | pub const MMIO_VIRTIO_STRIDE: usize = 0x1000;
constant MMIO_VIRTIO_MAGIC (line 215) | pub const MMIO_VIRTIO_MAGIC: u32 = 0x74_72_69_76;
type VirtioDevice (line 222) | pub struct VirtioDevice {
method new (line 227) | pub const fn new() -> Self {
method new_with (line 231) | pub const fn new_with(devtype: DeviceTypes) -> Self {
function probe (line 240) | pub fn probe() {
function setup_network_device (line 341) | pub fn setup_network_device(_ptr: *mut u32) -> bool {
function handle_interrupt (line 349) | pub fn handle_interrupt(interrupt: u32) {
FILE: risc_v/userspace/fb.cpp
type Pixel (line 24) | struct Pixel {
type Event (line 31) | struct Event {
type Rect (line 50) | struct Rect {
function u32 (line 57) | constexpr u32 lerp(u32 val, u32 mx1, u32 mx2) {
function main (line 62) | int main()
function set_pixel (line 89) | void set_pixel(Pixel *fb, u32 x, u32 y, Pixel &color) {
function fill_rect (line 95) | void fill_rect(Pixel *fb, u32 x, u32 y, u32 width, u32 height, Pixel &co...
function stroke_rect (line 103) | void stroke_rect(Pixel *fb, u32 x, u32 y, u32 width, u32 height, Pixel &...
function draw_cosine (line 115) | void draw_cosine(Pixel *fb, u32 x, u32 y, u32 width, u32 height, Pixel &...
function draw_circle (line 126) | void draw_circle(Pixel *fb, u32 x, u32 y, f64 r, Pixel &color)
FILE: risc_v/userspace/helloworld.cpp
function main (line 8) | int main()
FILE: risc_v/userspace/shell.cpp
function main (line 3) | int main()
FILE: risc_v/userspace/sleepy.cpp
function main (line 2) | int main()
FILE: risc_v/userspace/startlib/printf.cpp
function _out_buffer (line 125) | static inline void _out_buffer(char character, void *buffer, size_t idx,...
function _putchar (line 133) | void _putchar(char c)
function _out_null (line 139) | static inline void _out_null(char character, void *buffer, size_t idx, s...
function _out_char (line 148) | static inline void _out_char(char character, void *buffer, size_t idx, s...
function _out_fct (line 160) | static inline void _out_fct(char character, void *buffer, size_t idx, si...
function _strnlen_s (line 173) | static inline unsigned int _strnlen_s(const char *str, size_t maxsize)
function _is_digit (line 183) | static inline bool _is_digit(char ch)
function _atoi (line 189) | static unsigned int _atoi(const char **str)
function _out_rev (line 200) | static size_t _out_rev(out_fct_type out, char *buffer, size_t idx, size_...
function _ntoa_format (line 232) | static size_t _ntoa_format(out_fct_type out, char *buffer, size_t idx, s...
function _ntoa_long (line 300) | static size_t _ntoa_long(out_fct_type out, char *buffer, size_t idx, siz...
function _ntoa_long_long (line 327) | static size_t _ntoa_long_long(out_fct_type out, char *buffer, size_t idx...
function _ftoa (line 361) | static size_t _ftoa(out_fct_type out, char *buffer, size_t idx, size_t m...
function _etoa (line 512) | static size_t _etoa(out_fct_type out, char *buffer, size_t idx, size_t m...
function sprintf (line 1005) | int sprintf(char *buffer, const char *format, ...)
function snprintf (line 1014) | int snprintf(char *buffer, size_t count, const char *format, ...)
function vprintf_ (line 1023) | int vprintf_(const char *format, va_list va)
function vsnprintf_ (line 1029) | int vsnprintf_(char *buffer, size_t count, const char *format, va_list va)
function fctprintf (line 1034) | int fctprintf(void (*out)(char character, void *arg), void *arg, const c...
Condensed preview — 198 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (911K chars).
[
{
"path": "LICENSE",
"chars": 1069,
"preview": "MIT License\n\nCopyright (c) 2019 Stephen Marz\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
},
{
"path": "README.md",
"chars": 192,
"preview": "# osblog\nThe Adventures of OS\n\n# RISC-V OS in Rust\n- risc_v/src - contains RISC-V OS in Rust\n- risc_v/src/asm - contains"
},
{
"path": "assembly/.gitignore",
"chars": 34,
"preview": "*\n!*.S\n!*.c\n!.gitignore\n!Makefile\n"
},
{
"path": "assembly/intrin.S",
"chars": 532,
"preview": ".intel_syntax noprefix\n.section .text\n.global calc_asm\ncalc_asm:\n\t# rdi rsi rdx rcx\n\tmovupd\txmm0, [rsi + 0]\n\tmovupd\txmm1"
},
{
"path": "assembly/intrin.c",
"chars": 1021,
"preview": "#include <stdio.h>\n#include <pmmintrin.h>\n\nvoid calc_intrin(float result[], float matrix[], float vector[]);\nvoid calc_a"
},
{
"path": "risc_v/.cargo/config",
"chars": 488,
"preview": "[build]\ntarget = \"riscv64gc-unknown-none-elf\"\nrustflags = ['-Clink-arg=-Tsrc/lds/virt.lds']\n\n[target.riscv64gc-unknown-n"
},
{
"path": "risc_v/.gitignore",
"chars": 35,
"preview": "os.elf\ntarget/*\nCargo.lock\nhdd.dsk\n"
},
{
"path": "risc_v/BUILD.md",
"chars": 820,
"preview": "# PREREQS\nYou will need to install the riscv64gc target using rustup as well as cargo-binutils using cargo.\n\n* rustup ta"
},
{
"path": "risc_v/Cargo.toml",
"chars": 325,
"preview": "[package]\nname = \"sos\"\nversion = \"0.1.0\"\nauthors = [\"Stephen Marz <stephen.marz@utk.edu>\"]\nedition = \"2018\"\n\n# See more "
},
{
"path": "risc_v/chapters/ch0/.build.config",
"chars": 703,
"preview": "USE_SUDO=\"sudo\"\nJOBS=\"10\"\nLINUX_VER=\"git\"\nBUILD_ROOT=\"${PWD}\"\nTOOLCHAIN_ROOT=\"\"\nBUILD_BINUTILS=\"${BUILD_ROOT}/build-binu"
},
{
"path": "risc_v/chapters/ch0/.cargo/config",
"chars": 124,
"preview": "[build]\ntarget = \"riscv64gc-unknown-none-elf\"\n\n[target.riscv64gc-unknown-none-elf]\nlinker = \"riscv64-unknown-linux-gnu-g"
},
{
"path": "risc_v/chapters/ch0/Cargo.toml",
"chars": 256,
"preview": "[package]\nname = \"sos\"\nversion = \"0.1.0\"\nauthors = [\"Stephen Marz <stephen.marz@utk.edu>\"]\nedition = \"2018\"\n\n# See more "
},
{
"path": "risc_v/chapters/ch0/Makefile",
"chars": 876,
"preview": "#####\n## BUILD\n#####\nCC=riscv64-unknown-linux-gnu-g++\nCFLAGS=-Wall -Wextra -pedantic -Wextra -O0 -g -std=c++17\nCFLAGS+=-"
},
{
"path": "risc_v/chapters/ch0/do.sh",
"chars": 8825,
"preview": "#!/bin/bash\n# For building cross compilers\n# Use this at your own risk!\n# I make no warranties or guarantees with this s"
},
{
"path": "risc_v/chapters/ch0/virt.lds",
"chars": 9574,
"preview": "/*\n virt.lds\n Linker script for outputting to RISC-V QEMU \"virt\" machine.\n Stephen Marz\n 6 October 2019\n*/\n\n/*\n riscv i"
},
{
"path": "risc_v/chapters/ch1/.cargo/config",
"chars": 124,
"preview": "[build]\ntarget = \"riscv64gc-unknown-none-elf\"\n\n[target.riscv64gc-unknown-none-elf]\nlinker = \"riscv64-unknown-linux-gnu-g"
},
{
"path": "risc_v/chapters/ch1/.gitignore",
"chars": 35,
"preview": "os.elf\ntarget/*\nCargo.lock\nhdd.dsk\n"
},
{
"path": "risc_v/chapters/ch1/Cargo.toml",
"chars": 256,
"preview": "[package]\nname = \"sos\"\nversion = \"0.1.0\"\nauthors = [\"Stephen Marz <stephen.marz@utk.edu>\"]\nedition = \"2018\"\n\n# See more "
},
{
"path": "risc_v/chapters/ch1/Makefile",
"chars": 876,
"preview": "#####\n## BUILD\n#####\nCC=riscv64-unknown-linux-gnu-g++\nCFLAGS=-Wall -Wextra -pedantic -Wextra -O0 -g -std=c++17\nCFLAGS+=-"
},
{
"path": "risc_v/chapters/ch1/make_hdd.sh",
"chars": 53,
"preview": "#!/bin/sh\n\ndd if=/dev/zero of=hdd.dsk bs=1M count=32\n"
},
{
"path": "risc_v/chapters/ch1/src/asm/boot.S",
"chars": 2299,
"preview": "# boot.S\n# bootloader for SoS\n# Stephen Marz\n# 8 February 2019\n\n# Disable generation of compressed instructions.\n.option"
},
{
"path": "risc_v/chapters/ch1/src/asm/trap.S",
"chars": 172,
"preview": "# trap.S\n# In the future our trap vector will go here.\n\n.global asm_trap_vector\n# This will be our trap vector when we s"
},
{
"path": "risc_v/chapters/ch1/src/lds/virt.lds",
"chars": 951,
"preview": "OUTPUT_ARCH( \"riscv\" )\n\nENTRY( _start )\n\nMEMORY\n{\n ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M\n}\n\nPHDRS\n{\n tex"
},
{
"path": "risc_v/chapters/ch1/src/lib.rs",
"chars": 1523,
"preview": "// Steve Operating System\n// Stephen Marz\n// 21 Sep 2019\n#![no_std]\n#![feature(panic_info_message,asm)]\n\n// ////////////"
},
{
"path": "risc_v/chapters/ch2/.cargo/config",
"chars": 124,
"preview": "[build]\ntarget = \"riscv64gc-unknown-none-elf\"\n\n[target.riscv64gc-unknown-none-elf]\nlinker = \"riscv64-unknown-linux-gnu-g"
},
{
"path": "risc_v/chapters/ch2/.gitignore",
"chars": 35,
"preview": "os.elf\ntarget/*\nCargo.lock\nhdd.dsk\n"
},
{
"path": "risc_v/chapters/ch2/Cargo.toml",
"chars": 256,
"preview": "[package]\nname = \"sos\"\nversion = \"0.1.0\"\nauthors = [\"Stephen Marz <stephen.marz@utk.edu>\"]\nedition = \"2018\"\n\n# See more "
},
{
"path": "risc_v/chapters/ch2/Makefile",
"chars": 876,
"preview": "#####\n## BUILD\n#####\nCC=riscv64-unknown-linux-gnu-g++\nCFLAGS=-Wall -Wextra -pedantic -Wextra -O0 -g -std=c++17\nCFLAGS+=-"
},
{
"path": "risc_v/chapters/ch2/make_hdd.sh",
"chars": 53,
"preview": "#!/bin/sh\n\ndd if=/dev/zero of=hdd.dsk bs=1M count=32\n"
},
{
"path": "risc_v/chapters/ch2/src/asm/boot.S",
"chars": 1257,
"preview": "# boot.S\n# bootloader for SoS\n# Stephen Marz\n# 8 February 2019\n.option norvc\n.section .data\n.section .text.init\n.global "
},
{
"path": "risc_v/chapters/ch2/src/asm/trap.S",
"chars": 172,
"preview": "# trap.S\n# In the future our trap vector will go here.\n\n.global asm_trap_vector\n# This will be our trap vector when we s"
},
{
"path": "risc_v/chapters/ch2/src/lds/virt.lds",
"chars": 9574,
"preview": "/*\n virt.lds\n Linker script for outputting to RISC-V QEMU \"virt\" machine.\n Stephen Marz\n 6 October 2019\n*/\n\n/*\n riscv i"
},
{
"path": "risc_v/chapters/ch2/src/lib.rs",
"chars": 3577,
"preview": "// Steve Operating System\n// Stephen Marz\n// 21 Sep 2019\n#![no_std]\n#![feature(panic_info_message,asm)]\n\n// ////////////"
},
{
"path": "risc_v/chapters/ch2/src/uart.rs",
"chars": 3902,
"preview": "// uart.rs\n// UART routines and driver\n\nuse core::convert::TryInto;\nuse core::fmt::Write;\nuse core::fmt::Error;\n\npub str"
},
{
"path": "risc_v/chapters/ch3/.cargo/config",
"chars": 124,
"preview": "[build]\ntarget = \"riscv64gc-unknown-none-elf\"\n\n[target.riscv64gc-unknown-none-elf]\nlinker = \"riscv64-unknown-linux-gnu-g"
},
{
"path": "risc_v/chapters/ch3/.gitignore",
"chars": 35,
"preview": "os.elf\ntarget/*\nCargo.lock\nhdd.dsk\n"
},
{
"path": "risc_v/chapters/ch3/Cargo.toml",
"chars": 256,
"preview": "[package]\nname = \"sos\"\nversion = \"0.1.0\"\nauthors = [\"Stephen Marz <stephen.marz@utk.edu>\"]\nedition = \"2018\"\n\n# See more "
},
{
"path": "risc_v/chapters/ch3/Makefile",
"chars": 888,
"preview": "#####\n## BUILD\n#####\nCC=riscv64-unknown-linux-gnu-gcc\nCFLAGS=-Wall -Wextra -pedantic -Wextra -O0 -g\nCFLAGS+=-static -ffr"
},
{
"path": "risc_v/chapters/ch3/make_hdd.sh",
"chars": 53,
"preview": "#!/bin/sh\n\ndd if=/dev/zero of=hdd.dsk bs=1M count=32\n"
},
{
"path": "risc_v/chapters/ch3/src/asm/boot.S",
"chars": 4436,
"preview": "# boot.S\n# bootloader for SoS\n# Stephen Marz\n# 8 February 2019\n\n# Disable generation of compressed instructions.\n.option"
},
{
"path": "risc_v/chapters/ch3/src/asm/mem.S",
"chars": 739,
"preview": "// mem.S\n// Importation of linker symbols\n\n.section .rodata\n.global HEAP_START\nHEAP_START: .dword _heap_start\n\n.global H"
},
{
"path": "risc_v/chapters/ch3/src/asm/trap.S",
"chars": 206,
"preview": "# trap.S\n# In the future our trap vector will go here.\n\n.global asm_trap_vector\n# This will be our trap vector when we s"
},
{
"path": "risc_v/chapters/ch3/src/kmem.rs",
"chars": 7374,
"preview": "// kmem.rs\n// Sub-page level: malloc-like allocation system\n// Stephen Marz\n// 7 October 2019\n\nuse crate::page::{align_v"
},
{
"path": "risc_v/chapters/ch3/src/lds/virt.lds",
"chars": 9626,
"preview": "/*\n virt.lds\n Linker script for outputting to RISC-V QEMU \"virt\" machine.\n Stephen Marz\n 6 October 2019\n*/\n\n/*\n riscv i"
},
{
"path": "risc_v/chapters/ch3/src/lib.rs",
"chars": 9305,
"preview": "// Steve Operating System\n// Stephen Marz\n// 21 Sep 2019\n#![no_std]\n#![feature(panic_info_message,\n asm,\n "
},
{
"path": "risc_v/chapters/ch3/src/page.rs",
"chars": 16398,
"preview": "// page.rs\n// Memory routines\n// Stephen Marz\n// 6 October 2019\n\nuse core::{mem::size_of, ptr::null_mut};\n\n// //////////"
},
{
"path": "risc_v/chapters/ch3/src/uart.rs",
"chars": 3819,
"preview": "// uart.rs\n// UART routines and driver\n\nuse core::convert::TryInto;\nuse core::fmt::Write;\nuse core::fmt::Error;\n\npub str"
},
{
"path": "risc_v/chapters/ch4/.cargo/config",
"chars": 124,
"preview": "[build]\ntarget = \"riscv64gc-unknown-none-elf\"\n\n[target.riscv64gc-unknown-none-elf]\nlinker = \"riscv64-unknown-linux-gnu-g"
},
{
"path": "risc_v/chapters/ch4/.gitignore",
"chars": 35,
"preview": "os.elf\ntarget/*\nCargo.lock\nhdd.dsk\n"
},
{
"path": "risc_v/chapters/ch4/Cargo.toml",
"chars": 256,
"preview": "[package]\nname = \"sos\"\nversion = \"0.1.0\"\nauthors = [\"Stephen Marz <stephen.marz@utk.edu>\"]\nedition = \"2018\"\n\n# See more "
},
{
"path": "risc_v/chapters/ch4/Makefile",
"chars": 888,
"preview": "#####\n## BUILD\n#####\nCC=riscv64-unknown-linux-gnu-gcc\nCFLAGS=-Wall -Wextra -pedantic -Wextra -O0 -g\nCFLAGS+=-static -ffr"
},
{
"path": "risc_v/chapters/ch4/make_hdd.sh",
"chars": 53,
"preview": "#!/bin/sh\n\ndd if=/dev/zero of=hdd.dsk bs=1M count=32\n"
},
{
"path": "risc_v/chapters/ch4/src/asm/boot.S",
"chars": 5009,
"preview": "# boot.S\n# bootloader for SoS\n# Stephen Marz\n# 8 February 2019\n\n# Disable generation of compressed instructions.\n.option"
},
{
"path": "risc_v/chapters/ch4/src/asm/mem.S",
"chars": 680,
"preview": "// mem.S\n// Importation of linker symbols\n\n.section .rodata\n.global HEAP_START\nHEAP_START: .dword _heap_start\n\n.global H"
},
{
"path": "risc_v/chapters/ch4/src/asm/trap.S",
"chars": 2317,
"preview": "# trap.S\n# Trap handler and global context\n# Steve Operating System\n# Stephen Marz\n# 24 February 2019\n.option norvc\n.alt"
},
{
"path": "risc_v/chapters/ch4/src/cpu.rs",
"chars": 2696,
"preview": "// cpu.rs\n// CPU and CPU-related routines\n// Also contains the kernel's trap frame\n// Stephen Marz\n// 14 October 2019\n\nu"
},
{
"path": "risc_v/chapters/ch4/src/kmem.rs",
"chars": 7370,
"preview": "// kmem.rs\n// Sub-page level: malloc-like allocation system\n// Stephen Marz\n// 7 October 2019\n\nuse crate::page::{align_v"
},
{
"path": "risc_v/chapters/ch4/src/lds/virt.lds",
"chars": 9626,
"preview": "/*\n virt.lds\n Linker script for outputting to RISC-V QEMU \"virt\" machine.\n Stephen Marz\n 6 October 2019\n*/\n\n/*\n riscv i"
},
{
"path": "risc_v/chapters/ch4/src/lib.rs",
"chars": 12080,
"preview": "// Steve Operating System\n// Stephen Marz\n// 21 Sep 2019\n#![no_std]\n#![feature(panic_info_message,\n asm,\n "
},
{
"path": "risc_v/chapters/ch4/src/page.rs",
"chars": 16733,
"preview": "// page.rs\n// Memory routines\n// Stephen Marz\n// 6 October 2019\n\nuse core::{mem::size_of, ptr::null_mut};\n\n// //////////"
},
{
"path": "risc_v/chapters/ch4/src/trap.rs",
"chars": 2739,
"preview": "// trap.rs\n// Trap routines\n// Stephen Marz\n// 10 October 2019\n\nuse crate::cpu::TrapFrame;\n\n#[no_mangle]\nextern \"C\" fn m"
},
{
"path": "risc_v/chapters/ch4/src/uart.rs",
"chars": 3868,
"preview": "// uart.rs\n// UART routines and driver\n\nuse core::{convert::TryInto,\n fmt::{Error, Write}};\n\npub struct Uart {"
},
{
"path": "risc_v/chapters/ch5/.cargo/config",
"chars": 124,
"preview": "[build]\ntarget = \"riscv64gc-unknown-none-elf\"\n\n[target.riscv64gc-unknown-none-elf]\nlinker = \"riscv64-unknown-linux-gnu-g"
},
{
"path": "risc_v/chapters/ch5/.gitignore",
"chars": 35,
"preview": "os.elf\ntarget/*\nCargo.lock\nhdd.dsk\n"
},
{
"path": "risc_v/chapters/ch5/Cargo.toml",
"chars": 256,
"preview": "[package]\nname = \"sos\"\nversion = \"0.1.0\"\nauthors = [\"Stephen Marz <stephen.marz@utk.edu>\"]\nedition = \"2018\"\n\n# See more "
},
{
"path": "risc_v/chapters/ch5/Makefile",
"chars": 888,
"preview": "#####\n## BUILD\n#####\nCC=riscv64-unknown-linux-gnu-gcc\nCFLAGS=-Wall -Wextra -pedantic -Wextra -O0 -g\nCFLAGS+=-static -ffr"
},
{
"path": "risc_v/chapters/ch5/make_hdd.sh",
"chars": 53,
"preview": "#!/bin/sh\n\ndd if=/dev/zero of=hdd.dsk bs=1M count=32\n"
},
{
"path": "risc_v/chapters/ch5/src/asm/boot.S",
"chars": 5021,
"preview": "# boot.S\n# bootloader for SoS\n# Stephen Marz\n# 8 February 2019\n\n# Disable generation of compressed instructions.\n.option"
},
{
"path": "risc_v/chapters/ch5/src/asm/mem.S",
"chars": 680,
"preview": "// mem.S\n// Importation of linker symbols\n\n.section .rodata\n.global HEAP_START\nHEAP_START: .dword _heap_start\n\n.global H"
},
{
"path": "risc_v/chapters/ch5/src/asm/trap.S",
"chars": 2317,
"preview": "# trap.S\n# Trap handler and global context\n# Steve Operating System\n# Stephen Marz\n# 24 February 2019\n.option norvc\n.alt"
},
{
"path": "risc_v/chapters/ch5/src/cpu.rs",
"chars": 4542,
"preview": "// cpu.rs\n// CPU and CPU-related routines\n// Also contains the kernel's trap frame\n// Stephen Marz\n// 14 October 2019\n\nu"
},
{
"path": "risc_v/chapters/ch5/src/kmem.rs",
"chars": 7370,
"preview": "// kmem.rs\n// Sub-page level: malloc-like allocation system\n// Stephen Marz\n// 7 October 2019\n\nuse crate::page::{align_v"
},
{
"path": "risc_v/chapters/ch5/src/lds/virt.lds",
"chars": 9626,
"preview": "/*\n virt.lds\n Linker script for outputting to RISC-V QEMU \"virt\" machine.\n Stephen Marz\n 6 October 2019\n*/\n\n/*\n riscv i"
},
{
"path": "risc_v/chapters/ch5/src/lib.rs",
"chars": 11976,
"preview": "// Steve Operating System\n// Stephen Marz\n// 21 Sep 2019\n#![no_std]\n#![feature(panic_info_message,\n asm,\n "
},
{
"path": "risc_v/chapters/ch5/src/page.rs",
"chars": 16733,
"preview": "// page.rs\n// Memory routines\n// Stephen Marz\n// 6 October 2019\n\nuse core::{mem::size_of, ptr::null_mut};\n\n// //////////"
},
{
"path": "risc_v/chapters/ch5/src/plic.rs",
"chars": 3867,
"preview": "// plic.rs\n// Platform Level Interrupt Controller (PLIC)\n// Stephen Marz\n// 1 Nov 2019\n\nconst PLIC_PRIORITY: usize = 0x0"
},
{
"path": "risc_v/chapters/ch5/src/trap.rs",
"chars": 5110,
"preview": "// trap.rs\n// Trap routines\n// Stephen Marz\n// 10 October 2019\n\nuse crate::cpu::TrapFrame;\nuse crate::{plic, uart};\n\n#[n"
},
{
"path": "risc_v/chapters/ch5/src/uart.rs",
"chars": 3868,
"preview": "// uart.rs\n// UART routines and driver\n\nuse core::{convert::TryInto,\n fmt::{Error, Write}};\n\npub struct Uart {"
},
{
"path": "risc_v/chapters/ch6/.cargo/config",
"chars": 124,
"preview": "[build]\ntarget = \"riscv64gc-unknown-none-elf\"\n\n[target.riscv64gc-unknown-none-elf]\nlinker = \"riscv64-unknown-linux-gnu-g"
},
{
"path": "risc_v/chapters/ch6/.gitignore",
"chars": 35,
"preview": "os.elf\ntarget/*\nCargo.lock\nhdd.dsk\n"
},
{
"path": "risc_v/chapters/ch6/Cargo.toml",
"chars": 256,
"preview": "[package]\nname = \"sos\"\nversion = \"0.1.0\"\nauthors = [\"Stephen Marz <stephen.marz@utk.edu>\"]\nedition = \"2018\"\n\n# See more "
},
{
"path": "risc_v/chapters/ch6/Makefile",
"chars": 888,
"preview": "#####\n## BUILD\n#####\nCC=riscv64-unknown-linux-gnu-gcc\nCFLAGS=-Wall -Wextra -pedantic -Wextra -O0 -g\nCFLAGS+=-static -ffr"
},
{
"path": "risc_v/chapters/ch6/make_hdd.sh",
"chars": 53,
"preview": "#!/bin/sh\n\ndd if=/dev/zero of=hdd.dsk bs=1M count=32\n"
},
{
"path": "risc_v/chapters/ch6/src/asm/boot.S",
"chars": 4370,
"preview": "# boot.S\n# bootloader for SoS\n# Stephen Marz\n# 8 February 2019\n\n# Disable generation of compressed instructions.\n.option"
},
{
"path": "risc_v/chapters/ch6/src/asm/mem.S",
"chars": 680,
"preview": "// mem.S\n// Importation of linker symbols\n\n.section .rodata\n.global HEAP_START\nHEAP_START: .dword _heap_start\n\n.global H"
},
{
"path": "risc_v/chapters/ch6/src/asm/trap.S",
"chars": 2317,
"preview": "# trap.S\n# Trap handler and global context\n# Steve Operating System\n# Stephen Marz\n# 24 February 2019\n.option norvc\n.alt"
},
{
"path": "risc_v/chapters/ch6/src/cpu.rs",
"chars": 4542,
"preview": "// cpu.rs\n// CPU and CPU-related routines\n// Also contains the kernel's trap frame\n// Stephen Marz\n// 14 October 2019\n\nu"
},
{
"path": "risc_v/chapters/ch6/src/kmem.rs",
"chars": 7370,
"preview": "// kmem.rs\n// Sub-page level: malloc-like allocation system\n// Stephen Marz\n// 7 October 2019\n\nuse crate::page::{align_v"
},
{
"path": "risc_v/chapters/ch6/src/lds/virt.lds",
"chars": 9626,
"preview": "/*\n virt.lds\n Linker script for outputting to RISC-V QEMU \"virt\" machine.\n Stephen Marz\n 6 October 2019\n*/\n\n/*\n riscv i"
},
{
"path": "risc_v/chapters/ch6/src/lib.rs",
"chars": 5076,
"preview": "// Steve Operating System\n// Stephen Marz\n// 21 Sep 2019\n#![no_std]\n#![feature(panic_info_message,\n asm,\n "
},
{
"path": "risc_v/chapters/ch6/src/page.rs",
"chars": 16733,
"preview": "// page.rs\n// Memory routines\n// Stephen Marz\n// 6 October 2019\n\nuse core::{mem::size_of, ptr::null_mut};\n\n// //////////"
},
{
"path": "risc_v/chapters/ch6/src/plic.rs",
"chars": 3867,
"preview": "// plic.rs\n// Platform Level Interrupt Controller (PLIC)\n// Stephen Marz\n// 1 Nov 2019\n\nconst PLIC_PRIORITY: usize = 0x0"
},
{
"path": "risc_v/chapters/ch6/src/process.rs",
"chars": 8095,
"preview": "// process.rs\n// Kernel and user processes\n// Stephen Marz\n// 27 Nov 2019\n\nuse crate::{cpu::{build_satp,\n "
},
{
"path": "risc_v/chapters/ch6/src/trap.rs",
"chars": 5386,
"preview": "// trap.rs\n// Trap routines\n// Stephen Marz\n// 10 October 2019\n\nuse crate::cpu::TrapFrame;\nuse crate::{plic, uart};\n\n#[n"
},
{
"path": "risc_v/chapters/ch6/src/uart.rs",
"chars": 4018,
"preview": "// uart.rs\n// UART routines and driver\n\nuse core::{convert::TryInto,\n fmt::{Error, Write}};\n\npub struct Uart {"
},
{
"path": "risc_v/chapters/ch7/.cargo/config",
"chars": 124,
"preview": "[build]\ntarget = \"riscv64gc-unknown-none-elf\"\n\n[target.riscv64gc-unknown-none-elf]\nlinker = \"riscv64-unknown-linux-gnu-g"
},
{
"path": "risc_v/chapters/ch7/.gitignore",
"chars": 35,
"preview": "os.elf\ntarget/*\nCargo.lock\nhdd.dsk\n"
},
{
"path": "risc_v/chapters/ch7/Cargo.toml",
"chars": 256,
"preview": "[package]\nname = \"sos\"\nversion = \"0.1.0\"\nauthors = [\"Stephen Marz <stephen.marz@utk.edu>\"]\nedition = \"2018\"\n\n# See more "
},
{
"path": "risc_v/chapters/ch7/Makefile",
"chars": 888,
"preview": "#####\n## BUILD\n#####\nCC=riscv64-unknown-linux-gnu-gcc\nCFLAGS=-Wall -Wextra -pedantic -Wextra -O0 -g\nCFLAGS+=-static -ffr"
},
{
"path": "risc_v/chapters/ch7/make_hdd.sh",
"chars": 53,
"preview": "#!/bin/sh\n\ndd if=/dev/zero of=hdd.dsk bs=1M count=32\n"
},
{
"path": "risc_v/chapters/ch7/src/asm/boot.S",
"chars": 4370,
"preview": "# boot.S\n# bootloader for SoS\n# Stephen Marz\n# 8 February 2019\n\n# Disable generation of compressed instructions.\n.option"
},
{
"path": "risc_v/chapters/ch7/src/asm/mem.S",
"chars": 680,
"preview": "// mem.S\n// Importation of linker symbols\n\n.section .rodata\n.global HEAP_START\nHEAP_START: .dword _heap_start\n\n.global H"
},
{
"path": "risc_v/chapters/ch7/src/asm/trap.S",
"chars": 2317,
"preview": "# trap.S\n# Trap handler and global context\n# Steve Operating System\n# Stephen Marz\n# 24 February 2019\n.option norvc\n.alt"
},
{
"path": "risc_v/chapters/ch7/src/cpu.rs",
"chars": 4542,
"preview": "// cpu.rs\n// CPU and CPU-related routines\n// Also contains the kernel's trap frame\n// Stephen Marz\n// 14 October 2019\n\nu"
},
{
"path": "risc_v/chapters/ch7/src/kmem.rs",
"chars": 7370,
"preview": "// kmem.rs\n// Sub-page level: malloc-like allocation system\n// Stephen Marz\n// 7 October 2019\n\nuse crate::page::{align_v"
},
{
"path": "risc_v/chapters/ch7/src/lds/virt.lds",
"chars": 9626,
"preview": "/*\n virt.lds\n Linker script for outputting to RISC-V QEMU \"virt\" machine.\n Stephen Marz\n 6 October 2019\n*/\n\n/*\n riscv i"
},
{
"path": "risc_v/chapters/ch7/src/lib.rs",
"chars": 5093,
"preview": "// Steve Operating System\n// Stephen Marz\n// 21 Sep 2019\n#![no_std]\n#![feature(panic_info_message,\n asm,\n "
},
{
"path": "risc_v/chapters/ch7/src/page.rs",
"chars": 16733,
"preview": "// page.rs\n// Memory routines\n// Stephen Marz\n// 6 October 2019\n\nuse core::{mem::size_of, ptr::null_mut};\n\n// //////////"
},
{
"path": "risc_v/chapters/ch7/src/plic.rs",
"chars": 3867,
"preview": "// plic.rs\n// Platform Level Interrupt Controller (PLIC)\n// Stephen Marz\n// 1 Nov 2019\n\nconst PLIC_PRIORITY: usize = 0x0"
},
{
"path": "risc_v/chapters/ch7/src/process.rs",
"chars": 8099,
"preview": "// process.rs\n// Kernel and user processes\n// Stephen Marz\n// 27 Nov 2019\n\nuse crate::{cpu::{build_satp,\n "
},
{
"path": "risc_v/chapters/ch7/src/sched.rs",
"chars": 238,
"preview": "// sched.rs\n// Simple process scheduler\n// Stephen Marz\n// 27 Dec 2019\n\nuse crate::process::{PROCESS_LIST, Process};\nuse"
},
{
"path": "risc_v/chapters/ch7/src/syscall.rs",
"chars": 503,
"preview": "// syscall.rs\n// System calls\n// Stephen Marz\n// 3 Jan 2020\n\nuse crate::cpu::TrapFrame;\n\npub fn do_syscall(mepc: usize, "
},
{
"path": "risc_v/chapters/ch7/src/trap.rs",
"chars": 5470,
"preview": "// trap.rs\n// Trap routines\n// Stephen Marz\n// 10 October 2019\n\nuse crate::cpu::TrapFrame;\nuse crate::{plic, uart};\nuse "
},
{
"path": "risc_v/chapters/ch7/src/uart.rs",
"chars": 3868,
"preview": "// uart.rs\n// UART routines and driver\n\nuse core::{convert::TryInto,\n fmt::{Error, Write}};\n\npub struct Uart {"
},
{
"path": "risc_v/chapters/ch8/.cargo/config",
"chars": 124,
"preview": "[build]\ntarget = \"riscv64gc-unknown-none-elf\"\n\n[target.riscv64gc-unknown-none-elf]\nlinker = \"riscv64-unknown-linux-gnu-g"
},
{
"path": "risc_v/chapters/ch8/.gitignore",
"chars": 16,
"preview": "os.elf\ntarget/*\n"
},
{
"path": "risc_v/chapters/ch8/Cargo.toml",
"chars": 256,
"preview": "[package]\nname = \"sos\"\nversion = \"0.1.0\"\nauthors = [\"Stephen Marz <stephen.marz@utk.edu>\"]\nedition = \"2018\"\n\n# See more "
},
{
"path": "risc_v/chapters/ch8/Makefile",
"chars": 888,
"preview": "#####\n## BUILD\n#####\nCC=riscv64-unknown-linux-gnu-gcc\nCFLAGS=-Wall -Wextra -pedantic -Wextra -O0 -g\nCFLAGS+=-static -ffr"
},
{
"path": "risc_v/chapters/ch8/make_hdd.sh",
"chars": 53,
"preview": "#!/bin/sh\n\ndd if=/dev/zero of=hdd.dsk bs=1M count=32\n"
},
{
"path": "risc_v/chapters/ch8/src/asm/boot.S",
"chars": 4326,
"preview": "# boot.S\n# bootloader for SoS\n# Stephen Marz\n# 8 February 2019\n\n# Disable generation of compressed instructions.\n.option"
},
{
"path": "risc_v/chapters/ch8/src/asm/mem.S",
"chars": 680,
"preview": "// mem.S\n// Importation of linker symbols\n\n.section .rodata\n.global HEAP_START\nHEAP_START: .dword _heap_start\n\n.global H"
},
{
"path": "risc_v/chapters/ch8/src/asm/trap.S",
"chars": 3125,
"preview": "# trap.S\n# Trap handler and global context\n# Steve Operating System\n# Stephen Marz\n# 24 February 2019\n.option norvc\n.alt"
},
{
"path": "risc_v/chapters/ch8/src/cpu.rs",
"chars": 4729,
"preview": "// cpu.rs\n// CPU and CPU-related routines\n// Also contains the kernel's trap frame\n// Stephen Marz\n// 14 October 2019\n\nu"
},
{
"path": "risc_v/chapters/ch8/src/kmem.rs",
"chars": 7370,
"preview": "// kmem.rs\n// Sub-page level: malloc-like allocation system\n// Stephen Marz\n// 7 October 2019\n\nuse crate::page::{align_v"
},
{
"path": "risc_v/chapters/ch8/src/lds/virt.lds",
"chars": 9626,
"preview": "/*\n virt.lds\n Linker script for outputting to RISC-V QEMU \"virt\" machine.\n Stephen Marz\n 6 October 2019\n*/\n\n/*\n riscv i"
},
{
"path": "risc_v/chapters/ch8/src/lib.rs",
"chars": 5273,
"preview": "// Steve Operating System\n// Stephen Marz\n// 21 Sep 2019\n#![no_std]\n#![feature(panic_info_message,\n asm,\n "
},
{
"path": "risc_v/chapters/ch8/src/page.rs",
"chars": 16733,
"preview": "// page.rs\n// Memory routines\n// Stephen Marz\n// 6 October 2019\n\nuse core::{mem::size_of, ptr::null_mut};\n\n// //////////"
},
{
"path": "risc_v/chapters/ch8/src/plic.rs",
"chars": 3867,
"preview": "// plic.rs\n// Platform Level Interrupt Controller (PLIC)\n// Stephen Marz\n// 1 Nov 2019\n\nconst PLIC_PRIORITY: usize = 0x0"
},
{
"path": "risc_v/chapters/ch8/src/process.rs",
"chars": 8924,
"preview": "// process.rs\n// Kernel and user processes\n// Stephen Marz\n// 27 Nov 2019\n\nuse crate::{cpu::TrapFrame,\n page:"
},
{
"path": "risc_v/chapters/ch8/src/sched.rs",
"chars": 1152,
"preview": "// sched.rs\n// Simple process scheduler\n// Stephen Marz\n// 27 Dec 2019\n\nuse crate::{process::{ProcessState, PROCESS_LIST"
},
{
"path": "risc_v/chapters/ch8/src/syscall.rs",
"chars": 1028,
"preview": "// syscall.rs\n// System calls\n// Stephen Marz\n// 3 Jan 2020\n\nuse crate::cpu::TrapFrame;\n\npub fn do_syscall(mepc: usize, "
},
{
"path": "risc_v/chapters/ch8/src/trap.rs",
"chars": 6094,
"preview": "// trap.rs\n// Trap routines\n// Stephen Marz\n// 10 October 2019\n\nuse crate::cpu::TrapFrame;\nuse crate::{plic, uart};\nuse "
},
{
"path": "risc_v/chapters/ch8/src/uart.rs",
"chars": 3868,
"preview": "// uart.rs\n// UART routines and driver\n\nuse core::{convert::TryInto,\n fmt::{Error, Write}};\n\npub struct Uart {"
},
{
"path": "risc_v/chapters/ch9/.cargo/config",
"chars": 124,
"preview": "[build]\ntarget = \"riscv64gc-unknown-none-elf\"\n\n[target.riscv64gc-unknown-none-elf]\nlinker = \"riscv64-unknown-linux-gnu-g"
},
{
"path": "risc_v/chapters/ch9/.gitignore",
"chars": 35,
"preview": "os.elf\ntarget/*\nCargo.lock\nhdd.dsk\n"
},
{
"path": "risc_v/chapters/ch9/Cargo.toml",
"chars": 256,
"preview": "[package]\nname = \"sos\"\nversion = \"0.1.0\"\nauthors = [\"Stephen Marz <stephen.marz@utk.edu>\"]\nedition = \"2018\"\n\n# See more "
},
{
"path": "risc_v/chapters/ch9/Makefile",
"chars": 1044,
"preview": "#####\n## BUILD\n#####\nCC=riscv64-unknown-linux-gnu-gcc\nCFLAGS=-Wall -Wextra -pedantic -Wextra -O0 -g\nCFLAGS+=-static -ffr"
},
{
"path": "risc_v/chapters/ch9/make_hdd.sh",
"chars": 56,
"preview": "#!/bin/sh\n\ndd if=/dev/urandom of=hdd.dsk bs=1M count=32\n"
},
{
"path": "risc_v/chapters/ch9/src/asm/boot.S",
"chars": 4326,
"preview": "# boot.S\n# bootloader for SoS\n# Stephen Marz\n# 8 February 2019\n\n# Disable generation of compressed instructions.\n.option"
},
{
"path": "risc_v/chapters/ch9/src/asm/mem.S",
"chars": 680,
"preview": "// mem.S\n// Importation of linker symbols\n\n.section .rodata\n.global HEAP_START\nHEAP_START: .dword _heap_start\n\n.global H"
},
{
"path": "risc_v/chapters/ch9/src/asm/trap.S",
"chars": 3233,
"preview": "# trap.S\n# Trap handler and global context\n# Steve Operating System\n# Stephen Marz\n# 24 February 2019\n.option norvc\n.alt"
},
{
"path": "risc_v/chapters/ch9/src/block.rs",
"chars": 13516,
"preview": "// block.rs\n// Block device using VirtIO protocol\n// Stephen Marz\n// 10 March 2020\n\nuse crate::{kmem::{kfree, kmalloc},\n"
},
{
"path": "risc_v/chapters/ch9/src/cpu.rs",
"chars": 4653,
"preview": "// cpu.rs\n// CPU and CPU-related routines\n// Also contains the kernel's trap frame\n// Stephen Marz\n// 14 October 2019\n\n/"
},
{
"path": "risc_v/chapters/ch9/src/kmem.rs",
"chars": 7370,
"preview": "// kmem.rs\n// Sub-page level: malloc-like allocation system\n// Stephen Marz\n// 7 October 2019\n\nuse crate::page::{align_v"
},
{
"path": "risc_v/chapters/ch9/src/lds/virt.lds",
"chars": 9626,
"preview": "/*\n virt.lds\n Linker script for outputting to RISC-V QEMU \"virt\" machine.\n Stephen Marz\n 6 October 2019\n*/\n\n/*\n riscv i"
},
{
"path": "risc_v/chapters/ch9/src/lib.rs",
"chars": 5356,
"preview": "// Steve Operating System\n// Stephen Marz\n// 21 Sep 2019\n#![no_std]\n#![feature(panic_info_message,\n asm,\n "
},
{
"path": "risc_v/chapters/ch9/src/page.rs",
"chars": 16733,
"preview": "// page.rs\n// Memory routines\n// Stephen Marz\n// 6 October 2019\n\nuse core::{mem::size_of, ptr::null_mut};\n\n// //////////"
},
{
"path": "risc_v/chapters/ch9/src/plic.rs",
"chars": 6160,
"preview": "// plic.rs\n// Platform Level Interrupt Controller (PLIC)\n// Stephen Marz\n// 1 Nov 2019\n\nuse crate::uart::Uart;\nuse crate"
},
{
"path": "risc_v/chapters/ch9/src/process.rs",
"chars": 9262,
"preview": "// process.rs\n// Kernel and user processes\n// Stephen Marz\n// 27 Nov 2019\n\nuse crate::{cpu::{TrapFrame, satp_fence_asid,"
},
{
"path": "risc_v/chapters/ch9/src/rng.rs",
"chars": 146,
"preview": "// rng.rs\n// Random number generator using VirtIO\n// Stephen Marz\n// 16 March 2020\n\npub fn setup_entropy_device(_ptr: *m"
},
{
"path": "risc_v/chapters/ch9/src/sched.rs",
"chars": 980,
"preview": "// sched.rs\n// Simple process scheduler\n// Stephen Marz\n// 27 Dec 2019\n\nuse crate::process::{ProcessState, PROCESS_LIST}"
},
{
"path": "risc_v/chapters/ch9/src/syscall.rs",
"chars": 1028,
"preview": "// syscall.rs\n// System calls\n// Stephen Marz\n// 3 Jan 2020\n\nuse crate::cpu::TrapFrame;\n\npub fn do_syscall(mepc: usize, "
},
{
"path": "risc_v/chapters/ch9/src/trap.rs",
"chars": 4531,
"preview": "// trap.rs\n// Trap routines\n// Stephen Marz\n// 10 October 2019\n\nuse crate::cpu::{CONTEXT_SWITCH_TIME, TrapFrame};\nuse cr"
},
{
"path": "risc_v/chapters/ch9/src/uart.rs",
"chars": 3868,
"preview": "// uart.rs\n// UART routines and driver\n\nuse core::{convert::TryInto,\n fmt::{Error, Write}};\n\npub struct Uart {"
},
{
"path": "risc_v/chapters/ch9/src/virtio.rs",
"chars": 8178,
"preview": "// virtio.rs\n// VirtIO routines for the VirtIO protocol\n// Stephen Marz\n// 10 March 2020\n\nuse crate::{block, block::setu"
},
{
"path": "risc_v/src/asm/boot.S",
"chars": 4362,
"preview": "# boot.S\n# bootloader for SoS\n# Stephen Marz\n# 8 February 2019\n\n# Disable generation of compressed instructions.\n.option"
},
{
"path": "risc_v/src/asm/mem.S",
"chars": 680,
"preview": "// mem.S\n// Importation of linker symbols\n\n.section .rodata\n.global HEAP_START\nHEAP_START: .dword _heap_start\n\n.global H"
},
{
"path": "risc_v/src/asm/trap.S",
"chars": 3864,
"preview": "# trap.S\n# Trap handler and global context\n# Steve Operating System\n# Stephen Marz\n# 24 February 2019\n.option norvc\n.alt"
},
{
"path": "risc_v/src/assembly.rs",
"chars": 359,
"preview": "// assembly.rs\n// Assembly imports module\n// Stephen Marz\n// 20 April 2020\n\n// This came from the Rust book documenting "
},
{
"path": "risc_v/src/block.rs",
"chars": 17241,
"preview": "// block.rs\n// Block device using VirtIO protocol\n// Stephen Marz\n// 10 March 2020\n\nuse crate::{kmem::{kfree, kmalloc},\n"
},
{
"path": "risc_v/src/buffer.rs",
"chars": 1697,
"preview": "// buffer.rs\n// BlockBuffer is so useful, we put it here instead\n// of in the file system.\n// Stephen Marz\n\nuse crate::{"
},
{
"path": "risc_v/src/console.rs",
"chars": 2567,
"preview": "// console.rs\n// Console utilities for buffering\n// Stephen Marz\n// 4 June 2020\n\nuse alloc::collections::VecDeque;\nuse c"
},
{
"path": "risc_v/src/cpu.rs",
"chars": 6472,
"preview": "// cpu.rs\n// CPU and CPU-related routines\n// Also contains the kernel's trap frame\n// Stephen Marz\n// 14 October 2019\n\n/"
},
{
"path": "risc_v/src/elf.rs",
"chars": 8967,
"preview": "// elf.rs\n// Routines for reading and parsing ELF\n// (Executable and Linkable Format) files.\n// 26-April-2020\n// Stephen"
},
{
"path": "risc_v/src/fs.rs",
"chars": 20591,
"preview": "// minixfs.rs\n// Minix 3 Filesystem Implementation\n// Stephen Marz\n// 16 March 2020\n\nuse crate::{cpu::Registers,\n "
},
{
"path": "risc_v/src/gpu.rs",
"chars": 20838,
"preview": "// gpu.rs\n// Graphics stuff\n// Stephen Marz\n// 12 May 2020\n\n#![allow(dead_code)]\nuse crate::{page::{zalloc, PAGE_SIZE},\n"
},
{
"path": "risc_v/src/input.rs",
"chars": 10470,
"preview": "// input.rs\n// Input handling.\n// Stephen Marz\n\nuse crate::virtio::{Queue, MmioOffsets, MMIO_VIRTIO_START, StatusField, "
},
{
"path": "risc_v/src/kmem.rs",
"chars": 7371,
"preview": "// kmem.rs\n// Sub-page level: malloc-like allocation system\n// Stephen Marz\n// 7 October 2019\n\nuse crate::page::{align_v"
},
{
"path": "risc_v/src/lds/virt.lds",
"chars": 9622,
"preview": "/*\n virt.lds\n Linker script for outputting to RISC-V QEMU \"virt\" machine.\n Stephen Marz\n 6 October 2019\n*/\n\n/*\n riscv i"
},
{
"path": "risc_v/src/lock.rs",
"chars": 1513,
"preview": "// lock.rs\n// Locking routines\n// Stephen Marz\n// 26 Apr 2020\n\nuse crate::syscall::syscall_sleep;\n\npub const DEFAULT_LOC"
},
{
"path": "risc_v/src/main.rs",
"chars": 3409,
"preview": "// Steve Operating System\n// Stephen Marz\n// 21 Sep 2019\n#![no_main]\n#![no_std]\n#![feature(panic_info_message,\n "
},
{
"path": "risc_v/src/page.rs",
"chars": 16591,
"preview": "// page.rs\n// Memory routines\n// Stephen Marz\n// 6 October 2019\n\nuse core::{mem::size_of, ptr::null_mut};\n\n// //////////"
},
{
"path": "risc_v/src/plic.rs",
"chars": 4763,
"preview": "// plic.rs\n// Platform Level Interrupt Controller (PLIC)\n// Stephen Marz\n// 1 Nov 2019\n\nuse crate::uart;\nuse crate::virt"
},
{
"path": "risc_v/src/process.rs",
"chars": 15344,
"preview": "// process.rs\n// Kernel and user processes\n// Stephen Marz\n// 27 Nov 2019\n\nuse crate::{cpu::{get_mtime,\n "
},
{
"path": "risc_v/src/rng.rs",
"chars": 5462,
"preview": "// rng.rs\n// Random number generator using VirtIO\n// Stephen Marz\n// 16 March 2020\n\n#![allow(dead_code)]\nuse crate::{kme"
},
{
"path": "risc_v/src/sched.rs",
"chars": 1326,
"preview": "// sched.rs\n// Simple process scheduler\n// Stephen Marz\n// 27 Dec 2019\n\nuse crate::process::{ProcessState, PROCESS_LIST,"
},
{
"path": "risc_v/src/syscall.rs",
"chars": 17095,
"preview": "// syscall.rs\n// System calls\n// Stephen Marz\n// 3 Jan 2020\n\nuse crate::{block::block_op,\n buffer::Buffer,\n "
},
{
"path": "risc_v/src/test.rs",
"chars": 474,
"preview": "// test.rs\nuse crate::fs::MinixFileSystem;\nuse crate::syscall;\n/// Test block will load raw binaries into memory to exec"
},
{
"path": "risc_v/src/trap.rs",
"chars": 4915,
"preview": "// trap.rs\n// Trap routines\n// Stephen Marz\n// 10 October 2019\n\nuse crate::{cpu::{TrapFrame, CONTEXT_SWITCH_TIME},\n "
},
{
"path": "risc_v/src/uart.rs",
"chars": 4801,
"preview": "// uart.rs\n// UART routines and driver\n\nuse core::{convert::TryInto,\n\t\t fmt::{Error, Write}};\nuse crate::console::push"
},
{
"path": "risc_v/src/vfs.rs",
"chars": 65,
"preview": "// vfs.rs\n// Virtual File System\n// Stephen Marz\n// 4 June 2020\n\n"
},
{
"path": "risc_v/src/virtio.rs",
"chars": 9497,
"preview": "// virtio.rs\n// VirtIO routines for the VirtIO protocol\n// Stephen Marz\n// 10 March 2020\n\nuse crate::{block, block::setu"
},
{
"path": "risc_v/userspace/.gitignore",
"chars": 70,
"preview": "helloworld\nhelloworld.elf\nsleepy\nsleepy.elf\nshell\nshell.elf\nfb\nfb.elf\n"
},
{
"path": "risc_v/userspace/Makefile",
"chars": 220,
"preview": "CROSS=riscv64-unknown-elf-\nCXX=g++\nCXXFLAGS=-Wall -O3 -static -I.\nSOURCES=$(wildcard *.cpp)\nOUT=$(patsubst %.cpp,%,$(SOU"
},
{
"path": "risc_v/userspace/fb.cpp",
"chars": 2868,
"preview": "#include <sys/stat.h>\n#include <fcntl.h>\n#include <unistd.h>\n#include <cmath>\n#include <cstdio>\n#include <input-event-co"
},
{
"path": "risc_v/userspace/helloworld.cpp",
"chars": 581,
"preview": "#include <cstdio>\n#include <cmath>\n\nconst int SIZE = 100000;\ndouble myarray[SIZE];\nint another_array[5] = {1, 2, 3, 4, 5"
},
{
"path": "risc_v/userspace/input-event-codes.h",
"chars": 25227,
"preview": "/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */\n/*\n * Input event codes\n *\n * *** IMPORTANT ***\n * Thi"
},
{
"path": "risc_v/userspace/shell.cpp",
"chars": 231,
"preview": "#include <cstdio>\n#include <unistd.h>\nint main()\n{\n\tprintf(\"Started shell.\\n\");\n\tchar data[100];\n\twhile (1) {\n\t\tprintf(\""
},
{
"path": "risc_v/userspace/sleepy.cpp",
"chars": 129,
"preview": "#include <cstdio>\nint main()\n{\n\tprintf(\"I'm going to bed.\\nYou can watch me sleep for 100 switches using 'top'\\n\");\n\tret"
},
{
"path": "risc_v/userspace/startlib/.gitignore",
"chars": 8,
"preview": "*.a\n*.o\n"
},
{
"path": "risc_v/userspace/startlib/Makefile",
"chars": 521,
"preview": "CROSS=riscv64-unknown-linux-gnu-\nCXX=g++\nOBJCOPY=objcopy\nAR=ar\nCXXFLAGS=-Wall -O0 -ffreestanding -nostartfiles -nostdlib"
},
{
"path": "risc_v/userspace/startlib/linker.lds",
"chars": 950,
"preview": "OUTPUT_ARCH( \"riscv\" )\n\nENTRY( _start )\n\nMEMORY\n{\n ram (wxa!ri) : ORIGIN = 0x20000000, LENGTH = 128M\n}\n\nPHDRS\n{\n tex"
},
{
"path": "risc_v/userspace/startlib/printf.cpp",
"chars": 25600,
"preview": "///////////////////////////////////////////////////////////////////////////////\n// \\author (c) Marco Paland (info@paland"
},
{
"path": "risc_v/userspace/startlib/printf.h",
"chars": 4813,
"preview": "///////////////////////////////////////////////////////////////////////////////\n// \\author (c) Marco Paland (info@paland"
},
{
"path": "risc_v/userspace/startlib/start.S",
"chars": 222,
"preview": "\n.section .text\n.global _start\n_start:\n.option push\n.option norelax\n\tla\tgp, __global_pointer$\n.option pop\n\tcall\tmain\n\t# "
},
{
"path": "risc_v/userspace/startlib/syscall.S",
"chars": 203,
"preview": ".section .text\n.global make_syscall\nmake_syscall:\n\tmv a7, a0\n\tmv a0, a1\n\tmv a1, a2\n\tmv a2, a3\n\tmv a3, a4\n\tmv a4, a5\n\tmv "
},
{
"path": "risc_v/userspace/startlib/syscall.h",
"chars": 919,
"preview": "#pragma once\n\nextern \"C\"\n{\n\tunsigned long make_syscall(unsigned long sysno,\n\t\t\t\t unsigned long a1=0,\n\t\t\t\t unsigned l"
},
{
"path": "risc_v/userspace/upload.sh",
"chars": 333,
"preview": "#!/bin/sh\n\nif [ $# -ne 1 ]; then\n\techo \"You provied $# parameters, need 1\"\n\texit 1\nfi\n\nif [ ! -r $1 ]; then\n\techo \"Unkno"
}
]
About this extraction
This page contains the full source code of the sgmarz/osblog GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 198 files (811.8 KB), approximately 251.1k tokens, and a symbol index with 1210 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.