From 2c23d3538507b9d742717f89a00c0e561dffd3ca Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Thu, 1 Jun 2023 14:30:52 +0800 Subject: [PATCH 01/34] Lab 5 C: Import kernel from lab 4 --- lab5/c/.clang-format | 1 + lab5/c/.gitignore | 1 + lab5/c/Makefile | 93 +++ lab5/c/include/oscos/console.h | 91 +++ lab5/c/include/oscos/devicetree.h | 173 +++++ lab5/c/include/oscos/drivers/aux.h | 8 + lab5/c/include/oscos/drivers/board.h | 17 + lab5/c/include/oscos/drivers/gpio.h | 8 + lab5/c/include/oscos/drivers/l1ic.h | 24 + lab5/c/include/oscos/drivers/l2ic.h | 14 + lab5/c/include/oscos/drivers/mailbox.h | 19 + lab5/c/include/oscos/drivers/mini-uart.h | 14 + lab5/c/include/oscos/drivers/pm.h | 14 + lab5/c/include/oscos/initrd.h | 117 ++++ lab5/c/include/oscos/libc/ctype.h | 6 + lab5/c/include/oscos/libc/inttypes.h | 9 + lab5/c/include/oscos/libc/stdio.h | 12 + lab5/c/include/oscos/libc/stdlib.h | 11 + lab5/c/include/oscos/libc/string.h | 25 + lab5/c/include/oscos/mem/malloc.h | 30 + lab5/c/include/oscos/mem/memmap.h | 99 +++ lab5/c/include/oscos/mem/page-alloc.h | 61 ++ lab5/c/include/oscos/mem/startup-alloc.h | 34 + lab5/c/include/oscos/mem/types.h | 37 + lab5/c/include/oscos/mem/vm.h | 18 + lab5/c/include/oscos/panic.h | 23 + lab5/c/include/oscos/shell.h | 6 + lab5/c/include/oscos/timer/delay.h | 11 + lab5/c/include/oscos/timer/timeout.h | 13 + lab5/c/include/oscos/user-program.h | 22 + lab5/c/include/oscos/utils/align.h | 13 + lab5/c/include/oscos/utils/control-flow.h | 11 + lab5/c/include/oscos/utils/core-id.h | 8 + lab5/c/include/oscos/utils/critical-section.h | 20 + lab5/c/include/oscos/utils/endian.h | 21 + lab5/c/include/oscos/utils/fmt.h | 15 + lab5/c/include/oscos/utils/heapq.h | 14 + lab5/c/include/oscos/utils/math.h | 13 + lab5/c/include/oscos/utils/save-ctx.S | 25 + lab5/c/include/oscos/utils/suspend.h | 19 + lab5/c/include/oscos/utils/time.h | 6 + lab5/c/include/oscos/xcpt.h | 9 + lab5/c/include/oscos/xcpt/task-queue.h | 8 + lab5/c/src/console.c | 243 +++++++ lab5/c/src/devicetree.c | 129 ++++ lab5/c/src/drivers/aux.c | 28 + lab5/c/src/drivers/gpio.c | 105 +++ lab5/c/src/drivers/l1ic.c | 37 + lab5/c/src/drivers/l2ic.c | 36 + lab5/c/src/drivers/mailbox.c | 83 +++ lab5/c/src/drivers/mini-uart.c | 130 ++++ lab5/c/src/drivers/pm.c | 85 +++ lab5/c/src/initrd.c | 130 ++++ lab5/c/src/libc/ctype.c | 3 + lab5/c/src/libc/stdio.c | 41 ++ lab5/c/src/libc/stdlib/qsort.c | 154 +++++ lab5/c/src/libc/string.c | 99 +++ lab5/c/src/linker.ld | 77 +++ lab5/c/src/main.c | 58 ++ lab5/c/src/mem/malloc.c | 326 +++++++++ lab5/c/src/mem/memmap.c | 316 +++++++++ lab5/c/src/mem/page-alloc.c | 416 +++++++++++ lab5/c/src/mem/startup-alloc.c | 24 + lab5/c/src/mem/vm.c | 12 + lab5/c/src/panic.c | 22 + lab5/c/src/shell.c | 332 +++++++++ lab5/c/src/start.S | 65 ++ lab5/c/src/timer/delay.c | 15 + lab5/c/src/timer/timeout.c | 108 +++ lab5/c/src/user-program.c | 82 +++ lab5/c/src/utils/core-id.c | 9 + lab5/c/src/utils/fmt.c | 653 ++++++++++++++++++ lab5/c/src/utils/heapq.c | 77 +++ lab5/c/src/xcpt/default-handler.c | 21 + lab5/c/src/xcpt/irq-handler.c | 43 ++ lab5/c/src/xcpt/task-queue.c | 97 +++ lab5/c/src/xcpt/vector-table.S | 98 +++ lab5/c/src/xcpt/xcpt.c | 7 + lab5/tests/.gitignore | 2 + lab5/tests/build-initrd.sh | 19 + lab5/tests/get-dtb.sh | 5 + lab5/tests/user-program/xcpt_test/.gitignore | 1 + lab5/tests/user-program/xcpt_test/Makefile | 73 ++ .../user-program/xcpt_test/src/linker.ld | 80 +++ lab5/tests/user-program/xcpt_test/src/start.S | 11 + 85 files changed, 5545 insertions(+) create mode 100644 lab5/c/.clang-format create mode 100644 lab5/c/.gitignore create mode 100644 lab5/c/Makefile create mode 100644 lab5/c/include/oscos/console.h create mode 100644 lab5/c/include/oscos/devicetree.h create mode 100644 lab5/c/include/oscos/drivers/aux.h create mode 100644 lab5/c/include/oscos/drivers/board.h create mode 100644 lab5/c/include/oscos/drivers/gpio.h create mode 100644 lab5/c/include/oscos/drivers/l1ic.h create mode 100644 lab5/c/include/oscos/drivers/l2ic.h create mode 100644 lab5/c/include/oscos/drivers/mailbox.h create mode 100644 lab5/c/include/oscos/drivers/mini-uart.h create mode 100644 lab5/c/include/oscos/drivers/pm.h create mode 100644 lab5/c/include/oscos/initrd.h create mode 100644 lab5/c/include/oscos/libc/ctype.h create mode 100644 lab5/c/include/oscos/libc/inttypes.h create mode 100644 lab5/c/include/oscos/libc/stdio.h create mode 100644 lab5/c/include/oscos/libc/stdlib.h create mode 100644 lab5/c/include/oscos/libc/string.h create mode 100644 lab5/c/include/oscos/mem/malloc.h create mode 100644 lab5/c/include/oscos/mem/memmap.h create mode 100644 lab5/c/include/oscos/mem/page-alloc.h create mode 100644 lab5/c/include/oscos/mem/startup-alloc.h create mode 100644 lab5/c/include/oscos/mem/types.h create mode 100644 lab5/c/include/oscos/mem/vm.h create mode 100644 lab5/c/include/oscos/panic.h create mode 100644 lab5/c/include/oscos/shell.h create mode 100644 lab5/c/include/oscos/timer/delay.h create mode 100644 lab5/c/include/oscos/timer/timeout.h create mode 100644 lab5/c/include/oscos/user-program.h create mode 100644 lab5/c/include/oscos/utils/align.h create mode 100644 lab5/c/include/oscos/utils/control-flow.h create mode 100644 lab5/c/include/oscos/utils/core-id.h create mode 100644 lab5/c/include/oscos/utils/critical-section.h create mode 100644 lab5/c/include/oscos/utils/endian.h create mode 100644 lab5/c/include/oscos/utils/fmt.h create mode 100644 lab5/c/include/oscos/utils/heapq.h create mode 100644 lab5/c/include/oscos/utils/math.h create mode 100644 lab5/c/include/oscos/utils/save-ctx.S create mode 100644 lab5/c/include/oscos/utils/suspend.h create mode 100644 lab5/c/include/oscos/utils/time.h create mode 100644 lab5/c/include/oscos/xcpt.h create mode 100644 lab5/c/include/oscos/xcpt/task-queue.h create mode 100644 lab5/c/src/console.c create mode 100644 lab5/c/src/devicetree.c create mode 100644 lab5/c/src/drivers/aux.c create mode 100644 lab5/c/src/drivers/gpio.c create mode 100644 lab5/c/src/drivers/l1ic.c create mode 100644 lab5/c/src/drivers/l2ic.c create mode 100644 lab5/c/src/drivers/mailbox.c create mode 100644 lab5/c/src/drivers/mini-uart.c create mode 100644 lab5/c/src/drivers/pm.c create mode 100644 lab5/c/src/initrd.c create mode 100644 lab5/c/src/libc/ctype.c create mode 100644 lab5/c/src/libc/stdio.c create mode 100644 lab5/c/src/libc/stdlib/qsort.c create mode 100644 lab5/c/src/libc/string.c create mode 100644 lab5/c/src/linker.ld create mode 100644 lab5/c/src/main.c create mode 100644 lab5/c/src/mem/malloc.c create mode 100644 lab5/c/src/mem/memmap.c create mode 100644 lab5/c/src/mem/page-alloc.c create mode 100644 lab5/c/src/mem/startup-alloc.c create mode 100644 lab5/c/src/mem/vm.c create mode 100644 lab5/c/src/panic.c create mode 100644 lab5/c/src/shell.c create mode 100644 lab5/c/src/start.S create mode 100644 lab5/c/src/timer/delay.c create mode 100644 lab5/c/src/timer/timeout.c create mode 100644 lab5/c/src/user-program.c create mode 100644 lab5/c/src/utils/core-id.c create mode 100644 lab5/c/src/utils/fmt.c create mode 100644 lab5/c/src/utils/heapq.c create mode 100644 lab5/c/src/xcpt/default-handler.c create mode 100644 lab5/c/src/xcpt/irq-handler.c create mode 100644 lab5/c/src/xcpt/task-queue.c create mode 100644 lab5/c/src/xcpt/vector-table.S create mode 100644 lab5/c/src/xcpt/xcpt.c create mode 100644 lab5/tests/.gitignore create mode 100755 lab5/tests/build-initrd.sh create mode 100755 lab5/tests/get-dtb.sh create mode 100644 lab5/tests/user-program/xcpt_test/.gitignore create mode 100644 lab5/tests/user-program/xcpt_test/Makefile create mode 100644 lab5/tests/user-program/xcpt_test/src/linker.ld create mode 100644 lab5/tests/user-program/xcpt_test/src/start.S diff --git a/lab5/c/.clang-format b/lab5/c/.clang-format new file mode 100644 index 000000000..9b3aa8b72 --- /dev/null +++ b/lab5/c/.clang-format @@ -0,0 +1 @@ +BasedOnStyle: LLVM diff --git a/lab5/c/.gitignore b/lab5/c/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/lab5/c/.gitignore @@ -0,0 +1 @@ +/build diff --git a/lab5/c/Makefile b/lab5/c/Makefile new file mode 100644 index 000000000..7067a6ee9 --- /dev/null +++ b/lab5/c/Makefile @@ -0,0 +1,93 @@ +DEFAULT_PROFILE = DEBUG +SRC_DIR = src +BUILD_DIR = build + +AS = aarch64-linux-gnu-as +CC = aarch64-linux-gnu-gcc +CPP = aarch64-linux-gnu-cpp +OBJCOPY = aarch64-linux-gnu-objcopy + +CPPFLAGS_BASE = -Iinclude -DPAGE_ALLOC_ENABLE_LOG + +ASFLAGS_DEBUG = -g + +CFLAGS_BASE = -std=c17 -pedantic-errors -Wall -Wextra -ffreestanding \ + -mcpu=cortex-a53+nofp+nosimd -mstrict-align +CFLAGS_DEBUG = -g +CFLAGS_RELEASE = -O3 -flto + +LDFLAGS_BASE = -nostdlib -Xlinker --build-id=none +LDFLAGS_RELEASE = -flto -Xlinker --gc-sections + +LDLIBS_BASE = -lgcc + +OBJS = start main console devicetree initrd panic shell user-program \ + drivers/aux drivers/gpio drivers/l1ic drivers/l2ic drivers/mailbox \ + drivers/mini-uart drivers/pm \ + mem/malloc mem/memmap mem/page-alloc mem/startup-alloc mem/vm \ + timer/delay timer/timeout \ + xcpt/default-handler xcpt/irq-handler xcpt/task-queue \ + xcpt/vector-table xcpt/xcpt \ + libc/ctype libc/stdio libc/stdlib/qsort libc/string \ + utils/core-id utils/fmt utils/heapq +LD_SCRIPT = $(SRC_DIR)/linker.ld + +# ------------------------------------------------------------------------------ + +PROFILE = $(DEFAULT_PROFILE) +OUT_DIR = $(BUILD_DIR)/$(PROFILE) + +CPPFLAGS = $(CPPFLAGS_BASE) $(CPPFLAGS_$(PROFILE)) +ASFLAGS = $(ASFLAGS_BASE) $(ASFLAGS_$(PROFILE)) +CFLAGS = $(CFLAGS_BASE) $(CFLAGS_$(PROFILE)) +LDFLAGS = $(LDFLAGS_BASE) $(LDFLAGS_$(PROFILE)) +LDLIBS = $(LDLIBS_BASE) $(LDLIBS_$(PROFILE)) + +OBJ_PATHS = $(addprefix $(OUT_DIR)/,$(addsuffix .o,$(OBJS))) + +# ------------------------------------------------------------------------------ + +.PHONY: all qemu qemu-debug gdb clean-profile clean + +all: $(OUT_DIR)/kernel8.img + +$(OUT_DIR)/kernel8.img: $(OUT_DIR)/kernel8.elf + @mkdir -p $(@D) + $(OBJCOPY) -O binary $^ $@ + +$(OUT_DIR)/kernel8.elf: $(OBJ_PATHS) $(LD_SCRIPT) + @mkdir -p $(@D) + $(CC) -T $(LD_SCRIPT) $(LDFLAGS) $(filter-out $(LD_SCRIPT),$^) $(LDLIBS) -o $@ + +$(OUT_DIR)/%.o: $(SRC_DIR)/%.c + @mkdir -p $(@D) + $(CC) $(CPPFLAGS) $(CFLAGS) -c -MMD -MP -MF $(OUT_DIR)/$*.d $< -o $@ + +$(OUT_DIR)/%.o: $(OUT_DIR)/%.s + @mkdir -p $(@D) + $(AS) $(ASFLAGS) $^ -o $@ + +$(OUT_DIR)/%.s: $(SRC_DIR)/%.S + @mkdir -p $(@D) + $(CPP) $(CPPFLAGS) $^ -o $@ + +qemu: $(OUT_DIR)/kernel8.img + qemu-system-aarch64 -M raspi3b -kernel $< -initrd ../tests/initramfs.cpio \ + -dtb ../tests/bcm2710-rpi-3-b-plus.dtb -display none -serial null \ + -serial stdio + +qemu-debug: $(OUT_DIR)/kernel8.img + qemu-system-aarch64 -M raspi3b -kernel $< -initrd ../tests/initramfs.cpio \ + -dtb ../tests/bcm2710-rpi-3-b-plus.dtb -display none -serial null \ + -serial stdio -S -s + +gdb: $(OUT_DIR)/kernel8.elf + gdb -s $< -ex 'target remote :1234' + +clean-profile: + $(RM) -r $(OUT_DIR) + +clean: + $(RM) -r $(BUILD_DIR) + +-include $(OUT_DIR)/*.d diff --git a/lab5/c/include/oscos/console.h b/lab5/c/include/oscos/console.h new file mode 100644 index 000000000..dd8c26558 --- /dev/null +++ b/lab5/c/include/oscos/console.h @@ -0,0 +1,91 @@ +/// \file include/oscos/console.h +/// \brief Serial console. +/// +/// The serial console reads and writes data to the mini UART. +/// +/// Before using the serial console, it must be initialized by calling +/// console_init(void) exactly once. Initializing the serial console twice or +/// operating on the serial console before initialization are not checked and +/// may have unintended consequences. +/// +/// The serial console implements two modes: text mode and binary mode. In text +/// mode, automatic newline translation is performed. A "\r" character received +/// by the mini UART will be read as "\n", and writing "\n" to the serial +/// console will send "\r\n" down the mini UART. In binary mode, automatic +/// newline translation is not performed. Any byte received by the mini UART +/// will be read as-is, and every character written to the serial console will +/// also be sent as-is. Upon initialization, the mode is set to text mode. + +#ifndef OSCOS_CONSOLE_H +#define OSCOS_CONSOLE_H + +#include +#include + +/// \brief The mode of the serial console. +typedef enum { + CM_TEXT, ///< Text mode. Performs newline translation. + CM_BINARY ///< Binary mode. Every byte are sent/received as-is. +} console_mode_t; + +/// \brief Initializes the serial console. +/// +/// See the file-level documentation for requirements on initialization. +void console_init(void); + +/// \brief Sets the mode of the serial console. +/// +/// \param mode The mode. Must be `CM_TEXT` or `CM_BINARY`. +void console_set_mode(console_mode_t mode); + +/// \brief Reads a character from the serial console. +/// +/// When calling this function, the serial console must be initialized. +unsigned char console_getc(void); + +/// \brief Writes a character to the serial console. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return \p c +unsigned char console_putc(unsigned char c); + +/// \brief Writes characters to the serial console. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return \p count +size_t console_write(const void *buf, size_t count); + +/// \brief Writes a string to the serial console without trailing newline. +/// +/// When calling this function, the serial console must be initialized. +void console_fputs(const char *s); + +/// \brief Writes a string to the serial console with trailing newline. +/// +/// When calling this function, the serial console must be initialized. +void console_puts(const char *s); + +/// \brief Performs string formatting and writes the result to the serial +/// console. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return The number of characters written. +int console_vprintf(const char *restrict format, va_list ap) + __attribute__((format(printf, 1, 0))); + +/// \brief Performs string formatting and writes the result to the serial +/// console. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return The number of characters written. +int console_printf(const char *restrict format, ...) + __attribute__((format(printf, 1, 2))); + +/// \brief Interrupt handler. Not meant to be called directly. +void mini_uart_interrupt_handler(void); + +#endif diff --git a/lab5/c/include/oscos/devicetree.h b/lab5/c/include/oscos/devicetree.h new file mode 100644 index 000000000..b4fe42380 --- /dev/null +++ b/lab5/c/include/oscos/devicetree.h @@ -0,0 +1,173 @@ +#ifndef OSCOS_DEVICETREE_H +#define OSCOS_DEVICETREE_H + +#include +#include + +#include "oscos/libc/string.h" +#include "oscos/utils/align.h" +#include "oscos/utils/control-flow.h" +#include "oscos/utils/endian.h" + +/// \brief Flattened devicetree header. +typedef struct { + uint32_t magic; + uint32_t totalsize; + uint32_t off_dt_struct; + uint32_t off_dt_strings; + uint32_t off_mem_rsvmap; + uint32_t version; + uint32_t last_comp_version; + uint32_t boot_cpuid_phys; + uint32_t size_dt_strings; + uint32_t size_dt_struct; +} fdt_header_t; + +/// \brief An entry of the memory reservation block. +typedef struct { + uint64_t address; + uint64_t size; +} fdt_reserve_entry_t; + +/// \brief An item in a node in a flattened devicetree blob. +typedef struct { + uint32_t token; + char payload[]; +} fdt_item_t; + +/// \brief Flattened devicetree property; whatever follows the FDT_PROP token. +typedef struct { + uint32_t len; + uint32_t nameoff; + char value[]; +} fdt_prop_t; + +#define FDT_BEGIN_NODE ((uint32_t)0x00000001) +#define FDT_END_NODE ((uint32_t)0x00000002) +#define FDT_PROP ((uint32_t)0x00000003) +#define FDT_NOP ((uint32_t)0x00000004) +#define FDT_END ((uint32_t)0x00000009) + +/// \brief Initializes the device tree. +/// \param dtb_start The loading address of the devicetree blob. +/// \return Whether or not the initialization succeeds. +bool devicetree_init(const void *dtb_start); + +/// \brief Returns whether or not the devicetree has been successfully +/// initialized. +bool devicetree_is_init(void); + +/// \brief The start of the memory reservation block of the devicetree blob. +#define FDT_START_MEM_RSVMAP \ + ((const fdt_reserve_entry_t \ + *)(fdt_get_start() + \ + rev_u32(((const fdt_header_t *)fdt_get_start())->off_mem_rsvmap))) + +/// \brief The start of the strings block of the devicetree blob. +#define FDT_START_STRINGS \ + (fdt_get_start() + \ + rev_u32(((const fdt_header_t *)fdt_get_start())->off_dt_strings)) + +/// \brief The starting token of an item in the structure block. +#define FDT_TOKEN(ITEM) (rev_u32((ITEM)->token)) + +/// \brief The name of an node in the structure block. +#define FDT_NODE_NAME(NODE) ((NODE)->payload) + +/// \brief The name of a property in the structure block. +#define FDT_PROP_NAME(PROP) (FDT_START_STRINGS + rev_u32((PROP)->nameoff)) + +/// \brief The value of a property in the structure block. +#define FDT_PROP_VALUE(PROP) ((PROP)->value) + +/// \brief The length of the of a property in the structure block. +#define FDT_PROP_VALUE_LEN(PROP) (rev_u32((PROP)->len)) + +#define FDT_ITEMS_START(NODE) \ + ((const fdt_item_t *)ALIGN( \ + (uintptr_t)((NODE)->payload) + strlen(FDT_NODE_NAME(NODE)) + 1, 4)) +#define FDT_ITEM_IS_END(ITEM) (FDT_TOKEN(ITEM) == FDT_END_NODE) + +/// \brief Expands to a for loop that loops over each item in the given node. +/// +/// \param NODE The pointer to the node. +/// \param ITEM_NAME The name of the variable for the item. +#define FDT_FOR_ITEM(NODE, ITEM_NAME) \ + for (const fdt_item_t *ITEM_NAME = FDT_ITEMS_START(NODE); \ + !FDT_ITEM_IS_END(ITEM_NAME); ITEM_NAME = fdt_next_item(ITEM_NAME)) + +/// \brief Expands to a for loop that loops over each item in the given node. +/// +/// This is a variant of FDT_FOR_ITEM that does not declare the variable within +/// the for loop. This is useful, e. g., if one wants to obtain the pointer to +/// the FDT_END_NODE token. +/// +/// \param NODE The pointer to the node. +/// \param ITEM_NAME The name of the variable for the item. Must be a declared +/// variable. +#define FDT_FOR_ITEM_(NODE, ITEM_NAME) \ + for (ITEM_NAME = FDT_ITEMS_START(NODE); !FDT_ITEM_IS_END(ITEM_NAME); \ + ITEM_NAME = fdt_next_item(ITEM_NAME)) + +/// \brief Gets the starting address of the devicetree blob. +const char *fdt_get_start(void); + +/// \brief Gets the ending address of the devicetree blob. +const char *fdt_get_end(void); + +/// \brief Gets the pointer to the next item of the given item in the same node. +/// \param item The item. Must not point to the FDT_END_NODE token. +const fdt_item_t *fdt_next_item(const fdt_item_t *item); + +/// \brief A node of the parent node linked list. +typedef struct fdt_traverse_parent_list_node_t { + const fdt_item_t *node; + const struct fdt_traverse_parent_list_node_t *parent; +} fdt_traverse_parent_list_node_t; + +/// \brief Callback for void fdt_traverse(fdt_traverse_callback_t *, void *). +typedef control_flow_t +fdt_traverse_callback_t(void *, const fdt_item_t *, + const fdt_traverse_parent_list_node_t *); + +/// \brief Performs in-order traversal of the devicetree. +/// +/// When the traversal process encounters a node, \p callback is called with +/// \p arg, the pointer to the current node, and the linked list of the parent +/// nodes of the current node. +/// +/// \param callback The callback that is called on each node. +/// \param arg The first argument that will be passed to \p callback. +void fdt_traverse(fdt_traverse_callback_t *callback, void *arg); + +/// \brief The #address-cells and the #size-cells properties. +typedef struct { + uint32_t n_address_cells, n_size_cells; +} fdt_n_address_size_cells_t; + +/// \brief Gets the #address-cells and the #size-cells properties of a node. +/// +/// \param node The pointer to the node. +/// \return The value of the #address-cells and the #size-cells properties, or +/// their respective defaults if these properties are missing. +fdt_n_address_size_cells_t fdt_get_n_address_size_cells(const fdt_item_t *node); + +/// \brief The reg property. +typedef struct { + uintmax_t address, size; +} fdt_reg_t; + +typedef struct { + fdt_reg_t value; + bool address_overflow, size_overflow; +} fdt_read_reg_result_t; + +/// \brief Reads the reg property. +/// +/// \param prop The pointer to the reg property. +/// \param n_cells The #address-cells and the #size-cells properties of the +/// parent node. +fdt_read_reg_result_t fdt_read_reg(const fdt_prop_t *prop, + fdt_n_address_size_cells_t n_cells); + +#endif diff --git a/lab5/c/include/oscos/drivers/aux.h b/lab5/c/include/oscos/drivers/aux.h new file mode 100644 index 000000000..e04db4bd1 --- /dev/null +++ b/lab5/c/include/oscos/drivers/aux.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_DRIVERS_AUX_H +#define OSCOS_DRIVERS_AUX_H + +void aux_init(void); + +void aux_enable_mini_uart(void); + +#endif diff --git a/lab5/c/include/oscos/drivers/board.h b/lab5/c/include/oscos/drivers/board.h new file mode 100644 index 000000000..a91f11e1c --- /dev/null +++ b/lab5/c/include/oscos/drivers/board.h @@ -0,0 +1,17 @@ +#ifndef OSCOS_DRIVERS_BOARD_H +#define OSCOS_DRIVERS_BOARD_H + +// ARM physical address. See +// https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#peripheral-addresses +#define PERIPHERAL_BASE ((void *)0x3f000000) + +#define ARM_LOCAL_PERIPHERAL_BASE ((void *)0x40000000) + +// ? Is the shareability domain for the peripheral memory barriers correct? + +#define PERIPHERAL_READ_BARRIER() \ + __asm__ __volatile__("dsb nshld" : : : "memory") +#define PERIPHERAL_WRITE_BARRIER() \ + __asm__ __volatile__("dsb nshst" : : : "memory") + +#endif diff --git a/lab5/c/include/oscos/drivers/gpio.h b/lab5/c/include/oscos/drivers/gpio.h new file mode 100644 index 000000000..6994b22d6 --- /dev/null +++ b/lab5/c/include/oscos/drivers/gpio.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_DRIVERS_GPIO_H +#define OSCOS_DRIVERS_GPIO_H + +void gpio_init(void); + +void gpio_setup_uart0_gpio14(void); + +#endif diff --git a/lab5/c/include/oscos/drivers/l1ic.h b/lab5/c/include/oscos/drivers/l1ic.h new file mode 100644 index 000000000..a9f33889c --- /dev/null +++ b/lab5/c/include/oscos/drivers/l1ic.h @@ -0,0 +1,24 @@ +#ifndef OSCOS_DRIVERS_L1IC_H +#define OSCOS_DRIVERS_L1IC_H + +#include +#include + +#define INT_L1_SRC_TIMER0 ((uint32_t)(1 << 0)) +#define INT_L1_SRC_TIMER1 ((uint32_t)(1 << 1)) +#define INT_L1_SRC_TIMER2 ((uint32_t)(1 << 2)) +#define INT_L1_SRC_TIMER3 ((uint32_t)(1 << 3)) +#define INT_L1_SRC_MBOX0 ((uint32_t)(1 << 4)) +#define INT_L1_SRC_MBOX1 ((uint32_t)(1 << 5)) +#define INT_L1_SRC_MBOX2 ((uint32_t)(1 << 6)) +#define INT_L1_SRC_MBOX3 ((uint32_t)(1 << 7)) +#define INT_L1_SRC_GPU ((uint32_t)(1 << 8)) +#define INT_L1_SRC_PMU ((uint32_t)(1 << 9)) + +void l1ic_init(void); + +uint32_t l1ic_get_int_src(size_t core_id); + +void l1ic_enable_core_timer_irq(size_t core_id); + +#endif diff --git a/lab5/c/include/oscos/drivers/l2ic.h b/lab5/c/include/oscos/drivers/l2ic.h new file mode 100644 index 000000000..c11a71b50 --- /dev/null +++ b/lab5/c/include/oscos/drivers/l2ic.h @@ -0,0 +1,14 @@ +#ifndef OSCOS_DRIVERS_L2IC_H +#define OSCOS_DRIVERS_L2IC_H + +#include + +#define INT_L2_IRQ_0_SRC_AUX ((uint32_t)(1 << 29)) + +void l2ic_init(void); + +uint32_t l2ic_get_pending_irq_0(void); + +void l2ic_enable_irq_0(uint32_t mask); + +#endif diff --git a/lab5/c/include/oscos/drivers/mailbox.h b/lab5/c/include/oscos/drivers/mailbox.h new file mode 100644 index 000000000..57fa08254 --- /dev/null +++ b/lab5/c/include/oscos/drivers/mailbox.h @@ -0,0 +1,19 @@ +#ifndef OSCOS_DRIVERS_MAILBOX_H +#define OSCOS_DRIVERS_MAILBOX_H + +#include + +#define MAILBOX_CHANNEL_PROPERTY_TAGS_ARM_TO_VC ((unsigned char)8) + +typedef struct { + uint32_t base, size; +} arm_memory_t; + +void mailbox_init(void); + +void mailbox_call(uint32_t message[], unsigned char channel); + +uint32_t mailbox_get_board_revision(void); +arm_memory_t mailbox_get_arm_memory(void); + +#endif diff --git a/lab5/c/include/oscos/drivers/mini-uart.h b/lab5/c/include/oscos/drivers/mini-uart.h new file mode 100644 index 000000000..d2af57e07 --- /dev/null +++ b/lab5/c/include/oscos/drivers/mini-uart.h @@ -0,0 +1,14 @@ +#ifndef OSCOS_DRIVERS_MINI_UART_H +#define OSCOS_DRIVERS_MINI_UART_H + +void mini_uart_init(void); + +int mini_uart_recv_byte_nonblock(void); +int mini_uart_send_byte_nonblock(unsigned char b); + +void mini_uart_enable_rx_interrupt(void); +void mini_uart_disable_rx_interrupt(void); +void mini_uart_enable_tx_interrupt(void); +void mini_uart_disable_tx_interrupt(void); + +#endif diff --git a/lab5/c/include/oscos/drivers/pm.h b/lab5/c/include/oscos/drivers/pm.h new file mode 100644 index 000000000..531b09a50 --- /dev/null +++ b/lab5/c/include/oscos/drivers/pm.h @@ -0,0 +1,14 @@ +#ifndef OSCOS_DRIVERS_PM_H +#define OSCOS_DRIVERS_PM_H + +#include +#include + +void pm_init(void); + +void pm_reset(uint32_t tick); +void pm_cancel_reset(void); + +noreturn void pm_reboot(void); + +#endif diff --git a/lab5/c/include/oscos/initrd.h b/lab5/c/include/oscos/initrd.h new file mode 100644 index 000000000..112cf9b83 --- /dev/null +++ b/lab5/c/include/oscos/initrd.h @@ -0,0 +1,117 @@ +/// \file include/oscos/initrd.h +/// \brief Initial ramdisk. +/// +/// Before reading the initial ramdisk, it must be initialized by calling +/// initrd_init(void). + +#ifndef OSCOS_INITRD_H +#define OSCOS_INITRD_H + +#include +#include + +#include "oscos/libc/string.h" +#include "oscos/utils/align.h" + +/// \brief New ASCII Format CPIO archive header. +typedef struct { + char c_magic[6]; + char c_ino[8]; + char c_mode[8]; + char c_uid[8]; + char c_gid[8]; + char c_nlink[8]; + char c_mtime[8]; + char c_filesize[8]; + char c_devmajor[8]; + char c_devminor[8]; + char c_rdevmajor[8]; + char c_rdevminor[8]; + char c_namesize[8]; + char c_check[8]; +} cpio_newc_header_t; + +#define CPIO_NEWC_MODE_FILE_TYPE_MASK ((uint32_t)0170000) +#define CPIO_NEWC_MODE_FILE_TYPE_LNK ((uint32_t)0120000) +#define CPIO_NEWC_MODE_FILE_TYPE_REG ((uint32_t)0100000) +#define CPIO_NEWC_MODE_FILE_TYPE_DIR ((uint32_t)0040000) + +/// \brief New ASCII Format CPIO archive file entry. +typedef struct { + cpio_newc_header_t header; + char payload[]; +} cpio_newc_entry_t; + +/// \brief Pointer to the first file entry of the initial ramdisk. +#define INITRD_HEAD ((const cpio_newc_entry_t *)initrd_get_start()) + +/// \brief The value of the given header of the file entry. +#define CPIO_NEWC_HEADER_VALUE(ENTRY, HEADER) \ + cpio_newc_parse_header_field((ENTRY)->header.c_##HEADER) + +/// \brief The pathname of the file entry. +#define CPIO_NEWC_PATHNAME(ENTRY) ((const char *)(ENTRY)->payload) + +/// \brief The file data of the file entry. +#define CPIO_NEWC_FILE_DATA(ENTRY) \ + ((const char *)ALIGN((uintptr_t)(ENTRY)->payload + \ + CPIO_NEWC_HEADER_VALUE(ENTRY, namesize), \ + 4)) + +/// \brief The filesize of the file entry. +#define CPIO_NEWC_FILESIZE(ENTRY) CPIO_NEWC_HEADER_VALUE(ENTRY, filesize) + +/// \brief Determines if the file entry is the final sentinel entry. +#define CPIO_NEWC_IS_ENTRY_LAST(ENTRY) \ + (strcmp(CPIO_NEWC_PATHNAME(ENTRY), "TRAILER!!!") == 0) + +/// \brief Returns the next file entry of the given file entry. +/// +/// The given file entry must not be the final sentinel entry. +#define CPIO_NEWC_NEXT_ENTRY(ENTRY) \ + (const cpio_newc_entry_t *)ALIGN( \ + (uintptr_t)CPIO_NEWC_FILE_DATA(ENTRY) + CPIO_NEWC_FILESIZE(ENTRY), 4) + +/// \brief Expands to a for loop that loops over each file entry in the initial +/// ramdisk. +/// +/// \param ENTRY_NAME The name of the variable for the file entry. +#define INITRD_FOR_ENTRY(ENTRY_NAME) \ + for (const cpio_newc_entry_t *ENTRY_NAME = INITRD_HEAD; \ + !CPIO_NEWC_IS_ENTRY_LAST(ENTRY_NAME); \ + ENTRY_NAME = CPIO_NEWC_NEXT_ENTRY(ENTRY_NAME)) + +/// \brief Initializes the initial ramdisk. +/// +/// This function obtains the loading address of the initial ramdisk from the +/// device tree and determines if the initial ramdisk is a valid New ASCII +/// format CPIO archive. +/// +/// \return Whether or not initialization is successful. +bool initrd_init(void); + +/// \brief Returns whether or not the initial ramdisk has been successfully +/// initialized. +bool initrd_is_init(void); + +/// \brief Returns the loading address of the initial ramdisk. +const void *initrd_get_start(void); + +/// \brief Returns the ending address of the initial ramdisk. +const void *initrd_get_end(void); + +/// \brief Parses the given header field of a New ASCII format CPIO archive. +/// +/// The given header field must be valid. +/// +/// \see CPIO_NEWC_HEADER_VALUE +uint32_t cpio_newc_parse_header_field(const char field[static 8]); + +/// \brief Finds the file entry in the initial ramdisk corresponding to the +/// given pathname. +/// \param pathname The pathname. +/// \return The pointer to the file entry if the entry is found, or NULL +/// otherwise. +const cpio_newc_entry_t *initrd_find_entry_by_pathname(const char *pathname); + +#endif diff --git a/lab5/c/include/oscos/libc/ctype.h b/lab5/c/include/oscos/libc/ctype.h new file mode 100644 index 000000000..6e5eb134a --- /dev/null +++ b/lab5/c/include/oscos/libc/ctype.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_LIBC_CTYPE_H +#define OSCOS_LIBC_CTYPE_H + +int isdigit(int c); + +#endif diff --git a/lab5/c/include/oscos/libc/inttypes.h b/lab5/c/include/oscos/libc/inttypes.h new file mode 100644 index 000000000..90ba7b7c6 --- /dev/null +++ b/lab5/c/include/oscos/libc/inttypes.h @@ -0,0 +1,9 @@ +#ifndef OSCOS_LIBC_INTTYPES_H +#define OSCOS_LIBC_INTTYPES_H + +#include + +#define PRIx32 "x" +#define PRIx64 "lx" + +#endif diff --git a/lab5/c/include/oscos/libc/stdio.h b/lab5/c/include/oscos/libc/stdio.h new file mode 100644 index 000000000..2cf00d503 --- /dev/null +++ b/lab5/c/include/oscos/libc/stdio.h @@ -0,0 +1,12 @@ +#ifndef OSCOS_LIBC_STDIO_H +#define OSCOS_LIBC_STDIO_H + +#include +#include + +int snprintf(char str[restrict], size_t size, const char *restrict format, ...) + __attribute__((format(printf, 3, 4))); +int vsnprintf(char str[restrict], size_t size, const char *restrict format, + va_list ap) __attribute__((format(printf, 3, 0))); + +#endif diff --git a/lab5/c/include/oscos/libc/stdlib.h b/lab5/c/include/oscos/libc/stdlib.h new file mode 100644 index 000000000..a09506078 --- /dev/null +++ b/lab5/c/include/oscos/libc/stdlib.h @@ -0,0 +1,11 @@ +#ifndef OSCOS_LIBC_STDLIB_H +#define OSCOS_LIBC_STDLIB_H + +#include + +void qsort(void *base, size_t nmemb, size_t size, + int (*compar)(const void *, const void *)); +void qsort_r(void *base, size_t nmemb, size_t size, + int (*compar)(const void *, const void *, void *), void *arg); + +#endif diff --git a/lab5/c/include/oscos/libc/string.h b/lab5/c/include/oscos/libc/string.h new file mode 100644 index 000000000..5abc5b3af --- /dev/null +++ b/lab5/c/include/oscos/libc/string.h @@ -0,0 +1,25 @@ +#ifndef OSCOS_LIBC_STRING_H +#define OSCOS_LIBC_STRING_H + +#include + +int memcmp(const void *s1, const void *s2, size_t n); +void *memset(void *s, int c, size_t n); +void *memcpy(void *restrict dest, const void *restrict src, size_t n); +void *memmove(void *dest, const void *src, size_t n); + +int strcmp(const char *s1, const char *s2); +int strncmp(const char *s1, const char *s2, size_t n); + +size_t strlen(const char *s); + +// Extensions. + +/// \brief Swap two non-overlapping blocks of memory. +/// +/// \param xs The pointer to the beginning of the first block of memory. +/// \param ys The pointer to the beginning of the second block of memory. +/// \param n The size of the memory blocks. +void memswp(void *restrict xs, void *restrict ys, size_t n); + +#endif diff --git a/lab5/c/include/oscos/mem/malloc.h b/lab5/c/include/oscos/mem/malloc.h new file mode 100644 index 000000000..282ca848f --- /dev/null +++ b/lab5/c/include/oscos/mem/malloc.h @@ -0,0 +1,30 @@ +/// \file include/oscos/mem/malloc.h +/// \brief Dynamic memory allocator. +/// +/// The dynamic memory allocator is a general-purpose memory allocator that +/// allocates physically-contiguous memory. + +#ifndef OSCOS_MEM_MALLOC_H +#define OSCOS_MEM_MALLOC_H + +#include +#include + +/// \brief Initializes the dynamic memory allocator. +void malloc_init(void); + +/// \brief Frees memory allocated using the dynamic memory allocator. +/// +/// \param ptr The pointer to the allocated memory. +void free(void *ptr); + +/// \brief Dynamically allocates memory using the dynamic memory allocator. +/// +/// \param size The requested size in bytes of the allocation. +/// \return The pointer to the allocated memory, or NULL if the request cannot +/// be fulfilled. +void *malloc(size_t size) + __attribute__((alloc_size(1), assume_aligned(alignof(max_align_t)), malloc, + malloc(free, 1))); + +#endif diff --git a/lab5/c/include/oscos/mem/memmap.h b/lab5/c/include/oscos/mem/memmap.h new file mode 100644 index 000000000..967f57469 --- /dev/null +++ b/lab5/c/include/oscos/mem/memmap.h @@ -0,0 +1,99 @@ +/// \file include/oscos/mem/memmap.h +/// \brief Memory map. +/// +/// The memory map contains information about which part of the memory is usable +/// and which part of the memory is reserved. + +#ifndef OSCOS_MEM_MEMMAP_H +#define OSCOS_MEM_MEMMAP_H + +#include +#include + +#include "oscos/mem/types.h" + +/// \brief Purpose of memory reservation. +typedef enum { + RMP_FIRMWARE, ///< Reserved by firmware for whatever reason. + RMP_KERNEL, ///< Kernel text, rodata, data, and bss. + RMP_INITRD, ///< Initial ramdisk. + RMP_DTB, ///< Devicetree blob. + RMP_STACK, ///< Kernel stack. + RMP_UNKNOWN ///< Unknown reason. +} reserved_mem_purpose_t; + +/// \brief A reserved memory entry in the memory map. +typedef struct { + pa_range_t range; + reserved_mem_purpose_t purpose; +} reserved_mem_entry_t; + +/// \brief Initializes the memory map. +void memmap_init(void); + +/// \brief Adds a usable memory region. +/// +/// \return true if the addition succeeds. +/// \return false if the addition fails because the usable memory region array +/// is out of free space. +bool mem_add(pa_range_t mem); + +/// \brief Sorts the usable memory regions in ascending order. +void mem_sort(void); + +/// \brief Gets the number of usable memory regions. +size_t mem_get_n(void); + +/// \brief Gets the pointer to the usable memory region array. +const pa_range_t *mem_get(void); + +/// \brief Adds a reserved memory region. +/// +/// \return true if the addition succeeds. +/// \return false if the addition fails because the reserved memory region array +/// is out of free space. +bool reserved_mem_add(reserved_mem_entry_t reserved_mem); + +/// \brief Sorts the reserved memory regions in ascending order. +void reserved_mem_sort(void); + +/// \brief Gets the number of reserved memory regions. +size_t reserved_mem_get_n(void); + +/// \brief Gets the pointer to the reserved memory region array. +const reserved_mem_entry_t *reserved_mem_get(void); + +/// \brief Scans the device memory and adds the results to the memory map. +/// +/// This function starts a kernel panic when either the usable memory region +/// array or the reserved memory region array runs out of space. +/// +/// This function sorts the usable and reserved memory regions in ascending +/// order as if by calling void mem_sort(void) and void reserved_mem_sort(void). +void scan_mem(void); + +/// \brief Checks whether or not the memory map is well-formed. +/// +/// A memory map is well-formed if all of the following conditions hold: +/// - All usable memory regions do not overlap. +/// - All reserved memory regions do not overlap and are within one of the +/// usable memory regions. +/// +/// Before calling this function, the memory regions must be sorted in ascending +/// order (automatically done by void scan_mem(void)). +bool check_memmap(void); + +/// \brief Prints the device memory map to the serial console. +/// +/// Before calling this function, the usable memory regions and the reserved +/// memory regions must be sorted in ascending order (automatically done by +/// void scan_mem(void)). +void print_memmap(void); + +/// \brief Checks whether or not a memory range is usable, i.e., within a usable +/// memory region but does not overlap with any reserved memory regions. +/// +/// The memory map must be well-formed when calling this function. +bool memmap_is_usable(pa_range_t range); + +#endif diff --git a/lab5/c/include/oscos/mem/page-alloc.h b/lab5/c/include/oscos/mem/page-alloc.h new file mode 100644 index 000000000..968922985 --- /dev/null +++ b/lab5/c/include/oscos/mem/page-alloc.h @@ -0,0 +1,61 @@ +/// \file include/oscos/mem/page-alloc.h +/// \brief Page frame allocator. + +#ifndef OSCOS_MEM_PAGE_ALLOC_H +#define OSCOS_MEM_PAGE_ALLOC_H + +#include +#include + +#include "oscos/mem/types.h" + +#define PAGE_ORDER 12 +#define MAX_BLOCK_ORDER 18 + +/// \brief Initializes the page frame allocator. +/// +/// The memory map must be well-formed when calling this function. After calling +/// this function, the startup allocator should not be used. +void page_alloc_init(void); + +/// \brief Allocates a block of page frames. +/// \param order The order of the block. +/// \return The page number of the first page, or a negative number if the +/// request cannot be fulfilled. +spage_id_t alloc_pages(size_t order); + +/// \brief Frees a block of page frames. +/// \param page The page number of the first page of the block. +void free_pages(page_id_t page); + +/// \brief Marks a contiguous range of page frames as either reserved or +/// available. +/// +/// Note that the range can be arbitrary and doesn't need to be a block. +/// +/// \param range The range of page frames to mark. +/// \param is_avail The target reservation status. +void mark_pages(page_id_range_t range, bool is_avail); + +/// \brief Converts the given page ID into its corresponding physical address. +pa_t page_id_to_pa(page_id_t page) __attribute__((pure)); + +/// \brief Converts the given physical address into its corresponding page ID. +/// +/// Before conversion, the physical address is rounded down to the page +/// boundary. I.e., the returned page ID is the page ID whose corresponding +/// physical address range covers the given physical address. +page_id_t pa_to_page_id(pa_t pa) __attribute__((pure)); + +/// \brief Converts the given physical address range into its corresponding page +/// ID range. +/// +/// Before conversion, the starting physical address is rounded down to the page +/// boundary and the ending physical address is rounded up to the page boundary. +/// I.e., the returned page ID range is the smallest page ID range whose +/// corresponding physical address range covers the given physical address +/// range. +page_id_range_t pa_range_to_page_id_range(pa_range_t range) + __attribute__((pure)); + +#endif diff --git a/lab5/c/include/oscos/mem/startup-alloc.h b/lab5/c/include/oscos/mem/startup-alloc.h new file mode 100644 index 000000000..fb626c99d --- /dev/null +++ b/lab5/c/include/oscos/mem/startup-alloc.h @@ -0,0 +1,34 @@ +/// \file include/oscos/mem/startup-alloc.h +/// \brief Startup allocator. +/// +/// The startup allocator is a simple bump pointer allocator that is usable even +/// when no other subsystems are initialized. This memory allocator allocates +/// memory starting from the end of the kernel. Note that memory allocated using +/// this memory allocator cannot be freed. + +#ifndef OSCOS_MEM_STARTUP_ALLOC_H +#define OSCOS_MEM_STARTUP_ALLOC_H + +#include +#include + +#include "oscos/mem/types.h" + +/// \brief Initializes the startup allocator. +void startup_alloc_init(void); + +/// \brief Dynamically allocates memory using the startup allocator. +/// +/// Note that memory allocated by this function cannot be freed. +/// +/// \param size The requested size in bytes of the allocation. +/// \return The pointer to the allocated memory, or NULL if the request cannot +/// be fulfilled. +void *startup_alloc(size_t size) + __attribute__((alloc_size(1), assume_aligned(alignof(max_align_t)), + malloc)); + +/// \brief Gets the memory range allocated through the startup allocator. +va_range_t startup_alloc_get_alloc_range(void) __attribute__((pure)); + +#endif diff --git a/lab5/c/include/oscos/mem/types.h b/lab5/c/include/oscos/mem/types.h new file mode 100644 index 000000000..d679f55d0 --- /dev/null +++ b/lab5/c/include/oscos/mem/types.h @@ -0,0 +1,37 @@ +#ifndef OSCOS_MEM_TYPES_H +#define OSCOS_MEM_TYPES_H + +#include "oscos/libc/inttypes.h" + +/// \brief Physical address. +typedef uint32_t pa_t; + +/// \brief Format specifier for printing a pa_t in lowercase hexadecimal format. +#define PRIxPA PRIx32 + +/// \brief Page ID. +typedef uint32_t page_id_t; + +/// \brief Format specifier for printing a page_id_t in lowercase hexadecimal +/// format. +#define PRIxPAGEID PRIx32 + +/// \brief Signed page ID. +typedef int32_t spage_id_t; + +/// \brief Physical address range. +typedef struct { + pa_t start, end; +} pa_range_t; + +/// \brief Page range. +typedef struct { + page_id_t start, end; +} page_id_range_t; + +/// \brief Virtual address range. +typedef struct { + void *start, *end; +} va_range_t; + +#endif diff --git a/lab5/c/include/oscos/mem/vm.h b/lab5/c/include/oscos/mem/vm.h new file mode 100644 index 000000000..ecbc9545d --- /dev/null +++ b/lab5/c/include/oscos/mem/vm.h @@ -0,0 +1,18 @@ +#ifndef OSCOS_MEM_VM_H +#define OSCOS_MEM_VM_H + +#include "oscos/mem/types.h" + +/// \brief Converts a kernel space virtual address into its corresponding +/// physical address. +pa_t kernel_va_to_pa(const void *va) __attribute__((const)); + +/// \brief Converts a physical address into its corresponding kernel space +/// virtual address. +void *pa_to_kernel_va(pa_t pa) __attribute__((const)); + +/// \brief Converts a kernel space virtual address range into its corresponding +/// physical address range. +pa_range_t kernel_va_range_to_pa_range(va_range_t range) __attribute__((const)); + +#endif diff --git a/lab5/c/include/oscos/panic.h b/lab5/c/include/oscos/panic.h new file mode 100644 index 000000000..ce113c914 --- /dev/null +++ b/lab5/c/include/oscos/panic.h @@ -0,0 +1,23 @@ +#ifndef OSCOS_PANIC_H +#define OSCOS_PANIC_H + +#include + +/// \brief Starts a kernel panic. +#define PANIC(...) panic_begin(__FILE__, __LINE__, __VA_ARGS__) + +/// \brief Starts a kernel panic. +/// +/// In most cases, the PANIC macro should be used instead of directly calling +/// this function. +/// +/// \param file The path of the source file to appear in the panic message. +/// \param line The line number to appear in the panic message. +/// \param format The format string of the panic message. +/// +/// \see PANIC +noreturn void panic_begin(const char *restrict file, int line, + const char *restrict format, ...) + __attribute__((cold, format(printf, 3, 4))); + +#endif diff --git a/lab5/c/include/oscos/shell.h b/lab5/c/include/oscos/shell.h new file mode 100644 index 000000000..742f7907e --- /dev/null +++ b/lab5/c/include/oscos/shell.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_SHELL_H +#define OSCOS_SHELL_H + +void run_shell(void); + +#endif diff --git a/lab5/c/include/oscos/timer/delay.h b/lab5/c/include/oscos/timer/delay.h new file mode 100644 index 000000000..49eebc7cd --- /dev/null +++ b/lab5/c/include/oscos/timer/delay.h @@ -0,0 +1,11 @@ +#ifndef OSCOS_TIMER_DELAY_H +#define OSCOS_TIMER_DELAY_H + +#include + +/// \brief Delays for the specified number of nanoseconds. +/// +/// Note that this function may delay for a longer period than specified. +void delay_ns(uint64_t ns); + +#endif diff --git a/lab5/c/include/oscos/timer/timeout.h b/lab5/c/include/oscos/timer/timeout.h new file mode 100644 index 000000000..401f09f07 --- /dev/null +++ b/lab5/c/include/oscos/timer/timeout.h @@ -0,0 +1,13 @@ +#ifndef OSCOS_TIMER_TIMEOUT_H +#define OSCOS_TIMER_TIMEOUT_H + +#include +#include + +void timeout_init(void); + +bool timeout_add_timer(void (*callback)(void *), void *arg, uint64_t after_ns); + +void xcpt_core_timer_interrupt_handler(void); + +#endif diff --git a/lab5/c/include/oscos/user-program.h b/lab5/c/include/oscos/user-program.h new file mode 100644 index 000000000..c13f60021 --- /dev/null +++ b/lab5/c/include/oscos/user-program.h @@ -0,0 +1,22 @@ +#ifndef OSCOS_USER_PROGRAM_H +#define OSCOS_USER_PROGRAM_H + +#include +#include +#include + +/// \brief Loads the user program from somewhere in the memory to the place in +/// memory where it'll be executed. +/// +/// \param start The starting address of where the user program is currently +/// located. (Do not confuse it with the entry point of the user +/// program.) +/// \param len The length of the user program. +/// \return true if the loading succeeds. +/// \return false if the loading failed due to the user program being too long. +bool load_user_program(const void *start, size_t len); + +/// \brief Runs the loaded user program. +noreturn void run_user_program(void); + +#endif diff --git a/lab5/c/include/oscos/utils/align.h b/lab5/c/include/oscos/utils/align.h new file mode 100644 index 000000000..93d941b9e --- /dev/null +++ b/lab5/c/include/oscos/utils/align.h @@ -0,0 +1,13 @@ +#ifndef OSCOS_UTILS_ALIGN_H +#define OSCOS_UTILS_ALIGN_H + +/// \brief Returns \p X aligned to multiples of \p A. +/// +/// This macro is usually used to align a pointer. When doing so, cast the +/// pointer to `uintptr_t` and cast the result back to the desired pointer type. +/// +/// \param X The value to be aligned. Must be of an arithmetic type. +/// \param A The alignment. +#define ALIGN(X, A) (((X) + (A)-1) / (A) * (A)) + +#endif diff --git a/lab5/c/include/oscos/utils/control-flow.h b/lab5/c/include/oscos/utils/control-flow.h new file mode 100644 index 000000000..856c10c91 --- /dev/null +++ b/lab5/c/include/oscos/utils/control-flow.h @@ -0,0 +1,11 @@ +#ifndef OSCOS_UTILS_CONTROL_FLOW_H +#define OSCOS_UTILS_CONTROL_FLOW_H + +/// \brief Used to tell an operation whether it should exit early or go on as +/// usual. +/// +/// This is modelled after Rust's `std::ops::ControlFlow`, but this doesn't +/// carry values. +typedef enum { CF_CONTINUE, CF_BREAK } control_flow_t; + +#endif diff --git a/lab5/c/include/oscos/utils/core-id.h b/lab5/c/include/oscos/utils/core-id.h new file mode 100644 index 000000000..b23e78a25 --- /dev/null +++ b/lab5/c/include/oscos/utils/core-id.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_UTILS_CORE_ID_H +#define OSCOS_UTILS_CORE_ID_H + +#include + +size_t get_core_id(void); + +#endif diff --git a/lab5/c/include/oscos/utils/critical-section.h b/lab5/c/include/oscos/utils/critical-section.h new file mode 100644 index 000000000..05de74b18 --- /dev/null +++ b/lab5/c/include/oscos/utils/critical-section.h @@ -0,0 +1,20 @@ +#ifndef OSCOS_UTILS_CRITICAL_SECTION_H +#define OSCOS_UTILS_CRITICAL_SECTION_H + +#include "oscos/xcpt.h" + +#define CRITICAL_SECTION_ENTER(DAIF_VAL) \ + do { \ + __asm__ __volatile__("mrs %0, daif" : "=r"(DAIF_VAL)); \ + XCPT_MASK_ALL(); \ + } while (0) + +#define CRITICAL_SECTION_LEAVE(DAIF_VAL) \ + do { \ + /* Prevent the compiler from reordering memory accesses after interrupt \ + unmasking. */ \ + __asm__ __volatile__("" : : : "memory"); \ + __asm__ __volatile__("msr daif, %0" : : "r"(DAIF_VAL)); \ + } while (0) + +#endif diff --git a/lab5/c/include/oscos/utils/endian.h b/lab5/c/include/oscos/utils/endian.h new file mode 100644 index 000000000..1feb8f363 --- /dev/null +++ b/lab5/c/include/oscos/utils/endian.h @@ -0,0 +1,21 @@ +#ifndef OSCOS_UTILS_ENDIAN_H +#define OSCOS_UTILS_ENDIAN_H + +#include +#include + +/// \brief Reverses the byte order in a `uint32_t`. +static inline uint32_t rev_u32(const uint32_t x) { + uint32_t result; + __asm__("rev %w0, %w1" : "=r"(result) : "r"(x)); + return result; +} + +/// \brief Reverses the byte order in a `uint64_t`. +static inline uint64_t rev_u64(const uint64_t x) { + uint64_t result; + __asm__("rev %0, %1" : "=r"(result) : "r"(x)); + return result; +} + +#endif diff --git a/lab5/c/include/oscos/utils/fmt.h b/lab5/c/include/oscos/utils/fmt.h new file mode 100644 index 000000000..d9a68c4f9 --- /dev/null +++ b/lab5/c/include/oscos/utils/fmt.h @@ -0,0 +1,15 @@ +#ifndef OSCOS_UTILS_FMT_H +#define OSCOS_UTILS_FMT_H + +#include + +typedef struct { + void (*putc)(unsigned char, void *); + void (*finalize)(void *); +} printf_vtable_t; + +int vprintf_generic(const printf_vtable_t *vtable, void *arg, + const char *restrict format, va_list ap) + __attribute__((format(printf, 3, 0))); + +#endif diff --git a/lab5/c/include/oscos/utils/heapq.h b/lab5/c/include/oscos/utils/heapq.h new file mode 100644 index 000000000..6abd3d635 --- /dev/null +++ b/lab5/c/include/oscos/utils/heapq.h @@ -0,0 +1,14 @@ +#ifndef OSCOS_UTILS_HEAPQ_H +#define OSCOS_UTILS_HEAPQ_H + +#include + +void heappush(void *restrict base, size_t nmemb, size_t size, + const void *restrict item, + int (*compar)(const void *, const void *, void *), void *arg); + +void heappop(void *restrict base, size_t nmemb, size_t size, + void *restrict item, + int (*compar)(const void *, const void *, void *), void *arg); + +#endif diff --git a/lab5/c/include/oscos/utils/math.h b/lab5/c/include/oscos/utils/math.h new file mode 100644 index 000000000..1e91ed835 --- /dev/null +++ b/lab5/c/include/oscos/utils/math.h @@ -0,0 +1,13 @@ +#ifndef OSCOS_UTILS_MATH_H +#define OSCOS_UTILS_MATH_H + +#include + +/// \brief Returns the ceiling of the log base 2 of the argument. +static inline uint64_t clog2(const uint64_t x) { + uint64_t clz_result; + __asm__("clz %0, %1" : "=r"(clz_result) : "r"(x - 1)); + return 64 - clz_result; +} + +#endif diff --git a/lab5/c/include/oscos/utils/save-ctx.S b/lab5/c/include/oscos/utils/save-ctx.S new file mode 100644 index 000000000..4485dc9da --- /dev/null +++ b/lab5/c/include/oscos/utils/save-ctx.S @@ -0,0 +1,25 @@ +.macro save_aapcs + stp x0, x1, [sp, -(20 * 8)]! + stp x2, x3, [sp, 2 * 8] + stp x4, x5, [sp, 4 * 8] + stp x6, x7, [sp, 6 * 8] + stp x8, x9, [sp, 8 * 8] + stp x10, x11, [sp, 10 * 8] + stp x12, x13, [sp, 12 * 8] + stp x14, x15, [sp, 14 * 8] + stp x16, x17, [sp, 16 * 8] + stp x18, x30, [sp, 18 * 8] +.endm + +.macro load_aapcs + ldp x18, x30, [sp, 18 * 8] + ldp x16, x17, [sp, 16 * 8] + ldp x14, x15, [sp, 14 * 8] + ldp x12, x13, [sp, 12 * 8] + ldp x10, x11, [sp, 10 * 8] + ldp x8, x9, [sp, 8 * 8] + ldp x6, x7, [sp, 6 * 8] + ldp x4, x5, [sp, 4 * 8] + ldp x2, x3, [sp, 2 * 8] + ldp x0, x1, [sp], (20 * 8) +.endm diff --git a/lab5/c/include/oscos/utils/suspend.h b/lab5/c/include/oscos/utils/suspend.h new file mode 100644 index 000000000..c031cecc6 --- /dev/null +++ b/lab5/c/include/oscos/utils/suspend.h @@ -0,0 +1,19 @@ +#ifndef OSCOS_UTILS_SUSPEND_H +#define OSCOS_UTILS_SUSPEND_H + +#include "oscos/utils/critical-section.h" + +#define WFI_WHILE(COND) \ + do { \ + bool _wfi_while_cond_val = true; \ + while (_wfi_while_cond_val) { \ + uint64_t _wfi_while_daif_val; \ + CRITICAL_SECTION_ENTER(_wfi_while_daif_val); \ + if ((_wfi_while_cond_val = (COND))) { \ + __asm__ __volatile__("wfi"); \ + } \ + CRITICAL_SECTION_LEAVE(_wfi_while_daif_val); \ + } \ + } while (0) + +#endif diff --git a/lab5/c/include/oscos/utils/time.h b/lab5/c/include/oscos/utils/time.h new file mode 100644 index 000000000..12b4c463f --- /dev/null +++ b/lab5/c/include/oscos/utils/time.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_UTILS_TIME_H +#define OSCOS_UTILS_TIME_H + +#define NS_PER_SEC ((uint64_t)1000000000) + +#endif diff --git a/lab5/c/include/oscos/xcpt.h b/lab5/c/include/oscos/xcpt.h new file mode 100644 index 000000000..c5ca13530 --- /dev/null +++ b/lab5/c/include/oscos/xcpt.h @@ -0,0 +1,9 @@ +#ifndef OSCOS_XCPT_H +#define OSCOS_XCPT_H + +#define XCPT_MASK_ALL() __asm__ __volatile__("msr daifset, 0xf") +#define XCPT_UNMASK_ALL() __asm__ __volatile__("msr daifclr, 0xf") + +void xcpt_set_vector_table(void); + +#endif diff --git a/lab5/c/include/oscos/xcpt/task-queue.h b/lab5/c/include/oscos/xcpt/task-queue.h new file mode 100644 index 000000000..4f03ce4e6 --- /dev/null +++ b/lab5/c/include/oscos/xcpt/task-queue.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_XCPT_TASK_QUEUE_H +#define OSCOS_XCPT_TASK_QUEUE_H + +#include + +bool task_queue_add_task(void (*task)(void *), void *arg, int priority); + +#endif diff --git a/lab5/c/src/console.c b/lab5/c/src/console.c new file mode 100644 index 000000000..93729b480 --- /dev/null +++ b/lab5/c/src/console.c @@ -0,0 +1,243 @@ +#include "oscos/console.h" + +#include + +#include "oscos/drivers/gpio.h" +#include "oscos/drivers/l2ic.h" +#include "oscos/drivers/mini-uart.h" +#include "oscos/mem/startup-alloc.h" +#include "oscos/utils/critical-section.h" +#include "oscos/utils/fmt.h" +#include "oscos/xcpt.h" + +#define READ_BUF_SZ 1024 +#define WRITE_BUF_SZ 1024 + +static volatile unsigned char *_console_read_buf, *_console_write_buf; +static volatile size_t _console_read_buf_start = 0, _console_read_buf_len = 0, + _console_write_buf_start = 0, _console_write_buf_len = 0; + +// Buffer operations. + +static void _console_recv_to_buf(void) { + // Read until the read buffer is full or there are nothing to read. + + int read_result; + while (_console_read_buf_len != READ_BUF_SZ && + (read_result = mini_uart_recv_byte_nonblock()) >= 0) { + _console_read_buf[(_console_read_buf_start + _console_read_buf_len++) % + READ_BUF_SZ] = read_result; + } + + // Disable the receive interrupt if the read buffer is full. Otherwise, the + // interrupt will fire again and again after exception return, blocking the + // main code from executing. + if (_console_read_buf_len == READ_BUF_SZ) { + mini_uart_disable_rx_interrupt(); + } +} + +static void _console_send_from_buf(void) { + while (_console_write_buf_len != 0 && + mini_uart_send_byte_nonblock( + _console_write_buf[_console_write_buf_start]) >= 0) { + _console_write_buf_start = (_console_write_buf_start + 1) % WRITE_BUF_SZ; + _console_write_buf_len--; + } + + // Disable the transmit interrupt if the write buffer is full. Otherwise, the + // interrupt will fire again and again after exception return, blocking the + // main code from executing. + if (_console_write_buf_len == 0) { + mini_uart_disable_tx_interrupt(); + } +} + +// Raw operations. + +static unsigned char _console_recv_byte(void) { + bool suspend_cond_val = true; + uint64_t daif_val; + + // Wait for data in the read buffer. + while (suspend_cond_val) { + CRITICAL_SECTION_ENTER(daif_val); + + if ((suspend_cond_val = _console_read_buf_len == 0)) { + __asm__ __volatile__("wfi"); + _console_recv_to_buf(); + } + + CRITICAL_SECTION_LEAVE(daif_val); + } + + CRITICAL_SECTION_ENTER(daif_val); + + const unsigned char result = _console_read_buf[_console_read_buf_start]; + _console_read_buf_start = (_console_read_buf_start + 1) % READ_BUF_SZ; + _console_read_buf_len--; + + CRITICAL_SECTION_LEAVE(daif_val); + + // Re-enable receive interrupt. + mini_uart_enable_rx_interrupt(); + + return result; +} + +static void _console_send_byte(const unsigned char b) { + bool suspend_cond_val = true; + uint64_t daif_val; + + // Wait for the write buffer to clear. + while (suspend_cond_val) { + CRITICAL_SECTION_ENTER(daif_val); + + if ((suspend_cond_val = _console_write_buf_len == WRITE_BUF_SZ)) { + __asm__ __volatile__("wfi"); + _console_send_from_buf(); + } + + CRITICAL_SECTION_LEAVE(daif_val); + } + + CRITICAL_SECTION_ENTER(daif_val); + + _console_write_buf[(_console_write_buf_start + _console_write_buf_len++) % + WRITE_BUF_SZ] = b; + + CRITICAL_SECTION_LEAVE(daif_val); + + // Re-enable transmit interrupt. + mini_uart_enable_tx_interrupt(); +} + +// Mode switching is implemented by switching the vtables for the two primitive +// operations, getc and putc. + +typedef struct { + unsigned char (*getc)(void); + void (*putc)(unsigned char); +} console_primop_vtable_t; + +static unsigned char _console_getc_text_mode(void) { + const unsigned char c = _console_recv_byte(); + return c == '\r' ? '\n' : c; +} + +static unsigned char _console_getc_binary_mode(void) { + return _console_recv_byte(); +} + +static void _console_putc_text_mode(const unsigned char c) { + if (c == '\n') { + _console_send_byte('\r'); + _console_send_byte('\n'); + } else { + _console_send_byte(c); + } +} + +static void _console_putc_binary_mode(const unsigned char c) { + _console_send_byte(c); +} + +static const console_primop_vtable_t + _primop_vtable_text_mode = {.getc = _console_getc_text_mode, + .putc = _console_putc_text_mode}, + _primop_vtable_binary_mode = {.getc = _console_getc_binary_mode, + .putc = _console_putc_binary_mode}, + *_primop_vtable; + +// Public functions. + +void console_init(void) { + gpio_setup_uart0_gpio14(); + mini_uart_init(); + + console_set_mode(CM_TEXT); + + // Enable receive interrupt. + mini_uart_enable_rx_interrupt(); + // Enable AUX interrupt on the L2 interrupt controller. + l2ic_enable_irq_0(INT_L2_IRQ_0_SRC_AUX); + + // Allocate the buffers. + _console_read_buf = startup_alloc(READ_BUF_SZ * sizeof(unsigned char)); + _console_write_buf = startup_alloc(WRITE_BUF_SZ * sizeof(unsigned char)); +} + +void console_set_mode(const console_mode_t mode) { + // ? Should we check the value of `mode`? + // * FIXME: If the check is implemented, update the documentation in + // * `include/oscos/console.h`. + + switch (mode) { + case CM_TEXT: + _primop_vtable = &_primop_vtable_text_mode; + break; + + case CM_BINARY: + _primop_vtable = &_primop_vtable_binary_mode; + break; + + default: + __builtin_unreachable(); + } +} + +unsigned char console_getc(void) { return _primop_vtable->getc(); } + +unsigned char console_putc(const unsigned char c) { + _primop_vtable->putc(c); + return c; +} + +size_t console_write(const void *const buf, const size_t count) { + const unsigned char *const buf_c = buf; + for (size_t i = 0; i < count; i++) { + console_putc(buf_c[i]); + } + return count; +} + +void console_fputs(const char *const s) { + for (const char *c = s; *c; c++) { + console_putc(*c); + } +} + +void console_puts(const char *const s) { + console_fputs(s); + console_putc('\n'); +} + +int console_printf(const char *const restrict format, ...) { + va_list ap; + va_start(ap, format); + + const int result = console_vprintf(format, ap); + + va_end(ap); + return result; +} + +static void _console_printf_putc(const unsigned char c, void *const _arg) { + (void)_arg; + + console_putc(c); +} + +static void _console_printf_finalize(void *const _arg) { (void)_arg; } + +static const printf_vtable_t _console_printf_vtable = { + .putc = _console_printf_putc, .finalize = _console_printf_finalize}; + +int console_vprintf(const char *const restrict format, va_list ap) { + return vprintf_generic(&_console_printf_vtable, NULL, format, ap); +} + +void mini_uart_interrupt_handler(void) { + _console_recv_to_buf(); + _console_send_from_buf(); +} diff --git a/lab5/c/src/devicetree.c b/lab5/c/src/devicetree.c new file mode 100644 index 000000000..8a4d75275 --- /dev/null +++ b/lab5/c/src/devicetree.c @@ -0,0 +1,129 @@ +#include "oscos/devicetree.h" + +#include + +static const char *_dtb_start = NULL; + +bool devicetree_init(const void *const dtb_start) { + // TODO: More thoroughly validate the DTB. + + if (rev_u32(((const fdt_header_t *)dtb_start)->magic) == 0xd00dfeed) { + _dtb_start = dtb_start; + return true; + } else { + _dtb_start = NULL; + return false; + } +} + +bool devicetree_is_init(void) { return _dtb_start; } + +const char *fdt_get_start(void) { return _dtb_start; } + +const char *fdt_get_end(void) { + return _dtb_start + rev_u32(((const fdt_header_t *)_dtb_start)->totalsize); +} + +const fdt_item_t *fdt_next_item(const fdt_item_t *const item) { + switch (FDT_TOKEN(item)) { + case FDT_BEGIN_NODE: { + const fdt_item_t *curr; + FDT_FOR_ITEM_(item, curr); + return curr + 1; + } + case FDT_PROP: { + const fdt_prop_t *const prop = (const fdt_prop_t *)(item->payload); + return (const fdt_item_t *)ALIGN( + (uintptr_t)(prop->value + rev_u32(prop->len)), 4); + } + case FDT_NOP: + return item + 1; + default: + __builtin_unreachable(); + } +} + +static const fdt_item_t * +_fdt_traverse_rec(const fdt_item_t *const node, + const fdt_traverse_parent_list_node_t *const parent, + fdt_traverse_callback_t *const callback, void *const arg) { + const control_flow_t result = callback(arg, node, parent); + if (result == CF_CONTINUE) { + // No-op. + } else if (result == CF_BREAK) { + return NULL; + } else { + __builtin_unreachable(); + } + + const fdt_item_t *item; + for (item = FDT_ITEMS_START(node); !FDT_ITEM_IS_END(item);) { + if (FDT_TOKEN(item) == FDT_BEGIN_NODE) { + const fdt_traverse_parent_list_node_t next_parent = {.node = node, + .parent = parent}; + const fdt_item_t *const next_item = + _fdt_traverse_rec(item, &next_parent, callback, arg); + if (!next_item) + return NULL; + item = next_item; + } else { + item = fdt_next_item(item); + } + } + + return item + 1; +} + +void fdt_traverse(fdt_traverse_callback_t *const callback, void *const arg) { + const fdt_item_t *const root_node = + (const fdt_item_t *)(_dtb_start + + rev_u32(((const fdt_header_t *)_dtb_start) + ->off_dt_struct)); + _fdt_traverse_rec(root_node, NULL, callback, arg); +} + +fdt_n_address_size_cells_t +fdt_get_n_address_size_cells(const fdt_item_t *const node) { + uint32_t n_address_cells = 2, n_size_cells = 1; + bool n_address_cells_done = false, n_size_cells_done = false; + FDT_FOR_ITEM(node, item) { + if (FDT_TOKEN(item) == FDT_PROP) { + const fdt_prop_t *const prop = (const fdt_prop_t *)item->payload; + if (strcmp(FDT_PROP_NAME(prop), "#address-cells") == 0) { + n_address_cells = rev_u32(*(const uint32_t *)FDT_PROP_VALUE(prop)); + n_address_cells_done = true; + } else if (strcmp(FDT_PROP_NAME(prop), "#size-cells") == 0) { + n_size_cells = rev_u32(*(const uint32_t *)FDT_PROP_VALUE(prop)); + n_size_cells_done = true; + } + } + if (n_address_cells_done && n_size_cells_done) + break; + } + + return (fdt_n_address_size_cells_t){.n_address_cells = n_address_cells, + .n_size_cells = n_size_cells}; +} + +fdt_read_reg_result_t fdt_read_reg(const fdt_prop_t *const prop, + const fdt_n_address_size_cells_t n_cells) { + const uint32_t *arr = (const uint32_t *)FDT_PROP_VALUE(prop); + + uintmax_t address = 0; + bool address_overflow = false; + for (uint32_t i = 0; i < n_cells.n_address_cells; i++) { + address_overflow = address_overflow || (address >> 32) != 0; + address = address << 32 | rev_u32(*arr++); + } + + uintmax_t size = 0; + bool size_overflow = false; + for (uint32_t i = 0; i < n_cells.n_size_cells; i++) { + size_overflow = size_overflow || (size >> 32) != 0; + size = size << 32 | rev_u32(*arr++); + } + + return (fdt_read_reg_result_t){.value = {.address = address, .size = size}, + .address_overflow = address_overflow, + .size_overflow = size_overflow}; +} diff --git a/lab5/c/src/drivers/aux.c b/lab5/c/src/drivers/aux.c new file mode 100644 index 000000000..c40639f09 --- /dev/null +++ b/lab5/c/src/drivers/aux.c @@ -0,0 +1,28 @@ +#include "oscos/drivers/aux.h" + +#include + +#include "oscos/drivers/board.h" + +#define AUX_REG_BASE ((void *)((char *)PERIPHERAL_BASE + 0x215000)) + +typedef struct { + const volatile uint32_t irq; + volatile uint32_t enb; +} aux_reg_t; + +#define AUX_REG ((aux_reg_t *)AUX_REG_BASE) + +#define AUX_ENB_MINI_UART_ENABLE ((uint32_t)(1 << 0)) + +void aux_init(void) { + // No-op. +} + +void aux_enable_mini_uart(void) { + PERIPHERAL_WRITE_BARRIER(); + + AUX_REG->enb |= AUX_ENB_MINI_UART_ENABLE; + + PERIPHERAL_READ_BARRIER(); +} diff --git a/lab5/c/src/drivers/gpio.c b/lab5/c/src/drivers/gpio.c new file mode 100644 index 000000000..9f8802011 --- /dev/null +++ b/lab5/c/src/drivers/gpio.c @@ -0,0 +1,105 @@ +#include "oscos/drivers/gpio.h" + +#include + +#include "oscos/drivers/board.h" +#include "oscos/timer/delay.h" + +#define GPIO_REG_BASE ((void *)((char *)PERIPHERAL_BASE + 0x200000)) + +typedef struct { + volatile uint32_t fsel[6]; + const volatile uint32_t _reserved0; + volatile uint32_t set[2]; + const volatile uint32_t _reserved1; + volatile uint32_t clr[2]; + const volatile uint32_t _reserved2; + const volatile uint32_t lev[2]; + const volatile uint32_t _reserved3; + volatile uint32_t eds[2]; + const volatile uint32_t _reserved4; + volatile uint32_t ren[2]; + const volatile uint32_t _reserved5; + volatile uint32_t fen[2]; + const volatile uint32_t _reserved6; + volatile uint32_t hen[2]; + const volatile uint32_t _reserved7; + volatile uint32_t len[2]; + const volatile uint32_t _reserved8; + volatile uint32_t aren[2]; + const volatile uint32_t _reserved9; + volatile uint32_t afen[2]; + const volatile uint32_t _reserved10; + volatile uint32_t pud; + volatile uint32_t pudclk[2]; +} gpio_reg_t; + +#define GPIO_REG ((gpio_reg_t *)GPIO_REG_BASE) + +#define GPIO_FSEL_FSEL4_POSN 12 +#define GPIO_FSEL_FSEL4_MASK ((uint32_t)((uint32_t)0x7 << GPIO_FSEL_FSEL4_POSN)) +#define GPIO_FSEL_FSEL5_POSN 15 +#define GPIO_FSEL_FSEL5_MASK ((uint32_t)((uint32_t)0x7 << GPIO_FSEL_FSEL5_POSN)) +#define GPIO_FSEL_FSEL_INPUT ((uint32_t)0x0) +#define GPIO_FSEL_FSEL_ALT5 ((uint32_t)0x2) +#define GPIO_FSEL_FSEL4_INPUT \ + ((uint32_t)(GPIO_FSEL_FSEL_INPUT << GPIO_FSEL_FSEL4_POSN)) +#define GPIO_FSEL_FSEL4_ALT5 \ + ((uint32_t)(GPIO_FSEL_FSEL_ALT5 << GPIO_FSEL_FSEL4_POSN)) +#define GPIO_FSEL_FSEL5_INPUT \ + ((uint32_t)(GPIO_FSEL_FSEL_INPUT << GPIO_FSEL_FSEL5_POSN)) +#define GPIO_FSEL_FSEL5_ALT5 \ + ((uint32_t)(GPIO_FSEL_FSEL_ALT5 << GPIO_FSEL_FSEL5_POSN)) + +#define GPIO_PUD_PUD_OFF ((uint32_t)0x0) +#define GPIO_PUD_PUD_PULL_UP ((uint32_t)0x2) + +void gpio_init(void) { + // No-op. +} + +void gpio_setup_uart0_gpio14(void) { + PERIPHERAL_WRITE_BARRIER(); + + // Set GPIO pin 14 & 15 to use alternate function 5 ({T,R}XD1). + GPIO_REG->fsel[1] = + (GPIO_REG->fsel[1] & ~(GPIO_FSEL_FSEL4_MASK | GPIO_FSEL_FSEL5_MASK)) | + (GPIO_FSEL_FSEL4_ALT5 | GPIO_FSEL_FSEL5_ALT5); + + // Setup the GPIO pull up/down resistors on pin 14 & 15. + // Pin 14 (TXD1): Disabled. + // Pin 15 (RXD1): Pull up. + // + // Instead of disabling the pull up/down resistors on pin 15 as specified in + // lab 1, we instead pull it up to ensure that mini UART doesn't read in + // garbage data when the pin is not connected. + // + // The delay period of 1 μs is calculated by dividing the required delay + // period of 150 clock cycles (as specified in [bcm2835-datasheet]) by 150 + // MHz, the nominal core frequency mentioned in [bcm2835-datasheet], pp. 34. + // We believe 150 MHz should be used instead of the actual core frequency of + // 250 MHz because the setup/hold time of a digital circuit is usually + // specified in terms of real time (e.g. nanoseconds) instead of in clock + // cycles. The specified delay period of 150 clock cycles might have been + // derived by multiplying the actual required delay period of 1 μs by the + // nominal core frequency of 150 MHz. + // + // [bcm2835-datasheet]: + // https://datasheets.raspberrypi.com/bcm2835/bcm2835-peripherals.pdf + + GPIO_REG->pud = GPIO_PUD_PUD_OFF; + delay_ns(1000); + GPIO_REG->pudclk[0] = 1 << 14; + delay_ns(1000); + GPIO_REG->pud = 0; + GPIO_REG->pudclk[0] = 0; + + GPIO_REG->pud = GPIO_PUD_PUD_PULL_UP; + delay_ns(1000); + GPIO_REG->pudclk[0] = 1 << 15; + delay_ns(1000); + GPIO_REG->pud = 0; + GPIO_REG->pudclk[0] = 0; + + PERIPHERAL_READ_BARRIER(); +} diff --git a/lab5/c/src/drivers/l1ic.c b/lab5/c/src/drivers/l1ic.c new file mode 100644 index 000000000..9af46c781 --- /dev/null +++ b/lab5/c/src/drivers/l1ic.c @@ -0,0 +1,37 @@ +#include "oscos/drivers/l1ic.h" + +#include "oscos/drivers/board.h" + +#define CORE_TIMER_IRQCNTL \ + ((volatile uint32_t(*)[4])((char *)ARM_LOCAL_PERIPHERAL_BASE + 0x40)) +#define CORE_IRQ_SOURCE \ + ((volatile uint32_t(*)[4])((char *)ARM_LOCAL_PERIPHERAL_BASE + 0x60)) +#define CORE_FIQ_SOURCE \ + ((volatile uint32_t(*)[4])((char *)ARM_LOCAL_PERIPHERAL_BASE + 0x70)) + +#define CORE_TIMER_IRQCNTL_TIMER0_IRQ ((uint32_t)(1 << 0)) +#define CORE_TIMER_IRQCNTL_TIMER1_IRQ ((uint32_t)(1 << 1)) +#define CORE_TIMER_IRQCNTL_TIMER2_IRQ ((uint32_t)(1 << 2)) +#define CORE_TIMER_IRQCNTL_TIMER3_IRQ ((uint32_t)(1 << 3)) +#define CORE_TIMER_IRQCNTL_TIMER0_FIQ ((uint32_t)(1 << 4)) +#define CORE_TIMER_IRQCNTL_TIMER1_FIQ ((uint32_t)(1 << 5)) +#define CORE_TIMER_IRQCNTL_TIMER2_FIQ ((uint32_t)(1 << 6)) +#define CORE_TIMER_IRQCNTL_TIMER3_FIQ ((uint32_t)(1 << 7)) + +void l1ic_init(void) { + // No-op. +} + +uint32_t l1ic_get_int_src(const size_t core_id) { + const uint32_t result = (*CORE_IRQ_SOURCE)[core_id]; + + PERIPHERAL_READ_BARRIER(); + return result; +} + +void l1ic_enable_core_timer_irq(size_t core_id) { + PERIPHERAL_WRITE_BARRIER(); + + // nCNTPNSIRQ IRQ control = 1 + (*CORE_TIMER_IRQCNTL)[core_id] = CORE_TIMER_IRQCNTL_TIMER1_IRQ; +} diff --git a/lab5/c/src/drivers/l2ic.c b/lab5/c/src/drivers/l2ic.c new file mode 100644 index 000000000..13c15e94c --- /dev/null +++ b/lab5/c/src/drivers/l2ic.c @@ -0,0 +1,36 @@ +#include "oscos/drivers/l2ic.h" + +#include "oscos/drivers/board.h" + +#define L2IC_REG_BASE ((void *)((char *)PERIPHERAL_BASE + 0xb200)) + +typedef struct { + const volatile uint32_t irq_basic_pending; + const volatile uint32_t irq_pending[2]; + volatile uint32_t fiq_control; + volatile uint32_t enable_irqs[2]; + volatile uint32_t enable_basic_irqs; + volatile uint32_t disable_irqs[2]; + volatile uint32_t disable_basic_irqs; +} l2ic_reg_t; + +#define L2IC_REG ((l2ic_reg_t *)L2IC_REG_BASE) + +void l2ic_init(void) { + // No-op. +} + +uint32_t l2ic_get_pending_irq_0(void) { + const uint32_t result = L2IC_REG->irq_pending[0]; + + PERIPHERAL_READ_BARRIER(); + return result; +} + +void l2ic_enable_irq_0(uint32_t mask) { + PERIPHERAL_WRITE_BARRIER(); + + L2IC_REG->enable_irqs[0] |= mask; + + PERIPHERAL_READ_BARRIER(); +} diff --git a/lab5/c/src/drivers/mailbox.c b/lab5/c/src/drivers/mailbox.c new file mode 100644 index 000000000..b0a43570b --- /dev/null +++ b/lab5/c/src/drivers/mailbox.c @@ -0,0 +1,83 @@ +#include "oscos/drivers/mailbox.h" + +#include + +#include "oscos/drivers/board.h" + +#define MAILBOX_REG_BASE ((void *)((char *)PERIPHERAL_BASE + 0xb880)) + +typedef struct { + volatile uint32_t read_write; + const volatile uint32_t _reserved[3]; + volatile uint32_t peek; + volatile uint32_t sender; + volatile uint32_t status; + volatile uint32_t config; +} mailbox_reg_t; + +#define MAILBOX_REGS ((mailbox_reg_t *)MAILBOX_REG_BASE) + +#define MAILBOX_STATUS_EMPTY_MASK ((uint32_t)(1 << 30)) +#define MAILBOX_STATUS_FULL_MASK ((uint32_t)(1 << 31)) + +#define GET_BOARD_REVISION ((uint32_t)0x00010002) +#define GET_ARM_MEMORY ((uint32_t)0x00010005) +#define REQUEST_CODE ((uint32_t)0x00000000) +#define REQUEST_SUCCEED ((uint32_t)0x80000000) +#define REQUEST_FAILED ((uint32_t)0x80000001) +#define TAG_REQUEST_CODE ((uint32_t)0x00000000) +#define END_TAG ((uint32_t)0x00000000) + +void mailbox_init(void) { + // No-op. +} + +void mailbox_call(uint32_t message[], const unsigned char channel) { + PERIPHERAL_WRITE_BARRIER(); + + const uint32_t mailbox_write_data = (uint32_t)(uintptr_t)message | channel; + + while (MAILBOX_REGS[1].status & MAILBOX_STATUS_FULL_MASK) + ; + MAILBOX_REGS[1].read_write = mailbox_write_data; + + for (;;) { + while (MAILBOX_REGS[0].status & MAILBOX_STATUS_EMPTY_MASK) + ; + const uint32_t mailbox_read_data = MAILBOX_REGS[0].read_write; + + if (mailbox_write_data == mailbox_read_data) + break; + } + + PERIPHERAL_READ_BARRIER(); +} + +uint32_t mailbox_get_board_revision(void) { + alignas(16) uint32_t mailbox[7] = {7 * sizeof(uint32_t), + REQUEST_CODE, + GET_BOARD_REVISION, + 4, + TAG_REQUEST_CODE, + 0, + END_TAG}; + + mailbox_call(mailbox, MAILBOX_CHANNEL_PROPERTY_TAGS_ARM_TO_VC); + + return mailbox[5]; +} + +arm_memory_t mailbox_get_arm_memory(void) { + alignas(16) uint32_t mailbox[8] = {8 * sizeof(uint32_t), + REQUEST_CODE, + GET_ARM_MEMORY, + 8, + TAG_REQUEST_CODE, + 0, + 0, + END_TAG}; + + mailbox_call(mailbox, MAILBOX_CHANNEL_PROPERTY_TAGS_ARM_TO_VC); + + return (arm_memory_t){.base = mailbox[5], .size = mailbox[6]}; +} diff --git a/lab5/c/src/drivers/mini-uart.c b/lab5/c/src/drivers/mini-uart.c new file mode 100644 index 000000000..c23500049 --- /dev/null +++ b/lab5/c/src/drivers/mini-uart.c @@ -0,0 +1,130 @@ +#include "oscos/drivers/mini-uart.h" + +#include + +#include "oscos/drivers/aux.h" +#include "oscos/drivers/board.h" + +#define MINI_UART_REG_BASE ((void *)((char *)PERIPHERAL_BASE + 0x215040)) + +typedef struct { + volatile uint32_t io; + volatile uint32_t ier; + volatile uint32_t iir; + volatile uint32_t lcr; + volatile uint32_t mcr; + const volatile uint32_t lsr; + const volatile uint32_t msr; + volatile uint32_t scratch; + volatile uint32_t cntl; + const volatile uint32_t stat; + volatile uint32_t baud; +} mini_uart_reg_t; + +#define MINI_UART_REG ((mini_uart_reg_t *)MINI_UART_REG_BASE) + +// N. B. The interrupt bits specified in the datasheet are incorrect. The values +// listed here are correct. See [bcm2837-datasheet-errata]. +// [bcm2835-datasheet-errata]: https://elinux.org/BCM2835_datasheet_errata + +#define MINI_UART_IER_ENABLE_RECEIVE_INTERRUPT ((uint32_t)(1 << 0)) +#define MINI_UART_IER_ENABLE_TRANSMIT_INTERRUPT ((uint32_t)(1 << 1)) + +#define MINI_UART_LSR_DATA_READY ((uint32_t)(1 << 0)) +#define MINI_UART_LSR_TRANSMITTER_IDLE ((uint32_t)(1 << 6)) +#define MINI_UART_LSR_TRANSMITTER_EMPTY ((uint32_t)(1 << 5)) + +void mini_uart_init(void) { + // The initialization procedure is taken from + // https://oscapstone.github.io/labs/hardware/uart.html. + + // Enable mini UART. + aux_enable_mini_uart(); + + PERIPHERAL_WRITE_BARRIER(); + + // Disable TX and RX. + MINI_UART_REG->cntl = 0; + // Disable interrupt. + MINI_UART_REG->ier = 0; + // Set the data size to 8 bits. + // N. B. The datasheet [bcm2835-datasheet] incorrectly indicates that only bit + // 0 needs to be set. In fact, bits [1:0] need to be set to 3. See + // [bcm2835-datasheet-errata], #p14. + MINI_UART_REG->lcr = 3; + // Disable auto flow control. + MINI_UART_REG->mcr = 0; + // Set baud rate to 115200. + MINI_UART_REG->baud = 270; + // Clear the transmit and receive FIFOs. + MINI_UART_REG->iir = 6; + // Enable TX and RX. + MINI_UART_REG->cntl = 3; + + PERIPHERAL_READ_BARRIER(); +} + +int mini_uart_recv_byte_nonblock(void) { + int result; + + if (!(MINI_UART_REG->lsr & MINI_UART_LSR_DATA_READY)) { + result = -1; + goto end; + } + + result = (unsigned char)MINI_UART_REG->io; + +end: + PERIPHERAL_READ_BARRIER(); + return result; +} + +int mini_uart_send_byte_nonblock(const unsigned char b) { + PERIPHERAL_WRITE_BARRIER(); + + int result; + + if (!(MINI_UART_REG->lsr & MINI_UART_LSR_TRANSMITTER_EMPTY)) { + result = -1; + goto end; + } + + MINI_UART_REG->io = b; + result = 0; + +end: + PERIPHERAL_READ_BARRIER(); + return result; +} + +void mini_uart_enable_rx_interrupt(void) { + PERIPHERAL_WRITE_BARRIER(); + + MINI_UART_REG->ier |= MINI_UART_IER_ENABLE_RECEIVE_INTERRUPT; + + PERIPHERAL_READ_BARRIER(); +} + +void mini_uart_disable_rx_interrupt(void) { + PERIPHERAL_WRITE_BARRIER(); + + MINI_UART_REG->ier &= ~MINI_UART_IER_ENABLE_RECEIVE_INTERRUPT; + + PERIPHERAL_READ_BARRIER(); +} + +void mini_uart_enable_tx_interrupt(void) { + PERIPHERAL_WRITE_BARRIER(); + + MINI_UART_REG->ier |= MINI_UART_IER_ENABLE_TRANSMIT_INTERRUPT; + + PERIPHERAL_READ_BARRIER(); +} + +void mini_uart_disable_tx_interrupt(void) { + PERIPHERAL_WRITE_BARRIER(); + + MINI_UART_REG->ier &= ~MINI_UART_IER_ENABLE_TRANSMIT_INTERRUPT; + + PERIPHERAL_READ_BARRIER(); +} diff --git a/lab5/c/src/drivers/pm.c b/lab5/c/src/drivers/pm.c new file mode 100644 index 000000000..ca4a85fd1 --- /dev/null +++ b/lab5/c/src/drivers/pm.c @@ -0,0 +1,85 @@ +#include "oscos/drivers/pm.h" + +#include + +#include "oscos/drivers/board.h" + +// Information related to the PM registers can be retrieved from [bcm2835-regs], +// #PM. +// +// [bcm2835-regs]: https://elinux.org/BCM2835_registers#PM + +#define PM_REG_BASE ((void *)((char *)PERIPHERAL_BASE + 0x100000)) + +typedef struct { + volatile uint32_t gnric; + volatile uint32_t audio; + const volatile uint32_t _reserved0[4]; + const volatile uint32_t status; + volatile uint32_t rstc; + volatile uint32_t rsts; + volatile uint32_t wdot; + volatile uint32_t pads0; + volatile uint32_t pads2; + volatile uint32_t pads3; + volatile uint32_t pads4; + volatile uint32_t pads5; + volatile uint32_t pads6; + const volatile uint32_t _reserved1; + volatile uint32_t cam0; + volatile uint32_t cam1; + volatile uint32_t cpp2tx; + volatile uint32_t dsi0; + volatile uint32_t dsi1; + volatile uint32_t hdmi; + volatile uint32_t usb; + volatile uint32_t pxldo; + volatile uint32_t pxbg; + volatile uint32_t dft; + volatile uint32_t smps; + volatile uint32_t xosc; + volatile uint32_t sparew; + const volatile uint32_t sparer; + volatile uint32_t avs_rstdr; + volatile uint32_t avs_stat; + volatile uint32_t avs_event; + volatile uint32_t avs_inten; + const volatile uint32_t _reserved2[29]; + const volatile uint32_t dummy; + const volatile uint32_t _reserved3[2]; + volatile uint32_t image; + volatile uint32_t grafx; + volatile uint32_t proc; +} pm_reg_t; + +#define PM_REG ((pm_reg_t *)PM_REG_BASE) + +#define PM_PASSWORD 0x5a000000 + +void pm_init(void) { + // No-op. +} + +void pm_reset(const uint32_t tick) { + PERIPHERAL_WRITE_BARRIER(); + + PM_REG->rstc = PM_PASSWORD | 0x20; + PM_REG->wdot = PM_PASSWORD | tick; + + PERIPHERAL_READ_BARRIER(); +} + +void pm_cancel_reset(void) { + PERIPHERAL_WRITE_BARRIER(); + + PM_REG->rstc = PM_PASSWORD | 0; + PM_REG->wdot = PM_PASSWORD | 0; + + PERIPHERAL_READ_BARRIER(); +} + +noreturn void pm_reboot(void) { + pm_reset(1); + for (;;) + ; +} diff --git a/lab5/c/src/initrd.c b/lab5/c/src/initrd.c new file mode 100644 index 000000000..f215e76aa --- /dev/null +++ b/lab5/c/src/initrd.c @@ -0,0 +1,130 @@ +#include "oscos/initrd.h" + +#include "oscos/devicetree.h" + +static const void *_initrd_start, *_initrd_end; + +static bool _cpio_newc_is_header_field_valid(const char field[const static 8]) { + for (size_t i = 0; i < 8; i++) { + if (!(('0' <= field[i] && field[i] <= '9') || + ('A' <= field[i] && field[i] <= 'F'))) + return false; + } + return true; +} + +static bool _initrd_is_valid(void) { + const cpio_newc_entry_t *entry; + + // Cannot use INITRD_FOR_ENTRY here, since it will evaluate + // `CPIO_NEWC_IS_ENTRY_LAST(entry)` before `entry` is validated. + for (entry = INITRD_HEAD;; entry = CPIO_NEWC_NEXT_ENTRY(entry)) { + if (entry >= (cpio_newc_entry_t *)_initrd_end) + return false; + if (!(strncmp(entry->header.c_magic, "070701", 6) == 0 && + _cpio_newc_is_header_field_valid(entry->header.c_mode) && + _cpio_newc_is_header_field_valid(entry->header.c_uid) && + _cpio_newc_is_header_field_valid(entry->header.c_gid) && + _cpio_newc_is_header_field_valid(entry->header.c_nlink) && + _cpio_newc_is_header_field_valid(entry->header.c_mtime) && + _cpio_newc_is_header_field_valid(entry->header.c_filesize) && + _cpio_newc_is_header_field_valid(entry->header.c_devmajor) && + _cpio_newc_is_header_field_valid(entry->header.c_devminor) && + _cpio_newc_is_header_field_valid(entry->header.c_rdevmajor) && + _cpio_newc_is_header_field_valid(entry->header.c_rdevminor) && + _cpio_newc_is_header_field_valid(entry->header.c_namesize) && + _cpio_newc_is_header_field_valid(entry->header.c_check))) + return false; + + if (CPIO_NEWC_IS_ENTRY_LAST(entry)) + break; + } + + return CPIO_NEWC_NEXT_ENTRY(entry) <= (cpio_newc_entry_t *)_initrd_end; +} + +typedef struct { + bool start_done, end_done; +} initrd_init_dtb_traverse_callback_arg_t; + +static control_flow_t _initrd_init_dtb_traverse_callback( + initrd_init_dtb_traverse_callback_arg_t *const arg, + const fdt_item_t *const node, + const fdt_traverse_parent_list_node_t *const parent) { + if (parent && !parent->parent && + strcmp(FDT_NODE_NAME(node), "chosen") == 0) { // Current node is /chosen. + FDT_FOR_ITEM(node, item) { + if (FDT_TOKEN(item) == FDT_PROP) { + const fdt_prop_t *const prop = (const fdt_prop_t *)item->payload; + if (strcmp(FDT_PROP_NAME(prop), "linux,initrd-start") == 0) { + const uint32_t adr = rev_u32(*(const uint32_t *)FDT_PROP_VALUE(prop)); + _initrd_start = (const void *)(uintptr_t)adr; + arg->start_done = true; + } else if (strcmp(FDT_PROP_NAME(prop), "linux,initrd-end") == 0) { + const uint32_t adr = rev_u32(*(const uint32_t *)FDT_PROP_VALUE(prop)); + _initrd_end = (const void *)(uintptr_t)adr; + arg->end_done = true; + } + + if (arg->start_done && arg->end_done) + break; + } + } + return CF_BREAK; + } else { + return CF_CONTINUE; + } +} + +bool initrd_init(void) { + _initrd_start = NULL; + + if (devicetree_is_init()) { + // Discover the initrd loading address through the devicetree. + + initrd_init_dtb_traverse_callback_arg_t arg = {.start_done = false, + .end_done = false}; + fdt_traverse((fdt_traverse_callback_t *)_initrd_init_dtb_traverse_callback, + &arg); + if (!(arg.start_done && + arg.end_done)) { // Either the /chosen/linux,initrd-start or the + // /chosen/linux,initrd-end property is missing from + // the devicetree. + _initrd_start = NULL; + } + } + + // Validate the initial ramdisk. + if (!(_initrd_start && _initrd_is_valid())) { + _initrd_start = NULL; + } + + return _initrd_start; +} + +bool initrd_is_init(void) { return _initrd_start; } + +const void *initrd_get_start(void) { return _initrd_start; } + +const void *initrd_get_end(void) { return _initrd_end; } + +uint32_t cpio_newc_parse_header_field(const char field[static 8]) { + uint32_t result = 0; + for (size_t i = 0; i < 8; i++) { + const uint32_t digit_value = + '0' <= field[i] && field[i] <= '9' ? field[i] - '0' + : 'A' <= field[i] && field[i] <= 'F' ? field[i] - 'A' + 10 + : (__builtin_unreachable(), 0); + result = result << 4 | digit_value; + } + return result; +} + +const cpio_newc_entry_t *initrd_find_entry_by_pathname(const char *pathname) { + INITRD_FOR_ENTRY(entry) { + if (strcmp(CPIO_NEWC_PATHNAME(entry), pathname) == 0) { + return entry; + } + } + return NULL; +} diff --git a/lab5/c/src/libc/ctype.c b/lab5/c/src/libc/ctype.c new file mode 100644 index 000000000..6ff704afb --- /dev/null +++ b/lab5/c/src/libc/ctype.c @@ -0,0 +1,3 @@ +#include "oscos/libc/ctype.h" + +int isdigit(const int c) { return '0' <= c && c <= '9'; } diff --git a/lab5/c/src/libc/stdio.c b/lab5/c/src/libc/stdio.c new file mode 100644 index 000000000..766cbb465 --- /dev/null +++ b/lab5/c/src/libc/stdio.c @@ -0,0 +1,41 @@ +#include "oscos/libc/stdio.h" + +#include "oscos/utils/fmt.h" + +int snprintf(char str[const restrict], const size_t size, + const char *const restrict format, ...) { + va_list ap; + va_start(ap, format); + + const int result = vsnprintf(str, size, format, ap); + + va_end(ap); + return result; +} + +typedef struct { + char *str; + size_t size, len; +} snprintf_arg_t; + +static void _snprintf_putc(const unsigned char c, snprintf_arg_t *const arg) { + if (arg->size > 0 && arg->len < arg->size - 1) { + arg->str[arg->len++] = c; + } +} + +static void _snprintf_finalize(snprintf_arg_t *const arg) { + if (arg->size > 0) { + arg->str[arg->len] = '\0'; + } +} + +static const printf_vtable_t _snprintf_vtable = { + .putc = (void (*)(unsigned char, void *))_snprintf_putc, + .finalize = (void (*)(void *))_snprintf_finalize}; + +int vsnprintf(char str[const restrict], const size_t size, + const char *const restrict format, va_list ap) { + snprintf_arg_t arg = {.str = str, .size = size, .len = 0}; + return vprintf_generic(&_snprintf_vtable, &arg, format, ap); +} diff --git a/lab5/c/src/libc/stdlib/qsort.c b/lab5/c/src/libc/stdlib/qsort.c new file mode 100644 index 000000000..200755aa2 --- /dev/null +++ b/lab5/c/src/libc/stdlib/qsort.c @@ -0,0 +1,154 @@ +#include "oscos/libc/stdlib.h" + +#include "oscos/libc/string.h" + +#define INSERTION_SORT_THRESHOLD 16 + +// Insertion sort. + +/// \brief Insertion sort. +static void +_insertion_sort(void *const base, const size_t nmemb, const size_t size, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + for (size_t i = 1; i < nmemb; i++) { + for (size_t j = i; j > 0; j--) { + void *const pl = (char *)base + (j - 1) * size, *const pr = + (char *)pl + size; + if (compar(pl, pr, arg) < 0) + break; + memswp(pl, pr, size); + } + } +} + +// Quicksort. + +/// \brief The result of partitioning, indicating the pivot points. +typedef struct { + size_t eq_start; ///< The starting index of the part where the element + ///< compares equal to the pivot. + size_t gt_start; ///< The starting index of the part where the element + ///< compares greater than the pivot. +} partition_result_t; + +/// \brief Median-of-3 pivot selection. +/// +/// \return The index of the chosen pivot. +static size_t +_select_pivot(const void *const base, const size_t nmemb, const size_t size, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + if (nmemb == 0) + __builtin_unreachable(); + if (nmemb < 3) + return 0; + + const size_t imid = nmemb / 2, ilast = nmemb - 1; + const void *const mid = (char *)base + imid * size, *const last = + (char *)base + + ilast * size; + return compar(base, mid, arg) <= 0 ? compar(mid, last, arg) <= 0 ? imid + : compar(base, last, arg) <= 0 ? ilast + : 0 + : compar(mid, last, arg) > 0 ? imid + : compar(base, last, arg) <= 0 ? 0 + : ilast; +} + +/// \brief Three-way partitioning, using the first element as the pivot. +static partition_result_t +_partition(void *const base, const size_t nmemb, const size_t size, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + if (nmemb == 0) + __builtin_unreachable(); + + // Partition every element except for the first one (pivot). + + size_t il = 1, im = 1, ir = nmemb; + while (im < ir) { + void *const pl = (char *)base + il * size, + *const pm = (char *)base + im * size, + *const prm1 = (char *)base + (ir - 1) * size; + + const int compar_result = compar(pm, base, arg); + if (compar_result < 0) { + memswp(pl, pm, size); + il++; + im++; + } else if (compar_result > 0) { + memswp(pm, prm1, size); + ir--; + } else { + im++; + } + } + + // Move the pivot to its place. + + if (il != 0) { + memswp(base, (char *)base + --il * size, size); + } + + return (partition_result_t){.eq_start = il, .gt_start = im}; +} + +/// \brief Quicksort. +/// +/// A basic quicksort with median-of-3 pivot selection, three-way partitioning, +/// and insertion sort for small arrays. +static void _quicksort(void *const base, const size_t nmemb, const size_t size, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + if (nmemb <= INSERTION_SORT_THRESHOLD) { + _insertion_sort(base, nmemb, size, compar, arg); + return; + } + + // Select the pivot. + + const size_t pivot_ix = _select_pivot(base, nmemb, size, compar, arg); + + // Partition the array. + + if (pivot_ix != 0) { + memswp(base, (char *)base + pivot_ix * size, size); + } + + const partition_result_t partition_result = + _partition(base, nmemb, size, compar, arg); + + // Recursively sort the subarrays. + + _quicksort(base, partition_result.eq_start, size, compar, arg); + _quicksort((char *)base + partition_result.gt_start * size, + nmemb - partition_result.gt_start, size, compar, arg); +} + +// qsort. + +typedef struct { + int (*compar)(const void *, const void *); +} qsort_qsort_r_arg_t; + +static int _qsort_qsort_r_compar(const void *const x, const void *const y, + qsort_qsort_r_arg_t *const arg) { + return arg->compar(x, y); +} + +void qsort(void *const base, const size_t nmemb, const size_t size, + int (*const compar)(const void *, const void *)) { + qsort_qsort_r_arg_t arg = {.compar = compar}; + qsort_r(base, nmemb, size, + (int (*)(const void *, const void *, void *))_qsort_qsort_r_compar, + &arg); +} + +// qsort_r. + +void qsort_r(void *const base, const size_t nmemb, const size_t size, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + _quicksort(base, nmemb, size, compar, arg); +} diff --git a/lab5/c/src/libc/string.c b/lab5/c/src/libc/string.c new file mode 100644 index 000000000..b1f406f82 --- /dev/null +++ b/lab5/c/src/libc/string.c @@ -0,0 +1,99 @@ +#include "oscos/libc/string.h" + +__attribute__((used)) int memcmp(const void *const s1, const void *const s2, + const size_t n) { + const unsigned char *const s1_c = s1, *const s2_c = s2; + + for (size_t i = 0; i < n; i++) { + const int diff = (int)s1_c[i] - s2_c[i]; + if (diff != 0) + return diff; + } + + return 0; +} + +__attribute__((used)) void *memset(void *const s, const int c, const size_t n) { + unsigned char *const s_c = s; + + for (size_t i = 0; i < n; i++) { + s_c[i] = c; + } + + return s; +} + +static void __memmove_forward(unsigned char *const dest, + const unsigned char *const src, const size_t n) { + for (size_t i = 0; i < n; i++) { + dest[i] = src[i]; + } +} + +static void __memmove_backward(unsigned char *const dest, + const unsigned char *const src, const size_t n) { + for (size_t ip1 = n; ip1 > 0; ip1--) { + const size_t i = ip1 - 1; + dest[i] = src[i]; + } +} + +__attribute__((used)) void *memcpy(void *const restrict dest, + const void *const restrict src, + const size_t n) { + __memmove_forward(dest, src, n); + return dest; +} + +__attribute__((used)) void *memmove(void *const dest, const void *const src, + const size_t n) { + if (dest < src) { + __memmove_forward(dest, src, n); + } else if (dest > src) { + __memmove_backward(dest, src, n); + } + + return dest; +} + +int strcmp(const char *const s1, const char *const s2) { + for (const unsigned char *c1 = (const unsigned char *)s1, + *c2 = (const unsigned char *)s2; + *c1 || *c2; c1++, c2++) { + const int diff = (int)*c1 - *c2; + if (diff != 0) + return diff; + } + + return 0; +} + +int strncmp(const char *const s1, const char *const s2, const size_t n) { + size_t i = 0; + const unsigned char *c1 = (const unsigned char *)s1, + *c2 = (const unsigned char *)s2; + for (; i < n && (*c1 || *c2); i++, c1++, c2++) { + const int diff = (int)*c1 - *c2; + if (diff != 0) + return diff; + } + + return 0; +} + +size_t strlen(const char *s) { + size_t result = 0; + for (const char *c = s; *c; c++) { + result++; + } + return result; +} + +void memswp(void *const restrict xs, void *const restrict ys, const size_t n) { + unsigned char *const restrict xs_c = xs, *const restrict ys_c = ys; + for (size_t i = 0; i < n; i++) { + const unsigned char tmp = xs_c[i]; + xs_c[i] = ys_c[i]; + ys_c[i] = tmp; + } +} diff --git a/lab5/c/src/linker.ld b/lab5/c/src/linker.ld new file mode 100644 index 000000000..4c6bab1d0 --- /dev/null +++ b/lab5/c/src/linker.ld @@ -0,0 +1,77 @@ +/* +Memory map: (End addresses are exclusive) +0x 0 ( 0B) - 0x 1000 ( 4K): Reserved by firmware +0x 1000 ( 4K) - 0x 80000 (512K): Kernel heap +0x 80000 (512K) - 0x3ac00000 (940M): Kernel text, rodata, data, bss, heap +0x3ac00000 (940M) - 0x3b400000 (948M): Kernel stack +0x3b400000 (948M) - 0x3c000000 (960M): (QEMU only) Kernel heap +*/ + +_skernel = 0x80000; +_max_ekernel = 0x3ac00000; + +MEMORY +{ + RAM_KERNEL : ORIGIN = _skernel, LENGTH = _max_ekernel - _skernel +} + +/* Must be 16-byte aligned. + The highest address the ARM core can use. Total system SDRAM is 1G. Top 76M + (or 64M if on QEMU) are reserved for VideoCore. */ +_estack = 1024M - 76M; +_sstack = _estack - 8M; + +ENTRY(_start) + +SECTIONS +{ + .text : + { + _stext = .; + + *(.text._start) /* Entry point. See `start.S`. */ + *(.text.unlikely .text.*_unlikely .text.unlikely.*) + *(.text.startup .text.startup.*) + *(.text.hot .text.hot.*) + *(.text .text.*) + *(.eh_frame) + *(.eh_frame_hdr) + + _etext = .; + } >RAM_KERNEL + + .rodata : + { + _srodata = .; + + *(.rodata .rodata.*) + + _erodata = .; + } >RAM_KERNEL + + .data : + { + _sdata = .; + + *(.data .data.*) + + _edata = .; + } >RAM_KERNEL + + /* The .bss section is 16-byte aligned to allow the section to be + zero-initialized with the `stp` instruction without using unaligned + memory accesses. See `start.S`. */ + .bss : ALIGN(16) + { + _sbss = .; + + *(.bss .bss.*) + *(COMMON) + + . = ALIGN(16); + _ebss = .; + } >RAM_KERNEL + + _ekernel = .; + _sheap = ALIGN(16); +} diff --git a/lab5/c/src/main.c b/lab5/c/src/main.c new file mode 100644 index 000000000..3f501e33f --- /dev/null +++ b/lab5/c/src/main.c @@ -0,0 +1,58 @@ +#include "oscos/console.h" +#include "oscos/devicetree.h" +#include "oscos/drivers/aux.h" +#include "oscos/drivers/gpio.h" +#include "oscos/drivers/l1ic.h" +#include "oscos/drivers/l2ic.h" +#include "oscos/drivers/mailbox.h" +#include "oscos/drivers/pm.h" +#include "oscos/initrd.h" +#include "oscos/mem/malloc.h" +#include "oscos/mem/memmap.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/mem/startup-alloc.h" +#include "oscos/panic.h" +#include "oscos/shell.h" +#include "oscos/timer/timeout.h" +#include "oscos/xcpt.h" + +void main(const void *const dtb_start) { + // Initialize interrupt-related subsystems. + l1ic_init(); + l2ic_init(); + xcpt_set_vector_table(); + timeout_init(); + XCPT_UNMASK_ALL(); + + // Initialize the serial console. + gpio_init(); + aux_init(); + startup_alloc_init(); + console_init(); + + // Initialize the devicetree. + if (!devicetree_init(dtb_start)) { + console_puts("WARN: Devicetree blob is invalid."); + } + + // Initialize the initial ramdisk. + if (!initrd_init()) { + console_puts("WARN: Initial ramdisk is invalid."); + } + + // Initialize the memory allocators. + memmap_init(); + scan_mem(); + print_memmap(); + if (!check_memmap()) + PANIC("memmap: Memory map is not well-formed"); + page_alloc_init(); + malloc_init(); + + // Initialize miscellaneous subsystems. + mailbox_init(); + pm_init(); + + // Run the kernel-space shell. + run_shell(); +} diff --git a/lab5/c/src/mem/malloc.c b/lab5/c/src/mem/malloc.c new file mode 100644 index 000000000..95c02263d --- /dev/null +++ b/lab5/c/src/mem/malloc.c @@ -0,0 +1,326 @@ +// The design of the dynamic memory allocator resembles that of the slab +// allocator to some extent. However, compared to the slab allocator +// implementation in the Linux kernel [linux-slab-alloc], it only achieves the +// first of the three principle aims, namely, to help eliminate internal +// fragmentation. +// +// Each slab is backed by a single page. The first 32 bytes of the page are +// reserved for bookkeeping data, while the remaining area is split into +// equally-sized chunks that are units of allocation. There are different kinds +// of slabs for many different slot sizes. This allocator maintains free lists +// of slabs, one for each kind, chaining slabs of the same kind and with at +// least one available slot together. +// +// When the last reserved slot of a slab becomes available, the slab is +// immediately destroyed and the underlying page is returned to the page frame +// allocator. This design causes thrashing on certain allocation/deallocation +// patterns, but it keeps the code simple. +// +// Large allocation requests bypass the slab allocator and goes directly to the +// page frame allocator. The allocated memory is appropriately tagged so that +// void free(void *ptr) knows which memory allocator a memory is allocated with. +// An allocation request is considered large if the size is greater than 126 +// `max_align_t`, the maximum slot size that allows a slab to hold at least two +// slots. Indeed, if the request size is so large that a slab able to satisfy +// the request can only hold a single slot, then using the slab allocator offers +// no advantage at all. If the request size is even larger, then a slab with a +// large enough slot size will not be able to hold even a single slot. +// +// [linux-slab-alloc]: +// https://www.kernel.org/doc/gorman/html/understand/understand011.html + +#include "oscos/mem/malloc.h" + +#include +#include + +#include "oscos/libc/string.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/mem/vm.h" +#include "oscos/utils/math.h" + +/// \brief A node of a doubly-linked list. +typedef struct list_node_t { + struct list_node_t *prev, *next; +} list_node_t; + +/// \brief Slab metadata. +typedef struct { + /// \brief The number of slots. + uint8_t n_slots; + /// \brief The size of each slot in numbers of `max_align_t`. + uint8_t slot_size; +} slab_metadata_t; + +/// \brief Slab (or not). +typedef struct { + /// \brief Node of the free list. + /// + /// If `free_list_node.prev` is NULL, then this "slab" is in fact not a slab + /// but a memory allocated for a large allocation request. + /// + /// This field is put in the first position, so that obtaining a slab_t * from + /// a pointer to its `free_list_node` field is a no-op. + list_node_t free_list_node; + /// \brief Pointer to the head of the free list of the slabs of the same kind. + /// + /// This is used when the slab adds itself to the free list. + list_node_t *free_list_head; + /// \brief Metadata. + slab_metadata_t metadata; + /// \brief The number of reserved slots. + uint8_t n_slots_reserved; + /// \brief Bitset of reserved slots. + /// + /// The jth bit of `slots_reserved_bitset[i]` if set iff the i*64 + j slot is + /// reserved. + uint64_t slots_reserved_bitset[4]; + /// \brief The memory for the slots. + alignas(alignof(max_align_t)) unsigned char slots[]; +} slab_t; + +/// \brief Metadata of all supported types of slabs. +static const slab_metadata_t SLAB_METADATA[] = { + {.n_slots = 252, .slot_size = 1}, {.n_slots = 126, .slot_size = 2}, + {.n_slots = 84, .slot_size = 3}, {.n_slots = 63, .slot_size = 4}, + {.n_slots = 50, .slot_size = 5}, {.n_slots = 42, .slot_size = 6}, + {.n_slots = 36, .slot_size = 7}, {.n_slots = 31, .slot_size = 8}, + {.n_slots = 28, .slot_size = 9}, {.n_slots = 25, .slot_size = 10}, + {.n_slots = 22, .slot_size = 11}, {.n_slots = 21, .slot_size = 12}, + {.n_slots = 19, .slot_size = 13}, {.n_slots = 18, .slot_size = 14}, + {.n_slots = 16, .slot_size = 15}, {.n_slots = 15, .slot_size = 16}, + {.n_slots = 14, .slot_size = 18}, {.n_slots = 13, .slot_size = 19}, + {.n_slots = 12, .slot_size = 21}, {.n_slots = 11, .slot_size = 22}, + {.n_slots = 10, .slot_size = 25}, {.n_slots = 9, .slot_size = 28}, + {.n_slots = 8, .slot_size = 31}, {.n_slots = 7, .slot_size = 36}, + {.n_slots = 6, .slot_size = 42}, {.n_slots = 5, .slot_size = 50}, + {.n_slots = 4, .slot_size = 63}, {.n_slots = 3, .slot_size = 84}, + {.n_slots = 2, .slot_size = 126}}; + +/// \brief The number of slab types. +#define N_SLAB_TYPES (sizeof(SLAB_METADATA) / sizeof(slab_metadata_t)) + +/// \brief The threshold in numbers of `max_align_t` an allocation request whose +/// size is more than which is considered a large allocation request. +#define LARGE_ALLOC_THRESHOLD 126 + +/// \brief Free lists of slabs for each slab type. +static list_node_t _slab_free_lists[N_SLAB_TYPES]; + +/// \brief Gets the slab type ID (the index that can be used to index +/// `SLAB_METADATA` or the free list) from the size of the allocation +/// request. +/// +/// \param n_units The size of the allocation request in numbers of "allocation +/// units", i.e., `max_align_t`. +static size_t _get_slab_type_id(const size_t n_units) { + if (n_units == 0 || n_units > LARGE_ALLOC_THRESHOLD) + __builtin_unreachable(); + + return n_units <= 16 ? n_units - 1 : N_SLAB_TYPES + 1 - 252 / n_units; +} + +// Slab operations. + +/// \brief Adds a slab to its free list. +/// +/// The `free_list_head` field of \p slab must be initialized and \p slab must +/// not have been on any free list. +static void _add_slab_to_free_list(slab_t *const slab) { + list_node_t *const free_list_first_entry = slab->free_list_head->next; + slab->free_list_node.next = free_list_first_entry; + free_list_first_entry->prev = &slab->free_list_node; + slab->free_list_node.prev = slab->free_list_head; + slab->free_list_head->next = &slab->free_list_node; +} + +/// \brief Removes a slab from its free list. +/// +/// \p slab must have been on a free list. +static void _remove_slab_from_free_list(slab_t *const slab) { + slab->free_list_node.prev->next = slab->free_list_node.next; + slab->free_list_node.next->prev = slab->free_list_node.prev; +} + +/// \brief Allocates a new slab and adds it onto its free list. +static slab_t *_alloc_slab(const size_t slab_type_id) { + // Allocate space for the slab. + + const spage_id_t page = alloc_pages(0); + if (page < 0) + return NULL; + + slab_t *slab = (slab_t *)pa_to_kernel_va(page_id_to_pa(page)); + if (!slab) { + // We cannot accept a null slab pointer in this implementation due to the + // `free_list_node.prev` being used as a tag to identify memories for large + // allocation requests. Allocate a new page and return the previously + // allocated page to the page frame allocator. + // (In practice, this code path is never taken.) + + const spage_id_t another_page = alloc_pages(0); + free_pages(page); + if (another_page < 0) { + return NULL; + } + + slab = (slab_t *)pa_to_kernel_va(page_id_to_pa(another_page)); + } + + // Initialize the fields. + + slab->free_list_head = &_slab_free_lists[slab_type_id]; + slab->metadata = SLAB_METADATA[slab_type_id]; + slab->n_slots_reserved = 0; + memset(slab->slots_reserved_bitset, 0, sizeof(slab->slots_reserved_bitset)); + _add_slab_to_free_list(slab); + + return slab; +} + +/// \brief Gets a slab of the given type with at least one free slot. If there +/// is none, allocates a new one. +static slab_t *_get_or_alloc_slab(const size_t slab_type_id) { + if (_slab_free_lists[slab_type_id].next == + &_slab_free_lists[slab_type_id]) { // The free list is empty. + return _alloc_slab(slab_type_id); + } else { + list_node_t *const free_list_first_entry = + _slab_free_lists[slab_type_id].next; + return (slab_t *)((char *)free_list_first_entry - + offsetof(slab_t, free_list_node)); + } +} + +/// \brief Gets the index of the first free slot of the given slab. +/// +/// \p slab must have at least one free slot. +static size_t _get_first_free_slot_ix(const slab_t *const slab) { + for (size_t i = 0;; i++) { + uint64_t j; + __asm__ __volatile__("clz %0, %1" + : "=r"(j) + : "r"(~slab->slots_reserved_bitset[i])); + if (j != + 64) { // The (63-j)th bit of `slab->slots_reserved_bitset[i]` is clear. + return i * 64 + (63 - j); + } + } +} + +/// \brief Allocates a slot from the given slab. +/// +/// \p slab must have at least one free slot. +static void *_alloc_from_slab(slab_t *const slab) { + const size_t free_slot_ix = _get_first_free_slot_ix(slab); + + // Mark the `free_slot_ix`th slot as reserved. + + slab->n_slots_reserved++; + slab->slots_reserved_bitset[free_slot_ix / 64] |= 1 << (free_slot_ix % 64); + + // Remove itself from its free list if there are no free slots. + + if (slab->n_slots_reserved == slab->metadata.n_slots) { + _remove_slab_from_free_list(slab); + } + + return slab->slots + free_slot_ix * (slab->metadata.slot_size * 16); +} + +/// \brief Frees a slot to the given slab. +/// +/// \param slab The slab. +/// \param ptr The pointer to the slot. +static void _free_to_slab(slab_t *const slab, void *const ptr) { + const size_t slot_ix = + ((uintptr_t)ptr - (uintptr_t)slab) / (slab->metadata.slot_size * 16); + + // Adds the slab to its free list if it wasn't on its free list. + + if (slab->n_slots_reserved == slab->metadata.n_slots) { + _add_slab_to_free_list(slab); + } + + // Mark the slot as available. + + slab->n_slots_reserved--; + slab->slots_reserved_bitset[slot_ix / 64] &= ~(1 << (slot_ix % 64)); + + // Return the slab to the page frame allocator if it has no reserved slots. + + if (slab->n_slots_reserved == 0) { + _remove_slab_from_free_list(slab); + free_pages(pa_to_page_id(kernel_va_to_pa(slab))); + } +} + +// Large allocation. + +/// \brief Allocates memory using the large allocation mechanism. +/// +/// In principle, using this function to satisfy small allocation requests will +/// not cause problems (UB or kernel panic), but doing so wastes a lot of +/// memory. +/// +/// \param size The size of the allocation in bytes. +static void *_malloc_large(const size_t size) { + const size_t actual_size = alignof(max_align_t) + size; + const size_t n_pages = (actual_size + ((1 << PAGE_ORDER) - 1)) >> PAGE_ORDER; + const size_t block_order = clog2(n_pages); + + spage_id_t page = alloc_pages(block_order); + if (page < 0) + return NULL; + + char *page_va = pa_to_kernel_va(page_id_to_pa(page)); + // Mark the "slab" as not a slab. + ((slab_t *)page_va)->free_list_node.prev = NULL; + + return page_va + alignof(max_align_t); +} + +/// \brief Frees memory allocated using void *_malloc_large(size_t). +/// \param slab_ptr The pointer to the "slab". (N.B. not the pointer returned by +/// void *_malloc_large(size_t)!) +static void _free_large(slab_t *const slab_ptr) { + free_pages(pa_to_page_id(kernel_va_to_pa(slab_ptr))); +} + +// Public functions. + +void malloc_init(void) { + // Initialize the free lists. (All free lists are initially empty.) + + for (size_t i = 0; i < N_SLAB_TYPES; i++) { + _slab_free_lists[i].prev = _slab_free_lists[i].next = &_slab_free_lists[i]; + } +} + +void *malloc(const size_t size) { + if (size == 0) + return NULL; + + const size_t n_units = (size + (alignof(max_align_t) - 1)) >> 4; + if (n_units > LARGE_ALLOC_THRESHOLD) + return _malloc_large(size); + + const size_t slab_type_id = _get_slab_type_id(n_units); + slab_t *const slab = _get_or_alloc_slab(slab_type_id); + if (!slab) + return NULL; + + return _alloc_from_slab(slab); +} + +void free(void *const ptr) { + if (!ptr) + return; + + slab_t *const ptr_s = (slab_t *)((uintptr_t)ptr & ~((1 << PAGE_ORDER) - 1)); + if (!ptr_s->free_list_node.prev) { // Not a slab. + _free_large(ptr_s); + } else { + _free_to_slab(ptr_s, ptr); + } +} diff --git a/lab5/c/src/mem/memmap.c b/lab5/c/src/mem/memmap.c new file mode 100644 index 000000000..8c9ee1ea2 --- /dev/null +++ b/lab5/c/src/mem/memmap.c @@ -0,0 +1,316 @@ +#include "oscos/mem/memmap.h" + +#include "oscos/console.h" +#include "oscos/devicetree.h" +#include "oscos/initrd.h" +#include "oscos/libc/stdlib.h" +#include "oscos/panic.h" + +#define MAX_N_MEMS 1 +#define MAX_N_RESERVED_MEMS 8 + +// Symbols defined in the linker script. +extern char _skernel[], _ekernel[], _sstack[], _estack[]; + +static pa_range_t _mems[MAX_N_MEMS]; +static size_t _n_mems = 0; +static reserved_mem_entry_t _reserved_mems[MAX_N_RESERVED_MEMS]; +static size_t _n_reserved_mems = 0; + +static int _mem_range_cmp_by_start(const pa_range_t *const r1, + const pa_range_t *const r2) { + if (r1->start < r2->start) + return -1; + if (r1->start > r2->start) + return 1; + return 0; +} + +static int +_reserved_mem_entry_cmp_by_start(const reserved_mem_entry_t *const r1, + const reserved_mem_entry_t *const r2) { + return _mem_range_cmp_by_start(&r1->range, &r2->range); +} + +void memmap_init(void) { + // No-op. +} + +bool mem_add(const pa_range_t mem) { + if (_n_mems == MAX_N_MEMS) + return false; + + _mems[_n_mems++] = mem; + return true; +} + +void mem_sort(void) { + qsort(_mems, _n_mems, sizeof(pa_range_t), + (int (*)(const void *, const void *))_mem_range_cmp_by_start); +} + +size_t mem_get_n(void) { return _n_mems; } + +const pa_range_t *mem_get(void) { return _mems; } + +bool reserved_mem_add(const reserved_mem_entry_t reserved_mem) { + if (_n_reserved_mems == MAX_N_RESERVED_MEMS) + return false; + + _reserved_mems[_n_reserved_mems++] = reserved_mem; + return true; +} + +void reserved_mem_sort(void) { + qsort(_reserved_mems, _n_reserved_mems, sizeof(reserved_mem_entry_t), + (int (*)(const void *, const void *))_reserved_mem_entry_cmp_by_start); +} + +size_t reserved_mem_get_n(void) { return _n_reserved_mems; } + +const reserved_mem_entry_t *reserved_mem_get(void) { return _reserved_mems; } + +#define ADD_MEM_OR_PANIC(ENTRY) \ + do { \ + if (!mem_add(ENTRY)) \ + PANIC("memmap: Ran out of space for memory entries"); \ + } while (0) + +#define RESERVE_OR_PANIC(ENTRY) \ + do { \ + if (!reserved_mem_add(ENTRY)) \ + PANIC("memmap: Ran out of space for reserved memory entries"); \ + } while (0) + +typedef struct { + bool is_memory_done, is_reserved_memory_done; + fdt_n_address_size_cells_t root_n_cells, reserved_memory_n_cells; + const fdt_item_t *reserved_memory_node; +} scan_mem_fdt_traverse_callback_arg_t; + +static control_flow_t _scan_mem_fdt_traverse_callback( + scan_mem_fdt_traverse_callback_arg_t *arg, const fdt_item_t *const node, + const fdt_traverse_parent_list_node_t *const parent) { + if (!parent) { // Current node is the root node. + arg->root_n_cells = fdt_get_n_address_size_cells(node); + } else if (parent && !parent->parent && + strncmp(FDT_NODE_NAME(node), "memory@", 7) == + 0) { // Current node is /memory@... + FDT_FOR_ITEM(node, item) { + if (FDT_TOKEN(item) == FDT_PROP) { + const fdt_prop_t *const prop = (const fdt_prop_t *)item->payload; + if (strcmp(FDT_PROP_NAME(prop), "reg") == 0) { + const fdt_read_reg_result_t read_result = + fdt_read_reg(prop, arg->root_n_cells); + const uint32_t start = read_result.value.address, + end = start + read_result.value.size; + if (read_result.address_overflow || read_result.size_overflow || + read_result.value.address > UINT32_MAX || + read_result.value.size > UINT32_MAX || end < start) + PANIC("memmap: reg property value overflow in devicetree node %s", + FDT_NODE_NAME(node)); + ADD_MEM_OR_PANIC(((pa_range_t){.start = start, .end = end})); + break; + } + } + } + arg->is_memory_done = true; + } else if (parent && !parent->parent && + strcmp(FDT_NODE_NAME(node), "reserved-memory") == + 0) { // Current node is /reserved-memory. + arg->reserved_memory_node = node; + arg->reserved_memory_n_cells = fdt_get_n_address_size_cells(node); + } else if (arg->reserved_memory_node) { // The /reserved-memory node has been + // traversed. + if (parent->node == + arg->reserved_memory_node) { // Current node is /reserved-memory/... + FDT_FOR_ITEM(node, item) { + if (FDT_TOKEN(item) == FDT_PROP) { + const fdt_prop_t *const prop = (const fdt_prop_t *)item->payload; + if (strcmp(FDT_PROP_NAME(prop), "reg") == 0) { + const fdt_read_reg_result_t read_result = + fdt_read_reg(prop, arg->root_n_cells); + const uint32_t start = read_result.value.address, + end = start + read_result.value.size; + if (read_result.address_overflow || read_result.size_overflow || + read_result.value.address > UINT32_MAX || + read_result.value.size > UINT32_MAX || end < start) + PANIC("memmap: reg property value overflow in devicetree node %s", + FDT_NODE_NAME(node)); + RESERVE_OR_PANIC( + ((reserved_mem_entry_t){.range = {.start = start, .end = end}, + .purpose = RMP_FIRMWARE})); + break; + } + } + } + } else { // All children of the /reserved-memory node has been traversed. + arg->reserved_memory_node = NULL; + arg->is_reserved_memory_done = true; + } + } + + return arg->is_memory_done && arg->is_reserved_memory_done ? CF_BREAK + : CF_CONTINUE; +} + +void scan_mem(void) { + if (devicetree_is_init()) { + // Use the devicetree to discover usable and reserved memory regions. + + // Devicetree. + RESERVE_OR_PANIC(((reserved_mem_entry_t){ + .range = {.start = (pa_t)(uintptr_t)fdt_get_start(), + .end = (pa_t)(uintptr_t)fdt_get_end()}, + .purpose = RMP_DTB})); + + // Anything in the memory reservation block. + for (const fdt_reserve_entry_t *reserve_entry = FDT_START_MEM_RSVMAP; + !(reserve_entry->address == 0 && reserve_entry->size == 0); + reserve_entry++) { + const pa_t start = rev_u64(reserve_entry->address), + end = start + rev_u64(reserve_entry->size); + RESERVE_OR_PANIC(((reserved_mem_entry_t){ + .range = {.start = start, .end = end}, .purpose = RMP_FIRMWARE})); + } + + // - Usable memory region. + // - Spin tables for multicore boot. + + scan_mem_fdt_traverse_callback_arg_t arg = {.is_memory_done = false, + .is_reserved_memory_done = + false, + .reserved_memory_node = NULL}; + fdt_traverse((fdt_traverse_callback_t *)_scan_mem_fdt_traverse_callback, + &arg); + } else { + // Use hardcoded values as a fallback. + + // Usable memory region. + ADD_MEM_OR_PANIC(((pa_range_t){.start = 0x0, .end = 0x3b400000})); + + // Spin tables for multicore boot. + RESERVE_OR_PANIC(((reserved_mem_entry_t){ + .range = {.start = 0x0, .end = 0x1000}, .purpose = RMP_FIRMWARE})); + } + + // Kernel image in the physical memory. + RESERVE_OR_PANIC( + ((reserved_mem_entry_t){.range = {.start = (pa_t)(uintptr_t)_skernel, + .end = (pa_t)(uintptr_t)_ekernel}, + .purpose = RMP_KERNEL})); + + // Initial ramdisk. + if (initrd_is_init()) { + RESERVE_OR_PANIC(((reserved_mem_entry_t){ + .range = {.start = (pa_t)(uintptr_t)initrd_get_start(), + .end = (pa_t)(uintptr_t)initrd_get_end()}, + .purpose = RMP_INITRD})); + } + + // Kernel stack. + RESERVE_OR_PANIC( + ((reserved_mem_entry_t){.range = {.start = (pa_t)(uintptr_t)_sstack, + .end = (pa_t)(uintptr_t)_estack}, + .purpose = RMP_STACK})); + + // Sort the memory regions. + mem_sort(); + reserved_mem_sort(); +} + +bool check_memmap(void) { + size_t ir = 0; + for (size_t im = 0; im < _n_mems; im++) { + if (im != 0 && + _mems[im - 1].end > _mems[im].start) { // Usable memory region overlaps + // with the previous one. + return false; + } + + if (ir < _n_reserved_mems && + _reserved_mems[ir].range.start < + _mems[im].start) { // A reserved memory region lies outside all + // usable memory regions. + return false; + } + + for (; + ir < _n_reserved_mems && _reserved_mems[ir].range.end <= _mems[im].end; + ir++) { + if (ir != 0 && _reserved_mems[ir - 1].range.end > + _reserved_mems[ir] + .range.start) { // Reserved memory region overlaps + // with the previous one. + return false; + } + } + } + + if (ir != _n_reserved_mems) { // A reserved memory region lies outside all + // usable memory regions. + return false; + } + + return true; +} + +void print_memmap(void) { + console_puts("Memory map:"); + + size_t ir = 0; + for (size_t im = 0; im < _n_mems; im++) { + console_printf(" Memory region %zu: 0x%8" PRIxPA " - 0x%8" PRIxPA "\n", im, + _mems[im].start, _mems[im].end); + + pa_t curr_pa = _mems[im].start; + + for (; + ir < _n_reserved_mems && _reserved_mems[ir].range.end <= _mems[im].end; + ir++) { + if (curr_pa != _reserved_mems[ir].range.start) { + console_printf(" 0x%8" PRIxPA " - 0x%8" PRIxPA ": Usable\n", curr_pa, + _reserved_mems[ir].range.start); + } + + const char *const purpose_str = + _reserved_mems[ir].purpose == RMP_FIRMWARE ? "firmware" + : _reserved_mems[ir].purpose == RMP_KERNEL ? "kernel" + : _reserved_mems[ir].purpose == RMP_INITRD ? "initrd" + : _reserved_mems[ir].purpose == RMP_DTB ? "dtb" + : _reserved_mems[ir].purpose == RMP_STACK ? "stack" + : "unknown"; + console_printf(" 0x%8" PRIxPA " - 0x%8" PRIxPA ": Reserved: %s\n", + _reserved_mems[ir].range.start, + _reserved_mems[ir].range.end, purpose_str); + + curr_pa = _reserved_mems[ir].range.end; + } + + if (curr_pa != _mems[im].end) { + console_printf(" 0x%8" PRIxPA " - 0x%8" PRIxPA ": Usable\n", curr_pa, + _mems[im].end); + } + } +} + +bool memmap_is_usable(const pa_range_t range) { + bool is_within_any_usable_range = false; + for (size_t i = 0; i < _n_mems; i++) { + if (_mems[i].start <= range.start && range.end <= _mems[i].end) { + is_within_any_usable_range = true; + break; + } + } + + if (!is_within_any_usable_range) + return false; + + for (size_t i = 0; i < _n_reserved_mems; i++) { + if (!(_reserved_mems[i].range.end <= range.start || + range.end <= _reserved_mems[i].range.start)) + return false; + } + + return true; +} diff --git a/lab5/c/src/mem/page-alloc.c b/lab5/c/src/mem/page-alloc.c new file mode 100644 index 000000000..e1b83b324 --- /dev/null +++ b/lab5/c/src/mem/page-alloc.c @@ -0,0 +1,416 @@ +// The page frame allocator uses the buddy system. Much of the design is based +// on that described in The Art of Computer Programming by Donald Knuth, +// section 2.5. +// +// The page frame allocator maintains the page frame array, an array of size +// equal to the number of page frames and entry type `page_frame_array_entry_t`, +// which tracks each block's reservation status and order. Unlike the design +// described in the specification [spec], not all entries have valid data. If +// page i starts a block of order k, regardless of the reservation status of the +// block, the entries in the index range [i+1:i+2^k] are not read and are thus +// left uninitialized. Unlike TAOCP's design, the order of a block is stored in +// the page frame array even if the block is reserved. (Note: TAOCP's +// reservation algorithm skips storing the order of the allocated block.) This +// design decision is because, unlike TAOCP's liberation algorithm, the +// void free_pages(page_id_t) function does not have access to the order of the +// block from the arguments and must instead retrieve this information from the +// page frame array. +// +// The page frame allocator also maintains free lists of blocks, one for each +// order. Each free list is a doubly-linked list with page frame array entries +// themselves as nodes. Thus, in addition to the reservation status and the +// order of the corresponding block, each page frame array entry also contains +// the index into the page frame array of the previous and the next node on the +// free list. This implementation adopts the technique described in TAOCP +// section 2.2.5 of using the list node type itself to store the head and tail +// pointers of the list(from now on referred to as "free list header"), +// simplifying list manipulation code. TAOCP's allocator design also uses this +// technique. Since the free list headers are necessarily outside of the page +// frame array, this technique requires that the indices mentioned above be able +// to refer to page frame array entries (list nodes) outside the page frame +// array. To solve this problem without adding complexity to the code, the free +// list headers and the page frame array are allocated together, with the former +// placed right before the latter, and we use negative array indices to refer to +// entries in the free list headers. +// +// [spec]: https://oscapstone.github.io/labs/lab4.html + +#include "oscos/mem/page-alloc.h" + +#include "oscos/console.h" +#include "oscos/mem/memmap.h" +#include "oscos/mem/startup-alloc.h" +#include "oscos/mem/vm.h" +#include "oscos/panic.h" + +// `MAX_BLOCK_ORDER` can be changed to any positive integer up to and +// including 25 without modification to the code. + +typedef struct { + bool is_avail : 1; + unsigned order : 5; + signed linkf : 26; + signed linkb : 32; +} page_frame_array_entry_t; + +static pa_t _pa_start; +static page_frame_array_entry_t *_page_frame_array, *_free_list; + +void page_alloc_init(void) { + const size_t n_usable_mems = mem_get_n(); + const pa_range_t *const usable_mems = mem_get(); + + if (n_usable_mems == 0) { + console_puts("WARN: page-alloc: No usable memory region."); + } + + // Determine the starting physical address. + + _pa_start = n_usable_mems > 0 ? usable_mems[0].start : 0; + + // Check the ending physical address. + + const pa_t max_supported_pa = + _pa_start + (1 << (PAGE_ORDER + MAX_BLOCK_ORDER)); + if (n_usable_mems > 0 && + usable_mems[n_usable_mems - 1].end > max_supported_pa) { + console_printf("WARN: page-alloc: End of usable memory region 0x%" PRIxPA + " is greater than maximum supported PA 0x%" PRIxPA ".\n", + usable_mems[n_usable_mems - 1].end, max_supported_pa); + } + + // Allocate the page frame array and the free list. + + page_frame_array_entry_t *const entries = + startup_alloc(((MAX_BLOCK_ORDER + 1) + (1 << MAX_BLOCK_ORDER)) * + sizeof(page_frame_array_entry_t)); + _page_frame_array = entries + (MAX_BLOCK_ORDER + 1); + _free_list = entries; + + // Check if the startup allocator allocated any memory the kernel shouldn't + // use. + + if (!memmap_is_usable( + kernel_va_range_to_pa_range(startup_alloc_get_alloc_range()))) + PANIC("page-alloc: Startup allocator allocated memory the kernel shouldn't" + "use"); + + // Initialize the page frame array. (The entire memory region is initially + // reserved.) + + _page_frame_array[0].is_avail = false; + _page_frame_array[0].order = MAX_BLOCK_ORDER; + + // Initialize the free list. (The free list is initially empty.) + + for (size_t order = 0; order <= MAX_BLOCK_ORDER; order++) { + _free_list[order].linkf = _free_list[order].linkb = + (int)order - (MAX_BLOCK_ORDER + 1); + } + + // Mark the usable regions as usable. + + for (size_t i = 0; i < n_usable_mems; i++) { + if (usable_mems[i].start >= max_supported_pa) + break; + + const bool end_cut = usable_mems[i].end >= max_supported_pa; + mark_pages(pa_range_to_page_id_range((pa_range_t){ + .start = usable_mems[i].start, + .end = end_cut ? max_supported_pa : usable_mems[i].end}), + true); + if (end_cut) + break; + } + + // Mark the reserved regions as reserved. + + const size_t n_reserved_mems = reserved_mem_get_n(); + const reserved_mem_entry_t *const reserved_mems = reserved_mem_get(); + + for (size_t i = 0; i < n_reserved_mems; i++) { + if (reserved_mems[i].range.start >= max_supported_pa) + break; + + const bool end_cut = reserved_mems[i].range.end >= max_supported_pa; + mark_pages( + pa_range_to_page_id_range((pa_range_t){ + .start = reserved_mems[i].range.start, + .end = end_cut ? max_supported_pa : reserved_mems[i].range.end}), + false); + if (end_cut) + break; + } + + // Mark the region used by the startup allocator as reserved. + + mark_pages(pa_range_to_page_id_range( + kernel_va_range_to_pa_range(startup_alloc_get_alloc_range())), + false); +} + +/// \brief Adds a block to the free list. +/// +/// \param page The page number of the first page of the block. +static void _add_block_to_free_list(const page_id_t page) { + const size_t order = _page_frame_array[page].order; + + const int32_t free_list_first_entry = _free_list[order].linkf; + _page_frame_array[page].linkf = free_list_first_entry; + _page_frame_array[free_list_first_entry].linkb = page; + _page_frame_array[page].linkb = (int)order - (MAX_BLOCK_ORDER + 1); + _free_list[order].linkf = page; +} + +/// \brief Removes a block from the free list. +/// +/// \param page The page number of the first page of the block. +static void _remove_block_from_free_list(const page_id_t page) { + _page_frame_array[_page_frame_array[page].linkb].linkf = + _page_frame_array[page].linkf; + _page_frame_array[_page_frame_array[page].linkf].linkb = + _page_frame_array[page].linkb; +} + +/// \brief Removes all free blocks within a page range that is valid as the page +/// range of a block from the free list. +/// +/// \param range The page range. Must be valid as the page range of a block. +/// \param order The order of the block of which \p range is valid as the page +/// range. I.e., log base 2 of the span of the range. +static void +_remove_free_blocks_in_range_from_free_list(const page_id_range_t range, + const size_t order) { + if (_page_frame_array[range.start].order == order) { + if (_page_frame_array[range.start].is_avail) { + _remove_block_from_free_list(range.start); + } + } else { + if (order == 0) + __builtin_unreachable(); + + // This will not cause integer overflow, since the maximum order is at most + // 25 and the node ID is at most 2²⁵. + const page_id_t mid = (range.start + range.end) / 2; + + _remove_free_blocks_in_range_from_free_list( + (page_id_range_t){.start = range.start, .end = mid}, order - 1); + _remove_free_blocks_in_range_from_free_list( + (page_id_range_t){.start = mid, .end = range.end}, order - 1); + } +} + +/// \brief Splits a block. +/// +/// If the block is initially free, this function updates the free lists +/// accordingly. +/// +/// \param page The page number of the first page of the block. +static void _split_block(const page_id_t page) { + if (_page_frame_array[page].order == 0) + __builtin_unreachable(); + +#ifdef PAGE_ALLOC_ENABLE_LOG + const page_id_t end_page = page + (1 << _page_frame_array[page].order); + console_printf("DEBUG: page-alloc: Splitting block 0x%" PRIxPAGEID + " - 0x%" PRIxPAGEID " of order %u.\n", + page, end_page, _page_frame_array[page].order); +#endif + + if (_page_frame_array[page].is_avail) { + _remove_block_from_free_list(page); + } + + const size_t order_p1 = _page_frame_array[page].order - 1; + const page_id_t buddy_page = page + (1 << order_p1); + + _page_frame_array[page].order = order_p1; + _page_frame_array[buddy_page].is_avail = _page_frame_array[page].is_avail; + _page_frame_array[buddy_page].order = order_p1; + + if (_page_frame_array[page].is_avail) { + _add_block_to_free_list(page); + _add_block_to_free_list(buddy_page); + } +} + +spage_id_t alloc_pages(const size_t order) { +#ifdef PAGE_ALLOC_ENABLE_LOG + console_printf("DEBUG: page-alloc: Allocating a block of order %zu\n", order); +#endif + + // Find block. + + size_t avail_block_order; + for (avail_block_order = order; avail_block_order <= MAX_BLOCK_ORDER; + avail_block_order++) { + if (_free_list[avail_block_order].linkf >= + 0) { // The free list is nonempty. + break; + } + } + + if (avail_block_order > + MAX_BLOCK_ORDER) { // No block of order >= `order` found. + return -1; + } + + const page_id_t page = _free_list[avail_block_order].linkf; + + // Remove from list. + + _remove_block_from_free_list(page); + + // Split. + + while (avail_block_order > order) { + // We could have used void _split_block(page_id_t) to perform block + // splitting, but the custom logic here avoids unnecessary free list + // operations (adding a block to the free list and then immediately removing + // it in the next loop iteration) that would have been performed by the said + // function. + +#ifdef PAGE_ALLOC_ENABLE_LOG + const page_id_t end_page = page + (1 << avail_block_order); + console_printf("DEBUG: page-alloc: Splitting block 0x%" PRIxPAGEID + " - 0x%" PRIxPAGEID " of order %zu.\n", + page, end_page, avail_block_order); +#endif + + avail_block_order--; + + const page_id_t buddy_page = page + (1 << avail_block_order); + + _page_frame_array[buddy_page].is_avail = true; + _page_frame_array[buddy_page].order = avail_block_order; + + // Add `buddy_page` to the free list, which is empty. + // We could have used void _add_block_to_free_list(page_id_t), but the + // custom logic here saves a few instructions. + _page_frame_array[buddy_page].linkf = _page_frame_array[buddy_page].linkb = + (int)avail_block_order - (MAX_BLOCK_ORDER + 1); + _free_list[avail_block_order].linkf = _free_list[avail_block_order].linkb = + buddy_page; + } + + _page_frame_array[page].is_avail = false; + _page_frame_array[page].order = order; + return page; +} + +void free_pages(const page_id_t page) { + const size_t order = _page_frame_array[page].order; + +#ifdef PAGE_ALLOC_ENABLE_LOG + const page_id_t end_page = page + (1 << order); + console_printf("DEBUG: page-alloc: Freeing block 0x%" PRIxPAGEID + " - 0x%" PRIxPAGEID " of order %zu\n", + page, end_page, order); +#endif + + // Combine with buddy. + + page_id_t curr_page = page; + size_t curr_order; + for (curr_order = order; curr_order < MAX_BLOCK_ORDER; curr_order++) { + const page_id_t buddy_page = curr_page ^ (1 << curr_order); + if (!(_page_frame_array[buddy_page].is_avail && + _page_frame_array[buddy_page].order == curr_order)) + break; + +#ifdef PAGE_ALLOC_ENABLE_LOG + const page_id_t end_curr_page = curr_page + (1 << curr_order); + const page_id_t end_buddy_page = buddy_page + (1 << curr_order); + console_printf( + "DEBUG: page-alloc: Combining block 0x%" PRIxPAGEID " - 0x%" PRIxPAGEID + " of order %zu with its buddy 0x%" PRIxPAGEID " - 0x%" PRIxPAGEID ".\n", + curr_page, end_curr_page, curr_order, buddy_page, end_buddy_page); +#endif + + _remove_block_from_free_list(buddy_page); + if (buddy_page < curr_page) { + curr_page = buddy_page; + } + } + + _page_frame_array[curr_page].is_avail = true; + _page_frame_array[curr_page].order = curr_order; + _add_block_to_free_list(curr_page); +} + +static void _mark_pages_rec(const page_id_range_t range, const bool is_avail, + const size_t order, + const page_id_range_t block_range) { + if (_page_frame_array[block_range.start].order == order && + _page_frame_array[block_range.start].is_avail == + is_avail) { // The entire block is already marked as the desired + // reservation status. + // No-op. + } else if (range.start == block_range.start && range.end == block_range.end) { + // Mark the entire block. + + _remove_free_blocks_in_range_from_free_list(range, order); + + _page_frame_array[range.start].is_avail = is_avail; + _page_frame_array[range.start].order = order; + if (is_avail) { + _add_block_to_free_list(range.start); + } + } else { // Recursive case. + if (order == 0) + __builtin_unreachable(); + + // Split the block if needed. + if (_page_frame_array[block_range.start].order == order) { + _split_block(block_range.start); + } + + // This will not cause integer overflow, since the maximum order is at most + // 25 and the node ID is at most 2²⁵. + const size_t mid = (block_range.start + block_range.end) / 2; + + if (range.end <= mid) { // The range lies entirely within the left half of + // the node range. + _mark_pages_rec( + range, is_avail, order - 1, + (page_id_range_t){.start = block_range.start, .end = mid}); + } else if (mid <= range.start) { // The range lies entirely within the right + // half of the node range. + _mark_pages_rec(range, is_avail, order - 1, + (page_id_range_t){.start = mid, .end = block_range.end}); + } else { // The range crosses the middle of the node range. + _mark_pages_rec( + (page_id_range_t){.start = range.start, .end = mid}, is_avail, + order - 1, (page_id_range_t){.start = block_range.start, .end = mid}); + _mark_pages_rec((page_id_range_t){.start = mid, .end = range.end}, + is_avail, order - 1, + (page_id_range_t){.start = mid, .end = block_range.end}); + } + } +} + +void mark_pages(const page_id_range_t range, const bool is_avail) { +#ifdef PAGE_ALLOC_ENABLE_LOG + console_printf("DEBUG: page-alloc: Marking pages 0x%" PRIxPAGEID + " - 0x%" PRIxPAGEID " as %s.\n", + range.start, range.end, is_avail ? "available" : "reserved"); +#endif + + // TODO: Switch to a non-recursive implementation. + _mark_pages_rec(range, is_avail, MAX_BLOCK_ORDER, + (page_id_range_t){.start = 0, .end = 1 << MAX_BLOCK_ORDER}); +} + +pa_t page_id_to_pa(const page_id_t page) { + return _pa_start + (page << PAGE_ORDER); +} + +page_id_t pa_to_page_id(const pa_t pa) { + return (pa - _pa_start) >> PAGE_ORDER; +} + +page_id_range_t pa_range_to_page_id_range(const pa_range_t range) { + return (page_id_range_t){ + .start = (range.start - _pa_start) >> PAGE_ORDER, + .end = ((range.end - _pa_start) + ((1 << PAGE_ORDER) - 1)) >> PAGE_ORDER}; +} diff --git a/lab5/c/src/mem/startup-alloc.c b/lab5/c/src/mem/startup-alloc.c new file mode 100644 index 000000000..16ff447a6 --- /dev/null +++ b/lab5/c/src/mem/startup-alloc.c @@ -0,0 +1,24 @@ +#include "oscos/mem/startup-alloc.h" + +#include + +#include "oscos/utils/align.h" + +// Symbol defined in the linker script. +extern char _sheap[]; + +static char *_next = _sheap; + +void startup_alloc_init(void) { + // No-op. +} + +void *startup_alloc(const size_t size) { + char *const result = (char *)ALIGN((uintptr_t)_next, alignof(max_align_t)); + _next = result + size; + return result; +} + +va_range_t startup_alloc_get_alloc_range(void) { + return (va_range_t){.start = _sheap, .end = _next}; +} diff --git a/lab5/c/src/mem/vm.c b/lab5/c/src/mem/vm.c new file mode 100644 index 000000000..28ad21c07 --- /dev/null +++ b/lab5/c/src/mem/vm.c @@ -0,0 +1,12 @@ +#include "oscos/mem/vm.h" + +#include + +pa_t kernel_va_to_pa(const void *const va) { return (pa_t)(uintptr_t)va; } + +void *pa_to_kernel_va(const pa_t pa) { return (void *)(uintptr_t)pa; } + +pa_range_t kernel_va_range_to_pa_range(const va_range_t range) { + return (pa_range_t){.start = kernel_va_to_pa(range.start), + .end = kernel_va_to_pa(range.end)}; +} diff --git a/lab5/c/src/panic.c b/lab5/c/src/panic.c new file mode 100644 index 000000000..89109ce74 --- /dev/null +++ b/lab5/c/src/panic.c @@ -0,0 +1,22 @@ +#include "oscos/panic.h" + +#include "oscos/console.h" + +void panic_begin(const char *const restrict file, const int line, + const char *const restrict format, ...) { + // Prints the panic message. + + va_list ap; + va_start(ap, format); + + console_printf("panic: %s:%d: ", file, line); + console_vprintf(format, ap); + console_putc('\n'); + + va_end(ap); + + // Park the core. + for (;;) { + __asm__ __volatile__("wfe"); + } +} diff --git a/lab5/c/src/shell.c b/lab5/c/src/shell.c new file mode 100644 index 000000000..b3e033a9f --- /dev/null +++ b/lab5/c/src/shell.c @@ -0,0 +1,332 @@ +#include "oscos/shell.h" + +#include + +#include "oscos/console.h" +#include "oscos/drivers/mailbox.h" +#include "oscos/drivers/pm.h" +#include "oscos/initrd.h" +#include "oscos/libc/ctype.h" +#include "oscos/libc/inttypes.h" +#include "oscos/libc/string.h" +#include "oscos/mem/malloc.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/timer/timeout.h" +#include "oscos/user-program.h" +#include "oscos/utils/time.h" + +#define MAX_CMD_LEN 78 + +static void _shell_print_prompt(void) { console_fputs("# "); } + +static size_t _shell_read_line(char *const buf, const size_t n) { + size_t cmd_len = 0; + + for (;;) { + const char c = console_getc(); + + if (c == '\n') { + console_putc('\n'); + break; + } else if (c == '\x7f') { // Backspace. + if (cmd_len > 0) { + console_fputs("\b \b"); + cmd_len--; + } + } else { + console_putc(c); + if (n > 0 && cmd_len < n - 1) { + buf[cmd_len] = c; + } + cmd_len++; + } + } + + if (n > 0) { + buf[cmd_len > n - 1 ? n - 1 : cmd_len] = '\0'; + } + + return cmd_len; +} + +static void _shell_do_cmd_help(void) { + console_puts( + "help : print this help menu\n" + "hello : print Hello World!\n" + "hwinfo : get the hardware's information by mailbox\n" + "reboot : reboot the device\n" + "ls : list all files in the initial ramdisk\n" + "cat : print the content of a file in the initial ramdisk\n" + "exec : run a user program in the initial ramdisk\n" + "setTimeout : print a message after a timeout\n" + "alloc-pages : allocates a block of page frames using the page frame " + "allocator\n" + "free-pages : frees a block of page frames allocated using the page " + "frame allocator"); +} + +static void _shell_do_cmd_hello(void) { console_puts("Hello World!"); } + +static void _shell_do_cmd_hwinfo(void) { + const uint32_t board_revision = mailbox_get_board_revision(); + const arm_memory_t arm_memory = mailbox_get_arm_memory(); + + console_printf("Board revision: 0x%" PRIx32 "\nARM memory: Base: 0x%" PRIx32 + ", Size: 0x%" PRIx32 "\n", + board_revision, arm_memory.base, arm_memory.size); +} + +noreturn static void _shell_do_cmd_reboot(void) { pm_reboot(); } + +static void _shell_do_cmd_ls(void) { + if (!initrd_is_init()) { + console_puts("oscsh: ls: initrd is invalid"); + return; + } + + INITRD_FOR_ENTRY(entry) { console_puts(CPIO_NEWC_PATHNAME(entry)); } +} + +static void _shell_do_cmd_cat(void) { + if (!initrd_is_init()) { + console_puts("oscsh: cat: initrd is invalid"); + return; + } + + console_fputs("Filename: "); + + char filename_buf[MAX_CMD_LEN + 1]; + _shell_read_line(filename_buf, MAX_CMD_LEN + 1); + + const cpio_newc_entry_t *const entry = + initrd_find_entry_by_pathname(filename_buf); + if (!entry) { + console_puts("oscsh: cat: no such file or directory"); + return; + } + + const uint32_t mode = CPIO_NEWC_HEADER_VALUE(entry, mode); + const uint32_t file_type = mode & CPIO_NEWC_MODE_FILE_TYPE_MASK; + if (file_type == CPIO_NEWC_MODE_FILE_TYPE_REG) { + console_write(CPIO_NEWC_FILE_DATA(entry), CPIO_NEWC_FILESIZE(entry)); + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_DIR) { + console_puts("oscsh: cat: is a directory"); + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_LNK) { + console_fputs("Symbolic link to: "); + console_write(CPIO_NEWC_FILE_DATA(entry), CPIO_NEWC_FILESIZE(entry)); + console_putc('\n'); + } else { + console_puts("oscsh: cat: unknown file type"); + } +} + +static void _shell_do_cmd_exec(void) { + if (!initrd_is_init()) { + console_puts("oscsh: exec: initrd is invalid"); + return; + } + + console_fputs("Filename: "); + + char filename_buf[MAX_CMD_LEN + 1]; + _shell_read_line(filename_buf, MAX_CMD_LEN + 1); + + const cpio_newc_entry_t *const entry = + initrd_find_entry_by_pathname(filename_buf); + if (!entry) { + console_puts("oscsh: exec: no such file or directory"); + return; + } + + const void *user_program_start; + size_t user_program_len; + + const uint32_t mode = CPIO_NEWC_HEADER_VALUE(entry, mode); + const uint32_t file_type = mode & CPIO_NEWC_MODE_FILE_TYPE_MASK; + if (file_type == CPIO_NEWC_MODE_FILE_TYPE_REG) { + user_program_start = CPIO_NEWC_FILE_DATA(entry); + user_program_len = CPIO_NEWC_FILESIZE(entry); + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_DIR) { + console_puts("oscsh: exec: is a directory"); + return; + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_LNK) { + console_fputs("oscos: exec: is a symbolic link to: "); + console_write(CPIO_NEWC_FILE_DATA(entry), CPIO_NEWC_FILESIZE(entry)); + console_putc('\n'); + return; + } else { + console_puts("oscsh: exec: unknown file type"); + return; + } + + if (!load_user_program(user_program_start, user_program_len)) { + console_puts("oscsh: exec: user program too long"); + return; + } + + run_user_program(); +} + +static void _shell_cmd_set_timeout_timer_callback(char *const message) { + console_puts(message); + free(message); +} + +static void _shell_do_cmd_set_timeout(const char *const args) { + const char *c = args; + + // Skip initial whitespaces. + for (; *c == ' '; c++) + ; + + // Message. + + if (!*c) + goto invalid; + + const char *const message_start = c; + size_t message_len = 0; + + for (; *c && *c != ' '; c++) { + message_len++; + } + + // Skip whitespaces. + for (; *c == ' '; c++) + ; + + // Seconds. + + if (!*c) + goto invalid; + + size_t seconds = 0; + for (; *c && *c != ' '; c++) { + if (!isdigit(*c)) + goto invalid; + seconds = seconds * 10 + (*c - '0'); + } + + // Skip whitespaces. + for (; *c == ' '; c++) + ; + + // There should be no more arguments. + if (*c) + goto invalid; + + // Copy the string. + + char *const message_copy = malloc(message_len + 1); + if (!message_copy) { + console_puts("oscsh: setTimeout: out of memory"); + return; + } + memcpy(message_copy, message_start, message_len); + message_copy[message_len] = '\0'; + + // Register the callback. + timeout_add_timer((void (*)(void *))_shell_cmd_set_timeout_timer_callback, + message_copy, seconds * NS_PER_SEC); + return; + +invalid: + console_puts("oscsh: setTimeout: invalid command format"); +} + +static void _shell_do_cmd_alloc_pages(void) { + console_fputs("Order (decimal, leave blank for 0): "); + + char digit_buf[3]; + const size_t digit_len = _shell_read_line(digit_buf, 3); + if (digit_len > 2) + goto invalid; + + size_t order = 0; + for (const char *c = digit_buf; *c; c++) { + if (!isdigit(*c)) + goto invalid; + order = order * 10 + (*c - '0'); + } + + const spage_id_t page = alloc_pages(order); + if (page < 0) { + console_puts("oscsh: alloc-pages: out of memory"); + return; + } + + console_printf("Page ID: 0x%" PRIxPAGEID "\n", (page_id_t)page); + return; + +invalid: + console_puts("oscsh: alloc-pages: invalid order"); +} + +static void _shell_do_cmd_free_pages(void) { + console_fputs( + "Page number of the first page (lowercase hexadecimal without prefix): "); + + char digit_buf[9]; + const size_t digit_len = _shell_read_line(digit_buf, 9); + if (digit_len > 8) + goto invalid; + + page_id_t page_id = 0; + for (const char *c = digit_buf; *c; c++) { + page_id_t digit_value; + if (isdigit(*c)) { + digit_value = *c - '0'; + } else if ('a' <= *c && *c <= 'f') { + digit_value = *c - 'a' + 10; + } else { + goto invalid; + } + page_id = page_id << 4 | digit_value; + } + + free_pages(page_id); + return; + +invalid: + console_puts("oscsh: free-pages: invalid page number"); +} + +static void _shell_cmd_not_found(const char *const cmd) { + console_printf("oscsh: %s: command not found\n", cmd); +} + +void run_shell(void) { + for (;;) { + _shell_print_prompt(); + + char cmd_buf[MAX_CMD_LEN + 1]; + const size_t cmd_len = _shell_read_line(cmd_buf, MAX_CMD_LEN + 1); + + if (cmd_len == 0) { + // No-op. + } else if (strcmp(cmd_buf, "help") == 0) { + _shell_do_cmd_help(); + } else if (strcmp(cmd_buf, "hello") == 0) { + _shell_do_cmd_hello(); + } else if (strcmp(cmd_buf, "hwinfo") == 0) { + _shell_do_cmd_hwinfo(); + } else if (strcmp(cmd_buf, "reboot") == 0) { + _shell_do_cmd_reboot(); + } else if (strcmp(cmd_buf, "ls") == 0) { + _shell_do_cmd_ls(); + } else if (strcmp(cmd_buf, "cat") == 0) { + _shell_do_cmd_cat(); + } else if (strcmp(cmd_buf, "exec") == 0) { + _shell_do_cmd_exec(); + } else if (strncmp(cmd_buf, "setTimeout", 10) == 0 && + (!cmd_buf[10] || cmd_buf[10] == ' ')) { + _shell_do_cmd_set_timeout(cmd_buf + 10); + } else if (strcmp(cmd_buf, "alloc-pages") == 0) { + _shell_do_cmd_alloc_pages(); + } else if (strcmp(cmd_buf, "free-pages") == 0) { + _shell_do_cmd_free_pages(); + } else { + _shell_cmd_not_found(cmd_buf); + } + } +} diff --git a/lab5/c/src/start.S b/lab5/c/src/start.S new file mode 100644 index 000000000..26fe14ecb --- /dev/null +++ b/lab5/c/src/start.S @@ -0,0 +1,65 @@ +.section ".text._start" + +_start: + // Enable FP/SIMD. + mov x1, 0x300000 + msr cptr_el2, x1 + + // Switch to EL1. + + // - Use AArch64 in EL1. + // - Trap nothing to EL2. + // - Disable interrupt routing to EL2. + // - Disable stage 2 address translation. + mov x1, 0x80000000 + msr hcr_el2, x1 + + // - Mask all interrupts. + // - AArch64 execution state. + // - EL1h. (Uses SP_EL1). + mov x1, 0x3c5 + msr spsr_el2, x1 + + adr x1, .Lin_el1 + msr elr_el2, x1 + eret + +.Lin_el1: + + // Enable FP/SIMD. + mov x1, 0x300000 + msr cpacr_el1, x1 + + // Clear the .bss section. + + ldr x1, =_sbss + ldr x2, =_ebss + b .Lclear_bss_loop_test + +.Lclear_bss_loop_body: + // This does not generate unaligned memory accesses, since the .bss section + // is 16-byte aligned. See `linker.ld`. + stp xzr, xzr, [x1], 16 + +.Lclear_bss_loop_test: + cmp x1, x2 + b.ne .Lclear_bss_loop_body + + // Set the stack pointer. + + ldr x1, =_estack + mov sp, x1 + + // Call the main function. + + bl main + + // Park the core. + +.Lpark_loop: + wfe + b .Lpark_loop + +.size _start, . - _start +.type _start, function +.global _start diff --git a/lab5/c/src/timer/delay.c b/lab5/c/src/timer/delay.c new file mode 100644 index 000000000..a3468f3f4 --- /dev/null +++ b/lab5/c/src/timer/delay.c @@ -0,0 +1,15 @@ +#include "oscos/timer/delay.h" + +#include + +#include "oscos/timer/timeout.h" +#include "oscos/utils/suspend.h" + +static void _timeout_callback(volatile bool *const flag) { *flag = true; } + +void delay_ns(const uint64_t ns) { + volatile bool flag = false; + timeout_add_timer((void (*)(void *))_timeout_callback, (void *)&flag, ns); + + WFI_WHILE(!flag); +} diff --git a/lab5/c/src/timer/timeout.c b/lab5/c/src/timer/timeout.c new file mode 100644 index 000000000..8bc77a7e1 --- /dev/null +++ b/lab5/c/src/timer/timeout.c @@ -0,0 +1,108 @@ +#include "oscos/timer/timeout.h" + +#include + +#include "oscos/drivers/l1ic.h" +#include "oscos/utils/core-id.h" +#include "oscos/utils/critical-section.h" +#include "oscos/utils/heapq.h" +#include "oscos/utils/time.h" +#include "oscos/xcpt.h" + +#define MAX_N_TIMEOUT_ENTRIES 16 + +typedef struct { + uint64_t timestamp; + void (*callback)(void *); + void *arg; +} timeout_entry_t; + +static timeout_entry_t _timeout_entries[MAX_N_TIMEOUT_ENTRIES]; +static size_t _n_timeout_entries = 0; + +static int _timeout_entry_cmp_by_timestamp(const timeout_entry_t *const e1, + const timeout_entry_t *const e2, + void *const _arg) { + (void)_arg; + + if (e1->timestamp < e2->timestamp) + return -1; + if (e1->timestamp > e2->timestamp) + return 1; + return 0; +} + +void timeout_init(void) { + __asm__ __volatile__("msr cntp_ctl_el0, %0" : : "r"(0x3)); + l1ic_enable_core_timer_irq(get_core_id()); +} + +bool timeout_add_timer(void (*const callback)(void *), void *const arg, + const uint64_t after_ns) { + if (_n_timeout_entries == MAX_N_TIMEOUT_ENTRIES) + return false; + + uint64_t core_timer_freq_hz; + __asm__("mrs %0, cntfrq_el0" : "=r"(core_timer_freq_hz)); + core_timer_freq_hz &= 0xffffffff; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + uint64_t curr_timestamp; + __asm__ __volatile__("mrs %0, cntpct_el0" : "=r"(curr_timestamp)); + + // ceil(after_ns * core_timer_freq_hz / NS_PER_SEC). + const uint64_t delta_timestamp = + (after_ns * core_timer_freq_hz + (NS_PER_SEC - 1)) / NS_PER_SEC; + const uint64_t timestamp = curr_timestamp + delta_timestamp; + + // Reprogram the timer. + + const bool need_timer_reprogramming = + _n_timeout_entries == 0 || timestamp < _timeout_entries[0].timestamp; + if (need_timer_reprogramming) { + __asm__ __volatile__("msr cntp_cval_el0, %0" : : "r"(timestamp)); + } + + // Insert the new timeout entry into the timeout queue. + + timeout_entry_t entry = { + .timestamp = timestamp, .callback = callback, .arg = arg}; + heappush(_timeout_entries, _n_timeout_entries++, sizeof(timeout_entry_t), + &entry, + (int (*)(const void *, const void *, + void *))_timeout_entry_cmp_by_timestamp, + NULL); + + // Enable the core timer interrupt. (ENABLE = 1, IMASK = 0) + __asm__ __volatile__("msr cntp_ctl_el0, %0" : : "r"(0x1)); + + CRITICAL_SECTION_LEAVE(daif_val); + + return true; +} + +void xcpt_core_timer_interrupt_handler(void) { + // Remove the top entry from the timeout queue. + + timeout_entry_t top_entry; + heappop(_timeout_entries, _n_timeout_entries--, sizeof(timeout_entry_t), + &top_entry, + (int (*)(const void *, const void *, + void *))_timeout_entry_cmp_by_timestamp, + NULL); + + // Execute the callback. + top_entry.callback(top_entry.arg); + + // Reprogram the timer. + if (_n_timeout_entries == 0) { + // Disable the core timer interrupt. (ENABLE = 1, IMASK = 1) + __asm__ __volatile__("msr cntp_ctl_el0, %0" : : "r"(0x3)); + } else { + __asm__ __volatile__("msr cntp_cval_el0, %0" + : + : "r"(_timeout_entries[0].timestamp)); + } +} diff --git a/lab5/c/src/user-program.c b/lab5/c/src/user-program.c new file mode 100644 index 000000000..7f117a759 --- /dev/null +++ b/lab5/c/src/user-program.c @@ -0,0 +1,82 @@ +#include "oscos/user-program.h" + +#include "oscos/console.h" +#include "oscos/libc/string.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/mem/vm.h" +#include "oscos/timer/timeout.h" +#include "oscos/utils/math.h" +#include "oscos/utils/time.h" + +#define USER_STACK_ORDER 23 // 8MB. +#define USER_STACK_BLOCK_ORDER (USER_STACK_ORDER - PAGE_ORDER) + +static void *_user_program_start, *_user_stack_end; + +bool load_user_program(const void *const start, const size_t len) { + // Allocate page frames for the user program. + + const size_t user_program_n_pages = + (len + ((1 << PAGE_ORDER) - 1)) >> PAGE_ORDER; + const size_t user_program_block_order = clog2(user_program_n_pages); + const spage_id_t user_program_page_id = alloc_pages(user_program_block_order); + if (user_program_page_id < 0) { // Out of memory. + return false; + } + _user_program_start = pa_to_kernel_va(page_id_to_pa(user_program_page_id)); + + // Allocate page frames for the user stack. + + const spage_id_t user_stack_page_id = alloc_pages(USER_STACK_BLOCK_ORDER); + if (user_stack_page_id < 0) { // Out of memory. + free_pages(user_program_page_id); + return false; + } + _user_stack_end = (char *)pa_to_kernel_va(page_id_to_pa(user_stack_page_id)) + + (1 << USER_STACK_ORDER); + + // Copy the user program. + + memcpy(_user_program_start, start, len); + return true; +} + +static void _core_timer_el0_interrupt_handler(void *const _arg) { + (void)_arg; + + uint64_t core_timer_freq; + __asm__ __volatile__("mrs %0, cntfrq_el0" : "=r"(core_timer_freq)); + core_timer_freq &= 0xffffffff; + + // Print the number of seconds since boot. + + uint64_t timer_val; + __asm__ __volatile__("mrs %0, cntpct_el0" : "=r"(timer_val)); + + console_printf("# seconds since boot: %u\n", + (unsigned)(timer_val / core_timer_freq)); + + // Set the time to interrupt to 2 seconds later. + + timeout_add_timer(_core_timer_el0_interrupt_handler, NULL, 2 * NS_PER_SEC); +} + +void run_user_program(void) { + // We don't need to synchronize the data cache and the instruction cache since + // we haven't enabled any of them. Nonetheless, we still have to synchronize + // the fetched instruction stream using the `isb` instruction. + __asm__ __volatile__("isb"); + + timeout_add_timer(_core_timer_el0_interrupt_handler, NULL, 2 * NS_PER_SEC); + + // - Unask all interrupts. + // - AArch64 execution state. + // - EL0t. + __asm__ __volatile__("msr spsr_el1, xzr"); + + __asm__ __volatile__("msr elr_el1, %0" : : "r"(_user_program_start)); + __asm__ __volatile__("msr sp_el0, %0" : : "r"(_user_stack_end)); + __asm__ __volatile__("eret"); + + __builtin_unreachable(); +} diff --git a/lab5/c/src/utils/core-id.c b/lab5/c/src/utils/core-id.c new file mode 100644 index 000000000..a7a736439 --- /dev/null +++ b/lab5/c/src/utils/core-id.c @@ -0,0 +1,9 @@ +#include "oscos/utils/core-id.h" + +#include + +size_t get_core_id(void) { + uint64_t mpidr_val; + __asm__ __volatile__("mrs %0, mpidr_el1" : "=r"(mpidr_val)); + return mpidr_val & 0x3; +} diff --git a/lab5/c/src/utils/fmt.c b/lab5/c/src/utils/fmt.c new file mode 100644 index 000000000..9721b2884 --- /dev/null +++ b/lab5/c/src/utils/fmt.c @@ -0,0 +1,653 @@ +#include "oscos/utils/fmt.h" + +#include +#include +#include + +#include "oscos/libc/ctype.h" +#include "oscos/libc/string.h" + +#define OCT_MAX_N_DIGITS 22 +#define DEC_MAX_N_DIGITS 20 +#define HEX_MAX_N_DIGITS 16 + +typedef enum { + LM_NONE, + LM_HH, + LM_H, + LM_L, + LM_LL, + LM_J, + LM_Z, + LM_T, + LM_UPPER_L +} length_modifier_t; + +static const char LOWER_HEX_DIGITS[16] = "0123456789abcdef"; +static const char UPPER_HEX_DIGITS[16] = "0123456789ABCDEF"; + +static size_t +_render_unsigned_dec(char digits[const static DEC_MAX_N_DIGITS + 1], + const uintmax_t x) { + digits[DEC_MAX_N_DIGITS] = '\0'; + + size_t n_digits = 0; + for (uintmax_t xc = x; xc > 0; xc /= 10) { + digits[DEC_MAX_N_DIGITS - ++n_digits] = '0' + xc % 10; + } + + return n_digits; +} + +static size_t +_render_unsigned_base_p2(char *const restrict digits, const size_t digits_len, + const uintmax_t x, const size_t log2_base, + const char *const restrict digit_template) { + const uintmax_t mask = (1 << log2_base) - 1; + + digits[digits_len] = '\0'; + + size_t n_digits = 0; + for (uintmax_t xc = x; xc > 0; xc >>= log2_base) { + digits[digits_len - ++n_digits] = digit_template[xc & mask]; + } + + return n_digits; +} + +static size_t _puts_generic(void (*const putc)(unsigned char, void *), + void *const putc_arg, const char *const s) { + size_t n_chars_printed = 0; + for (const char *c = s; *c; c++) { + putc(*c, putc_arg); + n_chars_printed++; + } + return n_chars_printed; +} + +static size_t _puts_limited_generic(void (*const putc)(unsigned char, void *), + void *const putc_arg, const char *const s, + const size_t limit) { + size_t n_chars_printed = 0, i = 0; + for (const char *c = s; i < limit && *c; i++, c++) { + putc(*c, putc_arg); + n_chars_printed++; + } + return n_chars_printed; +} + +static size_t _pad_generic(void (*const putc)(unsigned char, void *), + void *const putc_arg, const unsigned char pad, + const size_t width) { + for (size_t i = 0; i < width; i++) { + putc(pad, putc_arg); + } + return width; +} + +static size_t _vprintf_generic_putc(void (*const putc)(unsigned char, void *), + void *const putc_arg, const unsigned char c, + const bool flag_minus, + const size_t min_field_width) { + size_t n_chars_printed = 0; + + if (flag_minus) { + putc(c, putc_arg); + n_chars_printed++; + + if (min_field_width > 1) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', min_field_width - 1); + } + } else { + if (min_field_width > 1) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', min_field_width - 1); + } + + putc(c, putc_arg); + n_chars_printed++; + } + + return n_chars_printed; +} + +static size_t _vprintf_generic_puts(void (*const putc)(unsigned char, void *), + void *const putc_arg, const char *const s, + const bool flag_minus, + const size_t min_field_width, + const bool precision_valid, + const size_t precision) { + const size_t len = strlen(s); + + // Calculate width. + + const size_t width = precision_valid && precision < len ? precision : len; + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + // The string. + if (precision_valid) { + n_chars_printed += _puts_limited_generic(putc, putc_arg, s, precision); + } else { + n_chars_printed += _puts_generic(putc, putc_arg, s); + } + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return n_chars_printed; +} + +static size_t _vprintf_generic_print_signed_dec( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const intmax_t x, const bool flag_minus, const bool flag_plus, + const bool flag_space, const bool flag_zero, const size_t min_field_width, + const size_t precision) { + // Render the digits. + + char digits[DEC_MAX_N_DIGITS + 1]; + const size_t n_digits = _render_unsigned_dec(digits, x < 0 ? -x : x); + + // Calculate width. + + size_t width = n_digits; + if (precision > n_digits) { + width = precision; + } + width += x < 0 || flag_plus || flag_space; // Sign character. + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // Sign. + if (x < 0) { + putc('-', putc_arg); + n_chars_printed++; + } else if (flag_plus) { + putc('+', putc_arg); + n_chars_printed++; + } else if (flag_space) { + putc(' ', putc_arg); + n_chars_printed++; + } + // Leading zeros. + if (precision > n_digits) { + n_chars_printed += _pad_generic(putc, putc_arg, '0', precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (DEC_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +static size_t _vprintf_generic_print_unsigned_oct( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const uintmax_t x, const bool flag_minus, const bool flag_hash, + const bool flag_zero, const size_t min_field_width, + const size_t precision) { + // Render the digits. + + char digits[OCT_MAX_N_DIGITS + 1]; + const size_t n_digits = _render_unsigned_base_p2(digits, OCT_MAX_N_DIGITS, x, + 3, LOWER_HEX_DIGITS); + + // Calculate width. + + const size_t effective_precision = + flag_hash && n_digits + 1 > precision ? n_digits + 1 : precision; + + size_t width = n_digits; + if (effective_precision > n_digits) { + width = effective_precision; + } + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // Leading zeros. + if (effective_precision > n_digits) { + n_chars_printed += + _pad_generic(putc, putc_arg, '0', effective_precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (OCT_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +static size_t _vprintf_generic_print_unsigned_hex( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const uintmax_t x, const bool flag_minus, const bool flag_hash, + const bool flag_zero, const size_t min_field_width, const size_t precision, + const bool is_upper) { + const char *const digit_template = + is_upper ? UPPER_HEX_DIGITS : LOWER_HEX_DIGITS; + const char *const prefix = is_upper ? "0X" : "0x"; + + // Render the digits. + + char digits[HEX_MAX_N_DIGITS + 1]; + const size_t n_digits = + _render_unsigned_base_p2(digits, HEX_MAX_N_DIGITS, x, 4, digit_template); + + // Calculate width. + + size_t width = n_digits; + if (precision > n_digits) { + width = precision; + } + // 0x prefix. + if (flag_hash && x != 0) { + width += 2; + } + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // 0x prefix. + if (flag_hash && x != 0) { + n_chars_printed += _puts_generic(putc, putc_arg, prefix); + } + // Leading zeros. + if (precision > n_digits) { + n_chars_printed += _pad_generic(putc, putc_arg, '0', precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (HEX_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +static size_t _vprintf_generic_print_unsigned_dec( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const uintmax_t x, const bool flag_minus, const bool flag_zero, + const size_t min_field_width, const size_t precision) { + // Render the digits. + + char digits[DEC_MAX_N_DIGITS + 1]; + const size_t n_digits = _render_unsigned_dec(digits, x); + + // Calculate width. + + size_t width = n_digits; + if (precision > n_digits) { + width = precision; + } + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // Leading zeros. + if (precision > n_digits) { + n_chars_printed += _pad_generic(putc, putc_arg, '0', precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (DEC_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +#define READ_ARG_S(LM, AP) \ + ((LM) == LM_NONE ? va_arg(AP, int) \ + : (LM) == LM_HH ? (signed char)va_arg(AP, int) \ + : (LM) == LM_H ? (short)va_arg(AP, int) \ + : (LM) == LM_L ? va_arg(AP, long) \ + : (LM) == LM_LL ? va_arg(AP, long long) \ + : (LM) == LM_J ? va_arg(AP, intmax_t) \ + : (LM) == LM_Z ? va_arg(AP, /* signed size_t = */ long) \ + : (LM) == LM_T ? va_arg(AP, ptrdiff_t) \ + : (__builtin_unreachable(), 0)) + +#define READ_ARG_U(LM, AP) \ + ((LM) == LM_NONE ? va_arg(AP, unsigned) \ + : (LM) == LM_HH ? (unsigned char)va_arg(AP, int) \ + : (LM) == LM_H ? (unsigned short)va_arg(AP, int) \ + : (LM) == LM_L ? va_arg(AP, unsigned long) \ + : (LM) == LM_LL ? va_arg(AP, unsigned long long) \ + : (LM) == LM_J ? va_arg(AP, uintmax_t) \ + : (LM) == LM_Z ? va_arg(AP, size_t) \ + : (LM) == LM_T ? va_arg(AP, /* unsigned ptrdiff_t = */ unsigned long) \ + : (__builtin_unreachable(), 0)) + +int vprintf_generic(const printf_vtable_t *const vtable, void *const vtable_arg, + const char *const restrict format, va_list ap) { + size_t n_chars_printed = 0; + for (const char *restrict c = format; *c;) { + if (*c == '%') { + c++; + + // Flags. + + bool flag_minus = false, flag_plus = false, flag_space = false, + flag_hash = false, flag_zero = false; + + for (;;) { + if (*c == '-') { + c++; + flag_minus = true; + } else if (*c == '+') { + c++; + flag_plus = true; + } else if (*c == ' ') { + c++; + flag_space = true; + } else if (*c == '#') { + c++; + flag_hash = true; + } else if (*c == '0') { + c++; + flag_zero = true; + } else { + break; + } + } + + // Minimum field width. + + bool min_field_width_specified = false; + size_t min_field_width = 0; + if (*c == '*') { + c++; + min_field_width_specified = true; + const int arg = va_arg(ap, int); + if (arg < 0) { + flag_minus = true; + min_field_width = -arg; + } else { + min_field_width = arg; + } + } else if (isdigit(*c)) { + min_field_width_specified = true; + for (; isdigit(*c); c++) { + min_field_width = min_field_width * 10 + (*c - '0'); + } + } + + // Precision. + + bool precision_specified = false, precision_valid = false; + size_t precision = 0; + if (*c == '.') { + c++; + precision_specified = true; + + if (*c == '*') { + c++; + const int arg = va_arg(ap, int); + if (arg >= 0) { + precision_valid = true; + precision = arg; + } + } else { + precision_valid = true; + for (; isdigit(*c); c++) { + precision = precision * 10 + (*c - '0'); + } + } + } + + // Length modifier. + + length_modifier_t length_modifier = LM_NONE; + + switch (*c) { + case 'h': + c++; + if (*c == 'h') { + c++; + length_modifier = LM_HH; + } else { + length_modifier = LM_H; + } + break; + + case 'l': + c++; + if (*c == 'l') { + c++; + length_modifier = LM_LL; + } else { + length_modifier = LM_L; + } + break; + + case 'j': + c++; + length_modifier = LM_J; + break; + + case 'z': + c++; + length_modifier = LM_Z; + break; + + case 't': + c++; + length_modifier = LM_T; + break; + + case 'L': + c++; + length_modifier = LM_UPPER_L; + break; + } + + // Format specifier. + + switch (*c) { + case '%': + c++; + if (flag_minus || flag_plus || flag_space || flag_hash || flag_zero || + min_field_width_specified || precision_specified || + length_modifier != LM_NONE) + __builtin_unreachable(); + vtable->putc('%', vtable_arg); + n_chars_printed++; + break; + + case 'c': + c++; + switch (length_modifier) { + case LM_NONE: + if (flag_hash || flag_zero) + __builtin_unreachable(); + n_chars_printed += + _vprintf_generic_putc(vtable->putc, vtable_arg, va_arg(ap, int), + flag_minus, min_field_width); + break; + + default: + __builtin_unreachable(); + } + break; + + case 's': + c++; + switch (length_modifier) { + case LM_NONE: + if (flag_hash || flag_zero) + __builtin_unreachable(); + n_chars_printed += _vprintf_generic_puts( + vtable->putc, vtable_arg, va_arg(ap, const char *), flag_minus, + min_field_width, precision_valid, precision); + break; + + default: + __builtin_unreachable(); + } + break; + + case 'd': + case 'i': + c++; + if (flag_hash) + __builtin_unreachable(); + n_chars_printed += _vprintf_generic_print_signed_dec( + vtable->putc, vtable_arg, READ_ARG_S(length_modifier, ap), + flag_minus, flag_plus, flag_space, flag_zero, min_field_width, + precision_valid ? precision : 1); + break; + + case 'o': + c++; + n_chars_printed += _vprintf_generic_print_unsigned_oct( + vtable->putc, vtable_arg, READ_ARG_U(length_modifier, ap), + flag_minus, flag_hash, flag_zero, min_field_width, + precision_specified ? precision : 1); + break; + + case 'x': + case 'X': { + const bool is_upper = *c == 'X'; + c++; + n_chars_printed += _vprintf_generic_print_unsigned_hex( + vtable->putc, vtable_arg, READ_ARG_U(length_modifier, ap), + flag_minus, flag_hash, flag_zero, min_field_width, + precision_specified ? precision : 1, is_upper); + break; + } + + case 'u': + c++; + if (flag_hash) + __builtin_unreachable(); + n_chars_printed += _vprintf_generic_print_unsigned_dec( + vtable->putc, vtable_arg, READ_ARG_S(length_modifier, ap), + flag_minus, flag_zero, min_field_width, + precision_valid ? precision : 1); + break; + + case 'n': + c++; + if (flag_minus || flag_plus || flag_space || flag_hash || flag_zero || + min_field_width_specified || precision_specified) + __builtin_unreachable(); + switch (length_modifier) { + case LM_NONE: + *va_arg(ap, int *) = n_chars_printed; + break; + + case LM_HH: + *va_arg(ap, signed char *) = n_chars_printed; + break; + + case LM_H: + *va_arg(ap, short *) = n_chars_printed; + break; + + case LM_L: + *va_arg(ap, long *) = n_chars_printed; + break; + + case LM_LL: + *va_arg(ap, long long *) = n_chars_printed; + break; + + case LM_J: + *va_arg(ap, intmax_t *) = n_chars_printed; + break; + + case LM_Z: + *va_arg(ap, /* signed size_t = */ long *) = n_chars_printed; + break; + + case LM_T: + *va_arg(ap, ptrdiff_t *) = n_chars_printed; + break; + + default: + __builtin_unreachable(); + } + break; + + case 'p': + c++; + switch (length_modifier) { + case LM_NONE: + n_chars_printed += _vprintf_generic_print_unsigned_hex( + vtable->putc, vtable_arg, (uintmax_t)va_arg(ap, void *), + flag_minus, flag_hash, flag_zero, min_field_width, + precision_specified ? precision : 1, false); + break; + + default: + __builtin_unreachable(); + } + break; + + default: + __builtin_unreachable(); + } + } else { + vtable->putc(*c++, vtable_arg); + n_chars_printed++; + } + } + + vtable->finalize(vtable_arg); + return n_chars_printed; +} diff --git a/lab5/c/src/utils/heapq.c b/lab5/c/src/utils/heapq.c new file mode 100644 index 000000000..274f71e07 --- /dev/null +++ b/lab5/c/src/utils/heapq.c @@ -0,0 +1,77 @@ +#include "oscos/utils/heapq.h" + +#include "oscos/libc/string.h" + +// TODO: Check integer overflows. + +static void _heap_sift_up(void *const base, const size_t size, const size_t i, + int (*const compar)(const void *, const void *, + void *), + void *const arg) { + size_t ic = i; + while (ic != 0) { + const size_t ip = (ic - 1) / 2; + void *const pc = (char *)base + ic * size, *const pp = + (char *)base + ip * size; + if (compar(pc, pp, arg) < 0) { + memswp(pc, pp, size); + ic = ip; + } else { + break; + } + } +} + +static void _heap_sift_down( + void *const base, const size_t nmemb, const size_t size, const size_t i, + int (*const compar)(const void *, const void *, void *), void *const arg) { + size_t ic = i, il; + while ((il = ic * 2 + 1) < nmemb) { + const size_t ir = il + 1; + void *const pc = (char *)base + ic * size, + *const pl = (char *)base + il * size, + *const pr = (char *)base + ir * size; + + size_t it; + void *pt; + if (ir < nmemb && compar(pl, pr, arg) >= 0) { + it = ir; + pt = pr; + } else { + it = il; + pt = pl; + } + + if (compar(pt, pc, arg) < 0) { + memswp(pt, pc, size); + ic = it; + } else { + break; + } + } +} + +void heappush(void *const restrict base, const size_t nmemb, const size_t size, + const void *const restrict item, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + memcpy((char *)base + nmemb * size, item, size); + _heap_sift_up(base, size, nmemb, compar, arg); +} + +void heappop(void *const restrict base, const size_t nmemb, const size_t size, + void *const restrict item, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + if (nmemb == 0) + __builtin_unreachable(); + + if (item) { + memcpy(item, base, size); + } + + if (nmemb > 1) { + memcpy(base, (const char *)base + (nmemb - 1) * size, size); + _heap_sift_down(base, nmemb - 1, size, 0, compar, arg); + } +} diff --git a/lab5/c/src/xcpt/default-handler.c b/lab5/c/src/xcpt/default-handler.c new file mode 100644 index 000000000..c06c7dc6e --- /dev/null +++ b/lab5/c/src/xcpt/default-handler.c @@ -0,0 +1,21 @@ +#include + +#include "oscos/console.h" +#include "oscos/libc/inttypes.h" + +#define DUMP_SYS_REG(REGNAME, PAD) \ + do { \ + uint64_t reg_val; \ + __asm__ __volatile__("mrs %0, " #REGNAME : "=r"(reg_val)); \ + \ + console_printf(#REGNAME ": " PAD "0x%.8" PRIx64 "\n", reg_val); \ + } while (0) + +void xcpt_default_handler(const uint64_t vten) { + console_printf("VTEN: 0x%8.1" PRIx64 "\n", vten); + + DUMP_SYS_REG(spsr_el1, ""); + DUMP_SYS_REG(elr_el1, " "); + DUMP_SYS_REG(esr_el1, " "); + console_putc('\n'); +} diff --git a/lab5/c/src/xcpt/irq-handler.c b/lab5/c/src/xcpt/irq-handler.c new file mode 100644 index 000000000..a77f6a17e --- /dev/null +++ b/lab5/c/src/xcpt/irq-handler.c @@ -0,0 +1,43 @@ +#include + +#include "oscos/console.h" +#include "oscos/drivers/board.h" +#include "oscos/drivers/l1ic.h" +#include "oscos/drivers/l2ic.h" +#include "oscos/libc/inttypes.h" +#include "oscos/timer/timeout.h" +#include "oscos/utils/core-id.h" + +void xcpt_irq_handler(void) { + PERIPHERAL_READ_BARRIER(); + + const size_t core_id = get_core_id(); + + for (;;) { + const uint32_t int_src = l1ic_get_int_src(core_id); + + if (int_src == 0) + break; + + if (int_src & INT_L1_SRC_GPU) { + const uint32_t l2_int_src = l2ic_get_pending_irq_0(); + + if (l2_int_src & INT_L2_IRQ_0_SRC_AUX) { + mini_uart_interrupt_handler(); + } else { + console_printf( + "WARN: Received an IRQ from GPU with an unknown source: %" PRIx32 + "\n", + l2_int_src); + } + } else if (int_src & INT_L1_SRC_TIMER1) { + xcpt_core_timer_interrupt_handler(); + } else { + console_printf("WARN: Received an IRQ with an unknown source: %" PRIx32 + "\n", + int_src); + } + } + + PERIPHERAL_WRITE_BARRIER(); +} diff --git a/lab5/c/src/xcpt/task-queue.c b/lab5/c/src/xcpt/task-queue.c new file mode 100644 index 000000000..2101b8827 --- /dev/null +++ b/lab5/c/src/xcpt/task-queue.c @@ -0,0 +1,97 @@ +#include "oscos/xcpt/task-queue.h" + +#include +#include + +#include "oscos/utils/critical-section.h" +#include "oscos/utils/heapq.h" + +#define MAX_N_PENDING_TASKS 16 + +typedef struct { + void (*task)(void *); + void *arg; + int priority; +} pending_task_t; + +static pending_task_t _task_queue_pending_tasks[MAX_N_PENDING_TASKS]; +static size_t _task_queue_n_pending_tasks = 0; +static int _task_queue_curr_task_priority = INT_MIN; + +static int +_task_queue_pending_task_cmp_by_priority(const pending_task_t *const t1, + const pending_task_t *const t2, + void *const _arg) { + (void)_arg; + + // Note that the order is reversed. This is because the heapq module + // implements a min heap, but we want the task with the highest priority to be + // at the front of the priority queue. + + if (t2->priority < t1->priority) + return -1; + if (t2->priority > t1->priority) + return 1; + return 0; +} + +bool task_queue_add_task(void (*const task)(void *), void *const arg, + const int priority) { + if (_task_queue_n_pending_tasks == MAX_N_PENDING_TASKS) + return false; + + const pending_task_t entry = {.task = task, .arg = arg, .priority = priority}; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + heappush(_task_queue_pending_tasks, _task_queue_n_pending_tasks++, + sizeof(pending_task_t), &entry, + (int (*)(const void *, const void *, + void *))_task_queue_pending_task_cmp_by_priority, + NULL); + CRITICAL_SECTION_LEAVE(daif_val); + + return true; +} + +void task_queue_sched(void) { + const int curr_priority = _task_queue_curr_task_priority; + + while (_task_queue_n_pending_tasks > 0 && + _task_queue_pending_tasks[0].priority > + curr_priority) { // There is a pending task of a higher priority. + // Preempt the current task and run the highest-priority pending task. + + // Remove the highest-priority pending task from the priority queue. + // No need to mask interrupts here; this function always runs within an ISR. + + pending_task_t entry; + heappop(_task_queue_pending_tasks, _task_queue_n_pending_tasks--, + sizeof(pending_task_t), &entry, + (int (*)(const void *, const void *, + void *))_task_queue_pending_task_cmp_by_priority, + NULL); + + // Update task priority. + _task_queue_curr_task_priority = entry.priority; + + // Save spsr_el1 and elr_el1, since they can be clobbered. + + uint64_t spsr_val, elr_val; + __asm__ __volatile__("mrs %0, spsr_el1" : "=r"(spsr_val)); + __asm__ __volatile__("mrs %0, elr_el1" : "=r"(elr_val)); + + // Run the task with interrupts enabled. + + XCPT_UNMASK_ALL(); + entry.task(entry.arg); + XCPT_MASK_ALL(); + + // Restore spsr_el1 and elr_el1. + __asm__ __volatile__("msr spsr_el1, %0" : : "r"(spsr_val)); + __asm__ __volatile__("msr elr_el1, %0" : : "r"(elr_val)); + } + + // Restore priority. + _task_queue_curr_task_priority = curr_priority; +} diff --git a/lab5/c/src/xcpt/vector-table.S b/lab5/c/src/xcpt/vector-table.S new file mode 100644 index 000000000..ad6f4806d --- /dev/null +++ b/lab5/c/src/xcpt/vector-table.S @@ -0,0 +1,98 @@ +#include "oscos/utils/save-ctx.S" + +.macro default_vt_entry vten label_name +\label_name: + save_aapcs + mov x0, \vten + bl xcpt_default_handler + load_aapcs + eret + +.type \label_name, function +.size \label_name, . - \label_name + +.align 7 +.endm + +.section ".text" + +.align 11 +xcpt_vector_table: + // Exception from the current EL while using SP_EL0. + + // Synchronous. + default_vt_entry 0x0 xcpt_sync_curr_el_sp_el0_handler + + // IRQ. + default_vt_entry 0x1 xcpt_irq_curr_el_sp_el0_handler + + // FIQ. + default_vt_entry 0x2 xcpt_fiq_curr_el_sp_el0_handler + + // SError. + default_vt_entry 0x3 xcpt_serror_curr_el_sp_el0_handler + + // Exception from the current EL while using SP_ELx. + + // Synchronous. + default_vt_entry 0x4 xcpt_sync_curr_el_sp_elx_handler + + // IRQ. +xcpt_irq_curr_el_sp_elx_handler: + save_aapcs + bl xcpt_irq_handler + bl task_queue_sched + load_aapcs + eret + +.type xcpt_irq_curr_el_sp_elx_handler, function +.size xcpt_irq_curr_el_sp_elx_handler, . - xcpt_irq_curr_el_sp_elx_handler + +.align 7 + + // FIQ. + default_vt_entry 0x6 xcpt_fiq_curr_el_sp_elx_handler + + // SError. + default_vt_entry 0x7 xcpt_serror_curr_el_sp_elx_handler + + // Exception from a lower EL and at least one lower EL is AArch64. + + // Synchronous. + default_vt_entry 0x8 xcpt_sync_lower_el_aarch64_handler + + // IRQ. +xcpt_irq_lower_el_aarch64_handler: + save_aapcs + bl xcpt_irq_handler + bl task_queue_sched + load_aapcs + eret + +.type xcpt_irq_lower_el_aarch64_handler, function +.size xcpt_irq_lower_el_aarch64_handler, . - xcpt_irq_lower_el_aarch64_handler + +.align 7 + + // FIQ. + default_vt_entry 0xa xcpt_fiq_lower_el_aarch64_handler + + // SError. + default_vt_entry 0xb xcpt_serror_lower_el_aarch64_handler + + // Exception from a lower EL and all lower ELs are AArch32. + + // Synchronous. + default_vt_entry 0xc xcpt_sync_lower_el_aarch32_handler + + // IRQ. + default_vt_entry 0xd xcpt_irq_lower_el_aarch32_handler + + // FIQ. + default_vt_entry 0xe xcpt_fiq_lower_el_aarch32_handler + + // SError. + default_vt_entry 0xf xcpt_serror_lower_el_aarch32_handler + +.size xcpt_vector_table, . - xcpt_vector_table +.global xcpt_vector_table diff --git a/lab5/c/src/xcpt/xcpt.c b/lab5/c/src/xcpt/xcpt.c new file mode 100644 index 000000000..05b558b38 --- /dev/null +++ b/lab5/c/src/xcpt/xcpt.c @@ -0,0 +1,7 @@ +#include "oscos/xcpt.h" + +extern char xcpt_vector_table[]; + +void xcpt_set_vector_table(void) { + __asm__ __volatile__("msr vbar_el1, %0" : : "r"(xcpt_vector_table)); +} diff --git a/lab5/tests/.gitignore b/lab5/tests/.gitignore new file mode 100644 index 000000000..30d29c406 --- /dev/null +++ b/lab5/tests/.gitignore @@ -0,0 +1,2 @@ +/initramfs.cpio +/bcm2710-rpi-3-b-plus.dtb diff --git a/lab5/tests/build-initrd.sh b/lab5/tests/build-initrd.sh new file mode 100755 index 000000000..62de9d676 --- /dev/null +++ b/lab5/tests/build-initrd.sh @@ -0,0 +1,19 @@ +#!/bin/sh -e + +if [ -e rootfs ]; then + rm -r rootfs +fi + +progs='xcpt_test' + +mkdir rootfs +for prog in $progs; do + make -C "user-program/$prog" PROFILE=RELEASE + cp "user-program/$prog/build/RELEASE/$prog.img" "rootfs/$prog" +done + +cd rootfs +find . -mindepth 1 | cpio -o -H newc > ../initramfs.cpio + +cd .. +rm -r rootfs diff --git a/lab5/tests/get-dtb.sh b/lab5/tests/get-dtb.sh new file mode 100755 index 000000000..12bb4bd6d --- /dev/null +++ b/lab5/tests/get-dtb.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +dtb_url=https://github.com/raspberrypi/firmware/raw/master/boot/bcm2710-rpi-3-b-plus.dtb + +wget "$dtb_url" diff --git a/lab5/tests/user-program/xcpt_test/.gitignore b/lab5/tests/user-program/xcpt_test/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/lab5/tests/user-program/xcpt_test/.gitignore @@ -0,0 +1 @@ +/build diff --git a/lab5/tests/user-program/xcpt_test/Makefile b/lab5/tests/user-program/xcpt_test/Makefile new file mode 100644 index 000000000..329399072 --- /dev/null +++ b/lab5/tests/user-program/xcpt_test/Makefile @@ -0,0 +1,73 @@ +DEFAULT_PROFILE = DEBUG +SRC_DIR = src +BUILD_DIR = build + +AS = aarch64-linux-gnu-as +CC = aarch64-linux-gnu-gcc +CPP = aarch64-linux-gnu-cpp +OBJCOPY = aarch64-linux-gnu-objcopy + +CPPFLAGS_BASE = -Iinclude + +ASFLAGS_DEBUG = -g + +CFLAGS_BASE = -std=c17 -pedantic-errors -Wall -Wextra -ffreestanding \ + -mcpu=cortex-a53 -mno-outline-atomics -mstrict-align +CFLAGS_DEBUG = -g +CFLAGS_RELEASE = -O3 -flto + +LDFLAGS_BASE = -nostdlib -Xlinker --build-id=none +LDFLAGS_RELEASE = -flto -Xlinker --gc-sections + +LDLIBS_BASE = -lgcc + +OBJS = start +LD_SCRIPT = $(SRC_DIR)/linker.ld + +# ------------------------------------------------------------------------------ + +PROFILE = $(DEFAULT_PROFILE) +OUT_DIR = $(BUILD_DIR)/$(PROFILE) + +CPPFLAGS = $(CPPFLAGS_BASE) $(CPPFLAGS_$(PROFILE)) +ASFLAGS = $(ASFLAGS_BASE) $(ASFLAGS_$(PROFILE)) +CFLAGS = $(CFLAGS_BASE) $(CFLAGS_$(PROFILE)) +LDFLAGS = $(LDFLAGS_BASE) $(LDFLAGS_$(PROFILE)) +LDLIBS = $(LDLIBS_BASE) $(LDLIBS_$(PROFILE)) + +OBJ_PATHS = $(addprefix $(OUT_DIR)/,$(addsuffix .o,$(OBJS))) + +# ------------------------------------------------------------------------------ + +.PHONY: all clean-profile clean + +all: $(OUT_DIR)/xcpt_test.img + +$(OUT_DIR)/xcpt_test.img: $(OUT_DIR)/xcpt_test.elf + @mkdir -p $(@D) + $(OBJCOPY) -O binary $^ $@ + +$(OUT_DIR)/xcpt_test.elf: $(OBJ_PATHS) $(LD_SCRIPT) + @mkdir -p $(@D) + $(CC) -T $(LD_SCRIPT) $(LDFLAGS) $(filter-out $(LD_SCRIPT),$^) $(LDLIBS) \ + -o $@ + +$(OUT_DIR)/%.o: $(SRC_DIR)/%.c + @mkdir -p $(@D) + $(CC) $(CPPFLAGS) $(CFLAGS) -c -MMD -MP -MF $(OUT_DIR)/$*.d $< -o $@ + +$(OUT_DIR)/%.o: $(OUT_DIR)/%.s + @mkdir -p $(@D) + $(AS) $(ASFLAGS) $^ -o $@ + +$(OUT_DIR)/%.s: $(SRC_DIR)/%.S + @mkdir -p $(@D) + $(CPP) $(CPPFLAGS) $^ -o $@ + +clean-profile: + $(RM) -r $(OUT_DIR) + +clean: + $(RM) -r $(BUILD_DIR) + +-include $(OUT_DIR)/*.d diff --git a/lab5/tests/user-program/xcpt_test/src/linker.ld b/lab5/tests/user-program/xcpt_test/src/linker.ld new file mode 100644 index 000000000..255954576 --- /dev/null +++ b/lab5/tests/user-program/xcpt_test/src/linker.ld @@ -0,0 +1,80 @@ +/* +Memory map: (End addresses are exclusive) +0x 0 ( 0B) - 0x 80000 (512K): Reserved for firmware +0x 80000 (512K) - 0x 4000000 ( 64M): Kernel text, rodata, data, bss, heap +0x 4000000 ( 64M) - 0x 8000000 (128M): User program +0x 8000000 (128M) - 0x3a400000 (932M): Reserved for initramfs +0x3a400000 (932M) - 0x3ac00000 (940M): User program stack +0x3ac00000 (940M) - 0x3b400000 (948M): Kernel stack +*/ + +_skernel = 0x80000; +_max_ekernel = 0x4000000; +_suser = 64M; +_max_euser = 128M; + +MEMORY +{ + RAM_USER : ORIGIN = _suser, LENGTH = _max_euser - _suser +} + +/* Must be 16-byte aligned. + The highest address the ARM core can use. Total system SDRAM is 1G. Top 76M + are reserved for VideoCore. */ +_estack = 1024M - 76M; +/* Again, must be 16-byte aligned. */ +_euserstack = _estack - 8M; + +ENTRY(_start) + +SECTIONS +{ + .text : + { + _stext = .; + + *(.text._start) /* Entry point. See `start.S`. */ + *(.text.unlikely .text.*_unlikely .text.unlikely.*) + *(.text.startup .text.startup.*) + *(.text.hot .text.hot.*) + *(.text .text.*) + *(.eh_frame) + *(.eh_frame_hdr) + + _etext = .; + } >RAM_USER + + .rodata : + { + _srodata = .; + + *(.rodata .rodata.*) + + _erodata = .; + } >RAM_USER + + .data : + { + _sdata = .; + + *(.data .data.*) + + _edata = .; + } >RAM_USER + + /* The .bss section is 16-byte aligned to allow the section to be + zero-initialized with the `stp` instruction without using unaligned + memory accesses. See `start.S`. */ + .bss : ALIGN(16) + { + _sbss = .; + + *(.bss .bss.*) + *(COMMON) + + . = ALIGN(16); + _ebss = .; + } >RAM_USER + + _ekernel = .; +} diff --git a/lab5/tests/user-program/xcpt_test/src/start.S b/lab5/tests/user-program/xcpt_test/src/start.S new file mode 100644 index 000000000..ce7e53908 --- /dev/null +++ b/lab5/tests/user-program/xcpt_test/src/start.S @@ -0,0 +1,11 @@ +.section ".text" +.global _start +_start: + mov x0, 0 +1: + add x0, x0, 1 + svc 0 + cmp x0, 5 + blt 1b +1: + b 1b From bad36d5ed04cd84fa840b2757c0ec36c72ad3abe Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Thu, 1 Jun 2023 15:51:29 +0800 Subject: [PATCH 02/34] Lab 5 C: Remove many consistency checks The consistency checks were meant to guarantee the correct operation of the kernel by making sure that everything is loaded into the memory in a sensible way (e.g. the memory region used by the kernel doesn't overlap with other reserved regions). However, they make the code convoluted. Given that the probability of something being incorrectly loaded is exceedingly low in practice, it makes more sense not to include these checks. The quicksort implementation remains in the source tree, albeit becoming unused. --- lab5/c/Makefile | 2 +- lab5/c/include/oscos/mem/memmap.h | 99 -------- lab5/c/include/oscos/mem/page-alloc.h | 3 +- lab5/c/include/oscos/mem/types.h | 3 + lab5/c/src/main.c | 7 - lab5/c/src/mem/memmap.c | 316 ------------------------- lab5/c/src/mem/page-alloc.c | 319 ++++++++++++++++++++++---- 7 files changed, 273 insertions(+), 476 deletions(-) delete mode 100644 lab5/c/include/oscos/mem/memmap.h delete mode 100644 lab5/c/src/mem/memmap.c diff --git a/lab5/c/Makefile b/lab5/c/Makefile index 7067a6ee9..0b1f1124e 100644 --- a/lab5/c/Makefile +++ b/lab5/c/Makefile @@ -24,7 +24,7 @@ LDLIBS_BASE = -lgcc OBJS = start main console devicetree initrd panic shell user-program \ drivers/aux drivers/gpio drivers/l1ic drivers/l2ic drivers/mailbox \ drivers/mini-uart drivers/pm \ - mem/malloc mem/memmap mem/page-alloc mem/startup-alloc mem/vm \ + mem/malloc mem/page-alloc mem/startup-alloc mem/vm \ timer/delay timer/timeout \ xcpt/default-handler xcpt/irq-handler xcpt/task-queue \ xcpt/vector-table xcpt/xcpt \ diff --git a/lab5/c/include/oscos/mem/memmap.h b/lab5/c/include/oscos/mem/memmap.h deleted file mode 100644 index 967f57469..000000000 --- a/lab5/c/include/oscos/mem/memmap.h +++ /dev/null @@ -1,99 +0,0 @@ -/// \file include/oscos/mem/memmap.h -/// \brief Memory map. -/// -/// The memory map contains information about which part of the memory is usable -/// and which part of the memory is reserved. - -#ifndef OSCOS_MEM_MEMMAP_H -#define OSCOS_MEM_MEMMAP_H - -#include -#include - -#include "oscos/mem/types.h" - -/// \brief Purpose of memory reservation. -typedef enum { - RMP_FIRMWARE, ///< Reserved by firmware for whatever reason. - RMP_KERNEL, ///< Kernel text, rodata, data, and bss. - RMP_INITRD, ///< Initial ramdisk. - RMP_DTB, ///< Devicetree blob. - RMP_STACK, ///< Kernel stack. - RMP_UNKNOWN ///< Unknown reason. -} reserved_mem_purpose_t; - -/// \brief A reserved memory entry in the memory map. -typedef struct { - pa_range_t range; - reserved_mem_purpose_t purpose; -} reserved_mem_entry_t; - -/// \brief Initializes the memory map. -void memmap_init(void); - -/// \brief Adds a usable memory region. -/// -/// \return true if the addition succeeds. -/// \return false if the addition fails because the usable memory region array -/// is out of free space. -bool mem_add(pa_range_t mem); - -/// \brief Sorts the usable memory regions in ascending order. -void mem_sort(void); - -/// \brief Gets the number of usable memory regions. -size_t mem_get_n(void); - -/// \brief Gets the pointer to the usable memory region array. -const pa_range_t *mem_get(void); - -/// \brief Adds a reserved memory region. -/// -/// \return true if the addition succeeds. -/// \return false if the addition fails because the reserved memory region array -/// is out of free space. -bool reserved_mem_add(reserved_mem_entry_t reserved_mem); - -/// \brief Sorts the reserved memory regions in ascending order. -void reserved_mem_sort(void); - -/// \brief Gets the number of reserved memory regions. -size_t reserved_mem_get_n(void); - -/// \brief Gets the pointer to the reserved memory region array. -const reserved_mem_entry_t *reserved_mem_get(void); - -/// \brief Scans the device memory and adds the results to the memory map. -/// -/// This function starts a kernel panic when either the usable memory region -/// array or the reserved memory region array runs out of space. -/// -/// This function sorts the usable and reserved memory regions in ascending -/// order as if by calling void mem_sort(void) and void reserved_mem_sort(void). -void scan_mem(void); - -/// \brief Checks whether or not the memory map is well-formed. -/// -/// A memory map is well-formed if all of the following conditions hold: -/// - All usable memory regions do not overlap. -/// - All reserved memory regions do not overlap and are within one of the -/// usable memory regions. -/// -/// Before calling this function, the memory regions must be sorted in ascending -/// order (automatically done by void scan_mem(void)). -bool check_memmap(void); - -/// \brief Prints the device memory map to the serial console. -/// -/// Before calling this function, the usable memory regions and the reserved -/// memory regions must be sorted in ascending order (automatically done by -/// void scan_mem(void)). -void print_memmap(void); - -/// \brief Checks whether or not a memory range is usable, i.e., within a usable -/// memory region but does not overlap with any reserved memory regions. -/// -/// The memory map must be well-formed when calling this function. -bool memmap_is_usable(pa_range_t range); - -#endif diff --git a/lab5/c/include/oscos/mem/page-alloc.h b/lab5/c/include/oscos/mem/page-alloc.h index 968922985..4f8fc9045 100644 --- a/lab5/c/include/oscos/mem/page-alloc.h +++ b/lab5/c/include/oscos/mem/page-alloc.h @@ -14,8 +14,7 @@ /// \brief Initializes the page frame allocator. /// -/// The memory map must be well-formed when calling this function. After calling -/// this function, the startup allocator should not be used. +/// After calling this function, the startup allocator should not be used. void page_alloc_init(void); /// \brief Allocates a block of page frames. diff --git a/lab5/c/include/oscos/mem/types.h b/lab5/c/include/oscos/mem/types.h index d679f55d0..39dbaf075 100644 --- a/lab5/c/include/oscos/mem/types.h +++ b/lab5/c/include/oscos/mem/types.h @@ -6,6 +6,9 @@ /// \brief Physical address. typedef uint32_t pa_t; +/// \brief Maximum value of pa_t. +#define PA_MAX UINT32_MAX + /// \brief Format specifier for printing a pa_t in lowercase hexadecimal format. #define PRIxPA PRIx32 diff --git a/lab5/c/src/main.c b/lab5/c/src/main.c index 3f501e33f..341176c82 100644 --- a/lab5/c/src/main.c +++ b/lab5/c/src/main.c @@ -8,10 +8,8 @@ #include "oscos/drivers/pm.h" #include "oscos/initrd.h" #include "oscos/mem/malloc.h" -#include "oscos/mem/memmap.h" #include "oscos/mem/page-alloc.h" #include "oscos/mem/startup-alloc.h" -#include "oscos/panic.h" #include "oscos/shell.h" #include "oscos/timer/timeout.h" #include "oscos/xcpt.h" @@ -41,11 +39,6 @@ void main(const void *const dtb_start) { } // Initialize the memory allocators. - memmap_init(); - scan_mem(); - print_memmap(); - if (!check_memmap()) - PANIC("memmap: Memory map is not well-formed"); page_alloc_init(); malloc_init(); diff --git a/lab5/c/src/mem/memmap.c b/lab5/c/src/mem/memmap.c deleted file mode 100644 index 8c9ee1ea2..000000000 --- a/lab5/c/src/mem/memmap.c +++ /dev/null @@ -1,316 +0,0 @@ -#include "oscos/mem/memmap.h" - -#include "oscos/console.h" -#include "oscos/devicetree.h" -#include "oscos/initrd.h" -#include "oscos/libc/stdlib.h" -#include "oscos/panic.h" - -#define MAX_N_MEMS 1 -#define MAX_N_RESERVED_MEMS 8 - -// Symbols defined in the linker script. -extern char _skernel[], _ekernel[], _sstack[], _estack[]; - -static pa_range_t _mems[MAX_N_MEMS]; -static size_t _n_mems = 0; -static reserved_mem_entry_t _reserved_mems[MAX_N_RESERVED_MEMS]; -static size_t _n_reserved_mems = 0; - -static int _mem_range_cmp_by_start(const pa_range_t *const r1, - const pa_range_t *const r2) { - if (r1->start < r2->start) - return -1; - if (r1->start > r2->start) - return 1; - return 0; -} - -static int -_reserved_mem_entry_cmp_by_start(const reserved_mem_entry_t *const r1, - const reserved_mem_entry_t *const r2) { - return _mem_range_cmp_by_start(&r1->range, &r2->range); -} - -void memmap_init(void) { - // No-op. -} - -bool mem_add(const pa_range_t mem) { - if (_n_mems == MAX_N_MEMS) - return false; - - _mems[_n_mems++] = mem; - return true; -} - -void mem_sort(void) { - qsort(_mems, _n_mems, sizeof(pa_range_t), - (int (*)(const void *, const void *))_mem_range_cmp_by_start); -} - -size_t mem_get_n(void) { return _n_mems; } - -const pa_range_t *mem_get(void) { return _mems; } - -bool reserved_mem_add(const reserved_mem_entry_t reserved_mem) { - if (_n_reserved_mems == MAX_N_RESERVED_MEMS) - return false; - - _reserved_mems[_n_reserved_mems++] = reserved_mem; - return true; -} - -void reserved_mem_sort(void) { - qsort(_reserved_mems, _n_reserved_mems, sizeof(reserved_mem_entry_t), - (int (*)(const void *, const void *))_reserved_mem_entry_cmp_by_start); -} - -size_t reserved_mem_get_n(void) { return _n_reserved_mems; } - -const reserved_mem_entry_t *reserved_mem_get(void) { return _reserved_mems; } - -#define ADD_MEM_OR_PANIC(ENTRY) \ - do { \ - if (!mem_add(ENTRY)) \ - PANIC("memmap: Ran out of space for memory entries"); \ - } while (0) - -#define RESERVE_OR_PANIC(ENTRY) \ - do { \ - if (!reserved_mem_add(ENTRY)) \ - PANIC("memmap: Ran out of space for reserved memory entries"); \ - } while (0) - -typedef struct { - bool is_memory_done, is_reserved_memory_done; - fdt_n_address_size_cells_t root_n_cells, reserved_memory_n_cells; - const fdt_item_t *reserved_memory_node; -} scan_mem_fdt_traverse_callback_arg_t; - -static control_flow_t _scan_mem_fdt_traverse_callback( - scan_mem_fdt_traverse_callback_arg_t *arg, const fdt_item_t *const node, - const fdt_traverse_parent_list_node_t *const parent) { - if (!parent) { // Current node is the root node. - arg->root_n_cells = fdt_get_n_address_size_cells(node); - } else if (parent && !parent->parent && - strncmp(FDT_NODE_NAME(node), "memory@", 7) == - 0) { // Current node is /memory@... - FDT_FOR_ITEM(node, item) { - if (FDT_TOKEN(item) == FDT_PROP) { - const fdt_prop_t *const prop = (const fdt_prop_t *)item->payload; - if (strcmp(FDT_PROP_NAME(prop), "reg") == 0) { - const fdt_read_reg_result_t read_result = - fdt_read_reg(prop, arg->root_n_cells); - const uint32_t start = read_result.value.address, - end = start + read_result.value.size; - if (read_result.address_overflow || read_result.size_overflow || - read_result.value.address > UINT32_MAX || - read_result.value.size > UINT32_MAX || end < start) - PANIC("memmap: reg property value overflow in devicetree node %s", - FDT_NODE_NAME(node)); - ADD_MEM_OR_PANIC(((pa_range_t){.start = start, .end = end})); - break; - } - } - } - arg->is_memory_done = true; - } else if (parent && !parent->parent && - strcmp(FDT_NODE_NAME(node), "reserved-memory") == - 0) { // Current node is /reserved-memory. - arg->reserved_memory_node = node; - arg->reserved_memory_n_cells = fdt_get_n_address_size_cells(node); - } else if (arg->reserved_memory_node) { // The /reserved-memory node has been - // traversed. - if (parent->node == - arg->reserved_memory_node) { // Current node is /reserved-memory/... - FDT_FOR_ITEM(node, item) { - if (FDT_TOKEN(item) == FDT_PROP) { - const fdt_prop_t *const prop = (const fdt_prop_t *)item->payload; - if (strcmp(FDT_PROP_NAME(prop), "reg") == 0) { - const fdt_read_reg_result_t read_result = - fdt_read_reg(prop, arg->root_n_cells); - const uint32_t start = read_result.value.address, - end = start + read_result.value.size; - if (read_result.address_overflow || read_result.size_overflow || - read_result.value.address > UINT32_MAX || - read_result.value.size > UINT32_MAX || end < start) - PANIC("memmap: reg property value overflow in devicetree node %s", - FDT_NODE_NAME(node)); - RESERVE_OR_PANIC( - ((reserved_mem_entry_t){.range = {.start = start, .end = end}, - .purpose = RMP_FIRMWARE})); - break; - } - } - } - } else { // All children of the /reserved-memory node has been traversed. - arg->reserved_memory_node = NULL; - arg->is_reserved_memory_done = true; - } - } - - return arg->is_memory_done && arg->is_reserved_memory_done ? CF_BREAK - : CF_CONTINUE; -} - -void scan_mem(void) { - if (devicetree_is_init()) { - // Use the devicetree to discover usable and reserved memory regions. - - // Devicetree. - RESERVE_OR_PANIC(((reserved_mem_entry_t){ - .range = {.start = (pa_t)(uintptr_t)fdt_get_start(), - .end = (pa_t)(uintptr_t)fdt_get_end()}, - .purpose = RMP_DTB})); - - // Anything in the memory reservation block. - for (const fdt_reserve_entry_t *reserve_entry = FDT_START_MEM_RSVMAP; - !(reserve_entry->address == 0 && reserve_entry->size == 0); - reserve_entry++) { - const pa_t start = rev_u64(reserve_entry->address), - end = start + rev_u64(reserve_entry->size); - RESERVE_OR_PANIC(((reserved_mem_entry_t){ - .range = {.start = start, .end = end}, .purpose = RMP_FIRMWARE})); - } - - // - Usable memory region. - // - Spin tables for multicore boot. - - scan_mem_fdt_traverse_callback_arg_t arg = {.is_memory_done = false, - .is_reserved_memory_done = - false, - .reserved_memory_node = NULL}; - fdt_traverse((fdt_traverse_callback_t *)_scan_mem_fdt_traverse_callback, - &arg); - } else { - // Use hardcoded values as a fallback. - - // Usable memory region. - ADD_MEM_OR_PANIC(((pa_range_t){.start = 0x0, .end = 0x3b400000})); - - // Spin tables for multicore boot. - RESERVE_OR_PANIC(((reserved_mem_entry_t){ - .range = {.start = 0x0, .end = 0x1000}, .purpose = RMP_FIRMWARE})); - } - - // Kernel image in the physical memory. - RESERVE_OR_PANIC( - ((reserved_mem_entry_t){.range = {.start = (pa_t)(uintptr_t)_skernel, - .end = (pa_t)(uintptr_t)_ekernel}, - .purpose = RMP_KERNEL})); - - // Initial ramdisk. - if (initrd_is_init()) { - RESERVE_OR_PANIC(((reserved_mem_entry_t){ - .range = {.start = (pa_t)(uintptr_t)initrd_get_start(), - .end = (pa_t)(uintptr_t)initrd_get_end()}, - .purpose = RMP_INITRD})); - } - - // Kernel stack. - RESERVE_OR_PANIC( - ((reserved_mem_entry_t){.range = {.start = (pa_t)(uintptr_t)_sstack, - .end = (pa_t)(uintptr_t)_estack}, - .purpose = RMP_STACK})); - - // Sort the memory regions. - mem_sort(); - reserved_mem_sort(); -} - -bool check_memmap(void) { - size_t ir = 0; - for (size_t im = 0; im < _n_mems; im++) { - if (im != 0 && - _mems[im - 1].end > _mems[im].start) { // Usable memory region overlaps - // with the previous one. - return false; - } - - if (ir < _n_reserved_mems && - _reserved_mems[ir].range.start < - _mems[im].start) { // A reserved memory region lies outside all - // usable memory regions. - return false; - } - - for (; - ir < _n_reserved_mems && _reserved_mems[ir].range.end <= _mems[im].end; - ir++) { - if (ir != 0 && _reserved_mems[ir - 1].range.end > - _reserved_mems[ir] - .range.start) { // Reserved memory region overlaps - // with the previous one. - return false; - } - } - } - - if (ir != _n_reserved_mems) { // A reserved memory region lies outside all - // usable memory regions. - return false; - } - - return true; -} - -void print_memmap(void) { - console_puts("Memory map:"); - - size_t ir = 0; - for (size_t im = 0; im < _n_mems; im++) { - console_printf(" Memory region %zu: 0x%8" PRIxPA " - 0x%8" PRIxPA "\n", im, - _mems[im].start, _mems[im].end); - - pa_t curr_pa = _mems[im].start; - - for (; - ir < _n_reserved_mems && _reserved_mems[ir].range.end <= _mems[im].end; - ir++) { - if (curr_pa != _reserved_mems[ir].range.start) { - console_printf(" 0x%8" PRIxPA " - 0x%8" PRIxPA ": Usable\n", curr_pa, - _reserved_mems[ir].range.start); - } - - const char *const purpose_str = - _reserved_mems[ir].purpose == RMP_FIRMWARE ? "firmware" - : _reserved_mems[ir].purpose == RMP_KERNEL ? "kernel" - : _reserved_mems[ir].purpose == RMP_INITRD ? "initrd" - : _reserved_mems[ir].purpose == RMP_DTB ? "dtb" - : _reserved_mems[ir].purpose == RMP_STACK ? "stack" - : "unknown"; - console_printf(" 0x%8" PRIxPA " - 0x%8" PRIxPA ": Reserved: %s\n", - _reserved_mems[ir].range.start, - _reserved_mems[ir].range.end, purpose_str); - - curr_pa = _reserved_mems[ir].range.end; - } - - if (curr_pa != _mems[im].end) { - console_printf(" 0x%8" PRIxPA " - 0x%8" PRIxPA ": Usable\n", curr_pa, - _mems[im].end); - } - } -} - -bool memmap_is_usable(const pa_range_t range) { - bool is_within_any_usable_range = false; - for (size_t i = 0; i < _n_mems; i++) { - if (_mems[i].start <= range.start && range.end <= _mems[i].end) { - is_within_any_usable_range = true; - break; - } - } - - if (!is_within_any_usable_range) - return false; - - for (size_t i = 0; i < _n_reserved_mems; i++) { - if (!(_reserved_mems[i].range.end <= range.start || - range.end <= _reserved_mems[i].range.start)) - return false; - } - - return true; -} diff --git a/lab5/c/src/mem/page-alloc.c b/lab5/c/src/mem/page-alloc.c index e1b83b324..896b8ad61 100644 --- a/lab5/c/src/mem/page-alloc.c +++ b/lab5/c/src/mem/page-alloc.c @@ -38,7 +38,8 @@ #include "oscos/mem/page-alloc.h" #include "oscos/console.h" -#include "oscos/mem/memmap.h" +#include "oscos/devicetree.h" +#include "oscos/initrd.h" #include "oscos/mem/startup-alloc.h" #include "oscos/mem/vm.h" #include "oscos/panic.h" @@ -46,6 +47,9 @@ // `MAX_BLOCK_ORDER` can be changed to any positive integer up to and // including 25 without modification to the code. +// Symbols defined in the linker script. +extern char _skernel[], _ekernel[], _sstack[], _estack[]; + typedef struct { bool is_avail : 1; unsigned order : 5; @@ -56,27 +60,274 @@ typedef struct { static pa_t _pa_start; static page_frame_array_entry_t *_page_frame_array, *_free_list; -void page_alloc_init(void) { - const size_t n_usable_mems = mem_get_n(); - const pa_range_t *const usable_mems = mem_get(); +// Utilities used by page_alloc_init. + +// _mark_region + +/// \brief Marks a region as either reserved or available. +/// +/// \param region_limit The region limit. Only the part of \p region that lies +/// within this limit will be marked. +/// \param region The region to mark. +/// \param is_avail The target reservation status. +static void _mark_region(const pa_range_t region_limit, const pa_range_t region, + const bool is_avail) { + const pa_t effective_start = region_limit.start > region.start + ? region_limit.start + : region.start, + effective_end = + region_limit.end < region.end ? region_limit.end : region.end; + + if (effective_start < effective_end) { + mark_pages(pa_range_to_page_id_range((pa_range_t){.start = effective_start, + .end = effective_end}), + is_avail); + } +} + +// _get_usable_pa_range - if (n_usable_mems == 0) { - console_puts("WARN: page-alloc: No usable memory region."); +typedef struct { + fdt_n_address_size_cells_t root_n_cells; + pa_range_t range; +} get_usable_pa_range_fdt_traverse_arg_t; + +static control_flow_t _get_usable_pa_range_fdt_traverse_callback( + get_usable_pa_range_fdt_traverse_arg_t *const arg, + const fdt_item_t *const node, + const fdt_traverse_parent_list_node_t *const parent) { + if (!parent) { // Current node is the root node. + arg->root_n_cells = fdt_get_n_address_size_cells(node); + } else if (parent && !parent->parent && + strncmp(FDT_NODE_NAME(node), "memory@", 7) == + 0) { // Current node is /memory@... + FDT_FOR_ITEM(node, item) { + if (FDT_TOKEN(item) == FDT_PROP) { + const fdt_prop_t *const prop = (const fdt_prop_t *)item->payload; + if (strcmp(FDT_PROP_NAME(prop), "reg") == 0) { + const fdt_read_reg_result_t read_result = + fdt_read_reg(prop, arg->root_n_cells); + const pa_t start = read_result.value.address, + end = start + read_result.value.size; + if (read_result.address_overflow || read_result.size_overflow || + read_result.value.address > PA_MAX || + read_result.value.size > PA_MAX || end < start) + PANIC( + "page-alloc: reg property value overflow in devicetree node %s", + FDT_NODE_NAME(node)); + + if (start < arg->range.start) { + arg->range.start = start; + } + if (end > arg->range.end) { + arg->range.end = end; + } + } + } + } } - // Determine the starting physical address. + return CF_CONTINUE; +} - _pa_start = n_usable_mems > 0 ? usable_mems[0].start : 0; +/// \brief Gets the physical address range containing all usable memory regions. +static pa_range_t _get_usable_pa_range(void) { + if (devicetree_is_init()) { + get_usable_pa_range_fdt_traverse_arg_t arg = { + .range = {.start = PA_MAX, .end = 0}}; + fdt_traverse( + (fdt_traverse_callback_t *)_get_usable_pa_range_fdt_traverse_callback, + &arg); + return arg.range; + } else { + return (pa_range_t){.start = 0x0, .end = 0x3b400000}; + } +} - // Check the ending physical address. +// _mark_usable_regions + +typedef struct { + pa_range_t usable_pa_range; + fdt_n_address_size_cells_t root_n_cells; +} mark_usable_regions_fdt_traverse_arg_t; + +static control_flow_t _mark_usable_regions_fdt_traverse_callback( + mark_usable_regions_fdt_traverse_arg_t *const arg, + const fdt_item_t *const node, + const fdt_traverse_parent_list_node_t *const parent) { + if (!parent) { // Current node is the root node. + arg->root_n_cells = fdt_get_n_address_size_cells(node); + } else if (parent && !parent->parent && + strncmp(FDT_NODE_NAME(node), "memory@", 7) == + 0) { // Current node is /memory@... + FDT_FOR_ITEM(node, item) { + if (FDT_TOKEN(item) == FDT_PROP) { + const fdt_prop_t *const prop = (const fdt_prop_t *)item->payload; + if (strcmp(FDT_PROP_NAME(prop), "reg") == 0) { + const fdt_read_reg_result_t read_result = + fdt_read_reg(prop, arg->root_n_cells); + const pa_t start = read_result.value.address, + end = start + read_result.value.size; + if (read_result.address_overflow || read_result.size_overflow || + read_result.value.address > PA_MAX || + read_result.value.size > PA_MAX || end < start) + PANIC( + "page-alloc: reg property value overflow in devicetree node %s", + FDT_NODE_NAME(node)); + + _mark_region(arg->usable_pa_range, + (pa_range_t){.start = start, .end = end}, true); + } + } + } + } + + return CF_CONTINUE; +} + +/// \brief Marks the usable memory regions as available. +/// +/// \param usable_pa_range The physical address range containing all usable +/// memory regions. Can be obtained by +/// pa_range_t _get_usable_pa_range(void). +static void _mark_usable_regions(const pa_range_t usable_pa_range) { + if (devicetree_is_init()) { + mark_usable_regions_fdt_traverse_arg_t arg = {.usable_pa_range = + usable_pa_range}; + fdt_traverse( + (fdt_traverse_callback_t *)_mark_usable_regions_fdt_traverse_callback, + &arg); + } else { + _mark_region(usable_pa_range, (pa_range_t){.start = 0x0, .end = 0x3b400000}, + true); + } +} + +// _mark_reserved_regions + +typedef struct { + pa_range_t usable_pa_range; + const fdt_item_t *reserved_memory_node; + fdt_n_address_size_cells_t reserved_memory_n_cells; +} mark_reserved_regions_fdt_traverse_arg_t; + +static control_flow_t _mark_reserved_regions_fdt_traverse_callback( + mark_reserved_regions_fdt_traverse_arg_t *const arg, + const fdt_item_t *const node, + const fdt_traverse_parent_list_node_t *const parent) { + if (parent && !parent->parent && + strcmp(FDT_NODE_NAME(node), "reserved-memory") == + 0) { // Current node is /reserved-memory. + arg->reserved_memory_node = node; + arg->reserved_memory_n_cells = fdt_get_n_address_size_cells(node); + } else if (arg->reserved_memory_node) { // The /reserved-memory node has been + // traversed. + if (parent->node == + arg->reserved_memory_node) { // Current node is /reserved-memory/... + FDT_FOR_ITEM(node, item) { + if (FDT_TOKEN(item) == FDT_PROP) { + const fdt_prop_t *const prop = (const fdt_prop_t *)item->payload; + if (strcmp(FDT_PROP_NAME(prop), "reg") == 0) { + const fdt_read_reg_result_t read_result = + fdt_read_reg(prop, arg->reserved_memory_n_cells); + const pa_t start = read_result.value.address, + end = start + read_result.value.size; + if (read_result.address_overflow || read_result.size_overflow || + read_result.value.address > PA_MAX || + read_result.value.size > PA_MAX || end < start) + PANIC("page-alloc: reg property value overflow in devicetree " + "node %s", + FDT_NODE_NAME(node)); + + _mark_region(arg->usable_pa_range, + (pa_range_t){.start = start, .end = end}, false); + + break; + } + } + } + } else { // All children of the /reserved-memory node has been traversed. + return CF_BREAK; + } + } + + return CF_CONTINUE; +} + +/// \brief Marks the reserved memory regions as reserved. +/// +/// \param usable_pa_range The physical address range containing all usable +/// memory regions. Can be obtained by +/// pa_range_t _get_usable_pa_range(void). +static void _mark_reserved_regions(const pa_range_t usable_pa_range) { + if (devicetree_is_init()) { + // Devicetree. + + _mark_region(usable_pa_range, + (pa_range_t){.start = (pa_t)(uintptr_t)fdt_get_start(), + .end = (pa_t)(uintptr_t)fdt_get_end()}, + false); + + // Anything in the memory reservation block. + + for (const fdt_reserve_entry_t *reserve_entry = FDT_START_MEM_RSVMAP; + !(reserve_entry->address == 0 && reserve_entry->size == 0); + reserve_entry++) { + const pa_t start = rev_u64(reserve_entry->address), + end = start + rev_u64(reserve_entry->size); + _mark_region(usable_pa_range, (pa_range_t){.start = start, .end = end}, + false); + } + + // Spin tables for multicore boot, etc. + + mark_reserved_regions_fdt_traverse_arg_t arg = {.usable_pa_range = + usable_pa_range}; + fdt_traverse( + (fdt_traverse_callback_t *)_mark_reserved_regions_fdt_traverse_callback, + &arg); + } else { + // Spin tables for multicore boot. + + _mark_region(usable_pa_range, (pa_range_t){.start = 0x0, .end = 0x1000}, + false); + } + + // Kernel image in the physical memory. + + _mark_region(usable_pa_range, + (pa_range_t){.start = (pa_t)(uintptr_t)_skernel, + .end = (pa_t)(uintptr_t)_ekernel}, + false); + + // Initial ramdisk. + + _mark_region(usable_pa_range, + (pa_range_t){.start = (pa_t)(uintptr_t)initrd_get_start(), + .end = (pa_t)(uintptr_t)initrd_get_end()}, + false); + + // Kernel stack. + + _mark_region(usable_pa_range, + (pa_range_t){.start = (pa_t)(uintptr_t)_sstack, + .end = (pa_t)(uintptr_t)_estack}, + false); +} + +void page_alloc_init(void) { + // Determine the starting and ending physical address. + + pa_range_t usable_pa_range = _get_usable_pa_range(); + _pa_start = usable_pa_range.start; const pa_t max_supported_pa = _pa_start + (1 << (PAGE_ORDER + MAX_BLOCK_ORDER)); - if (n_usable_mems > 0 && - usable_mems[n_usable_mems - 1].end > max_supported_pa) { + if (usable_pa_range.end > max_supported_pa) { console_printf("WARN: page-alloc: End of usable memory region 0x%" PRIxPA " is greater than maximum supported PA 0x%" PRIxPA ".\n", - usable_mems[n_usable_mems - 1].end, max_supported_pa); + usable_pa_range.end, max_supported_pa); + usable_pa_range.end = max_supported_pa; } // Allocate the page frame array and the free list. @@ -87,14 +338,6 @@ void page_alloc_init(void) { _page_frame_array = entries + (MAX_BLOCK_ORDER + 1); _free_list = entries; - // Check if the startup allocator allocated any memory the kernel shouldn't - // use. - - if (!memmap_is_usable( - kernel_va_range_to_pa_range(startup_alloc_get_alloc_range()))) - PANIC("page-alloc: Startup allocator allocated memory the kernel shouldn't" - "use"); - // Initialize the page frame array. (The entire memory region is initially // reserved.) @@ -110,43 +353,17 @@ void page_alloc_init(void) { // Mark the usable regions as usable. - for (size_t i = 0; i < n_usable_mems; i++) { - if (usable_mems[i].start >= max_supported_pa) - break; - - const bool end_cut = usable_mems[i].end >= max_supported_pa; - mark_pages(pa_range_to_page_id_range((pa_range_t){ - .start = usable_mems[i].start, - .end = end_cut ? max_supported_pa : usable_mems[i].end}), - true); - if (end_cut) - break; - } + _mark_usable_regions(usable_pa_range); // Mark the reserved regions as reserved. - const size_t n_reserved_mems = reserved_mem_get_n(); - const reserved_mem_entry_t *const reserved_mems = reserved_mem_get(); - - for (size_t i = 0; i < n_reserved_mems; i++) { - if (reserved_mems[i].range.start >= max_supported_pa) - break; - - const bool end_cut = reserved_mems[i].range.end >= max_supported_pa; - mark_pages( - pa_range_to_page_id_range((pa_range_t){ - .start = reserved_mems[i].range.start, - .end = end_cut ? max_supported_pa : reserved_mems[i].range.end}), - false); - if (end_cut) - break; - } + _mark_reserved_regions(usable_pa_range); // Mark the region used by the startup allocator as reserved. - mark_pages(pa_range_to_page_id_range( - kernel_va_range_to_pa_range(startup_alloc_get_alloc_range())), - false); + _mark_region(usable_pa_range, + kernel_va_range_to_pa_range(startup_alloc_get_alloc_range()), + false); } /// \brief Adds a block to the free list. From e9a89b28122bf771322c99f7442abb486cd99af4 Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Sun, 4 Jun 2023 22:08:53 +0800 Subject: [PATCH 03/34] Lab 5 C: Complete B1: Thread Other changes: - Properly handle critical sections in memory allocators. - Fixes a bug in the dynamic memory allocator that causes memory corruption. - Disable page frame allocator debug log. --- lab5/c/Makefile | 3 +- lab5/c/include/oscos/mem/page-alloc.h | 27 ++++ lab5/c/include/oscos/sched.h | 66 ++++++++++ lab5/c/src/main.c | 38 +++++- lab5/c/src/mem/malloc.c | 51 ++++++-- lab5/c/src/mem/page-alloc.c | 35 +++++- lab5/c/src/sched/idle-thread.c | 8 ++ lab5/c/src/sched/sched.c | 170 ++++++++++++++++++++++++++ lab5/c/src/sched/schedule.S | 39 ++++++ lab5/c/src/sched/thread-main.S | 10 ++ 10 files changed, 429 insertions(+), 18 deletions(-) create mode 100644 lab5/c/include/oscos/sched.h create mode 100644 lab5/c/src/sched/idle-thread.c create mode 100644 lab5/c/src/sched/sched.c create mode 100644 lab5/c/src/sched/schedule.S create mode 100644 lab5/c/src/sched/thread-main.S diff --git a/lab5/c/Makefile b/lab5/c/Makefile index 0b1f1124e..93f62e427 100644 --- a/lab5/c/Makefile +++ b/lab5/c/Makefile @@ -7,7 +7,7 @@ CC = aarch64-linux-gnu-gcc CPP = aarch64-linux-gnu-cpp OBJCOPY = aarch64-linux-gnu-objcopy -CPPFLAGS_BASE = -Iinclude -DPAGE_ALLOC_ENABLE_LOG +CPPFLAGS_BASE = -Iinclude ASFLAGS_DEBUG = -g @@ -25,6 +25,7 @@ OBJS = start main console devicetree initrd panic shell user-program \ drivers/aux drivers/gpio drivers/l1ic drivers/l2ic drivers/mailbox \ drivers/mini-uart drivers/pm \ mem/malloc mem/page-alloc mem/startup-alloc mem/vm \ + sched/idle-thread sched/sched sched/schedule sched/thread-main \ timer/delay timer/timeout \ xcpt/default-handler xcpt/irq-handler xcpt/task-queue \ xcpt/vector-table xcpt/xcpt \ diff --git a/lab5/c/include/oscos/mem/page-alloc.h b/lab5/c/include/oscos/mem/page-alloc.h index 4f8fc9045..eddee380c 100644 --- a/lab5/c/include/oscos/mem/page-alloc.h +++ b/lab5/c/include/oscos/mem/page-alloc.h @@ -23,10 +23,26 @@ void page_alloc_init(void); /// request cannot be fulfilled. spage_id_t alloc_pages(size_t order); +/// \brief Allocates a block of page frames. +/// +/// This function is safe to call only within a critical section. +/// +/// \param order The order of the block. +/// \return The page number of the first page, or a negative number if the +/// request cannot be fulfilled. +spage_id_t alloc_pages_unlocked(size_t order); + /// \brief Frees a block of page frames. /// \param page The page number of the first page of the block. void free_pages(page_id_t page); +/// \brief Frees a block of page frames. +/// +/// This function is safe to call only within a critical section. +/// +/// \param page The page number of the first page of the block. +void free_pages_unlocked(page_id_t page); + /// \brief Marks a contiguous range of page frames as either reserved or /// available. /// @@ -36,6 +52,17 @@ void free_pages(page_id_t page); /// \param is_avail The target reservation status. void mark_pages(page_id_range_t range, bool is_avail); +/// \brief Marks a contiguous range of page frames as either reserved or +/// available. +/// +/// Note that the range can be arbitrary and doesn't need to be a block. +/// +/// This function is safe to call only within a critical section. +/// +/// \param range The range of page frames to mark. +/// \param is_avail The target reservation status. +void mark_pages_unlocked(page_id_range_t range, bool is_avail); + /// \brief Converts the given page ID into its corresponding physical address. pa_t page_id_to_pa(page_id_t page) __attribute__((pure)); diff --git a/lab5/c/include/oscos/sched.h b/lab5/c/include/oscos/sched.h new file mode 100644 index 000000000..ac7f1f5df --- /dev/null +++ b/lab5/c/include/oscos/sched.h @@ -0,0 +1,66 @@ +#ifndef OSCOS_SCHED_H +#define OSCOS_SCHED_H + +#include +#include +#include +#include +#include + +#include "oscos/mem/types.h" + +typedef struct thread_list_node_t { + struct thread_list_node_t *prev, *next; +} thread_list_node_t; + +typedef struct { + alignas(16) union { + struct { + uint64_t x19, x20, x21, x22, x23, x24, x25, x26, x27, x28, x29, pc, sp; + }; + uint64_t regs[13]; + }; +} thread_ctx_t; + +typedef struct { + thread_list_node_t list_node; + thread_ctx_t ctx; + size_t id; + page_id_t stack_page_id; +} thread_t; + +/// \brief Initializes the scheduler and creates the idle thread. +/// +/// \return true if the initialization succeeds. +/// \return false if the initialization fails due to memory shortage. +bool sched_init(void); + +/// \brief Creates a thread. +/// +/// \param task The task to execute in the new thread. +/// \param arg The argument to pass to \p task. +/// \return true if the thread creation succeeds. +/// \return false if the thread creation fails due to memory shortage. +bool thread_create(void (*task)(void *), void *arg); + +/// \brief Terminates the current thread. +/// +/// This function should not be called on the idle thread. +noreturn void thread_exit(void); + +/// \brief Gets the current thread. +thread_t *current_thread(void); + +/// \brief Kills zombie threads. +void kill_zombies(void); + +/// \brief Yields CPU for the current thread and runs the scheduler. +void schedule(void); + +/// \brief Do what the idle thread should do. +/// +/// This function must be called on the idle thread, i.e., the thread that +/// called bool thread_create(void (*)(void *), void *). +noreturn void idle(void); + +#endif diff --git a/lab5/c/src/main.c b/lab5/c/src/main.c index 341176c82..bfaf90d01 100644 --- a/lab5/c/src/main.c +++ b/lab5/c/src/main.c @@ -10,10 +10,32 @@ #include "oscos/mem/malloc.h" #include "oscos/mem/page-alloc.h" #include "oscos/mem/startup-alloc.h" +#include "oscos/panic.h" +#include "oscos/sched.h" #include "oscos/shell.h" +#include "oscos/timer/delay.h" #include "oscos/timer/timeout.h" #include "oscos/xcpt.h" +static void _foo(void *const _arg) { + (void)_arg; + + for (int i = 0; i < 10; i++) { + console_printf("TID %2zu: %d\n", current_thread()->id, i); + delay_ns(1000000); + schedule(); + } +} + +static void _delayed_shell(void *const _arg) { + (void)_arg; + + for (int i = 0; i < 20; i++) { + schedule(); + } + run_shell(); +} + void main(const void *const dtb_start) { // Initialize interrupt-related subsystems. l1ic_init(); @@ -46,6 +68,18 @@ void main(const void *const dtb_start) { mailbox_init(); pm_init(); - // Run the kernel-space shell. - run_shell(); + // Initialize the scheduler. + + if (!sched_init()) { + PANIC("Cannot initialize scheduler: out of memory"); + } + + // Test the scheduler. + + for (int i = 0; i < 32; i++) { + thread_create(_foo, NULL); + } + thread_create(_delayed_shell, NULL); + + idle(); } diff --git a/lab5/c/src/mem/malloc.c b/lab5/c/src/mem/malloc.c index 95c02263d..2d45257e5 100644 --- a/lab5/c/src/mem/malloc.c +++ b/lab5/c/src/mem/malloc.c @@ -37,6 +37,7 @@ #include "oscos/libc/string.h" #include "oscos/mem/page-alloc.h" #include "oscos/mem/vm.h" +#include "oscos/utils/critical-section.h" #include "oscos/utils/math.h" /// \brief A node of a doubly-linked list. @@ -126,6 +127,8 @@ static size_t _get_slab_type_id(const size_t n_units) { /// /// The `free_list_head` field of \p slab must be initialized and \p slab must /// not have been on any free list. +/// +/// This function is safe to call only within a critical section. static void _add_slab_to_free_list(slab_t *const slab) { list_node_t *const free_list_first_entry = slab->free_list_head->next; slab->free_list_node.next = free_list_first_entry; @@ -137,16 +140,20 @@ static void _add_slab_to_free_list(slab_t *const slab) { /// \brief Removes a slab from its free list. /// /// \p slab must have been on a free list. +/// +/// This function is safe to call only within a critical section. static void _remove_slab_from_free_list(slab_t *const slab) { slab->free_list_node.prev->next = slab->free_list_node.next; slab->free_list_node.next->prev = slab->free_list_node.prev; } /// \brief Allocates a new slab and adds it onto its free list. +/// +/// This function is safe to call only within a critical section. static slab_t *_alloc_slab(const size_t slab_type_id) { // Allocate space for the slab. - const spage_id_t page = alloc_pages(0); + const spage_id_t page = alloc_pages_unlocked(0); if (page < 0) return NULL; @@ -158,7 +165,7 @@ static slab_t *_alloc_slab(const size_t slab_type_id) { // allocated page to the page frame allocator. // (In practice, this code path is never taken.) - const spage_id_t another_page = alloc_pages(0); + const spage_id_t another_page = alloc_pages_unlocked(0); free_pages(page); if (another_page < 0) { return NULL; @@ -181,15 +188,22 @@ static slab_t *_alloc_slab(const size_t slab_type_id) { /// \brief Gets a slab of the given type with at least one free slot. If there /// is none, allocates a new one. static slab_t *_get_or_alloc_slab(const size_t slab_type_id) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + slab_t *result; if (_slab_free_lists[slab_type_id].next == &_slab_free_lists[slab_type_id]) { // The free list is empty. - return _alloc_slab(slab_type_id); + result = _alloc_slab(slab_type_id); } else { list_node_t *const free_list_first_entry = _slab_free_lists[slab_type_id].next; - return (slab_t *)((char *)free_list_first_entry - - offsetof(slab_t, free_list_node)); + result = (slab_t *)((char *)free_list_first_entry - + offsetof(slab_t, free_list_node)); } + + CRITICAL_SECTION_LEAVE(daif_val); + return result; } /// \brief Gets the index of the first free slot of the given slab. @@ -197,13 +211,16 @@ static slab_t *_get_or_alloc_slab(const size_t slab_type_id) { /// \p slab must have at least one free slot. static size_t _get_first_free_slot_ix(const slab_t *const slab) { for (size_t i = 0;; i++) { + uint64_t reversed_negated_bitset; + __asm__("rbit %0, %1" + : "=r"(reversed_negated_bitset) + : "r"(~slab->slots_reserved_bitset[i])); + uint64_t j; - __asm__ __volatile__("clz %0, %1" - : "=r"(j) - : "r"(~slab->slots_reserved_bitset[i])); - if (j != - 64) { // The (63-j)th bit of `slab->slots_reserved_bitset[i]` is clear. - return i * 64 + (63 - j); + __asm__("clz %0, %1" : "=r"(j) : "r"(reversed_negated_bitset)); + + if (j != 64) { // The jth bit of `slab->slots_reserved_bitset[i]` is clear. + return i * 64 + j; } } } @@ -212,12 +229,16 @@ static size_t _get_first_free_slot_ix(const slab_t *const slab) { /// /// \p slab must have at least one free slot. static void *_alloc_from_slab(slab_t *const slab) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + const size_t free_slot_ix = _get_first_free_slot_ix(slab); // Mark the `free_slot_ix`th slot as reserved. slab->n_slots_reserved++; - slab->slots_reserved_bitset[free_slot_ix / 64] |= 1 << (free_slot_ix % 64); + slab->slots_reserved_bitset[free_slot_ix / 64] |= (uint64_t)1 + << (free_slot_ix % 64); // Remove itself from its free list if there are no free slots. @@ -225,6 +246,7 @@ static void *_alloc_from_slab(slab_t *const slab) { _remove_slab_from_free_list(slab); } + CRITICAL_SECTION_LEAVE(daif_val); return slab->slots + free_slot_ix * (slab->metadata.slot_size * 16); } @@ -233,6 +255,9 @@ static void *_alloc_from_slab(slab_t *const slab) { /// \param slab The slab. /// \param ptr The pointer to the slot. static void _free_to_slab(slab_t *const slab, void *const ptr) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + const size_t slot_ix = ((uintptr_t)ptr - (uintptr_t)slab) / (slab->metadata.slot_size * 16); @@ -253,6 +278,8 @@ static void _free_to_slab(slab_t *const slab, void *const ptr) { _remove_slab_from_free_list(slab); free_pages(pa_to_page_id(kernel_va_to_pa(slab))); } + + CRITICAL_SECTION_LEAVE(daif_val); } // Large allocation. diff --git a/lab5/c/src/mem/page-alloc.c b/lab5/c/src/mem/page-alloc.c index 896b8ad61..cd510492e 100644 --- a/lab5/c/src/mem/page-alloc.c +++ b/lab5/c/src/mem/page-alloc.c @@ -43,6 +43,7 @@ #include "oscos/mem/startup-alloc.h" #include "oscos/mem/vm.h" #include "oscos/panic.h" +#include "oscos/utils/critical-section.h" // `MAX_BLOCK_ORDER` can be changed to any positive integer up to and // including 25 without modification to the code. @@ -79,9 +80,9 @@ static void _mark_region(const pa_range_t region_limit, const pa_range_t region, region_limit.end < region.end ? region_limit.end : region.end; if (effective_start < effective_end) { - mark_pages(pa_range_to_page_id_range((pa_range_t){.start = effective_start, - .end = effective_end}), - is_avail); + mark_pages_unlocked(pa_range_to_page_id_range((pa_range_t){ + .start = effective_start, .end = effective_end}), + is_avail); } } @@ -452,6 +453,16 @@ static void _split_block(const page_id_t page) { } spage_id_t alloc_pages(const size_t order) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const spage_id_t result = alloc_pages_unlocked(order); + + CRITICAL_SECTION_LEAVE(daif_val); + return result; +} + +spage_id_t alloc_pages_unlocked(const size_t order) { #ifdef PAGE_ALLOC_ENABLE_LOG console_printf("DEBUG: page-alloc: Allocating a block of order %zu\n", order); #endif @@ -516,6 +527,15 @@ spage_id_t alloc_pages(const size_t order) { } void free_pages(const page_id_t page) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + free_pages_unlocked(page); + + CRITICAL_SECTION_LEAVE(daif_val); +} + +void free_pages_unlocked(const page_id_t page) { const size_t order = _page_frame_array[page].order; #ifdef PAGE_ALLOC_ENABLE_LOG @@ -607,6 +627,15 @@ static void _mark_pages_rec(const page_id_range_t range, const bool is_avail, } void mark_pages(const page_id_range_t range, const bool is_avail) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + mark_pages_unlocked(range, is_avail); + + CRITICAL_SECTION_LEAVE(daif_val); +} + +void mark_pages_unlocked(const page_id_range_t range, const bool is_avail) { #ifdef PAGE_ALLOC_ENABLE_LOG console_printf("DEBUG: page-alloc: Marking pages 0x%" PRIxPAGEID " - 0x%" PRIxPAGEID " as %s.\n", diff --git a/lab5/c/src/sched/idle-thread.c b/lab5/c/src/sched/idle-thread.c new file mode 100644 index 000000000..b570c09f9 --- /dev/null +++ b/lab5/c/src/sched/idle-thread.c @@ -0,0 +1,8 @@ +#include "oscos/sched.h" + +void idle(void) { + for (;;) { + kill_zombies(); + schedule(); + } +} diff --git a/lab5/c/src/sched/sched.c b/lab5/c/src/sched/sched.c new file mode 100644 index 000000000..9c953b0ce --- /dev/null +++ b/lab5/c/src/sched/sched.c @@ -0,0 +1,170 @@ +// This module implements a round-robin scheduler. + +#include "oscos/sched.h" + +#include "oscos/mem/malloc.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/mem/vm.h" +#include "oscos/utils/critical-section.h" + +#define THREAD_STACK_ORDER 13 // 8KB. +#define THREAD_STACK_BLOCK_ORDER (THREAD_STACK_ORDER - PAGE_ORDER) + +void _sched_run_thread(thread_t *thread); +void thread_main(); + +static size_t _sched_next_tid = 1; +static thread_list_node_t _run_queue, _zombies; + +bool sched_init(void) { + // Creates the idle thread. + + thread_t *const idle_thread = malloc(sizeof(thread_t)); + if (!idle_thread) + return false; + + idle_thread->id = 0; + + // Initializes the run queue. Initially, the run queue consists solely of the + // idle thread. + + _run_queue.prev = _run_queue.next = &idle_thread->list_node; + idle_thread->list_node.prev = idle_thread->list_node.next = &_run_queue; + + // Name the current thread the idle thread. + + __asm__ __volatile__("msr tpidr_el1, %0" : : "r"(idle_thread)); + + // Initialize the zombie list. Initially, there are no zombies. + + _zombies.prev = _zombies.next = &_zombies; + + return true; +} + +bool thread_create(void (*const task)(void *), void *const arg) { + // Create the thread structure. + + thread_t *const thread = malloc(sizeof(thread_t)); + if (!thread) + return false; + + thread->id = _sched_next_tid++; + thread->ctx.x19 = (uint64_t)(uintptr_t)task; + thread->ctx.x20 = (uint64_t)(uintptr_t)arg; + thread->ctx.pc = (uint64_t)(uintptr_t)thread_main; + + // Allocate the stack for the new thread. + + const spage_id_t stack_page_id = alloc_pages(THREAD_STACK_BLOCK_ORDER); + if (stack_page_id < 0) { + free(thread); + return false; + } + + thread->stack_page_id = stack_page_id; + void *const init_sp = (char *)pa_to_kernel_va(page_id_to_pa(stack_page_id)) + + (1 << (THREAD_STACK_BLOCK_ORDER + PAGE_ORDER)); + thread->ctx.sp = (uint64_t)(uintptr_t)init_sp; + + // Put the thread into the end of the run queue. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + thread_list_node_t *const last_node = _run_queue.prev; + thread->list_node.prev = last_node; + last_node->next = &thread->list_node; + thread->list_node.next = &_run_queue; + _run_queue.prev = &thread->list_node; + + CRITICAL_SECTION_LEAVE(daif_val); + + return true; +} + +void thread_exit(void) { + // Move the current thread from the run queue to the zombie list. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + thread_list_node_t *const current_thread_node = _run_queue.next, + *const next_node = current_thread_node->next; + _run_queue.next = next_node; + next_node->prev = &_run_queue; + + thread_list_node_t *const last_zombie = _zombies.prev; + current_thread_node->prev = last_zombie; + last_zombie->next = current_thread_node; + current_thread_node->next = &_zombies; + _zombies.prev = current_thread_node; + + CRITICAL_SECTION_LEAVE(daif_val); + + // Pick a new thread to run. + + thread_t *const next_thread = + (thread_t *)((char *)next_node - offsetof(thread_t, list_node)); + _sched_run_thread(next_thread); + __builtin_unreachable(); +} + +thread_t *current_thread(void) { + thread_t *result; + __asm__ __volatile__("mrs %0, tpidr_el1" : "=r"(result)); + return result; +} + +static void _thread_cleanup(thread_t *const thread) { + free_pages(thread->stack_page_id); + free(thread); +} + +void kill_zombies(void) { + for (;;) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + if (_zombies.next == &_zombies) { // No more zombies. + CRITICAL_SECTION_LEAVE(daif_val); + break; + } + + thread_list_node_t *const first_zombie_node = _zombies.next, + *const next_zombie_node = first_zombie_node->next; + + _zombies.next = next_zombie_node; + next_zombie_node->prev = &_zombies; + + CRITICAL_SECTION_LEAVE(daif_val); + + thread_t *const zombie = + (thread_t *)((char *)first_zombie_node - offsetof(thread_t, list_node)); + _thread_cleanup(zombie); + } +} + +thread_t *_sched_pick_thread(void) { + // Move the current thread to the end of the run queue. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + thread_list_node_t *const current_thread_node = _run_queue.next, + *const next_node = current_thread_node->next; + _run_queue.next = next_node; + next_node->prev = &_run_queue; + + thread_list_node_t *const last_node = _run_queue.prev; + current_thread_node->prev = last_node; + last_node->next = current_thread_node; + current_thread_node->next = &_run_queue; + _run_queue.prev = current_thread_node; + + thread_t *const next_thread = + (thread_t *)((char *)_run_queue.next - offsetof(thread_t, list_node)); + + CRITICAL_SECTION_LEAVE(daif_val); + return next_thread; +} diff --git a/lab5/c/src/sched/schedule.S b/lab5/c/src/sched/schedule.S new file mode 100644 index 000000000..9bbf37240 --- /dev/null +++ b/lab5/c/src/sched/schedule.S @@ -0,0 +1,39 @@ +.section ".text" + +schedule: + mrs x0, tpidr_el1 + + stp x19, x20, [x0, 16 + 0 * 16] + stp x21, x22, [x0, 16 + 1 * 16] + stp x23, x24, [x0, 16 + 2 * 16] + stp x25, x26, [x0, 16 + 3 * 16] + stp x27, x28, [x0, 16 + 4 * 16] + stp x29, x30, [x0, 16 + 5 * 16] + mov x1, sp + str x1, [x0, 16 + 6 * 16] + + bl _sched_pick_thread + + // Fallthrough. + +.size schedule, . - schedule +.type schedule, function +.global schedule + +_sched_run_thread: + ldp x19, x20, [x0, 16 + 0 * 16] + ldp x21, x22, [x0, 16 + 1 * 16] + ldp x23, x24, [x0, 16 + 2 * 16] + ldp x25, x26, [x0, 16 + 3 * 16] + ldp x27, x28, [x0, 16 + 4 * 16] + ldp x29, x30, [x0, 16 + 5 * 16] + ldr x1, [x0, 16 + 6 * 16] + mov sp, x1 + + msr tpidr_el1, x0 + + ret + +.size _sched_run_thread, . - _sched_run_thread +.type _sched_run_thread, function +.global _sched_run_thread diff --git a/lab5/c/src/sched/thread-main.S b/lab5/c/src/sched/thread-main.S new file mode 100644 index 000000000..7548f66ee --- /dev/null +++ b/lab5/c/src/sched/thread-main.S @@ -0,0 +1,10 @@ +.section ".text" + +thread_main: + mov x0, x20 + blr x19 + b thread_exit + +.size thread_main, . - thread_main +.type thread_main, function +.global thread_main From 5c70a56ac3f65c1c61ebf03faa4fe573afa80c08 Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Mon, 19 Jun 2023 16:20:36 +0800 Subject: [PATCH 04/34] Lab 5 C: Complete B2: User Process and System Call Other changes: - Scheduler: - Partial rewrite to simplify design. - The current thread no longer remains on the run queue after being scheduled. - Serial console: - Redesign around non-blocking primitives. - Add new non-blocking functions. - Add read/write readiness notification facility. - Dynamic memory allocator: - Fix a bug in `free` that causes the incorrect slot to be freed. - (Old) User program: - Retired. Superseded by the current scheduler. - Testers: - Retire `xcpt_test` since it no longer works. - Add `syscall_test` that tests some system calls. - Add a minimal libc. TODO: - Properly implement red-black tree in order to speed up the process of finding a process by its PID. --- lab5/c/Makefile | 17 +- lab5/c/include/oscos/console.h | 50 ++ lab5/c/include/oscos/mem/types.h | 29 + lab5/c/include/oscos/sched.h | 82 ++- lab5/c/include/oscos/uapi/errno.h | 9 + lab5/c/include/oscos/uapi/sys/syscall.h | 13 + lab5/c/include/oscos/uapi/unistd.h | 6 + lab5/c/include/oscos/user-program.h | 22 - lab5/c/include/oscos/utils/rb.h | 26 + lab5/c/include/oscos/xcpt/trap-frame.h | 24 + lab5/c/src/console.c | 217 ++++-- lab5/c/src/mem/malloc.c | 4 +- lab5/c/src/mem/types.c | 44 ++ lab5/c/src/sched/sched.c | 506 +++++++++++--- lab5/c/src/sched/schedule.S | 102 ++- lab5/c/src/sched/user-program-main.S | 93 +++ lab5/c/src/shell.c | 11 +- lab5/c/src/start.S | 4 + lab5/c/src/user-program.c | 82 --- lab5/c/src/utils/rb.c | 83 +++ lab5/c/src/xcpt/svc-handler.S | 50 ++ lab5/c/src/xcpt/syscall-table.S | 14 + lab5/c/src/xcpt/syscall/enosys.c | 3 + lab5/c/src/xcpt/syscall/exec.c | 44 ++ lab5/c/src/xcpt/syscall/exit.c | 3 + lab5/c/src/xcpt/syscall/fork-child-ret.S | 29 + lab5/c/src/xcpt/syscall/fork-impl.c | 11 + lab5/c/src/xcpt/syscall/fork.S | 48 ++ lab5/c/src/xcpt/syscall/getpid.c | 4 + lab5/c/src/xcpt/syscall/kill.c | 17 + lab5/c/src/xcpt/syscall/mbox-call.c | 29 + lab5/c/src/xcpt/syscall/uart-read.c | 37 + lab5/c/src/xcpt/syscall/uart-write.c | 35 + lab5/c/src/xcpt/vector-table.S | 27 +- lab5/tests/build-initrd.sh | 2 +- .../{xcpt_test => libc}/.gitignore | 0 lab5/tests/user-program/libc/Makefile | 61 ++ .../libc/include/__detail/utils/fmt.h | 15 + lab5/tests/user-program/libc/include/ctype.h | 6 + lab5/tests/user-program/libc/include/errno.h | 9 + lab5/tests/user-program/libc/include/mbox.h | 6 + .../user-program/libc/include/oscos-uapi | 1 + lab5/tests/user-program/libc/include/signal.h | 3 + lab5/tests/user-program/libc/include/stdio.h | 12 + lab5/tests/user-program/libc/include/stdlib.h | 8 + lab5/tests/user-program/libc/include/string.h | 25 + .../user-program/libc/include/sys/syscall.h | 1 + lab5/tests/user-program/libc/include/unistd.h | 21 + .../libc/src/__detail/utils/fmt.c | 654 ++++++++++++++++++ lab5/tests/user-program/libc/src/ctype.c | 3 + lab5/tests/user-program/libc/src/errno.c | 3 + lab5/tests/user-program/libc/src/mbox.c | 8 + lab5/tests/user-program/libc/src/signal.c | 5 + lab5/tests/user-program/libc/src/start.S | 15 + lab5/tests/user-program/libc/src/stdio.c | 31 + lab5/tests/user-program/libc/src/stdlib.c | 9 + lab5/tests/user-program/libc/src/string.c | 99 +++ lab5/tests/user-program/libc/src/unistd.c | 19 + .../user-program/libc/src/unistd/syscall.S | 30 + .../user-program/syscall_test/.gitignore | 1 + .../{xcpt_test => syscall_test}/Makefile | 12 +- .../{xcpt_test => syscall_test}/src/linker.ld | 0 .../user-program/syscall_test/src/main.c | 61 ++ lab5/tests/user-program/xcpt_test/src/start.S | 11 - 64 files changed, 2626 insertions(+), 280 deletions(-) create mode 100644 lab5/c/include/oscos/uapi/errno.h create mode 100644 lab5/c/include/oscos/uapi/sys/syscall.h create mode 100644 lab5/c/include/oscos/uapi/unistd.h delete mode 100644 lab5/c/include/oscos/user-program.h create mode 100644 lab5/c/include/oscos/utils/rb.h create mode 100644 lab5/c/include/oscos/xcpt/trap-frame.h create mode 100644 lab5/c/src/mem/types.c create mode 100644 lab5/c/src/sched/user-program-main.S delete mode 100644 lab5/c/src/user-program.c create mode 100644 lab5/c/src/utils/rb.c create mode 100644 lab5/c/src/xcpt/svc-handler.S create mode 100644 lab5/c/src/xcpt/syscall-table.S create mode 100644 lab5/c/src/xcpt/syscall/enosys.c create mode 100644 lab5/c/src/xcpt/syscall/exec.c create mode 100644 lab5/c/src/xcpt/syscall/exit.c create mode 100644 lab5/c/src/xcpt/syscall/fork-child-ret.S create mode 100644 lab5/c/src/xcpt/syscall/fork-impl.c create mode 100644 lab5/c/src/xcpt/syscall/fork.S create mode 100644 lab5/c/src/xcpt/syscall/getpid.c create mode 100644 lab5/c/src/xcpt/syscall/kill.c create mode 100644 lab5/c/src/xcpt/syscall/mbox-call.c create mode 100644 lab5/c/src/xcpt/syscall/uart-read.c create mode 100644 lab5/c/src/xcpt/syscall/uart-write.c rename lab5/tests/user-program/{xcpt_test => libc}/.gitignore (100%) create mode 100644 lab5/tests/user-program/libc/Makefile create mode 100644 lab5/tests/user-program/libc/include/__detail/utils/fmt.h create mode 100644 lab5/tests/user-program/libc/include/ctype.h create mode 100644 lab5/tests/user-program/libc/include/errno.h create mode 100644 lab5/tests/user-program/libc/include/mbox.h create mode 120000 lab5/tests/user-program/libc/include/oscos-uapi create mode 100644 lab5/tests/user-program/libc/include/signal.h create mode 100644 lab5/tests/user-program/libc/include/stdio.h create mode 100644 lab5/tests/user-program/libc/include/stdlib.h create mode 100644 lab5/tests/user-program/libc/include/string.h create mode 100644 lab5/tests/user-program/libc/include/sys/syscall.h create mode 100644 lab5/tests/user-program/libc/include/unistd.h create mode 100644 lab5/tests/user-program/libc/src/__detail/utils/fmt.c create mode 100644 lab5/tests/user-program/libc/src/ctype.c create mode 100644 lab5/tests/user-program/libc/src/errno.c create mode 100644 lab5/tests/user-program/libc/src/mbox.c create mode 100644 lab5/tests/user-program/libc/src/signal.c create mode 100644 lab5/tests/user-program/libc/src/start.S create mode 100644 lab5/tests/user-program/libc/src/stdio.c create mode 100644 lab5/tests/user-program/libc/src/stdlib.c create mode 100644 lab5/tests/user-program/libc/src/string.c create mode 100644 lab5/tests/user-program/libc/src/unistd.c create mode 100644 lab5/tests/user-program/libc/src/unistd/syscall.S create mode 100644 lab5/tests/user-program/syscall_test/.gitignore rename lab5/tests/user-program/{xcpt_test => syscall_test}/Makefile (86%) rename lab5/tests/user-program/{xcpt_test => syscall_test}/src/linker.ld (100%) create mode 100644 lab5/tests/user-program/syscall_test/src/main.c delete mode 100644 lab5/tests/user-program/xcpt_test/src/start.S diff --git a/lab5/c/Makefile b/lab5/c/Makefile index 93f62e427..fc4c454f0 100644 --- a/lab5/c/Makefile +++ b/lab5/c/Makefile @@ -21,16 +21,21 @@ LDFLAGS_RELEASE = -flto -Xlinker --gc-sections LDLIBS_BASE = -lgcc -OBJS = start main console devicetree initrd panic shell user-program \ +OBJS = start main console devicetree initrd panic shell \ drivers/aux drivers/gpio drivers/l1ic drivers/l2ic drivers/mailbox \ drivers/mini-uart drivers/pm \ - mem/malloc mem/page-alloc mem/startup-alloc mem/vm \ - sched/idle-thread sched/sched sched/schedule sched/thread-main \ + mem/malloc mem/page-alloc mem/startup-alloc mem/types mem/vm \ + sched/idle-thread sched/sched sched/schedule sched/thread-main \ + sched/user-program-main \ timer/delay timer/timeout \ - xcpt/default-handler xcpt/irq-handler xcpt/task-queue \ - xcpt/vector-table xcpt/xcpt \ + xcpt/default-handler xcpt/irq-handler xcpt/svc-handler \ + xcpt/syscall-table xcpt/task-queue xcpt/vector-table xcpt/xcpt \ + xcpt/syscall/enosys xcpt/syscall/getpid xcpt/syscall/uart-read \ + xcpt/syscall/uart-write xcpt/syscall/exec xcpt/syscall/fork \ + xcpt/syscall/fork-impl xcpt/syscall/fork-child-ret \ + xcpt/syscall/exit xcpt/syscall/mbox-call xcpt/syscall/kill \ libc/ctype libc/stdio libc/stdlib/qsort libc/string \ - utils/core-id utils/fmt utils/heapq + utils/core-id utils/fmt utils/heapq utils/rb LD_SCRIPT = $(SRC_DIR)/linker.ld # ------------------------------------------------------------------------------ diff --git a/lab5/c/include/oscos/console.h b/lab5/c/include/oscos/console.h index dd8c26558..65abb41d1 100644 --- a/lab5/c/include/oscos/console.h +++ b/lab5/c/include/oscos/console.h @@ -20,6 +20,7 @@ #define OSCOS_CONSOLE_H #include +#include #include /// \brief The mode of the serial console. @@ -43,6 +44,21 @@ void console_set_mode(console_mode_t mode); /// When calling this function, the serial console must be initialized. unsigned char console_getc(void); +/// \brief Reads a character from the serial console without blocking. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return The character read, or a negative number if the read would block. +int console_getc_nonblock(void); + +/// \brief Reads as many characters from the serial console as possible without +/// blocking. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return The number of characters read. +size_t console_read_nonblock(void *buf, size_t count); + /// \brief Writes a character to the serial console. /// /// When calling this function, the serial console must be initialized. @@ -50,6 +66,14 @@ unsigned char console_getc(void); /// \return \p c unsigned char console_putc(unsigned char c); +/// \brief Writes a character to the serial console without blocking. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return \p c if the operation is successful, or a negative number if the +/// write operation would block. +int console_putc_nonblock(unsigned char c); + /// \brief Writes characters to the serial console. /// /// When calling this function, the serial console must be initialized. @@ -57,6 +81,14 @@ unsigned char console_putc(unsigned char c); /// \return \p count size_t console_write(const void *buf, size_t count); +/// \brief Writes as many characters to the serial console as possible without +/// blocking. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return The number of characters written. +size_t console_write_nonblock(const void *buf, size_t count); + /// \brief Writes a string to the serial console without trailing newline. /// /// When calling this function, the serial console must be initialized. @@ -85,6 +117,24 @@ int console_vprintf(const char *restrict format, va_list ap) int console_printf(const char *restrict format, ...) __attribute__((format(printf, 1, 2))); +/// \brief Registers the notification callback for read readiness. +/// +/// \param callback The callback that will be called. +/// \param arg The argument that will be passed to \p callback. +/// \return true if the registration succeeds. +/// \return false if the registration fails because an earlier callback has not +/// been called. +bool console_notify_read_ready(void (*callback)(void *), void *arg); + +/// \brief Registers the notification callback for write readiness. +/// +/// \param callback The callback that will be called. +/// \param arg The argument that will be passed to \p callback. +/// \return true if the registration succeeds. +/// \return false if the registration fails because an earlier callback has not +/// been called. +bool console_notify_write_ready(void (*callback)(void *), void *arg); + /// \brief Interrupt handler. Not meant to be called directly. void mini_uart_interrupt_handler(void); diff --git a/lab5/c/include/oscos/mem/types.h b/lab5/c/include/oscos/mem/types.h index 39dbaf075..7aa7e7c99 100644 --- a/lab5/c/include/oscos/mem/types.h +++ b/lab5/c/include/oscos/mem/types.h @@ -1,6 +1,8 @@ #ifndef OSCOS_MEM_TYPES_H #define OSCOS_MEM_TYPES_H +#include + #include "oscos/libc/inttypes.h" /// \brief Physical address. @@ -37,4 +39,31 @@ typedef struct { void *start, *end; } va_range_t; +/// \brief Shared, reference-counted page. +typedef struct { + size_t ref_count; + page_id_t page_id; +} shared_page_t; + +/// \brief Initializes a shared page. +/// +/// This function sets the reference count of the newly-created shared page +/// to 1. +shared_page_t *shared_page_init(page_id_t page_id); + +/// \brief Clones a shared page. +/// +/// This function increases the reference count of the shared page by 1. +/// +/// \param shared_page The shared page to clone. +/// \return \p shared_page +shared_page_t *shared_page_clone(shared_page_t *shared_page); + +/// \brief Drops a shared page. +/// +/// This function decreases the reference count of the shared page by 1. The +/// shared page structure and the underlying page will be freed when the +/// reference count reaches 0. +void shared_page_drop(shared_page_t *shared_page); + #endif diff --git a/lab5/c/include/oscos/sched.h b/lab5/c/include/oscos/sched.h index ac7f1f5df..ff72d17b2 100644 --- a/lab5/c/include/oscos/sched.h +++ b/lab5/c/include/oscos/sched.h @@ -8,27 +8,50 @@ #include #include "oscos/mem/types.h" +#include "oscos/xcpt/trap-frame.h" typedef struct thread_list_node_t { struct thread_list_node_t *prev, *next; } thread_list_node_t; +typedef struct { + alignas(16) uint64_t words[2]; +} uint128_t; + +typedef struct { + uint128_t v[32]; + alignas(16) struct { + uint64_t fpcr, fpsr; + }; +} thread_fp_simd_ctx_t; + typedef struct { alignas(16) union { struct { - uint64_t x19, x20, x21, x22, x23, x24, x25, x26, x27, x28, x29, pc, sp; + uint64_t r19, r20, r21, r22, r23, r24, r25, r26, r27, r28, r29, pc, + kernel_sp, user_sp; }; - uint64_t regs[13]; + uint64_t regs[14]; }; + thread_fp_simd_ctx_t *fp_simd_ctx; } thread_ctx_t; +struct process_t; + typedef struct { thread_list_node_t list_node; thread_ctx_t ctx; size_t id; page_id_t stack_page_id; + struct process_t *process; } thread_t; +typedef struct process_t { + size_t id; + shared_page_t *text_page, *user_stack_page; + thread_t *main_thread; +} process_t; + /// \brief Initializes the scheduler and creates the idle thread. /// /// \return true if the initialization succeeds. @@ -51,16 +74,65 @@ noreturn void thread_exit(void); /// \brief Gets the current thread. thread_t *current_thread(void); +/// \brief Creates a process and name the current thread the main thread of the +/// process. +/// +/// \return true if the process creation succeeds. +/// \return false if the process creation fails due to memory shortage. +bool process_create(void); + +/// \brief Executes the first ever user program on the current process. +/// +/// This function jumps to the user program and does not return if the operation +/// succeeds. If the operation fails due to memory shortage, this function +/// returns. +/// +/// \param text_start The starting address of where the user program is +/// currently located. (Do not confuse it with the entry point +/// of the user program.) +/// \param text_len The length of the user program. +void exec_first(const void *text_start, size_t text_len); + +/// \brief Executes a user program on the current process. +/// +/// This function jumps to the user program and does not return if the operation +/// succeeds. If the operation fails due to memory shortage, this function +/// returns. +/// +/// The text_page_id field of the current process must be valid. +/// +/// \param text_start The starting address of where the user program is +/// currently located. (Do not confuse it with the entry point +/// of the user program.) +/// \param text_len The length of the user program. +void exec(const void *text_start, size_t text_len); + +/// \brief Forks the current process. +process_t *fork(const extended_trap_frame_t *trap_frame); + /// \brief Kills zombie threads. void kill_zombies(void); /// \brief Yields CPU for the current thread and runs the scheduler. void schedule(void); +/// \brief Puts the current thread to the given wait queue and runs the +/// scheduler. +void suspend_to_wait_queue(thread_list_node_t *wait_queue); + +/// \brief Adds every thread in the given wait queue to the run queue. +void add_all_threads_to_run_queue(thread_list_node_t *wait_queue); + +/// \brief Gets a process by its PID. +process_t *get_process_by_id(size_t pid); + +/// \brief Kills a process. +void kill_process(process_t *process); + +/// \brief Kills all processes. +void kill_all_processes(void); + /// \brief Do what the idle thread should do. -/// -/// This function must be called on the idle thread, i.e., the thread that -/// called bool thread_create(void (*)(void *), void *). noreturn void idle(void); #endif diff --git a/lab5/c/include/oscos/uapi/errno.h b/lab5/c/include/oscos/uapi/errno.h new file mode 100644 index 000000000..7bbe51420 --- /dev/null +++ b/lab5/c/include/oscos/uapi/errno.h @@ -0,0 +1,9 @@ +#define ENOENT 2 +#define ESRCH 3 +#define EIO 5 +#define ENOMEM 12 +#define EBUSY 16 +#define EISDIR 21 +#define EINVAL 22 +#define ENOSYS 38 +#define ELOOP 40 diff --git a/lab5/c/include/oscos/uapi/sys/syscall.h b/lab5/c/include/oscos/uapi/sys/syscall.h new file mode 100644 index 000000000..c4d4fa90b --- /dev/null +++ b/lab5/c/include/oscos/uapi/sys/syscall.h @@ -0,0 +1,13 @@ +#ifndef OSCOS_UAPI_SYS_SYSCALL_H +#define OSCOS_UAPI_SYS_SYSCALL_H + +#define SYS_getpid 0 +#define SYS_uart_read 1 +#define SYS_uart_write 2 +#define SYS_exec 3 +#define SYS_fork 4 +#define SYS_exit 5 +#define SYS_mbox_call 6 +#define SYS_kill 7 + +#endif diff --git a/lab5/c/include/oscos/uapi/unistd.h b/lab5/c/include/oscos/uapi/unistd.h new file mode 100644 index 000000000..80efebdf4 --- /dev/null +++ b/lab5/c/include/oscos/uapi/unistd.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_UAPI_UNISTD_H +#define OSCOS_UAPI_UNISTD_H + +typedef long ssize_t; + +#endif diff --git a/lab5/c/include/oscos/user-program.h b/lab5/c/include/oscos/user-program.h deleted file mode 100644 index c13f60021..000000000 --- a/lab5/c/include/oscos/user-program.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef OSCOS_USER_PROGRAM_H -#define OSCOS_USER_PROGRAM_H - -#include -#include -#include - -/// \brief Loads the user program from somewhere in the memory to the place in -/// memory where it'll be executed. -/// -/// \param start The starting address of where the user program is currently -/// located. (Do not confuse it with the entry point of the user -/// program.) -/// \param len The length of the user program. -/// \return true if the loading succeeds. -/// \return false if the loading failed due to the user program being too long. -bool load_user_program(const void *start, size_t len); - -/// \brief Runs the loaded user program. -noreturn void run_user_program(void); - -#endif diff --git a/lab5/c/include/oscos/utils/rb.h b/lab5/c/include/oscos/utils/rb.h new file mode 100644 index 000000000..85368c0c6 --- /dev/null +++ b/lab5/c/include/oscos/utils/rb.h @@ -0,0 +1,26 @@ +#ifndef OSCOS_UTILS_RB_H +#define OSCOS_UTILS_RB_H + +#include +#include +#include + +// typedef enum { RB_NC_BLACK, RB_NC_RED } rb_node_colour_t; + +typedef struct rb_node_t { + // rb_node_colour_t colour; + struct rb_node_t *children[2] /*, **parent */; + alignas(16) unsigned char payload[]; +} rb_node_t; + +const void *rb_search(const rb_node_t *root, const void *restrict key, + int (*compar)(const void *, const void *, void *), + void *arg); + +bool rb_insert(rb_node_t **root, size_t size, const void *restrict item, + int (*compar)(const void *, const void *, void *), void *arg); + +void rb_delete(rb_node_t **root, const void *restrict key, + int (*compar)(const void *, const void *, void *), void *arg); + +#endif diff --git a/lab5/c/include/oscos/xcpt/trap-frame.h b/lab5/c/include/oscos/xcpt/trap-frame.h new file mode 100644 index 000000000..f5a1c11ee --- /dev/null +++ b/lab5/c/include/oscos/xcpt/trap-frame.h @@ -0,0 +1,24 @@ +#ifndef OSCOS_XCPT_TRAP_FRAME_H +#define OSCOS_XCPT_TRAP_FRAME_H + +#include +#include + +typedef struct { + alignas(16) union { + struct { + uint64_t r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, + r15, r16, r17, r18, lr; + }; + uint64_t regs[20]; + }; +} trap_frame_t; + +typedef struct { + alignas(16) struct { + uint64_t spsr, elr; + }; + trap_frame_t trap_frame; +} extended_trap_frame_t; + +#endif diff --git a/lab5/c/src/console.c b/lab5/c/src/console.c index 93729b480..496a6f539 100644 --- a/lab5/c/src/console.c +++ b/lab5/c/src/console.c @@ -16,6 +16,11 @@ static volatile unsigned char *_console_read_buf, *_console_write_buf; static volatile size_t _console_read_buf_start = 0, _console_read_buf_len = 0, _console_write_buf_start = 0, _console_write_buf_len = 0; +static void (*volatile _console_read_notify_callback)( + void *) = NULL, + (*volatile _console_write_notify_callback)(void *) = NULL; +static void *volatile _console_read_notify_callback_arg, + *volatile _console_write_notify_callback_arg; // Buffer operations. @@ -35,6 +40,17 @@ static void _console_recv_to_buf(void) { if (_console_read_buf_len == READ_BUF_SZ) { mini_uart_disable_rx_interrupt(); } + + // Send notification. + + if (_console_read_buf_len != 0) { + void (*const callback)(void *) = _console_read_notify_callback; + _console_read_notify_callback = NULL; + + if (callback) { + callback(_console_read_notify_callback_arg); + } + } } static void _console_send_from_buf(void) { @@ -51,26 +67,26 @@ static void _console_send_from_buf(void) { if (_console_write_buf_len == 0) { mini_uart_disable_tx_interrupt(); } -} -// Raw operations. - -static unsigned char _console_recv_byte(void) { - bool suspend_cond_val = true; - uint64_t daif_val; + // Send notification. - // Wait for data in the read buffer. - while (suspend_cond_val) { - CRITICAL_SECTION_ENTER(daif_val); + if (_console_write_buf_len != WRITE_BUF_SZ) { + void (*const callback)(void *) = _console_write_notify_callback; + _console_write_notify_callback = NULL; - if ((suspend_cond_val = _console_read_buf_len == 0)) { - __asm__ __volatile__("wfi"); - _console_recv_to_buf(); + if (callback) { + callback(_console_write_notify_callback_arg); } - - CRITICAL_SECTION_LEAVE(daif_val); } +} + +// Raw operations. + +static int _console_recv_byte_nonblock(void) { + if (_console_read_buf_len == 0) + return -1; + uint64_t daif_val; CRITICAL_SECTION_ENTER(daif_val); const unsigned char result = _console_read_buf[_console_read_buf_start]; @@ -85,68 +101,85 @@ static unsigned char _console_recv_byte(void) { return result; } -static void _console_send_byte(const unsigned char b) { - bool suspend_cond_val = true; +static bool _console_send_byte_nonblock(const unsigned char b) { + if (_console_write_buf_len == WRITE_BUF_SZ) + return false; + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); - // Wait for the write buffer to clear. - while (suspend_cond_val) { - CRITICAL_SECTION_ENTER(daif_val); + _console_write_buf[(_console_write_buf_start + _console_write_buf_len++) % + WRITE_BUF_SZ] = b; - if ((suspend_cond_val = _console_write_buf_len == WRITE_BUF_SZ)) { - __asm__ __volatile__("wfi"); - _console_send_from_buf(); - } + CRITICAL_SECTION_LEAVE(daif_val); - CRITICAL_SECTION_LEAVE(daif_val); - } + // Re-enable transmit interrupt. + mini_uart_enable_tx_interrupt(); + + return true; +} + +static bool _console_send_two_bytes_nonblock(const unsigned char b1, + const unsigned char b2) { + if (_console_write_buf_len > WRITE_BUF_SZ - 2) + return false; + uint64_t daif_val; CRITICAL_SECTION_ENTER(daif_val); _console_write_buf[(_console_write_buf_start + _console_write_buf_len++) % - WRITE_BUF_SZ] = b; + WRITE_BUF_SZ] = b1; + _console_write_buf[(_console_write_buf_start + _console_write_buf_len++) % + WRITE_BUF_SZ] = b2; CRITICAL_SECTION_LEAVE(daif_val); // Re-enable transmit interrupt. mini_uart_enable_tx_interrupt(); + + return true; } // Mode switching is implemented by switching the vtables for the two primitive // operations, getc and putc. typedef struct { - unsigned char (*getc)(void); - void (*putc)(unsigned char); + int (*getc_nonblock)(void); + bool (*putc_nonblock)(unsigned char); } console_primop_vtable_t; -static unsigned char _console_getc_text_mode(void) { - const unsigned char c = _console_recv_byte(); +static int _console_getc_nonblock_text_mode(void) { + const int c = _console_recv_byte_nonblock(); + if (c < 0) + return c; return c == '\r' ? '\n' : c; } -static unsigned char _console_getc_binary_mode(void) { - return _console_recv_byte(); +static int _console_getc_nonblock_binary_mode(void) { + return _console_recv_byte_nonblock(); } -static void _console_putc_text_mode(const unsigned char c) { +static bool _console_putc_nonblock_text_mode(const unsigned char c) { if (c == '\n') { - _console_send_byte('\r'); - _console_send_byte('\n'); + return _console_send_two_bytes_nonblock('\r', '\n'); } else { - _console_send_byte(c); + return _console_send_byte_nonblock(c); } } -static void _console_putc_binary_mode(const unsigned char c) { - _console_send_byte(c); +static bool _console_putc_nonblock_binary_mode(const unsigned char c) { + return _console_send_byte_nonblock(c); } static const console_primop_vtable_t - _primop_vtable_text_mode = {.getc = _console_getc_text_mode, - .putc = _console_putc_text_mode}, - _primop_vtable_binary_mode = {.getc = _console_getc_binary_mode, - .putc = _console_putc_binary_mode}, + _primop_vtable_text_mode = {.getc_nonblock = + _console_getc_nonblock_text_mode, + .putc_nonblock = + _console_putc_nonblock_text_mode}, + _primop_vtable_binary_mode = {.getc_nonblock = + _console_getc_nonblock_binary_mode, + .putc_nonblock = + _console_putc_nonblock_binary_mode}, *_primop_vtable; // Public functions. @@ -186,13 +219,64 @@ void console_set_mode(const console_mode_t mode) { } } -unsigned char console_getc(void) { return _primop_vtable->getc(); } +unsigned char console_getc(void) { + int read_result = -1; + uint64_t daif_val; + + while (read_result < 0) { + CRITICAL_SECTION_ENTER(daif_val); + + if ((read_result = console_getc_nonblock()) < 0) { + __asm__ __volatile__("wfi"); + _console_recv_to_buf(); + } + + CRITICAL_SECTION_LEAVE(daif_val); + } + + return read_result; +} + +int console_getc_nonblock(void) { return _primop_vtable->getc_nonblock(); } + +size_t console_read_nonblock(void *const buf, const size_t count) { + unsigned char *const buf_c = (unsigned char *)buf; + + size_t n_chars_read; + int read_result; + + for (n_chars_read = 0; + n_chars_read < count && (read_result = console_getc_nonblock()) >= 0; + n_chars_read++) { + buf_c[n_chars_read] = read_result; + } + + return n_chars_read; +} unsigned char console_putc(const unsigned char c) { - _primop_vtable->putc(c); + bool putc_successful = false; + uint64_t daif_val; + + while (!putc_successful) { + CRITICAL_SECTION_ENTER(daif_val); + + if (!(putc_successful = console_putc_nonblock(c) >= 0)) { + __asm__ __volatile__("wfi"); + _console_send_from_buf(); + } + + CRITICAL_SECTION_LEAVE(daif_val); + } + return c; } +int console_putc_nonblock(const unsigned char c) { + const bool putc_successful = _primop_vtable->putc_nonblock(c); + return putc_successful ? c : -1; +} + size_t console_write(const void *const buf, const size_t count) { const unsigned char *const buf_c = buf; for (size_t i = 0; i < count; i++) { @@ -201,6 +285,21 @@ size_t console_write(const void *const buf, const size_t count) { return count; } +size_t console_write_nonblock(const void *const buf, const size_t count) { + const unsigned char *const buf_c = buf; + + size_t n_chars_written; + bool putc_successful; + + for (n_chars_written = 0; + n_chars_written < count && + (putc_successful = console_putc_nonblock(buf_c[n_chars_written]) >= 0); + n_chars_written++) + ; + + return n_chars_written; +} + void console_fputs(const char *const s) { for (const char *c = s; *c; c++) { console_putc(*c); @@ -237,6 +336,36 @@ int console_vprintf(const char *const restrict format, va_list ap) { return vprintf_generic(&_console_printf_vtable, NULL, format, ap); } +bool console_notify_read_ready(void (*const callback)(void *), + void *const arg) { + if (_console_read_notify_callback) + return false; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + _console_read_notify_callback = callback; + _console_read_notify_callback_arg = arg; + + CRITICAL_SECTION_LEAVE(daif_val); + return true; +} + +bool console_notify_write_ready(void (*const callback)(void *), + void *const arg) { + if (_console_write_notify_callback) + return false; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + _console_write_notify_callback = callback; + _console_write_notify_callback_arg = arg; + + CRITICAL_SECTION_LEAVE(daif_val); + return true; +} + void mini_uart_interrupt_handler(void) { _console_recv_to_buf(); _console_send_from_buf(); diff --git a/lab5/c/src/mem/malloc.c b/lab5/c/src/mem/malloc.c index 2d45257e5..5819828d0 100644 --- a/lab5/c/src/mem/malloc.c +++ b/lab5/c/src/mem/malloc.c @@ -258,8 +258,8 @@ static void _free_to_slab(slab_t *const slab, void *const ptr) { uint64_t daif_val; CRITICAL_SECTION_ENTER(daif_val); - const size_t slot_ix = - ((uintptr_t)ptr - (uintptr_t)slab) / (slab->metadata.slot_size * 16); + const size_t slot_ix = ((uintptr_t)ptr - (uintptr_t)slab->slots) / + (slab->metadata.slot_size * 16); // Adds the slab to its free list if it wasn't on its free list. diff --git a/lab5/c/src/mem/types.c b/lab5/c/src/mem/types.c new file mode 100644 index 000000000..15924b938 --- /dev/null +++ b/lab5/c/src/mem/types.c @@ -0,0 +1,44 @@ +#include "oscos/mem/types.h" + +#include + +#include "oscos/mem/malloc.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/utils/critical-section.h" + +shared_page_t *shared_page_init(const page_id_t page_id) { + shared_page_t *const shared_page = malloc(sizeof(shared_page_t)); + if (!shared_page) + return NULL; + + shared_page->ref_count = 1; + shared_page->page_id = page_id; + + return shared_page; +} + +shared_page_t *shared_page_clone(shared_page_t *const shared_page) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + shared_page->page_id++; + + CRITICAL_SECTION_LEAVE(daif_val); + + return shared_page; +} + +void shared_page_drop(shared_page_t *const shared_page) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + shared_page->page_id--; + const bool drop_page = shared_page->page_id == 0; + + CRITICAL_SECTION_LEAVE(daif_val); + + if (drop_page) { + free_pages(shared_page->page_id); + free(shared_page); + } +} diff --git a/lab5/c/src/sched/sched.c b/lab5/c/src/sched/sched.c index 9c953b0ce..d662ad33a 100644 --- a/lab5/c/src/sched/sched.c +++ b/lab5/c/src/sched/sched.c @@ -2,169 +2,513 @@ #include "oscos/sched.h" +#include "oscos/libc/string.h" #include "oscos/mem/malloc.h" #include "oscos/mem/page-alloc.h" #include "oscos/mem/vm.h" #include "oscos/utils/critical-section.h" +#include "oscos/utils/math.h" +#include "oscos/utils/rb.h" #define THREAD_STACK_ORDER 13 // 8KB. #define THREAD_STACK_BLOCK_ORDER (THREAD_STACK_ORDER - PAGE_ORDER) +#define USER_STACK_ORDER 23 // 8MB. +#define USER_STACK_BLOCK_ORDER (USER_STACK_ORDER - PAGE_ORDER) + void _sched_run_thread(thread_t *thread); -void thread_main(); +void thread_main(void); +noreturn void user_program_main(const void *init_pc, const void *init_user_sp, + const void *init_kernel_sp); +noreturn void fork_child_ret(void); + +static size_t _sched_next_tid = 1, _sched_next_pid = 1; +static thread_list_node_t _run_queue = {.prev = &_run_queue, + .next = &_run_queue}, + _zombies = {.prev = &_zombies, .next = &_zombies}; +static rb_node_t *_processes = NULL; + +static int _cmp_processes_by_pid(const process_t *const *const p1, + const process_t *const *const p2, void *_arg) { + (void)_arg; + + const size_t pid1 = (*p1)->id, pid2 = (*p2)->id; + return pid1 < pid2 ? -1 : pid1 > pid2 ? 1 : 0; +} + +static int _cmp_pid_and_processes_by_pid(const size_t *const pid, + const process_t *const *const process, + void *_arg) { + (void)_arg; + + const size_t pid1 = *pid, pid2 = (*process)->id; + return pid1 < pid2 ? -1 : pid1 > pid2 ? 1 : 0; +} + +static size_t _calc_text_block_order(const size_t text_len) { + const size_t text_n_pages = + (text_len + ((1 << PAGE_ORDER) - 1)) >> PAGE_ORDER; + const size_t actual_text_block_order = clog2(text_n_pages); + + // We don't know exactly how large the .bss section of a user program is, so + // we have to use a heuristic. We speculate that the .bss section is at most + // as large as the remaining parts (.text, .rodata, and .data sections) of the + // user program. + const size_t allocating_text_block_order = actual_text_block_order + 1; + + return allocating_text_block_order; +} + +static size_t _alloc_tid(void) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const size_t result = _sched_next_tid++; + + CRITICAL_SECTION_LEAVE(daif_val); + return result; +} + +static size_t _alloc_pid(void) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const size_t result = _sched_next_pid++; + + CRITICAL_SECTION_LEAVE(daif_val); + return result; +} + +static void _add_thread_to_queue(thread_t *const thread, + thread_list_node_t *const queue) { + thread_list_node_t *const thread_node = &thread->list_node; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + thread_list_node_t *const last_node = queue->prev; + thread_node->prev = last_node; + last_node->next = thread_node; + thread_node->next = queue; + queue->prev = thread_node; + + CRITICAL_SECTION_LEAVE(daif_val); +} + +static void _add_thread_to_run_queue(thread_t *const thread) { + _add_thread_to_queue(thread, &_run_queue); +} + +void _remove_thread_from_queue(thread_t *const thread) { + thread_list_node_t *const thread_node = &thread->list_node; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + thread_node->prev->next = thread_node->next; + thread_node->next->prev = thread_node->prev; + + CRITICAL_SECTION_LEAVE(daif_val); +} + +thread_t *_remove_first_thread_from_queue(thread_list_node_t *const queue) { + thread_t *result; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); -static size_t _sched_next_tid = 1; -static thread_list_node_t _run_queue, _zombies; + if (queue->next == queue) { + result = NULL; + } else { + thread_list_node_t *const first_node = queue->next; + queue->next = first_node->next; + first_node->next->prev = queue; + result = (thread_t *)((char *)first_node - offsetof(thread_t, list_node)); + } + + CRITICAL_SECTION_LEAVE(daif_val); + return result; +} bool sched_init(void) { - // Creates the idle thread. + // Create the idle thread. thread_t *const idle_thread = malloc(sizeof(thread_t)); if (!idle_thread) return false; idle_thread->id = 0; - - // Initializes the run queue. Initially, the run queue consists solely of the - // idle thread. - - _run_queue.prev = _run_queue.next = &idle_thread->list_node; - idle_thread->list_node.prev = idle_thread->list_node.next = &_run_queue; + idle_thread->process = NULL; + idle_thread->ctx.fp_simd_ctx = NULL; // Name the current thread the idle thread. __asm__ __volatile__("msr tpidr_el1, %0" : : "r"(idle_thread)); - // Initialize the zombie list. Initially, there are no zombies. - - _zombies.prev = _zombies.next = &_zombies; - return true; } bool thread_create(void (*const task)(void *), void *const arg) { - // Create the thread structure. + // Allocate memory. thread_t *const thread = malloc(sizeof(thread_t)); if (!thread) return false; - thread->id = _sched_next_tid++; - thread->ctx.x19 = (uint64_t)(uintptr_t)task; - thread->ctx.x20 = (uint64_t)(uintptr_t)arg; - thread->ctx.pc = (uint64_t)(uintptr_t)thread_main; - - // Allocate the stack for the new thread. - const spage_id_t stack_page_id = alloc_pages(THREAD_STACK_BLOCK_ORDER); if (stack_page_id < 0) { free(thread); return false; } + // Initialize the thread structure. + + thread->id = _alloc_tid(); + thread->process = NULL; + thread->ctx.r19 = (uint64_t)(uintptr_t)task; + thread->ctx.r20 = (uint64_t)(uintptr_t)arg; + thread->ctx.pc = (uint64_t)(uintptr_t)thread_main; + thread->ctx.fp_simd_ctx = NULL; + thread->stack_page_id = stack_page_id; void *const init_sp = (char *)pa_to_kernel_va(page_id_to_pa(stack_page_id)) + (1 << (THREAD_STACK_BLOCK_ORDER + PAGE_ORDER)); - thread->ctx.sp = (uint64_t)(uintptr_t)init_sp; + thread->ctx.kernel_sp = (uint64_t)(uintptr_t)init_sp; // Put the thread into the end of the run queue. + _add_thread_to_run_queue(thread); + + return true; +} + +thread_t * +_sched_move_thread_to_queue_and_pick_thread(thread_t *const thread, + thread_list_node_t *const queue) { + _add_thread_to_queue(thread, queue); + return _remove_first_thread_from_queue(&_run_queue); +} + +void thread_exit(void) { + XCPT_MASK_ALL(); + + _sched_run_thread( + _sched_move_thread_to_queue_and_pick_thread(current_thread(), &_zombies)); + __builtin_unreachable(); +} + +thread_t *current_thread(void) { + thread_t *result; + __asm__ __volatile__("mrs %0, tpidr_el1" : "=r"(result)); + return result; +} + +bool process_create(void) { + // Allocate memory. + + process_t *const process = malloc(sizeof(process_t)); + if (!process) // Out of memory. + return false; + + const spage_id_t user_stack_page_id = alloc_pages(USER_STACK_BLOCK_ORDER); + if (user_stack_page_id < 0) { // Out of memory. + free(process); + return false; + } + + shared_page_t *const user_stack_page = shared_page_init(user_stack_page_id); + if (!user_stack_page) { + free_pages(user_stack_page_id); + free(process); + return false; + } + + thread_fp_simd_ctx_t *const fp_simd_ctx = + malloc(sizeof(thread_fp_simd_ctx_t)); + if (!fp_simd_ctx) { + shared_page_drop(user_stack_page); + free(process); + return false; + } + + // Set thread/process data. + + thread_t *const curr_thread = current_thread(); + + process->id = _alloc_pid(); + process->user_stack_page = user_stack_page; + process->main_thread = curr_thread; + curr_thread->process = process; + curr_thread->ctx.fp_simd_ctx = fp_simd_ctx; + + // Zero the stack pages. + + void *user_stack_start = pa_to_kernel_va(page_id_to_pa(user_stack_page_id)); + memset(user_stack_start, 0, 1 << USER_STACK_ORDER); + + // Add the process to the process BST. + uint64_t daif_val; CRITICAL_SECTION_ENTER(daif_val); - thread_list_node_t *const last_node = _run_queue.prev; - thread->list_node.prev = last_node; - last_node->next = &thread->list_node; - thread->list_node.next = &_run_queue; - _run_queue.prev = &thread->list_node; + rb_insert(&_processes, sizeof(process_t *), &process, + (int (*)(const void *, const void *, void *))_cmp_processes_by_pid, + NULL); CRITICAL_SECTION_LEAVE(daif_val); return true; } -void thread_exit(void) { - // Move the current thread from the run queue to the zombie list. +static void _exec_generic(const void *const text_start, const size_t text_len, + const bool free_text_page) { + thread_t *const curr_thread = current_thread(); + process_t *const curr_process = curr_thread->process; - uint64_t daif_val; - CRITICAL_SECTION_ENTER(daif_val); + // Allocate memory. - thread_list_node_t *const current_thread_node = _run_queue.next, - *const next_node = current_thread_node->next; - _run_queue.next = next_node; - next_node->prev = &_run_queue; + const size_t text_block_order = _calc_text_block_order(text_len); + const spage_id_t text_page_id = alloc_pages(text_block_order); + if (text_page_id < 0) { + return; + } - thread_list_node_t *const last_zombie = _zombies.prev; - current_thread_node->prev = last_zombie; - last_zombie->next = current_thread_node; - current_thread_node->next = &_zombies; - _zombies.prev = current_thread_node; + shared_page_t *const text_page = shared_page_init(text_page_id); + if (!text_page) { + free_pages(text_page_id); + return; + } - CRITICAL_SECTION_LEAVE(daif_val); + const spage_id_t user_stack_page_id = alloc_pages(USER_STACK_BLOCK_ORDER); + if (user_stack_page_id < 0) { + shared_page_drop(text_page); + return; + } - // Pick a new thread to run. + shared_page_t *const user_stack_page = shared_page_init(user_stack_page_id); + if (!user_stack_page) { + free_pages(user_stack_page_id); + shared_page_drop(text_page); + return; + } - thread_t *const next_thread = - (thread_t *)((char *)next_node - offsetof(thread_t, list_node)); - _sched_run_thread(next_thread); - __builtin_unreachable(); + // Free old pages. + + if (free_text_page) { + shared_page_drop(curr_process->text_page); + } + + shared_page_drop(curr_process->user_stack_page); + + // Set process data. + + curr_process->text_page = text_page; + curr_process->user_stack_page = user_stack_page; + + // Copy the user program. + + void *const user_program_start = pa_to_kernel_va(page_id_to_pa(text_page_id)); + memcpy(user_program_start, text_start, text_len); + + // Zero the remaining spaces of the text pages. + memset((char *)user_program_start + text_len, 0, + (1 << (text_block_order + PAGE_ORDER)) - text_len); + + // Zero the stack pages. + + void *user_stack_start = pa_to_kernel_va(page_id_to_pa(user_stack_page_id)); + memset(user_stack_start, 0, 1 << USER_STACK_ORDER); + + // Run the user program. + + void *const user_stack_end = + (char *)user_stack_start + (1 << USER_STACK_ORDER), + *const kernel_stack_end = (char *)pa_to_kernel_va(page_id_to_pa( + curr_thread->stack_page_id)) + + (1 << THREAD_STACK_ORDER); + user_program_main(user_program_start, user_stack_end, kernel_stack_end); } -thread_t *current_thread(void) { - thread_t *result; - __asm__ __volatile__("mrs %0, tpidr_el1" : "=r"(result)); - return result; +void exec_first(const void *const text_start, const size_t text_len) { + _exec_generic(text_start, text_len, false); +} + +void exec(const void *const text_start, const size_t text_len) { + _exec_generic(text_start, text_len, true); } static void _thread_cleanup(thread_t *const thread) { + if (thread->process) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + rb_delete(&_processes, &thread->process->id, + (int (*)(const void *, const void *, + void *))_cmp_pid_and_processes_by_pid, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + shared_page_drop(thread->process->text_page); + shared_page_drop(thread->process->user_stack_page); + free(thread->process); + } free_pages(thread->stack_page_id); free(thread); } -void kill_zombies(void) { - for (;;) { - uint64_t daif_val; - CRITICAL_SECTION_ENTER(daif_val); +process_t *fork(const extended_trap_frame_t *const trap_frame) { + thread_t *const curr_thread = current_thread(); + process_t *const curr_process = curr_thread->process; - if (_zombies.next == &_zombies) { // No more zombies. - CRITICAL_SECTION_LEAVE(daif_val); - break; - } + // Allocate memory. - thread_list_node_t *const first_zombie_node = _zombies.next, - *const next_zombie_node = first_zombie_node->next; + thread_t *const new_thread = malloc(sizeof(thread_t)); + if (!new_thread) + return NULL; - _zombies.next = next_zombie_node; - next_zombie_node->prev = &_zombies; + process_t *const new_process = malloc(sizeof(process_t)); + if (!new_process) { + free(new_thread); + return NULL; + } - CRITICAL_SECTION_LEAVE(daif_val); + const spage_id_t kernel_stack_page_id = alloc_pages(THREAD_STACK_BLOCK_ORDER); + if (kernel_stack_page_id < 0) { + free(new_process); + free(new_thread); + return NULL; + } + + thread_fp_simd_ctx_t *const fp_simd_ctx = + malloc(sizeof(thread_fp_simd_ctx_t)); + if (!fp_simd_ctx) { + free_pages(kernel_stack_page_id); + free(new_process); + free(new_thread); + return NULL; + } + + // Set data. + + new_thread->id = _alloc_tid(); + new_thread->stack_page_id = kernel_stack_page_id; + new_thread->process = new_process; + + new_process->id = _alloc_pid(); + new_process->text_page = shared_page_clone(curr_process->text_page); + new_process->user_stack_page = + shared_page_clone(curr_process->user_stack_page); + new_process->main_thread = new_thread; + + // Set execution context. + + void *const kernel_stack_end = + (char *)pa_to_kernel_va(page_id_to_pa(kernel_stack_page_id)) + + (1 << THREAD_STACK_ORDER), + *const init_kernel_sp = + (char *)kernel_stack_end - sizeof(extended_trap_frame_t); + + memcpy(&new_thread->ctx, &curr_thread->ctx, sizeof(thread_ctx_t)); + new_thread->ctx.pc = (uint64_t)(uintptr_t)fork_child_ret; + new_thread->ctx.kernel_sp = (uint64_t)(uintptr_t)init_kernel_sp; + new_thread->ctx.fp_simd_ctx = fp_simd_ctx; + memcpy(fp_simd_ctx, curr_thread->ctx.fp_simd_ctx, + sizeof(thread_fp_simd_ctx_t)); + + memcpy(init_kernel_sp, trap_frame, sizeof(extended_trap_frame_t)); + + // Add the new thread to the end of the run queue and the process to the + // process BST. Note that these two steps can be done in either order but must + // not be interrupted in between, since: + // - If the former is done before the latter and the newly-created thread is + // scheduled between the two steps, then the new thread won't be able to + // find its own process. + // - If the latter is done before the former and the newly-created process is + // killed between the two steps, then a freed thread_t instance (i.e., + // `new_thread`) will be added onto the run queue – a use-after-free bug. - thread_t *const zombie = - (thread_t *)((char *)first_zombie_node - offsetof(thread_t, list_node)); + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + _add_thread_to_run_queue(new_thread); + + rb_insert(&_processes, sizeof(process_t *), &new_process, + (int (*)(const void *, const void *, void *))_cmp_processes_by_pid, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + return new_process; +} + +void kill_zombies(void) { + thread_t *zombie; + while ((zombie = _remove_first_thread_from_queue(&_zombies))) { _thread_cleanup(zombie); } } -thread_t *_sched_pick_thread(void) { - // Move the current thread to the end of the run queue. +void schedule(void) { suspend_to_wait_queue(&_run_queue); } +void add_all_threads_to_run_queue(thread_list_node_t *const wait_queue) { + thread_t *thread; + while ((thread = _remove_first_thread_from_queue(wait_queue))) { + _add_thread_to_run_queue(thread); + } +} + +process_t *get_process_by_id(const size_t pid) { uint64_t daif_val; CRITICAL_SECTION_ENTER(daif_val); - thread_list_node_t *const current_thread_node = _run_queue.next, - *const next_node = current_thread_node->next; - _run_queue.next = next_node; - next_node->prev = &_run_queue; + process_t *const result = + (process_t *)rb_search(_processes, &pid, + (int (*)(const void *, const void *, + void *))_cmp_pid_and_processes_by_pid, + NULL); - thread_list_node_t *const last_node = _run_queue.prev; - current_thread_node->prev = last_node; - last_node->next = current_thread_node; - current_thread_node->next = &_run_queue; - _run_queue.prev = current_thread_node; + CRITICAL_SECTION_LEAVE(daif_val); - thread_t *const next_thread = - (thread_t *)((char *)_run_queue.next - offsetof(thread_t, list_node)); + return result; +} - CRITICAL_SECTION_LEAVE(daif_val); - return next_thread; +void kill_process(process_t *const process) { + if (process == current_thread()->process) { + thread_exit(); + } else { + thread_t *const thread = process->main_thread; + + // Zombify the main thread. + + _remove_thread_from_queue(thread); + _add_thread_to_queue(thread, &_zombies); + } +} + +void kill_all_processes(void) { + // Kill all processes other than the current one. + for (;;) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + process_t *process_to_kill = *(process_t **)_processes->payload; + if (process_to_kill == current_thread()->process) { + if (_processes->children[0]) { + process_to_kill = *(process_t **)_processes->children[0]->payload; + } else if (_processes->children[1]) { + process_to_kill = *(process_t **)_processes->children[1]->payload; + } else { // Only the current process left. + CRITICAL_SECTION_LEAVE(daif_val); + break; + } + } + + CRITICAL_SECTION_LEAVE(daif_val); + + kill_process(process_to_kill); + } + + // Kill the current process. + thread_exit(); } diff --git a/lab5/c/src/sched/schedule.S b/lab5/c/src/sched/schedule.S index 9bbf37240..190ee9a47 100644 --- a/lab5/c/src/sched/schedule.S +++ b/lab5/c/src/sched/schedule.S @@ -1,37 +1,109 @@ .section ".text" -schedule: - mrs x0, tpidr_el1 - - stp x19, x20, [x0, 16 + 0 * 16] - stp x21, x22, [x0, 16 + 1 * 16] - stp x23, x24, [x0, 16 + 2 * 16] - stp x25, x26, [x0, 16 + 3 * 16] - stp x27, x28, [x0, 16 + 4 * 16] - stp x29, x30, [x0, 16 + 5 * 16] +suspend_to_wait_queue: + // Enter critical section. + + msr daifset, 0xf + + // Get current thread instance. + + mrs x2, tpidr_el1 + + // Save integer context. + + stp x19, x20, [x2, 16 + 0 * 16] + stp x21, x22, [x2, 16 + 1 * 16] + stp x23, x24, [x2, 16 + 2 * 16] + stp x25, x26, [x2, 16 + 3 * 16] + stp x27, x28, [x2, 16 + 4 * 16] + stp x29, x30, [x2, 16 + 5 * 16] mov x1, sp - str x1, [x0, 16 + 6 * 16] + mrs x3, sp_el0 + stp x1, x3, [x2, 16 + 6 * 16] + + // Save FP/SIMD context, if there is one. + + ldr x1, [x2, 16 + 7 * 16] + cbz x1, .Lafter_save_fp_ctx - bl _sched_pick_thread + stp q0, q1, [x1, 0 * 32] + stp q2, q3, [x1, 1 * 32] + stp q4, q5, [x1, 2 * 32] + stp q6, q7, [x1, 3 * 32] + stp q8, q9, [x1, 4 * 32] + stp q10, q11, [x1, 5 * 32] + stp q12, q13, [x1, 6 * 32] + stp q14, q15, [x1, 7 * 32] + stp q16, q17, [x1, 8 * 32] + stp q18, q19, [x1, 9 * 32] + stp q20, q21, [x1, 10 * 32] + stp q22, q23, [x1, 11 * 32] + stp q24, q25, [x1, 12 * 32] + stp q26, q27, [x1, 13 * 32] + stp q28, q29, [x1, 14 * 32] + stp q30, q31, [x1, 15 * 32] + add x1, x1, 16 * 32 + mrs x3, fpcr + mrs x4, fpsr + stp x3, x4, [x1] + +.Lafter_save_fp_ctx: + mov x1, x0 + mov x0, x2 + bl _sched_move_thread_to_queue_and_pick_thread // Fallthrough. -.size schedule, . - schedule -.type schedule, function -.global schedule +.size suspend_to_wait_queue, . - suspend_to_wait_queue +.type suspend_to_wait_queue, function +.global suspend_to_wait_queue _sched_run_thread: + // Restore integer context. + ldp x19, x20, [x0, 16 + 0 * 16] ldp x21, x22, [x0, 16 + 1 * 16] ldp x23, x24, [x0, 16 + 2 * 16] ldp x25, x26, [x0, 16 + 3 * 16] ldp x27, x28, [x0, 16 + 4 * 16] ldp x29, x30, [x0, 16 + 5 * 16] - ldr x1, [x0, 16 + 6 * 16] + ldp x1, x2, [x0, 16 + 6 * 16] mov sp, x1 + msr sp_el0, x2 + + // Restore FP/SIMD context, if there is one. + + ldr x1, [x0, 16 + 7 * 16] + cbz x1, .Lafter_load_fp_ctx + ldp q0, q1, [x1, 0 * 32] + ldp q2, q3, [x1, 1 * 32] + ldp q4, q5, [x1, 2 * 32] + ldp q6, q7, [x1, 3 * 32] + ldp q8, q9, [x1, 4 * 32] + ldp q10, q11, [x1, 5 * 32] + ldp q12, q13, [x1, 6 * 32] + ldp q14, q15, [x1, 7 * 32] + ldp q16, q17, [x1, 8 * 32] + ldp q18, q19, [x1, 9 * 32] + ldp q20, q21, [x1, 10 * 32] + ldp q22, q23, [x1, 11 * 32] + ldp q24, q25, [x1, 12 * 32] + ldp q26, q27, [x1, 13 * 32] + ldp q28, q29, [x1, 14 * 32] + ldp q30, q31, [x1, 15 * 32] + add x1, x1, 16 * 32 + ldp x2, x3, [x1] + msr fpcr, x2 + msr fpcr, x3 + +.Lafter_load_fp_ctx: msr tpidr_el1, x0 + // Unmask interrupts. + + msr daifclr, 0xf + ret .size _sched_run_thread, . - _sched_run_thread diff --git a/lab5/c/src/sched/user-program-main.S b/lab5/c/src/sched/user-program-main.S new file mode 100644 index 000000000..bfc44b8fb --- /dev/null +++ b/lab5/c/src/sched/user-program-main.S @@ -0,0 +1,93 @@ +.section ".text" + +user_program_main: + // We don't need to synchronize the data cache and the instruction cache + // since we haven't enabled any of them. Nonetheless, we still have to + // synchronize the fetched instruction stream using the `isb` instruction. + isb + + // Mask the interrupt to prevent spsr_el1 and elr_el1 from being clobbered + // by ISRs. + msr daifset, 0xf + + msr elr_el1, x0 + msr sp_el0, x1 + mov sp, x2 + + // - Unask all interrupts. + // - AArch64 execution state. + // - EL0t. + msr spsr_el1, xzr + + // Zero the integer registers. + mov x0, 0 + mov x1, 0 + mov x2, 0 + mov x3, 0 + mov x4, 0 + mov x5, 0 + mov x6, 0 + mov x7, 0 + mov x8, 0 + mov x9, 0 + mov x10, 0 + mov x11, 0 + mov x12, 0 + mov x13, 0 + mov x14, 0 + mov x15, 0 + mov x16, 0 + mov x17, 0 + mov x18, 0 + mov x19, 0 + mov x20, 0 + mov x21, 0 + mov x22, 0 + mov x23, 0 + mov x24, 0 + mov x25, 0 + mov x26, 0 + mov x27, 0 + mov x28, 0 + mov x29, 0 + mov lr, 0 + + // Zero the FP/SIMD registers. + movi d0, 0 + movi d1, 0 + movi d2, 0 + movi d3, 0 + movi d4, 0 + movi d5, 0 + movi d6, 0 + movi d7, 0 + movi d8, 0 + movi d9, 0 + movi d10, 0 + movi d11, 0 + movi d12, 0 + movi d13, 0 + movi d14, 0 + movi d15, 0 + movi d16, 0 + movi d17, 0 + movi d18, 0 + movi d19, 0 + movi d20, 0 + movi d21, 0 + movi d22, 0 + movi d23, 0 + movi d24, 0 + movi d25, 0 + movi d26, 0 + movi d27, 0 + movi d28, 0 + movi d29, 0 + movi d30, 0 + movi d31, 0 + + eret + +.type user_program_main, function +.size user_program_main, . - user_program_main +.global user_program_main diff --git a/lab5/c/src/shell.c b/lab5/c/src/shell.c index b3e033a9f..a87245fd4 100644 --- a/lab5/c/src/shell.c +++ b/lab5/c/src/shell.c @@ -11,8 +11,8 @@ #include "oscos/libc/string.h" #include "oscos/mem/malloc.h" #include "oscos/mem/page-alloc.h" +#include "oscos/sched.h" #include "oscos/timer/timeout.h" -#include "oscos/user-program.h" #include "oscos/utils/time.h" #define MAX_CMD_LEN 78 @@ -159,12 +159,15 @@ static void _shell_do_cmd_exec(void) { return; } - if (!load_user_program(user_program_start, user_program_len)) { - console_puts("oscsh: exec: user program too long"); + if (!process_create()) { + console_puts("oscsh: exec: out of memory"); return; } - run_user_program(); + exec_first(user_program_start, user_program_len); + + // If execution reaches here, then exec failed. + console_puts("oscsh: exec: out of memory"); } static void _shell_cmd_set_timeout_timer_callback(char *const message) { diff --git a/lab5/c/src/start.S b/lab5/c/src/start.S index 26fe14ecb..873f1db18 100644 --- a/lab5/c/src/start.S +++ b/lab5/c/src/start.S @@ -30,6 +30,10 @@ _start: mov x1, 0x300000 msr cpacr_el1, x1 + // Allow user programs to access the timer. + mov x1, 0x1 + msr cntkctl_el1, x1 + // Clear the .bss section. ldr x1, =_sbss diff --git a/lab5/c/src/user-program.c b/lab5/c/src/user-program.c deleted file mode 100644 index 7f117a759..000000000 --- a/lab5/c/src/user-program.c +++ /dev/null @@ -1,82 +0,0 @@ -#include "oscos/user-program.h" - -#include "oscos/console.h" -#include "oscos/libc/string.h" -#include "oscos/mem/page-alloc.h" -#include "oscos/mem/vm.h" -#include "oscos/timer/timeout.h" -#include "oscos/utils/math.h" -#include "oscos/utils/time.h" - -#define USER_STACK_ORDER 23 // 8MB. -#define USER_STACK_BLOCK_ORDER (USER_STACK_ORDER - PAGE_ORDER) - -static void *_user_program_start, *_user_stack_end; - -bool load_user_program(const void *const start, const size_t len) { - // Allocate page frames for the user program. - - const size_t user_program_n_pages = - (len + ((1 << PAGE_ORDER) - 1)) >> PAGE_ORDER; - const size_t user_program_block_order = clog2(user_program_n_pages); - const spage_id_t user_program_page_id = alloc_pages(user_program_block_order); - if (user_program_page_id < 0) { // Out of memory. - return false; - } - _user_program_start = pa_to_kernel_va(page_id_to_pa(user_program_page_id)); - - // Allocate page frames for the user stack. - - const spage_id_t user_stack_page_id = alloc_pages(USER_STACK_BLOCK_ORDER); - if (user_stack_page_id < 0) { // Out of memory. - free_pages(user_program_page_id); - return false; - } - _user_stack_end = (char *)pa_to_kernel_va(page_id_to_pa(user_stack_page_id)) + - (1 << USER_STACK_ORDER); - - // Copy the user program. - - memcpy(_user_program_start, start, len); - return true; -} - -static void _core_timer_el0_interrupt_handler(void *const _arg) { - (void)_arg; - - uint64_t core_timer_freq; - __asm__ __volatile__("mrs %0, cntfrq_el0" : "=r"(core_timer_freq)); - core_timer_freq &= 0xffffffff; - - // Print the number of seconds since boot. - - uint64_t timer_val; - __asm__ __volatile__("mrs %0, cntpct_el0" : "=r"(timer_val)); - - console_printf("# seconds since boot: %u\n", - (unsigned)(timer_val / core_timer_freq)); - - // Set the time to interrupt to 2 seconds later. - - timeout_add_timer(_core_timer_el0_interrupt_handler, NULL, 2 * NS_PER_SEC); -} - -void run_user_program(void) { - // We don't need to synchronize the data cache and the instruction cache since - // we haven't enabled any of them. Nonetheless, we still have to synchronize - // the fetched instruction stream using the `isb` instruction. - __asm__ __volatile__("isb"); - - timeout_add_timer(_core_timer_el0_interrupt_handler, NULL, 2 * NS_PER_SEC); - - // - Unask all interrupts. - // - AArch64 execution state. - // - EL0t. - __asm__ __volatile__("msr spsr_el1, xzr"); - - __asm__ __volatile__("msr elr_el1, %0" : : "r"(_user_program_start)); - __asm__ __volatile__("msr sp_el0, %0" : : "r"(_user_stack_end)); - __asm__ __volatile__("eret"); - - __builtin_unreachable(); -} diff --git a/lab5/c/src/utils/rb.c b/lab5/c/src/utils/rb.c new file mode 100644 index 000000000..391fbbb6e --- /dev/null +++ b/lab5/c/src/utils/rb.c @@ -0,0 +1,83 @@ +#include "oscos/utils/rb.h" + +#include "oscos/libc/string.h" +#include "oscos/mem/malloc.h" + +// TODO: Properly implement a red-black tree. + +const void *rb_search(const rb_node_t *const root, + const void *const restrict key, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + const rb_node_t *curr = root; + while (curr) { + const int compar_result = compar(key, curr->payload, arg); + if (compar_result == 0) + break; + curr = curr->children[compar_result > 0]; + } + + return curr ? curr->payload : NULL; +} + +bool rb_insert(rb_node_t **const root, const size_t size, + const void *const restrict item, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + rb_node_t **curr = root; + while (*curr) { + const int compar_result = compar(item, (*curr)->payload, arg); + if (compar_result == 0) + break; + curr = &(*curr)->children[compar_result > 0]; + } + + if (!*curr) { + *curr = malloc(sizeof(rb_node_t) + size); + if (!*curr) + return false; + + (*curr)->children[0] = (*curr)->children[1] = NULL; + } + + memcpy((*curr)->payload, item, size); + + return true; +} + +static rb_node_t **_rb_minimum(rb_node_t **const node) { + rb_node_t **curr = node; + while ((*curr)->children[0]) { + curr = &(*curr)->children[0]; + } + return curr; +} + +void rb_delete(rb_node_t **const root, const void *const restrict key, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + rb_node_t **curr = root; + while (*curr) { + const int compar_result = compar(key, (*curr)->payload, arg); + if (compar_result == 0) + break; + curr = &(*curr)->children[compar_result > 0]; + } + + rb_node_t *const curr_node = *curr; + if (!curr_node->children[0]) { + *curr = curr_node->children[1]; + } else if (!curr_node->children[1]) { + *curr = curr_node->children[0]; + } else { + rb_node_t **const min_right = _rb_minimum(&curr_node->children[1]), + *const min_right_node = *min_right; + + *min_right = min_right_node->children[1]; + *curr = min_right_node; + min_right_node->children[0] = curr_node->children[0]; + min_right_node->children[1] = curr_node->children[1]; + } + + free(curr_node); +} diff --git a/lab5/c/src/xcpt/svc-handler.S b/lab5/c/src/xcpt/svc-handler.S new file mode 100644 index 000000000..dadc36d22 --- /dev/null +++ b/lab5/c/src/xcpt/svc-handler.S @@ -0,0 +1,50 @@ +.section ".text" + +xcpt_svc_handler: + // Save lr. + str lr, [sp, -32]! + + // Save spsr_el1 and elr_el1 since they can be clobbered by other ISRs. + mrs x10, spsr_el1 + mrs x11, elr_el1 + stp x10, x11, [sp, 16] + + // Unmask interrupts. + msr daifclr, 0xf + + // Check the system call number. + ubfx x9, x9, 0, 16 + cbnz x9, .Lenosys + cmp x8, 8 + b.hi .Lenosys + + // Table-jump to the system call function. + adr lr, .Lend + adr x9, syscall_table + add x9, x9, x8, lsl 2 + br x9 + +.Lenosys: + bl sys_enosys + +.Lend: + // Store the result into the trap frame. + str x0, [sp, 32] + + // Mask the interrupt since we don't want ISRs to clobber spsr_el1 and + // esr_el1 while we're restoring them. + msr daifset, 0xf + + // Restore spsr_el1 and elr_el1. + ldp x0, x1, [sp, 16] + msr spsr_el1, x0 + msr elr_el1, x1 + + // Restore lr. + ldr lr, [sp], 32 + + ret + +.type xcpt_svc_handler, function +.size xcpt_svc_handler, . - xcpt_svc_handler +.global xcpt_svc_handler diff --git a/lab5/c/src/xcpt/syscall-table.S b/lab5/c/src/xcpt/syscall-table.S new file mode 100644 index 000000000..dd8a81e58 --- /dev/null +++ b/lab5/c/src/xcpt/syscall-table.S @@ -0,0 +1,14 @@ +.section ".text" + +syscall_table: + b sys_getpid + b sys_uart_read + b sys_uart_write + b sys_exec + b sys_fork + b sys_exit + b sys_mbox_call + b sys_kill + +.size syscall_table, . - syscall_table +.global syscall_table diff --git a/lab5/c/src/xcpt/syscall/enosys.c b/lab5/c/src/xcpt/syscall/enosys.c new file mode 100644 index 000000000..32ae47c8f --- /dev/null +++ b/lab5/c/src/xcpt/syscall/enosys.c @@ -0,0 +1,3 @@ +#include "oscos/uapi/errno.h" + +int sys_enosys(void) { return -ENOSYS; } diff --git a/lab5/c/src/xcpt/syscall/exec.c b/lab5/c/src/xcpt/syscall/exec.c new file mode 100644 index 000000000..b8a10cf2e --- /dev/null +++ b/lab5/c/src/xcpt/syscall/exec.c @@ -0,0 +1,44 @@ +#include +#include + +#include "oscos/initrd.h" +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" + +int sys_exec(const char *const name, char *const argv[const]) { + // TODO: Handle this. + (void)argv; + + if (!initrd_is_init()) { + // Act as if there are no files at all. + return -ENOENT; + } + + const cpio_newc_entry_t *const entry = initrd_find_entry_by_pathname(name); + if (!entry) { + return -ENOENT; + } + + const void *user_program_start; + size_t user_program_len; + + const uint32_t mode = CPIO_NEWC_HEADER_VALUE(entry, mode); + const uint32_t file_type = mode & CPIO_NEWC_MODE_FILE_TYPE_MASK; + if (file_type == CPIO_NEWC_MODE_FILE_TYPE_REG) { + user_program_start = CPIO_NEWC_FILE_DATA(entry); + user_program_len = CPIO_NEWC_FILESIZE(entry); + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_DIR) { + return -EISDIR; + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_LNK) { + // TODO: Handle symbolic link. + return -ELOOP; + } else { // Unknown file type. + // Just treat it as an I/O error. + return -EIO; + } + + exec(user_program_start, user_program_len); + + // If execution reaches here, then exec failed. + return -ENOMEM; +} diff --git a/lab5/c/src/xcpt/syscall/exit.c b/lab5/c/src/xcpt/syscall/exit.c new file mode 100644 index 000000000..d06756508 --- /dev/null +++ b/lab5/c/src/xcpt/syscall/exit.c @@ -0,0 +1,3 @@ +#include "oscos/sched.h" + +void sys_exit(void) { thread_exit(); } diff --git a/lab5/c/src/xcpt/syscall/fork-child-ret.S b/lab5/c/src/xcpt/syscall/fork-child-ret.S new file mode 100644 index 000000000..ae568d02a --- /dev/null +++ b/lab5/c/src/xcpt/syscall/fork-child-ret.S @@ -0,0 +1,29 @@ +.section ".text" + +fork_child_ret: + // Mask the interrupt to prevent spsr_el1 and elr_el1 from being clobbered + // by ISRs. + msr daifset, 0xf + + ldp x0, x1, [sp] + msr spsr_el1, x0 + msr elr_el1, x1 + + mov x0, 0 + ldr x1, [sp, 16 + 0 * 16 + 8] + ldp x2, x3, [sp, 16 + 1 * 16] + ldp x4, x5, [sp, 16 + 2 * 16] + ldp x6, x7, [sp, 16 + 3 * 16] + ldp x8, x9, [sp, 16 + 4 * 16] + ldp x10, x11, [sp, 16 + 5 * 16] + ldp x12, x13, [sp, 16 + 6 * 16] + ldp x14, x15, [sp, 16 + 7 * 16] + ldp x16, x17, [sp, 16 + 8 * 16] + ldp x18, lr, [sp, 16 + 9 * 16] + + add sp, sp, 16 + 10 * 16 + eret + +.type fork_child_ret, function +.size fork_child_ret, . - fork_child_ret +.global fork_child_ret diff --git a/lab5/c/src/xcpt/syscall/fork-impl.c b/lab5/c/src/xcpt/syscall/fork-impl.c new file mode 100644 index 000000000..af40a4c2d --- /dev/null +++ b/lab5/c/src/xcpt/syscall/fork-impl.c @@ -0,0 +1,11 @@ +#include "oscos/uapi/errno.h" + +#include "oscos/sched.h" + +int sys_fork_impl(const extended_trap_frame_t *const trap_frame) { + process_t *const new_process = fork(trap_frame); + if (!new_process) + return -ENOMEM; + + return new_process->id; +} diff --git a/lab5/c/src/xcpt/syscall/fork.S b/lab5/c/src/xcpt/syscall/fork.S new file mode 100644 index 000000000..c9bb17fed --- /dev/null +++ b/lab5/c/src/xcpt/syscall/fork.S @@ -0,0 +1,48 @@ +.section ".text" + +sys_fork: + str lr, [sp, -16]! + + // Save integer context. + mrs x0, tpidr_el1 + stp x19, x20, [x0, 16 + 0 * 16] + stp x21, x22, [x0, 16 + 1 * 16] + stp x23, x24, [x0, 16 + 2 * 16] + stp x25, x26, [x0, 16 + 3 * 16] + stp x27, x28, [x0, 16 + 4 * 16] + str x29, [x0, 16 + 5 * 16] + mrs x1, sp_el0 + str x1, [x0, 16 + 6 * 16 + 8] + + // Save FP/SIMD context. + ldr x0, [x0, 16 + 7 * 16] + stp q0, q1, [x0, 0 * 32] + stp q2, q3, [x0, 1 * 32] + stp q4, q5, [x0, 2 * 32] + stp q6, q7, [x0, 3 * 32] + stp q8, q9, [x0, 4 * 32] + stp q10, q11, [x0, 5 * 32] + stp q12, q13, [x0, 6 * 32] + stp q14, q15, [x0, 7 * 32] + stp q16, q17, [x0, 8 * 32] + stp q18, q19, [x0, 9 * 32] + stp q20, q21, [x0, 10 * 32] + stp q22, q23, [x0, 11 * 32] + stp q24, q25, [x0, 12 * 32] + stp q26, q27, [x0, 13 * 32] + stp q28, q29, [x0, 14 * 32] + stp q30, q31, [x0, 15 * 32] + add x0, x0, 16 * 32 + mrs x1, fpcr + mrs x2, fpsr + stp x1, x2, [x0] + + add x0, sp, 2 * 16 + bl sys_fork_impl + + ldr lr, [sp], 16 + ret + +.type sys_fork, function +.size sys_fork, . - sys_fork +.global sys_fork diff --git a/lab5/c/src/xcpt/syscall/getpid.c b/lab5/c/src/xcpt/syscall/getpid.c new file mode 100644 index 000000000..faa43be0e --- /dev/null +++ b/lab5/c/src/xcpt/syscall/getpid.c @@ -0,0 +1,4 @@ +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" + +int sys_getpid(void) { return current_thread()->process->id; } diff --git a/lab5/c/src/xcpt/syscall/kill.c b/lab5/c/src/xcpt/syscall/kill.c new file mode 100644 index 000000000..924686748 --- /dev/null +++ b/lab5/c/src/xcpt/syscall/kill.c @@ -0,0 +1,17 @@ +#include "oscos/uapi/errno.h" + +#include "oscos/sched.h" + +int sys_kill(const int pid) { + if (pid <= 0) { + kill_all_processes(); + } else { + process_t *const process = get_process_by_id(pid); + if (!process) + return -ESRCH; + + kill_process(process); + } + + return 0; +} diff --git a/lab5/c/src/xcpt/syscall/mbox-call.c b/lab5/c/src/xcpt/syscall/mbox-call.c new file mode 100644 index 000000000..0bdc7ef7b --- /dev/null +++ b/lab5/c/src/xcpt/syscall/mbox-call.c @@ -0,0 +1,29 @@ +#include +#include + +#include "oscos/drivers/mailbox.h" +#include "oscos/uapi/errno.h" +#include "oscos/utils/critical-section.h" + +static bool _is_valid_mbox_ch(const unsigned char ch) { return ch < 10; } + +static bool _is_valid_mbox_ptr(const unsigned int *const mbox) { + return ((uintptr_t)mbox & 0xf) == 0; +} + +int sys_mbox_call(const unsigned char ch, unsigned int *const mbox) { + if (!_is_valid_mbox_ch(ch)) + return -EINVAL; + + if (!_is_valid_mbox_ptr(mbox)) + return -EINVAL; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + mailbox_call(mbox, ch); + + CRITICAL_SECTION_LEAVE(daif_val); + + return 0; +} diff --git a/lab5/c/src/xcpt/syscall/uart-read.c b/lab5/c/src/xcpt/syscall/uart-read.c new file mode 100644 index 000000000..f4ebc20ca --- /dev/null +++ b/lab5/c/src/xcpt/syscall/uart-read.c @@ -0,0 +1,37 @@ +#include + +#include "oscos/console.h" +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" +#include "oscos/uapi/unistd.h" +#include "oscos/utils/critical-section.h" + +static thread_list_node_t _wait_queue = {.prev = &_wait_queue, + .next = &_wait_queue}; + +ssize_t sys_uart_read(char buf[const], const size_t size) { + size_t n_chars_read; + + for (;;) { + // We must enter critical section here. Otherwise, there will be a race + // condition between thread suspension and read readiness notification, + // whose callback adds the current thread to the run queue. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + n_chars_read = console_read_nonblock(buf, size); + if (n_chars_read != 0) { + CRITICAL_SECTION_LEAVE(daif_val); + break; + } + + console_notify_read_ready((void (*)(void *))add_all_threads_to_run_queue, + &_wait_queue); + + suspend_to_wait_queue(&_wait_queue); + CRITICAL_SECTION_LEAVE(daif_val); + } + + return n_chars_read; +} diff --git a/lab5/c/src/xcpt/syscall/uart-write.c b/lab5/c/src/xcpt/syscall/uart-write.c new file mode 100644 index 000000000..21fb22657 --- /dev/null +++ b/lab5/c/src/xcpt/syscall/uart-write.c @@ -0,0 +1,35 @@ +#include + +#include "oscos/console.h" +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" +#include "oscos/utils/critical-section.h" + +static thread_list_node_t _wait_queue = {.prev = &_wait_queue, + .next = &_wait_queue}; + +size_t sys_uart_write(const char buf[const], const size_t size) { + size_t n_chars_written; + + for (;;) { + // We must enter critical section here. Otherwise, there will be a race + // condition between thread suspension and write readiness notification, + // whose callback adds the current thread to the run queue. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + n_chars_written = console_write_nonblock(buf, size); + if (n_chars_written != 0) { + CRITICAL_SECTION_LEAVE(daif_val); + break; + } + + console_notify_write_ready((void (*)(void *))add_all_threads_to_run_queue, + &_wait_queue); + suspend_to_wait_queue(&_wait_queue); + CRITICAL_SECTION_LEAVE(daif_val); + } + + return n_chars_written; +} diff --git a/lab5/c/src/xcpt/vector-table.S b/lab5/c/src/xcpt/vector-table.S index ad6f4806d..3ca6e8631 100644 --- a/lab5/c/src/xcpt/vector-table.S +++ b/lab5/c/src/xcpt/vector-table.S @@ -59,7 +59,32 @@ xcpt_irq_curr_el_sp_elx_handler: // Exception from a lower EL and at least one lower EL is AArch64. // Synchronous. - default_vt_entry 0x8 xcpt_sync_lower_el_aarch64_handler +xcpt_sync_lower_el_aarch64_handler: + save_aapcs + + // Check if the exception is caused by svc. + mrs x9, esr_el1 + ubfx x10, x9, 26, 6 + cmp x10, 0x15 + b.ne .Lxcpt_sync_lower_el_aarch64_handler_not_svc + + // The exception is caused by svc. + bl xcpt_svc_handler + b .Lxcpt_sync_lower_el_aarch64_handler_end + +.Lxcpt_sync_lower_el_aarch64_handler_not_svc: + // The exception is not caused by svc. + mov x0, 0x8 + bl xcpt_default_handler + +.Lxcpt_sync_lower_el_aarch64_handler_end: + load_aapcs + eret + +.type xcpt_sync_lower_el_aarch64_handler, function +.size xcpt_sync_lower_el_aarch64_handler, . - xcpt_sync_lower_el_aarch64_handler + +.align 7 // IRQ. xcpt_irq_lower_el_aarch64_handler: diff --git a/lab5/tests/build-initrd.sh b/lab5/tests/build-initrd.sh index 62de9d676..fb15ec3b2 100755 --- a/lab5/tests/build-initrd.sh +++ b/lab5/tests/build-initrd.sh @@ -4,7 +4,7 @@ if [ -e rootfs ]; then rm -r rootfs fi -progs='xcpt_test' +progs='syscall_test' mkdir rootfs for prog in $progs; do diff --git a/lab5/tests/user-program/xcpt_test/.gitignore b/lab5/tests/user-program/libc/.gitignore similarity index 100% rename from lab5/tests/user-program/xcpt_test/.gitignore rename to lab5/tests/user-program/libc/.gitignore diff --git a/lab5/tests/user-program/libc/Makefile b/lab5/tests/user-program/libc/Makefile new file mode 100644 index 000000000..f21080034 --- /dev/null +++ b/lab5/tests/user-program/libc/Makefile @@ -0,0 +1,61 @@ +DEFAULT_PROFILE = DEBUG +SRC_DIR = src +BUILD_DIR = build + +AR = aarch64-linux-gnu-ar -rv +AS = aarch64-linux-gnu-as +CC = aarch64-linux-gnu-gcc +CPP = aarch64-linux-gnu-cpp + +CPPFLAGS_BASE = -Iinclude + +ASFLAGS_DEBUG = -g + +CFLAGS_BASE = -std=c17 -pedantic-errors -Wall -Wextra -ffreestanding \ + -mcpu=cortex-a53 -mno-outline-atomics -mstrict-align +CFLAGS_DEBUG = -g +CFLAGS_RELEASE = -O3 -flto + +OBJS = start ctype errno mbox signal stdio stdlib string unistd \ + unistd/syscall __detail/utils/fmt + +# ------------------------------------------------------------------------------ + +PROFILE = $(DEFAULT_PROFILE) +OUT_DIR = $(BUILD_DIR)/$(PROFILE) + +CPPFLAGS = $(CPPFLAGS_BASE) $(CPPFLAGS_$(PROFILE)) +ASFLAGS = $(ASFLAGS_BASE) $(ASFLAGS_$(PROFILE)) +CFLAGS = $(CFLAGS_BASE) $(CFLAGS_$(PROFILE)) + +OBJ_PATHS = $(addprefix $(OUT_DIR)/,$(addsuffix .o,$(OBJS))) + +# ------------------------------------------------------------------------------ + +.PHONY: all clean-profile clean + +all: $(OUT_DIR)/libc.a + +$(OUT_DIR)/libc.a: $(OBJ_PATHS) + @mkdir -p $(@D) + $(AR) $@ $^ + +$(OUT_DIR)/%.o: $(SRC_DIR)/%.c + @mkdir -p $(@D) + $(CC) $(CPPFLAGS) $(CFLAGS) -c -MMD -MP -MF $(OUT_DIR)/$*.d $< -o $@ + +$(OUT_DIR)/%.o: $(OUT_DIR)/%.s + @mkdir -p $(@D) + $(AS) $(ASFLAGS) $^ -o $@ + +$(OUT_DIR)/%.s: $(SRC_DIR)/%.S + @mkdir -p $(@D) + $(CPP) $(CPPFLAGS) $^ -o $@ + +clean-profile: + $(RM) -r $(OUT_DIR) + +clean: + $(RM) -r $(BUILD_DIR) + +-include $(OUT_DIR)/*.d diff --git a/lab5/tests/user-program/libc/include/__detail/utils/fmt.h b/lab5/tests/user-program/libc/include/__detail/utils/fmt.h new file mode 100644 index 000000000..36b328eb0 --- /dev/null +++ b/lab5/tests/user-program/libc/include/__detail/utils/fmt.h @@ -0,0 +1,15 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC___DETAIL_UTILS_FMT_H +#define OSCOS_USER_PROGRAM_LIBC___DETAIL_UTILS_FMT_H + +#include + +typedef struct { + void (*putc)(unsigned char, void *); + void (*finalize)(void *); +} printf_vtable_t; + +int __vprintf_generic(const printf_vtable_t *vtable, void *arg, + const char *restrict format, va_list ap) + __attribute__((format(printf, 3, 0))); + +#endif diff --git a/lab5/tests/user-program/libc/include/ctype.h b/lab5/tests/user-program/libc/include/ctype.h new file mode 100644 index 000000000..2f1cc43df --- /dev/null +++ b/lab5/tests/user-program/libc/include/ctype.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_CTYPE_H +#define OSCOS_USER_PROGRAM_LIBC_CTYPE_H + +int isdigit(int c); + +#endif diff --git a/lab5/tests/user-program/libc/include/errno.h b/lab5/tests/user-program/libc/include/errno.h new file mode 100644 index 000000000..e525f1be3 --- /dev/null +++ b/lab5/tests/user-program/libc/include/errno.h @@ -0,0 +1,9 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_ERRNO_H +#define OSCOS_USER_PROGRAM_LIBC_ERRNO_H + +// Error number definitions. +#include "oscos-uapi/errno.h" + +extern int errno; + +#endif diff --git a/lab5/tests/user-program/libc/include/mbox.h b/lab5/tests/user-program/libc/include/mbox.h new file mode 100644 index 000000000..5f5e6fd4d --- /dev/null +++ b/lab5/tests/user-program/libc/include/mbox.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_MBOX_H +#define OSCOS_USER_PROGRAM_LIBC_MBOX_H + +int mbox_call(unsigned char ch, unsigned int *mbox); + +#endif diff --git a/lab5/tests/user-program/libc/include/oscos-uapi b/lab5/tests/user-program/libc/include/oscos-uapi new file mode 120000 index 000000000..ce2ca8c4d --- /dev/null +++ b/lab5/tests/user-program/libc/include/oscos-uapi @@ -0,0 +1 @@ +../../../../c/include/oscos/uapi/ \ No newline at end of file diff --git a/lab5/tests/user-program/libc/include/signal.h b/lab5/tests/user-program/libc/include/signal.h new file mode 100644 index 000000000..e934cbe90 --- /dev/null +++ b/lab5/tests/user-program/libc/include/signal.h @@ -0,0 +1,3 @@ +#include "unistd.h" + +int kill(pid_t pid); diff --git a/lab5/tests/user-program/libc/include/stdio.h b/lab5/tests/user-program/libc/include/stdio.h new file mode 100644 index 000000000..ac998646d --- /dev/null +++ b/lab5/tests/user-program/libc/include/stdio.h @@ -0,0 +1,12 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_STDIO_H +#define OSCOS_USER_PROGRAM_LIBC_STDIO_H + +#include + +int printf(const char *restrict format, ...) + __attribute__((format(printf, 1, 2))); + +int vprintf(const char *restrict format, va_list ap) + __attribute__((format(printf, 1, 0))); + +#endif diff --git a/lab5/tests/user-program/libc/include/stdlib.h b/lab5/tests/user-program/libc/include/stdlib.h new file mode 100644 index 000000000..91d4e865d --- /dev/null +++ b/lab5/tests/user-program/libc/include/stdlib.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_STDLIB_H +#define OSCOS_USER_PROGRAM_LIBC_STDLIB_H + +#include + +noreturn void exit(int status); + +#endif diff --git a/lab5/tests/user-program/libc/include/string.h b/lab5/tests/user-program/libc/include/string.h new file mode 100644 index 000000000..ace3e78d1 --- /dev/null +++ b/lab5/tests/user-program/libc/include/string.h @@ -0,0 +1,25 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_STRING_H +#define OSCOS_USER_PROGRAM_LIBC_STRING_H + +#include + +int memcmp(const void *s1, const void *s2, size_t n); +void *memset(void *s, int c, size_t n); +void *memcpy(void *restrict dest, const void *restrict src, size_t n); +void *memmove(void *dest, const void *src, size_t n); + +int strcmp(const char *s1, const char *s2); +int strncmp(const char *s1, const char *s2, size_t n); + +size_t strlen(const char *s); + +// Extensions. + +/// \brief Swap two non-overlapping blocks of memory. +/// +/// \param xs The pointer to the beginning of the first block of memory. +/// \param ys The pointer to the beginning of the second block of memory. +/// \param n The size of the memory blocks. +void memswp(void *restrict xs, void *restrict ys, size_t n); + +#endif diff --git a/lab5/tests/user-program/libc/include/sys/syscall.h b/lab5/tests/user-program/libc/include/sys/syscall.h new file mode 100644 index 000000000..65efd73e5 --- /dev/null +++ b/lab5/tests/user-program/libc/include/sys/syscall.h @@ -0,0 +1 @@ +#include "../oscos-uapi/sys/syscall.h" diff --git a/lab5/tests/user-program/libc/include/unistd.h b/lab5/tests/user-program/libc/include/unistd.h new file mode 100644 index 000000000..01ea5ac4b --- /dev/null +++ b/lab5/tests/user-program/libc/include/unistd.h @@ -0,0 +1,21 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_UNISTD_H +#define OSCOS_USER_PROGRAM_LIBC_UNISTD_H + +#include + +#include "oscos-uapi/unistd.h" + +typedef int pid_t; + +pid_t getpid(void); + +ssize_t uart_read(void *buf, size_t count); +ssize_t uart_write(const void *buf, size_t count); + +int exec(const char *pathname, char *const argv[]); + +pid_t fork(void); + +long syscall(long number, ...); + +#endif diff --git a/lab5/tests/user-program/libc/src/__detail/utils/fmt.c b/lab5/tests/user-program/libc/src/__detail/utils/fmt.c new file mode 100644 index 000000000..f33b4067d --- /dev/null +++ b/lab5/tests/user-program/libc/src/__detail/utils/fmt.c @@ -0,0 +1,654 @@ +#include "__detail/utils/fmt.h" + +#include +#include +#include + +#include "ctype.h" +#include "string.h" + +#define OCT_MAX_N_DIGITS 22 +#define DEC_MAX_N_DIGITS 20 +#define HEX_MAX_N_DIGITS 16 + +typedef enum { + LM_NONE, + LM_HH, + LM_H, + LM_L, + LM_LL, + LM_J, + LM_Z, + LM_T, + LM_UPPER_L +} length_modifier_t; + +static const char LOWER_HEX_DIGITS[16] = "0123456789abcdef"; +static const char UPPER_HEX_DIGITS[16] = "0123456789ABCDEF"; + +static size_t +_render_unsigned_dec(char digits[const static DEC_MAX_N_DIGITS + 1], + const uintmax_t x) { + digits[DEC_MAX_N_DIGITS] = '\0'; + + size_t n_digits = 0; + for (uintmax_t xc = x; xc > 0; xc /= 10) { + digits[DEC_MAX_N_DIGITS - ++n_digits] = '0' + xc % 10; + } + + return n_digits; +} + +static size_t +_render_unsigned_base_p2(char *const restrict digits, const size_t digits_len, + const uintmax_t x, const size_t log2_base, + const char *const restrict digit_template) { + const uintmax_t mask = (1 << log2_base) - 1; + + digits[digits_len] = '\0'; + + size_t n_digits = 0; + for (uintmax_t xc = x; xc > 0; xc >>= log2_base) { + digits[digits_len - ++n_digits] = digit_template[xc & mask]; + } + + return n_digits; +} + +static size_t _puts_generic(void (*const putc)(unsigned char, void *), + void *const putc_arg, const char *const s) { + size_t n_chars_printed = 0; + for (const char *c = s; *c; c++) { + putc(*c, putc_arg); + n_chars_printed++; + } + return n_chars_printed; +} + +static size_t _puts_limited_generic(void (*const putc)(unsigned char, void *), + void *const putc_arg, const char *const s, + const size_t limit) { + size_t n_chars_printed = 0, i = 0; + for (const char *c = s; i < limit && *c; i++, c++) { + putc(*c, putc_arg); + n_chars_printed++; + } + return n_chars_printed; +} + +static size_t _pad_generic(void (*const putc)(unsigned char, void *), + void *const putc_arg, const unsigned char pad, + const size_t width) { + for (size_t i = 0; i < width; i++) { + putc(pad, putc_arg); + } + return width; +} + +static size_t _vprintf_generic_putc(void (*const putc)(unsigned char, void *), + void *const putc_arg, const unsigned char c, + const bool flag_minus, + const size_t min_field_width) { + size_t n_chars_printed = 0; + + if (flag_minus) { + putc(c, putc_arg); + n_chars_printed++; + + if (min_field_width > 1) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', min_field_width - 1); + } + } else { + if (min_field_width > 1) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', min_field_width - 1); + } + + putc(c, putc_arg); + n_chars_printed++; + } + + return n_chars_printed; +} + +static size_t _vprintf_generic_puts(void (*const putc)(unsigned char, void *), + void *const putc_arg, const char *const s, + const bool flag_minus, + const size_t min_field_width, + const bool precision_valid, + const size_t precision) { + const size_t len = strlen(s); + + // Calculate width. + + const size_t width = precision_valid && precision < len ? precision : len; + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + // The string. + if (precision_valid) { + n_chars_printed += _puts_limited_generic(putc, putc_arg, s, precision); + } else { + n_chars_printed += _puts_generic(putc, putc_arg, s); + } + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return n_chars_printed; +} + +static size_t _vprintf_generic_print_signed_dec( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const intmax_t x, const bool flag_minus, const bool flag_plus, + const bool flag_space, const bool flag_zero, const size_t min_field_width, + const size_t precision) { + // Render the digits. + + char digits[DEC_MAX_N_DIGITS + 1]; + const size_t n_digits = _render_unsigned_dec(digits, x < 0 ? -x : x); + + // Calculate width. + + size_t width = n_digits; + if (precision > n_digits) { + width = precision; + } + width += x < 0 || flag_plus || flag_space; // Sign character. + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // Sign. + if (x < 0) { + putc('-', putc_arg); + n_chars_printed++; + } else if (flag_plus) { + putc('+', putc_arg); + n_chars_printed++; + } else if (flag_space) { + putc(' ', putc_arg); + n_chars_printed++; + } + // Leading zeros. + if (precision > n_digits) { + n_chars_printed += _pad_generic(putc, putc_arg, '0', precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (DEC_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +static size_t _vprintf_generic_print_unsigned_oct( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const uintmax_t x, const bool flag_minus, const bool flag_hash, + const bool flag_zero, const size_t min_field_width, + const size_t precision) { + // Render the digits. + + char digits[OCT_MAX_N_DIGITS + 1]; + const size_t n_digits = _render_unsigned_base_p2(digits, OCT_MAX_N_DIGITS, x, + 3, LOWER_HEX_DIGITS); + + // Calculate width. + + const size_t effective_precision = + flag_hash && n_digits + 1 > precision ? n_digits + 1 : precision; + + size_t width = n_digits; + if (effective_precision > n_digits) { + width = effective_precision; + } + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // Leading zeros. + if (effective_precision > n_digits) { + n_chars_printed += + _pad_generic(putc, putc_arg, '0', effective_precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (OCT_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +static size_t _vprintf_generic_print_unsigned_hex( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const uintmax_t x, const bool flag_minus, const bool flag_hash, + const bool flag_zero, const size_t min_field_width, const size_t precision, + const bool is_upper) { + const char *const digit_template = + is_upper ? UPPER_HEX_DIGITS : LOWER_HEX_DIGITS; + const char *const prefix = is_upper ? "0X" : "0x"; + + // Render the digits. + + char digits[HEX_MAX_N_DIGITS + 1]; + const size_t n_digits = + _render_unsigned_base_p2(digits, HEX_MAX_N_DIGITS, x, 4, digit_template); + + // Calculate width. + + size_t width = n_digits; + if (precision > n_digits) { + width = precision; + } + // 0x prefix. + if (flag_hash && x != 0) { + width += 2; + } + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // 0x prefix. + if (flag_hash && x != 0) { + n_chars_printed += _puts_generic(putc, putc_arg, prefix); + } + // Leading zeros. + if (precision > n_digits) { + n_chars_printed += _pad_generic(putc, putc_arg, '0', precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (HEX_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +static size_t _vprintf_generic_print_unsigned_dec( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const uintmax_t x, const bool flag_minus, const bool flag_zero, + const size_t min_field_width, const size_t precision) { + // Render the digits. + + char digits[DEC_MAX_N_DIGITS + 1]; + const size_t n_digits = _render_unsigned_dec(digits, x); + + // Calculate width. + + size_t width = n_digits; + if (precision > n_digits) { + width = precision; + } + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // Leading zeros. + if (precision > n_digits) { + n_chars_printed += _pad_generic(putc, putc_arg, '0', precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (DEC_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +#define READ_ARG_S(LM, AP) \ + ((LM) == LM_NONE ? va_arg(AP, int) \ + : (LM) == LM_HH ? (signed char)va_arg(AP, int) \ + : (LM) == LM_H ? (short)va_arg(AP, int) \ + : (LM) == LM_L ? va_arg(AP, long) \ + : (LM) == LM_LL ? va_arg(AP, long long) \ + : (LM) == LM_J ? va_arg(AP, intmax_t) \ + : (LM) == LM_Z ? va_arg(AP, /* signed size_t = */ long) \ + : (LM) == LM_T ? va_arg(AP, ptrdiff_t) \ + : (__builtin_unreachable(), 0)) + +#define READ_ARG_U(LM, AP) \ + ((LM) == LM_NONE ? va_arg(AP, unsigned) \ + : (LM) == LM_HH ? (unsigned char)va_arg(AP, int) \ + : (LM) == LM_H ? (unsigned short)va_arg(AP, int) \ + : (LM) == LM_L ? va_arg(AP, unsigned long) \ + : (LM) == LM_LL ? va_arg(AP, unsigned long long) \ + : (LM) == LM_J ? va_arg(AP, uintmax_t) \ + : (LM) == LM_Z ? va_arg(AP, size_t) \ + : (LM) == LM_T ? va_arg(AP, /* unsigned ptrdiff_t = */ unsigned long) \ + : (__builtin_unreachable(), 0)) + +int __vprintf_generic(const printf_vtable_t *const vtable, + void *const vtable_arg, const char *const restrict format, + va_list ap) { + size_t n_chars_printed = 0; + for (const char *restrict c = format; *c;) { + if (*c == '%') { + c++; + + // Flags. + + bool flag_minus = false, flag_plus = false, flag_space = false, + flag_hash = false, flag_zero = false; + + for (;;) { + if (*c == '-') { + c++; + flag_minus = true; + } else if (*c == '+') { + c++; + flag_plus = true; + } else if (*c == ' ') { + c++; + flag_space = true; + } else if (*c == '#') { + c++; + flag_hash = true; + } else if (*c == '0') { + c++; + flag_zero = true; + } else { + break; + } + } + + // Minimum field width. + + bool min_field_width_specified = false; + size_t min_field_width = 0; + if (*c == '*') { + c++; + min_field_width_specified = true; + const int arg = va_arg(ap, int); + if (arg < 0) { + flag_minus = true; + min_field_width = -arg; + } else { + min_field_width = arg; + } + } else if (isdigit(*c)) { + min_field_width_specified = true; + for (; isdigit(*c); c++) { + min_field_width = min_field_width * 10 + (*c - '0'); + } + } + + // Precision. + + bool precision_specified = false, precision_valid = false; + size_t precision = 0; + if (*c == '.') { + c++; + precision_specified = true; + + if (*c == '*') { + c++; + const int arg = va_arg(ap, int); + if (arg >= 0) { + precision_valid = true; + precision = arg; + } + } else { + precision_valid = true; + for (; isdigit(*c); c++) { + precision = precision * 10 + (*c - '0'); + } + } + } + + // Length modifier. + + length_modifier_t length_modifier = LM_NONE; + + switch (*c) { + case 'h': + c++; + if (*c == 'h') { + c++; + length_modifier = LM_HH; + } else { + length_modifier = LM_H; + } + break; + + case 'l': + c++; + if (*c == 'l') { + c++; + length_modifier = LM_LL; + } else { + length_modifier = LM_L; + } + break; + + case 'j': + c++; + length_modifier = LM_J; + break; + + case 'z': + c++; + length_modifier = LM_Z; + break; + + case 't': + c++; + length_modifier = LM_T; + break; + + case 'L': + c++; + length_modifier = LM_UPPER_L; + break; + } + + // Format specifier. + + switch (*c) { + case '%': + c++; + if (flag_minus || flag_plus || flag_space || flag_hash || flag_zero || + min_field_width_specified || precision_specified || + length_modifier != LM_NONE) + __builtin_unreachable(); + vtable->putc('%', vtable_arg); + n_chars_printed++; + break; + + case 'c': + c++; + switch (length_modifier) { + case LM_NONE: + if (flag_hash || flag_zero) + __builtin_unreachable(); + n_chars_printed += + _vprintf_generic_putc(vtable->putc, vtable_arg, va_arg(ap, int), + flag_minus, min_field_width); + break; + + default: + __builtin_unreachable(); + } + break; + + case 's': + c++; + switch (length_modifier) { + case LM_NONE: + if (flag_hash || flag_zero) + __builtin_unreachable(); + n_chars_printed += _vprintf_generic_puts( + vtable->putc, vtable_arg, va_arg(ap, const char *), flag_minus, + min_field_width, precision_valid, precision); + break; + + default: + __builtin_unreachable(); + } + break; + + case 'd': + case 'i': + c++; + if (flag_hash) + __builtin_unreachable(); + n_chars_printed += _vprintf_generic_print_signed_dec( + vtable->putc, vtable_arg, READ_ARG_S(length_modifier, ap), + flag_minus, flag_plus, flag_space, flag_zero, min_field_width, + precision_valid ? precision : 1); + break; + + case 'o': + c++; + n_chars_printed += _vprintf_generic_print_unsigned_oct( + vtable->putc, vtable_arg, READ_ARG_U(length_modifier, ap), + flag_minus, flag_hash, flag_zero, min_field_width, + precision_specified ? precision : 1); + break; + + case 'x': + case 'X': { + const bool is_upper = *c == 'X'; + c++; + n_chars_printed += _vprintf_generic_print_unsigned_hex( + vtable->putc, vtable_arg, READ_ARG_U(length_modifier, ap), + flag_minus, flag_hash, flag_zero, min_field_width, + precision_specified ? precision : 1, is_upper); + break; + } + + case 'u': + c++; + if (flag_hash) + __builtin_unreachable(); + n_chars_printed += _vprintf_generic_print_unsigned_dec( + vtable->putc, vtable_arg, READ_ARG_S(length_modifier, ap), + flag_minus, flag_zero, min_field_width, + precision_valid ? precision : 1); + break; + + case 'n': + c++; + if (flag_minus || flag_plus || flag_space || flag_hash || flag_zero || + min_field_width_specified || precision_specified) + __builtin_unreachable(); + switch (length_modifier) { + case LM_NONE: + *va_arg(ap, int *) = n_chars_printed; + break; + + case LM_HH: + *va_arg(ap, signed char *) = n_chars_printed; + break; + + case LM_H: + *va_arg(ap, short *) = n_chars_printed; + break; + + case LM_L: + *va_arg(ap, long *) = n_chars_printed; + break; + + case LM_LL: + *va_arg(ap, long long *) = n_chars_printed; + break; + + case LM_J: + *va_arg(ap, intmax_t *) = n_chars_printed; + break; + + case LM_Z: + *va_arg(ap, /* signed size_t = */ long *) = n_chars_printed; + break; + + case LM_T: + *va_arg(ap, ptrdiff_t *) = n_chars_printed; + break; + + default: + __builtin_unreachable(); + } + break; + + case 'p': + c++; + switch (length_modifier) { + case LM_NONE: + n_chars_printed += _vprintf_generic_print_unsigned_hex( + vtable->putc, vtable_arg, (uintmax_t)va_arg(ap, void *), + flag_minus, flag_hash, flag_zero, min_field_width, + precision_specified ? precision : 1, false); + break; + + default: + __builtin_unreachable(); + } + break; + + default: + __builtin_unreachable(); + } + } else { + vtable->putc(*c++, vtable_arg); + n_chars_printed++; + } + } + + vtable->finalize(vtable_arg); + return n_chars_printed; +} diff --git a/lab5/tests/user-program/libc/src/ctype.c b/lab5/tests/user-program/libc/src/ctype.c new file mode 100644 index 000000000..eb145ab96 --- /dev/null +++ b/lab5/tests/user-program/libc/src/ctype.c @@ -0,0 +1,3 @@ +#include "ctype.h" + +int isdigit(const int c) { return '0' <= c && c <= '9'; } diff --git a/lab5/tests/user-program/libc/src/errno.c b/lab5/tests/user-program/libc/src/errno.c new file mode 100644 index 000000000..197b93086 --- /dev/null +++ b/lab5/tests/user-program/libc/src/errno.c @@ -0,0 +1,3 @@ +#include "errno.h" + +int errno = 0; diff --git a/lab5/tests/user-program/libc/src/mbox.c b/lab5/tests/user-program/libc/src/mbox.c new file mode 100644 index 000000000..450968f1b --- /dev/null +++ b/lab5/tests/user-program/libc/src/mbox.c @@ -0,0 +1,8 @@ +#include "mbox.h" + +#include "sys/syscall.h" +#include "unistd.h" + +int mbox_call(const unsigned char ch, unsigned int *const mbox) { + return syscall(SYS_mbox_call, ch, mbox); +} diff --git a/lab5/tests/user-program/libc/src/signal.c b/lab5/tests/user-program/libc/src/signal.c new file mode 100644 index 000000000..124d17afb --- /dev/null +++ b/lab5/tests/user-program/libc/src/signal.c @@ -0,0 +1,5 @@ +#include "signal.h" + +#include "sys/syscall.h" + +int kill(const pid_t pid) { return syscall(SYS_kill, pid); } diff --git a/lab5/tests/user-program/libc/src/start.S b/lab5/tests/user-program/libc/src/start.S new file mode 100644 index 000000000..2dc50db90 --- /dev/null +++ b/lab5/tests/user-program/libc/src/start.S @@ -0,0 +1,15 @@ +.section ".text._start" + +_start: + // We don't need to clear the .bss section here, since the kernel does this + // for us. + + // Call the main function. + bl main + + // Terminate the program. + b exit + +.size _start, . - _start +.type _start, function +.global _start diff --git a/lab5/tests/user-program/libc/src/stdio.c b/lab5/tests/user-program/libc/src/stdio.c new file mode 100644 index 000000000..87e894a80 --- /dev/null +++ b/lab5/tests/user-program/libc/src/stdio.c @@ -0,0 +1,31 @@ +#include "stdio.h" + +#include "__detail/utils/fmt.h" +#include "unistd.h" + +static void _printf_putc(const unsigned char c, void *const _arg) { + (void)_arg; + + // Very inefficient, but it works. + while (uart_write(&c, 1) != 1) + ; +} + +static void _printf_finalize(void *const _arg) { (void)_arg; } + +static const printf_vtable_t _printf_vtable = {.putc = _printf_putc, + .finalize = _printf_finalize}; + +int printf(const char *const restrict format, ...) { + va_list ap; + va_start(ap, format); + + const int result = vprintf(format, ap); + + va_end(ap); + return result; +} + +int vprintf(const char *const restrict format, va_list ap) { + return __vprintf_generic(&_printf_vtable, NULL, format, ap); +} diff --git a/lab5/tests/user-program/libc/src/stdlib.c b/lab5/tests/user-program/libc/src/stdlib.c new file mode 100644 index 000000000..6ca25fa10 --- /dev/null +++ b/lab5/tests/user-program/libc/src/stdlib.c @@ -0,0 +1,9 @@ +#include "stdlib.h" + +#include "sys/syscall.h" +#include "unistd.h" + +void exit(const int status) { + syscall(SYS_exit, status); + __builtin_unreachable(); +} diff --git a/lab5/tests/user-program/libc/src/string.c b/lab5/tests/user-program/libc/src/string.c new file mode 100644 index 000000000..567e116ec --- /dev/null +++ b/lab5/tests/user-program/libc/src/string.c @@ -0,0 +1,99 @@ +#include "string.h" + +__attribute__((used)) int memcmp(const void *const s1, const void *const s2, + const size_t n) { + const unsigned char *const s1_c = s1, *const s2_c = s2; + + for (size_t i = 0; i < n; i++) { + const int diff = (int)s1_c[i] - s2_c[i]; + if (diff != 0) + return diff; + } + + return 0; +} + +__attribute__((used)) void *memset(void *const s, const int c, const size_t n) { + unsigned char *const s_c = s; + + for (size_t i = 0; i < n; i++) { + s_c[i] = c; + } + + return s; +} + +static void __memmove_forward(unsigned char *const dest, + const unsigned char *const src, const size_t n) { + for (size_t i = 0; i < n; i++) { + dest[i] = src[i]; + } +} + +static void __memmove_backward(unsigned char *const dest, + const unsigned char *const src, const size_t n) { + for (size_t ip1 = n; ip1 > 0; ip1--) { + const size_t i = ip1 - 1; + dest[i] = src[i]; + } +} + +__attribute__((used)) void *memcpy(void *const restrict dest, + const void *const restrict src, + const size_t n) { + __memmove_forward(dest, src, n); + return dest; +} + +__attribute__((used)) void *memmove(void *const dest, const void *const src, + const size_t n) { + if (dest < src) { + __memmove_forward(dest, src, n); + } else if (dest > src) { + __memmove_backward(dest, src, n); + } + + return dest; +} + +int strcmp(const char *const s1, const char *const s2) { + for (const unsigned char *c1 = (const unsigned char *)s1, + *c2 = (const unsigned char *)s2; + *c1 || *c2; c1++, c2++) { + const int diff = (int)*c1 - *c2; + if (diff != 0) + return diff; + } + + return 0; +} + +int strncmp(const char *const s1, const char *const s2, const size_t n) { + size_t i = 0; + const unsigned char *c1 = (const unsigned char *)s1, + *c2 = (const unsigned char *)s2; + for (; i < n && (*c1 || *c2); i++, c1++, c2++) { + const int diff = (int)*c1 - *c2; + if (diff != 0) + return diff; + } + + return 0; +} + +size_t strlen(const char *s) { + size_t result = 0; + for (const char *c = s; *c; c++) { + result++; + } + return result; +} + +void memswp(void *const restrict xs, void *const restrict ys, const size_t n) { + unsigned char *const restrict xs_c = xs, *const restrict ys_c = ys; + for (size_t i = 0; i < n; i++) { + const unsigned char tmp = xs_c[i]; + xs_c[i] = ys_c[i]; + ys_c[i] = tmp; + } +} diff --git a/lab5/tests/user-program/libc/src/unistd.c b/lab5/tests/user-program/libc/src/unistd.c new file mode 100644 index 000000000..ba4be2c69 --- /dev/null +++ b/lab5/tests/user-program/libc/src/unistd.c @@ -0,0 +1,19 @@ +#include "unistd.h" + +#include "sys/syscall.h" + +pid_t getpid(void) { return syscall(SYS_getpid); } + +ssize_t uart_read(void *const buf, const size_t count) { + return syscall(SYS_uart_read, buf, count); +} + +ssize_t uart_write(const void *const buf, const size_t count) { + return syscall(SYS_uart_write, buf, count); +} + +int exec(const char *const pathname, char *const argv[const]) { + return syscall(SYS_exec, pathname, argv); +} + +pid_t fork(void) { return syscall(SYS_fork); } diff --git a/lab5/tests/user-program/libc/src/unistd/syscall.S b/lab5/tests/user-program/libc/src/unistd/syscall.S new file mode 100644 index 000000000..7783a7105 --- /dev/null +++ b/lab5/tests/user-program/libc/src/unistd/syscall.S @@ -0,0 +1,30 @@ +.section ".text" + +syscall: + mov x8, x0 + mov x0, x1 + mov x1, x2 + mov x2, x3 + mov x3, x4 + mov x4, x5 + mov x5, x6 + mov x6, x7 + + svc 0 + + cmp x0, -4096 + b.hi .Lsyscall_failed + + ret + +.Lsyscall_failed: + neg x0, x0 + ldr x1, =errno + str x0, [x1] + + mov x0, -1 + ret + +.type syscall, function +.size syscall, . - syscall +.global syscall diff --git a/lab5/tests/user-program/syscall_test/.gitignore b/lab5/tests/user-program/syscall_test/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/lab5/tests/user-program/syscall_test/.gitignore @@ -0,0 +1 @@ +/build diff --git a/lab5/tests/user-program/xcpt_test/Makefile b/lab5/tests/user-program/syscall_test/Makefile similarity index 86% rename from lab5/tests/user-program/xcpt_test/Makefile rename to lab5/tests/user-program/syscall_test/Makefile index 329399072..c2a97559b 100644 --- a/lab5/tests/user-program/xcpt_test/Makefile +++ b/lab5/tests/user-program/syscall_test/Makefile @@ -7,7 +7,7 @@ CC = aarch64-linux-gnu-gcc CPP = aarch64-linux-gnu-cpp OBJCOPY = aarch64-linux-gnu-objcopy -CPPFLAGS_BASE = -Iinclude +CPPFLAGS_BASE = -Iinclude -I../libc/include ASFLAGS_DEBUG = -g @@ -19,9 +19,9 @@ CFLAGS_RELEASE = -O3 -flto LDFLAGS_BASE = -nostdlib -Xlinker --build-id=none LDFLAGS_RELEASE = -flto -Xlinker --gc-sections -LDLIBS_BASE = -lgcc +LDLIBS_BASE = -lgcc -L../libc/build/$(PROFILE) -lc -OBJS = start +OBJS = main LD_SCRIPT = $(SRC_DIR)/linker.ld # ------------------------------------------------------------------------------ @@ -41,13 +41,13 @@ OBJ_PATHS = $(addprefix $(OUT_DIR)/,$(addsuffix .o,$(OBJS))) .PHONY: all clean-profile clean -all: $(OUT_DIR)/xcpt_test.img +all: $(OUT_DIR)/syscall_test.img -$(OUT_DIR)/xcpt_test.img: $(OUT_DIR)/xcpt_test.elf +$(OUT_DIR)/syscall_test.img: $(OUT_DIR)/syscall_test.elf @mkdir -p $(@D) $(OBJCOPY) -O binary $^ $@ -$(OUT_DIR)/xcpt_test.elf: $(OBJ_PATHS) $(LD_SCRIPT) +$(OUT_DIR)/syscall_test.elf: $(OBJ_PATHS) $(LD_SCRIPT) @mkdir -p $(@D) $(CC) -T $(LD_SCRIPT) $(LDFLAGS) $(filter-out $(LD_SCRIPT),$^) $(LDLIBS) \ -o $@ diff --git a/lab5/tests/user-program/xcpt_test/src/linker.ld b/lab5/tests/user-program/syscall_test/src/linker.ld similarity index 100% rename from lab5/tests/user-program/xcpt_test/src/linker.ld rename to lab5/tests/user-program/syscall_test/src/linker.ld diff --git a/lab5/tests/user-program/syscall_test/src/main.c b/lab5/tests/user-program/syscall_test/src/main.c new file mode 100644 index 000000000..330fe3ec9 --- /dev/null +++ b/lab5/tests/user-program/syscall_test/src/main.c @@ -0,0 +1,61 @@ +#include "stdint.h" +#include "stdio.h" +#include "stdlib.h" + +#include "unistd.h" + +#define NS_PER_SEC 1000000000 + +void delay_ns(const uint64_t ns) { + uint64_t start_counter_val; + __asm__ __volatile__("mrs %0, cntpct_el0" : "=r"(start_counter_val)::); + + uint64_t counter_clock_frequency_hz; + __asm__ __volatile__("mrs %0, cntfrq_el0" + : "=r"(counter_clock_frequency_hz)::); + counter_clock_frequency_hz &= 0xffffffff; + + // ceil(ns * counter_clock_frequency_hz / NS_PER_SEC). + const uint64_t delta_counter_val = + (ns * counter_clock_frequency_hz + (NS_PER_SEC - 1)) / NS_PER_SEC; + + for (;;) { + uint64_t counter_val; + __asm__ __volatile__("mrs %0, cntpct_el0" : "=r"(counter_val)::); + + if (counter_val - start_counter_val >= delta_counter_val) + break; + } +} + +void fork_test(void) { + printf("\nFork Test, pid %d\n", getpid()); + int cnt = 1; + int ret = 0; + if ((ret = fork()) == 0) { // child + long long cur_sp; + __asm__ __volatile__("mov %0, sp" : "=r"(cur_sp)); + printf("first child pid: %d, cnt: %d, ptr: %p, sp : %llx\n", getpid(), cnt, + (void *)&cnt, cur_sp); + ++cnt; + + if ((ret = fork()) != 0) { + __asm__ __volatile__("mov %0, sp" : "=r"(cur_sp)); + printf("first child pid: %d, cnt: %d, ptr: %p, sp : %llx\n", getpid(), + cnt, (void *)&cnt, cur_sp); + } else { + while (cnt < 5) { + __asm__ __volatile__("mov %0, sp" : "=r"(cur_sp)); + printf("second child pid: %d, cnt: %d, ptr: %p, sp : %llx\n", getpid(), + cnt, (void *)&cnt, cur_sp); + delay_ns(1000000); + ++cnt; + } + } + exit(0); + } else { + printf("parent here, pid %d, child %d\n", getpid(), ret); + } +} + +void main(void) { fork_test(); } diff --git a/lab5/tests/user-program/xcpt_test/src/start.S b/lab5/tests/user-program/xcpt_test/src/start.S deleted file mode 100644 index ce7e53908..000000000 --- a/lab5/tests/user-program/xcpt_test/src/start.S +++ /dev/null @@ -1,11 +0,0 @@ -.section ".text" -.global _start -_start: - mov x0, 0 -1: - add x0, x0, 1 - svc 0 - cmp x0, 5 - blt 1b -1: - b 1b From e2e67f43f4f516ecc407846f7cd1faa40a0969b7 Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Tue, 20 Jun 2023 02:41:30 +0800 Subject: [PATCH 05/34] Lab 5 C: Complete B3: Video Player Other changes: - Scheduler: - Disable user stack sharing by default. - Fix `get_process_by_id` and `kill_all_processes`. - System call: - Change the return values of `sys_mbox_call` to match the video player. - Main function: - Remove scheduler test code. --- lab5/c/Makefile | 8 +- lab5/c/include/oscos/sched.h | 10 ++- lab5/c/include/oscos/timer/timeout.h | 6 +- lab5/c/src/main.c | 22 +---- lab5/c/src/sched/periodic-sched.c | 33 ++++++++ lab5/c/src/sched/sched.c | 105 ++++++++++++++++++++---- lab5/c/src/shell.c | 4 +- lab5/c/src/timer/delay.c | 2 +- lab5/c/src/timer/timeout.c | 30 ++++--- lab5/c/src/xcpt/syscall/mbox-call.c | 13 ++- lab5/tests/build-initrd-video-player.sh | 4 + 11 files changed, 178 insertions(+), 59 deletions(-) create mode 100644 lab5/c/src/sched/periodic-sched.c create mode 100755 lab5/tests/build-initrd-video-player.sh diff --git a/lab5/c/Makefile b/lab5/c/Makefile index fc4c454f0..81e6075c8 100644 --- a/lab5/c/Makefile +++ b/lab5/c/Makefile @@ -25,8 +25,8 @@ OBJS = start main console devicetree initrd panic shell \ drivers/aux drivers/gpio drivers/l1ic drivers/l2ic drivers/mailbox \ drivers/mini-uart drivers/pm \ mem/malloc mem/page-alloc mem/startup-alloc mem/types mem/vm \ - sched/idle-thread sched/sched sched/schedule sched/thread-main \ - sched/user-program-main \ + sched/idle-thread sched/periodic-sched sched/sched sched/schedule \ + sched/thread-main sched/user-program-main \ timer/delay timer/timeout \ xcpt/default-handler xcpt/irq-handler xcpt/svc-handler \ xcpt/syscall-table xcpt/task-queue xcpt/vector-table xcpt/xcpt \ @@ -79,12 +79,12 @@ $(OUT_DIR)/%.s: $(SRC_DIR)/%.S qemu: $(OUT_DIR)/kernel8.img qemu-system-aarch64 -M raspi3b -kernel $< -initrd ../tests/initramfs.cpio \ - -dtb ../tests/bcm2710-rpi-3-b-plus.dtb -display none -serial null \ + -dtb ../tests/bcm2710-rpi-3-b-plus.dtb -display gtk -serial null \ -serial stdio qemu-debug: $(OUT_DIR)/kernel8.img qemu-system-aarch64 -M raspi3b -kernel $< -initrd ../tests/initramfs.cpio \ - -dtb ../tests/bcm2710-rpi-3-b-plus.dtb -display none -serial null \ + -dtb ../tests/bcm2710-rpi-3-b-plus.dtb -display gtk -serial null \ -serial stdio -S -s gdb: $(OUT_DIR)/kernel8.elf diff --git a/lab5/c/include/oscos/sched.h b/lab5/c/include/oscos/sched.h index ff72d17b2..ba38360e1 100644 --- a/lab5/c/include/oscos/sched.h +++ b/lab5/c/include/oscos/sched.h @@ -48,7 +48,12 @@ typedef struct { typedef struct process_t { size_t id; - shared_page_t *text_page, *user_stack_page; + shared_page_t *text_page; +#ifdef SCHED_ENABLE_SHARED_USER_STACK + shared_page_t *user_stack_page; +#else + page_id_t user_stack_page_id; +#endif thread_t *main_thread; } process_t; @@ -132,6 +137,9 @@ void kill_process(process_t *process); /// \brief Kills all processes. void kill_all_processes(void); +/// \brief Sets up periodic scheduling. +void sched_setup_periodic_scheduling(void); + /// \brief Do what the idle thread should do. noreturn void idle(void); diff --git a/lab5/c/include/oscos/timer/timeout.h b/lab5/c/include/oscos/timer/timeout.h index 401f09f07..a2e51e23f 100644 --- a/lab5/c/include/oscos/timer/timeout.h +++ b/lab5/c/include/oscos/timer/timeout.h @@ -6,7 +6,11 @@ void timeout_init(void); -bool timeout_add_timer(void (*callback)(void *), void *arg, uint64_t after_ns); +bool timeout_add_timer_ns(void (*callback)(void *), void *arg, + uint64_t after_ns); + +bool timeout_add_timer_ticks(void (*callback)(void *), void *arg, + uint64_t after_ticks); void xcpt_core_timer_interrupt_handler(void); diff --git a/lab5/c/src/main.c b/lab5/c/src/main.c index bfaf90d01..0feae407a 100644 --- a/lab5/c/src/main.c +++ b/lab5/c/src/main.c @@ -17,22 +17,8 @@ #include "oscos/timer/timeout.h" #include "oscos/xcpt.h" -static void _foo(void *const _arg) { +static void _run_shell(void *const _arg) { (void)_arg; - - for (int i = 0; i < 10; i++) { - console_printf("TID %2zu: %d\n", current_thread()->id, i); - delay_ns(1000000); - schedule(); - } -} - -static void _delayed_shell(void *const _arg) { - (void)_arg; - - for (int i = 0; i < 20; i++) { - schedule(); - } run_shell(); } @@ -76,10 +62,8 @@ void main(const void *const dtb_start) { // Test the scheduler. - for (int i = 0; i < 32; i++) { - thread_create(_foo, NULL); - } - thread_create(_delayed_shell, NULL); + thread_create(_run_shell, NULL); + sched_setup_periodic_scheduling(); idle(); } diff --git a/lab5/c/src/sched/periodic-sched.c b/lab5/c/src/sched/periodic-sched.c new file mode 100644 index 000000000..c81aeb140 --- /dev/null +++ b/lab5/c/src/sched/periodic-sched.c @@ -0,0 +1,33 @@ +#include "oscos/sched.h" + +#include + +#include "oscos/timer/timeout.h" +#include "oscos/xcpt.h" +#include "oscos/xcpt/task-queue.h" + +static void _periodic_sched(void *const _arg) { + (void)_arg; + + sched_setup_periodic_scheduling(); + + // Save spsr_el1 and elr_el1, since they can be clobbered by other threads. + + uint64_t spsr_val, elr_val; + __asm__ __volatile__("mrs %0, spsr_el1" : "=r"(spsr_val)); + __asm__ __volatile__("mrs %0, elr_el1" : "=r"(elr_val)); + + schedule(); + XCPT_MASK_ALL(); + + __asm__ __volatile__("msr spsr_el1, %0" : : "r"(spsr_val)); + __asm__ __volatile__("msr elr_el1, %0" : : "r"(elr_val)); +} + +void sched_setup_periodic_scheduling(void) { + uint64_t core_timer_freq_hz; + __asm__("mrs %0, cntfrq_el0" : "=r"(core_timer_freq_hz)); + core_timer_freq_hz &= 0xffffffff; + + timeout_add_timer_ticks(_periodic_sched, NULL, core_timer_freq_hz >> 5); +} diff --git a/lab5/c/src/sched/sched.c b/lab5/c/src/sched/sched.c index d662ad33a..bac5ea6dc 100644 --- a/lab5/c/src/sched/sched.c +++ b/lab5/c/src/sched/sched.c @@ -190,10 +190,20 @@ _sched_move_thread_to_queue_and_pick_thread(thread_t *const thread, } void thread_exit(void) { + thread_t *const curr_thread = current_thread(); + process_t *const curr_process = curr_thread->process; + XCPT_MASK_ALL(); + if (curr_process) { + rb_delete(&_processes, &curr_process->id, + (int (*)(const void *, const void *, + void *))_cmp_pid_and_processes_by_pid, + NULL); + } + _sched_run_thread( - _sched_move_thread_to_queue_and_pick_thread(current_thread(), &_zombies)); + _sched_move_thread_to_queue_and_pick_thread(curr_thread, &_zombies)); __builtin_unreachable(); } @@ -216,17 +226,23 @@ bool process_create(void) { return false; } +#ifdef SCHED_ENABLE_SHARED_USER_STACK shared_page_t *const user_stack_page = shared_page_init(user_stack_page_id); if (!user_stack_page) { free_pages(user_stack_page_id); free(process); return false; } +#endif thread_fp_simd_ctx_t *const fp_simd_ctx = malloc(sizeof(thread_fp_simd_ctx_t)); if (!fp_simd_ctx) { +#ifdef SCHED_ENABLE_SHARED_USER_STACK shared_page_drop(user_stack_page); +#else + free_pages(user_stack_page_id); +#endif free(process); return false; } @@ -236,7 +252,11 @@ bool process_create(void) { thread_t *const curr_thread = current_thread(); process->id = _alloc_pid(); +#ifdef SCHED_ENABLE_SHARED_USER_STACK process->user_stack_page = user_stack_page; +#else + process->user_stack_page_id = user_stack_page_id; +#endif process->main_thread = curr_thread; curr_thread->process = process; curr_thread->ctx.fp_simd_ctx = fp_simd_ctx; @@ -285,12 +305,14 @@ static void _exec_generic(const void *const text_start, const size_t text_len, return; } +#ifdef SCHED_ENABLE_SHARED_USER_STACK shared_page_t *const user_stack_page = shared_page_init(user_stack_page_id); if (!user_stack_page) { free_pages(user_stack_page_id); shared_page_drop(text_page); return; } +#endif // Free old pages. @@ -298,12 +320,20 @@ static void _exec_generic(const void *const text_start, const size_t text_len, shared_page_drop(curr_process->text_page); } +#ifdef SCHED_ENABLE_SHARED_USER_STACK shared_page_drop(curr_process->user_stack_page); +#else + free_pages(curr_process->user_stack_page_id); +#endif // Set process data. curr_process->text_page = text_page; +#ifdef SCHED_ENABLE_SHARED_USER_STACK curr_process->user_stack_page = user_stack_page; +#else + curr_process->user_stack_page_id = user_stack_page_id; +#endif // Copy the user program. @@ -339,18 +369,12 @@ void exec(const void *const text_start, const size_t text_len) { static void _thread_cleanup(thread_t *const thread) { if (thread->process) { - uint64_t daif_val; - CRITICAL_SECTION_ENTER(daif_val); - - rb_delete(&_processes, &thread->process->id, - (int (*)(const void *, const void *, - void *))_cmp_pid_and_processes_by_pid, - NULL); - - CRITICAL_SECTION_LEAVE(daif_val); - shared_page_drop(thread->process->text_page); +#ifdef SCHED_ENABLE_SHARED_USER_STACK shared_page_drop(thread->process->user_stack_page); +#else + free_pages(thread->process->user_stack_page_id); +#endif free(thread->process); } free_pages(thread->stack_page_id); @@ -389,6 +413,17 @@ process_t *fork(const extended_trap_frame_t *const trap_frame) { return NULL; } +#ifndef SCHED_ENABLE_SHARED_USER_STACK + const spage_id_t user_stack_page_id = alloc_pages(USER_STACK_BLOCK_ORDER); + if (user_stack_page_id < 0) { + free(fp_simd_ctx); + free_pages(kernel_stack_page_id); + free(new_process); + free(new_thread); + return NULL; + } +#endif + // Set data. new_thread->id = _alloc_tid(); @@ -397,8 +432,12 @@ process_t *fork(const extended_trap_frame_t *const trap_frame) { new_process->id = _alloc_pid(); new_process->text_page = shared_page_clone(curr_process->text_page); +#ifdef SCHED_ENABLE_SHARED_USER_STACK new_process->user_stack_page = shared_page_clone(curr_process->user_stack_page); +#else + new_process->user_stack_page_id = user_stack_page_id; +#endif new_process->main_thread = new_thread; // Set execution context. @@ -418,6 +457,21 @@ process_t *fork(const extended_trap_frame_t *const trap_frame) { memcpy(init_kernel_sp, trap_frame, sizeof(extended_trap_frame_t)); +#ifndef SCHED_ENABLE_SHARED_USER_STACK + // Setup the user stack. + + void *const old_user_stack_start = pa_to_kernel_va( + page_id_to_pa(curr_process->user_stack_page_id)), + *const user_stack_start = + pa_to_kernel_va(page_id_to_pa(user_stack_page_id)), + *const new_sp = + (char *)user_stack_start + ((char *)curr_thread->ctx.user_sp - + (char *)old_user_stack_start); + + memcpy(user_stack_start, old_user_stack_start, 1 << USER_STACK_ORDER); + new_thread->ctx.user_sp = (uint64_t)new_sp; +#endif + // Add the new thread to the end of the run queue and the process to the // process BST. Note that these two steps can be done in either order but must // not be interrupted in between, since: @@ -463,10 +517,10 @@ process_t *get_process_by_id(const size_t pid) { CRITICAL_SECTION_ENTER(daif_val); process_t *const result = - (process_t *)rb_search(_processes, &pid, - (int (*)(const void *, const void *, - void *))_cmp_pid_and_processes_by_pid, - NULL); + *(process_t **)rb_search(_processes, &pid, + (int (*)(const void *, const void *, + void *))_cmp_pid_and_processes_by_pid, + NULL); CRITICAL_SECTION_LEAVE(daif_val); @@ -479,10 +533,29 @@ void kill_process(process_t *const process) { } else { thread_t *const thread = process->main_thread; - // Zombify the main thread. + // Remove the process from the process BST and the thread from the run/wait + // queue. Note that these two steps can be done in either order but must not + // be interrupted in between, since: + // - If the former is done before the latter and the thread is scheduled + // between the two steps, then the new thread won't be able to find its + // own process. + // - If the latter is done before the former and the newly-created process + // is killed once again between the two steps, then a freed thread_t + // instance (i.e., `thread`) will be added onto the zombies list – a + // use-after-free bug. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + rb_delete(&_processes, &process->id, + (int (*)(const void *, const void *, + void *))_cmp_pid_and_processes_by_pid, + NULL); _remove_thread_from_queue(thread); _add_thread_to_queue(thread, &_zombies); + + CRITICAL_SECTION_LEAVE(daif_val); } } diff --git a/lab5/c/src/shell.c b/lab5/c/src/shell.c index a87245fd4..448c7df93 100644 --- a/lab5/c/src/shell.c +++ b/lab5/c/src/shell.c @@ -229,8 +229,8 @@ static void _shell_do_cmd_set_timeout(const char *const args) { message_copy[message_len] = '\0'; // Register the callback. - timeout_add_timer((void (*)(void *))_shell_cmd_set_timeout_timer_callback, - message_copy, seconds * NS_PER_SEC); + timeout_add_timer_ns((void (*)(void *))_shell_cmd_set_timeout_timer_callback, + message_copy, seconds * NS_PER_SEC); return; invalid: diff --git a/lab5/c/src/timer/delay.c b/lab5/c/src/timer/delay.c index a3468f3f4..8f7a29c2e 100644 --- a/lab5/c/src/timer/delay.c +++ b/lab5/c/src/timer/delay.c @@ -9,7 +9,7 @@ static void _timeout_callback(volatile bool *const flag) { *flag = true; } void delay_ns(const uint64_t ns) { volatile bool flag = false; - timeout_add_timer((void (*)(void *))_timeout_callback, (void *)&flag, ns); + timeout_add_timer_ns((void (*)(void *))_timeout_callback, (void *)&flag, ns); WFI_WHILE(!flag); } diff --git a/lab5/c/src/timer/timeout.c b/lab5/c/src/timer/timeout.c index 8bc77a7e1..a9c8b74a6 100644 --- a/lab5/c/src/timer/timeout.c +++ b/lab5/c/src/timer/timeout.c @@ -37,25 +37,31 @@ void timeout_init(void) { l1ic_enable_core_timer_irq(get_core_id()); } -bool timeout_add_timer(void (*const callback)(void *), void *const arg, - const uint64_t after_ns) { - if (_n_timeout_entries == MAX_N_TIMEOUT_ENTRIES) - return false; - +bool timeout_add_timer_ns(void (*const callback)(void *), void *const arg, + const uint64_t after_ns) { uint64_t core_timer_freq_hz; __asm__("mrs %0, cntfrq_el0" : "=r"(core_timer_freq_hz)); core_timer_freq_hz &= 0xffffffff; + // ceil(after_ns * core_timer_freq_hz / NS_PER_SEC). + const uint64_t after_ticks = + (after_ns * core_timer_freq_hz + (NS_PER_SEC - 1)) / NS_PER_SEC; + + return timeout_add_timer_ticks(callback, arg, after_ticks); +} + +bool timeout_add_timer_ticks(void (*const callback)(void *), void *const arg, + const uint64_t after_ticks) { + if (_n_timeout_entries == MAX_N_TIMEOUT_ENTRIES) + return false; + uint64_t daif_val; CRITICAL_SECTION_ENTER(daif_val); uint64_t curr_timestamp; __asm__ __volatile__("mrs %0, cntpct_el0" : "=r"(curr_timestamp)); - // ceil(after_ns * core_timer_freq_hz / NS_PER_SEC). - const uint64_t delta_timestamp = - (after_ns * core_timer_freq_hz + (NS_PER_SEC - 1)) / NS_PER_SEC; - const uint64_t timestamp = curr_timestamp + delta_timestamp; + const uint64_t timestamp = curr_timestamp + after_ticks; // Reprogram the timer. @@ -93,9 +99,6 @@ void xcpt_core_timer_interrupt_handler(void) { void *))_timeout_entry_cmp_by_timestamp, NULL); - // Execute the callback. - top_entry.callback(top_entry.arg); - // Reprogram the timer. if (_n_timeout_entries == 0) { // Disable the core timer interrupt. (ENABLE = 1, IMASK = 1) @@ -105,4 +108,7 @@ void xcpt_core_timer_interrupt_handler(void) { : : "r"(_timeout_entries[0].timestamp)); } + + // Execute the callback. + top_entry.callback(top_entry.arg); } diff --git a/lab5/c/src/xcpt/syscall/mbox-call.c b/lab5/c/src/xcpt/syscall/mbox-call.c index 0bdc7ef7b..c68a9a092 100644 --- a/lab5/c/src/xcpt/syscall/mbox-call.c +++ b/lab5/c/src/xcpt/syscall/mbox-call.c @@ -12,11 +12,18 @@ static bool _is_valid_mbox_ptr(const unsigned int *const mbox) { } int sys_mbox_call(const unsigned char ch, unsigned int *const mbox) { + // Note on return values: + // The video player treats a return value of 0 as failure and any non-zero + // return value as success. This is confirmed by reverse-engineering the video + // player. This system call therefore follows the protocol used by the video + // player rather than the usual convention since we have to execute the video + // player properly. + if (!_is_valid_mbox_ch(ch)) - return -EINVAL; + return /* -EINVAL */ 0; if (!_is_valid_mbox_ptr(mbox)) - return -EINVAL; + return /* -EINVAL */ 0; uint64_t daif_val; CRITICAL_SECTION_ENTER(daif_val); @@ -25,5 +32,5 @@ int sys_mbox_call(const unsigned char ch, unsigned int *const mbox) { CRITICAL_SECTION_LEAVE(daif_val); - return 0; + return /* 0 */ 1; } diff --git a/lab5/tests/build-initrd-video-player.sh b/lab5/tests/build-initrd-video-player.sh new file mode 100755 index 000000000..815b728a1 --- /dev/null +++ b/lab5/tests/build-initrd-video-player.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +rm -f initramfs.cpio +wget https://oscapstone.github.io/_downloads/58c515e3041658a045033c8e56ecff4c/initramfs.cpio From b8fc7ff25ca3a655af0fa5bed42c88595f980c72 Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Tue, 20 Jun 2023 19:06:00 +0800 Subject: [PATCH 06/34] Lab 5 C: Fix `get_process_by_id` --- lab5/c/src/sched/sched.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lab5/c/src/sched/sched.c b/lab5/c/src/sched/sched.c index bac5ea6dc..7adae68cb 100644 --- a/lab5/c/src/sched/sched.c +++ b/lab5/c/src/sched/sched.c @@ -516,15 +516,15 @@ process_t *get_process_by_id(const size_t pid) { uint64_t daif_val; CRITICAL_SECTION_ENTER(daif_val); - process_t *const result = - *(process_t **)rb_search(_processes, &pid, - (int (*)(const void *, const void *, - void *))_cmp_pid_and_processes_by_pid, - NULL); + process_t *const *const result = + (process_t **)rb_search(_processes, &pid, + (int (*)(const void *, const void *, + void *))_cmp_pid_and_processes_by_pid, + NULL); CRITICAL_SECTION_LEAVE(daif_val); - return result; + return result ? *result : NULL; } void kill_process(process_t *const process) { From c1b6c255b926846b6d76c951f942abe8a799a6b7 Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Wed, 21 Jun 2023 01:19:59 +0800 Subject: [PATCH 07/34] Lab 5 C: Complete A1: POSIX Signal --- lab5/c/Makefile | 5 +- lab5/c/include/oscos/sched.h | 25 ++ lab5/c/include/oscos/uapi/errno.h | 1 + lab5/c/include/oscos/uapi/signal.h | 43 +++ lab5/c/include/oscos/uapi/sys/syscall.h | 3 + lab5/c/src/sched/run-signal-handler.S | 121 +++++++++ lab5/c/src/sched/sched.c | 245 +++++++++++++++++- lab5/c/src/sched/schedule.S | 8 +- lab5/c/src/sched/sig-handler-main.S | 10 + lab5/c/src/start.S | 2 + lab5/c/src/xcpt/svc-handler.S | 2 +- lab5/c/src/xcpt/syscall-table.S | 3 + lab5/c/src/xcpt/syscall/signal-kill.c | 25 ++ lab5/c/src/xcpt/syscall/signal.c | 15 ++ lab5/c/src/xcpt/syscall/sigreturn-check.c | 13 + lab5/c/src/xcpt/syscall/sigreturn.S | 50 ++++ lab5/c/src/xcpt/syscall/uart-read.c | 11 +- lab5/c/src/xcpt/syscall/uart-write.c | 10 + lab5/c/src/xcpt/vector-table.S | 2 + lab5/tests/user-program/libc/include/signal.h | 6 + lab5/tests/user-program/libc/src/signal.c | 8 + 21 files changed, 599 insertions(+), 9 deletions(-) create mode 100644 lab5/c/include/oscos/uapi/signal.h create mode 100644 lab5/c/src/sched/run-signal-handler.S create mode 100644 lab5/c/src/sched/sig-handler-main.S create mode 100644 lab5/c/src/xcpt/syscall/signal-kill.c create mode 100644 lab5/c/src/xcpt/syscall/signal.c create mode 100644 lab5/c/src/xcpt/syscall/sigreturn-check.c create mode 100644 lab5/c/src/xcpt/syscall/sigreturn.S diff --git a/lab5/c/Makefile b/lab5/c/Makefile index 81e6075c8..283d07950 100644 --- a/lab5/c/Makefile +++ b/lab5/c/Makefile @@ -25,7 +25,8 @@ OBJS = start main console devicetree initrd panic shell \ drivers/aux drivers/gpio drivers/l1ic drivers/l2ic drivers/mailbox \ drivers/mini-uart drivers/pm \ mem/malloc mem/page-alloc mem/startup-alloc mem/types mem/vm \ - sched/idle-thread sched/periodic-sched sched/sched sched/schedule \ + sched/idle-thread sched/periodic-sched sched/run-signal-handler \ + sched/sched sched/schedule sched/sig-handler-main \ sched/thread-main sched/user-program-main \ timer/delay timer/timeout \ xcpt/default-handler xcpt/irq-handler xcpt/svc-handler \ @@ -34,6 +35,8 @@ OBJS = start main console devicetree initrd panic shell \ xcpt/syscall/uart-write xcpt/syscall/exec xcpt/syscall/fork \ xcpt/syscall/fork-impl xcpt/syscall/fork-child-ret \ xcpt/syscall/exit xcpt/syscall/mbox-call xcpt/syscall/kill \ + xcpt/syscall/signal xcpt/syscall/signal-kill \ + xcpt/syscall/sigreturn xcpt/syscall/sigreturn-check \ libc/ctype libc/stdio libc/stdlib/qsort libc/string \ utils/core-id utils/fmt utils/heapq utils/rb LD_SCRIPT = $(SRC_DIR)/linker.ld diff --git a/lab5/c/include/oscos/sched.h b/lab5/c/include/oscos/sched.h index ba38360e1..bd48e328c 100644 --- a/lab5/c/include/oscos/sched.h +++ b/lab5/c/include/oscos/sched.h @@ -8,6 +8,7 @@ #include #include "oscos/mem/types.h" +#include "oscos/uapi/signal.h" #include "oscos/xcpt/trap-frame.h" typedef struct thread_list_node_t { @@ -42,10 +43,21 @@ typedef struct { thread_list_node_t list_node; thread_ctx_t ctx; size_t id; + struct { + bool is_waiting : 1; + bool is_stopped : 1; + bool is_waken_up_by_signal : 1; + bool is_handling_signal : 1; + } status; page_id_t stack_page_id; struct process_t *process; } thread_t; +typedef struct page_id_list_node_t { + page_id_t page_id; + struct page_id_list_node_t *next; +} page_id_list_node_t; + typedef struct process_t { size_t id; shared_page_t *text_page; @@ -54,7 +66,10 @@ typedef struct process_t { #else page_id_t user_stack_page_id; #endif + page_id_list_node_t *signal_stack_pages; thread_t *main_thread; + uint32_t pending_signals, blocked_signals; + sighandler_t signal_handlers[32]; } process_t; /// \brief Initializes the scheduler and creates the idle thread. @@ -143,4 +158,14 @@ void sched_setup_periodic_scheduling(void); /// \brief Do what the idle thread should do. noreturn void idle(void); +/// \brief Sets the signal handler of a process and returns the old one. +sighandler_t set_signal_handler(process_t *process, int signal, + sighandler_t handler); + +/// \brief Delivers the given signal to the given process. +void deliver_signal(process_t *process, int signal); + +/// \brief Delivers the given signal to all processes. +void deliver_signal_to_all_processes(int signal); + #endif diff --git a/lab5/c/include/oscos/uapi/errno.h b/lab5/c/include/oscos/uapi/errno.h index 7bbe51420..1632c0383 100644 --- a/lab5/c/include/oscos/uapi/errno.h +++ b/lab5/c/include/oscos/uapi/errno.h @@ -1,5 +1,6 @@ #define ENOENT 2 #define ESRCH 3 +#define EINTR 4 #define EIO 5 #define ENOMEM 12 #define EBUSY 16 diff --git a/lab5/c/include/oscos/uapi/signal.h b/lab5/c/include/oscos/uapi/signal.h new file mode 100644 index 000000000..2e03401d8 --- /dev/null +++ b/lab5/c/include/oscos/uapi/signal.h @@ -0,0 +1,43 @@ +#ifndef OSCOS_UAPI_SIGNAL_H +#define OSCOS_UAPI_SIGNAL_H + +typedef void (*sighandler_t)(void); + +#define SIG_IGN ((sighandler_t)1) +#define SIG_DFL ((sighandler_t)2) + +#define SIGHUP 1 +#define SIGINT 2 +#define SIGQUIT 3 +#define SIGILL 4 +#define SIGTRAP 5 +#define SIGABRT 6 +#define SIGIOT 6 +#define SIGBUS 7 +#define SIGFPE 8 +#define SIGKILL 9 +#define SIGUSR1 10 +#define SIGSEGV 11 +#define SIGUSR2 12 +#define SIGPIPE 13 +#define SIGALRM 14 +#define SIGTERM 15 +#define SIGSTKFLT 16 +#define SIGCHLD 17 +#define SIGCONT 18 +#define SIGSTOP 19 +#define SIGTSTP 20 +#define SIGTTIN 21 +#define SIGTTOU 22 +#define SIGURG 23 +#define SIGXCPU 24 +#define SIGXFSZ 25 +#define SIGVTALRM 26 +#define SIGPROF 27 +#define SIGWINCH 28 +#define SIGIO 29 +#define SIGPWR 30 +#define SIGSYS 31 +#define SIGUNUSED 31 + +#endif diff --git a/lab5/c/include/oscos/uapi/sys/syscall.h b/lab5/c/include/oscos/uapi/sys/syscall.h index c4d4fa90b..910ddcc61 100644 --- a/lab5/c/include/oscos/uapi/sys/syscall.h +++ b/lab5/c/include/oscos/uapi/sys/syscall.h @@ -9,5 +9,8 @@ #define SYS_exit 5 #define SYS_mbox_call 6 #define SYS_kill 7 +#define SYS_signal 8 +#define SYS_signal_kill 9 +#define SYS_sigreturn 10 #endif diff --git a/lab5/c/src/sched/run-signal-handler.S b/lab5/c/src/sched/run-signal-handler.S new file mode 100644 index 000000000..570339b05 --- /dev/null +++ b/lab5/c/src/sched/run-signal-handler.S @@ -0,0 +1,121 @@ +#include "oscos/utils/save-ctx.S" + +.section ".text" + +run_signal_handler: + // Save integer context. + + stp x19, x20, [sp, -(12 * 8)]! + stp x21, x22, [sp, 2 * 8] + stp x23, x24, [sp, 4 * 8] + stp x25, x26, [sp, 6 * 8] + stp x27, x28, [sp, 8 * 8] + stp x29, lr, [sp, 10 * 8] + + // Save FP/SIMD context. + + mrs x2, fpcr + mrs x3, fpsr + stp x2, x3, [sp, -16]! + + stp q0, q1, [sp, -(32 * 16)]! + stp q2, q3, [sp, 2 * 16] + stp q4, q5, [sp, 4 * 16] + stp q6, q7, [sp, 6 * 16] + stp q8, q9, [sp, 8 * 16] + stp q10, q11, [sp, 10 * 16] + stp q12, q13, [sp, 12 * 16] + stp q14, q15, [sp, 14 * 16] + stp q16, q17, [sp, 16 * 16] + stp q18, q19, [sp, 18 * 16] + stp q20, q21, [sp, 20 * 16] + stp q22, q23, [sp, 22 * 16] + stp q24, q25, [sp, 24 * 16] + stp q26, q27, [sp, 26 * 16] + stp q28, q29, [sp, 28 * 16] + stp q30, q31, [sp, 30 * 16] + + // - Unmask all interrupts. + // - AArch64 execution state. + // - EL0t. + msr spsr_el1, xzr + + adr x2, sig_handler_main + msr elr_el1, x2 + + msr sp_el0, x1 + + // Zero the integer registers except for x0, which is used to pass an + // argument to sig_handler_main. + + mov x1, 0 + mov x2, 0 + mov x3, 0 + mov x4, 0 + mov x5, 0 + mov x6, 0 + mov x7, 0 + mov x8, 0 + mov x9, 0 + mov x10, 0 + mov x11, 0 + mov x12, 0 + mov x13, 0 + mov x14, 0 + mov x15, 0 + mov x16, 0 + mov x17, 0 + mov x18, 0 + mov x19, 0 + mov x20, 0 + mov x21, 0 + mov x22, 0 + mov x23, 0 + mov x24, 0 + mov x25, 0 + mov x26, 0 + mov x27, 0 + mov x28, 0 + mov x29, 0 + mov lr, 0 + + // Zero the FP/SIMD registers. + + movi d0, 0 + movi d1, 0 + movi d2, 0 + movi d3, 0 + movi d4, 0 + movi d5, 0 + movi d6, 0 + movi d7, 0 + movi d8, 0 + movi d9, 0 + movi d10, 0 + movi d11, 0 + movi d12, 0 + movi d13, 0 + movi d14, 0 + movi d15, 0 + movi d16, 0 + movi d17, 0 + movi d18, 0 + movi d19, 0 + movi d20, 0 + movi d21, 0 + movi d22, 0 + movi d23, 0 + movi d24, 0 + movi d25, 0 + movi d26, 0 + movi d27, 0 + movi d28, 0 + movi d29, 0 + movi d30, 0 + movi d31, 0 + + eret + +.type run_signal_handler, function +.size run_signal_handler, . - run_signal_handler +.global run_signal_handler diff --git a/lab5/c/src/sched/sched.c b/lab5/c/src/sched/sched.c index 7adae68cb..ebcf79e27 100644 --- a/lab5/c/src/sched/sched.c +++ b/lab5/c/src/sched/sched.c @@ -16,16 +16,20 @@ #define USER_STACK_ORDER 23 // 8MB. #define USER_STACK_BLOCK_ORDER (USER_STACK_ORDER - PAGE_ORDER) +void _suspend_to_wait_queue(thread_list_node_t *wait_queue); void _sched_run_thread(thread_t *thread); void thread_main(void); noreturn void user_program_main(const void *init_pc, const void *init_user_sp, const void *init_kernel_sp); noreturn void fork_child_ret(void); +void run_signal_handler(sighandler_t handler, void *init_user_sp); static size_t _sched_next_tid = 1, _sched_next_pid = 1; static thread_list_node_t _run_queue = {.prev = &_run_queue, .next = &_run_queue}, - _zombies = {.prev = &_zombies, .next = &_zombies}; + _zombies = {.prev = &_zombies, .next = &_zombies}, + _stopped_threads = {.prev = &_stopped_threads, + .next = &_stopped_threads}; static rb_node_t *_processes = NULL; static int _cmp_processes_by_pid(const process_t *const *const p1, @@ -164,6 +168,10 @@ bool thread_create(void (*const task)(void *), void *const arg) { // Initialize the thread structure. thread->id = _alloc_tid(); + thread->status.is_waiting = false; + thread->status.is_stopped = false; + thread->status.is_waken_up_by_signal = false; + thread->status.is_handling_signal = false; thread->process = NULL; thread->ctx.r19 = (uint64_t)(uintptr_t)task; thread->ctx.r20 = (uint64_t)(uintptr_t)arg; @@ -257,7 +265,13 @@ bool process_create(void) { #else process->user_stack_page_id = user_stack_page_id; #endif + process->signal_stack_pages = NULL; process->main_thread = curr_thread; + process->pending_signals = 0; + process->blocked_signals = 0; + for (size_t i = 0; i < 32; i++) { + process->signal_handlers[i] = SIG_DFL; + } curr_thread->process = process; curr_thread->ctx.fp_simd_ctx = fp_simd_ctx; @@ -375,6 +389,14 @@ static void _thread_cleanup(thread_t *const thread) { #else free_pages(thread->process->user_stack_page_id); #endif + + page_id_list_node_t *curr_node = thread->process->signal_stack_pages; + while (curr_node) { + page_id_list_node_t *const next_node = curr_node->next; + free(curr_node); + curr_node = next_node; + } + free(thread->process); } free_pages(thread->stack_page_id); @@ -427,6 +449,10 @@ process_t *fork(const extended_trap_frame_t *const trap_frame) { // Set data. new_thread->id = _alloc_tid(); + new_thread->status.is_waiting = false; + new_thread->status.is_stopped = false; + new_thread->status.is_waken_up_by_signal = false; + new_thread->status.is_handling_signal = false; new_thread->stack_page_id = kernel_stack_page_id; new_thread->process = new_process; @@ -438,7 +464,12 @@ process_t *fork(const extended_trap_frame_t *const trap_frame) { #else new_process->user_stack_page_id = user_stack_page_id; #endif + new_process->signal_stack_pages = NULL; new_process->main_thread = new_thread; + new_process->pending_signals = 0; + new_process->blocked_signals = 0; + memcpy(new_process->signal_handlers, curr_process->signal_handlers, + 32 * sizeof(sighandler_t)); // Set execution context. @@ -503,7 +534,13 @@ void kill_zombies(void) { } } -void schedule(void) { suspend_to_wait_queue(&_run_queue); } +void schedule(void) { _suspend_to_wait_queue(&_run_queue); } + +void suspend_to_wait_queue(thread_list_node_t *const wait_queue) { + XCPT_MASK_ALL(); + current_thread()->status.is_waiting = true; + _suspend_to_wait_queue(wait_queue); +} void add_all_threads_to_run_queue(thread_list_node_t *const wait_queue) { thread_t *thread; @@ -585,3 +622,207 @@ void kill_all_processes(void) { // Kill the current process. thread_exit(); } + +sighandler_t set_signal_handler(process_t *const process, const int signal, + const sighandler_t handler) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const sighandler_t old_handler = process->signal_handlers[signal]; + + // The video player expects SIGKILL to be catchable. This function thus + // follows the protocol used by the video player. + if (!(/* signal == SIGKILL || */ signal == SIGSTOP)) { + process->signal_handlers[signal] = handler; + } + + CRITICAL_SECTION_LEAVE(daif_val); + return old_handler; +} + +void deliver_signal(process_t *const process, const int signal) { + thread_t *const thread = process->main_thread; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + process->pending_signals |= 1 << signal; + + if (thread->status.is_waiting && + (!thread->status.is_stopped || signal == SIGCONT)) { + // Wake up the thread and notify it that it was waken up by a signal. + + thread->status.is_waiting = false; + thread->status.is_stopped = false; + thread->status.is_waken_up_by_signal = true; + + _remove_thread_from_queue(thread); + _add_thread_to_run_queue(thread); + } + + CRITICAL_SECTION_LEAVE(daif_val); +} + +static void _deliver_signal_to_all_processes_rec(const int signal, + const rb_node_t *const node) { + if (!node) + return; + + process_t *const process = *(process_t **)node->payload; + deliver_signal(process, signal); + + _deliver_signal_to_all_processes_rec(signal, node->children[0]); + _deliver_signal_to_all_processes_rec(signal, node->children[1]); +} + +void deliver_signal_to_all_processes(const int signal) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + _deliver_signal_to_all_processes_rec(signal, _processes); + + CRITICAL_SECTION_LEAVE(daif_val); +} + +static bool _signal_default_is_term_or_core(const int signal) { + return signal == SIGABRT || signal == SIGALRM || signal == SIGBUS || + signal == SIGFPE || signal == SIGHUP || signal == SIGILL || + signal == SIGINT || signal == SIGIO || signal == SIGIOT || + signal == SIGKILL || signal == SIGPIPE || signal == SIGPROF || + signal == SIGPWR || signal == SIGQUIT || signal == SIGSEGV || + signal == SIGSTKFLT || signal == SIGSYS || signal == SIGTERM || + signal == SIGTRAP || signal == SIGUNUSED || signal == SIGUSR1 || + signal == SIGUSR2 || signal == SIGVTALRM || signal == SIGXCPU || + signal == SIGXFSZ; +} + +static bool _signal_default_is_stop(const int signal) { + return signal == SIGSTOP || signal == SIGTSTP || signal == SIGTTIN || + signal == SIGTTOU; +} + +void handle_signals(void) { + thread_t *const curr_thread = current_thread(); + if (!curr_thread) // The scheduler has not been initialized. + return; + process_t *const curr_process = curr_thread->process; + + // Save spsr_el1 and elr_el1, since they can be clobbered when running the + // signal handlers. + + uint64_t spsr_val, elr_val; + __asm__ __volatile__("mrs %0, spsr_el1" : "=r"(spsr_val)); + __asm__ __volatile__("mrs %0, elr_el1" : "=r"(elr_val)); + + // Save the old user SP, since the signal handlers will run in a new user + // stack. + + uint64_t old_user_sp; + __asm__ __volatile__("mrs %0, sp_el0" : "=r"(old_user_sp)); + + // Save the status bit in order to support nested signal handling. + + const bool is_handling_signal = curr_thread->status.is_handling_signal; + + // The user stack is allocated lazily. + + bool user_stack_allocation_attempted = false; + spage_id_t user_stack_page_id; + page_id_list_node_t *const curr_signal_stack_pages_node = + curr_process->signal_stack_pages, + *new_signal_stack_pages_node; + + uint32_t deferred_signals = 0; + for (uint32_t handleable_signals; + (handleable_signals = curr_process->pending_signals & + ~curr_process->blocked_signals & + ~deferred_signals) != 0;) { + uint32_t reversed_handleable_signals; + __asm__("rbit %w0, %w1" + : "=r"(reversed_handleable_signals) + : "r"(handleable_signals)); + int signal; + __asm__("clz %w0, %w1" : "=r"(signal) : "r"(reversed_handleable_signals)); + + const sighandler_t handler = curr_process->signal_handlers[signal]; + if (handler == SIG_DFL) { + curr_process->pending_signals &= ~(1 << signal); + + if (_signal_default_is_term_or_core(signal)) { + thread_exit(); + } else if (_signal_default_is_stop(signal)) { + curr_thread->status.is_stopped = true; + suspend_to_wait_queue(&_stopped_threads); + curr_thread->status.is_waken_up_by_signal = false; + } + } else if (handler == SIG_IGN) { + curr_process->pending_signals &= ~(1 << signal); + } else { + // Allocate user stack. + + if (!user_stack_allocation_attempted) { + user_stack_allocation_attempted = true; + user_stack_page_id = alloc_pages(USER_STACK_BLOCK_ORDER); + + if (user_stack_page_id >= 0) { + new_signal_stack_pages_node = malloc(sizeof(page_id_list_node_t)); + if (new_signal_stack_pages_node) { + new_signal_stack_pages_node->page_id = user_stack_page_id; + new_signal_stack_pages_node->next = curr_signal_stack_pages_node; + curr_process->signal_stack_pages = new_signal_stack_pages_node; + } else { + free_pages(user_stack_page_id); + user_stack_page_id = -1; + } + } + } + + if (user_stack_page_id >= 0) { + void *const user_stack_start = + pa_to_kernel_va(page_id_to_pa(user_stack_page_id)), + *const user_stack_end = + (char *)user_stack_start + (1 << USER_STACK_ORDER); + + // Zero the user stack. + + memset(user_stack_start, 0, 1 << USER_STACK_ORDER); + + // Run the signal handler. + + curr_thread->status.is_handling_signal = true; + curr_process->blocked_signals |= 1 << signal; + + run_signal_handler(handler, user_stack_end); + + curr_thread->status.is_handling_signal = false; + curr_process->pending_signals &= ~(1 << signal); + curr_process->blocked_signals &= ~(1 << signal); + } else { + // Defer the handling of this signal. Hopefully, there will be enough + // memory to run the signal handler in the future. + deferred_signals |= 1 << signal; + } + } + } + + // Destroy the user stack. + + if (user_stack_allocation_attempted && user_stack_page_id >= 0) { + free_pages(user_stack_page_id); + curr_process->signal_stack_pages = curr_signal_stack_pages_node; + free(new_signal_stack_pages_node); + } + + // Restore spsr_el1 and elr_el1. + + __asm__ __volatile__("msr spsr_el1, %0" : : "r"(spsr_val)); + __asm__ __volatile__("msr elr_el1, %0" : : "r"(elr_val)); + + // Restore the user SP. + + __asm__ __volatile__("msr sp_el0, %0" : : "r"(old_user_sp)); + + // Restore the status bit. + + curr_thread->status.is_handling_signal = is_handling_signal; +} diff --git a/lab5/c/src/sched/schedule.S b/lab5/c/src/sched/schedule.S index 190ee9a47..51d7213b5 100644 --- a/lab5/c/src/sched/schedule.S +++ b/lab5/c/src/sched/schedule.S @@ -1,6 +1,6 @@ .section ".text" -suspend_to_wait_queue: +_suspend_to_wait_queue: // Enter critical section. msr daifset, 0xf @@ -54,9 +54,9 @@ suspend_to_wait_queue: // Fallthrough. -.size suspend_to_wait_queue, . - suspend_to_wait_queue -.type suspend_to_wait_queue, function -.global suspend_to_wait_queue +.size _suspend_to_wait_queue, . - _suspend_to_wait_queue +.type _suspend_to_wait_queue, function +.global _suspend_to_wait_queue _sched_run_thread: // Restore integer context. diff --git a/lab5/c/src/sched/sig-handler-main.S b/lab5/c/src/sched/sig-handler-main.S new file mode 100644 index 000000000..9c3f530de --- /dev/null +++ b/lab5/c/src/sched/sig-handler-main.S @@ -0,0 +1,10 @@ +.section ".text" + +sig_handler_main: + blr x0 + mov x8, 10 // SYS_sigreturn + svc 0 + +.type sig_handler_main, function +.size sig_handler_main, . - sig_handler_main +.global sig_handler_main diff --git a/lab5/c/src/start.S b/lab5/c/src/start.S index 873f1db18..df9718cad 100644 --- a/lab5/c/src/start.S +++ b/lab5/c/src/start.S @@ -34,6 +34,8 @@ _start: mov x1, 0x1 msr cntkctl_el1, x1 + msr tpidr_el1, xzr + // Clear the .bss section. ldr x1, =_sbss diff --git a/lab5/c/src/xcpt/svc-handler.S b/lab5/c/src/xcpt/svc-handler.S index dadc36d22..434fe9ab2 100644 --- a/lab5/c/src/xcpt/svc-handler.S +++ b/lab5/c/src/xcpt/svc-handler.S @@ -15,7 +15,7 @@ xcpt_svc_handler: // Check the system call number. ubfx x9, x9, 0, 16 cbnz x9, .Lenosys - cmp x8, 8 + cmp x8, 11 b.hi .Lenosys // Table-jump to the system call function. diff --git a/lab5/c/src/xcpt/syscall-table.S b/lab5/c/src/xcpt/syscall-table.S index dd8a81e58..396f9be11 100644 --- a/lab5/c/src/xcpt/syscall-table.S +++ b/lab5/c/src/xcpt/syscall-table.S @@ -9,6 +9,9 @@ syscall_table: b sys_exit b sys_mbox_call b sys_kill + b sys_signal + b sys_signal_kill + b sys_sigreturn .size syscall_table, . - syscall_table .global syscall_table diff --git a/lab5/c/src/xcpt/syscall/signal-kill.c b/lab5/c/src/xcpt/syscall/signal-kill.c new file mode 100644 index 000000000..ef748018d --- /dev/null +++ b/lab5/c/src/xcpt/syscall/signal-kill.c @@ -0,0 +1,25 @@ +#include + +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" + +static bool _is_valid_signal_num(const int signal) { + return 1 <= signal && signal <= 31; +} + +int sys_signal_kill(const int pid, const int signal) { + if (!_is_valid_signal_num(signal)) + return -EINVAL; + + if (pid < 0) { + deliver_signal_to_all_processes(signal); + } else { + process_t *const process = get_process_by_id(pid); + if (!process) + return -ESRCH; + + deliver_signal(process, signal); + } + + return 0; +} diff --git a/lab5/c/src/xcpt/syscall/signal.c b/lab5/c/src/xcpt/syscall/signal.c new file mode 100644 index 000000000..7c10e087a --- /dev/null +++ b/lab5/c/src/xcpt/syscall/signal.c @@ -0,0 +1,15 @@ +#include + +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" + +static bool _is_valid_signal_num(const int signal) { + return 1 <= signal && signal <= 31; +} + +sighandler_t sys_signal(const int signal, const sighandler_t handler) { + if (!_is_valid_signal_num(signal)) + return (sighandler_t)-EINVAL; + + return set_signal_handler(current_thread()->process, signal, handler); +} diff --git a/lab5/c/src/xcpt/syscall/sigreturn-check.c b/lab5/c/src/xcpt/syscall/sigreturn-check.c new file mode 100644 index 000000000..4abe75a25 --- /dev/null +++ b/lab5/c/src/xcpt/syscall/sigreturn-check.c @@ -0,0 +1,13 @@ +#include "oscos/sched.h" +#include "oscos/xcpt.h" + +void sys_sigreturn_check(void) { + XCPT_MASK_ALL(); + + // Crash the process if it incorrectly calls sys_sigreturn when not handling + // signals. + if (!current_thread()->status.is_handling_signal) + thread_exit(); + + XCPT_UNMASK_ALL(); +} diff --git a/lab5/c/src/xcpt/syscall/sigreturn.S b/lab5/c/src/xcpt/syscall/sigreturn.S new file mode 100644 index 000000000..006502119 --- /dev/null +++ b/lab5/c/src/xcpt/syscall/sigreturn.S @@ -0,0 +1,50 @@ +#include "oscos/utils/save-ctx.S" + +.section ".text" + +sys_sigreturn: + // Discard the trap frame. + + add sp, sp, 12 * 16 + + // Check if the system call is called within a signal handler context. + + bl sys_sigreturn_check + + // Restore FP/SIMD context. + + ldp q30, q31, [sp, 30 * 16] + ldp q28, q29, [sp, 28 * 16] + ldp q26, q27, [sp, 26 * 16] + ldp q24, q25, [sp, 24 * 16] + ldp q22, q23, [sp, 22 * 16] + ldp q20, q21, [sp, 20 * 16] + ldp q18, q19, [sp, 18 * 16] + ldp q16, q17, [sp, 16 * 16] + ldp q14, q15, [sp, 14 * 16] + ldp q12, q13, [sp, 12 * 16] + ldp q10, q11, [sp, 10 * 16] + ldp q8, q9, [sp, 8 * 16] + ldp q6, q7, [sp, 6 * 16] + ldp q4, q5, [sp, 4 * 16] + ldp q2, q3, [sp, 2 * 16] + ldp q0, q1, [sp], (32 * 16) + + ldp x0, x1, [sp], 16 + mrs x0, fpcr + mrs x1, fpsr + + // Restore integer context. + + ldp x29, lr, [sp, 10 * 8] + ldp x27, x28, [sp, 8 * 8] + ldp x25, x26, [sp, 6 * 8] + ldp x23, x24, [sp, 4 * 8] + ldp x21, x22, [sp, 2 * 8] + ldp x19, x20, [sp], (12 * 8) + + ret + +.type sys_sigreturn, function +.size sys_sigreturn, . - sys_sigreturn +.global sys_sigreturn diff --git a/lab5/c/src/xcpt/syscall/uart-read.c b/lab5/c/src/xcpt/syscall/uart-read.c index f4ebc20ca..e4646bc77 100644 --- a/lab5/c/src/xcpt/syscall/uart-read.c +++ b/lab5/c/src/xcpt/syscall/uart-read.c @@ -26,10 +26,19 @@ ssize_t sys_uart_read(char buf[const], const size_t size) { break; } + thread_t *const curr_thread = current_thread(); + console_notify_read_ready((void (*)(void *))add_all_threads_to_run_queue, &_wait_queue); - suspend_to_wait_queue(&_wait_queue); + XCPT_MASK_ALL(); + + if (curr_thread->status.is_waken_up_by_signal) { + curr_thread->status.is_waken_up_by_signal = false; + CRITICAL_SECTION_LEAVE(daif_val); + return -EINTR; + } + CRITICAL_SECTION_LEAVE(daif_val); } diff --git a/lab5/c/src/xcpt/syscall/uart-write.c b/lab5/c/src/xcpt/syscall/uart-write.c index 21fb22657..aa23d2722 100644 --- a/lab5/c/src/xcpt/syscall/uart-write.c +++ b/lab5/c/src/xcpt/syscall/uart-write.c @@ -25,9 +25,19 @@ size_t sys_uart_write(const char buf[const], const size_t size) { break; } + thread_t *const curr_thread = current_thread(); + console_notify_write_ready((void (*)(void *))add_all_threads_to_run_queue, &_wait_queue); suspend_to_wait_queue(&_wait_queue); + XCPT_MASK_ALL(); + + if (curr_thread->status.is_waken_up_by_signal) { + curr_thread->status.is_waken_up_by_signal = false; + CRITICAL_SECTION_LEAVE(daif_val); + return -EINTR; + } + CRITICAL_SECTION_LEAVE(daif_val); } diff --git a/lab5/c/src/xcpt/vector-table.S b/lab5/c/src/xcpt/vector-table.S index 3ca6e8631..1ab6922ea 100644 --- a/lab5/c/src/xcpt/vector-table.S +++ b/lab5/c/src/xcpt/vector-table.S @@ -78,6 +78,7 @@ xcpt_sync_lower_el_aarch64_handler: bl xcpt_default_handler .Lxcpt_sync_lower_el_aarch64_handler_end: + bl handle_signals load_aapcs eret @@ -91,6 +92,7 @@ xcpt_irq_lower_el_aarch64_handler: save_aapcs bl xcpt_irq_handler bl task_queue_sched + bl handle_signals load_aapcs eret diff --git a/lab5/tests/user-program/libc/include/signal.h b/lab5/tests/user-program/libc/include/signal.h index e934cbe90..866dc92c2 100644 --- a/lab5/tests/user-program/libc/include/signal.h +++ b/lab5/tests/user-program/libc/include/signal.h @@ -1,3 +1,9 @@ #include "unistd.h" +#include "oscos-uapi/signal.h" + int kill(pid_t pid); + +sighandler_t signal(int signal, sighandler_t handler); + +int signal_kill(pid_t pid, int signal); diff --git a/lab5/tests/user-program/libc/src/signal.c b/lab5/tests/user-program/libc/src/signal.c index 124d17afb..fdc717fa8 100644 --- a/lab5/tests/user-program/libc/src/signal.c +++ b/lab5/tests/user-program/libc/src/signal.c @@ -3,3 +3,11 @@ #include "sys/syscall.h" int kill(const pid_t pid) { return syscall(SYS_kill, pid); } + +sighandler_t signal(const int signal, const sighandler_t handler) { + return (sighandler_t)syscall(SYS_signal, signal, handler); +} + +int signal_kill(const pid_t pid, const int signal) { + return syscall(SYS_signal_kill, pid, signal); +} From 0dc4dd603f16e6d6e9922e7a495f8ed64c78fe0b Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Sun, 25 Jun 2023 09:18:21 +0800 Subject: [PATCH 08/34] Lab 6 C: Import kernel from lab 5 --- lab6/c/.clang-format | 1 + lab6/c/.gitignore | 1 + lab6/c/Makefile | 102 +++ lab6/c/include/oscos/console.h | 141 +++ lab6/c/include/oscos/devicetree.h | 173 ++++ lab6/c/include/oscos/drivers/aux.h | 8 + lab6/c/include/oscos/drivers/board.h | 17 + lab6/c/include/oscos/drivers/gpio.h | 8 + lab6/c/include/oscos/drivers/l1ic.h | 24 + lab6/c/include/oscos/drivers/l2ic.h | 14 + lab6/c/include/oscos/drivers/mailbox.h | 19 + lab6/c/include/oscos/drivers/mini-uart.h | 14 + lab6/c/include/oscos/drivers/pm.h | 14 + lab6/c/include/oscos/initrd.h | 117 +++ lab6/c/include/oscos/libc/ctype.h | 6 + lab6/c/include/oscos/libc/inttypes.h | 9 + lab6/c/include/oscos/libc/stdio.h | 12 + lab6/c/include/oscos/libc/stdlib.h | 11 + lab6/c/include/oscos/libc/string.h | 25 + lab6/c/include/oscos/mem/malloc.h | 30 + lab6/c/include/oscos/mem/page-alloc.h | 87 ++ lab6/c/include/oscos/mem/startup-alloc.h | 34 + lab6/c/include/oscos/mem/types.h | 69 ++ lab6/c/include/oscos/mem/vm.h | 18 + lab6/c/include/oscos/panic.h | 23 + lab6/c/include/oscos/sched.h | 171 ++++ lab6/c/include/oscos/shell.h | 6 + lab6/c/include/oscos/timer/delay.h | 11 + lab6/c/include/oscos/timer/timeout.h | 17 + lab6/c/include/oscos/uapi/errno.h | 10 + lab6/c/include/oscos/uapi/signal.h | 43 + lab6/c/include/oscos/uapi/sys/syscall.h | 16 + lab6/c/include/oscos/uapi/unistd.h | 6 + lab6/c/include/oscos/utils/align.h | 13 + lab6/c/include/oscos/utils/control-flow.h | 11 + lab6/c/include/oscos/utils/core-id.h | 8 + lab6/c/include/oscos/utils/critical-section.h | 20 + lab6/c/include/oscos/utils/endian.h | 21 + lab6/c/include/oscos/utils/fmt.h | 15 + lab6/c/include/oscos/utils/heapq.h | 14 + lab6/c/include/oscos/utils/math.h | 13 + lab6/c/include/oscos/utils/rb.h | 26 + lab6/c/include/oscos/utils/save-ctx.S | 25 + lab6/c/include/oscos/utils/suspend.h | 19 + lab6/c/include/oscos/utils/time.h | 6 + lab6/c/include/oscos/xcpt.h | 9 + lab6/c/include/oscos/xcpt/task-queue.h | 8 + lab6/c/include/oscos/xcpt/trap-frame.h | 24 + lab6/c/src/console.c | 372 ++++++++ lab6/c/src/devicetree.c | 129 +++ lab6/c/src/drivers/aux.c | 28 + lab6/c/src/drivers/gpio.c | 105 +++ lab6/c/src/drivers/l1ic.c | 37 + lab6/c/src/drivers/l2ic.c | 36 + lab6/c/src/drivers/mailbox.c | 83 ++ lab6/c/src/drivers/mini-uart.c | 130 +++ lab6/c/src/drivers/pm.c | 85 ++ lab6/c/src/initrd.c | 130 +++ lab6/c/src/libc/ctype.c | 3 + lab6/c/src/libc/stdio.c | 41 + lab6/c/src/libc/stdlib/qsort.c | 154 ++++ lab6/c/src/libc/string.c | 99 +++ lab6/c/src/linker.ld | 77 ++ lab6/c/src/main.c | 69 ++ lab6/c/src/mem/malloc.c | 353 ++++++++ lab6/c/src/mem/page-alloc.c | 662 ++++++++++++++ lab6/c/src/mem/startup-alloc.c | 24 + lab6/c/src/mem/types.c | 44 + lab6/c/src/mem/vm.c | 12 + lab6/c/src/panic.c | 22 + lab6/c/src/sched/idle-thread.c | 8 + lab6/c/src/sched/periodic-sched.c | 33 + lab6/c/src/sched/run-signal-handler.S | 121 +++ lab6/c/src/sched/sched.c | 828 ++++++++++++++++++ lab6/c/src/sched/schedule.S | 111 +++ lab6/c/src/sched/sig-handler-main.S | 10 + lab6/c/src/sched/thread-main.S | 10 + lab6/c/src/sched/user-program-main.S | 93 ++ lab6/c/src/shell.c | 335 +++++++ lab6/c/src/start.S | 71 ++ lab6/c/src/timer/delay.c | 15 + lab6/c/src/timer/timeout.c | 114 +++ lab6/c/src/utils/core-id.c | 9 + lab6/c/src/utils/fmt.c | 653 ++++++++++++++ lab6/c/src/utils/heapq.c | 77 ++ lab6/c/src/utils/rb.c | 83 ++ lab6/c/src/xcpt/default-handler.c | 21 + lab6/c/src/xcpt/irq-handler.c | 43 + lab6/c/src/xcpt/svc-handler.S | 50 ++ lab6/c/src/xcpt/syscall-table.S | 17 + lab6/c/src/xcpt/syscall/enosys.c | 3 + lab6/c/src/xcpt/syscall/exec.c | 44 + lab6/c/src/xcpt/syscall/exit.c | 3 + lab6/c/src/xcpt/syscall/fork-child-ret.S | 29 + lab6/c/src/xcpt/syscall/fork-impl.c | 11 + lab6/c/src/xcpt/syscall/fork.S | 48 + lab6/c/src/xcpt/syscall/getpid.c | 4 + lab6/c/src/xcpt/syscall/kill.c | 17 + lab6/c/src/xcpt/syscall/mbox-call.c | 36 + lab6/c/src/xcpt/syscall/signal-kill.c | 25 + lab6/c/src/xcpt/syscall/signal.c | 15 + lab6/c/src/xcpt/syscall/sigreturn-check.c | 13 + lab6/c/src/xcpt/syscall/sigreturn.S | 50 ++ lab6/c/src/xcpt/syscall/uart-read.c | 46 + lab6/c/src/xcpt/syscall/uart-write.c | 45 + lab6/c/src/xcpt/task-queue.c | 97 ++ lab6/c/src/xcpt/vector-table.S | 125 +++ lab6/c/src/xcpt/xcpt.c | 7 + lab6/tests/.gitignore | 2 + lab6/tests/build-initrd-video-player.sh | 4 + lab6/tests/build-initrd.sh | 19 + lab6/tests/get-dtb.sh | 5 + lab6/tests/user-program/libc/.gitignore | 1 + lab6/tests/user-program/libc/Makefile | 61 ++ .../libc/include/__detail/utils/fmt.h | 15 + lab6/tests/user-program/libc/include/ctype.h | 6 + lab6/tests/user-program/libc/include/errno.h | 9 + lab6/tests/user-program/libc/include/mbox.h | 6 + .../user-program/libc/include/oscos-uapi | 1 + lab6/tests/user-program/libc/include/signal.h | 9 + lab6/tests/user-program/libc/include/stdio.h | 12 + lab6/tests/user-program/libc/include/stdlib.h | 8 + lab6/tests/user-program/libc/include/string.h | 25 + .../user-program/libc/include/sys/syscall.h | 1 + lab6/tests/user-program/libc/include/unistd.h | 21 + .../libc/src/__detail/utils/fmt.c | 654 ++++++++++++++ lab6/tests/user-program/libc/src/ctype.c | 3 + lab6/tests/user-program/libc/src/errno.c | 3 + lab6/tests/user-program/libc/src/mbox.c | 8 + lab6/tests/user-program/libc/src/signal.c | 13 + lab6/tests/user-program/libc/src/start.S | 15 + lab6/tests/user-program/libc/src/stdio.c | 31 + lab6/tests/user-program/libc/src/stdlib.c | 9 + lab6/tests/user-program/libc/src/string.c | 99 +++ lab6/tests/user-program/libc/src/unistd.c | 19 + .../user-program/libc/src/unistd/syscall.S | 30 + .../user-program/syscall_test/.gitignore | 1 + lab6/tests/user-program/syscall_test/Makefile | 73 ++ .../user-program/syscall_test/src/linker.ld | 80 ++ .../user-program/syscall_test/src/main.c | 61 ++ 140 files changed, 8808 insertions(+) create mode 100644 lab6/c/.clang-format create mode 100644 lab6/c/.gitignore create mode 100644 lab6/c/Makefile create mode 100644 lab6/c/include/oscos/console.h create mode 100644 lab6/c/include/oscos/devicetree.h create mode 100644 lab6/c/include/oscos/drivers/aux.h create mode 100644 lab6/c/include/oscos/drivers/board.h create mode 100644 lab6/c/include/oscos/drivers/gpio.h create mode 100644 lab6/c/include/oscos/drivers/l1ic.h create mode 100644 lab6/c/include/oscos/drivers/l2ic.h create mode 100644 lab6/c/include/oscos/drivers/mailbox.h create mode 100644 lab6/c/include/oscos/drivers/mini-uart.h create mode 100644 lab6/c/include/oscos/drivers/pm.h create mode 100644 lab6/c/include/oscos/initrd.h create mode 100644 lab6/c/include/oscos/libc/ctype.h create mode 100644 lab6/c/include/oscos/libc/inttypes.h create mode 100644 lab6/c/include/oscos/libc/stdio.h create mode 100644 lab6/c/include/oscos/libc/stdlib.h create mode 100644 lab6/c/include/oscos/libc/string.h create mode 100644 lab6/c/include/oscos/mem/malloc.h create mode 100644 lab6/c/include/oscos/mem/page-alloc.h create mode 100644 lab6/c/include/oscos/mem/startup-alloc.h create mode 100644 lab6/c/include/oscos/mem/types.h create mode 100644 lab6/c/include/oscos/mem/vm.h create mode 100644 lab6/c/include/oscos/panic.h create mode 100644 lab6/c/include/oscos/sched.h create mode 100644 lab6/c/include/oscos/shell.h create mode 100644 lab6/c/include/oscos/timer/delay.h create mode 100644 lab6/c/include/oscos/timer/timeout.h create mode 100644 lab6/c/include/oscos/uapi/errno.h create mode 100644 lab6/c/include/oscos/uapi/signal.h create mode 100644 lab6/c/include/oscos/uapi/sys/syscall.h create mode 100644 lab6/c/include/oscos/uapi/unistd.h create mode 100644 lab6/c/include/oscos/utils/align.h create mode 100644 lab6/c/include/oscos/utils/control-flow.h create mode 100644 lab6/c/include/oscos/utils/core-id.h create mode 100644 lab6/c/include/oscos/utils/critical-section.h create mode 100644 lab6/c/include/oscos/utils/endian.h create mode 100644 lab6/c/include/oscos/utils/fmt.h create mode 100644 lab6/c/include/oscos/utils/heapq.h create mode 100644 lab6/c/include/oscos/utils/math.h create mode 100644 lab6/c/include/oscos/utils/rb.h create mode 100644 lab6/c/include/oscos/utils/save-ctx.S create mode 100644 lab6/c/include/oscos/utils/suspend.h create mode 100644 lab6/c/include/oscos/utils/time.h create mode 100644 lab6/c/include/oscos/xcpt.h create mode 100644 lab6/c/include/oscos/xcpt/task-queue.h create mode 100644 lab6/c/include/oscos/xcpt/trap-frame.h create mode 100644 lab6/c/src/console.c create mode 100644 lab6/c/src/devicetree.c create mode 100644 lab6/c/src/drivers/aux.c create mode 100644 lab6/c/src/drivers/gpio.c create mode 100644 lab6/c/src/drivers/l1ic.c create mode 100644 lab6/c/src/drivers/l2ic.c create mode 100644 lab6/c/src/drivers/mailbox.c create mode 100644 lab6/c/src/drivers/mini-uart.c create mode 100644 lab6/c/src/drivers/pm.c create mode 100644 lab6/c/src/initrd.c create mode 100644 lab6/c/src/libc/ctype.c create mode 100644 lab6/c/src/libc/stdio.c create mode 100644 lab6/c/src/libc/stdlib/qsort.c create mode 100644 lab6/c/src/libc/string.c create mode 100644 lab6/c/src/linker.ld create mode 100644 lab6/c/src/main.c create mode 100644 lab6/c/src/mem/malloc.c create mode 100644 lab6/c/src/mem/page-alloc.c create mode 100644 lab6/c/src/mem/startup-alloc.c create mode 100644 lab6/c/src/mem/types.c create mode 100644 lab6/c/src/mem/vm.c create mode 100644 lab6/c/src/panic.c create mode 100644 lab6/c/src/sched/idle-thread.c create mode 100644 lab6/c/src/sched/periodic-sched.c create mode 100644 lab6/c/src/sched/run-signal-handler.S create mode 100644 lab6/c/src/sched/sched.c create mode 100644 lab6/c/src/sched/schedule.S create mode 100644 lab6/c/src/sched/sig-handler-main.S create mode 100644 lab6/c/src/sched/thread-main.S create mode 100644 lab6/c/src/sched/user-program-main.S create mode 100644 lab6/c/src/shell.c create mode 100644 lab6/c/src/start.S create mode 100644 lab6/c/src/timer/delay.c create mode 100644 lab6/c/src/timer/timeout.c create mode 100644 lab6/c/src/utils/core-id.c create mode 100644 lab6/c/src/utils/fmt.c create mode 100644 lab6/c/src/utils/heapq.c create mode 100644 lab6/c/src/utils/rb.c create mode 100644 lab6/c/src/xcpt/default-handler.c create mode 100644 lab6/c/src/xcpt/irq-handler.c create mode 100644 lab6/c/src/xcpt/svc-handler.S create mode 100644 lab6/c/src/xcpt/syscall-table.S create mode 100644 lab6/c/src/xcpt/syscall/enosys.c create mode 100644 lab6/c/src/xcpt/syscall/exec.c create mode 100644 lab6/c/src/xcpt/syscall/exit.c create mode 100644 lab6/c/src/xcpt/syscall/fork-child-ret.S create mode 100644 lab6/c/src/xcpt/syscall/fork-impl.c create mode 100644 lab6/c/src/xcpt/syscall/fork.S create mode 100644 lab6/c/src/xcpt/syscall/getpid.c create mode 100644 lab6/c/src/xcpt/syscall/kill.c create mode 100644 lab6/c/src/xcpt/syscall/mbox-call.c create mode 100644 lab6/c/src/xcpt/syscall/signal-kill.c create mode 100644 lab6/c/src/xcpt/syscall/signal.c create mode 100644 lab6/c/src/xcpt/syscall/sigreturn-check.c create mode 100644 lab6/c/src/xcpt/syscall/sigreturn.S create mode 100644 lab6/c/src/xcpt/syscall/uart-read.c create mode 100644 lab6/c/src/xcpt/syscall/uart-write.c create mode 100644 lab6/c/src/xcpt/task-queue.c create mode 100644 lab6/c/src/xcpt/vector-table.S create mode 100644 lab6/c/src/xcpt/xcpt.c create mode 100644 lab6/tests/.gitignore create mode 100755 lab6/tests/build-initrd-video-player.sh create mode 100755 lab6/tests/build-initrd.sh create mode 100755 lab6/tests/get-dtb.sh create mode 100644 lab6/tests/user-program/libc/.gitignore create mode 100644 lab6/tests/user-program/libc/Makefile create mode 100644 lab6/tests/user-program/libc/include/__detail/utils/fmt.h create mode 100644 lab6/tests/user-program/libc/include/ctype.h create mode 100644 lab6/tests/user-program/libc/include/errno.h create mode 100644 lab6/tests/user-program/libc/include/mbox.h create mode 120000 lab6/tests/user-program/libc/include/oscos-uapi create mode 100644 lab6/tests/user-program/libc/include/signal.h create mode 100644 lab6/tests/user-program/libc/include/stdio.h create mode 100644 lab6/tests/user-program/libc/include/stdlib.h create mode 100644 lab6/tests/user-program/libc/include/string.h create mode 100644 lab6/tests/user-program/libc/include/sys/syscall.h create mode 100644 lab6/tests/user-program/libc/include/unistd.h create mode 100644 lab6/tests/user-program/libc/src/__detail/utils/fmt.c create mode 100644 lab6/tests/user-program/libc/src/ctype.c create mode 100644 lab6/tests/user-program/libc/src/errno.c create mode 100644 lab6/tests/user-program/libc/src/mbox.c create mode 100644 lab6/tests/user-program/libc/src/signal.c create mode 100644 lab6/tests/user-program/libc/src/start.S create mode 100644 lab6/tests/user-program/libc/src/stdio.c create mode 100644 lab6/tests/user-program/libc/src/stdlib.c create mode 100644 lab6/tests/user-program/libc/src/string.c create mode 100644 lab6/tests/user-program/libc/src/unistd.c create mode 100644 lab6/tests/user-program/libc/src/unistd/syscall.S create mode 100644 lab6/tests/user-program/syscall_test/.gitignore create mode 100644 lab6/tests/user-program/syscall_test/Makefile create mode 100644 lab6/tests/user-program/syscall_test/src/linker.ld create mode 100644 lab6/tests/user-program/syscall_test/src/main.c diff --git a/lab6/c/.clang-format b/lab6/c/.clang-format new file mode 100644 index 000000000..9b3aa8b72 --- /dev/null +++ b/lab6/c/.clang-format @@ -0,0 +1 @@ +BasedOnStyle: LLVM diff --git a/lab6/c/.gitignore b/lab6/c/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/lab6/c/.gitignore @@ -0,0 +1 @@ +/build diff --git a/lab6/c/Makefile b/lab6/c/Makefile new file mode 100644 index 000000000..283d07950 --- /dev/null +++ b/lab6/c/Makefile @@ -0,0 +1,102 @@ +DEFAULT_PROFILE = DEBUG +SRC_DIR = src +BUILD_DIR = build + +AS = aarch64-linux-gnu-as +CC = aarch64-linux-gnu-gcc +CPP = aarch64-linux-gnu-cpp +OBJCOPY = aarch64-linux-gnu-objcopy + +CPPFLAGS_BASE = -Iinclude + +ASFLAGS_DEBUG = -g + +CFLAGS_BASE = -std=c17 -pedantic-errors -Wall -Wextra -ffreestanding \ + -mcpu=cortex-a53+nofp+nosimd -mstrict-align +CFLAGS_DEBUG = -g +CFLAGS_RELEASE = -O3 -flto + +LDFLAGS_BASE = -nostdlib -Xlinker --build-id=none +LDFLAGS_RELEASE = -flto -Xlinker --gc-sections + +LDLIBS_BASE = -lgcc + +OBJS = start main console devicetree initrd panic shell \ + drivers/aux drivers/gpio drivers/l1ic drivers/l2ic drivers/mailbox \ + drivers/mini-uart drivers/pm \ + mem/malloc mem/page-alloc mem/startup-alloc mem/types mem/vm \ + sched/idle-thread sched/periodic-sched sched/run-signal-handler \ + sched/sched sched/schedule sched/sig-handler-main \ + sched/thread-main sched/user-program-main \ + timer/delay timer/timeout \ + xcpt/default-handler xcpt/irq-handler xcpt/svc-handler \ + xcpt/syscall-table xcpt/task-queue xcpt/vector-table xcpt/xcpt \ + xcpt/syscall/enosys xcpt/syscall/getpid xcpt/syscall/uart-read \ + xcpt/syscall/uart-write xcpt/syscall/exec xcpt/syscall/fork \ + xcpt/syscall/fork-impl xcpt/syscall/fork-child-ret \ + xcpt/syscall/exit xcpt/syscall/mbox-call xcpt/syscall/kill \ + xcpt/syscall/signal xcpt/syscall/signal-kill \ + xcpt/syscall/sigreturn xcpt/syscall/sigreturn-check \ + libc/ctype libc/stdio libc/stdlib/qsort libc/string \ + utils/core-id utils/fmt utils/heapq utils/rb +LD_SCRIPT = $(SRC_DIR)/linker.ld + +# ------------------------------------------------------------------------------ + +PROFILE = $(DEFAULT_PROFILE) +OUT_DIR = $(BUILD_DIR)/$(PROFILE) + +CPPFLAGS = $(CPPFLAGS_BASE) $(CPPFLAGS_$(PROFILE)) +ASFLAGS = $(ASFLAGS_BASE) $(ASFLAGS_$(PROFILE)) +CFLAGS = $(CFLAGS_BASE) $(CFLAGS_$(PROFILE)) +LDFLAGS = $(LDFLAGS_BASE) $(LDFLAGS_$(PROFILE)) +LDLIBS = $(LDLIBS_BASE) $(LDLIBS_$(PROFILE)) + +OBJ_PATHS = $(addprefix $(OUT_DIR)/,$(addsuffix .o,$(OBJS))) + +# ------------------------------------------------------------------------------ + +.PHONY: all qemu qemu-debug gdb clean-profile clean + +all: $(OUT_DIR)/kernel8.img + +$(OUT_DIR)/kernel8.img: $(OUT_DIR)/kernel8.elf + @mkdir -p $(@D) + $(OBJCOPY) -O binary $^ $@ + +$(OUT_DIR)/kernel8.elf: $(OBJ_PATHS) $(LD_SCRIPT) + @mkdir -p $(@D) + $(CC) -T $(LD_SCRIPT) $(LDFLAGS) $(filter-out $(LD_SCRIPT),$^) $(LDLIBS) -o $@ + +$(OUT_DIR)/%.o: $(SRC_DIR)/%.c + @mkdir -p $(@D) + $(CC) $(CPPFLAGS) $(CFLAGS) -c -MMD -MP -MF $(OUT_DIR)/$*.d $< -o $@ + +$(OUT_DIR)/%.o: $(OUT_DIR)/%.s + @mkdir -p $(@D) + $(AS) $(ASFLAGS) $^ -o $@ + +$(OUT_DIR)/%.s: $(SRC_DIR)/%.S + @mkdir -p $(@D) + $(CPP) $(CPPFLAGS) $^ -o $@ + +qemu: $(OUT_DIR)/kernel8.img + qemu-system-aarch64 -M raspi3b -kernel $< -initrd ../tests/initramfs.cpio \ + -dtb ../tests/bcm2710-rpi-3-b-plus.dtb -display gtk -serial null \ + -serial stdio + +qemu-debug: $(OUT_DIR)/kernel8.img + qemu-system-aarch64 -M raspi3b -kernel $< -initrd ../tests/initramfs.cpio \ + -dtb ../tests/bcm2710-rpi-3-b-plus.dtb -display gtk -serial null \ + -serial stdio -S -s + +gdb: $(OUT_DIR)/kernel8.elf + gdb -s $< -ex 'target remote :1234' + +clean-profile: + $(RM) -r $(OUT_DIR) + +clean: + $(RM) -r $(BUILD_DIR) + +-include $(OUT_DIR)/*.d diff --git a/lab6/c/include/oscos/console.h b/lab6/c/include/oscos/console.h new file mode 100644 index 000000000..65abb41d1 --- /dev/null +++ b/lab6/c/include/oscos/console.h @@ -0,0 +1,141 @@ +/// \file include/oscos/console.h +/// \brief Serial console. +/// +/// The serial console reads and writes data to the mini UART. +/// +/// Before using the serial console, it must be initialized by calling +/// console_init(void) exactly once. Initializing the serial console twice or +/// operating on the serial console before initialization are not checked and +/// may have unintended consequences. +/// +/// The serial console implements two modes: text mode and binary mode. In text +/// mode, automatic newline translation is performed. A "\r" character received +/// by the mini UART will be read as "\n", and writing "\n" to the serial +/// console will send "\r\n" down the mini UART. In binary mode, automatic +/// newline translation is not performed. Any byte received by the mini UART +/// will be read as-is, and every character written to the serial console will +/// also be sent as-is. Upon initialization, the mode is set to text mode. + +#ifndef OSCOS_CONSOLE_H +#define OSCOS_CONSOLE_H + +#include +#include +#include + +/// \brief The mode of the serial console. +typedef enum { + CM_TEXT, ///< Text mode. Performs newline translation. + CM_BINARY ///< Binary mode. Every byte are sent/received as-is. +} console_mode_t; + +/// \brief Initializes the serial console. +/// +/// See the file-level documentation for requirements on initialization. +void console_init(void); + +/// \brief Sets the mode of the serial console. +/// +/// \param mode The mode. Must be `CM_TEXT` or `CM_BINARY`. +void console_set_mode(console_mode_t mode); + +/// \brief Reads a character from the serial console. +/// +/// When calling this function, the serial console must be initialized. +unsigned char console_getc(void); + +/// \brief Reads a character from the serial console without blocking. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return The character read, or a negative number if the read would block. +int console_getc_nonblock(void); + +/// \brief Reads as many characters from the serial console as possible without +/// blocking. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return The number of characters read. +size_t console_read_nonblock(void *buf, size_t count); + +/// \brief Writes a character to the serial console. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return \p c +unsigned char console_putc(unsigned char c); + +/// \brief Writes a character to the serial console without blocking. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return \p c if the operation is successful, or a negative number if the +/// write operation would block. +int console_putc_nonblock(unsigned char c); + +/// \brief Writes characters to the serial console. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return \p count +size_t console_write(const void *buf, size_t count); + +/// \brief Writes as many characters to the serial console as possible without +/// blocking. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return The number of characters written. +size_t console_write_nonblock(const void *buf, size_t count); + +/// \brief Writes a string to the serial console without trailing newline. +/// +/// When calling this function, the serial console must be initialized. +void console_fputs(const char *s); + +/// \brief Writes a string to the serial console with trailing newline. +/// +/// When calling this function, the serial console must be initialized. +void console_puts(const char *s); + +/// \brief Performs string formatting and writes the result to the serial +/// console. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return The number of characters written. +int console_vprintf(const char *restrict format, va_list ap) + __attribute__((format(printf, 1, 0))); + +/// \brief Performs string formatting and writes the result to the serial +/// console. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return The number of characters written. +int console_printf(const char *restrict format, ...) + __attribute__((format(printf, 1, 2))); + +/// \brief Registers the notification callback for read readiness. +/// +/// \param callback The callback that will be called. +/// \param arg The argument that will be passed to \p callback. +/// \return true if the registration succeeds. +/// \return false if the registration fails because an earlier callback has not +/// been called. +bool console_notify_read_ready(void (*callback)(void *), void *arg); + +/// \brief Registers the notification callback for write readiness. +/// +/// \param callback The callback that will be called. +/// \param arg The argument that will be passed to \p callback. +/// \return true if the registration succeeds. +/// \return false if the registration fails because an earlier callback has not +/// been called. +bool console_notify_write_ready(void (*callback)(void *), void *arg); + +/// \brief Interrupt handler. Not meant to be called directly. +void mini_uart_interrupt_handler(void); + +#endif diff --git a/lab6/c/include/oscos/devicetree.h b/lab6/c/include/oscos/devicetree.h new file mode 100644 index 000000000..b4fe42380 --- /dev/null +++ b/lab6/c/include/oscos/devicetree.h @@ -0,0 +1,173 @@ +#ifndef OSCOS_DEVICETREE_H +#define OSCOS_DEVICETREE_H + +#include +#include + +#include "oscos/libc/string.h" +#include "oscos/utils/align.h" +#include "oscos/utils/control-flow.h" +#include "oscos/utils/endian.h" + +/// \brief Flattened devicetree header. +typedef struct { + uint32_t magic; + uint32_t totalsize; + uint32_t off_dt_struct; + uint32_t off_dt_strings; + uint32_t off_mem_rsvmap; + uint32_t version; + uint32_t last_comp_version; + uint32_t boot_cpuid_phys; + uint32_t size_dt_strings; + uint32_t size_dt_struct; +} fdt_header_t; + +/// \brief An entry of the memory reservation block. +typedef struct { + uint64_t address; + uint64_t size; +} fdt_reserve_entry_t; + +/// \brief An item in a node in a flattened devicetree blob. +typedef struct { + uint32_t token; + char payload[]; +} fdt_item_t; + +/// \brief Flattened devicetree property; whatever follows the FDT_PROP token. +typedef struct { + uint32_t len; + uint32_t nameoff; + char value[]; +} fdt_prop_t; + +#define FDT_BEGIN_NODE ((uint32_t)0x00000001) +#define FDT_END_NODE ((uint32_t)0x00000002) +#define FDT_PROP ((uint32_t)0x00000003) +#define FDT_NOP ((uint32_t)0x00000004) +#define FDT_END ((uint32_t)0x00000009) + +/// \brief Initializes the device tree. +/// \param dtb_start The loading address of the devicetree blob. +/// \return Whether or not the initialization succeeds. +bool devicetree_init(const void *dtb_start); + +/// \brief Returns whether or not the devicetree has been successfully +/// initialized. +bool devicetree_is_init(void); + +/// \brief The start of the memory reservation block of the devicetree blob. +#define FDT_START_MEM_RSVMAP \ + ((const fdt_reserve_entry_t \ + *)(fdt_get_start() + \ + rev_u32(((const fdt_header_t *)fdt_get_start())->off_mem_rsvmap))) + +/// \brief The start of the strings block of the devicetree blob. +#define FDT_START_STRINGS \ + (fdt_get_start() + \ + rev_u32(((const fdt_header_t *)fdt_get_start())->off_dt_strings)) + +/// \brief The starting token of an item in the structure block. +#define FDT_TOKEN(ITEM) (rev_u32((ITEM)->token)) + +/// \brief The name of an node in the structure block. +#define FDT_NODE_NAME(NODE) ((NODE)->payload) + +/// \brief The name of a property in the structure block. +#define FDT_PROP_NAME(PROP) (FDT_START_STRINGS + rev_u32((PROP)->nameoff)) + +/// \brief The value of a property in the structure block. +#define FDT_PROP_VALUE(PROP) ((PROP)->value) + +/// \brief The length of the of a property in the structure block. +#define FDT_PROP_VALUE_LEN(PROP) (rev_u32((PROP)->len)) + +#define FDT_ITEMS_START(NODE) \ + ((const fdt_item_t *)ALIGN( \ + (uintptr_t)((NODE)->payload) + strlen(FDT_NODE_NAME(NODE)) + 1, 4)) +#define FDT_ITEM_IS_END(ITEM) (FDT_TOKEN(ITEM) == FDT_END_NODE) + +/// \brief Expands to a for loop that loops over each item in the given node. +/// +/// \param NODE The pointer to the node. +/// \param ITEM_NAME The name of the variable for the item. +#define FDT_FOR_ITEM(NODE, ITEM_NAME) \ + for (const fdt_item_t *ITEM_NAME = FDT_ITEMS_START(NODE); \ + !FDT_ITEM_IS_END(ITEM_NAME); ITEM_NAME = fdt_next_item(ITEM_NAME)) + +/// \brief Expands to a for loop that loops over each item in the given node. +/// +/// This is a variant of FDT_FOR_ITEM that does not declare the variable within +/// the for loop. This is useful, e. g., if one wants to obtain the pointer to +/// the FDT_END_NODE token. +/// +/// \param NODE The pointer to the node. +/// \param ITEM_NAME The name of the variable for the item. Must be a declared +/// variable. +#define FDT_FOR_ITEM_(NODE, ITEM_NAME) \ + for (ITEM_NAME = FDT_ITEMS_START(NODE); !FDT_ITEM_IS_END(ITEM_NAME); \ + ITEM_NAME = fdt_next_item(ITEM_NAME)) + +/// \brief Gets the starting address of the devicetree blob. +const char *fdt_get_start(void); + +/// \brief Gets the ending address of the devicetree blob. +const char *fdt_get_end(void); + +/// \brief Gets the pointer to the next item of the given item in the same node. +/// \param item The item. Must not point to the FDT_END_NODE token. +const fdt_item_t *fdt_next_item(const fdt_item_t *item); + +/// \brief A node of the parent node linked list. +typedef struct fdt_traverse_parent_list_node_t { + const fdt_item_t *node; + const struct fdt_traverse_parent_list_node_t *parent; +} fdt_traverse_parent_list_node_t; + +/// \brief Callback for void fdt_traverse(fdt_traverse_callback_t *, void *). +typedef control_flow_t +fdt_traverse_callback_t(void *, const fdt_item_t *, + const fdt_traverse_parent_list_node_t *); + +/// \brief Performs in-order traversal of the devicetree. +/// +/// When the traversal process encounters a node, \p callback is called with +/// \p arg, the pointer to the current node, and the linked list of the parent +/// nodes of the current node. +/// +/// \param callback The callback that is called on each node. +/// \param arg The first argument that will be passed to \p callback. +void fdt_traverse(fdt_traverse_callback_t *callback, void *arg); + +/// \brief The #address-cells and the #size-cells properties. +typedef struct { + uint32_t n_address_cells, n_size_cells; +} fdt_n_address_size_cells_t; + +/// \brief Gets the #address-cells and the #size-cells properties of a node. +/// +/// \param node The pointer to the node. +/// \return The value of the #address-cells and the #size-cells properties, or +/// their respective defaults if these properties are missing. +fdt_n_address_size_cells_t fdt_get_n_address_size_cells(const fdt_item_t *node); + +/// \brief The reg property. +typedef struct { + uintmax_t address, size; +} fdt_reg_t; + +typedef struct { + fdt_reg_t value; + bool address_overflow, size_overflow; +} fdt_read_reg_result_t; + +/// \brief Reads the reg property. +/// +/// \param prop The pointer to the reg property. +/// \param n_cells The #address-cells and the #size-cells properties of the +/// parent node. +fdt_read_reg_result_t fdt_read_reg(const fdt_prop_t *prop, + fdt_n_address_size_cells_t n_cells); + +#endif diff --git a/lab6/c/include/oscos/drivers/aux.h b/lab6/c/include/oscos/drivers/aux.h new file mode 100644 index 000000000..e04db4bd1 --- /dev/null +++ b/lab6/c/include/oscos/drivers/aux.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_DRIVERS_AUX_H +#define OSCOS_DRIVERS_AUX_H + +void aux_init(void); + +void aux_enable_mini_uart(void); + +#endif diff --git a/lab6/c/include/oscos/drivers/board.h b/lab6/c/include/oscos/drivers/board.h new file mode 100644 index 000000000..a91f11e1c --- /dev/null +++ b/lab6/c/include/oscos/drivers/board.h @@ -0,0 +1,17 @@ +#ifndef OSCOS_DRIVERS_BOARD_H +#define OSCOS_DRIVERS_BOARD_H + +// ARM physical address. See +// https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#peripheral-addresses +#define PERIPHERAL_BASE ((void *)0x3f000000) + +#define ARM_LOCAL_PERIPHERAL_BASE ((void *)0x40000000) + +// ? Is the shareability domain for the peripheral memory barriers correct? + +#define PERIPHERAL_READ_BARRIER() \ + __asm__ __volatile__("dsb nshld" : : : "memory") +#define PERIPHERAL_WRITE_BARRIER() \ + __asm__ __volatile__("dsb nshst" : : : "memory") + +#endif diff --git a/lab6/c/include/oscos/drivers/gpio.h b/lab6/c/include/oscos/drivers/gpio.h new file mode 100644 index 000000000..6994b22d6 --- /dev/null +++ b/lab6/c/include/oscos/drivers/gpio.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_DRIVERS_GPIO_H +#define OSCOS_DRIVERS_GPIO_H + +void gpio_init(void); + +void gpio_setup_uart0_gpio14(void); + +#endif diff --git a/lab6/c/include/oscos/drivers/l1ic.h b/lab6/c/include/oscos/drivers/l1ic.h new file mode 100644 index 000000000..a9f33889c --- /dev/null +++ b/lab6/c/include/oscos/drivers/l1ic.h @@ -0,0 +1,24 @@ +#ifndef OSCOS_DRIVERS_L1IC_H +#define OSCOS_DRIVERS_L1IC_H + +#include +#include + +#define INT_L1_SRC_TIMER0 ((uint32_t)(1 << 0)) +#define INT_L1_SRC_TIMER1 ((uint32_t)(1 << 1)) +#define INT_L1_SRC_TIMER2 ((uint32_t)(1 << 2)) +#define INT_L1_SRC_TIMER3 ((uint32_t)(1 << 3)) +#define INT_L1_SRC_MBOX0 ((uint32_t)(1 << 4)) +#define INT_L1_SRC_MBOX1 ((uint32_t)(1 << 5)) +#define INT_L1_SRC_MBOX2 ((uint32_t)(1 << 6)) +#define INT_L1_SRC_MBOX3 ((uint32_t)(1 << 7)) +#define INT_L1_SRC_GPU ((uint32_t)(1 << 8)) +#define INT_L1_SRC_PMU ((uint32_t)(1 << 9)) + +void l1ic_init(void); + +uint32_t l1ic_get_int_src(size_t core_id); + +void l1ic_enable_core_timer_irq(size_t core_id); + +#endif diff --git a/lab6/c/include/oscos/drivers/l2ic.h b/lab6/c/include/oscos/drivers/l2ic.h new file mode 100644 index 000000000..c11a71b50 --- /dev/null +++ b/lab6/c/include/oscos/drivers/l2ic.h @@ -0,0 +1,14 @@ +#ifndef OSCOS_DRIVERS_L2IC_H +#define OSCOS_DRIVERS_L2IC_H + +#include + +#define INT_L2_IRQ_0_SRC_AUX ((uint32_t)(1 << 29)) + +void l2ic_init(void); + +uint32_t l2ic_get_pending_irq_0(void); + +void l2ic_enable_irq_0(uint32_t mask); + +#endif diff --git a/lab6/c/include/oscos/drivers/mailbox.h b/lab6/c/include/oscos/drivers/mailbox.h new file mode 100644 index 000000000..57fa08254 --- /dev/null +++ b/lab6/c/include/oscos/drivers/mailbox.h @@ -0,0 +1,19 @@ +#ifndef OSCOS_DRIVERS_MAILBOX_H +#define OSCOS_DRIVERS_MAILBOX_H + +#include + +#define MAILBOX_CHANNEL_PROPERTY_TAGS_ARM_TO_VC ((unsigned char)8) + +typedef struct { + uint32_t base, size; +} arm_memory_t; + +void mailbox_init(void); + +void mailbox_call(uint32_t message[], unsigned char channel); + +uint32_t mailbox_get_board_revision(void); +arm_memory_t mailbox_get_arm_memory(void); + +#endif diff --git a/lab6/c/include/oscos/drivers/mini-uart.h b/lab6/c/include/oscos/drivers/mini-uart.h new file mode 100644 index 000000000..d2af57e07 --- /dev/null +++ b/lab6/c/include/oscos/drivers/mini-uart.h @@ -0,0 +1,14 @@ +#ifndef OSCOS_DRIVERS_MINI_UART_H +#define OSCOS_DRIVERS_MINI_UART_H + +void mini_uart_init(void); + +int mini_uart_recv_byte_nonblock(void); +int mini_uart_send_byte_nonblock(unsigned char b); + +void mini_uart_enable_rx_interrupt(void); +void mini_uart_disable_rx_interrupt(void); +void mini_uart_enable_tx_interrupt(void); +void mini_uart_disable_tx_interrupt(void); + +#endif diff --git a/lab6/c/include/oscos/drivers/pm.h b/lab6/c/include/oscos/drivers/pm.h new file mode 100644 index 000000000..531b09a50 --- /dev/null +++ b/lab6/c/include/oscos/drivers/pm.h @@ -0,0 +1,14 @@ +#ifndef OSCOS_DRIVERS_PM_H +#define OSCOS_DRIVERS_PM_H + +#include +#include + +void pm_init(void); + +void pm_reset(uint32_t tick); +void pm_cancel_reset(void); + +noreturn void pm_reboot(void); + +#endif diff --git a/lab6/c/include/oscos/initrd.h b/lab6/c/include/oscos/initrd.h new file mode 100644 index 000000000..112cf9b83 --- /dev/null +++ b/lab6/c/include/oscos/initrd.h @@ -0,0 +1,117 @@ +/// \file include/oscos/initrd.h +/// \brief Initial ramdisk. +/// +/// Before reading the initial ramdisk, it must be initialized by calling +/// initrd_init(void). + +#ifndef OSCOS_INITRD_H +#define OSCOS_INITRD_H + +#include +#include + +#include "oscos/libc/string.h" +#include "oscos/utils/align.h" + +/// \brief New ASCII Format CPIO archive header. +typedef struct { + char c_magic[6]; + char c_ino[8]; + char c_mode[8]; + char c_uid[8]; + char c_gid[8]; + char c_nlink[8]; + char c_mtime[8]; + char c_filesize[8]; + char c_devmajor[8]; + char c_devminor[8]; + char c_rdevmajor[8]; + char c_rdevminor[8]; + char c_namesize[8]; + char c_check[8]; +} cpio_newc_header_t; + +#define CPIO_NEWC_MODE_FILE_TYPE_MASK ((uint32_t)0170000) +#define CPIO_NEWC_MODE_FILE_TYPE_LNK ((uint32_t)0120000) +#define CPIO_NEWC_MODE_FILE_TYPE_REG ((uint32_t)0100000) +#define CPIO_NEWC_MODE_FILE_TYPE_DIR ((uint32_t)0040000) + +/// \brief New ASCII Format CPIO archive file entry. +typedef struct { + cpio_newc_header_t header; + char payload[]; +} cpio_newc_entry_t; + +/// \brief Pointer to the first file entry of the initial ramdisk. +#define INITRD_HEAD ((const cpio_newc_entry_t *)initrd_get_start()) + +/// \brief The value of the given header of the file entry. +#define CPIO_NEWC_HEADER_VALUE(ENTRY, HEADER) \ + cpio_newc_parse_header_field((ENTRY)->header.c_##HEADER) + +/// \brief The pathname of the file entry. +#define CPIO_NEWC_PATHNAME(ENTRY) ((const char *)(ENTRY)->payload) + +/// \brief The file data of the file entry. +#define CPIO_NEWC_FILE_DATA(ENTRY) \ + ((const char *)ALIGN((uintptr_t)(ENTRY)->payload + \ + CPIO_NEWC_HEADER_VALUE(ENTRY, namesize), \ + 4)) + +/// \brief The filesize of the file entry. +#define CPIO_NEWC_FILESIZE(ENTRY) CPIO_NEWC_HEADER_VALUE(ENTRY, filesize) + +/// \brief Determines if the file entry is the final sentinel entry. +#define CPIO_NEWC_IS_ENTRY_LAST(ENTRY) \ + (strcmp(CPIO_NEWC_PATHNAME(ENTRY), "TRAILER!!!") == 0) + +/// \brief Returns the next file entry of the given file entry. +/// +/// The given file entry must not be the final sentinel entry. +#define CPIO_NEWC_NEXT_ENTRY(ENTRY) \ + (const cpio_newc_entry_t *)ALIGN( \ + (uintptr_t)CPIO_NEWC_FILE_DATA(ENTRY) + CPIO_NEWC_FILESIZE(ENTRY), 4) + +/// \brief Expands to a for loop that loops over each file entry in the initial +/// ramdisk. +/// +/// \param ENTRY_NAME The name of the variable for the file entry. +#define INITRD_FOR_ENTRY(ENTRY_NAME) \ + for (const cpio_newc_entry_t *ENTRY_NAME = INITRD_HEAD; \ + !CPIO_NEWC_IS_ENTRY_LAST(ENTRY_NAME); \ + ENTRY_NAME = CPIO_NEWC_NEXT_ENTRY(ENTRY_NAME)) + +/// \brief Initializes the initial ramdisk. +/// +/// This function obtains the loading address of the initial ramdisk from the +/// device tree and determines if the initial ramdisk is a valid New ASCII +/// format CPIO archive. +/// +/// \return Whether or not initialization is successful. +bool initrd_init(void); + +/// \brief Returns whether or not the initial ramdisk has been successfully +/// initialized. +bool initrd_is_init(void); + +/// \brief Returns the loading address of the initial ramdisk. +const void *initrd_get_start(void); + +/// \brief Returns the ending address of the initial ramdisk. +const void *initrd_get_end(void); + +/// \brief Parses the given header field of a New ASCII format CPIO archive. +/// +/// The given header field must be valid. +/// +/// \see CPIO_NEWC_HEADER_VALUE +uint32_t cpio_newc_parse_header_field(const char field[static 8]); + +/// \brief Finds the file entry in the initial ramdisk corresponding to the +/// given pathname. +/// \param pathname The pathname. +/// \return The pointer to the file entry if the entry is found, or NULL +/// otherwise. +const cpio_newc_entry_t *initrd_find_entry_by_pathname(const char *pathname); + +#endif diff --git a/lab6/c/include/oscos/libc/ctype.h b/lab6/c/include/oscos/libc/ctype.h new file mode 100644 index 000000000..6e5eb134a --- /dev/null +++ b/lab6/c/include/oscos/libc/ctype.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_LIBC_CTYPE_H +#define OSCOS_LIBC_CTYPE_H + +int isdigit(int c); + +#endif diff --git a/lab6/c/include/oscos/libc/inttypes.h b/lab6/c/include/oscos/libc/inttypes.h new file mode 100644 index 000000000..90ba7b7c6 --- /dev/null +++ b/lab6/c/include/oscos/libc/inttypes.h @@ -0,0 +1,9 @@ +#ifndef OSCOS_LIBC_INTTYPES_H +#define OSCOS_LIBC_INTTYPES_H + +#include + +#define PRIx32 "x" +#define PRIx64 "lx" + +#endif diff --git a/lab6/c/include/oscos/libc/stdio.h b/lab6/c/include/oscos/libc/stdio.h new file mode 100644 index 000000000..2cf00d503 --- /dev/null +++ b/lab6/c/include/oscos/libc/stdio.h @@ -0,0 +1,12 @@ +#ifndef OSCOS_LIBC_STDIO_H +#define OSCOS_LIBC_STDIO_H + +#include +#include + +int snprintf(char str[restrict], size_t size, const char *restrict format, ...) + __attribute__((format(printf, 3, 4))); +int vsnprintf(char str[restrict], size_t size, const char *restrict format, + va_list ap) __attribute__((format(printf, 3, 0))); + +#endif diff --git a/lab6/c/include/oscos/libc/stdlib.h b/lab6/c/include/oscos/libc/stdlib.h new file mode 100644 index 000000000..a09506078 --- /dev/null +++ b/lab6/c/include/oscos/libc/stdlib.h @@ -0,0 +1,11 @@ +#ifndef OSCOS_LIBC_STDLIB_H +#define OSCOS_LIBC_STDLIB_H + +#include + +void qsort(void *base, size_t nmemb, size_t size, + int (*compar)(const void *, const void *)); +void qsort_r(void *base, size_t nmemb, size_t size, + int (*compar)(const void *, const void *, void *), void *arg); + +#endif diff --git a/lab6/c/include/oscos/libc/string.h b/lab6/c/include/oscos/libc/string.h new file mode 100644 index 000000000..5abc5b3af --- /dev/null +++ b/lab6/c/include/oscos/libc/string.h @@ -0,0 +1,25 @@ +#ifndef OSCOS_LIBC_STRING_H +#define OSCOS_LIBC_STRING_H + +#include + +int memcmp(const void *s1, const void *s2, size_t n); +void *memset(void *s, int c, size_t n); +void *memcpy(void *restrict dest, const void *restrict src, size_t n); +void *memmove(void *dest, const void *src, size_t n); + +int strcmp(const char *s1, const char *s2); +int strncmp(const char *s1, const char *s2, size_t n); + +size_t strlen(const char *s); + +// Extensions. + +/// \brief Swap two non-overlapping blocks of memory. +/// +/// \param xs The pointer to the beginning of the first block of memory. +/// \param ys The pointer to the beginning of the second block of memory. +/// \param n The size of the memory blocks. +void memswp(void *restrict xs, void *restrict ys, size_t n); + +#endif diff --git a/lab6/c/include/oscos/mem/malloc.h b/lab6/c/include/oscos/mem/malloc.h new file mode 100644 index 000000000..282ca848f --- /dev/null +++ b/lab6/c/include/oscos/mem/malloc.h @@ -0,0 +1,30 @@ +/// \file include/oscos/mem/malloc.h +/// \brief Dynamic memory allocator. +/// +/// The dynamic memory allocator is a general-purpose memory allocator that +/// allocates physically-contiguous memory. + +#ifndef OSCOS_MEM_MALLOC_H +#define OSCOS_MEM_MALLOC_H + +#include +#include + +/// \brief Initializes the dynamic memory allocator. +void malloc_init(void); + +/// \brief Frees memory allocated using the dynamic memory allocator. +/// +/// \param ptr The pointer to the allocated memory. +void free(void *ptr); + +/// \brief Dynamically allocates memory using the dynamic memory allocator. +/// +/// \param size The requested size in bytes of the allocation. +/// \return The pointer to the allocated memory, or NULL if the request cannot +/// be fulfilled. +void *malloc(size_t size) + __attribute__((alloc_size(1), assume_aligned(alignof(max_align_t)), malloc, + malloc(free, 1))); + +#endif diff --git a/lab6/c/include/oscos/mem/page-alloc.h b/lab6/c/include/oscos/mem/page-alloc.h new file mode 100644 index 000000000..eddee380c --- /dev/null +++ b/lab6/c/include/oscos/mem/page-alloc.h @@ -0,0 +1,87 @@ +/// \file include/oscos/mem/page-alloc.h +/// \brief Page frame allocator. + +#ifndef OSCOS_MEM_PAGE_ALLOC_H +#define OSCOS_MEM_PAGE_ALLOC_H + +#include +#include + +#include "oscos/mem/types.h" + +#define PAGE_ORDER 12 +#define MAX_BLOCK_ORDER 18 + +/// \brief Initializes the page frame allocator. +/// +/// After calling this function, the startup allocator should not be used. +void page_alloc_init(void); + +/// \brief Allocates a block of page frames. +/// \param order The order of the block. +/// \return The page number of the first page, or a negative number if the +/// request cannot be fulfilled. +spage_id_t alloc_pages(size_t order); + +/// \brief Allocates a block of page frames. +/// +/// This function is safe to call only within a critical section. +/// +/// \param order The order of the block. +/// \return The page number of the first page, or a negative number if the +/// request cannot be fulfilled. +spage_id_t alloc_pages_unlocked(size_t order); + +/// \brief Frees a block of page frames. +/// \param page The page number of the first page of the block. +void free_pages(page_id_t page); + +/// \brief Frees a block of page frames. +/// +/// This function is safe to call only within a critical section. +/// +/// \param page The page number of the first page of the block. +void free_pages_unlocked(page_id_t page); + +/// \brief Marks a contiguous range of page frames as either reserved or +/// available. +/// +/// Note that the range can be arbitrary and doesn't need to be a block. +/// +/// \param range The range of page frames to mark. +/// \param is_avail The target reservation status. +void mark_pages(page_id_range_t range, bool is_avail); + +/// \brief Marks a contiguous range of page frames as either reserved or +/// available. +/// +/// Note that the range can be arbitrary and doesn't need to be a block. +/// +/// This function is safe to call only within a critical section. +/// +/// \param range The range of page frames to mark. +/// \param is_avail The target reservation status. +void mark_pages_unlocked(page_id_range_t range, bool is_avail); + +/// \brief Converts the given page ID into its corresponding physical address. +pa_t page_id_to_pa(page_id_t page) __attribute__((pure)); + +/// \brief Converts the given physical address into its corresponding page ID. +/// +/// Before conversion, the physical address is rounded down to the page +/// boundary. I.e., the returned page ID is the page ID whose corresponding +/// physical address range covers the given physical address. +page_id_t pa_to_page_id(pa_t pa) __attribute__((pure)); + +/// \brief Converts the given physical address range into its corresponding page +/// ID range. +/// +/// Before conversion, the starting physical address is rounded down to the page +/// boundary and the ending physical address is rounded up to the page boundary. +/// I.e., the returned page ID range is the smallest page ID range whose +/// corresponding physical address range covers the given physical address +/// range. +page_id_range_t pa_range_to_page_id_range(pa_range_t range) + __attribute__((pure)); + +#endif diff --git a/lab6/c/include/oscos/mem/startup-alloc.h b/lab6/c/include/oscos/mem/startup-alloc.h new file mode 100644 index 000000000..fb626c99d --- /dev/null +++ b/lab6/c/include/oscos/mem/startup-alloc.h @@ -0,0 +1,34 @@ +/// \file include/oscos/mem/startup-alloc.h +/// \brief Startup allocator. +/// +/// The startup allocator is a simple bump pointer allocator that is usable even +/// when no other subsystems are initialized. This memory allocator allocates +/// memory starting from the end of the kernel. Note that memory allocated using +/// this memory allocator cannot be freed. + +#ifndef OSCOS_MEM_STARTUP_ALLOC_H +#define OSCOS_MEM_STARTUP_ALLOC_H + +#include +#include + +#include "oscos/mem/types.h" + +/// \brief Initializes the startup allocator. +void startup_alloc_init(void); + +/// \brief Dynamically allocates memory using the startup allocator. +/// +/// Note that memory allocated by this function cannot be freed. +/// +/// \param size The requested size in bytes of the allocation. +/// \return The pointer to the allocated memory, or NULL if the request cannot +/// be fulfilled. +void *startup_alloc(size_t size) + __attribute__((alloc_size(1), assume_aligned(alignof(max_align_t)), + malloc)); + +/// \brief Gets the memory range allocated through the startup allocator. +va_range_t startup_alloc_get_alloc_range(void) __attribute__((pure)); + +#endif diff --git a/lab6/c/include/oscos/mem/types.h b/lab6/c/include/oscos/mem/types.h new file mode 100644 index 000000000..7aa7e7c99 --- /dev/null +++ b/lab6/c/include/oscos/mem/types.h @@ -0,0 +1,69 @@ +#ifndef OSCOS_MEM_TYPES_H +#define OSCOS_MEM_TYPES_H + +#include + +#include "oscos/libc/inttypes.h" + +/// \brief Physical address. +typedef uint32_t pa_t; + +/// \brief Maximum value of pa_t. +#define PA_MAX UINT32_MAX + +/// \brief Format specifier for printing a pa_t in lowercase hexadecimal format. +#define PRIxPA PRIx32 + +/// \brief Page ID. +typedef uint32_t page_id_t; + +/// \brief Format specifier for printing a page_id_t in lowercase hexadecimal +/// format. +#define PRIxPAGEID PRIx32 + +/// \brief Signed page ID. +typedef int32_t spage_id_t; + +/// \brief Physical address range. +typedef struct { + pa_t start, end; +} pa_range_t; + +/// \brief Page range. +typedef struct { + page_id_t start, end; +} page_id_range_t; + +/// \brief Virtual address range. +typedef struct { + void *start, *end; +} va_range_t; + +/// \brief Shared, reference-counted page. +typedef struct { + size_t ref_count; + page_id_t page_id; +} shared_page_t; + +/// \brief Initializes a shared page. +/// +/// This function sets the reference count of the newly-created shared page +/// to 1. +shared_page_t *shared_page_init(page_id_t page_id); + +/// \brief Clones a shared page. +/// +/// This function increases the reference count of the shared page by 1. +/// +/// \param shared_page The shared page to clone. +/// \return \p shared_page +shared_page_t *shared_page_clone(shared_page_t *shared_page); + +/// \brief Drops a shared page. +/// +/// This function decreases the reference count of the shared page by 1. The +/// shared page structure and the underlying page will be freed when the +/// reference count reaches 0. +void shared_page_drop(shared_page_t *shared_page); + +#endif diff --git a/lab6/c/include/oscos/mem/vm.h b/lab6/c/include/oscos/mem/vm.h new file mode 100644 index 000000000..ecbc9545d --- /dev/null +++ b/lab6/c/include/oscos/mem/vm.h @@ -0,0 +1,18 @@ +#ifndef OSCOS_MEM_VM_H +#define OSCOS_MEM_VM_H + +#include "oscos/mem/types.h" + +/// \brief Converts a kernel space virtual address into its corresponding +/// physical address. +pa_t kernel_va_to_pa(const void *va) __attribute__((const)); + +/// \brief Converts a physical address into its corresponding kernel space +/// virtual address. +void *pa_to_kernel_va(pa_t pa) __attribute__((const)); + +/// \brief Converts a kernel space virtual address range into its corresponding +/// physical address range. +pa_range_t kernel_va_range_to_pa_range(va_range_t range) __attribute__((const)); + +#endif diff --git a/lab6/c/include/oscos/panic.h b/lab6/c/include/oscos/panic.h new file mode 100644 index 000000000..ce113c914 --- /dev/null +++ b/lab6/c/include/oscos/panic.h @@ -0,0 +1,23 @@ +#ifndef OSCOS_PANIC_H +#define OSCOS_PANIC_H + +#include + +/// \brief Starts a kernel panic. +#define PANIC(...) panic_begin(__FILE__, __LINE__, __VA_ARGS__) + +/// \brief Starts a kernel panic. +/// +/// In most cases, the PANIC macro should be used instead of directly calling +/// this function. +/// +/// \param file The path of the source file to appear in the panic message. +/// \param line The line number to appear in the panic message. +/// \param format The format string of the panic message. +/// +/// \see PANIC +noreturn void panic_begin(const char *restrict file, int line, + const char *restrict format, ...) + __attribute__((cold, format(printf, 3, 4))); + +#endif diff --git a/lab6/c/include/oscos/sched.h b/lab6/c/include/oscos/sched.h new file mode 100644 index 000000000..bd48e328c --- /dev/null +++ b/lab6/c/include/oscos/sched.h @@ -0,0 +1,171 @@ +#ifndef OSCOS_SCHED_H +#define OSCOS_SCHED_H + +#include +#include +#include +#include +#include + +#include "oscos/mem/types.h" +#include "oscos/uapi/signal.h" +#include "oscos/xcpt/trap-frame.h" + +typedef struct thread_list_node_t { + struct thread_list_node_t *prev, *next; +} thread_list_node_t; + +typedef struct { + alignas(16) uint64_t words[2]; +} uint128_t; + +typedef struct { + uint128_t v[32]; + alignas(16) struct { + uint64_t fpcr, fpsr; + }; +} thread_fp_simd_ctx_t; + +typedef struct { + alignas(16) union { + struct { + uint64_t r19, r20, r21, r22, r23, r24, r25, r26, r27, r28, r29, pc, + kernel_sp, user_sp; + }; + uint64_t regs[14]; + }; + thread_fp_simd_ctx_t *fp_simd_ctx; +} thread_ctx_t; + +struct process_t; + +typedef struct { + thread_list_node_t list_node; + thread_ctx_t ctx; + size_t id; + struct { + bool is_waiting : 1; + bool is_stopped : 1; + bool is_waken_up_by_signal : 1; + bool is_handling_signal : 1; + } status; + page_id_t stack_page_id; + struct process_t *process; +} thread_t; + +typedef struct page_id_list_node_t { + page_id_t page_id; + struct page_id_list_node_t *next; +} page_id_list_node_t; + +typedef struct process_t { + size_t id; + shared_page_t *text_page; +#ifdef SCHED_ENABLE_SHARED_USER_STACK + shared_page_t *user_stack_page; +#else + page_id_t user_stack_page_id; +#endif + page_id_list_node_t *signal_stack_pages; + thread_t *main_thread; + uint32_t pending_signals, blocked_signals; + sighandler_t signal_handlers[32]; +} process_t; + +/// \brief Initializes the scheduler and creates the idle thread. +/// +/// \return true if the initialization succeeds. +/// \return false if the initialization fails due to memory shortage. +bool sched_init(void); + +/// \brief Creates a thread. +/// +/// \param task The task to execute in the new thread. +/// \param arg The argument to pass to \p task. +/// \return true if the thread creation succeeds. +/// \return false if the thread creation fails due to memory shortage. +bool thread_create(void (*task)(void *), void *arg); + +/// \brief Terminates the current thread. +/// +/// This function should not be called on the idle thread. +noreturn void thread_exit(void); + +/// \brief Gets the current thread. +thread_t *current_thread(void); + +/// \brief Creates a process and name the current thread the main thread of the +/// process. +/// +/// \return true if the process creation succeeds. +/// \return false if the process creation fails due to memory shortage. +bool process_create(void); + +/// \brief Executes the first ever user program on the current process. +/// +/// This function jumps to the user program and does not return if the operation +/// succeeds. If the operation fails due to memory shortage, this function +/// returns. +/// +/// \param text_start The starting address of where the user program is +/// currently located. (Do not confuse it with the entry point +/// of the user program.) +/// \param text_len The length of the user program. +void exec_first(const void *text_start, size_t text_len); + +/// \brief Executes a user program on the current process. +/// +/// This function jumps to the user program and does not return if the operation +/// succeeds. If the operation fails due to memory shortage, this function +/// returns. +/// +/// The text_page_id field of the current process must be valid. +/// +/// \param text_start The starting address of where the user program is +/// currently located. (Do not confuse it with the entry point +/// of the user program.) +/// \param text_len The length of the user program. +void exec(const void *text_start, size_t text_len); + +/// \brief Forks the current process. +process_t *fork(const extended_trap_frame_t *trap_frame); + +/// \brief Kills zombie threads. +void kill_zombies(void); + +/// \brief Yields CPU for the current thread and runs the scheduler. +void schedule(void); + +/// \brief Puts the current thread to the given wait queue and runs the +/// scheduler. +void suspend_to_wait_queue(thread_list_node_t *wait_queue); + +/// \brief Adds every thread in the given wait queue to the run queue. +void add_all_threads_to_run_queue(thread_list_node_t *wait_queue); + +/// \brief Gets a process by its PID. +process_t *get_process_by_id(size_t pid); + +/// \brief Kills a process. +void kill_process(process_t *process); + +/// \brief Kills all processes. +void kill_all_processes(void); + +/// \brief Sets up periodic scheduling. +void sched_setup_periodic_scheduling(void); + +/// \brief Do what the idle thread should do. +noreturn void idle(void); + +/// \brief Sets the signal handler of a process and returns the old one. +sighandler_t set_signal_handler(process_t *process, int signal, + sighandler_t handler); + +/// \brief Delivers the given signal to the given process. +void deliver_signal(process_t *process, int signal); + +/// \brief Delivers the given signal to all processes. +void deliver_signal_to_all_processes(int signal); + +#endif diff --git a/lab6/c/include/oscos/shell.h b/lab6/c/include/oscos/shell.h new file mode 100644 index 000000000..742f7907e --- /dev/null +++ b/lab6/c/include/oscos/shell.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_SHELL_H +#define OSCOS_SHELL_H + +void run_shell(void); + +#endif diff --git a/lab6/c/include/oscos/timer/delay.h b/lab6/c/include/oscos/timer/delay.h new file mode 100644 index 000000000..49eebc7cd --- /dev/null +++ b/lab6/c/include/oscos/timer/delay.h @@ -0,0 +1,11 @@ +#ifndef OSCOS_TIMER_DELAY_H +#define OSCOS_TIMER_DELAY_H + +#include + +/// \brief Delays for the specified number of nanoseconds. +/// +/// Note that this function may delay for a longer period than specified. +void delay_ns(uint64_t ns); + +#endif diff --git a/lab6/c/include/oscos/timer/timeout.h b/lab6/c/include/oscos/timer/timeout.h new file mode 100644 index 000000000..a2e51e23f --- /dev/null +++ b/lab6/c/include/oscos/timer/timeout.h @@ -0,0 +1,17 @@ +#ifndef OSCOS_TIMER_TIMEOUT_H +#define OSCOS_TIMER_TIMEOUT_H + +#include +#include + +void timeout_init(void); + +bool timeout_add_timer_ns(void (*callback)(void *), void *arg, + uint64_t after_ns); + +bool timeout_add_timer_ticks(void (*callback)(void *), void *arg, + uint64_t after_ticks); + +void xcpt_core_timer_interrupt_handler(void); + +#endif diff --git a/lab6/c/include/oscos/uapi/errno.h b/lab6/c/include/oscos/uapi/errno.h new file mode 100644 index 000000000..1632c0383 --- /dev/null +++ b/lab6/c/include/oscos/uapi/errno.h @@ -0,0 +1,10 @@ +#define ENOENT 2 +#define ESRCH 3 +#define EINTR 4 +#define EIO 5 +#define ENOMEM 12 +#define EBUSY 16 +#define EISDIR 21 +#define EINVAL 22 +#define ENOSYS 38 +#define ELOOP 40 diff --git a/lab6/c/include/oscos/uapi/signal.h b/lab6/c/include/oscos/uapi/signal.h new file mode 100644 index 000000000..2e03401d8 --- /dev/null +++ b/lab6/c/include/oscos/uapi/signal.h @@ -0,0 +1,43 @@ +#ifndef OSCOS_UAPI_SIGNAL_H +#define OSCOS_UAPI_SIGNAL_H + +typedef void (*sighandler_t)(void); + +#define SIG_IGN ((sighandler_t)1) +#define SIG_DFL ((sighandler_t)2) + +#define SIGHUP 1 +#define SIGINT 2 +#define SIGQUIT 3 +#define SIGILL 4 +#define SIGTRAP 5 +#define SIGABRT 6 +#define SIGIOT 6 +#define SIGBUS 7 +#define SIGFPE 8 +#define SIGKILL 9 +#define SIGUSR1 10 +#define SIGSEGV 11 +#define SIGUSR2 12 +#define SIGPIPE 13 +#define SIGALRM 14 +#define SIGTERM 15 +#define SIGSTKFLT 16 +#define SIGCHLD 17 +#define SIGCONT 18 +#define SIGSTOP 19 +#define SIGTSTP 20 +#define SIGTTIN 21 +#define SIGTTOU 22 +#define SIGURG 23 +#define SIGXCPU 24 +#define SIGXFSZ 25 +#define SIGVTALRM 26 +#define SIGPROF 27 +#define SIGWINCH 28 +#define SIGIO 29 +#define SIGPWR 30 +#define SIGSYS 31 +#define SIGUNUSED 31 + +#endif diff --git a/lab6/c/include/oscos/uapi/sys/syscall.h b/lab6/c/include/oscos/uapi/sys/syscall.h new file mode 100644 index 000000000..910ddcc61 --- /dev/null +++ b/lab6/c/include/oscos/uapi/sys/syscall.h @@ -0,0 +1,16 @@ +#ifndef OSCOS_UAPI_SYS_SYSCALL_H +#define OSCOS_UAPI_SYS_SYSCALL_H + +#define SYS_getpid 0 +#define SYS_uart_read 1 +#define SYS_uart_write 2 +#define SYS_exec 3 +#define SYS_fork 4 +#define SYS_exit 5 +#define SYS_mbox_call 6 +#define SYS_kill 7 +#define SYS_signal 8 +#define SYS_signal_kill 9 +#define SYS_sigreturn 10 + +#endif diff --git a/lab6/c/include/oscos/uapi/unistd.h b/lab6/c/include/oscos/uapi/unistd.h new file mode 100644 index 000000000..80efebdf4 --- /dev/null +++ b/lab6/c/include/oscos/uapi/unistd.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_UAPI_UNISTD_H +#define OSCOS_UAPI_UNISTD_H + +typedef long ssize_t; + +#endif diff --git a/lab6/c/include/oscos/utils/align.h b/lab6/c/include/oscos/utils/align.h new file mode 100644 index 000000000..93d941b9e --- /dev/null +++ b/lab6/c/include/oscos/utils/align.h @@ -0,0 +1,13 @@ +#ifndef OSCOS_UTILS_ALIGN_H +#define OSCOS_UTILS_ALIGN_H + +/// \brief Returns \p X aligned to multiples of \p A. +/// +/// This macro is usually used to align a pointer. When doing so, cast the +/// pointer to `uintptr_t` and cast the result back to the desired pointer type. +/// +/// \param X The value to be aligned. Must be of an arithmetic type. +/// \param A The alignment. +#define ALIGN(X, A) (((X) + (A)-1) / (A) * (A)) + +#endif diff --git a/lab6/c/include/oscos/utils/control-flow.h b/lab6/c/include/oscos/utils/control-flow.h new file mode 100644 index 000000000..856c10c91 --- /dev/null +++ b/lab6/c/include/oscos/utils/control-flow.h @@ -0,0 +1,11 @@ +#ifndef OSCOS_UTILS_CONTROL_FLOW_H +#define OSCOS_UTILS_CONTROL_FLOW_H + +/// \brief Used to tell an operation whether it should exit early or go on as +/// usual. +/// +/// This is modelled after Rust's `std::ops::ControlFlow`, but this doesn't +/// carry values. +typedef enum { CF_CONTINUE, CF_BREAK } control_flow_t; + +#endif diff --git a/lab6/c/include/oscos/utils/core-id.h b/lab6/c/include/oscos/utils/core-id.h new file mode 100644 index 000000000..b23e78a25 --- /dev/null +++ b/lab6/c/include/oscos/utils/core-id.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_UTILS_CORE_ID_H +#define OSCOS_UTILS_CORE_ID_H + +#include + +size_t get_core_id(void); + +#endif diff --git a/lab6/c/include/oscos/utils/critical-section.h b/lab6/c/include/oscos/utils/critical-section.h new file mode 100644 index 000000000..05de74b18 --- /dev/null +++ b/lab6/c/include/oscos/utils/critical-section.h @@ -0,0 +1,20 @@ +#ifndef OSCOS_UTILS_CRITICAL_SECTION_H +#define OSCOS_UTILS_CRITICAL_SECTION_H + +#include "oscos/xcpt.h" + +#define CRITICAL_SECTION_ENTER(DAIF_VAL) \ + do { \ + __asm__ __volatile__("mrs %0, daif" : "=r"(DAIF_VAL)); \ + XCPT_MASK_ALL(); \ + } while (0) + +#define CRITICAL_SECTION_LEAVE(DAIF_VAL) \ + do { \ + /* Prevent the compiler from reordering memory accesses after interrupt \ + unmasking. */ \ + __asm__ __volatile__("" : : : "memory"); \ + __asm__ __volatile__("msr daif, %0" : : "r"(DAIF_VAL)); \ + } while (0) + +#endif diff --git a/lab6/c/include/oscos/utils/endian.h b/lab6/c/include/oscos/utils/endian.h new file mode 100644 index 000000000..1feb8f363 --- /dev/null +++ b/lab6/c/include/oscos/utils/endian.h @@ -0,0 +1,21 @@ +#ifndef OSCOS_UTILS_ENDIAN_H +#define OSCOS_UTILS_ENDIAN_H + +#include +#include + +/// \brief Reverses the byte order in a `uint32_t`. +static inline uint32_t rev_u32(const uint32_t x) { + uint32_t result; + __asm__("rev %w0, %w1" : "=r"(result) : "r"(x)); + return result; +} + +/// \brief Reverses the byte order in a `uint64_t`. +static inline uint64_t rev_u64(const uint64_t x) { + uint64_t result; + __asm__("rev %0, %1" : "=r"(result) : "r"(x)); + return result; +} + +#endif diff --git a/lab6/c/include/oscos/utils/fmt.h b/lab6/c/include/oscos/utils/fmt.h new file mode 100644 index 000000000..d9a68c4f9 --- /dev/null +++ b/lab6/c/include/oscos/utils/fmt.h @@ -0,0 +1,15 @@ +#ifndef OSCOS_UTILS_FMT_H +#define OSCOS_UTILS_FMT_H + +#include + +typedef struct { + void (*putc)(unsigned char, void *); + void (*finalize)(void *); +} printf_vtable_t; + +int vprintf_generic(const printf_vtable_t *vtable, void *arg, + const char *restrict format, va_list ap) + __attribute__((format(printf, 3, 0))); + +#endif diff --git a/lab6/c/include/oscos/utils/heapq.h b/lab6/c/include/oscos/utils/heapq.h new file mode 100644 index 000000000..6abd3d635 --- /dev/null +++ b/lab6/c/include/oscos/utils/heapq.h @@ -0,0 +1,14 @@ +#ifndef OSCOS_UTILS_HEAPQ_H +#define OSCOS_UTILS_HEAPQ_H + +#include + +void heappush(void *restrict base, size_t nmemb, size_t size, + const void *restrict item, + int (*compar)(const void *, const void *, void *), void *arg); + +void heappop(void *restrict base, size_t nmemb, size_t size, + void *restrict item, + int (*compar)(const void *, const void *, void *), void *arg); + +#endif diff --git a/lab6/c/include/oscos/utils/math.h b/lab6/c/include/oscos/utils/math.h new file mode 100644 index 000000000..1e91ed835 --- /dev/null +++ b/lab6/c/include/oscos/utils/math.h @@ -0,0 +1,13 @@ +#ifndef OSCOS_UTILS_MATH_H +#define OSCOS_UTILS_MATH_H + +#include + +/// \brief Returns the ceiling of the log base 2 of the argument. +static inline uint64_t clog2(const uint64_t x) { + uint64_t clz_result; + __asm__("clz %0, %1" : "=r"(clz_result) : "r"(x - 1)); + return 64 - clz_result; +} + +#endif diff --git a/lab6/c/include/oscos/utils/rb.h b/lab6/c/include/oscos/utils/rb.h new file mode 100644 index 000000000..85368c0c6 --- /dev/null +++ b/lab6/c/include/oscos/utils/rb.h @@ -0,0 +1,26 @@ +#ifndef OSCOS_UTILS_RB_H +#define OSCOS_UTILS_RB_H + +#include +#include +#include + +// typedef enum { RB_NC_BLACK, RB_NC_RED } rb_node_colour_t; + +typedef struct rb_node_t { + // rb_node_colour_t colour; + struct rb_node_t *children[2] /*, **parent */; + alignas(16) unsigned char payload[]; +} rb_node_t; + +const void *rb_search(const rb_node_t *root, const void *restrict key, + int (*compar)(const void *, const void *, void *), + void *arg); + +bool rb_insert(rb_node_t **root, size_t size, const void *restrict item, + int (*compar)(const void *, const void *, void *), void *arg); + +void rb_delete(rb_node_t **root, const void *restrict key, + int (*compar)(const void *, const void *, void *), void *arg); + +#endif diff --git a/lab6/c/include/oscos/utils/save-ctx.S b/lab6/c/include/oscos/utils/save-ctx.S new file mode 100644 index 000000000..4485dc9da --- /dev/null +++ b/lab6/c/include/oscos/utils/save-ctx.S @@ -0,0 +1,25 @@ +.macro save_aapcs + stp x0, x1, [sp, -(20 * 8)]! + stp x2, x3, [sp, 2 * 8] + stp x4, x5, [sp, 4 * 8] + stp x6, x7, [sp, 6 * 8] + stp x8, x9, [sp, 8 * 8] + stp x10, x11, [sp, 10 * 8] + stp x12, x13, [sp, 12 * 8] + stp x14, x15, [sp, 14 * 8] + stp x16, x17, [sp, 16 * 8] + stp x18, x30, [sp, 18 * 8] +.endm + +.macro load_aapcs + ldp x18, x30, [sp, 18 * 8] + ldp x16, x17, [sp, 16 * 8] + ldp x14, x15, [sp, 14 * 8] + ldp x12, x13, [sp, 12 * 8] + ldp x10, x11, [sp, 10 * 8] + ldp x8, x9, [sp, 8 * 8] + ldp x6, x7, [sp, 6 * 8] + ldp x4, x5, [sp, 4 * 8] + ldp x2, x3, [sp, 2 * 8] + ldp x0, x1, [sp], (20 * 8) +.endm diff --git a/lab6/c/include/oscos/utils/suspend.h b/lab6/c/include/oscos/utils/suspend.h new file mode 100644 index 000000000..c031cecc6 --- /dev/null +++ b/lab6/c/include/oscos/utils/suspend.h @@ -0,0 +1,19 @@ +#ifndef OSCOS_UTILS_SUSPEND_H +#define OSCOS_UTILS_SUSPEND_H + +#include "oscos/utils/critical-section.h" + +#define WFI_WHILE(COND) \ + do { \ + bool _wfi_while_cond_val = true; \ + while (_wfi_while_cond_val) { \ + uint64_t _wfi_while_daif_val; \ + CRITICAL_SECTION_ENTER(_wfi_while_daif_val); \ + if ((_wfi_while_cond_val = (COND))) { \ + __asm__ __volatile__("wfi"); \ + } \ + CRITICAL_SECTION_LEAVE(_wfi_while_daif_val); \ + } \ + } while (0) + +#endif diff --git a/lab6/c/include/oscos/utils/time.h b/lab6/c/include/oscos/utils/time.h new file mode 100644 index 000000000..12b4c463f --- /dev/null +++ b/lab6/c/include/oscos/utils/time.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_UTILS_TIME_H +#define OSCOS_UTILS_TIME_H + +#define NS_PER_SEC ((uint64_t)1000000000) + +#endif diff --git a/lab6/c/include/oscos/xcpt.h b/lab6/c/include/oscos/xcpt.h new file mode 100644 index 000000000..c5ca13530 --- /dev/null +++ b/lab6/c/include/oscos/xcpt.h @@ -0,0 +1,9 @@ +#ifndef OSCOS_XCPT_H +#define OSCOS_XCPT_H + +#define XCPT_MASK_ALL() __asm__ __volatile__("msr daifset, 0xf") +#define XCPT_UNMASK_ALL() __asm__ __volatile__("msr daifclr, 0xf") + +void xcpt_set_vector_table(void); + +#endif diff --git a/lab6/c/include/oscos/xcpt/task-queue.h b/lab6/c/include/oscos/xcpt/task-queue.h new file mode 100644 index 000000000..4f03ce4e6 --- /dev/null +++ b/lab6/c/include/oscos/xcpt/task-queue.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_XCPT_TASK_QUEUE_H +#define OSCOS_XCPT_TASK_QUEUE_H + +#include + +bool task_queue_add_task(void (*task)(void *), void *arg, int priority); + +#endif diff --git a/lab6/c/include/oscos/xcpt/trap-frame.h b/lab6/c/include/oscos/xcpt/trap-frame.h new file mode 100644 index 000000000..f5a1c11ee --- /dev/null +++ b/lab6/c/include/oscos/xcpt/trap-frame.h @@ -0,0 +1,24 @@ +#ifndef OSCOS_XCPT_TRAP_FRAME_H +#define OSCOS_XCPT_TRAP_FRAME_H + +#include +#include + +typedef struct { + alignas(16) union { + struct { + uint64_t r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, + r15, r16, r17, r18, lr; + }; + uint64_t regs[20]; + }; +} trap_frame_t; + +typedef struct { + alignas(16) struct { + uint64_t spsr, elr; + }; + trap_frame_t trap_frame; +} extended_trap_frame_t; + +#endif diff --git a/lab6/c/src/console.c b/lab6/c/src/console.c new file mode 100644 index 000000000..496a6f539 --- /dev/null +++ b/lab6/c/src/console.c @@ -0,0 +1,372 @@ +#include "oscos/console.h" + +#include + +#include "oscos/drivers/gpio.h" +#include "oscos/drivers/l2ic.h" +#include "oscos/drivers/mini-uart.h" +#include "oscos/mem/startup-alloc.h" +#include "oscos/utils/critical-section.h" +#include "oscos/utils/fmt.h" +#include "oscos/xcpt.h" + +#define READ_BUF_SZ 1024 +#define WRITE_BUF_SZ 1024 + +static volatile unsigned char *_console_read_buf, *_console_write_buf; +static volatile size_t _console_read_buf_start = 0, _console_read_buf_len = 0, + _console_write_buf_start = 0, _console_write_buf_len = 0; +static void (*volatile _console_read_notify_callback)( + void *) = NULL, + (*volatile _console_write_notify_callback)(void *) = NULL; +static void *volatile _console_read_notify_callback_arg, + *volatile _console_write_notify_callback_arg; + +// Buffer operations. + +static void _console_recv_to_buf(void) { + // Read until the read buffer is full or there are nothing to read. + + int read_result; + while (_console_read_buf_len != READ_BUF_SZ && + (read_result = mini_uart_recv_byte_nonblock()) >= 0) { + _console_read_buf[(_console_read_buf_start + _console_read_buf_len++) % + READ_BUF_SZ] = read_result; + } + + // Disable the receive interrupt if the read buffer is full. Otherwise, the + // interrupt will fire again and again after exception return, blocking the + // main code from executing. + if (_console_read_buf_len == READ_BUF_SZ) { + mini_uart_disable_rx_interrupt(); + } + + // Send notification. + + if (_console_read_buf_len != 0) { + void (*const callback)(void *) = _console_read_notify_callback; + _console_read_notify_callback = NULL; + + if (callback) { + callback(_console_read_notify_callback_arg); + } + } +} + +static void _console_send_from_buf(void) { + while (_console_write_buf_len != 0 && + mini_uart_send_byte_nonblock( + _console_write_buf[_console_write_buf_start]) >= 0) { + _console_write_buf_start = (_console_write_buf_start + 1) % WRITE_BUF_SZ; + _console_write_buf_len--; + } + + // Disable the transmit interrupt if the write buffer is full. Otherwise, the + // interrupt will fire again and again after exception return, blocking the + // main code from executing. + if (_console_write_buf_len == 0) { + mini_uart_disable_tx_interrupt(); + } + + // Send notification. + + if (_console_write_buf_len != WRITE_BUF_SZ) { + void (*const callback)(void *) = _console_write_notify_callback; + _console_write_notify_callback = NULL; + + if (callback) { + callback(_console_write_notify_callback_arg); + } + } +} + +// Raw operations. + +static int _console_recv_byte_nonblock(void) { + if (_console_read_buf_len == 0) + return -1; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const unsigned char result = _console_read_buf[_console_read_buf_start]; + _console_read_buf_start = (_console_read_buf_start + 1) % READ_BUF_SZ; + _console_read_buf_len--; + + CRITICAL_SECTION_LEAVE(daif_val); + + // Re-enable receive interrupt. + mini_uart_enable_rx_interrupt(); + + return result; +} + +static bool _console_send_byte_nonblock(const unsigned char b) { + if (_console_write_buf_len == WRITE_BUF_SZ) + return false; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + _console_write_buf[(_console_write_buf_start + _console_write_buf_len++) % + WRITE_BUF_SZ] = b; + + CRITICAL_SECTION_LEAVE(daif_val); + + // Re-enable transmit interrupt. + mini_uart_enable_tx_interrupt(); + + return true; +} + +static bool _console_send_two_bytes_nonblock(const unsigned char b1, + const unsigned char b2) { + if (_console_write_buf_len > WRITE_BUF_SZ - 2) + return false; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + _console_write_buf[(_console_write_buf_start + _console_write_buf_len++) % + WRITE_BUF_SZ] = b1; + _console_write_buf[(_console_write_buf_start + _console_write_buf_len++) % + WRITE_BUF_SZ] = b2; + + CRITICAL_SECTION_LEAVE(daif_val); + + // Re-enable transmit interrupt. + mini_uart_enable_tx_interrupt(); + + return true; +} + +// Mode switching is implemented by switching the vtables for the two primitive +// operations, getc and putc. + +typedef struct { + int (*getc_nonblock)(void); + bool (*putc_nonblock)(unsigned char); +} console_primop_vtable_t; + +static int _console_getc_nonblock_text_mode(void) { + const int c = _console_recv_byte_nonblock(); + if (c < 0) + return c; + return c == '\r' ? '\n' : c; +} + +static int _console_getc_nonblock_binary_mode(void) { + return _console_recv_byte_nonblock(); +} + +static bool _console_putc_nonblock_text_mode(const unsigned char c) { + if (c == '\n') { + return _console_send_two_bytes_nonblock('\r', '\n'); + } else { + return _console_send_byte_nonblock(c); + } +} + +static bool _console_putc_nonblock_binary_mode(const unsigned char c) { + return _console_send_byte_nonblock(c); +} + +static const console_primop_vtable_t + _primop_vtable_text_mode = {.getc_nonblock = + _console_getc_nonblock_text_mode, + .putc_nonblock = + _console_putc_nonblock_text_mode}, + _primop_vtable_binary_mode = {.getc_nonblock = + _console_getc_nonblock_binary_mode, + .putc_nonblock = + _console_putc_nonblock_binary_mode}, + *_primop_vtable; + +// Public functions. + +void console_init(void) { + gpio_setup_uart0_gpio14(); + mini_uart_init(); + + console_set_mode(CM_TEXT); + + // Enable receive interrupt. + mini_uart_enable_rx_interrupt(); + // Enable AUX interrupt on the L2 interrupt controller. + l2ic_enable_irq_0(INT_L2_IRQ_0_SRC_AUX); + + // Allocate the buffers. + _console_read_buf = startup_alloc(READ_BUF_SZ * sizeof(unsigned char)); + _console_write_buf = startup_alloc(WRITE_BUF_SZ * sizeof(unsigned char)); +} + +void console_set_mode(const console_mode_t mode) { + // ? Should we check the value of `mode`? + // * FIXME: If the check is implemented, update the documentation in + // * `include/oscos/console.h`. + + switch (mode) { + case CM_TEXT: + _primop_vtable = &_primop_vtable_text_mode; + break; + + case CM_BINARY: + _primop_vtable = &_primop_vtable_binary_mode; + break; + + default: + __builtin_unreachable(); + } +} + +unsigned char console_getc(void) { + int read_result = -1; + uint64_t daif_val; + + while (read_result < 0) { + CRITICAL_SECTION_ENTER(daif_val); + + if ((read_result = console_getc_nonblock()) < 0) { + __asm__ __volatile__("wfi"); + _console_recv_to_buf(); + } + + CRITICAL_SECTION_LEAVE(daif_val); + } + + return read_result; +} + +int console_getc_nonblock(void) { return _primop_vtable->getc_nonblock(); } + +size_t console_read_nonblock(void *const buf, const size_t count) { + unsigned char *const buf_c = (unsigned char *)buf; + + size_t n_chars_read; + int read_result; + + for (n_chars_read = 0; + n_chars_read < count && (read_result = console_getc_nonblock()) >= 0; + n_chars_read++) { + buf_c[n_chars_read] = read_result; + } + + return n_chars_read; +} + +unsigned char console_putc(const unsigned char c) { + bool putc_successful = false; + uint64_t daif_val; + + while (!putc_successful) { + CRITICAL_SECTION_ENTER(daif_val); + + if (!(putc_successful = console_putc_nonblock(c) >= 0)) { + __asm__ __volatile__("wfi"); + _console_send_from_buf(); + } + + CRITICAL_SECTION_LEAVE(daif_val); + } + + return c; +} + +int console_putc_nonblock(const unsigned char c) { + const bool putc_successful = _primop_vtable->putc_nonblock(c); + return putc_successful ? c : -1; +} + +size_t console_write(const void *const buf, const size_t count) { + const unsigned char *const buf_c = buf; + for (size_t i = 0; i < count; i++) { + console_putc(buf_c[i]); + } + return count; +} + +size_t console_write_nonblock(const void *const buf, const size_t count) { + const unsigned char *const buf_c = buf; + + size_t n_chars_written; + bool putc_successful; + + for (n_chars_written = 0; + n_chars_written < count && + (putc_successful = console_putc_nonblock(buf_c[n_chars_written]) >= 0); + n_chars_written++) + ; + + return n_chars_written; +} + +void console_fputs(const char *const s) { + for (const char *c = s; *c; c++) { + console_putc(*c); + } +} + +void console_puts(const char *const s) { + console_fputs(s); + console_putc('\n'); +} + +int console_printf(const char *const restrict format, ...) { + va_list ap; + va_start(ap, format); + + const int result = console_vprintf(format, ap); + + va_end(ap); + return result; +} + +static void _console_printf_putc(const unsigned char c, void *const _arg) { + (void)_arg; + + console_putc(c); +} + +static void _console_printf_finalize(void *const _arg) { (void)_arg; } + +static const printf_vtable_t _console_printf_vtable = { + .putc = _console_printf_putc, .finalize = _console_printf_finalize}; + +int console_vprintf(const char *const restrict format, va_list ap) { + return vprintf_generic(&_console_printf_vtable, NULL, format, ap); +} + +bool console_notify_read_ready(void (*const callback)(void *), + void *const arg) { + if (_console_read_notify_callback) + return false; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + _console_read_notify_callback = callback; + _console_read_notify_callback_arg = arg; + + CRITICAL_SECTION_LEAVE(daif_val); + return true; +} + +bool console_notify_write_ready(void (*const callback)(void *), + void *const arg) { + if (_console_write_notify_callback) + return false; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + _console_write_notify_callback = callback; + _console_write_notify_callback_arg = arg; + + CRITICAL_SECTION_LEAVE(daif_val); + return true; +} + +void mini_uart_interrupt_handler(void) { + _console_recv_to_buf(); + _console_send_from_buf(); +} diff --git a/lab6/c/src/devicetree.c b/lab6/c/src/devicetree.c new file mode 100644 index 000000000..8a4d75275 --- /dev/null +++ b/lab6/c/src/devicetree.c @@ -0,0 +1,129 @@ +#include "oscos/devicetree.h" + +#include + +static const char *_dtb_start = NULL; + +bool devicetree_init(const void *const dtb_start) { + // TODO: More thoroughly validate the DTB. + + if (rev_u32(((const fdt_header_t *)dtb_start)->magic) == 0xd00dfeed) { + _dtb_start = dtb_start; + return true; + } else { + _dtb_start = NULL; + return false; + } +} + +bool devicetree_is_init(void) { return _dtb_start; } + +const char *fdt_get_start(void) { return _dtb_start; } + +const char *fdt_get_end(void) { + return _dtb_start + rev_u32(((const fdt_header_t *)_dtb_start)->totalsize); +} + +const fdt_item_t *fdt_next_item(const fdt_item_t *const item) { + switch (FDT_TOKEN(item)) { + case FDT_BEGIN_NODE: { + const fdt_item_t *curr; + FDT_FOR_ITEM_(item, curr); + return curr + 1; + } + case FDT_PROP: { + const fdt_prop_t *const prop = (const fdt_prop_t *)(item->payload); + return (const fdt_item_t *)ALIGN( + (uintptr_t)(prop->value + rev_u32(prop->len)), 4); + } + case FDT_NOP: + return item + 1; + default: + __builtin_unreachable(); + } +} + +static const fdt_item_t * +_fdt_traverse_rec(const fdt_item_t *const node, + const fdt_traverse_parent_list_node_t *const parent, + fdt_traverse_callback_t *const callback, void *const arg) { + const control_flow_t result = callback(arg, node, parent); + if (result == CF_CONTINUE) { + // No-op. + } else if (result == CF_BREAK) { + return NULL; + } else { + __builtin_unreachable(); + } + + const fdt_item_t *item; + for (item = FDT_ITEMS_START(node); !FDT_ITEM_IS_END(item);) { + if (FDT_TOKEN(item) == FDT_BEGIN_NODE) { + const fdt_traverse_parent_list_node_t next_parent = {.node = node, + .parent = parent}; + const fdt_item_t *const next_item = + _fdt_traverse_rec(item, &next_parent, callback, arg); + if (!next_item) + return NULL; + item = next_item; + } else { + item = fdt_next_item(item); + } + } + + return item + 1; +} + +void fdt_traverse(fdt_traverse_callback_t *const callback, void *const arg) { + const fdt_item_t *const root_node = + (const fdt_item_t *)(_dtb_start + + rev_u32(((const fdt_header_t *)_dtb_start) + ->off_dt_struct)); + _fdt_traverse_rec(root_node, NULL, callback, arg); +} + +fdt_n_address_size_cells_t +fdt_get_n_address_size_cells(const fdt_item_t *const node) { + uint32_t n_address_cells = 2, n_size_cells = 1; + bool n_address_cells_done = false, n_size_cells_done = false; + FDT_FOR_ITEM(node, item) { + if (FDT_TOKEN(item) == FDT_PROP) { + const fdt_prop_t *const prop = (const fdt_prop_t *)item->payload; + if (strcmp(FDT_PROP_NAME(prop), "#address-cells") == 0) { + n_address_cells = rev_u32(*(const uint32_t *)FDT_PROP_VALUE(prop)); + n_address_cells_done = true; + } else if (strcmp(FDT_PROP_NAME(prop), "#size-cells") == 0) { + n_size_cells = rev_u32(*(const uint32_t *)FDT_PROP_VALUE(prop)); + n_size_cells_done = true; + } + } + if (n_address_cells_done && n_size_cells_done) + break; + } + + return (fdt_n_address_size_cells_t){.n_address_cells = n_address_cells, + .n_size_cells = n_size_cells}; +} + +fdt_read_reg_result_t fdt_read_reg(const fdt_prop_t *const prop, + const fdt_n_address_size_cells_t n_cells) { + const uint32_t *arr = (const uint32_t *)FDT_PROP_VALUE(prop); + + uintmax_t address = 0; + bool address_overflow = false; + for (uint32_t i = 0; i < n_cells.n_address_cells; i++) { + address_overflow = address_overflow || (address >> 32) != 0; + address = address << 32 | rev_u32(*arr++); + } + + uintmax_t size = 0; + bool size_overflow = false; + for (uint32_t i = 0; i < n_cells.n_size_cells; i++) { + size_overflow = size_overflow || (size >> 32) != 0; + size = size << 32 | rev_u32(*arr++); + } + + return (fdt_read_reg_result_t){.value = {.address = address, .size = size}, + .address_overflow = address_overflow, + .size_overflow = size_overflow}; +} diff --git a/lab6/c/src/drivers/aux.c b/lab6/c/src/drivers/aux.c new file mode 100644 index 000000000..c40639f09 --- /dev/null +++ b/lab6/c/src/drivers/aux.c @@ -0,0 +1,28 @@ +#include "oscos/drivers/aux.h" + +#include + +#include "oscos/drivers/board.h" + +#define AUX_REG_BASE ((void *)((char *)PERIPHERAL_BASE + 0x215000)) + +typedef struct { + const volatile uint32_t irq; + volatile uint32_t enb; +} aux_reg_t; + +#define AUX_REG ((aux_reg_t *)AUX_REG_BASE) + +#define AUX_ENB_MINI_UART_ENABLE ((uint32_t)(1 << 0)) + +void aux_init(void) { + // No-op. +} + +void aux_enable_mini_uart(void) { + PERIPHERAL_WRITE_BARRIER(); + + AUX_REG->enb |= AUX_ENB_MINI_UART_ENABLE; + + PERIPHERAL_READ_BARRIER(); +} diff --git a/lab6/c/src/drivers/gpio.c b/lab6/c/src/drivers/gpio.c new file mode 100644 index 000000000..9f8802011 --- /dev/null +++ b/lab6/c/src/drivers/gpio.c @@ -0,0 +1,105 @@ +#include "oscos/drivers/gpio.h" + +#include + +#include "oscos/drivers/board.h" +#include "oscos/timer/delay.h" + +#define GPIO_REG_BASE ((void *)((char *)PERIPHERAL_BASE + 0x200000)) + +typedef struct { + volatile uint32_t fsel[6]; + const volatile uint32_t _reserved0; + volatile uint32_t set[2]; + const volatile uint32_t _reserved1; + volatile uint32_t clr[2]; + const volatile uint32_t _reserved2; + const volatile uint32_t lev[2]; + const volatile uint32_t _reserved3; + volatile uint32_t eds[2]; + const volatile uint32_t _reserved4; + volatile uint32_t ren[2]; + const volatile uint32_t _reserved5; + volatile uint32_t fen[2]; + const volatile uint32_t _reserved6; + volatile uint32_t hen[2]; + const volatile uint32_t _reserved7; + volatile uint32_t len[2]; + const volatile uint32_t _reserved8; + volatile uint32_t aren[2]; + const volatile uint32_t _reserved9; + volatile uint32_t afen[2]; + const volatile uint32_t _reserved10; + volatile uint32_t pud; + volatile uint32_t pudclk[2]; +} gpio_reg_t; + +#define GPIO_REG ((gpio_reg_t *)GPIO_REG_BASE) + +#define GPIO_FSEL_FSEL4_POSN 12 +#define GPIO_FSEL_FSEL4_MASK ((uint32_t)((uint32_t)0x7 << GPIO_FSEL_FSEL4_POSN)) +#define GPIO_FSEL_FSEL5_POSN 15 +#define GPIO_FSEL_FSEL5_MASK ((uint32_t)((uint32_t)0x7 << GPIO_FSEL_FSEL5_POSN)) +#define GPIO_FSEL_FSEL_INPUT ((uint32_t)0x0) +#define GPIO_FSEL_FSEL_ALT5 ((uint32_t)0x2) +#define GPIO_FSEL_FSEL4_INPUT \ + ((uint32_t)(GPIO_FSEL_FSEL_INPUT << GPIO_FSEL_FSEL4_POSN)) +#define GPIO_FSEL_FSEL4_ALT5 \ + ((uint32_t)(GPIO_FSEL_FSEL_ALT5 << GPIO_FSEL_FSEL4_POSN)) +#define GPIO_FSEL_FSEL5_INPUT \ + ((uint32_t)(GPIO_FSEL_FSEL_INPUT << GPIO_FSEL_FSEL5_POSN)) +#define GPIO_FSEL_FSEL5_ALT5 \ + ((uint32_t)(GPIO_FSEL_FSEL_ALT5 << GPIO_FSEL_FSEL5_POSN)) + +#define GPIO_PUD_PUD_OFF ((uint32_t)0x0) +#define GPIO_PUD_PUD_PULL_UP ((uint32_t)0x2) + +void gpio_init(void) { + // No-op. +} + +void gpio_setup_uart0_gpio14(void) { + PERIPHERAL_WRITE_BARRIER(); + + // Set GPIO pin 14 & 15 to use alternate function 5 ({T,R}XD1). + GPIO_REG->fsel[1] = + (GPIO_REG->fsel[1] & ~(GPIO_FSEL_FSEL4_MASK | GPIO_FSEL_FSEL5_MASK)) | + (GPIO_FSEL_FSEL4_ALT5 | GPIO_FSEL_FSEL5_ALT5); + + // Setup the GPIO pull up/down resistors on pin 14 & 15. + // Pin 14 (TXD1): Disabled. + // Pin 15 (RXD1): Pull up. + // + // Instead of disabling the pull up/down resistors on pin 15 as specified in + // lab 1, we instead pull it up to ensure that mini UART doesn't read in + // garbage data when the pin is not connected. + // + // The delay period of 1 μs is calculated by dividing the required delay + // period of 150 clock cycles (as specified in [bcm2835-datasheet]) by 150 + // MHz, the nominal core frequency mentioned in [bcm2835-datasheet], pp. 34. + // We believe 150 MHz should be used instead of the actual core frequency of + // 250 MHz because the setup/hold time of a digital circuit is usually + // specified in terms of real time (e.g. nanoseconds) instead of in clock + // cycles. The specified delay period of 150 clock cycles might have been + // derived by multiplying the actual required delay period of 1 μs by the + // nominal core frequency of 150 MHz. + // + // [bcm2835-datasheet]: + // https://datasheets.raspberrypi.com/bcm2835/bcm2835-peripherals.pdf + + GPIO_REG->pud = GPIO_PUD_PUD_OFF; + delay_ns(1000); + GPIO_REG->pudclk[0] = 1 << 14; + delay_ns(1000); + GPIO_REG->pud = 0; + GPIO_REG->pudclk[0] = 0; + + GPIO_REG->pud = GPIO_PUD_PUD_PULL_UP; + delay_ns(1000); + GPIO_REG->pudclk[0] = 1 << 15; + delay_ns(1000); + GPIO_REG->pud = 0; + GPIO_REG->pudclk[0] = 0; + + PERIPHERAL_READ_BARRIER(); +} diff --git a/lab6/c/src/drivers/l1ic.c b/lab6/c/src/drivers/l1ic.c new file mode 100644 index 000000000..9af46c781 --- /dev/null +++ b/lab6/c/src/drivers/l1ic.c @@ -0,0 +1,37 @@ +#include "oscos/drivers/l1ic.h" + +#include "oscos/drivers/board.h" + +#define CORE_TIMER_IRQCNTL \ + ((volatile uint32_t(*)[4])((char *)ARM_LOCAL_PERIPHERAL_BASE + 0x40)) +#define CORE_IRQ_SOURCE \ + ((volatile uint32_t(*)[4])((char *)ARM_LOCAL_PERIPHERAL_BASE + 0x60)) +#define CORE_FIQ_SOURCE \ + ((volatile uint32_t(*)[4])((char *)ARM_LOCAL_PERIPHERAL_BASE + 0x70)) + +#define CORE_TIMER_IRQCNTL_TIMER0_IRQ ((uint32_t)(1 << 0)) +#define CORE_TIMER_IRQCNTL_TIMER1_IRQ ((uint32_t)(1 << 1)) +#define CORE_TIMER_IRQCNTL_TIMER2_IRQ ((uint32_t)(1 << 2)) +#define CORE_TIMER_IRQCNTL_TIMER3_IRQ ((uint32_t)(1 << 3)) +#define CORE_TIMER_IRQCNTL_TIMER0_FIQ ((uint32_t)(1 << 4)) +#define CORE_TIMER_IRQCNTL_TIMER1_FIQ ((uint32_t)(1 << 5)) +#define CORE_TIMER_IRQCNTL_TIMER2_FIQ ((uint32_t)(1 << 6)) +#define CORE_TIMER_IRQCNTL_TIMER3_FIQ ((uint32_t)(1 << 7)) + +void l1ic_init(void) { + // No-op. +} + +uint32_t l1ic_get_int_src(const size_t core_id) { + const uint32_t result = (*CORE_IRQ_SOURCE)[core_id]; + + PERIPHERAL_READ_BARRIER(); + return result; +} + +void l1ic_enable_core_timer_irq(size_t core_id) { + PERIPHERAL_WRITE_BARRIER(); + + // nCNTPNSIRQ IRQ control = 1 + (*CORE_TIMER_IRQCNTL)[core_id] = CORE_TIMER_IRQCNTL_TIMER1_IRQ; +} diff --git a/lab6/c/src/drivers/l2ic.c b/lab6/c/src/drivers/l2ic.c new file mode 100644 index 000000000..13c15e94c --- /dev/null +++ b/lab6/c/src/drivers/l2ic.c @@ -0,0 +1,36 @@ +#include "oscos/drivers/l2ic.h" + +#include "oscos/drivers/board.h" + +#define L2IC_REG_BASE ((void *)((char *)PERIPHERAL_BASE + 0xb200)) + +typedef struct { + const volatile uint32_t irq_basic_pending; + const volatile uint32_t irq_pending[2]; + volatile uint32_t fiq_control; + volatile uint32_t enable_irqs[2]; + volatile uint32_t enable_basic_irqs; + volatile uint32_t disable_irqs[2]; + volatile uint32_t disable_basic_irqs; +} l2ic_reg_t; + +#define L2IC_REG ((l2ic_reg_t *)L2IC_REG_BASE) + +void l2ic_init(void) { + // No-op. +} + +uint32_t l2ic_get_pending_irq_0(void) { + const uint32_t result = L2IC_REG->irq_pending[0]; + + PERIPHERAL_READ_BARRIER(); + return result; +} + +void l2ic_enable_irq_0(uint32_t mask) { + PERIPHERAL_WRITE_BARRIER(); + + L2IC_REG->enable_irqs[0] |= mask; + + PERIPHERAL_READ_BARRIER(); +} diff --git a/lab6/c/src/drivers/mailbox.c b/lab6/c/src/drivers/mailbox.c new file mode 100644 index 000000000..b0a43570b --- /dev/null +++ b/lab6/c/src/drivers/mailbox.c @@ -0,0 +1,83 @@ +#include "oscos/drivers/mailbox.h" + +#include + +#include "oscos/drivers/board.h" + +#define MAILBOX_REG_BASE ((void *)((char *)PERIPHERAL_BASE + 0xb880)) + +typedef struct { + volatile uint32_t read_write; + const volatile uint32_t _reserved[3]; + volatile uint32_t peek; + volatile uint32_t sender; + volatile uint32_t status; + volatile uint32_t config; +} mailbox_reg_t; + +#define MAILBOX_REGS ((mailbox_reg_t *)MAILBOX_REG_BASE) + +#define MAILBOX_STATUS_EMPTY_MASK ((uint32_t)(1 << 30)) +#define MAILBOX_STATUS_FULL_MASK ((uint32_t)(1 << 31)) + +#define GET_BOARD_REVISION ((uint32_t)0x00010002) +#define GET_ARM_MEMORY ((uint32_t)0x00010005) +#define REQUEST_CODE ((uint32_t)0x00000000) +#define REQUEST_SUCCEED ((uint32_t)0x80000000) +#define REQUEST_FAILED ((uint32_t)0x80000001) +#define TAG_REQUEST_CODE ((uint32_t)0x00000000) +#define END_TAG ((uint32_t)0x00000000) + +void mailbox_init(void) { + // No-op. +} + +void mailbox_call(uint32_t message[], const unsigned char channel) { + PERIPHERAL_WRITE_BARRIER(); + + const uint32_t mailbox_write_data = (uint32_t)(uintptr_t)message | channel; + + while (MAILBOX_REGS[1].status & MAILBOX_STATUS_FULL_MASK) + ; + MAILBOX_REGS[1].read_write = mailbox_write_data; + + for (;;) { + while (MAILBOX_REGS[0].status & MAILBOX_STATUS_EMPTY_MASK) + ; + const uint32_t mailbox_read_data = MAILBOX_REGS[0].read_write; + + if (mailbox_write_data == mailbox_read_data) + break; + } + + PERIPHERAL_READ_BARRIER(); +} + +uint32_t mailbox_get_board_revision(void) { + alignas(16) uint32_t mailbox[7] = {7 * sizeof(uint32_t), + REQUEST_CODE, + GET_BOARD_REVISION, + 4, + TAG_REQUEST_CODE, + 0, + END_TAG}; + + mailbox_call(mailbox, MAILBOX_CHANNEL_PROPERTY_TAGS_ARM_TO_VC); + + return mailbox[5]; +} + +arm_memory_t mailbox_get_arm_memory(void) { + alignas(16) uint32_t mailbox[8] = {8 * sizeof(uint32_t), + REQUEST_CODE, + GET_ARM_MEMORY, + 8, + TAG_REQUEST_CODE, + 0, + 0, + END_TAG}; + + mailbox_call(mailbox, MAILBOX_CHANNEL_PROPERTY_TAGS_ARM_TO_VC); + + return (arm_memory_t){.base = mailbox[5], .size = mailbox[6]}; +} diff --git a/lab6/c/src/drivers/mini-uart.c b/lab6/c/src/drivers/mini-uart.c new file mode 100644 index 000000000..c23500049 --- /dev/null +++ b/lab6/c/src/drivers/mini-uart.c @@ -0,0 +1,130 @@ +#include "oscos/drivers/mini-uart.h" + +#include + +#include "oscos/drivers/aux.h" +#include "oscos/drivers/board.h" + +#define MINI_UART_REG_BASE ((void *)((char *)PERIPHERAL_BASE + 0x215040)) + +typedef struct { + volatile uint32_t io; + volatile uint32_t ier; + volatile uint32_t iir; + volatile uint32_t lcr; + volatile uint32_t mcr; + const volatile uint32_t lsr; + const volatile uint32_t msr; + volatile uint32_t scratch; + volatile uint32_t cntl; + const volatile uint32_t stat; + volatile uint32_t baud; +} mini_uart_reg_t; + +#define MINI_UART_REG ((mini_uart_reg_t *)MINI_UART_REG_BASE) + +// N. B. The interrupt bits specified in the datasheet are incorrect. The values +// listed here are correct. See [bcm2837-datasheet-errata]. +// [bcm2835-datasheet-errata]: https://elinux.org/BCM2835_datasheet_errata + +#define MINI_UART_IER_ENABLE_RECEIVE_INTERRUPT ((uint32_t)(1 << 0)) +#define MINI_UART_IER_ENABLE_TRANSMIT_INTERRUPT ((uint32_t)(1 << 1)) + +#define MINI_UART_LSR_DATA_READY ((uint32_t)(1 << 0)) +#define MINI_UART_LSR_TRANSMITTER_IDLE ((uint32_t)(1 << 6)) +#define MINI_UART_LSR_TRANSMITTER_EMPTY ((uint32_t)(1 << 5)) + +void mini_uart_init(void) { + // The initialization procedure is taken from + // https://oscapstone.github.io/labs/hardware/uart.html. + + // Enable mini UART. + aux_enable_mini_uart(); + + PERIPHERAL_WRITE_BARRIER(); + + // Disable TX and RX. + MINI_UART_REG->cntl = 0; + // Disable interrupt. + MINI_UART_REG->ier = 0; + // Set the data size to 8 bits. + // N. B. The datasheet [bcm2835-datasheet] incorrectly indicates that only bit + // 0 needs to be set. In fact, bits [1:0] need to be set to 3. See + // [bcm2835-datasheet-errata], #p14. + MINI_UART_REG->lcr = 3; + // Disable auto flow control. + MINI_UART_REG->mcr = 0; + // Set baud rate to 115200. + MINI_UART_REG->baud = 270; + // Clear the transmit and receive FIFOs. + MINI_UART_REG->iir = 6; + // Enable TX and RX. + MINI_UART_REG->cntl = 3; + + PERIPHERAL_READ_BARRIER(); +} + +int mini_uart_recv_byte_nonblock(void) { + int result; + + if (!(MINI_UART_REG->lsr & MINI_UART_LSR_DATA_READY)) { + result = -1; + goto end; + } + + result = (unsigned char)MINI_UART_REG->io; + +end: + PERIPHERAL_READ_BARRIER(); + return result; +} + +int mini_uart_send_byte_nonblock(const unsigned char b) { + PERIPHERAL_WRITE_BARRIER(); + + int result; + + if (!(MINI_UART_REG->lsr & MINI_UART_LSR_TRANSMITTER_EMPTY)) { + result = -1; + goto end; + } + + MINI_UART_REG->io = b; + result = 0; + +end: + PERIPHERAL_READ_BARRIER(); + return result; +} + +void mini_uart_enable_rx_interrupt(void) { + PERIPHERAL_WRITE_BARRIER(); + + MINI_UART_REG->ier |= MINI_UART_IER_ENABLE_RECEIVE_INTERRUPT; + + PERIPHERAL_READ_BARRIER(); +} + +void mini_uart_disable_rx_interrupt(void) { + PERIPHERAL_WRITE_BARRIER(); + + MINI_UART_REG->ier &= ~MINI_UART_IER_ENABLE_RECEIVE_INTERRUPT; + + PERIPHERAL_READ_BARRIER(); +} + +void mini_uart_enable_tx_interrupt(void) { + PERIPHERAL_WRITE_BARRIER(); + + MINI_UART_REG->ier |= MINI_UART_IER_ENABLE_TRANSMIT_INTERRUPT; + + PERIPHERAL_READ_BARRIER(); +} + +void mini_uart_disable_tx_interrupt(void) { + PERIPHERAL_WRITE_BARRIER(); + + MINI_UART_REG->ier &= ~MINI_UART_IER_ENABLE_TRANSMIT_INTERRUPT; + + PERIPHERAL_READ_BARRIER(); +} diff --git a/lab6/c/src/drivers/pm.c b/lab6/c/src/drivers/pm.c new file mode 100644 index 000000000..ca4a85fd1 --- /dev/null +++ b/lab6/c/src/drivers/pm.c @@ -0,0 +1,85 @@ +#include "oscos/drivers/pm.h" + +#include + +#include "oscos/drivers/board.h" + +// Information related to the PM registers can be retrieved from [bcm2835-regs], +// #PM. +// +// [bcm2835-regs]: https://elinux.org/BCM2835_registers#PM + +#define PM_REG_BASE ((void *)((char *)PERIPHERAL_BASE + 0x100000)) + +typedef struct { + volatile uint32_t gnric; + volatile uint32_t audio; + const volatile uint32_t _reserved0[4]; + const volatile uint32_t status; + volatile uint32_t rstc; + volatile uint32_t rsts; + volatile uint32_t wdot; + volatile uint32_t pads0; + volatile uint32_t pads2; + volatile uint32_t pads3; + volatile uint32_t pads4; + volatile uint32_t pads5; + volatile uint32_t pads6; + const volatile uint32_t _reserved1; + volatile uint32_t cam0; + volatile uint32_t cam1; + volatile uint32_t cpp2tx; + volatile uint32_t dsi0; + volatile uint32_t dsi1; + volatile uint32_t hdmi; + volatile uint32_t usb; + volatile uint32_t pxldo; + volatile uint32_t pxbg; + volatile uint32_t dft; + volatile uint32_t smps; + volatile uint32_t xosc; + volatile uint32_t sparew; + const volatile uint32_t sparer; + volatile uint32_t avs_rstdr; + volatile uint32_t avs_stat; + volatile uint32_t avs_event; + volatile uint32_t avs_inten; + const volatile uint32_t _reserved2[29]; + const volatile uint32_t dummy; + const volatile uint32_t _reserved3[2]; + volatile uint32_t image; + volatile uint32_t grafx; + volatile uint32_t proc; +} pm_reg_t; + +#define PM_REG ((pm_reg_t *)PM_REG_BASE) + +#define PM_PASSWORD 0x5a000000 + +void pm_init(void) { + // No-op. +} + +void pm_reset(const uint32_t tick) { + PERIPHERAL_WRITE_BARRIER(); + + PM_REG->rstc = PM_PASSWORD | 0x20; + PM_REG->wdot = PM_PASSWORD | tick; + + PERIPHERAL_READ_BARRIER(); +} + +void pm_cancel_reset(void) { + PERIPHERAL_WRITE_BARRIER(); + + PM_REG->rstc = PM_PASSWORD | 0; + PM_REG->wdot = PM_PASSWORD | 0; + + PERIPHERAL_READ_BARRIER(); +} + +noreturn void pm_reboot(void) { + pm_reset(1); + for (;;) + ; +} diff --git a/lab6/c/src/initrd.c b/lab6/c/src/initrd.c new file mode 100644 index 000000000..f215e76aa --- /dev/null +++ b/lab6/c/src/initrd.c @@ -0,0 +1,130 @@ +#include "oscos/initrd.h" + +#include "oscos/devicetree.h" + +static const void *_initrd_start, *_initrd_end; + +static bool _cpio_newc_is_header_field_valid(const char field[const static 8]) { + for (size_t i = 0; i < 8; i++) { + if (!(('0' <= field[i] && field[i] <= '9') || + ('A' <= field[i] && field[i] <= 'F'))) + return false; + } + return true; +} + +static bool _initrd_is_valid(void) { + const cpio_newc_entry_t *entry; + + // Cannot use INITRD_FOR_ENTRY here, since it will evaluate + // `CPIO_NEWC_IS_ENTRY_LAST(entry)` before `entry` is validated. + for (entry = INITRD_HEAD;; entry = CPIO_NEWC_NEXT_ENTRY(entry)) { + if (entry >= (cpio_newc_entry_t *)_initrd_end) + return false; + if (!(strncmp(entry->header.c_magic, "070701", 6) == 0 && + _cpio_newc_is_header_field_valid(entry->header.c_mode) && + _cpio_newc_is_header_field_valid(entry->header.c_uid) && + _cpio_newc_is_header_field_valid(entry->header.c_gid) && + _cpio_newc_is_header_field_valid(entry->header.c_nlink) && + _cpio_newc_is_header_field_valid(entry->header.c_mtime) && + _cpio_newc_is_header_field_valid(entry->header.c_filesize) && + _cpio_newc_is_header_field_valid(entry->header.c_devmajor) && + _cpio_newc_is_header_field_valid(entry->header.c_devminor) && + _cpio_newc_is_header_field_valid(entry->header.c_rdevmajor) && + _cpio_newc_is_header_field_valid(entry->header.c_rdevminor) && + _cpio_newc_is_header_field_valid(entry->header.c_namesize) && + _cpio_newc_is_header_field_valid(entry->header.c_check))) + return false; + + if (CPIO_NEWC_IS_ENTRY_LAST(entry)) + break; + } + + return CPIO_NEWC_NEXT_ENTRY(entry) <= (cpio_newc_entry_t *)_initrd_end; +} + +typedef struct { + bool start_done, end_done; +} initrd_init_dtb_traverse_callback_arg_t; + +static control_flow_t _initrd_init_dtb_traverse_callback( + initrd_init_dtb_traverse_callback_arg_t *const arg, + const fdt_item_t *const node, + const fdt_traverse_parent_list_node_t *const parent) { + if (parent && !parent->parent && + strcmp(FDT_NODE_NAME(node), "chosen") == 0) { // Current node is /chosen. + FDT_FOR_ITEM(node, item) { + if (FDT_TOKEN(item) == FDT_PROP) { + const fdt_prop_t *const prop = (const fdt_prop_t *)item->payload; + if (strcmp(FDT_PROP_NAME(prop), "linux,initrd-start") == 0) { + const uint32_t adr = rev_u32(*(const uint32_t *)FDT_PROP_VALUE(prop)); + _initrd_start = (const void *)(uintptr_t)adr; + arg->start_done = true; + } else if (strcmp(FDT_PROP_NAME(prop), "linux,initrd-end") == 0) { + const uint32_t adr = rev_u32(*(const uint32_t *)FDT_PROP_VALUE(prop)); + _initrd_end = (const void *)(uintptr_t)adr; + arg->end_done = true; + } + + if (arg->start_done && arg->end_done) + break; + } + } + return CF_BREAK; + } else { + return CF_CONTINUE; + } +} + +bool initrd_init(void) { + _initrd_start = NULL; + + if (devicetree_is_init()) { + // Discover the initrd loading address through the devicetree. + + initrd_init_dtb_traverse_callback_arg_t arg = {.start_done = false, + .end_done = false}; + fdt_traverse((fdt_traverse_callback_t *)_initrd_init_dtb_traverse_callback, + &arg); + if (!(arg.start_done && + arg.end_done)) { // Either the /chosen/linux,initrd-start or the + // /chosen/linux,initrd-end property is missing from + // the devicetree. + _initrd_start = NULL; + } + } + + // Validate the initial ramdisk. + if (!(_initrd_start && _initrd_is_valid())) { + _initrd_start = NULL; + } + + return _initrd_start; +} + +bool initrd_is_init(void) { return _initrd_start; } + +const void *initrd_get_start(void) { return _initrd_start; } + +const void *initrd_get_end(void) { return _initrd_end; } + +uint32_t cpio_newc_parse_header_field(const char field[static 8]) { + uint32_t result = 0; + for (size_t i = 0; i < 8; i++) { + const uint32_t digit_value = + '0' <= field[i] && field[i] <= '9' ? field[i] - '0' + : 'A' <= field[i] && field[i] <= 'F' ? field[i] - 'A' + 10 + : (__builtin_unreachable(), 0); + result = result << 4 | digit_value; + } + return result; +} + +const cpio_newc_entry_t *initrd_find_entry_by_pathname(const char *pathname) { + INITRD_FOR_ENTRY(entry) { + if (strcmp(CPIO_NEWC_PATHNAME(entry), pathname) == 0) { + return entry; + } + } + return NULL; +} diff --git a/lab6/c/src/libc/ctype.c b/lab6/c/src/libc/ctype.c new file mode 100644 index 000000000..6ff704afb --- /dev/null +++ b/lab6/c/src/libc/ctype.c @@ -0,0 +1,3 @@ +#include "oscos/libc/ctype.h" + +int isdigit(const int c) { return '0' <= c && c <= '9'; } diff --git a/lab6/c/src/libc/stdio.c b/lab6/c/src/libc/stdio.c new file mode 100644 index 000000000..766cbb465 --- /dev/null +++ b/lab6/c/src/libc/stdio.c @@ -0,0 +1,41 @@ +#include "oscos/libc/stdio.h" + +#include "oscos/utils/fmt.h" + +int snprintf(char str[const restrict], const size_t size, + const char *const restrict format, ...) { + va_list ap; + va_start(ap, format); + + const int result = vsnprintf(str, size, format, ap); + + va_end(ap); + return result; +} + +typedef struct { + char *str; + size_t size, len; +} snprintf_arg_t; + +static void _snprintf_putc(const unsigned char c, snprintf_arg_t *const arg) { + if (arg->size > 0 && arg->len < arg->size - 1) { + arg->str[arg->len++] = c; + } +} + +static void _snprintf_finalize(snprintf_arg_t *const arg) { + if (arg->size > 0) { + arg->str[arg->len] = '\0'; + } +} + +static const printf_vtable_t _snprintf_vtable = { + .putc = (void (*)(unsigned char, void *))_snprintf_putc, + .finalize = (void (*)(void *))_snprintf_finalize}; + +int vsnprintf(char str[const restrict], const size_t size, + const char *const restrict format, va_list ap) { + snprintf_arg_t arg = {.str = str, .size = size, .len = 0}; + return vprintf_generic(&_snprintf_vtable, &arg, format, ap); +} diff --git a/lab6/c/src/libc/stdlib/qsort.c b/lab6/c/src/libc/stdlib/qsort.c new file mode 100644 index 000000000..200755aa2 --- /dev/null +++ b/lab6/c/src/libc/stdlib/qsort.c @@ -0,0 +1,154 @@ +#include "oscos/libc/stdlib.h" + +#include "oscos/libc/string.h" + +#define INSERTION_SORT_THRESHOLD 16 + +// Insertion sort. + +/// \brief Insertion sort. +static void +_insertion_sort(void *const base, const size_t nmemb, const size_t size, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + for (size_t i = 1; i < nmemb; i++) { + for (size_t j = i; j > 0; j--) { + void *const pl = (char *)base + (j - 1) * size, *const pr = + (char *)pl + size; + if (compar(pl, pr, arg) < 0) + break; + memswp(pl, pr, size); + } + } +} + +// Quicksort. + +/// \brief The result of partitioning, indicating the pivot points. +typedef struct { + size_t eq_start; ///< The starting index of the part where the element + ///< compares equal to the pivot. + size_t gt_start; ///< The starting index of the part where the element + ///< compares greater than the pivot. +} partition_result_t; + +/// \brief Median-of-3 pivot selection. +/// +/// \return The index of the chosen pivot. +static size_t +_select_pivot(const void *const base, const size_t nmemb, const size_t size, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + if (nmemb == 0) + __builtin_unreachable(); + if (nmemb < 3) + return 0; + + const size_t imid = nmemb / 2, ilast = nmemb - 1; + const void *const mid = (char *)base + imid * size, *const last = + (char *)base + + ilast * size; + return compar(base, mid, arg) <= 0 ? compar(mid, last, arg) <= 0 ? imid + : compar(base, last, arg) <= 0 ? ilast + : 0 + : compar(mid, last, arg) > 0 ? imid + : compar(base, last, arg) <= 0 ? 0 + : ilast; +} + +/// \brief Three-way partitioning, using the first element as the pivot. +static partition_result_t +_partition(void *const base, const size_t nmemb, const size_t size, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + if (nmemb == 0) + __builtin_unreachable(); + + // Partition every element except for the first one (pivot). + + size_t il = 1, im = 1, ir = nmemb; + while (im < ir) { + void *const pl = (char *)base + il * size, + *const pm = (char *)base + im * size, + *const prm1 = (char *)base + (ir - 1) * size; + + const int compar_result = compar(pm, base, arg); + if (compar_result < 0) { + memswp(pl, pm, size); + il++; + im++; + } else if (compar_result > 0) { + memswp(pm, prm1, size); + ir--; + } else { + im++; + } + } + + // Move the pivot to its place. + + if (il != 0) { + memswp(base, (char *)base + --il * size, size); + } + + return (partition_result_t){.eq_start = il, .gt_start = im}; +} + +/// \brief Quicksort. +/// +/// A basic quicksort with median-of-3 pivot selection, three-way partitioning, +/// and insertion sort for small arrays. +static void _quicksort(void *const base, const size_t nmemb, const size_t size, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + if (nmemb <= INSERTION_SORT_THRESHOLD) { + _insertion_sort(base, nmemb, size, compar, arg); + return; + } + + // Select the pivot. + + const size_t pivot_ix = _select_pivot(base, nmemb, size, compar, arg); + + // Partition the array. + + if (pivot_ix != 0) { + memswp(base, (char *)base + pivot_ix * size, size); + } + + const partition_result_t partition_result = + _partition(base, nmemb, size, compar, arg); + + // Recursively sort the subarrays. + + _quicksort(base, partition_result.eq_start, size, compar, arg); + _quicksort((char *)base + partition_result.gt_start * size, + nmemb - partition_result.gt_start, size, compar, arg); +} + +// qsort. + +typedef struct { + int (*compar)(const void *, const void *); +} qsort_qsort_r_arg_t; + +static int _qsort_qsort_r_compar(const void *const x, const void *const y, + qsort_qsort_r_arg_t *const arg) { + return arg->compar(x, y); +} + +void qsort(void *const base, const size_t nmemb, const size_t size, + int (*const compar)(const void *, const void *)) { + qsort_qsort_r_arg_t arg = {.compar = compar}; + qsort_r(base, nmemb, size, + (int (*)(const void *, const void *, void *))_qsort_qsort_r_compar, + &arg); +} + +// qsort_r. + +void qsort_r(void *const base, const size_t nmemb, const size_t size, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + _quicksort(base, nmemb, size, compar, arg); +} diff --git a/lab6/c/src/libc/string.c b/lab6/c/src/libc/string.c new file mode 100644 index 000000000..b1f406f82 --- /dev/null +++ b/lab6/c/src/libc/string.c @@ -0,0 +1,99 @@ +#include "oscos/libc/string.h" + +__attribute__((used)) int memcmp(const void *const s1, const void *const s2, + const size_t n) { + const unsigned char *const s1_c = s1, *const s2_c = s2; + + for (size_t i = 0; i < n; i++) { + const int diff = (int)s1_c[i] - s2_c[i]; + if (diff != 0) + return diff; + } + + return 0; +} + +__attribute__((used)) void *memset(void *const s, const int c, const size_t n) { + unsigned char *const s_c = s; + + for (size_t i = 0; i < n; i++) { + s_c[i] = c; + } + + return s; +} + +static void __memmove_forward(unsigned char *const dest, + const unsigned char *const src, const size_t n) { + for (size_t i = 0; i < n; i++) { + dest[i] = src[i]; + } +} + +static void __memmove_backward(unsigned char *const dest, + const unsigned char *const src, const size_t n) { + for (size_t ip1 = n; ip1 > 0; ip1--) { + const size_t i = ip1 - 1; + dest[i] = src[i]; + } +} + +__attribute__((used)) void *memcpy(void *const restrict dest, + const void *const restrict src, + const size_t n) { + __memmove_forward(dest, src, n); + return dest; +} + +__attribute__((used)) void *memmove(void *const dest, const void *const src, + const size_t n) { + if (dest < src) { + __memmove_forward(dest, src, n); + } else if (dest > src) { + __memmove_backward(dest, src, n); + } + + return dest; +} + +int strcmp(const char *const s1, const char *const s2) { + for (const unsigned char *c1 = (const unsigned char *)s1, + *c2 = (const unsigned char *)s2; + *c1 || *c2; c1++, c2++) { + const int diff = (int)*c1 - *c2; + if (diff != 0) + return diff; + } + + return 0; +} + +int strncmp(const char *const s1, const char *const s2, const size_t n) { + size_t i = 0; + const unsigned char *c1 = (const unsigned char *)s1, + *c2 = (const unsigned char *)s2; + for (; i < n && (*c1 || *c2); i++, c1++, c2++) { + const int diff = (int)*c1 - *c2; + if (diff != 0) + return diff; + } + + return 0; +} + +size_t strlen(const char *s) { + size_t result = 0; + for (const char *c = s; *c; c++) { + result++; + } + return result; +} + +void memswp(void *const restrict xs, void *const restrict ys, const size_t n) { + unsigned char *const restrict xs_c = xs, *const restrict ys_c = ys; + for (size_t i = 0; i < n; i++) { + const unsigned char tmp = xs_c[i]; + xs_c[i] = ys_c[i]; + ys_c[i] = tmp; + } +} diff --git a/lab6/c/src/linker.ld b/lab6/c/src/linker.ld new file mode 100644 index 000000000..4c6bab1d0 --- /dev/null +++ b/lab6/c/src/linker.ld @@ -0,0 +1,77 @@ +/* +Memory map: (End addresses are exclusive) +0x 0 ( 0B) - 0x 1000 ( 4K): Reserved by firmware +0x 1000 ( 4K) - 0x 80000 (512K): Kernel heap +0x 80000 (512K) - 0x3ac00000 (940M): Kernel text, rodata, data, bss, heap +0x3ac00000 (940M) - 0x3b400000 (948M): Kernel stack +0x3b400000 (948M) - 0x3c000000 (960M): (QEMU only) Kernel heap +*/ + +_skernel = 0x80000; +_max_ekernel = 0x3ac00000; + +MEMORY +{ + RAM_KERNEL : ORIGIN = _skernel, LENGTH = _max_ekernel - _skernel +} + +/* Must be 16-byte aligned. + The highest address the ARM core can use. Total system SDRAM is 1G. Top 76M + (or 64M if on QEMU) are reserved for VideoCore. */ +_estack = 1024M - 76M; +_sstack = _estack - 8M; + +ENTRY(_start) + +SECTIONS +{ + .text : + { + _stext = .; + + *(.text._start) /* Entry point. See `start.S`. */ + *(.text.unlikely .text.*_unlikely .text.unlikely.*) + *(.text.startup .text.startup.*) + *(.text.hot .text.hot.*) + *(.text .text.*) + *(.eh_frame) + *(.eh_frame_hdr) + + _etext = .; + } >RAM_KERNEL + + .rodata : + { + _srodata = .; + + *(.rodata .rodata.*) + + _erodata = .; + } >RAM_KERNEL + + .data : + { + _sdata = .; + + *(.data .data.*) + + _edata = .; + } >RAM_KERNEL + + /* The .bss section is 16-byte aligned to allow the section to be + zero-initialized with the `stp` instruction without using unaligned + memory accesses. See `start.S`. */ + .bss : ALIGN(16) + { + _sbss = .; + + *(.bss .bss.*) + *(COMMON) + + . = ALIGN(16); + _ebss = .; + } >RAM_KERNEL + + _ekernel = .; + _sheap = ALIGN(16); +} diff --git a/lab6/c/src/main.c b/lab6/c/src/main.c new file mode 100644 index 000000000..0feae407a --- /dev/null +++ b/lab6/c/src/main.c @@ -0,0 +1,69 @@ +#include "oscos/console.h" +#include "oscos/devicetree.h" +#include "oscos/drivers/aux.h" +#include "oscos/drivers/gpio.h" +#include "oscos/drivers/l1ic.h" +#include "oscos/drivers/l2ic.h" +#include "oscos/drivers/mailbox.h" +#include "oscos/drivers/pm.h" +#include "oscos/initrd.h" +#include "oscos/mem/malloc.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/mem/startup-alloc.h" +#include "oscos/panic.h" +#include "oscos/sched.h" +#include "oscos/shell.h" +#include "oscos/timer/delay.h" +#include "oscos/timer/timeout.h" +#include "oscos/xcpt.h" + +static void _run_shell(void *const _arg) { + (void)_arg; + run_shell(); +} + +void main(const void *const dtb_start) { + // Initialize interrupt-related subsystems. + l1ic_init(); + l2ic_init(); + xcpt_set_vector_table(); + timeout_init(); + XCPT_UNMASK_ALL(); + + // Initialize the serial console. + gpio_init(); + aux_init(); + startup_alloc_init(); + console_init(); + + // Initialize the devicetree. + if (!devicetree_init(dtb_start)) { + console_puts("WARN: Devicetree blob is invalid."); + } + + // Initialize the initial ramdisk. + if (!initrd_init()) { + console_puts("WARN: Initial ramdisk is invalid."); + } + + // Initialize the memory allocators. + page_alloc_init(); + malloc_init(); + + // Initialize miscellaneous subsystems. + mailbox_init(); + pm_init(); + + // Initialize the scheduler. + + if (!sched_init()) { + PANIC("Cannot initialize scheduler: out of memory"); + } + + // Test the scheduler. + + thread_create(_run_shell, NULL); + + sched_setup_periodic_scheduling(); + idle(); +} diff --git a/lab6/c/src/mem/malloc.c b/lab6/c/src/mem/malloc.c new file mode 100644 index 000000000..5819828d0 --- /dev/null +++ b/lab6/c/src/mem/malloc.c @@ -0,0 +1,353 @@ +// The design of the dynamic memory allocator resembles that of the slab +// allocator to some extent. However, compared to the slab allocator +// implementation in the Linux kernel [linux-slab-alloc], it only achieves the +// first of the three principle aims, namely, to help eliminate internal +// fragmentation. +// +// Each slab is backed by a single page. The first 32 bytes of the page are +// reserved for bookkeeping data, while the remaining area is split into +// equally-sized chunks that are units of allocation. There are different kinds +// of slabs for many different slot sizes. This allocator maintains free lists +// of slabs, one for each kind, chaining slabs of the same kind and with at +// least one available slot together. +// +// When the last reserved slot of a slab becomes available, the slab is +// immediately destroyed and the underlying page is returned to the page frame +// allocator. This design causes thrashing on certain allocation/deallocation +// patterns, but it keeps the code simple. +// +// Large allocation requests bypass the slab allocator and goes directly to the +// page frame allocator. The allocated memory is appropriately tagged so that +// void free(void *ptr) knows which memory allocator a memory is allocated with. +// An allocation request is considered large if the size is greater than 126 +// `max_align_t`, the maximum slot size that allows a slab to hold at least two +// slots. Indeed, if the request size is so large that a slab able to satisfy +// the request can only hold a single slot, then using the slab allocator offers +// no advantage at all. If the request size is even larger, then a slab with a +// large enough slot size will not be able to hold even a single slot. +// +// [linux-slab-alloc]: +// https://www.kernel.org/doc/gorman/html/understand/understand011.html + +#include "oscos/mem/malloc.h" + +#include +#include + +#include "oscos/libc/string.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/mem/vm.h" +#include "oscos/utils/critical-section.h" +#include "oscos/utils/math.h" + +/// \brief A node of a doubly-linked list. +typedef struct list_node_t { + struct list_node_t *prev, *next; +} list_node_t; + +/// \brief Slab metadata. +typedef struct { + /// \brief The number of slots. + uint8_t n_slots; + /// \brief The size of each slot in numbers of `max_align_t`. + uint8_t slot_size; +} slab_metadata_t; + +/// \brief Slab (or not). +typedef struct { + /// \brief Node of the free list. + /// + /// If `free_list_node.prev` is NULL, then this "slab" is in fact not a slab + /// but a memory allocated for a large allocation request. + /// + /// This field is put in the first position, so that obtaining a slab_t * from + /// a pointer to its `free_list_node` field is a no-op. + list_node_t free_list_node; + /// \brief Pointer to the head of the free list of the slabs of the same kind. + /// + /// This is used when the slab adds itself to the free list. + list_node_t *free_list_head; + /// \brief Metadata. + slab_metadata_t metadata; + /// \brief The number of reserved slots. + uint8_t n_slots_reserved; + /// \brief Bitset of reserved slots. + /// + /// The jth bit of `slots_reserved_bitset[i]` if set iff the i*64 + j slot is + /// reserved. + uint64_t slots_reserved_bitset[4]; + /// \brief The memory for the slots. + alignas(alignof(max_align_t)) unsigned char slots[]; +} slab_t; + +/// \brief Metadata of all supported types of slabs. +static const slab_metadata_t SLAB_METADATA[] = { + {.n_slots = 252, .slot_size = 1}, {.n_slots = 126, .slot_size = 2}, + {.n_slots = 84, .slot_size = 3}, {.n_slots = 63, .slot_size = 4}, + {.n_slots = 50, .slot_size = 5}, {.n_slots = 42, .slot_size = 6}, + {.n_slots = 36, .slot_size = 7}, {.n_slots = 31, .slot_size = 8}, + {.n_slots = 28, .slot_size = 9}, {.n_slots = 25, .slot_size = 10}, + {.n_slots = 22, .slot_size = 11}, {.n_slots = 21, .slot_size = 12}, + {.n_slots = 19, .slot_size = 13}, {.n_slots = 18, .slot_size = 14}, + {.n_slots = 16, .slot_size = 15}, {.n_slots = 15, .slot_size = 16}, + {.n_slots = 14, .slot_size = 18}, {.n_slots = 13, .slot_size = 19}, + {.n_slots = 12, .slot_size = 21}, {.n_slots = 11, .slot_size = 22}, + {.n_slots = 10, .slot_size = 25}, {.n_slots = 9, .slot_size = 28}, + {.n_slots = 8, .slot_size = 31}, {.n_slots = 7, .slot_size = 36}, + {.n_slots = 6, .slot_size = 42}, {.n_slots = 5, .slot_size = 50}, + {.n_slots = 4, .slot_size = 63}, {.n_slots = 3, .slot_size = 84}, + {.n_slots = 2, .slot_size = 126}}; + +/// \brief The number of slab types. +#define N_SLAB_TYPES (sizeof(SLAB_METADATA) / sizeof(slab_metadata_t)) + +/// \brief The threshold in numbers of `max_align_t` an allocation request whose +/// size is more than which is considered a large allocation request. +#define LARGE_ALLOC_THRESHOLD 126 + +/// \brief Free lists of slabs for each slab type. +static list_node_t _slab_free_lists[N_SLAB_TYPES]; + +/// \brief Gets the slab type ID (the index that can be used to index +/// `SLAB_METADATA` or the free list) from the size of the allocation +/// request. +/// +/// \param n_units The size of the allocation request in numbers of "allocation +/// units", i.e., `max_align_t`. +static size_t _get_slab_type_id(const size_t n_units) { + if (n_units == 0 || n_units > LARGE_ALLOC_THRESHOLD) + __builtin_unreachable(); + + return n_units <= 16 ? n_units - 1 : N_SLAB_TYPES + 1 - 252 / n_units; +} + +// Slab operations. + +/// \brief Adds a slab to its free list. +/// +/// The `free_list_head` field of \p slab must be initialized and \p slab must +/// not have been on any free list. +/// +/// This function is safe to call only within a critical section. +static void _add_slab_to_free_list(slab_t *const slab) { + list_node_t *const free_list_first_entry = slab->free_list_head->next; + slab->free_list_node.next = free_list_first_entry; + free_list_first_entry->prev = &slab->free_list_node; + slab->free_list_node.prev = slab->free_list_head; + slab->free_list_head->next = &slab->free_list_node; +} + +/// \brief Removes a slab from its free list. +/// +/// \p slab must have been on a free list. +/// +/// This function is safe to call only within a critical section. +static void _remove_slab_from_free_list(slab_t *const slab) { + slab->free_list_node.prev->next = slab->free_list_node.next; + slab->free_list_node.next->prev = slab->free_list_node.prev; +} + +/// \brief Allocates a new slab and adds it onto its free list. +/// +/// This function is safe to call only within a critical section. +static slab_t *_alloc_slab(const size_t slab_type_id) { + // Allocate space for the slab. + + const spage_id_t page = alloc_pages_unlocked(0); + if (page < 0) + return NULL; + + slab_t *slab = (slab_t *)pa_to_kernel_va(page_id_to_pa(page)); + if (!slab) { + // We cannot accept a null slab pointer in this implementation due to the + // `free_list_node.prev` being used as a tag to identify memories for large + // allocation requests. Allocate a new page and return the previously + // allocated page to the page frame allocator. + // (In practice, this code path is never taken.) + + const spage_id_t another_page = alloc_pages_unlocked(0); + free_pages(page); + if (another_page < 0) { + return NULL; + } + + slab = (slab_t *)pa_to_kernel_va(page_id_to_pa(another_page)); + } + + // Initialize the fields. + + slab->free_list_head = &_slab_free_lists[slab_type_id]; + slab->metadata = SLAB_METADATA[slab_type_id]; + slab->n_slots_reserved = 0; + memset(slab->slots_reserved_bitset, 0, sizeof(slab->slots_reserved_bitset)); + _add_slab_to_free_list(slab); + + return slab; +} + +/// \brief Gets a slab of the given type with at least one free slot. If there +/// is none, allocates a new one. +static slab_t *_get_or_alloc_slab(const size_t slab_type_id) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + slab_t *result; + if (_slab_free_lists[slab_type_id].next == + &_slab_free_lists[slab_type_id]) { // The free list is empty. + result = _alloc_slab(slab_type_id); + } else { + list_node_t *const free_list_first_entry = + _slab_free_lists[slab_type_id].next; + result = (slab_t *)((char *)free_list_first_entry - + offsetof(slab_t, free_list_node)); + } + + CRITICAL_SECTION_LEAVE(daif_val); + return result; +} + +/// \brief Gets the index of the first free slot of the given slab. +/// +/// \p slab must have at least one free slot. +static size_t _get_first_free_slot_ix(const slab_t *const slab) { + for (size_t i = 0;; i++) { + uint64_t reversed_negated_bitset; + __asm__("rbit %0, %1" + : "=r"(reversed_negated_bitset) + : "r"(~slab->slots_reserved_bitset[i])); + + uint64_t j; + __asm__("clz %0, %1" : "=r"(j) : "r"(reversed_negated_bitset)); + + if (j != 64) { // The jth bit of `slab->slots_reserved_bitset[i]` is clear. + return i * 64 + j; + } + } +} + +/// \brief Allocates a slot from the given slab. +/// +/// \p slab must have at least one free slot. +static void *_alloc_from_slab(slab_t *const slab) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const size_t free_slot_ix = _get_first_free_slot_ix(slab); + + // Mark the `free_slot_ix`th slot as reserved. + + slab->n_slots_reserved++; + slab->slots_reserved_bitset[free_slot_ix / 64] |= (uint64_t)1 + << (free_slot_ix % 64); + + // Remove itself from its free list if there are no free slots. + + if (slab->n_slots_reserved == slab->metadata.n_slots) { + _remove_slab_from_free_list(slab); + } + + CRITICAL_SECTION_LEAVE(daif_val); + return slab->slots + free_slot_ix * (slab->metadata.slot_size * 16); +} + +/// \brief Frees a slot to the given slab. +/// +/// \param slab The slab. +/// \param ptr The pointer to the slot. +static void _free_to_slab(slab_t *const slab, void *const ptr) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const size_t slot_ix = ((uintptr_t)ptr - (uintptr_t)slab->slots) / + (slab->metadata.slot_size * 16); + + // Adds the slab to its free list if it wasn't on its free list. + + if (slab->n_slots_reserved == slab->metadata.n_slots) { + _add_slab_to_free_list(slab); + } + + // Mark the slot as available. + + slab->n_slots_reserved--; + slab->slots_reserved_bitset[slot_ix / 64] &= ~(1 << (slot_ix % 64)); + + // Return the slab to the page frame allocator if it has no reserved slots. + + if (slab->n_slots_reserved == 0) { + _remove_slab_from_free_list(slab); + free_pages(pa_to_page_id(kernel_va_to_pa(slab))); + } + + CRITICAL_SECTION_LEAVE(daif_val); +} + +// Large allocation. + +/// \brief Allocates memory using the large allocation mechanism. +/// +/// In principle, using this function to satisfy small allocation requests will +/// not cause problems (UB or kernel panic), but doing so wastes a lot of +/// memory. +/// +/// \param size The size of the allocation in bytes. +static void *_malloc_large(const size_t size) { + const size_t actual_size = alignof(max_align_t) + size; + const size_t n_pages = (actual_size + ((1 << PAGE_ORDER) - 1)) >> PAGE_ORDER; + const size_t block_order = clog2(n_pages); + + spage_id_t page = alloc_pages(block_order); + if (page < 0) + return NULL; + + char *page_va = pa_to_kernel_va(page_id_to_pa(page)); + // Mark the "slab" as not a slab. + ((slab_t *)page_va)->free_list_node.prev = NULL; + + return page_va + alignof(max_align_t); +} + +/// \brief Frees memory allocated using void *_malloc_large(size_t). +/// \param slab_ptr The pointer to the "slab". (N.B. not the pointer returned by +/// void *_malloc_large(size_t)!) +static void _free_large(slab_t *const slab_ptr) { + free_pages(pa_to_page_id(kernel_va_to_pa(slab_ptr))); +} + +// Public functions. + +void malloc_init(void) { + // Initialize the free lists. (All free lists are initially empty.) + + for (size_t i = 0; i < N_SLAB_TYPES; i++) { + _slab_free_lists[i].prev = _slab_free_lists[i].next = &_slab_free_lists[i]; + } +} + +void *malloc(const size_t size) { + if (size == 0) + return NULL; + + const size_t n_units = (size + (alignof(max_align_t) - 1)) >> 4; + if (n_units > LARGE_ALLOC_THRESHOLD) + return _malloc_large(size); + + const size_t slab_type_id = _get_slab_type_id(n_units); + slab_t *const slab = _get_or_alloc_slab(slab_type_id); + if (!slab) + return NULL; + + return _alloc_from_slab(slab); +} + +void free(void *const ptr) { + if (!ptr) + return; + + slab_t *const ptr_s = (slab_t *)((uintptr_t)ptr & ~((1 << PAGE_ORDER) - 1)); + if (!ptr_s->free_list_node.prev) { // Not a slab. + _free_large(ptr_s); + } else { + _free_to_slab(ptr_s, ptr); + } +} diff --git a/lab6/c/src/mem/page-alloc.c b/lab6/c/src/mem/page-alloc.c new file mode 100644 index 000000000..cd510492e --- /dev/null +++ b/lab6/c/src/mem/page-alloc.c @@ -0,0 +1,662 @@ +// The page frame allocator uses the buddy system. Much of the design is based +// on that described in The Art of Computer Programming by Donald Knuth, +// section 2.5. +// +// The page frame allocator maintains the page frame array, an array of size +// equal to the number of page frames and entry type `page_frame_array_entry_t`, +// which tracks each block's reservation status and order. Unlike the design +// described in the specification [spec], not all entries have valid data. If +// page i starts a block of order k, regardless of the reservation status of the +// block, the entries in the index range [i+1:i+2^k] are not read and are thus +// left uninitialized. Unlike TAOCP's design, the order of a block is stored in +// the page frame array even if the block is reserved. (Note: TAOCP's +// reservation algorithm skips storing the order of the allocated block.) This +// design decision is because, unlike TAOCP's liberation algorithm, the +// void free_pages(page_id_t) function does not have access to the order of the +// block from the arguments and must instead retrieve this information from the +// page frame array. +// +// The page frame allocator also maintains free lists of blocks, one for each +// order. Each free list is a doubly-linked list with page frame array entries +// themselves as nodes. Thus, in addition to the reservation status and the +// order of the corresponding block, each page frame array entry also contains +// the index into the page frame array of the previous and the next node on the +// free list. This implementation adopts the technique described in TAOCP +// section 2.2.5 of using the list node type itself to store the head and tail +// pointers of the list(from now on referred to as "free list header"), +// simplifying list manipulation code. TAOCP's allocator design also uses this +// technique. Since the free list headers are necessarily outside of the page +// frame array, this technique requires that the indices mentioned above be able +// to refer to page frame array entries (list nodes) outside the page frame +// array. To solve this problem without adding complexity to the code, the free +// list headers and the page frame array are allocated together, with the former +// placed right before the latter, and we use negative array indices to refer to +// entries in the free list headers. +// +// [spec]: https://oscapstone.github.io/labs/lab4.html + +#include "oscos/mem/page-alloc.h" + +#include "oscos/console.h" +#include "oscos/devicetree.h" +#include "oscos/initrd.h" +#include "oscos/mem/startup-alloc.h" +#include "oscos/mem/vm.h" +#include "oscos/panic.h" +#include "oscos/utils/critical-section.h" + +// `MAX_BLOCK_ORDER` can be changed to any positive integer up to and +// including 25 without modification to the code. + +// Symbols defined in the linker script. +extern char _skernel[], _ekernel[], _sstack[], _estack[]; + +typedef struct { + bool is_avail : 1; + unsigned order : 5; + signed linkf : 26; + signed linkb : 32; +} page_frame_array_entry_t; + +static pa_t _pa_start; +static page_frame_array_entry_t *_page_frame_array, *_free_list; + +// Utilities used by page_alloc_init. + +// _mark_region + +/// \brief Marks a region as either reserved or available. +/// +/// \param region_limit The region limit. Only the part of \p region that lies +/// within this limit will be marked. +/// \param region The region to mark. +/// \param is_avail The target reservation status. +static void _mark_region(const pa_range_t region_limit, const pa_range_t region, + const bool is_avail) { + const pa_t effective_start = region_limit.start > region.start + ? region_limit.start + : region.start, + effective_end = + region_limit.end < region.end ? region_limit.end : region.end; + + if (effective_start < effective_end) { + mark_pages_unlocked(pa_range_to_page_id_range((pa_range_t){ + .start = effective_start, .end = effective_end}), + is_avail); + } +} + +// _get_usable_pa_range + +typedef struct { + fdt_n_address_size_cells_t root_n_cells; + pa_range_t range; +} get_usable_pa_range_fdt_traverse_arg_t; + +static control_flow_t _get_usable_pa_range_fdt_traverse_callback( + get_usable_pa_range_fdt_traverse_arg_t *const arg, + const fdt_item_t *const node, + const fdt_traverse_parent_list_node_t *const parent) { + if (!parent) { // Current node is the root node. + arg->root_n_cells = fdt_get_n_address_size_cells(node); + } else if (parent && !parent->parent && + strncmp(FDT_NODE_NAME(node), "memory@", 7) == + 0) { // Current node is /memory@... + FDT_FOR_ITEM(node, item) { + if (FDT_TOKEN(item) == FDT_PROP) { + const fdt_prop_t *const prop = (const fdt_prop_t *)item->payload; + if (strcmp(FDT_PROP_NAME(prop), "reg") == 0) { + const fdt_read_reg_result_t read_result = + fdt_read_reg(prop, arg->root_n_cells); + const pa_t start = read_result.value.address, + end = start + read_result.value.size; + if (read_result.address_overflow || read_result.size_overflow || + read_result.value.address > PA_MAX || + read_result.value.size > PA_MAX || end < start) + PANIC( + "page-alloc: reg property value overflow in devicetree node %s", + FDT_NODE_NAME(node)); + + if (start < arg->range.start) { + arg->range.start = start; + } + if (end > arg->range.end) { + arg->range.end = end; + } + } + } + } + } + + return CF_CONTINUE; +} + +/// \brief Gets the physical address range containing all usable memory regions. +static pa_range_t _get_usable_pa_range(void) { + if (devicetree_is_init()) { + get_usable_pa_range_fdt_traverse_arg_t arg = { + .range = {.start = PA_MAX, .end = 0}}; + fdt_traverse( + (fdt_traverse_callback_t *)_get_usable_pa_range_fdt_traverse_callback, + &arg); + return arg.range; + } else { + return (pa_range_t){.start = 0x0, .end = 0x3b400000}; + } +} + +// _mark_usable_regions + +typedef struct { + pa_range_t usable_pa_range; + fdt_n_address_size_cells_t root_n_cells; +} mark_usable_regions_fdt_traverse_arg_t; + +static control_flow_t _mark_usable_regions_fdt_traverse_callback( + mark_usable_regions_fdt_traverse_arg_t *const arg, + const fdt_item_t *const node, + const fdt_traverse_parent_list_node_t *const parent) { + if (!parent) { // Current node is the root node. + arg->root_n_cells = fdt_get_n_address_size_cells(node); + } else if (parent && !parent->parent && + strncmp(FDT_NODE_NAME(node), "memory@", 7) == + 0) { // Current node is /memory@... + FDT_FOR_ITEM(node, item) { + if (FDT_TOKEN(item) == FDT_PROP) { + const fdt_prop_t *const prop = (const fdt_prop_t *)item->payload; + if (strcmp(FDT_PROP_NAME(prop), "reg") == 0) { + const fdt_read_reg_result_t read_result = + fdt_read_reg(prop, arg->root_n_cells); + const pa_t start = read_result.value.address, + end = start + read_result.value.size; + if (read_result.address_overflow || read_result.size_overflow || + read_result.value.address > PA_MAX || + read_result.value.size > PA_MAX || end < start) + PANIC( + "page-alloc: reg property value overflow in devicetree node %s", + FDT_NODE_NAME(node)); + + _mark_region(arg->usable_pa_range, + (pa_range_t){.start = start, .end = end}, true); + } + } + } + } + + return CF_CONTINUE; +} + +/// \brief Marks the usable memory regions as available. +/// +/// \param usable_pa_range The physical address range containing all usable +/// memory regions. Can be obtained by +/// pa_range_t _get_usable_pa_range(void). +static void _mark_usable_regions(const pa_range_t usable_pa_range) { + if (devicetree_is_init()) { + mark_usable_regions_fdt_traverse_arg_t arg = {.usable_pa_range = + usable_pa_range}; + fdt_traverse( + (fdt_traverse_callback_t *)_mark_usable_regions_fdt_traverse_callback, + &arg); + } else { + _mark_region(usable_pa_range, (pa_range_t){.start = 0x0, .end = 0x3b400000}, + true); + } +} + +// _mark_reserved_regions + +typedef struct { + pa_range_t usable_pa_range; + const fdt_item_t *reserved_memory_node; + fdt_n_address_size_cells_t reserved_memory_n_cells; +} mark_reserved_regions_fdt_traverse_arg_t; + +static control_flow_t _mark_reserved_regions_fdt_traverse_callback( + mark_reserved_regions_fdt_traverse_arg_t *const arg, + const fdt_item_t *const node, + const fdt_traverse_parent_list_node_t *const parent) { + if (parent && !parent->parent && + strcmp(FDT_NODE_NAME(node), "reserved-memory") == + 0) { // Current node is /reserved-memory. + arg->reserved_memory_node = node; + arg->reserved_memory_n_cells = fdt_get_n_address_size_cells(node); + } else if (arg->reserved_memory_node) { // The /reserved-memory node has been + // traversed. + if (parent->node == + arg->reserved_memory_node) { // Current node is /reserved-memory/... + FDT_FOR_ITEM(node, item) { + if (FDT_TOKEN(item) == FDT_PROP) { + const fdt_prop_t *const prop = (const fdt_prop_t *)item->payload; + if (strcmp(FDT_PROP_NAME(prop), "reg") == 0) { + const fdt_read_reg_result_t read_result = + fdt_read_reg(prop, arg->reserved_memory_n_cells); + const pa_t start = read_result.value.address, + end = start + read_result.value.size; + if (read_result.address_overflow || read_result.size_overflow || + read_result.value.address > PA_MAX || + read_result.value.size > PA_MAX || end < start) + PANIC("page-alloc: reg property value overflow in devicetree " + "node %s", + FDT_NODE_NAME(node)); + + _mark_region(arg->usable_pa_range, + (pa_range_t){.start = start, .end = end}, false); + + break; + } + } + } + } else { // All children of the /reserved-memory node has been traversed. + return CF_BREAK; + } + } + + return CF_CONTINUE; +} + +/// \brief Marks the reserved memory regions as reserved. +/// +/// \param usable_pa_range The physical address range containing all usable +/// memory regions. Can be obtained by +/// pa_range_t _get_usable_pa_range(void). +static void _mark_reserved_regions(const pa_range_t usable_pa_range) { + if (devicetree_is_init()) { + // Devicetree. + + _mark_region(usable_pa_range, + (pa_range_t){.start = (pa_t)(uintptr_t)fdt_get_start(), + .end = (pa_t)(uintptr_t)fdt_get_end()}, + false); + + // Anything in the memory reservation block. + + for (const fdt_reserve_entry_t *reserve_entry = FDT_START_MEM_RSVMAP; + !(reserve_entry->address == 0 && reserve_entry->size == 0); + reserve_entry++) { + const pa_t start = rev_u64(reserve_entry->address), + end = start + rev_u64(reserve_entry->size); + _mark_region(usable_pa_range, (pa_range_t){.start = start, .end = end}, + false); + } + + // Spin tables for multicore boot, etc. + + mark_reserved_regions_fdt_traverse_arg_t arg = {.usable_pa_range = + usable_pa_range}; + fdt_traverse( + (fdt_traverse_callback_t *)_mark_reserved_regions_fdt_traverse_callback, + &arg); + } else { + // Spin tables for multicore boot. + + _mark_region(usable_pa_range, (pa_range_t){.start = 0x0, .end = 0x1000}, + false); + } + + // Kernel image in the physical memory. + + _mark_region(usable_pa_range, + (pa_range_t){.start = (pa_t)(uintptr_t)_skernel, + .end = (pa_t)(uintptr_t)_ekernel}, + false); + + // Initial ramdisk. + + _mark_region(usable_pa_range, + (pa_range_t){.start = (pa_t)(uintptr_t)initrd_get_start(), + .end = (pa_t)(uintptr_t)initrd_get_end()}, + false); + + // Kernel stack. + + _mark_region(usable_pa_range, + (pa_range_t){.start = (pa_t)(uintptr_t)_sstack, + .end = (pa_t)(uintptr_t)_estack}, + false); +} + +void page_alloc_init(void) { + // Determine the starting and ending physical address. + + pa_range_t usable_pa_range = _get_usable_pa_range(); + _pa_start = usable_pa_range.start; + + const pa_t max_supported_pa = + _pa_start + (1 << (PAGE_ORDER + MAX_BLOCK_ORDER)); + if (usable_pa_range.end > max_supported_pa) { + console_printf("WARN: page-alloc: End of usable memory region 0x%" PRIxPA + " is greater than maximum supported PA 0x%" PRIxPA ".\n", + usable_pa_range.end, max_supported_pa); + usable_pa_range.end = max_supported_pa; + } + + // Allocate the page frame array and the free list. + + page_frame_array_entry_t *const entries = + startup_alloc(((MAX_BLOCK_ORDER + 1) + (1 << MAX_BLOCK_ORDER)) * + sizeof(page_frame_array_entry_t)); + _page_frame_array = entries + (MAX_BLOCK_ORDER + 1); + _free_list = entries; + + // Initialize the page frame array. (The entire memory region is initially + // reserved.) + + _page_frame_array[0].is_avail = false; + _page_frame_array[0].order = MAX_BLOCK_ORDER; + + // Initialize the free list. (The free list is initially empty.) + + for (size_t order = 0; order <= MAX_BLOCK_ORDER; order++) { + _free_list[order].linkf = _free_list[order].linkb = + (int)order - (MAX_BLOCK_ORDER + 1); + } + + // Mark the usable regions as usable. + + _mark_usable_regions(usable_pa_range); + + // Mark the reserved regions as reserved. + + _mark_reserved_regions(usable_pa_range); + + // Mark the region used by the startup allocator as reserved. + + _mark_region(usable_pa_range, + kernel_va_range_to_pa_range(startup_alloc_get_alloc_range()), + false); +} + +/// \brief Adds a block to the free list. +/// +/// \param page The page number of the first page of the block. +static void _add_block_to_free_list(const page_id_t page) { + const size_t order = _page_frame_array[page].order; + + const int32_t free_list_first_entry = _free_list[order].linkf; + _page_frame_array[page].linkf = free_list_first_entry; + _page_frame_array[free_list_first_entry].linkb = page; + _page_frame_array[page].linkb = (int)order - (MAX_BLOCK_ORDER + 1); + _free_list[order].linkf = page; +} + +/// \brief Removes a block from the free list. +/// +/// \param page The page number of the first page of the block. +static void _remove_block_from_free_list(const page_id_t page) { + _page_frame_array[_page_frame_array[page].linkb].linkf = + _page_frame_array[page].linkf; + _page_frame_array[_page_frame_array[page].linkf].linkb = + _page_frame_array[page].linkb; +} + +/// \brief Removes all free blocks within a page range that is valid as the page +/// range of a block from the free list. +/// +/// \param range The page range. Must be valid as the page range of a block. +/// \param order The order of the block of which \p range is valid as the page +/// range. I.e., log base 2 of the span of the range. +static void +_remove_free_blocks_in_range_from_free_list(const page_id_range_t range, + const size_t order) { + if (_page_frame_array[range.start].order == order) { + if (_page_frame_array[range.start].is_avail) { + _remove_block_from_free_list(range.start); + } + } else { + if (order == 0) + __builtin_unreachable(); + + // This will not cause integer overflow, since the maximum order is at most + // 25 and the node ID is at most 2²⁵. + const page_id_t mid = (range.start + range.end) / 2; + + _remove_free_blocks_in_range_from_free_list( + (page_id_range_t){.start = range.start, .end = mid}, order - 1); + _remove_free_blocks_in_range_from_free_list( + (page_id_range_t){.start = mid, .end = range.end}, order - 1); + } +} + +/// \brief Splits a block. +/// +/// If the block is initially free, this function updates the free lists +/// accordingly. +/// +/// \param page The page number of the first page of the block. +static void _split_block(const page_id_t page) { + if (_page_frame_array[page].order == 0) + __builtin_unreachable(); + +#ifdef PAGE_ALLOC_ENABLE_LOG + const page_id_t end_page = page + (1 << _page_frame_array[page].order); + console_printf("DEBUG: page-alloc: Splitting block 0x%" PRIxPAGEID + " - 0x%" PRIxPAGEID " of order %u.\n", + page, end_page, _page_frame_array[page].order); +#endif + + if (_page_frame_array[page].is_avail) { + _remove_block_from_free_list(page); + } + + const size_t order_p1 = _page_frame_array[page].order - 1; + const page_id_t buddy_page = page + (1 << order_p1); + + _page_frame_array[page].order = order_p1; + _page_frame_array[buddy_page].is_avail = _page_frame_array[page].is_avail; + _page_frame_array[buddy_page].order = order_p1; + + if (_page_frame_array[page].is_avail) { + _add_block_to_free_list(page); + _add_block_to_free_list(buddy_page); + } +} + +spage_id_t alloc_pages(const size_t order) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const spage_id_t result = alloc_pages_unlocked(order); + + CRITICAL_SECTION_LEAVE(daif_val); + return result; +} + +spage_id_t alloc_pages_unlocked(const size_t order) { +#ifdef PAGE_ALLOC_ENABLE_LOG + console_printf("DEBUG: page-alloc: Allocating a block of order %zu\n", order); +#endif + + // Find block. + + size_t avail_block_order; + for (avail_block_order = order; avail_block_order <= MAX_BLOCK_ORDER; + avail_block_order++) { + if (_free_list[avail_block_order].linkf >= + 0) { // The free list is nonempty. + break; + } + } + + if (avail_block_order > + MAX_BLOCK_ORDER) { // No block of order >= `order` found. + return -1; + } + + const page_id_t page = _free_list[avail_block_order].linkf; + + // Remove from list. + + _remove_block_from_free_list(page); + + // Split. + + while (avail_block_order > order) { + // We could have used void _split_block(page_id_t) to perform block + // splitting, but the custom logic here avoids unnecessary free list + // operations (adding a block to the free list and then immediately removing + // it in the next loop iteration) that would have been performed by the said + // function. + +#ifdef PAGE_ALLOC_ENABLE_LOG + const page_id_t end_page = page + (1 << avail_block_order); + console_printf("DEBUG: page-alloc: Splitting block 0x%" PRIxPAGEID + " - 0x%" PRIxPAGEID " of order %zu.\n", + page, end_page, avail_block_order); +#endif + + avail_block_order--; + + const page_id_t buddy_page = page + (1 << avail_block_order); + + _page_frame_array[buddy_page].is_avail = true; + _page_frame_array[buddy_page].order = avail_block_order; + + // Add `buddy_page` to the free list, which is empty. + // We could have used void _add_block_to_free_list(page_id_t), but the + // custom logic here saves a few instructions. + _page_frame_array[buddy_page].linkf = _page_frame_array[buddy_page].linkb = + (int)avail_block_order - (MAX_BLOCK_ORDER + 1); + _free_list[avail_block_order].linkf = _free_list[avail_block_order].linkb = + buddy_page; + } + + _page_frame_array[page].is_avail = false; + _page_frame_array[page].order = order; + return page; +} + +void free_pages(const page_id_t page) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + free_pages_unlocked(page); + + CRITICAL_SECTION_LEAVE(daif_val); +} + +void free_pages_unlocked(const page_id_t page) { + const size_t order = _page_frame_array[page].order; + +#ifdef PAGE_ALLOC_ENABLE_LOG + const page_id_t end_page = page + (1 << order); + console_printf("DEBUG: page-alloc: Freeing block 0x%" PRIxPAGEID + " - 0x%" PRIxPAGEID " of order %zu\n", + page, end_page, order); +#endif + + // Combine with buddy. + + page_id_t curr_page = page; + size_t curr_order; + for (curr_order = order; curr_order < MAX_BLOCK_ORDER; curr_order++) { + const page_id_t buddy_page = curr_page ^ (1 << curr_order); + if (!(_page_frame_array[buddy_page].is_avail && + _page_frame_array[buddy_page].order == curr_order)) + break; + +#ifdef PAGE_ALLOC_ENABLE_LOG + const page_id_t end_curr_page = curr_page + (1 << curr_order); + const page_id_t end_buddy_page = buddy_page + (1 << curr_order); + console_printf( + "DEBUG: page-alloc: Combining block 0x%" PRIxPAGEID " - 0x%" PRIxPAGEID + " of order %zu with its buddy 0x%" PRIxPAGEID " - 0x%" PRIxPAGEID ".\n", + curr_page, end_curr_page, curr_order, buddy_page, end_buddy_page); +#endif + + _remove_block_from_free_list(buddy_page); + if (buddy_page < curr_page) { + curr_page = buddy_page; + } + } + + _page_frame_array[curr_page].is_avail = true; + _page_frame_array[curr_page].order = curr_order; + _add_block_to_free_list(curr_page); +} + +static void _mark_pages_rec(const page_id_range_t range, const bool is_avail, + const size_t order, + const page_id_range_t block_range) { + if (_page_frame_array[block_range.start].order == order && + _page_frame_array[block_range.start].is_avail == + is_avail) { // The entire block is already marked as the desired + // reservation status. + // No-op. + } else if (range.start == block_range.start && range.end == block_range.end) { + // Mark the entire block. + + _remove_free_blocks_in_range_from_free_list(range, order); + + _page_frame_array[range.start].is_avail = is_avail; + _page_frame_array[range.start].order = order; + if (is_avail) { + _add_block_to_free_list(range.start); + } + } else { // Recursive case. + if (order == 0) + __builtin_unreachable(); + + // Split the block if needed. + if (_page_frame_array[block_range.start].order == order) { + _split_block(block_range.start); + } + + // This will not cause integer overflow, since the maximum order is at most + // 25 and the node ID is at most 2²⁵. + const size_t mid = (block_range.start + block_range.end) / 2; + + if (range.end <= mid) { // The range lies entirely within the left half of + // the node range. + _mark_pages_rec( + range, is_avail, order - 1, + (page_id_range_t){.start = block_range.start, .end = mid}); + } else if (mid <= range.start) { // The range lies entirely within the right + // half of the node range. + _mark_pages_rec(range, is_avail, order - 1, + (page_id_range_t){.start = mid, .end = block_range.end}); + } else { // The range crosses the middle of the node range. + _mark_pages_rec( + (page_id_range_t){.start = range.start, .end = mid}, is_avail, + order - 1, (page_id_range_t){.start = block_range.start, .end = mid}); + _mark_pages_rec((page_id_range_t){.start = mid, .end = range.end}, + is_avail, order - 1, + (page_id_range_t){.start = mid, .end = block_range.end}); + } + } +} + +void mark_pages(const page_id_range_t range, const bool is_avail) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + mark_pages_unlocked(range, is_avail); + + CRITICAL_SECTION_LEAVE(daif_val); +} + +void mark_pages_unlocked(const page_id_range_t range, const bool is_avail) { +#ifdef PAGE_ALLOC_ENABLE_LOG + console_printf("DEBUG: page-alloc: Marking pages 0x%" PRIxPAGEID + " - 0x%" PRIxPAGEID " as %s.\n", + range.start, range.end, is_avail ? "available" : "reserved"); +#endif + + // TODO: Switch to a non-recursive implementation. + _mark_pages_rec(range, is_avail, MAX_BLOCK_ORDER, + (page_id_range_t){.start = 0, .end = 1 << MAX_BLOCK_ORDER}); +} + +pa_t page_id_to_pa(const page_id_t page) { + return _pa_start + (page << PAGE_ORDER); +} + +page_id_t pa_to_page_id(const pa_t pa) { + return (pa - _pa_start) >> PAGE_ORDER; +} + +page_id_range_t pa_range_to_page_id_range(const pa_range_t range) { + return (page_id_range_t){ + .start = (range.start - _pa_start) >> PAGE_ORDER, + .end = ((range.end - _pa_start) + ((1 << PAGE_ORDER) - 1)) >> PAGE_ORDER}; +} diff --git a/lab6/c/src/mem/startup-alloc.c b/lab6/c/src/mem/startup-alloc.c new file mode 100644 index 000000000..16ff447a6 --- /dev/null +++ b/lab6/c/src/mem/startup-alloc.c @@ -0,0 +1,24 @@ +#include "oscos/mem/startup-alloc.h" + +#include + +#include "oscos/utils/align.h" + +// Symbol defined in the linker script. +extern char _sheap[]; + +static char *_next = _sheap; + +void startup_alloc_init(void) { + // No-op. +} + +void *startup_alloc(const size_t size) { + char *const result = (char *)ALIGN((uintptr_t)_next, alignof(max_align_t)); + _next = result + size; + return result; +} + +va_range_t startup_alloc_get_alloc_range(void) { + return (va_range_t){.start = _sheap, .end = _next}; +} diff --git a/lab6/c/src/mem/types.c b/lab6/c/src/mem/types.c new file mode 100644 index 000000000..15924b938 --- /dev/null +++ b/lab6/c/src/mem/types.c @@ -0,0 +1,44 @@ +#include "oscos/mem/types.h" + +#include + +#include "oscos/mem/malloc.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/utils/critical-section.h" + +shared_page_t *shared_page_init(const page_id_t page_id) { + shared_page_t *const shared_page = malloc(sizeof(shared_page_t)); + if (!shared_page) + return NULL; + + shared_page->ref_count = 1; + shared_page->page_id = page_id; + + return shared_page; +} + +shared_page_t *shared_page_clone(shared_page_t *const shared_page) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + shared_page->page_id++; + + CRITICAL_SECTION_LEAVE(daif_val); + + return shared_page; +} + +void shared_page_drop(shared_page_t *const shared_page) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + shared_page->page_id--; + const bool drop_page = shared_page->page_id == 0; + + CRITICAL_SECTION_LEAVE(daif_val); + + if (drop_page) { + free_pages(shared_page->page_id); + free(shared_page); + } +} diff --git a/lab6/c/src/mem/vm.c b/lab6/c/src/mem/vm.c new file mode 100644 index 000000000..28ad21c07 --- /dev/null +++ b/lab6/c/src/mem/vm.c @@ -0,0 +1,12 @@ +#include "oscos/mem/vm.h" + +#include + +pa_t kernel_va_to_pa(const void *const va) { return (pa_t)(uintptr_t)va; } + +void *pa_to_kernel_va(const pa_t pa) { return (void *)(uintptr_t)pa; } + +pa_range_t kernel_va_range_to_pa_range(const va_range_t range) { + return (pa_range_t){.start = kernel_va_to_pa(range.start), + .end = kernel_va_to_pa(range.end)}; +} diff --git a/lab6/c/src/panic.c b/lab6/c/src/panic.c new file mode 100644 index 000000000..89109ce74 --- /dev/null +++ b/lab6/c/src/panic.c @@ -0,0 +1,22 @@ +#include "oscos/panic.h" + +#include "oscos/console.h" + +void panic_begin(const char *const restrict file, const int line, + const char *const restrict format, ...) { + // Prints the panic message. + + va_list ap; + va_start(ap, format); + + console_printf("panic: %s:%d: ", file, line); + console_vprintf(format, ap); + console_putc('\n'); + + va_end(ap); + + // Park the core. + for (;;) { + __asm__ __volatile__("wfe"); + } +} diff --git a/lab6/c/src/sched/idle-thread.c b/lab6/c/src/sched/idle-thread.c new file mode 100644 index 000000000..b570c09f9 --- /dev/null +++ b/lab6/c/src/sched/idle-thread.c @@ -0,0 +1,8 @@ +#include "oscos/sched.h" + +void idle(void) { + for (;;) { + kill_zombies(); + schedule(); + } +} diff --git a/lab6/c/src/sched/periodic-sched.c b/lab6/c/src/sched/periodic-sched.c new file mode 100644 index 000000000..c81aeb140 --- /dev/null +++ b/lab6/c/src/sched/periodic-sched.c @@ -0,0 +1,33 @@ +#include "oscos/sched.h" + +#include + +#include "oscos/timer/timeout.h" +#include "oscos/xcpt.h" +#include "oscos/xcpt/task-queue.h" + +static void _periodic_sched(void *const _arg) { + (void)_arg; + + sched_setup_periodic_scheduling(); + + // Save spsr_el1 and elr_el1, since they can be clobbered by other threads. + + uint64_t spsr_val, elr_val; + __asm__ __volatile__("mrs %0, spsr_el1" : "=r"(spsr_val)); + __asm__ __volatile__("mrs %0, elr_el1" : "=r"(elr_val)); + + schedule(); + XCPT_MASK_ALL(); + + __asm__ __volatile__("msr spsr_el1, %0" : : "r"(spsr_val)); + __asm__ __volatile__("msr elr_el1, %0" : : "r"(elr_val)); +} + +void sched_setup_periodic_scheduling(void) { + uint64_t core_timer_freq_hz; + __asm__("mrs %0, cntfrq_el0" : "=r"(core_timer_freq_hz)); + core_timer_freq_hz &= 0xffffffff; + + timeout_add_timer_ticks(_periodic_sched, NULL, core_timer_freq_hz >> 5); +} diff --git a/lab6/c/src/sched/run-signal-handler.S b/lab6/c/src/sched/run-signal-handler.S new file mode 100644 index 000000000..570339b05 --- /dev/null +++ b/lab6/c/src/sched/run-signal-handler.S @@ -0,0 +1,121 @@ +#include "oscos/utils/save-ctx.S" + +.section ".text" + +run_signal_handler: + // Save integer context. + + stp x19, x20, [sp, -(12 * 8)]! + stp x21, x22, [sp, 2 * 8] + stp x23, x24, [sp, 4 * 8] + stp x25, x26, [sp, 6 * 8] + stp x27, x28, [sp, 8 * 8] + stp x29, lr, [sp, 10 * 8] + + // Save FP/SIMD context. + + mrs x2, fpcr + mrs x3, fpsr + stp x2, x3, [sp, -16]! + + stp q0, q1, [sp, -(32 * 16)]! + stp q2, q3, [sp, 2 * 16] + stp q4, q5, [sp, 4 * 16] + stp q6, q7, [sp, 6 * 16] + stp q8, q9, [sp, 8 * 16] + stp q10, q11, [sp, 10 * 16] + stp q12, q13, [sp, 12 * 16] + stp q14, q15, [sp, 14 * 16] + stp q16, q17, [sp, 16 * 16] + stp q18, q19, [sp, 18 * 16] + stp q20, q21, [sp, 20 * 16] + stp q22, q23, [sp, 22 * 16] + stp q24, q25, [sp, 24 * 16] + stp q26, q27, [sp, 26 * 16] + stp q28, q29, [sp, 28 * 16] + stp q30, q31, [sp, 30 * 16] + + // - Unmask all interrupts. + // - AArch64 execution state. + // - EL0t. + msr spsr_el1, xzr + + adr x2, sig_handler_main + msr elr_el1, x2 + + msr sp_el0, x1 + + // Zero the integer registers except for x0, which is used to pass an + // argument to sig_handler_main. + + mov x1, 0 + mov x2, 0 + mov x3, 0 + mov x4, 0 + mov x5, 0 + mov x6, 0 + mov x7, 0 + mov x8, 0 + mov x9, 0 + mov x10, 0 + mov x11, 0 + mov x12, 0 + mov x13, 0 + mov x14, 0 + mov x15, 0 + mov x16, 0 + mov x17, 0 + mov x18, 0 + mov x19, 0 + mov x20, 0 + mov x21, 0 + mov x22, 0 + mov x23, 0 + mov x24, 0 + mov x25, 0 + mov x26, 0 + mov x27, 0 + mov x28, 0 + mov x29, 0 + mov lr, 0 + + // Zero the FP/SIMD registers. + + movi d0, 0 + movi d1, 0 + movi d2, 0 + movi d3, 0 + movi d4, 0 + movi d5, 0 + movi d6, 0 + movi d7, 0 + movi d8, 0 + movi d9, 0 + movi d10, 0 + movi d11, 0 + movi d12, 0 + movi d13, 0 + movi d14, 0 + movi d15, 0 + movi d16, 0 + movi d17, 0 + movi d18, 0 + movi d19, 0 + movi d20, 0 + movi d21, 0 + movi d22, 0 + movi d23, 0 + movi d24, 0 + movi d25, 0 + movi d26, 0 + movi d27, 0 + movi d28, 0 + movi d29, 0 + movi d30, 0 + movi d31, 0 + + eret + +.type run_signal_handler, function +.size run_signal_handler, . - run_signal_handler +.global run_signal_handler diff --git a/lab6/c/src/sched/sched.c b/lab6/c/src/sched/sched.c new file mode 100644 index 000000000..ebcf79e27 --- /dev/null +++ b/lab6/c/src/sched/sched.c @@ -0,0 +1,828 @@ +// This module implements a round-robin scheduler. + +#include "oscos/sched.h" + +#include "oscos/libc/string.h" +#include "oscos/mem/malloc.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/mem/vm.h" +#include "oscos/utils/critical-section.h" +#include "oscos/utils/math.h" +#include "oscos/utils/rb.h" + +#define THREAD_STACK_ORDER 13 // 8KB. +#define THREAD_STACK_BLOCK_ORDER (THREAD_STACK_ORDER - PAGE_ORDER) + +#define USER_STACK_ORDER 23 // 8MB. +#define USER_STACK_BLOCK_ORDER (USER_STACK_ORDER - PAGE_ORDER) + +void _suspend_to_wait_queue(thread_list_node_t *wait_queue); +void _sched_run_thread(thread_t *thread); +void thread_main(void); +noreturn void user_program_main(const void *init_pc, const void *init_user_sp, + const void *init_kernel_sp); +noreturn void fork_child_ret(void); +void run_signal_handler(sighandler_t handler, void *init_user_sp); + +static size_t _sched_next_tid = 1, _sched_next_pid = 1; +static thread_list_node_t _run_queue = {.prev = &_run_queue, + .next = &_run_queue}, + _zombies = {.prev = &_zombies, .next = &_zombies}, + _stopped_threads = {.prev = &_stopped_threads, + .next = &_stopped_threads}; +static rb_node_t *_processes = NULL; + +static int _cmp_processes_by_pid(const process_t *const *const p1, + const process_t *const *const p2, void *_arg) { + (void)_arg; + + const size_t pid1 = (*p1)->id, pid2 = (*p2)->id; + return pid1 < pid2 ? -1 : pid1 > pid2 ? 1 : 0; +} + +static int _cmp_pid_and_processes_by_pid(const size_t *const pid, + const process_t *const *const process, + void *_arg) { + (void)_arg; + + const size_t pid1 = *pid, pid2 = (*process)->id; + return pid1 < pid2 ? -1 : pid1 > pid2 ? 1 : 0; +} + +static size_t _calc_text_block_order(const size_t text_len) { + const size_t text_n_pages = + (text_len + ((1 << PAGE_ORDER) - 1)) >> PAGE_ORDER; + const size_t actual_text_block_order = clog2(text_n_pages); + + // We don't know exactly how large the .bss section of a user program is, so + // we have to use a heuristic. We speculate that the .bss section is at most + // as large as the remaining parts (.text, .rodata, and .data sections) of the + // user program. + const size_t allocating_text_block_order = actual_text_block_order + 1; + + return allocating_text_block_order; +} + +static size_t _alloc_tid(void) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const size_t result = _sched_next_tid++; + + CRITICAL_SECTION_LEAVE(daif_val); + return result; +} + +static size_t _alloc_pid(void) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const size_t result = _sched_next_pid++; + + CRITICAL_SECTION_LEAVE(daif_val); + return result; +} + +static void _add_thread_to_queue(thread_t *const thread, + thread_list_node_t *const queue) { + thread_list_node_t *const thread_node = &thread->list_node; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + thread_list_node_t *const last_node = queue->prev; + thread_node->prev = last_node; + last_node->next = thread_node; + thread_node->next = queue; + queue->prev = thread_node; + + CRITICAL_SECTION_LEAVE(daif_val); +} + +static void _add_thread_to_run_queue(thread_t *const thread) { + _add_thread_to_queue(thread, &_run_queue); +} + +void _remove_thread_from_queue(thread_t *const thread) { + thread_list_node_t *const thread_node = &thread->list_node; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + thread_node->prev->next = thread_node->next; + thread_node->next->prev = thread_node->prev; + + CRITICAL_SECTION_LEAVE(daif_val); +} + +thread_t *_remove_first_thread_from_queue(thread_list_node_t *const queue) { + thread_t *result; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + if (queue->next == queue) { + result = NULL; + } else { + thread_list_node_t *const first_node = queue->next; + queue->next = first_node->next; + first_node->next->prev = queue; + result = (thread_t *)((char *)first_node - offsetof(thread_t, list_node)); + } + + CRITICAL_SECTION_LEAVE(daif_val); + return result; +} + +bool sched_init(void) { + // Create the idle thread. + + thread_t *const idle_thread = malloc(sizeof(thread_t)); + if (!idle_thread) + return false; + + idle_thread->id = 0; + idle_thread->process = NULL; + idle_thread->ctx.fp_simd_ctx = NULL; + + // Name the current thread the idle thread. + + __asm__ __volatile__("msr tpidr_el1, %0" : : "r"(idle_thread)); + + return true; +} + +bool thread_create(void (*const task)(void *), void *const arg) { + // Allocate memory. + + thread_t *const thread = malloc(sizeof(thread_t)); + if (!thread) + return false; + + const spage_id_t stack_page_id = alloc_pages(THREAD_STACK_BLOCK_ORDER); + if (stack_page_id < 0) { + free(thread); + return false; + } + + // Initialize the thread structure. + + thread->id = _alloc_tid(); + thread->status.is_waiting = false; + thread->status.is_stopped = false; + thread->status.is_waken_up_by_signal = false; + thread->status.is_handling_signal = false; + thread->process = NULL; + thread->ctx.r19 = (uint64_t)(uintptr_t)task; + thread->ctx.r20 = (uint64_t)(uintptr_t)arg; + thread->ctx.pc = (uint64_t)(uintptr_t)thread_main; + thread->ctx.fp_simd_ctx = NULL; + + thread->stack_page_id = stack_page_id; + void *const init_sp = (char *)pa_to_kernel_va(page_id_to_pa(stack_page_id)) + + (1 << (THREAD_STACK_BLOCK_ORDER + PAGE_ORDER)); + thread->ctx.kernel_sp = (uint64_t)(uintptr_t)init_sp; + + // Put the thread into the end of the run queue. + + _add_thread_to_run_queue(thread); + + return true; +} + +thread_t * +_sched_move_thread_to_queue_and_pick_thread(thread_t *const thread, + thread_list_node_t *const queue) { + _add_thread_to_queue(thread, queue); + return _remove_first_thread_from_queue(&_run_queue); +} + +void thread_exit(void) { + thread_t *const curr_thread = current_thread(); + process_t *const curr_process = curr_thread->process; + + XCPT_MASK_ALL(); + + if (curr_process) { + rb_delete(&_processes, &curr_process->id, + (int (*)(const void *, const void *, + void *))_cmp_pid_and_processes_by_pid, + NULL); + } + + _sched_run_thread( + _sched_move_thread_to_queue_and_pick_thread(curr_thread, &_zombies)); + __builtin_unreachable(); +} + +thread_t *current_thread(void) { + thread_t *result; + __asm__ __volatile__("mrs %0, tpidr_el1" : "=r"(result)); + return result; +} + +bool process_create(void) { + // Allocate memory. + + process_t *const process = malloc(sizeof(process_t)); + if (!process) // Out of memory. + return false; + + const spage_id_t user_stack_page_id = alloc_pages(USER_STACK_BLOCK_ORDER); + if (user_stack_page_id < 0) { // Out of memory. + free(process); + return false; + } + +#ifdef SCHED_ENABLE_SHARED_USER_STACK + shared_page_t *const user_stack_page = shared_page_init(user_stack_page_id); + if (!user_stack_page) { + free_pages(user_stack_page_id); + free(process); + return false; + } +#endif + + thread_fp_simd_ctx_t *const fp_simd_ctx = + malloc(sizeof(thread_fp_simd_ctx_t)); + if (!fp_simd_ctx) { +#ifdef SCHED_ENABLE_SHARED_USER_STACK + shared_page_drop(user_stack_page); +#else + free_pages(user_stack_page_id); +#endif + free(process); + return false; + } + + // Set thread/process data. + + thread_t *const curr_thread = current_thread(); + + process->id = _alloc_pid(); +#ifdef SCHED_ENABLE_SHARED_USER_STACK + process->user_stack_page = user_stack_page; +#else + process->user_stack_page_id = user_stack_page_id; +#endif + process->signal_stack_pages = NULL; + process->main_thread = curr_thread; + process->pending_signals = 0; + process->blocked_signals = 0; + for (size_t i = 0; i < 32; i++) { + process->signal_handlers[i] = SIG_DFL; + } + curr_thread->process = process; + curr_thread->ctx.fp_simd_ctx = fp_simd_ctx; + + // Zero the stack pages. + + void *user_stack_start = pa_to_kernel_va(page_id_to_pa(user_stack_page_id)); + memset(user_stack_start, 0, 1 << USER_STACK_ORDER); + + // Add the process to the process BST. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + rb_insert(&_processes, sizeof(process_t *), &process, + (int (*)(const void *, const void *, void *))_cmp_processes_by_pid, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + return true; +} + +static void _exec_generic(const void *const text_start, const size_t text_len, + const bool free_text_page) { + thread_t *const curr_thread = current_thread(); + process_t *const curr_process = curr_thread->process; + + // Allocate memory. + + const size_t text_block_order = _calc_text_block_order(text_len); + const spage_id_t text_page_id = alloc_pages(text_block_order); + if (text_page_id < 0) { + return; + } + + shared_page_t *const text_page = shared_page_init(text_page_id); + if (!text_page) { + free_pages(text_page_id); + return; + } + + const spage_id_t user_stack_page_id = alloc_pages(USER_STACK_BLOCK_ORDER); + if (user_stack_page_id < 0) { + shared_page_drop(text_page); + return; + } + +#ifdef SCHED_ENABLE_SHARED_USER_STACK + shared_page_t *const user_stack_page = shared_page_init(user_stack_page_id); + if (!user_stack_page) { + free_pages(user_stack_page_id); + shared_page_drop(text_page); + return; + } +#endif + + // Free old pages. + + if (free_text_page) { + shared_page_drop(curr_process->text_page); + } + +#ifdef SCHED_ENABLE_SHARED_USER_STACK + shared_page_drop(curr_process->user_stack_page); +#else + free_pages(curr_process->user_stack_page_id); +#endif + + // Set process data. + + curr_process->text_page = text_page; +#ifdef SCHED_ENABLE_SHARED_USER_STACK + curr_process->user_stack_page = user_stack_page; +#else + curr_process->user_stack_page_id = user_stack_page_id; +#endif + + // Copy the user program. + + void *const user_program_start = pa_to_kernel_va(page_id_to_pa(text_page_id)); + memcpy(user_program_start, text_start, text_len); + + // Zero the remaining spaces of the text pages. + memset((char *)user_program_start + text_len, 0, + (1 << (text_block_order + PAGE_ORDER)) - text_len); + + // Zero the stack pages. + + void *user_stack_start = pa_to_kernel_va(page_id_to_pa(user_stack_page_id)); + memset(user_stack_start, 0, 1 << USER_STACK_ORDER); + + // Run the user program. + + void *const user_stack_end = + (char *)user_stack_start + (1 << USER_STACK_ORDER), + *const kernel_stack_end = (char *)pa_to_kernel_va(page_id_to_pa( + curr_thread->stack_page_id)) + + (1 << THREAD_STACK_ORDER); + user_program_main(user_program_start, user_stack_end, kernel_stack_end); +} + +void exec_first(const void *const text_start, const size_t text_len) { + _exec_generic(text_start, text_len, false); +} + +void exec(const void *const text_start, const size_t text_len) { + _exec_generic(text_start, text_len, true); +} + +static void _thread_cleanup(thread_t *const thread) { + if (thread->process) { + shared_page_drop(thread->process->text_page); +#ifdef SCHED_ENABLE_SHARED_USER_STACK + shared_page_drop(thread->process->user_stack_page); +#else + free_pages(thread->process->user_stack_page_id); +#endif + + page_id_list_node_t *curr_node = thread->process->signal_stack_pages; + while (curr_node) { + page_id_list_node_t *const next_node = curr_node->next; + free(curr_node); + curr_node = next_node; + } + + free(thread->process); + } + free_pages(thread->stack_page_id); + free(thread); +} + +process_t *fork(const extended_trap_frame_t *const trap_frame) { + thread_t *const curr_thread = current_thread(); + process_t *const curr_process = curr_thread->process; + + // Allocate memory. + + thread_t *const new_thread = malloc(sizeof(thread_t)); + if (!new_thread) + return NULL; + + process_t *const new_process = malloc(sizeof(process_t)); + if (!new_process) { + free(new_thread); + return NULL; + } + + const spage_id_t kernel_stack_page_id = alloc_pages(THREAD_STACK_BLOCK_ORDER); + if (kernel_stack_page_id < 0) { + free(new_process); + free(new_thread); + return NULL; + } + + thread_fp_simd_ctx_t *const fp_simd_ctx = + malloc(sizeof(thread_fp_simd_ctx_t)); + if (!fp_simd_ctx) { + free_pages(kernel_stack_page_id); + free(new_process); + free(new_thread); + return NULL; + } + +#ifndef SCHED_ENABLE_SHARED_USER_STACK + const spage_id_t user_stack_page_id = alloc_pages(USER_STACK_BLOCK_ORDER); + if (user_stack_page_id < 0) { + free(fp_simd_ctx); + free_pages(kernel_stack_page_id); + free(new_process); + free(new_thread); + return NULL; + } +#endif + + // Set data. + + new_thread->id = _alloc_tid(); + new_thread->status.is_waiting = false; + new_thread->status.is_stopped = false; + new_thread->status.is_waken_up_by_signal = false; + new_thread->status.is_handling_signal = false; + new_thread->stack_page_id = kernel_stack_page_id; + new_thread->process = new_process; + + new_process->id = _alloc_pid(); + new_process->text_page = shared_page_clone(curr_process->text_page); +#ifdef SCHED_ENABLE_SHARED_USER_STACK + new_process->user_stack_page = + shared_page_clone(curr_process->user_stack_page); +#else + new_process->user_stack_page_id = user_stack_page_id; +#endif + new_process->signal_stack_pages = NULL; + new_process->main_thread = new_thread; + new_process->pending_signals = 0; + new_process->blocked_signals = 0; + memcpy(new_process->signal_handlers, curr_process->signal_handlers, + 32 * sizeof(sighandler_t)); + + // Set execution context. + + void *const kernel_stack_end = + (char *)pa_to_kernel_va(page_id_to_pa(kernel_stack_page_id)) + + (1 << THREAD_STACK_ORDER), + *const init_kernel_sp = + (char *)kernel_stack_end - sizeof(extended_trap_frame_t); + + memcpy(&new_thread->ctx, &curr_thread->ctx, sizeof(thread_ctx_t)); + new_thread->ctx.pc = (uint64_t)(uintptr_t)fork_child_ret; + new_thread->ctx.kernel_sp = (uint64_t)(uintptr_t)init_kernel_sp; + new_thread->ctx.fp_simd_ctx = fp_simd_ctx; + memcpy(fp_simd_ctx, curr_thread->ctx.fp_simd_ctx, + sizeof(thread_fp_simd_ctx_t)); + + memcpy(init_kernel_sp, trap_frame, sizeof(extended_trap_frame_t)); + +#ifndef SCHED_ENABLE_SHARED_USER_STACK + // Setup the user stack. + + void *const old_user_stack_start = pa_to_kernel_va( + page_id_to_pa(curr_process->user_stack_page_id)), + *const user_stack_start = + pa_to_kernel_va(page_id_to_pa(user_stack_page_id)), + *const new_sp = + (char *)user_stack_start + ((char *)curr_thread->ctx.user_sp - + (char *)old_user_stack_start); + + memcpy(user_stack_start, old_user_stack_start, 1 << USER_STACK_ORDER); + new_thread->ctx.user_sp = (uint64_t)new_sp; +#endif + + // Add the new thread to the end of the run queue and the process to the + // process BST. Note that these two steps can be done in either order but must + // not be interrupted in between, since: + // - If the former is done before the latter and the newly-created thread is + // scheduled between the two steps, then the new thread won't be able to + // find its own process. + // - If the latter is done before the former and the newly-created process is + // killed between the two steps, then a freed thread_t instance (i.e., + // `new_thread`) will be added onto the run queue – a use-after-free bug. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + _add_thread_to_run_queue(new_thread); + + rb_insert(&_processes, sizeof(process_t *), &new_process, + (int (*)(const void *, const void *, void *))_cmp_processes_by_pid, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + return new_process; +} + +void kill_zombies(void) { + thread_t *zombie; + while ((zombie = _remove_first_thread_from_queue(&_zombies))) { + _thread_cleanup(zombie); + } +} + +void schedule(void) { _suspend_to_wait_queue(&_run_queue); } + +void suspend_to_wait_queue(thread_list_node_t *const wait_queue) { + XCPT_MASK_ALL(); + current_thread()->status.is_waiting = true; + _suspend_to_wait_queue(wait_queue); +} + +void add_all_threads_to_run_queue(thread_list_node_t *const wait_queue) { + thread_t *thread; + while ((thread = _remove_first_thread_from_queue(wait_queue))) { + _add_thread_to_run_queue(thread); + } +} + +process_t *get_process_by_id(const size_t pid) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + process_t *const *const result = + (process_t **)rb_search(_processes, &pid, + (int (*)(const void *, const void *, + void *))_cmp_pid_and_processes_by_pid, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + return result ? *result : NULL; +} + +void kill_process(process_t *const process) { + if (process == current_thread()->process) { + thread_exit(); + } else { + thread_t *const thread = process->main_thread; + + // Remove the process from the process BST and the thread from the run/wait + // queue. Note that these two steps can be done in either order but must not + // be interrupted in between, since: + // - If the former is done before the latter and the thread is scheduled + // between the two steps, then the new thread won't be able to find its + // own process. + // - If the latter is done before the former and the newly-created process + // is killed once again between the two steps, then a freed thread_t + // instance (i.e., `thread`) will be added onto the zombies list – a + // use-after-free bug. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + rb_delete(&_processes, &process->id, + (int (*)(const void *, const void *, + void *))_cmp_pid_and_processes_by_pid, + NULL); + + _remove_thread_from_queue(thread); + _add_thread_to_queue(thread, &_zombies); + + CRITICAL_SECTION_LEAVE(daif_val); + } +} + +void kill_all_processes(void) { + // Kill all processes other than the current one. + for (;;) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + process_t *process_to_kill = *(process_t **)_processes->payload; + if (process_to_kill == current_thread()->process) { + if (_processes->children[0]) { + process_to_kill = *(process_t **)_processes->children[0]->payload; + } else if (_processes->children[1]) { + process_to_kill = *(process_t **)_processes->children[1]->payload; + } else { // Only the current process left. + CRITICAL_SECTION_LEAVE(daif_val); + break; + } + } + + CRITICAL_SECTION_LEAVE(daif_val); + + kill_process(process_to_kill); + } + + // Kill the current process. + thread_exit(); +} + +sighandler_t set_signal_handler(process_t *const process, const int signal, + const sighandler_t handler) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const sighandler_t old_handler = process->signal_handlers[signal]; + + // The video player expects SIGKILL to be catchable. This function thus + // follows the protocol used by the video player. + if (!(/* signal == SIGKILL || */ signal == SIGSTOP)) { + process->signal_handlers[signal] = handler; + } + + CRITICAL_SECTION_LEAVE(daif_val); + return old_handler; +} + +void deliver_signal(process_t *const process, const int signal) { + thread_t *const thread = process->main_thread; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + process->pending_signals |= 1 << signal; + + if (thread->status.is_waiting && + (!thread->status.is_stopped || signal == SIGCONT)) { + // Wake up the thread and notify it that it was waken up by a signal. + + thread->status.is_waiting = false; + thread->status.is_stopped = false; + thread->status.is_waken_up_by_signal = true; + + _remove_thread_from_queue(thread); + _add_thread_to_run_queue(thread); + } + + CRITICAL_SECTION_LEAVE(daif_val); +} + +static void _deliver_signal_to_all_processes_rec(const int signal, + const rb_node_t *const node) { + if (!node) + return; + + process_t *const process = *(process_t **)node->payload; + deliver_signal(process, signal); + + _deliver_signal_to_all_processes_rec(signal, node->children[0]); + _deliver_signal_to_all_processes_rec(signal, node->children[1]); +} + +void deliver_signal_to_all_processes(const int signal) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + _deliver_signal_to_all_processes_rec(signal, _processes); + + CRITICAL_SECTION_LEAVE(daif_val); +} + +static bool _signal_default_is_term_or_core(const int signal) { + return signal == SIGABRT || signal == SIGALRM || signal == SIGBUS || + signal == SIGFPE || signal == SIGHUP || signal == SIGILL || + signal == SIGINT || signal == SIGIO || signal == SIGIOT || + signal == SIGKILL || signal == SIGPIPE || signal == SIGPROF || + signal == SIGPWR || signal == SIGQUIT || signal == SIGSEGV || + signal == SIGSTKFLT || signal == SIGSYS || signal == SIGTERM || + signal == SIGTRAP || signal == SIGUNUSED || signal == SIGUSR1 || + signal == SIGUSR2 || signal == SIGVTALRM || signal == SIGXCPU || + signal == SIGXFSZ; +} + +static bool _signal_default_is_stop(const int signal) { + return signal == SIGSTOP || signal == SIGTSTP || signal == SIGTTIN || + signal == SIGTTOU; +} + +void handle_signals(void) { + thread_t *const curr_thread = current_thread(); + if (!curr_thread) // The scheduler has not been initialized. + return; + process_t *const curr_process = curr_thread->process; + + // Save spsr_el1 and elr_el1, since they can be clobbered when running the + // signal handlers. + + uint64_t spsr_val, elr_val; + __asm__ __volatile__("mrs %0, spsr_el1" : "=r"(spsr_val)); + __asm__ __volatile__("mrs %0, elr_el1" : "=r"(elr_val)); + + // Save the old user SP, since the signal handlers will run in a new user + // stack. + + uint64_t old_user_sp; + __asm__ __volatile__("mrs %0, sp_el0" : "=r"(old_user_sp)); + + // Save the status bit in order to support nested signal handling. + + const bool is_handling_signal = curr_thread->status.is_handling_signal; + + // The user stack is allocated lazily. + + bool user_stack_allocation_attempted = false; + spage_id_t user_stack_page_id; + page_id_list_node_t *const curr_signal_stack_pages_node = + curr_process->signal_stack_pages, + *new_signal_stack_pages_node; + + uint32_t deferred_signals = 0; + for (uint32_t handleable_signals; + (handleable_signals = curr_process->pending_signals & + ~curr_process->blocked_signals & + ~deferred_signals) != 0;) { + uint32_t reversed_handleable_signals; + __asm__("rbit %w0, %w1" + : "=r"(reversed_handleable_signals) + : "r"(handleable_signals)); + int signal; + __asm__("clz %w0, %w1" : "=r"(signal) : "r"(reversed_handleable_signals)); + + const sighandler_t handler = curr_process->signal_handlers[signal]; + if (handler == SIG_DFL) { + curr_process->pending_signals &= ~(1 << signal); + + if (_signal_default_is_term_or_core(signal)) { + thread_exit(); + } else if (_signal_default_is_stop(signal)) { + curr_thread->status.is_stopped = true; + suspend_to_wait_queue(&_stopped_threads); + curr_thread->status.is_waken_up_by_signal = false; + } + } else if (handler == SIG_IGN) { + curr_process->pending_signals &= ~(1 << signal); + } else { + // Allocate user stack. + + if (!user_stack_allocation_attempted) { + user_stack_allocation_attempted = true; + user_stack_page_id = alloc_pages(USER_STACK_BLOCK_ORDER); + + if (user_stack_page_id >= 0) { + new_signal_stack_pages_node = malloc(sizeof(page_id_list_node_t)); + if (new_signal_stack_pages_node) { + new_signal_stack_pages_node->page_id = user_stack_page_id; + new_signal_stack_pages_node->next = curr_signal_stack_pages_node; + curr_process->signal_stack_pages = new_signal_stack_pages_node; + } else { + free_pages(user_stack_page_id); + user_stack_page_id = -1; + } + } + } + + if (user_stack_page_id >= 0) { + void *const user_stack_start = + pa_to_kernel_va(page_id_to_pa(user_stack_page_id)), + *const user_stack_end = + (char *)user_stack_start + (1 << USER_STACK_ORDER); + + // Zero the user stack. + + memset(user_stack_start, 0, 1 << USER_STACK_ORDER); + + // Run the signal handler. + + curr_thread->status.is_handling_signal = true; + curr_process->blocked_signals |= 1 << signal; + + run_signal_handler(handler, user_stack_end); + + curr_thread->status.is_handling_signal = false; + curr_process->pending_signals &= ~(1 << signal); + curr_process->blocked_signals &= ~(1 << signal); + } else { + // Defer the handling of this signal. Hopefully, there will be enough + // memory to run the signal handler in the future. + deferred_signals |= 1 << signal; + } + } + } + + // Destroy the user stack. + + if (user_stack_allocation_attempted && user_stack_page_id >= 0) { + free_pages(user_stack_page_id); + curr_process->signal_stack_pages = curr_signal_stack_pages_node; + free(new_signal_stack_pages_node); + } + + // Restore spsr_el1 and elr_el1. + + __asm__ __volatile__("msr spsr_el1, %0" : : "r"(spsr_val)); + __asm__ __volatile__("msr elr_el1, %0" : : "r"(elr_val)); + + // Restore the user SP. + + __asm__ __volatile__("msr sp_el0, %0" : : "r"(old_user_sp)); + + // Restore the status bit. + + curr_thread->status.is_handling_signal = is_handling_signal; +} diff --git a/lab6/c/src/sched/schedule.S b/lab6/c/src/sched/schedule.S new file mode 100644 index 000000000..51d7213b5 --- /dev/null +++ b/lab6/c/src/sched/schedule.S @@ -0,0 +1,111 @@ +.section ".text" + +_suspend_to_wait_queue: + // Enter critical section. + + msr daifset, 0xf + + // Get current thread instance. + + mrs x2, tpidr_el1 + + // Save integer context. + + stp x19, x20, [x2, 16 + 0 * 16] + stp x21, x22, [x2, 16 + 1 * 16] + stp x23, x24, [x2, 16 + 2 * 16] + stp x25, x26, [x2, 16 + 3 * 16] + stp x27, x28, [x2, 16 + 4 * 16] + stp x29, x30, [x2, 16 + 5 * 16] + mov x1, sp + mrs x3, sp_el0 + stp x1, x3, [x2, 16 + 6 * 16] + + // Save FP/SIMD context, if there is one. + + ldr x1, [x2, 16 + 7 * 16] + cbz x1, .Lafter_save_fp_ctx + + stp q0, q1, [x1, 0 * 32] + stp q2, q3, [x1, 1 * 32] + stp q4, q5, [x1, 2 * 32] + stp q6, q7, [x1, 3 * 32] + stp q8, q9, [x1, 4 * 32] + stp q10, q11, [x1, 5 * 32] + stp q12, q13, [x1, 6 * 32] + stp q14, q15, [x1, 7 * 32] + stp q16, q17, [x1, 8 * 32] + stp q18, q19, [x1, 9 * 32] + stp q20, q21, [x1, 10 * 32] + stp q22, q23, [x1, 11 * 32] + stp q24, q25, [x1, 12 * 32] + stp q26, q27, [x1, 13 * 32] + stp q28, q29, [x1, 14 * 32] + stp q30, q31, [x1, 15 * 32] + add x1, x1, 16 * 32 + mrs x3, fpcr + mrs x4, fpsr + stp x3, x4, [x1] + +.Lafter_save_fp_ctx: + mov x1, x0 + mov x0, x2 + bl _sched_move_thread_to_queue_and_pick_thread + + // Fallthrough. + +.size _suspend_to_wait_queue, . - _suspend_to_wait_queue +.type _suspend_to_wait_queue, function +.global _suspend_to_wait_queue + +_sched_run_thread: + // Restore integer context. + + ldp x19, x20, [x0, 16 + 0 * 16] + ldp x21, x22, [x0, 16 + 1 * 16] + ldp x23, x24, [x0, 16 + 2 * 16] + ldp x25, x26, [x0, 16 + 3 * 16] + ldp x27, x28, [x0, 16 + 4 * 16] + ldp x29, x30, [x0, 16 + 5 * 16] + ldp x1, x2, [x0, 16 + 6 * 16] + mov sp, x1 + msr sp_el0, x2 + + // Restore FP/SIMD context, if there is one. + + ldr x1, [x0, 16 + 7 * 16] + cbz x1, .Lafter_load_fp_ctx + + ldp q0, q1, [x1, 0 * 32] + ldp q2, q3, [x1, 1 * 32] + ldp q4, q5, [x1, 2 * 32] + ldp q6, q7, [x1, 3 * 32] + ldp q8, q9, [x1, 4 * 32] + ldp q10, q11, [x1, 5 * 32] + ldp q12, q13, [x1, 6 * 32] + ldp q14, q15, [x1, 7 * 32] + ldp q16, q17, [x1, 8 * 32] + ldp q18, q19, [x1, 9 * 32] + ldp q20, q21, [x1, 10 * 32] + ldp q22, q23, [x1, 11 * 32] + ldp q24, q25, [x1, 12 * 32] + ldp q26, q27, [x1, 13 * 32] + ldp q28, q29, [x1, 14 * 32] + ldp q30, q31, [x1, 15 * 32] + add x1, x1, 16 * 32 + ldp x2, x3, [x1] + msr fpcr, x2 + msr fpcr, x3 + +.Lafter_load_fp_ctx: + msr tpidr_el1, x0 + + // Unmask interrupts. + + msr daifclr, 0xf + + ret + +.size _sched_run_thread, . - _sched_run_thread +.type _sched_run_thread, function +.global _sched_run_thread diff --git a/lab6/c/src/sched/sig-handler-main.S b/lab6/c/src/sched/sig-handler-main.S new file mode 100644 index 000000000..9c3f530de --- /dev/null +++ b/lab6/c/src/sched/sig-handler-main.S @@ -0,0 +1,10 @@ +.section ".text" + +sig_handler_main: + blr x0 + mov x8, 10 // SYS_sigreturn + svc 0 + +.type sig_handler_main, function +.size sig_handler_main, . - sig_handler_main +.global sig_handler_main diff --git a/lab6/c/src/sched/thread-main.S b/lab6/c/src/sched/thread-main.S new file mode 100644 index 000000000..7548f66ee --- /dev/null +++ b/lab6/c/src/sched/thread-main.S @@ -0,0 +1,10 @@ +.section ".text" + +thread_main: + mov x0, x20 + blr x19 + b thread_exit + +.size thread_main, . - thread_main +.type thread_main, function +.global thread_main diff --git a/lab6/c/src/sched/user-program-main.S b/lab6/c/src/sched/user-program-main.S new file mode 100644 index 000000000..bfc44b8fb --- /dev/null +++ b/lab6/c/src/sched/user-program-main.S @@ -0,0 +1,93 @@ +.section ".text" + +user_program_main: + // We don't need to synchronize the data cache and the instruction cache + // since we haven't enabled any of them. Nonetheless, we still have to + // synchronize the fetched instruction stream using the `isb` instruction. + isb + + // Mask the interrupt to prevent spsr_el1 and elr_el1 from being clobbered + // by ISRs. + msr daifset, 0xf + + msr elr_el1, x0 + msr sp_el0, x1 + mov sp, x2 + + // - Unask all interrupts. + // - AArch64 execution state. + // - EL0t. + msr spsr_el1, xzr + + // Zero the integer registers. + mov x0, 0 + mov x1, 0 + mov x2, 0 + mov x3, 0 + mov x4, 0 + mov x5, 0 + mov x6, 0 + mov x7, 0 + mov x8, 0 + mov x9, 0 + mov x10, 0 + mov x11, 0 + mov x12, 0 + mov x13, 0 + mov x14, 0 + mov x15, 0 + mov x16, 0 + mov x17, 0 + mov x18, 0 + mov x19, 0 + mov x20, 0 + mov x21, 0 + mov x22, 0 + mov x23, 0 + mov x24, 0 + mov x25, 0 + mov x26, 0 + mov x27, 0 + mov x28, 0 + mov x29, 0 + mov lr, 0 + + // Zero the FP/SIMD registers. + movi d0, 0 + movi d1, 0 + movi d2, 0 + movi d3, 0 + movi d4, 0 + movi d5, 0 + movi d6, 0 + movi d7, 0 + movi d8, 0 + movi d9, 0 + movi d10, 0 + movi d11, 0 + movi d12, 0 + movi d13, 0 + movi d14, 0 + movi d15, 0 + movi d16, 0 + movi d17, 0 + movi d18, 0 + movi d19, 0 + movi d20, 0 + movi d21, 0 + movi d22, 0 + movi d23, 0 + movi d24, 0 + movi d25, 0 + movi d26, 0 + movi d27, 0 + movi d28, 0 + movi d29, 0 + movi d30, 0 + movi d31, 0 + + eret + +.type user_program_main, function +.size user_program_main, . - user_program_main +.global user_program_main diff --git a/lab6/c/src/shell.c b/lab6/c/src/shell.c new file mode 100644 index 000000000..448c7df93 --- /dev/null +++ b/lab6/c/src/shell.c @@ -0,0 +1,335 @@ +#include "oscos/shell.h" + +#include + +#include "oscos/console.h" +#include "oscos/drivers/mailbox.h" +#include "oscos/drivers/pm.h" +#include "oscos/initrd.h" +#include "oscos/libc/ctype.h" +#include "oscos/libc/inttypes.h" +#include "oscos/libc/string.h" +#include "oscos/mem/malloc.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/sched.h" +#include "oscos/timer/timeout.h" +#include "oscos/utils/time.h" + +#define MAX_CMD_LEN 78 + +static void _shell_print_prompt(void) { console_fputs("# "); } + +static size_t _shell_read_line(char *const buf, const size_t n) { + size_t cmd_len = 0; + + for (;;) { + const char c = console_getc(); + + if (c == '\n') { + console_putc('\n'); + break; + } else if (c == '\x7f') { // Backspace. + if (cmd_len > 0) { + console_fputs("\b \b"); + cmd_len--; + } + } else { + console_putc(c); + if (n > 0 && cmd_len < n - 1) { + buf[cmd_len] = c; + } + cmd_len++; + } + } + + if (n > 0) { + buf[cmd_len > n - 1 ? n - 1 : cmd_len] = '\0'; + } + + return cmd_len; +} + +static void _shell_do_cmd_help(void) { + console_puts( + "help : print this help menu\n" + "hello : print Hello World!\n" + "hwinfo : get the hardware's information by mailbox\n" + "reboot : reboot the device\n" + "ls : list all files in the initial ramdisk\n" + "cat : print the content of a file in the initial ramdisk\n" + "exec : run a user program in the initial ramdisk\n" + "setTimeout : print a message after a timeout\n" + "alloc-pages : allocates a block of page frames using the page frame " + "allocator\n" + "free-pages : frees a block of page frames allocated using the page " + "frame allocator"); +} + +static void _shell_do_cmd_hello(void) { console_puts("Hello World!"); } + +static void _shell_do_cmd_hwinfo(void) { + const uint32_t board_revision = mailbox_get_board_revision(); + const arm_memory_t arm_memory = mailbox_get_arm_memory(); + + console_printf("Board revision: 0x%" PRIx32 "\nARM memory: Base: 0x%" PRIx32 + ", Size: 0x%" PRIx32 "\n", + board_revision, arm_memory.base, arm_memory.size); +} + +noreturn static void _shell_do_cmd_reboot(void) { pm_reboot(); } + +static void _shell_do_cmd_ls(void) { + if (!initrd_is_init()) { + console_puts("oscsh: ls: initrd is invalid"); + return; + } + + INITRD_FOR_ENTRY(entry) { console_puts(CPIO_NEWC_PATHNAME(entry)); } +} + +static void _shell_do_cmd_cat(void) { + if (!initrd_is_init()) { + console_puts("oscsh: cat: initrd is invalid"); + return; + } + + console_fputs("Filename: "); + + char filename_buf[MAX_CMD_LEN + 1]; + _shell_read_line(filename_buf, MAX_CMD_LEN + 1); + + const cpio_newc_entry_t *const entry = + initrd_find_entry_by_pathname(filename_buf); + if (!entry) { + console_puts("oscsh: cat: no such file or directory"); + return; + } + + const uint32_t mode = CPIO_NEWC_HEADER_VALUE(entry, mode); + const uint32_t file_type = mode & CPIO_NEWC_MODE_FILE_TYPE_MASK; + if (file_type == CPIO_NEWC_MODE_FILE_TYPE_REG) { + console_write(CPIO_NEWC_FILE_DATA(entry), CPIO_NEWC_FILESIZE(entry)); + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_DIR) { + console_puts("oscsh: cat: is a directory"); + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_LNK) { + console_fputs("Symbolic link to: "); + console_write(CPIO_NEWC_FILE_DATA(entry), CPIO_NEWC_FILESIZE(entry)); + console_putc('\n'); + } else { + console_puts("oscsh: cat: unknown file type"); + } +} + +static void _shell_do_cmd_exec(void) { + if (!initrd_is_init()) { + console_puts("oscsh: exec: initrd is invalid"); + return; + } + + console_fputs("Filename: "); + + char filename_buf[MAX_CMD_LEN + 1]; + _shell_read_line(filename_buf, MAX_CMD_LEN + 1); + + const cpio_newc_entry_t *const entry = + initrd_find_entry_by_pathname(filename_buf); + if (!entry) { + console_puts("oscsh: exec: no such file or directory"); + return; + } + + const void *user_program_start; + size_t user_program_len; + + const uint32_t mode = CPIO_NEWC_HEADER_VALUE(entry, mode); + const uint32_t file_type = mode & CPIO_NEWC_MODE_FILE_TYPE_MASK; + if (file_type == CPIO_NEWC_MODE_FILE_TYPE_REG) { + user_program_start = CPIO_NEWC_FILE_DATA(entry); + user_program_len = CPIO_NEWC_FILESIZE(entry); + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_DIR) { + console_puts("oscsh: exec: is a directory"); + return; + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_LNK) { + console_fputs("oscos: exec: is a symbolic link to: "); + console_write(CPIO_NEWC_FILE_DATA(entry), CPIO_NEWC_FILESIZE(entry)); + console_putc('\n'); + return; + } else { + console_puts("oscsh: exec: unknown file type"); + return; + } + + if (!process_create()) { + console_puts("oscsh: exec: out of memory"); + return; + } + + exec_first(user_program_start, user_program_len); + + // If execution reaches here, then exec failed. + console_puts("oscsh: exec: out of memory"); +} + +static void _shell_cmd_set_timeout_timer_callback(char *const message) { + console_puts(message); + free(message); +} + +static void _shell_do_cmd_set_timeout(const char *const args) { + const char *c = args; + + // Skip initial whitespaces. + for (; *c == ' '; c++) + ; + + // Message. + + if (!*c) + goto invalid; + + const char *const message_start = c; + size_t message_len = 0; + + for (; *c && *c != ' '; c++) { + message_len++; + } + + // Skip whitespaces. + for (; *c == ' '; c++) + ; + + // Seconds. + + if (!*c) + goto invalid; + + size_t seconds = 0; + for (; *c && *c != ' '; c++) { + if (!isdigit(*c)) + goto invalid; + seconds = seconds * 10 + (*c - '0'); + } + + // Skip whitespaces. + for (; *c == ' '; c++) + ; + + // There should be no more arguments. + if (*c) + goto invalid; + + // Copy the string. + + char *const message_copy = malloc(message_len + 1); + if (!message_copy) { + console_puts("oscsh: setTimeout: out of memory"); + return; + } + memcpy(message_copy, message_start, message_len); + message_copy[message_len] = '\0'; + + // Register the callback. + timeout_add_timer_ns((void (*)(void *))_shell_cmd_set_timeout_timer_callback, + message_copy, seconds * NS_PER_SEC); + return; + +invalid: + console_puts("oscsh: setTimeout: invalid command format"); +} + +static void _shell_do_cmd_alloc_pages(void) { + console_fputs("Order (decimal, leave blank for 0): "); + + char digit_buf[3]; + const size_t digit_len = _shell_read_line(digit_buf, 3); + if (digit_len > 2) + goto invalid; + + size_t order = 0; + for (const char *c = digit_buf; *c; c++) { + if (!isdigit(*c)) + goto invalid; + order = order * 10 + (*c - '0'); + } + + const spage_id_t page = alloc_pages(order); + if (page < 0) { + console_puts("oscsh: alloc-pages: out of memory"); + return; + } + + console_printf("Page ID: 0x%" PRIxPAGEID "\n", (page_id_t)page); + return; + +invalid: + console_puts("oscsh: alloc-pages: invalid order"); +} + +static void _shell_do_cmd_free_pages(void) { + console_fputs( + "Page number of the first page (lowercase hexadecimal without prefix): "); + + char digit_buf[9]; + const size_t digit_len = _shell_read_line(digit_buf, 9); + if (digit_len > 8) + goto invalid; + + page_id_t page_id = 0; + for (const char *c = digit_buf; *c; c++) { + page_id_t digit_value; + if (isdigit(*c)) { + digit_value = *c - '0'; + } else if ('a' <= *c && *c <= 'f') { + digit_value = *c - 'a' + 10; + } else { + goto invalid; + } + page_id = page_id << 4 | digit_value; + } + + free_pages(page_id); + return; + +invalid: + console_puts("oscsh: free-pages: invalid page number"); +} + +static void _shell_cmd_not_found(const char *const cmd) { + console_printf("oscsh: %s: command not found\n", cmd); +} + +void run_shell(void) { + for (;;) { + _shell_print_prompt(); + + char cmd_buf[MAX_CMD_LEN + 1]; + const size_t cmd_len = _shell_read_line(cmd_buf, MAX_CMD_LEN + 1); + + if (cmd_len == 0) { + // No-op. + } else if (strcmp(cmd_buf, "help") == 0) { + _shell_do_cmd_help(); + } else if (strcmp(cmd_buf, "hello") == 0) { + _shell_do_cmd_hello(); + } else if (strcmp(cmd_buf, "hwinfo") == 0) { + _shell_do_cmd_hwinfo(); + } else if (strcmp(cmd_buf, "reboot") == 0) { + _shell_do_cmd_reboot(); + } else if (strcmp(cmd_buf, "ls") == 0) { + _shell_do_cmd_ls(); + } else if (strcmp(cmd_buf, "cat") == 0) { + _shell_do_cmd_cat(); + } else if (strcmp(cmd_buf, "exec") == 0) { + _shell_do_cmd_exec(); + } else if (strncmp(cmd_buf, "setTimeout", 10) == 0 && + (!cmd_buf[10] || cmd_buf[10] == ' ')) { + _shell_do_cmd_set_timeout(cmd_buf + 10); + } else if (strcmp(cmd_buf, "alloc-pages") == 0) { + _shell_do_cmd_alloc_pages(); + } else if (strcmp(cmd_buf, "free-pages") == 0) { + _shell_do_cmd_free_pages(); + } else { + _shell_cmd_not_found(cmd_buf); + } + } +} diff --git a/lab6/c/src/start.S b/lab6/c/src/start.S new file mode 100644 index 000000000..df9718cad --- /dev/null +++ b/lab6/c/src/start.S @@ -0,0 +1,71 @@ +.section ".text._start" + +_start: + // Enable FP/SIMD. + mov x1, 0x300000 + msr cptr_el2, x1 + + // Switch to EL1. + + // - Use AArch64 in EL1. + // - Trap nothing to EL2. + // - Disable interrupt routing to EL2. + // - Disable stage 2 address translation. + mov x1, 0x80000000 + msr hcr_el2, x1 + + // - Mask all interrupts. + // - AArch64 execution state. + // - EL1h. (Uses SP_EL1). + mov x1, 0x3c5 + msr spsr_el2, x1 + + adr x1, .Lin_el1 + msr elr_el2, x1 + eret + +.Lin_el1: + + // Enable FP/SIMD. + mov x1, 0x300000 + msr cpacr_el1, x1 + + // Allow user programs to access the timer. + mov x1, 0x1 + msr cntkctl_el1, x1 + + msr tpidr_el1, xzr + + // Clear the .bss section. + + ldr x1, =_sbss + ldr x2, =_ebss + b .Lclear_bss_loop_test + +.Lclear_bss_loop_body: + // This does not generate unaligned memory accesses, since the .bss section + // is 16-byte aligned. See `linker.ld`. + stp xzr, xzr, [x1], 16 + +.Lclear_bss_loop_test: + cmp x1, x2 + b.ne .Lclear_bss_loop_body + + // Set the stack pointer. + + ldr x1, =_estack + mov sp, x1 + + // Call the main function. + + bl main + + // Park the core. + +.Lpark_loop: + wfe + b .Lpark_loop + +.size _start, . - _start +.type _start, function +.global _start diff --git a/lab6/c/src/timer/delay.c b/lab6/c/src/timer/delay.c new file mode 100644 index 000000000..8f7a29c2e --- /dev/null +++ b/lab6/c/src/timer/delay.c @@ -0,0 +1,15 @@ +#include "oscos/timer/delay.h" + +#include + +#include "oscos/timer/timeout.h" +#include "oscos/utils/suspend.h" + +static void _timeout_callback(volatile bool *const flag) { *flag = true; } + +void delay_ns(const uint64_t ns) { + volatile bool flag = false; + timeout_add_timer_ns((void (*)(void *))_timeout_callback, (void *)&flag, ns); + + WFI_WHILE(!flag); +} diff --git a/lab6/c/src/timer/timeout.c b/lab6/c/src/timer/timeout.c new file mode 100644 index 000000000..a9c8b74a6 --- /dev/null +++ b/lab6/c/src/timer/timeout.c @@ -0,0 +1,114 @@ +#include "oscos/timer/timeout.h" + +#include + +#include "oscos/drivers/l1ic.h" +#include "oscos/utils/core-id.h" +#include "oscos/utils/critical-section.h" +#include "oscos/utils/heapq.h" +#include "oscos/utils/time.h" +#include "oscos/xcpt.h" + +#define MAX_N_TIMEOUT_ENTRIES 16 + +typedef struct { + uint64_t timestamp; + void (*callback)(void *); + void *arg; +} timeout_entry_t; + +static timeout_entry_t _timeout_entries[MAX_N_TIMEOUT_ENTRIES]; +static size_t _n_timeout_entries = 0; + +static int _timeout_entry_cmp_by_timestamp(const timeout_entry_t *const e1, + const timeout_entry_t *const e2, + void *const _arg) { + (void)_arg; + + if (e1->timestamp < e2->timestamp) + return -1; + if (e1->timestamp > e2->timestamp) + return 1; + return 0; +} + +void timeout_init(void) { + __asm__ __volatile__("msr cntp_ctl_el0, %0" : : "r"(0x3)); + l1ic_enable_core_timer_irq(get_core_id()); +} + +bool timeout_add_timer_ns(void (*const callback)(void *), void *const arg, + const uint64_t after_ns) { + uint64_t core_timer_freq_hz; + __asm__("mrs %0, cntfrq_el0" : "=r"(core_timer_freq_hz)); + core_timer_freq_hz &= 0xffffffff; + + // ceil(after_ns * core_timer_freq_hz / NS_PER_SEC). + const uint64_t after_ticks = + (after_ns * core_timer_freq_hz + (NS_PER_SEC - 1)) / NS_PER_SEC; + + return timeout_add_timer_ticks(callback, arg, after_ticks); +} + +bool timeout_add_timer_ticks(void (*const callback)(void *), void *const arg, + const uint64_t after_ticks) { + if (_n_timeout_entries == MAX_N_TIMEOUT_ENTRIES) + return false; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + uint64_t curr_timestamp; + __asm__ __volatile__("mrs %0, cntpct_el0" : "=r"(curr_timestamp)); + + const uint64_t timestamp = curr_timestamp + after_ticks; + + // Reprogram the timer. + + const bool need_timer_reprogramming = + _n_timeout_entries == 0 || timestamp < _timeout_entries[0].timestamp; + if (need_timer_reprogramming) { + __asm__ __volatile__("msr cntp_cval_el0, %0" : : "r"(timestamp)); + } + + // Insert the new timeout entry into the timeout queue. + + timeout_entry_t entry = { + .timestamp = timestamp, .callback = callback, .arg = arg}; + heappush(_timeout_entries, _n_timeout_entries++, sizeof(timeout_entry_t), + &entry, + (int (*)(const void *, const void *, + void *))_timeout_entry_cmp_by_timestamp, + NULL); + + // Enable the core timer interrupt. (ENABLE = 1, IMASK = 0) + __asm__ __volatile__("msr cntp_ctl_el0, %0" : : "r"(0x1)); + + CRITICAL_SECTION_LEAVE(daif_val); + + return true; +} + +void xcpt_core_timer_interrupt_handler(void) { + // Remove the top entry from the timeout queue. + + timeout_entry_t top_entry; + heappop(_timeout_entries, _n_timeout_entries--, sizeof(timeout_entry_t), + &top_entry, + (int (*)(const void *, const void *, + void *))_timeout_entry_cmp_by_timestamp, + NULL); + + // Reprogram the timer. + if (_n_timeout_entries == 0) { + // Disable the core timer interrupt. (ENABLE = 1, IMASK = 1) + __asm__ __volatile__("msr cntp_ctl_el0, %0" : : "r"(0x3)); + } else { + __asm__ __volatile__("msr cntp_cval_el0, %0" + : + : "r"(_timeout_entries[0].timestamp)); + } + + // Execute the callback. + top_entry.callback(top_entry.arg); +} diff --git a/lab6/c/src/utils/core-id.c b/lab6/c/src/utils/core-id.c new file mode 100644 index 000000000..a7a736439 --- /dev/null +++ b/lab6/c/src/utils/core-id.c @@ -0,0 +1,9 @@ +#include "oscos/utils/core-id.h" + +#include + +size_t get_core_id(void) { + uint64_t mpidr_val; + __asm__ __volatile__("mrs %0, mpidr_el1" : "=r"(mpidr_val)); + return mpidr_val & 0x3; +} diff --git a/lab6/c/src/utils/fmt.c b/lab6/c/src/utils/fmt.c new file mode 100644 index 000000000..9721b2884 --- /dev/null +++ b/lab6/c/src/utils/fmt.c @@ -0,0 +1,653 @@ +#include "oscos/utils/fmt.h" + +#include +#include +#include + +#include "oscos/libc/ctype.h" +#include "oscos/libc/string.h" + +#define OCT_MAX_N_DIGITS 22 +#define DEC_MAX_N_DIGITS 20 +#define HEX_MAX_N_DIGITS 16 + +typedef enum { + LM_NONE, + LM_HH, + LM_H, + LM_L, + LM_LL, + LM_J, + LM_Z, + LM_T, + LM_UPPER_L +} length_modifier_t; + +static const char LOWER_HEX_DIGITS[16] = "0123456789abcdef"; +static const char UPPER_HEX_DIGITS[16] = "0123456789ABCDEF"; + +static size_t +_render_unsigned_dec(char digits[const static DEC_MAX_N_DIGITS + 1], + const uintmax_t x) { + digits[DEC_MAX_N_DIGITS] = '\0'; + + size_t n_digits = 0; + for (uintmax_t xc = x; xc > 0; xc /= 10) { + digits[DEC_MAX_N_DIGITS - ++n_digits] = '0' + xc % 10; + } + + return n_digits; +} + +static size_t +_render_unsigned_base_p2(char *const restrict digits, const size_t digits_len, + const uintmax_t x, const size_t log2_base, + const char *const restrict digit_template) { + const uintmax_t mask = (1 << log2_base) - 1; + + digits[digits_len] = '\0'; + + size_t n_digits = 0; + for (uintmax_t xc = x; xc > 0; xc >>= log2_base) { + digits[digits_len - ++n_digits] = digit_template[xc & mask]; + } + + return n_digits; +} + +static size_t _puts_generic(void (*const putc)(unsigned char, void *), + void *const putc_arg, const char *const s) { + size_t n_chars_printed = 0; + for (const char *c = s; *c; c++) { + putc(*c, putc_arg); + n_chars_printed++; + } + return n_chars_printed; +} + +static size_t _puts_limited_generic(void (*const putc)(unsigned char, void *), + void *const putc_arg, const char *const s, + const size_t limit) { + size_t n_chars_printed = 0, i = 0; + for (const char *c = s; i < limit && *c; i++, c++) { + putc(*c, putc_arg); + n_chars_printed++; + } + return n_chars_printed; +} + +static size_t _pad_generic(void (*const putc)(unsigned char, void *), + void *const putc_arg, const unsigned char pad, + const size_t width) { + for (size_t i = 0; i < width; i++) { + putc(pad, putc_arg); + } + return width; +} + +static size_t _vprintf_generic_putc(void (*const putc)(unsigned char, void *), + void *const putc_arg, const unsigned char c, + const bool flag_minus, + const size_t min_field_width) { + size_t n_chars_printed = 0; + + if (flag_minus) { + putc(c, putc_arg); + n_chars_printed++; + + if (min_field_width > 1) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', min_field_width - 1); + } + } else { + if (min_field_width > 1) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', min_field_width - 1); + } + + putc(c, putc_arg); + n_chars_printed++; + } + + return n_chars_printed; +} + +static size_t _vprintf_generic_puts(void (*const putc)(unsigned char, void *), + void *const putc_arg, const char *const s, + const bool flag_minus, + const size_t min_field_width, + const bool precision_valid, + const size_t precision) { + const size_t len = strlen(s); + + // Calculate width. + + const size_t width = precision_valid && precision < len ? precision : len; + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + // The string. + if (precision_valid) { + n_chars_printed += _puts_limited_generic(putc, putc_arg, s, precision); + } else { + n_chars_printed += _puts_generic(putc, putc_arg, s); + } + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return n_chars_printed; +} + +static size_t _vprintf_generic_print_signed_dec( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const intmax_t x, const bool flag_minus, const bool flag_plus, + const bool flag_space, const bool flag_zero, const size_t min_field_width, + const size_t precision) { + // Render the digits. + + char digits[DEC_MAX_N_DIGITS + 1]; + const size_t n_digits = _render_unsigned_dec(digits, x < 0 ? -x : x); + + // Calculate width. + + size_t width = n_digits; + if (precision > n_digits) { + width = precision; + } + width += x < 0 || flag_plus || flag_space; // Sign character. + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // Sign. + if (x < 0) { + putc('-', putc_arg); + n_chars_printed++; + } else if (flag_plus) { + putc('+', putc_arg); + n_chars_printed++; + } else if (flag_space) { + putc(' ', putc_arg); + n_chars_printed++; + } + // Leading zeros. + if (precision > n_digits) { + n_chars_printed += _pad_generic(putc, putc_arg, '0', precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (DEC_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +static size_t _vprintf_generic_print_unsigned_oct( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const uintmax_t x, const bool flag_minus, const bool flag_hash, + const bool flag_zero, const size_t min_field_width, + const size_t precision) { + // Render the digits. + + char digits[OCT_MAX_N_DIGITS + 1]; + const size_t n_digits = _render_unsigned_base_p2(digits, OCT_MAX_N_DIGITS, x, + 3, LOWER_HEX_DIGITS); + + // Calculate width. + + const size_t effective_precision = + flag_hash && n_digits + 1 > precision ? n_digits + 1 : precision; + + size_t width = n_digits; + if (effective_precision > n_digits) { + width = effective_precision; + } + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // Leading zeros. + if (effective_precision > n_digits) { + n_chars_printed += + _pad_generic(putc, putc_arg, '0', effective_precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (OCT_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +static size_t _vprintf_generic_print_unsigned_hex( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const uintmax_t x, const bool flag_minus, const bool flag_hash, + const bool flag_zero, const size_t min_field_width, const size_t precision, + const bool is_upper) { + const char *const digit_template = + is_upper ? UPPER_HEX_DIGITS : LOWER_HEX_DIGITS; + const char *const prefix = is_upper ? "0X" : "0x"; + + // Render the digits. + + char digits[HEX_MAX_N_DIGITS + 1]; + const size_t n_digits = + _render_unsigned_base_p2(digits, HEX_MAX_N_DIGITS, x, 4, digit_template); + + // Calculate width. + + size_t width = n_digits; + if (precision > n_digits) { + width = precision; + } + // 0x prefix. + if (flag_hash && x != 0) { + width += 2; + } + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // 0x prefix. + if (flag_hash && x != 0) { + n_chars_printed += _puts_generic(putc, putc_arg, prefix); + } + // Leading zeros. + if (precision > n_digits) { + n_chars_printed += _pad_generic(putc, putc_arg, '0', precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (HEX_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +static size_t _vprintf_generic_print_unsigned_dec( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const uintmax_t x, const bool flag_minus, const bool flag_zero, + const size_t min_field_width, const size_t precision) { + // Render the digits. + + char digits[DEC_MAX_N_DIGITS + 1]; + const size_t n_digits = _render_unsigned_dec(digits, x); + + // Calculate width. + + size_t width = n_digits; + if (precision > n_digits) { + width = precision; + } + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // Leading zeros. + if (precision > n_digits) { + n_chars_printed += _pad_generic(putc, putc_arg, '0', precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (DEC_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +#define READ_ARG_S(LM, AP) \ + ((LM) == LM_NONE ? va_arg(AP, int) \ + : (LM) == LM_HH ? (signed char)va_arg(AP, int) \ + : (LM) == LM_H ? (short)va_arg(AP, int) \ + : (LM) == LM_L ? va_arg(AP, long) \ + : (LM) == LM_LL ? va_arg(AP, long long) \ + : (LM) == LM_J ? va_arg(AP, intmax_t) \ + : (LM) == LM_Z ? va_arg(AP, /* signed size_t = */ long) \ + : (LM) == LM_T ? va_arg(AP, ptrdiff_t) \ + : (__builtin_unreachable(), 0)) + +#define READ_ARG_U(LM, AP) \ + ((LM) == LM_NONE ? va_arg(AP, unsigned) \ + : (LM) == LM_HH ? (unsigned char)va_arg(AP, int) \ + : (LM) == LM_H ? (unsigned short)va_arg(AP, int) \ + : (LM) == LM_L ? va_arg(AP, unsigned long) \ + : (LM) == LM_LL ? va_arg(AP, unsigned long long) \ + : (LM) == LM_J ? va_arg(AP, uintmax_t) \ + : (LM) == LM_Z ? va_arg(AP, size_t) \ + : (LM) == LM_T ? va_arg(AP, /* unsigned ptrdiff_t = */ unsigned long) \ + : (__builtin_unreachable(), 0)) + +int vprintf_generic(const printf_vtable_t *const vtable, void *const vtable_arg, + const char *const restrict format, va_list ap) { + size_t n_chars_printed = 0; + for (const char *restrict c = format; *c;) { + if (*c == '%') { + c++; + + // Flags. + + bool flag_minus = false, flag_plus = false, flag_space = false, + flag_hash = false, flag_zero = false; + + for (;;) { + if (*c == '-') { + c++; + flag_minus = true; + } else if (*c == '+') { + c++; + flag_plus = true; + } else if (*c == ' ') { + c++; + flag_space = true; + } else if (*c == '#') { + c++; + flag_hash = true; + } else if (*c == '0') { + c++; + flag_zero = true; + } else { + break; + } + } + + // Minimum field width. + + bool min_field_width_specified = false; + size_t min_field_width = 0; + if (*c == '*') { + c++; + min_field_width_specified = true; + const int arg = va_arg(ap, int); + if (arg < 0) { + flag_minus = true; + min_field_width = -arg; + } else { + min_field_width = arg; + } + } else if (isdigit(*c)) { + min_field_width_specified = true; + for (; isdigit(*c); c++) { + min_field_width = min_field_width * 10 + (*c - '0'); + } + } + + // Precision. + + bool precision_specified = false, precision_valid = false; + size_t precision = 0; + if (*c == '.') { + c++; + precision_specified = true; + + if (*c == '*') { + c++; + const int arg = va_arg(ap, int); + if (arg >= 0) { + precision_valid = true; + precision = arg; + } + } else { + precision_valid = true; + for (; isdigit(*c); c++) { + precision = precision * 10 + (*c - '0'); + } + } + } + + // Length modifier. + + length_modifier_t length_modifier = LM_NONE; + + switch (*c) { + case 'h': + c++; + if (*c == 'h') { + c++; + length_modifier = LM_HH; + } else { + length_modifier = LM_H; + } + break; + + case 'l': + c++; + if (*c == 'l') { + c++; + length_modifier = LM_LL; + } else { + length_modifier = LM_L; + } + break; + + case 'j': + c++; + length_modifier = LM_J; + break; + + case 'z': + c++; + length_modifier = LM_Z; + break; + + case 't': + c++; + length_modifier = LM_T; + break; + + case 'L': + c++; + length_modifier = LM_UPPER_L; + break; + } + + // Format specifier. + + switch (*c) { + case '%': + c++; + if (flag_minus || flag_plus || flag_space || flag_hash || flag_zero || + min_field_width_specified || precision_specified || + length_modifier != LM_NONE) + __builtin_unreachable(); + vtable->putc('%', vtable_arg); + n_chars_printed++; + break; + + case 'c': + c++; + switch (length_modifier) { + case LM_NONE: + if (flag_hash || flag_zero) + __builtin_unreachable(); + n_chars_printed += + _vprintf_generic_putc(vtable->putc, vtable_arg, va_arg(ap, int), + flag_minus, min_field_width); + break; + + default: + __builtin_unreachable(); + } + break; + + case 's': + c++; + switch (length_modifier) { + case LM_NONE: + if (flag_hash || flag_zero) + __builtin_unreachable(); + n_chars_printed += _vprintf_generic_puts( + vtable->putc, vtable_arg, va_arg(ap, const char *), flag_minus, + min_field_width, precision_valid, precision); + break; + + default: + __builtin_unreachable(); + } + break; + + case 'd': + case 'i': + c++; + if (flag_hash) + __builtin_unreachable(); + n_chars_printed += _vprintf_generic_print_signed_dec( + vtable->putc, vtable_arg, READ_ARG_S(length_modifier, ap), + flag_minus, flag_plus, flag_space, flag_zero, min_field_width, + precision_valid ? precision : 1); + break; + + case 'o': + c++; + n_chars_printed += _vprintf_generic_print_unsigned_oct( + vtable->putc, vtable_arg, READ_ARG_U(length_modifier, ap), + flag_minus, flag_hash, flag_zero, min_field_width, + precision_specified ? precision : 1); + break; + + case 'x': + case 'X': { + const bool is_upper = *c == 'X'; + c++; + n_chars_printed += _vprintf_generic_print_unsigned_hex( + vtable->putc, vtable_arg, READ_ARG_U(length_modifier, ap), + flag_minus, flag_hash, flag_zero, min_field_width, + precision_specified ? precision : 1, is_upper); + break; + } + + case 'u': + c++; + if (flag_hash) + __builtin_unreachable(); + n_chars_printed += _vprintf_generic_print_unsigned_dec( + vtable->putc, vtable_arg, READ_ARG_S(length_modifier, ap), + flag_minus, flag_zero, min_field_width, + precision_valid ? precision : 1); + break; + + case 'n': + c++; + if (flag_minus || flag_plus || flag_space || flag_hash || flag_zero || + min_field_width_specified || precision_specified) + __builtin_unreachable(); + switch (length_modifier) { + case LM_NONE: + *va_arg(ap, int *) = n_chars_printed; + break; + + case LM_HH: + *va_arg(ap, signed char *) = n_chars_printed; + break; + + case LM_H: + *va_arg(ap, short *) = n_chars_printed; + break; + + case LM_L: + *va_arg(ap, long *) = n_chars_printed; + break; + + case LM_LL: + *va_arg(ap, long long *) = n_chars_printed; + break; + + case LM_J: + *va_arg(ap, intmax_t *) = n_chars_printed; + break; + + case LM_Z: + *va_arg(ap, /* signed size_t = */ long *) = n_chars_printed; + break; + + case LM_T: + *va_arg(ap, ptrdiff_t *) = n_chars_printed; + break; + + default: + __builtin_unreachable(); + } + break; + + case 'p': + c++; + switch (length_modifier) { + case LM_NONE: + n_chars_printed += _vprintf_generic_print_unsigned_hex( + vtable->putc, vtable_arg, (uintmax_t)va_arg(ap, void *), + flag_minus, flag_hash, flag_zero, min_field_width, + precision_specified ? precision : 1, false); + break; + + default: + __builtin_unreachable(); + } + break; + + default: + __builtin_unreachable(); + } + } else { + vtable->putc(*c++, vtable_arg); + n_chars_printed++; + } + } + + vtable->finalize(vtable_arg); + return n_chars_printed; +} diff --git a/lab6/c/src/utils/heapq.c b/lab6/c/src/utils/heapq.c new file mode 100644 index 000000000..274f71e07 --- /dev/null +++ b/lab6/c/src/utils/heapq.c @@ -0,0 +1,77 @@ +#include "oscos/utils/heapq.h" + +#include "oscos/libc/string.h" + +// TODO: Check integer overflows. + +static void _heap_sift_up(void *const base, const size_t size, const size_t i, + int (*const compar)(const void *, const void *, + void *), + void *const arg) { + size_t ic = i; + while (ic != 0) { + const size_t ip = (ic - 1) / 2; + void *const pc = (char *)base + ic * size, *const pp = + (char *)base + ip * size; + if (compar(pc, pp, arg) < 0) { + memswp(pc, pp, size); + ic = ip; + } else { + break; + } + } +} + +static void _heap_sift_down( + void *const base, const size_t nmemb, const size_t size, const size_t i, + int (*const compar)(const void *, const void *, void *), void *const arg) { + size_t ic = i, il; + while ((il = ic * 2 + 1) < nmemb) { + const size_t ir = il + 1; + void *const pc = (char *)base + ic * size, + *const pl = (char *)base + il * size, + *const pr = (char *)base + ir * size; + + size_t it; + void *pt; + if (ir < nmemb && compar(pl, pr, arg) >= 0) { + it = ir; + pt = pr; + } else { + it = il; + pt = pl; + } + + if (compar(pt, pc, arg) < 0) { + memswp(pt, pc, size); + ic = it; + } else { + break; + } + } +} + +void heappush(void *const restrict base, const size_t nmemb, const size_t size, + const void *const restrict item, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + memcpy((char *)base + nmemb * size, item, size); + _heap_sift_up(base, size, nmemb, compar, arg); +} + +void heappop(void *const restrict base, const size_t nmemb, const size_t size, + void *const restrict item, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + if (nmemb == 0) + __builtin_unreachable(); + + if (item) { + memcpy(item, base, size); + } + + if (nmemb > 1) { + memcpy(base, (const char *)base + (nmemb - 1) * size, size); + _heap_sift_down(base, nmemb - 1, size, 0, compar, arg); + } +} diff --git a/lab6/c/src/utils/rb.c b/lab6/c/src/utils/rb.c new file mode 100644 index 000000000..391fbbb6e --- /dev/null +++ b/lab6/c/src/utils/rb.c @@ -0,0 +1,83 @@ +#include "oscos/utils/rb.h" + +#include "oscos/libc/string.h" +#include "oscos/mem/malloc.h" + +// TODO: Properly implement a red-black tree. + +const void *rb_search(const rb_node_t *const root, + const void *const restrict key, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + const rb_node_t *curr = root; + while (curr) { + const int compar_result = compar(key, curr->payload, arg); + if (compar_result == 0) + break; + curr = curr->children[compar_result > 0]; + } + + return curr ? curr->payload : NULL; +} + +bool rb_insert(rb_node_t **const root, const size_t size, + const void *const restrict item, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + rb_node_t **curr = root; + while (*curr) { + const int compar_result = compar(item, (*curr)->payload, arg); + if (compar_result == 0) + break; + curr = &(*curr)->children[compar_result > 0]; + } + + if (!*curr) { + *curr = malloc(sizeof(rb_node_t) + size); + if (!*curr) + return false; + + (*curr)->children[0] = (*curr)->children[1] = NULL; + } + + memcpy((*curr)->payload, item, size); + + return true; +} + +static rb_node_t **_rb_minimum(rb_node_t **const node) { + rb_node_t **curr = node; + while ((*curr)->children[0]) { + curr = &(*curr)->children[0]; + } + return curr; +} + +void rb_delete(rb_node_t **const root, const void *const restrict key, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + rb_node_t **curr = root; + while (*curr) { + const int compar_result = compar(key, (*curr)->payload, arg); + if (compar_result == 0) + break; + curr = &(*curr)->children[compar_result > 0]; + } + + rb_node_t *const curr_node = *curr; + if (!curr_node->children[0]) { + *curr = curr_node->children[1]; + } else if (!curr_node->children[1]) { + *curr = curr_node->children[0]; + } else { + rb_node_t **const min_right = _rb_minimum(&curr_node->children[1]), + *const min_right_node = *min_right; + + *min_right = min_right_node->children[1]; + *curr = min_right_node; + min_right_node->children[0] = curr_node->children[0]; + min_right_node->children[1] = curr_node->children[1]; + } + + free(curr_node); +} diff --git a/lab6/c/src/xcpt/default-handler.c b/lab6/c/src/xcpt/default-handler.c new file mode 100644 index 000000000..c06c7dc6e --- /dev/null +++ b/lab6/c/src/xcpt/default-handler.c @@ -0,0 +1,21 @@ +#include + +#include "oscos/console.h" +#include "oscos/libc/inttypes.h" + +#define DUMP_SYS_REG(REGNAME, PAD) \ + do { \ + uint64_t reg_val; \ + __asm__ __volatile__("mrs %0, " #REGNAME : "=r"(reg_val)); \ + \ + console_printf(#REGNAME ": " PAD "0x%.8" PRIx64 "\n", reg_val); \ + } while (0) + +void xcpt_default_handler(const uint64_t vten) { + console_printf("VTEN: 0x%8.1" PRIx64 "\n", vten); + + DUMP_SYS_REG(spsr_el1, ""); + DUMP_SYS_REG(elr_el1, " "); + DUMP_SYS_REG(esr_el1, " "); + console_putc('\n'); +} diff --git a/lab6/c/src/xcpt/irq-handler.c b/lab6/c/src/xcpt/irq-handler.c new file mode 100644 index 000000000..a77f6a17e --- /dev/null +++ b/lab6/c/src/xcpt/irq-handler.c @@ -0,0 +1,43 @@ +#include + +#include "oscos/console.h" +#include "oscos/drivers/board.h" +#include "oscos/drivers/l1ic.h" +#include "oscos/drivers/l2ic.h" +#include "oscos/libc/inttypes.h" +#include "oscos/timer/timeout.h" +#include "oscos/utils/core-id.h" + +void xcpt_irq_handler(void) { + PERIPHERAL_READ_BARRIER(); + + const size_t core_id = get_core_id(); + + for (;;) { + const uint32_t int_src = l1ic_get_int_src(core_id); + + if (int_src == 0) + break; + + if (int_src & INT_L1_SRC_GPU) { + const uint32_t l2_int_src = l2ic_get_pending_irq_0(); + + if (l2_int_src & INT_L2_IRQ_0_SRC_AUX) { + mini_uart_interrupt_handler(); + } else { + console_printf( + "WARN: Received an IRQ from GPU with an unknown source: %" PRIx32 + "\n", + l2_int_src); + } + } else if (int_src & INT_L1_SRC_TIMER1) { + xcpt_core_timer_interrupt_handler(); + } else { + console_printf("WARN: Received an IRQ with an unknown source: %" PRIx32 + "\n", + int_src); + } + } + + PERIPHERAL_WRITE_BARRIER(); +} diff --git a/lab6/c/src/xcpt/svc-handler.S b/lab6/c/src/xcpt/svc-handler.S new file mode 100644 index 000000000..434fe9ab2 --- /dev/null +++ b/lab6/c/src/xcpt/svc-handler.S @@ -0,0 +1,50 @@ +.section ".text" + +xcpt_svc_handler: + // Save lr. + str lr, [sp, -32]! + + // Save spsr_el1 and elr_el1 since they can be clobbered by other ISRs. + mrs x10, spsr_el1 + mrs x11, elr_el1 + stp x10, x11, [sp, 16] + + // Unmask interrupts. + msr daifclr, 0xf + + // Check the system call number. + ubfx x9, x9, 0, 16 + cbnz x9, .Lenosys + cmp x8, 11 + b.hi .Lenosys + + // Table-jump to the system call function. + adr lr, .Lend + adr x9, syscall_table + add x9, x9, x8, lsl 2 + br x9 + +.Lenosys: + bl sys_enosys + +.Lend: + // Store the result into the trap frame. + str x0, [sp, 32] + + // Mask the interrupt since we don't want ISRs to clobber spsr_el1 and + // esr_el1 while we're restoring them. + msr daifset, 0xf + + // Restore spsr_el1 and elr_el1. + ldp x0, x1, [sp, 16] + msr spsr_el1, x0 + msr elr_el1, x1 + + // Restore lr. + ldr lr, [sp], 32 + + ret + +.type xcpt_svc_handler, function +.size xcpt_svc_handler, . - xcpt_svc_handler +.global xcpt_svc_handler diff --git a/lab6/c/src/xcpt/syscall-table.S b/lab6/c/src/xcpt/syscall-table.S new file mode 100644 index 000000000..396f9be11 --- /dev/null +++ b/lab6/c/src/xcpt/syscall-table.S @@ -0,0 +1,17 @@ +.section ".text" + +syscall_table: + b sys_getpid + b sys_uart_read + b sys_uart_write + b sys_exec + b sys_fork + b sys_exit + b sys_mbox_call + b sys_kill + b sys_signal + b sys_signal_kill + b sys_sigreturn + +.size syscall_table, . - syscall_table +.global syscall_table diff --git a/lab6/c/src/xcpt/syscall/enosys.c b/lab6/c/src/xcpt/syscall/enosys.c new file mode 100644 index 000000000..32ae47c8f --- /dev/null +++ b/lab6/c/src/xcpt/syscall/enosys.c @@ -0,0 +1,3 @@ +#include "oscos/uapi/errno.h" + +int sys_enosys(void) { return -ENOSYS; } diff --git a/lab6/c/src/xcpt/syscall/exec.c b/lab6/c/src/xcpt/syscall/exec.c new file mode 100644 index 000000000..b8a10cf2e --- /dev/null +++ b/lab6/c/src/xcpt/syscall/exec.c @@ -0,0 +1,44 @@ +#include +#include + +#include "oscos/initrd.h" +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" + +int sys_exec(const char *const name, char *const argv[const]) { + // TODO: Handle this. + (void)argv; + + if (!initrd_is_init()) { + // Act as if there are no files at all. + return -ENOENT; + } + + const cpio_newc_entry_t *const entry = initrd_find_entry_by_pathname(name); + if (!entry) { + return -ENOENT; + } + + const void *user_program_start; + size_t user_program_len; + + const uint32_t mode = CPIO_NEWC_HEADER_VALUE(entry, mode); + const uint32_t file_type = mode & CPIO_NEWC_MODE_FILE_TYPE_MASK; + if (file_type == CPIO_NEWC_MODE_FILE_TYPE_REG) { + user_program_start = CPIO_NEWC_FILE_DATA(entry); + user_program_len = CPIO_NEWC_FILESIZE(entry); + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_DIR) { + return -EISDIR; + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_LNK) { + // TODO: Handle symbolic link. + return -ELOOP; + } else { // Unknown file type. + // Just treat it as an I/O error. + return -EIO; + } + + exec(user_program_start, user_program_len); + + // If execution reaches here, then exec failed. + return -ENOMEM; +} diff --git a/lab6/c/src/xcpt/syscall/exit.c b/lab6/c/src/xcpt/syscall/exit.c new file mode 100644 index 000000000..d06756508 --- /dev/null +++ b/lab6/c/src/xcpt/syscall/exit.c @@ -0,0 +1,3 @@ +#include "oscos/sched.h" + +void sys_exit(void) { thread_exit(); } diff --git a/lab6/c/src/xcpt/syscall/fork-child-ret.S b/lab6/c/src/xcpt/syscall/fork-child-ret.S new file mode 100644 index 000000000..ae568d02a --- /dev/null +++ b/lab6/c/src/xcpt/syscall/fork-child-ret.S @@ -0,0 +1,29 @@ +.section ".text" + +fork_child_ret: + // Mask the interrupt to prevent spsr_el1 and elr_el1 from being clobbered + // by ISRs. + msr daifset, 0xf + + ldp x0, x1, [sp] + msr spsr_el1, x0 + msr elr_el1, x1 + + mov x0, 0 + ldr x1, [sp, 16 + 0 * 16 + 8] + ldp x2, x3, [sp, 16 + 1 * 16] + ldp x4, x5, [sp, 16 + 2 * 16] + ldp x6, x7, [sp, 16 + 3 * 16] + ldp x8, x9, [sp, 16 + 4 * 16] + ldp x10, x11, [sp, 16 + 5 * 16] + ldp x12, x13, [sp, 16 + 6 * 16] + ldp x14, x15, [sp, 16 + 7 * 16] + ldp x16, x17, [sp, 16 + 8 * 16] + ldp x18, lr, [sp, 16 + 9 * 16] + + add sp, sp, 16 + 10 * 16 + eret + +.type fork_child_ret, function +.size fork_child_ret, . - fork_child_ret +.global fork_child_ret diff --git a/lab6/c/src/xcpt/syscall/fork-impl.c b/lab6/c/src/xcpt/syscall/fork-impl.c new file mode 100644 index 000000000..af40a4c2d --- /dev/null +++ b/lab6/c/src/xcpt/syscall/fork-impl.c @@ -0,0 +1,11 @@ +#include "oscos/uapi/errno.h" + +#include "oscos/sched.h" + +int sys_fork_impl(const extended_trap_frame_t *const trap_frame) { + process_t *const new_process = fork(trap_frame); + if (!new_process) + return -ENOMEM; + + return new_process->id; +} diff --git a/lab6/c/src/xcpt/syscall/fork.S b/lab6/c/src/xcpt/syscall/fork.S new file mode 100644 index 000000000..c9bb17fed --- /dev/null +++ b/lab6/c/src/xcpt/syscall/fork.S @@ -0,0 +1,48 @@ +.section ".text" + +sys_fork: + str lr, [sp, -16]! + + // Save integer context. + mrs x0, tpidr_el1 + stp x19, x20, [x0, 16 + 0 * 16] + stp x21, x22, [x0, 16 + 1 * 16] + stp x23, x24, [x0, 16 + 2 * 16] + stp x25, x26, [x0, 16 + 3 * 16] + stp x27, x28, [x0, 16 + 4 * 16] + str x29, [x0, 16 + 5 * 16] + mrs x1, sp_el0 + str x1, [x0, 16 + 6 * 16 + 8] + + // Save FP/SIMD context. + ldr x0, [x0, 16 + 7 * 16] + stp q0, q1, [x0, 0 * 32] + stp q2, q3, [x0, 1 * 32] + stp q4, q5, [x0, 2 * 32] + stp q6, q7, [x0, 3 * 32] + stp q8, q9, [x0, 4 * 32] + stp q10, q11, [x0, 5 * 32] + stp q12, q13, [x0, 6 * 32] + stp q14, q15, [x0, 7 * 32] + stp q16, q17, [x0, 8 * 32] + stp q18, q19, [x0, 9 * 32] + stp q20, q21, [x0, 10 * 32] + stp q22, q23, [x0, 11 * 32] + stp q24, q25, [x0, 12 * 32] + stp q26, q27, [x0, 13 * 32] + stp q28, q29, [x0, 14 * 32] + stp q30, q31, [x0, 15 * 32] + add x0, x0, 16 * 32 + mrs x1, fpcr + mrs x2, fpsr + stp x1, x2, [x0] + + add x0, sp, 2 * 16 + bl sys_fork_impl + + ldr lr, [sp], 16 + ret + +.type sys_fork, function +.size sys_fork, . - sys_fork +.global sys_fork diff --git a/lab6/c/src/xcpt/syscall/getpid.c b/lab6/c/src/xcpt/syscall/getpid.c new file mode 100644 index 000000000..faa43be0e --- /dev/null +++ b/lab6/c/src/xcpt/syscall/getpid.c @@ -0,0 +1,4 @@ +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" + +int sys_getpid(void) { return current_thread()->process->id; } diff --git a/lab6/c/src/xcpt/syscall/kill.c b/lab6/c/src/xcpt/syscall/kill.c new file mode 100644 index 000000000..924686748 --- /dev/null +++ b/lab6/c/src/xcpt/syscall/kill.c @@ -0,0 +1,17 @@ +#include "oscos/uapi/errno.h" + +#include "oscos/sched.h" + +int sys_kill(const int pid) { + if (pid <= 0) { + kill_all_processes(); + } else { + process_t *const process = get_process_by_id(pid); + if (!process) + return -ESRCH; + + kill_process(process); + } + + return 0; +} diff --git a/lab6/c/src/xcpt/syscall/mbox-call.c b/lab6/c/src/xcpt/syscall/mbox-call.c new file mode 100644 index 000000000..c68a9a092 --- /dev/null +++ b/lab6/c/src/xcpt/syscall/mbox-call.c @@ -0,0 +1,36 @@ +#include +#include + +#include "oscos/drivers/mailbox.h" +#include "oscos/uapi/errno.h" +#include "oscos/utils/critical-section.h" + +static bool _is_valid_mbox_ch(const unsigned char ch) { return ch < 10; } + +static bool _is_valid_mbox_ptr(const unsigned int *const mbox) { + return ((uintptr_t)mbox & 0xf) == 0; +} + +int sys_mbox_call(const unsigned char ch, unsigned int *const mbox) { + // Note on return values: + // The video player treats a return value of 0 as failure and any non-zero + // return value as success. This is confirmed by reverse-engineering the video + // player. This system call therefore follows the protocol used by the video + // player rather than the usual convention since we have to execute the video + // player properly. + + if (!_is_valid_mbox_ch(ch)) + return /* -EINVAL */ 0; + + if (!_is_valid_mbox_ptr(mbox)) + return /* -EINVAL */ 0; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + mailbox_call(mbox, ch); + + CRITICAL_SECTION_LEAVE(daif_val); + + return /* 0 */ 1; +} diff --git a/lab6/c/src/xcpt/syscall/signal-kill.c b/lab6/c/src/xcpt/syscall/signal-kill.c new file mode 100644 index 000000000..ef748018d --- /dev/null +++ b/lab6/c/src/xcpt/syscall/signal-kill.c @@ -0,0 +1,25 @@ +#include + +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" + +static bool _is_valid_signal_num(const int signal) { + return 1 <= signal && signal <= 31; +} + +int sys_signal_kill(const int pid, const int signal) { + if (!_is_valid_signal_num(signal)) + return -EINVAL; + + if (pid < 0) { + deliver_signal_to_all_processes(signal); + } else { + process_t *const process = get_process_by_id(pid); + if (!process) + return -ESRCH; + + deliver_signal(process, signal); + } + + return 0; +} diff --git a/lab6/c/src/xcpt/syscall/signal.c b/lab6/c/src/xcpt/syscall/signal.c new file mode 100644 index 000000000..7c10e087a --- /dev/null +++ b/lab6/c/src/xcpt/syscall/signal.c @@ -0,0 +1,15 @@ +#include + +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" + +static bool _is_valid_signal_num(const int signal) { + return 1 <= signal && signal <= 31; +} + +sighandler_t sys_signal(const int signal, const sighandler_t handler) { + if (!_is_valid_signal_num(signal)) + return (sighandler_t)-EINVAL; + + return set_signal_handler(current_thread()->process, signal, handler); +} diff --git a/lab6/c/src/xcpt/syscall/sigreturn-check.c b/lab6/c/src/xcpt/syscall/sigreturn-check.c new file mode 100644 index 000000000..4abe75a25 --- /dev/null +++ b/lab6/c/src/xcpt/syscall/sigreturn-check.c @@ -0,0 +1,13 @@ +#include "oscos/sched.h" +#include "oscos/xcpt.h" + +void sys_sigreturn_check(void) { + XCPT_MASK_ALL(); + + // Crash the process if it incorrectly calls sys_sigreturn when not handling + // signals. + if (!current_thread()->status.is_handling_signal) + thread_exit(); + + XCPT_UNMASK_ALL(); +} diff --git a/lab6/c/src/xcpt/syscall/sigreturn.S b/lab6/c/src/xcpt/syscall/sigreturn.S new file mode 100644 index 000000000..006502119 --- /dev/null +++ b/lab6/c/src/xcpt/syscall/sigreturn.S @@ -0,0 +1,50 @@ +#include "oscos/utils/save-ctx.S" + +.section ".text" + +sys_sigreturn: + // Discard the trap frame. + + add sp, sp, 12 * 16 + + // Check if the system call is called within a signal handler context. + + bl sys_sigreturn_check + + // Restore FP/SIMD context. + + ldp q30, q31, [sp, 30 * 16] + ldp q28, q29, [sp, 28 * 16] + ldp q26, q27, [sp, 26 * 16] + ldp q24, q25, [sp, 24 * 16] + ldp q22, q23, [sp, 22 * 16] + ldp q20, q21, [sp, 20 * 16] + ldp q18, q19, [sp, 18 * 16] + ldp q16, q17, [sp, 16 * 16] + ldp q14, q15, [sp, 14 * 16] + ldp q12, q13, [sp, 12 * 16] + ldp q10, q11, [sp, 10 * 16] + ldp q8, q9, [sp, 8 * 16] + ldp q6, q7, [sp, 6 * 16] + ldp q4, q5, [sp, 4 * 16] + ldp q2, q3, [sp, 2 * 16] + ldp q0, q1, [sp], (32 * 16) + + ldp x0, x1, [sp], 16 + mrs x0, fpcr + mrs x1, fpsr + + // Restore integer context. + + ldp x29, lr, [sp, 10 * 8] + ldp x27, x28, [sp, 8 * 8] + ldp x25, x26, [sp, 6 * 8] + ldp x23, x24, [sp, 4 * 8] + ldp x21, x22, [sp, 2 * 8] + ldp x19, x20, [sp], (12 * 8) + + ret + +.type sys_sigreturn, function +.size sys_sigreturn, . - sys_sigreturn +.global sys_sigreturn diff --git a/lab6/c/src/xcpt/syscall/uart-read.c b/lab6/c/src/xcpt/syscall/uart-read.c new file mode 100644 index 000000000..e4646bc77 --- /dev/null +++ b/lab6/c/src/xcpt/syscall/uart-read.c @@ -0,0 +1,46 @@ +#include + +#include "oscos/console.h" +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" +#include "oscos/uapi/unistd.h" +#include "oscos/utils/critical-section.h" + +static thread_list_node_t _wait_queue = {.prev = &_wait_queue, + .next = &_wait_queue}; + +ssize_t sys_uart_read(char buf[const], const size_t size) { + size_t n_chars_read; + + for (;;) { + // We must enter critical section here. Otherwise, there will be a race + // condition between thread suspension and read readiness notification, + // whose callback adds the current thread to the run queue. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + n_chars_read = console_read_nonblock(buf, size); + if (n_chars_read != 0) { + CRITICAL_SECTION_LEAVE(daif_val); + break; + } + + thread_t *const curr_thread = current_thread(); + + console_notify_read_ready((void (*)(void *))add_all_threads_to_run_queue, + &_wait_queue); + suspend_to_wait_queue(&_wait_queue); + XCPT_MASK_ALL(); + + if (curr_thread->status.is_waken_up_by_signal) { + curr_thread->status.is_waken_up_by_signal = false; + CRITICAL_SECTION_LEAVE(daif_val); + return -EINTR; + } + + CRITICAL_SECTION_LEAVE(daif_val); + } + + return n_chars_read; +} diff --git a/lab6/c/src/xcpt/syscall/uart-write.c b/lab6/c/src/xcpt/syscall/uart-write.c new file mode 100644 index 000000000..aa23d2722 --- /dev/null +++ b/lab6/c/src/xcpt/syscall/uart-write.c @@ -0,0 +1,45 @@ +#include + +#include "oscos/console.h" +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" +#include "oscos/utils/critical-section.h" + +static thread_list_node_t _wait_queue = {.prev = &_wait_queue, + .next = &_wait_queue}; + +size_t sys_uart_write(const char buf[const], const size_t size) { + size_t n_chars_written; + + for (;;) { + // We must enter critical section here. Otherwise, there will be a race + // condition between thread suspension and write readiness notification, + // whose callback adds the current thread to the run queue. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + n_chars_written = console_write_nonblock(buf, size); + if (n_chars_written != 0) { + CRITICAL_SECTION_LEAVE(daif_val); + break; + } + + thread_t *const curr_thread = current_thread(); + + console_notify_write_ready((void (*)(void *))add_all_threads_to_run_queue, + &_wait_queue); + suspend_to_wait_queue(&_wait_queue); + XCPT_MASK_ALL(); + + if (curr_thread->status.is_waken_up_by_signal) { + curr_thread->status.is_waken_up_by_signal = false; + CRITICAL_SECTION_LEAVE(daif_val); + return -EINTR; + } + + CRITICAL_SECTION_LEAVE(daif_val); + } + + return n_chars_written; +} diff --git a/lab6/c/src/xcpt/task-queue.c b/lab6/c/src/xcpt/task-queue.c new file mode 100644 index 000000000..2101b8827 --- /dev/null +++ b/lab6/c/src/xcpt/task-queue.c @@ -0,0 +1,97 @@ +#include "oscos/xcpt/task-queue.h" + +#include +#include + +#include "oscos/utils/critical-section.h" +#include "oscos/utils/heapq.h" + +#define MAX_N_PENDING_TASKS 16 + +typedef struct { + void (*task)(void *); + void *arg; + int priority; +} pending_task_t; + +static pending_task_t _task_queue_pending_tasks[MAX_N_PENDING_TASKS]; +static size_t _task_queue_n_pending_tasks = 0; +static int _task_queue_curr_task_priority = INT_MIN; + +static int +_task_queue_pending_task_cmp_by_priority(const pending_task_t *const t1, + const pending_task_t *const t2, + void *const _arg) { + (void)_arg; + + // Note that the order is reversed. This is because the heapq module + // implements a min heap, but we want the task with the highest priority to be + // at the front of the priority queue. + + if (t2->priority < t1->priority) + return -1; + if (t2->priority > t1->priority) + return 1; + return 0; +} + +bool task_queue_add_task(void (*const task)(void *), void *const arg, + const int priority) { + if (_task_queue_n_pending_tasks == MAX_N_PENDING_TASKS) + return false; + + const pending_task_t entry = {.task = task, .arg = arg, .priority = priority}; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + heappush(_task_queue_pending_tasks, _task_queue_n_pending_tasks++, + sizeof(pending_task_t), &entry, + (int (*)(const void *, const void *, + void *))_task_queue_pending_task_cmp_by_priority, + NULL); + CRITICAL_SECTION_LEAVE(daif_val); + + return true; +} + +void task_queue_sched(void) { + const int curr_priority = _task_queue_curr_task_priority; + + while (_task_queue_n_pending_tasks > 0 && + _task_queue_pending_tasks[0].priority > + curr_priority) { // There is a pending task of a higher priority. + // Preempt the current task and run the highest-priority pending task. + + // Remove the highest-priority pending task from the priority queue. + // No need to mask interrupts here; this function always runs within an ISR. + + pending_task_t entry; + heappop(_task_queue_pending_tasks, _task_queue_n_pending_tasks--, + sizeof(pending_task_t), &entry, + (int (*)(const void *, const void *, + void *))_task_queue_pending_task_cmp_by_priority, + NULL); + + // Update task priority. + _task_queue_curr_task_priority = entry.priority; + + // Save spsr_el1 and elr_el1, since they can be clobbered. + + uint64_t spsr_val, elr_val; + __asm__ __volatile__("mrs %0, spsr_el1" : "=r"(spsr_val)); + __asm__ __volatile__("mrs %0, elr_el1" : "=r"(elr_val)); + + // Run the task with interrupts enabled. + + XCPT_UNMASK_ALL(); + entry.task(entry.arg); + XCPT_MASK_ALL(); + + // Restore spsr_el1 and elr_el1. + __asm__ __volatile__("msr spsr_el1, %0" : : "r"(spsr_val)); + __asm__ __volatile__("msr elr_el1, %0" : : "r"(elr_val)); + } + + // Restore priority. + _task_queue_curr_task_priority = curr_priority; +} diff --git a/lab6/c/src/xcpt/vector-table.S b/lab6/c/src/xcpt/vector-table.S new file mode 100644 index 000000000..1ab6922ea --- /dev/null +++ b/lab6/c/src/xcpt/vector-table.S @@ -0,0 +1,125 @@ +#include "oscos/utils/save-ctx.S" + +.macro default_vt_entry vten label_name +\label_name: + save_aapcs + mov x0, \vten + bl xcpt_default_handler + load_aapcs + eret + +.type \label_name, function +.size \label_name, . - \label_name + +.align 7 +.endm + +.section ".text" + +.align 11 +xcpt_vector_table: + // Exception from the current EL while using SP_EL0. + + // Synchronous. + default_vt_entry 0x0 xcpt_sync_curr_el_sp_el0_handler + + // IRQ. + default_vt_entry 0x1 xcpt_irq_curr_el_sp_el0_handler + + // FIQ. + default_vt_entry 0x2 xcpt_fiq_curr_el_sp_el0_handler + + // SError. + default_vt_entry 0x3 xcpt_serror_curr_el_sp_el0_handler + + // Exception from the current EL while using SP_ELx. + + // Synchronous. + default_vt_entry 0x4 xcpt_sync_curr_el_sp_elx_handler + + // IRQ. +xcpt_irq_curr_el_sp_elx_handler: + save_aapcs + bl xcpt_irq_handler + bl task_queue_sched + load_aapcs + eret + +.type xcpt_irq_curr_el_sp_elx_handler, function +.size xcpt_irq_curr_el_sp_elx_handler, . - xcpt_irq_curr_el_sp_elx_handler + +.align 7 + + // FIQ. + default_vt_entry 0x6 xcpt_fiq_curr_el_sp_elx_handler + + // SError. + default_vt_entry 0x7 xcpt_serror_curr_el_sp_elx_handler + + // Exception from a lower EL and at least one lower EL is AArch64. + + // Synchronous. +xcpt_sync_lower_el_aarch64_handler: + save_aapcs + + // Check if the exception is caused by svc. + mrs x9, esr_el1 + ubfx x10, x9, 26, 6 + cmp x10, 0x15 + b.ne .Lxcpt_sync_lower_el_aarch64_handler_not_svc + + // The exception is caused by svc. + bl xcpt_svc_handler + b .Lxcpt_sync_lower_el_aarch64_handler_end + +.Lxcpt_sync_lower_el_aarch64_handler_not_svc: + // The exception is not caused by svc. + mov x0, 0x8 + bl xcpt_default_handler + +.Lxcpt_sync_lower_el_aarch64_handler_end: + bl handle_signals + load_aapcs + eret + +.type xcpt_sync_lower_el_aarch64_handler, function +.size xcpt_sync_lower_el_aarch64_handler, . - xcpt_sync_lower_el_aarch64_handler + +.align 7 + + // IRQ. +xcpt_irq_lower_el_aarch64_handler: + save_aapcs + bl xcpt_irq_handler + bl task_queue_sched + bl handle_signals + load_aapcs + eret + +.type xcpt_irq_lower_el_aarch64_handler, function +.size xcpt_irq_lower_el_aarch64_handler, . - xcpt_irq_lower_el_aarch64_handler + +.align 7 + + // FIQ. + default_vt_entry 0xa xcpt_fiq_lower_el_aarch64_handler + + // SError. + default_vt_entry 0xb xcpt_serror_lower_el_aarch64_handler + + // Exception from a lower EL and all lower ELs are AArch32. + + // Synchronous. + default_vt_entry 0xc xcpt_sync_lower_el_aarch32_handler + + // IRQ. + default_vt_entry 0xd xcpt_irq_lower_el_aarch32_handler + + // FIQ. + default_vt_entry 0xe xcpt_fiq_lower_el_aarch32_handler + + // SError. + default_vt_entry 0xf xcpt_serror_lower_el_aarch32_handler + +.size xcpt_vector_table, . - xcpt_vector_table +.global xcpt_vector_table diff --git a/lab6/c/src/xcpt/xcpt.c b/lab6/c/src/xcpt/xcpt.c new file mode 100644 index 000000000..05b558b38 --- /dev/null +++ b/lab6/c/src/xcpt/xcpt.c @@ -0,0 +1,7 @@ +#include "oscos/xcpt.h" + +extern char xcpt_vector_table[]; + +void xcpt_set_vector_table(void) { + __asm__ __volatile__("msr vbar_el1, %0" : : "r"(xcpt_vector_table)); +} diff --git a/lab6/tests/.gitignore b/lab6/tests/.gitignore new file mode 100644 index 000000000..30d29c406 --- /dev/null +++ b/lab6/tests/.gitignore @@ -0,0 +1,2 @@ +/initramfs.cpio +/bcm2710-rpi-3-b-plus.dtb diff --git a/lab6/tests/build-initrd-video-player.sh b/lab6/tests/build-initrd-video-player.sh new file mode 100755 index 000000000..815b728a1 --- /dev/null +++ b/lab6/tests/build-initrd-video-player.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +rm -f initramfs.cpio +wget https://oscapstone.github.io/_downloads/58c515e3041658a045033c8e56ecff4c/initramfs.cpio diff --git a/lab6/tests/build-initrd.sh b/lab6/tests/build-initrd.sh new file mode 100755 index 000000000..fb15ec3b2 --- /dev/null +++ b/lab6/tests/build-initrd.sh @@ -0,0 +1,19 @@ +#!/bin/sh -e + +if [ -e rootfs ]; then + rm -r rootfs +fi + +progs='syscall_test' + +mkdir rootfs +for prog in $progs; do + make -C "user-program/$prog" PROFILE=RELEASE + cp "user-program/$prog/build/RELEASE/$prog.img" "rootfs/$prog" +done + +cd rootfs +find . -mindepth 1 | cpio -o -H newc > ../initramfs.cpio + +cd .. +rm -r rootfs diff --git a/lab6/tests/get-dtb.sh b/lab6/tests/get-dtb.sh new file mode 100755 index 000000000..12bb4bd6d --- /dev/null +++ b/lab6/tests/get-dtb.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +dtb_url=https://github.com/raspberrypi/firmware/raw/master/boot/bcm2710-rpi-3-b-plus.dtb + +wget "$dtb_url" diff --git a/lab6/tests/user-program/libc/.gitignore b/lab6/tests/user-program/libc/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/lab6/tests/user-program/libc/.gitignore @@ -0,0 +1 @@ +/build diff --git a/lab6/tests/user-program/libc/Makefile b/lab6/tests/user-program/libc/Makefile new file mode 100644 index 000000000..f21080034 --- /dev/null +++ b/lab6/tests/user-program/libc/Makefile @@ -0,0 +1,61 @@ +DEFAULT_PROFILE = DEBUG +SRC_DIR = src +BUILD_DIR = build + +AR = aarch64-linux-gnu-ar -rv +AS = aarch64-linux-gnu-as +CC = aarch64-linux-gnu-gcc +CPP = aarch64-linux-gnu-cpp + +CPPFLAGS_BASE = -Iinclude + +ASFLAGS_DEBUG = -g + +CFLAGS_BASE = -std=c17 -pedantic-errors -Wall -Wextra -ffreestanding \ + -mcpu=cortex-a53 -mno-outline-atomics -mstrict-align +CFLAGS_DEBUG = -g +CFLAGS_RELEASE = -O3 -flto + +OBJS = start ctype errno mbox signal stdio stdlib string unistd \ + unistd/syscall __detail/utils/fmt + +# ------------------------------------------------------------------------------ + +PROFILE = $(DEFAULT_PROFILE) +OUT_DIR = $(BUILD_DIR)/$(PROFILE) + +CPPFLAGS = $(CPPFLAGS_BASE) $(CPPFLAGS_$(PROFILE)) +ASFLAGS = $(ASFLAGS_BASE) $(ASFLAGS_$(PROFILE)) +CFLAGS = $(CFLAGS_BASE) $(CFLAGS_$(PROFILE)) + +OBJ_PATHS = $(addprefix $(OUT_DIR)/,$(addsuffix .o,$(OBJS))) + +# ------------------------------------------------------------------------------ + +.PHONY: all clean-profile clean + +all: $(OUT_DIR)/libc.a + +$(OUT_DIR)/libc.a: $(OBJ_PATHS) + @mkdir -p $(@D) + $(AR) $@ $^ + +$(OUT_DIR)/%.o: $(SRC_DIR)/%.c + @mkdir -p $(@D) + $(CC) $(CPPFLAGS) $(CFLAGS) -c -MMD -MP -MF $(OUT_DIR)/$*.d $< -o $@ + +$(OUT_DIR)/%.o: $(OUT_DIR)/%.s + @mkdir -p $(@D) + $(AS) $(ASFLAGS) $^ -o $@ + +$(OUT_DIR)/%.s: $(SRC_DIR)/%.S + @mkdir -p $(@D) + $(CPP) $(CPPFLAGS) $^ -o $@ + +clean-profile: + $(RM) -r $(OUT_DIR) + +clean: + $(RM) -r $(BUILD_DIR) + +-include $(OUT_DIR)/*.d diff --git a/lab6/tests/user-program/libc/include/__detail/utils/fmt.h b/lab6/tests/user-program/libc/include/__detail/utils/fmt.h new file mode 100644 index 000000000..36b328eb0 --- /dev/null +++ b/lab6/tests/user-program/libc/include/__detail/utils/fmt.h @@ -0,0 +1,15 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC___DETAIL_UTILS_FMT_H +#define OSCOS_USER_PROGRAM_LIBC___DETAIL_UTILS_FMT_H + +#include + +typedef struct { + void (*putc)(unsigned char, void *); + void (*finalize)(void *); +} printf_vtable_t; + +int __vprintf_generic(const printf_vtable_t *vtable, void *arg, + const char *restrict format, va_list ap) + __attribute__((format(printf, 3, 0))); + +#endif diff --git a/lab6/tests/user-program/libc/include/ctype.h b/lab6/tests/user-program/libc/include/ctype.h new file mode 100644 index 000000000..2f1cc43df --- /dev/null +++ b/lab6/tests/user-program/libc/include/ctype.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_CTYPE_H +#define OSCOS_USER_PROGRAM_LIBC_CTYPE_H + +int isdigit(int c); + +#endif diff --git a/lab6/tests/user-program/libc/include/errno.h b/lab6/tests/user-program/libc/include/errno.h new file mode 100644 index 000000000..e525f1be3 --- /dev/null +++ b/lab6/tests/user-program/libc/include/errno.h @@ -0,0 +1,9 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_ERRNO_H +#define OSCOS_USER_PROGRAM_LIBC_ERRNO_H + +// Error number definitions. +#include "oscos-uapi/errno.h" + +extern int errno; + +#endif diff --git a/lab6/tests/user-program/libc/include/mbox.h b/lab6/tests/user-program/libc/include/mbox.h new file mode 100644 index 000000000..5f5e6fd4d --- /dev/null +++ b/lab6/tests/user-program/libc/include/mbox.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_MBOX_H +#define OSCOS_USER_PROGRAM_LIBC_MBOX_H + +int mbox_call(unsigned char ch, unsigned int *mbox); + +#endif diff --git a/lab6/tests/user-program/libc/include/oscos-uapi b/lab6/tests/user-program/libc/include/oscos-uapi new file mode 120000 index 000000000..ce2ca8c4d --- /dev/null +++ b/lab6/tests/user-program/libc/include/oscos-uapi @@ -0,0 +1 @@ +../../../../c/include/oscos/uapi/ \ No newline at end of file diff --git a/lab6/tests/user-program/libc/include/signal.h b/lab6/tests/user-program/libc/include/signal.h new file mode 100644 index 000000000..866dc92c2 --- /dev/null +++ b/lab6/tests/user-program/libc/include/signal.h @@ -0,0 +1,9 @@ +#include "unistd.h" + +#include "oscos-uapi/signal.h" + +int kill(pid_t pid); + +sighandler_t signal(int signal, sighandler_t handler); + +int signal_kill(pid_t pid, int signal); diff --git a/lab6/tests/user-program/libc/include/stdio.h b/lab6/tests/user-program/libc/include/stdio.h new file mode 100644 index 000000000..ac998646d --- /dev/null +++ b/lab6/tests/user-program/libc/include/stdio.h @@ -0,0 +1,12 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_STDIO_H +#define OSCOS_USER_PROGRAM_LIBC_STDIO_H + +#include + +int printf(const char *restrict format, ...) + __attribute__((format(printf, 1, 2))); + +int vprintf(const char *restrict format, va_list ap) + __attribute__((format(printf, 1, 0))); + +#endif diff --git a/lab6/tests/user-program/libc/include/stdlib.h b/lab6/tests/user-program/libc/include/stdlib.h new file mode 100644 index 000000000..91d4e865d --- /dev/null +++ b/lab6/tests/user-program/libc/include/stdlib.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_STDLIB_H +#define OSCOS_USER_PROGRAM_LIBC_STDLIB_H + +#include + +noreturn void exit(int status); + +#endif diff --git a/lab6/tests/user-program/libc/include/string.h b/lab6/tests/user-program/libc/include/string.h new file mode 100644 index 000000000..ace3e78d1 --- /dev/null +++ b/lab6/tests/user-program/libc/include/string.h @@ -0,0 +1,25 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_STRING_H +#define OSCOS_USER_PROGRAM_LIBC_STRING_H + +#include + +int memcmp(const void *s1, const void *s2, size_t n); +void *memset(void *s, int c, size_t n); +void *memcpy(void *restrict dest, const void *restrict src, size_t n); +void *memmove(void *dest, const void *src, size_t n); + +int strcmp(const char *s1, const char *s2); +int strncmp(const char *s1, const char *s2, size_t n); + +size_t strlen(const char *s); + +// Extensions. + +/// \brief Swap two non-overlapping blocks of memory. +/// +/// \param xs The pointer to the beginning of the first block of memory. +/// \param ys The pointer to the beginning of the second block of memory. +/// \param n The size of the memory blocks. +void memswp(void *restrict xs, void *restrict ys, size_t n); + +#endif diff --git a/lab6/tests/user-program/libc/include/sys/syscall.h b/lab6/tests/user-program/libc/include/sys/syscall.h new file mode 100644 index 000000000..65efd73e5 --- /dev/null +++ b/lab6/tests/user-program/libc/include/sys/syscall.h @@ -0,0 +1 @@ +#include "../oscos-uapi/sys/syscall.h" diff --git a/lab6/tests/user-program/libc/include/unistd.h b/lab6/tests/user-program/libc/include/unistd.h new file mode 100644 index 000000000..01ea5ac4b --- /dev/null +++ b/lab6/tests/user-program/libc/include/unistd.h @@ -0,0 +1,21 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_UNISTD_H +#define OSCOS_USER_PROGRAM_LIBC_UNISTD_H + +#include + +#include "oscos-uapi/unistd.h" + +typedef int pid_t; + +pid_t getpid(void); + +ssize_t uart_read(void *buf, size_t count); +ssize_t uart_write(const void *buf, size_t count); + +int exec(const char *pathname, char *const argv[]); + +pid_t fork(void); + +long syscall(long number, ...); + +#endif diff --git a/lab6/tests/user-program/libc/src/__detail/utils/fmt.c b/lab6/tests/user-program/libc/src/__detail/utils/fmt.c new file mode 100644 index 000000000..f33b4067d --- /dev/null +++ b/lab6/tests/user-program/libc/src/__detail/utils/fmt.c @@ -0,0 +1,654 @@ +#include "__detail/utils/fmt.h" + +#include +#include +#include + +#include "ctype.h" +#include "string.h" + +#define OCT_MAX_N_DIGITS 22 +#define DEC_MAX_N_DIGITS 20 +#define HEX_MAX_N_DIGITS 16 + +typedef enum { + LM_NONE, + LM_HH, + LM_H, + LM_L, + LM_LL, + LM_J, + LM_Z, + LM_T, + LM_UPPER_L +} length_modifier_t; + +static const char LOWER_HEX_DIGITS[16] = "0123456789abcdef"; +static const char UPPER_HEX_DIGITS[16] = "0123456789ABCDEF"; + +static size_t +_render_unsigned_dec(char digits[const static DEC_MAX_N_DIGITS + 1], + const uintmax_t x) { + digits[DEC_MAX_N_DIGITS] = '\0'; + + size_t n_digits = 0; + for (uintmax_t xc = x; xc > 0; xc /= 10) { + digits[DEC_MAX_N_DIGITS - ++n_digits] = '0' + xc % 10; + } + + return n_digits; +} + +static size_t +_render_unsigned_base_p2(char *const restrict digits, const size_t digits_len, + const uintmax_t x, const size_t log2_base, + const char *const restrict digit_template) { + const uintmax_t mask = (1 << log2_base) - 1; + + digits[digits_len] = '\0'; + + size_t n_digits = 0; + for (uintmax_t xc = x; xc > 0; xc >>= log2_base) { + digits[digits_len - ++n_digits] = digit_template[xc & mask]; + } + + return n_digits; +} + +static size_t _puts_generic(void (*const putc)(unsigned char, void *), + void *const putc_arg, const char *const s) { + size_t n_chars_printed = 0; + for (const char *c = s; *c; c++) { + putc(*c, putc_arg); + n_chars_printed++; + } + return n_chars_printed; +} + +static size_t _puts_limited_generic(void (*const putc)(unsigned char, void *), + void *const putc_arg, const char *const s, + const size_t limit) { + size_t n_chars_printed = 0, i = 0; + for (const char *c = s; i < limit && *c; i++, c++) { + putc(*c, putc_arg); + n_chars_printed++; + } + return n_chars_printed; +} + +static size_t _pad_generic(void (*const putc)(unsigned char, void *), + void *const putc_arg, const unsigned char pad, + const size_t width) { + for (size_t i = 0; i < width; i++) { + putc(pad, putc_arg); + } + return width; +} + +static size_t _vprintf_generic_putc(void (*const putc)(unsigned char, void *), + void *const putc_arg, const unsigned char c, + const bool flag_minus, + const size_t min_field_width) { + size_t n_chars_printed = 0; + + if (flag_minus) { + putc(c, putc_arg); + n_chars_printed++; + + if (min_field_width > 1) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', min_field_width - 1); + } + } else { + if (min_field_width > 1) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', min_field_width - 1); + } + + putc(c, putc_arg); + n_chars_printed++; + } + + return n_chars_printed; +} + +static size_t _vprintf_generic_puts(void (*const putc)(unsigned char, void *), + void *const putc_arg, const char *const s, + const bool flag_minus, + const size_t min_field_width, + const bool precision_valid, + const size_t precision) { + const size_t len = strlen(s); + + // Calculate width. + + const size_t width = precision_valid && precision < len ? precision : len; + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + // The string. + if (precision_valid) { + n_chars_printed += _puts_limited_generic(putc, putc_arg, s, precision); + } else { + n_chars_printed += _puts_generic(putc, putc_arg, s); + } + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return n_chars_printed; +} + +static size_t _vprintf_generic_print_signed_dec( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const intmax_t x, const bool flag_minus, const bool flag_plus, + const bool flag_space, const bool flag_zero, const size_t min_field_width, + const size_t precision) { + // Render the digits. + + char digits[DEC_MAX_N_DIGITS + 1]; + const size_t n_digits = _render_unsigned_dec(digits, x < 0 ? -x : x); + + // Calculate width. + + size_t width = n_digits; + if (precision > n_digits) { + width = precision; + } + width += x < 0 || flag_plus || flag_space; // Sign character. + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // Sign. + if (x < 0) { + putc('-', putc_arg); + n_chars_printed++; + } else if (flag_plus) { + putc('+', putc_arg); + n_chars_printed++; + } else if (flag_space) { + putc(' ', putc_arg); + n_chars_printed++; + } + // Leading zeros. + if (precision > n_digits) { + n_chars_printed += _pad_generic(putc, putc_arg, '0', precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (DEC_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +static size_t _vprintf_generic_print_unsigned_oct( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const uintmax_t x, const bool flag_minus, const bool flag_hash, + const bool flag_zero, const size_t min_field_width, + const size_t precision) { + // Render the digits. + + char digits[OCT_MAX_N_DIGITS + 1]; + const size_t n_digits = _render_unsigned_base_p2(digits, OCT_MAX_N_DIGITS, x, + 3, LOWER_HEX_DIGITS); + + // Calculate width. + + const size_t effective_precision = + flag_hash && n_digits + 1 > precision ? n_digits + 1 : precision; + + size_t width = n_digits; + if (effective_precision > n_digits) { + width = effective_precision; + } + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // Leading zeros. + if (effective_precision > n_digits) { + n_chars_printed += + _pad_generic(putc, putc_arg, '0', effective_precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (OCT_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +static size_t _vprintf_generic_print_unsigned_hex( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const uintmax_t x, const bool flag_minus, const bool flag_hash, + const bool flag_zero, const size_t min_field_width, const size_t precision, + const bool is_upper) { + const char *const digit_template = + is_upper ? UPPER_HEX_DIGITS : LOWER_HEX_DIGITS; + const char *const prefix = is_upper ? "0X" : "0x"; + + // Render the digits. + + char digits[HEX_MAX_N_DIGITS + 1]; + const size_t n_digits = + _render_unsigned_base_p2(digits, HEX_MAX_N_DIGITS, x, 4, digit_template); + + // Calculate width. + + size_t width = n_digits; + if (precision > n_digits) { + width = precision; + } + // 0x prefix. + if (flag_hash && x != 0) { + width += 2; + } + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // 0x prefix. + if (flag_hash && x != 0) { + n_chars_printed += _puts_generic(putc, putc_arg, prefix); + } + // Leading zeros. + if (precision > n_digits) { + n_chars_printed += _pad_generic(putc, putc_arg, '0', precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (HEX_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +static size_t _vprintf_generic_print_unsigned_dec( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const uintmax_t x, const bool flag_minus, const bool flag_zero, + const size_t min_field_width, const size_t precision) { + // Render the digits. + + char digits[DEC_MAX_N_DIGITS + 1]; + const size_t n_digits = _render_unsigned_dec(digits, x); + + // Calculate width. + + size_t width = n_digits; + if (precision > n_digits) { + width = precision; + } + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // Leading zeros. + if (precision > n_digits) { + n_chars_printed += _pad_generic(putc, putc_arg, '0', precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (DEC_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +#define READ_ARG_S(LM, AP) \ + ((LM) == LM_NONE ? va_arg(AP, int) \ + : (LM) == LM_HH ? (signed char)va_arg(AP, int) \ + : (LM) == LM_H ? (short)va_arg(AP, int) \ + : (LM) == LM_L ? va_arg(AP, long) \ + : (LM) == LM_LL ? va_arg(AP, long long) \ + : (LM) == LM_J ? va_arg(AP, intmax_t) \ + : (LM) == LM_Z ? va_arg(AP, /* signed size_t = */ long) \ + : (LM) == LM_T ? va_arg(AP, ptrdiff_t) \ + : (__builtin_unreachable(), 0)) + +#define READ_ARG_U(LM, AP) \ + ((LM) == LM_NONE ? va_arg(AP, unsigned) \ + : (LM) == LM_HH ? (unsigned char)va_arg(AP, int) \ + : (LM) == LM_H ? (unsigned short)va_arg(AP, int) \ + : (LM) == LM_L ? va_arg(AP, unsigned long) \ + : (LM) == LM_LL ? va_arg(AP, unsigned long long) \ + : (LM) == LM_J ? va_arg(AP, uintmax_t) \ + : (LM) == LM_Z ? va_arg(AP, size_t) \ + : (LM) == LM_T ? va_arg(AP, /* unsigned ptrdiff_t = */ unsigned long) \ + : (__builtin_unreachable(), 0)) + +int __vprintf_generic(const printf_vtable_t *const vtable, + void *const vtable_arg, const char *const restrict format, + va_list ap) { + size_t n_chars_printed = 0; + for (const char *restrict c = format; *c;) { + if (*c == '%') { + c++; + + // Flags. + + bool flag_minus = false, flag_plus = false, flag_space = false, + flag_hash = false, flag_zero = false; + + for (;;) { + if (*c == '-') { + c++; + flag_minus = true; + } else if (*c == '+') { + c++; + flag_plus = true; + } else if (*c == ' ') { + c++; + flag_space = true; + } else if (*c == '#') { + c++; + flag_hash = true; + } else if (*c == '0') { + c++; + flag_zero = true; + } else { + break; + } + } + + // Minimum field width. + + bool min_field_width_specified = false; + size_t min_field_width = 0; + if (*c == '*') { + c++; + min_field_width_specified = true; + const int arg = va_arg(ap, int); + if (arg < 0) { + flag_minus = true; + min_field_width = -arg; + } else { + min_field_width = arg; + } + } else if (isdigit(*c)) { + min_field_width_specified = true; + for (; isdigit(*c); c++) { + min_field_width = min_field_width * 10 + (*c - '0'); + } + } + + // Precision. + + bool precision_specified = false, precision_valid = false; + size_t precision = 0; + if (*c == '.') { + c++; + precision_specified = true; + + if (*c == '*') { + c++; + const int arg = va_arg(ap, int); + if (arg >= 0) { + precision_valid = true; + precision = arg; + } + } else { + precision_valid = true; + for (; isdigit(*c); c++) { + precision = precision * 10 + (*c - '0'); + } + } + } + + // Length modifier. + + length_modifier_t length_modifier = LM_NONE; + + switch (*c) { + case 'h': + c++; + if (*c == 'h') { + c++; + length_modifier = LM_HH; + } else { + length_modifier = LM_H; + } + break; + + case 'l': + c++; + if (*c == 'l') { + c++; + length_modifier = LM_LL; + } else { + length_modifier = LM_L; + } + break; + + case 'j': + c++; + length_modifier = LM_J; + break; + + case 'z': + c++; + length_modifier = LM_Z; + break; + + case 't': + c++; + length_modifier = LM_T; + break; + + case 'L': + c++; + length_modifier = LM_UPPER_L; + break; + } + + // Format specifier. + + switch (*c) { + case '%': + c++; + if (flag_minus || flag_plus || flag_space || flag_hash || flag_zero || + min_field_width_specified || precision_specified || + length_modifier != LM_NONE) + __builtin_unreachable(); + vtable->putc('%', vtable_arg); + n_chars_printed++; + break; + + case 'c': + c++; + switch (length_modifier) { + case LM_NONE: + if (flag_hash || flag_zero) + __builtin_unreachable(); + n_chars_printed += + _vprintf_generic_putc(vtable->putc, vtable_arg, va_arg(ap, int), + flag_minus, min_field_width); + break; + + default: + __builtin_unreachable(); + } + break; + + case 's': + c++; + switch (length_modifier) { + case LM_NONE: + if (flag_hash || flag_zero) + __builtin_unreachable(); + n_chars_printed += _vprintf_generic_puts( + vtable->putc, vtable_arg, va_arg(ap, const char *), flag_minus, + min_field_width, precision_valid, precision); + break; + + default: + __builtin_unreachable(); + } + break; + + case 'd': + case 'i': + c++; + if (flag_hash) + __builtin_unreachable(); + n_chars_printed += _vprintf_generic_print_signed_dec( + vtable->putc, vtable_arg, READ_ARG_S(length_modifier, ap), + flag_minus, flag_plus, flag_space, flag_zero, min_field_width, + precision_valid ? precision : 1); + break; + + case 'o': + c++; + n_chars_printed += _vprintf_generic_print_unsigned_oct( + vtable->putc, vtable_arg, READ_ARG_U(length_modifier, ap), + flag_minus, flag_hash, flag_zero, min_field_width, + precision_specified ? precision : 1); + break; + + case 'x': + case 'X': { + const bool is_upper = *c == 'X'; + c++; + n_chars_printed += _vprintf_generic_print_unsigned_hex( + vtable->putc, vtable_arg, READ_ARG_U(length_modifier, ap), + flag_minus, flag_hash, flag_zero, min_field_width, + precision_specified ? precision : 1, is_upper); + break; + } + + case 'u': + c++; + if (flag_hash) + __builtin_unreachable(); + n_chars_printed += _vprintf_generic_print_unsigned_dec( + vtable->putc, vtable_arg, READ_ARG_S(length_modifier, ap), + flag_minus, flag_zero, min_field_width, + precision_valid ? precision : 1); + break; + + case 'n': + c++; + if (flag_minus || flag_plus || flag_space || flag_hash || flag_zero || + min_field_width_specified || precision_specified) + __builtin_unreachable(); + switch (length_modifier) { + case LM_NONE: + *va_arg(ap, int *) = n_chars_printed; + break; + + case LM_HH: + *va_arg(ap, signed char *) = n_chars_printed; + break; + + case LM_H: + *va_arg(ap, short *) = n_chars_printed; + break; + + case LM_L: + *va_arg(ap, long *) = n_chars_printed; + break; + + case LM_LL: + *va_arg(ap, long long *) = n_chars_printed; + break; + + case LM_J: + *va_arg(ap, intmax_t *) = n_chars_printed; + break; + + case LM_Z: + *va_arg(ap, /* signed size_t = */ long *) = n_chars_printed; + break; + + case LM_T: + *va_arg(ap, ptrdiff_t *) = n_chars_printed; + break; + + default: + __builtin_unreachable(); + } + break; + + case 'p': + c++; + switch (length_modifier) { + case LM_NONE: + n_chars_printed += _vprintf_generic_print_unsigned_hex( + vtable->putc, vtable_arg, (uintmax_t)va_arg(ap, void *), + flag_minus, flag_hash, flag_zero, min_field_width, + precision_specified ? precision : 1, false); + break; + + default: + __builtin_unreachable(); + } + break; + + default: + __builtin_unreachable(); + } + } else { + vtable->putc(*c++, vtable_arg); + n_chars_printed++; + } + } + + vtable->finalize(vtable_arg); + return n_chars_printed; +} diff --git a/lab6/tests/user-program/libc/src/ctype.c b/lab6/tests/user-program/libc/src/ctype.c new file mode 100644 index 000000000..eb145ab96 --- /dev/null +++ b/lab6/tests/user-program/libc/src/ctype.c @@ -0,0 +1,3 @@ +#include "ctype.h" + +int isdigit(const int c) { return '0' <= c && c <= '9'; } diff --git a/lab6/tests/user-program/libc/src/errno.c b/lab6/tests/user-program/libc/src/errno.c new file mode 100644 index 000000000..197b93086 --- /dev/null +++ b/lab6/tests/user-program/libc/src/errno.c @@ -0,0 +1,3 @@ +#include "errno.h" + +int errno = 0; diff --git a/lab6/tests/user-program/libc/src/mbox.c b/lab6/tests/user-program/libc/src/mbox.c new file mode 100644 index 000000000..450968f1b --- /dev/null +++ b/lab6/tests/user-program/libc/src/mbox.c @@ -0,0 +1,8 @@ +#include "mbox.h" + +#include "sys/syscall.h" +#include "unistd.h" + +int mbox_call(const unsigned char ch, unsigned int *const mbox) { + return syscall(SYS_mbox_call, ch, mbox); +} diff --git a/lab6/tests/user-program/libc/src/signal.c b/lab6/tests/user-program/libc/src/signal.c new file mode 100644 index 000000000..fdc717fa8 --- /dev/null +++ b/lab6/tests/user-program/libc/src/signal.c @@ -0,0 +1,13 @@ +#include "signal.h" + +#include "sys/syscall.h" + +int kill(const pid_t pid) { return syscall(SYS_kill, pid); } + +sighandler_t signal(const int signal, const sighandler_t handler) { + return (sighandler_t)syscall(SYS_signal, signal, handler); +} + +int signal_kill(const pid_t pid, const int signal) { + return syscall(SYS_signal_kill, pid, signal); +} diff --git a/lab6/tests/user-program/libc/src/start.S b/lab6/tests/user-program/libc/src/start.S new file mode 100644 index 000000000..2dc50db90 --- /dev/null +++ b/lab6/tests/user-program/libc/src/start.S @@ -0,0 +1,15 @@ +.section ".text._start" + +_start: + // We don't need to clear the .bss section here, since the kernel does this + // for us. + + // Call the main function. + bl main + + // Terminate the program. + b exit + +.size _start, . - _start +.type _start, function +.global _start diff --git a/lab6/tests/user-program/libc/src/stdio.c b/lab6/tests/user-program/libc/src/stdio.c new file mode 100644 index 000000000..87e894a80 --- /dev/null +++ b/lab6/tests/user-program/libc/src/stdio.c @@ -0,0 +1,31 @@ +#include "stdio.h" + +#include "__detail/utils/fmt.h" +#include "unistd.h" + +static void _printf_putc(const unsigned char c, void *const _arg) { + (void)_arg; + + // Very inefficient, but it works. + while (uart_write(&c, 1) != 1) + ; +} + +static void _printf_finalize(void *const _arg) { (void)_arg; } + +static const printf_vtable_t _printf_vtable = {.putc = _printf_putc, + .finalize = _printf_finalize}; + +int printf(const char *const restrict format, ...) { + va_list ap; + va_start(ap, format); + + const int result = vprintf(format, ap); + + va_end(ap); + return result; +} + +int vprintf(const char *const restrict format, va_list ap) { + return __vprintf_generic(&_printf_vtable, NULL, format, ap); +} diff --git a/lab6/tests/user-program/libc/src/stdlib.c b/lab6/tests/user-program/libc/src/stdlib.c new file mode 100644 index 000000000..6ca25fa10 --- /dev/null +++ b/lab6/tests/user-program/libc/src/stdlib.c @@ -0,0 +1,9 @@ +#include "stdlib.h" + +#include "sys/syscall.h" +#include "unistd.h" + +void exit(const int status) { + syscall(SYS_exit, status); + __builtin_unreachable(); +} diff --git a/lab6/tests/user-program/libc/src/string.c b/lab6/tests/user-program/libc/src/string.c new file mode 100644 index 000000000..567e116ec --- /dev/null +++ b/lab6/tests/user-program/libc/src/string.c @@ -0,0 +1,99 @@ +#include "string.h" + +__attribute__((used)) int memcmp(const void *const s1, const void *const s2, + const size_t n) { + const unsigned char *const s1_c = s1, *const s2_c = s2; + + for (size_t i = 0; i < n; i++) { + const int diff = (int)s1_c[i] - s2_c[i]; + if (diff != 0) + return diff; + } + + return 0; +} + +__attribute__((used)) void *memset(void *const s, const int c, const size_t n) { + unsigned char *const s_c = s; + + for (size_t i = 0; i < n; i++) { + s_c[i] = c; + } + + return s; +} + +static void __memmove_forward(unsigned char *const dest, + const unsigned char *const src, const size_t n) { + for (size_t i = 0; i < n; i++) { + dest[i] = src[i]; + } +} + +static void __memmove_backward(unsigned char *const dest, + const unsigned char *const src, const size_t n) { + for (size_t ip1 = n; ip1 > 0; ip1--) { + const size_t i = ip1 - 1; + dest[i] = src[i]; + } +} + +__attribute__((used)) void *memcpy(void *const restrict dest, + const void *const restrict src, + const size_t n) { + __memmove_forward(dest, src, n); + return dest; +} + +__attribute__((used)) void *memmove(void *const dest, const void *const src, + const size_t n) { + if (dest < src) { + __memmove_forward(dest, src, n); + } else if (dest > src) { + __memmove_backward(dest, src, n); + } + + return dest; +} + +int strcmp(const char *const s1, const char *const s2) { + for (const unsigned char *c1 = (const unsigned char *)s1, + *c2 = (const unsigned char *)s2; + *c1 || *c2; c1++, c2++) { + const int diff = (int)*c1 - *c2; + if (diff != 0) + return diff; + } + + return 0; +} + +int strncmp(const char *const s1, const char *const s2, const size_t n) { + size_t i = 0; + const unsigned char *c1 = (const unsigned char *)s1, + *c2 = (const unsigned char *)s2; + for (; i < n && (*c1 || *c2); i++, c1++, c2++) { + const int diff = (int)*c1 - *c2; + if (diff != 0) + return diff; + } + + return 0; +} + +size_t strlen(const char *s) { + size_t result = 0; + for (const char *c = s; *c; c++) { + result++; + } + return result; +} + +void memswp(void *const restrict xs, void *const restrict ys, const size_t n) { + unsigned char *const restrict xs_c = xs, *const restrict ys_c = ys; + for (size_t i = 0; i < n; i++) { + const unsigned char tmp = xs_c[i]; + xs_c[i] = ys_c[i]; + ys_c[i] = tmp; + } +} diff --git a/lab6/tests/user-program/libc/src/unistd.c b/lab6/tests/user-program/libc/src/unistd.c new file mode 100644 index 000000000..ba4be2c69 --- /dev/null +++ b/lab6/tests/user-program/libc/src/unistd.c @@ -0,0 +1,19 @@ +#include "unistd.h" + +#include "sys/syscall.h" + +pid_t getpid(void) { return syscall(SYS_getpid); } + +ssize_t uart_read(void *const buf, const size_t count) { + return syscall(SYS_uart_read, buf, count); +} + +ssize_t uart_write(const void *const buf, const size_t count) { + return syscall(SYS_uart_write, buf, count); +} + +int exec(const char *const pathname, char *const argv[const]) { + return syscall(SYS_exec, pathname, argv); +} + +pid_t fork(void) { return syscall(SYS_fork); } diff --git a/lab6/tests/user-program/libc/src/unistd/syscall.S b/lab6/tests/user-program/libc/src/unistd/syscall.S new file mode 100644 index 000000000..7783a7105 --- /dev/null +++ b/lab6/tests/user-program/libc/src/unistd/syscall.S @@ -0,0 +1,30 @@ +.section ".text" + +syscall: + mov x8, x0 + mov x0, x1 + mov x1, x2 + mov x2, x3 + mov x3, x4 + mov x4, x5 + mov x5, x6 + mov x6, x7 + + svc 0 + + cmp x0, -4096 + b.hi .Lsyscall_failed + + ret + +.Lsyscall_failed: + neg x0, x0 + ldr x1, =errno + str x0, [x1] + + mov x0, -1 + ret + +.type syscall, function +.size syscall, . - syscall +.global syscall diff --git a/lab6/tests/user-program/syscall_test/.gitignore b/lab6/tests/user-program/syscall_test/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/lab6/tests/user-program/syscall_test/.gitignore @@ -0,0 +1 @@ +/build diff --git a/lab6/tests/user-program/syscall_test/Makefile b/lab6/tests/user-program/syscall_test/Makefile new file mode 100644 index 000000000..c2a97559b --- /dev/null +++ b/lab6/tests/user-program/syscall_test/Makefile @@ -0,0 +1,73 @@ +DEFAULT_PROFILE = DEBUG +SRC_DIR = src +BUILD_DIR = build + +AS = aarch64-linux-gnu-as +CC = aarch64-linux-gnu-gcc +CPP = aarch64-linux-gnu-cpp +OBJCOPY = aarch64-linux-gnu-objcopy + +CPPFLAGS_BASE = -Iinclude -I../libc/include + +ASFLAGS_DEBUG = -g + +CFLAGS_BASE = -std=c17 -pedantic-errors -Wall -Wextra -ffreestanding \ + -mcpu=cortex-a53 -mno-outline-atomics -mstrict-align +CFLAGS_DEBUG = -g +CFLAGS_RELEASE = -O3 -flto + +LDFLAGS_BASE = -nostdlib -Xlinker --build-id=none +LDFLAGS_RELEASE = -flto -Xlinker --gc-sections + +LDLIBS_BASE = -lgcc -L../libc/build/$(PROFILE) -lc + +OBJS = main +LD_SCRIPT = $(SRC_DIR)/linker.ld + +# ------------------------------------------------------------------------------ + +PROFILE = $(DEFAULT_PROFILE) +OUT_DIR = $(BUILD_DIR)/$(PROFILE) + +CPPFLAGS = $(CPPFLAGS_BASE) $(CPPFLAGS_$(PROFILE)) +ASFLAGS = $(ASFLAGS_BASE) $(ASFLAGS_$(PROFILE)) +CFLAGS = $(CFLAGS_BASE) $(CFLAGS_$(PROFILE)) +LDFLAGS = $(LDFLAGS_BASE) $(LDFLAGS_$(PROFILE)) +LDLIBS = $(LDLIBS_BASE) $(LDLIBS_$(PROFILE)) + +OBJ_PATHS = $(addprefix $(OUT_DIR)/,$(addsuffix .o,$(OBJS))) + +# ------------------------------------------------------------------------------ + +.PHONY: all clean-profile clean + +all: $(OUT_DIR)/syscall_test.img + +$(OUT_DIR)/syscall_test.img: $(OUT_DIR)/syscall_test.elf + @mkdir -p $(@D) + $(OBJCOPY) -O binary $^ $@ + +$(OUT_DIR)/syscall_test.elf: $(OBJ_PATHS) $(LD_SCRIPT) + @mkdir -p $(@D) + $(CC) -T $(LD_SCRIPT) $(LDFLAGS) $(filter-out $(LD_SCRIPT),$^) $(LDLIBS) \ + -o $@ + +$(OUT_DIR)/%.o: $(SRC_DIR)/%.c + @mkdir -p $(@D) + $(CC) $(CPPFLAGS) $(CFLAGS) -c -MMD -MP -MF $(OUT_DIR)/$*.d $< -o $@ + +$(OUT_DIR)/%.o: $(OUT_DIR)/%.s + @mkdir -p $(@D) + $(AS) $(ASFLAGS) $^ -o $@ + +$(OUT_DIR)/%.s: $(SRC_DIR)/%.S + @mkdir -p $(@D) + $(CPP) $(CPPFLAGS) $^ -o $@ + +clean-profile: + $(RM) -r $(OUT_DIR) + +clean: + $(RM) -r $(BUILD_DIR) + +-include $(OUT_DIR)/*.d diff --git a/lab6/tests/user-program/syscall_test/src/linker.ld b/lab6/tests/user-program/syscall_test/src/linker.ld new file mode 100644 index 000000000..255954576 --- /dev/null +++ b/lab6/tests/user-program/syscall_test/src/linker.ld @@ -0,0 +1,80 @@ +/* +Memory map: (End addresses are exclusive) +0x 0 ( 0B) - 0x 80000 (512K): Reserved for firmware +0x 80000 (512K) - 0x 4000000 ( 64M): Kernel text, rodata, data, bss, heap +0x 4000000 ( 64M) - 0x 8000000 (128M): User program +0x 8000000 (128M) - 0x3a400000 (932M): Reserved for initramfs +0x3a400000 (932M) - 0x3ac00000 (940M): User program stack +0x3ac00000 (940M) - 0x3b400000 (948M): Kernel stack +*/ + +_skernel = 0x80000; +_max_ekernel = 0x4000000; +_suser = 64M; +_max_euser = 128M; + +MEMORY +{ + RAM_USER : ORIGIN = _suser, LENGTH = _max_euser - _suser +} + +/* Must be 16-byte aligned. + The highest address the ARM core can use. Total system SDRAM is 1G. Top 76M + are reserved for VideoCore. */ +_estack = 1024M - 76M; +/* Again, must be 16-byte aligned. */ +_euserstack = _estack - 8M; + +ENTRY(_start) + +SECTIONS +{ + .text : + { + _stext = .; + + *(.text._start) /* Entry point. See `start.S`. */ + *(.text.unlikely .text.*_unlikely .text.unlikely.*) + *(.text.startup .text.startup.*) + *(.text.hot .text.hot.*) + *(.text .text.*) + *(.eh_frame) + *(.eh_frame_hdr) + + _etext = .; + } >RAM_USER + + .rodata : + { + _srodata = .; + + *(.rodata .rodata.*) + + _erodata = .; + } >RAM_USER + + .data : + { + _sdata = .; + + *(.data .data.*) + + _edata = .; + } >RAM_USER + + /* The .bss section is 16-byte aligned to allow the section to be + zero-initialized with the `stp` instruction without using unaligned + memory accesses. See `start.S`. */ + .bss : ALIGN(16) + { + _sbss = .; + + *(.bss .bss.*) + *(COMMON) + + . = ALIGN(16); + _ebss = .; + } >RAM_USER + + _ekernel = .; +} diff --git a/lab6/tests/user-program/syscall_test/src/main.c b/lab6/tests/user-program/syscall_test/src/main.c new file mode 100644 index 000000000..330fe3ec9 --- /dev/null +++ b/lab6/tests/user-program/syscall_test/src/main.c @@ -0,0 +1,61 @@ +#include "stdint.h" +#include "stdio.h" +#include "stdlib.h" + +#include "unistd.h" + +#define NS_PER_SEC 1000000000 + +void delay_ns(const uint64_t ns) { + uint64_t start_counter_val; + __asm__ __volatile__("mrs %0, cntpct_el0" : "=r"(start_counter_val)::); + + uint64_t counter_clock_frequency_hz; + __asm__ __volatile__("mrs %0, cntfrq_el0" + : "=r"(counter_clock_frequency_hz)::); + counter_clock_frequency_hz &= 0xffffffff; + + // ceil(ns * counter_clock_frequency_hz / NS_PER_SEC). + const uint64_t delta_counter_val = + (ns * counter_clock_frequency_hz + (NS_PER_SEC - 1)) / NS_PER_SEC; + + for (;;) { + uint64_t counter_val; + __asm__ __volatile__("mrs %0, cntpct_el0" : "=r"(counter_val)::); + + if (counter_val - start_counter_val >= delta_counter_val) + break; + } +} + +void fork_test(void) { + printf("\nFork Test, pid %d\n", getpid()); + int cnt = 1; + int ret = 0; + if ((ret = fork()) == 0) { // child + long long cur_sp; + __asm__ __volatile__("mov %0, sp" : "=r"(cur_sp)); + printf("first child pid: %d, cnt: %d, ptr: %p, sp : %llx\n", getpid(), cnt, + (void *)&cnt, cur_sp); + ++cnt; + + if ((ret = fork()) != 0) { + __asm__ __volatile__("mov %0, sp" : "=r"(cur_sp)); + printf("first child pid: %d, cnt: %d, ptr: %p, sp : %llx\n", getpid(), + cnt, (void *)&cnt, cur_sp); + } else { + while (cnt < 5) { + __asm__ __volatile__("mov %0, sp" : "=r"(cur_sp)); + printf("second child pid: %d, cnt: %d, ptr: %p, sp : %llx\n", getpid(), + cnt, (void *)&cnt, cur_sp); + delay_ns(1000000); + ++cnt; + } + } + exit(0); + } else { + printf("parent here, pid %d, child %d\n", getpid(), ret); + } +} + +void main(void) { fork_test(); } From c833acc416344ae3d6a7b3fb2ff4ab3744fa73aa Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Mon, 26 Jun 2023 16:33:37 +0800 Subject: [PATCH 09/34] Lab 6 C: Change default exception handler The default exception handler now starts a kernel panic instead of returning. This aids in debugging by avoiding cluttering the serial console due to repeated triggering of the same exception. --- lab6/c/include/oscos/console.h | 3 +++ lab6/c/src/console.c | 16 ++++++++++++++++ lab6/c/src/panic.c | 2 ++ lab6/c/src/xcpt/default-handler.c | 26 +++++++++++--------------- 4 files changed, 32 insertions(+), 15 deletions(-) diff --git a/lab6/c/include/oscos/console.h b/lab6/c/include/oscos/console.h index 65abb41d1..77425b4d2 100644 --- a/lab6/c/include/oscos/console.h +++ b/lab6/c/include/oscos/console.h @@ -135,6 +135,9 @@ bool console_notify_read_ready(void (*callback)(void *), void *arg); /// been called. bool console_notify_write_ready(void (*callback)(void *), void *arg); +/// \brief Waits until all buffered characters are sent to the serial console. +void console_flush_write_buffer(void); + /// \brief Interrupt handler. Not meant to be called directly. void mini_uart_interrupt_handler(void); diff --git a/lab6/c/src/console.c b/lab6/c/src/console.c index 496a6f539..a41e69a49 100644 --- a/lab6/c/src/console.c +++ b/lab6/c/src/console.c @@ -366,6 +366,22 @@ bool console_notify_write_ready(void (*const callback)(void *), return true; } +void console_flush_write_buffer(void) { + bool suspend_cond_val = true; + uint64_t daif_val; + + while (suspend_cond_val) { + CRITICAL_SECTION_ENTER(daif_val); + + if ((suspend_cond_val = _console_write_buf_len > 0)) { + __asm__ __volatile__("wfi"); + _console_send_from_buf(); + } + + CRITICAL_SECTION_LEAVE(daif_val); + } +} + void mini_uart_interrupt_handler(void) { _console_recv_to_buf(); _console_send_from_buf(); diff --git a/lab6/c/src/panic.c b/lab6/c/src/panic.c index 89109ce74..d43c8f2aa 100644 --- a/lab6/c/src/panic.c +++ b/lab6/c/src/panic.c @@ -15,6 +15,8 @@ void panic_begin(const char *const restrict file, const int line, va_end(ap); + console_flush_write_buffer(); + // Park the core. for (;;) { __asm__ __volatile__("wfe"); diff --git a/lab6/c/src/xcpt/default-handler.c b/lab6/c/src/xcpt/default-handler.c index c06c7dc6e..3be7eb3ed 100644 --- a/lab6/c/src/xcpt/default-handler.c +++ b/lab6/c/src/xcpt/default-handler.c @@ -1,21 +1,17 @@ -#include - #include "oscos/console.h" #include "oscos/libc/inttypes.h" - -#define DUMP_SYS_REG(REGNAME, PAD) \ - do { \ - uint64_t reg_val; \ - __asm__ __volatile__("mrs %0, " #REGNAME : "=r"(reg_val)); \ - \ - console_printf(#REGNAME ": " PAD "0x%.8" PRIx64 "\n", reg_val); \ - } while (0) +#include "oscos/panic.h" void xcpt_default_handler(const uint64_t vten) { - console_printf("VTEN: 0x%8.1" PRIx64 "\n", vten); + uint64_t spsr_val, elr_val, esr_val; + __asm__ __volatile__("mrs %0, spsr_el1" : "=r"(spsr_val)); + __asm__ __volatile__("mrs %0, elr_el1" : "=r"(elr_val)); + __asm__ __volatile__("mrs %0, esr_el1" : "=r"(esr_val)); - DUMP_SYS_REG(spsr_el1, ""); - DUMP_SYS_REG(elr_el1, " "); - DUMP_SYS_REG(esr_el1, " "); - console_putc('\n'); + PANIC("Unhandled exception\n" + "VTEN: 0x%8.1" PRIx64 "\n" + "spsr_el1: 0x%.8" PRIx64 "\n" + "elr_el1: 0x%.8" PRIx64 "\n" + "esr_el1: 0x%.8" PRIx64, + vten, spsr_val, elr_val, esr_val); } From 17609502a130c410215e767ab04d2d250e2c0e2d Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Mon, 26 Jun 2023 22:35:03 +0800 Subject: [PATCH 10/34] Lab 6 C: Complete B1 --- lab6/c/Makefile | 1 + lab6/c/include/oscos/drivers/board.h | 9 +- .../include/oscos/mem/vm/kernel-page-tables.h | 6 + lab6/c/include/oscos/mem/vm/page-table.h | 42 +++++++ lab6/c/src/initrd.c | 9 +- lab6/c/src/linker.ld | 7 +- lab6/c/src/main.c | 2 + lab6/c/src/mem/vm.c | 11 +- lab6/c/src/mem/vm/kernel-page-tables.c | 112 ++++++++++++++++++ lab6/c/src/start.S | 49 ++++++++ 10 files changed, 236 insertions(+), 12 deletions(-) create mode 100644 lab6/c/include/oscos/mem/vm/kernel-page-tables.h create mode 100644 lab6/c/include/oscos/mem/vm/page-table.h create mode 100644 lab6/c/src/mem/vm/kernel-page-tables.c diff --git a/lab6/c/Makefile b/lab6/c/Makefile index 283d07950..cbe7abc9e 100644 --- a/lab6/c/Makefile +++ b/lab6/c/Makefile @@ -25,6 +25,7 @@ OBJS = start main console devicetree initrd panic shell \ drivers/aux drivers/gpio drivers/l1ic drivers/l2ic drivers/mailbox \ drivers/mini-uart drivers/pm \ mem/malloc mem/page-alloc mem/startup-alloc mem/types mem/vm \ + mem/vm/kernel-page-tables \ sched/idle-thread sched/periodic-sched sched/run-signal-handler \ sched/sched sched/schedule sched/sig-handler-main \ sched/thread-main sched/user-program-main \ diff --git a/lab6/c/include/oscos/drivers/board.h b/lab6/c/include/oscos/drivers/board.h index a91f11e1c..95af6777a 100644 --- a/lab6/c/include/oscos/drivers/board.h +++ b/lab6/c/include/oscos/drivers/board.h @@ -1,11 +1,14 @@ #ifndef OSCOS_DRIVERS_BOARD_H #define OSCOS_DRIVERS_BOARD_H -// ARM physical address. See +// Symbols defined in the linker script. +extern char _kernel_vm_base[]; + +// ARM virtual address. See // https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#peripheral-addresses -#define PERIPHERAL_BASE ((void *)0x3f000000) +#define PERIPHERAL_BASE ((void *)(_kernel_vm_base + 0x3f000000)) -#define ARM_LOCAL_PERIPHERAL_BASE ((void *)0x40000000) +#define ARM_LOCAL_PERIPHERAL_BASE ((void *)(_kernel_vm_base + 0x40000000)) // ? Is the shareability domain for the peripheral memory barriers correct? diff --git a/lab6/c/include/oscos/mem/vm/kernel-page-tables.h b/lab6/c/include/oscos/mem/vm/kernel-page-tables.h new file mode 100644 index 000000000..d8dd6659e --- /dev/null +++ b/lab6/c/include/oscos/mem/vm/kernel-page-tables.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_MEM_VM_KERNEL_PAGE_TABLES_H +#define OSCOS_MEM_VM_KERNEL_PAGE_TABLES_H + +void vm_setup_finer_granularity_linear_mapping(void); + +#endif diff --git a/lab6/c/include/oscos/mem/vm/page-table.h b/lab6/c/include/oscos/mem/vm/page-table.h new file mode 100644 index 000000000..a89e304c2 --- /dev/null +++ b/lab6/c/include/oscos/mem/vm/page-table.h @@ -0,0 +1,42 @@ +#ifndef OSCOS_VM_PAGE_TABLE_H +#define OSCOS_VM_PAGE_TABLE_H + +#include + +typedef struct { + unsigned _reserved0 : (50 - 48 + 1); + unsigned ignored : (58 - 51 + 1); + bool pxntable : 1; + bool uxntable : 1; + unsigned aptable : 2; + unsigned _reserved1 : 1; +} table_descriptor_upper_t; + +typedef struct { + unsigned _reserved0 : (51 - 48 + 1); + bool contiguous : 1; + bool pxn : 1; + bool uxn : 1; + unsigned ignored : (63 - 55 + 1); +} block_page_descriptor_upper_t; + +typedef struct { + unsigned attr_indx : 3; + bool _reserved0 : 1; + unsigned ap : 2; + unsigned sh : 2; + bool af : 1; + bool ng : 1; +} block_page_descriptor_lower_t; + +typedef struct { + bool b0 : 1; + bool b1 : 1; + unsigned lower : (11 - 2 + 1); + unsigned long long addr : (47 - 12 + 1); + unsigned upper : (63 - 48 + 1); +} page_table_entry_t; + +typedef page_table_entry_t page_table_t[512]; + +#endif diff --git a/lab6/c/src/initrd.c b/lab6/c/src/initrd.c index f215e76aa..2ae0f9561 100644 --- a/lab6/c/src/initrd.c +++ b/lab6/c/src/initrd.c @@ -1,6 +1,7 @@ #include "oscos/initrd.h" #include "oscos/devicetree.h" +#include "oscos/mem/vm.h" static const void *_initrd_start, *_initrd_end; @@ -57,12 +58,12 @@ static control_flow_t _initrd_init_dtb_traverse_callback( if (FDT_TOKEN(item) == FDT_PROP) { const fdt_prop_t *const prop = (const fdt_prop_t *)item->payload; if (strcmp(FDT_PROP_NAME(prop), "linux,initrd-start") == 0) { - const uint32_t adr = rev_u32(*(const uint32_t *)FDT_PROP_VALUE(prop)); - _initrd_start = (const void *)(uintptr_t)adr; + const uint32_t pa = rev_u32(*(const uint32_t *)FDT_PROP_VALUE(prop)); + _initrd_start = pa_to_kernel_va(pa); arg->start_done = true; } else if (strcmp(FDT_PROP_NAME(prop), "linux,initrd-end") == 0) { - const uint32_t adr = rev_u32(*(const uint32_t *)FDT_PROP_VALUE(prop)); - _initrd_end = (const void *)(uintptr_t)adr; + const uint32_t pa = rev_u32(*(const uint32_t *)FDT_PROP_VALUE(prop)); + _initrd_end = pa_to_kernel_va(pa); arg->end_done = true; } diff --git a/lab6/c/src/linker.ld b/lab6/c/src/linker.ld index 4c6bab1d0..0ab7218ff 100644 --- a/lab6/c/src/linker.ld +++ b/lab6/c/src/linker.ld @@ -7,8 +7,9 @@ Memory map: (End addresses are exclusive) 0x3b400000 (948M) - 0x3c000000 (960M): (QEMU only) Kernel heap */ -_skernel = 0x80000; -_max_ekernel = 0x3ac00000; +_kernel_vm_base = 0xffff000000000000; +_skernel = _kernel_vm_base + 0x80000; +_max_ekernel = _kernel_vm_base + 0x3ac00000; MEMORY { @@ -18,7 +19,7 @@ MEMORY /* Must be 16-byte aligned. The highest address the ARM core can use. Total system SDRAM is 1G. Top 76M (or 64M if on QEMU) are reserved for VideoCore. */ -_estack = 1024M - 76M; +_estack = _kernel_vm_base + (1024M - 76M); _sstack = _estack - 8M; ENTRY(_start) diff --git a/lab6/c/src/main.c b/lab6/c/src/main.c index 0feae407a..7aa13fbf5 100644 --- a/lab6/c/src/main.c +++ b/lab6/c/src/main.c @@ -10,6 +10,7 @@ #include "oscos/mem/malloc.h" #include "oscos/mem/page-alloc.h" #include "oscos/mem/startup-alloc.h" +#include "oscos/mem/vm/kernel-page-tables.h" #include "oscos/panic.h" #include "oscos/sched.h" #include "oscos/shell.h" @@ -49,6 +50,7 @@ void main(const void *const dtb_start) { // Initialize the memory allocators. page_alloc_init(); malloc_init(); + vm_setup_finer_granularity_linear_mapping(); // Initialize miscellaneous subsystems. mailbox_init(); diff --git a/lab6/c/src/mem/vm.c b/lab6/c/src/mem/vm.c index 28ad21c07..bdca5f375 100644 --- a/lab6/c/src/mem/vm.c +++ b/lab6/c/src/mem/vm.c @@ -2,9 +2,16 @@ #include -pa_t kernel_va_to_pa(const void *const va) { return (pa_t)(uintptr_t)va; } +// Symbol defined in the linker script. +extern char _kernel_vm_base[]; -void *pa_to_kernel_va(const pa_t pa) { return (void *)(uintptr_t)pa; } +pa_t kernel_va_to_pa(const void *const va) { + return (pa_t)((uintptr_t)va - (uintptr_t)_kernel_vm_base); +} + +void *pa_to_kernel_va(const pa_t pa) { + return (void *)((uintptr_t)pa + (uintptr_t)_kernel_vm_base); +} pa_range_t kernel_va_range_to_pa_range(const va_range_t range) { return (pa_range_t){.start = kernel_va_to_pa(range.start), diff --git a/lab6/c/src/mem/vm/kernel-page-tables.c b/lab6/c/src/mem/vm/kernel-page-tables.c new file mode 100644 index 000000000..93338b811 --- /dev/null +++ b/lab6/c/src/mem/vm/kernel-page-tables.c @@ -0,0 +1,112 @@ +#include "oscos/mem/vm/kernel-page-tables.h" + +#include +#include + +#include "oscos/drivers/board.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/mem/types.h" +#include "oscos/mem/vm.h" +#include "oscos/mem/vm/page-table.h" + +alignas(4096) page_table_t kernel_pud = {{.b0 = 1, + .b1 = 0, + .lower = 1 << 8, // AF = 1. + .addr = 0x0, + .upper = 0}, + {.b0 = 1, + .b1 = 0, + .lower = 1 << 8, // AF = 1. + .addr = 0x40000, + .upper = 0}}; + +alignas(4096) page_table_t kernel_pgd = { + {.b0 = 1, + .b1 = 1, + .lower = 0, + .addr = 0, // The concrete value will be filled by `start.S`. + .upper = 0}}; + +static void _map_region_as_rec(const page_id_range_t range, + const block_page_descriptor_lower_t lower_attr, + const block_page_descriptor_upper_t upper_attr, + const size_t level, const page_id_t block_start, + page_table_entry_t *const entry) { + const union { + block_page_descriptor_lower_t s; + unsigned u; + } lower = {.s = lower_attr}; + const union { + block_page_descriptor_upper_t s; + unsigned u; + } upper = {.s = upper_attr}; + + const page_id_t block_end = block_start + (1 << (9 * level)); + + if (range.start == block_start && range.end == block_end) { + *entry = (page_table_entry_t){.b0 = 1, + .b1 = level == 0, + .lower = lower.u, + .addr = block_start, + .upper = upper.u}; + } else { + if (level == 0) + __builtin_unreachable(); + + const page_id_t subblock_stride = 1 << (9 * (level - 1)); + + page_table_entry_t *page_table; + if (entry->b1) { // Table. + page_table = (page_table_entry_t *)pa_to_kernel_va(entry->addr << 12); + } else { // Block. + const pa_t page_table_pa = page_id_to_pa(alloc_pages(0)); + page_table = pa_to_kernel_va(page_table_pa); + + for (size_t i = 0; i < 512; i++) { + page_table[i] = + (page_table_entry_t){.b0 = 1, + .b1 = level == 1, + .lower = entry->lower, + .addr = entry->addr + i * subblock_stride, + .upper = entry->upper}; + } + *entry = (page_table_entry_t){.b0 = 1, + .b1 = 1, + .lower = entry->lower, + .addr = page_table_pa >> 12, + .upper = entry->upper}; + } + + for (size_t i = 0; i < 512; i++) { + const page_id_t subblock_start = block_start + i * subblock_stride, + subblock_end = subblock_start + subblock_stride; + + const page_id_t max_start = range.start > subblock_start ? range.start + : subblock_start, + min_end = + range.end < subblock_end ? range.end : subblock_end; + + if (max_start < min_end) { + _map_region_as_rec( + (page_id_range_t){.start = max_start, .end = min_end}, lower_attr, + upper_attr, level - 1, subblock_start, &page_table[i]); + } + } + } +} + +static void _map_region_as(const page_id_range_t range, + const block_page_descriptor_lower_t lower_attr, + const block_page_descriptor_upper_t upper_attr) { + _map_region_as_rec(range, lower_attr, upper_attr, 3, 0, kernel_pgd); +} + +void vm_setup_finer_granularity_linear_mapping(void) { + // Map RAM as normal memory. + _map_region_as( + (page_id_range_t){.start = 0x0, + .end = kernel_va_to_pa(PERIPHERAL_BASE) >> 12}, + (block_page_descriptor_lower_t){ + .attr_indx = 1, .ap = 0x0, .sh = 0x0, .af = 1, .ng = 0}, + (block_page_descriptor_upper_t){.contiguous = 0, .pxn = 0, .uxn = 0}); +} diff --git a/lab6/c/src/start.S b/lab6/c/src/start.S index df9718cad..7520ceff1 100644 --- a/lab6/c/src/start.S +++ b/lab6/c/src/start.S @@ -1,3 +1,17 @@ +#define TCR_T0SZ_POSN 0 +#define TCR_T0SZ_REGION_48BIT ((64 - 48) << TCR_T0SZ_POSN) +#define TCR_TG0_POSN 14 +#define TCR_TG0_4KB (0b00 << TCR_TG0_POSN) +#define TCR_T1SZ_POSN 16 +#define TCR_T1SZ_REGION_48BIT ((64 - 48) << TCR_T1SZ_POSN) +#define TCR_TG1_POSN 30 +#define TCR_TG1_4KB (0b10 << TCR_TG1_POSN) + +#define MAIR_DEVICE_nGnRnE 0b00000000 +#define MAIR_NORMAL_NOCACHE 0b01000100 +#define MAIR_IX_DEVICE_nGnRnE 0 +#define MAIR_IX_NORMAL_NOCACHE 1 + .section ".text._start" _start: @@ -25,6 +39,41 @@ _start: eret .Lin_el1: + // Setup virtual memory. + + ldr x1, \ + =(TCR_T0SZ_REGION_48BIT | TCR_TG0_4KB | TCR_T1SZ_REGION_48BIT \ + | TCR_TG1_4KB) + msr tcr_el1, x1 + + ldr x1, \ + =((MAIR_DEVICE_nGnRnE << (MAIR_IX_DEVICE_nGnRnE * 8)) \ + | (MAIR_NORMAL_NOCACHE << (MAIR_IX_NORMAL_NOCACHE * 8))) + msr mair_el1, x1 + + ldr x1, =kernel_pgd + ldr x2, =_kernel_vm_base + sub x1, x1, x2 + + ldr x3, [x1] + ldr x4, =kernel_pud + sub x4, x4, x2 + orr x3, x3, x4 + str x3, [x1] + + msr ttbr0_el1, x1 + msr ttbr1_el1, x1 + + mrs x1, sctlr_el1 + orr x1, x1, 1 + msr sctlr_el1, x1 + + ldr x1, =.Lstart_after_mmu + br x1 + +.Lstart_after_mmu: + // Make the devicetree address virtual. + add x0, x0, x2 // Enable FP/SIMD. mov x1, 0x300000 From 1794c1af0cb66389a2b63e2f2c3218459289f504 Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Wed, 28 Jun 2023 02:23:12 +0800 Subject: [PATCH 11/34] Lab 6 C: Complete B2, B3, A2, A3 Other changes: - Adjust text width in the default exception handler. TODO: - Fix the release build. --- lab6/c/Makefile | 8 +- lab6/c/include/oscos/mem/shared-page.h | 12 + lab6/c/include/oscos/mem/types.h | 27 -- lab6/c/include/oscos/mem/vm.h | 30 ++ lab6/c/include/oscos/sched.h | 14 +- lab6/c/src/drivers/mailbox.c | 12 +- lab6/c/src/mem/shared-page.c | 147 ++++++ lab6/c/src/mem/types.c | 44 -- lab6/c/src/mem/vm.c | 428 ++++++++++++++++++ lab6/c/src/sched/run-signal-handler.S | 2 - lab6/c/src/sched/sched.c | 283 +++--------- lab6/c/src/sched/schedule.S | 4 + lab6/c/src/xcpt/data-abort-handler.c | 55 +++ lab6/c/src/xcpt/default-handler.c | 8 +- lab6/c/src/xcpt/insn-abort-handler.c | 55 +++ lab6/c/src/xcpt/load-aapcs-and-eret.S | 11 + lab6/c/src/xcpt/syscall/mbox-call.c | 23 + lab6/c/src/xcpt/vector-table.S | 60 ++- lab6/tests/build-initrd-video-player.sh | 4 - lab6/tests/build-initrd.sh | 12 +- .../user-program/syscall_test/src/linker.ld | 45 +- 21 files changed, 919 insertions(+), 365 deletions(-) create mode 100644 lab6/c/include/oscos/mem/shared-page.h create mode 100644 lab6/c/src/mem/shared-page.c delete mode 100644 lab6/c/src/mem/types.c create mode 100644 lab6/c/src/xcpt/data-abort-handler.c create mode 100644 lab6/c/src/xcpt/insn-abort-handler.c create mode 100644 lab6/c/src/xcpt/load-aapcs-and-eret.S delete mode 100755 lab6/tests/build-initrd-video-player.sh diff --git a/lab6/c/Makefile b/lab6/c/Makefile index cbe7abc9e..6b488b48e 100644 --- a/lab6/c/Makefile +++ b/lab6/c/Makefile @@ -7,7 +7,7 @@ CC = aarch64-linux-gnu-gcc CPP = aarch64-linux-gnu-cpp OBJCOPY = aarch64-linux-gnu-objcopy -CPPFLAGS_BASE = -Iinclude +CPPFLAGS_BASE = -Iinclude -DVM_ENABLE_DEBUG_LOG ASFLAGS_DEBUG = -g @@ -24,13 +24,15 @@ LDLIBS_BASE = -lgcc OBJS = start main console devicetree initrd panic shell \ drivers/aux drivers/gpio drivers/l1ic drivers/l2ic drivers/mailbox \ drivers/mini-uart drivers/pm \ - mem/malloc mem/page-alloc mem/startup-alloc mem/types mem/vm \ + mem/malloc mem/page-alloc mem/shared-page mem/startup-alloc mem/vm \ mem/vm/kernel-page-tables \ sched/idle-thread sched/periodic-sched sched/run-signal-handler \ sched/sched sched/schedule sched/sig-handler-main \ sched/thread-main sched/user-program-main \ timer/delay timer/timeout \ - xcpt/default-handler xcpt/irq-handler xcpt/svc-handler \ + xcpt/data-abort-handler xcpt/default-handler \ + xcpt/insn-abort-handler xcpt/irq-handler xcpt/load-aapcs-and-eret \ + xcpt/svc-handler \ xcpt/syscall-table xcpt/task-queue xcpt/vector-table xcpt/xcpt \ xcpt/syscall/enosys xcpt/syscall/getpid xcpt/syscall/uart-read \ xcpt/syscall/uart-write xcpt/syscall/exec xcpt/syscall/fork \ diff --git a/lab6/c/include/oscos/mem/shared-page.h b/lab6/c/include/oscos/mem/shared-page.h new file mode 100644 index 000000000..2a09ed628 --- /dev/null +++ b/lab6/c/include/oscos/mem/shared-page.h @@ -0,0 +1,12 @@ +#ifndef OSCOS_MEM_SHARED_PAGE_H +#define OSCOS_MEM_SHARED_PAGE_H + +#include "oscos/mem/types.h" + +spage_id_t shared_page_alloc(void); +size_t shared_page_getref(page_id_t page) __attribute__((pure)); +void shared_page_incref(page_id_t page); +void shared_page_decref(page_id_t page); +spage_id_t shared_page_clone_unshare(page_id_t page); + +#endif diff --git a/lab6/c/include/oscos/mem/types.h b/lab6/c/include/oscos/mem/types.h index 7aa7e7c99..1deee96e8 100644 --- a/lab6/c/include/oscos/mem/types.h +++ b/lab6/c/include/oscos/mem/types.h @@ -39,31 +39,4 @@ typedef struct { void *start, *end; } va_range_t; -/// \brief Shared, reference-counted page. -typedef struct { - size_t ref_count; - page_id_t page_id; -} shared_page_t; - -/// \brief Initializes a shared page. -/// -/// This function sets the reference count of the newly-created shared page -/// to 1. -shared_page_t *shared_page_init(page_id_t page_id); - -/// \brief Clones a shared page. -/// -/// This function increases the reference count of the shared page by 1. -/// -/// \param shared_page The shared page to clone. -/// \return \p shared_page -shared_page_t *shared_page_clone(shared_page_t *shared_page); - -/// \brief Drops a shared page. -/// -/// This function decreases the reference count of the shared page by 1. The -/// shared page structure and the underlying page will be freed when the -/// reference count reaches 0. -void shared_page_drop(shared_page_t *shared_page); - #endif diff --git a/lab6/c/include/oscos/mem/vm.h b/lab6/c/include/oscos/mem/vm.h index ecbc9545d..261b0c10d 100644 --- a/lab6/c/include/oscos/mem/vm.h +++ b/lab6/c/include/oscos/mem/vm.h @@ -2,6 +2,7 @@ #define OSCOS_MEM_VM_H #include "oscos/mem/types.h" +#include "oscos/mem/vm/page-table.h" /// \brief Converts a kernel space virtual address into its corresponding /// physical address. @@ -15,4 +16,33 @@ void *pa_to_kernel_va(pa_t pa) __attribute__((const)); /// physical address range. pa_range_t kernel_va_range_to_pa_range(va_range_t range) __attribute__((const)); +typedef enum { MEM_REGION_BACKED, MEM_REGION_LINEAR } mem_region_type_t; + +typedef struct { + void *start; + size_t len; + mem_region_type_t type; + const void *backing_storage_start; + size_t backing_storage_len; +} mem_region_t; + +typedef struct { + mem_region_t text_region, stack_region, vc_region; + page_table_entry_t *pgd; +} vm_addr_space_t; + +typedef enum { + VM_MAP_PAGE_SUCCESS, + VM_MAP_PAGE_SEGV, + VM_MAP_PAGE_NOMEM +} vm_map_page_result_t; + +page_table_entry_t *vm_new_pgd(void); +void vm_clone_pgd(page_table_entry_t *pgd); +void vm_drop_pgd(page_table_entry_t *pgd); +vm_map_page_result_t vm_map_page(vm_addr_space_t *addr_space, void *va); +vm_map_page_result_t vm_cow(vm_addr_space_t *addr_space, void *va); +void vm_switch_to_addr_space(const vm_addr_space_t *addr_space); +pa_t vm_translate_addr(const page_table_entry_t *pgd, void *va); + #endif diff --git a/lab6/c/include/oscos/sched.h b/lab6/c/include/oscos/sched.h index bd48e328c..560256f2c 100644 --- a/lab6/c/include/oscos/sched.h +++ b/lab6/c/include/oscos/sched.h @@ -8,6 +8,7 @@ #include #include "oscos/mem/types.h" +#include "oscos/mem/vm.h" #include "oscos/uapi/signal.h" #include "oscos/xcpt/trap-frame.h" @@ -53,20 +54,9 @@ typedef struct { struct process_t *process; } thread_t; -typedef struct page_id_list_node_t { - page_id_t page_id; - struct page_id_list_node_t *next; -} page_id_list_node_t; - typedef struct process_t { size_t id; - shared_page_t *text_page; -#ifdef SCHED_ENABLE_SHARED_USER_STACK - shared_page_t *user_stack_page; -#else - page_id_t user_stack_page_id; -#endif - page_id_list_node_t *signal_stack_pages; + vm_addr_space_t addr_space; thread_t *main_thread; uint32_t pending_signals, blocked_signals; sighandler_t signal_handlers[32]; diff --git a/lab6/c/src/drivers/mailbox.c b/lab6/c/src/drivers/mailbox.c index b0a43570b..eb9bad4ef 100644 --- a/lab6/c/src/drivers/mailbox.c +++ b/lab6/c/src/drivers/mailbox.c @@ -3,9 +3,14 @@ #include #include "oscos/drivers/board.h" +#include "oscos/mem/vm.h" +#include "oscos/sched.h" #define MAILBOX_REG_BASE ((void *)((char *)PERIPHERAL_BASE + 0xb880)) +// Symbols defined in the linker script. +extern char _kernel_vm_base[]; + typedef struct { volatile uint32_t read_write; const volatile uint32_t _reserved[3]; @@ -35,7 +40,12 @@ void mailbox_init(void) { void mailbox_call(uint32_t message[], const unsigned char channel) { PERIPHERAL_WRITE_BARRIER(); - const uint32_t mailbox_write_data = (uint32_t)(uintptr_t)message | channel; + const pa_t message_pa = + (char *)message >= _kernel_vm_base + ? kernel_va_to_pa(message) + : vm_translate_addr(current_thread()->process->addr_space.pgd, + message); + const uint32_t mailbox_write_data = message_pa | channel; while (MAILBOX_REGS[1].status & MAILBOX_STATUS_FULL_MASK) ; diff --git a/lab6/c/src/mem/shared-page.c b/lab6/c/src/mem/shared-page.c new file mode 100644 index 000000000..db4a86e3f --- /dev/null +++ b/lab6/c/src/mem/shared-page.c @@ -0,0 +1,147 @@ +#include "oscos/mem/shared-page.h" + +#include "oscos/libc/string.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/mem/vm.h" +#include "oscos/utils/critical-section.h" +#include "oscos/utils/rb.h" + +static rb_node_t *_page_refcnts = NULL; + +typedef struct { + page_id_t page_id; + size_t refcnt; +} page_refcnt_entry_t; + +static int +_cmp_page_refcnt_entry_by_page_id(const page_refcnt_entry_t *const e1, + const page_refcnt_entry_t *const e2, + void *_arg) { + (void)_arg; + + if (e1->page_id < e2->page_id) + return -1; + if (e1->page_id > e2->page_id) + return 1; + return 0; +} + +static int +_cmp_page_id_and_page_refcnt_entry(const page_id_t *const page, + const page_refcnt_entry_t *const entry, + void *const _arg) { + (void)_arg; + + if (*page < entry->page_id) + return -1; + if (*page > entry->page_id) + return 1; + return 0; +} + +spage_id_t shared_page_alloc(void) { + spage_id_t result = alloc_pages(0); + if (result < 0) + return result; + + const page_refcnt_entry_t new_entry = {.page_id = result, .refcnt = 1}; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + rb_insert(&_page_refcnts, sizeof(page_refcnt_entry_t), &new_entry, + (int (*)(const void *, const void *, + void *))_cmp_page_refcnt_entry_by_page_id, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + return result; +} + +size_t shared_page_getref(const page_id_t page) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const page_refcnt_entry_t *const entry = + rb_search(_page_refcnts, &page, + (int (*)(const void *, const void *, + void *))_cmp_page_id_and_page_refcnt_entry, + NULL); + const size_t result = entry->refcnt; + + CRITICAL_SECTION_LEAVE(daif_val); + + return result; +} + +void shared_page_incref(const page_id_t page) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + // It's safe to cast away const here, since every entry _page_refcnts points + // to is not const. Also, incrementing the reference count doesn't invalidate + // the BST invariant. + page_refcnt_entry_t *const entry = (page_refcnt_entry_t *)rb_search( + _page_refcnts, &page, + (int (*)(const void *, const void *, + void *))_cmp_page_id_and_page_refcnt_entry, + NULL); + + // This function is sometimes called on a non-shared page; more specifically, + // linearly-mapped pages. + if (entry) { + entry->refcnt++; + } + + CRITICAL_SECTION_LEAVE(daif_val); +} + +void shared_page_decref(const page_id_t page) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + // It's safe to cast away const here, since every entry _page_refcnts points + // to is not const. Also, incrementing the reference count doesn't invalidate + // the BST invariant. + page_refcnt_entry_t *const entry = (page_refcnt_entry_t *)rb_search( + _page_refcnts, &page, + (int (*)(const void *, const void *, + void *))_cmp_page_id_and_page_refcnt_entry, + NULL); + entry->refcnt--; + + if (entry->refcnt == 0) { + rb_delete(&_page_refcnts, &page, + (int (*)(const void *, const void *, + void *))_cmp_page_id_and_page_refcnt_entry, + NULL); + free_pages(page); + } + + CRITICAL_SECTION_LEAVE(daif_val); +} + +spage_id_t shared_page_clone_unshare(const page_id_t page) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + if (shared_page_getref(page) == 1) { // No need to clone. + CRITICAL_SECTION_LEAVE(daif_val); + return page; + } + + shared_page_decref(page); + + spage_id_t new_page_id = shared_page_alloc(); + if (new_page_id < 0) { + CRITICAL_SECTION_LEAVE(daif_val); + return new_page_id; + } + + memcpy(pa_to_kernel_va(page_id_to_pa(new_page_id)), + pa_to_kernel_va(page_id_to_pa(page)), 1 << PAGE_ORDER); + + CRITICAL_SECTION_LEAVE(daif_val); + return new_page_id; +} diff --git a/lab6/c/src/mem/types.c b/lab6/c/src/mem/types.c deleted file mode 100644 index 15924b938..000000000 --- a/lab6/c/src/mem/types.c +++ /dev/null @@ -1,44 +0,0 @@ -#include "oscos/mem/types.h" - -#include - -#include "oscos/mem/malloc.h" -#include "oscos/mem/page-alloc.h" -#include "oscos/utils/critical-section.h" - -shared_page_t *shared_page_init(const page_id_t page_id) { - shared_page_t *const shared_page = malloc(sizeof(shared_page_t)); - if (!shared_page) - return NULL; - - shared_page->ref_count = 1; - shared_page->page_id = page_id; - - return shared_page; -} - -shared_page_t *shared_page_clone(shared_page_t *const shared_page) { - uint64_t daif_val; - CRITICAL_SECTION_ENTER(daif_val); - - shared_page->page_id++; - - CRITICAL_SECTION_LEAVE(daif_val); - - return shared_page; -} - -void shared_page_drop(shared_page_t *const shared_page) { - uint64_t daif_val; - CRITICAL_SECTION_ENTER(daif_val); - - shared_page->page_id--; - const bool drop_page = shared_page->page_id == 0; - - CRITICAL_SECTION_LEAVE(daif_val); - - if (drop_page) { - free_pages(shared_page->page_id); - free(shared_page); - } -} diff --git a/lab6/c/src/mem/vm.c b/lab6/c/src/mem/vm.c index bdca5f375..ae605bb16 100644 --- a/lab6/c/src/mem/vm.c +++ b/lab6/c/src/mem/vm.c @@ -2,6 +2,12 @@ #include +#include "oscos/libc/string.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/mem/shared-page.h" +#include "oscos/sched.h" +#include "oscos/utils/critical-section.h" + // Symbol defined in the linker script. extern char _kernel_vm_base[]; @@ -17,3 +23,425 @@ pa_range_t kernel_va_range_to_pa_range(const va_range_t range) { return (pa_range_t){.start = kernel_va_to_pa(range.start), .end = kernel_va_to_pa(range.end)}; } + +page_table_entry_t *vm_new_pgd(void) { + const spage_id_t new_pgd_page_id = shared_page_alloc(); + if (new_pgd_page_id < 0) + return NULL; + + page_table_entry_t *const result = + pa_to_kernel_va(page_id_to_pa(new_pgd_page_id)); + + memset(result, 0, 1 << PAGE_ORDER); + + return result; +} + +void vm_clone_pgd(page_table_entry_t *const pgd) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const page_id_t pgd_page_id = pa_to_page_id(kernel_va_to_pa(pgd)); + shared_page_incref(pgd_page_id); + + for (size_t i = 0; i < 512; i++) { + if (pgd[i].b0) { + union { + table_descriptor_upper_t s; + unsigned u; + } upper = {.u = pgd[i].upper}; + upper.s.aptable = 0x2; + pgd[i].upper = upper.u; + } + } + + CRITICAL_SECTION_LEAVE(daif_val); +} + +static void _vm_drop_page_table(page_table_entry_t *const page_table, + const size_t level) { + const page_id_t page_table_page_id = + pa_to_page_id(kernel_va_to_pa(page_table)); + + if (shared_page_getref(page_table_page_id) == 1) { // About to be freed. + for (size_t i = 0; i < 512; i++) { + if (page_table[i].b0) { + const pa_t next_level_pa = page_table[i].addr << PAGE_ORDER; + if (level == 1) { + shared_page_decref(pa_to_page_id(next_level_pa)); + } else { + page_table_entry_t *const next_level_page_table = + pa_to_kernel_va(next_level_pa); + _vm_drop_page_table(next_level_page_table, level - 1); + } + } + } + } + + shared_page_decref(page_table_page_id); +} + +void vm_drop_pgd(page_table_entry_t *const pgd) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + _vm_drop_page_table(pgd, 3); + + CRITICAL_SECTION_LEAVE(daif_val); +} + +static page_table_entry_t * +_vm_clone_unshare_page_table(page_table_entry_t *const page_table) { + const page_id_t page_table_page_id = + pa_to_page_id(kernel_va_to_pa(page_table)); + + if (shared_page_getref(page_table_page_id) == 1) // No need to clone. + return page_table; + + shared_page_decref(page_table_page_id); + + const spage_id_t new_page_table_page_id = shared_page_alloc(); + if (new_page_table_page_id < 0) + return NULL; + page_table_entry_t *const new_page_table = + pa_to_kernel_va(page_id_to_pa(new_page_table_page_id)); + + for (size_t i = 0; i < 512; i++) { + if (page_table[i].b0) { + const pa_t next_level_pa = page_table[i].addr << PAGE_ORDER; + const page_id_t next_level_page_id = pa_to_page_id(next_level_pa); + + shared_page_incref(next_level_page_id); + + union { + table_descriptor_upper_t s; + unsigned u; + } upper = {.u = page_table[i].upper}; + upper.s.aptable = 0x2; + page_table[i].upper = upper.u; + } + } + + memcpy(new_page_table, page_table, 1 << PAGE_ORDER); + + return new_page_table; +} + +static page_table_entry_t * +_vm_clone_unshare_pte(page_table_entry_t *const pte) { + const page_id_t pte_page_id = pa_to_page_id(kernel_va_to_pa(pte)); + + if (shared_page_getref(pte_page_id) == 1) // No need to clone. + return pte; + + shared_page_decref(pte_page_id); + + const spage_id_t new_pte_page_id = shared_page_alloc(); + if (new_pte_page_id < 0) + return NULL; + page_table_entry_t *const new_pte = + pa_to_kernel_va(page_id_to_pa(new_pte_page_id)); + + for (size_t i = 0; i < 512; i++) { + if (pte[i].b0) { + const pa_t next_level_pa = pte[i].addr << PAGE_ORDER; + const page_id_t next_level_page_id = pa_to_page_id(next_level_pa); + + shared_page_incref(next_level_page_id); + + union { + block_page_descriptor_lower_t s; + unsigned u; + } lower = {.u = pte[i].lower}; + lower.s.ap = 0x3; + pte[i].lower = lower.u; + } + } + + memcpy(new_pte, pte, 1 << PAGE_ORDER); + + return new_pte; +} + +static void _init_backed_page(const mem_region_t *const mem_region, + void *const va, void *const kernel_va) { + void *const va_page_start = + (void *)((uintptr_t)va & ~((1 << PAGE_ORDER) - 1)); + size_t offset = (char *)va_page_start - (char *)mem_region->start; + + const size_t copy_len = + offset > mem_region->backing_storage_len ? 0 + : mem_region->backing_storage_len - offset > 1 << PAGE_ORDER + ? 1 << PAGE_ORDER + : mem_region->backing_storage_len - offset; + memcpy(kernel_va, (char *)mem_region->backing_storage_start + offset, + copy_len); + memset((char *)kernel_va + copy_len, 0, (1 << PAGE_ORDER) - copy_len); +} + +static bool _map_page(const mem_region_t *const mem_region, void *const va, + page_table_entry_t *const pte_entry) { + switch (mem_region->type) { + case MEM_REGION_BACKED: { + const spage_id_t page_id = shared_page_alloc(); + if (page_id < 0) { + return false; + } + + const pa_t page_pa = page_id_to_pa(page_id); + pte_entry->addr = page_pa >> PAGE_ORDER; + + _init_backed_page(mem_region, va, pa_to_kernel_va(page_pa)); + + break; + } + + case MEM_REGION_LINEAR: { + const size_t offset = (uintptr_t)va - (uintptr_t)mem_region->start; + pte_entry->addr = + ((uintptr_t)mem_region->backing_storage_start + offset) >> PAGE_ORDER; + break; + } + + default: + __builtin_unreachable(); + } + + pte_entry->b0 = 1; + pte_entry->b1 = 1; + const union { + block_page_descriptor_lower_t s; + unsigned u; + } lower = {.s = (block_page_descriptor_lower_t){.ap = 0x1, .af = 1}}; + pte_entry->lower = lower.u; + + return true; +} + +static page_table_entry_t * +_vm_clone_unshare_pte_entry(vm_addr_space_t *const addr_space, void *const va) { + page_table_entry_t *page_table = + _vm_clone_unshare_page_table(addr_space->pgd); + if (!page_table) + return NULL; + addr_space->pgd = page_table; + page_table_entry_t *prev_level_entry = &page_table[(uintptr_t)va >> 39]; + + for (size_t level = 2; level > 0; level--) { + if (prev_level_entry->b0) { + page_table = _vm_clone_unshare_page_table( + pa_to_kernel_va(prev_level_entry->addr << PAGE_ORDER)); + if (!page_table) + return NULL; + } else { + const spage_id_t page_table_page_id = shared_page_alloc(); + if (page_table_page_id < 0) + return NULL; + page_table = pa_to_kernel_va(page_id_to_pa(page_table_page_id)); + memset(page_table, 0, 1 << PAGE_ORDER); + prev_level_entry->b0 = 1; + prev_level_entry->b1 = 1; + } + + // Since the page table is not shared, we can remove the read-only bit now. + + union { + table_descriptor_upper_t s; + unsigned u; + } upper = {.u = prev_level_entry->upper}; + upper.s.aptable = 0x0; + prev_level_entry->upper = upper.u; + + const pa_t page_table_pa = kernel_va_to_pa(page_table); + prev_level_entry->addr = page_table_pa >> 12; + prev_level_entry = + &page_table[((uintptr_t)va >> (12 + level * 9)) & ((1 << 9) - 1)]; + } + + if (prev_level_entry->b0) { + page_table = _vm_clone_unshare_pte( + pa_to_kernel_va(prev_level_entry->addr << PAGE_ORDER)); + if (!page_table) + return NULL; + } else { + const spage_id_t page_table_page_id = shared_page_alloc(); + if (page_table_page_id < 0) + return NULL; + page_table = pa_to_kernel_va(page_id_to_pa(page_table_page_id)); + memset(page_table, 0, 1 << PAGE_ORDER); + prev_level_entry->b0 = 1; + prev_level_entry->b1 = 1; + } + + // Since the page table is not shared, we can remove the read-only bit now. + + { + union { + table_descriptor_upper_t s; + unsigned u; + } upper = {.u = prev_level_entry->upper}; + upper.s.aptable = 0x0; + prev_level_entry->upper = upper.u; + } + + const pa_t page_table_pa = kernel_va_to_pa(page_table); + prev_level_entry->addr = page_table_pa >> 12; + prev_level_entry = &page_table[((uintptr_t)va >> 12) & ((1 << 9) - 1)]; + + return prev_level_entry; +} + +vm_map_page_result_t vm_map_page(vm_addr_space_t *const addr_space, + void *const va) { + // Check the validity of the VA. + + if (!((addr_space->text_region.start <= va && + va < (void *)((char *)addr_space->text_region.start + + addr_space->text_region.len)) || + (addr_space->stack_region.start <= va && + va < (void *)((char *)addr_space->stack_region.start + + addr_space->stack_region.len)) || + (addr_space->vc_region.start <= va && + va < (void *)((char *)addr_space->vc_region.start + + addr_space->vc_region.len)))) + return VM_MAP_PAGE_SEGV; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + // Walk the page table. + + page_table_entry_t *const pte_entry = + _vm_clone_unshare_pte_entry(addr_space, va); + if (!pte_entry) { + CRITICAL_SECTION_LEAVE(daif_val); + return VM_MAP_PAGE_NOMEM; + } + + // Map the page. + + mem_region_t *const region = + addr_space->text_region.start <= va && + va < (void *)((char *)addr_space->text_region.start + + addr_space->text_region.len) + ? &addr_space->text_region + : addr_space->stack_region.start <= va && + va < (void *)((char *)addr_space->stack_region.start + + addr_space->stack_region.len) + ? &addr_space->stack_region + : addr_space->vc_region.start <= va && + va < (void *)((char *)addr_space->vc_region.start + + addr_space->vc_region.len) + ? &addr_space->vc_region + : (__builtin_unreachable(), NULL); + if (!_map_page(region, va, pte_entry)) { + CRITICAL_SECTION_LEAVE(daif_val); + return VM_MAP_PAGE_NOMEM; + } + + // Set ttbr0_el1 again, as the PGD may have changed. + vm_switch_to_addr_space(addr_space); + + CRITICAL_SECTION_LEAVE(daif_val); + + return VM_MAP_PAGE_SUCCESS; +} + +static bool _cow_page(const mem_region_t *const mem_region, + page_table_entry_t *const pte_entry) { + switch (mem_region->type) { + case MEM_REGION_BACKED: { + const page_id_t src_page_id = pa_to_page_id(pte_entry->addr << PAGE_ORDER); + const spage_id_t page_id = shared_page_clone_unshare(src_page_id); + if (page_id < 0) + return false; + + const pa_t page_pa = page_id_to_pa(page_id); + pte_entry->addr = page_pa >> PAGE_ORDER; + + break; + } + + case MEM_REGION_LINEAR: { + // No-op. + break; + } + + default: + __builtin_unreachable(); + } + + const union { + block_page_descriptor_lower_t s; + unsigned u; + } lower = {.s = (block_page_descriptor_lower_t){.ap = 0x1, .af = 1}}; + pte_entry->lower = lower.u; + + return true; +} + +vm_map_page_result_t vm_cow(vm_addr_space_t *const addr_space, void *const va) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + // Walk the page table. + + page_table_entry_t *const pte_entry = + _vm_clone_unshare_pte_entry(addr_space, va); + if (!pte_entry) { + CRITICAL_SECTION_LEAVE(daif_val); + return VM_MAP_PAGE_NOMEM; + } + + // Map the page. + + mem_region_t *const region = + addr_space->text_region.start <= va && + va < (void *)((char *)addr_space->text_region.start + + addr_space->text_region.len) + ? &addr_space->text_region + : addr_space->stack_region.start <= va && + va < (void *)((char *)addr_space->stack_region.start + + addr_space->stack_region.len) + ? &addr_space->stack_region + : addr_space->vc_region.start <= va && + va < (void *)((char *)addr_space->vc_region.start + + addr_space->vc_region.len) + ? &addr_space->vc_region + : (__builtin_unreachable(), NULL); + if (!_cow_page(region, pte_entry)) { + CRITICAL_SECTION_LEAVE(daif_val); + return VM_MAP_PAGE_NOMEM; + } + + // Set ttbr0_el1 again, as the PGD may have changed. + vm_switch_to_addr_space(addr_space); + + CRITICAL_SECTION_LEAVE(daif_val); + + return VM_MAP_PAGE_SUCCESS; +} + +void vm_switch_to_addr_space(const vm_addr_space_t *const addr_space) { + const pa_t pgd_pa = kernel_va_to_pa(addr_space->pgd); + __asm__ __volatile__( + "dsb ish // Ensure writes have completed.\n" + "msr ttbr0_el1, %0 // Switch page table.\n" + "tlbi vmalle1is // Invalidate all TLB entries.\n" + "dsb ish // Ensure completion of TLB invalidatation.\n" + "isb // Clear pipeline." + : + : "r"((uint64_t)pgd_pa) + : "memory"); +} + +pa_t vm_translate_addr(const page_table_entry_t *const pgd, void *const va) { + const page_table_entry_t *page_table = pgd; + for (size_t level = 3; level > 0; level--) { + page_table = pa_to_kernel_va( + page_table[((uintptr_t)va >> (12 + 9 * level)) & ((1 << 9) - 1)].addr + << PAGE_ORDER); + } + return page_table[((uintptr_t)va >> 12) & ((1 << 9) - 1)].addr << PAGE_ORDER | + ((uintptr_t)va & ((1 << 12) - 1)); +} diff --git a/lab6/c/src/sched/run-signal-handler.S b/lab6/c/src/sched/run-signal-handler.S index 570339b05..d18f35efe 100644 --- a/lab6/c/src/sched/run-signal-handler.S +++ b/lab6/c/src/sched/run-signal-handler.S @@ -43,8 +43,6 @@ run_signal_handler: adr x2, sig_handler_main msr elr_el1, x2 - msr sp_el0, x1 - // Zero the integer registers except for x0, which is used to pass an // argument to sig_handler_main. diff --git a/lab6/c/src/sched/sched.c b/lab6/c/src/sched/sched.c index ebcf79e27..078c80425 100644 --- a/lab6/c/src/sched/sched.c +++ b/lab6/c/src/sched/sched.c @@ -6,6 +6,7 @@ #include "oscos/mem/malloc.h" #include "oscos/mem/page-alloc.h" #include "oscos/mem/vm.h" +#include "oscos/utils/align.h" #include "oscos/utils/critical-section.h" #include "oscos/utils/math.h" #include "oscos/utils/rb.h" @@ -22,7 +23,7 @@ void thread_main(void); noreturn void user_program_main(const void *init_pc, const void *init_user_sp, const void *init_kernel_sp); noreturn void fork_child_ret(void); -void run_signal_handler(sighandler_t handler, void *init_user_sp); +void run_signal_handler(sighandler_t handler); static size_t _sched_next_tid = 1, _sched_next_pid = 1; static thread_list_node_t _run_queue = {.prev = &_run_queue, @@ -49,20 +50,6 @@ static int _cmp_pid_and_processes_by_pid(const size_t *const pid, return pid1 < pid2 ? -1 : pid1 > pid2 ? 1 : 0; } -static size_t _calc_text_block_order(const size_t text_len) { - const size_t text_n_pages = - (text_len + ((1 << PAGE_ORDER) - 1)) >> PAGE_ORDER; - const size_t actual_text_block_order = clog2(text_n_pages); - - // We don't know exactly how large the .bss section of a user program is, so - // we have to use a heuristic. We speculate that the .bss section is at most - // as large as the remaining parts (.text, .rodata, and .data sections) of the - // user program. - const size_t allocating_text_block_order = actual_text_block_order + 1; - - return allocating_text_block_order; -} - static size_t _alloc_tid(void) { uint64_t daif_val; CRITICAL_SECTION_ENTER(daif_val); @@ -228,29 +215,16 @@ bool process_create(void) { if (!process) // Out of memory. return false; - const spage_id_t user_stack_page_id = alloc_pages(USER_STACK_BLOCK_ORDER); - if (user_stack_page_id < 0) { // Out of memory. - free(process); - return false; - } - -#ifdef SCHED_ENABLE_SHARED_USER_STACK - shared_page_t *const user_stack_page = shared_page_init(user_stack_page_id); - if (!user_stack_page) { - free_pages(user_stack_page_id); + thread_fp_simd_ctx_t *const fp_simd_ctx = + malloc(sizeof(thread_fp_simd_ctx_t)); + if (!fp_simd_ctx) { free(process); return false; } -#endif - thread_fp_simd_ctx_t *const fp_simd_ctx = - malloc(sizeof(thread_fp_simd_ctx_t)); - if (!fp_simd_ctx) { -#ifdef SCHED_ENABLE_SHARED_USER_STACK - shared_page_drop(user_stack_page); -#else - free_pages(user_stack_page_id); -#endif + page_table_entry_t *const pgd = vm_new_pgd(); + if (!pgd) { + free(fp_simd_ctx); free(process); return false; } @@ -260,12 +234,18 @@ bool process_create(void) { thread_t *const curr_thread = current_thread(); process->id = _alloc_pid(); -#ifdef SCHED_ENABLE_SHARED_USER_STACK - process->user_stack_page = user_stack_page; -#else - process->user_stack_page_id = user_stack_page_id; -#endif - process->signal_stack_pages = NULL; + process->addr_space.stack_region = + (mem_region_t){.start = (void *)0xffffffffb000ULL, + .len = 4 << PAGE_ORDER, + .type = MEM_REGION_BACKED, + .backing_storage_start = NULL, + .backing_storage_len = 0}; + process->addr_space.vc_region = + (mem_region_t){.start = (void *)0x3b400000ULL, + .len = 0x3f000000ULL - 0x3b400000ULL, + .type = MEM_REGION_LINEAR, + .backing_storage_start = (void *)0x3b400000ULL}; + process->addr_space.pgd = pgd; process->main_thread = curr_thread; process->pending_signals = 0; process->blocked_signals = 0; @@ -275,11 +255,6 @@ bool process_create(void) { curr_thread->process = process; curr_thread->ctx.fp_simd_ctx = fp_simd_ctx; - // Zero the stack pages. - - void *user_stack_start = pa_to_kernel_va(page_id_to_pa(user_stack_page_id)); - memset(user_stack_start, 0, 1 << USER_STACK_ORDER); - // Add the process to the process BST. uint64_t daif_val; @@ -294,83 +269,55 @@ bool process_create(void) { return true; } +void switch_vm(const thread_t *const thread) { + if (thread->process) { + vm_switch_to_addr_space(&thread->process->addr_space); + } +} + static void _exec_generic(const void *const text_start, const size_t text_len, - const bool free_text_page) { + const bool free_old_pages) { thread_t *const curr_thread = current_thread(); process_t *const curr_process = curr_thread->process; // Allocate memory. - const size_t text_block_order = _calc_text_block_order(text_len); - const spage_id_t text_page_id = alloc_pages(text_block_order); - if (text_page_id < 0) { - return; - } - - shared_page_t *const text_page = shared_page_init(text_page_id); - if (!text_page) { - free_pages(text_page_id); - return; + page_table_entry_t *pgd = NULL; + if (free_old_pages) { + pgd = vm_new_pgd(); + if (!pgd) + return; } - const spage_id_t user_stack_page_id = alloc_pages(USER_STACK_BLOCK_ORDER); - if (user_stack_page_id < 0) { - shared_page_drop(text_page); - return; - } - -#ifdef SCHED_ENABLE_SHARED_USER_STACK - shared_page_t *const user_stack_page = shared_page_init(user_stack_page_id); - if (!user_stack_page) { - free_pages(user_stack_page_id); - shared_page_drop(text_page); - return; - } -#endif - // Free old pages. - if (free_text_page) { - shared_page_drop(curr_process->text_page); + if (free_old_pages) { + vm_drop_pgd(curr_process->addr_space.pgd); } -#ifdef SCHED_ENABLE_SHARED_USER_STACK - shared_page_drop(curr_process->user_stack_page); -#else - free_pages(curr_process->user_stack_page_id); -#endif - // Set process data. - curr_process->text_page = text_page; -#ifdef SCHED_ENABLE_SHARED_USER_STACK - curr_process->user_stack_page = user_stack_page; -#else - curr_process->user_stack_page_id = user_stack_page_id; -#endif - - // Copy the user program. - - void *const user_program_start = pa_to_kernel_va(page_id_to_pa(text_page_id)); - memcpy(user_program_start, text_start, text_len); - - // Zero the remaining spaces of the text pages. - memset((char *)user_program_start + text_len, 0, - (1 << (text_block_order + PAGE_ORDER)) - text_len); - - // Zero the stack pages. - - void *user_stack_start = pa_to_kernel_va(page_id_to_pa(user_stack_page_id)); - memset(user_stack_start, 0, 1 << USER_STACK_ORDER); + if (free_old_pages) { + curr_process->addr_space.pgd = pgd; + } + // We don't know exactly how large the .bss section of a user program is, so + // we have to use a heuristic. We speculate that the .bss section is at most + // as large as the remaining parts (.text, .rodata, and .data sections) of the + // user program. + curr_process->addr_space.text_region = + (mem_region_t){.start = (void *)0x0, + .len = ALIGN(text_len * 2, 4096), + .type = MEM_REGION_BACKED, + .backing_storage_start = text_start, + .backing_storage_len = text_len}; // Run the user program. - void *const user_stack_end = - (char *)user_stack_start + (1 << USER_STACK_ORDER), - *const kernel_stack_end = (char *)pa_to_kernel_va(page_id_to_pa( - curr_thread->stack_page_id)) + - (1 << THREAD_STACK_ORDER); - user_program_main(user_program_start, user_stack_end, kernel_stack_end); + void *const kernel_stack_end = + (char *)pa_to_kernel_va(page_id_to_pa(curr_thread->stack_page_id)) + + (1 << THREAD_STACK_ORDER); + switch_vm(curr_thread); + user_program_main((void *)0x0, (void *)0xfffffffff000ULL, kernel_stack_end); } void exec_first(const void *const text_start, const size_t text_len) { @@ -383,20 +330,7 @@ void exec(const void *const text_start, const size_t text_len) { static void _thread_cleanup(thread_t *const thread) { if (thread->process) { - shared_page_drop(thread->process->text_page); -#ifdef SCHED_ENABLE_SHARED_USER_STACK - shared_page_drop(thread->process->user_stack_page); -#else - free_pages(thread->process->user_stack_page_id); -#endif - - page_id_list_node_t *curr_node = thread->process->signal_stack_pages; - while (curr_node) { - page_id_list_node_t *const next_node = curr_node->next; - free(curr_node); - curr_node = next_node; - } - + vm_drop_pgd(thread->process->addr_space.pgd); free(thread->process); } free_pages(thread->stack_page_id); @@ -435,17 +369,6 @@ process_t *fork(const extended_trap_frame_t *const trap_frame) { return NULL; } -#ifndef SCHED_ENABLE_SHARED_USER_STACK - const spage_id_t user_stack_page_id = alloc_pages(USER_STACK_BLOCK_ORDER); - if (user_stack_page_id < 0) { - free(fp_simd_ctx); - free_pages(kernel_stack_page_id); - free(new_process); - free(new_thread); - return NULL; - } -#endif - // Set data. new_thread->id = _alloc_tid(); @@ -457,14 +380,9 @@ process_t *fork(const extended_trap_frame_t *const trap_frame) { new_thread->process = new_process; new_process->id = _alloc_pid(); - new_process->text_page = shared_page_clone(curr_process->text_page); -#ifdef SCHED_ENABLE_SHARED_USER_STACK - new_process->user_stack_page = - shared_page_clone(curr_process->user_stack_page); -#else - new_process->user_stack_page_id = user_stack_page_id; -#endif - new_process->signal_stack_pages = NULL; + vm_clone_pgd(curr_process->addr_space.pgd); + vm_switch_to_addr_space(&curr_process->addr_space); + new_process->addr_space = curr_process->addr_space; new_process->main_thread = new_thread; new_process->pending_signals = 0; new_process->blocked_signals = 0; @@ -488,21 +406,6 @@ process_t *fork(const extended_trap_frame_t *const trap_frame) { memcpy(init_kernel_sp, trap_frame, sizeof(extended_trap_frame_t)); -#ifndef SCHED_ENABLE_SHARED_USER_STACK - // Setup the user stack. - - void *const old_user_stack_start = pa_to_kernel_va( - page_id_to_pa(curr_process->user_stack_page_id)), - *const user_stack_start = - pa_to_kernel_va(page_id_to_pa(user_stack_page_id)), - *const new_sp = - (char *)user_stack_start + ((char *)curr_thread->ctx.user_sp - - (char *)old_user_stack_start); - - memcpy(user_stack_start, old_user_stack_start, 1 << USER_STACK_ORDER); - new_thread->ctx.user_sp = (uint64_t)new_sp; -#endif - // Add the new thread to the end of the run queue and the process to the // process BST. Note that these two steps can be done in either order but must // not be interrupted in between, since: @@ -714,24 +617,10 @@ void handle_signals(void) { __asm__ __volatile__("mrs %0, spsr_el1" : "=r"(spsr_val)); __asm__ __volatile__("mrs %0, elr_el1" : "=r"(elr_val)); - // Save the old user SP, since the signal handlers will run in a new user - // stack. - - uint64_t old_user_sp; - __asm__ __volatile__("mrs %0, sp_el0" : "=r"(old_user_sp)); - // Save the status bit in order to support nested signal handling. const bool is_handling_signal = curr_thread->status.is_handling_signal; - // The user stack is allocated lazily. - - bool user_stack_allocation_attempted = false; - spage_id_t user_stack_page_id; - page_id_list_node_t *const curr_signal_stack_pages_node = - curr_process->signal_stack_pages, - *new_signal_stack_pages_node; - uint32_t deferred_signals = 0; for (uint32_t handleable_signals; (handleable_signals = curr_process->pending_signals & @@ -758,70 +647,24 @@ void handle_signals(void) { } else if (handler == SIG_IGN) { curr_process->pending_signals &= ~(1 << signal); } else { - // Allocate user stack. - - if (!user_stack_allocation_attempted) { - user_stack_allocation_attempted = true; - user_stack_page_id = alloc_pages(USER_STACK_BLOCK_ORDER); - - if (user_stack_page_id >= 0) { - new_signal_stack_pages_node = malloc(sizeof(page_id_list_node_t)); - if (new_signal_stack_pages_node) { - new_signal_stack_pages_node->page_id = user_stack_page_id; - new_signal_stack_pages_node->next = curr_signal_stack_pages_node; - curr_process->signal_stack_pages = new_signal_stack_pages_node; - } else { - free_pages(user_stack_page_id); - user_stack_page_id = -1; - } - } - } - - if (user_stack_page_id >= 0) { - void *const user_stack_start = - pa_to_kernel_va(page_id_to_pa(user_stack_page_id)), - *const user_stack_end = - (char *)user_stack_start + (1 << USER_STACK_ORDER); - - // Zero the user stack. + // Run the signal handler. - memset(user_stack_start, 0, 1 << USER_STACK_ORDER); + curr_thread->status.is_handling_signal = true; + curr_process->blocked_signals |= 1 << signal; - // Run the signal handler. + run_signal_handler(handler); - curr_thread->status.is_handling_signal = true; - curr_process->blocked_signals |= 1 << signal; - - run_signal_handler(handler, user_stack_end); - - curr_thread->status.is_handling_signal = false; - curr_process->pending_signals &= ~(1 << signal); - curr_process->blocked_signals &= ~(1 << signal); - } else { - // Defer the handling of this signal. Hopefully, there will be enough - // memory to run the signal handler in the future. - deferred_signals |= 1 << signal; - } + curr_thread->status.is_handling_signal = false; + curr_process->pending_signals &= ~(1 << signal); + curr_process->blocked_signals &= ~(1 << signal); } } - // Destroy the user stack. - - if (user_stack_allocation_attempted && user_stack_page_id >= 0) { - free_pages(user_stack_page_id); - curr_process->signal_stack_pages = curr_signal_stack_pages_node; - free(new_signal_stack_pages_node); - } - // Restore spsr_el1 and elr_el1. __asm__ __volatile__("msr spsr_el1, %0" : : "r"(spsr_val)); __asm__ __volatile__("msr elr_el1, %0" : : "r"(elr_val)); - // Restore the user SP. - - __asm__ __volatile__("msr sp_el0, %0" : : "r"(old_user_sp)); - // Restore the status bit. curr_thread->status.is_handling_signal = is_handling_signal; diff --git a/lab6/c/src/sched/schedule.S b/lab6/c/src/sched/schedule.S index 51d7213b5..b16ff658a 100644 --- a/lab6/c/src/sched/schedule.S +++ b/lab6/c/src/sched/schedule.S @@ -59,6 +59,10 @@ _suspend_to_wait_queue: .global _suspend_to_wait_queue _sched_run_thread: + mov x19, x0 + bl switch_vm + mov x0, x19 + // Restore integer context. ldp x19, x20, [x0, 16 + 0 * 16] diff --git a/lab6/c/src/xcpt/data-abort-handler.c b/lab6/c/src/xcpt/data-abort-handler.c new file mode 100644 index 000000000..cb0a9765d --- /dev/null +++ b/lab6/c/src/xcpt/data-abort-handler.c @@ -0,0 +1,55 @@ +#include "oscos/console.h" +#include "oscos/panic.h" +#include "oscos/sched.h" + +void xcpt_default_handler(uint64_t vten); + +void xcpt_data_abort_handler(const uint64_t esr_val) { + thread_t *const curr_thread = current_thread(); + process_t *const curr_process = curr_thread->process; + + void *fault_addr; + __asm__ __volatile__("mrs %0, far_el1" : "=r"(fault_addr)); + + const uint64_t dfsc = esr_val & ((1 << 6) - 1); + + if (dfsc >> 2 == 0x1) { // Translation fault. + const vm_map_page_result_t result = + vm_map_page(&curr_process->addr_space, fault_addr); + if (result == VM_MAP_PAGE_SEGV) { +#ifdef VM_ENABLE_DEBUG_LOG + console_printf("DEBUG: vm: Segmentation fault, PID %zu, address 0x%p\n", + curr_process->id, fault_addr); +#endif + deliver_signal(curr_process, SIGSEGV); + } else if (result == VM_MAP_PAGE_NOMEM) { + // For a lack of better things to do. + thread_exit(); + } else { +#ifdef VM_ENABLE_DEBUG_LOG + console_printf("DEBUG: vm: Translation fault, PID %zu, address 0x%p\n", + curr_process->id, fault_addr); +#endif + } + } else if (dfsc >> 2 == 0x3) { // Permission fault. + const vm_map_page_result_t result = + vm_cow(&curr_process->addr_space, fault_addr); + if (result == VM_MAP_PAGE_SEGV) { +#ifdef VM_ENABLE_DEBUG_LOG + console_printf("DEBUG: vm: Segmentation fault, PID %zu, address 0x%p\n", + curr_process->id, fault_addr); +#endif + deliver_signal(curr_process, SIGSEGV); + } else if (result == VM_MAP_PAGE_NOMEM) { + // For a lack of better things to do. + thread_exit(); + } else { +#ifdef VM_ENABLE_DEBUG_LOG + console_printf("DEBUG: vm: CoW fault, PID %zu, address 0x%p\n", + curr_process->id, fault_addr); +#endif + } + } else { + xcpt_default_handler(8); + } +} diff --git a/lab6/c/src/xcpt/default-handler.c b/lab6/c/src/xcpt/default-handler.c index 3be7eb3ed..e2eba4385 100644 --- a/lab6/c/src/xcpt/default-handler.c +++ b/lab6/c/src/xcpt/default-handler.c @@ -9,9 +9,9 @@ void xcpt_default_handler(const uint64_t vten) { __asm__ __volatile__("mrs %0, esr_el1" : "=r"(esr_val)); PANIC("Unhandled exception\n" - "VTEN: 0x%8.1" PRIx64 "\n" - "spsr_el1: 0x%.8" PRIx64 "\n" - "elr_el1: 0x%.8" PRIx64 "\n" - "esr_el1: 0x%.8" PRIx64, + "VTEN: 0x%16.1" PRIx64 "\n" + "spsr_el1: 0x%16.8" PRIx64 "\n" + "elr_el1: 0x%16.1" PRIx64 "\n" + "esr_el1: 0x%16.8" PRIx64, vten, spsr_val, elr_val, esr_val); } diff --git a/lab6/c/src/xcpt/insn-abort-handler.c b/lab6/c/src/xcpt/insn-abort-handler.c new file mode 100644 index 000000000..d70ff4b7a --- /dev/null +++ b/lab6/c/src/xcpt/insn-abort-handler.c @@ -0,0 +1,55 @@ +#include "oscos/console.h" +#include "oscos/panic.h" +#include "oscos/sched.h" + +void xcpt_default_handler(uint64_t vten); + +void xcpt_insn_abort_handler(const uint64_t esr_val) { + thread_t *const curr_thread = current_thread(); + process_t *const curr_process = curr_thread->process; + + void *fault_addr; + __asm__ __volatile__("mrs %0, far_el1" : "=r"(fault_addr)); + + const uint64_t ifsc = esr_val & ((1 << 6) - 1); + + if (ifsc >> 2 == 0x1) { // Translation fault. + const vm_map_page_result_t result = + vm_map_page(&curr_process->addr_space, fault_addr); + if (result == VM_MAP_PAGE_SEGV) { +#ifdef VM_ENABLE_DEBUG_LOG + console_printf("DEBUG: vm: Segmentation fault, PID %zu, address 0x%p\n", + curr_process->id, fault_addr); +#endif + deliver_signal(curr_process, SIGSEGV); + } else if (result == VM_MAP_PAGE_NOMEM) { + // For a lack of better things to do. + thread_exit(); + } else { +#ifdef VM_ENABLE_DEBUG_LOG + console_printf("DEBUG: vm: Translation fault, PID %zu, address 0x%p\n", + curr_process->id, fault_addr); +#endif + } + } else if (ifsc >> 2 == 0x3) { // Permission fault. + const vm_map_page_result_t result = + vm_cow(&curr_process->addr_space, fault_addr); + if (result == VM_MAP_PAGE_SEGV) { +#ifdef VM_ENABLE_DEBUG_LOG + console_printf("DEBUG: vm: Segmentation fault, PID %zu, address 0x%p\n", + curr_process->id, fault_addr); +#endif + deliver_signal(curr_process, SIGSEGV); + } else if (result == VM_MAP_PAGE_NOMEM) { + // For a lack of better things to do. + thread_exit(); + } else { +#ifdef VM_ENABLE_DEBUG_LOG + console_printf("DEBUG: vm: CoW fault, PID %zu, address 0x%p\n", + curr_process->id, fault_addr); +#endif + } + } else { + xcpt_default_handler(8); + } +} diff --git a/lab6/c/src/xcpt/load-aapcs-and-eret.S b/lab6/c/src/xcpt/load-aapcs-and-eret.S new file mode 100644 index 000000000..b7ba5550f --- /dev/null +++ b/lab6/c/src/xcpt/load-aapcs-and-eret.S @@ -0,0 +1,11 @@ +#include "oscos/utils/save-ctx.S" + +.section ".text" + +load_aapcs_and_eret: + load_aapcs + eret + +.type load_aapcs_and_eret, function +.size load_aapcs_and_eret, . - load_aapcs_and_eret +.global load_aapcs_and_eret diff --git a/lab6/c/src/xcpt/syscall/mbox-call.c b/lab6/c/src/xcpt/syscall/mbox-call.c index c68a9a092..dbf7732f4 100644 --- a/lab6/c/src/xcpt/syscall/mbox-call.c +++ b/lab6/c/src/xcpt/syscall/mbox-call.c @@ -1,7 +1,12 @@ #include +#include #include +#include "oscos/console.h" #include "oscos/drivers/mailbox.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/mem/vm.h" +#include "oscos/sched.h" #include "oscos/uapi/errno.h" #include "oscos/utils/critical-section.h" @@ -25,6 +30,24 @@ int sys_mbox_call(const unsigned char ch, unsigned int *const mbox) { if (!_is_valid_mbox_ptr(mbox)) return /* -EINVAL */ 0; + process_t *const curr_process = current_thread()->process; + + // CoW-fault the mailbox memory, so that it is not shared with any other + // address spaces. + + const size_t mbox_buf_len = mbox[0]; + for (size_t offset = 0; offset < mbox_buf_len; offset += 1 << PAGE_ORDER) { + const vm_map_page_result_t result = + vm_cow(&curr_process->addr_space, (char *)mbox + offset); + if (result == VM_MAP_PAGE_SEGV) { + deliver_signal(curr_process, SIGSEGV); + return 0; + } else if (result == VM_MAP_PAGE_NOMEM) { + // For a lack of better things to do. + thread_exit(); + } + } + uint64_t daif_val; CRITICAL_SECTION_ENTER(daif_val); diff --git a/lab6/c/src/xcpt/vector-table.S b/lab6/c/src/xcpt/vector-table.S index 1ab6922ea..eedfe5f6b 100644 --- a/lab6/c/src/xcpt/vector-table.S +++ b/lab6/c/src/xcpt/vector-table.S @@ -35,7 +35,40 @@ xcpt_vector_table: // Exception from the current EL while using SP_ELx. // Synchronous. - default_vt_entry 0x4 xcpt_sync_curr_el_sp_elx_handler +xcpt_sync_curr_el_sp_elx_handler: + save_aapcs + + // Check if the exception is caused by an instruction abort without a change + // in exception level. + mrs x0, esr_el1 + ubfx x1, x0, 26, 6 + cmp x1, 0x21 + b.ne .Lxcpt_sync_curr_el_sp_elx_handler_not_insn_abort + + bl xcpt_insn_abort_handler + b .Lxcpt_sync_curr_el_sp_elx_handler_end + +.Lxcpt_sync_curr_el_sp_elx_handler_not_insn_abort: + // Check if the exception is caused by a data abort without a change in + // exception level. + cmp x1, 0x25 + b.ne .Lxcpt_sync_curr_el_sp_elx_handler_not_data_abort + + bl xcpt_data_abort_handler + b .Lxcpt_sync_curr_el_sp_elx_handler_end + +.Lxcpt_sync_curr_el_sp_elx_handler_not_data_abort: + mov x0, 0x4 + bl xcpt_default_handler + +.Lxcpt_sync_curr_el_sp_elx_handler_end: + // We have to split the handler, since it uses more than 32 instructions. + b load_aapcs_and_eret + +.type xcpt_sync_curr_el_sp_elx_handler, function +.size xcpt_sync_curr_el_sp_elx_handler, . - xcpt_sync_curr_el_sp_elx_handler + +.align 7 // IRQ. xcpt_irq_curr_el_sp_elx_handler: @@ -74,13 +107,34 @@ xcpt_sync_lower_el_aarch64_handler: .Lxcpt_sync_lower_el_aarch64_handler_not_svc: // The exception is not caused by svc. + + // Check if the exception is caused by an instruction abort from a lower + // exception level. + cmp x10, 0x20 + b.ne .Lxcpt_sync_lower_el_aarch64_handler_not_insn_abort + + mov x0, x9 + bl xcpt_insn_abort_handler + b .Lxcpt_sync_lower_el_aarch64_handler_end + +.Lxcpt_sync_lower_el_aarch64_handler_not_insn_abort: + // Check if the exception is caused by an data abort from a lower exception + // level. + cmp x10, 0x24 + b.ne .Lxcpt_sync_lower_el_aarch64_handler_not_data_abort + + mov x0, x9 + bl xcpt_data_abort_handler + b .Lxcpt_sync_lower_el_aarch64_handler_end + +.Lxcpt_sync_lower_el_aarch64_handler_not_data_abort: mov x0, 0x8 bl xcpt_default_handler .Lxcpt_sync_lower_el_aarch64_handler_end: bl handle_signals - load_aapcs - eret + // We have to split the handler, since it uses more than 32 instructions. + b load_aapcs_and_eret .type xcpt_sync_lower_el_aarch64_handler, function .size xcpt_sync_lower_el_aarch64_handler, . - xcpt_sync_lower_el_aarch64_handler diff --git a/lab6/tests/build-initrd-video-player.sh b/lab6/tests/build-initrd-video-player.sh deleted file mode 100755 index 815b728a1..000000000 --- a/lab6/tests/build-initrd-video-player.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -rm -f initramfs.cpio -wget https://oscapstone.github.io/_downloads/58c515e3041658a045033c8e56ecff4c/initramfs.cpio diff --git a/lab6/tests/build-initrd.sh b/lab6/tests/build-initrd.sh index fb15ec3b2..88be4059b 100755 --- a/lab6/tests/build-initrd.sh +++ b/lab6/tests/build-initrd.sh @@ -1,16 +1,10 @@ #!/bin/sh -e -if [ -e rootfs ]; then - rm -r rootfs -fi - -progs='syscall_test' +rm -rf rootfs mkdir rootfs -for prog in $progs; do - make -C "user-program/$prog" PROFILE=RELEASE - cp "user-program/$prog/build/RELEASE/$prog.img" "rootfs/$prog" -done +wget https://oscapstone.github.io/_downloads/4a3ff2431ab7fa74536c184270dbe5c0/vm.img \ + -O rootfs/vm.img cd rootfs find . -mindepth 1 | cpio -o -H newc > ../initramfs.cpio diff --git a/lab6/tests/user-program/syscall_test/src/linker.ld b/lab6/tests/user-program/syscall_test/src/linker.ld index 255954576..79510d671 100644 --- a/lab6/tests/user-program/syscall_test/src/linker.ld +++ b/lab6/tests/user-program/syscall_test/src/linker.ld @@ -1,34 +1,11 @@ -/* -Memory map: (End addresses are exclusive) -0x 0 ( 0B) - 0x 80000 (512K): Reserved for firmware -0x 80000 (512K) - 0x 4000000 ( 64M): Kernel text, rodata, data, bss, heap -0x 4000000 ( 64M) - 0x 8000000 (128M): User program -0x 8000000 (128M) - 0x3a400000 (932M): Reserved for initramfs -0x3a400000 (932M) - 0x3ac00000 (940M): User program stack -0x3ac00000 (940M) - 0x3b400000 (948M): Kernel stack -*/ - -_skernel = 0x80000; -_max_ekernel = 0x4000000; -_suser = 64M; -_max_euser = 128M; - -MEMORY -{ - RAM_USER : ORIGIN = _suser, LENGTH = _max_euser - _suser -} - -/* Must be 16-byte aligned. - The highest address the ARM core can use. Total system SDRAM is 1G. Top 76M - are reserved for VideoCore. */ -_estack = 1024M - 76M; -/* Again, must be 16-byte aligned. */ -_euserstack = _estack - 8M; +_suser = 0; ENTRY(_start) SECTIONS { + . = _suser; + .text : { _stext = .; @@ -42,7 +19,7 @@ SECTIONS *(.eh_frame_hdr) _etext = .; - } >RAM_USER + } .rodata : { @@ -51,7 +28,7 @@ SECTIONS *(.rodata .rodata.*) _erodata = .; - } >RAM_USER + } .data : { @@ -60,21 +37,17 @@ SECTIONS *(.data .data.*) _edata = .; - } >RAM_USER + } - /* The .bss section is 16-byte aligned to allow the section to be - zero-initialized with the `stp` instruction without using unaligned - memory accesses. See `start.S`. */ - .bss : ALIGN(16) + .bss : { _sbss = .; *(.bss .bss.*) *(COMMON) - . = ALIGN(16); _ebss = .; - } >RAM_USER + } - _ekernel = .; + _euser = .; } From bcfa76b44637f564b77e55124d9885a364e4d3f6 Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Wed, 28 Jun 2023 10:29:57 +0800 Subject: [PATCH 12/34] Lab 6 C: Fix memory attributes - User-space memory is now mapped as normal memory instead of device memory. - Kernel memory now disallows non-privileged access. --- lab6/c/src/mem/vm.c | 3 ++- lab6/c/src/mem/vm/kernel-page-tables.c | 14 ++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lab6/c/src/mem/vm.c b/lab6/c/src/mem/vm.c index ae605bb16..31d946345 100644 --- a/lab6/c/src/mem/vm.c +++ b/lab6/c/src/mem/vm.c @@ -212,7 +212,8 @@ static bool _map_page(const mem_region_t *const mem_region, void *const va, const union { block_page_descriptor_lower_t s; unsigned u; - } lower = {.s = (block_page_descriptor_lower_t){.ap = 0x1, .af = 1}}; + } lower = {.s = (block_page_descriptor_lower_t){ + .attr_indx = 0x1, .ap = 0x1, .af = 1}}; pte_entry->lower = lower.u; return true; diff --git a/lab6/c/src/mem/vm/kernel-page-tables.c b/lab6/c/src/mem/vm/kernel-page-tables.c index 93338b811..5fee7a55b 100644 --- a/lab6/c/src/mem/vm/kernel-page-tables.c +++ b/lab6/c/src/mem/vm/kernel-page-tables.c @@ -20,12 +20,14 @@ alignas(4096) page_table_t kernel_pud = {{.b0 = 1, .addr = 0x40000, .upper = 0}}; -alignas(4096) page_table_t kernel_pgd = { - {.b0 = 1, - .b1 = 1, - .lower = 0, - .addr = 0, // The concrete value will be filled by `start.S`. - .upper = 0}}; +alignas(4096) page_table_t kernel_pgd = {{ + .b0 = 1, + .b1 = 1, + .lower = 0, + .addr = 0, // The concrete value will be filled by `start.S`. + .upper = 0x1 << (61 - 48) // Unprivileged access not permitted + // (APTable = 0b01). +}}; static void _map_region_as_rec(const page_id_range_t range, const block_page_descriptor_lower_t lower_attr, From f3ee536905a1fb2d562f501d696763fc276a5eb6 Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Wed, 28 Jun 2023 20:15:45 +0800 Subject: [PATCH 13/34] Lab 6 C: Fix `sys_mbox_call` `sys_mbox_call` now copies the mailbox buffer into the kernel before calling `mailbox_call`. This is because it is generally unsafe to pass the user-space buffer directly, since it may be discontinuous physically. --- lab6/c/include/oscos/mem/vm.h | 1 - lab6/c/src/drivers/mailbox.c | 11 +--------- lab6/c/src/mem/vm.c | 11 ---------- lab6/c/src/xcpt/syscall/mbox-call.c | 32 ++++++++--------------------- 4 files changed, 10 insertions(+), 45 deletions(-) diff --git a/lab6/c/include/oscos/mem/vm.h b/lab6/c/include/oscos/mem/vm.h index 261b0c10d..3ee64e514 100644 --- a/lab6/c/include/oscos/mem/vm.h +++ b/lab6/c/include/oscos/mem/vm.h @@ -43,6 +43,5 @@ void vm_drop_pgd(page_table_entry_t *pgd); vm_map_page_result_t vm_map_page(vm_addr_space_t *addr_space, void *va); vm_map_page_result_t vm_cow(vm_addr_space_t *addr_space, void *va); void vm_switch_to_addr_space(const vm_addr_space_t *addr_space); -pa_t vm_translate_addr(const page_table_entry_t *pgd, void *va); #endif diff --git a/lab6/c/src/drivers/mailbox.c b/lab6/c/src/drivers/mailbox.c index eb9bad4ef..0cb84d408 100644 --- a/lab6/c/src/drivers/mailbox.c +++ b/lab6/c/src/drivers/mailbox.c @@ -4,13 +4,9 @@ #include "oscos/drivers/board.h" #include "oscos/mem/vm.h" -#include "oscos/sched.h" #define MAILBOX_REG_BASE ((void *)((char *)PERIPHERAL_BASE + 0xb880)) -// Symbols defined in the linker script. -extern char _kernel_vm_base[]; - typedef struct { volatile uint32_t read_write; const volatile uint32_t _reserved[3]; @@ -40,12 +36,7 @@ void mailbox_init(void) { void mailbox_call(uint32_t message[], const unsigned char channel) { PERIPHERAL_WRITE_BARRIER(); - const pa_t message_pa = - (char *)message >= _kernel_vm_base - ? kernel_va_to_pa(message) - : vm_translate_addr(current_thread()->process->addr_space.pgd, - message); - const uint32_t mailbox_write_data = message_pa | channel; + const uint32_t mailbox_write_data = kernel_va_to_pa(message) | channel; while (MAILBOX_REGS[1].status & MAILBOX_STATUS_FULL_MASK) ; diff --git a/lab6/c/src/mem/vm.c b/lab6/c/src/mem/vm.c index 31d946345..2f70a5a0a 100644 --- a/lab6/c/src/mem/vm.c +++ b/lab6/c/src/mem/vm.c @@ -435,14 +435,3 @@ void vm_switch_to_addr_space(const vm_addr_space_t *const addr_space) { : "r"((uint64_t)pgd_pa) : "memory"); } - -pa_t vm_translate_addr(const page_table_entry_t *const pgd, void *const va) { - const page_table_entry_t *page_table = pgd; - for (size_t level = 3; level > 0; level--) { - page_table = pa_to_kernel_va( - page_table[((uintptr_t)va >> (12 + 9 * level)) & ((1 << 9) - 1)].addr - << PAGE_ORDER); - } - return page_table[((uintptr_t)va >> 12) & ((1 << 9) - 1)].addr << PAGE_ORDER | - ((uintptr_t)va & ((1 << 12) - 1)); -} diff --git a/lab6/c/src/xcpt/syscall/mbox-call.c b/lab6/c/src/xcpt/syscall/mbox-call.c index dbf7732f4..415db0e96 100644 --- a/lab6/c/src/xcpt/syscall/mbox-call.c +++ b/lab6/c/src/xcpt/syscall/mbox-call.c @@ -1,12 +1,9 @@ #include -#include #include -#include "oscos/console.h" #include "oscos/drivers/mailbox.h" -#include "oscos/mem/page-alloc.h" -#include "oscos/mem/vm.h" -#include "oscos/sched.h" +#include "oscos/libc/string.h" +#include "oscos/mem/malloc.h" #include "oscos/uapi/errno.h" #include "oscos/utils/critical-section.h" @@ -30,30 +27,19 @@ int sys_mbox_call(const unsigned char ch, unsigned int *const mbox) { if (!_is_valid_mbox_ptr(mbox)) return /* -EINVAL */ 0; - process_t *const curr_process = current_thread()->process; - - // CoW-fault the mailbox memory, so that it is not shared with any other - // address spaces. - - const size_t mbox_buf_len = mbox[0]; - for (size_t offset = 0; offset < mbox_buf_len; offset += 1 << PAGE_ORDER) { - const vm_map_page_result_t result = - vm_cow(&curr_process->addr_space, (char *)mbox + offset); - if (result == VM_MAP_PAGE_SEGV) { - deliver_signal(curr_process, SIGSEGV); - return 0; - } else if (result == VM_MAP_PAGE_NOMEM) { - // For a lack of better things to do. - thread_exit(); - } - } + const size_t mbox_len = mbox[0]; + uint32_t *const mbox_kernel = malloc(mbox_len); + memcpy(mbox_kernel, mbox, mbox_len); uint64_t daif_val; CRITICAL_SECTION_ENTER(daif_val); - mailbox_call(mbox, ch); + mailbox_call(mbox_kernel, ch); CRITICAL_SECTION_LEAVE(daif_val); + memcpy(mbox, mbox_kernel, mbox_len); + free(mbox_kernel); + return /* 0 */ 1; } From c5925d161ed976b0f9344622a9960a58979027c1 Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Wed, 28 Jun 2023 20:26:32 +0800 Subject: [PATCH 14/34] Lab 7 C: Import kernel from lab 6 --- lab7/c/.clang-format | 1 + lab7/c/.gitignore | 1 + lab7/c/Makefile | 105 +++ lab7/c/include/oscos/console.h | 144 ++++ lab7/c/include/oscos/devicetree.h | 173 +++++ lab7/c/include/oscos/drivers/aux.h | 8 + lab7/c/include/oscos/drivers/board.h | 20 + lab7/c/include/oscos/drivers/gpio.h | 8 + lab7/c/include/oscos/drivers/l1ic.h | 24 + lab7/c/include/oscos/drivers/l2ic.h | 14 + lab7/c/include/oscos/drivers/mailbox.h | 19 + lab7/c/include/oscos/drivers/mini-uart.h | 14 + lab7/c/include/oscos/drivers/pm.h | 14 + lab7/c/include/oscos/initrd.h | 117 +++ lab7/c/include/oscos/libc/ctype.h | 6 + lab7/c/include/oscos/libc/inttypes.h | 9 + lab7/c/include/oscos/libc/stdio.h | 12 + lab7/c/include/oscos/libc/stdlib.h | 11 + lab7/c/include/oscos/libc/string.h | 25 + lab7/c/include/oscos/mem/malloc.h | 30 + lab7/c/include/oscos/mem/page-alloc.h | 87 +++ lab7/c/include/oscos/mem/shared-page.h | 12 + lab7/c/include/oscos/mem/startup-alloc.h | 34 + lab7/c/include/oscos/mem/types.h | 42 ++ lab7/c/include/oscos/mem/vm.h | 47 ++ .../include/oscos/mem/vm/kernel-page-tables.h | 6 + lab7/c/include/oscos/mem/vm/page-table.h | 42 ++ lab7/c/include/oscos/panic.h | 23 + lab7/c/include/oscos/sched.h | 161 +++++ lab7/c/include/oscos/shell.h | 6 + lab7/c/include/oscos/timer/delay.h | 11 + lab7/c/include/oscos/timer/timeout.h | 17 + lab7/c/include/oscos/uapi/errno.h | 10 + lab7/c/include/oscos/uapi/signal.h | 43 ++ lab7/c/include/oscos/uapi/sys/syscall.h | 16 + lab7/c/include/oscos/uapi/unistd.h | 6 + lab7/c/include/oscos/utils/align.h | 13 + lab7/c/include/oscos/utils/control-flow.h | 11 + lab7/c/include/oscos/utils/core-id.h | 8 + lab7/c/include/oscos/utils/critical-section.h | 20 + lab7/c/include/oscos/utils/endian.h | 21 + lab7/c/include/oscos/utils/fmt.h | 15 + lab7/c/include/oscos/utils/heapq.h | 14 + lab7/c/include/oscos/utils/math.h | 13 + lab7/c/include/oscos/utils/rb.h | 26 + lab7/c/include/oscos/utils/save-ctx.S | 25 + lab7/c/include/oscos/utils/suspend.h | 19 + lab7/c/include/oscos/utils/time.h | 6 + lab7/c/include/oscos/xcpt.h | 9 + lab7/c/include/oscos/xcpt/task-queue.h | 8 + lab7/c/include/oscos/xcpt/trap-frame.h | 24 + lab7/c/src/console.c | 388 ++++++++++ lab7/c/src/devicetree.c | 129 ++++ lab7/c/src/drivers/aux.c | 28 + lab7/c/src/drivers/gpio.c | 105 +++ lab7/c/src/drivers/l1ic.c | 37 + lab7/c/src/drivers/l2ic.c | 36 + lab7/c/src/drivers/mailbox.c | 84 +++ lab7/c/src/drivers/mini-uart.c | 130 ++++ lab7/c/src/drivers/pm.c | 85 +++ lab7/c/src/initrd.c | 131 ++++ lab7/c/src/libc/ctype.c | 3 + lab7/c/src/libc/stdio.c | 41 ++ lab7/c/src/libc/stdlib/qsort.c | 154 ++++ lab7/c/src/libc/string.c | 99 +++ lab7/c/src/linker.ld | 78 ++ lab7/c/src/main.c | 71 ++ lab7/c/src/mem/malloc.c | 353 +++++++++ lab7/c/src/mem/page-alloc.c | 662 +++++++++++++++++ lab7/c/src/mem/shared-page.c | 147 ++++ lab7/c/src/mem/startup-alloc.c | 24 + lab7/c/src/mem/vm.c | 437 ++++++++++++ lab7/c/src/mem/vm/kernel-page-tables.c | 114 +++ lab7/c/src/panic.c | 24 + lab7/c/src/sched/idle-thread.c | 8 + lab7/c/src/sched/periodic-sched.c | 33 + lab7/c/src/sched/run-signal-handler.S | 119 ++++ lab7/c/src/sched/sched.c | 671 ++++++++++++++++++ lab7/c/src/sched/schedule.S | 115 +++ lab7/c/src/sched/sig-handler-main.S | 10 + lab7/c/src/sched/thread-main.S | 10 + lab7/c/src/sched/user-program-main.S | 93 +++ lab7/c/src/shell.c | 335 +++++++++ lab7/c/src/start.S | 120 ++++ lab7/c/src/timer/delay.c | 15 + lab7/c/src/timer/timeout.c | 114 +++ lab7/c/src/utils/core-id.c | 9 + lab7/c/src/utils/fmt.c | 653 +++++++++++++++++ lab7/c/src/utils/heapq.c | 77 ++ lab7/c/src/utils/rb.c | 83 +++ lab7/c/src/xcpt/data-abort-handler.c | 55 ++ lab7/c/src/xcpt/default-handler.c | 17 + lab7/c/src/xcpt/insn-abort-handler.c | 55 ++ lab7/c/src/xcpt/irq-handler.c | 43 ++ lab7/c/src/xcpt/load-aapcs-and-eret.S | 11 + lab7/c/src/xcpt/svc-handler.S | 50 ++ lab7/c/src/xcpt/syscall-table.S | 17 + lab7/c/src/xcpt/syscall/enosys.c | 3 + lab7/c/src/xcpt/syscall/exec.c | 44 ++ lab7/c/src/xcpt/syscall/exit.c | 3 + lab7/c/src/xcpt/syscall/fork-child-ret.S | 29 + lab7/c/src/xcpt/syscall/fork-impl.c | 11 + lab7/c/src/xcpt/syscall/fork.S | 48 ++ lab7/c/src/xcpt/syscall/getpid.c | 4 + lab7/c/src/xcpt/syscall/kill.c | 17 + lab7/c/src/xcpt/syscall/mbox-call.c | 45 ++ lab7/c/src/xcpt/syscall/signal-kill.c | 25 + lab7/c/src/xcpt/syscall/signal.c | 15 + lab7/c/src/xcpt/syscall/sigreturn-check.c | 13 + lab7/c/src/xcpt/syscall/sigreturn.S | 50 ++ lab7/c/src/xcpt/syscall/uart-read.c | 46 ++ lab7/c/src/xcpt/syscall/uart-write.c | 45 ++ lab7/c/src/xcpt/task-queue.c | 97 +++ lab7/c/src/xcpt/vector-table.S | 179 +++++ lab7/c/src/xcpt/xcpt.c | 7 + lab7/tests/.gitignore | 2 + lab7/tests/build-initrd.sh | 13 + lab7/tests/get-dtb.sh | 5 + lab7/tests/user-program/libc/.gitignore | 1 + lab7/tests/user-program/libc/Makefile | 61 ++ .../libc/include/__detail/utils/fmt.h | 15 + lab7/tests/user-program/libc/include/ctype.h | 6 + lab7/tests/user-program/libc/include/errno.h | 9 + lab7/tests/user-program/libc/include/mbox.h | 6 + .../user-program/libc/include/oscos-uapi | 1 + lab7/tests/user-program/libc/include/signal.h | 9 + lab7/tests/user-program/libc/include/stdio.h | 12 + lab7/tests/user-program/libc/include/stdlib.h | 8 + lab7/tests/user-program/libc/include/string.h | 25 + .../user-program/libc/include/sys/syscall.h | 1 + lab7/tests/user-program/libc/include/unistd.h | 21 + .../libc/src/__detail/utils/fmt.c | 654 +++++++++++++++++ lab7/tests/user-program/libc/src/ctype.c | 3 + lab7/tests/user-program/libc/src/errno.c | 3 + lab7/tests/user-program/libc/src/mbox.c | 8 + lab7/tests/user-program/libc/src/signal.c | 13 + lab7/tests/user-program/libc/src/start.S | 15 + lab7/tests/user-program/libc/src/stdio.c | 31 + lab7/tests/user-program/libc/src/stdlib.c | 9 + lab7/tests/user-program/libc/src/string.c | 99 +++ lab7/tests/user-program/libc/src/unistd.c | 19 + .../user-program/libc/src/unistd/syscall.S | 30 + .../user-program/syscall_test/.gitignore | 1 + lab7/tests/user-program/syscall_test/Makefile | 73 ++ .../user-program/syscall_test/src/linker.ld | 53 ++ .../user-program/syscall_test/src/main.c | 61 ++ 146 files changed, 9571 insertions(+) create mode 100644 lab7/c/.clang-format create mode 100644 lab7/c/.gitignore create mode 100644 lab7/c/Makefile create mode 100644 lab7/c/include/oscos/console.h create mode 100644 lab7/c/include/oscos/devicetree.h create mode 100644 lab7/c/include/oscos/drivers/aux.h create mode 100644 lab7/c/include/oscos/drivers/board.h create mode 100644 lab7/c/include/oscos/drivers/gpio.h create mode 100644 lab7/c/include/oscos/drivers/l1ic.h create mode 100644 lab7/c/include/oscos/drivers/l2ic.h create mode 100644 lab7/c/include/oscos/drivers/mailbox.h create mode 100644 lab7/c/include/oscos/drivers/mini-uart.h create mode 100644 lab7/c/include/oscos/drivers/pm.h create mode 100644 lab7/c/include/oscos/initrd.h create mode 100644 lab7/c/include/oscos/libc/ctype.h create mode 100644 lab7/c/include/oscos/libc/inttypes.h create mode 100644 lab7/c/include/oscos/libc/stdio.h create mode 100644 lab7/c/include/oscos/libc/stdlib.h create mode 100644 lab7/c/include/oscos/libc/string.h create mode 100644 lab7/c/include/oscos/mem/malloc.h create mode 100644 lab7/c/include/oscos/mem/page-alloc.h create mode 100644 lab7/c/include/oscos/mem/shared-page.h create mode 100644 lab7/c/include/oscos/mem/startup-alloc.h create mode 100644 lab7/c/include/oscos/mem/types.h create mode 100644 lab7/c/include/oscos/mem/vm.h create mode 100644 lab7/c/include/oscos/mem/vm/kernel-page-tables.h create mode 100644 lab7/c/include/oscos/mem/vm/page-table.h create mode 100644 lab7/c/include/oscos/panic.h create mode 100644 lab7/c/include/oscos/sched.h create mode 100644 lab7/c/include/oscos/shell.h create mode 100644 lab7/c/include/oscos/timer/delay.h create mode 100644 lab7/c/include/oscos/timer/timeout.h create mode 100644 lab7/c/include/oscos/uapi/errno.h create mode 100644 lab7/c/include/oscos/uapi/signal.h create mode 100644 lab7/c/include/oscos/uapi/sys/syscall.h create mode 100644 lab7/c/include/oscos/uapi/unistd.h create mode 100644 lab7/c/include/oscos/utils/align.h create mode 100644 lab7/c/include/oscos/utils/control-flow.h create mode 100644 lab7/c/include/oscos/utils/core-id.h create mode 100644 lab7/c/include/oscos/utils/critical-section.h create mode 100644 lab7/c/include/oscos/utils/endian.h create mode 100644 lab7/c/include/oscos/utils/fmt.h create mode 100644 lab7/c/include/oscos/utils/heapq.h create mode 100644 lab7/c/include/oscos/utils/math.h create mode 100644 lab7/c/include/oscos/utils/rb.h create mode 100644 lab7/c/include/oscos/utils/save-ctx.S create mode 100644 lab7/c/include/oscos/utils/suspend.h create mode 100644 lab7/c/include/oscos/utils/time.h create mode 100644 lab7/c/include/oscos/xcpt.h create mode 100644 lab7/c/include/oscos/xcpt/task-queue.h create mode 100644 lab7/c/include/oscos/xcpt/trap-frame.h create mode 100644 lab7/c/src/console.c create mode 100644 lab7/c/src/devicetree.c create mode 100644 lab7/c/src/drivers/aux.c create mode 100644 lab7/c/src/drivers/gpio.c create mode 100644 lab7/c/src/drivers/l1ic.c create mode 100644 lab7/c/src/drivers/l2ic.c create mode 100644 lab7/c/src/drivers/mailbox.c create mode 100644 lab7/c/src/drivers/mini-uart.c create mode 100644 lab7/c/src/drivers/pm.c create mode 100644 lab7/c/src/initrd.c create mode 100644 lab7/c/src/libc/ctype.c create mode 100644 lab7/c/src/libc/stdio.c create mode 100644 lab7/c/src/libc/stdlib/qsort.c create mode 100644 lab7/c/src/libc/string.c create mode 100644 lab7/c/src/linker.ld create mode 100644 lab7/c/src/main.c create mode 100644 lab7/c/src/mem/malloc.c create mode 100644 lab7/c/src/mem/page-alloc.c create mode 100644 lab7/c/src/mem/shared-page.c create mode 100644 lab7/c/src/mem/startup-alloc.c create mode 100644 lab7/c/src/mem/vm.c create mode 100644 lab7/c/src/mem/vm/kernel-page-tables.c create mode 100644 lab7/c/src/panic.c create mode 100644 lab7/c/src/sched/idle-thread.c create mode 100644 lab7/c/src/sched/periodic-sched.c create mode 100644 lab7/c/src/sched/run-signal-handler.S create mode 100644 lab7/c/src/sched/sched.c create mode 100644 lab7/c/src/sched/schedule.S create mode 100644 lab7/c/src/sched/sig-handler-main.S create mode 100644 lab7/c/src/sched/thread-main.S create mode 100644 lab7/c/src/sched/user-program-main.S create mode 100644 lab7/c/src/shell.c create mode 100644 lab7/c/src/start.S create mode 100644 lab7/c/src/timer/delay.c create mode 100644 lab7/c/src/timer/timeout.c create mode 100644 lab7/c/src/utils/core-id.c create mode 100644 lab7/c/src/utils/fmt.c create mode 100644 lab7/c/src/utils/heapq.c create mode 100644 lab7/c/src/utils/rb.c create mode 100644 lab7/c/src/xcpt/data-abort-handler.c create mode 100644 lab7/c/src/xcpt/default-handler.c create mode 100644 lab7/c/src/xcpt/insn-abort-handler.c create mode 100644 lab7/c/src/xcpt/irq-handler.c create mode 100644 lab7/c/src/xcpt/load-aapcs-and-eret.S create mode 100644 lab7/c/src/xcpt/svc-handler.S create mode 100644 lab7/c/src/xcpt/syscall-table.S create mode 100644 lab7/c/src/xcpt/syscall/enosys.c create mode 100644 lab7/c/src/xcpt/syscall/exec.c create mode 100644 lab7/c/src/xcpt/syscall/exit.c create mode 100644 lab7/c/src/xcpt/syscall/fork-child-ret.S create mode 100644 lab7/c/src/xcpt/syscall/fork-impl.c create mode 100644 lab7/c/src/xcpt/syscall/fork.S create mode 100644 lab7/c/src/xcpt/syscall/getpid.c create mode 100644 lab7/c/src/xcpt/syscall/kill.c create mode 100644 lab7/c/src/xcpt/syscall/mbox-call.c create mode 100644 lab7/c/src/xcpt/syscall/signal-kill.c create mode 100644 lab7/c/src/xcpt/syscall/signal.c create mode 100644 lab7/c/src/xcpt/syscall/sigreturn-check.c create mode 100644 lab7/c/src/xcpt/syscall/sigreturn.S create mode 100644 lab7/c/src/xcpt/syscall/uart-read.c create mode 100644 lab7/c/src/xcpt/syscall/uart-write.c create mode 100644 lab7/c/src/xcpt/task-queue.c create mode 100644 lab7/c/src/xcpt/vector-table.S create mode 100644 lab7/c/src/xcpt/xcpt.c create mode 100644 lab7/tests/.gitignore create mode 100755 lab7/tests/build-initrd.sh create mode 100755 lab7/tests/get-dtb.sh create mode 100644 lab7/tests/user-program/libc/.gitignore create mode 100644 lab7/tests/user-program/libc/Makefile create mode 100644 lab7/tests/user-program/libc/include/__detail/utils/fmt.h create mode 100644 lab7/tests/user-program/libc/include/ctype.h create mode 100644 lab7/tests/user-program/libc/include/errno.h create mode 100644 lab7/tests/user-program/libc/include/mbox.h create mode 120000 lab7/tests/user-program/libc/include/oscos-uapi create mode 100644 lab7/tests/user-program/libc/include/signal.h create mode 100644 lab7/tests/user-program/libc/include/stdio.h create mode 100644 lab7/tests/user-program/libc/include/stdlib.h create mode 100644 lab7/tests/user-program/libc/include/string.h create mode 100644 lab7/tests/user-program/libc/include/sys/syscall.h create mode 100644 lab7/tests/user-program/libc/include/unistd.h create mode 100644 lab7/tests/user-program/libc/src/__detail/utils/fmt.c create mode 100644 lab7/tests/user-program/libc/src/ctype.c create mode 100644 lab7/tests/user-program/libc/src/errno.c create mode 100644 lab7/tests/user-program/libc/src/mbox.c create mode 100644 lab7/tests/user-program/libc/src/signal.c create mode 100644 lab7/tests/user-program/libc/src/start.S create mode 100644 lab7/tests/user-program/libc/src/stdio.c create mode 100644 lab7/tests/user-program/libc/src/stdlib.c create mode 100644 lab7/tests/user-program/libc/src/string.c create mode 100644 lab7/tests/user-program/libc/src/unistd.c create mode 100644 lab7/tests/user-program/libc/src/unistd/syscall.S create mode 100644 lab7/tests/user-program/syscall_test/.gitignore create mode 100644 lab7/tests/user-program/syscall_test/Makefile create mode 100644 lab7/tests/user-program/syscall_test/src/linker.ld create mode 100644 lab7/tests/user-program/syscall_test/src/main.c diff --git a/lab7/c/.clang-format b/lab7/c/.clang-format new file mode 100644 index 000000000..9b3aa8b72 --- /dev/null +++ b/lab7/c/.clang-format @@ -0,0 +1 @@ +BasedOnStyle: LLVM diff --git a/lab7/c/.gitignore b/lab7/c/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/lab7/c/.gitignore @@ -0,0 +1 @@ +/build diff --git a/lab7/c/Makefile b/lab7/c/Makefile new file mode 100644 index 000000000..6b488b48e --- /dev/null +++ b/lab7/c/Makefile @@ -0,0 +1,105 @@ +DEFAULT_PROFILE = DEBUG +SRC_DIR = src +BUILD_DIR = build + +AS = aarch64-linux-gnu-as +CC = aarch64-linux-gnu-gcc +CPP = aarch64-linux-gnu-cpp +OBJCOPY = aarch64-linux-gnu-objcopy + +CPPFLAGS_BASE = -Iinclude -DVM_ENABLE_DEBUG_LOG + +ASFLAGS_DEBUG = -g + +CFLAGS_BASE = -std=c17 -pedantic-errors -Wall -Wextra -ffreestanding \ + -mcpu=cortex-a53+nofp+nosimd -mstrict-align +CFLAGS_DEBUG = -g +CFLAGS_RELEASE = -O3 -flto + +LDFLAGS_BASE = -nostdlib -Xlinker --build-id=none +LDFLAGS_RELEASE = -flto -Xlinker --gc-sections + +LDLIBS_BASE = -lgcc + +OBJS = start main console devicetree initrd panic shell \ + drivers/aux drivers/gpio drivers/l1ic drivers/l2ic drivers/mailbox \ + drivers/mini-uart drivers/pm \ + mem/malloc mem/page-alloc mem/shared-page mem/startup-alloc mem/vm \ + mem/vm/kernel-page-tables \ + sched/idle-thread sched/periodic-sched sched/run-signal-handler \ + sched/sched sched/schedule sched/sig-handler-main \ + sched/thread-main sched/user-program-main \ + timer/delay timer/timeout \ + xcpt/data-abort-handler xcpt/default-handler \ + xcpt/insn-abort-handler xcpt/irq-handler xcpt/load-aapcs-and-eret \ + xcpt/svc-handler \ + xcpt/syscall-table xcpt/task-queue xcpt/vector-table xcpt/xcpt \ + xcpt/syscall/enosys xcpt/syscall/getpid xcpt/syscall/uart-read \ + xcpt/syscall/uart-write xcpt/syscall/exec xcpt/syscall/fork \ + xcpt/syscall/fork-impl xcpt/syscall/fork-child-ret \ + xcpt/syscall/exit xcpt/syscall/mbox-call xcpt/syscall/kill \ + xcpt/syscall/signal xcpt/syscall/signal-kill \ + xcpt/syscall/sigreturn xcpt/syscall/sigreturn-check \ + libc/ctype libc/stdio libc/stdlib/qsort libc/string \ + utils/core-id utils/fmt utils/heapq utils/rb +LD_SCRIPT = $(SRC_DIR)/linker.ld + +# ------------------------------------------------------------------------------ + +PROFILE = $(DEFAULT_PROFILE) +OUT_DIR = $(BUILD_DIR)/$(PROFILE) + +CPPFLAGS = $(CPPFLAGS_BASE) $(CPPFLAGS_$(PROFILE)) +ASFLAGS = $(ASFLAGS_BASE) $(ASFLAGS_$(PROFILE)) +CFLAGS = $(CFLAGS_BASE) $(CFLAGS_$(PROFILE)) +LDFLAGS = $(LDFLAGS_BASE) $(LDFLAGS_$(PROFILE)) +LDLIBS = $(LDLIBS_BASE) $(LDLIBS_$(PROFILE)) + +OBJ_PATHS = $(addprefix $(OUT_DIR)/,$(addsuffix .o,$(OBJS))) + +# ------------------------------------------------------------------------------ + +.PHONY: all qemu qemu-debug gdb clean-profile clean + +all: $(OUT_DIR)/kernel8.img + +$(OUT_DIR)/kernel8.img: $(OUT_DIR)/kernel8.elf + @mkdir -p $(@D) + $(OBJCOPY) -O binary $^ $@ + +$(OUT_DIR)/kernel8.elf: $(OBJ_PATHS) $(LD_SCRIPT) + @mkdir -p $(@D) + $(CC) -T $(LD_SCRIPT) $(LDFLAGS) $(filter-out $(LD_SCRIPT),$^) $(LDLIBS) -o $@ + +$(OUT_DIR)/%.o: $(SRC_DIR)/%.c + @mkdir -p $(@D) + $(CC) $(CPPFLAGS) $(CFLAGS) -c -MMD -MP -MF $(OUT_DIR)/$*.d $< -o $@ + +$(OUT_DIR)/%.o: $(OUT_DIR)/%.s + @mkdir -p $(@D) + $(AS) $(ASFLAGS) $^ -o $@ + +$(OUT_DIR)/%.s: $(SRC_DIR)/%.S + @mkdir -p $(@D) + $(CPP) $(CPPFLAGS) $^ -o $@ + +qemu: $(OUT_DIR)/kernel8.img + qemu-system-aarch64 -M raspi3b -kernel $< -initrd ../tests/initramfs.cpio \ + -dtb ../tests/bcm2710-rpi-3-b-plus.dtb -display gtk -serial null \ + -serial stdio + +qemu-debug: $(OUT_DIR)/kernel8.img + qemu-system-aarch64 -M raspi3b -kernel $< -initrd ../tests/initramfs.cpio \ + -dtb ../tests/bcm2710-rpi-3-b-plus.dtb -display gtk -serial null \ + -serial stdio -S -s + +gdb: $(OUT_DIR)/kernel8.elf + gdb -s $< -ex 'target remote :1234' + +clean-profile: + $(RM) -r $(OUT_DIR) + +clean: + $(RM) -r $(BUILD_DIR) + +-include $(OUT_DIR)/*.d diff --git a/lab7/c/include/oscos/console.h b/lab7/c/include/oscos/console.h new file mode 100644 index 000000000..77425b4d2 --- /dev/null +++ b/lab7/c/include/oscos/console.h @@ -0,0 +1,144 @@ +/// \file include/oscos/console.h +/// \brief Serial console. +/// +/// The serial console reads and writes data to the mini UART. +/// +/// Before using the serial console, it must be initialized by calling +/// console_init(void) exactly once. Initializing the serial console twice or +/// operating on the serial console before initialization are not checked and +/// may have unintended consequences. +/// +/// The serial console implements two modes: text mode and binary mode. In text +/// mode, automatic newline translation is performed. A "\r" character received +/// by the mini UART will be read as "\n", and writing "\n" to the serial +/// console will send "\r\n" down the mini UART. In binary mode, automatic +/// newline translation is not performed. Any byte received by the mini UART +/// will be read as-is, and every character written to the serial console will +/// also be sent as-is. Upon initialization, the mode is set to text mode. + +#ifndef OSCOS_CONSOLE_H +#define OSCOS_CONSOLE_H + +#include +#include +#include + +/// \brief The mode of the serial console. +typedef enum { + CM_TEXT, ///< Text mode. Performs newline translation. + CM_BINARY ///< Binary mode. Every byte are sent/received as-is. +} console_mode_t; + +/// \brief Initializes the serial console. +/// +/// See the file-level documentation for requirements on initialization. +void console_init(void); + +/// \brief Sets the mode of the serial console. +/// +/// \param mode The mode. Must be `CM_TEXT` or `CM_BINARY`. +void console_set_mode(console_mode_t mode); + +/// \brief Reads a character from the serial console. +/// +/// When calling this function, the serial console must be initialized. +unsigned char console_getc(void); + +/// \brief Reads a character from the serial console without blocking. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return The character read, or a negative number if the read would block. +int console_getc_nonblock(void); + +/// \brief Reads as many characters from the serial console as possible without +/// blocking. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return The number of characters read. +size_t console_read_nonblock(void *buf, size_t count); + +/// \brief Writes a character to the serial console. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return \p c +unsigned char console_putc(unsigned char c); + +/// \brief Writes a character to the serial console without blocking. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return \p c if the operation is successful, or a negative number if the +/// write operation would block. +int console_putc_nonblock(unsigned char c); + +/// \brief Writes characters to the serial console. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return \p count +size_t console_write(const void *buf, size_t count); + +/// \brief Writes as many characters to the serial console as possible without +/// blocking. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return The number of characters written. +size_t console_write_nonblock(const void *buf, size_t count); + +/// \brief Writes a string to the serial console without trailing newline. +/// +/// When calling this function, the serial console must be initialized. +void console_fputs(const char *s); + +/// \brief Writes a string to the serial console with trailing newline. +/// +/// When calling this function, the serial console must be initialized. +void console_puts(const char *s); + +/// \brief Performs string formatting and writes the result to the serial +/// console. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return The number of characters written. +int console_vprintf(const char *restrict format, va_list ap) + __attribute__((format(printf, 1, 0))); + +/// \brief Performs string formatting and writes the result to the serial +/// console. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return The number of characters written. +int console_printf(const char *restrict format, ...) + __attribute__((format(printf, 1, 2))); + +/// \brief Registers the notification callback for read readiness. +/// +/// \param callback The callback that will be called. +/// \param arg The argument that will be passed to \p callback. +/// \return true if the registration succeeds. +/// \return false if the registration fails because an earlier callback has not +/// been called. +bool console_notify_read_ready(void (*callback)(void *), void *arg); + +/// \brief Registers the notification callback for write readiness. +/// +/// \param callback The callback that will be called. +/// \param arg The argument that will be passed to \p callback. +/// \return true if the registration succeeds. +/// \return false if the registration fails because an earlier callback has not +/// been called. +bool console_notify_write_ready(void (*callback)(void *), void *arg); + +/// \brief Waits until all buffered characters are sent to the serial console. +void console_flush_write_buffer(void); + +/// \brief Interrupt handler. Not meant to be called directly. +void mini_uart_interrupt_handler(void); + +#endif diff --git a/lab7/c/include/oscos/devicetree.h b/lab7/c/include/oscos/devicetree.h new file mode 100644 index 000000000..b4fe42380 --- /dev/null +++ b/lab7/c/include/oscos/devicetree.h @@ -0,0 +1,173 @@ +#ifndef OSCOS_DEVICETREE_H +#define OSCOS_DEVICETREE_H + +#include +#include + +#include "oscos/libc/string.h" +#include "oscos/utils/align.h" +#include "oscos/utils/control-flow.h" +#include "oscos/utils/endian.h" + +/// \brief Flattened devicetree header. +typedef struct { + uint32_t magic; + uint32_t totalsize; + uint32_t off_dt_struct; + uint32_t off_dt_strings; + uint32_t off_mem_rsvmap; + uint32_t version; + uint32_t last_comp_version; + uint32_t boot_cpuid_phys; + uint32_t size_dt_strings; + uint32_t size_dt_struct; +} fdt_header_t; + +/// \brief An entry of the memory reservation block. +typedef struct { + uint64_t address; + uint64_t size; +} fdt_reserve_entry_t; + +/// \brief An item in a node in a flattened devicetree blob. +typedef struct { + uint32_t token; + char payload[]; +} fdt_item_t; + +/// \brief Flattened devicetree property; whatever follows the FDT_PROP token. +typedef struct { + uint32_t len; + uint32_t nameoff; + char value[]; +} fdt_prop_t; + +#define FDT_BEGIN_NODE ((uint32_t)0x00000001) +#define FDT_END_NODE ((uint32_t)0x00000002) +#define FDT_PROP ((uint32_t)0x00000003) +#define FDT_NOP ((uint32_t)0x00000004) +#define FDT_END ((uint32_t)0x00000009) + +/// \brief Initializes the device tree. +/// \param dtb_start The loading address of the devicetree blob. +/// \return Whether or not the initialization succeeds. +bool devicetree_init(const void *dtb_start); + +/// \brief Returns whether or not the devicetree has been successfully +/// initialized. +bool devicetree_is_init(void); + +/// \brief The start of the memory reservation block of the devicetree blob. +#define FDT_START_MEM_RSVMAP \ + ((const fdt_reserve_entry_t \ + *)(fdt_get_start() + \ + rev_u32(((const fdt_header_t *)fdt_get_start())->off_mem_rsvmap))) + +/// \brief The start of the strings block of the devicetree blob. +#define FDT_START_STRINGS \ + (fdt_get_start() + \ + rev_u32(((const fdt_header_t *)fdt_get_start())->off_dt_strings)) + +/// \brief The starting token of an item in the structure block. +#define FDT_TOKEN(ITEM) (rev_u32((ITEM)->token)) + +/// \brief The name of an node in the structure block. +#define FDT_NODE_NAME(NODE) ((NODE)->payload) + +/// \brief The name of a property in the structure block. +#define FDT_PROP_NAME(PROP) (FDT_START_STRINGS + rev_u32((PROP)->nameoff)) + +/// \brief The value of a property in the structure block. +#define FDT_PROP_VALUE(PROP) ((PROP)->value) + +/// \brief The length of the of a property in the structure block. +#define FDT_PROP_VALUE_LEN(PROP) (rev_u32((PROP)->len)) + +#define FDT_ITEMS_START(NODE) \ + ((const fdt_item_t *)ALIGN( \ + (uintptr_t)((NODE)->payload) + strlen(FDT_NODE_NAME(NODE)) + 1, 4)) +#define FDT_ITEM_IS_END(ITEM) (FDT_TOKEN(ITEM) == FDT_END_NODE) + +/// \brief Expands to a for loop that loops over each item in the given node. +/// +/// \param NODE The pointer to the node. +/// \param ITEM_NAME The name of the variable for the item. +#define FDT_FOR_ITEM(NODE, ITEM_NAME) \ + for (const fdt_item_t *ITEM_NAME = FDT_ITEMS_START(NODE); \ + !FDT_ITEM_IS_END(ITEM_NAME); ITEM_NAME = fdt_next_item(ITEM_NAME)) + +/// \brief Expands to a for loop that loops over each item in the given node. +/// +/// This is a variant of FDT_FOR_ITEM that does not declare the variable within +/// the for loop. This is useful, e. g., if one wants to obtain the pointer to +/// the FDT_END_NODE token. +/// +/// \param NODE The pointer to the node. +/// \param ITEM_NAME The name of the variable for the item. Must be a declared +/// variable. +#define FDT_FOR_ITEM_(NODE, ITEM_NAME) \ + for (ITEM_NAME = FDT_ITEMS_START(NODE); !FDT_ITEM_IS_END(ITEM_NAME); \ + ITEM_NAME = fdt_next_item(ITEM_NAME)) + +/// \brief Gets the starting address of the devicetree blob. +const char *fdt_get_start(void); + +/// \brief Gets the ending address of the devicetree blob. +const char *fdt_get_end(void); + +/// \brief Gets the pointer to the next item of the given item in the same node. +/// \param item The item. Must not point to the FDT_END_NODE token. +const fdt_item_t *fdt_next_item(const fdt_item_t *item); + +/// \brief A node of the parent node linked list. +typedef struct fdt_traverse_parent_list_node_t { + const fdt_item_t *node; + const struct fdt_traverse_parent_list_node_t *parent; +} fdt_traverse_parent_list_node_t; + +/// \brief Callback for void fdt_traverse(fdt_traverse_callback_t *, void *). +typedef control_flow_t +fdt_traverse_callback_t(void *, const fdt_item_t *, + const fdt_traverse_parent_list_node_t *); + +/// \brief Performs in-order traversal of the devicetree. +/// +/// When the traversal process encounters a node, \p callback is called with +/// \p arg, the pointer to the current node, and the linked list of the parent +/// nodes of the current node. +/// +/// \param callback The callback that is called on each node. +/// \param arg The first argument that will be passed to \p callback. +void fdt_traverse(fdt_traverse_callback_t *callback, void *arg); + +/// \brief The #address-cells and the #size-cells properties. +typedef struct { + uint32_t n_address_cells, n_size_cells; +} fdt_n_address_size_cells_t; + +/// \brief Gets the #address-cells and the #size-cells properties of a node. +/// +/// \param node The pointer to the node. +/// \return The value of the #address-cells and the #size-cells properties, or +/// their respective defaults if these properties are missing. +fdt_n_address_size_cells_t fdt_get_n_address_size_cells(const fdt_item_t *node); + +/// \brief The reg property. +typedef struct { + uintmax_t address, size; +} fdt_reg_t; + +typedef struct { + fdt_reg_t value; + bool address_overflow, size_overflow; +} fdt_read_reg_result_t; + +/// \brief Reads the reg property. +/// +/// \param prop The pointer to the reg property. +/// \param n_cells The #address-cells and the #size-cells properties of the +/// parent node. +fdt_read_reg_result_t fdt_read_reg(const fdt_prop_t *prop, + fdt_n_address_size_cells_t n_cells); + +#endif diff --git a/lab7/c/include/oscos/drivers/aux.h b/lab7/c/include/oscos/drivers/aux.h new file mode 100644 index 000000000..e04db4bd1 --- /dev/null +++ b/lab7/c/include/oscos/drivers/aux.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_DRIVERS_AUX_H +#define OSCOS_DRIVERS_AUX_H + +void aux_init(void); + +void aux_enable_mini_uart(void); + +#endif diff --git a/lab7/c/include/oscos/drivers/board.h b/lab7/c/include/oscos/drivers/board.h new file mode 100644 index 000000000..95af6777a --- /dev/null +++ b/lab7/c/include/oscos/drivers/board.h @@ -0,0 +1,20 @@ +#ifndef OSCOS_DRIVERS_BOARD_H +#define OSCOS_DRIVERS_BOARD_H + +// Symbols defined in the linker script. +extern char _kernel_vm_base[]; + +// ARM virtual address. See +// https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#peripheral-addresses +#define PERIPHERAL_BASE ((void *)(_kernel_vm_base + 0x3f000000)) + +#define ARM_LOCAL_PERIPHERAL_BASE ((void *)(_kernel_vm_base + 0x40000000)) + +// ? Is the shareability domain for the peripheral memory barriers correct? + +#define PERIPHERAL_READ_BARRIER() \ + __asm__ __volatile__("dsb nshld" : : : "memory") +#define PERIPHERAL_WRITE_BARRIER() \ + __asm__ __volatile__("dsb nshst" : : : "memory") + +#endif diff --git a/lab7/c/include/oscos/drivers/gpio.h b/lab7/c/include/oscos/drivers/gpio.h new file mode 100644 index 000000000..6994b22d6 --- /dev/null +++ b/lab7/c/include/oscos/drivers/gpio.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_DRIVERS_GPIO_H +#define OSCOS_DRIVERS_GPIO_H + +void gpio_init(void); + +void gpio_setup_uart0_gpio14(void); + +#endif diff --git a/lab7/c/include/oscos/drivers/l1ic.h b/lab7/c/include/oscos/drivers/l1ic.h new file mode 100644 index 000000000..a9f33889c --- /dev/null +++ b/lab7/c/include/oscos/drivers/l1ic.h @@ -0,0 +1,24 @@ +#ifndef OSCOS_DRIVERS_L1IC_H +#define OSCOS_DRIVERS_L1IC_H + +#include +#include + +#define INT_L1_SRC_TIMER0 ((uint32_t)(1 << 0)) +#define INT_L1_SRC_TIMER1 ((uint32_t)(1 << 1)) +#define INT_L1_SRC_TIMER2 ((uint32_t)(1 << 2)) +#define INT_L1_SRC_TIMER3 ((uint32_t)(1 << 3)) +#define INT_L1_SRC_MBOX0 ((uint32_t)(1 << 4)) +#define INT_L1_SRC_MBOX1 ((uint32_t)(1 << 5)) +#define INT_L1_SRC_MBOX2 ((uint32_t)(1 << 6)) +#define INT_L1_SRC_MBOX3 ((uint32_t)(1 << 7)) +#define INT_L1_SRC_GPU ((uint32_t)(1 << 8)) +#define INT_L1_SRC_PMU ((uint32_t)(1 << 9)) + +void l1ic_init(void); + +uint32_t l1ic_get_int_src(size_t core_id); + +void l1ic_enable_core_timer_irq(size_t core_id); + +#endif diff --git a/lab7/c/include/oscos/drivers/l2ic.h b/lab7/c/include/oscos/drivers/l2ic.h new file mode 100644 index 000000000..c11a71b50 --- /dev/null +++ b/lab7/c/include/oscos/drivers/l2ic.h @@ -0,0 +1,14 @@ +#ifndef OSCOS_DRIVERS_L2IC_H +#define OSCOS_DRIVERS_L2IC_H + +#include + +#define INT_L2_IRQ_0_SRC_AUX ((uint32_t)(1 << 29)) + +void l2ic_init(void); + +uint32_t l2ic_get_pending_irq_0(void); + +void l2ic_enable_irq_0(uint32_t mask); + +#endif diff --git a/lab7/c/include/oscos/drivers/mailbox.h b/lab7/c/include/oscos/drivers/mailbox.h new file mode 100644 index 000000000..57fa08254 --- /dev/null +++ b/lab7/c/include/oscos/drivers/mailbox.h @@ -0,0 +1,19 @@ +#ifndef OSCOS_DRIVERS_MAILBOX_H +#define OSCOS_DRIVERS_MAILBOX_H + +#include + +#define MAILBOX_CHANNEL_PROPERTY_TAGS_ARM_TO_VC ((unsigned char)8) + +typedef struct { + uint32_t base, size; +} arm_memory_t; + +void mailbox_init(void); + +void mailbox_call(uint32_t message[], unsigned char channel); + +uint32_t mailbox_get_board_revision(void); +arm_memory_t mailbox_get_arm_memory(void); + +#endif diff --git a/lab7/c/include/oscos/drivers/mini-uart.h b/lab7/c/include/oscos/drivers/mini-uart.h new file mode 100644 index 000000000..d2af57e07 --- /dev/null +++ b/lab7/c/include/oscos/drivers/mini-uart.h @@ -0,0 +1,14 @@ +#ifndef OSCOS_DRIVERS_MINI_UART_H +#define OSCOS_DRIVERS_MINI_UART_H + +void mini_uart_init(void); + +int mini_uart_recv_byte_nonblock(void); +int mini_uart_send_byte_nonblock(unsigned char b); + +void mini_uart_enable_rx_interrupt(void); +void mini_uart_disable_rx_interrupt(void); +void mini_uart_enable_tx_interrupt(void); +void mini_uart_disable_tx_interrupt(void); + +#endif diff --git a/lab7/c/include/oscos/drivers/pm.h b/lab7/c/include/oscos/drivers/pm.h new file mode 100644 index 000000000..531b09a50 --- /dev/null +++ b/lab7/c/include/oscos/drivers/pm.h @@ -0,0 +1,14 @@ +#ifndef OSCOS_DRIVERS_PM_H +#define OSCOS_DRIVERS_PM_H + +#include +#include + +void pm_init(void); + +void pm_reset(uint32_t tick); +void pm_cancel_reset(void); + +noreturn void pm_reboot(void); + +#endif diff --git a/lab7/c/include/oscos/initrd.h b/lab7/c/include/oscos/initrd.h new file mode 100644 index 000000000..112cf9b83 --- /dev/null +++ b/lab7/c/include/oscos/initrd.h @@ -0,0 +1,117 @@ +/// \file include/oscos/initrd.h +/// \brief Initial ramdisk. +/// +/// Before reading the initial ramdisk, it must be initialized by calling +/// initrd_init(void). + +#ifndef OSCOS_INITRD_H +#define OSCOS_INITRD_H + +#include +#include + +#include "oscos/libc/string.h" +#include "oscos/utils/align.h" + +/// \brief New ASCII Format CPIO archive header. +typedef struct { + char c_magic[6]; + char c_ino[8]; + char c_mode[8]; + char c_uid[8]; + char c_gid[8]; + char c_nlink[8]; + char c_mtime[8]; + char c_filesize[8]; + char c_devmajor[8]; + char c_devminor[8]; + char c_rdevmajor[8]; + char c_rdevminor[8]; + char c_namesize[8]; + char c_check[8]; +} cpio_newc_header_t; + +#define CPIO_NEWC_MODE_FILE_TYPE_MASK ((uint32_t)0170000) +#define CPIO_NEWC_MODE_FILE_TYPE_LNK ((uint32_t)0120000) +#define CPIO_NEWC_MODE_FILE_TYPE_REG ((uint32_t)0100000) +#define CPIO_NEWC_MODE_FILE_TYPE_DIR ((uint32_t)0040000) + +/// \brief New ASCII Format CPIO archive file entry. +typedef struct { + cpio_newc_header_t header; + char payload[]; +} cpio_newc_entry_t; + +/// \brief Pointer to the first file entry of the initial ramdisk. +#define INITRD_HEAD ((const cpio_newc_entry_t *)initrd_get_start()) + +/// \brief The value of the given header of the file entry. +#define CPIO_NEWC_HEADER_VALUE(ENTRY, HEADER) \ + cpio_newc_parse_header_field((ENTRY)->header.c_##HEADER) + +/// \brief The pathname of the file entry. +#define CPIO_NEWC_PATHNAME(ENTRY) ((const char *)(ENTRY)->payload) + +/// \brief The file data of the file entry. +#define CPIO_NEWC_FILE_DATA(ENTRY) \ + ((const char *)ALIGN((uintptr_t)(ENTRY)->payload + \ + CPIO_NEWC_HEADER_VALUE(ENTRY, namesize), \ + 4)) + +/// \brief The filesize of the file entry. +#define CPIO_NEWC_FILESIZE(ENTRY) CPIO_NEWC_HEADER_VALUE(ENTRY, filesize) + +/// \brief Determines if the file entry is the final sentinel entry. +#define CPIO_NEWC_IS_ENTRY_LAST(ENTRY) \ + (strcmp(CPIO_NEWC_PATHNAME(ENTRY), "TRAILER!!!") == 0) + +/// \brief Returns the next file entry of the given file entry. +/// +/// The given file entry must not be the final sentinel entry. +#define CPIO_NEWC_NEXT_ENTRY(ENTRY) \ + (const cpio_newc_entry_t *)ALIGN( \ + (uintptr_t)CPIO_NEWC_FILE_DATA(ENTRY) + CPIO_NEWC_FILESIZE(ENTRY), 4) + +/// \brief Expands to a for loop that loops over each file entry in the initial +/// ramdisk. +/// +/// \param ENTRY_NAME The name of the variable for the file entry. +#define INITRD_FOR_ENTRY(ENTRY_NAME) \ + for (const cpio_newc_entry_t *ENTRY_NAME = INITRD_HEAD; \ + !CPIO_NEWC_IS_ENTRY_LAST(ENTRY_NAME); \ + ENTRY_NAME = CPIO_NEWC_NEXT_ENTRY(ENTRY_NAME)) + +/// \brief Initializes the initial ramdisk. +/// +/// This function obtains the loading address of the initial ramdisk from the +/// device tree and determines if the initial ramdisk is a valid New ASCII +/// format CPIO archive. +/// +/// \return Whether or not initialization is successful. +bool initrd_init(void); + +/// \brief Returns whether or not the initial ramdisk has been successfully +/// initialized. +bool initrd_is_init(void); + +/// \brief Returns the loading address of the initial ramdisk. +const void *initrd_get_start(void); + +/// \brief Returns the ending address of the initial ramdisk. +const void *initrd_get_end(void); + +/// \brief Parses the given header field of a New ASCII format CPIO archive. +/// +/// The given header field must be valid. +/// +/// \see CPIO_NEWC_HEADER_VALUE +uint32_t cpio_newc_parse_header_field(const char field[static 8]); + +/// \brief Finds the file entry in the initial ramdisk corresponding to the +/// given pathname. +/// \param pathname The pathname. +/// \return The pointer to the file entry if the entry is found, or NULL +/// otherwise. +const cpio_newc_entry_t *initrd_find_entry_by_pathname(const char *pathname); + +#endif diff --git a/lab7/c/include/oscos/libc/ctype.h b/lab7/c/include/oscos/libc/ctype.h new file mode 100644 index 000000000..6e5eb134a --- /dev/null +++ b/lab7/c/include/oscos/libc/ctype.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_LIBC_CTYPE_H +#define OSCOS_LIBC_CTYPE_H + +int isdigit(int c); + +#endif diff --git a/lab7/c/include/oscos/libc/inttypes.h b/lab7/c/include/oscos/libc/inttypes.h new file mode 100644 index 000000000..90ba7b7c6 --- /dev/null +++ b/lab7/c/include/oscos/libc/inttypes.h @@ -0,0 +1,9 @@ +#ifndef OSCOS_LIBC_INTTYPES_H +#define OSCOS_LIBC_INTTYPES_H + +#include + +#define PRIx32 "x" +#define PRIx64 "lx" + +#endif diff --git a/lab7/c/include/oscos/libc/stdio.h b/lab7/c/include/oscos/libc/stdio.h new file mode 100644 index 000000000..2cf00d503 --- /dev/null +++ b/lab7/c/include/oscos/libc/stdio.h @@ -0,0 +1,12 @@ +#ifndef OSCOS_LIBC_STDIO_H +#define OSCOS_LIBC_STDIO_H + +#include +#include + +int snprintf(char str[restrict], size_t size, const char *restrict format, ...) + __attribute__((format(printf, 3, 4))); +int vsnprintf(char str[restrict], size_t size, const char *restrict format, + va_list ap) __attribute__((format(printf, 3, 0))); + +#endif diff --git a/lab7/c/include/oscos/libc/stdlib.h b/lab7/c/include/oscos/libc/stdlib.h new file mode 100644 index 000000000..a09506078 --- /dev/null +++ b/lab7/c/include/oscos/libc/stdlib.h @@ -0,0 +1,11 @@ +#ifndef OSCOS_LIBC_STDLIB_H +#define OSCOS_LIBC_STDLIB_H + +#include + +void qsort(void *base, size_t nmemb, size_t size, + int (*compar)(const void *, const void *)); +void qsort_r(void *base, size_t nmemb, size_t size, + int (*compar)(const void *, const void *, void *), void *arg); + +#endif diff --git a/lab7/c/include/oscos/libc/string.h b/lab7/c/include/oscos/libc/string.h new file mode 100644 index 000000000..5abc5b3af --- /dev/null +++ b/lab7/c/include/oscos/libc/string.h @@ -0,0 +1,25 @@ +#ifndef OSCOS_LIBC_STRING_H +#define OSCOS_LIBC_STRING_H + +#include + +int memcmp(const void *s1, const void *s2, size_t n); +void *memset(void *s, int c, size_t n); +void *memcpy(void *restrict dest, const void *restrict src, size_t n); +void *memmove(void *dest, const void *src, size_t n); + +int strcmp(const char *s1, const char *s2); +int strncmp(const char *s1, const char *s2, size_t n); + +size_t strlen(const char *s); + +// Extensions. + +/// \brief Swap two non-overlapping blocks of memory. +/// +/// \param xs The pointer to the beginning of the first block of memory. +/// \param ys The pointer to the beginning of the second block of memory. +/// \param n The size of the memory blocks. +void memswp(void *restrict xs, void *restrict ys, size_t n); + +#endif diff --git a/lab7/c/include/oscos/mem/malloc.h b/lab7/c/include/oscos/mem/malloc.h new file mode 100644 index 000000000..282ca848f --- /dev/null +++ b/lab7/c/include/oscos/mem/malloc.h @@ -0,0 +1,30 @@ +/// \file include/oscos/mem/malloc.h +/// \brief Dynamic memory allocator. +/// +/// The dynamic memory allocator is a general-purpose memory allocator that +/// allocates physically-contiguous memory. + +#ifndef OSCOS_MEM_MALLOC_H +#define OSCOS_MEM_MALLOC_H + +#include +#include + +/// \brief Initializes the dynamic memory allocator. +void malloc_init(void); + +/// \brief Frees memory allocated using the dynamic memory allocator. +/// +/// \param ptr The pointer to the allocated memory. +void free(void *ptr); + +/// \brief Dynamically allocates memory using the dynamic memory allocator. +/// +/// \param size The requested size in bytes of the allocation. +/// \return The pointer to the allocated memory, or NULL if the request cannot +/// be fulfilled. +void *malloc(size_t size) + __attribute__((alloc_size(1), assume_aligned(alignof(max_align_t)), malloc, + malloc(free, 1))); + +#endif diff --git a/lab7/c/include/oscos/mem/page-alloc.h b/lab7/c/include/oscos/mem/page-alloc.h new file mode 100644 index 000000000..eddee380c --- /dev/null +++ b/lab7/c/include/oscos/mem/page-alloc.h @@ -0,0 +1,87 @@ +/// \file include/oscos/mem/page-alloc.h +/// \brief Page frame allocator. + +#ifndef OSCOS_MEM_PAGE_ALLOC_H +#define OSCOS_MEM_PAGE_ALLOC_H + +#include +#include + +#include "oscos/mem/types.h" + +#define PAGE_ORDER 12 +#define MAX_BLOCK_ORDER 18 + +/// \brief Initializes the page frame allocator. +/// +/// After calling this function, the startup allocator should not be used. +void page_alloc_init(void); + +/// \brief Allocates a block of page frames. +/// \param order The order of the block. +/// \return The page number of the first page, or a negative number if the +/// request cannot be fulfilled. +spage_id_t alloc_pages(size_t order); + +/// \brief Allocates a block of page frames. +/// +/// This function is safe to call only within a critical section. +/// +/// \param order The order of the block. +/// \return The page number of the first page, or a negative number if the +/// request cannot be fulfilled. +spage_id_t alloc_pages_unlocked(size_t order); + +/// \brief Frees a block of page frames. +/// \param page The page number of the first page of the block. +void free_pages(page_id_t page); + +/// \brief Frees a block of page frames. +/// +/// This function is safe to call only within a critical section. +/// +/// \param page The page number of the first page of the block. +void free_pages_unlocked(page_id_t page); + +/// \brief Marks a contiguous range of page frames as either reserved or +/// available. +/// +/// Note that the range can be arbitrary and doesn't need to be a block. +/// +/// \param range The range of page frames to mark. +/// \param is_avail The target reservation status. +void mark_pages(page_id_range_t range, bool is_avail); + +/// \brief Marks a contiguous range of page frames as either reserved or +/// available. +/// +/// Note that the range can be arbitrary and doesn't need to be a block. +/// +/// This function is safe to call only within a critical section. +/// +/// \param range The range of page frames to mark. +/// \param is_avail The target reservation status. +void mark_pages_unlocked(page_id_range_t range, bool is_avail); + +/// \brief Converts the given page ID into its corresponding physical address. +pa_t page_id_to_pa(page_id_t page) __attribute__((pure)); + +/// \brief Converts the given physical address into its corresponding page ID. +/// +/// Before conversion, the physical address is rounded down to the page +/// boundary. I.e., the returned page ID is the page ID whose corresponding +/// physical address range covers the given physical address. +page_id_t pa_to_page_id(pa_t pa) __attribute__((pure)); + +/// \brief Converts the given physical address range into its corresponding page +/// ID range. +/// +/// Before conversion, the starting physical address is rounded down to the page +/// boundary and the ending physical address is rounded up to the page boundary. +/// I.e., the returned page ID range is the smallest page ID range whose +/// corresponding physical address range covers the given physical address +/// range. +page_id_range_t pa_range_to_page_id_range(pa_range_t range) + __attribute__((pure)); + +#endif diff --git a/lab7/c/include/oscos/mem/shared-page.h b/lab7/c/include/oscos/mem/shared-page.h new file mode 100644 index 000000000..2a09ed628 --- /dev/null +++ b/lab7/c/include/oscos/mem/shared-page.h @@ -0,0 +1,12 @@ +#ifndef OSCOS_MEM_SHARED_PAGE_H +#define OSCOS_MEM_SHARED_PAGE_H + +#include "oscos/mem/types.h" + +spage_id_t shared_page_alloc(void); +size_t shared_page_getref(page_id_t page) __attribute__((pure)); +void shared_page_incref(page_id_t page); +void shared_page_decref(page_id_t page); +spage_id_t shared_page_clone_unshare(page_id_t page); + +#endif diff --git a/lab7/c/include/oscos/mem/startup-alloc.h b/lab7/c/include/oscos/mem/startup-alloc.h new file mode 100644 index 000000000..fb626c99d --- /dev/null +++ b/lab7/c/include/oscos/mem/startup-alloc.h @@ -0,0 +1,34 @@ +/// \file include/oscos/mem/startup-alloc.h +/// \brief Startup allocator. +/// +/// The startup allocator is a simple bump pointer allocator that is usable even +/// when no other subsystems are initialized. This memory allocator allocates +/// memory starting from the end of the kernel. Note that memory allocated using +/// this memory allocator cannot be freed. + +#ifndef OSCOS_MEM_STARTUP_ALLOC_H +#define OSCOS_MEM_STARTUP_ALLOC_H + +#include +#include + +#include "oscos/mem/types.h" + +/// \brief Initializes the startup allocator. +void startup_alloc_init(void); + +/// \brief Dynamically allocates memory using the startup allocator. +/// +/// Note that memory allocated by this function cannot be freed. +/// +/// \param size The requested size in bytes of the allocation. +/// \return The pointer to the allocated memory, or NULL if the request cannot +/// be fulfilled. +void *startup_alloc(size_t size) + __attribute__((alloc_size(1), assume_aligned(alignof(max_align_t)), + malloc)); + +/// \brief Gets the memory range allocated through the startup allocator. +va_range_t startup_alloc_get_alloc_range(void) __attribute__((pure)); + +#endif diff --git a/lab7/c/include/oscos/mem/types.h b/lab7/c/include/oscos/mem/types.h new file mode 100644 index 000000000..1deee96e8 --- /dev/null +++ b/lab7/c/include/oscos/mem/types.h @@ -0,0 +1,42 @@ +#ifndef OSCOS_MEM_TYPES_H +#define OSCOS_MEM_TYPES_H + +#include + +#include "oscos/libc/inttypes.h" + +/// \brief Physical address. +typedef uint32_t pa_t; + +/// \brief Maximum value of pa_t. +#define PA_MAX UINT32_MAX + +/// \brief Format specifier for printing a pa_t in lowercase hexadecimal format. +#define PRIxPA PRIx32 + +/// \brief Page ID. +typedef uint32_t page_id_t; + +/// \brief Format specifier for printing a page_id_t in lowercase hexadecimal +/// format. +#define PRIxPAGEID PRIx32 + +/// \brief Signed page ID. +typedef int32_t spage_id_t; + +/// \brief Physical address range. +typedef struct { + pa_t start, end; +} pa_range_t; + +/// \brief Page range. +typedef struct { + page_id_t start, end; +} page_id_range_t; + +/// \brief Virtual address range. +typedef struct { + void *start, *end; +} va_range_t; + +#endif diff --git a/lab7/c/include/oscos/mem/vm.h b/lab7/c/include/oscos/mem/vm.h new file mode 100644 index 000000000..3ee64e514 --- /dev/null +++ b/lab7/c/include/oscos/mem/vm.h @@ -0,0 +1,47 @@ +#ifndef OSCOS_MEM_VM_H +#define OSCOS_MEM_VM_H + +#include "oscos/mem/types.h" +#include "oscos/mem/vm/page-table.h" + +/// \brief Converts a kernel space virtual address into its corresponding +/// physical address. +pa_t kernel_va_to_pa(const void *va) __attribute__((const)); + +/// \brief Converts a physical address into its corresponding kernel space +/// virtual address. +void *pa_to_kernel_va(pa_t pa) __attribute__((const)); + +/// \brief Converts a kernel space virtual address range into its corresponding +/// physical address range. +pa_range_t kernel_va_range_to_pa_range(va_range_t range) __attribute__((const)); + +typedef enum { MEM_REGION_BACKED, MEM_REGION_LINEAR } mem_region_type_t; + +typedef struct { + void *start; + size_t len; + mem_region_type_t type; + const void *backing_storage_start; + size_t backing_storage_len; +} mem_region_t; + +typedef struct { + mem_region_t text_region, stack_region, vc_region; + page_table_entry_t *pgd; +} vm_addr_space_t; + +typedef enum { + VM_MAP_PAGE_SUCCESS, + VM_MAP_PAGE_SEGV, + VM_MAP_PAGE_NOMEM +} vm_map_page_result_t; + +page_table_entry_t *vm_new_pgd(void); +void vm_clone_pgd(page_table_entry_t *pgd); +void vm_drop_pgd(page_table_entry_t *pgd); +vm_map_page_result_t vm_map_page(vm_addr_space_t *addr_space, void *va); +vm_map_page_result_t vm_cow(vm_addr_space_t *addr_space, void *va); +void vm_switch_to_addr_space(const vm_addr_space_t *addr_space); + +#endif diff --git a/lab7/c/include/oscos/mem/vm/kernel-page-tables.h b/lab7/c/include/oscos/mem/vm/kernel-page-tables.h new file mode 100644 index 000000000..d8dd6659e --- /dev/null +++ b/lab7/c/include/oscos/mem/vm/kernel-page-tables.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_MEM_VM_KERNEL_PAGE_TABLES_H +#define OSCOS_MEM_VM_KERNEL_PAGE_TABLES_H + +void vm_setup_finer_granularity_linear_mapping(void); + +#endif diff --git a/lab7/c/include/oscos/mem/vm/page-table.h b/lab7/c/include/oscos/mem/vm/page-table.h new file mode 100644 index 000000000..a89e304c2 --- /dev/null +++ b/lab7/c/include/oscos/mem/vm/page-table.h @@ -0,0 +1,42 @@ +#ifndef OSCOS_VM_PAGE_TABLE_H +#define OSCOS_VM_PAGE_TABLE_H + +#include + +typedef struct { + unsigned _reserved0 : (50 - 48 + 1); + unsigned ignored : (58 - 51 + 1); + bool pxntable : 1; + bool uxntable : 1; + unsigned aptable : 2; + unsigned _reserved1 : 1; +} table_descriptor_upper_t; + +typedef struct { + unsigned _reserved0 : (51 - 48 + 1); + bool contiguous : 1; + bool pxn : 1; + bool uxn : 1; + unsigned ignored : (63 - 55 + 1); +} block_page_descriptor_upper_t; + +typedef struct { + unsigned attr_indx : 3; + bool _reserved0 : 1; + unsigned ap : 2; + unsigned sh : 2; + bool af : 1; + bool ng : 1; +} block_page_descriptor_lower_t; + +typedef struct { + bool b0 : 1; + bool b1 : 1; + unsigned lower : (11 - 2 + 1); + unsigned long long addr : (47 - 12 + 1); + unsigned upper : (63 - 48 + 1); +} page_table_entry_t; + +typedef page_table_entry_t page_table_t[512]; + +#endif diff --git a/lab7/c/include/oscos/panic.h b/lab7/c/include/oscos/panic.h new file mode 100644 index 000000000..ce113c914 --- /dev/null +++ b/lab7/c/include/oscos/panic.h @@ -0,0 +1,23 @@ +#ifndef OSCOS_PANIC_H +#define OSCOS_PANIC_H + +#include + +/// \brief Starts a kernel panic. +#define PANIC(...) panic_begin(__FILE__, __LINE__, __VA_ARGS__) + +/// \brief Starts a kernel panic. +/// +/// In most cases, the PANIC macro should be used instead of directly calling +/// this function. +/// +/// \param file The path of the source file to appear in the panic message. +/// \param line The line number to appear in the panic message. +/// \param format The format string of the panic message. +/// +/// \see PANIC +noreturn void panic_begin(const char *restrict file, int line, + const char *restrict format, ...) + __attribute__((cold, format(printf, 3, 4))); + +#endif diff --git a/lab7/c/include/oscos/sched.h b/lab7/c/include/oscos/sched.h new file mode 100644 index 000000000..560256f2c --- /dev/null +++ b/lab7/c/include/oscos/sched.h @@ -0,0 +1,161 @@ +#ifndef OSCOS_SCHED_H +#define OSCOS_SCHED_H + +#include +#include +#include +#include +#include + +#include "oscos/mem/types.h" +#include "oscos/mem/vm.h" +#include "oscos/uapi/signal.h" +#include "oscos/xcpt/trap-frame.h" + +typedef struct thread_list_node_t { + struct thread_list_node_t *prev, *next; +} thread_list_node_t; + +typedef struct { + alignas(16) uint64_t words[2]; +} uint128_t; + +typedef struct { + uint128_t v[32]; + alignas(16) struct { + uint64_t fpcr, fpsr; + }; +} thread_fp_simd_ctx_t; + +typedef struct { + alignas(16) union { + struct { + uint64_t r19, r20, r21, r22, r23, r24, r25, r26, r27, r28, r29, pc, + kernel_sp, user_sp; + }; + uint64_t regs[14]; + }; + thread_fp_simd_ctx_t *fp_simd_ctx; +} thread_ctx_t; + +struct process_t; + +typedef struct { + thread_list_node_t list_node; + thread_ctx_t ctx; + size_t id; + struct { + bool is_waiting : 1; + bool is_stopped : 1; + bool is_waken_up_by_signal : 1; + bool is_handling_signal : 1; + } status; + page_id_t stack_page_id; + struct process_t *process; +} thread_t; + +typedef struct process_t { + size_t id; + vm_addr_space_t addr_space; + thread_t *main_thread; + uint32_t pending_signals, blocked_signals; + sighandler_t signal_handlers[32]; +} process_t; + +/// \brief Initializes the scheduler and creates the idle thread. +/// +/// \return true if the initialization succeeds. +/// \return false if the initialization fails due to memory shortage. +bool sched_init(void); + +/// \brief Creates a thread. +/// +/// \param task The task to execute in the new thread. +/// \param arg The argument to pass to \p task. +/// \return true if the thread creation succeeds. +/// \return false if the thread creation fails due to memory shortage. +bool thread_create(void (*task)(void *), void *arg); + +/// \brief Terminates the current thread. +/// +/// This function should not be called on the idle thread. +noreturn void thread_exit(void); + +/// \brief Gets the current thread. +thread_t *current_thread(void); + +/// \brief Creates a process and name the current thread the main thread of the +/// process. +/// +/// \return true if the process creation succeeds. +/// \return false if the process creation fails due to memory shortage. +bool process_create(void); + +/// \brief Executes the first ever user program on the current process. +/// +/// This function jumps to the user program and does not return if the operation +/// succeeds. If the operation fails due to memory shortage, this function +/// returns. +/// +/// \param text_start The starting address of where the user program is +/// currently located. (Do not confuse it with the entry point +/// of the user program.) +/// \param text_len The length of the user program. +void exec_first(const void *text_start, size_t text_len); + +/// \brief Executes a user program on the current process. +/// +/// This function jumps to the user program and does not return if the operation +/// succeeds. If the operation fails due to memory shortage, this function +/// returns. +/// +/// The text_page_id field of the current process must be valid. +/// +/// \param text_start The starting address of where the user program is +/// currently located. (Do not confuse it with the entry point +/// of the user program.) +/// \param text_len The length of the user program. +void exec(const void *text_start, size_t text_len); + +/// \brief Forks the current process. +process_t *fork(const extended_trap_frame_t *trap_frame); + +/// \brief Kills zombie threads. +void kill_zombies(void); + +/// \brief Yields CPU for the current thread and runs the scheduler. +void schedule(void); + +/// \brief Puts the current thread to the given wait queue and runs the +/// scheduler. +void suspend_to_wait_queue(thread_list_node_t *wait_queue); + +/// \brief Adds every thread in the given wait queue to the run queue. +void add_all_threads_to_run_queue(thread_list_node_t *wait_queue); + +/// \brief Gets a process by its PID. +process_t *get_process_by_id(size_t pid); + +/// \brief Kills a process. +void kill_process(process_t *process); + +/// \brief Kills all processes. +void kill_all_processes(void); + +/// \brief Sets up periodic scheduling. +void sched_setup_periodic_scheduling(void); + +/// \brief Do what the idle thread should do. +noreturn void idle(void); + +/// \brief Sets the signal handler of a process and returns the old one. +sighandler_t set_signal_handler(process_t *process, int signal, + sighandler_t handler); + +/// \brief Delivers the given signal to the given process. +void deliver_signal(process_t *process, int signal); + +/// \brief Delivers the given signal to all processes. +void deliver_signal_to_all_processes(int signal); + +#endif diff --git a/lab7/c/include/oscos/shell.h b/lab7/c/include/oscos/shell.h new file mode 100644 index 000000000..742f7907e --- /dev/null +++ b/lab7/c/include/oscos/shell.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_SHELL_H +#define OSCOS_SHELL_H + +void run_shell(void); + +#endif diff --git a/lab7/c/include/oscos/timer/delay.h b/lab7/c/include/oscos/timer/delay.h new file mode 100644 index 000000000..49eebc7cd --- /dev/null +++ b/lab7/c/include/oscos/timer/delay.h @@ -0,0 +1,11 @@ +#ifndef OSCOS_TIMER_DELAY_H +#define OSCOS_TIMER_DELAY_H + +#include + +/// \brief Delays for the specified number of nanoseconds. +/// +/// Note that this function may delay for a longer period than specified. +void delay_ns(uint64_t ns); + +#endif diff --git a/lab7/c/include/oscos/timer/timeout.h b/lab7/c/include/oscos/timer/timeout.h new file mode 100644 index 000000000..a2e51e23f --- /dev/null +++ b/lab7/c/include/oscos/timer/timeout.h @@ -0,0 +1,17 @@ +#ifndef OSCOS_TIMER_TIMEOUT_H +#define OSCOS_TIMER_TIMEOUT_H + +#include +#include + +void timeout_init(void); + +bool timeout_add_timer_ns(void (*callback)(void *), void *arg, + uint64_t after_ns); + +bool timeout_add_timer_ticks(void (*callback)(void *), void *arg, + uint64_t after_ticks); + +void xcpt_core_timer_interrupt_handler(void); + +#endif diff --git a/lab7/c/include/oscos/uapi/errno.h b/lab7/c/include/oscos/uapi/errno.h new file mode 100644 index 000000000..1632c0383 --- /dev/null +++ b/lab7/c/include/oscos/uapi/errno.h @@ -0,0 +1,10 @@ +#define ENOENT 2 +#define ESRCH 3 +#define EINTR 4 +#define EIO 5 +#define ENOMEM 12 +#define EBUSY 16 +#define EISDIR 21 +#define EINVAL 22 +#define ENOSYS 38 +#define ELOOP 40 diff --git a/lab7/c/include/oscos/uapi/signal.h b/lab7/c/include/oscos/uapi/signal.h new file mode 100644 index 000000000..2e03401d8 --- /dev/null +++ b/lab7/c/include/oscos/uapi/signal.h @@ -0,0 +1,43 @@ +#ifndef OSCOS_UAPI_SIGNAL_H +#define OSCOS_UAPI_SIGNAL_H + +typedef void (*sighandler_t)(void); + +#define SIG_IGN ((sighandler_t)1) +#define SIG_DFL ((sighandler_t)2) + +#define SIGHUP 1 +#define SIGINT 2 +#define SIGQUIT 3 +#define SIGILL 4 +#define SIGTRAP 5 +#define SIGABRT 6 +#define SIGIOT 6 +#define SIGBUS 7 +#define SIGFPE 8 +#define SIGKILL 9 +#define SIGUSR1 10 +#define SIGSEGV 11 +#define SIGUSR2 12 +#define SIGPIPE 13 +#define SIGALRM 14 +#define SIGTERM 15 +#define SIGSTKFLT 16 +#define SIGCHLD 17 +#define SIGCONT 18 +#define SIGSTOP 19 +#define SIGTSTP 20 +#define SIGTTIN 21 +#define SIGTTOU 22 +#define SIGURG 23 +#define SIGXCPU 24 +#define SIGXFSZ 25 +#define SIGVTALRM 26 +#define SIGPROF 27 +#define SIGWINCH 28 +#define SIGIO 29 +#define SIGPWR 30 +#define SIGSYS 31 +#define SIGUNUSED 31 + +#endif diff --git a/lab7/c/include/oscos/uapi/sys/syscall.h b/lab7/c/include/oscos/uapi/sys/syscall.h new file mode 100644 index 000000000..910ddcc61 --- /dev/null +++ b/lab7/c/include/oscos/uapi/sys/syscall.h @@ -0,0 +1,16 @@ +#ifndef OSCOS_UAPI_SYS_SYSCALL_H +#define OSCOS_UAPI_SYS_SYSCALL_H + +#define SYS_getpid 0 +#define SYS_uart_read 1 +#define SYS_uart_write 2 +#define SYS_exec 3 +#define SYS_fork 4 +#define SYS_exit 5 +#define SYS_mbox_call 6 +#define SYS_kill 7 +#define SYS_signal 8 +#define SYS_signal_kill 9 +#define SYS_sigreturn 10 + +#endif diff --git a/lab7/c/include/oscos/uapi/unistd.h b/lab7/c/include/oscos/uapi/unistd.h new file mode 100644 index 000000000..80efebdf4 --- /dev/null +++ b/lab7/c/include/oscos/uapi/unistd.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_UAPI_UNISTD_H +#define OSCOS_UAPI_UNISTD_H + +typedef long ssize_t; + +#endif diff --git a/lab7/c/include/oscos/utils/align.h b/lab7/c/include/oscos/utils/align.h new file mode 100644 index 000000000..93d941b9e --- /dev/null +++ b/lab7/c/include/oscos/utils/align.h @@ -0,0 +1,13 @@ +#ifndef OSCOS_UTILS_ALIGN_H +#define OSCOS_UTILS_ALIGN_H + +/// \brief Returns \p X aligned to multiples of \p A. +/// +/// This macro is usually used to align a pointer. When doing so, cast the +/// pointer to `uintptr_t` and cast the result back to the desired pointer type. +/// +/// \param X The value to be aligned. Must be of an arithmetic type. +/// \param A The alignment. +#define ALIGN(X, A) (((X) + (A)-1) / (A) * (A)) + +#endif diff --git a/lab7/c/include/oscos/utils/control-flow.h b/lab7/c/include/oscos/utils/control-flow.h new file mode 100644 index 000000000..856c10c91 --- /dev/null +++ b/lab7/c/include/oscos/utils/control-flow.h @@ -0,0 +1,11 @@ +#ifndef OSCOS_UTILS_CONTROL_FLOW_H +#define OSCOS_UTILS_CONTROL_FLOW_H + +/// \brief Used to tell an operation whether it should exit early or go on as +/// usual. +/// +/// This is modelled after Rust's `std::ops::ControlFlow`, but this doesn't +/// carry values. +typedef enum { CF_CONTINUE, CF_BREAK } control_flow_t; + +#endif diff --git a/lab7/c/include/oscos/utils/core-id.h b/lab7/c/include/oscos/utils/core-id.h new file mode 100644 index 000000000..b23e78a25 --- /dev/null +++ b/lab7/c/include/oscos/utils/core-id.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_UTILS_CORE_ID_H +#define OSCOS_UTILS_CORE_ID_H + +#include + +size_t get_core_id(void); + +#endif diff --git a/lab7/c/include/oscos/utils/critical-section.h b/lab7/c/include/oscos/utils/critical-section.h new file mode 100644 index 000000000..05de74b18 --- /dev/null +++ b/lab7/c/include/oscos/utils/critical-section.h @@ -0,0 +1,20 @@ +#ifndef OSCOS_UTILS_CRITICAL_SECTION_H +#define OSCOS_UTILS_CRITICAL_SECTION_H + +#include "oscos/xcpt.h" + +#define CRITICAL_SECTION_ENTER(DAIF_VAL) \ + do { \ + __asm__ __volatile__("mrs %0, daif" : "=r"(DAIF_VAL)); \ + XCPT_MASK_ALL(); \ + } while (0) + +#define CRITICAL_SECTION_LEAVE(DAIF_VAL) \ + do { \ + /* Prevent the compiler from reordering memory accesses after interrupt \ + unmasking. */ \ + __asm__ __volatile__("" : : : "memory"); \ + __asm__ __volatile__("msr daif, %0" : : "r"(DAIF_VAL)); \ + } while (0) + +#endif diff --git a/lab7/c/include/oscos/utils/endian.h b/lab7/c/include/oscos/utils/endian.h new file mode 100644 index 000000000..1feb8f363 --- /dev/null +++ b/lab7/c/include/oscos/utils/endian.h @@ -0,0 +1,21 @@ +#ifndef OSCOS_UTILS_ENDIAN_H +#define OSCOS_UTILS_ENDIAN_H + +#include +#include + +/// \brief Reverses the byte order in a `uint32_t`. +static inline uint32_t rev_u32(const uint32_t x) { + uint32_t result; + __asm__("rev %w0, %w1" : "=r"(result) : "r"(x)); + return result; +} + +/// \brief Reverses the byte order in a `uint64_t`. +static inline uint64_t rev_u64(const uint64_t x) { + uint64_t result; + __asm__("rev %0, %1" : "=r"(result) : "r"(x)); + return result; +} + +#endif diff --git a/lab7/c/include/oscos/utils/fmt.h b/lab7/c/include/oscos/utils/fmt.h new file mode 100644 index 000000000..d9a68c4f9 --- /dev/null +++ b/lab7/c/include/oscos/utils/fmt.h @@ -0,0 +1,15 @@ +#ifndef OSCOS_UTILS_FMT_H +#define OSCOS_UTILS_FMT_H + +#include + +typedef struct { + void (*putc)(unsigned char, void *); + void (*finalize)(void *); +} printf_vtable_t; + +int vprintf_generic(const printf_vtable_t *vtable, void *arg, + const char *restrict format, va_list ap) + __attribute__((format(printf, 3, 0))); + +#endif diff --git a/lab7/c/include/oscos/utils/heapq.h b/lab7/c/include/oscos/utils/heapq.h new file mode 100644 index 000000000..6abd3d635 --- /dev/null +++ b/lab7/c/include/oscos/utils/heapq.h @@ -0,0 +1,14 @@ +#ifndef OSCOS_UTILS_HEAPQ_H +#define OSCOS_UTILS_HEAPQ_H + +#include + +void heappush(void *restrict base, size_t nmemb, size_t size, + const void *restrict item, + int (*compar)(const void *, const void *, void *), void *arg); + +void heappop(void *restrict base, size_t nmemb, size_t size, + void *restrict item, + int (*compar)(const void *, const void *, void *), void *arg); + +#endif diff --git a/lab7/c/include/oscos/utils/math.h b/lab7/c/include/oscos/utils/math.h new file mode 100644 index 000000000..1e91ed835 --- /dev/null +++ b/lab7/c/include/oscos/utils/math.h @@ -0,0 +1,13 @@ +#ifndef OSCOS_UTILS_MATH_H +#define OSCOS_UTILS_MATH_H + +#include + +/// \brief Returns the ceiling of the log base 2 of the argument. +static inline uint64_t clog2(const uint64_t x) { + uint64_t clz_result; + __asm__("clz %0, %1" : "=r"(clz_result) : "r"(x - 1)); + return 64 - clz_result; +} + +#endif diff --git a/lab7/c/include/oscos/utils/rb.h b/lab7/c/include/oscos/utils/rb.h new file mode 100644 index 000000000..85368c0c6 --- /dev/null +++ b/lab7/c/include/oscos/utils/rb.h @@ -0,0 +1,26 @@ +#ifndef OSCOS_UTILS_RB_H +#define OSCOS_UTILS_RB_H + +#include +#include +#include + +// typedef enum { RB_NC_BLACK, RB_NC_RED } rb_node_colour_t; + +typedef struct rb_node_t { + // rb_node_colour_t colour; + struct rb_node_t *children[2] /*, **parent */; + alignas(16) unsigned char payload[]; +} rb_node_t; + +const void *rb_search(const rb_node_t *root, const void *restrict key, + int (*compar)(const void *, const void *, void *), + void *arg); + +bool rb_insert(rb_node_t **root, size_t size, const void *restrict item, + int (*compar)(const void *, const void *, void *), void *arg); + +void rb_delete(rb_node_t **root, const void *restrict key, + int (*compar)(const void *, const void *, void *), void *arg); + +#endif diff --git a/lab7/c/include/oscos/utils/save-ctx.S b/lab7/c/include/oscos/utils/save-ctx.S new file mode 100644 index 000000000..4485dc9da --- /dev/null +++ b/lab7/c/include/oscos/utils/save-ctx.S @@ -0,0 +1,25 @@ +.macro save_aapcs + stp x0, x1, [sp, -(20 * 8)]! + stp x2, x3, [sp, 2 * 8] + stp x4, x5, [sp, 4 * 8] + stp x6, x7, [sp, 6 * 8] + stp x8, x9, [sp, 8 * 8] + stp x10, x11, [sp, 10 * 8] + stp x12, x13, [sp, 12 * 8] + stp x14, x15, [sp, 14 * 8] + stp x16, x17, [sp, 16 * 8] + stp x18, x30, [sp, 18 * 8] +.endm + +.macro load_aapcs + ldp x18, x30, [sp, 18 * 8] + ldp x16, x17, [sp, 16 * 8] + ldp x14, x15, [sp, 14 * 8] + ldp x12, x13, [sp, 12 * 8] + ldp x10, x11, [sp, 10 * 8] + ldp x8, x9, [sp, 8 * 8] + ldp x6, x7, [sp, 6 * 8] + ldp x4, x5, [sp, 4 * 8] + ldp x2, x3, [sp, 2 * 8] + ldp x0, x1, [sp], (20 * 8) +.endm diff --git a/lab7/c/include/oscos/utils/suspend.h b/lab7/c/include/oscos/utils/suspend.h new file mode 100644 index 000000000..c031cecc6 --- /dev/null +++ b/lab7/c/include/oscos/utils/suspend.h @@ -0,0 +1,19 @@ +#ifndef OSCOS_UTILS_SUSPEND_H +#define OSCOS_UTILS_SUSPEND_H + +#include "oscos/utils/critical-section.h" + +#define WFI_WHILE(COND) \ + do { \ + bool _wfi_while_cond_val = true; \ + while (_wfi_while_cond_val) { \ + uint64_t _wfi_while_daif_val; \ + CRITICAL_SECTION_ENTER(_wfi_while_daif_val); \ + if ((_wfi_while_cond_val = (COND))) { \ + __asm__ __volatile__("wfi"); \ + } \ + CRITICAL_SECTION_LEAVE(_wfi_while_daif_val); \ + } \ + } while (0) + +#endif diff --git a/lab7/c/include/oscos/utils/time.h b/lab7/c/include/oscos/utils/time.h new file mode 100644 index 000000000..12b4c463f --- /dev/null +++ b/lab7/c/include/oscos/utils/time.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_UTILS_TIME_H +#define OSCOS_UTILS_TIME_H + +#define NS_PER_SEC ((uint64_t)1000000000) + +#endif diff --git a/lab7/c/include/oscos/xcpt.h b/lab7/c/include/oscos/xcpt.h new file mode 100644 index 000000000..c5ca13530 --- /dev/null +++ b/lab7/c/include/oscos/xcpt.h @@ -0,0 +1,9 @@ +#ifndef OSCOS_XCPT_H +#define OSCOS_XCPT_H + +#define XCPT_MASK_ALL() __asm__ __volatile__("msr daifset, 0xf") +#define XCPT_UNMASK_ALL() __asm__ __volatile__("msr daifclr, 0xf") + +void xcpt_set_vector_table(void); + +#endif diff --git a/lab7/c/include/oscos/xcpt/task-queue.h b/lab7/c/include/oscos/xcpt/task-queue.h new file mode 100644 index 000000000..4f03ce4e6 --- /dev/null +++ b/lab7/c/include/oscos/xcpt/task-queue.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_XCPT_TASK_QUEUE_H +#define OSCOS_XCPT_TASK_QUEUE_H + +#include + +bool task_queue_add_task(void (*task)(void *), void *arg, int priority); + +#endif diff --git a/lab7/c/include/oscos/xcpt/trap-frame.h b/lab7/c/include/oscos/xcpt/trap-frame.h new file mode 100644 index 000000000..f5a1c11ee --- /dev/null +++ b/lab7/c/include/oscos/xcpt/trap-frame.h @@ -0,0 +1,24 @@ +#ifndef OSCOS_XCPT_TRAP_FRAME_H +#define OSCOS_XCPT_TRAP_FRAME_H + +#include +#include + +typedef struct { + alignas(16) union { + struct { + uint64_t r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, + r15, r16, r17, r18, lr; + }; + uint64_t regs[20]; + }; +} trap_frame_t; + +typedef struct { + alignas(16) struct { + uint64_t spsr, elr; + }; + trap_frame_t trap_frame; +} extended_trap_frame_t; + +#endif diff --git a/lab7/c/src/console.c b/lab7/c/src/console.c new file mode 100644 index 000000000..a41e69a49 --- /dev/null +++ b/lab7/c/src/console.c @@ -0,0 +1,388 @@ +#include "oscos/console.h" + +#include + +#include "oscos/drivers/gpio.h" +#include "oscos/drivers/l2ic.h" +#include "oscos/drivers/mini-uart.h" +#include "oscos/mem/startup-alloc.h" +#include "oscos/utils/critical-section.h" +#include "oscos/utils/fmt.h" +#include "oscos/xcpt.h" + +#define READ_BUF_SZ 1024 +#define WRITE_BUF_SZ 1024 + +static volatile unsigned char *_console_read_buf, *_console_write_buf; +static volatile size_t _console_read_buf_start = 0, _console_read_buf_len = 0, + _console_write_buf_start = 0, _console_write_buf_len = 0; +static void (*volatile _console_read_notify_callback)( + void *) = NULL, + (*volatile _console_write_notify_callback)(void *) = NULL; +static void *volatile _console_read_notify_callback_arg, + *volatile _console_write_notify_callback_arg; + +// Buffer operations. + +static void _console_recv_to_buf(void) { + // Read until the read buffer is full or there are nothing to read. + + int read_result; + while (_console_read_buf_len != READ_BUF_SZ && + (read_result = mini_uart_recv_byte_nonblock()) >= 0) { + _console_read_buf[(_console_read_buf_start + _console_read_buf_len++) % + READ_BUF_SZ] = read_result; + } + + // Disable the receive interrupt if the read buffer is full. Otherwise, the + // interrupt will fire again and again after exception return, blocking the + // main code from executing. + if (_console_read_buf_len == READ_BUF_SZ) { + mini_uart_disable_rx_interrupt(); + } + + // Send notification. + + if (_console_read_buf_len != 0) { + void (*const callback)(void *) = _console_read_notify_callback; + _console_read_notify_callback = NULL; + + if (callback) { + callback(_console_read_notify_callback_arg); + } + } +} + +static void _console_send_from_buf(void) { + while (_console_write_buf_len != 0 && + mini_uart_send_byte_nonblock( + _console_write_buf[_console_write_buf_start]) >= 0) { + _console_write_buf_start = (_console_write_buf_start + 1) % WRITE_BUF_SZ; + _console_write_buf_len--; + } + + // Disable the transmit interrupt if the write buffer is full. Otherwise, the + // interrupt will fire again and again after exception return, blocking the + // main code from executing. + if (_console_write_buf_len == 0) { + mini_uart_disable_tx_interrupt(); + } + + // Send notification. + + if (_console_write_buf_len != WRITE_BUF_SZ) { + void (*const callback)(void *) = _console_write_notify_callback; + _console_write_notify_callback = NULL; + + if (callback) { + callback(_console_write_notify_callback_arg); + } + } +} + +// Raw operations. + +static int _console_recv_byte_nonblock(void) { + if (_console_read_buf_len == 0) + return -1; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const unsigned char result = _console_read_buf[_console_read_buf_start]; + _console_read_buf_start = (_console_read_buf_start + 1) % READ_BUF_SZ; + _console_read_buf_len--; + + CRITICAL_SECTION_LEAVE(daif_val); + + // Re-enable receive interrupt. + mini_uart_enable_rx_interrupt(); + + return result; +} + +static bool _console_send_byte_nonblock(const unsigned char b) { + if (_console_write_buf_len == WRITE_BUF_SZ) + return false; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + _console_write_buf[(_console_write_buf_start + _console_write_buf_len++) % + WRITE_BUF_SZ] = b; + + CRITICAL_SECTION_LEAVE(daif_val); + + // Re-enable transmit interrupt. + mini_uart_enable_tx_interrupt(); + + return true; +} + +static bool _console_send_two_bytes_nonblock(const unsigned char b1, + const unsigned char b2) { + if (_console_write_buf_len > WRITE_BUF_SZ - 2) + return false; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + _console_write_buf[(_console_write_buf_start + _console_write_buf_len++) % + WRITE_BUF_SZ] = b1; + _console_write_buf[(_console_write_buf_start + _console_write_buf_len++) % + WRITE_BUF_SZ] = b2; + + CRITICAL_SECTION_LEAVE(daif_val); + + // Re-enable transmit interrupt. + mini_uart_enable_tx_interrupt(); + + return true; +} + +// Mode switching is implemented by switching the vtables for the two primitive +// operations, getc and putc. + +typedef struct { + int (*getc_nonblock)(void); + bool (*putc_nonblock)(unsigned char); +} console_primop_vtable_t; + +static int _console_getc_nonblock_text_mode(void) { + const int c = _console_recv_byte_nonblock(); + if (c < 0) + return c; + return c == '\r' ? '\n' : c; +} + +static int _console_getc_nonblock_binary_mode(void) { + return _console_recv_byte_nonblock(); +} + +static bool _console_putc_nonblock_text_mode(const unsigned char c) { + if (c == '\n') { + return _console_send_two_bytes_nonblock('\r', '\n'); + } else { + return _console_send_byte_nonblock(c); + } +} + +static bool _console_putc_nonblock_binary_mode(const unsigned char c) { + return _console_send_byte_nonblock(c); +} + +static const console_primop_vtable_t + _primop_vtable_text_mode = {.getc_nonblock = + _console_getc_nonblock_text_mode, + .putc_nonblock = + _console_putc_nonblock_text_mode}, + _primop_vtable_binary_mode = {.getc_nonblock = + _console_getc_nonblock_binary_mode, + .putc_nonblock = + _console_putc_nonblock_binary_mode}, + *_primop_vtable; + +// Public functions. + +void console_init(void) { + gpio_setup_uart0_gpio14(); + mini_uart_init(); + + console_set_mode(CM_TEXT); + + // Enable receive interrupt. + mini_uart_enable_rx_interrupt(); + // Enable AUX interrupt on the L2 interrupt controller. + l2ic_enable_irq_0(INT_L2_IRQ_0_SRC_AUX); + + // Allocate the buffers. + _console_read_buf = startup_alloc(READ_BUF_SZ * sizeof(unsigned char)); + _console_write_buf = startup_alloc(WRITE_BUF_SZ * sizeof(unsigned char)); +} + +void console_set_mode(const console_mode_t mode) { + // ? Should we check the value of `mode`? + // * FIXME: If the check is implemented, update the documentation in + // * `include/oscos/console.h`. + + switch (mode) { + case CM_TEXT: + _primop_vtable = &_primop_vtable_text_mode; + break; + + case CM_BINARY: + _primop_vtable = &_primop_vtable_binary_mode; + break; + + default: + __builtin_unreachable(); + } +} + +unsigned char console_getc(void) { + int read_result = -1; + uint64_t daif_val; + + while (read_result < 0) { + CRITICAL_SECTION_ENTER(daif_val); + + if ((read_result = console_getc_nonblock()) < 0) { + __asm__ __volatile__("wfi"); + _console_recv_to_buf(); + } + + CRITICAL_SECTION_LEAVE(daif_val); + } + + return read_result; +} + +int console_getc_nonblock(void) { return _primop_vtable->getc_nonblock(); } + +size_t console_read_nonblock(void *const buf, const size_t count) { + unsigned char *const buf_c = (unsigned char *)buf; + + size_t n_chars_read; + int read_result; + + for (n_chars_read = 0; + n_chars_read < count && (read_result = console_getc_nonblock()) >= 0; + n_chars_read++) { + buf_c[n_chars_read] = read_result; + } + + return n_chars_read; +} + +unsigned char console_putc(const unsigned char c) { + bool putc_successful = false; + uint64_t daif_val; + + while (!putc_successful) { + CRITICAL_SECTION_ENTER(daif_val); + + if (!(putc_successful = console_putc_nonblock(c) >= 0)) { + __asm__ __volatile__("wfi"); + _console_send_from_buf(); + } + + CRITICAL_SECTION_LEAVE(daif_val); + } + + return c; +} + +int console_putc_nonblock(const unsigned char c) { + const bool putc_successful = _primop_vtable->putc_nonblock(c); + return putc_successful ? c : -1; +} + +size_t console_write(const void *const buf, const size_t count) { + const unsigned char *const buf_c = buf; + for (size_t i = 0; i < count; i++) { + console_putc(buf_c[i]); + } + return count; +} + +size_t console_write_nonblock(const void *const buf, const size_t count) { + const unsigned char *const buf_c = buf; + + size_t n_chars_written; + bool putc_successful; + + for (n_chars_written = 0; + n_chars_written < count && + (putc_successful = console_putc_nonblock(buf_c[n_chars_written]) >= 0); + n_chars_written++) + ; + + return n_chars_written; +} + +void console_fputs(const char *const s) { + for (const char *c = s; *c; c++) { + console_putc(*c); + } +} + +void console_puts(const char *const s) { + console_fputs(s); + console_putc('\n'); +} + +int console_printf(const char *const restrict format, ...) { + va_list ap; + va_start(ap, format); + + const int result = console_vprintf(format, ap); + + va_end(ap); + return result; +} + +static void _console_printf_putc(const unsigned char c, void *const _arg) { + (void)_arg; + + console_putc(c); +} + +static void _console_printf_finalize(void *const _arg) { (void)_arg; } + +static const printf_vtable_t _console_printf_vtable = { + .putc = _console_printf_putc, .finalize = _console_printf_finalize}; + +int console_vprintf(const char *const restrict format, va_list ap) { + return vprintf_generic(&_console_printf_vtable, NULL, format, ap); +} + +bool console_notify_read_ready(void (*const callback)(void *), + void *const arg) { + if (_console_read_notify_callback) + return false; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + _console_read_notify_callback = callback; + _console_read_notify_callback_arg = arg; + + CRITICAL_SECTION_LEAVE(daif_val); + return true; +} + +bool console_notify_write_ready(void (*const callback)(void *), + void *const arg) { + if (_console_write_notify_callback) + return false; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + _console_write_notify_callback = callback; + _console_write_notify_callback_arg = arg; + + CRITICAL_SECTION_LEAVE(daif_val); + return true; +} + +void console_flush_write_buffer(void) { + bool suspend_cond_val = true; + uint64_t daif_val; + + while (suspend_cond_val) { + CRITICAL_SECTION_ENTER(daif_val); + + if ((suspend_cond_val = _console_write_buf_len > 0)) { + __asm__ __volatile__("wfi"); + _console_send_from_buf(); + } + + CRITICAL_SECTION_LEAVE(daif_val); + } +} + +void mini_uart_interrupt_handler(void) { + _console_recv_to_buf(); + _console_send_from_buf(); +} diff --git a/lab7/c/src/devicetree.c b/lab7/c/src/devicetree.c new file mode 100644 index 000000000..8a4d75275 --- /dev/null +++ b/lab7/c/src/devicetree.c @@ -0,0 +1,129 @@ +#include "oscos/devicetree.h" + +#include + +static const char *_dtb_start = NULL; + +bool devicetree_init(const void *const dtb_start) { + // TODO: More thoroughly validate the DTB. + + if (rev_u32(((const fdt_header_t *)dtb_start)->magic) == 0xd00dfeed) { + _dtb_start = dtb_start; + return true; + } else { + _dtb_start = NULL; + return false; + } +} + +bool devicetree_is_init(void) { return _dtb_start; } + +const char *fdt_get_start(void) { return _dtb_start; } + +const char *fdt_get_end(void) { + return _dtb_start + rev_u32(((const fdt_header_t *)_dtb_start)->totalsize); +} + +const fdt_item_t *fdt_next_item(const fdt_item_t *const item) { + switch (FDT_TOKEN(item)) { + case FDT_BEGIN_NODE: { + const fdt_item_t *curr; + FDT_FOR_ITEM_(item, curr); + return curr + 1; + } + case FDT_PROP: { + const fdt_prop_t *const prop = (const fdt_prop_t *)(item->payload); + return (const fdt_item_t *)ALIGN( + (uintptr_t)(prop->value + rev_u32(prop->len)), 4); + } + case FDT_NOP: + return item + 1; + default: + __builtin_unreachable(); + } +} + +static const fdt_item_t * +_fdt_traverse_rec(const fdt_item_t *const node, + const fdt_traverse_parent_list_node_t *const parent, + fdt_traverse_callback_t *const callback, void *const arg) { + const control_flow_t result = callback(arg, node, parent); + if (result == CF_CONTINUE) { + // No-op. + } else if (result == CF_BREAK) { + return NULL; + } else { + __builtin_unreachable(); + } + + const fdt_item_t *item; + for (item = FDT_ITEMS_START(node); !FDT_ITEM_IS_END(item);) { + if (FDT_TOKEN(item) == FDT_BEGIN_NODE) { + const fdt_traverse_parent_list_node_t next_parent = {.node = node, + .parent = parent}; + const fdt_item_t *const next_item = + _fdt_traverse_rec(item, &next_parent, callback, arg); + if (!next_item) + return NULL; + item = next_item; + } else { + item = fdt_next_item(item); + } + } + + return item + 1; +} + +void fdt_traverse(fdt_traverse_callback_t *const callback, void *const arg) { + const fdt_item_t *const root_node = + (const fdt_item_t *)(_dtb_start + + rev_u32(((const fdt_header_t *)_dtb_start) + ->off_dt_struct)); + _fdt_traverse_rec(root_node, NULL, callback, arg); +} + +fdt_n_address_size_cells_t +fdt_get_n_address_size_cells(const fdt_item_t *const node) { + uint32_t n_address_cells = 2, n_size_cells = 1; + bool n_address_cells_done = false, n_size_cells_done = false; + FDT_FOR_ITEM(node, item) { + if (FDT_TOKEN(item) == FDT_PROP) { + const fdt_prop_t *const prop = (const fdt_prop_t *)item->payload; + if (strcmp(FDT_PROP_NAME(prop), "#address-cells") == 0) { + n_address_cells = rev_u32(*(const uint32_t *)FDT_PROP_VALUE(prop)); + n_address_cells_done = true; + } else if (strcmp(FDT_PROP_NAME(prop), "#size-cells") == 0) { + n_size_cells = rev_u32(*(const uint32_t *)FDT_PROP_VALUE(prop)); + n_size_cells_done = true; + } + } + if (n_address_cells_done && n_size_cells_done) + break; + } + + return (fdt_n_address_size_cells_t){.n_address_cells = n_address_cells, + .n_size_cells = n_size_cells}; +} + +fdt_read_reg_result_t fdt_read_reg(const fdt_prop_t *const prop, + const fdt_n_address_size_cells_t n_cells) { + const uint32_t *arr = (const uint32_t *)FDT_PROP_VALUE(prop); + + uintmax_t address = 0; + bool address_overflow = false; + for (uint32_t i = 0; i < n_cells.n_address_cells; i++) { + address_overflow = address_overflow || (address >> 32) != 0; + address = address << 32 | rev_u32(*arr++); + } + + uintmax_t size = 0; + bool size_overflow = false; + for (uint32_t i = 0; i < n_cells.n_size_cells; i++) { + size_overflow = size_overflow || (size >> 32) != 0; + size = size << 32 | rev_u32(*arr++); + } + + return (fdt_read_reg_result_t){.value = {.address = address, .size = size}, + .address_overflow = address_overflow, + .size_overflow = size_overflow}; +} diff --git a/lab7/c/src/drivers/aux.c b/lab7/c/src/drivers/aux.c new file mode 100644 index 000000000..c40639f09 --- /dev/null +++ b/lab7/c/src/drivers/aux.c @@ -0,0 +1,28 @@ +#include "oscos/drivers/aux.h" + +#include + +#include "oscos/drivers/board.h" + +#define AUX_REG_BASE ((void *)((char *)PERIPHERAL_BASE + 0x215000)) + +typedef struct { + const volatile uint32_t irq; + volatile uint32_t enb; +} aux_reg_t; + +#define AUX_REG ((aux_reg_t *)AUX_REG_BASE) + +#define AUX_ENB_MINI_UART_ENABLE ((uint32_t)(1 << 0)) + +void aux_init(void) { + // No-op. +} + +void aux_enable_mini_uart(void) { + PERIPHERAL_WRITE_BARRIER(); + + AUX_REG->enb |= AUX_ENB_MINI_UART_ENABLE; + + PERIPHERAL_READ_BARRIER(); +} diff --git a/lab7/c/src/drivers/gpio.c b/lab7/c/src/drivers/gpio.c new file mode 100644 index 000000000..9f8802011 --- /dev/null +++ b/lab7/c/src/drivers/gpio.c @@ -0,0 +1,105 @@ +#include "oscos/drivers/gpio.h" + +#include + +#include "oscos/drivers/board.h" +#include "oscos/timer/delay.h" + +#define GPIO_REG_BASE ((void *)((char *)PERIPHERAL_BASE + 0x200000)) + +typedef struct { + volatile uint32_t fsel[6]; + const volatile uint32_t _reserved0; + volatile uint32_t set[2]; + const volatile uint32_t _reserved1; + volatile uint32_t clr[2]; + const volatile uint32_t _reserved2; + const volatile uint32_t lev[2]; + const volatile uint32_t _reserved3; + volatile uint32_t eds[2]; + const volatile uint32_t _reserved4; + volatile uint32_t ren[2]; + const volatile uint32_t _reserved5; + volatile uint32_t fen[2]; + const volatile uint32_t _reserved6; + volatile uint32_t hen[2]; + const volatile uint32_t _reserved7; + volatile uint32_t len[2]; + const volatile uint32_t _reserved8; + volatile uint32_t aren[2]; + const volatile uint32_t _reserved9; + volatile uint32_t afen[2]; + const volatile uint32_t _reserved10; + volatile uint32_t pud; + volatile uint32_t pudclk[2]; +} gpio_reg_t; + +#define GPIO_REG ((gpio_reg_t *)GPIO_REG_BASE) + +#define GPIO_FSEL_FSEL4_POSN 12 +#define GPIO_FSEL_FSEL4_MASK ((uint32_t)((uint32_t)0x7 << GPIO_FSEL_FSEL4_POSN)) +#define GPIO_FSEL_FSEL5_POSN 15 +#define GPIO_FSEL_FSEL5_MASK ((uint32_t)((uint32_t)0x7 << GPIO_FSEL_FSEL5_POSN)) +#define GPIO_FSEL_FSEL_INPUT ((uint32_t)0x0) +#define GPIO_FSEL_FSEL_ALT5 ((uint32_t)0x2) +#define GPIO_FSEL_FSEL4_INPUT \ + ((uint32_t)(GPIO_FSEL_FSEL_INPUT << GPIO_FSEL_FSEL4_POSN)) +#define GPIO_FSEL_FSEL4_ALT5 \ + ((uint32_t)(GPIO_FSEL_FSEL_ALT5 << GPIO_FSEL_FSEL4_POSN)) +#define GPIO_FSEL_FSEL5_INPUT \ + ((uint32_t)(GPIO_FSEL_FSEL_INPUT << GPIO_FSEL_FSEL5_POSN)) +#define GPIO_FSEL_FSEL5_ALT5 \ + ((uint32_t)(GPIO_FSEL_FSEL_ALT5 << GPIO_FSEL_FSEL5_POSN)) + +#define GPIO_PUD_PUD_OFF ((uint32_t)0x0) +#define GPIO_PUD_PUD_PULL_UP ((uint32_t)0x2) + +void gpio_init(void) { + // No-op. +} + +void gpio_setup_uart0_gpio14(void) { + PERIPHERAL_WRITE_BARRIER(); + + // Set GPIO pin 14 & 15 to use alternate function 5 ({T,R}XD1). + GPIO_REG->fsel[1] = + (GPIO_REG->fsel[1] & ~(GPIO_FSEL_FSEL4_MASK | GPIO_FSEL_FSEL5_MASK)) | + (GPIO_FSEL_FSEL4_ALT5 | GPIO_FSEL_FSEL5_ALT5); + + // Setup the GPIO pull up/down resistors on pin 14 & 15. + // Pin 14 (TXD1): Disabled. + // Pin 15 (RXD1): Pull up. + // + // Instead of disabling the pull up/down resistors on pin 15 as specified in + // lab 1, we instead pull it up to ensure that mini UART doesn't read in + // garbage data when the pin is not connected. + // + // The delay period of 1 μs is calculated by dividing the required delay + // period of 150 clock cycles (as specified in [bcm2835-datasheet]) by 150 + // MHz, the nominal core frequency mentioned in [bcm2835-datasheet], pp. 34. + // We believe 150 MHz should be used instead of the actual core frequency of + // 250 MHz because the setup/hold time of a digital circuit is usually + // specified in terms of real time (e.g. nanoseconds) instead of in clock + // cycles. The specified delay period of 150 clock cycles might have been + // derived by multiplying the actual required delay period of 1 μs by the + // nominal core frequency of 150 MHz. + // + // [bcm2835-datasheet]: + // https://datasheets.raspberrypi.com/bcm2835/bcm2835-peripherals.pdf + + GPIO_REG->pud = GPIO_PUD_PUD_OFF; + delay_ns(1000); + GPIO_REG->pudclk[0] = 1 << 14; + delay_ns(1000); + GPIO_REG->pud = 0; + GPIO_REG->pudclk[0] = 0; + + GPIO_REG->pud = GPIO_PUD_PUD_PULL_UP; + delay_ns(1000); + GPIO_REG->pudclk[0] = 1 << 15; + delay_ns(1000); + GPIO_REG->pud = 0; + GPIO_REG->pudclk[0] = 0; + + PERIPHERAL_READ_BARRIER(); +} diff --git a/lab7/c/src/drivers/l1ic.c b/lab7/c/src/drivers/l1ic.c new file mode 100644 index 000000000..9af46c781 --- /dev/null +++ b/lab7/c/src/drivers/l1ic.c @@ -0,0 +1,37 @@ +#include "oscos/drivers/l1ic.h" + +#include "oscos/drivers/board.h" + +#define CORE_TIMER_IRQCNTL \ + ((volatile uint32_t(*)[4])((char *)ARM_LOCAL_PERIPHERAL_BASE + 0x40)) +#define CORE_IRQ_SOURCE \ + ((volatile uint32_t(*)[4])((char *)ARM_LOCAL_PERIPHERAL_BASE + 0x60)) +#define CORE_FIQ_SOURCE \ + ((volatile uint32_t(*)[4])((char *)ARM_LOCAL_PERIPHERAL_BASE + 0x70)) + +#define CORE_TIMER_IRQCNTL_TIMER0_IRQ ((uint32_t)(1 << 0)) +#define CORE_TIMER_IRQCNTL_TIMER1_IRQ ((uint32_t)(1 << 1)) +#define CORE_TIMER_IRQCNTL_TIMER2_IRQ ((uint32_t)(1 << 2)) +#define CORE_TIMER_IRQCNTL_TIMER3_IRQ ((uint32_t)(1 << 3)) +#define CORE_TIMER_IRQCNTL_TIMER0_FIQ ((uint32_t)(1 << 4)) +#define CORE_TIMER_IRQCNTL_TIMER1_FIQ ((uint32_t)(1 << 5)) +#define CORE_TIMER_IRQCNTL_TIMER2_FIQ ((uint32_t)(1 << 6)) +#define CORE_TIMER_IRQCNTL_TIMER3_FIQ ((uint32_t)(1 << 7)) + +void l1ic_init(void) { + // No-op. +} + +uint32_t l1ic_get_int_src(const size_t core_id) { + const uint32_t result = (*CORE_IRQ_SOURCE)[core_id]; + + PERIPHERAL_READ_BARRIER(); + return result; +} + +void l1ic_enable_core_timer_irq(size_t core_id) { + PERIPHERAL_WRITE_BARRIER(); + + // nCNTPNSIRQ IRQ control = 1 + (*CORE_TIMER_IRQCNTL)[core_id] = CORE_TIMER_IRQCNTL_TIMER1_IRQ; +} diff --git a/lab7/c/src/drivers/l2ic.c b/lab7/c/src/drivers/l2ic.c new file mode 100644 index 000000000..13c15e94c --- /dev/null +++ b/lab7/c/src/drivers/l2ic.c @@ -0,0 +1,36 @@ +#include "oscos/drivers/l2ic.h" + +#include "oscos/drivers/board.h" + +#define L2IC_REG_BASE ((void *)((char *)PERIPHERAL_BASE + 0xb200)) + +typedef struct { + const volatile uint32_t irq_basic_pending; + const volatile uint32_t irq_pending[2]; + volatile uint32_t fiq_control; + volatile uint32_t enable_irqs[2]; + volatile uint32_t enable_basic_irqs; + volatile uint32_t disable_irqs[2]; + volatile uint32_t disable_basic_irqs; +} l2ic_reg_t; + +#define L2IC_REG ((l2ic_reg_t *)L2IC_REG_BASE) + +void l2ic_init(void) { + // No-op. +} + +uint32_t l2ic_get_pending_irq_0(void) { + const uint32_t result = L2IC_REG->irq_pending[0]; + + PERIPHERAL_READ_BARRIER(); + return result; +} + +void l2ic_enable_irq_0(uint32_t mask) { + PERIPHERAL_WRITE_BARRIER(); + + L2IC_REG->enable_irqs[0] |= mask; + + PERIPHERAL_READ_BARRIER(); +} diff --git a/lab7/c/src/drivers/mailbox.c b/lab7/c/src/drivers/mailbox.c new file mode 100644 index 000000000..0cb84d408 --- /dev/null +++ b/lab7/c/src/drivers/mailbox.c @@ -0,0 +1,84 @@ +#include "oscos/drivers/mailbox.h" + +#include + +#include "oscos/drivers/board.h" +#include "oscos/mem/vm.h" + +#define MAILBOX_REG_BASE ((void *)((char *)PERIPHERAL_BASE + 0xb880)) + +typedef struct { + volatile uint32_t read_write; + const volatile uint32_t _reserved[3]; + volatile uint32_t peek; + volatile uint32_t sender; + volatile uint32_t status; + volatile uint32_t config; +} mailbox_reg_t; + +#define MAILBOX_REGS ((mailbox_reg_t *)MAILBOX_REG_BASE) + +#define MAILBOX_STATUS_EMPTY_MASK ((uint32_t)(1 << 30)) +#define MAILBOX_STATUS_FULL_MASK ((uint32_t)(1 << 31)) + +#define GET_BOARD_REVISION ((uint32_t)0x00010002) +#define GET_ARM_MEMORY ((uint32_t)0x00010005) +#define REQUEST_CODE ((uint32_t)0x00000000) +#define REQUEST_SUCCEED ((uint32_t)0x80000000) +#define REQUEST_FAILED ((uint32_t)0x80000001) +#define TAG_REQUEST_CODE ((uint32_t)0x00000000) +#define END_TAG ((uint32_t)0x00000000) + +void mailbox_init(void) { + // No-op. +} + +void mailbox_call(uint32_t message[], const unsigned char channel) { + PERIPHERAL_WRITE_BARRIER(); + + const uint32_t mailbox_write_data = kernel_va_to_pa(message) | channel; + + while (MAILBOX_REGS[1].status & MAILBOX_STATUS_FULL_MASK) + ; + MAILBOX_REGS[1].read_write = mailbox_write_data; + + for (;;) { + while (MAILBOX_REGS[0].status & MAILBOX_STATUS_EMPTY_MASK) + ; + const uint32_t mailbox_read_data = MAILBOX_REGS[0].read_write; + + if (mailbox_write_data == mailbox_read_data) + break; + } + + PERIPHERAL_READ_BARRIER(); +} + +uint32_t mailbox_get_board_revision(void) { + alignas(16) uint32_t mailbox[7] = {7 * sizeof(uint32_t), + REQUEST_CODE, + GET_BOARD_REVISION, + 4, + TAG_REQUEST_CODE, + 0, + END_TAG}; + + mailbox_call(mailbox, MAILBOX_CHANNEL_PROPERTY_TAGS_ARM_TO_VC); + + return mailbox[5]; +} + +arm_memory_t mailbox_get_arm_memory(void) { + alignas(16) uint32_t mailbox[8] = {8 * sizeof(uint32_t), + REQUEST_CODE, + GET_ARM_MEMORY, + 8, + TAG_REQUEST_CODE, + 0, + 0, + END_TAG}; + + mailbox_call(mailbox, MAILBOX_CHANNEL_PROPERTY_TAGS_ARM_TO_VC); + + return (arm_memory_t){.base = mailbox[5], .size = mailbox[6]}; +} diff --git a/lab7/c/src/drivers/mini-uart.c b/lab7/c/src/drivers/mini-uart.c new file mode 100644 index 000000000..c23500049 --- /dev/null +++ b/lab7/c/src/drivers/mini-uart.c @@ -0,0 +1,130 @@ +#include "oscos/drivers/mini-uart.h" + +#include + +#include "oscos/drivers/aux.h" +#include "oscos/drivers/board.h" + +#define MINI_UART_REG_BASE ((void *)((char *)PERIPHERAL_BASE + 0x215040)) + +typedef struct { + volatile uint32_t io; + volatile uint32_t ier; + volatile uint32_t iir; + volatile uint32_t lcr; + volatile uint32_t mcr; + const volatile uint32_t lsr; + const volatile uint32_t msr; + volatile uint32_t scratch; + volatile uint32_t cntl; + const volatile uint32_t stat; + volatile uint32_t baud; +} mini_uart_reg_t; + +#define MINI_UART_REG ((mini_uart_reg_t *)MINI_UART_REG_BASE) + +// N. B. The interrupt bits specified in the datasheet are incorrect. The values +// listed here are correct. See [bcm2837-datasheet-errata]. +// [bcm2835-datasheet-errata]: https://elinux.org/BCM2835_datasheet_errata + +#define MINI_UART_IER_ENABLE_RECEIVE_INTERRUPT ((uint32_t)(1 << 0)) +#define MINI_UART_IER_ENABLE_TRANSMIT_INTERRUPT ((uint32_t)(1 << 1)) + +#define MINI_UART_LSR_DATA_READY ((uint32_t)(1 << 0)) +#define MINI_UART_LSR_TRANSMITTER_IDLE ((uint32_t)(1 << 6)) +#define MINI_UART_LSR_TRANSMITTER_EMPTY ((uint32_t)(1 << 5)) + +void mini_uart_init(void) { + // The initialization procedure is taken from + // https://oscapstone.github.io/labs/hardware/uart.html. + + // Enable mini UART. + aux_enable_mini_uart(); + + PERIPHERAL_WRITE_BARRIER(); + + // Disable TX and RX. + MINI_UART_REG->cntl = 0; + // Disable interrupt. + MINI_UART_REG->ier = 0; + // Set the data size to 8 bits. + // N. B. The datasheet [bcm2835-datasheet] incorrectly indicates that only bit + // 0 needs to be set. In fact, bits [1:0] need to be set to 3. See + // [bcm2835-datasheet-errata], #p14. + MINI_UART_REG->lcr = 3; + // Disable auto flow control. + MINI_UART_REG->mcr = 0; + // Set baud rate to 115200. + MINI_UART_REG->baud = 270; + // Clear the transmit and receive FIFOs. + MINI_UART_REG->iir = 6; + // Enable TX and RX. + MINI_UART_REG->cntl = 3; + + PERIPHERAL_READ_BARRIER(); +} + +int mini_uart_recv_byte_nonblock(void) { + int result; + + if (!(MINI_UART_REG->lsr & MINI_UART_LSR_DATA_READY)) { + result = -1; + goto end; + } + + result = (unsigned char)MINI_UART_REG->io; + +end: + PERIPHERAL_READ_BARRIER(); + return result; +} + +int mini_uart_send_byte_nonblock(const unsigned char b) { + PERIPHERAL_WRITE_BARRIER(); + + int result; + + if (!(MINI_UART_REG->lsr & MINI_UART_LSR_TRANSMITTER_EMPTY)) { + result = -1; + goto end; + } + + MINI_UART_REG->io = b; + result = 0; + +end: + PERIPHERAL_READ_BARRIER(); + return result; +} + +void mini_uart_enable_rx_interrupt(void) { + PERIPHERAL_WRITE_BARRIER(); + + MINI_UART_REG->ier |= MINI_UART_IER_ENABLE_RECEIVE_INTERRUPT; + + PERIPHERAL_READ_BARRIER(); +} + +void mini_uart_disable_rx_interrupt(void) { + PERIPHERAL_WRITE_BARRIER(); + + MINI_UART_REG->ier &= ~MINI_UART_IER_ENABLE_RECEIVE_INTERRUPT; + + PERIPHERAL_READ_BARRIER(); +} + +void mini_uart_enable_tx_interrupt(void) { + PERIPHERAL_WRITE_BARRIER(); + + MINI_UART_REG->ier |= MINI_UART_IER_ENABLE_TRANSMIT_INTERRUPT; + + PERIPHERAL_READ_BARRIER(); +} + +void mini_uart_disable_tx_interrupt(void) { + PERIPHERAL_WRITE_BARRIER(); + + MINI_UART_REG->ier &= ~MINI_UART_IER_ENABLE_TRANSMIT_INTERRUPT; + + PERIPHERAL_READ_BARRIER(); +} diff --git a/lab7/c/src/drivers/pm.c b/lab7/c/src/drivers/pm.c new file mode 100644 index 000000000..ca4a85fd1 --- /dev/null +++ b/lab7/c/src/drivers/pm.c @@ -0,0 +1,85 @@ +#include "oscos/drivers/pm.h" + +#include + +#include "oscos/drivers/board.h" + +// Information related to the PM registers can be retrieved from [bcm2835-regs], +// #PM. +// +// [bcm2835-regs]: https://elinux.org/BCM2835_registers#PM + +#define PM_REG_BASE ((void *)((char *)PERIPHERAL_BASE + 0x100000)) + +typedef struct { + volatile uint32_t gnric; + volatile uint32_t audio; + const volatile uint32_t _reserved0[4]; + const volatile uint32_t status; + volatile uint32_t rstc; + volatile uint32_t rsts; + volatile uint32_t wdot; + volatile uint32_t pads0; + volatile uint32_t pads2; + volatile uint32_t pads3; + volatile uint32_t pads4; + volatile uint32_t pads5; + volatile uint32_t pads6; + const volatile uint32_t _reserved1; + volatile uint32_t cam0; + volatile uint32_t cam1; + volatile uint32_t cpp2tx; + volatile uint32_t dsi0; + volatile uint32_t dsi1; + volatile uint32_t hdmi; + volatile uint32_t usb; + volatile uint32_t pxldo; + volatile uint32_t pxbg; + volatile uint32_t dft; + volatile uint32_t smps; + volatile uint32_t xosc; + volatile uint32_t sparew; + const volatile uint32_t sparer; + volatile uint32_t avs_rstdr; + volatile uint32_t avs_stat; + volatile uint32_t avs_event; + volatile uint32_t avs_inten; + const volatile uint32_t _reserved2[29]; + const volatile uint32_t dummy; + const volatile uint32_t _reserved3[2]; + volatile uint32_t image; + volatile uint32_t grafx; + volatile uint32_t proc; +} pm_reg_t; + +#define PM_REG ((pm_reg_t *)PM_REG_BASE) + +#define PM_PASSWORD 0x5a000000 + +void pm_init(void) { + // No-op. +} + +void pm_reset(const uint32_t tick) { + PERIPHERAL_WRITE_BARRIER(); + + PM_REG->rstc = PM_PASSWORD | 0x20; + PM_REG->wdot = PM_PASSWORD | tick; + + PERIPHERAL_READ_BARRIER(); +} + +void pm_cancel_reset(void) { + PERIPHERAL_WRITE_BARRIER(); + + PM_REG->rstc = PM_PASSWORD | 0; + PM_REG->wdot = PM_PASSWORD | 0; + + PERIPHERAL_READ_BARRIER(); +} + +noreturn void pm_reboot(void) { + pm_reset(1); + for (;;) + ; +} diff --git a/lab7/c/src/initrd.c b/lab7/c/src/initrd.c new file mode 100644 index 000000000..2ae0f9561 --- /dev/null +++ b/lab7/c/src/initrd.c @@ -0,0 +1,131 @@ +#include "oscos/initrd.h" + +#include "oscos/devicetree.h" +#include "oscos/mem/vm.h" + +static const void *_initrd_start, *_initrd_end; + +static bool _cpio_newc_is_header_field_valid(const char field[const static 8]) { + for (size_t i = 0; i < 8; i++) { + if (!(('0' <= field[i] && field[i] <= '9') || + ('A' <= field[i] && field[i] <= 'F'))) + return false; + } + return true; +} + +static bool _initrd_is_valid(void) { + const cpio_newc_entry_t *entry; + + // Cannot use INITRD_FOR_ENTRY here, since it will evaluate + // `CPIO_NEWC_IS_ENTRY_LAST(entry)` before `entry` is validated. + for (entry = INITRD_HEAD;; entry = CPIO_NEWC_NEXT_ENTRY(entry)) { + if (entry >= (cpio_newc_entry_t *)_initrd_end) + return false; + if (!(strncmp(entry->header.c_magic, "070701", 6) == 0 && + _cpio_newc_is_header_field_valid(entry->header.c_mode) && + _cpio_newc_is_header_field_valid(entry->header.c_uid) && + _cpio_newc_is_header_field_valid(entry->header.c_gid) && + _cpio_newc_is_header_field_valid(entry->header.c_nlink) && + _cpio_newc_is_header_field_valid(entry->header.c_mtime) && + _cpio_newc_is_header_field_valid(entry->header.c_filesize) && + _cpio_newc_is_header_field_valid(entry->header.c_devmajor) && + _cpio_newc_is_header_field_valid(entry->header.c_devminor) && + _cpio_newc_is_header_field_valid(entry->header.c_rdevmajor) && + _cpio_newc_is_header_field_valid(entry->header.c_rdevminor) && + _cpio_newc_is_header_field_valid(entry->header.c_namesize) && + _cpio_newc_is_header_field_valid(entry->header.c_check))) + return false; + + if (CPIO_NEWC_IS_ENTRY_LAST(entry)) + break; + } + + return CPIO_NEWC_NEXT_ENTRY(entry) <= (cpio_newc_entry_t *)_initrd_end; +} + +typedef struct { + bool start_done, end_done; +} initrd_init_dtb_traverse_callback_arg_t; + +static control_flow_t _initrd_init_dtb_traverse_callback( + initrd_init_dtb_traverse_callback_arg_t *const arg, + const fdt_item_t *const node, + const fdt_traverse_parent_list_node_t *const parent) { + if (parent && !parent->parent && + strcmp(FDT_NODE_NAME(node), "chosen") == 0) { // Current node is /chosen. + FDT_FOR_ITEM(node, item) { + if (FDT_TOKEN(item) == FDT_PROP) { + const fdt_prop_t *const prop = (const fdt_prop_t *)item->payload; + if (strcmp(FDT_PROP_NAME(prop), "linux,initrd-start") == 0) { + const uint32_t pa = rev_u32(*(const uint32_t *)FDT_PROP_VALUE(prop)); + _initrd_start = pa_to_kernel_va(pa); + arg->start_done = true; + } else if (strcmp(FDT_PROP_NAME(prop), "linux,initrd-end") == 0) { + const uint32_t pa = rev_u32(*(const uint32_t *)FDT_PROP_VALUE(prop)); + _initrd_end = pa_to_kernel_va(pa); + arg->end_done = true; + } + + if (arg->start_done && arg->end_done) + break; + } + } + return CF_BREAK; + } else { + return CF_CONTINUE; + } +} + +bool initrd_init(void) { + _initrd_start = NULL; + + if (devicetree_is_init()) { + // Discover the initrd loading address through the devicetree. + + initrd_init_dtb_traverse_callback_arg_t arg = {.start_done = false, + .end_done = false}; + fdt_traverse((fdt_traverse_callback_t *)_initrd_init_dtb_traverse_callback, + &arg); + if (!(arg.start_done && + arg.end_done)) { // Either the /chosen/linux,initrd-start or the + // /chosen/linux,initrd-end property is missing from + // the devicetree. + _initrd_start = NULL; + } + } + + // Validate the initial ramdisk. + if (!(_initrd_start && _initrd_is_valid())) { + _initrd_start = NULL; + } + + return _initrd_start; +} + +bool initrd_is_init(void) { return _initrd_start; } + +const void *initrd_get_start(void) { return _initrd_start; } + +const void *initrd_get_end(void) { return _initrd_end; } + +uint32_t cpio_newc_parse_header_field(const char field[static 8]) { + uint32_t result = 0; + for (size_t i = 0; i < 8; i++) { + const uint32_t digit_value = + '0' <= field[i] && field[i] <= '9' ? field[i] - '0' + : 'A' <= field[i] && field[i] <= 'F' ? field[i] - 'A' + 10 + : (__builtin_unreachable(), 0); + result = result << 4 | digit_value; + } + return result; +} + +const cpio_newc_entry_t *initrd_find_entry_by_pathname(const char *pathname) { + INITRD_FOR_ENTRY(entry) { + if (strcmp(CPIO_NEWC_PATHNAME(entry), pathname) == 0) { + return entry; + } + } + return NULL; +} diff --git a/lab7/c/src/libc/ctype.c b/lab7/c/src/libc/ctype.c new file mode 100644 index 000000000..6ff704afb --- /dev/null +++ b/lab7/c/src/libc/ctype.c @@ -0,0 +1,3 @@ +#include "oscos/libc/ctype.h" + +int isdigit(const int c) { return '0' <= c && c <= '9'; } diff --git a/lab7/c/src/libc/stdio.c b/lab7/c/src/libc/stdio.c new file mode 100644 index 000000000..766cbb465 --- /dev/null +++ b/lab7/c/src/libc/stdio.c @@ -0,0 +1,41 @@ +#include "oscos/libc/stdio.h" + +#include "oscos/utils/fmt.h" + +int snprintf(char str[const restrict], const size_t size, + const char *const restrict format, ...) { + va_list ap; + va_start(ap, format); + + const int result = vsnprintf(str, size, format, ap); + + va_end(ap); + return result; +} + +typedef struct { + char *str; + size_t size, len; +} snprintf_arg_t; + +static void _snprintf_putc(const unsigned char c, snprintf_arg_t *const arg) { + if (arg->size > 0 && arg->len < arg->size - 1) { + arg->str[arg->len++] = c; + } +} + +static void _snprintf_finalize(snprintf_arg_t *const arg) { + if (arg->size > 0) { + arg->str[arg->len] = '\0'; + } +} + +static const printf_vtable_t _snprintf_vtable = { + .putc = (void (*)(unsigned char, void *))_snprintf_putc, + .finalize = (void (*)(void *))_snprintf_finalize}; + +int vsnprintf(char str[const restrict], const size_t size, + const char *const restrict format, va_list ap) { + snprintf_arg_t arg = {.str = str, .size = size, .len = 0}; + return vprintf_generic(&_snprintf_vtable, &arg, format, ap); +} diff --git a/lab7/c/src/libc/stdlib/qsort.c b/lab7/c/src/libc/stdlib/qsort.c new file mode 100644 index 000000000..200755aa2 --- /dev/null +++ b/lab7/c/src/libc/stdlib/qsort.c @@ -0,0 +1,154 @@ +#include "oscos/libc/stdlib.h" + +#include "oscos/libc/string.h" + +#define INSERTION_SORT_THRESHOLD 16 + +// Insertion sort. + +/// \brief Insertion sort. +static void +_insertion_sort(void *const base, const size_t nmemb, const size_t size, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + for (size_t i = 1; i < nmemb; i++) { + for (size_t j = i; j > 0; j--) { + void *const pl = (char *)base + (j - 1) * size, *const pr = + (char *)pl + size; + if (compar(pl, pr, arg) < 0) + break; + memswp(pl, pr, size); + } + } +} + +// Quicksort. + +/// \brief The result of partitioning, indicating the pivot points. +typedef struct { + size_t eq_start; ///< The starting index of the part where the element + ///< compares equal to the pivot. + size_t gt_start; ///< The starting index of the part where the element + ///< compares greater than the pivot. +} partition_result_t; + +/// \brief Median-of-3 pivot selection. +/// +/// \return The index of the chosen pivot. +static size_t +_select_pivot(const void *const base, const size_t nmemb, const size_t size, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + if (nmemb == 0) + __builtin_unreachable(); + if (nmemb < 3) + return 0; + + const size_t imid = nmemb / 2, ilast = nmemb - 1; + const void *const mid = (char *)base + imid * size, *const last = + (char *)base + + ilast * size; + return compar(base, mid, arg) <= 0 ? compar(mid, last, arg) <= 0 ? imid + : compar(base, last, arg) <= 0 ? ilast + : 0 + : compar(mid, last, arg) > 0 ? imid + : compar(base, last, arg) <= 0 ? 0 + : ilast; +} + +/// \brief Three-way partitioning, using the first element as the pivot. +static partition_result_t +_partition(void *const base, const size_t nmemb, const size_t size, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + if (nmemb == 0) + __builtin_unreachable(); + + // Partition every element except for the first one (pivot). + + size_t il = 1, im = 1, ir = nmemb; + while (im < ir) { + void *const pl = (char *)base + il * size, + *const pm = (char *)base + im * size, + *const prm1 = (char *)base + (ir - 1) * size; + + const int compar_result = compar(pm, base, arg); + if (compar_result < 0) { + memswp(pl, pm, size); + il++; + im++; + } else if (compar_result > 0) { + memswp(pm, prm1, size); + ir--; + } else { + im++; + } + } + + // Move the pivot to its place. + + if (il != 0) { + memswp(base, (char *)base + --il * size, size); + } + + return (partition_result_t){.eq_start = il, .gt_start = im}; +} + +/// \brief Quicksort. +/// +/// A basic quicksort with median-of-3 pivot selection, three-way partitioning, +/// and insertion sort for small arrays. +static void _quicksort(void *const base, const size_t nmemb, const size_t size, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + if (nmemb <= INSERTION_SORT_THRESHOLD) { + _insertion_sort(base, nmemb, size, compar, arg); + return; + } + + // Select the pivot. + + const size_t pivot_ix = _select_pivot(base, nmemb, size, compar, arg); + + // Partition the array. + + if (pivot_ix != 0) { + memswp(base, (char *)base + pivot_ix * size, size); + } + + const partition_result_t partition_result = + _partition(base, nmemb, size, compar, arg); + + // Recursively sort the subarrays. + + _quicksort(base, partition_result.eq_start, size, compar, arg); + _quicksort((char *)base + partition_result.gt_start * size, + nmemb - partition_result.gt_start, size, compar, arg); +} + +// qsort. + +typedef struct { + int (*compar)(const void *, const void *); +} qsort_qsort_r_arg_t; + +static int _qsort_qsort_r_compar(const void *const x, const void *const y, + qsort_qsort_r_arg_t *const arg) { + return arg->compar(x, y); +} + +void qsort(void *const base, const size_t nmemb, const size_t size, + int (*const compar)(const void *, const void *)) { + qsort_qsort_r_arg_t arg = {.compar = compar}; + qsort_r(base, nmemb, size, + (int (*)(const void *, const void *, void *))_qsort_qsort_r_compar, + &arg); +} + +// qsort_r. + +void qsort_r(void *const base, const size_t nmemb, const size_t size, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + _quicksort(base, nmemb, size, compar, arg); +} diff --git a/lab7/c/src/libc/string.c b/lab7/c/src/libc/string.c new file mode 100644 index 000000000..b1f406f82 --- /dev/null +++ b/lab7/c/src/libc/string.c @@ -0,0 +1,99 @@ +#include "oscos/libc/string.h" + +__attribute__((used)) int memcmp(const void *const s1, const void *const s2, + const size_t n) { + const unsigned char *const s1_c = s1, *const s2_c = s2; + + for (size_t i = 0; i < n; i++) { + const int diff = (int)s1_c[i] - s2_c[i]; + if (diff != 0) + return diff; + } + + return 0; +} + +__attribute__((used)) void *memset(void *const s, const int c, const size_t n) { + unsigned char *const s_c = s; + + for (size_t i = 0; i < n; i++) { + s_c[i] = c; + } + + return s; +} + +static void __memmove_forward(unsigned char *const dest, + const unsigned char *const src, const size_t n) { + for (size_t i = 0; i < n; i++) { + dest[i] = src[i]; + } +} + +static void __memmove_backward(unsigned char *const dest, + const unsigned char *const src, const size_t n) { + for (size_t ip1 = n; ip1 > 0; ip1--) { + const size_t i = ip1 - 1; + dest[i] = src[i]; + } +} + +__attribute__((used)) void *memcpy(void *const restrict dest, + const void *const restrict src, + const size_t n) { + __memmove_forward(dest, src, n); + return dest; +} + +__attribute__((used)) void *memmove(void *const dest, const void *const src, + const size_t n) { + if (dest < src) { + __memmove_forward(dest, src, n); + } else if (dest > src) { + __memmove_backward(dest, src, n); + } + + return dest; +} + +int strcmp(const char *const s1, const char *const s2) { + for (const unsigned char *c1 = (const unsigned char *)s1, + *c2 = (const unsigned char *)s2; + *c1 || *c2; c1++, c2++) { + const int diff = (int)*c1 - *c2; + if (diff != 0) + return diff; + } + + return 0; +} + +int strncmp(const char *const s1, const char *const s2, const size_t n) { + size_t i = 0; + const unsigned char *c1 = (const unsigned char *)s1, + *c2 = (const unsigned char *)s2; + for (; i < n && (*c1 || *c2); i++, c1++, c2++) { + const int diff = (int)*c1 - *c2; + if (diff != 0) + return diff; + } + + return 0; +} + +size_t strlen(const char *s) { + size_t result = 0; + for (const char *c = s; *c; c++) { + result++; + } + return result; +} + +void memswp(void *const restrict xs, void *const restrict ys, const size_t n) { + unsigned char *const restrict xs_c = xs, *const restrict ys_c = ys; + for (size_t i = 0; i < n; i++) { + const unsigned char tmp = xs_c[i]; + xs_c[i] = ys_c[i]; + ys_c[i] = tmp; + } +} diff --git a/lab7/c/src/linker.ld b/lab7/c/src/linker.ld new file mode 100644 index 000000000..0ab7218ff --- /dev/null +++ b/lab7/c/src/linker.ld @@ -0,0 +1,78 @@ +/* +Memory map: (End addresses are exclusive) +0x 0 ( 0B) - 0x 1000 ( 4K): Reserved by firmware +0x 1000 ( 4K) - 0x 80000 (512K): Kernel heap +0x 80000 (512K) - 0x3ac00000 (940M): Kernel text, rodata, data, bss, heap +0x3ac00000 (940M) - 0x3b400000 (948M): Kernel stack +0x3b400000 (948M) - 0x3c000000 (960M): (QEMU only) Kernel heap +*/ + +_kernel_vm_base = 0xffff000000000000; +_skernel = _kernel_vm_base + 0x80000; +_max_ekernel = _kernel_vm_base + 0x3ac00000; + +MEMORY +{ + RAM_KERNEL : ORIGIN = _skernel, LENGTH = _max_ekernel - _skernel +} + +/* Must be 16-byte aligned. + The highest address the ARM core can use. Total system SDRAM is 1G. Top 76M + (or 64M if on QEMU) are reserved for VideoCore. */ +_estack = _kernel_vm_base + (1024M - 76M); +_sstack = _estack - 8M; + +ENTRY(_start) + +SECTIONS +{ + .text : + { + _stext = .; + + *(.text._start) /* Entry point. See `start.S`. */ + *(.text.unlikely .text.*_unlikely .text.unlikely.*) + *(.text.startup .text.startup.*) + *(.text.hot .text.hot.*) + *(.text .text.*) + *(.eh_frame) + *(.eh_frame_hdr) + + _etext = .; + } >RAM_KERNEL + + .rodata : + { + _srodata = .; + + *(.rodata .rodata.*) + + _erodata = .; + } >RAM_KERNEL + + .data : + { + _sdata = .; + + *(.data .data.*) + + _edata = .; + } >RAM_KERNEL + + /* The .bss section is 16-byte aligned to allow the section to be + zero-initialized with the `stp` instruction without using unaligned + memory accesses. See `start.S`. */ + .bss : ALIGN(16) + { + _sbss = .; + + *(.bss .bss.*) + *(COMMON) + + . = ALIGN(16); + _ebss = .; + } >RAM_KERNEL + + _ekernel = .; + _sheap = ALIGN(16); +} diff --git a/lab7/c/src/main.c b/lab7/c/src/main.c new file mode 100644 index 000000000..7aa13fbf5 --- /dev/null +++ b/lab7/c/src/main.c @@ -0,0 +1,71 @@ +#include "oscos/console.h" +#include "oscos/devicetree.h" +#include "oscos/drivers/aux.h" +#include "oscos/drivers/gpio.h" +#include "oscos/drivers/l1ic.h" +#include "oscos/drivers/l2ic.h" +#include "oscos/drivers/mailbox.h" +#include "oscos/drivers/pm.h" +#include "oscos/initrd.h" +#include "oscos/mem/malloc.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/mem/startup-alloc.h" +#include "oscos/mem/vm/kernel-page-tables.h" +#include "oscos/panic.h" +#include "oscos/sched.h" +#include "oscos/shell.h" +#include "oscos/timer/delay.h" +#include "oscos/timer/timeout.h" +#include "oscos/xcpt.h" + +static void _run_shell(void *const _arg) { + (void)_arg; + run_shell(); +} + +void main(const void *const dtb_start) { + // Initialize interrupt-related subsystems. + l1ic_init(); + l2ic_init(); + xcpt_set_vector_table(); + timeout_init(); + XCPT_UNMASK_ALL(); + + // Initialize the serial console. + gpio_init(); + aux_init(); + startup_alloc_init(); + console_init(); + + // Initialize the devicetree. + if (!devicetree_init(dtb_start)) { + console_puts("WARN: Devicetree blob is invalid."); + } + + // Initialize the initial ramdisk. + if (!initrd_init()) { + console_puts("WARN: Initial ramdisk is invalid."); + } + + // Initialize the memory allocators. + page_alloc_init(); + malloc_init(); + vm_setup_finer_granularity_linear_mapping(); + + // Initialize miscellaneous subsystems. + mailbox_init(); + pm_init(); + + // Initialize the scheduler. + + if (!sched_init()) { + PANIC("Cannot initialize scheduler: out of memory"); + } + + // Test the scheduler. + + thread_create(_run_shell, NULL); + + sched_setup_periodic_scheduling(); + idle(); +} diff --git a/lab7/c/src/mem/malloc.c b/lab7/c/src/mem/malloc.c new file mode 100644 index 000000000..5819828d0 --- /dev/null +++ b/lab7/c/src/mem/malloc.c @@ -0,0 +1,353 @@ +// The design of the dynamic memory allocator resembles that of the slab +// allocator to some extent. However, compared to the slab allocator +// implementation in the Linux kernel [linux-slab-alloc], it only achieves the +// first of the three principle aims, namely, to help eliminate internal +// fragmentation. +// +// Each slab is backed by a single page. The first 32 bytes of the page are +// reserved for bookkeeping data, while the remaining area is split into +// equally-sized chunks that are units of allocation. There are different kinds +// of slabs for many different slot sizes. This allocator maintains free lists +// of slabs, one for each kind, chaining slabs of the same kind and with at +// least one available slot together. +// +// When the last reserved slot of a slab becomes available, the slab is +// immediately destroyed and the underlying page is returned to the page frame +// allocator. This design causes thrashing on certain allocation/deallocation +// patterns, but it keeps the code simple. +// +// Large allocation requests bypass the slab allocator and goes directly to the +// page frame allocator. The allocated memory is appropriately tagged so that +// void free(void *ptr) knows which memory allocator a memory is allocated with. +// An allocation request is considered large if the size is greater than 126 +// `max_align_t`, the maximum slot size that allows a slab to hold at least two +// slots. Indeed, if the request size is so large that a slab able to satisfy +// the request can only hold a single slot, then using the slab allocator offers +// no advantage at all. If the request size is even larger, then a slab with a +// large enough slot size will not be able to hold even a single slot. +// +// [linux-slab-alloc]: +// https://www.kernel.org/doc/gorman/html/understand/understand011.html + +#include "oscos/mem/malloc.h" + +#include +#include + +#include "oscos/libc/string.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/mem/vm.h" +#include "oscos/utils/critical-section.h" +#include "oscos/utils/math.h" + +/// \brief A node of a doubly-linked list. +typedef struct list_node_t { + struct list_node_t *prev, *next; +} list_node_t; + +/// \brief Slab metadata. +typedef struct { + /// \brief The number of slots. + uint8_t n_slots; + /// \brief The size of each slot in numbers of `max_align_t`. + uint8_t slot_size; +} slab_metadata_t; + +/// \brief Slab (or not). +typedef struct { + /// \brief Node of the free list. + /// + /// If `free_list_node.prev` is NULL, then this "slab" is in fact not a slab + /// but a memory allocated for a large allocation request. + /// + /// This field is put in the first position, so that obtaining a slab_t * from + /// a pointer to its `free_list_node` field is a no-op. + list_node_t free_list_node; + /// \brief Pointer to the head of the free list of the slabs of the same kind. + /// + /// This is used when the slab adds itself to the free list. + list_node_t *free_list_head; + /// \brief Metadata. + slab_metadata_t metadata; + /// \brief The number of reserved slots. + uint8_t n_slots_reserved; + /// \brief Bitset of reserved slots. + /// + /// The jth bit of `slots_reserved_bitset[i]` if set iff the i*64 + j slot is + /// reserved. + uint64_t slots_reserved_bitset[4]; + /// \brief The memory for the slots. + alignas(alignof(max_align_t)) unsigned char slots[]; +} slab_t; + +/// \brief Metadata of all supported types of slabs. +static const slab_metadata_t SLAB_METADATA[] = { + {.n_slots = 252, .slot_size = 1}, {.n_slots = 126, .slot_size = 2}, + {.n_slots = 84, .slot_size = 3}, {.n_slots = 63, .slot_size = 4}, + {.n_slots = 50, .slot_size = 5}, {.n_slots = 42, .slot_size = 6}, + {.n_slots = 36, .slot_size = 7}, {.n_slots = 31, .slot_size = 8}, + {.n_slots = 28, .slot_size = 9}, {.n_slots = 25, .slot_size = 10}, + {.n_slots = 22, .slot_size = 11}, {.n_slots = 21, .slot_size = 12}, + {.n_slots = 19, .slot_size = 13}, {.n_slots = 18, .slot_size = 14}, + {.n_slots = 16, .slot_size = 15}, {.n_slots = 15, .slot_size = 16}, + {.n_slots = 14, .slot_size = 18}, {.n_slots = 13, .slot_size = 19}, + {.n_slots = 12, .slot_size = 21}, {.n_slots = 11, .slot_size = 22}, + {.n_slots = 10, .slot_size = 25}, {.n_slots = 9, .slot_size = 28}, + {.n_slots = 8, .slot_size = 31}, {.n_slots = 7, .slot_size = 36}, + {.n_slots = 6, .slot_size = 42}, {.n_slots = 5, .slot_size = 50}, + {.n_slots = 4, .slot_size = 63}, {.n_slots = 3, .slot_size = 84}, + {.n_slots = 2, .slot_size = 126}}; + +/// \brief The number of slab types. +#define N_SLAB_TYPES (sizeof(SLAB_METADATA) / sizeof(slab_metadata_t)) + +/// \brief The threshold in numbers of `max_align_t` an allocation request whose +/// size is more than which is considered a large allocation request. +#define LARGE_ALLOC_THRESHOLD 126 + +/// \brief Free lists of slabs for each slab type. +static list_node_t _slab_free_lists[N_SLAB_TYPES]; + +/// \brief Gets the slab type ID (the index that can be used to index +/// `SLAB_METADATA` or the free list) from the size of the allocation +/// request. +/// +/// \param n_units The size of the allocation request in numbers of "allocation +/// units", i.e., `max_align_t`. +static size_t _get_slab_type_id(const size_t n_units) { + if (n_units == 0 || n_units > LARGE_ALLOC_THRESHOLD) + __builtin_unreachable(); + + return n_units <= 16 ? n_units - 1 : N_SLAB_TYPES + 1 - 252 / n_units; +} + +// Slab operations. + +/// \brief Adds a slab to its free list. +/// +/// The `free_list_head` field of \p slab must be initialized and \p slab must +/// not have been on any free list. +/// +/// This function is safe to call only within a critical section. +static void _add_slab_to_free_list(slab_t *const slab) { + list_node_t *const free_list_first_entry = slab->free_list_head->next; + slab->free_list_node.next = free_list_first_entry; + free_list_first_entry->prev = &slab->free_list_node; + slab->free_list_node.prev = slab->free_list_head; + slab->free_list_head->next = &slab->free_list_node; +} + +/// \brief Removes a slab from its free list. +/// +/// \p slab must have been on a free list. +/// +/// This function is safe to call only within a critical section. +static void _remove_slab_from_free_list(slab_t *const slab) { + slab->free_list_node.prev->next = slab->free_list_node.next; + slab->free_list_node.next->prev = slab->free_list_node.prev; +} + +/// \brief Allocates a new slab and adds it onto its free list. +/// +/// This function is safe to call only within a critical section. +static slab_t *_alloc_slab(const size_t slab_type_id) { + // Allocate space for the slab. + + const spage_id_t page = alloc_pages_unlocked(0); + if (page < 0) + return NULL; + + slab_t *slab = (slab_t *)pa_to_kernel_va(page_id_to_pa(page)); + if (!slab) { + // We cannot accept a null slab pointer in this implementation due to the + // `free_list_node.prev` being used as a tag to identify memories for large + // allocation requests. Allocate a new page and return the previously + // allocated page to the page frame allocator. + // (In practice, this code path is never taken.) + + const spage_id_t another_page = alloc_pages_unlocked(0); + free_pages(page); + if (another_page < 0) { + return NULL; + } + + slab = (slab_t *)pa_to_kernel_va(page_id_to_pa(another_page)); + } + + // Initialize the fields. + + slab->free_list_head = &_slab_free_lists[slab_type_id]; + slab->metadata = SLAB_METADATA[slab_type_id]; + slab->n_slots_reserved = 0; + memset(slab->slots_reserved_bitset, 0, sizeof(slab->slots_reserved_bitset)); + _add_slab_to_free_list(slab); + + return slab; +} + +/// \brief Gets a slab of the given type with at least one free slot. If there +/// is none, allocates a new one. +static slab_t *_get_or_alloc_slab(const size_t slab_type_id) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + slab_t *result; + if (_slab_free_lists[slab_type_id].next == + &_slab_free_lists[slab_type_id]) { // The free list is empty. + result = _alloc_slab(slab_type_id); + } else { + list_node_t *const free_list_first_entry = + _slab_free_lists[slab_type_id].next; + result = (slab_t *)((char *)free_list_first_entry - + offsetof(slab_t, free_list_node)); + } + + CRITICAL_SECTION_LEAVE(daif_val); + return result; +} + +/// \brief Gets the index of the first free slot of the given slab. +/// +/// \p slab must have at least one free slot. +static size_t _get_first_free_slot_ix(const slab_t *const slab) { + for (size_t i = 0;; i++) { + uint64_t reversed_negated_bitset; + __asm__("rbit %0, %1" + : "=r"(reversed_negated_bitset) + : "r"(~slab->slots_reserved_bitset[i])); + + uint64_t j; + __asm__("clz %0, %1" : "=r"(j) : "r"(reversed_negated_bitset)); + + if (j != 64) { // The jth bit of `slab->slots_reserved_bitset[i]` is clear. + return i * 64 + j; + } + } +} + +/// \brief Allocates a slot from the given slab. +/// +/// \p slab must have at least one free slot. +static void *_alloc_from_slab(slab_t *const slab) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const size_t free_slot_ix = _get_first_free_slot_ix(slab); + + // Mark the `free_slot_ix`th slot as reserved. + + slab->n_slots_reserved++; + slab->slots_reserved_bitset[free_slot_ix / 64] |= (uint64_t)1 + << (free_slot_ix % 64); + + // Remove itself from its free list if there are no free slots. + + if (slab->n_slots_reserved == slab->metadata.n_slots) { + _remove_slab_from_free_list(slab); + } + + CRITICAL_SECTION_LEAVE(daif_val); + return slab->slots + free_slot_ix * (slab->metadata.slot_size * 16); +} + +/// \brief Frees a slot to the given slab. +/// +/// \param slab The slab. +/// \param ptr The pointer to the slot. +static void _free_to_slab(slab_t *const slab, void *const ptr) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const size_t slot_ix = ((uintptr_t)ptr - (uintptr_t)slab->slots) / + (slab->metadata.slot_size * 16); + + // Adds the slab to its free list if it wasn't on its free list. + + if (slab->n_slots_reserved == slab->metadata.n_slots) { + _add_slab_to_free_list(slab); + } + + // Mark the slot as available. + + slab->n_slots_reserved--; + slab->slots_reserved_bitset[slot_ix / 64] &= ~(1 << (slot_ix % 64)); + + // Return the slab to the page frame allocator if it has no reserved slots. + + if (slab->n_slots_reserved == 0) { + _remove_slab_from_free_list(slab); + free_pages(pa_to_page_id(kernel_va_to_pa(slab))); + } + + CRITICAL_SECTION_LEAVE(daif_val); +} + +// Large allocation. + +/// \brief Allocates memory using the large allocation mechanism. +/// +/// In principle, using this function to satisfy small allocation requests will +/// not cause problems (UB or kernel panic), but doing so wastes a lot of +/// memory. +/// +/// \param size The size of the allocation in bytes. +static void *_malloc_large(const size_t size) { + const size_t actual_size = alignof(max_align_t) + size; + const size_t n_pages = (actual_size + ((1 << PAGE_ORDER) - 1)) >> PAGE_ORDER; + const size_t block_order = clog2(n_pages); + + spage_id_t page = alloc_pages(block_order); + if (page < 0) + return NULL; + + char *page_va = pa_to_kernel_va(page_id_to_pa(page)); + // Mark the "slab" as not a slab. + ((slab_t *)page_va)->free_list_node.prev = NULL; + + return page_va + alignof(max_align_t); +} + +/// \brief Frees memory allocated using void *_malloc_large(size_t). +/// \param slab_ptr The pointer to the "slab". (N.B. not the pointer returned by +/// void *_malloc_large(size_t)!) +static void _free_large(slab_t *const slab_ptr) { + free_pages(pa_to_page_id(kernel_va_to_pa(slab_ptr))); +} + +// Public functions. + +void malloc_init(void) { + // Initialize the free lists. (All free lists are initially empty.) + + for (size_t i = 0; i < N_SLAB_TYPES; i++) { + _slab_free_lists[i].prev = _slab_free_lists[i].next = &_slab_free_lists[i]; + } +} + +void *malloc(const size_t size) { + if (size == 0) + return NULL; + + const size_t n_units = (size + (alignof(max_align_t) - 1)) >> 4; + if (n_units > LARGE_ALLOC_THRESHOLD) + return _malloc_large(size); + + const size_t slab_type_id = _get_slab_type_id(n_units); + slab_t *const slab = _get_or_alloc_slab(slab_type_id); + if (!slab) + return NULL; + + return _alloc_from_slab(slab); +} + +void free(void *const ptr) { + if (!ptr) + return; + + slab_t *const ptr_s = (slab_t *)((uintptr_t)ptr & ~((1 << PAGE_ORDER) - 1)); + if (!ptr_s->free_list_node.prev) { // Not a slab. + _free_large(ptr_s); + } else { + _free_to_slab(ptr_s, ptr); + } +} diff --git a/lab7/c/src/mem/page-alloc.c b/lab7/c/src/mem/page-alloc.c new file mode 100644 index 000000000..cd510492e --- /dev/null +++ b/lab7/c/src/mem/page-alloc.c @@ -0,0 +1,662 @@ +// The page frame allocator uses the buddy system. Much of the design is based +// on that described in The Art of Computer Programming by Donald Knuth, +// section 2.5. +// +// The page frame allocator maintains the page frame array, an array of size +// equal to the number of page frames and entry type `page_frame_array_entry_t`, +// which tracks each block's reservation status and order. Unlike the design +// described in the specification [spec], not all entries have valid data. If +// page i starts a block of order k, regardless of the reservation status of the +// block, the entries in the index range [i+1:i+2^k] are not read and are thus +// left uninitialized. Unlike TAOCP's design, the order of a block is stored in +// the page frame array even if the block is reserved. (Note: TAOCP's +// reservation algorithm skips storing the order of the allocated block.) This +// design decision is because, unlike TAOCP's liberation algorithm, the +// void free_pages(page_id_t) function does not have access to the order of the +// block from the arguments and must instead retrieve this information from the +// page frame array. +// +// The page frame allocator also maintains free lists of blocks, one for each +// order. Each free list is a doubly-linked list with page frame array entries +// themselves as nodes. Thus, in addition to the reservation status and the +// order of the corresponding block, each page frame array entry also contains +// the index into the page frame array of the previous and the next node on the +// free list. This implementation adopts the technique described in TAOCP +// section 2.2.5 of using the list node type itself to store the head and tail +// pointers of the list(from now on referred to as "free list header"), +// simplifying list manipulation code. TAOCP's allocator design also uses this +// technique. Since the free list headers are necessarily outside of the page +// frame array, this technique requires that the indices mentioned above be able +// to refer to page frame array entries (list nodes) outside the page frame +// array. To solve this problem without adding complexity to the code, the free +// list headers and the page frame array are allocated together, with the former +// placed right before the latter, and we use negative array indices to refer to +// entries in the free list headers. +// +// [spec]: https://oscapstone.github.io/labs/lab4.html + +#include "oscos/mem/page-alloc.h" + +#include "oscos/console.h" +#include "oscos/devicetree.h" +#include "oscos/initrd.h" +#include "oscos/mem/startup-alloc.h" +#include "oscos/mem/vm.h" +#include "oscos/panic.h" +#include "oscos/utils/critical-section.h" + +// `MAX_BLOCK_ORDER` can be changed to any positive integer up to and +// including 25 without modification to the code. + +// Symbols defined in the linker script. +extern char _skernel[], _ekernel[], _sstack[], _estack[]; + +typedef struct { + bool is_avail : 1; + unsigned order : 5; + signed linkf : 26; + signed linkb : 32; +} page_frame_array_entry_t; + +static pa_t _pa_start; +static page_frame_array_entry_t *_page_frame_array, *_free_list; + +// Utilities used by page_alloc_init. + +// _mark_region + +/// \brief Marks a region as either reserved or available. +/// +/// \param region_limit The region limit. Only the part of \p region that lies +/// within this limit will be marked. +/// \param region The region to mark. +/// \param is_avail The target reservation status. +static void _mark_region(const pa_range_t region_limit, const pa_range_t region, + const bool is_avail) { + const pa_t effective_start = region_limit.start > region.start + ? region_limit.start + : region.start, + effective_end = + region_limit.end < region.end ? region_limit.end : region.end; + + if (effective_start < effective_end) { + mark_pages_unlocked(pa_range_to_page_id_range((pa_range_t){ + .start = effective_start, .end = effective_end}), + is_avail); + } +} + +// _get_usable_pa_range + +typedef struct { + fdt_n_address_size_cells_t root_n_cells; + pa_range_t range; +} get_usable_pa_range_fdt_traverse_arg_t; + +static control_flow_t _get_usable_pa_range_fdt_traverse_callback( + get_usable_pa_range_fdt_traverse_arg_t *const arg, + const fdt_item_t *const node, + const fdt_traverse_parent_list_node_t *const parent) { + if (!parent) { // Current node is the root node. + arg->root_n_cells = fdt_get_n_address_size_cells(node); + } else if (parent && !parent->parent && + strncmp(FDT_NODE_NAME(node), "memory@", 7) == + 0) { // Current node is /memory@... + FDT_FOR_ITEM(node, item) { + if (FDT_TOKEN(item) == FDT_PROP) { + const fdt_prop_t *const prop = (const fdt_prop_t *)item->payload; + if (strcmp(FDT_PROP_NAME(prop), "reg") == 0) { + const fdt_read_reg_result_t read_result = + fdt_read_reg(prop, arg->root_n_cells); + const pa_t start = read_result.value.address, + end = start + read_result.value.size; + if (read_result.address_overflow || read_result.size_overflow || + read_result.value.address > PA_MAX || + read_result.value.size > PA_MAX || end < start) + PANIC( + "page-alloc: reg property value overflow in devicetree node %s", + FDT_NODE_NAME(node)); + + if (start < arg->range.start) { + arg->range.start = start; + } + if (end > arg->range.end) { + arg->range.end = end; + } + } + } + } + } + + return CF_CONTINUE; +} + +/// \brief Gets the physical address range containing all usable memory regions. +static pa_range_t _get_usable_pa_range(void) { + if (devicetree_is_init()) { + get_usable_pa_range_fdt_traverse_arg_t arg = { + .range = {.start = PA_MAX, .end = 0}}; + fdt_traverse( + (fdt_traverse_callback_t *)_get_usable_pa_range_fdt_traverse_callback, + &arg); + return arg.range; + } else { + return (pa_range_t){.start = 0x0, .end = 0x3b400000}; + } +} + +// _mark_usable_regions + +typedef struct { + pa_range_t usable_pa_range; + fdt_n_address_size_cells_t root_n_cells; +} mark_usable_regions_fdt_traverse_arg_t; + +static control_flow_t _mark_usable_regions_fdt_traverse_callback( + mark_usable_regions_fdt_traverse_arg_t *const arg, + const fdt_item_t *const node, + const fdt_traverse_parent_list_node_t *const parent) { + if (!parent) { // Current node is the root node. + arg->root_n_cells = fdt_get_n_address_size_cells(node); + } else if (parent && !parent->parent && + strncmp(FDT_NODE_NAME(node), "memory@", 7) == + 0) { // Current node is /memory@... + FDT_FOR_ITEM(node, item) { + if (FDT_TOKEN(item) == FDT_PROP) { + const fdt_prop_t *const prop = (const fdt_prop_t *)item->payload; + if (strcmp(FDT_PROP_NAME(prop), "reg") == 0) { + const fdt_read_reg_result_t read_result = + fdt_read_reg(prop, arg->root_n_cells); + const pa_t start = read_result.value.address, + end = start + read_result.value.size; + if (read_result.address_overflow || read_result.size_overflow || + read_result.value.address > PA_MAX || + read_result.value.size > PA_MAX || end < start) + PANIC( + "page-alloc: reg property value overflow in devicetree node %s", + FDT_NODE_NAME(node)); + + _mark_region(arg->usable_pa_range, + (pa_range_t){.start = start, .end = end}, true); + } + } + } + } + + return CF_CONTINUE; +} + +/// \brief Marks the usable memory regions as available. +/// +/// \param usable_pa_range The physical address range containing all usable +/// memory regions. Can be obtained by +/// pa_range_t _get_usable_pa_range(void). +static void _mark_usable_regions(const pa_range_t usable_pa_range) { + if (devicetree_is_init()) { + mark_usable_regions_fdt_traverse_arg_t arg = {.usable_pa_range = + usable_pa_range}; + fdt_traverse( + (fdt_traverse_callback_t *)_mark_usable_regions_fdt_traverse_callback, + &arg); + } else { + _mark_region(usable_pa_range, (pa_range_t){.start = 0x0, .end = 0x3b400000}, + true); + } +} + +// _mark_reserved_regions + +typedef struct { + pa_range_t usable_pa_range; + const fdt_item_t *reserved_memory_node; + fdt_n_address_size_cells_t reserved_memory_n_cells; +} mark_reserved_regions_fdt_traverse_arg_t; + +static control_flow_t _mark_reserved_regions_fdt_traverse_callback( + mark_reserved_regions_fdt_traverse_arg_t *const arg, + const fdt_item_t *const node, + const fdt_traverse_parent_list_node_t *const parent) { + if (parent && !parent->parent && + strcmp(FDT_NODE_NAME(node), "reserved-memory") == + 0) { // Current node is /reserved-memory. + arg->reserved_memory_node = node; + arg->reserved_memory_n_cells = fdt_get_n_address_size_cells(node); + } else if (arg->reserved_memory_node) { // The /reserved-memory node has been + // traversed. + if (parent->node == + arg->reserved_memory_node) { // Current node is /reserved-memory/... + FDT_FOR_ITEM(node, item) { + if (FDT_TOKEN(item) == FDT_PROP) { + const fdt_prop_t *const prop = (const fdt_prop_t *)item->payload; + if (strcmp(FDT_PROP_NAME(prop), "reg") == 0) { + const fdt_read_reg_result_t read_result = + fdt_read_reg(prop, arg->reserved_memory_n_cells); + const pa_t start = read_result.value.address, + end = start + read_result.value.size; + if (read_result.address_overflow || read_result.size_overflow || + read_result.value.address > PA_MAX || + read_result.value.size > PA_MAX || end < start) + PANIC("page-alloc: reg property value overflow in devicetree " + "node %s", + FDT_NODE_NAME(node)); + + _mark_region(arg->usable_pa_range, + (pa_range_t){.start = start, .end = end}, false); + + break; + } + } + } + } else { // All children of the /reserved-memory node has been traversed. + return CF_BREAK; + } + } + + return CF_CONTINUE; +} + +/// \brief Marks the reserved memory regions as reserved. +/// +/// \param usable_pa_range The physical address range containing all usable +/// memory regions. Can be obtained by +/// pa_range_t _get_usable_pa_range(void). +static void _mark_reserved_regions(const pa_range_t usable_pa_range) { + if (devicetree_is_init()) { + // Devicetree. + + _mark_region(usable_pa_range, + (pa_range_t){.start = (pa_t)(uintptr_t)fdt_get_start(), + .end = (pa_t)(uintptr_t)fdt_get_end()}, + false); + + // Anything in the memory reservation block. + + for (const fdt_reserve_entry_t *reserve_entry = FDT_START_MEM_RSVMAP; + !(reserve_entry->address == 0 && reserve_entry->size == 0); + reserve_entry++) { + const pa_t start = rev_u64(reserve_entry->address), + end = start + rev_u64(reserve_entry->size); + _mark_region(usable_pa_range, (pa_range_t){.start = start, .end = end}, + false); + } + + // Spin tables for multicore boot, etc. + + mark_reserved_regions_fdt_traverse_arg_t arg = {.usable_pa_range = + usable_pa_range}; + fdt_traverse( + (fdt_traverse_callback_t *)_mark_reserved_regions_fdt_traverse_callback, + &arg); + } else { + // Spin tables for multicore boot. + + _mark_region(usable_pa_range, (pa_range_t){.start = 0x0, .end = 0x1000}, + false); + } + + // Kernel image in the physical memory. + + _mark_region(usable_pa_range, + (pa_range_t){.start = (pa_t)(uintptr_t)_skernel, + .end = (pa_t)(uintptr_t)_ekernel}, + false); + + // Initial ramdisk. + + _mark_region(usable_pa_range, + (pa_range_t){.start = (pa_t)(uintptr_t)initrd_get_start(), + .end = (pa_t)(uintptr_t)initrd_get_end()}, + false); + + // Kernel stack. + + _mark_region(usable_pa_range, + (pa_range_t){.start = (pa_t)(uintptr_t)_sstack, + .end = (pa_t)(uintptr_t)_estack}, + false); +} + +void page_alloc_init(void) { + // Determine the starting and ending physical address. + + pa_range_t usable_pa_range = _get_usable_pa_range(); + _pa_start = usable_pa_range.start; + + const pa_t max_supported_pa = + _pa_start + (1 << (PAGE_ORDER + MAX_BLOCK_ORDER)); + if (usable_pa_range.end > max_supported_pa) { + console_printf("WARN: page-alloc: End of usable memory region 0x%" PRIxPA + " is greater than maximum supported PA 0x%" PRIxPA ".\n", + usable_pa_range.end, max_supported_pa); + usable_pa_range.end = max_supported_pa; + } + + // Allocate the page frame array and the free list. + + page_frame_array_entry_t *const entries = + startup_alloc(((MAX_BLOCK_ORDER + 1) + (1 << MAX_BLOCK_ORDER)) * + sizeof(page_frame_array_entry_t)); + _page_frame_array = entries + (MAX_BLOCK_ORDER + 1); + _free_list = entries; + + // Initialize the page frame array. (The entire memory region is initially + // reserved.) + + _page_frame_array[0].is_avail = false; + _page_frame_array[0].order = MAX_BLOCK_ORDER; + + // Initialize the free list. (The free list is initially empty.) + + for (size_t order = 0; order <= MAX_BLOCK_ORDER; order++) { + _free_list[order].linkf = _free_list[order].linkb = + (int)order - (MAX_BLOCK_ORDER + 1); + } + + // Mark the usable regions as usable. + + _mark_usable_regions(usable_pa_range); + + // Mark the reserved regions as reserved. + + _mark_reserved_regions(usable_pa_range); + + // Mark the region used by the startup allocator as reserved. + + _mark_region(usable_pa_range, + kernel_va_range_to_pa_range(startup_alloc_get_alloc_range()), + false); +} + +/// \brief Adds a block to the free list. +/// +/// \param page The page number of the first page of the block. +static void _add_block_to_free_list(const page_id_t page) { + const size_t order = _page_frame_array[page].order; + + const int32_t free_list_first_entry = _free_list[order].linkf; + _page_frame_array[page].linkf = free_list_first_entry; + _page_frame_array[free_list_first_entry].linkb = page; + _page_frame_array[page].linkb = (int)order - (MAX_BLOCK_ORDER + 1); + _free_list[order].linkf = page; +} + +/// \brief Removes a block from the free list. +/// +/// \param page The page number of the first page of the block. +static void _remove_block_from_free_list(const page_id_t page) { + _page_frame_array[_page_frame_array[page].linkb].linkf = + _page_frame_array[page].linkf; + _page_frame_array[_page_frame_array[page].linkf].linkb = + _page_frame_array[page].linkb; +} + +/// \brief Removes all free blocks within a page range that is valid as the page +/// range of a block from the free list. +/// +/// \param range The page range. Must be valid as the page range of a block. +/// \param order The order of the block of which \p range is valid as the page +/// range. I.e., log base 2 of the span of the range. +static void +_remove_free_blocks_in_range_from_free_list(const page_id_range_t range, + const size_t order) { + if (_page_frame_array[range.start].order == order) { + if (_page_frame_array[range.start].is_avail) { + _remove_block_from_free_list(range.start); + } + } else { + if (order == 0) + __builtin_unreachable(); + + // This will not cause integer overflow, since the maximum order is at most + // 25 and the node ID is at most 2²⁵. + const page_id_t mid = (range.start + range.end) / 2; + + _remove_free_blocks_in_range_from_free_list( + (page_id_range_t){.start = range.start, .end = mid}, order - 1); + _remove_free_blocks_in_range_from_free_list( + (page_id_range_t){.start = mid, .end = range.end}, order - 1); + } +} + +/// \brief Splits a block. +/// +/// If the block is initially free, this function updates the free lists +/// accordingly. +/// +/// \param page The page number of the first page of the block. +static void _split_block(const page_id_t page) { + if (_page_frame_array[page].order == 0) + __builtin_unreachable(); + +#ifdef PAGE_ALLOC_ENABLE_LOG + const page_id_t end_page = page + (1 << _page_frame_array[page].order); + console_printf("DEBUG: page-alloc: Splitting block 0x%" PRIxPAGEID + " - 0x%" PRIxPAGEID " of order %u.\n", + page, end_page, _page_frame_array[page].order); +#endif + + if (_page_frame_array[page].is_avail) { + _remove_block_from_free_list(page); + } + + const size_t order_p1 = _page_frame_array[page].order - 1; + const page_id_t buddy_page = page + (1 << order_p1); + + _page_frame_array[page].order = order_p1; + _page_frame_array[buddy_page].is_avail = _page_frame_array[page].is_avail; + _page_frame_array[buddy_page].order = order_p1; + + if (_page_frame_array[page].is_avail) { + _add_block_to_free_list(page); + _add_block_to_free_list(buddy_page); + } +} + +spage_id_t alloc_pages(const size_t order) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const spage_id_t result = alloc_pages_unlocked(order); + + CRITICAL_SECTION_LEAVE(daif_val); + return result; +} + +spage_id_t alloc_pages_unlocked(const size_t order) { +#ifdef PAGE_ALLOC_ENABLE_LOG + console_printf("DEBUG: page-alloc: Allocating a block of order %zu\n", order); +#endif + + // Find block. + + size_t avail_block_order; + for (avail_block_order = order; avail_block_order <= MAX_BLOCK_ORDER; + avail_block_order++) { + if (_free_list[avail_block_order].linkf >= + 0) { // The free list is nonempty. + break; + } + } + + if (avail_block_order > + MAX_BLOCK_ORDER) { // No block of order >= `order` found. + return -1; + } + + const page_id_t page = _free_list[avail_block_order].linkf; + + // Remove from list. + + _remove_block_from_free_list(page); + + // Split. + + while (avail_block_order > order) { + // We could have used void _split_block(page_id_t) to perform block + // splitting, but the custom logic here avoids unnecessary free list + // operations (adding a block to the free list and then immediately removing + // it in the next loop iteration) that would have been performed by the said + // function. + +#ifdef PAGE_ALLOC_ENABLE_LOG + const page_id_t end_page = page + (1 << avail_block_order); + console_printf("DEBUG: page-alloc: Splitting block 0x%" PRIxPAGEID + " - 0x%" PRIxPAGEID " of order %zu.\n", + page, end_page, avail_block_order); +#endif + + avail_block_order--; + + const page_id_t buddy_page = page + (1 << avail_block_order); + + _page_frame_array[buddy_page].is_avail = true; + _page_frame_array[buddy_page].order = avail_block_order; + + // Add `buddy_page` to the free list, which is empty. + // We could have used void _add_block_to_free_list(page_id_t), but the + // custom logic here saves a few instructions. + _page_frame_array[buddy_page].linkf = _page_frame_array[buddy_page].linkb = + (int)avail_block_order - (MAX_BLOCK_ORDER + 1); + _free_list[avail_block_order].linkf = _free_list[avail_block_order].linkb = + buddy_page; + } + + _page_frame_array[page].is_avail = false; + _page_frame_array[page].order = order; + return page; +} + +void free_pages(const page_id_t page) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + free_pages_unlocked(page); + + CRITICAL_SECTION_LEAVE(daif_val); +} + +void free_pages_unlocked(const page_id_t page) { + const size_t order = _page_frame_array[page].order; + +#ifdef PAGE_ALLOC_ENABLE_LOG + const page_id_t end_page = page + (1 << order); + console_printf("DEBUG: page-alloc: Freeing block 0x%" PRIxPAGEID + " - 0x%" PRIxPAGEID " of order %zu\n", + page, end_page, order); +#endif + + // Combine with buddy. + + page_id_t curr_page = page; + size_t curr_order; + for (curr_order = order; curr_order < MAX_BLOCK_ORDER; curr_order++) { + const page_id_t buddy_page = curr_page ^ (1 << curr_order); + if (!(_page_frame_array[buddy_page].is_avail && + _page_frame_array[buddy_page].order == curr_order)) + break; + +#ifdef PAGE_ALLOC_ENABLE_LOG + const page_id_t end_curr_page = curr_page + (1 << curr_order); + const page_id_t end_buddy_page = buddy_page + (1 << curr_order); + console_printf( + "DEBUG: page-alloc: Combining block 0x%" PRIxPAGEID " - 0x%" PRIxPAGEID + " of order %zu with its buddy 0x%" PRIxPAGEID " - 0x%" PRIxPAGEID ".\n", + curr_page, end_curr_page, curr_order, buddy_page, end_buddy_page); +#endif + + _remove_block_from_free_list(buddy_page); + if (buddy_page < curr_page) { + curr_page = buddy_page; + } + } + + _page_frame_array[curr_page].is_avail = true; + _page_frame_array[curr_page].order = curr_order; + _add_block_to_free_list(curr_page); +} + +static void _mark_pages_rec(const page_id_range_t range, const bool is_avail, + const size_t order, + const page_id_range_t block_range) { + if (_page_frame_array[block_range.start].order == order && + _page_frame_array[block_range.start].is_avail == + is_avail) { // The entire block is already marked as the desired + // reservation status. + // No-op. + } else if (range.start == block_range.start && range.end == block_range.end) { + // Mark the entire block. + + _remove_free_blocks_in_range_from_free_list(range, order); + + _page_frame_array[range.start].is_avail = is_avail; + _page_frame_array[range.start].order = order; + if (is_avail) { + _add_block_to_free_list(range.start); + } + } else { // Recursive case. + if (order == 0) + __builtin_unreachable(); + + // Split the block if needed. + if (_page_frame_array[block_range.start].order == order) { + _split_block(block_range.start); + } + + // This will not cause integer overflow, since the maximum order is at most + // 25 and the node ID is at most 2²⁵. + const size_t mid = (block_range.start + block_range.end) / 2; + + if (range.end <= mid) { // The range lies entirely within the left half of + // the node range. + _mark_pages_rec( + range, is_avail, order - 1, + (page_id_range_t){.start = block_range.start, .end = mid}); + } else if (mid <= range.start) { // The range lies entirely within the right + // half of the node range. + _mark_pages_rec(range, is_avail, order - 1, + (page_id_range_t){.start = mid, .end = block_range.end}); + } else { // The range crosses the middle of the node range. + _mark_pages_rec( + (page_id_range_t){.start = range.start, .end = mid}, is_avail, + order - 1, (page_id_range_t){.start = block_range.start, .end = mid}); + _mark_pages_rec((page_id_range_t){.start = mid, .end = range.end}, + is_avail, order - 1, + (page_id_range_t){.start = mid, .end = block_range.end}); + } + } +} + +void mark_pages(const page_id_range_t range, const bool is_avail) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + mark_pages_unlocked(range, is_avail); + + CRITICAL_SECTION_LEAVE(daif_val); +} + +void mark_pages_unlocked(const page_id_range_t range, const bool is_avail) { +#ifdef PAGE_ALLOC_ENABLE_LOG + console_printf("DEBUG: page-alloc: Marking pages 0x%" PRIxPAGEID + " - 0x%" PRIxPAGEID " as %s.\n", + range.start, range.end, is_avail ? "available" : "reserved"); +#endif + + // TODO: Switch to a non-recursive implementation. + _mark_pages_rec(range, is_avail, MAX_BLOCK_ORDER, + (page_id_range_t){.start = 0, .end = 1 << MAX_BLOCK_ORDER}); +} + +pa_t page_id_to_pa(const page_id_t page) { + return _pa_start + (page << PAGE_ORDER); +} + +page_id_t pa_to_page_id(const pa_t pa) { + return (pa - _pa_start) >> PAGE_ORDER; +} + +page_id_range_t pa_range_to_page_id_range(const pa_range_t range) { + return (page_id_range_t){ + .start = (range.start - _pa_start) >> PAGE_ORDER, + .end = ((range.end - _pa_start) + ((1 << PAGE_ORDER) - 1)) >> PAGE_ORDER}; +} diff --git a/lab7/c/src/mem/shared-page.c b/lab7/c/src/mem/shared-page.c new file mode 100644 index 000000000..db4a86e3f --- /dev/null +++ b/lab7/c/src/mem/shared-page.c @@ -0,0 +1,147 @@ +#include "oscos/mem/shared-page.h" + +#include "oscos/libc/string.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/mem/vm.h" +#include "oscos/utils/critical-section.h" +#include "oscos/utils/rb.h" + +static rb_node_t *_page_refcnts = NULL; + +typedef struct { + page_id_t page_id; + size_t refcnt; +} page_refcnt_entry_t; + +static int +_cmp_page_refcnt_entry_by_page_id(const page_refcnt_entry_t *const e1, + const page_refcnt_entry_t *const e2, + void *_arg) { + (void)_arg; + + if (e1->page_id < e2->page_id) + return -1; + if (e1->page_id > e2->page_id) + return 1; + return 0; +} + +static int +_cmp_page_id_and_page_refcnt_entry(const page_id_t *const page, + const page_refcnt_entry_t *const entry, + void *const _arg) { + (void)_arg; + + if (*page < entry->page_id) + return -1; + if (*page > entry->page_id) + return 1; + return 0; +} + +spage_id_t shared_page_alloc(void) { + spage_id_t result = alloc_pages(0); + if (result < 0) + return result; + + const page_refcnt_entry_t new_entry = {.page_id = result, .refcnt = 1}; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + rb_insert(&_page_refcnts, sizeof(page_refcnt_entry_t), &new_entry, + (int (*)(const void *, const void *, + void *))_cmp_page_refcnt_entry_by_page_id, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + return result; +} + +size_t shared_page_getref(const page_id_t page) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const page_refcnt_entry_t *const entry = + rb_search(_page_refcnts, &page, + (int (*)(const void *, const void *, + void *))_cmp_page_id_and_page_refcnt_entry, + NULL); + const size_t result = entry->refcnt; + + CRITICAL_SECTION_LEAVE(daif_val); + + return result; +} + +void shared_page_incref(const page_id_t page) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + // It's safe to cast away const here, since every entry _page_refcnts points + // to is not const. Also, incrementing the reference count doesn't invalidate + // the BST invariant. + page_refcnt_entry_t *const entry = (page_refcnt_entry_t *)rb_search( + _page_refcnts, &page, + (int (*)(const void *, const void *, + void *))_cmp_page_id_and_page_refcnt_entry, + NULL); + + // This function is sometimes called on a non-shared page; more specifically, + // linearly-mapped pages. + if (entry) { + entry->refcnt++; + } + + CRITICAL_SECTION_LEAVE(daif_val); +} + +void shared_page_decref(const page_id_t page) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + // It's safe to cast away const here, since every entry _page_refcnts points + // to is not const. Also, incrementing the reference count doesn't invalidate + // the BST invariant. + page_refcnt_entry_t *const entry = (page_refcnt_entry_t *)rb_search( + _page_refcnts, &page, + (int (*)(const void *, const void *, + void *))_cmp_page_id_and_page_refcnt_entry, + NULL); + entry->refcnt--; + + if (entry->refcnt == 0) { + rb_delete(&_page_refcnts, &page, + (int (*)(const void *, const void *, + void *))_cmp_page_id_and_page_refcnt_entry, + NULL); + free_pages(page); + } + + CRITICAL_SECTION_LEAVE(daif_val); +} + +spage_id_t shared_page_clone_unshare(const page_id_t page) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + if (shared_page_getref(page) == 1) { // No need to clone. + CRITICAL_SECTION_LEAVE(daif_val); + return page; + } + + shared_page_decref(page); + + spage_id_t new_page_id = shared_page_alloc(); + if (new_page_id < 0) { + CRITICAL_SECTION_LEAVE(daif_val); + return new_page_id; + } + + memcpy(pa_to_kernel_va(page_id_to_pa(new_page_id)), + pa_to_kernel_va(page_id_to_pa(page)), 1 << PAGE_ORDER); + + CRITICAL_SECTION_LEAVE(daif_val); + return new_page_id; +} diff --git a/lab7/c/src/mem/startup-alloc.c b/lab7/c/src/mem/startup-alloc.c new file mode 100644 index 000000000..16ff447a6 --- /dev/null +++ b/lab7/c/src/mem/startup-alloc.c @@ -0,0 +1,24 @@ +#include "oscos/mem/startup-alloc.h" + +#include + +#include "oscos/utils/align.h" + +// Symbol defined in the linker script. +extern char _sheap[]; + +static char *_next = _sheap; + +void startup_alloc_init(void) { + // No-op. +} + +void *startup_alloc(const size_t size) { + char *const result = (char *)ALIGN((uintptr_t)_next, alignof(max_align_t)); + _next = result + size; + return result; +} + +va_range_t startup_alloc_get_alloc_range(void) { + return (va_range_t){.start = _sheap, .end = _next}; +} diff --git a/lab7/c/src/mem/vm.c b/lab7/c/src/mem/vm.c new file mode 100644 index 000000000..2f70a5a0a --- /dev/null +++ b/lab7/c/src/mem/vm.c @@ -0,0 +1,437 @@ +#include "oscos/mem/vm.h" + +#include + +#include "oscos/libc/string.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/mem/shared-page.h" +#include "oscos/sched.h" +#include "oscos/utils/critical-section.h" + +// Symbol defined in the linker script. +extern char _kernel_vm_base[]; + +pa_t kernel_va_to_pa(const void *const va) { + return (pa_t)((uintptr_t)va - (uintptr_t)_kernel_vm_base); +} + +void *pa_to_kernel_va(const pa_t pa) { + return (void *)((uintptr_t)pa + (uintptr_t)_kernel_vm_base); +} + +pa_range_t kernel_va_range_to_pa_range(const va_range_t range) { + return (pa_range_t){.start = kernel_va_to_pa(range.start), + .end = kernel_va_to_pa(range.end)}; +} + +page_table_entry_t *vm_new_pgd(void) { + const spage_id_t new_pgd_page_id = shared_page_alloc(); + if (new_pgd_page_id < 0) + return NULL; + + page_table_entry_t *const result = + pa_to_kernel_va(page_id_to_pa(new_pgd_page_id)); + + memset(result, 0, 1 << PAGE_ORDER); + + return result; +} + +void vm_clone_pgd(page_table_entry_t *const pgd) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const page_id_t pgd_page_id = pa_to_page_id(kernel_va_to_pa(pgd)); + shared_page_incref(pgd_page_id); + + for (size_t i = 0; i < 512; i++) { + if (pgd[i].b0) { + union { + table_descriptor_upper_t s; + unsigned u; + } upper = {.u = pgd[i].upper}; + upper.s.aptable = 0x2; + pgd[i].upper = upper.u; + } + } + + CRITICAL_SECTION_LEAVE(daif_val); +} + +static void _vm_drop_page_table(page_table_entry_t *const page_table, + const size_t level) { + const page_id_t page_table_page_id = + pa_to_page_id(kernel_va_to_pa(page_table)); + + if (shared_page_getref(page_table_page_id) == 1) { // About to be freed. + for (size_t i = 0; i < 512; i++) { + if (page_table[i].b0) { + const pa_t next_level_pa = page_table[i].addr << PAGE_ORDER; + if (level == 1) { + shared_page_decref(pa_to_page_id(next_level_pa)); + } else { + page_table_entry_t *const next_level_page_table = + pa_to_kernel_va(next_level_pa); + _vm_drop_page_table(next_level_page_table, level - 1); + } + } + } + } + + shared_page_decref(page_table_page_id); +} + +void vm_drop_pgd(page_table_entry_t *const pgd) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + _vm_drop_page_table(pgd, 3); + + CRITICAL_SECTION_LEAVE(daif_val); +} + +static page_table_entry_t * +_vm_clone_unshare_page_table(page_table_entry_t *const page_table) { + const page_id_t page_table_page_id = + pa_to_page_id(kernel_va_to_pa(page_table)); + + if (shared_page_getref(page_table_page_id) == 1) // No need to clone. + return page_table; + + shared_page_decref(page_table_page_id); + + const spage_id_t new_page_table_page_id = shared_page_alloc(); + if (new_page_table_page_id < 0) + return NULL; + page_table_entry_t *const new_page_table = + pa_to_kernel_va(page_id_to_pa(new_page_table_page_id)); + + for (size_t i = 0; i < 512; i++) { + if (page_table[i].b0) { + const pa_t next_level_pa = page_table[i].addr << PAGE_ORDER; + const page_id_t next_level_page_id = pa_to_page_id(next_level_pa); + + shared_page_incref(next_level_page_id); + + union { + table_descriptor_upper_t s; + unsigned u; + } upper = {.u = page_table[i].upper}; + upper.s.aptable = 0x2; + page_table[i].upper = upper.u; + } + } + + memcpy(new_page_table, page_table, 1 << PAGE_ORDER); + + return new_page_table; +} + +static page_table_entry_t * +_vm_clone_unshare_pte(page_table_entry_t *const pte) { + const page_id_t pte_page_id = pa_to_page_id(kernel_va_to_pa(pte)); + + if (shared_page_getref(pte_page_id) == 1) // No need to clone. + return pte; + + shared_page_decref(pte_page_id); + + const spage_id_t new_pte_page_id = shared_page_alloc(); + if (new_pte_page_id < 0) + return NULL; + page_table_entry_t *const new_pte = + pa_to_kernel_va(page_id_to_pa(new_pte_page_id)); + + for (size_t i = 0; i < 512; i++) { + if (pte[i].b0) { + const pa_t next_level_pa = pte[i].addr << PAGE_ORDER; + const page_id_t next_level_page_id = pa_to_page_id(next_level_pa); + + shared_page_incref(next_level_page_id); + + union { + block_page_descriptor_lower_t s; + unsigned u; + } lower = {.u = pte[i].lower}; + lower.s.ap = 0x3; + pte[i].lower = lower.u; + } + } + + memcpy(new_pte, pte, 1 << PAGE_ORDER); + + return new_pte; +} + +static void _init_backed_page(const mem_region_t *const mem_region, + void *const va, void *const kernel_va) { + void *const va_page_start = + (void *)((uintptr_t)va & ~((1 << PAGE_ORDER) - 1)); + size_t offset = (char *)va_page_start - (char *)mem_region->start; + + const size_t copy_len = + offset > mem_region->backing_storage_len ? 0 + : mem_region->backing_storage_len - offset > 1 << PAGE_ORDER + ? 1 << PAGE_ORDER + : mem_region->backing_storage_len - offset; + memcpy(kernel_va, (char *)mem_region->backing_storage_start + offset, + copy_len); + memset((char *)kernel_va + copy_len, 0, (1 << PAGE_ORDER) - copy_len); +} + +static bool _map_page(const mem_region_t *const mem_region, void *const va, + page_table_entry_t *const pte_entry) { + switch (mem_region->type) { + case MEM_REGION_BACKED: { + const spage_id_t page_id = shared_page_alloc(); + if (page_id < 0) { + return false; + } + + const pa_t page_pa = page_id_to_pa(page_id); + pte_entry->addr = page_pa >> PAGE_ORDER; + + _init_backed_page(mem_region, va, pa_to_kernel_va(page_pa)); + + break; + } + + case MEM_REGION_LINEAR: { + const size_t offset = (uintptr_t)va - (uintptr_t)mem_region->start; + pte_entry->addr = + ((uintptr_t)mem_region->backing_storage_start + offset) >> PAGE_ORDER; + break; + } + + default: + __builtin_unreachable(); + } + + pte_entry->b0 = 1; + pte_entry->b1 = 1; + const union { + block_page_descriptor_lower_t s; + unsigned u; + } lower = {.s = (block_page_descriptor_lower_t){ + .attr_indx = 0x1, .ap = 0x1, .af = 1}}; + pte_entry->lower = lower.u; + + return true; +} + +static page_table_entry_t * +_vm_clone_unshare_pte_entry(vm_addr_space_t *const addr_space, void *const va) { + page_table_entry_t *page_table = + _vm_clone_unshare_page_table(addr_space->pgd); + if (!page_table) + return NULL; + addr_space->pgd = page_table; + page_table_entry_t *prev_level_entry = &page_table[(uintptr_t)va >> 39]; + + for (size_t level = 2; level > 0; level--) { + if (prev_level_entry->b0) { + page_table = _vm_clone_unshare_page_table( + pa_to_kernel_va(prev_level_entry->addr << PAGE_ORDER)); + if (!page_table) + return NULL; + } else { + const spage_id_t page_table_page_id = shared_page_alloc(); + if (page_table_page_id < 0) + return NULL; + page_table = pa_to_kernel_va(page_id_to_pa(page_table_page_id)); + memset(page_table, 0, 1 << PAGE_ORDER); + prev_level_entry->b0 = 1; + prev_level_entry->b1 = 1; + } + + // Since the page table is not shared, we can remove the read-only bit now. + + union { + table_descriptor_upper_t s; + unsigned u; + } upper = {.u = prev_level_entry->upper}; + upper.s.aptable = 0x0; + prev_level_entry->upper = upper.u; + + const pa_t page_table_pa = kernel_va_to_pa(page_table); + prev_level_entry->addr = page_table_pa >> 12; + prev_level_entry = + &page_table[((uintptr_t)va >> (12 + level * 9)) & ((1 << 9) - 1)]; + } + + if (prev_level_entry->b0) { + page_table = _vm_clone_unshare_pte( + pa_to_kernel_va(prev_level_entry->addr << PAGE_ORDER)); + if (!page_table) + return NULL; + } else { + const spage_id_t page_table_page_id = shared_page_alloc(); + if (page_table_page_id < 0) + return NULL; + page_table = pa_to_kernel_va(page_id_to_pa(page_table_page_id)); + memset(page_table, 0, 1 << PAGE_ORDER); + prev_level_entry->b0 = 1; + prev_level_entry->b1 = 1; + } + + // Since the page table is not shared, we can remove the read-only bit now. + + { + union { + table_descriptor_upper_t s; + unsigned u; + } upper = {.u = prev_level_entry->upper}; + upper.s.aptable = 0x0; + prev_level_entry->upper = upper.u; + } + + const pa_t page_table_pa = kernel_va_to_pa(page_table); + prev_level_entry->addr = page_table_pa >> 12; + prev_level_entry = &page_table[((uintptr_t)va >> 12) & ((1 << 9) - 1)]; + + return prev_level_entry; +} + +vm_map_page_result_t vm_map_page(vm_addr_space_t *const addr_space, + void *const va) { + // Check the validity of the VA. + + if (!((addr_space->text_region.start <= va && + va < (void *)((char *)addr_space->text_region.start + + addr_space->text_region.len)) || + (addr_space->stack_region.start <= va && + va < (void *)((char *)addr_space->stack_region.start + + addr_space->stack_region.len)) || + (addr_space->vc_region.start <= va && + va < (void *)((char *)addr_space->vc_region.start + + addr_space->vc_region.len)))) + return VM_MAP_PAGE_SEGV; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + // Walk the page table. + + page_table_entry_t *const pte_entry = + _vm_clone_unshare_pte_entry(addr_space, va); + if (!pte_entry) { + CRITICAL_SECTION_LEAVE(daif_val); + return VM_MAP_PAGE_NOMEM; + } + + // Map the page. + + mem_region_t *const region = + addr_space->text_region.start <= va && + va < (void *)((char *)addr_space->text_region.start + + addr_space->text_region.len) + ? &addr_space->text_region + : addr_space->stack_region.start <= va && + va < (void *)((char *)addr_space->stack_region.start + + addr_space->stack_region.len) + ? &addr_space->stack_region + : addr_space->vc_region.start <= va && + va < (void *)((char *)addr_space->vc_region.start + + addr_space->vc_region.len) + ? &addr_space->vc_region + : (__builtin_unreachable(), NULL); + if (!_map_page(region, va, pte_entry)) { + CRITICAL_SECTION_LEAVE(daif_val); + return VM_MAP_PAGE_NOMEM; + } + + // Set ttbr0_el1 again, as the PGD may have changed. + vm_switch_to_addr_space(addr_space); + + CRITICAL_SECTION_LEAVE(daif_val); + + return VM_MAP_PAGE_SUCCESS; +} + +static bool _cow_page(const mem_region_t *const mem_region, + page_table_entry_t *const pte_entry) { + switch (mem_region->type) { + case MEM_REGION_BACKED: { + const page_id_t src_page_id = pa_to_page_id(pte_entry->addr << PAGE_ORDER); + const spage_id_t page_id = shared_page_clone_unshare(src_page_id); + if (page_id < 0) + return false; + + const pa_t page_pa = page_id_to_pa(page_id); + pte_entry->addr = page_pa >> PAGE_ORDER; + + break; + } + + case MEM_REGION_LINEAR: { + // No-op. + break; + } + + default: + __builtin_unreachable(); + } + + const union { + block_page_descriptor_lower_t s; + unsigned u; + } lower = {.s = (block_page_descriptor_lower_t){.ap = 0x1, .af = 1}}; + pte_entry->lower = lower.u; + + return true; +} + +vm_map_page_result_t vm_cow(vm_addr_space_t *const addr_space, void *const va) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + // Walk the page table. + + page_table_entry_t *const pte_entry = + _vm_clone_unshare_pte_entry(addr_space, va); + if (!pte_entry) { + CRITICAL_SECTION_LEAVE(daif_val); + return VM_MAP_PAGE_NOMEM; + } + + // Map the page. + + mem_region_t *const region = + addr_space->text_region.start <= va && + va < (void *)((char *)addr_space->text_region.start + + addr_space->text_region.len) + ? &addr_space->text_region + : addr_space->stack_region.start <= va && + va < (void *)((char *)addr_space->stack_region.start + + addr_space->stack_region.len) + ? &addr_space->stack_region + : addr_space->vc_region.start <= va && + va < (void *)((char *)addr_space->vc_region.start + + addr_space->vc_region.len) + ? &addr_space->vc_region + : (__builtin_unreachable(), NULL); + if (!_cow_page(region, pte_entry)) { + CRITICAL_SECTION_LEAVE(daif_val); + return VM_MAP_PAGE_NOMEM; + } + + // Set ttbr0_el1 again, as the PGD may have changed. + vm_switch_to_addr_space(addr_space); + + CRITICAL_SECTION_LEAVE(daif_val); + + return VM_MAP_PAGE_SUCCESS; +} + +void vm_switch_to_addr_space(const vm_addr_space_t *const addr_space) { + const pa_t pgd_pa = kernel_va_to_pa(addr_space->pgd); + __asm__ __volatile__( + "dsb ish // Ensure writes have completed.\n" + "msr ttbr0_el1, %0 // Switch page table.\n" + "tlbi vmalle1is // Invalidate all TLB entries.\n" + "dsb ish // Ensure completion of TLB invalidatation.\n" + "isb // Clear pipeline." + : + : "r"((uint64_t)pgd_pa) + : "memory"); +} diff --git a/lab7/c/src/mem/vm/kernel-page-tables.c b/lab7/c/src/mem/vm/kernel-page-tables.c new file mode 100644 index 000000000..5fee7a55b --- /dev/null +++ b/lab7/c/src/mem/vm/kernel-page-tables.c @@ -0,0 +1,114 @@ +#include "oscos/mem/vm/kernel-page-tables.h" + +#include +#include + +#include "oscos/drivers/board.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/mem/types.h" +#include "oscos/mem/vm.h" +#include "oscos/mem/vm/page-table.h" + +alignas(4096) page_table_t kernel_pud = {{.b0 = 1, + .b1 = 0, + .lower = 1 << 8, // AF = 1. + .addr = 0x0, + .upper = 0}, + {.b0 = 1, + .b1 = 0, + .lower = 1 << 8, // AF = 1. + .addr = 0x40000, + .upper = 0}}; + +alignas(4096) page_table_t kernel_pgd = {{ + .b0 = 1, + .b1 = 1, + .lower = 0, + .addr = 0, // The concrete value will be filled by `start.S`. + .upper = 0x1 << (61 - 48) // Unprivileged access not permitted + // (APTable = 0b01). +}}; + +static void _map_region_as_rec(const page_id_range_t range, + const block_page_descriptor_lower_t lower_attr, + const block_page_descriptor_upper_t upper_attr, + const size_t level, const page_id_t block_start, + page_table_entry_t *const entry) { + const union { + block_page_descriptor_lower_t s; + unsigned u; + } lower = {.s = lower_attr}; + const union { + block_page_descriptor_upper_t s; + unsigned u; + } upper = {.s = upper_attr}; + + const page_id_t block_end = block_start + (1 << (9 * level)); + + if (range.start == block_start && range.end == block_end) { + *entry = (page_table_entry_t){.b0 = 1, + .b1 = level == 0, + .lower = lower.u, + .addr = block_start, + .upper = upper.u}; + } else { + if (level == 0) + __builtin_unreachable(); + + const page_id_t subblock_stride = 1 << (9 * (level - 1)); + + page_table_entry_t *page_table; + if (entry->b1) { // Table. + page_table = (page_table_entry_t *)pa_to_kernel_va(entry->addr << 12); + } else { // Block. + const pa_t page_table_pa = page_id_to_pa(alloc_pages(0)); + page_table = pa_to_kernel_va(page_table_pa); + + for (size_t i = 0; i < 512; i++) { + page_table[i] = + (page_table_entry_t){.b0 = 1, + .b1 = level == 1, + .lower = entry->lower, + .addr = entry->addr + i * subblock_stride, + .upper = entry->upper}; + } + *entry = (page_table_entry_t){.b0 = 1, + .b1 = 1, + .lower = entry->lower, + .addr = page_table_pa >> 12, + .upper = entry->upper}; + } + + for (size_t i = 0; i < 512; i++) { + const page_id_t subblock_start = block_start + i * subblock_stride, + subblock_end = subblock_start + subblock_stride; + + const page_id_t max_start = range.start > subblock_start ? range.start + : subblock_start, + min_end = + range.end < subblock_end ? range.end : subblock_end; + + if (max_start < min_end) { + _map_region_as_rec( + (page_id_range_t){.start = max_start, .end = min_end}, lower_attr, + upper_attr, level - 1, subblock_start, &page_table[i]); + } + } + } +} + +static void _map_region_as(const page_id_range_t range, + const block_page_descriptor_lower_t lower_attr, + const block_page_descriptor_upper_t upper_attr) { + _map_region_as_rec(range, lower_attr, upper_attr, 3, 0, kernel_pgd); +} + +void vm_setup_finer_granularity_linear_mapping(void) { + // Map RAM as normal memory. + _map_region_as( + (page_id_range_t){.start = 0x0, + .end = kernel_va_to_pa(PERIPHERAL_BASE) >> 12}, + (block_page_descriptor_lower_t){ + .attr_indx = 1, .ap = 0x0, .sh = 0x0, .af = 1, .ng = 0}, + (block_page_descriptor_upper_t){.contiguous = 0, .pxn = 0, .uxn = 0}); +} diff --git a/lab7/c/src/panic.c b/lab7/c/src/panic.c new file mode 100644 index 000000000..d43c8f2aa --- /dev/null +++ b/lab7/c/src/panic.c @@ -0,0 +1,24 @@ +#include "oscos/panic.h" + +#include "oscos/console.h" + +void panic_begin(const char *const restrict file, const int line, + const char *const restrict format, ...) { + // Prints the panic message. + + va_list ap; + va_start(ap, format); + + console_printf("panic: %s:%d: ", file, line); + console_vprintf(format, ap); + console_putc('\n'); + + va_end(ap); + + console_flush_write_buffer(); + + // Park the core. + for (;;) { + __asm__ __volatile__("wfe"); + } +} diff --git a/lab7/c/src/sched/idle-thread.c b/lab7/c/src/sched/idle-thread.c new file mode 100644 index 000000000..b570c09f9 --- /dev/null +++ b/lab7/c/src/sched/idle-thread.c @@ -0,0 +1,8 @@ +#include "oscos/sched.h" + +void idle(void) { + for (;;) { + kill_zombies(); + schedule(); + } +} diff --git a/lab7/c/src/sched/periodic-sched.c b/lab7/c/src/sched/periodic-sched.c new file mode 100644 index 000000000..c81aeb140 --- /dev/null +++ b/lab7/c/src/sched/periodic-sched.c @@ -0,0 +1,33 @@ +#include "oscos/sched.h" + +#include + +#include "oscos/timer/timeout.h" +#include "oscos/xcpt.h" +#include "oscos/xcpt/task-queue.h" + +static void _periodic_sched(void *const _arg) { + (void)_arg; + + sched_setup_periodic_scheduling(); + + // Save spsr_el1 and elr_el1, since they can be clobbered by other threads. + + uint64_t spsr_val, elr_val; + __asm__ __volatile__("mrs %0, spsr_el1" : "=r"(spsr_val)); + __asm__ __volatile__("mrs %0, elr_el1" : "=r"(elr_val)); + + schedule(); + XCPT_MASK_ALL(); + + __asm__ __volatile__("msr spsr_el1, %0" : : "r"(spsr_val)); + __asm__ __volatile__("msr elr_el1, %0" : : "r"(elr_val)); +} + +void sched_setup_periodic_scheduling(void) { + uint64_t core_timer_freq_hz; + __asm__("mrs %0, cntfrq_el0" : "=r"(core_timer_freq_hz)); + core_timer_freq_hz &= 0xffffffff; + + timeout_add_timer_ticks(_periodic_sched, NULL, core_timer_freq_hz >> 5); +} diff --git a/lab7/c/src/sched/run-signal-handler.S b/lab7/c/src/sched/run-signal-handler.S new file mode 100644 index 000000000..d18f35efe --- /dev/null +++ b/lab7/c/src/sched/run-signal-handler.S @@ -0,0 +1,119 @@ +#include "oscos/utils/save-ctx.S" + +.section ".text" + +run_signal_handler: + // Save integer context. + + stp x19, x20, [sp, -(12 * 8)]! + stp x21, x22, [sp, 2 * 8] + stp x23, x24, [sp, 4 * 8] + stp x25, x26, [sp, 6 * 8] + stp x27, x28, [sp, 8 * 8] + stp x29, lr, [sp, 10 * 8] + + // Save FP/SIMD context. + + mrs x2, fpcr + mrs x3, fpsr + stp x2, x3, [sp, -16]! + + stp q0, q1, [sp, -(32 * 16)]! + stp q2, q3, [sp, 2 * 16] + stp q4, q5, [sp, 4 * 16] + stp q6, q7, [sp, 6 * 16] + stp q8, q9, [sp, 8 * 16] + stp q10, q11, [sp, 10 * 16] + stp q12, q13, [sp, 12 * 16] + stp q14, q15, [sp, 14 * 16] + stp q16, q17, [sp, 16 * 16] + stp q18, q19, [sp, 18 * 16] + stp q20, q21, [sp, 20 * 16] + stp q22, q23, [sp, 22 * 16] + stp q24, q25, [sp, 24 * 16] + stp q26, q27, [sp, 26 * 16] + stp q28, q29, [sp, 28 * 16] + stp q30, q31, [sp, 30 * 16] + + // - Unmask all interrupts. + // - AArch64 execution state. + // - EL0t. + msr spsr_el1, xzr + + adr x2, sig_handler_main + msr elr_el1, x2 + + // Zero the integer registers except for x0, which is used to pass an + // argument to sig_handler_main. + + mov x1, 0 + mov x2, 0 + mov x3, 0 + mov x4, 0 + mov x5, 0 + mov x6, 0 + mov x7, 0 + mov x8, 0 + mov x9, 0 + mov x10, 0 + mov x11, 0 + mov x12, 0 + mov x13, 0 + mov x14, 0 + mov x15, 0 + mov x16, 0 + mov x17, 0 + mov x18, 0 + mov x19, 0 + mov x20, 0 + mov x21, 0 + mov x22, 0 + mov x23, 0 + mov x24, 0 + mov x25, 0 + mov x26, 0 + mov x27, 0 + mov x28, 0 + mov x29, 0 + mov lr, 0 + + // Zero the FP/SIMD registers. + + movi d0, 0 + movi d1, 0 + movi d2, 0 + movi d3, 0 + movi d4, 0 + movi d5, 0 + movi d6, 0 + movi d7, 0 + movi d8, 0 + movi d9, 0 + movi d10, 0 + movi d11, 0 + movi d12, 0 + movi d13, 0 + movi d14, 0 + movi d15, 0 + movi d16, 0 + movi d17, 0 + movi d18, 0 + movi d19, 0 + movi d20, 0 + movi d21, 0 + movi d22, 0 + movi d23, 0 + movi d24, 0 + movi d25, 0 + movi d26, 0 + movi d27, 0 + movi d28, 0 + movi d29, 0 + movi d30, 0 + movi d31, 0 + + eret + +.type run_signal_handler, function +.size run_signal_handler, . - run_signal_handler +.global run_signal_handler diff --git a/lab7/c/src/sched/sched.c b/lab7/c/src/sched/sched.c new file mode 100644 index 000000000..078c80425 --- /dev/null +++ b/lab7/c/src/sched/sched.c @@ -0,0 +1,671 @@ +// This module implements a round-robin scheduler. + +#include "oscos/sched.h" + +#include "oscos/libc/string.h" +#include "oscos/mem/malloc.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/mem/vm.h" +#include "oscos/utils/align.h" +#include "oscos/utils/critical-section.h" +#include "oscos/utils/math.h" +#include "oscos/utils/rb.h" + +#define THREAD_STACK_ORDER 13 // 8KB. +#define THREAD_STACK_BLOCK_ORDER (THREAD_STACK_ORDER - PAGE_ORDER) + +#define USER_STACK_ORDER 23 // 8MB. +#define USER_STACK_BLOCK_ORDER (USER_STACK_ORDER - PAGE_ORDER) + +void _suspend_to_wait_queue(thread_list_node_t *wait_queue); +void _sched_run_thread(thread_t *thread); +void thread_main(void); +noreturn void user_program_main(const void *init_pc, const void *init_user_sp, + const void *init_kernel_sp); +noreturn void fork_child_ret(void); +void run_signal_handler(sighandler_t handler); + +static size_t _sched_next_tid = 1, _sched_next_pid = 1; +static thread_list_node_t _run_queue = {.prev = &_run_queue, + .next = &_run_queue}, + _zombies = {.prev = &_zombies, .next = &_zombies}, + _stopped_threads = {.prev = &_stopped_threads, + .next = &_stopped_threads}; +static rb_node_t *_processes = NULL; + +static int _cmp_processes_by_pid(const process_t *const *const p1, + const process_t *const *const p2, void *_arg) { + (void)_arg; + + const size_t pid1 = (*p1)->id, pid2 = (*p2)->id; + return pid1 < pid2 ? -1 : pid1 > pid2 ? 1 : 0; +} + +static int _cmp_pid_and_processes_by_pid(const size_t *const pid, + const process_t *const *const process, + void *_arg) { + (void)_arg; + + const size_t pid1 = *pid, pid2 = (*process)->id; + return pid1 < pid2 ? -1 : pid1 > pid2 ? 1 : 0; +} + +static size_t _alloc_tid(void) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const size_t result = _sched_next_tid++; + + CRITICAL_SECTION_LEAVE(daif_val); + return result; +} + +static size_t _alloc_pid(void) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const size_t result = _sched_next_pid++; + + CRITICAL_SECTION_LEAVE(daif_val); + return result; +} + +static void _add_thread_to_queue(thread_t *const thread, + thread_list_node_t *const queue) { + thread_list_node_t *const thread_node = &thread->list_node; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + thread_list_node_t *const last_node = queue->prev; + thread_node->prev = last_node; + last_node->next = thread_node; + thread_node->next = queue; + queue->prev = thread_node; + + CRITICAL_SECTION_LEAVE(daif_val); +} + +static void _add_thread_to_run_queue(thread_t *const thread) { + _add_thread_to_queue(thread, &_run_queue); +} + +void _remove_thread_from_queue(thread_t *const thread) { + thread_list_node_t *const thread_node = &thread->list_node; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + thread_node->prev->next = thread_node->next; + thread_node->next->prev = thread_node->prev; + + CRITICAL_SECTION_LEAVE(daif_val); +} + +thread_t *_remove_first_thread_from_queue(thread_list_node_t *const queue) { + thread_t *result; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + if (queue->next == queue) { + result = NULL; + } else { + thread_list_node_t *const first_node = queue->next; + queue->next = first_node->next; + first_node->next->prev = queue; + result = (thread_t *)((char *)first_node - offsetof(thread_t, list_node)); + } + + CRITICAL_SECTION_LEAVE(daif_val); + return result; +} + +bool sched_init(void) { + // Create the idle thread. + + thread_t *const idle_thread = malloc(sizeof(thread_t)); + if (!idle_thread) + return false; + + idle_thread->id = 0; + idle_thread->process = NULL; + idle_thread->ctx.fp_simd_ctx = NULL; + + // Name the current thread the idle thread. + + __asm__ __volatile__("msr tpidr_el1, %0" : : "r"(idle_thread)); + + return true; +} + +bool thread_create(void (*const task)(void *), void *const arg) { + // Allocate memory. + + thread_t *const thread = malloc(sizeof(thread_t)); + if (!thread) + return false; + + const spage_id_t stack_page_id = alloc_pages(THREAD_STACK_BLOCK_ORDER); + if (stack_page_id < 0) { + free(thread); + return false; + } + + // Initialize the thread structure. + + thread->id = _alloc_tid(); + thread->status.is_waiting = false; + thread->status.is_stopped = false; + thread->status.is_waken_up_by_signal = false; + thread->status.is_handling_signal = false; + thread->process = NULL; + thread->ctx.r19 = (uint64_t)(uintptr_t)task; + thread->ctx.r20 = (uint64_t)(uintptr_t)arg; + thread->ctx.pc = (uint64_t)(uintptr_t)thread_main; + thread->ctx.fp_simd_ctx = NULL; + + thread->stack_page_id = stack_page_id; + void *const init_sp = (char *)pa_to_kernel_va(page_id_to_pa(stack_page_id)) + + (1 << (THREAD_STACK_BLOCK_ORDER + PAGE_ORDER)); + thread->ctx.kernel_sp = (uint64_t)(uintptr_t)init_sp; + + // Put the thread into the end of the run queue. + + _add_thread_to_run_queue(thread); + + return true; +} + +thread_t * +_sched_move_thread_to_queue_and_pick_thread(thread_t *const thread, + thread_list_node_t *const queue) { + _add_thread_to_queue(thread, queue); + return _remove_first_thread_from_queue(&_run_queue); +} + +void thread_exit(void) { + thread_t *const curr_thread = current_thread(); + process_t *const curr_process = curr_thread->process; + + XCPT_MASK_ALL(); + + if (curr_process) { + rb_delete(&_processes, &curr_process->id, + (int (*)(const void *, const void *, + void *))_cmp_pid_and_processes_by_pid, + NULL); + } + + _sched_run_thread( + _sched_move_thread_to_queue_and_pick_thread(curr_thread, &_zombies)); + __builtin_unreachable(); +} + +thread_t *current_thread(void) { + thread_t *result; + __asm__ __volatile__("mrs %0, tpidr_el1" : "=r"(result)); + return result; +} + +bool process_create(void) { + // Allocate memory. + + process_t *const process = malloc(sizeof(process_t)); + if (!process) // Out of memory. + return false; + + thread_fp_simd_ctx_t *const fp_simd_ctx = + malloc(sizeof(thread_fp_simd_ctx_t)); + if (!fp_simd_ctx) { + free(process); + return false; + } + + page_table_entry_t *const pgd = vm_new_pgd(); + if (!pgd) { + free(fp_simd_ctx); + free(process); + return false; + } + + // Set thread/process data. + + thread_t *const curr_thread = current_thread(); + + process->id = _alloc_pid(); + process->addr_space.stack_region = + (mem_region_t){.start = (void *)0xffffffffb000ULL, + .len = 4 << PAGE_ORDER, + .type = MEM_REGION_BACKED, + .backing_storage_start = NULL, + .backing_storage_len = 0}; + process->addr_space.vc_region = + (mem_region_t){.start = (void *)0x3b400000ULL, + .len = 0x3f000000ULL - 0x3b400000ULL, + .type = MEM_REGION_LINEAR, + .backing_storage_start = (void *)0x3b400000ULL}; + process->addr_space.pgd = pgd; + process->main_thread = curr_thread; + process->pending_signals = 0; + process->blocked_signals = 0; + for (size_t i = 0; i < 32; i++) { + process->signal_handlers[i] = SIG_DFL; + } + curr_thread->process = process; + curr_thread->ctx.fp_simd_ctx = fp_simd_ctx; + + // Add the process to the process BST. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + rb_insert(&_processes, sizeof(process_t *), &process, + (int (*)(const void *, const void *, void *))_cmp_processes_by_pid, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + return true; +} + +void switch_vm(const thread_t *const thread) { + if (thread->process) { + vm_switch_to_addr_space(&thread->process->addr_space); + } +} + +static void _exec_generic(const void *const text_start, const size_t text_len, + const bool free_old_pages) { + thread_t *const curr_thread = current_thread(); + process_t *const curr_process = curr_thread->process; + + // Allocate memory. + + page_table_entry_t *pgd = NULL; + if (free_old_pages) { + pgd = vm_new_pgd(); + if (!pgd) + return; + } + + // Free old pages. + + if (free_old_pages) { + vm_drop_pgd(curr_process->addr_space.pgd); + } + + // Set process data. + + if (free_old_pages) { + curr_process->addr_space.pgd = pgd; + } + // We don't know exactly how large the .bss section of a user program is, so + // we have to use a heuristic. We speculate that the .bss section is at most + // as large as the remaining parts (.text, .rodata, and .data sections) of the + // user program. + curr_process->addr_space.text_region = + (mem_region_t){.start = (void *)0x0, + .len = ALIGN(text_len * 2, 4096), + .type = MEM_REGION_BACKED, + .backing_storage_start = text_start, + .backing_storage_len = text_len}; + + // Run the user program. + + void *const kernel_stack_end = + (char *)pa_to_kernel_va(page_id_to_pa(curr_thread->stack_page_id)) + + (1 << THREAD_STACK_ORDER); + switch_vm(curr_thread); + user_program_main((void *)0x0, (void *)0xfffffffff000ULL, kernel_stack_end); +} + +void exec_first(const void *const text_start, const size_t text_len) { + _exec_generic(text_start, text_len, false); +} + +void exec(const void *const text_start, const size_t text_len) { + _exec_generic(text_start, text_len, true); +} + +static void _thread_cleanup(thread_t *const thread) { + if (thread->process) { + vm_drop_pgd(thread->process->addr_space.pgd); + free(thread->process); + } + free_pages(thread->stack_page_id); + free(thread); +} + +process_t *fork(const extended_trap_frame_t *const trap_frame) { + thread_t *const curr_thread = current_thread(); + process_t *const curr_process = curr_thread->process; + + // Allocate memory. + + thread_t *const new_thread = malloc(sizeof(thread_t)); + if (!new_thread) + return NULL; + + process_t *const new_process = malloc(sizeof(process_t)); + if (!new_process) { + free(new_thread); + return NULL; + } + + const spage_id_t kernel_stack_page_id = alloc_pages(THREAD_STACK_BLOCK_ORDER); + if (kernel_stack_page_id < 0) { + free(new_process); + free(new_thread); + return NULL; + } + + thread_fp_simd_ctx_t *const fp_simd_ctx = + malloc(sizeof(thread_fp_simd_ctx_t)); + if (!fp_simd_ctx) { + free_pages(kernel_stack_page_id); + free(new_process); + free(new_thread); + return NULL; + } + + // Set data. + + new_thread->id = _alloc_tid(); + new_thread->status.is_waiting = false; + new_thread->status.is_stopped = false; + new_thread->status.is_waken_up_by_signal = false; + new_thread->status.is_handling_signal = false; + new_thread->stack_page_id = kernel_stack_page_id; + new_thread->process = new_process; + + new_process->id = _alloc_pid(); + vm_clone_pgd(curr_process->addr_space.pgd); + vm_switch_to_addr_space(&curr_process->addr_space); + new_process->addr_space = curr_process->addr_space; + new_process->main_thread = new_thread; + new_process->pending_signals = 0; + new_process->blocked_signals = 0; + memcpy(new_process->signal_handlers, curr_process->signal_handlers, + 32 * sizeof(sighandler_t)); + + // Set execution context. + + void *const kernel_stack_end = + (char *)pa_to_kernel_va(page_id_to_pa(kernel_stack_page_id)) + + (1 << THREAD_STACK_ORDER), + *const init_kernel_sp = + (char *)kernel_stack_end - sizeof(extended_trap_frame_t); + + memcpy(&new_thread->ctx, &curr_thread->ctx, sizeof(thread_ctx_t)); + new_thread->ctx.pc = (uint64_t)(uintptr_t)fork_child_ret; + new_thread->ctx.kernel_sp = (uint64_t)(uintptr_t)init_kernel_sp; + new_thread->ctx.fp_simd_ctx = fp_simd_ctx; + memcpy(fp_simd_ctx, curr_thread->ctx.fp_simd_ctx, + sizeof(thread_fp_simd_ctx_t)); + + memcpy(init_kernel_sp, trap_frame, sizeof(extended_trap_frame_t)); + + // Add the new thread to the end of the run queue and the process to the + // process BST. Note that these two steps can be done in either order but must + // not be interrupted in between, since: + // - If the former is done before the latter and the newly-created thread is + // scheduled between the two steps, then the new thread won't be able to + // find its own process. + // - If the latter is done before the former and the newly-created process is + // killed between the two steps, then a freed thread_t instance (i.e., + // `new_thread`) will be added onto the run queue – a use-after-free bug. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + _add_thread_to_run_queue(new_thread); + + rb_insert(&_processes, sizeof(process_t *), &new_process, + (int (*)(const void *, const void *, void *))_cmp_processes_by_pid, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + return new_process; +} + +void kill_zombies(void) { + thread_t *zombie; + while ((zombie = _remove_first_thread_from_queue(&_zombies))) { + _thread_cleanup(zombie); + } +} + +void schedule(void) { _suspend_to_wait_queue(&_run_queue); } + +void suspend_to_wait_queue(thread_list_node_t *const wait_queue) { + XCPT_MASK_ALL(); + current_thread()->status.is_waiting = true; + _suspend_to_wait_queue(wait_queue); +} + +void add_all_threads_to_run_queue(thread_list_node_t *const wait_queue) { + thread_t *thread; + while ((thread = _remove_first_thread_from_queue(wait_queue))) { + _add_thread_to_run_queue(thread); + } +} + +process_t *get_process_by_id(const size_t pid) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + process_t *const *const result = + (process_t **)rb_search(_processes, &pid, + (int (*)(const void *, const void *, + void *))_cmp_pid_and_processes_by_pid, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + return result ? *result : NULL; +} + +void kill_process(process_t *const process) { + if (process == current_thread()->process) { + thread_exit(); + } else { + thread_t *const thread = process->main_thread; + + // Remove the process from the process BST and the thread from the run/wait + // queue. Note that these two steps can be done in either order but must not + // be interrupted in between, since: + // - If the former is done before the latter and the thread is scheduled + // between the two steps, then the new thread won't be able to find its + // own process. + // - If the latter is done before the former and the newly-created process + // is killed once again between the two steps, then a freed thread_t + // instance (i.e., `thread`) will be added onto the zombies list – a + // use-after-free bug. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + rb_delete(&_processes, &process->id, + (int (*)(const void *, const void *, + void *))_cmp_pid_and_processes_by_pid, + NULL); + + _remove_thread_from_queue(thread); + _add_thread_to_queue(thread, &_zombies); + + CRITICAL_SECTION_LEAVE(daif_val); + } +} + +void kill_all_processes(void) { + // Kill all processes other than the current one. + for (;;) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + process_t *process_to_kill = *(process_t **)_processes->payload; + if (process_to_kill == current_thread()->process) { + if (_processes->children[0]) { + process_to_kill = *(process_t **)_processes->children[0]->payload; + } else if (_processes->children[1]) { + process_to_kill = *(process_t **)_processes->children[1]->payload; + } else { // Only the current process left. + CRITICAL_SECTION_LEAVE(daif_val); + break; + } + } + + CRITICAL_SECTION_LEAVE(daif_val); + + kill_process(process_to_kill); + } + + // Kill the current process. + thread_exit(); +} + +sighandler_t set_signal_handler(process_t *const process, const int signal, + const sighandler_t handler) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const sighandler_t old_handler = process->signal_handlers[signal]; + + // The video player expects SIGKILL to be catchable. This function thus + // follows the protocol used by the video player. + if (!(/* signal == SIGKILL || */ signal == SIGSTOP)) { + process->signal_handlers[signal] = handler; + } + + CRITICAL_SECTION_LEAVE(daif_val); + return old_handler; +} + +void deliver_signal(process_t *const process, const int signal) { + thread_t *const thread = process->main_thread; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + process->pending_signals |= 1 << signal; + + if (thread->status.is_waiting && + (!thread->status.is_stopped || signal == SIGCONT)) { + // Wake up the thread and notify it that it was waken up by a signal. + + thread->status.is_waiting = false; + thread->status.is_stopped = false; + thread->status.is_waken_up_by_signal = true; + + _remove_thread_from_queue(thread); + _add_thread_to_run_queue(thread); + } + + CRITICAL_SECTION_LEAVE(daif_val); +} + +static void _deliver_signal_to_all_processes_rec(const int signal, + const rb_node_t *const node) { + if (!node) + return; + + process_t *const process = *(process_t **)node->payload; + deliver_signal(process, signal); + + _deliver_signal_to_all_processes_rec(signal, node->children[0]); + _deliver_signal_to_all_processes_rec(signal, node->children[1]); +} + +void deliver_signal_to_all_processes(const int signal) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + _deliver_signal_to_all_processes_rec(signal, _processes); + + CRITICAL_SECTION_LEAVE(daif_val); +} + +static bool _signal_default_is_term_or_core(const int signal) { + return signal == SIGABRT || signal == SIGALRM || signal == SIGBUS || + signal == SIGFPE || signal == SIGHUP || signal == SIGILL || + signal == SIGINT || signal == SIGIO || signal == SIGIOT || + signal == SIGKILL || signal == SIGPIPE || signal == SIGPROF || + signal == SIGPWR || signal == SIGQUIT || signal == SIGSEGV || + signal == SIGSTKFLT || signal == SIGSYS || signal == SIGTERM || + signal == SIGTRAP || signal == SIGUNUSED || signal == SIGUSR1 || + signal == SIGUSR2 || signal == SIGVTALRM || signal == SIGXCPU || + signal == SIGXFSZ; +} + +static bool _signal_default_is_stop(const int signal) { + return signal == SIGSTOP || signal == SIGTSTP || signal == SIGTTIN || + signal == SIGTTOU; +} + +void handle_signals(void) { + thread_t *const curr_thread = current_thread(); + if (!curr_thread) // The scheduler has not been initialized. + return; + process_t *const curr_process = curr_thread->process; + + // Save spsr_el1 and elr_el1, since they can be clobbered when running the + // signal handlers. + + uint64_t spsr_val, elr_val; + __asm__ __volatile__("mrs %0, spsr_el1" : "=r"(spsr_val)); + __asm__ __volatile__("mrs %0, elr_el1" : "=r"(elr_val)); + + // Save the status bit in order to support nested signal handling. + + const bool is_handling_signal = curr_thread->status.is_handling_signal; + + uint32_t deferred_signals = 0; + for (uint32_t handleable_signals; + (handleable_signals = curr_process->pending_signals & + ~curr_process->blocked_signals & + ~deferred_signals) != 0;) { + uint32_t reversed_handleable_signals; + __asm__("rbit %w0, %w1" + : "=r"(reversed_handleable_signals) + : "r"(handleable_signals)); + int signal; + __asm__("clz %w0, %w1" : "=r"(signal) : "r"(reversed_handleable_signals)); + + const sighandler_t handler = curr_process->signal_handlers[signal]; + if (handler == SIG_DFL) { + curr_process->pending_signals &= ~(1 << signal); + + if (_signal_default_is_term_or_core(signal)) { + thread_exit(); + } else if (_signal_default_is_stop(signal)) { + curr_thread->status.is_stopped = true; + suspend_to_wait_queue(&_stopped_threads); + curr_thread->status.is_waken_up_by_signal = false; + } + } else if (handler == SIG_IGN) { + curr_process->pending_signals &= ~(1 << signal); + } else { + // Run the signal handler. + + curr_thread->status.is_handling_signal = true; + curr_process->blocked_signals |= 1 << signal; + + run_signal_handler(handler); + + curr_thread->status.is_handling_signal = false; + curr_process->pending_signals &= ~(1 << signal); + curr_process->blocked_signals &= ~(1 << signal); + } + } + + // Restore spsr_el1 and elr_el1. + + __asm__ __volatile__("msr spsr_el1, %0" : : "r"(spsr_val)); + __asm__ __volatile__("msr elr_el1, %0" : : "r"(elr_val)); + + // Restore the status bit. + + curr_thread->status.is_handling_signal = is_handling_signal; +} diff --git a/lab7/c/src/sched/schedule.S b/lab7/c/src/sched/schedule.S new file mode 100644 index 000000000..b16ff658a --- /dev/null +++ b/lab7/c/src/sched/schedule.S @@ -0,0 +1,115 @@ +.section ".text" + +_suspend_to_wait_queue: + // Enter critical section. + + msr daifset, 0xf + + // Get current thread instance. + + mrs x2, tpidr_el1 + + // Save integer context. + + stp x19, x20, [x2, 16 + 0 * 16] + stp x21, x22, [x2, 16 + 1 * 16] + stp x23, x24, [x2, 16 + 2 * 16] + stp x25, x26, [x2, 16 + 3 * 16] + stp x27, x28, [x2, 16 + 4 * 16] + stp x29, x30, [x2, 16 + 5 * 16] + mov x1, sp + mrs x3, sp_el0 + stp x1, x3, [x2, 16 + 6 * 16] + + // Save FP/SIMD context, if there is one. + + ldr x1, [x2, 16 + 7 * 16] + cbz x1, .Lafter_save_fp_ctx + + stp q0, q1, [x1, 0 * 32] + stp q2, q3, [x1, 1 * 32] + stp q4, q5, [x1, 2 * 32] + stp q6, q7, [x1, 3 * 32] + stp q8, q9, [x1, 4 * 32] + stp q10, q11, [x1, 5 * 32] + stp q12, q13, [x1, 6 * 32] + stp q14, q15, [x1, 7 * 32] + stp q16, q17, [x1, 8 * 32] + stp q18, q19, [x1, 9 * 32] + stp q20, q21, [x1, 10 * 32] + stp q22, q23, [x1, 11 * 32] + stp q24, q25, [x1, 12 * 32] + stp q26, q27, [x1, 13 * 32] + stp q28, q29, [x1, 14 * 32] + stp q30, q31, [x1, 15 * 32] + add x1, x1, 16 * 32 + mrs x3, fpcr + mrs x4, fpsr + stp x3, x4, [x1] + +.Lafter_save_fp_ctx: + mov x1, x0 + mov x0, x2 + bl _sched_move_thread_to_queue_and_pick_thread + + // Fallthrough. + +.size _suspend_to_wait_queue, . - _suspend_to_wait_queue +.type _suspend_to_wait_queue, function +.global _suspend_to_wait_queue + +_sched_run_thread: + mov x19, x0 + bl switch_vm + mov x0, x19 + + // Restore integer context. + + ldp x19, x20, [x0, 16 + 0 * 16] + ldp x21, x22, [x0, 16 + 1 * 16] + ldp x23, x24, [x0, 16 + 2 * 16] + ldp x25, x26, [x0, 16 + 3 * 16] + ldp x27, x28, [x0, 16 + 4 * 16] + ldp x29, x30, [x0, 16 + 5 * 16] + ldp x1, x2, [x0, 16 + 6 * 16] + mov sp, x1 + msr sp_el0, x2 + + // Restore FP/SIMD context, if there is one. + + ldr x1, [x0, 16 + 7 * 16] + cbz x1, .Lafter_load_fp_ctx + + ldp q0, q1, [x1, 0 * 32] + ldp q2, q3, [x1, 1 * 32] + ldp q4, q5, [x1, 2 * 32] + ldp q6, q7, [x1, 3 * 32] + ldp q8, q9, [x1, 4 * 32] + ldp q10, q11, [x1, 5 * 32] + ldp q12, q13, [x1, 6 * 32] + ldp q14, q15, [x1, 7 * 32] + ldp q16, q17, [x1, 8 * 32] + ldp q18, q19, [x1, 9 * 32] + ldp q20, q21, [x1, 10 * 32] + ldp q22, q23, [x1, 11 * 32] + ldp q24, q25, [x1, 12 * 32] + ldp q26, q27, [x1, 13 * 32] + ldp q28, q29, [x1, 14 * 32] + ldp q30, q31, [x1, 15 * 32] + add x1, x1, 16 * 32 + ldp x2, x3, [x1] + msr fpcr, x2 + msr fpcr, x3 + +.Lafter_load_fp_ctx: + msr tpidr_el1, x0 + + // Unmask interrupts. + + msr daifclr, 0xf + + ret + +.size _sched_run_thread, . - _sched_run_thread +.type _sched_run_thread, function +.global _sched_run_thread diff --git a/lab7/c/src/sched/sig-handler-main.S b/lab7/c/src/sched/sig-handler-main.S new file mode 100644 index 000000000..9c3f530de --- /dev/null +++ b/lab7/c/src/sched/sig-handler-main.S @@ -0,0 +1,10 @@ +.section ".text" + +sig_handler_main: + blr x0 + mov x8, 10 // SYS_sigreturn + svc 0 + +.type sig_handler_main, function +.size sig_handler_main, . - sig_handler_main +.global sig_handler_main diff --git a/lab7/c/src/sched/thread-main.S b/lab7/c/src/sched/thread-main.S new file mode 100644 index 000000000..7548f66ee --- /dev/null +++ b/lab7/c/src/sched/thread-main.S @@ -0,0 +1,10 @@ +.section ".text" + +thread_main: + mov x0, x20 + blr x19 + b thread_exit + +.size thread_main, . - thread_main +.type thread_main, function +.global thread_main diff --git a/lab7/c/src/sched/user-program-main.S b/lab7/c/src/sched/user-program-main.S new file mode 100644 index 000000000..bfc44b8fb --- /dev/null +++ b/lab7/c/src/sched/user-program-main.S @@ -0,0 +1,93 @@ +.section ".text" + +user_program_main: + // We don't need to synchronize the data cache and the instruction cache + // since we haven't enabled any of them. Nonetheless, we still have to + // synchronize the fetched instruction stream using the `isb` instruction. + isb + + // Mask the interrupt to prevent spsr_el1 and elr_el1 from being clobbered + // by ISRs. + msr daifset, 0xf + + msr elr_el1, x0 + msr sp_el0, x1 + mov sp, x2 + + // - Unask all interrupts. + // - AArch64 execution state. + // - EL0t. + msr spsr_el1, xzr + + // Zero the integer registers. + mov x0, 0 + mov x1, 0 + mov x2, 0 + mov x3, 0 + mov x4, 0 + mov x5, 0 + mov x6, 0 + mov x7, 0 + mov x8, 0 + mov x9, 0 + mov x10, 0 + mov x11, 0 + mov x12, 0 + mov x13, 0 + mov x14, 0 + mov x15, 0 + mov x16, 0 + mov x17, 0 + mov x18, 0 + mov x19, 0 + mov x20, 0 + mov x21, 0 + mov x22, 0 + mov x23, 0 + mov x24, 0 + mov x25, 0 + mov x26, 0 + mov x27, 0 + mov x28, 0 + mov x29, 0 + mov lr, 0 + + // Zero the FP/SIMD registers. + movi d0, 0 + movi d1, 0 + movi d2, 0 + movi d3, 0 + movi d4, 0 + movi d5, 0 + movi d6, 0 + movi d7, 0 + movi d8, 0 + movi d9, 0 + movi d10, 0 + movi d11, 0 + movi d12, 0 + movi d13, 0 + movi d14, 0 + movi d15, 0 + movi d16, 0 + movi d17, 0 + movi d18, 0 + movi d19, 0 + movi d20, 0 + movi d21, 0 + movi d22, 0 + movi d23, 0 + movi d24, 0 + movi d25, 0 + movi d26, 0 + movi d27, 0 + movi d28, 0 + movi d29, 0 + movi d30, 0 + movi d31, 0 + + eret + +.type user_program_main, function +.size user_program_main, . - user_program_main +.global user_program_main diff --git a/lab7/c/src/shell.c b/lab7/c/src/shell.c new file mode 100644 index 000000000..448c7df93 --- /dev/null +++ b/lab7/c/src/shell.c @@ -0,0 +1,335 @@ +#include "oscos/shell.h" + +#include + +#include "oscos/console.h" +#include "oscos/drivers/mailbox.h" +#include "oscos/drivers/pm.h" +#include "oscos/initrd.h" +#include "oscos/libc/ctype.h" +#include "oscos/libc/inttypes.h" +#include "oscos/libc/string.h" +#include "oscos/mem/malloc.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/sched.h" +#include "oscos/timer/timeout.h" +#include "oscos/utils/time.h" + +#define MAX_CMD_LEN 78 + +static void _shell_print_prompt(void) { console_fputs("# "); } + +static size_t _shell_read_line(char *const buf, const size_t n) { + size_t cmd_len = 0; + + for (;;) { + const char c = console_getc(); + + if (c == '\n') { + console_putc('\n'); + break; + } else if (c == '\x7f') { // Backspace. + if (cmd_len > 0) { + console_fputs("\b \b"); + cmd_len--; + } + } else { + console_putc(c); + if (n > 0 && cmd_len < n - 1) { + buf[cmd_len] = c; + } + cmd_len++; + } + } + + if (n > 0) { + buf[cmd_len > n - 1 ? n - 1 : cmd_len] = '\0'; + } + + return cmd_len; +} + +static void _shell_do_cmd_help(void) { + console_puts( + "help : print this help menu\n" + "hello : print Hello World!\n" + "hwinfo : get the hardware's information by mailbox\n" + "reboot : reboot the device\n" + "ls : list all files in the initial ramdisk\n" + "cat : print the content of a file in the initial ramdisk\n" + "exec : run a user program in the initial ramdisk\n" + "setTimeout : print a message after a timeout\n" + "alloc-pages : allocates a block of page frames using the page frame " + "allocator\n" + "free-pages : frees a block of page frames allocated using the page " + "frame allocator"); +} + +static void _shell_do_cmd_hello(void) { console_puts("Hello World!"); } + +static void _shell_do_cmd_hwinfo(void) { + const uint32_t board_revision = mailbox_get_board_revision(); + const arm_memory_t arm_memory = mailbox_get_arm_memory(); + + console_printf("Board revision: 0x%" PRIx32 "\nARM memory: Base: 0x%" PRIx32 + ", Size: 0x%" PRIx32 "\n", + board_revision, arm_memory.base, arm_memory.size); +} + +noreturn static void _shell_do_cmd_reboot(void) { pm_reboot(); } + +static void _shell_do_cmd_ls(void) { + if (!initrd_is_init()) { + console_puts("oscsh: ls: initrd is invalid"); + return; + } + + INITRD_FOR_ENTRY(entry) { console_puts(CPIO_NEWC_PATHNAME(entry)); } +} + +static void _shell_do_cmd_cat(void) { + if (!initrd_is_init()) { + console_puts("oscsh: cat: initrd is invalid"); + return; + } + + console_fputs("Filename: "); + + char filename_buf[MAX_CMD_LEN + 1]; + _shell_read_line(filename_buf, MAX_CMD_LEN + 1); + + const cpio_newc_entry_t *const entry = + initrd_find_entry_by_pathname(filename_buf); + if (!entry) { + console_puts("oscsh: cat: no such file or directory"); + return; + } + + const uint32_t mode = CPIO_NEWC_HEADER_VALUE(entry, mode); + const uint32_t file_type = mode & CPIO_NEWC_MODE_FILE_TYPE_MASK; + if (file_type == CPIO_NEWC_MODE_FILE_TYPE_REG) { + console_write(CPIO_NEWC_FILE_DATA(entry), CPIO_NEWC_FILESIZE(entry)); + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_DIR) { + console_puts("oscsh: cat: is a directory"); + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_LNK) { + console_fputs("Symbolic link to: "); + console_write(CPIO_NEWC_FILE_DATA(entry), CPIO_NEWC_FILESIZE(entry)); + console_putc('\n'); + } else { + console_puts("oscsh: cat: unknown file type"); + } +} + +static void _shell_do_cmd_exec(void) { + if (!initrd_is_init()) { + console_puts("oscsh: exec: initrd is invalid"); + return; + } + + console_fputs("Filename: "); + + char filename_buf[MAX_CMD_LEN + 1]; + _shell_read_line(filename_buf, MAX_CMD_LEN + 1); + + const cpio_newc_entry_t *const entry = + initrd_find_entry_by_pathname(filename_buf); + if (!entry) { + console_puts("oscsh: exec: no such file or directory"); + return; + } + + const void *user_program_start; + size_t user_program_len; + + const uint32_t mode = CPIO_NEWC_HEADER_VALUE(entry, mode); + const uint32_t file_type = mode & CPIO_NEWC_MODE_FILE_TYPE_MASK; + if (file_type == CPIO_NEWC_MODE_FILE_TYPE_REG) { + user_program_start = CPIO_NEWC_FILE_DATA(entry); + user_program_len = CPIO_NEWC_FILESIZE(entry); + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_DIR) { + console_puts("oscsh: exec: is a directory"); + return; + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_LNK) { + console_fputs("oscos: exec: is a symbolic link to: "); + console_write(CPIO_NEWC_FILE_DATA(entry), CPIO_NEWC_FILESIZE(entry)); + console_putc('\n'); + return; + } else { + console_puts("oscsh: exec: unknown file type"); + return; + } + + if (!process_create()) { + console_puts("oscsh: exec: out of memory"); + return; + } + + exec_first(user_program_start, user_program_len); + + // If execution reaches here, then exec failed. + console_puts("oscsh: exec: out of memory"); +} + +static void _shell_cmd_set_timeout_timer_callback(char *const message) { + console_puts(message); + free(message); +} + +static void _shell_do_cmd_set_timeout(const char *const args) { + const char *c = args; + + // Skip initial whitespaces. + for (; *c == ' '; c++) + ; + + // Message. + + if (!*c) + goto invalid; + + const char *const message_start = c; + size_t message_len = 0; + + for (; *c && *c != ' '; c++) { + message_len++; + } + + // Skip whitespaces. + for (; *c == ' '; c++) + ; + + // Seconds. + + if (!*c) + goto invalid; + + size_t seconds = 0; + for (; *c && *c != ' '; c++) { + if (!isdigit(*c)) + goto invalid; + seconds = seconds * 10 + (*c - '0'); + } + + // Skip whitespaces. + for (; *c == ' '; c++) + ; + + // There should be no more arguments. + if (*c) + goto invalid; + + // Copy the string. + + char *const message_copy = malloc(message_len + 1); + if (!message_copy) { + console_puts("oscsh: setTimeout: out of memory"); + return; + } + memcpy(message_copy, message_start, message_len); + message_copy[message_len] = '\0'; + + // Register the callback. + timeout_add_timer_ns((void (*)(void *))_shell_cmd_set_timeout_timer_callback, + message_copy, seconds * NS_PER_SEC); + return; + +invalid: + console_puts("oscsh: setTimeout: invalid command format"); +} + +static void _shell_do_cmd_alloc_pages(void) { + console_fputs("Order (decimal, leave blank for 0): "); + + char digit_buf[3]; + const size_t digit_len = _shell_read_line(digit_buf, 3); + if (digit_len > 2) + goto invalid; + + size_t order = 0; + for (const char *c = digit_buf; *c; c++) { + if (!isdigit(*c)) + goto invalid; + order = order * 10 + (*c - '0'); + } + + const spage_id_t page = alloc_pages(order); + if (page < 0) { + console_puts("oscsh: alloc-pages: out of memory"); + return; + } + + console_printf("Page ID: 0x%" PRIxPAGEID "\n", (page_id_t)page); + return; + +invalid: + console_puts("oscsh: alloc-pages: invalid order"); +} + +static void _shell_do_cmd_free_pages(void) { + console_fputs( + "Page number of the first page (lowercase hexadecimal without prefix): "); + + char digit_buf[9]; + const size_t digit_len = _shell_read_line(digit_buf, 9); + if (digit_len > 8) + goto invalid; + + page_id_t page_id = 0; + for (const char *c = digit_buf; *c; c++) { + page_id_t digit_value; + if (isdigit(*c)) { + digit_value = *c - '0'; + } else if ('a' <= *c && *c <= 'f') { + digit_value = *c - 'a' + 10; + } else { + goto invalid; + } + page_id = page_id << 4 | digit_value; + } + + free_pages(page_id); + return; + +invalid: + console_puts("oscsh: free-pages: invalid page number"); +} + +static void _shell_cmd_not_found(const char *const cmd) { + console_printf("oscsh: %s: command not found\n", cmd); +} + +void run_shell(void) { + for (;;) { + _shell_print_prompt(); + + char cmd_buf[MAX_CMD_LEN + 1]; + const size_t cmd_len = _shell_read_line(cmd_buf, MAX_CMD_LEN + 1); + + if (cmd_len == 0) { + // No-op. + } else if (strcmp(cmd_buf, "help") == 0) { + _shell_do_cmd_help(); + } else if (strcmp(cmd_buf, "hello") == 0) { + _shell_do_cmd_hello(); + } else if (strcmp(cmd_buf, "hwinfo") == 0) { + _shell_do_cmd_hwinfo(); + } else if (strcmp(cmd_buf, "reboot") == 0) { + _shell_do_cmd_reboot(); + } else if (strcmp(cmd_buf, "ls") == 0) { + _shell_do_cmd_ls(); + } else if (strcmp(cmd_buf, "cat") == 0) { + _shell_do_cmd_cat(); + } else if (strcmp(cmd_buf, "exec") == 0) { + _shell_do_cmd_exec(); + } else if (strncmp(cmd_buf, "setTimeout", 10) == 0 && + (!cmd_buf[10] || cmd_buf[10] == ' ')) { + _shell_do_cmd_set_timeout(cmd_buf + 10); + } else if (strcmp(cmd_buf, "alloc-pages") == 0) { + _shell_do_cmd_alloc_pages(); + } else if (strcmp(cmd_buf, "free-pages") == 0) { + _shell_do_cmd_free_pages(); + } else { + _shell_cmd_not_found(cmd_buf); + } + } +} diff --git a/lab7/c/src/start.S b/lab7/c/src/start.S new file mode 100644 index 000000000..7520ceff1 --- /dev/null +++ b/lab7/c/src/start.S @@ -0,0 +1,120 @@ +#define TCR_T0SZ_POSN 0 +#define TCR_T0SZ_REGION_48BIT ((64 - 48) << TCR_T0SZ_POSN) +#define TCR_TG0_POSN 14 +#define TCR_TG0_4KB (0b00 << TCR_TG0_POSN) +#define TCR_T1SZ_POSN 16 +#define TCR_T1SZ_REGION_48BIT ((64 - 48) << TCR_T1SZ_POSN) +#define TCR_TG1_POSN 30 +#define TCR_TG1_4KB (0b10 << TCR_TG1_POSN) + +#define MAIR_DEVICE_nGnRnE 0b00000000 +#define MAIR_NORMAL_NOCACHE 0b01000100 +#define MAIR_IX_DEVICE_nGnRnE 0 +#define MAIR_IX_NORMAL_NOCACHE 1 + +.section ".text._start" + +_start: + // Enable FP/SIMD. + mov x1, 0x300000 + msr cptr_el2, x1 + + // Switch to EL1. + + // - Use AArch64 in EL1. + // - Trap nothing to EL2. + // - Disable interrupt routing to EL2. + // - Disable stage 2 address translation. + mov x1, 0x80000000 + msr hcr_el2, x1 + + // - Mask all interrupts. + // - AArch64 execution state. + // - EL1h. (Uses SP_EL1). + mov x1, 0x3c5 + msr spsr_el2, x1 + + adr x1, .Lin_el1 + msr elr_el2, x1 + eret + +.Lin_el1: + // Setup virtual memory. + + ldr x1, \ + =(TCR_T0SZ_REGION_48BIT | TCR_TG0_4KB | TCR_T1SZ_REGION_48BIT \ + | TCR_TG1_4KB) + msr tcr_el1, x1 + + ldr x1, \ + =((MAIR_DEVICE_nGnRnE << (MAIR_IX_DEVICE_nGnRnE * 8)) \ + | (MAIR_NORMAL_NOCACHE << (MAIR_IX_NORMAL_NOCACHE * 8))) + msr mair_el1, x1 + + ldr x1, =kernel_pgd + ldr x2, =_kernel_vm_base + sub x1, x1, x2 + + ldr x3, [x1] + ldr x4, =kernel_pud + sub x4, x4, x2 + orr x3, x3, x4 + str x3, [x1] + + msr ttbr0_el1, x1 + msr ttbr1_el1, x1 + + mrs x1, sctlr_el1 + orr x1, x1, 1 + msr sctlr_el1, x1 + + ldr x1, =.Lstart_after_mmu + br x1 + +.Lstart_after_mmu: + // Make the devicetree address virtual. + add x0, x0, x2 + + // Enable FP/SIMD. + mov x1, 0x300000 + msr cpacr_el1, x1 + + // Allow user programs to access the timer. + mov x1, 0x1 + msr cntkctl_el1, x1 + + msr tpidr_el1, xzr + + // Clear the .bss section. + + ldr x1, =_sbss + ldr x2, =_ebss + b .Lclear_bss_loop_test + +.Lclear_bss_loop_body: + // This does not generate unaligned memory accesses, since the .bss section + // is 16-byte aligned. See `linker.ld`. + stp xzr, xzr, [x1], 16 + +.Lclear_bss_loop_test: + cmp x1, x2 + b.ne .Lclear_bss_loop_body + + // Set the stack pointer. + + ldr x1, =_estack + mov sp, x1 + + // Call the main function. + + bl main + + // Park the core. + +.Lpark_loop: + wfe + b .Lpark_loop + +.size _start, . - _start +.type _start, function +.global _start diff --git a/lab7/c/src/timer/delay.c b/lab7/c/src/timer/delay.c new file mode 100644 index 000000000..8f7a29c2e --- /dev/null +++ b/lab7/c/src/timer/delay.c @@ -0,0 +1,15 @@ +#include "oscos/timer/delay.h" + +#include + +#include "oscos/timer/timeout.h" +#include "oscos/utils/suspend.h" + +static void _timeout_callback(volatile bool *const flag) { *flag = true; } + +void delay_ns(const uint64_t ns) { + volatile bool flag = false; + timeout_add_timer_ns((void (*)(void *))_timeout_callback, (void *)&flag, ns); + + WFI_WHILE(!flag); +} diff --git a/lab7/c/src/timer/timeout.c b/lab7/c/src/timer/timeout.c new file mode 100644 index 000000000..a9c8b74a6 --- /dev/null +++ b/lab7/c/src/timer/timeout.c @@ -0,0 +1,114 @@ +#include "oscos/timer/timeout.h" + +#include + +#include "oscos/drivers/l1ic.h" +#include "oscos/utils/core-id.h" +#include "oscos/utils/critical-section.h" +#include "oscos/utils/heapq.h" +#include "oscos/utils/time.h" +#include "oscos/xcpt.h" + +#define MAX_N_TIMEOUT_ENTRIES 16 + +typedef struct { + uint64_t timestamp; + void (*callback)(void *); + void *arg; +} timeout_entry_t; + +static timeout_entry_t _timeout_entries[MAX_N_TIMEOUT_ENTRIES]; +static size_t _n_timeout_entries = 0; + +static int _timeout_entry_cmp_by_timestamp(const timeout_entry_t *const e1, + const timeout_entry_t *const e2, + void *const _arg) { + (void)_arg; + + if (e1->timestamp < e2->timestamp) + return -1; + if (e1->timestamp > e2->timestamp) + return 1; + return 0; +} + +void timeout_init(void) { + __asm__ __volatile__("msr cntp_ctl_el0, %0" : : "r"(0x3)); + l1ic_enable_core_timer_irq(get_core_id()); +} + +bool timeout_add_timer_ns(void (*const callback)(void *), void *const arg, + const uint64_t after_ns) { + uint64_t core_timer_freq_hz; + __asm__("mrs %0, cntfrq_el0" : "=r"(core_timer_freq_hz)); + core_timer_freq_hz &= 0xffffffff; + + // ceil(after_ns * core_timer_freq_hz / NS_PER_SEC). + const uint64_t after_ticks = + (after_ns * core_timer_freq_hz + (NS_PER_SEC - 1)) / NS_PER_SEC; + + return timeout_add_timer_ticks(callback, arg, after_ticks); +} + +bool timeout_add_timer_ticks(void (*const callback)(void *), void *const arg, + const uint64_t after_ticks) { + if (_n_timeout_entries == MAX_N_TIMEOUT_ENTRIES) + return false; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + uint64_t curr_timestamp; + __asm__ __volatile__("mrs %0, cntpct_el0" : "=r"(curr_timestamp)); + + const uint64_t timestamp = curr_timestamp + after_ticks; + + // Reprogram the timer. + + const bool need_timer_reprogramming = + _n_timeout_entries == 0 || timestamp < _timeout_entries[0].timestamp; + if (need_timer_reprogramming) { + __asm__ __volatile__("msr cntp_cval_el0, %0" : : "r"(timestamp)); + } + + // Insert the new timeout entry into the timeout queue. + + timeout_entry_t entry = { + .timestamp = timestamp, .callback = callback, .arg = arg}; + heappush(_timeout_entries, _n_timeout_entries++, sizeof(timeout_entry_t), + &entry, + (int (*)(const void *, const void *, + void *))_timeout_entry_cmp_by_timestamp, + NULL); + + // Enable the core timer interrupt. (ENABLE = 1, IMASK = 0) + __asm__ __volatile__("msr cntp_ctl_el0, %0" : : "r"(0x1)); + + CRITICAL_SECTION_LEAVE(daif_val); + + return true; +} + +void xcpt_core_timer_interrupt_handler(void) { + // Remove the top entry from the timeout queue. + + timeout_entry_t top_entry; + heappop(_timeout_entries, _n_timeout_entries--, sizeof(timeout_entry_t), + &top_entry, + (int (*)(const void *, const void *, + void *))_timeout_entry_cmp_by_timestamp, + NULL); + + // Reprogram the timer. + if (_n_timeout_entries == 0) { + // Disable the core timer interrupt. (ENABLE = 1, IMASK = 1) + __asm__ __volatile__("msr cntp_ctl_el0, %0" : : "r"(0x3)); + } else { + __asm__ __volatile__("msr cntp_cval_el0, %0" + : + : "r"(_timeout_entries[0].timestamp)); + } + + // Execute the callback. + top_entry.callback(top_entry.arg); +} diff --git a/lab7/c/src/utils/core-id.c b/lab7/c/src/utils/core-id.c new file mode 100644 index 000000000..a7a736439 --- /dev/null +++ b/lab7/c/src/utils/core-id.c @@ -0,0 +1,9 @@ +#include "oscos/utils/core-id.h" + +#include + +size_t get_core_id(void) { + uint64_t mpidr_val; + __asm__ __volatile__("mrs %0, mpidr_el1" : "=r"(mpidr_val)); + return mpidr_val & 0x3; +} diff --git a/lab7/c/src/utils/fmt.c b/lab7/c/src/utils/fmt.c new file mode 100644 index 000000000..9721b2884 --- /dev/null +++ b/lab7/c/src/utils/fmt.c @@ -0,0 +1,653 @@ +#include "oscos/utils/fmt.h" + +#include +#include +#include + +#include "oscos/libc/ctype.h" +#include "oscos/libc/string.h" + +#define OCT_MAX_N_DIGITS 22 +#define DEC_MAX_N_DIGITS 20 +#define HEX_MAX_N_DIGITS 16 + +typedef enum { + LM_NONE, + LM_HH, + LM_H, + LM_L, + LM_LL, + LM_J, + LM_Z, + LM_T, + LM_UPPER_L +} length_modifier_t; + +static const char LOWER_HEX_DIGITS[16] = "0123456789abcdef"; +static const char UPPER_HEX_DIGITS[16] = "0123456789ABCDEF"; + +static size_t +_render_unsigned_dec(char digits[const static DEC_MAX_N_DIGITS + 1], + const uintmax_t x) { + digits[DEC_MAX_N_DIGITS] = '\0'; + + size_t n_digits = 0; + for (uintmax_t xc = x; xc > 0; xc /= 10) { + digits[DEC_MAX_N_DIGITS - ++n_digits] = '0' + xc % 10; + } + + return n_digits; +} + +static size_t +_render_unsigned_base_p2(char *const restrict digits, const size_t digits_len, + const uintmax_t x, const size_t log2_base, + const char *const restrict digit_template) { + const uintmax_t mask = (1 << log2_base) - 1; + + digits[digits_len] = '\0'; + + size_t n_digits = 0; + for (uintmax_t xc = x; xc > 0; xc >>= log2_base) { + digits[digits_len - ++n_digits] = digit_template[xc & mask]; + } + + return n_digits; +} + +static size_t _puts_generic(void (*const putc)(unsigned char, void *), + void *const putc_arg, const char *const s) { + size_t n_chars_printed = 0; + for (const char *c = s; *c; c++) { + putc(*c, putc_arg); + n_chars_printed++; + } + return n_chars_printed; +} + +static size_t _puts_limited_generic(void (*const putc)(unsigned char, void *), + void *const putc_arg, const char *const s, + const size_t limit) { + size_t n_chars_printed = 0, i = 0; + for (const char *c = s; i < limit && *c; i++, c++) { + putc(*c, putc_arg); + n_chars_printed++; + } + return n_chars_printed; +} + +static size_t _pad_generic(void (*const putc)(unsigned char, void *), + void *const putc_arg, const unsigned char pad, + const size_t width) { + for (size_t i = 0; i < width; i++) { + putc(pad, putc_arg); + } + return width; +} + +static size_t _vprintf_generic_putc(void (*const putc)(unsigned char, void *), + void *const putc_arg, const unsigned char c, + const bool flag_minus, + const size_t min_field_width) { + size_t n_chars_printed = 0; + + if (flag_minus) { + putc(c, putc_arg); + n_chars_printed++; + + if (min_field_width > 1) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', min_field_width - 1); + } + } else { + if (min_field_width > 1) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', min_field_width - 1); + } + + putc(c, putc_arg); + n_chars_printed++; + } + + return n_chars_printed; +} + +static size_t _vprintf_generic_puts(void (*const putc)(unsigned char, void *), + void *const putc_arg, const char *const s, + const bool flag_minus, + const size_t min_field_width, + const bool precision_valid, + const size_t precision) { + const size_t len = strlen(s); + + // Calculate width. + + const size_t width = precision_valid && precision < len ? precision : len; + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + // The string. + if (precision_valid) { + n_chars_printed += _puts_limited_generic(putc, putc_arg, s, precision); + } else { + n_chars_printed += _puts_generic(putc, putc_arg, s); + } + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return n_chars_printed; +} + +static size_t _vprintf_generic_print_signed_dec( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const intmax_t x, const bool flag_minus, const bool flag_plus, + const bool flag_space, const bool flag_zero, const size_t min_field_width, + const size_t precision) { + // Render the digits. + + char digits[DEC_MAX_N_DIGITS + 1]; + const size_t n_digits = _render_unsigned_dec(digits, x < 0 ? -x : x); + + // Calculate width. + + size_t width = n_digits; + if (precision > n_digits) { + width = precision; + } + width += x < 0 || flag_plus || flag_space; // Sign character. + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // Sign. + if (x < 0) { + putc('-', putc_arg); + n_chars_printed++; + } else if (flag_plus) { + putc('+', putc_arg); + n_chars_printed++; + } else if (flag_space) { + putc(' ', putc_arg); + n_chars_printed++; + } + // Leading zeros. + if (precision > n_digits) { + n_chars_printed += _pad_generic(putc, putc_arg, '0', precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (DEC_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +static size_t _vprintf_generic_print_unsigned_oct( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const uintmax_t x, const bool flag_minus, const bool flag_hash, + const bool flag_zero, const size_t min_field_width, + const size_t precision) { + // Render the digits. + + char digits[OCT_MAX_N_DIGITS + 1]; + const size_t n_digits = _render_unsigned_base_p2(digits, OCT_MAX_N_DIGITS, x, + 3, LOWER_HEX_DIGITS); + + // Calculate width. + + const size_t effective_precision = + flag_hash && n_digits + 1 > precision ? n_digits + 1 : precision; + + size_t width = n_digits; + if (effective_precision > n_digits) { + width = effective_precision; + } + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // Leading zeros. + if (effective_precision > n_digits) { + n_chars_printed += + _pad_generic(putc, putc_arg, '0', effective_precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (OCT_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +static size_t _vprintf_generic_print_unsigned_hex( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const uintmax_t x, const bool flag_minus, const bool flag_hash, + const bool flag_zero, const size_t min_field_width, const size_t precision, + const bool is_upper) { + const char *const digit_template = + is_upper ? UPPER_HEX_DIGITS : LOWER_HEX_DIGITS; + const char *const prefix = is_upper ? "0X" : "0x"; + + // Render the digits. + + char digits[HEX_MAX_N_DIGITS + 1]; + const size_t n_digits = + _render_unsigned_base_p2(digits, HEX_MAX_N_DIGITS, x, 4, digit_template); + + // Calculate width. + + size_t width = n_digits; + if (precision > n_digits) { + width = precision; + } + // 0x prefix. + if (flag_hash && x != 0) { + width += 2; + } + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // 0x prefix. + if (flag_hash && x != 0) { + n_chars_printed += _puts_generic(putc, putc_arg, prefix); + } + // Leading zeros. + if (precision > n_digits) { + n_chars_printed += _pad_generic(putc, putc_arg, '0', precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (HEX_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +static size_t _vprintf_generic_print_unsigned_dec( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const uintmax_t x, const bool flag_minus, const bool flag_zero, + const size_t min_field_width, const size_t precision) { + // Render the digits. + + char digits[DEC_MAX_N_DIGITS + 1]; + const size_t n_digits = _render_unsigned_dec(digits, x); + + // Calculate width. + + size_t width = n_digits; + if (precision > n_digits) { + width = precision; + } + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // Leading zeros. + if (precision > n_digits) { + n_chars_printed += _pad_generic(putc, putc_arg, '0', precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (DEC_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +#define READ_ARG_S(LM, AP) \ + ((LM) == LM_NONE ? va_arg(AP, int) \ + : (LM) == LM_HH ? (signed char)va_arg(AP, int) \ + : (LM) == LM_H ? (short)va_arg(AP, int) \ + : (LM) == LM_L ? va_arg(AP, long) \ + : (LM) == LM_LL ? va_arg(AP, long long) \ + : (LM) == LM_J ? va_arg(AP, intmax_t) \ + : (LM) == LM_Z ? va_arg(AP, /* signed size_t = */ long) \ + : (LM) == LM_T ? va_arg(AP, ptrdiff_t) \ + : (__builtin_unreachable(), 0)) + +#define READ_ARG_U(LM, AP) \ + ((LM) == LM_NONE ? va_arg(AP, unsigned) \ + : (LM) == LM_HH ? (unsigned char)va_arg(AP, int) \ + : (LM) == LM_H ? (unsigned short)va_arg(AP, int) \ + : (LM) == LM_L ? va_arg(AP, unsigned long) \ + : (LM) == LM_LL ? va_arg(AP, unsigned long long) \ + : (LM) == LM_J ? va_arg(AP, uintmax_t) \ + : (LM) == LM_Z ? va_arg(AP, size_t) \ + : (LM) == LM_T ? va_arg(AP, /* unsigned ptrdiff_t = */ unsigned long) \ + : (__builtin_unreachable(), 0)) + +int vprintf_generic(const printf_vtable_t *const vtable, void *const vtable_arg, + const char *const restrict format, va_list ap) { + size_t n_chars_printed = 0; + for (const char *restrict c = format; *c;) { + if (*c == '%') { + c++; + + // Flags. + + bool flag_minus = false, flag_plus = false, flag_space = false, + flag_hash = false, flag_zero = false; + + for (;;) { + if (*c == '-') { + c++; + flag_minus = true; + } else if (*c == '+') { + c++; + flag_plus = true; + } else if (*c == ' ') { + c++; + flag_space = true; + } else if (*c == '#') { + c++; + flag_hash = true; + } else if (*c == '0') { + c++; + flag_zero = true; + } else { + break; + } + } + + // Minimum field width. + + bool min_field_width_specified = false; + size_t min_field_width = 0; + if (*c == '*') { + c++; + min_field_width_specified = true; + const int arg = va_arg(ap, int); + if (arg < 0) { + flag_minus = true; + min_field_width = -arg; + } else { + min_field_width = arg; + } + } else if (isdigit(*c)) { + min_field_width_specified = true; + for (; isdigit(*c); c++) { + min_field_width = min_field_width * 10 + (*c - '0'); + } + } + + // Precision. + + bool precision_specified = false, precision_valid = false; + size_t precision = 0; + if (*c == '.') { + c++; + precision_specified = true; + + if (*c == '*') { + c++; + const int arg = va_arg(ap, int); + if (arg >= 0) { + precision_valid = true; + precision = arg; + } + } else { + precision_valid = true; + for (; isdigit(*c); c++) { + precision = precision * 10 + (*c - '0'); + } + } + } + + // Length modifier. + + length_modifier_t length_modifier = LM_NONE; + + switch (*c) { + case 'h': + c++; + if (*c == 'h') { + c++; + length_modifier = LM_HH; + } else { + length_modifier = LM_H; + } + break; + + case 'l': + c++; + if (*c == 'l') { + c++; + length_modifier = LM_LL; + } else { + length_modifier = LM_L; + } + break; + + case 'j': + c++; + length_modifier = LM_J; + break; + + case 'z': + c++; + length_modifier = LM_Z; + break; + + case 't': + c++; + length_modifier = LM_T; + break; + + case 'L': + c++; + length_modifier = LM_UPPER_L; + break; + } + + // Format specifier. + + switch (*c) { + case '%': + c++; + if (flag_minus || flag_plus || flag_space || flag_hash || flag_zero || + min_field_width_specified || precision_specified || + length_modifier != LM_NONE) + __builtin_unreachable(); + vtable->putc('%', vtable_arg); + n_chars_printed++; + break; + + case 'c': + c++; + switch (length_modifier) { + case LM_NONE: + if (flag_hash || flag_zero) + __builtin_unreachable(); + n_chars_printed += + _vprintf_generic_putc(vtable->putc, vtable_arg, va_arg(ap, int), + flag_minus, min_field_width); + break; + + default: + __builtin_unreachable(); + } + break; + + case 's': + c++; + switch (length_modifier) { + case LM_NONE: + if (flag_hash || flag_zero) + __builtin_unreachable(); + n_chars_printed += _vprintf_generic_puts( + vtable->putc, vtable_arg, va_arg(ap, const char *), flag_minus, + min_field_width, precision_valid, precision); + break; + + default: + __builtin_unreachable(); + } + break; + + case 'd': + case 'i': + c++; + if (flag_hash) + __builtin_unreachable(); + n_chars_printed += _vprintf_generic_print_signed_dec( + vtable->putc, vtable_arg, READ_ARG_S(length_modifier, ap), + flag_minus, flag_plus, flag_space, flag_zero, min_field_width, + precision_valid ? precision : 1); + break; + + case 'o': + c++; + n_chars_printed += _vprintf_generic_print_unsigned_oct( + vtable->putc, vtable_arg, READ_ARG_U(length_modifier, ap), + flag_minus, flag_hash, flag_zero, min_field_width, + precision_specified ? precision : 1); + break; + + case 'x': + case 'X': { + const bool is_upper = *c == 'X'; + c++; + n_chars_printed += _vprintf_generic_print_unsigned_hex( + vtable->putc, vtable_arg, READ_ARG_U(length_modifier, ap), + flag_minus, flag_hash, flag_zero, min_field_width, + precision_specified ? precision : 1, is_upper); + break; + } + + case 'u': + c++; + if (flag_hash) + __builtin_unreachable(); + n_chars_printed += _vprintf_generic_print_unsigned_dec( + vtable->putc, vtable_arg, READ_ARG_S(length_modifier, ap), + flag_minus, flag_zero, min_field_width, + precision_valid ? precision : 1); + break; + + case 'n': + c++; + if (flag_minus || flag_plus || flag_space || flag_hash || flag_zero || + min_field_width_specified || precision_specified) + __builtin_unreachable(); + switch (length_modifier) { + case LM_NONE: + *va_arg(ap, int *) = n_chars_printed; + break; + + case LM_HH: + *va_arg(ap, signed char *) = n_chars_printed; + break; + + case LM_H: + *va_arg(ap, short *) = n_chars_printed; + break; + + case LM_L: + *va_arg(ap, long *) = n_chars_printed; + break; + + case LM_LL: + *va_arg(ap, long long *) = n_chars_printed; + break; + + case LM_J: + *va_arg(ap, intmax_t *) = n_chars_printed; + break; + + case LM_Z: + *va_arg(ap, /* signed size_t = */ long *) = n_chars_printed; + break; + + case LM_T: + *va_arg(ap, ptrdiff_t *) = n_chars_printed; + break; + + default: + __builtin_unreachable(); + } + break; + + case 'p': + c++; + switch (length_modifier) { + case LM_NONE: + n_chars_printed += _vprintf_generic_print_unsigned_hex( + vtable->putc, vtable_arg, (uintmax_t)va_arg(ap, void *), + flag_minus, flag_hash, flag_zero, min_field_width, + precision_specified ? precision : 1, false); + break; + + default: + __builtin_unreachable(); + } + break; + + default: + __builtin_unreachable(); + } + } else { + vtable->putc(*c++, vtable_arg); + n_chars_printed++; + } + } + + vtable->finalize(vtable_arg); + return n_chars_printed; +} diff --git a/lab7/c/src/utils/heapq.c b/lab7/c/src/utils/heapq.c new file mode 100644 index 000000000..274f71e07 --- /dev/null +++ b/lab7/c/src/utils/heapq.c @@ -0,0 +1,77 @@ +#include "oscos/utils/heapq.h" + +#include "oscos/libc/string.h" + +// TODO: Check integer overflows. + +static void _heap_sift_up(void *const base, const size_t size, const size_t i, + int (*const compar)(const void *, const void *, + void *), + void *const arg) { + size_t ic = i; + while (ic != 0) { + const size_t ip = (ic - 1) / 2; + void *const pc = (char *)base + ic * size, *const pp = + (char *)base + ip * size; + if (compar(pc, pp, arg) < 0) { + memswp(pc, pp, size); + ic = ip; + } else { + break; + } + } +} + +static void _heap_sift_down( + void *const base, const size_t nmemb, const size_t size, const size_t i, + int (*const compar)(const void *, const void *, void *), void *const arg) { + size_t ic = i, il; + while ((il = ic * 2 + 1) < nmemb) { + const size_t ir = il + 1; + void *const pc = (char *)base + ic * size, + *const pl = (char *)base + il * size, + *const pr = (char *)base + ir * size; + + size_t it; + void *pt; + if (ir < nmemb && compar(pl, pr, arg) >= 0) { + it = ir; + pt = pr; + } else { + it = il; + pt = pl; + } + + if (compar(pt, pc, arg) < 0) { + memswp(pt, pc, size); + ic = it; + } else { + break; + } + } +} + +void heappush(void *const restrict base, const size_t nmemb, const size_t size, + const void *const restrict item, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + memcpy((char *)base + nmemb * size, item, size); + _heap_sift_up(base, size, nmemb, compar, arg); +} + +void heappop(void *const restrict base, const size_t nmemb, const size_t size, + void *const restrict item, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + if (nmemb == 0) + __builtin_unreachable(); + + if (item) { + memcpy(item, base, size); + } + + if (nmemb > 1) { + memcpy(base, (const char *)base + (nmemb - 1) * size, size); + _heap_sift_down(base, nmemb - 1, size, 0, compar, arg); + } +} diff --git a/lab7/c/src/utils/rb.c b/lab7/c/src/utils/rb.c new file mode 100644 index 000000000..391fbbb6e --- /dev/null +++ b/lab7/c/src/utils/rb.c @@ -0,0 +1,83 @@ +#include "oscos/utils/rb.h" + +#include "oscos/libc/string.h" +#include "oscos/mem/malloc.h" + +// TODO: Properly implement a red-black tree. + +const void *rb_search(const rb_node_t *const root, + const void *const restrict key, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + const rb_node_t *curr = root; + while (curr) { + const int compar_result = compar(key, curr->payload, arg); + if (compar_result == 0) + break; + curr = curr->children[compar_result > 0]; + } + + return curr ? curr->payload : NULL; +} + +bool rb_insert(rb_node_t **const root, const size_t size, + const void *const restrict item, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + rb_node_t **curr = root; + while (*curr) { + const int compar_result = compar(item, (*curr)->payload, arg); + if (compar_result == 0) + break; + curr = &(*curr)->children[compar_result > 0]; + } + + if (!*curr) { + *curr = malloc(sizeof(rb_node_t) + size); + if (!*curr) + return false; + + (*curr)->children[0] = (*curr)->children[1] = NULL; + } + + memcpy((*curr)->payload, item, size); + + return true; +} + +static rb_node_t **_rb_minimum(rb_node_t **const node) { + rb_node_t **curr = node; + while ((*curr)->children[0]) { + curr = &(*curr)->children[0]; + } + return curr; +} + +void rb_delete(rb_node_t **const root, const void *const restrict key, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + rb_node_t **curr = root; + while (*curr) { + const int compar_result = compar(key, (*curr)->payload, arg); + if (compar_result == 0) + break; + curr = &(*curr)->children[compar_result > 0]; + } + + rb_node_t *const curr_node = *curr; + if (!curr_node->children[0]) { + *curr = curr_node->children[1]; + } else if (!curr_node->children[1]) { + *curr = curr_node->children[0]; + } else { + rb_node_t **const min_right = _rb_minimum(&curr_node->children[1]), + *const min_right_node = *min_right; + + *min_right = min_right_node->children[1]; + *curr = min_right_node; + min_right_node->children[0] = curr_node->children[0]; + min_right_node->children[1] = curr_node->children[1]; + } + + free(curr_node); +} diff --git a/lab7/c/src/xcpt/data-abort-handler.c b/lab7/c/src/xcpt/data-abort-handler.c new file mode 100644 index 000000000..cb0a9765d --- /dev/null +++ b/lab7/c/src/xcpt/data-abort-handler.c @@ -0,0 +1,55 @@ +#include "oscos/console.h" +#include "oscos/panic.h" +#include "oscos/sched.h" + +void xcpt_default_handler(uint64_t vten); + +void xcpt_data_abort_handler(const uint64_t esr_val) { + thread_t *const curr_thread = current_thread(); + process_t *const curr_process = curr_thread->process; + + void *fault_addr; + __asm__ __volatile__("mrs %0, far_el1" : "=r"(fault_addr)); + + const uint64_t dfsc = esr_val & ((1 << 6) - 1); + + if (dfsc >> 2 == 0x1) { // Translation fault. + const vm_map_page_result_t result = + vm_map_page(&curr_process->addr_space, fault_addr); + if (result == VM_MAP_PAGE_SEGV) { +#ifdef VM_ENABLE_DEBUG_LOG + console_printf("DEBUG: vm: Segmentation fault, PID %zu, address 0x%p\n", + curr_process->id, fault_addr); +#endif + deliver_signal(curr_process, SIGSEGV); + } else if (result == VM_MAP_PAGE_NOMEM) { + // For a lack of better things to do. + thread_exit(); + } else { +#ifdef VM_ENABLE_DEBUG_LOG + console_printf("DEBUG: vm: Translation fault, PID %zu, address 0x%p\n", + curr_process->id, fault_addr); +#endif + } + } else if (dfsc >> 2 == 0x3) { // Permission fault. + const vm_map_page_result_t result = + vm_cow(&curr_process->addr_space, fault_addr); + if (result == VM_MAP_PAGE_SEGV) { +#ifdef VM_ENABLE_DEBUG_LOG + console_printf("DEBUG: vm: Segmentation fault, PID %zu, address 0x%p\n", + curr_process->id, fault_addr); +#endif + deliver_signal(curr_process, SIGSEGV); + } else if (result == VM_MAP_PAGE_NOMEM) { + // For a lack of better things to do. + thread_exit(); + } else { +#ifdef VM_ENABLE_DEBUG_LOG + console_printf("DEBUG: vm: CoW fault, PID %zu, address 0x%p\n", + curr_process->id, fault_addr); +#endif + } + } else { + xcpt_default_handler(8); + } +} diff --git a/lab7/c/src/xcpt/default-handler.c b/lab7/c/src/xcpt/default-handler.c new file mode 100644 index 000000000..e2eba4385 --- /dev/null +++ b/lab7/c/src/xcpt/default-handler.c @@ -0,0 +1,17 @@ +#include "oscos/console.h" +#include "oscos/libc/inttypes.h" +#include "oscos/panic.h" + +void xcpt_default_handler(const uint64_t vten) { + uint64_t spsr_val, elr_val, esr_val; + __asm__ __volatile__("mrs %0, spsr_el1" : "=r"(spsr_val)); + __asm__ __volatile__("mrs %0, elr_el1" : "=r"(elr_val)); + __asm__ __volatile__("mrs %0, esr_el1" : "=r"(esr_val)); + + PANIC("Unhandled exception\n" + "VTEN: 0x%16.1" PRIx64 "\n" + "spsr_el1: 0x%16.8" PRIx64 "\n" + "elr_el1: 0x%16.1" PRIx64 "\n" + "esr_el1: 0x%16.8" PRIx64, + vten, spsr_val, elr_val, esr_val); +} diff --git a/lab7/c/src/xcpt/insn-abort-handler.c b/lab7/c/src/xcpt/insn-abort-handler.c new file mode 100644 index 000000000..d70ff4b7a --- /dev/null +++ b/lab7/c/src/xcpt/insn-abort-handler.c @@ -0,0 +1,55 @@ +#include "oscos/console.h" +#include "oscos/panic.h" +#include "oscos/sched.h" + +void xcpt_default_handler(uint64_t vten); + +void xcpt_insn_abort_handler(const uint64_t esr_val) { + thread_t *const curr_thread = current_thread(); + process_t *const curr_process = curr_thread->process; + + void *fault_addr; + __asm__ __volatile__("mrs %0, far_el1" : "=r"(fault_addr)); + + const uint64_t ifsc = esr_val & ((1 << 6) - 1); + + if (ifsc >> 2 == 0x1) { // Translation fault. + const vm_map_page_result_t result = + vm_map_page(&curr_process->addr_space, fault_addr); + if (result == VM_MAP_PAGE_SEGV) { +#ifdef VM_ENABLE_DEBUG_LOG + console_printf("DEBUG: vm: Segmentation fault, PID %zu, address 0x%p\n", + curr_process->id, fault_addr); +#endif + deliver_signal(curr_process, SIGSEGV); + } else if (result == VM_MAP_PAGE_NOMEM) { + // For a lack of better things to do. + thread_exit(); + } else { +#ifdef VM_ENABLE_DEBUG_LOG + console_printf("DEBUG: vm: Translation fault, PID %zu, address 0x%p\n", + curr_process->id, fault_addr); +#endif + } + } else if (ifsc >> 2 == 0x3) { // Permission fault. + const vm_map_page_result_t result = + vm_cow(&curr_process->addr_space, fault_addr); + if (result == VM_MAP_PAGE_SEGV) { +#ifdef VM_ENABLE_DEBUG_LOG + console_printf("DEBUG: vm: Segmentation fault, PID %zu, address 0x%p\n", + curr_process->id, fault_addr); +#endif + deliver_signal(curr_process, SIGSEGV); + } else if (result == VM_MAP_PAGE_NOMEM) { + // For a lack of better things to do. + thread_exit(); + } else { +#ifdef VM_ENABLE_DEBUG_LOG + console_printf("DEBUG: vm: CoW fault, PID %zu, address 0x%p\n", + curr_process->id, fault_addr); +#endif + } + } else { + xcpt_default_handler(8); + } +} diff --git a/lab7/c/src/xcpt/irq-handler.c b/lab7/c/src/xcpt/irq-handler.c new file mode 100644 index 000000000..a77f6a17e --- /dev/null +++ b/lab7/c/src/xcpt/irq-handler.c @@ -0,0 +1,43 @@ +#include + +#include "oscos/console.h" +#include "oscos/drivers/board.h" +#include "oscos/drivers/l1ic.h" +#include "oscos/drivers/l2ic.h" +#include "oscos/libc/inttypes.h" +#include "oscos/timer/timeout.h" +#include "oscos/utils/core-id.h" + +void xcpt_irq_handler(void) { + PERIPHERAL_READ_BARRIER(); + + const size_t core_id = get_core_id(); + + for (;;) { + const uint32_t int_src = l1ic_get_int_src(core_id); + + if (int_src == 0) + break; + + if (int_src & INT_L1_SRC_GPU) { + const uint32_t l2_int_src = l2ic_get_pending_irq_0(); + + if (l2_int_src & INT_L2_IRQ_0_SRC_AUX) { + mini_uart_interrupt_handler(); + } else { + console_printf( + "WARN: Received an IRQ from GPU with an unknown source: %" PRIx32 + "\n", + l2_int_src); + } + } else if (int_src & INT_L1_SRC_TIMER1) { + xcpt_core_timer_interrupt_handler(); + } else { + console_printf("WARN: Received an IRQ with an unknown source: %" PRIx32 + "\n", + int_src); + } + } + + PERIPHERAL_WRITE_BARRIER(); +} diff --git a/lab7/c/src/xcpt/load-aapcs-and-eret.S b/lab7/c/src/xcpt/load-aapcs-and-eret.S new file mode 100644 index 000000000..b7ba5550f --- /dev/null +++ b/lab7/c/src/xcpt/load-aapcs-and-eret.S @@ -0,0 +1,11 @@ +#include "oscos/utils/save-ctx.S" + +.section ".text" + +load_aapcs_and_eret: + load_aapcs + eret + +.type load_aapcs_and_eret, function +.size load_aapcs_and_eret, . - load_aapcs_and_eret +.global load_aapcs_and_eret diff --git a/lab7/c/src/xcpt/svc-handler.S b/lab7/c/src/xcpt/svc-handler.S new file mode 100644 index 000000000..434fe9ab2 --- /dev/null +++ b/lab7/c/src/xcpt/svc-handler.S @@ -0,0 +1,50 @@ +.section ".text" + +xcpt_svc_handler: + // Save lr. + str lr, [sp, -32]! + + // Save spsr_el1 and elr_el1 since they can be clobbered by other ISRs. + mrs x10, spsr_el1 + mrs x11, elr_el1 + stp x10, x11, [sp, 16] + + // Unmask interrupts. + msr daifclr, 0xf + + // Check the system call number. + ubfx x9, x9, 0, 16 + cbnz x9, .Lenosys + cmp x8, 11 + b.hi .Lenosys + + // Table-jump to the system call function. + adr lr, .Lend + adr x9, syscall_table + add x9, x9, x8, lsl 2 + br x9 + +.Lenosys: + bl sys_enosys + +.Lend: + // Store the result into the trap frame. + str x0, [sp, 32] + + // Mask the interrupt since we don't want ISRs to clobber spsr_el1 and + // esr_el1 while we're restoring them. + msr daifset, 0xf + + // Restore spsr_el1 and elr_el1. + ldp x0, x1, [sp, 16] + msr spsr_el1, x0 + msr elr_el1, x1 + + // Restore lr. + ldr lr, [sp], 32 + + ret + +.type xcpt_svc_handler, function +.size xcpt_svc_handler, . - xcpt_svc_handler +.global xcpt_svc_handler diff --git a/lab7/c/src/xcpt/syscall-table.S b/lab7/c/src/xcpt/syscall-table.S new file mode 100644 index 000000000..396f9be11 --- /dev/null +++ b/lab7/c/src/xcpt/syscall-table.S @@ -0,0 +1,17 @@ +.section ".text" + +syscall_table: + b sys_getpid + b sys_uart_read + b sys_uart_write + b sys_exec + b sys_fork + b sys_exit + b sys_mbox_call + b sys_kill + b sys_signal + b sys_signal_kill + b sys_sigreturn + +.size syscall_table, . - syscall_table +.global syscall_table diff --git a/lab7/c/src/xcpt/syscall/enosys.c b/lab7/c/src/xcpt/syscall/enosys.c new file mode 100644 index 000000000..32ae47c8f --- /dev/null +++ b/lab7/c/src/xcpt/syscall/enosys.c @@ -0,0 +1,3 @@ +#include "oscos/uapi/errno.h" + +int sys_enosys(void) { return -ENOSYS; } diff --git a/lab7/c/src/xcpt/syscall/exec.c b/lab7/c/src/xcpt/syscall/exec.c new file mode 100644 index 000000000..b8a10cf2e --- /dev/null +++ b/lab7/c/src/xcpt/syscall/exec.c @@ -0,0 +1,44 @@ +#include +#include + +#include "oscos/initrd.h" +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" + +int sys_exec(const char *const name, char *const argv[const]) { + // TODO: Handle this. + (void)argv; + + if (!initrd_is_init()) { + // Act as if there are no files at all. + return -ENOENT; + } + + const cpio_newc_entry_t *const entry = initrd_find_entry_by_pathname(name); + if (!entry) { + return -ENOENT; + } + + const void *user_program_start; + size_t user_program_len; + + const uint32_t mode = CPIO_NEWC_HEADER_VALUE(entry, mode); + const uint32_t file_type = mode & CPIO_NEWC_MODE_FILE_TYPE_MASK; + if (file_type == CPIO_NEWC_MODE_FILE_TYPE_REG) { + user_program_start = CPIO_NEWC_FILE_DATA(entry); + user_program_len = CPIO_NEWC_FILESIZE(entry); + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_DIR) { + return -EISDIR; + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_LNK) { + // TODO: Handle symbolic link. + return -ELOOP; + } else { // Unknown file type. + // Just treat it as an I/O error. + return -EIO; + } + + exec(user_program_start, user_program_len); + + // If execution reaches here, then exec failed. + return -ENOMEM; +} diff --git a/lab7/c/src/xcpt/syscall/exit.c b/lab7/c/src/xcpt/syscall/exit.c new file mode 100644 index 000000000..d06756508 --- /dev/null +++ b/lab7/c/src/xcpt/syscall/exit.c @@ -0,0 +1,3 @@ +#include "oscos/sched.h" + +void sys_exit(void) { thread_exit(); } diff --git a/lab7/c/src/xcpt/syscall/fork-child-ret.S b/lab7/c/src/xcpt/syscall/fork-child-ret.S new file mode 100644 index 000000000..ae568d02a --- /dev/null +++ b/lab7/c/src/xcpt/syscall/fork-child-ret.S @@ -0,0 +1,29 @@ +.section ".text" + +fork_child_ret: + // Mask the interrupt to prevent spsr_el1 and elr_el1 from being clobbered + // by ISRs. + msr daifset, 0xf + + ldp x0, x1, [sp] + msr spsr_el1, x0 + msr elr_el1, x1 + + mov x0, 0 + ldr x1, [sp, 16 + 0 * 16 + 8] + ldp x2, x3, [sp, 16 + 1 * 16] + ldp x4, x5, [sp, 16 + 2 * 16] + ldp x6, x7, [sp, 16 + 3 * 16] + ldp x8, x9, [sp, 16 + 4 * 16] + ldp x10, x11, [sp, 16 + 5 * 16] + ldp x12, x13, [sp, 16 + 6 * 16] + ldp x14, x15, [sp, 16 + 7 * 16] + ldp x16, x17, [sp, 16 + 8 * 16] + ldp x18, lr, [sp, 16 + 9 * 16] + + add sp, sp, 16 + 10 * 16 + eret + +.type fork_child_ret, function +.size fork_child_ret, . - fork_child_ret +.global fork_child_ret diff --git a/lab7/c/src/xcpt/syscall/fork-impl.c b/lab7/c/src/xcpt/syscall/fork-impl.c new file mode 100644 index 000000000..af40a4c2d --- /dev/null +++ b/lab7/c/src/xcpt/syscall/fork-impl.c @@ -0,0 +1,11 @@ +#include "oscos/uapi/errno.h" + +#include "oscos/sched.h" + +int sys_fork_impl(const extended_trap_frame_t *const trap_frame) { + process_t *const new_process = fork(trap_frame); + if (!new_process) + return -ENOMEM; + + return new_process->id; +} diff --git a/lab7/c/src/xcpt/syscall/fork.S b/lab7/c/src/xcpt/syscall/fork.S new file mode 100644 index 000000000..c9bb17fed --- /dev/null +++ b/lab7/c/src/xcpt/syscall/fork.S @@ -0,0 +1,48 @@ +.section ".text" + +sys_fork: + str lr, [sp, -16]! + + // Save integer context. + mrs x0, tpidr_el1 + stp x19, x20, [x0, 16 + 0 * 16] + stp x21, x22, [x0, 16 + 1 * 16] + stp x23, x24, [x0, 16 + 2 * 16] + stp x25, x26, [x0, 16 + 3 * 16] + stp x27, x28, [x0, 16 + 4 * 16] + str x29, [x0, 16 + 5 * 16] + mrs x1, sp_el0 + str x1, [x0, 16 + 6 * 16 + 8] + + // Save FP/SIMD context. + ldr x0, [x0, 16 + 7 * 16] + stp q0, q1, [x0, 0 * 32] + stp q2, q3, [x0, 1 * 32] + stp q4, q5, [x0, 2 * 32] + stp q6, q7, [x0, 3 * 32] + stp q8, q9, [x0, 4 * 32] + stp q10, q11, [x0, 5 * 32] + stp q12, q13, [x0, 6 * 32] + stp q14, q15, [x0, 7 * 32] + stp q16, q17, [x0, 8 * 32] + stp q18, q19, [x0, 9 * 32] + stp q20, q21, [x0, 10 * 32] + stp q22, q23, [x0, 11 * 32] + stp q24, q25, [x0, 12 * 32] + stp q26, q27, [x0, 13 * 32] + stp q28, q29, [x0, 14 * 32] + stp q30, q31, [x0, 15 * 32] + add x0, x0, 16 * 32 + mrs x1, fpcr + mrs x2, fpsr + stp x1, x2, [x0] + + add x0, sp, 2 * 16 + bl sys_fork_impl + + ldr lr, [sp], 16 + ret + +.type sys_fork, function +.size sys_fork, . - sys_fork +.global sys_fork diff --git a/lab7/c/src/xcpt/syscall/getpid.c b/lab7/c/src/xcpt/syscall/getpid.c new file mode 100644 index 000000000..faa43be0e --- /dev/null +++ b/lab7/c/src/xcpt/syscall/getpid.c @@ -0,0 +1,4 @@ +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" + +int sys_getpid(void) { return current_thread()->process->id; } diff --git a/lab7/c/src/xcpt/syscall/kill.c b/lab7/c/src/xcpt/syscall/kill.c new file mode 100644 index 000000000..924686748 --- /dev/null +++ b/lab7/c/src/xcpt/syscall/kill.c @@ -0,0 +1,17 @@ +#include "oscos/uapi/errno.h" + +#include "oscos/sched.h" + +int sys_kill(const int pid) { + if (pid <= 0) { + kill_all_processes(); + } else { + process_t *const process = get_process_by_id(pid); + if (!process) + return -ESRCH; + + kill_process(process); + } + + return 0; +} diff --git a/lab7/c/src/xcpt/syscall/mbox-call.c b/lab7/c/src/xcpt/syscall/mbox-call.c new file mode 100644 index 000000000..415db0e96 --- /dev/null +++ b/lab7/c/src/xcpt/syscall/mbox-call.c @@ -0,0 +1,45 @@ +#include +#include + +#include "oscos/drivers/mailbox.h" +#include "oscos/libc/string.h" +#include "oscos/mem/malloc.h" +#include "oscos/uapi/errno.h" +#include "oscos/utils/critical-section.h" + +static bool _is_valid_mbox_ch(const unsigned char ch) { return ch < 10; } + +static bool _is_valid_mbox_ptr(const unsigned int *const mbox) { + return ((uintptr_t)mbox & 0xf) == 0; +} + +int sys_mbox_call(const unsigned char ch, unsigned int *const mbox) { + // Note on return values: + // The video player treats a return value of 0 as failure and any non-zero + // return value as success. This is confirmed by reverse-engineering the video + // player. This system call therefore follows the protocol used by the video + // player rather than the usual convention since we have to execute the video + // player properly. + + if (!_is_valid_mbox_ch(ch)) + return /* -EINVAL */ 0; + + if (!_is_valid_mbox_ptr(mbox)) + return /* -EINVAL */ 0; + + const size_t mbox_len = mbox[0]; + uint32_t *const mbox_kernel = malloc(mbox_len); + memcpy(mbox_kernel, mbox, mbox_len); + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + mailbox_call(mbox_kernel, ch); + + CRITICAL_SECTION_LEAVE(daif_val); + + memcpy(mbox, mbox_kernel, mbox_len); + free(mbox_kernel); + + return /* 0 */ 1; +} diff --git a/lab7/c/src/xcpt/syscall/signal-kill.c b/lab7/c/src/xcpt/syscall/signal-kill.c new file mode 100644 index 000000000..ef748018d --- /dev/null +++ b/lab7/c/src/xcpt/syscall/signal-kill.c @@ -0,0 +1,25 @@ +#include + +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" + +static bool _is_valid_signal_num(const int signal) { + return 1 <= signal && signal <= 31; +} + +int sys_signal_kill(const int pid, const int signal) { + if (!_is_valid_signal_num(signal)) + return -EINVAL; + + if (pid < 0) { + deliver_signal_to_all_processes(signal); + } else { + process_t *const process = get_process_by_id(pid); + if (!process) + return -ESRCH; + + deliver_signal(process, signal); + } + + return 0; +} diff --git a/lab7/c/src/xcpt/syscall/signal.c b/lab7/c/src/xcpt/syscall/signal.c new file mode 100644 index 000000000..7c10e087a --- /dev/null +++ b/lab7/c/src/xcpt/syscall/signal.c @@ -0,0 +1,15 @@ +#include + +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" + +static bool _is_valid_signal_num(const int signal) { + return 1 <= signal && signal <= 31; +} + +sighandler_t sys_signal(const int signal, const sighandler_t handler) { + if (!_is_valid_signal_num(signal)) + return (sighandler_t)-EINVAL; + + return set_signal_handler(current_thread()->process, signal, handler); +} diff --git a/lab7/c/src/xcpt/syscall/sigreturn-check.c b/lab7/c/src/xcpt/syscall/sigreturn-check.c new file mode 100644 index 000000000..4abe75a25 --- /dev/null +++ b/lab7/c/src/xcpt/syscall/sigreturn-check.c @@ -0,0 +1,13 @@ +#include "oscos/sched.h" +#include "oscos/xcpt.h" + +void sys_sigreturn_check(void) { + XCPT_MASK_ALL(); + + // Crash the process if it incorrectly calls sys_sigreturn when not handling + // signals. + if (!current_thread()->status.is_handling_signal) + thread_exit(); + + XCPT_UNMASK_ALL(); +} diff --git a/lab7/c/src/xcpt/syscall/sigreturn.S b/lab7/c/src/xcpt/syscall/sigreturn.S new file mode 100644 index 000000000..006502119 --- /dev/null +++ b/lab7/c/src/xcpt/syscall/sigreturn.S @@ -0,0 +1,50 @@ +#include "oscos/utils/save-ctx.S" + +.section ".text" + +sys_sigreturn: + // Discard the trap frame. + + add sp, sp, 12 * 16 + + // Check if the system call is called within a signal handler context. + + bl sys_sigreturn_check + + // Restore FP/SIMD context. + + ldp q30, q31, [sp, 30 * 16] + ldp q28, q29, [sp, 28 * 16] + ldp q26, q27, [sp, 26 * 16] + ldp q24, q25, [sp, 24 * 16] + ldp q22, q23, [sp, 22 * 16] + ldp q20, q21, [sp, 20 * 16] + ldp q18, q19, [sp, 18 * 16] + ldp q16, q17, [sp, 16 * 16] + ldp q14, q15, [sp, 14 * 16] + ldp q12, q13, [sp, 12 * 16] + ldp q10, q11, [sp, 10 * 16] + ldp q8, q9, [sp, 8 * 16] + ldp q6, q7, [sp, 6 * 16] + ldp q4, q5, [sp, 4 * 16] + ldp q2, q3, [sp, 2 * 16] + ldp q0, q1, [sp], (32 * 16) + + ldp x0, x1, [sp], 16 + mrs x0, fpcr + mrs x1, fpsr + + // Restore integer context. + + ldp x29, lr, [sp, 10 * 8] + ldp x27, x28, [sp, 8 * 8] + ldp x25, x26, [sp, 6 * 8] + ldp x23, x24, [sp, 4 * 8] + ldp x21, x22, [sp, 2 * 8] + ldp x19, x20, [sp], (12 * 8) + + ret + +.type sys_sigreturn, function +.size sys_sigreturn, . - sys_sigreturn +.global sys_sigreturn diff --git a/lab7/c/src/xcpt/syscall/uart-read.c b/lab7/c/src/xcpt/syscall/uart-read.c new file mode 100644 index 000000000..e4646bc77 --- /dev/null +++ b/lab7/c/src/xcpt/syscall/uart-read.c @@ -0,0 +1,46 @@ +#include + +#include "oscos/console.h" +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" +#include "oscos/uapi/unistd.h" +#include "oscos/utils/critical-section.h" + +static thread_list_node_t _wait_queue = {.prev = &_wait_queue, + .next = &_wait_queue}; + +ssize_t sys_uart_read(char buf[const], const size_t size) { + size_t n_chars_read; + + for (;;) { + // We must enter critical section here. Otherwise, there will be a race + // condition between thread suspension and read readiness notification, + // whose callback adds the current thread to the run queue. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + n_chars_read = console_read_nonblock(buf, size); + if (n_chars_read != 0) { + CRITICAL_SECTION_LEAVE(daif_val); + break; + } + + thread_t *const curr_thread = current_thread(); + + console_notify_read_ready((void (*)(void *))add_all_threads_to_run_queue, + &_wait_queue); + suspend_to_wait_queue(&_wait_queue); + XCPT_MASK_ALL(); + + if (curr_thread->status.is_waken_up_by_signal) { + curr_thread->status.is_waken_up_by_signal = false; + CRITICAL_SECTION_LEAVE(daif_val); + return -EINTR; + } + + CRITICAL_SECTION_LEAVE(daif_val); + } + + return n_chars_read; +} diff --git a/lab7/c/src/xcpt/syscall/uart-write.c b/lab7/c/src/xcpt/syscall/uart-write.c new file mode 100644 index 000000000..aa23d2722 --- /dev/null +++ b/lab7/c/src/xcpt/syscall/uart-write.c @@ -0,0 +1,45 @@ +#include + +#include "oscos/console.h" +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" +#include "oscos/utils/critical-section.h" + +static thread_list_node_t _wait_queue = {.prev = &_wait_queue, + .next = &_wait_queue}; + +size_t sys_uart_write(const char buf[const], const size_t size) { + size_t n_chars_written; + + for (;;) { + // We must enter critical section here. Otherwise, there will be a race + // condition between thread suspension and write readiness notification, + // whose callback adds the current thread to the run queue. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + n_chars_written = console_write_nonblock(buf, size); + if (n_chars_written != 0) { + CRITICAL_SECTION_LEAVE(daif_val); + break; + } + + thread_t *const curr_thread = current_thread(); + + console_notify_write_ready((void (*)(void *))add_all_threads_to_run_queue, + &_wait_queue); + suspend_to_wait_queue(&_wait_queue); + XCPT_MASK_ALL(); + + if (curr_thread->status.is_waken_up_by_signal) { + curr_thread->status.is_waken_up_by_signal = false; + CRITICAL_SECTION_LEAVE(daif_val); + return -EINTR; + } + + CRITICAL_SECTION_LEAVE(daif_val); + } + + return n_chars_written; +} diff --git a/lab7/c/src/xcpt/task-queue.c b/lab7/c/src/xcpt/task-queue.c new file mode 100644 index 000000000..2101b8827 --- /dev/null +++ b/lab7/c/src/xcpt/task-queue.c @@ -0,0 +1,97 @@ +#include "oscos/xcpt/task-queue.h" + +#include +#include + +#include "oscos/utils/critical-section.h" +#include "oscos/utils/heapq.h" + +#define MAX_N_PENDING_TASKS 16 + +typedef struct { + void (*task)(void *); + void *arg; + int priority; +} pending_task_t; + +static pending_task_t _task_queue_pending_tasks[MAX_N_PENDING_TASKS]; +static size_t _task_queue_n_pending_tasks = 0; +static int _task_queue_curr_task_priority = INT_MIN; + +static int +_task_queue_pending_task_cmp_by_priority(const pending_task_t *const t1, + const pending_task_t *const t2, + void *const _arg) { + (void)_arg; + + // Note that the order is reversed. This is because the heapq module + // implements a min heap, but we want the task with the highest priority to be + // at the front of the priority queue. + + if (t2->priority < t1->priority) + return -1; + if (t2->priority > t1->priority) + return 1; + return 0; +} + +bool task_queue_add_task(void (*const task)(void *), void *const arg, + const int priority) { + if (_task_queue_n_pending_tasks == MAX_N_PENDING_TASKS) + return false; + + const pending_task_t entry = {.task = task, .arg = arg, .priority = priority}; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + heappush(_task_queue_pending_tasks, _task_queue_n_pending_tasks++, + sizeof(pending_task_t), &entry, + (int (*)(const void *, const void *, + void *))_task_queue_pending_task_cmp_by_priority, + NULL); + CRITICAL_SECTION_LEAVE(daif_val); + + return true; +} + +void task_queue_sched(void) { + const int curr_priority = _task_queue_curr_task_priority; + + while (_task_queue_n_pending_tasks > 0 && + _task_queue_pending_tasks[0].priority > + curr_priority) { // There is a pending task of a higher priority. + // Preempt the current task and run the highest-priority pending task. + + // Remove the highest-priority pending task from the priority queue. + // No need to mask interrupts here; this function always runs within an ISR. + + pending_task_t entry; + heappop(_task_queue_pending_tasks, _task_queue_n_pending_tasks--, + sizeof(pending_task_t), &entry, + (int (*)(const void *, const void *, + void *))_task_queue_pending_task_cmp_by_priority, + NULL); + + // Update task priority. + _task_queue_curr_task_priority = entry.priority; + + // Save spsr_el1 and elr_el1, since they can be clobbered. + + uint64_t spsr_val, elr_val; + __asm__ __volatile__("mrs %0, spsr_el1" : "=r"(spsr_val)); + __asm__ __volatile__("mrs %0, elr_el1" : "=r"(elr_val)); + + // Run the task with interrupts enabled. + + XCPT_UNMASK_ALL(); + entry.task(entry.arg); + XCPT_MASK_ALL(); + + // Restore spsr_el1 and elr_el1. + __asm__ __volatile__("msr spsr_el1, %0" : : "r"(spsr_val)); + __asm__ __volatile__("msr elr_el1, %0" : : "r"(elr_val)); + } + + // Restore priority. + _task_queue_curr_task_priority = curr_priority; +} diff --git a/lab7/c/src/xcpt/vector-table.S b/lab7/c/src/xcpt/vector-table.S new file mode 100644 index 000000000..eedfe5f6b --- /dev/null +++ b/lab7/c/src/xcpt/vector-table.S @@ -0,0 +1,179 @@ +#include "oscos/utils/save-ctx.S" + +.macro default_vt_entry vten label_name +\label_name: + save_aapcs + mov x0, \vten + bl xcpt_default_handler + load_aapcs + eret + +.type \label_name, function +.size \label_name, . - \label_name + +.align 7 +.endm + +.section ".text" + +.align 11 +xcpt_vector_table: + // Exception from the current EL while using SP_EL0. + + // Synchronous. + default_vt_entry 0x0 xcpt_sync_curr_el_sp_el0_handler + + // IRQ. + default_vt_entry 0x1 xcpt_irq_curr_el_sp_el0_handler + + // FIQ. + default_vt_entry 0x2 xcpt_fiq_curr_el_sp_el0_handler + + // SError. + default_vt_entry 0x3 xcpt_serror_curr_el_sp_el0_handler + + // Exception from the current EL while using SP_ELx. + + // Synchronous. +xcpt_sync_curr_el_sp_elx_handler: + save_aapcs + + // Check if the exception is caused by an instruction abort without a change + // in exception level. + mrs x0, esr_el1 + ubfx x1, x0, 26, 6 + cmp x1, 0x21 + b.ne .Lxcpt_sync_curr_el_sp_elx_handler_not_insn_abort + + bl xcpt_insn_abort_handler + b .Lxcpt_sync_curr_el_sp_elx_handler_end + +.Lxcpt_sync_curr_el_sp_elx_handler_not_insn_abort: + // Check if the exception is caused by a data abort without a change in + // exception level. + cmp x1, 0x25 + b.ne .Lxcpt_sync_curr_el_sp_elx_handler_not_data_abort + + bl xcpt_data_abort_handler + b .Lxcpt_sync_curr_el_sp_elx_handler_end + +.Lxcpt_sync_curr_el_sp_elx_handler_not_data_abort: + mov x0, 0x4 + bl xcpt_default_handler + +.Lxcpt_sync_curr_el_sp_elx_handler_end: + // We have to split the handler, since it uses more than 32 instructions. + b load_aapcs_and_eret + +.type xcpt_sync_curr_el_sp_elx_handler, function +.size xcpt_sync_curr_el_sp_elx_handler, . - xcpt_sync_curr_el_sp_elx_handler + +.align 7 + + // IRQ. +xcpt_irq_curr_el_sp_elx_handler: + save_aapcs + bl xcpt_irq_handler + bl task_queue_sched + load_aapcs + eret + +.type xcpt_irq_curr_el_sp_elx_handler, function +.size xcpt_irq_curr_el_sp_elx_handler, . - xcpt_irq_curr_el_sp_elx_handler + +.align 7 + + // FIQ. + default_vt_entry 0x6 xcpt_fiq_curr_el_sp_elx_handler + + // SError. + default_vt_entry 0x7 xcpt_serror_curr_el_sp_elx_handler + + // Exception from a lower EL and at least one lower EL is AArch64. + + // Synchronous. +xcpt_sync_lower_el_aarch64_handler: + save_aapcs + + // Check if the exception is caused by svc. + mrs x9, esr_el1 + ubfx x10, x9, 26, 6 + cmp x10, 0x15 + b.ne .Lxcpt_sync_lower_el_aarch64_handler_not_svc + + // The exception is caused by svc. + bl xcpt_svc_handler + b .Lxcpt_sync_lower_el_aarch64_handler_end + +.Lxcpt_sync_lower_el_aarch64_handler_not_svc: + // The exception is not caused by svc. + + // Check if the exception is caused by an instruction abort from a lower + // exception level. + cmp x10, 0x20 + b.ne .Lxcpt_sync_lower_el_aarch64_handler_not_insn_abort + + mov x0, x9 + bl xcpt_insn_abort_handler + b .Lxcpt_sync_lower_el_aarch64_handler_end + +.Lxcpt_sync_lower_el_aarch64_handler_not_insn_abort: + // Check if the exception is caused by an data abort from a lower exception + // level. + cmp x10, 0x24 + b.ne .Lxcpt_sync_lower_el_aarch64_handler_not_data_abort + + mov x0, x9 + bl xcpt_data_abort_handler + b .Lxcpt_sync_lower_el_aarch64_handler_end + +.Lxcpt_sync_lower_el_aarch64_handler_not_data_abort: + mov x0, 0x8 + bl xcpt_default_handler + +.Lxcpt_sync_lower_el_aarch64_handler_end: + bl handle_signals + // We have to split the handler, since it uses more than 32 instructions. + b load_aapcs_and_eret + +.type xcpt_sync_lower_el_aarch64_handler, function +.size xcpt_sync_lower_el_aarch64_handler, . - xcpt_sync_lower_el_aarch64_handler + +.align 7 + + // IRQ. +xcpt_irq_lower_el_aarch64_handler: + save_aapcs + bl xcpt_irq_handler + bl task_queue_sched + bl handle_signals + load_aapcs + eret + +.type xcpt_irq_lower_el_aarch64_handler, function +.size xcpt_irq_lower_el_aarch64_handler, . - xcpt_irq_lower_el_aarch64_handler + +.align 7 + + // FIQ. + default_vt_entry 0xa xcpt_fiq_lower_el_aarch64_handler + + // SError. + default_vt_entry 0xb xcpt_serror_lower_el_aarch64_handler + + // Exception from a lower EL and all lower ELs are AArch32. + + // Synchronous. + default_vt_entry 0xc xcpt_sync_lower_el_aarch32_handler + + // IRQ. + default_vt_entry 0xd xcpt_irq_lower_el_aarch32_handler + + // FIQ. + default_vt_entry 0xe xcpt_fiq_lower_el_aarch32_handler + + // SError. + default_vt_entry 0xf xcpt_serror_lower_el_aarch32_handler + +.size xcpt_vector_table, . - xcpt_vector_table +.global xcpt_vector_table diff --git a/lab7/c/src/xcpt/xcpt.c b/lab7/c/src/xcpt/xcpt.c new file mode 100644 index 000000000..05b558b38 --- /dev/null +++ b/lab7/c/src/xcpt/xcpt.c @@ -0,0 +1,7 @@ +#include "oscos/xcpt.h" + +extern char xcpt_vector_table[]; + +void xcpt_set_vector_table(void) { + __asm__ __volatile__("msr vbar_el1, %0" : : "r"(xcpt_vector_table)); +} diff --git a/lab7/tests/.gitignore b/lab7/tests/.gitignore new file mode 100644 index 000000000..30d29c406 --- /dev/null +++ b/lab7/tests/.gitignore @@ -0,0 +1,2 @@ +/initramfs.cpio +/bcm2710-rpi-3-b-plus.dtb diff --git a/lab7/tests/build-initrd.sh b/lab7/tests/build-initrd.sh new file mode 100755 index 000000000..88be4059b --- /dev/null +++ b/lab7/tests/build-initrd.sh @@ -0,0 +1,13 @@ +#!/bin/sh -e + +rm -rf rootfs + +mkdir rootfs +wget https://oscapstone.github.io/_downloads/4a3ff2431ab7fa74536c184270dbe5c0/vm.img \ + -O rootfs/vm.img + +cd rootfs +find . -mindepth 1 | cpio -o -H newc > ../initramfs.cpio + +cd .. +rm -r rootfs diff --git a/lab7/tests/get-dtb.sh b/lab7/tests/get-dtb.sh new file mode 100755 index 000000000..12bb4bd6d --- /dev/null +++ b/lab7/tests/get-dtb.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +dtb_url=https://github.com/raspberrypi/firmware/raw/master/boot/bcm2710-rpi-3-b-plus.dtb + +wget "$dtb_url" diff --git a/lab7/tests/user-program/libc/.gitignore b/lab7/tests/user-program/libc/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/lab7/tests/user-program/libc/.gitignore @@ -0,0 +1 @@ +/build diff --git a/lab7/tests/user-program/libc/Makefile b/lab7/tests/user-program/libc/Makefile new file mode 100644 index 000000000..f21080034 --- /dev/null +++ b/lab7/tests/user-program/libc/Makefile @@ -0,0 +1,61 @@ +DEFAULT_PROFILE = DEBUG +SRC_DIR = src +BUILD_DIR = build + +AR = aarch64-linux-gnu-ar -rv +AS = aarch64-linux-gnu-as +CC = aarch64-linux-gnu-gcc +CPP = aarch64-linux-gnu-cpp + +CPPFLAGS_BASE = -Iinclude + +ASFLAGS_DEBUG = -g + +CFLAGS_BASE = -std=c17 -pedantic-errors -Wall -Wextra -ffreestanding \ + -mcpu=cortex-a53 -mno-outline-atomics -mstrict-align +CFLAGS_DEBUG = -g +CFLAGS_RELEASE = -O3 -flto + +OBJS = start ctype errno mbox signal stdio stdlib string unistd \ + unistd/syscall __detail/utils/fmt + +# ------------------------------------------------------------------------------ + +PROFILE = $(DEFAULT_PROFILE) +OUT_DIR = $(BUILD_DIR)/$(PROFILE) + +CPPFLAGS = $(CPPFLAGS_BASE) $(CPPFLAGS_$(PROFILE)) +ASFLAGS = $(ASFLAGS_BASE) $(ASFLAGS_$(PROFILE)) +CFLAGS = $(CFLAGS_BASE) $(CFLAGS_$(PROFILE)) + +OBJ_PATHS = $(addprefix $(OUT_DIR)/,$(addsuffix .o,$(OBJS))) + +# ------------------------------------------------------------------------------ + +.PHONY: all clean-profile clean + +all: $(OUT_DIR)/libc.a + +$(OUT_DIR)/libc.a: $(OBJ_PATHS) + @mkdir -p $(@D) + $(AR) $@ $^ + +$(OUT_DIR)/%.o: $(SRC_DIR)/%.c + @mkdir -p $(@D) + $(CC) $(CPPFLAGS) $(CFLAGS) -c -MMD -MP -MF $(OUT_DIR)/$*.d $< -o $@ + +$(OUT_DIR)/%.o: $(OUT_DIR)/%.s + @mkdir -p $(@D) + $(AS) $(ASFLAGS) $^ -o $@ + +$(OUT_DIR)/%.s: $(SRC_DIR)/%.S + @mkdir -p $(@D) + $(CPP) $(CPPFLAGS) $^ -o $@ + +clean-profile: + $(RM) -r $(OUT_DIR) + +clean: + $(RM) -r $(BUILD_DIR) + +-include $(OUT_DIR)/*.d diff --git a/lab7/tests/user-program/libc/include/__detail/utils/fmt.h b/lab7/tests/user-program/libc/include/__detail/utils/fmt.h new file mode 100644 index 000000000..36b328eb0 --- /dev/null +++ b/lab7/tests/user-program/libc/include/__detail/utils/fmt.h @@ -0,0 +1,15 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC___DETAIL_UTILS_FMT_H +#define OSCOS_USER_PROGRAM_LIBC___DETAIL_UTILS_FMT_H + +#include + +typedef struct { + void (*putc)(unsigned char, void *); + void (*finalize)(void *); +} printf_vtable_t; + +int __vprintf_generic(const printf_vtable_t *vtable, void *arg, + const char *restrict format, va_list ap) + __attribute__((format(printf, 3, 0))); + +#endif diff --git a/lab7/tests/user-program/libc/include/ctype.h b/lab7/tests/user-program/libc/include/ctype.h new file mode 100644 index 000000000..2f1cc43df --- /dev/null +++ b/lab7/tests/user-program/libc/include/ctype.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_CTYPE_H +#define OSCOS_USER_PROGRAM_LIBC_CTYPE_H + +int isdigit(int c); + +#endif diff --git a/lab7/tests/user-program/libc/include/errno.h b/lab7/tests/user-program/libc/include/errno.h new file mode 100644 index 000000000..e525f1be3 --- /dev/null +++ b/lab7/tests/user-program/libc/include/errno.h @@ -0,0 +1,9 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_ERRNO_H +#define OSCOS_USER_PROGRAM_LIBC_ERRNO_H + +// Error number definitions. +#include "oscos-uapi/errno.h" + +extern int errno; + +#endif diff --git a/lab7/tests/user-program/libc/include/mbox.h b/lab7/tests/user-program/libc/include/mbox.h new file mode 100644 index 000000000..5f5e6fd4d --- /dev/null +++ b/lab7/tests/user-program/libc/include/mbox.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_MBOX_H +#define OSCOS_USER_PROGRAM_LIBC_MBOX_H + +int mbox_call(unsigned char ch, unsigned int *mbox); + +#endif diff --git a/lab7/tests/user-program/libc/include/oscos-uapi b/lab7/tests/user-program/libc/include/oscos-uapi new file mode 120000 index 000000000..ce2ca8c4d --- /dev/null +++ b/lab7/tests/user-program/libc/include/oscos-uapi @@ -0,0 +1 @@ +../../../../c/include/oscos/uapi/ \ No newline at end of file diff --git a/lab7/tests/user-program/libc/include/signal.h b/lab7/tests/user-program/libc/include/signal.h new file mode 100644 index 000000000..866dc92c2 --- /dev/null +++ b/lab7/tests/user-program/libc/include/signal.h @@ -0,0 +1,9 @@ +#include "unistd.h" + +#include "oscos-uapi/signal.h" + +int kill(pid_t pid); + +sighandler_t signal(int signal, sighandler_t handler); + +int signal_kill(pid_t pid, int signal); diff --git a/lab7/tests/user-program/libc/include/stdio.h b/lab7/tests/user-program/libc/include/stdio.h new file mode 100644 index 000000000..ac998646d --- /dev/null +++ b/lab7/tests/user-program/libc/include/stdio.h @@ -0,0 +1,12 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_STDIO_H +#define OSCOS_USER_PROGRAM_LIBC_STDIO_H + +#include + +int printf(const char *restrict format, ...) + __attribute__((format(printf, 1, 2))); + +int vprintf(const char *restrict format, va_list ap) + __attribute__((format(printf, 1, 0))); + +#endif diff --git a/lab7/tests/user-program/libc/include/stdlib.h b/lab7/tests/user-program/libc/include/stdlib.h new file mode 100644 index 000000000..91d4e865d --- /dev/null +++ b/lab7/tests/user-program/libc/include/stdlib.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_STDLIB_H +#define OSCOS_USER_PROGRAM_LIBC_STDLIB_H + +#include + +noreturn void exit(int status); + +#endif diff --git a/lab7/tests/user-program/libc/include/string.h b/lab7/tests/user-program/libc/include/string.h new file mode 100644 index 000000000..ace3e78d1 --- /dev/null +++ b/lab7/tests/user-program/libc/include/string.h @@ -0,0 +1,25 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_STRING_H +#define OSCOS_USER_PROGRAM_LIBC_STRING_H + +#include + +int memcmp(const void *s1, const void *s2, size_t n); +void *memset(void *s, int c, size_t n); +void *memcpy(void *restrict dest, const void *restrict src, size_t n); +void *memmove(void *dest, const void *src, size_t n); + +int strcmp(const char *s1, const char *s2); +int strncmp(const char *s1, const char *s2, size_t n); + +size_t strlen(const char *s); + +// Extensions. + +/// \brief Swap two non-overlapping blocks of memory. +/// +/// \param xs The pointer to the beginning of the first block of memory. +/// \param ys The pointer to the beginning of the second block of memory. +/// \param n The size of the memory blocks. +void memswp(void *restrict xs, void *restrict ys, size_t n); + +#endif diff --git a/lab7/tests/user-program/libc/include/sys/syscall.h b/lab7/tests/user-program/libc/include/sys/syscall.h new file mode 100644 index 000000000..65efd73e5 --- /dev/null +++ b/lab7/tests/user-program/libc/include/sys/syscall.h @@ -0,0 +1 @@ +#include "../oscos-uapi/sys/syscall.h" diff --git a/lab7/tests/user-program/libc/include/unistd.h b/lab7/tests/user-program/libc/include/unistd.h new file mode 100644 index 000000000..01ea5ac4b --- /dev/null +++ b/lab7/tests/user-program/libc/include/unistd.h @@ -0,0 +1,21 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_UNISTD_H +#define OSCOS_USER_PROGRAM_LIBC_UNISTD_H + +#include + +#include "oscos-uapi/unistd.h" + +typedef int pid_t; + +pid_t getpid(void); + +ssize_t uart_read(void *buf, size_t count); +ssize_t uart_write(const void *buf, size_t count); + +int exec(const char *pathname, char *const argv[]); + +pid_t fork(void); + +long syscall(long number, ...); + +#endif diff --git a/lab7/tests/user-program/libc/src/__detail/utils/fmt.c b/lab7/tests/user-program/libc/src/__detail/utils/fmt.c new file mode 100644 index 000000000..f33b4067d --- /dev/null +++ b/lab7/tests/user-program/libc/src/__detail/utils/fmt.c @@ -0,0 +1,654 @@ +#include "__detail/utils/fmt.h" + +#include +#include +#include + +#include "ctype.h" +#include "string.h" + +#define OCT_MAX_N_DIGITS 22 +#define DEC_MAX_N_DIGITS 20 +#define HEX_MAX_N_DIGITS 16 + +typedef enum { + LM_NONE, + LM_HH, + LM_H, + LM_L, + LM_LL, + LM_J, + LM_Z, + LM_T, + LM_UPPER_L +} length_modifier_t; + +static const char LOWER_HEX_DIGITS[16] = "0123456789abcdef"; +static const char UPPER_HEX_DIGITS[16] = "0123456789ABCDEF"; + +static size_t +_render_unsigned_dec(char digits[const static DEC_MAX_N_DIGITS + 1], + const uintmax_t x) { + digits[DEC_MAX_N_DIGITS] = '\0'; + + size_t n_digits = 0; + for (uintmax_t xc = x; xc > 0; xc /= 10) { + digits[DEC_MAX_N_DIGITS - ++n_digits] = '0' + xc % 10; + } + + return n_digits; +} + +static size_t +_render_unsigned_base_p2(char *const restrict digits, const size_t digits_len, + const uintmax_t x, const size_t log2_base, + const char *const restrict digit_template) { + const uintmax_t mask = (1 << log2_base) - 1; + + digits[digits_len] = '\0'; + + size_t n_digits = 0; + for (uintmax_t xc = x; xc > 0; xc >>= log2_base) { + digits[digits_len - ++n_digits] = digit_template[xc & mask]; + } + + return n_digits; +} + +static size_t _puts_generic(void (*const putc)(unsigned char, void *), + void *const putc_arg, const char *const s) { + size_t n_chars_printed = 0; + for (const char *c = s; *c; c++) { + putc(*c, putc_arg); + n_chars_printed++; + } + return n_chars_printed; +} + +static size_t _puts_limited_generic(void (*const putc)(unsigned char, void *), + void *const putc_arg, const char *const s, + const size_t limit) { + size_t n_chars_printed = 0, i = 0; + for (const char *c = s; i < limit && *c; i++, c++) { + putc(*c, putc_arg); + n_chars_printed++; + } + return n_chars_printed; +} + +static size_t _pad_generic(void (*const putc)(unsigned char, void *), + void *const putc_arg, const unsigned char pad, + const size_t width) { + for (size_t i = 0; i < width; i++) { + putc(pad, putc_arg); + } + return width; +} + +static size_t _vprintf_generic_putc(void (*const putc)(unsigned char, void *), + void *const putc_arg, const unsigned char c, + const bool flag_minus, + const size_t min_field_width) { + size_t n_chars_printed = 0; + + if (flag_minus) { + putc(c, putc_arg); + n_chars_printed++; + + if (min_field_width > 1) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', min_field_width - 1); + } + } else { + if (min_field_width > 1) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', min_field_width - 1); + } + + putc(c, putc_arg); + n_chars_printed++; + } + + return n_chars_printed; +} + +static size_t _vprintf_generic_puts(void (*const putc)(unsigned char, void *), + void *const putc_arg, const char *const s, + const bool flag_minus, + const size_t min_field_width, + const bool precision_valid, + const size_t precision) { + const size_t len = strlen(s); + + // Calculate width. + + const size_t width = precision_valid && precision < len ? precision : len; + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + // The string. + if (precision_valid) { + n_chars_printed += _puts_limited_generic(putc, putc_arg, s, precision); + } else { + n_chars_printed += _puts_generic(putc, putc_arg, s); + } + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return n_chars_printed; +} + +static size_t _vprintf_generic_print_signed_dec( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const intmax_t x, const bool flag_minus, const bool flag_plus, + const bool flag_space, const bool flag_zero, const size_t min_field_width, + const size_t precision) { + // Render the digits. + + char digits[DEC_MAX_N_DIGITS + 1]; + const size_t n_digits = _render_unsigned_dec(digits, x < 0 ? -x : x); + + // Calculate width. + + size_t width = n_digits; + if (precision > n_digits) { + width = precision; + } + width += x < 0 || flag_plus || flag_space; // Sign character. + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // Sign. + if (x < 0) { + putc('-', putc_arg); + n_chars_printed++; + } else if (flag_plus) { + putc('+', putc_arg); + n_chars_printed++; + } else if (flag_space) { + putc(' ', putc_arg); + n_chars_printed++; + } + // Leading zeros. + if (precision > n_digits) { + n_chars_printed += _pad_generic(putc, putc_arg, '0', precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (DEC_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +static size_t _vprintf_generic_print_unsigned_oct( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const uintmax_t x, const bool flag_minus, const bool flag_hash, + const bool flag_zero, const size_t min_field_width, + const size_t precision) { + // Render the digits. + + char digits[OCT_MAX_N_DIGITS + 1]; + const size_t n_digits = _render_unsigned_base_p2(digits, OCT_MAX_N_DIGITS, x, + 3, LOWER_HEX_DIGITS); + + // Calculate width. + + const size_t effective_precision = + flag_hash && n_digits + 1 > precision ? n_digits + 1 : precision; + + size_t width = n_digits; + if (effective_precision > n_digits) { + width = effective_precision; + } + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // Leading zeros. + if (effective_precision > n_digits) { + n_chars_printed += + _pad_generic(putc, putc_arg, '0', effective_precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (OCT_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +static size_t _vprintf_generic_print_unsigned_hex( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const uintmax_t x, const bool flag_minus, const bool flag_hash, + const bool flag_zero, const size_t min_field_width, const size_t precision, + const bool is_upper) { + const char *const digit_template = + is_upper ? UPPER_HEX_DIGITS : LOWER_HEX_DIGITS; + const char *const prefix = is_upper ? "0X" : "0x"; + + // Render the digits. + + char digits[HEX_MAX_N_DIGITS + 1]; + const size_t n_digits = + _render_unsigned_base_p2(digits, HEX_MAX_N_DIGITS, x, 4, digit_template); + + // Calculate width. + + size_t width = n_digits; + if (precision > n_digits) { + width = precision; + } + // 0x prefix. + if (flag_hash && x != 0) { + width += 2; + } + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // 0x prefix. + if (flag_hash && x != 0) { + n_chars_printed += _puts_generic(putc, putc_arg, prefix); + } + // Leading zeros. + if (precision > n_digits) { + n_chars_printed += _pad_generic(putc, putc_arg, '0', precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (HEX_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +static size_t _vprintf_generic_print_unsigned_dec( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const uintmax_t x, const bool flag_minus, const bool flag_zero, + const size_t min_field_width, const size_t precision) { + // Render the digits. + + char digits[DEC_MAX_N_DIGITS + 1]; + const size_t n_digits = _render_unsigned_dec(digits, x); + + // Calculate width. + + size_t width = n_digits; + if (precision > n_digits) { + width = precision; + } + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // Leading zeros. + if (precision > n_digits) { + n_chars_printed += _pad_generic(putc, putc_arg, '0', precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (DEC_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +#define READ_ARG_S(LM, AP) \ + ((LM) == LM_NONE ? va_arg(AP, int) \ + : (LM) == LM_HH ? (signed char)va_arg(AP, int) \ + : (LM) == LM_H ? (short)va_arg(AP, int) \ + : (LM) == LM_L ? va_arg(AP, long) \ + : (LM) == LM_LL ? va_arg(AP, long long) \ + : (LM) == LM_J ? va_arg(AP, intmax_t) \ + : (LM) == LM_Z ? va_arg(AP, /* signed size_t = */ long) \ + : (LM) == LM_T ? va_arg(AP, ptrdiff_t) \ + : (__builtin_unreachable(), 0)) + +#define READ_ARG_U(LM, AP) \ + ((LM) == LM_NONE ? va_arg(AP, unsigned) \ + : (LM) == LM_HH ? (unsigned char)va_arg(AP, int) \ + : (LM) == LM_H ? (unsigned short)va_arg(AP, int) \ + : (LM) == LM_L ? va_arg(AP, unsigned long) \ + : (LM) == LM_LL ? va_arg(AP, unsigned long long) \ + : (LM) == LM_J ? va_arg(AP, uintmax_t) \ + : (LM) == LM_Z ? va_arg(AP, size_t) \ + : (LM) == LM_T ? va_arg(AP, /* unsigned ptrdiff_t = */ unsigned long) \ + : (__builtin_unreachable(), 0)) + +int __vprintf_generic(const printf_vtable_t *const vtable, + void *const vtable_arg, const char *const restrict format, + va_list ap) { + size_t n_chars_printed = 0; + for (const char *restrict c = format; *c;) { + if (*c == '%') { + c++; + + // Flags. + + bool flag_minus = false, flag_plus = false, flag_space = false, + flag_hash = false, flag_zero = false; + + for (;;) { + if (*c == '-') { + c++; + flag_minus = true; + } else if (*c == '+') { + c++; + flag_plus = true; + } else if (*c == ' ') { + c++; + flag_space = true; + } else if (*c == '#') { + c++; + flag_hash = true; + } else if (*c == '0') { + c++; + flag_zero = true; + } else { + break; + } + } + + // Minimum field width. + + bool min_field_width_specified = false; + size_t min_field_width = 0; + if (*c == '*') { + c++; + min_field_width_specified = true; + const int arg = va_arg(ap, int); + if (arg < 0) { + flag_minus = true; + min_field_width = -arg; + } else { + min_field_width = arg; + } + } else if (isdigit(*c)) { + min_field_width_specified = true; + for (; isdigit(*c); c++) { + min_field_width = min_field_width * 10 + (*c - '0'); + } + } + + // Precision. + + bool precision_specified = false, precision_valid = false; + size_t precision = 0; + if (*c == '.') { + c++; + precision_specified = true; + + if (*c == '*') { + c++; + const int arg = va_arg(ap, int); + if (arg >= 0) { + precision_valid = true; + precision = arg; + } + } else { + precision_valid = true; + for (; isdigit(*c); c++) { + precision = precision * 10 + (*c - '0'); + } + } + } + + // Length modifier. + + length_modifier_t length_modifier = LM_NONE; + + switch (*c) { + case 'h': + c++; + if (*c == 'h') { + c++; + length_modifier = LM_HH; + } else { + length_modifier = LM_H; + } + break; + + case 'l': + c++; + if (*c == 'l') { + c++; + length_modifier = LM_LL; + } else { + length_modifier = LM_L; + } + break; + + case 'j': + c++; + length_modifier = LM_J; + break; + + case 'z': + c++; + length_modifier = LM_Z; + break; + + case 't': + c++; + length_modifier = LM_T; + break; + + case 'L': + c++; + length_modifier = LM_UPPER_L; + break; + } + + // Format specifier. + + switch (*c) { + case '%': + c++; + if (flag_minus || flag_plus || flag_space || flag_hash || flag_zero || + min_field_width_specified || precision_specified || + length_modifier != LM_NONE) + __builtin_unreachable(); + vtable->putc('%', vtable_arg); + n_chars_printed++; + break; + + case 'c': + c++; + switch (length_modifier) { + case LM_NONE: + if (flag_hash || flag_zero) + __builtin_unreachable(); + n_chars_printed += + _vprintf_generic_putc(vtable->putc, vtable_arg, va_arg(ap, int), + flag_minus, min_field_width); + break; + + default: + __builtin_unreachable(); + } + break; + + case 's': + c++; + switch (length_modifier) { + case LM_NONE: + if (flag_hash || flag_zero) + __builtin_unreachable(); + n_chars_printed += _vprintf_generic_puts( + vtable->putc, vtable_arg, va_arg(ap, const char *), flag_minus, + min_field_width, precision_valid, precision); + break; + + default: + __builtin_unreachable(); + } + break; + + case 'd': + case 'i': + c++; + if (flag_hash) + __builtin_unreachable(); + n_chars_printed += _vprintf_generic_print_signed_dec( + vtable->putc, vtable_arg, READ_ARG_S(length_modifier, ap), + flag_minus, flag_plus, flag_space, flag_zero, min_field_width, + precision_valid ? precision : 1); + break; + + case 'o': + c++; + n_chars_printed += _vprintf_generic_print_unsigned_oct( + vtable->putc, vtable_arg, READ_ARG_U(length_modifier, ap), + flag_minus, flag_hash, flag_zero, min_field_width, + precision_specified ? precision : 1); + break; + + case 'x': + case 'X': { + const bool is_upper = *c == 'X'; + c++; + n_chars_printed += _vprintf_generic_print_unsigned_hex( + vtable->putc, vtable_arg, READ_ARG_U(length_modifier, ap), + flag_minus, flag_hash, flag_zero, min_field_width, + precision_specified ? precision : 1, is_upper); + break; + } + + case 'u': + c++; + if (flag_hash) + __builtin_unreachable(); + n_chars_printed += _vprintf_generic_print_unsigned_dec( + vtable->putc, vtable_arg, READ_ARG_S(length_modifier, ap), + flag_minus, flag_zero, min_field_width, + precision_valid ? precision : 1); + break; + + case 'n': + c++; + if (flag_minus || flag_plus || flag_space || flag_hash || flag_zero || + min_field_width_specified || precision_specified) + __builtin_unreachable(); + switch (length_modifier) { + case LM_NONE: + *va_arg(ap, int *) = n_chars_printed; + break; + + case LM_HH: + *va_arg(ap, signed char *) = n_chars_printed; + break; + + case LM_H: + *va_arg(ap, short *) = n_chars_printed; + break; + + case LM_L: + *va_arg(ap, long *) = n_chars_printed; + break; + + case LM_LL: + *va_arg(ap, long long *) = n_chars_printed; + break; + + case LM_J: + *va_arg(ap, intmax_t *) = n_chars_printed; + break; + + case LM_Z: + *va_arg(ap, /* signed size_t = */ long *) = n_chars_printed; + break; + + case LM_T: + *va_arg(ap, ptrdiff_t *) = n_chars_printed; + break; + + default: + __builtin_unreachable(); + } + break; + + case 'p': + c++; + switch (length_modifier) { + case LM_NONE: + n_chars_printed += _vprintf_generic_print_unsigned_hex( + vtable->putc, vtable_arg, (uintmax_t)va_arg(ap, void *), + flag_minus, flag_hash, flag_zero, min_field_width, + precision_specified ? precision : 1, false); + break; + + default: + __builtin_unreachable(); + } + break; + + default: + __builtin_unreachable(); + } + } else { + vtable->putc(*c++, vtable_arg); + n_chars_printed++; + } + } + + vtable->finalize(vtable_arg); + return n_chars_printed; +} diff --git a/lab7/tests/user-program/libc/src/ctype.c b/lab7/tests/user-program/libc/src/ctype.c new file mode 100644 index 000000000..eb145ab96 --- /dev/null +++ b/lab7/tests/user-program/libc/src/ctype.c @@ -0,0 +1,3 @@ +#include "ctype.h" + +int isdigit(const int c) { return '0' <= c && c <= '9'; } diff --git a/lab7/tests/user-program/libc/src/errno.c b/lab7/tests/user-program/libc/src/errno.c new file mode 100644 index 000000000..197b93086 --- /dev/null +++ b/lab7/tests/user-program/libc/src/errno.c @@ -0,0 +1,3 @@ +#include "errno.h" + +int errno = 0; diff --git a/lab7/tests/user-program/libc/src/mbox.c b/lab7/tests/user-program/libc/src/mbox.c new file mode 100644 index 000000000..450968f1b --- /dev/null +++ b/lab7/tests/user-program/libc/src/mbox.c @@ -0,0 +1,8 @@ +#include "mbox.h" + +#include "sys/syscall.h" +#include "unistd.h" + +int mbox_call(const unsigned char ch, unsigned int *const mbox) { + return syscall(SYS_mbox_call, ch, mbox); +} diff --git a/lab7/tests/user-program/libc/src/signal.c b/lab7/tests/user-program/libc/src/signal.c new file mode 100644 index 000000000..fdc717fa8 --- /dev/null +++ b/lab7/tests/user-program/libc/src/signal.c @@ -0,0 +1,13 @@ +#include "signal.h" + +#include "sys/syscall.h" + +int kill(const pid_t pid) { return syscall(SYS_kill, pid); } + +sighandler_t signal(const int signal, const sighandler_t handler) { + return (sighandler_t)syscall(SYS_signal, signal, handler); +} + +int signal_kill(const pid_t pid, const int signal) { + return syscall(SYS_signal_kill, pid, signal); +} diff --git a/lab7/tests/user-program/libc/src/start.S b/lab7/tests/user-program/libc/src/start.S new file mode 100644 index 000000000..2dc50db90 --- /dev/null +++ b/lab7/tests/user-program/libc/src/start.S @@ -0,0 +1,15 @@ +.section ".text._start" + +_start: + // We don't need to clear the .bss section here, since the kernel does this + // for us. + + // Call the main function. + bl main + + // Terminate the program. + b exit + +.size _start, . - _start +.type _start, function +.global _start diff --git a/lab7/tests/user-program/libc/src/stdio.c b/lab7/tests/user-program/libc/src/stdio.c new file mode 100644 index 000000000..87e894a80 --- /dev/null +++ b/lab7/tests/user-program/libc/src/stdio.c @@ -0,0 +1,31 @@ +#include "stdio.h" + +#include "__detail/utils/fmt.h" +#include "unistd.h" + +static void _printf_putc(const unsigned char c, void *const _arg) { + (void)_arg; + + // Very inefficient, but it works. + while (uart_write(&c, 1) != 1) + ; +} + +static void _printf_finalize(void *const _arg) { (void)_arg; } + +static const printf_vtable_t _printf_vtable = {.putc = _printf_putc, + .finalize = _printf_finalize}; + +int printf(const char *const restrict format, ...) { + va_list ap; + va_start(ap, format); + + const int result = vprintf(format, ap); + + va_end(ap); + return result; +} + +int vprintf(const char *const restrict format, va_list ap) { + return __vprintf_generic(&_printf_vtable, NULL, format, ap); +} diff --git a/lab7/tests/user-program/libc/src/stdlib.c b/lab7/tests/user-program/libc/src/stdlib.c new file mode 100644 index 000000000..6ca25fa10 --- /dev/null +++ b/lab7/tests/user-program/libc/src/stdlib.c @@ -0,0 +1,9 @@ +#include "stdlib.h" + +#include "sys/syscall.h" +#include "unistd.h" + +void exit(const int status) { + syscall(SYS_exit, status); + __builtin_unreachable(); +} diff --git a/lab7/tests/user-program/libc/src/string.c b/lab7/tests/user-program/libc/src/string.c new file mode 100644 index 000000000..567e116ec --- /dev/null +++ b/lab7/tests/user-program/libc/src/string.c @@ -0,0 +1,99 @@ +#include "string.h" + +__attribute__((used)) int memcmp(const void *const s1, const void *const s2, + const size_t n) { + const unsigned char *const s1_c = s1, *const s2_c = s2; + + for (size_t i = 0; i < n; i++) { + const int diff = (int)s1_c[i] - s2_c[i]; + if (diff != 0) + return diff; + } + + return 0; +} + +__attribute__((used)) void *memset(void *const s, const int c, const size_t n) { + unsigned char *const s_c = s; + + for (size_t i = 0; i < n; i++) { + s_c[i] = c; + } + + return s; +} + +static void __memmove_forward(unsigned char *const dest, + const unsigned char *const src, const size_t n) { + for (size_t i = 0; i < n; i++) { + dest[i] = src[i]; + } +} + +static void __memmove_backward(unsigned char *const dest, + const unsigned char *const src, const size_t n) { + for (size_t ip1 = n; ip1 > 0; ip1--) { + const size_t i = ip1 - 1; + dest[i] = src[i]; + } +} + +__attribute__((used)) void *memcpy(void *const restrict dest, + const void *const restrict src, + const size_t n) { + __memmove_forward(dest, src, n); + return dest; +} + +__attribute__((used)) void *memmove(void *const dest, const void *const src, + const size_t n) { + if (dest < src) { + __memmove_forward(dest, src, n); + } else if (dest > src) { + __memmove_backward(dest, src, n); + } + + return dest; +} + +int strcmp(const char *const s1, const char *const s2) { + for (const unsigned char *c1 = (const unsigned char *)s1, + *c2 = (const unsigned char *)s2; + *c1 || *c2; c1++, c2++) { + const int diff = (int)*c1 - *c2; + if (diff != 0) + return diff; + } + + return 0; +} + +int strncmp(const char *const s1, const char *const s2, const size_t n) { + size_t i = 0; + const unsigned char *c1 = (const unsigned char *)s1, + *c2 = (const unsigned char *)s2; + for (; i < n && (*c1 || *c2); i++, c1++, c2++) { + const int diff = (int)*c1 - *c2; + if (diff != 0) + return diff; + } + + return 0; +} + +size_t strlen(const char *s) { + size_t result = 0; + for (const char *c = s; *c; c++) { + result++; + } + return result; +} + +void memswp(void *const restrict xs, void *const restrict ys, const size_t n) { + unsigned char *const restrict xs_c = xs, *const restrict ys_c = ys; + for (size_t i = 0; i < n; i++) { + const unsigned char tmp = xs_c[i]; + xs_c[i] = ys_c[i]; + ys_c[i] = tmp; + } +} diff --git a/lab7/tests/user-program/libc/src/unistd.c b/lab7/tests/user-program/libc/src/unistd.c new file mode 100644 index 000000000..ba4be2c69 --- /dev/null +++ b/lab7/tests/user-program/libc/src/unistd.c @@ -0,0 +1,19 @@ +#include "unistd.h" + +#include "sys/syscall.h" + +pid_t getpid(void) { return syscall(SYS_getpid); } + +ssize_t uart_read(void *const buf, const size_t count) { + return syscall(SYS_uart_read, buf, count); +} + +ssize_t uart_write(const void *const buf, const size_t count) { + return syscall(SYS_uart_write, buf, count); +} + +int exec(const char *const pathname, char *const argv[const]) { + return syscall(SYS_exec, pathname, argv); +} + +pid_t fork(void) { return syscall(SYS_fork); } diff --git a/lab7/tests/user-program/libc/src/unistd/syscall.S b/lab7/tests/user-program/libc/src/unistd/syscall.S new file mode 100644 index 000000000..7783a7105 --- /dev/null +++ b/lab7/tests/user-program/libc/src/unistd/syscall.S @@ -0,0 +1,30 @@ +.section ".text" + +syscall: + mov x8, x0 + mov x0, x1 + mov x1, x2 + mov x2, x3 + mov x3, x4 + mov x4, x5 + mov x5, x6 + mov x6, x7 + + svc 0 + + cmp x0, -4096 + b.hi .Lsyscall_failed + + ret + +.Lsyscall_failed: + neg x0, x0 + ldr x1, =errno + str x0, [x1] + + mov x0, -1 + ret + +.type syscall, function +.size syscall, . - syscall +.global syscall diff --git a/lab7/tests/user-program/syscall_test/.gitignore b/lab7/tests/user-program/syscall_test/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/lab7/tests/user-program/syscall_test/.gitignore @@ -0,0 +1 @@ +/build diff --git a/lab7/tests/user-program/syscall_test/Makefile b/lab7/tests/user-program/syscall_test/Makefile new file mode 100644 index 000000000..c2a97559b --- /dev/null +++ b/lab7/tests/user-program/syscall_test/Makefile @@ -0,0 +1,73 @@ +DEFAULT_PROFILE = DEBUG +SRC_DIR = src +BUILD_DIR = build + +AS = aarch64-linux-gnu-as +CC = aarch64-linux-gnu-gcc +CPP = aarch64-linux-gnu-cpp +OBJCOPY = aarch64-linux-gnu-objcopy + +CPPFLAGS_BASE = -Iinclude -I../libc/include + +ASFLAGS_DEBUG = -g + +CFLAGS_BASE = -std=c17 -pedantic-errors -Wall -Wextra -ffreestanding \ + -mcpu=cortex-a53 -mno-outline-atomics -mstrict-align +CFLAGS_DEBUG = -g +CFLAGS_RELEASE = -O3 -flto + +LDFLAGS_BASE = -nostdlib -Xlinker --build-id=none +LDFLAGS_RELEASE = -flto -Xlinker --gc-sections + +LDLIBS_BASE = -lgcc -L../libc/build/$(PROFILE) -lc + +OBJS = main +LD_SCRIPT = $(SRC_DIR)/linker.ld + +# ------------------------------------------------------------------------------ + +PROFILE = $(DEFAULT_PROFILE) +OUT_DIR = $(BUILD_DIR)/$(PROFILE) + +CPPFLAGS = $(CPPFLAGS_BASE) $(CPPFLAGS_$(PROFILE)) +ASFLAGS = $(ASFLAGS_BASE) $(ASFLAGS_$(PROFILE)) +CFLAGS = $(CFLAGS_BASE) $(CFLAGS_$(PROFILE)) +LDFLAGS = $(LDFLAGS_BASE) $(LDFLAGS_$(PROFILE)) +LDLIBS = $(LDLIBS_BASE) $(LDLIBS_$(PROFILE)) + +OBJ_PATHS = $(addprefix $(OUT_DIR)/,$(addsuffix .o,$(OBJS))) + +# ------------------------------------------------------------------------------ + +.PHONY: all clean-profile clean + +all: $(OUT_DIR)/syscall_test.img + +$(OUT_DIR)/syscall_test.img: $(OUT_DIR)/syscall_test.elf + @mkdir -p $(@D) + $(OBJCOPY) -O binary $^ $@ + +$(OUT_DIR)/syscall_test.elf: $(OBJ_PATHS) $(LD_SCRIPT) + @mkdir -p $(@D) + $(CC) -T $(LD_SCRIPT) $(LDFLAGS) $(filter-out $(LD_SCRIPT),$^) $(LDLIBS) \ + -o $@ + +$(OUT_DIR)/%.o: $(SRC_DIR)/%.c + @mkdir -p $(@D) + $(CC) $(CPPFLAGS) $(CFLAGS) -c -MMD -MP -MF $(OUT_DIR)/$*.d $< -o $@ + +$(OUT_DIR)/%.o: $(OUT_DIR)/%.s + @mkdir -p $(@D) + $(AS) $(ASFLAGS) $^ -o $@ + +$(OUT_DIR)/%.s: $(SRC_DIR)/%.S + @mkdir -p $(@D) + $(CPP) $(CPPFLAGS) $^ -o $@ + +clean-profile: + $(RM) -r $(OUT_DIR) + +clean: + $(RM) -r $(BUILD_DIR) + +-include $(OUT_DIR)/*.d diff --git a/lab7/tests/user-program/syscall_test/src/linker.ld b/lab7/tests/user-program/syscall_test/src/linker.ld new file mode 100644 index 000000000..79510d671 --- /dev/null +++ b/lab7/tests/user-program/syscall_test/src/linker.ld @@ -0,0 +1,53 @@ +_suser = 0; + +ENTRY(_start) + +SECTIONS +{ + . = _suser; + + .text : + { + _stext = .; + + *(.text._start) /* Entry point. See `start.S`. */ + *(.text.unlikely .text.*_unlikely .text.unlikely.*) + *(.text.startup .text.startup.*) + *(.text.hot .text.hot.*) + *(.text .text.*) + *(.eh_frame) + *(.eh_frame_hdr) + + _etext = .; + } + + .rodata : + { + _srodata = .; + + *(.rodata .rodata.*) + + _erodata = .; + } + + .data : + { + _sdata = .; + + *(.data .data.*) + + _edata = .; + } + + .bss : + { + _sbss = .; + + *(.bss .bss.*) + *(COMMON) + + _ebss = .; + } + + _euser = .; +} diff --git a/lab7/tests/user-program/syscall_test/src/main.c b/lab7/tests/user-program/syscall_test/src/main.c new file mode 100644 index 000000000..330fe3ec9 --- /dev/null +++ b/lab7/tests/user-program/syscall_test/src/main.c @@ -0,0 +1,61 @@ +#include "stdint.h" +#include "stdio.h" +#include "stdlib.h" + +#include "unistd.h" + +#define NS_PER_SEC 1000000000 + +void delay_ns(const uint64_t ns) { + uint64_t start_counter_val; + __asm__ __volatile__("mrs %0, cntpct_el0" : "=r"(start_counter_val)::); + + uint64_t counter_clock_frequency_hz; + __asm__ __volatile__("mrs %0, cntfrq_el0" + : "=r"(counter_clock_frequency_hz)::); + counter_clock_frequency_hz &= 0xffffffff; + + // ceil(ns * counter_clock_frequency_hz / NS_PER_SEC). + const uint64_t delta_counter_val = + (ns * counter_clock_frequency_hz + (NS_PER_SEC - 1)) / NS_PER_SEC; + + for (;;) { + uint64_t counter_val; + __asm__ __volatile__("mrs %0, cntpct_el0" : "=r"(counter_val)::); + + if (counter_val - start_counter_val >= delta_counter_val) + break; + } +} + +void fork_test(void) { + printf("\nFork Test, pid %d\n", getpid()); + int cnt = 1; + int ret = 0; + if ((ret = fork()) == 0) { // child + long long cur_sp; + __asm__ __volatile__("mov %0, sp" : "=r"(cur_sp)); + printf("first child pid: %d, cnt: %d, ptr: %p, sp : %llx\n", getpid(), cnt, + (void *)&cnt, cur_sp); + ++cnt; + + if ((ret = fork()) != 0) { + __asm__ __volatile__("mov %0, sp" : "=r"(cur_sp)); + printf("first child pid: %d, cnt: %d, ptr: %p, sp : %llx\n", getpid(), + cnt, (void *)&cnt, cur_sp); + } else { + while (cnt < 5) { + __asm__ __volatile__("mov %0, sp" : "=r"(cur_sp)); + printf("second child pid: %d, cnt: %d, ptr: %p, sp : %llx\n", getpid(), + cnt, (void *)&cnt, cur_sp); + delay_ns(1000000); + ++cnt; + } + } + exit(0); + } else { + printf("parent here, pid %d, child %d\n", getpid(), ret); + } +} + +void main(void) { fork_test(); } From b596fa44dd767d744de26746198973b7514ced8a Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Thu, 29 Jun 2023 00:16:59 +0800 Subject: [PATCH 15/34] Lab 7 C: Complete B1 --- lab7/c/Makefile | 1 + lab7/c/include/oscos/fs/tmpfs.h | 8 + lab7/c/include/oscos/fs/vfs.h | 62 +++++ lab7/c/include/oscos/libc/string.h | 5 + lab7/c/include/oscos/uapi/errno.h | 3 + lab7/c/include/oscos/uapi/fcntl.h | 6 + lab7/c/include/oscos/uapi/stdio.h | 8 + lab7/c/src/fs/tmpfs.c | 375 +++++++++++++++++++++++++++++ lab7/c/src/fs/vfs.c | 83 +++++++ lab7/c/src/libc/string.c | 39 +++ lab7/c/src/main.c | 5 +- lab7/c/src/shell.c | 72 ++++++ 12 files changed, 666 insertions(+), 1 deletion(-) create mode 100644 lab7/c/include/oscos/fs/tmpfs.h create mode 100644 lab7/c/include/oscos/fs/vfs.h create mode 100644 lab7/c/include/oscos/uapi/fcntl.h create mode 100644 lab7/c/include/oscos/uapi/stdio.h create mode 100644 lab7/c/src/fs/tmpfs.c create mode 100644 lab7/c/src/fs/vfs.c diff --git a/lab7/c/Makefile b/lab7/c/Makefile index 6b488b48e..bbefb51f6 100644 --- a/lab7/c/Makefile +++ b/lab7/c/Makefile @@ -24,6 +24,7 @@ LDLIBS_BASE = -lgcc OBJS = start main console devicetree initrd panic shell \ drivers/aux drivers/gpio drivers/l1ic drivers/l2ic drivers/mailbox \ drivers/mini-uart drivers/pm \ + fs/tmpfs fs/vfs \ mem/malloc mem/page-alloc mem/shared-page mem/startup-alloc mem/vm \ mem/vm/kernel-page-tables \ sched/idle-thread sched/periodic-sched sched/run-signal-handler \ diff --git a/lab7/c/include/oscos/fs/tmpfs.h b/lab7/c/include/oscos/fs/tmpfs.h new file mode 100644 index 000000000..41329169a --- /dev/null +++ b/lab7/c/include/oscos/fs/tmpfs.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_FS_TMPFS_H +#define OSCOS_FS_TMPFS_H + +#include "oscos/fs/vfs.h" + +extern struct filesystem tmpfs; + +#endif diff --git a/lab7/c/include/oscos/fs/vfs.h b/lab7/c/include/oscos/fs/vfs.h new file mode 100644 index 000000000..59f2b888c --- /dev/null +++ b/lab7/c/include/oscos/fs/vfs.h @@ -0,0 +1,62 @@ +#ifndef OSCOS_FS_VFS_H +#define OSCOS_FS_VFS_H + +#include + +#include "oscos/uapi/fcntl.h" // O_* constants. + +struct vnode { + struct mount *mount; + struct vnode_operations *v_ops; + struct file_operations *f_ops; + void *internal; +}; + +// file handle +struct file { + struct vnode *vnode; + size_t f_pos; // RW position of this file handle + struct file_operations *f_ops; + int flags; +}; + +struct mount { + struct vnode *root; + struct filesystem *fs; +}; + +struct filesystem { + const char *name; + int (*setup_mount)(struct filesystem *fs, struct mount *mount); +}; + +struct file_operations { + int (*write)(struct file *file, const void *buf, size_t len); + int (*read)(struct file *file, void *buf, size_t len); + int (*open)(struct vnode *file_node, struct file **target); + int (*close)(struct file *file); + long (*lseek64)(struct file *file, long offset, int whence); +}; + +struct vnode_operations { + int (*lookup)(struct vnode *dir_node, struct vnode **target, + const char *component_name); + int (*create)(struct vnode *dir_node, struct vnode **target, + const char *component_name); + int (*mkdir)(struct vnode *dir_node, struct vnode **target, + const char *component_name); +}; + +extern struct mount rootfs; + +int register_filesystem(struct filesystem *fs); +int vfs_open(const char *pathname, int flags, struct file **target); +int vfs_close(struct file *file); +int vfs_write(struct file *file, const void *buf, size_t len); +int vfs_read(struct file *file, void *buf, size_t len); + +int vfs_mkdir(const char *pathname); +int vfs_mount(const char *target, const char *filesystem); +int vfs_lookup(const char *pathname, struct vnode **target); + +#endif diff --git a/lab7/c/include/oscos/libc/string.h b/lab7/c/include/oscos/libc/string.h index 5abc5b3af..654a00164 100644 --- a/lab7/c/include/oscos/libc/string.h +++ b/lab7/c/include/oscos/libc/string.h @@ -13,6 +13,11 @@ int strncmp(const char *s1, const char *s2, size_t n); size_t strlen(const char *s); +char *strdup(const char *s); +char *strndup(const char *s, size_t n); + +char *strchr(const char *s, int c); + // Extensions. /// \brief Swap two non-overlapping blocks of memory. diff --git a/lab7/c/include/oscos/uapi/errno.h b/lab7/c/include/oscos/uapi/errno.h index 1632c0383..bfb3db420 100644 --- a/lab7/c/include/oscos/uapi/errno.h +++ b/lab7/c/include/oscos/uapi/errno.h @@ -4,7 +4,10 @@ #define EIO 5 #define ENOMEM 12 #define EBUSY 16 +#define EEXIST 17 +#define ENOTDIR 20 #define EISDIR 21 #define EINVAL 22 +#define EFBIG 27 #define ENOSYS 38 #define ELOOP 40 diff --git a/lab7/c/include/oscos/uapi/fcntl.h b/lab7/c/include/oscos/uapi/fcntl.h new file mode 100644 index 000000000..d3850760d --- /dev/null +++ b/lab7/c/include/oscos/uapi/fcntl.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_UAPI_FCNTL_H +#define OSCOS_UAPI_FCNTL_H + +#define O_CREAT 00000100 + +#endif diff --git a/lab7/c/include/oscos/uapi/stdio.h b/lab7/c/include/oscos/uapi/stdio.h new file mode 100644 index 000000000..23ebacbc9 --- /dev/null +++ b/lab7/c/include/oscos/uapi/stdio.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_UAPI_STDIO_H +#define OSCOS_UAPI_STDIO_H + +#define SEEK_SET 0 +#define SEEK_END 1 +#define SEEK_CUR 2 + +#endif diff --git a/lab7/c/src/fs/tmpfs.c b/lab7/c/src/fs/tmpfs.c new file mode 100644 index 000000000..c07b2773d --- /dev/null +++ b/lab7/c/src/fs/tmpfs.c @@ -0,0 +1,375 @@ +#include "oscos/fs/tmpfs.h" + +#include "oscos/libc/string.h" +#include "oscos/mem/malloc.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/mem/vm.h" +#include "oscos/uapi/errno.h" +#include "oscos/uapi/stdio.h" +#include "oscos/utils/critical-section.h" +#include "oscos/utils/rb.h" + +#define MAX_FILE_SZ (1 << PAGE_ORDER) + +typedef enum { TYPE_DIR, TYPE_FILE } tmpfs_internal_type_t; + +typedef struct { + rb_node_t *contents; +} tmpfs_internal_dir_data_t; + +typedef struct { + unsigned char *contents; + size_t size; +} tmpfs_internal_file_data_t; + +typedef struct { + tmpfs_internal_type_t type; + union { + tmpfs_internal_dir_data_t dir_data; + tmpfs_internal_file_data_t file_data; + }; +} tmpfs_internal_t; + +typedef struct { + const char *component_name; + struct vnode *vnode; +} tmpfs_dir_contents_entry_t; + +static int _tmpfs_setup_mount(struct filesystem *fs, struct mount *mount); + +static int _tmpfs_write(struct file *file, const void *buf, size_t len); +static int _tmpfs_read(struct file *file, void *buf, size_t len); +static int _tmpfs_open(struct vnode *file_node, struct file **target); +static int _tmpfs_close(struct file *file); +static long _tmpfs_lseek64(struct file *file, long offset, int whence); + +static int _tmpfs_lookup(struct vnode *dir_node, struct vnode **target, + const char *component_name); +static int _tmpfs_create(struct vnode *dir_node, struct vnode **target, + const char *component_name); +static int _tmpfs_mkdir(struct vnode *dir_node, struct vnode **target, + const char *component_name); + +struct filesystem tmpfs = {.name = "tmpfs", .setup_mount = _tmpfs_setup_mount}; + +static struct file_operations _tmpfs_file_operations = {.write = _tmpfs_write, + .read = _tmpfs_read, + .open = _tmpfs_open, + .close = _tmpfs_close, + .lseek64 = + _tmpfs_lseek64}; + +static struct vnode_operations _tmpfs_vnode_operations = { + .lookup = _tmpfs_lookup, .create = _tmpfs_create, .mkdir = _tmpfs_mkdir}; + +static int +_tmpfs_cmp_dir_contents_entries(const tmpfs_dir_contents_entry_t *const e1, + const tmpfs_dir_contents_entry_t *const e2, + void *_arg) { + (void)_arg; + + return strcmp(e1->component_name, e2->component_name); +} + +static int _tmpfs_cmp_component_name_and_dir_contents_entry( + const char *const component_name, + const tmpfs_dir_contents_entry_t *const entry, void *_arg) { + (void)_arg; + + return strcmp(component_name, entry->component_name); +} + +static struct vnode *_tmpfs_create_file_vnode(struct mount *const mount) { + struct vnode *const result = malloc(sizeof(struct vnode)); + if (!result) + return NULL; + + tmpfs_internal_t *const internal = malloc(sizeof(tmpfs_internal_t)); + if (!internal) { + free(result); + return NULL; + } + + const spage_id_t contents_page = alloc_pages(0); + if (contents_page < 0) { + free(internal); + free(result); + return NULL; + } + unsigned char *const contents = pa_to_kernel_va(page_id_to_pa(contents_page)); + + *internal = (tmpfs_internal_t){.type = TYPE_FILE, + .file_data = (tmpfs_internal_file_data_t){ + .contents = contents, .size = 0}}; + *result = (struct vnode){.mount = mount, + .v_ops = &_tmpfs_vnode_operations, + .f_ops = &_tmpfs_file_operations, + .internal = internal}; + return result; +} + +static struct vnode *_tmpfs_create_dir_vnode(struct mount *const mount) { + struct vnode *const result = malloc(sizeof(struct vnode)); + if (!result) + return NULL; + + tmpfs_internal_t *const internal = malloc(sizeof(tmpfs_internal_t)); + if (!internal) { + free(result); + return NULL; + } + + *internal = (tmpfs_internal_t){ + .type = TYPE_DIR, + .dir_data = (tmpfs_internal_dir_data_t){.contents = NULL}}; + *result = (struct vnode){.mount = mount, + .v_ops = &_tmpfs_vnode_operations, + .f_ops = &_tmpfs_file_operations, + .internal = internal}; + return result; +} + +static int _tmpfs_setup_mount(struct filesystem *const fs, + struct mount *const mount) { + struct vnode *const root_vnode = _tmpfs_create_dir_vnode(mount); + if (!root_vnode) + return -ENOMEM; + + *mount = (struct mount){.fs = fs, .root = root_vnode}; + + return 0; +} + +static int _tmpfs_write(struct file *const file, const void *const buf, + const size_t len) { + tmpfs_internal_t *const internal = (tmpfs_internal_t *)file->vnode->internal; + if (internal->type != TYPE_FILE) + return -EISDIR; + + tmpfs_internal_file_data_t *const file_data = &internal->file_data; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + if (file->f_pos >= MAX_FILE_SZ) { + CRITICAL_SECTION_LEAVE(daif_val); + return len == 0 ? 0 : -EFBIG; + } + + const size_t remaining_len = MAX_FILE_SZ - file->f_pos, + cpy_len = len < remaining_len ? len : remaining_len; + + if (file->f_pos > file_data->size) { + memset(file_data->contents, 0, file->f_pos - file_data->size); + } + + memcpy(file_data->contents + file->f_pos, buf, cpy_len); + file->f_pos += cpy_len; + if (file->f_pos > file_data->size) { + file_data->size = file->f_pos; + } + + CRITICAL_SECTION_LEAVE(daif_val); + + return cpy_len; +} + +static int _tmpfs_read(struct file *const file, void *const buf, + const size_t len) { + tmpfs_internal_t *const internal = (tmpfs_internal_t *)file->vnode->internal; + if (internal->type != TYPE_FILE) + return -EISDIR; + + tmpfs_internal_file_data_t *const file_data = &internal->file_data; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const size_t remaining_len = file->f_pos >= file_data->size + ? 0 + : file_data->size - file->f_pos, + cpy_len = len < remaining_len ? len : remaining_len; + + memcpy(buf, file_data->contents + file->f_pos, cpy_len); + file->f_pos += cpy_len; + + CRITICAL_SECTION_LEAVE(daif_val); + + return cpy_len; +} + +static int _tmpfs_open(struct vnode *const file_node, + struct file **const target) { + tmpfs_internal_t *const internal = (tmpfs_internal_t *)file_node->internal; + if (internal->type != TYPE_FILE) + return -EISDIR; + + struct file *const file_handle = malloc(sizeof(struct file)); + if (!file_handle) + return -ENOMEM; + + *file_handle = (struct file){.vnode = file_node, + .f_pos = 0, + .f_ops = &_tmpfs_file_operations, + .flags = 0}; + *target = file_handle; + + return 0; +} + +static int _tmpfs_close(struct file *const file) { + free(file); + return 0; +} + +static long _tmpfs_lseek64(struct file *const file, const long offset, + const int whence) { + tmpfs_internal_t *const internal = (tmpfs_internal_t *)file->vnode->internal; + if (internal->type != TYPE_FILE) + return -EISDIR; + + if (!(whence == SEEK_SET || whence == SEEK_CUR || whence == SEEK_END)) + return -EINVAL; + + const long f_pos_base = whence == SEEK_SET ? 0 + : whence == SEEK_CUR ? file->f_pos + : MAX_FILE_SZ, + new_f_pos = f_pos_base + offset; + + if (new_f_pos < 0) { + return -EINVAL; + } else { + file->f_pos = new_f_pos; + return 0; + } +} + +static int _tmpfs_lookup(struct vnode *const dir_node, + struct vnode **const target, + const char *const component_name) { + tmpfs_internal_t *const internal = (tmpfs_internal_t *)dir_node->internal; + if (internal->type != TYPE_DIR) + return -ENOTDIR; + + int result; + tmpfs_internal_dir_data_t *const dir_data = &internal->dir_data; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const tmpfs_dir_contents_entry_t *const entry = rb_search( + dir_data->contents, component_name, + (int (*)(const void *, const void *, + void *))_tmpfs_cmp_component_name_and_dir_contents_entry, + NULL); + + if (entry) { + *target = entry->vnode; + result = 0; + } else { + result = -ENOENT; + } + + CRITICAL_SECTION_LEAVE(daif_val); + return result; +} + +static int _tmpfs_create(struct vnode *const dir_node, + struct vnode **const target, + const char *const component_name) { + tmpfs_internal_t *const internal = (tmpfs_internal_t *)dir_node->internal; + if (internal->type != TYPE_DIR) + return -ENOTDIR; + + int result; + tmpfs_internal_dir_data_t *const dir_data = &internal->dir_data; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const tmpfs_dir_contents_entry_t *const existing_entry = rb_search( + dir_data->contents, component_name, + (int (*)(const void *, const void *, + void *))_tmpfs_cmp_component_name_and_dir_contents_entry, + NULL); + if (existing_entry) { + result = -EEXIST; + goto end; + } + + const char *const entry_component_name = strdup(component_name); + if (!entry_component_name) { + result = -ENOMEM; + goto end; + } + + struct vnode *const vnode = _tmpfs_create_file_vnode(dir_node->mount); + if (!vnode) { + result = -ENOMEM; + goto end; + } + + const tmpfs_dir_contents_entry_t new_entry = { + .component_name = entry_component_name, .vnode = vnode}; + rb_insert(&dir_data->contents, sizeof(tmpfs_dir_contents_entry_t), &new_entry, + (int (*)(const void *, const void *, + void *))_tmpfs_cmp_dir_contents_entries, + NULL); + + *target = vnode; + result = 0; + +end: + CRITICAL_SECTION_LEAVE(daif_val); + return result; +} + +static int _tmpfs_mkdir(struct vnode *const dir_node, + struct vnode **const target, + const char *const component_name) { + tmpfs_internal_t *const internal = (tmpfs_internal_t *)dir_node->internal; + if (internal->type != TYPE_DIR) + return -ENOTDIR; + + int result; + tmpfs_internal_dir_data_t *const dir_data = &internal->dir_data; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const tmpfs_dir_contents_entry_t *const existing_entry = rb_search( + dir_data->contents, component_name, + (int (*)(const void *, const void *, + void *))_tmpfs_cmp_component_name_and_dir_contents_entry, + NULL); + if (existing_entry) { + result = -EEXIST; + goto end; + } + + const char *const entry_component_name = strdup(component_name); + if (!entry_component_name) { + result = -ENOMEM; + goto end; + } + + struct vnode *const vnode = _tmpfs_create_dir_vnode(dir_node->mount); + if (!vnode) { + result = -ENOMEM; + goto end; + } + + const tmpfs_dir_contents_entry_t new_entry = { + .component_name = entry_component_name, .vnode = vnode}; + rb_insert(&dir_data->contents, sizeof(tmpfs_dir_contents_entry_t), &new_entry, + (int (*)(const void *, const void *, + void *))_tmpfs_cmp_dir_contents_entries, + NULL); + + *target = vnode; + result = 0; + +end: + CRITICAL_SECTION_LEAVE(daif_val); + return result; +} diff --git a/lab7/c/src/fs/vfs.c b/lab7/c/src/fs/vfs.c new file mode 100644 index 000000000..34e44773c --- /dev/null +++ b/lab7/c/src/fs/vfs.c @@ -0,0 +1,83 @@ +#include "oscos/fs/vfs.h" + +#include + +#include "oscos/libc/string.h" +#include "oscos/mem/malloc.h" +#include "oscos/uapi/errno.h" +#include "oscos/utils/critical-section.h" +#include "oscos/utils/rb.h" + +struct mount rootfs; + +static rb_node_t *_filesystems = NULL; + +static int _vfs_cmp_filesystems_by_name(const struct filesystem *const fs1, + const struct filesystem *const fs2, + void *const _arg) { + (void)_arg; + + return strcmp(fs1->name, fs2->name); +} + +int register_filesystem(struct filesystem *const fs) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + rb_insert( + &_filesystems, sizeof(struct filesystem), fs, + (int (*)(const void *, const void *, void *))_vfs_cmp_filesystems_by_name, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + return 0; +} + +int vfs_open(const char *const pathname, const int flags, + struct file **const target) { + struct vnode *curr_vnode = rootfs.root; + const char *curr_pathname_component = pathname + 1; + for (const char *curr_pathname_component_end; + (curr_pathname_component_end = strchr(curr_pathname_component, '/')); + curr_pathname_component = curr_pathname_component_end + 1) { + const size_t curr_pathname_component_len = + curr_pathname_component_end - curr_pathname_component; + char *const curr_pathname_component_copy = + strndup(curr_pathname_component, curr_pathname_component_len); + if (!curr_pathname_component_copy) + return -ENOMEM; + + const int result = curr_vnode->v_ops->lookup(curr_vnode, &(*target)->vnode, + curr_pathname_component_copy); + free(curr_pathname_component_copy); + if (result < 0) + return result; + curr_vnode = (*target)->vnode; + } + + struct vnode *const parent_vnode = curr_vnode; + + const int result = parent_vnode->v_ops->lookup(parent_vnode, &curr_vnode, + curr_pathname_component); + if (result == -ENOENT && flags & O_CREAT) { + const int result = parent_vnode->v_ops->create(parent_vnode, &curr_vnode, + curr_pathname_component); + if (result < 0) + return result; + } else if (result < 0) { + return result; + } + return curr_vnode->f_ops->open(curr_vnode, target); +} + +int vfs_close(struct file *const file) { return file->f_ops->close(file); } + +int vfs_write(struct file *const file, const void *const buf, + const size_t len) { + return file->f_ops->write(file, buf, len); +} + +int vfs_read(struct file *const file, void *const buf, const size_t len) { + return file->f_ops->read(file, buf, len); +} diff --git a/lab7/c/src/libc/string.c b/lab7/c/src/libc/string.c index b1f406f82..3ea2156fa 100644 --- a/lab7/c/src/libc/string.c +++ b/lab7/c/src/libc/string.c @@ -1,5 +1,7 @@ #include "oscos/libc/string.h" +#include "oscos/mem/malloc.h" + __attribute__((used)) int memcmp(const void *const s1, const void *const s2, const size_t n) { const unsigned char *const s1_c = s1, *const s2_c = s2; @@ -89,6 +91,43 @@ size_t strlen(const char *s) { return result; } +char *strdup(const char *s) { + const size_t len = strlen(s); + + char *const result = malloc(len + 1); + if (!result) + return NULL; + + memcpy(result, s, len); + result[len] = '\0'; + + return result; +} + +char *strndup(const char *const s, const size_t n) { + size_t len = 0; + for (const char *c = s; len < n && *c; c++) { + len++; + } + + char *const result = malloc(len + 1); + if (!result) + return NULL; + + memcpy(result, s, len); + result[len] = '\0'; + + return result; +} + +char *strchr(const char *const s, const int c) { + for (const char *p = s; *p; p++) { + if (*p == c) + return (char *)p; + } + return NULL; +} + void memswp(void *const restrict xs, void *const restrict ys, const size_t n) { unsigned char *const restrict xs_c = xs, *const restrict ys_c = ys; for (size_t i = 0; i < n; i++) { diff --git a/lab7/c/src/main.c b/lab7/c/src/main.c index 7aa13fbf5..af8a3aace 100644 --- a/lab7/c/src/main.c +++ b/lab7/c/src/main.c @@ -6,6 +6,8 @@ #include "oscos/drivers/l2ic.h" #include "oscos/drivers/mailbox.h" #include "oscos/drivers/pm.h" +#include "oscos/fs/tmpfs.h" +#include "oscos/fs/vfs.h" #include "oscos/initrd.h" #include "oscos/mem/malloc.h" #include "oscos/mem/page-alloc.h" @@ -62,7 +64,8 @@ void main(const void *const dtb_start) { PANIC("Cannot initialize scheduler: out of memory"); } - // Test the scheduler. + // Initialize VFS. + tmpfs.setup_mount(&tmpfs, &rootfs); thread_create(_run_shell, NULL); diff --git a/lab7/c/src/shell.c b/lab7/c/src/shell.c index 448c7df93..fbaff6def 100644 --- a/lab7/c/src/shell.c +++ b/lab7/c/src/shell.c @@ -5,6 +5,7 @@ #include "oscos/console.h" #include "oscos/drivers/mailbox.h" #include "oscos/drivers/pm.h" +#include "oscos/fs/vfs.h" #include "oscos/initrd.h" #include "oscos/libc/ctype.h" #include "oscos/libc/inttypes.h" @@ -294,6 +295,75 @@ static void _shell_do_cmd_free_pages(void) { console_puts("oscsh: free-pages: invalid page number"); } +static void _shell_do_cmd_vfs_test_1(void) { + char buf[11] = {0}; + struct file *foo, *bar; + int result; + + result = vfs_open("/foo", O_CREAT, &foo); + console_printf("open(\"/foo\", O_CREAT) = %d\n", result); + if (result < 0) + return; + + result = vfs_write(foo, "aaaaa", 5); + console_printf("write(\"/foo\", \"aaaaa\") = %d\n", result); + if (result < 0) + return; + + result = vfs_close(foo); + console_printf("close(\"/foo\") = %d\n", result); + if (result < 0) + return; + + result = vfs_open("/bar", O_CREAT, &bar); + console_printf("open(\"/bar\", O_CREAT) = %d\n", result); + if (result < 0) + return; + + result = vfs_write(bar, "bbbbb", 5); + console_printf("write(\"/bar\", \"bbbbb\") = %d\n", result); + if (result < 0) + return; + + result = vfs_close(bar); + console_printf("close(\"/bar\") = %d\n", result); + if (result < 0) + return; + + result = vfs_open("/foo", O_CREAT, &foo); + console_printf("open(\"/foo\", O_CREAT) = %d\n", result); + if (result < 0) + return; + + result = vfs_open("/bar", O_CREAT, &bar); + console_printf("open(\"/bar\", O_CREAT) = %d\n", result); + if (result < 0) + return; + + result = vfs_read(foo, buf, 10); + console_printf("read(\"/foo\", ...) = %d\n", result); + if (result < 0) + return; + console_puts(buf); + + memset(buf, 0, 10); + result = vfs_read(bar, buf, 10); + console_printf("read(\"/bar\", ...) = %d\n", result); + if (result < 0) + return; + console_puts(buf); + + result = vfs_close(foo); + console_printf("close(\"/foo\") = %d\n", result); + if (result < 0) + return; + + result = vfs_close(bar); + console_printf("close(\"/bar\") = %d\n", result); + if (result < 0) + return; +} + static void _shell_cmd_not_found(const char *const cmd) { console_printf("oscsh: %s: command not found\n", cmd); } @@ -328,6 +398,8 @@ void run_shell(void) { _shell_do_cmd_alloc_pages(); } else if (strcmp(cmd_buf, "free-pages") == 0) { _shell_do_cmd_free_pages(); + } else if (strcmp(cmd_buf, "vfs-test-1") == 0) { + _shell_do_cmd_vfs_test_1(); } else { _shell_cmd_not_found(cmd_buf); } From 507855e5f5977bc15097709ad38cec821eed7f5d Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Thu, 29 Jun 2023 03:46:48 +0800 Subject: [PATCH 16/34] Lab 7 C: Complete B2 --- lab7/c/include/oscos/uapi/errno.h | 1 + lab7/c/src/fs/vfs.c | 194 ++++++++++++++++++++++++++++-- lab7/c/src/main.c | 1 + lab7/c/src/shell.c | 157 ++++++++++++++++++++++++ 4 files changed, 345 insertions(+), 8 deletions(-) diff --git a/lab7/c/include/oscos/uapi/errno.h b/lab7/c/include/oscos/uapi/errno.h index bfb3db420..c0363fcee 100644 --- a/lab7/c/include/oscos/uapi/errno.h +++ b/lab7/c/include/oscos/uapi/errno.h @@ -5,6 +5,7 @@ #define ENOMEM 12 #define EBUSY 16 #define EEXIST 17 +#define ENODEV 19 #define ENOTDIR 20 #define EISDIR 21 #define EINVAL 22 diff --git a/lab7/c/src/fs/vfs.c b/lab7/c/src/fs/vfs.c index 34e44773c..a23a4b605 100644 --- a/lab7/c/src/fs/vfs.c +++ b/lab7/c/src/fs/vfs.c @@ -8,9 +8,15 @@ #include "oscos/utils/critical-section.h" #include "oscos/utils/rb.h" +typedef struct { + struct vnode *mountpoint; + struct mount *mount; +} mount_entry_t; + struct mount rootfs; static rb_node_t *_filesystems = NULL; +static rb_node_t *_mounts_by_mountpoint = NULL, *_mounts_by_root = NULL; static int _vfs_cmp_filesystems_by_name(const struct filesystem *const fs1, const struct filesystem *const fs2, @@ -20,6 +26,62 @@ static int _vfs_cmp_filesystems_by_name(const struct filesystem *const fs1, return strcmp(fs1->name, fs2->name); } +static int _vfs_cmp_name_and_filesystem(const char *const name, + const struct filesystem *const fs, + void *const _arg) { + (void)_arg; + + return strcmp(name, fs->name); +} + +static int _vfs_cmp_mounts_by_mountpoint(const mount_entry_t *const m1, + const mount_entry_t *const m2, + void *const _arg) { + (void)_arg; + + if (m1->mountpoint < m2->mountpoint) + return -1; + if (m1->mountpoint > m2->mountpoint) + return 1; + return 0; +} + +static int _vfs_cmp_mountpoint_and_mount(const struct vnode *const mountpoint, + const mount_entry_t *const mount, + void *const _arg) { + (void)_arg; + + if (mountpoint < mount->mountpoint) + return -1; + if (mountpoint > mount->mountpoint) + return 1; + return 0; +} + +static int _vfs_cmp_mounts_by_root(const mount_entry_t *const m1, + const mount_entry_t *const m2, + void *const _arg) { + (void)_arg; + + if (m1->mount->root < m2->mount->root) + return -1; + if (m1->mount->root > m2->mount->root) + return 1; + return 0; +} + +static int _vfs_cmp_root_and_mount(const struct vnode *const root, + const mount_entry_t *const mount, + void *const _arg) { + (void)_arg; + + if (root < mount->mount->root) + return -1; + if (root > mount->mount->root) + return 1; + return 0; +} + int register_filesystem(struct filesystem *const fs) { uint64_t daif_val; CRITICAL_SECTION_ENTER(daif_val); @@ -34,8 +96,39 @@ int register_filesystem(struct filesystem *const fs) { return 0; } -int vfs_open(const char *const pathname, const int flags, - struct file **const target) { +static int _vfs_lookup_step(struct vnode *const curr_vnode, + struct vnode **const target, + const char *const component_name) { + const int result = + curr_vnode->v_ops->lookup(curr_vnode, target, component_name); + if (result < 0) + return result; + + // If the new node is the mountpoint of a mounted file system, jump to the + // filesystem root. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const mount_entry_t *const entry = + rb_search(_mounts_by_mountpoint, *target, + (int (*)(const void *, const void *, + void *))_vfs_cmp_mountpoint_and_mount, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + if (entry) { + *target = entry->mount->root; + } + + return 0; +} + +static int +_vfs_lookup_sans_last_level(const char *const pathname, + struct vnode **const target, + const char **const last_pathname_component) { struct vnode *curr_vnode = rootfs.root; const char *curr_pathname_component = pathname + 1; for (const char *curr_pathname_component_end; @@ -48,21 +141,34 @@ int vfs_open(const char *const pathname, const int flags, if (!curr_pathname_component_copy) return -ENOMEM; - const int result = curr_vnode->v_ops->lookup(curr_vnode, &(*target)->vnode, - curr_pathname_component_copy); + const int result = + _vfs_lookup_step(curr_vnode, target, curr_pathname_component_copy); free(curr_pathname_component_copy); if (result < 0) return result; - curr_vnode = (*target)->vnode; + curr_vnode = *target; } - struct vnode *const parent_vnode = curr_vnode; + *target = curr_vnode; + *last_pathname_component = curr_pathname_component; + return 0; +} + +int vfs_open(const char *const pathname, const int flags, + struct file **const target) { + const char *last_pathname_component; + struct vnode *parent_vnode; + const int lookup_result = _vfs_lookup_sans_last_level( + pathname, &parent_vnode, &last_pathname_component); + if (lookup_result < 0) + return lookup_result; + struct vnode *curr_vnode; const int result = parent_vnode->v_ops->lookup(parent_vnode, &curr_vnode, - curr_pathname_component); + last_pathname_component); if (result == -ENOENT && flags & O_CREAT) { const int result = parent_vnode->v_ops->create(parent_vnode, &curr_vnode, - curr_pathname_component); + last_pathname_component); if (result < 0) return result; } else if (result < 0) { @@ -81,3 +187,75 @@ int vfs_write(struct file *const file, const void *const buf, int vfs_read(struct file *const file, void *const buf, const size_t len) { return file->f_ops->read(file, buf, len); } + +int vfs_mkdir(const char *const pathname) { + const char *last_pathname_component; + struct vnode *parent_vnode; + const int lookup_result = _vfs_lookup_sans_last_level( + pathname, &parent_vnode, &last_pathname_component); + if (lookup_result < 0) + return lookup_result; + + struct vnode *target; + return parent_vnode->v_ops->mkdir(parent_vnode, &target, + last_pathname_component); +} + +int vfs_mount(const char *const target, const char *const filesystem) { + struct vnode *mountpoint; + const int result = vfs_lookup(target, &mountpoint); + if (result < 0) + return result; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + struct filesystem *fs = (struct filesystem *)rb_search( + _filesystems, filesystem, + (int (*)(const void *, const void *, void *))_vfs_cmp_name_and_filesystem, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + if (!fs) + return -ENODEV; + + struct mount *const mount = malloc(sizeof(struct mount)); + if (!mount) + return -ENOMEM; + + fs->setup_mount(fs, mount); + + const mount_entry_t entry = {.mountpoint = mountpoint, .mount = mount}; + + CRITICAL_SECTION_ENTER(daif_val); + + rb_insert(&_mounts_by_mountpoint, sizeof(mount_entry_t), &entry, + (int (*)(const void *, const void *, + void *))_vfs_cmp_mounts_by_mountpoint, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + CRITICAL_SECTION_ENTER(daif_val); + + rb_insert( + &_mounts_by_root, sizeof(mount_entry_t), &entry, + (int (*)(const void *, const void *, void *))_vfs_cmp_mounts_by_root, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + return 0; +} + +int vfs_lookup(const char *const pathname, struct vnode **const target) { + const char *last_pathname_component; + struct vnode *parent_vnode; + const int lookup_result = _vfs_lookup_sans_last_level( + pathname, &parent_vnode, &last_pathname_component); + if (lookup_result < 0) + return lookup_result; + + return _vfs_lookup_step(parent_vnode, target, last_pathname_component); +} diff --git a/lab7/c/src/main.c b/lab7/c/src/main.c index af8a3aace..ec29abe8f 100644 --- a/lab7/c/src/main.c +++ b/lab7/c/src/main.c @@ -65,6 +65,7 @@ void main(const void *const dtb_start) { } // Initialize VFS. + register_filesystem(&tmpfs); tmpfs.setup_mount(&tmpfs, &rootfs); thread_create(_run_shell, NULL); diff --git a/lab7/c/src/shell.c b/lab7/c/src/shell.c index fbaff6def..53b2fdaf2 100644 --- a/lab7/c/src/shell.c +++ b/lab7/c/src/shell.c @@ -364,6 +364,159 @@ static void _shell_do_cmd_vfs_test_1(void) { return; } +static void _shell_do_cmd_vfs_test_2(void) { + char buf[11] = {0}; + struct file *foo, *bar; + int result; + + result = vfs_mkdir("/dir"); + console_printf("mkdir(\"/dir\") = %d\n", result); + if (result < 0) + return; + + result = vfs_open("/dir/foo", O_CREAT, &foo); + console_printf("open(\"/dir/foo\", O_CREAT) = %d\n", result); + if (result < 0) + return; + + result = vfs_write(foo, "aaaaa", 5); + console_printf("write(\"/dir/foo\", \"aaaaa\") = %d\n", result); + if (result < 0) + return; + + result = vfs_close(foo); + console_printf("close(\"/dir/foo\") = %d\n", result); + if (result < 0) + return; + + result = vfs_open("/dir/bar", O_CREAT, &bar); + console_printf("open(\"/dir/bar\", O_CREAT) = %d\n", result); + if (result < 0) + return; + + result = vfs_write(bar, "bbbbb", 5); + console_printf("write(\"/dir/bar\", \"bbbbb\") = %d\n", result); + if (result < 0) + return; + + result = vfs_close(bar); + console_printf("close(\"/dir/bar\") = %d\n", result); + if (result < 0) + return; + + result = vfs_open("/dir/foo", O_CREAT, &foo); + console_printf("open(\"/dir/foo\", O_CREAT) = %d\n", result); + if (result < 0) + return; + + result = vfs_open("/dir/bar", O_CREAT, &bar); + console_printf("open(\"/dir/bar\", O_CREAT) = %d\n", result); + if (result < 0) + return; + + result = vfs_read(foo, buf, 10); + console_printf("read(\"/dir/foo\", ...) = %d\n", result); + if (result < 0) + return; + console_puts(buf); + + memset(buf, 0, 10); + result = vfs_read(bar, buf, 10); + console_printf("read(\"/dir/bar\", ...) = %d\n", result); + if (result < 0) + return; + console_puts(buf); + + result = vfs_close(foo); + console_printf("close(\"/dir/foo\") = %d\n", result); + if (result < 0) + return; + + result = vfs_close(bar); + console_printf("close(\"/dir/bar\") = %d\n", result); + if (result < 0) + return; +} + +static void _shell_do_cmd_vfs_test_3(void) { + char buf[11] = {0}; + struct file *foo, *bar; + int result; + + result = vfs_mkdir("/dir"); + console_printf("mkdir(\"/dir\") = %d\n", result); + if (result < 0) + return; + + result = vfs_mount("/dir", "tmpfs"); + console_printf("mount(\"/dir\", \"tmpfs\") = %d\n", result); + if (result < 0) + return; + + result = vfs_open("/dir/foo", O_CREAT, &foo); + console_printf("open(\"/dir/foo\", O_CREAT) = %d\n", result); + if (result < 0) + return; + + result = vfs_write(foo, "aaaaa", 5); + console_printf("write(\"/dir/foo\", \"aaaaa\") = %d\n", result); + if (result < 0) + return; + + result = vfs_close(foo); + console_printf("close(\"/dir/foo\") = %d\n", result); + if (result < 0) + return; + + result = vfs_open("/dir/bar", O_CREAT, &bar); + console_printf("open(\"/dir/bar\", O_CREAT) = %d\n", result); + if (result < 0) + return; + + result = vfs_write(bar, "bbbbb", 5); + console_printf("write(\"/dir/bar\", \"bbbbb\") = %d\n", result); + if (result < 0) + return; + + result = vfs_close(bar); + console_printf("close(\"/dir/bar\") = %d\n", result); + if (result < 0) + return; + + result = vfs_open("/dir/foo", O_CREAT, &foo); + console_printf("open(\"/dir/foo\", O_CREAT) = %d\n", result); + if (result < 0) + return; + + result = vfs_open("/dir/bar", O_CREAT, &bar); + console_printf("open(\"/dir/bar\", O_CREAT) = %d\n", result); + if (result < 0) + return; + + result = vfs_read(foo, buf, 10); + console_printf("read(\"/dir/foo\", ...) = %d\n", result); + if (result < 0) + return; + console_puts(buf); + + memset(buf, 0, 10); + result = vfs_read(bar, buf, 10); + console_printf("read(\"/dir/bar\", ...) = %d\n", result); + if (result < 0) + return; + console_puts(buf); + + result = vfs_close(foo); + console_printf("close(\"/dir/foo\") = %d\n", result); + if (result < 0) + return; + + result = vfs_close(bar); + console_printf("close(\"/dir/bar\") = %d\n", result); + if (result < 0) + return; +} + static void _shell_cmd_not_found(const char *const cmd) { console_printf("oscsh: %s: command not found\n", cmd); } @@ -400,6 +553,10 @@ void run_shell(void) { _shell_do_cmd_free_pages(); } else if (strcmp(cmd_buf, "vfs-test-1") == 0) { _shell_do_cmd_vfs_test_1(); + } else if (strcmp(cmd_buf, "vfs-test-2") == 0) { + _shell_do_cmd_vfs_test_2(); + } else if (strcmp(cmd_buf, "vfs-test-3") == 0) { + _shell_do_cmd_vfs_test_3(); } else { _shell_cmd_not_found(cmd_buf); } From 8a64b3071fac2e295d49281afa420e427f59d65f Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Thu, 29 Jun 2023 05:37:50 +0800 Subject: [PATCH 17/34] Lab 7 C: Complete B3 Other changes: - Disable demand paging/CoW debug log. - Fix a bug in `free` that causes an incorrect slot to be freed. --- lab7/c/Makefile | 5 +- lab7/c/include/oscos/fs/vfs.h | 16 +++ lab7/c/include/oscos/sched.h | 5 + lab7/c/include/oscos/uapi/errno.h | 2 + lab7/c/include/oscos/uapi/sys/syscall.h | 7 ++ lab7/c/src/fs/tmpfs.c | 23 +++- lab7/c/src/fs/vfs.c | 113 +++++++++++++++--- lab7/c/src/mem/malloc.c | 2 +- lab7/c/src/sched/sched.c | 14 +++ lab7/c/src/shell.c | 88 ++++++++++++++ lab7/c/src/xcpt/svc-handler.S | 2 +- lab7/c/src/xcpt/syscall-table.S | 7 ++ lab7/c/src/xcpt/syscall/chdir.c | 14 +++ lab7/c/src/xcpt/syscall/close.c | 15 +++ lab7/c/src/xcpt/syscall/mkdir.c | 10 ++ lab7/c/src/xcpt/syscall/mount.c | 14 +++ lab7/c/src/xcpt/syscall/open.c | 34 ++++++ lab7/c/src/xcpt/syscall/read.c | 12 ++ lab7/c/src/xcpt/syscall/write.c | 12 ++ lab7/tests/build-initrd.sh | 4 +- lab7/tests/user-program/libc/Makefile | 4 +- lab7/tests/user-program/libc/include/fcntl.h | 8 ++ .../user-program/libc/include/sys/mount.h | 7 ++ .../user-program/libc/include/sys/stat.h | 8 ++ lab7/tests/user-program/libc/include/unistd.h | 7 ++ lab7/tests/user-program/libc/src/fcntl.c | 8 ++ lab7/tests/user-program/libc/src/sys/mount.c | 10 ++ lab7/tests/user-program/libc/src/sys/stat.c | 8 ++ lab7/tests/user-program/libc/src/unistd.c | 12 ++ 29 files changed, 443 insertions(+), 28 deletions(-) create mode 100644 lab7/c/src/xcpt/syscall/chdir.c create mode 100644 lab7/c/src/xcpt/syscall/close.c create mode 100644 lab7/c/src/xcpt/syscall/mkdir.c create mode 100644 lab7/c/src/xcpt/syscall/mount.c create mode 100644 lab7/c/src/xcpt/syscall/open.c create mode 100644 lab7/c/src/xcpt/syscall/read.c create mode 100644 lab7/c/src/xcpt/syscall/write.c create mode 100644 lab7/tests/user-program/libc/include/fcntl.h create mode 100644 lab7/tests/user-program/libc/include/sys/mount.h create mode 100644 lab7/tests/user-program/libc/include/sys/stat.h create mode 100644 lab7/tests/user-program/libc/src/fcntl.c create mode 100644 lab7/tests/user-program/libc/src/sys/mount.c create mode 100644 lab7/tests/user-program/libc/src/sys/stat.c diff --git a/lab7/c/Makefile b/lab7/c/Makefile index bbefb51f6..6b3f6bda3 100644 --- a/lab7/c/Makefile +++ b/lab7/c/Makefile @@ -7,7 +7,7 @@ CC = aarch64-linux-gnu-gcc CPP = aarch64-linux-gnu-cpp OBJCOPY = aarch64-linux-gnu-objcopy -CPPFLAGS_BASE = -Iinclude -DVM_ENABLE_DEBUG_LOG +CPPFLAGS_BASE = -Iinclude ASFLAGS_DEBUG = -g @@ -41,6 +41,9 @@ OBJS = start main console devicetree initrd panic shell \ xcpt/syscall/exit xcpt/syscall/mbox-call xcpt/syscall/kill \ xcpt/syscall/signal xcpt/syscall/signal-kill \ xcpt/syscall/sigreturn xcpt/syscall/sigreturn-check \ + xcpt/syscall/open xcpt/syscall/close xcpt/syscall/write \ + xcpt/syscall/read xcpt/syscall/mkdir xcpt/syscall/mount \ + xcpt/syscall/chdir \ libc/ctype libc/stdio libc/stdlib/qsort libc/string \ utils/core-id utils/fmt utils/heapq utils/rb LD_SCRIPT = $(SRC_DIR)/linker.ld diff --git a/lab7/c/include/oscos/fs/vfs.h b/lab7/c/include/oscos/fs/vfs.h index 59f2b888c..4af926ce2 100644 --- a/lab7/c/include/oscos/fs/vfs.h +++ b/lab7/c/include/oscos/fs/vfs.h @@ -51,12 +51,28 @@ extern struct mount rootfs; int register_filesystem(struct filesystem *fs); int vfs_open(const char *pathname, int flags, struct file **target); +int vfs_open_relative(struct vnode *cwd, const char *pathname, int flags, + struct file **target); int vfs_close(struct file *file); int vfs_write(struct file *file, const void *buf, size_t len); int vfs_read(struct file *file, void *buf, size_t len); int vfs_mkdir(const char *pathname); +int vfs_mkdir_relative(struct vnode *cwd, const char *pathname); int vfs_mount(const char *target, const char *filesystem); +int vfs_mount_relative(struct vnode *cwd, const char *target, + const char *filesystem); int vfs_lookup(const char *pathname, struct vnode **target); +int vfs_lookup_relative(struct vnode *cwd, const char *pathname, + struct vnode **target); + +typedef struct { + struct file *file; + size_t refcnt; +} shared_file_t; + +shared_file_t *shared_file_new(struct file *file); +shared_file_t *shared_file_clone(shared_file_t *shared_file); +void shared_file_drop(shared_file_t *shared_file); #endif diff --git a/lab7/c/include/oscos/sched.h b/lab7/c/include/oscos/sched.h index 560256f2c..bc77420e6 100644 --- a/lab7/c/include/oscos/sched.h +++ b/lab7/c/include/oscos/sched.h @@ -7,11 +7,14 @@ #include #include +#include "oscos/fs/vfs.h" #include "oscos/mem/types.h" #include "oscos/mem/vm.h" #include "oscos/uapi/signal.h" #include "oscos/xcpt/trap-frame.h" +#define N_FDS 16 + typedef struct thread_list_node_t { struct thread_list_node_t *prev, *next; } thread_list_node_t; @@ -60,6 +63,8 @@ typedef struct process_t { thread_t *main_thread; uint32_t pending_signals, blocked_signals; sighandler_t signal_handlers[32]; + struct vnode *cwd; + shared_file_t *fds[N_FDS]; } process_t; /// \brief Initializes the scheduler and creates the idle thread. diff --git a/lab7/c/include/oscos/uapi/errno.h b/lab7/c/include/oscos/uapi/errno.h index c0363fcee..b6c5c4da1 100644 --- a/lab7/c/include/oscos/uapi/errno.h +++ b/lab7/c/include/oscos/uapi/errno.h @@ -2,6 +2,7 @@ #define ESRCH 3 #define EINTR 4 #define EIO 5 +#define EBADF 9 #define ENOMEM 12 #define EBUSY 16 #define EEXIST 17 @@ -9,6 +10,7 @@ #define ENOTDIR 20 #define EISDIR 21 #define EINVAL 22 +#define EMFILE 24 #define EFBIG 27 #define ENOSYS 38 #define ELOOP 40 diff --git a/lab7/c/include/oscos/uapi/sys/syscall.h b/lab7/c/include/oscos/uapi/sys/syscall.h index 910ddcc61..8a64fa0ee 100644 --- a/lab7/c/include/oscos/uapi/sys/syscall.h +++ b/lab7/c/include/oscos/uapi/sys/syscall.h @@ -12,5 +12,12 @@ #define SYS_signal 8 #define SYS_signal_kill 9 #define SYS_sigreturn 10 +#define SYS_open 11 +#define SYS_close 12 +#define SYS_write 13 +#define SYS_read 14 +#define SYS_mkdir 15 +#define SYS_mount 16 +#define SYS_chdir 17 #endif diff --git a/lab7/c/src/fs/tmpfs.c b/lab7/c/src/fs/tmpfs.c index c07b2773d..180a9cb2f 100644 --- a/lab7/c/src/fs/tmpfs.c +++ b/lab7/c/src/fs/tmpfs.c @@ -14,6 +14,7 @@ typedef enum { TYPE_DIR, TYPE_FILE } tmpfs_internal_type_t; typedef struct { + struct vnode *parent; rb_node_t *contents; } tmpfs_internal_dir_data_t; @@ -108,7 +109,8 @@ static struct vnode *_tmpfs_create_file_vnode(struct mount *const mount) { return result; } -static struct vnode *_tmpfs_create_dir_vnode(struct mount *const mount) { +static struct vnode *_tmpfs_create_dir_vnode(struct mount *const mount, + struct vnode *const parent) { struct vnode *const result = malloc(sizeof(struct vnode)); if (!result) return NULL; @@ -119,9 +121,9 @@ static struct vnode *_tmpfs_create_dir_vnode(struct mount *const mount) { return NULL; } - *internal = (tmpfs_internal_t){ - .type = TYPE_DIR, - .dir_data = (tmpfs_internal_dir_data_t){.contents = NULL}}; + *internal = (tmpfs_internal_t){.type = TYPE_DIR, + .dir_data = (tmpfs_internal_dir_data_t){ + .parent = parent, .contents = NULL}}; *result = (struct vnode){.mount = mount, .v_ops = &_tmpfs_vnode_operations, .f_ops = &_tmpfs_file_operations, @@ -131,7 +133,7 @@ static struct vnode *_tmpfs_create_dir_vnode(struct mount *const mount) { static int _tmpfs_setup_mount(struct filesystem *const fs, struct mount *const mount) { - struct vnode *const root_vnode = _tmpfs_create_dir_vnode(mount); + struct vnode *const root_vnode = _tmpfs_create_dir_vnode(mount, NULL); if (!root_vnode) return -ENOMEM; @@ -254,6 +256,14 @@ static int _tmpfs_lookup(struct vnode *const dir_node, int result; tmpfs_internal_dir_data_t *const dir_data = &internal->dir_data; + if (strcmp(component_name, ".") == 0) { + *target = dir_node; + return 0; + } else if (strcmp(component_name, "..") == 0) { + *target = dir_data->parent; + return 0; + } + uint64_t daif_val; CRITICAL_SECTION_ENTER(daif_val); @@ -353,7 +363,8 @@ static int _tmpfs_mkdir(struct vnode *const dir_node, goto end; } - struct vnode *const vnode = _tmpfs_create_dir_vnode(dir_node->mount); + struct vnode *const vnode = + _tmpfs_create_dir_vnode(dir_node->mount, dir_node); if (!vnode) { result = -ENOMEM; goto end; diff --git a/lab7/c/src/fs/vfs.c b/lab7/c/src/fs/vfs.c index a23a4b605..15aec9946 100644 --- a/lab7/c/src/fs/vfs.c +++ b/lab7/c/src/fs/vfs.c @@ -96,9 +96,29 @@ int register_filesystem(struct filesystem *const fs) { return 0; } -static int _vfs_lookup_step(struct vnode *const curr_vnode, +static int _vfs_lookup_step(struct vnode *curr_vnode, struct vnode **const target, const char *const component_name) { + if (strcmp(component_name, "..") == 0 && + curr_vnode == curr_vnode->mount->root) { + if (curr_vnode == rootfs.root) { + *target = curr_vnode; + return 0; + } else { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const mount_entry_t *const entry = rb_search( + _mounts_by_root, curr_vnode, + (int (*)(const void *, const void *, void *))_vfs_cmp_root_and_mount, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + curr_vnode = entry->mountpoint; + } + } + const int result = curr_vnode->v_ops->lookup(curr_vnode, target, component_name); if (result < 0) @@ -125,12 +145,16 @@ static int _vfs_lookup_step(struct vnode *const curr_vnode, return 0; } -static int -_vfs_lookup_sans_last_level(const char *const pathname, - struct vnode **const target, - const char **const last_pathname_component) { - struct vnode *curr_vnode = rootfs.root; - const char *curr_pathname_component = pathname + 1; +static int _vfs_lookup_sans_last_level_relative( + struct vnode *const cwd, const char *const pathname, + struct vnode **const target, const char **const last_pathname_component) { + struct vnode *curr_vnode = cwd; + const char *curr_pathname_component = pathname; + if (*curr_pathname_component == '/') { + curr_vnode = rootfs.root; + curr_pathname_component += 1; + } + for (const char *curr_pathname_component_end; (curr_pathname_component_end = strchr(curr_pathname_component, '/')); curr_pathname_component = curr_pathname_component_end + 1) { @@ -156,10 +180,15 @@ _vfs_lookup_sans_last_level(const char *const pathname, int vfs_open(const char *const pathname, const int flags, struct file **const target) { + return vfs_open_relative(rootfs.root, pathname, flags, target); +} + +int vfs_open_relative(struct vnode *const cwd, const char *const pathname, + const int flags, struct file **const target) { const char *last_pathname_component; struct vnode *parent_vnode; - const int lookup_result = _vfs_lookup_sans_last_level( - pathname, &parent_vnode, &last_pathname_component); + const int lookup_result = _vfs_lookup_sans_last_level_relative( + cwd, pathname, &parent_vnode, &last_pathname_component); if (lookup_result < 0) return lookup_result; @@ -189,10 +218,14 @@ int vfs_read(struct file *const file, void *const buf, const size_t len) { } int vfs_mkdir(const char *const pathname) { + return vfs_mkdir_relative(rootfs.root, pathname); +} + +int vfs_mkdir_relative(struct vnode *const cwd, const char *const pathname) { const char *last_pathname_component; struct vnode *parent_vnode; - const int lookup_result = _vfs_lookup_sans_last_level( - pathname, &parent_vnode, &last_pathname_component); + const int lookup_result = _vfs_lookup_sans_last_level_relative( + cwd, pathname, &parent_vnode, &last_pathname_component); if (lookup_result < 0) return lookup_result; @@ -202,8 +235,13 @@ int vfs_mkdir(const char *const pathname) { } int vfs_mount(const char *const target, const char *const filesystem) { + return vfs_mount_relative(rootfs.root, target, filesystem); +} + +int vfs_mount_relative(struct vnode *const cwd, const char *const target, + const char *const filesystem) { struct vnode *mountpoint; - const int result = vfs_lookup(target, &mountpoint); + const int result = vfs_lookup_relative(cwd, target, &mountpoint); if (result < 0) return result; @@ -250,12 +288,57 @@ int vfs_mount(const char *const target, const char *const filesystem) { } int vfs_lookup(const char *const pathname, struct vnode **const target) { + return vfs_lookup_relative(rootfs.root, pathname, target); +} + +int vfs_lookup_relative(struct vnode *const cwd, const char *const pathname, + struct vnode **const target) { const char *last_pathname_component; struct vnode *parent_vnode; - const int lookup_result = _vfs_lookup_sans_last_level( - pathname, &parent_vnode, &last_pathname_component); + const int lookup_result = _vfs_lookup_sans_last_level_relative( + cwd, pathname, &parent_vnode, &last_pathname_component); if (lookup_result < 0) return lookup_result; - return _vfs_lookup_step(parent_vnode, target, last_pathname_component); + if (*last_pathname_component == '\0') { + *target = parent_vnode; + return 0; + } else { + return _vfs_lookup_step(parent_vnode, target, last_pathname_component); + } +} + +shared_file_t *shared_file_new(struct file *const file) { + shared_file_t *const shared_file = malloc(sizeof(shared_file_t)); + if (!shared_file) + return NULL; + + *shared_file = (shared_file_t){.file = file, .refcnt = 1}; + return shared_file; +} + +shared_file_t *shared_file_clone(shared_file_t *const shared_file) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + shared_file->refcnt++; + + CRITICAL_SECTION_LEAVE(daif_val); + + return shared_file; +} + +void shared_file_drop(shared_file_t *const shared_file) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + shared_file->refcnt--; + const bool drop = shared_file->refcnt == 0; + + CRITICAL_SECTION_LEAVE(daif_val); + + if (drop) { + vfs_close(shared_file->file); + free(shared_file); + } } diff --git a/lab7/c/src/mem/malloc.c b/lab7/c/src/mem/malloc.c index 5819828d0..87cacb7b2 100644 --- a/lab7/c/src/mem/malloc.c +++ b/lab7/c/src/mem/malloc.c @@ -270,7 +270,7 @@ static void _free_to_slab(slab_t *const slab, void *const ptr) { // Mark the slot as available. slab->n_slots_reserved--; - slab->slots_reserved_bitset[slot_ix / 64] &= ~(1 << (slot_ix % 64)); + slab->slots_reserved_bitset[slot_ix / 64] &= ~(1ULL << (slot_ix % 64)); // Return the slab to the page frame allocator if it has no reserved slots. diff --git a/lab7/c/src/sched/sched.c b/lab7/c/src/sched/sched.c index 078c80425..e0147ff20 100644 --- a/lab7/c/src/sched/sched.c +++ b/lab7/c/src/sched/sched.c @@ -252,6 +252,10 @@ bool process_create(void) { for (size_t i = 0; i < 32; i++) { process->signal_handlers[i] = SIG_DFL; } + process->cwd = rootfs.root; + for (size_t i = 0; i < N_FDS; i++) { + process->fds[i] = NULL; + } curr_thread->process = process; curr_thread->ctx.fp_simd_ctx = fp_simd_ctx; @@ -331,6 +335,11 @@ void exec(const void *const text_start, const size_t text_len) { static void _thread_cleanup(thread_t *const thread) { if (thread->process) { vm_drop_pgd(thread->process->addr_space.pgd); + for (size_t i = 0; i < N_FDS; i++) { + if (thread->process->fds[i]) { + shared_file_drop(thread->process->fds[i]); + } + } free(thread->process); } free_pages(thread->stack_page_id); @@ -388,6 +397,11 @@ process_t *fork(const extended_trap_frame_t *const trap_frame) { new_process->blocked_signals = 0; memcpy(new_process->signal_handlers, curr_process->signal_handlers, 32 * sizeof(sighandler_t)); + new_process->cwd = curr_process->cwd; + for (size_t i = 0; i < N_FDS; i++) { + new_process->fds[i] = + curr_process->fds[i] ? shared_file_clone(curr_process->fds[i]) : NULL; + } // Set execution context. diff --git a/lab7/c/src/shell.c b/lab7/c/src/shell.c index 53b2fdaf2..b95a0981b 100644 --- a/lab7/c/src/shell.c +++ b/lab7/c/src/shell.c @@ -517,6 +517,92 @@ static void _shell_do_cmd_vfs_test_3(void) { return; } +static void _shell_do_cmd_vfs_test_4(void) { + char buf[11] = {0}; + struct file *foo, *bar; + int result; + + result = vfs_mkdir("/dir"); + console_printf("mkdir(\"/dir\") = %d\n", result); + if (result < 0) + return; + + result = vfs_mount("/dir", "tmpfs"); + console_printf("mount(\"/dir\", \"tmpfs\") = %d\n", result); + if (result < 0) + return; + + result = vfs_mkdir("/dir/dir"); + console_printf("mkdir(\"/dir/dir\") = %d\n", result); + if (result < 0) + return; + + result = vfs_open("/dir/foo", O_CREAT, &foo); + console_printf("open(\"/dir/foo\", O_CREAT) = %d\n", result); + if (result < 0) + return; + + result = vfs_write(foo, "aaaaa", 5); + console_printf("write(\"/dir/foo\", \"aaaaa\") = %d\n", result); + if (result < 0) + return; + + result = vfs_close(foo); + console_printf("close(\"/dir/foo\") = %d\n", result); + if (result < 0) + return; + + result = vfs_open("/dir/bar", O_CREAT, &bar); + console_printf("open(\"/dir/bar\", O_CREAT) = %d\n", result); + if (result < 0) + return; + + result = vfs_write(bar, "bbbbb", 5); + console_printf("write(\"/dir/bar\", \"bbbbb\") = %d\n", result); + if (result < 0) + return; + + result = vfs_close(bar); + console_printf("close(\"/dir/bar\") = %d\n", result); + if (result < 0) + return; + + result = vfs_open("/dir/./dir/../../dir/./foo", O_CREAT, &foo); + console_printf("open(\"/dir/./dir/../../dir/./foo\", O_CREAT) = %d\n", + result); + if (result < 0) + return; + + result = vfs_open("/dir/./dir/../../dir/./bar", O_CREAT, &bar); + console_printf("open(\"/dir/./dir/../../dir/./bar\", O_CREAT) = %d\n", + result); + if (result < 0) + return; + + result = vfs_read(foo, buf, 10); + console_printf("read(\"/dir/./dir/../../dir/./foo\", ...) = %d\n", result); + if (result < 0) + return; + console_puts(buf); + + memset(buf, 0, 10); + result = vfs_read(bar, buf, 10); + console_printf("read(\"/dir/./dir/../../dir/./bar\", ...) = %d\n", result); + if (result < 0) + return; + console_puts(buf); + + result = vfs_close(foo); + console_printf("close(\"/dir/./dir/../../dir/./foo\") = %d\n", result); + if (result < 0) + return; + + result = vfs_close(bar); + console_printf("close(\"/dir/./dir/../../dir/./bar\") = %d\n", result); + if (result < 0) + return; +} + static void _shell_cmd_not_found(const char *const cmd) { console_printf("oscsh: %s: command not found\n", cmd); } @@ -557,6 +643,8 @@ void run_shell(void) { _shell_do_cmd_vfs_test_2(); } else if (strcmp(cmd_buf, "vfs-test-3") == 0) { _shell_do_cmd_vfs_test_3(); + } else if (strcmp(cmd_buf, "vfs-test-4") == 0) { + _shell_do_cmd_vfs_test_4(); } else { _shell_cmd_not_found(cmd_buf); } diff --git a/lab7/c/src/xcpt/svc-handler.S b/lab7/c/src/xcpt/svc-handler.S index 434fe9ab2..3ce4f5949 100644 --- a/lab7/c/src/xcpt/svc-handler.S +++ b/lab7/c/src/xcpt/svc-handler.S @@ -15,7 +15,7 @@ xcpt_svc_handler: // Check the system call number. ubfx x9, x9, 0, 16 cbnz x9, .Lenosys - cmp x8, 11 + cmp x8, 17 b.hi .Lenosys // Table-jump to the system call function. diff --git a/lab7/c/src/xcpt/syscall-table.S b/lab7/c/src/xcpt/syscall-table.S index 396f9be11..685bf88a0 100644 --- a/lab7/c/src/xcpt/syscall-table.S +++ b/lab7/c/src/xcpt/syscall-table.S @@ -12,6 +12,13 @@ syscall_table: b sys_signal b sys_signal_kill b sys_sigreturn + b sys_open + b sys_close + b sys_write + b sys_read + b sys_mkdir + b sys_mount + b sys_chdir .size syscall_table, . - syscall_table .global syscall_table diff --git a/lab7/c/src/xcpt/syscall/chdir.c b/lab7/c/src/xcpt/syscall/chdir.c new file mode 100644 index 000000000..6cc5b5e8b --- /dev/null +++ b/lab7/c/src/xcpt/syscall/chdir.c @@ -0,0 +1,14 @@ +#include "oscos/fs/vfs.h" +#include "oscos/sched.h" + +int sys_chdir(const char *const path) { + process_t *const curr_process = current_thread()->process; + + struct vnode *next_cwd; + const int result = vfs_lookup_relative(curr_process->cwd, path, &next_cwd); + if (result < 0) + return result; + + curr_process->cwd = next_cwd; + return 0; +} diff --git a/lab7/c/src/xcpt/syscall/close.c b/lab7/c/src/xcpt/syscall/close.c new file mode 100644 index 000000000..05a41f3a0 --- /dev/null +++ b/lab7/c/src/xcpt/syscall/close.c @@ -0,0 +1,15 @@ +#include "oscos/fs/vfs.h" +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" + +int sys_close(const int fd) { + process_t *const curr_process = current_thread()->process; + + if (!(0 <= fd && fd < N_FDS && curr_process->fds[fd])) + return -EBADF; + + shared_file_drop(curr_process->fds[fd]); + curr_process->fds[fd] = NULL; + + return 0; +} diff --git a/lab7/c/src/xcpt/syscall/mkdir.c b/lab7/c/src/xcpt/syscall/mkdir.c new file mode 100644 index 000000000..294917289 --- /dev/null +++ b/lab7/c/src/xcpt/syscall/mkdir.c @@ -0,0 +1,10 @@ +#include "oscos/fs/vfs.h" +#include "oscos/sched.h" + +int sys_mkdir(const char *const pathname, const unsigned mode) { + (void)mode; + + process_t *const curr_process = current_thread()->process; + + return vfs_mkdir_relative(curr_process->cwd, pathname); +} diff --git a/lab7/c/src/xcpt/syscall/mount.c b/lab7/c/src/xcpt/syscall/mount.c new file mode 100644 index 000000000..cb9c764d6 --- /dev/null +++ b/lab7/c/src/xcpt/syscall/mount.c @@ -0,0 +1,14 @@ +#include "oscos/fs/vfs.h" +#include "oscos/sched.h" + +int sys_mount(const char *const src, const char *const target, + const char *const filesystem, const unsigned long flags, + const void *const data) { + (void)src; + (void)flags; + (void)data; + + process_t *const curr_process = current_thread()->process; + + return vfs_mount_relative(curr_process->cwd, target, filesystem); +} diff --git a/lab7/c/src/xcpt/syscall/open.c b/lab7/c/src/xcpt/syscall/open.c new file mode 100644 index 000000000..81c6048f0 --- /dev/null +++ b/lab7/c/src/xcpt/syscall/open.c @@ -0,0 +1,34 @@ +#include "oscos/fs/vfs.h" +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" + +int sys_open(const char *const pathname, const int flags) { + process_t *const curr_process = current_thread()->process; + + // Find the first available FD. + + size_t fd; + for (fd = 0; fd < N_FDS && curr_process->fds[fd]; fd++) + ; + + if (fd == N_FDS) // File descriptors used up. + return -EMFILE; + + // Open the file. + + struct file *file; + const int result = + vfs_open_relative(curr_process->cwd, pathname, flags, &file); + if (result < 0) + return result; + + shared_file_t *const shared_file = shared_file_new(file); + if (!shared_file) { + vfs_close(file); + return -ENOMEM; + } + + curr_process->fds[fd] = shared_file; + + return 0; +} diff --git a/lab7/c/src/xcpt/syscall/read.c b/lab7/c/src/xcpt/syscall/read.c new file mode 100644 index 000000000..095fdf3cb --- /dev/null +++ b/lab7/c/src/xcpt/syscall/read.c @@ -0,0 +1,12 @@ +#include "oscos/fs/vfs.h" +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" + +long sys_read(const int fd, void *const buf, const unsigned long count) { + process_t *const curr_process = current_thread()->process; + + if (!(0 <= fd && fd < N_FDS && curr_process->fds[fd])) + return -EBADF; + + return vfs_read(curr_process->fds[fd]->file, buf, count); +} diff --git a/lab7/c/src/xcpt/syscall/write.c b/lab7/c/src/xcpt/syscall/write.c new file mode 100644 index 000000000..1452ec32a --- /dev/null +++ b/lab7/c/src/xcpt/syscall/write.c @@ -0,0 +1,12 @@ +#include "oscos/fs/vfs.h" +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" + +long sys_write(const int fd, const void *const buf, const unsigned long count) { + process_t *const curr_process = current_thread()->process; + + if (!(0 <= fd && fd < N_FDS && curr_process->fds[fd])) + return -EBADF; + + return vfs_write(curr_process->fds[fd]->file, buf, count); +} diff --git a/lab7/tests/build-initrd.sh b/lab7/tests/build-initrd.sh index 88be4059b..7ee63fafb 100755 --- a/lab7/tests/build-initrd.sh +++ b/lab7/tests/build-initrd.sh @@ -3,8 +3,8 @@ rm -rf rootfs mkdir rootfs -wget https://oscapstone.github.io/_downloads/4a3ff2431ab7fa74536c184270dbe5c0/vm.img \ - -O rootfs/vm.img +wget https://oscapstone.github.io/_downloads/3cb3bdb8f851d1cf29ac6f4f5d585981/vfs1.img \ + -O rootfs/vfs1.img cd rootfs find . -mindepth 1 | cpio -o -H newc > ../initramfs.cpio diff --git a/lab7/tests/user-program/libc/Makefile b/lab7/tests/user-program/libc/Makefile index f21080034..37456b1f1 100644 --- a/lab7/tests/user-program/libc/Makefile +++ b/lab7/tests/user-program/libc/Makefile @@ -16,8 +16,8 @@ CFLAGS_BASE = -std=c17 -pedantic-errors -Wall -Wextra -ffreestanding \ CFLAGS_DEBUG = -g CFLAGS_RELEASE = -O3 -flto -OBJS = start ctype errno mbox signal stdio stdlib string unistd \ - unistd/syscall __detail/utils/fmt +OBJS = start ctype errno fcntl mbox signal stdio stdlib string sys/mount \ + sys/stat unistd unistd/syscall __detail/utils/fmt # ------------------------------------------------------------------------------ diff --git a/lab7/tests/user-program/libc/include/fcntl.h b/lab7/tests/user-program/libc/include/fcntl.h new file mode 100644 index 000000000..bae3c7e89 --- /dev/null +++ b/lab7/tests/user-program/libc/include/fcntl.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_FCNTL_H +#define OSCOS_USER_PROGRAM_LIBC_FCNTL_H + +#include "oscos-uapi/fcntl.h" + +int open(const char *pathname, int flags); + +#endif diff --git a/lab7/tests/user-program/libc/include/sys/mount.h b/lab7/tests/user-program/libc/include/sys/mount.h new file mode 100644 index 000000000..e1d968293 --- /dev/null +++ b/lab7/tests/user-program/libc/include/sys/mount.h @@ -0,0 +1,7 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_SYS_MOUNT_H +#define OSCOS_USER_PROGRAM_LIBC_SYS_MOUNT_H + +int mount(const char *source, const char *target, const char *filesystemtype, + unsigned long mountflags, const void *data); + +#endif diff --git a/lab7/tests/user-program/libc/include/sys/stat.h b/lab7/tests/user-program/libc/include/sys/stat.h new file mode 100644 index 000000000..ae5eb2e06 --- /dev/null +++ b/lab7/tests/user-program/libc/include/sys/stat.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_SYS_STAT_H +#define OSCOS_USER_PROGRAM_LIBC_SYS_STAT_H + +typedef unsigned mode_t; + +int mkdir(const char *pathname, mode_t mode); + +#endif diff --git a/lab7/tests/user-program/libc/include/unistd.h b/lab7/tests/user-program/libc/include/unistd.h index 01ea5ac4b..3ad6ec7c7 100644 --- a/lab7/tests/user-program/libc/include/unistd.h +++ b/lab7/tests/user-program/libc/include/unistd.h @@ -16,6 +16,13 @@ int exec(const char *pathname, char *const argv[]); pid_t fork(void); +int close(int fd); + +ssize_t write(int fd, const void *buf, size_t count); +ssize_t read(int fd, void *buf, size_t count); + +int chdir(const char *path); + long syscall(long number, ...); #endif diff --git a/lab7/tests/user-program/libc/src/fcntl.c b/lab7/tests/user-program/libc/src/fcntl.c new file mode 100644 index 000000000..5829cf1b1 --- /dev/null +++ b/lab7/tests/user-program/libc/src/fcntl.c @@ -0,0 +1,8 @@ +#include "fcntl.h" + +#include "sys/syscall.h" +#include "unistd.h" + +int open(const char *const pathname, const int flags) { + return syscall(SYS_open, pathname, flags); +} diff --git a/lab7/tests/user-program/libc/src/sys/mount.c b/lab7/tests/user-program/libc/src/sys/mount.c new file mode 100644 index 000000000..ca787c16e --- /dev/null +++ b/lab7/tests/user-program/libc/src/sys/mount.c @@ -0,0 +1,10 @@ +#include "sys/mount.h" + +#include "sys/syscall.h" +#include "unistd.h" + +int mount(const char *const source, const char *const target, + const char *const filesystemtype, const unsigned long mountflags, + const void *const data) { + return syscall(SYS_mount, source, target, filesystemtype, mountflags, data); +} diff --git a/lab7/tests/user-program/libc/src/sys/stat.c b/lab7/tests/user-program/libc/src/sys/stat.c new file mode 100644 index 000000000..bbf6ca131 --- /dev/null +++ b/lab7/tests/user-program/libc/src/sys/stat.c @@ -0,0 +1,8 @@ +#include "sys/stat.h" + +#include "sys/syscall.h" +#include "unistd.h" + +int mkdir(const char *const pathname, const mode_t mode) { + return syscall(SYS_mkdir, pathname, mode); +} diff --git a/lab7/tests/user-program/libc/src/unistd.c b/lab7/tests/user-program/libc/src/unistd.c index ba4be2c69..a5ca260aa 100644 --- a/lab7/tests/user-program/libc/src/unistd.c +++ b/lab7/tests/user-program/libc/src/unistd.c @@ -17,3 +17,15 @@ int exec(const char *const pathname, char *const argv[const]) { } pid_t fork(void) { return syscall(SYS_fork); } + +int close(int fd) { return syscall(SYS_close, fd); } + +ssize_t write(const int fd, const void *const buf, const size_t count) { + return syscall(SYS_write, fd, buf, count); +} + +ssize_t read(const int fd, void *const buf, const size_t count) { + return syscall(SYS_read, fd, buf, count); +} + +int chdir(const char *const path) { return syscall(SYS_chdir, path); } From 0a1b274a1a032c488d640a82853f88f99e441a26 Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Thu, 29 Jun 2023 07:14:30 +0800 Subject: [PATCH 18/34] Lab 7 C: Complete B4 --- lab7/c/Makefile | 2 +- lab7/c/include/oscos/fs/initramfs.h | 8 + lab7/c/include/oscos/libc/string.h | 2 + lab7/c/include/oscos/uapi/errno.h | 1 + lab7/c/src/fs/initramfs.c | 333 ++++++++++++++++++++++++++++ lab7/c/src/libc/string.c | 10 + lab7/c/src/main.c | 23 +- lab7/c/src/shell.c | 27 +++ 8 files changed, 403 insertions(+), 3 deletions(-) create mode 100644 lab7/c/include/oscos/fs/initramfs.h create mode 100644 lab7/c/src/fs/initramfs.c diff --git a/lab7/c/Makefile b/lab7/c/Makefile index 6b3f6bda3..fb902eca7 100644 --- a/lab7/c/Makefile +++ b/lab7/c/Makefile @@ -24,7 +24,7 @@ LDLIBS_BASE = -lgcc OBJS = start main console devicetree initrd panic shell \ drivers/aux drivers/gpio drivers/l1ic drivers/l2ic drivers/mailbox \ drivers/mini-uart drivers/pm \ - fs/tmpfs fs/vfs \ + fs/initramfs fs/tmpfs fs/vfs \ mem/malloc mem/page-alloc mem/shared-page mem/startup-alloc mem/vm \ mem/vm/kernel-page-tables \ sched/idle-thread sched/periodic-sched sched/run-signal-handler \ diff --git a/lab7/c/include/oscos/fs/initramfs.h b/lab7/c/include/oscos/fs/initramfs.h new file mode 100644 index 000000000..8f72722ed --- /dev/null +++ b/lab7/c/include/oscos/fs/initramfs.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_FS_INITRAMFS_H +#define OSCOS_FS_INITRAMFS_H + +#include "oscos/fs/vfs.h" + +extern struct filesystem initramfs; + +#endif diff --git a/lab7/c/include/oscos/libc/string.h b/lab7/c/include/oscos/libc/string.h index 654a00164..e57f17902 100644 --- a/lab7/c/include/oscos/libc/string.h +++ b/lab7/c/include/oscos/libc/string.h @@ -18,6 +18,8 @@ char *strndup(const char *s, size_t n); char *strchr(const char *s, int c); +char *strcpy(char *restrict dst, const char *restrict src); + // Extensions. /// \brief Swap two non-overlapping blocks of memory. diff --git a/lab7/c/include/oscos/uapi/errno.h b/lab7/c/include/oscos/uapi/errno.h index b6c5c4da1..d0b2e9a5d 100644 --- a/lab7/c/include/oscos/uapi/errno.h +++ b/lab7/c/include/oscos/uapi/errno.h @@ -12,5 +12,6 @@ #define EINVAL 22 #define EMFILE 24 #define EFBIG 27 +#define EROFS 30 #define ENOSYS 38 #define ELOOP 40 diff --git a/lab7/c/src/fs/initramfs.c b/lab7/c/src/fs/initramfs.c new file mode 100644 index 000000000..373dc65d5 --- /dev/null +++ b/lab7/c/src/fs/initramfs.c @@ -0,0 +1,333 @@ +#include "oscos/fs/initramfs.h" + +#include "oscos/initrd.h" +#include "oscos/libc/string.h" +#include "oscos/mem/malloc.h" +#include "oscos/uapi/errno.h" +#include "oscos/uapi/stdio.h" +#include "oscos/utils/critical-section.h" +#include "oscos/utils/rb.h" + +typedef struct { + const char *component_name; + struct vnode *vnode; +} initramfs_child_vnode_entry_t; + +typedef struct { + struct vnode *parent; + const cpio_newc_entry_t *entry; + rb_node_t *child_vnodes; // Created lazily. +} initramfs_internal_t; + +static int _initramfs_setup_mount(struct filesystem *fs, struct mount *mount); + +static int _initramfs_write(struct file *file, const void *buf, size_t len); +static int _initramfs_read(struct file *file, void *buf, size_t len); +static int _initramfs_open(struct vnode *file_node, struct file **target); +static int _initramfs_close(struct file *file); +static long _initramfs_lseek64(struct file *file, long offset, int whence); + +static int _initramfs_lookup(struct vnode *dir_node, struct vnode **target, + const char *component_name); +static int _initramfs_create(struct vnode *dir_node, struct vnode **target, + const char *component_name); +static int _initramfs_mkdir(struct vnode *dir_node, struct vnode **target, + const char *component_name); + +struct filesystem initramfs = {.name = "initramfs", + .setup_mount = _initramfs_setup_mount}; + +static struct file_operations _initramfs_file_operations = { + .write = _initramfs_write, + .read = _initramfs_read, + .open = _initramfs_open, + .close = _initramfs_close, + .lseek64 = _initramfs_lseek64}; + +static struct vnode_operations _initramfs_vnode_operations = { + .lookup = _initramfs_lookup, + .create = _initramfs_create, + .mkdir = _initramfs_mkdir}; + +static int _initramfs_cmp_child_vnode_entries_by_component_name( + const initramfs_child_vnode_entry_t *const e1, + const initramfs_child_vnode_entry_t *const e2, void *const _arg) { + (void)_arg; + + return strcmp(e1->component_name, e2->component_name); +} + +static int _initramfs_cmp_component_name_and_child_vnode_entry( + const char *component_name, + const initramfs_child_vnode_entry_t *const entry, void *const _arg) { + (void)_arg; + + return strcmp(component_name, entry->component_name); +} + +static struct vnode * +_initramfs_create_vnode(struct mount *const mount, struct vnode *const parent, + const cpio_newc_entry_t *const entry) { + struct vnode *const result = malloc(sizeof(struct vnode)); + if (!result) + return NULL; + + initramfs_internal_t *const internal = malloc(sizeof(initramfs_internal_t)); + if (!internal) { + free(result); + return NULL; + } + + *internal = (initramfs_internal_t){ + .parent = parent, .entry = entry, .child_vnodes = NULL}; + *result = (struct vnode){.mount = mount, + .v_ops = &_initramfs_vnode_operations, + .f_ops = &_initramfs_file_operations, + .internal = internal}; + return result; +} + +static int _initramfs_setup_mount(struct filesystem *const fs, + struct mount *const mount) { + struct vnode *const root_vnode = _initramfs_create_vnode(mount, NULL, NULL); + if (!root_vnode) + return -ENOMEM; + + *mount = (struct mount){.fs = fs, .root = root_vnode}; + + return 0; +} + +static int _initramfs_write(struct file *const file, const void *const buf, + const size_t len) { + (void)file; + (void)buf; + (void)len; + + return -EROFS; +} + +static int _initramfs_read(struct file *const file, void *const buf, + const size_t len) { + initramfs_internal_t *const internal = + (initramfs_internal_t *)file->vnode->internal; + const cpio_newc_entry_t *const entry = internal->entry; + + const uint32_t mode = CPIO_NEWC_HEADER_VALUE(entry, mode); + const uint32_t file_type = mode & CPIO_NEWC_MODE_FILE_TYPE_MASK; + if (file_type == CPIO_NEWC_MODE_FILE_TYPE_REG) { + // No-op. + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_DIR) { + return -EISDIR; + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_LNK) { + return -ELOOP; + } else { // Unknown file type. + return -EIO; + } + + const size_t file_size = CPIO_NEWC_FILESIZE(entry); + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const size_t remaining_len = + file->f_pos >= file_size ? 0 : file_size - file->f_pos, + cpy_len = len < remaining_len ? len : remaining_len; + + memcpy(buf, CPIO_NEWC_FILE_DATA(entry) + file->f_pos, cpy_len); + file->f_pos += cpy_len; + + CRITICAL_SECTION_LEAVE(daif_val); + + return cpy_len; +} + +static int _initramfs_open(struct vnode *const file_node, + struct file **const target) { + initramfs_internal_t *const internal = + (initramfs_internal_t *)file_node->internal; + const cpio_newc_entry_t *const entry = internal->entry; + + const uint32_t mode = CPIO_NEWC_HEADER_VALUE(entry, mode); + const uint32_t file_type = mode & CPIO_NEWC_MODE_FILE_TYPE_MASK; + if (file_type == CPIO_NEWC_MODE_FILE_TYPE_REG) { + // No-op. + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_DIR) { + return -EISDIR; + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_LNK) { + return -ELOOP; + } else { // Unknown file type. + return -EIO; + } + + struct file *const file_handle = malloc(sizeof(struct file)); + if (!file_handle) + return -ENOMEM; + + *file_handle = (struct file){.vnode = file_node, + .f_pos = 0, + .f_ops = &_initramfs_file_operations, + .flags = 0}; + *target = file_handle; + + return 0; +} + +static int _initramfs_close(struct file *const file) { + free(file); + return 0; +} + +static long _initramfs_lseek64(struct file *const file, const long offset, + const int whence) { + initramfs_internal_t *const internal = + (initramfs_internal_t *)file->vnode->internal; + const cpio_newc_entry_t *const entry = internal->entry; + + const uint32_t mode = CPIO_NEWC_HEADER_VALUE(entry, mode); + const uint32_t file_type = mode & CPIO_NEWC_MODE_FILE_TYPE_MASK; + if (file_type == CPIO_NEWC_MODE_FILE_TYPE_REG) { + // No-op. + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_DIR) { + return -EISDIR; + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_LNK) { + return -ELOOP; + } else { // Unknown file type. + return -EIO; + } + + if (!(whence == SEEK_SET || whence == SEEK_CUR || whence == SEEK_END)) + return -EINVAL; + + const long f_pos_base = whence == SEEK_SET ? 0 + : whence == SEEK_CUR ? file->f_pos + : CPIO_NEWC_FILESIZE(entry), + new_f_pos = f_pos_base + offset; + + if (new_f_pos < 0) { + return -EINVAL; + } else { + file->f_pos = new_f_pos; + return 0; + } +} + +static int _initramfs_lookup(struct vnode *const dir_node, + struct vnode **const target, + const char *const component_name) { + initramfs_internal_t *const internal = + (initramfs_internal_t *)dir_node->internal; + const cpio_newc_entry_t *const entry = internal->entry; + + if (dir_node != dir_node->mount->root) { // The vnode of the root directory + // doesn't have an entry. + const uint32_t mode = CPIO_NEWC_HEADER_VALUE(entry, mode); + const uint32_t file_type = mode & CPIO_NEWC_MODE_FILE_TYPE_MASK; + if (file_type == CPIO_NEWC_MODE_FILE_TYPE_REG) { + return -ENOTDIR; + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_DIR) { + // No-op. + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_LNK) { + return -ELOOP; + } else { // Unknown file type. + return -EIO; + } + } + + if (strcmp(component_name, ".") == 0) { + *target = dir_node; + return 0; + } else if (strcmp(component_name, "..") == 0) { + *target = internal->parent; + return 0; + } + + // Check if the vnode has been created before. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const initramfs_child_vnode_entry_t *const child_vnode_entry = rb_search( + internal->child_vnodes, component_name, + (int (*)(const void *, const void *, + void *))_initramfs_cmp_component_name_and_child_vnode_entry, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + if (child_vnode_entry) { + *target = child_vnode_entry->vnode; + return 0; + } + + // A vnode has not been created before. Check if the file/directory exists. + + size_t dirname_len; + const cpio_newc_entry_t *child_initrd_entry; + + if (dir_node != dir_node->mount->root) { + dirname_len = strlen(CPIO_NEWC_PATHNAME(entry)); + char *const filename_buf = + malloc(dirname_len + 1 + strlen(component_name) + 1); + if (!filename_buf) + return -ENOMEM; + memcpy(filename_buf, CPIO_NEWC_PATHNAME(entry), dirname_len); + filename_buf[dirname_len] = '/'; + strcpy(filename_buf + dirname_len + 1, component_name); + + child_initrd_entry = initrd_find_entry_by_pathname(filename_buf); + free(filename_buf); + } else { + child_initrd_entry = initrd_find_entry_by_pathname(component_name); + } + if (!child_initrd_entry) { + return -ENOENT; + } + + // Create a new vnode. + + struct vnode *const vnode = + _initramfs_create_vnode(dir_node->mount, dir_node, child_initrd_entry); + if (!vnode) + return -ENOMEM; + + const initramfs_child_vnode_entry_t new_child_vnode_entry = { + .component_name = + CPIO_NEWC_PATHNAME(child_initrd_entry) + + (dir_node != dir_node->mount->root ? dirname_len + 1 : 0), + .vnode = vnode}; + + CRITICAL_SECTION_ENTER(daif_val); + + rb_insert(&internal->child_vnodes, sizeof(initramfs_child_vnode_entry_t), + &new_child_vnode_entry, + (int (*)(const void *, const void *, void *)) + _initramfs_cmp_child_vnode_entries_by_component_name, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + *target = vnode; + + return 0; +} + +static int _initramfs_create(struct vnode *const dir_node, + struct vnode **const target, + const char *const component_name) { + (void)dir_node; + (void)target; + (void)component_name; + + return -EROFS; +} + +static int _initramfs_mkdir(struct vnode *const dir_node, + struct vnode **const target, + const char *const component_name) { + (void)dir_node; + (void)target; + (void)component_name; + + return -EROFS; +} diff --git a/lab7/c/src/libc/string.c b/lab7/c/src/libc/string.c index 3ea2156fa..6451926c4 100644 --- a/lab7/c/src/libc/string.c +++ b/lab7/c/src/libc/string.c @@ -128,6 +128,16 @@ char *strchr(const char *const s, const int c) { return NULL; } +char *strcpy(char *const restrict dst, const char *const restrict src) { + char *restrict cd = dst; + const char *restrict cs = src; + while (*cs) { + *cd++ = *cs++; + } + + return dst; +} + void memswp(void *const restrict xs, void *const restrict ys, const size_t n) { unsigned char *const restrict xs_c = xs, *const restrict ys_c = ys; for (size_t i = 0; i < n; i++) { diff --git a/lab7/c/src/main.c b/lab7/c/src/main.c index ec29abe8f..cdf681d76 100644 --- a/lab7/c/src/main.c +++ b/lab7/c/src/main.c @@ -6,6 +6,7 @@ #include "oscos/drivers/l2ic.h" #include "oscos/drivers/mailbox.h" #include "oscos/drivers/pm.h" +#include "oscos/fs/initramfs.h" #include "oscos/fs/tmpfs.h" #include "oscos/fs/vfs.h" #include "oscos/initrd.h" @@ -65,8 +66,26 @@ void main(const void *const dtb_start) { } // Initialize VFS. - register_filesystem(&tmpfs); - tmpfs.setup_mount(&tmpfs, &rootfs); + + int vfs_op_result; + + vfs_op_result = register_filesystem(&tmpfs); + if (vfs_op_result < 0) + PANIC("Cannot register tmpfs: errno %d", -vfs_op_result); + vfs_op_result = register_filesystem(&initramfs); + if (vfs_op_result < 0) + PANIC("Cannot register initramfs: errno %d", -vfs_op_result); + + vfs_op_result = tmpfs.setup_mount(&tmpfs, &rootfs); + if (vfs_op_result < 0) + PANIC("Cannot setup root file system: errno %d", -vfs_op_result); + + vfs_op_result = vfs_mkdir("/initramfs"); + if (vfs_op_result < 0) + PANIC("Cannot mkdir /initramfs: errno %d", -vfs_op_result); + vfs_op_result = vfs_mount("/initramfs", "initramfs"); + if (vfs_op_result < 0) + PANIC("Cannot mount initramfs on /initramfs: errno %d", -vfs_op_result); thread_create(_run_shell, NULL); diff --git a/lab7/c/src/shell.c b/lab7/c/src/shell.c index b95a0981b..7ac05bb53 100644 --- a/lab7/c/src/shell.c +++ b/lab7/c/src/shell.c @@ -603,6 +603,31 @@ static void _shell_do_cmd_vfs_test_4(void) { return; } +static void _shell_do_cmd_vfs_test_5(void) { + unsigned char buf[8] = {0}; + struct file *vfs1; + int result; + + result = vfs_open("/initramfs/vfs1.img", 0, &vfs1); + console_printf("open(\"/initramfs/vfs1.img\", 0) = %d\n", result); + if (result < 0) + return; + + result = vfs_read(vfs1, buf, 8); + console_printf("read(\"/initramfs/vfs1.img\", ...) = %d\n", result); + if (result < 0) + return; + for (size_t i = 0; i < 8; i++) { + console_printf("\\x%02hhx", buf[i]); + } + console_putc('\n'); + + result = vfs_close(vfs1); + console_printf("close(\"/initramfs/vfs1.img\") = %d\n", result); + if (result < 0) + return; +} + static void _shell_cmd_not_found(const char *const cmd) { console_printf("oscsh: %s: command not found\n", cmd); } @@ -645,6 +670,8 @@ void run_shell(void) { _shell_do_cmd_vfs_test_3(); } else if (strcmp(cmd_buf, "vfs-test-4") == 0) { _shell_do_cmd_vfs_test_4(); + } else if (strcmp(cmd_buf, "vfs-test-5") == 0) { + _shell_do_cmd_vfs_test_5(); } else { _shell_cmd_not_found(cmd_buf); } From 27b439403d32f8608e42416180021ef899be478a Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Thu, 29 Jun 2023 10:34:16 +0800 Subject: [PATCH 19/34] Lab 6 C: Fix `free` Fix a bug in `free` that causes an incorrect slot to be freed. Backported from 8a64b30. --- lab6/c/src/mem/malloc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lab6/c/src/mem/malloc.c b/lab6/c/src/mem/malloc.c index 5819828d0..87cacb7b2 100644 --- a/lab6/c/src/mem/malloc.c +++ b/lab6/c/src/mem/malloc.c @@ -270,7 +270,7 @@ static void _free_to_slab(slab_t *const slab, void *const ptr) { // Mark the slot as available. slab->n_slots_reserved--; - slab->slots_reserved_bitset[slot_ix / 64] &= ~(1 << (slot_ix % 64)); + slab->slots_reserved_bitset[slot_ix / 64] &= ~(1ULL << (slot_ix % 64)); // Return the slab to the page frame allocator if it has no reserved slots. From ee7f8b5eefb877a9b6ffec58edfd3472f0d3e6ac Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Thu, 29 Jun 2023 10:40:38 +0800 Subject: [PATCH 20/34] Lab 8 C: Import kernel from lab 7 --- lab8/c/.clang-format | 1 + lab8/c/.gitignore | 1 + lab8/c/Makefile | 109 +++ lab8/c/include/oscos/console.h | 144 ++++ lab8/c/include/oscos/devicetree.h | 173 +++++ lab8/c/include/oscos/drivers/aux.h | 8 + lab8/c/include/oscos/drivers/board.h | 20 + lab8/c/include/oscos/drivers/gpio.h | 8 + lab8/c/include/oscos/drivers/l1ic.h | 24 + lab8/c/include/oscos/drivers/l2ic.h | 14 + lab8/c/include/oscos/drivers/mailbox.h | 19 + lab8/c/include/oscos/drivers/mini-uart.h | 14 + lab8/c/include/oscos/drivers/pm.h | 14 + lab8/c/include/oscos/fs/initramfs.h | 8 + lab8/c/include/oscos/fs/tmpfs.h | 8 + lab8/c/include/oscos/fs/vfs.h | 78 ++ lab8/c/include/oscos/initrd.h | 117 +++ lab8/c/include/oscos/libc/ctype.h | 6 + lab8/c/include/oscos/libc/inttypes.h | 9 + lab8/c/include/oscos/libc/stdio.h | 12 + lab8/c/include/oscos/libc/stdlib.h | 11 + lab8/c/include/oscos/libc/string.h | 32 + lab8/c/include/oscos/mem/malloc.h | 30 + lab8/c/include/oscos/mem/page-alloc.h | 87 +++ lab8/c/include/oscos/mem/shared-page.h | 12 + lab8/c/include/oscos/mem/startup-alloc.h | 34 + lab8/c/include/oscos/mem/types.h | 42 ++ lab8/c/include/oscos/mem/vm.h | 47 ++ .../include/oscos/mem/vm/kernel-page-tables.h | 6 + lab8/c/include/oscos/mem/vm/page-table.h | 42 ++ lab8/c/include/oscos/panic.h | 23 + lab8/c/include/oscos/sched.h | 166 +++++ lab8/c/include/oscos/shell.h | 6 + lab8/c/include/oscos/timer/delay.h | 11 + lab8/c/include/oscos/timer/timeout.h | 17 + lab8/c/include/oscos/uapi/errno.h | 17 + lab8/c/include/oscos/uapi/fcntl.h | 6 + lab8/c/include/oscos/uapi/signal.h | 43 ++ lab8/c/include/oscos/uapi/stdio.h | 8 + lab8/c/include/oscos/uapi/sys/syscall.h | 23 + lab8/c/include/oscos/uapi/unistd.h | 6 + lab8/c/include/oscos/utils/align.h | 13 + lab8/c/include/oscos/utils/control-flow.h | 11 + lab8/c/include/oscos/utils/core-id.h | 8 + lab8/c/include/oscos/utils/critical-section.h | 20 + lab8/c/include/oscos/utils/endian.h | 21 + lab8/c/include/oscos/utils/fmt.h | 15 + lab8/c/include/oscos/utils/heapq.h | 14 + lab8/c/include/oscos/utils/math.h | 13 + lab8/c/include/oscos/utils/rb.h | 26 + lab8/c/include/oscos/utils/save-ctx.S | 25 + lab8/c/include/oscos/utils/suspend.h | 19 + lab8/c/include/oscos/utils/time.h | 6 + lab8/c/include/oscos/xcpt.h | 9 + lab8/c/include/oscos/xcpt/task-queue.h | 8 + lab8/c/include/oscos/xcpt/trap-frame.h | 24 + lab8/c/src/console.c | 388 ++++++++++ lab8/c/src/devicetree.c | 129 ++++ lab8/c/src/drivers/aux.c | 28 + lab8/c/src/drivers/gpio.c | 105 +++ lab8/c/src/drivers/l1ic.c | 37 + lab8/c/src/drivers/l2ic.c | 36 + lab8/c/src/drivers/mailbox.c | 84 +++ lab8/c/src/drivers/mini-uart.c | 130 ++++ lab8/c/src/drivers/pm.c | 85 +++ lab8/c/src/fs/initramfs.c | 333 +++++++++ lab8/c/src/fs/tmpfs.c | 386 ++++++++++ lab8/c/src/fs/vfs.c | 344 +++++++++ lab8/c/src/initrd.c | 131 ++++ lab8/c/src/libc/ctype.c | 3 + lab8/c/src/libc/stdio.c | 41 ++ lab8/c/src/libc/stdlib/qsort.c | 154 ++++ lab8/c/src/libc/string.c | 148 ++++ lab8/c/src/linker.ld | 78 ++ lab8/c/src/main.c | 94 +++ lab8/c/src/mem/malloc.c | 353 +++++++++ lab8/c/src/mem/page-alloc.c | 662 +++++++++++++++++ lab8/c/src/mem/shared-page.c | 147 ++++ lab8/c/src/mem/startup-alloc.c | 24 + lab8/c/src/mem/vm.c | 437 +++++++++++ lab8/c/src/mem/vm/kernel-page-tables.c | 114 +++ lab8/c/src/panic.c | 24 + lab8/c/src/sched/idle-thread.c | 8 + lab8/c/src/sched/periodic-sched.c | 33 + lab8/c/src/sched/run-signal-handler.S | 119 +++ lab8/c/src/sched/sched.c | 685 ++++++++++++++++++ lab8/c/src/sched/schedule.S | 115 +++ lab8/c/src/sched/sig-handler-main.S | 10 + lab8/c/src/sched/thread-main.S | 10 + lab8/c/src/sched/user-program-main.S | 93 +++ lab8/c/src/shell.c | 679 +++++++++++++++++ lab8/c/src/start.S | 120 +++ lab8/c/src/timer/delay.c | 15 + lab8/c/src/timer/timeout.c | 114 +++ lab8/c/src/utils/core-id.c | 9 + lab8/c/src/utils/fmt.c | 653 +++++++++++++++++ lab8/c/src/utils/heapq.c | 77 ++ lab8/c/src/utils/rb.c | 83 +++ lab8/c/src/xcpt/data-abort-handler.c | 55 ++ lab8/c/src/xcpt/default-handler.c | 17 + lab8/c/src/xcpt/insn-abort-handler.c | 55 ++ lab8/c/src/xcpt/irq-handler.c | 43 ++ lab8/c/src/xcpt/load-aapcs-and-eret.S | 11 + lab8/c/src/xcpt/svc-handler.S | 50 ++ lab8/c/src/xcpt/syscall-table.S | 24 + lab8/c/src/xcpt/syscall/chdir.c | 14 + lab8/c/src/xcpt/syscall/close.c | 15 + lab8/c/src/xcpt/syscall/enosys.c | 3 + lab8/c/src/xcpt/syscall/exec.c | 44 ++ lab8/c/src/xcpt/syscall/exit.c | 3 + lab8/c/src/xcpt/syscall/fork-child-ret.S | 29 + lab8/c/src/xcpt/syscall/fork-impl.c | 11 + lab8/c/src/xcpt/syscall/fork.S | 48 ++ lab8/c/src/xcpt/syscall/getpid.c | 4 + lab8/c/src/xcpt/syscall/kill.c | 17 + lab8/c/src/xcpt/syscall/mbox-call.c | 45 ++ lab8/c/src/xcpt/syscall/mkdir.c | 10 + lab8/c/src/xcpt/syscall/mount.c | 14 + lab8/c/src/xcpt/syscall/open.c | 34 + lab8/c/src/xcpt/syscall/read.c | 12 + lab8/c/src/xcpt/syscall/signal-kill.c | 25 + lab8/c/src/xcpt/syscall/signal.c | 15 + lab8/c/src/xcpt/syscall/sigreturn-check.c | 13 + lab8/c/src/xcpt/syscall/sigreturn.S | 50 ++ lab8/c/src/xcpt/syscall/uart-read.c | 46 ++ lab8/c/src/xcpt/syscall/uart-write.c | 45 ++ lab8/c/src/xcpt/syscall/write.c | 12 + lab8/c/src/xcpt/task-queue.c | 97 +++ lab8/c/src/xcpt/vector-table.S | 179 +++++ lab8/c/src/xcpt/xcpt.c | 7 + lab8/tests/.gitignore | 2 + lab8/tests/build-initrd.sh | 13 + lab8/tests/get-dtb.sh | 5 + lab8/tests/user-program/libc/.gitignore | 1 + lab8/tests/user-program/libc/Makefile | 61 ++ .../libc/include/__detail/utils/fmt.h | 15 + lab8/tests/user-program/libc/include/ctype.h | 6 + lab8/tests/user-program/libc/include/errno.h | 9 + lab8/tests/user-program/libc/include/fcntl.h | 8 + lab8/tests/user-program/libc/include/mbox.h | 6 + .../user-program/libc/include/oscos-uapi | 1 + lab8/tests/user-program/libc/include/signal.h | 9 + lab8/tests/user-program/libc/include/stdio.h | 12 + lab8/tests/user-program/libc/include/stdlib.h | 8 + lab8/tests/user-program/libc/include/string.h | 25 + .../user-program/libc/include/sys/mount.h | 7 + .../user-program/libc/include/sys/stat.h | 8 + .../user-program/libc/include/sys/syscall.h | 1 + lab8/tests/user-program/libc/include/unistd.h | 28 + .../libc/src/__detail/utils/fmt.c | 654 +++++++++++++++++ lab8/tests/user-program/libc/src/ctype.c | 3 + lab8/tests/user-program/libc/src/errno.c | 3 + lab8/tests/user-program/libc/src/fcntl.c | 8 + lab8/tests/user-program/libc/src/mbox.c | 8 + lab8/tests/user-program/libc/src/signal.c | 13 + lab8/tests/user-program/libc/src/start.S | 15 + lab8/tests/user-program/libc/src/stdio.c | 31 + lab8/tests/user-program/libc/src/stdlib.c | 9 + lab8/tests/user-program/libc/src/string.c | 99 +++ lab8/tests/user-program/libc/src/sys/mount.c | 10 + lab8/tests/user-program/libc/src/sys/stat.c | 8 + lab8/tests/user-program/libc/src/unistd.c | 31 + .../user-program/libc/src/unistd/syscall.S | 30 + .../user-program/syscall_test/.gitignore | 1 + lab8/tests/user-program/syscall_test/Makefile | 73 ++ .../user-program/syscall_test/src/linker.ld | 53 ++ .../user-program/syscall_test/src/main.c | 61 ++ 167 files changed, 11388 insertions(+) create mode 100644 lab8/c/.clang-format create mode 100644 lab8/c/.gitignore create mode 100644 lab8/c/Makefile create mode 100644 lab8/c/include/oscos/console.h create mode 100644 lab8/c/include/oscos/devicetree.h create mode 100644 lab8/c/include/oscos/drivers/aux.h create mode 100644 lab8/c/include/oscos/drivers/board.h create mode 100644 lab8/c/include/oscos/drivers/gpio.h create mode 100644 lab8/c/include/oscos/drivers/l1ic.h create mode 100644 lab8/c/include/oscos/drivers/l2ic.h create mode 100644 lab8/c/include/oscos/drivers/mailbox.h create mode 100644 lab8/c/include/oscos/drivers/mini-uart.h create mode 100644 lab8/c/include/oscos/drivers/pm.h create mode 100644 lab8/c/include/oscos/fs/initramfs.h create mode 100644 lab8/c/include/oscos/fs/tmpfs.h create mode 100644 lab8/c/include/oscos/fs/vfs.h create mode 100644 lab8/c/include/oscos/initrd.h create mode 100644 lab8/c/include/oscos/libc/ctype.h create mode 100644 lab8/c/include/oscos/libc/inttypes.h create mode 100644 lab8/c/include/oscos/libc/stdio.h create mode 100644 lab8/c/include/oscos/libc/stdlib.h create mode 100644 lab8/c/include/oscos/libc/string.h create mode 100644 lab8/c/include/oscos/mem/malloc.h create mode 100644 lab8/c/include/oscos/mem/page-alloc.h create mode 100644 lab8/c/include/oscos/mem/shared-page.h create mode 100644 lab8/c/include/oscos/mem/startup-alloc.h create mode 100644 lab8/c/include/oscos/mem/types.h create mode 100644 lab8/c/include/oscos/mem/vm.h create mode 100644 lab8/c/include/oscos/mem/vm/kernel-page-tables.h create mode 100644 lab8/c/include/oscos/mem/vm/page-table.h create mode 100644 lab8/c/include/oscos/panic.h create mode 100644 lab8/c/include/oscos/sched.h create mode 100644 lab8/c/include/oscos/shell.h create mode 100644 lab8/c/include/oscos/timer/delay.h create mode 100644 lab8/c/include/oscos/timer/timeout.h create mode 100644 lab8/c/include/oscos/uapi/errno.h create mode 100644 lab8/c/include/oscos/uapi/fcntl.h create mode 100644 lab8/c/include/oscos/uapi/signal.h create mode 100644 lab8/c/include/oscos/uapi/stdio.h create mode 100644 lab8/c/include/oscos/uapi/sys/syscall.h create mode 100644 lab8/c/include/oscos/uapi/unistd.h create mode 100644 lab8/c/include/oscos/utils/align.h create mode 100644 lab8/c/include/oscos/utils/control-flow.h create mode 100644 lab8/c/include/oscos/utils/core-id.h create mode 100644 lab8/c/include/oscos/utils/critical-section.h create mode 100644 lab8/c/include/oscos/utils/endian.h create mode 100644 lab8/c/include/oscos/utils/fmt.h create mode 100644 lab8/c/include/oscos/utils/heapq.h create mode 100644 lab8/c/include/oscos/utils/math.h create mode 100644 lab8/c/include/oscos/utils/rb.h create mode 100644 lab8/c/include/oscos/utils/save-ctx.S create mode 100644 lab8/c/include/oscos/utils/suspend.h create mode 100644 lab8/c/include/oscos/utils/time.h create mode 100644 lab8/c/include/oscos/xcpt.h create mode 100644 lab8/c/include/oscos/xcpt/task-queue.h create mode 100644 lab8/c/include/oscos/xcpt/trap-frame.h create mode 100644 lab8/c/src/console.c create mode 100644 lab8/c/src/devicetree.c create mode 100644 lab8/c/src/drivers/aux.c create mode 100644 lab8/c/src/drivers/gpio.c create mode 100644 lab8/c/src/drivers/l1ic.c create mode 100644 lab8/c/src/drivers/l2ic.c create mode 100644 lab8/c/src/drivers/mailbox.c create mode 100644 lab8/c/src/drivers/mini-uart.c create mode 100644 lab8/c/src/drivers/pm.c create mode 100644 lab8/c/src/fs/initramfs.c create mode 100644 lab8/c/src/fs/tmpfs.c create mode 100644 lab8/c/src/fs/vfs.c create mode 100644 lab8/c/src/initrd.c create mode 100644 lab8/c/src/libc/ctype.c create mode 100644 lab8/c/src/libc/stdio.c create mode 100644 lab8/c/src/libc/stdlib/qsort.c create mode 100644 lab8/c/src/libc/string.c create mode 100644 lab8/c/src/linker.ld create mode 100644 lab8/c/src/main.c create mode 100644 lab8/c/src/mem/malloc.c create mode 100644 lab8/c/src/mem/page-alloc.c create mode 100644 lab8/c/src/mem/shared-page.c create mode 100644 lab8/c/src/mem/startup-alloc.c create mode 100644 lab8/c/src/mem/vm.c create mode 100644 lab8/c/src/mem/vm/kernel-page-tables.c create mode 100644 lab8/c/src/panic.c create mode 100644 lab8/c/src/sched/idle-thread.c create mode 100644 lab8/c/src/sched/periodic-sched.c create mode 100644 lab8/c/src/sched/run-signal-handler.S create mode 100644 lab8/c/src/sched/sched.c create mode 100644 lab8/c/src/sched/schedule.S create mode 100644 lab8/c/src/sched/sig-handler-main.S create mode 100644 lab8/c/src/sched/thread-main.S create mode 100644 lab8/c/src/sched/user-program-main.S create mode 100644 lab8/c/src/shell.c create mode 100644 lab8/c/src/start.S create mode 100644 lab8/c/src/timer/delay.c create mode 100644 lab8/c/src/timer/timeout.c create mode 100644 lab8/c/src/utils/core-id.c create mode 100644 lab8/c/src/utils/fmt.c create mode 100644 lab8/c/src/utils/heapq.c create mode 100644 lab8/c/src/utils/rb.c create mode 100644 lab8/c/src/xcpt/data-abort-handler.c create mode 100644 lab8/c/src/xcpt/default-handler.c create mode 100644 lab8/c/src/xcpt/insn-abort-handler.c create mode 100644 lab8/c/src/xcpt/irq-handler.c create mode 100644 lab8/c/src/xcpt/load-aapcs-and-eret.S create mode 100644 lab8/c/src/xcpt/svc-handler.S create mode 100644 lab8/c/src/xcpt/syscall-table.S create mode 100644 lab8/c/src/xcpt/syscall/chdir.c create mode 100644 lab8/c/src/xcpt/syscall/close.c create mode 100644 lab8/c/src/xcpt/syscall/enosys.c create mode 100644 lab8/c/src/xcpt/syscall/exec.c create mode 100644 lab8/c/src/xcpt/syscall/exit.c create mode 100644 lab8/c/src/xcpt/syscall/fork-child-ret.S create mode 100644 lab8/c/src/xcpt/syscall/fork-impl.c create mode 100644 lab8/c/src/xcpt/syscall/fork.S create mode 100644 lab8/c/src/xcpt/syscall/getpid.c create mode 100644 lab8/c/src/xcpt/syscall/kill.c create mode 100644 lab8/c/src/xcpt/syscall/mbox-call.c create mode 100644 lab8/c/src/xcpt/syscall/mkdir.c create mode 100644 lab8/c/src/xcpt/syscall/mount.c create mode 100644 lab8/c/src/xcpt/syscall/open.c create mode 100644 lab8/c/src/xcpt/syscall/read.c create mode 100644 lab8/c/src/xcpt/syscall/signal-kill.c create mode 100644 lab8/c/src/xcpt/syscall/signal.c create mode 100644 lab8/c/src/xcpt/syscall/sigreturn-check.c create mode 100644 lab8/c/src/xcpt/syscall/sigreturn.S create mode 100644 lab8/c/src/xcpt/syscall/uart-read.c create mode 100644 lab8/c/src/xcpt/syscall/uart-write.c create mode 100644 lab8/c/src/xcpt/syscall/write.c create mode 100644 lab8/c/src/xcpt/task-queue.c create mode 100644 lab8/c/src/xcpt/vector-table.S create mode 100644 lab8/c/src/xcpt/xcpt.c create mode 100644 lab8/tests/.gitignore create mode 100755 lab8/tests/build-initrd.sh create mode 100755 lab8/tests/get-dtb.sh create mode 100644 lab8/tests/user-program/libc/.gitignore create mode 100644 lab8/tests/user-program/libc/Makefile create mode 100644 lab8/tests/user-program/libc/include/__detail/utils/fmt.h create mode 100644 lab8/tests/user-program/libc/include/ctype.h create mode 100644 lab8/tests/user-program/libc/include/errno.h create mode 100644 lab8/tests/user-program/libc/include/fcntl.h create mode 100644 lab8/tests/user-program/libc/include/mbox.h create mode 120000 lab8/tests/user-program/libc/include/oscos-uapi create mode 100644 lab8/tests/user-program/libc/include/signal.h create mode 100644 lab8/tests/user-program/libc/include/stdio.h create mode 100644 lab8/tests/user-program/libc/include/stdlib.h create mode 100644 lab8/tests/user-program/libc/include/string.h create mode 100644 lab8/tests/user-program/libc/include/sys/mount.h create mode 100644 lab8/tests/user-program/libc/include/sys/stat.h create mode 100644 lab8/tests/user-program/libc/include/sys/syscall.h create mode 100644 lab8/tests/user-program/libc/include/unistd.h create mode 100644 lab8/tests/user-program/libc/src/__detail/utils/fmt.c create mode 100644 lab8/tests/user-program/libc/src/ctype.c create mode 100644 lab8/tests/user-program/libc/src/errno.c create mode 100644 lab8/tests/user-program/libc/src/fcntl.c create mode 100644 lab8/tests/user-program/libc/src/mbox.c create mode 100644 lab8/tests/user-program/libc/src/signal.c create mode 100644 lab8/tests/user-program/libc/src/start.S create mode 100644 lab8/tests/user-program/libc/src/stdio.c create mode 100644 lab8/tests/user-program/libc/src/stdlib.c create mode 100644 lab8/tests/user-program/libc/src/string.c create mode 100644 lab8/tests/user-program/libc/src/sys/mount.c create mode 100644 lab8/tests/user-program/libc/src/sys/stat.c create mode 100644 lab8/tests/user-program/libc/src/unistd.c create mode 100644 lab8/tests/user-program/libc/src/unistd/syscall.S create mode 100644 lab8/tests/user-program/syscall_test/.gitignore create mode 100644 lab8/tests/user-program/syscall_test/Makefile create mode 100644 lab8/tests/user-program/syscall_test/src/linker.ld create mode 100644 lab8/tests/user-program/syscall_test/src/main.c diff --git a/lab8/c/.clang-format b/lab8/c/.clang-format new file mode 100644 index 000000000..9b3aa8b72 --- /dev/null +++ b/lab8/c/.clang-format @@ -0,0 +1 @@ +BasedOnStyle: LLVM diff --git a/lab8/c/.gitignore b/lab8/c/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/lab8/c/.gitignore @@ -0,0 +1 @@ +/build diff --git a/lab8/c/Makefile b/lab8/c/Makefile new file mode 100644 index 000000000..fb902eca7 --- /dev/null +++ b/lab8/c/Makefile @@ -0,0 +1,109 @@ +DEFAULT_PROFILE = DEBUG +SRC_DIR = src +BUILD_DIR = build + +AS = aarch64-linux-gnu-as +CC = aarch64-linux-gnu-gcc +CPP = aarch64-linux-gnu-cpp +OBJCOPY = aarch64-linux-gnu-objcopy + +CPPFLAGS_BASE = -Iinclude + +ASFLAGS_DEBUG = -g + +CFLAGS_BASE = -std=c17 -pedantic-errors -Wall -Wextra -ffreestanding \ + -mcpu=cortex-a53+nofp+nosimd -mstrict-align +CFLAGS_DEBUG = -g +CFLAGS_RELEASE = -O3 -flto + +LDFLAGS_BASE = -nostdlib -Xlinker --build-id=none +LDFLAGS_RELEASE = -flto -Xlinker --gc-sections + +LDLIBS_BASE = -lgcc + +OBJS = start main console devicetree initrd panic shell \ + drivers/aux drivers/gpio drivers/l1ic drivers/l2ic drivers/mailbox \ + drivers/mini-uart drivers/pm \ + fs/initramfs fs/tmpfs fs/vfs \ + mem/malloc mem/page-alloc mem/shared-page mem/startup-alloc mem/vm \ + mem/vm/kernel-page-tables \ + sched/idle-thread sched/periodic-sched sched/run-signal-handler \ + sched/sched sched/schedule sched/sig-handler-main \ + sched/thread-main sched/user-program-main \ + timer/delay timer/timeout \ + xcpt/data-abort-handler xcpt/default-handler \ + xcpt/insn-abort-handler xcpt/irq-handler xcpt/load-aapcs-and-eret \ + xcpt/svc-handler \ + xcpt/syscall-table xcpt/task-queue xcpt/vector-table xcpt/xcpt \ + xcpt/syscall/enosys xcpt/syscall/getpid xcpt/syscall/uart-read \ + xcpt/syscall/uart-write xcpt/syscall/exec xcpt/syscall/fork \ + xcpt/syscall/fork-impl xcpt/syscall/fork-child-ret \ + xcpt/syscall/exit xcpt/syscall/mbox-call xcpt/syscall/kill \ + xcpt/syscall/signal xcpt/syscall/signal-kill \ + xcpt/syscall/sigreturn xcpt/syscall/sigreturn-check \ + xcpt/syscall/open xcpt/syscall/close xcpt/syscall/write \ + xcpt/syscall/read xcpt/syscall/mkdir xcpt/syscall/mount \ + xcpt/syscall/chdir \ + libc/ctype libc/stdio libc/stdlib/qsort libc/string \ + utils/core-id utils/fmt utils/heapq utils/rb +LD_SCRIPT = $(SRC_DIR)/linker.ld + +# ------------------------------------------------------------------------------ + +PROFILE = $(DEFAULT_PROFILE) +OUT_DIR = $(BUILD_DIR)/$(PROFILE) + +CPPFLAGS = $(CPPFLAGS_BASE) $(CPPFLAGS_$(PROFILE)) +ASFLAGS = $(ASFLAGS_BASE) $(ASFLAGS_$(PROFILE)) +CFLAGS = $(CFLAGS_BASE) $(CFLAGS_$(PROFILE)) +LDFLAGS = $(LDFLAGS_BASE) $(LDFLAGS_$(PROFILE)) +LDLIBS = $(LDLIBS_BASE) $(LDLIBS_$(PROFILE)) + +OBJ_PATHS = $(addprefix $(OUT_DIR)/,$(addsuffix .o,$(OBJS))) + +# ------------------------------------------------------------------------------ + +.PHONY: all qemu qemu-debug gdb clean-profile clean + +all: $(OUT_DIR)/kernel8.img + +$(OUT_DIR)/kernel8.img: $(OUT_DIR)/kernel8.elf + @mkdir -p $(@D) + $(OBJCOPY) -O binary $^ $@ + +$(OUT_DIR)/kernel8.elf: $(OBJ_PATHS) $(LD_SCRIPT) + @mkdir -p $(@D) + $(CC) -T $(LD_SCRIPT) $(LDFLAGS) $(filter-out $(LD_SCRIPT),$^) $(LDLIBS) -o $@ + +$(OUT_DIR)/%.o: $(SRC_DIR)/%.c + @mkdir -p $(@D) + $(CC) $(CPPFLAGS) $(CFLAGS) -c -MMD -MP -MF $(OUT_DIR)/$*.d $< -o $@ + +$(OUT_DIR)/%.o: $(OUT_DIR)/%.s + @mkdir -p $(@D) + $(AS) $(ASFLAGS) $^ -o $@ + +$(OUT_DIR)/%.s: $(SRC_DIR)/%.S + @mkdir -p $(@D) + $(CPP) $(CPPFLAGS) $^ -o $@ + +qemu: $(OUT_DIR)/kernel8.img + qemu-system-aarch64 -M raspi3b -kernel $< -initrd ../tests/initramfs.cpio \ + -dtb ../tests/bcm2710-rpi-3-b-plus.dtb -display gtk -serial null \ + -serial stdio + +qemu-debug: $(OUT_DIR)/kernel8.img + qemu-system-aarch64 -M raspi3b -kernel $< -initrd ../tests/initramfs.cpio \ + -dtb ../tests/bcm2710-rpi-3-b-plus.dtb -display gtk -serial null \ + -serial stdio -S -s + +gdb: $(OUT_DIR)/kernel8.elf + gdb -s $< -ex 'target remote :1234' + +clean-profile: + $(RM) -r $(OUT_DIR) + +clean: + $(RM) -r $(BUILD_DIR) + +-include $(OUT_DIR)/*.d diff --git a/lab8/c/include/oscos/console.h b/lab8/c/include/oscos/console.h new file mode 100644 index 000000000..77425b4d2 --- /dev/null +++ b/lab8/c/include/oscos/console.h @@ -0,0 +1,144 @@ +/// \file include/oscos/console.h +/// \brief Serial console. +/// +/// The serial console reads and writes data to the mini UART. +/// +/// Before using the serial console, it must be initialized by calling +/// console_init(void) exactly once. Initializing the serial console twice or +/// operating on the serial console before initialization are not checked and +/// may have unintended consequences. +/// +/// The serial console implements two modes: text mode and binary mode. In text +/// mode, automatic newline translation is performed. A "\r" character received +/// by the mini UART will be read as "\n", and writing "\n" to the serial +/// console will send "\r\n" down the mini UART. In binary mode, automatic +/// newline translation is not performed. Any byte received by the mini UART +/// will be read as-is, and every character written to the serial console will +/// also be sent as-is. Upon initialization, the mode is set to text mode. + +#ifndef OSCOS_CONSOLE_H +#define OSCOS_CONSOLE_H + +#include +#include +#include + +/// \brief The mode of the serial console. +typedef enum { + CM_TEXT, ///< Text mode. Performs newline translation. + CM_BINARY ///< Binary mode. Every byte are sent/received as-is. +} console_mode_t; + +/// \brief Initializes the serial console. +/// +/// See the file-level documentation for requirements on initialization. +void console_init(void); + +/// \brief Sets the mode of the serial console. +/// +/// \param mode The mode. Must be `CM_TEXT` or `CM_BINARY`. +void console_set_mode(console_mode_t mode); + +/// \brief Reads a character from the serial console. +/// +/// When calling this function, the serial console must be initialized. +unsigned char console_getc(void); + +/// \brief Reads a character from the serial console without blocking. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return The character read, or a negative number if the read would block. +int console_getc_nonblock(void); + +/// \brief Reads as many characters from the serial console as possible without +/// blocking. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return The number of characters read. +size_t console_read_nonblock(void *buf, size_t count); + +/// \brief Writes a character to the serial console. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return \p c +unsigned char console_putc(unsigned char c); + +/// \brief Writes a character to the serial console without blocking. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return \p c if the operation is successful, or a negative number if the +/// write operation would block. +int console_putc_nonblock(unsigned char c); + +/// \brief Writes characters to the serial console. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return \p count +size_t console_write(const void *buf, size_t count); + +/// \brief Writes as many characters to the serial console as possible without +/// blocking. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return The number of characters written. +size_t console_write_nonblock(const void *buf, size_t count); + +/// \brief Writes a string to the serial console without trailing newline. +/// +/// When calling this function, the serial console must be initialized. +void console_fputs(const char *s); + +/// \brief Writes a string to the serial console with trailing newline. +/// +/// When calling this function, the serial console must be initialized. +void console_puts(const char *s); + +/// \brief Performs string formatting and writes the result to the serial +/// console. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return The number of characters written. +int console_vprintf(const char *restrict format, va_list ap) + __attribute__((format(printf, 1, 0))); + +/// \brief Performs string formatting and writes the result to the serial +/// console. +/// +/// When calling this function, the serial console must be initialized. +/// +/// \return The number of characters written. +int console_printf(const char *restrict format, ...) + __attribute__((format(printf, 1, 2))); + +/// \brief Registers the notification callback for read readiness. +/// +/// \param callback The callback that will be called. +/// \param arg The argument that will be passed to \p callback. +/// \return true if the registration succeeds. +/// \return false if the registration fails because an earlier callback has not +/// been called. +bool console_notify_read_ready(void (*callback)(void *), void *arg); + +/// \brief Registers the notification callback for write readiness. +/// +/// \param callback The callback that will be called. +/// \param arg The argument that will be passed to \p callback. +/// \return true if the registration succeeds. +/// \return false if the registration fails because an earlier callback has not +/// been called. +bool console_notify_write_ready(void (*callback)(void *), void *arg); + +/// \brief Waits until all buffered characters are sent to the serial console. +void console_flush_write_buffer(void); + +/// \brief Interrupt handler. Not meant to be called directly. +void mini_uart_interrupt_handler(void); + +#endif diff --git a/lab8/c/include/oscos/devicetree.h b/lab8/c/include/oscos/devicetree.h new file mode 100644 index 000000000..b4fe42380 --- /dev/null +++ b/lab8/c/include/oscos/devicetree.h @@ -0,0 +1,173 @@ +#ifndef OSCOS_DEVICETREE_H +#define OSCOS_DEVICETREE_H + +#include +#include + +#include "oscos/libc/string.h" +#include "oscos/utils/align.h" +#include "oscos/utils/control-flow.h" +#include "oscos/utils/endian.h" + +/// \brief Flattened devicetree header. +typedef struct { + uint32_t magic; + uint32_t totalsize; + uint32_t off_dt_struct; + uint32_t off_dt_strings; + uint32_t off_mem_rsvmap; + uint32_t version; + uint32_t last_comp_version; + uint32_t boot_cpuid_phys; + uint32_t size_dt_strings; + uint32_t size_dt_struct; +} fdt_header_t; + +/// \brief An entry of the memory reservation block. +typedef struct { + uint64_t address; + uint64_t size; +} fdt_reserve_entry_t; + +/// \brief An item in a node in a flattened devicetree blob. +typedef struct { + uint32_t token; + char payload[]; +} fdt_item_t; + +/// \brief Flattened devicetree property; whatever follows the FDT_PROP token. +typedef struct { + uint32_t len; + uint32_t nameoff; + char value[]; +} fdt_prop_t; + +#define FDT_BEGIN_NODE ((uint32_t)0x00000001) +#define FDT_END_NODE ((uint32_t)0x00000002) +#define FDT_PROP ((uint32_t)0x00000003) +#define FDT_NOP ((uint32_t)0x00000004) +#define FDT_END ((uint32_t)0x00000009) + +/// \brief Initializes the device tree. +/// \param dtb_start The loading address of the devicetree blob. +/// \return Whether or not the initialization succeeds. +bool devicetree_init(const void *dtb_start); + +/// \brief Returns whether or not the devicetree has been successfully +/// initialized. +bool devicetree_is_init(void); + +/// \brief The start of the memory reservation block of the devicetree blob. +#define FDT_START_MEM_RSVMAP \ + ((const fdt_reserve_entry_t \ + *)(fdt_get_start() + \ + rev_u32(((const fdt_header_t *)fdt_get_start())->off_mem_rsvmap))) + +/// \brief The start of the strings block of the devicetree blob. +#define FDT_START_STRINGS \ + (fdt_get_start() + \ + rev_u32(((const fdt_header_t *)fdt_get_start())->off_dt_strings)) + +/// \brief The starting token of an item in the structure block. +#define FDT_TOKEN(ITEM) (rev_u32((ITEM)->token)) + +/// \brief The name of an node in the structure block. +#define FDT_NODE_NAME(NODE) ((NODE)->payload) + +/// \brief The name of a property in the structure block. +#define FDT_PROP_NAME(PROP) (FDT_START_STRINGS + rev_u32((PROP)->nameoff)) + +/// \brief The value of a property in the structure block. +#define FDT_PROP_VALUE(PROP) ((PROP)->value) + +/// \brief The length of the of a property in the structure block. +#define FDT_PROP_VALUE_LEN(PROP) (rev_u32((PROP)->len)) + +#define FDT_ITEMS_START(NODE) \ + ((const fdt_item_t *)ALIGN( \ + (uintptr_t)((NODE)->payload) + strlen(FDT_NODE_NAME(NODE)) + 1, 4)) +#define FDT_ITEM_IS_END(ITEM) (FDT_TOKEN(ITEM) == FDT_END_NODE) + +/// \brief Expands to a for loop that loops over each item in the given node. +/// +/// \param NODE The pointer to the node. +/// \param ITEM_NAME The name of the variable for the item. +#define FDT_FOR_ITEM(NODE, ITEM_NAME) \ + for (const fdt_item_t *ITEM_NAME = FDT_ITEMS_START(NODE); \ + !FDT_ITEM_IS_END(ITEM_NAME); ITEM_NAME = fdt_next_item(ITEM_NAME)) + +/// \brief Expands to a for loop that loops over each item in the given node. +/// +/// This is a variant of FDT_FOR_ITEM that does not declare the variable within +/// the for loop. This is useful, e. g., if one wants to obtain the pointer to +/// the FDT_END_NODE token. +/// +/// \param NODE The pointer to the node. +/// \param ITEM_NAME The name of the variable for the item. Must be a declared +/// variable. +#define FDT_FOR_ITEM_(NODE, ITEM_NAME) \ + for (ITEM_NAME = FDT_ITEMS_START(NODE); !FDT_ITEM_IS_END(ITEM_NAME); \ + ITEM_NAME = fdt_next_item(ITEM_NAME)) + +/// \brief Gets the starting address of the devicetree blob. +const char *fdt_get_start(void); + +/// \brief Gets the ending address of the devicetree blob. +const char *fdt_get_end(void); + +/// \brief Gets the pointer to the next item of the given item in the same node. +/// \param item The item. Must not point to the FDT_END_NODE token. +const fdt_item_t *fdt_next_item(const fdt_item_t *item); + +/// \brief A node of the parent node linked list. +typedef struct fdt_traverse_parent_list_node_t { + const fdt_item_t *node; + const struct fdt_traverse_parent_list_node_t *parent; +} fdt_traverse_parent_list_node_t; + +/// \brief Callback for void fdt_traverse(fdt_traverse_callback_t *, void *). +typedef control_flow_t +fdt_traverse_callback_t(void *, const fdt_item_t *, + const fdt_traverse_parent_list_node_t *); + +/// \brief Performs in-order traversal of the devicetree. +/// +/// When the traversal process encounters a node, \p callback is called with +/// \p arg, the pointer to the current node, and the linked list of the parent +/// nodes of the current node. +/// +/// \param callback The callback that is called on each node. +/// \param arg The first argument that will be passed to \p callback. +void fdt_traverse(fdt_traverse_callback_t *callback, void *arg); + +/// \brief The #address-cells and the #size-cells properties. +typedef struct { + uint32_t n_address_cells, n_size_cells; +} fdt_n_address_size_cells_t; + +/// \brief Gets the #address-cells and the #size-cells properties of a node. +/// +/// \param node The pointer to the node. +/// \return The value of the #address-cells and the #size-cells properties, or +/// their respective defaults if these properties are missing. +fdt_n_address_size_cells_t fdt_get_n_address_size_cells(const fdt_item_t *node); + +/// \brief The reg property. +typedef struct { + uintmax_t address, size; +} fdt_reg_t; + +typedef struct { + fdt_reg_t value; + bool address_overflow, size_overflow; +} fdt_read_reg_result_t; + +/// \brief Reads the reg property. +/// +/// \param prop The pointer to the reg property. +/// \param n_cells The #address-cells and the #size-cells properties of the +/// parent node. +fdt_read_reg_result_t fdt_read_reg(const fdt_prop_t *prop, + fdt_n_address_size_cells_t n_cells); + +#endif diff --git a/lab8/c/include/oscos/drivers/aux.h b/lab8/c/include/oscos/drivers/aux.h new file mode 100644 index 000000000..e04db4bd1 --- /dev/null +++ b/lab8/c/include/oscos/drivers/aux.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_DRIVERS_AUX_H +#define OSCOS_DRIVERS_AUX_H + +void aux_init(void); + +void aux_enable_mini_uart(void); + +#endif diff --git a/lab8/c/include/oscos/drivers/board.h b/lab8/c/include/oscos/drivers/board.h new file mode 100644 index 000000000..95af6777a --- /dev/null +++ b/lab8/c/include/oscos/drivers/board.h @@ -0,0 +1,20 @@ +#ifndef OSCOS_DRIVERS_BOARD_H +#define OSCOS_DRIVERS_BOARD_H + +// Symbols defined in the linker script. +extern char _kernel_vm_base[]; + +// ARM virtual address. See +// https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#peripheral-addresses +#define PERIPHERAL_BASE ((void *)(_kernel_vm_base + 0x3f000000)) + +#define ARM_LOCAL_PERIPHERAL_BASE ((void *)(_kernel_vm_base + 0x40000000)) + +// ? Is the shareability domain for the peripheral memory barriers correct? + +#define PERIPHERAL_READ_BARRIER() \ + __asm__ __volatile__("dsb nshld" : : : "memory") +#define PERIPHERAL_WRITE_BARRIER() \ + __asm__ __volatile__("dsb nshst" : : : "memory") + +#endif diff --git a/lab8/c/include/oscos/drivers/gpio.h b/lab8/c/include/oscos/drivers/gpio.h new file mode 100644 index 000000000..6994b22d6 --- /dev/null +++ b/lab8/c/include/oscos/drivers/gpio.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_DRIVERS_GPIO_H +#define OSCOS_DRIVERS_GPIO_H + +void gpio_init(void); + +void gpio_setup_uart0_gpio14(void); + +#endif diff --git a/lab8/c/include/oscos/drivers/l1ic.h b/lab8/c/include/oscos/drivers/l1ic.h new file mode 100644 index 000000000..a9f33889c --- /dev/null +++ b/lab8/c/include/oscos/drivers/l1ic.h @@ -0,0 +1,24 @@ +#ifndef OSCOS_DRIVERS_L1IC_H +#define OSCOS_DRIVERS_L1IC_H + +#include +#include + +#define INT_L1_SRC_TIMER0 ((uint32_t)(1 << 0)) +#define INT_L1_SRC_TIMER1 ((uint32_t)(1 << 1)) +#define INT_L1_SRC_TIMER2 ((uint32_t)(1 << 2)) +#define INT_L1_SRC_TIMER3 ((uint32_t)(1 << 3)) +#define INT_L1_SRC_MBOX0 ((uint32_t)(1 << 4)) +#define INT_L1_SRC_MBOX1 ((uint32_t)(1 << 5)) +#define INT_L1_SRC_MBOX2 ((uint32_t)(1 << 6)) +#define INT_L1_SRC_MBOX3 ((uint32_t)(1 << 7)) +#define INT_L1_SRC_GPU ((uint32_t)(1 << 8)) +#define INT_L1_SRC_PMU ((uint32_t)(1 << 9)) + +void l1ic_init(void); + +uint32_t l1ic_get_int_src(size_t core_id); + +void l1ic_enable_core_timer_irq(size_t core_id); + +#endif diff --git a/lab8/c/include/oscos/drivers/l2ic.h b/lab8/c/include/oscos/drivers/l2ic.h new file mode 100644 index 000000000..c11a71b50 --- /dev/null +++ b/lab8/c/include/oscos/drivers/l2ic.h @@ -0,0 +1,14 @@ +#ifndef OSCOS_DRIVERS_L2IC_H +#define OSCOS_DRIVERS_L2IC_H + +#include + +#define INT_L2_IRQ_0_SRC_AUX ((uint32_t)(1 << 29)) + +void l2ic_init(void); + +uint32_t l2ic_get_pending_irq_0(void); + +void l2ic_enable_irq_0(uint32_t mask); + +#endif diff --git a/lab8/c/include/oscos/drivers/mailbox.h b/lab8/c/include/oscos/drivers/mailbox.h new file mode 100644 index 000000000..57fa08254 --- /dev/null +++ b/lab8/c/include/oscos/drivers/mailbox.h @@ -0,0 +1,19 @@ +#ifndef OSCOS_DRIVERS_MAILBOX_H +#define OSCOS_DRIVERS_MAILBOX_H + +#include + +#define MAILBOX_CHANNEL_PROPERTY_TAGS_ARM_TO_VC ((unsigned char)8) + +typedef struct { + uint32_t base, size; +} arm_memory_t; + +void mailbox_init(void); + +void mailbox_call(uint32_t message[], unsigned char channel); + +uint32_t mailbox_get_board_revision(void); +arm_memory_t mailbox_get_arm_memory(void); + +#endif diff --git a/lab8/c/include/oscos/drivers/mini-uart.h b/lab8/c/include/oscos/drivers/mini-uart.h new file mode 100644 index 000000000..d2af57e07 --- /dev/null +++ b/lab8/c/include/oscos/drivers/mini-uart.h @@ -0,0 +1,14 @@ +#ifndef OSCOS_DRIVERS_MINI_UART_H +#define OSCOS_DRIVERS_MINI_UART_H + +void mini_uart_init(void); + +int mini_uart_recv_byte_nonblock(void); +int mini_uart_send_byte_nonblock(unsigned char b); + +void mini_uart_enable_rx_interrupt(void); +void mini_uart_disable_rx_interrupt(void); +void mini_uart_enable_tx_interrupt(void); +void mini_uart_disable_tx_interrupt(void); + +#endif diff --git a/lab8/c/include/oscos/drivers/pm.h b/lab8/c/include/oscos/drivers/pm.h new file mode 100644 index 000000000..531b09a50 --- /dev/null +++ b/lab8/c/include/oscos/drivers/pm.h @@ -0,0 +1,14 @@ +#ifndef OSCOS_DRIVERS_PM_H +#define OSCOS_DRIVERS_PM_H + +#include +#include + +void pm_init(void); + +void pm_reset(uint32_t tick); +void pm_cancel_reset(void); + +noreturn void pm_reboot(void); + +#endif diff --git a/lab8/c/include/oscos/fs/initramfs.h b/lab8/c/include/oscos/fs/initramfs.h new file mode 100644 index 000000000..8f72722ed --- /dev/null +++ b/lab8/c/include/oscos/fs/initramfs.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_FS_INITRAMFS_H +#define OSCOS_FS_INITRAMFS_H + +#include "oscos/fs/vfs.h" + +extern struct filesystem initramfs; + +#endif diff --git a/lab8/c/include/oscos/fs/tmpfs.h b/lab8/c/include/oscos/fs/tmpfs.h new file mode 100644 index 000000000..41329169a --- /dev/null +++ b/lab8/c/include/oscos/fs/tmpfs.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_FS_TMPFS_H +#define OSCOS_FS_TMPFS_H + +#include "oscos/fs/vfs.h" + +extern struct filesystem tmpfs; + +#endif diff --git a/lab8/c/include/oscos/fs/vfs.h b/lab8/c/include/oscos/fs/vfs.h new file mode 100644 index 000000000..4af926ce2 --- /dev/null +++ b/lab8/c/include/oscos/fs/vfs.h @@ -0,0 +1,78 @@ +#ifndef OSCOS_FS_VFS_H +#define OSCOS_FS_VFS_H + +#include + +#include "oscos/uapi/fcntl.h" // O_* constants. + +struct vnode { + struct mount *mount; + struct vnode_operations *v_ops; + struct file_operations *f_ops; + void *internal; +}; + +// file handle +struct file { + struct vnode *vnode; + size_t f_pos; // RW position of this file handle + struct file_operations *f_ops; + int flags; +}; + +struct mount { + struct vnode *root; + struct filesystem *fs; +}; + +struct filesystem { + const char *name; + int (*setup_mount)(struct filesystem *fs, struct mount *mount); +}; + +struct file_operations { + int (*write)(struct file *file, const void *buf, size_t len); + int (*read)(struct file *file, void *buf, size_t len); + int (*open)(struct vnode *file_node, struct file **target); + int (*close)(struct file *file); + long (*lseek64)(struct file *file, long offset, int whence); +}; + +struct vnode_operations { + int (*lookup)(struct vnode *dir_node, struct vnode **target, + const char *component_name); + int (*create)(struct vnode *dir_node, struct vnode **target, + const char *component_name); + int (*mkdir)(struct vnode *dir_node, struct vnode **target, + const char *component_name); +}; + +extern struct mount rootfs; + +int register_filesystem(struct filesystem *fs); +int vfs_open(const char *pathname, int flags, struct file **target); +int vfs_open_relative(struct vnode *cwd, const char *pathname, int flags, + struct file **target); +int vfs_close(struct file *file); +int vfs_write(struct file *file, const void *buf, size_t len); +int vfs_read(struct file *file, void *buf, size_t len); + +int vfs_mkdir(const char *pathname); +int vfs_mkdir_relative(struct vnode *cwd, const char *pathname); +int vfs_mount(const char *target, const char *filesystem); +int vfs_mount_relative(struct vnode *cwd, const char *target, + const char *filesystem); +int vfs_lookup(const char *pathname, struct vnode **target); +int vfs_lookup_relative(struct vnode *cwd, const char *pathname, + struct vnode **target); + +typedef struct { + struct file *file; + size_t refcnt; +} shared_file_t; + +shared_file_t *shared_file_new(struct file *file); +shared_file_t *shared_file_clone(shared_file_t *shared_file); +void shared_file_drop(shared_file_t *shared_file); + +#endif diff --git a/lab8/c/include/oscos/initrd.h b/lab8/c/include/oscos/initrd.h new file mode 100644 index 000000000..112cf9b83 --- /dev/null +++ b/lab8/c/include/oscos/initrd.h @@ -0,0 +1,117 @@ +/// \file include/oscos/initrd.h +/// \brief Initial ramdisk. +/// +/// Before reading the initial ramdisk, it must be initialized by calling +/// initrd_init(void). + +#ifndef OSCOS_INITRD_H +#define OSCOS_INITRD_H + +#include +#include + +#include "oscos/libc/string.h" +#include "oscos/utils/align.h" + +/// \brief New ASCII Format CPIO archive header. +typedef struct { + char c_magic[6]; + char c_ino[8]; + char c_mode[8]; + char c_uid[8]; + char c_gid[8]; + char c_nlink[8]; + char c_mtime[8]; + char c_filesize[8]; + char c_devmajor[8]; + char c_devminor[8]; + char c_rdevmajor[8]; + char c_rdevminor[8]; + char c_namesize[8]; + char c_check[8]; +} cpio_newc_header_t; + +#define CPIO_NEWC_MODE_FILE_TYPE_MASK ((uint32_t)0170000) +#define CPIO_NEWC_MODE_FILE_TYPE_LNK ((uint32_t)0120000) +#define CPIO_NEWC_MODE_FILE_TYPE_REG ((uint32_t)0100000) +#define CPIO_NEWC_MODE_FILE_TYPE_DIR ((uint32_t)0040000) + +/// \brief New ASCII Format CPIO archive file entry. +typedef struct { + cpio_newc_header_t header; + char payload[]; +} cpio_newc_entry_t; + +/// \brief Pointer to the first file entry of the initial ramdisk. +#define INITRD_HEAD ((const cpio_newc_entry_t *)initrd_get_start()) + +/// \brief The value of the given header of the file entry. +#define CPIO_NEWC_HEADER_VALUE(ENTRY, HEADER) \ + cpio_newc_parse_header_field((ENTRY)->header.c_##HEADER) + +/// \brief The pathname of the file entry. +#define CPIO_NEWC_PATHNAME(ENTRY) ((const char *)(ENTRY)->payload) + +/// \brief The file data of the file entry. +#define CPIO_NEWC_FILE_DATA(ENTRY) \ + ((const char *)ALIGN((uintptr_t)(ENTRY)->payload + \ + CPIO_NEWC_HEADER_VALUE(ENTRY, namesize), \ + 4)) + +/// \brief The filesize of the file entry. +#define CPIO_NEWC_FILESIZE(ENTRY) CPIO_NEWC_HEADER_VALUE(ENTRY, filesize) + +/// \brief Determines if the file entry is the final sentinel entry. +#define CPIO_NEWC_IS_ENTRY_LAST(ENTRY) \ + (strcmp(CPIO_NEWC_PATHNAME(ENTRY), "TRAILER!!!") == 0) + +/// \brief Returns the next file entry of the given file entry. +/// +/// The given file entry must not be the final sentinel entry. +#define CPIO_NEWC_NEXT_ENTRY(ENTRY) \ + (const cpio_newc_entry_t *)ALIGN( \ + (uintptr_t)CPIO_NEWC_FILE_DATA(ENTRY) + CPIO_NEWC_FILESIZE(ENTRY), 4) + +/// \brief Expands to a for loop that loops over each file entry in the initial +/// ramdisk. +/// +/// \param ENTRY_NAME The name of the variable for the file entry. +#define INITRD_FOR_ENTRY(ENTRY_NAME) \ + for (const cpio_newc_entry_t *ENTRY_NAME = INITRD_HEAD; \ + !CPIO_NEWC_IS_ENTRY_LAST(ENTRY_NAME); \ + ENTRY_NAME = CPIO_NEWC_NEXT_ENTRY(ENTRY_NAME)) + +/// \brief Initializes the initial ramdisk. +/// +/// This function obtains the loading address of the initial ramdisk from the +/// device tree and determines if the initial ramdisk is a valid New ASCII +/// format CPIO archive. +/// +/// \return Whether or not initialization is successful. +bool initrd_init(void); + +/// \brief Returns whether or not the initial ramdisk has been successfully +/// initialized. +bool initrd_is_init(void); + +/// \brief Returns the loading address of the initial ramdisk. +const void *initrd_get_start(void); + +/// \brief Returns the ending address of the initial ramdisk. +const void *initrd_get_end(void); + +/// \brief Parses the given header field of a New ASCII format CPIO archive. +/// +/// The given header field must be valid. +/// +/// \see CPIO_NEWC_HEADER_VALUE +uint32_t cpio_newc_parse_header_field(const char field[static 8]); + +/// \brief Finds the file entry in the initial ramdisk corresponding to the +/// given pathname. +/// \param pathname The pathname. +/// \return The pointer to the file entry if the entry is found, or NULL +/// otherwise. +const cpio_newc_entry_t *initrd_find_entry_by_pathname(const char *pathname); + +#endif diff --git a/lab8/c/include/oscos/libc/ctype.h b/lab8/c/include/oscos/libc/ctype.h new file mode 100644 index 000000000..6e5eb134a --- /dev/null +++ b/lab8/c/include/oscos/libc/ctype.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_LIBC_CTYPE_H +#define OSCOS_LIBC_CTYPE_H + +int isdigit(int c); + +#endif diff --git a/lab8/c/include/oscos/libc/inttypes.h b/lab8/c/include/oscos/libc/inttypes.h new file mode 100644 index 000000000..90ba7b7c6 --- /dev/null +++ b/lab8/c/include/oscos/libc/inttypes.h @@ -0,0 +1,9 @@ +#ifndef OSCOS_LIBC_INTTYPES_H +#define OSCOS_LIBC_INTTYPES_H + +#include + +#define PRIx32 "x" +#define PRIx64 "lx" + +#endif diff --git a/lab8/c/include/oscos/libc/stdio.h b/lab8/c/include/oscos/libc/stdio.h new file mode 100644 index 000000000..2cf00d503 --- /dev/null +++ b/lab8/c/include/oscos/libc/stdio.h @@ -0,0 +1,12 @@ +#ifndef OSCOS_LIBC_STDIO_H +#define OSCOS_LIBC_STDIO_H + +#include +#include + +int snprintf(char str[restrict], size_t size, const char *restrict format, ...) + __attribute__((format(printf, 3, 4))); +int vsnprintf(char str[restrict], size_t size, const char *restrict format, + va_list ap) __attribute__((format(printf, 3, 0))); + +#endif diff --git a/lab8/c/include/oscos/libc/stdlib.h b/lab8/c/include/oscos/libc/stdlib.h new file mode 100644 index 000000000..a09506078 --- /dev/null +++ b/lab8/c/include/oscos/libc/stdlib.h @@ -0,0 +1,11 @@ +#ifndef OSCOS_LIBC_STDLIB_H +#define OSCOS_LIBC_STDLIB_H + +#include + +void qsort(void *base, size_t nmemb, size_t size, + int (*compar)(const void *, const void *)); +void qsort_r(void *base, size_t nmemb, size_t size, + int (*compar)(const void *, const void *, void *), void *arg); + +#endif diff --git a/lab8/c/include/oscos/libc/string.h b/lab8/c/include/oscos/libc/string.h new file mode 100644 index 000000000..e57f17902 --- /dev/null +++ b/lab8/c/include/oscos/libc/string.h @@ -0,0 +1,32 @@ +#ifndef OSCOS_LIBC_STRING_H +#define OSCOS_LIBC_STRING_H + +#include + +int memcmp(const void *s1, const void *s2, size_t n); +void *memset(void *s, int c, size_t n); +void *memcpy(void *restrict dest, const void *restrict src, size_t n); +void *memmove(void *dest, const void *src, size_t n); + +int strcmp(const char *s1, const char *s2); +int strncmp(const char *s1, const char *s2, size_t n); + +size_t strlen(const char *s); + +char *strdup(const char *s); +char *strndup(const char *s, size_t n); + +char *strchr(const char *s, int c); + +char *strcpy(char *restrict dst, const char *restrict src); + +// Extensions. + +/// \brief Swap two non-overlapping blocks of memory. +/// +/// \param xs The pointer to the beginning of the first block of memory. +/// \param ys The pointer to the beginning of the second block of memory. +/// \param n The size of the memory blocks. +void memswp(void *restrict xs, void *restrict ys, size_t n); + +#endif diff --git a/lab8/c/include/oscos/mem/malloc.h b/lab8/c/include/oscos/mem/malloc.h new file mode 100644 index 000000000..282ca848f --- /dev/null +++ b/lab8/c/include/oscos/mem/malloc.h @@ -0,0 +1,30 @@ +/// \file include/oscos/mem/malloc.h +/// \brief Dynamic memory allocator. +/// +/// The dynamic memory allocator is a general-purpose memory allocator that +/// allocates physically-contiguous memory. + +#ifndef OSCOS_MEM_MALLOC_H +#define OSCOS_MEM_MALLOC_H + +#include +#include + +/// \brief Initializes the dynamic memory allocator. +void malloc_init(void); + +/// \brief Frees memory allocated using the dynamic memory allocator. +/// +/// \param ptr The pointer to the allocated memory. +void free(void *ptr); + +/// \brief Dynamically allocates memory using the dynamic memory allocator. +/// +/// \param size The requested size in bytes of the allocation. +/// \return The pointer to the allocated memory, or NULL if the request cannot +/// be fulfilled. +void *malloc(size_t size) + __attribute__((alloc_size(1), assume_aligned(alignof(max_align_t)), malloc, + malloc(free, 1))); + +#endif diff --git a/lab8/c/include/oscos/mem/page-alloc.h b/lab8/c/include/oscos/mem/page-alloc.h new file mode 100644 index 000000000..eddee380c --- /dev/null +++ b/lab8/c/include/oscos/mem/page-alloc.h @@ -0,0 +1,87 @@ +/// \file include/oscos/mem/page-alloc.h +/// \brief Page frame allocator. + +#ifndef OSCOS_MEM_PAGE_ALLOC_H +#define OSCOS_MEM_PAGE_ALLOC_H + +#include +#include + +#include "oscos/mem/types.h" + +#define PAGE_ORDER 12 +#define MAX_BLOCK_ORDER 18 + +/// \brief Initializes the page frame allocator. +/// +/// After calling this function, the startup allocator should not be used. +void page_alloc_init(void); + +/// \brief Allocates a block of page frames. +/// \param order The order of the block. +/// \return The page number of the first page, or a negative number if the +/// request cannot be fulfilled. +spage_id_t alloc_pages(size_t order); + +/// \brief Allocates a block of page frames. +/// +/// This function is safe to call only within a critical section. +/// +/// \param order The order of the block. +/// \return The page number of the first page, or a negative number if the +/// request cannot be fulfilled. +spage_id_t alloc_pages_unlocked(size_t order); + +/// \brief Frees a block of page frames. +/// \param page The page number of the first page of the block. +void free_pages(page_id_t page); + +/// \brief Frees a block of page frames. +/// +/// This function is safe to call only within a critical section. +/// +/// \param page The page number of the first page of the block. +void free_pages_unlocked(page_id_t page); + +/// \brief Marks a contiguous range of page frames as either reserved or +/// available. +/// +/// Note that the range can be arbitrary and doesn't need to be a block. +/// +/// \param range The range of page frames to mark. +/// \param is_avail The target reservation status. +void mark_pages(page_id_range_t range, bool is_avail); + +/// \brief Marks a contiguous range of page frames as either reserved or +/// available. +/// +/// Note that the range can be arbitrary and doesn't need to be a block. +/// +/// This function is safe to call only within a critical section. +/// +/// \param range The range of page frames to mark. +/// \param is_avail The target reservation status. +void mark_pages_unlocked(page_id_range_t range, bool is_avail); + +/// \brief Converts the given page ID into its corresponding physical address. +pa_t page_id_to_pa(page_id_t page) __attribute__((pure)); + +/// \brief Converts the given physical address into its corresponding page ID. +/// +/// Before conversion, the physical address is rounded down to the page +/// boundary. I.e., the returned page ID is the page ID whose corresponding +/// physical address range covers the given physical address. +page_id_t pa_to_page_id(pa_t pa) __attribute__((pure)); + +/// \brief Converts the given physical address range into its corresponding page +/// ID range. +/// +/// Before conversion, the starting physical address is rounded down to the page +/// boundary and the ending physical address is rounded up to the page boundary. +/// I.e., the returned page ID range is the smallest page ID range whose +/// corresponding physical address range covers the given physical address +/// range. +page_id_range_t pa_range_to_page_id_range(pa_range_t range) + __attribute__((pure)); + +#endif diff --git a/lab8/c/include/oscos/mem/shared-page.h b/lab8/c/include/oscos/mem/shared-page.h new file mode 100644 index 000000000..2a09ed628 --- /dev/null +++ b/lab8/c/include/oscos/mem/shared-page.h @@ -0,0 +1,12 @@ +#ifndef OSCOS_MEM_SHARED_PAGE_H +#define OSCOS_MEM_SHARED_PAGE_H + +#include "oscos/mem/types.h" + +spage_id_t shared_page_alloc(void); +size_t shared_page_getref(page_id_t page) __attribute__((pure)); +void shared_page_incref(page_id_t page); +void shared_page_decref(page_id_t page); +spage_id_t shared_page_clone_unshare(page_id_t page); + +#endif diff --git a/lab8/c/include/oscos/mem/startup-alloc.h b/lab8/c/include/oscos/mem/startup-alloc.h new file mode 100644 index 000000000..fb626c99d --- /dev/null +++ b/lab8/c/include/oscos/mem/startup-alloc.h @@ -0,0 +1,34 @@ +/// \file include/oscos/mem/startup-alloc.h +/// \brief Startup allocator. +/// +/// The startup allocator is a simple bump pointer allocator that is usable even +/// when no other subsystems are initialized. This memory allocator allocates +/// memory starting from the end of the kernel. Note that memory allocated using +/// this memory allocator cannot be freed. + +#ifndef OSCOS_MEM_STARTUP_ALLOC_H +#define OSCOS_MEM_STARTUP_ALLOC_H + +#include +#include + +#include "oscos/mem/types.h" + +/// \brief Initializes the startup allocator. +void startup_alloc_init(void); + +/// \brief Dynamically allocates memory using the startup allocator. +/// +/// Note that memory allocated by this function cannot be freed. +/// +/// \param size The requested size in bytes of the allocation. +/// \return The pointer to the allocated memory, or NULL if the request cannot +/// be fulfilled. +void *startup_alloc(size_t size) + __attribute__((alloc_size(1), assume_aligned(alignof(max_align_t)), + malloc)); + +/// \brief Gets the memory range allocated through the startup allocator. +va_range_t startup_alloc_get_alloc_range(void) __attribute__((pure)); + +#endif diff --git a/lab8/c/include/oscos/mem/types.h b/lab8/c/include/oscos/mem/types.h new file mode 100644 index 000000000..1deee96e8 --- /dev/null +++ b/lab8/c/include/oscos/mem/types.h @@ -0,0 +1,42 @@ +#ifndef OSCOS_MEM_TYPES_H +#define OSCOS_MEM_TYPES_H + +#include + +#include "oscos/libc/inttypes.h" + +/// \brief Physical address. +typedef uint32_t pa_t; + +/// \brief Maximum value of pa_t. +#define PA_MAX UINT32_MAX + +/// \brief Format specifier for printing a pa_t in lowercase hexadecimal format. +#define PRIxPA PRIx32 + +/// \brief Page ID. +typedef uint32_t page_id_t; + +/// \brief Format specifier for printing a page_id_t in lowercase hexadecimal +/// format. +#define PRIxPAGEID PRIx32 + +/// \brief Signed page ID. +typedef int32_t spage_id_t; + +/// \brief Physical address range. +typedef struct { + pa_t start, end; +} pa_range_t; + +/// \brief Page range. +typedef struct { + page_id_t start, end; +} page_id_range_t; + +/// \brief Virtual address range. +typedef struct { + void *start, *end; +} va_range_t; + +#endif diff --git a/lab8/c/include/oscos/mem/vm.h b/lab8/c/include/oscos/mem/vm.h new file mode 100644 index 000000000..3ee64e514 --- /dev/null +++ b/lab8/c/include/oscos/mem/vm.h @@ -0,0 +1,47 @@ +#ifndef OSCOS_MEM_VM_H +#define OSCOS_MEM_VM_H + +#include "oscos/mem/types.h" +#include "oscos/mem/vm/page-table.h" + +/// \brief Converts a kernel space virtual address into its corresponding +/// physical address. +pa_t kernel_va_to_pa(const void *va) __attribute__((const)); + +/// \brief Converts a physical address into its corresponding kernel space +/// virtual address. +void *pa_to_kernel_va(pa_t pa) __attribute__((const)); + +/// \brief Converts a kernel space virtual address range into its corresponding +/// physical address range. +pa_range_t kernel_va_range_to_pa_range(va_range_t range) __attribute__((const)); + +typedef enum { MEM_REGION_BACKED, MEM_REGION_LINEAR } mem_region_type_t; + +typedef struct { + void *start; + size_t len; + mem_region_type_t type; + const void *backing_storage_start; + size_t backing_storage_len; +} mem_region_t; + +typedef struct { + mem_region_t text_region, stack_region, vc_region; + page_table_entry_t *pgd; +} vm_addr_space_t; + +typedef enum { + VM_MAP_PAGE_SUCCESS, + VM_MAP_PAGE_SEGV, + VM_MAP_PAGE_NOMEM +} vm_map_page_result_t; + +page_table_entry_t *vm_new_pgd(void); +void vm_clone_pgd(page_table_entry_t *pgd); +void vm_drop_pgd(page_table_entry_t *pgd); +vm_map_page_result_t vm_map_page(vm_addr_space_t *addr_space, void *va); +vm_map_page_result_t vm_cow(vm_addr_space_t *addr_space, void *va); +void vm_switch_to_addr_space(const vm_addr_space_t *addr_space); + +#endif diff --git a/lab8/c/include/oscos/mem/vm/kernel-page-tables.h b/lab8/c/include/oscos/mem/vm/kernel-page-tables.h new file mode 100644 index 000000000..d8dd6659e --- /dev/null +++ b/lab8/c/include/oscos/mem/vm/kernel-page-tables.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_MEM_VM_KERNEL_PAGE_TABLES_H +#define OSCOS_MEM_VM_KERNEL_PAGE_TABLES_H + +void vm_setup_finer_granularity_linear_mapping(void); + +#endif diff --git a/lab8/c/include/oscos/mem/vm/page-table.h b/lab8/c/include/oscos/mem/vm/page-table.h new file mode 100644 index 000000000..a89e304c2 --- /dev/null +++ b/lab8/c/include/oscos/mem/vm/page-table.h @@ -0,0 +1,42 @@ +#ifndef OSCOS_VM_PAGE_TABLE_H +#define OSCOS_VM_PAGE_TABLE_H + +#include + +typedef struct { + unsigned _reserved0 : (50 - 48 + 1); + unsigned ignored : (58 - 51 + 1); + bool pxntable : 1; + bool uxntable : 1; + unsigned aptable : 2; + unsigned _reserved1 : 1; +} table_descriptor_upper_t; + +typedef struct { + unsigned _reserved0 : (51 - 48 + 1); + bool contiguous : 1; + bool pxn : 1; + bool uxn : 1; + unsigned ignored : (63 - 55 + 1); +} block_page_descriptor_upper_t; + +typedef struct { + unsigned attr_indx : 3; + bool _reserved0 : 1; + unsigned ap : 2; + unsigned sh : 2; + bool af : 1; + bool ng : 1; +} block_page_descriptor_lower_t; + +typedef struct { + bool b0 : 1; + bool b1 : 1; + unsigned lower : (11 - 2 + 1); + unsigned long long addr : (47 - 12 + 1); + unsigned upper : (63 - 48 + 1); +} page_table_entry_t; + +typedef page_table_entry_t page_table_t[512]; + +#endif diff --git a/lab8/c/include/oscos/panic.h b/lab8/c/include/oscos/panic.h new file mode 100644 index 000000000..ce113c914 --- /dev/null +++ b/lab8/c/include/oscos/panic.h @@ -0,0 +1,23 @@ +#ifndef OSCOS_PANIC_H +#define OSCOS_PANIC_H + +#include + +/// \brief Starts a kernel panic. +#define PANIC(...) panic_begin(__FILE__, __LINE__, __VA_ARGS__) + +/// \brief Starts a kernel panic. +/// +/// In most cases, the PANIC macro should be used instead of directly calling +/// this function. +/// +/// \param file The path of the source file to appear in the panic message. +/// \param line The line number to appear in the panic message. +/// \param format The format string of the panic message. +/// +/// \see PANIC +noreturn void panic_begin(const char *restrict file, int line, + const char *restrict format, ...) + __attribute__((cold, format(printf, 3, 4))); + +#endif diff --git a/lab8/c/include/oscos/sched.h b/lab8/c/include/oscos/sched.h new file mode 100644 index 000000000..bc77420e6 --- /dev/null +++ b/lab8/c/include/oscos/sched.h @@ -0,0 +1,166 @@ +#ifndef OSCOS_SCHED_H +#define OSCOS_SCHED_H + +#include +#include +#include +#include +#include + +#include "oscos/fs/vfs.h" +#include "oscos/mem/types.h" +#include "oscos/mem/vm.h" +#include "oscos/uapi/signal.h" +#include "oscos/xcpt/trap-frame.h" + +#define N_FDS 16 + +typedef struct thread_list_node_t { + struct thread_list_node_t *prev, *next; +} thread_list_node_t; + +typedef struct { + alignas(16) uint64_t words[2]; +} uint128_t; + +typedef struct { + uint128_t v[32]; + alignas(16) struct { + uint64_t fpcr, fpsr; + }; +} thread_fp_simd_ctx_t; + +typedef struct { + alignas(16) union { + struct { + uint64_t r19, r20, r21, r22, r23, r24, r25, r26, r27, r28, r29, pc, + kernel_sp, user_sp; + }; + uint64_t regs[14]; + }; + thread_fp_simd_ctx_t *fp_simd_ctx; +} thread_ctx_t; + +struct process_t; + +typedef struct { + thread_list_node_t list_node; + thread_ctx_t ctx; + size_t id; + struct { + bool is_waiting : 1; + bool is_stopped : 1; + bool is_waken_up_by_signal : 1; + bool is_handling_signal : 1; + } status; + page_id_t stack_page_id; + struct process_t *process; +} thread_t; + +typedef struct process_t { + size_t id; + vm_addr_space_t addr_space; + thread_t *main_thread; + uint32_t pending_signals, blocked_signals; + sighandler_t signal_handlers[32]; + struct vnode *cwd; + shared_file_t *fds[N_FDS]; +} process_t; + +/// \brief Initializes the scheduler and creates the idle thread. +/// +/// \return true if the initialization succeeds. +/// \return false if the initialization fails due to memory shortage. +bool sched_init(void); + +/// \brief Creates a thread. +/// +/// \param task The task to execute in the new thread. +/// \param arg The argument to pass to \p task. +/// \return true if the thread creation succeeds. +/// \return false if the thread creation fails due to memory shortage. +bool thread_create(void (*task)(void *), void *arg); + +/// \brief Terminates the current thread. +/// +/// This function should not be called on the idle thread. +noreturn void thread_exit(void); + +/// \brief Gets the current thread. +thread_t *current_thread(void); + +/// \brief Creates a process and name the current thread the main thread of the +/// process. +/// +/// \return true if the process creation succeeds. +/// \return false if the process creation fails due to memory shortage. +bool process_create(void); + +/// \brief Executes the first ever user program on the current process. +/// +/// This function jumps to the user program and does not return if the operation +/// succeeds. If the operation fails due to memory shortage, this function +/// returns. +/// +/// \param text_start The starting address of where the user program is +/// currently located. (Do not confuse it with the entry point +/// of the user program.) +/// \param text_len The length of the user program. +void exec_first(const void *text_start, size_t text_len); + +/// \brief Executes a user program on the current process. +/// +/// This function jumps to the user program and does not return if the operation +/// succeeds. If the operation fails due to memory shortage, this function +/// returns. +/// +/// The text_page_id field of the current process must be valid. +/// +/// \param text_start The starting address of where the user program is +/// currently located. (Do not confuse it with the entry point +/// of the user program.) +/// \param text_len The length of the user program. +void exec(const void *text_start, size_t text_len); + +/// \brief Forks the current process. +process_t *fork(const extended_trap_frame_t *trap_frame); + +/// \brief Kills zombie threads. +void kill_zombies(void); + +/// \brief Yields CPU for the current thread and runs the scheduler. +void schedule(void); + +/// \brief Puts the current thread to the given wait queue and runs the +/// scheduler. +void suspend_to_wait_queue(thread_list_node_t *wait_queue); + +/// \brief Adds every thread in the given wait queue to the run queue. +void add_all_threads_to_run_queue(thread_list_node_t *wait_queue); + +/// \brief Gets a process by its PID. +process_t *get_process_by_id(size_t pid); + +/// \brief Kills a process. +void kill_process(process_t *process); + +/// \brief Kills all processes. +void kill_all_processes(void); + +/// \brief Sets up periodic scheduling. +void sched_setup_periodic_scheduling(void); + +/// \brief Do what the idle thread should do. +noreturn void idle(void); + +/// \brief Sets the signal handler of a process and returns the old one. +sighandler_t set_signal_handler(process_t *process, int signal, + sighandler_t handler); + +/// \brief Delivers the given signal to the given process. +void deliver_signal(process_t *process, int signal); + +/// \brief Delivers the given signal to all processes. +void deliver_signal_to_all_processes(int signal); + +#endif diff --git a/lab8/c/include/oscos/shell.h b/lab8/c/include/oscos/shell.h new file mode 100644 index 000000000..742f7907e --- /dev/null +++ b/lab8/c/include/oscos/shell.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_SHELL_H +#define OSCOS_SHELL_H + +void run_shell(void); + +#endif diff --git a/lab8/c/include/oscos/timer/delay.h b/lab8/c/include/oscos/timer/delay.h new file mode 100644 index 000000000..49eebc7cd --- /dev/null +++ b/lab8/c/include/oscos/timer/delay.h @@ -0,0 +1,11 @@ +#ifndef OSCOS_TIMER_DELAY_H +#define OSCOS_TIMER_DELAY_H + +#include + +/// \brief Delays for the specified number of nanoseconds. +/// +/// Note that this function may delay for a longer period than specified. +void delay_ns(uint64_t ns); + +#endif diff --git a/lab8/c/include/oscos/timer/timeout.h b/lab8/c/include/oscos/timer/timeout.h new file mode 100644 index 000000000..a2e51e23f --- /dev/null +++ b/lab8/c/include/oscos/timer/timeout.h @@ -0,0 +1,17 @@ +#ifndef OSCOS_TIMER_TIMEOUT_H +#define OSCOS_TIMER_TIMEOUT_H + +#include +#include + +void timeout_init(void); + +bool timeout_add_timer_ns(void (*callback)(void *), void *arg, + uint64_t after_ns); + +bool timeout_add_timer_ticks(void (*callback)(void *), void *arg, + uint64_t after_ticks); + +void xcpt_core_timer_interrupt_handler(void); + +#endif diff --git a/lab8/c/include/oscos/uapi/errno.h b/lab8/c/include/oscos/uapi/errno.h new file mode 100644 index 000000000..d0b2e9a5d --- /dev/null +++ b/lab8/c/include/oscos/uapi/errno.h @@ -0,0 +1,17 @@ +#define ENOENT 2 +#define ESRCH 3 +#define EINTR 4 +#define EIO 5 +#define EBADF 9 +#define ENOMEM 12 +#define EBUSY 16 +#define EEXIST 17 +#define ENODEV 19 +#define ENOTDIR 20 +#define EISDIR 21 +#define EINVAL 22 +#define EMFILE 24 +#define EFBIG 27 +#define EROFS 30 +#define ENOSYS 38 +#define ELOOP 40 diff --git a/lab8/c/include/oscos/uapi/fcntl.h b/lab8/c/include/oscos/uapi/fcntl.h new file mode 100644 index 000000000..d3850760d --- /dev/null +++ b/lab8/c/include/oscos/uapi/fcntl.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_UAPI_FCNTL_H +#define OSCOS_UAPI_FCNTL_H + +#define O_CREAT 00000100 + +#endif diff --git a/lab8/c/include/oscos/uapi/signal.h b/lab8/c/include/oscos/uapi/signal.h new file mode 100644 index 000000000..2e03401d8 --- /dev/null +++ b/lab8/c/include/oscos/uapi/signal.h @@ -0,0 +1,43 @@ +#ifndef OSCOS_UAPI_SIGNAL_H +#define OSCOS_UAPI_SIGNAL_H + +typedef void (*sighandler_t)(void); + +#define SIG_IGN ((sighandler_t)1) +#define SIG_DFL ((sighandler_t)2) + +#define SIGHUP 1 +#define SIGINT 2 +#define SIGQUIT 3 +#define SIGILL 4 +#define SIGTRAP 5 +#define SIGABRT 6 +#define SIGIOT 6 +#define SIGBUS 7 +#define SIGFPE 8 +#define SIGKILL 9 +#define SIGUSR1 10 +#define SIGSEGV 11 +#define SIGUSR2 12 +#define SIGPIPE 13 +#define SIGALRM 14 +#define SIGTERM 15 +#define SIGSTKFLT 16 +#define SIGCHLD 17 +#define SIGCONT 18 +#define SIGSTOP 19 +#define SIGTSTP 20 +#define SIGTTIN 21 +#define SIGTTOU 22 +#define SIGURG 23 +#define SIGXCPU 24 +#define SIGXFSZ 25 +#define SIGVTALRM 26 +#define SIGPROF 27 +#define SIGWINCH 28 +#define SIGIO 29 +#define SIGPWR 30 +#define SIGSYS 31 +#define SIGUNUSED 31 + +#endif diff --git a/lab8/c/include/oscos/uapi/stdio.h b/lab8/c/include/oscos/uapi/stdio.h new file mode 100644 index 000000000..23ebacbc9 --- /dev/null +++ b/lab8/c/include/oscos/uapi/stdio.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_UAPI_STDIO_H +#define OSCOS_UAPI_STDIO_H + +#define SEEK_SET 0 +#define SEEK_END 1 +#define SEEK_CUR 2 + +#endif diff --git a/lab8/c/include/oscos/uapi/sys/syscall.h b/lab8/c/include/oscos/uapi/sys/syscall.h new file mode 100644 index 000000000..8a64fa0ee --- /dev/null +++ b/lab8/c/include/oscos/uapi/sys/syscall.h @@ -0,0 +1,23 @@ +#ifndef OSCOS_UAPI_SYS_SYSCALL_H +#define OSCOS_UAPI_SYS_SYSCALL_H + +#define SYS_getpid 0 +#define SYS_uart_read 1 +#define SYS_uart_write 2 +#define SYS_exec 3 +#define SYS_fork 4 +#define SYS_exit 5 +#define SYS_mbox_call 6 +#define SYS_kill 7 +#define SYS_signal 8 +#define SYS_signal_kill 9 +#define SYS_sigreturn 10 +#define SYS_open 11 +#define SYS_close 12 +#define SYS_write 13 +#define SYS_read 14 +#define SYS_mkdir 15 +#define SYS_mount 16 +#define SYS_chdir 17 + +#endif diff --git a/lab8/c/include/oscos/uapi/unistd.h b/lab8/c/include/oscos/uapi/unistd.h new file mode 100644 index 000000000..80efebdf4 --- /dev/null +++ b/lab8/c/include/oscos/uapi/unistd.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_UAPI_UNISTD_H +#define OSCOS_UAPI_UNISTD_H + +typedef long ssize_t; + +#endif diff --git a/lab8/c/include/oscos/utils/align.h b/lab8/c/include/oscos/utils/align.h new file mode 100644 index 000000000..93d941b9e --- /dev/null +++ b/lab8/c/include/oscos/utils/align.h @@ -0,0 +1,13 @@ +#ifndef OSCOS_UTILS_ALIGN_H +#define OSCOS_UTILS_ALIGN_H + +/// \brief Returns \p X aligned to multiples of \p A. +/// +/// This macro is usually used to align a pointer. When doing so, cast the +/// pointer to `uintptr_t` and cast the result back to the desired pointer type. +/// +/// \param X The value to be aligned. Must be of an arithmetic type. +/// \param A The alignment. +#define ALIGN(X, A) (((X) + (A)-1) / (A) * (A)) + +#endif diff --git a/lab8/c/include/oscos/utils/control-flow.h b/lab8/c/include/oscos/utils/control-flow.h new file mode 100644 index 000000000..856c10c91 --- /dev/null +++ b/lab8/c/include/oscos/utils/control-flow.h @@ -0,0 +1,11 @@ +#ifndef OSCOS_UTILS_CONTROL_FLOW_H +#define OSCOS_UTILS_CONTROL_FLOW_H + +/// \brief Used to tell an operation whether it should exit early or go on as +/// usual. +/// +/// This is modelled after Rust's `std::ops::ControlFlow`, but this doesn't +/// carry values. +typedef enum { CF_CONTINUE, CF_BREAK } control_flow_t; + +#endif diff --git a/lab8/c/include/oscos/utils/core-id.h b/lab8/c/include/oscos/utils/core-id.h new file mode 100644 index 000000000..b23e78a25 --- /dev/null +++ b/lab8/c/include/oscos/utils/core-id.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_UTILS_CORE_ID_H +#define OSCOS_UTILS_CORE_ID_H + +#include + +size_t get_core_id(void); + +#endif diff --git a/lab8/c/include/oscos/utils/critical-section.h b/lab8/c/include/oscos/utils/critical-section.h new file mode 100644 index 000000000..05de74b18 --- /dev/null +++ b/lab8/c/include/oscos/utils/critical-section.h @@ -0,0 +1,20 @@ +#ifndef OSCOS_UTILS_CRITICAL_SECTION_H +#define OSCOS_UTILS_CRITICAL_SECTION_H + +#include "oscos/xcpt.h" + +#define CRITICAL_SECTION_ENTER(DAIF_VAL) \ + do { \ + __asm__ __volatile__("mrs %0, daif" : "=r"(DAIF_VAL)); \ + XCPT_MASK_ALL(); \ + } while (0) + +#define CRITICAL_SECTION_LEAVE(DAIF_VAL) \ + do { \ + /* Prevent the compiler from reordering memory accesses after interrupt \ + unmasking. */ \ + __asm__ __volatile__("" : : : "memory"); \ + __asm__ __volatile__("msr daif, %0" : : "r"(DAIF_VAL)); \ + } while (0) + +#endif diff --git a/lab8/c/include/oscos/utils/endian.h b/lab8/c/include/oscos/utils/endian.h new file mode 100644 index 000000000..1feb8f363 --- /dev/null +++ b/lab8/c/include/oscos/utils/endian.h @@ -0,0 +1,21 @@ +#ifndef OSCOS_UTILS_ENDIAN_H +#define OSCOS_UTILS_ENDIAN_H + +#include +#include + +/// \brief Reverses the byte order in a `uint32_t`. +static inline uint32_t rev_u32(const uint32_t x) { + uint32_t result; + __asm__("rev %w0, %w1" : "=r"(result) : "r"(x)); + return result; +} + +/// \brief Reverses the byte order in a `uint64_t`. +static inline uint64_t rev_u64(const uint64_t x) { + uint64_t result; + __asm__("rev %0, %1" : "=r"(result) : "r"(x)); + return result; +} + +#endif diff --git a/lab8/c/include/oscos/utils/fmt.h b/lab8/c/include/oscos/utils/fmt.h new file mode 100644 index 000000000..d9a68c4f9 --- /dev/null +++ b/lab8/c/include/oscos/utils/fmt.h @@ -0,0 +1,15 @@ +#ifndef OSCOS_UTILS_FMT_H +#define OSCOS_UTILS_FMT_H + +#include + +typedef struct { + void (*putc)(unsigned char, void *); + void (*finalize)(void *); +} printf_vtable_t; + +int vprintf_generic(const printf_vtable_t *vtable, void *arg, + const char *restrict format, va_list ap) + __attribute__((format(printf, 3, 0))); + +#endif diff --git a/lab8/c/include/oscos/utils/heapq.h b/lab8/c/include/oscos/utils/heapq.h new file mode 100644 index 000000000..6abd3d635 --- /dev/null +++ b/lab8/c/include/oscos/utils/heapq.h @@ -0,0 +1,14 @@ +#ifndef OSCOS_UTILS_HEAPQ_H +#define OSCOS_UTILS_HEAPQ_H + +#include + +void heappush(void *restrict base, size_t nmemb, size_t size, + const void *restrict item, + int (*compar)(const void *, const void *, void *), void *arg); + +void heappop(void *restrict base, size_t nmemb, size_t size, + void *restrict item, + int (*compar)(const void *, const void *, void *), void *arg); + +#endif diff --git a/lab8/c/include/oscos/utils/math.h b/lab8/c/include/oscos/utils/math.h new file mode 100644 index 000000000..1e91ed835 --- /dev/null +++ b/lab8/c/include/oscos/utils/math.h @@ -0,0 +1,13 @@ +#ifndef OSCOS_UTILS_MATH_H +#define OSCOS_UTILS_MATH_H + +#include + +/// \brief Returns the ceiling of the log base 2 of the argument. +static inline uint64_t clog2(const uint64_t x) { + uint64_t clz_result; + __asm__("clz %0, %1" : "=r"(clz_result) : "r"(x - 1)); + return 64 - clz_result; +} + +#endif diff --git a/lab8/c/include/oscos/utils/rb.h b/lab8/c/include/oscos/utils/rb.h new file mode 100644 index 000000000..85368c0c6 --- /dev/null +++ b/lab8/c/include/oscos/utils/rb.h @@ -0,0 +1,26 @@ +#ifndef OSCOS_UTILS_RB_H +#define OSCOS_UTILS_RB_H + +#include +#include +#include + +// typedef enum { RB_NC_BLACK, RB_NC_RED } rb_node_colour_t; + +typedef struct rb_node_t { + // rb_node_colour_t colour; + struct rb_node_t *children[2] /*, **parent */; + alignas(16) unsigned char payload[]; +} rb_node_t; + +const void *rb_search(const rb_node_t *root, const void *restrict key, + int (*compar)(const void *, const void *, void *), + void *arg); + +bool rb_insert(rb_node_t **root, size_t size, const void *restrict item, + int (*compar)(const void *, const void *, void *), void *arg); + +void rb_delete(rb_node_t **root, const void *restrict key, + int (*compar)(const void *, const void *, void *), void *arg); + +#endif diff --git a/lab8/c/include/oscos/utils/save-ctx.S b/lab8/c/include/oscos/utils/save-ctx.S new file mode 100644 index 000000000..4485dc9da --- /dev/null +++ b/lab8/c/include/oscos/utils/save-ctx.S @@ -0,0 +1,25 @@ +.macro save_aapcs + stp x0, x1, [sp, -(20 * 8)]! + stp x2, x3, [sp, 2 * 8] + stp x4, x5, [sp, 4 * 8] + stp x6, x7, [sp, 6 * 8] + stp x8, x9, [sp, 8 * 8] + stp x10, x11, [sp, 10 * 8] + stp x12, x13, [sp, 12 * 8] + stp x14, x15, [sp, 14 * 8] + stp x16, x17, [sp, 16 * 8] + stp x18, x30, [sp, 18 * 8] +.endm + +.macro load_aapcs + ldp x18, x30, [sp, 18 * 8] + ldp x16, x17, [sp, 16 * 8] + ldp x14, x15, [sp, 14 * 8] + ldp x12, x13, [sp, 12 * 8] + ldp x10, x11, [sp, 10 * 8] + ldp x8, x9, [sp, 8 * 8] + ldp x6, x7, [sp, 6 * 8] + ldp x4, x5, [sp, 4 * 8] + ldp x2, x3, [sp, 2 * 8] + ldp x0, x1, [sp], (20 * 8) +.endm diff --git a/lab8/c/include/oscos/utils/suspend.h b/lab8/c/include/oscos/utils/suspend.h new file mode 100644 index 000000000..c031cecc6 --- /dev/null +++ b/lab8/c/include/oscos/utils/suspend.h @@ -0,0 +1,19 @@ +#ifndef OSCOS_UTILS_SUSPEND_H +#define OSCOS_UTILS_SUSPEND_H + +#include "oscos/utils/critical-section.h" + +#define WFI_WHILE(COND) \ + do { \ + bool _wfi_while_cond_val = true; \ + while (_wfi_while_cond_val) { \ + uint64_t _wfi_while_daif_val; \ + CRITICAL_SECTION_ENTER(_wfi_while_daif_val); \ + if ((_wfi_while_cond_val = (COND))) { \ + __asm__ __volatile__("wfi"); \ + } \ + CRITICAL_SECTION_LEAVE(_wfi_while_daif_val); \ + } \ + } while (0) + +#endif diff --git a/lab8/c/include/oscos/utils/time.h b/lab8/c/include/oscos/utils/time.h new file mode 100644 index 000000000..12b4c463f --- /dev/null +++ b/lab8/c/include/oscos/utils/time.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_UTILS_TIME_H +#define OSCOS_UTILS_TIME_H + +#define NS_PER_SEC ((uint64_t)1000000000) + +#endif diff --git a/lab8/c/include/oscos/xcpt.h b/lab8/c/include/oscos/xcpt.h new file mode 100644 index 000000000..c5ca13530 --- /dev/null +++ b/lab8/c/include/oscos/xcpt.h @@ -0,0 +1,9 @@ +#ifndef OSCOS_XCPT_H +#define OSCOS_XCPT_H + +#define XCPT_MASK_ALL() __asm__ __volatile__("msr daifset, 0xf") +#define XCPT_UNMASK_ALL() __asm__ __volatile__("msr daifclr, 0xf") + +void xcpt_set_vector_table(void); + +#endif diff --git a/lab8/c/include/oscos/xcpt/task-queue.h b/lab8/c/include/oscos/xcpt/task-queue.h new file mode 100644 index 000000000..4f03ce4e6 --- /dev/null +++ b/lab8/c/include/oscos/xcpt/task-queue.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_XCPT_TASK_QUEUE_H +#define OSCOS_XCPT_TASK_QUEUE_H + +#include + +bool task_queue_add_task(void (*task)(void *), void *arg, int priority); + +#endif diff --git a/lab8/c/include/oscos/xcpt/trap-frame.h b/lab8/c/include/oscos/xcpt/trap-frame.h new file mode 100644 index 000000000..f5a1c11ee --- /dev/null +++ b/lab8/c/include/oscos/xcpt/trap-frame.h @@ -0,0 +1,24 @@ +#ifndef OSCOS_XCPT_TRAP_FRAME_H +#define OSCOS_XCPT_TRAP_FRAME_H + +#include +#include + +typedef struct { + alignas(16) union { + struct { + uint64_t r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, + r15, r16, r17, r18, lr; + }; + uint64_t regs[20]; + }; +} trap_frame_t; + +typedef struct { + alignas(16) struct { + uint64_t spsr, elr; + }; + trap_frame_t trap_frame; +} extended_trap_frame_t; + +#endif diff --git a/lab8/c/src/console.c b/lab8/c/src/console.c new file mode 100644 index 000000000..a41e69a49 --- /dev/null +++ b/lab8/c/src/console.c @@ -0,0 +1,388 @@ +#include "oscos/console.h" + +#include + +#include "oscos/drivers/gpio.h" +#include "oscos/drivers/l2ic.h" +#include "oscos/drivers/mini-uart.h" +#include "oscos/mem/startup-alloc.h" +#include "oscos/utils/critical-section.h" +#include "oscos/utils/fmt.h" +#include "oscos/xcpt.h" + +#define READ_BUF_SZ 1024 +#define WRITE_BUF_SZ 1024 + +static volatile unsigned char *_console_read_buf, *_console_write_buf; +static volatile size_t _console_read_buf_start = 0, _console_read_buf_len = 0, + _console_write_buf_start = 0, _console_write_buf_len = 0; +static void (*volatile _console_read_notify_callback)( + void *) = NULL, + (*volatile _console_write_notify_callback)(void *) = NULL; +static void *volatile _console_read_notify_callback_arg, + *volatile _console_write_notify_callback_arg; + +// Buffer operations. + +static void _console_recv_to_buf(void) { + // Read until the read buffer is full or there are nothing to read. + + int read_result; + while (_console_read_buf_len != READ_BUF_SZ && + (read_result = mini_uart_recv_byte_nonblock()) >= 0) { + _console_read_buf[(_console_read_buf_start + _console_read_buf_len++) % + READ_BUF_SZ] = read_result; + } + + // Disable the receive interrupt if the read buffer is full. Otherwise, the + // interrupt will fire again and again after exception return, blocking the + // main code from executing. + if (_console_read_buf_len == READ_BUF_SZ) { + mini_uart_disable_rx_interrupt(); + } + + // Send notification. + + if (_console_read_buf_len != 0) { + void (*const callback)(void *) = _console_read_notify_callback; + _console_read_notify_callback = NULL; + + if (callback) { + callback(_console_read_notify_callback_arg); + } + } +} + +static void _console_send_from_buf(void) { + while (_console_write_buf_len != 0 && + mini_uart_send_byte_nonblock( + _console_write_buf[_console_write_buf_start]) >= 0) { + _console_write_buf_start = (_console_write_buf_start + 1) % WRITE_BUF_SZ; + _console_write_buf_len--; + } + + // Disable the transmit interrupt if the write buffer is full. Otherwise, the + // interrupt will fire again and again after exception return, blocking the + // main code from executing. + if (_console_write_buf_len == 0) { + mini_uart_disable_tx_interrupt(); + } + + // Send notification. + + if (_console_write_buf_len != WRITE_BUF_SZ) { + void (*const callback)(void *) = _console_write_notify_callback; + _console_write_notify_callback = NULL; + + if (callback) { + callback(_console_write_notify_callback_arg); + } + } +} + +// Raw operations. + +static int _console_recv_byte_nonblock(void) { + if (_console_read_buf_len == 0) + return -1; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const unsigned char result = _console_read_buf[_console_read_buf_start]; + _console_read_buf_start = (_console_read_buf_start + 1) % READ_BUF_SZ; + _console_read_buf_len--; + + CRITICAL_SECTION_LEAVE(daif_val); + + // Re-enable receive interrupt. + mini_uart_enable_rx_interrupt(); + + return result; +} + +static bool _console_send_byte_nonblock(const unsigned char b) { + if (_console_write_buf_len == WRITE_BUF_SZ) + return false; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + _console_write_buf[(_console_write_buf_start + _console_write_buf_len++) % + WRITE_BUF_SZ] = b; + + CRITICAL_SECTION_LEAVE(daif_val); + + // Re-enable transmit interrupt. + mini_uart_enable_tx_interrupt(); + + return true; +} + +static bool _console_send_two_bytes_nonblock(const unsigned char b1, + const unsigned char b2) { + if (_console_write_buf_len > WRITE_BUF_SZ - 2) + return false; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + _console_write_buf[(_console_write_buf_start + _console_write_buf_len++) % + WRITE_BUF_SZ] = b1; + _console_write_buf[(_console_write_buf_start + _console_write_buf_len++) % + WRITE_BUF_SZ] = b2; + + CRITICAL_SECTION_LEAVE(daif_val); + + // Re-enable transmit interrupt. + mini_uart_enable_tx_interrupt(); + + return true; +} + +// Mode switching is implemented by switching the vtables for the two primitive +// operations, getc and putc. + +typedef struct { + int (*getc_nonblock)(void); + bool (*putc_nonblock)(unsigned char); +} console_primop_vtable_t; + +static int _console_getc_nonblock_text_mode(void) { + const int c = _console_recv_byte_nonblock(); + if (c < 0) + return c; + return c == '\r' ? '\n' : c; +} + +static int _console_getc_nonblock_binary_mode(void) { + return _console_recv_byte_nonblock(); +} + +static bool _console_putc_nonblock_text_mode(const unsigned char c) { + if (c == '\n') { + return _console_send_two_bytes_nonblock('\r', '\n'); + } else { + return _console_send_byte_nonblock(c); + } +} + +static bool _console_putc_nonblock_binary_mode(const unsigned char c) { + return _console_send_byte_nonblock(c); +} + +static const console_primop_vtable_t + _primop_vtable_text_mode = {.getc_nonblock = + _console_getc_nonblock_text_mode, + .putc_nonblock = + _console_putc_nonblock_text_mode}, + _primop_vtable_binary_mode = {.getc_nonblock = + _console_getc_nonblock_binary_mode, + .putc_nonblock = + _console_putc_nonblock_binary_mode}, + *_primop_vtable; + +// Public functions. + +void console_init(void) { + gpio_setup_uart0_gpio14(); + mini_uart_init(); + + console_set_mode(CM_TEXT); + + // Enable receive interrupt. + mini_uart_enable_rx_interrupt(); + // Enable AUX interrupt on the L2 interrupt controller. + l2ic_enable_irq_0(INT_L2_IRQ_0_SRC_AUX); + + // Allocate the buffers. + _console_read_buf = startup_alloc(READ_BUF_SZ * sizeof(unsigned char)); + _console_write_buf = startup_alloc(WRITE_BUF_SZ * sizeof(unsigned char)); +} + +void console_set_mode(const console_mode_t mode) { + // ? Should we check the value of `mode`? + // * FIXME: If the check is implemented, update the documentation in + // * `include/oscos/console.h`. + + switch (mode) { + case CM_TEXT: + _primop_vtable = &_primop_vtable_text_mode; + break; + + case CM_BINARY: + _primop_vtable = &_primop_vtable_binary_mode; + break; + + default: + __builtin_unreachable(); + } +} + +unsigned char console_getc(void) { + int read_result = -1; + uint64_t daif_val; + + while (read_result < 0) { + CRITICAL_SECTION_ENTER(daif_val); + + if ((read_result = console_getc_nonblock()) < 0) { + __asm__ __volatile__("wfi"); + _console_recv_to_buf(); + } + + CRITICAL_SECTION_LEAVE(daif_val); + } + + return read_result; +} + +int console_getc_nonblock(void) { return _primop_vtable->getc_nonblock(); } + +size_t console_read_nonblock(void *const buf, const size_t count) { + unsigned char *const buf_c = (unsigned char *)buf; + + size_t n_chars_read; + int read_result; + + for (n_chars_read = 0; + n_chars_read < count && (read_result = console_getc_nonblock()) >= 0; + n_chars_read++) { + buf_c[n_chars_read] = read_result; + } + + return n_chars_read; +} + +unsigned char console_putc(const unsigned char c) { + bool putc_successful = false; + uint64_t daif_val; + + while (!putc_successful) { + CRITICAL_SECTION_ENTER(daif_val); + + if (!(putc_successful = console_putc_nonblock(c) >= 0)) { + __asm__ __volatile__("wfi"); + _console_send_from_buf(); + } + + CRITICAL_SECTION_LEAVE(daif_val); + } + + return c; +} + +int console_putc_nonblock(const unsigned char c) { + const bool putc_successful = _primop_vtable->putc_nonblock(c); + return putc_successful ? c : -1; +} + +size_t console_write(const void *const buf, const size_t count) { + const unsigned char *const buf_c = buf; + for (size_t i = 0; i < count; i++) { + console_putc(buf_c[i]); + } + return count; +} + +size_t console_write_nonblock(const void *const buf, const size_t count) { + const unsigned char *const buf_c = buf; + + size_t n_chars_written; + bool putc_successful; + + for (n_chars_written = 0; + n_chars_written < count && + (putc_successful = console_putc_nonblock(buf_c[n_chars_written]) >= 0); + n_chars_written++) + ; + + return n_chars_written; +} + +void console_fputs(const char *const s) { + for (const char *c = s; *c; c++) { + console_putc(*c); + } +} + +void console_puts(const char *const s) { + console_fputs(s); + console_putc('\n'); +} + +int console_printf(const char *const restrict format, ...) { + va_list ap; + va_start(ap, format); + + const int result = console_vprintf(format, ap); + + va_end(ap); + return result; +} + +static void _console_printf_putc(const unsigned char c, void *const _arg) { + (void)_arg; + + console_putc(c); +} + +static void _console_printf_finalize(void *const _arg) { (void)_arg; } + +static const printf_vtable_t _console_printf_vtable = { + .putc = _console_printf_putc, .finalize = _console_printf_finalize}; + +int console_vprintf(const char *const restrict format, va_list ap) { + return vprintf_generic(&_console_printf_vtable, NULL, format, ap); +} + +bool console_notify_read_ready(void (*const callback)(void *), + void *const arg) { + if (_console_read_notify_callback) + return false; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + _console_read_notify_callback = callback; + _console_read_notify_callback_arg = arg; + + CRITICAL_SECTION_LEAVE(daif_val); + return true; +} + +bool console_notify_write_ready(void (*const callback)(void *), + void *const arg) { + if (_console_write_notify_callback) + return false; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + _console_write_notify_callback = callback; + _console_write_notify_callback_arg = arg; + + CRITICAL_SECTION_LEAVE(daif_val); + return true; +} + +void console_flush_write_buffer(void) { + bool suspend_cond_val = true; + uint64_t daif_val; + + while (suspend_cond_val) { + CRITICAL_SECTION_ENTER(daif_val); + + if ((suspend_cond_val = _console_write_buf_len > 0)) { + __asm__ __volatile__("wfi"); + _console_send_from_buf(); + } + + CRITICAL_SECTION_LEAVE(daif_val); + } +} + +void mini_uart_interrupt_handler(void) { + _console_recv_to_buf(); + _console_send_from_buf(); +} diff --git a/lab8/c/src/devicetree.c b/lab8/c/src/devicetree.c new file mode 100644 index 000000000..8a4d75275 --- /dev/null +++ b/lab8/c/src/devicetree.c @@ -0,0 +1,129 @@ +#include "oscos/devicetree.h" + +#include + +static const char *_dtb_start = NULL; + +bool devicetree_init(const void *const dtb_start) { + // TODO: More thoroughly validate the DTB. + + if (rev_u32(((const fdt_header_t *)dtb_start)->magic) == 0xd00dfeed) { + _dtb_start = dtb_start; + return true; + } else { + _dtb_start = NULL; + return false; + } +} + +bool devicetree_is_init(void) { return _dtb_start; } + +const char *fdt_get_start(void) { return _dtb_start; } + +const char *fdt_get_end(void) { + return _dtb_start + rev_u32(((const fdt_header_t *)_dtb_start)->totalsize); +} + +const fdt_item_t *fdt_next_item(const fdt_item_t *const item) { + switch (FDT_TOKEN(item)) { + case FDT_BEGIN_NODE: { + const fdt_item_t *curr; + FDT_FOR_ITEM_(item, curr); + return curr + 1; + } + case FDT_PROP: { + const fdt_prop_t *const prop = (const fdt_prop_t *)(item->payload); + return (const fdt_item_t *)ALIGN( + (uintptr_t)(prop->value + rev_u32(prop->len)), 4); + } + case FDT_NOP: + return item + 1; + default: + __builtin_unreachable(); + } +} + +static const fdt_item_t * +_fdt_traverse_rec(const fdt_item_t *const node, + const fdt_traverse_parent_list_node_t *const parent, + fdt_traverse_callback_t *const callback, void *const arg) { + const control_flow_t result = callback(arg, node, parent); + if (result == CF_CONTINUE) { + // No-op. + } else if (result == CF_BREAK) { + return NULL; + } else { + __builtin_unreachable(); + } + + const fdt_item_t *item; + for (item = FDT_ITEMS_START(node); !FDT_ITEM_IS_END(item);) { + if (FDT_TOKEN(item) == FDT_BEGIN_NODE) { + const fdt_traverse_parent_list_node_t next_parent = {.node = node, + .parent = parent}; + const fdt_item_t *const next_item = + _fdt_traverse_rec(item, &next_parent, callback, arg); + if (!next_item) + return NULL; + item = next_item; + } else { + item = fdt_next_item(item); + } + } + + return item + 1; +} + +void fdt_traverse(fdt_traverse_callback_t *const callback, void *const arg) { + const fdt_item_t *const root_node = + (const fdt_item_t *)(_dtb_start + + rev_u32(((const fdt_header_t *)_dtb_start) + ->off_dt_struct)); + _fdt_traverse_rec(root_node, NULL, callback, arg); +} + +fdt_n_address_size_cells_t +fdt_get_n_address_size_cells(const fdt_item_t *const node) { + uint32_t n_address_cells = 2, n_size_cells = 1; + bool n_address_cells_done = false, n_size_cells_done = false; + FDT_FOR_ITEM(node, item) { + if (FDT_TOKEN(item) == FDT_PROP) { + const fdt_prop_t *const prop = (const fdt_prop_t *)item->payload; + if (strcmp(FDT_PROP_NAME(prop), "#address-cells") == 0) { + n_address_cells = rev_u32(*(const uint32_t *)FDT_PROP_VALUE(prop)); + n_address_cells_done = true; + } else if (strcmp(FDT_PROP_NAME(prop), "#size-cells") == 0) { + n_size_cells = rev_u32(*(const uint32_t *)FDT_PROP_VALUE(prop)); + n_size_cells_done = true; + } + } + if (n_address_cells_done && n_size_cells_done) + break; + } + + return (fdt_n_address_size_cells_t){.n_address_cells = n_address_cells, + .n_size_cells = n_size_cells}; +} + +fdt_read_reg_result_t fdt_read_reg(const fdt_prop_t *const prop, + const fdt_n_address_size_cells_t n_cells) { + const uint32_t *arr = (const uint32_t *)FDT_PROP_VALUE(prop); + + uintmax_t address = 0; + bool address_overflow = false; + for (uint32_t i = 0; i < n_cells.n_address_cells; i++) { + address_overflow = address_overflow || (address >> 32) != 0; + address = address << 32 | rev_u32(*arr++); + } + + uintmax_t size = 0; + bool size_overflow = false; + for (uint32_t i = 0; i < n_cells.n_size_cells; i++) { + size_overflow = size_overflow || (size >> 32) != 0; + size = size << 32 | rev_u32(*arr++); + } + + return (fdt_read_reg_result_t){.value = {.address = address, .size = size}, + .address_overflow = address_overflow, + .size_overflow = size_overflow}; +} diff --git a/lab8/c/src/drivers/aux.c b/lab8/c/src/drivers/aux.c new file mode 100644 index 000000000..c40639f09 --- /dev/null +++ b/lab8/c/src/drivers/aux.c @@ -0,0 +1,28 @@ +#include "oscos/drivers/aux.h" + +#include + +#include "oscos/drivers/board.h" + +#define AUX_REG_BASE ((void *)((char *)PERIPHERAL_BASE + 0x215000)) + +typedef struct { + const volatile uint32_t irq; + volatile uint32_t enb; +} aux_reg_t; + +#define AUX_REG ((aux_reg_t *)AUX_REG_BASE) + +#define AUX_ENB_MINI_UART_ENABLE ((uint32_t)(1 << 0)) + +void aux_init(void) { + // No-op. +} + +void aux_enable_mini_uart(void) { + PERIPHERAL_WRITE_BARRIER(); + + AUX_REG->enb |= AUX_ENB_MINI_UART_ENABLE; + + PERIPHERAL_READ_BARRIER(); +} diff --git a/lab8/c/src/drivers/gpio.c b/lab8/c/src/drivers/gpio.c new file mode 100644 index 000000000..9f8802011 --- /dev/null +++ b/lab8/c/src/drivers/gpio.c @@ -0,0 +1,105 @@ +#include "oscos/drivers/gpio.h" + +#include + +#include "oscos/drivers/board.h" +#include "oscos/timer/delay.h" + +#define GPIO_REG_BASE ((void *)((char *)PERIPHERAL_BASE + 0x200000)) + +typedef struct { + volatile uint32_t fsel[6]; + const volatile uint32_t _reserved0; + volatile uint32_t set[2]; + const volatile uint32_t _reserved1; + volatile uint32_t clr[2]; + const volatile uint32_t _reserved2; + const volatile uint32_t lev[2]; + const volatile uint32_t _reserved3; + volatile uint32_t eds[2]; + const volatile uint32_t _reserved4; + volatile uint32_t ren[2]; + const volatile uint32_t _reserved5; + volatile uint32_t fen[2]; + const volatile uint32_t _reserved6; + volatile uint32_t hen[2]; + const volatile uint32_t _reserved7; + volatile uint32_t len[2]; + const volatile uint32_t _reserved8; + volatile uint32_t aren[2]; + const volatile uint32_t _reserved9; + volatile uint32_t afen[2]; + const volatile uint32_t _reserved10; + volatile uint32_t pud; + volatile uint32_t pudclk[2]; +} gpio_reg_t; + +#define GPIO_REG ((gpio_reg_t *)GPIO_REG_BASE) + +#define GPIO_FSEL_FSEL4_POSN 12 +#define GPIO_FSEL_FSEL4_MASK ((uint32_t)((uint32_t)0x7 << GPIO_FSEL_FSEL4_POSN)) +#define GPIO_FSEL_FSEL5_POSN 15 +#define GPIO_FSEL_FSEL5_MASK ((uint32_t)((uint32_t)0x7 << GPIO_FSEL_FSEL5_POSN)) +#define GPIO_FSEL_FSEL_INPUT ((uint32_t)0x0) +#define GPIO_FSEL_FSEL_ALT5 ((uint32_t)0x2) +#define GPIO_FSEL_FSEL4_INPUT \ + ((uint32_t)(GPIO_FSEL_FSEL_INPUT << GPIO_FSEL_FSEL4_POSN)) +#define GPIO_FSEL_FSEL4_ALT5 \ + ((uint32_t)(GPIO_FSEL_FSEL_ALT5 << GPIO_FSEL_FSEL4_POSN)) +#define GPIO_FSEL_FSEL5_INPUT \ + ((uint32_t)(GPIO_FSEL_FSEL_INPUT << GPIO_FSEL_FSEL5_POSN)) +#define GPIO_FSEL_FSEL5_ALT5 \ + ((uint32_t)(GPIO_FSEL_FSEL_ALT5 << GPIO_FSEL_FSEL5_POSN)) + +#define GPIO_PUD_PUD_OFF ((uint32_t)0x0) +#define GPIO_PUD_PUD_PULL_UP ((uint32_t)0x2) + +void gpio_init(void) { + // No-op. +} + +void gpio_setup_uart0_gpio14(void) { + PERIPHERAL_WRITE_BARRIER(); + + // Set GPIO pin 14 & 15 to use alternate function 5 ({T,R}XD1). + GPIO_REG->fsel[1] = + (GPIO_REG->fsel[1] & ~(GPIO_FSEL_FSEL4_MASK | GPIO_FSEL_FSEL5_MASK)) | + (GPIO_FSEL_FSEL4_ALT5 | GPIO_FSEL_FSEL5_ALT5); + + // Setup the GPIO pull up/down resistors on pin 14 & 15. + // Pin 14 (TXD1): Disabled. + // Pin 15 (RXD1): Pull up. + // + // Instead of disabling the pull up/down resistors on pin 15 as specified in + // lab 1, we instead pull it up to ensure that mini UART doesn't read in + // garbage data when the pin is not connected. + // + // The delay period of 1 μs is calculated by dividing the required delay + // period of 150 clock cycles (as specified in [bcm2835-datasheet]) by 150 + // MHz, the nominal core frequency mentioned in [bcm2835-datasheet], pp. 34. + // We believe 150 MHz should be used instead of the actual core frequency of + // 250 MHz because the setup/hold time of a digital circuit is usually + // specified in terms of real time (e.g. nanoseconds) instead of in clock + // cycles. The specified delay period of 150 clock cycles might have been + // derived by multiplying the actual required delay period of 1 μs by the + // nominal core frequency of 150 MHz. + // + // [bcm2835-datasheet]: + // https://datasheets.raspberrypi.com/bcm2835/bcm2835-peripherals.pdf + + GPIO_REG->pud = GPIO_PUD_PUD_OFF; + delay_ns(1000); + GPIO_REG->pudclk[0] = 1 << 14; + delay_ns(1000); + GPIO_REG->pud = 0; + GPIO_REG->pudclk[0] = 0; + + GPIO_REG->pud = GPIO_PUD_PUD_PULL_UP; + delay_ns(1000); + GPIO_REG->pudclk[0] = 1 << 15; + delay_ns(1000); + GPIO_REG->pud = 0; + GPIO_REG->pudclk[0] = 0; + + PERIPHERAL_READ_BARRIER(); +} diff --git a/lab8/c/src/drivers/l1ic.c b/lab8/c/src/drivers/l1ic.c new file mode 100644 index 000000000..9af46c781 --- /dev/null +++ b/lab8/c/src/drivers/l1ic.c @@ -0,0 +1,37 @@ +#include "oscos/drivers/l1ic.h" + +#include "oscos/drivers/board.h" + +#define CORE_TIMER_IRQCNTL \ + ((volatile uint32_t(*)[4])((char *)ARM_LOCAL_PERIPHERAL_BASE + 0x40)) +#define CORE_IRQ_SOURCE \ + ((volatile uint32_t(*)[4])((char *)ARM_LOCAL_PERIPHERAL_BASE + 0x60)) +#define CORE_FIQ_SOURCE \ + ((volatile uint32_t(*)[4])((char *)ARM_LOCAL_PERIPHERAL_BASE + 0x70)) + +#define CORE_TIMER_IRQCNTL_TIMER0_IRQ ((uint32_t)(1 << 0)) +#define CORE_TIMER_IRQCNTL_TIMER1_IRQ ((uint32_t)(1 << 1)) +#define CORE_TIMER_IRQCNTL_TIMER2_IRQ ((uint32_t)(1 << 2)) +#define CORE_TIMER_IRQCNTL_TIMER3_IRQ ((uint32_t)(1 << 3)) +#define CORE_TIMER_IRQCNTL_TIMER0_FIQ ((uint32_t)(1 << 4)) +#define CORE_TIMER_IRQCNTL_TIMER1_FIQ ((uint32_t)(1 << 5)) +#define CORE_TIMER_IRQCNTL_TIMER2_FIQ ((uint32_t)(1 << 6)) +#define CORE_TIMER_IRQCNTL_TIMER3_FIQ ((uint32_t)(1 << 7)) + +void l1ic_init(void) { + // No-op. +} + +uint32_t l1ic_get_int_src(const size_t core_id) { + const uint32_t result = (*CORE_IRQ_SOURCE)[core_id]; + + PERIPHERAL_READ_BARRIER(); + return result; +} + +void l1ic_enable_core_timer_irq(size_t core_id) { + PERIPHERAL_WRITE_BARRIER(); + + // nCNTPNSIRQ IRQ control = 1 + (*CORE_TIMER_IRQCNTL)[core_id] = CORE_TIMER_IRQCNTL_TIMER1_IRQ; +} diff --git a/lab8/c/src/drivers/l2ic.c b/lab8/c/src/drivers/l2ic.c new file mode 100644 index 000000000..13c15e94c --- /dev/null +++ b/lab8/c/src/drivers/l2ic.c @@ -0,0 +1,36 @@ +#include "oscos/drivers/l2ic.h" + +#include "oscos/drivers/board.h" + +#define L2IC_REG_BASE ((void *)((char *)PERIPHERAL_BASE + 0xb200)) + +typedef struct { + const volatile uint32_t irq_basic_pending; + const volatile uint32_t irq_pending[2]; + volatile uint32_t fiq_control; + volatile uint32_t enable_irqs[2]; + volatile uint32_t enable_basic_irqs; + volatile uint32_t disable_irqs[2]; + volatile uint32_t disable_basic_irqs; +} l2ic_reg_t; + +#define L2IC_REG ((l2ic_reg_t *)L2IC_REG_BASE) + +void l2ic_init(void) { + // No-op. +} + +uint32_t l2ic_get_pending_irq_0(void) { + const uint32_t result = L2IC_REG->irq_pending[0]; + + PERIPHERAL_READ_BARRIER(); + return result; +} + +void l2ic_enable_irq_0(uint32_t mask) { + PERIPHERAL_WRITE_BARRIER(); + + L2IC_REG->enable_irqs[0] |= mask; + + PERIPHERAL_READ_BARRIER(); +} diff --git a/lab8/c/src/drivers/mailbox.c b/lab8/c/src/drivers/mailbox.c new file mode 100644 index 000000000..0cb84d408 --- /dev/null +++ b/lab8/c/src/drivers/mailbox.c @@ -0,0 +1,84 @@ +#include "oscos/drivers/mailbox.h" + +#include + +#include "oscos/drivers/board.h" +#include "oscos/mem/vm.h" + +#define MAILBOX_REG_BASE ((void *)((char *)PERIPHERAL_BASE + 0xb880)) + +typedef struct { + volatile uint32_t read_write; + const volatile uint32_t _reserved[3]; + volatile uint32_t peek; + volatile uint32_t sender; + volatile uint32_t status; + volatile uint32_t config; +} mailbox_reg_t; + +#define MAILBOX_REGS ((mailbox_reg_t *)MAILBOX_REG_BASE) + +#define MAILBOX_STATUS_EMPTY_MASK ((uint32_t)(1 << 30)) +#define MAILBOX_STATUS_FULL_MASK ((uint32_t)(1 << 31)) + +#define GET_BOARD_REVISION ((uint32_t)0x00010002) +#define GET_ARM_MEMORY ((uint32_t)0x00010005) +#define REQUEST_CODE ((uint32_t)0x00000000) +#define REQUEST_SUCCEED ((uint32_t)0x80000000) +#define REQUEST_FAILED ((uint32_t)0x80000001) +#define TAG_REQUEST_CODE ((uint32_t)0x00000000) +#define END_TAG ((uint32_t)0x00000000) + +void mailbox_init(void) { + // No-op. +} + +void mailbox_call(uint32_t message[], const unsigned char channel) { + PERIPHERAL_WRITE_BARRIER(); + + const uint32_t mailbox_write_data = kernel_va_to_pa(message) | channel; + + while (MAILBOX_REGS[1].status & MAILBOX_STATUS_FULL_MASK) + ; + MAILBOX_REGS[1].read_write = mailbox_write_data; + + for (;;) { + while (MAILBOX_REGS[0].status & MAILBOX_STATUS_EMPTY_MASK) + ; + const uint32_t mailbox_read_data = MAILBOX_REGS[0].read_write; + + if (mailbox_write_data == mailbox_read_data) + break; + } + + PERIPHERAL_READ_BARRIER(); +} + +uint32_t mailbox_get_board_revision(void) { + alignas(16) uint32_t mailbox[7] = {7 * sizeof(uint32_t), + REQUEST_CODE, + GET_BOARD_REVISION, + 4, + TAG_REQUEST_CODE, + 0, + END_TAG}; + + mailbox_call(mailbox, MAILBOX_CHANNEL_PROPERTY_TAGS_ARM_TO_VC); + + return mailbox[5]; +} + +arm_memory_t mailbox_get_arm_memory(void) { + alignas(16) uint32_t mailbox[8] = {8 * sizeof(uint32_t), + REQUEST_CODE, + GET_ARM_MEMORY, + 8, + TAG_REQUEST_CODE, + 0, + 0, + END_TAG}; + + mailbox_call(mailbox, MAILBOX_CHANNEL_PROPERTY_TAGS_ARM_TO_VC); + + return (arm_memory_t){.base = mailbox[5], .size = mailbox[6]}; +} diff --git a/lab8/c/src/drivers/mini-uart.c b/lab8/c/src/drivers/mini-uart.c new file mode 100644 index 000000000..c23500049 --- /dev/null +++ b/lab8/c/src/drivers/mini-uart.c @@ -0,0 +1,130 @@ +#include "oscos/drivers/mini-uart.h" + +#include + +#include "oscos/drivers/aux.h" +#include "oscos/drivers/board.h" + +#define MINI_UART_REG_BASE ((void *)((char *)PERIPHERAL_BASE + 0x215040)) + +typedef struct { + volatile uint32_t io; + volatile uint32_t ier; + volatile uint32_t iir; + volatile uint32_t lcr; + volatile uint32_t mcr; + const volatile uint32_t lsr; + const volatile uint32_t msr; + volatile uint32_t scratch; + volatile uint32_t cntl; + const volatile uint32_t stat; + volatile uint32_t baud; +} mini_uart_reg_t; + +#define MINI_UART_REG ((mini_uart_reg_t *)MINI_UART_REG_BASE) + +// N. B. The interrupt bits specified in the datasheet are incorrect. The values +// listed here are correct. See [bcm2837-datasheet-errata]. +// [bcm2835-datasheet-errata]: https://elinux.org/BCM2835_datasheet_errata + +#define MINI_UART_IER_ENABLE_RECEIVE_INTERRUPT ((uint32_t)(1 << 0)) +#define MINI_UART_IER_ENABLE_TRANSMIT_INTERRUPT ((uint32_t)(1 << 1)) + +#define MINI_UART_LSR_DATA_READY ((uint32_t)(1 << 0)) +#define MINI_UART_LSR_TRANSMITTER_IDLE ((uint32_t)(1 << 6)) +#define MINI_UART_LSR_TRANSMITTER_EMPTY ((uint32_t)(1 << 5)) + +void mini_uart_init(void) { + // The initialization procedure is taken from + // https://oscapstone.github.io/labs/hardware/uart.html. + + // Enable mini UART. + aux_enable_mini_uart(); + + PERIPHERAL_WRITE_BARRIER(); + + // Disable TX and RX. + MINI_UART_REG->cntl = 0; + // Disable interrupt. + MINI_UART_REG->ier = 0; + // Set the data size to 8 bits. + // N. B. The datasheet [bcm2835-datasheet] incorrectly indicates that only bit + // 0 needs to be set. In fact, bits [1:0] need to be set to 3. See + // [bcm2835-datasheet-errata], #p14. + MINI_UART_REG->lcr = 3; + // Disable auto flow control. + MINI_UART_REG->mcr = 0; + // Set baud rate to 115200. + MINI_UART_REG->baud = 270; + // Clear the transmit and receive FIFOs. + MINI_UART_REG->iir = 6; + // Enable TX and RX. + MINI_UART_REG->cntl = 3; + + PERIPHERAL_READ_BARRIER(); +} + +int mini_uart_recv_byte_nonblock(void) { + int result; + + if (!(MINI_UART_REG->lsr & MINI_UART_LSR_DATA_READY)) { + result = -1; + goto end; + } + + result = (unsigned char)MINI_UART_REG->io; + +end: + PERIPHERAL_READ_BARRIER(); + return result; +} + +int mini_uart_send_byte_nonblock(const unsigned char b) { + PERIPHERAL_WRITE_BARRIER(); + + int result; + + if (!(MINI_UART_REG->lsr & MINI_UART_LSR_TRANSMITTER_EMPTY)) { + result = -1; + goto end; + } + + MINI_UART_REG->io = b; + result = 0; + +end: + PERIPHERAL_READ_BARRIER(); + return result; +} + +void mini_uart_enable_rx_interrupt(void) { + PERIPHERAL_WRITE_BARRIER(); + + MINI_UART_REG->ier |= MINI_UART_IER_ENABLE_RECEIVE_INTERRUPT; + + PERIPHERAL_READ_BARRIER(); +} + +void mini_uart_disable_rx_interrupt(void) { + PERIPHERAL_WRITE_BARRIER(); + + MINI_UART_REG->ier &= ~MINI_UART_IER_ENABLE_RECEIVE_INTERRUPT; + + PERIPHERAL_READ_BARRIER(); +} + +void mini_uart_enable_tx_interrupt(void) { + PERIPHERAL_WRITE_BARRIER(); + + MINI_UART_REG->ier |= MINI_UART_IER_ENABLE_TRANSMIT_INTERRUPT; + + PERIPHERAL_READ_BARRIER(); +} + +void mini_uart_disable_tx_interrupt(void) { + PERIPHERAL_WRITE_BARRIER(); + + MINI_UART_REG->ier &= ~MINI_UART_IER_ENABLE_TRANSMIT_INTERRUPT; + + PERIPHERAL_READ_BARRIER(); +} diff --git a/lab8/c/src/drivers/pm.c b/lab8/c/src/drivers/pm.c new file mode 100644 index 000000000..ca4a85fd1 --- /dev/null +++ b/lab8/c/src/drivers/pm.c @@ -0,0 +1,85 @@ +#include "oscos/drivers/pm.h" + +#include + +#include "oscos/drivers/board.h" + +// Information related to the PM registers can be retrieved from [bcm2835-regs], +// #PM. +// +// [bcm2835-regs]: https://elinux.org/BCM2835_registers#PM + +#define PM_REG_BASE ((void *)((char *)PERIPHERAL_BASE + 0x100000)) + +typedef struct { + volatile uint32_t gnric; + volatile uint32_t audio; + const volatile uint32_t _reserved0[4]; + const volatile uint32_t status; + volatile uint32_t rstc; + volatile uint32_t rsts; + volatile uint32_t wdot; + volatile uint32_t pads0; + volatile uint32_t pads2; + volatile uint32_t pads3; + volatile uint32_t pads4; + volatile uint32_t pads5; + volatile uint32_t pads6; + const volatile uint32_t _reserved1; + volatile uint32_t cam0; + volatile uint32_t cam1; + volatile uint32_t cpp2tx; + volatile uint32_t dsi0; + volatile uint32_t dsi1; + volatile uint32_t hdmi; + volatile uint32_t usb; + volatile uint32_t pxldo; + volatile uint32_t pxbg; + volatile uint32_t dft; + volatile uint32_t smps; + volatile uint32_t xosc; + volatile uint32_t sparew; + const volatile uint32_t sparer; + volatile uint32_t avs_rstdr; + volatile uint32_t avs_stat; + volatile uint32_t avs_event; + volatile uint32_t avs_inten; + const volatile uint32_t _reserved2[29]; + const volatile uint32_t dummy; + const volatile uint32_t _reserved3[2]; + volatile uint32_t image; + volatile uint32_t grafx; + volatile uint32_t proc; +} pm_reg_t; + +#define PM_REG ((pm_reg_t *)PM_REG_BASE) + +#define PM_PASSWORD 0x5a000000 + +void pm_init(void) { + // No-op. +} + +void pm_reset(const uint32_t tick) { + PERIPHERAL_WRITE_BARRIER(); + + PM_REG->rstc = PM_PASSWORD | 0x20; + PM_REG->wdot = PM_PASSWORD | tick; + + PERIPHERAL_READ_BARRIER(); +} + +void pm_cancel_reset(void) { + PERIPHERAL_WRITE_BARRIER(); + + PM_REG->rstc = PM_PASSWORD | 0; + PM_REG->wdot = PM_PASSWORD | 0; + + PERIPHERAL_READ_BARRIER(); +} + +noreturn void pm_reboot(void) { + pm_reset(1); + for (;;) + ; +} diff --git a/lab8/c/src/fs/initramfs.c b/lab8/c/src/fs/initramfs.c new file mode 100644 index 000000000..373dc65d5 --- /dev/null +++ b/lab8/c/src/fs/initramfs.c @@ -0,0 +1,333 @@ +#include "oscos/fs/initramfs.h" + +#include "oscos/initrd.h" +#include "oscos/libc/string.h" +#include "oscos/mem/malloc.h" +#include "oscos/uapi/errno.h" +#include "oscos/uapi/stdio.h" +#include "oscos/utils/critical-section.h" +#include "oscos/utils/rb.h" + +typedef struct { + const char *component_name; + struct vnode *vnode; +} initramfs_child_vnode_entry_t; + +typedef struct { + struct vnode *parent; + const cpio_newc_entry_t *entry; + rb_node_t *child_vnodes; // Created lazily. +} initramfs_internal_t; + +static int _initramfs_setup_mount(struct filesystem *fs, struct mount *mount); + +static int _initramfs_write(struct file *file, const void *buf, size_t len); +static int _initramfs_read(struct file *file, void *buf, size_t len); +static int _initramfs_open(struct vnode *file_node, struct file **target); +static int _initramfs_close(struct file *file); +static long _initramfs_lseek64(struct file *file, long offset, int whence); + +static int _initramfs_lookup(struct vnode *dir_node, struct vnode **target, + const char *component_name); +static int _initramfs_create(struct vnode *dir_node, struct vnode **target, + const char *component_name); +static int _initramfs_mkdir(struct vnode *dir_node, struct vnode **target, + const char *component_name); + +struct filesystem initramfs = {.name = "initramfs", + .setup_mount = _initramfs_setup_mount}; + +static struct file_operations _initramfs_file_operations = { + .write = _initramfs_write, + .read = _initramfs_read, + .open = _initramfs_open, + .close = _initramfs_close, + .lseek64 = _initramfs_lseek64}; + +static struct vnode_operations _initramfs_vnode_operations = { + .lookup = _initramfs_lookup, + .create = _initramfs_create, + .mkdir = _initramfs_mkdir}; + +static int _initramfs_cmp_child_vnode_entries_by_component_name( + const initramfs_child_vnode_entry_t *const e1, + const initramfs_child_vnode_entry_t *const e2, void *const _arg) { + (void)_arg; + + return strcmp(e1->component_name, e2->component_name); +} + +static int _initramfs_cmp_component_name_and_child_vnode_entry( + const char *component_name, + const initramfs_child_vnode_entry_t *const entry, void *const _arg) { + (void)_arg; + + return strcmp(component_name, entry->component_name); +} + +static struct vnode * +_initramfs_create_vnode(struct mount *const mount, struct vnode *const parent, + const cpio_newc_entry_t *const entry) { + struct vnode *const result = malloc(sizeof(struct vnode)); + if (!result) + return NULL; + + initramfs_internal_t *const internal = malloc(sizeof(initramfs_internal_t)); + if (!internal) { + free(result); + return NULL; + } + + *internal = (initramfs_internal_t){ + .parent = parent, .entry = entry, .child_vnodes = NULL}; + *result = (struct vnode){.mount = mount, + .v_ops = &_initramfs_vnode_operations, + .f_ops = &_initramfs_file_operations, + .internal = internal}; + return result; +} + +static int _initramfs_setup_mount(struct filesystem *const fs, + struct mount *const mount) { + struct vnode *const root_vnode = _initramfs_create_vnode(mount, NULL, NULL); + if (!root_vnode) + return -ENOMEM; + + *mount = (struct mount){.fs = fs, .root = root_vnode}; + + return 0; +} + +static int _initramfs_write(struct file *const file, const void *const buf, + const size_t len) { + (void)file; + (void)buf; + (void)len; + + return -EROFS; +} + +static int _initramfs_read(struct file *const file, void *const buf, + const size_t len) { + initramfs_internal_t *const internal = + (initramfs_internal_t *)file->vnode->internal; + const cpio_newc_entry_t *const entry = internal->entry; + + const uint32_t mode = CPIO_NEWC_HEADER_VALUE(entry, mode); + const uint32_t file_type = mode & CPIO_NEWC_MODE_FILE_TYPE_MASK; + if (file_type == CPIO_NEWC_MODE_FILE_TYPE_REG) { + // No-op. + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_DIR) { + return -EISDIR; + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_LNK) { + return -ELOOP; + } else { // Unknown file type. + return -EIO; + } + + const size_t file_size = CPIO_NEWC_FILESIZE(entry); + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const size_t remaining_len = + file->f_pos >= file_size ? 0 : file_size - file->f_pos, + cpy_len = len < remaining_len ? len : remaining_len; + + memcpy(buf, CPIO_NEWC_FILE_DATA(entry) + file->f_pos, cpy_len); + file->f_pos += cpy_len; + + CRITICAL_SECTION_LEAVE(daif_val); + + return cpy_len; +} + +static int _initramfs_open(struct vnode *const file_node, + struct file **const target) { + initramfs_internal_t *const internal = + (initramfs_internal_t *)file_node->internal; + const cpio_newc_entry_t *const entry = internal->entry; + + const uint32_t mode = CPIO_NEWC_HEADER_VALUE(entry, mode); + const uint32_t file_type = mode & CPIO_NEWC_MODE_FILE_TYPE_MASK; + if (file_type == CPIO_NEWC_MODE_FILE_TYPE_REG) { + // No-op. + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_DIR) { + return -EISDIR; + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_LNK) { + return -ELOOP; + } else { // Unknown file type. + return -EIO; + } + + struct file *const file_handle = malloc(sizeof(struct file)); + if (!file_handle) + return -ENOMEM; + + *file_handle = (struct file){.vnode = file_node, + .f_pos = 0, + .f_ops = &_initramfs_file_operations, + .flags = 0}; + *target = file_handle; + + return 0; +} + +static int _initramfs_close(struct file *const file) { + free(file); + return 0; +} + +static long _initramfs_lseek64(struct file *const file, const long offset, + const int whence) { + initramfs_internal_t *const internal = + (initramfs_internal_t *)file->vnode->internal; + const cpio_newc_entry_t *const entry = internal->entry; + + const uint32_t mode = CPIO_NEWC_HEADER_VALUE(entry, mode); + const uint32_t file_type = mode & CPIO_NEWC_MODE_FILE_TYPE_MASK; + if (file_type == CPIO_NEWC_MODE_FILE_TYPE_REG) { + // No-op. + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_DIR) { + return -EISDIR; + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_LNK) { + return -ELOOP; + } else { // Unknown file type. + return -EIO; + } + + if (!(whence == SEEK_SET || whence == SEEK_CUR || whence == SEEK_END)) + return -EINVAL; + + const long f_pos_base = whence == SEEK_SET ? 0 + : whence == SEEK_CUR ? file->f_pos + : CPIO_NEWC_FILESIZE(entry), + new_f_pos = f_pos_base + offset; + + if (new_f_pos < 0) { + return -EINVAL; + } else { + file->f_pos = new_f_pos; + return 0; + } +} + +static int _initramfs_lookup(struct vnode *const dir_node, + struct vnode **const target, + const char *const component_name) { + initramfs_internal_t *const internal = + (initramfs_internal_t *)dir_node->internal; + const cpio_newc_entry_t *const entry = internal->entry; + + if (dir_node != dir_node->mount->root) { // The vnode of the root directory + // doesn't have an entry. + const uint32_t mode = CPIO_NEWC_HEADER_VALUE(entry, mode); + const uint32_t file_type = mode & CPIO_NEWC_MODE_FILE_TYPE_MASK; + if (file_type == CPIO_NEWC_MODE_FILE_TYPE_REG) { + return -ENOTDIR; + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_DIR) { + // No-op. + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_LNK) { + return -ELOOP; + } else { // Unknown file type. + return -EIO; + } + } + + if (strcmp(component_name, ".") == 0) { + *target = dir_node; + return 0; + } else if (strcmp(component_name, "..") == 0) { + *target = internal->parent; + return 0; + } + + // Check if the vnode has been created before. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const initramfs_child_vnode_entry_t *const child_vnode_entry = rb_search( + internal->child_vnodes, component_name, + (int (*)(const void *, const void *, + void *))_initramfs_cmp_component_name_and_child_vnode_entry, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + if (child_vnode_entry) { + *target = child_vnode_entry->vnode; + return 0; + } + + // A vnode has not been created before. Check if the file/directory exists. + + size_t dirname_len; + const cpio_newc_entry_t *child_initrd_entry; + + if (dir_node != dir_node->mount->root) { + dirname_len = strlen(CPIO_NEWC_PATHNAME(entry)); + char *const filename_buf = + malloc(dirname_len + 1 + strlen(component_name) + 1); + if (!filename_buf) + return -ENOMEM; + memcpy(filename_buf, CPIO_NEWC_PATHNAME(entry), dirname_len); + filename_buf[dirname_len] = '/'; + strcpy(filename_buf + dirname_len + 1, component_name); + + child_initrd_entry = initrd_find_entry_by_pathname(filename_buf); + free(filename_buf); + } else { + child_initrd_entry = initrd_find_entry_by_pathname(component_name); + } + if (!child_initrd_entry) { + return -ENOENT; + } + + // Create a new vnode. + + struct vnode *const vnode = + _initramfs_create_vnode(dir_node->mount, dir_node, child_initrd_entry); + if (!vnode) + return -ENOMEM; + + const initramfs_child_vnode_entry_t new_child_vnode_entry = { + .component_name = + CPIO_NEWC_PATHNAME(child_initrd_entry) + + (dir_node != dir_node->mount->root ? dirname_len + 1 : 0), + .vnode = vnode}; + + CRITICAL_SECTION_ENTER(daif_val); + + rb_insert(&internal->child_vnodes, sizeof(initramfs_child_vnode_entry_t), + &new_child_vnode_entry, + (int (*)(const void *, const void *, void *)) + _initramfs_cmp_child_vnode_entries_by_component_name, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + *target = vnode; + + return 0; +} + +static int _initramfs_create(struct vnode *const dir_node, + struct vnode **const target, + const char *const component_name) { + (void)dir_node; + (void)target; + (void)component_name; + + return -EROFS; +} + +static int _initramfs_mkdir(struct vnode *const dir_node, + struct vnode **const target, + const char *const component_name) { + (void)dir_node; + (void)target; + (void)component_name; + + return -EROFS; +} diff --git a/lab8/c/src/fs/tmpfs.c b/lab8/c/src/fs/tmpfs.c new file mode 100644 index 000000000..180a9cb2f --- /dev/null +++ b/lab8/c/src/fs/tmpfs.c @@ -0,0 +1,386 @@ +#include "oscos/fs/tmpfs.h" + +#include "oscos/libc/string.h" +#include "oscos/mem/malloc.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/mem/vm.h" +#include "oscos/uapi/errno.h" +#include "oscos/uapi/stdio.h" +#include "oscos/utils/critical-section.h" +#include "oscos/utils/rb.h" + +#define MAX_FILE_SZ (1 << PAGE_ORDER) + +typedef enum { TYPE_DIR, TYPE_FILE } tmpfs_internal_type_t; + +typedef struct { + struct vnode *parent; + rb_node_t *contents; +} tmpfs_internal_dir_data_t; + +typedef struct { + unsigned char *contents; + size_t size; +} tmpfs_internal_file_data_t; + +typedef struct { + tmpfs_internal_type_t type; + union { + tmpfs_internal_dir_data_t dir_data; + tmpfs_internal_file_data_t file_data; + }; +} tmpfs_internal_t; + +typedef struct { + const char *component_name; + struct vnode *vnode; +} tmpfs_dir_contents_entry_t; + +static int _tmpfs_setup_mount(struct filesystem *fs, struct mount *mount); + +static int _tmpfs_write(struct file *file, const void *buf, size_t len); +static int _tmpfs_read(struct file *file, void *buf, size_t len); +static int _tmpfs_open(struct vnode *file_node, struct file **target); +static int _tmpfs_close(struct file *file); +static long _tmpfs_lseek64(struct file *file, long offset, int whence); + +static int _tmpfs_lookup(struct vnode *dir_node, struct vnode **target, + const char *component_name); +static int _tmpfs_create(struct vnode *dir_node, struct vnode **target, + const char *component_name); +static int _tmpfs_mkdir(struct vnode *dir_node, struct vnode **target, + const char *component_name); + +struct filesystem tmpfs = {.name = "tmpfs", .setup_mount = _tmpfs_setup_mount}; + +static struct file_operations _tmpfs_file_operations = {.write = _tmpfs_write, + .read = _tmpfs_read, + .open = _tmpfs_open, + .close = _tmpfs_close, + .lseek64 = + _tmpfs_lseek64}; + +static struct vnode_operations _tmpfs_vnode_operations = { + .lookup = _tmpfs_lookup, .create = _tmpfs_create, .mkdir = _tmpfs_mkdir}; + +static int +_tmpfs_cmp_dir_contents_entries(const tmpfs_dir_contents_entry_t *const e1, + const tmpfs_dir_contents_entry_t *const e2, + void *_arg) { + (void)_arg; + + return strcmp(e1->component_name, e2->component_name); +} + +static int _tmpfs_cmp_component_name_and_dir_contents_entry( + const char *const component_name, + const tmpfs_dir_contents_entry_t *const entry, void *_arg) { + (void)_arg; + + return strcmp(component_name, entry->component_name); +} + +static struct vnode *_tmpfs_create_file_vnode(struct mount *const mount) { + struct vnode *const result = malloc(sizeof(struct vnode)); + if (!result) + return NULL; + + tmpfs_internal_t *const internal = malloc(sizeof(tmpfs_internal_t)); + if (!internal) { + free(result); + return NULL; + } + + const spage_id_t contents_page = alloc_pages(0); + if (contents_page < 0) { + free(internal); + free(result); + return NULL; + } + unsigned char *const contents = pa_to_kernel_va(page_id_to_pa(contents_page)); + + *internal = (tmpfs_internal_t){.type = TYPE_FILE, + .file_data = (tmpfs_internal_file_data_t){ + .contents = contents, .size = 0}}; + *result = (struct vnode){.mount = mount, + .v_ops = &_tmpfs_vnode_operations, + .f_ops = &_tmpfs_file_operations, + .internal = internal}; + return result; +} + +static struct vnode *_tmpfs_create_dir_vnode(struct mount *const mount, + struct vnode *const parent) { + struct vnode *const result = malloc(sizeof(struct vnode)); + if (!result) + return NULL; + + tmpfs_internal_t *const internal = malloc(sizeof(tmpfs_internal_t)); + if (!internal) { + free(result); + return NULL; + } + + *internal = (tmpfs_internal_t){.type = TYPE_DIR, + .dir_data = (tmpfs_internal_dir_data_t){ + .parent = parent, .contents = NULL}}; + *result = (struct vnode){.mount = mount, + .v_ops = &_tmpfs_vnode_operations, + .f_ops = &_tmpfs_file_operations, + .internal = internal}; + return result; +} + +static int _tmpfs_setup_mount(struct filesystem *const fs, + struct mount *const mount) { + struct vnode *const root_vnode = _tmpfs_create_dir_vnode(mount, NULL); + if (!root_vnode) + return -ENOMEM; + + *mount = (struct mount){.fs = fs, .root = root_vnode}; + + return 0; +} + +static int _tmpfs_write(struct file *const file, const void *const buf, + const size_t len) { + tmpfs_internal_t *const internal = (tmpfs_internal_t *)file->vnode->internal; + if (internal->type != TYPE_FILE) + return -EISDIR; + + tmpfs_internal_file_data_t *const file_data = &internal->file_data; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + if (file->f_pos >= MAX_FILE_SZ) { + CRITICAL_SECTION_LEAVE(daif_val); + return len == 0 ? 0 : -EFBIG; + } + + const size_t remaining_len = MAX_FILE_SZ - file->f_pos, + cpy_len = len < remaining_len ? len : remaining_len; + + if (file->f_pos > file_data->size) { + memset(file_data->contents, 0, file->f_pos - file_data->size); + } + + memcpy(file_data->contents + file->f_pos, buf, cpy_len); + file->f_pos += cpy_len; + if (file->f_pos > file_data->size) { + file_data->size = file->f_pos; + } + + CRITICAL_SECTION_LEAVE(daif_val); + + return cpy_len; +} + +static int _tmpfs_read(struct file *const file, void *const buf, + const size_t len) { + tmpfs_internal_t *const internal = (tmpfs_internal_t *)file->vnode->internal; + if (internal->type != TYPE_FILE) + return -EISDIR; + + tmpfs_internal_file_data_t *const file_data = &internal->file_data; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const size_t remaining_len = file->f_pos >= file_data->size + ? 0 + : file_data->size - file->f_pos, + cpy_len = len < remaining_len ? len : remaining_len; + + memcpy(buf, file_data->contents + file->f_pos, cpy_len); + file->f_pos += cpy_len; + + CRITICAL_SECTION_LEAVE(daif_val); + + return cpy_len; +} + +static int _tmpfs_open(struct vnode *const file_node, + struct file **const target) { + tmpfs_internal_t *const internal = (tmpfs_internal_t *)file_node->internal; + if (internal->type != TYPE_FILE) + return -EISDIR; + + struct file *const file_handle = malloc(sizeof(struct file)); + if (!file_handle) + return -ENOMEM; + + *file_handle = (struct file){.vnode = file_node, + .f_pos = 0, + .f_ops = &_tmpfs_file_operations, + .flags = 0}; + *target = file_handle; + + return 0; +} + +static int _tmpfs_close(struct file *const file) { + free(file); + return 0; +} + +static long _tmpfs_lseek64(struct file *const file, const long offset, + const int whence) { + tmpfs_internal_t *const internal = (tmpfs_internal_t *)file->vnode->internal; + if (internal->type != TYPE_FILE) + return -EISDIR; + + if (!(whence == SEEK_SET || whence == SEEK_CUR || whence == SEEK_END)) + return -EINVAL; + + const long f_pos_base = whence == SEEK_SET ? 0 + : whence == SEEK_CUR ? file->f_pos + : MAX_FILE_SZ, + new_f_pos = f_pos_base + offset; + + if (new_f_pos < 0) { + return -EINVAL; + } else { + file->f_pos = new_f_pos; + return 0; + } +} + +static int _tmpfs_lookup(struct vnode *const dir_node, + struct vnode **const target, + const char *const component_name) { + tmpfs_internal_t *const internal = (tmpfs_internal_t *)dir_node->internal; + if (internal->type != TYPE_DIR) + return -ENOTDIR; + + int result; + tmpfs_internal_dir_data_t *const dir_data = &internal->dir_data; + + if (strcmp(component_name, ".") == 0) { + *target = dir_node; + return 0; + } else if (strcmp(component_name, "..") == 0) { + *target = dir_data->parent; + return 0; + } + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const tmpfs_dir_contents_entry_t *const entry = rb_search( + dir_data->contents, component_name, + (int (*)(const void *, const void *, + void *))_tmpfs_cmp_component_name_and_dir_contents_entry, + NULL); + + if (entry) { + *target = entry->vnode; + result = 0; + } else { + result = -ENOENT; + } + + CRITICAL_SECTION_LEAVE(daif_val); + return result; +} + +static int _tmpfs_create(struct vnode *const dir_node, + struct vnode **const target, + const char *const component_name) { + tmpfs_internal_t *const internal = (tmpfs_internal_t *)dir_node->internal; + if (internal->type != TYPE_DIR) + return -ENOTDIR; + + int result; + tmpfs_internal_dir_data_t *const dir_data = &internal->dir_data; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const tmpfs_dir_contents_entry_t *const existing_entry = rb_search( + dir_data->contents, component_name, + (int (*)(const void *, const void *, + void *))_tmpfs_cmp_component_name_and_dir_contents_entry, + NULL); + if (existing_entry) { + result = -EEXIST; + goto end; + } + + const char *const entry_component_name = strdup(component_name); + if (!entry_component_name) { + result = -ENOMEM; + goto end; + } + + struct vnode *const vnode = _tmpfs_create_file_vnode(dir_node->mount); + if (!vnode) { + result = -ENOMEM; + goto end; + } + + const tmpfs_dir_contents_entry_t new_entry = { + .component_name = entry_component_name, .vnode = vnode}; + rb_insert(&dir_data->contents, sizeof(tmpfs_dir_contents_entry_t), &new_entry, + (int (*)(const void *, const void *, + void *))_tmpfs_cmp_dir_contents_entries, + NULL); + + *target = vnode; + result = 0; + +end: + CRITICAL_SECTION_LEAVE(daif_val); + return result; +} + +static int _tmpfs_mkdir(struct vnode *const dir_node, + struct vnode **const target, + const char *const component_name) { + tmpfs_internal_t *const internal = (tmpfs_internal_t *)dir_node->internal; + if (internal->type != TYPE_DIR) + return -ENOTDIR; + + int result; + tmpfs_internal_dir_data_t *const dir_data = &internal->dir_data; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const tmpfs_dir_contents_entry_t *const existing_entry = rb_search( + dir_data->contents, component_name, + (int (*)(const void *, const void *, + void *))_tmpfs_cmp_component_name_and_dir_contents_entry, + NULL); + if (existing_entry) { + result = -EEXIST; + goto end; + } + + const char *const entry_component_name = strdup(component_name); + if (!entry_component_name) { + result = -ENOMEM; + goto end; + } + + struct vnode *const vnode = + _tmpfs_create_dir_vnode(dir_node->mount, dir_node); + if (!vnode) { + result = -ENOMEM; + goto end; + } + + const tmpfs_dir_contents_entry_t new_entry = { + .component_name = entry_component_name, .vnode = vnode}; + rb_insert(&dir_data->contents, sizeof(tmpfs_dir_contents_entry_t), &new_entry, + (int (*)(const void *, const void *, + void *))_tmpfs_cmp_dir_contents_entries, + NULL); + + *target = vnode; + result = 0; + +end: + CRITICAL_SECTION_LEAVE(daif_val); + return result; +} diff --git a/lab8/c/src/fs/vfs.c b/lab8/c/src/fs/vfs.c new file mode 100644 index 000000000..15aec9946 --- /dev/null +++ b/lab8/c/src/fs/vfs.c @@ -0,0 +1,344 @@ +#include "oscos/fs/vfs.h" + +#include + +#include "oscos/libc/string.h" +#include "oscos/mem/malloc.h" +#include "oscos/uapi/errno.h" +#include "oscos/utils/critical-section.h" +#include "oscos/utils/rb.h" + +typedef struct { + struct vnode *mountpoint; + struct mount *mount; +} mount_entry_t; + +struct mount rootfs; + +static rb_node_t *_filesystems = NULL; +static rb_node_t *_mounts_by_mountpoint = NULL, *_mounts_by_root = NULL; + +static int _vfs_cmp_filesystems_by_name(const struct filesystem *const fs1, + const struct filesystem *const fs2, + void *const _arg) { + (void)_arg; + + return strcmp(fs1->name, fs2->name); +} + +static int _vfs_cmp_name_and_filesystem(const char *const name, + const struct filesystem *const fs, + void *const _arg) { + (void)_arg; + + return strcmp(name, fs->name); +} + +static int _vfs_cmp_mounts_by_mountpoint(const mount_entry_t *const m1, + const mount_entry_t *const m2, + void *const _arg) { + (void)_arg; + + if (m1->mountpoint < m2->mountpoint) + return -1; + if (m1->mountpoint > m2->mountpoint) + return 1; + return 0; +} + +static int _vfs_cmp_mountpoint_and_mount(const struct vnode *const mountpoint, + const mount_entry_t *const mount, + void *const _arg) { + (void)_arg; + + if (mountpoint < mount->mountpoint) + return -1; + if (mountpoint > mount->mountpoint) + return 1; + return 0; +} + +static int _vfs_cmp_mounts_by_root(const mount_entry_t *const m1, + const mount_entry_t *const m2, + void *const _arg) { + (void)_arg; + + if (m1->mount->root < m2->mount->root) + return -1; + if (m1->mount->root > m2->mount->root) + return 1; + return 0; +} + +static int _vfs_cmp_root_and_mount(const struct vnode *const root, + const mount_entry_t *const mount, + void *const _arg) { + (void)_arg; + + if (root < mount->mount->root) + return -1; + if (root > mount->mount->root) + return 1; + return 0; +} + +int register_filesystem(struct filesystem *const fs) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + rb_insert( + &_filesystems, sizeof(struct filesystem), fs, + (int (*)(const void *, const void *, void *))_vfs_cmp_filesystems_by_name, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + return 0; +} + +static int _vfs_lookup_step(struct vnode *curr_vnode, + struct vnode **const target, + const char *const component_name) { + if (strcmp(component_name, "..") == 0 && + curr_vnode == curr_vnode->mount->root) { + if (curr_vnode == rootfs.root) { + *target = curr_vnode; + return 0; + } else { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const mount_entry_t *const entry = rb_search( + _mounts_by_root, curr_vnode, + (int (*)(const void *, const void *, void *))_vfs_cmp_root_and_mount, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + curr_vnode = entry->mountpoint; + } + } + + const int result = + curr_vnode->v_ops->lookup(curr_vnode, target, component_name); + if (result < 0) + return result; + + // If the new node is the mountpoint of a mounted file system, jump to the + // filesystem root. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const mount_entry_t *const entry = + rb_search(_mounts_by_mountpoint, *target, + (int (*)(const void *, const void *, + void *))_vfs_cmp_mountpoint_and_mount, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + if (entry) { + *target = entry->mount->root; + } + + return 0; +} + +static int _vfs_lookup_sans_last_level_relative( + struct vnode *const cwd, const char *const pathname, + struct vnode **const target, const char **const last_pathname_component) { + struct vnode *curr_vnode = cwd; + const char *curr_pathname_component = pathname; + if (*curr_pathname_component == '/') { + curr_vnode = rootfs.root; + curr_pathname_component += 1; + } + + for (const char *curr_pathname_component_end; + (curr_pathname_component_end = strchr(curr_pathname_component, '/')); + curr_pathname_component = curr_pathname_component_end + 1) { + const size_t curr_pathname_component_len = + curr_pathname_component_end - curr_pathname_component; + char *const curr_pathname_component_copy = + strndup(curr_pathname_component, curr_pathname_component_len); + if (!curr_pathname_component_copy) + return -ENOMEM; + + const int result = + _vfs_lookup_step(curr_vnode, target, curr_pathname_component_copy); + free(curr_pathname_component_copy); + if (result < 0) + return result; + curr_vnode = *target; + } + + *target = curr_vnode; + *last_pathname_component = curr_pathname_component; + return 0; +} + +int vfs_open(const char *const pathname, const int flags, + struct file **const target) { + return vfs_open_relative(rootfs.root, pathname, flags, target); +} + +int vfs_open_relative(struct vnode *const cwd, const char *const pathname, + const int flags, struct file **const target) { + const char *last_pathname_component; + struct vnode *parent_vnode; + const int lookup_result = _vfs_lookup_sans_last_level_relative( + cwd, pathname, &parent_vnode, &last_pathname_component); + if (lookup_result < 0) + return lookup_result; + + struct vnode *curr_vnode; + const int result = parent_vnode->v_ops->lookup(parent_vnode, &curr_vnode, + last_pathname_component); + if (result == -ENOENT && flags & O_CREAT) { + const int result = parent_vnode->v_ops->create(parent_vnode, &curr_vnode, + last_pathname_component); + if (result < 0) + return result; + } else if (result < 0) { + return result; + } + return curr_vnode->f_ops->open(curr_vnode, target); +} + +int vfs_close(struct file *const file) { return file->f_ops->close(file); } + +int vfs_write(struct file *const file, const void *const buf, + const size_t len) { + return file->f_ops->write(file, buf, len); +} + +int vfs_read(struct file *const file, void *const buf, const size_t len) { + return file->f_ops->read(file, buf, len); +} + +int vfs_mkdir(const char *const pathname) { + return vfs_mkdir_relative(rootfs.root, pathname); +} + +int vfs_mkdir_relative(struct vnode *const cwd, const char *const pathname) { + const char *last_pathname_component; + struct vnode *parent_vnode; + const int lookup_result = _vfs_lookup_sans_last_level_relative( + cwd, pathname, &parent_vnode, &last_pathname_component); + if (lookup_result < 0) + return lookup_result; + + struct vnode *target; + return parent_vnode->v_ops->mkdir(parent_vnode, &target, + last_pathname_component); +} + +int vfs_mount(const char *const target, const char *const filesystem) { + return vfs_mount_relative(rootfs.root, target, filesystem); +} + +int vfs_mount_relative(struct vnode *const cwd, const char *const target, + const char *const filesystem) { + struct vnode *mountpoint; + const int result = vfs_lookup_relative(cwd, target, &mountpoint); + if (result < 0) + return result; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + struct filesystem *fs = (struct filesystem *)rb_search( + _filesystems, filesystem, + (int (*)(const void *, const void *, void *))_vfs_cmp_name_and_filesystem, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + if (!fs) + return -ENODEV; + + struct mount *const mount = malloc(sizeof(struct mount)); + if (!mount) + return -ENOMEM; + + fs->setup_mount(fs, mount); + + const mount_entry_t entry = {.mountpoint = mountpoint, .mount = mount}; + + CRITICAL_SECTION_ENTER(daif_val); + + rb_insert(&_mounts_by_mountpoint, sizeof(mount_entry_t), &entry, + (int (*)(const void *, const void *, + void *))_vfs_cmp_mounts_by_mountpoint, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + CRITICAL_SECTION_ENTER(daif_val); + + rb_insert( + &_mounts_by_root, sizeof(mount_entry_t), &entry, + (int (*)(const void *, const void *, void *))_vfs_cmp_mounts_by_root, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + return 0; +} + +int vfs_lookup(const char *const pathname, struct vnode **const target) { + return vfs_lookup_relative(rootfs.root, pathname, target); +} + +int vfs_lookup_relative(struct vnode *const cwd, const char *const pathname, + struct vnode **const target) { + const char *last_pathname_component; + struct vnode *parent_vnode; + const int lookup_result = _vfs_lookup_sans_last_level_relative( + cwd, pathname, &parent_vnode, &last_pathname_component); + if (lookup_result < 0) + return lookup_result; + + if (*last_pathname_component == '\0') { + *target = parent_vnode; + return 0; + } else { + return _vfs_lookup_step(parent_vnode, target, last_pathname_component); + } +} + +shared_file_t *shared_file_new(struct file *const file) { + shared_file_t *const shared_file = malloc(sizeof(shared_file_t)); + if (!shared_file) + return NULL; + + *shared_file = (shared_file_t){.file = file, .refcnt = 1}; + return shared_file; +} + +shared_file_t *shared_file_clone(shared_file_t *const shared_file) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + shared_file->refcnt++; + + CRITICAL_SECTION_LEAVE(daif_val); + + return shared_file; +} + +void shared_file_drop(shared_file_t *const shared_file) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + shared_file->refcnt--; + const bool drop = shared_file->refcnt == 0; + + CRITICAL_SECTION_LEAVE(daif_val); + + if (drop) { + vfs_close(shared_file->file); + free(shared_file); + } +} diff --git a/lab8/c/src/initrd.c b/lab8/c/src/initrd.c new file mode 100644 index 000000000..2ae0f9561 --- /dev/null +++ b/lab8/c/src/initrd.c @@ -0,0 +1,131 @@ +#include "oscos/initrd.h" + +#include "oscos/devicetree.h" +#include "oscos/mem/vm.h" + +static const void *_initrd_start, *_initrd_end; + +static bool _cpio_newc_is_header_field_valid(const char field[const static 8]) { + for (size_t i = 0; i < 8; i++) { + if (!(('0' <= field[i] && field[i] <= '9') || + ('A' <= field[i] && field[i] <= 'F'))) + return false; + } + return true; +} + +static bool _initrd_is_valid(void) { + const cpio_newc_entry_t *entry; + + // Cannot use INITRD_FOR_ENTRY here, since it will evaluate + // `CPIO_NEWC_IS_ENTRY_LAST(entry)` before `entry` is validated. + for (entry = INITRD_HEAD;; entry = CPIO_NEWC_NEXT_ENTRY(entry)) { + if (entry >= (cpio_newc_entry_t *)_initrd_end) + return false; + if (!(strncmp(entry->header.c_magic, "070701", 6) == 0 && + _cpio_newc_is_header_field_valid(entry->header.c_mode) && + _cpio_newc_is_header_field_valid(entry->header.c_uid) && + _cpio_newc_is_header_field_valid(entry->header.c_gid) && + _cpio_newc_is_header_field_valid(entry->header.c_nlink) && + _cpio_newc_is_header_field_valid(entry->header.c_mtime) && + _cpio_newc_is_header_field_valid(entry->header.c_filesize) && + _cpio_newc_is_header_field_valid(entry->header.c_devmajor) && + _cpio_newc_is_header_field_valid(entry->header.c_devminor) && + _cpio_newc_is_header_field_valid(entry->header.c_rdevmajor) && + _cpio_newc_is_header_field_valid(entry->header.c_rdevminor) && + _cpio_newc_is_header_field_valid(entry->header.c_namesize) && + _cpio_newc_is_header_field_valid(entry->header.c_check))) + return false; + + if (CPIO_NEWC_IS_ENTRY_LAST(entry)) + break; + } + + return CPIO_NEWC_NEXT_ENTRY(entry) <= (cpio_newc_entry_t *)_initrd_end; +} + +typedef struct { + bool start_done, end_done; +} initrd_init_dtb_traverse_callback_arg_t; + +static control_flow_t _initrd_init_dtb_traverse_callback( + initrd_init_dtb_traverse_callback_arg_t *const arg, + const fdt_item_t *const node, + const fdt_traverse_parent_list_node_t *const parent) { + if (parent && !parent->parent && + strcmp(FDT_NODE_NAME(node), "chosen") == 0) { // Current node is /chosen. + FDT_FOR_ITEM(node, item) { + if (FDT_TOKEN(item) == FDT_PROP) { + const fdt_prop_t *const prop = (const fdt_prop_t *)item->payload; + if (strcmp(FDT_PROP_NAME(prop), "linux,initrd-start") == 0) { + const uint32_t pa = rev_u32(*(const uint32_t *)FDT_PROP_VALUE(prop)); + _initrd_start = pa_to_kernel_va(pa); + arg->start_done = true; + } else if (strcmp(FDT_PROP_NAME(prop), "linux,initrd-end") == 0) { + const uint32_t pa = rev_u32(*(const uint32_t *)FDT_PROP_VALUE(prop)); + _initrd_end = pa_to_kernel_va(pa); + arg->end_done = true; + } + + if (arg->start_done && arg->end_done) + break; + } + } + return CF_BREAK; + } else { + return CF_CONTINUE; + } +} + +bool initrd_init(void) { + _initrd_start = NULL; + + if (devicetree_is_init()) { + // Discover the initrd loading address through the devicetree. + + initrd_init_dtb_traverse_callback_arg_t arg = {.start_done = false, + .end_done = false}; + fdt_traverse((fdt_traverse_callback_t *)_initrd_init_dtb_traverse_callback, + &arg); + if (!(arg.start_done && + arg.end_done)) { // Either the /chosen/linux,initrd-start or the + // /chosen/linux,initrd-end property is missing from + // the devicetree. + _initrd_start = NULL; + } + } + + // Validate the initial ramdisk. + if (!(_initrd_start && _initrd_is_valid())) { + _initrd_start = NULL; + } + + return _initrd_start; +} + +bool initrd_is_init(void) { return _initrd_start; } + +const void *initrd_get_start(void) { return _initrd_start; } + +const void *initrd_get_end(void) { return _initrd_end; } + +uint32_t cpio_newc_parse_header_field(const char field[static 8]) { + uint32_t result = 0; + for (size_t i = 0; i < 8; i++) { + const uint32_t digit_value = + '0' <= field[i] && field[i] <= '9' ? field[i] - '0' + : 'A' <= field[i] && field[i] <= 'F' ? field[i] - 'A' + 10 + : (__builtin_unreachable(), 0); + result = result << 4 | digit_value; + } + return result; +} + +const cpio_newc_entry_t *initrd_find_entry_by_pathname(const char *pathname) { + INITRD_FOR_ENTRY(entry) { + if (strcmp(CPIO_NEWC_PATHNAME(entry), pathname) == 0) { + return entry; + } + } + return NULL; +} diff --git a/lab8/c/src/libc/ctype.c b/lab8/c/src/libc/ctype.c new file mode 100644 index 000000000..6ff704afb --- /dev/null +++ b/lab8/c/src/libc/ctype.c @@ -0,0 +1,3 @@ +#include "oscos/libc/ctype.h" + +int isdigit(const int c) { return '0' <= c && c <= '9'; } diff --git a/lab8/c/src/libc/stdio.c b/lab8/c/src/libc/stdio.c new file mode 100644 index 000000000..766cbb465 --- /dev/null +++ b/lab8/c/src/libc/stdio.c @@ -0,0 +1,41 @@ +#include "oscos/libc/stdio.h" + +#include "oscos/utils/fmt.h" + +int snprintf(char str[const restrict], const size_t size, + const char *const restrict format, ...) { + va_list ap; + va_start(ap, format); + + const int result = vsnprintf(str, size, format, ap); + + va_end(ap); + return result; +} + +typedef struct { + char *str; + size_t size, len; +} snprintf_arg_t; + +static void _snprintf_putc(const unsigned char c, snprintf_arg_t *const arg) { + if (arg->size > 0 && arg->len < arg->size - 1) { + arg->str[arg->len++] = c; + } +} + +static void _snprintf_finalize(snprintf_arg_t *const arg) { + if (arg->size > 0) { + arg->str[arg->len] = '\0'; + } +} + +static const printf_vtable_t _snprintf_vtable = { + .putc = (void (*)(unsigned char, void *))_snprintf_putc, + .finalize = (void (*)(void *))_snprintf_finalize}; + +int vsnprintf(char str[const restrict], const size_t size, + const char *const restrict format, va_list ap) { + snprintf_arg_t arg = {.str = str, .size = size, .len = 0}; + return vprintf_generic(&_snprintf_vtable, &arg, format, ap); +} diff --git a/lab8/c/src/libc/stdlib/qsort.c b/lab8/c/src/libc/stdlib/qsort.c new file mode 100644 index 000000000..200755aa2 --- /dev/null +++ b/lab8/c/src/libc/stdlib/qsort.c @@ -0,0 +1,154 @@ +#include "oscos/libc/stdlib.h" + +#include "oscos/libc/string.h" + +#define INSERTION_SORT_THRESHOLD 16 + +// Insertion sort. + +/// \brief Insertion sort. +static void +_insertion_sort(void *const base, const size_t nmemb, const size_t size, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + for (size_t i = 1; i < nmemb; i++) { + for (size_t j = i; j > 0; j--) { + void *const pl = (char *)base + (j - 1) * size, *const pr = + (char *)pl + size; + if (compar(pl, pr, arg) < 0) + break; + memswp(pl, pr, size); + } + } +} + +// Quicksort. + +/// \brief The result of partitioning, indicating the pivot points. +typedef struct { + size_t eq_start; ///< The starting index of the part where the element + ///< compares equal to the pivot. + size_t gt_start; ///< The starting index of the part where the element + ///< compares greater than the pivot. +} partition_result_t; + +/// \brief Median-of-3 pivot selection. +/// +/// \return The index of the chosen pivot. +static size_t +_select_pivot(const void *const base, const size_t nmemb, const size_t size, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + if (nmemb == 0) + __builtin_unreachable(); + if (nmemb < 3) + return 0; + + const size_t imid = nmemb / 2, ilast = nmemb - 1; + const void *const mid = (char *)base + imid * size, *const last = + (char *)base + + ilast * size; + return compar(base, mid, arg) <= 0 ? compar(mid, last, arg) <= 0 ? imid + : compar(base, last, arg) <= 0 ? ilast + : 0 + : compar(mid, last, arg) > 0 ? imid + : compar(base, last, arg) <= 0 ? 0 + : ilast; +} + +/// \brief Three-way partitioning, using the first element as the pivot. +static partition_result_t +_partition(void *const base, const size_t nmemb, const size_t size, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + if (nmemb == 0) + __builtin_unreachable(); + + // Partition every element except for the first one (pivot). + + size_t il = 1, im = 1, ir = nmemb; + while (im < ir) { + void *const pl = (char *)base + il * size, + *const pm = (char *)base + im * size, + *const prm1 = (char *)base + (ir - 1) * size; + + const int compar_result = compar(pm, base, arg); + if (compar_result < 0) { + memswp(pl, pm, size); + il++; + im++; + } else if (compar_result > 0) { + memswp(pm, prm1, size); + ir--; + } else { + im++; + } + } + + // Move the pivot to its place. + + if (il != 0) { + memswp(base, (char *)base + --il * size, size); + } + + return (partition_result_t){.eq_start = il, .gt_start = im}; +} + +/// \brief Quicksort. +/// +/// A basic quicksort with median-of-3 pivot selection, three-way partitioning, +/// and insertion sort for small arrays. +static void _quicksort(void *const base, const size_t nmemb, const size_t size, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + if (nmemb <= INSERTION_SORT_THRESHOLD) { + _insertion_sort(base, nmemb, size, compar, arg); + return; + } + + // Select the pivot. + + const size_t pivot_ix = _select_pivot(base, nmemb, size, compar, arg); + + // Partition the array. + + if (pivot_ix != 0) { + memswp(base, (char *)base + pivot_ix * size, size); + } + + const partition_result_t partition_result = + _partition(base, nmemb, size, compar, arg); + + // Recursively sort the subarrays. + + _quicksort(base, partition_result.eq_start, size, compar, arg); + _quicksort((char *)base + partition_result.gt_start * size, + nmemb - partition_result.gt_start, size, compar, arg); +} + +// qsort. + +typedef struct { + int (*compar)(const void *, const void *); +} qsort_qsort_r_arg_t; + +static int _qsort_qsort_r_compar(const void *const x, const void *const y, + qsort_qsort_r_arg_t *const arg) { + return arg->compar(x, y); +} + +void qsort(void *const base, const size_t nmemb, const size_t size, + int (*const compar)(const void *, const void *)) { + qsort_qsort_r_arg_t arg = {.compar = compar}; + qsort_r(base, nmemb, size, + (int (*)(const void *, const void *, void *))_qsort_qsort_r_compar, + &arg); +} + +// qsort_r. + +void qsort_r(void *const base, const size_t nmemb, const size_t size, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + _quicksort(base, nmemb, size, compar, arg); +} diff --git a/lab8/c/src/libc/string.c b/lab8/c/src/libc/string.c new file mode 100644 index 000000000..6451926c4 --- /dev/null +++ b/lab8/c/src/libc/string.c @@ -0,0 +1,148 @@ +#include "oscos/libc/string.h" + +#include "oscos/mem/malloc.h" + +__attribute__((used)) int memcmp(const void *const s1, const void *const s2, + const size_t n) { + const unsigned char *const s1_c = s1, *const s2_c = s2; + + for (size_t i = 0; i < n; i++) { + const int diff = (int)s1_c[i] - s2_c[i]; + if (diff != 0) + return diff; + } + + return 0; +} + +__attribute__((used)) void *memset(void *const s, const int c, const size_t n) { + unsigned char *const s_c = s; + + for (size_t i = 0; i < n; i++) { + s_c[i] = c; + } + + return s; +} + +static void __memmove_forward(unsigned char *const dest, + const unsigned char *const src, const size_t n) { + for (size_t i = 0; i < n; i++) { + dest[i] = src[i]; + } +} + +static void __memmove_backward(unsigned char *const dest, + const unsigned char *const src, const size_t n) { + for (size_t ip1 = n; ip1 > 0; ip1--) { + const size_t i = ip1 - 1; + dest[i] = src[i]; + } +} + +__attribute__((used)) void *memcpy(void *const restrict dest, + const void *const restrict src, + const size_t n) { + __memmove_forward(dest, src, n); + return dest; +} + +__attribute__((used)) void *memmove(void *const dest, const void *const src, + const size_t n) { + if (dest < src) { + __memmove_forward(dest, src, n); + } else if (dest > src) { + __memmove_backward(dest, src, n); + } + + return dest; +} + +int strcmp(const char *const s1, const char *const s2) { + for (const unsigned char *c1 = (const unsigned char *)s1, + *c2 = (const unsigned char *)s2; + *c1 || *c2; c1++, c2++) { + const int diff = (int)*c1 - *c2; + if (diff != 0) + return diff; + } + + return 0; +} + +int strncmp(const char *const s1, const char *const s2, const size_t n) { + size_t i = 0; + const unsigned char *c1 = (const unsigned char *)s1, + *c2 = (const unsigned char *)s2; + for (; i < n && (*c1 || *c2); i++, c1++, c2++) { + const int diff = (int)*c1 - *c2; + if (diff != 0) + return diff; + } + + return 0; +} + +size_t strlen(const char *s) { + size_t result = 0; + for (const char *c = s; *c; c++) { + result++; + } + return result; +} + +char *strdup(const char *s) { + const size_t len = strlen(s); + + char *const result = malloc(len + 1); + if (!result) + return NULL; + + memcpy(result, s, len); + result[len] = '\0'; + + return result; +} + +char *strndup(const char *const s, const size_t n) { + size_t len = 0; + for (const char *c = s; len < n && *c; c++) { + len++; + } + + char *const result = malloc(len + 1); + if (!result) + return NULL; + + memcpy(result, s, len); + result[len] = '\0'; + + return result; +} + +char *strchr(const char *const s, const int c) { + for (const char *p = s; *p; p++) { + if (*p == c) + return (char *)p; + } + return NULL; +} + +char *strcpy(char *const restrict dst, const char *const restrict src) { + char *restrict cd = dst; + const char *restrict cs = src; + while (*cs) { + *cd++ = *cs++; + } + + return dst; +} + +void memswp(void *const restrict xs, void *const restrict ys, const size_t n) { + unsigned char *const restrict xs_c = xs, *const restrict ys_c = ys; + for (size_t i = 0; i < n; i++) { + const unsigned char tmp = xs_c[i]; + xs_c[i] = ys_c[i]; + ys_c[i] = tmp; + } +} diff --git a/lab8/c/src/linker.ld b/lab8/c/src/linker.ld new file mode 100644 index 000000000..0ab7218ff --- /dev/null +++ b/lab8/c/src/linker.ld @@ -0,0 +1,78 @@ +/* +Memory map: (End addresses are exclusive) +0x 0 ( 0B) - 0x 1000 ( 4K): Reserved by firmware +0x 1000 ( 4K) - 0x 80000 (512K): Kernel heap +0x 80000 (512K) - 0x3ac00000 (940M): Kernel text, rodata, data, bss, heap +0x3ac00000 (940M) - 0x3b400000 (948M): Kernel stack +0x3b400000 (948M) - 0x3c000000 (960M): (QEMU only) Kernel heap +*/ + +_kernel_vm_base = 0xffff000000000000; +_skernel = _kernel_vm_base + 0x80000; +_max_ekernel = _kernel_vm_base + 0x3ac00000; + +MEMORY +{ + RAM_KERNEL : ORIGIN = _skernel, LENGTH = _max_ekernel - _skernel +} + +/* Must be 16-byte aligned. + The highest address the ARM core can use. Total system SDRAM is 1G. Top 76M + (or 64M if on QEMU) are reserved for VideoCore. */ +_estack = _kernel_vm_base + (1024M - 76M); +_sstack = _estack - 8M; + +ENTRY(_start) + +SECTIONS +{ + .text : + { + _stext = .; + + *(.text._start) /* Entry point. See `start.S`. */ + *(.text.unlikely .text.*_unlikely .text.unlikely.*) + *(.text.startup .text.startup.*) + *(.text.hot .text.hot.*) + *(.text .text.*) + *(.eh_frame) + *(.eh_frame_hdr) + + _etext = .; + } >RAM_KERNEL + + .rodata : + { + _srodata = .; + + *(.rodata .rodata.*) + + _erodata = .; + } >RAM_KERNEL + + .data : + { + _sdata = .; + + *(.data .data.*) + + _edata = .; + } >RAM_KERNEL + + /* The .bss section is 16-byte aligned to allow the section to be + zero-initialized with the `stp` instruction without using unaligned + memory accesses. See `start.S`. */ + .bss : ALIGN(16) + { + _sbss = .; + + *(.bss .bss.*) + *(COMMON) + + . = ALIGN(16); + _ebss = .; + } >RAM_KERNEL + + _ekernel = .; + _sheap = ALIGN(16); +} diff --git a/lab8/c/src/main.c b/lab8/c/src/main.c new file mode 100644 index 000000000..cdf681d76 --- /dev/null +++ b/lab8/c/src/main.c @@ -0,0 +1,94 @@ +#include "oscos/console.h" +#include "oscos/devicetree.h" +#include "oscos/drivers/aux.h" +#include "oscos/drivers/gpio.h" +#include "oscos/drivers/l1ic.h" +#include "oscos/drivers/l2ic.h" +#include "oscos/drivers/mailbox.h" +#include "oscos/drivers/pm.h" +#include "oscos/fs/initramfs.h" +#include "oscos/fs/tmpfs.h" +#include "oscos/fs/vfs.h" +#include "oscos/initrd.h" +#include "oscos/mem/malloc.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/mem/startup-alloc.h" +#include "oscos/mem/vm/kernel-page-tables.h" +#include "oscos/panic.h" +#include "oscos/sched.h" +#include "oscos/shell.h" +#include "oscos/timer/delay.h" +#include "oscos/timer/timeout.h" +#include "oscos/xcpt.h" + +static void _run_shell(void *const _arg) { + (void)_arg; + run_shell(); +} + +void main(const void *const dtb_start) { + // Initialize interrupt-related subsystems. + l1ic_init(); + l2ic_init(); + xcpt_set_vector_table(); + timeout_init(); + XCPT_UNMASK_ALL(); + + // Initialize the serial console. + gpio_init(); + aux_init(); + startup_alloc_init(); + console_init(); + + // Initialize the devicetree. + if (!devicetree_init(dtb_start)) { + console_puts("WARN: Devicetree blob is invalid."); + } + + // Initialize the initial ramdisk. + if (!initrd_init()) { + console_puts("WARN: Initial ramdisk is invalid."); + } + + // Initialize the memory allocators. + page_alloc_init(); + malloc_init(); + vm_setup_finer_granularity_linear_mapping(); + + // Initialize miscellaneous subsystems. + mailbox_init(); + pm_init(); + + // Initialize the scheduler. + + if (!sched_init()) { + PANIC("Cannot initialize scheduler: out of memory"); + } + + // Initialize VFS. + + int vfs_op_result; + + vfs_op_result = register_filesystem(&tmpfs); + if (vfs_op_result < 0) + PANIC("Cannot register tmpfs: errno %d", -vfs_op_result); + vfs_op_result = register_filesystem(&initramfs); + if (vfs_op_result < 0) + PANIC("Cannot register initramfs: errno %d", -vfs_op_result); + + vfs_op_result = tmpfs.setup_mount(&tmpfs, &rootfs); + if (vfs_op_result < 0) + PANIC("Cannot setup root file system: errno %d", -vfs_op_result); + + vfs_op_result = vfs_mkdir("/initramfs"); + if (vfs_op_result < 0) + PANIC("Cannot mkdir /initramfs: errno %d", -vfs_op_result); + vfs_op_result = vfs_mount("/initramfs", "initramfs"); + if (vfs_op_result < 0) + PANIC("Cannot mount initramfs on /initramfs: errno %d", -vfs_op_result); + + thread_create(_run_shell, NULL); + + sched_setup_periodic_scheduling(); + idle(); +} diff --git a/lab8/c/src/mem/malloc.c b/lab8/c/src/mem/malloc.c new file mode 100644 index 000000000..87cacb7b2 --- /dev/null +++ b/lab8/c/src/mem/malloc.c @@ -0,0 +1,353 @@ +// The design of the dynamic memory allocator resembles that of the slab +// allocator to some extent. However, compared to the slab allocator +// implementation in the Linux kernel [linux-slab-alloc], it only achieves the +// first of the three principle aims, namely, to help eliminate internal +// fragmentation. +// +// Each slab is backed by a single page. The first 32 bytes of the page are +// reserved for bookkeeping data, while the remaining area is split into +// equally-sized chunks that are units of allocation. There are different kinds +// of slabs for many different slot sizes. This allocator maintains free lists +// of slabs, one for each kind, chaining slabs of the same kind and with at +// least one available slot together. +// +// When the last reserved slot of a slab becomes available, the slab is +// immediately destroyed and the underlying page is returned to the page frame +// allocator. This design causes thrashing on certain allocation/deallocation +// patterns, but it keeps the code simple. +// +// Large allocation requests bypass the slab allocator and goes directly to the +// page frame allocator. The allocated memory is appropriately tagged so that +// void free(void *ptr) knows which memory allocator a memory is allocated with. +// An allocation request is considered large if the size is greater than 126 +// `max_align_t`, the maximum slot size that allows a slab to hold at least two +// slots. Indeed, if the request size is so large that a slab able to satisfy +// the request can only hold a single slot, then using the slab allocator offers +// no advantage at all. If the request size is even larger, then a slab with a +// large enough slot size will not be able to hold even a single slot. +// +// [linux-slab-alloc]: +// https://www.kernel.org/doc/gorman/html/understand/understand011.html + +#include "oscos/mem/malloc.h" + +#include +#include + +#include "oscos/libc/string.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/mem/vm.h" +#include "oscos/utils/critical-section.h" +#include "oscos/utils/math.h" + +/// \brief A node of a doubly-linked list. +typedef struct list_node_t { + struct list_node_t *prev, *next; +} list_node_t; + +/// \brief Slab metadata. +typedef struct { + /// \brief The number of slots. + uint8_t n_slots; + /// \brief The size of each slot in numbers of `max_align_t`. + uint8_t slot_size; +} slab_metadata_t; + +/// \brief Slab (or not). +typedef struct { + /// \brief Node of the free list. + /// + /// If `free_list_node.prev` is NULL, then this "slab" is in fact not a slab + /// but a memory allocated for a large allocation request. + /// + /// This field is put in the first position, so that obtaining a slab_t * from + /// a pointer to its `free_list_node` field is a no-op. + list_node_t free_list_node; + /// \brief Pointer to the head of the free list of the slabs of the same kind. + /// + /// This is used when the slab adds itself to the free list. + list_node_t *free_list_head; + /// \brief Metadata. + slab_metadata_t metadata; + /// \brief The number of reserved slots. + uint8_t n_slots_reserved; + /// \brief Bitset of reserved slots. + /// + /// The jth bit of `slots_reserved_bitset[i]` if set iff the i*64 + j slot is + /// reserved. + uint64_t slots_reserved_bitset[4]; + /// \brief The memory for the slots. + alignas(alignof(max_align_t)) unsigned char slots[]; +} slab_t; + +/// \brief Metadata of all supported types of slabs. +static const slab_metadata_t SLAB_METADATA[] = { + {.n_slots = 252, .slot_size = 1}, {.n_slots = 126, .slot_size = 2}, + {.n_slots = 84, .slot_size = 3}, {.n_slots = 63, .slot_size = 4}, + {.n_slots = 50, .slot_size = 5}, {.n_slots = 42, .slot_size = 6}, + {.n_slots = 36, .slot_size = 7}, {.n_slots = 31, .slot_size = 8}, + {.n_slots = 28, .slot_size = 9}, {.n_slots = 25, .slot_size = 10}, + {.n_slots = 22, .slot_size = 11}, {.n_slots = 21, .slot_size = 12}, + {.n_slots = 19, .slot_size = 13}, {.n_slots = 18, .slot_size = 14}, + {.n_slots = 16, .slot_size = 15}, {.n_slots = 15, .slot_size = 16}, + {.n_slots = 14, .slot_size = 18}, {.n_slots = 13, .slot_size = 19}, + {.n_slots = 12, .slot_size = 21}, {.n_slots = 11, .slot_size = 22}, + {.n_slots = 10, .slot_size = 25}, {.n_slots = 9, .slot_size = 28}, + {.n_slots = 8, .slot_size = 31}, {.n_slots = 7, .slot_size = 36}, + {.n_slots = 6, .slot_size = 42}, {.n_slots = 5, .slot_size = 50}, + {.n_slots = 4, .slot_size = 63}, {.n_slots = 3, .slot_size = 84}, + {.n_slots = 2, .slot_size = 126}}; + +/// \brief The number of slab types. +#define N_SLAB_TYPES (sizeof(SLAB_METADATA) / sizeof(slab_metadata_t)) + +/// \brief The threshold in numbers of `max_align_t` an allocation request whose +/// size is more than which is considered a large allocation request. +#define LARGE_ALLOC_THRESHOLD 126 + +/// \brief Free lists of slabs for each slab type. +static list_node_t _slab_free_lists[N_SLAB_TYPES]; + +/// \brief Gets the slab type ID (the index that can be used to index +/// `SLAB_METADATA` or the free list) from the size of the allocation +/// request. +/// +/// \param n_units The size of the allocation request in numbers of "allocation +/// units", i.e., `max_align_t`. +static size_t _get_slab_type_id(const size_t n_units) { + if (n_units == 0 || n_units > LARGE_ALLOC_THRESHOLD) + __builtin_unreachable(); + + return n_units <= 16 ? n_units - 1 : N_SLAB_TYPES + 1 - 252 / n_units; +} + +// Slab operations. + +/// \brief Adds a slab to its free list. +/// +/// The `free_list_head` field of \p slab must be initialized and \p slab must +/// not have been on any free list. +/// +/// This function is safe to call only within a critical section. +static void _add_slab_to_free_list(slab_t *const slab) { + list_node_t *const free_list_first_entry = slab->free_list_head->next; + slab->free_list_node.next = free_list_first_entry; + free_list_first_entry->prev = &slab->free_list_node; + slab->free_list_node.prev = slab->free_list_head; + slab->free_list_head->next = &slab->free_list_node; +} + +/// \brief Removes a slab from its free list. +/// +/// \p slab must have been on a free list. +/// +/// This function is safe to call only within a critical section. +static void _remove_slab_from_free_list(slab_t *const slab) { + slab->free_list_node.prev->next = slab->free_list_node.next; + slab->free_list_node.next->prev = slab->free_list_node.prev; +} + +/// \brief Allocates a new slab and adds it onto its free list. +/// +/// This function is safe to call only within a critical section. +static slab_t *_alloc_slab(const size_t slab_type_id) { + // Allocate space for the slab. + + const spage_id_t page = alloc_pages_unlocked(0); + if (page < 0) + return NULL; + + slab_t *slab = (slab_t *)pa_to_kernel_va(page_id_to_pa(page)); + if (!slab) { + // We cannot accept a null slab pointer in this implementation due to the + // `free_list_node.prev` being used as a tag to identify memories for large + // allocation requests. Allocate a new page and return the previously + // allocated page to the page frame allocator. + // (In practice, this code path is never taken.) + + const spage_id_t another_page = alloc_pages_unlocked(0); + free_pages(page); + if (another_page < 0) { + return NULL; + } + + slab = (slab_t *)pa_to_kernel_va(page_id_to_pa(another_page)); + } + + // Initialize the fields. + + slab->free_list_head = &_slab_free_lists[slab_type_id]; + slab->metadata = SLAB_METADATA[slab_type_id]; + slab->n_slots_reserved = 0; + memset(slab->slots_reserved_bitset, 0, sizeof(slab->slots_reserved_bitset)); + _add_slab_to_free_list(slab); + + return slab; +} + +/// \brief Gets a slab of the given type with at least one free slot. If there +/// is none, allocates a new one. +static slab_t *_get_or_alloc_slab(const size_t slab_type_id) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + slab_t *result; + if (_slab_free_lists[slab_type_id].next == + &_slab_free_lists[slab_type_id]) { // The free list is empty. + result = _alloc_slab(slab_type_id); + } else { + list_node_t *const free_list_first_entry = + _slab_free_lists[slab_type_id].next; + result = (slab_t *)((char *)free_list_first_entry - + offsetof(slab_t, free_list_node)); + } + + CRITICAL_SECTION_LEAVE(daif_val); + return result; +} + +/// \brief Gets the index of the first free slot of the given slab. +/// +/// \p slab must have at least one free slot. +static size_t _get_first_free_slot_ix(const slab_t *const slab) { + for (size_t i = 0;; i++) { + uint64_t reversed_negated_bitset; + __asm__("rbit %0, %1" + : "=r"(reversed_negated_bitset) + : "r"(~slab->slots_reserved_bitset[i])); + + uint64_t j; + __asm__("clz %0, %1" : "=r"(j) : "r"(reversed_negated_bitset)); + + if (j != 64) { // The jth bit of `slab->slots_reserved_bitset[i]` is clear. + return i * 64 + j; + } + } +} + +/// \brief Allocates a slot from the given slab. +/// +/// \p slab must have at least one free slot. +static void *_alloc_from_slab(slab_t *const slab) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const size_t free_slot_ix = _get_first_free_slot_ix(slab); + + // Mark the `free_slot_ix`th slot as reserved. + + slab->n_slots_reserved++; + slab->slots_reserved_bitset[free_slot_ix / 64] |= (uint64_t)1 + << (free_slot_ix % 64); + + // Remove itself from its free list if there are no free slots. + + if (slab->n_slots_reserved == slab->metadata.n_slots) { + _remove_slab_from_free_list(slab); + } + + CRITICAL_SECTION_LEAVE(daif_val); + return slab->slots + free_slot_ix * (slab->metadata.slot_size * 16); +} + +/// \brief Frees a slot to the given slab. +/// +/// \param slab The slab. +/// \param ptr The pointer to the slot. +static void _free_to_slab(slab_t *const slab, void *const ptr) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const size_t slot_ix = ((uintptr_t)ptr - (uintptr_t)slab->slots) / + (slab->metadata.slot_size * 16); + + // Adds the slab to its free list if it wasn't on its free list. + + if (slab->n_slots_reserved == slab->metadata.n_slots) { + _add_slab_to_free_list(slab); + } + + // Mark the slot as available. + + slab->n_slots_reserved--; + slab->slots_reserved_bitset[slot_ix / 64] &= ~(1ULL << (slot_ix % 64)); + + // Return the slab to the page frame allocator if it has no reserved slots. + + if (slab->n_slots_reserved == 0) { + _remove_slab_from_free_list(slab); + free_pages(pa_to_page_id(kernel_va_to_pa(slab))); + } + + CRITICAL_SECTION_LEAVE(daif_val); +} + +// Large allocation. + +/// \brief Allocates memory using the large allocation mechanism. +/// +/// In principle, using this function to satisfy small allocation requests will +/// not cause problems (UB or kernel panic), but doing so wastes a lot of +/// memory. +/// +/// \param size The size of the allocation in bytes. +static void *_malloc_large(const size_t size) { + const size_t actual_size = alignof(max_align_t) + size; + const size_t n_pages = (actual_size + ((1 << PAGE_ORDER) - 1)) >> PAGE_ORDER; + const size_t block_order = clog2(n_pages); + + spage_id_t page = alloc_pages(block_order); + if (page < 0) + return NULL; + + char *page_va = pa_to_kernel_va(page_id_to_pa(page)); + // Mark the "slab" as not a slab. + ((slab_t *)page_va)->free_list_node.prev = NULL; + + return page_va + alignof(max_align_t); +} + +/// \brief Frees memory allocated using void *_malloc_large(size_t). +/// \param slab_ptr The pointer to the "slab". (N.B. not the pointer returned by +/// void *_malloc_large(size_t)!) +static void _free_large(slab_t *const slab_ptr) { + free_pages(pa_to_page_id(kernel_va_to_pa(slab_ptr))); +} + +// Public functions. + +void malloc_init(void) { + // Initialize the free lists. (All free lists are initially empty.) + + for (size_t i = 0; i < N_SLAB_TYPES; i++) { + _slab_free_lists[i].prev = _slab_free_lists[i].next = &_slab_free_lists[i]; + } +} + +void *malloc(const size_t size) { + if (size == 0) + return NULL; + + const size_t n_units = (size + (alignof(max_align_t) - 1)) >> 4; + if (n_units > LARGE_ALLOC_THRESHOLD) + return _malloc_large(size); + + const size_t slab_type_id = _get_slab_type_id(n_units); + slab_t *const slab = _get_or_alloc_slab(slab_type_id); + if (!slab) + return NULL; + + return _alloc_from_slab(slab); +} + +void free(void *const ptr) { + if (!ptr) + return; + + slab_t *const ptr_s = (slab_t *)((uintptr_t)ptr & ~((1 << PAGE_ORDER) - 1)); + if (!ptr_s->free_list_node.prev) { // Not a slab. + _free_large(ptr_s); + } else { + _free_to_slab(ptr_s, ptr); + } +} diff --git a/lab8/c/src/mem/page-alloc.c b/lab8/c/src/mem/page-alloc.c new file mode 100644 index 000000000..cd510492e --- /dev/null +++ b/lab8/c/src/mem/page-alloc.c @@ -0,0 +1,662 @@ +// The page frame allocator uses the buddy system. Much of the design is based +// on that described in The Art of Computer Programming by Donald Knuth, +// section 2.5. +// +// The page frame allocator maintains the page frame array, an array of size +// equal to the number of page frames and entry type `page_frame_array_entry_t`, +// which tracks each block's reservation status and order. Unlike the design +// described in the specification [spec], not all entries have valid data. If +// page i starts a block of order k, regardless of the reservation status of the +// block, the entries in the index range [i+1:i+2^k] are not read and are thus +// left uninitialized. Unlike TAOCP's design, the order of a block is stored in +// the page frame array even if the block is reserved. (Note: TAOCP's +// reservation algorithm skips storing the order of the allocated block.) This +// design decision is because, unlike TAOCP's liberation algorithm, the +// void free_pages(page_id_t) function does not have access to the order of the +// block from the arguments and must instead retrieve this information from the +// page frame array. +// +// The page frame allocator also maintains free lists of blocks, one for each +// order. Each free list is a doubly-linked list with page frame array entries +// themselves as nodes. Thus, in addition to the reservation status and the +// order of the corresponding block, each page frame array entry also contains +// the index into the page frame array of the previous and the next node on the +// free list. This implementation adopts the technique described in TAOCP +// section 2.2.5 of using the list node type itself to store the head and tail +// pointers of the list(from now on referred to as "free list header"), +// simplifying list manipulation code. TAOCP's allocator design also uses this +// technique. Since the free list headers are necessarily outside of the page +// frame array, this technique requires that the indices mentioned above be able +// to refer to page frame array entries (list nodes) outside the page frame +// array. To solve this problem without adding complexity to the code, the free +// list headers and the page frame array are allocated together, with the former +// placed right before the latter, and we use negative array indices to refer to +// entries in the free list headers. +// +// [spec]: https://oscapstone.github.io/labs/lab4.html + +#include "oscos/mem/page-alloc.h" + +#include "oscos/console.h" +#include "oscos/devicetree.h" +#include "oscos/initrd.h" +#include "oscos/mem/startup-alloc.h" +#include "oscos/mem/vm.h" +#include "oscos/panic.h" +#include "oscos/utils/critical-section.h" + +// `MAX_BLOCK_ORDER` can be changed to any positive integer up to and +// including 25 without modification to the code. + +// Symbols defined in the linker script. +extern char _skernel[], _ekernel[], _sstack[], _estack[]; + +typedef struct { + bool is_avail : 1; + unsigned order : 5; + signed linkf : 26; + signed linkb : 32; +} page_frame_array_entry_t; + +static pa_t _pa_start; +static page_frame_array_entry_t *_page_frame_array, *_free_list; + +// Utilities used by page_alloc_init. + +// _mark_region + +/// \brief Marks a region as either reserved or available. +/// +/// \param region_limit The region limit. Only the part of \p region that lies +/// within this limit will be marked. +/// \param region The region to mark. +/// \param is_avail The target reservation status. +static void _mark_region(const pa_range_t region_limit, const pa_range_t region, + const bool is_avail) { + const pa_t effective_start = region_limit.start > region.start + ? region_limit.start + : region.start, + effective_end = + region_limit.end < region.end ? region_limit.end : region.end; + + if (effective_start < effective_end) { + mark_pages_unlocked(pa_range_to_page_id_range((pa_range_t){ + .start = effective_start, .end = effective_end}), + is_avail); + } +} + +// _get_usable_pa_range + +typedef struct { + fdt_n_address_size_cells_t root_n_cells; + pa_range_t range; +} get_usable_pa_range_fdt_traverse_arg_t; + +static control_flow_t _get_usable_pa_range_fdt_traverse_callback( + get_usable_pa_range_fdt_traverse_arg_t *const arg, + const fdt_item_t *const node, + const fdt_traverse_parent_list_node_t *const parent) { + if (!parent) { // Current node is the root node. + arg->root_n_cells = fdt_get_n_address_size_cells(node); + } else if (parent && !parent->parent && + strncmp(FDT_NODE_NAME(node), "memory@", 7) == + 0) { // Current node is /memory@... + FDT_FOR_ITEM(node, item) { + if (FDT_TOKEN(item) == FDT_PROP) { + const fdt_prop_t *const prop = (const fdt_prop_t *)item->payload; + if (strcmp(FDT_PROP_NAME(prop), "reg") == 0) { + const fdt_read_reg_result_t read_result = + fdt_read_reg(prop, arg->root_n_cells); + const pa_t start = read_result.value.address, + end = start + read_result.value.size; + if (read_result.address_overflow || read_result.size_overflow || + read_result.value.address > PA_MAX || + read_result.value.size > PA_MAX || end < start) + PANIC( + "page-alloc: reg property value overflow in devicetree node %s", + FDT_NODE_NAME(node)); + + if (start < arg->range.start) { + arg->range.start = start; + } + if (end > arg->range.end) { + arg->range.end = end; + } + } + } + } + } + + return CF_CONTINUE; +} + +/// \brief Gets the physical address range containing all usable memory regions. +static pa_range_t _get_usable_pa_range(void) { + if (devicetree_is_init()) { + get_usable_pa_range_fdt_traverse_arg_t arg = { + .range = {.start = PA_MAX, .end = 0}}; + fdt_traverse( + (fdt_traverse_callback_t *)_get_usable_pa_range_fdt_traverse_callback, + &arg); + return arg.range; + } else { + return (pa_range_t){.start = 0x0, .end = 0x3b400000}; + } +} + +// _mark_usable_regions + +typedef struct { + pa_range_t usable_pa_range; + fdt_n_address_size_cells_t root_n_cells; +} mark_usable_regions_fdt_traverse_arg_t; + +static control_flow_t _mark_usable_regions_fdt_traverse_callback( + mark_usable_regions_fdt_traverse_arg_t *const arg, + const fdt_item_t *const node, + const fdt_traverse_parent_list_node_t *const parent) { + if (!parent) { // Current node is the root node. + arg->root_n_cells = fdt_get_n_address_size_cells(node); + } else if (parent && !parent->parent && + strncmp(FDT_NODE_NAME(node), "memory@", 7) == + 0) { // Current node is /memory@... + FDT_FOR_ITEM(node, item) { + if (FDT_TOKEN(item) == FDT_PROP) { + const fdt_prop_t *const prop = (const fdt_prop_t *)item->payload; + if (strcmp(FDT_PROP_NAME(prop), "reg") == 0) { + const fdt_read_reg_result_t read_result = + fdt_read_reg(prop, arg->root_n_cells); + const pa_t start = read_result.value.address, + end = start + read_result.value.size; + if (read_result.address_overflow || read_result.size_overflow || + read_result.value.address > PA_MAX || + read_result.value.size > PA_MAX || end < start) + PANIC( + "page-alloc: reg property value overflow in devicetree node %s", + FDT_NODE_NAME(node)); + + _mark_region(arg->usable_pa_range, + (pa_range_t){.start = start, .end = end}, true); + } + } + } + } + + return CF_CONTINUE; +} + +/// \brief Marks the usable memory regions as available. +/// +/// \param usable_pa_range The physical address range containing all usable +/// memory regions. Can be obtained by +/// pa_range_t _get_usable_pa_range(void). +static void _mark_usable_regions(const pa_range_t usable_pa_range) { + if (devicetree_is_init()) { + mark_usable_regions_fdt_traverse_arg_t arg = {.usable_pa_range = + usable_pa_range}; + fdt_traverse( + (fdt_traverse_callback_t *)_mark_usable_regions_fdt_traverse_callback, + &arg); + } else { + _mark_region(usable_pa_range, (pa_range_t){.start = 0x0, .end = 0x3b400000}, + true); + } +} + +// _mark_reserved_regions + +typedef struct { + pa_range_t usable_pa_range; + const fdt_item_t *reserved_memory_node; + fdt_n_address_size_cells_t reserved_memory_n_cells; +} mark_reserved_regions_fdt_traverse_arg_t; + +static control_flow_t _mark_reserved_regions_fdt_traverse_callback( + mark_reserved_regions_fdt_traverse_arg_t *const arg, + const fdt_item_t *const node, + const fdt_traverse_parent_list_node_t *const parent) { + if (parent && !parent->parent && + strcmp(FDT_NODE_NAME(node), "reserved-memory") == + 0) { // Current node is /reserved-memory. + arg->reserved_memory_node = node; + arg->reserved_memory_n_cells = fdt_get_n_address_size_cells(node); + } else if (arg->reserved_memory_node) { // The /reserved-memory node has been + // traversed. + if (parent->node == + arg->reserved_memory_node) { // Current node is /reserved-memory/... + FDT_FOR_ITEM(node, item) { + if (FDT_TOKEN(item) == FDT_PROP) { + const fdt_prop_t *const prop = (const fdt_prop_t *)item->payload; + if (strcmp(FDT_PROP_NAME(prop), "reg") == 0) { + const fdt_read_reg_result_t read_result = + fdt_read_reg(prop, arg->reserved_memory_n_cells); + const pa_t start = read_result.value.address, + end = start + read_result.value.size; + if (read_result.address_overflow || read_result.size_overflow || + read_result.value.address > PA_MAX || + read_result.value.size > PA_MAX || end < start) + PANIC("page-alloc: reg property value overflow in devicetree " + "node %s", + FDT_NODE_NAME(node)); + + _mark_region(arg->usable_pa_range, + (pa_range_t){.start = start, .end = end}, false); + + break; + } + } + } + } else { // All children of the /reserved-memory node has been traversed. + return CF_BREAK; + } + } + + return CF_CONTINUE; +} + +/// \brief Marks the reserved memory regions as reserved. +/// +/// \param usable_pa_range The physical address range containing all usable +/// memory regions. Can be obtained by +/// pa_range_t _get_usable_pa_range(void). +static void _mark_reserved_regions(const pa_range_t usable_pa_range) { + if (devicetree_is_init()) { + // Devicetree. + + _mark_region(usable_pa_range, + (pa_range_t){.start = (pa_t)(uintptr_t)fdt_get_start(), + .end = (pa_t)(uintptr_t)fdt_get_end()}, + false); + + // Anything in the memory reservation block. + + for (const fdt_reserve_entry_t *reserve_entry = FDT_START_MEM_RSVMAP; + !(reserve_entry->address == 0 && reserve_entry->size == 0); + reserve_entry++) { + const pa_t start = rev_u64(reserve_entry->address), + end = start + rev_u64(reserve_entry->size); + _mark_region(usable_pa_range, (pa_range_t){.start = start, .end = end}, + false); + } + + // Spin tables for multicore boot, etc. + + mark_reserved_regions_fdt_traverse_arg_t arg = {.usable_pa_range = + usable_pa_range}; + fdt_traverse( + (fdt_traverse_callback_t *)_mark_reserved_regions_fdt_traverse_callback, + &arg); + } else { + // Spin tables for multicore boot. + + _mark_region(usable_pa_range, (pa_range_t){.start = 0x0, .end = 0x1000}, + false); + } + + // Kernel image in the physical memory. + + _mark_region(usable_pa_range, + (pa_range_t){.start = (pa_t)(uintptr_t)_skernel, + .end = (pa_t)(uintptr_t)_ekernel}, + false); + + // Initial ramdisk. + + _mark_region(usable_pa_range, + (pa_range_t){.start = (pa_t)(uintptr_t)initrd_get_start(), + .end = (pa_t)(uintptr_t)initrd_get_end()}, + false); + + // Kernel stack. + + _mark_region(usable_pa_range, + (pa_range_t){.start = (pa_t)(uintptr_t)_sstack, + .end = (pa_t)(uintptr_t)_estack}, + false); +} + +void page_alloc_init(void) { + // Determine the starting and ending physical address. + + pa_range_t usable_pa_range = _get_usable_pa_range(); + _pa_start = usable_pa_range.start; + + const pa_t max_supported_pa = + _pa_start + (1 << (PAGE_ORDER + MAX_BLOCK_ORDER)); + if (usable_pa_range.end > max_supported_pa) { + console_printf("WARN: page-alloc: End of usable memory region 0x%" PRIxPA + " is greater than maximum supported PA 0x%" PRIxPA ".\n", + usable_pa_range.end, max_supported_pa); + usable_pa_range.end = max_supported_pa; + } + + // Allocate the page frame array and the free list. + + page_frame_array_entry_t *const entries = + startup_alloc(((MAX_BLOCK_ORDER + 1) + (1 << MAX_BLOCK_ORDER)) * + sizeof(page_frame_array_entry_t)); + _page_frame_array = entries + (MAX_BLOCK_ORDER + 1); + _free_list = entries; + + // Initialize the page frame array. (The entire memory region is initially + // reserved.) + + _page_frame_array[0].is_avail = false; + _page_frame_array[0].order = MAX_BLOCK_ORDER; + + // Initialize the free list. (The free list is initially empty.) + + for (size_t order = 0; order <= MAX_BLOCK_ORDER; order++) { + _free_list[order].linkf = _free_list[order].linkb = + (int)order - (MAX_BLOCK_ORDER + 1); + } + + // Mark the usable regions as usable. + + _mark_usable_regions(usable_pa_range); + + // Mark the reserved regions as reserved. + + _mark_reserved_regions(usable_pa_range); + + // Mark the region used by the startup allocator as reserved. + + _mark_region(usable_pa_range, + kernel_va_range_to_pa_range(startup_alloc_get_alloc_range()), + false); +} + +/// \brief Adds a block to the free list. +/// +/// \param page The page number of the first page of the block. +static void _add_block_to_free_list(const page_id_t page) { + const size_t order = _page_frame_array[page].order; + + const int32_t free_list_first_entry = _free_list[order].linkf; + _page_frame_array[page].linkf = free_list_first_entry; + _page_frame_array[free_list_first_entry].linkb = page; + _page_frame_array[page].linkb = (int)order - (MAX_BLOCK_ORDER + 1); + _free_list[order].linkf = page; +} + +/// \brief Removes a block from the free list. +/// +/// \param page The page number of the first page of the block. +static void _remove_block_from_free_list(const page_id_t page) { + _page_frame_array[_page_frame_array[page].linkb].linkf = + _page_frame_array[page].linkf; + _page_frame_array[_page_frame_array[page].linkf].linkb = + _page_frame_array[page].linkb; +} + +/// \brief Removes all free blocks within a page range that is valid as the page +/// range of a block from the free list. +/// +/// \param range The page range. Must be valid as the page range of a block. +/// \param order The order of the block of which \p range is valid as the page +/// range. I.e., log base 2 of the span of the range. +static void +_remove_free_blocks_in_range_from_free_list(const page_id_range_t range, + const size_t order) { + if (_page_frame_array[range.start].order == order) { + if (_page_frame_array[range.start].is_avail) { + _remove_block_from_free_list(range.start); + } + } else { + if (order == 0) + __builtin_unreachable(); + + // This will not cause integer overflow, since the maximum order is at most + // 25 and the node ID is at most 2²⁵. + const page_id_t mid = (range.start + range.end) / 2; + + _remove_free_blocks_in_range_from_free_list( + (page_id_range_t){.start = range.start, .end = mid}, order - 1); + _remove_free_blocks_in_range_from_free_list( + (page_id_range_t){.start = mid, .end = range.end}, order - 1); + } +} + +/// \brief Splits a block. +/// +/// If the block is initially free, this function updates the free lists +/// accordingly. +/// +/// \param page The page number of the first page of the block. +static void _split_block(const page_id_t page) { + if (_page_frame_array[page].order == 0) + __builtin_unreachable(); + +#ifdef PAGE_ALLOC_ENABLE_LOG + const page_id_t end_page = page + (1 << _page_frame_array[page].order); + console_printf("DEBUG: page-alloc: Splitting block 0x%" PRIxPAGEID + " - 0x%" PRIxPAGEID " of order %u.\n", + page, end_page, _page_frame_array[page].order); +#endif + + if (_page_frame_array[page].is_avail) { + _remove_block_from_free_list(page); + } + + const size_t order_p1 = _page_frame_array[page].order - 1; + const page_id_t buddy_page = page + (1 << order_p1); + + _page_frame_array[page].order = order_p1; + _page_frame_array[buddy_page].is_avail = _page_frame_array[page].is_avail; + _page_frame_array[buddy_page].order = order_p1; + + if (_page_frame_array[page].is_avail) { + _add_block_to_free_list(page); + _add_block_to_free_list(buddy_page); + } +} + +spage_id_t alloc_pages(const size_t order) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const spage_id_t result = alloc_pages_unlocked(order); + + CRITICAL_SECTION_LEAVE(daif_val); + return result; +} + +spage_id_t alloc_pages_unlocked(const size_t order) { +#ifdef PAGE_ALLOC_ENABLE_LOG + console_printf("DEBUG: page-alloc: Allocating a block of order %zu\n", order); +#endif + + // Find block. + + size_t avail_block_order; + for (avail_block_order = order; avail_block_order <= MAX_BLOCK_ORDER; + avail_block_order++) { + if (_free_list[avail_block_order].linkf >= + 0) { // The free list is nonempty. + break; + } + } + + if (avail_block_order > + MAX_BLOCK_ORDER) { // No block of order >= `order` found. + return -1; + } + + const page_id_t page = _free_list[avail_block_order].linkf; + + // Remove from list. + + _remove_block_from_free_list(page); + + // Split. + + while (avail_block_order > order) { + // We could have used void _split_block(page_id_t) to perform block + // splitting, but the custom logic here avoids unnecessary free list + // operations (adding a block to the free list and then immediately removing + // it in the next loop iteration) that would have been performed by the said + // function. + +#ifdef PAGE_ALLOC_ENABLE_LOG + const page_id_t end_page = page + (1 << avail_block_order); + console_printf("DEBUG: page-alloc: Splitting block 0x%" PRIxPAGEID + " - 0x%" PRIxPAGEID " of order %zu.\n", + page, end_page, avail_block_order); +#endif + + avail_block_order--; + + const page_id_t buddy_page = page + (1 << avail_block_order); + + _page_frame_array[buddy_page].is_avail = true; + _page_frame_array[buddy_page].order = avail_block_order; + + // Add `buddy_page` to the free list, which is empty. + // We could have used void _add_block_to_free_list(page_id_t), but the + // custom logic here saves a few instructions. + _page_frame_array[buddy_page].linkf = _page_frame_array[buddy_page].linkb = + (int)avail_block_order - (MAX_BLOCK_ORDER + 1); + _free_list[avail_block_order].linkf = _free_list[avail_block_order].linkb = + buddy_page; + } + + _page_frame_array[page].is_avail = false; + _page_frame_array[page].order = order; + return page; +} + +void free_pages(const page_id_t page) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + free_pages_unlocked(page); + + CRITICAL_SECTION_LEAVE(daif_val); +} + +void free_pages_unlocked(const page_id_t page) { + const size_t order = _page_frame_array[page].order; + +#ifdef PAGE_ALLOC_ENABLE_LOG + const page_id_t end_page = page + (1 << order); + console_printf("DEBUG: page-alloc: Freeing block 0x%" PRIxPAGEID + " - 0x%" PRIxPAGEID " of order %zu\n", + page, end_page, order); +#endif + + // Combine with buddy. + + page_id_t curr_page = page; + size_t curr_order; + for (curr_order = order; curr_order < MAX_BLOCK_ORDER; curr_order++) { + const page_id_t buddy_page = curr_page ^ (1 << curr_order); + if (!(_page_frame_array[buddy_page].is_avail && + _page_frame_array[buddy_page].order == curr_order)) + break; + +#ifdef PAGE_ALLOC_ENABLE_LOG + const page_id_t end_curr_page = curr_page + (1 << curr_order); + const page_id_t end_buddy_page = buddy_page + (1 << curr_order); + console_printf( + "DEBUG: page-alloc: Combining block 0x%" PRIxPAGEID " - 0x%" PRIxPAGEID + " of order %zu with its buddy 0x%" PRIxPAGEID " - 0x%" PRIxPAGEID ".\n", + curr_page, end_curr_page, curr_order, buddy_page, end_buddy_page); +#endif + + _remove_block_from_free_list(buddy_page); + if (buddy_page < curr_page) { + curr_page = buddy_page; + } + } + + _page_frame_array[curr_page].is_avail = true; + _page_frame_array[curr_page].order = curr_order; + _add_block_to_free_list(curr_page); +} + +static void _mark_pages_rec(const page_id_range_t range, const bool is_avail, + const size_t order, + const page_id_range_t block_range) { + if (_page_frame_array[block_range.start].order == order && + _page_frame_array[block_range.start].is_avail == + is_avail) { // The entire block is already marked as the desired + // reservation status. + // No-op. + } else if (range.start == block_range.start && range.end == block_range.end) { + // Mark the entire block. + + _remove_free_blocks_in_range_from_free_list(range, order); + + _page_frame_array[range.start].is_avail = is_avail; + _page_frame_array[range.start].order = order; + if (is_avail) { + _add_block_to_free_list(range.start); + } + } else { // Recursive case. + if (order == 0) + __builtin_unreachable(); + + // Split the block if needed. + if (_page_frame_array[block_range.start].order == order) { + _split_block(block_range.start); + } + + // This will not cause integer overflow, since the maximum order is at most + // 25 and the node ID is at most 2²⁵. + const size_t mid = (block_range.start + block_range.end) / 2; + + if (range.end <= mid) { // The range lies entirely within the left half of + // the node range. + _mark_pages_rec( + range, is_avail, order - 1, + (page_id_range_t){.start = block_range.start, .end = mid}); + } else if (mid <= range.start) { // The range lies entirely within the right + // half of the node range. + _mark_pages_rec(range, is_avail, order - 1, + (page_id_range_t){.start = mid, .end = block_range.end}); + } else { // The range crosses the middle of the node range. + _mark_pages_rec( + (page_id_range_t){.start = range.start, .end = mid}, is_avail, + order - 1, (page_id_range_t){.start = block_range.start, .end = mid}); + _mark_pages_rec((page_id_range_t){.start = mid, .end = range.end}, + is_avail, order - 1, + (page_id_range_t){.start = mid, .end = block_range.end}); + } + } +} + +void mark_pages(const page_id_range_t range, const bool is_avail) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + mark_pages_unlocked(range, is_avail); + + CRITICAL_SECTION_LEAVE(daif_val); +} + +void mark_pages_unlocked(const page_id_range_t range, const bool is_avail) { +#ifdef PAGE_ALLOC_ENABLE_LOG + console_printf("DEBUG: page-alloc: Marking pages 0x%" PRIxPAGEID + " - 0x%" PRIxPAGEID " as %s.\n", + range.start, range.end, is_avail ? "available" : "reserved"); +#endif + + // TODO: Switch to a non-recursive implementation. + _mark_pages_rec(range, is_avail, MAX_BLOCK_ORDER, + (page_id_range_t){.start = 0, .end = 1 << MAX_BLOCK_ORDER}); +} + +pa_t page_id_to_pa(const page_id_t page) { + return _pa_start + (page << PAGE_ORDER); +} + +page_id_t pa_to_page_id(const pa_t pa) { + return (pa - _pa_start) >> PAGE_ORDER; +} + +page_id_range_t pa_range_to_page_id_range(const pa_range_t range) { + return (page_id_range_t){ + .start = (range.start - _pa_start) >> PAGE_ORDER, + .end = ((range.end - _pa_start) + ((1 << PAGE_ORDER) - 1)) >> PAGE_ORDER}; +} diff --git a/lab8/c/src/mem/shared-page.c b/lab8/c/src/mem/shared-page.c new file mode 100644 index 000000000..db4a86e3f --- /dev/null +++ b/lab8/c/src/mem/shared-page.c @@ -0,0 +1,147 @@ +#include "oscos/mem/shared-page.h" + +#include "oscos/libc/string.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/mem/vm.h" +#include "oscos/utils/critical-section.h" +#include "oscos/utils/rb.h" + +static rb_node_t *_page_refcnts = NULL; + +typedef struct { + page_id_t page_id; + size_t refcnt; +} page_refcnt_entry_t; + +static int +_cmp_page_refcnt_entry_by_page_id(const page_refcnt_entry_t *const e1, + const page_refcnt_entry_t *const e2, + void *_arg) { + (void)_arg; + + if (e1->page_id < e2->page_id) + return -1; + if (e1->page_id > e2->page_id) + return 1; + return 0; +} + +static int +_cmp_page_id_and_page_refcnt_entry(const page_id_t *const page, + const page_refcnt_entry_t *const entry, + void *const _arg) { + (void)_arg; + + if (*page < entry->page_id) + return -1; + if (*page > entry->page_id) + return 1; + return 0; +} + +spage_id_t shared_page_alloc(void) { + spage_id_t result = alloc_pages(0); + if (result < 0) + return result; + + const page_refcnt_entry_t new_entry = {.page_id = result, .refcnt = 1}; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + rb_insert(&_page_refcnts, sizeof(page_refcnt_entry_t), &new_entry, + (int (*)(const void *, const void *, + void *))_cmp_page_refcnt_entry_by_page_id, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + return result; +} + +size_t shared_page_getref(const page_id_t page) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const page_refcnt_entry_t *const entry = + rb_search(_page_refcnts, &page, + (int (*)(const void *, const void *, + void *))_cmp_page_id_and_page_refcnt_entry, + NULL); + const size_t result = entry->refcnt; + + CRITICAL_SECTION_LEAVE(daif_val); + + return result; +} + +void shared_page_incref(const page_id_t page) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + // It's safe to cast away const here, since every entry _page_refcnts points + // to is not const. Also, incrementing the reference count doesn't invalidate + // the BST invariant. + page_refcnt_entry_t *const entry = (page_refcnt_entry_t *)rb_search( + _page_refcnts, &page, + (int (*)(const void *, const void *, + void *))_cmp_page_id_and_page_refcnt_entry, + NULL); + + // This function is sometimes called on a non-shared page; more specifically, + // linearly-mapped pages. + if (entry) { + entry->refcnt++; + } + + CRITICAL_SECTION_LEAVE(daif_val); +} + +void shared_page_decref(const page_id_t page) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + // It's safe to cast away const here, since every entry _page_refcnts points + // to is not const. Also, incrementing the reference count doesn't invalidate + // the BST invariant. + page_refcnt_entry_t *const entry = (page_refcnt_entry_t *)rb_search( + _page_refcnts, &page, + (int (*)(const void *, const void *, + void *))_cmp_page_id_and_page_refcnt_entry, + NULL); + entry->refcnt--; + + if (entry->refcnt == 0) { + rb_delete(&_page_refcnts, &page, + (int (*)(const void *, const void *, + void *))_cmp_page_id_and_page_refcnt_entry, + NULL); + free_pages(page); + } + + CRITICAL_SECTION_LEAVE(daif_val); +} + +spage_id_t shared_page_clone_unshare(const page_id_t page) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + if (shared_page_getref(page) == 1) { // No need to clone. + CRITICAL_SECTION_LEAVE(daif_val); + return page; + } + + shared_page_decref(page); + + spage_id_t new_page_id = shared_page_alloc(); + if (new_page_id < 0) { + CRITICAL_SECTION_LEAVE(daif_val); + return new_page_id; + } + + memcpy(pa_to_kernel_va(page_id_to_pa(new_page_id)), + pa_to_kernel_va(page_id_to_pa(page)), 1 << PAGE_ORDER); + + CRITICAL_SECTION_LEAVE(daif_val); + return new_page_id; +} diff --git a/lab8/c/src/mem/startup-alloc.c b/lab8/c/src/mem/startup-alloc.c new file mode 100644 index 000000000..16ff447a6 --- /dev/null +++ b/lab8/c/src/mem/startup-alloc.c @@ -0,0 +1,24 @@ +#include "oscos/mem/startup-alloc.h" + +#include + +#include "oscos/utils/align.h" + +// Symbol defined in the linker script. +extern char _sheap[]; + +static char *_next = _sheap; + +void startup_alloc_init(void) { + // No-op. +} + +void *startup_alloc(const size_t size) { + char *const result = (char *)ALIGN((uintptr_t)_next, alignof(max_align_t)); + _next = result + size; + return result; +} + +va_range_t startup_alloc_get_alloc_range(void) { + return (va_range_t){.start = _sheap, .end = _next}; +} diff --git a/lab8/c/src/mem/vm.c b/lab8/c/src/mem/vm.c new file mode 100644 index 000000000..2f70a5a0a --- /dev/null +++ b/lab8/c/src/mem/vm.c @@ -0,0 +1,437 @@ +#include "oscos/mem/vm.h" + +#include + +#include "oscos/libc/string.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/mem/shared-page.h" +#include "oscos/sched.h" +#include "oscos/utils/critical-section.h" + +// Symbol defined in the linker script. +extern char _kernel_vm_base[]; + +pa_t kernel_va_to_pa(const void *const va) { + return (pa_t)((uintptr_t)va - (uintptr_t)_kernel_vm_base); +} + +void *pa_to_kernel_va(const pa_t pa) { + return (void *)((uintptr_t)pa + (uintptr_t)_kernel_vm_base); +} + +pa_range_t kernel_va_range_to_pa_range(const va_range_t range) { + return (pa_range_t){.start = kernel_va_to_pa(range.start), + .end = kernel_va_to_pa(range.end)}; +} + +page_table_entry_t *vm_new_pgd(void) { + const spage_id_t new_pgd_page_id = shared_page_alloc(); + if (new_pgd_page_id < 0) + return NULL; + + page_table_entry_t *const result = + pa_to_kernel_va(page_id_to_pa(new_pgd_page_id)); + + memset(result, 0, 1 << PAGE_ORDER); + + return result; +} + +void vm_clone_pgd(page_table_entry_t *const pgd) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const page_id_t pgd_page_id = pa_to_page_id(kernel_va_to_pa(pgd)); + shared_page_incref(pgd_page_id); + + for (size_t i = 0; i < 512; i++) { + if (pgd[i].b0) { + union { + table_descriptor_upper_t s; + unsigned u; + } upper = {.u = pgd[i].upper}; + upper.s.aptable = 0x2; + pgd[i].upper = upper.u; + } + } + + CRITICAL_SECTION_LEAVE(daif_val); +} + +static void _vm_drop_page_table(page_table_entry_t *const page_table, + const size_t level) { + const page_id_t page_table_page_id = + pa_to_page_id(kernel_va_to_pa(page_table)); + + if (shared_page_getref(page_table_page_id) == 1) { // About to be freed. + for (size_t i = 0; i < 512; i++) { + if (page_table[i].b0) { + const pa_t next_level_pa = page_table[i].addr << PAGE_ORDER; + if (level == 1) { + shared_page_decref(pa_to_page_id(next_level_pa)); + } else { + page_table_entry_t *const next_level_page_table = + pa_to_kernel_va(next_level_pa); + _vm_drop_page_table(next_level_page_table, level - 1); + } + } + } + } + + shared_page_decref(page_table_page_id); +} + +void vm_drop_pgd(page_table_entry_t *const pgd) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + _vm_drop_page_table(pgd, 3); + + CRITICAL_SECTION_LEAVE(daif_val); +} + +static page_table_entry_t * +_vm_clone_unshare_page_table(page_table_entry_t *const page_table) { + const page_id_t page_table_page_id = + pa_to_page_id(kernel_va_to_pa(page_table)); + + if (shared_page_getref(page_table_page_id) == 1) // No need to clone. + return page_table; + + shared_page_decref(page_table_page_id); + + const spage_id_t new_page_table_page_id = shared_page_alloc(); + if (new_page_table_page_id < 0) + return NULL; + page_table_entry_t *const new_page_table = + pa_to_kernel_va(page_id_to_pa(new_page_table_page_id)); + + for (size_t i = 0; i < 512; i++) { + if (page_table[i].b0) { + const pa_t next_level_pa = page_table[i].addr << PAGE_ORDER; + const page_id_t next_level_page_id = pa_to_page_id(next_level_pa); + + shared_page_incref(next_level_page_id); + + union { + table_descriptor_upper_t s; + unsigned u; + } upper = {.u = page_table[i].upper}; + upper.s.aptable = 0x2; + page_table[i].upper = upper.u; + } + } + + memcpy(new_page_table, page_table, 1 << PAGE_ORDER); + + return new_page_table; +} + +static page_table_entry_t * +_vm_clone_unshare_pte(page_table_entry_t *const pte) { + const page_id_t pte_page_id = pa_to_page_id(kernel_va_to_pa(pte)); + + if (shared_page_getref(pte_page_id) == 1) // No need to clone. + return pte; + + shared_page_decref(pte_page_id); + + const spage_id_t new_pte_page_id = shared_page_alloc(); + if (new_pte_page_id < 0) + return NULL; + page_table_entry_t *const new_pte = + pa_to_kernel_va(page_id_to_pa(new_pte_page_id)); + + for (size_t i = 0; i < 512; i++) { + if (pte[i].b0) { + const pa_t next_level_pa = pte[i].addr << PAGE_ORDER; + const page_id_t next_level_page_id = pa_to_page_id(next_level_pa); + + shared_page_incref(next_level_page_id); + + union { + block_page_descriptor_lower_t s; + unsigned u; + } lower = {.u = pte[i].lower}; + lower.s.ap = 0x3; + pte[i].lower = lower.u; + } + } + + memcpy(new_pte, pte, 1 << PAGE_ORDER); + + return new_pte; +} + +static void _init_backed_page(const mem_region_t *const mem_region, + void *const va, void *const kernel_va) { + void *const va_page_start = + (void *)((uintptr_t)va & ~((1 << PAGE_ORDER) - 1)); + size_t offset = (char *)va_page_start - (char *)mem_region->start; + + const size_t copy_len = + offset > mem_region->backing_storage_len ? 0 + : mem_region->backing_storage_len - offset > 1 << PAGE_ORDER + ? 1 << PAGE_ORDER + : mem_region->backing_storage_len - offset; + memcpy(kernel_va, (char *)mem_region->backing_storage_start + offset, + copy_len); + memset((char *)kernel_va + copy_len, 0, (1 << PAGE_ORDER) - copy_len); +} + +static bool _map_page(const mem_region_t *const mem_region, void *const va, + page_table_entry_t *const pte_entry) { + switch (mem_region->type) { + case MEM_REGION_BACKED: { + const spage_id_t page_id = shared_page_alloc(); + if (page_id < 0) { + return false; + } + + const pa_t page_pa = page_id_to_pa(page_id); + pte_entry->addr = page_pa >> PAGE_ORDER; + + _init_backed_page(mem_region, va, pa_to_kernel_va(page_pa)); + + break; + } + + case MEM_REGION_LINEAR: { + const size_t offset = (uintptr_t)va - (uintptr_t)mem_region->start; + pte_entry->addr = + ((uintptr_t)mem_region->backing_storage_start + offset) >> PAGE_ORDER; + break; + } + + default: + __builtin_unreachable(); + } + + pte_entry->b0 = 1; + pte_entry->b1 = 1; + const union { + block_page_descriptor_lower_t s; + unsigned u; + } lower = {.s = (block_page_descriptor_lower_t){ + .attr_indx = 0x1, .ap = 0x1, .af = 1}}; + pte_entry->lower = lower.u; + + return true; +} + +static page_table_entry_t * +_vm_clone_unshare_pte_entry(vm_addr_space_t *const addr_space, void *const va) { + page_table_entry_t *page_table = + _vm_clone_unshare_page_table(addr_space->pgd); + if (!page_table) + return NULL; + addr_space->pgd = page_table; + page_table_entry_t *prev_level_entry = &page_table[(uintptr_t)va >> 39]; + + for (size_t level = 2; level > 0; level--) { + if (prev_level_entry->b0) { + page_table = _vm_clone_unshare_page_table( + pa_to_kernel_va(prev_level_entry->addr << PAGE_ORDER)); + if (!page_table) + return NULL; + } else { + const spage_id_t page_table_page_id = shared_page_alloc(); + if (page_table_page_id < 0) + return NULL; + page_table = pa_to_kernel_va(page_id_to_pa(page_table_page_id)); + memset(page_table, 0, 1 << PAGE_ORDER); + prev_level_entry->b0 = 1; + prev_level_entry->b1 = 1; + } + + // Since the page table is not shared, we can remove the read-only bit now. + + union { + table_descriptor_upper_t s; + unsigned u; + } upper = {.u = prev_level_entry->upper}; + upper.s.aptable = 0x0; + prev_level_entry->upper = upper.u; + + const pa_t page_table_pa = kernel_va_to_pa(page_table); + prev_level_entry->addr = page_table_pa >> 12; + prev_level_entry = + &page_table[((uintptr_t)va >> (12 + level * 9)) & ((1 << 9) - 1)]; + } + + if (prev_level_entry->b0) { + page_table = _vm_clone_unshare_pte( + pa_to_kernel_va(prev_level_entry->addr << PAGE_ORDER)); + if (!page_table) + return NULL; + } else { + const spage_id_t page_table_page_id = shared_page_alloc(); + if (page_table_page_id < 0) + return NULL; + page_table = pa_to_kernel_va(page_id_to_pa(page_table_page_id)); + memset(page_table, 0, 1 << PAGE_ORDER); + prev_level_entry->b0 = 1; + prev_level_entry->b1 = 1; + } + + // Since the page table is not shared, we can remove the read-only bit now. + + { + union { + table_descriptor_upper_t s; + unsigned u; + } upper = {.u = prev_level_entry->upper}; + upper.s.aptable = 0x0; + prev_level_entry->upper = upper.u; + } + + const pa_t page_table_pa = kernel_va_to_pa(page_table); + prev_level_entry->addr = page_table_pa >> 12; + prev_level_entry = &page_table[((uintptr_t)va >> 12) & ((1 << 9) - 1)]; + + return prev_level_entry; +} + +vm_map_page_result_t vm_map_page(vm_addr_space_t *const addr_space, + void *const va) { + // Check the validity of the VA. + + if (!((addr_space->text_region.start <= va && + va < (void *)((char *)addr_space->text_region.start + + addr_space->text_region.len)) || + (addr_space->stack_region.start <= va && + va < (void *)((char *)addr_space->stack_region.start + + addr_space->stack_region.len)) || + (addr_space->vc_region.start <= va && + va < (void *)((char *)addr_space->vc_region.start + + addr_space->vc_region.len)))) + return VM_MAP_PAGE_SEGV; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + // Walk the page table. + + page_table_entry_t *const pte_entry = + _vm_clone_unshare_pte_entry(addr_space, va); + if (!pte_entry) { + CRITICAL_SECTION_LEAVE(daif_val); + return VM_MAP_PAGE_NOMEM; + } + + // Map the page. + + mem_region_t *const region = + addr_space->text_region.start <= va && + va < (void *)((char *)addr_space->text_region.start + + addr_space->text_region.len) + ? &addr_space->text_region + : addr_space->stack_region.start <= va && + va < (void *)((char *)addr_space->stack_region.start + + addr_space->stack_region.len) + ? &addr_space->stack_region + : addr_space->vc_region.start <= va && + va < (void *)((char *)addr_space->vc_region.start + + addr_space->vc_region.len) + ? &addr_space->vc_region + : (__builtin_unreachable(), NULL); + if (!_map_page(region, va, pte_entry)) { + CRITICAL_SECTION_LEAVE(daif_val); + return VM_MAP_PAGE_NOMEM; + } + + // Set ttbr0_el1 again, as the PGD may have changed. + vm_switch_to_addr_space(addr_space); + + CRITICAL_SECTION_LEAVE(daif_val); + + return VM_MAP_PAGE_SUCCESS; +} + +static bool _cow_page(const mem_region_t *const mem_region, + page_table_entry_t *const pte_entry) { + switch (mem_region->type) { + case MEM_REGION_BACKED: { + const page_id_t src_page_id = pa_to_page_id(pte_entry->addr << PAGE_ORDER); + const spage_id_t page_id = shared_page_clone_unshare(src_page_id); + if (page_id < 0) + return false; + + const pa_t page_pa = page_id_to_pa(page_id); + pte_entry->addr = page_pa >> PAGE_ORDER; + + break; + } + + case MEM_REGION_LINEAR: { + // No-op. + break; + } + + default: + __builtin_unreachable(); + } + + const union { + block_page_descriptor_lower_t s; + unsigned u; + } lower = {.s = (block_page_descriptor_lower_t){.ap = 0x1, .af = 1}}; + pte_entry->lower = lower.u; + + return true; +} + +vm_map_page_result_t vm_cow(vm_addr_space_t *const addr_space, void *const va) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + // Walk the page table. + + page_table_entry_t *const pte_entry = + _vm_clone_unshare_pte_entry(addr_space, va); + if (!pte_entry) { + CRITICAL_SECTION_LEAVE(daif_val); + return VM_MAP_PAGE_NOMEM; + } + + // Map the page. + + mem_region_t *const region = + addr_space->text_region.start <= va && + va < (void *)((char *)addr_space->text_region.start + + addr_space->text_region.len) + ? &addr_space->text_region + : addr_space->stack_region.start <= va && + va < (void *)((char *)addr_space->stack_region.start + + addr_space->stack_region.len) + ? &addr_space->stack_region + : addr_space->vc_region.start <= va && + va < (void *)((char *)addr_space->vc_region.start + + addr_space->vc_region.len) + ? &addr_space->vc_region + : (__builtin_unreachable(), NULL); + if (!_cow_page(region, pte_entry)) { + CRITICAL_SECTION_LEAVE(daif_val); + return VM_MAP_PAGE_NOMEM; + } + + // Set ttbr0_el1 again, as the PGD may have changed. + vm_switch_to_addr_space(addr_space); + + CRITICAL_SECTION_LEAVE(daif_val); + + return VM_MAP_PAGE_SUCCESS; +} + +void vm_switch_to_addr_space(const vm_addr_space_t *const addr_space) { + const pa_t pgd_pa = kernel_va_to_pa(addr_space->pgd); + __asm__ __volatile__( + "dsb ish // Ensure writes have completed.\n" + "msr ttbr0_el1, %0 // Switch page table.\n" + "tlbi vmalle1is // Invalidate all TLB entries.\n" + "dsb ish // Ensure completion of TLB invalidatation.\n" + "isb // Clear pipeline." + : + : "r"((uint64_t)pgd_pa) + : "memory"); +} diff --git a/lab8/c/src/mem/vm/kernel-page-tables.c b/lab8/c/src/mem/vm/kernel-page-tables.c new file mode 100644 index 000000000..5fee7a55b --- /dev/null +++ b/lab8/c/src/mem/vm/kernel-page-tables.c @@ -0,0 +1,114 @@ +#include "oscos/mem/vm/kernel-page-tables.h" + +#include +#include + +#include "oscos/drivers/board.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/mem/types.h" +#include "oscos/mem/vm.h" +#include "oscos/mem/vm/page-table.h" + +alignas(4096) page_table_t kernel_pud = {{.b0 = 1, + .b1 = 0, + .lower = 1 << 8, // AF = 1. + .addr = 0x0, + .upper = 0}, + {.b0 = 1, + .b1 = 0, + .lower = 1 << 8, // AF = 1. + .addr = 0x40000, + .upper = 0}}; + +alignas(4096) page_table_t kernel_pgd = {{ + .b0 = 1, + .b1 = 1, + .lower = 0, + .addr = 0, // The concrete value will be filled by `start.S`. + .upper = 0x1 << (61 - 48) // Unprivileged access not permitted + // (APTable = 0b01). +}}; + +static void _map_region_as_rec(const page_id_range_t range, + const block_page_descriptor_lower_t lower_attr, + const block_page_descriptor_upper_t upper_attr, + const size_t level, const page_id_t block_start, + page_table_entry_t *const entry) { + const union { + block_page_descriptor_lower_t s; + unsigned u; + } lower = {.s = lower_attr}; + const union { + block_page_descriptor_upper_t s; + unsigned u; + } upper = {.s = upper_attr}; + + const page_id_t block_end = block_start + (1 << (9 * level)); + + if (range.start == block_start && range.end == block_end) { + *entry = (page_table_entry_t){.b0 = 1, + .b1 = level == 0, + .lower = lower.u, + .addr = block_start, + .upper = upper.u}; + } else { + if (level == 0) + __builtin_unreachable(); + + const page_id_t subblock_stride = 1 << (9 * (level - 1)); + + page_table_entry_t *page_table; + if (entry->b1) { // Table. + page_table = (page_table_entry_t *)pa_to_kernel_va(entry->addr << 12); + } else { // Block. + const pa_t page_table_pa = page_id_to_pa(alloc_pages(0)); + page_table = pa_to_kernel_va(page_table_pa); + + for (size_t i = 0; i < 512; i++) { + page_table[i] = + (page_table_entry_t){.b0 = 1, + .b1 = level == 1, + .lower = entry->lower, + .addr = entry->addr + i * subblock_stride, + .upper = entry->upper}; + } + *entry = (page_table_entry_t){.b0 = 1, + .b1 = 1, + .lower = entry->lower, + .addr = page_table_pa >> 12, + .upper = entry->upper}; + } + + for (size_t i = 0; i < 512; i++) { + const page_id_t subblock_start = block_start + i * subblock_stride, + subblock_end = subblock_start + subblock_stride; + + const page_id_t max_start = range.start > subblock_start ? range.start + : subblock_start, + min_end = + range.end < subblock_end ? range.end : subblock_end; + + if (max_start < min_end) { + _map_region_as_rec( + (page_id_range_t){.start = max_start, .end = min_end}, lower_attr, + upper_attr, level - 1, subblock_start, &page_table[i]); + } + } + } +} + +static void _map_region_as(const page_id_range_t range, + const block_page_descriptor_lower_t lower_attr, + const block_page_descriptor_upper_t upper_attr) { + _map_region_as_rec(range, lower_attr, upper_attr, 3, 0, kernel_pgd); +} + +void vm_setup_finer_granularity_linear_mapping(void) { + // Map RAM as normal memory. + _map_region_as( + (page_id_range_t){.start = 0x0, + .end = kernel_va_to_pa(PERIPHERAL_BASE) >> 12}, + (block_page_descriptor_lower_t){ + .attr_indx = 1, .ap = 0x0, .sh = 0x0, .af = 1, .ng = 0}, + (block_page_descriptor_upper_t){.contiguous = 0, .pxn = 0, .uxn = 0}); +} diff --git a/lab8/c/src/panic.c b/lab8/c/src/panic.c new file mode 100644 index 000000000..d43c8f2aa --- /dev/null +++ b/lab8/c/src/panic.c @@ -0,0 +1,24 @@ +#include "oscos/panic.h" + +#include "oscos/console.h" + +void panic_begin(const char *const restrict file, const int line, + const char *const restrict format, ...) { + // Prints the panic message. + + va_list ap; + va_start(ap, format); + + console_printf("panic: %s:%d: ", file, line); + console_vprintf(format, ap); + console_putc('\n'); + + va_end(ap); + + console_flush_write_buffer(); + + // Park the core. + for (;;) { + __asm__ __volatile__("wfe"); + } +} diff --git a/lab8/c/src/sched/idle-thread.c b/lab8/c/src/sched/idle-thread.c new file mode 100644 index 000000000..b570c09f9 --- /dev/null +++ b/lab8/c/src/sched/idle-thread.c @@ -0,0 +1,8 @@ +#include "oscos/sched.h" + +void idle(void) { + for (;;) { + kill_zombies(); + schedule(); + } +} diff --git a/lab8/c/src/sched/periodic-sched.c b/lab8/c/src/sched/periodic-sched.c new file mode 100644 index 000000000..c81aeb140 --- /dev/null +++ b/lab8/c/src/sched/periodic-sched.c @@ -0,0 +1,33 @@ +#include "oscos/sched.h" + +#include + +#include "oscos/timer/timeout.h" +#include "oscos/xcpt.h" +#include "oscos/xcpt/task-queue.h" + +static void _periodic_sched(void *const _arg) { + (void)_arg; + + sched_setup_periodic_scheduling(); + + // Save spsr_el1 and elr_el1, since they can be clobbered by other threads. + + uint64_t spsr_val, elr_val; + __asm__ __volatile__("mrs %0, spsr_el1" : "=r"(spsr_val)); + __asm__ __volatile__("mrs %0, elr_el1" : "=r"(elr_val)); + + schedule(); + XCPT_MASK_ALL(); + + __asm__ __volatile__("msr spsr_el1, %0" : : "r"(spsr_val)); + __asm__ __volatile__("msr elr_el1, %0" : : "r"(elr_val)); +} + +void sched_setup_periodic_scheduling(void) { + uint64_t core_timer_freq_hz; + __asm__("mrs %0, cntfrq_el0" : "=r"(core_timer_freq_hz)); + core_timer_freq_hz &= 0xffffffff; + + timeout_add_timer_ticks(_periodic_sched, NULL, core_timer_freq_hz >> 5); +} diff --git a/lab8/c/src/sched/run-signal-handler.S b/lab8/c/src/sched/run-signal-handler.S new file mode 100644 index 000000000..d18f35efe --- /dev/null +++ b/lab8/c/src/sched/run-signal-handler.S @@ -0,0 +1,119 @@ +#include "oscos/utils/save-ctx.S" + +.section ".text" + +run_signal_handler: + // Save integer context. + + stp x19, x20, [sp, -(12 * 8)]! + stp x21, x22, [sp, 2 * 8] + stp x23, x24, [sp, 4 * 8] + stp x25, x26, [sp, 6 * 8] + stp x27, x28, [sp, 8 * 8] + stp x29, lr, [sp, 10 * 8] + + // Save FP/SIMD context. + + mrs x2, fpcr + mrs x3, fpsr + stp x2, x3, [sp, -16]! + + stp q0, q1, [sp, -(32 * 16)]! + stp q2, q3, [sp, 2 * 16] + stp q4, q5, [sp, 4 * 16] + stp q6, q7, [sp, 6 * 16] + stp q8, q9, [sp, 8 * 16] + stp q10, q11, [sp, 10 * 16] + stp q12, q13, [sp, 12 * 16] + stp q14, q15, [sp, 14 * 16] + stp q16, q17, [sp, 16 * 16] + stp q18, q19, [sp, 18 * 16] + stp q20, q21, [sp, 20 * 16] + stp q22, q23, [sp, 22 * 16] + stp q24, q25, [sp, 24 * 16] + stp q26, q27, [sp, 26 * 16] + stp q28, q29, [sp, 28 * 16] + stp q30, q31, [sp, 30 * 16] + + // - Unmask all interrupts. + // - AArch64 execution state. + // - EL0t. + msr spsr_el1, xzr + + adr x2, sig_handler_main + msr elr_el1, x2 + + // Zero the integer registers except for x0, which is used to pass an + // argument to sig_handler_main. + + mov x1, 0 + mov x2, 0 + mov x3, 0 + mov x4, 0 + mov x5, 0 + mov x6, 0 + mov x7, 0 + mov x8, 0 + mov x9, 0 + mov x10, 0 + mov x11, 0 + mov x12, 0 + mov x13, 0 + mov x14, 0 + mov x15, 0 + mov x16, 0 + mov x17, 0 + mov x18, 0 + mov x19, 0 + mov x20, 0 + mov x21, 0 + mov x22, 0 + mov x23, 0 + mov x24, 0 + mov x25, 0 + mov x26, 0 + mov x27, 0 + mov x28, 0 + mov x29, 0 + mov lr, 0 + + // Zero the FP/SIMD registers. + + movi d0, 0 + movi d1, 0 + movi d2, 0 + movi d3, 0 + movi d4, 0 + movi d5, 0 + movi d6, 0 + movi d7, 0 + movi d8, 0 + movi d9, 0 + movi d10, 0 + movi d11, 0 + movi d12, 0 + movi d13, 0 + movi d14, 0 + movi d15, 0 + movi d16, 0 + movi d17, 0 + movi d18, 0 + movi d19, 0 + movi d20, 0 + movi d21, 0 + movi d22, 0 + movi d23, 0 + movi d24, 0 + movi d25, 0 + movi d26, 0 + movi d27, 0 + movi d28, 0 + movi d29, 0 + movi d30, 0 + movi d31, 0 + + eret + +.type run_signal_handler, function +.size run_signal_handler, . - run_signal_handler +.global run_signal_handler diff --git a/lab8/c/src/sched/sched.c b/lab8/c/src/sched/sched.c new file mode 100644 index 000000000..e0147ff20 --- /dev/null +++ b/lab8/c/src/sched/sched.c @@ -0,0 +1,685 @@ +// This module implements a round-robin scheduler. + +#include "oscos/sched.h" + +#include "oscos/libc/string.h" +#include "oscos/mem/malloc.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/mem/vm.h" +#include "oscos/utils/align.h" +#include "oscos/utils/critical-section.h" +#include "oscos/utils/math.h" +#include "oscos/utils/rb.h" + +#define THREAD_STACK_ORDER 13 // 8KB. +#define THREAD_STACK_BLOCK_ORDER (THREAD_STACK_ORDER - PAGE_ORDER) + +#define USER_STACK_ORDER 23 // 8MB. +#define USER_STACK_BLOCK_ORDER (USER_STACK_ORDER - PAGE_ORDER) + +void _suspend_to_wait_queue(thread_list_node_t *wait_queue); +void _sched_run_thread(thread_t *thread); +void thread_main(void); +noreturn void user_program_main(const void *init_pc, const void *init_user_sp, + const void *init_kernel_sp); +noreturn void fork_child_ret(void); +void run_signal_handler(sighandler_t handler); + +static size_t _sched_next_tid = 1, _sched_next_pid = 1; +static thread_list_node_t _run_queue = {.prev = &_run_queue, + .next = &_run_queue}, + _zombies = {.prev = &_zombies, .next = &_zombies}, + _stopped_threads = {.prev = &_stopped_threads, + .next = &_stopped_threads}; +static rb_node_t *_processes = NULL; + +static int _cmp_processes_by_pid(const process_t *const *const p1, + const process_t *const *const p2, void *_arg) { + (void)_arg; + + const size_t pid1 = (*p1)->id, pid2 = (*p2)->id; + return pid1 < pid2 ? -1 : pid1 > pid2 ? 1 : 0; +} + +static int _cmp_pid_and_processes_by_pid(const size_t *const pid, + const process_t *const *const process, + void *_arg) { + (void)_arg; + + const size_t pid1 = *pid, pid2 = (*process)->id; + return pid1 < pid2 ? -1 : pid1 > pid2 ? 1 : 0; +} + +static size_t _alloc_tid(void) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const size_t result = _sched_next_tid++; + + CRITICAL_SECTION_LEAVE(daif_val); + return result; +} + +static size_t _alloc_pid(void) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const size_t result = _sched_next_pid++; + + CRITICAL_SECTION_LEAVE(daif_val); + return result; +} + +static void _add_thread_to_queue(thread_t *const thread, + thread_list_node_t *const queue) { + thread_list_node_t *const thread_node = &thread->list_node; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + thread_list_node_t *const last_node = queue->prev; + thread_node->prev = last_node; + last_node->next = thread_node; + thread_node->next = queue; + queue->prev = thread_node; + + CRITICAL_SECTION_LEAVE(daif_val); +} + +static void _add_thread_to_run_queue(thread_t *const thread) { + _add_thread_to_queue(thread, &_run_queue); +} + +void _remove_thread_from_queue(thread_t *const thread) { + thread_list_node_t *const thread_node = &thread->list_node; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + thread_node->prev->next = thread_node->next; + thread_node->next->prev = thread_node->prev; + + CRITICAL_SECTION_LEAVE(daif_val); +} + +thread_t *_remove_first_thread_from_queue(thread_list_node_t *const queue) { + thread_t *result; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + if (queue->next == queue) { + result = NULL; + } else { + thread_list_node_t *const first_node = queue->next; + queue->next = first_node->next; + first_node->next->prev = queue; + result = (thread_t *)((char *)first_node - offsetof(thread_t, list_node)); + } + + CRITICAL_SECTION_LEAVE(daif_val); + return result; +} + +bool sched_init(void) { + // Create the idle thread. + + thread_t *const idle_thread = malloc(sizeof(thread_t)); + if (!idle_thread) + return false; + + idle_thread->id = 0; + idle_thread->process = NULL; + idle_thread->ctx.fp_simd_ctx = NULL; + + // Name the current thread the idle thread. + + __asm__ __volatile__("msr tpidr_el1, %0" : : "r"(idle_thread)); + + return true; +} + +bool thread_create(void (*const task)(void *), void *const arg) { + // Allocate memory. + + thread_t *const thread = malloc(sizeof(thread_t)); + if (!thread) + return false; + + const spage_id_t stack_page_id = alloc_pages(THREAD_STACK_BLOCK_ORDER); + if (stack_page_id < 0) { + free(thread); + return false; + } + + // Initialize the thread structure. + + thread->id = _alloc_tid(); + thread->status.is_waiting = false; + thread->status.is_stopped = false; + thread->status.is_waken_up_by_signal = false; + thread->status.is_handling_signal = false; + thread->process = NULL; + thread->ctx.r19 = (uint64_t)(uintptr_t)task; + thread->ctx.r20 = (uint64_t)(uintptr_t)arg; + thread->ctx.pc = (uint64_t)(uintptr_t)thread_main; + thread->ctx.fp_simd_ctx = NULL; + + thread->stack_page_id = stack_page_id; + void *const init_sp = (char *)pa_to_kernel_va(page_id_to_pa(stack_page_id)) + + (1 << (THREAD_STACK_BLOCK_ORDER + PAGE_ORDER)); + thread->ctx.kernel_sp = (uint64_t)(uintptr_t)init_sp; + + // Put the thread into the end of the run queue. + + _add_thread_to_run_queue(thread); + + return true; +} + +thread_t * +_sched_move_thread_to_queue_and_pick_thread(thread_t *const thread, + thread_list_node_t *const queue) { + _add_thread_to_queue(thread, queue); + return _remove_first_thread_from_queue(&_run_queue); +} + +void thread_exit(void) { + thread_t *const curr_thread = current_thread(); + process_t *const curr_process = curr_thread->process; + + XCPT_MASK_ALL(); + + if (curr_process) { + rb_delete(&_processes, &curr_process->id, + (int (*)(const void *, const void *, + void *))_cmp_pid_and_processes_by_pid, + NULL); + } + + _sched_run_thread( + _sched_move_thread_to_queue_and_pick_thread(curr_thread, &_zombies)); + __builtin_unreachable(); +} + +thread_t *current_thread(void) { + thread_t *result; + __asm__ __volatile__("mrs %0, tpidr_el1" : "=r"(result)); + return result; +} + +bool process_create(void) { + // Allocate memory. + + process_t *const process = malloc(sizeof(process_t)); + if (!process) // Out of memory. + return false; + + thread_fp_simd_ctx_t *const fp_simd_ctx = + malloc(sizeof(thread_fp_simd_ctx_t)); + if (!fp_simd_ctx) { + free(process); + return false; + } + + page_table_entry_t *const pgd = vm_new_pgd(); + if (!pgd) { + free(fp_simd_ctx); + free(process); + return false; + } + + // Set thread/process data. + + thread_t *const curr_thread = current_thread(); + + process->id = _alloc_pid(); + process->addr_space.stack_region = + (mem_region_t){.start = (void *)0xffffffffb000ULL, + .len = 4 << PAGE_ORDER, + .type = MEM_REGION_BACKED, + .backing_storage_start = NULL, + .backing_storage_len = 0}; + process->addr_space.vc_region = + (mem_region_t){.start = (void *)0x3b400000ULL, + .len = 0x3f000000ULL - 0x3b400000ULL, + .type = MEM_REGION_LINEAR, + .backing_storage_start = (void *)0x3b400000ULL}; + process->addr_space.pgd = pgd; + process->main_thread = curr_thread; + process->pending_signals = 0; + process->blocked_signals = 0; + for (size_t i = 0; i < 32; i++) { + process->signal_handlers[i] = SIG_DFL; + } + process->cwd = rootfs.root; + for (size_t i = 0; i < N_FDS; i++) { + process->fds[i] = NULL; + } + curr_thread->process = process; + curr_thread->ctx.fp_simd_ctx = fp_simd_ctx; + + // Add the process to the process BST. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + rb_insert(&_processes, sizeof(process_t *), &process, + (int (*)(const void *, const void *, void *))_cmp_processes_by_pid, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + return true; +} + +void switch_vm(const thread_t *const thread) { + if (thread->process) { + vm_switch_to_addr_space(&thread->process->addr_space); + } +} + +static void _exec_generic(const void *const text_start, const size_t text_len, + const bool free_old_pages) { + thread_t *const curr_thread = current_thread(); + process_t *const curr_process = curr_thread->process; + + // Allocate memory. + + page_table_entry_t *pgd = NULL; + if (free_old_pages) { + pgd = vm_new_pgd(); + if (!pgd) + return; + } + + // Free old pages. + + if (free_old_pages) { + vm_drop_pgd(curr_process->addr_space.pgd); + } + + // Set process data. + + if (free_old_pages) { + curr_process->addr_space.pgd = pgd; + } + // We don't know exactly how large the .bss section of a user program is, so + // we have to use a heuristic. We speculate that the .bss section is at most + // as large as the remaining parts (.text, .rodata, and .data sections) of the + // user program. + curr_process->addr_space.text_region = + (mem_region_t){.start = (void *)0x0, + .len = ALIGN(text_len * 2, 4096), + .type = MEM_REGION_BACKED, + .backing_storage_start = text_start, + .backing_storage_len = text_len}; + + // Run the user program. + + void *const kernel_stack_end = + (char *)pa_to_kernel_va(page_id_to_pa(curr_thread->stack_page_id)) + + (1 << THREAD_STACK_ORDER); + switch_vm(curr_thread); + user_program_main((void *)0x0, (void *)0xfffffffff000ULL, kernel_stack_end); +} + +void exec_first(const void *const text_start, const size_t text_len) { + _exec_generic(text_start, text_len, false); +} + +void exec(const void *const text_start, const size_t text_len) { + _exec_generic(text_start, text_len, true); +} + +static void _thread_cleanup(thread_t *const thread) { + if (thread->process) { + vm_drop_pgd(thread->process->addr_space.pgd); + for (size_t i = 0; i < N_FDS; i++) { + if (thread->process->fds[i]) { + shared_file_drop(thread->process->fds[i]); + } + } + free(thread->process); + } + free_pages(thread->stack_page_id); + free(thread); +} + +process_t *fork(const extended_trap_frame_t *const trap_frame) { + thread_t *const curr_thread = current_thread(); + process_t *const curr_process = curr_thread->process; + + // Allocate memory. + + thread_t *const new_thread = malloc(sizeof(thread_t)); + if (!new_thread) + return NULL; + + process_t *const new_process = malloc(sizeof(process_t)); + if (!new_process) { + free(new_thread); + return NULL; + } + + const spage_id_t kernel_stack_page_id = alloc_pages(THREAD_STACK_BLOCK_ORDER); + if (kernel_stack_page_id < 0) { + free(new_process); + free(new_thread); + return NULL; + } + + thread_fp_simd_ctx_t *const fp_simd_ctx = + malloc(sizeof(thread_fp_simd_ctx_t)); + if (!fp_simd_ctx) { + free_pages(kernel_stack_page_id); + free(new_process); + free(new_thread); + return NULL; + } + + // Set data. + + new_thread->id = _alloc_tid(); + new_thread->status.is_waiting = false; + new_thread->status.is_stopped = false; + new_thread->status.is_waken_up_by_signal = false; + new_thread->status.is_handling_signal = false; + new_thread->stack_page_id = kernel_stack_page_id; + new_thread->process = new_process; + + new_process->id = _alloc_pid(); + vm_clone_pgd(curr_process->addr_space.pgd); + vm_switch_to_addr_space(&curr_process->addr_space); + new_process->addr_space = curr_process->addr_space; + new_process->main_thread = new_thread; + new_process->pending_signals = 0; + new_process->blocked_signals = 0; + memcpy(new_process->signal_handlers, curr_process->signal_handlers, + 32 * sizeof(sighandler_t)); + new_process->cwd = curr_process->cwd; + for (size_t i = 0; i < N_FDS; i++) { + new_process->fds[i] = + curr_process->fds[i] ? shared_file_clone(curr_process->fds[i]) : NULL; + } + + // Set execution context. + + void *const kernel_stack_end = + (char *)pa_to_kernel_va(page_id_to_pa(kernel_stack_page_id)) + + (1 << THREAD_STACK_ORDER), + *const init_kernel_sp = + (char *)kernel_stack_end - sizeof(extended_trap_frame_t); + + memcpy(&new_thread->ctx, &curr_thread->ctx, sizeof(thread_ctx_t)); + new_thread->ctx.pc = (uint64_t)(uintptr_t)fork_child_ret; + new_thread->ctx.kernel_sp = (uint64_t)(uintptr_t)init_kernel_sp; + new_thread->ctx.fp_simd_ctx = fp_simd_ctx; + memcpy(fp_simd_ctx, curr_thread->ctx.fp_simd_ctx, + sizeof(thread_fp_simd_ctx_t)); + + memcpy(init_kernel_sp, trap_frame, sizeof(extended_trap_frame_t)); + + // Add the new thread to the end of the run queue and the process to the + // process BST. Note that these two steps can be done in either order but must + // not be interrupted in between, since: + // - If the former is done before the latter and the newly-created thread is + // scheduled between the two steps, then the new thread won't be able to + // find its own process. + // - If the latter is done before the former and the newly-created process is + // killed between the two steps, then a freed thread_t instance (i.e., + // `new_thread`) will be added onto the run queue – a use-after-free bug. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + _add_thread_to_run_queue(new_thread); + + rb_insert(&_processes, sizeof(process_t *), &new_process, + (int (*)(const void *, const void *, void *))_cmp_processes_by_pid, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + return new_process; +} + +void kill_zombies(void) { + thread_t *zombie; + while ((zombie = _remove_first_thread_from_queue(&_zombies))) { + _thread_cleanup(zombie); + } +} + +void schedule(void) { _suspend_to_wait_queue(&_run_queue); } + +void suspend_to_wait_queue(thread_list_node_t *const wait_queue) { + XCPT_MASK_ALL(); + current_thread()->status.is_waiting = true; + _suspend_to_wait_queue(wait_queue); +} + +void add_all_threads_to_run_queue(thread_list_node_t *const wait_queue) { + thread_t *thread; + while ((thread = _remove_first_thread_from_queue(wait_queue))) { + _add_thread_to_run_queue(thread); + } +} + +process_t *get_process_by_id(const size_t pid) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + process_t *const *const result = + (process_t **)rb_search(_processes, &pid, + (int (*)(const void *, const void *, + void *))_cmp_pid_and_processes_by_pid, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + return result ? *result : NULL; +} + +void kill_process(process_t *const process) { + if (process == current_thread()->process) { + thread_exit(); + } else { + thread_t *const thread = process->main_thread; + + // Remove the process from the process BST and the thread from the run/wait + // queue. Note that these two steps can be done in either order but must not + // be interrupted in between, since: + // - If the former is done before the latter and the thread is scheduled + // between the two steps, then the new thread won't be able to find its + // own process. + // - If the latter is done before the former and the newly-created process + // is killed once again between the two steps, then a freed thread_t + // instance (i.e., `thread`) will be added onto the zombies list – a + // use-after-free bug. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + rb_delete(&_processes, &process->id, + (int (*)(const void *, const void *, + void *))_cmp_pid_and_processes_by_pid, + NULL); + + _remove_thread_from_queue(thread); + _add_thread_to_queue(thread, &_zombies); + + CRITICAL_SECTION_LEAVE(daif_val); + } +} + +void kill_all_processes(void) { + // Kill all processes other than the current one. + for (;;) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + process_t *process_to_kill = *(process_t **)_processes->payload; + if (process_to_kill == current_thread()->process) { + if (_processes->children[0]) { + process_to_kill = *(process_t **)_processes->children[0]->payload; + } else if (_processes->children[1]) { + process_to_kill = *(process_t **)_processes->children[1]->payload; + } else { // Only the current process left. + CRITICAL_SECTION_LEAVE(daif_val); + break; + } + } + + CRITICAL_SECTION_LEAVE(daif_val); + + kill_process(process_to_kill); + } + + // Kill the current process. + thread_exit(); +} + +sighandler_t set_signal_handler(process_t *const process, const int signal, + const sighandler_t handler) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const sighandler_t old_handler = process->signal_handlers[signal]; + + // The video player expects SIGKILL to be catchable. This function thus + // follows the protocol used by the video player. + if (!(/* signal == SIGKILL || */ signal == SIGSTOP)) { + process->signal_handlers[signal] = handler; + } + + CRITICAL_SECTION_LEAVE(daif_val); + return old_handler; +} + +void deliver_signal(process_t *const process, const int signal) { + thread_t *const thread = process->main_thread; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + process->pending_signals |= 1 << signal; + + if (thread->status.is_waiting && + (!thread->status.is_stopped || signal == SIGCONT)) { + // Wake up the thread and notify it that it was waken up by a signal. + + thread->status.is_waiting = false; + thread->status.is_stopped = false; + thread->status.is_waken_up_by_signal = true; + + _remove_thread_from_queue(thread); + _add_thread_to_run_queue(thread); + } + + CRITICAL_SECTION_LEAVE(daif_val); +} + +static void _deliver_signal_to_all_processes_rec(const int signal, + const rb_node_t *const node) { + if (!node) + return; + + process_t *const process = *(process_t **)node->payload; + deliver_signal(process, signal); + + _deliver_signal_to_all_processes_rec(signal, node->children[0]); + _deliver_signal_to_all_processes_rec(signal, node->children[1]); +} + +void deliver_signal_to_all_processes(const int signal) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + _deliver_signal_to_all_processes_rec(signal, _processes); + + CRITICAL_SECTION_LEAVE(daif_val); +} + +static bool _signal_default_is_term_or_core(const int signal) { + return signal == SIGABRT || signal == SIGALRM || signal == SIGBUS || + signal == SIGFPE || signal == SIGHUP || signal == SIGILL || + signal == SIGINT || signal == SIGIO || signal == SIGIOT || + signal == SIGKILL || signal == SIGPIPE || signal == SIGPROF || + signal == SIGPWR || signal == SIGQUIT || signal == SIGSEGV || + signal == SIGSTKFLT || signal == SIGSYS || signal == SIGTERM || + signal == SIGTRAP || signal == SIGUNUSED || signal == SIGUSR1 || + signal == SIGUSR2 || signal == SIGVTALRM || signal == SIGXCPU || + signal == SIGXFSZ; +} + +static bool _signal_default_is_stop(const int signal) { + return signal == SIGSTOP || signal == SIGTSTP || signal == SIGTTIN || + signal == SIGTTOU; +} + +void handle_signals(void) { + thread_t *const curr_thread = current_thread(); + if (!curr_thread) // The scheduler has not been initialized. + return; + process_t *const curr_process = curr_thread->process; + + // Save spsr_el1 and elr_el1, since they can be clobbered when running the + // signal handlers. + + uint64_t spsr_val, elr_val; + __asm__ __volatile__("mrs %0, spsr_el1" : "=r"(spsr_val)); + __asm__ __volatile__("mrs %0, elr_el1" : "=r"(elr_val)); + + // Save the status bit in order to support nested signal handling. + + const bool is_handling_signal = curr_thread->status.is_handling_signal; + + uint32_t deferred_signals = 0; + for (uint32_t handleable_signals; + (handleable_signals = curr_process->pending_signals & + ~curr_process->blocked_signals & + ~deferred_signals) != 0;) { + uint32_t reversed_handleable_signals; + __asm__("rbit %w0, %w1" + : "=r"(reversed_handleable_signals) + : "r"(handleable_signals)); + int signal; + __asm__("clz %w0, %w1" : "=r"(signal) : "r"(reversed_handleable_signals)); + + const sighandler_t handler = curr_process->signal_handlers[signal]; + if (handler == SIG_DFL) { + curr_process->pending_signals &= ~(1 << signal); + + if (_signal_default_is_term_or_core(signal)) { + thread_exit(); + } else if (_signal_default_is_stop(signal)) { + curr_thread->status.is_stopped = true; + suspend_to_wait_queue(&_stopped_threads); + curr_thread->status.is_waken_up_by_signal = false; + } + } else if (handler == SIG_IGN) { + curr_process->pending_signals &= ~(1 << signal); + } else { + // Run the signal handler. + + curr_thread->status.is_handling_signal = true; + curr_process->blocked_signals |= 1 << signal; + + run_signal_handler(handler); + + curr_thread->status.is_handling_signal = false; + curr_process->pending_signals &= ~(1 << signal); + curr_process->blocked_signals &= ~(1 << signal); + } + } + + // Restore spsr_el1 and elr_el1. + + __asm__ __volatile__("msr spsr_el1, %0" : : "r"(spsr_val)); + __asm__ __volatile__("msr elr_el1, %0" : : "r"(elr_val)); + + // Restore the status bit. + + curr_thread->status.is_handling_signal = is_handling_signal; +} diff --git a/lab8/c/src/sched/schedule.S b/lab8/c/src/sched/schedule.S new file mode 100644 index 000000000..b16ff658a --- /dev/null +++ b/lab8/c/src/sched/schedule.S @@ -0,0 +1,115 @@ +.section ".text" + +_suspend_to_wait_queue: + // Enter critical section. + + msr daifset, 0xf + + // Get current thread instance. + + mrs x2, tpidr_el1 + + // Save integer context. + + stp x19, x20, [x2, 16 + 0 * 16] + stp x21, x22, [x2, 16 + 1 * 16] + stp x23, x24, [x2, 16 + 2 * 16] + stp x25, x26, [x2, 16 + 3 * 16] + stp x27, x28, [x2, 16 + 4 * 16] + stp x29, x30, [x2, 16 + 5 * 16] + mov x1, sp + mrs x3, sp_el0 + stp x1, x3, [x2, 16 + 6 * 16] + + // Save FP/SIMD context, if there is one. + + ldr x1, [x2, 16 + 7 * 16] + cbz x1, .Lafter_save_fp_ctx + + stp q0, q1, [x1, 0 * 32] + stp q2, q3, [x1, 1 * 32] + stp q4, q5, [x1, 2 * 32] + stp q6, q7, [x1, 3 * 32] + stp q8, q9, [x1, 4 * 32] + stp q10, q11, [x1, 5 * 32] + stp q12, q13, [x1, 6 * 32] + stp q14, q15, [x1, 7 * 32] + stp q16, q17, [x1, 8 * 32] + stp q18, q19, [x1, 9 * 32] + stp q20, q21, [x1, 10 * 32] + stp q22, q23, [x1, 11 * 32] + stp q24, q25, [x1, 12 * 32] + stp q26, q27, [x1, 13 * 32] + stp q28, q29, [x1, 14 * 32] + stp q30, q31, [x1, 15 * 32] + add x1, x1, 16 * 32 + mrs x3, fpcr + mrs x4, fpsr + stp x3, x4, [x1] + +.Lafter_save_fp_ctx: + mov x1, x0 + mov x0, x2 + bl _sched_move_thread_to_queue_and_pick_thread + + // Fallthrough. + +.size _suspend_to_wait_queue, . - _suspend_to_wait_queue +.type _suspend_to_wait_queue, function +.global _suspend_to_wait_queue + +_sched_run_thread: + mov x19, x0 + bl switch_vm + mov x0, x19 + + // Restore integer context. + + ldp x19, x20, [x0, 16 + 0 * 16] + ldp x21, x22, [x0, 16 + 1 * 16] + ldp x23, x24, [x0, 16 + 2 * 16] + ldp x25, x26, [x0, 16 + 3 * 16] + ldp x27, x28, [x0, 16 + 4 * 16] + ldp x29, x30, [x0, 16 + 5 * 16] + ldp x1, x2, [x0, 16 + 6 * 16] + mov sp, x1 + msr sp_el0, x2 + + // Restore FP/SIMD context, if there is one. + + ldr x1, [x0, 16 + 7 * 16] + cbz x1, .Lafter_load_fp_ctx + + ldp q0, q1, [x1, 0 * 32] + ldp q2, q3, [x1, 1 * 32] + ldp q4, q5, [x1, 2 * 32] + ldp q6, q7, [x1, 3 * 32] + ldp q8, q9, [x1, 4 * 32] + ldp q10, q11, [x1, 5 * 32] + ldp q12, q13, [x1, 6 * 32] + ldp q14, q15, [x1, 7 * 32] + ldp q16, q17, [x1, 8 * 32] + ldp q18, q19, [x1, 9 * 32] + ldp q20, q21, [x1, 10 * 32] + ldp q22, q23, [x1, 11 * 32] + ldp q24, q25, [x1, 12 * 32] + ldp q26, q27, [x1, 13 * 32] + ldp q28, q29, [x1, 14 * 32] + ldp q30, q31, [x1, 15 * 32] + add x1, x1, 16 * 32 + ldp x2, x3, [x1] + msr fpcr, x2 + msr fpcr, x3 + +.Lafter_load_fp_ctx: + msr tpidr_el1, x0 + + // Unmask interrupts. + + msr daifclr, 0xf + + ret + +.size _sched_run_thread, . - _sched_run_thread +.type _sched_run_thread, function +.global _sched_run_thread diff --git a/lab8/c/src/sched/sig-handler-main.S b/lab8/c/src/sched/sig-handler-main.S new file mode 100644 index 000000000..9c3f530de --- /dev/null +++ b/lab8/c/src/sched/sig-handler-main.S @@ -0,0 +1,10 @@ +.section ".text" + +sig_handler_main: + blr x0 + mov x8, 10 // SYS_sigreturn + svc 0 + +.type sig_handler_main, function +.size sig_handler_main, . - sig_handler_main +.global sig_handler_main diff --git a/lab8/c/src/sched/thread-main.S b/lab8/c/src/sched/thread-main.S new file mode 100644 index 000000000..7548f66ee --- /dev/null +++ b/lab8/c/src/sched/thread-main.S @@ -0,0 +1,10 @@ +.section ".text" + +thread_main: + mov x0, x20 + blr x19 + b thread_exit + +.size thread_main, . - thread_main +.type thread_main, function +.global thread_main diff --git a/lab8/c/src/sched/user-program-main.S b/lab8/c/src/sched/user-program-main.S new file mode 100644 index 000000000..bfc44b8fb --- /dev/null +++ b/lab8/c/src/sched/user-program-main.S @@ -0,0 +1,93 @@ +.section ".text" + +user_program_main: + // We don't need to synchronize the data cache and the instruction cache + // since we haven't enabled any of them. Nonetheless, we still have to + // synchronize the fetched instruction stream using the `isb` instruction. + isb + + // Mask the interrupt to prevent spsr_el1 and elr_el1 from being clobbered + // by ISRs. + msr daifset, 0xf + + msr elr_el1, x0 + msr sp_el0, x1 + mov sp, x2 + + // - Unask all interrupts. + // - AArch64 execution state. + // - EL0t. + msr spsr_el1, xzr + + // Zero the integer registers. + mov x0, 0 + mov x1, 0 + mov x2, 0 + mov x3, 0 + mov x4, 0 + mov x5, 0 + mov x6, 0 + mov x7, 0 + mov x8, 0 + mov x9, 0 + mov x10, 0 + mov x11, 0 + mov x12, 0 + mov x13, 0 + mov x14, 0 + mov x15, 0 + mov x16, 0 + mov x17, 0 + mov x18, 0 + mov x19, 0 + mov x20, 0 + mov x21, 0 + mov x22, 0 + mov x23, 0 + mov x24, 0 + mov x25, 0 + mov x26, 0 + mov x27, 0 + mov x28, 0 + mov x29, 0 + mov lr, 0 + + // Zero the FP/SIMD registers. + movi d0, 0 + movi d1, 0 + movi d2, 0 + movi d3, 0 + movi d4, 0 + movi d5, 0 + movi d6, 0 + movi d7, 0 + movi d8, 0 + movi d9, 0 + movi d10, 0 + movi d11, 0 + movi d12, 0 + movi d13, 0 + movi d14, 0 + movi d15, 0 + movi d16, 0 + movi d17, 0 + movi d18, 0 + movi d19, 0 + movi d20, 0 + movi d21, 0 + movi d22, 0 + movi d23, 0 + movi d24, 0 + movi d25, 0 + movi d26, 0 + movi d27, 0 + movi d28, 0 + movi d29, 0 + movi d30, 0 + movi d31, 0 + + eret + +.type user_program_main, function +.size user_program_main, . - user_program_main +.global user_program_main diff --git a/lab8/c/src/shell.c b/lab8/c/src/shell.c new file mode 100644 index 000000000..7ac05bb53 --- /dev/null +++ b/lab8/c/src/shell.c @@ -0,0 +1,679 @@ +#include "oscos/shell.h" + +#include + +#include "oscos/console.h" +#include "oscos/drivers/mailbox.h" +#include "oscos/drivers/pm.h" +#include "oscos/fs/vfs.h" +#include "oscos/initrd.h" +#include "oscos/libc/ctype.h" +#include "oscos/libc/inttypes.h" +#include "oscos/libc/string.h" +#include "oscos/mem/malloc.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/sched.h" +#include "oscos/timer/timeout.h" +#include "oscos/utils/time.h" + +#define MAX_CMD_LEN 78 + +static void _shell_print_prompt(void) { console_fputs("# "); } + +static size_t _shell_read_line(char *const buf, const size_t n) { + size_t cmd_len = 0; + + for (;;) { + const char c = console_getc(); + + if (c == '\n') { + console_putc('\n'); + break; + } else if (c == '\x7f') { // Backspace. + if (cmd_len > 0) { + console_fputs("\b \b"); + cmd_len--; + } + } else { + console_putc(c); + if (n > 0 && cmd_len < n - 1) { + buf[cmd_len] = c; + } + cmd_len++; + } + } + + if (n > 0) { + buf[cmd_len > n - 1 ? n - 1 : cmd_len] = '\0'; + } + + return cmd_len; +} + +static void _shell_do_cmd_help(void) { + console_puts( + "help : print this help menu\n" + "hello : print Hello World!\n" + "hwinfo : get the hardware's information by mailbox\n" + "reboot : reboot the device\n" + "ls : list all files in the initial ramdisk\n" + "cat : print the content of a file in the initial ramdisk\n" + "exec : run a user program in the initial ramdisk\n" + "setTimeout : print a message after a timeout\n" + "alloc-pages : allocates a block of page frames using the page frame " + "allocator\n" + "free-pages : frees a block of page frames allocated using the page " + "frame allocator"); +} + +static void _shell_do_cmd_hello(void) { console_puts("Hello World!"); } + +static void _shell_do_cmd_hwinfo(void) { + const uint32_t board_revision = mailbox_get_board_revision(); + const arm_memory_t arm_memory = mailbox_get_arm_memory(); + + console_printf("Board revision: 0x%" PRIx32 "\nARM memory: Base: 0x%" PRIx32 + ", Size: 0x%" PRIx32 "\n", + board_revision, arm_memory.base, arm_memory.size); +} + +noreturn static void _shell_do_cmd_reboot(void) { pm_reboot(); } + +static void _shell_do_cmd_ls(void) { + if (!initrd_is_init()) { + console_puts("oscsh: ls: initrd is invalid"); + return; + } + + INITRD_FOR_ENTRY(entry) { console_puts(CPIO_NEWC_PATHNAME(entry)); } +} + +static void _shell_do_cmd_cat(void) { + if (!initrd_is_init()) { + console_puts("oscsh: cat: initrd is invalid"); + return; + } + + console_fputs("Filename: "); + + char filename_buf[MAX_CMD_LEN + 1]; + _shell_read_line(filename_buf, MAX_CMD_LEN + 1); + + const cpio_newc_entry_t *const entry = + initrd_find_entry_by_pathname(filename_buf); + if (!entry) { + console_puts("oscsh: cat: no such file or directory"); + return; + } + + const uint32_t mode = CPIO_NEWC_HEADER_VALUE(entry, mode); + const uint32_t file_type = mode & CPIO_NEWC_MODE_FILE_TYPE_MASK; + if (file_type == CPIO_NEWC_MODE_FILE_TYPE_REG) { + console_write(CPIO_NEWC_FILE_DATA(entry), CPIO_NEWC_FILESIZE(entry)); + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_DIR) { + console_puts("oscsh: cat: is a directory"); + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_LNK) { + console_fputs("Symbolic link to: "); + console_write(CPIO_NEWC_FILE_DATA(entry), CPIO_NEWC_FILESIZE(entry)); + console_putc('\n'); + } else { + console_puts("oscsh: cat: unknown file type"); + } +} + +static void _shell_do_cmd_exec(void) { + if (!initrd_is_init()) { + console_puts("oscsh: exec: initrd is invalid"); + return; + } + + console_fputs("Filename: "); + + char filename_buf[MAX_CMD_LEN + 1]; + _shell_read_line(filename_buf, MAX_CMD_LEN + 1); + + const cpio_newc_entry_t *const entry = + initrd_find_entry_by_pathname(filename_buf); + if (!entry) { + console_puts("oscsh: exec: no such file or directory"); + return; + } + + const void *user_program_start; + size_t user_program_len; + + const uint32_t mode = CPIO_NEWC_HEADER_VALUE(entry, mode); + const uint32_t file_type = mode & CPIO_NEWC_MODE_FILE_TYPE_MASK; + if (file_type == CPIO_NEWC_MODE_FILE_TYPE_REG) { + user_program_start = CPIO_NEWC_FILE_DATA(entry); + user_program_len = CPIO_NEWC_FILESIZE(entry); + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_DIR) { + console_puts("oscsh: exec: is a directory"); + return; + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_LNK) { + console_fputs("oscos: exec: is a symbolic link to: "); + console_write(CPIO_NEWC_FILE_DATA(entry), CPIO_NEWC_FILESIZE(entry)); + console_putc('\n'); + return; + } else { + console_puts("oscsh: exec: unknown file type"); + return; + } + + if (!process_create()) { + console_puts("oscsh: exec: out of memory"); + return; + } + + exec_first(user_program_start, user_program_len); + + // If execution reaches here, then exec failed. + console_puts("oscsh: exec: out of memory"); +} + +static void _shell_cmd_set_timeout_timer_callback(char *const message) { + console_puts(message); + free(message); +} + +static void _shell_do_cmd_set_timeout(const char *const args) { + const char *c = args; + + // Skip initial whitespaces. + for (; *c == ' '; c++) + ; + + // Message. + + if (!*c) + goto invalid; + + const char *const message_start = c; + size_t message_len = 0; + + for (; *c && *c != ' '; c++) { + message_len++; + } + + // Skip whitespaces. + for (; *c == ' '; c++) + ; + + // Seconds. + + if (!*c) + goto invalid; + + size_t seconds = 0; + for (; *c && *c != ' '; c++) { + if (!isdigit(*c)) + goto invalid; + seconds = seconds * 10 + (*c - '0'); + } + + // Skip whitespaces. + for (; *c == ' '; c++) + ; + + // There should be no more arguments. + if (*c) + goto invalid; + + // Copy the string. + + char *const message_copy = malloc(message_len + 1); + if (!message_copy) { + console_puts("oscsh: setTimeout: out of memory"); + return; + } + memcpy(message_copy, message_start, message_len); + message_copy[message_len] = '\0'; + + // Register the callback. + timeout_add_timer_ns((void (*)(void *))_shell_cmd_set_timeout_timer_callback, + message_copy, seconds * NS_PER_SEC); + return; + +invalid: + console_puts("oscsh: setTimeout: invalid command format"); +} + +static void _shell_do_cmd_alloc_pages(void) { + console_fputs("Order (decimal, leave blank for 0): "); + + char digit_buf[3]; + const size_t digit_len = _shell_read_line(digit_buf, 3); + if (digit_len > 2) + goto invalid; + + size_t order = 0; + for (const char *c = digit_buf; *c; c++) { + if (!isdigit(*c)) + goto invalid; + order = order * 10 + (*c - '0'); + } + + const spage_id_t page = alloc_pages(order); + if (page < 0) { + console_puts("oscsh: alloc-pages: out of memory"); + return; + } + + console_printf("Page ID: 0x%" PRIxPAGEID "\n", (page_id_t)page); + return; + +invalid: + console_puts("oscsh: alloc-pages: invalid order"); +} + +static void _shell_do_cmd_free_pages(void) { + console_fputs( + "Page number of the first page (lowercase hexadecimal without prefix): "); + + char digit_buf[9]; + const size_t digit_len = _shell_read_line(digit_buf, 9); + if (digit_len > 8) + goto invalid; + + page_id_t page_id = 0; + for (const char *c = digit_buf; *c; c++) { + page_id_t digit_value; + if (isdigit(*c)) { + digit_value = *c - '0'; + } else if ('a' <= *c && *c <= 'f') { + digit_value = *c - 'a' + 10; + } else { + goto invalid; + } + page_id = page_id << 4 | digit_value; + } + + free_pages(page_id); + return; + +invalid: + console_puts("oscsh: free-pages: invalid page number"); +} + +static void _shell_do_cmd_vfs_test_1(void) { + char buf[11] = {0}; + struct file *foo, *bar; + int result; + + result = vfs_open("/foo", O_CREAT, &foo); + console_printf("open(\"/foo\", O_CREAT) = %d\n", result); + if (result < 0) + return; + + result = vfs_write(foo, "aaaaa", 5); + console_printf("write(\"/foo\", \"aaaaa\") = %d\n", result); + if (result < 0) + return; + + result = vfs_close(foo); + console_printf("close(\"/foo\") = %d\n", result); + if (result < 0) + return; + + result = vfs_open("/bar", O_CREAT, &bar); + console_printf("open(\"/bar\", O_CREAT) = %d\n", result); + if (result < 0) + return; + + result = vfs_write(bar, "bbbbb", 5); + console_printf("write(\"/bar\", \"bbbbb\") = %d\n", result); + if (result < 0) + return; + + result = vfs_close(bar); + console_printf("close(\"/bar\") = %d\n", result); + if (result < 0) + return; + + result = vfs_open("/foo", O_CREAT, &foo); + console_printf("open(\"/foo\", O_CREAT) = %d\n", result); + if (result < 0) + return; + + result = vfs_open("/bar", O_CREAT, &bar); + console_printf("open(\"/bar\", O_CREAT) = %d\n", result); + if (result < 0) + return; + + result = vfs_read(foo, buf, 10); + console_printf("read(\"/foo\", ...) = %d\n", result); + if (result < 0) + return; + console_puts(buf); + + memset(buf, 0, 10); + result = vfs_read(bar, buf, 10); + console_printf("read(\"/bar\", ...) = %d\n", result); + if (result < 0) + return; + console_puts(buf); + + result = vfs_close(foo); + console_printf("close(\"/foo\") = %d\n", result); + if (result < 0) + return; + + result = vfs_close(bar); + console_printf("close(\"/bar\") = %d\n", result); + if (result < 0) + return; +} + +static void _shell_do_cmd_vfs_test_2(void) { + char buf[11] = {0}; + struct file *foo, *bar; + int result; + + result = vfs_mkdir("/dir"); + console_printf("mkdir(\"/dir\") = %d\n", result); + if (result < 0) + return; + + result = vfs_open("/dir/foo", O_CREAT, &foo); + console_printf("open(\"/dir/foo\", O_CREAT) = %d\n", result); + if (result < 0) + return; + + result = vfs_write(foo, "aaaaa", 5); + console_printf("write(\"/dir/foo\", \"aaaaa\") = %d\n", result); + if (result < 0) + return; + + result = vfs_close(foo); + console_printf("close(\"/dir/foo\") = %d\n", result); + if (result < 0) + return; + + result = vfs_open("/dir/bar", O_CREAT, &bar); + console_printf("open(\"/dir/bar\", O_CREAT) = %d\n", result); + if (result < 0) + return; + + result = vfs_write(bar, "bbbbb", 5); + console_printf("write(\"/dir/bar\", \"bbbbb\") = %d\n", result); + if (result < 0) + return; + + result = vfs_close(bar); + console_printf("close(\"/dir/bar\") = %d\n", result); + if (result < 0) + return; + + result = vfs_open("/dir/foo", O_CREAT, &foo); + console_printf("open(\"/dir/foo\", O_CREAT) = %d\n", result); + if (result < 0) + return; + + result = vfs_open("/dir/bar", O_CREAT, &bar); + console_printf("open(\"/dir/bar\", O_CREAT) = %d\n", result); + if (result < 0) + return; + + result = vfs_read(foo, buf, 10); + console_printf("read(\"/dir/foo\", ...) = %d\n", result); + if (result < 0) + return; + console_puts(buf); + + memset(buf, 0, 10); + result = vfs_read(bar, buf, 10); + console_printf("read(\"/dir/bar\", ...) = %d\n", result); + if (result < 0) + return; + console_puts(buf); + + result = vfs_close(foo); + console_printf("close(\"/dir/foo\") = %d\n", result); + if (result < 0) + return; + + result = vfs_close(bar); + console_printf("close(\"/dir/bar\") = %d\n", result); + if (result < 0) + return; +} + +static void _shell_do_cmd_vfs_test_3(void) { + char buf[11] = {0}; + struct file *foo, *bar; + int result; + + result = vfs_mkdir("/dir"); + console_printf("mkdir(\"/dir\") = %d\n", result); + if (result < 0) + return; + + result = vfs_mount("/dir", "tmpfs"); + console_printf("mount(\"/dir\", \"tmpfs\") = %d\n", result); + if (result < 0) + return; + + result = vfs_open("/dir/foo", O_CREAT, &foo); + console_printf("open(\"/dir/foo\", O_CREAT) = %d\n", result); + if (result < 0) + return; + + result = vfs_write(foo, "aaaaa", 5); + console_printf("write(\"/dir/foo\", \"aaaaa\") = %d\n", result); + if (result < 0) + return; + + result = vfs_close(foo); + console_printf("close(\"/dir/foo\") = %d\n", result); + if (result < 0) + return; + + result = vfs_open("/dir/bar", O_CREAT, &bar); + console_printf("open(\"/dir/bar\", O_CREAT) = %d\n", result); + if (result < 0) + return; + + result = vfs_write(bar, "bbbbb", 5); + console_printf("write(\"/dir/bar\", \"bbbbb\") = %d\n", result); + if (result < 0) + return; + + result = vfs_close(bar); + console_printf("close(\"/dir/bar\") = %d\n", result); + if (result < 0) + return; + + result = vfs_open("/dir/foo", O_CREAT, &foo); + console_printf("open(\"/dir/foo\", O_CREAT) = %d\n", result); + if (result < 0) + return; + + result = vfs_open("/dir/bar", O_CREAT, &bar); + console_printf("open(\"/dir/bar\", O_CREAT) = %d\n", result); + if (result < 0) + return; + + result = vfs_read(foo, buf, 10); + console_printf("read(\"/dir/foo\", ...) = %d\n", result); + if (result < 0) + return; + console_puts(buf); + + memset(buf, 0, 10); + result = vfs_read(bar, buf, 10); + console_printf("read(\"/dir/bar\", ...) = %d\n", result); + if (result < 0) + return; + console_puts(buf); + + result = vfs_close(foo); + console_printf("close(\"/dir/foo\") = %d\n", result); + if (result < 0) + return; + + result = vfs_close(bar); + console_printf("close(\"/dir/bar\") = %d\n", result); + if (result < 0) + return; +} + +static void _shell_do_cmd_vfs_test_4(void) { + char buf[11] = {0}; + struct file *foo, *bar; + int result; + + result = vfs_mkdir("/dir"); + console_printf("mkdir(\"/dir\") = %d\n", result); + if (result < 0) + return; + + result = vfs_mount("/dir", "tmpfs"); + console_printf("mount(\"/dir\", \"tmpfs\") = %d\n", result); + if (result < 0) + return; + + result = vfs_mkdir("/dir/dir"); + console_printf("mkdir(\"/dir/dir\") = %d\n", result); + if (result < 0) + return; + + result = vfs_open("/dir/foo", O_CREAT, &foo); + console_printf("open(\"/dir/foo\", O_CREAT) = %d\n", result); + if (result < 0) + return; + + result = vfs_write(foo, "aaaaa", 5); + console_printf("write(\"/dir/foo\", \"aaaaa\") = %d\n", result); + if (result < 0) + return; + + result = vfs_close(foo); + console_printf("close(\"/dir/foo\") = %d\n", result); + if (result < 0) + return; + + result = vfs_open("/dir/bar", O_CREAT, &bar); + console_printf("open(\"/dir/bar\", O_CREAT) = %d\n", result); + if (result < 0) + return; + + result = vfs_write(bar, "bbbbb", 5); + console_printf("write(\"/dir/bar\", \"bbbbb\") = %d\n", result); + if (result < 0) + return; + + result = vfs_close(bar); + console_printf("close(\"/dir/bar\") = %d\n", result); + if (result < 0) + return; + + result = vfs_open("/dir/./dir/../../dir/./foo", O_CREAT, &foo); + console_printf("open(\"/dir/./dir/../../dir/./foo\", O_CREAT) = %d\n", + result); + if (result < 0) + return; + + result = vfs_open("/dir/./dir/../../dir/./bar", O_CREAT, &bar); + console_printf("open(\"/dir/./dir/../../dir/./bar\", O_CREAT) = %d\n", + result); + if (result < 0) + return; + + result = vfs_read(foo, buf, 10); + console_printf("read(\"/dir/./dir/../../dir/./foo\", ...) = %d\n", result); + if (result < 0) + return; + console_puts(buf); + + memset(buf, 0, 10); + result = vfs_read(bar, buf, 10); + console_printf("read(\"/dir/./dir/../../dir/./bar\", ...) = %d\n", result); + if (result < 0) + return; + console_puts(buf); + + result = vfs_close(foo); + console_printf("close(\"/dir/./dir/../../dir/./foo\") = %d\n", result); + if (result < 0) + return; + + result = vfs_close(bar); + console_printf("close(\"/dir/./dir/../../dir/./bar\") = %d\n", result); + if (result < 0) + return; +} + +static void _shell_do_cmd_vfs_test_5(void) { + unsigned char buf[8] = {0}; + struct file *vfs1; + int result; + + result = vfs_open("/initramfs/vfs1.img", 0, &vfs1); + console_printf("open(\"/initramfs/vfs1.img\", 0) = %d\n", result); + if (result < 0) + return; + + result = vfs_read(vfs1, buf, 8); + console_printf("read(\"/initramfs/vfs1.img\", ...) = %d\n", result); + if (result < 0) + return; + for (size_t i = 0; i < 8; i++) { + console_printf("\\x%02hhx", buf[i]); + } + console_putc('\n'); + + result = vfs_close(vfs1); + console_printf("close(\"/initramfs/vfs1.img\") = %d\n", result); + if (result < 0) + return; +} + +static void _shell_cmd_not_found(const char *const cmd) { + console_printf("oscsh: %s: command not found\n", cmd); +} + +void run_shell(void) { + for (;;) { + _shell_print_prompt(); + + char cmd_buf[MAX_CMD_LEN + 1]; + const size_t cmd_len = _shell_read_line(cmd_buf, MAX_CMD_LEN + 1); + + if (cmd_len == 0) { + // No-op. + } else if (strcmp(cmd_buf, "help") == 0) { + _shell_do_cmd_help(); + } else if (strcmp(cmd_buf, "hello") == 0) { + _shell_do_cmd_hello(); + } else if (strcmp(cmd_buf, "hwinfo") == 0) { + _shell_do_cmd_hwinfo(); + } else if (strcmp(cmd_buf, "reboot") == 0) { + _shell_do_cmd_reboot(); + } else if (strcmp(cmd_buf, "ls") == 0) { + _shell_do_cmd_ls(); + } else if (strcmp(cmd_buf, "cat") == 0) { + _shell_do_cmd_cat(); + } else if (strcmp(cmd_buf, "exec") == 0) { + _shell_do_cmd_exec(); + } else if (strncmp(cmd_buf, "setTimeout", 10) == 0 && + (!cmd_buf[10] || cmd_buf[10] == ' ')) { + _shell_do_cmd_set_timeout(cmd_buf + 10); + } else if (strcmp(cmd_buf, "alloc-pages") == 0) { + _shell_do_cmd_alloc_pages(); + } else if (strcmp(cmd_buf, "free-pages") == 0) { + _shell_do_cmd_free_pages(); + } else if (strcmp(cmd_buf, "vfs-test-1") == 0) { + _shell_do_cmd_vfs_test_1(); + } else if (strcmp(cmd_buf, "vfs-test-2") == 0) { + _shell_do_cmd_vfs_test_2(); + } else if (strcmp(cmd_buf, "vfs-test-3") == 0) { + _shell_do_cmd_vfs_test_3(); + } else if (strcmp(cmd_buf, "vfs-test-4") == 0) { + _shell_do_cmd_vfs_test_4(); + } else if (strcmp(cmd_buf, "vfs-test-5") == 0) { + _shell_do_cmd_vfs_test_5(); + } else { + _shell_cmd_not_found(cmd_buf); + } + } +} diff --git a/lab8/c/src/start.S b/lab8/c/src/start.S new file mode 100644 index 000000000..7520ceff1 --- /dev/null +++ b/lab8/c/src/start.S @@ -0,0 +1,120 @@ +#define TCR_T0SZ_POSN 0 +#define TCR_T0SZ_REGION_48BIT ((64 - 48) << TCR_T0SZ_POSN) +#define TCR_TG0_POSN 14 +#define TCR_TG0_4KB (0b00 << TCR_TG0_POSN) +#define TCR_T1SZ_POSN 16 +#define TCR_T1SZ_REGION_48BIT ((64 - 48) << TCR_T1SZ_POSN) +#define TCR_TG1_POSN 30 +#define TCR_TG1_4KB (0b10 << TCR_TG1_POSN) + +#define MAIR_DEVICE_nGnRnE 0b00000000 +#define MAIR_NORMAL_NOCACHE 0b01000100 +#define MAIR_IX_DEVICE_nGnRnE 0 +#define MAIR_IX_NORMAL_NOCACHE 1 + +.section ".text._start" + +_start: + // Enable FP/SIMD. + mov x1, 0x300000 + msr cptr_el2, x1 + + // Switch to EL1. + + // - Use AArch64 in EL1. + // - Trap nothing to EL2. + // - Disable interrupt routing to EL2. + // - Disable stage 2 address translation. + mov x1, 0x80000000 + msr hcr_el2, x1 + + // - Mask all interrupts. + // - AArch64 execution state. + // - EL1h. (Uses SP_EL1). + mov x1, 0x3c5 + msr spsr_el2, x1 + + adr x1, .Lin_el1 + msr elr_el2, x1 + eret + +.Lin_el1: + // Setup virtual memory. + + ldr x1, \ + =(TCR_T0SZ_REGION_48BIT | TCR_TG0_4KB | TCR_T1SZ_REGION_48BIT \ + | TCR_TG1_4KB) + msr tcr_el1, x1 + + ldr x1, \ + =((MAIR_DEVICE_nGnRnE << (MAIR_IX_DEVICE_nGnRnE * 8)) \ + | (MAIR_NORMAL_NOCACHE << (MAIR_IX_NORMAL_NOCACHE * 8))) + msr mair_el1, x1 + + ldr x1, =kernel_pgd + ldr x2, =_kernel_vm_base + sub x1, x1, x2 + + ldr x3, [x1] + ldr x4, =kernel_pud + sub x4, x4, x2 + orr x3, x3, x4 + str x3, [x1] + + msr ttbr0_el1, x1 + msr ttbr1_el1, x1 + + mrs x1, sctlr_el1 + orr x1, x1, 1 + msr sctlr_el1, x1 + + ldr x1, =.Lstart_after_mmu + br x1 + +.Lstart_after_mmu: + // Make the devicetree address virtual. + add x0, x0, x2 + + // Enable FP/SIMD. + mov x1, 0x300000 + msr cpacr_el1, x1 + + // Allow user programs to access the timer. + mov x1, 0x1 + msr cntkctl_el1, x1 + + msr tpidr_el1, xzr + + // Clear the .bss section. + + ldr x1, =_sbss + ldr x2, =_ebss + b .Lclear_bss_loop_test + +.Lclear_bss_loop_body: + // This does not generate unaligned memory accesses, since the .bss section + // is 16-byte aligned. See `linker.ld`. + stp xzr, xzr, [x1], 16 + +.Lclear_bss_loop_test: + cmp x1, x2 + b.ne .Lclear_bss_loop_body + + // Set the stack pointer. + + ldr x1, =_estack + mov sp, x1 + + // Call the main function. + + bl main + + // Park the core. + +.Lpark_loop: + wfe + b .Lpark_loop + +.size _start, . - _start +.type _start, function +.global _start diff --git a/lab8/c/src/timer/delay.c b/lab8/c/src/timer/delay.c new file mode 100644 index 000000000..8f7a29c2e --- /dev/null +++ b/lab8/c/src/timer/delay.c @@ -0,0 +1,15 @@ +#include "oscos/timer/delay.h" + +#include + +#include "oscos/timer/timeout.h" +#include "oscos/utils/suspend.h" + +static void _timeout_callback(volatile bool *const flag) { *flag = true; } + +void delay_ns(const uint64_t ns) { + volatile bool flag = false; + timeout_add_timer_ns((void (*)(void *))_timeout_callback, (void *)&flag, ns); + + WFI_WHILE(!flag); +} diff --git a/lab8/c/src/timer/timeout.c b/lab8/c/src/timer/timeout.c new file mode 100644 index 000000000..a9c8b74a6 --- /dev/null +++ b/lab8/c/src/timer/timeout.c @@ -0,0 +1,114 @@ +#include "oscos/timer/timeout.h" + +#include + +#include "oscos/drivers/l1ic.h" +#include "oscos/utils/core-id.h" +#include "oscos/utils/critical-section.h" +#include "oscos/utils/heapq.h" +#include "oscos/utils/time.h" +#include "oscos/xcpt.h" + +#define MAX_N_TIMEOUT_ENTRIES 16 + +typedef struct { + uint64_t timestamp; + void (*callback)(void *); + void *arg; +} timeout_entry_t; + +static timeout_entry_t _timeout_entries[MAX_N_TIMEOUT_ENTRIES]; +static size_t _n_timeout_entries = 0; + +static int _timeout_entry_cmp_by_timestamp(const timeout_entry_t *const e1, + const timeout_entry_t *const e2, + void *const _arg) { + (void)_arg; + + if (e1->timestamp < e2->timestamp) + return -1; + if (e1->timestamp > e2->timestamp) + return 1; + return 0; +} + +void timeout_init(void) { + __asm__ __volatile__("msr cntp_ctl_el0, %0" : : "r"(0x3)); + l1ic_enable_core_timer_irq(get_core_id()); +} + +bool timeout_add_timer_ns(void (*const callback)(void *), void *const arg, + const uint64_t after_ns) { + uint64_t core_timer_freq_hz; + __asm__("mrs %0, cntfrq_el0" : "=r"(core_timer_freq_hz)); + core_timer_freq_hz &= 0xffffffff; + + // ceil(after_ns * core_timer_freq_hz / NS_PER_SEC). + const uint64_t after_ticks = + (after_ns * core_timer_freq_hz + (NS_PER_SEC - 1)) / NS_PER_SEC; + + return timeout_add_timer_ticks(callback, arg, after_ticks); +} + +bool timeout_add_timer_ticks(void (*const callback)(void *), void *const arg, + const uint64_t after_ticks) { + if (_n_timeout_entries == MAX_N_TIMEOUT_ENTRIES) + return false; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + uint64_t curr_timestamp; + __asm__ __volatile__("mrs %0, cntpct_el0" : "=r"(curr_timestamp)); + + const uint64_t timestamp = curr_timestamp + after_ticks; + + // Reprogram the timer. + + const bool need_timer_reprogramming = + _n_timeout_entries == 0 || timestamp < _timeout_entries[0].timestamp; + if (need_timer_reprogramming) { + __asm__ __volatile__("msr cntp_cval_el0, %0" : : "r"(timestamp)); + } + + // Insert the new timeout entry into the timeout queue. + + timeout_entry_t entry = { + .timestamp = timestamp, .callback = callback, .arg = arg}; + heappush(_timeout_entries, _n_timeout_entries++, sizeof(timeout_entry_t), + &entry, + (int (*)(const void *, const void *, + void *))_timeout_entry_cmp_by_timestamp, + NULL); + + // Enable the core timer interrupt. (ENABLE = 1, IMASK = 0) + __asm__ __volatile__("msr cntp_ctl_el0, %0" : : "r"(0x1)); + + CRITICAL_SECTION_LEAVE(daif_val); + + return true; +} + +void xcpt_core_timer_interrupt_handler(void) { + // Remove the top entry from the timeout queue. + + timeout_entry_t top_entry; + heappop(_timeout_entries, _n_timeout_entries--, sizeof(timeout_entry_t), + &top_entry, + (int (*)(const void *, const void *, + void *))_timeout_entry_cmp_by_timestamp, + NULL); + + // Reprogram the timer. + if (_n_timeout_entries == 0) { + // Disable the core timer interrupt. (ENABLE = 1, IMASK = 1) + __asm__ __volatile__("msr cntp_ctl_el0, %0" : : "r"(0x3)); + } else { + __asm__ __volatile__("msr cntp_cval_el0, %0" + : + : "r"(_timeout_entries[0].timestamp)); + } + + // Execute the callback. + top_entry.callback(top_entry.arg); +} diff --git a/lab8/c/src/utils/core-id.c b/lab8/c/src/utils/core-id.c new file mode 100644 index 000000000..a7a736439 --- /dev/null +++ b/lab8/c/src/utils/core-id.c @@ -0,0 +1,9 @@ +#include "oscos/utils/core-id.h" + +#include + +size_t get_core_id(void) { + uint64_t mpidr_val; + __asm__ __volatile__("mrs %0, mpidr_el1" : "=r"(mpidr_val)); + return mpidr_val & 0x3; +} diff --git a/lab8/c/src/utils/fmt.c b/lab8/c/src/utils/fmt.c new file mode 100644 index 000000000..9721b2884 --- /dev/null +++ b/lab8/c/src/utils/fmt.c @@ -0,0 +1,653 @@ +#include "oscos/utils/fmt.h" + +#include +#include +#include + +#include "oscos/libc/ctype.h" +#include "oscos/libc/string.h" + +#define OCT_MAX_N_DIGITS 22 +#define DEC_MAX_N_DIGITS 20 +#define HEX_MAX_N_DIGITS 16 + +typedef enum { + LM_NONE, + LM_HH, + LM_H, + LM_L, + LM_LL, + LM_J, + LM_Z, + LM_T, + LM_UPPER_L +} length_modifier_t; + +static const char LOWER_HEX_DIGITS[16] = "0123456789abcdef"; +static const char UPPER_HEX_DIGITS[16] = "0123456789ABCDEF"; + +static size_t +_render_unsigned_dec(char digits[const static DEC_MAX_N_DIGITS + 1], + const uintmax_t x) { + digits[DEC_MAX_N_DIGITS] = '\0'; + + size_t n_digits = 0; + for (uintmax_t xc = x; xc > 0; xc /= 10) { + digits[DEC_MAX_N_DIGITS - ++n_digits] = '0' + xc % 10; + } + + return n_digits; +} + +static size_t +_render_unsigned_base_p2(char *const restrict digits, const size_t digits_len, + const uintmax_t x, const size_t log2_base, + const char *const restrict digit_template) { + const uintmax_t mask = (1 << log2_base) - 1; + + digits[digits_len] = '\0'; + + size_t n_digits = 0; + for (uintmax_t xc = x; xc > 0; xc >>= log2_base) { + digits[digits_len - ++n_digits] = digit_template[xc & mask]; + } + + return n_digits; +} + +static size_t _puts_generic(void (*const putc)(unsigned char, void *), + void *const putc_arg, const char *const s) { + size_t n_chars_printed = 0; + for (const char *c = s; *c; c++) { + putc(*c, putc_arg); + n_chars_printed++; + } + return n_chars_printed; +} + +static size_t _puts_limited_generic(void (*const putc)(unsigned char, void *), + void *const putc_arg, const char *const s, + const size_t limit) { + size_t n_chars_printed = 0, i = 0; + for (const char *c = s; i < limit && *c; i++, c++) { + putc(*c, putc_arg); + n_chars_printed++; + } + return n_chars_printed; +} + +static size_t _pad_generic(void (*const putc)(unsigned char, void *), + void *const putc_arg, const unsigned char pad, + const size_t width) { + for (size_t i = 0; i < width; i++) { + putc(pad, putc_arg); + } + return width; +} + +static size_t _vprintf_generic_putc(void (*const putc)(unsigned char, void *), + void *const putc_arg, const unsigned char c, + const bool flag_minus, + const size_t min_field_width) { + size_t n_chars_printed = 0; + + if (flag_minus) { + putc(c, putc_arg); + n_chars_printed++; + + if (min_field_width > 1) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', min_field_width - 1); + } + } else { + if (min_field_width > 1) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', min_field_width - 1); + } + + putc(c, putc_arg); + n_chars_printed++; + } + + return n_chars_printed; +} + +static size_t _vprintf_generic_puts(void (*const putc)(unsigned char, void *), + void *const putc_arg, const char *const s, + const bool flag_minus, + const size_t min_field_width, + const bool precision_valid, + const size_t precision) { + const size_t len = strlen(s); + + // Calculate width. + + const size_t width = precision_valid && precision < len ? precision : len; + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + // The string. + if (precision_valid) { + n_chars_printed += _puts_limited_generic(putc, putc_arg, s, precision); + } else { + n_chars_printed += _puts_generic(putc, putc_arg, s); + } + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return n_chars_printed; +} + +static size_t _vprintf_generic_print_signed_dec( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const intmax_t x, const bool flag_minus, const bool flag_plus, + const bool flag_space, const bool flag_zero, const size_t min_field_width, + const size_t precision) { + // Render the digits. + + char digits[DEC_MAX_N_DIGITS + 1]; + const size_t n_digits = _render_unsigned_dec(digits, x < 0 ? -x : x); + + // Calculate width. + + size_t width = n_digits; + if (precision > n_digits) { + width = precision; + } + width += x < 0 || flag_plus || flag_space; // Sign character. + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // Sign. + if (x < 0) { + putc('-', putc_arg); + n_chars_printed++; + } else if (flag_plus) { + putc('+', putc_arg); + n_chars_printed++; + } else if (flag_space) { + putc(' ', putc_arg); + n_chars_printed++; + } + // Leading zeros. + if (precision > n_digits) { + n_chars_printed += _pad_generic(putc, putc_arg, '0', precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (DEC_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +static size_t _vprintf_generic_print_unsigned_oct( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const uintmax_t x, const bool flag_minus, const bool flag_hash, + const bool flag_zero, const size_t min_field_width, + const size_t precision) { + // Render the digits. + + char digits[OCT_MAX_N_DIGITS + 1]; + const size_t n_digits = _render_unsigned_base_p2(digits, OCT_MAX_N_DIGITS, x, + 3, LOWER_HEX_DIGITS); + + // Calculate width. + + const size_t effective_precision = + flag_hash && n_digits + 1 > precision ? n_digits + 1 : precision; + + size_t width = n_digits; + if (effective_precision > n_digits) { + width = effective_precision; + } + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // Leading zeros. + if (effective_precision > n_digits) { + n_chars_printed += + _pad_generic(putc, putc_arg, '0', effective_precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (OCT_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +static size_t _vprintf_generic_print_unsigned_hex( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const uintmax_t x, const bool flag_minus, const bool flag_hash, + const bool flag_zero, const size_t min_field_width, const size_t precision, + const bool is_upper) { + const char *const digit_template = + is_upper ? UPPER_HEX_DIGITS : LOWER_HEX_DIGITS; + const char *const prefix = is_upper ? "0X" : "0x"; + + // Render the digits. + + char digits[HEX_MAX_N_DIGITS + 1]; + const size_t n_digits = + _render_unsigned_base_p2(digits, HEX_MAX_N_DIGITS, x, 4, digit_template); + + // Calculate width. + + size_t width = n_digits; + if (precision > n_digits) { + width = precision; + } + // 0x prefix. + if (flag_hash && x != 0) { + width += 2; + } + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // 0x prefix. + if (flag_hash && x != 0) { + n_chars_printed += _puts_generic(putc, putc_arg, prefix); + } + // Leading zeros. + if (precision > n_digits) { + n_chars_printed += _pad_generic(putc, putc_arg, '0', precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (HEX_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +static size_t _vprintf_generic_print_unsigned_dec( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const uintmax_t x, const bool flag_minus, const bool flag_zero, + const size_t min_field_width, const size_t precision) { + // Render the digits. + + char digits[DEC_MAX_N_DIGITS + 1]; + const size_t n_digits = _render_unsigned_dec(digits, x); + + // Calculate width. + + size_t width = n_digits; + if (precision > n_digits) { + width = precision; + } + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // Leading zeros. + if (precision > n_digits) { + n_chars_printed += _pad_generic(putc, putc_arg, '0', precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (DEC_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +#define READ_ARG_S(LM, AP) \ + ((LM) == LM_NONE ? va_arg(AP, int) \ + : (LM) == LM_HH ? (signed char)va_arg(AP, int) \ + : (LM) == LM_H ? (short)va_arg(AP, int) \ + : (LM) == LM_L ? va_arg(AP, long) \ + : (LM) == LM_LL ? va_arg(AP, long long) \ + : (LM) == LM_J ? va_arg(AP, intmax_t) \ + : (LM) == LM_Z ? va_arg(AP, /* signed size_t = */ long) \ + : (LM) == LM_T ? va_arg(AP, ptrdiff_t) \ + : (__builtin_unreachable(), 0)) + +#define READ_ARG_U(LM, AP) \ + ((LM) == LM_NONE ? va_arg(AP, unsigned) \ + : (LM) == LM_HH ? (unsigned char)va_arg(AP, int) \ + : (LM) == LM_H ? (unsigned short)va_arg(AP, int) \ + : (LM) == LM_L ? va_arg(AP, unsigned long) \ + : (LM) == LM_LL ? va_arg(AP, unsigned long long) \ + : (LM) == LM_J ? va_arg(AP, uintmax_t) \ + : (LM) == LM_Z ? va_arg(AP, size_t) \ + : (LM) == LM_T ? va_arg(AP, /* unsigned ptrdiff_t = */ unsigned long) \ + : (__builtin_unreachable(), 0)) + +int vprintf_generic(const printf_vtable_t *const vtable, void *const vtable_arg, + const char *const restrict format, va_list ap) { + size_t n_chars_printed = 0; + for (const char *restrict c = format; *c;) { + if (*c == '%') { + c++; + + // Flags. + + bool flag_minus = false, flag_plus = false, flag_space = false, + flag_hash = false, flag_zero = false; + + for (;;) { + if (*c == '-') { + c++; + flag_minus = true; + } else if (*c == '+') { + c++; + flag_plus = true; + } else if (*c == ' ') { + c++; + flag_space = true; + } else if (*c == '#') { + c++; + flag_hash = true; + } else if (*c == '0') { + c++; + flag_zero = true; + } else { + break; + } + } + + // Minimum field width. + + bool min_field_width_specified = false; + size_t min_field_width = 0; + if (*c == '*') { + c++; + min_field_width_specified = true; + const int arg = va_arg(ap, int); + if (arg < 0) { + flag_minus = true; + min_field_width = -arg; + } else { + min_field_width = arg; + } + } else if (isdigit(*c)) { + min_field_width_specified = true; + for (; isdigit(*c); c++) { + min_field_width = min_field_width * 10 + (*c - '0'); + } + } + + // Precision. + + bool precision_specified = false, precision_valid = false; + size_t precision = 0; + if (*c == '.') { + c++; + precision_specified = true; + + if (*c == '*') { + c++; + const int arg = va_arg(ap, int); + if (arg >= 0) { + precision_valid = true; + precision = arg; + } + } else { + precision_valid = true; + for (; isdigit(*c); c++) { + precision = precision * 10 + (*c - '0'); + } + } + } + + // Length modifier. + + length_modifier_t length_modifier = LM_NONE; + + switch (*c) { + case 'h': + c++; + if (*c == 'h') { + c++; + length_modifier = LM_HH; + } else { + length_modifier = LM_H; + } + break; + + case 'l': + c++; + if (*c == 'l') { + c++; + length_modifier = LM_LL; + } else { + length_modifier = LM_L; + } + break; + + case 'j': + c++; + length_modifier = LM_J; + break; + + case 'z': + c++; + length_modifier = LM_Z; + break; + + case 't': + c++; + length_modifier = LM_T; + break; + + case 'L': + c++; + length_modifier = LM_UPPER_L; + break; + } + + // Format specifier. + + switch (*c) { + case '%': + c++; + if (flag_minus || flag_plus || flag_space || flag_hash || flag_zero || + min_field_width_specified || precision_specified || + length_modifier != LM_NONE) + __builtin_unreachable(); + vtable->putc('%', vtable_arg); + n_chars_printed++; + break; + + case 'c': + c++; + switch (length_modifier) { + case LM_NONE: + if (flag_hash || flag_zero) + __builtin_unreachable(); + n_chars_printed += + _vprintf_generic_putc(vtable->putc, vtable_arg, va_arg(ap, int), + flag_minus, min_field_width); + break; + + default: + __builtin_unreachable(); + } + break; + + case 's': + c++; + switch (length_modifier) { + case LM_NONE: + if (flag_hash || flag_zero) + __builtin_unreachable(); + n_chars_printed += _vprintf_generic_puts( + vtable->putc, vtable_arg, va_arg(ap, const char *), flag_minus, + min_field_width, precision_valid, precision); + break; + + default: + __builtin_unreachable(); + } + break; + + case 'd': + case 'i': + c++; + if (flag_hash) + __builtin_unreachable(); + n_chars_printed += _vprintf_generic_print_signed_dec( + vtable->putc, vtable_arg, READ_ARG_S(length_modifier, ap), + flag_minus, flag_plus, flag_space, flag_zero, min_field_width, + precision_valid ? precision : 1); + break; + + case 'o': + c++; + n_chars_printed += _vprintf_generic_print_unsigned_oct( + vtable->putc, vtable_arg, READ_ARG_U(length_modifier, ap), + flag_minus, flag_hash, flag_zero, min_field_width, + precision_specified ? precision : 1); + break; + + case 'x': + case 'X': { + const bool is_upper = *c == 'X'; + c++; + n_chars_printed += _vprintf_generic_print_unsigned_hex( + vtable->putc, vtable_arg, READ_ARG_U(length_modifier, ap), + flag_minus, flag_hash, flag_zero, min_field_width, + precision_specified ? precision : 1, is_upper); + break; + } + + case 'u': + c++; + if (flag_hash) + __builtin_unreachable(); + n_chars_printed += _vprintf_generic_print_unsigned_dec( + vtable->putc, vtable_arg, READ_ARG_S(length_modifier, ap), + flag_minus, flag_zero, min_field_width, + precision_valid ? precision : 1); + break; + + case 'n': + c++; + if (flag_minus || flag_plus || flag_space || flag_hash || flag_zero || + min_field_width_specified || precision_specified) + __builtin_unreachable(); + switch (length_modifier) { + case LM_NONE: + *va_arg(ap, int *) = n_chars_printed; + break; + + case LM_HH: + *va_arg(ap, signed char *) = n_chars_printed; + break; + + case LM_H: + *va_arg(ap, short *) = n_chars_printed; + break; + + case LM_L: + *va_arg(ap, long *) = n_chars_printed; + break; + + case LM_LL: + *va_arg(ap, long long *) = n_chars_printed; + break; + + case LM_J: + *va_arg(ap, intmax_t *) = n_chars_printed; + break; + + case LM_Z: + *va_arg(ap, /* signed size_t = */ long *) = n_chars_printed; + break; + + case LM_T: + *va_arg(ap, ptrdiff_t *) = n_chars_printed; + break; + + default: + __builtin_unreachable(); + } + break; + + case 'p': + c++; + switch (length_modifier) { + case LM_NONE: + n_chars_printed += _vprintf_generic_print_unsigned_hex( + vtable->putc, vtable_arg, (uintmax_t)va_arg(ap, void *), + flag_minus, flag_hash, flag_zero, min_field_width, + precision_specified ? precision : 1, false); + break; + + default: + __builtin_unreachable(); + } + break; + + default: + __builtin_unreachable(); + } + } else { + vtable->putc(*c++, vtable_arg); + n_chars_printed++; + } + } + + vtable->finalize(vtable_arg); + return n_chars_printed; +} diff --git a/lab8/c/src/utils/heapq.c b/lab8/c/src/utils/heapq.c new file mode 100644 index 000000000..274f71e07 --- /dev/null +++ b/lab8/c/src/utils/heapq.c @@ -0,0 +1,77 @@ +#include "oscos/utils/heapq.h" + +#include "oscos/libc/string.h" + +// TODO: Check integer overflows. + +static void _heap_sift_up(void *const base, const size_t size, const size_t i, + int (*const compar)(const void *, const void *, + void *), + void *const arg) { + size_t ic = i; + while (ic != 0) { + const size_t ip = (ic - 1) / 2; + void *const pc = (char *)base + ic * size, *const pp = + (char *)base + ip * size; + if (compar(pc, pp, arg) < 0) { + memswp(pc, pp, size); + ic = ip; + } else { + break; + } + } +} + +static void _heap_sift_down( + void *const base, const size_t nmemb, const size_t size, const size_t i, + int (*const compar)(const void *, const void *, void *), void *const arg) { + size_t ic = i, il; + while ((il = ic * 2 + 1) < nmemb) { + const size_t ir = il + 1; + void *const pc = (char *)base + ic * size, + *const pl = (char *)base + il * size, + *const pr = (char *)base + ir * size; + + size_t it; + void *pt; + if (ir < nmemb && compar(pl, pr, arg) >= 0) { + it = ir; + pt = pr; + } else { + it = il; + pt = pl; + } + + if (compar(pt, pc, arg) < 0) { + memswp(pt, pc, size); + ic = it; + } else { + break; + } + } +} + +void heappush(void *const restrict base, const size_t nmemb, const size_t size, + const void *const restrict item, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + memcpy((char *)base + nmemb * size, item, size); + _heap_sift_up(base, size, nmemb, compar, arg); +} + +void heappop(void *const restrict base, const size_t nmemb, const size_t size, + void *const restrict item, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + if (nmemb == 0) + __builtin_unreachable(); + + if (item) { + memcpy(item, base, size); + } + + if (nmemb > 1) { + memcpy(base, (const char *)base + (nmemb - 1) * size, size); + _heap_sift_down(base, nmemb - 1, size, 0, compar, arg); + } +} diff --git a/lab8/c/src/utils/rb.c b/lab8/c/src/utils/rb.c new file mode 100644 index 000000000..391fbbb6e --- /dev/null +++ b/lab8/c/src/utils/rb.c @@ -0,0 +1,83 @@ +#include "oscos/utils/rb.h" + +#include "oscos/libc/string.h" +#include "oscos/mem/malloc.h" + +// TODO: Properly implement a red-black tree. + +const void *rb_search(const rb_node_t *const root, + const void *const restrict key, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + const rb_node_t *curr = root; + while (curr) { + const int compar_result = compar(key, curr->payload, arg); + if (compar_result == 0) + break; + curr = curr->children[compar_result > 0]; + } + + return curr ? curr->payload : NULL; +} + +bool rb_insert(rb_node_t **const root, const size_t size, + const void *const restrict item, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + rb_node_t **curr = root; + while (*curr) { + const int compar_result = compar(item, (*curr)->payload, arg); + if (compar_result == 0) + break; + curr = &(*curr)->children[compar_result > 0]; + } + + if (!*curr) { + *curr = malloc(sizeof(rb_node_t) + size); + if (!*curr) + return false; + + (*curr)->children[0] = (*curr)->children[1] = NULL; + } + + memcpy((*curr)->payload, item, size); + + return true; +} + +static rb_node_t **_rb_minimum(rb_node_t **const node) { + rb_node_t **curr = node; + while ((*curr)->children[0]) { + curr = &(*curr)->children[0]; + } + return curr; +} + +void rb_delete(rb_node_t **const root, const void *const restrict key, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + rb_node_t **curr = root; + while (*curr) { + const int compar_result = compar(key, (*curr)->payload, arg); + if (compar_result == 0) + break; + curr = &(*curr)->children[compar_result > 0]; + } + + rb_node_t *const curr_node = *curr; + if (!curr_node->children[0]) { + *curr = curr_node->children[1]; + } else if (!curr_node->children[1]) { + *curr = curr_node->children[0]; + } else { + rb_node_t **const min_right = _rb_minimum(&curr_node->children[1]), + *const min_right_node = *min_right; + + *min_right = min_right_node->children[1]; + *curr = min_right_node; + min_right_node->children[0] = curr_node->children[0]; + min_right_node->children[1] = curr_node->children[1]; + } + + free(curr_node); +} diff --git a/lab8/c/src/xcpt/data-abort-handler.c b/lab8/c/src/xcpt/data-abort-handler.c new file mode 100644 index 000000000..cb0a9765d --- /dev/null +++ b/lab8/c/src/xcpt/data-abort-handler.c @@ -0,0 +1,55 @@ +#include "oscos/console.h" +#include "oscos/panic.h" +#include "oscos/sched.h" + +void xcpt_default_handler(uint64_t vten); + +void xcpt_data_abort_handler(const uint64_t esr_val) { + thread_t *const curr_thread = current_thread(); + process_t *const curr_process = curr_thread->process; + + void *fault_addr; + __asm__ __volatile__("mrs %0, far_el1" : "=r"(fault_addr)); + + const uint64_t dfsc = esr_val & ((1 << 6) - 1); + + if (dfsc >> 2 == 0x1) { // Translation fault. + const vm_map_page_result_t result = + vm_map_page(&curr_process->addr_space, fault_addr); + if (result == VM_MAP_PAGE_SEGV) { +#ifdef VM_ENABLE_DEBUG_LOG + console_printf("DEBUG: vm: Segmentation fault, PID %zu, address 0x%p\n", + curr_process->id, fault_addr); +#endif + deliver_signal(curr_process, SIGSEGV); + } else if (result == VM_MAP_PAGE_NOMEM) { + // For a lack of better things to do. + thread_exit(); + } else { +#ifdef VM_ENABLE_DEBUG_LOG + console_printf("DEBUG: vm: Translation fault, PID %zu, address 0x%p\n", + curr_process->id, fault_addr); +#endif + } + } else if (dfsc >> 2 == 0x3) { // Permission fault. + const vm_map_page_result_t result = + vm_cow(&curr_process->addr_space, fault_addr); + if (result == VM_MAP_PAGE_SEGV) { +#ifdef VM_ENABLE_DEBUG_LOG + console_printf("DEBUG: vm: Segmentation fault, PID %zu, address 0x%p\n", + curr_process->id, fault_addr); +#endif + deliver_signal(curr_process, SIGSEGV); + } else if (result == VM_MAP_PAGE_NOMEM) { + // For a lack of better things to do. + thread_exit(); + } else { +#ifdef VM_ENABLE_DEBUG_LOG + console_printf("DEBUG: vm: CoW fault, PID %zu, address 0x%p\n", + curr_process->id, fault_addr); +#endif + } + } else { + xcpt_default_handler(8); + } +} diff --git a/lab8/c/src/xcpt/default-handler.c b/lab8/c/src/xcpt/default-handler.c new file mode 100644 index 000000000..e2eba4385 --- /dev/null +++ b/lab8/c/src/xcpt/default-handler.c @@ -0,0 +1,17 @@ +#include "oscos/console.h" +#include "oscos/libc/inttypes.h" +#include "oscos/panic.h" + +void xcpt_default_handler(const uint64_t vten) { + uint64_t spsr_val, elr_val, esr_val; + __asm__ __volatile__("mrs %0, spsr_el1" : "=r"(spsr_val)); + __asm__ __volatile__("mrs %0, elr_el1" : "=r"(elr_val)); + __asm__ __volatile__("mrs %0, esr_el1" : "=r"(esr_val)); + + PANIC("Unhandled exception\n" + "VTEN: 0x%16.1" PRIx64 "\n" + "spsr_el1: 0x%16.8" PRIx64 "\n" + "elr_el1: 0x%16.1" PRIx64 "\n" + "esr_el1: 0x%16.8" PRIx64, + vten, spsr_val, elr_val, esr_val); +} diff --git a/lab8/c/src/xcpt/insn-abort-handler.c b/lab8/c/src/xcpt/insn-abort-handler.c new file mode 100644 index 000000000..d70ff4b7a --- /dev/null +++ b/lab8/c/src/xcpt/insn-abort-handler.c @@ -0,0 +1,55 @@ +#include "oscos/console.h" +#include "oscos/panic.h" +#include "oscos/sched.h" + +void xcpt_default_handler(uint64_t vten); + +void xcpt_insn_abort_handler(const uint64_t esr_val) { + thread_t *const curr_thread = current_thread(); + process_t *const curr_process = curr_thread->process; + + void *fault_addr; + __asm__ __volatile__("mrs %0, far_el1" : "=r"(fault_addr)); + + const uint64_t ifsc = esr_val & ((1 << 6) - 1); + + if (ifsc >> 2 == 0x1) { // Translation fault. + const vm_map_page_result_t result = + vm_map_page(&curr_process->addr_space, fault_addr); + if (result == VM_MAP_PAGE_SEGV) { +#ifdef VM_ENABLE_DEBUG_LOG + console_printf("DEBUG: vm: Segmentation fault, PID %zu, address 0x%p\n", + curr_process->id, fault_addr); +#endif + deliver_signal(curr_process, SIGSEGV); + } else if (result == VM_MAP_PAGE_NOMEM) { + // For a lack of better things to do. + thread_exit(); + } else { +#ifdef VM_ENABLE_DEBUG_LOG + console_printf("DEBUG: vm: Translation fault, PID %zu, address 0x%p\n", + curr_process->id, fault_addr); +#endif + } + } else if (ifsc >> 2 == 0x3) { // Permission fault. + const vm_map_page_result_t result = + vm_cow(&curr_process->addr_space, fault_addr); + if (result == VM_MAP_PAGE_SEGV) { +#ifdef VM_ENABLE_DEBUG_LOG + console_printf("DEBUG: vm: Segmentation fault, PID %zu, address 0x%p\n", + curr_process->id, fault_addr); +#endif + deliver_signal(curr_process, SIGSEGV); + } else if (result == VM_MAP_PAGE_NOMEM) { + // For a lack of better things to do. + thread_exit(); + } else { +#ifdef VM_ENABLE_DEBUG_LOG + console_printf("DEBUG: vm: CoW fault, PID %zu, address 0x%p\n", + curr_process->id, fault_addr); +#endif + } + } else { + xcpt_default_handler(8); + } +} diff --git a/lab8/c/src/xcpt/irq-handler.c b/lab8/c/src/xcpt/irq-handler.c new file mode 100644 index 000000000..a77f6a17e --- /dev/null +++ b/lab8/c/src/xcpt/irq-handler.c @@ -0,0 +1,43 @@ +#include + +#include "oscos/console.h" +#include "oscos/drivers/board.h" +#include "oscos/drivers/l1ic.h" +#include "oscos/drivers/l2ic.h" +#include "oscos/libc/inttypes.h" +#include "oscos/timer/timeout.h" +#include "oscos/utils/core-id.h" + +void xcpt_irq_handler(void) { + PERIPHERAL_READ_BARRIER(); + + const size_t core_id = get_core_id(); + + for (;;) { + const uint32_t int_src = l1ic_get_int_src(core_id); + + if (int_src == 0) + break; + + if (int_src & INT_L1_SRC_GPU) { + const uint32_t l2_int_src = l2ic_get_pending_irq_0(); + + if (l2_int_src & INT_L2_IRQ_0_SRC_AUX) { + mini_uart_interrupt_handler(); + } else { + console_printf( + "WARN: Received an IRQ from GPU with an unknown source: %" PRIx32 + "\n", + l2_int_src); + } + } else if (int_src & INT_L1_SRC_TIMER1) { + xcpt_core_timer_interrupt_handler(); + } else { + console_printf("WARN: Received an IRQ with an unknown source: %" PRIx32 + "\n", + int_src); + } + } + + PERIPHERAL_WRITE_BARRIER(); +} diff --git a/lab8/c/src/xcpt/load-aapcs-and-eret.S b/lab8/c/src/xcpt/load-aapcs-and-eret.S new file mode 100644 index 000000000..b7ba5550f --- /dev/null +++ b/lab8/c/src/xcpt/load-aapcs-and-eret.S @@ -0,0 +1,11 @@ +#include "oscos/utils/save-ctx.S" + +.section ".text" + +load_aapcs_and_eret: + load_aapcs + eret + +.type load_aapcs_and_eret, function +.size load_aapcs_and_eret, . - load_aapcs_and_eret +.global load_aapcs_and_eret diff --git a/lab8/c/src/xcpt/svc-handler.S b/lab8/c/src/xcpt/svc-handler.S new file mode 100644 index 000000000..3ce4f5949 --- /dev/null +++ b/lab8/c/src/xcpt/svc-handler.S @@ -0,0 +1,50 @@ +.section ".text" + +xcpt_svc_handler: + // Save lr. + str lr, [sp, -32]! + + // Save spsr_el1 and elr_el1 since they can be clobbered by other ISRs. + mrs x10, spsr_el1 + mrs x11, elr_el1 + stp x10, x11, [sp, 16] + + // Unmask interrupts. + msr daifclr, 0xf + + // Check the system call number. + ubfx x9, x9, 0, 16 + cbnz x9, .Lenosys + cmp x8, 17 + b.hi .Lenosys + + // Table-jump to the system call function. + adr lr, .Lend + adr x9, syscall_table + add x9, x9, x8, lsl 2 + br x9 + +.Lenosys: + bl sys_enosys + +.Lend: + // Store the result into the trap frame. + str x0, [sp, 32] + + // Mask the interrupt since we don't want ISRs to clobber spsr_el1 and + // esr_el1 while we're restoring them. + msr daifset, 0xf + + // Restore spsr_el1 and elr_el1. + ldp x0, x1, [sp, 16] + msr spsr_el1, x0 + msr elr_el1, x1 + + // Restore lr. + ldr lr, [sp], 32 + + ret + +.type xcpt_svc_handler, function +.size xcpt_svc_handler, . - xcpt_svc_handler +.global xcpt_svc_handler diff --git a/lab8/c/src/xcpt/syscall-table.S b/lab8/c/src/xcpt/syscall-table.S new file mode 100644 index 000000000..685bf88a0 --- /dev/null +++ b/lab8/c/src/xcpt/syscall-table.S @@ -0,0 +1,24 @@ +.section ".text" + +syscall_table: + b sys_getpid + b sys_uart_read + b sys_uart_write + b sys_exec + b sys_fork + b sys_exit + b sys_mbox_call + b sys_kill + b sys_signal + b sys_signal_kill + b sys_sigreturn + b sys_open + b sys_close + b sys_write + b sys_read + b sys_mkdir + b sys_mount + b sys_chdir + +.size syscall_table, . - syscall_table +.global syscall_table diff --git a/lab8/c/src/xcpt/syscall/chdir.c b/lab8/c/src/xcpt/syscall/chdir.c new file mode 100644 index 000000000..6cc5b5e8b --- /dev/null +++ b/lab8/c/src/xcpt/syscall/chdir.c @@ -0,0 +1,14 @@ +#include "oscos/fs/vfs.h" +#include "oscos/sched.h" + +int sys_chdir(const char *const path) { + process_t *const curr_process = current_thread()->process; + + struct vnode *next_cwd; + const int result = vfs_lookup_relative(curr_process->cwd, path, &next_cwd); + if (result < 0) + return result; + + curr_process->cwd = next_cwd; + return 0; +} diff --git a/lab8/c/src/xcpt/syscall/close.c b/lab8/c/src/xcpt/syscall/close.c new file mode 100644 index 000000000..05a41f3a0 --- /dev/null +++ b/lab8/c/src/xcpt/syscall/close.c @@ -0,0 +1,15 @@ +#include "oscos/fs/vfs.h" +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" + +int sys_close(const int fd) { + process_t *const curr_process = current_thread()->process; + + if (!(0 <= fd && fd < N_FDS && curr_process->fds[fd])) + return -EBADF; + + shared_file_drop(curr_process->fds[fd]); + curr_process->fds[fd] = NULL; + + return 0; +} diff --git a/lab8/c/src/xcpt/syscall/enosys.c b/lab8/c/src/xcpt/syscall/enosys.c new file mode 100644 index 000000000..32ae47c8f --- /dev/null +++ b/lab8/c/src/xcpt/syscall/enosys.c @@ -0,0 +1,3 @@ +#include "oscos/uapi/errno.h" + +int sys_enosys(void) { return -ENOSYS; } diff --git a/lab8/c/src/xcpt/syscall/exec.c b/lab8/c/src/xcpt/syscall/exec.c new file mode 100644 index 000000000..b8a10cf2e --- /dev/null +++ b/lab8/c/src/xcpt/syscall/exec.c @@ -0,0 +1,44 @@ +#include +#include + +#include "oscos/initrd.h" +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" + +int sys_exec(const char *const name, char *const argv[const]) { + // TODO: Handle this. + (void)argv; + + if (!initrd_is_init()) { + // Act as if there are no files at all. + return -ENOENT; + } + + const cpio_newc_entry_t *const entry = initrd_find_entry_by_pathname(name); + if (!entry) { + return -ENOENT; + } + + const void *user_program_start; + size_t user_program_len; + + const uint32_t mode = CPIO_NEWC_HEADER_VALUE(entry, mode); + const uint32_t file_type = mode & CPIO_NEWC_MODE_FILE_TYPE_MASK; + if (file_type == CPIO_NEWC_MODE_FILE_TYPE_REG) { + user_program_start = CPIO_NEWC_FILE_DATA(entry); + user_program_len = CPIO_NEWC_FILESIZE(entry); + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_DIR) { + return -EISDIR; + } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_LNK) { + // TODO: Handle symbolic link. + return -ELOOP; + } else { // Unknown file type. + // Just treat it as an I/O error. + return -EIO; + } + + exec(user_program_start, user_program_len); + + // If execution reaches here, then exec failed. + return -ENOMEM; +} diff --git a/lab8/c/src/xcpt/syscall/exit.c b/lab8/c/src/xcpt/syscall/exit.c new file mode 100644 index 000000000..d06756508 --- /dev/null +++ b/lab8/c/src/xcpt/syscall/exit.c @@ -0,0 +1,3 @@ +#include "oscos/sched.h" + +void sys_exit(void) { thread_exit(); } diff --git a/lab8/c/src/xcpt/syscall/fork-child-ret.S b/lab8/c/src/xcpt/syscall/fork-child-ret.S new file mode 100644 index 000000000..ae568d02a --- /dev/null +++ b/lab8/c/src/xcpt/syscall/fork-child-ret.S @@ -0,0 +1,29 @@ +.section ".text" + +fork_child_ret: + // Mask the interrupt to prevent spsr_el1 and elr_el1 from being clobbered + // by ISRs. + msr daifset, 0xf + + ldp x0, x1, [sp] + msr spsr_el1, x0 + msr elr_el1, x1 + + mov x0, 0 + ldr x1, [sp, 16 + 0 * 16 + 8] + ldp x2, x3, [sp, 16 + 1 * 16] + ldp x4, x5, [sp, 16 + 2 * 16] + ldp x6, x7, [sp, 16 + 3 * 16] + ldp x8, x9, [sp, 16 + 4 * 16] + ldp x10, x11, [sp, 16 + 5 * 16] + ldp x12, x13, [sp, 16 + 6 * 16] + ldp x14, x15, [sp, 16 + 7 * 16] + ldp x16, x17, [sp, 16 + 8 * 16] + ldp x18, lr, [sp, 16 + 9 * 16] + + add sp, sp, 16 + 10 * 16 + eret + +.type fork_child_ret, function +.size fork_child_ret, . - fork_child_ret +.global fork_child_ret diff --git a/lab8/c/src/xcpt/syscall/fork-impl.c b/lab8/c/src/xcpt/syscall/fork-impl.c new file mode 100644 index 000000000..af40a4c2d --- /dev/null +++ b/lab8/c/src/xcpt/syscall/fork-impl.c @@ -0,0 +1,11 @@ +#include "oscos/uapi/errno.h" + +#include "oscos/sched.h" + +int sys_fork_impl(const extended_trap_frame_t *const trap_frame) { + process_t *const new_process = fork(trap_frame); + if (!new_process) + return -ENOMEM; + + return new_process->id; +} diff --git a/lab8/c/src/xcpt/syscall/fork.S b/lab8/c/src/xcpt/syscall/fork.S new file mode 100644 index 000000000..c9bb17fed --- /dev/null +++ b/lab8/c/src/xcpt/syscall/fork.S @@ -0,0 +1,48 @@ +.section ".text" + +sys_fork: + str lr, [sp, -16]! + + // Save integer context. + mrs x0, tpidr_el1 + stp x19, x20, [x0, 16 + 0 * 16] + stp x21, x22, [x0, 16 + 1 * 16] + stp x23, x24, [x0, 16 + 2 * 16] + stp x25, x26, [x0, 16 + 3 * 16] + stp x27, x28, [x0, 16 + 4 * 16] + str x29, [x0, 16 + 5 * 16] + mrs x1, sp_el0 + str x1, [x0, 16 + 6 * 16 + 8] + + // Save FP/SIMD context. + ldr x0, [x0, 16 + 7 * 16] + stp q0, q1, [x0, 0 * 32] + stp q2, q3, [x0, 1 * 32] + stp q4, q5, [x0, 2 * 32] + stp q6, q7, [x0, 3 * 32] + stp q8, q9, [x0, 4 * 32] + stp q10, q11, [x0, 5 * 32] + stp q12, q13, [x0, 6 * 32] + stp q14, q15, [x0, 7 * 32] + stp q16, q17, [x0, 8 * 32] + stp q18, q19, [x0, 9 * 32] + stp q20, q21, [x0, 10 * 32] + stp q22, q23, [x0, 11 * 32] + stp q24, q25, [x0, 12 * 32] + stp q26, q27, [x0, 13 * 32] + stp q28, q29, [x0, 14 * 32] + stp q30, q31, [x0, 15 * 32] + add x0, x0, 16 * 32 + mrs x1, fpcr + mrs x2, fpsr + stp x1, x2, [x0] + + add x0, sp, 2 * 16 + bl sys_fork_impl + + ldr lr, [sp], 16 + ret + +.type sys_fork, function +.size sys_fork, . - sys_fork +.global sys_fork diff --git a/lab8/c/src/xcpt/syscall/getpid.c b/lab8/c/src/xcpt/syscall/getpid.c new file mode 100644 index 000000000..faa43be0e --- /dev/null +++ b/lab8/c/src/xcpt/syscall/getpid.c @@ -0,0 +1,4 @@ +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" + +int sys_getpid(void) { return current_thread()->process->id; } diff --git a/lab8/c/src/xcpt/syscall/kill.c b/lab8/c/src/xcpt/syscall/kill.c new file mode 100644 index 000000000..924686748 --- /dev/null +++ b/lab8/c/src/xcpt/syscall/kill.c @@ -0,0 +1,17 @@ +#include "oscos/uapi/errno.h" + +#include "oscos/sched.h" + +int sys_kill(const int pid) { + if (pid <= 0) { + kill_all_processes(); + } else { + process_t *const process = get_process_by_id(pid); + if (!process) + return -ESRCH; + + kill_process(process); + } + + return 0; +} diff --git a/lab8/c/src/xcpt/syscall/mbox-call.c b/lab8/c/src/xcpt/syscall/mbox-call.c new file mode 100644 index 000000000..415db0e96 --- /dev/null +++ b/lab8/c/src/xcpt/syscall/mbox-call.c @@ -0,0 +1,45 @@ +#include +#include + +#include "oscos/drivers/mailbox.h" +#include "oscos/libc/string.h" +#include "oscos/mem/malloc.h" +#include "oscos/uapi/errno.h" +#include "oscos/utils/critical-section.h" + +static bool _is_valid_mbox_ch(const unsigned char ch) { return ch < 10; } + +static bool _is_valid_mbox_ptr(const unsigned int *const mbox) { + return ((uintptr_t)mbox & 0xf) == 0; +} + +int sys_mbox_call(const unsigned char ch, unsigned int *const mbox) { + // Note on return values: + // The video player treats a return value of 0 as failure and any non-zero + // return value as success. This is confirmed by reverse-engineering the video + // player. This system call therefore follows the protocol used by the video + // player rather than the usual convention since we have to execute the video + // player properly. + + if (!_is_valid_mbox_ch(ch)) + return /* -EINVAL */ 0; + + if (!_is_valid_mbox_ptr(mbox)) + return /* -EINVAL */ 0; + + const size_t mbox_len = mbox[0]; + uint32_t *const mbox_kernel = malloc(mbox_len); + memcpy(mbox_kernel, mbox, mbox_len); + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + mailbox_call(mbox_kernel, ch); + + CRITICAL_SECTION_LEAVE(daif_val); + + memcpy(mbox, mbox_kernel, mbox_len); + free(mbox_kernel); + + return /* 0 */ 1; +} diff --git a/lab8/c/src/xcpt/syscall/mkdir.c b/lab8/c/src/xcpt/syscall/mkdir.c new file mode 100644 index 000000000..294917289 --- /dev/null +++ b/lab8/c/src/xcpt/syscall/mkdir.c @@ -0,0 +1,10 @@ +#include "oscos/fs/vfs.h" +#include "oscos/sched.h" + +int sys_mkdir(const char *const pathname, const unsigned mode) { + (void)mode; + + process_t *const curr_process = current_thread()->process; + + return vfs_mkdir_relative(curr_process->cwd, pathname); +} diff --git a/lab8/c/src/xcpt/syscall/mount.c b/lab8/c/src/xcpt/syscall/mount.c new file mode 100644 index 000000000..cb9c764d6 --- /dev/null +++ b/lab8/c/src/xcpt/syscall/mount.c @@ -0,0 +1,14 @@ +#include "oscos/fs/vfs.h" +#include "oscos/sched.h" + +int sys_mount(const char *const src, const char *const target, + const char *const filesystem, const unsigned long flags, + const void *const data) { + (void)src; + (void)flags; + (void)data; + + process_t *const curr_process = current_thread()->process; + + return vfs_mount_relative(curr_process->cwd, target, filesystem); +} diff --git a/lab8/c/src/xcpt/syscall/open.c b/lab8/c/src/xcpt/syscall/open.c new file mode 100644 index 000000000..81c6048f0 --- /dev/null +++ b/lab8/c/src/xcpt/syscall/open.c @@ -0,0 +1,34 @@ +#include "oscos/fs/vfs.h" +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" + +int sys_open(const char *const pathname, const int flags) { + process_t *const curr_process = current_thread()->process; + + // Find the first available FD. + + size_t fd; + for (fd = 0; fd < N_FDS && curr_process->fds[fd]; fd++) + ; + + if (fd == N_FDS) // File descriptors used up. + return -EMFILE; + + // Open the file. + + struct file *file; + const int result = + vfs_open_relative(curr_process->cwd, pathname, flags, &file); + if (result < 0) + return result; + + shared_file_t *const shared_file = shared_file_new(file); + if (!shared_file) { + vfs_close(file); + return -ENOMEM; + } + + curr_process->fds[fd] = shared_file; + + return 0; +} diff --git a/lab8/c/src/xcpt/syscall/read.c b/lab8/c/src/xcpt/syscall/read.c new file mode 100644 index 000000000..095fdf3cb --- /dev/null +++ b/lab8/c/src/xcpt/syscall/read.c @@ -0,0 +1,12 @@ +#include "oscos/fs/vfs.h" +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" + +long sys_read(const int fd, void *const buf, const unsigned long count) { + process_t *const curr_process = current_thread()->process; + + if (!(0 <= fd && fd < N_FDS && curr_process->fds[fd])) + return -EBADF; + + return vfs_read(curr_process->fds[fd]->file, buf, count); +} diff --git a/lab8/c/src/xcpt/syscall/signal-kill.c b/lab8/c/src/xcpt/syscall/signal-kill.c new file mode 100644 index 000000000..ef748018d --- /dev/null +++ b/lab8/c/src/xcpt/syscall/signal-kill.c @@ -0,0 +1,25 @@ +#include + +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" + +static bool _is_valid_signal_num(const int signal) { + return 1 <= signal && signal <= 31; +} + +int sys_signal_kill(const int pid, const int signal) { + if (!_is_valid_signal_num(signal)) + return -EINVAL; + + if (pid < 0) { + deliver_signal_to_all_processes(signal); + } else { + process_t *const process = get_process_by_id(pid); + if (!process) + return -ESRCH; + + deliver_signal(process, signal); + } + + return 0; +} diff --git a/lab8/c/src/xcpt/syscall/signal.c b/lab8/c/src/xcpt/syscall/signal.c new file mode 100644 index 000000000..7c10e087a --- /dev/null +++ b/lab8/c/src/xcpt/syscall/signal.c @@ -0,0 +1,15 @@ +#include + +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" + +static bool _is_valid_signal_num(const int signal) { + return 1 <= signal && signal <= 31; +} + +sighandler_t sys_signal(const int signal, const sighandler_t handler) { + if (!_is_valid_signal_num(signal)) + return (sighandler_t)-EINVAL; + + return set_signal_handler(current_thread()->process, signal, handler); +} diff --git a/lab8/c/src/xcpt/syscall/sigreturn-check.c b/lab8/c/src/xcpt/syscall/sigreturn-check.c new file mode 100644 index 000000000..4abe75a25 --- /dev/null +++ b/lab8/c/src/xcpt/syscall/sigreturn-check.c @@ -0,0 +1,13 @@ +#include "oscos/sched.h" +#include "oscos/xcpt.h" + +void sys_sigreturn_check(void) { + XCPT_MASK_ALL(); + + // Crash the process if it incorrectly calls sys_sigreturn when not handling + // signals. + if (!current_thread()->status.is_handling_signal) + thread_exit(); + + XCPT_UNMASK_ALL(); +} diff --git a/lab8/c/src/xcpt/syscall/sigreturn.S b/lab8/c/src/xcpt/syscall/sigreturn.S new file mode 100644 index 000000000..006502119 --- /dev/null +++ b/lab8/c/src/xcpt/syscall/sigreturn.S @@ -0,0 +1,50 @@ +#include "oscos/utils/save-ctx.S" + +.section ".text" + +sys_sigreturn: + // Discard the trap frame. + + add sp, sp, 12 * 16 + + // Check if the system call is called within a signal handler context. + + bl sys_sigreturn_check + + // Restore FP/SIMD context. + + ldp q30, q31, [sp, 30 * 16] + ldp q28, q29, [sp, 28 * 16] + ldp q26, q27, [sp, 26 * 16] + ldp q24, q25, [sp, 24 * 16] + ldp q22, q23, [sp, 22 * 16] + ldp q20, q21, [sp, 20 * 16] + ldp q18, q19, [sp, 18 * 16] + ldp q16, q17, [sp, 16 * 16] + ldp q14, q15, [sp, 14 * 16] + ldp q12, q13, [sp, 12 * 16] + ldp q10, q11, [sp, 10 * 16] + ldp q8, q9, [sp, 8 * 16] + ldp q6, q7, [sp, 6 * 16] + ldp q4, q5, [sp, 4 * 16] + ldp q2, q3, [sp, 2 * 16] + ldp q0, q1, [sp], (32 * 16) + + ldp x0, x1, [sp], 16 + mrs x0, fpcr + mrs x1, fpsr + + // Restore integer context. + + ldp x29, lr, [sp, 10 * 8] + ldp x27, x28, [sp, 8 * 8] + ldp x25, x26, [sp, 6 * 8] + ldp x23, x24, [sp, 4 * 8] + ldp x21, x22, [sp, 2 * 8] + ldp x19, x20, [sp], (12 * 8) + + ret + +.type sys_sigreturn, function +.size sys_sigreturn, . - sys_sigreturn +.global sys_sigreturn diff --git a/lab8/c/src/xcpt/syscall/uart-read.c b/lab8/c/src/xcpt/syscall/uart-read.c new file mode 100644 index 000000000..e4646bc77 --- /dev/null +++ b/lab8/c/src/xcpt/syscall/uart-read.c @@ -0,0 +1,46 @@ +#include + +#include "oscos/console.h" +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" +#include "oscos/uapi/unistd.h" +#include "oscos/utils/critical-section.h" + +static thread_list_node_t _wait_queue = {.prev = &_wait_queue, + .next = &_wait_queue}; + +ssize_t sys_uart_read(char buf[const], const size_t size) { + size_t n_chars_read; + + for (;;) { + // We must enter critical section here. Otherwise, there will be a race + // condition between thread suspension and read readiness notification, + // whose callback adds the current thread to the run queue. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + n_chars_read = console_read_nonblock(buf, size); + if (n_chars_read != 0) { + CRITICAL_SECTION_LEAVE(daif_val); + break; + } + + thread_t *const curr_thread = current_thread(); + + console_notify_read_ready((void (*)(void *))add_all_threads_to_run_queue, + &_wait_queue); + suspend_to_wait_queue(&_wait_queue); + XCPT_MASK_ALL(); + + if (curr_thread->status.is_waken_up_by_signal) { + curr_thread->status.is_waken_up_by_signal = false; + CRITICAL_SECTION_LEAVE(daif_val); + return -EINTR; + } + + CRITICAL_SECTION_LEAVE(daif_val); + } + + return n_chars_read; +} diff --git a/lab8/c/src/xcpt/syscall/uart-write.c b/lab8/c/src/xcpt/syscall/uart-write.c new file mode 100644 index 000000000..aa23d2722 --- /dev/null +++ b/lab8/c/src/xcpt/syscall/uart-write.c @@ -0,0 +1,45 @@ +#include + +#include "oscos/console.h" +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" +#include "oscos/utils/critical-section.h" + +static thread_list_node_t _wait_queue = {.prev = &_wait_queue, + .next = &_wait_queue}; + +size_t sys_uart_write(const char buf[const], const size_t size) { + size_t n_chars_written; + + for (;;) { + // We must enter critical section here. Otherwise, there will be a race + // condition between thread suspension and write readiness notification, + // whose callback adds the current thread to the run queue. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + n_chars_written = console_write_nonblock(buf, size); + if (n_chars_written != 0) { + CRITICAL_SECTION_LEAVE(daif_val); + break; + } + + thread_t *const curr_thread = current_thread(); + + console_notify_write_ready((void (*)(void *))add_all_threads_to_run_queue, + &_wait_queue); + suspend_to_wait_queue(&_wait_queue); + XCPT_MASK_ALL(); + + if (curr_thread->status.is_waken_up_by_signal) { + curr_thread->status.is_waken_up_by_signal = false; + CRITICAL_SECTION_LEAVE(daif_val); + return -EINTR; + } + + CRITICAL_SECTION_LEAVE(daif_val); + } + + return n_chars_written; +} diff --git a/lab8/c/src/xcpt/syscall/write.c b/lab8/c/src/xcpt/syscall/write.c new file mode 100644 index 000000000..1452ec32a --- /dev/null +++ b/lab8/c/src/xcpt/syscall/write.c @@ -0,0 +1,12 @@ +#include "oscos/fs/vfs.h" +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" + +long sys_write(const int fd, const void *const buf, const unsigned long count) { + process_t *const curr_process = current_thread()->process; + + if (!(0 <= fd && fd < N_FDS && curr_process->fds[fd])) + return -EBADF; + + return vfs_write(curr_process->fds[fd]->file, buf, count); +} diff --git a/lab8/c/src/xcpt/task-queue.c b/lab8/c/src/xcpt/task-queue.c new file mode 100644 index 000000000..2101b8827 --- /dev/null +++ b/lab8/c/src/xcpt/task-queue.c @@ -0,0 +1,97 @@ +#include "oscos/xcpt/task-queue.h" + +#include +#include + +#include "oscos/utils/critical-section.h" +#include "oscos/utils/heapq.h" + +#define MAX_N_PENDING_TASKS 16 + +typedef struct { + void (*task)(void *); + void *arg; + int priority; +} pending_task_t; + +static pending_task_t _task_queue_pending_tasks[MAX_N_PENDING_TASKS]; +static size_t _task_queue_n_pending_tasks = 0; +static int _task_queue_curr_task_priority = INT_MIN; + +static int +_task_queue_pending_task_cmp_by_priority(const pending_task_t *const t1, + const pending_task_t *const t2, + void *const _arg) { + (void)_arg; + + // Note that the order is reversed. This is because the heapq module + // implements a min heap, but we want the task with the highest priority to be + // at the front of the priority queue. + + if (t2->priority < t1->priority) + return -1; + if (t2->priority > t1->priority) + return 1; + return 0; +} + +bool task_queue_add_task(void (*const task)(void *), void *const arg, + const int priority) { + if (_task_queue_n_pending_tasks == MAX_N_PENDING_TASKS) + return false; + + const pending_task_t entry = {.task = task, .arg = arg, .priority = priority}; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + heappush(_task_queue_pending_tasks, _task_queue_n_pending_tasks++, + sizeof(pending_task_t), &entry, + (int (*)(const void *, const void *, + void *))_task_queue_pending_task_cmp_by_priority, + NULL); + CRITICAL_SECTION_LEAVE(daif_val); + + return true; +} + +void task_queue_sched(void) { + const int curr_priority = _task_queue_curr_task_priority; + + while (_task_queue_n_pending_tasks > 0 && + _task_queue_pending_tasks[0].priority > + curr_priority) { // There is a pending task of a higher priority. + // Preempt the current task and run the highest-priority pending task. + + // Remove the highest-priority pending task from the priority queue. + // No need to mask interrupts here; this function always runs within an ISR. + + pending_task_t entry; + heappop(_task_queue_pending_tasks, _task_queue_n_pending_tasks--, + sizeof(pending_task_t), &entry, + (int (*)(const void *, const void *, + void *))_task_queue_pending_task_cmp_by_priority, + NULL); + + // Update task priority. + _task_queue_curr_task_priority = entry.priority; + + // Save spsr_el1 and elr_el1, since they can be clobbered. + + uint64_t spsr_val, elr_val; + __asm__ __volatile__("mrs %0, spsr_el1" : "=r"(spsr_val)); + __asm__ __volatile__("mrs %0, elr_el1" : "=r"(elr_val)); + + // Run the task with interrupts enabled. + + XCPT_UNMASK_ALL(); + entry.task(entry.arg); + XCPT_MASK_ALL(); + + // Restore spsr_el1 and elr_el1. + __asm__ __volatile__("msr spsr_el1, %0" : : "r"(spsr_val)); + __asm__ __volatile__("msr elr_el1, %0" : : "r"(elr_val)); + } + + // Restore priority. + _task_queue_curr_task_priority = curr_priority; +} diff --git a/lab8/c/src/xcpt/vector-table.S b/lab8/c/src/xcpt/vector-table.S new file mode 100644 index 000000000..eedfe5f6b --- /dev/null +++ b/lab8/c/src/xcpt/vector-table.S @@ -0,0 +1,179 @@ +#include "oscos/utils/save-ctx.S" + +.macro default_vt_entry vten label_name +\label_name: + save_aapcs + mov x0, \vten + bl xcpt_default_handler + load_aapcs + eret + +.type \label_name, function +.size \label_name, . - \label_name + +.align 7 +.endm + +.section ".text" + +.align 11 +xcpt_vector_table: + // Exception from the current EL while using SP_EL0. + + // Synchronous. + default_vt_entry 0x0 xcpt_sync_curr_el_sp_el0_handler + + // IRQ. + default_vt_entry 0x1 xcpt_irq_curr_el_sp_el0_handler + + // FIQ. + default_vt_entry 0x2 xcpt_fiq_curr_el_sp_el0_handler + + // SError. + default_vt_entry 0x3 xcpt_serror_curr_el_sp_el0_handler + + // Exception from the current EL while using SP_ELx. + + // Synchronous. +xcpt_sync_curr_el_sp_elx_handler: + save_aapcs + + // Check if the exception is caused by an instruction abort without a change + // in exception level. + mrs x0, esr_el1 + ubfx x1, x0, 26, 6 + cmp x1, 0x21 + b.ne .Lxcpt_sync_curr_el_sp_elx_handler_not_insn_abort + + bl xcpt_insn_abort_handler + b .Lxcpt_sync_curr_el_sp_elx_handler_end + +.Lxcpt_sync_curr_el_sp_elx_handler_not_insn_abort: + // Check if the exception is caused by a data abort without a change in + // exception level. + cmp x1, 0x25 + b.ne .Lxcpt_sync_curr_el_sp_elx_handler_not_data_abort + + bl xcpt_data_abort_handler + b .Lxcpt_sync_curr_el_sp_elx_handler_end + +.Lxcpt_sync_curr_el_sp_elx_handler_not_data_abort: + mov x0, 0x4 + bl xcpt_default_handler + +.Lxcpt_sync_curr_el_sp_elx_handler_end: + // We have to split the handler, since it uses more than 32 instructions. + b load_aapcs_and_eret + +.type xcpt_sync_curr_el_sp_elx_handler, function +.size xcpt_sync_curr_el_sp_elx_handler, . - xcpt_sync_curr_el_sp_elx_handler + +.align 7 + + // IRQ. +xcpt_irq_curr_el_sp_elx_handler: + save_aapcs + bl xcpt_irq_handler + bl task_queue_sched + load_aapcs + eret + +.type xcpt_irq_curr_el_sp_elx_handler, function +.size xcpt_irq_curr_el_sp_elx_handler, . - xcpt_irq_curr_el_sp_elx_handler + +.align 7 + + // FIQ. + default_vt_entry 0x6 xcpt_fiq_curr_el_sp_elx_handler + + // SError. + default_vt_entry 0x7 xcpt_serror_curr_el_sp_elx_handler + + // Exception from a lower EL and at least one lower EL is AArch64. + + // Synchronous. +xcpt_sync_lower_el_aarch64_handler: + save_aapcs + + // Check if the exception is caused by svc. + mrs x9, esr_el1 + ubfx x10, x9, 26, 6 + cmp x10, 0x15 + b.ne .Lxcpt_sync_lower_el_aarch64_handler_not_svc + + // The exception is caused by svc. + bl xcpt_svc_handler + b .Lxcpt_sync_lower_el_aarch64_handler_end + +.Lxcpt_sync_lower_el_aarch64_handler_not_svc: + // The exception is not caused by svc. + + // Check if the exception is caused by an instruction abort from a lower + // exception level. + cmp x10, 0x20 + b.ne .Lxcpt_sync_lower_el_aarch64_handler_not_insn_abort + + mov x0, x9 + bl xcpt_insn_abort_handler + b .Lxcpt_sync_lower_el_aarch64_handler_end + +.Lxcpt_sync_lower_el_aarch64_handler_not_insn_abort: + // Check if the exception is caused by an data abort from a lower exception + // level. + cmp x10, 0x24 + b.ne .Lxcpt_sync_lower_el_aarch64_handler_not_data_abort + + mov x0, x9 + bl xcpt_data_abort_handler + b .Lxcpt_sync_lower_el_aarch64_handler_end + +.Lxcpt_sync_lower_el_aarch64_handler_not_data_abort: + mov x0, 0x8 + bl xcpt_default_handler + +.Lxcpt_sync_lower_el_aarch64_handler_end: + bl handle_signals + // We have to split the handler, since it uses more than 32 instructions. + b load_aapcs_and_eret + +.type xcpt_sync_lower_el_aarch64_handler, function +.size xcpt_sync_lower_el_aarch64_handler, . - xcpt_sync_lower_el_aarch64_handler + +.align 7 + + // IRQ. +xcpt_irq_lower_el_aarch64_handler: + save_aapcs + bl xcpt_irq_handler + bl task_queue_sched + bl handle_signals + load_aapcs + eret + +.type xcpt_irq_lower_el_aarch64_handler, function +.size xcpt_irq_lower_el_aarch64_handler, . - xcpt_irq_lower_el_aarch64_handler + +.align 7 + + // FIQ. + default_vt_entry 0xa xcpt_fiq_lower_el_aarch64_handler + + // SError. + default_vt_entry 0xb xcpt_serror_lower_el_aarch64_handler + + // Exception from a lower EL and all lower ELs are AArch32. + + // Synchronous. + default_vt_entry 0xc xcpt_sync_lower_el_aarch32_handler + + // IRQ. + default_vt_entry 0xd xcpt_irq_lower_el_aarch32_handler + + // FIQ. + default_vt_entry 0xe xcpt_fiq_lower_el_aarch32_handler + + // SError. + default_vt_entry 0xf xcpt_serror_lower_el_aarch32_handler + +.size xcpt_vector_table, . - xcpt_vector_table +.global xcpt_vector_table diff --git a/lab8/c/src/xcpt/xcpt.c b/lab8/c/src/xcpt/xcpt.c new file mode 100644 index 000000000..05b558b38 --- /dev/null +++ b/lab8/c/src/xcpt/xcpt.c @@ -0,0 +1,7 @@ +#include "oscos/xcpt.h" + +extern char xcpt_vector_table[]; + +void xcpt_set_vector_table(void) { + __asm__ __volatile__("msr vbar_el1, %0" : : "r"(xcpt_vector_table)); +} diff --git a/lab8/tests/.gitignore b/lab8/tests/.gitignore new file mode 100644 index 000000000..30d29c406 --- /dev/null +++ b/lab8/tests/.gitignore @@ -0,0 +1,2 @@ +/initramfs.cpio +/bcm2710-rpi-3-b-plus.dtb diff --git a/lab8/tests/build-initrd.sh b/lab8/tests/build-initrd.sh new file mode 100755 index 000000000..7ee63fafb --- /dev/null +++ b/lab8/tests/build-initrd.sh @@ -0,0 +1,13 @@ +#!/bin/sh -e + +rm -rf rootfs + +mkdir rootfs +wget https://oscapstone.github.io/_downloads/3cb3bdb8f851d1cf29ac6f4f5d585981/vfs1.img \ + -O rootfs/vfs1.img + +cd rootfs +find . -mindepth 1 | cpio -o -H newc > ../initramfs.cpio + +cd .. +rm -r rootfs diff --git a/lab8/tests/get-dtb.sh b/lab8/tests/get-dtb.sh new file mode 100755 index 000000000..12bb4bd6d --- /dev/null +++ b/lab8/tests/get-dtb.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +dtb_url=https://github.com/raspberrypi/firmware/raw/master/boot/bcm2710-rpi-3-b-plus.dtb + +wget "$dtb_url" diff --git a/lab8/tests/user-program/libc/.gitignore b/lab8/tests/user-program/libc/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/lab8/tests/user-program/libc/.gitignore @@ -0,0 +1 @@ +/build diff --git a/lab8/tests/user-program/libc/Makefile b/lab8/tests/user-program/libc/Makefile new file mode 100644 index 000000000..37456b1f1 --- /dev/null +++ b/lab8/tests/user-program/libc/Makefile @@ -0,0 +1,61 @@ +DEFAULT_PROFILE = DEBUG +SRC_DIR = src +BUILD_DIR = build + +AR = aarch64-linux-gnu-ar -rv +AS = aarch64-linux-gnu-as +CC = aarch64-linux-gnu-gcc +CPP = aarch64-linux-gnu-cpp + +CPPFLAGS_BASE = -Iinclude + +ASFLAGS_DEBUG = -g + +CFLAGS_BASE = -std=c17 -pedantic-errors -Wall -Wextra -ffreestanding \ + -mcpu=cortex-a53 -mno-outline-atomics -mstrict-align +CFLAGS_DEBUG = -g +CFLAGS_RELEASE = -O3 -flto + +OBJS = start ctype errno fcntl mbox signal stdio stdlib string sys/mount \ + sys/stat unistd unistd/syscall __detail/utils/fmt + +# ------------------------------------------------------------------------------ + +PROFILE = $(DEFAULT_PROFILE) +OUT_DIR = $(BUILD_DIR)/$(PROFILE) + +CPPFLAGS = $(CPPFLAGS_BASE) $(CPPFLAGS_$(PROFILE)) +ASFLAGS = $(ASFLAGS_BASE) $(ASFLAGS_$(PROFILE)) +CFLAGS = $(CFLAGS_BASE) $(CFLAGS_$(PROFILE)) + +OBJ_PATHS = $(addprefix $(OUT_DIR)/,$(addsuffix .o,$(OBJS))) + +# ------------------------------------------------------------------------------ + +.PHONY: all clean-profile clean + +all: $(OUT_DIR)/libc.a + +$(OUT_DIR)/libc.a: $(OBJ_PATHS) + @mkdir -p $(@D) + $(AR) $@ $^ + +$(OUT_DIR)/%.o: $(SRC_DIR)/%.c + @mkdir -p $(@D) + $(CC) $(CPPFLAGS) $(CFLAGS) -c -MMD -MP -MF $(OUT_DIR)/$*.d $< -o $@ + +$(OUT_DIR)/%.o: $(OUT_DIR)/%.s + @mkdir -p $(@D) + $(AS) $(ASFLAGS) $^ -o $@ + +$(OUT_DIR)/%.s: $(SRC_DIR)/%.S + @mkdir -p $(@D) + $(CPP) $(CPPFLAGS) $^ -o $@ + +clean-profile: + $(RM) -r $(OUT_DIR) + +clean: + $(RM) -r $(BUILD_DIR) + +-include $(OUT_DIR)/*.d diff --git a/lab8/tests/user-program/libc/include/__detail/utils/fmt.h b/lab8/tests/user-program/libc/include/__detail/utils/fmt.h new file mode 100644 index 000000000..36b328eb0 --- /dev/null +++ b/lab8/tests/user-program/libc/include/__detail/utils/fmt.h @@ -0,0 +1,15 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC___DETAIL_UTILS_FMT_H +#define OSCOS_USER_PROGRAM_LIBC___DETAIL_UTILS_FMT_H + +#include + +typedef struct { + void (*putc)(unsigned char, void *); + void (*finalize)(void *); +} printf_vtable_t; + +int __vprintf_generic(const printf_vtable_t *vtable, void *arg, + const char *restrict format, va_list ap) + __attribute__((format(printf, 3, 0))); + +#endif diff --git a/lab8/tests/user-program/libc/include/ctype.h b/lab8/tests/user-program/libc/include/ctype.h new file mode 100644 index 000000000..2f1cc43df --- /dev/null +++ b/lab8/tests/user-program/libc/include/ctype.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_CTYPE_H +#define OSCOS_USER_PROGRAM_LIBC_CTYPE_H + +int isdigit(int c); + +#endif diff --git a/lab8/tests/user-program/libc/include/errno.h b/lab8/tests/user-program/libc/include/errno.h new file mode 100644 index 000000000..e525f1be3 --- /dev/null +++ b/lab8/tests/user-program/libc/include/errno.h @@ -0,0 +1,9 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_ERRNO_H +#define OSCOS_USER_PROGRAM_LIBC_ERRNO_H + +// Error number definitions. +#include "oscos-uapi/errno.h" + +extern int errno; + +#endif diff --git a/lab8/tests/user-program/libc/include/fcntl.h b/lab8/tests/user-program/libc/include/fcntl.h new file mode 100644 index 000000000..bae3c7e89 --- /dev/null +++ b/lab8/tests/user-program/libc/include/fcntl.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_FCNTL_H +#define OSCOS_USER_PROGRAM_LIBC_FCNTL_H + +#include "oscos-uapi/fcntl.h" + +int open(const char *pathname, int flags); + +#endif diff --git a/lab8/tests/user-program/libc/include/mbox.h b/lab8/tests/user-program/libc/include/mbox.h new file mode 100644 index 000000000..5f5e6fd4d --- /dev/null +++ b/lab8/tests/user-program/libc/include/mbox.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_MBOX_H +#define OSCOS_USER_PROGRAM_LIBC_MBOX_H + +int mbox_call(unsigned char ch, unsigned int *mbox); + +#endif diff --git a/lab8/tests/user-program/libc/include/oscos-uapi b/lab8/tests/user-program/libc/include/oscos-uapi new file mode 120000 index 000000000..ce2ca8c4d --- /dev/null +++ b/lab8/tests/user-program/libc/include/oscos-uapi @@ -0,0 +1 @@ +../../../../c/include/oscos/uapi/ \ No newline at end of file diff --git a/lab8/tests/user-program/libc/include/signal.h b/lab8/tests/user-program/libc/include/signal.h new file mode 100644 index 000000000..866dc92c2 --- /dev/null +++ b/lab8/tests/user-program/libc/include/signal.h @@ -0,0 +1,9 @@ +#include "unistd.h" + +#include "oscos-uapi/signal.h" + +int kill(pid_t pid); + +sighandler_t signal(int signal, sighandler_t handler); + +int signal_kill(pid_t pid, int signal); diff --git a/lab8/tests/user-program/libc/include/stdio.h b/lab8/tests/user-program/libc/include/stdio.h new file mode 100644 index 000000000..ac998646d --- /dev/null +++ b/lab8/tests/user-program/libc/include/stdio.h @@ -0,0 +1,12 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_STDIO_H +#define OSCOS_USER_PROGRAM_LIBC_STDIO_H + +#include + +int printf(const char *restrict format, ...) + __attribute__((format(printf, 1, 2))); + +int vprintf(const char *restrict format, va_list ap) + __attribute__((format(printf, 1, 0))); + +#endif diff --git a/lab8/tests/user-program/libc/include/stdlib.h b/lab8/tests/user-program/libc/include/stdlib.h new file mode 100644 index 000000000..91d4e865d --- /dev/null +++ b/lab8/tests/user-program/libc/include/stdlib.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_STDLIB_H +#define OSCOS_USER_PROGRAM_LIBC_STDLIB_H + +#include + +noreturn void exit(int status); + +#endif diff --git a/lab8/tests/user-program/libc/include/string.h b/lab8/tests/user-program/libc/include/string.h new file mode 100644 index 000000000..ace3e78d1 --- /dev/null +++ b/lab8/tests/user-program/libc/include/string.h @@ -0,0 +1,25 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_STRING_H +#define OSCOS_USER_PROGRAM_LIBC_STRING_H + +#include + +int memcmp(const void *s1, const void *s2, size_t n); +void *memset(void *s, int c, size_t n); +void *memcpy(void *restrict dest, const void *restrict src, size_t n); +void *memmove(void *dest, const void *src, size_t n); + +int strcmp(const char *s1, const char *s2); +int strncmp(const char *s1, const char *s2, size_t n); + +size_t strlen(const char *s); + +// Extensions. + +/// \brief Swap two non-overlapping blocks of memory. +/// +/// \param xs The pointer to the beginning of the first block of memory. +/// \param ys The pointer to the beginning of the second block of memory. +/// \param n The size of the memory blocks. +void memswp(void *restrict xs, void *restrict ys, size_t n); + +#endif diff --git a/lab8/tests/user-program/libc/include/sys/mount.h b/lab8/tests/user-program/libc/include/sys/mount.h new file mode 100644 index 000000000..e1d968293 --- /dev/null +++ b/lab8/tests/user-program/libc/include/sys/mount.h @@ -0,0 +1,7 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_SYS_MOUNT_H +#define OSCOS_USER_PROGRAM_LIBC_SYS_MOUNT_H + +int mount(const char *source, const char *target, const char *filesystemtype, + unsigned long mountflags, const void *data); + +#endif diff --git a/lab8/tests/user-program/libc/include/sys/stat.h b/lab8/tests/user-program/libc/include/sys/stat.h new file mode 100644 index 000000000..ae5eb2e06 --- /dev/null +++ b/lab8/tests/user-program/libc/include/sys/stat.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_SYS_STAT_H +#define OSCOS_USER_PROGRAM_LIBC_SYS_STAT_H + +typedef unsigned mode_t; + +int mkdir(const char *pathname, mode_t mode); + +#endif diff --git a/lab8/tests/user-program/libc/include/sys/syscall.h b/lab8/tests/user-program/libc/include/sys/syscall.h new file mode 100644 index 000000000..65efd73e5 --- /dev/null +++ b/lab8/tests/user-program/libc/include/sys/syscall.h @@ -0,0 +1 @@ +#include "../oscos-uapi/sys/syscall.h" diff --git a/lab8/tests/user-program/libc/include/unistd.h b/lab8/tests/user-program/libc/include/unistd.h new file mode 100644 index 000000000..3ad6ec7c7 --- /dev/null +++ b/lab8/tests/user-program/libc/include/unistd.h @@ -0,0 +1,28 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_UNISTD_H +#define OSCOS_USER_PROGRAM_LIBC_UNISTD_H + +#include + +#include "oscos-uapi/unistd.h" + +typedef int pid_t; + +pid_t getpid(void); + +ssize_t uart_read(void *buf, size_t count); +ssize_t uart_write(const void *buf, size_t count); + +int exec(const char *pathname, char *const argv[]); + +pid_t fork(void); + +int close(int fd); + +ssize_t write(int fd, const void *buf, size_t count); +ssize_t read(int fd, void *buf, size_t count); + +int chdir(const char *path); + +long syscall(long number, ...); + +#endif diff --git a/lab8/tests/user-program/libc/src/__detail/utils/fmt.c b/lab8/tests/user-program/libc/src/__detail/utils/fmt.c new file mode 100644 index 000000000..f33b4067d --- /dev/null +++ b/lab8/tests/user-program/libc/src/__detail/utils/fmt.c @@ -0,0 +1,654 @@ +#include "__detail/utils/fmt.h" + +#include +#include +#include + +#include "ctype.h" +#include "string.h" + +#define OCT_MAX_N_DIGITS 22 +#define DEC_MAX_N_DIGITS 20 +#define HEX_MAX_N_DIGITS 16 + +typedef enum { + LM_NONE, + LM_HH, + LM_H, + LM_L, + LM_LL, + LM_J, + LM_Z, + LM_T, + LM_UPPER_L +} length_modifier_t; + +static const char LOWER_HEX_DIGITS[16] = "0123456789abcdef"; +static const char UPPER_HEX_DIGITS[16] = "0123456789ABCDEF"; + +static size_t +_render_unsigned_dec(char digits[const static DEC_MAX_N_DIGITS + 1], + const uintmax_t x) { + digits[DEC_MAX_N_DIGITS] = '\0'; + + size_t n_digits = 0; + for (uintmax_t xc = x; xc > 0; xc /= 10) { + digits[DEC_MAX_N_DIGITS - ++n_digits] = '0' + xc % 10; + } + + return n_digits; +} + +static size_t +_render_unsigned_base_p2(char *const restrict digits, const size_t digits_len, + const uintmax_t x, const size_t log2_base, + const char *const restrict digit_template) { + const uintmax_t mask = (1 << log2_base) - 1; + + digits[digits_len] = '\0'; + + size_t n_digits = 0; + for (uintmax_t xc = x; xc > 0; xc >>= log2_base) { + digits[digits_len - ++n_digits] = digit_template[xc & mask]; + } + + return n_digits; +} + +static size_t _puts_generic(void (*const putc)(unsigned char, void *), + void *const putc_arg, const char *const s) { + size_t n_chars_printed = 0; + for (const char *c = s; *c; c++) { + putc(*c, putc_arg); + n_chars_printed++; + } + return n_chars_printed; +} + +static size_t _puts_limited_generic(void (*const putc)(unsigned char, void *), + void *const putc_arg, const char *const s, + const size_t limit) { + size_t n_chars_printed = 0, i = 0; + for (const char *c = s; i < limit && *c; i++, c++) { + putc(*c, putc_arg); + n_chars_printed++; + } + return n_chars_printed; +} + +static size_t _pad_generic(void (*const putc)(unsigned char, void *), + void *const putc_arg, const unsigned char pad, + const size_t width) { + for (size_t i = 0; i < width; i++) { + putc(pad, putc_arg); + } + return width; +} + +static size_t _vprintf_generic_putc(void (*const putc)(unsigned char, void *), + void *const putc_arg, const unsigned char c, + const bool flag_minus, + const size_t min_field_width) { + size_t n_chars_printed = 0; + + if (flag_minus) { + putc(c, putc_arg); + n_chars_printed++; + + if (min_field_width > 1) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', min_field_width - 1); + } + } else { + if (min_field_width > 1) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', min_field_width - 1); + } + + putc(c, putc_arg); + n_chars_printed++; + } + + return n_chars_printed; +} + +static size_t _vprintf_generic_puts(void (*const putc)(unsigned char, void *), + void *const putc_arg, const char *const s, + const bool flag_minus, + const size_t min_field_width, + const bool precision_valid, + const size_t precision) { + const size_t len = strlen(s); + + // Calculate width. + + const size_t width = precision_valid && precision < len ? precision : len; + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + // The string. + if (precision_valid) { + n_chars_printed += _puts_limited_generic(putc, putc_arg, s, precision); + } else { + n_chars_printed += _puts_generic(putc, putc_arg, s); + } + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return n_chars_printed; +} + +static size_t _vprintf_generic_print_signed_dec( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const intmax_t x, const bool flag_minus, const bool flag_plus, + const bool flag_space, const bool flag_zero, const size_t min_field_width, + const size_t precision) { + // Render the digits. + + char digits[DEC_MAX_N_DIGITS + 1]; + const size_t n_digits = _render_unsigned_dec(digits, x < 0 ? -x : x); + + // Calculate width. + + size_t width = n_digits; + if (precision > n_digits) { + width = precision; + } + width += x < 0 || flag_plus || flag_space; // Sign character. + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // Sign. + if (x < 0) { + putc('-', putc_arg); + n_chars_printed++; + } else if (flag_plus) { + putc('+', putc_arg); + n_chars_printed++; + } else if (flag_space) { + putc(' ', putc_arg); + n_chars_printed++; + } + // Leading zeros. + if (precision > n_digits) { + n_chars_printed += _pad_generic(putc, putc_arg, '0', precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (DEC_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +static size_t _vprintf_generic_print_unsigned_oct( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const uintmax_t x, const bool flag_minus, const bool flag_hash, + const bool flag_zero, const size_t min_field_width, + const size_t precision) { + // Render the digits. + + char digits[OCT_MAX_N_DIGITS + 1]; + const size_t n_digits = _render_unsigned_base_p2(digits, OCT_MAX_N_DIGITS, x, + 3, LOWER_HEX_DIGITS); + + // Calculate width. + + const size_t effective_precision = + flag_hash && n_digits + 1 > precision ? n_digits + 1 : precision; + + size_t width = n_digits; + if (effective_precision > n_digits) { + width = effective_precision; + } + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // Leading zeros. + if (effective_precision > n_digits) { + n_chars_printed += + _pad_generic(putc, putc_arg, '0', effective_precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (OCT_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +static size_t _vprintf_generic_print_unsigned_hex( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const uintmax_t x, const bool flag_minus, const bool flag_hash, + const bool flag_zero, const size_t min_field_width, const size_t precision, + const bool is_upper) { + const char *const digit_template = + is_upper ? UPPER_HEX_DIGITS : LOWER_HEX_DIGITS; + const char *const prefix = is_upper ? "0X" : "0x"; + + // Render the digits. + + char digits[HEX_MAX_N_DIGITS + 1]; + const size_t n_digits = + _render_unsigned_base_p2(digits, HEX_MAX_N_DIGITS, x, 4, digit_template); + + // Calculate width. + + size_t width = n_digits; + if (precision > n_digits) { + width = precision; + } + // 0x prefix. + if (flag_hash && x != 0) { + width += 2; + } + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // 0x prefix. + if (flag_hash && x != 0) { + n_chars_printed += _puts_generic(putc, putc_arg, prefix); + } + // Leading zeros. + if (precision > n_digits) { + n_chars_printed += _pad_generic(putc, putc_arg, '0', precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (HEX_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +static size_t _vprintf_generic_print_unsigned_dec( + void (*const putc)(unsigned char, void *), void *const putc_arg, + const uintmax_t x, const bool flag_minus, const bool flag_zero, + const size_t min_field_width, const size_t precision) { + // Render the digits. + + char digits[DEC_MAX_N_DIGITS + 1]; + const size_t n_digits = _render_unsigned_dec(digits, x); + + // Calculate width. + + size_t width = n_digits; + if (precision > n_digits) { + width = precision; + } + + const size_t pad_width = + min_field_width > width ? min_field_width - width : 0; + + // Print the characters. + + size_t n_chars_printed = 0; + + // Padding if right justified. + if (!flag_minus) { + n_chars_printed += + _pad_generic(putc, putc_arg, flag_zero ? '0' : ' ', pad_width); + } + // Leading zeros. + if (precision > n_digits) { + n_chars_printed += _pad_generic(putc, putc_arg, '0', precision - n_digits); + } + // The digits. + n_chars_printed += + _puts_generic(putc, putc_arg, digits + (DEC_MAX_N_DIGITS - n_digits)); + // Padding if left justified. + if (flag_minus) { + n_chars_printed += _pad_generic(putc, putc_arg, ' ', pad_width); + } + + return 0; +} + +#define READ_ARG_S(LM, AP) \ + ((LM) == LM_NONE ? va_arg(AP, int) \ + : (LM) == LM_HH ? (signed char)va_arg(AP, int) \ + : (LM) == LM_H ? (short)va_arg(AP, int) \ + : (LM) == LM_L ? va_arg(AP, long) \ + : (LM) == LM_LL ? va_arg(AP, long long) \ + : (LM) == LM_J ? va_arg(AP, intmax_t) \ + : (LM) == LM_Z ? va_arg(AP, /* signed size_t = */ long) \ + : (LM) == LM_T ? va_arg(AP, ptrdiff_t) \ + : (__builtin_unreachable(), 0)) + +#define READ_ARG_U(LM, AP) \ + ((LM) == LM_NONE ? va_arg(AP, unsigned) \ + : (LM) == LM_HH ? (unsigned char)va_arg(AP, int) \ + : (LM) == LM_H ? (unsigned short)va_arg(AP, int) \ + : (LM) == LM_L ? va_arg(AP, unsigned long) \ + : (LM) == LM_LL ? va_arg(AP, unsigned long long) \ + : (LM) == LM_J ? va_arg(AP, uintmax_t) \ + : (LM) == LM_Z ? va_arg(AP, size_t) \ + : (LM) == LM_T ? va_arg(AP, /* unsigned ptrdiff_t = */ unsigned long) \ + : (__builtin_unreachable(), 0)) + +int __vprintf_generic(const printf_vtable_t *const vtable, + void *const vtable_arg, const char *const restrict format, + va_list ap) { + size_t n_chars_printed = 0; + for (const char *restrict c = format; *c;) { + if (*c == '%') { + c++; + + // Flags. + + bool flag_minus = false, flag_plus = false, flag_space = false, + flag_hash = false, flag_zero = false; + + for (;;) { + if (*c == '-') { + c++; + flag_minus = true; + } else if (*c == '+') { + c++; + flag_plus = true; + } else if (*c == ' ') { + c++; + flag_space = true; + } else if (*c == '#') { + c++; + flag_hash = true; + } else if (*c == '0') { + c++; + flag_zero = true; + } else { + break; + } + } + + // Minimum field width. + + bool min_field_width_specified = false; + size_t min_field_width = 0; + if (*c == '*') { + c++; + min_field_width_specified = true; + const int arg = va_arg(ap, int); + if (arg < 0) { + flag_minus = true; + min_field_width = -arg; + } else { + min_field_width = arg; + } + } else if (isdigit(*c)) { + min_field_width_specified = true; + for (; isdigit(*c); c++) { + min_field_width = min_field_width * 10 + (*c - '0'); + } + } + + // Precision. + + bool precision_specified = false, precision_valid = false; + size_t precision = 0; + if (*c == '.') { + c++; + precision_specified = true; + + if (*c == '*') { + c++; + const int arg = va_arg(ap, int); + if (arg >= 0) { + precision_valid = true; + precision = arg; + } + } else { + precision_valid = true; + for (; isdigit(*c); c++) { + precision = precision * 10 + (*c - '0'); + } + } + } + + // Length modifier. + + length_modifier_t length_modifier = LM_NONE; + + switch (*c) { + case 'h': + c++; + if (*c == 'h') { + c++; + length_modifier = LM_HH; + } else { + length_modifier = LM_H; + } + break; + + case 'l': + c++; + if (*c == 'l') { + c++; + length_modifier = LM_LL; + } else { + length_modifier = LM_L; + } + break; + + case 'j': + c++; + length_modifier = LM_J; + break; + + case 'z': + c++; + length_modifier = LM_Z; + break; + + case 't': + c++; + length_modifier = LM_T; + break; + + case 'L': + c++; + length_modifier = LM_UPPER_L; + break; + } + + // Format specifier. + + switch (*c) { + case '%': + c++; + if (flag_minus || flag_plus || flag_space || flag_hash || flag_zero || + min_field_width_specified || precision_specified || + length_modifier != LM_NONE) + __builtin_unreachable(); + vtable->putc('%', vtable_arg); + n_chars_printed++; + break; + + case 'c': + c++; + switch (length_modifier) { + case LM_NONE: + if (flag_hash || flag_zero) + __builtin_unreachable(); + n_chars_printed += + _vprintf_generic_putc(vtable->putc, vtable_arg, va_arg(ap, int), + flag_minus, min_field_width); + break; + + default: + __builtin_unreachable(); + } + break; + + case 's': + c++; + switch (length_modifier) { + case LM_NONE: + if (flag_hash || flag_zero) + __builtin_unreachable(); + n_chars_printed += _vprintf_generic_puts( + vtable->putc, vtable_arg, va_arg(ap, const char *), flag_minus, + min_field_width, precision_valid, precision); + break; + + default: + __builtin_unreachable(); + } + break; + + case 'd': + case 'i': + c++; + if (flag_hash) + __builtin_unreachable(); + n_chars_printed += _vprintf_generic_print_signed_dec( + vtable->putc, vtable_arg, READ_ARG_S(length_modifier, ap), + flag_minus, flag_plus, flag_space, flag_zero, min_field_width, + precision_valid ? precision : 1); + break; + + case 'o': + c++; + n_chars_printed += _vprintf_generic_print_unsigned_oct( + vtable->putc, vtable_arg, READ_ARG_U(length_modifier, ap), + flag_minus, flag_hash, flag_zero, min_field_width, + precision_specified ? precision : 1); + break; + + case 'x': + case 'X': { + const bool is_upper = *c == 'X'; + c++; + n_chars_printed += _vprintf_generic_print_unsigned_hex( + vtable->putc, vtable_arg, READ_ARG_U(length_modifier, ap), + flag_minus, flag_hash, flag_zero, min_field_width, + precision_specified ? precision : 1, is_upper); + break; + } + + case 'u': + c++; + if (flag_hash) + __builtin_unreachable(); + n_chars_printed += _vprintf_generic_print_unsigned_dec( + vtable->putc, vtable_arg, READ_ARG_S(length_modifier, ap), + flag_minus, flag_zero, min_field_width, + precision_valid ? precision : 1); + break; + + case 'n': + c++; + if (flag_minus || flag_plus || flag_space || flag_hash || flag_zero || + min_field_width_specified || precision_specified) + __builtin_unreachable(); + switch (length_modifier) { + case LM_NONE: + *va_arg(ap, int *) = n_chars_printed; + break; + + case LM_HH: + *va_arg(ap, signed char *) = n_chars_printed; + break; + + case LM_H: + *va_arg(ap, short *) = n_chars_printed; + break; + + case LM_L: + *va_arg(ap, long *) = n_chars_printed; + break; + + case LM_LL: + *va_arg(ap, long long *) = n_chars_printed; + break; + + case LM_J: + *va_arg(ap, intmax_t *) = n_chars_printed; + break; + + case LM_Z: + *va_arg(ap, /* signed size_t = */ long *) = n_chars_printed; + break; + + case LM_T: + *va_arg(ap, ptrdiff_t *) = n_chars_printed; + break; + + default: + __builtin_unreachable(); + } + break; + + case 'p': + c++; + switch (length_modifier) { + case LM_NONE: + n_chars_printed += _vprintf_generic_print_unsigned_hex( + vtable->putc, vtable_arg, (uintmax_t)va_arg(ap, void *), + flag_minus, flag_hash, flag_zero, min_field_width, + precision_specified ? precision : 1, false); + break; + + default: + __builtin_unreachable(); + } + break; + + default: + __builtin_unreachable(); + } + } else { + vtable->putc(*c++, vtable_arg); + n_chars_printed++; + } + } + + vtable->finalize(vtable_arg); + return n_chars_printed; +} diff --git a/lab8/tests/user-program/libc/src/ctype.c b/lab8/tests/user-program/libc/src/ctype.c new file mode 100644 index 000000000..eb145ab96 --- /dev/null +++ b/lab8/tests/user-program/libc/src/ctype.c @@ -0,0 +1,3 @@ +#include "ctype.h" + +int isdigit(const int c) { return '0' <= c && c <= '9'; } diff --git a/lab8/tests/user-program/libc/src/errno.c b/lab8/tests/user-program/libc/src/errno.c new file mode 100644 index 000000000..197b93086 --- /dev/null +++ b/lab8/tests/user-program/libc/src/errno.c @@ -0,0 +1,3 @@ +#include "errno.h" + +int errno = 0; diff --git a/lab8/tests/user-program/libc/src/fcntl.c b/lab8/tests/user-program/libc/src/fcntl.c new file mode 100644 index 000000000..5829cf1b1 --- /dev/null +++ b/lab8/tests/user-program/libc/src/fcntl.c @@ -0,0 +1,8 @@ +#include "fcntl.h" + +#include "sys/syscall.h" +#include "unistd.h" + +int open(const char *const pathname, const int flags) { + return syscall(SYS_open, pathname, flags); +} diff --git a/lab8/tests/user-program/libc/src/mbox.c b/lab8/tests/user-program/libc/src/mbox.c new file mode 100644 index 000000000..450968f1b --- /dev/null +++ b/lab8/tests/user-program/libc/src/mbox.c @@ -0,0 +1,8 @@ +#include "mbox.h" + +#include "sys/syscall.h" +#include "unistd.h" + +int mbox_call(const unsigned char ch, unsigned int *const mbox) { + return syscall(SYS_mbox_call, ch, mbox); +} diff --git a/lab8/tests/user-program/libc/src/signal.c b/lab8/tests/user-program/libc/src/signal.c new file mode 100644 index 000000000..fdc717fa8 --- /dev/null +++ b/lab8/tests/user-program/libc/src/signal.c @@ -0,0 +1,13 @@ +#include "signal.h" + +#include "sys/syscall.h" + +int kill(const pid_t pid) { return syscall(SYS_kill, pid); } + +sighandler_t signal(const int signal, const sighandler_t handler) { + return (sighandler_t)syscall(SYS_signal, signal, handler); +} + +int signal_kill(const pid_t pid, const int signal) { + return syscall(SYS_signal_kill, pid, signal); +} diff --git a/lab8/tests/user-program/libc/src/start.S b/lab8/tests/user-program/libc/src/start.S new file mode 100644 index 000000000..2dc50db90 --- /dev/null +++ b/lab8/tests/user-program/libc/src/start.S @@ -0,0 +1,15 @@ +.section ".text._start" + +_start: + // We don't need to clear the .bss section here, since the kernel does this + // for us. + + // Call the main function. + bl main + + // Terminate the program. + b exit + +.size _start, . - _start +.type _start, function +.global _start diff --git a/lab8/tests/user-program/libc/src/stdio.c b/lab8/tests/user-program/libc/src/stdio.c new file mode 100644 index 000000000..87e894a80 --- /dev/null +++ b/lab8/tests/user-program/libc/src/stdio.c @@ -0,0 +1,31 @@ +#include "stdio.h" + +#include "__detail/utils/fmt.h" +#include "unistd.h" + +static void _printf_putc(const unsigned char c, void *const _arg) { + (void)_arg; + + // Very inefficient, but it works. + while (uart_write(&c, 1) != 1) + ; +} + +static void _printf_finalize(void *const _arg) { (void)_arg; } + +static const printf_vtable_t _printf_vtable = {.putc = _printf_putc, + .finalize = _printf_finalize}; + +int printf(const char *const restrict format, ...) { + va_list ap; + va_start(ap, format); + + const int result = vprintf(format, ap); + + va_end(ap); + return result; +} + +int vprintf(const char *const restrict format, va_list ap) { + return __vprintf_generic(&_printf_vtable, NULL, format, ap); +} diff --git a/lab8/tests/user-program/libc/src/stdlib.c b/lab8/tests/user-program/libc/src/stdlib.c new file mode 100644 index 000000000..6ca25fa10 --- /dev/null +++ b/lab8/tests/user-program/libc/src/stdlib.c @@ -0,0 +1,9 @@ +#include "stdlib.h" + +#include "sys/syscall.h" +#include "unistd.h" + +void exit(const int status) { + syscall(SYS_exit, status); + __builtin_unreachable(); +} diff --git a/lab8/tests/user-program/libc/src/string.c b/lab8/tests/user-program/libc/src/string.c new file mode 100644 index 000000000..567e116ec --- /dev/null +++ b/lab8/tests/user-program/libc/src/string.c @@ -0,0 +1,99 @@ +#include "string.h" + +__attribute__((used)) int memcmp(const void *const s1, const void *const s2, + const size_t n) { + const unsigned char *const s1_c = s1, *const s2_c = s2; + + for (size_t i = 0; i < n; i++) { + const int diff = (int)s1_c[i] - s2_c[i]; + if (diff != 0) + return diff; + } + + return 0; +} + +__attribute__((used)) void *memset(void *const s, const int c, const size_t n) { + unsigned char *const s_c = s; + + for (size_t i = 0; i < n; i++) { + s_c[i] = c; + } + + return s; +} + +static void __memmove_forward(unsigned char *const dest, + const unsigned char *const src, const size_t n) { + for (size_t i = 0; i < n; i++) { + dest[i] = src[i]; + } +} + +static void __memmove_backward(unsigned char *const dest, + const unsigned char *const src, const size_t n) { + for (size_t ip1 = n; ip1 > 0; ip1--) { + const size_t i = ip1 - 1; + dest[i] = src[i]; + } +} + +__attribute__((used)) void *memcpy(void *const restrict dest, + const void *const restrict src, + const size_t n) { + __memmove_forward(dest, src, n); + return dest; +} + +__attribute__((used)) void *memmove(void *const dest, const void *const src, + const size_t n) { + if (dest < src) { + __memmove_forward(dest, src, n); + } else if (dest > src) { + __memmove_backward(dest, src, n); + } + + return dest; +} + +int strcmp(const char *const s1, const char *const s2) { + for (const unsigned char *c1 = (const unsigned char *)s1, + *c2 = (const unsigned char *)s2; + *c1 || *c2; c1++, c2++) { + const int diff = (int)*c1 - *c2; + if (diff != 0) + return diff; + } + + return 0; +} + +int strncmp(const char *const s1, const char *const s2, const size_t n) { + size_t i = 0; + const unsigned char *c1 = (const unsigned char *)s1, + *c2 = (const unsigned char *)s2; + for (; i < n && (*c1 || *c2); i++, c1++, c2++) { + const int diff = (int)*c1 - *c2; + if (diff != 0) + return diff; + } + + return 0; +} + +size_t strlen(const char *s) { + size_t result = 0; + for (const char *c = s; *c; c++) { + result++; + } + return result; +} + +void memswp(void *const restrict xs, void *const restrict ys, const size_t n) { + unsigned char *const restrict xs_c = xs, *const restrict ys_c = ys; + for (size_t i = 0; i < n; i++) { + const unsigned char tmp = xs_c[i]; + xs_c[i] = ys_c[i]; + ys_c[i] = tmp; + } +} diff --git a/lab8/tests/user-program/libc/src/sys/mount.c b/lab8/tests/user-program/libc/src/sys/mount.c new file mode 100644 index 000000000..ca787c16e --- /dev/null +++ b/lab8/tests/user-program/libc/src/sys/mount.c @@ -0,0 +1,10 @@ +#include "sys/mount.h" + +#include "sys/syscall.h" +#include "unistd.h" + +int mount(const char *const source, const char *const target, + const char *const filesystemtype, const unsigned long mountflags, + const void *const data) { + return syscall(SYS_mount, source, target, filesystemtype, mountflags, data); +} diff --git a/lab8/tests/user-program/libc/src/sys/stat.c b/lab8/tests/user-program/libc/src/sys/stat.c new file mode 100644 index 000000000..bbf6ca131 --- /dev/null +++ b/lab8/tests/user-program/libc/src/sys/stat.c @@ -0,0 +1,8 @@ +#include "sys/stat.h" + +#include "sys/syscall.h" +#include "unistd.h" + +int mkdir(const char *const pathname, const mode_t mode) { + return syscall(SYS_mkdir, pathname, mode); +} diff --git a/lab8/tests/user-program/libc/src/unistd.c b/lab8/tests/user-program/libc/src/unistd.c new file mode 100644 index 000000000..a5ca260aa --- /dev/null +++ b/lab8/tests/user-program/libc/src/unistd.c @@ -0,0 +1,31 @@ +#include "unistd.h" + +#include "sys/syscall.h" + +pid_t getpid(void) { return syscall(SYS_getpid); } + +ssize_t uart_read(void *const buf, const size_t count) { + return syscall(SYS_uart_read, buf, count); +} + +ssize_t uart_write(const void *const buf, const size_t count) { + return syscall(SYS_uart_write, buf, count); +} + +int exec(const char *const pathname, char *const argv[const]) { + return syscall(SYS_exec, pathname, argv); +} + +pid_t fork(void) { return syscall(SYS_fork); } + +int close(int fd) { return syscall(SYS_close, fd); } + +ssize_t write(const int fd, const void *const buf, const size_t count) { + return syscall(SYS_write, fd, buf, count); +} + +ssize_t read(const int fd, void *const buf, const size_t count) { + return syscall(SYS_read, fd, buf, count); +} + +int chdir(const char *const path) { return syscall(SYS_chdir, path); } diff --git a/lab8/tests/user-program/libc/src/unistd/syscall.S b/lab8/tests/user-program/libc/src/unistd/syscall.S new file mode 100644 index 000000000..7783a7105 --- /dev/null +++ b/lab8/tests/user-program/libc/src/unistd/syscall.S @@ -0,0 +1,30 @@ +.section ".text" + +syscall: + mov x8, x0 + mov x0, x1 + mov x1, x2 + mov x2, x3 + mov x3, x4 + mov x4, x5 + mov x5, x6 + mov x6, x7 + + svc 0 + + cmp x0, -4096 + b.hi .Lsyscall_failed + + ret + +.Lsyscall_failed: + neg x0, x0 + ldr x1, =errno + str x0, [x1] + + mov x0, -1 + ret + +.type syscall, function +.size syscall, . - syscall +.global syscall diff --git a/lab8/tests/user-program/syscall_test/.gitignore b/lab8/tests/user-program/syscall_test/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/lab8/tests/user-program/syscall_test/.gitignore @@ -0,0 +1 @@ +/build diff --git a/lab8/tests/user-program/syscall_test/Makefile b/lab8/tests/user-program/syscall_test/Makefile new file mode 100644 index 000000000..c2a97559b --- /dev/null +++ b/lab8/tests/user-program/syscall_test/Makefile @@ -0,0 +1,73 @@ +DEFAULT_PROFILE = DEBUG +SRC_DIR = src +BUILD_DIR = build + +AS = aarch64-linux-gnu-as +CC = aarch64-linux-gnu-gcc +CPP = aarch64-linux-gnu-cpp +OBJCOPY = aarch64-linux-gnu-objcopy + +CPPFLAGS_BASE = -Iinclude -I../libc/include + +ASFLAGS_DEBUG = -g + +CFLAGS_BASE = -std=c17 -pedantic-errors -Wall -Wextra -ffreestanding \ + -mcpu=cortex-a53 -mno-outline-atomics -mstrict-align +CFLAGS_DEBUG = -g +CFLAGS_RELEASE = -O3 -flto + +LDFLAGS_BASE = -nostdlib -Xlinker --build-id=none +LDFLAGS_RELEASE = -flto -Xlinker --gc-sections + +LDLIBS_BASE = -lgcc -L../libc/build/$(PROFILE) -lc + +OBJS = main +LD_SCRIPT = $(SRC_DIR)/linker.ld + +# ------------------------------------------------------------------------------ + +PROFILE = $(DEFAULT_PROFILE) +OUT_DIR = $(BUILD_DIR)/$(PROFILE) + +CPPFLAGS = $(CPPFLAGS_BASE) $(CPPFLAGS_$(PROFILE)) +ASFLAGS = $(ASFLAGS_BASE) $(ASFLAGS_$(PROFILE)) +CFLAGS = $(CFLAGS_BASE) $(CFLAGS_$(PROFILE)) +LDFLAGS = $(LDFLAGS_BASE) $(LDFLAGS_$(PROFILE)) +LDLIBS = $(LDLIBS_BASE) $(LDLIBS_$(PROFILE)) + +OBJ_PATHS = $(addprefix $(OUT_DIR)/,$(addsuffix .o,$(OBJS))) + +# ------------------------------------------------------------------------------ + +.PHONY: all clean-profile clean + +all: $(OUT_DIR)/syscall_test.img + +$(OUT_DIR)/syscall_test.img: $(OUT_DIR)/syscall_test.elf + @mkdir -p $(@D) + $(OBJCOPY) -O binary $^ $@ + +$(OUT_DIR)/syscall_test.elf: $(OBJ_PATHS) $(LD_SCRIPT) + @mkdir -p $(@D) + $(CC) -T $(LD_SCRIPT) $(LDFLAGS) $(filter-out $(LD_SCRIPT),$^) $(LDLIBS) \ + -o $@ + +$(OUT_DIR)/%.o: $(SRC_DIR)/%.c + @mkdir -p $(@D) + $(CC) $(CPPFLAGS) $(CFLAGS) -c -MMD -MP -MF $(OUT_DIR)/$*.d $< -o $@ + +$(OUT_DIR)/%.o: $(OUT_DIR)/%.s + @mkdir -p $(@D) + $(AS) $(ASFLAGS) $^ -o $@ + +$(OUT_DIR)/%.s: $(SRC_DIR)/%.S + @mkdir -p $(@D) + $(CPP) $(CPPFLAGS) $^ -o $@ + +clean-profile: + $(RM) -r $(OUT_DIR) + +clean: + $(RM) -r $(BUILD_DIR) + +-include $(OUT_DIR)/*.d diff --git a/lab8/tests/user-program/syscall_test/src/linker.ld b/lab8/tests/user-program/syscall_test/src/linker.ld new file mode 100644 index 000000000..79510d671 --- /dev/null +++ b/lab8/tests/user-program/syscall_test/src/linker.ld @@ -0,0 +1,53 @@ +_suser = 0; + +ENTRY(_start) + +SECTIONS +{ + . = _suser; + + .text : + { + _stext = .; + + *(.text._start) /* Entry point. See `start.S`. */ + *(.text.unlikely .text.*_unlikely .text.unlikely.*) + *(.text.startup .text.startup.*) + *(.text.hot .text.hot.*) + *(.text .text.*) + *(.eh_frame) + *(.eh_frame_hdr) + + _etext = .; + } + + .rodata : + { + _srodata = .; + + *(.rodata .rodata.*) + + _erodata = .; + } + + .data : + { + _sdata = .; + + *(.data .data.*) + + _edata = .; + } + + .bss : + { + _sbss = .; + + *(.bss .bss.*) + *(COMMON) + + _ebss = .; + } + + _euser = .; +} diff --git a/lab8/tests/user-program/syscall_test/src/main.c b/lab8/tests/user-program/syscall_test/src/main.c new file mode 100644 index 000000000..330fe3ec9 --- /dev/null +++ b/lab8/tests/user-program/syscall_test/src/main.c @@ -0,0 +1,61 @@ +#include "stdint.h" +#include "stdio.h" +#include "stdlib.h" + +#include "unistd.h" + +#define NS_PER_SEC 1000000000 + +void delay_ns(const uint64_t ns) { + uint64_t start_counter_val; + __asm__ __volatile__("mrs %0, cntpct_el0" : "=r"(start_counter_val)::); + + uint64_t counter_clock_frequency_hz; + __asm__ __volatile__("mrs %0, cntfrq_el0" + : "=r"(counter_clock_frequency_hz)::); + counter_clock_frequency_hz &= 0xffffffff; + + // ceil(ns * counter_clock_frequency_hz / NS_PER_SEC). + const uint64_t delta_counter_val = + (ns * counter_clock_frequency_hz + (NS_PER_SEC - 1)) / NS_PER_SEC; + + for (;;) { + uint64_t counter_val; + __asm__ __volatile__("mrs %0, cntpct_el0" : "=r"(counter_val)::); + + if (counter_val - start_counter_val >= delta_counter_val) + break; + } +} + +void fork_test(void) { + printf("\nFork Test, pid %d\n", getpid()); + int cnt = 1; + int ret = 0; + if ((ret = fork()) == 0) { // child + long long cur_sp; + __asm__ __volatile__("mov %0, sp" : "=r"(cur_sp)); + printf("first child pid: %d, cnt: %d, ptr: %p, sp : %llx\n", getpid(), cnt, + (void *)&cnt, cur_sp); + ++cnt; + + if ((ret = fork()) != 0) { + __asm__ __volatile__("mov %0, sp" : "=r"(cur_sp)); + printf("first child pid: %d, cnt: %d, ptr: %p, sp : %llx\n", getpid(), + cnt, (void *)&cnt, cur_sp); + } else { + while (cnt < 5) { + __asm__ __volatile__("mov %0, sp" : "=r"(cur_sp)); + printf("second child pid: %d, cnt: %d, ptr: %p, sp : %llx\n", getpid(), + cnt, (void *)&cnt, cur_sp); + delay_ns(1000000); + ++cnt; + } + } + exit(0); + } else { + printf("parent here, pid %d, child %d\n", getpid(), ret); + } +} + +void main(void) { fork_test(); } From 4cdedb41d4e8c880ff8520462bb529b03fc86928 Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Thu, 29 Jun 2023 14:58:15 +0800 Subject: [PATCH 21/34] Lab 8 C: Complete B1 --- lab8/c/Makefile | 8 +- lab8/c/include/oscos/drivers/sdhost.h | 8 + lab8/c/include/oscos/fs/sd-fat32.h | 8 + lab8/c/include/oscos/libc/string.h | 1 + lab8/c/src/drivers/sdhost.c | 243 ++++++++++++ lab8/c/src/fs/sd-fat32.c | 515 ++++++++++++++++++++++++++ lab8/c/src/libc/string.c | 9 + lab8/c/src/main.c | 14 + lab8/c/src/shell.c | 27 ++ lab8/tests/.gitignore | 2 + lab8/tests/build-initrd.sh | 4 +- lab8/tests/build-sd-img.sh | 22 ++ 12 files changed, 855 insertions(+), 6 deletions(-) create mode 100644 lab8/c/include/oscos/drivers/sdhost.h create mode 100644 lab8/c/include/oscos/fs/sd-fat32.h create mode 100644 lab8/c/src/drivers/sdhost.c create mode 100644 lab8/c/src/fs/sd-fat32.c create mode 100755 lab8/tests/build-sd-img.sh diff --git a/lab8/c/Makefile b/lab8/c/Makefile index fb902eca7..5475a00a9 100644 --- a/lab8/c/Makefile +++ b/lab8/c/Makefile @@ -23,8 +23,8 @@ LDLIBS_BASE = -lgcc OBJS = start main console devicetree initrd panic shell \ drivers/aux drivers/gpio drivers/l1ic drivers/l2ic drivers/mailbox \ - drivers/mini-uart drivers/pm \ - fs/initramfs fs/tmpfs fs/vfs \ + drivers/mini-uart drivers/pm drivers/sdhost \ + fs/initramfs fs/sd-fat32 fs/tmpfs fs/vfs \ mem/malloc mem/page-alloc mem/shared-page mem/startup-alloc mem/vm \ mem/vm/kernel-page-tables \ sched/idle-thread sched/periodic-sched sched/run-signal-handler \ @@ -90,12 +90,12 @@ $(OUT_DIR)/%.s: $(SRC_DIR)/%.S qemu: $(OUT_DIR)/kernel8.img qemu-system-aarch64 -M raspi3b -kernel $< -initrd ../tests/initramfs.cpio \ -dtb ../tests/bcm2710-rpi-3-b-plus.dtb -display gtk -serial null \ - -serial stdio + -serial stdio -drive if=sd,file=../tests/sd.img,format=raw qemu-debug: $(OUT_DIR)/kernel8.img qemu-system-aarch64 -M raspi3b -kernel $< -initrd ../tests/initramfs.cpio \ -dtb ../tests/bcm2710-rpi-3-b-plus.dtb -display gtk -serial null \ - -serial stdio -S -s + -serial stdio -drive if=sd,file=../tests/sd.img,format=raw -S -s gdb: $(OUT_DIR)/kernel8.elf gdb -s $< -ex 'target remote :1234' diff --git a/lab8/c/include/oscos/drivers/sdhost.h b/lab8/c/include/oscos/drivers/sdhost.h new file mode 100644 index 000000000..0a55752ed --- /dev/null +++ b/lab8/c/include/oscos/drivers/sdhost.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_DRIVERS_SDHOST_H +#define OSCOS_DRIVERS_SDHOST_H + +void sd_init(void); +void readblock(int block_idx, void *buf); +void writeblock(int block_idx, void *buf); + +#endif diff --git a/lab8/c/include/oscos/fs/sd-fat32.h b/lab8/c/include/oscos/fs/sd-fat32.h new file mode 100644 index 000000000..956d0f4a7 --- /dev/null +++ b/lab8/c/include/oscos/fs/sd-fat32.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_FS_SD_FAT32_H +#define OSCOS_FS_SD_FAT32_H + +#include "oscos/fs/vfs.h" + +extern struct filesystem sd_fat32; + +#endif diff --git a/lab8/c/include/oscos/libc/string.h b/lab8/c/include/oscos/libc/string.h index e57f17902..107a6d5d4 100644 --- a/lab8/c/include/oscos/libc/string.h +++ b/lab8/c/include/oscos/libc/string.h @@ -17,6 +17,7 @@ char *strdup(const char *s); char *strndup(const char *s, size_t n); char *strchr(const char *s, int c); +char *strrchr(const char *s, int c); char *strcpy(char *restrict dst, const char *restrict src); diff --git a/lab8/c/src/drivers/sdhost.c b/lab8/c/src/drivers/sdhost.c new file mode 100644 index 000000000..a6452404e --- /dev/null +++ b/lab8/c/src/drivers/sdhost.c @@ -0,0 +1,243 @@ +// mmio +#define KVA 0xffff000000000000 +#define MMIO_BASE (KVA + 0x3f000000) + +// SD card command +#define GO_IDLE_STATE 0 +#define SEND_OP_CMD 1 +#define ALL_SEND_CID 2 +#define SEND_RELATIVE_ADDR 3 +#define SELECT_CARD 7 +#define SEND_IF_COND 8 +#define VOLTAGE_CHECK_PATTERN 0x1aa +#define STOP_TRANSMISSION 12 +#define SET_BLOCKLEN 16 +#define READ_SINGLE_BLOCK 17 +#define WRITE_SINGLE_BLOCK 24 +#define SD_APP_OP_COND 41 +#define SDCARD_3_3V (1 << 21) +#define SDCARD_ISHCS (1 << 30) +#define SDCARD_READY (1 << 31) +#define APP_CMD 55 + +// gpio +#define GPIO_BASE (MMIO_BASE + 0x200000) +#define GPIO_GPFSEL4 (GPIO_BASE + 0x10) +#define GPIO_GPFSEL5 (GPIO_BASE + 0x14) +#define GPIO_GPPUD (GPIO_BASE + 0x94) +#define GPIO_GPPUDCLK1 (GPIO_BASE + 0x9c) + +// sdhost +#define SDHOST_BASE (MMIO_BASE + 0x202000) +#define SDHOST_CMD (SDHOST_BASE + 0) +#define SDHOST_READ 0x40 +#define SDHOST_WRITE 0x80 +#define SDHOST_LONG_RESPONSE 0x200 +#define SDHOST_NO_REPONSE 0x400 +#define SDHOST_BUSY 0x800 +#define SDHOST_NEW_CMD 0x8000 +#define SDHOST_ARG (SDHOST_BASE + 0x4) +#define SDHOST_TOUT (SDHOST_BASE + 0x8) +#define SDHOST_TOUT_DEFAULT 0xf00000 +#define SDHOST_CDIV (SDHOST_BASE + 0xc) +#define SDHOST_CDIV_MAXDIV 0x7ff +#define SDHOST_CDIV_DEFAULT 0x148 +#define SDHOST_RESP0 (SDHOST_BASE + 0x10) +#define SDHOST_RESP1 (SDHOST_BASE + 0x14) +#define SDHOST_RESP2 (SDHOST_BASE + 0x18) +#define SDHOST_RESP3 (SDHOST_BASE + 0x1c) +#define SDHOST_HSTS (SDHOST_BASE + 0x20) +#define SDHOST_HSTS_MASK (0x7f8) +#define SDHOST_HSTS_ERR_MASK (0xf8) +#define SDHOST_HSTS_DATA (1 << 0) +#define SDHOST_PWR (SDHOST_BASE + 0x30) +#define SDHOST_DBG (SDHOST_BASE + 0x34) +#define SDHOST_DBG_FSM_DATA 1 +#define SDHOST_DBG_FSM_MASK 0xf +#define SDHOST_DBG_MASK (0x1f << 14 | 0x1f << 9) +#define SDHOST_DBG_FIFO (0x4 << 14 | 0x4 << 9) +#define SDHOST_CFG (SDHOST_BASE + 0x38) +#define SDHOST_CFG_DATA_EN (1 << 4) +#define SDHOST_CFG_SLOW (1 << 3) +#define SDHOST_CFG_INTBUS (1 << 1) +#define SDHOST_SIZE (SDHOST_BASE + 0x3c) +#define SDHOST_DATA (SDHOST_BASE + 0x40) +#define SDHOST_CNT (SDHOST_BASE + 0x50) + +// helper +#define set(io_addr, val) \ + __asm__ __volatile__("str %w1, [%0]" ::"r"(io_addr), "r"(val) : "memory"); + +#define get(io_addr, val) \ + __asm__ __volatile__("ldr %w0, [%1]" : "=r"(val) : "r"(io_addr) : "memory"); + +static inline void delay(unsigned long tick) { + while (tick--) { + __asm__ __volatile__("nop"); + } +} + +static int is_hcs; // high capcacity(SDHC) + +static void pin_setup() { + set(GPIO_GPFSEL4, 0x24000000); + set(GPIO_GPFSEL5, 0x924); + set(GPIO_GPPUD, 0); + delay(15000); + set(GPIO_GPPUDCLK1, 0xffffffff); + delay(15000); + set(GPIO_GPPUDCLK1, 0); +} + +static void sdhost_setup() { + unsigned int tmp; + set(SDHOST_PWR, 0); + set(SDHOST_CMD, 0); + set(SDHOST_ARG, 0); + set(SDHOST_TOUT, SDHOST_TOUT_DEFAULT); + set(SDHOST_CDIV, 0); + set(SDHOST_HSTS, SDHOST_HSTS_MASK); + set(SDHOST_CFG, 0); + set(SDHOST_CNT, 0); + set(SDHOST_SIZE, 0); + get(SDHOST_DBG, tmp); + tmp &= ~SDHOST_DBG_MASK; + tmp |= SDHOST_DBG_FIFO; + set(SDHOST_DBG, tmp); + delay(250000); + set(SDHOST_PWR, 1); + delay(250000); + set(SDHOST_CFG, SDHOST_CFG_SLOW | SDHOST_CFG_INTBUS | SDHOST_CFG_DATA_EN); + set(SDHOST_CDIV, SDHOST_CDIV_DEFAULT); +} + +static int wait_sd() { + int cnt = 1000000; + unsigned int cmd; + do { + if (cnt == 0) { + return -1; + } + get(SDHOST_CMD, cmd); + --cnt; + } while (cmd & SDHOST_NEW_CMD); + return 0; +} + +static int sd_cmd(unsigned cmd, unsigned int arg) { + set(SDHOST_ARG, arg); + set(SDHOST_CMD, cmd | SDHOST_NEW_CMD); + return wait_sd(); +} + +static int sdcard_setup() { + unsigned int tmp; + sd_cmd(GO_IDLE_STATE | SDHOST_NO_REPONSE, 0); + sd_cmd(SEND_IF_COND, VOLTAGE_CHECK_PATTERN); + get(SDHOST_RESP0, tmp); + if (tmp != VOLTAGE_CHECK_PATTERN) { + return -1; + } + while (1) { + if (sd_cmd(APP_CMD, 0) == -1) { + // MMC card or invalid card status + // currently not support + continue; + } + sd_cmd(SD_APP_OP_COND, SDCARD_3_3V | SDCARD_ISHCS); + get(SDHOST_RESP0, tmp); + if (tmp & SDCARD_READY) { + break; + } + delay(1000000); + } + + is_hcs = tmp & SDCARD_ISHCS; + sd_cmd(ALL_SEND_CID | SDHOST_LONG_RESPONSE, 0); + sd_cmd(SEND_RELATIVE_ADDR, 0); + get(SDHOST_RESP0, tmp); + sd_cmd(SELECT_CARD, tmp); + sd_cmd(SET_BLOCKLEN, 512); + return 0; +} + +static int wait_fifo() { + int cnt = 1000000; + unsigned int hsts; + do { + if (cnt == 0) { + return -1; + } + get(SDHOST_HSTS, hsts); + --cnt; + } while ((hsts & SDHOST_HSTS_DATA) == 0); + return 0; +} + +static void set_block(int size, int cnt) { + set(SDHOST_SIZE, size); + set(SDHOST_CNT, cnt); +} + +static void wait_finish() { + unsigned int dbg; + do { + get(SDHOST_DBG, dbg); + } while ((dbg & SDHOST_DBG_FSM_MASK) != SDHOST_HSTS_DATA); +} + +void readblock(int block_idx, void *buf) { + unsigned int *buf_u = (unsigned int *)buf; + int succ = 0; + if (!is_hcs) { + block_idx <<= 9; + } + do { + set_block(512, 1); + sd_cmd(READ_SINGLE_BLOCK | SDHOST_READ, block_idx); + for (int i = 0; i < 128; ++i) { + wait_fifo(); + get(SDHOST_DATA, buf_u[i]); + } + unsigned int hsts; + get(SDHOST_HSTS, hsts); + if (hsts & SDHOST_HSTS_ERR_MASK) { + set(SDHOST_HSTS, SDHOST_HSTS_ERR_MASK); + sd_cmd(STOP_TRANSMISSION | SDHOST_BUSY, 0); + } else { + succ = 1; + } + } while (!succ); + wait_finish(); +} + +void writeblock(int block_idx, void *buf) { + unsigned int *buf_u = (unsigned int *)buf; + int succ = 0; + if (!is_hcs) { + block_idx <<= 9; + } + do { + set_block(512, 1); + sd_cmd(WRITE_SINGLE_BLOCK | SDHOST_WRITE, block_idx); + for (int i = 0; i < 128; ++i) { + wait_fifo(); + set(SDHOST_DATA, buf_u[i]); + } + unsigned int hsts; + get(SDHOST_HSTS, hsts); + if (hsts & SDHOST_HSTS_ERR_MASK) { + set(SDHOST_HSTS, SDHOST_HSTS_ERR_MASK); + sd_cmd(STOP_TRANSMISSION | SDHOST_BUSY, 0); + } else { + succ = 1; + } + } while (!succ); + wait_finish(); +} + +void sd_init() { + pin_setup(); + sdhost_setup(); + sdcard_setup(); +} diff --git a/lab8/c/src/fs/sd-fat32.c b/lab8/c/src/fs/sd-fat32.c new file mode 100644 index 000000000..2d8611060 --- /dev/null +++ b/lab8/c/src/fs/sd-fat32.c @@ -0,0 +1,515 @@ +#include "oscos/fs/initramfs.h" + +#include "oscos/console.h" +#include "oscos/drivers/sdhost.h" +#include "oscos/initrd.h" +#include "oscos/libc/string.h" +#include "oscos/mem/malloc.h" +#include "oscos/uapi/errno.h" +#include "oscos/uapi/stdio.h" +#include "oscos/utils/critical-section.h" +#include "oscos/utils/rb.h" + +typedef struct { + char jmp[3]; + char oem[8]; + unsigned char bps0; + unsigned char bps1; + unsigned char spc; + unsigned short rsc; + unsigned char nf; + unsigned char nr0; + unsigned char nr1; + unsigned short ts16; + unsigned char media; + unsigned short spf16; + unsigned short spt; + unsigned short nh; + unsigned int hs; + unsigned int ts32; + unsigned int spf32; + unsigned int flg; + unsigned int rc; + char vol[6]; + char fst[8]; + char dmy[20]; + char fst2[8]; +} __attribute__((packed)) bpb_t; + +typedef struct { + char name[8]; + char ext[3]; + char attr[9]; + unsigned short ch; + unsigned int attr2; + unsigned short cl; + unsigned int size; +} __attribute__((packed)) fatdir_t; + +typedef struct { + size_t partition_lba, fat_lba_offset, data_region_lba_offset, + cluster_n_sectors; +} fat32_fsinfo_t; + +typedef struct { + const char *component_name; + struct vnode *vnode; +} sd_fat32_child_vnode_entry_t; + +typedef enum { TYPE_DIR, TYPE_FILE } sd_fat32_internal_type_t; + +typedef struct { + struct vnode *parent; + size_t cluster_addr; + rb_node_t *child_vnodes; // Created lazily. +} sd_fat32_internal_dir_data_t; + +typedef struct { + size_t cluster_addr; + size_t size; +} sd_fat32_internal_file_data_t; + +typedef struct { + sd_fat32_internal_type_t type; + const fat32_fsinfo_t *fsinfo; + union { + sd_fat32_internal_file_data_t file_data; + sd_fat32_internal_dir_data_t dir_data; + }; +} sd_fat32_internal_t; + +static int _sd_fat32_setup_mount(struct filesystem *fs, struct mount *mount); + +static int _sd_fat32_write(struct file *file, const void *buf, size_t len); +static int _sd_fat32_read(struct file *file, void *buf, size_t len); +static int _sd_fat32_open(struct vnode *file_node, struct file **target); +static int _sd_fat32_close(struct file *file); +static long _sd_fat32_lseek64(struct file *file, long offset, int whence); + +static int _sd_fat32_lookup(struct vnode *dir_node, struct vnode **target, + const char *component_name); +static int _sd_fat32_create(struct vnode *dir_node, struct vnode **target, + const char *component_name); +static int _sd_fat32_mkdir(struct vnode *dir_node, struct vnode **target, + const char *component_name); + +struct filesystem sd_fat32 = {.name = "fat32", + .setup_mount = _sd_fat32_setup_mount}; + +static struct file_operations _sd_fat32_file_operations = { + .write = _sd_fat32_write, + .read = _sd_fat32_read, + .open = _sd_fat32_open, + .close = _sd_fat32_close, + .lseek64 = _sd_fat32_lseek64}; + +static struct vnode_operations _sd_fat32_vnode_operations = { + .lookup = _sd_fat32_lookup, + .create = _sd_fat32_create, + .mkdir = _sd_fat32_mkdir}; + +static size_t +_sd_fat32_cluster_addr_to_data_lba(const fat32_fsinfo_t *const fsinfo, + const size_t cluster_addr) { + return fsinfo->partition_lba + fsinfo->data_region_lba_offset + + cluster_addr * fsinfo->cluster_n_sectors; +} + +static size_t +_sd_fat32_cluster_addr_to_fat_lba(const fat32_fsinfo_t *const fsinfo, + const size_t cluster_addr) { + // Each block has 2⁹⁻² FAT entries. + return fsinfo->partition_lba + fsinfo->fat_lba_offset + (cluster_addr >> 7); +} + +static int _sd_fat32_cmp_child_vnode_entries_by_component_name( + const sd_fat32_child_vnode_entry_t *const e1, + const sd_fat32_child_vnode_entry_t *const e2, void *const _arg) { + (void)_arg; + + return strcmp(e1->component_name, e2->component_name); +} + +static int _sd_fat32_cmp_component_name_and_child_vnode_entry( + const char *component_name, const sd_fat32_child_vnode_entry_t *const entry, + void *const _arg) { + (void)_arg; + + return strcmp(component_name, entry->component_name); +} + +static struct vnode *_sd_fat32_create_dir_vnode(struct mount *const mount, + struct vnode *const parent, + const size_t cluster_addr) { + struct vnode *const result = malloc(sizeof(struct vnode)); + if (!result) + return NULL; + + sd_fat32_internal_t *const internal = malloc(sizeof(sd_fat32_internal_t)); + if (!internal) { + free(result); + return NULL; + } + + *internal = (sd_fat32_internal_t){ + .type = TYPE_DIR, + .dir_data = (sd_fat32_internal_dir_data_t){.parent = parent, + .cluster_addr = cluster_addr, + .child_vnodes = NULL}}; + *result = (struct vnode){.mount = mount, + .v_ops = &_sd_fat32_vnode_operations, + .f_ops = &_sd_fat32_file_operations, + .internal = internal}; + return result; +} + +static struct vnode *_sd_fat32_create_file_vnode(struct mount *const mount, + const size_t cluster_addr, + const size_t size) { + struct vnode *const result = malloc(sizeof(struct vnode)); + if (!result) + return NULL; + + sd_fat32_internal_t *const internal = malloc(sizeof(sd_fat32_internal_t)); + if (!internal) { + free(result); + return NULL; + } + + *internal = + (sd_fat32_internal_t){.type = TYPE_FILE, + .file_data = (sd_fat32_internal_file_data_t){ + .cluster_addr = cluster_addr, .size = size}}; + *result = (struct vnode){.mount = mount, + .v_ops = &_sd_fat32_vnode_operations, + .f_ops = &_sd_fat32_file_operations, + .internal = internal}; + return result; +} + +static int _sd_fat32_setup_mount(struct filesystem *const fs, + struct mount *const mount) { + unsigned char *const block_buf = malloc(512); + if (!block_buf) + return -ENOMEM; + + fat32_fsinfo_t *const fsinfo = malloc(sizeof(fat32_fsinfo_t)); + if (!fsinfo) { + free(block_buf); + return -ENOMEM; + } + + // Read MBR. + + readblock(0, block_buf); + + const size_t partition_lba = block_buf[0x1c6] + (block_buf[0x1c7] << 8) + + (block_buf[0x1c8] << 16) + + (block_buf[0x1c9] << 24); + console_printf("DEBUG: sd-fat32: Partition LBA = 0x%zx\n", partition_lba); + + // Read VBR. + + readblock(partition_lba, block_buf); + + bpb_t *const bpb = (bpb_t *)block_buf; + *fsinfo = (fat32_fsinfo_t){.partition_lba = partition_lba, + .fat_lba_offset = bpb->rsc, + .data_region_lba_offset = + bpb->rsc + bpb->nf * bpb->spf32 - 2 * bpb->spc, + .cluster_n_sectors = bpb->spc}; + + const size_t root_cluster_addr = bpb->rc; + console_printf("DEBUG: sd-fat32: Root dir cluster addr = 0x%zx\n", + root_cluster_addr); + + // Create vnode. + + struct vnode *const root_vnode = + _sd_fat32_create_dir_vnode(mount, NULL, root_cluster_addr); + if (!root_vnode) { + free(fsinfo); + free(block_buf); + return -ENOMEM; + } + + ((sd_fat32_internal_t *)root_vnode->internal)->fsinfo = fsinfo; + *mount = (struct mount){.fs = fs, .root = root_vnode}; + + free(block_buf); + return 0; +} + +static int _sd_fat32_write(struct file *const file, const void *const buf, + const size_t len) { + (void)file; + (void)buf; + (void)len; + + return -EROFS; +} + +static int _sd_fat32_read(struct file *const file, void *const buf, + const size_t len) { + sd_fat32_internal_t *const internal = + (sd_fat32_internal_t *)file->vnode->internal; + if (internal->type != TYPE_FILE) + return -EISDIR; + + sd_fat32_internal_file_data_t *const file_data = &internal->file_data; + + unsigned char *const block_buf = malloc(512); + if (!block_buf) + return -ENOMEM; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + size_t curr_cluster_addr = file_data->cluster_addr, file_offset = 0, + n_chars_read = 0; + for (;;) { + // Read the next cluster address. + + readblock(_sd_fat32_cluster_addr_to_fat_lba( + ((sd_fat32_internal_t *)(file->vnode->mount->root->internal)) + ->fsinfo, + curr_cluster_addr), + block_buf); + const uint32_t *const fat_entries = (const uint32_t *)block_buf; + const size_t next_cluster_addr = + fat_entries[curr_cluster_addr >> 7] & 0x0fffffff; + + const size_t end_offset = file_offset + 512 > file_data->size + ? file_data->size + : file_offset + 512; + const size_t max_start = + file_offset > file->f_pos ? file_offset : file->f_pos, + min_end = end_offset < file->f_pos + len ? end_offset + : file->f_pos + len; + if (max_start < min_end) { + readblock( + _sd_fat32_cluster_addr_to_data_lba( + ((sd_fat32_internal_t *)(file->vnode->mount->root->internal)) + ->fsinfo, + curr_cluster_addr), + block_buf); + memcpy((char *)buf + n_chars_read, block_buf + (max_start & 511), + min_end - max_start); + n_chars_read += min_end - max_start; + } + + if (file_offset + 512 >= file->f_pos + len) // Done reading. + break; + if (!(1 < next_cluster_addr && next_cluster_addr && + 0xfffffff8)) // Nothing more to read. + break; + + curr_cluster_addr = next_cluster_addr; + file_offset += 512; + } + + file->f_pos += n_chars_read; + + CRITICAL_SECTION_LEAVE(daif_val); + return n_chars_read; +} + +static int _sd_fat32_open(struct vnode *const file_node, + struct file **const target) { + sd_fat32_internal_t *const internal = + (sd_fat32_internal_t *)file_node->internal; + if (internal->type != TYPE_FILE) + return -EISDIR; + + struct file *const file_handle = malloc(sizeof(struct file)); + if (!file_handle) + return -ENOMEM; + + *file_handle = (struct file){.vnode = file_node, + .f_pos = 0, + .f_ops = &_sd_fat32_file_operations, + .flags = 0}; + *target = file_handle; + + return 0; +} + +static int _sd_fat32_close(struct file *const file) { + free(file); + return 0; +} + +static long _sd_fat32_lseek64(struct file *const file, const long offset, + const int whence) { + sd_fat32_internal_t *const internal = + (sd_fat32_internal_t *)file->vnode->internal; + if (internal->type != TYPE_FILE) + return -EISDIR; + + sd_fat32_internal_file_data_t *const file_data = &internal->file_data; + + if (!(whence == SEEK_SET || whence == SEEK_CUR || whence == SEEK_END)) + return -EINVAL; + + const long f_pos_base = whence == SEEK_SET ? 0 + : whence == SEEK_CUR ? file->f_pos + : file_data->size, + new_f_pos = f_pos_base + offset; + + if (new_f_pos < 0) { + return -EINVAL; + } else { + file->f_pos = new_f_pos; + return 0; + } + + return -ENOSYS; +} + +static int _sd_fat32_lookup(struct vnode *const dir_node, + struct vnode **const target, + const char *const component_name) { + sd_fat32_internal_t *const internal = + (sd_fat32_internal_t *)dir_node->internal; + if (internal->type != TYPE_DIR) + return -ENOTDIR; + + sd_fat32_internal_dir_data_t *const dir_data = &internal->dir_data; + + if (strcmp(component_name, ".") == 0) { + *target = dir_node; + return 0; + } else if (strcmp(component_name, "..") == 0) { + *target = dir_data->parent; + return 0; + } + + // Check if the vnode has been created before. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const sd_fat32_child_vnode_entry_t *const child_vnode_entry = rb_search( + dir_data->child_vnodes, component_name, + (int (*)(const void *, const void *, + void *))_sd_fat32_cmp_component_name_and_child_vnode_entry, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + if (child_vnode_entry) { + *target = child_vnode_entry->vnode; + return 0; + } + + // A vnode has not been created before. Check if the file/directory exists. + + char filename_buf[11] = " "; + + const char *const component_name_last_dot_ptr = strrchr(component_name, '.'); + const size_t component_name_noext_len = + component_name_last_dot_ptr + ? (size_t)(component_name_last_dot_ptr - component_name) + : strlen(component_name), + component_name_noext_cpy_len = + component_name_noext_len > 8 ? 8 : component_name_noext_len; + memcpy(filename_buf, component_name, component_name_noext_cpy_len); + + const size_t component_name_ext_len = + component_name_last_dot_ptr + ? strlen(component_name_last_dot_ptr + 1) + : 0, + component_name_ext_cpy_len = + component_name_ext_len > 3 ? 3 : component_name_ext_len; + memcpy(filename_buf + 8, component_name_last_dot_ptr + 1, + component_name_ext_cpy_len); + + unsigned char *const block_buf = malloc(512); + if (!block_buf) + return -ENOMEM; + + readblock( + _sd_fat32_cluster_addr_to_data_lba( + ((sd_fat32_internal_t *)(dir_node->mount->root->internal))->fsinfo, + dir_data->cluster_addr), + block_buf); + + const fatdir_t *dir_entry = NULL; + for (size_t offset = 0; offset < 512; offset += sizeof(fatdir_t)) { + const fatdir_t *const curr_dir_entry = + (const fatdir_t *)(block_buf + offset); + if (curr_dir_entry->name[0] == 0) + break; + + if (curr_dir_entry->name[0] == 0xe5 || + curr_dir_entry->attr[0] == 0xf) // Invalid entry. + continue; + + if (memcmp(curr_dir_entry->name, filename_buf, 11) == 0) { + dir_entry = curr_dir_entry; + break; + } + } + + if (!dir_entry) { + free(block_buf); + return -ENOENT; + } + + // Create a new vnode. + + const bool is_dir = dir_entry->attr[0] & 0x10; + const size_t cluster_addr = (dir_entry->ch << 16) + dir_entry->cl; + const size_t file_size = dir_entry->size; + free(block_buf); + struct vnode *const vnode = + is_dir + ? _sd_fat32_create_dir_vnode(dir_node->mount, dir_node, cluster_addr) + : _sd_fat32_create_file_vnode(dir_node->mount, cluster_addr, + file_size); + if (!vnode) + return -ENOMEM; + + const char *const entry_component_name = strdup(component_name); + if (!entry_component_name) { + free(vnode); + return -ENOMEM; + } + + const sd_fat32_child_vnode_entry_t new_child_vnode_entry = { + .component_name = entry_component_name, .vnode = vnode}; + + CRITICAL_SECTION_ENTER(daif_val); + + rb_insert(&dir_data->child_vnodes, sizeof(sd_fat32_child_vnode_entry_t), + &new_child_vnode_entry, + (int (*)(const void *, const void *, void *)) + _sd_fat32_cmp_child_vnode_entries_by_component_name, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + *target = vnode; + + return 0; +} + +static int _sd_fat32_create(struct vnode *const dir_node, + struct vnode **const target, + const char *const component_name) { + (void)dir_node; + (void)target; + (void)component_name; + + return -EROFS; +} + +static int _sd_fat32_mkdir(struct vnode *const dir_node, + struct vnode **const target, + const char *const component_name) { + (void)dir_node; + (void)target; + (void)component_name; + + return -EROFS; +} diff --git a/lab8/c/src/libc/string.c b/lab8/c/src/libc/string.c index 6451926c4..70a97f88c 100644 --- a/lab8/c/src/libc/string.c +++ b/lab8/c/src/libc/string.c @@ -128,6 +128,15 @@ char *strchr(const char *const s, const int c) { return NULL; } +char *strrchr(const char *const s, const int c) { + for (const char *pp1 = s + strlen(s); pp1 != s; pp1--) { + const char *const p = pp1 - 1; + if (*p == c) + return (char *)p; + } + return NULL; +} + char *strcpy(char *const restrict dst, const char *const restrict src) { char *restrict cd = dst; const char *restrict cs = src; diff --git a/lab8/c/src/main.c b/lab8/c/src/main.c index cdf681d76..16cb8570a 100644 --- a/lab8/c/src/main.c +++ b/lab8/c/src/main.c @@ -6,7 +6,9 @@ #include "oscos/drivers/l2ic.h" #include "oscos/drivers/mailbox.h" #include "oscos/drivers/pm.h" +#include "oscos/drivers/sdhost.h" #include "oscos/fs/initramfs.h" +#include "oscos/fs/sd-fat32.h" #include "oscos/fs/tmpfs.h" #include "oscos/fs/vfs.h" #include "oscos/initrd.h" @@ -67,6 +69,8 @@ void main(const void *const dtb_start) { // Initialize VFS. + sd_init(); + int vfs_op_result; vfs_op_result = register_filesystem(&tmpfs); @@ -75,6 +79,9 @@ void main(const void *const dtb_start) { vfs_op_result = register_filesystem(&initramfs); if (vfs_op_result < 0) PANIC("Cannot register initramfs: errno %d", -vfs_op_result); + vfs_op_result = register_filesystem(&sd_fat32); + if (vfs_op_result < 0) + PANIC("Cannot register fat32: errno %d", -vfs_op_result); vfs_op_result = tmpfs.setup_mount(&tmpfs, &rootfs); if (vfs_op_result < 0) @@ -87,6 +94,13 @@ void main(const void *const dtb_start) { if (vfs_op_result < 0) PANIC("Cannot mount initramfs on /initramfs: errno %d", -vfs_op_result); + vfs_op_result = vfs_mkdir("/boot"); + if (vfs_op_result < 0) + PANIC("Cannot mkdir /boot: errno %d", -vfs_op_result); + vfs_op_result = vfs_mount("/boot", "fat32"); + if (vfs_op_result < 0) + PANIC("Cannot mount fat32 on /boot: errno %d", -vfs_op_result); + thread_create(_run_shell, NULL); sched_setup_periodic_scheduling(); diff --git a/lab8/c/src/shell.c b/lab8/c/src/shell.c index 7ac05bb53..f184efda8 100644 --- a/lab8/c/src/shell.c +++ b/lab8/c/src/shell.c @@ -603,6 +603,31 @@ static void _shell_do_cmd_vfs_test_4(void) { return; } +static void _shell_do_cmd_fat_test_1(void) { + char buf[8] = {0}; + struct file *kernel; + int result; + + result = vfs_open("/boot/KERNEL8.IMG", 0, &kernel); + console_printf("open(\"/boot/KERNEL8.IMG\", 0) = %d\n", result); + if (result < 0) + return; + + result = vfs_read(kernel, buf, 8); + console_printf("read(\"/boot/KERNEL8.IMG\", ..., 8) = %d\n", result); + if (result < 0) + return; + for (size_t i = 0; i < 8; i++) { + console_printf("\\x%02hhx", buf[i]); + } + console_putc('\n'); + + result = vfs_close(kernel); + console_printf("close(\"/boot/KERNEL8.IMG\") = %d\n", result); + if (result < 0) + return; +} + static void _shell_do_cmd_vfs_test_5(void) { unsigned char buf[8] = {0}; struct file *vfs1; @@ -672,6 +697,8 @@ void run_shell(void) { _shell_do_cmd_vfs_test_4(); } else if (strcmp(cmd_buf, "vfs-test-5") == 0) { _shell_do_cmd_vfs_test_5(); + } else if (strcmp(cmd_buf, "fat-test-1") == 0) { + _shell_do_cmd_fat_test_1(); } else { _shell_cmd_not_found(cmd_buf); } diff --git a/lab8/tests/.gitignore b/lab8/tests/.gitignore index 30d29c406..8adec4632 100644 --- a/lab8/tests/.gitignore +++ b/lab8/tests/.gitignore @@ -1,2 +1,4 @@ /initramfs.cpio /bcm2710-rpi-3-b-plus.dtb +/sd.img +/FAT_R.TXT diff --git a/lab8/tests/build-initrd.sh b/lab8/tests/build-initrd.sh index 7ee63fafb..e64bb7ed9 100755 --- a/lab8/tests/build-initrd.sh +++ b/lab8/tests/build-initrd.sh @@ -3,8 +3,8 @@ rm -rf rootfs mkdir rootfs -wget https://oscapstone.github.io/_downloads/3cb3bdb8f851d1cf29ac6f4f5d585981/vfs1.img \ - -O rootfs/vfs1.img +wget https://oscapstone.github.io/_downloads/4ee703906d67d0333ef4c215dc060ab3/vfs2.img \ + -O rootfs/vfs2.img cd rootfs find . -mindepth 1 | cpio -o -H newc > ../initramfs.cpio diff --git a/lab8/tests/build-sd-img.sh b/lab8/tests/build-sd-img.sh new file mode 100755 index 000000000..8a97333e8 --- /dev/null +++ b/lab8/tests/build-sd-img.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +wget https://github.com/GrassLab/osdi/raw/master/supplement/sfn_nctuos.img \ + -O sd.img + +img_sz=$(du -b sd.img | cut -f 1) + +# Round img_sz to the next power of 2. +img_sz=$((img_sz - 1)) +img_sz=$((img_sz | (img_sz >> 1))) +img_sz=$((img_sz | (img_sz >> 2))) +img_sz=$((img_sz | (img_sz >> 4))) +img_sz=$((img_sz | (img_sz >> 8))) +img_sz=$((img_sz | (img_sz >> 16))) +img_sz=$((img_sz + 1)) + +qemu-img resize -f raw sd.img $img_sz + +wget https://oscapstone.github.io/_downloads/10fbdc3e04b471849e714edcdcf4ce26/FAT_R.TXT + +echo +echo 'Now, manually put FAT_R.TXT into sd.img.' From 138c0126fb840b34381d1b63d46123c94cb32b39 Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Thu, 29 Jun 2023 20:23:33 +0800 Subject: [PATCH 22/34] Lab 6 C: Complete A1 Other changes: - Add a workaround for a run queue corruption but that causes terminated threads to be scheduled. --- lab6/c/Makefile | 2 +- lab6/c/include/oscos/mem/vm.h | 25 +- lab6/c/include/oscos/uapi/sys/mman.h | 11 + lab6/c/include/oscos/uapi/sys/syscall.h | 3 +- lab6/c/include/oscos/utils/rb.h | 14 ++ lab6/c/src/mem/vm.c | 237 ++++++++++++++---- lab6/c/src/sched/sched.c | 80 +++--- lab6/c/src/sched/sig-handler-main.S | 2 +- lab6/c/src/utils/rb.c | 93 +++++++ lab6/c/src/xcpt/data-abort-handler.c | 5 +- lab6/c/src/xcpt/insn-abort-handler.c | 4 +- lab6/c/src/xcpt/syscall-table.S | 1 + lab6/c/src/xcpt/syscall/mmap.c | 33 +++ lab6/tests/user-program/libc/Makefile | 2 +- .../user-program/libc/include/sys/mman.h | 13 + lab6/tests/user-program/libc/src/sys/mman.c | 9 + 16 files changed, 449 insertions(+), 85 deletions(-) create mode 100644 lab6/c/include/oscos/uapi/sys/mman.h create mode 100644 lab6/c/src/xcpt/syscall/mmap.c create mode 100644 lab6/tests/user-program/libc/include/sys/mman.h create mode 100644 lab6/tests/user-program/libc/src/sys/mman.c diff --git a/lab6/c/Makefile b/lab6/c/Makefile index 6b488b48e..2d5442152 100644 --- a/lab6/c/Makefile +++ b/lab6/c/Makefile @@ -38,7 +38,7 @@ OBJS = start main console devicetree initrd panic shell \ xcpt/syscall/uart-write xcpt/syscall/exec xcpt/syscall/fork \ xcpt/syscall/fork-impl xcpt/syscall/fork-child-ret \ xcpt/syscall/exit xcpt/syscall/mbox-call xcpt/syscall/kill \ - xcpt/syscall/signal xcpt/syscall/signal-kill \ + xcpt/syscall/signal xcpt/syscall/signal-kill xcpt/syscall/mmap \ xcpt/syscall/sigreturn xcpt/syscall/sigreturn-check \ libc/ctype libc/stdio libc/stdlib/qsort libc/string \ utils/core-id utils/fmt utils/heapq utils/rb diff --git a/lab6/c/include/oscos/mem/vm.h b/lab6/c/include/oscos/mem/vm.h index 3ee64e514..c0841f158 100644 --- a/lab6/c/include/oscos/mem/vm.h +++ b/lab6/c/include/oscos/mem/vm.h @@ -3,6 +3,8 @@ #include "oscos/mem/types.h" #include "oscos/mem/vm/page-table.h" +#include "oscos/uapi/sys/mman.h" +#include "oscos/utils/rb.h" /// \brief Converts a kernel space virtual address into its corresponding /// physical address. @@ -24,10 +26,15 @@ typedef struct { mem_region_type_t type; const void *backing_storage_start; size_t backing_storage_len; + int prot; } mem_region_t; typedef struct { - mem_region_t text_region, stack_region, vc_region; + rb_node_t *root; +} mem_regions_t; + +typedef struct { + mem_regions_t mem_regions; page_table_entry_t *pgd; } vm_addr_space_t; @@ -37,11 +44,19 @@ typedef enum { VM_MAP_PAGE_NOMEM } vm_map_page_result_t; -page_table_entry_t *vm_new_pgd(void); -void vm_clone_pgd(page_table_entry_t *pgd); -void vm_drop_pgd(page_table_entry_t *pgd); +void vm_mem_regions_insert_region(mem_regions_t *regions, + const mem_region_t *region); +const mem_region_t *vm_mem_regions_find_region(const mem_regions_t *regions, + void *va); + +vm_addr_space_t vm_new_addr_space(void); +vm_addr_space_t vm_clone_addr_space(vm_addr_space_t addr_space); +void vm_drop_addr_space(vm_addr_space_t pgd); vm_map_page_result_t vm_map_page(vm_addr_space_t *addr_space, void *va); -vm_map_page_result_t vm_cow(vm_addr_space_t *addr_space, void *va); +vm_map_page_result_t vm_handle_permission_fault(vm_addr_space_t *addr_space, + void *va, int access_mode); void vm_switch_to_addr_space(const vm_addr_space_t *addr_space); +void *vm_decide_mmap_addr(vm_addr_space_t addr_space, void *va, size_t len); + #endif diff --git a/lab6/c/include/oscos/uapi/sys/mman.h b/lab6/c/include/oscos/uapi/sys/mman.h new file mode 100644 index 000000000..6d57130a7 --- /dev/null +++ b/lab6/c/include/oscos/uapi/sys/mman.h @@ -0,0 +1,11 @@ +#ifndef OSCOS_UAPI_SYS_MMAN_H +#define OSCOS_UAPI_SYS_MMAN_H + +#define PROT_NONE 0 +#define PROT_READ 1 +#define PROT_WRITE 2 +#define PROT_EXEC 4 + +#define MAP_FAILED ((void *)-1) + +#endif diff --git a/lab6/c/include/oscos/uapi/sys/syscall.h b/lab6/c/include/oscos/uapi/sys/syscall.h index 910ddcc61..62c10613d 100644 --- a/lab6/c/include/oscos/uapi/sys/syscall.h +++ b/lab6/c/include/oscos/uapi/sys/syscall.h @@ -11,6 +11,7 @@ #define SYS_kill 7 #define SYS_signal 8 #define SYS_signal_kill 9 -#define SYS_sigreturn 10 +#define SYS_mmap 10 +#define SYS_sigreturn 11 #endif diff --git a/lab6/c/include/oscos/utils/rb.h b/lab6/c/include/oscos/utils/rb.h index 85368c0c6..2ae638b36 100644 --- a/lab6/c/include/oscos/utils/rb.h +++ b/lab6/c/include/oscos/utils/rb.h @@ -13,14 +13,28 @@ typedef struct rb_node_t { alignas(16) unsigned char payload[]; } rb_node_t; +rb_node_t *rb_clone(const rb_node_t *root, size_t size, + bool (*cloner)(void *dst, const void *src), + void (*deleter)(void *payload)); + const void *rb_search(const rb_node_t *root, const void *restrict key, int (*compar)(const void *, const void *, void *), void *arg); +const void *rb_predecessor(const rb_node_t *root, const void *restrict key, + int (*compar)(const void *, const void *, void *), + void *arg); + +const void *rb_successor(const rb_node_t *root, const void *restrict key, + int (*compar)(const void *, const void *, void *), + void *arg); + bool rb_insert(rb_node_t **root, size_t size, const void *restrict item, int (*compar)(const void *, const void *, void *), void *arg); void rb_delete(rb_node_t **root, const void *restrict key, int (*compar)(const void *, const void *, void *), void *arg); +void rb_drop(rb_node_t *root, void (*deleter)(void *payload)); + #endif diff --git a/lab6/c/src/mem/vm.c b/lab6/c/src/mem/vm.c index 2f70a5a0a..510eadfff 100644 --- a/lab6/c/src/mem/vm.c +++ b/lab6/c/src/mem/vm.c @@ -2,10 +2,12 @@ #include +#include "oscos/console.h" #include "oscos/libc/string.h" #include "oscos/mem/page-alloc.h" #include "oscos/mem/shared-page.h" #include "oscos/sched.h" +#include "oscos/utils/align.h" #include "oscos/utils/critical-section.h" // Symbol defined in the linker script. @@ -24,7 +26,50 @@ pa_range_t kernel_va_range_to_pa_range(const va_range_t range) { .end = kernel_va_to_pa(range.end)}; } -page_table_entry_t *vm_new_pgd(void) { +static int _vm_cmp_mem_regions_by_start(const mem_region_t *const r1, + const mem_region_t *const r2, + void *const _arg) { + (void)_arg; + + if (r1->start < r2->start) + return -1; + if (r1->start > r2->start) + return 1; + return 0; +} + +static int _vm_cmp_va_and_mem_region(const void *const va, + const mem_region_t *const region, + void *const _arg) { + (void)_arg; + + if (va < region->start) + return -1; + if (va > region->start) + return 1; + return 0; +} + +void vm_mem_regions_insert_region(mem_regions_t *const regions, + const mem_region_t *const region) { + rb_insert( + ®ions->root, sizeof(mem_region_t), region, + (int (*)(const void *, const void *, void *))_vm_cmp_mem_regions_by_start, + NULL); +} + +const mem_region_t * +vm_mem_regions_find_region(const mem_regions_t *const regions, void *const va) { + const mem_region_t *const region = rb_predecessor( + regions->root, va, + (int (*)(const void *, const void *, void *))_vm_cmp_va_and_mem_region, + NULL); + + return region && va < (void *)((char *)region->start + region->len) ? region + : NULL; +} + +static page_table_entry_t *_vm_new_pgd(void) { const spage_id_t new_pgd_page_id = shared_page_alloc(); if (new_pgd_page_id < 0) return NULL; @@ -37,7 +82,11 @@ page_table_entry_t *vm_new_pgd(void) { return result; } -void vm_clone_pgd(page_table_entry_t *const pgd) { +vm_addr_space_t vm_new_addr_space(void) { + return (vm_addr_space_t){.mem_regions = {.root = NULL}, .pgd = _vm_new_pgd()}; +} + +static void _vm_clone_pgd(page_table_entry_t *const pgd) { uint64_t daif_val; CRITICAL_SECTION_ENTER(daif_val); @@ -58,6 +107,18 @@ void vm_clone_pgd(page_table_entry_t *const pgd) { CRITICAL_SECTION_LEAVE(daif_val); } +vm_addr_space_t vm_clone_addr_space(const vm_addr_space_t addr_space) { + rb_node_t *const new_regions_root = + rb_clone(addr_space.mem_regions.root, sizeof(mem_region_t), NULL, NULL); + if (!new_regions_root) + return (vm_addr_space_t){.mem_regions = (mem_regions_t){.root = NULL}}; + + _vm_clone_pgd(addr_space.pgd); + return (vm_addr_space_t){.mem_regions = + (mem_regions_t){.root = new_regions_root}, + .pgd = addr_space.pgd}; +} + static void _vm_drop_page_table(page_table_entry_t *const page_table, const size_t level) { const page_id_t page_table_page_id = @@ -81,7 +142,7 @@ static void _vm_drop_page_table(page_table_entry_t *const page_table, shared_page_decref(page_table_page_id); } -void vm_drop_pgd(page_table_entry_t *const pgd) { +static void _vm_drop_pgd(page_table_entry_t *const pgd) { uint64_t daif_val; CRITICAL_SECTION_ENTER(daif_val); @@ -90,6 +151,11 @@ void vm_drop_pgd(page_table_entry_t *const pgd) { CRITICAL_SECTION_LEAVE(daif_val); } +void vm_drop_addr_space(const vm_addr_space_t addr_space) { + _vm_drop_pgd(addr_space.pgd); + rb_drop(addr_space.mem_regions.root, NULL); +} + static page_table_entry_t * _vm_clone_unshare_page_table(page_table_entry_t *const page_table) { const page_id_t page_table_page_id = @@ -207,14 +273,29 @@ static bool _map_page(const mem_region_t *const mem_region, void *const va, __builtin_unreachable(); } + // Set attributes. + + const bool is_accessible = + mem_region->prot & (PROT_READ | PROT_WRITE | PROT_EXEC); + const bool is_writable = mem_region->prot & PROT_WRITE; + const bool is_executable = mem_region->prot & PROT_EXEC; + + const unsigned ap = ((unsigned)!is_writable << 1) | (unsigned)is_accessible; + pte_entry->b0 = 1; pte_entry->b1 = 1; const union { block_page_descriptor_lower_t s; unsigned u; } lower = {.s = (block_page_descriptor_lower_t){ - .attr_indx = 0x1, .ap = 0x1, .af = 1}}; + .attr_indx = 0x1, .ap = ap, .af = 1}}; pte_entry->lower = lower.u; + const union { + block_page_descriptor_upper_t s; + unsigned u; + } upper = {.s = (block_page_descriptor_upper_t){.pxn = !is_executable, + .uxn = !is_executable}}; + pte_entry->upper = upper.u; return true; } @@ -296,15 +377,9 @@ vm_map_page_result_t vm_map_page(vm_addr_space_t *const addr_space, void *const va) { // Check the validity of the VA. - if (!((addr_space->text_region.start <= va && - va < (void *)((char *)addr_space->text_region.start + - addr_space->text_region.len)) || - (addr_space->stack_region.start <= va && - va < (void *)((char *)addr_space->stack_region.start + - addr_space->stack_region.len)) || - (addr_space->vc_region.start <= va && - va < (void *)((char *)addr_space->vc_region.start + - addr_space->vc_region.len)))) + const mem_region_t *const region = + vm_mem_regions_find_region(&addr_space->mem_regions, va); + if (!region) return VM_MAP_PAGE_SEGV; uint64_t daif_val; @@ -321,20 +396,6 @@ vm_map_page_result_t vm_map_page(vm_addr_space_t *const addr_space, // Map the page. - mem_region_t *const region = - addr_space->text_region.start <= va && - va < (void *)((char *)addr_space->text_region.start + - addr_space->text_region.len) - ? &addr_space->text_region - : addr_space->stack_region.start <= va && - va < (void *)((char *)addr_space->stack_region.start + - addr_space->stack_region.len) - ? &addr_space->stack_region - : addr_space->vc_region.start <= va && - va < (void *)((char *)addr_space->vc_region.start + - addr_space->vc_region.len) - ? &addr_space->vc_region - : (__builtin_unreachable(), NULL); if (!_map_page(region, va, pte_entry)) { CRITICAL_SECTION_LEAVE(daif_val); return VM_MAP_PAGE_NOMEM; @@ -372,16 +433,33 @@ static bool _cow_page(const mem_region_t *const mem_region, __builtin_unreachable(); } + // Set attributes. + + const bool is_accessible = + mem_region->prot & (PROT_READ | PROT_WRITE | PROT_EXEC); + const bool is_writable = mem_region->prot & PROT_WRITE; + const bool is_executable = mem_region->prot & PROT_EXEC; + + const unsigned ap = ((unsigned)!is_writable << 1) | (unsigned)is_accessible; + const union { block_page_descriptor_lower_t s; unsigned u; - } lower = {.s = (block_page_descriptor_lower_t){.ap = 0x1, .af = 1}}; + } lower = {.s = (block_page_descriptor_lower_t){ + .attr_indx = 0x1, .ap = ap, .af = 1}}; pte_entry->lower = lower.u; + const union { + block_page_descriptor_upper_t s; + unsigned u; + } upper = {.s = (block_page_descriptor_upper_t){.pxn = !is_executable, + .uxn = !is_executable}}; + pte_entry->upper = upper.u; return true; } -vm_map_page_result_t vm_cow(vm_addr_space_t *const addr_space, void *const va) { +static vm_map_page_result_t _vm_cow(vm_addr_space_t *const addr_space, + void *const va) { uint64_t daif_val; CRITICAL_SECTION_ENTER(daif_val); @@ -396,20 +474,8 @@ vm_map_page_result_t vm_cow(vm_addr_space_t *const addr_space, void *const va) { // Map the page. - mem_region_t *const region = - addr_space->text_region.start <= va && - va < (void *)((char *)addr_space->text_region.start + - addr_space->text_region.len) - ? &addr_space->text_region - : addr_space->stack_region.start <= va && - va < (void *)((char *)addr_space->stack_region.start + - addr_space->stack_region.len) - ? &addr_space->stack_region - : addr_space->vc_region.start <= va && - va < (void *)((char *)addr_space->vc_region.start + - addr_space->vc_region.len) - ? &addr_space->vc_region - : (__builtin_unreachable(), NULL); + const mem_region_t *const region = + vm_mem_regions_find_region(&addr_space->mem_regions, va); if (!_cow_page(region, pte_entry)) { CRITICAL_SECTION_LEAVE(daif_val); return VM_MAP_PAGE_NOMEM; @@ -423,6 +489,28 @@ vm_map_page_result_t vm_cow(vm_addr_space_t *const addr_space, void *const va) { return VM_MAP_PAGE_SUCCESS; } +vm_map_page_result_t +vm_handle_permission_fault(vm_addr_space_t *const addr_space, void *const va, + const int access_mode) { + const mem_region_t *const region = + vm_mem_regions_find_region(&addr_space->mem_regions, va); + if (access_mode & region->prot) { + return _vm_cow(addr_space, va); + } else { // Illegal access. +#ifdef VM_ENABLE_DEBUG_LOG + if (access_mode & PROT_EXEC) { + console_puts( + "DEBUG: vm: Attempted to execute from a non-executable page"); + } else if (access_mode & PROT_WRITE) { + console_puts("DEBUG: vm: Attempted to write to a non-writable page"); + } else { + console_puts("DEBUG: vm: Attempted to access a non-accessible page"); + } +#endif + return VM_MAP_PAGE_SEGV; + } +} + void vm_switch_to_addr_space(const vm_addr_space_t *const addr_space) { const pa_t pgd_pa = kernel_va_to_pa(addr_space->pgd); __asm__ __volatile__( @@ -435,3 +523,66 @@ void vm_switch_to_addr_space(const vm_addr_space_t *const addr_space) { : "r"((uint64_t)pgd_pa) : "memory"); } + +static void *_vm_find_mmap_addr(const vm_addr_space_t addr_space, + const size_t len) { + void *addr = (void *)(1 << PAGE_ORDER); + const mem_region_t *const first_predecessor = rb_predecessor( + addr_space.mem_regions.root, addr, + (int (*)(const void *, const void *, void *))_vm_cmp_va_and_mem_region, + NULL); + if (first_predecessor && + addr < (void *)((char *)first_predecessor->start + + first_predecessor + ->len)) { // The initial address (0x1000) is taken. + addr = (char *)first_predecessor->start + first_predecessor->len; + } + + while (addr < (void *)(0x1000000000000ULL - len)) { + const mem_region_t *const begin_successor = rb_successor( + addr_space.mem_regions.root, addr, + (int (*)(const void *, const void *, void *))_vm_cmp_va_and_mem_region, + NULL); + if (!begin_successor || + (void *)((char *)addr + len) <= begin_successor->start) { + // Found a large enough space. + return addr; + } + addr = (char *)begin_successor->start + begin_successor->len; + } + + return NULL; +} + +void *vm_decide_mmap_addr(const vm_addr_space_t addr_space, void *const va, + const size_t len) { + const size_t effective_len = ALIGN(len, 1 << PAGE_ORDER); + + // Check for exact fit. + + const bool va_is_page_aligned = + ((uintptr_t)va & ((1 << PAGE_ORDER) - 1)) == 0; + if (va && va_is_page_aligned) { + const mem_region_t *const begin_predecessor = rb_predecessor( + addr_space.mem_regions.root, va, + (int (*)(const void *, const void *, void *))_vm_cmp_va_and_mem_region, + NULL); + if (!begin_predecessor || + (void *)((char *)begin_predecessor->start + begin_predecessor->len) <= + va) { // The predecessor doesn't cover the start address. + const mem_region_t *const end_predecessor = rb_predecessor( + addr_space.mem_regions.root, (char *)va + effective_len - 1, + (int (*)(const void *, const void *, + void *))_vm_cmp_va_and_mem_region, + NULL); + if (end_predecessor == + begin_predecessor) { // There is nothing between the start and end + // addresses. + // Exact fit is possible. + return va; + } + } + } + + return _vm_find_mmap_addr(addr_space, effective_len); +} diff --git a/lab6/c/src/sched/sched.c b/lab6/c/src/sched/sched.c index 078c80425..0ee8cbb27 100644 --- a/lab6/c/src/sched/sched.c +++ b/lab6/c/src/sched/sched.c @@ -190,6 +190,10 @@ void thread_exit(void) { XCPT_MASK_ALL(); + // Workaround of a weird bug where the current thread still remains on the run + // queue. + _remove_thread_from_queue(curr_thread); + if (curr_process) { rb_delete(&_processes, &curr_process->id, (int (*)(const void *, const void *, @@ -222,8 +226,8 @@ bool process_create(void) { return false; } - page_table_entry_t *const pgd = vm_new_pgd(); - if (!pgd) { + vm_addr_space_t addr_space = vm_new_addr_space(); + if (!addr_space.pgd) { free(fp_simd_ctx); free(process); return false; @@ -234,18 +238,24 @@ bool process_create(void) { thread_t *const curr_thread = current_thread(); process->id = _alloc_pid(); - process->addr_space.stack_region = - (mem_region_t){.start = (void *)0xffffffffb000ULL, - .len = 4 << PAGE_ORDER, - .type = MEM_REGION_BACKED, - .backing_storage_start = NULL, - .backing_storage_len = 0}; - process->addr_space.vc_region = - (mem_region_t){.start = (void *)0x3b400000ULL, - .len = 0x3f000000ULL - 0x3b400000ULL, - .type = MEM_REGION_LINEAR, - .backing_storage_start = (void *)0x3b400000ULL}; - process->addr_space.pgd = pgd; + process->addr_space = addr_space; + + const mem_region_t stack_region = {.start = (void *)0xffffffffb000ULL, + .len = 4 << PAGE_ORDER, + .type = MEM_REGION_BACKED, + .backing_storage_start = NULL, + .backing_storage_len = 0, + .prot = PROT_READ | PROT_WRITE}; + vm_mem_regions_insert_region(&process->addr_space.mem_regions, &stack_region); + + const mem_region_t vc_region = {.start = (void *)0x3b400000ULL, + .len = 0x3f000000ULL - 0x3b400000ULL, + .type = MEM_REGION_LINEAR, + .backing_storage_start = + (void *)0x3b400000ULL, + .prot = PROT_READ | PROT_WRITE}; + vm_mem_regions_insert_region(&process->addr_space.mem_regions, &vc_region); + process->main_thread = curr_thread; process->pending_signals = 0; process->blocked_signals = 0; @@ -282,34 +292,38 @@ static void _exec_generic(const void *const text_start, const size_t text_len, // Allocate memory. - page_table_entry_t *pgd = NULL; + vm_addr_space_t addr_space = {.pgd = NULL}; if (free_old_pages) { - pgd = vm_new_pgd(); - if (!pgd) + addr_space = vm_new_addr_space(); + if (!addr_space.pgd) return; } // Free old pages. if (free_old_pages) { - vm_drop_pgd(curr_process->addr_space.pgd); + vm_drop_addr_space(curr_process->addr_space); } // Set process data. if (free_old_pages) { - curr_process->addr_space.pgd = pgd; + curr_process->addr_space = addr_space; } + // We don't know exactly how large the .bss section of a user program is, so // we have to use a heuristic. We speculate that the .bss section is at most // as large as the remaining parts (.text, .rodata, and .data sections) of the // user program. - curr_process->addr_space.text_region = - (mem_region_t){.start = (void *)0x0, - .len = ALIGN(text_len * 2, 4096), - .type = MEM_REGION_BACKED, - .backing_storage_start = text_start, - .backing_storage_len = text_len}; + + const mem_region_t text_region = {.start = (void *)0x0, + .len = ALIGN(text_len * 2, 4096), + .type = MEM_REGION_BACKED, + .backing_storage_start = text_start, + .backing_storage_len = text_len, + .prot = PROT_READ | PROT_WRITE | PROT_EXEC}; + vm_mem_regions_insert_region(&curr_process->addr_space.mem_regions, + &text_region); // Run the user program. @@ -330,7 +344,7 @@ void exec(const void *const text_start, const size_t text_len) { static void _thread_cleanup(thread_t *const thread) { if (thread->process) { - vm_drop_pgd(thread->process->addr_space.pgd); + vm_drop_addr_space(thread->process->addr_space); free(thread->process); } free_pages(thread->stack_page_id); @@ -369,6 +383,16 @@ process_t *fork(const extended_trap_frame_t *const trap_frame) { return NULL; } + vm_addr_space_t addr_space = vm_clone_addr_space(curr_process->addr_space); + if (curr_process->addr_space.mem_regions.root && + !addr_space.mem_regions.root) { // Out of memory. + free(fp_simd_ctx); + free_pages(kernel_stack_page_id); + free(new_process); + free(new_thread); + return NULL; + } + // Set data. new_thread->id = _alloc_tid(); @@ -380,9 +404,7 @@ process_t *fork(const extended_trap_frame_t *const trap_frame) { new_thread->process = new_process; new_process->id = _alloc_pid(); - vm_clone_pgd(curr_process->addr_space.pgd); - vm_switch_to_addr_space(&curr_process->addr_space); - new_process->addr_space = curr_process->addr_space; + new_process->addr_space = addr_space; new_process->main_thread = new_thread; new_process->pending_signals = 0; new_process->blocked_signals = 0; diff --git a/lab6/c/src/sched/sig-handler-main.S b/lab6/c/src/sched/sig-handler-main.S index 9c3f530de..c9541fa42 100644 --- a/lab6/c/src/sched/sig-handler-main.S +++ b/lab6/c/src/sched/sig-handler-main.S @@ -2,7 +2,7 @@ sig_handler_main: blr x0 - mov x8, 10 // SYS_sigreturn + mov x8, 11 // SYS_sigreturn svc 0 .type sig_handler_main, function diff --git a/lab6/c/src/utils/rb.c b/lab6/c/src/utils/rb.c index 391fbbb6e..1587619e7 100644 --- a/lab6/c/src/utils/rb.c +++ b/lab6/c/src/utils/rb.c @@ -5,6 +5,41 @@ // TODO: Properly implement a red-black tree. +rb_node_t *rb_clone(const rb_node_t *const root, const size_t size, + bool (*const cloner)(void *dst, const void *src), + void (*const deleter)(void *payload)) { + if (!root) + return NULL; + + rb_node_t *const new_root = malloc(sizeof(rb_node_t) + size); + if (!new_root) + return NULL; + + for (size_t i = 0; i < 2; i++) { + new_root->children[i] = rb_clone(root->children[i], size, cloner, deleter); + if (root->children[i] && !new_root->children[i]) { // Out of memory. + for (size_t j = 0; j < i; j++) { + rb_drop(new_root->children[j], deleter); + } + return NULL; + } + } + + if (cloner) { + const bool clone_successful = cloner(new_root->payload, root->payload); + if (!clone_successful) { + for (size_t i = 0; i < 2; i++) { + rb_drop(new_root->children[i], deleter); + } + return NULL; + } + } else { + memcpy(new_root->payload, root->payload, size); + } + + return new_root; +} + const void *rb_search(const rb_node_t *const root, const void *const restrict key, int (*const compar)(const void *, const void *, void *), @@ -20,6 +55,50 @@ const void *rb_search(const rb_node_t *const root, return curr ? curr->payload : NULL; } +const void * +rb_predecessor(const rb_node_t *const root, const void *const restrict key, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + const rb_node_t *curr = root, *result = NULL; + while (curr) { + const int compar_result = compar(key, curr->payload, arg); + + if (compar_result >= 0) { + result = curr; + } + + if (compar_result == 0) { + break; + } + + curr = curr->children[compar_result > 0]; + } + + return result ? result->payload : NULL; +} + +const void * +rb_successor(const rb_node_t *const root, const void *const restrict key, + int (*const compar)(const void *, const void *, void *), + void *const arg) { + const rb_node_t *curr = root, *result = NULL; + while (curr) { + const int compar_result = compar(key, curr->payload, arg); + + if (compar_result <= 0) { + result = curr; + } + + if (compar_result == 0) { + break; + } + + curr = curr->children[compar_result > 0]; + } + + return result ? result->payload : NULL; +} + bool rb_insert(rb_node_t **const root, const size_t size, const void *const restrict item, int (*const compar)(const void *, const void *, void *), @@ -81,3 +160,17 @@ void rb_delete(rb_node_t **const root, const void *const restrict key, free(curr_node); } + +void rb_drop(rb_node_t *root, void (*deleter)(void *payload)) { + if (!root) + return; + + if (deleter) { + deleter(root->payload); + } + + for (size_t i = 0; i < 2; i++) { + rb_drop(root->children[i], deleter); + } + free(root); +} diff --git a/lab6/c/src/xcpt/data-abort-handler.c b/lab6/c/src/xcpt/data-abort-handler.c index cb0a9765d..3cb754079 100644 --- a/lab6/c/src/xcpt/data-abort-handler.c +++ b/lab6/c/src/xcpt/data-abort-handler.c @@ -32,8 +32,9 @@ void xcpt_data_abort_handler(const uint64_t esr_val) { #endif } } else if (dfsc >> 2 == 0x3) { // Permission fault. - const vm_map_page_result_t result = - vm_cow(&curr_process->addr_space, fault_addr); + const bool wnr = esr_val & (1 << 6); + const vm_map_page_result_t result = vm_handle_permission_fault( + &curr_process->addr_space, fault_addr, wnr ? PROT_WRITE : PROT_READ); if (result == VM_MAP_PAGE_SEGV) { #ifdef VM_ENABLE_DEBUG_LOG console_printf("DEBUG: vm: Segmentation fault, PID %zu, address 0x%p\n", diff --git a/lab6/c/src/xcpt/insn-abort-handler.c b/lab6/c/src/xcpt/insn-abort-handler.c index d70ff4b7a..dd4ec645f 100644 --- a/lab6/c/src/xcpt/insn-abort-handler.c +++ b/lab6/c/src/xcpt/insn-abort-handler.c @@ -32,8 +32,8 @@ void xcpt_insn_abort_handler(const uint64_t esr_val) { #endif } } else if (ifsc >> 2 == 0x3) { // Permission fault. - const vm_map_page_result_t result = - vm_cow(&curr_process->addr_space, fault_addr); + const vm_map_page_result_t result = vm_handle_permission_fault( + &curr_process->addr_space, fault_addr, PROT_EXEC); if (result == VM_MAP_PAGE_SEGV) { #ifdef VM_ENABLE_DEBUG_LOG console_printf("DEBUG: vm: Segmentation fault, PID %zu, address 0x%p\n", diff --git a/lab6/c/src/xcpt/syscall-table.S b/lab6/c/src/xcpt/syscall-table.S index 396f9be11..e96b36ead 100644 --- a/lab6/c/src/xcpt/syscall-table.S +++ b/lab6/c/src/xcpt/syscall-table.S @@ -11,6 +11,7 @@ syscall_table: b sys_kill b sys_signal b sys_signal_kill + b sys_mmap b sys_sigreturn .size syscall_table, . - syscall_table diff --git a/lab6/c/src/xcpt/syscall/mmap.c b/lab6/c/src/xcpt/syscall/mmap.c new file mode 100644 index 000000000..d887be094 --- /dev/null +++ b/lab6/c/src/xcpt/syscall/mmap.c @@ -0,0 +1,33 @@ +#include + +#include "oscos/mem/page-alloc.h" +#include "oscos/mem/vm.h" +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" +#include "oscos/utils/align.h" + +void *sys_mmap(void *const addr, const size_t len, const int prot, + const int flags, const int fd, const int file_offset) { + (void)flags; + (void)fd; + (void)file_offset; + + process_t *const curr_process = current_thread()->process; + + void *const mmap_addr = + vm_decide_mmap_addr(curr_process->addr_space, addr, len); + if (!mmap_addr) { + return (void *)-EINVAL; + } + + const mem_region_t mem_region = {.start = mmap_addr, + .len = ALIGN(len, 1 << PAGE_ORDER), + .type = MEM_REGION_BACKED, + .backing_storage_start = NULL, + .backing_storage_len = 0, + .prot = prot}; + vm_mem_regions_insert_region(&curr_process->addr_space.mem_regions, + &mem_region); + + return mmap_addr; +} diff --git a/lab6/tests/user-program/libc/Makefile b/lab6/tests/user-program/libc/Makefile index f21080034..b0300d176 100644 --- a/lab6/tests/user-program/libc/Makefile +++ b/lab6/tests/user-program/libc/Makefile @@ -16,7 +16,7 @@ CFLAGS_BASE = -std=c17 -pedantic-errors -Wall -Wextra -ffreestanding \ CFLAGS_DEBUG = -g CFLAGS_RELEASE = -O3 -flto -OBJS = start ctype errno mbox signal stdio stdlib string unistd \ +OBJS = start ctype errno mbox signal stdio stdlib string sys/mman unistd \ unistd/syscall __detail/utils/fmt # ------------------------------------------------------------------------------ diff --git a/lab6/tests/user-program/libc/include/sys/mman.h b/lab6/tests/user-program/libc/include/sys/mman.h new file mode 100644 index 000000000..527474c2e --- /dev/null +++ b/lab6/tests/user-program/libc/include/sys/mman.h @@ -0,0 +1,13 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_SYS_MMAN_H +#define OSCOS_USER_PROGRAM_LIBC_SYS_MMAN_H + +#include + +#include "oscos-uapi/sys/mman.h" + +typedef int off_t; + +void *mmap(void *addr, size_t length, int prot, int flags, int fd, + off_t offset); + +#endif diff --git a/lab6/tests/user-program/libc/src/sys/mman.c b/lab6/tests/user-program/libc/src/sys/mman.c new file mode 100644 index 000000000..ab3e7faa5 --- /dev/null +++ b/lab6/tests/user-program/libc/src/sys/mman.c @@ -0,0 +1,9 @@ +#include "sys/mman.h" + +#include "sys/syscall.h" +#include "unistd.h" + +void *mmap(void *const addr, const size_t length, const int prot, + const int flags, const int fd, const off_t offset) { + return (void *)syscall(SYS_mmap, addr, length, prot, flags, fd, offset); +} From afe1588d848e5967dd136db065f7abdfab7f3790 Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Fri, 30 Jun 2023 23:28:01 +0800 Subject: [PATCH 23/34] Lab 6 C: Fix `exec` destroying memory regions Other changes: - Fix a bug in `_vm_drop_page_table` (called by `vm_drop_addr_space`) that causes memory leak by not freeing pages. --- lab6/c/include/oscos/mem/vm.h | 1 + lab6/c/src/mem/vm.c | 109 +++++++++++++++++++++++++++++++++- lab6/c/src/sched/sched.c | 23 ++----- 3 files changed, 115 insertions(+), 18 deletions(-) diff --git a/lab6/c/include/oscos/mem/vm.h b/lab6/c/include/oscos/mem/vm.h index c0841f158..801329a90 100644 --- a/lab6/c/include/oscos/mem/vm.h +++ b/lab6/c/include/oscos/mem/vm.h @@ -55,6 +55,7 @@ void vm_drop_addr_space(vm_addr_space_t pgd); vm_map_page_result_t vm_map_page(vm_addr_space_t *addr_space, void *va); vm_map_page_result_t vm_handle_permission_fault(vm_addr_space_t *addr_space, void *va, int access_mode); +bool vm_remove_region(vm_addr_space_t *addr_space, void *start_va); void vm_switch_to_addr_space(const vm_addr_space_t *addr_space); void *vm_decide_mmap_addr(vm_addr_space_t addr_space, void *va, size_t len); diff --git a/lab6/c/src/mem/vm.c b/lab6/c/src/mem/vm.c index 510eadfff..6d18e7648 100644 --- a/lab6/c/src/mem/vm.c +++ b/lab6/c/src/mem/vm.c @@ -128,7 +128,7 @@ static void _vm_drop_page_table(page_table_entry_t *const page_table, for (size_t i = 0; i < 512; i++) { if (page_table[i].b0) { const pa_t next_level_pa = page_table[i].addr << PAGE_ORDER; - if (level == 1) { + if (level == 0) { shared_page_decref(pa_to_page_id(next_level_pa)); } else { page_table_entry_t *const next_level_page_table = @@ -511,6 +511,113 @@ vm_handle_permission_fault(vm_addr_space_t *const addr_space, void *const va, } } +static page_table_entry_t * +_vm_remove_region_from_pgd_rec(page_table_entry_t *const page_table, + void *const start_va, void *const end_va, + size_t level, void *const block_start, + page_table_entry_t *const prev_level_entry) { + void *const block_end = (char *)block_start + (1 << (9 * level + PAGE_ORDER)); + + if (start_va == block_start && end_va == block_end) { + _vm_drop_page_table(page_table, level); + if (prev_level_entry) { + prev_level_entry->b0 = false; + } + return NULL; + } else { + page_table_entry_t *const new_page_table = + level == 0 ? _vm_clone_unshare_pte(page_table) + : _vm_clone_unshare_page_table(page_table); + if (!new_page_table) + return NULL; + + if (prev_level_entry) { + prev_level_entry->addr = kernel_va_to_pa(new_page_table) >> PAGE_ORDER; + + // Since the page table is not shared, we can remove the read-only bit + // now. + + union { + table_descriptor_upper_t s; + unsigned u; + } upper = {.u = prev_level_entry->upper}; + upper.s.aptable = 0x0; + prev_level_entry->upper = upper.u; + } + + size_t subblock_stride = 1 << (9 * (level - 1) + PAGE_ORDER); + + for (size_t i = 0; i < 512; i++) { + if (new_page_table[i].b0) { + void *const subblock_start = (char *)block_start + i * subblock_stride, + *const subblock_end = + (char *)subblock_start + subblock_stride; + + void *const max_start = + start_va > subblock_start ? start_va : subblock_start, + *const min_end = + end_va < subblock_end ? end_va : subblock_end; + + if (max_start < min_end) { + if (level == 0) { + const page_id_t page_id = + pa_to_page_id(new_page_table[i].addr << PAGE_ORDER); + shared_page_decref(page_id); + } else { + page_table_entry_t *const next_level_page_table = + pa_to_kernel_va(new_page_table[i].addr << PAGE_ORDER); + _vm_remove_region_from_pgd_rec(next_level_page_table, max_start, + min_end, level - 1, subblock_start, + &new_page_table[i]); + } + } + } + } + + return new_page_table; + } +} + +static page_table_entry_t * +_vm_remove_region_from_pgd(page_table_entry_t *const pgd, void *const start_va, + void *const end_va) { + return _vm_remove_region_from_pgd_rec(pgd, start_va, end_va, 3, (void *)0, + NULL); +} + +bool vm_remove_region(vm_addr_space_t *const addr_space, void *const start_va) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const mem_region_t *const mem_region = rb_search( + addr_space->mem_regions.root, start_va, + (int (*)(const void *, const void *, void *))_vm_cmp_va_and_mem_region, + NULL); + void *const end_va = (char *)start_va + mem_region->len; + + rb_delete( + &addr_space->mem_regions.root, start_va, + (int (*)(const void *, const void *, void *))_vm_cmp_va_and_mem_region, + NULL); + + page_table_entry_t *const new_pgd = + _vm_remove_region_from_pgd(addr_space->pgd, start_va, end_va); + // Note: `_vm_remove_region_from_pgd` returns NULL when the last region is + // removed, i.e., a new page table need not be allocated. However, in all + // places where `vm_remove_region` is used, the region to be removed is never + // the last one. Therefore, checking for an out-of-memory condition in this + // way is fine. + if (!new_pgd) { + CRITICAL_SECTION_LEAVE(daif_val); + return false; + } + + addr_space->pgd = new_pgd; + + CRITICAL_SECTION_LEAVE(daif_val); + return true; +} + void vm_switch_to_addr_space(const vm_addr_space_t *const addr_space) { const pa_t pgd_pa = kernel_va_to_pa(addr_space->pgd); __asm__ __volatile__( diff --git a/lab6/c/src/sched/sched.c b/lab6/c/src/sched/sched.c index 0ee8cbb27..42ec4c02a 100644 --- a/lab6/c/src/sched/sched.c +++ b/lab6/c/src/sched/sched.c @@ -286,29 +286,18 @@ void switch_vm(const thread_t *const thread) { } static void _exec_generic(const void *const text_start, const size_t text_len, - const bool free_old_pages) { + const bool remove_text_region) { thread_t *const curr_thread = current_thread(); process_t *const curr_process = curr_thread->process; - // Allocate memory. + // Remove old text region. - vm_addr_space_t addr_space = {.pgd = NULL}; - if (free_old_pages) { - addr_space = vm_new_addr_space(); - if (!addr_space.pgd) + if (remove_text_region) { + if (!vm_remove_region(&curr_process->addr_space, (void *)0x0)) return; - } - - // Free old pages. - - if (free_old_pages) { - vm_drop_addr_space(curr_process->addr_space); - } - - // Set process data. - if (free_old_pages) { - curr_process->addr_space = addr_space; + // Set ttbr0_el1 again, as it might have changed. + vm_switch_to_addr_space(&curr_process->addr_space); } // We don't know exactly how large the .bss section of a user program is, so From c5d8f934e3433024b5e3d5a0aa6e2fb8ec6fbf65 Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Sat, 1 Jul 2023 01:02:19 +0800 Subject: [PATCH 24/34] Lab 5 C: Fix run queue corruption `deliver_signal` depends on the invariant that, if the `is_waiting` status bit is set on a thread, then that thread must be in a wait queue. However, when waking up a thread from a wait queue, the `is_waiting` status bit was not correctly cleared, causing `deliver_signal` to corrupt the run queue. --- lab5/c/include/oscos/sched.h | 4 ++-- lab5/c/src/sched/sched.c | 3 ++- lab5/c/src/xcpt/syscall/uart-read.c | 4 ++-- lab5/c/src/xcpt/syscall/uart-write.c | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lab5/c/include/oscos/sched.h b/lab5/c/include/oscos/sched.h index bd48e328c..2e92b5284 100644 --- a/lab5/c/include/oscos/sched.h +++ b/lab5/c/include/oscos/sched.h @@ -140,8 +140,8 @@ void schedule(void); /// scheduler. void suspend_to_wait_queue(thread_list_node_t *wait_queue); -/// \brief Adds every thread in the given wait queue to the run queue. -void add_all_threads_to_run_queue(thread_list_node_t *wait_queue); +/// \brief Wake up every thread in the given wait queue. +void wake_up_all_threads_in_wait_queue(thread_list_node_t *wait_queue); /// \brief Gets a process by its PID. process_t *get_process_by_id(size_t pid); diff --git a/lab5/c/src/sched/sched.c b/lab5/c/src/sched/sched.c index ebcf79e27..3f937739e 100644 --- a/lab5/c/src/sched/sched.c +++ b/lab5/c/src/sched/sched.c @@ -542,9 +542,10 @@ void suspend_to_wait_queue(thread_list_node_t *const wait_queue) { _suspend_to_wait_queue(wait_queue); } -void add_all_threads_to_run_queue(thread_list_node_t *const wait_queue) { +void wake_up_all_threads_in_wait_queue(thread_list_node_t *const wait_queue) { thread_t *thread; while ((thread = _remove_first_thread_from_queue(wait_queue))) { + thread->status.is_waiting = false; _add_thread_to_run_queue(thread); } } diff --git a/lab5/c/src/xcpt/syscall/uart-read.c b/lab5/c/src/xcpt/syscall/uart-read.c index e4646bc77..12e00d39c 100644 --- a/lab5/c/src/xcpt/syscall/uart-read.c +++ b/lab5/c/src/xcpt/syscall/uart-read.c @@ -28,8 +28,8 @@ ssize_t sys_uart_read(char buf[const], const size_t size) { thread_t *const curr_thread = current_thread(); - console_notify_read_ready((void (*)(void *))add_all_threads_to_run_queue, - &_wait_queue); + console_notify_read_ready( + (void (*)(void *))wake_up_all_threads_in_wait_queue, &_wait_queue); suspend_to_wait_queue(&_wait_queue); XCPT_MASK_ALL(); diff --git a/lab5/c/src/xcpt/syscall/uart-write.c b/lab5/c/src/xcpt/syscall/uart-write.c index aa23d2722..d612ea85e 100644 --- a/lab5/c/src/xcpt/syscall/uart-write.c +++ b/lab5/c/src/xcpt/syscall/uart-write.c @@ -27,8 +27,8 @@ size_t sys_uart_write(const char buf[const], const size_t size) { thread_t *const curr_thread = current_thread(); - console_notify_write_ready((void (*)(void *))add_all_threads_to_run_queue, - &_wait_queue); + console_notify_write_ready( + (void (*)(void *))wake_up_all_threads_in_wait_queue, &_wait_queue); suspend_to_wait_queue(&_wait_queue); XCPT_MASK_ALL(); From 05ac4e96e0028acb35bae03d22c0a702b7e0198c Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Sat, 1 Jul 2023 23:04:31 +0800 Subject: [PATCH 25/34] Lab 6 C: Fix `shared_page_decref` `shared_page_decref` used to dereference null pointers when called on a non-shared page. --- lab6/c/src/mem/shared-page.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/lab6/c/src/mem/shared-page.c b/lab6/c/src/mem/shared-page.c index db4a86e3f..ef47a871f 100644 --- a/lab6/c/src/mem/shared-page.c +++ b/lab6/c/src/mem/shared-page.c @@ -109,14 +109,19 @@ void shared_page_decref(const page_id_t page) { (int (*)(const void *, const void *, void *))_cmp_page_id_and_page_refcnt_entry, NULL); - entry->refcnt--; - - if (entry->refcnt == 0) { - rb_delete(&_page_refcnts, &page, - (int (*)(const void *, const void *, - void *))_cmp_page_id_and_page_refcnt_entry, - NULL); - free_pages(page); + + // This function is sometimes called on a non-shared page; more specifically, + // linearly-mapped pages. + if (entry) { + entry->refcnt--; + + if (entry->refcnt == 0) { + rb_delete(&_page_refcnts, &page, + (int (*)(const void *, const void *, + void *))_cmp_page_id_and_page_refcnt_entry, + NULL); + free_pages(page); + } } CRITICAL_SECTION_LEAVE(daif_val); From 1e6d2df78f307f621188c19f11b726402c5f5c24 Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Mon, 10 Jul 2023 16:39:25 +0800 Subject: [PATCH 26/34] Lab 7 C: Complete A1 --- lab7/c/Makefile | 3 +- lab7/c/include/oscos/console-dev.h | 8 + lab7/c/include/oscos/console-suspend.h | 11 ++ lab7/c/include/oscos/fs/vfs.h | 9 ++ lab7/c/include/oscos/uapi/errno.h | 1 + lab7/c/src/console-dev.c | 125 +++++++++++++++ lab7/c/src/console-suspend.c | 84 ++++++++++ lab7/c/src/fs/vfs.c | 207 ++++++++++++++++++++++++- lab7/c/src/main.c | 11 ++ lab7/c/src/sched/sched.c | 67 +++++++- lab7/c/src/xcpt/syscall/open.c | 2 +- lab7/c/src/xcpt/syscall/uart-read.c | 45 +----- lab7/c/src/xcpt/syscall/uart-write.c | 44 +----- 13 files changed, 526 insertions(+), 91 deletions(-) create mode 100644 lab7/c/include/oscos/console-dev.h create mode 100644 lab7/c/include/oscos/console-suspend.h create mode 100644 lab7/c/src/console-dev.c create mode 100644 lab7/c/src/console-suspend.c diff --git a/lab7/c/Makefile b/lab7/c/Makefile index fa6f7fc59..74b10591d 100644 --- a/lab7/c/Makefile +++ b/lab7/c/Makefile @@ -21,7 +21,8 @@ LDFLAGS_RELEASE = -flto -Xlinker --gc-sections LDLIBS_BASE = -lgcc -OBJS = start main console devicetree initrd panic shell \ +OBJS = start main console console-dev console-suspend devicetree initrd \ + panic shell \ drivers/aux drivers/gpio drivers/l1ic drivers/l2ic drivers/mailbox \ drivers/mini-uart drivers/pm \ fs/initramfs fs/tmpfs fs/vfs \ diff --git a/lab7/c/include/oscos/console-dev.h b/lab7/c/include/oscos/console-dev.h new file mode 100644 index 000000000..bf048079f --- /dev/null +++ b/lab7/c/include/oscos/console-dev.h @@ -0,0 +1,8 @@ +#ifndef OSCOS_CONSOLE_DEV_H +#define OSCOS_CONSOLE_DEV_H + +#include "oscos/fs/vfs.h" + +extern struct device console_dev; + +#endif diff --git a/lab7/c/include/oscos/console-suspend.h b/lab7/c/include/oscos/console-suspend.h new file mode 100644 index 000000000..c667cd49f --- /dev/null +++ b/lab7/c/include/oscos/console-suspend.h @@ -0,0 +1,11 @@ +#ifndef OSCOS_CONSOLE_SUSPEND_H +#define OSCOS_CONSOLE_SUSPEND_H + +#include + +#include "oscos/uapi/unistd.h" + +ssize_t console_write_suspend(const char *buf, size_t size); +ssize_t console_read_suspend(char *buf, size_t size); + +#endif diff --git a/lab7/c/include/oscos/fs/vfs.h b/lab7/c/include/oscos/fs/vfs.h index 4af926ce2..2c87ab853 100644 --- a/lab7/c/include/oscos/fs/vfs.h +++ b/lab7/c/include/oscos/fs/vfs.h @@ -30,6 +30,11 @@ struct filesystem { int (*setup_mount)(struct filesystem *fs, struct mount *mount); }; +struct device { + const char *name; + int (*setup_mount)(struct device *dev, struct vnode *vnode); +}; + struct file_operations { int (*write)(struct file *file, const void *buf, size_t len); int (*read)(struct file *file, void *buf, size_t len); @@ -50,6 +55,8 @@ struct vnode_operations { extern struct mount rootfs; int register_filesystem(struct filesystem *fs); +int register_device(struct device *dev); + int vfs_open(const char *pathname, int flags, struct file **target); int vfs_open_relative(struct vnode *cwd, const char *pathname, int flags, struct file **target); @@ -66,6 +73,8 @@ int vfs_lookup(const char *pathname, struct vnode **target); int vfs_lookup_relative(struct vnode *cwd, const char *pathname, struct vnode **target); +int vfs_mknod(const char *target, const char *device); + typedef struct { struct file *file; size_t refcnt; diff --git a/lab7/c/include/oscos/uapi/errno.h b/lab7/c/include/oscos/uapi/errno.h index d0b2e9a5d..6cc28ab2a 100644 --- a/lab7/c/include/oscos/uapi/errno.h +++ b/lab7/c/include/oscos/uapi/errno.h @@ -12,6 +12,7 @@ #define EINVAL 22 #define EMFILE 24 #define EFBIG 27 +#define ESPIPE 29 #define EROFS 30 #define ENOSYS 38 #define ELOOP 40 diff --git a/lab7/c/src/console-dev.c b/lab7/c/src/console-dev.c new file mode 100644 index 000000000..014eeae78 --- /dev/null +++ b/lab7/c/src/console-dev.c @@ -0,0 +1,125 @@ +#include "oscos/console-dev.h" + +#include "oscos/console-suspend.h" +#include "oscos/libc/string.h" +#include "oscos/mem/malloc.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/mem/vm.h" +#include "oscos/uapi/errno.h" +#include "oscos/uapi/stdio.h" +#include "oscos/utils/critical-section.h" +#include "oscos/utils/rb.h" + +static int _console_dev_setup_mount(struct device *dev, struct vnode *vnode); + +static int _console_dev_write(struct file *file, const void *buf, size_t len); +static int _console_dev_read(struct file *file, void *buf, size_t len); +static int _console_dev_open(struct vnode *file_node, struct file **target); +static int _console_dev_close(struct file *file); +static long _console_dev_lseek64(struct file *file, long offset, int whence); + +static int _console_dev_lookup(struct vnode *dir_node, struct vnode **target, + const char *component_name); +static int _console_dev_create(struct vnode *dir_node, struct vnode **target, + const char *component_name); +static int _console_dev_mkdir(struct vnode *dir_node, struct vnode **target, + const char *component_name); + +struct device console_dev = {.name = "console", + .setup_mount = _console_dev_setup_mount}; + +static struct file_operations _console_dev_file_operations = { + .write = _console_dev_write, + .read = _console_dev_read, + .open = _console_dev_open, + .close = _console_dev_close, + .lseek64 = _console_dev_lseek64}; + +static struct vnode_operations _console_dev_vnode_operations = { + .lookup = _console_dev_lookup, + .create = _console_dev_create, + .mkdir = _console_dev_mkdir}; + +static int _console_dev_setup_mount(struct device *const dev, + struct vnode *const vnode) { + (void)dev; + + *vnode = (struct vnode){.mount = vnode->mount, + .v_ops = &_console_dev_vnode_operations, + .f_ops = &_console_dev_file_operations, + .internal = NULL}; + return 0; +} + +static int _console_dev_write(struct file *const file, const void *const buf, + const size_t len) { + (void)file; + + return console_write_suspend(buf, len); +} + +static int _console_dev_read(struct file *const file, void *const buf, + const size_t len) { + (void)file; + + return console_read_suspend(buf, len); +} + +static int _console_dev_open(struct vnode *const file_node, + struct file **const target) { + struct file *const file_handle = malloc(sizeof(struct file)); + if (!file_handle) + return -ENOMEM; + + *file_handle = (struct file){.vnode = file_node, + .f_pos = 0, + .f_ops = &_console_dev_file_operations, + .flags = 0}; + *target = file_handle; + + return 0; +} + +static int _console_dev_close(struct file *const file) { + free(file); + return 0; +} + +static long _console_dev_lseek64(struct file *const file, const long offset, + const int whence) { + (void)file; + (void)offset; + (void)whence; + + return -ESPIPE; +} + +static int _console_dev_lookup(struct vnode *const dir_node, + struct vnode **const target, + const char *const component_name) { + (void)dir_node; + (void)target; + (void)component_name; + + return -ENOTDIR; +} + +static int _console_dev_create(struct vnode *const dir_node, + struct vnode **const target, + const char *const component_name) { + (void)dir_node; + (void)target; + (void)component_name; + + return -ENOTDIR; +} + +static int _console_dev_mkdir(struct vnode *const dir_node, + struct vnode **const target, + const char *const component_name) { + (void)dir_node; + (void)target; + (void)component_name; + + return -ENOTDIR; +} diff --git a/lab7/c/src/console-suspend.c b/lab7/c/src/console-suspend.c new file mode 100644 index 000000000..44be5c869 --- /dev/null +++ b/lab7/c/src/console-suspend.c @@ -0,0 +1,84 @@ +#include "oscos/console-suspend.h" + +#include "oscos/console.h" +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" +#include "oscos/utils/critical-section.h" + +static thread_list_node_t _read_wait_queue = {.prev = &_read_wait_queue, + .next = &_read_wait_queue}, + _write_wait_queue = {.prev = &_write_wait_queue, + .next = &_write_wait_queue}; + +ssize_t console_write_suspend(const char *const buf, const size_t size) { + size_t n_chars_written; + + for (;;) { + // We must enter critical section here. Otherwise, there will be a race + // condition between thread suspension and write readiness notification, + // whose callback adds the current thread to the run queue. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + n_chars_written = console_write_nonblock(buf, size); + if (n_chars_written != 0) { + CRITICAL_SECTION_LEAVE(daif_val); + break; + } + + thread_t *const curr_thread = current_thread(); + + console_notify_write_ready( + (void (*)(void *))wake_up_all_threads_in_wait_queue, + &_write_wait_queue); + suspend_to_wait_queue(&_write_wait_queue); + XCPT_MASK_ALL(); + + if (curr_thread->status.is_waken_up_by_signal) { + curr_thread->status.is_waken_up_by_signal = false; + CRITICAL_SECTION_LEAVE(daif_val); + return -EINTR; + } + + CRITICAL_SECTION_LEAVE(daif_val); + } + + return n_chars_written; +} + +ssize_t console_read_suspend(char *const buf, const size_t size) { + size_t n_chars_read; + + for (;;) { + // We must enter critical section here. Otherwise, there will be a race + // condition between thread suspension and read readiness notification, + // whose callback adds the current thread to the run queue. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + n_chars_read = console_read_nonblock(buf, size); + if (n_chars_read != 0) { + CRITICAL_SECTION_LEAVE(daif_val); + break; + } + + thread_t *const curr_thread = current_thread(); + + console_notify_read_ready( + (void (*)(void *))wake_up_all_threads_in_wait_queue, &_read_wait_queue); + suspend_to_wait_queue(&_read_wait_queue); + XCPT_MASK_ALL(); + + if (curr_thread->status.is_waken_up_by_signal) { + curr_thread->status.is_waken_up_by_signal = false; + CRITICAL_SECTION_LEAVE(daif_val); + return -EINTR; + } + + CRITICAL_SECTION_LEAVE(daif_val); + } + + return n_chars_read; +} diff --git a/lab7/c/src/fs/vfs.c b/lab7/c/src/fs/vfs.c index 15aec9946..9ad5fec44 100644 --- a/lab7/c/src/fs/vfs.c +++ b/lab7/c/src/fs/vfs.c @@ -13,10 +13,22 @@ typedef struct { struct mount *mount; } mount_entry_t; +typedef struct { + struct vnode *mountpoint; + rb_node_t *devices; +} device_mountpoints_entry_t; + +typedef struct { + const char *name; + struct vnode *vnode; +} devices_entry_t; + struct mount rootfs; static rb_node_t *_filesystems = NULL; +static rb_node_t *_devices = NULL; static rb_node_t *_mounts_by_mountpoint = NULL, *_mounts_by_root = NULL; +static rb_node_t *_device_mountpoints = NULL; static int _vfs_cmp_filesystems_by_name(const struct filesystem *const fs1, const struct filesystem *const fs2, @@ -34,6 +46,62 @@ static int _vfs_cmp_name_and_filesystem(const char *const name, return strcmp(name, fs->name); } +static int _vfs_cmp_devices_by_name(const struct device *const dev1, + const struct device *const dev2, + void *const _arg) { + (void)_arg; + + return strcmp(dev1->name, dev2->name); +} + +static int _vfs_cmp_name_and_device(const char *const name, + const struct device *const dev, + void *const _arg) { + (void)_arg; + + return strcmp(name, dev->name); +} + +static int _vfs_cmp_device_mountpoints_entries_by_mountpoint( + const device_mountpoints_entry_t *const e1, + const device_mountpoints_entry_t *const e2, void *const _arg) { + (void)_arg; + + if (e1->mountpoint < e2->mountpoint) + return -1; + if (e1->mountpoint > e2->mountpoint) + return 1; + return 0; +} + +static int _vfs_cmp_mountpoint_and_device_mountpoints_entry( + const struct vnode *const mountpoint, + const device_mountpoints_entry_t *const entry, void *const _arg) { + (void)_arg; + + if (mountpoint < entry->mountpoint) + return -1; + if (mountpoint > entry->mountpoint) + return 1; + return 0; +} + +static int _vfs_cmp_devices_entry_by_name(const devices_entry_t *const e1, + const devices_entry_t *const e2, + void *const _arg) { + (void)_arg; + + return strcmp(e1->name, e2->name); +} + +static int _vfs_cmp_name_and_devices_entry(const char *const name, + const devices_entry_t *const entry, + void *const _arg) { + (void)_arg; + + return strcmp(name, entry->name); +} + static int _vfs_cmp_mounts_by_mountpoint(const mount_entry_t *const m1, const mount_entry_t *const m2, void *const _arg) { @@ -96,6 +164,20 @@ int register_filesystem(struct filesystem *const fs) { return 0; } +int register_device(struct device *const dev) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + rb_insert( + &_devices, sizeof(struct device), dev, + (int (*)(const void *, const void *, void *))_vfs_cmp_devices_by_name, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + return 0; +} + static int _vfs_lookup_step(struct vnode *curr_vnode, struct vnode **const target, const char *const component_name) { @@ -119,6 +201,34 @@ static int _vfs_lookup_step(struct vnode *curr_vnode, } } + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const device_mountpoints_entry_t *const device_mountpoints_entry = rb_search( + _device_mountpoints, curr_vnode, + (int (*)(const void *, const void *, + void *))_vfs_cmp_mountpoint_and_device_mountpoints_entry, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + if (device_mountpoints_entry) { + CRITICAL_SECTION_ENTER(daif_val); + + const devices_entry_t *const devices_entry = + rb_search(device_mountpoints_entry->devices, component_name, + (int (*)(const void *, const void *, + void *))_vfs_cmp_name_and_devices_entry, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + if (devices_entry) { + *target = devices_entry->vnode; + return 0; + } + } + const int result = curr_vnode->v_ops->lookup(curr_vnode, target, component_name); if (result < 0) @@ -127,7 +237,6 @@ static int _vfs_lookup_step(struct vnode *curr_vnode, // If the new node is the mountpoint of a mounted file system, jump to the // filesystem root. - uint64_t daif_val; CRITICAL_SECTION_ENTER(daif_val); const mount_entry_t *const entry = @@ -193,8 +302,8 @@ int vfs_open_relative(struct vnode *const cwd, const char *const pathname, return lookup_result; struct vnode *curr_vnode; - const int result = parent_vnode->v_ops->lookup(parent_vnode, &curr_vnode, - last_pathname_component); + const int result = + _vfs_lookup_step(parent_vnode, &curr_vnode, last_pathname_component); if (result == -ENOENT && flags & O_CREAT) { const int result = parent_vnode->v_ops->create(parent_vnode, &curr_vnode, last_pathname_component); @@ -308,6 +417,98 @@ int vfs_lookup_relative(struct vnode *const cwd, const char *const pathname, } } +int vfs_mknod(const char *target, const char *device) { + // Lookup the pathname. + + struct vnode *mountpoint; + const char *last_pathname_component; + const int result = _vfs_lookup_sans_last_level_relative( + rootfs.root, target, &mountpoint, &last_pathname_component); + if (result < 0) + return result; + + // Check if the target exists. + + struct vnode *full_lookup_vnode; + const int full_lookup_result = vfs_lookup(target, &full_lookup_vnode); + if (full_lookup_result >= 0) + return -EEXIST; + + // Find the device struct. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + struct device *dev = (struct device *)rb_search( + _devices, device, + (int (*)(const void *, const void *, void *))_vfs_cmp_name_and_device, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + if (!dev) + return -ENODEV; + + // Allocate the vnode and the entry name. + + struct vnode *const vnode = malloc(sizeof(struct vnode)); + if (!vnode) + return -ENOMEM; + + const char *const entry_name = strdup(last_pathname_component); + if (!entry_name) { + free(vnode); + return -ENOMEM; + } + + // Create the mountpoint entry. + + CRITICAL_SECTION_ENTER(daif_val); + + device_mountpoints_entry_t *device_mountpoints_entry = + (device_mountpoints_entry_t *)rb_search( + _device_mountpoints, mountpoint, + (int (*)(const void *, const void *, + void *))_vfs_cmp_mountpoint_and_device_mountpoints_entry, + NULL); + if (!device_mountpoints_entry) { + const device_mountpoints_entry_t new_entry = {.mountpoint = mountpoint, + .devices = NULL}; + rb_insert( + &_device_mountpoints, sizeof(device_mountpoints_entry_t), &new_entry, + (int (*)(const void *, const void *, + void *))_vfs_cmp_device_mountpoints_entries_by_mountpoint, + NULL); + device_mountpoints_entry = (device_mountpoints_entry_t *)rb_search( + _device_mountpoints, mountpoint, + (int (*)(const void *, const void *, + void *))_vfs_cmp_mountpoint_and_device_mountpoints_entry, + NULL); + } + + CRITICAL_SECTION_LEAVE(daif_val); + + // Initialize the device. + + vnode->mount = mountpoint->mount; + dev->setup_mount(dev, vnode); + + // Insert everything. + + CRITICAL_SECTION_ENTER(daif_val); + + const devices_entry_t new_entry = {.name = entry_name, .vnode = vnode}; + rb_insert(&device_mountpoints_entry->devices, sizeof(devices_entry_t), + &new_entry, + (int (*)(const void *, const void *, + void *))_vfs_cmp_devices_entry_by_name, + NULL); + + CRITICAL_SECTION_LEAVE(daif_val); + + return 0; +} + shared_file_t *shared_file_new(struct file *const file) { shared_file_t *const shared_file = malloc(sizeof(shared_file_t)); if (!shared_file) diff --git a/lab7/c/src/main.c b/lab7/c/src/main.c index cdf681d76..f3dfb9eb8 100644 --- a/lab7/c/src/main.c +++ b/lab7/c/src/main.c @@ -1,3 +1,4 @@ +#include "oscos/console-dev.h" #include "oscos/console.h" #include "oscos/devicetree.h" #include "oscos/drivers/aux.h" @@ -75,6 +76,9 @@ void main(const void *const dtb_start) { vfs_op_result = register_filesystem(&initramfs); if (vfs_op_result < 0) PANIC("Cannot register initramfs: errno %d", -vfs_op_result); + vfs_op_result = register_device(&console_dev); + if (vfs_op_result < 0) + PANIC("Cannot register console device: errno %d", -vfs_op_result); vfs_op_result = tmpfs.setup_mount(&tmpfs, &rootfs); if (vfs_op_result < 0) @@ -87,6 +91,13 @@ void main(const void *const dtb_start) { if (vfs_op_result < 0) PANIC("Cannot mount initramfs on /initramfs: errno %d", -vfs_op_result); + vfs_op_result = vfs_mkdir("/dev"); + if (vfs_op_result < 0) + PANIC("Cannot mkdir /dev: errno %d", -vfs_op_result); + vfs_op_result = vfs_mknod("/dev/uart", "console"); + if (vfs_op_result < 0) + PANIC("Cannot mknod /dev/uart: errno %d", -vfs_op_result); + thread_create(_run_shell, NULL); sched_setup_periodic_scheduling(); diff --git a/lab7/c/src/sched/sched.c b/lab7/c/src/sched/sched.c index c387d00bf..7a371dfdf 100644 --- a/lab7/c/src/sched/sched.c +++ b/lab7/c/src/sched/sched.c @@ -229,6 +229,68 @@ bool process_create(void) { return false; } + // Open files. + + struct file *stdin; + const int open_stdin_result = vfs_open("/dev/uart", 0, &stdin); + if (open_stdin_result < 0) { + vm_drop_addr_space(addr_space); + free(fp_simd_ctx); + free(process); + return false; + } + + shared_file_t *const shared_stdin = shared_file_new(stdin); + if (!shared_stdin) { + vfs_close(stdin); + vm_drop_addr_space(addr_space); + free(fp_simd_ctx); + free(process); + return false; + } + + struct file *stdout; + const int open_stdout_result = vfs_open("/dev/uart", 0, &stdout); + if (open_stdout_result < 0) { + shared_file_drop(shared_stdin); + vm_drop_addr_space(addr_space); + free(fp_simd_ctx); + free(process); + return false; + } + + shared_file_t *const shared_stdout = shared_file_new(stdout); + if (!shared_stdout) { + vfs_close(stdout); + shared_file_drop(shared_stdin); + vm_drop_addr_space(addr_space); + free(fp_simd_ctx); + free(process); + return false; + } + + struct file *stderr; + const int open_stderr_result = vfs_open("/dev/uart", 0, &stderr); + if (open_stderr_result < 0) { + shared_file_drop(shared_stdout); + shared_file_drop(shared_stdin); + vm_drop_addr_space(addr_space); + free(fp_simd_ctx); + free(process); + return false; + } + + shared_file_t *const shared_stderr = shared_file_new(stderr); + if (!shared_stderr) { + vfs_close(stderr); + shared_file_drop(shared_stdout); + shared_file_drop(shared_stdin); + vm_drop_addr_space(addr_space); + free(fp_simd_ctx); + free(process); + return false; + } + // Set thread/process data. thread_t *const curr_thread = current_thread(); @@ -259,7 +321,10 @@ bool process_create(void) { process->signal_handlers[i] = SIG_DFL; } process->cwd = rootfs.root; - for (size_t i = 0; i < N_FDS; i++) { + process->fds[0] = shared_stdin; + process->fds[1] = shared_stdout; + process->fds[2] = shared_stderr; + for (size_t i = 3; i < N_FDS; i++) { process->fds[i] = NULL; } curr_thread->process = process; diff --git a/lab7/c/src/xcpt/syscall/open.c b/lab7/c/src/xcpt/syscall/open.c index 81c6048f0..c86415a33 100644 --- a/lab7/c/src/xcpt/syscall/open.c +++ b/lab7/c/src/xcpt/syscall/open.c @@ -30,5 +30,5 @@ int sys_open(const char *const pathname, const int flags) { curr_process->fds[fd] = shared_file; - return 0; + return fd; } diff --git a/lab7/c/src/xcpt/syscall/uart-read.c b/lab7/c/src/xcpt/syscall/uart-read.c index 12e00d39c..55fe74917 100644 --- a/lab7/c/src/xcpt/syscall/uart-read.c +++ b/lab7/c/src/xcpt/syscall/uart-read.c @@ -1,46 +1,5 @@ -#include - -#include "oscos/console.h" -#include "oscos/sched.h" -#include "oscos/uapi/errno.h" -#include "oscos/uapi/unistd.h" -#include "oscos/utils/critical-section.h" - -static thread_list_node_t _wait_queue = {.prev = &_wait_queue, - .next = &_wait_queue}; +#include "oscos/console-suspend.h" ssize_t sys_uart_read(char buf[const], const size_t size) { - size_t n_chars_read; - - for (;;) { - // We must enter critical section here. Otherwise, there will be a race - // condition between thread suspension and read readiness notification, - // whose callback adds the current thread to the run queue. - - uint64_t daif_val; - CRITICAL_SECTION_ENTER(daif_val); - - n_chars_read = console_read_nonblock(buf, size); - if (n_chars_read != 0) { - CRITICAL_SECTION_LEAVE(daif_val); - break; - } - - thread_t *const curr_thread = current_thread(); - - console_notify_read_ready( - (void (*)(void *))wake_up_all_threads_in_wait_queue, &_wait_queue); - suspend_to_wait_queue(&_wait_queue); - XCPT_MASK_ALL(); - - if (curr_thread->status.is_waken_up_by_signal) { - curr_thread->status.is_waken_up_by_signal = false; - CRITICAL_SECTION_LEAVE(daif_val); - return -EINTR; - } - - CRITICAL_SECTION_LEAVE(daif_val); - } - - return n_chars_read; + return console_read_suspend(buf, size); } diff --git a/lab7/c/src/xcpt/syscall/uart-write.c b/lab7/c/src/xcpt/syscall/uart-write.c index d612ea85e..155c7fbce 100644 --- a/lab7/c/src/xcpt/syscall/uart-write.c +++ b/lab7/c/src/xcpt/syscall/uart-write.c @@ -1,45 +1,5 @@ -#include - -#include "oscos/console.h" -#include "oscos/sched.h" -#include "oscos/uapi/errno.h" -#include "oscos/utils/critical-section.h" - -static thread_list_node_t _wait_queue = {.prev = &_wait_queue, - .next = &_wait_queue}; +#include "oscos/console-suspend.h" size_t sys_uart_write(const char buf[const], const size_t size) { - size_t n_chars_written; - - for (;;) { - // We must enter critical section here. Otherwise, there will be a race - // condition between thread suspension and write readiness notification, - // whose callback adds the current thread to the run queue. - - uint64_t daif_val; - CRITICAL_SECTION_ENTER(daif_val); - - n_chars_written = console_write_nonblock(buf, size); - if (n_chars_written != 0) { - CRITICAL_SECTION_LEAVE(daif_val); - break; - } - - thread_t *const curr_thread = current_thread(); - - console_notify_write_ready( - (void (*)(void *))wake_up_all_threads_in_wait_queue, &_wait_queue); - suspend_to_wait_queue(&_wait_queue); - XCPT_MASK_ALL(); - - if (curr_thread->status.is_waken_up_by_signal) { - curr_thread->status.is_waken_up_by_signal = false; - CRITICAL_SECTION_LEAVE(daif_val); - return -EINTR; - } - - CRITICAL_SECTION_LEAVE(daif_val); - } - - return n_chars_written; + return console_write_suspend(buf, size); } From cbef6e770c29a29cba0ceed5660ea954af19b487 Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Wed, 12 Jul 2023 16:01:51 +0800 Subject: [PATCH 27/34] Lab 7 C: Change mknod implementation Other changes: - Fix memory leaks in `_tmpfs_create` and `_tmpfs_mkdir`. --- lab7/c/include/oscos/fs/vfs.h | 2 + lab7/c/src/console-dev.c | 16 +++- lab7/c/src/fs/initramfs.c | 16 +++- lab7/c/src/fs/tmpfs.c | 72 +++++++++++++++- lab7/c/src/fs/vfs.c | 152 ++-------------------------------- 5 files changed, 107 insertions(+), 151 deletions(-) diff --git a/lab7/c/include/oscos/fs/vfs.h b/lab7/c/include/oscos/fs/vfs.h index 2c87ab853..4effbebeb 100644 --- a/lab7/c/include/oscos/fs/vfs.h +++ b/lab7/c/include/oscos/fs/vfs.h @@ -50,6 +50,8 @@ struct vnode_operations { const char *component_name); int (*mkdir)(struct vnode *dir_node, struct vnode **target, const char *component_name); + int (*mknod)(struct vnode *dir_node, struct vnode **target, + const char *component_name, struct device *device); }; extern struct mount rootfs; diff --git a/lab7/c/src/console-dev.c b/lab7/c/src/console-dev.c index 014eeae78..34a1a0552 100644 --- a/lab7/c/src/console-dev.c +++ b/lab7/c/src/console-dev.c @@ -24,6 +24,8 @@ static int _console_dev_create(struct vnode *dir_node, struct vnode **target, const char *component_name); static int _console_dev_mkdir(struct vnode *dir_node, struct vnode **target, const char *component_name); +int _console_mknod(struct vnode *dir_node, struct vnode **target, + const char *component_name, struct device *device); struct device console_dev = {.name = "console", .setup_mount = _console_dev_setup_mount}; @@ -38,7 +40,8 @@ static struct file_operations _console_dev_file_operations = { static struct vnode_operations _console_dev_vnode_operations = { .lookup = _console_dev_lookup, .create = _console_dev_create, - .mkdir = _console_dev_mkdir}; + .mkdir = _console_dev_mkdir, + .mknod = _console_mknod}; static int _console_dev_setup_mount(struct device *const dev, struct vnode *const vnode) { @@ -123,3 +126,14 @@ static int _console_dev_mkdir(struct vnode *const dir_node, return -ENOTDIR; } + +int _console_mknod(struct vnode *const dir_node, struct vnode **const target, + const char *const component_name, + struct device *const device) { + (void)dir_node; + (void)target; + (void)component_name; + (void)device; + + return -ENOTDIR; +} diff --git a/lab7/c/src/fs/initramfs.c b/lab7/c/src/fs/initramfs.c index 373dc65d5..94e1160fb 100644 --- a/lab7/c/src/fs/initramfs.c +++ b/lab7/c/src/fs/initramfs.c @@ -33,6 +33,8 @@ static int _initramfs_create(struct vnode *dir_node, struct vnode **target, const char *component_name); static int _initramfs_mkdir(struct vnode *dir_node, struct vnode **target, const char *component_name); +int _initramfs_mknod(struct vnode *dir_node, struct vnode **target, + const char *component_name, struct device *device); struct filesystem initramfs = {.name = "initramfs", .setup_mount = _initramfs_setup_mount}; @@ -47,7 +49,8 @@ static struct file_operations _initramfs_file_operations = { static struct vnode_operations _initramfs_vnode_operations = { .lookup = _initramfs_lookup, .create = _initramfs_create, - .mkdir = _initramfs_mkdir}; + .mkdir = _initramfs_mkdir, + .mknod = _initramfs_mknod}; static int _initramfs_cmp_child_vnode_entries_by_component_name( const initramfs_child_vnode_entry_t *const e1, @@ -331,3 +334,14 @@ static int _initramfs_mkdir(struct vnode *const dir_node, return -EROFS; } + +int _initramfs_mknod(struct vnode *const dir_node, struct vnode **const target, + const char *const component_name, + struct device *const device) { + (void)dir_node; + (void)target; + (void)component_name; + (void)device; + + return -EROFS; +} diff --git a/lab7/c/src/fs/tmpfs.c b/lab7/c/src/fs/tmpfs.c index 180a9cb2f..5f8f96ad6 100644 --- a/lab7/c/src/fs/tmpfs.c +++ b/lab7/c/src/fs/tmpfs.c @@ -50,6 +50,8 @@ static int _tmpfs_create(struct vnode *dir_node, struct vnode **target, const char *component_name); static int _tmpfs_mkdir(struct vnode *dir_node, struct vnode **target, const char *component_name); +static int _tmpfs_mknod(struct vnode *dir_node, struct vnode **target, + const char *component_name, struct device *device); struct filesystem tmpfs = {.name = "tmpfs", .setup_mount = _tmpfs_setup_mount}; @@ -61,7 +63,10 @@ static struct file_operations _tmpfs_file_operations = {.write = _tmpfs_write, _tmpfs_lseek64}; static struct vnode_operations _tmpfs_vnode_operations = { - .lookup = _tmpfs_lookup, .create = _tmpfs_create, .mkdir = _tmpfs_mkdir}; + .lookup = _tmpfs_lookup, + .create = _tmpfs_create, + .mkdir = _tmpfs_mkdir, + .mknod = _tmpfs_mknod}; static int _tmpfs_cmp_dir_contents_entries(const tmpfs_dir_contents_entry_t *const e1, @@ -307,7 +312,7 @@ static int _tmpfs_create(struct vnode *const dir_node, goto end; } - const char *const entry_component_name = strdup(component_name); + char *const entry_component_name = strdup(component_name); if (!entry_component_name) { result = -ENOMEM; goto end; @@ -315,6 +320,7 @@ static int _tmpfs_create(struct vnode *const dir_node, struct vnode *const vnode = _tmpfs_create_file_vnode(dir_node->mount); if (!vnode) { + free(entry_component_name); result = -ENOMEM; goto end; } @@ -357,7 +363,7 @@ static int _tmpfs_mkdir(struct vnode *const dir_node, goto end; } - const char *const entry_component_name = strdup(component_name); + char *const entry_component_name = strdup(component_name); if (!entry_component_name) { result = -ENOMEM; goto end; @@ -366,6 +372,7 @@ static int _tmpfs_mkdir(struct vnode *const dir_node, struct vnode *const vnode = _tmpfs_create_dir_vnode(dir_node->mount, dir_node); if (!vnode) { + free(entry_component_name); result = -ENOMEM; goto end; } @@ -384,3 +391,62 @@ static int _tmpfs_mkdir(struct vnode *const dir_node, CRITICAL_SECTION_LEAVE(daif_val); return result; } + +static int _tmpfs_mknod(struct vnode *const dir_node, + struct vnode **const target, + const char *const component_name, + struct device *const device) { + tmpfs_internal_t *const internal = (tmpfs_internal_t *)dir_node->internal; + if (internal->type != TYPE_DIR) + return -ENOTDIR; + + int result; + tmpfs_internal_dir_data_t *const dir_data = &internal->dir_data; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const tmpfs_dir_contents_entry_t *const existing_entry = rb_search( + dir_data->contents, component_name, + (int (*)(const void *, const void *, + void *))_tmpfs_cmp_component_name_and_dir_contents_entry, + NULL); + if (existing_entry) { + result = -EEXIST; + goto end; + } + + char *const entry_component_name = strdup(component_name); + if (!entry_component_name) { + result = -ENOMEM; + goto end; + } + + struct vnode *const vnode = malloc(sizeof(struct vnode)); + if (!vnode) { + free(entry_component_name); + result = -ENOMEM; + goto end; + } + vnode->mount = dir_node->mount; + + if ((result = device->setup_mount(device, vnode)) < 0) { + free(vnode); + free(entry_component_name); + goto end; + } + + const tmpfs_dir_contents_entry_t new_entry = { + .component_name = entry_component_name, .vnode = vnode}; + rb_insert(&dir_data->contents, sizeof(tmpfs_dir_contents_entry_t), &new_entry, + (int (*)(const void *, const void *, + void *))_tmpfs_cmp_dir_contents_entries, + NULL); + + *target = vnode; + result = 0; + +end: + CRITICAL_SECTION_LEAVE(daif_val); + return result; +} diff --git a/lab7/c/src/fs/vfs.c b/lab7/c/src/fs/vfs.c index 9ad5fec44..d64106ad7 100644 --- a/lab7/c/src/fs/vfs.c +++ b/lab7/c/src/fs/vfs.c @@ -13,22 +13,11 @@ typedef struct { struct mount *mount; } mount_entry_t; -typedef struct { - struct vnode *mountpoint; - rb_node_t *devices; -} device_mountpoints_entry_t; - -typedef struct { - const char *name; - struct vnode *vnode; -} devices_entry_t; - struct mount rootfs; static rb_node_t *_filesystems = NULL; static rb_node_t *_devices = NULL; static rb_node_t *_mounts_by_mountpoint = NULL, *_mounts_by_root = NULL; -static rb_node_t *_device_mountpoints = NULL; static int _vfs_cmp_filesystems_by_name(const struct filesystem *const fs1, const struct filesystem *const fs2, @@ -62,46 +51,6 @@ static int _vfs_cmp_name_and_device(const char *const name, return strcmp(name, dev->name); } -static int _vfs_cmp_device_mountpoints_entries_by_mountpoint( - const device_mountpoints_entry_t *const e1, - const device_mountpoints_entry_t *const e2, void *const _arg) { - (void)_arg; - - if (e1->mountpoint < e2->mountpoint) - return -1; - if (e1->mountpoint > e2->mountpoint) - return 1; - return 0; -} - -static int _vfs_cmp_mountpoint_and_device_mountpoints_entry( - const struct vnode *const mountpoint, - const device_mountpoints_entry_t *const entry, void *const _arg) { - (void)_arg; - - if (mountpoint < entry->mountpoint) - return -1; - if (mountpoint > entry->mountpoint) - return 1; - return 0; -} - -static int _vfs_cmp_devices_entry_by_name(const devices_entry_t *const e1, - const devices_entry_t *const e2, - void *const _arg) { - (void)_arg; - - return strcmp(e1->name, e2->name); -} - -static int _vfs_cmp_name_and_devices_entry(const char *const name, - const devices_entry_t *const entry, - void *const _arg) { - (void)_arg; - - return strcmp(name, entry->name); -} - static int _vfs_cmp_mounts_by_mountpoint(const mount_entry_t *const m1, const mount_entry_t *const m2, void *const _arg) { @@ -201,34 +150,6 @@ static int _vfs_lookup_step(struct vnode *curr_vnode, } } - uint64_t daif_val; - CRITICAL_SECTION_ENTER(daif_val); - - const device_mountpoints_entry_t *const device_mountpoints_entry = rb_search( - _device_mountpoints, curr_vnode, - (int (*)(const void *, const void *, - void *))_vfs_cmp_mountpoint_and_device_mountpoints_entry, - NULL); - - CRITICAL_SECTION_LEAVE(daif_val); - - if (device_mountpoints_entry) { - CRITICAL_SECTION_ENTER(daif_val); - - const devices_entry_t *const devices_entry = - rb_search(device_mountpoints_entry->devices, component_name, - (int (*)(const void *, const void *, - void *))_vfs_cmp_name_and_devices_entry, - NULL); - - CRITICAL_SECTION_LEAVE(daif_val); - - if (devices_entry) { - *target = devices_entry->vnode; - return 0; - } - } - const int result = curr_vnode->v_ops->lookup(curr_vnode, target, component_name); if (result < 0) @@ -237,6 +158,7 @@ static int _vfs_lookup_step(struct vnode *curr_vnode, // If the new node is the mountpoint of a mounted file system, jump to the // filesystem root. + uint64_t daif_val; CRITICAL_SECTION_ENTER(daif_val); const mount_entry_t *const entry = @@ -302,8 +224,8 @@ int vfs_open_relative(struct vnode *const cwd, const char *const pathname, return lookup_result; struct vnode *curr_vnode; - const int result = - _vfs_lookup_step(parent_vnode, &curr_vnode, last_pathname_component); + const int result = parent_vnode->v_ops->lookup(parent_vnode, &curr_vnode, + last_pathname_component); if (result == -ENOENT && flags & O_CREAT) { const int result = parent_vnode->v_ops->create(parent_vnode, &curr_vnode, last_pathname_component); @@ -427,13 +349,6 @@ int vfs_mknod(const char *target, const char *device) { if (result < 0) return result; - // Check if the target exists. - - struct vnode *full_lookup_vnode; - const int full_lookup_result = vfs_lookup(target, &full_lookup_vnode); - if (full_lookup_result >= 0) - return -EEXIST; - // Find the device struct. uint64_t daif_val; @@ -449,64 +364,9 @@ int vfs_mknod(const char *target, const char *device) { if (!dev) return -ENODEV; - // Allocate the vnode and the entry name. - - struct vnode *const vnode = malloc(sizeof(struct vnode)); - if (!vnode) - return -ENOMEM; - - const char *const entry_name = strdup(last_pathname_component); - if (!entry_name) { - free(vnode); - return -ENOMEM; - } - - // Create the mountpoint entry. - - CRITICAL_SECTION_ENTER(daif_val); - - device_mountpoints_entry_t *device_mountpoints_entry = - (device_mountpoints_entry_t *)rb_search( - _device_mountpoints, mountpoint, - (int (*)(const void *, const void *, - void *))_vfs_cmp_mountpoint_and_device_mountpoints_entry, - NULL); - if (!device_mountpoints_entry) { - const device_mountpoints_entry_t new_entry = {.mountpoint = mountpoint, - .devices = NULL}; - rb_insert( - &_device_mountpoints, sizeof(device_mountpoints_entry_t), &new_entry, - (int (*)(const void *, const void *, - void *))_vfs_cmp_device_mountpoints_entries_by_mountpoint, - NULL); - device_mountpoints_entry = (device_mountpoints_entry_t *)rb_search( - _device_mountpoints, mountpoint, - (int (*)(const void *, const void *, - void *))_vfs_cmp_mountpoint_and_device_mountpoints_entry, - NULL); - } - - CRITICAL_SECTION_LEAVE(daif_val); - - // Initialize the device. - - vnode->mount = mountpoint->mount; - dev->setup_mount(dev, vnode); - - // Insert everything. - - CRITICAL_SECTION_ENTER(daif_val); - - const devices_entry_t new_entry = {.name = entry_name, .vnode = vnode}; - rb_insert(&device_mountpoints_entry->devices, sizeof(devices_entry_t), - &new_entry, - (int (*)(const void *, const void *, - void *))_vfs_cmp_devices_entry_by_name, - NULL); - - CRITICAL_SECTION_LEAVE(daif_val); - - return 0; + struct vnode *target_vnode; + return mountpoint->v_ops->mknod(mountpoint, &target_vnode, + last_pathname_component, dev); } shared_file_t *shared_file_new(struct file *const file) { From 41019c44e0be6089119bac3fe37a663215c42449 Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Wed, 12 Jul 2023 18:21:41 +0800 Subject: [PATCH 28/34] Lab 7 C: Complete A2 Other changes: - Fix syscall number checking in `xcpt_svc_handler`. --- lab7/c/Makefile | 8 +- lab7/c/include/oscos/drivers/mailbox.h | 11 + lab7/c/include/oscos/framebuffer-dev.h | 15 ++ lab7/c/include/oscos/fs/vfs.h | 3 + lab7/c/include/oscos/uapi/errno.h | 1 + lab7/c/include/oscos/uapi/sys/syscall.h | 4 +- lab7/c/src/console-dev.c | 14 +- lab7/c/src/drivers/mailbox.c | 51 ++++ lab7/c/src/framebuffer-dev.c | 236 ++++++++++++++++++ lab7/c/src/fs/initramfs.c | 14 +- lab7/c/src/fs/tmpfs.c | 14 +- lab7/c/src/fs/vfs.c | 9 + lab7/c/src/main.c | 7 + lab7/c/src/xcpt/svc-handler.S | 4 +- lab7/c/src/xcpt/syscall-table.S | 2 + lab7/c/src/xcpt/syscall/ioctl.c | 11 + lab7/c/src/xcpt/syscall/lseek64.c | 11 + lab7/tests/user-program/libc/Makefile | 4 +- .../user-program/libc/include/sys/ioctl.h | 6 + lab7/tests/user-program/libc/include/unistd.h | 3 + lab7/tests/user-program/libc/src/sys/ioctl.c | 17 ++ lab7/tests/user-program/libc/src/unistd.c | 4 + 22 files changed, 437 insertions(+), 12 deletions(-) create mode 100644 lab7/c/include/oscos/framebuffer-dev.h create mode 100644 lab7/c/src/framebuffer-dev.c create mode 100644 lab7/c/src/xcpt/syscall/ioctl.c create mode 100644 lab7/c/src/xcpt/syscall/lseek64.c create mode 100644 lab7/tests/user-program/libc/include/sys/ioctl.h create mode 100644 lab7/tests/user-program/libc/src/sys/ioctl.c diff --git a/lab7/c/Makefile b/lab7/c/Makefile index 74b10591d..d924de51d 100644 --- a/lab7/c/Makefile +++ b/lab7/c/Makefile @@ -21,8 +21,8 @@ LDFLAGS_RELEASE = -flto -Xlinker --gc-sections LDLIBS_BASE = -lgcc -OBJS = start main console console-dev console-suspend devicetree initrd \ - panic shell \ +OBJS = start main console console-dev console-suspend devicetree \ + framebuffer-dev initrd panic shell \ drivers/aux drivers/gpio drivers/l1ic drivers/l2ic drivers/mailbox \ drivers/mini-uart drivers/pm \ fs/initramfs fs/tmpfs fs/vfs \ @@ -43,8 +43,8 @@ OBJS = start main console console-dev console-suspend devicetree initrd \ xcpt/syscall/signal xcpt/syscall/signal-kill xcpt/syscall/mmap \ xcpt/syscall/open xcpt/syscall/close xcpt/syscall/write \ xcpt/syscall/read xcpt/syscall/mkdir xcpt/syscall/mount \ - xcpt/syscall/chdir xcpt/syscall/sigreturn \ - xcpt/syscall/sigreturn-check \ + xcpt/syscall/chdir xcpt/syscall/lseek64 xcpt/syscall/ioctl \ + xcpt/syscall/sigreturn xcpt/syscall/sigreturn-check \ libc/ctype libc/stdio libc/stdlib/qsort libc/string \ utils/core-id utils/fmt utils/heapq utils/rb LD_SCRIPT = $(SRC_DIR)/linker.ld diff --git a/lab7/c/include/oscos/drivers/mailbox.h b/lab7/c/include/oscos/drivers/mailbox.h index 57fa08254..75d483dd6 100644 --- a/lab7/c/include/oscos/drivers/mailbox.h +++ b/lab7/c/include/oscos/drivers/mailbox.h @@ -1,6 +1,7 @@ #ifndef OSCOS_DRIVERS_MAILBOX_H #define OSCOS_DRIVERS_MAILBOX_H +#include #include #define MAILBOX_CHANNEL_PROPERTY_TAGS_ARM_TO_VC ((unsigned char)8) @@ -9,11 +10,21 @@ typedef struct { uint32_t base, size; } arm_memory_t; +typedef struct { + unsigned int width; + unsigned int height; + unsigned int pitch; + unsigned int isrgb; + void *framebuffer_base; + size_t framebuffer_size; +} init_framebuffer_result_t; + void mailbox_init(void); void mailbox_call(uint32_t message[], unsigned char channel); uint32_t mailbox_get_board_revision(void); arm_memory_t mailbox_get_arm_memory(void); +init_framebuffer_result_t mailbox_init_framebuffer(void); #endif diff --git a/lab7/c/include/oscos/framebuffer-dev.h b/lab7/c/include/oscos/framebuffer-dev.h new file mode 100644 index 000000000..7748b43f6 --- /dev/null +++ b/lab7/c/include/oscos/framebuffer-dev.h @@ -0,0 +1,15 @@ +#ifndef OSCOS_FRAMEBUFFER_DEV_H +#define OSCOS_FRAMEBUFFER_DEV_H + +#include "oscos/fs/vfs.h" + +typedef struct { + unsigned int width; + unsigned int height; + unsigned int pitch; + unsigned int isrgb; +} framebuffer_info_t; + +extern struct device framebuffer_dev; + +#endif diff --git a/lab7/c/include/oscos/fs/vfs.h b/lab7/c/include/oscos/fs/vfs.h index 4effbebeb..d13015e03 100644 --- a/lab7/c/include/oscos/fs/vfs.h +++ b/lab7/c/include/oscos/fs/vfs.h @@ -41,6 +41,7 @@ struct file_operations { int (*open)(struct vnode *file_node, struct file **target); int (*close)(struct file *file); long (*lseek64)(struct file *file, long offset, int whence); + int (*ioctl)(struct file *file, unsigned long request, void *payload); }; struct vnode_operations { @@ -65,6 +66,8 @@ int vfs_open_relative(struct vnode *cwd, const char *pathname, int flags, int vfs_close(struct file *file); int vfs_write(struct file *file, const void *buf, size_t len); int vfs_read(struct file *file, void *buf, size_t len); +long vfs_lseek64(struct file *file, long offset, int whence); +int vfs_ioctl(struct file *file, unsigned long request, void *payload); int vfs_mkdir(const char *pathname); int vfs_mkdir_relative(struct vnode *cwd, const char *pathname); diff --git a/lab7/c/include/oscos/uapi/errno.h b/lab7/c/include/oscos/uapi/errno.h index 6cc28ab2a..78a8da16d 100644 --- a/lab7/c/include/oscos/uapi/errno.h +++ b/lab7/c/include/oscos/uapi/errno.h @@ -11,6 +11,7 @@ #define EISDIR 21 #define EINVAL 22 #define EMFILE 24 +#define ENOTTY 25 #define EFBIG 27 #define ESPIPE 29 #define EROFS 30 diff --git a/lab7/c/include/oscos/uapi/sys/syscall.h b/lab7/c/include/oscos/uapi/sys/syscall.h index da6c48df8..3f5cd7545 100644 --- a/lab7/c/include/oscos/uapi/sys/syscall.h +++ b/lab7/c/include/oscos/uapi/sys/syscall.h @@ -19,6 +19,8 @@ #define SYS_mkdir 15 #define SYS_mount 16 #define SYS_chdir 17 -#define SYS_sigreturn 18 +#define SYS_lseek64 18 +#define SYS_ioctl 19 +#define SYS_sigreturn 20 #endif diff --git a/lab7/c/src/console-dev.c b/lab7/c/src/console-dev.c index 34a1a0552..99fd1dd20 100644 --- a/lab7/c/src/console-dev.c +++ b/lab7/c/src/console-dev.c @@ -17,6 +17,8 @@ static int _console_dev_read(struct file *file, void *buf, size_t len); static int _console_dev_open(struct vnode *file_node, struct file **target); static int _console_dev_close(struct file *file); static long _console_dev_lseek64(struct file *file, long offset, int whence); +static int _console_dev_ioctl(struct file *file, unsigned long request, + void *payload); static int _console_dev_lookup(struct vnode *dir_node, struct vnode **target, const char *component_name); @@ -35,7 +37,8 @@ static struct file_operations _console_dev_file_operations = { .read = _console_dev_read, .open = _console_dev_open, .close = _console_dev_close, - .lseek64 = _console_dev_lseek64}; + .lseek64 = _console_dev_lseek64, + .ioctl = _console_dev_ioctl}; static struct vnode_operations _console_dev_vnode_operations = { .lookup = _console_dev_lookup, @@ -97,6 +100,15 @@ static long _console_dev_lseek64(struct file *const file, const long offset, return -ESPIPE; } +static int _console_dev_ioctl(struct file *file, unsigned long request, + void *payload) { + (void)file; + (void)request; + (void)payload; + + return -ENOTTY; +} + static int _console_dev_lookup(struct vnode *const dir_node, struct vnode **const target, const char *const component_name) { diff --git a/lab7/c/src/drivers/mailbox.c b/lab7/c/src/drivers/mailbox.c index 0cb84d408..7835aad9e 100644 --- a/lab7/c/src/drivers/mailbox.c +++ b/lab7/c/src/drivers/mailbox.c @@ -82,3 +82,54 @@ arm_memory_t mailbox_get_arm_memory(void) { return (arm_memory_t){.base = mailbox[5], .size = mailbox[6]}; } + +init_framebuffer_result_t mailbox_init_framebuffer(void) { + alignas(16) uint32_t mailbox[36] = { + [0] = 35 * 4, [1] = REQUEST_CODE, + + [2] = 0x48003, // set phy wh + [3] = 8, [4] = 8, + [5] = 1024, // FrameBufferInfo.width + [6] = 768, // FrameBufferInfo.height + + [7] = 0x48004, // set virt wh + [8] = 8, [9] = 8, + [10] = 1024, // FrameBufferInfo.virtual_width + [11] = 768, // FrameBufferInfo.virtual_height + + [12] = 0x48009, // set virt offset + [13] = 8, [14] = 8, + [15] = 0, // FrameBufferInfo.x_offset + [16] = 0, // FrameBufferInfo.y.offset + + [17] = 0x48005, // set depth + [18] = 4, [19] = 4, + [20] = 32, // FrameBufferInfo.depth + + [21] = 0x48006, // set pixel order + [22] = 4, [23] = 4, + [24] = 1, // RGB, not BGR preferably + + [25] = 0x40001, // get framebuffer, gets alignment on request + [26] = 8, [27] = 8, + [28] = 4096, // FrameBufferInfo.pointer + [29] = 0, // FrameBufferInfo.size + + [30] = 0x40008, // get pitch + [31] = 4, [32] = 4, + [33] = 0, // FrameBufferInfo.pitch + + [34] = END_TAG}; + + mailbox_call(mailbox, MAILBOX_CHANNEL_PROPERTY_TAGS_ARM_TO_VC); + + return mailbox[20] == 32 && mailbox[28] != 0 + ? (init_framebuffer_result_t){.width = mailbox[5], + .height = mailbox[6], + .pitch = mailbox[33], + .isrgb = mailbox[24], + .framebuffer_base = pa_to_kernel_va( + mailbox[28] & 0x3fffffff), + .framebuffer_size = mailbox[29]} + : (init_framebuffer_result_t){.framebuffer_base = NULL}; +} diff --git a/lab7/c/src/framebuffer-dev.c b/lab7/c/src/framebuffer-dev.c new file mode 100644 index 000000000..d750c279a --- /dev/null +++ b/lab7/c/src/framebuffer-dev.c @@ -0,0 +1,236 @@ +#include "oscos/framebuffer-dev.h" + +#include "oscos/drivers/mailbox.h" +#include "oscos/libc/string.h" +#include "oscos/mem/malloc.h" +#include "oscos/mem/page-alloc.h" +#include "oscos/mem/vm.h" +#include "oscos/uapi/errno.h" +#include "oscos/uapi/stdio.h" +#include "oscos/utils/critical-section.h" +#include "oscos/utils/rb.h" + +typedef struct { + void *framebuffer_base; + size_t framebuffer_size; + unsigned int width; + unsigned int height; + unsigned int pitch; + unsigned int isrgb; +} framebuffer_dev_internal_t; + +static int _framebuffer_dev_setup_mount(struct device *dev, + struct vnode *vnode); + +static int _framebuffer_dev_write(struct file *file, const void *buf, + size_t len); +static int _framebuffer_dev_read(struct file *file, void *buf, size_t len); +static int _framebuffer_dev_open(struct vnode *file_node, struct file **target); +static int _framebuffer_dev_close(struct file *file); +static long _framebuffer_dev_lseek64(struct file *file, long offset, + int whence); +static int _framebuffer_dev_ioctl(struct file *file, unsigned long request, + void *payload); + +static int _framebuffer_dev_lookup(struct vnode *dir_node, + struct vnode **target, + const char *component_name); +static int _framebuffer_dev_create(struct vnode *dir_node, + struct vnode **target, + const char *component_name); +static int _framebuffer_dev_mkdir(struct vnode *dir_node, struct vnode **target, + const char *component_name); +static int _framebuffer_dev_mknod(struct vnode *dir_node, struct vnode **target, + const char *component_name, + struct device *device); + +struct device framebuffer_dev = {.name = "framebuffer", + .setup_mount = _framebuffer_dev_setup_mount}; + +static struct file_operations _framebuffer_dev_file_operations = { + .write = _framebuffer_dev_write, + .read = _framebuffer_dev_read, + .open = _framebuffer_dev_open, + .close = _framebuffer_dev_close, + .lseek64 = _framebuffer_dev_lseek64, + .ioctl = _framebuffer_dev_ioctl}; + +static struct vnode_operations _framebuffer_dev_vnode_operations = { + .lookup = _framebuffer_dev_lookup, + .create = _framebuffer_dev_create, + .mkdir = _framebuffer_dev_mkdir, + .mknod = _framebuffer_dev_mknod}; + +static int _framebuffer_dev_setup_mount(struct device *const dev, + struct vnode *const vnode) { + (void)dev; + + // Allocate internal data. + + framebuffer_dev_internal_t *const internal = + malloc(sizeof(framebuffer_dev_internal_t)); + if (!internal) + return -ENOMEM; + + // Initialize the framebuffer. + + init_framebuffer_result_t init_framebuffer_result = + mailbox_init_framebuffer(); + if (!init_framebuffer_result.framebuffer_base) { + free(internal); + return -EIO; + } + + // Save vnode data. + + *internal = (framebuffer_dev_internal_t){ + .framebuffer_base = init_framebuffer_result.framebuffer_base, + .framebuffer_size = init_framebuffer_result.framebuffer_size, + .width = init_framebuffer_result.width, + .height = init_framebuffer_result.height, + .pitch = init_framebuffer_result.pitch, + .isrgb = init_framebuffer_result.isrgb}; + + *vnode = (struct vnode){.mount = vnode->mount, + .v_ops = &_framebuffer_dev_vnode_operations, + .f_ops = &_framebuffer_dev_file_operations, + .internal = internal}; + + return 0; +} + +static int _framebuffer_dev_write(struct file *const file, + const void *const buf, const size_t len) { + framebuffer_dev_internal_t *const internal = + (framebuffer_dev_internal_t *)file->vnode->internal; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + if (file->f_pos >= internal->framebuffer_size) { + CRITICAL_SECTION_LEAVE(daif_val); + return len == 0 ? 0 : -EFBIG; + } + + const size_t remaining_len = internal->framebuffer_size - file->f_pos, + cpy_len = len < remaining_len ? len : remaining_len; + + memcpy((char *)internal->framebuffer_base + file->f_pos, buf, cpy_len); + file->f_pos += cpy_len; + + CRITICAL_SECTION_LEAVE(daif_val); + + return cpy_len; +} + +static int _framebuffer_dev_read(struct file *const file, void *const buf, + const size_t len) { + (void)file; + (void)buf; + (void)len; + + return -EIO; +} + +static int _framebuffer_dev_open(struct vnode *const file_node, + struct file **const target) { + struct file *const file_handle = malloc(sizeof(struct file)); + if (!file_handle) + return -ENOMEM; + + *file_handle = (struct file){.vnode = file_node, + .f_pos = 0, + .f_ops = &_framebuffer_dev_file_operations, + .flags = 0}; + *target = file_handle; + + return 0; +} + +static int _framebuffer_dev_close(struct file *const file) { + free(file); + return 0; +} + +static long _framebuffer_dev_lseek64(struct file *const file, const long offset, + const int whence) { + framebuffer_dev_internal_t *const internal = + (framebuffer_dev_internal_t *)file->vnode->internal; + + if (!(whence == SEEK_SET || whence == SEEK_CUR || whence == SEEK_END)) + return -EINVAL; + + const long f_pos_base = whence == SEEK_SET ? 0 + : whence == SEEK_CUR ? file->f_pos + : internal->framebuffer_size, + new_f_pos = f_pos_base + offset; + + if (new_f_pos < 0) { + return -EINVAL; + } else { + file->f_pos = new_f_pos; + return 0; + } +} + +static int _framebuffer_dev_ioctl(struct file *const file, + const unsigned long request, + void *const payload) { + (void)file; + + framebuffer_dev_internal_t *const internal = + (framebuffer_dev_internal_t *)file->vnode->internal; + + if (request != 0) + return -ENOTTY; + + framebuffer_info_t *const info = (framebuffer_info_t *)payload; + *info = (framebuffer_info_t){.width = internal->width, + .height = internal->height, + .pitch = internal->pitch, + .isrgb = internal->isrgb}; + + return 0; +} + +static int _framebuffer_dev_lookup(struct vnode *const dir_node, + struct vnode **const target, + const char *const component_name) { + (void)dir_node; + (void)target; + (void)component_name; + + return -ENOTDIR; +} + +static int _framebuffer_dev_create(struct vnode *const dir_node, + struct vnode **const target, + const char *const component_name) { + (void)dir_node; + (void)target; + (void)component_name; + + return -ENOTDIR; +} + +static int _framebuffer_dev_mkdir(struct vnode *const dir_node, + struct vnode **const target, + const char *const component_name) { + (void)dir_node; + (void)target; + (void)component_name; + + return -ENOTDIR; +} + +static int _framebuffer_dev_mknod(struct vnode *const dir_node, + struct vnode **const target, + const char *const component_name, + struct device *const device) { + (void)dir_node; + (void)target; + (void)component_name; + (void)device; + + return -ENOTDIR; +} diff --git a/lab7/c/src/fs/initramfs.c b/lab7/c/src/fs/initramfs.c index 94e1160fb..576be0623 100644 --- a/lab7/c/src/fs/initramfs.c +++ b/lab7/c/src/fs/initramfs.c @@ -26,6 +26,8 @@ static int _initramfs_read(struct file *file, void *buf, size_t len); static int _initramfs_open(struct vnode *file_node, struct file **target); static int _initramfs_close(struct file *file); static long _initramfs_lseek64(struct file *file, long offset, int whence); +static int _initramfs_ioctl(struct file *file, unsigned long request, + void *payload); static int _initramfs_lookup(struct vnode *dir_node, struct vnode **target, const char *component_name); @@ -44,7 +46,8 @@ static struct file_operations _initramfs_file_operations = { .read = _initramfs_read, .open = _initramfs_open, .close = _initramfs_close, - .lseek64 = _initramfs_lseek64}; + .lseek64 = _initramfs_lseek64, + .ioctl = _initramfs_ioctl}; static struct vnode_operations _initramfs_vnode_operations = { .lookup = _initramfs_lookup, @@ -215,6 +218,15 @@ static long _initramfs_lseek64(struct file *const file, const long offset, } } +static int _initramfs_ioctl(struct file *const file, + const unsigned long request, void *const payload) { + (void)file; + (void)request; + (void)payload; + + return -ENOTTY; +} + static int _initramfs_lookup(struct vnode *const dir_node, struct vnode **const target, const char *const component_name) { diff --git a/lab7/c/src/fs/tmpfs.c b/lab7/c/src/fs/tmpfs.c index 5f8f96ad6..982a9d9ed 100644 --- a/lab7/c/src/fs/tmpfs.c +++ b/lab7/c/src/fs/tmpfs.c @@ -43,6 +43,8 @@ static int _tmpfs_read(struct file *file, void *buf, size_t len); static int _tmpfs_open(struct vnode *file_node, struct file **target); static int _tmpfs_close(struct file *file); static long _tmpfs_lseek64(struct file *file, long offset, int whence); +static int _tmpfs_ioctl(struct file *file, unsigned long request, + void *payload); static int _tmpfs_lookup(struct vnode *dir_node, struct vnode **target, const char *component_name); @@ -60,7 +62,8 @@ static struct file_operations _tmpfs_file_operations = {.write = _tmpfs_write, .open = _tmpfs_open, .close = _tmpfs_close, .lseek64 = - _tmpfs_lseek64}; + _tmpfs_lseek64, + .ioctl = _tmpfs_ioctl}; static struct vnode_operations _tmpfs_vnode_operations = { .lookup = _tmpfs_lookup, @@ -251,6 +254,15 @@ static long _tmpfs_lseek64(struct file *const file, const long offset, } } +static int _tmpfs_ioctl(struct file *const file, const unsigned long request, + void *const payload) { + (void)file; + (void)request; + (void)payload; + + return -ENOTTY; +} + static int _tmpfs_lookup(struct vnode *const dir_node, struct vnode **const target, const char *const component_name) { diff --git a/lab7/c/src/fs/vfs.c b/lab7/c/src/fs/vfs.c index d64106ad7..091450be5 100644 --- a/lab7/c/src/fs/vfs.c +++ b/lab7/c/src/fs/vfs.c @@ -248,6 +248,15 @@ int vfs_read(struct file *const file, void *const buf, const size_t len) { return file->f_ops->read(file, buf, len); } +long vfs_lseek64(struct file *const file, const long offset, const int whence) { + return file->f_ops->lseek64(file, offset, whence); +} + +int vfs_ioctl(struct file *const file, const unsigned long request, + void *const payload) { + return file->f_ops->ioctl(file, request, payload); +} + int vfs_mkdir(const char *const pathname) { return vfs_mkdir_relative(rootfs.root, pathname); } diff --git a/lab7/c/src/main.c b/lab7/c/src/main.c index f3dfb9eb8..624d09e4a 100644 --- a/lab7/c/src/main.c +++ b/lab7/c/src/main.c @@ -7,6 +7,7 @@ #include "oscos/drivers/l2ic.h" #include "oscos/drivers/mailbox.h" #include "oscos/drivers/pm.h" +#include "oscos/framebuffer-dev.h" #include "oscos/fs/initramfs.h" #include "oscos/fs/tmpfs.h" #include "oscos/fs/vfs.h" @@ -79,6 +80,9 @@ void main(const void *const dtb_start) { vfs_op_result = register_device(&console_dev); if (vfs_op_result < 0) PANIC("Cannot register console device: errno %d", -vfs_op_result); + vfs_op_result = register_device(&framebuffer_dev); + if (vfs_op_result < 0) + PANIC("Cannot register framebuffer device: errno %d", -vfs_op_result); vfs_op_result = tmpfs.setup_mount(&tmpfs, &rootfs); if (vfs_op_result < 0) @@ -97,6 +101,9 @@ void main(const void *const dtb_start) { vfs_op_result = vfs_mknod("/dev/uart", "console"); if (vfs_op_result < 0) PANIC("Cannot mknod /dev/uart: errno %d", -vfs_op_result); + vfs_op_result = vfs_mknod("/dev/framebuffer", "framebuffer"); + if (vfs_op_result < 0) + PANIC("Cannot mknod /dev/framebuffer: errno %d", -vfs_op_result); thread_create(_run_shell, NULL); diff --git a/lab7/c/src/xcpt/svc-handler.S b/lab7/c/src/xcpt/svc-handler.S index 2b5604f80..cbf9f47b3 100644 --- a/lab7/c/src/xcpt/svc-handler.S +++ b/lab7/c/src/xcpt/svc-handler.S @@ -13,9 +13,9 @@ xcpt_svc_handler: msr daifclr, 0xf // Check the system call number. - ubfx x9, x9, 0, 18 + ubfx x9, x9, 0, 16 cbnz x9, .Lenosys - cmp x8, 17 + cmp x8, 20 b.hi .Lenosys // Table-jump to the system call function. diff --git a/lab7/c/src/xcpt/syscall-table.S b/lab7/c/src/xcpt/syscall-table.S index 9b18af084..31fa48b94 100644 --- a/lab7/c/src/xcpt/syscall-table.S +++ b/lab7/c/src/xcpt/syscall-table.S @@ -19,6 +19,8 @@ syscall_table: b sys_mkdir b sys_mount b sys_chdir + b sys_lseek64 + b sys_ioctl b sys_sigreturn .size syscall_table, . - syscall_table diff --git a/lab7/c/src/xcpt/syscall/ioctl.c b/lab7/c/src/xcpt/syscall/ioctl.c new file mode 100644 index 000000000..00091c3c1 --- /dev/null +++ b/lab7/c/src/xcpt/syscall/ioctl.c @@ -0,0 +1,11 @@ +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" + +int sys_ioctl(const int fd, const unsigned long request, void *const payload) { + process_t *const curr_process = current_thread()->process; + + if (!(0 <= fd && fd < N_FDS && curr_process->fds[fd])) + return -EBADF; + + return vfs_ioctl(curr_process->fds[fd]->file, request, payload); +} diff --git a/lab7/c/src/xcpt/syscall/lseek64.c b/lab7/c/src/xcpt/syscall/lseek64.c new file mode 100644 index 000000000..6b6db0c5f --- /dev/null +++ b/lab7/c/src/xcpt/syscall/lseek64.c @@ -0,0 +1,11 @@ +#include "oscos/sched.h" +#include "oscos/uapi/errno.h" + +long sys_lseek64(const int fd, const long offset, const int whence) { + process_t *const curr_process = current_thread()->process; + + if (!(0 <= fd && fd < N_FDS && curr_process->fds[fd])) + return -EBADF; + + return vfs_lseek64(curr_process->fds[fd]->file, offset, whence); +} diff --git a/lab7/tests/user-program/libc/Makefile b/lab7/tests/user-program/libc/Makefile index 37456b1f1..89787ac9c 100644 --- a/lab7/tests/user-program/libc/Makefile +++ b/lab7/tests/user-program/libc/Makefile @@ -16,8 +16,8 @@ CFLAGS_BASE = -std=c17 -pedantic-errors -Wall -Wextra -ffreestanding \ CFLAGS_DEBUG = -g CFLAGS_RELEASE = -O3 -flto -OBJS = start ctype errno fcntl mbox signal stdio stdlib string sys/mount \ - sys/stat unistd unistd/syscall __detail/utils/fmt +OBJS = start ctype errno fcntl mbox signal stdio stdlib string sys/ioctl \ + sys/mount sys/stat unistd unistd/syscall __detail/utils/fmt # ------------------------------------------------------------------------------ diff --git a/lab7/tests/user-program/libc/include/sys/ioctl.h b/lab7/tests/user-program/libc/include/sys/ioctl.h new file mode 100644 index 000000000..bb95ddc71 --- /dev/null +++ b/lab7/tests/user-program/libc/include/sys/ioctl.h @@ -0,0 +1,6 @@ +#ifndef OSCOS_USER_PROGRAM_LIBC_IOCTL_H +#define OSCOS_USER_PROGRAM_LIBC_IOCTL_H + +int ioctl(int fd, unsigned long request, ...); + +#endif diff --git a/lab7/tests/user-program/libc/include/unistd.h b/lab7/tests/user-program/libc/include/unistd.h index 3ad6ec7c7..96e427fd9 100644 --- a/lab7/tests/user-program/libc/include/unistd.h +++ b/lab7/tests/user-program/libc/include/unistd.h @@ -3,6 +3,7 @@ #include +#include "oscos-uapi/stdio.h" // SEEK_* #include "oscos-uapi/unistd.h" typedef int pid_t; @@ -23,6 +24,8 @@ ssize_t read(int fd, void *buf, size_t count); int chdir(const char *path); +long lseek64(int fd, long offset, int whence); + long syscall(long number, ...); #endif diff --git a/lab7/tests/user-program/libc/src/sys/ioctl.c b/lab7/tests/user-program/libc/src/sys/ioctl.c new file mode 100644 index 000000000..52ce1c2e7 --- /dev/null +++ b/lab7/tests/user-program/libc/src/sys/ioctl.c @@ -0,0 +1,17 @@ +#include "sys/ioctl.h" + +#include + +#include "sys/syscall.h" +#include "unistd.h" + +int ioctl(const int fd, const unsigned long request, ...) { + va_list ap; + va_start(ap, request); + + void *const payload = va_arg(ap, void *); + + va_end(ap); + + return syscall(SYS_ioctl, fd, request, payload); +} diff --git a/lab7/tests/user-program/libc/src/unistd.c b/lab7/tests/user-program/libc/src/unistd.c index a5ca260aa..fd9811ad3 100644 --- a/lab7/tests/user-program/libc/src/unistd.c +++ b/lab7/tests/user-program/libc/src/unistd.c @@ -29,3 +29,7 @@ ssize_t read(const int fd, void *const buf, const size_t count) { } int chdir(const char *const path) { return syscall(SYS_chdir, path); } + +long lseek64(const int fd, const long offset, const int whence) { + return syscall(SYS_lseek64, fd, offset, whence); +} From f5070268c937cd57ce87eee95c5642132a8acfdf Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Fri, 14 Jul 2023 16:12:29 +0800 Subject: [PATCH 29/34] Lab 7 C: Use VFS API in `sys_exec` Other changes: - Move `SEEK_*` constants around. - Properly handle setup errors in `vfs_mount`. - Add missing `static`s. --- lab7/c/include/oscos/fs/vfs.h | 1 + lab7/c/include/oscos/mem/vm.h | 13 ++- lab7/c/include/oscos/sched.h | 14 +--- lab7/c/include/oscos/uapi/stdio.h | 8 -- lab7/c/include/oscos/uapi/unistd.h | 4 + lab7/c/src/console-dev.c | 23 +++-- lab7/c/src/framebuffer-dev.c | 11 ++- lab7/c/src/fs/initramfs.c | 31 +++++-- lab7/c/src/fs/tmpfs.c | 15 +++- lab7/c/src/fs/vfs.c | 6 +- lab7/c/src/mem/vm.c | 83 ++++++++++++++++--- lab7/c/src/sched/sched.c | 35 +++++--- lab7/c/src/shell.c | 43 ++-------- lab7/c/src/xcpt/syscall/exec.c | 33 ++------ lab7/c/src/xcpt/syscall/mmap.c | 4 +- lab7/tests/user-program/libc/include/unistd.h | 1 - 16 files changed, 194 insertions(+), 131 deletions(-) delete mode 100644 lab7/c/include/oscos/uapi/stdio.h diff --git a/lab7/c/include/oscos/fs/vfs.h b/lab7/c/include/oscos/fs/vfs.h index d13015e03..2f903c39a 100644 --- a/lab7/c/include/oscos/fs/vfs.h +++ b/lab7/c/include/oscos/fs/vfs.h @@ -53,6 +53,7 @@ struct vnode_operations { const char *component_name); int (*mknod)(struct vnode *dir_node, struct vnode **target, const char *component_name, struct device *device); + long (*get_size)(struct vnode *vnode); }; extern struct mount rootfs; diff --git a/lab7/c/include/oscos/mem/vm.h b/lab7/c/include/oscos/mem/vm.h index 801329a90..14c3ca8fd 100644 --- a/lab7/c/include/oscos/mem/vm.h +++ b/lab7/c/include/oscos/mem/vm.h @@ -1,6 +1,7 @@ #ifndef OSCOS_MEM_VM_H #define OSCOS_MEM_VM_H +#include "oscos/fs/vfs.h" #include "oscos/mem/types.h" #include "oscos/mem/vm/page-table.h" #include "oscos/uapi/sys/mman.h" @@ -18,14 +19,20 @@ void *pa_to_kernel_va(pa_t pa) __attribute__((const)); /// physical address range. pa_range_t kernel_va_range_to_pa_range(va_range_t range) __attribute__((const)); -typedef enum { MEM_REGION_BACKED, MEM_REGION_LINEAR } mem_region_type_t; +typedef enum { + MEM_REGION_ANONYMOUS, + MEM_REGION_BACKED, + MEM_REGION_LINEAR +} mem_region_type_t; typedef struct { void *start; size_t len; mem_region_type_t type; - const void *backing_storage_start; - size_t backing_storage_len; + union { + shared_file_t *backing_file; + pa_t pa_base; + }; int prot; } mem_region_t; diff --git a/lab7/c/include/oscos/sched.h b/lab7/c/include/oscos/sched.h index 403efed6a..88df65f7b 100644 --- a/lab7/c/include/oscos/sched.h +++ b/lab7/c/include/oscos/sched.h @@ -102,11 +102,8 @@ bool process_create(void); /// succeeds. If the operation fails due to memory shortage, this function /// returns. /// -/// \param text_start The starting address of where the user program is -/// currently located. (Do not confuse it with the entry point -/// of the user program.) -/// \param text_len The length of the user program. -void exec_first(const void *text_start, size_t text_len); +/// \param text_file The user program. +void exec_first(struct file *text_file); /// \brief Executes a user program on the current process. /// @@ -116,11 +113,8 @@ void exec_first(const void *text_start, size_t text_len); /// /// The text_page_id field of the current process must be valid. /// -/// \param text_start The starting address of where the user program is -/// currently located. (Do not confuse it with the entry point -/// of the user program.) -/// \param text_len The length of the user program. -void exec(const void *text_start, size_t text_len); +/// \param text_file The user program. +void exec(struct file *text_file); /// \brief Forks the current process. process_t *fork(const extended_trap_frame_t *trap_frame); diff --git a/lab7/c/include/oscos/uapi/stdio.h b/lab7/c/include/oscos/uapi/stdio.h deleted file mode 100644 index 23ebacbc9..000000000 --- a/lab7/c/include/oscos/uapi/stdio.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef OSCOS_UAPI_STDIO_H -#define OSCOS_UAPI_STDIO_H - -#define SEEK_SET 0 -#define SEEK_END 1 -#define SEEK_CUR 2 - -#endif diff --git a/lab7/c/include/oscos/uapi/unistd.h b/lab7/c/include/oscos/uapi/unistd.h index 80efebdf4..e13b2ea8c 100644 --- a/lab7/c/include/oscos/uapi/unistd.h +++ b/lab7/c/include/oscos/uapi/unistd.h @@ -1,6 +1,10 @@ #ifndef OSCOS_UAPI_UNISTD_H #define OSCOS_UAPI_UNISTD_H +#define SEEK_SET 0 +#define SEEK_END 1 +#define SEEK_CUR 2 + typedef long ssize_t; #endif diff --git a/lab7/c/src/console-dev.c b/lab7/c/src/console-dev.c index 99fd1dd20..2c9fa1829 100644 --- a/lab7/c/src/console-dev.c +++ b/lab7/c/src/console-dev.c @@ -6,7 +6,7 @@ #include "oscos/mem/page-alloc.h" #include "oscos/mem/vm.h" #include "oscos/uapi/errno.h" -#include "oscos/uapi/stdio.h" +#include "oscos/uapi/unistd.h" #include "oscos/utils/critical-section.h" #include "oscos/utils/rb.h" @@ -26,8 +26,9 @@ static int _console_dev_create(struct vnode *dir_node, struct vnode **target, const char *component_name); static int _console_dev_mkdir(struct vnode *dir_node, struct vnode **target, const char *component_name); -int _console_mknod(struct vnode *dir_node, struct vnode **target, - const char *component_name, struct device *device); +static int _console_mknod(struct vnode *dir_node, struct vnode **target, + const char *component_name, struct device *device); +static long _console_get_size(struct vnode *vnode); struct device console_dev = {.name = "console", .setup_mount = _console_dev_setup_mount}; @@ -44,7 +45,8 @@ static struct vnode_operations _console_dev_vnode_operations = { .lookup = _console_dev_lookup, .create = _console_dev_create, .mkdir = _console_dev_mkdir, - .mknod = _console_mknod}; + .mknod = _console_mknod, + .get_size = _console_get_size}; static int _console_dev_setup_mount(struct device *const dev, struct vnode *const vnode) { @@ -139,9 +141,10 @@ static int _console_dev_mkdir(struct vnode *const dir_node, return -ENOTDIR; } -int _console_mknod(struct vnode *const dir_node, struct vnode **const target, - const char *const component_name, - struct device *const device) { +static int _console_mknod(struct vnode *const dir_node, + struct vnode **const target, + const char *const component_name, + struct device *const device) { (void)dir_node; (void)target; (void)component_name; @@ -149,3 +152,9 @@ int _console_mknod(struct vnode *const dir_node, struct vnode **const target, return -ENOTDIR; } + +static long _console_get_size(struct vnode *const vnode) { + (void)vnode; + + return -1; +} diff --git a/lab7/c/src/framebuffer-dev.c b/lab7/c/src/framebuffer-dev.c index d750c279a..e9c01669a 100644 --- a/lab7/c/src/framebuffer-dev.c +++ b/lab7/c/src/framebuffer-dev.c @@ -6,7 +6,7 @@ #include "oscos/mem/page-alloc.h" #include "oscos/mem/vm.h" #include "oscos/uapi/errno.h" -#include "oscos/uapi/stdio.h" +#include "oscos/uapi/unistd.h" #include "oscos/utils/critical-section.h" #include "oscos/utils/rb.h" @@ -43,6 +43,7 @@ static int _framebuffer_dev_mkdir(struct vnode *dir_node, struct vnode **target, static int _framebuffer_dev_mknod(struct vnode *dir_node, struct vnode **target, const char *component_name, struct device *device); +static long _framebuffer_dev_get_size(struct vnode *dir_node); struct device framebuffer_dev = {.name = "framebuffer", .setup_mount = _framebuffer_dev_setup_mount}; @@ -59,7 +60,8 @@ static struct vnode_operations _framebuffer_dev_vnode_operations = { .lookup = _framebuffer_dev_lookup, .create = _framebuffer_dev_create, .mkdir = _framebuffer_dev_mkdir, - .mknod = _framebuffer_dev_mknod}; + .mknod = _framebuffer_dev_mknod, + .get_size = _framebuffer_dev_get_size}; static int _framebuffer_dev_setup_mount(struct device *const dev, struct vnode *const vnode) { @@ -234,3 +236,8 @@ static int _framebuffer_dev_mknod(struct vnode *const dir_node, return -ENOTDIR; } + +static long _framebuffer_dev_get_size(struct vnode *const vnode) { + const framebuffer_dev_internal_t *const internal = vnode->internal; + return internal->framebuffer_size; +} diff --git a/lab7/c/src/fs/initramfs.c b/lab7/c/src/fs/initramfs.c index 576be0623..94cd79172 100644 --- a/lab7/c/src/fs/initramfs.c +++ b/lab7/c/src/fs/initramfs.c @@ -4,7 +4,7 @@ #include "oscos/libc/string.h" #include "oscos/mem/malloc.h" #include "oscos/uapi/errno.h" -#include "oscos/uapi/stdio.h" +#include "oscos/uapi/unistd.h" #include "oscos/utils/critical-section.h" #include "oscos/utils/rb.h" @@ -35,8 +35,9 @@ static int _initramfs_create(struct vnode *dir_node, struct vnode **target, const char *component_name); static int _initramfs_mkdir(struct vnode *dir_node, struct vnode **target, const char *component_name); -int _initramfs_mknod(struct vnode *dir_node, struct vnode **target, - const char *component_name, struct device *device); +static int _initramfs_mknod(struct vnode *dir_node, struct vnode **target, + const char *component_name, struct device *device); +static long _initramfs_get_size(struct vnode *vnode); struct filesystem initramfs = {.name = "initramfs", .setup_mount = _initramfs_setup_mount}; @@ -53,7 +54,8 @@ static struct vnode_operations _initramfs_vnode_operations = { .lookup = _initramfs_lookup, .create = _initramfs_create, .mkdir = _initramfs_mkdir, - .mknod = _initramfs_mknod}; + .mknod = _initramfs_mknod, + .get_size = _initramfs_get_size}; static int _initramfs_cmp_child_vnode_entries_by_component_name( const initramfs_child_vnode_entry_t *const e1, @@ -95,6 +97,9 @@ _initramfs_create_vnode(struct mount *const mount, struct vnode *const parent, static int _initramfs_setup_mount(struct filesystem *const fs, struct mount *const mount) { + if (!initrd_is_init()) + return -EIO; + struct vnode *const root_vnode = _initramfs_create_vnode(mount, NULL, NULL); if (!root_vnode) return -ENOMEM; @@ -347,9 +352,10 @@ static int _initramfs_mkdir(struct vnode *const dir_node, return -EROFS; } -int _initramfs_mknod(struct vnode *const dir_node, struct vnode **const target, - const char *const component_name, - struct device *const device) { +static int _initramfs_mknod(struct vnode *const dir_node, + struct vnode **const target, + const char *const component_name, + struct device *const device) { (void)dir_node; (void)target; (void)component_name; @@ -357,3 +363,14 @@ int _initramfs_mknod(struct vnode *const dir_node, struct vnode **const target, return -EROFS; } + +static long _initramfs_get_size(struct vnode *const vnode) { + const initramfs_internal_t *const internal = vnode->internal; + const cpio_newc_entry_t *const entry = internal->entry; + + const uint32_t mode = CPIO_NEWC_HEADER_VALUE(entry, mode); + const uint32_t file_type = mode & CPIO_NEWC_MODE_FILE_TYPE_MASK; + return file_type == CPIO_NEWC_MODE_FILE_TYPE_REG + ? (long)CPIO_NEWC_FILESIZE(entry) + : -1; +} diff --git a/lab7/c/src/fs/tmpfs.c b/lab7/c/src/fs/tmpfs.c index 982a9d9ed..f4d9f1ac5 100644 --- a/lab7/c/src/fs/tmpfs.c +++ b/lab7/c/src/fs/tmpfs.c @@ -5,7 +5,7 @@ #include "oscos/mem/page-alloc.h" #include "oscos/mem/vm.h" #include "oscos/uapi/errno.h" -#include "oscos/uapi/stdio.h" +#include "oscos/uapi/unistd.h" #include "oscos/utils/critical-section.h" #include "oscos/utils/rb.h" @@ -54,6 +54,7 @@ static int _tmpfs_mkdir(struct vnode *dir_node, struct vnode **target, const char *component_name); static int _tmpfs_mknod(struct vnode *dir_node, struct vnode **target, const char *component_name, struct device *device); +static long _tmpfs_get_size(struct vnode *vnode); struct filesystem tmpfs = {.name = "tmpfs", .setup_mount = _tmpfs_setup_mount}; @@ -69,7 +70,8 @@ static struct vnode_operations _tmpfs_vnode_operations = { .lookup = _tmpfs_lookup, .create = _tmpfs_create, .mkdir = _tmpfs_mkdir, - .mknod = _tmpfs_mknod}; + .mknod = _tmpfs_mknod, + .get_size = _tmpfs_get_size}; static int _tmpfs_cmp_dir_contents_entries(const tmpfs_dir_contents_entry_t *const e1, @@ -462,3 +464,12 @@ static int _tmpfs_mknod(struct vnode *const dir_node, CRITICAL_SECTION_LEAVE(daif_val); return result; } + +static long _tmpfs_get_size(struct vnode *const vnode) { + const tmpfs_internal_t *const internal = vnode->internal; + if (internal->type != TYPE_FILE) + return -1; + + const tmpfs_internal_file_data_t *const file_data = &internal->file_data; + return file_data->size; +} diff --git a/lab7/c/src/fs/vfs.c b/lab7/c/src/fs/vfs.c index 091450be5..2ecd67b55 100644 --- a/lab7/c/src/fs/vfs.c +++ b/lab7/c/src/fs/vfs.c @@ -302,7 +302,11 @@ int vfs_mount_relative(struct vnode *const cwd, const char *const target, if (!mount) return -ENOMEM; - fs->setup_mount(fs, mount); + const int setup_mount_result = fs->setup_mount(fs, mount); + if (setup_mount_result < 0) { + free(mount); + return setup_mount_result; + } const mount_entry_t entry = {.mountpoint = mountpoint, .mount = mount}; diff --git a/lab7/c/src/mem/vm.c b/lab7/c/src/mem/vm.c index 6d18e7648..60ab8f2de 100644 --- a/lab7/c/src/mem/vm.c +++ b/lab7/c/src/mem/vm.c @@ -7,6 +7,7 @@ #include "oscos/mem/page-alloc.h" #include "oscos/mem/shared-page.h" #include "oscos/sched.h" +#include "oscos/uapi/unistd.h" #include "oscos/utils/align.h" #include "oscos/utils/critical-section.h" @@ -107,9 +108,26 @@ static void _vm_clone_pgd(page_table_entry_t *const pgd) { CRITICAL_SECTION_LEAVE(daif_val); } +static bool _vm_mem_regions_cloner(mem_region_t *const dst, + const mem_region_t *const src) { + *dst = *src; + if (src->type == MEM_REGION_BACKED) { + dst->backing_file = shared_file_clone(src->backing_file); + } + return true; +} + +static void _vm_mem_regions_deleter(mem_region_t *const region) { + if (region->type == MEM_REGION_BACKED) { + shared_file_drop(region->backing_file); + } +} + vm_addr_space_t vm_clone_addr_space(const vm_addr_space_t addr_space) { rb_node_t *const new_regions_root = - rb_clone(addr_space.mem_regions.root, sizeof(mem_region_t), NULL, NULL); + rb_clone(addr_space.mem_regions.root, sizeof(mem_region_t), + (bool (*)(void *, const void *))_vm_mem_regions_cloner, + (void (*)(void *))_vm_mem_regions_deleter); if (!new_regions_root) return (vm_addr_space_t){.mem_regions = (mem_regions_t){.root = NULL}}; @@ -153,7 +171,8 @@ static void _vm_drop_pgd(page_table_entry_t *const pgd) { void vm_drop_addr_space(const vm_addr_space_t addr_space) { _vm_drop_pgd(addr_space.pgd); - rb_drop(addr_space.mem_regions.root, NULL); + rb_drop(addr_space.mem_regions.root, + (void (*)(void *))_vm_mem_regions_deleter); } static page_table_entry_t * @@ -235,19 +254,59 @@ static void _init_backed_page(const mem_region_t *const mem_region, (void *)((uintptr_t)va & ~((1 << PAGE_ORDER) - 1)); size_t offset = (char *)va_page_start - (char *)mem_region->start; - const size_t copy_len = - offset > mem_region->backing_storage_len ? 0 - : mem_region->backing_storage_len - offset > 1 << PAGE_ORDER - ? 1 << PAGE_ORDER - : mem_region->backing_storage_len - offset; - memcpy(kernel_va, (char *)mem_region->backing_storage_start + offset, - copy_len); - memset((char *)kernel_va + copy_len, 0, (1 << PAGE_ORDER) - copy_len); + // We need to ensure that the seek-read sequence is atomic without using + // pread, hence the critical section. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const int seek_result = + vfs_lseek64(mem_region->backing_file->file, offset, SEEK_SET); + if (seek_result < 0) { + console_printf("ERROR: vm: (PID %zu) Cannot seek backing file: errno %d\n", + current_thread()->process->id, -seek_result); + thread_exit(); + } + + size_t n_bytes_read = 0; + while (n_bytes_read < 1 << PAGE_ORDER) { + const int n_bytes_just_read = vfs_read(mem_region->backing_file->file, + (char *)kernel_va + n_bytes_read, + (1 << PAGE_ORDER) - n_bytes_read); + if (n_bytes_just_read < 0) { + console_printf( + "ERROR: vm: (PID %zu) Cannot read backing file: errno %d\n", + current_thread()->process->id, -n_bytes_just_read); + thread_exit(); + } + if (n_bytes_just_read == 0) + break; + + n_bytes_read += n_bytes_just_read; + } + + CRITICAL_SECTION_LEAVE(daif_val); + + memset((char *)kernel_va + n_bytes_read, 0, (1 << PAGE_ORDER) - n_bytes_read); } static bool _map_page(const mem_region_t *const mem_region, void *const va, page_table_entry_t *const pte_entry) { switch (mem_region->type) { + case MEM_REGION_ANONYMOUS: { + const spage_id_t page_id = shared_page_alloc(); + if (page_id < 0) { + return false; + } + + const pa_t page_pa = page_id_to_pa(page_id); + pte_entry->addr = page_pa >> PAGE_ORDER; + + memset(pa_to_kernel_va(page_pa), 0, 1 << PAGE_ORDER); + + break; + } + case MEM_REGION_BACKED: { const spage_id_t page_id = shared_page_alloc(); if (page_id < 0) { @@ -264,8 +323,7 @@ static bool _map_page(const mem_region_t *const mem_region, void *const va, case MEM_REGION_LINEAR: { const size_t offset = (uintptr_t)va - (uintptr_t)mem_region->start; - pte_entry->addr = - ((uintptr_t)mem_region->backing_storage_start + offset) >> PAGE_ORDER; + pte_entry->addr = (mem_region->pa_base + offset) >> PAGE_ORDER; break; } @@ -412,6 +470,7 @@ vm_map_page_result_t vm_map_page(vm_addr_space_t *const addr_space, static bool _cow_page(const mem_region_t *const mem_region, page_table_entry_t *const pte_entry) { switch (mem_region->type) { + case MEM_REGION_ANONYMOUS: case MEM_REGION_BACKED: { const page_id_t src_page_id = pa_to_page_id(pte_entry->addr << PAGE_ORDER); const spage_id_t page_id = shared_page_clone_unshare(src_page_id); diff --git a/lab7/c/src/sched/sched.c b/lab7/c/src/sched/sched.c index 7a371dfdf..4deffe3e0 100644 --- a/lab7/c/src/sched/sched.c +++ b/lab7/c/src/sched/sched.c @@ -300,17 +300,14 @@ bool process_create(void) { const mem_region_t stack_region = {.start = (void *)0xffffffffb000ULL, .len = 4 << PAGE_ORDER, - .type = MEM_REGION_BACKED, - .backing_storage_start = NULL, - .backing_storage_len = 0, + .type = MEM_REGION_ANONYMOUS, .prot = PROT_READ | PROT_WRITE}; vm_mem_regions_insert_region(&process->addr_space.mem_regions, &stack_region); const mem_region_t vc_region = {.start = (void *)0x3b400000ULL, .len = 0x3f000000ULL - 0x3b400000ULL, .type = MEM_REGION_LINEAR, - .backing_storage_start = - (void *)0x3b400000ULL, + .pa_base = 0x3b400000, .prot = PROT_READ | PROT_WRITE}; vm_mem_regions_insert_region(&process->addr_space.mem_regions, &vc_region); @@ -350,19 +347,34 @@ void switch_vm(const thread_t *const thread) { } } -static void _exec_generic(const void *const text_start, const size_t text_len, +static void _exec_generic(struct file *const text_file, const bool remove_text_region) { thread_t *const curr_thread = current_thread(); process_t *const curr_process = curr_thread->process; + const long text_len = text_file->vnode->v_ops->get_size(text_file->vnode); + if (text_len < 0) + return; + + shared_file_t *const shared_text_file = shared_file_new(text_file); + if (!shared_text_file) + return; + // Remove old text region. if (remove_text_region) { + shared_file_t *const old_text_file = + vm_mem_regions_find_region(&curr_process->addr_space.mem_regions, + (void *)0x0) + ->backing_file; + if (!vm_remove_region(&curr_process->addr_space, (void *)0x0)) return; // Set ttbr0_el1 again, as it might have changed. vm_switch_to_addr_space(&curr_process->addr_space); + + shared_file_drop(old_text_file); } // We don't know exactly how large the .bss section of a user program is, so @@ -373,8 +385,7 @@ static void _exec_generic(const void *const text_start, const size_t text_len, const mem_region_t text_region = {.start = (void *)0x0, .len = ALIGN(text_len * 2, 4096), .type = MEM_REGION_BACKED, - .backing_storage_start = text_start, - .backing_storage_len = text_len, + .backing_file = shared_text_file, .prot = PROT_READ | PROT_WRITE | PROT_EXEC}; vm_mem_regions_insert_region(&curr_process->addr_space.mem_regions, &text_region); @@ -388,13 +399,11 @@ static void _exec_generic(const void *const text_start, const size_t text_len, user_program_main((void *)0x0, (void *)0xfffffffff000ULL, kernel_stack_end); } -void exec_first(const void *const text_start, const size_t text_len) { - _exec_generic(text_start, text_len, false); +void exec_first(struct file *const text_file) { + _exec_generic(text_file, false); } -void exec(const void *const text_start, const size_t text_len) { - _exec_generic(text_start, text_len, true); -} +void exec(struct file *const text_file) { _exec_generic(text_file, true); } static void _thread_cleanup(thread_t *const thread) { if (thread->process) { diff --git a/lab7/c/src/shell.c b/lab7/c/src/shell.c index 7ac05bb53..ba0e6d6ba 100644 --- a/lab7/c/src/shell.c +++ b/lab7/c/src/shell.c @@ -122,41 +122,16 @@ static void _shell_do_cmd_cat(void) { } static void _shell_do_cmd_exec(void) { - if (!initrd_is_init()) { - console_puts("oscsh: exec: initrd is invalid"); - return; - } - - console_fputs("Filename: "); - - char filename_buf[MAX_CMD_LEN + 1]; - _shell_read_line(filename_buf, MAX_CMD_LEN + 1); - - const cpio_newc_entry_t *const entry = - initrd_find_entry_by_pathname(filename_buf); - if (!entry) { - console_puts("oscsh: exec: no such file or directory"); - return; - } + console_fputs("Path: "); - const void *user_program_start; - size_t user_program_len; + char path_buf[MAX_CMD_LEN + 1]; + _shell_read_line(path_buf, MAX_CMD_LEN + 1); - const uint32_t mode = CPIO_NEWC_HEADER_VALUE(entry, mode); - const uint32_t file_type = mode & CPIO_NEWC_MODE_FILE_TYPE_MASK; - if (file_type == CPIO_NEWC_MODE_FILE_TYPE_REG) { - user_program_start = CPIO_NEWC_FILE_DATA(entry); - user_program_len = CPIO_NEWC_FILESIZE(entry); - } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_DIR) { - console_puts("oscsh: exec: is a directory"); - return; - } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_LNK) { - console_fputs("oscos: exec: is a symbolic link to: "); - console_write(CPIO_NEWC_FILE_DATA(entry), CPIO_NEWC_FILESIZE(entry)); - console_putc('\n'); - return; - } else { - console_puts("oscsh: exec: unknown file type"); + struct file *user_program_file; + const int open_result = vfs_open(path_buf, 0, &user_program_file); + if (open_result < 0) { + console_printf("oscsh: exec: cannot open user program: errno %d\n", + -open_result); return; } @@ -165,7 +140,7 @@ static void _shell_do_cmd_exec(void) { return; } - exec_first(user_program_start, user_program_len); + exec_first(user_program_file); // If execution reaches here, then exec failed. console_puts("oscsh: exec: out of memory"); diff --git a/lab7/c/src/xcpt/syscall/exec.c b/lab7/c/src/xcpt/syscall/exec.c index b8a10cf2e..698e14089 100644 --- a/lab7/c/src/xcpt/syscall/exec.c +++ b/lab7/c/src/xcpt/syscall/exec.c @@ -9,35 +9,12 @@ int sys_exec(const char *const name, char *const argv[const]) { // TODO: Handle this. (void)argv; - if (!initrd_is_init()) { - // Act as if there are no files at all. - return -ENOENT; - } + struct file *user_program_file; + const int open_result = vfs_open(name, 0, &user_program_file); + if (open_result < 0) + return open_result; - const cpio_newc_entry_t *const entry = initrd_find_entry_by_pathname(name); - if (!entry) { - return -ENOENT; - } - - const void *user_program_start; - size_t user_program_len; - - const uint32_t mode = CPIO_NEWC_HEADER_VALUE(entry, mode); - const uint32_t file_type = mode & CPIO_NEWC_MODE_FILE_TYPE_MASK; - if (file_type == CPIO_NEWC_MODE_FILE_TYPE_REG) { - user_program_start = CPIO_NEWC_FILE_DATA(entry); - user_program_len = CPIO_NEWC_FILESIZE(entry); - } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_DIR) { - return -EISDIR; - } else if (file_type == CPIO_NEWC_MODE_FILE_TYPE_LNK) { - // TODO: Handle symbolic link. - return -ELOOP; - } else { // Unknown file type. - // Just treat it as an I/O error. - return -EIO; - } - - exec(user_program_start, user_program_len); + exec(user_program_file); // If execution reaches here, then exec failed. return -ENOMEM; diff --git a/lab7/c/src/xcpt/syscall/mmap.c b/lab7/c/src/xcpt/syscall/mmap.c index d887be094..9a6c9fd96 100644 --- a/lab7/c/src/xcpt/syscall/mmap.c +++ b/lab7/c/src/xcpt/syscall/mmap.c @@ -22,9 +22,7 @@ void *sys_mmap(void *const addr, const size_t len, const int prot, const mem_region_t mem_region = {.start = mmap_addr, .len = ALIGN(len, 1 << PAGE_ORDER), - .type = MEM_REGION_BACKED, - .backing_storage_start = NULL, - .backing_storage_len = 0, + .type = MEM_REGION_ANONYMOUS, .prot = prot}; vm_mem_regions_insert_region(&curr_process->addr_space.mem_regions, &mem_region); diff --git a/lab7/tests/user-program/libc/include/unistd.h b/lab7/tests/user-program/libc/include/unistd.h index 96e427fd9..8fd615423 100644 --- a/lab7/tests/user-program/libc/include/unistd.h +++ b/lab7/tests/user-program/libc/include/unistd.h @@ -3,7 +3,6 @@ #include -#include "oscos-uapi/stdio.h" // SEEK_* #include "oscos-uapi/unistd.h" typedef int pid_t; From 9b0608636a8beca6c97e95b16c462dd498738cb5 Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Wed, 18 Oct 2023 00:40:34 +0800 Subject: [PATCH 30/34] Lab 8 C: Fix issues with FAT - Stops assuming that the number of sectors per cluster is 1. - Fix memory leak in `_sd_fat32_read`. - Slightly clean up `_sd_fat32_read`. - Remove useless code in `_sd_fat32_lseek64`. - Adds component name check in `_sd_fat32_lookup`. - Stops assuming that directories live in 1 cluster. --- lab8/c/include/oscos/libc/ctype.h | 6 + lab8/c/src/fs/sd-fat32.c | 204 +++++++++++++++++++----------- lab8/c/src/libc/ctype.c | 10 ++ 3 files changed, 146 insertions(+), 74 deletions(-) diff --git a/lab8/c/include/oscos/libc/ctype.h b/lab8/c/include/oscos/libc/ctype.h index 6e5eb134a..0764a113e 100644 --- a/lab8/c/include/oscos/libc/ctype.h +++ b/lab8/c/include/oscos/libc/ctype.h @@ -1,6 +1,12 @@ #ifndef OSCOS_LIBC_CTYPE_H #define OSCOS_LIBC_CTYPE_H +int isalnum(int c); +int isalpha(int c); int isdigit(int c); +int islower(int c); +int isupper(int c); + +int toupper(int c); #endif diff --git a/lab8/c/src/fs/sd-fat32.c b/lab8/c/src/fs/sd-fat32.c index a5d4cfd73..5d37d6a62 100644 --- a/lab8/c/src/fs/sd-fat32.c +++ b/lab8/c/src/fs/sd-fat32.c @@ -1,8 +1,9 @@ -#include "oscos/fs/initramfs.h" +#include "oscos/fs/sd-fat32.h" #include "oscos/console.h" #include "oscos/drivers/sdhost.h" #include "oscos/initrd.h" +#include "oscos/libc/ctype.h" #include "oscos/libc/string.h" #include "oscos/mem/malloc.h" #include "oscos/uapi/errno.h" @@ -122,6 +123,54 @@ _sd_fat32_cluster_addr_to_fat_lba(const fat32_fsinfo_t *const fsinfo, return fsinfo->partition_lba + fsinfo->fat_lba_offset + (cluster_addr >> 7); } +static uint32_t _sd_fat32_read_fat(const fat32_fsinfo_t *const fsinfo, + const size_t cluster_addr, + unsigned char block_buf[static 512]) { + readblock(_sd_fat32_cluster_addr_to_fat_lba(fsinfo, cluster_addr), block_buf); + const uint32_t *const fat_entries = (const uint32_t *)block_buf; + return fat_entries[cluster_addr & ((1 << 7) - 1)] & 0x0fffffff; +} + +static bool _sd_fat32_is_component_name_char_valid_lax(const char c) { + const unsigned char u = c; + return isalnum(c) || strchr(" !#$%&'()-@^_`{}~", c) || u >= 128; +} + +static char _sd_fat32_map_component_name_char(const char c) { + if (!_sd_fat32_is_component_name_char_valid_lax(c)) + __builtin_unreachable(); + + return islower(c) ? toupper(c) : (unsigned char)c == 0xe5 ? 0x05 : c; +} + +static bool _sd_fat32_check_and_map_filename(const char *const filename, + char target[static 11]) { + const char *const last_dot = strrchr(filename, '.'); + const size_t noext_len = + last_dot ? (size_t)(last_dot - filename) : strlen(filename); + if (noext_len > 8) + return false; + const size_t ext_len = last_dot ? strlen(filename) - noext_len - 1 : 0; + if (ext_len > 3) + return false; + + for (const char *c = filename; *c; c++) { + if (c != last_dot && !_sd_fat32_is_component_name_char_valid_lax(*c)) + return false; + } + + for (size_t i = 0; i < 8; i++) { + target[i] = + i < noext_len ? _sd_fat32_map_component_name_char(filename[i]) : ' '; + } + for (size_t i = 0; i < 3; i++) { + target[i + 8] = + i < ext_len ? _sd_fat32_map_component_name_char(last_dot[1 + i]) : ' '; + } + + return true; +} + static int _sd_fat32_cmp_child_vnode_entries_by_component_name( const sd_fat32_child_vnode_entry_t *const e1, const sd_fat32_child_vnode_entry_t *const e2, void *const _arg) { @@ -257,6 +306,8 @@ static int _sd_fat32_read(struct file *const file, void *const buf, return -EISDIR; sd_fat32_internal_file_data_t *const file_data = &internal->file_data; + const fat32_fsinfo_t *const fsinfo = + ((sd_fat32_internal_t *)(file->vnode->mount->root->internal))->fsinfo; unsigned char *const block_buf = malloc(512); if (!block_buf) @@ -265,51 +316,52 @@ static int _sd_fat32_read(struct file *const file, void *const buf, uint64_t daif_val; CRITICAL_SECTION_ENTER(daif_val); + const size_t read_end_offset = + file->f_pos + len < file_data->size ? file->f_pos + len : file_data->size; + size_t curr_cluster_addr = file_data->cluster_addr, file_offset = 0, n_chars_read = 0; for (;;) { - // Read the next cluster address. - - readblock(_sd_fat32_cluster_addr_to_fat_lba( - ((sd_fat32_internal_t *)(file->vnode->mount->root->internal)) - ->fsinfo, - curr_cluster_addr), - block_buf); - const uint32_t *const fat_entries = (const uint32_t *)block_buf; - const size_t next_cluster_addr = - fat_entries[curr_cluster_addr >> 7] & 0x0fffffff; - - const size_t end_offset = file_offset + 512 > file_data->size - ? file_data->size - : file_offset + 512; - const size_t max_start = - file_offset > file->f_pos ? file_offset : file->f_pos, - min_end = end_offset < file->f_pos + len ? end_offset - : file->f_pos + len; - if (max_start < min_end) { - readblock( - _sd_fat32_cluster_addr_to_data_lba( - ((sd_fat32_internal_t *)(file->vnode->mount->root->internal)) - ->fsinfo, - curr_cluster_addr), - block_buf); - memcpy((char *)buf + n_chars_read, block_buf + (max_start & 511), - min_end - max_start); - n_chars_read += min_end - max_start; + for (size_t sector_of_cluster = 0; + sector_of_cluster < fsinfo->cluster_n_sectors; sector_of_cluster++) { + const size_t end_offset = file_offset + 512; + const size_t max_start = + file_offset > file->f_pos ? file_offset : file->f_pos, + min_end = end_offset < read_end_offset ? end_offset + : read_end_offset; + if (max_start < min_end) { + readblock( + _sd_fat32_cluster_addr_to_data_lba(fsinfo, curr_cluster_addr) + + sector_of_cluster, + block_buf); + memcpy((char *)buf + n_chars_read, block_buf + (max_start & 511), + min_end - max_start); + n_chars_read += min_end - max_start; + } + + file_offset += 512; + if (file_offset >= read_end_offset) // Done reading. + break; } - if (file_offset + 512 >= file->f_pos + len) // Done reading. + if (file_offset >= read_end_offset) // Done reading. break; - if (!(1 < next_cluster_addr && next_cluster_addr && - 0xfffffff8)) // Nothing more to read. + + const uint32_t fat_entry = + _sd_fat32_read_fat(fsinfo, curr_cluster_addr, block_buf); + if (!(0x2 <= fat_entry && fat_entry <= 0x0ffffff7)) { // No more chains. + // The file has a hole, which should read zero. + memset(block_buf + n_chars_read, 0, read_end_offset - file_offset); + n_chars_read += read_end_offset - file_offset; break; + } - curr_cluster_addr = next_cluster_addr; - file_offset += 512; + curr_cluster_addr = fat_entry; } file->f_pos += n_chars_read; + free(block_buf); CRITICAL_SECTION_LEAVE(daif_val); return n_chars_read; } @@ -362,8 +414,6 @@ static long _sd_fat32_lseek64(struct file *const file, const long offset, file->f_pos = new_f_pos; return 0; } - - return -ENOSYS; } static int _sd_fat32_lookup(struct vnode *const dir_node, @@ -375,6 +425,8 @@ static int _sd_fat32_lookup(struct vnode *const dir_node, return -ENOTDIR; sd_fat32_internal_dir_data_t *const dir_data = &internal->dir_data; + const fat32_fsinfo_t *const fsinfo = + ((sd_fat32_internal_t *)(dir_node->mount->root->internal))->fsinfo; if (strcmp(component_name, ".") == 0) { *target = dir_node; @@ -402,53 +454,57 @@ static int _sd_fat32_lookup(struct vnode *const dir_node, return 0; } - // A vnode has not been created before. Check if the file/directory exists. - - char filename_buf[11] = " "; - - const char *const component_name_last_dot_ptr = strrchr(component_name, '.'); - const size_t component_name_noext_len = - component_name_last_dot_ptr - ? (size_t)(component_name_last_dot_ptr - component_name) - : strlen(component_name), - component_name_noext_cpy_len = - component_name_noext_len > 8 ? 8 : component_name_noext_len; - memcpy(filename_buf, component_name, component_name_noext_cpy_len); + // A vnode has not been created before. + // Check the filename. - const size_t component_name_ext_len = - component_name_last_dot_ptr - ? strlen(component_name_last_dot_ptr + 1) - : 0, - component_name_ext_cpy_len = - component_name_ext_len > 3 ? 3 : component_name_ext_len; - memcpy(filename_buf + 8, component_name_last_dot_ptr + 1, - component_name_ext_cpy_len); + char filename_buf[11]; + if (!_sd_fat32_check_and_map_filename( + component_name, filename_buf)) // Invalid component name. + return -ENOENT; unsigned char *const block_buf = malloc(512); if (!block_buf) return -ENOMEM; - readblock( - _sd_fat32_cluster_addr_to_data_lba( - ((sd_fat32_internal_t *)(dir_node->mount->root->internal))->fsinfo, - dir_data->cluster_addr), - block_buf); - + size_t curr_cluster_addr = dir_data->cluster_addr; const fatdir_t *dir_entry = NULL; - for (size_t offset = 0; offset < 512; offset += sizeof(fatdir_t)) { - const fatdir_t *const curr_dir_entry = - (const fatdir_t *)(block_buf + offset); - if (curr_dir_entry->name[0] == 0) - break; + bool done = false; + while (!done) { + for (size_t sector_of_cluster = 0; + sector_of_cluster < fsinfo->cluster_n_sectors; sector_of_cluster++) { + readblock(_sd_fat32_cluster_addr_to_data_lba(fsinfo, curr_cluster_addr) + + sector_of_cluster, + block_buf); + + for (size_t offset = 0; offset < 512; offset += sizeof(fatdir_t)) { + const fatdir_t *const curr_dir_entry = + (const fatdir_t *)(block_buf + offset); + if (curr_dir_entry->name[0] == 0) { + done = true; + break; + } + + if (curr_dir_entry->name[0] == 0xe5 || + curr_dir_entry->attr[0] == 0xf) // Invalid entry. + continue; + + if (memcmp(curr_dir_entry->name, filename_buf, 11) == 0) { + dir_entry = curr_dir_entry; + done = true; + break; + } + } + } - if (curr_dir_entry->name[0] == 0xe5 || - curr_dir_entry->attr[0] == 0xf) // Invalid entry. - continue; + if (done) + break; - if (memcmp(curr_dir_entry->name, filename_buf, 11) == 0) { - dir_entry = curr_dir_entry; + const uint32_t fat_entry = + _sd_fat32_read_fat(fsinfo, curr_cluster_addr, block_buf); + if (!(0x2 <= fat_entry && fat_entry <= 0x0ffffff7)) // Nothing more to read. break; - } + + curr_cluster_addr = fat_entry; } if (!dir_entry) { @@ -459,7 +515,7 @@ static int _sd_fat32_lookup(struct vnode *const dir_node, // Create a new vnode. const bool is_dir = dir_entry->attr[0] & 0x10; - const size_t cluster_addr = (dir_entry->ch << 16) + dir_entry->cl; + const size_t cluster_addr = ((size_t)dir_entry->ch << 16) + dir_entry->cl; const size_t file_size = dir_entry->size; free(block_buf); struct vnode *const vnode = diff --git a/lab8/c/src/libc/ctype.c b/lab8/c/src/libc/ctype.c index 6ff704afb..38c56102f 100644 --- a/lab8/c/src/libc/ctype.c +++ b/lab8/c/src/libc/ctype.c @@ -1,3 +1,13 @@ #include "oscos/libc/ctype.h" +int isalnum(const int c) { return isalpha(c) || isdigit(c); } + +int isalpha(const int c) { return isupper(c) || islower(c); } + int isdigit(const int c) { return '0' <= c && c <= '9'; } + +int islower(const int c) { return 'a' <= c && c <= 'z'; } + +int isupper(const int c) { return 'A' <= c && c <= 'Z'; } + +int toupper(const int c) { return islower(c) ? c | 0x20 : c; } From 9bcd6ec767a99e2e8e5c1ee6510da17ce2c38c3e Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Wed, 18 Oct 2023 03:54:55 +0800 Subject: [PATCH 31/34] Lab 8 C: Complete B2 --- lab8/c/include/oscos/uapi/errno.h | 1 + lab8/c/src/fs/sd-fat32.c | 323 ++++++++++++++++++++++++++++-- 2 files changed, 309 insertions(+), 15 deletions(-) diff --git a/lab8/c/include/oscos/uapi/errno.h b/lab8/c/include/oscos/uapi/errno.h index 78a8da16d..64ef2eac0 100644 --- a/lab8/c/include/oscos/uapi/errno.h +++ b/lab8/c/include/oscos/uapi/errno.h @@ -13,6 +13,7 @@ #define EMFILE 24 #define ENOTTY 25 #define EFBIG 27 +#define ENOSPC 28 #define ESPIPE 29 #define EROFS 30 #define ENOSYS 38 diff --git a/lab8/c/src/fs/sd-fat32.c b/lab8/c/src/fs/sd-fat32.c index 5d37d6a62..8334c7b6f 100644 --- a/lab8/c/src/fs/sd-fat32.c +++ b/lab8/c/src/fs/sd-fat32.c @@ -48,7 +48,7 @@ typedef struct { } __attribute__((packed)) fatdir_t; typedef struct { - size_t partition_lba, fat_lba_offset, data_region_lba_offset, + size_t partition_lba, fat_lba_offset, fat_n_sectors, data_region_lba_offset, cluster_n_sectors; } fat32_fsinfo_t; @@ -131,6 +131,16 @@ static uint32_t _sd_fat32_read_fat(const fat32_fsinfo_t *const fsinfo, return fat_entries[cluster_addr & ((1 << 7) - 1)] & 0x0fffffff; } +static void _sd_fat32_write_fat(const fat32_fsinfo_t *const fsinfo, + const size_t cluster_addr, const uint32_t value, + unsigned char block_buf[static 512]) { + readblock(_sd_fat32_cluster_addr_to_fat_lba(fsinfo, cluster_addr), block_buf); + uint32_t *const fat_entries = (uint32_t *)block_buf; + fat_entries[cluster_addr & ((1 << 7) - 1)] = value; + writeblock(_sd_fat32_cluster_addr_to_fat_lba(fsinfo, cluster_addr), + block_buf); +} + static bool _sd_fat32_is_component_name_char_valid_lax(const char c) { const unsigned char u = c; return isalnum(c) || strchr(" !#$%&'()-@^_`{}~", c) || u >= 128; @@ -171,6 +181,50 @@ static bool _sd_fat32_check_and_map_filename(const char *const filename, return true; } +static size_t _held_cluster_addr = -1; + +static size_t _sd_fat32_find_free_cluster(const fat32_fsinfo_t *const fsinfo, + unsigned char block_buf[static 512]) { + const size_t max_n_clusters = fsinfo->fat_n_sectors << 7; + for (size_t cluster_addr = 0; cluster_addr < max_n_clusters; cluster_addr++) { + if (cluster_addr != _held_cluster_addr && + _sd_fat32_read_fat(fsinfo, cluster_addr, block_buf) == 0) { + return cluster_addr; + } + } + return -1; +} + +static size_t +_sd_fat32_find_free_cluster_and_hold(const fat32_fsinfo_t *const fsinfo, + unsigned char block_buf[static 512]) { + return _held_cluster_addr = _sd_fat32_find_free_cluster(fsinfo, block_buf); +} + +static size_t _sd_fat32_alloc_free_cluster_and_extend_chain( + const fat32_fsinfo_t *const fsinfo, const size_t chain_end_cluster_addr, + unsigned char block_buf[static 512]) { + const size_t new_cluster_addr = + _sd_fat32_find_free_cluster(fsinfo, block_buf); + if (new_cluster_addr == (size_t)-1) + return -1; + + _sd_fat32_write_fat(fsinfo, new_cluster_addr, 0x0fffffff, block_buf); + if (chain_end_cluster_addr != (size_t)-1) { + _sd_fat32_write_fat(fsinfo, chain_end_cluster_addr, new_cluster_addr, + block_buf); + } + + return new_cluster_addr; +} + +static void _sd_fat32_alloc_held_cluster(const fat32_fsinfo_t *const fsinfo, + unsigned char block_buf[static 512]) { + _sd_fat32_write_fat(fsinfo, _held_cluster_addr, 0x0fffffff, block_buf); +} + +static void _sd_fat32_unhold_cluster(void) { _held_cluster_addr = -1; } + static int _sd_fat32_cmp_child_vnode_entries_by_component_name( const sd_fat32_child_vnode_entry_t *const e1, const sd_fat32_child_vnode_entry_t *const e2, void *const _arg) { @@ -264,6 +318,7 @@ static int _sd_fat32_setup_mount(struct filesystem *const fs, bpb_t *const bpb = (bpb_t *)block_buf; *fsinfo = (fat32_fsinfo_t){.partition_lba = partition_lba, .fat_lba_offset = bpb->rsc, + .fat_n_sectors = bpb->spf32, .data_region_lba_offset = bpb->rsc + bpb->nf * bpb->spf32 - 2 * bpb->spc, .cluster_n_sectors = bpb->spc}; @@ -291,11 +346,88 @@ static int _sd_fat32_setup_mount(struct filesystem *const fs, static int _sd_fat32_write(struct file *const file, const void *const buf, const size_t len) { - (void)file; - (void)buf; - (void)len; + sd_fat32_internal_t *const internal = + (sd_fat32_internal_t *)file->vnode->internal; + if (internal->type != TYPE_FILE) + return -EISDIR; + + sd_fat32_internal_file_data_t *const file_data = &internal->file_data; + const fat32_fsinfo_t *const fsinfo = + ((sd_fat32_internal_t *)(file->vnode->mount->root->internal))->fsinfo; + + unsigned char *const block_buf = malloc(512); + if (!block_buf) + return -ENOMEM; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + const size_t write_end_offset = file->f_pos + len; + + size_t curr_cluster_addr = file_data->cluster_addr, file_offset = 0, + n_chars_written = 0; + for (;;) { + for (size_t sector_of_cluster = 0; + sector_of_cluster < fsinfo->cluster_n_sectors; sector_of_cluster++) { + const size_t end_offset = file_offset + 512; + const size_t max_start = + file_offset > file->f_pos ? file_offset : file->f_pos, + min_end = end_offset < write_end_offset ? end_offset + : write_end_offset; + if (max_start < min_end) { + readblock( + _sd_fat32_cluster_addr_to_data_lba(fsinfo, curr_cluster_addr) + + sector_of_cluster, + block_buf); + memcpy(block_buf + (max_start & 511), (char *)buf + n_chars_written, + min_end - max_start); + writeblock( + _sd_fat32_cluster_addr_to_data_lba(fsinfo, curr_cluster_addr) + + sector_of_cluster, + block_buf); + n_chars_written += min_end - max_start; + } + + file_offset += 512; + if (file_offset >= write_end_offset) // Done writing. + break; + } - return -EROFS; + if (file_offset >= write_end_offset) // Done writing. + break; + + const uint32_t fat_entry = + _sd_fat32_read_fat(fsinfo, curr_cluster_addr, block_buf); + if (!(0x2 <= fat_entry && fat_entry <= 0x0ffffff7)) { // No more chains. + curr_cluster_addr = _sd_fat32_alloc_free_cluster_and_extend_chain( + fsinfo, curr_cluster_addr, block_buf); + if (curr_cluster_addr == (size_t)-1) { // Out of space. + free(block_buf); + CRITICAL_SECTION_LEAVE(daif_val); + return -ENOSPC; + } + + for (size_t sector_of_cluster = 0; + sector_of_cluster < fsinfo->cluster_n_sectors; sector_of_cluster++) { + memset(block_buf, 0, 512); + writeblock( + _sd_fat32_cluster_addr_to_data_lba(fsinfo, curr_cluster_addr) + + sector_of_cluster, + block_buf); + } + } else { + curr_cluster_addr = fat_entry; + } + } + + file->f_pos += n_chars_written; + if (file_data->size > file->f_pos) { + file_data->size = file->f_pos; + } + + free(block_buf); + CRITICAL_SECTION_LEAVE(daif_val); + return n_chars_written; } static int _sd_fat32_read(struct file *const file, void *const buf, @@ -550,22 +682,183 @@ static int _sd_fat32_lookup(struct vnode *const dir_node, return 0; } +static int _sd_fat32_create_impl(struct vnode *const dir_node, + struct vnode **const target, + const char *const component_name, + const bool is_creating_dir) { + sd_fat32_internal_t *const internal = + (sd_fat32_internal_t *)dir_node->internal; + if (internal->type != TYPE_DIR) + return -ENOTDIR; + + sd_fat32_internal_dir_data_t *const dir_data = &internal->dir_data; + const fat32_fsinfo_t *const fsinfo = + ((sd_fat32_internal_t *)(dir_node->mount->root->internal))->fsinfo; + + // Check the filename. + + if (strcmp(component_name, ".") == 0) { + return -EINVAL; + } else if (strcmp(component_name, "..") == 0) { + return -EINVAL; + } + + char filename_buf[11]; + if (!_sd_fat32_check_and_map_filename(component_name, filename_buf)) + return -EINVAL; + + // Check if the file already exists. + + if (_sd_fat32_lookup(dir_node, target, component_name) == 0) + return -EEXIST; + + unsigned char *const block_buf = malloc(512); + if (!block_buf) + return -ENOMEM; + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + // Find an empty entry in the FAT. + + const size_t new_file_cluster_addr = + _sd_fat32_find_free_cluster_and_hold(fsinfo, block_buf); + if (new_file_cluster_addr == (size_t)-1) { + free(block_buf); + CRITICAL_SECTION_LEAVE(daif_val); + return -ENOSPC; + } + + // Allocate things early, + // so that we won't have to unroll a lot of FS state later. + + struct vnode *const vnode = + is_creating_dir ? _sd_fat32_create_dir_vnode(dir_node->mount, dir_node, + new_file_cluster_addr) + : _sd_fat32_create_file_vnode(dir_node->mount, + new_file_cluster_addr, 0); + if (!vnode) { + _sd_fat32_unhold_cluster(); + free(block_buf); + CRITICAL_SECTION_LEAVE(daif_val); + return -ENOMEM; + } + + char *const entry_component_name = strdup(component_name); + if (!entry_component_name) { + free(vnode); + _sd_fat32_unhold_cluster(); + free(block_buf); + CRITICAL_SECTION_LEAVE(daif_val); + return -ENOMEM; + } + + // Find an empty directory entry in the target directory. + + size_t dir_entry_cluster_addr = dir_data->cluster_addr; + fatdir_t *dir_entry = NULL, *dir_entry_to_zero = NULL; + bool done = false; + while (!done) { + for (size_t sector_of_cluster = 0; + sector_of_cluster < fsinfo->cluster_n_sectors; sector_of_cluster++) { + readblock( + _sd_fat32_cluster_addr_to_data_lba(fsinfo, dir_entry_cluster_addr) + + sector_of_cluster, + block_buf); + + for (size_t offset = 0; offset < 512; offset += sizeof(fatdir_t)) { + fatdir_t *const curr_dir_entry = (fatdir_t *)(block_buf + offset); + if (curr_dir_entry->name[0] == 0) { + dir_entry = curr_dir_entry; + done = true; + dir_entry_to_zero = curr_dir_entry + 1; + if ((unsigned char *)dir_entry_to_zero - block_buf >= 512) + dir_entry_to_zero = NULL; + break; + } + + if (curr_dir_entry->name[0] == 0xe5 || + curr_dir_entry->attr[0] == 0xf) { // Invalid entry. + dir_entry = curr_dir_entry; + done = true; + break; + } + } + } + + if (done) + break; + + const uint32_t fat_entry = + _sd_fat32_read_fat(fsinfo, dir_entry_cluster_addr, block_buf); + if (!(0x2 <= fat_entry && fat_entry <= 0x0ffffff7)) // Nothing more to read. + break; + + dir_entry_cluster_addr = fat_entry; + } + + if (!dir_entry) { + dir_entry_cluster_addr = _sd_fat32_alloc_free_cluster_and_extend_chain( + fsinfo, dir_entry_cluster_addr, block_buf); + if (dir_entry_cluster_addr == (size_t)-1) { + free(entry_component_name); + free(vnode); + _sd_fat32_unhold_cluster(); + free(block_buf); + CRITICAL_SECTION_LEAVE(daif_val); + return -ENOSPC; + } + + memset(block_buf, 0, 512); + dir_entry = (fatdir_t *)block_buf; + done = true; + } + + // Set the data in the new directory entry. + + memcpy(dir_entry->name, filename_buf, 11); + // Can't set the timestamps properly, since this kernel doesn't keep time. + memset(dir_entry->attr, 0, 9); + dir_entry->ch = new_file_cluster_addr >> 16; + dir_entry->attr2 = 0; + dir_entry->cl = new_file_cluster_addr & ((1 << 16) - 1); + dir_entry->size = 0; + + if (dir_entry_to_zero) { + dir_entry_to_zero->name[0] = 0; + } + + writeblock(dir_entry_cluster_addr, block_buf); + + _sd_fat32_alloc_held_cluster(fsinfo, block_buf); + + free(block_buf); + + // Insert the vnode. + + const sd_fat32_child_vnode_entry_t new_child_vnode_entry = { + .component_name = entry_component_name, .vnode = vnode}; + + rb_insert(&dir_data->child_vnodes, sizeof(sd_fat32_child_vnode_entry_t), + &new_child_vnode_entry, + (int (*)(const void *, const void *, void *)) + _sd_fat32_cmp_child_vnode_entries_by_component_name, + NULL); + + *target = vnode; + + CRITICAL_SECTION_LEAVE(daif_val); + return 0; +} + static int _sd_fat32_create(struct vnode *const dir_node, struct vnode **const target, const char *const component_name) { - (void)dir_node; - (void)target; - (void)component_name; - - return -EROFS; + return _sd_fat32_create_impl(dir_node, target, component_name, false); } static int _sd_fat32_mkdir(struct vnode *const dir_node, struct vnode **const target, const char *const component_name) { - (void)dir_node; - (void)target; - (void)component_name; - - return -EROFS; + return _sd_fat32_create_impl(dir_node, target, component_name, true); } From c9e686f187380a7d4a9742a54a79b286804be3c8 Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Thu, 19 Oct 2023 01:40:21 +0800 Subject: [PATCH 32/34] Lab 8 C: Add missing operations in sd_fat32 --- lab8/c/include/oscos/uapi/errno.h | 1 + lab8/c/src/fs/sd-fat32.c | 43 +++++++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/lab8/c/include/oscos/uapi/errno.h b/lab8/c/include/oscos/uapi/errno.h index 64ef2eac0..e690799f2 100644 --- a/lab8/c/include/oscos/uapi/errno.h +++ b/lab8/c/include/oscos/uapi/errno.h @@ -1,3 +1,4 @@ +#define EPERM 1 #define ENOENT 2 #define ESRCH 3 #define EINTR 4 diff --git a/lab8/c/src/fs/sd-fat32.c b/lab8/c/src/fs/sd-fat32.c index 8334c7b6f..02a631ca4 100644 --- a/lab8/c/src/fs/sd-fat32.c +++ b/lab8/c/src/fs/sd-fat32.c @@ -86,6 +86,8 @@ static int _sd_fat32_read(struct file *file, void *buf, size_t len); static int _sd_fat32_open(struct vnode *file_node, struct file **target); static int _sd_fat32_close(struct file *file); static long _sd_fat32_lseek64(struct file *file, long offset, int whence); +static int _sd_fat32_ioctl(struct file *file, unsigned long request, + void *payload); static int _sd_fat32_lookup(struct vnode *dir_node, struct vnode **target, const char *component_name); @@ -93,6 +95,9 @@ static int _sd_fat32_create(struct vnode *dir_node, struct vnode **target, const char *component_name); static int _sd_fat32_mkdir(struct vnode *dir_node, struct vnode **target, const char *component_name); +static int _sd_fat32_mknod(struct vnode *dir_node, struct vnode **target, + const char *component_name, struct device *device); +static long _sd_fat32_get_size(struct vnode *vnode); struct filesystem sd_fat32 = {.name = "fat32", .setup_mount = _sd_fat32_setup_mount}; @@ -102,12 +107,15 @@ static struct file_operations _sd_fat32_file_operations = { .read = _sd_fat32_read, .open = _sd_fat32_open, .close = _sd_fat32_close, - .lseek64 = _sd_fat32_lseek64}; + .lseek64 = _sd_fat32_lseek64, + .ioctl = _sd_fat32_ioctl}; static struct vnode_operations _sd_fat32_vnode_operations = { .lookup = _sd_fat32_lookup, .create = _sd_fat32_create, - .mkdir = _sd_fat32_mkdir}; + .mkdir = _sd_fat32_mkdir, + .mknod = _sd_fat32_mknod, + .get_size = _sd_fat32_get_size}; static size_t _sd_fat32_cluster_addr_to_data_lba(const fat32_fsinfo_t *const fsinfo, @@ -548,6 +556,15 @@ static long _sd_fat32_lseek64(struct file *const file, const long offset, } } +static int _sd_fat32_ioctl(struct file *const file, const unsigned long request, + void *const payload) { + (void)file; + (void)request; + (void)payload; + + return -ENOTTY; +} + static int _sd_fat32_lookup(struct vnode *const dir_node, struct vnode **const target, const char *const component_name) { @@ -862,3 +879,25 @@ static int _sd_fat32_mkdir(struct vnode *const dir_node, const char *const component_name) { return _sd_fat32_create_impl(dir_node, target, component_name, true); } + +static int _sd_fat32_mknod(struct vnode *const dir_node, + struct vnode **const target, + const char *const component_name, + struct device *const device) { + (void)dir_node; + (void)target; + (void)component_name; + (void)device; + + return -EPERM; +} + +static long _sd_fat32_get_size(struct vnode *const vnode) { + sd_fat32_internal_t *const internal = (sd_fat32_internal_t *)vnode->internal; + if (internal->type != TYPE_FILE) + return -EISDIR; + + sd_fat32_internal_file_data_t *const file_data = &internal->file_data; + + return file_data->size; +} From a41a73a20a07ef73c05515534389c8f39e95a13d Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Thu, 19 Oct 2023 22:12:57 +0800 Subject: [PATCH 33/34] Lab 8 C: Fix write and create in sd_fat32 - Fix file size update in `_sd_fat32_write`. - `_sd_fat32_write` now persists the file size update in the SD card. - Fix directory entries used for LFN being overwritten in `_sd_fat32_create_impl`. - Fix directory table persistence using the wrong LBA in `_sd_fat32_create_impl`. --- lab8/c/src/fs/sd-fat32.c | 131 ++++++++++++++++++++++++++++----------- 1 file changed, 95 insertions(+), 36 deletions(-) diff --git a/lab8/c/src/fs/sd-fat32.c b/lab8/c/src/fs/sd-fat32.c index 02a631ca4..68d3d729d 100644 --- a/lab8/c/src/fs/sd-fat32.c +++ b/lab8/c/src/fs/sd-fat32.c @@ -66,6 +66,9 @@ typedef struct { } sd_fat32_internal_dir_data_t; typedef struct { + size_t directory_table_cluster_addr; + size_t directory_table_sector_of_cluster; + size_t directory_entry_ix; size_t cluster_addr; size_t size; } sd_fat32_internal_file_data_t; @@ -274,9 +277,12 @@ static struct vnode *_sd_fat32_create_dir_vnode(struct mount *const mount, return result; } -static struct vnode *_sd_fat32_create_file_vnode(struct mount *const mount, - const size_t cluster_addr, - const size_t size) { +static struct vnode * +_sd_fat32_create_file_vnode(struct mount *const mount, + const size_t directory_table_cluster_addr, + const size_t directory_table_sector_of_cluster, + const size_t directory_entry_ix, + const size_t cluster_addr, const size_t size) { struct vnode *const result = malloc(sizeof(struct vnode)); if (!result) return NULL; @@ -287,10 +293,15 @@ static struct vnode *_sd_fat32_create_file_vnode(struct mount *const mount, return NULL; } - *internal = - (sd_fat32_internal_t){.type = TYPE_FILE, - .file_data = (sd_fat32_internal_file_data_t){ - .cluster_addr = cluster_addr, .size = size}}; + *internal = (sd_fat32_internal_t){ + .type = TYPE_FILE, + .file_data = (sd_fat32_internal_file_data_t){ + .directory_table_cluster_addr = directory_table_cluster_addr, + .directory_table_sector_of_cluster = + directory_table_sector_of_cluster, + .directory_entry_ix = directory_entry_ix, + .cluster_addr = cluster_addr, + .size = size}}; *result = (struct vnode){.mount = mount, .v_ops = &_sd_fat32_vnode_operations, .f_ops = &_sd_fat32_file_operations, @@ -429,8 +440,23 @@ static int _sd_fat32_write(struct file *const file, const void *const buf, } file->f_pos += n_chars_written; - if (file_data->size > file->f_pos) { + if (file->f_pos > file_data->size) { file_data->size = file->f_pos; + + // Write the new file size back to the directory table + // so that it is persisted. + + readblock(_sd_fat32_cluster_addr_to_data_lba( + fsinfo, file_data->directory_table_cluster_addr) + + file_data->directory_table_sector_of_cluster, + block_buf); + fatdir_t *const dir_entry = + (fatdir_t *)block_buf + file_data->directory_entry_ix; + dir_entry->size = file_data->size; + writeblock(_sd_fat32_cluster_addr_to_data_lba( + fsinfo, file_data->directory_table_cluster_addr) + + file_data->directory_table_sector_of_cluster, + block_buf); } free(block_buf); @@ -615,19 +641,23 @@ static int _sd_fat32_lookup(struct vnode *const dir_node, if (!block_buf) return -ENOMEM; - size_t curr_cluster_addr = dir_data->cluster_addr; + size_t dir_table_cluster_addr = dir_data->cluster_addr, + dir_table_sector_of_cluster, dir_entry_ix; const fatdir_t *dir_entry = NULL; bool done = false; while (!done) { - for (size_t sector_of_cluster = 0; - sector_of_cluster < fsinfo->cluster_n_sectors; sector_of_cluster++) { - readblock(_sd_fat32_cluster_addr_to_data_lba(fsinfo, curr_cluster_addr) + - sector_of_cluster, - block_buf); + for (dir_table_sector_of_cluster = 0; + dir_table_sector_of_cluster < fsinfo->cluster_n_sectors; + dir_table_sector_of_cluster++) { + readblock( + _sd_fat32_cluster_addr_to_data_lba(fsinfo, dir_table_cluster_addr) + + dir_table_sector_of_cluster, + block_buf); - for (size_t offset = 0; offset < 512; offset += sizeof(fatdir_t)) { + for (dir_entry_ix = 0; dir_entry_ix * sizeof(fatdir_t) < 512; + dir_entry_ix++) { const fatdir_t *const curr_dir_entry = - (const fatdir_t *)(block_buf + offset); + (const fatdir_t *)block_buf + dir_entry_ix; if (curr_dir_entry->name[0] == 0) { done = true; break; @@ -643,17 +673,20 @@ static int _sd_fat32_lookup(struct vnode *const dir_node, break; } } + + if (done) + break; } if (done) break; const uint32_t fat_entry = - _sd_fat32_read_fat(fsinfo, curr_cluster_addr, block_buf); + _sd_fat32_read_fat(fsinfo, dir_table_cluster_addr, block_buf); if (!(0x2 <= fat_entry && fat_entry <= 0x0ffffff7)) // Nothing more to read. break; - curr_cluster_addr = fat_entry; + dir_table_cluster_addr = fat_entry; } if (!dir_entry) { @@ -670,8 +703,9 @@ static int _sd_fat32_lookup(struct vnode *const dir_node, struct vnode *const vnode = is_dir ? _sd_fat32_create_dir_vnode(dir_node->mount, dir_node, cluster_addr) - : _sd_fat32_create_file_vnode(dir_node->mount, cluster_addr, - file_size); + : _sd_fat32_create_file_vnode(dir_node->mount, dir_table_cluster_addr, + dir_table_sector_of_cluster, + dir_entry_ix, cluster_addr, file_size); if (!vnode) return -ENOMEM; @@ -748,11 +782,14 @@ static int _sd_fat32_create_impl(struct vnode *const dir_node, // Allocate things early, // so that we won't have to unroll a lot of FS state later. + // directory_table_cluster_addr, directory_table_sector_of_cluster, + // and directory_entry_ix get dummy values for now. + // Actual values will be set later. struct vnode *const vnode = is_creating_dir ? _sd_fat32_create_dir_vnode(dir_node->mount, dir_node, new_file_cluster_addr) - : _sd_fat32_create_file_vnode(dir_node->mount, + : _sd_fat32_create_file_vnode(dir_node->mount, 0, 0, 0, new_file_cluster_addr, 0); if (!vnode) { _sd_fat32_unhold_cluster(); @@ -772,19 +809,22 @@ static int _sd_fat32_create_impl(struct vnode *const dir_node, // Find an empty directory entry in the target directory. - size_t dir_entry_cluster_addr = dir_data->cluster_addr; + size_t dir_table_cluster_addr = dir_data->cluster_addr, + dir_table_sector_of_cluster, dir_entry_ix; fatdir_t *dir_entry = NULL, *dir_entry_to_zero = NULL; bool done = false; while (!done) { - for (size_t sector_of_cluster = 0; - sector_of_cluster < fsinfo->cluster_n_sectors; sector_of_cluster++) { + for (dir_table_sector_of_cluster = 0; + dir_table_sector_of_cluster < fsinfo->cluster_n_sectors; + dir_table_sector_of_cluster++) { readblock( - _sd_fat32_cluster_addr_to_data_lba(fsinfo, dir_entry_cluster_addr) + - sector_of_cluster, + _sd_fat32_cluster_addr_to_data_lba(fsinfo, dir_table_cluster_addr) + + dir_table_sector_of_cluster, block_buf); - for (size_t offset = 0; offset < 512; offset += sizeof(fatdir_t)) { - fatdir_t *const curr_dir_entry = (fatdir_t *)(block_buf + offset); + for (dir_entry_ix = 0; dir_entry_ix * sizeof(fatdir_t) < 512; + dir_entry_ix++) { + fatdir_t *const curr_dir_entry = (fatdir_t *)(block_buf) + dir_entry_ix; if (curr_dir_entry->name[0] == 0) { dir_entry = curr_dir_entry; done = true; @@ -794,30 +834,36 @@ static int _sd_fat32_create_impl(struct vnode *const dir_node, break; } - if (curr_dir_entry->name[0] == 0xe5 || - curr_dir_entry->attr[0] == 0xf) { // Invalid entry. + if (curr_dir_entry->name[0] == 0xe5) { // Invalid entry. + // Note: The entry is also invalid when + // curr_dir_entry->attr[0] == 0xf, + // but in this case this entry is used for LFN + // and we shouldn't overwrite it. dir_entry = curr_dir_entry; done = true; break; } } + + if (done) + break; } if (done) break; const uint32_t fat_entry = - _sd_fat32_read_fat(fsinfo, dir_entry_cluster_addr, block_buf); + _sd_fat32_read_fat(fsinfo, dir_table_cluster_addr, block_buf); if (!(0x2 <= fat_entry && fat_entry <= 0x0ffffff7)) // Nothing more to read. break; - dir_entry_cluster_addr = fat_entry; + dir_table_cluster_addr = fat_entry; } if (!dir_entry) { - dir_entry_cluster_addr = _sd_fat32_alloc_free_cluster_and_extend_chain( - fsinfo, dir_entry_cluster_addr, block_buf); - if (dir_entry_cluster_addr == (size_t)-1) { + dir_table_cluster_addr = _sd_fat32_alloc_free_cluster_and_extend_chain( + fsinfo, dir_table_cluster_addr, block_buf); + if (dir_table_cluster_addr == (size_t)-1) { free(entry_component_name); free(vnode); _sd_fat32_unhold_cluster(); @@ -828,6 +874,8 @@ static int _sd_fat32_create_impl(struct vnode *const dir_node, memset(block_buf, 0, 512); dir_entry = (fatdir_t *)block_buf; + dir_table_sector_of_cluster = 0; + dir_entry_ix = 0; done = true; } @@ -845,7 +893,10 @@ static int _sd_fat32_create_impl(struct vnode *const dir_node, dir_entry_to_zero->name[0] = 0; } - writeblock(dir_entry_cluster_addr, block_buf); + writeblock( + _sd_fat32_cluster_addr_to_data_lba(fsinfo, dir_table_cluster_addr) + + dir_table_sector_of_cluster, + block_buf); _sd_fat32_alloc_held_cluster(fsinfo, block_buf); @@ -853,6 +904,14 @@ static int _sd_fat32_create_impl(struct vnode *const dir_node, // Insert the vnode. + if (!is_creating_dir) { + sd_fat32_internal_file_data_t *const file_data = + &((sd_fat32_internal_t *)vnode->internal)->file_data; + file_data->directory_table_cluster_addr = dir_table_cluster_addr; + file_data->directory_table_sector_of_cluster = dir_table_sector_of_cluster; + file_data->directory_entry_ix = dir_entry_ix; + } + const sd_fat32_child_vnode_entry_t new_child_vnode_entry = { .component_name = entry_component_name, .vnode = vnode}; From 44e2dada452a2a87422ae77900bf68ab91662bce Mon Sep 17 00:00:00 2001 From: Po-Yi Tsai Date: Thu, 19 Oct 2023 22:51:08 +0800 Subject: [PATCH 34/34] Lab 8 C: Complete A1 Implements block-level caching, which covers FAT caching and file content caching. Metadata caching (both FS-level and file-level) and the component name cache have been implemented, accidentally, by the previous commits. Also fixes signal handling due to a wrong system call number for the `sys_sigreturn` system call. --- lab8/c/Makefile | 1 + lab8/c/include/oscos/fs/vfs.h | 8 + lab8/c/include/oscos/uapi/sys/syscall.h | 3 +- lab8/c/src/fs/initramfs.c | 15 +- lab8/c/src/fs/sd-fat32.c | 276 ++++++++++++++---- lab8/c/src/fs/tmpfs.c | 15 +- lab8/c/src/fs/vfs.c | 24 ++ lab8/c/src/sched/sig-handler-main.S | 4 +- lab8/c/src/xcpt/svc-handler.S | 2 +- lab8/c/src/xcpt/syscall-table.S | 1 + lab8/c/src/xcpt/syscall/sync.c | 6 + lab8/tests/CONFIG.TXT | 1 + lab8/tests/user-program/libc/include/unistd.h | 2 + lab8/tests/user-program/libc/src/unistd.c | 2 + 14 files changed, 297 insertions(+), 63 deletions(-) create mode 100644 lab8/c/src/xcpt/syscall/sync.c create mode 100644 lab8/tests/CONFIG.TXT diff --git a/lab8/c/Makefile b/lab8/c/Makefile index 591c62f83..564d466aa 100644 --- a/lab8/c/Makefile +++ b/lab8/c/Makefile @@ -44,6 +44,7 @@ OBJS = start main console console-dev console-suspend devicetree \ xcpt/syscall/open xcpt/syscall/close xcpt/syscall/write \ xcpt/syscall/read xcpt/syscall/mkdir xcpt/syscall/mount \ xcpt/syscall/chdir xcpt/syscall/lseek64 xcpt/syscall/ioctl \ + xcpt/syscall/sync \ xcpt/syscall/sigreturn xcpt/syscall/sigreturn-check \ libc/ctype libc/stdio libc/stdlib/qsort libc/string \ utils/core-id utils/fmt utils/heapq utils/rb diff --git a/lab8/c/include/oscos/fs/vfs.h b/lab8/c/include/oscos/fs/vfs.h index 2f903c39a..9a1d72b47 100644 --- a/lab8/c/include/oscos/fs/vfs.h +++ b/lab8/c/include/oscos/fs/vfs.h @@ -23,6 +23,8 @@ struct file { struct mount { struct vnode *root; struct filesystem *fs; + struct super_operations *s_ops; + void *internal; }; struct filesystem { @@ -56,6 +58,10 @@ struct vnode_operations { long (*get_size)(struct vnode *vnode); }; +struct super_operations { + void (*sync_fs)(struct mount *mount); +}; + extern struct mount rootfs; int register_filesystem(struct filesystem *fs); @@ -81,6 +87,8 @@ int vfs_lookup_relative(struct vnode *cwd, const char *pathname, int vfs_mknod(const char *target, const char *device); +void vfs_sync_all(void); + typedef struct { struct file *file; size_t refcnt; diff --git a/lab8/c/include/oscos/uapi/sys/syscall.h b/lab8/c/include/oscos/uapi/sys/syscall.h index 3f5cd7545..32e8784ec 100644 --- a/lab8/c/include/oscos/uapi/sys/syscall.h +++ b/lab8/c/include/oscos/uapi/sys/syscall.h @@ -21,6 +21,7 @@ #define SYS_chdir 17 #define SYS_lseek64 18 #define SYS_ioctl 19 -#define SYS_sigreturn 20 +#define SYS_sync 20 +#define SYS_sigreturn 21 #endif diff --git a/lab8/c/src/fs/initramfs.c b/lab8/c/src/fs/initramfs.c index 94cd79172..7b7f92f09 100644 --- a/lab8/c/src/fs/initramfs.c +++ b/lab8/c/src/fs/initramfs.c @@ -39,6 +39,8 @@ static int _initramfs_mknod(struct vnode *dir_node, struct vnode **target, const char *component_name, struct device *device); static long _initramfs_get_size(struct vnode *vnode); +static void _initramfs_sync_fs(struct mount *mount); + struct filesystem initramfs = {.name = "initramfs", .setup_mount = _initramfs_setup_mount}; @@ -57,6 +59,9 @@ static struct vnode_operations _initramfs_vnode_operations = { .mknod = _initramfs_mknod, .get_size = _initramfs_get_size}; +static struct super_operations _initramfs_super_operations = { + .sync_fs = _initramfs_sync_fs}; + static int _initramfs_cmp_child_vnode_entries_by_component_name( const initramfs_child_vnode_entry_t *const e1, const initramfs_child_vnode_entry_t *const e2, void *const _arg) { @@ -104,7 +109,10 @@ static int _initramfs_setup_mount(struct filesystem *const fs, if (!root_vnode) return -ENOMEM; - *mount = (struct mount){.fs = fs, .root = root_vnode}; + *mount = (struct mount){.fs = fs, + .root = root_vnode, + .s_ops = &_initramfs_super_operations, + .internal = NULL}; return 0; } @@ -374,3 +382,8 @@ static long _initramfs_get_size(struct vnode *const vnode) { ? (long)CPIO_NEWC_FILESIZE(entry) : -1; } + +static void _initramfs_sync_fs(struct mount *const mount) { + // No-op. + (void)mount; +} diff --git a/lab8/c/src/fs/sd-fat32.c b/lab8/c/src/fs/sd-fat32.c index 68d3d729d..5a6b23142 100644 --- a/lab8/c/src/fs/sd-fat32.c +++ b/lab8/c/src/fs/sd-fat32.c @@ -75,13 +75,27 @@ typedef struct { typedef struct { sd_fat32_internal_type_t type; - const fat32_fsinfo_t *fsinfo; union { sd_fat32_internal_file_data_t file_data; sd_fat32_internal_dir_data_t dir_data; }; } sd_fat32_internal_t; +typedef struct { + size_t lba; + bool is_dirty; + unsigned char data[512]; +} block_cache_entry_t; + +typedef struct { + rb_node_t *root; +} block_cache_t; + +typedef struct { + fat32_fsinfo_t fsinfo; + block_cache_t block_cache; +} sd_fat32_fs_internal_t; + static int _sd_fat32_setup_mount(struct filesystem *fs, struct mount *mount); static int _sd_fat32_write(struct file *file, const void *buf, size_t len); @@ -102,6 +116,8 @@ static int _sd_fat32_mknod(struct vnode *dir_node, struct vnode **target, const char *component_name, struct device *device); static long _sd_fat32_get_size(struct vnode *vnode); +static void _sd_fat32_sync_fs(struct mount *mount); + struct filesystem sd_fat32 = {.name = "fat32", .setup_mount = _sd_fat32_setup_mount}; @@ -120,6 +136,110 @@ static struct vnode_operations _sd_fat32_vnode_operations = { .mknod = _sd_fat32_mknod, .get_size = _sd_fat32_get_size}; +static struct super_operations _sd_fat32_super_operations = { + .sync_fs = _sd_fat32_sync_fs}; + +static int +_sd_fat32_cmp_block_cache_entries_by_lba(const block_cache_entry_t *const e1, + const block_cache_entry_t *const e2, + void *const _arg) { + (void)_arg; + + if (e1->lba < e2->lba) + return -1; + if (e1->lba > e2->lba) + return 1; + return 0; +} + +static int +_sd_fat32_cmp_lba_and_block_cache_entry(const size_t *const lba, + const block_cache_entry_t *const entry, + void *const _arg) { + (void)_arg; + + if (*lba < entry->lba) + return -1; + if (*lba > entry->lba) + return 1; + return 0; +} + +static void readblock_cached(block_cache_t *const cache, const int block_idx, + void *const buf) { + const size_t lba = block_idx; + + const block_cache_entry_t *const entry = + rb_search(cache->root, &lba, + (int (*)(const void *, const void *, + void *))_sd_fat32_cmp_lba_and_block_cache_entry, + NULL); + + if (entry) { + memcpy(buf, entry->data, 512); + return; + } + + readblock(block_idx, buf); + + block_cache_entry_t new_entry = {.lba = lba, .is_dirty = false}; + memcpy(new_entry.data, buf, 512); + + rb_insert(&cache->root, sizeof(block_cache_entry_t), &new_entry, + (int (*)(const void *, const void *, + void *))_sd_fat32_cmp_block_cache_entries_by_lba, + NULL); +} + +static void writeblock_cached(block_cache_t *const cache, int block_idx, + void *buf) { + const size_t lba = block_idx; + + block_cache_entry_t *const entry = (block_cache_entry_t *)rb_search( + cache->root, &lba, + (int (*)(const void *, const void *, + void *))_sd_fat32_cmp_lba_and_block_cache_entry, + NULL); + + if (entry) { + memcpy(entry->data, buf, 512); + entry->is_dirty = true; + return; + } + + block_cache_entry_t new_entry = {.lba = lba, .is_dirty = true}; + memcpy(new_entry.data, buf, 512); + + rb_insert(&cache->root, sizeof(block_cache_entry_t), &new_entry, + (int (*)(const void *, const void *, + void *))_sd_fat32_cmp_block_cache_entries_by_lba, + NULL); +} + +static void _flush_cache_rec(rb_node_t *const node) { + if (!node) + return; + + block_cache_entry_t *const entry = (block_cache_entry_t *)node->payload; + if (entry->is_dirty) { + writeblock(entry->lba, entry->data); + entry->is_dirty = false; + } + + for (size_t i = 0; i < 2; i++) { + _flush_cache_rec(node->children[i]); + } +} + +static void flush_cache(block_cache_t *const cache) { + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + _flush_cache_rec(cache->root); + + CRITICAL_SECTION_LEAVE(daif_val); +} + static size_t _sd_fat32_cluster_addr_to_data_lba(const fat32_fsinfo_t *const fsinfo, const size_t cluster_addr) { @@ -135,21 +255,28 @@ _sd_fat32_cluster_addr_to_fat_lba(const fat32_fsinfo_t *const fsinfo, } static uint32_t _sd_fat32_read_fat(const fat32_fsinfo_t *const fsinfo, + block_cache_t *const block_cache, const size_t cluster_addr, unsigned char block_buf[static 512]) { - readblock(_sd_fat32_cluster_addr_to_fat_lba(fsinfo, cluster_addr), block_buf); + readblock_cached(block_cache, + _sd_fat32_cluster_addr_to_fat_lba(fsinfo, cluster_addr), + block_buf); const uint32_t *const fat_entries = (const uint32_t *)block_buf; return fat_entries[cluster_addr & ((1 << 7) - 1)] & 0x0fffffff; } static void _sd_fat32_write_fat(const fat32_fsinfo_t *const fsinfo, + block_cache_t *const block_cache, const size_t cluster_addr, const uint32_t value, unsigned char block_buf[static 512]) { - readblock(_sd_fat32_cluster_addr_to_fat_lba(fsinfo, cluster_addr), block_buf); + readblock_cached(block_cache, + _sd_fat32_cluster_addr_to_fat_lba(fsinfo, cluster_addr), + block_buf); uint32_t *const fat_entries = (uint32_t *)block_buf; fat_entries[cluster_addr & ((1 << 7) - 1)] = value; - writeblock(_sd_fat32_cluster_addr_to_fat_lba(fsinfo, cluster_addr), - block_buf); + writeblock_cached(block_cache, + _sd_fat32_cluster_addr_to_fat_lba(fsinfo, cluster_addr), + block_buf); } static bool _sd_fat32_is_component_name_char_valid_lax(const char c) { @@ -195,11 +322,12 @@ static bool _sd_fat32_check_and_map_filename(const char *const filename, static size_t _held_cluster_addr = -1; static size_t _sd_fat32_find_free_cluster(const fat32_fsinfo_t *const fsinfo, + block_cache_t *const block_cache, unsigned char block_buf[static 512]) { const size_t max_n_clusters = fsinfo->fat_n_sectors << 7; for (size_t cluster_addr = 0; cluster_addr < max_n_clusters; cluster_addr++) { if (cluster_addr != _held_cluster_addr && - _sd_fat32_read_fat(fsinfo, cluster_addr, block_buf) == 0) { + _sd_fat32_read_fat(fsinfo, block_cache, cluster_addr, block_buf) == 0) { return cluster_addr; } } @@ -208,30 +336,35 @@ static size_t _sd_fat32_find_free_cluster(const fat32_fsinfo_t *const fsinfo, static size_t _sd_fat32_find_free_cluster_and_hold(const fat32_fsinfo_t *const fsinfo, + block_cache_t *const block_cache, unsigned char block_buf[static 512]) { - return _held_cluster_addr = _sd_fat32_find_free_cluster(fsinfo, block_buf); + return _held_cluster_addr = + _sd_fat32_find_free_cluster(fsinfo, block_cache, block_buf); } static size_t _sd_fat32_alloc_free_cluster_and_extend_chain( - const fat32_fsinfo_t *const fsinfo, const size_t chain_end_cluster_addr, - unsigned char block_buf[static 512]) { + const fat32_fsinfo_t *const fsinfo, block_cache_t *const block_cache, + const size_t chain_end_cluster_addr, unsigned char block_buf[static 512]) { const size_t new_cluster_addr = - _sd_fat32_find_free_cluster(fsinfo, block_buf); + _sd_fat32_find_free_cluster(fsinfo, block_cache, block_buf); if (new_cluster_addr == (size_t)-1) return -1; - _sd_fat32_write_fat(fsinfo, new_cluster_addr, 0x0fffffff, block_buf); + _sd_fat32_write_fat(fsinfo, block_cache, new_cluster_addr, 0x0fffffff, + block_buf); if (chain_end_cluster_addr != (size_t)-1) { - _sd_fat32_write_fat(fsinfo, chain_end_cluster_addr, new_cluster_addr, - block_buf); + _sd_fat32_write_fat(fsinfo, block_cache, chain_end_cluster_addr, + new_cluster_addr, block_buf); } return new_cluster_addr; } static void _sd_fat32_alloc_held_cluster(const fat32_fsinfo_t *const fsinfo, + block_cache_t *const block_cache, unsigned char block_buf[static 512]) { - _sd_fat32_write_fat(fsinfo, _held_cluster_addr, 0x0fffffff, block_buf); + _sd_fat32_write_fat(fsinfo, block_cache, _held_cluster_addr, 0x0fffffff, + block_buf); } static void _sd_fat32_unhold_cluster(void) { _held_cluster_addr = -1; } @@ -315,8 +448,9 @@ static int _sd_fat32_setup_mount(struct filesystem *const fs, if (!block_buf) return -ENOMEM; - fat32_fsinfo_t *const fsinfo = malloc(sizeof(fat32_fsinfo_t)); - if (!fsinfo) { + sd_fat32_fs_internal_t *const fs_internal = + malloc(sizeof(sd_fat32_fs_internal_t)); + if (!fs_internal) { free(block_buf); return -ENOMEM; } @@ -335,12 +469,12 @@ static int _sd_fat32_setup_mount(struct filesystem *const fs, readblock(partition_lba, block_buf); bpb_t *const bpb = (bpb_t *)block_buf; - *fsinfo = (fat32_fsinfo_t){.partition_lba = partition_lba, - .fat_lba_offset = bpb->rsc, - .fat_n_sectors = bpb->spf32, - .data_region_lba_offset = - bpb->rsc + bpb->nf * bpb->spf32 - 2 * bpb->spc, - .cluster_n_sectors = bpb->spc}; + fs_internal->fsinfo = (fat32_fsinfo_t){ + .partition_lba = partition_lba, + .fat_lba_offset = bpb->rsc, + .fat_n_sectors = bpb->spf32, + .data_region_lba_offset = bpb->rsc + bpb->nf * bpb->spf32 - 2 * bpb->spc, + .cluster_n_sectors = bpb->spc}; const size_t root_cluster_addr = bpb->rc; console_printf("DEBUG: sd-fat32: Root dir cluster addr = 0x%zx\n", @@ -351,13 +485,16 @@ static int _sd_fat32_setup_mount(struct filesystem *const fs, struct vnode *const root_vnode = _sd_fat32_create_dir_vnode(mount, NULL, root_cluster_addr); if (!root_vnode) { - free(fsinfo); + free(fs_internal); free(block_buf); return -ENOMEM; } - ((sd_fat32_internal_t *)root_vnode->internal)->fsinfo = fsinfo; - *mount = (struct mount){.fs = fs, .root = root_vnode}; + fs_internal->block_cache.root = NULL; + *mount = (struct mount){.fs = fs, + .root = root_vnode, + .s_ops = &_sd_fat32_super_operations, + .internal = fs_internal}; free(block_buf); return 0; @@ -371,8 +508,10 @@ static int _sd_fat32_write(struct file *const file, const void *const buf, return -EISDIR; sd_fat32_internal_file_data_t *const file_data = &internal->file_data; - const fat32_fsinfo_t *const fsinfo = - ((sd_fat32_internal_t *)(file->vnode->mount->root->internal))->fsinfo; + sd_fat32_fs_internal_t *const fs_internal = + (sd_fat32_fs_internal_t *)file->vnode->mount->internal; + const fat32_fsinfo_t *const fsinfo = &fs_internal->fsinfo; + block_cache_t *const block_cache = &fs_internal->block_cache; unsigned char *const block_buf = malloc(512); if (!block_buf) @@ -394,13 +533,15 @@ static int _sd_fat32_write(struct file *const file, const void *const buf, min_end = end_offset < write_end_offset ? end_offset : write_end_offset; if (max_start < min_end) { - readblock( + readblock_cached( + block_cache, _sd_fat32_cluster_addr_to_data_lba(fsinfo, curr_cluster_addr) + sector_of_cluster, block_buf); memcpy(block_buf + (max_start & 511), (char *)buf + n_chars_written, min_end - max_start); - writeblock( + writeblock_cached( + block_cache, _sd_fat32_cluster_addr_to_data_lba(fsinfo, curr_cluster_addr) + sector_of_cluster, block_buf); @@ -416,10 +557,10 @@ static int _sd_fat32_write(struct file *const file, const void *const buf, break; const uint32_t fat_entry = - _sd_fat32_read_fat(fsinfo, curr_cluster_addr, block_buf); + _sd_fat32_read_fat(fsinfo, block_cache, curr_cluster_addr, block_buf); if (!(0x2 <= fat_entry && fat_entry <= 0x0ffffff7)) { // No more chains. curr_cluster_addr = _sd_fat32_alloc_free_cluster_and_extend_chain( - fsinfo, curr_cluster_addr, block_buf); + fsinfo, block_cache, curr_cluster_addr, block_buf); if (curr_cluster_addr == (size_t)-1) { // Out of space. free(block_buf); CRITICAL_SECTION_LEAVE(daif_val); @@ -429,7 +570,8 @@ static int _sd_fat32_write(struct file *const file, const void *const buf, for (size_t sector_of_cluster = 0; sector_of_cluster < fsinfo->cluster_n_sectors; sector_of_cluster++) { memset(block_buf, 0, 512); - writeblock( + writeblock_cached( + block_cache, _sd_fat32_cluster_addr_to_data_lba(fsinfo, curr_cluster_addr) + sector_of_cluster, block_buf); @@ -446,17 +588,19 @@ static int _sd_fat32_write(struct file *const file, const void *const buf, // Write the new file size back to the directory table // so that it is persisted. - readblock(_sd_fat32_cluster_addr_to_data_lba( - fsinfo, file_data->directory_table_cluster_addr) + - file_data->directory_table_sector_of_cluster, - block_buf); + readblock_cached(block_cache, + _sd_fat32_cluster_addr_to_data_lba( + fsinfo, file_data->directory_table_cluster_addr) + + file_data->directory_table_sector_of_cluster, + block_buf); fatdir_t *const dir_entry = (fatdir_t *)block_buf + file_data->directory_entry_ix; dir_entry->size = file_data->size; - writeblock(_sd_fat32_cluster_addr_to_data_lba( - fsinfo, file_data->directory_table_cluster_addr) + - file_data->directory_table_sector_of_cluster, - block_buf); + writeblock_cached(block_cache, + _sd_fat32_cluster_addr_to_data_lba( + fsinfo, file_data->directory_table_cluster_addr) + + file_data->directory_table_sector_of_cluster, + block_buf); } free(block_buf); @@ -472,8 +616,10 @@ static int _sd_fat32_read(struct file *const file, void *const buf, return -EISDIR; sd_fat32_internal_file_data_t *const file_data = &internal->file_data; - const fat32_fsinfo_t *const fsinfo = - ((sd_fat32_internal_t *)(file->vnode->mount->root->internal))->fsinfo; + sd_fat32_fs_internal_t *const fs_internal = + (sd_fat32_fs_internal_t *)file->vnode->mount->internal; + const fat32_fsinfo_t *const fsinfo = &fs_internal->fsinfo; + block_cache_t *const block_cache = &fs_internal->block_cache; unsigned char *const block_buf = malloc(512); if (!block_buf) @@ -496,7 +642,8 @@ static int _sd_fat32_read(struct file *const file, void *const buf, min_end = end_offset < read_end_offset ? end_offset : read_end_offset; if (max_start < min_end) { - readblock( + readblock_cached( + block_cache, _sd_fat32_cluster_addr_to_data_lba(fsinfo, curr_cluster_addr) + sector_of_cluster, block_buf); @@ -514,7 +661,7 @@ static int _sd_fat32_read(struct file *const file, void *const buf, break; const uint32_t fat_entry = - _sd_fat32_read_fat(fsinfo, curr_cluster_addr, block_buf); + _sd_fat32_read_fat(fsinfo, block_cache, curr_cluster_addr, block_buf); if (!(0x2 <= fat_entry && fat_entry <= 0x0ffffff7)) { // No more chains. // The file has a hole, which should read zero. memset(block_buf + n_chars_read, 0, read_end_offset - file_offset); @@ -600,8 +747,10 @@ static int _sd_fat32_lookup(struct vnode *const dir_node, return -ENOTDIR; sd_fat32_internal_dir_data_t *const dir_data = &internal->dir_data; - const fat32_fsinfo_t *const fsinfo = - ((sd_fat32_internal_t *)(dir_node->mount->root->internal))->fsinfo; + sd_fat32_fs_internal_t *const fs_internal = + (sd_fat32_fs_internal_t *)(dir_node->mount->internal); + const fat32_fsinfo_t *const fsinfo = &fs_internal->fsinfo; + block_cache_t *const block_cache = &fs_internal->block_cache; if (strcmp(component_name, ".") == 0) { *target = dir_node; @@ -649,7 +798,8 @@ static int _sd_fat32_lookup(struct vnode *const dir_node, for (dir_table_sector_of_cluster = 0; dir_table_sector_of_cluster < fsinfo->cluster_n_sectors; dir_table_sector_of_cluster++) { - readblock( + readblock_cached( + block_cache, _sd_fat32_cluster_addr_to_data_lba(fsinfo, dir_table_cluster_addr) + dir_table_sector_of_cluster, block_buf); @@ -681,8 +831,8 @@ static int _sd_fat32_lookup(struct vnode *const dir_node, if (done) break; - const uint32_t fat_entry = - _sd_fat32_read_fat(fsinfo, dir_table_cluster_addr, block_buf); + const uint32_t fat_entry = _sd_fat32_read_fat( + fsinfo, block_cache, dir_table_cluster_addr, block_buf); if (!(0x2 <= fat_entry && fat_entry <= 0x0ffffff7)) // Nothing more to read. break; @@ -743,8 +893,10 @@ static int _sd_fat32_create_impl(struct vnode *const dir_node, return -ENOTDIR; sd_fat32_internal_dir_data_t *const dir_data = &internal->dir_data; - const fat32_fsinfo_t *const fsinfo = - ((sd_fat32_internal_t *)(dir_node->mount->root->internal))->fsinfo; + sd_fat32_fs_internal_t *const fs_internal = + (sd_fat32_fs_internal_t *)(dir_node->mount->internal); + const fat32_fsinfo_t *const fsinfo = &fs_internal->fsinfo; + block_cache_t *const block_cache = &fs_internal->block_cache; // Check the filename. @@ -773,7 +925,7 @@ static int _sd_fat32_create_impl(struct vnode *const dir_node, // Find an empty entry in the FAT. const size_t new_file_cluster_addr = - _sd_fat32_find_free_cluster_and_hold(fsinfo, block_buf); + _sd_fat32_find_free_cluster_and_hold(fsinfo, block_cache, block_buf); if (new_file_cluster_addr == (size_t)-1) { free(block_buf); CRITICAL_SECTION_LEAVE(daif_val); @@ -817,7 +969,8 @@ static int _sd_fat32_create_impl(struct vnode *const dir_node, for (dir_table_sector_of_cluster = 0; dir_table_sector_of_cluster < fsinfo->cluster_n_sectors; dir_table_sector_of_cluster++) { - readblock( + readblock_cached( + block_cache, _sd_fat32_cluster_addr_to_data_lba(fsinfo, dir_table_cluster_addr) + dir_table_sector_of_cluster, block_buf); @@ -852,8 +1005,8 @@ static int _sd_fat32_create_impl(struct vnode *const dir_node, if (done) break; - const uint32_t fat_entry = - _sd_fat32_read_fat(fsinfo, dir_table_cluster_addr, block_buf); + const uint32_t fat_entry = _sd_fat32_read_fat( + fsinfo, block_cache, dir_table_cluster_addr, block_buf); if (!(0x2 <= fat_entry && fat_entry <= 0x0ffffff7)) // Nothing more to read. break; @@ -862,7 +1015,7 @@ static int _sd_fat32_create_impl(struct vnode *const dir_node, if (!dir_entry) { dir_table_cluster_addr = _sd_fat32_alloc_free_cluster_and_extend_chain( - fsinfo, dir_table_cluster_addr, block_buf); + fsinfo, block_cache, dir_table_cluster_addr, block_buf); if (dir_table_cluster_addr == (size_t)-1) { free(entry_component_name); free(vnode); @@ -893,12 +1046,13 @@ static int _sd_fat32_create_impl(struct vnode *const dir_node, dir_entry_to_zero->name[0] = 0; } - writeblock( + writeblock_cached( + block_cache, _sd_fat32_cluster_addr_to_data_lba(fsinfo, dir_table_cluster_addr) + dir_table_sector_of_cluster, block_buf); - _sd_fat32_alloc_held_cluster(fsinfo, block_buf); + _sd_fat32_alloc_held_cluster(fsinfo, block_cache, block_buf); free(block_buf); @@ -960,3 +1114,9 @@ static long _sd_fat32_get_size(struct vnode *const vnode) { return file_data->size; } + +static void _sd_fat32_sync_fs(struct mount *const mount) { + sd_fat32_fs_internal_t *const fs_internal = + (sd_fat32_fs_internal_t *)mount->internal; + flush_cache(&fs_internal->block_cache); +} diff --git a/lab8/c/src/fs/tmpfs.c b/lab8/c/src/fs/tmpfs.c index f4d9f1ac5..b9b440c4b 100644 --- a/lab8/c/src/fs/tmpfs.c +++ b/lab8/c/src/fs/tmpfs.c @@ -56,6 +56,8 @@ static int _tmpfs_mknod(struct vnode *dir_node, struct vnode **target, const char *component_name, struct device *device); static long _tmpfs_get_size(struct vnode *vnode); +static void _tmpfs_sync_fs(struct mount *mount); + struct filesystem tmpfs = {.name = "tmpfs", .setup_mount = _tmpfs_setup_mount}; static struct file_operations _tmpfs_file_operations = {.write = _tmpfs_write, @@ -73,6 +75,9 @@ static struct vnode_operations _tmpfs_vnode_operations = { .mknod = _tmpfs_mknod, .get_size = _tmpfs_get_size}; +static struct super_operations _tmpfs_super_operations = {.sync_fs = + _tmpfs_sync_fs}; + static int _tmpfs_cmp_dir_contents_entries(const tmpfs_dir_contents_entry_t *const e1, const tmpfs_dir_contents_entry_t *const e2, @@ -147,7 +152,10 @@ static int _tmpfs_setup_mount(struct filesystem *const fs, if (!root_vnode) return -ENOMEM; - *mount = (struct mount){.fs = fs, .root = root_vnode}; + *mount = (struct mount){.fs = fs, + .root = root_vnode, + .s_ops = &_tmpfs_super_operations, + .internal = NULL}; return 0; } @@ -473,3 +481,8 @@ static long _tmpfs_get_size(struct vnode *const vnode) { const tmpfs_internal_file_data_t *const file_data = &internal->file_data; return file_data->size; } + +static void _tmpfs_sync_fs(struct mount *const mount) { + // No-op. + (void)mount; +} diff --git a/lab8/c/src/fs/vfs.c b/lab8/c/src/fs/vfs.c index 2ecd67b55..8ab883277 100644 --- a/lab8/c/src/fs/vfs.c +++ b/lab8/c/src/fs/vfs.c @@ -382,6 +382,30 @@ int vfs_mknod(const char *target, const char *device) { last_pathname_component, dev); } +static void _vfs_sync_all_rec(rb_node_t *const node) { + if (!node) + return; + + mount_entry_t *const entry = (mount_entry_t *)node->payload; + entry->mount->s_ops->sync_fs(entry->mount); + + for (size_t i = 0; i < 2; i++) { + _vfs_sync_all_rec(node->children[i]); + } +} + +void vfs_sync_all(void) { + // Enter critical section, + // since we require that the layout of the BST to be stable. + + uint64_t daif_val; + CRITICAL_SECTION_ENTER(daif_val); + + _vfs_sync_all_rec(_mounts_by_mountpoint); + + CRITICAL_SECTION_LEAVE(daif_val); +} + shared_file_t *shared_file_new(struct file *const file) { shared_file_t *const shared_file = malloc(sizeof(shared_file_t)); if (!shared_file) diff --git a/lab8/c/src/sched/sig-handler-main.S b/lab8/c/src/sched/sig-handler-main.S index 6187d0bcb..841ae7a62 100644 --- a/lab8/c/src/sched/sig-handler-main.S +++ b/lab8/c/src/sched/sig-handler-main.S @@ -1,8 +1,10 @@ +#include "oscos/uapi/sys/syscall.h" + .section ".text" sig_handler_main: blr x0 - mov x8, 18 // SYS_sigreturn + mov x8, SYS_sigreturn svc 0 .type sig_handler_main, function diff --git a/lab8/c/src/xcpt/svc-handler.S b/lab8/c/src/xcpt/svc-handler.S index cbf9f47b3..11aa071e8 100644 --- a/lab8/c/src/xcpt/svc-handler.S +++ b/lab8/c/src/xcpt/svc-handler.S @@ -15,7 +15,7 @@ xcpt_svc_handler: // Check the system call number. ubfx x9, x9, 0, 16 cbnz x9, .Lenosys - cmp x8, 20 + cmp x8, 21 b.hi .Lenosys // Table-jump to the system call function. diff --git a/lab8/c/src/xcpt/syscall-table.S b/lab8/c/src/xcpt/syscall-table.S index 31fa48b94..98bd54ec3 100644 --- a/lab8/c/src/xcpt/syscall-table.S +++ b/lab8/c/src/xcpt/syscall-table.S @@ -21,6 +21,7 @@ syscall_table: b sys_chdir b sys_lseek64 b sys_ioctl + b sys_sync b sys_sigreturn .size syscall_table, . - syscall_table diff --git a/lab8/c/src/xcpt/syscall/sync.c b/lab8/c/src/xcpt/syscall/sync.c new file mode 100644 index 000000000..de4c88ff9 --- /dev/null +++ b/lab8/c/src/xcpt/syscall/sync.c @@ -0,0 +1,6 @@ +#include "oscos/fs/vfs.h" + +int sys_sync(void) { + vfs_sync_all(); + return 0; +} diff --git a/lab8/tests/CONFIG.TXT b/lab8/tests/CONFIG.TXT new file mode 100644 index 000000000..10286b5bb --- /dev/null +++ b/lab8/tests/CONFIG.TXT @@ -0,0 +1 @@ +initramfs initramfs.cpio 0x8000000 diff --git a/lab8/tests/user-program/libc/include/unistd.h b/lab8/tests/user-program/libc/include/unistd.h index 8fd615423..185b0494f 100644 --- a/lab8/tests/user-program/libc/include/unistd.h +++ b/lab8/tests/user-program/libc/include/unistd.h @@ -25,6 +25,8 @@ int chdir(const char *path); long lseek64(int fd, long offset, int whence); +void sync(void); + long syscall(long number, ...); #endif diff --git a/lab8/tests/user-program/libc/src/unistd.c b/lab8/tests/user-program/libc/src/unistd.c index fd9811ad3..60af46688 100644 --- a/lab8/tests/user-program/libc/src/unistd.c +++ b/lab8/tests/user-program/libc/src/unistd.c @@ -33,3 +33,5 @@ int chdir(const char *const path) { return syscall(SYS_chdir, path); } long lseek64(const int fd, const long offset, const int whence) { return syscall(SYS_lseek64, fd, offset, whence); } + +void sync(void) { syscall(SYS_sync); }