├── usr ├── lib │ ├── sys │ │ ├── syscall.S │ │ ├── entry.yu │ │ ├── structs.yu │ │ └── syscall.yu │ ├── except.yu │ ├── c │ │ └── string.yu │ ├── sync │ │ ├── spinlock.yu │ │ └── slimpl.c │ ├── stack.yu │ ├── io.yu │ └── alloc.yu ├── bin │ ├── hello.yu │ ├── notepad.yu │ ├── alloc.yu │ └── shell.yu └── Makefile ├── CHANGELOG.md ├── src ├── lib │ ├── algo.yu │ ├── except.yu │ ├── alloc.yu │ ├── c │ │ └── string.yu │ ├── queue.yu │ ├── io.yu │ ├── strview.yu │ ├── elf.yu │ └── hashmap.yu ├── boot │ ├── linker.ld │ ├── init.S │ ├── uart.yu │ └── entry.yu ├── init.S ├── syscall │ ├── proc.yu │ ├── syscall.yu │ └── fs.yu ├── main.yu ├── proc │ ├── consts.yu │ ├── switch.S │ ├── proc.yu │ ├── scheduler.yu │ ├── pool.yu │ ├── processor.yu │ └── structs.yu ├── arch │ ├── riscv │ │ ├── framealloc.yu │ │ ├── consts.yu │ │ ├── csr.S │ │ ├── csr.yu │ │ ├── addr.yu │ │ ├── pagetable.yu │ │ └── recursive.yu │ ├── target │ │ ├── risky32.yu │ │ ├── virt.yu │ │ └── fuxi.yu │ └── arch.yu ├── fs │ ├── fs.yu │ ├── geefs │ │ ├── fs.yu │ │ ├── structs.yu │ │ └── geefs.yu │ ├── consts.yu │ ├── devfs │ │ ├── fs.yu │ │ └── stdio.yu │ ├── dev │ │ ├── device.yu │ │ └── mem.yu │ ├── info.yu │ ├── file.yu │ └── vfs │ │ └── vfs.yu ├── linker.ld ├── mem │ ├── consts.yu │ ├── attr.yu │ ├── mset.yu │ ├── pm.yu │ ├── mem.yu │ ├── area.yu │ ├── handler.yu │ ├── heap.yu │ └── paging.yu ├── sync │ ├── intr.yu │ ├── condvar.yu │ ├── spinlock.yu │ ├── semaphore.yu │ └── slimpl.c ├── entry.yu ├── trap │ ├── trap.yu │ └── traphand.S ├── Makefile └── define │ └── context.yu ├── mkfs ├── Makefile ├── iosdev.h ├── iosdev.cpp ├── structs.h ├── device.h ├── geefs.h └── main.cpp ├── rules.mk ├── .gitignore ├── utils ├── bin2coe.py └── uart.py ├── README.md ├── Makefile └── toolchain.mk /usr/lib/sys/syscall.S: -------------------------------------------------------------------------------- 1 | .section .text 2 | .globl syscall 3 | syscall: 4 | ecall 5 | ret 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to the Gee OS will be documented in this file. 4 | 5 | ## Unreleased 6 | -------------------------------------------------------------------------------- /usr/bin/hello.yu: -------------------------------------------------------------------------------- 1 | import lib.io 2 | 3 | extern def main(argc: i32, argv: u8**): i32 { 4 | io <<< "Hello world!\n" 5 | 0 6 | } 7 | -------------------------------------------------------------------------------- /src/lib/algo.yu: -------------------------------------------------------------------------------- 1 | inline def min(x: u32, y: u32): u32 { 2 | if x < y { x } else { y } 3 | } 4 | 5 | inline def max(x: u32, y: u32): u32 { 6 | if x > y { x } else { y } 7 | } 8 | -------------------------------------------------------------------------------- /usr/lib/sys/entry.yu: -------------------------------------------------------------------------------- 1 | import lib.alloc 2 | import lib.sys.syscall 3 | 4 | // user defined main function 5 | extern declare main: (i32, u8**): i32 6 | 7 | // entry of user threads 8 | extern def _start(argc: i32, argv: u8**) { 9 | initUserHeap() 10 | exit(main(argc, argv) as usize) 11 | } 12 | -------------------------------------------------------------------------------- /src/boot/linker.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_ARCH("riscv") 2 | ENTRY(_start) 3 | SECTIONS 4 | { 5 | . = 0x00000200; 6 | .text : { 7 | *(.text.init) 8 | *(.text*) 9 | *(.rodata*) 10 | } 11 | .data : { 12 | *(.data) 13 | *(.data*) 14 | *(.sdata) 15 | } 16 | . = 0x80000000; 17 | .bss : { 18 | *(.bss*) 19 | } 20 | /DISCARD/ : { 21 | *(.eh_frame) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/init.S: -------------------------------------------------------------------------------- 1 | .section .text.init 2 | .globl _start 3 | _start: 4 | # setup stack 5 | la sp, _stack_top 6 | # jump to machine mode entry (in 'entry.yu') 7 | call entry 8 | 9 | 10 | # filesystem image 11 | .section .data 12 | .globl _user_img_start 13 | .align 2 14 | _user_img_start: 15 | .incbin "user.img" 16 | .globl _user_img_end 17 | _user_img_end: 18 | -------------------------------------------------------------------------------- /src/lib/except.yu: -------------------------------------------------------------------------------- 1 | import arch.arch 2 | import lib.io 3 | 4 | public def panic(str: u8*) { 5 | io <<< "===== KERNEL PANIC! =====\n" 6 | io <<< "message: " <<< str <<< '\n' 7 | halt(1) 8 | } 9 | 10 | public def abort() { 11 | panic("abort") 12 | } 13 | 14 | public def assert(cond: bool, str: u8*) { 15 | if !cond { panic(str) } 16 | } 17 | 18 | public def assert(cond: bool) { 19 | if !cond { abort() } 20 | } 21 | -------------------------------------------------------------------------------- /src/syscall/proc.yu: -------------------------------------------------------------------------------- 1 | import proc.proc 2 | import lib.io 3 | 4 | public def sysExit(code: usize): isize { 5 | exit(code) 6 | 0 as isize 7 | } 8 | 9 | public def sysYield(): isize { 10 | yield() 11 | 0 12 | } 13 | 14 | public def sysExecve(path: u8*, argv: u8**, envp: u8**): isize { 15 | if execute(path, getCurrentTid()) { 16 | sleep() 17 | 0 as isize 18 | } 19 | else { 20 | -1 as isize 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /usr/lib/except.yu: -------------------------------------------------------------------------------- 1 | import lib.sys.syscall 2 | import lib.io 3 | 4 | public def panic(str: u8*) { 5 | io <<< "===== USER PANIC! =====\n" 6 | io <<< "message: " <<< str <<< '\n' 7 | exit(1 as usize) 8 | } 9 | 10 | public def abort() { 11 | panic("abort") 12 | } 13 | 14 | public def assert(cond: bool, str: u8*) { 15 | if !cond { panic(str) } 16 | } 17 | 18 | public def assert(cond: bool) { 19 | if !cond { abort() } 20 | } 21 | -------------------------------------------------------------------------------- /usr/lib/sys/structs.yu: -------------------------------------------------------------------------------- 1 | // constants 2 | inline let FILE_NAME_MAX_LEN = 28 as u32 3 | 4 | // file type 5 | public enum FileType { 6 | File, Dir, CharDevice, BlockDevice, 7 | } 8 | 9 | // file status 10 | public struct Stat { 11 | ino: u32, 12 | file_type: FileType, 13 | size: usize, 14 | blk_size: usize, 15 | blocks: usize, 16 | } 17 | 18 | // directory entry 19 | public struct Dirent { 20 | inode_id: u32, 21 | filename: u8[FILE_NAME_MAX_LEN], 22 | } 23 | -------------------------------------------------------------------------------- /src/main.yu: -------------------------------------------------------------------------------- 1 | import lib.io 2 | import trap.trap 3 | import mem.mem 4 | import fs.fs 5 | import proc.proc 6 | 7 | // supervisor mode entry of GeeOS 8 | public def main() { 9 | io <<< "initializing trap handler...\n" 10 | initTrap() 11 | io <<< "initializing memory...\n" 12 | initMem() 13 | io <<< "initializing filesystem...\n" 14 | initFileSystem() 15 | io <<< "initializing process manager...\n" 16 | initProcess() 17 | io <<< "done!\n\n" 18 | runCpu() 19 | } 20 | -------------------------------------------------------------------------------- /mkfs/Makefile: -------------------------------------------------------------------------------- 1 | # sources & targets 2 | MKFS_SRC := $(call rwildcard, $(MKFS_DIR), *.cpp) 3 | $(call make_obj, MKFS, $(MKFS_SRC)) 4 | MKFS_TARGET := $(BUILD_DIR)/mkfs 5 | 6 | # complier flags 7 | CXXFLAGS := -I$(MKFS_DIR) 8 | 9 | 10 | .PHONY: all clean mkfs 11 | 12 | all: mkfs 13 | 14 | clean: 15 | -rm $(MKFS_TARGET) 16 | 17 | mkfs: $(MKFS_TARGET) 18 | 19 | $(MKFS_TARGET): $(MKFS_OBJ) 20 | $(info making mkfs utility...) 21 | $(NLD) -o $@ $^ 22 | 23 | include $(TOP_DIR)/rules.mk 24 | -------------------------------------------------------------------------------- /usr/lib/c/string.yu: -------------------------------------------------------------------------------- 1 | public import lib.sys.structs 2 | 3 | extern def memcmp(lhs: u8*, rhs: u8*, count: usize): i32 { 4 | var l = lhs, r = rhs, n = 0 as usize 5 | while n < count { 6 | if l[n] != r[n] { 7 | return (l[n] - r[n]) as i32 8 | } 9 | n += 1 as usize 10 | } 11 | 0 12 | } 13 | 14 | extern def strcmp(lhs: u8*, rhs: u8*): i32 { 15 | var s1 = lhs, s2 = rhs 16 | while *s1 == *s2 && *s1 != '\0' { 17 | s1 += 1 18 | s2 += 1 19 | } 20 | *s1 as i32 - *s2 as i32 21 | } 22 | -------------------------------------------------------------------------------- /src/proc/consts.yu: -------------------------------------------------------------------------------- 1 | public import arch.arch 2 | 3 | // some definitions about process managing 4 | 5 | // type alias of thread id 6 | public type Tid = usize 7 | 8 | // tid for 'none' 9 | inline let TID_NONE = -1 as Tid 10 | 11 | // maximum number of thread 12 | inline let MAX_THREAD_COUNT = 128 13 | 14 | // maximum time slice of round robin scheduler 15 | inline let TIME_SLICE = 1 as usize 16 | 17 | // stack of user thread 18 | inline let USER_STACK_SIZE = 0x10000 19 | inline let USER_STACK_OFFSET = 0x7ffff000 - USER_STACK_SIZE 20 | -------------------------------------------------------------------------------- /usr/bin/notepad.yu: -------------------------------------------------------------------------------- 1 | import lib.io 2 | 3 | extern def main(argc: i32, argv: u8**): i32 { 4 | io <<< "this is a notepad. try to type something:\n" 5 | var line_count = 0 6 | while true { 7 | let c = io.getChar() 8 | when c { 9 | '\r', '\n' { 10 | line_count = 0 11 | io <<< "\r\n" 12 | } 13 | '\x7f' { 14 | if line_count > 0 { 15 | line_count -= 1 16 | io <<< "\b \b" 17 | } 18 | } 19 | else { 20 | line_count += 1 21 | io <<< c 22 | } 23 | } 24 | } 25 | 0 26 | } 27 | -------------------------------------------------------------------------------- /rules.mk: -------------------------------------------------------------------------------- 1 | $(OBJ_DIR)/%.yu.ll: $(TOP_DIR)/%.yu 2 | $(info YUC $@) 3 | -mkdir -p $(dir $@) 4 | $(YUC) $(YUCFLAGS) -ot llvm $^ > $@ 5 | 6 | $(OBJ_DIR)/%.c.o: $(TOP_DIR)/%.c 7 | $(info CC $@) 8 | -mkdir -p $(dir $@) 9 | $(CC) $(CFLAGS) -o $@ $^ 10 | 11 | $(OBJ_DIR)/%.cpp.o: $(TOP_DIR)/%.cpp 12 | $(info CXX $@) 13 | -mkdir -p $(dir $@) 14 | $(CXX) $(CXXFLAGS) -o $@ $^ 15 | 16 | $(OBJ_DIR)/%.S.o: $(TOP_DIR)/%.S 17 | $(info AS $@) 18 | -mkdir -p $(dir $@) 19 | $(CC) $(ASFLAGS) -o $@ $^ 20 | 21 | $(OBJ_DIR)/%.o: $(OBJ_DIR)/%.ll 22 | $(info LLC $@) 23 | $(LLC) $(LLCFLAGS) $^ -o $@ 24 | -------------------------------------------------------------------------------- /src/arch/riscv/framealloc.yu: -------------------------------------------------------------------------------- 1 | public import arch.riscv.addr 2 | 3 | // interface of frame allocator 4 | public struct FrameAllocator { 5 | alloc: (FrameAllocator var*): Frame, 6 | } 7 | 8 | // Allocate a frame of the appropriate size, panic on error. 9 | inline def alloc(this: FrameAllocator var*): Frame { 10 | ((*this).alloc)(this) 11 | } 12 | 13 | // interface of frame deallocator 14 | public struct FrameDeallocator { 15 | dealloc: (FrameDeallocator var*, Frame), 16 | } 17 | 18 | inline def dealloc(this: FrameDeallocator var*, frame: Frame) { 19 | ((*this).dealloc)(this, frame) 20 | } 21 | -------------------------------------------------------------------------------- /src/lib/alloc.yu: -------------------------------------------------------------------------------- 1 | public import arch.arch 2 | 3 | public type HeapAllocator = (usize): u8 var* 4 | public type HeapDeallocator = (u8 var*) 5 | 6 | public struct HeapManager { 7 | alloc: HeapAllocator, 8 | dealloc: HeapDeallocator, 9 | } 10 | 11 | public var heap: HeapManager = [HeapManager] {} 12 | 13 | 14 | inline def init(this: HeapManager var&, alloc: HeapAllocator, 15 | dealloc: HeapDeallocator) { 16 | this.alloc = alloc 17 | this.dealloc = dealloc 18 | } 19 | 20 | inline def alloc(this: HeapManager&, size: usize): u8 var* { 21 | (this.alloc)(size) 22 | } 23 | 24 | inline def dealloc(this: HeapManager&, ptr: u8 var*) { 25 | (this.dealloc)(ptr) 26 | } 27 | -------------------------------------------------------------------------------- /src/arch/riscv/consts.yu: -------------------------------------------------------------------------------- 1 | public import arch.arch 2 | 3 | // bytes per page 4 | inline let PAGE_SIZE = (1 << 12) 5 | inline let MEGAPAGE_SIZE = (1 << 22) 6 | 7 | // count of PTEs per page 8 | inline let PTE_COUNT = PAGE_SIZE / 4 9 | 10 | // PTE flags 11 | inline let PTE_FLAG_V = (1 << 0) as usize 12 | inline let PTE_FLAG_R = (1 << 1) as usize 13 | inline let PTE_FLAG_W = (1 << 2) as usize 14 | inline let PTE_FLAG_X = (1 << 3) as usize 15 | inline let PTE_FLAG_U = (1 << 4) as usize 16 | inline let PTE_FLAG_G = (1 << 5) as usize 17 | inline let PTE_FLAG_A = (1 << 6) as usize 18 | inline let PTE_FLAG_D = (1 << 7) as usize 19 | 20 | // recursive index 21 | inline let RECURSIVE_INDEX = 0x3fd as usize 22 | -------------------------------------------------------------------------------- /src/fs/fs.yu: -------------------------------------------------------------------------------- 1 | public import fs.vfs.vfs 2 | 3 | import fs.geefs.fs 4 | import fs.devfs.fs 5 | 6 | // root inode of filesystem 7 | public var root_inode: INode var* = null as INode var* 8 | 9 | 10 | // initialize filesystem 11 | public def initFileSystem() { 12 | // initialize GeeFS 13 | initGeeFs() 14 | // initialize DevFS 15 | initDevFs() 16 | // initialize root inode 17 | root_inode = geefs.getRoot() 18 | root_inode.open() 19 | } 20 | 21 | // clean up filesystem 22 | public def cleanUpFileSystem() { 23 | // release root inode 24 | root_inode.close() 25 | root_inode = null as INode var* 26 | // clean up GeeFS 27 | cleanUpGeeFs() 28 | // clean up DevFS 29 | cleanUpDevFs() 30 | } 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/macos 2 | 3 | ### macOS ### 4 | *.DS_Store 5 | .AppleDouble 6 | .LSOverride 7 | 8 | # Icon must end with two \r 9 | Icon 10 | 11 | 12 | # Thumbnails 13 | ._* 14 | 15 | # Files that might appear in the root of a volume 16 | .DocumentRevisions-V100 17 | .fseventsd 18 | .Spotlight-V100 19 | .TemporaryItems 20 | .Trashes 21 | .VolumeIcon.icns 22 | .com.apple.timemachine.donotpresent 23 | 24 | # Directories potentially created on remote AFP share 25 | .AppleDB 26 | .AppleDesktop 27 | Network Trash Folder 28 | Temporary Items 29 | .apdisk 30 | 31 | # End of https://www.gitignore.io/api/macos 32 | 33 | # VS Code 34 | .vscode 35 | 36 | # Build & Debug 37 | build 38 | debug 39 | -------------------------------------------------------------------------------- /src/boot/init.S: -------------------------------------------------------------------------------- 1 | .section .text.init, "ax" 2 | 3 | # entry of bootloader 4 | .globl _start 5 | _start: 6 | # setup stack 7 | la sp, _stack 8 | # setup trap handler 9 | la t0, _handleTrap 10 | csrw mtvec, t0 11 | # jump to YuLang entry 12 | j entry 13 | 14 | 15 | # trap handler 16 | .globl _handleTrap 17 | _handleTrap: 18 | # read value of important CSRs 19 | csrr a0, mepc 20 | csrr a1, mcause 21 | csrr a2, mtval 22 | # jump to YuLang trap handler 23 | j handleTrap 24 | 25 | 26 | # jump to address 27 | .globl jumpToAddr 28 | jumpToAddr: 29 | fence.i 30 | jr a0 31 | 32 | 33 | # bootloader stack 34 | .section .data 35 | .align 2 36 | .zero 512 37 | .globl _stack 38 | _stack: 39 | -------------------------------------------------------------------------------- /src/linker.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_ARCH("riscv") 2 | ENTRY(_start) 3 | SECTIONS 4 | { 5 | . = 0x80000000; 6 | _geeos_start = .; 7 | .text : { 8 | _text_start = .; 9 | *(.text.init) 10 | *(.text) 11 | *(.text*) 12 | . = ALIGN(4K); 13 | _text_end = .; 14 | } 15 | .rodata : { 16 | _rodata_start = .; 17 | *(.rodata) 18 | *(.rodata*) 19 | . = ALIGN(4K); 20 | _rodata_end = .; 21 | } 22 | .data : { 23 | _data_start = .; 24 | *(.data) 25 | *(.data*) 26 | *(.sdata) 27 | _data_end = .; 28 | } 29 | .bss : { 30 | _bss_start = .; 31 | *(.bss) 32 | *(.sbss*) 33 | _bss_end = .; 34 | } 35 | _stack_start = .; 36 | . = ALIGN(4) + 0x1000; 37 | _stack_top = .; 38 | _stack_end = .; 39 | _geeos_end = .; 40 | } 41 | -------------------------------------------------------------------------------- /usr/lib/sync/spinlock.yu: -------------------------------------------------------------------------------- 1 | import lib.except 2 | import lib.sys.syscall 3 | 4 | public struct Spinlock { 5 | locked: u32, 6 | } 7 | 8 | // implementation of spinlock, in 'slimpl.c' 9 | extern declare __try_to_acquire: (Spinlock var&): i32 10 | extern declare __release: (Spinlock var&) 11 | 12 | public def newSpinlock(): Spinlock { 13 | [Spinlock] {0 as u32} 14 | } 15 | 16 | public def isHold(this: Spinlock&): bool { 17 | this.locked != 0 as u32 18 | } 19 | 20 | public def acquire(this: Spinlock var&) { 21 | assert(!this.isHold(), "acquire") 22 | // perform acquire 23 | while __try_to_acquire(this) != 0 { 24 | yield() 25 | } 26 | } 27 | 28 | public def release(this: Spinlock var&) { 29 | assert(this.isHold(), "release") 30 | // perform release 31 | __release(this) 32 | } 33 | -------------------------------------------------------------------------------- /mkfs/iosdev.h: -------------------------------------------------------------------------------- 1 | #ifndef GEEOS_MKFS_IOSDEV_H_ 2 | #define GEEOS_MKFS_IOSDEV_H_ 3 | 4 | #include 5 | 6 | #include "device.h" 7 | 8 | class IOStreamDevice : public DeviceBase { 9 | public: 10 | IOStreamDevice(std::iostream &ios) : ios_(ios) { 11 | auto pos = ios_.tellg(); 12 | ios_.seekg(0, std::ios::end); 13 | size_ = ios_.tellg() - pos; 14 | } 15 | 16 | std::int32_t Read(std::uint8_t *buf, std::size_t len, 17 | std::size_t offset) override; 18 | std::int32_t Write(const std::uint8_t *buf, std::size_t len, 19 | std::size_t offset) override; 20 | bool Sync() override; 21 | bool Resize(std::size_t size) override; 22 | 23 | private: 24 | std::iostream &ios_; 25 | std::size_t size_; 26 | }; 27 | 28 | #endif // GEEOS_MKFS_IOSDEV_H_ 29 | -------------------------------------------------------------------------------- /src/mem/consts.yu: -------------------------------------------------------------------------------- 1 | public import arch.arch 2 | 3 | // position definitions in 'linker.ld' 4 | extern declare _geeos_start: () 5 | extern declare _geeos_end: () 6 | extern declare _text_start: () 7 | extern declare _text_end: () 8 | extern declare _rodata_start: () 9 | extern declare _rodata_end: () 10 | extern declare _data_start: () 11 | extern declare _data_end: () 12 | extern declare _bss_start: () 13 | extern declare _bss_end: () 14 | extern declare _stack_start: () 15 | extern declare _stack_end: () 16 | 17 | // definitions about physical memory 18 | inline let KERNEL_BASE = MEM_ADDR 19 | inline let KERNEL_VM_BASE = 0x80000000 20 | inline let PHY_STOP = KERNEL_BASE + MEM_SIZE_MIB * 1024 * 1024 21 | inline let HEAP_SIZE = MEM_SIZE_MIB * 1024 22 | inline let HEAP_BASE = PHY_STOP - HEAP_SIZE 23 | -------------------------------------------------------------------------------- /src/sync/intr.yu: -------------------------------------------------------------------------------- 1 | import arch.riscv.csr 2 | import lib.except 3 | 4 | // pushOff/popOff are like setIntrOff()/setIntrOn(), 5 | // except that they are matched: 6 | // 1. it takes two popOff()s to undo two pushOff()s 7 | // 2. if interrupts are initially off, then pushOff, popOff leaves them off 8 | 9 | // depth of 'pushOff' nesting 10 | var push_offset = 0 11 | // where interrupt enabled before 'pushOff' 12 | var last_intr = false 13 | 14 | public def pushOff() { 15 | let last = getIntr() 16 | setIntrOff() 17 | if !push_offset { 18 | last_intr = last 19 | } 20 | push_offset += 1 21 | } 22 | 23 | public def popOff() { 24 | assert(!getIntr(), "popOff - interruptible") 25 | push_offset -= 1 26 | assert(push_offset >= 0, "popOff") 27 | if !push_offset && last_intr { 28 | setIntrOn() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /usr/bin/alloc.yu: -------------------------------------------------------------------------------- 1 | import lib.io 2 | import lib.alloc 3 | 4 | extern def main(argc: i32, argv: u8**): i32 { 5 | io <<< "testing heap allocator...\n" 6 | 7 | let mem4 = alloc.alloc(4 as usize) as i32 var* 8 | (*mem4) = 42 9 | io <<< "allocated 4-byte mem: " <<$ mem4 as usize <<< '\n' 10 | 11 | let mem32 = alloc.alloc(32 as usize) as i32 var* 12 | mem32[0] = 233 13 | mem32[4] = 0xdeadbeef 14 | io <<< "allocated 32-byte mem: " <<$ mem32 as usize <<< '\n' 15 | 16 | alloc.dealloc(mem32 as u8 var*) 17 | io <<< "deallocated 32-byte mem\n" 18 | 19 | let mem2 = alloc.alloc(2 as usize) as i16 var* 20 | (*mem2) = 16 as i16 21 | io <<< "allocated 2-byte mem: " <<$ mem2 as usize <<< '\n' 22 | 23 | alloc.dealloc(mem4 as u8 var*) 24 | alloc.dealloc(mem2 as u8 var*) 25 | io <<< "deallocated all\n" 26 | 0 27 | } 28 | -------------------------------------------------------------------------------- /src/fs/geefs/fs.yu: -------------------------------------------------------------------------------- 1 | public import fs.geefs.geefs 2 | 3 | import fs.dev.mem 4 | import lib.except 5 | 6 | // filesystem image embedded in kernel, defined in 'init.S' 7 | extern declare _user_img_start: () 8 | extern declare _user_img_end: () 9 | 10 | // filesystem related objects 11 | public var geefs: FileSystem var* = null as FileSystem var* 12 | var dev: MemDevice 13 | 14 | 15 | // initialize GeeFS 16 | public def initGeeFs() { 17 | // initialize device 18 | dev = newMemDevice(_user_img_start as u8 var*, 19 | _user_img_end as usize - _user_img_start as usize) 20 | // initialize GeeFS 21 | initGeeFsOps() 22 | geefs = newGeeFs(&dev as DeviceInterface var*) 23 | if geefs == null as FileSystem var* { 24 | panic("failed to open GeeFS") 25 | } 26 | } 27 | 28 | // clean up GeeFS 29 | public def cleanUpGeeFs() { 30 | geefs.del() 31 | dev.del() 32 | } 33 | -------------------------------------------------------------------------------- /src/sync/condvar.yu: -------------------------------------------------------------------------------- 1 | public import sync.spinlock 2 | public import lib.queue 3 | 4 | import proc.proc 5 | 6 | public struct CondVar { 7 | lock: Spinlock, 8 | wait_queue: Queue, 9 | } 10 | 11 | public def newCondVar(): CondVar { 12 | [CondVar] {newSpinlock(), newQueue()} 13 | } 14 | 15 | public def del(this: CondVar var&) { 16 | this.wait_queue.del() 17 | } 18 | 19 | public def wait(this: CondVar var&) { 20 | this.lock.acquire() 21 | this.wait_queue.push(getCurrentTid() as usize) 22 | this.lock.release() 23 | sleep() 24 | } 25 | 26 | public def notify(this: CondVar var&) { 27 | var tid: usize 28 | // pop thread id from queue 29 | this.lock.acquire() 30 | let valid = !this.wait_queue.empty() 31 | if valid { 32 | tid = this.wait_queue.pop() 33 | } 34 | this.lock.release() 35 | // wake up thread 36 | if valid { 37 | wakeUp(tid as Tid) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/fs/consts.yu: -------------------------------------------------------------------------------- 1 | // constant definitions about file system 2 | 3 | // open files per thread 4 | inline let FILE_COUNT = 16 5 | 6 | // flags for open: choose one of these 7 | inline let O_RDONLY = 0 // open for reading only 8 | inline let O_WRONLY = 1 // open for writing only 9 | inline let O_RDWR = 2 // open for reading and writing 10 | // then or in any of these: 11 | inline let O_CREAT = 0x00000004 // create file if it does not exist 12 | inline let O_EXCL = 0x00000008 // error if O_CREAT and the file exists 13 | inline let O_TRUNC = 0x00000010 // truncate file upon open 14 | inline let O_APPEND = 0x00000020 // append on each write 15 | // additonal related definition 16 | inline let O_ACCMODE = 3 // mask for O_RDONLY/O_WRONLY/O_RDWR 17 | 18 | // flags for lseek 19 | inline let SEEK_SET = 0 20 | inline let SEEK_CUR = 1 21 | inline let SEEK_END = 2 22 | -------------------------------------------------------------------------------- /src/mem/attr.yu: -------------------------------------------------------------------------------- 1 | public import mem.paging 2 | 3 | // attribute of page 4 | public struct MemoryAttr { 5 | // is user accessable 6 | user: bool, 7 | // is readonly 8 | readonly: bool, 9 | // is executable 10 | execute: bool, 11 | } 12 | 13 | public def newMemoryAttr(): MemoryAttr { 14 | [MemoryAttr] {false, false, false} 15 | } 16 | 17 | public def setUser(this: MemoryAttr): MemoryAttr { 18 | [MemoryAttr] {true, this.readonly, this.execute} 19 | } 20 | 21 | public def setReadonly(this: MemoryAttr): MemoryAttr { 22 | [MemoryAttr] {this.user, true, this.execute} 23 | } 24 | 25 | public def setExecute(this: MemoryAttr): MemoryAttr { 26 | [MemoryAttr] {this.user, this.readonly, true} 27 | } 28 | 29 | public def apply(this: MemoryAttr&, entry: PageEntry var&) { 30 | entry.setPresent(true) 31 | entry.setUser(this.user) 32 | entry.setWritable(!this.readonly) 33 | entry.setExecute(this.execute) 34 | } 35 | -------------------------------------------------------------------------------- /src/sync/spinlock.yu: -------------------------------------------------------------------------------- 1 | import sync.intr 2 | import lib.except 3 | 4 | // structure definition of spinlock 5 | public struct Spinlock { 6 | // is the lock held 7 | locked: u32, 8 | } 9 | 10 | // implementation of spinlock, in 'slimpl.c' 11 | extern declare __acquire: (Spinlock var&) 12 | extern declare __release: (Spinlock var&) 13 | 14 | public def newSpinlock(): Spinlock { 15 | [Spinlock] {0 as u32} 16 | } 17 | 18 | public def isHold(this: Spinlock&): bool { 19 | pushOff() 20 | let r = this.locked != 0 as u32 21 | popOff() 22 | r 23 | } 24 | 25 | public def acquire(this: Spinlock var&) { 26 | // disable interrupts to avoid deadlock 27 | pushOff() 28 | assert(!this.isHold(), "acquire") 29 | // perform acquire 30 | __acquire(this) 31 | } 32 | 33 | public def release(this: Spinlock var&) { 34 | assert(this.isHold(), "release") 35 | // perform release 36 | __release(this) 37 | popOff() 38 | } 39 | -------------------------------------------------------------------------------- /src/arch/target/risky32.yu: -------------------------------------------------------------------------------- 1 | // arch definitions of Risky32 emulator 2 | 3 | inline let FLASH_ADDR = 0x90020000 4 | inline let MEM_ADDR = 0x80000000 5 | inline let MEM_SIZE_MIB = 4 6 | inline let SWITCH_ADDR = 0x1000f020 7 | inline let LED_ADDR = 0x1000f000 8 | inline let NUM_ADDR = 0x1000f010 9 | inline let UART_ADDR = 0x90000104 10 | inline let UART_END = 0x90000200 11 | inline let PLIC_ADDR = 0x00000000 12 | inline let PLIC_END = 0x00000000 13 | inline let CLINT_MTIME = 0x90010000 14 | inline let CLINT_MCMP = 0x90010100 15 | 16 | inline def initIO() {} 17 | inline def initIntr() {} 18 | inline def ackIntr() {} 19 | 20 | inline def putChar(c: u8) { 21 | *(0x90000104 as u8 volatile var*) = c 22 | } 23 | 24 | inline def getChar(): i32 { 25 | *(0x90000104 as u8 volatile var*) as i32 26 | } 27 | 28 | inline def halt(code: i32) { 29 | *(0x90000100 as u8 volatile var*) = 1 as u8 30 | while true {} 31 | } 32 | -------------------------------------------------------------------------------- /src/proc/switch.S: -------------------------------------------------------------------------------- 1 | .equ XLENB, 4 2 | 3 | .macro LOAD a1, a2 4 | lw \a1, \a2*XLENB(sp) 5 | .endm 6 | 7 | .macro STORE a1, a2 8 | sw \a1, \a2*XLENB(sp) 9 | .endm 10 | 11 | .section .text 12 | .globl switchTo 13 | switchTo: 14 | addi sp, sp, -14*XLENB 15 | sw sp, 0(a0) 16 | STORE ra, 0 17 | STORE s0, 2 18 | STORE s1, 3 19 | STORE s2, 4 20 | STORE s3, 5 21 | STORE s4, 6 22 | STORE s5, 7 23 | STORE s6, 8 24 | STORE s7, 9 25 | STORE s8, 10 26 | STORE s9, 11 27 | STORE s10, 12 28 | STORE s11, 13 29 | csrr s11, satp 30 | STORE s11, 1 31 | 32 | lw sp, 0(a1) 33 | LOAD s11, 1 34 | csrw satp, s11 35 | sfence.vma 36 | LOAD ra, 0 37 | LOAD s0, 2 38 | LOAD s1, 3 39 | LOAD s2, 4 40 | LOAD s3, 5 41 | LOAD s4, 6 42 | LOAD s5, 7 43 | LOAD s6, 8 44 | LOAD s7, 9 45 | LOAD s8, 10 46 | LOAD s9, 11 47 | LOAD s10, 12 48 | LOAD s11, 13 49 | addi sp, sp, 14*XLENB 50 | 51 | sw zero, 0(a1) 52 | ret 53 | -------------------------------------------------------------------------------- /src/fs/devfs/fs.yu: -------------------------------------------------------------------------------- 1 | public import fs.vfs.vfs 2 | 3 | import fs.devfs.stdio 4 | 5 | // inodes of stdio 6 | public var stdin: INode var* = null as INode var* 7 | public var stdout: INode var* = null as INode var* 8 | 9 | // input buffer 10 | var inbuffer = [StdIn] {} 11 | 12 | 13 | // initialize device filesystem 14 | public def initDevFs() { 15 | // initialize input buffer 16 | inbuffer = newStdIn() 17 | // initialize stdio 18 | initStdIoOps() 19 | stdin = newStdInINode(&inbuffer) 20 | stdout = newStdOutINode() 21 | // TODO: other devices 22 | } 23 | 24 | // clean up device file system 25 | public def cleanUpDevFs() { 26 | // release stdio 27 | stdin.close() 28 | stdin = null as INode var* 29 | stdout.close() 30 | stdout = null as INode var* 31 | inbuffer.del() 32 | // TODO: other devices 33 | } 34 | 35 | // push character to input buffer 36 | public def pushChar(c: u8) { 37 | if c == '\r' { 38 | inbuffer.push('\n') 39 | } 40 | else { 41 | inbuffer.push(c) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /utils/bin2coe.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | 3 | # convert binary data file to coe file 4 | # designed for Fuxi SoC 5 | # by MaxXing 6 | 7 | import sys 8 | 9 | 10 | def read_bin(file_name): 11 | words = [] 12 | with open(file_name, 'rb') as f: 13 | while True: 14 | x = f.read(4) 15 | if x == b'': 16 | break 17 | word = int.from_bytes(x, byteorder='little') 18 | words.append(hex(word)[2:].zfill(8)) 19 | return words 20 | 21 | 22 | def make_coe(words): 23 | coe = 'memory_initialization_radix = 16;\n' 24 | coe += 'memory_initialization_vector =\n' 25 | contents = ['00000000'] * 128 + words 26 | coe += '\n'.join(contents) + '\n' 27 | return coe 28 | 29 | 30 | if __name__ == '__main__': 31 | if len(sys.argv) < 2: 32 | print('usage: ./bin2coe.py FILE') 33 | exit(1) 34 | 35 | bin_file = sys.argv[1] 36 | coe_file = f'{bin_file}.coe' 37 | 38 | words = read_bin(bin_file) 39 | coe = make_coe(words) 40 | 41 | with open(coe_file, 'w') as f: 42 | f.write(coe) 43 | -------------------------------------------------------------------------------- /mkfs/iosdev.cpp: -------------------------------------------------------------------------------- 1 | #include "iosdev.h" 2 | 3 | #include 4 | 5 | std::int32_t IOStreamDevice::Read(std::uint8_t *buf, std::size_t len, 6 | std::size_t offset) { 7 | if (offset >= size_) return -1; 8 | ios_.seekg(offset); 9 | auto size = std::min(size_ - offset, len); 10 | ios_.read(reinterpret_cast(buf), size); 11 | return !ios_ ? -1 : size; 12 | } 13 | 14 | std::int32_t IOStreamDevice::Write(const std::uint8_t *buf, 15 | std::size_t len, 16 | std::size_t offset) { 17 | if (offset >= size_) return -1; 18 | ios_.seekp(offset); 19 | auto size = std::min(size_ - offset, len); 20 | ios_.write(reinterpret_cast(buf), size); 21 | return !ios_ ? -1 : size; 22 | } 23 | 24 | bool IOStreamDevice::Sync() { 25 | ios_.flush(); 26 | return !!ios_; 27 | } 28 | 29 | bool IOStreamDevice::Resize(std::size_t size) { 30 | ios_.seekp(size - 1); 31 | ios_ << '\0'; 32 | size_ = size; 33 | return !!ios_; 34 | } 35 | -------------------------------------------------------------------------------- /src/fs/dev/device.yu: -------------------------------------------------------------------------------- 1 | public import arch.arch 2 | 3 | // interface of devices 4 | public struct DeviceInterface { 5 | read: (DeviceInterface var*, u8 var*, usize, usize): i32, 6 | write: (DeviceInterface var*, u8*, usize, usize): i32, 7 | sync: (DeviceInterface var*): bool, 8 | } 9 | 10 | inline def read(this: DeviceInterface var*, buf: u8 var*, len: usize, 11 | offset: usize): i32 { 12 | ((*this).read)(this, buf, len, offset) 13 | } 14 | 15 | inline def write(this: DeviceInterface var*, buf: u8*, len: usize, 16 | offset: usize): i32 { 17 | ((*this).write)(this, buf, len, offset) 18 | } 19 | 20 | inline def sync(this: DeviceInterface var*): bool { 21 | ((*this).sync)(this) 22 | } 23 | 24 | inline def readAssert(this: DeviceInterface var*, size: usize, 25 | buf: u8 var*, offset: usize): bool { 26 | this.read(buf, size, offset) == size as i32 27 | } 28 | 29 | inline def writeAssert(this: DeviceInterface var*, size: usize, 30 | buf: u8*, offset: usize): bool { 31 | this.write(buf, size, offset) == size as i32 32 | } 33 | -------------------------------------------------------------------------------- /src/sync/semaphore.yu: -------------------------------------------------------------------------------- 1 | public import sync.spinlock 2 | public import lib.queue 3 | 4 | public struct Semaphore { 5 | lock: Spinlock, 6 | value: i32, 7 | wait_queue: Queue, 8 | } 9 | 10 | import lib.except 11 | import proc.proc 12 | 13 | public def newSemaphore(value: i32): Semaphore { 14 | [Semaphore] {newSpinlock(), value, newQueue()} 15 | } 16 | 17 | public def newSemaphore(): Semaphore { 18 | newSemaphore(1) 19 | } 20 | 21 | public def del(this: Semaphore var&) { 22 | this.wait_queue.del() 23 | } 24 | 25 | public def wait(this: Semaphore var&) { 26 | this.lock.acquire() 27 | if this.value > 0 { 28 | this.value -= 1 29 | this.lock.release() 30 | } 31 | else { 32 | this.wait_queue.push(getCurrentTid() as usize) 33 | this.lock.release() 34 | sleep() 35 | } 36 | } 37 | 38 | public def signal(this: Semaphore var&) { 39 | this.lock.acquire() 40 | if this.wait_queue.empty() { 41 | this.value += 1 42 | this.lock.release() 43 | } 44 | else { 45 | let tid = this.wait_queue.pop() 46 | this.lock.release() 47 | wakeUp(tid as Tid) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /usr/lib/sync/slimpl.c: -------------------------------------------------------------------------------- 1 | // structure definition of spinlock 2 | typedef struct { 3 | unsigned locked; 4 | } Spinlock; 5 | 6 | int __try_to_acquire(Spinlock *lock) { 7 | // On RISC-V, sync_lock_test_and_set turns into an atomic swap: 8 | // a5 = 1 9 | // s1 = &lock->locked 10 | // amoswap.w.aq a5, a5, (s1) 11 | return __sync_lock_test_and_set(&lock->locked, 1); 12 | } 13 | 14 | void __release(Spinlock *lock) { 15 | // Tell the C compiler and the CPU to not move loads or stores 16 | // past this point, to ensure that all the stores in the critical 17 | // section are visible to other CPUs before the lock is released. 18 | // On RISC-V, this turns into a fence instruction. 19 | __sync_synchronize(); 20 | // Release the lock, equivalent to lock->locked = 0. 21 | // This code doesn't use a C assignment, since the C standard 22 | // implies that an assignment might be implemented with 23 | // multiple store instructions. 24 | // On RISC-V, sync_lock_release turns into an atomic swap: 25 | // s1 = &lock->locked 26 | // amoswap.w zero, zero, (s1) 27 | __sync_lock_release(&lock->locked); 28 | } 29 | -------------------------------------------------------------------------------- /src/lib/c/string.yu: -------------------------------------------------------------------------------- 1 | public import arch.arch 2 | 3 | extern def memset(dst: u8 var*, c: i32, n: usize): u8 var* { 4 | var d = dst, i = 0 as usize 5 | while i < n { 6 | d[i] = c as u8 7 | i += 1 as usize 8 | } 9 | dst 10 | } 11 | 12 | extern def memmove(dst: u8 var*, src: u8*, n: usize): u8 var* { 13 | var s = src, d = dst, n = n 14 | if s < d && s + n > d { 15 | // overlapped 16 | s += n 17 | d += n 18 | while n > 0 as usize { 19 | d -= 1 20 | s -= 1 21 | (*d) = *s 22 | n -= 1 as usize 23 | } 24 | } 25 | else { 26 | while n > 0 as usize { 27 | *d = *s 28 | d += 1 29 | s += 1 30 | n -= 1 as usize 31 | } 32 | } 33 | dst 34 | } 35 | 36 | extern def memcpy(dst: u8 var*, src: u8*, n: usize): u8 var* { 37 | memmove(dst, src, n) 38 | } 39 | 40 | extern def memcmp(lhs: u8*, rhs: u8*, count: usize): i32 { 41 | var l = lhs, r = rhs, n = 0 as usize 42 | while n < count { 43 | if l[n] != r[n] { 44 | return (l[n] - r[n]) as i32 45 | } 46 | n += 1 as usize 47 | } 48 | 0 49 | } 50 | 51 | extern def strlen(str: u8*): usize { 52 | var len = 0 53 | while str[len] != '\0' { 54 | len += 1 55 | } 56 | len as usize 57 | } 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GeeOS 2 | 3 | GeeOS (寂) is a lightweight, UNIX like operating system, written in [YuLang](https://github.com/MaxXSoft/YuLang), developed for [Fuxi](https://github.com/MaxXSoft/Fuxi) processor. 4 | 5 | ## Getting Started 6 | 7 | Before building GeeOS, please make sure you have installed the following dependencies: 8 | 9 | * [YuLang](https://github.com/MaxXSoft/YuLang) compiler 10 | * `llvm` toolchain 11 | * C++ compiler supporting C++17 12 | 13 | You may want to check the toolchain configuration in `toolchain.mk`. Then you can build this repository by executing the following command lines: 14 | 15 | ``` 16 | $ git clone https://github.com/MaxXSoft/GeeOS.git 17 | $ cd GeeOS 18 | $ make -j8 19 | ``` 20 | 21 | ELF file of GeeOS will be generated in directory `build`. By default, you can run it with QEMU: 22 | 23 | ``` 24 | $ qemu-system-riscv32 -nographic -machine virt -m 128m -kernel geeos.elf 25 | ``` 26 | 27 | ## Details 28 | 29 | > UNDER CONSTRUCTION... 30 | 31 | ## Changelog 32 | 33 | See [CHANGELOG.md](CHANGELOG.md) 34 | 35 | ## References 36 | 37 | GeeOS is heavily influenced by [rCore](https://github.com/rcore-os/rCore) and [xv6](https://github.com/mit-pdos/xv6-riscv). 38 | 39 | ## License 40 | 41 | Copyright (C) 2010-2020 MaxXing. License GPLv3. 42 | -------------------------------------------------------------------------------- /src/boot/uart.yu: -------------------------------------------------------------------------------- 1 | import arch.arch 2 | import lib.io 3 | 4 | // read a byte from UART 5 | def readUart(): u8 { 6 | var c = -1 7 | while c < 0 { 8 | c = getChar() 9 | } 10 | c as u8 11 | } 12 | 13 | // wait for 0x9e9e9e9e 14 | def waitMagicHeader() { 15 | var i = 0 16 | while i < 4 { 17 | if readUart() == 0x9e as u8 { 18 | i += 1 19 | } 20 | else { 21 | i = 0 22 | } 23 | } 24 | } 25 | 26 | // read 32-bit word from UART 27 | def readWordFromUart(): u32 { 28 | var word = readUart() as u32 29 | word |= readUart() as u32 << 8 as u32 30 | word |= readUart() as u32 << 16 as u32 31 | word |= readUart() as u32 << 24 as u32 32 | word 33 | } 34 | 35 | // receive data from UART 36 | public def receiveFromUart(): u8* { 37 | // wait header 38 | io <<< "waiting for u32 sequence: 0x9e9e9e9e OFFSET LEN DATA...\n" 39 | waitMagicHeader() 40 | // read offset & len 41 | let offset = readWordFromUart() as u8 var*, len = readWordFromUart() 42 | io <<< "offset: 0x" <<$ offset as u32 <<< ", len: " <<< len <<< '\n' 43 | // receive data 44 | io <<< "receiving data...\n" 45 | var i = 0 46 | while i as u32 < len { 47 | offset[i] = readUart() 48 | i += 1 49 | } 50 | io <<< "done receiving data\n" 51 | offset 52 | } 53 | -------------------------------------------------------------------------------- /src/arch/arch.yu: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | all targets must define the following constants: 4 | 5 | - FLASH_ADDR: i32: boot flash address, used by the bootloader 6 | - MEM_ADDR: i32: physical memory start address 7 | - MEM_SIZE_MIB: i32: physical memory size (MiB) 8 | - SWITCH_ADDR: i32: address of GPIO switch 9 | - LED_ADDR: i32: address of GPIO led 10 | - NUM_ADDR: i32: address of GPIO segment display 11 | - UART_ADDR: i32: address of UART device 12 | - UART_END: i32: end address of UART device 13 | - PLIC_ADDR: i32: address of PLIC device 14 | - PLIC_END: i32: end address of PLIC device 15 | - CLINT_MTIME: i32: address of CLINT's 'mtime' register 16 | - CLINT_MCMP: i32: address of CLINT's 'mtimecmp' register 17 | 18 | all targets must define the following functions: 19 | 20 | - initIO: (): initialize IO driver (like UART) 21 | - initIntr: (): initialize interrupt (PLIC) 22 | - ackIntr: (): acknowledge all IRQs (PLIC) 23 | - putChar: (u8): put a character to IO 24 | - getChar: (): i32: get a character from IO 25 | - halt: (i32): halt the CPU, and return exit code (if possible) 26 | 27 | */ 28 | 29 | 30 | // use the following target 31 | public import arch.target.virt 32 | 33 | // halt the CPU 34 | inline def halt() { 35 | halt(0) 36 | } 37 | -------------------------------------------------------------------------------- /src/mem/mset.yu: -------------------------------------------------------------------------------- 1 | public import arch.arch 2 | public import mem.paging 3 | public import mem.attr 4 | public import mem.handler 5 | public import mem.area 6 | 7 | import mem.consts 8 | import lib.except 9 | 10 | public struct MemorySet { 11 | page_table: InactivePageTable, 12 | areas: MemAreaList, 13 | } 14 | 15 | public def newMemorySet(): MemorySet { 16 | var pt = newInactivePageTable() 17 | pt.mapKernel() 18 | [MemorySet] {pt} 19 | } 20 | 21 | public def del(this: MemorySet var&) { 22 | // unmap all mapped entries 23 | for pt in this.page_table.edit() { 24 | this.areas.unmap(pt) 25 | } 26 | // delete page table & area list 27 | this.page_table.del() 28 | this.areas.del() 29 | } 30 | 31 | public def push(this: MemorySet var&, start: usize, end: usize, 32 | attr: MemoryAttr, handler: MemoryHandler var*) { 33 | assert(start <= end, "invalid memory area") 34 | let area = newMemArea(start, end, handler, attr) 35 | this.areas.push(area) 36 | for pt in this.page_table.edit() { 37 | area.map(pt) 38 | } 39 | } 40 | 41 | public def activate(this: MemorySet&) { 42 | this.page_table.activate() 43 | } 44 | 45 | public def with(this: MemorySet&): IptWithIter { 46 | this.page_table.with() 47 | } 48 | 49 | public def token(this: MemorySet&): usize { 50 | this.page_table.token() 51 | } 52 | -------------------------------------------------------------------------------- /src/sync/slimpl.c: -------------------------------------------------------------------------------- 1 | // structure definition of spinlock 2 | typedef struct { 3 | unsigned locked; 4 | } Spinlock; 5 | 6 | void __acquire(Spinlock *lock) { 7 | // On RISC-V, sync_lock_test_and_set turns into an atomic swap: 8 | // a5 = 1 9 | // s1 = &lock->locked 10 | // amoswap.w.aq a5, a5, (s1) 11 | while(__sync_lock_test_and_set(&lock->locked, 1) != 0); 12 | // Tell the C compiler and the processor to not move loads or stores 13 | // past this point, to ensure that the critical section's memory 14 | // references happen after the lock is acquired. 15 | __sync_synchronize(); 16 | } 17 | 18 | void __release(Spinlock *lock) { 19 | // Tell the C compiler and the CPU to not move loads or stores 20 | // past this point, to ensure that all the stores in the critical 21 | // section are visible to other CPUs before the lock is released. 22 | // On RISC-V, this turns into a fence instruction. 23 | __sync_synchronize(); 24 | // Release the lock, equivalent to lock->locked = 0. 25 | // This code doesn't use a C assignment, since the C standard 26 | // implies that an assignment might be implemented with 27 | // multiple store instructions. 28 | // On RISC-V, sync_lock_release turns into an atomic swap: 29 | // s1 = &lock->locked 30 | // amoswap.w zero, zero, (s1) 31 | __sync_lock_release(&lock->locked); 32 | } 33 | -------------------------------------------------------------------------------- /src/lib/queue.yu: -------------------------------------------------------------------------------- 1 | public import arch.arch 2 | 3 | import lib.alloc 4 | 5 | public struct QueueElem { 6 | val: usize, 7 | prev: QueueElem var*, 8 | next: QueueElem var*, 9 | } 10 | 11 | public struct Queue { 12 | head: QueueElem var*, 13 | } 14 | 15 | public def newQueue(): Queue { 16 | let head = heap.alloc(sizeof QueueElem) as QueueElem var* 17 | (*head).prev = head 18 | (*head).next = head 19 | [Queue] {head} 20 | } 21 | 22 | // check if current queue is empty 23 | public def empty(this: Queue&): bool { 24 | (*this.head).next == this.head 25 | } 26 | 27 | // push element to back of queue 28 | public def push(this: Queue var&, val: usize) { 29 | let elem = heap.alloc(sizeof QueueElem) as QueueElem var* 30 | (*elem).val = val 31 | // push into queue 32 | let prev = (*this.head).prev 33 | (*prev).next = elem 34 | (*elem).prev = prev 35 | (*this.head).prev = elem 36 | (*elem).next = this.head 37 | } 38 | 39 | // pop element from front of queue 40 | public def pop(this: Queue var&): usize { 41 | // get result 42 | let elem = (*this.head).next, val = (*elem).val 43 | // pop from queue 44 | let prev = (*elem).prev, next = (*elem).next 45 | (*next).prev = prev 46 | (*prev).next = next 47 | heap.dealloc(elem as u8 var*) 48 | val 49 | } 50 | 51 | // destructor 52 | public def del(this: Queue var&) { 53 | while !this.empty() { 54 | this.pop() 55 | } 56 | heap.dealloc(this.head as u8 var*) 57 | } 58 | -------------------------------------------------------------------------------- /src/fs/dev/mem.yu: -------------------------------------------------------------------------------- 1 | public import fs.dev.device 2 | public import sync.semaphore 3 | 4 | import lib.c.string 5 | import lib.algo 6 | 7 | public struct MemDevice { 8 | dev: DeviceInterface, 9 | sema: Semaphore, 10 | buf: u8 var*, 11 | len: usize, 12 | } 13 | 14 | def getMemDev(this: DeviceInterface var*): MemDevice var& { 15 | *(this as MemDevice var*) 16 | } 17 | 18 | public def readMem(this: DeviceInterface var*, buf: u8 var*, len: usize, 19 | offset: usize): i32 { 20 | let dev: MemDevice var& = this.getMemDev() 21 | if offset > dev.len { return -1 } 22 | // read memory 23 | dev.sema.wait() 24 | let len = min(len as u32, (dev.len - offset) as u32) 25 | memcpy(buf, dev.buf + offset, len) 26 | dev.sema.signal() 27 | len as i32 28 | } 29 | 30 | public def writeMem(this: DeviceInterface var*, buf: u8*, len: usize, 31 | offset: usize): i32 { 32 | let dev: MemDevice var& = this.getMemDev() 33 | if offset > dev.len { return -1 } 34 | // write memory 35 | dev.sema.wait() 36 | let len = min(len as u32, (dev.len - offset) as u32) 37 | memcpy(dev.buf + offset, buf, len) 38 | dev.sema.signal() 39 | len as i32 40 | } 41 | 42 | public def syncMem(this: DeviceInterface var*): bool { 43 | true 44 | } 45 | 46 | public def newMemDevice(buf: u8 var*, len: usize): MemDevice { 47 | [MemDevice] { 48 | [DeviceInterface] {readMem, writeMem, syncMem}, 49 | newSemaphore(), 50 | buf, 51 | len, 52 | } 53 | } 54 | 55 | public def del(this: MemDevice var&) { 56 | this.sema.del() 57 | } 58 | -------------------------------------------------------------------------------- /src/entry.yu: -------------------------------------------------------------------------------- 1 | import arch.arch 2 | import arch.riscv.csr 3 | import lib.io 4 | import main 5 | 6 | // M-mode timer interrupt handler 7 | extern declare _handleTimer: () 8 | 9 | // interval of timer 10 | let TIMER_INTERVAL = 1000000 11 | 12 | // scratch area for M-mode timer interrupt 13 | public var mscratch: usize[5] = [usize[5]] {} 14 | 15 | // initialize timer interrupt 16 | def initTimer() { 17 | // ask CLINT for a timer interrupt 18 | let time = *(CLINT_MTIME as u64 volatile var*) 19 | (*(CLINT_MCMP as u64 volatile var*)) = time + TIMER_INTERVAL as u64 20 | // initialize scratch area 21 | mscratch[3] = CLINT_MCMP as usize 22 | mscratch[4] = TIMER_INTERVAL as usize 23 | setMscratch(mscratch as usize) 24 | // set M-mode handler 25 | setMtvec(_handleTimer as usize) 26 | // enable timer interrupt 27 | setMstatus(getMstatus() | MSTATUS_MIE) 28 | setMie(getMie() | MIE_MTIE) 29 | } 30 | 31 | // machine mode entry of GeeOS 32 | extern def entry() { 33 | // initialize IO 34 | initIO() 35 | io <<< "GeeOS is initializing...\n" 36 | // set previous mode to S-mode 37 | var ms = getMstatus() 38 | ms &= ~MSTATUS_MPP_MASK 39 | ms |= MSTATUS_MPP_S 40 | setMstatus(ms) 41 | // set mepc to S-mode entry 42 | setMepc(main as usize) 43 | // disable paging 44 | setSatp(0 as usize) 45 | // delegate all interrupts & exceptions to S-mode 46 | setMedeleg(0xffff as usize) 47 | setMideleg(0xffff as usize) 48 | // initialize timer 49 | initTimer() 50 | // enter S-mode 51 | io <<< "entering supervisor mode...\n" 52 | asm { "mret" } 53 | } 54 | -------------------------------------------------------------------------------- /usr/Makefile: -------------------------------------------------------------------------------- 1 | # directories 2 | LIB_DIR := $(USR_DIR)/lib 3 | BIN_DIR := $(USR_DIR)/bin 4 | BIN_OBJ_DIR := $(OBJ_DIR)/usr/bin 5 | BIN_TARGET_DIR := $(BUILD_DIR)/usr 6 | 7 | # sources & targets of library 8 | LIB_SRC := $(call rwildcard, $(LIB_DIR), *.yu) 9 | LIB_SRC += $(call rwildcard, $(LIB_DIR), *.c) 10 | LIB_SRC += $(call rwildcard, $(LIB_DIR), *.S) 11 | $(call make_obj, LIB, $(LIB_SRC)) 12 | LIB_TARGET := $(BUILD_DIR)/libgrt.a 13 | 14 | # sources & targets of user binaries 15 | BIN_SRC := $(call rwildcard, $(BIN_DIR), *.yu) 16 | $(call make_obj, BIN, $(BIN_SRC)) 17 | BIN_TARGET := $(patsubst $(BIN_OBJ_DIR)/%.yu.o, $(BIN_TARGET_DIR)/%, $(BIN_OBJ)) 18 | USER_IMG := $(BUILD_DIR)/user.img 19 | 20 | # compiler flags 21 | YUCFLAGS := -I $(USR_DIR) 22 | CFLAGS := -I$(USR_DIR) 23 | 24 | 25 | .PHONY: all clean libgrt user 26 | 27 | all: libgrt user 28 | 29 | clean: 30 | -rm $(LIB_TARGET) 31 | -rm -rf $(BIN_TARGET_DIR) 32 | -rm $(USER_IMG) 33 | 34 | libgrt: $(LIB_TARGET) 35 | 36 | user: $(USER_IMG) 37 | 38 | $(BIN_TARGET_DIR): 39 | mkdir $@ 40 | 41 | $(LIB_TARGET): $(LIB_OBJ) 42 | $(info making GeeOS user library...) 43 | $(AR) $@ $^ 44 | $(RANLIB) $@ 45 | 46 | $(BIN_TARGET_DIR)/%: $(BIN_OBJ_DIR)/%.yu.o $(LIB_TARGET) 47 | $(info making user binary "$(notdir $@)"...) 48 | $(LD) -L$(BUILD_DIR) -lgrt -o $@ $< 49 | $(OBJD) $@ > $@.dump 50 | $(if $(filter 0, $(DEBUG)), $(STRIP) $@ -o $@) 51 | 52 | $(USER_IMG): $(BIN_TARGET_DIR) $(BIN_TARGET) 53 | $(info making filesystem image...) 54 | $(BUILD_DIR)/mkfs $@ -c 256 1 2 -a $(BIN_TARGET) 55 | 56 | include $(TOP_DIR)/rules.mk 57 | -------------------------------------------------------------------------------- /src/fs/geefs/structs.yu: -------------------------------------------------------------------------------- 1 | // constants 2 | inline let MAGIC_NUM = 0x9eef5000 as u32 3 | inline let DIRECT_BLOCK_NUM = 12 as u32 4 | inline let BLOCK_OFS_SIZE = sizeof u32 as u32 5 | inline let FILE_NAME_MAX_LEN = 28 as u32 6 | 7 | // disk inode type 8 | public enum GfsINodeType: u32 { 9 | Unused = 0 as u32, 10 | File = 1 as u32, 11 | Dir = 2 as u32, 12 | } 13 | 14 | // super block header 15 | public struct GfsSbHeader { 16 | magic_num: u32, // magic number 17 | header_size: u32, // size of current header 18 | block_size: u32, // size of block 19 | free_map_num: u32, // number of free map blocks 20 | inode_blk_num: u32, // number of inode blocks 21 | } 22 | 23 | // free map block header 24 | public struct GfsFmbHeader { 25 | unused_num: u32, // number of unused blocks 26 | } 27 | 28 | // inode block header 29 | public struct GfsInbHeader { 30 | unused_num: u32, // number of unused inodes 31 | } 32 | 33 | // disk inode 34 | public struct GfsINode { 35 | itype: GfsINodeType, // type of inode 36 | size: u32, // size of file 37 | block_num: u32, // number of blocks 38 | direct: u32[DIRECT_BLOCK_NUM], // direct blocks 39 | indirect: u32, // indirect block id 40 | indirect2: u32, // 2nd indirect block id 41 | } 42 | 43 | // directory entry 44 | public struct GfsEntry { 45 | inode_id: u32, // inode id of file 46 | filename: u8[FILE_NAME_MAX_LEN], // file name, ends with '\0' 47 | } 48 | -------------------------------------------------------------------------------- /mkfs/structs.h: -------------------------------------------------------------------------------- 1 | #ifndef GEEOS_MKFS_STRUCTS_H_ 2 | #define GEEOS_MKFS_STRUCTS_H_ 3 | 4 | #include 5 | 6 | constexpr auto kMagicNum = 0x9eef5000; 7 | constexpr auto kDirectBlockNum = 12; 8 | constexpr auto kBlockOfsSize = sizeof(std::uint32_t); 9 | constexpr auto kFileNameMaxLen = 28; 10 | 11 | enum class INodeType : std::uint32_t { 12 | Unused = 0, 13 | File = 1, 14 | Dir = 2, 15 | }; 16 | 17 | struct SuperBlockHeader { 18 | std::uint32_t magic_num; // magic number 19 | std::uint32_t header_size; // size of current header 20 | std::uint32_t block_size; // size of block 21 | std::uint32_t free_map_num; // number of free map blocks 22 | std::uint32_t inode_blk_num; // number of inode blocks 23 | }; 24 | 25 | struct FreeMapBlockHeader { 26 | std::uint32_t unused_num; // number of unused blocks 27 | }; 28 | 29 | struct INodeBlockHeader { 30 | std::uint32_t unused_num; // number of unused inodes 31 | }; 32 | 33 | struct INode { 34 | INodeType type; // type of inode 35 | std::uint32_t size; // size of file 36 | std::uint32_t block_num; // number of blocks 37 | std::uint32_t direct[kDirectBlockNum]; // direct blocks 38 | std::uint32_t indirect; // indirect block id 39 | std::uint32_t indirect2; // 2nd indirect block id 40 | }; 41 | 42 | struct Entry { 43 | std::uint32_t inode_id; // inode id of file 44 | std::uint8_t filename[kFileNameMaxLen]; // file name, ends with '\0' 45 | }; 46 | 47 | #endif // GEEOS_MKFS_STRUCTS_H_ 48 | -------------------------------------------------------------------------------- /src/syscall/syscall.yu: -------------------------------------------------------------------------------- 1 | public import arch.arch 2 | public import define.context 3 | 4 | import syscall.fs 5 | import syscall.proc 6 | import lib.io 7 | 8 | // definitions of system call id 9 | let SYS_DUP3 = 24 as usize 10 | let SYS_CHDIR = 49 as usize 11 | let SYS_OPEN = 56 as usize 12 | let SYS_CLOSE = 57 as usize 13 | let SYS_LSEEK = 62 as usize 14 | let SYS_READ = 63 as usize 15 | let SYS_WRITE = 64 as usize 16 | let SYS_FSTAT = 80 as usize 17 | let SYS_SYNC = 81 as usize 18 | let SYS_EXIT = 93 as usize 19 | let SYS_YIELD = 124 as usize 20 | let SYS_EXECVE = 221 as usize 21 | 22 | 23 | // perform system call 24 | public def runSyscall(tf: TrapFrame var&): isize { 25 | when tf.x[17] { 26 | SYS_DUP3 { 27 | sysDup3(tf.x[10] as i32, tf.x[11] as i32, tf.x[12] as i32) 28 | } 29 | SYS_CHDIR { sysChdir(tf.x[10] as u8*) } 30 | SYS_OPEN { sysOpen(tf.x[10] as u8*, tf.x[11] as i32) } 31 | SYS_CLOSE { sysClose(tf.x[10] as i32) } 32 | SYS_LSEEK { 33 | sysLseek(tf.x[10] as i32, tf.x[11], tf.x[12] as i32) as isize 34 | } 35 | SYS_READ { 36 | sysRead(tf.x[10] as i32, tf.x[11] as u8 var*, tf.x[12] as usize) 37 | } 38 | SYS_WRITE { 39 | sysWrite(tf.x[10] as i32, tf.x[11] as u8*, tf.x[12] as usize) 40 | } 41 | SYS_FSTAT { sysFstat(tf.x[10] as i32, tf.x[11] as Stat var*) } 42 | SYS_SYNC { sysSync() } 43 | SYS_EXIT { sysExit(tf.x[10]) } 44 | SYS_YIELD { sysYield() } 45 | SYS_EXECVE { 46 | sysExecve(tf.x[10] as u8*, tf.x[11] as u8**, tf.x[12] as u8**) 47 | } 48 | else { 49 | io <<< "unknown user system call! id = " <<< tf.x[17] <<< '\n' 50 | -1 as isize 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # toolchain 2 | include toolchain.mk 3 | 4 | # helper functions 5 | export rwildcard = $$(foreach d, $$(wildcard $$(1:=/*)), $$(call rwildcard, $$d, $$2) $$(filter $$(subst *, %, $$2), $$d)) 6 | define make_obj 7 | $$(eval TEMP := $$(patsubst $(TOP_DIR)/%.yu, $(OBJ_DIR)/%.yu.o, $$(2))); 8 | $$(eval TEMP := $$(patsubst $(TOP_DIR)/%.c, $(OBJ_DIR)/%.c.o, $$(TEMP))); 9 | $$(eval TEMP := $$(patsubst $(TOP_DIR)/%.cpp, $(OBJ_DIR)/%.cpp.o, $$(TEMP))); 10 | $$(eval TEMP := $$(patsubst $(TOP_DIR)/%.S, $(OBJ_DIR)/%.S.o, $$(TEMP))); 11 | $$(eval $$(1)_OBJ := $$(TEMP)); 12 | endef 13 | export make_obj 14 | 15 | # directories 16 | export TOP_DIR := $(shell if [ "$$PWD" != "" ]; then echo $$PWD; else pwd; fi) 17 | export BUILD_DIR := $(TOP_DIR)/build 18 | export SRC_DIR := $(TOP_DIR)/src 19 | export USR_DIR := $(TOP_DIR)/usr 20 | export MKFS_DIR := $(TOP_DIR)/mkfs 21 | export UTILS_DIR := $(TOP_DIR)/utils 22 | export OBJ_DIR := $(BUILD_DIR)/obj 23 | 24 | # all sub-makes 25 | SUB_MAKE := $(SRC_DIR) $(USR_DIR) $(MKFS_DIR) 26 | 27 | 28 | .SILENT: 29 | .PHONY: all clean libgee boot kernel libgrt user mkfs $(SUB_MAKE) 30 | 31 | all: libgee boot kernel libgrt user mkfs 32 | 33 | clean: 34 | $(info cleaning...) 35 | -rm -rf $(OBJ_DIR) 36 | -$(MAKE) -C $(SRC_DIR) $@ 37 | -$(MAKE) -C $(USR_DIR) $@ 38 | -$(MAKE) -C $(MKFS_DIR) $@ 39 | 40 | libgee: $(BUILD_DIR) $(SRC_DIR) 41 | 42 | boot: $(BUILD_DIR) $(SRC_DIR) 43 | 44 | kernel: $(BUILD_DIR) $(SRC_DIR) 45 | 46 | libgrt: $(BUILD_DIR) $(USR_DIR) 47 | 48 | user: $(BUILD_DIR) $(USR_DIR) 49 | 50 | mkfs: $(BUILD_DIR) $(MKFS_DIR) 51 | 52 | $(SUB_MAKE): 53 | $(MAKE) -C $@ $(MAKECMDGOALS) 54 | 55 | $(SRC_DIR): $(USR_DIR) 56 | 57 | $(USR_DIR): $(MKFS_DIR) 58 | 59 | $(BUILD_DIR): 60 | mkdir $@ 61 | -------------------------------------------------------------------------------- /mkfs/device.h: -------------------------------------------------------------------------------- 1 | #ifndef GEEOS_MKFS_DEVICE_H_ 2 | #define GEEOS_MKFS_DEVICE_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class DeviceBase { 10 | public: 11 | virtual ~DeviceBase() = default; 12 | 13 | virtual std::int32_t Read(std::uint8_t *buf, std::size_t len, 14 | std::size_t offset) = 0; 15 | virtual std::int32_t Write(const std::uint8_t *buf, std::size_t len, 16 | std::size_t offset) = 0; 17 | virtual bool Sync() = 0; 18 | virtual bool Resize(std::size_t size) = 0; 19 | 20 | template 21 | std::int32_t Read(T &object, std::size_t offset) { 22 | auto buf = reinterpret_cast(&object); 23 | return Read(buf, sizeof(T), offset); 24 | } 25 | 26 | std::int32_t Read(std::vector &buffer, 27 | std::size_t offset) { 28 | return Read(buffer.data(), buffer.size(), offset); 29 | } 30 | 31 | template 32 | std::int32_t Write(const T &object, std::size_t offset) { 33 | auto buf = reinterpret_cast(&object); 34 | return Write(buf, sizeof(T), offset); 35 | } 36 | 37 | std::int32_t Write(const std::vector &buffer, 38 | std::size_t offset) { 39 | return Write(buffer.data(), buffer.size(), offset); 40 | } 41 | 42 | template 43 | bool ReadAssert(std::int32_t read_count, Args &&... args) { 44 | return Read(args...) == read_count; 45 | } 46 | 47 | template 48 | bool WriteAssert(std::int32_t write_count, Args &&... args) { 49 | return Write(args...) == write_count; 50 | } 51 | }; 52 | 53 | using Device = DeviceBase; 54 | 55 | #endif // GEEOS_MKFS_DEVICE_H_ 56 | -------------------------------------------------------------------------------- /src/mem/pm.yu: -------------------------------------------------------------------------------- 1 | import arch.arch 2 | import arch.riscv.consts 3 | import arch.riscv.addr 4 | import sync.spinlock 5 | import mem.consts 6 | import lib.except 7 | import lib.c.string 8 | 9 | // structure of all free physical pages 10 | struct FreePage { 11 | next: FreePage var*, 12 | } 13 | 14 | // physical memory structure 15 | struct PhyMemory { 16 | lock: Spinlock, 17 | free_list: FreePage var*, 18 | } 19 | var phy_mem: PhyMemory 20 | 21 | 22 | // free physical page 23 | public def freePhyMem(addr: u8 var*) { 24 | // check if address is not aligned, or out of range 25 | if addr as usize % PAGE_SIZE as usize != 0 as usize || 26 | addr < _geeos_end as u8 var* || addr >= HEAP_BASE as u8 var* { 27 | panic("freePhyMem") 28 | } 29 | // fill with junk to catch dangling refs 30 | memset(addr, 1, PAGE_SIZE as usize) 31 | // insert current page into 'free_list' 32 | let fp = addr as FreePage var* 33 | phy_mem.lock.acquire() 34 | (*fp).next = phy_mem.free_list 35 | phy_mem.free_list = fp 36 | phy_mem.lock.release() 37 | } 38 | 39 | // initialize physical page allocator 40 | public def initPhyMem() { 41 | phy_mem.lock = newSpinlock() 42 | // free all avaliable physical pages 43 | let pa = newPhysAddr(_geeos_end as usize) 44 | let pa_4k = pa.roundUp() 45 | var p = pa_4k.getAddr() as u8 var* 46 | while p + PAGE_SIZE <= HEAP_BASE as u8 var* { 47 | freePhyMem(p) 48 | p += PAGE_SIZE 49 | } 50 | } 51 | 52 | // allocate physical page 53 | public def allocPhyMem(): u8 var* { 54 | // pick a new free page from 'free_list' 55 | phy_mem.lock.acquire() 56 | let fp = phy_mem.free_list 57 | if fp != null as FreePage var* { 58 | phy_mem.free_list = (*fp).next 59 | } 60 | phy_mem.lock.release() 61 | // fill with junk 62 | if fp != null as FreePage var* { 63 | memset(fp as u8 var*, 5, PAGE_SIZE as usize) 64 | } 65 | fp as u8 var* 66 | } 67 | -------------------------------------------------------------------------------- /src/proc/proc.yu: -------------------------------------------------------------------------------- 1 | public import arch.arch 2 | public import proc.consts 3 | public import proc.structs 4 | 5 | import fs.fs 6 | import lib.alloc 7 | import proc.processor 8 | import lib.except 9 | import arch.riscv.csr 10 | 11 | // current processor 12 | var cpu: Processor var* 13 | 14 | 15 | // execute new process 16 | public def execute(path: u8*, host_tid: Tid): bool { 17 | let path = newStrView(path) 18 | // find file in root inode 19 | let inode = root_inode.lookup(path) 20 | if inode != null as INode var* { 21 | var ret = false 22 | inode.open() 23 | // read content of inode 24 | let meta = inode.getMetadata() 25 | let size = meta.size, buf = heap.alloc(size) 26 | if inode.read(buf, size, 0 as usize) as usize == size { 27 | // create user thread 28 | let thread = newUserThread(buf, host_tid) 29 | if thread != null as Thread var* { 30 | cpu.addThread(thread) 31 | ret = true 32 | } 33 | } 34 | // free buffer & inode 35 | heap.dealloc(buf) 36 | inode.close() 37 | ret 38 | } 39 | else { 40 | false 41 | } 42 | } 43 | 44 | // initialize process 45 | public def initProcess() { 46 | cpu = newProcessor(TIME_SLICE) 47 | let ret = execute("shell", TID_NONE) 48 | assert(ret, "initProcess") 49 | } 50 | 51 | // turn interrupt on and run scheduler on current CPU 52 | public def runCpu() { 53 | setIntrOn() 54 | cpu.run() 55 | } 56 | 57 | public def yield() { 58 | cpu.yield() 59 | } 60 | 61 | public def tick() { 62 | cpu.tick() 63 | } 64 | 65 | public def exit(code: usize) { 66 | cpu.exit(code) 67 | } 68 | 69 | public def sleep() { 70 | cpu.sleep() 71 | } 72 | 73 | public def wakeUp(tid: Tid) { 74 | cpu.wakeUp(tid) 75 | } 76 | 77 | public def getCurrentTid(): Tid { 78 | cpu.getCurrentTid() 79 | } 80 | 81 | public def getCurrentThread(): Thread var* { 82 | cpu.getCurrentThread() 83 | } 84 | -------------------------------------------------------------------------------- /toolchain.mk: -------------------------------------------------------------------------------- 1 | # external parameters 2 | DEBUG = 1 3 | OPT_LEVEL = 2 4 | 5 | # judge if is debug mode 6 | ifeq ($(DEBUG), 0) 7 | C_DEBUG_ARG = -DNDEBUG 8 | C_OPT_ARG = -O$(OPT_LEVEL) 9 | YU_OPT_ARG = -O $(OPT_LEVEL) 10 | else 11 | C_DEBUG_ARG = -g 12 | C_OPT_ARG = -O0 13 | YU_OPT_ARG = -O 0 14 | endif 15 | 16 | # cross compile toolchain prefix 17 | LLVM_BIN := /usr/local/opt/llvm/bin 18 | YU_BIN := /Users/maxxing/Programming/MyRepo/YuLang/build 19 | 20 | # cross Yu compiler 21 | YUFLAGS := -Werror $(YU_OPT_ARG) 22 | YUFLAGS += -tt riscv32-unknown-elf -tc generic-rv32 -tf +m,+a 23 | export YUC := $(YU_BIN)/yuc $(YUFLAGS) 24 | 25 | # cross C compiler 26 | CFLAGS := -Wall -Werror -c -static $(C_DEBUG_ARG) $(C_OPT_ARG) 27 | CFLAGS += -fno-builtin -fno-pic 28 | CFLAGS += -target riscv32-unknown-elf -march=rv32ima -mabi=ilp32 29 | export CC := $(LLVM_BIN)/clang $(CFLAGS) 30 | 31 | # native C++ compiler 32 | CXXFLAGS := -Wall -Werror -c $(C_DEBUG_ARG) $(C_OPT_ARG) 33 | CXXFLAGS += -std=c++17 34 | export CXX := clang++ $(CXXFLAGS) 35 | 36 | # cross LLVM compiler 37 | LLCFLAGS := $(C_OPT_ARG) -filetype=obj 38 | LLCFLAGS += -march=riscv32 -mcpu=generic-rv32 -mattr=+m,+a 39 | export LLC := $(LLVM_BIN)/llc $(LLCFLAGS) 40 | 41 | # cross linker 42 | LDFLAGS := -nostdlib -melf32lriscv 43 | export LD := $(LLVM_BIN)/ld.lld $(LDFLAGS) 44 | 45 | # native linker 46 | NLDFLAGS := 47 | export NLD := clang++ $(NLDFLAGS) 48 | 49 | # objcopy 50 | OBJCFLAGS := -O binary 51 | export OBJC := $(LLVM_BIN)/llvm-objcopy $(OBJCFLAGS) 52 | 53 | # objdump 54 | OBJDFLAGS := -D 55 | export OBJD := objdump $(OBJDFLAGS) 56 | 57 | # strip 58 | STRIPFLAGS := --strip-unneeded --strip-sections 59 | export STRIP := $(LLVM_BIN)/llvm-strip $(STRIPFLAGS) 60 | 61 | # archiver 62 | ARFLAGS := ru 63 | export AR := $(LLVM_BIN)/llvm-ar $(ARFLAGS) 64 | 65 | # ranlib 66 | RANLIBFLAGS := 67 | export RANLIB := $(LLVM_BIN)/llvm-ranlib $(RANLIBFLAGS) 68 | -------------------------------------------------------------------------------- /src/mem/mem.yu: -------------------------------------------------------------------------------- 1 | import arch.arch 2 | import arch.riscv.pagetable 3 | import arch.riscv.addr 4 | import arch.riscv.consts 5 | import arch.riscv.csr 6 | import mem.mset 7 | import mem.consts 8 | import mem.pm 9 | import mem.heap 10 | import lib.io 11 | 12 | // initialize temporary root page table for kernel 13 | // returns temporary allocated root page table 14 | def initTempTable(): PageTable var* { 15 | let root_table = allocPhyMem() as PageTable var* 16 | let flags = PTE_FLAG_V | PTE_FLAG_R | PTE_FLAG_W 17 | let offset = (KERNEL_VM_BASE - KERNEL_BASE) as usize 18 | root_table.zero() 19 | // set identity mapping vm(KERNEL_BASE) -> pm(KERNEL_BASE) 20 | root_table.mapLinearRange(KERNEL_BASE as usize, PHY_STOP as usize, 21 | offset, flags | PTE_FLAG_X) 22 | // set identity mapping vm(UART_ADDR) -> pm(UART_ADDR) 23 | root_table.mapLinearRange(UART_ADDR as usize, UART_END as usize, 24 | offset, flags) 25 | // set recursive mapping 26 | let root_frame = newFrame(newPhysAddr(root_table as usize)) 27 | root_table.setRecursive(RECURSIVE_INDEX, root_frame) 28 | // activate root page table 29 | setSatp(root_frame.getPpn() | SATP_SV32) 30 | runSfence() 31 | root_table 32 | } 33 | 34 | // remapping kernel address space 35 | def remapKernel() { 36 | // NOTE: do not release this page table 37 | var mset = newMemorySet() 38 | mset.activate() 39 | } 40 | 41 | public def initMem() { 42 | // enable user memory access in S-mode 43 | setSstatus(getSstatus() | SSTATUS_SUM) 44 | // initialize phyical memory 45 | initPhyMem() 46 | // initialize heap memory 47 | io <<< "initializing heap allocator...\n" 48 | initHeapMem() 49 | // initialize temporary page table 50 | io <<< "setting up temp page table...\n" 51 | let temp_table = initTempTable() 52 | // remap kernel 53 | io <<< "remapping kernel...\n" 54 | remapKernel() 55 | // free temporary page table 56 | freePhyMem(temp_table as u8 var*) 57 | } 58 | -------------------------------------------------------------------------------- /src/trap/trap.yu: -------------------------------------------------------------------------------- 1 | public import define.context 2 | 3 | import arch.arch 4 | import arch.riscv.csr 5 | import lib.io 6 | import fs.devfs.fs 7 | import syscall.syscall 8 | import lib.except 9 | import proc.proc 10 | 11 | // trap handler in 'traphand.S' 12 | extern declare _handleTrap: () 13 | 14 | // handle timer interrupt 15 | def handleTimer() { 16 | // tell processor a tick passed 17 | tick() 18 | // acknowledge soft interrupt 19 | setSip(getSip() & ~SIE_SSIE) 20 | } 21 | 22 | // handle external interrupt 23 | def handleExternal() { 24 | // acknowledge IRQ 25 | ackIntr() 26 | // read characters from UART 27 | while true { 28 | let c = getChar() 29 | if c < 0 { break } 30 | pushChar(c as u8) 31 | } 32 | } 33 | 34 | // handle system call 35 | def handleSyscall(tf: TrapFrame var&) { 36 | tf.incSepc() 37 | tf.x[10] = runSyscall(tf) as usize 38 | } 39 | 40 | // handle page fault 41 | def handlePageFault(tf: TrapFrame var&) { 42 | // print debug info 43 | io <<< "scause = " <<< tf.scause <<< '\n' 44 | io <<< "stval = 0x" <<$ tf.stval <<< '\n' 45 | io <<< "sepc = 0x" <<$ tf.sepc <<< '\n' 46 | // exit or panic 47 | if tf.isUser() { 48 | io <<< "ERROR: user thread memory access violation\n" 49 | exit(-1 as usize) 50 | } 51 | else { 52 | panic("page fault!") 53 | } 54 | } 55 | 56 | // initialize trap handling 57 | public def initTrap() { 58 | initIntr() 59 | setSscratch(0 as usize) 60 | setStvec(_handleTrap as usize) 61 | } 62 | 63 | // trap handler 64 | extern def handleTrap(tf: TrapFrame var&) { 65 | when tf.scause { 66 | TRAP_S_SOFT_INT { 67 | handleTimer() 68 | } 69 | TRAP_S_EXT_INT { 70 | handleExternal() 71 | } 72 | TRAP_U_SYSCALL { 73 | handleSyscall(tf) 74 | } 75 | TRAP_INST_FAULT, TRAP_LOAD_FAULT, TRAP_STORE_FAULT { 76 | handlePageFault(tf) 77 | } 78 | else { 79 | io <<< "sstatus = 0x" <<$ tf.sstatus <<< '\n' 80 | io <<< "sepc = 0x" <<$ tf.sepc <<< '\n' 81 | io <<< "scause = " <<< tf.scause <<< '\n' 82 | io <<< "stval = 0x" <<$ tf.stval <<< '\n' 83 | panic("uexpected trap!") 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/arch/riscv/csr.S: -------------------------------------------------------------------------------- 1 | .section .text 2 | 3 | .globl getMstatus 4 | getMstatus: 5 | csrr a0, mstatus 6 | ret 7 | 8 | .globl setMstatus 9 | setMstatus: 10 | csrw mstatus, a0 11 | ret 12 | 13 | .globl setMepc 14 | setMepc: 15 | csrw mepc, a0 16 | ret 17 | 18 | .globl getSstatus 19 | getSstatus: 20 | csrr a0, sstatus 21 | ret 22 | 23 | .globl setSstatus 24 | setSstatus: 25 | csrw sstatus, a0 26 | ret 27 | 28 | .globl getSip 29 | getSip: 30 | csrr a0, sip 31 | ret 32 | 33 | .globl setSip 34 | setSip: 35 | csrw sip, a0 36 | ret 37 | 38 | .globl getSie 39 | getSie: 40 | csrr a0, sie 41 | ret 42 | 43 | .globl setSie 44 | setSie: 45 | csrw sie, a0 46 | ret 47 | 48 | .globl getMie 49 | getMie: 50 | csrr a0, mie 51 | ret 52 | 53 | .globl setMie 54 | setMie: 55 | csrw mie, a0 56 | ret 57 | 58 | .globl setSepc 59 | setSepc: 60 | csrw sepc, a0 61 | ret 62 | 63 | .globl getSepc 64 | getSepc: 65 | csrr a0, sepc 66 | ret 67 | 68 | .globl getMedeleg 69 | getMedeleg: 70 | csrr a0, medeleg 71 | ret 72 | 73 | .globl setMedeleg 74 | setMedeleg: 75 | csrw medeleg, a0 76 | ret 77 | 78 | .globl getMideleg 79 | getMideleg: 80 | csrr a0, mideleg 81 | ret 82 | 83 | .globl setMideleg 84 | setMideleg: 85 | csrw mideleg, a0 86 | ret 87 | 88 | .globl setStvec 89 | setStvec: 90 | csrw stvec, a0 91 | ret 92 | 93 | .globl getStvec 94 | getStvec: 95 | csrr a0, stvec 96 | ret 97 | 98 | .globl setMtvec 99 | setMtvec: 100 | csrw mtvec, a0 101 | ret 102 | 103 | .globl setSatp 104 | setSatp: 105 | csrw satp, a0 106 | ret 107 | 108 | .globl getSatp 109 | getSatp: 110 | csrr a0, satp 111 | ret 112 | 113 | .globl setSscratch 114 | setSscratch: 115 | csrw sscratch, a0 116 | ret 117 | 118 | .globl setMscratch 119 | setMscratch: 120 | csrw mscratch, a0 121 | ret 122 | 123 | .globl getScause 124 | getScause: 125 | csrr a0, scause 126 | ret 127 | 128 | .globl getStval 129 | getStval: 130 | csrr a0, stval 131 | ret 132 | 133 | .globl runSfence 134 | runSfence: 135 | sfence.vma a0, a1 136 | ret 137 | -------------------------------------------------------------------------------- /usr/lib/sys/syscall.yu: -------------------------------------------------------------------------------- 1 | public import lib.sys.structs 2 | 3 | // id of system call 4 | public enum SyscallId { 5 | Dup3 = 24, 6 | Chdir = 49, 7 | Open = 56, 8 | Close = 57, 9 | Lseek = 62, 10 | Read = 63, 11 | Write = 64, 12 | Fstat = 80, 13 | Sync = 81, 14 | Exit = 93, 15 | Yield = 124, 16 | Execve = 221, 17 | } 18 | 19 | // defined in 'syscall.S' 20 | extern declare syscall: (i32, i32, i32, i32, i32, i32, i32, SyscallId): i32 21 | 22 | // duplicate file descriptor 23 | inline def dup3(oldfd: i32, newfd: i32, flags: i32): i32 { 24 | syscall(oldfd, newfd, flags, 0, 0, 0, 0, SyscallId.Dup3) 25 | } 26 | 27 | // change cwd 28 | inline def chdir(path: u8*): i32 { 29 | syscall(path as i32, 0, 0, 0, 0, 0, 0, SyscallId.Chdir) 30 | } 31 | 32 | // open file 33 | inline def open(path: u8*, flags: i32): i32 { 34 | syscall(path as i32, flags, 0, 0, 0, 0, 0, SyscallId.Open) 35 | } 36 | 37 | // close file 38 | inline def close(fd: i32): i32 { 39 | syscall(fd, 0, 0, 0, 0, 0, 0, SyscallId.Close) 40 | } 41 | 42 | // reposition read/write file offset 43 | inline def lseek(fd: i32, offset: usize, whence: i32): usize { 44 | syscall(fd, offset as i32, whence, 0, 0, 0, 0, SyscallId.Lseek) as usize 45 | } 46 | 47 | // read bytes from file to buffer 48 | inline def read(fd: i32, buf: u8 var*, len: usize): i32 { 49 | syscall(fd, buf as i32, len as i32, 0, 0, 0, 0, SyscallId.Read) 50 | } 51 | 52 | // write bytes from buffer to file 53 | inline def write(fd: i32, buf: u8*, len: usize): i32 { 54 | syscall(fd, buf as i32, len as i32, 0, 0, 0, 0, SyscallId.Write) 55 | } 56 | 57 | // get file status 58 | inline def fstat(fd: i32, buf: Stat var*): i32 { 59 | syscall(fd, buf as i32, 0, 0, 0, 0, 0, SyscallId.Fstat) 60 | } 61 | 62 | // commit filesystem caches to disk 63 | inline def sync() { 64 | syscall(0, 0, 0, 0, 0, 0, 0, SyscallId.Sync) 65 | } 66 | 67 | // exit current thread 68 | inline def exit(code: usize) { 69 | syscall(code as i32, 0, 0, 0, 0, 0, 0, SyscallId.Exit) 70 | while true {} 71 | } 72 | 73 | // yield and reschedule 74 | inline def yield(): i32 { 75 | syscall(0, 0, 0, 0, 0, 0, 0, SyscallId.Yield) 76 | } 77 | 78 | // execute program 79 | inline def execve(path: u8*, argv: u8**, envp: u8**): i32 { 80 | syscall(path as i32, argv as i32, envp as i32, 81 | 0, 0, 0, 0, SyscallId.Execve) 82 | } 83 | -------------------------------------------------------------------------------- /usr/lib/stack.yu: -------------------------------------------------------------------------------- 1 | import lib.alloc 2 | 3 | // length of stack frame 4 | inline let STACK_FRAME_LEN = 32 5 | 6 | public struct StackFrame { 7 | data: u8 var*[STACK_FRAME_LEN], 8 | next: StackFrame var*, 9 | } 10 | 11 | public struct Stack { 12 | frames: StackFrame var*, 13 | sp: i32, 14 | } 15 | 16 | 17 | /* 18 | * stack frame related stuffs 19 | */ 20 | def newStackFrame(next: StackFrame var*): StackFrame var* { 21 | let frame = alloc.alloc(sizeof StackFrame) as StackFrame var* 22 | (*frame).next = next 23 | frame 24 | } 25 | 26 | def empty(this: StackFrame var*): bool { 27 | this == null as StackFrame var* 28 | } 29 | 30 | def push(this: StackFrame var*): StackFrame var* { 31 | let frame = newStackFrame((*this).next) 32 | (*this).next = frame 33 | frame 34 | } 35 | 36 | def pop(this: StackFrame var*): StackFrame var* { 37 | let frame = (*this).next 38 | alloc.dealloc(this as u8 var*) 39 | frame 40 | } 41 | 42 | def at(this: StackFrame var*, index: i32): u8 var* var& { 43 | (*this).data[index] 44 | } 45 | 46 | def del(this: StackFrame var*) { 47 | var cur = this 48 | while !cur.empty() { 49 | cur = cur.pop() 50 | } 51 | } 52 | 53 | 54 | /* 55 | * stack related stuffs 56 | */ 57 | public def newStack(): Stack { 58 | [Stack] {newStackFrame(null as StackFrame var*), 0} 59 | } 60 | 61 | public def del(this: Stack var&) { 62 | this.frames.del() 63 | } 64 | 65 | public def empty(this: Stack&): bool { 66 | this.sp == 0 && (*this.frames).next.empty() 67 | } 68 | 69 | public def top(this: Stack var&): u8 var* var& { 70 | if this.sp == 0 { 71 | (*this.frames).next.at(STACK_FRAME_LEN - 1) 72 | } 73 | else { 74 | this.frames.at(this.sp - 1) 75 | } 76 | } 77 | 78 | public def push(this: Stack var&, data: u8 var*) { 79 | this.frames.at(this.sp) = data 80 | this.sp += 1 81 | if this.sp == STACK_FRAME_LEN { 82 | this.frames = this.frames.push() 83 | this.sp == 0 84 | } 85 | } 86 | 87 | public def pop(this: Stack var&): u8 var* { 88 | if this.sp == 0 { 89 | this.frames = this.frames.pop() 90 | this.sp = STACK_FRAME_LEN 91 | } 92 | this.sp -= 1 93 | let top = this.frames.at(this.sp) 94 | top 95 | } 96 | 97 | public def clear(this: Stack var&) { 98 | (*this.frames).next.del() 99 | (*this.frames).next = null as StackFrame var* 100 | this.sp = 0 101 | } 102 | -------------------------------------------------------------------------------- /src/mem/area.yu: -------------------------------------------------------------------------------- 1 | public import arch.arch 2 | public import mem.handler 3 | public import mem.attr 4 | public import mem.paging 5 | 6 | import lib.alloc 7 | 8 | 9 | // representation of a memory area 10 | public struct MemoryArea { 11 | start: usize, 12 | end: usize, 13 | handler: MemoryHandler var*, 14 | attr: MemoryAttr, 15 | } 16 | 17 | public def newMemArea(start: usize, end: usize, 18 | handler: MemoryHandler var*, 19 | attr: MemoryAttr): MemoryArea { 20 | handler.incCounter() 21 | [MemoryArea] {start, end, handler, attr} 22 | } 23 | 24 | public def del(this: MemoryArea var&) { 25 | this.handler.decCounter() 26 | } 27 | 28 | // map memory area to page table 29 | public def map(this: MemoryArea&, page_table: ActivePageTable var&) { 30 | for page in newPageRange(this.start, this.end) { 31 | this.handler.map(page_table, page, this.attr) 32 | } 33 | } 34 | 35 | // unmap memory area in page table 36 | public def unmap(this: MemoryArea&, page_table: ActivePageTable var&) { 37 | for page in newPageRange(this.start, this.end) { 38 | this.handler.unmap(page_table, page) 39 | } 40 | } 41 | 42 | // check if overlaps with specific address range 43 | public def isOverlap(this: MemoryArea&, start: usize, end: usize): bool { 44 | let p1 = this.start / PAGE_SIZE as usize 45 | let p2 = (this.end - 1 as usize) / (PAGE_SIZE + 1) as usize 46 | let p3 = start / PAGE_SIZE as usize 47 | let p4 = (end - 1 as usize) / (PAGE_SIZE + 1) as usize 48 | !(p1 >= p4 || p2 <= p3) 49 | } 50 | 51 | 52 | // linked list of memory area 53 | public struct MemAreaList { 54 | area: MemoryArea, 55 | next: MemAreaList var*, 56 | } 57 | 58 | public def newMemAreaList(): MemAreaList { 59 | [MemAreaList] {} 60 | } 61 | 62 | public def del(this: MemAreaList var&) { 63 | var cur = this.next 64 | while cur != null as MemAreaList var* { 65 | (*cur).area.del() 66 | let next = (*cur).next 67 | heap.dealloc(cur as u8 var*) 68 | cur = next 69 | } 70 | } 71 | 72 | public def push(this: MemAreaList var&, area: MemoryArea) { 73 | let node = heap.alloc(sizeof MemAreaList) as MemAreaList var* 74 | (*node).area = area 75 | (*node).next = this.next 76 | this.next = node 77 | } 78 | 79 | public def unmap(this: MemAreaList var&, pt: ActivePageTable var&) { 80 | var cur = this.next 81 | while cur != null as MemAreaList var* { 82 | (*cur).area.unmap(pt) 83 | cur = (*cur).next 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/boot/entry.yu: -------------------------------------------------------------------------------- 1 | import arch.arch 2 | import lib.io 3 | import lib.except 4 | import lib.elf 5 | import lib.c.string 6 | import boot.uart 7 | 8 | // boot from specific address 9 | extern declare jumpToAddr: (u8*) 10 | def boot(addr: u8*) { 11 | io <<< "booting from 0x" <<$ addr as u32 <<< "...\n\n" 12 | jumpToAddr(addr) 13 | } 14 | 15 | // load ELF file from specific address to memory 16 | def loadElf(addr: u8*): u8* { 17 | // read & check ELF file 18 | let elf = newElfFile(addr) 19 | let ehdr = elf.getEhdr() 20 | if !ehdr.isValid() { 21 | panic("invalid ELF file") 22 | } 23 | // copy ELF to RAM 24 | io <<< "loading ELF...\n" 25 | for phdr in elf.getPhdrIter() { 26 | let dst = phdr.getPaddr() as u8 var* 27 | let src = elf.getPhdrData(phdr) 28 | // copy data 29 | memcpy(dst, src, phdr.getFileSize() as usize) 30 | // fill zeros to the end of the data 31 | let rem_size = phdr.getMemSize() - phdr.getFileSize() 32 | memset(dst + phdr.getFileSize(), 0, rem_size as usize) 33 | } 34 | // get address of entry 35 | let phdr = elf.getPhdr() 36 | let off = phdr.getVaddr() - phdr.getPaddr() 37 | (ehdr.getEntry() - off) as u8* 38 | } 39 | 40 | // read word from address 41 | def readWord(addr: i32): i32 { 42 | *(addr as u32 volatile var*) as i32 43 | } 44 | 45 | // write word to address 46 | def writeWord(addr: i32, word: i32) { 47 | *(addr as u32 volatile var*) = word as u32 48 | } 49 | 50 | extern def entry() { 51 | initIO() 52 | writeWord(LED_ADDR, 0xffff) 53 | io <<< "GeeOS bootloader v0.0.1\n" 54 | // determine boot mode 55 | io <<< "boot mode: " 56 | let addr = if readWord(SWITCH_ADDR) == 0 { 57 | // boot from flash 58 | writeWord(NUM_ADDR, 1) 59 | io <<< "flash\n" 60 | FLASH_ADDR as u8* 61 | } 62 | else { 63 | // boot from UART 64 | writeWord(NUM_ADDR, 2) 65 | io <<< "UART\n" 66 | receiveFromUart() 67 | } 68 | // load ELF data to memory 69 | let entry = loadElf(addr) 70 | // execute ELF data 71 | writeWord(NUM_ADDR, 0) 72 | io <<< "exiting bootloader...\n" 73 | boot(entry) 74 | } 75 | 76 | extern def handleTrap(mepc: u32, mcause: u32, mtval: u32) { 77 | // display messsage 78 | writeWord(LED_ADDR, ~mcause as i32) 79 | writeWord(NUM_ADDR, mepc as i32) 80 | io <<< " mepc: 0x" <<$ mepc <<< '\n' 81 | io <<< " mcause: 0x" <<$ mcause <<< '\n' 82 | io <<< " mtval: 0x" <<$ mtval <<< '\n' 83 | panic("unexpected trap occurred while booting!") 84 | } 85 | -------------------------------------------------------------------------------- /src/arch/target/virt.yu: -------------------------------------------------------------------------------- 1 | // arch definitions of QEMU virt 2 | 3 | // constant address 4 | inline let FLASH_ADDR = 0x20000000 5 | inline let MEM_ADDR = 0x80000000 6 | inline let MEM_SIZE_MIB = 128 7 | inline let SWITCH_ADDR = 0x00000000 8 | inline let LED_ADDR = 0x00000000 9 | inline let NUM_ADDR = 0x00000000 10 | inline let UART_ADDR = 0x10000000 11 | inline let UART_END = 0x10000100 12 | inline let PLIC_ADDR = 0x0c000000 13 | inline let PLIC_END = 0x0c400000 14 | inline let CLINT_MTIME = 0x0200bff8 15 | inline let CLINT_MCMP = 0x02004000 16 | 17 | // UART related definitions 18 | inline let UART_DEV = UART_ADDR as u8 volatile var* 19 | inline let UART_CLOCK_FREQ = 1843200 as u32 20 | inline let UART_BAUD_RATE = 115200 as u32 21 | inline let UART_RBR = 0x00 22 | inline let UART_THR = 0x00 23 | inline let UART_DLL = 0x00 24 | inline let UART_DLM = 0x01 25 | inline let UART_IER = 0x01 26 | inline let UART_FCR = 0x02 27 | inline let UART_LCR = 0x03 28 | inline let UART_LSR = 0x05 29 | inline let UART_LCR_DLAB = 0x80 as u8 30 | inline let UART_LCR_8BIT = 0x03 as u8 31 | inline let UART_LCR_PODD = 0x08 as u8 32 | inline let UART_LSR_DA = 0x01 as u8 33 | inline let UART_LSR_RI = 0x40 as u8 34 | 35 | // initialize UART 36 | inline def initIO() { 37 | let divisor = UART_CLOCK_FREQ / ((16 as u32) * UART_BAUD_RATE) 38 | UART_DEV[UART_LCR] = UART_LCR_DLAB 39 | UART_DEV[UART_DLL] = divisor as u8 40 | UART_DEV[UART_DLM] = (divisor >> 8 as u32) as u8 41 | UART_DEV[UART_LCR] = UART_LCR_PODD | UART_LCR_8BIT 42 | UART_DEV[UART_FCR] = 0x07 as u8 43 | UART_DEV[UART_IER] = 0x01 as u8 44 | } 45 | 46 | // initialize PLIC 47 | inline def initIntr() { 48 | let UART_IRQ = 10 49 | let PLIC_UART_IRQ = (PLIC_ADDR + UART_IRQ * 4) as u32 volatile var* 50 | let S_MODE_INT_EN = (PLIC_ADDR + 0x2080) as u32 volatile var* 51 | let S_MODE_PRIORITY = (PLIC_ADDR + 0x201000) as u32 volatile var* 52 | (*PLIC_UART_IRQ) = 1 as u32 53 | (*S_MODE_INT_EN) = (1 << UART_IRQ) as u32 54 | (*S_MODE_PRIORITY) = 0 as u32 55 | } 56 | 57 | // acknowledge all IRQs from PLIC 58 | inline def ackIntr() {} 59 | 60 | // put character 61 | inline def putChar(c: u8) { 62 | while (UART_DEV[UART_LSR] & UART_LSR_RI) == 0 as u8 {} 63 | UART_DEV[UART_THR] = c 64 | } 65 | 66 | // get character 67 | inline def getChar(): i32 { 68 | if (UART_DEV[UART_LSR] & UART_LSR_DA) != 0 as u8 { 69 | UART_DEV[UART_RBR] as i32 70 | } 71 | else { 72 | -1 73 | } 74 | } 75 | 76 | // halt 77 | inline def halt(code: i32) { 78 | while true {} 79 | } 80 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | # directories 2 | LIB_DIR := $(SRC_DIR)/lib 3 | ARCH_DIR := $(SRC_DIR)/arch 4 | BOOT_DIR := $(SRC_DIR)/boot 5 | 6 | # sources & targets of library 7 | LIB_SRC := $(call rwildcard, $(LIB_DIR), *.yu) 8 | LIB_SRC += $(call rwildcard, $(ARCH_DIR)/riscv, *.yu) 9 | LIB_SRC += $(call rwildcard, $(ARCH_DIR)/riscv, *.S) 10 | $(call make_obj, LIB, $(LIB_SRC)) 11 | LIB_TARGET := $(BUILD_DIR)/libgee.a 12 | 13 | # sources & targets of bootloader 14 | BOOT_SRC := $(call rwildcard, $(BOOT_DIR), *.yu) 15 | BOOT_SRC += $(call rwildcard, $(BOOT_DIR), *.S) 16 | $(call make_obj, BOOT, $(BOOT_SRC)) 17 | BOOT_LDS := $(BOOT_DIR)/linker.ld 18 | BOOT_TARGET := $(BUILD_DIR)/boot.bin 19 | 20 | # sources & targets of kernel 21 | KERNEL_SRC := $(call rwildcard, $(SRC_DIR), *.yu) 22 | KERNEL_SRC += $(call rwildcard, $(SRC_DIR), *.c) 23 | KERNEL_SRC += $(call rwildcard, $(SRC_DIR), *.S) 24 | KERNEL_SRC := $(filter-out $(LIB_SRC) $(BOOT_SRC), $(KERNEL_SRC)) 25 | KERNEL_SRC := $(filter-out $(call rwildcard, $(ARCH_DIR), *.*), $(KERNEL_SRC)) 26 | KERNEL_SRC := $(filter-out $(call rwildcard, $(SRC_DIR)/fs/ucore, *.*), $(KERNEL_SRC)) 27 | $(call make_obj, KERNEL, $(KERNEL_SRC)) 28 | KERNEL_INIT := $(SRC_DIR)/init.S 29 | $(call make_obj, KERNEL_INIT, $(KERNEL_INIT)) 30 | KERNEL_LDS := $(SRC_DIR)/linker.ld 31 | USER_IMG := $(BUILD_DIR)/user.img 32 | KERNEL_TARGET := $(BUILD_DIR)/geeos.elf 33 | 34 | # compiler flags 35 | YUCFLAGS := -I $(SRC_DIR) 36 | CFLAGS := -I$(SRC_DIR) 37 | ASFLAGS := -I$(BUILD_DIR) 38 | 39 | 40 | .PHONY: all clean libgee boot kernel 41 | 42 | all: libgee boot kernel 43 | 44 | clean: 45 | -rm $(LIB_TARGET) 46 | -rm $(BOOT_TARGET) 47 | -rm $(BOOT_TARGET).* 48 | -rm $(KERNEL_TARGET) 49 | -rm $(KERNEL_TARGET).* 50 | 51 | libgee: $(LIB_TARGET) 52 | 53 | boot: $(BOOT_TARGET) 54 | 55 | kernel: $(KERNEL_TARGET) 56 | 57 | $(LIB_TARGET): $(LIB_OBJ) 58 | $(info making GeeOS kernel library...) 59 | $(AR) $@ $^ 60 | $(RANLIB) $@ 61 | 62 | $(BOOT_TARGET): $(BOOT_TARGET).elf 63 | $(info making GeeOS bootloader...) 64 | # create bin file 65 | $(OBJC) -j .text -j .data $^ $@ 66 | # create dump 67 | $(OBJD) $^ > $@.dump 68 | # create coe file 69 | $(UTILS_DIR)/bin2coe.py $@ 70 | 71 | $(BOOT_TARGET).elf: $(BOOT_OBJ) $(BOOT_LDS) $(LIB_TARGET) 72 | $(LD) -T$(BOOT_LDS) -L$(BUILD_DIR) -lgee -o $@ $(BOOT_OBJ) 73 | 74 | $(KERNEL_TARGET): $(KERNEL_OBJ) $(KERNEL_LDS) $(LIB_TARGET) 75 | $(info making GeeOS kernel...) 76 | $(LD) -T$(KERNEL_LDS) -L$(BUILD_DIR) -lgee -o $@ $(KERNEL_OBJ) 77 | $(OBJD) $@ > $@.dump 78 | $(if $(filter 0, $(DEBUG)), $(STRIP) $@ -o $@) 79 | 80 | $(KERNEL_INIT_OBJ): $(KERNEL_INIT) $(USER_IMG) 81 | $(info AS $@) 82 | -mkdir -p $(dir $@) 83 | $(CC) $(ASFLAGS) -o $@ $< 84 | 85 | include $(TOP_DIR)/rules.mk 86 | -------------------------------------------------------------------------------- /src/proc/scheduler.yu: -------------------------------------------------------------------------------- 1 | public import arch.arch 2 | public import proc.consts 3 | 4 | // info of each thread 5 | public struct RoundRobinInfo { 6 | // is current thread valid 7 | is_valid: bool, 8 | // time slice of current thread 9 | time: usize, 10 | // index of previous element in queue 11 | prev: Tid, 12 | // index of next element in queue 13 | next: Tid, 14 | } 15 | 16 | // definition of round robin thread scheduler 17 | public struct Scheduler { 18 | // info of all threads (threads[0] for head of queue) 19 | threads: RoundRobinInfo[MAX_THREAD_COUNT + 1], 20 | // maximum time slice 21 | max_time: usize, 22 | // index of current thread 23 | current: Tid, 24 | } 25 | 26 | // initialize current scheduler 27 | public def init(this: Scheduler var&, max_time: usize) { 28 | var i = 0 29 | while i < MAX_THREAD_COUNT + 1 { 30 | this.threads[i] = [RoundRobinInfo] {false} 31 | i += 1 32 | } 33 | this.max_time = max_time 34 | this.current = 0 as Tid 35 | } 36 | 37 | // add thread to scheduler 38 | public def push(this: Scheduler var&, tid: Tid) { 39 | let tid = tid + 1 as Tid 40 | // allocate time slice for new thread 41 | let info: RoundRobinInfo var& = this.threads[tid] 42 | info.is_valid = true 43 | if !info.time { 44 | info.time = this.max_time 45 | } 46 | // push into queue 47 | let prev = this.threads[0].prev 48 | this.threads[prev].next = tid 49 | this.threads[tid].prev = prev 50 | this.threads[0].prev = tid 51 | this.threads[tid].next = 0 as Tid 52 | } 53 | 54 | // get next thread from scheduler 55 | public def pop(this: Scheduler var&, tid: Tid var&): bool { 56 | let ret = this.threads[0].next 57 | if ret != 0 as Tid { 58 | // pop from queue 59 | let prev = this.threads[ret].prev, next = this.threads[ret].next 60 | this.threads[next].prev = prev 61 | this.threads[prev].next = next 62 | this.threads[ret].prev = 0 as Tid 63 | this.threads[ret].next = 0 as Tid 64 | // set as current thread 65 | this.threads[ret].is_valid = false 66 | this.current = ret 67 | tid = ret - 1 as Tid 68 | true 69 | } 70 | else { 71 | false 72 | } 73 | } 74 | 75 | // tell scheduler a tick passed 76 | // returns true if there is no current thread 77 | // or current thread run out of time slices 78 | public def tick(this: Scheduler var&): bool { 79 | let tid = this.current 80 | if tid != 0 as Tid { 81 | this.threads[tid].time -= 1 as usize 82 | !this.threads[tid].time 83 | } 84 | else { 85 | true 86 | } 87 | } 88 | 89 | // remove thread from scheduler 90 | public def exit(this: Scheduler var&, tid: Tid) { 91 | let tid = tid + 1 as Tid 92 | if this.current == tid { 93 | this.current = 0 as Tid 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/lib/io.yu: -------------------------------------------------------------------------------- 1 | import arch.arch 2 | 3 | // IO object 4 | public struct IO {} 5 | public let io: IO = [IO] {} 6 | 7 | // put character 8 | public def putChar(this: IO&, c: u8) { 9 | putChar(c) 10 | } 11 | 12 | // get character 13 | public def getChar(this: IO&): u8 { 14 | getChar() as u8 15 | } 16 | 17 | // print string 18 | public def printString(this: IO&, str: u8*) { 19 | var s = str 20 | while *s != '\0' { 21 | this.putChar(*s) 22 | s += 1 23 | } 24 | } 25 | 26 | // print integer in specific base 27 | public def printInt(this: IO&, int: usize, base: i32) { 28 | var buf: u8[(sizeof usize) * 8 as usize], len = 0, digit: usize 29 | if !int { 30 | buf[0] = '0' 31 | len = 1 32 | } 33 | else { 34 | let b = base as usize 35 | var n = int 36 | while n != 0 as usize { 37 | digit = n % b 38 | n /= b 39 | buf[len] = if digit < 10 as usize { 40 | '0' + digit as u8 41 | } 42 | else { 43 | 'a' + (digit - 10 as usize) as u8 44 | } 45 | len += 1 46 | } 47 | } 48 | while len > 0 { 49 | len -= 1 50 | this.putChar(buf[len]) 51 | } 52 | } 53 | 54 | // print character 55 | public def <<<(this: IO&, c: u8): IO& { 56 | this.putChar(c) 57 | this 58 | } 59 | 60 | // print string 61 | public def <<<(this: IO&, str: u8*): IO& { 62 | this.printString(str) 63 | this 64 | } 65 | 66 | // print signed integer (decimal) 67 | public def <<<(this: IO&, int: i32): IO& { 68 | var i = int 69 | if i < 0 { 70 | this.putChar('-') 71 | i = -i 72 | } 73 | this.printInt(i as usize, 10) 74 | this 75 | } 76 | 77 | // print unsigned integer (decimal) 78 | public def <<<(this: IO&, int: u32): IO& { 79 | this.printInt(int as usize, 10) 80 | this 81 | } 82 | 83 | // print pointer-sized unsigned integer (decimal) 84 | public def <<<(this: IO&, int: usize): IO& { 85 | this.printInt(int, 10) 86 | this 87 | } 88 | 89 | // print signed integer (hexadecimal) 90 | public def <<$(this: IO&, int: i32): IO& { 91 | var i = int 92 | if i < 0 { 93 | this.putChar('-') 94 | i = -i 95 | } 96 | this.printInt(i as usize, 16) 97 | this 98 | } 99 | 100 | // print unsigned integer (hexadecimal) 101 | public def <<$(this: IO&, int: u32): IO& { 102 | this.printInt(int as usize, 16) 103 | this 104 | } 105 | 106 | // print pointer-sized unsigned integer (hexadecimal) 107 | public def <<$(this: IO&, int: usize): IO& { 108 | this.printInt(int, 16) 109 | this 110 | } 111 | 112 | // print boolean 113 | public def <<<(this: IO&, b: bool): IO& { 114 | if b { 115 | this.printString("true") 116 | } 117 | else { 118 | this.printString("false") 119 | } 120 | this 121 | } 122 | -------------------------------------------------------------------------------- /src/fs/info.yu: -------------------------------------------------------------------------------- 1 | public import sync.semaphore 2 | public import fs.vfs.vfs 3 | public import fs.file 4 | public import fs.consts 5 | 6 | import fs.fs 7 | import lib.except 8 | import fs.devfs.fs 9 | 10 | public struct FileInfo { 11 | // semaphore for file descriptors 12 | sema: Semaphore, 13 | cwd: INode var*, 14 | files: File[FILE_COUNT], 15 | } 16 | 17 | public def init(this: FileInfo var&) { 18 | this.sema = newSemaphore() 19 | root_inode.open() 20 | this.cwd = root_inode 21 | var i = 0 22 | while i < FILE_COUNT { 23 | this.files[i] = newFile() 24 | i += 1 25 | } 26 | } 27 | 28 | public def del(this: FileInfo var&) { 29 | // release semaphore 30 | this.sema.del() 31 | // close cwd inode 32 | this.cwd.close() 33 | // close opened files 34 | var i = 0 35 | while i < FILE_COUNT { 36 | if this.files[i].isOpened() { 37 | this.files[i].close() 38 | } 39 | i += 1 40 | } 41 | } 42 | 43 | public def lock(this: FileInfo var&) { 44 | this.sema.wait() 45 | } 46 | 47 | public def unlock(this: FileInfo var&) { 48 | this.sema.signal() 49 | } 50 | 51 | inline def getCwd(this: FileInfo&): INode var* { 52 | this.cwd 53 | } 54 | 55 | public def setCwd(this: FileInfo var&, cwd: INode var*) { 56 | assert(cwd != null as INode var*, "FileInfo.setCwd") 57 | this.cwd.close() 58 | cwd.open() 59 | this.cwd = cwd 60 | } 61 | 62 | // allocate a new file descriptor 63 | public def allocFd(this: FileInfo var&, fd: i32 var&): bool { 64 | var i = 0 65 | while i < FILE_COUNT { 66 | if this.files[i].isNone() { 67 | this.files[i].setAllocated() 68 | fd = i 69 | return true 70 | } 71 | i += 1 72 | } 73 | // no avaliable slot for new file descriptor 74 | false 75 | } 76 | 77 | // initialize standard input/output file descriptor 78 | public def initStdFds(this: FileInfo var&) { 79 | var fd: i32, i = 0 80 | while i < 3 { 81 | let ret = this.allocFd(fd) 82 | assert(ret && fd == i, "FileInfo.initStdFd") 83 | if i == 0 { 84 | // open stdin 85 | this.files[i].open(stdin, O_RDONLY) 86 | } 87 | else { 88 | // open stdout/stderr 89 | this.files[i].open(stdout, O_WRONLY) 90 | } 91 | i += 1 92 | } 93 | } 94 | 95 | // deallocate a file descriptor 96 | public def deallocFd(this: FileInfo var&, fd: i32) { 97 | assert(fd >= 0 && fd < FILE_COUNT && this.files[fd].isAllocated(), 98 | "FileInfo.deallocFd") 99 | this.files[fd].setNone() 100 | } 101 | 102 | // get file item by `fd` 103 | public def getFile(this: FileInfo var&, fd: i32): File var* { 104 | if fd >= 0 && fd < FILE_COUNT && !this.files[fd].isNone() { 105 | &this.files[fd] 106 | } 107 | else { 108 | null as File var* 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /usr/lib/io.yu: -------------------------------------------------------------------------------- 1 | import lib.sys.syscall 2 | 3 | // standard file descriptor 4 | let FD_STDIN = 0 5 | let FD_STDOUT = 1 6 | let FD_STDERR = 2 7 | 8 | // IO object 9 | public struct IO {} 10 | public let io: IO = [IO] {} 11 | 12 | // put character 13 | public def putChar(this: IO&, c: u8) { 14 | write(FD_STDOUT, &c, 1 as usize) 15 | } 16 | 17 | // get character 18 | public def getChar(this: IO&): u8 { 19 | var c: u8 20 | read(FD_STDIN, &c, 1 as usize) 21 | c 22 | } 23 | 24 | // print string 25 | public def printString(this: IO&, str: u8*) { 26 | var s = str 27 | while *s != '\0' { 28 | this.putChar(*s) 29 | s += 1 30 | } 31 | } 32 | 33 | // print integer in specific base 34 | public def printInt(this: IO&, int: usize, base: i32) { 35 | var buf: u8[(sizeof usize) * 8 as usize], len = 0, digit: usize 36 | if !int { 37 | buf[0] = '0' 38 | len = 1 39 | } 40 | else { 41 | let b = base as usize 42 | var n = int 43 | while n != 0 as usize { 44 | digit = n % b 45 | n /= b 46 | buf[len] = if digit < 10 as usize { 47 | '0' + digit as u8 48 | } 49 | else { 50 | 'a' + (digit - 10 as usize) as u8 51 | } 52 | len += 1 53 | } 54 | } 55 | while len > 0 { 56 | len -= 1 57 | this.putChar(buf[len]) 58 | } 59 | } 60 | 61 | // print character 62 | public def <<<(this: IO&, c: u8): IO& { 63 | this.putChar(c) 64 | this 65 | } 66 | 67 | // print string 68 | public def <<<(this: IO&, str: u8*): IO& { 69 | this.printString(str) 70 | this 71 | } 72 | 73 | // print signed integer (decimal) 74 | public def <<<(this: IO&, int: i32): IO& { 75 | var i = int 76 | if i < 0 { 77 | this.putChar('-') 78 | i = -i 79 | } 80 | this.printInt(i as usize, 10) 81 | this 82 | } 83 | 84 | // print unsigned integer (decimal) 85 | public def <<<(this: IO&, int: u32): IO& { 86 | this.printInt(int as usize, 10) 87 | this 88 | } 89 | 90 | // print pointer-sized unsigned integer (decimal) 91 | public def <<<(this: IO&, int: usize): IO& { 92 | this.printInt(int, 10) 93 | this 94 | } 95 | 96 | // print signed integer (hexadecimal) 97 | public def <<$(this: IO&, int: i32): IO& { 98 | var i = int 99 | if i < 0 { 100 | this.putChar('-') 101 | i = -i 102 | } 103 | this.printInt(i as usize, 16) 104 | this 105 | } 106 | 107 | // print unsigned integer (hexadecimal) 108 | public def <<$(this: IO&, int: u32): IO& { 109 | this.printInt(int as usize, 16) 110 | this 111 | } 112 | 113 | // print pointer-sized unsigned integer (hexadecimal) 114 | public def <<$(this: IO&, int: usize): IO& { 115 | this.printInt(int, 16) 116 | this 117 | } 118 | 119 | // print boolean 120 | public def <<<(this: IO&, b: bool): IO& { 121 | if b { 122 | this.printString("true") 123 | } 124 | else { 125 | this.printString("false") 126 | } 127 | this 128 | } 129 | -------------------------------------------------------------------------------- /src/arch/target/fuxi.yu: -------------------------------------------------------------------------------- 1 | // arch definition of Fuxi SoC 2 | 3 | // constant address 4 | inline let FLASH_ADDR = 0x10800000 5 | inline let MEM_ADDR = 0x80000000 6 | inline let MEM_SIZE_MIB = 128 7 | inline let SWITCH_ADDR = 0x1107f020 8 | inline let LED_ADDR = 0x1107f000 9 | inline let NUM_ADDR = 0x1107f010 10 | inline let UART_ADDR = 0x11040000 11 | inline let UART_END = 0x11042000 12 | inline let PLIC_ADDR = 0x11010000 13 | inline let PLIC_END = 0x11020000 14 | inline let CLINT_MTIME = 0x11000100 15 | inline let CLINT_MCMP = 0x11000108 16 | 17 | // UART related definitions 18 | inline let UART_CLOCK_FREQ = 100000000 as u32 19 | inline let UART_BAUD_RATE = 115200 as u32 20 | inline let UART_DAT = 0x11041000 as u32 volatile var* 21 | inline let UART_FCR = 0x11041008 as u32 volatile var* 22 | inline let UART_LCR = 0x1104100C as u32 volatile var* 23 | inline let UART_LSR = 0x11041014 as u32 volatile var* 24 | inline let UART_DLL = 0x11041000 as u32 volatile var* 25 | inline let UART_DLM = 0x11041004 as u32 volatile var* 26 | inline let UART_MCR = 0x11041010 as u32 volatile var* 27 | inline let UART_IER = 0x11041004 as u32 volatile var* 28 | inline let UART_LCR_DLAB = 0x80 as u32 29 | inline let UART_LCR_8BIT = 0x03 as u32 30 | inline let UART_LSR_DA = 0x01 as u32 31 | inline let UART_LSR_RI = 0x40 as u32 32 | 33 | // PLIC related definitions 34 | inline let PLIC_IER = 0x08 35 | inline let PLIC_IAR = 0x0c 36 | inline let PLIC_MER = 0x1c 37 | 38 | // initialize UART 39 | inline def initIO() { 40 | // set baud rate 41 | let divisor = UART_CLOCK_FREQ / ((16 as u32) * UART_BAUD_RATE) 42 | (*UART_LCR) = UART_LCR_DLAB 43 | (*UART_DLL) = divisor & 0xff as u32 44 | (*UART_DLM) = divisor >> 8 as u32 45 | // set transfer format (8-bit, no parity bit) 46 | (*UART_LCR) = UART_LCR_8BIT 47 | // enable 8 bytes receive FIFO 48 | (*UART_FCR) = 0x81 as u32 49 | // enable interrupts 50 | (*UART_IER) = 0x01 as u32 51 | // disable modem controls 52 | (*UART_MCR) = 0 as u32 53 | } 54 | 55 | // initialize PLIC 56 | inline def initIntr() { 57 | let UART_IRQ = 1 << 2 58 | (*((PLIC_ADDR + PLIC_IER) as u32 volatile var*)) = UART_IRQ as u32 59 | (*((PLIC_ADDR + PLIC_MER) as u32 volatile var*)) = 3 as u32 60 | } 61 | 62 | // acknowledge all IRQs from PLIC 63 | inline def ackIntr() { 64 | // acknowledge IRQ #2 (UART) only 65 | let UART_IRQ = 1 << 2 66 | (*((PLIC_ADDR + PLIC_IAR) as u32 volatile var*)) = UART_IRQ as u32 67 | } 68 | 69 | // put character 70 | inline def putChar(c: u8) { 71 | while ((*UART_LSR) & UART_LSR_RI) == 0 as u32 {} 72 | (*UART_DAT) = c as u32 73 | } 74 | 75 | // get character 76 | inline def getChar(): i32 { 77 | if ((*UART_LSR) & UART_LSR_DA) != 0 as u32 { 78 | (*UART_DAT) as i32 79 | } 80 | else { 81 | -1 82 | } 83 | } 84 | 85 | // halt 86 | inline def halt(code: i32) { 87 | while true {} 88 | } 89 | -------------------------------------------------------------------------------- /src/mem/handler.yu: -------------------------------------------------------------------------------- 1 | public import arch.arch 2 | public import mem.attr 3 | public import mem.paging 4 | 5 | import lib.alloc 6 | import mem.pm 7 | import lib.except 8 | 9 | 10 | // interface of memory handler 11 | // object of this structure must be allocated on heap 12 | public struct MemoryHandler { 13 | map: (MemoryHandler var*, ActivePageTable var&, usize, MemoryAttr&), 14 | unmap: (MemoryHandler var*, ActivePageTable var&, usize), 15 | ref_count: i32, 16 | } 17 | 18 | public def del(this: MemoryHandler var*) { 19 | assert((*this).ref_count == 0, "MemoryHandler.del") 20 | heap.dealloc(this as u8 var*) 21 | } 22 | 23 | public def incCounter(this: MemoryHandler var*) { 24 | (*this).ref_count += 1 25 | } 26 | 27 | public def decCounter(this: MemoryHandler var*) { 28 | (*this).ref_count -= 1 29 | if (*this).ref_count == 0 { 30 | this.del() 31 | } 32 | } 33 | 34 | // map address to page table 35 | public def map(this: MemoryHandler var*, pt: ActivePageTable var&, 36 | addr: usize, attr: MemoryAttr&) { 37 | ((*this).map)(this, pt, addr, attr) 38 | } 39 | 40 | // unmap address in page table 41 | public def unmap(this: MemoryHandler var*, pt: ActivePageTable var&, 42 | addr: usize) { 43 | ((*this).unmap)(this, pt, addr) 44 | } 45 | 46 | 47 | // linear mapping 48 | public struct LinearHandler { 49 | base: MemoryHandler, 50 | offset: isize, 51 | } 52 | 53 | def linearMap(this: MemoryHandler var*, pt: ActivePageTable var&, 54 | addr: usize, attr: MemoryAttr&) { 55 | let this: LinearHandler& = *(this as LinearHandler*) 56 | attr.apply(pt.map(addr, (addr as isize + this.offset) as usize)) 57 | } 58 | 59 | def linearUnmap(this: MemoryHandler var*, pt: ActivePageTable var&, 60 | addr: usize) { 61 | pt.unmap(addr) 62 | } 63 | 64 | public def newLinearHandler(offset: isize): MemoryHandler var* { 65 | let handler = heap.alloc(sizeof LinearHandler) as LinearHandler var* 66 | (*handler).base = [MemoryHandler] {linearMap, linearUnmap} 67 | (*handler).offset = offset 68 | handler as MemoryHandler var* 69 | } 70 | 71 | 72 | // map by frame 73 | public struct FrameHandler { 74 | base: MemoryHandler, 75 | } 76 | 77 | def frameMap(this: MemoryHandler var*, pt: ActivePageTable var&, 78 | addr: usize, attr: MemoryAttr&) { 79 | // allocate new frame 80 | let target = allocPhyMem() as usize 81 | assert(target != 0 as usize, "frameMap - OOM") 82 | attr.apply(pt.map(addr, target)) 83 | } 84 | 85 | def frameUnmap(this: MemoryHandler var*, pt: ActivePageTable var&, 86 | addr: usize) { 87 | let frame = pt.unmap(addr) 88 | // release allocated frame 89 | let pa = frame.getAddr() 90 | freePhyMem(pa.getAddr() as u8 var*) 91 | } 92 | 93 | public def newFrameHandler(): MemoryHandler var* { 94 | let handler = heap.alloc(sizeof FrameHandler) as FrameHandler var* 95 | (*handler).base = [MemoryHandler] {frameMap, frameUnmap} 96 | handler as MemoryHandler var* 97 | } 98 | -------------------------------------------------------------------------------- /mkfs/geefs.h: -------------------------------------------------------------------------------- 1 | #ifndef GEEOS_MKFS_GEEFS_H_ 2 | #define GEEOS_MKFS_GEEFS_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "device.h" 14 | #include "structs.h" 15 | 16 | class GeeFS { 17 | public: 18 | GeeFS(Device &dev) : dev_(dev) {} 19 | ~GeeFS() { Sync(); } 20 | 21 | // create an empty GeeFS image on device 22 | bool Create(std::uint32_t block_size, std::uint32_t free_map_num, 23 | std::uint32_t inode_blk_num); 24 | // open GeeFS image on device 25 | bool Open(); 26 | // sync all modifications to device 27 | bool Sync(); 28 | 29 | // list all files/dirs in cwd 30 | void List(std::ostream &os); 31 | // create new file in cwd 32 | bool CreateFile(std::string_view file_name); 33 | // create new directory in cwd 34 | bool MakeDir(std::string_view dir_name); 35 | // change cwd 36 | bool ChangeDir(std::string_view dir_name); 37 | // remove file in cwd 38 | bool Remove(std::string_view file_name); 39 | // read file in cwd to output stream 40 | std::int32_t Read(std::string_view file_name, std::ostream &os, 41 | std::size_t offset, std::size_t len); 42 | // write input stream to file in cwd 43 | std::int32_t Write(std::string_view file_name, std::istream &is, 44 | std::size_t offset, std::size_t len); 45 | 46 | // get current path 47 | std::string cur_path() const { 48 | std::string cur_path; 49 | if (cur_path_.empty()) { 50 | cur_path += '/'; 51 | } 52 | else { 53 | for (const auto &i : cur_path_) cur_path += "/" + i; 54 | } 55 | return cur_path; 56 | } 57 | 58 | private: 59 | // allocate a data block, returns block offset 60 | std::optional AllocDataBlock(); 61 | // allocate an inode, returns inode id 62 | std::optional AllocINode(); 63 | // initialize data block of directory 64 | void InitDirBlock(std::uint32_t blk_ofs, std::uint32_t cur_id, 65 | std::uint32_t parent_id); 66 | // update inode by id 67 | void UpdateINode(const INode &inode, std::uint32_t id); 68 | // read inode by id 69 | bool ReadINode(INode &inode, std::uint32_t id); 70 | // read inode by file name, returns inode id 71 | std::optional ReadINode(INode &inode, 72 | std::string_view name); 73 | // get nth data block offset of inode 74 | std::optional GetBlockOffset(const INode &inode, 75 | std::size_t n); 76 | // append block to inode 77 | bool AppendBlock(INode &inode, std::uint32_t blk_ofs); 78 | // traverse all entries of cwd 79 | bool WalkEntry(std::function callback); 80 | // add new entry in cwd 81 | bool AddEntry(std::uint32_t inode_id, std::string_view file_name); 82 | 83 | // low-level device 84 | Device &dev_; 85 | // super block of disk 86 | SuperBlockHeader super_block_; 87 | // current working directory 88 | INode cwd_; 89 | // inode id of cwd 90 | std::uint32_t cwd_id_; 91 | // current path 92 | std::vector cur_path_; 93 | }; 94 | 95 | #endif // GEEOS_MKFS_GEEFS_H_ 96 | -------------------------------------------------------------------------------- /src/arch/riscv/csr.yu: -------------------------------------------------------------------------------- 1 | public import arch.arch 2 | 3 | // definitions about RISC-V CSR 4 | 5 | // constants definitions 6 | // machine status register 7 | inline let MSTATUS_MPP_MASK = (3 << 11) as usize 8 | inline let MSTATUS_MPP_M = (3 << 11) as usize 9 | inline let MSTATUS_MPP_S = (1 << 11) as usize 10 | inline let MSTATUS_MPP_U = (0 << 11) as usize 11 | inline let MSTATUS_MIE = (1 << 3) as usize 12 | // supervisor status register 13 | inline let SSTATUS_SUM = (1 << 18) as usize 14 | inline let SSTATUS_SPP = (1 << 8) as usize 15 | inline let SSTATUS_SPIE = (1 << 5) as usize 16 | inline let SSTATUS_UPIE = (1 << 4) as usize 17 | inline let SSTATUS_SIE = (1 << 1) as usize 18 | inline let SSTATUS_UIE = (1 << 0) as usize 19 | // supervisor interrupt enable 20 | inline let SIE_SEIE = (1 << 9) as usize 21 | inline let SIE_STIE = (1 << 5) as usize 22 | inline let SIE_SSIE = (1 << 1) as usize 23 | // machine-mode interrupt enable 24 | inline let MIE_MEIE = (1 << 11) as usize 25 | inline let MIE_MTIE = (1 << 7) as usize 26 | inline let MIE_MSIE = (1 << 3) as usize 27 | // supervisor address translation and protection 28 | inline let SATP_SV32 = (1 << 31) as usize 29 | // trap cause 30 | inline let TRAP_S_SOFT_INT = ((1 << 31) | 1) as usize 31 | inline let TRAP_S_EXT_INT = ((1 << 31) | 9) as usize 32 | inline let TRAP_ILLEGAL = 2 as usize 33 | inline let TRAP_U_SYSCALL = 8 as usize 34 | inline let TRAP_INST_FAULT = 12 as usize 35 | inline let TRAP_LOAD_FAULT = 13 as usize 36 | inline let TRAP_STORE_FAULT = 15 as usize 37 | 38 | // declarations of functions in 'csr.S' 39 | extern declare getMstatus: (): usize 40 | extern declare setMstatus: (usize) 41 | extern declare setMepc: (usize) 42 | extern declare getSstatus: (): usize 43 | extern declare setSstatus: (usize) 44 | extern declare getSip: (): usize 45 | extern declare setSip: (usize) 46 | extern declare getSie: (): usize 47 | extern declare setSie: (usize) 48 | extern declare getMie: (): usize 49 | extern declare setMie: (usize) 50 | extern declare setSepc: (usize) 51 | extern declare getSepc: (): usize 52 | extern declare getMedeleg: (): usize 53 | extern declare setMedeleg: (usize) 54 | extern declare getMideleg: (): usize 55 | extern declare setMideleg: (usize) 56 | extern declare setStvec: (usize) 57 | extern declare getStvec: (): usize 58 | extern declare setMtvec: (usize) 59 | extern declare setSatp: (usize) 60 | extern declare getSatp: (): usize 61 | extern declare setSscratch: (usize) 62 | extern declare setMscratch: (usize) 63 | extern declare getScause: (): usize 64 | extern declare getStval: (): usize 65 | extern declare runSfence: (usize, usize) 66 | inline def runSfence() { 67 | asm { "sfence.vma" } 68 | } 69 | 70 | // enable device interrupts 71 | inline def setIntrOn() { 72 | setSie(getSie() | SIE_SEIE | SIE_STIE | SIE_SSIE) 73 | setSstatus(getSstatus() | SSTATUS_SIE) 74 | } 75 | 76 | // disable device interrupts 77 | inline def setIntrOff() { 78 | setSstatus(getSstatus() & ~SSTATUS_SIE) 79 | } 80 | 81 | // check if device interrupts are enabled 82 | inline def getIntr(): bool { 83 | (getSstatus() & SSTATUS_SIE) != 0 as usize 84 | } 85 | 86 | // make SATP value 87 | inline def makeSatp(pt_addr: usize): usize { 88 | (pt_addr >> 12 as usize) | SATP_SV32 89 | } 90 | -------------------------------------------------------------------------------- /src/define/context.yu: -------------------------------------------------------------------------------- 1 | public import arch.arch 2 | public import arch.riscv.csr 3 | 4 | // trap return in 'traphand.S' 5 | extern declare _retTrap: () 6 | 7 | // definition of trap frame 8 | public struct TrapFrame { 9 | // GPRs 10 | x: usize[32], 11 | // supervisor status register 12 | sstatus: usize, 13 | // supervisor exception program counter 14 | sepc: usize, 15 | // supervisor trap value 16 | stval: usize, 17 | // supervisor trap cause 18 | scause: usize, 19 | } 20 | 21 | inline def incSepc(this: TrapFrame var&) { 22 | this.sepc += 4 as usize 23 | } 24 | 25 | inline def isUser(this: TrapFrame&): bool { 26 | !(this.sstatus & SSTATUS_SPP) 27 | } 28 | 29 | // definition of context of thread context 30 | public struct ContextContext { 31 | // return address 32 | ra: usize, 33 | // page table 34 | satp: usize, 35 | // saved registers 36 | s: usize[12], 37 | // trap frame 38 | tf: TrapFrame, 39 | } 40 | 41 | // definition of thread context 42 | public struct Context { 43 | context_addr: ContextContext var*, 44 | } 45 | 46 | // create new context of kernel context 47 | def newKernelCC(entry: usize, kstack_top: usize, 48 | satp: usize): ContextContext { 49 | // make sure privilege mode after 'sret' in kernel thread is still S-mode 50 | var tf = [TrapFrame] {} 51 | tf.x[2] = kstack_top 52 | tf.sepc = entry 53 | tf.sstatus = (getSstatus() | SSTATUS_SPP | SSTATUS_SPIE) & ~SSTATUS_SIE 54 | [ContextContext] { 55 | _retTrap as usize, 56 | satp, 57 | [usize[12]] {}, 58 | tf, 59 | } 60 | } 61 | 62 | // create new context of user context 63 | def newUserCC(entry: usize, ustack_top: usize, 64 | satp: usize): ContextContext { 65 | // make sure privilege mode after 'sret' in user thread is U-mode 66 | var tf = [TrapFrame] {} 67 | tf.x[2] = ustack_top 68 | tf.sepc = entry 69 | tf.sstatus = (getSstatus() | SSTATUS_SPIE) & ~SSTATUS_SIE & ~SSTATUS_SPP 70 | [ContextContext] { 71 | _retTrap as usize, 72 | satp, 73 | [usize[12]] {}, 74 | tf 75 | } 76 | } 77 | 78 | def pushAt(this: ContextContext, stack_top: usize): Context { 79 | let ptr = stack_top as ContextContext var* - 1 80 | (*ptr) = this 81 | [Context] {ptr} 82 | } 83 | 84 | // create new null context 85 | public def newNullContext(): Context { 86 | [Context] {null as ContextContext var*} 87 | } 88 | 89 | // create new kernel thread context 90 | public def newKernelContext(entry: usize, kstack_top: usize, 91 | satp: usize): Context { 92 | newKernelCC(entry, kstack_top, satp).pushAt(kstack_top) 93 | } 94 | 95 | // create new user thread context 96 | public def newUserContext(entry: usize, ustack_top: usize, 97 | kstack_top: usize, satp: usize): Context { 98 | newUserCC(entry, ustack_top, satp).pushAt(kstack_top) 99 | } 100 | 101 | // set initialize arguments for context 102 | public def appendInitArgs(this: Context&, args: usize[3]) { 103 | let cc: ContextContext var& = *this.context_addr 104 | cc.tf.x[10] = args[0] 105 | cc.tf.x[11] = args[1] 106 | cc.tf.x[12] = args[2] 107 | } 108 | 109 | // switch to another context 110 | // defined in 'proc/switch.S' 111 | extern declare switchTo: (Context var&, Context var&) 112 | -------------------------------------------------------------------------------- /usr/bin/shell.yu: -------------------------------------------------------------------------------- 1 | import lib.io 2 | import lib.c.string 3 | import lib.sys.syscall 4 | 5 | let BUF_SIZE = 128 6 | var buf: u8[BUF_SIZE] 7 | 8 | 9 | // read user input to buffer 10 | def getCmd(): bool { 11 | var len = 0, pos = 0 12 | while true { 13 | let c = io.getChar() 14 | when c { 15 | // new line 16 | '\r', '\n' { 17 | io <<< "\r\n" 18 | buf[len] = '\0' 19 | return len > 0 20 | } 21 | // backspace 22 | '\x7f' { 23 | if pos > 0 { 24 | io <<< '\b' 25 | // remove character in current position 26 | var i = pos 27 | while i < len { 28 | io <<< buf[i] 29 | buf[i - 1] = buf[i] 30 | i += 1 31 | } 32 | // clear the last character 33 | io <<< " \b" 34 | // reset position of cursor 35 | i = pos 36 | while i < len { 37 | io <<< '\b' 38 | i += 1 39 | } 40 | // update length and cursor position 41 | len -= 1 42 | pos -= 1 43 | continue 44 | } 45 | } 46 | // console virtual terminal sequences 47 | '\x1b' { 48 | if io.getChar() == '[' { 49 | when io.getChar() { 50 | // cursor forward 51 | 'C' { 52 | if pos < len { 53 | io <<< "\x1b[C" 54 | pos += 1 55 | continue 56 | } 57 | } 58 | // cursor backward 59 | 'D' { 60 | if pos > 0 { 61 | io <<< "\x1b[D" 62 | pos -= 1 63 | continue 64 | } 65 | } 66 | } 67 | } 68 | } 69 | // other characters 70 | else { 71 | if len + 1 < BUF_SIZE { 72 | // insert a character in current position 73 | var i = len - 1 74 | while i >= pos { 75 | buf[i + 1] = buf[i] 76 | i -= 1 77 | } 78 | buf[pos] = c 79 | // update display 80 | i = pos 81 | while i < len + 1 { 82 | io <<< buf[i] 83 | i += 1 84 | } 85 | // reset position of cursor 86 | i = pos 87 | while i < len { 88 | io <<< '\b' 89 | i += 1 90 | } 91 | // update length and cursor position 92 | len += 1 93 | pos += 1 94 | continue 95 | } 96 | } 97 | } 98 | io <<< '\a' 99 | } 100 | false 101 | } 102 | 103 | // parse and execute command line in buffer 104 | def runCmd(): i32 { 105 | if !strcmp(buf as u8*, "exit") { 106 | // exit current thread 107 | exit(0 as usize) 108 | 0 109 | } 110 | else { 111 | // just execute 112 | let ret = execve(buf as u8*, null as u8**, null as u8**) 113 | if ret < 0 { 114 | io <<< "command not found: " <<< buf as u8* <<< '\n' 115 | } 116 | ret 117 | } 118 | } 119 | 120 | extern def main(argc: i32, argv: u8**): i32 { 121 | io <<< "Welcome to GeeOS shell!\n" 122 | io <<< "$ " 123 | while true { 124 | if getCmd() { 125 | runCmd() 126 | } 127 | io <<< "$ " 128 | } 129 | 0 130 | } 131 | -------------------------------------------------------------------------------- /utils/uart.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | 3 | # send binary data file via serial port 4 | # designed for Fuxi SoC 5 | # by MaxXing 6 | 7 | import serial 8 | from serial.tools.list_ports import comports 9 | 10 | import os 11 | import sys 12 | 13 | baudrate = 115200 14 | 15 | 16 | def get_word(num): 17 | byte_list = [] 18 | for _ in range(4): 19 | byte_list.append(int(num & 0xff)) 20 | num >>= 8 21 | return bytes(byte_list) 22 | 23 | 24 | def make_packet(file_name, offset, slice_len=1): 25 | packet = [] 26 | with open(file_name, 'rb') as f: 27 | size = os.path.getsize(file_name) 28 | packet.append(get_word(0x9e9e9e9e)) 29 | packet.append(get_word(offset)) 30 | packet.append(get_word(size)) 31 | for _ in range(0, size, slice_len): 32 | packet.append(f.read(slice_len)) 33 | return packet 34 | 35 | 36 | def send_uart(ser, packet): 37 | for i, p in enumerate(packet): 38 | ser.write(p) 39 | print('sending {:.2%}...\r'.format(i / len(packet)), end='') 40 | if ser.in_waiting: 41 | print(ser.read(ser.in_waiting).decode('utf-8', errors='replace'), end='') 42 | print() 43 | 44 | 45 | if sys.platform == 'win32': 46 | from msvcrt import kbhit as has_char 47 | from msvcrt import getch as get_char 48 | 49 | def init_tty(): 50 | pass 51 | 52 | def restore_tty(settings): 53 | pass 54 | else: 55 | import tty 56 | 57 | def init_tty(): 58 | from termios import tcgetattr 59 | from tty import setraw 60 | fd = sys.stdin.fileno() 61 | settings = tcgetattr(sys.stdin.fileno()) 62 | setraw(fd) 63 | return settings 64 | 65 | def restore_tty(settings): 66 | from termios import tcsetattr, TCSADRAIN 67 | tcsetattr(sys.stdin.fileno(), TCSADRAIN, settings) 68 | 69 | def has_char(): 70 | from select import select 71 | return select([sys.stdin], [], [], 0) == ([sys.stdin], [], []) 72 | 73 | def get_char(): 74 | return sys.stdin.read(1) 75 | 76 | 77 | def read_uart(ser): 78 | settings = init_tty() 79 | try: 80 | while True: 81 | # read from UART 82 | if ser.in_waiting: 83 | sys.stdout.write(ser.read(ser.in_waiting).decode( 84 | 'utf-8', errors='replace')) 85 | sys.stdout.flush() 86 | # read user input 87 | if has_char(): 88 | ch = get_char() 89 | # ^C, just exit 90 | if ord(ch) == 3: 91 | print() 92 | break 93 | # send to uart 94 | ser.write(ch) 95 | except KeyboardInterrupt: 96 | print() 97 | finally: 98 | restore_tty(settings) 99 | 100 | 101 | if __name__ == '__main__': 102 | args = sys.argv 103 | args.pop(0) 104 | 105 | if len(args) < 1: 106 | print('usage: ./uart.py DEVICE') 107 | print(' or: ./uart.py DEVICE FILE OFFSET') 108 | print('avaliable devices:') 109 | for i in comports(): 110 | print(f' {i.device}') 111 | elif len(args) < 3: 112 | print(f'reading via {args[0]}...') 113 | ser = serial.Serial(args[0], baudrate, timeout=1) 114 | read_uart(ser) 115 | else: 116 | print(f'sending via {args[0]}...') 117 | ser = serial.Serial(args[0], baudrate, timeout=1) 118 | packet = make_packet(args[1], int(eval(args[2]))) 119 | send_uart(ser, packet) 120 | read_uart(ser) 121 | -------------------------------------------------------------------------------- /src/fs/file.yu: -------------------------------------------------------------------------------- 1 | public import arch.arch 2 | public import fs.vfs.vfs 3 | 4 | import lib.except 5 | import fs.consts 6 | 7 | public enum FileStatus { 8 | None, Allocated, Opened, 9 | } 10 | 11 | public struct File { 12 | status: FileStatus, 13 | readable: bool, 14 | writable: bool, 15 | inode: INode var*, 16 | offset: usize, 17 | } 18 | 19 | public def newFile(): File { 20 | [File] {FileStatus.None} 21 | } 22 | 23 | inline def isNone(this: File&): bool { 24 | this.status == FileStatus.None 25 | } 26 | 27 | inline def isAllocated(this: File&): bool { 28 | this.status == FileStatus.Allocated 29 | } 30 | 31 | inline def isOpened(this: File&): bool { 32 | this.status == FileStatus.Opened 33 | } 34 | 35 | public def setNone(this: File var&) { 36 | assert(this.isAllocated(), "File.setNone") 37 | this.status = FileStatus.None 38 | } 39 | 40 | public def setAllocated(this: File var&) { 41 | assert(this.isNone(), "File.setAllocated") 42 | this.status = FileStatus.Allocated 43 | } 44 | 45 | // get metadata of inode 46 | public def getMetadata(this: File&, meta: Metadata var&): bool { 47 | if this.isOpened() { 48 | meta = this.inode.getMetadata() 49 | true 50 | } 51 | else { 52 | false 53 | } 54 | } 55 | 56 | // open an inode and store necessary information to current fd 57 | public def open(this: File var&, inode: INode var*, flags: i32): i32 { 58 | if !this.isAllocated() { 59 | return -1 60 | } 61 | // parse flags 62 | this.readable = (flags & O_ACCMODE) == O_RDONLY || 63 | (flags & O_ACCMODE) == O_RDWR 64 | this.writable = (flags & O_ACCMODE) == O_WRONLY || 65 | (flags & O_ACCMODE) == O_RDWR 66 | // update current object 67 | inode.open() 68 | this.inode = inode 69 | this.status = FileStatus.Opened 70 | this.offset = 0 as usize 71 | 0 72 | } 73 | 74 | public def close(this: File var&): i32 { 75 | if this.isOpened() { 76 | this.status = FileStatus.Allocated 77 | this.inode.close() 78 | this.inode = null as INode var* 79 | 0 80 | } 81 | else { 82 | -1 83 | } 84 | } 85 | 86 | public def seek(this: File var&, offset: usize, whence: i32): usize { 87 | if this.isOpened() { 88 | when whence { 89 | SEEK_SET { this.offset = offset } 90 | SEEK_CUR { this.offset += offset } 91 | SEEK_END { 92 | let meta = this.inode.getMetadata() 93 | this.offset = meta.size + offset 94 | } 95 | else { return -1 as usize } 96 | } 97 | this.offset 98 | } 99 | else { 100 | -1 as usize 101 | } 102 | } 103 | 104 | public def read(this: File var&, buf: u8 var*, len: usize): i32 { 105 | if this.isOpened() && this.readable { 106 | let s = this.inode.read(buf, len, this.offset) 107 | this.offset += s as usize 108 | s 109 | } 110 | else { 111 | -1 112 | } 113 | } 114 | 115 | public def write(this: File var&, buf: u8*, len: usize): i32 { 116 | if this.isOpened() && this.writable { 117 | let s = this.inode.write(buf, len, this.offset) 118 | this.offset += s as usize 119 | s 120 | } 121 | else { 122 | -1 123 | } 124 | } 125 | 126 | // replace current fd with another fd 127 | public def replaceWith(this: File var&, that: File var&) { 128 | assert(this.isAllocated() && that.isOpened(), "File.replace") 129 | this = that 130 | this.inode.open() 131 | } 132 | -------------------------------------------------------------------------------- /src/lib/strview.yu: -------------------------------------------------------------------------------- 1 | public import arch.arch 2 | public import lib.c.string 3 | 4 | public struct StrView { 5 | str: u8*, 6 | len: usize, 7 | } 8 | 9 | // create a new string view by a null terminated string 10 | inline def newStrView(str: u8*): StrView { 11 | [StrView] {str, strlen(str)} 12 | } 13 | 14 | // create a new string view by a slice of bytes 15 | inline def newStrView(buf: u8*, len: usize): StrView { 16 | [StrView] {buf, len} 17 | } 18 | 19 | // get string data of string view (not guarantee to be null terminated) 20 | inline def getStr(this: StrView&): u8* { 21 | this.str 22 | } 23 | 24 | // get length of string view 25 | inline def getLen(this: StrView&): usize { 26 | this.len 27 | } 28 | 29 | // check whether the view is empty 30 | inline def empty(this: StrView&): bool { 31 | this.len == 0 as usize 32 | } 33 | 34 | // get the specified character 35 | inline def at(this: StrView&, i: i32): u8 { 36 | this.str[i] 37 | } 38 | inline def at(this: StrView&, i: usize): u8 { 39 | this.str[i] 40 | } 41 | 42 | // get the first character 43 | inline def front(this: StrView&): u8 { 44 | this.str[0] 45 | } 46 | 47 | // get the last character 48 | inline def back(this: StrView&): u8 { 49 | this.str[this.len - 1 as usize] 50 | } 51 | 52 | // check if equals to a null terminated string 53 | public def ==(this: StrView&, str: u8*): bool { 54 | var i = 0 55 | while i as usize < this.len { 56 | if this.str[i] != str[i] || str[i] == '\0' { 57 | return false 58 | } 59 | i += 1 60 | } 61 | str[i] == '\0' 62 | } 63 | inline def !=(this: StrView&, str: u8*): bool { 64 | !(this == str) 65 | } 66 | inline def ==(str: u8*, this: StrView&): bool { 67 | this == str 68 | } 69 | inline def !=(str: u8*, this: StrView&): bool { 70 | !(this == str) 71 | } 72 | 73 | // check if equals to another string view 74 | public def ==(this: StrView&, that: StrView&): bool { 75 | if this.len == that.len { 76 | var i = 0 77 | while i as usize < this.len { 78 | if this.str[i] != that.str[i] { 79 | return false 80 | } 81 | i += 1 82 | } 83 | true 84 | } 85 | else { 86 | false 87 | } 88 | } 89 | inline def !=(this: StrView&, that: StrView&): bool { 90 | !(this == that) 91 | } 92 | 93 | // shrinks the view by moving its start forward 94 | inline def removePrefix(this: StrView var&, n: i32) { 95 | this.str += n 96 | this.len -= n as usize 97 | } 98 | 99 | // shrinks the view by moving its end backward 100 | inline def removeSuffix(this: StrView var&, n: i32) { 101 | this.len -= n as usize 102 | } 103 | 104 | 105 | public struct StrViewSplitIter { 106 | cur: u8*, 107 | len: usize, 108 | delimiter: u8, 109 | } 110 | 111 | // get split iterator 112 | inline def split(this: StrView&, delimiter: u8): StrViewSplitIter { 113 | [StrViewSplitIter] {this.str, this.len, delimiter} 114 | } 115 | 116 | // iterator method 'next' 117 | public def next(this: StrViewSplitIter var&): StrView { 118 | // find next position of delimiter 119 | var len = 0 as usize 120 | while len < this.len { 121 | if this.cur[len] == this.delimiter { 122 | break 123 | } 124 | len += 1 as usize 125 | } 126 | // get next string view 127 | let sv = newStrView(this.cur, len) 128 | // update current iterator 129 | if len < this.len { 130 | len += 1 as usize 131 | } 132 | this.cur += len 133 | this.len -= len 134 | sv 135 | } 136 | 137 | // iterator method 'last' 138 | inline def last(this: StrViewSplitIter&): bool { 139 | this.len == 0 as usize 140 | } 141 | -------------------------------------------------------------------------------- /src/arch/riscv/addr.yu: -------------------------------------------------------------------------------- 1 | public import arch.arch 2 | public import arch.riscv.csr 3 | 4 | // 32-bit virtual addresss 5 | public struct VirtAddr { 6 | addr: usize, 7 | } 8 | 9 | inline def newVirtAddr(addr: usize): VirtAddr { 10 | [VirtAddr] {addr} 11 | } 12 | 13 | inline def newVirtAddr(vpn1: usize, vpn0: usize, offset: usize): VirtAddr { 14 | [VirtAddr] {(vpn1 << 22 as usize) | (vpn0 << 12 as usize) | offset} 15 | } 16 | 17 | inline def getAddr(this: VirtAddr&): usize { 18 | this.addr 19 | } 20 | 21 | inline def getVpn1(this: VirtAddr&): usize { 22 | this.addr >> 22 as usize 23 | } 24 | 25 | inline def getVpn0(this: VirtAddr&): usize { 26 | (this.addr >> 12 as usize) & 0x3ff as usize 27 | } 28 | 29 | inline def getVpn(this: VirtAddr&): usize { 30 | this.addr >> 12 as usize 31 | } 32 | 33 | inline def getOffset(this: VirtAddr&): usize { 34 | this.addr & 0xfff as usize 35 | } 36 | 37 | inline def roundUp(this: VirtAddr&): VirtAddr { 38 | [VirtAddr] {(this.addr + 0xfff as usize) & (~0xfff as usize)} 39 | } 40 | 41 | inline def roundDown(this: VirtAddr&): VirtAddr { 42 | [VirtAddr] {this.addr & (~0xfff as usize)} 43 | } 44 | 45 | // 32-bit physical address 46 | public struct PhysAddr { 47 | addr: usize, 48 | } 49 | 50 | inline def newPhysAddr(addr: usize): PhysAddr { 51 | [PhysAddr] {addr} 52 | } 53 | 54 | inline def getAddr(this: PhysAddr&): usize { 55 | this.addr 56 | } 57 | 58 | inline def getPpn1(this: PhysAddr&): usize { 59 | this.addr >> 22 as usize 60 | } 61 | 62 | inline def getPpn0(this: PhysAddr&): usize { 63 | (this.addr >> 12 as usize) & 0x3ff as usize 64 | } 65 | 66 | inline def getPpn(this: PhysAddr&): usize { 67 | this.addr >> 12 as usize 68 | } 69 | 70 | inline def getOffset(this: PhysAddr&): usize { 71 | this.addr & 0xfff as usize 72 | } 73 | 74 | inline def roundUp(this: PhysAddr&): PhysAddr { 75 | [PhysAddr] {(this.addr + 0xfff as usize) & (~0xfff as usize)} 76 | } 77 | 78 | inline def roundDown(this: PhysAddr&): PhysAddr { 79 | [PhysAddr] {this.addr & (~0xfff as usize)} 80 | } 81 | 82 | // virtual page address 83 | public struct Page { 84 | addr: VirtAddr, 85 | } 86 | 87 | inline def newPage(addr: VirtAddr): Page { 88 | [Page] {addr.roundDown()} 89 | } 90 | 91 | inline def newPage(vpn: usize): Page { 92 | [Page] {newVirtAddr(vpn << 12 as usize)} 93 | } 94 | 95 | inline def newPage(vpn1: usize, vpn0: usize): Page { 96 | [Page] {newVirtAddr(vpn1, vpn0, 0 as usize)} 97 | } 98 | 99 | inline def getAddr(this: Page&): VirtAddr { 100 | this.addr 101 | } 102 | 103 | inline def getVpn1(this: Page&): usize { 104 | this.addr.getVpn1() 105 | } 106 | 107 | inline def getVpn0(this: Page&): usize { 108 | this.addr.getVpn0() 109 | } 110 | 111 | inline def getVpn(this: Page&): usize { 112 | this.addr.getVpn() 113 | } 114 | 115 | // physical frame address 116 | public struct Frame { 117 | addr: PhysAddr, 118 | } 119 | 120 | inline def newFrame(addr: PhysAddr): Frame { 121 | [Frame] {addr.roundDown()} 122 | } 123 | 124 | inline def newFrame(ppn: usize): Frame { 125 | [Frame] {newPhysAddr(ppn << 12 as usize)} 126 | } 127 | 128 | inline def newFrameFromSatp(): Frame { 129 | newFrame(getSatp() & 0x3fffff as usize) 130 | } 131 | 132 | inline def getAddr(this: Frame&): PhysAddr { 133 | this.addr 134 | } 135 | 136 | inline def getPpn1(this: Frame&): usize { 137 | this.addr.getPpn1() 138 | } 139 | 140 | inline def getPpn0(this: Frame&): usize { 141 | this.addr.getPpn0() 142 | } 143 | 144 | inline def getPpn(this: Frame&): usize { 145 | this.addr.getPpn() 146 | } 147 | 148 | inline def !=(this: Frame&, that: Frame): bool { 149 | this.addr.getAddr() != that.addr.getAddr() 150 | } 151 | -------------------------------------------------------------------------------- /src/fs/devfs/stdio.yu: -------------------------------------------------------------------------------- 1 | public import sync.spinlock 2 | public import lib.queue 3 | public import sync.condvar 4 | public import fs.vfs.vfs 5 | 6 | import lib.alloc 7 | import lib.io 8 | 9 | 10 | // buffer for standard input 11 | public struct StdIn { 12 | lock: Spinlock, 13 | buf: Queue, 14 | pushed: CondVar, 15 | } 16 | 17 | public def newStdIn(): StdIn { 18 | [StdIn] {newSpinlock(), newQueue(), newCondVar()} 19 | } 20 | 21 | public def del(this: StdIn var&) { 22 | this.buf.del() 23 | this.pushed.del() 24 | } 25 | 26 | public def push(this: StdIn var&, c: u8) { 27 | this.lock.acquire() 28 | this.buf.push(c as usize) 29 | this.lock.release() 30 | this.pushed.notify() 31 | } 32 | 33 | public def pop(this: StdIn var&): u8 { 34 | var ret: u8 35 | while true { 36 | // try to pop character from queue 37 | this.lock.acquire() 38 | let valid = !this.buf.empty() 39 | if valid { 40 | ret = this.buf.pop() as u8 41 | } 42 | this.lock.release() 43 | // wait if is invalid 44 | if !valid { 45 | this.pushed.wait() 46 | } 47 | else { 48 | break 49 | } 50 | } 51 | ret 52 | } 53 | 54 | 55 | /* 56 | * INode related stuffs 57 | */ 58 | // inode of stdin 59 | public struct StdInINode { 60 | inode: INode, 61 | stdin: StdIn var*, 62 | } 63 | var stdin_ops: INodeOps 64 | 65 | // inode of stdout 66 | public struct StdOutINode { 67 | inode: INode, 68 | } 69 | var stdout_ops: INodeOps 70 | 71 | public def newStdInINode(stdin: StdIn var*): INode var* { 72 | var inode = heap.alloc(sizeof StdInINode) as StdInINode var* 73 | (inode as INode var*).init(&stdin_ops) 74 | (*inode).stdin = stdin 75 | inode as INode var* 76 | } 77 | 78 | public def newStdOutINode(): INode var* { 79 | var inode = heap.alloc(sizeof StdOutINode) as StdOutINode var* 80 | (inode as INode var*).init(&stdout_ops) 81 | inode as INode var* 82 | } 83 | 84 | 85 | /* 86 | * inode virtual operations of stdio 87 | */ 88 | def stdIoGetFs(this: INode var*): FileSystem var* { 89 | null as FileSystem var* 90 | } 91 | 92 | def stdIoGetMetadata(this: INode var*): Metadata { 93 | [Metadata] {0 as u32, INodeType.CharDevice} 94 | } 95 | 96 | def stdIoFind(this: INode var*, name: StrView&): INode var* { 97 | null as INode var* 98 | } 99 | 100 | def stdIoRead(this: INode var*, buf: u8 var*, len: usize, 101 | offset: usize): i32 { 102 | -1 103 | } 104 | 105 | def stdIoWrite(this: INode var*, buf: u8*, len: usize, 106 | offset: usize): i32 { 107 | -1 108 | } 109 | 110 | def stdIoCleanUp(this: INode var*) {} 111 | 112 | def stdInRead(this: INode var*, buf: u8 var*, len: usize, 113 | offset: usize): i32 { 114 | var i = 0 115 | while i as usize < len { 116 | buf[i] = (*(*(this as StdInINode var*)).stdin).pop() 117 | i += 1 118 | } 119 | i 120 | } 121 | 122 | def stdOutWrite(this: INode var*, buf: u8*, len: usize, 123 | offset: usize): i32 { 124 | var i = 0 125 | while i as usize < len { 126 | io.putChar(buf[i]) 127 | i += 1 128 | } 129 | i 130 | } 131 | 132 | 133 | /* 134 | * filesystem initializers 135 | */ 136 | // initialize virtual operations 137 | public def initStdIoOps() { 138 | // stdin related ops 139 | stdin_ops.op_get_fs = stdIoGetFs 140 | stdin_ops.op_get_metadata = stdIoGetMetadata 141 | stdin_ops.op_find = stdIoFind 142 | stdin_ops.op_read = stdInRead 143 | stdin_ops.op_write = stdIoWrite 144 | stdin_ops.op_cleanup = stdIoCleanUp 145 | // stdout related ops 146 | stdout_ops.op_get_fs = stdIoGetFs 147 | stdout_ops.op_get_metadata = stdIoGetMetadata 148 | stdout_ops.op_find = stdIoFind 149 | stdout_ops.op_read = stdIoRead 150 | stdout_ops.op_write = stdOutWrite 151 | stdout_ops.op_cleanup = stdIoCleanUp 152 | } 153 | -------------------------------------------------------------------------------- /src/proc/pool.yu: -------------------------------------------------------------------------------- 1 | public import proc.structs 2 | public import proc.consts 3 | public import proc.scheduler 4 | 5 | import lib.except 6 | 7 | // definition of thread status 8 | public enum ThreadStatus { 9 | Unused, 10 | Ready, 11 | Running, 12 | Sleeping, 13 | Exited, 14 | } 15 | 16 | // definition of thread status with code 17 | public struct ThreadStatusCode { 18 | status: ThreadStatus, 19 | code: usize, 20 | } 21 | 22 | public struct ThreadInfo { 23 | status: ThreadStatusCode, 24 | present: bool, 25 | thread: Thread var*, 26 | } 27 | 28 | // definition of thread pool 29 | public struct ThreadPool { 30 | threads: ThreadInfo[MAX_THREAD_COUNT], 31 | scheduler: Scheduler, 32 | } 33 | 34 | def allocTid(this: ThreadPool&): Tid { 35 | var i = 0 36 | while i < MAX_THREAD_COUNT { 37 | if this.threads[i].status.status == ThreadStatus.Unused { 38 | return i as Tid 39 | } 40 | i += 1 41 | } 42 | panic("allocTid") 43 | 0 as Tid 44 | } 45 | 46 | def getInfo(this: ThreadPool var&, tid: Tid): ThreadInfo var& { 47 | let info: ThreadInfo var& = this.threads[tid] 48 | assert(info.status.status != ThreadStatus.Unused, 49 | "thread does not exist") 50 | info 51 | } 52 | 53 | // initialize thread pool 54 | public def init(this: ThreadPool var&, max_time: usize) { 55 | var i = 0 56 | while i < MAX_THREAD_COUNT { 57 | this.threads[i] = [ThreadInfo] { 58 | [ThreadStatusCode] {ThreadStatus.Unused}, 59 | false, 60 | null as Thread var*, 61 | } 62 | i += 1 63 | } 64 | this.scheduler.init(max_time) 65 | } 66 | 67 | // add thread to pool 68 | public def add(this: ThreadPool var&, thread: Thread var*) { 69 | let tid = this.allocTid() 70 | this.threads[tid] = [ThreadInfo] { 71 | [ThreadStatusCode] {ThreadStatus.Ready, 0 as usize}, 72 | true, 73 | thread, 74 | } 75 | this.scheduler.push(tid) 76 | } 77 | 78 | // acquire a thread 79 | public def acquire(this: ThreadPool var&, tid: Tid var&): Thread var* { 80 | if this.scheduler.pop(tid) { 81 | // get thread info of specific tid 82 | let info: ThreadInfo var& = this.getInfo(tid) 83 | // update thread info 84 | info.status.status = ThreadStatus.Running 85 | info.status.code = tid 86 | info.thread 87 | } 88 | else { 89 | null as Thread var* 90 | } 91 | } 92 | 93 | // retrieve thread to pool 94 | public def retrieve(this: ThreadPool var&, tid: Tid, thread: Thread var*) { 95 | let info: ThreadInfo var& = this.getInfo(tid) 96 | if info.present { 97 | assert(info.thread == thread, "retrieve") 98 | // just schedule running threads 99 | // do not care sleeping threads until they are awakened 100 | if info.status.status == ThreadStatus.Running { 101 | info.status.status = ThreadStatus.Ready 102 | this.scheduler.push(tid) 103 | } 104 | } 105 | else { 106 | // thread exited, release resources 107 | info.status.status = ThreadStatus.Unused 108 | info.thread.del() 109 | info.thread = null as Thread var* 110 | } 111 | } 112 | 113 | // tell scheduler a tick passed 114 | public def tick(this: ThreadPool var&): bool { 115 | this.scheduler.tick() 116 | } 117 | 118 | // mark specific thread as exited 119 | public def exit(this: ThreadPool var&, tid: Tid, code: usize) { 120 | let info: ThreadInfo var& = this.getInfo(tid) 121 | info.status.status = ThreadStatus.Exited 122 | info.status.code = code 123 | info.present = false 124 | // tell scheduler thread exited 125 | this.scheduler.exit(tid) 126 | } 127 | 128 | // mark specific thread as sleeping 129 | public def sleep(this: ThreadPool var&, tid: Tid) { 130 | let info: ThreadInfo var& = this.getInfo(tid) 131 | info.status.status = ThreadStatus.Sleeping 132 | } 133 | 134 | // wake up specific thread 135 | public def wakeUp(this: ThreadPool var&, tid: Tid) { 136 | let info: ThreadInfo var& = this.getInfo(tid) 137 | info.status.status = ThreadStatus.Ready 138 | // push to scheduler, wating for next schedule 139 | this.scheduler.push(tid) 140 | } 141 | -------------------------------------------------------------------------------- /src/arch/riscv/pagetable.yu: -------------------------------------------------------------------------------- 1 | public import arch.arch 2 | public import arch.riscv.consts 3 | public import arch.riscv.addr 4 | public import arch.riscv.framealloc 5 | 6 | // definition of RISC-V PTE 7 | public struct PageTableEntry { 8 | data: usize, 9 | } 10 | 11 | inline def newPte(data: usize) { 12 | [PageTableEntry] {data} 13 | } 14 | 15 | inline def isUnused(this: PageTableEntry&): bool { 16 | this.data == 0 as usize 17 | } 18 | 19 | inline def setUnused(this: PageTableEntry var&) { 20 | this.data = 0 as usize 21 | } 22 | 23 | inline def getFlags(this: PageTableEntry&): usize { 24 | this.data & 0xff as usize 25 | } 26 | 27 | inline def getFlag(this: PageTableEntry&, flag: usize): bool { 28 | (this.getFlags() & flag) != 0 as usize 29 | } 30 | 31 | inline def isLeaf(this: PageTableEntry&): bool { 32 | let flag_vad = PTE_FLAG_V | PTE_FLAG_A | PTE_FLAG_D 33 | (this.getFlags() & ~flag_vad) != 0 as usize 34 | } 35 | 36 | inline def setFlags(this: PageTableEntry var&, flags: usize) { 37 | this.data = (this.data & ~(0xff as usize)) | flags 38 | } 39 | 40 | inline def setFlag(this: PageTableEntry var&, flag: usize, value: bool) { 41 | let flags = if value { 42 | this.getFlags() | flag 43 | } 44 | else { 45 | this.getFlags() & ~flag 46 | } 47 | this.setFlags(flags) 48 | } 49 | 50 | inline def clearFlag(this: PageTableEntry var&, flag: usize) { 51 | this.setFlag(flag, false) 52 | } 53 | 54 | inline def getPpn(this: PageTableEntry&): usize { 55 | this.data >> 10 as usize 56 | } 57 | 58 | inline def getAddr(this: PageTableEntry&): PhysAddr { 59 | newPhysAddr(this.getPpn() << 12 as usize) 60 | } 61 | 62 | inline def getFrame(this: PageTableEntry&): Frame { 63 | newFrame(this.getPpn()) 64 | } 65 | 66 | inline def set(this: PageTableEntry var&, frame: Frame, flags: usize) { 67 | let flags = flags | PTE_FLAG_A | PTE_FLAG_D 68 | this.data = (frame.getPpn() << 10 as usize) | flags 69 | } 70 | 71 | // RISC-V page table 72 | public struct PageTable { 73 | ptes: PageTableEntry[PTE_COUNT], 74 | } 75 | 76 | inline def getPte(this: PageTable var*, i: usize): PageTableEntry var& { 77 | (*this).ptes[i] 78 | } 79 | 80 | // clear all PTEs 81 | inline def zero(this: PageTable var*) { 82 | var i = 0 83 | while i < PTE_COUNT { 84 | (*this).ptes[i].setUnused() 85 | i += 1 86 | } 87 | } 88 | 89 | // deallocate & clear all avaliable non-leaf PTEs frames before `index` 90 | inline def clear(this: PageTable var*, index: usize, 91 | dealloc: FrameDeallocator var*) { 92 | var i = 0 93 | while i as usize < index { 94 | let pte: PageTableEntry var& = this.getPte(i as usize) 95 | if !pte.isUnused() && !pte.isLeaf() { 96 | let frame = pte.getFrame() 97 | dealloc.dealloc(frame) 98 | pte.setUnused() 99 | } 100 | i += 1 101 | } 102 | } 103 | 104 | // Parameter `frame` is the actual physical frame where the 105 | // root page table resides, it can be anywhere in the main memory. 106 | // Denote `recursive_index` by K, then virtual address of the 107 | // root page table is (K, K+1, 0) in Sv32. 108 | inline def setRecursive(this: PageTable var*, index: usize, frame: Frame) { 109 | (*this).ptes[index].set(frame, PTE_FLAG_V) 110 | (*this).ptes[index + 1 as usize].set(frame, PTE_FLAG_V | PTE_FLAG_R | 111 | PTE_FLAG_W) 112 | } 113 | 114 | // setup linear mapping for the page with `PPN[1]` 115 | inline def mapLinear(this: PageTable var*, ppn1: usize, 116 | offset: usize, flags: usize) { 117 | let pa = newPhysAddr((ppn1 * MEGAPAGE_SIZE as usize) + offset) 118 | (*this).ptes[ppn1].set(newFrame(pa), flags) 119 | } 120 | 121 | // setup linear mapping of address range 122 | // `begin` & `end` will be treated as virtual address 123 | inline def mapLinearRange(this: PageTable var*, begin: usize, end: usize, 124 | offset: usize, flags: usize) { 125 | let begin_ppn = begin / MEGAPAGE_SIZE as usize 126 | let end_ppn = end / MEGAPAGE_SIZE as usize 127 | var ppn1 = begin_ppn 128 | while ppn1 <= end_ppn { 129 | this.mapLinear(ppn1, offset, flags) 130 | ppn1 += 1 as usize 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/proc/processor.yu: -------------------------------------------------------------------------------- 1 | public import proc.pool 2 | public import proc.structs 3 | 4 | import arch.arch 5 | import lib.alloc 6 | import arch.riscv.csr 7 | import proc.consts 8 | 9 | // model of processor 10 | // objects of this structure will always be allocated on the heap 11 | public struct Processor { 12 | // thread pool 13 | pool: ThreadPool, 14 | // idle thread 15 | idle: Thread var*, 16 | // current thread id 17 | current_tid: Tid, 18 | // current thread 19 | current_thread: Thread var*, 20 | } 21 | 22 | public def newProcessor(max_time: usize): Processor var* { 23 | let proc = heap.alloc(sizeof Processor) as Processor var* 24 | (*proc).pool.init(max_time) 25 | (*proc).idle = newIdleThread() 26 | (*proc).current_thread = null as Thread var* 27 | proc 28 | } 29 | 30 | // add thread to processor 31 | public def addThread(this: Processor var*, thread: Thread var*) { 32 | (*this).pool.add(thread) 33 | } 34 | 35 | // get current thread id 36 | public def getCurrentTid(this: Processor var*): Tid { 37 | (*this).current_tid 38 | } 39 | 40 | // get current thread 41 | public def getCurrentThread(this: Processor var*): Thread var* { 42 | (*this).current_thread 43 | } 44 | 45 | // run current processor 46 | public def run(this: Processor var*) { 47 | // turn off interrupts to prevent sync problem 48 | setIntrOff() 49 | // perform thread switching 50 | while true { 51 | // try to acquire next runnable thread 52 | var thread = (*this).pool.acquire((*this).current_tid) 53 | if thread != null as Thread var* { 54 | // update current thread 55 | (*this).current_thread = thread 56 | // switch to target thread 57 | (*this).idle.switchTo(thread) 58 | // thread exited or run out of time slice, switch back to idle 59 | // retrieve last thread to pool 60 | (*this).pool.retrieve((*this).current_tid, thread) 61 | (*this).current_thread = null as Thread var* 62 | } 63 | else { 64 | // enable interrupt 65 | setIntrOn() 66 | // wait for next tick 67 | asm { "wfi" } 68 | // disable interrupt and find next thread 69 | setIntrOff() 70 | } 71 | } 72 | } 73 | 74 | // yield and reschedule 75 | public def yield(this: Processor var*) { 76 | if (*this).current_thread != null as Thread var* { 77 | // 'switchTo' does not ensure that 'sstatus' will 78 | // not be changed after switching, so 79 | // store 'sstatus' and disable interrupt 80 | let sstatus = getSstatus() 81 | setIntrOff() 82 | // switch to idle thread 83 | (*this).current_thread.switchTo((*this).idle) 84 | // restore 'sstatus' 85 | setSstatus(sstatus) 86 | } 87 | } 88 | 89 | // tell processor a tick passed 90 | // will be called by timer interrupt handler 91 | public def tick(this: Processor var*) { 92 | if (*this).current_thread != null as Thread var* { 93 | // check if current thread runs out of time slices 94 | if (*this).pool.tick() { 95 | this.yield() 96 | } 97 | } 98 | } 99 | 100 | // exit current thread 101 | public def exit(this: Processor var*, code: usize) { 102 | // turn off interrupt because processor will switch to idle thread 103 | setIntrOff() 104 | // tell pool a thread just exited 105 | (*this).pool.exit((*this).current_tid, code) 106 | // wake up the thread that waiting for this thread 107 | let wait = (*this).current_thread.getWait() 108 | if wait != TID_NONE { 109 | (*this).pool.wakeUp(wait) 110 | } 111 | // switch to idle thread and schedule 112 | (*this).current_thread.switchTo((*this).idle) 113 | while true {} 114 | } 115 | 116 | // give up CPU and sleep 117 | public def sleep(this: Processor var*) { 118 | if (*this).current_thread != null as Thread var* { 119 | // store 'sstatus' and disable interrupt 120 | let sstatus = getSstatus() 121 | setIntrOff() 122 | // tell pool a thread will sleep 123 | (*this).pool.sleep((*this).current_tid) 124 | // switch to idle thread 125 | (*this).current_thread.switchTo((*this).idle) 126 | // restore 'sstatus' 127 | setSstatus(sstatus) 128 | } 129 | } 130 | 131 | // wake up current thread 132 | public def wakeUp(this: Processor var*, tid: Tid) { 133 | (*this).pool.wakeUp(tid) 134 | } 135 | -------------------------------------------------------------------------------- /src/trap/traphand.S: -------------------------------------------------------------------------------- 1 | # XLEN in bytes 2 | .equ XLENB, 4 3 | 4 | 5 | # load register from stack 6 | .macro LOAD a1, a2 7 | lw \a1, \a2*XLENB(sp) 8 | .endm 9 | 10 | 11 | # store register to stack 12 | .macro STORE a1, a2 13 | sw \a1, \a2*XLENB(sp) 14 | .endm 15 | 16 | 17 | # store all necessary registers to trap frame 18 | .macro SAVE_ALL 19 | # If coming from userspace, preserve the user stack pointer and load 20 | # the kernel stack pointer. If we came from the kernel, sscratch 21 | # will contain 0, and we should continue on the current stack. 22 | csrrw sp, sscratch, sp 23 | bnez sp, _from_user 24 | _from_kernel: 25 | csrr sp, sscratch 26 | # sscratch = previous_sp, sp = kernel_sp 27 | _from_user: 28 | # provide space for trap frame 29 | addi sp, sp, -36*XLENB 30 | # save x registers except x2 (sp) 31 | STORE x1, 1 32 | STORE x3, 3 33 | STORE x4, 4 34 | STORE x5, 5 35 | STORE x6, 6 36 | STORE x7, 7 37 | STORE x8, 8 38 | STORE x9, 9 39 | STORE x10, 10 40 | STORE x11, 11 41 | STORE x12, 12 42 | STORE x13, 13 43 | STORE x14, 14 44 | STORE x15, 15 45 | STORE x16, 16 46 | STORE x17, 17 47 | STORE x18, 18 48 | STORE x19, 19 49 | STORE x20, 20 50 | STORE x21, 21 51 | STORE x22, 22 52 | STORE x23, 23 53 | STORE x24, 24 54 | STORE x25, 25 55 | STORE x26, 26 56 | STORE x27, 27 57 | STORE x28, 28 58 | STORE x29, 29 59 | STORE x30, 30 60 | STORE x31, 31 61 | # get sp, sstatus, sepc, stval, scause 62 | # set sscratch = 0 63 | csrrw s0, sscratch, x0 64 | csrr s1, sstatus 65 | csrr s2, sepc 66 | csrr s3, stval 67 | csrr s4, scause 68 | # store sp, sstatus, sepc, stval, scause 69 | STORE s0, 2 70 | STORE s1, 32 71 | STORE s2, 33 72 | STORE s3, 34 73 | STORE s4, 35 74 | .endm 75 | 76 | # load all necessary registers from trap frame 77 | .macro RESTORE_ALL 78 | LOAD s1, 32 # s1 = sstatus 79 | LOAD s2, 33 # s2 = sepc 80 | andi s0, s1, 1 << 8 # sstatus.SPP = 1? 81 | bnez s0, _to_kernel # s0 = back to kernel? 82 | _to_user: 83 | addi s0, sp, 36*XLENB 84 | csrw sscratch, s0 # sscratch = kernel_sp 85 | _to_kernel: 86 | # restore sstatus, sepc 87 | csrw sstatus, s1 88 | csrw sepc, s2 89 | # restore x registers except x2 (sp) 90 | LOAD x1, 1 91 | LOAD x3, 3 92 | LOAD x4, 4 93 | LOAD x5, 5 94 | LOAD x6, 6 95 | LOAD x7, 7 96 | LOAD x8, 8 97 | LOAD x9, 9 98 | LOAD x10, 10 99 | LOAD x11, 11 100 | LOAD x12, 12 101 | LOAD x13, 13 102 | LOAD x14, 14 103 | LOAD x15, 15 104 | LOAD x16, 16 105 | LOAD x17, 17 106 | LOAD x18, 18 107 | LOAD x19, 19 108 | LOAD x20, 20 109 | LOAD x21, 21 110 | LOAD x22, 22 111 | LOAD x23, 23 112 | LOAD x24, 24 113 | LOAD x25, 25 114 | LOAD x26, 26 115 | LOAD x27, 27 116 | LOAD x28, 28 117 | LOAD x29, 29 118 | LOAD x30, 30 119 | LOAD x31, 31 120 | # restore sp last 121 | LOAD x2, 2 122 | .endm 123 | 124 | 125 | # S-mode trap handler 126 | .section .text 127 | .globl _handleTrap 128 | .align 2 129 | _handleTrap: 130 | SAVE_ALL 131 | mv a0, sp 132 | # call trap handler in 'trap.yu' 133 | call handleTrap 134 | .globl _retTrap 135 | _retTrap: 136 | RESTORE_ALL 137 | # return from S-mode 138 | sret 139 | 140 | 141 | # M-mode timer interrupt handler 142 | .globl _handleTimer 143 | _handleTimer: 144 | # the memory that mscratch points to has been defined in 'entry.yu' 145 | # scratch[0, 4, 8]: register save area 146 | # scratch[12]: address of CLINT's 'mtimecmp' register 147 | # scratch[16]: desired interval between interrupts 148 | csrrw a0, mscratch, a0 149 | sw a1, 0(a0) 150 | sw a2, 4(a0) 151 | sw a3, 8(a0) 152 | # schedule the next timer interrupt 153 | # by adding interval to 'mtimecmp' 154 | lw a1, 12(a0) # CLINT_MTIMECMP 155 | lw a2, 16(a0) # interval 156 | # update lower 32-bit of 'mtimecmp' 157 | lw a3, 0(a1) 158 | add a2, a2, a3 159 | sw a2, 0(a1) 160 | sltu a2, a2, a3 161 | # update higher 32-bit of 'mtimecmp' 162 | lw a3, 4(a1) 163 | add a3, a3, a2 164 | sw a3, 4(a1) 165 | # raise a supervisor software interrupt 166 | csrsi sip, 1 << 1 167 | # restore registers 168 | lw a3, 8(a0) 169 | lw a2, 4(a0) 170 | lw a1, 0(a0) 171 | csrrw a0, mscratch, a0 172 | mret 173 | -------------------------------------------------------------------------------- /src/mem/heap.yu: -------------------------------------------------------------------------------- 1 | public import arch.arch 2 | 3 | import mem.consts 4 | import sync.spinlock 5 | import lib.alloc 6 | 7 | // Memory allocator by Kernighan and Ritchie, 8 | // The C programming Language, 2nd ed. Section 8.7. 9 | 10 | // size of heap memory that to be allocated each time 11 | let LEAST_ALLOC = 32 as usize 12 | 13 | // definition of linked list header 14 | struct Header { 15 | next: Header var*, 16 | size: usize, 17 | } 18 | 19 | struct Heap { 20 | // spinlock of heap 21 | lock: Spinlock, 22 | // head of linked list 23 | base: Header, 24 | // linked list that stores free memory blocks 25 | free_unit: Header var*, 26 | // definition of heap memory 27 | heap_mem: u8 var*, 28 | // definition of heap base pointer, 29 | heap_base: u8 var*, 30 | } 31 | var heap_mem: Heap 32 | 33 | // allocate more heap space 34 | def increaseHeap(size: usize): u8 var* { 35 | if heap_mem.heap_base + size > heap_mem.heap_mem + HEAP_SIZE { 36 | // heap is full 37 | null as u8 var* 38 | } 39 | else { 40 | // just increase 41 | let p = heap_mem.heap_base 42 | heap_mem.heap_base += size 43 | p 44 | } 45 | } 46 | 47 | def freeMem(ptr: u8 var*) { 48 | // point to the header 49 | let p = ptr as Header var* - 1 50 | var q = heap_mem.free_unit 51 | while !(p > q && p < (*q).next) { 52 | if q >= (*q).next && (p > q || p < (*q).next) { 53 | break 54 | } 55 | q = (*q).next 56 | } 57 | // merge with previous adjacent unit 58 | if p + (*p).size == (*q).next { 59 | (*p).size += (*(*q).next).size 60 | (*p).next = (*(*q).next).next 61 | } 62 | else { 63 | (*p).next = (*q).next 64 | } 65 | // merge with next adjacent unit 66 | if q + (*q).size == p { 67 | (*q).size += (*p).size 68 | (*q).next = (*p).next 69 | } 70 | else { 71 | (*q).next = p 72 | } 73 | // set the free unit pointer 74 | heap_mem.free_unit = q 75 | } 76 | 77 | def moreCore(len: usize): Header var* { 78 | // get size of increased headers 79 | let unit_size = LEAST_ALLOC * ((len + LEAST_ALLOC - 1 as usize) / 80 | LEAST_ALLOC) 81 | // allocate more headers 82 | let core_ptr = increaseHeap(unit_size * sizeof Header) 83 | if core_ptr == null as u8 var* { 84 | null as Header var* 85 | } 86 | else { 87 | // initialize new header 88 | let unit_ptr = core_ptr as Header var* 89 | (*unit_ptr).size = unit_size 90 | freeMem((unit_ptr + 1) as u8 var*) 91 | heap_mem.free_unit 92 | } 93 | } 94 | 95 | def allocMem(size: usize): u8 var* { 96 | let unit_size = ((size + sizeof Header - 1 as usize) / 97 | sizeof Header) + 1 as usize 98 | var ret = null as u8 var* 99 | // check if no free blocks 100 | var prev = heap_mem.free_unit 101 | if prev == null as Header var* { 102 | prev = &heap_mem.base 103 | heap_mem.free_unit = prev 104 | heap_mem.base.next = heap_mem.free_unit 105 | heap_mem.base.size = 0 as usize 106 | } 107 | // allocate free block 108 | var p = (*prev).next 109 | while true { 110 | // big enough 111 | if (*p).size >= unit_size { 112 | if (*p).size == unit_size { 113 | // exactly 114 | (*prev).next = (*p).next 115 | } 116 | else { 117 | // allocate tail end 118 | (*p).size -= unit_size 119 | p += (*p).size 120 | (*p).size = unit_size 121 | } 122 | heap_mem.free_unit = prev 123 | ret = (p + 1) as u8 var* 124 | break 125 | } 126 | if p == heap_mem.free_unit { 127 | p = moreCore(unit_size) 128 | if p == null as Header var* { 129 | // no spare space 130 | ret = null as u8 var* 131 | break 132 | } 133 | } 134 | prev = p 135 | p = (*p).next 136 | } 137 | ret 138 | } 139 | 140 | // allocate new heap memory 141 | def allocHeapMem(size: usize): u8 var* { 142 | heap_mem.lock.acquire() 143 | let ret = allocMem(size) 144 | heap_mem.lock.release() 145 | ret 146 | } 147 | 148 | // free allocated heap memory 149 | def freeHeapMem(ptr: u8 var*) { 150 | heap_mem.lock.acquire() 151 | freeMem(ptr) 152 | heap_mem.lock.release() 153 | } 154 | 155 | // initialize heap memory 156 | public def initHeapMem() { 157 | heap_mem.lock = newSpinlock() 158 | heap_mem.heap_mem = HEAP_BASE as u8 var* 159 | heap_mem.heap_base = heap_mem.heap_mem 160 | heap.init(allocHeapMem, freeHeapMem) 161 | } 162 | -------------------------------------------------------------------------------- /usr/lib/alloc.yu: -------------------------------------------------------------------------------- 1 | import lib.sync.spinlock 2 | 3 | // Memory allocator by Kernighan and Ritchie, 4 | // The C programming Language, 2nd ed. Section 8.7. 5 | 6 | // size of heap 7 | let HEAP_SIZE = 0x4000 as usize 8 | // size of heap memory that to be allocated each time 9 | let LEAST_ALLOC = 32 as usize 10 | 11 | // definition of linked list header 12 | struct Header { 13 | next: Header var*, 14 | size: usize, 15 | } 16 | 17 | // heap manager 18 | struct Heap { 19 | // spinlock of heap 20 | lock: Spinlock, 21 | // head of linked list 22 | base: Header, 23 | // linked list that stores free memory blocks 24 | free_unit: Header var*, 25 | // definition of heap memory 26 | heap_mem: u8 var*, 27 | // definition of heap base pointer, 28 | heap_base: u8 var*, 29 | } 30 | var heap: Heap 31 | 32 | // heap memory 33 | var heap_mem: u8[HEAP_SIZE] = [u8[HEAP_SIZE]] {} 34 | 35 | // global heap allocator 36 | public struct HeapAllocator {} 37 | public let alloc: HeapAllocator = [HeapAllocator] {} 38 | 39 | 40 | // allocate more heap space 41 | def increaseHeap(size: usize): u8 var* { 42 | if heap.heap_base + size > heap.heap_mem + HEAP_SIZE { 43 | // heap is full 44 | null as u8 var* 45 | } 46 | else { 47 | // just increase 48 | let p = heap.heap_base 49 | heap.heap_base += size 50 | p 51 | } 52 | } 53 | 54 | def freeMem(ptr: u8 var*) { 55 | // point to the header 56 | let p = ptr as Header var* - 1 57 | var q = heap.free_unit 58 | while !(p > q && p < (*q).next) { 59 | if q >= (*q).next && (p > q || p < (*q).next) { 60 | break 61 | } 62 | q = (*q).next 63 | } 64 | // merge with previous adjacent unit 65 | if p + (*p).size == (*q).next { 66 | (*p).size += (*(*q).next).size 67 | (*p).next = (*(*q).next).next 68 | } 69 | else { 70 | (*p).next = (*q).next 71 | } 72 | // merge with next adjacent unit 73 | if q + (*q).size == p { 74 | (*q).size += (*p).size 75 | (*q).next = (*p).next 76 | } 77 | else { 78 | (*q).next = p 79 | } 80 | // set the free unit pointer 81 | heap.free_unit = q 82 | } 83 | 84 | def moreCore(len: usize): Header var* { 85 | // get size of increased headers 86 | let unit_size = LEAST_ALLOC * ((len + LEAST_ALLOC - 1 as usize) / 87 | LEAST_ALLOC) 88 | // allocate more headers 89 | let core_ptr = increaseHeap(unit_size * sizeof Header) 90 | if core_ptr == null as u8 var* { 91 | null as Header var* 92 | } 93 | else { 94 | // initialize new header 95 | let unit_ptr = core_ptr as Header var* 96 | (*unit_ptr).size = unit_size 97 | freeMem((unit_ptr + 1) as u8 var*) 98 | heap.free_unit 99 | } 100 | } 101 | 102 | def allocMem(size: usize): u8 var* { 103 | let unit_size = ((size + sizeof Header - 1 as usize) / 104 | sizeof Header) + 1 as usize 105 | var ret = null as u8 var* 106 | // check if no free blocks 107 | var prev = heap.free_unit 108 | if prev == null as Header var* { 109 | prev = &heap.base 110 | heap.free_unit = prev 111 | heap.base.next = heap.free_unit 112 | heap.base.size = 0 as usize 113 | } 114 | // allocate free block 115 | var p = (*prev).next 116 | while true { 117 | // big enough 118 | if (*p).size >= unit_size { 119 | if (*p).size == unit_size { 120 | // exactly 121 | (*prev).next = (*p).next 122 | } 123 | else { 124 | // allocate tail end 125 | (*p).size -= unit_size 126 | p += (*p).size 127 | (*p).size = unit_size 128 | } 129 | heap.free_unit = prev 130 | ret = (p + 1) as u8 var* 131 | break 132 | } 133 | if p == heap.free_unit { 134 | p = moreCore(unit_size) 135 | if p == null as Header var* { 136 | // no spare space 137 | ret = null as u8 var* 138 | break 139 | } 140 | } 141 | prev = p 142 | p = (*p).next 143 | } 144 | ret 145 | } 146 | 147 | // allocate new heap memory 148 | def allocHeapMem(size: usize): u8 var* { 149 | heap.lock.acquire() 150 | let ret = allocMem(size) 151 | heap.lock.release() 152 | ret 153 | } 154 | 155 | // free allocated heap memory 156 | def freeHeapMem(ptr: u8 var*) { 157 | heap.lock.acquire() 158 | freeMem(ptr) 159 | heap.lock.release() 160 | } 161 | 162 | // initialize user heap 163 | public def initUserHeap() { 164 | heap.lock = newSpinlock() 165 | heap.heap_mem = heap_mem as u8 var* 166 | heap.heap_base = heap.heap_mem 167 | } 168 | 169 | // allocate memory 170 | public def alloc(this: HeapAllocator&, size: usize): u8 var* { 171 | allocHeapMem(size) 172 | } 173 | 174 | // deallocate memory 175 | public def dealloc(this: HeapAllocator&, ptr: u8 var*) { 176 | freeHeapMem(ptr) 177 | } 178 | -------------------------------------------------------------------------------- /src/syscall/fs.yu: -------------------------------------------------------------------------------- 1 | import proc.proc 2 | import fs.fs 3 | 4 | // file type 5 | public enum FileType { 6 | File, Dir, CharDevice, BlockDevice, 7 | } 8 | 9 | // file status 10 | public struct Stat { 11 | ino: u32, 12 | file_type: FileType, 13 | size: usize, 14 | blk_size: usize, 15 | blocks: usize, 16 | } 17 | 18 | def convToFileType(this: INodeType): FileType { 19 | when this { 20 | INodeType.File { FileType.File } 21 | INodeType.Dir { FileType.Dir } 22 | INodeType.CharDevice { FileType.CharDevice } 23 | else { FileType.BlockDevice } 24 | } 25 | } 26 | 27 | def convToStat(this: Metadata&): Stat { 28 | [Stat] { 29 | this.id, 30 | this.itype.convToFileType(), 31 | this.size, 32 | this.blk_size as usize, 33 | this.blocks as usize, 34 | } 35 | } 36 | 37 | // NOTE: current implementation will not sync offset between two fds 38 | public def sysDup3(oldfd: i32, newfd: i32, flags: i32): isize { 39 | let fds: FileInfo var& = getCurrentThread().getFds() 40 | fds.lock() 41 | // get file1 42 | let file1 = fds.getFile(oldfd) 43 | if file1 == null as File var* || !(*file1).isOpened() { 44 | fds.unlock() 45 | return -1 as isize 46 | } 47 | // initialize file2 48 | var fd2 = newfd, file2 = fds.getFile(newfd) 49 | if file2 == null as File var* { 50 | // allocate new file 51 | if !fds.allocFd(fd2) { 52 | fds.unlock() 53 | return -1 as isize 54 | } 55 | file2 = fds.getFile(fd2) 56 | } 57 | else if (*file2).isOpened() { 58 | // close file2 if it is opened 59 | (*file2).close() 60 | } 61 | // duplicate 62 | (*file2).replaceWith(*file1) 63 | fds.unlock() 64 | fd2 as isize 65 | } 66 | 67 | public def sysChdir(path: u8*): isize { 68 | let fds: FileInfo var& = getCurrentThread().getFds() 69 | var ret = -1 70 | fds.lock() 71 | // get target inode 72 | let path = newStrView(path) 73 | let inode = fds.getCwd().lookup(path) 74 | let meta = inode.getMetadata() 75 | if inode != null as INode var* && meta.itype == INodeType.Dir { 76 | // set inode as cwd 77 | fds.setCwd(inode) 78 | ret = 0 79 | } 80 | fds.unlock() 81 | ret as isize 82 | } 83 | 84 | public def sysOpen(path: u8*, flags: i32): isize { 85 | let fds: FileInfo var& = getCurrentThread().getFds() 86 | var fd = -1 87 | fds.lock() 88 | // lookup the specific file 89 | let path = newStrView(path) 90 | let inode = fds.getCwd().lookup(path) 91 | if inode != null as INode var* { 92 | // allocate a new file descriptor & open file 93 | !fds.allocFd(fd) || (*fds.getFile(fd)).open(inode, flags) < 0 94 | } 95 | fds.unlock() 96 | fd as isize 97 | } 98 | 99 | public def sysClose(fd: i32): isize { 100 | let fds: FileInfo var& = getCurrentThread().getFds() 101 | var ret = -1 102 | fds.lock() 103 | // get file by descriptor 104 | let file = fds.getFile(fd) 105 | if file != null as File var* { 106 | // close file & deallocate file descriptor 107 | ret = (*file).close() 108 | if ret >= 0 { fds.deallocFd(fd) } 109 | } 110 | fds.unlock() 111 | ret as isize 112 | } 113 | 114 | public def sysLseek(fd: i32, offset: usize, whence: i32): usize { 115 | let fds: FileInfo var& = getCurrentThread().getFds() 116 | var ret = -1 as usize 117 | fds.lock() 118 | // get file by descriptor 119 | let file = fds.getFile(fd) 120 | if file != null as File var* { 121 | // seek offset 122 | ret = (*file).seek(offset, whence) 123 | } 124 | fds.unlock() 125 | ret 126 | } 127 | 128 | public def sysRead(fd: i32, buf: u8 var*, len: usize): isize { 129 | let fds: FileInfo var& = getCurrentThread().getFds() 130 | var ret = -1 131 | fds.lock() 132 | // get file by descriptor 133 | let file = fds.getFile(fd) 134 | if file != null as File var* { 135 | // read file 136 | ret = (*file).read(buf, len) 137 | } 138 | fds.unlock() 139 | ret as isize 140 | } 141 | 142 | public def sysWrite(fd: i32, buf: u8*, len: usize): isize { 143 | let fds: FileInfo var& = getCurrentThread().getFds() 144 | var ret = -1 145 | fds.lock() 146 | // get file by descriptor 147 | let file = fds.getFile(fd) 148 | if file != null as File var* { 149 | // write file 150 | ret = (*file).write(buf, len) 151 | } 152 | fds.unlock() 153 | ret as isize 154 | } 155 | 156 | public def sysFstat(fd: i32, buf: Stat var*): isize { 157 | let fds: FileInfo var& = getCurrentThread().getFds() 158 | var ret = -1 159 | fds.lock() 160 | // get file by descriptor 161 | let file = fds.getFile(fd) 162 | if file != null as File var* { 163 | // get metadata 164 | var meta: Metadata 165 | if (*file).getMetadata(meta) { 166 | // fill file status info 167 | (*buf) = meta.convToStat() 168 | ret = 0 169 | } 170 | } 171 | fds.unlock() 172 | ret as isize 173 | } 174 | 175 | public def sysSync(): isize { 176 | let fds: FileInfo var& = getCurrentThread().getFds() 177 | fds.lock() 178 | let ret = if fds.getCwd().getFs().sync() { 0 } else { -1 } 179 | fds.unlock() 180 | ret as isize 181 | } 182 | -------------------------------------------------------------------------------- /src/fs/vfs/vfs.yu: -------------------------------------------------------------------------------- 1 | public import sync.spinlock 2 | public import arch.arch 3 | public import lib.strview 4 | 5 | import lib.except 6 | import lib.alloc 7 | 8 | // type of inode 9 | public enum INodeType { 10 | File, Dir, CharDevice, BlockDevice, 11 | } 12 | 13 | // metadata of inode 14 | public struct Metadata { 15 | id: u32, 16 | itype: INodeType, 17 | size: usize, 18 | blk_size: u32, 19 | blocks: u32, 20 | } 21 | 22 | // inode interface (base struct) 23 | // object of this structure must be allocated on heap 24 | public struct INode { 25 | // lock for reference counter 26 | lock: Spinlock, 27 | ref_count: i32, 28 | ops: u8*, 29 | } 30 | 31 | // filesystem interface 32 | // object of this structure must be allocated on heap 33 | public struct FileSystem { 34 | ops: u8*, 35 | } 36 | 37 | // operations on inode 38 | public struct INodeOps { 39 | op_get_fs: (INode var*): FileSystem var*, 40 | op_get_metadata: (INode var*): Metadata, 41 | op_find: (INode var*, StrView&): INode var*, 42 | op_read: (INode var*, u8 var*, usize, usize): i32, 43 | op_write: (INode var*, u8*, usize, usize): i32, 44 | op_cleanup: (INode var*), 45 | } 46 | 47 | // operations on filesystem 48 | public struct FileSystemOps { 49 | op_get_root: (FileSystem var*): INode var*, 50 | op_sync: (FileSystem var*): bool, 51 | op_cleanup: (FileSystem var*), 52 | } 53 | 54 | 55 | /* 56 | * inode related stuffs 57 | */ 58 | // initialize status of inode 59 | public def init(this: INode var*, ops: INodeOps*) { 60 | (*this).lock = newSpinlock() 61 | (*this).ref_count = 0 62 | (*this).ops = ops as u8* 63 | } 64 | 65 | // delete current inode 66 | public def del(this: INode var*) { 67 | assert((*this).ref_count == 0, "INode.del") 68 | let ops = (*this).ops as INodeOps* 69 | ((*ops).op_cleanup)(this) 70 | heap.dealloc(this as u8 var*) 71 | } 72 | 73 | // get virtual filesystem 74 | inline def getFs(this: INode var*): FileSystem var* { 75 | let ops = (*this).ops as INodeOps* 76 | ((*ops).op_get_fs)(this) 77 | } 78 | 79 | // get metadata of current inode 80 | inline def getMetadata(this: INode var*): Metadata { 81 | let ops = (*this).ops as INodeOps* 82 | ((*ops).op_get_metadata)(this) 83 | } 84 | 85 | // find inode in current inode 86 | // `name` will not contains '/' 87 | inline def find(this: INode var*, name: StrView&): INode var* { 88 | let ops = (*this).ops as INodeOps* 89 | ((*ops).op_find)(this, name) 90 | } 91 | 92 | // read the content of current inode to buffer 93 | inline def read(this: INode var*, buf: u8 var*, len: usize, 94 | offset: usize): i32 { 95 | let ops = (*this).ops as INodeOps* 96 | ((*ops).op_read)(this, buf, len, offset) 97 | } 98 | 99 | // write the content of buffer to current inode 100 | inline def write(this: INode var*, buf: u8*, len: usize, 101 | offset: usize): i32 { 102 | let ops = (*this).ops as INodeOps* 103 | ((*ops).op_write)(this, buf, len, offset) 104 | } 105 | 106 | 107 | /* 108 | * virtual filesystem related stuffs 109 | */ 110 | // initialize virtual filesystem 111 | public def init(this: FileSystem var*, ops: FileSystemOps*) { 112 | (*this).ops = ops as u8* 113 | } 114 | 115 | // delete virtual filesystem 116 | public def del(this: FileSystem var*) { 117 | let ops = (*this).ops as FileSystemOps* 118 | ((*ops).op_cleanup)(this) 119 | heap.dealloc(this as u8 var*) 120 | } 121 | 122 | // get root inode of filesystem 123 | inline def getRoot(this: FileSystem var*): INode var* { 124 | let ops = (*this).ops as FileSystemOps* 125 | ((*ops).op_get_root)(this) 126 | } 127 | 128 | // commit filesystem caches to disk 129 | inline def sync(this: FileSystem var*): bool { 130 | let ops = (*this).ops as FileSystemOps* 131 | ((*ops).op_sync)(this) 132 | } 133 | 134 | 135 | /* 136 | * other extended operations for inode 137 | */ 138 | // open file 139 | public def open(this: INode var*) { 140 | (*this).lock.acquire() 141 | (*this).ref_count += 1 142 | (*this).lock.release() 143 | } 144 | 145 | // close file 146 | public def close(this: INode var*) { 147 | assert((*this).ref_count > 0, "INode.close") 148 | (*this).lock.acquire() 149 | (*this).ref_count -= 1 150 | (*this).lock.release() 151 | if (*this).ref_count == 0 { 152 | this.del() 153 | } 154 | } 155 | 156 | // lookup file and return inode 157 | public def lookup(this: INode var*, path: StrView&): INode var* { 158 | // check if is valid 159 | let meta = this.getMetadata() 160 | if meta.itype != INodeType.Dir || path.empty() { 161 | return null as INode var* 162 | } 163 | // get trimmed path 164 | var path = path 165 | if path.getLen() > 1 as usize && path.back() == '/' { 166 | path.removeSuffix(1) 167 | } 168 | // get current inode 169 | var cur = if path.front() == '/' { 170 | path.removePrefix(1) 171 | this.getFs().getRoot() 172 | } 173 | else { 174 | this 175 | } 176 | // get target inode 177 | for name in path.split('/') { 178 | let next = cur.find(name) 179 | if next != null as INode var* { 180 | cur = next 181 | } 182 | else { 183 | return null as INode var* 184 | } 185 | } 186 | cur 187 | } 188 | -------------------------------------------------------------------------------- /src/lib/elf.yu: -------------------------------------------------------------------------------- 1 | // magic number of ELF 2 | inline let ELF_MAGIC = 0x464c457f as u32 3 | 4 | // e_machine definitions 5 | inline let ELF_EM_386 = 0x03 as u16 // Intel x86 6 | inline let ELF_EM_MIPS = 0x08 as u16 // MIPS 7 | inline let ELF_EM_RISCV = 0xf3 as u16 // RISC-V 8 | 9 | // p_flags definitions 10 | inline let ELF_PF_EXEC = (1 << 0) as u32 11 | inline let ELF_PF_WRITE = (1 << 1) as u32 12 | inline let ELF_PF_READ = (1 << 2) as u32 13 | 14 | // ELF header 15 | public struct Elf32Ehdr { 16 | e_ident: u32[4], // Magic number and other info 17 | e_type: u16, // Object file type 18 | e_machine: u16, // Architecture 19 | e_version: u32, // Object file version 20 | e_entry: u32, // Entry point virtual address 21 | e_phoff: u32, // Program header table file offset 22 | e_shoff: u32, // Section header table file offset 23 | e_flags: u32, // Processor-specific flags 24 | e_ehsize: u16, // ELF header size in bytes 25 | e_phentsize: u16, // Program header table entry size 26 | e_phnum: u16, // Program header table entry count 27 | e_shentsize: u16, // Section header table entry size 28 | e_shnum: u16, // Section header table entry count 29 | e_shstrndx: u16, // Section header string table index 30 | } 31 | 32 | // program header 33 | public struct Elf32Phdr { 34 | p_type: u32, 35 | p_offset: u32, // Offset from beginning of file 36 | p_vaddr: u32, 37 | p_paddr: u32, 38 | p_filesz: u32, // Bytes of segment in file 39 | p_memsz: u32, // Bytes of segment in memory 40 | p_flags: u32, 41 | p_align: u32, 42 | } 43 | 44 | // ELF header type 45 | public enum ElfEtype: u16 { 46 | None, Relocatable, Executable, SharedObj, Core, 47 | } 48 | 49 | // ELF program header type 50 | public enum ElfPtype: u32 { 51 | Load = 1 as u32, 52 | } 53 | 54 | // check if current header is a valid RISC-V ELF file header 55 | inline def isValid(this: Elf32Ehdr*): bool { 56 | (*this).e_ident[0] == ELF_MAGIC && (*this).e_machine == ELF_EM_RISCV 57 | } 58 | 59 | // get type of ELF file 60 | inline def getType(this: Elf32Ehdr*): ElfEtype { 61 | (*this).e_type as ElfEtype 62 | } 63 | 64 | // get entry of ELF file 65 | inline def getEntry(this: Elf32Ehdr*): u32 { 66 | (*this).e_entry 67 | } 68 | 69 | // get address of first program header 70 | inline def getPhdr(this: Elf32Ehdr*): Elf32Phdr* { 71 | (this as u32 + (*this).e_phoff) as Elf32Phdr* 72 | } 73 | 74 | // get address of last program header 75 | inline def getLastPhdr(this: Elf32Ehdr*): Elf32Phdr* { 76 | let phdr = this.getPhdr() 77 | (phdr as u32 + ((*this).e_phentsize * 78 | (*this).e_phnum) as u32) as Elf32Phdr* 79 | } 80 | 81 | // get type of current program header 82 | inline def getType(this: Elf32Phdr*): ElfPtype { 83 | (*this).p_type as ElfPtype 84 | } 85 | 86 | // get offset of current program header 87 | inline def getOffset(this: Elf32Phdr*): u32 { 88 | (*this).p_offset 89 | } 90 | 91 | // get virtual address of current program header 92 | inline def getVaddr(this: Elf32Phdr*): u32 { 93 | (*this).p_vaddr 94 | } 95 | 96 | // get physical address of current program header 97 | inline def getPaddr(this: Elf32Phdr*): u32 { 98 | (*this).p_paddr 99 | } 100 | 101 | // get file size of current program header 102 | inline def getFileSize(this: Elf32Phdr*): u32 { 103 | (*this).p_filesz 104 | } 105 | 106 | // get memory size of current program header 107 | inline def getMemSize(this: Elf32Phdr*): u32 { 108 | (*this).p_memsz 109 | } 110 | 111 | // check if current program header is executable 112 | inline def isExecute(this: Elf32Phdr*): bool { 113 | ((*this).p_flags & ELF_PF_EXEC) != 0 as u32 114 | } 115 | 116 | // check if current program header is writable 117 | inline def isWrite(this: Elf32Phdr*): bool { 118 | ((*this).p_flags & ELF_PF_WRITE) != 0 as u32 119 | } 120 | 121 | // check if current program header is readable 122 | inline def isRead(this: Elf32Phdr*): bool { 123 | ((*this).p_flags & ELF_PF_READ) != 0 as u32 124 | } 125 | 126 | // ELF file reader 127 | public struct ElfFile { 128 | ehdr: Elf32Ehdr*, 129 | } 130 | 131 | // iterator for ELF program headers 132 | public struct ElfPhdrIter { 133 | cur: Elf32Phdr*, 134 | end: Elf32Phdr*, 135 | } 136 | 137 | inline def newElfFile(data: u8*): ElfFile { 138 | [ElfFile] {data as Elf32Ehdr*} 139 | } 140 | 141 | // get ELF header 142 | inline def getEhdr(this: ElfFile&): Elf32Ehdr* { 143 | this.ehdr 144 | } 145 | 146 | // get program header 147 | inline def getPhdr(this: ElfFile&): Elf32Phdr* { 148 | this.ehdr.getPhdr() 149 | } 150 | 151 | // get data of specific program header 152 | inline def getPhdrData(this: ElfFile&, phdr: Elf32Phdr*): u8* { 153 | this.ehdr as u8* + phdr.getOffset() 154 | } 155 | 156 | // get program header iter 157 | inline def getPhdrIter(this: ElfFile&): ElfPhdrIter { 158 | [ElfPhdrIter] {this.getPhdr(), this.ehdr.getLastPhdr()} 159 | } 160 | 161 | // iterator method 'next' 162 | inline def next(this: ElfPhdrIter var&): Elf32Phdr* { 163 | let cur = this.cur 164 | this.cur += 1 165 | cur 166 | } 167 | 168 | // iterator method 'last' 169 | inline def last(this: ElfPhdrIter&): bool { 170 | this.cur >= this.end 171 | } 172 | -------------------------------------------------------------------------------- /mkfs/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "geefs.h" 8 | #include "iosdev.h" 9 | 10 | using namespace std; 11 | 12 | namespace { 13 | 14 | void PrintHelp() { 15 | cout << "mkfs utility for GeeFS, by MaxXing" << endl; 16 | cout << "usage: mkfs [-h] image [-i]" << endl; 17 | cout << " [-c blk_size free_map_num inode_blk_num]" << endl; 18 | cout << " [-a file ...]" << endl << endl; 19 | cout << "options:" << endl; 20 | cout << " -h display this message" << endl; 21 | cout << " -i interactive mode" << endl; 22 | cout << " -c create a new GeeFS image" << endl; 23 | cout << " -a add files to current image" << endl; 24 | } 25 | 26 | int LogError(string_view msg) { 27 | cerr << msg << endl; 28 | return 1; 29 | } 30 | 31 | IOStreamDevice GetDeviceFromFile(fstream &fs, string_view file_name) { 32 | fs.open(file_name, ios::binary | ios::in | ios::out); 33 | if (!fs.is_open()) { 34 | fs.clear(); 35 | fs.open(file_name, ios::out); 36 | fs.close(); 37 | fs.open(file_name, ios::binary | ios::in | ios::out); 38 | } 39 | return IOStreamDevice(fs); 40 | } 41 | 42 | size_t GetStreamSize(istream &is) { 43 | auto pos = is.tellg(); 44 | is.seekg(0, ios::end); 45 | auto size = is.tellg() - pos; 46 | is.seekg(0); 47 | return size; 48 | } 49 | 50 | bool GetInteger(const char *str, uint32_t &num) { 51 | istringstream iss(str); 52 | iss >> num; 53 | return !!iss; 54 | } 55 | 56 | string_view GetFileName(string_view path) { 57 | auto pos = path.find_last_of('/'); 58 | if (pos == string_view::npos) return path; 59 | return path.substr(pos + 1); 60 | } 61 | 62 | int EnterIMode(GeeFS &geefs) { 63 | string line; 64 | // print prompt 65 | cout << geefs.cur_path() << "> "; 66 | while (getline(cin, line)) { 67 | if (!line.empty()) { 68 | // parse input 69 | if (line == "ls") { 70 | geefs.List(cout); 71 | } 72 | else if (line == "quit") { 73 | return 0; 74 | } 75 | else if (line.substr(0, 6) == "create") { 76 | if (!geefs.CreateFile(line.substr(7))) { 77 | LogError("failed to create file"); 78 | } 79 | } 80 | else if (line.substr(0, 5) == "mkdir") { 81 | if (!geefs.MakeDir(line.substr(6))) { 82 | LogError("failed to create directory"); 83 | } 84 | } 85 | else if (line.substr(0, 2) == "cd") { 86 | if (!geefs.ChangeDir(line.substr(3))) { 87 | LogError("failed to change directory"); 88 | } 89 | } 90 | else if (line.substr(0, 2) == "rm") { 91 | if (!geefs.Remove(line.substr(3))) { 92 | LogError("failed to remove file"); 93 | } 94 | } 95 | else if (line.substr(0, 4) == "read") { 96 | geefs.Read(line.substr(5), cout, 0, -1); 97 | } 98 | else { 99 | LogError("unknown command"); 100 | } 101 | } 102 | // print next prompt 103 | cout << geefs.cur_path() << "> "; 104 | } 105 | cout << endl; 106 | return 0; 107 | } 108 | 109 | } // namespace 110 | 111 | int main(int argc, const char *argv[]) { 112 | // print help message 113 | if (argc < 2 || argv[1] == "-h"sv) { 114 | PrintHelp(); 115 | return argc < 2; 116 | } 117 | 118 | // create GeeFS object 119 | auto fs = fstream(); 120 | auto dev = GetDeviceFromFile(fs, argv[1]); 121 | auto geefs = GeeFS(dev); 122 | 123 | // read arguments 124 | bool imode = false, opened = false; 125 | for (int i = 2; i < argc; ++i) { 126 | if (argv[i][0] == '-') { 127 | switch (argv[i][1]) { 128 | case 'i': { 129 | imode = true; 130 | break; 131 | } 132 | case 'c': { 133 | // read arguments 134 | uint32_t blk_size, free_map_num, inode_blk_num; 135 | if (argc - i - 1 < 3) return LogError("insufficient argument"); 136 | if (!GetInteger(argv[++i], blk_size) || 137 | !GetInteger(argv[++i], free_map_num) || 138 | !GetInteger(argv[++i], inode_blk_num)) { 139 | return LogError("invalid argument"); 140 | } 141 | opened = true; 142 | if (!geefs.Create(blk_size, free_map_num, inode_blk_num)) { 143 | return LogError("can not create image"); 144 | } 145 | break; 146 | } 147 | case 'a': { 148 | // open image 149 | if (!opened && !geefs.Open()) { 150 | return LogError("can not open image"); 151 | } 152 | opened = true; 153 | // add files 154 | ++i; 155 | while (i < argc && argv[i][0] != '-') { 156 | auto file = GetFileName(argv[i]); 157 | // create file 158 | if (!geefs.CreateFile(file)) { 159 | return LogError("can not create file in image"); 160 | } 161 | // create file stream 162 | ifstream ifs(argv[i]); 163 | auto size = GetStreamSize(ifs); 164 | if (!ifs || !geefs.Write(file, ifs, 0, size)) { 165 | return LogError("can not write file in image"); 166 | } 167 | ++i; 168 | } 169 | break; 170 | } 171 | } 172 | } 173 | } 174 | 175 | // enter interactive mode 176 | if (imode) { 177 | if (!opened && !geefs.Open()) return LogError("can not open image"); 178 | return EnterIMode(geefs); 179 | } 180 | return 0; 181 | } 182 | -------------------------------------------------------------------------------- /src/arch/riscv/recursive.yu: -------------------------------------------------------------------------------- 1 | public import arch.arch 2 | public import arch.riscv.pagetable 3 | public import arch.riscv.addr 4 | public import arch.riscv.framealloc 5 | 6 | import lib.except 7 | 8 | // helper struct for mappers (like `RecursivePageTable`) 9 | public struct MapperFlush { 10 | page: Page, 11 | } 12 | 13 | def newMapperFlush(page: Page): MapperFlush { 14 | [MapperFlush] {page} 15 | } 16 | 17 | // flush current page 18 | inline def flush(this: MapperFlush) { 19 | let addr = this.page.getAddr() 20 | runSfence(0 as usize, addr.getAddr()) 21 | } 22 | 23 | // helper struct for `RecursivePageTable` 24 | public struct TempMap { 25 | entry: PageTableEntry var*, 26 | pt_addr: VirtAddr, 27 | } 28 | 29 | def newTempMap(index: usize): TempMap { 30 | let va = newVirtAddr(index, index + 1 as usize, 31 | (index + 2 as usize) * 4 as usize) 32 | [TempMap] { 33 | va.getAddr() as PageTableEntry var*, 34 | newVirtAddr(index, index + 2 as usize, 0 as usize), 35 | } 36 | } 37 | 38 | def map(this: TempMap var&, frame: Frame): PageTable var* { 39 | (*this.entry).set(frame, PTE_FLAG_V | PTE_FLAG_R | PTE_FLAG_W) 40 | runSfence(0 as usize, this.pt_addr.getAddr()) 41 | this.pt_addr.getAddr() as PageTable var* 42 | } 43 | 44 | // a recursive page table is a last level page table 45 | // with an entry mapped to the table itself 46 | public struct RecursivePageTable { 47 | root_table: PageTable var*, 48 | // Recursive index as `R` 49 | // `R`: point to root frame, flags: V 50 | // `R+1`: point to root frame, flags: V+R+W 51 | // `R+2`: point to temp frame, flags: V+R+W 52 | // At any time, we can access root page table through (R, R, ..., R+1, 0) 53 | // To access a temp frame, 54 | // first set `root[R+2]` pointing to the frame with R+W+X, 55 | // then we can access the frame through (R, R, ..., R+2, 0). 56 | index: usize, 57 | temp_map: TempMap, 58 | } 59 | 60 | def createP1IfNotExist(this: RecursivePageTable var&, vpn1: usize, 61 | alloc: FrameAllocator var*): PageTable var* { 62 | assert(vpn1 < this.index || vpn1 > this.index + 2 as usize, 63 | "invalid vpn1") 64 | let pte: PageTableEntry var& = this.root_table.getPte(vpn1) 65 | if pte.isUnused() { 66 | let frame = alloc.alloc() 67 | pte.set(frame, PTE_FLAG_V) 68 | let p1_table = this.temp_map.map(frame) 69 | p1_table.zero() 70 | p1_table 71 | } 72 | else { 73 | let frame = pte.getFrame() 74 | let p1_table = this.temp_map.map(frame) 75 | p1_table 76 | } 77 | } 78 | 79 | // Creates a new RecursivePageTable from the passed level 2 PageTable. 80 | // The page table must be recursively mapped, that means: 81 | // - The page table must have one recursive entry, 82 | // i.e. an entry that points to the table itself. 83 | // - The page table must be active, 84 | // i.e. the satp register must contain its physical address. 85 | public def newRecursivePageTable(pt: PageTable var*): RecursivePageTable { 86 | let page = newPage(newVirtAddr(pt as usize)) 87 | let index = page.getVpn1() 88 | // check if is valid 89 | let satp_frame = newFrameFromSatp() 90 | if page.getVpn0() != index + 1 as usize || 91 | satp_frame != pt.getPte(index).getFrame() || 92 | satp_frame != pt.getPte(index + 1 as usize).getFrame() || 93 | !(pt.getPte(index).getFlags() & PTE_FLAG_V) || 94 | !(!(pt.getPte(index).getFlags() & (PTE_FLAG_R | PTE_FLAG_W))) || 95 | !(pt.getPte(index + 1 as usize).getFlags() & 96 | (PTE_FLAG_V | PTE_FLAG_R | PTE_FLAG_W)) { 97 | panic("newRecursivePageTable") 98 | } 99 | // create page table 100 | [RecursivePageTable] {pt, index, newTempMap(index)} 101 | } 102 | 103 | // Creates a new RecursivePageTable without performing any checks. 104 | // The `index` parameter must be the index of the recursively mapped entry. 105 | public def newUncheckedRecursivePageTable( 106 | pt: PageTable var*, index: usize): RecursivePageTable { 107 | [RecursivePageTable] {pt, index, newTempMap(index)} 108 | } 109 | 110 | // Creates a new mapping in the page table. 111 | // This function might need additional physical frames to 112 | // create new page tables. These frames are allocated from 113 | // the `alloc` argument. At most three frames are required. 114 | public def mapTo(this: RecursivePageTable var&, page: Page, frame: Frame, 115 | flags: usize, alloc: FrameAllocator var*): MapperFlush { 116 | let p1_table = this.createP1IfNotExist(page.getVpn1(), alloc) 117 | if !p1_table.getPte(page.getVpn0()).isUnused() { 118 | panic("mapTo - page already mapped") 119 | } 120 | p1_table.getPte(page.getVpn0()).set(frame, flags) 121 | newMapperFlush(page) 122 | } 123 | 124 | // Removes a mapping from the page table, 125 | // set `frame` to the frame that used to be mapped. 126 | // Note that no page tables or pages are deallocated. 127 | public def unmap(this: RecursivePageTable var&, page: Page, 128 | frame: Frame var&): MapperFlush { 129 | if this.root_table.getPte(page.getVpn1()).isUnused() { 130 | panic("unmap - page not mapped") 131 | } 132 | let p1_frame = this.root_table.getPte(page.getVpn1()).getFrame() 133 | let p1_table = this.temp_map.map(p1_frame) 134 | let p1_entry: PageTableEntry var& = p1_table.getPte(page.getVpn0()) 135 | if !(p1_entry.getFlags() & PTE_FLAG_V) { 136 | panic("unmap - page not mapped") 137 | } 138 | frame = p1_entry.getFrame() 139 | p1_entry.setUnused() 140 | newMapperFlush(page) 141 | } 142 | 143 | // Get the pointer of the specified `page` entry. 144 | // Returns null if not mapped. 145 | public def getPtePtr(this: RecursivePageTable var&, 146 | page: Page): PageTableEntry var* { 147 | if this.root_table.getPte(page.getVpn1()).isUnused() { 148 | return null as PageTableEntry var* 149 | } 150 | let p1_frame = this.root_table.getPte(page.getVpn1()).getFrame() 151 | let p1_table = this.temp_map.map(p1_frame) 152 | &p1_table.getPte(page.getVpn0()) 153 | } 154 | -------------------------------------------------------------------------------- /src/proc/structs.yu: -------------------------------------------------------------------------------- 1 | public import define.context 2 | public import mem.mset 3 | public import fs.info 4 | public import proc.consts 5 | 6 | import arch.arch 7 | import arch.riscv.consts 8 | import arch.riscv.csr 9 | import mem.pm 10 | import mem.attr 11 | import mem.handler 12 | import lib.except 13 | import lib.alloc 14 | import lib.elf 15 | import lib.c.string 16 | import proc.consts 17 | 18 | // size of kernel stack 19 | // must be page size because stack is allocated by frame allocator 20 | let STACK_SIZE = PAGE_SIZE as usize 21 | 22 | // definition of kernel thread stack 23 | public struct KernelStack { 24 | addr: u8 var*, 25 | } 26 | 27 | // create new kernel stack 28 | public def newKernelStack(): KernelStack { 29 | let addr = allocPhyMem() 30 | assert(addr as i32 != 0, "newKernelStack - OOM") 31 | [KernelStack] {addr} 32 | } 33 | 34 | // release kernel stack 35 | public def del(this: KernelStack var&) { 36 | if this.addr as i32 != 0 { 37 | freePhyMem(this.addr) 38 | } 39 | this.addr = null as u8 var* 40 | } 41 | 42 | // get top of kernel stack 43 | public def getTop(this: KernelStack&): usize { 44 | this.addr as usize + STACK_SIZE 45 | } 46 | 47 | 48 | // definition of process 49 | public struct Process { 50 | vm: MemorySet, 51 | fds: FileInfo, 52 | thread_count: i32, 53 | } 54 | 55 | def newProcess(vm: MemorySet): Process var* { 56 | let proc = heap.alloc(sizeof Process) as Process var* 57 | (*proc).vm = vm 58 | (*proc).fds.init() 59 | (*proc).thread_count = 0 60 | proc 61 | } 62 | 63 | def del(this: Process var*) { 64 | assert((*this).thread_count == 0, "Process.del") 65 | (*this).vm.del() 66 | (*this).fds.del() 67 | heap.dealloc(this as u8 var*) 68 | } 69 | 70 | def incCounter(this: Process var*): Process var* { 71 | (*this).thread_count += 1 72 | this 73 | } 74 | 75 | def decCounter(this: Process var*): Process var* { 76 | (*this).thread_count -= 1 77 | if (*this).thread_count < 0 { 78 | panic("decCounter") 79 | } 80 | else if (*this).thread_count == 0 { 81 | this.del() 82 | } 83 | this 84 | } 85 | 86 | def getFds(this: Process var*): FileInfo var& { 87 | (*this).fds 88 | } 89 | 90 | 91 | // definition of thread 92 | // objects of this structure will always be allocated on the heap 93 | public struct Thread { 94 | context: Context, 95 | kstack: KernelStack, 96 | proc: Process var*, 97 | wait: Tid, 98 | } 99 | 100 | // create memory attribute by ELF program header 101 | def toAttr(this: Elf32Phdr*): MemoryAttr { 102 | var attr = newMemoryAttr().setUser() 103 | if this.isExecute() { 104 | attr = attr.setExecute() 105 | } 106 | attr 107 | } 108 | 109 | // create memory set by ELF file 110 | def makeMemorySet(this: ElfFile&): MemorySet { 111 | // create a new memory set with kernel mapping 112 | var ms = newMemorySet() 113 | // traverse program headers in ELF file 114 | let handler = newFrameHandler() 115 | for ph in this.getPhdrIter() { 116 | if ph.getType() != ElfPtype.Load { 117 | continue 118 | } 119 | // get address info 120 | let virt_addr = ph.getVaddr(), mem_size = ph.getMemSize() 121 | // get data of program header 122 | let data = this.getPhdrData(ph), data_len = ph.getFileSize() 123 | // get slice of target virtual address 124 | let target = { 125 | ms.push(virt_addr as usize, (virt_addr + mem_size) as usize, 126 | ph.toAttr(), handler) 127 | virt_addr as u8 var* 128 | } 129 | // copy data to page 130 | for _ in ms.with() { 131 | memcpy(target, data, data_len as usize) 132 | memset(target + data_len, 0, (mem_size - data_len) as usize) 133 | } 134 | } 135 | ms 136 | } 137 | 138 | // create new idle thread 139 | public def newIdleThread(): Thread var* { 140 | let thread = heap.alloc(sizeof Thread) as Thread var* 141 | (*thread) = [Thread] { 142 | newNullContext(), 143 | newKernelStack(), 144 | null as Process var*, 145 | 0 as Tid, 146 | } 147 | thread 148 | } 149 | 150 | // create new kernel thread 151 | public def newKernelThread(entry: usize): Thread var* { 152 | let kstack = newKernelStack() 153 | let thread = heap.alloc(sizeof Thread) as Thread var* 154 | (*thread) = [Thread] { 155 | newKernelContext(entry, kstack.getTop(), getSatp()), 156 | kstack, 157 | null as Process var*, 158 | 0 as Tid, 159 | } 160 | thread 161 | } 162 | 163 | // create new user thread 164 | public def newUserThread(data: u8*, wait_thread: Tid): Thread var* { 165 | let elf = newElfFile(data), ehdr = elf.getEhdr() 166 | if !ehdr.isValid() || ehdr.getType() != ElfEtype.Executable { 167 | return null as Thread var* 168 | } 169 | // get entry point 170 | let entry = ehdr.getEntry() 171 | // create page table & user stack 172 | var ms = elf.makeMemorySet() 173 | let ustack_top = { 174 | let ustack_bottom = USER_STACK_OFFSET as usize 175 | let ustack_top = (USER_STACK_OFFSET + USER_STACK_SIZE) as usize 176 | let handler = newFrameHandler() 177 | ms.push(ustack_bottom, ustack_top, newMemoryAttr().setUser(), handler) 178 | ustack_top 179 | } 180 | // create process 181 | let proc = newProcess(ms).incCounter() 182 | proc.getFds().initStdFds() 183 | // create kernel stack & user thread 184 | let kstack = newKernelStack() 185 | let thread = heap.alloc(sizeof Thread) as Thread var* 186 | (*thread) = [Thread] { 187 | newUserContext(entry as usize, ustack_top, kstack.getTop(), ms.token()), 188 | kstack, 189 | proc, 190 | wait_thread, 191 | } 192 | thread 193 | } 194 | 195 | // release current thread structure 196 | public def del(this: Thread var*) { 197 | (*this).kstack.del() 198 | if (*this).proc != null as Process var* { 199 | (*this).proc.decCounter() 200 | } 201 | heap.dealloc(this as u8 var*) 202 | } 203 | 204 | // append initial arguments for current thread 205 | public def appendInitArgs(this: Thread*, args: usize[3]) { 206 | (*this).context.appendInitArgs(args) 207 | } 208 | 209 | // switch to target thread 210 | public def switchTo(this: Thread var*, target: Thread var*) { 211 | (*this).context.switchTo((*target).context) 212 | } 213 | 214 | // get file info 215 | public def getFds(this: Thread var*): FileInfo var& { 216 | assert((*this).proc != null as Process var*, "Thread.getFds") 217 | (*this).proc.getFds() 218 | } 219 | 220 | // get wait thread 221 | inline def getWait(this: Thread var*): Tid { 222 | (*this).wait 223 | } 224 | -------------------------------------------------------------------------------- /src/lib/hashmap.yu: -------------------------------------------------------------------------------- 1 | public import arch.arch 2 | 3 | import lib.alloc 4 | import lib.c.string 5 | 6 | // key-value pair 7 | public struct KeyValue { 8 | key: u32, 9 | value: u8 var*, 10 | next: KeyValue var*, 11 | } 12 | 13 | // table of pairs 14 | public struct KvPairTable { 15 | table: KeyValue var* var*, 16 | size: usize, 17 | // number of non-null elements in table 18 | occupied: usize, 19 | } 20 | 21 | // table iterator 22 | public struct KvPairTableIter { 23 | table: KvPairTable*, 24 | cur_index: usize, 25 | cur_pair: KeyValue var*, 26 | } 27 | 28 | // hash map (dynamic table size) 29 | public struct HashMap { 30 | table: KvPairTable, 31 | // size of hash map 32 | size: usize, 33 | // hash function 34 | hash_func: (HashMap var&, u32): usize, 35 | } 36 | 37 | // hash function 38 | public type HashFunc = (HashMap var&, u32): usize 39 | 40 | 41 | /* 42 | * key-value pair related implementations 43 | */ 44 | inline def key(this: KeyValue*): u32 { 45 | (*this).key 46 | } 47 | 48 | inline def value(this: KeyValue*): u8 var* { 49 | (*this).value 50 | } 51 | 52 | 53 | /* 54 | * iterator related implementations 55 | */ 56 | // set 'cur_index' and 'cur_pair' to next avaliable index 57 | // which means 'table[cur_index]' is not null 58 | def findNextIndex(this: KvPairTableIter var&) { 59 | while this.cur_index < (*this.table).size { 60 | if (*this.table).table[this.cur_index] != null as KeyValue var* { 61 | this.cur_pair = (*this.table).table[this.cur_index] 62 | break 63 | } 64 | this.cur_index += 1 as usize 65 | } 66 | } 67 | 68 | // iterator method 'next' 69 | public def next(this: KvPairTableIter var&): KeyValue var* { 70 | let cur = this.cur_pair 71 | // find next key-value pair 72 | this.cur_pair = (*this.cur_pair).next 73 | if this.cur_pair == null as KeyValue var* { 74 | this.cur_index += 1 as usize 75 | this.findNextIndex() 76 | } 77 | cur 78 | } 79 | 80 | // iterator method 'last' 81 | inline def last(this: KvPairTableIter&): bool { 82 | this.cur_index >= (*this.table).size 83 | } 84 | 85 | 86 | /* 87 | * table related implementations 88 | */ 89 | // internal constructor 90 | def newTable(size: usize): KvPairTable { 91 | let mem_size = size * (sizeof KeyValue var*) as usize 92 | let table = heap.alloc(mem_size) as KeyValue var* var* 93 | memset(table as u8 var*, 0, mem_size) 94 | [KvPairTable] {table, size, 0 as usize} 95 | } 96 | 97 | // internal destructor 98 | def del(this: KvPairTable var&) { 99 | var i = 0 100 | while i as usize < this.size { 101 | var cur = this.table[i] 102 | while cur != null as KeyValue var* { 103 | let next = (*cur).next 104 | heap.dealloc(cur as u8 var*) 105 | cur = next 106 | } 107 | i += 1 108 | } 109 | heap.dealloc(this.table as u8 var*) 110 | } 111 | 112 | // insert existing pair to table 113 | def insert(this: KvPairTable var&, index: usize, pair: KeyValue var*) { 114 | if this.table[index] == null as KeyValue var* { 115 | this.occupied += 1 as usize 116 | } 117 | (*pair).next = this.table[index] 118 | this.table[index] = pair 119 | } 120 | 121 | // insert key-value pair to table 122 | def insert(this: KvPairTable var&, index: usize, 123 | key: u32, value: u8 var*) { 124 | // allocate a new key-value pair 125 | let pair = heap.alloc((sizeof KeyValue) as usize) as KeyValue var* 126 | (*pair).key = key 127 | (*pair).value = value 128 | // insert to table 129 | this.insert(index, pair) 130 | } 131 | 132 | // get value by key 133 | def get(this: KvPairTable&, index: usize, key: u32): u8 var* { 134 | var cur = this.table[index] 135 | while cur != null as KeyValue var* { 136 | if (*cur).key == key { 137 | return (*cur).value 138 | } 139 | cur = (*cur).next 140 | } 141 | null as u8 var* 142 | } 143 | 144 | // remove value by key 145 | def remove(this: KvPairTable var&, index: usize, key: u32): bool { 146 | var cur = this.table[index], last = this.table + index 147 | while cur != null as KeyValue var* { 148 | if (*cur).key == key { 149 | (*last) = (*cur).next 150 | heap.dealloc(cur as u8 var*) 151 | if this.table[index] == null as KeyValue var* { 152 | this.occupied -= 1 as usize 153 | } 154 | return true 155 | } 156 | last = &((*cur).next) 157 | cur = (*cur).next 158 | } 159 | false 160 | } 161 | 162 | // get iterator 163 | def iter(this: KvPairTable&): KvPairTableIter { 164 | var iter = [KvPairTableIter] {&this, 0 as usize, null as KeyValue var*} 165 | iter.findNextIndex() 166 | iter 167 | } 168 | 169 | 170 | /* 171 | * hash map related implementations 172 | */ 173 | // default table size 174 | let TABLE_SIZE = 32 as usize 175 | 176 | // default hash function 177 | def defaultHasher(this: HashMap var&, key: u32): usize { 178 | (key as usize) * 0x9e3779b9 as usize 179 | } 180 | 181 | // call hash function and returns table index 182 | def hash(this: HashMap var&, key: u32): usize { 183 | (this.hash_func)(this, key) % this.table.size 184 | } 185 | 186 | // rehash when table is full, otherwise do nothing 187 | // returns true if rehash is performed 188 | def rehash(this: HashMap var&): bool { 189 | if this.table.occupied == this.table.size { 190 | // allocate new table 191 | let size = this.table.size * 2 as usize 192 | var table = newTable(size) 193 | // move key-value pairs to new table 194 | for kv in this.table.iter() { 195 | let hash_val = (this.hash_func)(this, kv.key()) % size 196 | table.insert(hash_val, kv) 197 | } 198 | // update table 199 | // NOTE: do not use 'del' method 200 | // because it will release all existing pairs 201 | heap.dealloc(this.table.table as u8 var*) 202 | this.table = table 203 | true 204 | } 205 | else { 206 | false 207 | } 208 | } 209 | 210 | // constructor (with parameters) 211 | public def newHashMap(hash_func: HashFunc): HashMap { 212 | [HashMap] {newTable(TABLE_SIZE), 0 as usize, hash_func} 213 | } 214 | 215 | // constructor 216 | public def newHashMap(): HashMap { 217 | newHashMap(defaultHasher) 218 | } 219 | 220 | // destructor 221 | public def del(this: HashMap var&) { 222 | this.table.del() 223 | } 224 | 225 | inline def size(this: HashMap&): usize { 226 | this.size 227 | } 228 | 229 | inline def empty(this: HashMap&): bool { 230 | this.size == 0 as usize 231 | } 232 | 233 | // insert key value pair to hash map 234 | public def insert(this: HashMap var&, key: u32, value: u8 var*): bool { 235 | var hash_val = this.hash(key) 236 | if this.table.get(hash_val, key) == null as u8 var* { 237 | if this.rehash() { 238 | hash_val = this.hash(key) 239 | } 240 | this.table.insert(hash_val, key, value) 241 | this.size += 1 as usize 242 | true 243 | } 244 | else { 245 | false 246 | } 247 | } 248 | 249 | // get value by key 250 | public def get(this: HashMap var&, key: u32): u8 var* { 251 | let hash_val = this.hash(key) 252 | this.table.get(hash_val, key) 253 | } 254 | 255 | // remove value by key 256 | public def remove(this: HashMap var&, key: u32): bool { 257 | let hash_val = this.hash(key) 258 | let ret = this.table.remove(hash_val, key) 259 | if ret { 260 | this.size -= 1 as usize 261 | } 262 | ret 263 | } 264 | 265 | // remove all key-value pairs 266 | public def clear(this: HashMap var&) { 267 | this.table.del() 268 | this.table = newTable(TABLE_SIZE) 269 | this.size = 0 as usize 270 | } 271 | 272 | // get iterator 273 | public def iter(this: HashMap&): KvPairTableIter { 274 | this.table.iter() 275 | } 276 | -------------------------------------------------------------------------------- /src/mem/paging.yu: -------------------------------------------------------------------------------- 1 | public import arch.riscv.pagetable 2 | public import arch.riscv.addr 3 | public import arch.riscv.framealloc 4 | public import arch.riscv.recursive 5 | 6 | import arch.riscv.csr 7 | import arch.riscv.consts 8 | import mem.pm 9 | import lib.except 10 | import mem.consts 11 | 12 | // address for temporary mapping 13 | let TEMP_PAGE_ADDR = 0xcafeb000 as usize 14 | // address of root page table 15 | let ROOT_PAGE_TABLE = 16 | ((RECURSIVE_INDEX << 22 as usize) | 17 | ((RECURSIVE_INDEX + 1 as usize) << 12 as usize)) as PageTable var* 18 | 19 | 20 | // page entry representation 21 | public struct PageEntry { 22 | pte: PageTableEntry var*, 23 | page: Page, 24 | } 25 | 26 | public def newPageEntry(pte: PageTableEntry var*, page: Page): PageEntry { 27 | [PageEntry] {pte, page} 28 | } 29 | 30 | public def update(this: PageEntry var&) { 31 | let pa = this.page.getAddr() 32 | runSfence(0 as usize, pa.getAddr()) 33 | } 34 | 35 | public def isAccessed(this: PageEntry&): bool { 36 | (*this.pte).getFlag(PTE_FLAG_A) 37 | } 38 | 39 | public def clearAccessed(this: PageEntry var&) { 40 | (*this.pte).clearFlag(PTE_FLAG_A) 41 | } 42 | 43 | public def isDirty(this: PageEntry&): bool { 44 | (*this.pte).getFlag(PTE_FLAG_D) 45 | } 46 | 47 | public def clearDirty(this: PageEntry var&) { 48 | (*this.pte).clearFlag(PTE_FLAG_A) 49 | } 50 | 51 | public def isWritable(this: PageEntry&): bool { 52 | (*this.pte).getFlag(PTE_FLAG_W) 53 | } 54 | 55 | public def setWritable(this: PageEntry var&, value: bool) { 56 | (*this.pte).setFlag(PTE_FLAG_W, value) 57 | } 58 | 59 | public def isPresent(this: PageEntry&): bool { 60 | (*this.pte).getFlag(PTE_FLAG_V) 61 | } 62 | 63 | public def setPresent(this: PageEntry var&, value: bool) { 64 | (*this.pte).setFlag(PTE_FLAG_V, value) 65 | } 66 | 67 | public def getTarget(this: PageEntry&): usize { 68 | let pa = (*this.pte).getAddr() 69 | pa.getAddr() 70 | } 71 | 72 | public def setTarget(this: PageEntry var&, target: usize) { 73 | let flags = (*this.pte).getFlags() 74 | let frame = newFrame(newPhysAddr(target)) 75 | (*this.pte).set(frame, flags) 76 | } 77 | 78 | public def isUser(this: PageEntry&): bool { 79 | (*this.pte).getFlag(PTE_FLAG_U) 80 | } 81 | 82 | public def setUser(this: PageEntry var&, value: bool) { 83 | (*this.pte).setFlag(PTE_FLAG_U, value) 84 | } 85 | 86 | public def isExecute(this: PageEntry&): bool { 87 | (*this.pte).getFlag(PTE_FLAG_X) 88 | } 89 | 90 | public def setExecute(this: PageEntry var&, value: bool) { 91 | (*this.pte).setFlag(PTE_FLAG_X, value) 92 | } 93 | 94 | 95 | // physical frame allocator 96 | struct PhysFrameAlloc { 97 | base: FrameAllocator, 98 | } 99 | 100 | def allocPhysFrame(this: FrameAllocator var*): Frame { 101 | let addr = allocPhyMem() as usize 102 | if addr == 0 as usize { 103 | panic("allocPhysFrame - OOM") 104 | } 105 | newFrame(newPhysAddr(addr)) 106 | } 107 | 108 | def newPhysFrameAlloc(): PhysFrameAlloc { 109 | [PhysFrameAlloc] {[FrameAllocator] {allocPhysFrame}} 110 | } 111 | 112 | // physical frame deallocator 113 | struct PhysFrameDealloc { 114 | base: FrameDeallocator, 115 | } 116 | 117 | def deallocPhysFrame(this: FrameDeallocator var*, frame: Frame) { 118 | let pa = frame.getAddr() 119 | freePhyMem(pa.getAddr() as u8 var*) 120 | } 121 | 122 | def newPhysFrameDealloc(): PhysFrameDealloc { 123 | [PhysFrameDealloc] {[FrameDeallocator] {deallocPhysFrame}} 124 | } 125 | 126 | 127 | // activated page table 128 | public struct ActivePageTable { 129 | page_table: RecursivePageTable, 130 | entry: PageEntry, 131 | } 132 | 133 | def getEntry(this: ActivePageTable var&, vaddr: usize): PageEntry var& { 134 | let page = newPage(newVirtAddr(vaddr)) 135 | let pte_ptr = this.page_table.getPtePtr(page) 136 | if pte_ptr == null as PageTableEntry var* { 137 | panic("getEntry - fail to get entry") 138 | } 139 | this.entry = newPageEntry(pte_ptr, page) 140 | this.entry 141 | } 142 | 143 | def getEntryPtr(this: ActivePageTable var&, vaddr: usize): PageEntry var* { 144 | let page = newPage(newVirtAddr(vaddr)) 145 | let pte_ptr = this.page_table.getPtePtr(page) 146 | if pte_ptr == null as PageTableEntry var* { 147 | null as PageEntry var* 148 | } 149 | else { 150 | this.entry = newPageEntry(pte_ptr, page) 151 | &this.entry 152 | } 153 | } 154 | 155 | public def newActivePageTable(): ActivePageTable { 156 | [ActivePageTable] {newRecursivePageTable(ROOT_PAGE_TABLE)} 157 | } 158 | 159 | public def map(this: ActivePageTable var&, addr: usize, 160 | target: usize): PageEntry var& { 161 | let flags = PTE_FLAG_V | PTE_FLAG_R | PTE_FLAG_W 162 | let page = newPage(newVirtAddr(addr)) 163 | let frame = newFrame(newPhysAddr(target)) 164 | var frame_alloc = newPhysFrameAlloc() 165 | let alloc = &frame_alloc as FrameAllocator var* 166 | this.page_table.mapTo(page, frame, flags, alloc).flush() 167 | this.getEntry(addr) 168 | } 169 | 170 | public def unmap(this: ActivePageTable var&, addr: usize): Frame { 171 | let page = newPage(newVirtAddr(addr)) 172 | var frame: Frame 173 | this.page_table.unmap(page, frame).flush() 174 | frame 175 | } 176 | 177 | def getPageSlice(addr: VirtAddr): PageTable var* { 178 | let addr = addr.roundDown() 179 | addr.getAddr() as PageTable var* 180 | } 181 | 182 | def withTempMapBegin(this: ActivePageTable var&, 183 | target: PhysAddr): PageTable var* { 184 | this.map(TEMP_PAGE_ADDR, target.getAddr()) 185 | getPageSlice(newVirtAddr(TEMP_PAGE_ADDR)) 186 | } 187 | 188 | def withTempMapEnd(this: ActivePageTable var&) { 189 | this.unmap(TEMP_PAGE_ADDR) 190 | } 191 | 192 | 193 | // inactivated page table 194 | public struct InactivePageTable { 195 | root_frame: Frame, 196 | } 197 | 198 | // helper iterator for `edit` method 199 | public struct IptEditIter { 200 | last: bool, 201 | act_pt: ActivePageTable, 202 | root_table: PageTable var*, 203 | backup: PageTableEntry, 204 | } 205 | 206 | // helper iterator for `with` method 207 | public struct IptWithIter { 208 | last: bool, 209 | old_token: usize, 210 | new_token: usize, 211 | } 212 | 213 | public def newInactivePageTable(): InactivePageTable { 214 | let frame = allocPhysFrame(null as FrameAllocator var*) 215 | var act_pt = newActivePageTable() 216 | let temp_pt = act_pt.withTempMapBegin(frame.getAddr()); { 217 | temp_pt.zero() 218 | temp_pt.setRecursive(RECURSIVE_INDEX, frame) 219 | }; act_pt.withTempMapEnd() 220 | [InactivePageTable] {frame} 221 | } 222 | 223 | public def del(this: InactivePageTable var&) { 224 | let pa = this.root_frame.getAddr() 225 | let pt = pa.getAddr() as PageTable var* 226 | // clear current page table 227 | var frame_dealloc = newPhysFrameDealloc() 228 | let dealloc = &frame_dealloc as FrameDeallocator var* 229 | pt.clear(RECURSIVE_INDEX, dealloc) 230 | // deallocate root frame 231 | freePhyMem(pt as u8 var*) 232 | } 233 | 234 | public def token(this: InactivePageTable&): usize { 235 | this.root_frame.getPpn() | SATP_SV32 236 | } 237 | 238 | // setup kernel mapping 239 | public def mapKernel(this: InactivePageTable var&) { 240 | var act_pt = newActivePageTable() 241 | let flags = PTE_FLAG_V | PTE_FLAG_R | PTE_FLAG_W 242 | let offset = (KERNEL_VM_BASE - KERNEL_BASE) as usize 243 | let temp_pt = act_pt.withTempMapBegin(this.root_frame.getAddr()); { 244 | // kernel and physical memory 245 | temp_pt.mapLinearRange(KERNEL_BASE as usize, PHY_STOP as usize, 246 | offset, flags | PTE_FLAG_X) 247 | // UART 248 | temp_pt.mapLinearRange(UART_ADDR as usize, UART_END as usize, 249 | offset, flags) 250 | // PLIC 251 | temp_pt.mapLinearRange(PLIC_ADDR as usize, PLIC_END as usize, 252 | offset, flags) 253 | }; act_pt.withTempMapEnd() 254 | } 255 | 256 | // activate current inactive page table temporarily 257 | // and provide the current page table for editing 258 | public def edit(this: InactivePageTable var&): IptEditIter { 259 | let frame = newFrameFromSatp() 260 | var act_pt = newActivePageTable() 261 | let root_table = act_pt.withTempMapBegin(frame.getAddr()) 262 | let backup = root_table.getPte(RECURSIVE_INDEX) 263 | root_table.getPte(RECURSIVE_INDEX).set(this.root_frame, PTE_FLAG_V) 264 | [IptEditIter] {false, act_pt, root_table, backup} 265 | } 266 | 267 | // iterator methods of `IptEditIter` 268 | public def next(this: IptEditIter var&): ActivePageTable var& { 269 | this.act_pt 270 | } 271 | public def last(this: IptEditIter var&): bool { 272 | if !this.last { 273 | this.last = true 274 | // edit begin 275 | runSfence() 276 | false 277 | } 278 | else { 279 | // edit end 280 | this.root_table.getPte(RECURSIVE_INDEX) = this.backup 281 | runSfence() 282 | this.act_pt.withTempMapEnd() 283 | true 284 | } 285 | } 286 | 287 | // activate current inactive page table temporarily 288 | // and run specific function in new context 289 | public def with(this: InactivePageTable&): IptWithIter { 290 | [IptWithIter] {false, getSatp(), this.token()} 291 | } 292 | 293 | // iterator methods of `IptWithIter` 294 | public def next(this: IptWithIter&): i32 { 295 | 0 296 | } 297 | public def last(this: IptWithIter var&): bool { 298 | if !this.last { 299 | this.last = true 300 | // switch to new context 301 | if this.old_token != this.new_token { 302 | setSatp(this.new_token) 303 | runSfence() 304 | } 305 | false 306 | } 307 | else { 308 | // switch back to old context 309 | if this.old_token != this.new_token { 310 | setSatp(this.old_token) 311 | runSfence() 312 | } 313 | true 314 | } 315 | } 316 | 317 | // activate current page table 318 | public def activate(this: InactivePageTable&) { 319 | let old_token = getSatp() 320 | let new_token = this.token() 321 | if old_token != new_token { 322 | setSatp(new_token) 323 | runSfence() 324 | } 325 | } 326 | 327 | // iterator for pages 328 | public struct PageRange { 329 | start: usize, 330 | end: usize, 331 | } 332 | 333 | public def newPageRange(start_addr: usize, end_addr: usize): PageRange { 334 | [PageRange] { 335 | start_addr / PAGE_SIZE as usize, 336 | (end_addr - 1 as usize) / PAGE_SIZE as usize + 1 as usize, 337 | } 338 | } 339 | 340 | public def next(this: PageRange var&): usize { 341 | let page = this.start * PAGE_SIZE as usize 342 | this.start += 1 as usize 343 | page 344 | } 345 | 346 | public def last(this: PageRange&): bool { 347 | this.start >= this.end 348 | } 349 | -------------------------------------------------------------------------------- /src/fs/geefs/geefs.yu: -------------------------------------------------------------------------------- 1 | public import fs.dev.device 2 | public import fs.vfs.vfs 3 | 4 | import fs.geefs.structs 5 | import lib.hashmap 6 | import sync.semaphore 7 | import lib.except 8 | import lib.alloc 9 | import lib.algo 10 | 11 | // filesystem object 12 | // NOTE: this object will NOT HAVE any inodes 13 | // i.e. stores only references of inodes 14 | struct GeeFs { 15 | // vfs interface 16 | fs: FileSystem, 17 | // low-level device 18 | dev: DeviceInterface var*, 19 | // super block on disk 20 | super_block: GfsSbHeader, 21 | // semaphore for inode map 22 | // TODO: use spinlock? 23 | sema: Semaphore, 24 | // inode map 25 | inodes: HashMap, 26 | } 27 | 28 | // memory inode 29 | struct GeeFsINode { 30 | inode: INode, 31 | id: u32, 32 | gfs_inode: GfsINode, 33 | fs: GeeFs var*, 34 | } 35 | 36 | 37 | /* 38 | * INode related stuffs 39 | */ 40 | // virtual operations of GeeFS object, shared static object 41 | var inode_ops: INodeOps 42 | 43 | // inode type converter 44 | def convToINodeType(this: GfsINodeType): INodeType { 45 | assert(this != GfsINodeType.Unused, "convToINodeType") 46 | if this == GfsINodeType.File { 47 | INodeType.File 48 | } 49 | else { 50 | INodeType.Dir 51 | } 52 | } 53 | 54 | // create new inode 55 | def newGeeFsINode(id: u32, gfs_inode: GfsINode&, 56 | fs: GeeFs var*): INode var* { 57 | var inode = heap.alloc(sizeof GeeFsINode) as GeeFsINode var* 58 | (inode as INode var*).init(&inode_ops) 59 | (*inode).id = id 60 | (*inode).gfs_inode = gfs_inode 61 | (*inode).fs = fs 62 | inode as INode var* 63 | } 64 | 65 | // get GeeFS memory inode from inode 66 | def getINode(this: INode var*): GeeFsINode var& { 67 | *(this as GeeFsINode var*) 68 | } 69 | 70 | // get filesystem object from inode 71 | def getGeeFs(this: INode var*): GeeFs var& { 72 | *(this.getINode().fs) 73 | } 74 | 75 | 76 | /* 77 | * filesystem related stuffs 78 | */ 79 | var geefs_ops: FileSystemOps 80 | 81 | // get inode from map 82 | // if not found, read inode from device and add inode to map 83 | def getINode(this: GeeFs var&, id: u32): INode var* { 84 | this.sema.wait() 85 | // try to find in map 86 | let inode_ret = this.inodes.get(id) 87 | if inode_ret as i32 != 0 { 88 | this.sema.signal() 89 | return inode_ret as INode var* 90 | } 91 | // get offset 92 | let in_per_blk = (this.super_block.block_size - sizeof GfsInbHeader) / 93 | sizeof GfsINode 94 | var offset = 1 as u32 + this.super_block.free_map_num + id / in_per_blk 95 | offset *= this.super_block.block_size 96 | offset += sizeof GfsInbHeader + (id % in_per_blk) * sizeof GfsINode 97 | // read inode 98 | var inode: GfsINode 99 | if !this.dev.readAssert(sizeof GfsINode, &inode as u8 var*, 100 | offset as usize) { 101 | this.sema.signal() 102 | return null as INode var* 103 | } 104 | // add to map 105 | let mem_inode = newGeeFsINode(id, inode, &this) 106 | let ret = this.inodes.insert(id, mem_inode as u8 var*) 107 | assert(ret, "GeeFs.getINode") 108 | this.sema.signal() 109 | mem_inode 110 | } 111 | 112 | // remove inode from map by id 113 | // called by 'gfsCleanUp', so DO NOT delete removed inode 114 | def removeINode(this: GeeFs var&, id: u32): bool { 115 | this.sema.wait() 116 | let ret = this.inodes.remove(id) 117 | this.sema.signal() 118 | ret 119 | } 120 | 121 | // get nth data block offset of inode 122 | def getBlockOffset(this: GeeFs var&, inode: GfsINode&, n: u32, 123 | ofs: u32 var&): bool { 124 | let ofs_per_blk = this.super_block.block_size / BLOCK_OFS_SIZE 125 | if n >= inode.block_num { return false } 126 | if n < DIRECT_BLOCK_NUM { 127 | ofs = inode.direct[n] 128 | true 129 | } 130 | else if n - DIRECT_BLOCK_NUM < ofs_per_blk { 131 | let offset = inode.indirect * this.super_block.block_size + 132 | (n - DIRECT_BLOCK_NUM) * BLOCK_OFS_SIZE 133 | this.dev.readAssert(BLOCK_OFS_SIZE as usize, &ofs as u8 var*, 134 | offset as usize) 135 | } 136 | else if n - DIRECT_BLOCK_NUM - ofs_per_blk < ofs_per_blk * ofs_per_blk { 137 | var offset = inode.indirect2 * this.super_block.block_size + 138 | (n / ofs_per_blk) * BLOCK_OFS_SIZE 139 | if !this.dev.readAssert(BLOCK_OFS_SIZE as usize, &offset as u8 var*, 140 | offset as usize) { 141 | return false 142 | } 143 | offset *= this.super_block.block_size 144 | offset += (n % ofs_per_blk) & BLOCK_OFS_SIZE 145 | this.dev.readAssert(BLOCK_OFS_SIZE as usize, &ofs as u8 var*, 146 | offset as usize) 147 | } 148 | else { 149 | false 150 | } 151 | } 152 | 153 | // open filesystem image on device, returns false if failed 154 | def open(this: GeeFs var&): bool { 155 | // read super block header 156 | if !this.dev.readAssert(sizeof GfsSbHeader, &this.super_block as u8 var*, 157 | 0 as usize) { 158 | return false 159 | } 160 | // clear the inode map 161 | if !this.inodes.empty() { 162 | for kv in this.inodes.iter() { 163 | let inode = kv.value() as INode var* 164 | inode.del() 165 | } 166 | this.inodes.clear() 167 | } 168 | // initialize root inode 169 | this.getINode(0 as u32) != null as INode var* 170 | } 171 | 172 | // create filesystem by opening image on device 173 | public def newGeeFs(dev: DeviceInterface var*): FileSystem var* { 174 | // create GeeFs object 175 | let geefs = heap.alloc(sizeof GeeFs) as GeeFs var* 176 | (geefs as FileSystem var*).init(&geefs_ops) 177 | (*geefs).dev = dev 178 | (*geefs).super_block = [GfsSbHeader] {} 179 | (*geefs).sema = newSemaphore() 180 | (*geefs).inodes = newHashMap() 181 | // perform open operation 182 | if (*geefs).open() { 183 | geefs as FileSystem var* 184 | } 185 | else { 186 | (geefs as FileSystem var*).del() 187 | null as FileSystem var* 188 | } 189 | } 190 | 191 | // get GeeFs object from filesystem 192 | def getGeeFs(this: FileSystem var*): GeeFs var& { 193 | *(this as GeeFs var*) 194 | } 195 | 196 | 197 | /* 198 | * inode virtual operations 199 | */ 200 | // get filesystem 201 | def gfsINodeGetFs(this: INode var*): FileSystem var* { 202 | this.getINode().fs as FileSystem var* 203 | } 204 | 205 | // get metadata of current inode 206 | def gfsINodeGetMetadata(this: INode var*): Metadata { 207 | let inode: GfsINode& = this.getINode().gfs_inode 208 | [Metadata] { 209 | this.getINode().id, 210 | inode.itype.convToINodeType(), 211 | inode.size as usize, 212 | this.getGeeFs().super_block.block_size, 213 | inode.block_num, 214 | } 215 | } 216 | 217 | // find inode by name 218 | def gfsINodeFind(this: INode var*, name: StrView&): INode var* { 219 | let inode: GfsINode& = this.getINode().gfs_inode 220 | let fs: GeeFs var& = this.getGeeFs() 221 | // make sure current inode represents a directory 222 | if inode.itype != GfsINodeType.Dir { 223 | return null as INode var* 224 | } 225 | // traverse all entries in current inode 226 | let ent_num = inode.size / sizeof GfsEntry 227 | let ent_per_blk = fs.super_block.block_size / sizeof GfsEntry 228 | // traverse data blocks 229 | var i = 0 as u32 230 | while i < inode.block_num { 231 | // get offset 232 | var blk_ofs: u32 233 | if !fs.getBlockOffset(inode, i, blk_ofs) { 234 | return null as INode var* 235 | } 236 | let offset = blk_ofs * fs.super_block.block_size 237 | // traverse entries in current block 238 | let entry_num = min(ent_num - i * ent_per_blk, ent_per_blk) 239 | var j = 0 as u32 240 | while j < entry_num { 241 | // read entry 242 | let ent_ofs = offset + j * sizeof GfsEntry as u32 243 | var entry: GfsEntry 244 | if !fs.dev.readAssert(sizeof GfsEntry, &entry as u8 var*, 245 | ent_ofs as usize) { 246 | return null as INode var* 247 | } 248 | // get inode 249 | if name == entry.filename as u8* { 250 | return fs.getINode(entry.inode_id) 251 | } 252 | j += 1 as u32 253 | } 254 | i += 1 as u32 255 | } 256 | null as INode var* 257 | } 258 | 259 | // read inode content to buffer 260 | def gfsINodeRead(this: INode var*, buf: u8 var*, len: usize, 261 | offset: usize): i32 { 262 | let inode: GfsINode& = this.getINode().gfs_inode 263 | let fs: GeeFs var& = this.getGeeFs() 264 | // read file 265 | var data_len = 0, i = offset as u32 266 | let end_len = min((offset + len) as u32, inode.size) 267 | while i < end_len { 268 | // get block offset 269 | let n = i / fs.super_block.block_size 270 | if n >= inode.block_num { break } 271 | var blk_ofs: u32 272 | if !fs.getBlockOffset(inode, n, blk_ofs) { break } 273 | // get offset 274 | let ofs = blk_ofs * fs.super_block.block_size + 275 | i % fs.super_block.block_size 276 | // read to buffer 277 | if !fs.dev.readAssert(1 as usize, buf + data_len, ofs as usize) { 278 | break 279 | } 280 | i += 1 as u32 281 | data_len += 1 282 | } 283 | data_len 284 | } 285 | 286 | // write buffer content to inode 287 | def gfsINodeWrite(this: INode var*, buf: u8*, len: usize, 288 | offset: usize): i32 { 289 | // TODO 290 | -1 291 | } 292 | 293 | // clean up before being released 294 | def gfsINodeCleanUp(this: INode var*) { 295 | this.getGeeFs().removeINode(this.getINode().id) 296 | } 297 | 298 | 299 | /* 300 | * filesystem virtual operations 301 | */ 302 | // get inode of root directory 303 | def gfsGetRoot(this: FileSystem var*): INode var* { 304 | let fs: GeeFs var& = this.getGeeFs() 305 | fs.sema.wait() 306 | let root = fs.inodes.get(0 as u32) as INode var* 307 | assert(root != null as INode var*, "GeeFs.getRoot") 308 | fs.sema.signal() 309 | root 310 | } 311 | 312 | def gfsSync(this: FileSystem var*): bool { 313 | this.getGeeFs().dev.sync() 314 | } 315 | 316 | def gfsCleanUp(this: FileSystem var*) { 317 | let fs: GeeFs var& = this.getGeeFs() 318 | for kv in fs.inodes.iter() { 319 | let inode = kv.value() as INode var* 320 | inode.del() 321 | } 322 | fs.inodes.del() 323 | fs.sema.del() 324 | } 325 | 326 | 327 | /* 328 | * filesystem initializers 329 | */ 330 | // initialize virtual operations 331 | public def initGeeFsOps() { 332 | // inode ops 333 | inode_ops.op_get_fs = gfsINodeGetFs 334 | inode_ops.op_get_metadata = gfsINodeGetMetadata 335 | inode_ops.op_find = gfsINodeFind 336 | inode_ops.op_read = gfsINodeRead 337 | inode_ops.op_write = gfsINodeWrite 338 | inode_ops.op_cleanup = gfsINodeCleanUp 339 | // filesystem ops 340 | geefs_ops.op_get_root = gfsGetRoot 341 | geefs_ops.op_sync = gfsSync 342 | geefs_ops.op_cleanup = gfsCleanUp 343 | } 344 | --------------------------------------------------------------------------------