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..283d07950 --- /dev/null +++ b/lab5/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/lab5/c/include/oscos/console.h b/lab5/c/include/oscos/console.h new file mode 100644 index 000000000..65abb41d1 --- /dev/null +++ b/lab5/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/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/page-alloc.h b/lab5/c/include/oscos/mem/page-alloc.h new file mode 100644 index 000000000..eddee380c --- /dev/null +++ b/lab5/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/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..7aa7e7c99 --- /dev/null +++ b/lab5/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/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/sched.h b/lab5/c/include/oscos/sched.h new file mode 100644 index 000000000..2e92b5284 --- /dev/null +++ b/lab5/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 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); + +/// \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/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..a2e51e23f --- /dev/null +++ b/lab5/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/lab5/c/include/oscos/uapi/errno.h b/lab5/c/include/oscos/uapi/errno.h new file mode 100644 index 000000000..1632c0383 --- /dev/null +++ b/lab5/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/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 new file mode 100644 index 000000000..910ddcc61 --- /dev/null +++ b/lab5/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/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/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/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/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/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 new file mode 100644 index 000000000..496a6f539 --- /dev/null +++ b/lab5/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/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..0feae407a --- /dev/null +++ b/lab5/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/lab5/c/src/mem/malloc.c b/lab5/c/src/mem/malloc.c new file mode 100644 index 000000000..5819828d0 --- /dev/null +++ b/lab5/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/lab5/c/src/mem/page-alloc.c b/lab5/c/src/mem/page-alloc.c new file mode 100644 index 000000000..cd510492e --- /dev/null +++ b/lab5/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/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/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/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/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/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/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 new file mode 100644 index 000000000..3f937739e --- /dev/null +++ b/lab5/c/src/sched/sched.c @@ -0,0 +1,829 @@ +// 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 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); + } +} + +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/lab5/c/src/sched/schedule.S b/lab5/c/src/sched/schedule.S new file mode 100644 index 000000000..51d7213b5 --- /dev/null +++ b/lab5/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/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/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 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 new file mode 100644 index 000000000..448c7df93 --- /dev/null +++ b/lab5/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/lab5/c/src/start.S b/lab5/c/src/start.S new file mode 100644 index 000000000..df9718cad --- /dev/null +++ b/lab5/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/lab5/c/src/timer/delay.c b/lab5/c/src/timer/delay.c new file mode 100644 index 000000000..8f7a29c2e --- /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_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 new file mode 100644 index 000000000..a9c8b74a6 --- /dev/null +++ b/lab5/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/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/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/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/svc-handler.S b/lab5/c/src/xcpt/svc-handler.S new file mode 100644 index 000000000..434fe9ab2 --- /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, 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/lab5/c/src/xcpt/syscall-table.S b/lab5/c/src/xcpt/syscall-table.S new file mode 100644 index 000000000..396f9be11 --- /dev/null +++ b/lab5/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/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..c68a9a092 --- /dev/null +++ b/lab5/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/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 new file mode 100644 index 000000000..12e00d39c --- /dev/null +++ b/lab5/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 *))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; +} 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..d612ea85e --- /dev/null +++ b/lab5/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 *))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; +} 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..1ab6922ea --- /dev/null +++ b/lab5/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/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-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 diff --git a/lab5/tests/build-initrd.sh b/lab5/tests/build-initrd.sh new file mode 100755 index 000000000..fb15ec3b2 --- /dev/null +++ b/lab5/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/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/libc/.gitignore b/lab5/tests/user-program/libc/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/lab5/tests/user-program/libc/.gitignore @@ -0,0 +1 @@ +/build 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..866dc92c2 --- /dev/null +++ b/lab5/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/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..fdc717fa8 --- /dev/null +++ b/lab5/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/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/syscall_test/Makefile b/lab5/tests/user-program/syscall_test/Makefile new file mode 100644 index 000000000..c2a97559b --- /dev/null +++ b/lab5/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/lab5/tests/user-program/syscall_test/src/linker.ld b/lab5/tests/user-program/syscall_test/src/linker.ld new file mode 100644 index 000000000..255954576 --- /dev/null +++ b/lab5/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/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/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..2d5442152 --- /dev/null +++ b/lab6/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/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 +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..77425b4d2 --- /dev/null +++ b/lab6/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/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..95af6777a --- /dev/null +++ b/lab6/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/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/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/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..1deee96e8 --- /dev/null +++ b/lab6/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/lab6/c/include/oscos/mem/vm.h b/lab6/c/include/oscos/mem/vm.h new file mode 100644 index 000000000..801329a90 --- /dev/null +++ b/lab6/c/include/oscos/mem/vm.h @@ -0,0 +1,63 @@ +#ifndef OSCOS_MEM_VM_H +#define OSCOS_MEM_VM_H + +#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. +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; + int prot; +} mem_region_t; + +typedef struct { + rb_node_t *root; +} mem_regions_t; + +typedef struct { + mem_regions_t mem_regions; + 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; + +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_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); + +#endif 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/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..d3d710b84 --- /dev/null +++ b/lab6/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 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); + +/// \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/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 new file mode 100644 index 000000000..62c10613d --- /dev/null +++ b/lab6/c/include/oscos/uapi/sys/syscall.h @@ -0,0 +1,17 @@ +#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_mmap 10 +#define SYS_sigreturn 11 + +#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..2ae638b36 --- /dev/null +++ b/lab6/c/include/oscos/utils/rb.h @@ -0,0 +1,40 @@ +#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; + +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/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..a41e69a49 --- /dev/null +++ b/lab6/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/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..0cb84d408 --- /dev/null +++ b/lab6/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/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..2ae0f9561 --- /dev/null +++ b/lab6/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/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..0ab7218ff --- /dev/null +++ b/lab6/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/lab6/c/src/main.c b/lab6/c/src/main.c new file mode 100644 index 000000000..7aa13fbf5 --- /dev/null +++ b/lab6/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/lab6/c/src/mem/malloc.c b/lab6/c/src/mem/malloc.c new file mode 100644 index 000000000..87cacb7b2 --- /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] &= ~(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/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/shared-page.c b/lab6/c/src/mem/shared-page.c new file mode 100644 index 000000000..ef47a871f --- /dev/null +++ b/lab6/c/src/mem/shared-page.c @@ -0,0 +1,152 @@ +#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); + + // 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); +} + +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/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/vm.c b/lab6/c/src/mem/vm.c new file mode 100644 index 000000000..6d18e7648 --- /dev/null +++ b/lab6/c/src/mem/vm.c @@ -0,0 +1,695 @@ +#include "oscos/mem/vm.h" + +#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. +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)}; +} + +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; + + 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; +} + +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); + + 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); +} + +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 = + 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 == 0) { + 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); +} + +static 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); +} + +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 = + 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(); + } + + // 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 = 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; +} + +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. + + 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; + 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. + + 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(); + } + + // 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){ + .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; +} + +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); + + // 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. + + 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; + } + + // 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; +} + +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; + } +} + +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__( + "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"); +} + +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/mem/vm/kernel-page-tables.c b/lab6/c/src/mem/vm/kernel-page-tables.c new file mode 100644 index 000000000..5fee7a55b --- /dev/null +++ b/lab6/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/lab6/c/src/panic.c b/lab6/c/src/panic.c new file mode 100644 index 000000000..d43c8f2aa --- /dev/null +++ b/lab6/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/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..d18f35efe --- /dev/null +++ b/lab6/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/lab6/c/src/sched/sched.c b/lab6/c/src/sched/sched.c new file mode 100644 index 000000000..356f3db36 --- /dev/null +++ b/lab6/c/src/sched/sched.c @@ -0,0 +1,679 @@ +// 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; + } + + vm_addr_space_t addr_space = vm_new_addr_space(); + if (!addr_space.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 = 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; + 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 remove_text_region) { + thread_t *const curr_thread = current_thread(); + process_t *const curr_process = curr_thread->process; + + // Remove old text region. + + if (remove_text_region) { + 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); + } + + // 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 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. + + 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_addr_space(thread->process->addr_space); + 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; + } + + 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(); + 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->addr_space = 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 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); + } +} + +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/lab6/c/src/sched/schedule.S b/lab6/c/src/sched/schedule.S new file mode 100644 index 000000000..b16ff658a --- /dev/null +++ b/lab6/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/lab6/c/src/sched/sig-handler-main.S b/lab6/c/src/sched/sig-handler-main.S new file mode 100644 index 000000000..c9541fa42 --- /dev/null +++ b/lab6/c/src/sched/sig-handler-main.S @@ -0,0 +1,10 @@ +.section ".text" + +sig_handler_main: + blr x0 + mov x8, 11 // 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..7520ceff1 --- /dev/null +++ b/lab6/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/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..1587619e7 --- /dev/null +++ b/lab6/c/src/utils/rb.c @@ -0,0 +1,176 @@ +#include "oscos/utils/rb.h" + +#include "oscos/libc/string.h" +#include "oscos/mem/malloc.h" + +// 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 *), + 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; +} + +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 *), + 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); +} + +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 new file mode 100644 index 000000000..3cb754079 --- /dev/null +++ b/lab6/c/src/xcpt/data-abort-handler.c @@ -0,0 +1,56 @@ +#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 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", + 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 new file mode 100644 index 000000000..e2eba4385 --- /dev/null +++ b/lab6/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/lab6/c/src/xcpt/insn-abort-handler.c b/lab6/c/src/xcpt/insn-abort-handler.c new file mode 100644 index 000000000..dd4ec645f --- /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_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", + 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/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/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/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..e96b36ead --- /dev/null +++ b/lab6/c/src/xcpt/syscall-table.S @@ -0,0 +1,18 @@ +.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_mmap + 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..415db0e96 --- /dev/null +++ b/lab6/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/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/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..12e00d39c --- /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 *))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; +} 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..d612ea85e --- /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 *))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; +} 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..eedfe5f6b --- /dev/null +++ b/lab6/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/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.sh b/lab6/tests/build-initrd.sh new file mode 100755 index 000000000..88be4059b --- /dev/null +++ b/lab6/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/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..b0300d176 --- /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 sys/mman 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/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/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/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); +} 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..79510d671 --- /dev/null +++ b/lab6/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/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(); } 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..d924de51d --- /dev/null +++ b/lab7/c/Makefile @@ -0,0 +1,110 @@ +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 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 \ + 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/mmap \ + 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/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-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/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..75d483dd6 --- /dev/null +++ b/lab7/c/include/oscos/drivers/mailbox.h @@ -0,0 +1,30 @@ +#ifndef OSCOS_DRIVERS_MAILBOX_H +#define OSCOS_DRIVERS_MAILBOX_H + +#include +#include + +#define MAILBOX_CHANNEL_PROPERTY_TAGS_ARM_TO_VC ((unsigned char)8) + +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/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/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/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/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..2f903c39a --- /dev/null +++ b/lab7/c/include/oscos/fs/vfs.h @@ -0,0 +1,93 @@ +#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 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); + 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 { + 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); + 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; + +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); +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); +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); + +int vfs_mknod(const char *target, const char *device); + +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/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..e57f17902 --- /dev/null +++ b/lab7/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/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..14c3ca8fd --- /dev/null +++ b/lab7/c/include/oscos/mem/vm.h @@ -0,0 +1,70 @@ +#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" +#include "oscos/utils/rb.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_ANONYMOUS, + MEM_REGION_BACKED, + MEM_REGION_LINEAR +} mem_region_type_t; + +typedef struct { + void *start; + size_t len; + mem_region_type_t type; + union { + shared_file_t *backing_file; + pa_t pa_base; + }; + int prot; +} mem_region_t; + +typedef struct { + rb_node_t *root; +} mem_regions_t; + +typedef struct { + mem_regions_t mem_regions; + 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; + +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_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); + +#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..88df65f7b --- /dev/null +++ b/lab7/c/include/oscos/sched.h @@ -0,0 +1,160 @@ +#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_file The user program. +void exec_first(struct file *text_file); + +/// \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_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); + +/// \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 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); + +/// \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..78a8da16d --- /dev/null +++ b/lab7/c/include/oscos/uapi/errno.h @@ -0,0 +1,19 @@ +#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 ENOTTY 25 +#define EFBIG 27 +#define ESPIPE 29 +#define EROFS 30 +#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/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/mman.h b/lab7/c/include/oscos/uapi/sys/mman.h new file mode 100644 index 000000000..6d57130a7 --- /dev/null +++ b/lab7/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/lab7/c/include/oscos/uapi/sys/syscall.h b/lab7/c/include/oscos/uapi/sys/syscall.h new file mode 100644 index 000000000..3f5cd7545 --- /dev/null +++ b/lab7/c/include/oscos/uapi/sys/syscall.h @@ -0,0 +1,26 @@ +#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_mmap 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 +#define SYS_lseek64 18 +#define SYS_ioctl 19 +#define SYS_sigreturn 20 + +#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..e13b2ea8c --- /dev/null +++ b/lab7/c/include/oscos/uapi/unistd.h @@ -0,0 +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/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..2ae638b36 --- /dev/null +++ b/lab7/c/include/oscos/utils/rb.h @@ -0,0 +1,40 @@ +#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; + +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/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-dev.c b/lab7/c/src/console-dev.c new file mode 100644 index 000000000..2c9fa1829 --- /dev/null +++ b/lab7/c/src/console-dev.c @@ -0,0 +1,160 @@ +#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/unistd.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_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); +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); +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}; + +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, + .ioctl = _console_dev_ioctl}; + +static struct vnode_operations _console_dev_vnode_operations = { + .lookup = _console_dev_lookup, + .create = _console_dev_create, + .mkdir = _console_dev_mkdir, + .mknod = _console_mknod, + .get_size = _console_get_size}; + +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_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) { + (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; +} + +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; + (void)device; + + return -ENOTDIR; +} + +static long _console_get_size(struct vnode *const vnode) { + (void)vnode; + + return -1; +} 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/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..7835aad9e --- /dev/null +++ b/lab7/c/src/drivers/mailbox.c @@ -0,0 +1,135 @@ +#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]}; +} + +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/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/framebuffer-dev.c b/lab7/c/src/framebuffer-dev.c new file mode 100644 index 000000000..e9c01669a --- /dev/null +++ b/lab7/c/src/framebuffer-dev.c @@ -0,0 +1,243 @@ +#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/unistd.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); +static long _framebuffer_dev_get_size(struct vnode *dir_node); + +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, + .get_size = _framebuffer_dev_get_size}; + +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; +} + +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 new file mode 100644 index 000000000..94cd79172 --- /dev/null +++ b/lab7/c/src/fs/initramfs.c @@ -0,0 +1,376 @@ +#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/unistd.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_ioctl(struct file *file, unsigned long request, + void *payload); + +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); +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}; + +static struct file_operations _initramfs_file_operations = { + .write = _initramfs_write, + .read = _initramfs_read, + .open = _initramfs_open, + .close = _initramfs_close, + .lseek64 = _initramfs_lseek64, + .ioctl = _initramfs_ioctl}; + +static struct vnode_operations _initramfs_vnode_operations = { + .lookup = _initramfs_lookup, + .create = _initramfs_create, + .mkdir = _initramfs_mkdir, + .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, + 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) { + if (!initrd_is_init()) + return -EIO; + + 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_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) { + 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; +} + +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; + (void)device; + + 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 new file mode 100644 index 000000000..f4d9f1ac5 --- /dev/null +++ b/lab7/c/src/fs/tmpfs.c @@ -0,0 +1,475 @@ +#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/unistd.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_ioctl(struct file *file, unsigned long request, + void *payload); + +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); +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}; + +static struct file_operations _tmpfs_file_operations = {.write = _tmpfs_write, + .read = _tmpfs_read, + .open = _tmpfs_open, + .close = _tmpfs_close, + .lseek64 = + _tmpfs_lseek64, + .ioctl = _tmpfs_ioctl}; + +static struct vnode_operations _tmpfs_vnode_operations = { + .lookup = _tmpfs_lookup, + .create = _tmpfs_create, + .mkdir = _tmpfs_mkdir, + .mknod = _tmpfs_mknod, + .get_size = _tmpfs_get_size}; + +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_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) { + 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; + } + + 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) { + free(entry_component_name); + 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; + } + + 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) { + free(entry_component_name); + 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_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; +} + +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 new file mode 100644 index 000000000..2ecd67b55 --- /dev/null +++ b/lab7/c/src/fs/vfs.c @@ -0,0 +1,418 @@ +#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 *_devices = 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_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_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; +} + +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) { + 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); +} + +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); +} + +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; + + 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}; + + 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); + } +} + +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; + + // 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; + + 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) { + 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/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..6451926c4 --- /dev/null +++ b/lab7/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/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..624d09e4a --- /dev/null +++ b/lab7/c/src/main.c @@ -0,0 +1,112 @@ +#include "oscos/console-dev.h" +#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/framebuffer-dev.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 = 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) + 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); + + 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); + 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); + + 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..87cacb7b2 --- /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] &= ~(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/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..ef47a871f --- /dev/null +++ b/lab7/c/src/mem/shared-page.c @@ -0,0 +1,152 @@ +#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); + + // 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); +} + +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..60ab8f2de --- /dev/null +++ b/lab7/c/src/mem/vm.c @@ -0,0 +1,754 @@ +#include "oscos/mem/vm.h" + +#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/uapi/unistd.h" +#include "oscos/utils/align.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)}; +} + +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; + + 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; +} + +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); + + 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 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), + (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}}; + + _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 = + 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 == 0) { + 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); +} + +static 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); +} + +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, + (void (*)(void *))_vm_mem_regions_deleter); +} + +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; + + // 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) { + 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 = (mem_region->pa_base + offset) >> PAGE_ORDER; + break; + } + + default: + __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 = 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; +} + +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. + + 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; + 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. + + 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_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); + 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(); + } + + // 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){ + .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; +} + +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); + + // 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. + + 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; + } + + // 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; +} + +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; + } +} + +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__( + "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"); +} + +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/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..4deffe3e0 --- /dev/null +++ b/lab7/c/src/sched/sched.c @@ -0,0 +1,767 @@ +// 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; + } + + vm_addr_space_t addr_space = vm_new_addr_space(); + if (!addr_space.pgd) { + free(fp_simd_ctx); + free(process); + 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(); + + process->id = _alloc_pid(); + process->addr_space = addr_space; + + const mem_region_t stack_region = {.start = (void *)0xffffffffb000ULL, + .len = 4 << PAGE_ORDER, + .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, + .pa_base = 0x3b400000, + .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; + for (size_t i = 0; i < 32; i++) { + process->signal_handlers[i] = SIG_DFL; + } + process->cwd = rootfs.root; + 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; + 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(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 + // 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 mem_region_t text_region = {.start = (void *)0x0, + .len = ALIGN(text_len * 2, 4096), + .type = MEM_REGION_BACKED, + .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); + + // 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(struct file *const text_file) { + _exec_generic(text_file, false); +} + +void exec(struct file *const text_file) { _exec_generic(text_file, true); } + +static void _thread_cleanup(thread_t *const thread) { + if (thread->process) { + vm_drop_addr_space(thread->process->addr_space); + 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; + } + + 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(); + 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->addr_space = 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 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); + } +} + +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..6187d0bcb --- /dev/null +++ b/lab7/c/src/sched/sig-handler-main.S @@ -0,0 +1,10 @@ +.section ".text" + +sig_handler_main: + blr x0 + mov x8, 18 // 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..ba0e6d6ba --- /dev/null +++ b/lab7/c/src/shell.c @@ -0,0 +1,654 @@ +#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) { + console_fputs("Path: "); + + char path_buf[MAX_CMD_LEN + 1]; + _shell_read_line(path_buf, MAX_CMD_LEN + 1); + + 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; + } + + if (!process_create()) { + console_puts("oscsh: exec: out of memory"); + return; + } + + exec_first(user_program_file); + + // 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/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..1587619e7 --- /dev/null +++ b/lab7/c/src/utils/rb.c @@ -0,0 +1,176 @@ +#include "oscos/utils/rb.h" + +#include "oscos/libc/string.h" +#include "oscos/mem/malloc.h" + +// 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 *), + 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; +} + +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 *), + 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); +} + +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/lab7/c/src/xcpt/data-abort-handler.c b/lab7/c/src/xcpt/data-abort-handler.c new file mode 100644 index 000000000..3cb754079 --- /dev/null +++ b/lab7/c/src/xcpt/data-abort-handler.c @@ -0,0 +1,56 @@ +#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 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", + 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..dd4ec645f --- /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_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", + 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..cbf9f47b3 --- /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, 20 + 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..31fa48b94 --- /dev/null +++ b/lab7/c/src/xcpt/syscall-table.S @@ -0,0 +1,27 @@ +.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_mmap + b sys_open + b sys_close + b sys_write + b sys_read + b sys_mkdir + b sys_mount + b sys_chdir + b sys_lseek64 + b sys_ioctl + b sys_sigreturn + +.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/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..698e14089 --- /dev/null +++ b/lab7/c/src/xcpt/syscall/exec.c @@ -0,0 +1,21 @@ +#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; + + struct file *user_program_file; + const int open_result = vfs_open(name, 0, &user_program_file); + if (open_result < 0) + return open_result; + + exec(user_program_file); + + // 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/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/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/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/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/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/mmap.c b/lab7/c/src/xcpt/syscall/mmap.c new file mode 100644 index 000000000..9a6c9fd96 --- /dev/null +++ b/lab7/c/src/xcpt/syscall/mmap.c @@ -0,0 +1,31 @@ +#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_ANONYMOUS, + .prot = prot}; + vm_mem_regions_insert_region(&curr_process->addr_space.mem_regions, + &mem_region); + + return mmap_addr; +} 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..c86415a33 --- /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 fd; +} 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/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..55fe74917 --- /dev/null +++ b/lab7/c/src/xcpt/syscall/uart-read.c @@ -0,0 +1,5 @@ +#include "oscos/console-suspend.h" + +ssize_t sys_uart_read(char buf[const], const size_t size) { + 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 new file mode 100644 index 000000000..155c7fbce --- /dev/null +++ b/lab7/c/src/xcpt/syscall/uart-write.c @@ -0,0 +1,5 @@ +#include "oscos/console-suspend.h" + +size_t sys_uart_write(const char buf[const], const size_t size) { + return console_write_suspend(buf, size); +} 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/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..7ee63fafb --- /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/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/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..89787ac9c --- /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 fcntl mbox signal stdio stdlib string sys/ioctl \ + 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/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/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/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/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/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/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..8fd615423 --- /dev/null +++ b/lab7/tests/user-program/libc/include/unistd.h @@ -0,0 +1,30 @@ +#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 lseek64(int fd, long offset, int whence); + +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/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/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/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/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 new file mode 100644 index 000000000..fd9811ad3 --- /dev/null +++ b/lab7/tests/user-program/libc/src/unistd.c @@ -0,0 +1,35 @@ +#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); } + +long lseek64(const int fd, const long offset, const int whence) { + return syscall(SYS_lseek64, fd, offset, whence); +} 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(); } 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..564d466aa --- /dev/null +++ b/lab8/c/Makefile @@ -0,0 +1,111 @@ +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 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 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 \ + 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/mmap \ + 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 +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 -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 -drive if=sd,file=../tests/sd.img,format=raw -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-dev.h b/lab8/c/include/oscos/console-dev.h new file mode 100644 index 000000000..bf048079f --- /dev/null +++ b/lab8/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/lab8/c/include/oscos/console-suspend.h b/lab8/c/include/oscos/console-suspend.h new file mode 100644 index 000000000..c667cd49f --- /dev/null +++ b/lab8/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/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..75d483dd6 --- /dev/null +++ b/lab8/c/include/oscos/drivers/mailbox.h @@ -0,0 +1,30 @@ +#ifndef OSCOS_DRIVERS_MAILBOX_H +#define OSCOS_DRIVERS_MAILBOX_H + +#include +#include + +#define MAILBOX_CHANNEL_PROPERTY_TAGS_ARM_TO_VC ((unsigned char)8) + +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/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/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/framebuffer-dev.h b/lab8/c/include/oscos/framebuffer-dev.h new file mode 100644 index 000000000..7748b43f6 --- /dev/null +++ b/lab8/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/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/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/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..9a1d72b47 --- /dev/null +++ b/lab8/c/include/oscos/fs/vfs.h @@ -0,0 +1,101 @@ +#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 super_operations *s_ops; + void *internal; +}; + +struct filesystem { + const char *name; + 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); + 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 { + 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); + int (*mknod)(struct vnode *dir_node, struct vnode **target, + const char *component_name, struct device *device); + 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); +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); +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); +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); + +int vfs_mknod(const char *target, const char *device); + +void vfs_sync_all(void); + +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..0764a113e --- /dev/null +++ b/lab8/c/include/oscos/libc/ctype.h @@ -0,0 +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/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..107a6d5d4 --- /dev/null +++ b/lab8/c/include/oscos/libc/string.h @@ -0,0 +1,33 @@ +#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 *strrchr(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..14c3ca8fd --- /dev/null +++ b/lab8/c/include/oscos/mem/vm.h @@ -0,0 +1,70 @@ +#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" +#include "oscos/utils/rb.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_ANONYMOUS, + MEM_REGION_BACKED, + MEM_REGION_LINEAR +} mem_region_type_t; + +typedef struct { + void *start; + size_t len; + mem_region_type_t type; + union { + shared_file_t *backing_file; + pa_t pa_base; + }; + int prot; +} mem_region_t; + +typedef struct { + rb_node_t *root; +} mem_regions_t; + +typedef struct { + mem_regions_t mem_regions; + 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; + +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_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); + +#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..88df65f7b --- /dev/null +++ b/lab8/c/include/oscos/sched.h @@ -0,0 +1,160 @@ +#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_file The user program. +void exec_first(struct file *text_file); + +/// \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_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); + +/// \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 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); + +/// \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..e690799f2 --- /dev/null +++ b/lab8/c/include/oscos/uapi/errno.h @@ -0,0 +1,21 @@ +#define EPERM 1 +#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 ENOTTY 25 +#define EFBIG 27 +#define ENOSPC 28 +#define ESPIPE 29 +#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/sys/mman.h b/lab8/c/include/oscos/uapi/sys/mman.h new file mode 100644 index 000000000..6d57130a7 --- /dev/null +++ b/lab8/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/lab8/c/include/oscos/uapi/sys/syscall.h b/lab8/c/include/oscos/uapi/sys/syscall.h new file mode 100644 index 000000000..32e8784ec --- /dev/null +++ b/lab8/c/include/oscos/uapi/sys/syscall.h @@ -0,0 +1,27 @@ +#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_mmap 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 +#define SYS_lseek64 18 +#define SYS_ioctl 19 +#define SYS_sync 20 +#define SYS_sigreturn 21 + +#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..e13b2ea8c --- /dev/null +++ b/lab8/c/include/oscos/uapi/unistd.h @@ -0,0 +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/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..2ae638b36 --- /dev/null +++ b/lab8/c/include/oscos/utils/rb.h @@ -0,0 +1,40 @@ +#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; + +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/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-dev.c b/lab8/c/src/console-dev.c new file mode 100644 index 000000000..2c9fa1829 --- /dev/null +++ b/lab8/c/src/console-dev.c @@ -0,0 +1,160 @@ +#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/unistd.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_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); +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); +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}; + +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, + .ioctl = _console_dev_ioctl}; + +static struct vnode_operations _console_dev_vnode_operations = { + .lookup = _console_dev_lookup, + .create = _console_dev_create, + .mkdir = _console_dev_mkdir, + .mknod = _console_mknod, + .get_size = _console_get_size}; + +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_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) { + (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; +} + +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; + (void)device; + + return -ENOTDIR; +} + +static long _console_get_size(struct vnode *const vnode) { + (void)vnode; + + return -1; +} diff --git a/lab8/c/src/console-suspend.c b/lab8/c/src/console-suspend.c new file mode 100644 index 000000000..44be5c869 --- /dev/null +++ b/lab8/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/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..7835aad9e --- /dev/null +++ b/lab8/c/src/drivers/mailbox.c @@ -0,0 +1,135 @@ +#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]}; +} + +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/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/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/framebuffer-dev.c b/lab8/c/src/framebuffer-dev.c new file mode 100644 index 000000000..e9c01669a --- /dev/null +++ b/lab8/c/src/framebuffer-dev.c @@ -0,0 +1,243 @@ +#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/unistd.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); +static long _framebuffer_dev_get_size(struct vnode *dir_node); + +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, + .get_size = _framebuffer_dev_get_size}; + +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; +} + +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/lab8/c/src/fs/initramfs.c b/lab8/c/src/fs/initramfs.c new file mode 100644 index 000000000..7b7f92f09 --- /dev/null +++ b/lab8/c/src/fs/initramfs.c @@ -0,0 +1,389 @@ +#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/unistd.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_ioctl(struct file *file, unsigned long request, + void *payload); + +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); +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}; + +static struct file_operations _initramfs_file_operations = { + .write = _initramfs_write, + .read = _initramfs_read, + .open = _initramfs_open, + .close = _initramfs_close, + .lseek64 = _initramfs_lseek64, + .ioctl = _initramfs_ioctl}; + +static struct vnode_operations _initramfs_vnode_operations = { + .lookup = _initramfs_lookup, + .create = _initramfs_create, + .mkdir = _initramfs_mkdir, + .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) { + (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) { + if (!initrd_is_init()) + return -EIO; + + struct vnode *const root_vnode = _initramfs_create_vnode(mount, NULL, NULL); + if (!root_vnode) + return -ENOMEM; + + *mount = (struct mount){.fs = fs, + .root = root_vnode, + .s_ops = &_initramfs_super_operations, + .internal = NULL}; + + 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_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) { + 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; +} + +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; + (void)device; + + 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; +} + +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 new file mode 100644 index 000000000..5a6b23142 --- /dev/null +++ b/lab8/c/src/fs/sd-fat32.c @@ -0,0 +1,1122 @@ +#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" +#include "oscos/uapi/unistd.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, fat_n_sectors, 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 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; + +typedef struct { + sd_fat32_internal_type_t type; + 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); +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); +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); + +static void _sd_fat32_sync_fs(struct mount *mount); + +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, + .ioctl = _sd_fat32_ioctl}; + +static struct vnode_operations _sd_fat32_vnode_operations = { + .lookup = _sd_fat32_lookup, + .create = _sd_fat32_create, + .mkdir = _sd_fat32_mkdir, + .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) { + 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 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_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_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_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) { + 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 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, block_cache, 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, + block_cache_t *const block_cache, + unsigned char block_buf[static 512]) { + 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, 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_cache, block_buf); + if (new_cluster_addr == (size_t)-1) + return -1; + + _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, 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, block_cache, _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) { + (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 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; + + 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){ + .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, + .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; + + sd_fat32_fs_internal_t *const fs_internal = + malloc(sizeof(sd_fat32_fs_internal_t)); + if (!fs_internal) { + 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; + 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", + root_cluster_addr); + + // Create vnode. + + struct vnode *const root_vnode = + _sd_fat32_create_dir_vnode(mount, NULL, root_cluster_addr); + if (!root_vnode) { + free(fs_internal); + free(block_buf); + return -ENOMEM; + } + + 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; +} + +static int _sd_fat32_write(struct file *const file, const 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; + 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) + 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_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_cached( + block_cache, + _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; + } + + if (file_offset >= write_end_offset) // Done writing. + break; + + const uint32_t fat_entry = + _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, 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); + 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_cached( + block_cache, + _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->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_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_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); + CRITICAL_SECTION_LEAVE(daif_val); + return n_chars_written; +} + +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; + 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) + return -ENOMEM; + + 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 (;;) { + 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_cached( + block_cache, + _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 >= read_end_offset) // Done reading. + break; + + const uint32_t fat_entry = + _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); + n_chars_read += read_end_offset - file_offset; + break; + } + + curr_cluster_addr = fat_entry; + } + + file->f_pos += n_chars_read; + + free(block_buf); + 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; + } +} + +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) { + 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; + 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; + 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 the filename. + + 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; + + 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 (dir_table_sector_of_cluster = 0; + dir_table_sector_of_cluster < fsinfo->cluster_n_sectors; + dir_table_sector_of_cluster++) { + readblock_cached( + block_cache, + _sd_fat32_cluster_addr_to_data_lba(fsinfo, dir_table_cluster_addr) + + dir_table_sector_of_cluster, + block_buf); + + 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 + dir_entry_ix; + 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 (done) + break; + } + + if (done) + break; + + 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; + + dir_table_cluster_addr = fat_entry; + } + + 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 = ((size_t)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, dir_table_cluster_addr, + dir_table_sector_of_cluster, + dir_entry_ix, 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_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; + 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. + + 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_cache, 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. + // 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, 0, 0, 0, + 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_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 (dir_table_sector_of_cluster = 0; + dir_table_sector_of_cluster < fsinfo->cluster_n_sectors; + dir_table_sector_of_cluster++) { + readblock_cached( + block_cache, + _sd_fat32_cluster_addr_to_data_lba(fsinfo, dir_table_cluster_addr) + + dir_table_sector_of_cluster, + block_buf); + + 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; + 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) { // 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, block_cache, dir_table_cluster_addr, block_buf); + if (!(0x2 <= fat_entry && fat_entry <= 0x0ffffff7)) // Nothing more to read. + break; + + dir_table_cluster_addr = fat_entry; + } + + if (!dir_entry) { + dir_table_cluster_addr = _sd_fat32_alloc_free_cluster_and_extend_chain( + fsinfo, block_cache, dir_table_cluster_addr, block_buf); + if (dir_table_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; + dir_table_sector_of_cluster = 0; + dir_entry_ix = 0; + 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_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_cache, block_buf); + + free(block_buf); + + // 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}; + + 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) { + 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) { + 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; +} + +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 new file mode 100644 index 000000000..b9b440c4b --- /dev/null +++ b/lab8/c/src/fs/tmpfs.c @@ -0,0 +1,488 @@ +#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/unistd.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_ioctl(struct file *file, unsigned long request, + void *payload); + +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); +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, + .read = _tmpfs_read, + .open = _tmpfs_open, + .close = _tmpfs_close, + .lseek64 = + _tmpfs_lseek64, + .ioctl = _tmpfs_ioctl}; + +static struct vnode_operations _tmpfs_vnode_operations = { + .lookup = _tmpfs_lookup, + .create = _tmpfs_create, + .mkdir = _tmpfs_mkdir, + .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, + 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, + .s_ops = &_tmpfs_super_operations, + .internal = NULL}; + + 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_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) { + 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; + } + + 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) { + free(entry_component_name); + 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; + } + + 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) { + free(entry_component_name); + 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_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; +} + +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; +} + +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 new file mode 100644 index 000000000..8ab883277 --- /dev/null +++ b/lab8/c/src/fs/vfs.c @@ -0,0 +1,442 @@ +#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 *_devices = 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_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_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; +} + +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) { + 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); +} + +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); +} + +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; + + 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}; + + 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); + } +} + +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; + + // 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; + + struct vnode *target_vnode; + return mountpoint->v_ops->mknod(mountpoint, &target_vnode, + 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) + 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..38c56102f --- /dev/null +++ b/lab8/c/src/libc/ctype.c @@ -0,0 +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; } 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..70a97f88c --- /dev/null +++ b/lab8/c/src/libc/string.c @@ -0,0 +1,157 @@ +#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 *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; + 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..fa7b0bf6e --- /dev/null +++ b/lab8/c/src/main.c @@ -0,0 +1,126 @@ +#include "oscos/console-dev.h" +#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/drivers/sdhost.h" +#include "oscos/framebuffer-dev.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" +#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. + + sd_init(); + + 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 = register_filesystem(&sd_fat32); + if (vfs_op_result < 0) + PANIC("Cannot register fat32: 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 = 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) + 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); + + 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); + vfs_op_result = vfs_mknod("/dev/framebuffer", "framebuffer"); + if (vfs_op_result < 0) + PANIC("Cannot mknod /dev/framebuffer: 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(); + 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..ef47a871f --- /dev/null +++ b/lab8/c/src/mem/shared-page.c @@ -0,0 +1,152 @@ +#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); + + // 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); +} + +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..60ab8f2de --- /dev/null +++ b/lab8/c/src/mem/vm.c @@ -0,0 +1,754 @@ +#include "oscos/mem/vm.h" + +#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/uapi/unistd.h" +#include "oscos/utils/align.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)}; +} + +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; + + 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; +} + +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); + + 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 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), + (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}}; + + _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 = + 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 == 0) { + 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); +} + +static 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); +} + +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, + (void (*)(void *))_vm_mem_regions_deleter); +} + +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; + + // 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) { + 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 = (mem_region->pa_base + offset) >> PAGE_ORDER; + break; + } + + default: + __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 = 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; +} + +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. + + 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; + 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. + + 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_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); + 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(); + } + + // 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){ + .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; +} + +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); + + // 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. + + 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; + } + + // 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; +} + +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; + } +} + +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__( + "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"); +} + +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/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..4deffe3e0 --- /dev/null +++ b/lab8/c/src/sched/sched.c @@ -0,0 +1,767 @@ +// 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; + } + + vm_addr_space_t addr_space = vm_new_addr_space(); + if (!addr_space.pgd) { + free(fp_simd_ctx); + free(process); + 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(); + + process->id = _alloc_pid(); + process->addr_space = addr_space; + + const mem_region_t stack_region = {.start = (void *)0xffffffffb000ULL, + .len = 4 << PAGE_ORDER, + .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, + .pa_base = 0x3b400000, + .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; + for (size_t i = 0; i < 32; i++) { + process->signal_handlers[i] = SIG_DFL; + } + process->cwd = rootfs.root; + 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; + 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(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 + // 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 mem_region_t text_region = {.start = (void *)0x0, + .len = ALIGN(text_len * 2, 4096), + .type = MEM_REGION_BACKED, + .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); + + // 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(struct file *const text_file) { + _exec_generic(text_file, false); +} + +void exec(struct file *const text_file) { _exec_generic(text_file, true); } + +static void _thread_cleanup(thread_t *const thread) { + if (thread->process) { + vm_drop_addr_space(thread->process->addr_space); + 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; + } + + 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(); + 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->addr_space = 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 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); + } +} + +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..841ae7a62 --- /dev/null +++ b/lab8/c/src/sched/sig-handler-main.S @@ -0,0 +1,12 @@ +#include "oscos/uapi/sys/syscall.h" + +.section ".text" + +sig_handler_main: + blr x0 + mov x8, 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..4f8e1acf7 --- /dev/null +++ b/lab8/c/src/shell.c @@ -0,0 +1,681 @@ +#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) { + console_fputs("Path: "); + + char path_buf[MAX_CMD_LEN + 1]; + _shell_read_line(path_buf, MAX_CMD_LEN + 1); + + 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; + } + + if (!process_create()) { + console_puts("oscsh: exec: out of memory"); + return; + } + + exec_first(user_program_file); + + // 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_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; + 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 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/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..1587619e7 --- /dev/null +++ b/lab8/c/src/utils/rb.c @@ -0,0 +1,176 @@ +#include "oscos/utils/rb.h" + +#include "oscos/libc/string.h" +#include "oscos/mem/malloc.h" + +// 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 *), + 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; +} + +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 *), + 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); +} + +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/lab8/c/src/xcpt/data-abort-handler.c b/lab8/c/src/xcpt/data-abort-handler.c new file mode 100644 index 000000000..3cb754079 --- /dev/null +++ b/lab8/c/src/xcpt/data-abort-handler.c @@ -0,0 +1,56 @@ +#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 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", + 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..dd4ec645f --- /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_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", + 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..11aa071e8 --- /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, 21 + 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..98bd54ec3 --- /dev/null +++ b/lab8/c/src/xcpt/syscall-table.S @@ -0,0 +1,28 @@ +.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_mmap + b sys_open + b sys_close + b sys_write + b sys_read + b sys_mkdir + b sys_mount + b sys_chdir + b sys_lseek64 + b sys_ioctl + b sys_sync + b sys_sigreturn + +.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..698e14089 --- /dev/null +++ b/lab8/c/src/xcpt/syscall/exec.c @@ -0,0 +1,21 @@ +#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; + + struct file *user_program_file; + const int open_result = vfs_open(name, 0, &user_program_file); + if (open_result < 0) + return open_result; + + exec(user_program_file); + + // 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/ioctl.c b/lab8/c/src/xcpt/syscall/ioctl.c new file mode 100644 index 000000000..00091c3c1 --- /dev/null +++ b/lab8/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/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/lseek64.c b/lab8/c/src/xcpt/syscall/lseek64.c new file mode 100644 index 000000000..6b6db0c5f --- /dev/null +++ b/lab8/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/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/mmap.c b/lab8/c/src/xcpt/syscall/mmap.c new file mode 100644 index 000000000..9a6c9fd96 --- /dev/null +++ b/lab8/c/src/xcpt/syscall/mmap.c @@ -0,0 +1,31 @@ +#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_ANONYMOUS, + .prot = prot}; + vm_mem_regions_insert_region(&curr_process->addr_space.mem_regions, + &mem_region); + + return mmap_addr; +} 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..c86415a33 --- /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 fd; +} 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/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/c/src/xcpt/syscall/uart-read.c b/lab8/c/src/xcpt/syscall/uart-read.c new file mode 100644 index 000000000..55fe74917 --- /dev/null +++ b/lab8/c/src/xcpt/syscall/uart-read.c @@ -0,0 +1,5 @@ +#include "oscos/console-suspend.h" + +ssize_t sys_uart_read(char buf[const], const size_t size) { + return console_read_suspend(buf, size); +} 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..155c7fbce --- /dev/null +++ b/lab8/c/src/xcpt/syscall/uart-write.c @@ -0,0 +1,5 @@ +#include "oscos/console-suspend.h" + +size_t sys_uart_write(const char buf[const], const size_t size) { + return console_write_suspend(buf, size); +} 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..8adec4632 --- /dev/null +++ b/lab8/tests/.gitignore @@ -0,0 +1,4 @@ +/initramfs.cpio +/bcm2710-rpi-3-b-plus.dtb +/sd.img +/FAT_R.TXT 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/build-initrd.sh b/lab8/tests/build-initrd.sh new file mode 100755 index 000000000..e64bb7ed9 --- /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/4ee703906d67d0333ef4c215dc060ab3/vfs2.img \ + -O rootfs/vfs2.img + +cd rootfs +find . -mindepth 1 | cpio -o -H newc > ../initramfs.cpio + +cd .. +rm -r rootfs 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.' 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..89787ac9c --- /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/ioctl \ + 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/ioctl.h b/lab8/tests/user-program/libc/include/sys/ioctl.h new file mode 100644 index 000000000..bb95ddc71 --- /dev/null +++ b/lab8/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/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..185b0494f --- /dev/null +++ b/lab8/tests/user-program/libc/include/unistd.h @@ -0,0 +1,32 @@ +#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 lseek64(int fd, long offset, int whence); + +void sync(void); + +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/ioctl.c b/lab8/tests/user-program/libc/src/sys/ioctl.c new file mode 100644 index 000000000..52ce1c2e7 --- /dev/null +++ b/lab8/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/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..60af46688 --- /dev/null +++ b/lab8/tests/user-program/libc/src/unistd.c @@ -0,0 +1,37 @@ +#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); } + +long lseek64(const int fd, const long offset, const int whence) { + return syscall(SYS_lseek64, fd, offset, whence); +} + +void sync(void) { syscall(SYS_sync); } 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(); }