commit 9ee197f57baa336642208a2df73f692bd5b564fe Author: Nathan Giddings Date: Tue May 28 14:26:46 2024 -0500 New repo setup diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aada14a --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.o +*.elf +*.img +.vscode +*.a +*.bin +testprog +init \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5378915 --- /dev/null +++ b/Makefile @@ -0,0 +1,87 @@ +CC = aarch64-none-elf-gcc +CXX = aarch64-none-elf-g++ +AS = aarch64-none-elf-as +LD = aarch64-none-elf-ld +AR = aarch64-none-elf-ar +OBJCOPY = aarch64-none-elf-objcopy +prefix:=$(HOME)/.cros/root + +aarch64_ldscript = src/aarch64/aarch64.ld +aarch64_objs = src/aarch64/boot.o src/aarch64/aarch64.o \ + src/aarch64/irq/exceptions.o src/aarch64/irq/irq.o \ + src/aarch64/bootstrap.o src/aarch64/sysreg.o src/aarch64/irq/interrupts.o + +memory_objs_common = src/memory/addressspace.o src/memory/heap.o src/memory/memorymap.o \ + src/memory/mmap.o src/memory/new.o src/memory/pageallocator.o +memory_objs_aarch64 = src/memory/aarch64/mmu.o + +fs_objs_common = src/fs/fat32/helpers.o src/fs/fat32/entry_helpers.o src/fs/fat32/entry.o \ + src/fs/fat32/fat32.o src/fs/fat32/fs_helpers.o \ + src/fs/fat32/disk_interface/disk_interface.o src/fs/fat32/filecontextfat32.o \ + src/fs/pipe.o src/fs/filecontext.o + +loader_objs_common = src/loader/elf.o + +sched_objs_common = src/sched/process.o src/sched/queue.o +sched_objs_aarch64 = src/sched/aarch64/context.o src/sched/aarch64/loadcontext.o + +device_objs_common = src/devices/timer.o src/devices/uart.o + +util_objs_common = src/util/log.o src/util/string.o src/util/hasrefcount.o +util_objs_aarch64 = src/util/aarch64/hacf.o + +objs = src/kernel.o src/irq/interrupts.o src/containers/string.o \ + $(memory_objs_common) $(memory_objs_aarch64) $(loader_objs_common) $(fs_objs_common) $(device_objs_common) $(sched_objs_common) $(sched_objs_aarch64) $(util_objs_common) $(util_objs_aarch64) + +CRTI_OBJ=src/aarch64/crti.o +CRTBEGIN_OBJ:=$(shell $(CC) $(CFLAGS) -print-file-name=crtbegin.o) +CRTEND_OBJ:=$(shell $(CC) $(CFLAGS) -print-file-name=crtend.o) +CRTN_OBJ=src/aarch64/crtn.o + +kernel_elf = kernel.elf +kernel_binary = kernel8.img + +libsyscall = libsyscall.a +libsyscall_obj = src/aarch64/dosyscall.o + +testprog_bin = init +testprog_obj = test/entry.o test/main.o + +CFLAGS = -Iinclude/ -Isrc/ -ffreestanding -Wall -Wextra -ggdb -O0 -mgeneral-regs-only +CXXFLAGS = -Iinclude/ -Isrc/ -ffreestanding -fpermissive -fno-exceptions -fno-rtti -fno-use-cxa-atexit -Wall -Wextra -ggdb -O0 -mgeneral-regs-only +LDFLAGS = -T $(aarch64_ldscript) -nostdlib + +.PHONY: all +all: $(libsyscall) $(testprog_bin) $(kernel_binary) + +.PHONY: clean +clean: + rm -f $(CRTI_OBJ) $(CRTN_OBJ) $(objs) $(aarch64_objs) $(kernel_elf) $(kernel_binary) $(libsyscall) $(libsyscall_obj) $(testprog_bin) $(testprog_obj) + +.PHONY: install +install: + ./../scripts/create_and_mount_img.sh $(prefix) + mkdir -p $(prefix)/include + mkdir -p $(prefix)/boot + mkdir -p $(prefix)/lib + mkdir -p $(prefix)/bin + cp kernel8.img $(prefix)/boot + cp -r include/* $(prefix)/include + cp libsyscall.a $(prefix)/lib + cp $(testprog_bin) $(prefix)/bin + +$(kernel_binary): $(kernel_elf) + $(OBJCOPY) $(kernel_elf) -O binary $@ + +$(kernel_elf): $(CRTI_OBJ) $(CRTBEGIN_OBJ) $(objs) $(aarch64_objs) $(CRTEND_OBJ) $(CRTN_OBJ) $(aarch64_ldscript) $(testprog_bin) + $(CXX) -o $@ $(LDFLAGS) $(CRTI_OBJ) $(CRTBEGIN_OBJ) $(objs) $(aarch64_objs) $(CRTEND_OBJ) $(CRTN_OBJ) -lgcc + +$(libsyscall): $(libsyscall_obj) + $(AR) -rcs $@ $^ + +$(testprog_bin): $(testprog_obj) + $(CC) -o $@ -T test/linker.ld -nostdlib $^ -L. -lgcc -lsyscall + +.PHONY: clobber +clobber: + ./../scripts/unmount_img.sh $(prefix) diff --git a/README.md b/README.md new file mode 100644 index 0000000..d66ac75 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# Kernel + +## Introduction + +## Building + +You will need: GCC cross compiler targeting aarch64. See [Arm GNU Toolchain Downloads](https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads) +or consult your package manager for help obtaining the correct compiler. + +To build the kernel, simply run `make` in this directory. `make` will produce an ELF binary, `kernel.elf`, +as well as a flat binary, `kernel8.img`, for running on the Raspberry Pi. + +## Running + +This kernel has so far been tested on an emulator, [QEMU](https://www.qemu.org/), as well as the Raspberry Pi 3B. + +To run this kernel on QEMU, run: + `qemu-system-aarch64 -M raspi3b -serial stdio -kernel kernel8.img` + +To run this kernel on the Raspberry Pi, place `kernel8.img` in the boot partition of your Pi's SD card. +On the Raspberry Pi 3B, you may also need to add the following like to `confix.txt`: + `arm_64bit=1` diff --git a/include/sys/syscall.h b/include/sys/syscall.h new file mode 100644 index 0000000..d9d3635 --- /dev/null +++ b/include/sys/syscall.h @@ -0,0 +1,222 @@ +#ifndef KERNEL_SYSCALL_H +#define KERNEL_SYSCALL_H + +#include "types/syscallid.h" +#include "types/pid.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + int do_syscall(long id, unsigned long arg1, unsigned long arg2, unsigned long arg3, unsigned long arg4); + + /** + * @brief Prints `str` on the kernel log. + * @param str + * @return 0 + */ + static inline int printk(const char *str) + { + return do_syscall(SYS_PRINTK, (unsigned long)str, 0, 0, 0); + } + + /** + * @brief Map a region in memory to newly allocated page frames + * @param ptr Pointer to the start of the region to map + * @param size Size in bytes of the region to map + * @param flags Access flags for the pages to map + * @return + */ + static inline int mmap(void *ptr, unsigned long size, int flags) + { + return do_syscall(SYS_MMAP, (unsigned long)ptr, size, (unsigned long)flags, 0); + } + + /** + * @brief Free the memory in a particular region + * @param ptr Pointer to the start of the region to unmap + * @param size Size in bytes of the region to unmap + * @return + */ + static inline int munmap(void *ptr, unsigned long size) + { + return do_syscall(SYS_MUNMAP, (unsigned long)ptr, size, 0, 0); + } + + /** + * @brief Create a new process which shares the current process's address space, + * but has a separate context and is scheduled separately. + * + * @param fn Function pointer to start executing the new process at + * @param stack Stack pointer for the new process + * @param flags + * @return + */ + static inline int clone(int (*fn)(void *), void *stack, void *userdata, int flags) + { + return do_syscall(SYS_CLONE, (unsigned long)fn, (unsigned long)stack, (unsigned long)userdata, (unsigned long)flags); + } + + /** + * @brief Completely terminate the current process, freeing all resources that + * belong to it. + * + * @return + */ + static inline int terminate() + { + return do_syscall(SYS_TERMINATE, 0, 0, 0, 0); + } + + /** + * @brief Replace the current process's address space with a fresh one, then + * load a new program image from the executable specified by `path`. + * + * @param path Path to the executable to load + * @param argv Program arguments to pass to the new program + * @param envp Environment variables to pass to the new program + * @return + */ + static inline int exec(const char *path, char *const argv[], char *const envp[]) + { + return do_syscall(SYS_EXEC, (unsigned long)path, (unsigned long)argv, (unsigned long)envp, 0); + } + + /** + * @brief Put current process at the end of the scheduler queue, then switch to + * next process. + * + * @return + */ + static inline int yield() + { + return do_syscall(SYS_YIELD, 0, 0, 0, 0); + } + + /** + * @brief Call the specified signal handler on the process with id `pid`. + * @param pid Process to call a signal handler on + * @param signal Signal handler to call + * @return + */ + static inline int sigraise(pid_t pid, int signal) + { + return do_syscall(SYS_SIGRAISE, (unsigned long)pid, (unsigned long)signal, 0, 0); + } + + /** + * @brief Return from a signal handler, putting the stack and process context + * back to the state they were in just before the signal was triggered. + * @return + */ + static inline int sigret() + { + return do_syscall(SYS_SIGRET, 0, 0, 0, 0); + } + + /** + * @brief Stop scheduling process until a signal occurs. + * @return + */ + static inline int sigwait() + { + return do_syscall(SYS_SIGWAIT, 0, 0, 0, 0); + } + + /** + * @brief Sets the handler function to call when a particular signal occurs. + * Kernel will pass the pointer `userdata` to the handler function when it is + * called. + * @param signal Signal to specify handler for + * @param handler Function pointer to signal handler + * @param trampoline Function pointer to trampoline function called when handler returns. + * @param userdata Userdata to pass to handler function (can be NULL) + * @return + */ + static inline int sigaction(int signal, void (*handler)(void *), void (*trampoline)(void), void *userdata) + { + return do_syscall(SYS_SIGACTION, (unsigned long)signal, (unsigned long)handler, (unsigned long)trampoline, (unsigned long)userdata); + } + + /** + * @brief Open the file specified by `path` + * @param path Path of the file to open + * @param flags + * @return The file descriptor for the file just opened + */ + static inline int openf(const char *path, int flags) + { + return do_syscall(SYS_OPEN, (unsigned long)path, (unsigned long)flags, 0, 0); + } + + /** + * @brief Close a proviously open file + * @param fd File descriptor of the open file to close + * @return + */ + static inline int closef(int fd) + { + return do_syscall(SYS_CLOSE, (unsigned long)fd, 0, 0, 0); + } + + /** + * @brief Create a new file at the given path + * @param path Path of the file to create + * @param flags Mode for the new file + * @return + */ + static inline int create(const char *path, int flags) + { + return do_syscall(SYS_CREATE, (unsigned long)path, (unsigned long)flags, 0, 0); + } + + /** + * @brief + * @param fd + * @return + */ + static inline int unlink(int fd) + { + return do_syscall(SYS_UNLINK, (unsigned long)fd, 0, 0, 0); + } + + /** + * @brief + * @param fd + * @param buffer + * @param size + * @return + */ + static inline int read(int fd, void *buffer, unsigned long size) + { + return do_syscall(SYS_READ, (unsigned long)fd, (unsigned long)buffer, size, 0); + } + + /** + * @brief + * @param fd + * @param buffer + * @param size + * @return + */ + static inline int write(int fd, const void *buffer, unsigned long size) + { + return do_syscall(SYS_WRITE, (unsigned long)fd, (unsigned long)buffer, size, 0); + } + + static inline int fddup(int oldfd, int newfd) + { + return do_syscall(SYS_FDDUP, (unsigned long)oldfd, (unsigned long)newfd, 0, 0); + } + + static inline int create_pipe(int pipefd[2]) + { + return do_syscall(SYS_CREATE_PIPE, (unsigned long)pipefd, 0, 0, 0); + } + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/include/types/physaddr.h b/include/types/physaddr.h new file mode 100644 index 0000000..8830b3d --- /dev/null +++ b/include/types/physaddr.h @@ -0,0 +1,6 @@ +#ifndef KERNEL_PHYSADDR_H +#define KERNEL_PHYSADDR_H + +typedef unsigned long physaddr_t; + +#endif \ No newline at end of file diff --git a/include/types/pid.h b/include/types/pid.h new file mode 100644 index 0000000..9909c05 --- /dev/null +++ b/include/types/pid.h @@ -0,0 +1,6 @@ +#ifndef KERNEL_PID_H +#define KERNEL_PID_H + +typedef unsigned int pid_t; + +#endif \ No newline at end of file diff --git a/include/types/status.h b/include/types/status.h new file mode 100644 index 0000000..720a0cc --- /dev/null +++ b/include/types/status.h @@ -0,0 +1,28 @@ +#ifndef KERNEL_STATUS_H +#define KERNEL_STATUS_H + +#ifdef __cplusplus +extern "C" +{ +#endif + + enum error_t + { + ENONE = 0, + EUNKNOWN = -1, + ENOSYS = -2, + EEOF = -3, + ENOFILE = -4, + ENOMEM = -5, + EINVAL = -6, + EIO = -7, + EEXISTS = -8, + EPIPE = -9, + EFULL = -10 + }; + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/include/types/syscallid.h b/include/types/syscallid.h new file mode 100644 index 0000000..d2aaf0d --- /dev/null +++ b/include/types/syscallid.h @@ -0,0 +1,36 @@ +#ifndef KERNEL_SYSCALLID_H +#define KERNEL_SYSCALLID_H + +#ifdef __cplusplus +extern "C" +{ +#endif + + typedef enum syscallid_t + { + SYS_PRINTK = 0, + SYS_MMAP, + SYS_MUNMAP, + SYS_CLONE, + SYS_TERMINATE, + SYS_EXEC, + SYS_YIELD, + SYS_SIGRAISE, + SYS_SIGRET, + SYS_SIGWAIT, + SYS_SIGACTION, + SYS_OPEN, + SYS_CLOSE, + SYS_CREATE, + SYS_UNLINK, + SYS_READ, + SYS_WRITE, + SYS_FDDUP, + SYS_CREATE_PIPE + } syscallid_t; + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/src/aarch64/aarch64.cpp b/src/aarch64/aarch64.cpp new file mode 100644 index 0000000..6126d37 --- /dev/null +++ b/src/aarch64/aarch64.cpp @@ -0,0 +1,79 @@ +#include +#include "mmgmt.h" +#include "sysreg.h" +#include "irq/exceptions.h" +#include "util/string.h" +#include "devices/uart.h" +#include "devices/timer.h" +#include "util/log.h" +#include "sched/context.h" +#include "irq/interrupts.h" +#include "loader/elf.h" +#include "util/hacf.h" +#include "sched/process.h" +#include "kernel.h" +#include "fs/fat32/fat32.h" +#include "containers/binary_search_tree.h" +#include "util/charstream.h" + +using namespace kernel::memory; +using namespace kernel::devices; +using namespace kernel::sched; +using namespace kernel::interrupt; +using namespace kernel::loader; +using namespace kernel::fs; + +UART uart; + +SystemTimer timer; + +extern "C" void aarch64_boot(uint64_t dtb, uint64_t kernelSize) +{ + Interrupts::init(); + // Construct UART driver + new (&uart) UART((void *)0xFFFFFF803F201000); + Interrupts::insertHandler(57, &uart); + + // Initialize log + logInit(&uart); + + kernelLog(LogLevel::INFO, "CROS startup..."); + kernelLog(LogLevel::DEBUG, "DTB Location = %016x", dtb); + kernelLog(LogLevel::DEBUG, "Kernel size = %i MiB", kernelSize >> 20); + + MemoryMap map; + map.place(MemoryMap::MemoryType::AVAILABLE, 0, 0x20000000); + map.place(MemoryMap::MemoryType::MMIO, 0x3f000000, 0x1000000); + map.place(MemoryMap::MemoryType::UNAVAILABLE, (unsigned long)0, kernelSize); + map.place(MemoryMap::MemoryType::UNAVAILABLE, (unsigned long)&__end - (unsigned long)&__high_mem, PageAllocator::mapSize(map, page_size)); + map.place(MemoryMap::MemoryType::UNAVAILABLE, (unsigned long)0x8000000, 1 << 26); + kernel::kernel.initMemory(map, kernelSize); + + setPageEntry(2, &__high_mem + 0x100000000, 0, PAGE_RW); + kernel::kernel.initRamFS((void *)(&__high_mem + 0x108000000)); + + new (&timer) SystemTimer(50); + Interrupts::insertHandler(0, &timer); + Interrupts::insertHandler(1, &timer); + Interrupts::insertHandler(2, &timer); + Interrupts::insertHandler(3, &timer); + + char *const argv[] = {"/bin/init", nullptr}; + char *const envp[] = {"cwd=/", nullptr}; + + kernelLog(LogLevel::DEBUG, "Creating first process."); + Process *p = new Process(); + kernel::kernel.addProcess(*p); + kernel::kernel.switchTask(); + if (kernel::kernel.exec("/bin/init", argv, envp)) + { + kernelLog(LogLevel::PANIC, "Failed to load /bin/init"); + hacf(); + } + + kernelLog(LogLevel::INFO, "Bootup complete, loading first process..."); + load_context(kernel::kernel.getActiveProcess()->getContext()); + + while (1) + asm("nop"); +} \ No newline at end of file diff --git a/src/aarch64/aarch64.ld b/src/aarch64/aarch64.ld new file mode 100644 index 0000000..9212d01 --- /dev/null +++ b/src/aarch64/aarch64.ld @@ -0,0 +1,52 @@ +ENTRY(_start) + +SECTIONS +{ + __high_mem = 0xFFFFFF8000000000; + __load_addr = 0x80000; + __begin = __high_mem + __load_addr; + + . = __load_addr; + .boot : + { + KEEP(*(.boot.text)) + KEEP(*(.boot.data)) + src/aarch64/bootstrap.o + } + . = __high_mem + ALIGN(4096); + + __text_start = .; + .text : AT(ADDR(.text) - __high_mem) + { + *(.text) + } + . = ALIGN(4096); + __text_end = .; + + __rodata_start = .; + .rodata : AT(ADDR(.rodata) - __high_mem) + { + *(.rodata) + } + . = ALIGN(4096); + __rodata_end = .; + + __data_start = .; + .data : AT(ADDR(.data) - __high_mem) + { + *(.data) + } + . = ALIGN(4096); + __data_end = .; + + __bss_start = .; + .bss : AT(ADDR(.bss) - __high_mem) + { + bss = .; + *(.bss) + } + . = ALIGN(4096); + __bss_end = .; + __bss_size = __bss_end - __bss_start; + __end = .; +} \ No newline at end of file diff --git a/src/aarch64/boot.s b/src/aarch64/boot.s new file mode 100644 index 0000000..008bc30 --- /dev/null +++ b/src/aarch64/boot.s @@ -0,0 +1,181 @@ +.section ".boot.data" +.balign 4096 +ttl0: +.skip 4096 +ttl1: +.skip 4096 +ttl2: +.skip 4096 + +.section ".boot.text" + +// Entry point for the kernel. Registers: +// x0 -> 32 bit pointer to DTB in memory (primary core only) / 0 (secondary cores) +.globl _start +_start: + + // Check if we're already in EL1 + /*ldr x6, =_el1_prepare + bic x6, x6, #0xFFFF << 48 + mrs x5, CurrentEL + cmp x5, #8 + blt x6*/ + + // Disable IRQ routing to EL2, set EL1 execution mode to AArch64 + mrs x5, HCR_EL2 + orr x5, x5, #0x80000000 + bic x5, x5, #0x38 + msr HCR_EL2, x5 + + // Set virtual processor ID + mrs x5, MIDR_EL1 + msr VPIDR_EL2, x5 + + // Enable floating-point + mrs x5, CPACR_EL1 + orr x5, x5, #3 << 20 + msr CPACR_EL1, x5 + + // Set stack before our code + ldr x5, =_start + msr SP_EL1, x5 + + // Point exception link register to _el1_begin + ldr x5, =_el1_begin + msr ELR_EL2, x5 + + // Modify saved program status register to mask exceptions and be in EL1 + mrs x5, SPSR_EL2 + bic x5, x5, #0xF + mov x6, #0x3C5 + orr x5, x5, x6 + msr SPSR_EL2, x5 + + // Fall into EL1, jump to _el1_begin + eret +_el1_prepare: + ldr x5, =_start + mov sp, x5 +_el1_begin: + // Save x0 on stack + stp x0, x1, [sp, #-16]! + + ldr x0, =bootstrap_vector_table_el1 + msr VBAR_EL1, x0 + + // Prepare arguments for call to mmio_init + ldr x0, =0x2000000 + ldr x1, =ttl0 + ldr x2, =ttl1 + ldr x3, =ttl2 + ldr x4, =mmu_init + + // Call mmio_init + blr x4 + + // Restore x0 + ldp x0, x1, [sp], #16 + ldr x1, =0x2000000 + + // Initialize stack in high memory + ldr x5, =__begin + mov sp, x5 + + stp x0, x1, [sp, #-16]! + + ldr x5, =_init + blr x5 + + ldp x0, x1, [sp], #16 + + ldr x5, =aarch64_boot + blr x5 + +halt: + wfe + b halt + +.balign 0x800 +bootstrap_vector_table_el1: + bootstrap__ex_el1_curr_sp0_sync: + mrs x0, ESR_EL1 + bl handle_ex + b halt + + .balign 0x80 + bootstrap__ex_el1_curr_sp0_irq: + bl handle_ex + b halt + + .balign 0x80 + bootstrap__ex_el1_curr_sp0_fiq: + bl handle_ex + b halt + + .balign 0x80 + bootstrap__ex_el1_curr_sp0_serror: + bl handle_ex + b halt + + .balign 0x80 + bootstrap__ex_el1_curr_spx_sync: + ldr x0, =_start + mov sp, x0 + mrs x0, ESR_EL1 + bl handle_ex + b halt + + .balign 0x80 + bootstrap__ex_el1_curr_spx_irq: + bl handle_ex + b halt + + .balign 0x80 + bootstrap__ex_el1_curr_spx_fiq: + bl handle_ex + b halt + + .balign 0x80 + bootstrap__ex_el1_curr_spx_serror: + bl handle_ex + b halt + + .balign 0x80 + bootstrap__ex_el1_lower_aarch64_sync: + bl handle_ex + b halt + + .balign 0x80 + bootstrap__ex_el1_lower_aarch64_irq: + bl handle_ex + b halt + + .balign 0x80 + bootstrap__ex_el1_lower_aarch64_fiq: + bl handle_ex + b halt + + .balign 0x80 + bootstrap__ex_el1_lower_aarch64_serror: + bl handle_ex + b halt + + .balign 0x80 + bootstrap__ex_el1_lower_aarch32_sync: + bl handle_ex + b halt + + .balign 0x80 + bootstrap__ex_el1_lower_aarch32_irq: + bl handle_ex + b halt + + .balign 0x80 + bootstrap__ex_el1_lower_aarch32_fiq: + bl handle_ex + b halt + + .balign 0x80 + bootstrap__ex_el1_lower_aarch32_serror: + bl handle_ex + b halt diff --git a/src/aarch64/bootstrap.cpp b/src/aarch64/bootstrap.cpp new file mode 100644 index 0000000..b0d0e1d --- /dev/null +++ b/src/aarch64/bootstrap.cpp @@ -0,0 +1,324 @@ +#include +#include +#include + +#define BASE 0x3f000000 + +#define MBOX_READ ((volatile unsigned int *)(BASE + 0xB880 + 0x00)) +#define MBOX_STATUS ((volatile unsigned int *)(BASE + 0xB880 + 0x18)) +#define MBOX_WRITE ((volatile unsigned int *)(BASE + 0xB880 + 0x20)) + +#define GPPUD ((volatile unsigned int *)(BASE + 0x200000 + 0x94)) +#define GPPUDCLK0 ((volatile unsigned int *)(BASE + 0x200000 + 0x98)) + +#define UART_DR ((volatile unsigned int *)(BASE + 0x200000 + 0x1000 + 0x00)) +#define UART_FR ((volatile unsigned int *)(BASE + 0x200000 + 0x1000 + 0x18)) +#define UART_IBRD ((volatile unsigned int *)(BASE + 0x200000 + 0x1000 + 0x24)) +#define UART_FBRD ((volatile unsigned int *)(BASE + 0x200000 + 0x1000 + 0x28)) +#define UART_LCRH ((volatile unsigned int *)(BASE + 0x200000 + 0x1000 + 0x2C)) +#define UART_CR ((volatile unsigned int *)(BASE + 0x200000 + 0x1000 + 0x30)) +#define UART_IMSC ((volatile unsigned int *)(BASE + 0x200000 + 0x1000 + 0x38)) +#define UART_ICR ((volatile unsigned int *)(BASE + 0x200000 + 0x1000 + 0x44)) + +#define TIME_CLO ((volatile unsigned int *)(BASE + 0x3004)) +#define TIME_CHI ((volatile unsigned int *)(BASE + 0x3008)) + +enum format_flags_t +{ + FORMAT_PADDING = '0', + FORMAT_WIDTH = '*', + + FORMAT_SIGNED_DECIMAL = 'i', + FORMAT_UNSIGNED_DECIMAL = 'u', + FORMAT_UNSIGNED_OCTAL = 'o', + FORMAT_UNSIGNED_HEX = 'x', + FORMAT_STRING = 's', + FORMAT_CHARACTER = 'c', + FORMAT_COUNT = 'n', + FORMAT_PERCENT = '%' + +}; + +// Loop times in a way that the compiler won't optimize away +static inline void delay(int32_t count) +{ + while (count > 0) + { + count--; + } +} + +static char *itoa(unsigned long n, unsigned int base, unsigned int width) +{ + if (base < 2 || base > 16) + { + return NULL; + } + static const char *digits = "0123456789abcdef"; + static char buffer[65]; + char *s = &buffer[64]; + *s = 0; + unsigned int count = 0; + do + { + *--s = digits[n % base]; + n /= base; + count++; + } while (count < width || n != 0); + return s; +} + +// A Mailbox message with set clock rate of PL011 to 3MHz tag +volatile unsigned int __attribute__((aligned(16))) mbox[9] = { + 9 * 4, 0, 0x38002, 12, 8, 2, 3000000, 0, 0}; + +static void uart_init() +{ + *UART_CR = 0; + *GPPUD = 0; + delay(150); + + *GPPUDCLK0 = (1 << 14) | (1 << 15); + delay(150); + + *GPPUDCLK0 = 0; + *UART_ICR = 0x7FF; + + unsigned int r = (((unsigned int)(&mbox) & ~0xF) | 8); + while (*MBOX_STATUS & 0x80000000) + { + } + + *MBOX_WRITE = r; + while ((*MBOX_STATUS & 0x40000000) || *MBOX_READ != r) + { + } + + *UART_IBRD = 1; + *UART_FBRD = 40; + *UART_LCRH = (1 << 4) | (1 << 5) | (1 << 6); + //*UART_IMSC = 0; //(1 << 1) | (1 << 4) | (1 << 5) | (1 << 6) | (1 << 7) | (1 << 8) | (1 << 9) | (1 << 10); + *UART_CR = (1 << 0) | (1 << 8) | (1 << 9); +} + +static void uart_puts(const char *str) +{ + for (const char *s = str; *s != '\0'; s++) + { + while (*UART_FR & (1 << 5)) + { + } + *UART_DR = *s; + } +} + +static void uart_putc(unsigned int c) +{ + while (*UART_FR & (1 << 5)) + { + } + *UART_DR = c; +} + +static unsigned long get_time() +{ + return ((unsigned long)*TIME_CHI << 32UL) + (unsigned long)*TIME_CLO; +} + +static int vprintf(const char *format, va_list valist) +{ + while (*format) + { + if (*format == '%') + { + size_t width = 0; + bool padding = false; + switch (*++format) + { + case FORMAT_PADDING: + padding = true; + format++; + break; + } + while (*format >= '0' && *format <= '9') + { + width = (width * 10) + *format - '0'; + format++; + } + switch (*format) + { + case FORMAT_SIGNED_DECIMAL: + { + int n = va_arg(valist, int); + if (n < 0) + { + uart_puts("-"); + n *= -1; + } + uart_puts(itoa((unsigned int)n, 10, width)); + break; + } + case FORMAT_UNSIGNED_DECIMAL: + uart_puts(itoa(va_arg(valist, unsigned int), 10, width)); + break; + case FORMAT_UNSIGNED_OCTAL: + uart_puts(itoa(va_arg(valist, unsigned int), 8, width)); + break; + case FORMAT_UNSIGNED_HEX: + uart_puts(itoa(va_arg(valist, unsigned long), 16, width)); + break; + case FORMAT_STRING: + uart_puts(va_arg(valist, const char *)); + break; + case FORMAT_CHARACTER: + uart_putc(va_arg(valist, unsigned int)); + break; + case FORMAT_PERCENT: + uart_putc('%'); + break; + } + } + else + { + uart_putc(*format); + } + format++; + } + return 0; +} + +static int printf(const char *format, ...) +{ + va_list valist; + va_start(valist, format); + vprintf(format, valist); + va_end(valist); + return 0; +} + +static void log(const char *fmt, ...) +{ + unsigned long time = get_time(); + printf("[bootstrap][%i:%03i:%03i]: ", time / 1000000, (time / 1000) % 1000, time % 1000); + va_list valist; + va_start(valist, fmt); + vprintf(fmt, valist); + uart_puts("\r\n"); + va_end(valist); +} + +extern "C" void handle_ex(uint64_t syndrome) +{ + uint64_t far; + asm volatile("mrs %0, far_el1" : "=r"(far)); + log("Fatal error during bootstrap:\r\n\tESR_EL1 = %016x\r\n\tFAR_EL1 = %016x", syndrome, far); + while (true) + { + } +} + +extern "C" void mmu_init(uint64_t kernel_size, uint64_t *level0, uint64_t *level1, uint64_t *level2) +{ + uart_init(); + log("Bootstrap start:\r\n\tkernel_size: %i MiB\r\n\tlevel0: %08x\r\n\tlevel1: %08x\r\n\tlevel2: %08x", kernel_size >> 20, level0, level1, level2); + void *linearAddress = (void *)0; + unsigned long offset = (unsigned long)linearAddress & 0x0000FFFFFFFFFFFF; + int level0Index = offset >> 39; + int level1Index = offset >> 30; + int level2Index = offset >> 21; + + /* + * Point level 0 entry at level 1 entry; level 1 entry at level 2 entry, + * level 2 entry at physical address 0. Maps first 2MiB in kernel address + * space to first 2 MiB of physical addresses. + */ + level0[level0Index] = (uint64_t)level1 | 1027; + level1[level1Index] = (uint64_t)level2 | 1027; + for (unsigned long p = 0; p < kernel_size; p += 0x200000) + { + level2[level2Index + (p / 0x200000)] = p | 1025; + } + level2[504] = 0x3f000000 | 1025 | 4; + level2[505] = 0x3f200000 | 1025 | 4; + + log("Filled kernel table entries."); + + /* + * Point last entry of level 0 table at level 0 table. + * Maps all tage tables into virtual address space, greatly simplifying + * process of manipulating tables. + */ + level1[511] = (uint64_t)level1 | 1027; + + log("Filled loopback entry."); + + /* + * Point both translation table base registers to level0 + * (allows creation of temporary identity map) + */ + asm volatile("msr TTBR0_EL1, %0" ::"r"(level1 + 1)); + asm volatile("msr TTBR1_EL1, %0" ::"r"(level1 + 1)); + + log("Set TTBR registers."); + + uint64_t mmfr0; + asm volatile("mrs %0, id_aa64mmfr0_el1" : "=r"(mmfr0)); + + log("ID_AA64MMFR0 = %016x", mmfr0); + + unsigned long mair = (0xFF << 0) | // AttrIdx=0: normal, IWBWA, OWBWA, NTR + (0x04 << 8) | // AttrIdx=1: device, nGnRE (must be OSH too) + (0x44 << 16); // AttrIdx=2: non cacheable + asm volatile("msr MAIR_EL1, %0" : : "r"(mair)); + + log("Set MAIR_EL1 = %016x", mair); + + /* + * Configure the translation control register: + * - 4KiB granules for both translation tables + * - 48-bit intermediate physical address size + * - 16 most significant bits select either TTBR0 or TTBR1 + */ + uint64_t tcr; + asm volatile("mrs %0, TCR_EL1" : "=r"(tcr)); + tcr = (0b00LL << 37) | // TBI=0, no tagging + ((mmfr0 & 0xF) << 32) | // IPS=autodetected + (0b10LL << 30) | // TG1=4k + (0b11LL << 28) | // SH1=3 inner + (0b01LL << 26) | // ORGN1=1 write back + (0b01LL << 24) | // IRGN1=1 write back + (0b0LL << 23) | // EPD1 enable higher half + (25LL << 16) | // T1SZ=25, 3 levels (512G) + (0b00LL << 14) | // TG0=4k + (0b11LL << 12) | // SH0=3 inner + (0b01LL << 10) | // ORGN0=1 write back + (0b01LL << 8) | // IRGN0=1 write back + (0b0LL << 7) | // EPD0 enable lower half + (25LL << 0); // T0SZ=25, 3 levels (512G) + asm volatile("msr TCR_EL1, %0" ::"r"(tcr)); + asm volatile("isb"); + + log("Set TCR_EL1 = %016x", tcr); + + /* + * Enable MMU by setting bit 0 of SCTLR + */ + uint64_t sctlr; + asm volatile("dsb ish"); + asm volatile("isb"); + asm volatile("mrs %0, SCTLR_EL1" : "=r"(sctlr)); + sctlr |= 0xC00801; + sctlr &= ~((1 << 25) | // clear EE, little endian translation tables + (1 << 24) | // clear E0E + (1 << 19) | // clear WXN + (1 << 12) | // clear I, no instruction cache + (1 << 4) | // clear SA0 + (1 << 3) | // clear SA + (1 << 2) | // clear C, no cache at all + (1 << 1)); // clear A, no aligment check + asm volatile("msr SCTLR_EL1, %0" ::"r"(sctlr)); + asm volatile("isb"); + + log("Set SCTLR_EL1 = %016x", sctlr); + log("Enabled MMU."); +} \ No newline at end of file diff --git a/src/aarch64/crti.c b/src/aarch64/crti.c new file mode 100644 index 0000000..7f7795b --- /dev/null +++ b/src/aarch64/crti.c @@ -0,0 +1,19 @@ +typedef void (*func_ptr)(void); + +extern func_ptr _init_array_start[0], _init_array_end[0]; +extern func_ptr _fini_array_start[0], _fini_array_end[0]; + +void _init(void) +{ + for (func_ptr *func = _init_array_start; func != _init_array_end; func++) + (*func)(); +} + +void _fini(void) +{ + for (func_ptr *func = _fini_array_start; func != _fini_array_end; func++) + (*func)(); +} + +func_ptr _init_array_start[0] __attribute__((used, section(".init_array"), aligned(sizeof(func_ptr)))) = {}; +func_ptr _fini_array_start[0] __attribute__((used, section(".fini_array"), aligned(sizeof(func_ptr)))) = {}; \ No newline at end of file diff --git a/src/aarch64/crtn.c b/src/aarch64/crtn.c new file mode 100644 index 0000000..6d4fc5d --- /dev/null +++ b/src/aarch64/crtn.c @@ -0,0 +1,4 @@ +typedef void (*func_ptr)(void); + +func_ptr _init_array_end[0] __attribute__((used, section(".init_array"), aligned(sizeof(func_ptr)))) = {}; +func_ptr _fini_array_end[0] __attribute__((used, section(".fini_array"), aligned(sizeof(func_ptr)))) = {}; \ No newline at end of file diff --git a/src/aarch64/dosyscall.s b/src/aarch64/dosyscall.s new file mode 100644 index 0000000..10000e0 --- /dev/null +++ b/src/aarch64/dosyscall.s @@ -0,0 +1,6 @@ +.section ".text" +.global do_syscall +.type do_syscall, "function" +do_syscall: + svc #0 + ret diff --git a/src/aarch64/irq/exceptionclass.h b/src/aarch64/irq/exceptionclass.h new file mode 100644 index 0000000..b5d79bf --- /dev/null +++ b/src/aarch64/irq/exceptionclass.h @@ -0,0 +1,42 @@ +#ifndef KERNEL_EXCEPTIONCLASS_H +#define KERNEL_EXCEPTIONCLASS_H + +enum class ExceptionClass +{ + UNKNOWN = 0x00, + TRAPPED_WF = 0x01, + TRAPPED_MCR = 0x03, + TRAPPED_MCRR = 0x04, + TRAPPED_MCR_2 = 0x05, + TRAPPED_LDC = 0x06, + FP_ACCESS = 0x07, + TRAPPED_LD64B = 0x0A, + TRAPPED_MRRC = 0x0C, + BRANCH_TARGET = 0x0D, + ILLEGAL_EXE_STATE = 0x0E, + SVC_AARCH32 = 0x11, + SVC_AARCH64 = 0x15, + TRAPPED_SYS_INST = 0x18, + SVE_ACCESS = 0x19, + POINTER_AUCH = 0x1C, + INST_ABORT_EL0 = 0x20, + INST_ABORT_EL1 = 0x21, + PC_ALIGNMENT = 0x22, + DATA_ABORT_EL0 = 0x24, + DATA_ABORT_EL1 = 0x25, + SP_ALIGNMENT = 0x26, + MEMORY_OP = 0x27, + TRAPPED_FP_AARCH32 = 0x28, + TRAPPED_FP_AARCH64 = 0x2C, + SERROR = 0x2F, + BRK_EL0 = 0x30, + BRK_EL1 = 0x31, + STEP_EL0 = 0x32, + STEP_EL1 = 0x33, + WATCH_EL0 = 0x34, + WATCH_EL1 = 0x35, + BKPT_AARCH32 = 0x38, + BRK_AARCH64 = 0x3C +}; + +#endif \ No newline at end of file diff --git a/src/aarch64/irq/exceptions.h b/src/aarch64/irq/exceptions.h new file mode 100644 index 0000000..e97ce0d --- /dev/null +++ b/src/aarch64/irq/exceptions.h @@ -0,0 +1,9 @@ +#ifndef KERNEL_EXCEPTIONS_H +#define KERNEL_EXCEPTIONS_H + +/** + * @brief Symbol located at the start of the interrupt vector table + */ +extern int vector_table_el1; + +#endif \ No newline at end of file diff --git a/src/aarch64/irq/exceptions.s b/src/aarch64/irq/exceptions.s new file mode 100644 index 0000000..4f9e69a --- /dev/null +++ b/src/aarch64/irq/exceptions.s @@ -0,0 +1,244 @@ +.section ".text" + +.macro save_context + // Make room on stack for process context + sub sp, sp, #816 + + // Save FP registers + st4 {v0.2d, v1.2d, v2.2d, v3.2d}, [sp], #64 + st4 {v4.2d, v5.2d, v6.2d, v7.2d}, [sp], #64 + st4 {v8.2d, v9.2d, v10.2d, v11.2d}, [sp], #64 + st4 {v12.2d, v13.2d, v14.2d, v15.2d}, [sp], #64 + st4 {v16.2d, v17.2d, v18.2d, v19.2d}, [sp], #64 + st4 {v20.2d, v21.2d, v22.2d, v23.2d}, [sp], #64 + st4 {v24.2d, v25.2d, v26.2d, v27.2d}, [sp], #64 + st4 {v28.2d, v29.2d, v30.2d, v31.2d}, [sp], #64 + + // Save X0-X29 + stp x0, x1, [sp], #16 + stp x2, x3, [sp], #16 + stp x4, x5, [sp], #16 + stp x6, x7, [sp], #16 + stp x8, x9, [sp], #16 + stp x10, x11, [sp], #16 + stp x12, x13, [sp], #16 + stp x14, x15, [sp], #16 + stp x16, x17, [sp], #16 + stp x18, x19, [sp], #16 + stp x20, x21, [sp], #16 + stp x22, x23, [sp], #16 + stp x24, x25, [sp], #16 + stp x26, x27, [sp], #16 + stp x28, x29, [sp], #16 + + // Save x30 and SP + mrs x8, sp_el0 + stp x30, x8, [sp], #16 + + // Save PC and program status + mrs x8, elr_el1 + mrs x9, spsr_el1 + stp x8, x9, [sp], #16 + + // Save FPCR and FPSR + mrs x8, fpcr + mrs x9, fpsr + stp x8, x9, [sp], #16 + + // Save kernel SP + mov x8, sp + add x8, x8, #16 + stp x8, x8, [sp], #16 + + // Set SP to beginning of context struct + sub sp, sp, #816 +.endm + +_sync_el0_wrapper: + save_context + + // Obtain and unpack exception syndrome + mrs x8, esr_el1 + mov x9, x8 + ldr x10, = 0x1F03FFFFFF + bic x8, x8, x10 + lsr x8, x8, #26 + mvn x10, x10 + bic x9, x9, x10 + + // Point X5 to process context struct in case we are doing a syscall + mov x5, sp + + // If exception class 0x15, we are doing a syscall + cmp x8, #0x15 + bne _not_syscall + + bl do_syscall + bl load_context + +_not_syscall: + // Else, call handle_sync with exception class and ISR + mov x0, x8 + mov x1, x9 + mov x2, x5 + bl handle_sync + + // Load next process context returned from handle_sync + bl load_context + +_sync_el1_wrapper: + // Save caller-saved registers + stp x0, x1, [sp, #-16]! + stp x2, x3, [sp, #-16]! + stp x4, x5, [sp, #-16]! + stp x6, x7, [sp, #-16]! + stp x8, x9, [sp, #-16]! + stp x10, x11, [sp, #-16]! + stp x12, x13, [sp, #-16]! + stp x14, x15, [sp, #-16]! + stp x16, x17, [sp, #-16]! + stp x18, lr, [sp, #-16]! + + // Unpack exception syndrome register + mrs x0, esr_el1 + mov x1, x0 + ldr x2, = 0x1F03FFFFFF + bic x0, x0, x2 + lsr x0, x0, #26 + mvn x2, x2 + bic x1, x1, x2 + + // Give handle_sync() a NULL-pointer as a process context, as we came from kernelspace + mov x2, #0 + + // Call handle_sync() + bl handle_sync + + // Restore saved registers + ldp x18, lr, [sp], #16 + ldp x16, x17, [sp], #16 + ldp x14, x15, [sp], #16 + ldp x12, x13, [sp], #16 + ldp x10, x11, [sp], #16 + ldp x8, x9, [sp], #16 + ldp x6, x7, [sp], #16 + ldp x4, x5, [sp], #16 + ldp x2, x3, [sp], #16 + ldp x0, x1, [sp], #16 + + // Exception return + eret + +_irq_el0_wrapper: + save_context + + // Call find_irq_source() to obtain an interrupt number + bl find_irq_source + + // Point X1 to saved process context, then call handle_irq() + mov x1, sp + bl handle_irq + + // Load context returned by handle_irq() + bl load_context + +_irq_el1_wrapper: + // Save caller-saved registers + stp x0, x1, [sp, #-16]! + stp x2, x3, [sp, #-16]! + stp x4, x5, [sp, #-16]! + stp x6, x7, [sp, #-16]! + stp x8, x9, [sp, #-16]! + stp x10, x11, [sp, #-16]! + stp x12, x13, [sp, #-16]! + stp x14, x15, [sp, #-16]! + stp x16, x17, [sp, #-16]! + stp x18, lr, [sp, #-16]! + + // Call find_irq_source() to obtain an interrupt number + bl find_irq_source + + // Set X1 to NULL because we came from kernelspace, then call handle_irq() + mov x1, #0 + bl handle_irq + + // Restore saved registers + ldp x18, lr, [sp], #16 + ldp x16, x17, [sp], #16 + ldp x14, x15, [sp], #16 + ldp x12, x13, [sp], #16 + ldp x10, x11, [sp], #16 + ldp x8, x9, [sp], #16 + ldp x6, x7, [sp], #16 + ldp x4, x5, [sp], #16 + ldp x2, x3, [sp], #16 + ldp x0, x1, [sp], #16 + + // Exception return + eret + +.balign 0x800 +.global vector_table_el1 +vector_table_el1: + _ex_el1_curr_sp0_sync: + b hacf + + .balign 0x80 + _ex_el1_curr_sp0_irq: + b hacf + + .balign 0x80 + _ex_el1_curr_sp0_fiq: + b hacf + + .balign 0x80 + _ex_el1_curr_sp0_serror: + b hacf + + .balign 0x80 + _ex_el1_curr_spx_sync: + b _sync_el1_wrapper + + .balign 0x80 + _ex_el1_curr_spx_irq: + b _irq_el1_wrapper + + .balign 0x80 + _ex_el1_curr_spx_fiq: + b _irq_el1_wrapper + + .balign 0x80 + _ex_el1_curr_spx_serror: + b hacf + + .balign 0x80 + _ex_el1_lower_aarch64_sync: + b _sync_el0_wrapper + + .balign 0x80 + _ex_el1_lower_aarch64_irq: + b _irq_el0_wrapper + + .balign 0x80 + _ex_el1_lower_aarch64_fiq: + b _irq_el0_wrapper + + .balign 0x80 + _ex_el1_lower_aarch64_serror: + b hacf + + .balign 0x80 + _ex_el1_lower_aarch32_sync: + b hacf + + .balign 0x80 + _ex_el1_lower_aarch32_irq: + b hacf + + .balign 0x80 + _ex_el1_lower_aarch32_fiq: + b hacf + + .balign 0x80 + _ex_el1_lower_aarch32_serror: + b hacf diff --git a/src/aarch64/irq/interrupts.cpp b/src/aarch64/irq/interrupts.cpp new file mode 100644 index 0000000..b896a3d --- /dev/null +++ b/src/aarch64/irq/interrupts.cpp @@ -0,0 +1,30 @@ +#include "irq/interrupts.h" +#include "exceptions.h" +#include "aarch64/sysreg.h" +#include "devices/mmio.h" + +void kernel::interrupt::Interrupts::init() +{ + set_vbar_el1(&vector_table_el1); + disable(); + for (int i = 0; i < HANDLER_COUNT; i++) + { + handlers[i] = nullptr; + } + mmio_write((void *)MMIOOffset::INTR_IRQ_ENABLE_BASE, 0x80002); + // mmio_write((void *)MMIOOffset::INTR_IRQ_DISABLE_BASE, 0xFE); + mmio_write((void *)MMIOOffset::INTR_IRQ_ENABLE_1, 0x00000002 /*0xFFFFFFFF*/); + // mmio_write((void *)MMIOOffset::INTR_IRQ_DISABLE_1, 0x20000000); + mmio_write((void *)MMIOOffset::INTR_IRQ_ENABLE_2, 0x2000000); + // mmio_write((void *)MMIOOffset::INTR_IRQ_DISABLE_2, 0xFF6800); +} + +void kernel::interrupt::Interrupts::enable() +{ + set_daif(0); +} + +void kernel::interrupt::Interrupts::disable() +{ + set_daif(15 << 6); +} \ No newline at end of file diff --git a/src/aarch64/irq/irq.cpp b/src/aarch64/irq/irq.cpp new file mode 100644 index 0000000..ed453c9 --- /dev/null +++ b/src/aarch64/irq/irq.cpp @@ -0,0 +1,86 @@ +#include +#include "irq/interrupts.h" +#include "util/hacf.h" +#include "exceptionclass.h" +#include "syndromedataabort.h" +#include "devices/mmio.h" +#include "sched/context.h" +#include "../sysreg.h" +#include "util/log.h" +#include "kernel.h" + +void handlePageFault(ExceptionClass type, SyndromeDataAbort syndrome); + +extern "C" int find_irq_source() +{ + unsigned int pending1 = mmio_read((void *)MMIOOffset::INTR_IRQ_PENDING_1); + if (pending1 != 0) + { + return __builtin_ctz(pending1); + } + + unsigned int pending2 = mmio_read((void *)MMIOOffset::INTR_IRQ_PENDING_2); + if (pending2 != 0) + { + return 32 + __builtin_ctz(pending2); + } + + unsigned int base = mmio_read((void *)MMIOOffset::INTR_BASIC_PENDING) & 255; + if (base != 0) + { + return __builtin_ctz(base); + } + + return -1; +} + +extern "C" kernel::sched::Context *handle_sync(ExceptionClass type, unsigned long syndrome, kernel::sched::Context *ctx) +{ + switch (type) + { + case ExceptionClass::INST_ABORT_EL0: + kernelLog(LogLevel::PANIC, "Unhandled INST_ABORT_EL0, FAR_EL1 = %016x", get_far_el1()); + hacf(); + case ExceptionClass::INST_ABORT_EL1: + kernelLog(LogLevel::PANIC, "Unhandled INST_ABORT_EL1, FAR_EL1 = %016x", get_far_el1()); + hacf(); + case ExceptionClass::DATA_ABORT_EL0: + kernelLog(LogLevel::PANIC, "Unhandled DATA_ABORT_EL0, FAR_EL1 = %016x", get_far_el1()); + hacf(); + break; + case ExceptionClass::DATA_ABORT_EL1: + handlePageFault(type, *(SyndromeDataAbort *)&syndrome); + break; + case ExceptionClass::SVC_AARCH64: + case ExceptionClass::SVC_AARCH32: + break; + default: + kernelLog(LogLevel::PANIC, "Unimplemented exception class"); + hacf(); + break; + } + return ctx; +} + +extern "C" kernel::sched::Context *handle_irq(int source, kernel::sched::Context *ctx) +{ + using namespace kernel::interrupt; + if (kernel::kernel.getActiveProcess() != nullptr) + { + kernel::kernel.getActiveProcess()->storeContext(ctx); + } + + // kernelLog(LogLevel::DEBUG, "handle_irq(%i, %016x)", source, ctx); + + if (source < 0) + { + return ctx; + } + + Interrupts::callHandler(source); + if (kernel::kernel.getActiveProcess() != nullptr) + { + return kernel::kernel.getActiveProcess()->getContext(); + } + return ctx; +} diff --git a/src/aarch64/irq/syndromedataabort.h b/src/aarch64/irq/syndromedataabort.h new file mode 100644 index 0000000..64255ff --- /dev/null +++ b/src/aarch64/irq/syndromedataabort.h @@ -0,0 +1,100 @@ +#ifndef KERNEL_SYNDROMEDATAABORT_H +#define KERNEL_SYNDROMEDATAABORT_H + +#include + +enum class DataAbortStatus +{ + ADDR_SIZE_FAULT_0 = 0x00, + ADDR_SIZE_FAULT_1 = 0x01, + ADDR_SIZE_FAULT_2 = 0x02, + ADDR_SIZE_FAULT_3 = 0x03, + TRANSLATE_FAULT_0 = 0x04, + TRANSLATE_FAULT_1 = 0x05, + TRANSLATE_FAULT_2 = 0x06, + TRANSLATE_FAULT_3 = 0x07, + ACCESS_FAULT_0 = 0x08, + ACCESS_FAULT_1 = 0x09, + ACCESS_FAULT_2 = 0x0A, + ACCESS_FAULT_3 = 0x0B, + PERM_FAULT_0 = 0x0C, + PERM_FAULT_1 = 0x0D, + PERM_FAULT_2 = 0x0E, + PERM_FAULT_3 = 0x0F +}; + +struct SyndromeDataAbort +{ + /** + * @brief Data fault status code + */ + DataAbortStatus statusCode : 6; + + /** + * @brief Write not read. Set when an abort is caused by an instruction + * writing to a memory location. Clear when an abort is caused by a read. + */ + uint64_t wnr : 1; + + /** + * @brief + */ + uint64_t s1ptw : 1; + + /** + * @brief Cache maintenance. + */ + uint64_t cm : 1; + + /** + * @brief External abort. + */ + uint64_t ea : 1; + + /** + * @brief FAR not valid for a synchronous external abort. + */ + uint64_t fnv : 1; + + /** + * @brief Synchronous error type. + */ + uint64_t set : 2; + + /** + * @brief Indicates that the fault came from use of VNCR_EL2 register. + */ + uint64_t vncr : 1; + + /** + * @brief Aquire/release. + */ + uint64_t ar : 1; + + /** + * @brief 64-bit GP register transfer. + */ + uint64_t sf : 1; + + /** + * @brief Register number of the faulting instruction. + */ + uint64_t srt : 5; + + /** + * @brief Syndrome sign extend. + */ + uint64_t sse : 1; + + /** + * @brief Syndrome access size. + */ + uint64_t sas : 2; + + /** + * @brief Instruction syndrome valid. + */ + uint64_t isv : 1; +}; + +#endif \ No newline at end of file diff --git a/src/aarch64/sysreg.h b/src/aarch64/sysreg.h new file mode 100644 index 0000000..cf169bd --- /dev/null +++ b/src/aarch64/sysreg.h @@ -0,0 +1,76 @@ +#ifndef KERNEL_SYSREG_H +#define KERNEL_SYSREG_H + +extern "C" +void *get_vbar_el1(); + +extern "C" +void *set_vbar_el1(void *vbar_el1); + +extern "C" +int get_exception_level(); + +extern "C" +int get_daif(); + +extern "C" +int set_daif(int bits); + +extern "C" +void *get_far_el1(); + +extern "C" +unsigned long get_spsr_el1(); + +extern "C" +unsigned long set_spsr_el1(unsigned long spsr_el1); + +extern "C" +void *get_elr_el1(); + +extern "C" +void *set_elr_el1(void *elr_el1); + +extern "C" +unsigned long get_esr_el1(); + +extern "C" +unsigned long get_fpcr(); + +extern "C" +unsigned long set_fpcr(unsigned long fpcr); + +extern "C" +unsigned long get_fpsr(); + +extern "C" +unsigned long set_fpsr(unsigned long fpsr); + +extern "C" +unsigned long get_cpacr_el1(); + +extern "C" +unsigned long set_cpacr_el1(unsigned long cpacr_el1); + +extern "C" +unsigned long get_sctlr_el1(); + +extern "C" +unsigned long set_sctlr_el1(unsigned long sctlr_el1); + +extern "C" +unsigned long get_midr_el1(); + +extern "C" +unsigned long get_sp_el0(); + +extern "C" +unsigned long set_sp_el0(void *sp_el0); + +extern "C" +unsigned long get_ttbr0_el1(); + +extern "C" +unsigned long set_ttbr0_el1(unsigned long ttbr0_el1); + +#endif \ No newline at end of file diff --git a/src/aarch64/sysreg.s b/src/aarch64/sysreg.s new file mode 100644 index 0000000..420cbfe --- /dev/null +++ b/src/aarch64/sysreg.s @@ -0,0 +1,145 @@ +.section .text + +# void *get_vbar_el1(); +.global get_vbar_el1 +get_vbar_el1: + mrs x0, vbar_el1 + ret + +# void *set_vbar_el1(void *vbar_el1); +.global set_vbar_el1 +set_vbar_el1: + msr vbar_el1, x0 + ret + +# int get_exception_level(); +.global get_exception_level +get_exception_level: + mrs x0, CurrentEL + ret + +# int get_daif(); +.global get_daif +get_daif: + mrs x0, daif + ret + +# int set_daif(int bits); +.global set_daif +set_daif: + msr daif, x0 + ret + +# void *get_far_el1(); +.global get_far_el1 +get_far_el1: + mrs x0, far_el1 + ret + +# unsigned long get_spsr_el1(); +.global get_spsr_el1 +get_spsr_el1: + mrs x0, spsr_el1 + ret + +# unsigned long set_spsr_el1(unsigned long spsr_el1); +.global set_spsr_el1 +set_spsr_el1: + msr spsr_el1, x0 + ret + +# void *get_elr_el1(); +.global get_elr_el1 +get_elr_el1: + mrs x0, elr_el1 + ret + +# void *set_elr_el1(void *elr_el1); +.global set_elr_el1 +set_elr_el1: + msr elr_el1, x0 + ret + +# unsigned long get_esr_el1(); +.global get_esr_el1 +get_esr_el1: + mrs x0, esr_el1 + ret + +# unsigned long get_fpcr(); +.global get_fpcr +get_fpcr: + mrs x0, fpcr + ret + +# unsigned long set_fpcr(unsigned long fpcr); +.global set_fpcr +set_fpcr: + msr fpcr, x0 + ret + +# unsigned long get_fpsr(); +.global get_fpsr +get_fpsr: + mrs x0, fpsr + ret + +# unsigned long set_fpsr(unsigned long fpsr); +.global set_fpsr +set_fpsr: + msr fpsr, x0 + ret + +# unsigned long get_cpacr_el1(); +.global get_cpacr_el1 +get_cpacr_el1: + mrs x0, cpacr_el1 + ret + +# unsigned long set_cpacr_el1(unsigned long cpacr_el1); +.global set_cpacr_el1 +set_cpacr_el1: + msr cpacr_el1, x0 + ret + +# unsigned long get_sctlr_el1(); +.global get_sctlr_el1 +get_sctlr_el1: + mrs x0, sctlr_el1 + ret + +# unsigned long set_sctlr_el1(unsigned long sctlr_el1); +.global set_sctlr_el1 +set_sctlr_el1: + msr sctlr_el1, x0 + ret + +# unsigned long get_midr_el1(); +.global get_midr_el1 +get_midr_el1: + mrs x0, midr_el1 + ret + +# void *get_sp_el0(); +.global get_sp_el0 +get_sp_el0: + mrs x0, sp_el0 + ret + +# void *set_sp_el0(void *sp_el0); +.global set_sp_el0 +set_sp_el0: + msr sp_el0, x0 + ret + +# void *get_ttbr0_el1(); +.global get_ttbr0_el1 +get_ttbr0_el1: + mrs x0, ttbr0_el1 + ret + +# void *set_ttbr0_el1(unsigned long ttbr0_el1); +.global set_ttbr0_el1 +set_ttbr0_el1: + msr ttbr0_el1, x0 + ret diff --git a/src/containers/binary_search_tree.h b/src/containers/binary_search_tree.h new file mode 100644 index 0000000..c061215 --- /dev/null +++ b/src/containers/binary_search_tree.h @@ -0,0 +1,706 @@ +#ifndef BINARY_SEARCH_TREE_H +#define BINARY_SEARCH_TREE_H + +#include "math.h" +#include "string.h" + +template +class binary_search_tree +{ +public: + class node + { + public: + binary_search_tree *tree; + + K key; + V value; + + node *parent; + node *left; + node *right; + + int height; + + node(binary_search_tree *tree, K key, V &val); + + int balance() const; + void update_height(); + + node *successor() const; + void replace(node *other); + void unlink(); + + private: + }; + + class tree_iterator + { + public: + tree_iterator(); + + tree_iterator(node *p); + + tree_iterator(tree_iterator &other); + + tree_iterator &operator=(tree_iterator &other); + + tree_iterator &operator++(); + + bool operator!=(tree_iterator &other); + + K &operator*(); + + K *operator->(); + + private: + node *p; + }; + + binary_search_tree(); + ~binary_search_tree(); + + int size() const; + bool empty() const; + + void insert(K key, V &val); + void remove(K key); + void clear(); + + V &get(K key) const; + V &search(K key) const; + bool contains(K key) const; + + V &pop_min(); + V &pop_max(); + V &peek_min() const; + V &peek_max() const; + + tree_iterator begin(); + tree_iterator end(); + + string to_string() const; + +private: + node *root; + int _size; + + node *search_rec(K key) const; + void add_rec(node *cur, K key, V &val, bool &node_added); + void remove_rec(node *cur, K key); + void clear_rec(node *cur); + + void rebalance(node *node); + void rotate_right(node *node); + void rotate_left(node *node); + + void to_string_rec(K ***grid, node *cur, int row, int col, int height) const; +}; + +template +binary_search_tree::node::node(binary_search_tree *tree, K key, V &val) + : tree(tree), key(key), value(val), parent(nullptr), left(nullptr), right(nullptr), height(1) {} + +template +int binary_search_tree::node::balance() const +{ + if (left == nullptr && right == nullptr) + return 0; + else if (left == nullptr) + return -right->height; + else if (right == nullptr) + return left->height; + else + return left->height - right->height; +} + +template +void binary_search_tree::node::update_height() +{ + if (left == nullptr && right == nullptr) + height = 1; + else if (left == nullptr) + height = 1 + right->height; + else if (right == nullptr) + height = 1 + left->height; + else + height = 1 + max(left->height, right->height); +} + +template +typename binary_search_tree::node *binary_search_tree::node::successor() const +{ + if (right != nullptr) + { + node *cur = right; + while (cur->left != nullptr) + cur = cur->left; + return cur; + } + else if (left != nullptr) + { + node *cur = left; + while (cur->right != nullptr) + cur = cur->right; + return cur; + } + else + { + return nullptr; + } +} + +template +void binary_search_tree::node::replace(node *other) +{ + key = other->key; + value = other->value; + + node *succ = other->successor(); + if (succ == nullptr) + { + node *other_parent = other->parent; + other->unlink(); + delete other; + tree->rebalance(other_parent); + } + else + { + node *succ_parent = succ->parent; + other->replace(succ); + tree->rebalance(succ_parent); + } +} + +template +void binary_search_tree::node::unlink() +{ + if (parent != nullptr) + { + if (parent->left == this) + parent->left = nullptr; + else + parent->right = nullptr; + } +} + +template +binary_search_tree::binary_search_tree() + : root(nullptr), _size(0) {} + +template +binary_search_tree::~binary_search_tree() +{ + clear(); +} + +template +int binary_search_tree::size() const +{ + return _size; +} + +template +bool binary_search_tree::empty() const +{ + return _size == 0; +} + +template +void binary_search_tree::insert(K key, V &val) +{ + bool node_added = false; + add_rec(root, key, val, node_added); +} + +template +void binary_search_tree::remove(K key) +{ + remove_rec(root, key); +} + +template +V &binary_search_tree::get(K key) const +{ + binary_search_tree::node *node = search_rec(key); + if (node == nullptr || node->key != key) + return root->value; + else + return node->value; +} + +template +V &binary_search_tree::search(K key) const +{ + binary_search_tree::node *node = search_rec(key); + if (node == nullptr) + return root->value; + else + return node->value; +} + +template +bool binary_search_tree::contains(K key) const +{ + binary_search_tree::node *node = search_rec(key); + if (node == nullptr || node->key != key) + return false; + else + return true; +} + +template +void binary_search_tree::clear() +{ + clear_rec(root); + root = nullptr; + _size = 0; +} + +template +V &binary_search_tree::peek_min() const +{ + node *cur = root; + + if (cur == nullptr) + return root->value; + + while (cur->left != nullptr) + cur = cur->left; + + return cur->value; +} + +template +V &binary_search_tree::peek_max() const +{ + node *cur = root; + + if (cur == nullptr) + return root->value; + + while (cur->right != nullptr) + cur = cur->right; + + return cur->value; +} + +template +inline typename binary_search_tree::tree_iterator binary_search_tree::begin() +{ + if (root == nullptr) + { + return tree_iterator(); + } + node *p = root; + while (p->left != nullptr) + { + p = p->left; + } + return tree_iterator(p); +} + +template +inline typename binary_search_tree::tree_iterator binary_search_tree::end() +{ + return tree_iterator(); +} + +template +V &binary_search_tree::pop_min() +{ + node *cur = root; + + if (cur == nullptr) + return root->value; + + while (cur->left != nullptr) + cur = cur->left; + + V &val = cur->value; + remove_rec(root, cur->key); + + return val; +} + +template +V &binary_search_tree::pop_max() +{ + node *cur = root; + + if (cur == nullptr) + return root->value; + + while (cur->right != nullptr) + cur = cur->right; + + V &val = cur->value; + remove_rec(root, cur->key); + + return val; +} + +template +string binary_search_tree::to_string() const +{ + string str = ""; + + if (root == nullptr) + return str; + + int height = root->height; + int cols = (1 << height) - 1; + K ***grid = new K **[height]; + for (int i = 0; i < height; i++) + grid[i] = new K *[cols]; + + for (int i = 0; i < height; i++) + { + for (int j = 0; j < cols; j++) + { + grid[i][j] = nullptr; + } + } + + to_string_rec(grid, root, 0, cols / 2, height); + + for (int i = 0; i < height; i++) + { + for (int j = 0; j < cols; j++) + { + if (grid[i][j] == nullptr) + { + str += " "; + } + else + { + str += ('0' + *(grid[i][j])); + str += " "; + } + } + if (i != height - 1) + str += "\n"; + } + + return str; +} + +template +typename binary_search_tree::node *binary_search_tree::search_rec(K key) const +{ + binary_search_tree::node *cur = root; + while (cur != nullptr) + { + if (cur->key > key) + { + if (cur->left == nullptr) + return cur; + cur = cur->left; + } + else if (cur->key < key) + { + if (cur->right == nullptr) + return cur; + cur = cur->right; + } + else + { + return cur; + } + } + return nullptr; +} + +template +void binary_search_tree::add_rec(node *cur, K key, V &val, bool &node_added) +{ + if (root == nullptr) + { + _size++; + node_added = true; + root = new node(this, key, val); + cur = root; + } + else if (cur->key > key) + { + if (cur->left != nullptr) + { + add_rec(cur->left, key, val, node_added); + } + else + { + _size++; + node_added = true; + cur->left = new node(this, key, val); + cur->left->parent = cur; + } + } + else if (cur->key < key) + { + if (cur->right != nullptr) + { + add_rec(cur->right, key, val, node_added); + } + else + { + _size++; + node_added = true; + cur->right = new node(this, key, val); + cur->right->parent = cur; + } + } + else + { + if (node_added) + { + return; + } + else if (cur->right != nullptr) + { + add_rec(cur->right, key, val, node_added); + } + else + { + _size++; + node_added = true; + cur->right = new node(this, key, val); + cur->right->parent = cur; + } + } + + rebalance(cur); +} + +template +void binary_search_tree::remove_rec(node *cur, K key) +{ + if (cur == nullptr) + return; + + if (cur->key > key) + { + remove_rec(cur->left, key); + } + else if (cur->key < key) + { + remove_rec(cur->right, key); + } + else + { + node *succ = cur->successor(); + if (succ == nullptr) + { + if (root == cur) + root = nullptr; + node *cur_parent = cur->parent; + cur->unlink(); + delete cur; + cur = nullptr; + if (cur_parent != nullptr) + rebalance(cur_parent); + } + else + { + node *succ_parent = succ->parent; + cur->replace(succ); + rebalance(succ_parent); + } + _size--; + } + + if (cur == nullptr) + return; + + rebalance(cur); +} + +template +void binary_search_tree::clear_rec(node *cur) +{ + if (cur->left != nullptr && cur->right != nullptr) + { + clear_rec(cur->left); + clear_rec(cur->right); + } + else if (cur->left != nullptr) + { + clear_rec(cur->left); + } + else if (cur->right != nullptr) + { + clear_rec(cur->right); + } + delete cur; +} + +template +void binary_search_tree::rebalance(node *node) +{ + node->update_height(); + int bal = node->balance(); + + if (bal > 1) + { + if (node->left != nullptr && node->left->balance() < 0) + rotate_left(node->left); + rotate_right(node); + } + else if (bal < -1) + { + if (node->right != nullptr && node->right->balance() > 0) + rotate_right(node->right); + rotate_left(node); + } +} + +template +void binary_search_tree::rotate_right(node *node) +{ + binary_search_tree::node *parent = node->parent; + binary_search_tree::node *left = node->left; + binary_search_tree::node *left_right = left->right; + + left->right = node; + node->left = left_right; + + left->parent = parent; + node->parent = left; + if (left_right != nullptr) + left_right->parent = node; + + if (parent != nullptr) + { + if (parent->left == node) + parent->left = left; + else + parent->right = left; + } + else + { + root = left; + } + + node->update_height(); + left->update_height(); +} + +template +void binary_search_tree::rotate_left(node *node) +{ + binary_search_tree::node *parent = node->parent; + binary_search_tree::node *right = node->right; + binary_search_tree::node *right_left = right->left; + + right->left = node; + node->right = right_left; + + right->parent = parent; + node->parent = right; + if (right_left != nullptr) + right_left->parent = node; + + if (parent != nullptr) + { + if (parent->left == node) + parent->left = right; + else + parent->right = right; + } + else + { + root = right; + } + + node->update_height(); + right->update_height(); +} + +template +void binary_search_tree::to_string_rec(K ***grid, node *cur, int row, int col, int height) const +{ + if (cur == nullptr) + return; + + grid[row][col] = &cur->key; + to_string_rec(grid, cur->left, row + 1, col - pow(2, height - 2), height - 1); + to_string_rec(grid, cur->right, row + 1, col + pow(2, height - 2), height - 1); +} + +template +inline binary_search_tree::tree_iterator::tree_iterator() + : p(nullptr) +{ +} + +template +inline binary_search_tree::tree_iterator::tree_iterator(node *p) + : p(p) +{ +} + +template +inline binary_search_tree::tree_iterator::tree_iterator(tree_iterator &other) + : p(other.p) +{ +} + +template +inline typename binary_search_tree::tree_iterator &binary_search_tree::tree_iterator::operator=(tree_iterator &other) +{ + p = other.p; + return *this; +} + +template +inline typename binary_search_tree::tree_iterator &binary_search_tree::tree_iterator::operator++() +{ + if (p->right != nullptr) + { + p = p->right; + while (p->left != nullptr) + { + p = p->left; + } + } + else + { + while (true) + { + if (p->parent == nullptr) + { + p = nullptr; + break; + } + else if (p->parent->left == p) + { + p = p->parent; + break; + } + else + { + p = p->parent; + } + } + } + return *this; +} + +template +inline bool binary_search_tree::tree_iterator::operator!=(tree_iterator &other) +{ + return p != other.p; +} + +template +inline K &binary_search_tree::tree_iterator::operator*() +{ + return p->key; +} + +template +inline K *binary_search_tree::tree_iterator::operator->() +{ + return &p->key; +} + +#endif diff --git a/src/containers/hashmap.h b/src/containers/hashmap.h new file mode 100644 index 0000000..9d0d5a6 --- /dev/null +++ b/src/containers/hashmap.h @@ -0,0 +1,177 @@ +#ifndef HASHMAP_H +#define HASHMAP_H + +#include "pair.h" +#include "string.h" +#include "vector.h" + +template +class hashmap { + public: + hashmap(); + ~hashmap(); + + int size() const; + bool empty() const; + + void insert(const K& key, const V& value); + void remove(const K& key); + const V& get(const K& key) const; + bool contains(const K& key) const; + + void clear(); + + V& operator[](const K& key) const; + + private: + static constexpr int hash_prime = 5381; + static constexpr int default_capacity = 16; + static constexpr double load_factor_threshold = 0.75; + static V default_value; + + vector>* table; + int _size; + int _capacity; + + int hash(const int& key) const; + int hash(const double& key) const; + int hash(const string& key) const; + + void rehash_table(); +}; + +template +V hashmap::default_value = V(); + +template +hashmap::hashmap() { + _size = 0; + _capacity = default_capacity; + table = new vector>[_capacity]; + for (int i = 0; i < _capacity; i++) + table[i] = vector>(); +} + +template +hashmap::~hashmap() { + if (table != nullptr) + // delete[] array; // need to overload delete[] to not call deconstructors of array object, std implementation has unwanted side effects + table = nullptr; +} + +template +int hashmap::size() const { + return _size; +} + +template +bool hashmap::empty() const { + return size == 0; +} + +template +void hashmap::insert(const K& key, const V& value) { + double load_factor = (double)_size / _capacity; + if (load_factor > load_factor_threshold) + rehash_table(); + int index = hash(key) % _capacity; + table[index].push_back(pair(key, value)); + _size++; +} + +template +void hashmap::remove(const K& key) { + int index = hash(key) % _capacity; + for (int i = 0; i < table[index].size(); i++) { + if (table[index][i].first == key) { + table[index] = V(); + _size--; + break; + } + } +} + +template +const V& hashmap::get(const K& key) const { + int index = hash(key) % _capacity; + for (int i = 0; i < table[index].size(); i++) { + if (table[index][i].first == key) + return table[index][i].second; + } + return default_value; +} + +template +bool hashmap::contains(const K& key) const { + int index = hash(key) % table.capacity(); + for (int i = 0; i < table[index].size(); i++) { + if (table[index][i].first == key) + return true; + } + return false; +} + +template +void hashmap::clear() { + _size = 0; + for (int i = 0; i < _capacity; i++) + table[i].clear(); +} + +template +V& hashmap::operator[](const K& key) const { + int index = hash(key) % _capacity; + for (int i = 0; i < table[index].size(); i++) { + if (table[index][i].first == key) + return table[index][i].second; + } + return default_value; +} + +template +int hashmap::hash(const int& key) const { + int hash = hash_prime; + int size_bytes = sizeof(key); + const char* data = reinterpret_cast(&key); + for (int i = 0; i < size_bytes; i++) + hash = (hash << 5) + hash + data[i]; + return hash; +} + +template +int hashmap::hash(const double& key) const { + int hash = hash_prime; + int size_bytes = sizeof(key); + const char* data = reinterpret_cast(&key); + for (int i = 0; i < size_bytes; i++) + hash = (hash << 5) + hash + data[i]; + return hash; +} + +template +int hashmap::hash(const string& key) const { + int hash = hash_prime; + for (int i = 0; i < key.size(); i++) + hash = (hash << 5) + hash + key[i]; + return hash; +} + +template +void hashmap::rehash_table() { + int new_capacity = _capacity * 2; + vector>* new_table = new vector>[new_capacity]; + for (int i = 0; i < new_capacity; i++) + new_table[i] = vector>(); + + for (int i = 0; i < _capacity; i++) { + for (int j = 0; j < table[i].size(); j++) { + int new_index = hash(table[i][j].first) % new_capacity; + new_table[new_index].push_back(table[i][j]); + } + } + // delete[] table; // need to overload delete[] to not call deconstructors of array object, std implementation has unwanted side effects + table = new_table; + _capacity = new_capacity; +} + +#endif \ No newline at end of file diff --git a/src/containers/linked_list.h b/src/containers/linked_list.h new file mode 100644 index 0000000..c465c77 --- /dev/null +++ b/src/containers/linked_list.h @@ -0,0 +1,350 @@ +#ifndef LINKED_LIST_H +#define LINKED_LIST_H + +#include "string.h" + +template +class linked_list +{ +public: + linked_list(); + ~linked_list(); + + bool empty() const; + int size() const; + + void push_front(T& data); + void push_back(T& data); + T& pop_front(); + T& pop_back(); + T& peek_front() const; + T& peek_back() const; + + void insert(int index, T &data); // inserts new element before the existing element at the given index + void replace(int index, T &data); + void remove(int index); + + void clear(); + + T &operator[](int index) const; + + string to_string() const; + + private: + class node { + public: + T& value; + node* prev; + node* next; + + node(T &value); + + private: + }; + + static T default_value; + + node *head; + node *tail; + int _size; +}; + +template +linked_list::node::node(T &value) + : value(value), prev(nullptr), next(nullptr) {} + +template +T linked_list::default_value = 0x0;//CHECKME Legal? + +template +linked_list::linked_list() + : head(nullptr), tail(nullptr), _size(0) {} + +template +linked_list::~linked_list() +{ +} + +template +bool linked_list::empty() const +{ + return _size == 0; +} + +template +int linked_list::size() const +{ + return _size; +} + +template +void linked_list::push_front(T &data) +{ + node *new_node = new node(data); + + if (head == nullptr) + { + head = new_node; + tail = new_node; + } + else if (head == tail) + { + head = new_node; + head->next = tail; + tail->prev = head; + } + else + { + head->prev = new_node; + new_node->next = head; + head = new_node; + } + + _size++; +} + +template +void linked_list::push_back(T &data) +{ + node *new_node = new node(data); + + if (tail == nullptr) + { + tail = new_node; + head = new_node; + } + else if (tail == head) + { + tail = new_node; + tail->prev = head; + head->next = tail; + } + else + { + tail->next = new_node; + new_node->prev = tail; + tail = new_node; + } + + _size++; +} + +template +T& linked_list::pop_front() { + T& data = default_value; + + if (head == nullptr) + { + data = default_value; + } + else if (head == tail) + { + data = head->value; + delete head; + head = nullptr; + tail = nullptr; + } + else + { + data = head->value; + head = head->next; + delete head->prev; + head->prev = nullptr; + } + + _size--; + return data; +} + +template +T& linked_list::pop_back() { + T& data = default_value; + + if (tail == nullptr) + { + data = default_value; + } + else if (tail == head) + { + data = tail->value; + delete tail; + tail = nullptr; + head = nullptr; + } + else + { + data = tail->value; + tail = tail->prev; + delete tail->next; + tail->next = nullptr; + } + + _size--; + return data; +} + +template +T& linked_list::peek_front() const { + if (head == nullptr) + return default_value; + else + return head->value; +} + +template +T& linked_list::peek_back() const { + if (tail == nullptr) + return default_value; + else + return tail->value; +} + +template +void linked_list::insert(int index, T &data) +{ + if (index > _size) + return; + + node *cur = head; + for (int i = 0; i < index; i++) + cur = cur->next; + + node *new_node = new node(data); + if (index == _size && _size == 0) + { + head = new_node; + tail = new_node; + } + else if (index == _size && _size == 1) + { + tail = new_node; + head->next = tail; + tail->prev = head; + } + else if (index == _size && _size > 1) + { + tail->next = new_node; + new_node->prev = tail; + tail = new_node; + } + else if (index == 0) + { + head->prev = new_node; + new_node->next = head; + head = new_node; + } + else + { + cur->prev->next = new_node; + new_node->prev = cur->prev; + cur->prev = new_node; + new_node->next = cur; + } + + _size++; +} + +template +void linked_list::replace(int index, T &data) +{ + if (index >= _size) + return; + + node *cur = head; + for (int i = 0; i < index; i++) + cur = cur->next; + + cur->value = data; +} + +template +void linked_list::remove(int index) +{ + if (index >= _size) + return; + + node *cur = head; + for (int i = 0; i < index; i++) + cur = cur->next; + + if (head == tail) + { + delete head; + head = nullptr; + tail = nullptr; + } + else if (cur == head) + { + head = head->next; + delete head->prev; + head->prev = nullptr; + } + else if (cur == tail) + { + tail = tail->prev; + delete tail->next; + tail->next = nullptr; + } + else + { + cur->prev->next = cur->next; + cur->next->prev = cur->prev; + delete cur; + } + + _size--; +} + +template +void linked_list::clear() +{ + node *cur = head; + while (cur != nullptr) + { + if (cur->next != nullptr) + { + cur = cur->next; + delete cur->prev; + } + else + { + delete cur; + cur = nullptr; + } + } + head = nullptr; + tail = nullptr; + _size = 0; +} + +template +T &linked_list::operator[](int index) const +{ + if (index >= _size) + return default_value; + + node *cur = head; + for (int i = 0; i < index; i++) + cur = cur->next; + + return cur->value; +} + +template +string linked_list::to_string() const { + string str; + + node* cur = head; + str += "["; + for (int i = 0; i < _size; i++) { + str += '0' + cur->value; + if (i != _size - 1) + str += ", "; + cur = cur->next; + } + str += "]"; + + return str; +} + +#endif \ No newline at end of file diff --git a/src/containers/math.h b/src/containers/math.h new file mode 100644 index 0000000..7892fe4 --- /dev/null +++ b/src/containers/math.h @@ -0,0 +1,28 @@ +#ifndef MATH_H +#define MATH_H + +template +const T& min(const T& x, const T& y) { + return (x < y) ? x : y; +} + +template +const T& max(const T& x, const T& y) { + return (x > y) ? x : y; +} + +template +const T abs(const T& x) { + return (x < 0) ? -x : x; +} + +template +const T pow(const T& base, const T& exponent) { + T result = 1; + for (int i = 0; i < abs(exponent); i++) { + result *= base; + } + return (exponent >= 0) ? result : 1 / result; +} + +#endif \ No newline at end of file diff --git a/src/containers/pair.h b/src/containers/pair.h new file mode 100644 index 0000000..18546ef --- /dev/null +++ b/src/containers/pair.h @@ -0,0 +1,27 @@ +#ifndef PAIR_H +#define PAIR_H + +template +struct pair +{ + T first; + U second; + + pair() + : first(T()), second(U()){}; + + pair(const T &first, const U &second) + : first(first), second(second) {} + + bool operator==(const pair &other) const + { + return first == other.first && second == other.second; + } + + bool operator!=(const pair &other) const + { + return !(*this == other); + } +}; + +#endif \ No newline at end of file diff --git a/src/containers/string.cpp b/src/containers/string.cpp new file mode 100644 index 0000000..347dade --- /dev/null +++ b/src/containers/string.cpp @@ -0,0 +1,260 @@ +#include "string.h" + +string::string() +{ + s = strdup(""); +} + +string::string(const char *other) +{ + s = strdup(other); +} + +string::string(const string &other) +{ + s = strdup(other.s); +} + +string::~string() +{ + if (s != nullptr) + delete[] s; +} + +int string::size() const +{ + return strlen(s); +} + +bool string::operator==(const string &other) const +{ + return strcmp(s, other.s); +} + +bool string::operator!=(const string &other) const +{ + return strcmp(s, other.s); +} + +string &string::operator=(char c) +{ + if (s != nullptr) + delete[] s; + char cnvrt[2] = {c, '\0'}; + s = strdup(cnvrt); + return *this; +} + +string &string::operator=(const char *other) +{ + if (s != nullptr) + delete[] s; + s = strdup(other); + return *this; +} + +string &string::operator=(const string &other) +{ + if (s != nullptr) + delete[] s; + s = strdup(other.s); + return *this; +} + +string string::operator+(char c) const +{ + string result; + result.s = new char[strlen(s) + 1 + 1]; + strcpy(result.s, s); + char cnvrt[2] = {c, '\0'}; + strcat(result.s, cnvrt); + return result; +} + +string string::operator+(const char *other) const +{ + string result; + result.s = new char[strlen(s) + strlen(other) + 1]; + strcpy(result.s, s); + strcat(result.s, other); + return result; +} + +string string::operator+(const string &other) const +{ + return *this + other.s; +} + +string &string::operator+=(char c) +{ + char *temp = new char[strlen(s) + 1 + 1]; + strcpy(temp, s); + char cnvrt[2] = {c, '\0'}; + strcat(temp, cnvrt); + if (s != nullptr) + delete[] s; + s = temp; + return *this; +} + +string &string::operator+=(const char *other) +{ + char *temp = new char[strlen(s) + strlen(other) + 1]; + strcpy(temp, s); + strcat(temp, other); + if (s != nullptr) + delete[] s; + s = temp; + return *this; +} + +string &string::operator+=(const string &other) +{ + return *this += other.s; +} + +const char &string::operator[](int index) const +{ + return s[index]; +} + +const char *string::c_str() const +{ + return s; +} + +string string::substr(int start_index) const +{ + if (start_index < 0 || start_index >= strlen(s)) // invalid index + return string(); + return string(s + start_index); // pointer arithmatic, eat your heart out rust +} + +string string::substr(int start_index, int end_index) const +{ + if (start_index < 0 || start_index >= strlen(s) || end_index <= start_index || end_index > strlen(s)) // invalid indices + return string(); + + int length = end_index - start_index; + char *temp = new char[length + 1]; + strncpy(temp, s + start_index, length); + temp[length] = '\0'; // make sure to null-terminate the string + return string(temp); +} + +bool string::strcmp(const char *first, const char *second) +{ + while (*first && *second && *first == *second) + { + first++; + second++; + } + return *first == *second; +} + +char *string::substr(const char *str, int start_index) +{ + int len = strlen(str + start_index); + char *substr = new char[len + 1]; + strcpy(substr, str + start_index); + return substr; +} + +char *string::substr(const char *str, int start_index, int end_index) +{ + int len = end_index - start_index; + char *substr = new char[len + 1]; + strncpy(substr, str + start_index, len); + substr[len] = '\0'; + return substr; +} + +char *string::strdup(const char *s) +{ + int len = strlen(s); + char *dup = new char[len + 1]; + strcpy(dup, s); + return dup; +} + +void string::strcpy(char *&dest, const char *src) +{ + if (dest != nullptr) + delete[] dest; + + int len = strlen(src); + dest = new char[len + 1]; + char *temp = dest; + + while (*src) + { + *temp = *src; + temp++; + src++; + } + *temp = '\0'; +} + +void string::strncpy(char *&dest, const char *src, int len) +{ + if (dest != nullptr) + delete[] dest; + + dest = new char[len + 1]; + char *temp = dest; + + while (*src && len > 0) + { + *temp = *src; + temp++; + src++; + len--; + } + *temp = '\0'; +} + +void string::strcat(char *&dest, const char *src) +{ + if (dest == nullptr) + { + dest = strdup(src); + return; + } + + int len_dest = strlen(dest); + int len_src = strlen(src); + char *temp = new char[len_dest + len_src + 1]; + + char *temp_ptr = temp; + const char *dest_ptr = dest; + const char *src_ptr = src; + + while (*dest_ptr) + { + *temp_ptr = *dest_ptr; + temp_ptr++; + dest_ptr++; + } + + while (*src_ptr) + { + *temp_ptr = *src_ptr; + temp_ptr++; + src_ptr++; + } + *temp_ptr = '\0'; + + delete[] dest; + dest = temp; +} + +int string::strlen(const char *s) +{ + int len = 0; + while (*s) + { + len++; + s++; + } + return len; +} diff --git a/src/containers/string.h b/src/containers/string.h new file mode 100644 index 0000000..314aaf8 --- /dev/null +++ b/src/containers/string.h @@ -0,0 +1,47 @@ +#ifndef STRING_H +#define STRING_H + +class string +{ +public: + string(); + string(const char *other); + string(const string &other); + ~string(); + + int size() const; + + bool operator==(const string &other) const; + bool operator!=(const string &other) const; + + string &operator=(char c); + string &operator=(const char *other); + string &operator=(const string &other); + string operator+(char other) const; + string operator+(const char *other) const; + string operator+(const string &other) const; + string &operator+=(char c); + string &operator+=(const char *other); + string &operator+=(const string &other); + + const char &operator[](int index) const; + + const char *c_str() const; + + string substr(int start_index) const; + string substr(int start_index, int end_index) const; + + static bool strcmp(const char *first, const char *second); // differs from standard implemtation + static char *substr(const char *str, int start_index); + static char *substr(const char *str, int start_index, int end_index); + static char *strdup(const char *s); + static void strcpy(char *&dest, const char *src); // differs from standard implemtation, allocates more memory in destination string if its not big enough + static void strncpy(char *&dest, const char *src, int len); // ^ + static void strcat(char *&dest, const char *src); // ^ + static int strlen(const char *s); // doesn't include the null-terminator + +private: + char *s; +}; + +#endif \ No newline at end of file diff --git a/src/containers/vector.h b/src/containers/vector.h new file mode 100644 index 0000000..12a5c25 --- /dev/null +++ b/src/containers/vector.h @@ -0,0 +1,137 @@ +#ifndef VECTOR_H +#define VECTOR_H + +template +class vector +{ +public: + vector(); + vector(int capacity); + vector(const vector &other); + ~vector(); + + bool empty() const; + int size() const; + int capacity() const; + + void push_back(const T &data); + void pop_back(); + T &back(); + void remove(int index); + + void clear(); + void resize(int new_capacity); + + T &operator[](int index) const; + +private: + T *array; + int _capacity; + int _size; +}; + +template +vector::vector() +{ + _size = 0; + _capacity = 8; + array = new T[_capacity]; +} + +template +vector::vector(int capacity) +{ + _size = 0; + _capacity = capacity; + array = new T[_capacity]; +} + +template +vector::vector(const vector &other) +{ + _size = other.size(); + _capacity = other.capacity(); + array = new T[_capacity]; + for (int i = 0; i < _size; i++) + array[i] = other[i]; +} + +template +vector::~vector() +{ + if (array != nullptr) + delete[] array; +} + +template +bool vector::empty() const +{ + return _size == 0; +} + +template +int vector::size() const +{ + return _size; +} + +template +int vector::capacity() const +{ + return _capacity; +} + +template +T &vector::operator[](int index) const +{ + return array[index]; +} + +template +void vector::push_back(const T &data) +{ + if (_size == _capacity) + resize(_capacity * 2); + array[_size] = data; + _size++; +} + +template +void vector::pop_back() +{ + array[_size - 1].~T(); + _size--; +} + +template +T &vector::back() +{ + return array[_size - 1]; +} + +template +void vector::remove(int index) +{ + return array[index] = T(); +} + +template +void vector::clear() +{ + _size = 0; + for (int i = 0; i < _capacity; i++) + array[i].~T(); +} + +template +void vector::resize(int new_capacity) +{ + _capacity = new_capacity; + T *temp = new T[_capacity]; + for (int i = 0; i < _size; i++) + temp[i] = array[i]; + delete[] array; + array = temp; +} + +#endif \ No newline at end of file diff --git a/src/devices/disk_driver.c++ b/src/devices/disk_driver.c++ new file mode 100644 index 0000000..46be820 --- /dev/null +++ b/src/devices/disk_driver.c++ @@ -0,0 +1,135 @@ +#include "disk_driver.h" + +void int_to_bytes(unsigned int num, byte* buffer, int offset) { + int size = sizeof(unsigned int); + for (int i = 0; i < size; i++) + buffer[i + offset] = ((num >> (i * 8)) & 0xFF); + return; +} + +disk_driver::disk_driver() { + registers = (unsigned int*)0xFFFFFF7FFF300000; +} + +disk_driver::disk_driver(unsigned int emmc_registers_base_address) { + registers = (unsigned int*)emmc_registers_base_address; +} + +disk_driver::~disk_driver() {} + +int disk_driver::read(unsigned int sector_offset, byte*& buffer) { + unsigned int byte_offset = sector_offset * bytes_per_sector; + + Status status = wait_for_read_rdy(); + if (status != Status::OK) + return status; + + registers[BLKSIZECNT] = (1 << 16) | bytes_per_sector; + + for (int i = 0; i < bytes_per_sector; i += bytes_per_int) { + unsigned int int_byte_offset = byte_offset + (i * bytes_per_int); + + unsigned int response; + Status status = issue_cmd(READ_SINGLE, int_byte_offset, response); + if (status != Status::OK) + return status; + + Status status = wait_for_read_rdy(); + if (status != Status::OK) + return status; + + int_to_bytes(registers[DATA], buffer, i); + } + + return Status::OK; +} + +int disk_driver::write(unsigned int sector_offset, const byte*& buffer) { +} + +disk_driver::Status disk_driver::issue_cmd(COMMAND_MASK cmd, unsigned int arg, unsigned int& response) { + Status status = wait_for_cmd_rdy(); + if (status != Status::OK) + return status; + + registers[ARG1] = arg; + registers[CMDTM] = cmd; + + Status status = wait_for_cmd_done(); + if (status != Status::OK) + return status; + + response = registers[RESP0]; + + return Status::OK; +} + +disk_driver::Status disk_driver::wait_for_read_rdy() { + int count = 1000000; + while (any_errors() || !can_read()) { + // wait(1); + count--; + } + if (any_errors()) + return Status::ERROR; + else if (!can_read()) + return Status::TIMEOUT; + else + return Status::OK; +} + +disk_driver::Status disk_driver::wait_for_cmd_rdy() { + int count = 1000000; + while (any_errors() || !can_issue_cmd()) { + // wait(1); + count--; + } + if (any_errors()) + return Status::ERROR; + else if (!can_issue_cmd()) + return Status::TIMEOUT; + else + return Status::OK; +} + +disk_driver::Status disk_driver::wait_for_cmd_done() { + int count = 1000000; + while (any_errors() || !cmd_done()) { + // wait(1); + count--; + } + if (any_errors()) + return Status::ERROR; + else if (!cmd_done()) + return Status::TIMEOUT; + else + return Status::OK; +} + +bool disk_driver::any_errors() { + bool any_interrupt_errors = registers[INTERRUPT] & INTERRUPT_MASK::ERROR; + bool any_command_errors = registers[CMDTM] & COMMAND_MASK::ERROR; + return any_interrupt_errors || any_command_errors; +} + +bool disk_driver::can_read() { + bool read_available = registers[STATUS] & STATUS_MASK::READ_AVAILABLE; + bool read_ready = registers[INTERRUPT] & INTERRUPT_MASK::READ_READY; + bool prev_transfer_done = !(registers[STATUS] & STATUS_MASK::DATA_INHIBIT); + return read_available && read_ready && prev_transfer_done; +} + +bool disk_driver::can_write() { + bool write_avl = registers[STATUS] & STATUS_MASK::WRITE_AVAILABLE; + bool write_rdy = registers[INTERRUPT] & INTERRUPT_MASK::WRITE_READY; + bool prev_transfer_done = !(registers[STATUS] & STATUS_MASK::DATA_INHIBIT); + return write_avl && write_rdy && prev_transfer_done; +} + +bool disk_driver::can_issue_cmd() { + return !(registers[STATUS] & STATUS_MASK::CMD_INHIBIT); +} + +bool disk_driver::cmd_done() { + return !(registers[INTERRUPT] & INTERRUPT_MASK::CMD_DONE); +} \ No newline at end of file diff --git a/src/devices/disk_driver.h b/src/devices/disk_driver.h new file mode 100644 index 0000000..f5da7f5 --- /dev/null +++ b/src/devices/disk_driver.h @@ -0,0 +1,108 @@ +#ifndef KERNEL_DISK_DRIVER_H +#define KERNEL_DISK_DRIVER_H + +typedef unsigned char byte; + +class disk_driver { + public: + enum Status : int { + OK = 0, + ERROR = 1, + TIMEOUT = 2 + }; + + disk_driver(); + disk_driver(unsigned int emmc_registers_base_address); + ~disk_driver(); + + int read(unsigned int sector_offset, byte*& buffer); + int write(unsigned int sector_offset, const byte*& buffer); + + private: + enum Register : int { + ARG2 = 0, + BLKSIZECNT = 1, + ARG1 = 2, + CMDTM = 3, + RESP0 = 4, + RESP1 = 5, + RESP2 = 6, + RESP3 = 7, + DATA = 8, + STATUS = 9, + CONTROL0 = 10, + CONTROL1 = 11, + INTERRUPT = 12, + IRPT_MASK = 13, + IRPT_EN = 14, + CONTROL2 = 15, + FORCE_IRPT = 16, + BOOT_TIMEOUT = 17, + DBG_SEL = 18, + EXRDFIFO_CFG = 19, + EXRDFIFO_EN = 20, + TUNE_STEP = 21, + TUNE_STEPS_STD = 22, + TUNE_STEPS_DDR = 23, + SPI_INT_SPT = 24, + SLOTISR_VER = 25 + }; + + enum STATUS_MASK : unsigned int { + READ_AVAILABLE = 0x00000800, + WRITE_AVAILABLE = 0x00000400, + DATA_INHIBIT = 0x00000002, + CMD_INHIBIT = 0x00000001 + }; + + enum INTERRUPT_MASK : unsigned int { + ERROR = 0x017E8000, + DATA_TIMEOUT = 0x00100000, + CMD_TIMEOUT = 0x00010000, + READ_READY = 0x00000020, + WRITE_READY = 0x00000010, + DATA_DONE = 0x00000002, + CMD_DONE = 0x00000001 + }; + + enum COMMAND_MASK : unsigned int { + ERROR = 0xfff9c004, + NEED_APP = 0x80000000, + GO_IDLE = 0x00000000, + ALL_SEND_CID = 0x02010000, + SEND_REL_ADDR = 0x03020000, + CARD_SELECT = 0x07030000, + SEND_IF_COND = 0x08020000, + STOP_TRANS = 0x0C030000, + READ_SINGLE = 0x11220010, + READ_MULTI = 0x12220032, + SET_BLOCKCNT = 0x17020000, + WRITE_SINGLE = 0x18220000, + WRITE_MULTI = 0x19220022, + APP_CMD = 0x37000000, + SET_BUS_WIDTH = (0x06020000 | NEED_APP), + SEND_OP_COND = (0x29020000 | NEED_APP), + SEND_SCR = (0x33220010 | NEED_APP) + }; + + const unsigned int bytes_per_sector = 512; + const unsigned int bytes_per_int = 4; + const unsigned int ints_per_sector = bytes_per_sector / bytes_per_int; + + unsigned int* volatile registers; + + Status issue_cmd(COMMAND_MASK cmd, unsigned int arg, unsigned int& response); + + Status wait_for_cmd_done(); + Status wait_for_cmd_rdy(); + Status wait_for_read_rdy(); + + bool any_errors(); + + bool can_read(); + bool can_write(); + bool can_issue_cmd(); + bool cmd_done(); +}; + +#endif \ No newline at end of file diff --git a/src/devices/mmio.h b/src/devices/mmio.h new file mode 100644 index 0000000..5782d55 --- /dev/null +++ b/src/devices/mmio.h @@ -0,0 +1,64 @@ +#ifndef KERNEL_MMIO_H +#define KERNEL_MMIO_H + +#include + +enum class MMIOOffset : uint64_t +{ + /** + * @brief Base address for GPIO registers + */ + GPIO_BASE = 0x200000, + + // Controls actuation of pull up/down to ALL GPIO pins. + GPPUD = (GPIO_BASE + 0x94), + + // Controls actuation of pull up/down for specific GPIO pin. + GPPUDCLK0 = (GPIO_BASE + 0x98), + + /** + * @brief Base address for UART0 registers. + */ + UART0_BASE = (GPIO_BASE + 0x1000), // for raspi4 0xFE201000, raspi2 & 3 0x3F201000, and 0x20201000 for raspi1 + + INTR_OFFSET = 0xB000, + INTR_BASIC_PENDING = INTR_OFFSET + 0x200, + INTR_IRQ_PENDING_1 = INTR_OFFSET + 0x204, + INTR_IRQ_PENDING_2 = INTR_OFFSET + 0x208, + INTR_FIQ_CTRL = INTR_OFFSET + 0x20C, + INTR_IRQ_ENABLE_1 = INTR_OFFSET + 0x210, + INTR_IRQ_ENABLE_2 = INTR_OFFSET + 0x214, + INTR_IRQ_ENABLE_BASE = INTR_OFFSET + 0x218, + INTR_IRQ_DISABLE_BASE = INTR_OFFSET + 0x224, + INTR_IRQ_DISABLE_1 = INTR_OFFSET + 0x21C, + INTR_IRQ_DISABLE_2 = INTR_OFFSET + 0x220, + + MBOX_BASE = 0xB880, + MBOX_READ = (MBOX_BASE + 0x00), + MBOX_STATUS = (MBOX_BASE + 0x18), + MBOX_WRITE = (MBOX_BASE + 0x20) +}; + +/** + * @brief Write to a 32-bit MMIO register + * + * @param reg pointer to the register to write to + * @param data the data to write to the specified register + */ +static inline void mmio_write(void *reg, uint32_t data) +{ + *(volatile uint32_t *)(reg + 0xFFFFFF803F000000) = data; +} + +/** + * @brief Read from a 32-bit MMIO register + * + * @param reg pointer to the register to read from + * @return uint32_t the data read from the specified register + */ +static inline uint32_t mmio_read(void *reg) +{ + return *(volatile uint32_t *)(reg + 0xFFFFFF803F000000); +} + +#endif \ No newline at end of file diff --git a/src/devices/timer.cpp b/src/devices/timer.cpp new file mode 100644 index 0000000..db7a19a --- /dev/null +++ b/src/devices/timer.cpp @@ -0,0 +1,31 @@ +#include "timer.h" +#include "kernel.h" +#include "util/log.h" + +volatile unsigned int *const kernel::devices::SystemTimer::registers = (unsigned int *)0xFFFFFF803F003000; + +kernel::devices::SystemTimer::SystemTimer() +{ + this->delta = 50; + reset(); +} + +kernel::devices::SystemTimer::SystemTimer(unsigned int delta) +{ + this->delta = delta; + reset(); +} + +void kernel::devices::SystemTimer::handleInterrupt(int src) +{ + reset(); + // kernelLog(LogLevel::DEBUG, "Timer interrupt: %i", registers[CLO]); + kernel::kernel.switchTask(); +} + +void kernel::devices::SystemTimer::reset() +{ + unsigned int currentTime = registers[CLO]; + registers[C1] = currentTime + (delta * 1000); + registers[CS] = 0xF; +} diff --git a/src/devices/timer.h b/src/devices/timer.h new file mode 100644 index 0000000..43cacca --- /dev/null +++ b/src/devices/timer.h @@ -0,0 +1,41 @@ +#ifndef KERNEL_TIMER_H +#define KERNEL_TIMER_H + +#include "irq/interrupthandler.h" + +namespace kernel::devices +{ + +class SystemTimer : public kernel::interrupt::InterruptHandler +{ +public: + + SystemTimer(); + + SystemTimer(unsigned int delta); + + void handleInterrupt(int src); + +private: + + enum TimerRegisters { + CS = 0, + CLO, + CHI, + C0, + C1, + C2, + C3 + }; + + static volatile unsigned int *const registers; + + unsigned int delta; + + void reset(); + +}; + +} // namespace kernel::devices + +#endif \ No newline at end of file diff --git a/src/devices/uart.cpp b/src/devices/uart.cpp new file mode 100644 index 0000000..5c5e08b --- /dev/null +++ b/src/devices/uart.cpp @@ -0,0 +1,323 @@ +#include "uart.h" +#include "mmio.h" +#include "util/log.h" +#include "util/string.h" +#include + +namespace kernel::devices +{ + + enum UARTRegisters + { + /** + * @brief Data Register + */ + DR = 0, + + /** + * @brief Receive status register/error clear register + */ + RSRECR = 1, + + /** + * @brief Flag register + */ + FR = 6, + + /** + * @brief Not in use + */ + ILPR = 8, + + /** + * @brief Integer baud rate divisor + */ + IBRD = 9, + + /** + * @brief Fractional baud rate divisor + */ + FBRD = 10, + + /** + * @brief Line control register + */ + LCRH = 11, + + /** + * @brief Control register + */ + CR = 12, + + /** + * @brief Interrupt FIFO level select register + */ + IFLS = 13, + + /** + * @brief Interrupt mask set/clear register + */ + IMSC = 14, + + /** + * @brief Raw interrupt status register + */ + RIS = 15, + + /** + * @brief Masked interrupt status register + */ + MIS = 16, + + /** + * @brief Interrupt clear register + */ + ICR = 17, + + /** + * @brief DMA control register + */ + DMACR = 18, + + /** + * @brief Test control register + */ + ITCR = 32, + + /** + * @brief Integration test input register + */ + ITIP = 33, + + /** + * @brief Integration test output register + */ + ITOP = 34, + + /** + * @brief Test data register + */ + IDR = 35 + + }; + + volatile unsigned int __attribute__((aligned(16))) mbox[9] = { + 9 * 4, 0, 0x38002, 12, 8, 2, 3000000, 0, 0}; + + // Loop times in a way that the compiler won't optimize away + static inline void delay(int32_t count) + { + /*asm volatile("__delay_%=: subs %[count], %[count], #1; bne __delay_%=\n" + : "=r"(count) : [count] "0"(count) : "cc");*/ + while (count > 0) + { + count--; + } + } + + UART::UART() + : registers(nullptr), bufferIndex(0) + { + memset(buffer, bufferSize, 0); + } + + UART::UART(void *mmio_offset) + : registers((uint32_t *)mmio_offset), bufferIndex(0) + { + memset(buffer, bufferSize, 0); + /*int raspi = 3; + // Disable UART0. + registers[CR] = 0; + // Setup the GPIO pin 14 && 15. + + // Disable pull up/down for all GPIO pins & delay for 150 cycles. + mmio_write((void *)MMIOOffset::GPPUD, 0x00000000); + delay(150); + + // Disable pull up/down for pin 14,15 & delay for 150 cycles. + mmio_write((void *)MMIOOffset::GPPUDCLK0, (1 << 14) | (1 << 15)); + delay(150); + + // Write 0 to GPPUDCLK0 to make it take effect. + mmio_write((void *)MMIOOffset::GPPUDCLK0, 0x00000000); + + // Clear pending interrupts. + registers[ICR] = 0x7FF; + + // Set integer & fractional part of baud rate. + // Divider = UART_CLOCK/(16 * Baud) + // Fraction part register = (Fractional part * 64) + 0.5 + // Baud = 115200. + + // For Raspi3 and 4 the UART_CLOCK is system-clock dependent by default. + // Set it to 3Mhz so that we can consistently set the baud rate + if (raspi >= 3) + { + // UART_CLOCK = 30000000; + unsigned int r = (((unsigned int)(&mbox) & ~0xF) | 8); + // wait until we can talk to the VC + while (mmio_read((void *)MMIOOffset::MBOX_STATUS) & 0x80000000) + { + } + // send our message to property channel and wait for the response + mmio_write((void *)MMIOOffset::MBOX_WRITE, r); + while ((mmio_read((void *)MMIOOffset::MBOX_STATUS) & 0x40000000) || mmio_read((void *)MMIOOffset::MBOX_READ) != r) + { + } + } + + // Divider = 3000000 / (16 * 115200) = 1.627 = ~1. + registers[IBRD] = 1; + // Fractional part register = (.627 * 64) + 0.5 = 40.6 = ~40. + registers[FBRD] = 40; + + // Enable FIFO & 8 bit data transmission (1 stop bit, no parity). + registers[LCRH] = (1 << 4) | (1 << 5) | (1 << 6); + + // Mask all interrupts. + */ + registers[IMSC] = Receive | /*Transmit |*/ ReceiveTimeout | + Framing | Parity | Break | Overrun + /*0*/ + ; + + // Enable UART0, receive & transfer part of UART. + // registers[CR] = (1 << 0) | (1 << 8) | (1 << 9); + } + + UART &UART::operator<<(char c) + { + while (registers[UARTRegisters::FR] & (1 << 5)) + ; + registers[UARTRegisters::DR] = c; + return *this; + } + + UART &UART::operator<<(const char *str) + { + for (const char *s = str; *s != '\0'; s++) + { + *this << *s; + } + return *this; + } + + UART &UART::operator>>(char &c) + { + while (registers[FR] & (1 << 4)) + { + } + c = registers[DR]; + } + + kernel::fs::FileContext *UART::open(Mode mode) + { + return new UARTContext(*this); + } + + void UART::close(int id) + { + } + + void UART::handleInterrupt(int src) + { + int status = registers[UARTRegisters::MIS]; + if (status & Receive) + { + // kernelLog(LogLevel::DEBUG, "UART receive interrupt"); + readByte(); + } + if (status & Transmit) + { + // kernelLog(LogLevel::DEBUG, "UART transmit interrupt"); + } + if (status & CTSM) + { + kernelLog(LogLevel::DEBUG, "UART nUARTCTS modem interrupt"); + } + if (status & ReceiveTimeout) + { + // kernelLog(LogLevel::DEBUG, "UART receive timeout interrupt"); + readByte(); + } + if (status & Framing) + { + kernelLog(LogLevel::DEBUG, "UART framing interrupt"); + } + if (status & Parity) + { + kernelLog(LogLevel::DEBUG, "UART parity interrupt"); + } + if (status & Break) + { + kernelLog(LogLevel::DEBUG, "UART break interrupt"); + } + if (status & Overrun) + { + kernelLog(LogLevel::DEBUG, "UART overrun interrupt"); + } + registers[UARTRegisters::ICR] = /*0x7F2*/ status; + } + + void UART::readByte() + { + while (!(registers[UARTRegisters::FR] & (1 << 4))) + { + buffer[bufferIndex] = registers[UARTRegisters::DR]; + bufferIndex++; + if (bufferIndex >= bufferSize) + { + bufferIndex = 0; + } + } + } +} + +kernel::devices::UART::UARTContext::UARTContext(UART &uart) + : uart(uart), pos(uart.bufferIndex) +{ +} + +kernel::devices::UART::UARTContext::~UARTContext() +{ +} + +int kernel::devices::UART::UARTContext::read(void *buffer, int n) +{ + char *s = (char *)buffer; + int c = 0; + while (pos != uart.bufferIndex && c < n) + { + s[c] = uart.buffer[pos]; + c++; + pos++; + if (pos >= uart.bufferSize) + { + pos = 0; + } + } + return c; +} + +int kernel::devices::UART::UARTContext::write(const void *buffer, int n) +{ + for (int i = 0; i < n; i++) + { + uart << ((char *)buffer)[i]; + if (((char *)buffer)[i] == '\n') + { + uart << '\r'; + } + } + return n; +} + +kernel::fs::FileContext *kernel::devices::UART::UARTContext::copy() +{ + UARTContext *f = new UARTContext(uart); + if (f != nullptr) + { + f->pos = pos; + } + return f; +} diff --git a/src/devices/uart.h b/src/devices/uart.h new file mode 100644 index 0000000..fde1f28 --- /dev/null +++ b/src/devices/uart.h @@ -0,0 +1,77 @@ +#ifndef KERNEL_UART_H +#define KERNEL_UART_H + +#include "util/charstream.h" +#include "irq/interrupthandler.h" +#include "containers/binary_search_tree.h" +#include "fs/filecontext.h" +#include + +namespace kernel::devices +{ + + class UART : public CharStream, public kernel::interrupt::InterruptHandler + { + public: + UART(); + + UART(void *mmio_offset); + + UART &operator<<(char c); + + UART &operator<<(const char *str); + + UART &operator>>(char &c); + + kernel::fs::FileContext *open(Mode mode); + + void close(int id); + + void handleInterrupt(int src); + + private: + enum InterruptBits + { + CTSM = (1 << 1), + Receive = (1 << 4), + Transmit = (1 << 5), + ReceiveTimeout = (1 << 6), + Framing = (1 << 7), + Parity = (1 << 8), + Break = (1 << 9), + Overrun = (1 << 10) + }; + + class UARTContext : public kernel::fs::FileContext + { + public: + UARTContext(UART &uart); + + ~UARTContext(); + + int read(void *buffer, int n); + + int write(const void *buffer, int n); + + kernel::fs::FileContext *copy(); + + private: + UART &uart; + + int pos; + }; + + static const int bufferSize = 4096; + + uint32_t *volatile registers; + + char buffer[bufferSize]; + + int bufferIndex; + + void readByte(); + }; + +} + +#endif \ No newline at end of file diff --git a/src/fs/fat32/disk_interface/disk_interface.cpp b/src/fs/fat32/disk_interface/disk_interface.cpp new file mode 100644 index 0000000..e1d83cf --- /dev/null +++ b/src/fs/fat32/disk_interface/disk_interface.cpp @@ -0,0 +1,82 @@ +#include "disk_interface.h" +/*#include +#include +#include */ +#include "containers/string.h" +#include "util/log.h" + +DiskInterface::DiskInterface(string disk_filepath, int bytes_per_sector) + : disk_filepath(disk_filepath), bytes_per_sector(bytes_per_sector) +{ + /*std::ifstream file(std::string(string::strdup(disk_filepath.c_str())), std::ios::binary); + if (!file.is_open()) + { + throw std::runtime_error("Error opening disk file"); + } + + file.seekg(0, std::ios::end); + std::streampos file_size = file.tellg(); + file.seekg(0, std::ios::beg); + + size = static_cast(file_size); + disk = new byte[size]; + + file.read(reinterpret_cast(disk), size); + + if (!file) + { + throw std::runtime_error("Error reading disk file"); + } + + file.close();*/ +} + +DiskInterface::DiskInterface(void *ramfs, int bytes_per_sector) + : disk((byte *)ramfs), bytes_per_sector(bytes_per_sector), disk_filepath("") +{ +} + +DiskInterface ::~DiskInterface() +{ + write_to_disk(); + delete[] disk; +} + +// must return a copy of the data cuz the buffer may be modified +byte *DiskInterface::read(int sector_index) +{ + byte *buffer = new byte[bytes_per_sector]; + + int offset = bytes_per_sector * sector_index; + for (int i = 0; i < bytes_per_sector; i++) + buffer[i] = disk[i + offset]; + + return buffer; +} + +void DiskInterface::write(int sector_index, const byte *buffer) +{ + int offset = bytes_per_sector * sector_index; + for (int i = 0; i < bytes_per_sector; i++) + disk[i + offset] = buffer[i]; + + return; +} + +void DiskInterface::write_to_disk() +{ + /*disk_filepath = "disk_modified.img"; // to avoid disk containmination + std::ofstream file(std::string(string::strdup(disk_filepath.c_str())), std::ios::binary); + if (!file.is_open()) + { + throw std::runtime_error("Error opening disk file for writing"); + } + + file.write(reinterpret_cast(disk), size); + + if (!file) + { + throw std::runtime_error("Error writing to disk file"); + } + file.close();*/ +} diff --git a/src/fs/fat32/disk_interface/disk_interface.h b/src/fs/fat32/disk_interface/disk_interface.h new file mode 100644 index 0000000..14afcc2 --- /dev/null +++ b/src/fs/fat32/disk_interface/disk_interface.h @@ -0,0 +1,25 @@ +#ifndef DISK_INTERFACE_H +#define DISK_INTERFACE_H + +#include "../helpers.h" + +class DiskInterface +{ +public: + DiskInterface(string disk_filepath, int bytes_per_sector); + DiskInterface(void *ramfs, int bytes_per_sector); + ~DiskInterface(); + + byte *read(int sector_index); + void write(int sector_index, const byte *buffer); + +private: + string disk_filepath; + int bytes_per_sector; + byte *disk; + unsigned int size; + + void write_to_disk(); +}; + +#endif \ No newline at end of file diff --git a/src/fs/fat32/entry.cpp b/src/fs/fat32/entry.cpp new file mode 100644 index 0000000..8cb592e --- /dev/null +++ b/src/fs/fat32/entry.cpp @@ -0,0 +1,186 @@ +/* + * Author: Hunter Overstake + * Email: huntstake@gmail.com + * Date: 5/14/2024 + */ + +#include "fat32.h" + +/* Creates an entry in memory for the FAT32 entry at the given byte offset to keep track of meta-data */ +FAT32::Entry::Entry(FAT32 *fs, Entry *parent_dir, int byte_offset) + : byte_offset(byte_offset), parent_dir(parent_dir), fs(fs) +{ + // figure out what type of entry this is + // then parse accordingly + type = parse_attribute(byte_offset); + switch (type) + { + case ROOT: + parse_root_entry(); + break; + case DIRECTORY: + case FILE: + parse_dir_entry(); // similar enough to do in a single function + break; + case LONG_NAME: + parse_long_name(); + break; + default: // must be EMPTY + clear_entry(byte_offset); + break; + } +} + +// TODO +FAT32::Entry::~Entry() +{ + // will segfault + for (int i = 0; i < sub_dirs.size(); i++) + delete sub_dirs[i]; + + for (int i = 0; i < long_name_entries.size(); i++) + delete sub_dirs[i]; +} + +/* Reads the byte data of the entry at the specifed sector offset, will fail by returning null +if: entry isn't a file (e.g. a directory), the sector offset is bigger that the number of sectors +allocated to the file. */ +byte *FAT32::Entry::read_data(int sector_offset) +{ + if (type != FILE) + return nullptr; // entries not of type FILE don't have readable data + + int target_cluster = sector_offset / fs->sectors_per_cluster; + + if (target_cluster >= (int)fat_allocations.size()) + return nullptr; // can't read data that isn't allocated to the entry + + int target_cluster_offset = fat_entry_to_cluster_offset(fat_allocations[target_cluster]); + int target_sector_offset = (target_cluster_offset * fs->sectors_per_cluster) + (sector_offset % fs->sectors_per_cluster); + + return fs->di->read(target_sector_offset); +} + +/* Writes the given byte data to the given sector offset (using zero-indexing) into the provided data +buffer, will fail if: the entry isn't a file (e.g. a directory), there isn't enough space left on disk +to allocate more clusters if required. */ +int FAT32::Entry::write_data(int sector_offset, const byte *data) +{ + if (type != FILE) + return failure; // entries not of type FILE can't be written do + + int target_cluster = sector_offset / fs->sectors_per_cluster; // cluster the sector is in + + int num_clusters = (int)fat_allocations.size(); + if (target_cluster >= num_clusters) + { + int num_additional_clusters = (target_cluster + 1) - num_clusters; // need to allocate more clusters + if (!allocate_more_clusters(num_additional_clusters)) + return failure; // couldn't allocate more clusters, data region must be full + } + + int target_cluster_offset = fat_entry_to_cluster_offset(fat_allocations[target_cluster]); + int target_sector_offset = (target_cluster_offset * fs->sectors_per_cluster) + (sector_offset % fs->sectors_per_cluster); + + fs->di->write(target_sector_offset, data); // write data + + unsigned int required_size = (sector_offset + 1) * fs->bytes_per_sector; // update file size if needed + if (file_size_bytes < (int)required_size) + { + int data_size_bytes = 0; + for (int i = fs->bytes_per_sector - 1; i >= 0; i--) + { // data may not fill a sector + if (data[i] != 0) + { + data_size_bytes = i + 1; + break; + } + } + + file_size_bytes = sector_offset * fs->bytes_per_sector + data_size_bytes; + + int sector_offset = byte_offset / fs->bytes_per_sector; + int sector_byte_offset = byte_offset % fs->bytes_per_sector; + + byte *buffer = fs->di->read(sector_offset); + int_to_bytes(required_size, buffer, 28 + sector_byte_offset); // update file size on disk entry too + fs->di->write(sector_offset, buffer); + delete[] buffer; + } + + return success; +} + +/* Finds the sub directory with the matching given name, fails by returning null if no match is found */ +FAT32::Entry *FAT32::Entry::find_sub_dir(string name) +{ + for (int i = 0; i < sub_dirs.size(); i++) + { + Entry *dir = sub_dirs[i]; + if (dir->name == name) + return dir; + } + + return nullptr; // couldn't find sub dir with matching name +} + +/* Used for creating a new directory entry in memory as well as on disk with the given name under the given +parent directory. */ +int FAT32::Entry::new_dir_entry(Entry *par_dir, string name, EntryAttribute type) +{ + this->parent_dir = par_dir; + this->name = name; + this->type = type; + this->fs = par_dir->fs; + this->order = -1; + + int name_size = name.size(); + int num_long_name_entries = (name_size / 13) + 1; // conpute the number of required long file name entries for the name + + if (name_size % 13 == 0) + num_long_name_entries--; // edge case where the name will fill the last long file name entry - usually it will need an 'end' char but not in this case + + int first_entry_byte_offset = parent_dir->find_empty_entries(num_long_name_entries + 1); // find a continous region to store long name entries on disk + + if (first_entry_byte_offset == failure) // not enough space in data region for long name entries (at the very least not a continous space) + return failure; + + this->byte_offset = first_entry_byte_offset + (num_long_name_entries * fs->bytes_per_dir_entry); + + if (!allocate_more_clusters(1)) // allocate a single cluster + return failure; + + if (type == FILE) + this->file_size_bytes = 0; + else + this->file_size_bytes = fs->bytes_per_cluster; // idk if directory entries actually need this field + + for (int i = 0; i < num_long_name_entries; i++) + { // create long name entries in memory + string name_part = ""; + int start_index = i * 13; + int end_index = (i * 13) + 13; + if (end_index >= (int)name.size()) + name_part = name.substr(start_index); + else + name_part = name.substr(start_index, end_index); + + Entry *long_name = new Entry(); + long_name->fs = fs; + long_name->parent_dir = this; + long_name->name = name_part; + long_name->extension = ""; + long_name->type = LONG_NAME; + long_name->byte_offset = first_entry_byte_offset + ((num_long_name_entries - i - 1) * fs->bytes_per_dir_entry); + long_name->order = i + 1; + long_name->last = false; + + long_name_entries.push_back(long_name); + } + + long_name_entries.back()->last = true; + write_dir_entry(); // write the new directory and all its long name entries to disk + par_dir->sub_dirs.push_back(this); + + return success; +} diff --git a/src/fs/fat32/entry_helpers.cpp b/src/fs/fat32/entry_helpers.cpp new file mode 100644 index 0000000..57bdbaa --- /dev/null +++ b/src/fs/fat32/entry_helpers.cpp @@ -0,0 +1,773 @@ +/* + * Author: Hunter Overstake + * Email: huntstake@gmail.com + * Date: 5/14/2024 + */ + +#include "fat32.h" + +/* Parses this entry's attribute from disk to find out what type of entry this is. */ +FAT32::EntryAttribute FAT32::Entry::parse_attribute(int byte_offset) +{ + int sector_offset = byte_offset / fs->bytes_per_sector; + int sector_byte_offset = byte_offset % fs->bytes_per_sector; + + byte *buffer = fs->di->read(sector_offset); + + FAT32::EntryAttribute type = EMPTY; + + unsigned char attribute = buffer[11 + sector_byte_offset]; + attribute &= 0x3F; // upper 2 bits are reserved, so we must mask those 2 bits when reading the value + switch (attribute) + { + case 0x08: + type = ROOT; + break; + case 0x10: + type = DIRECTORY; + break; + case 0x20: + type = FILE; + break; + case 0x0F: + type = LONG_NAME; + break; + default: + type = EMPTY; + break; + } + + delete[] buffer; + return type; +} + +/* Parses the short file name of this entry from disk. */ +string FAT32::Entry::parse_short_file_name(int byte_offset) +{ + int sector_offset = byte_offset / fs->bytes_per_sector; + int sector_byte_offset = byte_offset % fs->bytes_per_sector; + + byte *buffer = fs->di->read(sector_offset); + + string name = ""; + for (int i = 0; i < 8; i++) + { + char c = buffer[i + sector_byte_offset]; + if (c != ' ') + name += c; + } + + delete[] buffer; + + return name; +} + +/* Treats this entry as the root directory while parsing its meta-data from disk. */ +void FAT32::Entry::parse_root_entry() +{ + int sector_offset = byte_offset / fs->bytes_per_sector; + int sector_byte_offset = byte_offset % fs->bytes_per_sector; + + byte *buffer = fs->di->read(sector_offset); + + name = ""; + extension = ""; + + find_cluster_allocations((unsigned int)fs->reserved_fat_entries); // just used as an offset, dont read into it too much + + file_size_bytes = bytes_to_int(buffer, 28 + sector_byte_offset); + + delete[] buffer; + + order = -1; + + find_sub_dir_entries(); // start of the recursive search of the file system +} + +/* Treats this entry as directory while parsing its meta-data from disk - also used +for entries of type file since their actual disk entry structure is identical. */ +void FAT32::Entry::parse_dir_entry() +{ + int sector_offset = byte_offset / fs->bytes_per_sector; + int sector_byte_offset = byte_offset % fs->bytes_per_sector; + + byte *buffer = fs->di->read(sector_offset); + + name = parse_short_file_name(byte_offset); + + extension = ""; + for (int i = 8; i < 11; i++) + { + char c = buffer[i + sector_byte_offset]; + if (c != ' ') + extension += c; + } + + unsigned short first_cluster_high = bytes_to_short(buffer, 20 + sector_byte_offset); // first cluster num is split up for some reason + unsigned short first_cluster_low = bytes_to_short(buffer, 26 + sector_byte_offset); + unsigned int first_cluster_offset = (static_cast(first_cluster_high) << 16) | first_cluster_low; + + find_cluster_allocations(first_cluster_offset); + + file_size_bytes = bytes_to_int(buffer, 28 + sector_byte_offset); + + delete[] buffer; + + find_long_name_entries(); // also updates this entries file name if it finds any long file name entries + + order = -1; + + if (type == DIRECTORY) + find_sub_dir_entries(); // keep searching +} + +/* Treats this entry as a long file name while parsing its meta-data from disk. */ +void FAT32::Entry::parse_long_name() +{ + int sector_offset = byte_offset / fs->bytes_per_sector; + int sector_byte_offset = byte_offset % fs->bytes_per_sector; + + byte *buffer = fs->di->read(sector_offset); + + order = buffer[0 + sector_byte_offset] & 0x1F; + last = false; + name = ""; + + // name pieces are split up for some reason + int offset = 1; + for (int i = 0; i < 10; i += 2) + { + byte first = buffer[i + offset + sector_byte_offset]; + byte second = buffer[i + 1 + offset + sector_byte_offset]; + wchar_t unicode = chars_to_unicode(first, second); + if (unicode != L'\xFFFF') + name += first; + else + break; + } + + offset = 14; + for (int i = 0; i < 12; i += 2) + { + byte first = buffer[i + offset + sector_byte_offset]; + byte second = buffer[i + 1 + offset + sector_byte_offset]; + wchar_t unicode = chars_to_unicode(first, second); + if (unicode != L'\xFFFF') + name += first; + else + break; + } + + offset = 28; + for (int i = 0; i < 4; i += 2) + { + byte first = buffer[i + offset + sector_byte_offset]; + byte second = buffer[i + 1 + offset + sector_byte_offset]; + wchar_t unicode = chars_to_unicode(first, second); + if (unicode != L'\xFFFF') + name += first; + else + break; + } + + extension = ""; + file_size_bytes = -1; + + delete[] buffer; +} + +/* Finds all the clusters allocated to this entry given its first cluster offset in the FAT */ +void FAT32::Entry::find_cluster_allocations(unsigned int first_fat_entry_offset) +{ + if (first_fat_entry_offset == 0) + return; + + unsigned int current_fat_entry_offset = first_fat_entry_offset; + int current_fat_sector_offset = fs->reserved_sectors + (first_fat_entry_offset * fs->bytes_per_fat_entry) / fs->bytes_per_sector; + int current_fat_secotor_int_offset = (current_fat_entry_offset * fs->bytes_per_fat_entry) % fs->bytes_per_sector; + + byte *buffer = fs->di->read(current_fat_sector_offset); + + FatEntryType type = check_fat_entry_type(bytes_to_int(buffer, current_fat_secotor_int_offset)); + + while (type == ALLOCATED) + { + fat_allocations.push_back(current_fat_entry_offset); + + unsigned int next_fat_entry_offset = bytes_to_int(buffer, current_fat_secotor_int_offset); + + int next_fat_sector_offset = fs->reserved_sectors + (next_fat_entry_offset * fs->bytes_per_fat_entry) / fs->bytes_per_sector; + if (check_fat_entry_type(next_fat_entry_offset) == ALLOCATED && next_fat_sector_offset != current_fat_sector_offset) + { // update the sector being read from if the next cluster is outside the current sector + delete[] buffer; + buffer = fs->di->read(next_fat_sector_offset); + } + + current_fat_entry_offset = next_fat_entry_offset; + current_fat_sector_offset = next_fat_sector_offset; + current_fat_secotor_int_offset = (current_fat_entry_offset * fs->bytes_per_fat_entry) % fs->bytes_per_sector; + + type = check_fat_entry_type(current_fat_entry_offset); + } + fat_allocations.push_back(current_fat_entry_offset); + + delete[] buffer; +} + +/* Given the raw value of the fat cluster entry, returns the type - see offical docs for explaintion on the values. */ +FAT32::FatEntryType FAT32::Entry::check_fat_entry_type(unsigned int val) +{ + unsigned int snipped_val = val & 0xFFFFFFF; + if (val == 0xFFFFFFFF) + return END; + switch (snipped_val) + { + case 0x0000000: + return FREE; + case 0xFFFFFFE: + return DEFECTIVE; + default: + unsigned int total_num_clusters = fs->total_sectors / fs->sectors_per_cluster; + if (val < total_num_clusters) + return ALLOCATED; + else + return RESERVED; + } +} + +/* Finds the long file name entries associated with this entry, doesn't search beyond the current cluster, needs +refactoring if you want to support file/dir names longer than 208 characters long. */ +void FAT32::Entry::find_long_name_entries() +{ + int current_cluster_start_byte_offset = byte_offset / fs->bytes_per_cluster; + int search_byte_offset = byte_offset - fs->bytes_per_dir_entry; + + while (search_byte_offset >= current_cluster_start_byte_offset) + { // only search within current cluster + if (parse_attribute(search_byte_offset) == LONG_NAME) + { + Entry *long_name = new Entry(fs, this, search_byte_offset); + long_name_entries.push_back(long_name); + } + else + { + break; + } + search_byte_offset -= fs->bytes_per_dir_entry; + } + + if (!long_name_entries.empty()) + { + name = ""; + for (int i = 0; i < (int)long_name_entries.size(); i++) + { // construct the full name of this entry from the long file names + name += long_name_entries[i]->name; + } + long_name_entries.back()->last = true; // mark the last one + } + + return; +} + +/* Searches through all of this entries clusters and finds all of its sub-directoies and creates memory entries for each, passes over long +file names as they are owned by sub-directors - only call this function once per entry. */ +void FAT32::Entry::find_sub_dir_entries() +{ + for (int i = 0; i < (int)fat_allocations.size(); i++) + { + int search_cluster_offset = fat_entry_to_cluster_offset(fat_allocations[i]); + int serach_cluster_start_byte_offset = search_cluster_offset * fs->bytes_per_cluster; + int serach_cluster_end_byte_offset = serach_cluster_start_byte_offset + fs->bytes_per_cluster; + int search_byte_offset = serach_cluster_start_byte_offset; + + while (search_byte_offset < serach_cluster_end_byte_offset) + { + EntryAttribute type = parse_attribute(search_byte_offset); + string name = parse_short_file_name(search_byte_offset); + + bool is_dir_or_file = (type == DIRECTORY || type == FILE); + bool is_dot_or_dotdot = (name == "." || name == ".."); + + if (is_dir_or_file && !is_dot_or_dotdot) + { // make sure they aren't the two required copy-entries + Entry *sub_dir = new Entry(fs, this, search_byte_offset); + sub_dirs.push_back(sub_dir); + } + + search_byte_offset += fs->bytes_per_dir_entry; + } + } +} + +/* Converts the fat entry value to its corresponding cluster offset in the data region. */ +int FAT32::Entry::fat_entry_to_cluster_offset(unsigned int fat_entry) +{ + return (fat_entry - fs->reserved_fat_entries) + fs->data_region_start_cluster_offset; +} + +/* Allocats a given number of new clusters to this entry, will fail if: there are no remaining +free clusters in the data region. */ +int FAT32::Entry::allocate_more_clusters(int num_additional_clusters) +{ + if (num_additional_clusters <= 0) + return failure; // yeah just don't do that + + int fat_start_byte_offset = fs->reserved_sectors * fs->bytes_per_sector; + + if (fat_allocations.empty()) + { // allocate first cluster if none are allocated yet + if (!initial_cluster()) + return failure; + num_additional_clusters--; + } + + int current_entry_offset = fat_allocations[(int)fat_allocations.size() - 1]; + int current_entry_byte_offset = fat_start_byte_offset + current_entry_offset * fs->bytes_per_fat_entry; + int current_entry_sector_offset = current_entry_byte_offset / fs->bytes_per_sector; + int current_entry_sector_byte_offset = current_entry_byte_offset % fs->bytes_per_sector; + + byte *buffer = fs->di->read(current_entry_sector_offset); + + bool no_more_clusters = false; + for (int i = 0; i < num_additional_clusters; i++) + { + int next_free_offset = find_free_cluster(); + + if (next_free_offset == failure) + { + no_more_clusters = true; + break; + } + + int next_entry_offset = next_free_offset; + int next_entry_byte_offset = fat_start_byte_offset + next_entry_offset * fs->bytes_per_fat_entry; + int next_entry_sector_offset = next_entry_byte_offset / fs->bytes_per_sector; + int next_entry_sector_byte_offset = next_entry_byte_offset % fs->bytes_per_sector; + + int_to_bytes(next_entry_offset, buffer, current_entry_sector_byte_offset); + fs->di->write(current_entry_sector_offset, buffer); + delete[] buffer; + buffer = fs->di->read(next_entry_sector_offset); + + current_entry_offset = next_entry_offset; + current_entry_byte_offset = next_entry_byte_offset; + current_entry_sector_offset = next_entry_sector_offset; + current_entry_sector_byte_offset = next_entry_sector_byte_offset; + + if (type == DIRECTORY) + init_dir_cluster(fat_entry_to_cluster_offset(current_entry_offset)); // if this entry is a directory, each cluster allocated to it needs some setup done to it, see init_dir_cluster() for details + + fat_allocations.push_back(current_entry_offset); + } + + unsigned int end_marker = 0xFFFFFFFF; // mark last fat entry as the end + int_to_bytes(end_marker, buffer, current_entry_sector_byte_offset); + fs->di->write(current_entry_sector_offset, buffer); + + delete[] buffer; + + if (no_more_clusters) + return failure; // no more space in data region + return success; +} + +int FAT32::Entry::find_free_cluster() +{ + int fat_start_sector_offset = fs->reserved_sectors; + int fat_start_byte_offset = fat_start_sector_offset * fs->bytes_per_sector; + int clusters_per_fat = fs->sectors_per_fat / fs->sectors_per_cluster; + + int current_entry_offset = 2; + int current_entry_byte_offset = fat_start_byte_offset + current_entry_offset * fs->bytes_per_fat_entry; + int current_entry_sector_offset = current_entry_byte_offset / fs->bytes_per_sector; + int current_entry_sector_byte_offset = current_entry_byte_offset % fs->bytes_per_sector; + + byte *buffer = fs->di->read(current_entry_sector_offset); + + while (current_entry_offset < clusters_per_fat) + { + unsigned int entry_val = bytes_to_int(buffer, current_entry_sector_byte_offset); + FatEntryType type = check_fat_entry_type(entry_val); + if (type == FREE) + { + delete[] buffer; + return current_entry_offset; + } + + int next_entry_offset = current_entry_offset + 1; + int next_entry_byte_offset = fat_start_byte_offset + next_entry_offset * fs->bytes_per_fat_entry; + int next_entry_sector_offset = next_entry_byte_offset / fs->bytes_per_sector; + int next_entry_sector_byte_offset = next_entry_byte_offset % fs->bytes_per_sector; + + if (next_entry_sector_offset != current_entry_sector_offset) + { + delete[] buffer; + buffer = fs->di->read(next_entry_sector_offset); + } + + current_entry_offset = next_entry_offset; + current_entry_byte_offset = next_entry_byte_offset; + current_entry_sector_offset = next_entry_sector_offset; + current_entry_sector_byte_offset = next_entry_sector_byte_offset; + } + + delete[] buffer; + return failure; +} + +/* Finds a continuous space big enough for the given number of entries within the clusters +allocated to this entry, will allocate more if it can't find a space, will fail if: it needs +to allocate more clusters but there is no more free clusters in the data region. */ +int FAT32::Entry::find_empty_entries(int num_entries) +{ + int current_byte_offset; + if (parent_dir == nullptr) // root dir + current_byte_offset = parent_dir->byte_offset; + else + current_byte_offset = fat_entry_to_cluster_offset(fat_allocations[0]) * fs->bytes_per_cluster; + + int current_cluster_offset = byte_offset / fs->bytes_per_cluster; + + int num_empty_found = 0; + int empty_start_offset = 0; + int cluster_num = 1; + int the_sky_is_blue = true; + while (the_sky_is_blue) + { + EntryAttribute type = parse_attribute(current_byte_offset); + if (type == EMPTY) + { + if (num_empty_found == 0) + empty_start_offset = current_byte_offset; + + num_empty_found++; + + if (num_empty_found == num_entries) + break; + } + else + { + num_empty_found = 0; + } + + int next_byte_offset = current_byte_offset += fs->bytes_per_dir_entry; + int next_cluster_offset = byte_offset / fs->bytes_per_cluster; + + if (next_cluster_offset != current_cluster_offset) + { + cluster_num++; + if (cluster_num > (int)parent_dir->fat_allocations.size()) + { // can't find a space in the current cluster and this is the last cluster, so allocate another one + if (!parent_dir->allocate_more_clusters(1)) + return failure; // out of space in data region + } + + next_byte_offset = fat_entry_to_cluster_offset(parent_dir->fat_allocations.back()) + (2 * fs->bytes_per_dir_entry); // skip first two entries, we know they aren't empty + next_cluster_offset = byte_offset / fs->bytes_per_cluster; + } + + current_byte_offset = next_byte_offset; + current_cluster_offset = next_cluster_offset; + } + + return empty_start_offset; +} + +/* Intializes a given cluster of this directory entry with two base entries - '.' which +is a copy of this entry, and '..' which is a copy of this entry's parent entry. */ +void FAT32::Entry::init_dir_cluster(int cluster_offset) +{ + int cluster_byte_offset = cluster_offset * fs->bytes_per_cluster; + int sector_offset = byte_offset / fs->bytes_per_sector; + int sector_byte_offset = byte_offset % fs->bytes_per_sector; + + clear_entry(cluster_byte_offset); + clear_entry(cluster_byte_offset + fs->bytes_per_dir_entry); + + byte *buffer = fs->di->read(sector_offset); + + buffer[0 + sector_byte_offset] = '.'; + buffer[11 + sector_byte_offset] = 0x10; + + unsigned short first_cluster_high = (fat_allocations[0] >> 16) & 0xFFFF; + unsigned short first_cluster_low = fat_allocations[0] & 0xFFFF; + + short_to_bytes(first_cluster_high, buffer, 20 + sector_byte_offset); + short_to_bytes(first_cluster_low, buffer, 26 + sector_byte_offset); + int_to_bytes(file_size_bytes, buffer, 28 + sector_byte_offset); + + sector_byte_offset += fs->bytes_per_dir_entry; + + buffer[0 + sector_byte_offset] = '.'; + buffer[0 + sector_byte_offset] = '.'; + buffer[11 + sector_byte_offset] = 0x10; + + first_cluster_high = (parent_dir->fat_allocations[0] >> 16) & 0xFFFF; + first_cluster_low = parent_dir->fat_allocations[0] & 0xFFFF; + + short_to_bytes(first_cluster_high, buffer, 20 + sector_byte_offset); + short_to_bytes(first_cluster_low, buffer, 26 + sector_byte_offset); + int_to_bytes(parent_dir->file_size_bytes, buffer, 28 + sector_byte_offset); + + fs->di->write(sector_offset, buffer); + + delete[] buffer; +} + +/* Clears the entry specified by the given byte offset, removes any garbage data that may be there. */ +void FAT32::Entry::clear_entry(int byte_offset) +{ + int sector_offset = byte_offset / fs->bytes_per_sector; + int sector_byte_offset = byte_offset % fs->bytes_per_sector; + + byte *buffer = fs->di->read(sector_offset); + + for (int i = 0; i < fs->bytes_per_dir_entry; i++) + buffer[i + sector_byte_offset] = 0x0; + + fs->di->write(sector_offset, buffer); + + delete[] buffer; +} + +/* Allocates the first cluster of this entry, assumes that there are no clusters already allocated to this +entry, will fail if: there are no free clusters left in the data region. */ +int FAT32::Entry::initial_cluster() +{ + int fat_start_byte_offset = fs->reserved_sectors * fs->bytes_per_sector; + + int current_entry_offset = find_free_cluster(); + + if (current_entry_offset == failure) // no free clusters left + return failure; + + int current_entry_byte_offset = fat_start_byte_offset + current_entry_offset * fs->bytes_per_fat_entry; + int current_entry_sector_offset = current_entry_byte_offset / fs->bytes_per_sector; + int current_entry_sector_byte_offset = current_entry_byte_offset % fs->bytes_per_sector; + + byte *buffer = fs->di->read(current_entry_sector_offset); + + unsigned int end_marker = 0xFFFFFFFF; + int_to_bytes(end_marker, buffer, current_entry_sector_byte_offset); + fs->di->write(current_entry_sector_offset, buffer); + delete[] buffer; + + fat_allocations.push_back(current_entry_offset); + + if (type == DIRECTORY) + init_dir_cluster(fat_entry_to_cluster_offset(current_entry_offset)); // if directory, perform cluster setup + + return success; +} + +/* Writes the meta-data stored in this file/directory memory entry to the corresponding entry on disk. */ +void FAT32::Entry::write_dir_entry() +{ + clear_entry(byte_offset); // clear old data + + int sector_offset = byte_offset / fs->bytes_per_sector; + int sector_byte_offset = byte_offset % fs->bytes_per_sector; + + byte *buffer = fs->di->read(sector_offset); + + string short_name = long_to_short_name(name, 8); + for (int i = 0; i < 8; i++) + { + buffer[i + sector_byte_offset] = short_name[i]; + } + + string short_ext = long_to_short_name(extension, 3); + for (int i = 0; i < 3; i++) + { + buffer[8 + i + sector_byte_offset] = short_ext[i]; + } + + if (type == FILE) + buffer[sector_byte_offset + 11] = 0x20; + else + buffer[sector_byte_offset + 11] = 0x10; + + unsigned int first_cluster = fat_allocations[0]; + unsigned short cluster_high = (first_cluster >> 16) & 0xFFFF; + unsigned short cluster_low = first_cluster & 0xFFFF; + + short_to_bytes(cluster_high, buffer, 20 + sector_byte_offset); + short_to_bytes(cluster_low, buffer, 26 + sector_byte_offset); + + int_to_bytes(file_size_bytes, buffer, 28 + sector_byte_offset); + + fs->di->write(sector_offset, buffer); + + delete[] buffer; + + for (int i = 0; i < long_name_entries.size(); i++) + long_name_entries[i]->write_long_name_entry(); // also update long file name disk entries +} + +/* Writes the meta-data stored in this long file name memory entry to the corresponding entry on disk, +this function probably doesn't calcuate the checksum properly and it probably the reason why it isn't +comforming to the FAT32 standard fully. */ +void FAT32::Entry::write_long_name_entry() +{ + clear_entry(byte_offset); // clear old data + + int sector_offset = byte_offset / fs->bytes_per_sector; + int sector_byte_offset = byte_offset % fs->bytes_per_sector; + + byte *buffer = fs->di->read(sector_offset); + + if (last) + buffer[sector_byte_offset] = (unsigned char)order | 0x40; + else + buffer[sector_byte_offset] = (unsigned char)order; + + bool done = false; + string name_part_1 = ""; + int start_index = 0; + int end_index = 5; + if (end_index >= (int)name.size()) + { + name_part_1 = name.substr(start_index); + done = true; + } + else + { + name_part_1 = name.substr(start_index, end_index); + } + + string name_part_2 = ""; + start_index = 5; + end_index = 11; + if (!done) + { + if (end_index >= (int)name.size()) + { + name_part_2 = name.substr(start_index); + done = true; + } + else + { + name_part_2 = name.substr(start_index, end_index); + } + } + + string name_part_3 = ""; + start_index = 11; + end_index = 13; + if (!done) + { + if (end_index >= (int)name.size()) + { + name_part_3 = name.substr(start_index); + } + else + { + name_part_3 = name.substr(start_index, end_index); + } + } + + for (int i = 0; i < 5; i++) + { + if (i == (int)name_part_1.size()) + { + buffer[sector_byte_offset + 1 + i * 2] = 0x0; + } + else if (i > name_part_1.size()) + { + buffer[sector_byte_offset + 1 + i * 2] = 0xFF; + buffer[sector_byte_offset + 1 + i * 2 + 1] = 0xFF; + } + else + { + buffer[sector_byte_offset + 1 + i * 2] = name_part_1[i]; + } + } + + for (int i = 0; i < 6; i++) + { + if (i == (int)name_part_2.size()) + { + buffer[sector_byte_offset + 14 + i * 2] = 0x0; + } + else if (i > name_part_2.size()) + { + buffer[sector_byte_offset + 14 + i * 2] = 0xFF; + buffer[sector_byte_offset + 14 + i * 2 + 1] = 0xFF; + } + else + { + buffer[sector_byte_offset + 14 + i * 2] = name_part_2[i]; + } + } + + for (int i = 0; i < 2; i++) + { + if (i == name_part_3.size()) + { + buffer[sector_byte_offset + 28 + i * 2] = 0x0; + } + else if (i > name_part_3.size()) + { + buffer[sector_byte_offset + 28 + i * 2] = 0xFF; + buffer[sector_byte_offset + 28 + i * 2 + 1] = 0xFF; + } + else + { + buffer[sector_byte_offset + 28 + i * 2] = name_part_3[i]; + } + } + + buffer[sector_byte_offset + 11] = 0x0F; + + unsigned char checksum = 0; + + string short_name = long_to_short_name(parent_dir->name, 8); + short_name += long_to_short_name(extension, 3); + for (int i = 0; i < short_name.size(); i++) + { + char c = short_name[i]; + + checksum = ((checksum & 1) ? 0x80 : 0) + (checksum >> 1) + c; // calculate checksum + // TODO: this probably isnt correct + } + + buffer[sector_byte_offset + 13] = checksum; + + fs->di->write(sector_offset, buffer); + + delete[] buffer; +} + +/* Converts the given long file name to the short name format of a given length used by FAT32 file/directory disk entries. */ +string FAT32::Entry::long_to_short_name(string long_name, int len) +{ + string short_name = ""; + for (int i = 0; i < len; i++) + { + char c = long_name[i]; + if (c >= 'A' && c <= 'Z') + { + short_name += c; + } + else if (c >= 'a' && c <= 'z') + { + c -= 'a' - 'A'; + short_name += c; + } + else if ( + c == '$' || c == '%' || c == '\'' || c == '-' || c == '_' || + c == '@' || c == '~' || c == '`' || c == '!' || c == '(' || + c == ')' || c == '{' || c == '}' || c == '^' || c == '#' || + c == '&' || c == '.') + { + short_name += c; + } + } + + for (int i = short_name.size(); i < len; i++) + { + short_name += ' '; + } + + return short_name; +} diff --git a/src/fs/fat32/fat32.cpp b/src/fs/fat32/fat32.cpp new file mode 100644 index 0000000..7ea3f02 --- /dev/null +++ b/src/fs/fat32/fat32.cpp @@ -0,0 +1,255 @@ +/* + * Author: Hunter Overstake + * Email: huntstake@gmail.com + * Date: 5/14/2024 + */ + +#include "fat32.h" +#include "util/log.h" + +/* Constructs the file system tree in memeory to keep track of meta-data */ +FAT32::FAT32(string disk_filepath) +{ + di = new DiskInterface(disk_filepath, 512); + + // read and save required boot sector info + byte *buffer = di->read(0); + init(buffer); + delete[] buffer; +} + +FAT32::FAT32(void *ramfs) +{ + di = new DiskInterface(ramfs, 512); + // read and save required boot sector info + byte *buffer = di->read(0); + init(buffer); + delete[] buffer; +} + +// TODO +FAT32::~FAT32() +{ + // will segfaults if you uncomment + + // delete root_dir; + // delete di; +} + +void FAT32::init(byte *root_sector) +{ + bytes_per_sector = bytes_to_short(root_sector, 11); + sectors_per_cluster = root_sector[13]; + reserved_sectors = bytes_to_short(root_sector, 14); + fat_count = root_sector[16]; + total_sectors = bytes_to_int(root_sector, 32); + if (total_sectors == 0) + { + total_sectors = bytes_to_short(root_sector, 19); + } + sectors_per_fat = bytes_to_int(root_sector, 36); + + bytes_per_fat_entry = sizeof(unsigned int); + bytes_per_dir_entry = 32; + bytes_per_cluster = bytes_per_sector * sectors_per_cluster; + + data_region_start_cluster_offset = (reserved_sectors + (fat_count * sectors_per_fat)) / sectors_per_cluster; + reserved_fat_entries = 2; + + // start recursive search of the filesystem + + root_dir = new Entry(this, nullptr, data_region_start_cluster_offset * bytes_per_cluster); +} + +/* Finds the file specified by the given file path and fills the given type parameter +with the type of the file (file or directory), will fail if it can't find the file. */ +int FAT32::file_type(string file_path, FileType &type) +{ + Entry *entry = find_entry(file_path); + + if (entry == nullptr) + return failure; // couldn't find file + + type = (FileType)entry->type; + + return success; +} + +/* Finds the file specified by the given file path and fills the given size parameter +with the size of the file, will fail if it can't find the file. */ +int FAT32::file_size(string file_path, int &size) +{ + Entry *entry = find_entry(file_path); + + if (entry == nullptr) + return failure; // couldn't find file + + size = entry->file_size_bytes; + + return success; +} + +/* Finds the file specified by the given file path and fills the given buffer parameter +with the byte data of the file at the given sector offset (using zero-indexing), will fail +if it can't find the file, or if the sector offset is bigger than the number of sectors the +file has. */ +int FAT32::read_file(string file_path, int sector_offset, byte *&buffer) +{ + buffer = nullptr; + + Entry *entry = find_entry(file_path); + + if (entry == nullptr) + return failure; // couldn't find file + + buffer = entry->read_data(sector_offset); + + if (buffer == nullptr) + return failure; // couldn't read data + + return success; +} + +/* Finds the file specified by the given file path and writes the byte data at the given sector +offset (using zero-indexing) into the provided data buffer, will allocate space for the buffer +so don't allocate space for the buffer before passing, will fail if: it can't find the file, +there isn't enough space left on disk to allocate more space for the write. */ +int FAT32::write_file(string file_path, int sector_offset, const byte *data) +{ + Entry *entry = find_entry(file_path); + + if (entry == nullptr) + return failure; // couldn't find file + + if (!entry->write_data(sector_offset, data)) + return failure; // couldn't write data + + return success; +} + +/* Will create a new file at the given file path, will fail if: file path doesn't exist, the parent +directory isn't a directory or, file name already exits in parent directory, there isn't enough space +left on disk to allocate more space for a new file entry. */ +int FAT32::create_file(string file_path) +{ + string par_dir_path = snip_file_path(file_path); + Entry *par_dir = find_entry(par_dir_path); + + if (par_dir == nullptr) + return failure; // invalid file path + + if (par_dir->type != EntryAttribute::DIRECTORY) + return failure; // parent 'directory' isnt a directory + + Entry *existing_entry = find_entry(file_path); + + if (existing_entry != nullptr) + return failure; // file with name already exists in directory + + Entry *new_entry = new Entry(); + string name = parse_file_path(file_path).back(); + if (!new_entry->new_dir_entry(par_dir, name, FILE)) + { + delete new_entry; + return failure; // not enough space in data region + } + + return success; +} + +// TODO +int FAT32::remove_file(string file_name) +{ + // TODO + return failure; +} + +// TODO +int FAT32::copy_file(string file_name, string dest_dir) +{ + // TODO + return failure; +} + +// TODO +int FAT32::move_file(string file_name, string dest_dir) +{ + // TODO + return failure; +} + +/* Finds the directory specified by the given file path and fill the given list with the directory's +sub-directory names, the given list will be emptied upon being passed, will fail if: it can't find the +directory, the 'directory' isn't a directory.*/ +int FAT32::list_dir(string dir_path, vector &list) +{ + list.clear(); + + Entry *entry = find_entry(dir_path); + + if (entry == nullptr) + return failure; // couldn't find directory + + if (entry->type != EntryAttribute::DIRECTORY) + return failure; // 'directory' isnt a directory + + for (int i = 0; i < (int)entry->sub_dirs.size(); i++) + { + list.push_back(entry->sub_dirs[i]->name); + } + + return success; +} + +/* Will create a new directory at the given file path, will fail if: file path doesn't exist, the parent +directory isn't a directory or, file name already exits in parent directory, there isn't enough space +left on disk to allocate more space for a new directory entry. */ +int FAT32::create_dir(string dir_path) +{ + string par_dir_path = snip_file_path(dir_path); + Entry *par_dir = find_entry(par_dir_path); + + if (par_dir == nullptr) + return failure; // invalid file path + + Entry *existing_entry = find_entry(dir_path); + + if (existing_entry != nullptr) + return failure; // directory with name already exists in parent directory + + Entry *new_entry = new Entry(); + string name = parse_file_path(dir_path).back(); + if (!new_entry->new_dir_entry(par_dir, name, DIRECTORY)) + { + delete new_entry; + return failure; // not enough space in data region + } + + return success; +} + +// TODO +int FAT32::remove_dir(string name) +{ + // TODO + return failure; +} + +// TODO +int FAT32::copy_dir(string file_name, string dest_dir) +{ + // TODO + return failure; +} + +// TODO +int FAT32::move_dir(string file_name, string dest_dir) +{ + // TODO + return failure; +} + +int FAT32::get_sector_size() +{ + return bytes_per_sector; +} diff --git a/src/fs/fat32/fat32.h b/src/fs/fat32/fat32.h new file mode 100644 index 0000000..aee25c2 --- /dev/null +++ b/src/fs/fat32/fat32.h @@ -0,0 +1,158 @@ +#ifndef FILE_SYSTEM_H +#define FILE_SYSTEM_H + +/* + * Author: Hunter Overstake + * Email: huntstake@gmail.com + * Date: 5/15/2024 + */ + +#include "disk_interface/disk_interface.h" +#include "helpers.h" + +#define success 1 +#define failure 0 + +enum FileType +{ + File = 0, + Directory = 2, +}; + +/* A FAT32 file system interface which constructs a tree of the file system strucuture storing all the + * neccesary meta-data and is used to read/write disk entries/data. */ +class FAT32 +{ +public: + FAT32() = default; + FAT32(string disk_filepath); + FAT32(void *ramfs); + ~FAT32(); + + void init(byte *root_sector); + + int file_type(string file_path, FileType &type); + int file_size(string file_path, int &size); + int read_file(string file_path, int sector_offset, byte *&buffer); + int write_file(string file_path, int sector_offset, const byte *data); + int create_file(string file_path); + int remove_file(string file_path); + int copy_file(string file_path, string dest_dir); + int move_file(string file_path, string dest_dir); + + int list_dir(string dir_path, vector &list); + int create_dir(string dir_path); + int remove_dir(string dir_path); + int copy_dir(string src_dir_path, string dest_dir_path); + int move_dir(string src_dir_path, string dest_dir_path); + + int get_sector_size(); + + // temp functions for testing/debugging, remove when merged with kernel + void dump_fs_structure(); + void fat_dump(int cluster_offset); + void mem_dump(int cluster_offset); + int test(); + +private: + enum EntryAttribute + { + FILE, + EMPTY, + DIRECTORY, + LONG_NAME, + ROOT + }; + + enum FatEntryType + { + END, + FREE, + RESERVED, + ALLOCATED, + DEFECTIVE + }; + + /* Memory representation of a disk entry, only storing the required/useful meta-data */ + class Entry + { + public: + Entry() = default; + Entry(FAT32 *fs, Entry *parent_dir, int byte_offset); + + ~Entry(); + + string name; + string extension; // may not have one + EntryAttribute type; + + int file_size_bytes; // idk if directories or long file name entries really need this but I keep it updated anyway + int byte_offset; // actual byte offset of the corresponding disk entry + int order; // only used for entries of type LONG_NAME + bool last; // only used for entries of type LONG_NAME + + Entry *parent_dir; // when entry type is LONG_NAME, this is used to link back to its associated FILE or DIRECTORY entry + vector sub_dirs; // only for entries of type DIRECTORY + vector long_name_entries; + vector fat_allocations; + + byte *read_data(int sector_offset); + int write_data(int sector_offset, const byte *data); + + int new_dir_entry(Entry *par_dir, string name, EntryAttribute type); + + Entry *find_sub_dir(string name); + + private: + FAT32 *fs; + + // these functions use explicit parameters and return types because they are used to inspect an entry without creating an entry object + + EntryAttribute parse_attribute(int byte_offset); + string parse_short_file_name(int byte_offset); + + void parse_root_entry(); + void parse_dir_entry(); + void parse_long_name(); + void clear_entry(int byte_offset); + + void write_dir_entry(); + void write_long_name_entry(); + + int allocate_more_clusters(int num_additional_clusters); + int find_free_cluster(); + int initial_cluster(); + + int find_empty_entries(int num_entries); + void init_dir_cluster(int cluster_offset); + + void find_cluster_allocations(unsigned int first_fat_cluster_offset); + void find_long_name_entries(); + void find_sub_dir_entries(); // use only once per entry + + FatEntryType check_fat_entry_type(unsigned int cluster_val); + int fat_entry_to_cluster_offset(unsigned int fat_entry_val); + string long_to_short_name(string long_name, int len); + }; + + DiskInterface *di; + Entry *root_dir; + int data_region_start_cluster_offset; + + int bytes_per_fat_entry; + int bytes_per_dir_entry; + int bytes_per_cluster; + int bytes_per_sector; + int sectors_per_cluster; + int sectors_per_fat; + int reserved_sectors; + int reserved_fat_entries; + int total_sectors; + int fat_count; + + Entry *find_entry(string file_path); + vector parse_file_path(string file_path); + string snip_file_path(string file_path); +}; + +#endif \ No newline at end of file diff --git a/src/fs/fat32/filecontextfat32.cpp b/src/fs/fat32/filecontextfat32.cpp new file mode 100644 index 0000000..b45cc17 --- /dev/null +++ b/src/fs/fat32/filecontextfat32.cpp @@ -0,0 +1,78 @@ +#include "filecontextfat32.h" +#include "util/string.h" +#include "util/log.h" +#include "types/status.h" + +kernel::fs::FileContextFAT32::FileContextFAT32(FAT32 &fs, string path) + : fs(fs), path(path), sectorBuffer(nullptr), pos(0), lastSector(-1) +{ +} + +kernel::fs::FileContextFAT32::~FileContextFAT32() +{ + if (sectorBuffer != nullptr) + { + delete sectorBuffer; + } +} + +int kernel::fs::FileContextFAT32::read(void *buffer, int n) +{ + int filesize; + if (fs.file_size(path, filesize) == failure) + { + kernelLog(LogLevel::ERROR, "Failed to obtain file size of %s during read.", path.c_str()); + return EIO; + } + + if (pos >= filesize) + { + // kernelLog(LogLevel::DEBUG, "Hit EOF on %s during read.", path.c_str()); + return EEOF; + } + else if (pos + n > filesize) + { + n = filesize - pos; + } + + unsigned int count = 0; + while (count < n) + { + int sector = pos / fs.get_sector_size(); + int offset = pos % fs.get_sector_size(); + int bytesLeft = fs.get_sector_size() - offset; + if (sector != lastSector) + { + if (sectorBuffer != nullptr) + { + delete sectorBuffer; + } + if (fs.read_file(path, sector, sectorBuffer) == failure) + { + kernelLog(LogLevel::WARNING, "I/O error reading sector %i of %s", sector, path.c_str()); + break; + } + } + int c = (n > bytesLeft) ? bytesLeft : n; + memcpy(buffer + count, sectorBuffer + offset, c); + count += c; + pos += c; + } + return count; +} + +int kernel::fs::FileContextFAT32::write(const void *buffer, int n) +{ + return EIO; +} + +kernel::fs::FileContext *kernel::fs::FileContextFAT32::copy() +{ + FileContextFAT32 *f = new FileContextFAT32(fs, path); + if (f != nullptr) + { + f->pos = pos; + f->fileSize = fileSize; + } + return f; +} diff --git a/src/fs/fat32/filecontextfat32.h b/src/fs/fat32/filecontextfat32.h new file mode 100644 index 0000000..5f6605b --- /dev/null +++ b/src/fs/fat32/filecontextfat32.h @@ -0,0 +1,38 @@ +#ifndef KERNEL_FILECONTEXTFAT32_H +#define KERNEL_FILECONTEXTFAT32_H + +#include "../filecontext.h" +#include "fat32.h" +#include "containers/string.h" + +namespace kernel::fs +{ + class FileContextFAT32 : public FileContext + { + public: + FileContextFAT32(FAT32 &fs, string path); + + ~FileContextFAT32(); + + int read(void *buffer, int n); + + int write(const void *buffer, int n); + + FileContext *copy(); + + private: + FAT32 &fs; + + string path; + + byte *sectorBuffer; + + unsigned int fileSize; + + unsigned int pos; + + int lastSector; + }; +} + +#endif \ No newline at end of file diff --git a/src/fs/fat32/fs_helpers.cpp b/src/fs/fat32/fs_helpers.cpp new file mode 100644 index 0000000..c585ec3 --- /dev/null +++ b/src/fs/fat32/fs_helpers.cpp @@ -0,0 +1,213 @@ +/* + * Author: Hunter Overstake + * Email: huntstake@gmail.com + * Date: 5/14/2024 + */ + +#include "util/log.h" +#include "containers/vector.h" +#include "containers/pair.h" +#include "fat32.h" + +/* Finds the entry base on a given file path, fails if: the path is invalid, couldn't find entry via the path. */ +FAT32::Entry *FAT32::find_entry(string file_path) +{ + if (file_path == "/") + return root_dir; + + vector path = parse_file_path(file_path); + + if (path.empty()) + return nullptr; // couldn't parse file_path + + Entry *current = root_dir; + for (int i = 0; i < (int)path.size(); i++) + { + current = current->find_sub_dir(path[i]); + if (current == nullptr) + return nullptr; // couldn't find sub dir with matching name + } + + return current; +} + +/* Parses a given file path into its corresponding sequence of entry string names. */ +vector FAT32::parse_file_path(string file_path) +{ + vector path; + + if (file_path[0] != '/') + return path; + + string part = ""; + for (int i = 1; i < (int)file_path.size(); i++) + { + char c = file_path[i]; + if (c == '/') + { + path.push_back(part); + part = ""; + } + else + { + part += c; + } + } + path.push_back(part); + + return path; +} + +/* Cuts off the last entry name of the file path, used for getting the parent directory file path. */ +string FAT32::snip_file_path(string file_path) +{ + int last_slash_index = 0; + for (int i = 1; i < (int)file_path.size(); i++) + { + if (file_path[i] == '/') + last_slash_index = i; + } + + string snipped = "/"; + + if (last_slash_index != 0) + snipped = file_path.substr(0, last_slash_index); + return snipped; +} + +// below this are temp functions used for testing/debugging, remove when merged with kernel + +void FAT32::dump_fs_structure() +{ + // Use a stack to perform iterative depth-first traversal + vector> s; + s.push_back({root_dir, 0}); + + while (!s.empty()) + { + Entry *current = s.back().first; + vector sub_dirs = current->sub_dirs; + int num_sub_dirs = (int)sub_dirs.size(); + int depth = s.back().second; + s.pop_back(); + + // Print current directory name + for (int i = 0; i < depth; ++i) + { + printf("| "); + } + printf("|-- %s\n", current->name.c_str()); + + // Push subdirectories onto the stack in reverse order + for (int i = num_sub_dirs - 1; i >= 0; i--) + { + s.push_back({sub_dirs[i], depth + 1}); + } + } +} + +void FAT32::fat_dump(int cluster_offset) +{ + int sector_offset = reserved_sectors + (sectors_per_cluster * cluster_offset); + + printf("\n"); + + for (int i = 0; i < sectors_per_cluster; i++) + { + byte *sector = di->read(i + sector_offset); + + for (int j = 0; j < bytes_per_sector; j += 4) + { + printf("%i ", bytes_to_int(sector, j)); + } + delete[] sector; + } + printf("\n"); +} + +void FAT32::mem_dump(int cluster_offset) +{ + int sector_offset = (sectors_per_cluster * cluster_offset); + + printf("\n"); + + for (int i = 0; i < sectors_per_cluster; i++) + { + byte *sector = di->read(i + sector_offset); + + for (int j = 0; j < bytes_per_sector * sectors_per_cluster; j++) + { + if (sector[j] == 0) + { + printf("_ "); + } + else + { + printf("%02x ", sector[j]); + } + } + + delete[] sector; + } + + printf("\n"); +} + +int FAT32::test() +{ + byte *buffer; + + if (!read_file("/folder2/file6.txt", 0, buffer)) + { + kernelLog(LogLevel::WARNING, "FAT32 Test: read failure"); + delete[] buffer; + return failure; + } + + vector list; + if (!list_dir("/folder2", list)) + { + kernelLog(LogLevel::WARNING, "FAT32 Test: list failure"); + return failure; + } + + if (!write_file("/file2.txt", 0, buffer)) + { + kernelLog(LogLevel::WARNING, "FAT32 Test: write failure"); + return failure; + } + + if (!create_file("/folder2/griddy_uncanny.ohi")) + { + kernelLog(LogLevel::WARNING, "FAT32 Test: file creation failure"); + return failure; + } + + if (!write_file("/folder2/griddy_uncanny.ohi", 0, buffer)) + { + kernelLog(LogLevel::WARNING, "FAT32 Test: write failure"); + return failure; + } + + if (!create_dir("/folder1/skibidi_gyatt_rizzler")) + { + kernelLog(LogLevel::WARNING, "FAT32 Test: directory creation failure"); + return failure; + } + + if (!create_file("/folder1/skibidi_gyatt_rizzler/yolo.txt")) + { + kernelLog(LogLevel::WARNING, "FAT32 Test: file creation failure"); + return failure; + } + + if (!write_file("/folder1/skibidi_gyatt_rizzler/yolo.txt", 0, buffer)) + { + kernelLog(LogLevel::WARNING, "FAT32 Test: write failure"); + return failure; + } + + delete[] buffer; + + return success; +} \ No newline at end of file diff --git a/src/fs/fat32/helpers.cpp b/src/fs/fat32/helpers.cpp new file mode 100644 index 0000000..b87f04b --- /dev/null +++ b/src/fs/fat32/helpers.cpp @@ -0,0 +1,76 @@ +/* + * Author: Hunter Overstake + * Email: huntstake@gmail.com + * Date: 5/14/2024 + */ + +#include "helpers.h" + +/* Will convert the sequence of bytes in a given byte array starting at the given start index to an +unsigned short, will only read enough bytes required by an unsigned short. */ +unsigned short bytes_to_short(const byte *bytes, int index) +{ + int size = sizeof(unsigned short); + byte temp[size]; + for (int i = 0; i < size; i++) + temp[i] = bytes[i + index]; + return *reinterpret_cast(temp); +} + +/* Will convert the sequence of bytes in a given byte array starting at the given start index to an +unsigned int, will only read enough bytes required by an unsigned int. */ +unsigned int bytes_to_int(const byte *bytes, int index) +{ + int size = sizeof(unsigned int); + byte temp[size]; + for (int i = 0; i < size; i++) + temp[i] = bytes[i + index]; + return *reinterpret_cast(temp); +} + +/* Will convert the sequence of bytes in a given byte array starting at the given start index to an +unsigned long, will only read enough bytes required by an unsigned long. */ +unsigned long bytes_to_long(const byte *bytes, int index) +{ + int size = sizeof(unsigned long); + byte temp[size]; + for (int i = 0; i < size; i++) + temp[i] = bytes[i + index]; + return *reinterpret_cast(temp); +} + +/* Will convert the given unsigned short to a sequence of bytes in a given byte array starting at a +given index. Will only convert enough bytes required by an unsigned short. */ +void short_to_bytes(unsigned short num, byte *buffer, int offset) +{ + int size = sizeof(unsigned short); + for (int i = 0; i < size; i++) + buffer[i + offset] = ((num >> (i * 8)) & 0xFF); + return; +} + +/* Will convert the given unsigned int to a sequence of bytes in a given byte array starting at a +given index. Will only convert enough bytes required by an unsigned int. */ +void int_to_bytes(unsigned int num, byte *buffer, int offset) +{ + int size = sizeof(unsigned int); + for (int i = 0; i < size; i++) + buffer[i + offset] = ((num >> (i * 8)) & 0xFF); + return; +} + +/* Will convert the given unsigned long to a sequence of bytes in a given byte array starting at a +given index. Will only convert enough bytes required by an unsigned long. */ +void long_to_bytes(unsigned long num, byte *buffer, int offset) +{ + int size = sizeof(unsigned long); + for (int i = 0; i < size; i++) + buffer[i + offset] = ((num >> (i * 8)) & 0xFF); + return; +} + +/* Given two characters, will return the unicode character of them slapped together. */ +wchar_t chars_to_unicode(unsigned char first, unsigned char second) +{ + return (static_cast(first) << 8) | static_cast(second); +} diff --git a/src/fs/fat32/helpers.h b/src/fs/fat32/helpers.h new file mode 100755 index 0000000..be8f0fc --- /dev/null +++ b/src/fs/fat32/helpers.h @@ -0,0 +1,26 @@ +#ifndef MAIN_H +#define MAIN_H + +/* + * Author: Hunter Overstake + * Email: huntstake@gmail.com + * Date: 5/14/2024 + */ + +#include "containers/string.h" +#include "containers/vector.h" + +typedef unsigned char byte; + +unsigned short bytes_to_short(const byte *bytes, int index); +unsigned int bytes_to_int(const byte *bytes, int index); +unsigned long bytes_to_long(const byte *bytes, int index); + +void short_to_bytes(unsigned short num, byte *buffer, int offset); +void int_to_bytes(unsigned int num, byte *buffer, int offset); +void long_to_bytes(unsigned long num, byte *buffer, int offset); + +wchar_t chars_to_unicode(unsigned char first, unsigned char second); +bool string_compare(string first, string second); + +#endif \ No newline at end of file diff --git a/src/fs/filecontext.cpp b/src/fs/filecontext.cpp new file mode 100644 index 0000000..b0fabc8 --- /dev/null +++ b/src/fs/filecontext.cpp @@ -0,0 +1,5 @@ +#include "filecontext.h" + +kernel::fs::FileContext::~FileContext() +{ +} diff --git a/src/fs/filecontext.h b/src/fs/filecontext.h new file mode 100644 index 0000000..3f96176 --- /dev/null +++ b/src/fs/filecontext.h @@ -0,0 +1,21 @@ +#ifndef KERNEL_FILECONTEXT_H +#define KERNEL_FILECONTEXT_H + +#include "util/hasrefcount.h" + +namespace kernel::fs +{ + class FileContext : public HasRefcount + { + public: + virtual ~FileContext() = 0; + + virtual int read(void *buffer, int n) = 0; + + virtual int write(const void *buffer, int n) = 0; + + virtual FileContext *copy() = 0; + }; +} + +#endif \ No newline at end of file diff --git a/src/fs/pipe.cpp b/src/fs/pipe.cpp new file mode 100644 index 0000000..ffe3a70 --- /dev/null +++ b/src/fs/pipe.cpp @@ -0,0 +1,167 @@ +#include "pipe.h" +#include "filecontext.h" +#include "types/status.h" + +kernel::fs::Pipe::Pipe() + : writePos(0), readPos(0), readerCount(0), writerCount(0) +{ +} + +kernel::fs::Pipe::~Pipe() +{ +} + +int kernel::fs::Pipe::put(void *data, int n) +{ + if (getReaderCount() == 0) + { + return EPIPE; + } + + char *s = (char *)data; + int c = 0; + while (c < n && !(writePos == PIPE_SIZE - 1 && readPos == 0) && !(writePos + 1 == readPos)) + { + buffer[writePos] = s[c]; + c++; + writePos++; + if (writePos >= PIPE_SIZE) + { + writePos = 0; + } + } + + if (n > 0 && c == 0) + { + return EFULL; + } + else + { + return c; + } +} + +int kernel::fs::Pipe::read(void *data, int n) +{ + char *s = (char *)data; + int c = 0; + while (readPos != writePos && c < n) + { + s[c] = buffer[readPos]; + c++; + readPos++; + if (readPos >= PIPE_SIZE) + { + readPos = 0; + } + } + return c; +} + +kernel::fs::FileContext *kernel::fs::Pipe::createReader() +{ + return new PipeReader(this); +} + +kernel::fs::FileContext *kernel::fs::Pipe::createWriter() +{ + return new PipeWriter(this); +} + +void kernel::fs::Pipe::addReader() +{ + readerCount++; +} + +void kernel::fs::Pipe::removeReader() +{ + readerCount--; +} + +void kernel::fs::Pipe::addWriter() +{ + writerCount++; +} + +void kernel::fs::Pipe::removeWriter() +{ + writerCount--; +} + +int kernel::fs::Pipe::getReaderCount() const +{ + return readerCount; +} + +int kernel::fs::Pipe::getWriterCount() const +{ + return writerCount; +} + +kernel::fs::Pipe::PipeReader::PipeReader(Pipe *pipe) + : pipe(pipe) +{ + pipe->addReader(); +} + +kernel::fs::Pipe::PipeReader::~PipeReader() +{ + pipe->removeReader(); + if (pipe->getReaderCount() == 0 && pipe->getWriterCount() == 0) + { + delete pipe; + } +} + +int kernel::fs::Pipe::PipeReader::read(void *buffer, int n) +{ + int c = pipe->read(buffer, n); + if (c == 0 && pipe->getWriterCount() == 0) + { + return EEOF; + } + else + { + return c; + } +} + +int kernel::fs::Pipe::PipeReader::write(const void *buffer, int n) +{ + return EIO; +} + +kernel::fs::FileContext *kernel::fs::Pipe::PipeReader::copy() +{ + return new PipeReader(pipe); +} + +kernel::fs::Pipe::PipeWriter::PipeWriter(Pipe *pipe) + : pipe(pipe) +{ + pipe->addWriter(); +} + +kernel::fs::Pipe::PipeWriter::~PipeWriter() +{ + pipe->removeWriter(); + if (pipe->getReaderCount() == 0 && pipe->getWriterCount() == 0) + { + delete pipe; + } +} + +int kernel::fs::Pipe::PipeWriter::read(void *buffer, int n) +{ + return EIO; +} + +int kernel::fs::Pipe::PipeWriter::write(const void *buffer, int n) +{ + return pipe->put(buffer, n); +} + +kernel::fs::FileContext *kernel::fs::Pipe::PipeWriter::copy() +{ + return new PipeWriter(pipe); +} diff --git a/src/fs/pipe.h b/src/fs/pipe.h new file mode 100644 index 0000000..42b934b --- /dev/null +++ b/src/fs/pipe.h @@ -0,0 +1,83 @@ +#ifndef KERNEL_PIPE_H +#define KERNEL_PIPE_H + +#include "util/hasrefcount.h" +#include "filecontext.h" + +namespace kernel::fs +{ + + class Pipe + { + public: + Pipe(); + + ~Pipe(); + + int put(void *data, int n); + + int read(void *data, int n); + + FileContext *createReader(); + + FileContext *createWriter(); + + void addReader(); + + void removeReader(); + + void addWriter(); + + void removeWriter(); + + int getReaderCount() const; + + int getWriterCount() const; + + private: + static const int PIPE_SIZE = 4096; + + char buffer[PIPE_SIZE]; + + int writePos, readPos; + + int readerCount, writerCount; + + class PipeReader : public FileContext + { + public: + PipeReader(Pipe *pipe); + + ~PipeReader(); + + int read(void *buffer, int n); + + int write(const void *buffer, int n); + + FileContext *copy(); + + private: + Pipe *pipe; + }; + + class PipeWriter : public FileContext + { + public: + PipeWriter(Pipe *pipe); + + ~PipeWriter(); + + int read(void *buffer, int n); + + int write(const void *buffer, int n); + + FileContext *copy(); + + private: + Pipe *pipe; + }; + }; + +} + +#endif \ No newline at end of file diff --git a/src/irq/interrupthandler.h b/src/irq/interrupthandler.h new file mode 100644 index 0000000..4beafdc --- /dev/null +++ b/src/irq/interrupthandler.h @@ -0,0 +1,27 @@ +#ifndef KERNEL_INTERRUPTHANDLER_H +#define KERNEL_INTERRUPTHANDLER_H + +namespace kernel::interrupt +{ + + /** + * @brief An interface which drivers that wish to handle hardware interrupts + * must implement. The handleInterrupt() method will be called when the associated + * interrupt number fires. + */ + class InterruptHandler + { + public: + /** + * @brief Method to be called when the interrupt number associated with this + * handler fires. `source` is the interrupt number which caused the handler + * to be called. A handler object may choose to handle multiple interrupt + * numbers. + * @param source + */ + virtual void handleInterrupt(int source) = 0; + }; + +} + +#endif \ No newline at end of file diff --git a/src/irq/interrupts.cpp b/src/irq/interrupts.cpp new file mode 100644 index 0000000..66204ad --- /dev/null +++ b/src/irq/interrupts.cpp @@ -0,0 +1,19 @@ +#include "interrupts.h" + +kernel::interrupt::InterruptHandler *kernel::interrupt::Interrupts::handlers[256]; + +void kernel::interrupt::Interrupts::insertHandler(int id, InterruptHandler *handler) +{ + if(id < HANDLER_COUNT) + { + handlers[id] = handler; + } +} + +void kernel::interrupt::Interrupts::callHandler(int id) +{ + if(handlers[id] != nullptr) + { + handlers[id]->handleInterrupt(id); + } +} \ No newline at end of file diff --git a/src/irq/interrupts.h b/src/irq/interrupts.h new file mode 100644 index 0000000..659fe3b --- /dev/null +++ b/src/irq/interrupts.h @@ -0,0 +1,64 @@ +#ifndef KERNEL_INTERRUPTS_H +#define KERNEL_INTERRUPTS_H + +#include "interrupthandler.h" + +namespace kernel::interrupt +{ + + /** + * @brief Static class which handles the enabling and disabling of interrupts, + * as well as calling the appropriate handler for a particuler interrupt number. + */ + class Interrupts + { + public: + /** + * @brief Initialize interrupts. Must be called before interrupts can work + * properly. This function disables interrupts. When the caller is ready for + * interrupts to fire, they must call `enable()`. + */ + static void init(); + + /** + * @brief Enable interrupts. + */ + static void enable(); + + /** + * @brief Disable interrupts. + */ + static void disable(); + + /** + * @brief Insert a handler object to handle interrupts with the given id. + * Only one handler may be associated with a given interrupt. If a handler + * is already present for `id`, this function will overwrite it. + * @param id + * @param handler + */ + static void insertHandler(int id, InterruptHandler *handler); + + /** + * @param id ler associated with the given interrupt id. + * @param id + */ + static void callHandler(int id); + + private: + /** + * @brief Maximum size of the interrupt handler array. Must be at least as + * big as the largest possible interrupt number. + */ + static const int HANDLER_COUNT = 256; + + /** + * @brief An array of pointers to handler objects. Each index in the array + * corresponds to an interrupt number. + */ + static InterruptHandler *handlers[HANDLER_COUNT]; + }; + +} + +#endif \ No newline at end of file diff --git a/src/kernel.cpp b/src/kernel.cpp new file mode 100644 index 0000000..457b554 --- /dev/null +++ b/src/kernel.cpp @@ -0,0 +1,547 @@ +#include "kernel.h" +#include "util/log.h" +#include "util/hacf.h" +#include "util/string.h" +#include "mmgmt.h" +#include "types/physaddr.h" +#include "sched/process.h" +#include "loader/elf.h" +#include "fs/fat32/filecontextfat32.h" +#include "types/status.h" +#include "fs/pipe.h" + +kernel::Kernel kernel::kernel; + +class PipeReadContext : public kernel::fs::FileContext +{ + int read(void *buffer, int n); + + int write(const void *buffer, int n); +}; + +void (*syscall_table[])(long, long, long, long) = { + (void (*)(long, long, long, long))kernel::syscall_printk, + (void (*)(long, long, long, long))kernel::syscall_mmap, + (void (*)(long, long, long, long))kernel::syscall_munmap, + (void (*)(long, long, long, long))kernel::syscall_clone, + (void (*)(long, long, long, long))kernel::syscall_terminate, + (void (*)(long, long, long, long))kernel::syscall_exec, + (void (*)(long, long, long, long))kernel::syscall_yield, + (void (*)(long, long, long, long))kernel::syscall_sigraise, + (void (*)(long, long, long, long))kernel::syscall_sigret, + (void (*)(long, long, long, long))kernel::syscall_sigwait, + (void (*)(long, long, long, long))kernel::syscall_sigaction, + (void (*)(long, long, long, long))kernel::syscall_open, + (void (*)(long, long, long, long))kernel::syscall_close, + (void (*)(long, long, long, long))kernel::syscall_create, + (void (*)(long, long, long, long))kernel::syscall_unlink, + (void (*)(long, long, long, long))kernel::syscall_read, + (void (*)(long, long, long, long))kernel::syscall_write, + (void (*)(long, long, long, long))kernel::syscall_fddup, + (void (*)(long, long, long, long))kernel::syscall_create_pipe}; + +kernel::sched::Context *do_syscall(unsigned long id, unsigned long arg1, unsigned long arg2, unsigned long arg3, unsigned long arg4, kernel::sched::Context *ctx) +{ + using namespace kernel::sched; + kernel::kernel.getActiveProcess()->storeContext(ctx); + syscall_table[id](arg1, arg2, arg3, arg4); + // kernelLog(LogLevel::DEBUG, "Returning from call %i:\n\tpc = %016x\n\tsp=%016x", id, ctx->getProgramCounter(), ctx->getStackPointer()); + return kernel::kernel.getActiveProcess()->getContext(); +} + +void kernel::syscall_printk(const char *str) +{ + printf(str); + kernel.setCallerReturn(ENONE); +} + +void kernel::syscall_mmap(void *ptr, unsigned long size, int flags) +{ + using namespace kernel::memory; + physaddr_t frame = pageAllocator.reserve(size); + if (frame == PageAllocator::NOMEM) + { + kernel.setCallerReturn(ENOMEM); + } + else + { + kernel.setCallerReturn(map_region(ptr, size, frame, flags | PAGE_USER)); + } +} + +void kernel::syscall_munmap(void *ptr, unsigned long size) +{ + using namespace kernel::memory; + unmap_region(ptr, size); + kernel.setCallerReturn(ENONE); +} + +void kernel::syscall_clone(int (*fn)(void *), void *stack, void *userdata, int flags) +{ + using namespace kernel::sched; + Process *newProcess = kernel.getActiveProcess()->clone(kernel.nextPid(), (void *)fn, stack, userdata); + + kernel.addProcess(*newProcess); + kernel.setCallerReturn(ENONE); +} + +void kernel::syscall_terminate() +{ + kernel.raiseSignal(kernel.getActiveProcess()->getParent(), 17); + kernel.deleteActiveProcess(); + kernel.switchTask(); +} + +void kernel::syscall_exec(const char *path, char *const argv[], char *const envp[]) +{ + if (path == nullptr || argv == nullptr || envp == nullptr) + { + kernel.setCallerReturn(EINVAL); + return; + } + + int argc = 0; + while (argv[argc] != nullptr) + { + argc++; + } + + int envc = 0; + while (envp[envc] != nullptr) + { + envc++; + } + + char **argvCopy = new char *[argc + 1]; + for (int i = 0; i <= argc; i++) + { + if (argv[i] != nullptr) + { + argvCopy[i] = new char[strlen(argv[i]) + 1]; + strcpy(argvCopy[i], argv[i]); + } + else + { + argvCopy[i] = nullptr; + } + } + + char **envpCopy = new char *[envc + 1]; + for (int i = 0; i <= envc; i++) + { + if (envp[i] != nullptr) + { + envpCopy[i] = new char[strlen(envp[i]) + 1]; + strcpy(envpCopy[i], envp[i]); + } + else + { + envpCopy[i] = nullptr; + } + } + + char *pathCopy = (char *)rmalloc(strlen(path) + 1); + strcpy(pathCopy, path); + + int status; + if ((status = kernel.exec(path, argvCopy, envpCopy)) != ENONE) + { + kernel.setCallerReturn(status); + } + + for (int i = 0; argvCopy[i] != nullptr; i++) + { + delete argvCopy[i]; + } + + for (int i = 0; envpCopy[i] != nullptr; i++) + { + delete envpCopy[i]; + } + + delete envpCopy; + delete argvCopy; + // kernelLog(LogLevel::DEBUG, "Returning exec() into %s", pathCopy); + rfree(pathCopy); +} + +void kernel::syscall_yield() +{ + kernel.setCallerReturn(ENONE); + kernel.switchTask(); +} + +void kernel::syscall_sigraise(pid_t pid, int signal) +{ + int status = kernel.raiseSignal(pid, signal); + if (status <= 0) + { + kernel.setCallerReturn(status); + } +} + +void kernel::syscall_sigret() +{ + kernel.getActiveProcess()->signalReturn(); +} + +void kernel::syscall_sigwait() +{ + using namespace kernel::sched; + kernel.getActiveProcess()->setState(Process::State::SIGWAIT); + kernel.sleepActiveProcess(); + kernel.switchTask(); +} + +void kernel::syscall_sigaction(int signal, void (*handler)(void *), void (*trampoline)(void), void *userdata) +{ + kernel.getActiveProcess()->setSignalAction(signal, handler, trampoline, userdata); + kernel.setCallerReturn(ENONE); +} + +void kernel::syscall_open(const char *path, int flags) +{ + using namespace kernel::fs; + if (kernel.getRamFS() == nullptr) + { + kernel.setCallerReturn(EIO); + return; + } + + FileType type; + if (kernel.getRamFS()->file_type(path, type) == failure || type != File) + { + kernel.setCallerReturn(ENOFILE); + return; + } + + FileContext *fc = new FileContextFAT32(*kernel.getRamFS(), path); + int fd = kernel.getActiveProcess()->storeFileContext(fc); + kernel.setCallerReturn(fd); +} + +void kernel::syscall_close(int fd) +{ + kernel.setCallerReturn(kernel.getActiveProcess()->closeFileContext(fd)); +} + +void kernel::syscall_create(const char *path, int flags) +{ + kernel.setCallerReturn(ENOSYS); +} + +void kernel::syscall_unlink(int fd) +{ + kernel.setCallerReturn(ENOSYS); +} + +void kernel::syscall_read(int fd, void *buffer, unsigned long size) +{ + using namespace kernel::fs; + // kernelLog(LogLevel::DEBUG, "pid %i: read(%i, %016x, %i)", kernel.getActiveProcess()->getPid(), fd, buffer, size); + FileContext *fc = kernel.getActiveProcess()->getFileContext(fd); + if (fc == nullptr) + { + kernel.setCallerReturn(ENOFILE); + return; + } + + int count = fc->read(buffer, size); + kernel.setCallerReturn(count); +} + +void kernel::syscall_write(int fd, const void *buffer, unsigned long size) +{ + using namespace kernel::fs; + // kernelLog(LogLevel::DEBUG, "pid %i: write(%i, %016x, %i)", kernel.getActiveProcess()->getPid(), fd, buffer, size); + FileContext *fc = kernel.getActiveProcess()->getFileContext(fd); + if (fc == nullptr) + { + kernelLog(LogLevel::WARNING, "Failed to write on fd %i", fd); + kernel.setCallerReturn(ENOFILE); + return; + } + kernel.setCallerReturn(fc->write(buffer, size)); +} + +void kernel::syscall_fddup(int oldfd, int newfd) +{ + using namespace kernel::fs; + // kernelLog(LogLevel::DEBUG, "pid %i: fddup(%i, %i)", kernel.getActiveProcess()->getPid(), oldfd, newfd); + FileContext *fc = kernel.getActiveProcess()->getFileContext(oldfd); + if (fc == nullptr) + { + kernel.setCallerReturn(ENOFILE); + return; + } + if (kernel.getActiveProcess()->getFileContext(newfd) != nullptr && kernel.getActiveProcess()->closeFileContext(newfd)) + { + kernelLog(LogLevel::ERROR, "fddup() failed to close newfd=%i", newfd); + kernel.setCallerReturn(EUNKNOWN); + return; + } + if (kernel.getActiveProcess()->storeFileContext(fc, newfd) != ENONE) + { + kernelLog(LogLevel::ERROR, "fddup() failed to store newfd=%i", newfd); + kernel.setCallerReturn(EUNKNOWN); + return; + } + kernel.setCallerReturn(ENONE); +} + +void kernel::syscall_create_pipe(int pipefd[2]) +{ + using namespace kernel::fs; + // kernelLog(LogLevel::DEBUG, "pid %i: pipe", kernel.getActiveProcess()->getPid()); + Pipe *pipe = new Pipe(); + if (pipe == nullptr) + { + kernel.setCallerReturn(ENOMEM); + return; + } + + FileContext *reader = pipe->createReader(); + FileContext *writer = pipe->createWriter(); + if (reader == nullptr || writer == nullptr) + { + if (reader) + { + delete reader; + } + if (writer) + { + delete writer; + } + kernel.setCallerReturn(ENOMEM); + return; + } + + pipefd[0] = kernel.getActiveProcess()->storeFileContext(reader); + pipefd[1] = kernel.getActiveProcess()->storeFileContext(writer); + kernel.setCallerReturn(ENONE); +} + +kernel::Kernel::Kernel() + : scheduler(), processTable(), fs(nullptr), currPid(1) +{ +} + +pid_t kernel::Kernel::nextPid() +{ + pid_t v = currPid; + currPid++; + return v; +} + +int kernel::Kernel::initMemory(memory::MemoryMap &memoryMap, unsigned long kernelSize) +{ + using namespace memory; + kernelLog(LogLevel::DEBUG, "Constructing page allocator at %016x", &__end); + new (&pageAllocator) memory::PageAllocator(memoryMap, &__end, page_size); + unsigned long pageMapEnd = (unsigned long)&__end + PageAllocator::mapSize(memoryMap, page_size); + pageMapEnd = (pageMapEnd + (page_size - 1)) & ~(page_size - 1); + unsigned long heapSize = ((unsigned long)&__high_mem + kernelSize) - pageMapEnd; + + kernelLog(LogLevel::DEBUG, "Constructing kernel heap at %016x with size %016x", pageMapEnd, heapSize); + init_heap((void *)pageMapEnd, heapSize); + return 0; +} + +int kernel::Kernel::initRamFS(void *ramfs) +{ + kernelLog(LogLevel::DEBUG, "Creating ramfs %016x", ramfs); + if (fs != nullptr) + { + kernelLog(LogLevel::INFO, "Deleting previous ramfs %016x", fs); + delete fs; + } + fs = new FAT32(ramfs); + return 0; +} + +FAT32 *kernel::Kernel::getRamFS() +{ + return fs; +} + +void kernel::Kernel::switchTask() +{ + scheduler.sched_next(); + memory::loadAddressSpace(*scheduler.get_cur_process()->getAddressSpace()); + // kernelLog(LogLevel::DEBUG, "Switched to pid %i", getActiveProcess()->getPid()); +} + +void kernel::Kernel::setCallerReturn(unsigned long v) +{ + scheduler.get_cur_process()->getContext()->setReturnValue(v); +} + +void kernel::Kernel::addProcess(sched::Process &p) +{ + using namespace sched; + processTable.insert(p.getPid(), p); + if (p.getState() == Process::State::ACTIVE) + { + scheduler.enqueue(&processTable.get(p.getPid())); + } +} + +kernel::sched::Process *kernel::Kernel::getActiveProcess() +{ + return scheduler.get_cur_process(); +} + +void kernel::Kernel::sleepActiveProcess() +{ + scheduler.set_cur_process(nullptr); +} + +void kernel::Kernel::deleteActiveProcess() +{ + processTable.remove(scheduler.get_cur_process()->getPid()); + scheduler.set_cur_process(nullptr); +} + +int kernel::Kernel::raiseSignal(pid_t pid, int signal) +{ + using namespace kernel::sched; + if (!processTable.contains(pid)) + { + kernelLog(LogLevel::WARNING, "Attempt to raise signal %i on non-existent pid %i", signal, pid); + return -1; + } + else if (processTable.get(pid).getState() != Process::State::ACTIVE && processTable.get(pid).getState() != Process::State::SIGWAIT) + { + kernelLog(LogLevel::WARNING, "Process %i cannot acccept signal: invalid state.", pid); + return -1; + } + + bool schedule = processTable.get(pid).getState() == Process::State::SIGWAIT; + int status = processTable.get(pid).signalTrigger(signal); + if (status > 0) + { + kernelLog(LogLevel::DEBUG, "Killing process %i due to signal.", pid); + if (processTable.get(pid).getState() == Process::State::ACTIVE) + { + scheduler.remove(pid); + if (getActiveProcess()->getPid() == pid) + { + scheduler.set_cur_process(nullptr); + switchTask(); + } + } + processTable.remove(pid); + } + else if (status == 0 && schedule) + { + // kernelLog(LogLevel::DEBUG, "Placing process %i back on schedule queue.", pid); + scheduler.enqueue(&processTable.get(pid)); + } + return status; +} + +int kernel::Kernel::exec(const char *path, char *const argv[], char *const envp[]) +{ + using namespace memory; + using namespace loader; + using namespace sched; + using namespace fs; + + // kernelLog(LogLevel::DEBUG, "exec %s", path); + + if (getActiveProcess() == nullptr || getActiveProcess()->getState() != Process::State::ACTIVE || argv == nullptr || envp == nullptr) + { + return -1; + } + + FileType type; + int size; + if (fs->file_type(path, type) == failure || fs->file_size(path, size) == failure) + { + kernelLog(LogLevel::INFO, "kernel.exec() failure: %s not found.", path); + return ENOFILE; + } + + void *exeData = rmalloc(size); + for (int i = 0; i < size; i += 512) + { + byte *data; + if (fs->read_file(path, i / 512, data) == failure) + { + kernelLog(LogLevel::WARNING, "kernel.exec() failure: I/O error."); + return EIO; + } + memcpy((char *)exeData + i, data, size - i >= 512 ? 512 : size - i); + delete data; + data = nullptr; + } + + AddressSpace *addressSpace = createAddressSpace(); + loadAddressSpace(*addressSpace); + + ELF exe(exeData); + buildProgramImage(exe); + + map_region((void *)0x7FBFFF0000, 0x10000, pageAllocator.reserve(0x10000), PAGE_USER | PAGE_RW); + void *base = rmalloc(1UL << 16); + if (base == nullptr) + { + rfree(exeData); + return ENOMEM; + } + void *kernelStack = (void *)((unsigned long)base + (1UL << 16)); + + getActiveProcess()->exec(exe.fileHeader().entry, (void *)0x7FC0000000, kernelStack, addressSpace); + getActiveProcess()->storeProgramArgs(argv, envp); + + if (getActiveProcess()->getFileContext(0) == nullptr) + { + FileContext *in = getLogStream()->open(CharStream::Mode::RO); + if (in != nullptr) + { + if (getActiveProcess()->storeFileContext(in, 0)) + { + delete in; + } + } + } + + if (getActiveProcess()->getFileContext(1) == nullptr) + { + FileContext *out = getLogStream()->open(CharStream::Mode::W); + if (out != nullptr) + { + if (getActiveProcess()->storeFileContext(out, 1)) + { + delete out; + } + } + } + + if (getActiveProcess()->getFileContext(1) == nullptr) + { + FileContext *out = getLogStream()->open(CharStream::Mode::W); + if (out != nullptr) + { + if (getActiveProcess()->storeFileContext(out, 1)) + { + delete out; + } + } + } + + if (getActiveProcess()->getFileContext(2) == nullptr) + { + FileContext *err = getLogStream()->open(CharStream::Mode::W); + if (err != nullptr) + { + if (getActiveProcess()->storeFileContext(err, 2)) + { + delete err; + } + } + } + + rfree(exeData); + return ENONE; +} diff --git a/src/kernel.h b/src/kernel.h new file mode 100644 index 0000000..6d9e225 --- /dev/null +++ b/src/kernel.h @@ -0,0 +1,234 @@ +#ifndef KERNEL_H +#define KERNEL_H + +#include "memory/memorymap.h" +#include "fs/fat32/fat32.h" +#include "sched/queue.h" +#include "containers/binary_search_tree.h" +#include "types/pid.h" + +/** + * @brief Symbol located at the beginning of the kernel binary in memory. + */ +extern char __begin; + +/** + * @brief Symbol located at the end of the kernel binary in memory. + */ +extern char __end; + +/** + * @brief Symbol located at the start of high memory. + */ +extern char __high_mem; + +extern "C" void (*syscall_table[])(long, long, long, long); + +extern "C" kernel::sched::Context *do_syscall(unsigned long id, unsigned long arg1, unsigned long arg2, unsigned long arg3, unsigned long arg4, kernel::sched::Context *ctx); + +namespace kernel +{ + + void initialize(memory::MemoryMap &memoryMap, unsigned long kernelSize); + + class Kernel + { + public: + Kernel(); + + pid_t nextPid(); + + int initMemory(memory::MemoryMap &memoryMap, unsigned long kernelSize); + + int initRamFS(void *ramfs); + + FAT32 *getRamFS(); + + void switchTask(); + + void setCallerReturn(unsigned long v); + + void addProcess(sched::Process &p); + + sched::Process *getActiveProcess(); + + void sleepActiveProcess(); + + void deleteActiveProcess(); + + int raiseSignal(pid_t pid, int signal); + + int exec(const char *path, char *const argv[], char *const envp[]); + + void loadInitProgram(); + + private: + queue scheduler; + + binary_search_tree processTable; + + FAT32 *fs; + + pid_t currPid; + }; + + extern Kernel kernel; + + /** + * @brief Prints `str` on the kernel log. + * @param str + * @return 0 + */ + void syscall_printk(const char *str); + + /** + * @brief Map a region in memory to newly allocated page frames + * @param ptr Pointer to the start of the region to map + * @param size Size in bytes of the region to map + * @param flags Access flags for the pages to map + * @return + */ + void syscall_mmap(void *ptr, unsigned long size, int flags); + + /** + * @brief Free the memory in a particular region + * @param ptr Pointer to the start of the region to unmap + * @param size Size in bytes of the region to unmap + * @return + */ + void syscall_munmap(void *ptr, unsigned long size); + + /** + * @brief Create a new process which shares the current process's address space, + * but has a separate context and is scheduled separately. + * + * @param fn Function pointer to start executing the new process at + * @param stack Stack pointer for the new process + * @param flags + * @return + */ + void syscall_clone(int (*fn)(void *), void *stack, void *userdata, int flags); + + /** + * @brief Completely terminate the current process, freeing all resources that + * belong to it. + * + * @return + */ + void syscall_terminate(); + + /** + * @brief Replace the current process's address space with a fresh one, then + * load a new program image from the executable specified by `path`. + * + * @param path Path to the executable to load + * @param argv Program arguments to pass to the new program + * @param envp Environment variables to pass to the new program + * @return + */ + void syscall_exec(const char *path, char *const argv[], char *const envp[]); + + /** + * @brief Put current process at the end of the scheduler queue, then switch to + * next process. + * + * @return + */ + void syscall_yield(); + + /** + * @brief Call the specified signal handler on the process with id `pid`. + * @param pid Process to call a signal handler on + * @param signal Signal handler to call + * @return + */ + void syscall_sigraise(pid_t pid, int signal); + + /** + * @brief Return from a signal handler, putting the stack and process context + * back to the state they were in just before the signal was triggered. + * @return + */ + void syscall_sigret(); + + /** + * @brief Stop scheduling process until a signal occurs. + * @return + */ + void syscall_sigwait(); + + /** + * @brief Sets the handler function to call when a particular signal occurs. + * Kernel will pass the pointer `userdata` to the handler function when it is + * called. + * @param signal Signal to specify handler for + * @param handler Function pointer to signal handler + * @param trampoline Function pointer to trampoline function called when handler returns. + * @param userdata Userdata to pass to handler function (can be NULL) + * @return + */ + void syscall_sigaction(int signal, void (*handler)(void *), void (*trampoline)(void), void *userdata); + + /** + * @brief Open the file specified by `path` + * @param path Path of the file to open + * @param flags + * @return The file descriptor for the file just opened + */ + void syscall_open(const char *path, int flags); + + /** + * @brief Close a proviously open file + * @param fd File descriptor of the open file to close + * @return + */ + void syscall_close(int fd); + + /** + * @brief Create a new file at the given path + * @param path Path of the file to create + * @param flags Mode for the new file + * @return + */ + void syscall_create(const char *path, int flags); + + /** + * @brief + * @param fd + * @return + */ + void syscall_unlink(int fd); + + /** + * @brief + * @param fd + * @param buffer + * @param size + * @return + */ + void syscall_read(int fd, void *buffer, unsigned long size); + + /** + * @brief + * @param fd + * @param buffer + * @param size + * @return + */ + void syscall_write(int fd, const void *buffer, unsigned long size); + + /** + * @brief Duplicate a file descriptor. `newfd` will serve as an alias for + * `oldfd`. + * @param oldfd + * @param newfd + */ + void syscall_fddup(int oldfd, int newfd); + + /** + * @brief Creates a new pipe. + */ + void syscall_create_pipe(int pipefd[2]); +} + +#endif \ No newline at end of file diff --git a/src/loader/elf.cpp b/src/loader/elf.cpp new file mode 100644 index 0000000..a464303 --- /dev/null +++ b/src/loader/elf.cpp @@ -0,0 +1,87 @@ +/** + * Author: Nathan Giddings + */ + +#include "elf.h" +#include "memory/mmap.h" +#include "memory/pageallocator.h" +#include "util/string.h" + +kernel::loader::ELF::ELF(void *file) +{ + header = (ELFFileHeader *)file; + sectionIndex = 0; +} + +const void *kernel::loader::ELF::fileLocation() const +{ + return (void *)header; +} + +bool kernel::loader::ELF::isValid() const +{ + return header->magic == 0x464c457f && header->phcount > 0; +} + +const kernel::loader::ELFFileHeader &kernel::loader::ELF::fileHeader() const +{ + return *header; +} + +const kernel::loader::ELFProgramHeader &kernel::loader::ELF::currentSection() const +{ + ELFProgramHeader *sectionHeader = (ELFProgramHeader *)((void *)header + header->phoffset); + return sectionHeader[sectionIndex]; +} + +bool kernel::loader::ELF::nextSection() +{ + sectionIndex++; + if (sectionIndex >= header->phcount) + { + sectionIndex = 0; + } + return (sectionIndex > 0); +} + +int kernel::loader::buildProgramImage(ELF &elf) +{ + using namespace kernel::memory; + + // Abort if binary isn't valid format + if (!elf.isValid()) + { + return -1; + } + + do + { + // Check if section should be loaded + if ((ELFSegmentType)elf.currentSection().type != ELFSegmentType::LOAD) + { + continue; + } + + // Try to allocate enough physical memory + unsigned long sectionSize = elf.currentSection().memsize; + physaddr_t frame = pageAllocator.reserve(sectionSize); + if (frame == PageAllocator::NOMEM) + { + return -1; + } + + unsigned long fileSize = elf.currentSection().filesize; + + // Map virtual memory, then copy section data + void *dest = elf.currentSection().vaddr; + const void *src = elf.fileLocation() + elf.currentSection().offset; + unsigned long diff = (unsigned long)dest % page_size; + void *mapPtr = getPageFrame(dest) == 0 ? (dest - diff) : (dest + (page_size - diff)); + unsigned long mapSize = getPageFrame(dest) == 0 ? (sectionSize + diff) : (sectionSize - (page_size - diff)); + map_region(mapPtr, mapSize, frame, PAGE_RW | PAGE_USER | PAGE_EXE); + memset(dest, sectionSize, 0); + memcpy(dest, src, fileSize); + } while (elf.nextSection()); + + return 0; +} diff --git a/src/loader/elf.h b/src/loader/elf.h new file mode 100644 index 0000000..4d3f518 --- /dev/null +++ b/src/loader/elf.h @@ -0,0 +1,131 @@ +/** + * Author: Nathan Giddings + */ +#ifndef KERNEL_ELF_H +#define KERNEL_ELF_H + +#include "types/physaddr.h" +#include + +namespace kernel::loader +{ + + enum class ELFEndianness + { + LITTLE = 1, + BIG = 2 + }; + + enum class ELFISA + { + NA = 0x00, + x86 = 0x03, + MIPS = 0x08, + PPC = 0x14, + PPC64 = 0x15, + ARM = 0x28, + x86_64 = 0x3E, + AARCH64 = 0xB7 + }; + + enum class ELFSegmentType + { + UNUSED = 0, + LOAD = 1, + DYNAMIC = 2 + }; + + class ELFFileHeader + { + public: + uint32_t magic; + char size; + char endianness; + char version; + char abi; + char abi_version; + char reserved[7]; + uint16_t type; + uint16_t machine; + uint32_t _version; + void *entry; +#if defined __i386__ || defined __arm__ + uint32_t phoffset; + uint32_t shoffset; +#elif defined __x86_64__ || defined __aarch64__ + uint64_t phoffset; + uint64_t shoffset; +#endif + uint32_t flags; + uint16_t header_size; + uint16_t phsize; + uint16_t phcount; + uint16_t shsize; + uint16_t shcount; + uint16_t shstrndx; + }; + + class ELFProgramHeader + { + public: + uint32_t type; +#if defined __i386__ || defined __arm__ + uint32_t offset; + void *vaddr; + physaddr_t paddr; + uint32_t filesize; + uint32_t memsize; + uint32_t flags; + uint32_t align; +#elif defined __x86_64__ || defined __aarch64__ + uint32_t flags; + uint64_t offset; + void *vaddr; + physaddr_t paddr; + uint64_t filesize; + uint64_t memsize; + uint64_t align; +#endif + }; + + class ELF + { + public: + ELF(void *file); + + const void *fileLocation() const; + + bool isValid() const; + + const ELFFileHeader &fileHeader() const; + + const ELFProgramHeader ¤tSection() const; + + bool nextSection(); + + private: + ELFFileHeader *header; + + int sectionIndex; + }; + + /** + * @brief Creates a program image from the given binary in the current address space. + * @param elf Binary to unpack + * @return zero upon success, nonzero upon failure + */ + int buildProgramImage(ELF &elf); + +#if defined __i386__ + static const ELFISA HOST_ISA = ELFISA::x86; +#elif defined __x86_64__ + static const ELFISA HOST_ISA = ELFISA::x86_64; +#elif defined __arm__ + static const ELFISA HOST_ISA = ELFISA::ARM; +#elif defined __aarch64__ + static const ELFISA HOST_ISA = ELFISA::AARCH64; +#endif + +} + +#endif \ No newline at end of file diff --git a/src/memory/aarch64/mmu.cpp b/src/memory/aarch64/mmu.cpp new file mode 100644 index 0000000..58e98ab --- /dev/null +++ b/src/memory/aarch64/mmu.cpp @@ -0,0 +1,448 @@ +#include "memory/mmap.h" +#include "memory/pageallocator.h" +#include "types/physaddr.h" +#include "aarch64/irq/exceptionclass.h" +#include "aarch64/irq/syndromedataabort.h" +#include "aarch64/sysreg.h" +#include "util/hacf.h" +#include "kernel.h" +#include "util/log.h" +#include + +using namespace kernel::memory; + +class PageTableEntry +{ +public: + /** + * @brief Set when descriptor points to valid page or table + */ + uint64_t present : 1; + + /** + * @brief Descriptor type: 1 for table/page entry, 0 for block entry + */ + uint64_t type : 1; + + /** + * @brief Index into MAIR_ELn register + */ + uint64_t attrIndex : 3; + + /** + * @brief Non-secure + */ + uint64_t ns : 1; + + /** + * @brief Access permission[1]: when set, accessable by EL0 + * + */ + uint64_t apEL0 : 1; + + /** + * @brief Access permission[2]: when set, memory is read-only + * + */ + uint64_t apReadOnly : 1; + + /** + * @brief Sharability + */ + uint64_t sh : 2; + + /** + * @brief Access flag + */ + uint64_t af : 1; + + /** + * @brief No global + */ + uint64_t ng : 1; + + /** + * @brief Significant bits of physical address of next table or page. + */ + uint64_t outputAddress : 40; + + /** + * @brief Privileged execute-never + */ + uint64_t pxn : 1; + + /** + * @brief Execute-never + */ + uint64_t xn : 1; + + /** + * @brief Copy-on-write + */ + uint64_t cow : 1; + + /** + * @brief Free for software use + */ + uint64_t reserved : 3; + + /** + * @brief When set, all child tables will be treated as if pxn=1 + */ + uint64_t pxnTable : 1; + + /** + * @brief When set, all child tables will be treated as if xn=1 + */ + uint64_t xnTable : 1; + + /** + * @brief When set, all child tables will be treated as if ap=1 + */ + uint64_t apTable : 2; + + /** + * @brief when set, all child tables will be treated as if ns=1 + */ + uint64_t nsTable : 1; + + void clear() + { + *(uint64_t *)this = 0; + } + + void makeTableDescriptor(physaddr_t nextLevel) + { + clear(); + present = 1; + type = 1; + af = 1; + physicalAddress(nextLevel); + } + + void makeBlockDescriptor(physaddr_t frame, int permissions) + { + clear(); + present = 1; + apEL0 = (permissions & PAGE_USER) ? 1 : 0; + apReadOnly = (permissions & PAGE_RW) ? 0 : 1; + xn = (permissions & PAGE_EXE) ? 0 : 1; + af = 1; + physicalAddress(frame); + } + + void makePageDescriptor(physaddr_t frame, int permissions) + { + clear(); + present = 1; + type = 1; + apEL0 = (permissions & PAGE_USER) ? 1 : 0; + apReadOnly = (permissions & PAGE_RW) ? 0 : 1; + xn = (permissions & PAGE_EXE) ? 0 : 1; + af = 1; + physicalAddress(frame); + } + + void physicalAddress(physaddr_t addr) + { + outputAddress = addr >> 12; + } + + physaddr_t physicalAddress() + { + return outputAddress << 12; + } +}; + +PageTableEntry *const kernelTables[] = { + (PageTableEntry *)0xFFFFFFFFFFFFF000, + (PageTableEntry *)0xFFFFFFFFFFE00000, + (PageTableEntry *)0xFFFFFFFFC0000000}; + +PageTableEntry *const userTables[] = { + (PageTableEntry *)0x0000007FFFFFF000, + (PageTableEntry *)0x0000007FFFE00000, + (PageTableEntry *)0x0000007FC0000000}; + +const unsigned long kernel::memory::page_size = 4096; + +void kernel::memory::loadAddressSpace(AddressSpace &addressSpace) +{ + unsigned long ttbr0 = addressSpace.getTableFrame() | ((unsigned long)addressSpace.getId() << 48); + set_ttbr0_el1(ttbr0); + asm volatile("DSB ISH"); + asm volatile("TLBI VMALLE1"); + asm volatile("DSB ISH"); + asm volatile("ISB"); +} + +void kernel::memory::initializeTopTable(physaddr_t frame) +{ + static PageTableEntry *scratchAddr = (PageTableEntry *)0xFFFFFFFFBFFFF000; + physaddr_t buffer = getPageFrame(scratchAddr); + setPageEntry(0, scratchAddr, frame, PAGE_RW); + for (int i = 0; i < 511; i++) + { + ((unsigned long *)scratchAddr)[i] = 0; + } + scratchAddr[511].makeTableDescriptor(frame); + setPageEntry(0, scratchAddr, buffer, PAGE_RW); +} + +size_t kernel::memory::getBlockSize(int level) +{ + switch (level) + { + case 0: + return (1UL << 12); + case 1: + return (1UL << 21); + default: + return 0; + } +} + +physaddr_t kernel::memory::getPageFrame(void *page) +{ + PageTableEntry *const *tables = (unsigned long)page > (unsigned long)&__high_mem ? kernelTables : userTables; + unsigned long linearAddr = (unsigned long)page & 0x0000007FFFFFFFFF; + + for (int i = 0; i < 2; i++) + { + int index = linearAddr >> (30 - i * 9); + if (!tables[i][index].present) + { + return 0; + } + else if (!tables[i][index].type) + { + return tables[i][index].physicalAddress(); + } + } + + if (!tables[2][linearAddr >> 12].present) + { + return 0; + } + return tables[2][linearAddr >> 12].physicalAddress(); +} + +void kernel::memory::setPageEntry(int level, void *page, physaddr_t frame, int flags) +{ + if (level > 2) + { + return; + } + + PageTableEntry *entry; + if ((unsigned long)page >= (unsigned long)&__high_mem) + { + page = (void *)((unsigned long)page - (unsigned long)&__high_mem); + unsigned long index = (unsigned long)page >> (12 + 9 * level); + entry = &kernelTables[2 - level][index]; + } + else if ((unsigned long)page < 0x0000008000000000) + { + unsigned long index = (unsigned long)page >> (12 + 9 * level); + entry = &userTables[2 - level][index]; + } + + if (level == 0) + { + entry->makePageDescriptor(frame, flags); + } + else + { + entry->makeBlockDescriptor(frame, flags); + } + asm volatile("DSB ISH"); + asm volatile("TLBI VMALLE1"); + asm volatile("DSB ISH"); + asm volatile("ISB"); +} + +void kernel::memory::setTableEntry(int level, void *page, physaddr_t table) +{ + if (level == 0) + { + return; + } + + PageTableEntry *entry; + if ((unsigned long)page >= (unsigned long)&__high_mem) + { + page = (void *)((unsigned long)page - (unsigned long)&__high_mem); + unsigned long index = (unsigned long)page >> (12 + 9 * level); + entry = &kernelTables[2 - level][index]; + } + else if ((unsigned long)page < 0x0000008000000000) + { + unsigned long index = (unsigned long)page >> (12 + 9 * level); + entry = &userTables[2 - level][index]; + } + + entry->makeTableDescriptor(table); + asm volatile("DSB ISH"); + asm volatile("TLBI VMALLE1"); + asm volatile("DSB ISH"); + asm volatile("ISB"); +} + +void kernel::memory::clearEntry(int level, void *page) +{ + PageTableEntry *entry; + if ((unsigned long)page >= (unsigned long)&__high_mem) + { + page = (void *)((unsigned long)page - (unsigned long)&__high_mem); + unsigned long index = (unsigned long)page >> (12 + 9 * level); + entry = &kernelTables[2 - level][index]; + } + else if ((unsigned long)page < 0x0000008000000000) + { + unsigned long index = (unsigned long)page >> (12 + 9 * level); + entry = &userTables[2 - level][index]; + } + entry->clear(); + asm volatile("DSB ISH"); + asm volatile("TLBI VMALLE1"); + asm volatile("DSB ISH"); + asm volatile("ISB"); +} + +void fillTranslationTable(int faultLevel, int targetLevel, void *far) +{ + // kernelLog(LogLevel::DEBUG, "fillTranslationTable(%i, %i, %016x)", faultLevel, targetLevel, far); + PageTableEntry *const *tables = nullptr; + if (far >= &__high_mem) + { + tables = kernelTables; + } + else + { + tables = userTables; + } + + unsigned long farOffset = (unsigned long)far - (unsigned long)tables[targetLevel]; + for (int i = (targetLevel + faultLevel - 4); i < targetLevel; i++) + { + unsigned long tableIndex = farOffset >> (12 + 9 * (targetLevel - i - 1)); + physaddr_t tableFrame = pageAllocator.reserve(page_size); + if (tableFrame == PageAllocator::NOMEM) + { + kernelLog(LogLevel::PANIC, "Out of memory while allocating page table"); + hacf(); + } + tables[i][tableIndex].makeTableDescriptor(tableFrame); + asm volatile("DSB ISH"); + asm volatile("TLBI VMALLE1"); + asm volatile("DSB ISH"); + asm volatile("ISB"); + } +} + +void handlePageFault(ExceptionClass type, SyndromeDataAbort syndrome) +{ + void *far = get_far_el1(); + switch (syndrome.statusCode) + { + case DataAbortStatus::ACCESS_FAULT_0: + kernelLog(LogLevel::PANIC, "Unhandled page fault (ACCESS_FAULT_0), FAR_EL1 = %016x", far); + hacf(); + break; + case DataAbortStatus::ACCESS_FAULT_1: + kernelLog(LogLevel::PANIC, "Unhandled page fault (ACCESS_FAULT_1), FAR_EL1 = %016x", far); + hacf(); + break; + case DataAbortStatus::ACCESS_FAULT_2: + kernelLog(LogLevel::PANIC, "Unhandled page fault (ACCESS_FAULT_2), FAR_EL1 = %016x", far); + hacf(); + break; + case DataAbortStatus::ACCESS_FAULT_3: + kernelLog(LogLevel::PANIC, "Unhandled page fault (ACCESS_FAULT_3), FAR_EL1 = %016x", far); + hacf(); + break; + case DataAbortStatus::ADDR_SIZE_FAULT_0: + kernelLog(LogLevel::PANIC, "Unhandled page fault (ADDR_SIZE_FAULT_0), FAR_EL1 = %016x", far); + hacf(); + break; + case DataAbortStatus::ADDR_SIZE_FAULT_1: + kernelLog(LogLevel::PANIC, "Unhandled page fault (ADDR_SIZE_FAULT_1), FAR_EL1 = %016x", far); + hacf(); + break; + case DataAbortStatus::ADDR_SIZE_FAULT_2: + kernelLog(LogLevel::PANIC, "Unhandled page fault (ADDR_SIZE_FAULT_2), FAR_EL1 = %016x", far); + hacf(); + break; + case DataAbortStatus::ADDR_SIZE_FAULT_3: + kernelLog(LogLevel::PANIC, "Unhandled page fault (ADDR_SIZE_FAULT_3), FAR_EL1 = %016x", far); + hacf(); + break; + case DataAbortStatus::PERM_FAULT_0: + kernelLog(LogLevel::PANIC, "Unhandled page fault (PERM_FAULT_0), FAR_EL1 = %016x", far); + hacf(); + break; + case DataAbortStatus::PERM_FAULT_1: + kernelLog(LogLevel::PANIC, "Unhandled page fault (PERM_FAULT_1), FAR_EL1 = %016x", far); + hacf(); + break; + case DataAbortStatus::PERM_FAULT_2: + kernelLog(LogLevel::PANIC, "Unhandled page fault (PERM_FAULT_2), FAR_EL1 = %016x", far); + hacf(); + break; + case DataAbortStatus::PERM_FAULT_3: + kernelLog(LogLevel::PANIC, "Unhandled page fault (PERM_FAULT_3), FAR_EL1 = %016x", far); + hacf(); + break; + case DataAbortStatus::TRANSLATE_FAULT_0: + kernelLog(LogLevel::PANIC, "Unhandled page fault (TRANSLATE_FAULT_0), FAR_EL1 = %016x", far); + hacf(); + break; + case DataAbortStatus::TRANSLATE_FAULT_1: + case DataAbortStatus::TRANSLATE_FAULT_2: + case DataAbortStatus::TRANSLATE_FAULT_3: + int level = (int)syndrome.statusCode & 3; + if (!syndrome.wnr) + { + kernelLog(LogLevel::PANIC, "Unhandled page fault (TRANSLATE_FAULT_%c), FAR_EL1 = %016x", '0' + level, get_far_el1()); + hacf(); + } + + if (far >= kernelTables[0]) + { + kernelLog(LogLevel::PANIC, "Unhandled page fault (TRANSLATE_FAULT_%c), FAR_EL1 = %016x", '0' + level, get_far_el1()); + hacf(); + } + else if (far >= kernelTables[1]) + { + fillTranslationTable(level, 1, far); + } + else if (far >= kernelTables[2]) + { + fillTranslationTable(level, 2, far); + } + else if (far >= userTables[0]) + { + kernelLog(LogLevel::PANIC, "Unhandled page fault (TRANSLATE_FAULT_%c), FAR_EL1 = %016x", '0' + level, get_far_el1()); + hacf(); + } + else if (far >= userTables[1]) + { + fillTranslationTable(level, 1, far); + } + else if (far >= userTables[2]) + { + fillTranslationTable(level, 2, far); + } + else if (far == nullptr) + { + kernelLog(LogLevel::PANIC, "Null pointer exception."); + hacf(); + } + else + { + kernelLog(LogLevel::PANIC, "Unhandled page fault (TRANSLATE_FAULT_%c), FAR_EL1 = %016x", '0' + level, get_far_el1()); + hacf(); + } + break; + } +} diff --git a/src/memory/addressspace.cpp b/src/memory/addressspace.cpp new file mode 100644 index 0000000..c021ca3 --- /dev/null +++ b/src/memory/addressspace.cpp @@ -0,0 +1,18 @@ +#include "addressspace.h" + +using namespace kernel::memory; + +AddressSpace::AddressSpace(physaddr_t frame, int id) + : topLevelTable(frame), id(id) +{ +} + +physaddr_t AddressSpace::getTableFrame() const +{ + return topLevelTable; +} + +int AddressSpace::getId() const +{ + return id; +} diff --git a/src/memory/addressspace.h b/src/memory/addressspace.h new file mode 100644 index 0000000..2beb423 --- /dev/null +++ b/src/memory/addressspace.h @@ -0,0 +1,27 @@ +#ifndef KERNEL_ADDRESSSPACE_H +#define KERNEL_ADDRESSSPACE_H + +#include "util/hasrefcount.h" +#include "types/physaddr.h" + +namespace kernel::memory +{ + + class AddressSpace : public HasRefcount + { + public: + AddressSpace(physaddr_t frame, int id); + + physaddr_t getTableFrame() const; + + int getId() const; + + private: + physaddr_t topLevelTable; + + int id; + }; + +}; + +#endif \ No newline at end of file diff --git a/src/memory/heap.cpp b/src/memory/heap.cpp new file mode 100644 index 0000000..b244350 --- /dev/null +++ b/src/memory/heap.cpp @@ -0,0 +1,344 @@ + +#include "heap.h" +#include "mmap.h" +#include "pageallocator.h" +#include "util/string.h" +#include "../containers/math.h" +#include +#include "util/log.h" + +unsigned long *heap; +unsigned long *linked_list_start = nullptr; // Actually last block in heap, but start of ll +unsigned long *search_start = nullptr; // Set at start of search, don't maintain +unsigned long *search_previous = nullptr; // Must maintain for search +unsigned long *search_current = nullptr; // Must maintain for search +unsigned long *heap_end = nullptr; +// unsigned long num_free_blocks; + +void init_heap(void *heap_ptr, unsigned long mem_size) +{ + linked_list_start = nullptr; + search_start = nullptr; + search_previous = nullptr; + search_current = nullptr; + int overhead_adj_size = mem_size - (WORD_SIZE * OVERHEAD); + heap = (unsigned long *)heap_ptr; + heap[0] = overhead_adj_size / SIZE_T_SIZE; // Header size var + heap[1] = (unsigned long)nullptr; + heap[2] = 0; // Header flag var + heap[(mem_size / SIZE_T_SIZE) - 2] = heap[0]; + heap[(mem_size / SIZE_T_SIZE) - 1] = heap[1]; + linked_list_start = search_start = search_current = heap; + // num_free_blocks = 1; + heap_end = &heap[(mem_size / SIZE_T_SIZE)]; +} + +unsigned long next_power_of_2(unsigned long n) +{ + unsigned count = 0; + // First n in the below condition + // is for the case where n is 0 + if (n && !(n & (n - 1))) + { + return n; + } + while (n != 0) + { + n >>= 1; + count += 1; + } + return 1 << count; +} + +void expand_heap(unsigned long size) +{ + physaddr_t new_frame; + unsigned long adj_size = next_power_of_2(size); + if (size <= 4096) + { // Min size + new_frame = kernel::memory::pageAllocator.reserve(4096); + } + else + { + new_frame = kernel::memory::pageAllocator.reserve(adj_size); + } + kernel::memory::map_region(heap_end, adj_size, new_frame, kernel::memory::PAGE_RW); + + heap_end = heap_end + adj_size; + rebuild_list(search_current); +} + +unsigned long *split_blk(unsigned long size, unsigned long *blk_to_split) +{ + unsigned long *return_block = blk_to_split; + unsigned long *split_block = blk_to_split + (size / SIZE_T_SIZE) + OVERHEAD; // Current address + size + overhead to get address of new block + + split_block[0] = blk_to_split[0] - (size / SIZE_T_SIZE) - OVERHEAD; // Set header size + split_block[1] = blk_to_split[1]; // What ever blk_to_split points to, split block now points to + split_block[2] = 0; // Flag + split_block[split_block[0] + OVERHEAD - 2] = split_block[0]; // Set footer size + split_block[split_block[0] + OVERHEAD - 1] = 0; // Set footer flag + + // Maintain Linked list pointers + if (search_previous && search_previous[1]) + { + search_previous[1] = (unsigned long)split_block; + } + if (blk_to_split == linked_list_start) + { + linked_list_start = split_block; + } + if (blk_to_split == search_previous) + { + search_previous = split_block; + } + if (blk_to_split == search_current) + { + search_current = split_block; + } + return_block[0] = size / SIZE_T_SIZE; // Set header size + return_block[1] = 0; // Reset next address to null + return_block[2] = 1; // Set flag + return_block[return_block[0] + OVERHEAD - 2] = return_block[0]; // Setting footer size + return_block[return_block[0] + OVERHEAD - 1] = 1; // Setting footer flag + + return &return_block[3]; +} + +unsigned long *find_fit(unsigned long size) +{ + search_start = search_current; + do + { + if ((search_current[0] * SIZE_T_SIZE) == size + OVERHEAD) + { // Block is correct size, use it + unsigned long *return_block = search_current; + if (search_previous && search_current != search_start) + { + search_previous[1] = return_block[1]; // Update linked list, removing search next as it is now in use + } + search_current = (unsigned long *)return_block[1]; // Go to next block for future calls + return_block[2] = 1; // Set flag + return_block[return_block[0] + 2] = 1; // Set footer flag + return &return_block[3]; // Return address of start of useable memory + } + else if ((search_current[0] * SIZE_T_SIZE) > ALIGN(size + ((OVERHEAD + 1) * SIZE_T_SIZE * 2))) + { // Block is oversized, split + return split_blk(size, search_current); + } + else if (search_current[1]) + { // Block is not right size and not end of linked list, iterate + search_previous = search_current; + search_current = (unsigned long *)search_current[1]; + } + else + { // End of list, go to start and iterate + search_previous = search_current; + search_current = linked_list_start; + } + } while (search_previous && search_start != search_current && search_current < heap_end); + + return nullptr; // Failed to find block big enough +} + +void *rmalloc(unsigned long size) +{ + unsigned long blk_size = ALIGN(size); + unsigned long *block = find_fit(blk_size); + if (!block) + { // Out of space, request more; + expand_heap(size); + block = find_fit(blk_size); // Try allocation again + if (!block) + { + return nullptr; // Panic, mem expansion failed + } + } + return block; +} + +void rebuild_list(unsigned long *current) +{ + unsigned long *ll_cur, *ll_prev; + ll_prev = linked_list_start; // First block in ll + ll_cur = heap; // First block in physical mem + + // Rebuild ll WATCHME High computation since traversing whole ll? + while (ll_cur < heap_end) + { + while (ll_cur[2] != 0) + { + ll_cur = ll_cur + ll_cur[0] + OVERHEAD; // Start of next block + } + if (ll_cur != linked_list_start) + { + if (ll_cur == current) + { // Set previous and current + search_previous = ll_prev; + search_current = ll_cur; + } + ll_prev[1] = (unsigned long)ll_cur; + ll_prev = ll_cur; + ll_cur = ll_cur + ll_cur[0] + OVERHEAD; // Start of next block + } + else + { + break; // End of heap + } + } + if (current == linked_list_start && linked_list_start[1]) + { // Edge Case when linked_list_start is current, move current to next node + search_previous = linked_list_start; + search_current = (unsigned long *)linked_list_start[1]; + } + ll_prev[1] = 0; // End of list, set next addr to 0 so list doesn't loop +} + +void merge(unsigned long *current) +{ + unsigned long *previous, *next, *ll_cur, *ll_prev; + previous = next = nullptr; + + if (current != heap) + { + if ((current - 1)[0] == 0) + { // Previous block is free, merge + previous = (current - ((current - 2)[0] + OVERHEAD)); + } + } + + if ((current + current[0] + OVERHEAD + 2)[0] == 0 && (current + current[0] + OVERHEAD + 2) < heap_end) + { // Next block is free, merge NOTE 2 to get to the flag on the next block + next = (current + current[0] + OVERHEAD); + } + + if (previous) + { // Merge previous + previous[0] = previous[0] + current[0] + OVERHEAD; // Set size + current[1] = 0; // NOTE Not required, just cleans up heap for debugging + + if (current == linked_list_start) + { // Need to maintain, otherwise rebuild will fail, all other pointers set in rebuild + linked_list_start = previous; + } + + if (current == search_current) + { + search_current = previous; + } + + previous[previous[0] + OVERHEAD - 2] = previous[0]; // Set footer size, flag is already set + current = previous; // Set this incase next is also being merged + // num_free_blocks--; + } + + if (next) + { // Merge next + current[0] = current[0] + next[0] + OVERHEAD; // Set size + next[1] = 0; // NOTE Not required, just cleans up heap + if (next == linked_list_start) + { // Need to maintain, otherwise rebuild will fail, all other pointers set in rebuild + linked_list_start = current; + } + + if (next == search_current) + { + search_current = current; + } + + current[current[0] + OVERHEAD - 2] = current[0]; // Set footer size, flag is already set + // num_free_blocks--; + } + + if (previous || next) + { // Only rebuild list if merge occured + rebuild_list(current); + } +} + +void rfree(void *ptr) +{ + unsigned long *blk_free = (unsigned long *)ptr - 3; // Subtract 3 to get from start of data field to start of header + blk_free[2] = 0; // Set flag + blk_free[blk_free[0] + OVERHEAD - 1] = 0; // Set footer flag + // Insert into linked list + if (search_previous) + { // Already a list, insert + blk_free[1] = search_previous[1]; // search_previous-> blk_free -> search_next + search_previous[1] = (unsigned long)blk_free; + } + else + { // No list, start one + search_previous = linked_list_start; + search_previous[1] = (unsigned long)blk_free; + } + search_current = blk_free; + + // num_free_blocks++; + // Check for merging + merge(blk_free); +} + +void *realloc(void *ptr, unsigned long new_size) +{ + unsigned long *return_blk, *blk_old; + int flag = 1; // Tries mem expansion twice before failing + blk_old = (unsigned long *)ptr - 3; // Subtract 3 to get from start of data field to start of header + + if (!ptr) + { // If pointer null, same as malloc + return rmalloc(new_size); + } + else if (blk_old[0] > new_size + OVERHEAD + 1) + { // Need to shrink and free FIXME Potential Fragmentation, merge not called on freed block + return_blk = split_blk(new_size, blk_old); + rebuild_list(return_blk); + return return_blk; + } + // Need to expand, check if space in current heap + do + { // CLEANME + return_blk = find_fit(new_size); + if (return_blk) + { // Found space in current heap, copy data; + memcpy(return_blk, ptr, blk_old[0]); + } + // No mem left, expand + expand_heap(new_size); + flag--; + } while (flag >= 0); + + return NULL; // Panic, expansion failed +} + +/*Debug Fuctions*/ + +uint64_t xorshift64(uint64_t state) // Psuedo Random Number Generator +{ + uint64_t x = state; + x ^= x << 13; + x ^= x >> 7; + x ^= x << 17; + return state = x; +} + +uint64_t randinrange(uint64_t lower, uint64_t upper, uint64_t rand) +{ + return (rand % (upper - lower + 1)) + lower; +} + +void debug_bounds_check(unsigned long *a, unsigned long *b, uint64_t size_b) +{ + unsigned long *blk1 = a - 3; + unsigned long *blk2 = b - 3; + if (blk1 == blk2) + { + return; + } + if (blk1 >= blk2 && blk1 < blk2 + size_b) + { + while (1) + { + } + } +} diff --git a/src/memory/heap.h b/src/memory/heap.h new file mode 100644 index 0000000..4a222b7 --- /dev/null +++ b/src/memory/heap.h @@ -0,0 +1,137 @@ +/* +Author: Sam Lane + +*/ + +#ifndef _KERNEL_HEAP_H +#define _KERNEL_HEAP_H + +#include + +#define WORD_SIZE sizeof(unsigned long) // Either 4 or 8, we're 64 bit so 8 +#define OVERHEAD 5 +#define ALIGN(size) (((size) + (WORD_SIZE - 1)) & ~(WORD_SIZE - 1)) +#define SIZE_T_SIZE (ALIGN(sizeof(unsigned long))) // header size + +/** + * @brief Initialize heap at the memory address pointed to by + * heap_ptr of size mem_size. Sets initial block up with flags + * and all pointers used for traversing memory. + * + * @param heap_ptr + * @param mem_size + */ +void init_heap(void *heap_ptr, unsigned long mem_size); + +/** + * @brief Expands avaliable heap memory by allocating and mapping + * additional pages. + * + * @param size + */ +void expand_heap(unsigned long size); + +/** + * @brief Iterates through all of memory, adding free blocks + * to the linked list for later allocation. + * + * @param current + */ +void rebuild_list(unsigned long *current); + +/** + * @brief Checks previous and next blocks of memory to see + * if they are free. If they are, a single block is created + * from them and rebuild list is called to fix the linked + * list pointers. + * + * @param current + */ +void merge(unsigned long *current); + +/** + * @brief Frees memory by setting the free bit flag and calling + * merge to check surrounding blocks of memory. Ptr is the pointer + * to writable memory, so it is subtracted by 3 to get to the start + * of the memory block header. + * + * @param ptr + */ +void rfree(void *ptr); + +/** + * @brief Allocates a memory block of size +5 for the header and footer + * info. The header bytes are, in order, size of the block in bytes, pointer to + * the next free block if in linked list, and in use flag. The footer + * bytes are, in order, size of block in bytes and in use flag. Returns a ptr + * to the user of the fourth byte in the allocated block, which is the first + * free byte of memory after the header. + * + * @param size + * @return void* + */ +void *rmalloc(unsigned long size); + +/** + * @brief Takes a pointer to allocated memory. If pointer is null, operates the + * same as malloc. Otherwise, it either shrinks the allocated memory to fit in + * the new size, copying all memory up to the new size. If new size is larger, + * the heap is checked for a block of sufficient size, which is then allocated + * and the memory copied. If no block of sufficient size is found, memory is + * expanded and then the allocation is made. + * + * @param ptr + * @param new_size + * @return void* + */ +void *realloc(void *ptr, unsigned long new_size); + +/** + * @brief Helper function used when expanding memory. Finds the next power + * of 2 larger than n and returns it. + * + * @param n + * @return unsigned long + */ +unsigned long next_power_of_2(unsigned long n); + +/** + * @brief Takes a large block of memory and splits it into two smaller + * blocks, one of size size, and the other with the remaining size minus + * the overhead for the header and footers. The block of size size is + * returned + * + * @param size + * @param blk_to_split + * @return unsigned long* + */ +unsigned long *split_blk(unsigned long size, unsigned long *blk_to_split); + +/** + * @brief Find fit traverses memory looking for free blocks of sufficient + * size. If a block is oversized, it is split into two via split_blk. + * Find_fit iterates over all free blocks of memory in the linked list + * until the starting free block is reached. It then fails to find a block + * of sufficient size and returns a nullptr. + * + * @param size + * @return unsigned long* + */ +unsigned long *find_fit(unsigned long size); + + + +// Testing functions KILLME +#define SIZE 30 + +void debug_bounds_check(unsigned long *a, unsigned long *b, uint64_t size_b); +uint64_t xorshift64(uint64_t state); +uint64_t randinrange(uint64_t lower, uint64_t upper, uint64_t rand); + +struct test_struct +{ + unsigned long *pointer; + int size; +}; + +#endif \ No newline at end of file diff --git a/src/memory/memorymap.cpp b/src/memory/memorymap.cpp new file mode 100644 index 0000000..b963bcc --- /dev/null +++ b/src/memory/memorymap.cpp @@ -0,0 +1,190 @@ +/** + * Author: Nathan Giddings + */ + +#include "memorymap.h" + +using namespace kernel::memory; + +MemoryMap::MemoryMap() : mapSize(0) +{} + +int MemoryMap::place(MemoryType type, unsigned long location, unsigned long size) +{ + if(mapSize <= capacity - 2) + { + insert(type, location, size); + int i = 0; + while(i >= 0) + { + i = trim(i); + } + return 0; + } + else + { + return -1; + } +} + +int MemoryMap::size() const +{ + return mapSize; +} + +const MemoryMap::MemoryRegion& MemoryMap::operator[](int index) const +{ + return map[index]; +} + +int MemoryMap::trim(int index) +{ + if(index + 1 >= mapSize) + { + return -1; + } + MemoryRegion& left = map[index]; + MemoryRegion& right = map[index + 1]; + if(left.overlaps(right) && left.type == right.type) + { + left.size = (right.end() > left.end() ? right.end() : left.end()) - left.location; + remove(index + 1); + return index; + } + else if(left.overlaps(right) && left.type < right.type && right.contains(left)) + { + remove(index); + return index; + } + else if(left.overlaps(right) && left.type < right.type && left.end() <= right.end()) + { + left.size = (right.location > left.location) ? right.location - left.location : 0; + return index + 1; + } + else if(left.overlaps(right) && left.type < right.type) + { + MemoryRegion newRight(left.type, right.end(), left.end() - right.end()); + left.size = (right.location > left.location) ? right.location - left.location : 0; + if (left.size == 0) + { + remove(index); + } + insert(newRight.type, newRight.location, newRight.size); + return index + 2; + } + else if(left.overlaps(right) && left.contains(right)) + { + remove(index + 1); + return index; + } + else if(left.overlaps(right)) + { + right.size = right.end() - left.end(); + right.location = left.end(); + return index + 1; + } + else if((left.end() == right.location) && left.type == right.type) + { + left.size = right.end() - left.location; + remove(index + 1); + return index; + } + else + { + return index + 1; + } +} + +void MemoryMap::remove(int index) +{ + if(index >= 0 && index < mapSize) + { + for(int i = index; i < mapSize - 1; i++) + { + map[i] = map[i + 1]; + } + mapSize--; + } +} + +void MemoryMap::insert(MemoryType type, unsigned long location, unsigned long size) +{ + MemoryRegion newRegion(type, location, size); + unsigned int i = 0; + while(i < mapSize) + { + if(newRegion < map[i]) + { + MemoryRegion buffer = newRegion; + newRegion = map[i]; + map[i] = buffer; + } + i++; + } + map[i] = newRegion; + mapSize++; +} + +MemoryMap::MemoryRegion::MemoryRegion() + : type(MemoryType::UNAVAILABLE), location(0), size(0) +{} + +MemoryMap::MemoryRegion::MemoryRegion(MemoryType type, unsigned long location, unsigned long size) + : type(type), location(location), size(size) +{} + +bool MemoryMap::MemoryRegion::operator<(const MemoryRegion& rhs) const +{ + if(location == rhs.location) + { + return size < rhs.size; + } + else + { + return location < rhs.location; + } +} + +bool MemoryMap::MemoryRegion::operator>(const MemoryRegion& rhs) const +{ + if(location == rhs.location) + { + return size > rhs.size; + } + else + { + return location > rhs.location; + } +} + +bool MemoryMap::MemoryRegion::overlaps(const MemoryRegion& rhs) const +{ + if(rhs.location < location) + { + return rhs.end() > location; + } + else + { + return rhs.location < end(); + } +} + +bool MemoryMap::MemoryRegion::contains(const MemoryRegion& rhs) const +{ + return (rhs.location >= location) && (rhs.end() <= end()); +} + +unsigned long MemoryMap::MemoryRegion::end() const +{ + return location + size; +} + +MemoryMap::MemoryType MemoryMap::MemoryRegion::getType() const +{ + return type; +} + +unsigned long MemoryMap::MemoryRegion::getLocation() const +{ + return location; +} \ No newline at end of file diff --git a/src/memory/memorymap.h b/src/memory/memorymap.h new file mode 100644 index 0000000..57a2b14 --- /dev/null +++ b/src/memory/memorymap.h @@ -0,0 +1,220 @@ +/** + * Author: Nathan Giddings + */ + +#ifndef KERNEL_MEMORYMAP_H +#define KERNEL_MEMORYMAP_H + +namespace kernel::memory +{ + +/** + * @brief Describes in broad terms the layout of an address space + * (or parts(s) of an address space). Most useful for getting an initial + * layout of the physical address space. + */ +class MemoryMap +{ +public: + + /** + * @brief Enumerator declaring the various memory types + */ + enum class MemoryType + { + /** + * @brief Memory at the described location is available for use as RAM. + */ + AVAILABLE = 1, + + /** + * @brief Memory at the described location is unavailable for use for + * some unspecified reason. + */ + UNAVAILABLE = 2, + + /** + * @brief Memory at this region is used for MMIO and should not be used + * as RAM. + */ + MMIO = 3, + + /** + * @brief Memory at the described region is defective and cannot be used. + */ + DEFECTIVE = 4 + }; + + /** + * @brief Describes a particular region of memory starting at `location` + * and extending `size` bytes, with its use indicated by `type`. + */ + class MemoryRegion + { + friend class MemoryMap; + public: + + /** + * @brief Default contructor + */ + MemoryRegion(); + + /** + * @brief Contructs a MemoryRegion object + * @param type + * @param location + * @param size + */ + MemoryRegion(MemoryType type, unsigned long location, unsigned long size); + + /** + * @brief Compares the location and size of two MemoryRegions + * @param rhs object to compare to + * @return true if `rhs` should be placed after `lhs` in a MemoryMap. + */ + bool operator<(const MemoryRegion& rhs) const; + + /** + * @brief Compares the location and size of two MemoryRegions + * @param rhs object to compare to + * @return true if `rhs` should be placed before `lhs` in a MemoryMap. + */ + bool operator>(const MemoryRegion& rhs) const; + + /** + * @brief Checks if this object overlaps the region described by `rhs` + * @param rhs object to compare to + * @return true if the two regions overlap + */ + bool overlaps(const MemoryRegion& rhs) const; + + /** + * @brief Checks if this object completely contains the region + * described by `rhs` + * + * @param rhs object to compare to + * @return true if `rhs` is completely contained within this region + */ + bool contains(const MemoryRegion& rhs) const; + + /** + * @brief Computes the location at the end of this memory region, + * given by (`location` + `size`). + * + * @return The first location after this memory region. + */ + unsigned long end() const; + + /** + * @return An enumerator representing the type of memory in this region. + */ + MemoryType getType() const; + + /** + * @return The starting location of this region. + */ + unsigned long getLocation() const; + + private: + + /** + * @brief The type or use of the memory in this region. Higher values + * overwrite lower values as the memory map is built. + */ + MemoryType type; + + /** + * @brief The location of the beginning of this memory region. + */ + unsigned long location; + + /** + * @brief The size of this memory region in bytes. + */ + unsigned long size; + + }; + + /** + * @brief Default constructor. + */ + MemoryMap(); + + /** + * @brief Places a new region into the memory map. Regions with higher type + * values overwrite overlapping regions with lower type values. The map's + * array is modified so that no two regions overlap, and the regions are + * by location in ascending order. + * + * @param type the type of new new region to place + * @param location the location of the new region to place + * @param size the size of the new region to place + * @return Nonzero if there is insufficient room left in the map's internal + * array. + */ + int place(MemoryType type, unsigned long location, unsigned long size); + + /** + * @brief Gets the size of the MemoryRegion array + * @return the number of MemoryRegions inside this map. + */ + int size() const; + + /** + * @brief Access a particular entry in the memory map + * @param index the index of the entry to access + * @return a reference to the requested entry + */ + const MemoryRegion& operator[](int index) const; + +private: + + /** + * @brief Maximum number of different regions that can be inside a memory map + */ + static const unsigned long capacity = 16; + + /** + * @brief Current number of regions inside this memory map. Any objects + * in `map` at or after index `mapSize` will be uninitialized. + */ + int mapSize; + + /** + * @brief Array of memory regions describing the layout of an address space. + */ + MemoryRegion map[capacity]; + + /** + * @brief Modifies the map array starting at `index` to ensure that + * subsequent regions do not overlap, and that adjacent regions of the + * same type are merged. + * @param index the array index to start operation on + * @return the next index at which this method should be called, or -1 if + * the end of the array has been reached + */ + int trim(int index); + + /** + * @brief Removes a particular map entry from the array and moves + * subsequent entries backwards one space. + * + * @param index the index of the element to be removed + */ + void remove(int index); + + /** + * @brief Inserts a new region into the map such that the array remains + * sorted in ascending order + * + * @param type the type of the new region to insert + * @param location the location of the new region to insert + * @param size the size of the new region to insert + */ + void insert(MemoryType type, unsigned long location, unsigned long size); + +}; + +} + +#endif \ No newline at end of file diff --git a/src/memory/mmap.cpp b/src/memory/mmap.cpp new file mode 100644 index 0000000..dd6c8cd --- /dev/null +++ b/src/memory/mmap.cpp @@ -0,0 +1,46 @@ +#include "mmap.h" +#include "pageallocator.h" +#include "util/log.h" + +using namespace kernel::memory; + +int idCounter = 1; + +AddressSpace *kernel::memory::createAddressSpace() +{ + physaddr_t frame = pageAllocator.reserve(getBlockSize(0)); + if (frame == PageAllocator::NOMEM) + { + return nullptr; + } + + initializeTopTable(frame); + AddressSpace *obj = new AddressSpace(frame, idCounter); + idCounter++; + return obj; +} + +void kernel::memory::destoryAddressSpace(AddressSpace &addressSpace) +{ + // kernelLog(LogLevel::WARNING, "destroyAddressSpace(id = %i) called: not implemented.", addressSpace.getId()); + // TODO +} + +int kernel::memory::map_region(void *addr, size_t size, physaddr_t frame, int flags) +{ + // TODO: Make this more efficient. Map larger blocks when possible. + for (unsigned long p = 0; p < size; p += getBlockSize(0)) + { + setPageEntry(0, addr + p, frame + p, flags); + } + return 0; +} + +physaddr_t kernel::memory::unmap_region(void *addr, size_t size) +{ + for (unsigned long p = 0; p < size; p += getBlockSize(0)) + { + clearEntry(0, addr + p); + } + return 0; +} diff --git a/src/memory/mmap.h b/src/memory/mmap.h new file mode 100644 index 0000000..bde0399 --- /dev/null +++ b/src/memory/mmap.h @@ -0,0 +1,152 @@ +#ifndef KERNEL_MMAP_H +#define KERNEL_MMAP_H + +#include "addressspace.h" +#include "types/physaddr.h" +#include + +namespace kernel::memory +{ + + /** + * @brief Size in bytes of a single page + */ + extern const unsigned long page_size; + + /** + * @brief Permission flags for controlling memory access at page level + */ + enum PageFlags + { + /** + * @brief When set, page is writable. When clear, page is read-only. + */ + PAGE_RW = (1 << 0), + + /** + * @brief When set, page is accesable from usermode. When clear, page is + * only accessable in kernelmode. + */ + PAGE_USER = (1 << 1), + + /** + * @brief When set, page can contain executable code. When clear, code + * in this page cannot be executed. + */ + PAGE_EXE = (1 << 2) + }; + + /** + * @brief Creates and initialzes a new address space, the constructs a + * corresponding AddressSpace object. + * + * @return a pointer to a newly allocated AddressSpace object + */ + AddressSpace *createAddressSpace(); + + /** + * @brief Frees all physical memory associated with the given address space. + * The address space will no longer be usable, and the caller is expected to + * delete the relevant AddressSpace object. + * + * @param addressSpace object describing the address space to destory. + */ + void destoryAddressSpace(AddressSpace &addressSpace); + + /** + * @brief Switches to the provided address space. Calls to `map_region` and + * other such functions will modify only the currently active address space + * (details on this may be platform specific). + * + * @param addressSpace address space to load + */ + void loadAddressSpace(AddressSpace &addressSpace); + + /** + * @brief Map the region of memory starting at `addr` to frames starting at `frame`. + * @param addr linear address of the region to map + * @param size size in bytes of the region to map (should be multiple of page size) + * @param frame starting physical address to map this region to + * @param flags permission flags for the pages to map + * @return 0 upon success, nonzero upon failure + */ + int map_region(void *addr, size_t size, physaddr_t frame, int flags); + + /** + * @brief Unmap the region of memory starting at `addr`. Does not free any + * frames; this must be done by caller. + * + * @param addr Starting address of region to unmap + * @param size Size in bytes of region to unmap + * @return Frame pointed to by `addr` + */ + physaddr_t unmap_region(void *addr, size_t size); + + /** + * @brief Initializes the given frame as a top-level translation table. + * + * Implementation of this function is platform-dependent. + * + * @param frame Frame to store new top table in + */ + void initializeTopTable(physaddr_t frame); + + /** + * @brief Gets the size of a page or block at the given level. Level 0 is + * the smallest available granularity. + * + * Implementation of this function is platform-dependent. + * + * @param level Translation level to use + * @return Size in bytes of block at given level + */ + size_t getBlockSize(int level); + + /** + * @brief Gets the frame referenced by virtual address `page`, if it exists. + * + * Implementation of this function is platform-dependent. + * + * @param page Virtual address for with to get corresponding frame + * @return Frame pointed to by `page`, or 0 if no frame exists. + */ + physaddr_t getPageFrame(void *page); + + /** + * @brief Writes a new page entry for the given page, pointing to the given + * frame. The size of the page is implied by `level`. + * + * Implementation of this function is platform-dependent. + * + * @param level Translation level to use + * @param page Virtual address to map + * @param frame Frame to point the new page to + * @param flags Permission flags for the new page + */ + void setPageEntry(int level, void *page, physaddr_t frame, int flags); + + /** + * @brief Writes a new translation table entry for the virtual memory region + * starting at `page`. The new table is backed by `table`. + * + * Implementation of this function is platform-dependent. + * + * @param level Translation level of the new table entry. 0 is invalid, as it + * is always the final translation level. + * @param page Virtual address for the new table entry + * @param table Frame to store the new translation table + */ + void setTableEntry(int level, void *page, physaddr_t table); + + /** + * @brief Clears the translation table entry corresponding to this location, + * and marks it as 'not present'. + * + * @param level Translation level of the entry to clear + * @param page Virtual address of the page corresponding to the entry to clear + */ + void clearEntry(int level, void *page); + +} + +#endif \ No newline at end of file diff --git a/src/memory/new.cpp b/src/memory/new.cpp new file mode 100644 index 0000000..f77889e --- /dev/null +++ b/src/memory/new.cpp @@ -0,0 +1,32 @@ +#include "new.h" +#include "heap.h" + +void *operator new(size_t size) +{ + return rmalloc(size); +} + +void operator delete(void *ptr) +{ + rfree(ptr); +} + +void *operator new[](size_t size) +{ + return rmalloc(size); +} + +void operator delete[](void *ptr) +{ + return rfree(ptr); +} + +void operator delete(void *ptr, unsigned long sz) +{ + return rfree(ptr); +} + +void operator delete[](void *ptr, unsigned long sz) +{ + return rfree(ptr); +} \ No newline at end of file diff --git a/src/memory/new.h b/src/memory/new.h new file mode 100644 index 0000000..b7f0880 --- /dev/null +++ b/src/memory/new.h @@ -0,0 +1,34 @@ +#ifndef KERNEL_NEW_H +#define KERNEL_NEW_H + +#include + +void *operator new(size_t size); + +void operator delete(void* ptr); + +void *operator new[](size_t size); + +void operator delete[](void *ptr); + +void operator delete(void *ptr, unsigned long sz); + +void operator delete[](void *ptr, unsigned long sz); + +inline void *operator new(size_t, void *p) throw() +{ + return p; +} + +inline void *operator new[](size_t, void *p) throw() +{ + return p; +} + +inline void operator delete(void *, void *) throw() +{ } + +inline void operator delete[](void *, void *) throw() +{ } + +#endif \ No newline at end of file diff --git a/src/memory/pageallocator.cpp b/src/memory/pageallocator.cpp new file mode 100644 index 0000000..4d36f51 --- /dev/null +++ b/src/memory/pageallocator.cpp @@ -0,0 +1,135 @@ +#include "pageallocator.h" +#include "util/math.h" + +using namespace kernel::memory; + +PageAllocator kernel::memory::pageAllocator; + +unsigned long PageAllocator::mapSize(MemoryMap &map, unsigned long blockSize) +{ + int index = map.size() - 1; + while (map[index].getType() != MemoryMap::MemoryType::AVAILABLE) + { + index--; + } + + return 1UL << llog2(sizeof(PageAllocator::Block) * map[index].end() / blockSize); +} + +PageAllocator::PageAllocator() +{ + this->blockMap = nullptr; + this->blockMapSize = 0; + this->maxKVal = 0; + this->blockSize = 0; + this->offset = 0; + this->freeBlockCount = 0; +} + +PageAllocator::PageAllocator(MemoryMap &map, void *mapBase, unsigned long blockSize) +{ + this->blockMap = (Block *)mapBase; + this->blockSize = blockSize; + this->blockMapSize = mapSize(map, blockSize); + this->offset = 0; + this->freeBlockCount = 0; + this->maxKVal = llog2(blockMapSize / sizeof(Block)); + for (int i = 0; i <= maxKVal; i++) + { + availList[i].linkf = &availList[i]; + availList[i].linkb = &availList[i]; + } + + for (int i = 0; i < blockMapSize / sizeof(Block); i++) + { + blockMap[i] = Block(nullptr, nullptr, 0, Block::RESERVED); + } + + for (int i = 0; i < map.size(); i++) + { + if (map[i].getType() != MemoryMap::MemoryType::AVAILABLE) + { + continue; + } + + unsigned long location = map[i].getLocation() + blockSize - 1; + location -= location % blockSize; + while (location + blockSize <= map[i].end()) + { + insert(location / blockSize, 0); + location += blockSize; + freeBlockCount++; + } + } +} + +physaddr_t PageAllocator::reserve(unsigned long size) +{ + unsigned long k = llog2((size - 1) / blockSize + 1); + for (unsigned long j = k; j <= maxKVal; j++) + { + if (availList[j].linkf != &availList[j]) + { + Block *block = availList[j].linkb; + availList[j].linkb = block->linkb; + availList[j].linkb->linkf = &availList[j]; + block->tag = Block::RESERVED; + while (j > k) + { + j--; + Block *buddy = block + (1UL << j); + *buddy = Block(&availList[j], &availList[j], j, Block::FREE); + block->kval = j; + availList[j].linkb = buddy; + availList[j].linkf = buddy; + } + unsigned long index = block - blockMap; + freeBlockCount -= 1UL << k; + return offset + index * blockSize; + } + } + return NOMEM; +} + +unsigned long PageAllocator::free(physaddr_t location) +{ + unsigned long index = (location - offset) / blockSize; + unsigned long k = blockMap[index].kval; + insert(index, k); + return (1UL << k) * blockSize; +} + +void PageAllocator::insert(unsigned long index, unsigned long k) +{ + freeBlockCount += 1UL << k; + while (k < maxKVal) + { + unsigned long buddyIndex = index ^ (1UL << k); + if (blockMap[buddyIndex].tag != Block::FREE || blockMap[buddyIndex].kval != k) + { + break; + } + blockMap[buddyIndex].linkb->linkf = blockMap[buddyIndex].linkf; + blockMap[buddyIndex].linkf->linkb = blockMap[buddyIndex].linkb; + blockMap[buddyIndex].tag = Block::RESERVED; + k++; + if (buddyIndex < index) + { + index = buddyIndex; + } + } + Block *p = availList[k].linkf; + blockMap[index] = Block(p, &availList[k], k, Block::FREE); + p->linkb = &blockMap[index]; + availList[k].linkf = &blockMap[index]; +} + +PageAllocator::Block::Block() + : linkf(nullptr), linkb(nullptr), kval(0), tag(RESERVED) +{ +} + +PageAllocator::Block::Block(Block *linkf, Block *linkb, unsigned long kval, unsigned long tag) + : linkf(linkf), linkb(linkb), kval(kval), tag(tag) +{ +} diff --git a/src/memory/pageallocator.h b/src/memory/pageallocator.h new file mode 100644 index 0000000..663dd90 --- /dev/null +++ b/src/memory/pageallocator.h @@ -0,0 +1,116 @@ +/** + * Author: Nathan Giddings + * + * Code adapted from personal project: https://github.com/ngiddings/quark-libmalloc/blob/master/include/libmalloc/buddy_alloc.h + */ +#ifndef PAGE_ALLOCATOR_H +#define PAGE_ALLOCATOR_H + +#include "memorymap.h" +#include "types/physaddr.h" + +namespace kernel::memory +{ + +/** + * @brief Manages the reservation and freeing of physical memory pages. + */ +class PageAllocator +{ +public: + + static const physaddr_t NOMEM = (physaddr_t)(~0); + + static unsigned long mapSize(MemoryMap& map, unsigned long blockSize); + + PageAllocator(); + + /** + * @brief Constructs the page allocator's internal data structures from an + * existing description of the memory layout. + * + * @param map a description of the physical memory layout + * @param mapBase location + * @param blockSize the size in bytes of a single block of memory + */ + PageAllocator(MemoryMap& map, void *mapBase, unsigned long blockSize); + + /** + * @brief Reserves a chunk of memory at least `size` bytes long. The + * reserved physical memory will be contiguous; therefore, the allocator + * can potentially fail due to fragmentation. + * + * @param size the minimum number of bytes to reserve + * @return the physical address of the newly allocated chunk of memory, or + * NOMEM upon failure. + */ + physaddr_t reserve(unsigned long size); + + /** + * @brief + * @param location + * @return + */ + unsigned long free(physaddr_t location); + +private: + + class Block + { + public: + + static const unsigned long RESERVED = 0; + + static const unsigned long FREE = 1; + + Block(); + + Block(Block *linkf, Block *linkb, unsigned long kval, unsigned long tag); + + Block *linkb; + + Block *linkf; + + unsigned long kval; + + unsigned long tag; + + }; + + /** + * @brief The number of distinct block sizes that are supported. Serves + * as the maximum possible value of `maxKVal`, and defines the size of + * the largest possible block: (2 ^ (availListSize-1)) * blockSize. + */ + static const int availListSize = 32; + + Block availList[availListSize]; + + Block *blockMap; + + unsigned long blockMapSize; + + unsigned long maxKVal; + + unsigned long blockSize; + + unsigned long offset; + + unsigned long freeBlockCount; + + /** + * @brief Inserts a new block into the appropriate linked list, performing + * mergers with buddy blocks as needed. + * + * @param index + * @param k + */ + void insert(unsigned long index, unsigned long k); + +}; + +extern PageAllocator pageAllocator; + +} + +#endif \ No newline at end of file diff --git a/src/mmgmt.h b/src/mmgmt.h new file mode 100644 index 0000000..8132e53 --- /dev/null +++ b/src/mmgmt.h @@ -0,0 +1,10 @@ +#ifndef KERNEL_MEMORY +#define KERNEL_MEMORY + +#include "memory/heap.h" +#include "memory/pageallocator.h" +#include "memory/new.h" +#include "memory/mmap.h" +#include "memory/addressspace.h" + +#endif \ No newline at end of file diff --git a/src/sched/aarch64/context.cpp b/src/sched/aarch64/context.cpp new file mode 100644 index 0000000..2797be1 --- /dev/null +++ b/src/sched/aarch64/context.cpp @@ -0,0 +1,138 @@ +#include "sched/context.h" +#include "util/string.h" + +kernel::sched::Context::Context() +{ + for (int i = 0; i < 64; i++) + { + fpRegs[i] = 0; + } + for (int i = 0; i < 31; i++) + { + gpRegs[i] = i; + } + stackPointer = 0; + programCounter = 0; + programStatus = 0; + fpcr = 0; + fpsr = 0; +} + +kernel::sched::Context::Context(void *pc) +{ + for (int i = 0; i < 64; i++) + { + fpRegs[i] = 0; + } + for (int i = 0; i < 31; i++) + { + gpRegs[i] = i; + } + stackPointer = 0; + programCounter = (uint64_t)pc; + programStatus = 0; + fpcr = 0; + fpsr = 0; + kernelStack = 0; +} + +kernel::sched::Context::Context(void *pc, void *sp) +{ + for (int i = 0; i < 64; i++) + { + fpRegs[i] = 0; + } + for (int i = 0; i < 31; i++) + { + gpRegs[i] = i; + } + stackPointer = (uint64_t)sp; + programCounter = (uint64_t)pc; + programStatus = 0; + fpcr = 0; + fpsr = 0; + kernelStack = 0; +} + +kernel::sched::Context::Context(void *pc, void *sp, void *ksp) +{ + for (int i = 0; i < 64; i++) + { + fpRegs[i] = 0; + } + for (int i = 0; i < 31; i++) + { + gpRegs[i] = i; + } + stackPointer = (uint64_t)sp; + programCounter = (uint64_t)pc; + programStatus = 0; + fpcr = 0; + fpsr = 0; + kernelStack = (uint64_t)ksp; +} + +void kernel::sched::Context::functionCall(void *func_ptr, void *returnLoc, unsigned long arg) +{ + programCounter = (unsigned long)func_ptr; + gpRegs[30] = (unsigned long)returnLoc; + gpRegs[0] = arg; +} + +void kernel::sched::Context::setProgramCounter(void *pc) +{ + programCounter = (uint64_t)pc; +} + +void *kernel::sched::Context::getProgramCounter() const +{ + return (void *)programCounter; +} + +void kernel::sched::Context::setStackPointer(void *sp) +{ + stackPointer = (uint64_t)sp; +} + +void *kernel::sched::Context::getStackPointer() const +{ + return (void *)stackPointer; +} + +void kernel::sched::Context::setKernelStack(void *sp) +{ + kernelStack = (uint64_t)sp; +} + +void *kernel::sched::Context::getKernelStack() const +{ + return (void *)kernelStack; +} + +void kernel::sched::Context::setProcessArgs(int argc, char **argv, char **envp) +{ + gpRegs[0] = (uint64_t)argc; + gpRegs[1] = (uint64_t)argv; + gpRegs[2] = (uint64_t)envp; +} + +void kernel::sched::Context::setReturnValue(unsigned long v) +{ + gpRegs[0] = v; +} + +void kernel::sched::Context::pushLong(unsigned long v) +{ + unsigned long *sp = (unsigned long *)stackPointer; + *--sp = v; + stackPointer = (unsigned long)sp; +} + +void kernel::sched::Context::pushString(const char *str) +{ + int len = strlen(str) + 1; + len += 15; + len -= len % 16; + stackPointer -= len; + strcpy((char *)stackPointer, str); +} diff --git a/src/sched/aarch64/loadcontext.s b/src/sched/aarch64/loadcontext.s new file mode 100644 index 0000000..ae29384 --- /dev/null +++ b/src/sched/aarch64/loadcontext.s @@ -0,0 +1,58 @@ +.section ".text" + +.global load_context +load_context: + // Load FP registers from (*x0) + ld4 {v0.2d, v1.2d, v2.2d, v3.2d}, [x0], #64 + ld4 {v4.2d, v5.2d, v6.2d, v7.2d}, [x0], #64 + ld4 {v8.2d, v9.2d, v10.2d, v11.2d}, [x0], #64 + ld4 {v12.2d, v13.2d, v14.2d, v15.2d}, [x0], #64 + ld4 {v16.2d, v17.2d, v18.2d, v19.2d}, [x0], #64 + ld4 {v20.2d, v21.2d, v22.2d, v23.2d}, [x0], #64 + ld4 {v24.2d, v25.2d, v26.2d, v27.2d}, [x0], #64 + ld4 {v28.2d, v29.2d, v30.2d, v31.2d}, [x0], #64 + + // Skip X0-X3 (we need to use them as scratch registers) + add x0, x0, #32 + + // Load X4-X30, SP + ldp x4, x5, [x0], #16 + ldp x6, x7, [x0], #16 + ldp x8, x9, [x0], #16 + ldp x10, x11, [x0], #16 + ldp x12, x13, [x0], #16 + ldp x14, x15, [x0], #16 + ldp x16, x17, [x0], #16 + ldp x18, x19, [x0], #16 + ldp x20, x21, [x0], #16 + ldp x22, x23, [x0], #16 + ldp x24, x25, [x0], #16 + ldp x26, x27, [x0], #16 + ldp x28, x29, [x0], #16 + ldp x30, x1, [x0], #16 + msr sp_el0, x1 + + // Load PC, status register + ldp x2, x3, [x0], #16 + msr elr_el1, x2 + msr spsr_el1, x3 + + // Load FP control & status + ldp x2, x3, [x0], #16 + msr fpcr, x2 + msr fpsr, x3 + + // Load kernel SP + ldp x2, x3, [x0, #-8] + mov sp, x3 + + // Go back to X0-X3 + sub x0, x0, #288 + + // Load X2 & X3 + ldp x2, x3, [x0, #16] + + // Load X0 & X1 + ldp x0, x1, [x0] + + eret diff --git a/src/sched/actiontype.h b/src/sched/actiontype.h new file mode 100644 index 0000000..c8198f8 --- /dev/null +++ b/src/sched/actiontype.h @@ -0,0 +1,14 @@ +#ifndef KERNEL_ACTIONTYPE_H +#define KERNEL_ACTIONTYPE_H + +namespace kernel::sched +{ + enum class ActionType + { + NONE, + HANDLER, + KILL + }; +} + +#endif \ No newline at end of file diff --git a/src/sched/context.h b/src/sched/context.h new file mode 100644 index 0000000..844dcfd --- /dev/null +++ b/src/sched/context.h @@ -0,0 +1,70 @@ +#ifndef KERNEL_CONTEXT_H +#define KERNEL_CONTEXT_H + +#include + +namespace kernel::sched +{ + + class Context + { + public: + Context(); + + Context(void *pc); + + Context(void *pc, void *sp); + + Context(void *pc, void *sp, void *ksp); + + void functionCall(void *func_ptr, void *returnLoc, unsigned long arg); + + void setProgramCounter(void *pc); + + void *getProgramCounter() const; + + void setStackPointer(void *sp); + + void *getStackPointer() const; + + void setKernelStack(void *sp); + + void *getKernelStack() const; + + void setProcessArgs(int argc, char **argv, char **envp); + + void setReturnValue(unsigned long v); + + void pushLong(unsigned long v); + + void pushString(const char *str); + + private: +#if defined __aarch64__ + + uint64_t fpRegs[64]; + + uint64_t gpRegs[31]; + + uint64_t stackPointer; + + uint64_t programCounter; + + uint64_t programStatus; + + uint64_t fpcr; + + uint64_t fpsr; + + uint64_t kernelStack; + +#else +#error "Platform not supported" +#endif + }; + + extern "C" void load_context(const Context *ctx); + +} + +#endif \ No newline at end of file diff --git a/src/sched/process.cpp b/src/sched/process.cpp new file mode 100644 index 0000000..09cab30 --- /dev/null +++ b/src/sched/process.cpp @@ -0,0 +1,380 @@ +#include "process.h" +#include "memory/new.h" +#include "memory/mmap.h" +#include "types/status.h" +#include "util/log.h" +#include "memory/heap.h" + +pid_t kernel::sched::Process::nextPidVal = 1; + +pid_t kernel::sched::Process::nextPid() +{ + pid_t v = nextPidVal; + nextPidVal++; + return v; +} + +kernel::sched::Process::Process() + : pid(0), parent(0), state(State::ACTIVE), ctx(), addressSpace(nullptr), backupCtx(nullptr), files() +{ + for (int i = 0; i < MAX_SIGNAL; i++) + { + signalHandlers[i].type = ActionType::NONE; + } +} + +kernel::sched::Process::Process(pid_t pid, pid_t parent, void *entry, void *stack, void *kernelStack, kernel::memory::AddressSpace *addressSpace) + : pid(pid), parent(parent), state(State::ACTIVE), ctx(entry, stack, kernelStack), addressSpace(addressSpace), backupCtx(nullptr), files() +{ + addressSpace->addReference(); + for (int i = 0; i < MAX_SIGNAL; i++) + { + signalHandlers[i].type = ActionType::NONE; + } +} + +kernel::sched::Process::Process(Process &other) + : files() +{ + pid = other.pid; + parent = other.parent; + ctx = other.ctx; + state = other.state; + addressSpace = other.addressSpace; + if (addressSpace != nullptr) + { + addressSpace->addReference(); + } + if (other.backupCtx == nullptr) + { + backupCtx = nullptr; + } + else + { + backupCtx = new Context(*other.backupCtx); + } + for (int i = 0; i < MAX_SIGNAL; i++) + { + signalHandlers[i].type = other.signalHandlers[i].type; + signalHandlers[i].handler = other.signalHandlers[i].handler; + signalHandlers[i].trampoline = other.signalHandlers[i].trampoline; + signalHandlers[i].userdata = other.signalHandlers[i].userdata; + } + for (int fd : other.files) + { + storeFileContext(other.files.get(fd), fd); + } +} + +kernel::sched::Process::~Process() +{ + addressSpace->removeReference(); + if (addressSpace->getRefCount() <= 0) + { + kernel::memory::destoryAddressSpace(*addressSpace); + delete addressSpace; + } + if (backupCtx != nullptr) + { + delete backupCtx; + } + for (int fd : files) + { + closeFileContext(fd); + } +} + +int kernel::sched::Process::exec(void *pc, void *stack, void *kernelStack, kernel::memory::AddressSpace *addressSpace) +{ + if (state != State::ACTIVE) + { + // This will probably break if called during a signal handler + return -1; + } + if (this->addressSpace != nullptr) + { + this->addressSpace->removeReference(); + if (this->addressSpace->getRefCount() <= 0) + { + kernel::memory::destoryAddressSpace(*this->addressSpace); + delete this->addressSpace; + } + } + this->addressSpace = addressSpace; + this->addressSpace->addReference(); + ctx.setProgramCounter(pc); + ctx.setStackPointer(stack); + ctx.setKernelStack(kernelStack); + return 0; +} + +kernel::sched::Process *kernel::sched::Process::clone(pid_t pid, void *pc, void *stack, void *userdata) +{ + using namespace kernel::fs; + + void *base = rmalloc(1UL << 16); + if (base == nullptr) + { + return nullptr; + } + + void *kernelStack = (void *)((unsigned long)base + (1UL << 16)); + Process *copy = new Process(pid, this->pid, pc, stack, kernelStack, this->addressSpace); + if (copy == nullptr) + { + return nullptr; + } + copy->getContext()->functionCall(pc, nullptr, (unsigned long)userdata); + + for (int fd : files) + { + copy->storeFileContext(files.get(fd)->copy(), fd); + } + + return copy; +} + +kernel::sched::Context *kernel::sched::Process::getContext() +{ + return &ctx; +} + +void kernel::sched::Process::storeContext(kernel::sched::Context *newCtx) +{ + ctx = *newCtx; +} + +kernel::sched::Process::State kernel::sched::Process::getState() const +{ + return state; +} + +void kernel::sched::Process::setState(State newState) +{ + state = newState; +} + +pid_t kernel::sched::Process::getPid() const +{ + return pid; +} + +pid_t kernel::sched::Process::getParent() const +{ + return parent; +} + +kernel::memory::AddressSpace *kernel::sched::Process::getAddressSpace() +{ + return addressSpace; +} + +void kernel::sched::Process::setSignalAction(int signal, void (*handler)(void *), void (*trampoline)(void), void *userdata) +{ + if (signal < 0 || signal >= MAX_SIGNAL) + { + return; + } + if (handler == nullptr) + { + signalHandlers[signal].type = ActionType::NONE; + signalHandlers[signal].handler = nullptr; + signalHandlers[signal].trampoline = nullptr; + signalHandlers[signal].userdata = nullptr; + } + else + { + signalHandlers[signal].type = ActionType::HANDLER; + signalHandlers[signal].handler = handler; + signalHandlers[signal].trampoline = trampoline; + signalHandlers[signal].userdata = userdata; + } +} + +int kernel::sched::Process::signalTrigger(int sig) +{ + if (sig < 0 || sig >= MAX_SIGNAL) + { + return 0; + } + + switch (signalHandlers[sig].type) + { + case ActionType::NONE: + return 0; + case ActionType::HANDLER: + if (state == State::SIGNAL) + { + return -1; + } + backupCtx = new Context(ctx); + ctx.functionCall((void *)signalHandlers[sig].handler, + (void *)signalHandlers[sig].trampoline, + (unsigned long)signalHandlers[sig].userdata); + state = State::SIGNAL; + return 0; + case ActionType::KILL: + return 1; + } +} + +void kernel::sched::Process::signalReturn() +{ + if (state != State::SIGNAL) + { + return; + } + ctx = *backupCtx; + delete backupCtx; + backupCtx = nullptr; + state = State::ACTIVE; +} + +void kernel::sched::Process::storeProgramArgs(char *const argv[], char *const envp[]) +{ + int envc = 0; + while (envp[envc] != nullptr) + { + envc++; + } + + int argc = 0; + while (argv[argc] != nullptr) + { + argc++; + } + + char **argArray = new char *[argc]; + char **envArray = new char *[envc + 1]; + for (int i = argc - 1; i >= 0; i--) + { + ctx.pushString(argv[i]); + argArray[i] = (char *)ctx.getStackPointer(); + } + for (int i = envc - 1; i >= 0; i--) + { + ctx.pushString(envp[i]); + envArray[i] = (char *)ctx.getStackPointer(); + } + envArray[envc] = nullptr; + + if (argc % 2 == 1) + { + ctx.pushLong(0); + } + for (int i = argc - 1; i >= 0; i--) + { + ctx.pushLong((unsigned long)argArray[i]); + } + void *argPtr = ctx.getStackPointer(); + + if ((envc + 1) % 2 == 1) + { + ctx.pushLong(0); + } + for (int i = envc; i >= 0; i--) + { + ctx.pushLong((unsigned long)envArray[i]); + } + void *envPtr = ctx.getStackPointer(); + + ctx.setProcessArgs(argc, (char **)argPtr, (char **)envPtr); +} + +kernel::fs::FileContext *kernel::sched::Process::getFileContext(int fd) const +{ + if (files.contains(fd)) + { + return files.get(fd); + } + else + { + return nullptr; + } +} + +int kernel::sched::Process::storeFileContext(kernel::fs::FileContext *f) +{ + int fd = files.size(); + files.insert(fd, f); + f->addReference(); + return fd; +} + +int kernel::sched::Process::storeFileContext(kernel::fs::FileContext *f, int fd) +{ + if (files.contains(fd)) + { + return EEXISTS; + } + else + { + files.insert(fd, f); + f->addReference(); + return ENONE; + } +} + +int kernel::sched::Process::closeFileContext(int fd) +{ + using namespace kernel::fs; + if (!files.contains(fd)) + { + return ENOFILE; + } + FileContext *fc = files.get(fd); + files.remove(fd); + fc->removeReference(); + if (fc->getRefCount() <= 0) + { + // kernelLog(LogLevel::DEBUG, "Freeing file context %i", fd); + delete fc; + } + return ENONE; +} + +kernel::sched::Process &kernel::sched::Process::operator=(Process &other) +{ + pid = other.pid; + parent = other.parent; + ctx = other.ctx; + state = other.state; + if (addressSpace != nullptr && addressSpace != other.addressSpace) + { + addressSpace->removeReference(); + if (addressSpace->getRefCount() <= 0) + { + kernel::memory::destoryAddressSpace(*addressSpace); + delete addressSpace; + } + } + addressSpace = other.addressSpace; + if (addressSpace != nullptr) + { + addressSpace->addReference(); + } + if (other.backupCtx == nullptr) + { + backupCtx = nullptr; + } + else + { + backupCtx = new Context(*other.backupCtx); + } + for (int i = 0; i < MAX_SIGNAL; i++) + { + signalHandlers[i].type = other.signalHandlers[i].type; + signalHandlers[i].handler = other.signalHandlers[i].handler; + signalHandlers[i].trampoline = other.signalHandlers[i].trampoline; + signalHandlers[i].userdata = other.signalHandlers[i].userdata; + } + for (int fd : files) + { + closeFileContext(fd); + } + for (int fd : other.files) + { + storeFileContext(other.files.get(fd), fd); + } + return *this; +} diff --git a/src/sched/process.h b/src/sched/process.h new file mode 100644 index 0000000..bca3753 --- /dev/null +++ b/src/sched/process.h @@ -0,0 +1,90 @@ +#ifndef KERNEL_PROCESS_H +#define KERNEL_PROCESS_H + +#include "memory/addressspace.h" +#include "context.h" +#include "types/pid.h" +#include "signalaction.h" +#include "containers/binary_search_tree.h" +#include "fs/filecontext.h" + +namespace kernel::sched +{ + + class Process + { + public: + enum class State + { + ACTIVE, + SIGNAL, + SIGWAIT + }; + + static pid_t nextPid(); + + Process(); + + Process(pid_t pid, pid_t parent, void *entry, void *stack, void *kernelStack, kernel::memory::AddressSpace *addressSpace); + + Process(Process &other); + + ~Process(); + + Process &operator=(Process &other); + + int exec(void *pc, void *stack, void *kernelStack, kernel::memory::AddressSpace *addressSpace); + + Process *clone(pid_t pid, void *pc, void *stack, void *userdata); + + Context *getContext(); + + void storeContext(Context *newCtx); + + State getState() const; + + void setState(State newState); + + pid_t getPid() const; + + pid_t getParent() const; + + kernel::memory::AddressSpace *getAddressSpace(); + + void setSignalAction(int signal, void (*handler)(void *), void (*trampoline)(void), void *userdata); + + int signalTrigger(int sig); + + void signalReturn(); + + void storeProgramArgs(char *const argv[], char *const envp[]); + + kernel::fs::FileContext *getFileContext(int fd) const; + + int storeFileContext(kernel::fs::FileContext *f); + + int storeFileContext(kernel::fs::FileContext *f, int fd); + + int closeFileContext(int fd); + + private: + static pid_t nextPidVal; + + static const int MAX_SIGNAL = 64; + + pid_t pid, parent; + + Context ctx, *backupCtx; + + State state; + + kernel::memory::AddressSpace *addressSpace; + + SignalAction signalHandlers[MAX_SIGNAL]; + + binary_search_tree files; + }; + +} + +#endif \ No newline at end of file diff --git a/src/sched/queue.cpp b/src/sched/queue.cpp new file mode 100644 index 0000000..efb7020 --- /dev/null +++ b/src/sched/queue.cpp @@ -0,0 +1,104 @@ +#include "queue.h" +#include "process.h" + +node::node(kernel::sched::Process *value) : value(value), prev(nullptr), next(nullptr){}; + +queue::queue() +{ + queue_size = 0; + linked_list_front = linked_list_back = nullptr; + cur_Process = nullptr; +}; + +void queue::enqueue(kernel::sched::Process *process) +{ + node *new_node = new node(process); + if (empty()) + { + linked_list_front = new_node; + } + else + { + node *last_node = linked_list_back; + last_node->next = new_node; + new_node->prev = last_node; + } + linked_list_back = new_node; + queue_size++; +} + +kernel::sched::Process *queue::dequeue() +{ + if (queue_size == 0) + { + return nullptr; + } + node *return_node = linked_list_front; + linked_list_front = linked_list_front->next; + if (linked_list_back == return_node) + { + linked_list_back = nullptr; + } + kernel::sched::Process *p = return_node->value; + delete return_node; + queue_size--; + return p; +} + +kernel::sched::Process *queue::remove(pid_t pid) +{ + node *cur_node = linked_list_front; + kernel::sched::Process *p; + + while (cur_node->next) + { + if (cur_node->value->getPid() == pid) + { + cur_node->prev->next = cur_node->next; + cur_node->next->prev = cur_node->prev; + p = cur_node->value; + delete cur_node; + return p; + } + cur_node = cur_node->next; + } +} + +kernel::sched::Process *queue::peek() +{ + return linked_list_front->value; +} + +kernel::sched::Process *queue::sched_next() +{ + if (cur_Process != nullptr) + { + enqueue(cur_Process); + } + if (!empty()) + { + cur_Process = dequeue(); + return cur_Process; + } + return nullptr; +} + +int queue::size() const +{ + return queue_size; +} + +bool queue::empty() const +{ + return queue_size == 0 ? true : false; +} + +kernel::sched::Process *queue::get_cur_process() +{ + return cur_Process; +} + +void queue::set_cur_process(kernel::sched::Process *proc) +{ + cur_Process = proc; +} \ No newline at end of file diff --git a/src/sched/queue.h b/src/sched/queue.h new file mode 100644 index 0000000..7ce96dd --- /dev/null +++ b/src/sched/queue.h @@ -0,0 +1,37 @@ +#ifndef QUEUE_H +#define QUEUE_H + +#include "containers/linked_list.h" +#include "process.h" + +class node +{ +public: + node(kernel::sched::Process *value); + kernel::sched::Process *value; + node *prev; + node *next; +}; + +class queue +{ +public: + queue(); + void enqueue(kernel::sched::Process *process); + kernel::sched::Process *dequeue(); + kernel::sched::Process *remove(pid_t pid); + kernel::sched::Process *peek(); + kernel::sched::Process *sched_next(); + kernel::sched::Process *get_cur_process(); + void set_cur_process(kernel::sched::Process *proc); + bool empty() const; + int size() const; + +private: + int queue_size; + node *linked_list_front; + node *linked_list_back; + kernel::sched::Process *cur_Process; +}; + +#endif \ No newline at end of file diff --git a/src/sched/signalaction.h b/src/sched/signalaction.h new file mode 100644 index 0000000..59dacab --- /dev/null +++ b/src/sched/signalaction.h @@ -0,0 +1,19 @@ +#ifndef KERNEL_SIGNALACTION_H +#define KERNEL_SIGNALACTION_H + +#include "actiontype.h" + +namespace kernel::sched +{ + + struct SignalAction + { + ActionType type; + void (*handler)(void *); + void (*trampoline)(void); + void *userdata; + }; + +} + +#endif \ No newline at end of file diff --git a/src/sched/signaltype.h b/src/sched/signaltype.h new file mode 100644 index 0000000..6fcc774 --- /dev/null +++ b/src/sched/signaltype.h @@ -0,0 +1,33 @@ +#ifndef KERNEL_SIGNALTYPE_H +#define KERNEL_SIGNALTYPE_H + +namespace kernel::sched +{ + + enum class SignalType + { + Unknown = 0, + Hangup, + Interrupt, + Quit, + IllegalInstruction, + Trap, + Abort, + Bus, + FloatingPoint, + Kill, + User_1, + Segfault, + User_2, + Pipe, + Alarm, + Terminate, + StackFault, + Child, + Continue, + Stop + }; + +} + +#endif \ No newline at end of file diff --git a/src/util/aarch64/hacf.cpp b/src/util/aarch64/hacf.cpp new file mode 100644 index 0000000..26b245d --- /dev/null +++ b/src/util/aarch64/hacf.cpp @@ -0,0 +1,12 @@ +#include "../hacf.h" +#include + +extern "C" void hacf() +{ + uint64_t daif = 0xF << 6; + asm("msr daif, %0" ::"r"(daif)); + while (1) + { + asm("wfi"); + } +} \ No newline at end of file diff --git a/src/util/charstream.h b/src/util/charstream.h new file mode 100644 index 0000000..8feb0f7 --- /dev/null +++ b/src/util/charstream.h @@ -0,0 +1,46 @@ +#ifndef _CHARSTREAM_H +#define _CHARSTREAM_H + +#include "fs/filecontext.h" + +namespace kernel +{ + + /** + * @brief Abstract class representing an object that accepts character data + * for output. + */ + class CharStream + { + public: + enum class Mode + { + RO, + RW, + W + }; + + /** + * @brief Outputs a single character + * @param c The character to output + * @return A reference to this object + */ + virtual CharStream &operator<<(char c) = 0; + + /** + * @brief Outputs an entire null-terminated string. + * @param str The string to output + * @return A reference to this object + */ + virtual CharStream &operator<<(const char *str) = 0; + + virtual CharStream &operator>>(char &c) = 0; + + virtual kernel::fs::FileContext *open(Mode mode) = 0; + + virtual void close(int id) = 0; + }; + +} + +#endif \ No newline at end of file diff --git a/src/util/hacf.h b/src/util/hacf.h new file mode 100644 index 0000000..dcbc1f6 --- /dev/null +++ b/src/util/hacf.h @@ -0,0 +1,10 @@ +#ifndef _HACF_H +#define _HACF_H + +extern "C" +/** + * @brief Cease all meaningful execution. Implementation is platform-dependent. + */ +void hacf() __attribute__((noreturn)); + +#endif \ No newline at end of file diff --git a/src/util/hasrefcount.cpp b/src/util/hasrefcount.cpp new file mode 100644 index 0000000..8516175 --- /dev/null +++ b/src/util/hasrefcount.cpp @@ -0,0 +1,21 @@ +#include "hasrefcount.h" + +HasRefcount::HasRefcount() + : refcount(0) +{ +} + +int HasRefcount::getRefCount() const +{ + return refcount; +} + +void HasRefcount::addReference() +{ + refcount++; +} + +void HasRefcount::removeReference() +{ + refcount--; +} \ No newline at end of file diff --git a/src/util/hasrefcount.h b/src/util/hasrefcount.h new file mode 100644 index 0000000..4e8912a --- /dev/null +++ b/src/util/hasrefcount.h @@ -0,0 +1,19 @@ +#ifndef _KERNEL_HASREFCOUNT_H +#define _KERNEL_HASREFCOUNT_H + +class HasRefcount +{ +public: + HasRefcount(); + + virtual int getRefCount() const; + + virtual void addReference(); + + virtual void removeReference(); + +protected: + int refcount; +}; + +#endif \ No newline at end of file diff --git a/src/util/log.cpp b/src/util/log.cpp new file mode 100644 index 0000000..469ed14 --- /dev/null +++ b/src/util/log.cpp @@ -0,0 +1,183 @@ +#include "log.h" + +#include +#include + +using namespace kernel; + +enum format_flags_t +{ + FORMAT_PADDING = '0', + FORMAT_WIDTH = '*', + + FORMAT_SIGNED_DECIMAL = 'i', + FORMAT_UNSIGNED_DECIMAL = 'u', + FORMAT_UNSIGNED_OCTAL = 'o', + FORMAT_UNSIGNED_HEX = 'x', + FORMAT_STRING = 's', + FORMAT_CHARACTER = 'c', + FORMAT_COUNT = 'n', + FORMAT_PERCENT = '%' + +}; + +static const char *FG_BLUE = "\x1b[34m"; +static const char *FG_YELLOW = "\x1b[33m"; +static const char *FG_RED = "\x1b[31m"; +static const char *BOLD_RED = "\x1b[1;31m"; +static const char *FG_FAINT = "\x1b[1;2m"; +static const char *MODE_RESET = "\x1b[0m"; + +CharStream *stream = nullptr; + +static char *itoa(unsigned long n, unsigned int base, unsigned int width) +{ + if (base < 2 || base > 16) + { + return NULL; + } + static const char *digits = "0123456789abcdef"; + static char buffer[65]; + char *s = &buffer[64]; + *s = 0; + unsigned int count = 0; + do + { + *--s = digits[n % base]; + n /= base; + count++; + } while (count < width || n != 0); + return s; +} + +int logInit(CharStream *outStream) +{ + stream = outStream; + return 0; +} + +kernel::CharStream *getLogStream() +{ + return stream; +} + +int vprintf(const char *format, va_list valist) +{ + if (stream == nullptr) + { + return 0; + } + + while (*format) + { + if (*format == '%') + { + size_t width = 0; + switch (*++format) + { + case FORMAT_PADDING: + format++; + break; + } + while (*format >= '0' && *format <= '9') + { + width = (width * 10) + *format - '0'; + format++; + } + switch (*format) + { + case FORMAT_SIGNED_DECIMAL: + { + int n = va_arg(valist, int); + if (n < 0) + { + *stream << '-'; + n *= -1; + } + *stream << itoa((unsigned int)n, 10, width); + break; + } + case FORMAT_UNSIGNED_DECIMAL: + *stream << itoa(va_arg(valist, unsigned long), 10, width); + break; + case FORMAT_UNSIGNED_OCTAL: + *stream << itoa(va_arg(valist, unsigned long), 8, width); + break; + case FORMAT_UNSIGNED_HEX: + *stream << itoa(va_arg(valist, unsigned long), 16, width); + break; + case FORMAT_STRING: + *stream << va_arg(valist, const char *); + break; + case FORMAT_CHARACTER: + *stream << va_arg(valist, int); + break; + case FORMAT_PERCENT: + *stream << '%'; + break; + } + } + else + { + *stream << *format; + if (*format == '\n') + { + *stream << '\r'; + } + } + format++; + } +} + +int printf(const char *format, ...) +{ + if (stream == nullptr) + { + return 0; + } + + va_list valist; + va_start(valist, format); + vprintf(format, valist); + va_end(valist); + return 0; +} + +void kernelLog(LogLevel level, const char *fmt, ...) +{ + va_list valist; + va_start(valist, fmt); + switch (level) + { +#if !defined KERNEL_NODEBUG + case LogLevel::DEBUG: + printf("%s[Debug]: ", FG_FAINT); + vprintf(fmt, valist); + printf("%s\r\n", MODE_RESET); + break; +#endif + case LogLevel::INFO: + printf("[%sInfo%s]: ", FG_BLUE, MODE_RESET); + vprintf(fmt, valist); + printf("\r\n"); + break; + case LogLevel::WARNING: + printf("[%sWarning%s]: ", FG_YELLOW, MODE_RESET); + vprintf(fmt, valist); + printf("\r\n"); + break; + case LogLevel::ERROR: + printf("[%sError%s]: ", FG_RED, MODE_RESET); + vprintf(fmt, valist); + printf("\r\n"); + break; + case LogLevel::PANIC: + printf("[%sPANIC%s]: ", BOLD_RED, MODE_RESET); + vprintf(fmt, valist); + printf("\r\n"); + break; + default: + break; + } + va_end(valist); +} diff --git a/src/util/log.h b/src/util/log.h new file mode 100644 index 0000000..a56be67 --- /dev/null +++ b/src/util/log.h @@ -0,0 +1,50 @@ +#ifndef LOG_H +#define LOG_H + +#include "charstream.h" +#include + +enum class LogLevel +{ + DEBUG, + INFO, + WARNING, + ERROR, + PANIC +}; + +/** + * @brief Initialize the logger to output characters to `outStream`. + * @param outStream + * @return Zero upon success, nonzero upon failure. + */ +int logInit(kernel::CharStream *outStream); + +/** + * @return the stream being used for the kernel log + */ +kernel::CharStream *getLogStream(); + +/** + * @brief C-style printf function, accepting commonly used formatting flags. + * Outputs to the stream provided to `logInit`. Calls to this function before + * a call to `logInit` will result in no data being outputted. + * + * @param format + * @param + * @return zero + */ +int printf(const char *format, ...); + +/** + * @brief Logs a formatted message tagged with the log level. Appends a newline + * and carriage return after each message, so log messages need not include + * them. + * + * @param level The log level of the message + * @param fmt Format string of the log message + * @param + */ +void kernelLog(LogLevel level, const char *fmt, ...); + +#endif diff --git a/src/util/math.h b/src/util/math.h new file mode 100644 index 0000000..6309e4a --- /dev/null +++ b/src/util/math.h @@ -0,0 +1,84 @@ +/** + * Author: Nathan Giddings + * + * Source: https://stackoverflow.com/questions/11376288/fast-computing-of-log2-for-64-bit-integers + */ +#ifndef MATH_H +#define MATH_H + +/** + * @brief Quickly compute log2 of an int, rounding up. + * @param x + * @return ceil(log2(x)) + */ +static inline int ilog2(unsigned int x) +{ +#if defined __GNUC__ + if(x <= 1) + return 0; + return 32 - __builtin_clz(x - 1); +#else + static const int table[32] = { + 0, 9, 1, 10, 13, 21, 2, 29, + 11, 14, 16, 18, 22, 25, 3, 30, + 8, 12, 20, 28, 15, 17, 24, 7, + 19, 27, 23, 6, 26, 5, 4, 31}; + + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + + return table[(x * 0x07C4ACDD) >> 27]; +#endif +} + +/** + * @brief Quickly compute log2 of a long, rounding up. + * @param x + * @return ceil(log2(x)) + */ +static inline int llog2(unsigned long x) +{ +#if (defined __GNUC__) && (__SIZEOF_LONG__ == 4) + if(x <= 1) + return 0; + return 32 - __builtin_clzl(x - 1); +#elif (defined __GNUC__) && (__SIZEOF_LONG__ == 8) + if(x <= 1) + return 0; + return 64 - __builtin_clzl(x - 1); +#elif __SIZEOF_LONG__ == 8 + static const int table[64] = { + 0, 58, 1, 59, 47, 53, 2, 60, 39, 48, 27, 54, 33, 42, 3, 61, + 51, 37, 40, 49, 18, 28, 20, 55, 30, 34, 11, 43, 14, 22, 4, 62, + 57, 46, 52, 38, 26, 32, 41, 50, 36, 17, 19, 29, 10, 13, 21, 56, + 45, 25, 31, 35, 16, 9, 12, 44, 24, 15, 8, 23, 7, 6, 5, 63}; + + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + x |= x >> 32; + + return table[(x * 0x03f6eaf2cd271461) >> 58]; +#else + static const int table[32] = { + 0, 9, 1, 10, 13, 21, 2, 29, + 11, 14, 16, 18, 22, 25, 3, 30, + 8, 12, 20, 28, 15, 17, 24, 7, + 19, 27, 23, 6, 26, 5, 4, 31}; + + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + + return table[(x * 0x07C4ACDD) >> 27]; +#endif +} + +#endif \ No newline at end of file diff --git a/src/util/string.cpp b/src/util/string.cpp new file mode 100644 index 0000000..18e0cbb --- /dev/null +++ b/src/util/string.cpp @@ -0,0 +1,49 @@ +#include "string.h" + +extern "C" void *memcpy(void *dest, const void *src, size_t count) +{ + char *d = (char *)dest; + const char *s = (const char *)src; + for (int i = 0; i < count; i++) + { + d[i] = s[i]; + } + return dest; +} + +void memset(char *s, int size, int v) +{ + for (int i = 0; i < size; i++) + { + s[i] = (char)v; + } +} + +int strlen(const char *s) +{ + int c = 0; + if (s == nullptr) + { + return 0; + } + while (s[c] != '\0') + { + c++; + } + return c; +} + +void strcpy(char *dest, const char *src) +{ + if (dest == nullptr || src == nullptr) + { + return; + } + while (*src != '\0') + { + *dest = *src; + dest++; + src++; + } + *dest = '\0'; +} diff --git a/src/util/string.h b/src/util/string.h new file mode 100644 index 0000000..d35a92f --- /dev/null +++ b/src/util/string.h @@ -0,0 +1,22 @@ +#ifndef KERNEL_STRING_H +#define KERNEL_STRING_H + +#include + +/** + * @brief + * @see https://en.cppreference.com/w/c/string/byte/memcpy + * @param dest + * @param src + * @param count + * @return + */ +extern "C" void *memcpy(void *dest, const void *src, size_t count); + +extern "C" void memset(char *s, int size, int v); + +extern "C" int strlen(const char *s); + +extern "C" void strcpy(char *dest, const char *src); + +#endif \ No newline at end of file diff --git a/test/entry.s b/test/entry.s new file mode 100644 index 0000000..9dc7ef8 --- /dev/null +++ b/test/entry.s @@ -0,0 +1,6 @@ +.section ".text" + +.global _start +_start: + bl main + b _start diff --git a/test/linker.ld b/test/linker.ld new file mode 100644 index 0000000..da1ba58 --- /dev/null +++ b/test/linker.ld @@ -0,0 +1,43 @@ +ENTRY(_start) + +SECTIONS +{ + . = 0x200000; + + __begin = .; + + __text_start = .; + .text : + { + *(.text) + } + . = ALIGN(4096); + __text_end = .; + + __rodata_start = .; + .rodata : + { + *(.rodata) + } + . = ALIGN(4096); + __rodata_end = .; + + __data_start = .; + .data : + { + *(.data) + } + . = ALIGN(4096); + __data_end = .; + + __bss_start = .; + .bss : + { + bss = .; + *(.bss) + } + . = ALIGN(4096); + __bss_end = .; + __bss_size = __bss_end - __bss_start; + __end = .; +} \ No newline at end of file diff --git a/test/main.c b/test/main.c new file mode 100644 index 0000000..d072c00 --- /dev/null +++ b/test/main.c @@ -0,0 +1,32 @@ +#include +#include "sys/syscall.h" + +void trampoline() +{ + sigret(); +} + +void handler(void *userdata) +{ + printk("Handler called.\n"); +} + +void thread(void *data) +{ + printk("Thread 2\n"); + terminate(); +} + +int main(int argc, char **argv, char **envp) +{ + printk("Process start: "); + printk(argv[0]); + printk("\n"); + mmap((void *)0, 0x10000, 1); + sigaction(17, handler, trampoline, (void *)0); + while (1) + { + clone(thread, (void *)0x10000, 0, 0); + sigwait(); + } +}