From 60eacadafeaedfcd220c36348d509d8c86d474b9 Mon Sep 17 00:00:00 2001 From: Jay Lee Date: Wed, 26 Jun 2024 14:40:18 +0800 Subject: [PATCH 1/3] lab6: enable interrupt after syscall --- lab6/src/exception.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lab6/src/exception.c b/lab6/src/exception.c index b67d2c6e1..53757854b 100644 --- a/lab6/src/exception.c +++ b/lab6/src/exception.c @@ -137,6 +137,7 @@ void user_exception_handler_c(trapframe_t* tf) { sigreturn(); break; } + el1_interrupt_disable(); return; } From 5a0a392be3f9df4a528393a95a1e729286d44834 Mon Sep 17 00:00:00 2001 From: Jay Lee Date: Wed, 26 Jun 2024 14:40:54 +0800 Subject: [PATCH 2/3] lab8: init commit --- lab8/.gitignore | 1 + lab8/Makefile | 47 ++ lab8/a.out | 477 ++++++++++++++ lab8/bcm2710-rpi-3-b-plus.dtb | Bin 0 -> 34228 bytes lab8/bootloader/Makefile | 37 ++ lab8/bootloader/build.sh | 3 + lab8/bootloader/include/mini_uart.h | 11 + lab8/bootloader/include/mm.h | 20 + lab8/bootloader/include/peripherals/base.h | 6 + lab8/bootloader/include/peripherals/gpio.h | 12 + .../include/peripherals/mini_uart.h | 19 + lab8/bootloader/include/shell.h | 7 + lab8/bootloader/include/utils.h | 8 + lab8/bootloader/sender.py | 52 ++ lab8/bootloader/src/boot.S | 47 ++ lab8/bootloader/src/config.txt | 2 + lab8/bootloader/src/kernel.c | 10 + lab8/bootloader/src/linker.ld | 23 + lab8/bootloader/src/mini_uart.c | 79 +++ lab8/bootloader/src/mm.S | 6 + lab8/bootloader/src/shell.c | 125 ++++ lab8/bootloader/src/utils.S | 15 + lab8/build.sh | 3 + lab8/config.txt | 3 + lab8/include/alloc.h | 94 +++ lab8/include/c_utils.h | 12 + lab8/include/exception.h | 35 ++ lab8/include/fdt.h | 34 + lab8/include/fs_cpio.h | 76 +++ lab8/include/fs_framebufferfs.h | 57 ++ lab8/include/fs_fsinit.h | 13 + lab8/include/fs_tmpfs.h | 64 ++ lab8/include/fs_uartfs.h | 41 ++ lab8/include/fs_vfs.h | 110 ++++ lab8/include/initrd.h | 40 ++ lab8/include/list.h | 459 ++++++++++++++ lab8/include/mbox.h | 26 + lab8/include/mini_uart.h | 19 + lab8/include/mm.h | 19 + lab8/include/peripherals/base.h | 6 + lab8/include/peripherals/gpio.h | 12 + lab8/include/peripherals/irq.h | 17 + lab8/include/peripherals/p_mini_uart.h | 20 + lab8/include/reboot.h | 11 + lab8/include/shell.h | 6 + lab8/include/signal.h | 10 + lab8/include/string.h | 14 + lab8/include/syscall.h | 27 + lab8/include/tasklist.h | 17 + lab8/include/thread.h | 97 +++ lab8/include/timer.h | 30 + lab8/initramfs.cpio | Bin 0 -> 405504 bytes lab8/rootfs/dir/file1 | 1 + lab8/rootfs/dir/file2.txt | 1 + lab8/rootfs/test3 | 0 lab8/rootfs/user_prog.img | Bin 0 -> 24 bytes lab8/src/alloc.c | 590 ++++++++++++++++++ lab8/src/boot.S | 182 ++++++ lab8/src/c_utils.c | 66 ++ lab8/src/context_switch.S | 51 ++ lab8/src/exception.c | 290 +++++++++ lab8/src/fdt.c | 98 +++ lab8/src/fs_cpiofs.c | 500 +++++++++++++++ lab8/src/fs_framebufferfs.c | 272 ++++++++ lab8/src/fs_fsinit.c | 49 ++ lab8/src/fs_tmpfs.c | 398 ++++++++++++ lab8/src/fs_uartfs.c | 118 ++++ lab8/src/fs_vfs.c | 572 +++++++++++++++++ lab8/src/initrd.c | 295 +++++++++ lab8/src/kernel.c | 74 +++ lab8/src/linker.ld | 21 + lab8/src/mbox.c | 67 ++ lab8/src/mini_uart.c | 228 +++++++ lab8/src/mm.S | 19 + lab8/src/reboot.c | 11 + lab8/src/shell.c | 95 +++ lab8/src/signal.c | 66 ++ lab8/src/string.c | 110 ++++ lab8/src/syscall.c | 243 ++++++++ lab8/src/tasklist.c | 93 +++ lab8/src/thread.c | 400 ++++++++++++ lab8/src/timer.c | 114 ++++ lab8/user_prog/Makefile | 28 + lab8/user_prog/linker.ld | 9 + lab8/user_prog/user_prog.S | 11 + lab8/user_prog/user_prog.img | Bin 0 -> 24 bytes 86 files changed, 7451 insertions(+) create mode 100644 lab8/.gitignore create mode 100644 lab8/Makefile create mode 100644 lab8/a.out create mode 100644 lab8/bcm2710-rpi-3-b-plus.dtb create mode 100644 lab8/bootloader/Makefile create mode 100755 lab8/bootloader/build.sh create mode 100644 lab8/bootloader/include/mini_uart.h create mode 100644 lab8/bootloader/include/mm.h create mode 100644 lab8/bootloader/include/peripherals/base.h create mode 100644 lab8/bootloader/include/peripherals/gpio.h create mode 100644 lab8/bootloader/include/peripherals/mini_uart.h create mode 100644 lab8/bootloader/include/shell.h create mode 100644 lab8/bootloader/include/utils.h create mode 100644 lab8/bootloader/sender.py create mode 100644 lab8/bootloader/src/boot.S create mode 100644 lab8/bootloader/src/config.txt create mode 100644 lab8/bootloader/src/kernel.c create mode 100644 lab8/bootloader/src/linker.ld create mode 100644 lab8/bootloader/src/mini_uart.c create mode 100644 lab8/bootloader/src/mm.S create mode 100644 lab8/bootloader/src/shell.c create mode 100644 lab8/bootloader/src/utils.S create mode 100755 lab8/build.sh create mode 100644 lab8/config.txt create mode 100644 lab8/include/alloc.h create mode 100644 lab8/include/c_utils.h create mode 100644 lab8/include/exception.h create mode 100644 lab8/include/fdt.h create mode 100644 lab8/include/fs_cpio.h create mode 100644 lab8/include/fs_framebufferfs.h create mode 100644 lab8/include/fs_fsinit.h create mode 100644 lab8/include/fs_tmpfs.h create mode 100644 lab8/include/fs_uartfs.h create mode 100644 lab8/include/fs_vfs.h create mode 100644 lab8/include/initrd.h create mode 100644 lab8/include/list.h create mode 100644 lab8/include/mbox.h create mode 100644 lab8/include/mini_uart.h create mode 100644 lab8/include/mm.h create mode 100644 lab8/include/peripherals/base.h create mode 100644 lab8/include/peripherals/gpio.h create mode 100644 lab8/include/peripherals/irq.h create mode 100644 lab8/include/peripherals/p_mini_uart.h create mode 100644 lab8/include/reboot.h create mode 100644 lab8/include/shell.h create mode 100644 lab8/include/signal.h create mode 100644 lab8/include/string.h create mode 100644 lab8/include/syscall.h create mode 100644 lab8/include/tasklist.h create mode 100644 lab8/include/thread.h create mode 100644 lab8/include/timer.h create mode 100644 lab8/initramfs.cpio create mode 100644 lab8/rootfs/dir/file1 create mode 100644 lab8/rootfs/dir/file2.txt create mode 100644 lab8/rootfs/test3 create mode 100755 lab8/rootfs/user_prog.img create mode 100644 lab8/src/alloc.c create mode 100644 lab8/src/boot.S create mode 100644 lab8/src/c_utils.c create mode 100644 lab8/src/context_switch.S create mode 100644 lab8/src/exception.c create mode 100644 lab8/src/fdt.c create mode 100644 lab8/src/fs_cpiofs.c create mode 100644 lab8/src/fs_framebufferfs.c create mode 100644 lab8/src/fs_fsinit.c create mode 100644 lab8/src/fs_tmpfs.c create mode 100644 lab8/src/fs_uartfs.c create mode 100644 lab8/src/fs_vfs.c create mode 100644 lab8/src/initrd.c create mode 100644 lab8/src/kernel.c create mode 100644 lab8/src/linker.ld create mode 100644 lab8/src/mbox.c create mode 100644 lab8/src/mini_uart.c create mode 100644 lab8/src/mm.S create mode 100644 lab8/src/reboot.c create mode 100644 lab8/src/shell.c create mode 100644 lab8/src/signal.c create mode 100644 lab8/src/string.c create mode 100644 lab8/src/syscall.c create mode 100644 lab8/src/tasklist.c create mode 100644 lab8/src/thread.c create mode 100644 lab8/src/timer.c create mode 100644 lab8/user_prog/Makefile create mode 100644 lab8/user_prog/linker.ld create mode 100644 lab8/user_prog/user_prog.S create mode 100755 lab8/user_prog/user_prog.img diff --git a/lab8/.gitignore b/lab8/.gitignore new file mode 100644 index 000000000..70f5b5ae2 --- /dev/null +++ b/lab8/.gitignore @@ -0,0 +1 @@ +!user_prog.img \ No newline at end of file diff --git a/lab8/Makefile b/lab8/Makefile new file mode 100644 index 000000000..c1309f61a --- /dev/null +++ b/lab8/Makefile @@ -0,0 +1,47 @@ +ARMGNU ?= aarch64-linux-gnu + +COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only -ggdb +ASMOPS = -Iinclude -ggdb + +BUILD_DIR = build +SRC_DIR = src + +all : kernel8.img + +clean : + rm -rf $(BUILD_DIR) *.img + +gui: + qemu-system-aarch64 -M raspi3b -kernel kernel8.img -serial null -serial stdio -initrd initramfs.cpio -dtb bcm2710-rpi-3-b-plus.dtb + +run: + qemu-system-aarch64 -M raspi3b -kernel kernel8.img -display none -serial null -serial stdio -initrd initramfs.cpio -dtb bcm2710-rpi-3-b-plus.dtb + +debug: + qemu-system-aarch64 -M raspi3b -kernel kernel8.img -display none -serial null -serial stdio -initrd initramfs.cpio -dtb bcm2710-rpi-3-b-plus.dtb -s -S + +send: + sudo python bootloader/sender.py -f kernel8.img + +cpio: + rm -f initramfs.cpio + cd rootfs ; find . | cpio -o -H newc > ../initramfs.cpio + +$(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c + mkdir -p $(@D) + $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ + +$(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S + $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ + +C_FILES = $(wildcard $(SRC_DIR)/*.c) +ASM_FILES = $(wildcard $(SRC_DIR)/*.S) +OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) +OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) + +DEP_FILES = $(OBJ_FILES:%.o=%.d) +-include $(DEP_FILES) + +kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) + $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) + $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img \ No newline at end of file diff --git a/lab8/a.out b/lab8/a.out new file mode 100644 index 000000000..cf8b4cebd --- /dev/null +++ b/lab8/a.out @@ -0,0 +1,477 @@ +qemu-system-aarch64 -M raspi3b -kernel kernel8.img -display none -serial null -serial stdio -initrd initramfs.cpio -dtb bcm2710-rpi-3-b-plus.dtb +DTB base address: 08200000 +ramfs_end: 0803C600 +ramfs_base: 08000000 +total_page: 0003B400 +memory_reserve: 00000000 ~ 00001000 +memory_reserve: 08000000 ~ 0803C600 +memory_reserve: 08200000 ~ 08215988 +memory_reserve: 00080000 ~ 00200000 +old_heap_top: 10000000 +heap_top: 10942000 +memory_reserve: 10000000 ~ 10942000 +mm finished +[INFO] Init thread +[INFO] Print Queue: +[00000000, ] +Welcome to OSC2024 shell! +# +# chunk_alloc: 00000001 (size: 00000000) + +no available chunk +Requesting 00001000 bytes, order: 00000000 +Checking free_list[00000000] = 00082C14 +truncate (addr: 0x000878A0, idx: 00080168, val: 10297480, order: 00000000) to order 00000000 +Allocated (addr: 0x000878A0, idx: 00080168, val: FFFFFFFE, order: 00000000) +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 +new chunk: 0x000978A0 +000878B0 \ No newline at end of file diff --git a/lab8/bcm2710-rpi-3-b-plus.dtb b/lab8/bcm2710-rpi-3-b-plus.dtb new file mode 100644 index 0000000000000000000000000000000000000000..c83b0817e2eb5295a3dfe6aafe3aa55d93e9785d GIT binary patch literal 34228 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 3; +__bootloader_size = (__bootloader_end - __bootloader_start) >> 3; \ No newline at end of file diff --git a/lab8/bootloader/src/mini_uart.c b/lab8/bootloader/src/mini_uart.c new file mode 100644 index 000000000..55f6b7e4c --- /dev/null +++ b/lab8/bootloader/src/mini_uart.c @@ -0,0 +1,79 @@ +#include "utils.h" +#include "peripherals/mini_uart.h" +#include "peripherals/gpio.h" + +void uart_send ( char c ) +{ + while(1) { + if(get32(AUX_MU_LSR_REG)&0x20) + break; + } + put32(AUX_MU_IO_REG,c); +} + +char uart_recv ( void ) +{ + while(1) { + if(get32(AUX_MU_LSR_REG)&0x01) + break; + } + char c = get32(AUX_MU_IO_REG)&0xFF; + return c == '\r' ? '\n' : c; +} + +char uart_raw_recv (void) { + while(1) { + if(get32(AUX_MU_LSR_REG)&0x01) + break; + } + return get32(AUX_MU_IO_REG)&0xFF; +} + +void uart_send_string(char* str) +{ + for (int i = 0; str[i] != '\0'; i ++) { + if(str[i] == '\n'){ + uart_send('\r'); + } + uart_send((char)str[i]); + } +} + +void uart_hex(unsigned int d) { + unsigned int n; + int c; + for (c = 28; c >= 0; c -= 4) { + // get highest tetrad + n = (d >> c) & 0xF; + // 0-9 => '0'-'9', 10-15 => 'A'-'F' + n += n > 9 ? 0x37 : 0x30; + uart_send(n); + } +} + +void uart_init ( void ) +{ + unsigned int selector; + + selector = get32(GPFSEL1); + selector &= ~(7<<12); // clean gpio14 + selector |= 2<<12; // set alt5 for gpio14 + selector &= ~(7<<15); // clean gpio15 + selector |= 2<<15; // set alt5 for gpio15 + put32(GPFSEL1,selector); + + put32(GPPUD,0); + delay(150); + put32(GPPUDCLK0,(1<<14)|(1<<15)); + delay(150); + put32(GPPUDCLK0,0); + + put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) + put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) + put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts + put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode + put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high + put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 + + put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver +} diff --git a/lab8/bootloader/src/mm.S b/lab8/bootloader/src/mm.S new file mode 100644 index 000000000..1bd32ff37 --- /dev/null +++ b/lab8/bootloader/src/mm.S @@ -0,0 +1,6 @@ +.globl memzero +memzero: + str xzr, [x0], #8 + subs x1, x1, #8 + b.gt memzero + ret diff --git a/lab8/bootloader/src/shell.c b/lab8/bootloader/src/shell.c new file mode 100644 index 000000000..aabbc592f --- /dev/null +++ b/lab8/bootloader/src/shell.c @@ -0,0 +1,125 @@ +#include "mini_uart.h" + +unsigned int is_visible(unsigned int c){ + if(c >= 32 && c <= 126){ + return 1; + } + return 0; +} + +unsigned int strcmp(char* str1, char* str2){ + int i = 0; + while(str1[i] != '\0' && str2[i] != '\0'){ + if(str1[i] != str2[i]){ + return 0; + } + i++; + } + if(str1[i] != '\0' || str2[i] != '\0'){ + return 0; + } + return 1; +} + + +void uart_recv_command(char *str){ + char c; + int i = 0; + while(1){ + c = uart_recv(); + if(c == '\n'){ + str[i] = '\0'; + break; + } else if(c == 127 || c == 8){ + if(i > 0){ + i--; + uart_send('\b'); + uart_send(' '); + uart_send('\b'); + } + continue; + } + if(is_visible(c)){ + str[i] = c; + i++; + uart_send(c); + } + } + +} + +int atoi(const char *s){ + int sign = 1; + int i = 0; + int result = 0; + + while(s[i] == ' ') + i ++; + + if(s[i] == '-') { + sign = -1; + i++; + } + + while(s[i] >= '0' && s[i] <= '9') { + result = result * 10 + (s[i] - '0'); + i ++; + } + + return sign * result; +} + + +void load_img(){ + char buf[16] = {0}; + for(int i = 0; i < 16; i++) { + buf[i] = uart_recv(); + if (buf[i] == '\n') { + buf[i] = '\0'; + break; + } + } + + uart_send_string("Kernel size: "); + uart_send_string(buf); + uart_send_string(" bytes\n"); + + uart_send_string("Loading kernel...\n"); + unsigned int size = atoi(buf); + char *kernel_addr = (char *)0x80000; + while (size --) { + *kernel_addr++ = uart_raw_recv(); + } + + asm volatile( + "mov x0, x10;" + "mov x1, x11;" + "mov x2, x12;" + "mov x3, x13;" + "mov x30, 0x80000;" + "ret;" + ); + +} + + +void shell(){ + uart_send_string("Welcome to Jayinnn's OSC bootloader!\n"); + while(1){ + uart_send_string("# "); + char str[100]; + uart_recv_command(str); + // uart_send_string(str); + uart_send_string("\n"); + if(strcmp(str, "hello")){ + uart_send_string("Hello World!\n"); + } else if(strcmp(str, "load")){ + load_img(); + return; + } else if(strcmp(str, "help")){ + uart_send_string("help\t: print this help menu\n"); + uart_send_string("hello\t: print Hello World!\n"); + uart_send_string("load\t: load kernel8.img\n"); + } + } +} \ No newline at end of file diff --git a/lab8/bootloader/src/utils.S b/lab8/bootloader/src/utils.S new file mode 100644 index 000000000..c35527a42 --- /dev/null +++ b/lab8/bootloader/src/utils.S @@ -0,0 +1,15 @@ +.globl put32 +put32: + str w1,[x0] + ret + +.globl get32 +get32: + ldr w0,[x0] + ret + +.globl delay +delay: + subs x0, x0, #1 + bne delay + ret diff --git a/lab8/build.sh b/lab8/build.sh new file mode 100755 index 000000000..56cbff299 --- /dev/null +++ b/lab8/build.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 diff --git a/lab8/config.txt b/lab8/config.txt new file mode 100644 index 000000000..49fc25695 --- /dev/null +++ b/lab8/config.txt @@ -0,0 +1,3 @@ +kernel=bootloader.img +arm_64bit=1 +initramfs initramfs.cpio 0x20000000 diff --git a/lab8/include/alloc.h b/lab8/include/alloc.h new file mode 100644 index 000000000..0b182a695 --- /dev/null +++ b/lab8/include/alloc.h @@ -0,0 +1,94 @@ +#ifndef _ALLOC_H_ +#define _ALLOC_H_ + +#define PAGE_BASE (void*)0x0 +#define PAGE_END (void*)0x3b400000 +#define PAGE_SIZE 0x1000 // 4KB + +#define MAX_ORDER 11 +#define MAX_CHUNK 5 + +#define BUDDY -1 // buddy memory pages +#define ALLOCATED -2 // allocated memory pages +#define RESERVED -3 // reserved memory pages + +extern int debug; + +typedef struct page { + unsigned char* addr; + unsigned long long idx; + int val; // if val > 0, it is free + int order; // 2^order = size + struct page* next; + struct page* prev; +} page; + +typedef struct free_list_t { + page* head; + int cnt; +} free_list_t; + +typedef struct page_info { + int idx; + struct page_info* next; +} page_info_t; + +typedef struct chunk { + unsigned char* addr; + struct chunk* next; +} chunk; + +typedef struct chunk_info { + int idx; // chunk index + int size; // chunk size + int cnt; // free chunk count + page_info_t* page_head; // page list + chunk* chunk_head; // free chunk list +} chunk_info; + +int buddy(int idx); + +// print information +void page_info(page* p); +void page_info_addr(void* addr); +void print_chunk_info(); +void free_list_info(); +void check_free_list(); + +// helper functions +unsigned long long align_page(unsigned long long size); +int log2(unsigned long long size); +int is_page(void* addr); +int size2chunkidx(unsigned long long size); + +void init_page_arr(); +void init_chunk_info(); +void init_page_allocator(); + +// related to page +void release(page* r_page); +void merge(page* m_page); +page* truncate(page* t_page, int order); + + +// related to free_list +void insert_page(page* new_page, int order); +page* pop_page(int order); +void erase_page(page* e_page, int order); + +// related to chunk +void* chunk_alloc(int idx); +void* chunk_free(void* addr); + +void alloc_init(); // main function + +// APIs +void memory_reserve(void* start, void* end); +void* page_alloc(unsigned long long size); +void page_free(void* addr); +void* kmalloc(unsigned long long size); +void kfree(void* addr); + +void *simple_malloc(unsigned long long size); + +#endif \ No newline at end of file diff --git a/lab8/include/c_utils.h b/lab8/include/c_utils.h new file mode 100644 index 000000000..87dcdbabd --- /dev/null +++ b/lab8/include/c_utils.h @@ -0,0 +1,12 @@ +#ifndef _C_UTILS_H +#define _C_UTILS_H + +#define ALIGN(num, base) ((num + base - 1) & ~(base - 1)) + +void uart_recv_command(char *str); +int align4(int n); +int atoi(const char *s); +unsigned int endian_big2little(unsigned int x); +void delay (unsigned int loop); + +#endif // _C_UTILS_H \ No newline at end of file diff --git a/lab8/include/exception.h b/lab8/include/exception.h new file mode 100644 index 000000000..13ace7719 --- /dev/null +++ b/lab8/include/exception.h @@ -0,0 +1,35 @@ +#ifndef _EXECPTION_H_ +#define _EXECPTION_H_ + +#define CORE0_INTERRUPT_SOURCE ((volatile unsigned int *)0x40000060) + +#define INTERRUPT_SOURCE_CNTPNSIRQ (1<<1) +#define INTERRUPT_SOURCE_GPU (1<<8) + +#define IRQ_PENDING_1_AUX_INT (1<<29) + +typedef struct trapframe_t { + unsigned long x[31]; // general purpose register + // three reg that will be used to kernel mode -> user mode + unsigned long spsr_el1; + unsigned long elr_el1; + unsigned long sp_el0; +} trapframe_t; + +void el1_interrupt_enable(); +void el1_interrupt_disable(); + +void core_timer_init(); +void core_timer_enable(); +void core_timer_disable(); + +void exception_handler_c(); +void irq_exception_handler_c(); +void user_exception_handler_c(trapframe_t* tf); + +void irq_timer_exception(); +void user_irq_timer_exception(); +void irq_uart_rx_exception(); +void irq_uart_tx_exception(); + +#endif \ No newline at end of file diff --git a/lab8/include/fdt.h b/lab8/include/fdt.h new file mode 100644 index 000000000..5f70b2a6b --- /dev/null +++ b/lab8/include/fdt.h @@ -0,0 +1,34 @@ +#ifndef _FDT_H +#define _FDT_H + +#include "mini_uart.h" +#include "string.h" +#include "c_utils.h" + +struct fdt_header { + unsigned int magic; + unsigned int totalsize; + unsigned int off_dt_struct; + unsigned int off_dt_strings; + unsigned int off_mem_rsvmap; + unsigned int version; + unsigned int last_comp_version; + unsigned int boot_cpuid_phys; + unsigned int size_dt_strings; + unsigned int size_dt_struct; +}; + +#define FDT_BEGIN_NODE 0x1 /* Start node: full name */ +#define FDT_END_NODE 0x2 /* End node */ +#define FDT_PROP 0x3 /* Property: name off, size, content */ +#define FDT_NOP 0x4 /* nop */ +#define FDT_END 0x9 + +typedef void (*dtb_callback)(unsigned int node_type, char *name, void *value, unsigned int name_size); + +extern char* dtb_base; +extern char* dtb_end; + +void fdt_traverse(dtb_callback callback); + +#endif diff --git a/lab8/include/fs_cpio.h b/lab8/include/fs_cpio.h new file mode 100644 index 000000000..b0e360a10 --- /dev/null +++ b/lab8/include/fs_cpio.h @@ -0,0 +1,76 @@ +#ifndef _FS_CPIO_H +#define _FS_CPIO_H + +#include "fs_vfs.h" +#include "initrd.h" +#include "list.h" +#include "string.h" + +#define TMPFS_FILE_MAXSIZE PAGE_SIZE // define in the spec +#define TMPFS_DIR_MAXSIZE 16 +#define TMPFS_NAME_MAXLEN 16 + +#define CPIOFS_TYPE_UNDEFINE 0x0 +#define CPIOFS_TYPE_FILE 0x1 +#define CPIOFS_TYPE_DIR 0x2 + +#define CPIO_TYPE_MASK 0060000 +#define CPIO_TYPE_DIR 0040000 +#define CPIO_TYPE_FILE 0000000 + +// init fs +filesystem *cpiofs_init(void); + +// static methods +int cpiofs_mount(filesystem *fs, mount *mnt); + +extern filesystem static_cpiofs; + +int cpiofs_lookup(vnode *dir_node, vnode **target, const char *component_name); +int cpiofs_create(vnode *dir_node, vnode **target, const char *component_name); +int cpiofs_mkdir(vnode *dir_node, vnode **target, const char *component_name); +int cpiofs_isdir(vnode *dir_node); +int cpiofs_getname(vnode *dir_node, const char **name); +int cpiofs_getsize(vnode *dir_node); + +extern struct vnode_operations cpiofs_v_ops; + +int cpiofs_open(vnode *file_node, file *target); +int cpiofs_close(file *target); +int cpiofs_write(file *target, const void *buf, size_t len); +int cpiofs_read(file *target, void *buf, size_t len); +long cpiofs_lseek64(file *target, long offset, int whence); +int cpiofs_ioctl(struct file *file, uint64_t request, va_list args); + + +uint32_t cpio_read_8hex(const char *s); +vnode *get_vnode_from_path(vnode *dir_node, const char **name); +void cpio_init_mkdir(const char *pathname); + +extern struct file_operations cpiofs_f_ops; + +typedef struct cpiofs_file { + const char *data; + int size; +} cpiofs_file; + +typedef struct cpiofs_dir { + struct list_head list; +} cpiofs_dir; + +typedef struct cpiofs_internal { + const char* name; + int type; + union { + cpiofs_file file; + cpiofs_dir dir; + }; + vnode* node; + struct list_head list; +} cpiofs_internal; + +extern vnode cpio_root_node; +extern vnode mount_old_node; +extern int cpio_mounted; + +#endif // _FS_CPIO_H \ No newline at end of file diff --git a/lab8/include/fs_framebufferfs.h b/lab8/include/fs_framebufferfs.h new file mode 100644 index 000000000..343a2372b --- /dev/null +++ b/lab8/include/fs_framebufferfs.h @@ -0,0 +1,57 @@ +#ifndef _FS_FRAMEBUFFERFS_H +#define _FS_FRAMEBUFFERFS_H + +#include "fs_vfs.h" +#include "mbox.h" +#include "string.h" + +#define MBOX_REQUEST 0 +#define MBOX_CH_PROP 8 +#define MBOX_TAG_LAST 0 + + +typedef struct fb_info { + uint32_t width; + uint32_t height; + uint32_t pitch; + uint32_t isrgb; +} fb_info; + +filesystem *framebufferfs_init(void); + +int framebufferfs_mount(filesystem *fs, mount *mnt); + +extern filesystem static_framebufferfs; + + +int framebufferfs_lookup(vnode *dir_node, vnode **target, + const char *component_name); +int framebufferfs_create(vnode *dir_node, vnode **target, + const char *component_name); +int framebufferfs_mkdir(vnode *dir_node, vnode **target, + const char *component_name); +int framebufferfs_isdir(vnode *dir_node); +int framebufferfs_getname(vnode *dir_node, const char **name); +int framebufferfs_getsize(vnode *dir_node); + +extern vnode_operations framebufferfs_v_ops; + +int framebufferfs_open(vnode *file_node, file *target); +int framebufferfs_close(file *target); +int framebufferfs_write(file *target, const void *buf, size_t len); +int framebufferfs_read(file *target, void *buf, size_t len); +long framebufferfs_lseek64(file *target, long offset, int whence); +int framebufferfs_ioctl(struct file *file, uint64_t request, va_list args); + +extern file_operations framebufferfs_f_ops; + +typedef struct framebufferfs_internal { + const char *name; + struct vnode oldnode; + /* raw frame buffer address */ + uint8_t *lfb; + uint32_t lfbsize; + int isopened; + int isinit; +} framebufferfs_internal; +#endif // _FS_FRAMEBUFFERFS_H \ No newline at end of file diff --git a/lab8/include/fs_fsinit.h b/lab8/include/fs_fsinit.h new file mode 100644 index 000000000..1ee7ae47b --- /dev/null +++ b/lab8/include/fs_fsinit.h @@ -0,0 +1,13 @@ +#ifndef _FSINIT_H +#define _FSINIT_H + +#include "fs_vfs.h" +#include "fs_tmpfs.h" +#include "fs_cpio.h" +#include "fs_uartfs.h" +#include "fs_framebufferfs.h" + +void fs_early_init(void); +void fs_init(void); + +#endif // _FSINIT_H \ No newline at end of file diff --git a/lab8/include/fs_tmpfs.h b/lab8/include/fs_tmpfs.h new file mode 100644 index 000000000..873352f50 --- /dev/null +++ b/lab8/include/fs_tmpfs.h @@ -0,0 +1,64 @@ +#ifndef _TMPFS_H +#define _TMPFS_H + +#include "fs_vfs.h" + +#define TMPFS_FILE_MAXSIZE PAGE_SIZE // define in the spec +#define TMPFS_DIR_MAXSIZE 16 +#define TMPFS_NAME_MAXLEN 16 + +#define TMPFS_TYPE_UNDEFINE 0x0 +#define TMPFS_TYPE_FILE 0x1 +#define TMPFS_TYPE_DIR 0x2 + +// init fs +filesystem *tmpfs_init(void); + +// static methods +int tmpfs_mount(filesystem *fs, mount *mnt); +int tmpfs_alloc_vnode(filesystem *fs, vnode **target); + +extern filesystem static_tmpfs; + +int tmpfs_lookup(vnode *dir_node, vnode **target, const char *component_name); +int tmpfs_create(vnode *dir_node, vnode **target, const char *component_name); +int tmpfs_mkdir(vnode *dir_node, vnode **target, const char *component_name); +int tmpfs_isdir(vnode *dir_node); +int tmpfs_getname(vnode *dir_node, const char **name); +int tmpfs_getsize(vnode *dir_node); + +extern struct vnode_operations tmpfs_v_ops; + +int tmpfs_open(vnode *file_node, file *target); +int tmpfs_close(file *target); +int tmpfs_write(file *target, const void *buf, size_t len); +int tmpfs_read(file *target, void *buf, size_t len); +long tmpfs_lseek64(file *target, long offset, int whence); +int tmpfs_ioctl(struct file *file, uint64_t request, va_list args); + + +extern struct file_operations tmpfs_f_ops; + +typedef struct tmpfs_file { + char *data; + int size; + int capacity; +} tmpfs_file; + +typedef struct tmpfs_dir { + int size; + vnode *files[TMPFS_DIR_MAXSIZE]; +} tmpfs_dir; + +typedef struct tmpfs_internal { + char name[TMPFS_NAME_MAXLEN]; + int type; + union { + tmpfs_file *file; + tmpfs_dir *dir; + }; + vnode* old_node; +} tmpfs_internal; + + +#endif // _TMPFS_H \ No newline at end of file diff --git a/lab8/include/fs_uartfs.h b/lab8/include/fs_uartfs.h new file mode 100644 index 000000000..5a1fa9bfa --- /dev/null +++ b/lab8/include/fs_uartfs.h @@ -0,0 +1,41 @@ +#ifndef _FS_UARTFS_H +#define _FS_UARTFS_H + +#include "fs_vfs.h" +#include "mini_uart.h" + +filesystem *uartfs_init(void); + +int uartfs_mount(filesystem *fs, mount *mnt); + +extern filesystem static_uartfs; + + +int uartfs_lookup(vnode *dir_node, vnode **target, + const char *component_name); +int uartfs_create(vnode *dir_node, vnode **target, + const char *component_name); +int uartfs_mkdir(vnode *dir_node, vnode **target, + const char *component_name); +int uartfs_isdir(vnode *dir_node); +int uartfs_getname(vnode *dir_node, const char **name); +int uartfs_getsize(vnode *dir_node); + +extern vnode_operations uartfs_v_ops; + +int uartfs_open(vnode *file_node, file *target); +int uartfs_close(file *target); +int uartfs_write(file *target, const void *buf, size_t len); +int uartfs_read(file *target, void *buf, size_t len); +long uartfs_lseek64(file *target, long offset, int whence); +int uartfs_ioctl(struct file *file, uint64_t request, va_list args); + +extern file_operations uartfs_f_ops; + +typedef struct uartfs_internal { + const char *name; + vnode oldnode; +} uartfs_internal; + + +#endif // _FS_UARTFS_H \ No newline at end of file diff --git a/lab8/include/fs_vfs.h b/lab8/include/fs_vfs.h new file mode 100644 index 000000000..e70d85934 --- /dev/null +++ b/lab8/include/fs_vfs.h @@ -0,0 +1,110 @@ +#ifndef _FS_VFS_H +#define _FS_VFS_H + +#include +#include +#include +#include "list.h" +#include "exception.h" + +#define O_CREAT 00000100 + +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 + +typedef struct vnode { + struct mount *mount; + struct vnode_operations *v_ops; + struct file_operations *f_ops; + struct vnode *parent; + void *internal; +} vnode; + +typedef struct file { + vnode *vnode; + unsigned f_pos; + struct file_operations *f_ops; + int flags; +} file; + +typedef struct mount { + struct vnode *root; + struct filesystem *fs; +} mount; + +typedef struct filesystem { + const char *name; + struct list_head fs_list; + int (*mount)(struct filesystem *fs, struct mount *mount); + int (*alloc_vnode)(struct filesystem *fs, struct vnode **target); +} filesystem; + +typedef struct file_operations { + int (*write)(file *f, const void *buf, size_t len); + int (*read)(file *f, void *buf, size_t len); + int (*open)(vnode *file_node, file *target); + int (*close)(file *f); + long (*lseek64)(file *f, long offset, int whence); + int (*ioctl)(file *file, uint64_t request, va_list args); +} file_operations; + +typedef struct vnode_operations { + int (*lookup)(vnode *dir_node, vnode **target, const char *component_name); + int (*create)(vnode *dir_node, vnode **target, const char *component_name); + int (*mkdir)(vnode *dir_node, vnode **target, const char *component_name); + int (*isdir)(vnode *dir_node); + int (*getname)(vnode *dir_node, const char **name); + int (*getsize)(vnode *dir_node); + +} vnode_operations; + + +// root mount point +extern mount *rootfs; + +void vfs_init(); +void vfs_init_rootfs(filesystem *fs); + +int register_filesystem(filesystem *fs); +int vfs_open(const char* pathname, int flags, file* target); +int vfs_close(file *f); +int vfs_write(file *f, const void *buf, size_t len); +int vfs_read(file *f, void *buf, size_t len); +int vfs_mkdir(const char *pathname); +int vfs_mount(const char* target, const char* filesystem); +int vfs_lookup(const char *pathname, vnode **target); +long vfs_lseek64(file *f, long offset, int whence); +int vfs_ioctl(struct file *file, uint64_t request, va_list args); + +void syscall_open(trapframe_t *tf, const char *pathname, int flags); +void syscall_close(trapframe_t *tf, int fd); +void syscall_write(trapframe_t *tf, int fd, const void *buf, size_t len); +void syscall_read(trapframe_t *tf, int fd, void *buf, size_t len); +void syscall_mkdir(trapframe_t *tf, const char *pathname, uint32_t mode); +void syscall_mount( + trapframe_t *tf, + const char *src, + const char *target, + const char *fs_name, + int flags, + const void *data +); +void syscall_chdir(trapframe_t *tf, const char *path); +void syscall_lseek64(trapframe_t *tf, int fd, long offset, int whence); +void syscall_ioctl(trapframe_t *tf, int fd, uint64_t requests, ...); + +// static methods + +int open_wrapper(const char* pathname, int flags); +int close_wrapper(int fd); +int write_wrapper(int fd, const void *buf, size_t len); +int read_wrapper(int fd, void *buf, size_t len); +int mkdir_wrapper(const char* pathname); +int mount_wrapper(const char* target, const char* fs_name); +int chdir_wrapper(const char* path); +long lseek64_wrapper(int fd, long offset, int whence); +int ioctl_wrapper(int fd, uint64_t cmd, va_list args); + + +#endif // _FS_VFS_H \ No newline at end of file diff --git a/lab8/include/initrd.h b/lab8/include/initrd.h new file mode 100644 index 000000000..60af6f43c --- /dev/null +++ b/lab8/include/initrd.h @@ -0,0 +1,40 @@ +#ifndef INITRD_H +#define INITRD_H + +#define CPIO_BASE_QEMU (0x8000000) +#define CPIO_BASE_RPI (0x20000000) + +#define CPIO_NEWC_HEADER_MAGIC "070701" // big endian + + +extern char *ramfs_base; +extern char *ramfs_end; + +// Cpio Archive File Header (New ASCII Format) +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_t; + +int cpio_newc_parse_header(cpio_t *this_header_pointer, char **pathname, unsigned int *filesize, char **data, cpio_t **next_header_pointer); +void initrd_list(); +void initrd_cat(const char *target); +void initrd_callback(unsigned int node_type, char *name, void *value, unsigned int name_size); +void initrd_exec_prog(); +void initrd_run_prog(const char* path_name); +// void initrd_exec_syscall(); +// void initrd_run_syscall(); +// void exec_user_prog (); +#endif // INITRD_H \ No newline at end of file diff --git a/lab8/include/list.h b/lab8/include/list.h new file mode 100644 index 000000000..67d77d14a --- /dev/null +++ b/lab8/include/list.h @@ -0,0 +1,459 @@ +/* Linux-like doubly-linked list implementation */ +#ifndef _LIST_H +#define _LIST_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include + +/* "typeof" is a GNU extension. + * Reference: https://gcc.gnu.org/onlinedocs/gcc/Typeof.html + */ +#if defined(__GNUC__) +#define __LIST_HAVE_TYPEOF 1 +#endif /* defined(__GNUC__) */ + +/** + * struct list_head - Head and node of a doubly-linked list + * @prev: pointer to the previous node in the list + * @next: pointer to the next node in the list + * + * The simple doubly-linked list consists of a head and nodes attached to + * this head. Both node and head share the same struct type. The list_* + * functions and macros can be used to access and modify this data structure. + * + * The @prev pointer of the list head points to the last list node of the + * list and @next points to the first list node of the list. For an empty list, + * both member variables point to the head. + * + * The list nodes are usually embedded in a container structure which holds the + * actual data. Such an container object is called entry. The helper list_entry + * can be used to calculate the object address from the address of the node. + */ +struct list_head { + struct list_head *prev; + struct list_head *next; +}; + +/** + * container_of() - Calculate address of object that contains address ptr + * @ptr: pointer to member variable + * @type: type of the structure containing ptr + * @member: name of the member variable in struct @type + * + * Return: @type pointer of object containing ptr + */ +#ifndef container_of +#ifdef __LIST_HAVE_TYPEOF +#define container_of(ptr, type, member) \ + __extension__({ \ + const __typeof__(((type *) 0)->member) *__pmember = (ptr); \ + (type *) ((char *) __pmember - offsetof(type, member)); \ + }) +#else /* __LIST_HAVE_TYPEOF */ +#define container_of(ptr, type, member) \ + ((type *) ((char *) (ptr) -offsetof(type, member))) +#endif /* __LIST_HAVE_TYPEOF */ +#endif /* container_of */ + +/** + * LIST_HEAD - Declare list head and initialize it + * @head: name of the new object + */ +#define LIST_HEAD(head) struct list_head head = {&(head), &(head)} + +/** + * INIT_LIST_HEAD() - Initialize empty list head + * @head: pointer to list head + * + * This can also be used to initialize a unlinked list node. + * + * A node is usually linked inside a list, will be added to a list in + * the near future or the entry containing the node will be free'd soon. + * + * But an unlinked node may be given to a function which uses list_del(_init) + * before it ends up in a previously mentioned state. The list_del(_init) on an + * initialized node is well defined and safe. But the result of a + * list_del(_init) on an uninitialized node is undefined (unrelated memory is + * modified, crashes, ...). + */ +static inline void INIT_LIST_HEAD(struct list_head *head) +{ + head->next = head; + head->prev = head; +} + +/** + * list_add() - Add a list node to the beginning of the list + * @node: pointer to the new node + * @head: pointer to the head of the list + */ +static inline void list_add(struct list_head *node, struct list_head *head) +{ + struct list_head *next = head->next; + + next->prev = node; + node->next = next; + node->prev = head; + head->next = node; +} + +/** + * list_add_tail() - Add a list node to the end of the list + * @node: pointer to the new node + * @head: pointer to the head of the list + */ +static inline void list_add_tail(struct list_head *node, struct list_head *head) +{ + struct list_head *prev = head->prev; + + prev->next = node; + node->next = head; + node->prev = prev; + head->prev = node; +} + +/** + * list_del() - Remove a list node from the list + * @node: pointer to the node + * + * The node is only removed from the list. Neither the memory of the removed + * node nor the memory of the entry containing the node is free'd. The node + * has to be handled like an uninitialized node. Accessing the next or prev + * pointer of the node is not safe. + * + * Unlinked, initialized nodes are also uninitialized after list_del. + * + * LIST_POISONING can be enabled during build-time to provoke an invalid memory + * access when the memory behind the next/prev pointer is used after a list_del. + * This only works on systems which prohibit access to the predefined memory + * addresses. + */ +static inline void list_del(struct list_head *node) +{ + struct list_head *next = node->next; + struct list_head *prev = node->prev; + + next->prev = prev; + prev->next = next; + +#ifdef LIST_POISONING + node->prev = (struct list_head *) (0x00100100); + node->next = (struct list_head *) (0x00200200); +#endif /* LIST_POISONING */ +} + +/** + * list_del_init() - Remove a list node from the list and reinitialize it + * @node: pointer to the node + * + * The removed node will not end up in an uninitialized state like when using + * list_del. Instead the node is initialized again to the unlinked state. + */ +static inline void list_del_init(struct list_head *node) +{ + list_del(node); + INIT_LIST_HEAD(node); +} + +/** + * list_empty() - Check if list head has no nodes attached + * @head: pointer to the head of the list + * + * Return: 0 - list is not empty !0 - list is empty + */ +static inline int list_empty(const struct list_head *head) +{ + return (head->next == head); +} + +/** + * list_is_singular() - Check if list head has exactly one node attached + * @head: pointer to the head of the list + * + * Return: 0 - list is not singular !0 -list has exactly one entry + */ +static inline int list_is_singular(const struct list_head *head) +{ + return (!list_empty(head) && head->prev == head->next); +} + +/** + * list_splice() - Add list nodes from a list to beginning of another list + * @list: pointer to the head of the list with the node entries + * @head: pointer to the head of the list + * + * All nodes from @list are added to the beginning of the list of @head. + * It is similar to list_add but for multiple nodes. The @list head is not + * modified and has to be initialized to be used as a valid list head/node + * again. + */ +static inline void list_splice(struct list_head *list, struct list_head *head) +{ + struct list_head *head_first = head->next; + struct list_head *list_first = list->next; + struct list_head *list_last = list->prev; + + if (list_empty(list)) + return; + + head->next = list_first; + list_first->prev = head; + + list_last->next = head_first; + head_first->prev = list_last; +} + +/** + * list_splice_tail() - Add list nodes from a list to end of another list + * @list: pointer to the head of the list with the node entries + * @head: pointer to the head of the list + * + * All nodes from @list are added to to the end of the list of @head. + * It is similar to list_add_tail but for multiple nodes. The @list head is not + * modified and has to be initialized to be used as a valid list head/node + * again. + */ +static inline void list_splice_tail(struct list_head *list, + struct list_head *head) +{ + struct list_head *head_last = head->prev; + struct list_head *list_first = list->next; + struct list_head *list_last = list->prev; + + if (list_empty(list)) + return; + + head->prev = list_last; + list_last->next = head; + + list_first->prev = head_last; + head_last->next = list_first; +} + +/** + * list_splice_init() - Move list nodes from a list to beginning of another list + * @list: pointer to the head of the list with the node entries + * @head: pointer to the head of the list + * + * All nodes from @list are added to to the beginning of the list of @head. + * It is similar to list_add but for multiple nodes. + * + * The @list head will not end up in an uninitialized state like when using + * list_splice. Instead the @list is initialized again to the an empty + * list/unlinked state. + */ +static inline void list_splice_init(struct list_head *list, + struct list_head *head) +{ + list_splice(list, head); + INIT_LIST_HEAD(list); +} + +/** + * list_splice_tail_init() - Move list nodes from a list to end of another list + * @list: pointer to the head of the list with the node entries + * @head: pointer to the head of the list + * + * All nodes from @list are added to to the end of the list of @head. + * It is similar to list_add_tail but for multiple nodes. + * + * The @list head will not end up in an uninitialized state like when using + * list_splice. Instead the @list is initialized again to the an empty + * list/unlinked state. + */ +static inline void list_splice_tail_init(struct list_head *list, + struct list_head *head) +{ + list_splice_tail(list, head); + INIT_LIST_HEAD(list); +} + +/** + * list_cut_position() - Move beginning of a list to another list + * @head_to: pointer to the head of the list which receives nodes + * @head_from: pointer to the head of the list + * @node: pointer to the node in which defines the cutting point + * + * All entries from the beginning of the list @head_from to (including) the + * @node is moved to @head_to. + * + * @head_to is replaced when @head_from is not empty. @node must be a real + * list node from @head_from or the behavior is undefined. + */ +static inline void list_cut_position(struct list_head *head_to, + struct list_head *head_from, + struct list_head *node) +{ + struct list_head *head_from_first = head_from->next; + + if (list_empty(head_from)) + return; + + if (head_from == node) { + INIT_LIST_HEAD(head_to); + return; + } + + head_from->next = node->next; + head_from->next->prev = head_from; + + head_to->prev = node; + node->next = head_to; + head_to->next = head_from_first; + head_to->next->prev = head_to; +} + +/** + * list_move() - Move a list node to the beginning of the list + * @node: pointer to the node + * @head: pointer to the head of the list + * + * The @node is removed from its old position/node and add to the beginning of + * @head + */ +static inline void list_move(struct list_head *node, struct list_head *head) +{ + list_del(node); + list_add(node, head); +} + +/** + * list_move_tail() - Move a list node to the end of the list + * @node: pointer to the node + * @head: pointer to the head of the list + * + * The @node is removed from its old position/node and add to the end of @head + */ +static inline void list_move_tail(struct list_head *node, + struct list_head *head) +{ + list_del(node); + list_add_tail(node, head); +} + +/** + * list_entry() - Calculate address of entry that contains list node + * @node: pointer to list node + * @type: type of the entry containing the list node + * @member: name of the list_head member variable in struct @type + * + * Return: @type pointer of entry containing node + */ +#define list_entry(node, type, member) container_of(node, type, member) + +/** + * list_first_entry() - get first entry of the list + * @head: pointer to the head of the list + * @type: type of the entry containing the list node + * @member: name of the list_head member variable in struct @type + * + * Return: @type pointer of first entry in list + */ +#define list_first_entry(head, type, member) \ + list_entry((head)->next, type, member) + +/** + * list_last_entry() - get last entry of the list + * @head: pointer to the head of the list + * @type: type of the entry containing the list node + * @member: name of the list_head member variable in struct @type + * + * Return: @type pointer of last entry in list + */ +#define list_last_entry(head, type, member) \ + list_entry((head)->prev, type, member) + +/** + * list_for_each - iterate over list nodes + * @node: list_head pointer used as iterator + * @head: pointer to the head of the list + * + * The nodes and the head of the list must be kept unmodified while + * iterating through it. Any modifications to the the list will cause undefined + * behavior. + */ +#define list_for_each(node, head) \ + for (node = (head)->next; node != (head); node = node->next) + +/** + * list_for_each_entry - iterate over list entries + * @entry: pointer used as iterator + * @head: pointer to the head of the list + * @member: name of the list_head member variable in struct type of @entry + * + * The nodes and the head of the list must be kept unmodified while + * iterating through it. Any modifications to the the list will cause undefined + * behavior. + * + * FIXME: remove dependency of __typeof__ extension + */ +#ifdef __LIST_HAVE_TYPEOF +#define list_for_each_entry(entry, head, member) \ + for (entry = list_entry((head)->next, __typeof__(*entry), member); \ + &entry->member != (head); \ + entry = list_entry(entry->member.next, __typeof__(*entry), member)) +#endif /* __LIST_HAVE_TYPEOF */ + +/** + * iter_for_each_entry - iterate over list entries from @iter + * @entry: pointer used as iterator + * @iter: pointer to the start of this iteration + * @head: pointer to the head of the list + * @member: name of the list_head member variable in struct type of @entry + * + * FIXME: remove dependency of __typeof__ extension + */ +#ifdef __LIST_HAVE_TYPEOF +#define iter_for_each_entry(entry, iter, head, member) \ + for (entry = list_entry(iter, __typeof__(*entry), member); \ + &entry->member != (head); \ + entry = list_entry(entry->member.next, __typeof__(*entry), member)) +#endif /* __LIST_HAVE_TYPEOF */ + +/** + * list_for_each_safe - iterate over list nodes and allow deletes + * @node: list_head pointer used as iterator + * @safe: list_head pointer used to store info for next entry in list + * @head: pointer to the head of the list + * + * The current node (iterator) is allowed to be removed from the list. Any + * other modifications to the the list will cause undefined behavior. + */ +#define list_for_each_safe(node, safe, head) \ + for (node = (head)->next, safe = node->next; node != (head); \ + node = safe, safe = node->next) + +/** + * list_for_each_entry_safe - iterate over list entries and allow deletes + * @entry: pointer used as iterator + * @safe: @type pointer used to store info for next entry in list + * @head: pointer to the head of the list + * @member: name of the list_head member variable in struct type of @entry + * + * The current node (iterator) is allowed to be removed from the list. Any + * other modifications to the the list will cause undefined behavior. + * + * FIXME: remove dependency of __typeof__ extension + */ +#define list_for_each_entry_safe(entry, safe, head, member) \ + for (entry = list_entry((head)->next, __typeof__(*entry), member), \ + safe = list_entry(entry->member.next, __typeof__(*entry), member); \ + &entry->member != (head); entry = safe, \ + safe = list_entry(safe->member.next, __typeof__(*entry), member)) + +#define list_for_each_entry_safe_rev(entry, safe, head, member) \ + for (entry = list_entry((head)->prev, __typeof__(*entry), member), \ + safe = list_entry(entry->member.prev, __typeof__(*entry), member); \ + &entry->member != (head); entry = safe, \ + safe = list_entry(safe->member.prev, __typeof__(*entry), member)) + +#undef __LIST_HAVE_TYPEOF + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _LIST_H */ \ No newline at end of file diff --git a/lab8/include/mbox.h b/lab8/include/mbox.h new file mode 100644 index 000000000..6a05d1782 --- /dev/null +++ b/lab8/include/mbox.h @@ -0,0 +1,26 @@ +#ifndef _MBOX_H +#define _MBOX_H + +#define MMIO_BASE 0x3f000000 +#define MAILBOX_BASE MMIO_BASE + 0xb880 + +#define MAILBOX_READ (unsigned int*)(MAILBOX_BASE) +#define MAILBOX_STATUS (unsigned int*)(MAILBOX_BASE + 0x18) +#define MAILBOX_WRITE (unsigned int*)(MAILBOX_BASE + 0x20) + +#define MAILBOX_EMPTY 0x40000000 +#define MAILBOX_FULL 0x80000000 + +#define GET_BOARD_REVISION 0x00010002 +#define GET_ARM_MEMORY 0x00010005 +#define REQUEST_CODE 0x00000000 +#define REQUEST_SUCCEED 0x80000000 +#define REQUEST_FAILED 0x80000001 +#define TAG_REQUEST_CODE 0x00000000 +#define END_TAG 0x00000000 + +int mailbox_call(unsigned char ch, unsigned int* mailbox); +void get_board_revision(); +void get_memory_info(); + +#endif \ No newline at end of file diff --git a/lab8/include/mini_uart.h b/lab8/include/mini_uart.h new file mode 100644 index 000000000..2234f1c69 --- /dev/null +++ b/lab8/include/mini_uart.h @@ -0,0 +1,19 @@ +#ifndef _MINI_UART_H +#define _MINI_UART_H + +#define BUFFER_SIZE 1024 + +void uart_init ( void ); +void uart_enable_interrupt( void ); +void uart_disable_interrupt( void ); +char uart_recv ( void ); +void uart_recvn(char *buff, int n); +void uart_send ( char c ); +void uart_sendn (const char* buffer, int n); +void uart_send_string(const char* str); +void uart_hex(unsigned int d); +void uart_irq_handler(void); +char uart_async_recv( void ); +void uart_async_send_string(const char* buffer); + +#endif /*_MINI_UART_H */ diff --git a/lab8/include/mm.h b/lab8/include/mm.h new file mode 100644 index 000000000..9d05242d8 --- /dev/null +++ b/lab8/include/mm.h @@ -0,0 +1,19 @@ +#ifndef _MM_H +#define _MM_H + +#define PAGE_SHIFT 12 +#define TABLE_SHIFT 9 +#define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) + +#define PAGE_SIZE (1 << PAGE_SHIFT) +#define SECTION_SIZE (1 << SECTION_SHIFT) + +#define LOW_MEMORY (2 * SECTION_SIZE) + +#ifndef __ASSEMBLER__ + +void memzero(unsigned long src, unsigned long n); + +#endif + +#endif /*_MM_H */ diff --git a/lab8/include/peripherals/base.h b/lab8/include/peripherals/base.h new file mode 100644 index 000000000..63f9c038f --- /dev/null +++ b/lab8/include/peripherals/base.h @@ -0,0 +1,6 @@ +#ifndef _P_BASE_H +#define _P_BASE_H + +#define PBASE 0x3F000000 + +#endif /*_P_BASE_H */ diff --git a/lab8/include/peripherals/gpio.h b/lab8/include/peripherals/gpio.h new file mode 100644 index 000000000..4a1f0f18f --- /dev/null +++ b/lab8/include/peripherals/gpio.h @@ -0,0 +1,12 @@ +#ifndef _P_GPIO_H +#define _P_GPIO_H + +#include "peripherals/base.h" + +#define GPFSEL1 ((volatile unsigned int *)(PBASE+0x00200004)) +#define GPSET0 ((volatile unsigned int *)(PBASE+0x0020001C)) +#define GPCLR0 ((volatile unsigned int *)(PBASE+0x00200028)) +#define GPPUD ((volatile unsigned int *)(PBASE+0x00200094)) +#define GPPUDCLK0 ((volatile unsigned int *)(PBASE+0x00200098)) + +#endif /*_P_GPIO_H */ diff --git a/lab8/include/peripherals/irq.h b/lab8/include/peripherals/irq.h new file mode 100644 index 000000000..885b0b68f --- /dev/null +++ b/lab8/include/peripherals/irq.h @@ -0,0 +1,17 @@ +#ifndef _IRQ_H +#define _IRQ_H +#include "peripherals/base.h" + +#define IRQ_BASIC_PENDING ((volatile unsigned int *)(PBASE+0x0000B200)) +#define IRQ_PENDING_1 ((volatile unsigned int *)(PBASE+0x0000B204)) +#define IRQ_PENDING_2 ((volatile unsigned int *)(PBASE+0x0000B208)) +#define FIQ_CONTROL ((volatile unsigned int *)(PBASE+0x0000B20C)) +#define ENABLE_IRQS_1 ((volatile unsigned int *)(PBASE+0x0000B210)) +#define ENABLE_IRQS_2 ((volatile unsigned int *)(PBASE+0x0000B214)) +#define ENABLE_BASIC_IRQS ((volatile unsigned int *)(PBASE+0x0000B218)) +#define DISABLE_IRQS_1 ((volatile unsigned int *)(PBASE+0x0000B21C)) +#define DISABLE_IRQS_2 ((volatile unsigned int *)(PBASE+0x0000B220)) +#define DISABLE_BASIC_IRQS ((volatile unsigned int *)(PBASE+0x0000B224)) + + +#endif // _IRQ_H \ No newline at end of file diff --git a/lab8/include/peripherals/p_mini_uart.h b/lab8/include/peripherals/p_mini_uart.h new file mode 100644 index 000000000..5e78297d8 --- /dev/null +++ b/lab8/include/peripherals/p_mini_uart.h @@ -0,0 +1,20 @@ +#ifndef _P_MINI_UART_H +#define _P_MINI_UART_H + +#include "peripherals/base.h" + +#define AUX_ENABLES ((volatile unsigned int *)(PBASE+0x00215004)) +#define AUX_MU_IO_REG ((volatile unsigned int *)(PBASE+0x00215040)) +#define AUX_MU_IER_REG ((volatile unsigned int *)(PBASE+0x00215044)) +#define AUX_MU_IIR_REG ((volatile unsigned int *)(PBASE+0x00215048)) +#define AUX_MU_LCR_REG ((volatile unsigned int *)(PBASE+0x0021504C)) +#define AUX_MU_MCR_REG ((volatile unsigned int *)(PBASE+0x00215050)) +#define AUX_MU_LSR_REG ((volatile unsigned int *)(PBASE+0x00215054)) +#define AUX_MU_MSR_REG ((volatile unsigned int *)(PBASE+0x00215058)) +#define AUX_MU_SCRATCH ((volatile unsigned int *)(PBASE+0x0021505C)) +#define AUX_MU_CNTL_REG ((volatile unsigned int *)(PBASE+0x00215060)) +#define AUX_MU_STAT_REG ((volatile unsigned int *)(PBASE+0x00215064)) +#define AUX_MU_BAUD_REG ((volatile unsigned int *)(PBASE+0x00215068)) + + +#endif /*_P_MINI_UART_H */ diff --git a/lab8/include/reboot.h b/lab8/include/reboot.h new file mode 100644 index 000000000..55f2df255 --- /dev/null +++ b/lab8/include/reboot.h @@ -0,0 +1,11 @@ +#ifndef _REBOOT_H +#define _REBOOT_H + +#define PM_PASSWORD (0x5a000000) +#define PM_RSTC ((volatile unsigned int *)(0x3F10001c)) +#define PM_WDOG ((volatile unsigned int *)(0x3F100024)) + +void reset(int tick); +void cancel_reset(); + +#endif /*_REBOOT_H */ diff --git a/lab8/include/shell.h b/lab8/include/shell.h new file mode 100644 index 000000000..d4fc954a6 --- /dev/null +++ b/lab8/include/shell.h @@ -0,0 +1,6 @@ +#ifndef SHELL_H +#define SHELL_H + +void shell(); + +#endif \ No newline at end of file diff --git a/lab8/include/signal.h b/lab8/include/signal.h new file mode 100644 index 000000000..47f1dad30 --- /dev/null +++ b/lab8/include/signal.h @@ -0,0 +1,10 @@ +#ifndef _SIGNAL_H +#define _SIGNAL_H + +#include "thread.h" + +void check_and_run_signal(); +void exec_signal(thread_t* t, int signal); +void default_signal_handler(); +void handler_warper(void (*handler)()); +#endif // _SIGNAL_H \ No newline at end of file diff --git a/lab8/include/string.h b/lab8/include/string.h new file mode 100644 index 000000000..8e2de821c --- /dev/null +++ b/lab8/include/string.h @@ -0,0 +1,14 @@ +#ifndef _STRING_H +#define _STRING_H + +#include + +unsigned int is_visible(unsigned int c); +int strcmp(const char* str1, const char* str2); +int memcmp(const void *str1, const void *str2, int n); +void *memcpy(void *dst, const void *src, size_t len); +char *strncpy_(char *dest, const char *src, int n); +int strlen(const char *str); +int strncmp(const char *s1, const char *s2, int n); +int strcpy(char *dst, const char *src); +#endif // _STRING_H \ No newline at end of file diff --git a/lab8/include/syscall.h b/lab8/include/syscall.h new file mode 100644 index 000000000..a6a2e584b --- /dev/null +++ b/lab8/include/syscall.h @@ -0,0 +1,27 @@ +#ifndef _SYSCALL_H +#define _SYSCALL_H + +#include "thread.h" +#include "mini_uart.h" +#include "exception.h" +#include + +int getpid(); +size_t uart_read(char buf[], size_t size); +size_t uart_write(const char buf[], size_t size); +int exec(const char* name, char *const argv[]); +void fork(trapframe_t* tf); +void exit(int status); +int mbox_call(unsigned char ch, unsigned int *mbox); +void kill(int pid); +void signal(int signal, void (*handler)()); +void posix_kill(int pid, int signal); +void sigreturn(); + + +int sys_getpid(); +int sys_fork(); +void sys_exit(int status); +void sys_sigreturn(); + +#endif \ No newline at end of file diff --git a/lab8/include/tasklist.h b/lab8/include/tasklist.h new file mode 100644 index 000000000..337d7a6f3 --- /dev/null +++ b/lab8/include/tasklist.h @@ -0,0 +1,17 @@ +#ifndef _TASKLIST_H +#define _TASKLIST_H + +typedef void (*task_callback_t)(void); + +typedef struct task_t { + struct task_t *prev; + struct task_t *next; + task_callback_t callback; + unsigned long long priority; +} task_t; + +void execute_tasks(); +void create_task(task_callback_t callback, unsigned long long priority); +void enqueue_task(task_t *task); +void execute_tasks_preemptive(); +#endif \ No newline at end of file diff --git a/lab8/include/thread.h b/lab8/include/thread.h new file mode 100644 index 000000000..fa485eaf3 --- /dev/null +++ b/lab8/include/thread.h @@ -0,0 +1,97 @@ +#ifndef _THREAD_H +#define _THREAD_H + + +#define T_STACK_SIZE (10 * 0x1000) // 2^12 = 4096 = 4KB = 1 page +#define SIGNAL_NUM 9 +#define THREAD_MAX_FD 16 + +#include +#include "fs_vfs.h" + +extern int is_init_thread; + +typedef enum thread_state { + TASK_RUNNING, + TASK_WAITING, + TASK_ZOMBIE, +} thread_state; + +// for callee-saved registers +typedef struct callee_reg_t { + uint64_t x19; + uint64_t x20; + uint64_t x21; + uint64_t x22; + uint64_t x23; + uint64_t x24; + uint64_t x25; + uint64_t x26; + uint64_t x27; + uint64_t x28; + uint64_t fp; + uint64_t lr; + uint64_t sp; +} callee_reg_t; + +typedef struct thread_t { + // need to be put as the first variable + callee_reg_t callee_reg; + int tid; // thread id + thread_state state; + void* user_stack; + void* kernel_stack; + void* data; + void* data_size; + + // signal + void (*signal_handler[SIGNAL_NUM+1])(); + // 0: not waiting, 1: waiting + int waiting_signal[SIGNAL_NUM+1]; + int is_processing_signal; + callee_reg_t signal_regs; + + // use in queue + struct thread_t *prev; + struct thread_t *next; + + // use in file + int max_fd; + file fds[THREAD_MAX_FD]; + vnode *working_dir; +} thread_t; + + + +// defined in context_switch.S +extern void switch_to(thread_t* cur, thread_t* next); +extern thread_t* get_current_thread(); + +// queue-related +void push(thread_t** head, thread_t* t); +void push_running(thread_t* t); +thread_t* pop(thread_t** head); // pop front +void pop_t(thread_t** head, thread_t* t); // pop given thread +void print_queue(thread_t* head); +void print_running(); + +void schedule(); +thread_t* create_thread(void (*func)(void)); +thread_t* create_fork_thread(); +thread_t* get_thread_from_tid(int tid); + +void kill_thread(int tid); +void kill_zombies(); + +void thread_init(); +void thread_exit(); +void thread_wait(int tid); + +void idle(); // function for idle thread +void foo(); +void thread_test(); +void run_fork_test(); +void main_fork_test(); +void fork_test(); + +#endif \ No newline at end of file diff --git a/lab8/include/timer.h b/lab8/include/timer.h new file mode 100644 index 000000000..38f21bed6 --- /dev/null +++ b/lab8/include/timer.h @@ -0,0 +1,30 @@ +#ifndef _TIMER_H +#define _TIMER_H + +#define CORE0_TIMER_IRQ_CTRL ((volatile unsigned int *)(0x40000040)) + +typedef void (*timer_callback_t)(void *data); + +typedef struct timer { + struct timer *next; + timer_callback_t callback; + void *data; + unsigned long long timeout; +} timer_t; + +void set_timeout(char* message, unsigned long long timeout); +void print_message(void *data); +void create_timer( + timer_callback_t callback, + void *data, + unsigned long long timeout +); +void create_timer_freq_shift( + timer_callback_t callback, + void *data, + unsigned long long shift +); +void add_timer(timer_t **timer); +void schedule_task(void* data); + +#endif \ No newline at end of file diff --git a/lab8/initramfs.cpio b/lab8/initramfs.cpio new file mode 100644 index 0000000000000000000000000000000000000000..7a819231aa448c873f964aac42ee2014b7d7ed8e GIT binary patch literal 405504 zcmeFad$1(gStnLKJxGcXyC9@k2!uU1v)+%)tanvbRaR!!`1;R{Z^c zd)f2l6MX+UzR#Z4Zr}R%JimPD+tsh1-TkL;IREe~fA-odU;ndPKl05#JNfkMzwG&T z?dvz6@cbM-Pw)N86CeDOFWtL0rL^hu=aYZ+tFK?S|KQ$xwXfZ~?2+%?Q{}VEo>RJq zYo~ON<^2PmyLaA?&*dk+{6UZBLuWsr-F@Oo&*_`E{y~o>pS^$g8_#?Qzd!TDhd%zA z$1}Zi`Gb+O%O8rIcs$*w@ZM*h@VtBl*FBzF&jdA*!6Ro zKIi$`*?T>A?}6`oANJh-0G_`Ce(_Gk^UA+F@pvx1-}B>7Pu{%jiJX1-*6lm*)$Ssn zpYtGp&Et8$=XKABWOnvp?Y8Gh&j-$s#>3Ac-82 zK7I#$lRj8lxJEo`Pk5$(k9a})4?q7|SJyv8dLVvJpm#d|K6-waE~Rk?{2b~4x^Q(5 z9Xv_TLPnt7)d}T;^E;rI=%V*8!KUe%`#Sthro&6fkIDZeblUtr$WR6Cs;is-kPvwsiIcy58lD9W0HZ^+)``Rw>C<+;sh&*A+q!R}F(koFst*1u2bMzz~_ zpz}9V+U;-TwA&rzfzLGcVpHdpmpn#-Z{$wUwYxLWac|?feGN1{0U6MB;;RO}?%AH(J(9oIwE@phdqzahr62QrkX?fw zNC)itr=R?w2iK8)gue0lp|j7yX6fC-=brnq`|rQ+@ck#={{GMG-amZq>W|)k|N9T# zM}BY1|L1n^{~&A!GCPBA$X;$i_n?i*oR!1Rq3o4@aB}uD;KlqAl2PQ&&*9v2DZ=WI zo1W9pLf_ECmq|AN1C?dS$wTQverGmhO?3?9b%)LmbgH7iiBP47T2hu2At$fx_T zF3vW)w4jqabbg>?O6wbcc5iy?`)`du^%wVk@r>+;@`X*E+tMx9cG$E3=HB$`y?a0W z)~DR^K>B3qy%s)Q!4@yQZ&yA%k<-r*Pq_ctOSj&jXD(@xQ{w9%(e=dgp-Ozkm$M1|H`39~``c zegf5Zcb`7-eCwZ~eftLK{xFZrm%seyZ@pc2(T8{o?s?NmTrad3GVyFMp zj;3?FeBI^aj~E@UUH;EFKYzy~oU-=w0b3Rr?<}pIE&ndwVYYHhI7Jz{FNfEJ)35CE zd{_>@caZ15!1;Hhcb3PQaQc(r@!ZCV2iU-mJi-Rf~%3<16 zHV*2o=N@|h*~9m>_^F2Xli%Te^P%^b`0dgbKb_&+?eqQjtk3t>r~X^k_hI*6kDvDG z*-!q|`2YI$yxl5U;}UBo8G(hT7ucd#~-$f z*Au5{JpTu%m+wDYV9z?cXD>mwKTG;}`qJ$?PwQ?Q^m^g+zrlOIefXZI$le?8-uo(K z2%BQ{od-7cPmsn}4xc}9pKt7+M?PPme7u)#pLuoG*8ddK1KLYG^Xqu`4z9a$D4zaD z$p2Rl)4S!Sm)xayW%usSAkA+;4%7#5WkJtf{_<_el-elbkK~1x>GTa;I}0NJpnjX) z`#jR8cR#YPBVTvl{qpWzZvSrFbNXBKZuHV^4t@KxsDArD!27oj>GPDBe4g6#>)P1o zFa0TO5bxZ;^RFB}ues0v#V#)$d1;mW@1IGaPl6mi{lo{+r*ZqIPU#fF{=(J6_g{10 z|37!{U%Bj=UVifPW0a}!CC_Iei_gAs4SAmZGWw&xOnpT3U0*MqR*=pa{eJBSU^|+8 zo`**{E#e+MdxCdQcH_0f`zhS79@224FrI&RN5fCOjfPuq)QE;(0S&)$p9aVk*B(v7 zkM8bqj1Tk?*?SMt`2jq`fsP0Ct`RMlzg*nY;@St~@bn-2gF~7fm*&^@a>H{$oYOez z&3$>gkyFW&}A%Bbweu$pG zL}OWelm2PU>cMm8zmfkfvisfr9|Y~}oW^*_7k%s7m!9;DD1RznI>zWkBYXzzIb4%5 zUcCF(r~bR0Ty|r2$b`m`Z0zwRJo6 zo8S7>PJZB>`6<9F=sCTE-^9;1!6%J38E;hnG5#d_{#RVTFUR{d-GT4n%^&vM_Gn)m zYd8PX8#Jz@HiO2QL@)99Q)El;xi$V>q#;0-kO#ZY>WAGkG-jpe&n{!!0A64FzFXt3 z;(4+I@-3Xa@AV(Wcm#Ei`>qEvzK1;M{+qZ)&uADMqTI3fX$<+=7r!{Z^~KvaPCoq_ z{6)^!J)gcjA^K=cOLX0$JmEt*KSw(7t$YA}q+g`;==!B^-<#f--OhfkX1EO+wykMb-n@V>jw-Uod&mOJNbC+`!o%MahW?fKK6{Q8~G!9JdK z{-3klElQL5maY-sJ9>ydI;S-L&HsDfuXf)q|7*7_!EWwIp1W_RJa@m5^W6Pr(R23? zRnOf&s(SAJZ%vH9oA7Z}kyiByCL{Ju<)C`$6tcK`_MTfmM0pX9zd`cY+uj#2KKc;o zrQcL`kk9$O*Wp)z?wE{`HnaCfJ@*yQ%fCiC-lzFZ&&&V%FI;~Lv+v)7e`}u(=zL^e z|NIiO-$&j<-|7}*N`0)~`TO|(ol7q}Pr2Vc`t4kL`8Cw(Pk$cIy~i_s9{k;bOwT^Z z>X(=QGvs|K20i@gPyXXOupg>J`4i6v-h@2f#B(-e|LYGuL%hC;y5;24+CPELNpH8` ztfEdqnOj1Cn|Cn}4tc1^qw;R$(RBIvV~icbkIJL;UC8R-nMcS9_3y6UgO7vNd-zSZ z@Sh-0)GZIxd(3~bvz>p6`{#Cs=YNTwf0WHZXLtU0*Wdfrr~Wmo!(snldmrlPOCHbf zp?>=^^GCiHKE0~zm)-jW(O0+|I(%+t^LyXl;d8C;^0{Zf({rHfXAkec7iH{_F5E}C zyZx)XXITEKhx$BsfBW#ghw0qC_s6?us1JY6>+T+<@jmn$NhXiH|9;eukGlU`j3(x@ zNBe#I8+fj@dydJL>K2rbd%yLNY~Sm-{p{g$57YcPm)D>DPR~%@s9Wy<3wRT<`qjf{ zAI|roXMg)UJxh5by*r@sj}M=Fn8wSOzQ+14s4J-N0^aX_6Yst6i38h0e+BL6-FufW zF`xh5W?(A96@eT0(P1NImho@kvuyp8oO7AdD_p$Tj zJkP)Q>I3=AhT|UY4`#ZYQPhEfE`YTY=M{l~fr1Rg>eNYd1KBZs%#FZDG zxpuN6{N^iHufK2-dg%s)c@q5bV0f1@uD|#y31oNR_q7}M#p-!#3PqgMU%0xv|J3Tm zS5Ka~@wLue~Tf#U!&q9@_`f>XN{AS)F|3Cx{5~shih7_QI9t z?1x`@#Iq-#y#C6wC+_p1oqDf^#iwpu``GoHuUv!D9yrGA^6vSQXRo|)^|@;|4&S?a z?Ri{$=&h5J8oPY*(v^>0f8k@|Q_nwta zsjQE7@Q1F^y~oB!GF5xcIv3-K&JoA) z2`(5-$$4ezxApwkU&=-NayD!#K|WYpMRLvdc&z3HC&IUrJm(j<1jhx&;ao78$@nWA z>gKTQA1laDk&nh_C7cW`ioYV{Ij#=k!yK>sIVD)Pj2Pco zYz@wlyqY6+OLlo8J9bWUY$@RkY|*j3X-AyYY&#ls?5Z{{6{cFAmw25EAt5f1Y@>Zq z6n~PFf|am#EIxvKA{pRD`CuiX`nXv*Q0YS#pr7Q9Phu+9`q98 zvSZmfd?&zz;tV!f$Bxo@27KUtv_=N0(WM8niN!&4_ zGH6fWx{hmj#v2FkSzFEN316nbbK#ITDa4M&hksm;`;tQ3H)cFU#A-WZmaBQEyb+Lp z-jQ+@+XyD@f$et;3D=6XRlk@qx_k>dC_pD+(3f(oMFBKJUs2T_OwzqVpxf<;UD#j4 z5si#v1k-kTleg4VrXDV&QC|E(KBF9qk6ODruOna7t3}w%B)L$U zu|!Q?ZLMC;aY}7Ri|I=%ACt44XtWlNwN``NLdn|O?WW*Qw7F5g6mgtdX(`kqNnaz{ zSz9aEpadJ03iC1QE-LSdTwp9;yuC~&3c;kF0Uy51Oz1eaQMIKg?8*z;ZSaFKFxa3z zO_kN=+&AhOW5;&JmThW|6RJCoujLr6_BdvG)n;eTjr_q#(w~l0GLmIYvzBVAj*~W) zgHSj!US+4f!O|()xj{EOrgqhzL_IlH?vKIVVL!%)cF#~zviRrLuq*1oa zmMuBm&bk|!#x~QbkaH5gL~$!dh3z1fiTjiamk4vH?=Rf0hxkWXo8p^>)*b^3+9|jowD=P~C!d@xtw)Uth(cU#1I`Q`DvMm%i+%}oV5`#t_yN7co-HjqwmJk)aq#17 z*^UpqogRZ0C1MRR^ zTK1Fc!n~5LhMT^oY@yC77BaqBe=0Or&d}G=LSlDlrbdoBtvbVe-8H}!yw*a`QE<=HoUC@ zUqXGGv!z_l5jW`nMcY-mqWDIcV6IUgpw87=;dN^qo1i{(NUo+G4?+H+rlUGlTkG`~ ziWwz(I<4$F?d><0{T^>Ry^cLZnX#wfcUiNCKGbjVLZ?&Bm(a(j{)(|_^r*b;>3OH$ zlt!sxd2PGcKwAvk%B86tw?$hUY{aHm)itx+^bR-TthJh?tYIm`%Czca(SE45hPrp? z1Fg$mRo;YYtWgYfP}W0T+b~rdbr?HWhp2zG?x-|bi<42ewQ5vR4@%`$xq+tZ;(YKj z#u$EYxY8DDp-yV8Od^mI+KrqJJ=E1!L^8VN;ZkUZ#7VumEDz(e?ULV0A+nVLll3sx z+}gc@J&e~()rY=7uZdDsvs6vlicWqeG>7e?SBlq0j&IcWHri`_TG|BK;ZBXy(T`91 zL+?}%`}h2nI<=(=>WmoKMSh-`b=LE)oK3V#ftg(hhPUb3tT$6~vDieHR-rJj!}r92pKz8^r|}<$It=-67?*OOnF}Y|VKFVJt+lN=Xf?K4sDZX* zFqejnT3GdGD~dj>1(L-|y{(2LYg1h6TXkuGsT7h4pq!}W{9Z3o=0^z*V}>Zk zpv9y3RmQUFt8j5Y`Vs!Io87-x$=@e5E<bfePT;`{jh#wfeU-;pAy1yJ;8>{o zsjod2F5WL0Cy=I+_p$MyjO`5Lctu%CJYItY6!nPi~SF8fE@1bjt#(#hV= z-|07G5errzA8v*5e;DIJzW|*`!6f+^!fLQ0!`8yw!j7T7HP`6BIWEA&OcgXs*Hcz$ViqF#;40UQ6N{CPjGB*@PYMjeBGBK$Jp3-{ky zf0KMANm;|VAKw|)kM<>2kw8+(P@gor;L9XiFfiZoF|(ZIn9Rz7UMxd1xRJmOr-ek- zE6fKm$slG)S5Ca;%m(o+H`2ZSu|E^!MskSf6tX2Bufzkqob=O}`hx8s`2ZnzxBtKM z{~-_P#>G`oxAT2dnmR+%cC@8M+}QZx#HU+2$2DhKjtOzp6H8wtH}I~5^+Gj_HfGLP z!F#4*8mM2r=-0abMamN;hVtI$bK&oO+Mt{0XFSn0e5foEAB#Z`?HZ%mV*Icl`#+{* zO2baZ-|$tv%0}e$$VfrI-XWezUpT-ECCJhy9>_liU)j0*E~<+i?MzN%<$m88=ZuGu z;FFsX^j{x3NH5Tbt+mMhVLOg97fb2DXe+k6i-v_d3}YwLwM8oLt#y4ZrAa1~F3}%M z)(eMvXEG!?zSGa6Yzi{-=_`ry9iyQjJ7P5NZRr8F-1-WA!R^{9O>jZST`H=;H7?sh<6Q+w<_5f?>1|UijDWOxWkgIn@sw zC$qoje|BlYq=M|)LwTG(_fAd@>4N;vlQ`t%@R{=m_9xWuK--^yH)wb8I!@4UqWYQ+ zM)y9A{WrUF#6O3$lY9<;Q#Qz#$@@`Rp3?+g&|cFYUJpZ$sIJkMp?6t&2WjrpV){LQ zpWeOT@t38wzrLqo|C_R-^Zj)$?De_!#JkEQ{V=|~yiO>XEUx<7A_q@32jF>#!^NauWL*J0mA?=hc(VYN4pqI&wenXb%?y>I?^CXSPCh11T?tcga=*%x(|t54t109q+)n<~*^zU6b5KvcM;Z7T5;W zs~Goqp&Ne0g?V6PUeKGW%L6qx2*UR+*TR8JwPmF$t$9ih6;in@H}>l~hi74fNCKxM z8=kA9KHKve7P%uIu8}Z#Q=3LIbnTw(3T*?3@WQ9rZng*dv^QU^`nK1xyBhqy@WrQO zM+(9R41YFZHo&7CHCo<^xzxBfSQoR~EfZD(`awE8*Txrk(FKMX9)%UMaV9U8CV24s zpa;mGLxI&9W7;yCnkxJTcmObw6#NK5_@#hB4Hoci+2k_<9z{5gO}K!{zUdskd&i!n zw!jG+;be_`GrU_LLNEJrcN8ul6B_G-TwNe#yX6GDMZ96L66R}bLG^()@>?^k34;yD zJqo(XXBR91I|FZo(WLUgr_#^^VGenG1KR->WHrGX?m-5F?${7gZNgB|O?Q0{N8*B# zfy$|mgmUZ3Do_XrExRlMn+-~iS|q=m3fU7rOZb#(TV3FSh78^We9De0<$&d;Erak| zV7Y`7S{vw1%7EXDQ#MMDlp;N4O$?cRtQ@kkc0@Ly18Ntj-p7%c`MYZcm~y!bd8;J% z5^z_-I_VttpUcm;#l*Om65vMzcGU##+y$PAcdSj?CR|r4mit|?YD5P!TdjisAYr{E zkBlSQrJBs64=-@M&PkN#k&Y0R1y=4*E=IkqZCeY-GDY}t9`c3{S_RGu8sLxJh(S?I zbLsfBKT(4I&9Wb`TpXJG?~=mG%3#qIQ}wiu_h*`gK+QHJ9T;bv*_s`#PI{d!^uKF! z=;R0oY{{FTJ~~zRer_Xrfxmjm-eE&1)4jN8qUo?9ohWe4N)e0u~Zx>ysc z)%+l{wW?{%2Z*4I#G%GsCL=cuGg_;J_@UsH#a4&!Y|Fr?fw7x|s^UwaPtWR@Bk+JE zcs+xu7&y_DD9H%AM_!vW(}ha-IIwbS1DmHhv|@@A9q>!7;0$B36OS7kJCyBpn>9cO zz51$L9-Cr+u}yL19EPvJ+8n9Wo@{G;8T^#y1uxQqje1M9kd;N9MQ#3Zjs&$+GSySw zR9fkvJiFzB$}8Fg+pbbQl_Q;CK8&|lOaX@~O&z0_2KLbeZj0Zsi8+`SN3o!x2CXg{ zYM$DQl>lG1Q!;EVUfM|cTynZv_BTA{M|9ogpL5!RQZ`by`xexWiXED-f* z+8(faR=3J%8&q#%*^ef|d?L6uVuht*hTsn|fIoppvj-fZR_@}w;^8fTVfHik}_&Ovh%|pffH8ODna2z=96G92-UHv$Pj9Qo%P!5WCvw4 z3oK1oi81i0^-IlxG8TdcYzuhh5ZFwp30y}r<2AfL1?D~l4}|4v4bn$D(u}tY-3;OF zC{Ov&ST3yx#Te`>R>+3O$RE~y(ZkRAbtN$C^9#40k`xpDq6iTpUo<8K=eVDZCi`AK|63Yr)fJeU{LAexE}<59v5MY zn3rXiv}JiVjENKE*D*Go3F^xW_p^8%n8$1#bX3eYBJ3aiCQFLu5$S z?iM^jDf zVTs^<+Usn3y+{Xbjh&eHvlh{C(SwIgHQEET7pSK!^gXNz+TENyG=u&OT>Ijr--Ep+ zroD=zG&^v*4M7`#B`04ZObz;g9W8;Y4O@PGl`PgeK|h!AO$BKK{Ii0xQ$e{W92ei| zK6F*-MT~SW(t!R_m1%5j^ZG@(!27_V%F85=sbOg6g5U*n#L#Zrb}-+IB%A3Bx15$D zLra`sPRyzWybL;90v89ahTfOu)W8?nRj)ef7cFmM6-r`O%vSXkoOmACGaS`gUf&Kw z<&~6&7cQ3%px-|0E&SHNkkni!!jJsBaomOY!F*57pU?Vx!(w^is6o(F%}~D;^_1A_ zc6xcgGT98O!B|QUF9vydL47UD?g8HfcFu7BbPv2iFHUD8g-WJal5In)%@xx>=|>0S znAESd#C2rct|`s7IW1Kjvn1_zb1|!sWy5k5Jzv<0Ts-e6U*>kK}oHR+_K5 z)_UCVB?O_dYRKxcBVlY8r8X{H6|#x6Ze{D0P;xfslK~99>fu#rk|?`l`UD5O(9Sip zc{!__ZMmX#!{z2m2=e1@VVU-gYHpv7>EPn}V7>%4&cyhejln5RKoqW=fPK<9oWmH% z2Yu8#(PG2We6zK&Y_3&bb2XT%?P9bn>a%ozGKfcYMH+0PXcx)13&3YE?Z#4xGH@Ye z62^EC{VK@I%TXOg`BD=?W2$%g06Q2TdZX2P3vP2jYSjL@K6LgCdU3@pHXC)Hu7@jm z6lc&+{%U+IKqr07C4!G-=SM#G-+}JK^LLpIQVV@r5d(b;mkCUX0iL$QahTwH)=Zef zuj1#1TR(hv9ybAdAYtq#!zG4BkE^nr8O&SpEz zZ%`*|MmiA6^6kQ~H{}U*vxd%`^oA!fZx9F3f2fDU^LL5-^JJk9qt{%bnOmOgg7Bz4 zKf~o}Dh!i}ai%((T7{}NJZgp);2ULRNH{h4(eiUj@7%8F$K%umvLk#LNG006oj%UX z1yP6t`k=mtfLU*qi82q5M!}_$PdRZ`VKKX+@+8#46C$4-(96zWw!iYOf7jZJ7^sE( zm*?ez$Y)p^EAOU7HQ2mAjpdqrZcz@!fe+$9*IaXXr)L4vF+T<)_%Z8(@|+0{`l&>Z z$;ty!$m%OP*&iC_2MnPX(93X7vMIW!4P9&+r_D~ccy3F3KK9@5x;7q52Jo-;@7vsC z$?{#vO)D`I`eiGEKX?^ty2%8TFA$Galdcbw>P^uflIG@ij{l%RQ?grS9aJwY)c zxUMh)F%A@fGt+|@4q`lwK6)(G*J{ggsV)Z+z?%~Fb|9w($Fbrf5|GmUru#l&q%>cX z`d5TaW86f^5(ddq=8nS$;x$-uG!KB$2wZ}bDGM4G(kH@m0(=1+mrToAv)w2g-DDU3 zS&QKrEPg5_TjD~b7%2Ea;H!Y2n_)Ja;9{E-dYy4UHVMc15n-k@o+3tQ9xBa^JrW;C zl1J#tYA>p`E z!GbSoxvi#R$$k=Jea!8Gu16ViSsaNE!f)6-1Hzd(UPp;oWMgKT;sb$)8PzPX5YUQm z1WtjbGO#gx7KA-P58f=|>6|e@}&wy_oaWQl+4_|(KV&+(U0rDch z2C_a9AIx9EaBV8voKVepH-ji}R?q`{4e^@rH3DZQKO@lv3}!3X1#?UE=3qN1_^l*- zL+;QCt|8{ODXjX<(4;3}uDloi$somyLV7IQkeN#RTdp&M#u_ zHrFeR;UA>IDpt1*jP(5-KKFB~_{i04n{W$GaBhJ8p4R{gHR7}F& z72tQ$0RMzNWDsX4wmVWW-H2pc zodIG*VVhPJzUm<2_oNU{@*}MKNPM7zLR#bp%W&|yaT^OU69#Zs!%G+^$rso@{Jt!< zv#R80GiMa54&ZO5n3n2tP_>(FBN(Y{3;L3e_hU(YU2cYoi^OJTnjzDOr8*rcA5PZu zRhiE-9KO6!;bUA=<&Gm@tE0Mm`p*9%EzHGGXv`U^t$38%r~)hy^Ax-&3y9BgV^mAv zLpP$yMy@{QEyJu4K11d>;AGzxbIEPZ2iz^+13#Jc zOrMX73!ci85zP*~I_j%YIBMG?%#FX`!EiOSNjevplnP_`+ChI2u`-xbLGxXR7sUB) z2dbW&>hW=JGF^#130`^l(qmoVwiKVH14BSr0^Z@I7h7>ixD$LP@dB`%L1*0*5C;it z-k2w98Pp3Y)XNhwHA@f{<#%jMiofu|=CC2&T%H2!C+wpF92~eaW+X);6V&<0Csj5! z{8(RB>cG!>K|UND_cjPPNDrL24-UjM77FO8pc@#JPlc{@dl*728*~OhS`7tyy&~pA zz*(UL%ev7O5yzwEjm==g>weBL`phqMF;-AEMqG%+df7$=b(3r(#(c4D#&UE20tFyU z*Njba(urV>2Ka#IqcPnUE!Y)dgJavC63)?dbpu?)3DNJKB?0`gonkF)L>lX;WZSjs z*qm&N+1V2ELR_a5k5z@TSr+Vz@j!3aF|mq^ER>>`;>^*;QH*?}91L&dz+5N<03~CN z$pGO@TBu_}&fQ9@G91PvV4P+hv2-XaF^aQAJQT&!#@coqaf42+q`A&QfjEO^(We z*{su^8(3GNE2YB9YLEif=^HtQgBVk%j5#J~>mBgcYYxn)L4xfR&@Xf1UBuYJt`JeB z`84=RwRyP~LVW0o=EY5VsQ~8v`2!Ulm?O8o7!!CKa|U^x^9o)eU2TOx?pVzd zQD>>u!hAcCB9#QUET=+ApHeq^L47&U#D!pr6q{{IF?nq*lc=whjTFnQL`_z7?be0)K>ba1IQob*20=_2 z$JN@|CfdZT9^9IZb+!Q?wAzoZrvk=*a8sj>r1mDk=0U)Q0+27IfdhR4{6(x|G(Vr~ znjG}k3sN|h$@II0`C!xw^fy*Bi8W^=uZwTOp8T9`>m7RgLI-H)b?|n_3K7lq!+cDgg2S0IWnbXV;!#z>vGkXAFrBIUsleH+m(K?i~5U#aayLS zP7(Jnz`u@kk)}^?$K3hC_^m4hLt?&HeR&}s*vkyN_CA135DQqt2e(N_v_22)u0O7} zi`ikx2Broo87#nPWjrAkOcO_arI9ZN5o<@jb`+Z_+|^4b>5q`yAr_jSUB@}~*C8zT z2!N02{>MB2hFn}7czGT6RO7kIvE96Fv(jBV0hgT~#jLzXH8pu1<50DG!>f z{m?vd+`zo?Vb8!E854CC$=RIai?9TIVfqPRg1E20=uWaxAJ%yQTBW)80q!}iZLe!g zc+@ADgU#mL5kJsBv0FV}H=AlsLm_8U60L4`tp~>nyhLZJ8hQ0&IAn+<6u*qhaDSYabI^Z&jWIgSSmk13J0M)&`Rw!Xrnki(9Kh98smA_mXjQCpN9<|j}Imb z*H#$aB*()K>4Gqx-8GUc{HLJ9E#o?!@7e;oMjtv5XNAA6j@fVGVW%6&AM^S+enjyN zN7Bb0kD|Qxv{H=c4xeH)BR_8<5%nfy5p>>X67i{SycS+%@@4Uxuv0wZ#w70h=&&Qa zHzFNi3`TNr50vvG@j?1$2jxw;3LB45zKA!%hrVg-241K>;&F{$z<5L|F4{A#m5nM` zLZ{GZ&&V%T4UIGHNV2ai8i8nXh4-KXA{+Kab1QkBY?MSnX=8!1WAQOU42Dj4%3jQ5 zN34GhOp*@7pOPJbo>Ef`47=8Bp;RzeI#*!pRZ@(X0gQuu*%U)X@oKaNq>(CO9jf9O z)2J(~%%>7YSb@yb1Kt+;`@?F0$(+cL{5DQf#Dwcr*$A9#w)b5Ak<& z9lBzC*H=E;E=_AhS~k|A z+T^kBlIqr79LDBt>a()~gYS2Iz;>wEx_saS>x-(UsLBkRR0;Q!u0DF)n)R>B@4w zE(J!L)wZ(U#YsZeSZ^1LR;?pu5wWwj3fVS+fi(%Mg>E(#VB#Ud$LpcBTy1-K#Go-hZD`z2^frOn zdfSDaVNEuyFK5E98qmYBxzL>VqEWdNsGI$cHA9>y#rEiHu}TgguC_|dcYIt>7xX&C zs<|=kxp@Hg3S6z60$z@}3N~SDD$0bU*y2K(3so-doBPeO3154@3_o>?;)UewKqOyd zb2UdiK{aZM)yvwsflQUv(p8_^W|EN~%=IDw}*D6FRD0CER*8lL^CD zox%s}GBX?m^AK!r3T1%i0aUQQWo#77t$}#}CkU0mO9LfY$9H|wtIo>7LX*}T3-kRz zD6y)ThA$0#s*C$W%!ds|t6{8HEx`|N2gcp90slPw;cddAE?mw@BP?bY_6ps%P*+kM zIcw7pFN`{s*4`}^2rJHJ5?f#d)yXypzYk$fK9pT^3D-Qpp4tfa`kTSDfcVGI*3cS= z(L-J3R9YQ1K2uU#@?9e?S4Vtb!L}qhGxtN+b@H`S`*^Vf^piNml+xUkr7+3>&8r|g zmMi&4Xx`%|sDDaSF2<#3h5YNpw_z5y-~m_~@XcYo;>1H`imM}Bim)4e>+X7lPP@Du zbUPGxhwH%Dg5Agfb5<5f;Df*k>m~@mJcNrLbyiPx*A2u5Va|dzDS~c93`8WstLWWz z1^NhL;j@`F?W-u4!&-XIN4v!?;vQpohvEl)O)DO&Sx%aC1l-1Hu=OA5x)U2V*THZj z9QDG90$Dk^WLiPlqWMj>ycx8q?m9xf%f$ktwtrK`n%)$zW*61@Hp&+o6}eX6r_Jyj z?MASi$z%{$T7?s2X{e!W6F5lQF{V|r9bmS=aZJQMYQPF>z@YM2KUD)(m2QI{ZMdqW$mMyxZPvP(mFJ{_GZf+7yEq%A(CF zfR{?QGbt(ac>wc&G8<7Jz^*RL2TGS$&dyd_1K1#~;b9q&vp0>kodsa#rc~``wxTyb z?DG9axEpbX8f=Kxs7-4&<}rXLEUX)9p(3kS!xfwQ&T^oE`86o(th|T1m0E-i% ze4ySQCaT+Bt6e}kx#Ff=?;odp2pH=r7;vn7dUG8y%ix9P_^3sc_o%Ebtk|$yS**6* zP~7Jq%m;C9HSkGm+XtUI*0NIxi(TR~#mDS!jX3%rEnw>ox?nXOF>T^xn_IUEYAjjn z_=EZ=(rAw{pKQErw+C2*b;kNA7p<+RJ;y=wL?d4KUvhJy-!1a3d}iBMumC%)p^`|C zl(|wAW0QJ0o}O8uv49vs)G;VqVpVR%XW?}slUOIF+ZnAf8mWbwgcVZX1AZ^GPrz74 zN*K!vcY0B)+E@oP_}PO=-xllA%TtEg;n@ovU4>br)^2 z(Ym~e(AtoMh3~fmEcvOA)L(wmL%-sFydW%|4k{ZIuLj(CtSsXq z@0VxkM#}JEkDTxVR)ywMo3eFDSsB^Kq&}3*W8(vITa;TYCY8~TKZp82Sw`KJpXX-T zaK$@XMG`VUu}~Li^t5YNbe3sEQmBKmmL7blJ6+KEV(p$68ES%fZUMG){50F6OcR$3>{ve*%L6~ff;sni& z69PDhuKf9RG&Q@Ui(^kd``(rJ$EA-4@_r{jnEx2hnFj5*F5OPns$Uc(_9*N4M<1rA7Nf1(G6MAAJV}n z1G;H^$v*T4gokOq2#0y&;EG%4@+33?>Ml z5MmM7I$x;{_V>bCezcdLK^PR)5rr??@K)jDmMNwSaf65t$n<+2gZ_d$L zt_8${0ecRTJyCoY=>X@n2NmI)wC7?mWm{GFRpHyCy$tPccez!0A8f1{!D3k|wjn&$ zL32TrRvXUH~k0>iXIeeik2d7`jWlmqx;6s&EK0bHh-vg~G6 zsJ64%?~`j6#HvMjH1?mPI5^A=NX<%?Q(0!v!O!%OJ;l{Z-Dach){Ey;@wl$LY&{}BUfN4wZV6}Wj$%hrY2 zsFxcQTag;}ZYsCimBv=FvEQfN*oe`f5|l+-T^+67MVZ4qW9BNOIYyi-+hTbuQQfm? z03QTS(S*F!63wv?Cc3ee5Q{^5TLHg8TuvI>;uVs}*dR99tUh^vDS9rX>ZYfQ1r$1?pmQQGFk8!}RSXt)+{X&88RLrlSwV6>5NmbYi z;>rij+)C+Uf2MF~ZHEw7YDN)bLzp09ZLk)n;VAH>qrSpgoYHdQs5QiMu~;bhC(;v` zTz%RPj(pP29kd++hdD;Yt(Afv5SOi@y+CYpXWeN| z%<>A?ij6iewF@fdBiGvbtQeVWqnLF;d-SC%6fX!&3VI4528{M@(P+I?V26aUne}$0 zn8$8kiUoe_9Z3QnmB9QN|3c^^4idJoAM;*_59&iDxb|w?r}-CTyHv)9o60sZZFgGj zHGX#sdax5%iupL<$VxDq#-4HTT_M(s)*Un)V9SUjG_dxdlUDHzFb7~bh6%reuh=Ry z<#wk@F;mU86ibJaVZkgnHrv5Ci*nUOxg^8B;6Y`a3t-<$=vs{fUn?TEJQy9f6Y5eN zvA8nPjyG1KijcdGgSpj+nRLcIdks6H^*GxM9|We!_KX9@iTTvPuPK%-O}3^_6luB? zwt1LG@qdP}A&kovH;FH&>KXK7R-~eQU2keo`)-R`6{*)T`&g6DsQc!}C zFy>`sf#gyC@fE%YEHd2QiR2T8A1ol)_vVTQQ3BX?ivg zM6UwAq#F9TW;qlgQ@Kd9#7~M{v`ivJnC=J@z8*{DDUu9S)>RHTfGlHGYYMssb z+oQ3WoNO#LAtd{tKLp$y`)}G)1R8}}E^UN6FRRnOcj?pnUSXy$HwN3kug2x-hEDxm}A!<+;x7^>yFSo0D3^3}`%qS=fP)(Xyx^L}wK zjd_RqGO~*Bvqmw;%_qGkVg_^m%~r7(7IqXq+%;EV3vnuJg?W98^+kvIh96Opg-{o< z+rR|SKNS@Nxs!A8?Y+5 zKzP;ai0Rg=J#4$@9r zdcIL3%aOZ=C_#XP z8w{<9<%`lj2_wKexCk~0$Dvz}nQv66t;4=X7;BQuNKQDnH9zf>2RY(%;e+JE@Mf9= z1-rt$bRFv=Vax_)!xrEh01c}$_6^4Rg0=>Dz}-&~Sb_tLgK$92Yb)j=w`*g<{}r0J@Il@YR4;*In}Fl!j& z+_g+}fm{+aFh8Olqz<7Ee62B`A(X0@r>S`%oy_R0&w_O$Ee-ea&Qy${?*oyH1neV2 zX)7p@6l+iiCPF;WwRiadR%?`+m>&Z?YJVJ_OJ~;YpIH&fNe4v;Yg*McUUD1I#V~=t}Z<3FJr?%;F z9S1anF8|>i5t`=$8KY|3ts}s-cNHkjZPux8fP?i3=sGGatl0)jf<0l3*{8Z{_dGrK zH*@S`8C||W4zqM3oaQ~Xsr{jIj)(oQvdx(C(*$%cTw!zD==XWn{?c=g=^&l#^g=pe zxt{lluzmy9BSf4m#n-@oSeuG<80aY-&*Hz%;xX_W_JvE1yd=m%-8T-mo9&xXw0`UmTIklevG zF6}?yiCuH!-gfm7#X>_L6c>p>iet~Z*dC{aynyW-=^)x^&&ES}6Q-jRVUX9(7GTRn zlgr2VICfdq{G z9o_XfN&buw*aWkA@U+8vsGQ&?eb7em@Ts?JVKGW^LUpV!iv0(Zh_TC*nvrNc?XOW` z;&<;*xnO-#=+Lp(A=uS=UuEF23m;yNjhm1T$q#no>LrBoiS_$fJB2y10xR!{nyz7Q zwA`>en9jEmxVxkx_9TN3mGFg>OzU#mWm>N=s=!B#^|?|hEbK0H3xmEmX&1p4>C=xn zqGQZ=#lCkeKg#n62js6)dc+6po)fT5lsksv(GHh`tI;6Z=`RE$WEofkQH{bk1U!mj zwk(_QHo~STW)RmAKS=wzu)POuTH6wH+_8qFl#&)oE}HcDt4KfQNPWwZwhh?a@!E4X zZeslmk`?X!O7#vfWu-DKjkk;z+9Q!+gTTr}>}_YOSkrFuv%X{gw*uo$ISH@pLXRuHjn8{mYb zwk&jIfnpW>5^9 z8%yY*{y1U*!G4@nud^~PaKH)%z?HLX-ARg*V)$~j3Oq-I+#&mJNe>PrDJ5qqK4`0K z2;U@37qJSomMZq_w2SbskI@g*xn?H<-hwLVCp`hz%Bg0#uvjVj2zB}q;)!~JL+Z7h@np%h{0tVGyidD0|_Qt}Mm8Rva%y3ig zZMKuN7L0^uO4QldX{B2*QsE>QuD7f#i;Kbnjgwfox2xxm#0m63eL}R`h`|AVRIE5y z2ND<__UNSjIDw6`Ix3Ymy4iltFGjJaU=w@x0sF{f)apxzL1h zeh7TkIXv6nO0CLPZM8D>{&-lNZylRAr)C>*YR4-cXe%HO8YlR@?UW%nK=o>iF5VpVUgQpJS)qvxaT7*McYN%-0=`wJfCe~4H zcRF@_VTA%CtW=no)(0pn(XBYnVtYKqJs;=bb!uZ}F71~$ux;2ka2)JO%j%U)vcFca zP8#qiqga_$^W()9pj5DukSD+`p$F8jgsBlWn6BFf+rtmP5&uYd6xL6~bp>mo4+n(> zqJ!hrVy>f%q88$w2YP=KIilYOzBcruTB|v1imjihVD0HL#k|Y!*%(C&GwgUEW= zSudKvmeg2vwVDfgFJjLW#BSj|#CvIRti=gz%|>jbZ4T@q#j|03TkH*q^@cYsIOrX< z*PL!c*q3hAbJXm5JCJq zW+)@#a5J8iLuJxIMTcInAD)48l=Twk(vTB$szUZ{lh%4Aj1#&-{1jbBpJs?Paht%d zu%0UL+s+!BOxal-@&R5q9=m>ti?M++=@U|k4BBJt$=RhguZF#IrD8KYYj-A1>?fw6 zZxNY7#Mmnhb2dsP(@slUiPqaSDeew9TYP9?pSZFdSYp;jI9ngb7GgR^>!n(8@a332 zhJg<8+cwKhlI{{u?M93Czh4fV%^;lrN zmC~jqrTobR{DdLs#D`dCAMM=-9yE+4u$CzHOqJ4GQ}%Ttn9JZ@sdxta-@%cEa!tuRihVJ9fc3He?qliT9+kYl{usx1V+DzqU|n@qT&*mRG^Ij zbM!6ZSa7efvS<&~Sd;eVwBxi6a!j|3B``x@eb8GBsQzn4ssiW+mV>pD34e2Z*vD^J zUzGFM8+|=)$*cTU8B<)fR9;xLe;zem7d+rUz*tJGj*@7Dvx}IGfd$qJr@j)_cAjsW zTqIfNEwLI%Oy$O)7aS}_Exk~Nv8`lQO|_)ZyqRsemol-I?P}W!Z-thy=o#z5);k>b z)L}oWs^w;f7I_Jj7i)3V9~fJ)Q*Cr2HEgL)?eN9>Cv3ezi~}%6=P@=7j{V_E%ZgN6 z^R(UyJFQN#7Q?;-n4dEhymid$8Ntu7Yja?eWJ8bxjy67YUBUXI+juTJDC+H$ud=Fg zsZ4l;`7wR8qYIi_vgn}%l@hA^9$wLp)gV&-v~L2nuS66h4C8jp@ucxNm!Uoj)BT`W z9m0oht|_ir)uvl5KB>qPe;&BUw1&+DD&ZonK?+|x9N4|})>w9YBRRwqzi6j?`Zm%j z=$QW_ILHw7Eyn%F^f!R|M;^={;^$+j*`SKRGe8K0N4W>Zw*XgLqYM`VR$ZuM3KcA! z>N?rUXHD3Zxs6XPkPLA^fAsCxt0g{!4Kmq0GT(ROBKlw0AQX!k+&?S?sIp)1zk?YE zaYK3P-&ERVXRA@{Ag$j=Ix)9Zxe2+$&zs1w@fx;($GoOG>V(Yk{3U-C-=q)l!QzXE zW}Gnk>5q#cSa#gprfQ>gy{fE(h;dz&mn!K5_6JPCu@IU>^WJv)2=k87o?;El{$tk% zGF%z7-n@J41E%Pg?etb#r#1`g3GcL)P3x)Es#Ta2UHpdaEoV&;m&bg1WLLV3d71SI z{8yNJbqt*LuTsSQ`w0Ex83(L9xV#+vwWh)}n&joeYRcy;k$J(cfV<7S ziGJ8E9|<4h!(AKov6hdTyufBS4`EW^iS&YU4!d$3IapBSv8<-D5q{(Jum%9ix#nPx zJa@l8d6BJB>^9jKa09F+qvj&Hge*$3pUuzGeem{lw-)}wO1vn$kH zj%5u?E3Eak4+!w_Mx3-RH3W%P#I@-Mj-l*ZfClI4iIn%HED#g z)B4G~+a-JJ+Lc?**_yk@nO}Lo0KA)LRNt`!I2AAjTB8=Rk3%%I@Fi0F`*-B{9v^?Z z6!krQd`}Wd6oG;YooaDgq@G)UN#t3bB7ru7PjXUB2K6ssBnh}N*Voov+Kl0=u zOAfw;TE8o9*CPDyQWuy2#a850H*O8M8O6I0UPAtP!qRLnFb1rXXJdcA0oGH5uNbji zIm|O(hm*2@T%0v*yNxxe$sceqhlp~$$U*%B(g*3qJus(7@TcqH{#@{4UCj;ro3s|{ zgw~9M9|bs7d4YX_5Q9c*rW%mHt?Go&V(m!WSFx^OR7SiYVGkPCp#q;XtEbN`QNWtfhd219qhd4mOWeM+NcnIu> z_FkiSFt&y&-3yX^X|OAI-y_06ZLBLvv0u;`@Dt)$>_##vtSgK89>*Lj(Ej|#uq27bJ# zihUK+y+WYd?TB5(_5hEg^)ZRZAZ#qM>P1F1$ZJ=>97i8+p=NQ-Z0@(wEucTzrF}OA zw#VIILF?*)KElnqrHv>RQL9C2@Cz0L>%g+?#d@hI3s@T!I?IBu7?u~!##1xQ#J7T4 zt|P4efA-Ep$#Lxnvfo1Q33^Y0p!ePzfdBs$d-9^$>L+Qnl7??$UxaQ9ho(v3dQ(}M z<*pg?!b@Wl`doDXn4ur__Fef*Im9a?Vb7??()}&>8^bXj3ukU%cgL;??u-^8{Ym4Y zI*88_ ztj6u`_TC&E`t)ZK?Ge?ygsm-FkG9X`A1>Xv>2>bXy`%4hm{%1aat}{!oX!vG*QdJ> zUN)7E;Ki+_>y=q5HAti<*H!i0u6WPqy%E9Qxm~uBhybmxo#wrO*$Q9^yEW$C)$LKQ z3>Vx~_MlIl2|4J&bMNeuV7tj93McSh5f)C3|Jdbq~zPkBKxC&z(UjQ%tn>EdT?r8b#r9OC92O z@G<@%mlI~24=o$Bov-8EXH2H*x8QNl~^;B+1qi}31xoMZn^Jlt_Me{jbBQj0&)uO|8C z;S+^FPVbQo#i@5<9QMI%y%Ii1=|j4}xKA|(wMuT&>hB-cq9U!U_1v3r2nVlC!}Yvi z+p2+wq(`rAu1TM>!3XgndBEK~fM0o`Su@34@UXAhYV@+~fK8<`%Vre50Y$*4qUI#D;ZTUvK_6Yf`rr^tLBqRn3DZ^A=( zb=MNZ5^ihwgyK(i!xQ*v^r6RSIK8I_(+;LmaA>43SE*In?lW}_TFE)SXURUFN3JUib!id)6!d~Isr&IkM+ zqz&1^U*Xa95ExWkJNlb&&cU{Y9BDHJ9*EB3t9qZ_+TDF~1TQyL`xt)^e?)t{!bgO) zPg>u^{e8Hxgf)mv=kw-(_;k|zuC~X?#4)qRPJZZ~+LPqNF2PZiZs=i(K~A{ZWYw~l zy-V-WKF<~}D@S{QeTrvmaJ4kHH)4)j>&v2t~U1|0(`pB;?1mv|2t!$Uef| z_j}VyBeET)s-q#8+P*tU+$Qikm^WW7@&kQ2PW^+O*{{CWT`a{N)><^1RBolva(r1X zf{)i?2L8+#fd}bby70W&(`?F%RN}xgpOEJA$M}QX8w@b{bfq1kIL>f^vv>9nvEJ&Q z9gA!CSk1$`RPQ#a&-2&@iKRH$Fjv7tC@D_rkiG_Vxz8KEBX$6`tu^&JQyWX^R$0+e`Kk+4(Wr@iFlIb`ADnmCEN! zHdx=@pU02`4(iTvmal5Xu<9 z3w-p!Yj-DWGa4l9NTt_yHiKxP*&9ynMDq5?UYw_WSzoe`4r_#b5oI7vtnW)_(_9WH zfX+Q>&Qc%Y4{|Lstz}5-AuqSY-p&i(#NU?k>0T;d?X(-&lhNepmU4!ll7s9;3^-o9 z9-tw5mdVDhKC|}6)!8d`pBwRsCie%r!y(7`uyF1#2Oh+D_2XW__w#Kjy7>q8SY+HwT0N1_FyWO| zPJx5qYQhiP`f1cRKF6Fn@IPkmtzT6^`e!hZPv9W<0^=hp3|#)x0o9q#&ZdsG8Tn<9 z5`nXgHnWMDh?b!0n!FmU2F@mU$F&@}zQ&;hJ{H%&Rq<-OfCrn)O}x9=Y(M0h+K;l< z4k`FiaV(AP!S4%dl3z_+!#|^ItVe_5Z>XLahq?#t60BB}T~oKOhvA-4hCTHS<|KII zpwJBBWd=5fM!p})oK|TIUm&(#YOB}ldG4g~$2a|!I01F^59(k#RG6tFmBG~8uNLHA z|DHa)d{{;&^vzc@c-kA6>Rw)lPCAq81&Wt&vbqZ0V#7use>wPVDhfQW$E!p?0(YjK zz@lcZJ%qWc$iE^A*(!fxj(6Iamtn=}DSescnG79%Y?Rz%hB)4t$_27nndp(3> zy(-aJ9{e0%vgPcx9^waQVo3cq{Jf6yjw!>>|5P5@!;cLKp2=7_Wm~1__L-f|&m-cC zQijsQ?~8DX>3@S{OgoSGzTaB-;{RGZros*-N_%hso&-D=PUNQ&)chY z_!>w26}RNBzQh>n9$w2w^(aj+Z{6NKzvDk@%>3T;&9{?}>>rYBrZ`<;%fmlUwy~++ z4b?p4=#T7AoD65Q;nDMk6Muf9u+AseKW_8yB+6jw`=5RHyL@Ku<{M1X*rKUxVY4cW zX@{U<2s2mVy^GVy^~GPXFD!%1@x1&^>5apD=b8JO{As(Slz``N`V+h)CFRYOD-?8i zVb%!svrJI7c5Kx5kp6ByPhj`?R5JaS>k`vW`SH*EUzw5Jo%`sXen01h@+d6?*)=2L zn`yl0@%Mqnn$Dv=Ed(?V0tN1BVm4y{VV2Y*dOtHpKSgP!3(k{Ptt^MAen? z=Q920o%gS2!yo*%V^a?CMBvKlS$L27H)Z;Mn6mSp6wKGvR@A$=KFW6_!oG^lV_R!G z?WePX_Mjh2^x?Pt{#_1TTdz%BYacILyzZ&~qdclJQl^BZ{g@dSlqVD*j{4UglR_%< z>i_WF_#t(RNJNU=QF3$CR&Lv;@K-#KqzS4UvGfu!KGVFuN3w=ZNNnUie3vMM5 z8Hf`*J5U_fS9Wz3LtCmoNJPu<2aCPQ2_M}?v04f2A`YF)q1^N&>H?qh=fPWVX94U? zLH3oT4~}zPj(eF-V(MFcTI1dC_ZkPR4b*ukkF)J~x^fPS)AhQPFZ>+aj(9rog-q-+ z?Aac1o4}*Mt2)nYwYY1I&}a!p*&{}tt5fW0C)gv>J}TRHKQLMNwg1ch$y+xq_%K~? z-0q=OzAWzv;;%SgebKbZujT*!>o=F#$*9l!^9N;1h7wr>tp>M@9VTz!d}rz%{Zn_1 z?Oir5#mYj14X%i<|5dCRoj=8c#Fj^Uh*K!88f_LzKG%EenRU~X>2aj{1jVm6>&`nP zzx?da9U8a(+7WF;GZU|4!3T`tS>F_Y%QpNquP8jn_FV8j4nvepF^=G?5Zls^KL{Qt z9W5|4qg@Lh60V)!W?s{^IwLo`j}!)F%0jg{W-j^V0(@P7IiL2`yidv>BXi=B!Ht5q z=-GyEYB*ow*16(KRcohbGrwC9r)iynS2PBXoeRsA%?nN%n4;C}fK~U`sTKPvX?hmH z)NFkJcT;f2*f8#L7L;A)qe?hjJj}z885QG6U$CxWp$osi^Ebl7$J6Q5Ic6P1vd%26WcxL^s#mFxTogr z&lQBeGp*!gQJCVG~=RNu8-G`)WEK~V=Lt3W{J`A(V5XMmB;PrCCy~-Ye{{iEZ9e|y>Th0 zEqx{S4&dSR57QR5@Nsm4W%~tN-YtN7_Riv-9f$)`f8}!PyBDu4=Hmg-!D4vrbas#I z^874fGll^P*5;zOcx<-<&bYHYw5HfmF;db;>!Un&qOW=76D47a#cri}DpV}BXY>hw zTxh(qCZNfzd8(>5!5=2HXcoaRS_gP6C9sdNyWa3Vy??9@&22iiwqx#`4OUR~P5eFr zsnv$hO}O^#JetR4{`GWc#GFc1HmTK!dShMxObTi*r@f9t{J}#>W(+%P~#<7R2!JjDO1A|t0=Hof&i@tK=9f3dT*b__?N}mMPZ8gc{yT2DHgi$O>#b%T`_$^4J$TOJ zdbT;c%~NmyoysYj+KVzF4N+l&U{K&SaEs7PwKp9f-=Rzf`<$RzId3Wcpy%DuC{0{e zH}qJu=kX%dTYRh>M%NrRP5s7Pat3da-%c^Pp;_o>i|MT}1IMX=rDd+!H0A6rTjN!G zmw|T&M+&13*mHaguG4)`!QcpRBM+28SZ;UZDckn5y>Eog1X_G@lto3@D|Rq73|`_R4}#zvJJ zKE*x7_Ks#HbMIve?`w+wD>mF0oSZcAR}Z~o%R^%@U3}W-kZXA-c*V)}yg(hK%Aey8 z1_HI?Eru@(>qLnD9An*0RrBHYcyt@58%#(n_72h%Rh!ZOqz8MBz7SBIH|?fVb<>Ii`ziilkU4iW zU9n!sABH%JCWdph4qr1;juc|q{c=Hn%O9Z6wZ}7zZsdu$X^iuhFg4nPenk9KZ!?Jm zuZ8Ad7K;SR;X}qECiko7ZCYRB1-`1?$4=e%rZc0>Vm@0)UsQa#bkRwiS2e7lVZ`4S;G1CQ(h){an6iocJiA-*)+5^jC+!YS?MFmf z&gh13dhyI_3zT~)-VJ}`do`QuaN3UVx8sF!MO{g<{o}+9ArG2+i z9aVy(TQYymJm=STgLQwj_V*=UekG2diC>z7jV;0K*EoNhGq~1vbm>{>IhSMeGy6Ht zb79&8*}LWl9l_%bU0`B8j<2hOJ#D5sS?5?TW3xB}pZdWD3=zEckXaaAQFKwkaJFEal)-P0 zj~;CQWtDKh^n@H_c{K4m`NpicV2IDPRZ$$RJY*y3wS{9w+W#@45C@|Q{EbsNCeE{$5V z-pbzkPQP#7ec$oFe*RxM{_CpyQ-1ir_V`n(^}oL8|JvifuDUv1H2-(`1j;j~(h=11s@4yVy7nqBGUIT^J| z%dvj11wtG3%WAtvYnUW?9L0xsl0hHt(ucpE@TW8v@%PZ&LmUb}mIj|||C3}-mkg4J zLaE*zPe&cuo~{`r8>C%kReRE|bF=6Y3bPSbC_6X0qmRs;y;eUuPtF$CRemw3N6-i7 z%*%PpuOs|uK9yKo!S8iGqYToP7Jto@g>y$F(5_~Vo96C(U7js44E*?|A7=?81lL&$ z>%jh4#fD;c42EOk4}ychA2Ilv-rH5|^^HR7bdCA@5fej9?ee#k{g=zb9-26Jf4d@H zM_j#7x|a9p4x6dikh+C~x5sB3j18SoaN3GzS;R+F-Gk3~^WM{4V_$(2NuJ^A!6ES) z(kbNk;EA&FRTAfA0q$x=aecs!z_6W9;?BZvyskQ^cpV3>*qDV|iQ^~`t;VoZ@=^a+h7s^a!h@3aY@ zwY<(ab6t+(`-O$SZn5vVW7_bBJ{WBTJ~y~Vi}c@;`tQfmmMi6%rvj0z{BIVG9ri)+ zeACa1*q*6d*TXg^yb}JGWjVcCG;bB1%X!Fq-Q{KJeTMBz94=$(dbY?Zn(FlCI!s_APuv>#| zxofoMQhA*RgU;v9gIX)hT*!PV9twJ<#PwpnS%<`bRQ)pFVE0{zl#h1pczkWs{T0}N zI4%9ls_%`b>sYe)Y&e%-ZkaAPx6U};Jm>Gn$0PpmYJ=JJUbwn}#k$p~_;`HGu9x}5 z^s68KE!60LnY^J-Bl&y&?G7A{I=WobpMI}C`W zA#n$7@-5uPRy;y)qgZ6L8M>D3@o9H)GvY+<26ZbkpUtoaAqvpgjf`)fsmuSiN4#{HVDllKLJ=ZF$wlGb1oof>(mA+r1uf%)Av{U_Gd(%*;a?FCq4PTe`Ap+Jp zL4TBfHvzv@{}kVLG8Et2nFJM2*8UuSFc2pWb%^yCOdlVT4r_(i%bwi7)bXVcRfIor8a zOti17B3Ng5OH59H5vR6qv8tcPm}KX1fI4ZDn`q`<~e6`%SAlU*4<4t6kMwvGO|VM9Py! z*50*lYn^32#vjDiCwm`zM1v7Uu)Yqal`(5##fd$M*j^c7gSM9`^hG)UVWvK5&gc8` zS?BB$Fg}!et$l&^IpOUxRXd%WZao(1*XL-6v`>#t$D$nBb@R3zu>%USXZL+Zb?(jU z$G?`iM9{(S*4Qk+;tuJvtvSH*Y~rk#GZlGFN;_0|6A>SgKSzW!H`om8{cvq}9UsD# zdU-ePEt_-ZFA+Z`k4HB=16zz;JNZE?Hf-}c6m-b49~UJ0G5(;z8f7=uIq*Bl2CMU~ z+Q`IH$b^=={#9h21ac2KZ-{rY|~k5y?r63brh@;a`eM((eC7X*)&3|8AmIH%xcPRqGregquYN~= z(!52P!Jp}0FDo!g=KI>G2OV*FlpOQ_@AxmW%ak6(*d`;44d0)^yw-mzi}Ji#Jj6fs z$F@FDzQ4+W3J!QJenj_Dw;mYDwx{{;Q{(&Ju7fuxgdcpbF+aIqnuGF+N1~WriZ`h7 zD{hMj$C{r4Dc8E5d98R@W)HwKpF7eY__=23<3BHv=kNS;7uH{64^}?SP0g6qe51E- zd(@Qt8D21D#JAaMFGX+0!WRx2UY@>NSAlQ;3hfP=@A z);Hd3kKo|PU`?m|$ugWHeczN>^ZlPc-|B8ADX;H-q!0A|-PiQF_L=HWCV$SCk9j>B zbj(UJk_AT}ydUbzXs6_t!eX?J(SE|(^!Km4s~q}G-h4)z zn0Cfb7o5P?m(eWxqwOM&SyO#!@aE(rP(kSK+kkZUJAwH^iqF=Hf$tM`w{`Euosm?WD>pHji z#bo-D6X5BqPHA%{B=8B@UhC3O%4(jej}nh%!`+>i#RND2c5LYkiUZaZhGw+ae4CLt zgm(M68dopNVtknBHJ__nFutqHF;V%3exdUx_tSamQ)$bE%w9p5qsk(mS99hLq;|*r zG_k5GrkFTs*i*2hfFlr_#n`TW-XOS@@Fuu;UI+|IG0b=t$IS~lU?JFxJL%2+9!l<} z;@n1pYA18A&y^#X_vaKY%>0ul_9PXCp6TX(dfhwZ+t{k%NtqaLUvGNo*Qyc)V`pT`LqnXZE2+75kQ>btIz!_sM?6+z4)9#%3T( ze3{avb;g&FyxK*$a?%zh|0aHANVu|MCyEzHy_-D4*D>+o&>lkr(&)VwvDb$vYl`*@ zp+i-XFFV(Xm0jY`6$cscXJNvhOX@?~>u78Toj~Bwf(r<@2<=v3Az-k=TZva`@^9r7 zcW`kV&Cw(zk2V^-ipL4x5s@s)L)q5#?R>Tz)RN)nYR=f1w>;MsPKrIwDk0ICA19Ix z$|2tV$5P1AWNFsj&@g_&E_z0Twkk|qc(ZJ!!lwG}>YN7pthJ-NaPLx?lp~oMhMy32 zRBVRgNa7)wx7a^H3$yq!duqdeW8W(5C_~(z*FRJg)rr}Q zV#jUUN%8|OSWQb?jn~4CtCWNO(yVRnw|o7ROre)JKR(yzY;jqHvu!bs;Ir|4gZZGl z7`sIm^JZ(?wob*;apYRTEHPGh{2lD}o(B^uGk=p*f1gU4qbSTalLb!}Ulz{8T)bxW zE3oAleWRa4=e10{Q|FqEr(T=Vl=y>#r&a6r;@Mkdn+f!5odV=L_txnbomw`tOf}*4 zJnXw2hkM(oRgTVVF-sCxO>;u3i=H)hNq>U;Oi7GCusQSG+w(Z(VE&x9;0SAN5I?5` zhMr#BcKgZI%fGr{G#fMpU+E3p4`CqA@makOuk&miTO#MZP;|SjoR;wz_Ux84<6hp* zIlJj>rgyCz7QmzHP<4~)teGo)-Ts3zM%AbRz*Xe>W465F`1x|BV5OnJJSXJW0rD^V3 z`&04YOo(He0eiM`g!KDJ5p)s%3=z1rE&4sx;L9(Pouzdc_WRWxb8D>YVg>=SM?UeF)L0V>tud>6&`ijd!0=ycc@ox zt8vABo=czN53+tl4r$m{9nCkf^0O0*#S4Kr9L!ENTAtE2wPAvQr*nvrx!iD-s`iJ8 z27HJXc+`P$0=A|YmfoU|m=uN*`3Hb|YA)F1pbm$g54Ts{rc)z0VT}39e0YmRSIy2? z^WmrXgJ>eMS3);hdx_4THmT0ae*J3;SnjK$R!&J2LtoiK0V^8jv18_~yxnc~zL z#47VnxyvK+LV}rW6FkqP;Izx%}+s=gUlhn zJa$S+FwQYpC}Ra|+FOBhJ|@pxt1T{i4yFj4yDoKC{1|@_otNzHIIW#Bi<{ezGVc=C zGSrc;+hR7F53kvtyPgyDJ6||1`!8Y-20;dik9;APXnYCRkvzhyMQ8NhVXiVqya)<3HveUUw zdk)qnG+geTLNvCGr1IhAbVGsACT{!tlCOQc!{+xFlW$LAUlI^EE)mxCgN}H8bzc7pW~myX>&v2owj-`Qo*@cl!d^5I&}g zfzfj{i^QF!HbCj1BcEFCK|DF`$)!KXAN*dG zZ~9|mz3aoz)ZTnMa2;_s6g`YYL4G+tN|^UlXM zO}&|NsEIz4Ox?UM^|p6PBZb#~y=8#>;z4E0Bw+`Czz;U~?NxKFeq@q&p4MskfKPQ1 z=Z@5UpunAax}#XE*9NQQtYgmR!c?Ur_qlSY2!ANcPg^d33+InnNM{Bh`-wvte9E-P z;#D}7Z|g%NISUbquwEel2BTuz0=F=i-aK%zO!S^#h75+Z!DzWSbAayuvGi^SF8KFl za;B+}YW5Gf+K)DQS})EKnZSO~%WV#Yba9vwM@@Z7ed~m#E$i&59qrY9{KG#xg+fl9 zKK#M<_)&&;y{Ihj@<6Q1>fpb2L&w#gT zl}}0LV&C4!wT->_qvcTM{cL}mFW&Q4q8RCcwXRKnQXl%mxcDj>b$@-O7pxnXZasfW3YlRoZ*w_TCV=qK6;N0J@eUMicLhxpO^me+5C}$ zY$h%lKAEv?#y8`mt3PUMPx3~s#r-~Kp2c2e`qEc9KG?6+9)Ds&Gg1DnX5C;-^^>p# zE#W@C@F(8=bC>>KIX+jHpSaTh+T#!G>F2mUi_64x>N>F;ZUIlnP=_C%DcFwCze5OGe6GdiI$e8q*I z5nsyPFMM1OjL^lG)Ft*{))P<6IkrZdr|YJ=mmpX+Wsr|0bz;^)e;xeg^XzvGP;X{` zb9Wf`p?DvN*eo6x;KlyFAiJfnT6&iww_CcPh)rnkoY=Aw5e zY}TEN_XYN}m+w11?ycM%vf$?03%` zH(F6JXzdrZDKW9)K2#9@JODNn$OfH!pmpv9Zf+57k?f=?uq14B;1$GM1Dk?pC;qA7 z?1Z}LbrO^COxcJ-*fx}soI1Mw(9Rg ziGGV_ZO}n8YLu8hvU7Nv!ZXD?Rs6>$I3<{}xR3CSuFGcpy0rKV+>MxAd`B@Zg~N%T z2bS0;wo#*VX!V1XZv@|tvcU7SVkxf!7tWg(iHTLBIj}Y0*YL5#ag%FCz~nEPe%f`< zG1CSs;d#=F6AvRD$78!d zW;L%|+zwHFo*w;88nK4sT13O^LR_@1N^F9>%|zCMKEv0DbxHDtL4L)(yn#Jmz}s*;5YDW zz&T#v>W>NbUg1u%#7*cd$ygx)zf-=lxfwn(@Q=c?OQ)Mm6^@17qjKSzopma^#HS(h zG5#R^G)TRi(ux;qVrXhjAmDD5atW)Awcwv0tH)Cad;lG_1?77YOLxIUq_}qR(N|2x z0XUnuH;Uf{k1#zGR&{rGxlXK86Q63gSf2`|W&bJiRo1esL4Ih~k`M3)*_VW?5oc%S zRkJ?CnoyZb{rD_>N$<}P)yUrAuUt7e5hg8s34SFFL- zIB{|^p?0NTX*VNi;mA7Y)#!4IF)o?`J8eupKd2k z2r!3GSzsayaO&`Gm{?u6&D%loc|XM;9L9HAYjgA~_Ck9!Snu$<+WAq^&QGrV4Lty6 zB74~+4l;fImd)X(Hvk(nu@d?1rps8LOS5c{uvlKk!KYnbd@VbMw7a@!m0eH_=7qZ zG{ymM|6p!H*jv6Ca2~DaI#n6gE9uV3jo&)!Qf&JS+*gk|^-OuvBQ%}CY!w?3d~l8U zBfQ94W|3xoC3feJAT}KSug>2x41V{K_=75h+2=S~ zkBojEFSMWb<6UaKEgf%V+yI8e9<+Qchh=4hK^i{MV?9`w%BOS$tZ&lrnzBv1xe3t6 z<#qGWeYP44@H(UY5X4uTGc6B6IwB_M+-5d-nkIfJnK(B-F68f{w?1rS`mNSl?z2i4 z;oK>)HQF4+S361)5}u>s4(-$PyL{^Q&x7GW`t1RqD0e{<`2;ra_Nc3*-{dNwM zRuMkNA510OQ+I2SL*NiPOgaymqD%*~`gd+=}i!fVC7^G3VF^2Pih&XL*|jGq5Z z)v?xmeWn+`mVRP4?<>7OLC z;l#O{z&u~|(6oj-I8@$~K}R>y{3Sbs>Kf6J1@fwv`nK-E>GZ)AWGxxf>^gXYeWUvfg{aLD_a`<`ITxiN+`t$pK zpLk||`iHaf&904%@4_1&Q~~t*jd$|wb?HB<|L(z$V`*F!#Us`{kOq4$u^|L-zKJMyuh%dq|g9sRhD zKk)Ht=2d@slFbs%AUuPu`2ycfh!66)^p&?CeZ?<_@~+3vyz8!icu%EL|MVZ?C)01l zNj3dU3`Ao^=OYfCGKac!fFX$Wa zz*NsB)|}Ka*+kB!xrJezIHX+Mi2)rJGKT`JJ_#KFtG{ z(LjQFhBMCo1V2XZZ?YSd?O*-ep>Rew!jvN%bP~a!_RZuy=qP3oJ^r`%o`=~t}%3#{jaOv{; zK6`W7csYL)qfg~kGQ7L5w&8vu+9Jq(K9^`;qs^czk|)Z6?gVp>>P-(7O!xn)OZkGC zex)+XpHXi~TMDGFnr4jz*2~2DWr44mzJtb#F23bF!{38Ghe^LO^`{rVn!e6a)GO=8 z&;7e817|j8$i38W)Gn+Gj_NWHXr6u{(n;WsU#?dPG@=dBGt3R49fikS0o8=Gf#)dC}U_2QjMaBH%wdys!HFma7_ zk547PFKE2!nr3YZbN0{epY>^dw>myu(IgZH4_+6BAo2{(X4_@gf+wjMexF8zjbf$%a&(k0sNu zxEj;YIGJ5hZ}buRi*z8-Si|lMw#Ik>3lY!Zyo{ykS`PM7+90IZQp8o#TvK6dGz_l4K0AHmZTe${N>R{b0{uc$Mb)=!zp1dYq$Z3z#~ z%nQ`Mgdxoy- zhrPfie{7#zxID8bI6rk=oQufPOI3423A=04 zpTCUHxRKoA;SVoShhdoljuz^w;Ux0_y^v)yP0fB$_ecxOH+Z<{L=+)R9=kF*DCQB-~` za1+J10}re^Xf&5xGhbRZeBlZl$X(;q>)hA1!hIb_2k!u%S#kOXv>~s+ci?oDJ?z#G zyVitjMBI}ca|4b5({a94o%QZsE5BXXhr(gFysu(~hg(hMOVVxlP-1^eAK%lz(R2lK zcF%){+b>QN#Et_mbnShCP_6LJO5sHcUemaNc47|uRd|KYE9JP%Tob2kfvtW*{=>{q z_%6RD3HKF>!QWd39ky1F&|Z3-{pmQmXePn6SzzwL>xW7aNc5Ja=kv@OHfId>iHB&# zN7vI#Yb~74EZpmS3|paV=PH|YXxlvBz+=K=VkfS_IKV%oJ%;Y9Vkv?<@tPRUD#s@A zvb=cv(%WgXFinPV;13^#`Q!)X%H3^t9;;C5Q~bgHG)aHov+fm(>>s+vN8?#N6+-dd z(Y~acQR#q((S5l*FX4-uE0th#QQwpUj2}KKI0yz%j9D9D&=cAO?ONeDEOv~u+^%J> z+vBXgp5Oa!4n5x7WHP8%u3KCim`kir@dr1l5&j9GDi{uXj8yCRs0Q1Y;KHu0?z>bL zY|`$PhKc1dea1#u+nlpC?4knv>}bx$gUn7PP1^h zQ*HGAEBl2l{M-TS1lxS9(Ln~6h0BbF@SJU9@UvOJ#^tIRoJoTM*L zw6oWx{+)QVk9Dr=xew>Ls-ZQS$joDNC;avE6)mhLS zA2@#-TT4K;O>H(gPlA^~biCCs(c$=B7+s%jVvy-?1m_WwehdZ9=#&>*m;6!9{bUFFb$i@T+pcVG@AQ7n zbLY~z$UOZ(NRfVTrdjjMfAi*j3V4Zo+_HPk!lQp(&>s~othQu)WS==|LHBsyN7wyv z?f32ejAvul(S^V62MG%Ne<1zJzavYl*Zq0zcbk0w;e#*UZOpSr=apa2g;?zBa zv(Z>#TDg0VgYZFo9yp_qllmPhi@ERm|9Oue?eoD>{NYmX`@SI?lXtzLKfTMO^3~44 z;k>x@c74JXU~;+J59z|Py=t-6%v1WEIB)OknVbAcZDH>5Px_QP961-TM83Z8&A)rS zNuOVZU8YhkWTY`nf1?at;k)wjV_p49Rfv#(&)@w++5S+AA9(W~FFb2}n*9G9BtKqj z>;vAq<5?=dZ0?QTGudYRYn&5r+g_(k`|-VT5}jcswZ*&6K9Jq-OZ@1sf4DyPTBP>= zJo~OxmEqkkU*&PT$~5amm#mxbU2c9XNMa1z^Yr_ty4LqS_2N(JTYtF6fBpvJApf++ zVaFpP=k;YTIk$e$uJfPg=|BI4!3MxSG|f>CB(OGL<&Zu1uaW%z@oo?DTn?IYcvtZ_ z`TQ@H=uf6!nXD84`1o3HS>^vyNqYeHeqd=nh7%$d=hbLG{*{X1`<|Kc?T?QD8Ynkn zQg79-{yIs1U?u@Ss3$q)jN5r0U*7d&Hl5#88T2XP;lfVzkJv7mNXzq_Bd^uxep52K z7=GW*>z4X&$U}ZC_*nXwA%0WDlB2#8yJ?{v%Le+BQMwadMQfw-x_ORxqsu8ZZ3Ks{ zOR_SaB?h}i8H|%QlF!u^Urzu0M4N#ZeV?JkUHee_!cK$Ib@A&@&m4_7w~}vFXnS0V zf@v&nOKk9~a}7)boy#%21mdbG)|X-&xo9Q2bF}@&;7{4>tJCP6;Zp4%yPI9zfu~9D zQxk!~(sVvVSLB21q+`yms+Ty|rrvQn;87Tqd&ECnJza1o+scrC;Y#FL+!k!82H&x< z%fnMJScCK`;jJ0YSM>SvqHI=#!^JFbU9GO0^I&4PHNKX{H<4!NO#De4hcy?Uko?m4 z?-mYi=diGY;x2+a2{-7HKWr#)H`f+86t8V!m3bkx!N`x>LX100u^^kW9}R(SJ#W3k z03+_<8QWDq1NIO z31{37mP7SH{iK3V>%1Hj$ID=wU{KhyvUARKDcU0?USs-}IS04%?D4rmhtu>W^j%#K zPM-Kw!lat56~lbn1QxUl`A0T1rEksn4!EdIY`;78QY@qWR}MOC0L;GZQw@T4wi5`< z8-9XhU&lV)%ZKx}bE)j>k>}}>sj_Ytsbkt=b*2x2f5J_z zMyBp^9?flAk-6rGZmg5KcZy(*(v6Lcgw^dAYy6z#5Ax-E@|mJ!#0P$6`|vk~slwef zc>fq3NaiII@9x|jT|3p@Www)E;^xs@B?{%_=(^it`%i$=(jKvb+wn^IW1t6@GwiMI zsok@Go!mqD`lx)^-Ya+|GpDItSFt>GM=_0$+`m~0y1sw@!v1HlK)+i$d~UjDB*5r^ zqkM-)Yq#k?nv2vX(nw`|hnknnJYa=}z?(Vz*U`H3x~pr)J~qdNalAmNjO(g>Om~jL zw2Gxb^Vkw5aPPiy#Ovd6SsoI_@G3K#&5m4;@nqF%ql|b{ltF%2|3=c3?UzH}H^1Rr zj88N3x3O1b$fLux(GnH^+Qds#Tv2=$O#C~mHkiyR8;o#9D|CI4EuGkKuNvBLo6|EL zv}=t?q?bttIXQ)QiN@I_p6B{J#Wqhoyb1ohY50TjXqxzEhehK|+qKv8(Mk3AAM*#% z>o%oUnf_KT3KGHLwIJj?xEcmo+Q1#AsVTU zv>-Xm!gp*JGBU)666-(2lNKg%bRg=OKkLcba9MCysWTlK+YWTDIY@YZ%~Qoq$! z^XdvrW14`ExopAPCSZzArT+4 z{DGOw9(KF}aeHxhW7P-s_iytD^Nb0{e`1%KYn}7@3EQ-l0csqwuBbsq-NUWRdNhp%7*9Zt?{QW z!YO?9c8k;wjc{k3wl%J}&t^7A6fWCV@X0(<{I~go4nCUS7*Z8nr&MB zJ$T}(d5P|h*;dMX#V`RiJ7|y!55;!WVm!fFGnjjLzqt3So%4)6L$UU6^9R))N^&do z&u-dBF;@`}F439Mr$($%Tr-3Y!5)e1;3AyA;=eGQyw=sqJh^lB>EhX7|1ZNC1RF%p z)sLMxAg+`xd;4$_99K(`+a(jDZkpKUtr|97=?haw23zZbyK4-!X19K;9ZPc0RQccL z55DKAx82K}dF1O@4;0GnC+iiy7*%{S@Q-oV+|%l8@10XAoG+f9c6#5coQJg=Qg`vC zx2d6-7H5u<-l@2m4{+v9XV^$_HmL_H!(Q~c!vAFL9baAczlzF+Z z!EEt3pGxW+!t|{M2lHcEqi?Lp-&&kKD%$(N9JM}ESV+;5k!ZqNn8GAL`zHWNfj8D2Gzkto_25`W5^WYF}?vG9QMZW#Bli@?jS4r_2e}8@WFB0wWWt-tk z8t*EX9_njb-`CaDnK@@_jnPBvem*dr;|K!Z(O>~QdAZFI8@j6k!B;zOmexz(N9GZK z4Vl-$qH-_dJFFM*+E_9lxB56a-%;y zP7+`BqqCYo;YjSVX0GgBzRE!!g~u9XhmUBWc09T3!o`mV%Gt9mzpEE>zzg3i%YB;i zPi|;*vyW1{YfLZw_BMT(JelTDll<%Cj{&~(o_FKNYHfd?3Emp0)O@lx^M0p|}ykEKzpU(8RD*FJeUVNn~-#B%6Sd+^KU%W&0#4&wxS zMktU_Tpj94Wm6e+-wz~}@!eiO$}60eO!-Op%@@|J52VkDB<~=Oum1JWYc4~_mlN-4 zXFzHNUu$Gtd_?ZWFE`)ysqDVT>-+fwMKG86C)ZO4Jg*jT0hJ3L@ATufxNMcvso?bU z`}}rUUS+E6tJEG7@U$v^PW)41UN9Isb@V+}|H(ansW?Bb7s?O6?MZN$zcI>xD94X> zIa{TAsu;1yjedv162H%ko1|y;=ROCQa!__Cm8C!A^GCb?;qy=|F`$Xj;9OA z_K^qUTzc^B?!2K4;*IHJ>gntE-{Zsl;e-FZ>y=A>&nZ9KU9W%S;~(x!&6|c`jpmCX zq`WvA=C?uSSTnCG|39zC{l4$>Bs;;(i4_VVg*oAGfoztJ_MC!sZ4WzzGXz#j}4 zPMpT0?5)~2`VZM+9P*$T%;eAHN%hLv%hWI5m)%I82LnT1gFQHjFwFmp=VfL=cuvf52mc9pIuh< z115IXQ~gd!<^J>egUpR&Ot&)mqYCv_55=S<@9eYT|&+sy0vg!bzi7m9txSV_c* z<4T0x;|uP@5xt%;add=@NRLqbBVkVRUAFP}Er8`o@TSvE8c;C(C^|8GN!wiDDftxg)05o7#yw`LTcb_>M!7!k4`9+ z*(dVr*7$ZqFJ@Z5=Tp0BVlg{y=szYVo9B(g=XNo;N#{3@)p>GlU#f>?YX2MggTdZp z#`x{43}Bt&&Jy#=_$h!Bu4AL|EZXc(PAhD*bNDNYAvL?SAH;MOzsc!M8)&CJMz6~= zNmnd0X6`%m>_+ZpfS>qp;t$dule!BR1wO621}C%$E;eUeqsi&+L2~S1v=>RAGYiHA znvZMz*3q@&+{d}KGUb^A=@8S zVu^8?d3M&=!r@=LUT4xsjymVt49s$N#ehit!nG;EaeRU8ira~`>J>)&Cd|2`TY9}h zoTKJf%8VWV(fwWgK{${lPvc;U`7g=~>;J-FPF^3lI+y z4NKt$-i339^as-eX(Ngo$YTPRFy(Dh*xMgymW2nO;}2?`CNX<_B^Er^KH1{vQ-@X* zquRL5oJqckV>}JkiD3-ix8u5c5ALI|RpEi4Ff6s099}(k3Fi$1HiO?@MYd{RXZ!uCHp5nu6KLk0M`6*Sdo!W)JWS_?DNMJ_prdph=cedfZPU* z*4r>|p~ty?^pDRLJ+>SuAEVjmF5r0G)RJ=Cpr}(;aA>|m`~5n9P~$>p3$qv3zMQt< zj2Cgr)~dNWlXvDF#bF-f%T}!s$22pn6FjkB@7!0#^rqO?+pt`%z&oHI7!bb88EBjs{7Oj{gYxl+%go;5FdhELDpr+A#N*67sfKjeGy z%%1y?tPQSSs^0BE=5zc(oy#z=D3*lI-==#>E9twxB_i8M)|o_OlUa0_?K|Cmd`BIR z8Y=4w*S*lTZZH?&VOHq5&kc^(((sfh4?DqJ#Leao*Lk?-G_fs&HmTukanECi!!I-t z;e4wO@d^H*&Pe8rr}Lf8s=U=h=YG!B`kT(Jj&widYbkCb_jb?(b>t^^4TlSX%5D`; zLw7!&-9hi{!q7%@lX$U%PS2Unie)eA93%T|oV~Aox_alE_;%=QuYMbU(AY^R;yZoW z#{6?G+Q28b+luXQSQjUlrRE3!4q9P{z4Ag6$9Ay^8*rg}Niq+or8_9x=Xdc3u_5>z zjh}WytuuPiEXQovaOYHpKg3o84A5ue+qp?GOYSDldf5_g$lfIv6Al8VW4`lS_=B=T znF;NyL4K4ce@q76c)^>a_QneY?x-BOW9C%c9Xq}0KIP*!4>Vy*Y!d!fj^D%|)KX$n z@F$;-_L!&Sqw~kU^e&e^KeR8|VlYrx^>@)&;F-U9ilK@QK=+lSVJPJ0^4s`>25+Po zKia_*igH|57xo-q3H;`}O$>hi@D#Og6gqm>KH?YKX=(RN$Jj?Eyxbz`o%R5ESPFl{=ducRV+OnmJd$0PNq|gtSjYFmi;1J&PRs*3gbiA z)CQjbKm7*&VCWD1NsU1{)I8+Cl;fQjGgqlCNJg)d=h-8*Y}Go;`zC$lGeEZs<9xta zG1HsD-}Jjboj<5~SLuyK@T@qmWJstnSf8J7<^4PT;WLit!Z%+Wp$tCDx$!lKs{-(A zFlE;7{{QC>nmrHy3H#Wd^!VDtX+1Yp^w;>$>^o^w%3(P>J-*6eKKpBwLAf;T!J3(Y z+ciDtq1UXTl;f||9$)29d%VlxpAG(6<@izeFRQ)R<7XTF1^X2|iYbmw(+mB|$LRiQ z{YqzNusP4GpY$*|oj&`kmBVm^0-?KGAL&6~VoxAk<#&#A&71fWiPLBN2f>`h!ISSL z=RutizicUL}GT!@~NL#_6;U)%3$W?-@+f{ zyo)cN>WzDv2YKG&W8qM#TbvYvnY0sg3-j5mduXk8&+I(scF^Rs;M-Y6@SZU^8(5Tk z=XGKBaFa*3Q*A%pX7Rd=S-*=v$UzWo8;80Vt}OpY_&LY=aDJN&;V~xhe+SouKj%r` z5T}!dhslU^CU)%?-5+gLd=Mf!sK6wzB=L)4TKvrzg;| z;z1f7Yq5*|*cy)49_~bs_;$hl(*7WS5I;eqS+4a}T=Q}~NZ!?#n4jXZVQ*8N4V*4C z)k1KZy_tz&X*c1brt;F%Bi7TbKe^+3_`>%x-F>Vc*0R}zG4^z8mBdkV+bN4*hE-ww zOz^SHfqfV|I~p?|;t#SW$fpPlhDAbffz~uFxYCENCv%7T1k9K`qsOfHnwl#Ou1}m$ zuQ~;bipI9f0C*HwR7n^VzR7XnX%k?y?WsL>$MvY2LRm(!}6n6BQO$f8!i7jNLp148J&+K+Y3 zHX4@eL*Mf*Lmx-mU{ENb+XwFsK_9vqcv=ZNi(+G4Mjzu3nyRM}OgXe}E0zy_syu@2E^STn!^gbjlK!O5ZC zACCJ%7t-6)r&2e?DMTL3`qPitol9U zHF~v9F%=4A-PO8!%QQU2pUMQxT@(%tH?|ZKj{VX?tKkU40iof2| z_a-a&o^x@GKFA*oPzLsUw1s^2^pCPopyV=nV}4;?Vr_($m+mqj!J`Xrkc-V% zB8g+Bf6N_POZ+ix%8_RLh|?0TeT+Z&){K1f*3_{yUpWzCf6i-KA6Uy-H}+_lulJW{ zEI675ttz^dNGdeP*Ii|u(*8ti2N+0BaVtwpZ@m#l*uA$WRw|rX)T6h-(`sUWt8u_}hb&{`s22JO>TKGWxno_?Sab@r3S|Do$1|{hwm~ zSc2Vk?wz^!%-0mBOWhPg8?#c&XJkbB$DK>gu`Z`1_LYbUP4sH{QL;ZAB?`HP8N2CU z)TLr`1KO*2>jLc?YrbRiN?u{BFWqjwJHs{dwQ+9t1%H!2sPbBjwPxI-g?C9mg?@dr aApK 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
  • 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 C2spU_ literal 0 HcmV?d00001 diff --git a/lab8/rootfs/dir/file1 b/lab8/rootfs/dir/file1 new file mode 100644 index 000000000..433eb1727 --- /dev/null +++ b/lab8/rootfs/dir/file1 @@ -0,0 +1 @@ +this is file1 diff --git a/lab8/rootfs/dir/file2.txt b/lab8/rootfs/dir/file2.txt new file mode 100644 index 000000000..f13882009 --- /dev/null +++ b/lab8/rootfs/dir/file2.txt @@ -0,0 +1 @@ +this is file2 diff --git a/lab8/rootfs/test3 b/lab8/rootfs/test3 new file mode 100644 index 000000000..e69de29bb diff --git a/lab8/rootfs/user_prog.img b/lab8/rootfs/user_prog.img new file mode 100755 index 0000000000000000000000000000000000000000..1adf648a4c1b9d2d1b5e9fa08b84e52ef2fafc78 GIT binary patch literal 24 gcmZQzXt>0{!Z4AMf#Hh02*bzK|Nn + +int debug = 1; + +// `bss_end` is defined in linker script +extern int __heap_top; +volatile char *heap_top; + +page* page_arr = 0; +uint64_t total_page = 0; +free_list_t free_list[MAX_ORDER + 1]; +chunk_info chunk_info_arr[MAX_CHUNK + 1]; + +int buddy(int idx) { + return idx ^ (1 << page_arr[idx].order); +} + +void page_info_addr(void* addr) { + unsigned int idx = ((unsigned long long)addr - (unsigned long long)PAGE_BASE) / PAGE_SIZE; + page_info(&page_arr[idx]); +} + +void page_info(page* p) { + uart_send_string("(addr: 0x"); + uart_hex((unsigned long long)p->addr); + uart_send_string(", idx: "); + uart_hex(p->idx); + uart_send_string(", val: "); + uart_hex(p->val); + uart_send_string(", order: "); + uart_hex(p->order); + uart_send_string(")"); +} + +void print_chunk_info() { + for(int i=0;ival != i) { + uart_send_string("Error: "); + page_info(p); + uart_send_string(" is in free_list["); + uart_hex(i); + uart_send_string("]\n"); + } + cnt ++; + p = p->next; + } + if(cnt != free_list[i].cnt) { + uart_send_string("Error: free_list["); + uart_hex(i); + uart_send_string("].cnt is "); + uart_hex(free_list[i].cnt); + uart_send_string(" but there are "); + uart_hex(cnt); + uart_send_string(" pages\n"); + } + } +} + +unsigned long long align_page(unsigned long long size) { + return (size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); +} + +int log2(unsigned long long size) { + int order = 0; + while (size > 1) { + size >>= 1; + order++; + } + return order; +} + +int is_page(void* addr) { + return (addr - PAGE_BASE) % PAGE_SIZE == 0; +} + +int size2chunkidx(unsigned long long size) { + // split to 6 size of chunk + // 16, 32, 64, 128, 256, 512 + if (size <= 16) { + return 0; + } + if (size <= 32) { + return 1; + } + if (size <= 64) { + return 2; + } + if (size <= 128) { + return 3; + } + if (size <= 256) { + return 4; + } + if(size <= 512){ + return 5; + } + return -1; // use buddy system instead +} + +void init_page_arr() { + total_page = ((uint64_t)PAGE_END - (uint64_t)PAGE_BASE) / (uint64_t)PAGE_SIZE; + if(0 && debug) { + uart_send_string("total_page: "); + uart_hex(total_page); + uart_send_string("\n"); + } + page_arr = (page*)simple_malloc(total_page * sizeof(page)); + char* addr = PAGE_BASE; + for (uint64_t i = 0; i < total_page; i++) { + page_arr[i].addr = addr; + page_arr[i].idx = i; + page_arr[i].val = ALLOCATED; + page_arr[i].order = 0; + page_arr[i].next = 0; // NULL + page_arr[i].prev = 0; // NULL + addr += PAGE_SIZE; + + if(0 && debug) { + page_info(&page_arr[i]); + uart_send_string("\n"); + } + } +} + +void init_chunk_info() { + for(int i=0;i<=MAX_CHUNK;i++) { + chunk_info_arr[i].idx = i; + chunk_info_arr[i].size = 1 << (i + 4); + chunk_info_arr[i].page_head = 0; + chunk_info_arr[i].chunk_head = 0; + chunk_info_arr[i].cnt = 0; + } + print_chunk_info(); +} + +void init_page_allocator() { + for (int i = 0; i < MAX_ORDER + 1; i++) { + free_list[i].head = 0; // NULL + free_list[i].cnt = 0; + } + for(uint64_t i=0;i val > 0){ + uart_send_string("Error: "); + page_info(r_page); + uart_send_string(" is already free\n"); + while(1); + } + + if(debug) { + uart_send_string("release "); + page_info(r_page); + uart_send_string("\n"); + } + int order = r_page -> order; + r_page->val = order; + insert_page(r_page, order); + merge(r_page); +} + +void merge(page* m_page) { + int a_idx = m_page->idx; + if(page_arr[a_idx].val < 0) { + uart_send_string("Error: "); + page_info(&page_arr[a_idx]); + uart_send_string(" is not free\n"); + while(1); + } + while(page_arr[a_idx].order + 1 < MAX_ORDER) { + int b_idx = buddy(a_idx); + if(buddy(b_idx) != a_idx + || page_arr[a_idx].val < 0 + || page_arr[b_idx].val < 0) { + break; + } + if(a_idx > b_idx) { + // swap a_idx and b_idx + int tmp = a_idx; + a_idx = b_idx; + b_idx = tmp; + } + page* a_page = &page_arr[a_idx]; + page* b_page = &page_arr[b_idx]; + if(0 && debug) { + uart_send_string("merge "); + page_info(a_page); + uart_send_string(" and "); + page_info(b_page); + uart_send_string("\n"); + } + if(b_page->order != a_page->order) { + uart_send_string("Error: "); + page_info(a_page); + uart_send_string(" and "); + page_info(b_page); + uart_send_string(" have different order\n"); + while(1); + } + // b_page becomes a_page's buddy + // b_page->order = a_page->order; + + // remove a_page and b_page from free_list + erase_page(a_page, a_page->order); + erase_page(b_page, b_page->order); + b_page->val = BUDDY; + + // a_page's order increases + a_page -> order++; + a_page -> val++; + // insert a_page to free_list + insert_page(a_page, a_page->order); + } +} + +page* truncate(page* t_page, int order) { + if(debug) { + uart_send_string("truncate "); + page_info(t_page); + uart_send_string(" to order "); + uart_hex(order); + uart_send_string("\n"); + } + int idx = t_page->idx; + while(t_page->order > order) { + t_page->order--; + int buddy_idx = buddy(idx); + page* buddy_page = &(page_arr[buddy_idx]); + buddy_page->val = ALLOCATED; + buddy_page->order = t_page->order; + + if(debug) { + // split page into two buddies + uart_send_string("split "); + page_info(t_page); + uart_send_string(" and "); + page_info(buddy_page); + uart_send_string("\n"); + } + release(buddy_page); + } + + return t_page; +} + +void insert_page(page* new_page, int order) { + if(new_page -> val < 0 || order < 0) { + uart_send_string("Error: insert_page "); + page_info(new_page); + uart_send_string(" with val < 0\n"); + while(1); + } + new_page->val = order; + new_page->order = order; + new_page->next = free_list[order].head; + if (free_list[order].head != 0) { + free_list[order].head -> prev = new_page; + } + free_list[order].head = new_page; + free_list[order].cnt++; + return; +} + +page* pop_page(int order) { + if(free_list[order].cnt == 0) { + uart_send_string("Error: pop_page from free_list["); + uart_hex(order); + uart_send_string("] with cnt = 0\n"); + while(1); + } + page* ret = free_list[order].head; + free_list[order].head = ret->next; + if (free_list[order].head -> next != 0) { + free_list[order].head -> prev = 0; + } + free_list[order].cnt--; + return ret; +} + +void erase_page(page* e_page, int order) { + if(e_page -> val < 0) { + uart_send_string("Error: erase_page "); + page_info(e_page); + uart_send_string(" with val < 0\n"); + while(1); + } + if(e_page -> order != order) { + uart_send_string("Error: erase_page "); + page_info(e_page); + uart_send_string(" with order "); + uart_hex(e_page->order); + uart_send_string(" but want to erase with order "); + uart_hex(order); + uart_send_string("\n"); + while(1); + } + if (e_page -> prev != 0) { + e_page -> prev->next = e_page -> next; + } + else { + free_list[order].head = e_page->next; + } + if (e_page->next != 0) { + e_page->next->prev = e_page->prev; + } + e_page->next = 0; + e_page->prev = 0; + free_list[order].cnt--; +} +void* chunk_alloc(int idx) { + int chunk_size = chunk_info_arr[idx].size; + if(debug) { + uart_send_string("chunk_alloc: "); + uart_hex(idx); + uart_send_string(" (size: "); + uart_hex(chunk_size); + uart_send_string(")\n"); + uart_send_string("\n"); + } + + + if(chunk_info_arr[idx].chunk_head == 0) { + if(debug) { + // no available chunk + uart_send_string("no available chunk\n"); + } + // create a new page + page_info_t* new_page = page_alloc(PAGE_SIZE); + new_page -> idx = idx; + + // insert new_page to page list + new_page -> next = chunk_info_arr[idx].page_head; + chunk_info_arr[idx].page_head = new_page; + + // split page to chunk + for( + unsigned char* addr = new_page + PAGE_SIZE - chunk_size; + addr > new_page + sizeof(page_info_t); + addr -= chunk_size + ) { + chunk* new_chunk = (chunk*)addr; + new_chunk -> addr = addr; + new_chunk -> next = chunk_info_arr[idx].chunk_head; + chunk_info_arr[idx].chunk_head = new_chunk; + chunk_info_arr[idx].cnt ++; + if(debug) { + // uart_hex(chunk_size); + uart_send_string("new chunk: 0x"); + uart_hex((unsigned long long)addr); + uart_send_string("\n"); + uart_hex((unsigned char*)new_page + sizeof(page_info_t)); + uart_send_string("\n"); + } + } + } + if(debug) { + // find available chunk + uart_send_string("find available chunk\n"); + // print addr + uart_send_string("chunk_info_arr["); + uart_hex(idx); + uart_send_string("].chunk_head->addr: 0x"); + uart_hex((unsigned long long)chunk_info_arr[idx].chunk_head->addr); + uart_send_string("\n"); + } + chunk* res_chunk = chunk_info_arr[idx].chunk_head; + chunk_info_arr[idx].chunk_head = res_chunk->next; + chunk_info_arr[idx].cnt --; + return (void*)res_chunk->addr; +} + +void* chunk_free(void* addr) { + if(debug) { + uart_send_string("Releasing chunk: 0x"); + uart_hex((unsigned long long)addr); + uart_send_string("\n"); + } + // convert chunk addr to page addr + void* page_addr = (void*)((uint64_t)addr & (~(PAGE_SIZE - 1))); + page_info_t* chunk_page = (page_info_t*)page_addr; + int idx = chunk_page -> idx; + chunk* new_chunk = (chunk*)addr; + new_chunk -> addr = addr; + // insert new_chunk to free list + new_chunk -> next = chunk_info_arr[idx].chunk_head; + chunk_info_arr[idx].chunk_head = new_chunk; + chunk_info_arr[idx].cnt ++; +} + +// Set heap base address +void alloc_init() +{ + heap_top = ((volatile unsigned char *)(0x10000000)); + char* old_heap_top = heap_top; + // uart_send_string("heap_top: "); + // uart_hex((unsigned long long)heap_top); + // uart_send_string("\n"); + init_page_arr(); + // reserve memory + memory_reserve((void*)0x0000, (void*)0x1000); // spin tables + memory_reserve((void*)ramfs_base, (void*)ramfs_end); // ramfs + // memory_reserve((void*)dtb_base, (void*)dtb_end); // dtb + + // kernel, bss, stack + // 0x80000 = _start + // 0x0200000 = __stack_end + memory_reserve((void*)0x80000, (void*)0x0200000); + + if(debug) { + uart_send_string("old_heap_top: "); + uart_hex((unsigned long)old_heap_top); + uart_send_string("\n"); + uart_send_string("heap_top: "); + uart_hex((unsigned long)heap_top); + uart_send_string("\n"); + } + // heap + memory_reserve((void*)old_heap_top, (void*)heap_top); + debug = 0; + init_page_allocator(); + // debug = 1; + check_free_list(); + // print the number of free pages for each order + // if(debug) { + // } + free_list_info(); + // init chunk info + init_chunk_info(); +} + +void memory_reserve(void* start, void* end) { + if(debug) { + uart_send_string("memory_reserve: "); + uart_hex((unsigned long)start); + uart_send_string(" ~ "); + uart_hex((unsigned long)end); + uart_send_string("\n"); + } + // Align start to the nearest page boundary (round down) + uint64_t aligned_start = (uint64_t)start & ~(PAGE_SIZE - 1); + // Align end to the nearest page boundary (round up) + uint64_t aligned_end = ((uint64_t)end + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); + + uint64_t s_idx = (aligned_start - (uint64_t)PAGE_BASE) / PAGE_SIZE; + uint64_t e_idx = (aligned_end - (uint64_t)PAGE_BASE) / PAGE_SIZE; + + for (uint64_t i = s_idx; i <= e_idx; i++) { + page_arr[i].val = RESERVED; + page_arr[i].order = 0; + } +} + +void* page_alloc(unsigned long long size) { + int order = log2(align_page(size) / PAGE_SIZE); + if(debug) { + uart_send_string("Requesting "); + uart_hex(size); + uart_send_string(" bytes, order: "); + uart_hex(order); + uart_send_string("\n"); + } + page* res_page = 0; + for(int i=order; i<=MAX_ORDER; i++) { + if(debug) { + uart_send_string("Checking free_list["); + uart_hex(i); + uart_send_string("] = "); + uart_hex(free_list[i].cnt); + uart_send_string("\n"); + } + if(free_list[i].cnt > 0) { + res_page = pop_page(i); + break; + } + } + if(!res_page){ + if(0 && debug) { + uart_send_string("No enough memory\n"); + } + return 0; + } + res_page -> val = ALLOCATED; + truncate(res_page, order); + if(debug) { + uart_send_string("Allocated "); + page_info(res_page); + uart_send_string("\n"); + } + return (void*)res_page->addr; +} + +void page_free(void* addr) { + unsigned int idx = ((unsigned long long)addr - (unsigned long long)PAGE_BASE) / PAGE_SIZE; + if(0 && debug) { + uart_send_string("0x"); + uart_hex((unsigned long long)addr); + uart_send_string(" -> "); + uart_hex(idx); + uart_send_string(" Freeing "); + page_info(&page_arr[idx]); + uart_send_string("\n"); + } + release(&page_arr[idx]); +} + +void* kmalloc(unsigned long long size) { + el1_interrupt_disable(); + if(size == 0){ + el1_interrupt_enable(); + return 0; + } + // void* addr = page_alloc(size); + // free_list_info(); + // return addr; + int idx = size2chunkidx(size); + + void* addr; + int use_page_only = 0; + if(use_page_only) { + addr = page_alloc(size); + } else if(idx >= 0) { + addr = chunk_alloc(idx); + } else { + addr = page_alloc(size); + } + el1_interrupt_enable(); + return addr; +} + +void kfree(void* addr) { + if(!addr) return; + el1_interrupt_disable(); + if(is_page(addr)) { + // uart_send_string("page addr release\n"); + page_free(addr); + } + else { + // uart_send_string("chunk addr release\n"); + chunk_free(addr); + } + // free_list_info(); + el1_interrupt_enable(); +} + +void *simple_malloc(unsigned long long size) +{ + void *p = (void *)heap_top; + heap_top += size; + return p; +} \ No newline at end of file diff --git a/lab8/src/boot.S b/lab8/src/boot.S new file mode 100644 index 000000000..596e47891 --- /dev/null +++ b/lab8/src/boot.S @@ -0,0 +1,182 @@ +#include "mm.h" +#define CORE0_TIMER_IRQ_CTRL 0x40000040 + +.section ".text.boot" + +.globl _start + +_start: + mrs x1, mpidr_el1 + and x1, x1,#0xFF // Check processor id + cbz x1, master // Hang for all non-primary CPU + +proc_hang: + wfe + b proc_hang + +master: + bl from_el2_to_el1 + +// core_timer_enable: +// mov x20, 1 +// msr cntp_ctl_el0, x20 // enable timer +// mrs x20, cntfrq_el0 +// msr cntp_tval_el0, x20 // set expired time +// mov x20, 2 +// ldr x1, =CORE0_TIMER_IRQ_CTRL +// str w20, [x1] // unmask timer interrupt + +set_exception_vector: + // set exception vector table + adr x20, exception_vector_table + msr vbar_el1, x20 // vector base address register + +set_stack: + ldr x1, =__stack_end // __stack_end = 0x0200000 + mov sp, x1 + ldr x1, =bss_begin + ldr w2, =__bss_size + +bss_reset: + cbz w2, run_main + // save 0 and plus 8 + str xzr, [x1], #8 + sub w2, w2, #1 + cbnz w2, bss_reset + +run_main: + bl main + b proc_hang // should never come here + + +from_el2_to_el1: + mov x1, (1 << 31) // EL1 uses aarch64 + msr hcr_el2, x1 + // 0x3c5 = 0b1111000101 + // 9876543210 + // DAIFRM + // 0x345 = 0b1101000101 + // 9876543210 + // DAIFRM + // M[3:0] = 0101 = EL1h + mov x1, 0x345 // EL1h (SPSel = 1) with interrupt disabled + msr spsr_el2, x1 + msr elr_el2, lr + eret // return to EL1 + + +// save general registers to stack +.macro save_all + sub sp, sp, 32*9 + stp x0, x1, [sp, 16*0] + stp x2, x3, [sp, 16*1] + stp x4, x5, [sp, 16*2] + stp x6, x7, [sp, 16*3] + stp x8, x9, [sp, 16*4] + stp x10, x11, [sp, 16*5] + stp x12, x13, [sp, 16*6] + stp x14, x15, [sp, 16*7] + stp x16, x17, [sp, 16*8] + stp x18, x19, [sp, 16*9] + stp x20, x21, [sp, 16*10] + stp x22, x23, [sp, 16*11] + stp x24, x25, [sp, 16*12] + stp x26, x27, [sp, 16*13] + stp x28, x29, [sp, 16*14] + mrs x0, spsr_el1 + stp x30, x0, [sp, 16*15] + mrs x0, elr_el1 + mrs x1, sp_el0 + stp x0, x1, [sp, 16*16] +.endm + +// load general registers from stack +.macro load_all + ldp x0, x1, [sp, 16*16] + msr elr_el1, x0 + msr sp_el0, x1 + ldp x30, x0, [sp, 16*15] + msr spsr_el1, x0 + ldp x0, x1, [sp, 16*0] + ldp x2, x3, [sp, 16*1] + ldp x4, x5, [sp, 16*2] + ldp x6, x7, [sp, 16*3] + ldp x8, x9, [sp, 16*4] + ldp x10, x11, [sp, 16*5] + ldp x12, x13, [sp, 16*6] + ldp x14, x15, [sp, 16*7] + ldp x16, x17, [sp, 16*8] + ldp x18, x19, [sp, 16*9] + ldp x20, x21, [sp, 16*10] + ldp x22, x23, [sp, 16*11] + ldp x24, x25, [sp, 16*12] + ldp x26, x27, [sp, 16*13] + ldp x28, x29, [sp, 16*14] + add sp, sp, 32*9 +.endm + +exception_handler: + save_all + bl exception_handler_c + load_all + eret + +irq_exception_handler: + save_all + bl irq_exception_handler_c + load_all + eret + +user_exception_handler: + save_all + mov x0, sp // trapframe store in the top of kernel stack + bl user_exception_handler_c + load_all + eret + +user_irq_exception_handler: + save_all + bl user_irq_exception_handler_c + load_all + eret + +// EL1 exception vector table +.align 11 // vector table should be aligned to 0x800 +.global exception_vector_table +exception_vector_table: + b exception_handler // branch to a handler function. + .align 7 // entry size is 0x80, .align will pad 0 + b exception_handler + .align 7 + b exception_handler + .align 7 + b exception_handler + .align 7 + + b exception_handler + .align 7 + b irq_exception_handler + .align 7 + b exception_handler + .align 7 + b exception_handler + .align 7 + + b user_exception_handler + .align 7 + b user_irq_exception_handler + .align 7 + b exception_handler + .align 7 + b exception_handler + .align 7 + + b exception_handler + .align 7 + b exception_handler + .align 7 + b exception_handler + .align 7 + b exception_handler + .align 7 + diff --git a/lab8/src/c_utils.c b/lab8/src/c_utils.c new file mode 100644 index 000000000..58abc19e9 --- /dev/null +++ b/lab8/src/c_utils.c @@ -0,0 +1,66 @@ +#include "c_utils.h" +#include "mini_uart.h" +#include "string.h" + +void uart_recv_command(char *str){ + char c; + int i = 0; + while(1){ + c = uart_recv(); + if(c == '\r'){ + str[i] = '\0'; + break; + } else if(c == 127 || c == 8){ + if(i > 0){ + i--; + uart_send('\b'); + uart_send(' '); + uart_send('\b'); + } + continue; + } + if(is_visible(c)){ + str[i] = c; + i++; + uart_send(c); + } + } + +} + +int align4(int n) +{ + return n + (4 - n % 4) % 4; +} + + +int atoi(const char *s){ + int sign = 1; + int i = 0; + int result = 0; + + while(s[i] == ' ') + i ++; + + if(s[i] == '-') { + sign = -1; + i++; + } + + while(s[i] >= '0' && s[i] <= '9') { + result = result * 10 + (s[i] - '0'); + i ++; + } + + return sign * result; +} + +unsigned int endian_big2little(unsigned int x) { + return ((x & 0xFF) << 24) | ((x & 0xFF00) << 8) | ((x & 0xFF0000) >> 8) | ((x & 0xFF000000) >> 24); +} + +void delay (unsigned int loop) { + while (loop --) { + asm volatile("nop"); + } +} diff --git a/lab8/src/context_switch.S b/lab8/src/context_switch.S new file mode 100644 index 000000000..467c1c9dc --- /dev/null +++ b/lab8/src/context_switch.S @@ -0,0 +1,51 @@ +.global switch_to +switch_to: + stp x19, x20, [x0, 16 * 0] + stp x21, x22, [x0, 16 * 1] + stp x23, x24, [x0, 16 * 2] + stp x25, x26, [x0, 16 * 3] + stp x27, x28, [x0, 16 * 4] + stp fp, lr, [x0, 16 * 5] + mov x9, sp + str x9, [x0, 16 * 6] + + ldp x19, x20, [x1, 16 * 0] + ldp x21, x22, [x1, 16 * 1] + ldp x23, x24, [x1, 16 * 2] + ldp x25, x26, [x1, 16 * 3] + ldp x27, x28, [x1, 16 * 4] + ldp fp, lr, [x1, 16 * 5] + ldr x9, [x1, 16 * 6] + mov sp, x9 + msr tpidr_el1, x1 + ret + + +.global save_regs +save_regs: + stp x19, x20, [x0, 16 * 0] + stp x21, x22, [x0, 16 * 1] + stp x23, x24, [x0, 16 * 2] + stp x25, x26, [x0, 16 * 3] + stp x27, x28, [x0, 16 * 4] + stp fp, lr, [x0, 16 * 5] + mov x9, sp + str x9, [x0, 16 * 6] + ret + +.global load_regs +load_regs: + ldp x19, x20, [x0, 16 * 0] + ldp x21, x22, [x0, 16 * 1] + ldp x23, x24, [x0, 16 * 2] + ldp x25, x26, [x0, 16 * 3] + ldp x27, x28, [x0, 16 * 4] + ldp fp, lr, [x0, 16 * 5] + ldr x9, [x0, 16 * 6] + mov sp, x9 + ret + +.global get_current_thread +get_current_thread: + mrs x0, tpidr_el1 + ret \ No newline at end of file diff --git a/lab8/src/exception.c b/lab8/src/exception.c new file mode 100644 index 000000000..e39ca1baf --- /dev/null +++ b/lab8/src/exception.c @@ -0,0 +1,290 @@ +#include + +#include "exception.h" +#include "mini_uart.h" +#include "peripherals/irq.h" +#include "timer.h" +#include "tasklist.h" +#include "syscall.h" +#include "alloc.h" +#include "thread.h" +#include "reboot.h" +#include "signal.h" +#include "fs_vfs.h" +#include "c_utils.h" + +extern timer_t *timer_head; + +void el1_interrupt_enable(){ + asm volatile ("msr daifclr, 0xf"); // umask all DAIF +} + +void el1_interrupt_disable(){ + asm volatile ("msr daifset, 0xf"); // mask all DAIF +} + +void exception_handler_c() { + uart_send_string("Exception Occurs!\n"); + + //read spsr_el1 + unsigned long long spsr_el1 = 0; + asm volatile("mrs %0, spsr_el1":"=r"(spsr_el1)); + uart_send_string("spsr_el1: "); + uart_hex(spsr_el1); + uart_send_string("\n"); + + //read elr_el1 + unsigned long long elr_el1 = 0; + asm volatile("mrs %0, elr_el1":"=r"(elr_el1)); + uart_send_string("elr_el1: "); + uart_hex(elr_el1); + uart_send_string("\n"); + + //esr_el1 + unsigned long long esr_el1 = 0; + asm volatile("mrs %0, esr_el1":"=r"(esr_el1)); + uart_send_string("esr_el1: "); + uart_hex(esr_el1); + uart_send_string("\n"); + + //ec + unsigned ec = (esr_el1 >> 26) & 0x3F; //0x3F = 0b111111(6) + uart_send_string("ec: "); + uart_hex(ec); + uart_send_string("\n"); + // while(1); + reset(1); +} + +void user_irq_exception_handler_c() { + // uart_send_string("User IRQ Exception Occurs!\n"); + el1_interrupt_disable(); + unsigned int irq = *IRQ_PENDING_1; + unsigned int interrupt_source = *CORE0_INTERRUPT_SOURCE; + + if((irq & IRQ_PENDING_1_AUX_INT) && (interrupt_source & INTERRUPT_SOURCE_GPU)){ + // uart_send_string("UART interrupt\n"); + uart_irq_handler(); + } + if(interrupt_source & INTERRUPT_SOURCE_CNTPNSIRQ) { + core_timer_disable(); + create_task(user_irq_timer_exception, 10); + execute_tasks_preemptive(); + } + check_and_run_signal(); + schedule(); +} + +void irq_exception_handler_c(){ + // uart_send_string("IRQ Exception Occurs!\n"); + unsigned int irq = *IRQ_PENDING_1; + unsigned int interrupt_source = *CORE0_INTERRUPT_SOURCE; + + if((irq & IRQ_PENDING_1_AUX_INT) && (interrupt_source & INTERRUPT_SOURCE_GPU) ){ + // uart_send_string("kernel UART interrupt\n"); + uart_irq_handler(); + } + else if(interrupt_source & INTERRUPT_SOURCE_CNTPNSIRQ) { + // uart_send_string("\nkernel Timer interrupt"); + core_timer_disable(); + create_task(irq_timer_exception, 10); + execute_tasks_preemptive(); + + } + check_and_run_signal(); + schedule(); +} + +void user_exception_handler_c(trapframe_t* tf) { + // check svc syscall from el0 + unsigned long long esr_el1 = 0; + asm volatile("mrs %0, esr_el1":"=r"(esr_el1)); + unsigned ec = (esr_el1 >> 26) & 0x3F; + + if(ec != 0x15) { + exception_handler_c(); + return; + } + int print_info = 0; + int syscall_code = tf -> x[8]; + el1_interrupt_enable(); + switch (syscall_code) { + case 0: + tf -> x[0] = getpid(); + break; + case 1: + tf -> x[0] = uart_read(tf -> x[0], tf -> x[1]); + break; + case 2: + tf -> x[0] = uart_write(tf -> x[0], tf -> x[1]); + break; + case 3: + tf -> x[0] = exec((const char*)tf -> x[0], tf->x[1]); + break; + case 4: + if(print_info) uart_send_string("[INFO] system call: fork\n"); + core_timer_disable(); + // el1_interrupt_disable(); + fork(tf); + // el1_interrupt_enable(); + core_timer_enable(); + break; + case 5: + exit(0); + break; + case 6: + tf -> x[0] = mbox_call( + (unsigned char)tf -> x[0], (unsigned int*)tf -> x[1] + ); + break; + case 7: + kill((int)tf -> x[0]); + break; + case 8: + signal((int)tf -> x[0], (void*)tf -> x[1]); + break; + case 9: + posix_kill((int)tf -> x[0], (int)tf -> x[1]); + break; + case 11: + if(print_info) uart_send_string("[INFO] system call: open\n"); + syscall_open(tf, (const char*)tf -> x[0], tf -> x[1]); + break; + case 12: + if(print_info) uart_send_string("[INFO] system call: close\n"); + syscall_close(tf, tf -> x[0]); + break; + case 13: + if(print_info) uart_send_string("[INFO] system call: write\n"); + // core_timer_disable(); + // el1_interrupt_disable(); + syscall_write(tf, tf -> x[0], (const void*)tf -> x[1], tf -> x[2]); + // el1_interrupt_enable(); + // core_timer_enable(); + break; + case 14: + if(print_info) uart_send_string("[INFO] system call: read\n"); + syscall_read(tf, tf -> x[0], (void*)tf -> x[1], tf -> x[2]); + break; + case 15: + if(print_info) uart_send_string("[INFO] system call: mkdir\n"); + syscall_mkdir(tf, (const char*)tf -> x[0], tf -> x[1]); + break; + case 16: + if(print_info) uart_send_string("[INFO] system call: mount\n"); + syscall_mount( + tf, + (const char*)tf -> x[0], // ignore source + (const char*)tf -> x[1], + (const char*)tf -> x[2], + 0, // ignore flags + 0 // ignore data + ); + break; + case 17: + if(print_info) uart_send_string("[INFO] system call: chdir\n"); + syscall_chdir(tf, (const char*)tf -> x[0]); + break; + case 18: + if(print_info) uart_send_string("[INFO] system call: lseek64\n"); + // core_timer_disable(); + // el1_interrupt_disable(); + syscall_lseek64(tf, tf -> x[0], tf -> x[1], tf -> x[2]); + // el1_interrupt_enable(); + // core_timer_enable(); + break; + case 19: + if(print_info) uart_send_string("[INFO] system call: ioctl\n"); + syscall_ioctl( + tf, + tf -> x[0], + tf -> x[1], + tf -> x[2], + tf -> x[3], + tf -> x[4], + tf -> x[5] + ); + break; + case 20: + sigreturn(); + break; + } + el1_interrupt_disable(); + // uart_send_string("sysre\n"); + return; +} + +void irq_timer_exception(){ + // uart_send_string("enter timer\n"); + core_timer_disable(); + while(timer_head){ + unsigned long long current_time; + asm volatile("mrs %0, cntpct_el0":"=r"(current_time)); + if(timer_head -> timeout <= current_time) { + timer_t *timer = timer_head; + timer -> callback(timer -> data); + timer_head = timer_head -> next; + kfree(timer); + } else { + break; + } + } + // uart_send_string("exit timer\n"); + if(timer_head){ + // uart_send_string("have timer\n"); + asm volatile("msr cntp_ctl_el0, %0"::"r"(1)); + asm volatile("msr cntp_cval_el0, %0"::"r"(timer_head -> timeout)); + } else { + // uart_send_string("no timer\n"); + asm volatile("msr cntp_ctl_el0, %0" :: "r"(0)); + } + core_timer_enable(); +} + +void user_irq_timer_exception(){ + // uart_send_string("enter timer\n"); + core_timer_disable(); + while(timer_head){ + unsigned long long current_time; + asm volatile("mrs %0, cntpct_el0":"=r"(current_time)); + if(timer_head -> timeout <= current_time) { + timer_t *timer = timer_head; + timer -> callback(timer -> data); + timer_head = timer_head -> next; + kfree(timer); + } else { + break; + } + } + if(timer_head){ + asm volatile("msr cntp_ctl_el0, %0"::"r"(1)); + asm volatile("msr cntp_cval_el0, %0"::"r"(timer_head -> timeout)); + } else { + asm volatile("msr cntp_ctl_el0, %0" :: "r"(0)); + } + core_timer_enable(); +} + + +void core_timer_init() { + asm volatile("msr cntp_ctl_el0, %0"::"r"(1)); + uint64_t freq; + asm volatile("mrs %0, cntfrq_el0" : "=r"(freq)); + freq = freq << 5; + asm volatile("msr cntp_tval_el0, %0" : : "r"(freq)); + + *CORE0_TIMER_IRQ_CTRL = 2; + + uint64_t tmp; + asm volatile("mrs %0, cntkctl_el1" : "=r"(tmp)); + tmp |= 1; + asm volatile("msr cntkctl_el1, %0" : : "r"(tmp)); +} + +void core_timer_enable(){ + *CORE0_TIMER_IRQ_CTRL = 2; +} + +void core_timer_disable() { + *CORE0_TIMER_IRQ_CTRL = 0; +} \ No newline at end of file diff --git a/lab8/src/fdt.c b/lab8/src/fdt.c new file mode 100644 index 000000000..89d1054d3 --- /dev/null +++ b/lab8/src/fdt.c @@ -0,0 +1,98 @@ +#include "fdt.h" + +char* dtb_base; +char* dtb_end; + +void fdt_traverse(dtb_callback callback) +{ + struct fdt_header *header = (struct fdt_header *)dtb_base; + dtb_end = dtb_base + endian_big2little(header->totalsize); + // fdt header magic 0xD00DFEED (big-endian) + if (endian_big2little(header->magic) != 0xD00DFEED) + { + uart_send_string("fdt_traverse: wrong magic in fdt_traverse\n"); + uart_send_string("expect: 0XD00DFEED\nget:"); + uart_hex(endian_big2little(header->magic)); + uart_send_string("\n"); + return; + } + + // length in bytes of structure block section of dtb + unsigned int struct_size = endian_big2little(header->size_dt_struct); + + // check hackmd notes about the picture of DTB structure + // header is address of fdt_header, so we need (char *) + // offset in bytes of the structure block from beginning of header + // to locate struct start + char *dt_struct_ptr = (char *)((char *)header + endian_big2little(header->off_dt_struct)); + // offset in bytes of strings block from beginning of header + // to locate string start + // fdt_prop use string_ptr + nameoff to get the pathname + char *dt_strings_ptr = (char *)((char *)header + endian_big2little(header->off_dt_strings)); + + // parse from struct begin to end + char *end = (char *)dt_struct_ptr + struct_size; + char *pointer = dt_struct_ptr; + + // for the later parsing + unsigned int len; + char* name; + + // according to lexical structure + while (pointer < end) + { + // lexical big-endian-32-bit integer + // all tokens shall be alligned on 32-bit boundary + unsigned int token_type = endian_big2little(*(unsigned int *)pointer); + pointer += 4; + + // lexical structure + switch (token_type) + { + // begin of node's representation + case FDT_BEGIN_NODE: + // move node's unit name + // string end \0 + pointer += strlen(pointer); + // node name is followed by zeroed padding bytes + // allign + pointer += (4 - (unsigned long long)pointer % 4); + break; + + // end of node's representation + case FDT_END_NODE: + break; + + case FDT_PROP: + + // len | name offset | address + // uint32_t + // length of prop values in byte + len = endian_big2little(*(unsigned int *)pointer); + pointer += 4; + + // nameoff save offset of string blocks + // strings_ptr + nameoff get the name + name = (char *)dt_strings_ptr + endian_big2little(*(unsigned int *)pointer); + pointer += 4; + + // check node is initrd-start/end and set cpio_start/end address + callback(token_type, name, pointer, len); + // address, byte string of length len + pointer += len; + // followed by zeroed padding bytes + if ((unsigned long long)pointer % 4 != 0) + pointer += 4 - (unsigned long long)pointer % 4; // alignment 4 byte + break; + // ** cant skip + // ignore NOP + case FDT_NOP: + break; + // marks end of structures block + case FDT_END: + break; + default: + return; + } + } +} diff --git a/lab8/src/fs_cpiofs.c b/lab8/src/fs_cpiofs.c new file mode 100644 index 000000000..4b45e61b4 --- /dev/null +++ b/lab8/src/fs_cpiofs.c @@ -0,0 +1,500 @@ +#include "fs_cpio.h" +#include "initrd.h" +#include "c_utils.h" +#include "mini_uart.h" + +vnode cpio_root_node; +vnode mount_old_node; +int cpio_mounted; + +// define struct +filesystem static_cpiofs = { + .name = "cpiofs", + .mount = cpiofs_mount + // don't need alloc vnode because this fs is read-only +}; + +struct vnode_operations cpiofs_v_ops = { + .lookup = cpiofs_lookup, + .create = cpiofs_create, + .mkdir = cpiofs_mkdir, + .isdir = cpiofs_isdir, + .getname = cpiofs_getname, + .getsize = cpiofs_getsize, +}; + +struct file_operations cpiofs_f_ops = { + .write = cpiofs_write, + .read = cpiofs_read, + .open = cpiofs_open, + .close = cpiofs_close, + .lseek64 = cpiofs_lseek64, + .ioctl = cpiofs_ioctl, +}; + + +// methods +int cpiofs_mount(filesystem *fs, mount *mnt) { + vnode *backup_node, *cur_node; + cpiofs_internal *internal; + const char* name; + + if(cpio_mounted) { + uart_send_string("cpiofs_mount: already mounted\n"); + return -1; + } + + cur_node = mnt->root; + + cur_node -> v_ops -> getname(cur_node, &name); + + internal = cpio_root_node.internal; + + internal -> name = name; + + mount_old_node.mount = cur_node -> mount; + mount_old_node.v_ops = cur_node -> v_ops; + mount_old_node.f_ops = cur_node -> f_ops; + mount_old_node.internal = cur_node -> internal; + mount_old_node.parent = cur_node -> parent; + + cur_node -> mount = mnt; + cur_node -> v_ops = cpio_root_node.v_ops; + cur_node -> f_ops = cpio_root_node.f_ops; + cur_node -> internal = internal; + + cpio_mounted = 1; + + return 0; +} + +int cpiofs_lookup(vnode *dir_node, vnode **target, const char *component_name) { + cpiofs_internal *internal, *entry; + + internal = dir_node -> internal; + + if(internal -> type != CPIOFS_TYPE_DIR) { + uart_send_string("cpiofs_lookup: not a directory\n"); + return -1; + } + + list_for_each_entry(entry, &internal -> dir.list, list) { + uart_send_string("cpiofs_lookup: entry -> name: "); + uart_send_string(entry -> name); + uart_send_string("\n"); + if(strcmp(entry -> name, component_name) == 0) { + break; + } + } + + if(&entry -> list == &internal -> dir.list) { + uart_send_string("cpiofs_lookup: file not found\n"); + return -1; + } + char* name; + uart_send_string("entry -> name: "); + uart_send_string(entry -> name); + uart_send_string("\n"); + uart_send_string("entry -> internal -> name: "); + entry -> node -> v_ops -> getname(entry -> node, &name); + uart_send_string(name); + uart_send_string("\n"); + + *target = entry -> node; + + return 0; +} + +int cpiofs_create(vnode *dir_node, vnode **target, const char *component_name) { + return -1; +} + +int cpiofs_mkdir(vnode *dir_node, vnode **target, const char *component_name) { + return -1; +} + +int cpiofs_isdir(vnode *dir_node) { + cpiofs_internal *internal; + + internal = dir_node -> internal; + + if (internal -> type != CPIOFS_TYPE_DIR) { + return 0; + } + + return 1; +} + +int cpiofs_getname(vnode *dir_node, const char **name) { + cpiofs_internal *internal = dir_node -> internal; + *name = internal -> name; + + return 0; +} + +int cpiofs_getsize(vnode *dir_node) { + cpiofs_internal *internal = dir_node -> internal; + if (internal -> type == CPIOFS_TYPE_DIR) { + uart_send_string("cpiofs_getsize: is a directory\n"); + return -1; + } + + return internal -> file.size; +} + +int cpiofs_open(vnode *file_node, file *target) { + target -> f_ops = file_node -> f_ops; + target -> vnode = file_node; + target -> f_pos = 0; + + return 0; +} + +int cpiofs_close(file *target) { + target -> f_ops = NULL; + target -> vnode = NULL; + target -> f_pos = 0; + + return 0; +} + +int cpiofs_write(file *target, const void *buf, size_t len) { + return -1; +} + +int cpiofs_read(file *target, void *buf, size_t len) { + cpiofs_internal* internal = target -> vnode -> internal; + uart_send_string("cpiofs_read: internal -> name: "); + uart_send_string(internal -> name); + uart_send_string("\n"); + uart_send_string("cpiofs_read: internal -> type: "); + uart_hex(internal -> type); + uart_send_string("\n"); + if(internal -> type != CPIOFS_TYPE_FILE) { + uart_send_string("cpiofs_read: not a file\n"); + return -1; + } + + if(len > internal -> file.size - target -> f_pos) { + len = internal -> file.size - target -> f_pos; + } + + if(!len) { + return 0; + } + memcpy(buf, internal -> file.data + target -> f_pos, len); + target -> f_pos += len; + + return len; +} + +long cpiofs_lseek64(file *target, long offset, int whence) { + int filesize; + int base; + + filesize = target -> vnode -> v_ops -> getsize(target -> vnode); + + if (filesize < 0) { + return -1; + } + + switch(whence) { + case SEEK_SET: + base = 0; + break; + case SEEK_CUR: + base = target -> f_pos; + break; + case SEEK_END: + base = filesize; + break; + default: + return -1; + } + + if(base + offset > filesize) { + return -1; + } + + target -> f_pos = base + offset; + + return 0; +} + +int cpiofs_ioctl(struct file *file, uint64_t request, va_list args) { + return -1; +} + +uint32_t cpio_read_8hex(const char *s) { + int r = 0; + int n = 8; + while (n-- > 0) { + r = r << 4; + if (*s >= 'A') + r += *s++ - 'A' + 10; + else if (*s >= '0') + r += *s++ - '0'; + } + return r; +} + +vnode *cpio_get_vnode_from_path(vnode *dir_node, const char **name) { + vnode* result; + const char* start; + const char* end; + char buf[256]; + + start = end = *name; + + if(*start == '/') { + result = &cpio_root_node; + } else { + result = dir_node; + } + + while(1) { + if(!strncmp("./", start, 2)) { + start += 2; + end = start; + continue; + } else if(!strncmp("../", start, 3)) { + if(result -> parent) { + result = result -> parent; + } + start += 3; + end = start; + continue; + } + + while(*end != '\0' && *end != '/') { + end++; + } + + if(*end == '/') { + int ret; + + if(start == end) { + end ++; + start = end; + continue; + } + + memcpy(buf, start, end - start); + buf[end - start] = '\0'; + + ret = result -> v_ops -> lookup(result, &result, buf); + uart_send_string("cpio_get_vnode_from_path lookup ret: "); + uart_hex(ret); + uart_send_string("\n"); + if(ret < 0) { + return 0; + } + + end ++; + start = end; + } else { + break; + } + } + + *name = *start ? start : 0; + + return result; +} + +void cpio_init_mkdir(const char *pathname) { + char* curname; + vnode *dir_node; + vnode *new_dir_node; + cpiofs_internal *internal; + cpiofs_internal *new_internal; + // use kmalloc to allocate memory + curname = (char *)kmalloc(strlen(pathname) + 1); + strcpy(curname, pathname); + dir_node = cpio_get_vnode_from_path(&cpio_root_node, &curname); + + if(!dir_node) { + uart_send_string("cpio_init_mkdir: directory not found\n"); + return; + } + + if(!curname) { + uart_send_string("cpio_init_mkdir: invalid pathname\n"); + return; + } + + internal = dir_node -> internal; + + uart_send_string("cpio_init_mkdir: internal -> name: "); + if(!internal -> name) { + uart_send_string("/"); + } else { + uart_send_string(internal -> name); + } + uart_send_string("\n"); + + if(internal -> type != CPIOFS_TYPE_DIR) { + uart_send_string("cpio_init_mkdir: not a directory\n"); + return; + } + + new_internal = (cpiofs_internal *)kmalloc(sizeof(cpiofs_internal)); + new_dir_node = (vnode *)kmalloc(sizeof(vnode)); + uart_send_string("cpio_init_mkdir: new_internal -> name: "); + uart_send_string(curname); + uart_send_string("\n"); + uart_send_string("new_dir_node addr: "); + uart_hex((unsigned long)new_dir_node); + uart_send_string("\n"); + new_internal -> name = curname; + new_internal -> type = CPIOFS_TYPE_DIR; + INIT_LIST_HEAD(&new_internal -> dir.list); + new_internal -> node = new_dir_node; + list_add_tail(&new_internal -> list, &internal -> dir.list); + + new_dir_node -> mount = 0; + new_dir_node -> v_ops = &cpiofs_v_ops; + new_dir_node -> f_ops = &cpiofs_f_ops; + new_dir_node -> internal = new_internal; + new_dir_node -> parent = dir_node; + + uart_send_string("[success] cpio_init_mkdir: create directory "); + uart_send_string(curname); + uart_send_string("\n"); + + return; +} + +void cpio_init_create(const char* pathname, const char *data, uint64_t size) { + char *curname; + vnode *dir_node; + vnode *new_file_node; + cpiofs_internal *internal, *new_internal; + + curname = (char *)kmalloc(strlen(pathname) + 1); + strcpy(curname, pathname); + dir_node = cpio_get_vnode_from_path(&cpio_root_node, &curname); + + if(!dir_node){ + uart_send_string("cpio_init_create: directory not found\n"); + return; + } + if(!curname) { + uart_send_string("cpio_init_create: invalid pathname\n"); + return; + } + + char *dir_name = (char*)kmalloc(256); + dir_node -> v_ops -> getname(dir_node, &dir_name); + + + internal = dir_node -> internal; + + if(internal -> type != CPIOFS_TYPE_DIR) { + uart_send_string("cpio_init_create: not a directory\n"); + return; + } + + uart_send_string("cpio_init_create: internal -> name: "); + if(!internal -> name) { + uart_send_string("/"); + } else { + uart_send_string(internal -> name); + } + uart_send_string("\n"); + + + new_internal = (cpiofs_internal *)kmalloc(sizeof(cpiofs_internal)); + new_file_node = (vnode *)kmalloc(sizeof(vnode)); + + new_internal -> name = curname; + new_internal -> type = CPIOFS_TYPE_FILE; + new_internal -> file.data = data; + new_internal -> file.size = size; + new_internal -> node = new_file_node; + list_add_tail(&new_internal -> list, &internal -> dir.list); + + new_file_node -> mount = 0; + new_file_node -> v_ops = &cpiofs_v_ops; + new_file_node -> f_ops = &cpiofs_f_ops; + new_file_node -> internal = new_internal; + new_file_node -> parent = dir_node; + + uart_send_string("[success] cpio_init_create: create file "); + uart_send_string(curname); + uart_send_string("\n"); + + return; +} + +filesystem *cpiofs_init(void) { + cpiofs_internal *internal; + + internal = (cpiofs_internal *)kmalloc(sizeof(cpiofs_internal)); + + internal -> name = 0; + internal -> type = CPIOFS_TYPE_DIR; + INIT_LIST_HEAD(&internal -> dir.list); + internal -> node = &cpio_root_node; + INIT_LIST_HEAD(&internal -> list); + + cpio_root_node.mount = 0; + cpio_root_node.v_ops = &cpiofs_v_ops; + cpio_root_node.f_ops = &cpiofs_f_ops; + cpio_root_node.internal = internal; + cpio_root_node.parent = 0; + + cpio_t* header = ramfs_base; + + while(header) { + char *component_name, *data; + uint32_t namesize, filesize, aligned_namesize, aligned_filesize, type; + + if(strncmp(header -> c_magic, "070701", sizeof(header->c_magic)) != 0) { + uart_send_string("cpiofs_init: invalid cpio format\n"); + break; + } + + namesize = cpio_read_8hex(header -> c_namesize); + filesize = cpio_read_8hex(header -> c_filesize); + type = cpio_read_8hex(header -> c_mode) & CPIO_TYPE_MASK; + + component_name = ((char *)header) + sizeof(cpio_t); + + aligned_namesize = align4(namesize); + aligned_filesize = align4(filesize); + unsigned int offset = namesize + sizeof(cpio_t); + offset = offset % 4 == 0 ? offset : (offset + 4 - offset % 4); // padding + + + // uart_send_string("file name: "); + // uart_send_string(component_name); + // uart_send_string("\n"); + + if (strncmp(component_name, "TRAILER!!!", sizeof("TRAILER!!!")) == 0) { + break; + } + + data = (char*)header + offset; + if(filesize == 0) { + header = (cpio_t *)data; + } else { + offset = filesize; + header = (cpio_t *)(data + (offset % 4 == 0 ? offset : (offset + 4 - offset % 4))); + } + + if(type == CPIO_TYPE_FILE) { + uart_send_string("cpiofs_init: create file "); + uart_send_string(component_name); + uart_send_string("\n"); + cpio_init_create(component_name, data, filesize); + } else if(type == CPIO_TYPE_DIR) { + uart_send_string("cpiofs_init: mkdir "); + uart_send_string(component_name); + uart_send_string("\n"); + cpio_init_mkdir(component_name); + } + } + + return &static_cpiofs; +} diff --git a/lab8/src/fs_framebufferfs.c b/lab8/src/fs_framebufferfs.c new file mode 100644 index 000000000..309d26aaf --- /dev/null +++ b/lab8/src/fs_framebufferfs.c @@ -0,0 +1,272 @@ +#include "fs_framebufferfs.h" +#include "fs_tmpfs.h" +#include "exception.h" +#include "mm.h" + +uint32_t __attribute__((aligned(0x10))) mbox[36]; + +filesystem *framebufferfs_init(void) { + return &static_framebufferfs; +}; + +filesystem static_framebufferfs = { + .name = "framebufferfs", + .mount = framebufferfs_mount, +}; + +vnode_operations framebufferfs_v_ops = { + .lookup = framebufferfs_lookup, + .create = framebufferfs_create, + .mkdir = framebufferfs_mkdir, + .isdir = framebufferfs_isdir, + .getname = framebufferfs_getname, + .getsize = framebufferfs_getsize, +}; + +file_operations framebufferfs_f_ops = { + .write = framebufferfs_write, + .read = framebufferfs_read, + .open = framebufferfs_open, + .close = framebufferfs_close, + .lseek64 = framebufferfs_lseek64, + .ioctl = framebufferfs_ioctl +}; + +int framebufferfs_mount(filesystem *fs, mount *mnt) { + uart_send_string("framebufferfs_mount\n"); + vnode *cur_node; + framebufferfs_internal *internal; + const char* name; + internal = (framebufferfs_internal *)kmalloc(sizeof(framebufferfs_internal)); + + cur_node = mnt -> root; + cur_node -> v_ops -> getname(cur_node, &name); + + uart_send_string("framebufferfs_mount: name = "); + uart_send_string(name); + uart_send_string("\n"); + + internal -> name = name; + internal -> oldnode.mount = cur_node -> mount; + internal -> oldnode.v_ops = cur_node -> v_ops; + internal -> oldnode.f_ops = cur_node -> f_ops; + internal -> oldnode.parent = cur_node -> parent; + internal -> oldnode.internal = cur_node -> internal; + + internal -> lfb = 0; + internal -> isopened = 0; + internal -> isinit = 0; + + cur_node -> mount = mnt; + cur_node -> v_ops = &framebufferfs_v_ops; + cur_node -> f_ops = &framebufferfs_f_ops; + cur_node -> internal = internal; + + + return 0; +} + + +int framebufferfs_lookup(vnode *dir_node, vnode **target, const char *component_name) { + return -1; +} + +int framebufferfs_create(vnode *dir_node, vnode **target, const char *component_name) { + return -1; +} + +int framebufferfs_mkdir(vnode *dir_node, vnode **target, const char *component_name) { + return -1; +} + +int framebufferfs_isdir(vnode *dir_node) { + return 0; +} + +int framebufferfs_getname(vnode *dir_node, const char **name) { + framebufferfs_internal *internal = (framebufferfs_internal *)dir_node -> internal; + + *name = internal -> name; + return 0; +} + +int framebufferfs_getsize(vnode *dir_node) { + return -1; +} + + +int framebufferfs_open(vnode *file_node, file *target) { + framebufferfs_internal *internal; + el1_interrupt_disable(); + internal = (framebufferfs_internal *)file_node -> internal; + + if(internal -> isopened) { + uart_send_string("framebufferfs_open: File already opened\n"); + el1_interrupt_enable(); + return -1; + } + + target -> vnode = file_node; + target -> f_pos = 0; + target -> f_ops = file_node -> f_ops; + uart_send_string("--------------------------------\n"); + + uart_send_string("framebufferfs_open: File opened\n"); + internal -> isopened = 1; + el1_interrupt_enable(); + return 0; +} + +int framebufferfs_close(file *target) { + framebufferfs_internal *internal = (framebufferfs_internal *)target -> vnode -> internal; + + target -> vnode = NULL; + target -> f_pos = 0; + target -> f_ops = NULL; + + internal -> isopened = 0; + + return 0; +} + +int framebufferfs_write(file *target, const void *buf, size_t len) { + // uart_send_string("w "); + framebufferfs_internal *internal = (framebufferfs_internal *)target -> vnode -> internal; + + if(!internal -> isinit) { + uart_send_string("framebufferfs_write: Framebuffer not initialized\n"); + return -1; + } + + if(target -> f_pos + len > internal -> lfbsize) { + uart_send_string("framebufferfs_write: Write out of bounds\n"); + return -1; + } + memncpy((void *)(internal -> lfb + target -> f_pos), buf, len); + target -> f_pos += len; + // uart_send_string("WS\n"); + return len; +} + +int framebufferfs_read(file *target, void *buf, size_t len) { + return -1; +} + +long framebufferfs_lseek64(file *target, long offset, int whence) { + el1_interrupt_disable(); + int base; + framebufferfs_internal *internal = (framebufferfs_internal *)target -> vnode -> internal; + + switch(whence) { + case SEEK_SET: + base = 0; + break; + case SEEK_CUR: + base = target -> f_pos; + break; + case SEEK_END: + base = internal -> lfbsize; + break; + default: + uart_send_string("framebufferfs_lseek64: Invalid whence\n"); + el1_interrupt_enable(); + return -1; + } + + if(base + offset > internal -> lfbsize) { + uart_send_string("framebufferfs_lseek64: Seek out of bounds\n"); + el1_interrupt_enable(); + return -1; + } + + target -> f_pos = base + offset; + el1_interrupt_enable(); + return 0; +} + +int framebufferfs_ioctl(struct file *file, uint64_t request, va_list args) { + if (request != 0) { + return -1; + } + fb_info *info; + + framebufferfs_internal *internal = (framebufferfs_internal *)file -> vnode -> internal; + + if(internal -> isinit) { + return 0; + } + uint32_t width, height, pitch, isrgb; + + mbox[0] = 35 * 4; + mbox[1] = MBOX_REQUEST; + + mbox[2] = 0x48003; // set phy wh + mbox[3] = 8; + mbox[4] = 8; + mbox[5] = 1024; // FrameBufferInfo.width + mbox[6] = 768; // FrameBufferInfo.height + + mbox[7] = 0x48004; // set virt wh + mbox[8] = 8; + mbox[9] = 8; + mbox[10] = 1024; // FrameBufferInfo.virtual_width + mbox[11] = 768; // FrameBufferInfo.virtual_height + + mbox[12] = 0x48009; // set virt offset + mbox[13] = 8; + mbox[14] = 8; + mbox[15] = 0; // FrameBufferInfo.x_offset + mbox[16] = 0; // FrameBufferInfo.y.offset + + mbox[17] = 0x48005; // set depth + mbox[18] = 4; + mbox[19] = 4; + mbox[20] = 32; // FrameBufferInfo.depth + + mbox[21] = 0x48006; // set pixel order + mbox[22] = 4; + mbox[23] = 4; + mbox[24] = 1; // RGB, not BGR preferably + + mbox[25] = 0x40001; // get framebuffer, gets alignment on request + mbox[26] = 8; + mbox[27] = 8; + mbox[28] = 4096; // FrameBufferInfo.pointer + mbox[29] = 0; // FrameBufferInfo.size + + mbox[30] = 0x40008; // get pitch + mbox[31] = 4; + mbox[32] = 4; + mbox[33] = 0; // FrameBufferInfo.pitch + + mbox[34] = MBOX_TAG_LAST; + uart_send_string("Calling mailbox_call\n"); + mailbox_call(MBOX_CH_PROP, mbox); + uart_send_string("Returned from mailbox_call\n"); + + if (mbox[20] == 32 && mbox[28] != 0) { + uart_send_string("FrameBuffer initialized\n"); + mbox[28] &= 0x3FFFFFFF; // convert GPU address to ARM address + width = mbox[5]; // get actual physical width + height = mbox[6]; // get actual physical height + pitch = mbox[33]; // get number of bytes per line + isrgb = mbox[24]; // get the actual channel order + internal->lfb = (void *)((unsigned long)mbox[28]); + internal->lfbsize = mbox[29]; + } else { + // Unable to set screen resolution to 1024x768x32 + uart_send_string("Unable to set screen resolution to 1024x768x32\n"); + return -1; + } + + info = va_arg(args, void*); + + info -> width = width; + info -> height = height; + info -> pitch = pitch; + info -> isrgb = isrgb; + + internal -> isinit = 1; + + return 0; +} \ No newline at end of file diff --git a/lab8/src/fs_fsinit.c b/lab8/src/fs_fsinit.c new file mode 100644 index 000000000..6a2e95b47 --- /dev/null +++ b/lab8/src/fs_fsinit.c @@ -0,0 +1,49 @@ +#include "fs_fsinit.h" +#include "mm.h" + +void fs_early_init(void) { + filesystem *tmpfs, *cpiofs, *uartfs, *framebufferfs; + + vfs_init(); + // init tmpfs + tmpfs = tmpfs_init(); + uart_send_string("tmpfs_init\n"); + // init cpiofs + cpiofs = cpiofs_init(); + // init uartfs + uartfs = uartfs_init(); + + framebufferfs = framebufferfs_init(); + + uart_send_string("cpiofs_init\n"); + register_filesystem(tmpfs); + uart_send_string("register tmpfs\n"); + register_filesystem(cpiofs); + uart_send_string("register cpiofs\n"); + register_filesystem(uartfs); + uart_send_string("register uartfs\n"); + register_filesystem(framebufferfs); + uart_send_string("register framebufferfs\n"); + + vfs_init_rootfs(tmpfs); + uart_send_string("init rootfs\n"); +} + +void fs_init(void) { + // mount rootfs + vfs_mkdir("/initramfs"); + uart_send_string("mkdir /initramfs\n"); + vfs_mount("/initramfs", "cpiofs"); + uart_send_string("mount /initramfs\n"); + + // mount uartfs + vfs_mkdir("/dev"); + uart_send_string("mkdir /dev\n"); + vfs_mkdir("/dev/uart"); + uart_send_string("mkdir /dev/uart\n"); + vfs_mount("/dev/uart", "uartfs"); + + // mount framebufferfs + vfs_mkdir("/dev/framebuffer"); + vfs_mount("/dev/framebuffer", "framebufferfs"); +} \ No newline at end of file diff --git a/lab8/src/fs_tmpfs.c b/lab8/src/fs_tmpfs.c new file mode 100644 index 000000000..9a86a51b1 --- /dev/null +++ b/lab8/src/fs_tmpfs.c @@ -0,0 +1,398 @@ +#include "fs_tmpfs.h" +#include "alloc.h" +#include "string.h" + + +// define struct +filesystem static_tmpfs = { + .name = "tmpfs", + .mount = tmpfs_mount, + .alloc_vnode = tmpfs_alloc_vnode, +}; + +struct vnode_operations tmpfs_v_ops = { + .lookup = tmpfs_lookup, + .create = tmpfs_create, + .mkdir = tmpfs_mkdir, + .isdir = tmpfs_isdir, + .getname = tmpfs_getname, + .getsize = tmpfs_getsize, +}; + +struct file_operations tmpfs_f_ops = { + .write = tmpfs_write, + .read = tmpfs_read, + .open = tmpfs_open, + .close = tmpfs_close, + .lseek64 = tmpfs_lseek64, + .ioctl = tmpfs_ioctl, +}; + +filesystem *tmpfs_init(void) { + return &static_tmpfs; +} + +int tmpfs_mount(filesystem *fs, mount *mnt) { + vnode *backup_node, *cur_node; + tmpfs_internal *internal; + tmpfs_dir *dir; + const char* name; + + cur_node = mnt->root; + + cur_node -> v_ops -> getname(cur_node, &name); + + if(strlen(name) >= TMPFS_NAME_MAXLEN) { + uart_send_string("tmpfs_mount: name too long\n"); + return -1; + } + + backup_node = (vnode *)kmalloc(sizeof(vnode)); + internal = (tmpfs_internal *)kmalloc(sizeof(tmpfs_internal)); + dir = (tmpfs_dir *)kmalloc(sizeof(tmpfs_dir)); + + dir -> size = 0; + + backup_node -> mount = mnt; + backup_node -> v_ops = cur_node -> v_ops; + backup_node -> f_ops = cur_node -> f_ops; + backup_node -> internal = cur_node -> internal; + + strcpy(internal -> name, name); + internal -> type = TMPFS_TYPE_DIR; + internal -> dir = dir; + internal -> old_node = backup_node; + + cur_node -> mount = mnt; + cur_node -> v_ops = &tmpfs_v_ops; + cur_node -> f_ops = &tmpfs_f_ops; + cur_node -> internal = internal; + + return 0; +} + +int tmpfs_alloc_vnode(filesystem *fs, vnode **target) { + vnode *node; + tmpfs_internal *internal; + tmpfs_dir *dir; + + node = (vnode *)kmalloc(sizeof(vnode)); + internal = (tmpfs_internal *)kmalloc(sizeof(tmpfs_internal)); + dir = (tmpfs_dir *)kmalloc(sizeof(tmpfs_dir)); + + dir -> size = 0; + + internal -> name[0] = '\0'; + internal -> type = TMPFS_TYPE_DIR; + internal -> dir = dir; + internal -> old_node = 0; + + node -> mount = 0; + node -> v_ops = &tmpfs_v_ops; + node -> f_ops = &tmpfs_f_ops; + node -> internal = internal; + + *target = node; + + return 0; +} + + +// vnode operations +int tmpfs_lookup(vnode *dir_node, vnode **target, const char *component_name) { + // uart_send_string("tmpfs_lookup\n"); + // uart_send_string(component_name); + // uart_send_string("\n"); + tmpfs_internal *internal; + tmpfs_dir *dir; + vnode *node; + int i; + + internal = (tmpfs_internal *)dir_node -> internal; + + if(internal -> type != TMPFS_TYPE_DIR) { + uart_send_string("tmpfs_lookup: not a dir\n"); + return -1; + } + // uart_send_string("tmpfs_lookup: is a dir size: "); + dir = internal -> dir; + + + // uart_hex(dir -> size); + // uart_send_string("\n"); + + for(i = 0; i < dir -> size; i++) { + // uart_send_string("tmpfs_lookup i: "); + // uart_hex(i); + // uart_send_string("\n"); + const char *name; + int ret; + node = dir -> files[i]; + ret = node -> v_ops -> getname(node, &name); + if(ret < 0){ + continue; + } + if(strcmp(name, component_name) == 0) { + break; + } + } + + + if(i >= dir -> size) { + uart_send_string("tmpfs_lookup: not found\n"); + return -1; + } + + *target = dir -> files[i]; + + return 0; +} + +// create a new file under dir_node +// return 0 on success, -1 on error +int tmpfs_create(vnode *dir_node, vnode **target, const char *component_name) { + tmpfs_internal *internal, *new_internal; + tmpfs_file *file; + tmpfs_dir *dir; + vnode *node; + int ret; + + if(strlen(component_name) >= TMPFS_NAME_MAXLEN) { + uart_send_string("tmpfs_create: name too long\n"); + return -1; + } + + internal = (tmpfs_internal *)dir_node -> internal; + if(internal -> type != TMPFS_TYPE_DIR) { + uart_send_string("tmpfs_create: not a dir\n"); + return -1; + } + + dir = internal -> dir; + + if(dir -> size >= TMPFS_DIR_MAXSIZE) { + uart_send_string("tmpfs_create: dir full\n"); + return -1; + } + + ret = tmpfs_lookup(dir_node, &node, component_name); + + if(!ret){ + uart_send_string("tmpfs_create: file exists\n"); + return -1; + } + + node = (vnode *)kmalloc(sizeof(vnode)); + new_internal = (tmpfs_internal *)kmalloc(sizeof(tmpfs_internal)); + file = (tmpfs_file *)kmalloc(sizeof(tmpfs_file)); + + file -> data = kmalloc(TMPFS_FILE_MAXSIZE); + file -> size = 0; + file -> capacity = TMPFS_FILE_MAXSIZE; + + strcpy(new_internal -> name, component_name); + new_internal -> type = TMPFS_TYPE_FILE; + new_internal -> file = file; + new_internal -> old_node = 0; + + node -> mount = dir_node -> mount; + node -> v_ops = &tmpfs_v_ops; + node -> f_ops = &tmpfs_f_ops; + node -> parent = dir_node; + node -> internal = new_internal; + + dir -> files[dir -> size] = node; + dir -> size ++; + + *target = node; + + return 0; +} + +int tmpfs_mkdir(vnode *dir_node, vnode **target, const char *component_name) { + tmpfs_internal *internal, *new_internal; + tmpfs_dir *dir, *new_dir; + vnode *node; + int ret; + + if(strlen(component_name) >= TMPFS_NAME_MAXLEN) { + uart_send_string("tmpfs_create: name too long\n"); + return -1; + } + + internal = (tmpfs_internal *)dir_node -> internal; + if(internal -> type != TMPFS_TYPE_DIR) { + uart_send_string("tmpfs_create: not a dir\n"); + return -1; + } + + dir = internal -> dir; + + if(dir -> size >= TMPFS_DIR_MAXSIZE) { + uart_send_string("tmpfs_create: dir full\n"); + return -1; + } + + ret = tmpfs_lookup(dir_node, &node, component_name); + + if(!ret){ + uart_send_string("tmpfs_create: file exists\n"); + return -1; + } + + node = (vnode *)kmalloc(sizeof(vnode)); + new_internal = (tmpfs_internal *)kmalloc(sizeof(tmpfs_internal)); + new_dir = (tmpfs_dir *)kmalloc(sizeof(tmpfs_dir)); + + new_dir -> size = 0; + + strcpy(new_internal -> name, component_name); + new_internal -> type = TMPFS_TYPE_DIR; + new_internal -> dir = new_dir; + new_internal -> old_node = 0; + + node -> mount = dir_node -> mount; + node -> v_ops = &tmpfs_v_ops; + node -> f_ops = &tmpfs_f_ops; + node -> parent = dir_node; + node -> internal = new_internal; + + dir -> files[dir -> size] = node; + dir -> size ++; + + *target = node; + + return 0; +} + +int tmpfs_isdir(vnode *dir_node) { + tmpfs_internal *internal = (tmpfs_internal *)dir_node -> internal; + if (internal -> type == TMPFS_TYPE_DIR) { + return 1; + } + + return 0; +} + +int tmpfs_getname(vnode *dir_node, const char **name) { + tmpfs_internal *internal = internal = (tmpfs_internal *)dir_node -> internal; + *name = internal -> name; + return 0; +} + +int tmpfs_getsize(vnode *dir_node) { + tmpfs_internal *internal = (tmpfs_internal *)dir_node -> internal; + return internal -> type == TMPFS_TYPE_FILE ? internal -> file -> size : -1; +} + +// file operations +int tmpfs_open(vnode *file_node, file *target) { + if(((tmpfs_internal*)file_node -> internal) -> type != TMPFS_TYPE_FILE) { + uart_send_string("tmpfs_open: not a file\n"); + return -1; + } + target -> vnode = file_node; + target -> f_pos = 0; + target -> f_ops = file_node -> f_ops; + + return 0; +} + +int tmpfs_close(file *target) { + target -> vnode = 0; + target -> f_ops = 0; + target -> f_pos = 0; + + return 0; +} + +int tmpfs_write(file *target, const void *buf, size_t len) { + tmpfs_internal *internal = (tmpfs_internal *)target -> vnode -> internal; + size_t i; + + if(internal -> type != TMPFS_TYPE_FILE) { + uart_send_string("tmpfs_write: not a file\n"); + return -1; + } + + tmpfs_file *file = internal -> file; + + if(len > file -> capacity - file -> size) { + len = file -> capacity - file -> size; + } + + if(!len) { + uart_send_string("tmpfs_write: no space left\n"); + return 0; + } + + memcpy(&file->data[target->f_pos], buf, len); + + target -> f_pos += len; + + if(target -> f_pos > file -> size) { + file -> size = target -> f_pos; + } + + return len; +} + +int tmpfs_read(file *target, void *buf, size_t len) { + tmpfs_internal *internal = (tmpfs_internal *)target -> vnode -> internal; + + if(internal -> type != TMPFS_TYPE_FILE) { + uart_send_string("tmpfs_read: not a file\n"); + return -1; + } + + tmpfs_file *file = internal -> file; + + if(len > file -> size - target -> f_pos) { + len = file -> size - target -> f_pos; + } + + memcpy(buf, &file -> data[target -> f_pos], len); + + target -> f_pos += len; + + return len; +} + + +long tmpfs_lseek64(file *target, long offset, int whence) { + int filesize; + int base; + + filesize = target -> vnode -> v_ops -> getsize(target -> vnode); + + if(filesize < 0) { + return -1; + } + + switch (whence) { + case SEEK_SET: + base = 0; + break; + case SEEK_CUR: + base = target -> f_pos; + break; + case SEEK_END: + base = filesize; + break; + default: + return -1; + } + + if(base + offset > filesize) { + uart_send_string("tmpfs_lseek64: invalid offset\n"); + return -1; + } + + target -> f_pos = base + offset; + + return 0; +} + +int tmpfs_ioctl(struct file *file, uint64_t request, va_list args) { + return -1; +} diff --git a/lab8/src/fs_uartfs.c b/lab8/src/fs_uartfs.c new file mode 100644 index 000000000..f7201406c --- /dev/null +++ b/lab8/src/fs_uartfs.c @@ -0,0 +1,118 @@ +#include "fs_uartfs.h" +#include "fs_tmpfs.h" + +filesystem *uartfs_init(void) { + return &static_uartfs; +}; + +filesystem static_uartfs = { + .name = "uartfs", + .mount = uartfs_mount, +}; + +vnode_operations uartfs_v_ops = { + .lookup = uartfs_lookup, + .create = uartfs_create, + .mkdir = uartfs_mkdir, + .isdir = uartfs_isdir, + .getname = uartfs_getname, + .getsize = uartfs_getsize, +}; + +file_operations uartfs_f_ops = { + .write = uartfs_write, + .read = uartfs_read, + .open = uartfs_open, + .close = uartfs_close, + .lseek64 = uartfs_lseek64, + .ioctl = uartfs_ioctl, +}; + +int uartfs_mount(filesystem *fs, mount *mnt) { + vnode *cur_node; + uartfs_internal *internal; + const char* name; + internal = (uartfs_internal *)kmalloc(sizeof(uartfs_internal)); + + cur_node = mnt -> root; + cur_node -> v_ops -> getname(cur_node, &name); + + internal -> name = name; + internal -> oldnode.mount = cur_node -> mount; + internal -> oldnode.v_ops = cur_node -> v_ops; + internal -> oldnode.f_ops = cur_node -> f_ops; + internal -> oldnode.parent = cur_node -> parent; + internal -> oldnode.internal = cur_node -> internal; + + cur_node -> mount = mnt; + cur_node -> v_ops = &uartfs_v_ops; + cur_node -> f_ops = &uartfs_f_ops; + cur_node -> internal = internal; + + return 0; +} + + +int uartfs_lookup(vnode *dir_node, vnode **target, const char *component_name) { + return -1; +} + +int uartfs_create(vnode *dir_node, vnode **target, const char *component_name) { + return -1; +} + +int uartfs_mkdir(vnode *dir_node, vnode **target, const char *component_name) { + return -1; +} + +int uartfs_isdir(vnode *dir_node) { + return 0; +} + +int uartfs_getname(vnode *dir_node, const char **name) { + uartfs_internal *internal = (uartfs_internal *)dir_node -> internal; + + *name = internal -> name; + return 0; +} + +int uartfs_getsize(vnode *dir_node) { + return -1; +} + + +int uartfs_open(vnode *file_node, file *target) { + target -> vnode = file_node; + target -> f_pos = 0; + target -> f_ops = file_node -> f_ops; + + return 0; +} + +int uartfs_close(file *target) { + target -> vnode = NULL; + target -> f_pos = 0; + target -> f_ops = NULL; + + return 0; +} + +int uartfs_write(file *target, const void *buf, size_t len) { + uart_sendn(buf, len); + + return len; +} + +int uartfs_read(file *target, void *buf, size_t len) { + uart_recvn(buf, len); + + return len; +} + +long uartfs_lseek64(file *target, long offset, int whence) { + return -1; +} + +int uartfs_ioctl(struct file *file, uint64_t request, va_list args) { + return -1; +} \ No newline at end of file diff --git a/lab8/src/fs_vfs.c b/lab8/src/fs_vfs.c new file mode 100644 index 000000000..e2e17c2d5 --- /dev/null +++ b/lab8/src/fs_vfs.c @@ -0,0 +1,572 @@ +#include "fs_vfs.h" +#include "thread.h" +#include "string.h" +#include "alloc.h" +#include "mini_uart.h" +#include "reboot.h" +#include "thread.h" + +mount* rootfs; + +struct list_head fs_lists; + +vnode* get_vnode_from_path(vnode* dir_node, const char **pathname) { + uart_send_string("get_vnode_from_path\n"); + vnode* result; + const char *start; + const char *end; + char buf[256]; + + start = end = *pathname; + + if(*start == '/') { + result = rootfs->root; + } else { + result = dir_node; + } + uart_send_string("get_vnode_from_path: "); + uart_send_string(*pathname); + uart_send_string("\n"); + + + // find the vnode from the path + while(1) { + // handle ./ and ../ + if(!strncmp("./", start, 2)) { + start += 2; + end = start; + continue; + } else if(!strncmp("../", start, 3)) { + if(result -> parent) { + result = result -> parent; + } + start += 3; + end = start; + continue; + } + + + while(*end != '\0' && *end != '/') { + end++; + } + + if(*end == '/') { + int ret; + + if(start == end) { + // handle the case like /usr//bin + end ++; + start = end; + continue; + } + + memcpy(buf, start, end - start); + buf[end - start] = '\0'; + uart_send_string(buf); + uart_send_string("\n"); + + ret = result -> v_ops -> lookup(result, &result, buf); + // failed + if(ret < 0) { + return NULL; + } + + end ++; + start = end; + } else { + // reach the end of the path + break; + } + } + + *pathname = *start ? start : NULL; + + return result; +} + +filesystem* find_fs(const char *fs_name) { + struct filesystem *fs; + + list_for_each_entry(fs, &fs_lists, fs_list) { + if(!strcmp(fs -> name, fs_name)) { + return fs; + } + } + + return NULL; +} + +void vfs_init() { + INIT_LIST_HEAD(&fs_lists); +} + +void vfs_init_rootfs(filesystem *fs) { + // original code: vnode* new_vnode; + vnode *new_vnode = (vnode*)kmalloc(sizeof(vnode)); + int ret; + + ret = fs -> alloc_vnode(fs, &new_vnode); + if(ret < 0) { + uart_send_string("vfs_init_rootfs: failed to alloc vnode\n"); + reset(1); + return; + } + + rootfs = (mount*)kmalloc(sizeof(mount)); + + rootfs -> fs = fs; + rootfs -> root = new_vnode; + new_vnode -> mount = rootfs; + new_vnode -> parent = NULL; +} + +int register_filesystem(filesystem *fs) { + if(find_fs(fs -> name)) { + uart_send_string("register_filesystem: filesystem already registered\n"); + return -1; + } + + list_add_tail(&fs -> fs_list, &fs_lists); + + return 0; +} + +int vfs_open(const char* pathname, int flags, file* target) { + const char* cur_name = pathname; + vnode* dir_node; + vnode* file_node; + int ret; + + if(is_init_thread){ + dir_node = get_vnode_from_path(get_current_thread() -> working_dir, &cur_name); + } else { + dir_node = get_vnode_from_path(rootfs -> root, &cur_name); + } + + if(!dir_node) { + uart_send_string("vfs_open: failed to get dir node\n"); + return -1; + } + + if(!cur_name) { + uart_send_string("vfs_open: invalid pathname\n"); + return -1; + } + char* name; + dir_node -> v_ops -> getname(dir_node, &name); + + ret = dir_node -> v_ops -> lookup(dir_node, &file_node, cur_name); + + + if(flags & O_CREAT) { + if(ret == 0){ + uart_send_string("vfs_open: file already exists\n"); + return -1; + } + + ret = dir_node -> v_ops -> create(dir_node, &file_node, cur_name); + uart_send_string("vfs_open: create success\n"); + + } + + if (ret < 0) { + uart_send_string("vfs_open: failed to lookup/create file\n"); + return ret; + } + + if(!file_node) { + uart_send_string("vfs_open: failed to get file node\n"); + return -1; + } + char* name2; + file_node -> v_ops -> getname(file_node, &name2); + uart_send_string("vfs_open: "); + uart_send_string(name2); + uart_send_string("\n"); + + ret = file_node -> f_ops -> open(file_node, target); + if(ret < 0){ + uart_send_string("vfs_open: failed to open file\n"); + return ret; + } + + target -> flags = 0; + + return 0; +} + +int vfs_close(file *f) { + return f -> f_ops -> close(f); +} + +int vfs_write(file *f, const void *buf, size_t len) { + return f -> f_ops -> write(f, buf, len); +} + +int vfs_read(file *f, void *buf, size_t len) { + return f -> f_ops -> read(f, buf, len); +} + +int vfs_mkdir(const char* pathname) { + char* cur_name = (char*)kmalloc(strlen(pathname) + 1); + strcpy(cur_name, pathname); + vnode* dir_node; + vnode* new_node; + int ret; + uart_send_string("vfs_mkdir: "); + uart_send_string(cur_name); + uart_send_string("\n"); + + if(is_init_thread){ + dir_node = get_vnode_from_path(get_current_thread() -> working_dir, &cur_name); + } else { + dir_node = get_vnode_from_path(rootfs -> root, &cur_name); + } + + if(!dir_node) { + uart_send_string("vfs_mkdir: failed to get dir node\n"); + return -1; + } + + if(!cur_name) { + uart_send_string("vfs_mkdir: invalid pathname\n"); + return -1; + } + + ret = dir_node -> v_ops -> mkdir(dir_node, &new_node, cur_name); + + if(ret < 0) { + uart_send_string("vfs_mkdir: failed to mkdir\n"); + return ret; + } + + uart_send_string("vfs_mkdir: success\n"); + + return 0; +} + +int vfs_mount(const char* target, const char* fs_name) { + const char* cur_name = target; + vnode* dir_node; + filesystem* fs; + mount* mnt; + int ret; + + if(is_init_thread) { + dir_node = get_vnode_from_path(get_current_thread() -> working_dir, &cur_name); + } else { + dir_node = get_vnode_from_path(rootfs -> root, &cur_name); + } + + if(!dir_node) { + uart_send_string("vfs_mount: failed to get dir node\n"); + return -1; + } + + if(cur_name) { + ret = dir_node -> v_ops -> lookup(dir_node, &dir_node, cur_name); + + if(ret < 0) { + return ret; + } + } + + if(!dir_node -> v_ops -> isdir(dir_node)) { + uart_send_string("vfs_mount: target is not a directory\n"); + return -1; + } + + fs = find_fs(fs_name); + + if(!fs) { + uart_send_string("vfs_mount: filesystem not found\n"); + return -1; + } + + mnt = (mount*)kmalloc(sizeof(mount)); + + mnt -> fs = fs; + mnt -> root = dir_node; + + ret = fs -> mount(fs, mnt); + + if(ret < 0) { + uart_send_string("vfs_mount: failed to mount\n"); + kfree(mnt); + return ret; + } +} + +int vfs_lookup(const char *pathname, vnode **target) { + char* cur_name = pathname; + vnode* dir_node; + vnode* file_node; + int ret; + uart_send_string("vfs_lookup: "); + uart_send_string(cur_name); + uart_send_string("\n"); + dir_node = get_vnode_from_path(get_current_thread() -> working_dir, &cur_name); + uart_send_string("vfs_lookup: "); + uart_send_string(cur_name); + uart_send_string("\n"); + + if(!dir_node) { + uart_send_string("vfs_lookup: failed to get dir node\n"); + return -1; + } + + if(!cur_name) { + // found the directory node as target + *target = dir_node; + return 0; + } + + ret = dir_node -> v_ops -> lookup(dir_node, &file_node, cur_name); + + if(ret < 0) { + uart_send_string("vfs_lookup: failed to lookup file\n"); + return ret; + } + + *target = file_node; + + return 0; +} + +long vfs_lseek64(file *f, long offset, int whence) { + return f -> f_ops -> lseek64(f, offset, whence); +} + +int vfs_ioctl(file *f, uint64_t request, va_list args) { + return f -> f_ops -> ioctl(f, request, args); +} + +// wrapper's +int open_wrapper(const char* pathname, int flags) { + int i, ret; + for(i = 0; i <= get_current_thread() -> max_fd; i++ ) { + if(get_current_thread() -> fds[i].vnode == 0) { + break; + } + } + + if(i > get_current_thread() -> max_fd) { + if(get_current_thread() -> max_fd >= THREAD_MAX_FD) { + uart_send_string("open_wrapper: too many files opened\n"); + return -1; + } + + get_current_thread() -> max_fd ++; + i = get_current_thread() -> max_fd; + } + + // uart_send_string("open_wrapper: "); + // uart_send_string(pathname); + // uart_send_string("\n"); + // int tid = get_current_thread() -> tid; + // uart_send_string("tid: "); + // uart_hex(tid); + // uart_send_string("\n"); + // uart_send_string("i: "); + // uart_hex(i); + // uart_send_string("\n"); + // uart_send_string("flags: "); + // uart_hex(flags); + // uart_send_string("\n"); + + ret = vfs_open(pathname, flags, &get_current_thread() -> fds[i]); + + if(ret < 0) { + uart_send_string("open_wrapper: failed to open file\n"); + get_current_thread() -> fds[i].vnode = 0; + return -1; + } + + return i; +} + +int close_wrapper(int fd) { + if(fd < 0 || fd > get_current_thread() -> max_fd) { + uart_send_string("close_wrapper: invalid fd\n"); + return -1; + } + + if(get_current_thread() -> fds[fd].vnode == 0) { + uart_send_string("close_wrapper: file not opened\n"); + return -1; + } + + int ret; + ret = vfs_close(&get_current_thread() -> fds[fd]); + + if(ret < 0) return ret; + + return 0; +} + +int write_wrapper(int fd, const void *buf, size_t len) { + if(fd < 0 || fd > get_current_thread() -> max_fd) { + uart_send_string("write_wrapper: invalid fd\n"); + return -1; + } + + if(get_current_thread() -> fds[fd].vnode == 0) { + uart_send_string("write_wrapper: file not opened\n"); + return -1; + } + + int ret; + ret = vfs_write(&get_current_thread() -> fds[fd], buf, len); + if(ret < 0) { + uart_send_string("write_wrapper: failed to write\n"); + } + return ret; +} + +int read_wrapper(int fd, void *buf, size_t len) { + if(fd < 0 || fd > get_current_thread() -> max_fd) { + uart_send_string("read_wrapper: invalid fd\n"); + return -1; + } + + if(get_current_thread() -> fds[fd].vnode == 0) { + uart_send_string("read_wrapper: file not opened\n"); + return -1; + } + + int ret; + ret = vfs_read(&get_current_thread() -> fds[fd], buf, len); + + return ret; +} + +int mkdir_wrapper(const char* pathname) { + return vfs_mkdir(pathname); +} + +int mount_wrapper(const char* target, const char* fs_name) { + return vfs_mount(target, fs_name); +} + +int chdir_wrapper(const char* path) { + vnode* target; + int ret; + + ret = vfs_lookup(path, &target); + + if(ret < 0) { + uart_send_string("chdir_wrapper: failed to lookup\n"); + return -1; + } + + if(!target -> v_ops -> isdir(target)) { + uart_send_string("chdir_wrapper: not a directory\n"); + return -1; + } + + get_current_thread() -> working_dir = target; + + return 0; +} + +long lseek64_wrapper(int fd, long offset, int whence) { + // uart_send_string("lseek64_wrapper\n"); + if(fd < 0 || fd > get_current_thread() -> max_fd) { + uart_send_string("lseek64_wrapper: invalid fd\n"); + return -1; + } + + if(get_current_thread() -> fds[fd].vnode == 0) { + uart_send_string("lseek64_wrapper: file not opened\n"); + return -1; + } + + int ret; + ret = vfs_lseek64(&get_current_thread() -> fds[fd], offset, whence); + if(ret < 0) { + uart_send_string("lseek64_wrapper: failed to lseek\n"); + } + return ret; +} + +int ioctl_wrapper(int fd, uint64_t cmd, va_list args) { + if(fd < 0 || fd > get_current_thread() -> max_fd) { + uart_send_string("ioctl_wrapper: invalid fd\n"); + return -1; + } + + if(get_current_thread() -> fds[fd].vnode == 0) { + uart_send_string("ioctl_wrapper: file not opened\n"); + return -1; + } + + int ret; + uart_send_string("ioctl_wrapper\n"); + ret = vfs_ioctl(&get_current_thread() -> fds[fd], cmd, args); + + return ret; +} + +void syscall_open(trapframe_t *tf, const char *pathname, int flags) { + int fd = open_wrapper(pathname, flags); + tf -> x[0] = fd; + return; +} + +void syscall_close(trapframe_t *tf, int fd) { + int ret = close_wrapper(fd); + tf -> x[0] = ret; + return; +} + +void syscall_write(trapframe_t *tf, int fd, const void *buf, size_t len) { + int ret = write_wrapper(fd, buf, len); + tf -> x[0] = ret; + return; +} + +void syscall_read(trapframe_t *tf, int fd, void *buf, size_t len) { + int ret = read_wrapper(fd, buf, len); + tf -> x[0] = ret; + return; +} + +void syscall_mkdir(trapframe_t *tf, const char *pathname, uint32_t mode) { + int ret = mkdir_wrapper(pathname); + tf -> x[0] = ret; + return; +} + +void syscall_mount( + trapframe_t *tf, + const char *src, + const char *target, + const char *fs_name, + int flags, + const void *data +) { + int ret = mount_wrapper(target, fs_name); + tf -> x[0] = ret; +} + +void syscall_chdir(trapframe_t *tf, const char *path) { + int ret = chdir_wrapper(path); + tf -> x[0] = ret; +} + +void syscall_lseek64(trapframe_t *tf, int fd, long offset, int whence) { + long ret = lseek64_wrapper(fd, offset, whence); + tf -> x[0] = ret; +} + +void syscall_ioctl(trapframe_t *tf, int fd, uint64_t requests, ...) { + uart_send_string("syscall_ioctl\n"); + int ret; + va_list args; + + va_start(args, requests); + ret = ioctl_wrapper(fd, requests, args); + va_end(args); + + tf -> x[0] = ret; +} diff --git a/lab8/src/initrd.c b/lab8/src/initrd.c new file mode 100644 index 000000000..49b32f2eb --- /dev/null +++ b/lab8/src/initrd.c @@ -0,0 +1,295 @@ +#include + +#include "initrd.h" +#include "alloc.h" +#include "mini_uart.h" +#include "string.h" +#include "c_utils.h" +#include "thread.h" +#include "exception.h" +#include "timer.h" +#include "fs_vfs.h" +#include "mm.h" + +char *ramfs_base; +char *ramfs_end; +char *exec_path_name; + +// Convert hexadecimal string to int +// @param s: hexadecimal string +// @param n: string length +static int hextoi(char *s, int n) +{ + int r = 0; + while (n-- > 0) { + r = r << 4; + if (*s >= 'A') + r += *s++ - 'A' + 10; + else if (*s >= '0') + r += *s++ - '0'; + } + return r; +} + +int cpio_newc_parse_header(cpio_t *this_header_pointer, char **pathname, unsigned int *filesize, char **data, cpio_t **next_header_pointer) +{ + // Ensure magic header 070701 + // new ascii format + if (strncmp(this_header_pointer->c_magic, CPIO_NEWC_HEADER_MAGIC, sizeof(this_header_pointer->c_magic)) != 0) + return -1; + + // transfer big endian 8 byte hex string to unsinged int + // data size + *filesize = hextoi(this_header_pointer->c_filesize, 8); + + // end of header is the pathname + // header | pathname str | data + *pathname = ((char *)this_header_pointer) + sizeof(cpio_t); + + // get file data, file data is just after pathname + // header | pathname str | data + // check picture on hackmd note + unsigned int pathname_length = hextoi(this_header_pointer->c_namesize, 8); + // get the offset to start of data + // | offset | data + unsigned int offset = pathname_length + sizeof(cpio_t); + // pathname and data might be zero padding + // section % 4 ==0 + offset = offset % 4 == 0 ? offset : (offset + 4 - offset % 4); // padding + // header pointer + offset = start of data + // h| offset | data + *data = (char *)this_header_pointer + offset; + + // get next header pointer + if (*filesize == 0) + // hardlinked files handeld by setting filesize to zero + *next_header_pointer = (cpio_t *)*data; + else + { + // data size + offset = *filesize; + // move pointer to the end of data + *next_header_pointer = (cpio_t *)(*data + (offset % 4 == 0 ? offset : (offset + 4 - offset % 4))); + } + + // if filepath is TRAILER!!! means there is no more files. + // end of archieve + // empty filename : TRAILER!!! + if (strncmp(*pathname, "TRAILER!!!", sizeof("TRAILER!!!")) == 0) + *next_header_pointer = 0; + + return 0; +} + +void initrd_list() +{ + char *filepath; + char *filedata; + unsigned int filesize; + // current pointer + cpio_t *header_pointer = (cpio_t *)(ramfs_base); + + // print every cpio pathname + while (header_pointer) + { + // uart_send_string("header_pointer: "); + // uart_hex((unsigned long)header_pointer); + // uart_send_string("\n"); + int error = cpio_newc_parse_header(header_pointer, &filepath, &filesize, &filedata, &header_pointer); + // if parse header error + if (error) + { + uart_send_string("Error parsing cpio header\n"); + break; + } + // uart_send_string("header_pointer: "); + // uart_hex((unsigned long)header_pointer); + // uart_send_string("\n"); + // if this is not TRAILER!!! (last of file) + if (header_pointer != 0){ + uart_send_string(filepath); + uart_send_string("\n"); + } + } + return; +} + + +void initrd_cat(const char *target) +{ + char *filepath; + char *filedata; + unsigned int filesize; + // current pointer + cpio_t *header_pointer = (cpio_t *)(ramfs_base); + + // print every cpio pathname + while (header_pointer) + { + // uart_send_string("header_pointer: "); + // uart_hex((unsigned long)header_pointer); + // uart_send_string("\n"); + int error = cpio_newc_parse_header(header_pointer, &filepath, &filesize, &filedata, &header_pointer); + // if parse header error + if (error) + { + // uart_printf("error\n"); + uart_send_string("Error parsing cpio header\n"); + break; + } + if (!strcmp(target, filepath)) + { + for (unsigned int i = 0; i < filesize; i++) + uart_send(filedata[i]); + uart_send_string("\n"); + break; + } + // uart_send_string("header_pointer: "); + // uart_hex((unsigned long)header_pointer); + // uart_send_string("\n"); + // if this is not TRAILER!!! (last of file) + if (header_pointer == 0){ + uart_send_string("File not found\n"); + break; + } + } + return; +} + +void initrd_callback(unsigned int node_type, char *name, void *value, unsigned int name_size){ + if(!strcmp(name, "linux,initrd-start")){ + ramfs_base = (char *)(unsigned long long)endian_big2little(*(unsigned int *)value); + uart_send_string("ramfs_base: "); + uart_hex((unsigned long)ramfs_base); + uart_send_string("\n"); + } + if(!strcmp(name, "linux,initrd-end")){ + ramfs_end = (char *)(unsigned long long)endian_big2little(*(unsigned int *)value); + uart_send_string("ramfs_end: "); + uart_hex((unsigned long)ramfs_end); + uart_send_string("\n"); + } +} + +// void initrd_exec_prog(char* target) { +// el1_interrupt_disable(); +// void* target_addr; +// char *filepath; +// char *filedata; +// unsigned int filesize; +// // current pointer +// cpio_t *header_pointer = (cpio_t *)(ramfs_base); + +// // print every cpio pathname +// while (header_pointer) +// { +// // uart_send_string("header_pointer: "); +// // uart_hex((unsigned long)header_pointer); +// // uart_send_string("\n"); +// int error = cpio_newc_parse_header(header_pointer, &filepath, &filesize, &filedata, &header_pointer); +// // if parse header error +// if (error) +// { +// // uart_printf("error\n"); +// uart_send_string("Error parsing cpio header\n"); +// break; +// } +// if (!strcmp(target, filepath)) +// { +// uart_send_string("filesize: "); +// uart_hex(filesize); +// uart_send_string("\n"); +// target_addr = kmalloc(filesize); +// memcpy(target_addr, filedata, filesize); +// break; +// } +// // uart_send_string("header_pointer: "); +// // uart_hex((unsigned long)header_pointer); +// // uart_send_string("\n"); +// // if this is not TRAILER!!! (last of file) +// if (header_pointer == 0){ +// uart_send_string("Program not found\n"); +// return; +// } +// } +// uart_send_string("prog addr: "); +// uart_hex(target_addr); +// uart_send_string("\n"); +// thread_t* t = create_thread(target_addr); +// el1_interrupt_enable(); +// unsigned long spsr_el1 = 0x0; // run in el0 and enable all interrupt (DAIF) +// unsigned long elr_el1 = t -> callee_reg.lr; +// unsigned long user_sp = t -> callee_reg.sp; +// unsigned long kernel_sp = (unsigned long)t -> kernel_stack + T_STACK_SIZE; +// // "r": Any general-purpose register, except sp +// asm volatile("msr tpidr_el1, %0" : : "r" (t)); +// asm volatile("msr spsr_el1, %0" : : "r" (spsr_el1)); +// asm volatile("msr elr_el1, %0" : : "r" (elr_el1)); +// asm volatile("msr sp_el0, %0" : : "r" (user_sp)); +// asm volatile("mov sp, %0" :: "r" (kernel_sp)); +// asm volatile("eret"); // jump to user program +// return; +// } + +void initrd_exec_prog() { + int data_len, aligned_data_len; + file f; + void* data; + int ret; + ret = vfs_open(exec_path_name, 0, &f); + + if(ret < 0) return; + + data_len = f.vnode -> v_ops -> getsize(f.vnode); + aligned_data_len = ALIGN(data_len, PAGE_SIZE); + data = kmalloc(aligned_data_len); + memzero(data, aligned_data_len); // clear data + + ret = vfs_read(&f, data, data_len); + + vfs_close(&f); + + uart_send_string("prog addr: "); + uart_hex(data); + uart_send_string("\n"); + thread_t* t = get_current_thread(); + uart_send_string("thread tid: "); + uart_hex(t -> tid); + uart_send_string("\n"); + + for(int i=0;i<=SIGNAL_NUM;i++) { + t -> signal_handler[i] = 0; + t -> waiting_signal[i] = 0; + } + + unsigned long spsr_el1 = 0x0; // run in el0 and enable all interrupt (DAIF) + unsigned long elr_el1 = data; + unsigned long user_sp = t -> user_stack + T_STACK_SIZE; + unsigned long kernel_sp = (unsigned long)t -> kernel_stack + T_STACK_SIZE; + // unsigned long kernel_sp = (unsigned long)kmalloc(T_STACK_SIZE) + T_STACK_SIZE; + + + // reset t call reg + t -> callee_reg.lr = data; + // core_timer_enable(); + // el1_interrupt_enable(); + + asm volatile("msr spsr_el1, %0" : : "r" (spsr_el1)); + asm volatile("msr elr_el1, %0" : : "r" (elr_el1)); + asm volatile("msr sp_el0, %0" : : "r" (user_sp)); + asm volatile("mov sp, %0" :: "r" (kernel_sp)); + // print_running(); + core_timer_enable(); + // el1_interrupt_enable(); + asm volatile("eret"); // jump to user program + // print_running(); +} + +void initrd_run_prog(const char* path_name) { + exec_path_name = (char*)kmalloc(strlen(path_name) + 1); + strcpy(exec_path_name, path_name); + // core_timer_disable(); + // el1_interrupt_disable(); + int tid = create_thread(initrd_exec_prog) -> tid; + thread_wait(tid); +} \ No newline at end of file diff --git a/lab8/src/kernel.c b/lab8/src/kernel.c new file mode 100644 index 000000000..84553bb60 --- /dev/null +++ b/lab8/src/kernel.c @@ -0,0 +1,74 @@ +#include "mini_uart.h" +#include "shell.h" +#include "alloc.h" +#include "fdt.h" +#include "initrd.h" +#include "c_utils.h" +#include "exception.h" +#include "thread.h" +#include "timer.h" +#include "fs_fsinit.h" +#include + +#define TEST_SIZE 30 + +extern char *dtb_base; + +void demo_mm() { + char* page_addr[TEST_SIZE]; + for(int i=0;i>3; +__stack_end = 0x0200000; \ No newline at end of file diff --git a/lab8/src/mbox.c b/lab8/src/mbox.c new file mode 100644 index 000000000..ef7eb085b --- /dev/null +++ b/lab8/src/mbox.c @@ -0,0 +1,67 @@ +#include "mbox.h" +#include "mini_uart.h" + + +int mailbox_call(unsigned char ch, unsigned int* mailbox){ + unsigned int r = (((unsigned long) mailbox) & ~0xF) | (ch & 0xf); + // ~0xF is used to clear the last 4 bits of the address + // 8 is used to set the last 4 bits to 8 (channel 8 is the mailbox channel) + + while(*MAILBOX_STATUS & MAILBOX_FULL){ + delay(1); + } // wait until the mailbox is not full + + *MAILBOX_WRITE = r; + + while(1){ + while(*MAILBOX_STATUS & MAILBOX_EMPTY){ + delay(1); + } // wait until the mailbox is not empty + + if(r == *MAILBOX_READ){ + return mailbox[1] == REQUEST_SUCCEED; + } + } +} + +void get_board_revision(){ + unsigned int mailbox[7]; + mailbox[0] = 7 * 4; // buffer size in bytes + mailbox[1] = REQUEST_CODE; + // tags begin + mailbox[2] = GET_BOARD_REVISION; // tag identifier + mailbox[3] = 4; // maximum of request and response value buffer's length. + mailbox[4] = TAG_REQUEST_CODE; + mailbox[5] = 0; // value buffer + // tags end + mailbox[6] = END_TAG; + + mailbox_call((unsigned char)0x8, mailbox); // message passing procedure call, you should implement it following the 6 steps provided above. + + uart_send_string("Board revision: "); + uart_hex(mailbox[5]); + uart_send_string("\n"); +} + +void get_memory_info() { + unsigned int mailbox[8]; + mailbox[0] = 8 * 4; // buffer size in bytes + mailbox[1] = REQUEST_CODE; + // tags begin + mailbox[2] = GET_ARM_MEMORY; // tag identifier + mailbox[3] = 8; // maximum of request and response value buffer's length. + mailbox[4] = TAG_REQUEST_CODE; // tag code + mailbox[5] = 0; // base address + mailbox[6] = 0; // size in bytes + mailbox[7] = END_TAG; // end tag + // tags end + mailbox_call((unsigned char)0x8, mailbox); + uart_send_string("ARM memory base address : "); + uart_hex(mailbox[5]); + uart_send_string("\n"); + + uart_send_string("ARM memory size : "); + uart_hex(mailbox[6]); + uart_send_string("\n"); + +} \ No newline at end of file diff --git a/lab8/src/mini_uart.c b/lab8/src/mini_uart.c new file mode 100644 index 000000000..17848a8c1 --- /dev/null +++ b/lab8/src/mini_uart.c @@ -0,0 +1,228 @@ +#include "mini_uart.h" +#include "c_utils.h" +#include "exception.h" +#include "peripherals/p_mini_uart.h" +#include "peripherals/gpio.h" +#include "peripherals/irq.h" +#include "tasklist.h" + +char uart_read_buffer[BUFFER_SIZE]; +char uart_write_buffer[BUFFER_SIZE]; +int uart_read_index = 0; +int uart_read_head = 0; +int uart_write_index = 0; +int uart_write_head = 0; + +void uart_send ( char c ) +{ + if (c == '\n') + uart_send('\r'); + while(1) { + if(*AUX_MU_LSR_REG&0x20) + break; + } + *AUX_MU_IO_REG = c; +} + +char uart_recv ( void ) +{ + while(1) { + if(*AUX_MU_LSR_REG&0x01) + break; + } + return(*AUX_MU_IO_REG); +} + +void uart_recvn(char *buffer, int n) +{ + while (n--) { + *buffer++ = uart_recv(); + } +} + +void uart_send_string(const char* str) +{ + while (*str) { + uart_send(*str++); + } +} + +void uart_sendn(const char* buffer, int n) +{ + while (*buffer && n--) { + uart_send(*buffer++); + } +} + +void irq_uart_rx_exception() { + if((uart_read_index + 1) % BUFFER_SIZE == uart_read_head) { + // buffer is full, discard the data + unsigned int ier = *AUX_MU_IER_REG; + // only enable receiver interrupt for now + ier &= ~0x01; + *AUX_MU_IER_REG = ier; + return; + } + char c = *AUX_MU_IO_REG&0xFF; + uart_read_buffer[uart_read_index++] = c; + if (uart_read_index >= BUFFER_SIZE) { + uart_read_index = 0; + } +} + +void irq_uart_tx_exception() { + if (uart_write_index != uart_write_head) { + *AUX_MU_IO_REG = uart_write_buffer[uart_write_index++]; + // uart_send_string("before delayed\n"); + // for(unsigned int i=0;i<100000000;i++){ + // asm volatile("nop"); + // asm volatile("nop"); + // asm volatile("nop"); + // } + // uart_send_string("after delayed\n"); + + // uart_send_string("delayed finished\n"); + if (uart_write_index >= BUFFER_SIZE) { + uart_write_index = 0; + } + unsigned int ier = *AUX_MU_IER_REG; + ier |= 0x02; + *AUX_MU_IER_REG = ier; + } else { + // no more data to send, disable transmit interrupt + unsigned int ier = *AUX_MU_IER_REG; + ier &= ~0x02; + *AUX_MU_IER_REG = ier; + } +} + +void uart_irq_handler() { + unsigned int iir = *AUX_MU_IIR_REG; + unsigned int ier = *AUX_MU_IER_REG; + ier &= ~0x02; + ier &= ~0x01; + *AUX_MU_IER_REG = ier; + // check if it's a receive interrupt + if ((iir & 0x06) == 0x04) { + // uart_send_string("Receive interrupt\n"); + create_task(irq_uart_rx_exception, 2); + execute_tasks_preemptive(); + } + // check if it's a transmit interrupt + if ((iir & 0x06) == 0x02) { + // uart_send_string("Transmit interrupt\n"); + create_task(irq_uart_tx_exception, 1); + execute_tasks_preemptive(); + } +} + + +char uart_async_recv( void ) { + + while (uart_read_index == uart_read_head) { + unsigned int ier = *AUX_MU_IER_REG; + // only enable receiver interrupt for now + ier |= 0x01; + *AUX_MU_IER_REG = ier; + } + + el1_interrupt_disable(); + char c = uart_read_buffer[uart_read_head++]; + if (uart_read_head >= BUFFER_SIZE) { + uart_read_head = 0; + } + el1_interrupt_enable(); + return c; +} + +void uart_async_send_string(const char* str){ + + // if the write buffer is full, wait for it to be empty + while ((uart_write_index + 1) % BUFFER_SIZE == uart_write_head) { + unsigned int ier = *AUX_MU_IER_REG; + // only enable transmit interrupt for now + ier |= 0x02; + *AUX_MU_IER_REG = ier; + } + el1_interrupt_disable(); + while(*str){ + if(*str == '\n') { + uart_write_buffer[uart_write_head++] = '\r'; + uart_write_buffer[uart_write_head++] = '\n'; + str++; + } else { + uart_write_buffer[uart_write_head++] = *str++; + } + if (uart_write_head >= BUFFER_SIZE) { + uart_write_head = 0; + } + } + el1_interrupt_enable(); + // enable transmit interrupt + unsigned int ier = *AUX_MU_IER_REG; + ier |= 0x02; + *AUX_MU_IER_REG = ier; + return; +} + +void uart_hex(unsigned int d) { + unsigned int n; + int c; + for (c = 28; c >= 0; c -= 4) { + // get highest tetrad + n = (d >> c) & 0xF; + // 0-9 => '0'-'9', 10-15 => 'A'-'F' + n += n > 9 ? 0x37 : 0x30; + uart_send(n); + } +} + +void uart_init ( void ) +{ + unsigned int selector; + + selector = *GPFSEL1; + selector &= ~(7<<12); // clean gpio14 + selector |= 2<<12; // set alt5 for gpio14 + selector &= ~(7<<15); // clean gpio15 + selector |= 2<<15; // set alt5 for gpio15 + *GPFSEL1 = selector; + + *GPPUD = 0; + delay(150); + *GPPUDCLK0 = (1<<14)|(1<<15); + delay(150); + *GPPUDCLK0 = 0; + + *AUX_ENABLES = 1; //Enable mini uart (this also enables access to its registers) + *AUX_MU_CNTL_REG = 0; //Disable auto flow control and disable receiver and transmitter (for now) + *AUX_MU_IER_REG = 0; //Disable receive and transmit interrupts + *AUX_MU_LCR_REG = 3; //Enable 8 bit mode + *AUX_MU_MCR_REG = 0; //Set RTS line to be always high + *AUX_MU_BAUD_REG = 270; //Set baud rate to 115200 + *AUX_MU_CNTL_REG = 3; //Finally, enable transmitter and receiver +} + +void uart_enable_interrupt( void ) { + unsigned int ier = *AUX_MU_IER_REG; + // only enable receiver interrupt for now + ier |= 0x01; + // ier |= 0x02; + *AUX_MU_IER_REG = ier; + + unsigned int enable_irqs_1 = *ENABLE_IRQS_1; + enable_irqs_1 |= 0x01 << 29; + *ENABLE_IRQS_1 = enable_irqs_1; +} + +void uart_disable_interrupt( void ) { + unsigned int ier = *AUX_MU_IER_REG; + // only enable receiver interrupt for now + ier &= ~0x01; + ier &= ~0x02; + *AUX_MU_IER_REG = ier; + + unsigned int enable_irqs_1 = *ENABLE_IRQS_1; + enable_irqs_1 &= ~(0x01 << 29); + *ENABLE_IRQS_1 = enable_irqs_1; +} diff --git a/lab8/src/mm.S b/lab8/src/mm.S new file mode 100644 index 000000000..463c9141e --- /dev/null +++ b/lab8/src/mm.S @@ -0,0 +1,19 @@ +.globl memzero +memzero: + str xzr, [x0], #8 + subs x1, x1, #8 + b.gt memzero + ret + +.globl memncpy +memncpy: + b memncpy_1_cmp +memncpy_1: + ldrb w3, [x1], #1 + strb w3, [x0], #1 + subs x2, x2, #1 +memncpy_1_cmp: + cmp x2, 1 + bge memncpy_1 +memncpy_ok: + ret \ No newline at end of file diff --git a/lab8/src/reboot.c b/lab8/src/reboot.c new file mode 100644 index 000000000..a6f370d5d --- /dev/null +++ b/lab8/src/reboot.c @@ -0,0 +1,11 @@ +#include "reboot.h" + +void reset(int tick) { // reboot after watchdog timer expire + *PM_RSTC = PM_PASSWORD | 0x20; // full reset + *PM_WDOG = PM_PASSWORD | tick; // number of watchdog tick +} + +void cancel_reset() { + *PM_RSTC = PM_PASSWORD | 0; // cancel reset + *PM_WDOG = PM_PASSWORD | 0; // set watchdog tick to 0 to prevent reset +} \ No newline at end of file diff --git a/lab8/src/shell.c b/lab8/src/shell.c new file mode 100644 index 000000000..0a0a0fbda --- /dev/null +++ b/lab8/src/shell.c @@ -0,0 +1,95 @@ +#include "mini_uart.h" +#include "mbox.h" +#include "reboot.h" +#include "c_utils.h" +#include "string.h" +#include "initrd.h" +#include "timer.h" +#include "thread.h" +#include "alloc.h" +#include "exception.h" +#include "thread.h" + +void shell(){ + uart_async_send_string("Welcome to OSC2024 shell!\n"); + while(1){ + uart_send_string("# "); + char* str = kmalloc(100); + uart_recv_command(str); + uart_send_string("\n"); + if(!strcmp(str, "hello")){ + uart_async_send_string("Hello World!\n"); + } else if(!strcmp(str, "mailbox")){ + uart_send_string("Mailbox info: \n"); + get_board_revision(); + get_memory_info(); + } + // else if(!strcmp(str, "ls")){ + // initrd_list(); + // } else if (!strcmp(str, "cat")){ + // uart_async_send_string("Enter file name: "); + // char file_name[100]; + // uart_recv_command(file_name); + // uart_async_send_string("\n"); + // initrd_cat(file_name); + // } + else if (!strcmp(str, "exec")){ + uart_async_send_string("Enter program name: "); + char file_name[100]; + uart_recv_command(file_name); + uart_async_send_string("\n"); + initrd_run_prog(file_name); + } else if(!strcmp(str, "run")){ + initrd_run_prog("/initramfs/vfs1.img"); + } + else if (!strncmp(str, "setTimeout", 10)){ + // str = setTimeout MESSAGE SECONDS + // split the message and seconds into two char* + // without using lib + + // find the first space + int i = 0; + while(str[i] != ' '){ + i++; + } + // find the second space + int j = i + 1; + while(str[j] != ' '){ + j++; + } + // copy the message + char message[100]; + for(int k = i + 1; k < j; k++){ + message[k - i - 1] = str[k]; + } + message[j - i - 1] = '\0'; + // copy the seconds + char seconds[100]; + for(int k = j + 1; k < strlen(str); k++){ + seconds[k - j - 1] = str[k]; + } + seconds[strlen(str) - j - 1] = '\0'; + // convert seconds to int + unsigned int timeout = atoi(seconds); + uart_send_string(message); + uart_send_string(" will be printed in "); + uart_send_string(seconds); + uart_send_string(" seconds\n"); + set_timeout(message, timeout); + } else if (!strcmp(str, "reboot")){ + uart_async_send_string("Rebooting...\n"); + reset(200); + break; + } else if(!strcmp(str, "help")){ + uart_async_send_string("help\t: print this help menu\n"); + uart_async_send_string("hello\t: print Hello World!\n"); + uart_async_send_string("mailbox\t: print board revision and memory info\n"); + // uart_async_send_string("ls\t: list files in the ramdisk\n"); + // uart_async_send_string("cat\t: print content of a file\n"); + uart_async_send_string("reboot\t: reboot this device\n"); + uart_async_send_string("exec\t: execute program from initramfs\n"); + uart_async_send_string("setTimeout\t: setTimeout MESSAGE SECONDS\n"); + } + } + thread_exit(); +} \ No newline at end of file diff --git a/lab8/src/signal.c b/lab8/src/signal.c new file mode 100644 index 000000000..0b42e68c2 --- /dev/null +++ b/lab8/src/signal.c @@ -0,0 +1,66 @@ +#include +#include "alloc.h" +#include "signal.h" +#include "syscall.h" +#include "string.h" +#include "thread.h" + + +void check_and_run_signal() { + thread_t* cur_thread = get_current_thread(); + // if(cur_thread -> tid == 3) { + // uart_send_string("[signal] chcecking for tid: "); + // uart_hex(cur_thread->tid); + // uart_send_string("\n"); + // uart_send_string("[signal] signal 9: "); + // uart_hex(cur_thread->waiting_signal[9]); + // uart_send_string("\n"); + // } + for(int i=0;i<=SIGNAL_NUM;i++) { + if(cur_thread->waiting_signal[i]) { + cur_thread->waiting_signal[i] = 0; + exec_signal(cur_thread, i); + } + } +} + +void exec_signal(thread_t* t, int signal) { + if(t -> is_processing_signal) return; // don't need to handle nested signal + t -> is_processing_signal = 1; + uart_send_string("enter signal handler\n"); + // default signal handler can be run in kernel mode + if(!t->signal_handler[signal]) { + default_signal_handler(); + return; + } + // copy current callee register to thread's callee register + memcpy((void*)&t->signal_regs, (void*)&t->callee_reg, sizeof(callee_reg_t)); + + // set new user stack for signal handler + t -> callee_reg.sp = (uint64_t)kmalloc(T_STACK_SIZE) + T_STACK_SIZE; + t -> callee_reg.fp = t -> callee_reg.sp; + t -> callee_reg.lr = handler_warper; + + unsigned long spsr_el1 = 0x0; // run in el0 and enable all interrupt (DAIF) + unsigned long elr_el1 = (unsigned long)handler_warper; + unsigned long user_sp = t -> callee_reg.sp; + // switch to user mode + asm volatile("msr spsr_el1, %0" : : "r" (spsr_el1)); + asm volatile("msr elr_el1, %0" : : "r" (elr_el1)); + asm volatile("msr sp_el0, %0" : : "r" (user_sp)); + asm volatile("mov x0, %0" : : "r" (t->signal_handler[signal])); + asm volatile("eret"); // jump to user program +} + +void handler_warper(void (*handler)()) { + handler(); + sys_sigreturn(); +} + +void default_signal_handler() { + uart_send_string("Default signal handler\n"); + uart_send_string("current thread tid: "); + uart_hex(get_current_thread()->tid); + uart_send_string("\n"); + kill(get_current_thread()->tid); +} \ No newline at end of file diff --git a/lab8/src/string.c b/lab8/src/string.c new file mode 100644 index 000000000..4b7462373 --- /dev/null +++ b/lab8/src/string.c @@ -0,0 +1,110 @@ +#include "string.h" +#include +#include "mini_uart.h" + +unsigned int is_visible(unsigned int c){ + if(c >= 32 && c <= 126){ + return 1; + } + return 0; +} + +int strcmp(const char *s1, const char *s2) { + while (*s1 && *s2 && (*s1 == *s2)) { + s1++; + s2++; + } + return *s1 - *s2; +} + + +int memcmp(const void *str1, const void *str2, int n) { + const unsigned char *a = str1, *b = str2; + while (n-- > 0) { + if (*a != *b) { + return *a - *b; + } + a++; + b++; + } + return 0; +} + +void *memcpy(void *dst, const void *src, size_t len) +{ + size_t i; + + /* + * memcpy does not support overlapping buffers, so always do it + * forwards. (Don't change this without adjusting memmove.) + * + * For speedy copying, optimize the common case where both pointers + * and the length are word-aligned, and copy word-at-a-time instead + * of byte-at-a-time. Otherwise, copy by bytes. + * + * The alignment logic below should be portable. We rely on + * the compiler to be reasonably intelligent about optimizing + * the divides and modulos out. Fortunately, it is. + */ + + if ((uintptr_t)dst % sizeof(long) == 0 && + (uintptr_t)src % sizeof(long) == 0 && + len % sizeof(long) == 0) { + long *d = dst; + const long *s = src; + + for (i=0; i + +int getpid() { + thread_t* t = get_current_thread(); + return t -> tid; +} + +size_t uart_read(char buf[], size_t size) { + size_t i; + for(i=0;itid); + uart_send_string("\n"); + regs->x19 = get_current_thread()->callee_reg.x19; + regs->x20 = get_current_thread()->callee_reg.x20; + regs->x21 = get_current_thread()->callee_reg.x21; + regs->x22 = get_current_thread()->callee_reg.x22; + regs->x23 = get_current_thread()->callee_reg.x23; + regs->x24 = get_current_thread()->callee_reg.x24; + regs->x25 = get_current_thread()->callee_reg.x25; + regs->x26 = get_current_thread()->callee_reg.x26; + regs->x27 = get_current_thread()->callee_reg.x27; + regs->x28 = get_current_thread()->callee_reg.x28; + regs->fp = get_current_thread()->callee_reg.fp; + regs->lr = get_current_thread()->callee_reg.lr; + regs->sp = get_current_thread()->callee_reg.sp; +} + +int exec(const char* name, char *const argv[]) { + char* target_addr; + char *filepath; + char *filedata; + unsigned int filesize; + // current pointer + cpio_t *header_pointer = (cpio_t *)(ramfs_base); + + // print every cpio pathname + while (header_pointer) + { + + int error = cpio_newc_parse_header(header_pointer, &filepath, &filesize, &filedata, &header_pointer); + // if parse header error + if (error) + { + // uart_printf("error\n"); + uart_send_string("Error parsing cpio header\n"); + break; + } + if (!strcmp(name, filepath)) + { + target_addr = (char*)kmalloc(filesize); + memcpy(target_addr, filedata, filesize); + break; + } + + // if this is not TRAILER!!! (last of file) + if (header_pointer == 0){ + uart_send_string("Program not found\n"); + return 1; + } + } + // run program from current thread + thread_t* cur_thread = get_current_thread(); + + // TODO: signal handling, should clear all signal handler here + for(int i=0;i<=SIGNAL_NUM;i++) { + cur_thread -> signal_handler[i] = 0; + cur_thread -> waiting_signal[i] = 0; + } + + cur_thread -> callee_reg.lr = (unsigned long)target_addr; + + unsigned long spsr_el1 = 0x0; // run in el0 and enable all interrupt (DAIF) + unsigned long elr_el1 = cur_thread -> callee_reg.lr; + unsigned long user_sp = cur_thread -> callee_reg.sp; + unsigned long kernel_sp = (unsigned long)cur_thread -> kernel_stack + T_STACK_SIZE; + + // "r": Any general-purpose register, except sp + asm volatile("msr tpidr_el1, %0" : : "r" (cur_thread)); + asm volatile("msr spsr_el1, %0" : : "r" (spsr_el1)); + asm volatile("msr elr_el1, %0" : : "r" (elr_el1)); + asm volatile("msr sp_el0, %0" : : "r" (user_sp)); + asm volatile("mov sp, %0" :: "r" (kernel_sp)); + asm volatile("eret"); // jump to user program + return 0; +} + +void fork(trapframe_t* tf) { + // uart_send_string("forked\n"); + thread_t* parent_thread = get_current_thread(); + thread_t* child_thread = create_fork_thread(); + + memcpy( + (void*)child_thread->user_stack, + (void*)parent_thread->user_stack, + T_STACK_SIZE + ); + + // copy kernel stack + memcpy( + (void*)child_thread->kernel_stack, + (void*)parent_thread->kernel_stack, + T_STACK_SIZE + ); + save_regs(parent_thread); + copy_regs(&child_thread->callee_reg); + + // copy signal_handler + for(int i=0;i<=SIGNAL_NUM;i++) { + child_thread -> signal_handler[i] = parent_thread -> signal_handler[i]; + child_thread -> waiting_signal[i] = parent_thread -> waiting_signal[i]; + } + + uint64_t parent_sp = get_current_thread() -> callee_reg.sp; + uint64_t parent_fp = get_current_thread() -> callee_reg.fp; + + + child_thread -> callee_reg.sp = (uint64_t)((uint64_t)parent_sp - (uint64_t)parent_thread->kernel_stack + (uint64_t)child_thread->kernel_stack); + child_thread -> callee_reg.fp = (uint64_t)((uint64_t)parent_fp - (uint64_t)parent_thread->kernel_stack + (uint64_t)child_thread->kernel_stack); + + void* label_address = &&SYSCALL_FORK_END; + uart_send_string("Address of SYSCALL_FORK_END: "); + uart_hex((uint64_t)label_address); + uart_send_string("\n"); + + child_thread -> callee_reg.lr = &&SYSCALL_FORK_END; + + // set child trapframe + trapframe_t *child_tf = (trapframe_t*)(child_thread -> kernel_stack + + ((char*)tf - (char*)(parent_thread -> kernel_stack)) + ); + // set child user stack sp + child_tf -> x[0] = 0; + child_tf -> x[30] = tf -> x[30]; + child_tf -> sp_el0 = (void*)((uint64_t)tf -> sp_el0 - (uint64_t)parent_thread->user_stack + (uint64_t)child_thread->user_stack); + child_tf -> spsr_el1 = tf -> spsr_el1; + child_tf -> elr_el1 = tf -> elr_el1; + push_running(child_thread); + tf -> x[0] = child_thread -> tid; +SYSCALL_FORK_END: + int tid = get_current_thread() -> tid; + uart_send_string("forked tid: "); + uart_hex(tid); + uart_send_string("\nforked end\n"); + asm volatile("nop"); + return; +} + +void exit(int status) { + thread_exit(); +} + +int mbox_call(unsigned char ch, unsigned int *mbox) { + return mailbox_call(ch, mbox); +} + +void kill(int pid) { + kill_thread(pid); +} + +void signal(int signal, void (*handler)()) { + thread_t* t = get_current_thread(); + uart_send_string("register signal\n"); + uart_hex(t->tid); + uart_send_string("\n"); + uart_send_string("singal num: "); + uart_hex(signal); + uart_send_string("\n"); + t -> signal_handler[signal] = handler; +} + +void posix_kill(int pid, int signal) { + thread_t* t = get_thread_from_tid(pid); + if(!t) return; + uart_send_string("[set signal] tid: "); + uart_hex(t->tid); + uart_send_string("\n"); + t -> waiting_signal[signal] = 1; +} + +void sigreturn() { + thread_t* t = get_current_thread(); + // uart_send_string("sigreturn\n tid: "); + // uart_hex(t->tid); + // uart_send_string("\n"); + kfree(t -> callee_reg.fp - T_STACK_SIZE); + load_regs(t->signal_regs); + t -> is_processing_signal = 0; + return; +} + + +int sys_getpid() { + int res; + asm volatile("mov x8, 0"); + asm volatile("svc 0"); + asm volatile("mov %0, x0": "=r"(res)); + return res; +} + +int sys_fork() { + int res; + asm volatile("mov x8, 4"); + asm volatile("svc 0"); + asm volatile("mov %0, x0": "=r"(res)); + return res; +} + +void sys_exit(int status){ + asm volatile("mov x8, 5"); + asm volatile("svc 0"); +} + +void sys_posix_kill(){ + asm volatile("mov x8, 9"); + asm volatile("svc 0"); +} + +void sys_sigreturn() { + asm volatile("mov x8, 20"); + asm volatile("svc 0"); +} \ No newline at end of file diff --git a/lab8/src/tasklist.c b/lab8/src/tasklist.c new file mode 100644 index 000000000..f0b742969 --- /dev/null +++ b/lab8/src/tasklist.c @@ -0,0 +1,93 @@ +#include "tasklist.h" +#include "alloc.h" +#include "exception.h" +#include "mini_uart.h" + +task_t *task_head = 0; +int cur_priority = 100; + + +void create_task(task_callback_t callback, unsigned long long priority) { + el1_interrupt_disable(); + task_t* task = (task_t*)kmalloc(sizeof(task_t)); + if(!task) return; + + task->callback = callback; + task->priority = priority; + + enqueue_task(task); + el1_interrupt_enable(); +} + +void enqueue_task(task_t *task) { + el1_interrupt_disable(); + // uart_send_string("enqueue task\n"); + if(!task_head || (task->priority < task_head->priority)) { + task -> next = task_head; + task -> prev = 0; + if(task_head) + task_head->prev = task; + task_head = task; + } else { + task_t *current = task_head; + while(current->next && current->next->priority <= task->priority) { + current = current->next; + } + task->next = current->next; + task->prev = current; + if (current -> next){ + current -> next -> prev = task; + } + current -> next = task; + } + el1_interrupt_enable(); +} + +void execute_tasks() { + // el1_interrupt_disable(); + while(task_head) { + el1_interrupt_disable(); + task_t* cur = task_head; + cur -> callback(); + task_head = cur->next; + if(task_head) { + task_head -> prev = 0; + } + kfree(cur); + el1_interrupt_enable(); + } + // el1_interrupt_enable(); +} + +void execute_tasks_preemptive() { + el1_interrupt_enable(); + while(task_head) { + el1_interrupt_disable(); + task_t* new_task = task_head; + + if(cur_priority <= new_task->priority) { + el1_interrupt_enable(); + break; + } + + task_head = task_head->next; + if(task_head) { + task_head -> prev = 0; + } + int prev_priority = cur_priority; + cur_priority = new_task->priority; + + el1_interrupt_enable(); + // running in preemptive mode + + new_task -> callback(); + + el1_interrupt_disable(); + cur_priority = prev_priority; + kfree(new_task); + + el1_interrupt_enable(); + + // free the task: TDB + } +} \ No newline at end of file diff --git a/lab8/src/thread.c b/lab8/src/thread.c new file mode 100644 index 000000000..85feab104 --- /dev/null +++ b/lab8/src/thread.c @@ -0,0 +1,400 @@ +#include "thread.h" +#include "alloc.h" +#include "mini_uart.h" +#include "c_utils.h" +#include "syscall.h" +#include "timer.h" +#include "shell.h" +#include "alloc.h" +#include + +// define three queues +thread_t* running_q_head = 0; +thread_t* zombie_q_head = 0; + +thread_t* idle_thread = 0; + +int cur_tid = 0; +int cnt_ = 0; + +is_init_thread = 0; + +void push(thread_t** head, thread_t* t) { + // uart_send_string("push thread\n"); + // uart_hex(t->tid); + // uart_send_string("\n"); + // uart_hex(running_q_head->tid); + // uart_send_string("\n"); + // if(running_q_head) + // print_queue(running_q_head); + // uart_hex((*head)->tid); + el1_interrupt_disable(); + if(!(*head)) { + t -> prev = t; + t -> next = t; + (*head) = t; + } else { + t->prev = (*head)->prev; + t->next = (*head); + (*head)->prev->next = t; + (*head)->prev = t; + } + // print_queue(running_q_head); +} + +void print_running() { + if(running_q_head){ + uart_send_string("running q head addr: "); + uart_hex(running_q_head); + uart_send_string("\n"); + print_queue(running_q_head); + page_info_addr(running_q_head); + } +} + +void push_running(thread_t* t) { + push(&running_q_head, t); +} + +thread_t* pop(thread_t** head) { + if(!(*head)) return 0; + thread_t* res = (*head); + el1_interrupt_disable(); + if(res -> next == res){ + (*head) = 0; + } else { + res -> prev -> next = res -> next; + res -> next -> prev = res -> prev; + (*head) = res -> next; + } + el1_interrupt_enable(); + return res; +} + +void pop_t(thread_t** head, thread_t* t) { + el1_interrupt_disable(); + if(!(*head)) return; + if(t -> next == t){ + (*head) = 0; + } else { + t -> prev -> next = t -> next; + t -> next -> prev = t -> prev; + } + el1_interrupt_enable(); +} + +void print_queue(thread_t* head) { + uart_send_string("[INFO] Print Queue:\n["); + thread_t* cur = head; + do { + uart_hex(cur->tid); + uart_send_string(", "); + cur = cur -> next; + } while(cur != head); + uart_send_string("]\n"); +} + +int is_more_than_two_thread() { + thread_t* cur = running_q_head; + int cnt = 0; + do { + cnt++; + cur = cur -> next; + } while(cur != running_q_head); + return cnt > 2; +} + +void schedule() { + el1_interrupt_disable(); + core_timer_disable(); + thread_t* cur_thread = get_current_thread(); + thread_t* next_thread; + if(cur_thread -> state != TASK_RUNNING) { + if(!running_q_head) return; + next_thread = running_q_head; + } else { + next_thread = cur_thread -> next; + } + if (next_thread && next_thread->tid != cur_thread->tid) { + // + // uart_send_string("[SCHEDULE] Switching from "); + // uart_hex(cur_thread->tid); + // uart_send_string(" to "); + // uart_hex(next_thread->tid); + // uart_send_string("\n"); + switch_to(cur_thread, next_thread); + } + core_timer_enable(); + el1_interrupt_enable(); +} + +void kill_zombies() { + el1_interrupt_disable(); + while(zombie_q_head) { + uart_send_string(": "); + print_queue(zombie_q_head); + thread_t* zombie = pop(&zombie_q_head); + uart_send_string("[KILL] killing tid="); + uart_hex(zombie->tid); + uart_send_string("\n"); + kfree(zombie->user_stack); + kfree(zombie->kernel_stack); + // close files + for(int i=0;i <= zombie -> max_fd;i++) { + if(zombie->fds[i].vnode) { + vfs_close(&zombie->fds[i]); + } + } + kfree(zombie); + uart_send_string("[KILL] killed\n"); + } + el1_interrupt_enable(); +} + +thread_t* create_thread(void (*func)(void)) { + thread_t* t = (thread_t*)kmalloc(sizeof(thread_t)); + t -> tid = cur_tid ++; + t -> state = TASK_RUNNING; + t -> callee_reg.lr = (unsigned long)func; + t -> user_stack = kmalloc(T_STACK_SIZE); + t -> kernel_stack = kmalloc(T_STACK_SIZE); + t -> callee_reg.sp = (unsigned long)(t->user_stack + T_STACK_SIZE); + t -> callee_reg.fp = t -> callee_reg.sp; // set fp to sp as the pointer that fixed + + // init signal + for(int i=0;i<=SIGNAL_NUM;i++) { + t -> signal_handler[i] = 0; + t -> waiting_signal[i] = 0; + } + t -> is_processing_signal = 0; + + t -> prev = 0; + t -> next = 0; + + t -> working_dir = rootfs -> root; + for(int i=0;i fds[i].vnode = 0; + } + vfs_open("/dev/uart", 0, &t->fds[0]); + vfs_open("/dev/uart", 0, &t->fds[1]); + vfs_open("/dev/uart", 0, &t->fds[2]); + t -> max_fd = 2; // because we have 3 fds + + // TODO: pass data into function + uart_send_string("creating thread\n"); + // if(running_q_head) + // print_queue(running_q_head); + push(&running_q_head, t); + print_queue(running_q_head); + return t; +} + +thread_t* create_fork_thread() { + thread_t* t = (thread_t*)kmalloc(sizeof(thread_t)); + t -> tid = cur_tid ++; + t -> state = TASK_RUNNING; + t -> callee_reg.lr = 0; + t -> user_stack = kmalloc(T_STACK_SIZE); + t -> kernel_stack = kmalloc(T_STACK_SIZE); + t -> callee_reg.sp = (unsigned long)(t->user_stack + T_STACK_SIZE); + t -> callee_reg.fp = t -> callee_reg.sp; // set fp to sp as the pointer that fixed + + for(int i=0;i<=SIGNAL_NUM;i++) { + t -> signal_handler[i] = 0; + t -> waiting_signal[i] = 0; + } + t -> is_processing_signal = 0; + + t -> prev = 0; + t -> next = 0; + + t -> working_dir = rootfs -> root; + for(int i=0;i fds[i].vnode = 0; + } + + vfs_open("/dev/uart", 0, &t->fds[0]); + vfs_open("/dev/uart", 0, &t->fds[1]); + vfs_open("/dev/uart", 0, &t->fds[2]); + t -> max_fd = 2; + + + return t; +} + + +thread_t* get_thread_from_tid(int tid) { + thread_t* cur = running_q_head; + do { + if(cur -> tid == tid) { + return cur; + } + cur = cur -> next; + } while(cur != running_q_head); + + return 0; +} + +void kill_thread(int tid) { + thread_t* t = get_thread_from_tid(tid); + if(!t) return; + t -> state = TASK_ZOMBIE; + running_q_head = t -> next; + pop_t(&running_q_head, t); + push(&zombie_q_head, t); + uart_send_string("[kill]\n"); + schedule(); +} + +void thread_init() { + uart_send_string("[INFO] Init thread\n"); + idle_thread = create_thread(idle); + asm volatile("msr tpidr_el1, %0" :: "r"(idle_thread)); + is_init_thread = 1; +} + +void thread_exit() { + thread_t* cur = get_current_thread(); + uart_send_string("[EXIT] tid="); + uart_hex(cur->tid); + uart_send_string("\n"); + cur -> state = TASK_ZOMBIE; + running_q_head = cur -> next; + pop_t(&running_q_head, cur); + push(&zombie_q_head, cur); + schedule(); +} + +void thread_wait(int tid) { + thread_t* cur = running_q_head; + + while(1) { + el1_interrupt_disable(); + int running = 0; + thread_t* cur = running_q_head; + do { + if(cur -> tid == tid){ + running = 1; + break; + } + cur = cur -> next; + } while(cur != running_q_head); + if(running) { + schedule(); + } else { + break; + } + el1_interrupt_enable(); + } + el1_interrupt_enable(); +} + +void idle() { + while(1) { + kill_zombies(); + schedule(); + } +} + +void foo(){ + for(int i = 0; i < 10; ++i) { + uart_send_string("Thread id: "); + uart_hex(get_current_thread()->tid); + uart_send_string(" "); + uart_hex(i); + uart_send_string("\n"); + delay(1000000); + schedule(); + } + thread_exit(); +} + +void thread_test() { + for(int i=0;i<3;i++) { + uart_send_string("[INFO] Thread id: "); + uart_hex(i); + uart_send_string("\n"); + create_thread(foo); + } + // print_queue(running_q_head); + idle(); +} + +void run_fork_test() { + thread_t* t = get_current_thread(); + uart_hex(t->tid); + uart_send_string("\n"); + uart_hex(t->callee_reg.sp); + for(int i=0;i<1000000;i++); + asm volatile("msr spsr_el1, %0" ::"r"(0x3c0)); // disable E A I F + asm volatile("msr elr_el1, %0" ::"r"(main_fork_test)); // get back to caller function + asm volatile("msr sp_el0, %0" ::"r"(t->callee_reg.sp)); + asm volatile("mov sp, %0" ::"r"(t->kernel_stack + T_STACK_SIZE)); + asm volatile("eret"); +} + +void main_fork_test(){ + uart_send_string("\nFork Test, pid: "); + uart_hex(sys_getpid()); + uart_send_string("\n"); + int cnt = 1; + int ret = 0; + if ((ret = sys_fork()) == 0) { // child + long long cur_sp; + asm volatile("mov %0, sp" : "=r"(cur_sp)); + uart_send_string("first child pid: "); + uart_hex(sys_getpid()); + uart_send_string(", cnt:"); + uart_hex(cnt); + uart_send_string(", ptr:"); + uart_hex(&cnt); + uart_send_string(", sp: "); + uart_hex(cur_sp); + uart_send_string("\n"); + ++cnt; + + if ((ret = sys_fork()) != 0){ + asm volatile("mov %0, sp" : "=r"(cur_sp)); + uart_send_string("first child pid: "); + uart_hex(sys_getpid()); + uart_send_string(", cnt:"); + uart_hex(cnt); + uart_send_string(", ptr:"); + uart_hex(&cnt); + uart_send_string(", sp: "); + uart_hex(cur_sp); + uart_send_string("\n"); + } + else { + while (cnt < 5) { + asm volatile("mov %0, sp" : "=r"(cur_sp)); + uart_send_string("second child pid: "); + uart_hex(sys_getpid()); + uart_send_string(", cnt: "); + uart_hex(cnt); + uart_send_string(", ptr: "); + uart_hex(&cnt); + uart_send_string(", sp: "); + uart_hex(cur_sp); + uart_send_string("\n"); + for(int i=0;i<1000000;i++); + ++cnt; + } + } + sys_exit(0); + } + else { + uart_send_string("parent here, pid "); + uart_hex(sys_getpid()); + uart_send_string(", child "); + uart_hex(ret); + uart_send_string("\n"); + print_queue(running_q_head); + } + sys_exit(0); +} + +void fork_test() { + create_thread(run_fork_test); + idle(); +} \ No newline at end of file diff --git a/lab8/src/timer.c b/lab8/src/timer.c new file mode 100644 index 000000000..dbbd25dca --- /dev/null +++ b/lab8/src/timer.c @@ -0,0 +1,114 @@ +#include "timer.h" +#include "mini_uart.h" +#include "string.h" +#include "alloc.h" +#include "c_utils.h" +#include "exception.h" +#include "thread.h" +#include "mini_uart.h" + +timer_t *timer_head = 0; + +void print_message(void *data) { + char* message = data; + unsigned long long current_time, cntfrq; + asm volatile("mrs %0, cntpct_el0" : "=r"(current_time)); + asm volatile("mrs %0, cntfrq_el0" : "=r"(cntfrq)); + + unsigned int sec = current_time / cntfrq; + + uart_send_string("\nTimeout message: "); + uart_send_string(message); + uart_send_string(" at "); + uart_hex(sec); + uart_send_string(" seconds\n# "); +} + +void set_timeout(char* message, unsigned long long timeout) { + char* message_copy = (char*)kmalloc(strlen(message)+1); + strncpy_(message_copy, message, strlen(message)+1); + if(!message_copy) return; + + if(!timer_head) { + // enable timer + *CORE0_TIMER_IRQ_CTRL = 2; + } + + create_timer(print_message, message_copy, timeout); +} + +void create_timer( + timer_callback_t callback, + void *data, + unsigned long long timeout +) { + timer_t *timer = (timer_t*)kmalloc(sizeof(timer_t)); + if(!timer) return; + + timer->callback = callback; + timer->data = data; + timer->next = 0; + + unsigned long long current_time, cntfrq; + asm volatile("mrs %0, cntpct_el0" : "=r"(current_time)); + asm volatile("mrs %0, cntfrq_el0" : "=r"(cntfrq)); + + timer->timeout = current_time + timeout * cntfrq; + + add_timer(&timer); +} + +void add_timer(timer_t **timer) { + el1_interrupt_disable(); + // uart_send_string("add timer\n"); + // if the timer list is empty or the new timer is the first to expire + if(!timer_head) uart_send_string("timer_head is null\n"); + if(!timer_head || (timer_head -> timeout > (*timer) -> timeout)) { + (*timer) -> next = timer_head; + timer_head = *timer; + asm volatile("msr cntp_ctl_el0, %0" : : "r"(1)); + asm volatile("msr cntp_cval_el0, %0" : : "r"(timer_head -> timeout)); + el1_interrupt_enable(); + return; + } + + timer_t *current = timer_head; + + // find the correct position to insert the new timer + while(current -> next && current -> next -> timeout < (*timer) -> timeout) { + current = current -> next; + } + + (*timer) -> next = current -> next; + current -> next = (*timer); + + el1_interrupt_enable(); +} + + +void create_timer_freq_shift( + timer_callback_t callback, + void *data, + unsigned long long shift +) { + timer_t *timer = (timer_t*)kmalloc(sizeof(timer_t)); + if(!timer) return; + + timer->callback = callback; + timer->data = data; + + unsigned long long current_time, cntfrq; + asm volatile("mrs %0, cntpct_el0" : "=r"(current_time)); + asm volatile("mrs %0, cntfrq_el0" : "=r"(cntfrq)); + + timer->timeout = current_time + (cntfrq >> shift); + // uart_send_string("timeout: "); + // uart_hex(timer->timeout); + // uart_send_string("\n"); + add_timer(&timer); +} + +void schedule_task(void* data) { + // uart_send_string("Schedule task\n"); + create_timer_freq_shift(schedule_task, 0, 5); +} \ No newline at end of file diff --git a/lab8/user_prog/Makefile b/lab8/user_prog/Makefile new file mode 100644 index 000000000..1cf1797fa --- /dev/null +++ b/lab8/user_prog/Makefile @@ -0,0 +1,28 @@ +ARMGNU ?= aarch64-linux-gnu + +COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only +ASMOPS = -Iinclude + +BUILD_DIR = build + +all : user_prog.img + +clean : + rm -rf $(BUILD_DIR) *.img + + +$(BUILD_DIR)/%_s.o: %.S + mkdir -p $(@D) + $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ + +C_FILES = $(wildcard *.c) +ASM_FILES = $(wildcard *.S) +OBJ_FILES = $(C_FILES:%.c=$(BUILD_DIR)/%_c.o) +OBJ_FILES += $(ASM_FILES:%.S=$(BUILD_DIR)/%_s.o) + +DEP_FILES = $(OBJ_FILES:%.o=%.d) +-include $(DEP_FILES) + +user_prog.img: linker.ld $(OBJ_FILES) + $(ARMGNU)-ld -T linker.ld -o $(BUILD_DIR)/user_prog.elf $(OBJ_FILES) + $(ARMGNU)-objcopy $(BUILD_DIR)/user_prog.elf -O binary user_prog.img \ No newline at end of file diff --git a/lab8/user_prog/linker.ld b/lab8/user_prog/linker.ld new file mode 100644 index 000000000..8c825e31d --- /dev/null +++ b/lab8/user_prog/linker.ld @@ -0,0 +1,9 @@ + +SECTIONS +{ + . = 0x80000; + .text : + { + *(.text) + } +} \ No newline at end of file diff --git a/lab8/user_prog/user_prog.S b/lab8/user_prog/user_prog.S new file mode 100644 index 000000000..838323763 --- /dev/null +++ b/lab8/user_prog/user_prog.S @@ -0,0 +1,11 @@ +.section ".text" +.global _start +_start: + mov x0, 0 +1: + add x0, x0, 1 + svc 0 + cmp x0, 5 + blt 1b +1: + b 1b \ No newline at end of file diff --git a/lab8/user_prog/user_prog.img b/lab8/user_prog/user_prog.img new file mode 100755 index 0000000000000000000000000000000000000000..1adf648a4c1b9d2d1b5e9fa08b84e52ef2fafc78 GIT binary patch literal 24 gcmZQzXt>0{!Z4AMf#Hh02*bzK|Nn Date: Thu, 27 Jun 2024 17:15:40 +0800 Subject: [PATCH 3/3] lab6: finished --- lab6/a.out | 477 -------------------------------- lab6/bootloader/include/utils.h | 14 + lab6/bootloader/src/mini_uart.c | 5 +- lab6/include/alloc.h | 5 +- lab6/include/exec.h | 7 + lab6/include/initrd.h | 2 +- lab6/include/mbox.h | 4 +- lab6/include/mini_uart.h | 4 +- lab6/include/mmu.h | 24 ++ lab6/include/thread.h | 5 +- lab6/include/utils.h | 42 +++ lab6/initramfs.cpio | Bin 247296 -> 248320 bytes lab6/rootfs/file1 | 1 - lab6/rootfs/file2.txt | 1 - lab6/rootfs/test3 | 0 lab6/rootfs/user_prog.img | Bin 24 -> 0 bytes lab6/src/alloc.c | 22 +- lab6/src/boot.S | 18 +- lab6/src/context_switch.S | 12 + lab6/src/exception.c | 23 +- lab6/src/exec.S | 39 +++ lab6/src/initrd.c | 79 ++++-- lab6/src/kernel.c | 5 +- lab6/src/linker.ld | 8 +- lab6/src/mbox.c | 10 +- lab6/src/mini_uart.c | 92 +++--- lab6/src/mmu.c | 165 +++++++++++ lab6/src/reboot.c | 10 +- lab6/src/shell.c | 20 +- lab6/src/signal.c | 2 +- lab6/src/syscall.c | 95 +++++-- lab6/src/thread.c | 12 + lab6/src/timer.c | 4 +- lab6/src/utils.S | 9 + 34 files changed, 595 insertions(+), 621 deletions(-) delete mode 100644 lab6/a.out create mode 100644 lab6/include/exec.h create mode 100644 lab6/include/mmu.h create mode 100644 lab6/include/utils.h delete mode 100644 lab6/rootfs/file1 delete mode 100644 lab6/rootfs/file2.txt delete mode 100644 lab6/rootfs/test3 delete mode 100755 lab6/rootfs/user_prog.img create mode 100644 lab6/src/exec.S create mode 100644 lab6/src/mmu.c create mode 100644 lab6/src/utils.S diff --git a/lab6/a.out b/lab6/a.out deleted file mode 100644 index cf8b4cebd..000000000 --- a/lab6/a.out +++ /dev/null @@ -1,477 +0,0 @@ -qemu-system-aarch64 -M raspi3b -kernel kernel8.img -display none -serial null -serial stdio -initrd initramfs.cpio -dtb bcm2710-rpi-3-b-plus.dtb -DTB base address: 08200000 -ramfs_end: 0803C600 -ramfs_base: 08000000 -total_page: 0003B400 -memory_reserve: 00000000 ~ 00001000 -memory_reserve: 08000000 ~ 0803C600 -memory_reserve: 08200000 ~ 08215988 -memory_reserve: 00080000 ~ 00200000 -old_heap_top: 10000000 -heap_top: 10942000 -memory_reserve: 10000000 ~ 10942000 -mm finished -[INFO] Init thread -[INFO] Print Queue: -[00000000, ] -Welcome to OSC2024 shell! -# -# chunk_alloc: 00000001 (size: 00000000) - -no available chunk -Requesting 00001000 bytes, order: 00000000 -Checking free_list[00000000] = 00082C14 -truncate (addr: 0x000878A0, idx: 00080168, val: 10297480, order: 00000000) to order 00000000 -Allocated (addr: 0x000878A0, idx: 00080168, val: FFFFFFFE, order: 00000000) -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 -new chunk: 0x000978A0 -000878B0 \ No newline at end of file diff --git a/lab6/bootloader/include/utils.h b/lab6/bootloader/include/utils.h index acd33ddc2..be983f494 100644 --- a/lab6/bootloader/include/utils.h +++ b/lab6/bootloader/include/utils.h @@ -1,6 +1,20 @@ #ifndef _UTILS_H #define _UTILS_H +// Reference from https://elixir.bootlin.com/linux/latest/source/tools/lib/perf/mmap.c#L299 +#define read_sysreg(r) ({ \ + uint64_t __val; \ + asm volatile("mrs %0, " #r : "=r" (__val)); \ + __val; \ +}) + +// Reference from https://elixir.bootlin.com/linux/latest/source/arch/arm64/include/asm/sysreg.h#L1281 +#define write_sysreg(r, v) do { \ + uint64_t __val = (uint64_t)(v); \ + asm volatile("msr " #r ", %x0" \ + : : "rZ" (__val)); \ +} while (0) + extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); diff --git a/lab6/bootloader/src/mini_uart.c b/lab6/bootloader/src/mini_uart.c index 55f6b7e4c..6c77c79e0 100644 --- a/lab6/bootloader/src/mini_uart.c +++ b/lab6/bootloader/src/mini_uart.c @@ -1,6 +1,7 @@ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" +#include void uart_send ( char c ) { @@ -39,10 +40,10 @@ void uart_send_string(char* str) } } -void uart_hex(unsigned int d) { +void uart_hex(uint64_t d) { unsigned int n; int c; - for (c = 28; c >= 0; c -= 4) { + for (c = 60; c >= 0; c -= 4) { // get highest tetrad n = (d >> c) & 0xF; // 0-9 => '0'-'9', 10-15 => 'A'-'F' diff --git a/lab6/include/alloc.h b/lab6/include/alloc.h index 0b182a695..499682b79 100644 --- a/lab6/include/alloc.h +++ b/lab6/include/alloc.h @@ -1,8 +1,9 @@ #ifndef _ALLOC_H_ #define _ALLOC_H_ +#include "utils.h" -#define PAGE_BASE (void*)0x0 -#define PAGE_END (void*)0x3b400000 +#define PAGE_BASE (void*)PA2VA(0x0) +#define PAGE_END (void*)PA2VA(0x3b400000) #define PAGE_SIZE 0x1000 // 4KB #define MAX_ORDER 11 diff --git a/lab6/include/exec.h b/lab6/include/exec.h new file mode 100644 index 000000000..650cff151 --- /dev/null +++ b/lab6/include/exec.h @@ -0,0 +1,7 @@ +#ifndef _EXEC_H +#define _EXEC_H + +void exec_user_prog(void *entry, char *user_sp, char *kernel_sp); +void enter_el0_run_user_prog(void *entry, char *user_sp); + +#endif /* _EXEC_H */ \ No newline at end of file diff --git a/lab6/include/initrd.h b/lab6/include/initrd.h index 59f088984..cef7e4927 100644 --- a/lab6/include/initrd.h +++ b/lab6/include/initrd.h @@ -35,5 +35,5 @@ void initrd_callback(unsigned int node_type, char *name, void *value, unsigned i void initrd_exec_prog(char* target); void initrd_exec_syscall(); void initrd_run_syscall(); -void exec_user_prog (); + #endif // INITRD_H \ No newline at end of file diff --git a/lab6/include/mbox.h b/lab6/include/mbox.h index 6a05d1782..b5f3c5ef9 100644 --- a/lab6/include/mbox.h +++ b/lab6/include/mbox.h @@ -1,8 +1,10 @@ #ifndef _MBOX_H #define _MBOX_H +#include "utils.h" + #define MMIO_BASE 0x3f000000 -#define MAILBOX_BASE MMIO_BASE + 0xb880 +#define MAILBOX_BASE PA2VA(MMIO_BASE + 0xb880) #define MAILBOX_READ (unsigned int*)(MAILBOX_BASE) #define MAILBOX_STATUS (unsigned int*)(MAILBOX_BASE + 0x18) diff --git a/lab6/include/mini_uart.h b/lab6/include/mini_uart.h index 83c9d42e9..e209c8d34 100644 --- a/lab6/include/mini_uart.h +++ b/lab6/include/mini_uart.h @@ -3,13 +3,15 @@ #define BUFFER_SIZE 1024 +#include + void uart_init ( void ); void uart_enable_interrupt( void ); void uart_disable_interrupt( void ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(const char* str); -void uart_hex(unsigned int d); +void uart_hex(uint64_t d); void uart_irq_handler(void); char uart_async_recv( void ); void uart_async_send_string(const char* buffer); diff --git a/lab6/include/mmu.h b/lab6/include/mmu.h new file mode 100644 index 000000000..d4e1c05d5 --- /dev/null +++ b/lab6/include/mmu.h @@ -0,0 +1,24 @@ +#ifndef _MMU_H +#define _MMU_H + + +#define PAGE_TABLE_SIZE 0x1000 + +#define PT_R 0x0001 +#define PT_W 0x0002 +#define PT_X 0x0004 + +#include + +/* + * Set identity paging, enable MMU + */ +void mmu_init(void); + +uint64_t *pt_create(void); +void pt_free(uint64_t *pt); + +void pt_map(uint64_t *pt, void *va, uint64_t size, void *pa, uint64_t flag); + + +#endif /* _MMU_H */ \ No newline at end of file diff --git a/lab6/include/thread.h b/lab6/include/thread.h index 8a3b24a1b..f9b025b58 100644 --- a/lab6/include/thread.h +++ b/lab6/include/thread.h @@ -2,7 +2,7 @@ #define _THREAD_H -#define T_STACK_SIZE (2 * 0x1000) // 2^12 = 4096 = 4KB = 1 page +#define T_STACK_SIZE (4 * 0x1000) // 2^12 = 4096 = 4KB = 1 page #define SIGNAL_NUM 9 #include @@ -33,8 +33,10 @@ typedef struct callee_reg_t { typedef struct thread_t { // need to be put as the first variable callee_reg_t callee_reg; + uint64_t *page_table; int tid; // thread id thread_state state; + void* user_stack; void* kernel_stack; void* data; @@ -46,6 +48,7 @@ typedef struct thread_t { int waiting_signal[SIGNAL_NUM+1]; int is_processing_signal; callee_reg_t signal_regs; + uint64_t signal_page_table; // use in queue struct thread_t *prev; diff --git a/lab6/include/utils.h b/lab6/include/utils.h new file mode 100644 index 000000000..87cbc6467 --- /dev/null +++ b/lab6/include/utils.h @@ -0,0 +1,42 @@ +#ifndef _UTILS_H +#define _UTILS_H + + +#include + +#define PA2VA(x) (((uint64_t)(x)) | 0xffff000000000000) +#define VA2PA(x) (((uint64_t)(x)) & 0x0000ffffffffffff) +#define ALIGN(num, base) ((num + base - 1) & ~(base - 1)) + + +// Reference from https://elixir.bootlin.com/linux/latest/source/tools/lib/perf/mmap.c#L299 +#define read_sysreg(r) ({ \ + uint64_t __val; \ + asm volatile("mrs %0, " #r : "=r" (__val)); \ + __val; \ +}) + +// Reference from https://elixir.bootlin.com/linux/latest/source/arch/arm64/include/asm/sysreg.h#L1281 +#define write_sysreg(r, v) do { \ + uint64_t __val = (uint64_t)(v); \ + asm volatile("msr " #r ", %x0" \ + : : "rZ" (__val)); \ +} while (0) + +#define set_page_table(thread) do { \ + asm volatile( \ + "mov x9, %0\n" \ + "and x9, x9, #0x0000ffffffffffff\n" \ + "dsb ish\n" \ + "msr ttbr0_el1, x9\n" \ + "tlbi vmalle1is\n" \ + "dsb ish\n" \ + "isb\n" \ + :: "r" (thread->page_table) \ + ); \ +} while (0) + +extern void put32 ( unsigned long, unsigned int ); +extern unsigned int get32 ( unsigned long ); + +#endif /*_UTILS_H */ diff --git a/lab6/initramfs.cpio b/lab6/initramfs.cpio index 0676fb1584617bc4d73624b3b212a12133e97d8e..06cb275e0104febd98f0fd2974670e8460d8db72 100644 GIT binary patch delta 2005 zcmd^9UuauZ82|3Q$(p4m*|M}Z|5)x;Dq9CzlE$=|554(Qr%X^no>XkJrk1*;bXr?w z&fauFha0%#DO4DkZ1}JhrG+S45N3*O;DboZ9PDYgEk5})1SLp}-?{h3rA~YjeDH+a zbIN=nzfdreNF@@nz95cBXfVa*VCb2rw#~s{Z#3GMOvO@z zi-p@lCm#;8StO&*W0BHOjG z{21V<@Zh>h9*(#hZSq<}fLNn35K{#p0ZI4)ZKV7n_Z(*i8wm^a6TatQfrkb4h+F{^ z>{gFJCC=8%5L1`sSl|GhUj40+kvSzjQ+~)F9uIThz5nCuFXz zpBowF2_>DNYi({`^3$K%_V%kblHy>B!D*k=f@lp7~C2hEH+R;?9G~v*?>? z<*w@7&o@=+MY+VS;2Gk%k0btMD;_+w-Fj%&Lyn`$ms>r<9!+JisZxN9U{cciK}mBk zhYIJ`UqLqYGSoIM%X()h9uh+ym90w-)=aSt0eZCZRy`z)K8BSrR>F?@(?83)4FNp} zINB;g)kQcA5rxM@DCNjvn<9^G`E zi0aG$*<23!yWh4lg*du%`kUs*zJL9#{3NsMde0>~<`W$p{`nh;4#B(Ptlklr8$LGX X(w)VL+rme9-2dtLzvqA9U#|Wiq1V(0 delta 1111 zcmZ`(TS!x39RGjkOr7(x%h4_HvLilt3H0ceZoPEGh6NQg_Yf92Z=`4!Q*2?dV?HF* zgCC!;$D}6{N@I}hp_&xzQK$?gh_fV!z^5VzS?hn!j-=> z3aXEb0tJv0b$6f&J=Q_Wtwn7;UE7=Hp;_;T1u6pESNq|Yfn(OfT22(=t%ZHWX~>)a z7@z-{4e1b3U=NErqK2Y2+WnplDWyvA^zUp;-7+F?K_ac)YwmK+UBkZK%ZA3YSyyKE z)#D5gm=fpVBDx2#1h?jbKGe{AY7za4J-5hjXJ$87l6?P%%VLQEi_}bpF zR7tHlX{TSXb&c(n40B04>n(lB$&R>CHbct84yp>q*-JX&vGN@gTM^3Gq%hAKD|T#o zzm*otjj>_YRVl>Xl@?+eiHuEaEB4fBVH?i6@;oReVV|6X^AXp*h>PIGZA4uBk@ece ZH$2V&!bTl6YVQ9dmQL{vvF#M!{|ByIO#=V` diff --git a/lab6/rootfs/file1 b/lab6/rootfs/file1 deleted file mode 100644 index 433eb1727..000000000 --- a/lab6/rootfs/file1 +++ /dev/null @@ -1 +0,0 @@ -this is file1 diff --git a/lab6/rootfs/file2.txt b/lab6/rootfs/file2.txt deleted file mode 100644 index f13882009..000000000 --- a/lab6/rootfs/file2.txt +++ /dev/null @@ -1 +0,0 @@ -this is file2 diff --git a/lab6/rootfs/test3 b/lab6/rootfs/test3 deleted file mode 100644 index e69de29bb..000000000 diff --git a/lab6/rootfs/user_prog.img b/lab6/rootfs/user_prog.img deleted file mode 100755 index 1adf648a4c1b9d2d1b5e9fa08b84e52ef2fafc78..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24 gcmZQzXt>0{!Z4AMf#Hh02*bzK|Nn int debug = 1; @@ -13,6 +14,9 @@ volatile char *heap_top; page* page_arr = 0; uint64_t total_page = 0; +// free_list_t* free_list = 0; +// chunk_info* chunk_info_arr = 0; + free_list_t free_list[MAX_ORDER + 1]; chunk_info chunk_info_arr[MAX_CHUNK + 1]; @@ -296,6 +300,9 @@ void insert_page(page* new_page, int order) { if (free_list[order].head != 0) { free_list[order].head -> prev = new_page; } + if(!new_page){ + uart_send_string("Error: insert_page with new_page = 0\n"); + } free_list[order].head = new_page; free_list[order].cnt++; return; @@ -309,10 +316,10 @@ page* pop_page(int order) { while(1); } page* ret = free_list[order].head; - free_list[order].head = ret->next; if (free_list[order].head -> next != 0) { - free_list[order].head -> prev = 0; + free_list[order].head -> next -> prev = 0; } + free_list[order].head = ret->next; free_list[order].cnt--; return ret; } @@ -430,21 +437,24 @@ void* chunk_free(void* addr) { // Set heap base address void alloc_init() { - heap_top = ((volatile unsigned char *)(0x10000000)); + + heap_top = ((volatile unsigned char *)PA2VA(0x10000000)); char* old_heap_top = heap_top; + // free_list = PA2VA(simple_malloc((MAX_ORDER + 1) * sizeof(free_list_t))); + // chunk_info_arr = PA2VA(simple_malloc((MAX_CHUNK + 1) * sizeof(chunk_info))); // uart_send_string("heap_top: "); // uart_hex((unsigned long long)heap_top); // uart_send_string("\n"); init_page_arr(); // reserve memory - memory_reserve((void*)0x0000, (void*)0x1000); // spin tables + memory_reserve((void*)PA2VA(0x0000), (void*)PA2VA(0x4000)); // spin tables and page tables memory_reserve((void*)ramfs_base, (void*)ramfs_end); // ramfs // memory_reserve((void*)dtb_base, (void*)dtb_end); // dtb // kernel, bss, stack // 0x80000 = _start // 0x0200000 = __stack_end - memory_reserve((void*)0x80000, (void*)0x0200000); + memory_reserve((void*)PA2VA(0x80000), (void*)PA2VA(0x0200000)); if(debug) { uart_send_string("old_heap_top: "); @@ -555,7 +565,7 @@ void* kmalloc(unsigned long long size) { int idx = size2chunkidx(size); void* addr; - int use_page_only = 0; + int use_page_only = 1; if(use_page_only) { addr = page_alloc(size); } else if(idx >= 0) { diff --git a/lab6/src/boot.S b/lab6/src/boot.S index 596e47891..0c698c1ae 100644 --- a/lab6/src/boot.S +++ b/lab6/src/boot.S @@ -1,4 +1,5 @@ #include "mm.h" + #define CORE0_TIMER_IRQ_CTRL 0x40000040 .section ".text.boot" @@ -17,14 +18,15 @@ proc_hang: master: bl from_el2_to_el1 -// core_timer_enable: -// mov x20, 1 -// msr cntp_ctl_el0, x20 // enable timer -// mrs x20, cntfrq_el0 -// msr cntp_tval_el0, x20 // set expired time -// mov x20, 2 -// ldr x1, =CORE0_TIMER_IRQ_CTRL -// str w20, [x1] // unmask timer interrupt + ldr x1, =__PA_stack_end + mov sp, x1 + mov x19, x0 + bl mmu_init + mov x0, x19 + ldr x1, =boot_rest + br x1 + +boot_rest: set_exception_vector: // set exception vector table diff --git a/lab6/src/context_switch.S b/lab6/src/context_switch.S index 467c1c9dc..5a93ae49f 100644 --- a/lab6/src/context_switch.S +++ b/lab6/src/context_switch.S @@ -17,7 +17,19 @@ switch_to: ldp fp, lr, [x1, 16 * 5] ldr x9, [x1, 16 * 6] mov sp, x9 + msr tpidr_el1, x1 + + // switch page table + ldr x9, [x1, 8 * 13] // page_table in thread_t + and x9, x9, #0x0000ffffffffffff + dsb ish // ensure write has completed + msr ttbr0_el1, x9 // switch translation based address. + tlbi vmalle1is // invalidate all TLB entries + dsb ish // ensure completion of TLB invalidatation + isb // clear pipeline + + ret diff --git a/lab6/src/exception.c b/lab6/src/exception.c index 53757854b..8389b1fc7 100644 --- a/lab6/src/exception.c +++ b/lab6/src/exception.c @@ -10,6 +10,7 @@ #include "thread.h" #include "reboot.h" #include "signal.h" +#include "utils.h" extern timer_t *timer_head; @@ -56,8 +57,8 @@ void exception_handler_c() { void user_irq_exception_handler_c() { // uart_send_string("User IRQ Exception Occurs!\n"); el1_interrupt_disable(); - unsigned int irq = *IRQ_PENDING_1; - unsigned int interrupt_source = *CORE0_INTERRUPT_SOURCE; + unsigned int irq = get32(PA2VA(IRQ_PENDING_1)); + unsigned int interrupt_source = get32(PA2VA(CORE0_INTERRUPT_SOURCE)); if((irq & IRQ_PENDING_1_AUX_INT) && (interrupt_source & INTERRUPT_SOURCE_GPU)){ // uart_send_string("UART interrupt\n"); @@ -74,8 +75,8 @@ void user_irq_exception_handler_c() { void irq_exception_handler_c(){ // uart_send_string("IRQ Exception Occurs!\n"); - unsigned int irq = *IRQ_PENDING_1; - unsigned int interrupt_source = *CORE0_INTERRUPT_SOURCE; + unsigned int irq = get32(PA2VA(IRQ_PENDING_1)); + unsigned int interrupt_source = get32(PA2VA(CORE0_INTERRUPT_SOURCE)); if((irq & IRQ_PENDING_1_AUX_INT) && (interrupt_source & INTERRUPT_SOURCE_GPU) ){ // uart_send_string("kernel UART interrupt\n"); @@ -93,7 +94,15 @@ void irq_exception_handler_c(){ } void user_exception_handler_c(trapframe_t* tf) { + unsigned long long esr_el1 = 0; + asm volatile("mrs %0, esr_el1":"=r"(esr_el1)); + unsigned ec = (esr_el1 >> 26) & 0x3F; + if(ec != 0x15) { + exception_handler_c(); + return; + } int syscall_code = tf -> x[8]; + el1_interrupt_enable(); switch (syscall_code) { case 0: @@ -120,6 +129,7 @@ void user_exception_handler_c(trapframe_t* tf) { exit(0); break; case 6: + uart_send_string("[INFO] system call: mbox_call\n"); tf -> x[0] = mbox_call( (unsigned char)tf -> x[0], (unsigned int*)tf -> x[1] ); @@ -201,6 +211,7 @@ void core_timer_init() { asm volatile("msr cntp_tval_el0, %0" : : "r"(freq)); *CORE0_TIMER_IRQ_CTRL = 2; + uint64_t tmp; asm volatile("mrs %0, cntkctl_el1" : "=r"(tmp)); @@ -209,9 +220,9 @@ void core_timer_init() { } void core_timer_enable(){ - *CORE0_TIMER_IRQ_CTRL = 2; + put32(PA2VA(CORE0_TIMER_IRQ_CTRL), 2); } void core_timer_disable() { - *CORE0_TIMER_IRQ_CTRL = 0; + put32(PA2VA(CORE0_TIMER_IRQ_CTRL), 0); } \ No newline at end of file diff --git a/lab6/src/exec.S b/lab6/src/exec.S new file mode 100644 index 000000000..8f10058ed --- /dev/null +++ b/lab6/src/exec.S @@ -0,0 +1,39 @@ + +.globl enter_el0_run_user_prog +enter_el0_run_user_prog: + // Set exception return address + msr elr_el1, x0 + + // Set user stack + msr sp_el0, x1 + + // Enable interrupt ({D, A, I, F} = 0 (unmasked)) + // EL0 ({M[3:0]} = 0) + mov x0, 0 + msr spsr_el1, x0 + + // TODO: Clear all general registers + + // return to EL0 + eret + +.globl exec_user_prog +exec_user_prog: + // Set exception return address + msr elr_el1, x0 + + // Set user stack + msr sp_el0, x1 + + // Enable interrupt ({D, A, I, F} = 0 (unmasked)) + // EL0 ({M[3:0]} = 0) + mov x0, 0 + msr spsr_el1, x0 + + // Set kernel stack + mov sp, x2 + + // TODO: Clear all general registers + + // return to EL0 + eret \ No newline at end of file diff --git a/lab6/src/initrd.c b/lab6/src/initrd.c index 5d05f71e8..c558e3265 100644 --- a/lab6/src/initrd.c +++ b/lab6/src/initrd.c @@ -8,11 +8,24 @@ #include "thread.h" #include "exception.h" #include "timer.h" +#include "utils.h" +#include "mmu.h" +#include "exec.h" char *ramfs_base; char *ramfs_end; +void enter_el0_run_user_prog(void *entry, char *user_sp); + +static void user_prog_start(void) +{ + uart_send_string("User program start\n"); + enter_el0_run_user_prog((void *)0, (char *)0xffffffffeff0); + + // User program should call exit() to terminate +} + // Convert hexadecimal string to int // @param s: hexadecimal string // @param n: string length @@ -157,12 +170,14 @@ void initrd_cat(const char *target) void initrd_callback(unsigned int node_type, char *name, void *value, unsigned int name_size){ if(!strcmp(name, "linux,initrd-start")){ ramfs_base = (char *)(unsigned long long)endian_big2little(*(unsigned int *)value); + ramfs_base = (char *)PA2VA(ramfs_base); uart_send_string("ramfs_base: "); uart_hex((unsigned long)ramfs_base); uart_send_string("\n"); } if(!strcmp(name, "linux,initrd-end")){ ramfs_end = (char *)(unsigned long long)endian_big2little(*(unsigned int *)value); + ramfs_end = (char *)PA2VA(ramfs_end); uart_send_string("ramfs_end: "); uart_hex((unsigned long)ramfs_end); uart_send_string("\n"); @@ -234,16 +249,20 @@ void initrd_exec_syscall() { char *filepath; char *filedata; unsigned int filesize; - char* target = "syscall.img"; + char* target = "vm.img"; + thread_t* t = get_current_thread(); + uart_send_string("current thread tid: "); + uart_hex(t -> tid); + uart_send_string("\n"); // current pointer cpio_t *header_pointer = (cpio_t *)(ramfs_base); // print_running(); // print every cpio pathname while (header_pointer) { - // uart_send_string("header_pointer: "); - // uart_hex((unsigned long)header_pointer); - // uart_send_string("\n"); + uart_send_string("header_pointer: "); + uart_hex((unsigned long)header_pointer); + uart_send_string("\n"); int error = cpio_newc_parse_header(header_pointer, &filepath, &filesize, &filedata, &header_pointer); // if parse header error if (error) @@ -257,9 +276,10 @@ void initrd_exec_syscall() { uart_send_string("filesize: "); uart_hex(filesize); uart_send_string("\n"); - target_addr = kmalloc(filesize); + t -> data = (void*)kmalloc(filesize); + t -> data_size = filesize; uart_send_string("Copying user program\n"); - memcpy(target_addr, filedata, filesize); + memcpy(t -> data, filedata, t -> data_size); uart_send_string("Finished\n"); break; } @@ -268,14 +288,13 @@ void initrd_exec_syscall() { // uart_send_string("\n"); // if this is not TRAILER!!! (last of file) if (header_pointer == 0){ - uart_send_string("Program not found\n"); + // uart_send_string("Program not found\n"); return; } } uart_send_string("prog addr: "); - uart_hex(target_addr); + uart_hex(t->data); uart_send_string("\n"); - thread_t* t = get_current_thread(); uart_send_string("thread tid: "); uart_hex(t -> tid); uart_send_string("\n"); @@ -286,26 +305,42 @@ void initrd_exec_syscall() { } unsigned long spsr_el1 = 0x0; // run in el0 and enable all interrupt (DAIF) - unsigned long elr_el1 = target_addr; - unsigned long user_sp = t -> user_stack + T_STACK_SIZE; + unsigned long elr_el1 = 0x0; unsigned long kernel_sp = (unsigned long)t -> kernel_stack + T_STACK_SIZE; // unsigned long kernel_sp = (unsigned long)kmalloc(T_STACK_SIZE) + T_STACK_SIZE; - // reset t call reg - t -> callee_reg.lr = target_addr; - // core_timer_enable(); - // el1_interrupt_enable(); + // t -> callee_reg.lr = user_prog_start; + t -> page_table = pt_create(); + pt_map(t -> page_table, (void*)0, t -> data_size, + (void*)VA2PA(t -> data), PT_R | PT_W | PT_X); // map user program + pt_map(t -> page_table, (void*)0xffffffffb000, T_STACK_SIZE, + (void*)VA2PA(t -> user_stack), PT_R | PT_W); // map user stack + + pt_map(t->page_table, (void *)0x3c000000, 0x04000000, + (void *)0x3c000000, PT_R | PT_W); // map mailbox - asm volatile("msr spsr_el1, %0" : : "r" (spsr_el1)); - asm volatile("msr elr_el1, %0" : : "r" (elr_el1)); - asm volatile("msr sp_el0, %0" : : "r" (user_sp)); - asm volatile("mov sp, %0" :: "r" (kernel_sp)); - // print_running(); + uart_send_string("page table addr: "); + uart_hex(t -> page_table); + uart_send_string("\n"); + + set_page_table(t); core_timer_enable(); // el1_interrupt_enable(); - asm volatile("eret"); // jump to user program - // print_running(); + uart_send_string("exec user prog\n"); + exec_user_prog((void *)0, (char *)0xffffffffeff0, kernel_sp); + // return; + // // core_timer_enable(); + // // el1_interrupt_enable(); + + // asm volatile("msr spsr_el1, %0" : : "r" (spsr_el1)); + // asm volatile("msr elr_el1, %0" : : "r" (elr_el1)); + // asm volatile("msr sp_el0, %0" : : "r" (user_sp)); + // asm volatile("mov sp, %0" :: "r" (kernel_sp)); + // // print_running(); + // // el1_interrupt_enable(); + // asm volatile("eret"); // jump to user program + // // print_running(); } void initrd_run_syscall() { diff --git a/lab6/src/kernel.c b/lab6/src/kernel.c index 991d96633..f9ce3ffd7 100644 --- a/lab6/src/kernel.c +++ b/lab6/src/kernel.c @@ -7,6 +7,7 @@ #include "exception.h" #include "thread.h" #include "timer.h" +#include "utils.h" #include #define TEST_SIZE 30 @@ -46,14 +47,14 @@ void demo_mm() { } void main(char* base) { - dtb_base = base; + dtb_base = PA2VA(base); uart_init(); uart_send_string("DTB base address: "); uart_hex(dtb_base); uart_send_string("\n"); fdt_traverse(initrd_callback); alloc_init(); - // demo_mm(); + // for(int i=0;i<1000;i++) kmalloc(PAGE_SIZE); uart_send_string("mm finished\n"); uart_enable_interrupt(); core_timer_init(); diff --git a/lab6/src/linker.ld b/lab6/src/linker.ld index bd7ba3352..6db10ceb8 100644 --- a/lab6/src/linker.ld +++ b/lab6/src/linker.ld @@ -1,7 +1,8 @@ SECTIONS { - . = 0x80000; + . = 0xffff000000000000; + . += 0x80000; .text : { KEEP(*(.text.boot)) @@ -17,5 +18,6 @@ SECTIONS bss_end = .; } } -__bss_size = (bss_end - bss_begin)>>3; -__stack_end = 0x0200000; \ No newline at end of file +__bss_size = (bss_end - bss_begin)>>3; +__stack_end = 0xffff000000200000; +__PA_stack_end = 0x0000000000200000; \ No newline at end of file diff --git a/lab6/src/mbox.c b/lab6/src/mbox.c index ef7eb085b..4c421aa15 100644 --- a/lab6/src/mbox.c +++ b/lab6/src/mbox.c @@ -1,24 +1,24 @@ #include "mbox.h" #include "mini_uart.h" - +#include "utils.h" int mailbox_call(unsigned char ch, unsigned int* mailbox){ unsigned int r = (((unsigned long) mailbox) & ~0xF) | (ch & 0xf); // ~0xF is used to clear the last 4 bits of the address // 8 is used to set the last 4 bits to 8 (channel 8 is the mailbox channel) - while(*MAILBOX_STATUS & MAILBOX_FULL){ + while(get32(PA2VA(MAILBOX_STATUS)) & MAILBOX_FULL){ delay(1); } // wait until the mailbox is not full - *MAILBOX_WRITE = r; + put32(PA2VA(MAILBOX_WRITE), r); // write the address of the mailbox message to the mailbox write register (channel 8 is the mailbox channel while(1){ - while(*MAILBOX_STATUS & MAILBOX_EMPTY){ + while(get32(PA2VA(MAILBOX_STATUS)) & MAILBOX_EMPTY){ delay(1); } // wait until the mailbox is not empty - if(r == *MAILBOX_READ){ + if(r == get32(PA2VA(MAILBOX_READ))){ return mailbox[1] == REQUEST_SUCCEED; } } diff --git a/lab6/src/mini_uart.c b/lab6/src/mini_uart.c index 63e7585e8..ab3b9bb69 100644 --- a/lab6/src/mini_uart.c +++ b/lab6/src/mini_uart.c @@ -5,6 +5,7 @@ #include "peripherals/gpio.h" #include "peripherals/irq.h" #include "tasklist.h" +#include "utils.h" char uart_read_buffer[BUFFER_SIZE]; char uart_write_buffer[BUFFER_SIZE]; @@ -18,19 +19,19 @@ void uart_send ( char c ) if (c == '\n') uart_send('\r'); while(1) { - if(*AUX_MU_LSR_REG&0x20) + if(get32(PA2VA(AUX_MU_LSR_REG))&0x20) break; } - *AUX_MU_IO_REG = c; + put32(PA2VA(AUX_MU_IO_REG),c); } char uart_recv ( void ) { while(1) { - if(*AUX_MU_LSR_REG&0x01) + if(get32(PA2VA(AUX_MU_LSR_REG))&0x01) break; } - return(*AUX_MU_IO_REG); + return(get32(PA2VA(AUX_MU_IO_REG))); } void uart_send_string(const char* str) @@ -43,13 +44,13 @@ void uart_send_string(const char* str) void irq_uart_rx_exception() { if((uart_read_index + 1) % BUFFER_SIZE == uart_read_head) { // buffer is full, discard the data - unsigned int ier = *AUX_MU_IER_REG; + unsigned int ier = get32(PA2VA(AUX_MU_IER_REG)); // only enable receiver interrupt for now ier &= ~0x01; - *AUX_MU_IER_REG = ier; + put32(PA2VA(AUX_MU_IER_REG), ier); return; } - char c = *AUX_MU_IO_REG&0xFF; + char c = get32(PA2VA(AUX_MU_IO_REG))&0xFF; uart_read_buffer[uart_read_index++] = c; if (uart_read_index >= BUFFER_SIZE) { uart_read_index = 0; @@ -58,7 +59,7 @@ void irq_uart_rx_exception() { void irq_uart_tx_exception() { if (uart_write_index != uart_write_head) { - *AUX_MU_IO_REG = uart_write_buffer[uart_write_index++]; + put32(PA2VA(AUX_MU_IO_REG), uart_write_buffer[uart_write_index++]); // uart_send_string("before delayed\n"); // for(unsigned int i=0;i<100000000;i++){ // asm volatile("nop"); @@ -71,23 +72,23 @@ void irq_uart_tx_exception() { if (uart_write_index >= BUFFER_SIZE) { uart_write_index = 0; } - unsigned int ier = *AUX_MU_IER_REG; + unsigned int ier = get32(PA2VA(AUX_MU_IER_REG)); ier |= 0x02; - *AUX_MU_IER_REG = ier; + put32(PA2VA(AUX_MU_IER_REG), ier); } else { // no more data to send, disable transmit interrupt - unsigned int ier = *AUX_MU_IER_REG; + unsigned int ier = get32(PA2VA(AUX_MU_IER_REG)); ier &= ~0x02; - *AUX_MU_IER_REG = ier; + put32(PA2VA(AUX_MU_IER_REG), ier); } } void uart_irq_handler() { - unsigned int iir = *AUX_MU_IIR_REG; - unsigned int ier = *AUX_MU_IER_REG; + unsigned int iir = get32(PA2VA(AUX_MU_IIR_REG)); + unsigned int ier = get32(PA2VA(AUX_MU_IER_REG)); ier &= ~0x02; ier &= ~0x01; - *AUX_MU_IER_REG = ier; + put32(PA2VA(AUX_MU_IER_REG), ier); // check if it's a receive interrupt if ((iir & 0x06) == 0x04) { // uart_send_string("Receive interrupt\n"); @@ -106,10 +107,10 @@ void uart_irq_handler() { char uart_async_recv( void ) { while (uart_read_index == uart_read_head) { - unsigned int ier = *AUX_MU_IER_REG; + unsigned int ier = get32(PA2VA(AUX_MU_IER_REG)); // only enable receiver interrupt for now ier |= 0x01; - *AUX_MU_IER_REG = ier; + put32(PA2VA(AUX_MU_IER_REG), ier); } el1_interrupt_disable(); @@ -125,10 +126,10 @@ void uart_async_send_string(const char* str){ // if the write buffer is full, wait for it to be empty while ((uart_write_index + 1) % BUFFER_SIZE == uart_write_head) { - unsigned int ier = *AUX_MU_IER_REG; + unsigned int ier = get32(PA2VA(AUX_MU_IER_REG)); // only enable transmit interrupt for now ier |= 0x02; - *AUX_MU_IER_REG = ier; + put32(PA2VA(AUX_MU_IER_REG), ier); } el1_interrupt_disable(); while(*str){ @@ -145,16 +146,16 @@ void uart_async_send_string(const char* str){ } el1_interrupt_enable(); // enable transmit interrupt - unsigned int ier = *AUX_MU_IER_REG; + unsigned int ier = get32(PA2VA(AUX_MU_IER_REG)); ier |= 0x02; - *AUX_MU_IER_REG = ier; + put32(PA2VA(AUX_MU_IER_REG), ier); return; } -void uart_hex(unsigned int d) { +void uart_hex(uint64_t d) { unsigned int n; int c; - for (c = 28; c >= 0; c -= 4) { + for (c = 60; c >= 0; c -= 4) { // get highest tetrad n = (d >> c) & 0xF; // 0-9 => '0'-'9', 10-15 => 'A'-'F' @@ -163,52 +164,55 @@ void uart_hex(unsigned int d) { } } + void uart_init ( void ) { unsigned int selector; - selector = *GPFSEL1; + selector = get32(PA2VA(GPFSEL1)); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 - *GPFSEL1 = selector; + put32(PA2VA(GPFSEL1),selector); - *GPPUD = 0; + put32(PA2VA(GPPUD),0); delay(150); - *GPPUDCLK0 = (1<<14)|(1<<15); + put32(PA2VA(GPPUDCLK0),(1<<14)|(1<<15)); delay(150); - *GPPUDCLK0 = 0; - - *AUX_ENABLES = 1; //Enable mini uart (this also enables access to its registers) - *AUX_MU_CNTL_REG = 0; //Disable auto flow control and disable receiver and transmitter (for now) - *AUX_MU_IER_REG = 0; //Disable receive and transmit interrupts - *AUX_MU_LCR_REG = 3; //Enable 8 bit mode - *AUX_MU_MCR_REG = 0; //Set RTS line to be always high - *AUX_MU_BAUD_REG = 270; //Set baud rate to 115200 - *AUX_MU_CNTL_REG = 3; //Finally, enable transmitter and receiver + put32(PA2VA(GPPUDCLK0),0); + + put32(PA2VA(AUX_ENABLES),1); //Enable mini uart (this also enables access to its registers) + put32(PA2VA(AUX_MU_CNTL_REG),0); //Disable auto flow control and disable receiver and transmitter (for now) + put32(PA2VA(AUX_MU_IER_REG),0); //Disable receive and transmit interrupts + put32(PA2VA(AUX_MU_LCR_REG),3); //Enable 8 bit mode + put32(PA2VA(AUX_MU_MCR_REG),0); //Set RTS line to be always high + put32(PA2VA(AUX_MU_BAUD_REG),270); //Set baud rate to 115200 + put32(PA2VA(AUX_MU_CNTL_REG),3); //Finally, enable transmitter and receiver } + void uart_enable_interrupt( void ) { - unsigned int ier = *AUX_MU_IER_REG; + unsigned int ier = get32(PA2VA(AUX_MU_IER_REG)); // only enable receiver interrupt for now ier |= 0x01; // ier |= 0x02; - *AUX_MU_IER_REG = ier; + put32(PA2VA(AUX_MU_IER_REG), ier); + - unsigned int enable_irqs_1 = *ENABLE_IRQS_1; + unsigned int enable_irqs_1 = get32(PA2VA(ENABLE_IRQS_1)); enable_irqs_1 |= 0x01 << 29; - *ENABLE_IRQS_1 = enable_irqs_1; + put32(PA2VA(ENABLE_IRQS_1), enable_irqs_1); } void uart_disable_interrupt( void ) { - unsigned int ier = *AUX_MU_IER_REG; + unsigned int ier = get32(PA2VA(AUX_MU_IER_REG)); // only enable receiver interrupt for now ier &= ~0x01; ier &= ~0x02; - *AUX_MU_IER_REG = ier; + put32(PA2VA(AUX_MU_IER_REG), ier); - unsigned int enable_irqs_1 = *ENABLE_IRQS_1; + unsigned int enable_irqs_1 = get32(PA2VA(ENABLE_IRQS_1)); enable_irqs_1 &= ~(0x01 << 29); - *ENABLE_IRQS_1 = enable_irqs_1; + put32(PA2VA(ENABLE_IRQS_1), enable_irqs_1); } diff --git a/lab6/src/mmu.c b/lab6/src/mmu.c new file mode 100644 index 000000000..b275e0992 --- /dev/null +++ b/lab6/src/mmu.c @@ -0,0 +1,165 @@ +#include "mmu.h" +#include "utils.h" +#include "alloc.h" +#include "mini_uart.h" +#include "mm.h" + +#define TCR_CONFIG_REGION_48bit (((64 - 48) << 0) | ((64 - 48) << 16)) +#define TCR_CONFIG_4KB ((0b00 << 14) | (0b10 << 30)) +#define TCR_CONFIG_DEFAULT (TCR_CONFIG_REGION_48bit | TCR_CONFIG_4KB) + +#define MAIR_DEVICE_nGnRnE 0b00000000 +#define MAIR_NORMAL_NOCACHE 0b01000100 +#define MAIR_IDX_DEVICE_nGnRnE 0 +#define MAIR_IDX_NORMAL_NOCACHE 1 + +#define PD_TABLE 0b11 // Table Entry +#define PD_BLOCK 0b01 // Block Entry +#define PD_ACCESS (1 << 10) +#define PD_NSTABLE ((uint64_t)1 << 63) +#define PD_UXNTABLE ((uint64_t)1 << 60) +#define PD_PXN (1 << 53) +#define PD_MAIR_DEVICE_IDX (MAIR_IDX_DEVICE_nGnRnE << 2) +#define PD_MAIR_NORMAL_IDX (MAIR_IDX_NORMAL_NOCACHE << 2) +#define PD_L3BE (PD_ACCESS | PD_TABLE) + +// Block Entry +#define PD_BE PD_ACCESS | PD_BLOCK + +#define BOOT_PGD ((uint64_t *)0x1000) +#define BOOT_PUD ((uint64_t *)0x2000) +#define BOOT_PMD ((uint64_t *)0x3000) + + +/* + 000000000 000000000 000000000 000000000 000000000000 +47 39 38 30 29 21 20 12 11 0 + PGD PUD PMD PTE offset + +1 GB = 2^30 = 0x40000000 +*/ + +void mmu_init(void) +{ + uint32_t sctlr_el1; + + // Set Translation Control Register + write_sysreg(TCR_EL1, TCR_CONFIG_DEFAULT); + + // Set Memory Attribute Indirection Register + write_sysreg(MAIR_EL1, + (MAIR_DEVICE_nGnRnE << (MAIR_IDX_DEVICE_nGnRnE * 8)) | + (MAIR_NORMAL_NOCACHE << (MAIR_IDX_NORMAL_NOCACHE * 8))); + + // Set Identity Paging + // 0x00000000 ~ 0x3f000000: Normal + // 0x3f000000 ~ 0x40000000: Device (GPU) + // 0x40000000 ~ 0x80000000: Device + /* TODO: Support run user program in EL0 */ + BOOT_PGD[0] = (uint64_t)BOOT_PUD | PD_NSTABLE | PD_UXNTABLE | PD_TABLE; + + BOOT_PUD[0] = (uint64_t)BOOT_PMD | PD_TABLE; + BOOT_PUD[1] = 0x40000000 | PD_MAIR_DEVICE_IDX | PD_BE; + + for (int i = 0; i < 504; ++i) { + BOOT_PMD[i] = (i * (1 << 21)) | PD_MAIR_NORMAL_IDX | PD_BE; // 1 << 21 = 2MB + } + // 504 * 2MB = 0x3f000000 + for (int i = 504; i < 512; ++i) { + BOOT_PMD[i] = (i * (1 << 21)) | PD_MAIR_DEVICE_IDX | PD_BE; + } + + write_sysreg(TTBR0_EL1, BOOT_PGD); + write_sysreg(TTBR1_EL1, BOOT_PGD); + + // Enable MMU + sctlr_el1 = read_sysreg(SCTLR_EL1); + write_sysreg(SCTLR_EL1, sctlr_el1 | 1); +} + + +uint64_t *pt_create(void) +{ + uint64_t *pt = kmalloc(PAGE_TABLE_SIZE); + // uart_send_string("pt_create addr: "); + // uart_hex(pt); + // uart_send_string("\n"); + + memzero(pt, PAGE_TABLE_SIZE); + + return pt; +} + +void pt_free(uint64_t *pt) +{ + // TODO +} + +static void _pt_map(uint64_t *pt, void *va, void *pa, uint64_t flag) +{ + uint64_t pd; + int idx; + + // 47 ~ 39, 38 ~ 30, 29 ~ 21, 20 ~ 12 + for (int layer = 3; layer > 0; --layer) { + idx = ((uint64_t)va >> (12 + 9 * layer)) & 0b111111111; + pd = pt[idx]; + + if (!(pd & 1)) { + // not a table entry + uint64_t *tmp = pt_create(); + pt[idx] = VA2PA(tmp) | PD_TABLE; + pt = tmp; + continue; + } + + // Must be a table entry + pt = (uint64_t *)PA2VA(pd & ~((uint64_t)0xfff)); // Clear lowest 12 bits (for page table entry) + } + // pt is now table entry + idx = ((uint64_t)va >> 12) & 0b111111111; + pd = pt[idx]; + + if (!(pd & 1)) { + // Invalid entry + // Access permissions + uint64_t ap; + uint64_t uxn; + + if (flag & PT_R) { + if (flag & PT_W) { + ap = 0b01; + } else { + ap = 0b11; + } + } else { + ap = 0b00; + } + + if (flag & PT_X) { + uxn = 0; + } else { + uxn = 1; + } + + pt[idx] = (uint64_t)pa | (uxn << 54) | PD_PXN | + PD_MAIR_NORMAL_IDX | (ap << 6) | PD_L3BE; + } +} + +void pt_map(uint64_t *pt, void *va, uint64_t size, void *pa, uint64_t flag) +{ + if ((uint64_t)va & (PAGE_SIZE - 1)) { + return; + } + + if ((uint64_t)pa & (PAGE_SIZE - 1)) { + return; + } + + size = ALIGN(size, PAGE_SIZE); + + for (uint64_t i = 0; i < size; i += PAGE_SIZE) { + _pt_map(pt, (void *)((uint64_t)va + i), (void *)((uint64_t)pa + i), flag); + } +} \ No newline at end of file diff --git a/lab6/src/reboot.c b/lab6/src/reboot.c index a6f370d5d..5c0fd7906 100644 --- a/lab6/src/reboot.c +++ b/lab6/src/reboot.c @@ -1,11 +1,13 @@ #include "reboot.h" +#include "c_utils.h" +#include "utils.h" void reset(int tick) { // reboot after watchdog timer expire - *PM_RSTC = PM_PASSWORD | 0x20; // full reset - *PM_WDOG = PM_PASSWORD | tick; // number of watchdog tick + put32(PA2VA(PM_RSTC), PM_PASSWORD | 0x20); // full reset + put32(PA2VA(PM_WDOG), PM_PASSWORD | tick); // number of watchdog tick } void cancel_reset() { - *PM_RSTC = PM_PASSWORD | 0; // cancel reset - *PM_WDOG = PM_PASSWORD | 0; // set watchdog tick to 0 to prevent reset + put32(PA2VA(PM_RSTC), PM_PASSWORD | 0); // cancel reset + put32(PA2VA(PM_WDOG), PM_PASSWORD | 0); // set watchdog tick to 0 to prevent reset } \ No newline at end of file diff --git a/lab6/src/shell.c b/lab6/src/shell.c index 3c80f0ba2..e34572cdc 100644 --- a/lab6/src/shell.c +++ b/lab6/src/shell.c @@ -11,10 +11,10 @@ #include "thread.h" void shell(){ - uart_async_send_string("Welcome to OSC2024 shell!\n"); + uart_send_string("Welcome to OSC2024 shell!\n"); while(1){ - uart_send_string("# "); char* str = kmalloc(100); + uart_send_string("# "); uart_recv_command(str); uart_send_string("\n"); if(!strcmp(str, "hello")){ @@ -78,14 +78,14 @@ void shell(){ reset(200); break; } else if(!strcmp(str, "help")){ - uart_async_send_string("help\t: print this help menu\n"); - uart_async_send_string("hello\t: print Hello World!\n"); - uart_async_send_string("mailbox\t: print board revision and memory info\n"); - uart_async_send_string("ls\t: list files in the ramdisk\n"); - uart_async_send_string("cat\t: print content of a file\n"); - uart_async_send_string("reboot\t: reboot this device\n"); - uart_async_send_string("exec\t: execute program from initramfs\n"); - uart_async_send_string("setTimeout\t: setTimeout MESSAGE SECONDS\n"); + uart_send_string("help\t: print this help menu\n"); + uart_send_string("hello\t: print Hello World!\n"); + uart_send_string("mailbox\t: print board revision and memory info\n"); + uart_send_string("ls\t: list files in the ramdisk\n"); + uart_send_string("cat\t: print content of a file\n"); + uart_send_string("reboot\t: reboot this device\n"); + uart_send_string("exec\t: execute program from initramfs\n"); + uart_send_string("setTimeout\t: setTimeout MESSAGE SECONDS\n"); } } thread_exit(); diff --git a/lab6/src/signal.c b/lab6/src/signal.c index 0b42e68c2..d1db72e13 100644 --- a/lab6/src/signal.c +++ b/lab6/src/signal.c @@ -4,7 +4,7 @@ #include "syscall.h" #include "string.h" #include "thread.h" - +#include "mmu.h" void check_and_run_signal() { thread_t* cur_thread = get_current_thread(); diff --git a/lab6/src/syscall.c b/lab6/src/syscall.c index 1ea81fb66..98c6757cd 100644 --- a/lab6/src/syscall.c +++ b/lab6/src/syscall.c @@ -5,6 +5,7 @@ #include "exception.h" #include "mbox.h" #include "signal.h" +#include "mmu.h" #include int getpid() { @@ -53,6 +54,7 @@ int exec(const char* name, char *const argv[]) { char *filedata; unsigned int filesize; // current pointer + thread_t* cur_thread = get_current_thread(); cpio_t *header_pointer = (cpio_t *)(ramfs_base); // print every cpio pathname @@ -69,8 +71,14 @@ int exec(const char* name, char *const argv[]) { } if (!strcmp(name, filepath)) { - target_addr = (char*)kmalloc(filesize); - memcpy(target_addr, filedata, filesize); + uart_send_string("filesize: "); + uart_hex(filesize); + uart_send_string("\n"); + cur_thread -> data = (void*)kmalloc(filesize); + cur_thread -> data_size = filesize; + uart_send_string("Copying user program\n"); + memcpy(cur_thread -> data, filedata, cur_thread -> data_size); + uart_send_string("Finished\n"); break; } @@ -80,29 +88,27 @@ int exec(const char* name, char *const argv[]) { return 1; } } - // run program from current thread - thread_t* cur_thread = get_current_thread(); // TODO: signal handling, should clear all signal handler here for(int i=0;i<=SIGNAL_NUM;i++) { cur_thread -> signal_handler[i] = 0; cur_thread -> waiting_signal[i] = 0; } + unsigned long kernel_sp = (unsigned long)cur_thread -> kernel_stack + T_STACK_SIZE; - cur_thread -> callee_reg.lr = (unsigned long)target_addr; + // cur_thread -> callee_reg.lr = (unsigned long)target_addr; + cur_thread -> page_table = pt_create(); + pt_map(cur_thread -> page_table, (void*)0, cur_thread -> data_size, + (void*)VA2PA(cur_thread -> data), PT_R | PT_W | PT_X); // map user program + pt_map(cur_thread -> page_table, (void*)0xffffffffb000, T_STACK_SIZE, + (void*)VA2PA(cur_thread -> user_stack), PT_R | PT_W); // map user stack - unsigned long spsr_el1 = 0x0; // run in el0 and enable all interrupt (DAIF) - unsigned long elr_el1 = cur_thread -> callee_reg.lr; - unsigned long user_sp = cur_thread -> callee_reg.sp; - unsigned long kernel_sp = (unsigned long)cur_thread -> kernel_stack + T_STACK_SIZE; + pt_map(cur_thread->page_table, (void *)0x3c000000, 0x04000000, + (void *)0x3c000000, PT_R | PT_W); // map mailbox + + set_page_table(cur_thread); + exec_user_prog((void *)0, (char *)0xffffffffeff0, kernel_sp); - // "r": Any general-purpose register, except sp - asm volatile("msr tpidr_el1, %0" : : "r" (cur_thread)); - asm volatile("msr spsr_el1, %0" : : "r" (spsr_el1)); - asm volatile("msr elr_el1, %0" : : "r" (elr_el1)); - asm volatile("msr sp_el0, %0" : : "r" (user_sp)); - asm volatile("mov sp, %0" :: "r" (kernel_sp)); - asm volatile("eret"); // jump to user program return 0; } @@ -111,6 +117,9 @@ void fork(trapframe_t* tf) { thread_t* parent_thread = get_current_thread(); thread_t* child_thread = create_fork_thread(); + child_thread -> data = (void*)kmalloc(parent_thread -> data_size); + child_thread -> data_size = parent_thread -> data_size; + memcpy( (void*)child_thread->user_stack, (void*)parent_thread->user_stack, @@ -123,6 +132,24 @@ void fork(trapframe_t* tf) { (void*)parent_thread->kernel_stack, T_STACK_SIZE ); + + memcpy( + (void*)child_thread->data, + (void*)parent_thread->data, + (uint64_t)parent_thread->data_size + ); + + child_thread -> page_table = pt_create(); + + pt_map(child_thread->page_table, (void *)0, child_thread->data_size, + (void *)VA2PA(child_thread->data), PT_R | PT_W | PT_X); + pt_map(child_thread->page_table, (void *)0xffffffffb000, T_STACK_SIZE, + (void *)VA2PA(child_thread->user_stack), PT_R | PT_W); + + // TODO: Why is this needed for the vm.img to run? + pt_map(child_thread->page_table, (void *)0x3c000000, 0x04000000, + (void *)0x3c000000, PT_R | PT_W); + save_regs(parent_thread); copy_regs(&child_thread->callee_reg); @@ -139,6 +166,10 @@ void fork(trapframe_t* tf) { child_thread -> callee_reg.sp = (uint64_t)((uint64_t)parent_sp - (uint64_t)parent_thread->kernel_stack + (uint64_t)child_thread->kernel_stack); child_thread -> callee_reg.fp = (uint64_t)((uint64_t)parent_fp - (uint64_t)parent_thread->kernel_stack + (uint64_t)child_thread->kernel_stack); + trapframe_t *child_tf = (trapframe_t*)(child_thread -> kernel_stack + + ((char*)tf - (char*)(parent_thread -> kernel_stack)) + ); + void* label_address = &&SYSCALL_FORK_END; uart_send_string("Address of SYSCALL_FORK_END: "); uart_hex((uint64_t)label_address); @@ -147,19 +178,23 @@ void fork(trapframe_t* tf) { child_thread -> callee_reg.lr = &&SYSCALL_FORK_END; // set child trapframe - trapframe_t *child_tf = (trapframe_t*)(child_thread -> kernel_stack + - ((char*)tf - (char*)(parent_thread -> kernel_stack)) - ); + // set child user stack sp child_tf -> x[0] = 0; child_tf -> x[30] = tf -> x[30]; - child_tf -> sp_el0 = (void*)((uint64_t)tf -> sp_el0 - (uint64_t)parent_thread->user_stack + (uint64_t)child_thread->user_stack); + child_tf -> sp_el0 = tf -> sp_el0; child_tf -> spsr_el1 = tf -> spsr_el1; child_tf -> elr_el1 = tf -> elr_el1; + uart_send_string("elr_el1: "); + uart_hex(child_tf -> elr_el1); + uart_send_string("\n"); tf -> x[0] = child_thread -> tid; push_running(child_thread); SYSCALL_FORK_END: - // uart_send_string("forked end\n"); + uart_send_string("forked end\n"); + uart_send_string("current tid: "); + uart_hex(get_current_thread()->tid); + uart_send_string("\n"); asm volatile("nop"); return; } @@ -169,7 +204,23 @@ void exit(int status) { } int mbox_call(unsigned char ch, unsigned int *mbox) { - return mailbox_call(ch, mbox); + int mbox_size; + char *kmbox; + + mbox_size = (int)mbox[0]; + + if (mbox_size <= 0) + return; + + kmbox = kmalloc(mbox_size); + + memcpy(kmbox, (char *)mbox, mbox_size); + + mailbox_call(ch, (unsigned int *)kmbox); + + memcpy((char *)mbox, kmbox, mbox_size); + + kfree(kmbox); } void kill(int pid) { diff --git a/lab6/src/thread.c b/lab6/src/thread.c index a4e25ef06..6afeeea79 100644 --- a/lab6/src/thread.c +++ b/lab6/src/thread.c @@ -6,6 +6,7 @@ #include "timer.h" #include "shell.h" #include "alloc.h" +#include "mmu.h" #include // define three queues @@ -144,12 +145,17 @@ void kill_zombies() { } thread_t* create_thread(void (*func)(void)) { + uint64_t* page_table = pt_create(); + thread_t* t = (thread_t*)kmalloc(sizeof(thread_t)); + t -> page_table = page_table; t -> tid = cur_tid ++; t -> state = TASK_RUNNING; t -> callee_reg.lr = (unsigned long)func; t -> user_stack = kmalloc(T_STACK_SIZE); t -> kernel_stack = kmalloc(T_STACK_SIZE); + t -> data = NULL; + t -> data_size = 0; t -> callee_reg.sp = (unsigned long)(t->user_stack + T_STACK_SIZE); t -> callee_reg.fp = t -> callee_reg.sp; // set fp to sp as the pointer that fixed @@ -172,7 +178,11 @@ thread_t* create_thread(void (*func)(void)) { } thread_t* create_fork_thread() { + uart_send_string("creating fork thread\n"); + uint64_t* page_table = pt_create(); + thread_t* t = (thread_t*)kmalloc(sizeof(thread_t)); + t -> page_table = page_table; t -> tid = cur_tid ++; t -> state = TASK_RUNNING; t -> callee_reg.lr = 0; @@ -180,6 +190,8 @@ thread_t* create_fork_thread() { t -> kernel_stack = kmalloc(T_STACK_SIZE); t -> callee_reg.sp = (unsigned long)(t->user_stack + T_STACK_SIZE); t -> callee_reg.fp = t -> callee_reg.sp; // set fp to sp as the pointer that fixed + t -> data = NULL; + t -> data_size = 0; for(int i=0;i<=SIGNAL_NUM;i++) { t -> signal_handler[i] = 0; diff --git a/lab6/src/timer.c b/lab6/src/timer.c index dbbd25dca..028a02a9c 100644 --- a/lab6/src/timer.c +++ b/lab6/src/timer.c @@ -25,13 +25,15 @@ void print_message(void *data) { } void set_timeout(char* message, unsigned long long timeout) { + uart_send_string("Set timeout\n"); char* message_copy = (char*)kmalloc(strlen(message)+1); + uart_send_string("Set timeout\n"); strncpy_(message_copy, message, strlen(message)+1); if(!message_copy) return; if(!timer_head) { // enable timer - *CORE0_TIMER_IRQ_CTRL = 2; + core_timer_enable(); } create_timer(print_message, message_copy, timeout); diff --git a/lab6/src/utils.S b/lab6/src/utils.S new file mode 100644 index 000000000..9fe830f36 --- /dev/null +++ b/lab6/src/utils.S @@ -0,0 +1,9 @@ +.globl put32 +put32: + str w1,[x0] + ret + +.globl get32 +get32: + ldr w0,[x0] + ret \ No newline at end of file