From 5053a20d4623951f8ef7c2106bd8b0eca30e7303 Mon Sep 17 00:00:00 2001 From: Jonathan Chang Date: Sun, 26 May 2024 05:26:31 +0800 Subject: [PATCH 1/7] fix cat --- lab2/src/initramfs.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lab2/src/initramfs.c b/lab2/src/initramfs.c index 941f6c944..db9f91cd2 100644 --- a/lab2/src/initramfs.c +++ b/lab2/src/initramfs.c @@ -62,8 +62,9 @@ void initramfs_cat(const char *target) { strncpy(pathname, fptr + sizeof(cpio_t), namesize); if (!strcmp(target, pathname)) { // Print its content - char data[filesize]; + char data[filesize + 1]; strncpy(data, fptr + headsize, filesize); + data[filesize] = '\0'; uart_puts(data); uart_putc('\n'); return; From 207c56d41b19bca4bd03963197840c5abe0e4e16 Mon Sep 17 00:00:00 2001 From: "Chang, Chun-yien" Date: Mon, 3 Jun 2024 15:57:41 +0800 Subject: [PATCH 2/7] lab3 --- lab2/bootloader/main.c | 2 +- lab3/.gitignore | 6 ++ lab3/Makefile | 36 +++++++ lab3/bootloader/Makefile | 19 ++++ lab3/bootloader/boot.c | 61 +++++++++++ lab3/bootloader/boot.h | 49 +++++++++ lab3/bootloader/linker.ld | 16 +++ lab3/bootloader/main.c | 42 ++++++++ lab3/bootloader/sender.sh | 27 +++++ lab3/bootloader/start.S | 40 +++++++ lab3/include/command.h | 25 +++++ lab3/include/devtree.h | 18 ++++ lab3/include/hardware.h | 219 ++++++++++++++++++++++++++++++++++++++ lab3/include/initramfs.h | 26 +++++ lab3/include/irq.h | 10 ++ lab3/include/malloc.h | 4 + lab3/include/mbox.h | 8 ++ lab3/include/shell.h | 9 ++ lab3/include/string.h | 7 ++ lab3/include/timer.h | 14 +++ lab3/include/uart.h | 28 +++++ lab3/include/utils.h | 20 ++++ lab3/initramfs.cpio | Bin 0 -> 4096 bytes lab3/linker.ld | 13 +++ lab3/src/command.c | 175 ++++++++++++++++++++++++++++++ lab3/src/devtree.c | 66 ++++++++++++ lab3/src/initramfs.c | 118 ++++++++++++++++++++ lab3/src/irq.c | 93 ++++++++++++++++ lab3/src/main.c | 22 ++++ lab3/src/malloc.c | 16 +++ lab3/src/mbox.c | 44 ++++++++ lab3/src/shell.c | 88 +++++++++++++++ lab3/src/string.c | 40 +++++++ lab3/src/timer.c | 104 ++++++++++++++++++ lab3/src/traps.c | 34 ++++++ lab3/src/uart.c | 126 ++++++++++++++++++++++ lab3/src/utils.c | 42 ++++++++ lab3/start.S | 50 +++++++++ lab3/traps.S | 103 ++++++++++++++++++ 39 files changed, 1819 insertions(+), 1 deletion(-) create mode 100644 lab3/.gitignore create mode 100644 lab3/Makefile create mode 100644 lab3/bootloader/Makefile create mode 100644 lab3/bootloader/boot.c create mode 100644 lab3/bootloader/boot.h create mode 100644 lab3/bootloader/linker.ld create mode 100644 lab3/bootloader/main.c create mode 100755 lab3/bootloader/sender.sh create mode 100644 lab3/bootloader/start.S create mode 100644 lab3/include/command.h create mode 100644 lab3/include/devtree.h create mode 100644 lab3/include/hardware.h create mode 100644 lab3/include/initramfs.h create mode 100644 lab3/include/irq.h create mode 100644 lab3/include/malloc.h create mode 100644 lab3/include/mbox.h create mode 100644 lab3/include/shell.h create mode 100644 lab3/include/string.h create mode 100644 lab3/include/timer.h create mode 100644 lab3/include/uart.h create mode 100644 lab3/include/utils.h create mode 100644 lab3/initramfs.cpio create mode 100644 lab3/linker.ld create mode 100644 lab3/src/command.c create mode 100644 lab3/src/devtree.c create mode 100644 lab3/src/initramfs.c create mode 100644 lab3/src/irq.c create mode 100644 lab3/src/main.c create mode 100644 lab3/src/malloc.c create mode 100644 lab3/src/mbox.c create mode 100644 lab3/src/shell.c create mode 100644 lab3/src/string.c create mode 100644 lab3/src/timer.c create mode 100644 lab3/src/traps.c create mode 100644 lab3/src/uart.c create mode 100644 lab3/src/utils.c create mode 100644 lab3/start.S create mode 100644 lab3/traps.S diff --git a/lab2/bootloader/main.c b/lab2/bootloader/main.c index 393de8cae..265e655f2 100644 --- a/lab2/bootloader/main.c +++ b/lab2/bootloader/main.c @@ -39,4 +39,4 @@ int main() { "ret;"); return 0; -} \ No newline at end of file +} diff --git a/lab3/.gitignore b/lab3/.gitignore new file mode 100644 index 000000000..eddcca901 --- /dev/null +++ b/lab3/.gitignore @@ -0,0 +1,6 @@ +.vscode +build +rootfs +temp +*.img +*.dtb \ No newline at end of file diff --git a/lab3/Makefile b/lab3/Makefile new file mode 100644 index 000000000..729b22978 --- /dev/null +++ b/lab3/Makefile @@ -0,0 +1,36 @@ +ARMGNU ?= aarch64-linux-gnu + +CFLAGS = -Iinclude -nostdlib -nostartfiles -ffreestanding -mgeneral-regs-only -Wall -g +ASMFLAGS = -Iinclude +QEMUFLAGS = -M raspi3b -display none -serial null -serial stdio + +SRC_DIR = src +BUILD_DIR = build + +all: clean kernel8.img + +clean: + rm -rf $(BUILD_DIR) *.img + +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c + mkdir -p $(@D) + $(ARMGNU)-gcc $(CFLAGS) -c $< -o $@ + +$(BUILD_DIR)/_%.o: %.S + $(ARMGNU)-gcc $(ASMFLAGS) -c $< -o $@ + +SRC_FILES = $(wildcard $(SRC_DIR)/*.c) +ASM_FILES = $(wildcard *.S) +OBJ_FILES = $(SRC_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%.o) +OBJ_FILES += $(ASM_FILES:%.S=$(BUILD_DIR)/_%.o) + +kernel8.img: linker.ld $(OBJ_FILES) + $(ARMGNU)-ld -T linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) + $(ARMGNU)-objcopy -O binary $(BUILD_DIR)/kernel8.elf kernel8.img + +qemu: all kernel8.img initramfs.cpio bcm2710-rpi-3-b-plus.dtb + clear + qemu-system-aarch64 $(QEMUFLAGS) -kernel kernel8.img -initrd initramfs.cpio -dtb bcm2710-rpi-3-b-plus.dtb + +debug: all kernel8.img initramfs.cpio bcm2710-rpi-3-b-plus.dtb + qemu-system-aarch64 $(QEMUFLAGS) -kernel kernel8.img -initrd initramfs.cpio -dtb bcm2710-rpi-3-b-plus.dtb -S -s -d int \ No newline at end of file diff --git a/lab3/bootloader/Makefile b/lab3/bootloader/Makefile new file mode 100644 index 000000000..d7882aa27 --- /dev/null +++ b/lab3/bootloader/Makefile @@ -0,0 +1,19 @@ +ARMGNU ?= aarch64-linux-gnu +CFLAGS = -nostdlib -nostartfiles -ffreestanding -mgeneral-regs-only -Wall -g +QEMUFLAGS = -M raspi3b -display none -serial null -serial pty + +all: clean bootloader.img + +clean: + rm -rf build *.img + +bootloader.img: + mkdir -p build + $(ARMGNU)-gcc $(CFLAGS) -c main.c -o build/main.o + $(ARMGNU)-gcc $(CFLAGS) -c start.S -o build/start.o + $(ARMGNU)-gcc $(CFLAGS) -c boot.c -o build/boot.o + $(ARMGNU)-ld -T linker.ld -o build/bootloader.elf build/main.o build/start.o build/boot.o + $(ARMGNU)-objcopy -O binary build/bootloader.elf bootloader.img + +qemu: clean bootloader.img + qemu-system-aarch64 $(QEMUFLAGS) -kernel bootloader.img -initrd ../initramfs.cpio -dtb ../bcm2710-rpi-3-b-plus.dtb \ No newline at end of file diff --git a/lab3/bootloader/boot.c b/lab3/bootloader/boot.c new file mode 100644 index 000000000..fd16525a6 --- /dev/null +++ b/lab3/bootloader/boot.c @@ -0,0 +1,61 @@ +#include "boot.h" + +void uart_init() { + // Configure GPIO pins + register unsigned int r = *GPFSEL1; + r &= ~((7 << 12) | (7 << 15)); + r |= (2 << 12) | (2 << 15); + *GPFSEL1 = r; + + *GPPUD = 0; + for (int i = 0; i < 150; i++) asm volatile("nop"); + *GPPUDCLK0 = (1 << 14) | (1 << 15); + for (int i = 0; i < 150; i++) asm volatile("nop"); + *GPPUD = 0; + *GPPUDCLK0 = 0; + + *AUX_ENABLE |= 1; + *AUX_MU_CNTL = 0; + *AUX_MU_IER = 0; + *AUX_MU_LCR = 3; + *AUX_MU_MCR = 0; + *AUX_MU_BAUD = 270; + *AUX_MU_IIR = 6; + *AUX_MU_CNTL = 3; +} + +char uart_recv() { + while (!(*AUX_MU_LSR & 0x01)) asm volatile("nop"); + return (char)(*AUX_MU_IO); +} + +void uart_putc(char c) { + if (c == '\n') uart_putc('\r'); + while (!(*AUX_MU_LSR & 0x20)) asm volatile("nop"); + *AUX_MU_IO = c; +} + +void uart_puts(const char *s) { + while (*s) uart_putc(*s++); +} + +int atoi(const char *s) { + int result = 0; + int sign = 1; + int i = 0; + + while (s[i] == ' ') i++; + + if (s[i] == '-') { + sign = -1; + i++; + } else if (s[i] == '+') + i++; + + while (s[i] >= '0' && s[i] <= '9') { + result = result * 10 + (s[i] - '0'); + i++; + } + + return sign * result; +} \ No newline at end of file diff --git a/lab3/bootloader/boot.h b/lab3/bootloader/boot.h new file mode 100644 index 000000000..6eedd57fd --- /dev/null +++ b/lab3/bootloader/boot.h @@ -0,0 +1,49 @@ +#ifndef BOOT_H +#define BOOT_H + +/* ==================== GPIO ==================== */ +#define MMIO_BASE 0x3F000000 + +#define GPFSEL0 ((volatile unsigned int *)(MMIO_BASE + 0x00200000)) +#define GPFSEL1 ((volatile unsigned int *)(MMIO_BASE + 0x00200004)) +#define GPFSEL2 ((volatile unsigned int *)(MMIO_BASE + 0x00200008)) +#define GPFSEL3 ((volatile unsigned int *)(MMIO_BASE + 0x0020000C)) +#define GPFSEL4 ((volatile unsigned int *)(MMIO_BASE + 0x00200010)) +#define GPFSEL5 ((volatile unsigned int *)(MMIO_BASE + 0x00200014)) +#define GPSET0 ((volatile unsigned int *)(MMIO_BASE + 0x0020001C)) +#define GPSET1 ((volatile unsigned int *)(MMIO_BASE + 0x00200020)) +#define GPCLR0 ((volatile unsigned int *)(MMIO_BASE + 0x00200028)) +#define GPCLR1 ((volatile unsigned int *)(MMIO_BASE + 0x0020002C)) +#define GPLEV0 ((volatile unsigned int *)(MMIO_BASE + 0x00200034)) +#define GPLEV1 ((volatile unsigned int *)(MMIO_BASE + 0x00200038)) +#define GPEDS0 ((volatile unsigned int *)(MMIO_BASE + 0x00200040)) +#define GPEDS1 ((volatile unsigned int *)(MMIO_BASE + 0x00200044)) +#define GPHEN0 ((volatile unsigned int *)(MMIO_BASE + 0x00200064)) +#define GPHEN1 ((volatile unsigned int *)(MMIO_BASE + 0x00200068)) +#define GPPUD ((volatile unsigned int *)(MMIO_BASE + 0x00200094)) +#define GPPUDCLK0 ((volatile unsigned int *)(MMIO_BASE + 0x00200098)) +#define GPPUDCLK1 ((volatile unsigned int *)(MMIO_BASE + 0x0020009C)) + +/* ==================== UART ==================== */ +#define AUX_ENABLE ((volatile unsigned int *)(MMIO_BASE + 0x00215004)) +#define AUX_MU_IO ((volatile unsigned int *)(MMIO_BASE + 0x00215040)) +#define AUX_MU_IER ((volatile unsigned int *)(MMIO_BASE + 0x00215044)) +#define AUX_MU_IIR ((volatile unsigned int *)(MMIO_BASE + 0x00215048)) +#define AUX_MU_LCR ((volatile unsigned int *)(MMIO_BASE + 0x0021504C)) +#define AUX_MU_MCR ((volatile unsigned int *)(MMIO_BASE + 0x00215050)) +#define AUX_MU_LSR ((volatile unsigned int *)(MMIO_BASE + 0x00215054)) +#define AUX_MU_MSR ((volatile unsigned int *)(MMIO_BASE + 0x00215058)) +#define AUX_MU_SCRATCH ((volatile unsigned int *)(MMIO_BASE + 0x0021505C)) +#define AUX_MU_CNTL ((volatile unsigned int *)(MMIO_BASE + 0x00215060)) +#define AUX_MU_STAT ((volatile unsigned int *)(MMIO_BASE + 0x00215064)) +#define AUX_MU_BAUD ((volatile unsigned int *)(MMIO_BASE + 0x00215068)) + +void uart_init(); +char uart_recv(); +void uart_putc(char c); +void uart_puts(const char *s); + +/* ==================== UTILS ==================== */ +int atoi(const char *s); + +#endif // BOOT_H \ No newline at end of file diff --git a/lab3/bootloader/linker.ld b/lab3/bootloader/linker.ld new file mode 100644 index 000000000..de083fba3 --- /dev/null +++ b/lab3/bootloader/linker.ld @@ -0,0 +1,16 @@ +SECTIONS +{ + . = 0x60000; + __loader_start = .; + .text : { *(.text.boot) *(.text) } + .rodata : { *(.rodata) } + .data : { *(.data) } + .bss : { + __bss_start = .; + *(.bss) + __bss_end = .; + } + __loader_end = .; +} +__bss_size = (__bss_end - __bss_start) >> 3; +__loader_size = (__loader_end - __loader_start) >> 3; \ No newline at end of file diff --git a/lab3/bootloader/main.c b/lab3/bootloader/main.c new file mode 100644 index 000000000..393de8cae --- /dev/null +++ b/lab3/bootloader/main.c @@ -0,0 +1,42 @@ +#include "boot.h" + +int main() { + uart_init(); + uart_puts("\033[2J\033[H"); + uart_puts( + "UART Bootloader\n" + "Waiting for kernel...\n"); + + // Get kernel image size + char buf[16] = {0}; + for (int i = 0; i < 16; i++) { + buf[i] = uart_recv(); + if (buf[i] == '\n') { + buf[i] = '\0'; + break; + } + } + + // Load kernel image + uart_puts("Kernel size: "); + uart_puts(buf); + uart_puts(" bytes.\n"); + uart_puts("Loading the kernel image...\n"); + + unsigned int size = atoi(buf); + char *kernel = (char *)0x80000; + while (size--) *kernel++ = uart_recv(); + + // Restore registers x0 x1 x2 x3 + // Jump to the new kernel + asm volatile( + "" + "mov x0, x10;" + "mov x1, x11;" + "mov x2, x12;" + "mov x3, x13;" + "mov x30, 0x80000;" + "ret;"); + + return 0; +} \ No newline at end of file diff --git a/lab3/bootloader/sender.sh b/lab3/bootloader/sender.sh new file mode 100755 index 000000000..6de9ec437 --- /dev/null +++ b/lab3/bootloader/sender.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# This script sends the kernel image to Rpi3 through UART + +DEST_PATH="/dev/ttyUSB0" +KERNEL_PATH="../kernel8.img" + +# Check the root permission +if [ "$(id -u)" != "0" ]; then + echo "This script must be run as root" + exit 1 +fi + +if [ $1 ] +then + DEST_PATH="$1" +fi + +# Get the size of the kernel image file and send it to Rpi3 +# wc -c: count bytes of a file +# sleep: wait n seconds +wc -c < $KERNEL_PATH > $DEST_PATH | sleep 1 + +# Send the kernel image +# pv: redirect file input to specified tty +# add --rate-limit option to limit the speed +pv $KERNEL_PATH > $DEST_PATH diff --git a/lab3/bootloader/start.S b/lab3/bootloader/start.S new file mode 100644 index 000000000..ec8488dde --- /dev/null +++ b/lab3/bootloader/start.S @@ -0,0 +1,40 @@ +.section ".text.boot" + +.global _start + +_start: + /* save registers x0 x1 x2 x3 */ + mov x10, x0 /* dtb_base address */ + mov x11, x1 + mov x12, x2 + mov x12, x3 + + /* relocate bootloader */ + ldr x1, =0x80000 + ldr x2, =__loader_start // 0x60000 + ldr w3, =__loader_size + +relocate: + ldr x4, [x1], #8 + str x4, [x2], #8 + sub w3, w3, #1 + cbnz w3, relocate + + /* set stack pointer */ + ldr x1, =_start + mov sp, x1 + + /* clear bss section */ + ldr x1, =__bss_start + ldr w2, =__bss_size + +bss_reset: + cbz w2, run_main + str xzr, [x1], #8 + sub w2, w2, #1 + cbnz w2, bss_reset + +run_main: + /* branch to main function */ + bl main-0x20000 + b run_main diff --git a/lab3/include/command.h b/lab3/include/command.h new file mode 100644 index 000000000..5342dbd96 --- /dev/null +++ b/lab3/include/command.h @@ -0,0 +1,25 @@ +#pragma once + +#define MAX_BUF_SIZE 1024 +#define END_OF_COMMAND_LIST "NULL" + +struct command { + const char *name; + const char *help; + void (*func)(void); +}; + +extern struct command commands[]; + +// Commands +void cmd_help(); +void cmd_hello(); +void cmd_reboot(); +void cmd_cancel(); +void cmd_info(); +void cmd_ls(); +void cmd_cat(); +void cmd_run(); +void cmd_clear(); +void cmd_timer(); +void cmd_lab(); \ No newline at end of file diff --git a/lab3/include/devtree.h b/lab3/include/devtree.h new file mode 100644 index 000000000..8bfbc9e49 --- /dev/null +++ b/lab3/include/devtree.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +struct fdt_header { + uint32_t magic; + uint32_t totalsize; + uint32_t off_dt_struct; + uint32_t off_dt_strings; + uint32_t off_mem_rsvmap; + uint32_t version; + uint32_t last_comp_version; + uint32_t boot_cpuid_phys; + uint32_t size_dt_strings; + uint32_t size_dt_struct; +}; + +void fdt_traverse(void (*callback)(void *, char *)); diff --git a/lab3/include/hardware.h b/lab3/include/hardware.h new file mode 100644 index 000000000..41c5c6053 --- /dev/null +++ b/lab3/include/hardware.h @@ -0,0 +1,219 @@ +#pragma once + +#define MMIO_BASE 0x3F000000 + +// ARM Interrupt Registers + +#define IRQ_BASIC_PENDING (volatile unsigned int *)(MMIO_BASE + 0x0000B200) +#define IRQ_PENDING_1 (volatile unsigned int *)(MMIO_BASE + 0x0000B204) +#define IRQ_PENDING_2 (volatile unsigned int *)(MMIO_BASE + 0x0000B208) +#define FIQ_CONTROL (volatile unsigned int *)(MMIO_BASE + 0x0000B20C) +#define ENABLE_IRQS_1 (volatile unsigned int *)(MMIO_BASE + 0x0000B210) +#define ENABLE_IRQS_2 (volatile unsigned int *)(MMIO_BASE + 0x0000B214) +#define ENABLE_BASIC_IRQS (volatile unsigned int *)(MMIO_BASE + 0x0000B218) +#define DISABLE_IRQS_1 (volatile unsigned int *)(MMIO_BASE + 0x0000B21C) +#define DISABLE_IRQS_2 (volatile unsigned int *)(MMIO_BASE + 0x0000B220) +#define DISABLE_BASIC_IRQS (volatile unsigned int *)(MMIO_BASE + 0x0000B224) +#define CORE0_INTERRUPT_SOURCE (volatile unsigned int *)(0x40000060) + +// Timers interrupt control registers + +#define CORE0_TIMER_IRQCNTL 0x40000040 +#define CORE1_TIMER_IRQCNTL 0x40000044 +#define CORE2_TIMER_IRQCNTL 0x40000048 +#define CORE3_TIMER_IRQCNTL 0x4000004C + +// Where to route timer interrupt to, IRQ/FIQ +// Setting both the IRQ and FIQ bit gives an FIQ +#define TIMER0_IRQ 0x01 +#define TIMER1_IRQ 0x02 +#define TIMER2_IRQ 0x04 +#define TIMER3_IRQ 0x08 +#define TIMER0_FIQ 0x10 +#define TIMER1_FIQ 0x20 +#define TIMER2_FIQ 0x40 +#define TIMER3_FIQ 0x80 + +// PASSWORD + +#define PM_PASSWORD 0x5A000000 +#define PM_RSTC (volatile unsigned int *)0x3F10001C +#define PM_WDOG (volatile unsigned int *)0x3F100024 + +// DEVICETREE + +#define FDT_BEGIN_NODE 0x00000001 +#define FDT_END_NODE 0x00000002 +#define FDT_PROP 0x00000003 +#define FDT_NOP 0x00000004 +#define FDT_END 0x00000009 + +// GPIO + +#define GPFSEL0 ((volatile unsigned int *)(MMIO_BASE + 0x00200000)) +#define GPFSEL1 ((volatile unsigned int *)(MMIO_BASE + 0x00200004)) +#define GPFSEL2 ((volatile unsigned int *)(MMIO_BASE + 0x00200008)) +#define GPFSEL3 ((volatile unsigned int *)(MMIO_BASE + 0x0020000C)) +#define GPFSEL4 ((volatile unsigned int *)(MMIO_BASE + 0x00200010)) +#define GPFSEL5 ((volatile unsigned int *)(MMIO_BASE + 0x00200014)) +#define GPSET0 ((volatile unsigned int *)(MMIO_BASE + 0x0020001C)) +#define GPSET1 ((volatile unsigned int *)(MMIO_BASE + 0x00200020)) +#define GPCLR0 ((volatile unsigned int *)(MMIO_BASE + 0x00200028)) +#define GPCLR1 ((volatile unsigned int *)(MMIO_BASE + 0x0020002C)) +#define GPLEV0 ((volatile unsigned int *)(MMIO_BASE + 0x00200034)) +#define GPLEV1 ((volatile unsigned int *)(MMIO_BASE + 0x00200038)) +#define GPEDS0 ((volatile unsigned int *)(MMIO_BASE + 0x00200040)) +#define GPEDS1 ((volatile unsigned int *)(MMIO_BASE + 0x00200044)) +// Skip GPRENn, GPFENn +#define GPHEN0 ((volatile unsigned int *)(MMIO_BASE + 0x00200064)) +#define GPHEN1 ((volatile unsigned int *)(MMIO_BASE + 0x00200068)) +// Skip GPLENn, GPARENn +#define GPPUD ((volatile unsigned int *)(MMIO_BASE + 0x00200094)) +#define GPPUDCLK0 ((volatile unsigned int *)(MMIO_BASE + 0x00200098)) +#define GPPUDCLK1 ((volatile unsigned int *)(MMIO_BASE + 0x0020009C)) + +// UART + +#define AUX_ENABLE ((volatile unsigned int *)(MMIO_BASE + 0x00215004)) +#define AUX_MU_IO ((volatile unsigned int *)(MMIO_BASE + 0x00215040)) +#define AUX_MU_IER ((volatile unsigned int *)(MMIO_BASE + 0x00215044)) +#define AUX_MU_IIR ((volatile unsigned int *)(MMIO_BASE + 0x00215048)) +#define AUX_MU_LCR ((volatile unsigned int *)(MMIO_BASE + 0x0021504C)) +#define AUX_MU_MCR ((volatile unsigned int *)(MMIO_BASE + 0x00215050)) +#define AUX_MU_LSR ((volatile unsigned int *)(MMIO_BASE + 0x00215054)) +#define AUX_MU_MSR ((volatile unsigned int *)(MMIO_BASE + 0x00215058)) +#define AUX_MU_SCRATCH ((volatile unsigned int *)(MMIO_BASE + 0x0021505C)) +#define AUX_MU_CNTL ((volatile unsigned int *)(MMIO_BASE + 0x00215060)) +#define AUX_MU_STAT ((volatile unsigned int *)(MMIO_BASE + 0x00215064)) +#define AUX_MU_BAUD ((volatile unsigned int *)(MMIO_BASE + 0x00215068)) + +// MAILBOX + +#define MAILBOX_EMPTY 0x40000000 +#define MAILBOX_FULL 0x80000000 +#define MAILBOX_REQUEST 0x00000000 +#define MAILBOX_RESPONSE 0x80000000 +#define REQUEST_CODE 0x00000000 +#define REQUEST_SUCCEED 0x80000000 +#define REQUEST_FAILED 0x80000001 +#define TAG_REQUEST_CODE 0x00000000 +#define END_TAG 0x00000000 + +// Channels +#define MAILBOX_CH_POWER 0 +#define MAILBOX_CH_FB 1 +#define MAILBOX_CH_VUART 2 +#define MAILBOX_CH_VCHIQ 3 +#define MAILBOX_CH_LEDS 4 +#define MAILBOX_CH_BTNS 5 +#define MAILBOX_CH_TOUCH 6 +#define MAILBOX_CH_COUNT 7 +#define MAILBOX_CH_PROP 8 + +#define MAILBOX_BASE (MMIO_BASE + 0x0000B880) + +// mailbox register +#define MAILBOX_REG_READ ((volatile unsigned int *)(MAILBOX_BASE + 0x00000000)) +#define MAILBOX_REG_STATUS \ + ((volatile unsigned int *)(MAILBOX_BASE + 0x00000018)) +#define MAILBOX_REG_WRITE ((volatile unsigned int *)(MAILBOX_BASE + 0x00000020)) + +// mailbox channel +// https://github.com/raspberrypi/firmware/wiki/Mailboxes#channels +#define MAILBOX_CH_POWER 0 +#define MAILBOX_CH_FB 1 +#define MAILBOX_CH_VUART 2 +#define MAILBOX_CH_VCHIQ 3 +#define MAILBOX_CH_LEDS 4 +#define MAILBOX_CH_BTNS 5 +#define MAILBOX_CH_TOUCH 6 +#define MAILBOX_CH_COUNT 7 +#define MAILBOX_CH_PROP 8 + +// mailbox status +#define MAILBOX_EMPTY 0x40000000 +#define MAILBOX_FULL 0x80000000 + +// tags +#define TAGS_REQ_CODE 0x00000000 +#define TAGS_REQ_SUCCEED 0x80000000 +#define TAGS_REQ_FAILED 0x80000001 +#define TAGS_END 0x00000000 + +// hardware tags operator +#define TAGS_HARDWARE_BOARD_MODEL 0x00010001 +#define TAGS_HARDWARE_BOARD_REVISION 0x00010002 +#define TAGS_HARDWARE_MAC_ADDR 0x00010003 +#define TAGS_HARDWARE_BOARD_SERIAL 0x00010004 +#define TAGS_HARDWARE_ARM_MEM 0x00010005 +#define TAGS_HARDWARE_VC_MEM 0x00010006 +#define TAGS_HARDWARE_CLOCKS 0x00010007 + +// CLOCK ID + +#define CLOCK_ID_RESERVED 0x000000000 +#define CLOCK_ID_EMMC 0x000000001 +#define CLOCK_ID_UART 0x000000002 +#define CLOCK_ID_ARM 0x000000003 +#define CLOCK_ID_CORE 0x000000004 +#define CLOCK_ID_V3D 0x000000005 +#define CLOCK_ID_H264 0x000000006 +#define CLOCK_ID_ISP 0x000000007 +#define CLOCK_ID_SDRAM 0x000000008 +#define CLOCK_ID_PIXEL 0x000000009 +#define CLOCK_ID_PWM 0x00000000a +#define CLOCK_ID_HEVC 0x00000000b +#define CLOCK_ID_EMMC2 0x00000000c +#define CLOCK_ID_M2MC 0x00000000d +#define CLOCK_ID_PIXEL_BVB 0x00000000e + +// clock tags operator +#define TAGS_GET_CLOCK 0x00030002 +#define TAGS_SET_CLOCK 0x00038002 + +// framebuffer tages operator +#define FB_ALLOC_BUFFER 0x00040001 +#define FB_FREE_BUFFER 0x00048001 + +#define FB_BLANK_SCREEN 0x00040002 + +#define FB_PHY_WID_HEIGHT_GET 0x00040003 +#define FB_PHY_WID_HEIGHT_TEST 0x00044003 +#define FB_PHY_WID_HEIGHT_SET 0x00048003 + +#define FB_VIR_WID_HEIGHT_GET 0x00040004 +#define FB_VIR_WID_HEIGHT_TEST 0x00044004 +#define FB_VIR_WID_HEIGHT_SET 0x00048004 + +#define FB_DEPTH_GET 0x00040005 +#define FB_DEPTH_TEST 0x00044005 +#define FB_DEPTH_SET 0x00048005 + +#define FB_DEPTH_GET 0x00040005 +#define FB_DEPTH_TEST 0x00044005 +#define FB_DEPTH_SET 0x00048005 + +#define FB_PIXEL_ORDER_GET 0x00040006 +#define FB_PIXEL_ORDER_TEST 0x00044006 +#define FB_PIXEL_ORDER_SET 0x00048006 + +#define FB_ALPHA_MODE_GET 0x00040007 +#define FB_ALPHA_MODE_TEST 0x00044007 +#define FB_ALPHA_MODE_SET 0x00048007 + +#define FB_PITCH_GET 0x00040008 + +#define FB_VIR_OFFSET_GET 0x00040009 +#define FB_VIR_OFFSET_TEST 0x00044009 +#define FB_VIR_OFFSET_SET 0x00048009 + +#define FB_OVERSCAN_GET 0x0004000A +#define FB_OVERSCAN_TEST 0x0004400A +#define FB_OVERSCAN_SET 0x0004800A + +#define FB_PALETTE_GET 0x0004000B +#define FB_PALETTE_TEST 0x0004400B +#define FB_PALETTE_SET 0x0004800B + +#define FB_CURSOR_INFO_SET 0x00008010 +#define FB_CURSOR_STATE_SET 0x00008011 diff --git a/lab3/include/initramfs.h b/lab3/include/initramfs.h new file mode 100644 index 000000000..ba65e30fb --- /dev/null +++ b/lab3/include/initramfs.h @@ -0,0 +1,26 @@ +#pragma once + +// 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; + +#define DEFAULT_STACK_SIZE 8192 + +void initramfs_list(); +void initramfs_cat(const char *target); +void initramfs_callback(void *addr, char *property); +void initramfs_run(const char *target); diff --git a/lab3/include/irq.h b/lab3/include/irq.h new file mode 100644 index 000000000..88310213e --- /dev/null +++ b/lab3/include/irq.h @@ -0,0 +1,10 @@ +#pragma once + +#include "hardware.h" + +#define PRIORITY_HIGH 0 +#define PRIORITY_LOW 255 + +void enable_interrupt(); +void disable_interrupt(); +void irq_entry(); \ No newline at end of file diff --git a/lab3/include/malloc.h b/lab3/include/malloc.h new file mode 100644 index 000000000..0ef6b09b4 --- /dev/null +++ b/lab3/include/malloc.h @@ -0,0 +1,4 @@ +#pragma once + +void malloc_init(); +void *simple_malloc(int size); \ No newline at end of file diff --git a/lab3/include/mbox.h b/lab3/include/mbox.h new file mode 100644 index 000000000..223864626 --- /dev/null +++ b/lab3/include/mbox.h @@ -0,0 +1,8 @@ +#pragma once + +#include "hardware.h" + +extern volatile unsigned int mbox[36]; +int mailbox_call(unsigned char c); +int get_board_revision(); +int get_arm_memory_status(); diff --git a/lab3/include/shell.h b/lab3/include/shell.h new file mode 100644 index 000000000..407f09837 --- /dev/null +++ b/lab3/include/shell.h @@ -0,0 +1,9 @@ +#pragma once + +#define SHELL_BUF_SIZE 256 + +void welcome_msg(); +void run_shell(); +void read_user_input(char *buf); +void buffer_overflow_message(); +int exec_command(const char *command); diff --git a/lab3/include/string.h b/lab3/include/string.h new file mode 100644 index 000000000..489673d47 --- /dev/null +++ b/lab3/include/string.h @@ -0,0 +1,7 @@ +#pragma once + +int strcmp(const char *str1, const char *str2); +int memcmp(const void *str1, const void *str2, int n); +char *strncpy(char *dest, const char *src, int n); +int strlen(const char *str); +char *strcat(char *dest, const char *src); \ No newline at end of file diff --git a/lab3/include/timer.h b/lab3/include/timer.h new file mode 100644 index 000000000..e7066718e --- /dev/null +++ b/lab3/include/timer.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +void timer_enable_interrupt(); +void timer_disable_interrupt(); +void timer_irq_handler(); +uint64_t timer_get_uptime(); + +void timer_add(void (*callback)(void *), void *arg, int after); +void set_timer(const char *message, int after); +void reset_timeup(); +int get_timeup(); +void set_timeup(); \ No newline at end of file diff --git a/lab3/include/uart.h b/lab3/include/uart.h new file mode 100644 index 000000000..0a8480fa0 --- /dev/null +++ b/lab3/include/uart.h @@ -0,0 +1,28 @@ +#pragma once + +#include "hardware.h" + +#define UART_BUF_SIZE 1024 + +#define BACKSPACE '\b' +#define DELETE 127 +#define NEWLINE '\n' +#define TAB '\t' +#define CARRIAGE_RETURN '\r' + +void uart_init(); +char uart_getc(); +void uart_putc(char c); +void uart_hex(unsigned int h); +void uart_puts(const char *s); + +void uart_enable_tx_interrupt(); +void uart_disable_tx_interrupt(); +void uart_enable_rx_interrupt(); +void uart_disable_rx_interrupt(); + +void uart_tx_irq_handler(); +void uart_rx_irq_handler(); + +void uart_async_read(char *buf, int len); +void uart_async_write(const char *s); \ No newline at end of file diff --git a/lab3/include/utils.h b/lab3/include/utils.h new file mode 100644 index 000000000..f3d3597d8 --- /dev/null +++ b/lab3/include/utils.h @@ -0,0 +1,20 @@ +#pragma once + +/** + * @brief Align `n` to be a multiple of 4. + * + * @param n A number + * @return Algined number + */ +int align4(int n); + +int atoi(const char *s); + +/** + * @brief Convert hexadecimal string to int. + * + * @param s: hexadecimal string + * @param n: string length + * @return Converted int number + */ +int hextoi(char *s, int n); \ No newline at end of file diff --git a/lab3/initramfs.cpio b/lab3/initramfs.cpio new file mode 100644 index 0000000000000000000000000000000000000000..4710cde216dcc1a54264c473b0848d67be1b5c08 GIT binary patch literal 4096 zcmd^?%Z}4X6o!q&%4_yhjpa=|q|1q8x~BzlL&pM%6=EMbw$oU0;wY}pFgIylVV;Ax zLA(f00OwLp;#)$)a!Rd~Q`gyy;74fb+8y{Pq4j(mS| z<4?vejaMJ89P7(bHPq;L<3j_Y?c=?`br=2dT;KP?a6D!ma>Tk$$MdWBNu(mzSY)e2 z48`9fUT1}P0)MZ>D*Zhc&&6EXqTt)%K!Ifc=(51e8K7$^%t_sMdir36?`5C<$@| zg6@m}9j7%Q0T}24b?gE;0TLX?vSF^Kqp1NF!A@wAR-ybxeO`8eY0X_vy(7M2h!6BfJfDthosnAEknK^K893uq0jN%bOp96I0 zEoJB&Cy?RnC1<=tR0PMVNoZyfkO<7c-Y0;@?KCueY&i)}7Ho%x&$zY$%?S9xfTO4j zfN#yytPt!Zq)^?Fh=f-60C8<$Bug$OMPT?CM!m!lojZ~$9}gl+<4jmtp^ zJxJgpH+Xk^rv_Gm@o7UvfP1BFWRC{)0>Uz3r{G=$xGk_DqXIxy^zv%v(Qy{6oC&C7 zpG1cXou|n`h(KOe2<^)EYQVQXHqwAPYVU+YN?#V$1Hx(i7@_(XL1)q{!M6J~!1G8q z72Zs}07&N;0U6wSO*ly)1RThrbIt4H8tBb(OaN+V){s8chDYn%$u#t?J+2|8%k}O( z>fADp@*FDHe?J)yjsG4P#^+xi8DBnr{rV0DAMI;y*OOEmU%1EWS*-CVRGUL}&b9kJ zv|!Ec8RzGMM47`fja62x#N(k#Kd8s%@HQ{zQ7&esiZ;sR?l)6FTNYdKLH;VF>GD97 zBs747E-fjyF-+s)c)6yz34NRAO1SfC-yE*%b-W**uHz?eI1OJ?9q*2HfAY6Y?YG7V z^A7V!sdSYl;wIf@ajG_HDqf{gl5RwprFl}CW~nxJu@Z|!&EE6OUy#OKwo>EKOo?T5 zo5fd$BdSnQUS@x$wb5d+m;r)mJu}(rwrou;)8%YaZkN%zHnmMRC7e}lERRYxi;LxY z)-^jX3YcBrWyx&){*QVfl&j^nL>$b45m)9vSEi}&Q#yc?ANAj$CztOQ==)mAcW QznQ*0J3E7C> 3; \ No newline at end of file diff --git a/lab3/src/command.c b/lab3/src/command.c new file mode 100644 index 000000000..2b69e71c2 --- /dev/null +++ b/lab3/src/command.c @@ -0,0 +1,175 @@ +#include "command.h" + +#include "initramfs.h" +#include "malloc.h" +#include "mbox.h" +#include "shell.h" +#include "string.h" +#include "timer.h" +#include "uart.h" +#include "utils.h" + +struct command commands[] = { + {.name = "help", .help = "print this help menu", .func = cmd_help}, + {.name = "hello", .help = "print Hello there!", .func = cmd_hello}, + {.name = "clear", .help = "clear screen", .func = cmd_clear}, + {.name = "reboot", .help = "reboot the device", .func = cmd_reboot}, + {.name = "cancel", .help = "cancel reboot", .func = cmd_cancel}, + {.name = "info", .help = "hardware information", .func = cmd_info}, + {.name = "ls", .help = "list ramdisk files", .func = cmd_ls}, + {.name = "cat", .help = "print ramdisk file", .func = cmd_cat}, + {.name = "run", .help = "run a program", .func = cmd_run}, + {.name = "timer", .help = "set timer", .func = cmd_timer}, + {.name = "lab", .help = "showcase lab requirements", .func = cmd_lab}, + {.name = END_OF_COMMAND_LIST}}; + +void cmd_lab() { + char buf[SHELL_BUF_SIZE]; + uart_puts("(1) UART async write"); + uart_putc(NEWLINE); + uart_puts("(2) UART async read"); + uart_putc(NEWLINE); + uart_puts("Please select: "); + read_user_input(buf); + switch (atoi(buf)) { + case 1: + uart_puts(": "); + read_user_input(buf); + uart_async_write(buf); + // uart_async_write("[INFO] Test the UART async write function\n"); + break; + case 2: + uart_puts("(Please type something in 3 sec.)"); + uart_putc(NEWLINE); + // set a timer to 3 sec. + reset_timeup(); + timer_add((void (*)(void *))set_timeup, (void *)0, 3); + uart_enable_rx_interrupt(); + while (!get_timeup()); + // time's up, get the buf content and print + uart_async_read(buf, 256); + uart_puts(buf); + uart_putc(NEWLINE); + uart_putc(NEWLINE); + break; + default: + uart_puts("Option not found."); + uart_putc(NEWLINE); + break; + } +} + +void cmd_timer() { + char sec[SHELL_BUF_SIZE]; + uart_puts("Duration(sec.): "); + read_user_input(sec); + + char *msg = simple_malloc(SHELL_BUF_SIZE); + uart_puts(": "); + read_user_input(msg); + set_timer(msg, atoi(sec)); +} + +void cmd_run() { + // Get filename from user input + char buffer[SHELL_BUF_SIZE]; + uart_puts(": "); + read_user_input(buffer); + initramfs_run(buffer); +} + +void cmd_help() { + int i = 0; + + uart_puts("------------------------------"); + uart_putc(NEWLINE); + while (1) { + if (!strcmp(commands[i].name, END_OF_COMMAND_LIST)) { + break; + } + uart_puts(commands[i].name); + uart_putc(TAB); + uart_puts(": "); + uart_puts(commands[i].help); + uart_putc(NEWLINE); + i++; + } + uart_puts("------------------------------"); + uart_putc(NEWLINE); + uart_putc(NEWLINE); +} + +void cmd_hello() { + uart_puts("Hello there!"); + uart_putc(NEWLINE); + uart_putc(NEWLINE); +} + +void cmd_reboot() { + uart_puts("Start Rebooting..."); + uart_putc(NEWLINE); + uart_putc(NEWLINE); + + // Reboot after 0x20000 ticks + *PM_RSTC = PM_PASSWORD | 0x20; // Full reset + *PM_WDOG = PM_PASSWORD | 0x20000; // Number of watchdog ticks +} + +void cmd_cancel() { + uart_puts("Rebooting Attempt Aborted, if any."); + uart_putc(NEWLINE); + uart_putc(NEWLINE); + *PM_RSTC = PM_PASSWORD | 0; + *PM_WDOG = PM_PASSWORD | 0; +} + +void cmd_info() { + // Get board revision + get_board_revision(); + uart_puts("board revision: "); + uart_hex(mbox[5]); + uart_putc(NEWLINE); + + // Get ARM memory base address and size + get_arm_memory_status(); + uart_puts("device base memory address: "); + uart_hex(mbox[5]); + uart_putc(NEWLINE); + uart_puts("device memory size: "); + uart_hex(mbox[6]); + uart_putc(NEWLINE); + uart_putc(NEWLINE); +} + +void cmd_ls() { + initramfs_list(); + uart_putc(NEWLINE); +} + +void cmd_cat() { + // Get filename from user input + uart_puts(": "); + char buf[MAX_BUF_SIZE + 1]; + int idx = 0; + while (1) { + char c = uart_getc(); + uart_putc(c); + if (c == NEWLINE) { + buf[idx] = '\0'; + break; + } else if (c == 127) { // Backspaces + if (idx > 0) { + buf[idx--] = 0; + uart_putc(BACKSPACE); + uart_putc(' '); + uart_putc(BACKSPACE); + } + } else { + buf[idx++] = c; + } + } + initramfs_cat(buf); + uart_putc(NEWLINE); +} + +void cmd_clear() { uart_puts("\033[2J\033[H"); } diff --git a/lab3/src/devtree.c b/lab3/src/devtree.c new file mode 100644 index 000000000..441d6bafc --- /dev/null +++ b/lab3/src/devtree.c @@ -0,0 +1,66 @@ +#include "devtree.h" + +#include "string.h" +#include "uart.h" +#include "utils.h" + +// Assign a non-zero value to be stored in the .data section +void *DTB_BASE = (void *)0xF; + +/** + * @brief Convert a 4-byte big-endian sequence to little-endian. + * + * @param s: big-endian sequence + * @return little-endian sequence + */ +static uint32_t be2le(const void *s) { + const uint8_t *bytes = (const uint8_t *)s; + return (uint32_t)bytes[0] << 24 | (uint32_t)bytes[1] << 16 | + (uint32_t)bytes[2] << 8 | (uint32_t)bytes[3]; +} + +void fdt_traverse(void (*callback)(void *, char *)) { + uart_puts("[INFO] Dtb is loaded at "); + uart_hex((uintptr_t)DTB_BASE); + uart_putc(NEWLINE); + + uintptr_t dtb_ptr = (uintptr_t)DTB_BASE; + struct fdt_header *header = (struct fdt_header *)dtb_ptr; + + // Check the magic number + if (be2le(&(header->magic)) != 0xD00DFEED) { + uart_puts("[ERROR] Dtb header magic does not match!"); + uart_putc(NEWLINE); + } + + uintptr_t structure = (uintptr_t)header + be2le(&header->off_dt_struct); + uintptr_t strings = (uintptr_t)header + be2le(&header->off_dt_strings); + uint32_t structure_size = be2le(&header->size_dt_struct); + + // Parse the structure block + uintptr_t ptr = structure; // Point to the beginning of structure block + while (ptr < structure + structure_size) { + uint32_t token = be2le((char *)ptr); + ptr += 4; // Token takes 4 bytes + + switch (token) { + case FDT_BEGIN_NODE: + ptr += align4(strlen((char *)ptr) + 1); + break; + case FDT_PROP: + uint32_t len = be2le((char *)ptr); + ptr += 4; + uint32_t nameoff = be2le((char *)ptr); + ptr += 4; + callback((void *)(uintptr_t)be2le((void *)ptr), + (char *)(strings + nameoff)); + // if (!strcmp((char *)(strings + nameoff), "linux,initrd-start")) + // callback((void *)(uintptr_t)be2le((void *)ptr)); + ptr += align4(len); + break; + case FDT_END_NODE: + case FDT_NOP: + case FDT_END: + } + } +} \ No newline at end of file diff --git a/lab3/src/initramfs.c b/lab3/src/initramfs.c new file mode 100644 index 000000000..bf69e44b8 --- /dev/null +++ b/lab3/src/initramfs.c @@ -0,0 +1,118 @@ +#include "initramfs.h" + +#include + +#include "malloc.h" +#include "string.h" +#include "uart.h" +#include "utils.h" + +static void *ramfs_base; + +void initramfs_callback(void *addr, char *property) { + if (!strcmp(property, "linux,initrd-start")) { + uart_puts("[INFO] Initial Ramdisk is mounted at "); + uart_hex((uintptr_t)addr); + uart_putc(NEWLINE); + ramfs_base = (char *)addr; + } +} + +void initramfs_list() { + char *fptr = (char *)ramfs_base; + + // Check if the file is encoded with New ASCII Format + while (memcmp(fptr + sizeof(cpio_t), "TRAILER!!!", 10)) { + cpio_t *header = (cpio_t *)fptr; + + // New ASCII Format uses 8-byte hexadecimal string for all numbers + int namesize = hextoi(header->c_namesize, 8); + int filesize = hextoi(header->c_filesize, 8); + + // Total size of (header + pathname) is a multiple of four bytes + // File data is also padded to a multiple of four bytes + int headsize = align4(sizeof(cpio_t) + namesize); + int datasize = align4(filesize); + + // Print file pathname + char pathname[namesize]; + strncpy(pathname, fptr + sizeof(cpio_t), namesize); + uart_puts(pathname); + uart_putc(NEWLINE); + + fptr += headsize + datasize; + } +} + +void initramfs_cat(const char *filename) { + char *fptr = (char *)ramfs_base; + + // Check if the file is encoded with New ASCII Format + while (memcmp(fptr + sizeof(cpio_t), "TRAILER!!!", 10)) { + cpio_t *header = (cpio_t *)fptr; + + // New ASCII Format uses 8-byte hexadecimal string for all numbers + int namesize = hextoi(header->c_namesize, 8); + int filesize = hextoi(header->c_filesize, 8); + + // Total size of (header + pathname) is a multiple of four bytes + // File data is also padded to a multiple of four bytes + int headsize = align4(sizeof(cpio_t) + namesize); + int datasize = align4(filesize); + + // Match target filename + char pathname[namesize]; + strncpy(pathname, fptr + sizeof(cpio_t), namesize); + if (!strcmp(filename, pathname)) { + // Print its content + char data[filesize + 1]; + strncpy(data, fptr + headsize, filesize); + data[filesize] = '\0'; + uart_puts(data); + return; + } + + fptr += headsize + datasize; + } + + uart_puts("File not found."); + uart_putc(NEWLINE); +} + +void initramfs_run(const char *filename) { + char *fptr = (char *)ramfs_base; + + // Check if the file is encoded with New ASCII Format + while (memcmp(fptr + sizeof(cpio_t), "TRAILER!!!", 10)) { + cpio_t *header = (cpio_t *)fptr; + + // New ASCII Format uses 8-byte hexadecimal string for all numbers + int namesize = hextoi(header->c_namesize, 8); + int filesize = hextoi(header->c_filesize, 8); + + // Total size of (header + pathname) is a multiple of four bytes + // File data is also padded to a multiple of four bytes + int headsize = align4(sizeof(cpio_t) + namesize); + int datasize = align4(filesize); + + // Match target filename + char pathname[namesize]; + strncpy(pathname, fptr + sizeof(cpio_t), namesize); + if (!strcmp(filename, pathname)) { + // Load the user program + char *program = (char *)0x40000; + for (int i = 0; i < filesize; i++) *program++ = (fptr + headsize)[i]; + unsigned long sp = (unsigned long)simple_malloc(DEFAULT_STACK_SIZE); + asm volatile("msr spsr_el1, %0" ::"r"(0x3C0)); + asm volatile("msr elr_el1, %0" ::"r"(0x40000)); + asm volatile("msr sp_el0, %0" ::"r"(sp + DEFAULT_STACK_SIZE)); + asm volatile("eret;"); // Return to EL0 and execute + return; + } + + fptr += headsize + datasize; + } + + uart_puts("File not found."); + uart_putc(NEWLINE); +} \ No newline at end of file diff --git a/lab3/src/irq.c b/lab3/src/irq.c new file mode 100644 index 000000000..0677cf743 --- /dev/null +++ b/lab3/src/irq.c @@ -0,0 +1,93 @@ +#include "irq.h" + +#include "malloc.h" +#include "timer.h" +#include "uart.h" + +typedef struct __irq_task_t { + void (*func)(); + int priority; + int busy; // 0 (default) or 1 (busy) + struct __irq_task_t *prev; + struct __irq_task_t *next; +} irq_task_t; + +static irq_task_t *head = 0; + +/* Disable interrupt before calling irq_add_task() */ +void irq_add_task(void (*callback)(), int priority) { + irq_task_t *task = (irq_task_t *)simple_malloc(sizeof(irq_task_t)); + task->func = callback; + task->priority = priority; + task->busy = 0; + task->prev = 0; + task->next = 0; + + if (head == 0 || task->priority < head->priority) { + task->next = head; + task->prev = 0; + if (head != 0) head->prev = task; + head = task; + return; + } + + irq_task_t *current = head; + while (current->next != 0 && current->next->priority <= task->priority) + current = current->next; + task->next = current->next; + if (current->next != 0) current->next->prev = task; + current->next = task; + task->prev = current; +} + +void enable_interrupt() { + // to enable interrupts in EL1 + asm volatile( // Clear the D, A, I, F bits in the DAIF register + "msr DAIFClr, 0xF;"); +} + +void disable_interrupt() { + // to disable interrupts in EL1 + asm volatile( // Set the D, A, I, F bits to 1 in the DAIF register + "msr DAIFSet, 0xF;"); +} + +void irq_entry() { + disable_interrupt(); // Enter the critical section + + if (*IRQ_PENDING_1 & (1 << 29)) { // UART interrupt + // 0110 -> 0x2: Transmit interrupt, 0x4: Receive interrupt + switch (*AUX_MU_IIR & 0x6) { + case 0x2: + uart_disable_tx_interrupt(); + irq_add_task(uart_tx_irq_handler, PRIORITY_HIGH); + break; + case 0x4: + uart_disable_rx_interrupt(); + irq_add_task(uart_rx_irq_handler, PRIORITY_HIGH); + break; + } + } else if (*CORE0_INTERRUPT_SOURCE & 0x2) { // Core 0 timer interrupt + timer_disable_interrupt(); + irq_add_task(timer_irq_handler, PRIORITY_LOW); + } + + enable_interrupt(); // Leave the critical section + + // Preemption: run the task with the highest priority + while (head != 0 && !head->busy) { + disable_interrupt(); + irq_task_t *task = head; // Get a task from head + task->busy = 1; // Flag the task as under processing + enable_interrupt(); + + task->func(); // Run the tasks with interrupts enabled + + // Remove the task + disable_interrupt(); + if (task->prev != 0) task->prev->next = task->next; + if (task->next != 0) task->next->prev = task->prev; + if (task == head) head = task->next; + enable_interrupt(); + } +} \ No newline at end of file diff --git a/lab3/src/main.c b/lab3/src/main.c new file mode 100644 index 000000000..c0f235395 --- /dev/null +++ b/lab3/src/main.c @@ -0,0 +1,22 @@ +#include "devtree.h" +#include "initramfs.h" +#include "irq.h" +#include "malloc.h" +#include "shell.h" +#include "timer.h" +#include "uart.h" + +int main() { + /* Initialization */ + uart_init(); + malloc_init(); + enable_interrupt(); + timer_enable_interrupt(); + fdt_traverse(initramfs_callback); + + /* Shell */ + welcome_msg(); + run_shell(); + + return 0; +} \ No newline at end of file diff --git a/lab3/src/malloc.c b/lab3/src/malloc.c new file mode 100644 index 000000000..b583d6f8a --- /dev/null +++ b/lab3/src/malloc.c @@ -0,0 +1,16 @@ +#include "malloc.h" + +// `__bss_end` is defined in linker script +extern char *__bss_end; + +static char *heap_top; + +// Set heap base address +void malloc_init() { heap_top = (char *)&__bss_end; } + +void *simple_malloc(int size) { + void *p = (void *)heap_top; + if (size < 0) return 0; + heap_top += size; + return p; +} \ No newline at end of file diff --git a/lab3/src/mbox.c b/lab3/src/mbox.c new file mode 100644 index 000000000..7bb73792a --- /dev/null +++ b/lab3/src/mbox.c @@ -0,0 +1,44 @@ +#include "mbox.h" + +volatile unsigned int __attribute__((aligned(16))) mbox[36]; + +int mailbox_call(unsigned char c) { + unsigned int r = ((unsigned int)(((unsigned long)&mbox & ~0xF) | (c & 0xF))); + + // Wait until the mailbox is not full + while (*MAILBOX_REG_STATUS & MAILBOX_FULL) asm volatile("nop"); + + // Write to the register + *MAILBOX_REG_WRITE = r; + + while (1) { + // Wait for the response + while (*MAILBOX_REG_STATUS & MAILBOX_EMPTY) asm volatile("nop"); + + if (r == *MAILBOX_REG_READ) return mbox[1] == MAILBOX_RESPONSE; + } + return 0; +} + +int get_board_revision() { + mbox[0] = 7 * 4; + mbox[1] = REQUEST_CODE; + mbox[2] = TAGS_HARDWARE_BOARD_REVISION; + mbox[3] = 4; + mbox[4] = TAG_REQUEST_CODE; + mbox[5] = 0; + mbox[6] = END_TAG; + return mailbox_call(MAILBOX_CH_PROP); +} + +int get_arm_memory_status() { + mbox[0] = 8 * 4; + mbox[1] = REQUEST_CODE; + mbox[2] = TAGS_HARDWARE_ARM_MEM; + mbox[3] = 8; + mbox[4] = TAG_REQUEST_CODE; + mbox[5] = 0; + mbox[6] = 0; + mbox[7] = END_TAG; + return mailbox_call(MAILBOX_CH_PROP); +} \ No newline at end of file diff --git a/lab3/src/shell.c b/lab3/src/shell.c new file mode 100644 index 000000000..d7ad7d386 --- /dev/null +++ b/lab3/src/shell.c @@ -0,0 +1,88 @@ +#include "shell.h" + +#include "command.h" +#include "string.h" +#include "uart.h" + +void welcome_msg() { + cmd_info(); + uart_puts( + "*******************************\n" + "*** YADOS 0.03 for OSC 2024 ***\n" + "*******************************\n" + "Hopefully this will be Yet Another Dope OS!"); + uart_putc(NEWLINE); + uart_putc(NEWLINE); + cmd_hello(); +} + +void run_shell() { + while (1) { + char buffer[SHELL_BUF_SIZE]; + + uart_puts("# "); + read_user_input(buffer); + exec_command(buffer); + + // Stop the shell if rebooting + // if (!strcmp(buffer, "reboot")) break; + } +} + +void read_user_input(char *buf) { + int idx = 0; + while (idx < SHELL_BUF_SIZE) { + char c = uart_getc(); + if (c == NEWLINE) { + uart_putc(c); + buf[idx] = '\0'; + return; + } + if (c >= 32 && c <= 126) { + uart_putc(c); + buf[idx++] = c; + } else if (c == BACKSPACE || c == DELETE) { + // Handle backspaces + if (idx > 0) { + idx--; + // buf[idx--] = '\0'; + uart_putc(BACKSPACE); + uart_putc(' '); + uart_putc(BACKSPACE); + } + } else { + // Ignore unprintable chars + continue; + } + } + buffer_overflow_message(); +} + +void buffer_overflow_message() { + uart_putc(NEWLINE); + uart_puts("Buffer overflow:"); + uart_putc(NEWLINE); + uart_puts("Please re-enter your command."); + uart_putc(NEWLINE); +} + +int exec_command(const char *command) { + int i = 0; + + while (1) { + if (strlen(command) == 0) { + return 0; + } + if (!strcmp(commands[i].name, END_OF_COMMAND_LIST)) { + uart_puts("Command not found."); + uart_putc(NEWLINE); + return -1; + } + if (!strcmp(commands[i].name, command)) { + commands[i].func(); + return 0; + } + i++; + } + return 0; +} \ No newline at end of file diff --git a/lab3/src/string.c b/lab3/src/string.c new file mode 100644 index 000000000..ffe02eb48 --- /dev/null +++ b/lab3/src/string.c @@ -0,0 +1,40 @@ +#include "string.h" + +int strcmp(const char *str1, const char *str2) { + while (*str1 && *str1 == *str2) str1++, str2++; + return *(unsigned char *)str1 - *(unsigned char *)str2; +} + +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; +} + +char *strncpy(char *dest, const char *src, int n) { + while (n-- && (*dest++ = *src++)); + return dest; +} + +int strlen(const char *str) { + int len = 0; + while (*str++ != '\0') len++; + return len; +} + +char *strcat(char *dest, const char *src) { + char *d = dest; + + // Move the pointer to the end of the dest string + while (*d != '\0') d++; + // Copy the src string to the end of the dest string + while (*src != '\0') *d++ = *src++; + // Add the null terminator + *d = '\0'; + + return dest; +} \ No newline at end of file diff --git a/lab3/src/timer.c b/lab3/src/timer.c new file mode 100644 index 000000000..f2e139067 --- /dev/null +++ b/lab3/src/timer.c @@ -0,0 +1,104 @@ +#include "timer.h" + +#include "malloc.h" +#include "string.h" +#include "uart.h" + +static int timeup = 0; + +typedef struct __timer_t { + void (*func)(void *); + void *arg; + int time; + struct __timer_t *next; +} timer_entry; + +static timer_entry *head = 0; + +void timer_enable_interrupt() { + asm volatile( + // Enable Counter-timer interrupt + "mov x0, 1;" + "msr cntp_ctl_el0, x0;" // Physical Timer Control register + + // Set the Counter-timer frequency to 1 second + "mrs x0, cntfrq_el0;" // Frequency register + "msr cntp_tval_el0, x0;" // Physical Timer TimerValue register + + // Unmask Counter-timer interrupt + "mov x0, 2;" + "ldr x1, =%0;" + "str w0, [x1];" // IRQ Enable Register + : + : "i"(CORE0_TIMER_IRQCNTL) + : "x0", "x1" // registers modified + ); +} + +void timer_disable_interrupt() { + asm volatile( + // Mask timer interrupt + "mov x0, 0;" + "ldr x1, =%0;" + "str w0, [x1];" + : + : "i"(CORE0_TIMER_IRQCNTL) + : "x0", "x1"); +} + +void timer_irq_handler() { + // Set up 1 second core timer interrupt + asm volatile( + "mrs x0, cntfrq_el0;" // Frequency register + "msr cntp_tval_el0, x0;" // Physical Timer TimerValue register + ); + + // Check the timer queue + while (head != 0 && timer_get_uptime() >= head->time) { + head->func(head->arg); // Execute the callback function + head = head->next; // Remove the head node after func finished + } + + timer_enable_interrupt(); +} + +uint64_t timer_get_uptime() { + uint64_t cntpct_el0 = 0; + uint64_t cntfrq_el0 = 0; + asm volatile("mrs %0, cntpct_el0" : "=r"(cntpct_el0)); + asm volatile("mrs %0, cntfrq_el0" : "=r"(cntfrq_el0)); + return cntpct_el0 / cntfrq_el0; +} + +void timer_add(void (*callback)(void *), void *arg, int duration) { + // Insert the new timer node into the linked list (sorted by time) + + timer_entry *timer = (timer_entry *)simple_malloc(sizeof(timer_entry)); + timer->func = callback; + timer->arg = arg; + timer->time = timer_get_uptime() + duration; + timer->next = 0; + + if (head == 0 || timer->time < head->time) { + // Insert at the beginning of the list + timer->next = head; + head = timer; + return; + } + + timer_entry *current = head; + while (current->next != 0 && current->next->time <= timer->time) + current = current->next; + timer->next = current->next; + current->next = timer; +} + +void set_timer(const char *message, int duration) { + timer_add((void (*)(void *))uart_puts, (void *)message, duration); +} + +void reset_timeup() { timeup = 0; } + +int get_timeup() { return timeup; } + +void set_timeup() { timeup = 1; } \ No newline at end of file diff --git a/lab3/src/traps.c b/lab3/src/traps.c new file mode 100644 index 000000000..f4a97dc44 --- /dev/null +++ b/lab3/src/traps.c @@ -0,0 +1,34 @@ +#include + +#include "uart.h" + +void print_registers(uint64_t elr, uint64_t esr, uint64_t spsr) { + // Print spsr_el1 + uart_puts("spsr_el1: "); + uart_hex(spsr); + uart_putc(NEWLINE); + + // Print elr_el1 + uart_puts(" elr_el1: "); + uart_hex(elr); + uart_putc(NEWLINE); + + // Print esr_el1 + uart_puts(" esr_el1: "); + uart_hex(esr); + uart_putc(NEWLINE); + uart_putc(NEWLINE); +} + +void exception_entry(uint64_t elr, uint64_t esr, uint64_t spsr) { + uart_puts("Exception Handler"); + uart_putc(NEWLINE); + print_registers(elr, esr, spsr); +} + +void invalid_entry(uint64_t elr, uint64_t esr, uint64_t spsr) { + uart_puts("[ERROR] The exception handler is not implemented"); + uart_putc(NEWLINE); + print_registers(elr, esr, spsr); + while (1); +} \ No newline at end of file diff --git a/lab3/src/uart.c b/lab3/src/uart.c new file mode 100644 index 000000000..1adfdd0e3 --- /dev/null +++ b/lab3/src/uart.c @@ -0,0 +1,126 @@ +#include "uart.h" + +#include "irq.h" +#include "string.h" + +static int uart_read_idx = 0; +static int uart_write_idx = 0; +static char uart_read_buffer[UART_BUF_SIZE]; +static char uart_write_buffer[UART_BUF_SIZE]; + +void uart_init() { + // Configure GPIO pins + register unsigned int r = *GPFSEL1; + r &= ~((7 << 12) | (7 << 15)); + r |= (2 << 12) | (2 << 15); + *GPFSEL1 = r; + + // Disable GPIO pull up/down + *GPPUD = 0; + for (int i = 0; i < 150; i++); + *GPPUDCLK0 = (1 << 14) | (1 << 15); + for (int i = 0; i < 150; i++); + *GPPUD = 0; + *GPPUDCLK0 = 0; + + // Initialize mini UART + *AUX_ENABLE |= 1; // Enable mini UART + *AUX_MU_CNTL = 0; // Disable Tx and Rx during setup + *AUX_MU_IER = 0; // Disable interrupt + *AUX_MU_LCR = 3; // Set data size to 8 bits + *AUX_MU_MCR = 0; // Disable auto flow control + *AUX_MU_BAUD = 270; // Set baud rate to 115200 + *AUX_MU_IIR = 6; // No FIFO + *AUX_MU_CNTL = 3; // Enable Tx and Rx + + // Enable AUX interrupts + *ENABLE_IRQS_1 |= 1 << 29; +} + +char uart_getc() { + // Check the data ready field on bit 0 of AUX_MU_LSR_REG + while (!(*AUX_MU_LSR & 0x01)); + char c = (char)(*AUX_MU_IO); // Read from AUX_MU_IO_REG + return c == CARRIAGE_RETURN ? NEWLINE : c; +} + +void uart_putc(char c) { + // Convert '\n' to '\r\n' + if (c == NEWLINE) uart_putc(CARRIAGE_RETURN); + + // Check the transmitter empty field on bit 5 of AUX_MU_LSR_REG + while (!(*AUX_MU_LSR & 0x20)); + *AUX_MU_IO = c; // Write to AUX_MU_IO_REG +} + +void uart_hex(unsigned int h) { + uart_puts("0x"); + unsigned int n; + for (int c = 28; c >= 0; c -= 4) { + n = (h >> c) & 0xF; + n += n > 9 ? 0x37 : '0'; + uart_putc(n); + } +} + +void uart_puts(const char *s) { + while (*s) { + uart_putc(*s++); + } +} + +void uart_enable_tx_interrupt() { *AUX_MU_IER |= 0x02; } + +void uart_disable_tx_interrupt() { *AUX_MU_IER &= 0x01; } + +void uart_enable_rx_interrupt() { *AUX_MU_IER |= 0x01; } + +void uart_disable_rx_interrupt() { *AUX_MU_IER &= 0x2; } + +void uart_tx_irq_handler() { + uart_disable_tx_interrupt(); + if (uart_write_idx < UART_BUF_SIZE && + uart_write_buffer[uart_write_idx] != 0) { + // Buffer is not empty -> send characters + // *AUX_MU_IO = uart_write_buffer[uart_write_idx++]; + uart_putc(uart_write_buffer[uart_write_idx++]); + uart_enable_tx_interrupt(); + } +} + +void uart_rx_irq_handler() { + char c = (char)(*AUX_MU_IO); + uart_read_buffer[uart_read_idx++] = (c == CARRIAGE_RETURN) ? NEWLINE : c; + if (uart_read_idx >= UART_BUF_SIZE) uart_read_idx = 0; + uart_enable_rx_interrupt(); +} + +void uart_async_read(char *buf, int len) { + uart_disable_rx_interrupt(); + for (int i = 0; i < uart_read_idx && i < len; i++) + buf[i] = uart_read_buffer[i]; + buf[uart_read_idx] = 0; + uart_read_idx = 0; +} + +void uart_async_write(const char *s) { + // Copy string to the write buffer + int len = 0; + while (*s != '\0') { + if (len >= UART_BUF_SIZE) { + uart_puts("[ERROR] Exceed the UART buffer size"); + uart_putc(NEWLINE); + return; + } + if (*s == NEWLINE) { + // Convert \n to \r\n + uart_write_buffer[len++] = CARRIAGE_RETURN; + uart_write_buffer[len++] = NEWLINE; + } else + uart_write_buffer[len++] = *s; + s++; + } + uart_write_buffer[len] = '\0'; + uart_write_idx = 0; // Reset the buffer index + uart_enable_tx_interrupt(); +} \ No newline at end of file diff --git a/lab3/src/utils.c b/lab3/src/utils.c new file mode 100644 index 000000000..f5226d183 --- /dev/null +++ b/lab3/src/utils.c @@ -0,0 +1,42 @@ +#include "utils.h" + +int align4(int n) { return n + (4 - n % 4) % 4; } + +int atoi(const char *s) { + int result = 0; + int sign = 1; + int i = 0; + + // Skip leading spaces + while (s[i] == ' ') { + i++; + } + + // Handle positive and negative sign + if (s[i] == '-') { + sign = -1; + i++; + } else if (s[i] == '+') { + i++; + } + + // Convert string to integer + while (s[i] >= '0' && s[i] <= '9') { + result = result * 10 + (s[i] - '0'); + i++; + } + + return sign * result; +} + +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; +} \ No newline at end of file diff --git a/lab3/start.S b/lab3/start.S new file mode 100644 index 000000000..c6b255ecf --- /dev/null +++ b/lab3/start.S @@ -0,0 +1,50 @@ +.section ".text.boot" + +.global _start + +_start: + /* get dtb address from x0 */ + ldr x1, =DTB_BASE // defined in devtree.c + str x0, [x1] + + /* get cpu id */ + mrs x1, mpidr_el1 /* Multiprocessor Affinity Register */ + and x1, x1, #3 + cbnz x1, halt // halt if cpu id != 0 + + /* set exception vector table */ + adr x0, exception_vector_table // defined in traps.S + msr vbar_el1, x0 + + /* switch from EL2 to EL1 */ + bl from_el2_to_el1 + + /* set stack pointer */ + ldr x1, =_start + mov sp, x1 + + /* clear bss section */ + ldr x1, =__bss_start + ldr w2, =__bss_size + +bss_reset: + cbz w2, run_main + str xzr, [x1], #8 + sub w2, w2, #1 + cbnz w2, bss_reset + +run_main: + /* branch to main function */ + bl main + +halt: + wfe + b halt + +from_el2_to_el1: + mov x0, (1 << 31) + msr hcr_el2, x0 // EL1 uses aarch64 + mov x0, 0x3C5 // ... 0011 1100 0101 + msr spsr_el2, x0 + msr elr_el2, lr + eret // return to EL1 diff --git a/lab3/traps.S b/lab3/traps.S new file mode 100644 index 000000000..80b4eafc4 --- /dev/null +++ b/lab3/traps.S @@ -0,0 +1,103 @@ +/* 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] + str x30, [sp, 16 * 15] + mrs x28, spsr_el1 /* Saved Program Status Register (EL1) */ + mrs x29, elr_el1 /* Exception Link Register (EL1) */ + stp x28, x29, [sp, 16 * 16] +.endm + +/* load general registers from stack */ +.macro load_all + ldp x28, x29, [sp, 16 * 16] + msr spsr_el1, x28 /* Saved Program Status Register (EL1) */ + msr elr_el1, x29 /* Exception Link Register (EL1) */ + 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] + ldr x30, [sp, 16 * 15] + add sp, sp, 32 * 9 +.endm + +exception_handler: + save_all + mrs x0, elr_el1 /* Exception Link Register (EL1) */ + mrs x1, esr_el1 /* ESR_EL1, Exception Syndrome Register (EL1) */ + mrs x2, spsr_el1 /* Saved Program Status Register (EL1) */ + bl exception_entry /* defined in traps.c */ + load_all + eret + +irq_handler: + save_all + bl irq_entry /* defined in irq.c */ + load_all + eret + +invalid_exc_handler: + save_all + mrs x0, elr_el1 /* Exception Link Register (EL1) */ + mrs x1, esr_el1 /* ESR_EL1, Exception Syndrome Register (EL1) */ + mrs x2, spsr_el1 /* Saved Program Status Register (EL1) */ + bl invalid_entry /* defined in traps.c */ + load_all + eret + +.macro v_entry label + b \label +.align 7 +.endm + +/* EL1 Exception Vector table + * vector table should be aligned to 0x800 + * and each entry size is 0x80 */ +.align 11 +.global exception_vector_table +exception_vector_table: + // EL1 -> EL1 while using SP_EL0 + v_entry invalid_exc_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler + // EL1 -> EL1 while using SP_EL1 + v_entry invalid_exc_handler + v_entry irq_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler + // EL0 (AArch64) -> EL1 + v_entry exception_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler + // EL0 (AArch32) -> EL1 + v_entry invalid_exc_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler From ebe13a38428739c0820783b93ddd1e585e270c0d Mon Sep 17 00:00:00 2001 From: "Chang, Chun-yien" Date: Wed, 12 Jun 2024 16:01:17 +0800 Subject: [PATCH 3/7] lab4 --- lab4/.gitignore | 6 + lab4/Makefile | 36 ++++ lab4/bootloader/Makefile | 19 ++ lab4/bootloader/boot.c | 61 +++++++ lab4/bootloader/boot.h | 49 +++++ lab4/bootloader/linker.ld | 16 ++ lab4/bootloader/main.c | 42 +++++ lab4/bootloader/start.S | 40 ++++ lab4/include/command.h | 31 ++++ lab4/include/devtree.h | 19 ++ lab4/include/hardware.h | 219 ++++++++++++++++++++++ lab4/include/initramfs.h | 32 ++++ lab4/include/irq.h | 10 + lab4/include/mbox.h | 9 + lab4/include/mem.h | 44 +++++ lab4/include/shell.h | 7 + lab4/include/string.h | 7 + lab4/include/timer.h | 18 ++ lab4/include/uart.h | 31 ++++ lab4/include/utils.h | 20 ++ lab4/initramfs.cpio | Bin 0 -> 4096 bytes lab4/linker.ld | 13 ++ lab4/sender.sh | 27 +++ lab4/src/command.c | 228 +++++++++++++++++++++++ lab4/src/devtree.c | 67 +++++++ lab4/src/initramfs.c | 124 +++++++++++++ lab4/src/irq.c | 98 ++++++++++ lab4/src/main.c | 21 +++ lab4/src/mbox.c | 44 +++++ lab4/src/mem.c | 373 ++++++++++++++++++++++++++++++++++++++ lab4/src/shell.c | 87 +++++++++ lab4/src/string.c | 40 ++++ lab4/src/timer.c | 99 ++++++++++ lab4/src/traps.c | 34 ++++ lab4/src/uart.c | 171 +++++++++++++++++ lab4/src/utils.c | 42 +++++ lab4/start.S | 50 +++++ lab4/traps.S | 103 +++++++++++ 38 files changed, 2337 insertions(+) create mode 100644 lab4/.gitignore create mode 100644 lab4/Makefile create mode 100644 lab4/bootloader/Makefile create mode 100644 lab4/bootloader/boot.c create mode 100644 lab4/bootloader/boot.h create mode 100644 lab4/bootloader/linker.ld create mode 100644 lab4/bootloader/main.c create mode 100644 lab4/bootloader/start.S create mode 100644 lab4/include/command.h create mode 100644 lab4/include/devtree.h create mode 100644 lab4/include/hardware.h create mode 100644 lab4/include/initramfs.h create mode 100644 lab4/include/irq.h create mode 100644 lab4/include/mbox.h create mode 100644 lab4/include/mem.h create mode 100644 lab4/include/shell.h create mode 100644 lab4/include/string.h create mode 100644 lab4/include/timer.h create mode 100644 lab4/include/uart.h create mode 100644 lab4/include/utils.h create mode 100644 lab4/initramfs.cpio create mode 100644 lab4/linker.ld create mode 100755 lab4/sender.sh create mode 100644 lab4/src/command.c create mode 100644 lab4/src/devtree.c create mode 100644 lab4/src/initramfs.c create mode 100644 lab4/src/irq.c create mode 100644 lab4/src/main.c create mode 100644 lab4/src/mbox.c create mode 100644 lab4/src/mem.c create mode 100644 lab4/src/shell.c create mode 100644 lab4/src/string.c create mode 100644 lab4/src/timer.c create mode 100644 lab4/src/traps.c create mode 100644 lab4/src/uart.c create mode 100644 lab4/src/utils.c create mode 100644 lab4/start.S create mode 100644 lab4/traps.S diff --git a/lab4/.gitignore b/lab4/.gitignore new file mode 100644 index 000000000..eddcca901 --- /dev/null +++ b/lab4/.gitignore @@ -0,0 +1,6 @@ +.vscode +build +rootfs +temp +*.img +*.dtb \ No newline at end of file diff --git a/lab4/Makefile b/lab4/Makefile new file mode 100644 index 000000000..729b22978 --- /dev/null +++ b/lab4/Makefile @@ -0,0 +1,36 @@ +ARMGNU ?= aarch64-linux-gnu + +CFLAGS = -Iinclude -nostdlib -nostartfiles -ffreestanding -mgeneral-regs-only -Wall -g +ASMFLAGS = -Iinclude +QEMUFLAGS = -M raspi3b -display none -serial null -serial stdio + +SRC_DIR = src +BUILD_DIR = build + +all: clean kernel8.img + +clean: + rm -rf $(BUILD_DIR) *.img + +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c + mkdir -p $(@D) + $(ARMGNU)-gcc $(CFLAGS) -c $< -o $@ + +$(BUILD_DIR)/_%.o: %.S + $(ARMGNU)-gcc $(ASMFLAGS) -c $< -o $@ + +SRC_FILES = $(wildcard $(SRC_DIR)/*.c) +ASM_FILES = $(wildcard *.S) +OBJ_FILES = $(SRC_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%.o) +OBJ_FILES += $(ASM_FILES:%.S=$(BUILD_DIR)/_%.o) + +kernel8.img: linker.ld $(OBJ_FILES) + $(ARMGNU)-ld -T linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) + $(ARMGNU)-objcopy -O binary $(BUILD_DIR)/kernel8.elf kernel8.img + +qemu: all kernel8.img initramfs.cpio bcm2710-rpi-3-b-plus.dtb + clear + qemu-system-aarch64 $(QEMUFLAGS) -kernel kernel8.img -initrd initramfs.cpio -dtb bcm2710-rpi-3-b-plus.dtb + +debug: all kernel8.img initramfs.cpio bcm2710-rpi-3-b-plus.dtb + qemu-system-aarch64 $(QEMUFLAGS) -kernel kernel8.img -initrd initramfs.cpio -dtb bcm2710-rpi-3-b-plus.dtb -S -s -d int \ No newline at end of file diff --git a/lab4/bootloader/Makefile b/lab4/bootloader/Makefile new file mode 100644 index 000000000..d7882aa27 --- /dev/null +++ b/lab4/bootloader/Makefile @@ -0,0 +1,19 @@ +ARMGNU ?= aarch64-linux-gnu +CFLAGS = -nostdlib -nostartfiles -ffreestanding -mgeneral-regs-only -Wall -g +QEMUFLAGS = -M raspi3b -display none -serial null -serial pty + +all: clean bootloader.img + +clean: + rm -rf build *.img + +bootloader.img: + mkdir -p build + $(ARMGNU)-gcc $(CFLAGS) -c main.c -o build/main.o + $(ARMGNU)-gcc $(CFLAGS) -c start.S -o build/start.o + $(ARMGNU)-gcc $(CFLAGS) -c boot.c -o build/boot.o + $(ARMGNU)-ld -T linker.ld -o build/bootloader.elf build/main.o build/start.o build/boot.o + $(ARMGNU)-objcopy -O binary build/bootloader.elf bootloader.img + +qemu: clean bootloader.img + qemu-system-aarch64 $(QEMUFLAGS) -kernel bootloader.img -initrd ../initramfs.cpio -dtb ../bcm2710-rpi-3-b-plus.dtb \ No newline at end of file diff --git a/lab4/bootloader/boot.c b/lab4/bootloader/boot.c new file mode 100644 index 000000000..d4ff535f5 --- /dev/null +++ b/lab4/bootloader/boot.c @@ -0,0 +1,61 @@ +#include "boot.h" + +void init_uart() { + // Configure GPIO pins + register unsigned int r = *GPFSEL1; + r &= ~((7 << 12) | (7 << 15)); + r |= (2 << 12) | (2 << 15); + *GPFSEL1 = r; + + *GPPUD = 0; + for (int i = 0; i < 150; i++) asm volatile("nop"); + *GPPUDCLK0 = (1 << 14) | (1 << 15); + for (int i = 0; i < 150; i++) asm volatile("nop"); + *GPPUD = 0; + *GPPUDCLK0 = 0; + + *AUX_ENABLE |= 1; + *AUX_MU_CNTL = 0; + *AUX_MU_IER = 0; + *AUX_MU_LCR = 3; + *AUX_MU_MCR = 0; + *AUX_MU_BAUD = 270; + *AUX_MU_IIR = 6; + *AUX_MU_CNTL = 3; +} + +char uart_recv() { + while (!(*AUX_MU_LSR & 0x01)) asm volatile("nop"); + return (char)(*AUX_MU_IO); +} + +void uart_putc(char c) { + if (c == '\n') uart_putc('\r'); + while (!(*AUX_MU_LSR & 0x20)) asm volatile("nop"); + *AUX_MU_IO = c; +} + +void uart_puts(const char *s) { + while (*s) uart_putc(*s++); +} + +int atoi(const char *s) { + int result = 0; + int sign = 1; + int i = 0; + + while (s[i] == ' ') i++; + + if (s[i] == '-') { + sign = -1; + i++; + } else if (s[i] == '+') + i++; + + while (s[i] >= '0' && s[i] <= '9') { + result = result * 10 + (s[i] - '0'); + i++; + } + + return sign * result; +} \ No newline at end of file diff --git a/lab4/bootloader/boot.h b/lab4/bootloader/boot.h new file mode 100644 index 000000000..84cece020 --- /dev/null +++ b/lab4/bootloader/boot.h @@ -0,0 +1,49 @@ +#ifndef BOOT_H +#define BOOT_H + +/* ==================== GPIO ==================== */ +#define MMIO_BASE 0x3F000000 + +#define GPFSEL0 ((volatile unsigned int *)(MMIO_BASE + 0x00200000)) +#define GPFSEL1 ((volatile unsigned int *)(MMIO_BASE + 0x00200004)) +#define GPFSEL2 ((volatile unsigned int *)(MMIO_BASE + 0x00200008)) +#define GPFSEL3 ((volatile unsigned int *)(MMIO_BASE + 0x0020000C)) +#define GPFSEL4 ((volatile unsigned int *)(MMIO_BASE + 0x00200010)) +#define GPFSEL5 ((volatile unsigned int *)(MMIO_BASE + 0x00200014)) +#define GPSET0 ((volatile unsigned int *)(MMIO_BASE + 0x0020001C)) +#define GPSET1 ((volatile unsigned int *)(MMIO_BASE + 0x00200020)) +#define GPCLR0 ((volatile unsigned int *)(MMIO_BASE + 0x00200028)) +#define GPCLR1 ((volatile unsigned int *)(MMIO_BASE + 0x0020002C)) +#define GPLEV0 ((volatile unsigned int *)(MMIO_BASE + 0x00200034)) +#define GPLEV1 ((volatile unsigned int *)(MMIO_BASE + 0x00200038)) +#define GPEDS0 ((volatile unsigned int *)(MMIO_BASE + 0x00200040)) +#define GPEDS1 ((volatile unsigned int *)(MMIO_BASE + 0x00200044)) +#define GPHEN0 ((volatile unsigned int *)(MMIO_BASE + 0x00200064)) +#define GPHEN1 ((volatile unsigned int *)(MMIO_BASE + 0x00200068)) +#define GPPUD ((volatile unsigned int *)(MMIO_BASE + 0x00200094)) +#define GPPUDCLK0 ((volatile unsigned int *)(MMIO_BASE + 0x00200098)) +#define GPPUDCLK1 ((volatile unsigned int *)(MMIO_BASE + 0x0020009C)) + +/* ==================== UART ==================== */ +#define AUX_ENABLE ((volatile unsigned int *)(MMIO_BASE + 0x00215004)) +#define AUX_MU_IO ((volatile unsigned int *)(MMIO_BASE + 0x00215040)) +#define AUX_MU_IER ((volatile unsigned int *)(MMIO_BASE + 0x00215044)) +#define AUX_MU_IIR ((volatile unsigned int *)(MMIO_BASE + 0x00215048)) +#define AUX_MU_LCR ((volatile unsigned int *)(MMIO_BASE + 0x0021504C)) +#define AUX_MU_MCR ((volatile unsigned int *)(MMIO_BASE + 0x00215050)) +#define AUX_MU_LSR ((volatile unsigned int *)(MMIO_BASE + 0x00215054)) +#define AUX_MU_MSR ((volatile unsigned int *)(MMIO_BASE + 0x00215058)) +#define AUX_MU_SCRATCH ((volatile unsigned int *)(MMIO_BASE + 0x0021505C)) +#define AUX_MU_CNTL ((volatile unsigned int *)(MMIO_BASE + 0x00215060)) +#define AUX_MU_STAT ((volatile unsigned int *)(MMIO_BASE + 0x00215064)) +#define AUX_MU_BAUD ((volatile unsigned int *)(MMIO_BASE + 0x00215068)) + +void init_uart(); +char uart_recv(); +void uart_putc(char c); +void uart_puts(const char *s); + +/* ==================== UTILS ==================== */ +int atoi(const char *s); + +#endif // BOOT_H \ No newline at end of file diff --git a/lab4/bootloader/linker.ld b/lab4/bootloader/linker.ld new file mode 100644 index 000000000..de083fba3 --- /dev/null +++ b/lab4/bootloader/linker.ld @@ -0,0 +1,16 @@ +SECTIONS +{ + . = 0x60000; + __loader_start = .; + .text : { *(.text.boot) *(.text) } + .rodata : { *(.rodata) } + .data : { *(.data) } + .bss : { + __bss_start = .; + *(.bss) + __bss_end = .; + } + __loader_end = .; +} +__bss_size = (__bss_end - __bss_start) >> 3; +__loader_size = (__loader_end - __loader_start) >> 3; \ No newline at end of file diff --git a/lab4/bootloader/main.c b/lab4/bootloader/main.c new file mode 100644 index 000000000..8cc94937b --- /dev/null +++ b/lab4/bootloader/main.c @@ -0,0 +1,42 @@ +#include "boot.h" + +int main() { + init_uart(); + uart_puts("\033[2J\033[H"); + uart_puts( + "UART Bootloader\n" + "Waiting for kernel...\n"); + + // Get kernel image size + char buf[16] = {0}; + for (int i = 0; i < 16; i++) { + buf[i] = uart_recv(); + if (buf[i] == '\n') { + buf[i] = '\0'; + break; + } + } + + // Load kernel image + uart_puts("Kernel size: "); + uart_puts(buf); + uart_puts(" bytes.\n"); + uart_puts("Loading the kernel image...\n"); + + unsigned int size = atoi(buf); + char *kernel = (char *)0x80000; + while (size--) *kernel++ = uart_recv(); + + // Restore registers x0 x1 x2 x3 + // Jump to the new kernel + asm volatile( + "" + "mov x0, x10;" + "mov x1, x11;" + "mov x2, x12;" + "mov x3, x13;" + "mov x30, 0x80000;" + "ret;"); + + return 0; +} \ No newline at end of file diff --git a/lab4/bootloader/start.S b/lab4/bootloader/start.S new file mode 100644 index 000000000..ec8488dde --- /dev/null +++ b/lab4/bootloader/start.S @@ -0,0 +1,40 @@ +.section ".text.boot" + +.global _start + +_start: + /* save registers x0 x1 x2 x3 */ + mov x10, x0 /* dtb_base address */ + mov x11, x1 + mov x12, x2 + mov x12, x3 + + /* relocate bootloader */ + ldr x1, =0x80000 + ldr x2, =__loader_start // 0x60000 + ldr w3, =__loader_size + +relocate: + ldr x4, [x1], #8 + str x4, [x2], #8 + sub w3, w3, #1 + cbnz w3, relocate + + /* set stack pointer */ + ldr x1, =_start + mov sp, x1 + + /* clear bss section */ + ldr x1, =__bss_start + ldr w2, =__bss_size + +bss_reset: + cbz w2, run_main + str xzr, [x1], #8 + sub w2, w2, #1 + cbnz w2, bss_reset + +run_main: + /* branch to main function */ + bl main-0x20000 + b run_main diff --git a/lab4/include/command.h b/lab4/include/command.h new file mode 100644 index 000000000..d81861093 --- /dev/null +++ b/lab4/include/command.h @@ -0,0 +1,31 @@ +#pragma once + +#define MAX_BUF_SIZE 1024 +#define END_OF_COMMAND_LIST "NULL" + +struct command { + const char *name; + const char *help; + void (*func)(void); +}; + +extern struct command commands[]; +extern unsigned int BOARD_REVISION; +extern unsigned int BASE_MEMORY; +extern unsigned int NUM_PAGES; + +// Commands +void cmd_help(); +void cmd_hello(); +void cmd_reboot(); +void cmd_cancel(); +void cmd_info(); +void cmd_cat(); +void cmd_run(); +void cmd_clear(); +void cmd_timer(); +void cmd_mem(); +void cmd_bd(); +void cmd_fm(); +void cmd_ca(); +void cmd_lab(); \ No newline at end of file diff --git a/lab4/include/devtree.h b/lab4/include/devtree.h new file mode 100644 index 000000000..0e9bb3da4 --- /dev/null +++ b/lab4/include/devtree.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +struct fdt_header { + uint32_t magic; + uint32_t totalsize; + uint32_t off_dt_struct; + uint32_t off_dt_strings; + uint32_t off_mem_rsvmap; + uint32_t version; + uint32_t last_comp_version; + uint32_t boot_cpuid_phys; + uint32_t size_dt_strings; + uint32_t size_dt_struct; +}; + +uint32_t be2le(const void *s); +void fdt_traverse(void (*callback)(void *, char *)); diff --git a/lab4/include/hardware.h b/lab4/include/hardware.h new file mode 100644 index 000000000..41c5c6053 --- /dev/null +++ b/lab4/include/hardware.h @@ -0,0 +1,219 @@ +#pragma once + +#define MMIO_BASE 0x3F000000 + +// ARM Interrupt Registers + +#define IRQ_BASIC_PENDING (volatile unsigned int *)(MMIO_BASE + 0x0000B200) +#define IRQ_PENDING_1 (volatile unsigned int *)(MMIO_BASE + 0x0000B204) +#define IRQ_PENDING_2 (volatile unsigned int *)(MMIO_BASE + 0x0000B208) +#define FIQ_CONTROL (volatile unsigned int *)(MMIO_BASE + 0x0000B20C) +#define ENABLE_IRQS_1 (volatile unsigned int *)(MMIO_BASE + 0x0000B210) +#define ENABLE_IRQS_2 (volatile unsigned int *)(MMIO_BASE + 0x0000B214) +#define ENABLE_BASIC_IRQS (volatile unsigned int *)(MMIO_BASE + 0x0000B218) +#define DISABLE_IRQS_1 (volatile unsigned int *)(MMIO_BASE + 0x0000B21C) +#define DISABLE_IRQS_2 (volatile unsigned int *)(MMIO_BASE + 0x0000B220) +#define DISABLE_BASIC_IRQS (volatile unsigned int *)(MMIO_BASE + 0x0000B224) +#define CORE0_INTERRUPT_SOURCE (volatile unsigned int *)(0x40000060) + +// Timers interrupt control registers + +#define CORE0_TIMER_IRQCNTL 0x40000040 +#define CORE1_TIMER_IRQCNTL 0x40000044 +#define CORE2_TIMER_IRQCNTL 0x40000048 +#define CORE3_TIMER_IRQCNTL 0x4000004C + +// Where to route timer interrupt to, IRQ/FIQ +// Setting both the IRQ and FIQ bit gives an FIQ +#define TIMER0_IRQ 0x01 +#define TIMER1_IRQ 0x02 +#define TIMER2_IRQ 0x04 +#define TIMER3_IRQ 0x08 +#define TIMER0_FIQ 0x10 +#define TIMER1_FIQ 0x20 +#define TIMER2_FIQ 0x40 +#define TIMER3_FIQ 0x80 + +// PASSWORD + +#define PM_PASSWORD 0x5A000000 +#define PM_RSTC (volatile unsigned int *)0x3F10001C +#define PM_WDOG (volatile unsigned int *)0x3F100024 + +// DEVICETREE + +#define FDT_BEGIN_NODE 0x00000001 +#define FDT_END_NODE 0x00000002 +#define FDT_PROP 0x00000003 +#define FDT_NOP 0x00000004 +#define FDT_END 0x00000009 + +// GPIO + +#define GPFSEL0 ((volatile unsigned int *)(MMIO_BASE + 0x00200000)) +#define GPFSEL1 ((volatile unsigned int *)(MMIO_BASE + 0x00200004)) +#define GPFSEL2 ((volatile unsigned int *)(MMIO_BASE + 0x00200008)) +#define GPFSEL3 ((volatile unsigned int *)(MMIO_BASE + 0x0020000C)) +#define GPFSEL4 ((volatile unsigned int *)(MMIO_BASE + 0x00200010)) +#define GPFSEL5 ((volatile unsigned int *)(MMIO_BASE + 0x00200014)) +#define GPSET0 ((volatile unsigned int *)(MMIO_BASE + 0x0020001C)) +#define GPSET1 ((volatile unsigned int *)(MMIO_BASE + 0x00200020)) +#define GPCLR0 ((volatile unsigned int *)(MMIO_BASE + 0x00200028)) +#define GPCLR1 ((volatile unsigned int *)(MMIO_BASE + 0x0020002C)) +#define GPLEV0 ((volatile unsigned int *)(MMIO_BASE + 0x00200034)) +#define GPLEV1 ((volatile unsigned int *)(MMIO_BASE + 0x00200038)) +#define GPEDS0 ((volatile unsigned int *)(MMIO_BASE + 0x00200040)) +#define GPEDS1 ((volatile unsigned int *)(MMIO_BASE + 0x00200044)) +// Skip GPRENn, GPFENn +#define GPHEN0 ((volatile unsigned int *)(MMIO_BASE + 0x00200064)) +#define GPHEN1 ((volatile unsigned int *)(MMIO_BASE + 0x00200068)) +// Skip GPLENn, GPARENn +#define GPPUD ((volatile unsigned int *)(MMIO_BASE + 0x00200094)) +#define GPPUDCLK0 ((volatile unsigned int *)(MMIO_BASE + 0x00200098)) +#define GPPUDCLK1 ((volatile unsigned int *)(MMIO_BASE + 0x0020009C)) + +// UART + +#define AUX_ENABLE ((volatile unsigned int *)(MMIO_BASE + 0x00215004)) +#define AUX_MU_IO ((volatile unsigned int *)(MMIO_BASE + 0x00215040)) +#define AUX_MU_IER ((volatile unsigned int *)(MMIO_BASE + 0x00215044)) +#define AUX_MU_IIR ((volatile unsigned int *)(MMIO_BASE + 0x00215048)) +#define AUX_MU_LCR ((volatile unsigned int *)(MMIO_BASE + 0x0021504C)) +#define AUX_MU_MCR ((volatile unsigned int *)(MMIO_BASE + 0x00215050)) +#define AUX_MU_LSR ((volatile unsigned int *)(MMIO_BASE + 0x00215054)) +#define AUX_MU_MSR ((volatile unsigned int *)(MMIO_BASE + 0x00215058)) +#define AUX_MU_SCRATCH ((volatile unsigned int *)(MMIO_BASE + 0x0021505C)) +#define AUX_MU_CNTL ((volatile unsigned int *)(MMIO_BASE + 0x00215060)) +#define AUX_MU_STAT ((volatile unsigned int *)(MMIO_BASE + 0x00215064)) +#define AUX_MU_BAUD ((volatile unsigned int *)(MMIO_BASE + 0x00215068)) + +// MAILBOX + +#define MAILBOX_EMPTY 0x40000000 +#define MAILBOX_FULL 0x80000000 +#define MAILBOX_REQUEST 0x00000000 +#define MAILBOX_RESPONSE 0x80000000 +#define REQUEST_CODE 0x00000000 +#define REQUEST_SUCCEED 0x80000000 +#define REQUEST_FAILED 0x80000001 +#define TAG_REQUEST_CODE 0x00000000 +#define END_TAG 0x00000000 + +// Channels +#define MAILBOX_CH_POWER 0 +#define MAILBOX_CH_FB 1 +#define MAILBOX_CH_VUART 2 +#define MAILBOX_CH_VCHIQ 3 +#define MAILBOX_CH_LEDS 4 +#define MAILBOX_CH_BTNS 5 +#define MAILBOX_CH_TOUCH 6 +#define MAILBOX_CH_COUNT 7 +#define MAILBOX_CH_PROP 8 + +#define MAILBOX_BASE (MMIO_BASE + 0x0000B880) + +// mailbox register +#define MAILBOX_REG_READ ((volatile unsigned int *)(MAILBOX_BASE + 0x00000000)) +#define MAILBOX_REG_STATUS \ + ((volatile unsigned int *)(MAILBOX_BASE + 0x00000018)) +#define MAILBOX_REG_WRITE ((volatile unsigned int *)(MAILBOX_BASE + 0x00000020)) + +// mailbox channel +// https://github.com/raspberrypi/firmware/wiki/Mailboxes#channels +#define MAILBOX_CH_POWER 0 +#define MAILBOX_CH_FB 1 +#define MAILBOX_CH_VUART 2 +#define MAILBOX_CH_VCHIQ 3 +#define MAILBOX_CH_LEDS 4 +#define MAILBOX_CH_BTNS 5 +#define MAILBOX_CH_TOUCH 6 +#define MAILBOX_CH_COUNT 7 +#define MAILBOX_CH_PROP 8 + +// mailbox status +#define MAILBOX_EMPTY 0x40000000 +#define MAILBOX_FULL 0x80000000 + +// tags +#define TAGS_REQ_CODE 0x00000000 +#define TAGS_REQ_SUCCEED 0x80000000 +#define TAGS_REQ_FAILED 0x80000001 +#define TAGS_END 0x00000000 + +// hardware tags operator +#define TAGS_HARDWARE_BOARD_MODEL 0x00010001 +#define TAGS_HARDWARE_BOARD_REVISION 0x00010002 +#define TAGS_HARDWARE_MAC_ADDR 0x00010003 +#define TAGS_HARDWARE_BOARD_SERIAL 0x00010004 +#define TAGS_HARDWARE_ARM_MEM 0x00010005 +#define TAGS_HARDWARE_VC_MEM 0x00010006 +#define TAGS_HARDWARE_CLOCKS 0x00010007 + +// CLOCK ID + +#define CLOCK_ID_RESERVED 0x000000000 +#define CLOCK_ID_EMMC 0x000000001 +#define CLOCK_ID_UART 0x000000002 +#define CLOCK_ID_ARM 0x000000003 +#define CLOCK_ID_CORE 0x000000004 +#define CLOCK_ID_V3D 0x000000005 +#define CLOCK_ID_H264 0x000000006 +#define CLOCK_ID_ISP 0x000000007 +#define CLOCK_ID_SDRAM 0x000000008 +#define CLOCK_ID_PIXEL 0x000000009 +#define CLOCK_ID_PWM 0x00000000a +#define CLOCK_ID_HEVC 0x00000000b +#define CLOCK_ID_EMMC2 0x00000000c +#define CLOCK_ID_M2MC 0x00000000d +#define CLOCK_ID_PIXEL_BVB 0x00000000e + +// clock tags operator +#define TAGS_GET_CLOCK 0x00030002 +#define TAGS_SET_CLOCK 0x00038002 + +// framebuffer tages operator +#define FB_ALLOC_BUFFER 0x00040001 +#define FB_FREE_BUFFER 0x00048001 + +#define FB_BLANK_SCREEN 0x00040002 + +#define FB_PHY_WID_HEIGHT_GET 0x00040003 +#define FB_PHY_WID_HEIGHT_TEST 0x00044003 +#define FB_PHY_WID_HEIGHT_SET 0x00048003 + +#define FB_VIR_WID_HEIGHT_GET 0x00040004 +#define FB_VIR_WID_HEIGHT_TEST 0x00044004 +#define FB_VIR_WID_HEIGHT_SET 0x00048004 + +#define FB_DEPTH_GET 0x00040005 +#define FB_DEPTH_TEST 0x00044005 +#define FB_DEPTH_SET 0x00048005 + +#define FB_DEPTH_GET 0x00040005 +#define FB_DEPTH_TEST 0x00044005 +#define FB_DEPTH_SET 0x00048005 + +#define FB_PIXEL_ORDER_GET 0x00040006 +#define FB_PIXEL_ORDER_TEST 0x00044006 +#define FB_PIXEL_ORDER_SET 0x00048006 + +#define FB_ALPHA_MODE_GET 0x00040007 +#define FB_ALPHA_MODE_TEST 0x00044007 +#define FB_ALPHA_MODE_SET 0x00048007 + +#define FB_PITCH_GET 0x00040008 + +#define FB_VIR_OFFSET_GET 0x00040009 +#define FB_VIR_OFFSET_TEST 0x00044009 +#define FB_VIR_OFFSET_SET 0x00048009 + +#define FB_OVERSCAN_GET 0x0004000A +#define FB_OVERSCAN_TEST 0x0004400A +#define FB_OVERSCAN_SET 0x0004800A + +#define FB_PALETTE_GET 0x0004000B +#define FB_PALETTE_TEST 0x0004400B +#define FB_PALETTE_SET 0x0004800B + +#define FB_CURSOR_INFO_SET 0x00008010 +#define FB_CURSOR_STATE_SET 0x00008011 diff --git a/lab4/include/initramfs.h b/lab4/include/initramfs.h new file mode 100644 index 000000000..7704bc1b0 --- /dev/null +++ b/lab4/include/initramfs.h @@ -0,0 +1,32 @@ +#pragma once + +// 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; + +typedef struct { + int namesize; + int filesize; + int headsize; + int datasize; + char *pathname; +} ramfsRec; + +void initramfs_ls(); +void initramfs_cat(const char *target); +void initramfs_callback(void *addr, char *property); +void initramfs_run(const char *target); diff --git a/lab4/include/irq.h b/lab4/include/irq.h new file mode 100644 index 000000000..a7e823584 --- /dev/null +++ b/lab4/include/irq.h @@ -0,0 +1,10 @@ +#pragma once + +#include "hardware.h" + +#define ORDER_FIRST 0 +#define ORDER_LAST 255 + +void enable_interrupt(); +void disable_interrupt(); +void irq_entry(); \ No newline at end of file diff --git a/lab4/include/mbox.h b/lab4/include/mbox.h new file mode 100644 index 000000000..4ea42ba26 --- /dev/null +++ b/lab4/include/mbox.h @@ -0,0 +1,9 @@ +#pragma once + +#include "hardware.h" + +extern volatile unsigned int mbox[36]; + +int mailbox_call(unsigned char c); +int get_board_revision(); +int get_arm_memory_status(); diff --git a/lab4/include/mem.h b/lab4/include/mem.h new file mode 100644 index 000000000..75123f534 --- /dev/null +++ b/lab4/include/mem.h @@ -0,0 +1,44 @@ +#pragma once + +#include + +#define BUDDY_MAX_ORDER 16 +#define CACHE_MAX_ORDER 6 +#define PAGE_SIZE 0x1000 +#define MIN_CACHE_SIZE (PAGE_SIZE / (2 << CACHE_MAX_ORDER)) +#define DEFAULT_STACK_SIZE 8192 + +struct page { + unsigned int order; + unsigned int used; + unsigned int cache_order; + struct page *prev; + struct page *next; +}; + +struct object { + unsigned int order; + struct object *next; +}; + +void malloc_init(); + +struct page *allocatePagesByOrder(unsigned int order, int silent); +void freePages(struct page *page, unsigned int order, int silent); +void mergePages(struct page *page, unsigned int order, int echo); + +void *allocateCacheMemory(unsigned int index, int silent); +void freeCacheEntry(void *ptr, unsigned int index, int silent); + +void *kmalloc(unsigned int size, int silent); +void kfree(void *ptr, int silent); + +void init_mem(); +void reserveMemory(uint64_t start, uint64_t end); + +void printFreeListByOrder(unsigned int order); + +void pushPageToFreeList(struct page **list, struct page *page, + unsigned int order); +void removePageFromFreeList(struct page **list, struct page *page); +struct page *lookupBuddy(struct page *page, unsigned int order); \ No newline at end of file diff --git a/lab4/include/shell.h b/lab4/include/shell.h new file mode 100644 index 000000000..fccef918e --- /dev/null +++ b/lab4/include/shell.h @@ -0,0 +1,7 @@ +#pragma once + +#define SHELL_BUF_SIZE 1024 + +void run_shell(); +void read_user_input(char *buf); +int exec_command(const char *command); diff --git a/lab4/include/string.h b/lab4/include/string.h new file mode 100644 index 000000000..489673d47 --- /dev/null +++ b/lab4/include/string.h @@ -0,0 +1,7 @@ +#pragma once + +int strcmp(const char *str1, const char *str2); +int memcmp(const void *str1, const void *str2, int n); +char *strncpy(char *dest, const char *src, int n); +int strlen(const char *str); +char *strcat(char *dest, const char *src); \ No newline at end of file diff --git a/lab4/include/timer.h b/lab4/include/timer.h new file mode 100644 index 000000000..a5171a0df --- /dev/null +++ b/lab4/include/timer.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +typedef struct __timer_t { + void (*func)(void *); + void *arg; + int time; + struct __timer_t *next; +} timer_entry; + +void enable_timer_interrupt(); +void disable_timer_interrupt(); +void timer_irq_handler(); +uint64_t timer_get_uptime(); + +void timer_add(void (*callback)(void *), void *arg, int duration); +void set_timer(const char *message, int duration); diff --git a/lab4/include/uart.h b/lab4/include/uart.h new file mode 100644 index 000000000..f23d6c9a0 --- /dev/null +++ b/lab4/include/uart.h @@ -0,0 +1,31 @@ +#pragma once + +#include "hardware.h" + +#define UART_BUF_SIZE 1024 + +#define BACKSPACE '\b' +#define DELETE 127 +#define ESC 27 +#define NEWLINE '\n' +#define TAB '\t' +#define CARRIAGE_RETURN '\r' + +void init_uart(); +char uart_getc(); +void uart_putc(char c); +void uart_hex(unsigned int h); +void uart_simple_hex(unsigned int h); +void uart_dec(unsigned int h); +void uart_puts(const char *s); + +void enable_uart_tx_interrupt(); +void disable_uart_tx_interrupt(); +void enable_uart_rx_interrupt(); +void disable_uart_rx_interrupt(); + +void uart_tx_irq_handler(); +void uart_rx_irq_handler(); + +void uart_async_read(char *buf, int len); +void uart_async_write(const char *s); \ No newline at end of file diff --git a/lab4/include/utils.h b/lab4/include/utils.h new file mode 100644 index 000000000..f3d3597d8 --- /dev/null +++ b/lab4/include/utils.h @@ -0,0 +1,20 @@ +#pragma once + +/** + * @brief Align `n` to be a multiple of 4. + * + * @param n A number + * @return Algined number + */ +int align4(int n); + +int atoi(const char *s); + +/** + * @brief Convert hexadecimal string to int. + * + * @param s: hexadecimal string + * @param n: string length + * @return Converted int number + */ +int hextoi(char *s, int n); \ No newline at end of file diff --git a/lab4/initramfs.cpio b/lab4/initramfs.cpio new file mode 100644 index 0000000000000000000000000000000000000000..4710cde216dcc1a54264c473b0848d67be1b5c08 GIT binary patch literal 4096 zcmd^?%Z}4X6o!q&%4_yhjpa=|q|1q8x~BzlL&pM%6=EMbw$oU0;wY}pFgIylVV;Ax zLA(f00OwLp;#)$)a!Rd~Q`gyy;74fb+8y{Pq4j(mS| z<4?vejaMJ89P7(bHPq;L<3j_Y?c=?`br=2dT;KP?a6D!ma>Tk$$MdWBNu(mzSY)e2 z48`9fUT1}P0)MZ>D*Zhc&&6EXqTt)%K!Ifc=(51e8K7$^%t_sMdir36?`5C<$@| zg6@m}9j7%Q0T}24b?gE;0TLX?vSF^Kqp1NF!A@wAR-ybxeO`8eY0X_vy(7M2h!6BfJfDthosnAEknK^K893uq0jN%bOp96I0 zEoJB&Cy?RnC1<=tR0PMVNoZyfkO<7c-Y0;@?KCueY&i)}7Ho%x&$zY$%?S9xfTO4j zfN#yytPt!Zq)^?Fh=f-60C8<$Bug$OMPT?CM!m!lojZ~$9}gl+<4jmtp^ zJxJgpH+Xk^rv_Gm@o7UvfP1BFWRC{)0>Uz3r{G=$xGk_DqXIxy^zv%v(Qy{6oC&C7 zpG1cXou|n`h(KOe2<^)EYQVQXHqwAPYVU+YN?#V$1Hx(i7@_(XL1)q{!M6J~!1G8q z72Zs}07&N;0U6wSO*ly)1RThrbIt4H8tBb(OaN+V){s8chDYn%$u#t?J+2|8%k}O( z>fADp@*FDHe?J)yjsG4P#^+xi8DBnr{rV0DAMI;y*OOEmU%1EWS*-CVRGUL}&b9kJ zv|!Ec8RzGMM47`fja62x#N(k#Kd8s%@HQ{zQ7&esiZ;sR?l)6FTNYdKLH;VF>GD97 zBs747E-fjyF-+s)c)6yz34NRAO1SfC-yE*%b-W**uHz?eI1OJ?9q*2HfAY6Y?YG7V z^A7V!sdSYl;wIf@ajG_HDqf{gl5RwprFl}CW~nxJu@Z|!&EE6OUy#OKwo>EKOo?T5 zo5fd$BdSnQUS@x$wb5d+m;r)mJu}(rwrou;)8%YaZkN%zHnmMRC7e}lERRYxi;LxY z)-^jX3YcBrWyx&){*QVfl&j^nL>$b45m)9vSEi}&Q#yc?ANAj$CztOQ==)mAcW QznQ*0J3E7C> 3; \ No newline at end of file diff --git a/lab4/sender.sh b/lab4/sender.sh new file mode 100755 index 000000000..501756d76 --- /dev/null +++ b/lab4/sender.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# This script sends the kernel image to Rpi3 through UART + +DEST_PATH="/dev/ttyUSB0" +KERNEL_PATH="./kernel8.img" + +# Check the root permission +if [ "$(id -u)" != "0" ]; then + echo "This script must be run as root" + exit 1 +fi + +if [ $1 ] +then + DEST_PATH="$1" +fi + +# Get the size of the kernel image file and send it to Rpi3 +# wc -c: count bytes of a file +# sleep: wait n seconds +wc -c < $KERNEL_PATH > $DEST_PATH | sleep 1 + +# Send the kernel image +# pv: redirect file input to specified tty +# add --rate-limit option to limit the speed +pv $KERNEL_PATH > $DEST_PATH diff --git a/lab4/src/command.c b/lab4/src/command.c new file mode 100644 index 000000000..fcbd0747c --- /dev/null +++ b/lab4/src/command.c @@ -0,0 +1,228 @@ +#include "command.h" + +#include "initramfs.h" +#include "mem.h" +#include "shell.h" +#include "string.h" +#include "timer.h" +#include "uart.h" +#include "utils.h" + +#define DEMO_ALLOCATED_LIST_MAX 64 + +static void *allocatedList[DEMO_ALLOCATED_LIST_MAX]; +static int allocatedListSize = 0; + +struct command commands[] = { + {.name = "help", .help = "Display this help menu", .func = cmd_help}, + {.name = "hello", .help = "Print 'Hello there!'", .func = cmd_hello}, + {.name = "clear", .help = "Clear the screen", .func = cmd_clear}, + {.name = "reboot", .help = "Reboot the device", .func = cmd_reboot}, + {.name = "cancel", .help = "Cancel a scheduled reboot", .func = cmd_cancel}, + {.name = "info", .help = "Show hardware information", .func = cmd_info}, + {.name = "ls", .help = "List files in ramdisk", .func = initramfs_ls}, + {.name = "cat", .help = "Display content of ramdisk file", .func = cmd_cat}, + {.name = "run", .help = "Run a specified program", .func = cmd_run}, + {.name = "timer", .help = "Set timer with duration", .func = cmd_timer}, + {.name = "mem", .help = "Display free memory blocks", .func = cmd_mem}, + {.name = "bd", .help = "Allocate a memory block", .func = cmd_bd}, + {.name = "ca", .help = "Use the dynamic memory allocator", .func = cmd_ca}, + {.name = "fm", .help = "Free all memory in the demo list", .func = cmd_fm}, + {.name = "lab", .help = "Showcase lab requirements", .func = cmd_lab}, + {.name = END_OF_COMMAND_LIST}}; + +void cmd_mem() { + for (int i = BUDDY_MAX_ORDER; i >= 0; i--) { + printFreeListByOrder(i); + } +} + +static void allocatedListFullMessage() { + uart_puts( + "Max memory block allocation for demo is reached.\n" + "Consider using 'fm' to clear all allocated memory blocks.\n"); +} + +void cmd_bd() { + if (allocatedListSize >= DEMO_ALLOCATED_LIST_MAX) { + allocatedListFullMessage(); + return; + } + uart_puts("Buddy System: request order (0-"); + uart_dec(BUDDY_MAX_ORDER); + uart_puts("): "); + char *buf = (char *)kmalloc(SHELL_BUF_SIZE, 1); + read_user_input(buf); + int order = atoi(buf); + if (buf[0] < '0' || buf[0] > '9' || order < 0 || order > BUDDY_MAX_ORDER) { + uart_puts("Invalid order."); + uart_putc(NEWLINE); + } else + allocatedList[allocatedListSize++] = kmalloc(PAGE_SIZE << order, 0); + kfree(buf, 1); +} + +void cmd_fm() { + if (allocatedListSize == 0) { + uart_puts("No memory block allocated."); + uart_putc(NEWLINE); + } else { + uart_puts("Freeing all allocated memory blocks..."); + uart_putc(NEWLINE); + for (int i = 0; i < allocatedListSize; i++) { + kfree(allocatedList[i], 0); + } + allocatedListSize = 0; + } +} + +void cmd_ca() { + if (allocatedListSize >= DEMO_ALLOCATED_LIST_MAX) { + allocatedListFullMessage(); + return; + } + uart_puts("Dynamic Memory Allocator: request byte(s) (1-"); + uart_dec(PAGE_SIZE / 2); + uart_puts("): "); + char *buf = (char *)kmalloc(SHELL_BUF_SIZE, 1); + read_user_input(buf); + int size = atoi(buf); + if (buf[0] == '\0' || size <= 0) { + uart_puts("Invalid size for dynamic allocator."); + uart_putc(NEWLINE); + } else { + if (size > PAGE_SIZE / 2) { + uart_puts( + "[INFO] Size too large for dynamic allocator.\n" + "[INFO] Switching to Buddy System.\n"); + } + void *p = kmalloc(size, 0); + allocatedList[allocatedListSize++] = p; + // kfree(p, 0); + } + kfree(buf, 1); +} + +static int *timeup = 0; +static void set_timeup(int *timeup) { *timeup = 1; } + +void cmd_lab() { + uart_puts("(1) Lab 3: UART async write"); + uart_putc(NEWLINE); + uart_puts("(2) Lab 3: UART async read"); + uart_putc(NEWLINE); + uart_puts("Please select: "); + char *buf = (char *)kmalloc(SHELL_BUF_SIZE, 1); + read_user_input(buf); + switch (atoi(buf)) { + case 1: + uart_puts(": "); + strncpy(buf, "[INFO] Async write: ", strlen("[INFO] Async write: ")); + read_user_input(buf + strlen("[INFO] Async write: ")); + // read_user_input(buf); + uart_async_write(buf); + break; + case 2: + uart_puts("(Please type something in 3 sec.)"); + uart_putc(NEWLINE); + // set a timer to 3 sec. + *timeup = 0; + timer_add((void (*)(void *))set_timeup, (void *)timeup, 3); + enable_uart_rx_interrupt(); + while (!*timeup); + // time's up, get the buf content and print + uart_async_read(buf, SHELL_BUF_SIZE); + uart_puts("[INFO] Async read received: "); + uart_puts(buf); + uart_putc(NEWLINE); + break; + default: + uart_puts("Option not found."); + uart_putc(NEWLINE); + } + kfree(buf, 1); +} + +void cmd_timer() { + uart_puts("Duration(sec.): "); + char *buf = (char *)kmalloc(SHELL_BUF_SIZE, 1); + read_user_input(buf); + int sec = atoi(buf); + + char *msg = (char *)kmalloc(SHELL_BUF_SIZE, 0); + uart_puts(": "); + read_user_input(msg); + set_timer(msg, sec); + kfree(buf, 1); +} + +void cmd_run() { + // Get filename from user input + uart_puts(": "); + char *buf = (char *)kmalloc(SHELL_BUF_SIZE, 1); + read_user_input(buf); + initramfs_run(buf); + kfree(buf, 1); +} + +void cmd_help() { + uart_puts("-----------------------------------------"); + uart_putc(NEWLINE); + struct command *cmd = commands; + while (1) { + if (!strcmp(cmd->name, END_OF_COMMAND_LIST)) { + break; + } + uart_puts(cmd->name); + uart_putc(TAB); + uart_puts(": "); + uart_puts(cmd->help); + uart_putc(NEWLINE); + cmd++; + } + uart_puts("-----------------------------------------"); + uart_putc(NEWLINE); +} + +void cmd_hello() { + uart_puts("Hello there!"); + uart_putc(NEWLINE); +} + +void cmd_reboot() { + uart_puts("Start Rebooting..."); + uart_putc(NEWLINE); + // Reboot after 0x20000 ticks + *PM_RSTC = PM_PASSWORD | 0x20; // Full reset + *PM_WDOG = PM_PASSWORD | 0x20000; // Number of watchdog ticks +} + +void cmd_cancel() { + uart_puts("Rebooting Attempt Aborted, if any."); + uart_putc(NEWLINE); + *PM_RSTC = PM_PASSWORD | 0; + *PM_WDOG = PM_PASSWORD | 0; +} + +void cmd_info() { + uart_puts("[INFO] board revision: "); + uart_hex(BOARD_REVISION); + uart_putc(NEWLINE); + uart_puts("[INFO] device base memory address: "); + uart_hex(BASE_MEMORY); + uart_putc(NEWLINE); + uart_puts("[INFO] device memory size: "); + uart_hex(NUM_PAGES * PAGE_SIZE); + uart_putc(NEWLINE); +} + +void cmd_cat() { + // Get filename from user input + uart_puts(": "); + char *buf = (char *)kmalloc(SHELL_BUF_SIZE, 1); + read_user_input(buf); + initramfs_cat(buf); + kfree(buf, 1); +} + +void cmd_clear() { uart_puts("\033[2J\033[H"); } diff --git a/lab4/src/devtree.c b/lab4/src/devtree.c new file mode 100644 index 000000000..0c905e312 --- /dev/null +++ b/lab4/src/devtree.c @@ -0,0 +1,67 @@ +#include "devtree.h" + +#include "string.h" +#include "uart.h" +#include "utils.h" + +// Assign a non-zero value to be stored in the .data section +void *DTB_BASE = (void *)0xF; +void *DTB_END = (void *)0xF; + +/** + * @brief Convert a 4-byte big-endian sequence to little-endian. + * + * @param s: big-endian sequence + * @return little-endian sequence + */ +uint32_t be2le(const void *s) { + const uint8_t *bytes = (const uint8_t *)s; + return (uint32_t)bytes[0] << 24 | (uint32_t)bytes[1] << 16 | + (uint32_t)bytes[2] << 8 | (uint32_t)bytes[3]; +} + +void fdt_traverse(void (*callback)(void *, char *)) { + struct fdt_header *header = (struct fdt_header *)(uintptr_t)DTB_BASE; + + // Check the magic number + if (be2le(&(header->magic)) != 0xD00DFEED) { + uart_puts("[WARN] Dtb header magic does not match!"); + uart_putc(NEWLINE); + } + DTB_END = DTB_BASE + be2le(&header->totalsize); + + uart_puts("[INFO] Dtb loaded at "); + uart_hex((uintptr_t)DTB_BASE); + uart_putc('-'); + uart_hex((uintptr_t)DTB_END); + uart_putc(NEWLINE); + + uintptr_t structure = (uintptr_t)header + be2le(&header->off_dt_struct); + uintptr_t strings = (uintptr_t)header + be2le(&header->off_dt_strings); + uint32_t structure_size = be2le(&header->size_dt_struct); + + // Parse the structure block + uintptr_t ptr = structure; // Point to the beginning of structure block + while (ptr < structure + structure_size) { + uint32_t token = be2le((char *)ptr); + ptr += 4; // Token takes 4 bytes + + switch (token) { + case FDT_BEGIN_NODE: + ptr += align4(strlen((char *)ptr) + 1); + break; + case FDT_PROP: + uint32_t len = be2le((char *)ptr); + ptr += 4; + uint32_t nameoff = be2le((char *)ptr); + ptr += 4; + callback((void *)(uintptr_t)be2le((void *)ptr), + (char *)(strings + nameoff)); + ptr += align4(len); + break; + case FDT_END_NODE: + case FDT_NOP: + case FDT_END: + } + } +} \ No newline at end of file diff --git a/lab4/src/initramfs.c b/lab4/src/initramfs.c new file mode 100644 index 000000000..6ed817dc5 --- /dev/null +++ b/lab4/src/initramfs.c @@ -0,0 +1,124 @@ +#include "initramfs.h" + +#include + +#include "mem.h" +#include "string.h" +#include "uart.h" +#include "utils.h" + +void *initrd_start; +void *initrd_end; + +void initramfs_callback(void *addr, char *property) { + if (!strcmp(property, "linux,initrd-start")) { + uart_puts("[INFO] linux,initrd-start: "); + uart_hex((uintptr_t)addr); + initrd_start = (char *)addr; + uart_putc(NEWLINE); + } else if (!strcmp(property, "linux,initrd-end")) { + uart_puts("[INFO] linux,initrd-end: "); + uart_hex((uintptr_t)addr); + initrd_end = (char *)addr; + uart_putc(NEWLINE); + } +} + +static ramfsRec *ramfsNext(char *fptr) { + ramfsRec *rec = (ramfsRec *)kmalloc(sizeof(ramfsRec), 1); + cpio_t *header = (cpio_t *)fptr; + + // New ASCII Format uses 8-byte hexadecimal string for all numbers + rec->namesize = hextoi(header->c_namesize, 8); + rec->filesize = hextoi(header->c_filesize, 8); + + // Total size of (header + pathname) is a multiple of four bytes + // File data is also padded to a multiple of four bytes + rec->headsize = align4(sizeof(cpio_t) + rec->namesize); + rec->datasize = align4(rec->filesize); + + // Get file pathname + rec->pathname = kmalloc(rec->namesize, 1); + strncpy(rec->pathname, fptr + sizeof(cpio_t), rec->namesize); + + return rec; +} + +void initramfs_ls() { + uart_putc(NEWLINE); + + char *fptr = (char *)initrd_start; + while (memcmp(fptr + sizeof(cpio_t), "TRAILER!!!", 10)) { + ramfsRec *rec = ramfsNext(fptr); + uart_puts(rec->pathname); + uart_putc(TAB); + uart_dec(rec->filesize); + uart_puts(" byte"); + if (rec->filesize > 1) uart_putc('s'); + uart_putc(NEWLINE); + + fptr += rec->headsize + rec->datasize; + kfree(rec->pathname, 1); + kfree(rec, 1); + } +} + +void initramfs_cat(const char *filename) { + char *fptr = (char *)initrd_start; + + while (memcmp(fptr + sizeof(cpio_t), "TRAILER!!!", 10)) { + ramfsRec *rec = ramfsNext(fptr); + if (!strcmp(filename, rec->pathname)) { + // Dump its content + uart_putc(NEWLINE); + for (char *c = fptr + rec->headsize; + c < fptr + rec->headsize + rec->filesize; c++) { + uart_putc(*c); + } + uart_putc(NEWLINE); + uart_putc(NEWLINE); + // kfree(data, 0); + kfree(rec->pathname, 1); + kfree(rec, 1); + return; + } + fptr += rec->headsize + rec->datasize; + kfree(rec->pathname, 1); + kfree(rec, 1); + } + + uart_puts("File not found."); + uart_putc(NEWLINE); +} + +void initramfs_run(const char *filename) { + char *fptr = (char *)initrd_start; + + while (memcmp(fptr + sizeof(cpio_t), "TRAILER!!!", 10)) { + ramfsRec *rec = ramfsNext(fptr); + // Match target filename + if (!strcmp(filename, rec->pathname)) { + // Load the user program + char *program = (char *)0x40000; + for (int i = 0; i < rec->filesize; i++) + *program++ = (fptr + rec->headsize)[i]; + unsigned long sp = (unsigned long)kmalloc(DEFAULT_STACK_SIZE, 0); + asm volatile( + "msr spsr_el1, %0;" + "msr elr_el1, %1;" + "msr sp_el0, %2;" + "eret" // Return to EL0 and execute + : + : "r"(0x3C0), "r"(0x40000), "r"(sp + DEFAULT_STACK_SIZE) + : "memory"); + return; + } + + fptr += rec->headsize + rec->datasize; + kfree(rec->pathname, 1); + kfree(rec, 1); + } + + uart_puts("File not found."); + uart_putc(NEWLINE); +} \ No newline at end of file diff --git a/lab4/src/irq.c b/lab4/src/irq.c new file mode 100644 index 000000000..d57fdb74b --- /dev/null +++ b/lab4/src/irq.c @@ -0,0 +1,98 @@ +#include "irq.h" + +#include "mem.h" +#include "timer.h" +#include "uart.h" + +typedef struct __irq_task_t { + void (*func)(); + int order; + int busy; // 0 (default) or 1 (busy) + struct __irq_task_t *prev; + struct __irq_task_t *next; +} irq_task_t; + +static irq_task_t *head = 0; + +// Disable interrupt before calling irq_add_task() +void irq_add_task(void (*callback)(), int order) { + irq_task_t *task = (irq_task_t *)kmalloc(sizeof(irq_task_t), 1); + task->func = callback; + task->order = order; + task->busy = 0; + task->prev = 0; + task->next = 0; + + // 0 -> task -> head -> ... + if (head == 0 || task->order < head->order) { + task->next = head; + task->prev = 0; + if (head != 0) head->prev = task; + head = task; + return; + } + + irq_task_t *current = head; + while (current->next != 0 && current->next->order <= task->order) + current = current->next; + task->next = current->next; + if (current->next != 0) current->next->prev = task; + current->next = task; + task->prev = current; +} + +// to enable interrupts in EL1 +void enable_interrupt() { + asm volatile( + "msr DAIFClr, 0xF\n" // Clear the D, A, I, F bits in the DAIF register + ); +} + +// to disable interrupts in EL1 +void disable_interrupt() { + asm volatile( + "msr DAIFSet, 0xF\n" // Set the D, A, I, F bits to 1 in the DAIF register + ); +} + +void irq_entry() { + disable_interrupt(); // Enter the critical section + + if (*IRQ_PENDING_1 & (1 << 29)) { // UART interrupt + switch (*AUX_MU_IIR & 0x6) { // 0x6 = 0110 -> Get 0x2 and 0x4 + + case 0x2: // 0x2 Transmit interrupt + disable_uart_tx_interrupt(); + irq_add_task(uart_tx_irq_handler, ORDER_FIRST); + break; + + case 0x4: // 0x4 Receive interrupt + disable_uart_rx_interrupt(); + irq_add_task(uart_rx_irq_handler, ORDER_FIRST); + break; + } + } else if (*CORE0_INTERRUPT_SOURCE & 0x2) { // Core 0 timer interrupt + disable_timer_interrupt(); + irq_add_task(timer_irq_handler, ORDER_LAST); + } + + enable_interrupt(); // Leave the critical section + + // Preemption: run the task with the highest priority + while (head != 0 && !head->busy) { + disable_interrupt(); + irq_task_t *task = head; // Get a task from head + task->busy = 1; // Flag the task as under processing + enable_interrupt(); + + task->func(); // Run the tasks with interrupts enabled + + // Remove the task + disable_interrupt(); + if (task->prev != 0) task->prev->next = task->next; + if (task->next != 0) task->next->prev = task->prev; + if (task == head) head = task->next; + kfree((void *)task, 1); + enable_interrupt(); + } +} \ No newline at end of file diff --git a/lab4/src/main.c b/lab4/src/main.c new file mode 100644 index 000000000..5b6bf0d14 --- /dev/null +++ b/lab4/src/main.c @@ -0,0 +1,21 @@ +#include "devtree.h" +#include "initramfs.h" +#include "irq.h" +#include "mem.h" +#include "shell.h" +#include "timer.h" +#include "uart.h" + +int main() { + /* Initialization */ + fdt_traverse(initramfs_callback); + init_mem(); + + init_uart(); + enable_interrupt(); + enable_timer_interrupt(); + + run_shell(); + + return 0; +} \ No newline at end of file diff --git a/lab4/src/mbox.c b/lab4/src/mbox.c new file mode 100644 index 000000000..7bb73792a --- /dev/null +++ b/lab4/src/mbox.c @@ -0,0 +1,44 @@ +#include "mbox.h" + +volatile unsigned int __attribute__((aligned(16))) mbox[36]; + +int mailbox_call(unsigned char c) { + unsigned int r = ((unsigned int)(((unsigned long)&mbox & ~0xF) | (c & 0xF))); + + // Wait until the mailbox is not full + while (*MAILBOX_REG_STATUS & MAILBOX_FULL) asm volatile("nop"); + + // Write to the register + *MAILBOX_REG_WRITE = r; + + while (1) { + // Wait for the response + while (*MAILBOX_REG_STATUS & MAILBOX_EMPTY) asm volatile("nop"); + + if (r == *MAILBOX_REG_READ) return mbox[1] == MAILBOX_RESPONSE; + } + return 0; +} + +int get_board_revision() { + mbox[0] = 7 * 4; + mbox[1] = REQUEST_CODE; + mbox[2] = TAGS_HARDWARE_BOARD_REVISION; + mbox[3] = 4; + mbox[4] = TAG_REQUEST_CODE; + mbox[5] = 0; + mbox[6] = END_TAG; + return mailbox_call(MAILBOX_CH_PROP); +} + +int get_arm_memory_status() { + mbox[0] = 8 * 4; + mbox[1] = REQUEST_CODE; + mbox[2] = TAGS_HARDWARE_ARM_MEM; + mbox[3] = 8; + mbox[4] = TAG_REQUEST_CODE; + mbox[5] = 0; + mbox[6] = 0; + mbox[7] = END_TAG; + return mailbox_call(MAILBOX_CH_PROP); +} \ No newline at end of file diff --git a/lab4/src/mem.c b/lab4/src/mem.c new file mode 100644 index 000000000..a5b917f1f --- /dev/null +++ b/lab4/src/mem.c @@ -0,0 +1,373 @@ +#include "mem.h" + +#include "devtree.h" +#include "mbox.h" +#include "string.h" +#include "uart.h" +#include "utils.h" + +extern char *__bss_end; +extern void *DTB_BASE; +extern void *DTB_END; +extern void *initrd_start; +extern void *initrd_end; + +unsigned int BOARD_REVISION; +unsigned int BASE_MEMORY; +unsigned int NUM_PAGES; + +static struct page *pageTable; +static struct page *freeList[BUDDY_MAX_ORDER + 1]; +static struct object *objectCache[CACHE_MAX_ORDER + 1]; + +// `__bss_end` is defined in linker script +extern char *__bss_end; +static char *heap_top; + +static void *simple_malloc(int size) { + void *p = (void *)heap_top; + if (size < 0) return 0; + heap_top += size; + return p; +} + +void init_mem() { + // Simple malloc init: Set heap base address + heap_top = (char *)&__bss_end; + + if (BOARD_REVISION == 0) { + // Get board revision + get_board_revision(); + BOARD_REVISION = mbox[5]; + } + // Get ARM memory base address and size + if (NUM_PAGES == 0) { + get_arm_memory_status(); + BASE_MEMORY = mbox[5]; + NUM_PAGES = (mbox[6] - BASE_MEMORY) / PAGE_SIZE; + } + // Initialize the buddy allocator + pageTable = simple_malloc(sizeof(struct page) * NUM_PAGES); + unsigned int current_order = BUDDY_MAX_ORDER; + // for (int i = NUM_PAGES - 1; i >= 0; i--) { + for (int i = 0; i < NUM_PAGES; i++) { + pageTable[i].order = 0; + pageTable[i].used = 0; + pageTable[i].cache_order = -1; + pageTable[i].prev = 0; + pageTable[i].next = 0; + if (i % (1 << current_order) == 0) { + while (current_order > 0 && NUM_PAGES - i < (1 << current_order)) { + current_order--; + } + pushPageToFreeList(&freeList[current_order], &pageTable[i], + current_order); + } + } + unsigned int sa = NUM_PAGES * sizeof(struct page); + + // Reserve memory: + // Spin tables for multicore boot + reserveMemory(0x0, 0x1000); + // User program + reserveMemory(0x40000, 0x40000 + PAGE_SIZE); + // Kernel & stack; Simple allocator + reserveMemory(0x80000 - DEFAULT_STACK_SIZE, (unsigned long)&__bss_end + sa); + // Initramfs + reserveMemory((uint64_t)initrd_start, (uint64_t)initrd_end); + // Devicetree + reserveMemory((uint64_t)DTB_BASE, (uint64_t)DTB_END); +} + +void reserveMemory(uint64_t resv_start, uint64_t resv_end) { + // Round the start and end addresses to the page boundary + resv_start = resv_start & ~(PAGE_SIZE - 1); + resv_end = (resv_end + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); + resv_end--; + + uart_puts("[INFO] Reserving memory: "); + uart_hex(resv_start); + uart_putc('-'); + uart_hex(resv_end); + uart_putc(NEWLINE); + + for (int order = BUDDY_MAX_ORDER; order >= 0; order--) { + struct page *current = freeList[order]; + while (current != 0) { + struct page *next = current->next; + uint64_t page_start = (current - pageTable) * PAGE_SIZE; + uint64_t page_end = page_start + (PAGE_SIZE << order) - 1; + if (page_start >= resv_start && page_end <= resv_end) { + // [page page] + // Remove the page from the free list + current->used = 1; + removePageFromFreeList(&freeList[order], current); + } else if (resv_start > page_end || resv_end < page_start) { + // ---resv> [page or page] or order = order; + page->used = 0; + page->prev = 0; + page->next = 0; + + if (*list_head == 0 || (*list_head) < page) { + if (*list_head != 0) (*list_head)->prev = page; + page->next = *list_head; + *list_head = page; + return; + } + + struct page *current = *list_head; + while (current->next != 0 && page < current->next) { + current = current->next; + } + page->prev = current; + page->next = current->next; + if (current->next != 0) current->next->prev = page; + current->next = page; +} + +struct page *popFreeList(struct page **list_head) { + if (*list_head == 0) return 0; + + struct page *page = *list_head; + *list_head = page->next; + page->used = 1; + return page; +} + +void removePageFromFreeList(struct page **list_head, struct page *page) { + if (page->prev != 0) page->prev->next = page->next; + if (page->next != 0) page->next->prev = page->prev; + if (page == *list_head) *list_head = page->next; +} + +void printFreeListByOrder(unsigned int order) { + struct page *page = freeList[order]; + if (page > 0) uart_puts("[BUDD]"); + while (page != 0) { + uart_putc(TAB); + uart_hex((unsigned int)(page - pageTable) * PAGE_SIZE); + uart_puts("-"); + uart_hex((unsigned int)(page - pageTable + (1 << order)) * PAGE_SIZE - 1); + uart_puts(" ["); + if (order < 10) uart_putc(' '); + uart_dec(order); + uart_puts("] "); + uart_putc(NEWLINE); + page = page->next; + } +} + +struct page *lookupBuddy(struct page *page, unsigned int order) { + unsigned int buddy_pfn = (unsigned int)(page - pageTable) ^ (1 << order); + return &pageTable[buddy_pfn]; +} + +struct page *allocatePagesByOrder(unsigned int order, int silent) { + if (!silent) { + uart_puts("[BUDD] "); + uart_dec(1 << order); + uart_puts("-page memory block requested."); + uart_putc(NEWLINE); + } + + for (int i = order; i <= BUDDY_MAX_ORDER; i++) { + if (freeList[i] == 0) // No free page available + continue; // Try next order + struct page *page = popFreeList(&freeList[i]); + page->order = order; // Update order of the page + + while (i > order) { // requires splitting + i--; + struct page *buddy = lookupBuddy(page, i); + pushPageToFreeList(&freeList[i], buddy, i); + + if (silent) continue; + // Print information + unsigned int pfn = page - pageTable; + unsigned int buddy_pfn = buddy - pageTable; + uart_puts("[BUDD] Split "); + uart_hex(pfn * PAGE_SIZE); + uart_puts("-"); + uart_hex((pfn + (1 << i)) * PAGE_SIZE - 1); + uart_puts("//"); + uart_hex(buddy_pfn * PAGE_SIZE); + uart_puts("-"); + uart_hex((buddy_pfn + (1 << i)) * PAGE_SIZE - 1); + uart_puts(" => ["); + uart_dec(i); + uart_puts("]"); + uart_putc(NEWLINE); + } + if (!silent) { + uart_puts("[BUDD] Memory allocated: "); + uart_hex((unsigned int)(page - pageTable) * PAGE_SIZE); + uart_puts("-"); + uart_hex((unsigned int)(page - pageTable + (1 << order)) * PAGE_SIZE - 1); + uart_putc(NEWLINE); + } + + return page; + } + return 0; +} + +void freePages(struct page *page, unsigned int order, int silence) { + if (!silence) { + uart_puts("[BUDD] Free "); + uart_dec(1 << order); + uart_puts("-page memory block starting from "); + uart_hex((unsigned int)(page - pageTable) * PAGE_SIZE); + uart_putc(NEWLINE); + } + mergePages(page, order, silence); +} + +void mergePages(struct page *page, unsigned int order, int silence) { + struct page *current = page; + while (order < BUDDY_MAX_ORDER) { + struct page *buddy = lookupBuddy(current, order); + if (buddy->order != order || buddy->used == 1) break; + + removePageFromFreeList(&freeList[order], buddy); + + if (current > buddy) { + struct page *tmp = current; + current = buddy; + buddy = tmp; + } + + order++; + if (silence) continue; + unsigned int pfn = current - pageTable; + unsigned int buddy_pfn = buddy - pageTable; + uart_puts("[BUDD] Merge "); + uart_hex(pfn * PAGE_SIZE); + uart_puts("-"); + uart_hex((pfn + (1 << (order - 1))) * PAGE_SIZE - 1); + uart_puts("~~"); + uart_hex(buddy_pfn * PAGE_SIZE); + uart_puts("-"); + uart_hex((buddy_pfn + (1 << (order - 1))) * PAGE_SIZE - 1); + uart_puts(" => ["); + uart_dec(order); + uart_puts("]"); + uart_putc(NEWLINE); + } + pushPageToFreeList(&freeList[order], current, order); +} + +/* Cache Allocator */ + +void pushObjectToList(struct object **list_head, struct object *object, + unsigned int order) { + object->order = order; + object->next = *list_head; + *list_head = object; +} + +struct object *popObjectFromCache(struct object **list_head) { + if (*list_head == 0) return 0; + + struct object *object = *list_head; + *list_head = object->next; + return object; +} + +void *allocateCacheMemory(unsigned int order, int silent) { + if (!silent) { + uart_puts("[CACH] Allocating "); + uart_dec(MIN_CACHE_SIZE << order); + uart_puts(" bytes."); + uart_putc(NEWLINE); + } + + if (objectCache[order] == 0) { + struct page *page = allocatePagesByOrder(0, silent); + page->cache_order = order; + unsigned int page_addr = (page - pageTable) * PAGE_SIZE; + unsigned int cache_size = MIN_CACHE_SIZE << order; + for (int i = 0; i < PAGE_SIZE; i += cache_size) { + struct object *obj = (struct object *)(uintptr_t)(page_addr + i); + pushObjectToList(&objectCache[order], obj, order); + } + } + void *p = popObjectFromCache(&objectCache[order]); + if (!silent) { + uart_puts("[CACH] Allocated memory starting from "); + uart_hex((uintptr_t)p); + uart_putc(NEWLINE); + } + return p; +} + +void freeCacheEntry(void *ptr, unsigned int index, int silence) { + if (!silence) { + uart_puts("[CACH] Free memory cache of "); + uart_dec(MIN_CACHE_SIZE << index); + uart_puts(" bytes starting from "); + uart_hex((uintptr_t)ptr); + uart_putc(NEWLINE); + } + pushObjectToList(&objectCache[index], ptr, index); +} + +/* Dynamic Memory Allocator */ + +void *kmalloc(unsigned int size, int silent) { + if (size == 0) return 0; + + if (!silent) { + uart_puts("[INFO] Memory requested: "); + uart_dec(size); + uart_puts(" byte(s)."); + uart_putc(NEWLINE); + } + + if (size > PAGE_SIZE / 2) { + // Buddy Allocator + int order = 0; + while ((PAGE_SIZE << order) < size) order++; + struct page *page = allocatePagesByOrder(order, silent); + return (void *)((page - pageTable) * PAGE_SIZE); + } else { + // Cache Allocator + int power = 0; + while ((1 << power) < size) power++; + int order = (power > 5) ? power - 5 : 0; + return allocateCacheMemory(order, silent); + } +} + +void kfree(void *ptr, int silence) { + // Check if the pointer is page-aligned + struct page *page = &pageTable[(uintptr_t)ptr / PAGE_SIZE]; + if ((uintptr_t)ptr % PAGE_SIZE == 0) { + // Check if the page is allocated by the buddy allocator + if (page->cache_order == -1) { + // Free the page using the buddy allocator + freePages(page, page->order, silence); + return; + } + } + // Free the object using the cache allocator + struct object *object = ptr; + freeCacheEntry(object, page->cache_order, silence); +} diff --git a/lab4/src/shell.c b/lab4/src/shell.c new file mode 100644 index 000000000..0f1d63888 --- /dev/null +++ b/lab4/src/shell.c @@ -0,0 +1,87 @@ +#include "shell.h" + +#include "command.h" +#include "string.h" +#include "uart.h" + +static void welcome_msg() { + cmd_info(); + uart_puts( + "*******************************\n" + "*** YADOS 0.04 for OSC 2024 ***\n" + "*******************************\n" + "Hopefully this will be Yet Another Dope OS!"); + uart_putc(NEWLINE); + uart_putc(NEWLINE); + cmd_hello(); +} + +void run_shell() { + welcome_msg(); + while (1) { + char buffer[SHELL_BUF_SIZE]; + uart_putc(NEWLINE); + uart_puts("# "); + read_user_input(buffer); + exec_command(buffer); + } +} + +void read_user_input(char *buf) { + int idx = 0; + while (idx < SHELL_BUF_SIZE) { + char c = uart_getc(); + switch (c) { + case NEWLINE: + uart_putc(NEWLINE); + buf[idx] = '\0'; + return; + case DELETE: + if (idx > 0) { + idx--; + uart_putc(BACKSPACE); + uart_putc(' '); + uart_putc(BACKSPACE); + } + break; + case ESC: + uart_putc(NEWLINE); + uart_puts( + "ESC detected. Clearing buffer. Press Spacebar or Enter to exit."); + uart_putc(NEWLINE); + while (1) { + char c = uart_getc(); + if (c == ' ' || c == NEWLINE) break; + } + buf[0] = '\0'; + return; + default: + if (c >= 32 && c <= 126) { + uart_putc(c); + buf[idx++] = c; + } + } + } + uart_putc(NEWLINE); + uart_puts("Buffer overflow. Please re-enter your command."); + uart_putc(NEWLINE); + buf[0] = '\0'; +} + +int exec_command(const char *input) { + if (strlen(input) == 0) return 0; + struct command *cmd = commands; + while (1) { + if (!strcmp(cmd->name, END_OF_COMMAND_LIST)) { + uart_puts("Command not found."); + uart_putc(NEWLINE); + break; + } + if (!strcmp(cmd->name, input)) { + cmd->func(); + break; + } + cmd++; + } + return 0; +} \ No newline at end of file diff --git a/lab4/src/string.c b/lab4/src/string.c new file mode 100644 index 000000000..ffe02eb48 --- /dev/null +++ b/lab4/src/string.c @@ -0,0 +1,40 @@ +#include "string.h" + +int strcmp(const char *str1, const char *str2) { + while (*str1 && *str1 == *str2) str1++, str2++; + return *(unsigned char *)str1 - *(unsigned char *)str2; +} + +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; +} + +char *strncpy(char *dest, const char *src, int n) { + while (n-- && (*dest++ = *src++)); + return dest; +} + +int strlen(const char *str) { + int len = 0; + while (*str++ != '\0') len++; + return len; +} + +char *strcat(char *dest, const char *src) { + char *d = dest; + + // Move the pointer to the end of the dest string + while (*d != '\0') d++; + // Copy the src string to the end of the dest string + while (*src != '\0') *d++ = *src++; + // Add the null terminator + *d = '\0'; + + return dest; +} \ No newline at end of file diff --git a/lab4/src/timer.c b/lab4/src/timer.c new file mode 100644 index 000000000..662559223 --- /dev/null +++ b/lab4/src/timer.c @@ -0,0 +1,99 @@ +#include "timer.h" + +#include "mem.h" +#include "string.h" +#include "uart.h" + +static timer_entry *head = 0; + +void enable_timer_interrupt() { + asm volatile( + // Enable Counter-timer interrupt + "mov x0, 1;" + "msr cntp_ctl_el0, x0;" // Physical Timer Control register + + // Set the Counter-timer frequency to 1 second + "mrs x0, cntfrq_el0;" // Frequency register + "msr cntp_tval_el0, x0;" // Physical Timer TimerValue register + + // Unmask Counter-timer interrupt + "mov x0, 2;" + "ldr x1, =%0;" + "str w0, [x1];" // IRQ Enable Register + : + : "i"(CORE0_TIMER_IRQCNTL) + : "x0", "x1" // registers modified + ); +} + +void disable_timer_interrupt() { + // Mask timer interrupt + asm volatile( + "mov x0, 0;" + "ldr x1, =%0;" + "str w0, [x1];" + : + : "i"(CORE0_TIMER_IRQCNTL) + : "x0", "x1"); +} + +void timer_irq_handler() { + // Set up 1 second core timer interrupt + asm volatile( + "mrs x0, cntfrq_el0;" // Frequency register + "msr cntp_tval_el0, x0;" // Physical Timer TimerValue register + ); + + // Check the timer queue + while (head != 0 && timer_get_uptime() >= head->time) { + head->func(head->arg); // Execute the callback function + kfree((void *)head, 0); // Free the memory allocated for the timer node + head = head->next; // Remove the head node after func finished + } + + enable_timer_interrupt(); +} + +uint64_t timer_get_uptime() { + uint64_t cntpct_el0 = 0, cntfrq_el0 = 0; + asm volatile( + "mrs %0, cntpct_el0;" + "mrs %1, cntfrq_el0;" + : "=r"(cntpct_el0), "=r"(cntfrq_el0)); + return cntpct_el0 / cntfrq_el0; +} + +void timer_add(void (*callback)(void *), void *arg, int duration) { + // Insert the new timer node into the linked list (sorted by time) + + timer_entry *timer = (timer_entry *)kmalloc(sizeof(timer_entry), 0); + timer->func = callback; + timer->arg = arg; + timer->time = timer_get_uptime() + duration; + timer->next = 0; + + if (head == 0 || timer->time < head->time) { + // Insert at the beginning of the list + timer->next = head; + head = timer; + return; + } + + timer_entry *current = head; + while (current->next != 0 && current->next->time <= timer->time) + current = current->next; + timer->next = current->next; + current->next = timer; +} + +static void show_timer_message(void *arg) { + uart_putc(NEWLINE); + uart_puts("[INFO] Timer message: "); + uart_puts((char *)arg); + uart_putc(NEWLINE); + kfree(arg, 0); // Free the memory allocated for the argument +} + +void set_timer(const char *message, int duration) { + timer_add((void (*)(void *))show_timer_message, (void *)message, duration); +} diff --git a/lab4/src/traps.c b/lab4/src/traps.c new file mode 100644 index 000000000..f4a97dc44 --- /dev/null +++ b/lab4/src/traps.c @@ -0,0 +1,34 @@ +#include + +#include "uart.h" + +void print_registers(uint64_t elr, uint64_t esr, uint64_t spsr) { + // Print spsr_el1 + uart_puts("spsr_el1: "); + uart_hex(spsr); + uart_putc(NEWLINE); + + // Print elr_el1 + uart_puts(" elr_el1: "); + uart_hex(elr); + uart_putc(NEWLINE); + + // Print esr_el1 + uart_puts(" esr_el1: "); + uart_hex(esr); + uart_putc(NEWLINE); + uart_putc(NEWLINE); +} + +void exception_entry(uint64_t elr, uint64_t esr, uint64_t spsr) { + uart_puts("Exception Handler"); + uart_putc(NEWLINE); + print_registers(elr, esr, spsr); +} + +void invalid_entry(uint64_t elr, uint64_t esr, uint64_t spsr) { + uart_puts("[ERROR] The exception handler is not implemented"); + uart_putc(NEWLINE); + print_registers(elr, esr, spsr); + while (1); +} \ No newline at end of file diff --git a/lab4/src/uart.c b/lab4/src/uart.c new file mode 100644 index 000000000..3925f9f7b --- /dev/null +++ b/lab4/src/uart.c @@ -0,0 +1,171 @@ +#include "uart.h" + +#include "irq.h" +#include "mem.h" +#include "string.h" + +static int uart_read_idx = 0; +static int uart_write_idx = 0; +static char *uart_read_buffer; +static char *uart_write_buffer; + +void init_uart() { + uart_puts("[INFO] initializing uart_read_buffer."); + uart_putc(NEWLINE); + uart_read_buffer = (char *)kmalloc(UART_BUF_SIZE, 0); + + uart_puts("[INFO] initializing uart_write_buffer."); + uart_putc(NEWLINE); + uart_write_buffer = (char *)kmalloc(UART_BUF_SIZE, 0); + + // Configure GPIO pins + register unsigned int r = *GPFSEL1; + r &= ~((7 << 12) | (7 << 15)); + r |= (2 << 12) | (2 << 15); + *GPFSEL1 = r; + + // Disable GPIO pull up/down + *GPPUD = 0; + for (int i = 0; i < 150; i++); + *GPPUDCLK0 = (1 << 14) | (1 << 15); + for (int i = 0; i < 150; i++); + *GPPUD = 0; + *GPPUDCLK0 = 0; + + // Initialize mini UART + *AUX_ENABLE |= 1; // Enable mini UART + *AUX_MU_CNTL = 0; // Disable Tx and Rx during setup + *AUX_MU_IER = 0; // Disable interrupt + *AUX_MU_LCR = 3; // Set data size to 8 bits + *AUX_MU_MCR = 0; // Disable auto flow control + *AUX_MU_BAUD = 270; // Set baud rate to 115200 + *AUX_MU_IIR = 6; // No FIFO + *AUX_MU_CNTL = 3; // Enable Tx and Rx + + // Enable AUX interrupts + *ENABLE_IRQS_1 |= 1 << 29; +} + +char uart_getc() { + // Check the data ready field on bit 0 of AUX_MU_LSR_REG + while (!(*AUX_MU_LSR & 0x01)); + char c = (char)(*AUX_MU_IO); // Read from AUX_MU_IO_REG + return c == CARRIAGE_RETURN ? NEWLINE : c; +} + +void uart_putc(char c) { + // Add CARRIAGE_RETURN before NEWLINE + if (c == NEWLINE) uart_putc(CARRIAGE_RETURN); + + // Check the transmitter empty field on bit 5 of AUX_MU_LSR_REG + while (!(*AUX_MU_LSR & 0x20)); + *AUX_MU_IO = c; // Write to AUX_MU_IO_REG +} + +void uart_hex(unsigned int h) { + uart_puts("0x"); + unsigned int n; + for (int c = 28; c >= 0; c -= 4) { + n = (h >> c) & 0xF; + n += n > 9 ? 0x37 : '0'; + uart_putc(n); + } +} + +void uart_simple_hex(unsigned int h) { + uart_puts("0x"); + if (h == 0) { + uart_putc('0'); + return; + } + char buf[10]; + int i = 0; + unsigned int n; + while (h > 0) { + n = h % 16; + buf[i++] = n + (n > 9 ? 0x37 : '0'); + h /= 16; + } + for (int j = i - 1; j >= 0; j--) { + uart_putc(buf[j]); + } +} + +void uart_dec(unsigned int d) { + if (d == 0) { + uart_putc('0'); + return; + } + char buf[10]; + int i = 0; + while (d > 0) { + buf[i++] = d % 10 + '0'; + d /= 10; + } + for (int j = i - 1; j >= 0; j--) { + uart_putc(buf[j]); + } +} + +void uart_puts(const char *s) { + while (*s) { + uart_putc(*s++); + } +} + +void enable_uart_tx_interrupt() { *AUX_MU_IER |= 0x02; } + +void disable_uart_tx_interrupt() { *AUX_MU_IER &= 0x01; } + +void enable_uart_rx_interrupt() { *AUX_MU_IER |= 0x01; } + +void disable_uart_rx_interrupt() { *AUX_MU_IER &= 0x2; } + +void uart_tx_irq_handler() { + disable_uart_tx_interrupt(); + // Buffer is not empty -> send characters + if (uart_write_idx < UART_BUF_SIZE && + uart_write_buffer[uart_write_idx] != 0) { + while (!(*AUX_MU_LSR & 0x20)); + *AUX_MU_IO = uart_write_buffer[uart_write_idx++]; + enable_uart_tx_interrupt(); + } +} + +void uart_rx_irq_handler() { + while (!(*AUX_MU_LSR & 0x01)); + char c = (char)(*AUX_MU_IO); + uart_read_buffer[uart_read_idx++] = (c == CARRIAGE_RETURN) ? NEWLINE : c; + if (uart_read_idx >= UART_BUF_SIZE) uart_read_idx = 0; + enable_uart_rx_interrupt(); +} + +void uart_async_read(char *buf, int len) { + disable_uart_rx_interrupt(); + for (int i = 0; i < uart_read_idx && i < len; i++) + buf[i] = uart_read_buffer[i]; + buf[uart_read_idx] = 0; + uart_read_idx = 0; +} + +void uart_async_write(const char *s) { + // Copy string to the write buffer + int len = 0; + while (*s != '\0') { + if (len >= UART_BUF_SIZE) { + uart_puts("[ERROR] Exceed the UART buffer size"); + uart_putc(NEWLINE); + return; + } + if (*s == NEWLINE) { + // Insert CARRIAGE_RETURN before NEWLINE + uart_write_buffer[len++] = CARRIAGE_RETURN; + uart_write_buffer[len++] = NEWLINE; + } else + uart_write_buffer[len++] = *s; + s++; + } + uart_write_buffer[len] = '\0'; + uart_write_idx = 0; // Reset the buffer index + enable_uart_tx_interrupt(); +} \ No newline at end of file diff --git a/lab4/src/utils.c b/lab4/src/utils.c new file mode 100644 index 000000000..f5226d183 --- /dev/null +++ b/lab4/src/utils.c @@ -0,0 +1,42 @@ +#include "utils.h" + +int align4(int n) { return n + (4 - n % 4) % 4; } + +int atoi(const char *s) { + int result = 0; + int sign = 1; + int i = 0; + + // Skip leading spaces + while (s[i] == ' ') { + i++; + } + + // Handle positive and negative sign + if (s[i] == '-') { + sign = -1; + i++; + } else if (s[i] == '+') { + i++; + } + + // Convert string to integer + while (s[i] >= '0' && s[i] <= '9') { + result = result * 10 + (s[i] - '0'); + i++; + } + + return sign * result; +} + +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; +} \ No newline at end of file diff --git a/lab4/start.S b/lab4/start.S new file mode 100644 index 000000000..c6b255ecf --- /dev/null +++ b/lab4/start.S @@ -0,0 +1,50 @@ +.section ".text.boot" + +.global _start + +_start: + /* get dtb address from x0 */ + ldr x1, =DTB_BASE // defined in devtree.c + str x0, [x1] + + /* get cpu id */ + mrs x1, mpidr_el1 /* Multiprocessor Affinity Register */ + and x1, x1, #3 + cbnz x1, halt // halt if cpu id != 0 + + /* set exception vector table */ + adr x0, exception_vector_table // defined in traps.S + msr vbar_el1, x0 + + /* switch from EL2 to EL1 */ + bl from_el2_to_el1 + + /* set stack pointer */ + ldr x1, =_start + mov sp, x1 + + /* clear bss section */ + ldr x1, =__bss_start + ldr w2, =__bss_size + +bss_reset: + cbz w2, run_main + str xzr, [x1], #8 + sub w2, w2, #1 + cbnz w2, bss_reset + +run_main: + /* branch to main function */ + bl main + +halt: + wfe + b halt + +from_el2_to_el1: + mov x0, (1 << 31) + msr hcr_el2, x0 // EL1 uses aarch64 + mov x0, 0x3C5 // ... 0011 1100 0101 + msr spsr_el2, x0 + msr elr_el2, lr + eret // return to EL1 diff --git a/lab4/traps.S b/lab4/traps.S new file mode 100644 index 000000000..80b4eafc4 --- /dev/null +++ b/lab4/traps.S @@ -0,0 +1,103 @@ +/* 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] + str x30, [sp, 16 * 15] + mrs x28, spsr_el1 /* Saved Program Status Register (EL1) */ + mrs x29, elr_el1 /* Exception Link Register (EL1) */ + stp x28, x29, [sp, 16 * 16] +.endm + +/* load general registers from stack */ +.macro load_all + ldp x28, x29, [sp, 16 * 16] + msr spsr_el1, x28 /* Saved Program Status Register (EL1) */ + msr elr_el1, x29 /* Exception Link Register (EL1) */ + 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] + ldr x30, [sp, 16 * 15] + add sp, sp, 32 * 9 +.endm + +exception_handler: + save_all + mrs x0, elr_el1 /* Exception Link Register (EL1) */ + mrs x1, esr_el1 /* ESR_EL1, Exception Syndrome Register (EL1) */ + mrs x2, spsr_el1 /* Saved Program Status Register (EL1) */ + bl exception_entry /* defined in traps.c */ + load_all + eret + +irq_handler: + save_all + bl irq_entry /* defined in irq.c */ + load_all + eret + +invalid_exc_handler: + save_all + mrs x0, elr_el1 /* Exception Link Register (EL1) */ + mrs x1, esr_el1 /* ESR_EL1, Exception Syndrome Register (EL1) */ + mrs x2, spsr_el1 /* Saved Program Status Register (EL1) */ + bl invalid_entry /* defined in traps.c */ + load_all + eret + +.macro v_entry label + b \label +.align 7 +.endm + +/* EL1 Exception Vector table + * vector table should be aligned to 0x800 + * and each entry size is 0x80 */ +.align 11 +.global exception_vector_table +exception_vector_table: + // EL1 -> EL1 while using SP_EL0 + v_entry invalid_exc_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler + // EL1 -> EL1 while using SP_EL1 + v_entry invalid_exc_handler + v_entry irq_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler + // EL0 (AArch64) -> EL1 + v_entry exception_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler + // EL0 (AArch32) -> EL1 + v_entry invalid_exc_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler From e2c5a763c19a2fee4458b69b8f71f701ae07981f Mon Sep 17 00:00:00 2001 From: "Chang, Chun-yien" Date: Tue, 18 Jun 2024 03:30:50 +0800 Subject: [PATCH 4/7] 411551025 lab5 --- lab5/.gitignore | 6 + lab5/Makefile | 38 ++++ lab5/bootloader/Makefile | 19 ++ lab5/bootloader/boot.c | 61 +++++++ lab5/bootloader/boot.h | 49 +++++ lab5/bootloader/linker.ld | 16 ++ lab5/bootloader/main.c | 41 +++++ lab5/bootloader/start.S | 40 ++++ lab5/include/command.h | 19 ++ lab5/include/devtree.h | 26 +++ lab5/include/hardware.h | 219 ++++++++++++++++++++++ lab5/include/initramfs.h | 35 ++++ lab5/include/irq.h | 20 ++ lab5/include/mbox.h | 11 ++ lab5/include/mem.h | 45 +++++ lab5/include/scheduler.h | 59 ++++++ lab5/include/shell.h | 7 + lab5/include/signals.h | 11 ++ lab5/include/str.h | 8 + lab5/include/syscalls.h | 18 ++ lab5/include/test.h | 4 + lab5/include/timer.h | 22 +++ lab5/include/traps.h | 38 ++++ lab5/include/uart.h | 40 ++++ lab5/include/utils.h | 24 +++ lab5/initramfs.cpio | Bin 0 -> 498176 bytes lab5/sender.sh | 27 +++ lab5/src/command.c | 234 ++++++++++++++++++++++++ lab5/src/devtree.c | 60 ++++++ lab5/src/initramfs.c | 158 ++++++++++++++++ lab5/src/irq.c | 96 ++++++++++ lab5/src/linker.ld | 13 ++ lab5/src/main.c | 26 +++ lab5/src/mbox.c | 39 ++++ lab5/src/mem.c | 374 ++++++++++++++++++++++++++++++++++++++ lab5/src/scheduler.S | 47 +++++ lab5/src/scheduler.c | 128 +++++++++++++ lab5/src/shell.c | 84 +++++++++ lab5/src/signals.c | 52 ++++++ lab5/src/start.S | 50 +++++ lab5/src/str.c | 30 +++ lab5/src/syscalls.c | 77 ++++++++ lab5/src/test.c | 137 ++++++++++++++ lab5/src/timer.c | 111 +++++++++++ lab5/src/traps.S | 114 ++++++++++++ lab5/src/traps.c | 134 ++++++++++++++ lab5/src/uart.c | 193 ++++++++++++++++++++ lab5/src/utils.c | 71 ++++++++ 48 files changed, 3131 insertions(+) create mode 100644 lab5/.gitignore create mode 100644 lab5/Makefile create mode 100644 lab5/bootloader/Makefile create mode 100644 lab5/bootloader/boot.c create mode 100644 lab5/bootloader/boot.h create mode 100644 lab5/bootloader/linker.ld create mode 100644 lab5/bootloader/main.c create mode 100644 lab5/bootloader/start.S create mode 100644 lab5/include/command.h create mode 100644 lab5/include/devtree.h create mode 100644 lab5/include/hardware.h create mode 100644 lab5/include/initramfs.h create mode 100644 lab5/include/irq.h create mode 100644 lab5/include/mbox.h create mode 100644 lab5/include/mem.h create mode 100644 lab5/include/scheduler.h create mode 100644 lab5/include/shell.h create mode 100644 lab5/include/signals.h create mode 100644 lab5/include/str.h create mode 100644 lab5/include/syscalls.h create mode 100644 lab5/include/test.h create mode 100644 lab5/include/timer.h create mode 100644 lab5/include/traps.h create mode 100644 lab5/include/uart.h create mode 100644 lab5/include/utils.h create mode 100644 lab5/initramfs.cpio create mode 100755 lab5/sender.sh create mode 100644 lab5/src/command.c create mode 100644 lab5/src/devtree.c create mode 100644 lab5/src/initramfs.c create mode 100644 lab5/src/irq.c create mode 100644 lab5/src/linker.ld create mode 100644 lab5/src/main.c create mode 100644 lab5/src/mbox.c create mode 100644 lab5/src/mem.c create mode 100644 lab5/src/scheduler.S create mode 100644 lab5/src/scheduler.c create mode 100644 lab5/src/shell.c create mode 100644 lab5/src/signals.c create mode 100644 lab5/src/start.S create mode 100644 lab5/src/str.c create mode 100644 lab5/src/syscalls.c create mode 100644 lab5/src/test.c create mode 100644 lab5/src/timer.c create mode 100644 lab5/src/traps.S create mode 100644 lab5/src/traps.c create mode 100644 lab5/src/uart.c create mode 100644 lab5/src/utils.c diff --git a/lab5/.gitignore b/lab5/.gitignore new file mode 100644 index 000000000..eddcca901 --- /dev/null +++ b/lab5/.gitignore @@ -0,0 +1,6 @@ +.vscode +build +rootfs +temp +*.img +*.dtb \ No newline at end of file diff --git a/lab5/Makefile b/lab5/Makefile new file mode 100644 index 000000000..8821c90c1 --- /dev/null +++ b/lab5/Makefile @@ -0,0 +1,38 @@ +ARMGNU ?= aarch64-linux-gnu + +CFLAGS = -Iinclude -nostdlib -nostartfiles -ffreestanding -mgeneral-regs-only -Wall -g +ASMFLAGS = -Iinclude +QEMUFLAGS = -M raspi3b -serial null -serial stdio +# QEMUFLAGS = -M raspi3b -display none -serial null -serial stdio + +SRC_DIR = src +BUILD_DIR = build + +all: clean kernel8.img + +clean: + rm -rf $(BUILD_DIR) *.img + +build: + mkdir -p $(BUILD_DIR) + +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(BUILD_DIR) + $(ARMGNU)-gcc $(CFLAGS) -c $< -o $@ + +$(BUILD_DIR)/_%.o: $(SRC_DIR)/%.S | $(BUILD_DIR) + $(ARMGNU)-gcc $(ASMFLAGS) -c $< -o $@ + +SRC_FILES = $(wildcard $(SRC_DIR)/*.c) +ASM_FILES = $(wildcard $(SRC_DIR)/*.S) +OBJ_FILES = $(SRC_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%.o) +OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/_%.o) + +kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) + $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) + $(ARMGNU)-objcopy -O binary $(BUILD_DIR)/kernel8.elf kernel8.img + +qemu: all kernel8.img initramfs.cpio bcm2710-rpi-3-b-plus.dtb + clear & qemu-system-aarch64 $(QEMUFLAGS) -kernel kernel8.img -initrd initramfs.cpio -dtb bcm2710-rpi-3-b-plus.dtb + +debug: all kernel8.img initramfs.cpio bcm2710-rpi-3-b-plus.dtb + qemu-system-aarch64 $(QEMUFLAGS) -kernel kernel8.img -initrd initramfs.cpio -dtb bcm2710-rpi-3-b-plus.dtb -S -s -d int \ No newline at end of file diff --git a/lab5/bootloader/Makefile b/lab5/bootloader/Makefile new file mode 100644 index 000000000..d7882aa27 --- /dev/null +++ b/lab5/bootloader/Makefile @@ -0,0 +1,19 @@ +ARMGNU ?= aarch64-linux-gnu +CFLAGS = -nostdlib -nostartfiles -ffreestanding -mgeneral-regs-only -Wall -g +QEMUFLAGS = -M raspi3b -display none -serial null -serial pty + +all: clean bootloader.img + +clean: + rm -rf build *.img + +bootloader.img: + mkdir -p build + $(ARMGNU)-gcc $(CFLAGS) -c main.c -o build/main.o + $(ARMGNU)-gcc $(CFLAGS) -c start.S -o build/start.o + $(ARMGNU)-gcc $(CFLAGS) -c boot.c -o build/boot.o + $(ARMGNU)-ld -T linker.ld -o build/bootloader.elf build/main.o build/start.o build/boot.o + $(ARMGNU)-objcopy -O binary build/bootloader.elf bootloader.img + +qemu: clean bootloader.img + qemu-system-aarch64 $(QEMUFLAGS) -kernel bootloader.img -initrd ../initramfs.cpio -dtb ../bcm2710-rpi-3-b-plus.dtb \ No newline at end of file diff --git a/lab5/bootloader/boot.c b/lab5/bootloader/boot.c new file mode 100644 index 000000000..d4ff535f5 --- /dev/null +++ b/lab5/bootloader/boot.c @@ -0,0 +1,61 @@ +#include "boot.h" + +void init_uart() { + // Configure GPIO pins + register unsigned int r = *GPFSEL1; + r &= ~((7 << 12) | (7 << 15)); + r |= (2 << 12) | (2 << 15); + *GPFSEL1 = r; + + *GPPUD = 0; + for (int i = 0; i < 150; i++) asm volatile("nop"); + *GPPUDCLK0 = (1 << 14) | (1 << 15); + for (int i = 0; i < 150; i++) asm volatile("nop"); + *GPPUD = 0; + *GPPUDCLK0 = 0; + + *AUX_ENABLE |= 1; + *AUX_MU_CNTL = 0; + *AUX_MU_IER = 0; + *AUX_MU_LCR = 3; + *AUX_MU_MCR = 0; + *AUX_MU_BAUD = 270; + *AUX_MU_IIR = 6; + *AUX_MU_CNTL = 3; +} + +char uart_recv() { + while (!(*AUX_MU_LSR & 0x01)) asm volatile("nop"); + return (char)(*AUX_MU_IO); +} + +void uart_putc(char c) { + if (c == '\n') uart_putc('\r'); + while (!(*AUX_MU_LSR & 0x20)) asm volatile("nop"); + *AUX_MU_IO = c; +} + +void uart_puts(const char *s) { + while (*s) uart_putc(*s++); +} + +int atoi(const char *s) { + int result = 0; + int sign = 1; + int i = 0; + + while (s[i] == ' ') i++; + + if (s[i] == '-') { + sign = -1; + i++; + } else if (s[i] == '+') + i++; + + while (s[i] >= '0' && s[i] <= '9') { + result = result * 10 + (s[i] - '0'); + i++; + } + + return sign * result; +} \ No newline at end of file diff --git a/lab5/bootloader/boot.h b/lab5/bootloader/boot.h new file mode 100644 index 000000000..84cece020 --- /dev/null +++ b/lab5/bootloader/boot.h @@ -0,0 +1,49 @@ +#ifndef BOOT_H +#define BOOT_H + +/* ==================== GPIO ==================== */ +#define MMIO_BASE 0x3F000000 + +#define GPFSEL0 ((volatile unsigned int *)(MMIO_BASE + 0x00200000)) +#define GPFSEL1 ((volatile unsigned int *)(MMIO_BASE + 0x00200004)) +#define GPFSEL2 ((volatile unsigned int *)(MMIO_BASE + 0x00200008)) +#define GPFSEL3 ((volatile unsigned int *)(MMIO_BASE + 0x0020000C)) +#define GPFSEL4 ((volatile unsigned int *)(MMIO_BASE + 0x00200010)) +#define GPFSEL5 ((volatile unsigned int *)(MMIO_BASE + 0x00200014)) +#define GPSET0 ((volatile unsigned int *)(MMIO_BASE + 0x0020001C)) +#define GPSET1 ((volatile unsigned int *)(MMIO_BASE + 0x00200020)) +#define GPCLR0 ((volatile unsigned int *)(MMIO_BASE + 0x00200028)) +#define GPCLR1 ((volatile unsigned int *)(MMIO_BASE + 0x0020002C)) +#define GPLEV0 ((volatile unsigned int *)(MMIO_BASE + 0x00200034)) +#define GPLEV1 ((volatile unsigned int *)(MMIO_BASE + 0x00200038)) +#define GPEDS0 ((volatile unsigned int *)(MMIO_BASE + 0x00200040)) +#define GPEDS1 ((volatile unsigned int *)(MMIO_BASE + 0x00200044)) +#define GPHEN0 ((volatile unsigned int *)(MMIO_BASE + 0x00200064)) +#define GPHEN1 ((volatile unsigned int *)(MMIO_BASE + 0x00200068)) +#define GPPUD ((volatile unsigned int *)(MMIO_BASE + 0x00200094)) +#define GPPUDCLK0 ((volatile unsigned int *)(MMIO_BASE + 0x00200098)) +#define GPPUDCLK1 ((volatile unsigned int *)(MMIO_BASE + 0x0020009C)) + +/* ==================== UART ==================== */ +#define AUX_ENABLE ((volatile unsigned int *)(MMIO_BASE + 0x00215004)) +#define AUX_MU_IO ((volatile unsigned int *)(MMIO_BASE + 0x00215040)) +#define AUX_MU_IER ((volatile unsigned int *)(MMIO_BASE + 0x00215044)) +#define AUX_MU_IIR ((volatile unsigned int *)(MMIO_BASE + 0x00215048)) +#define AUX_MU_LCR ((volatile unsigned int *)(MMIO_BASE + 0x0021504C)) +#define AUX_MU_MCR ((volatile unsigned int *)(MMIO_BASE + 0x00215050)) +#define AUX_MU_LSR ((volatile unsigned int *)(MMIO_BASE + 0x00215054)) +#define AUX_MU_MSR ((volatile unsigned int *)(MMIO_BASE + 0x00215058)) +#define AUX_MU_SCRATCH ((volatile unsigned int *)(MMIO_BASE + 0x0021505C)) +#define AUX_MU_CNTL ((volatile unsigned int *)(MMIO_BASE + 0x00215060)) +#define AUX_MU_STAT ((volatile unsigned int *)(MMIO_BASE + 0x00215064)) +#define AUX_MU_BAUD ((volatile unsigned int *)(MMIO_BASE + 0x00215068)) + +void init_uart(); +char uart_recv(); +void uart_putc(char c); +void uart_puts(const char *s); + +/* ==================== UTILS ==================== */ +int atoi(const char *s); + +#endif // BOOT_H \ No newline at end of file diff --git a/lab5/bootloader/linker.ld b/lab5/bootloader/linker.ld new file mode 100644 index 000000000..556816b11 --- /dev/null +++ b/lab5/bootloader/linker.ld @@ -0,0 +1,16 @@ +SECTIONS +{ + . = 0x60000; + __loader_start = .; + .text : { *(.text.boot) *(.text) } + .rodata : { *(.rodata) } + .data : { *(.data) } + .bss : { + __bss_start = .; + *(.bss) + __bss_end = .; + } + __loader_end = .; +} +__bss_size = SIZEOF(.bss); +__loader_size = __loader_end - __loader_start; \ No newline at end of file diff --git a/lab5/bootloader/main.c b/lab5/bootloader/main.c new file mode 100644 index 000000000..cc8faa0fe --- /dev/null +++ b/lab5/bootloader/main.c @@ -0,0 +1,41 @@ +#include "boot.h" + +int main() { + init_uart(); + uart_puts("\033[2J\033[H"); + uart_puts( + "UART Bootloader\n" + "Waiting for kernel...\n"); + + // Get kernel image size + char buf[16] = {0}; + for (int i = 0; i < 16; i++) { + buf[i] = uart_recv(); + if (buf[i] == '\n') { + buf[i] = '\0'; + break; + } + } + + // Load kernel image + uart_puts("Kernel size: "); + uart_puts(buf); + uart_puts(" bytes.\n"); + uart_puts("Loading the kernel image...\n"); + + unsigned int size = atoi(buf); + char *kernel = (char *)0x80000; + while (size--) *kernel++ = uart_recv(); + + // Restore registers x0 x1 x2 x3 + asm volatile( + "mov x0, x10\n" + "mov x1, x11\n" + "mov x2, x12\n" + "mov x3, x13\n" + "mov x30, 0x80000\n" + "ret\n" // Jump to the new kernel + ); + + return 0; +} \ No newline at end of file diff --git a/lab5/bootloader/start.S b/lab5/bootloader/start.S new file mode 100644 index 000000000..ec8488dde --- /dev/null +++ b/lab5/bootloader/start.S @@ -0,0 +1,40 @@ +.section ".text.boot" + +.global _start + +_start: + /* save registers x0 x1 x2 x3 */ + mov x10, x0 /* dtb_base address */ + mov x11, x1 + mov x12, x2 + mov x12, x3 + + /* relocate bootloader */ + ldr x1, =0x80000 + ldr x2, =__loader_start // 0x60000 + ldr w3, =__loader_size + +relocate: + ldr x4, [x1], #8 + str x4, [x2], #8 + sub w3, w3, #1 + cbnz w3, relocate + + /* set stack pointer */ + ldr x1, =_start + mov sp, x1 + + /* clear bss section */ + ldr x1, =__bss_start + ldr w2, =__bss_size + +bss_reset: + cbz w2, run_main + str xzr, [x1], #8 + sub w2, w2, #1 + cbnz w2, bss_reset + +run_main: + /* branch to main function */ + bl main-0x20000 + b run_main diff --git a/lab5/include/command.h b/lab5/include/command.h new file mode 100644 index 000000000..6fce9aa9a --- /dev/null +++ b/lab5/include/command.h @@ -0,0 +1,19 @@ +#pragma once + +#define MAX_BUF_SIZE 1024 +#define END_OF_COMMAND_LIST "NULL" + +struct command { + const char *name; + const char *help; + void (*func)(void); +}; + +extern struct command cmd_list[]; +extern unsigned int BOARD_REVISION; +extern unsigned int BASE_MEMORY; +extern unsigned int NUM_PAGES; + +// Commands +void cmd_info(); +void cmd_hello(); \ No newline at end of file diff --git a/lab5/include/devtree.h b/lab5/include/devtree.h new file mode 100644 index 000000000..a55abe78b --- /dev/null +++ b/lab5/include/devtree.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +struct fdt_header { + uint32_t magic; + uint32_t totalsize; + uint32_t off_dt_struct; + uint32_t off_dt_strings; + uint32_t off_mem_rsvmap; + uint32_t version; + uint32_t last_comp_version; + uint32_t boot_cpuid_phys; + uint32_t size_dt_strings; + uint32_t size_dt_struct; +}; + +/** + * @brief Convert a 4-byte big-endian sequence to little-endian. + * + * @param s: big-endian sequence + * @return little-endian sequence + */ +uint32_t be2le(const void *s); + +void fdt_traverse(void (*callback)(void *, char *)); diff --git a/lab5/include/hardware.h b/lab5/include/hardware.h new file mode 100644 index 000000000..41c5c6053 --- /dev/null +++ b/lab5/include/hardware.h @@ -0,0 +1,219 @@ +#pragma once + +#define MMIO_BASE 0x3F000000 + +// ARM Interrupt Registers + +#define IRQ_BASIC_PENDING (volatile unsigned int *)(MMIO_BASE + 0x0000B200) +#define IRQ_PENDING_1 (volatile unsigned int *)(MMIO_BASE + 0x0000B204) +#define IRQ_PENDING_2 (volatile unsigned int *)(MMIO_BASE + 0x0000B208) +#define FIQ_CONTROL (volatile unsigned int *)(MMIO_BASE + 0x0000B20C) +#define ENABLE_IRQS_1 (volatile unsigned int *)(MMIO_BASE + 0x0000B210) +#define ENABLE_IRQS_2 (volatile unsigned int *)(MMIO_BASE + 0x0000B214) +#define ENABLE_BASIC_IRQS (volatile unsigned int *)(MMIO_BASE + 0x0000B218) +#define DISABLE_IRQS_1 (volatile unsigned int *)(MMIO_BASE + 0x0000B21C) +#define DISABLE_IRQS_2 (volatile unsigned int *)(MMIO_BASE + 0x0000B220) +#define DISABLE_BASIC_IRQS (volatile unsigned int *)(MMIO_BASE + 0x0000B224) +#define CORE0_INTERRUPT_SOURCE (volatile unsigned int *)(0x40000060) + +// Timers interrupt control registers + +#define CORE0_TIMER_IRQCNTL 0x40000040 +#define CORE1_TIMER_IRQCNTL 0x40000044 +#define CORE2_TIMER_IRQCNTL 0x40000048 +#define CORE3_TIMER_IRQCNTL 0x4000004C + +// Where to route timer interrupt to, IRQ/FIQ +// Setting both the IRQ and FIQ bit gives an FIQ +#define TIMER0_IRQ 0x01 +#define TIMER1_IRQ 0x02 +#define TIMER2_IRQ 0x04 +#define TIMER3_IRQ 0x08 +#define TIMER0_FIQ 0x10 +#define TIMER1_FIQ 0x20 +#define TIMER2_FIQ 0x40 +#define TIMER3_FIQ 0x80 + +// PASSWORD + +#define PM_PASSWORD 0x5A000000 +#define PM_RSTC (volatile unsigned int *)0x3F10001C +#define PM_WDOG (volatile unsigned int *)0x3F100024 + +// DEVICETREE + +#define FDT_BEGIN_NODE 0x00000001 +#define FDT_END_NODE 0x00000002 +#define FDT_PROP 0x00000003 +#define FDT_NOP 0x00000004 +#define FDT_END 0x00000009 + +// GPIO + +#define GPFSEL0 ((volatile unsigned int *)(MMIO_BASE + 0x00200000)) +#define GPFSEL1 ((volatile unsigned int *)(MMIO_BASE + 0x00200004)) +#define GPFSEL2 ((volatile unsigned int *)(MMIO_BASE + 0x00200008)) +#define GPFSEL3 ((volatile unsigned int *)(MMIO_BASE + 0x0020000C)) +#define GPFSEL4 ((volatile unsigned int *)(MMIO_BASE + 0x00200010)) +#define GPFSEL5 ((volatile unsigned int *)(MMIO_BASE + 0x00200014)) +#define GPSET0 ((volatile unsigned int *)(MMIO_BASE + 0x0020001C)) +#define GPSET1 ((volatile unsigned int *)(MMIO_BASE + 0x00200020)) +#define GPCLR0 ((volatile unsigned int *)(MMIO_BASE + 0x00200028)) +#define GPCLR1 ((volatile unsigned int *)(MMIO_BASE + 0x0020002C)) +#define GPLEV0 ((volatile unsigned int *)(MMIO_BASE + 0x00200034)) +#define GPLEV1 ((volatile unsigned int *)(MMIO_BASE + 0x00200038)) +#define GPEDS0 ((volatile unsigned int *)(MMIO_BASE + 0x00200040)) +#define GPEDS1 ((volatile unsigned int *)(MMIO_BASE + 0x00200044)) +// Skip GPRENn, GPFENn +#define GPHEN0 ((volatile unsigned int *)(MMIO_BASE + 0x00200064)) +#define GPHEN1 ((volatile unsigned int *)(MMIO_BASE + 0x00200068)) +// Skip GPLENn, GPARENn +#define GPPUD ((volatile unsigned int *)(MMIO_BASE + 0x00200094)) +#define GPPUDCLK0 ((volatile unsigned int *)(MMIO_BASE + 0x00200098)) +#define GPPUDCLK1 ((volatile unsigned int *)(MMIO_BASE + 0x0020009C)) + +// UART + +#define AUX_ENABLE ((volatile unsigned int *)(MMIO_BASE + 0x00215004)) +#define AUX_MU_IO ((volatile unsigned int *)(MMIO_BASE + 0x00215040)) +#define AUX_MU_IER ((volatile unsigned int *)(MMIO_BASE + 0x00215044)) +#define AUX_MU_IIR ((volatile unsigned int *)(MMIO_BASE + 0x00215048)) +#define AUX_MU_LCR ((volatile unsigned int *)(MMIO_BASE + 0x0021504C)) +#define AUX_MU_MCR ((volatile unsigned int *)(MMIO_BASE + 0x00215050)) +#define AUX_MU_LSR ((volatile unsigned int *)(MMIO_BASE + 0x00215054)) +#define AUX_MU_MSR ((volatile unsigned int *)(MMIO_BASE + 0x00215058)) +#define AUX_MU_SCRATCH ((volatile unsigned int *)(MMIO_BASE + 0x0021505C)) +#define AUX_MU_CNTL ((volatile unsigned int *)(MMIO_BASE + 0x00215060)) +#define AUX_MU_STAT ((volatile unsigned int *)(MMIO_BASE + 0x00215064)) +#define AUX_MU_BAUD ((volatile unsigned int *)(MMIO_BASE + 0x00215068)) + +// MAILBOX + +#define MAILBOX_EMPTY 0x40000000 +#define MAILBOX_FULL 0x80000000 +#define MAILBOX_REQUEST 0x00000000 +#define MAILBOX_RESPONSE 0x80000000 +#define REQUEST_CODE 0x00000000 +#define REQUEST_SUCCEED 0x80000000 +#define REQUEST_FAILED 0x80000001 +#define TAG_REQUEST_CODE 0x00000000 +#define END_TAG 0x00000000 + +// Channels +#define MAILBOX_CH_POWER 0 +#define MAILBOX_CH_FB 1 +#define MAILBOX_CH_VUART 2 +#define MAILBOX_CH_VCHIQ 3 +#define MAILBOX_CH_LEDS 4 +#define MAILBOX_CH_BTNS 5 +#define MAILBOX_CH_TOUCH 6 +#define MAILBOX_CH_COUNT 7 +#define MAILBOX_CH_PROP 8 + +#define MAILBOX_BASE (MMIO_BASE + 0x0000B880) + +// mailbox register +#define MAILBOX_REG_READ ((volatile unsigned int *)(MAILBOX_BASE + 0x00000000)) +#define MAILBOX_REG_STATUS \ + ((volatile unsigned int *)(MAILBOX_BASE + 0x00000018)) +#define MAILBOX_REG_WRITE ((volatile unsigned int *)(MAILBOX_BASE + 0x00000020)) + +// mailbox channel +// https://github.com/raspberrypi/firmware/wiki/Mailboxes#channels +#define MAILBOX_CH_POWER 0 +#define MAILBOX_CH_FB 1 +#define MAILBOX_CH_VUART 2 +#define MAILBOX_CH_VCHIQ 3 +#define MAILBOX_CH_LEDS 4 +#define MAILBOX_CH_BTNS 5 +#define MAILBOX_CH_TOUCH 6 +#define MAILBOX_CH_COUNT 7 +#define MAILBOX_CH_PROP 8 + +// mailbox status +#define MAILBOX_EMPTY 0x40000000 +#define MAILBOX_FULL 0x80000000 + +// tags +#define TAGS_REQ_CODE 0x00000000 +#define TAGS_REQ_SUCCEED 0x80000000 +#define TAGS_REQ_FAILED 0x80000001 +#define TAGS_END 0x00000000 + +// hardware tags operator +#define TAGS_HARDWARE_BOARD_MODEL 0x00010001 +#define TAGS_HARDWARE_BOARD_REVISION 0x00010002 +#define TAGS_HARDWARE_MAC_ADDR 0x00010003 +#define TAGS_HARDWARE_BOARD_SERIAL 0x00010004 +#define TAGS_HARDWARE_ARM_MEM 0x00010005 +#define TAGS_HARDWARE_VC_MEM 0x00010006 +#define TAGS_HARDWARE_CLOCKS 0x00010007 + +// CLOCK ID + +#define CLOCK_ID_RESERVED 0x000000000 +#define CLOCK_ID_EMMC 0x000000001 +#define CLOCK_ID_UART 0x000000002 +#define CLOCK_ID_ARM 0x000000003 +#define CLOCK_ID_CORE 0x000000004 +#define CLOCK_ID_V3D 0x000000005 +#define CLOCK_ID_H264 0x000000006 +#define CLOCK_ID_ISP 0x000000007 +#define CLOCK_ID_SDRAM 0x000000008 +#define CLOCK_ID_PIXEL 0x000000009 +#define CLOCK_ID_PWM 0x00000000a +#define CLOCK_ID_HEVC 0x00000000b +#define CLOCK_ID_EMMC2 0x00000000c +#define CLOCK_ID_M2MC 0x00000000d +#define CLOCK_ID_PIXEL_BVB 0x00000000e + +// clock tags operator +#define TAGS_GET_CLOCK 0x00030002 +#define TAGS_SET_CLOCK 0x00038002 + +// framebuffer tages operator +#define FB_ALLOC_BUFFER 0x00040001 +#define FB_FREE_BUFFER 0x00048001 + +#define FB_BLANK_SCREEN 0x00040002 + +#define FB_PHY_WID_HEIGHT_GET 0x00040003 +#define FB_PHY_WID_HEIGHT_TEST 0x00044003 +#define FB_PHY_WID_HEIGHT_SET 0x00048003 + +#define FB_VIR_WID_HEIGHT_GET 0x00040004 +#define FB_VIR_WID_HEIGHT_TEST 0x00044004 +#define FB_VIR_WID_HEIGHT_SET 0x00048004 + +#define FB_DEPTH_GET 0x00040005 +#define FB_DEPTH_TEST 0x00044005 +#define FB_DEPTH_SET 0x00048005 + +#define FB_DEPTH_GET 0x00040005 +#define FB_DEPTH_TEST 0x00044005 +#define FB_DEPTH_SET 0x00048005 + +#define FB_PIXEL_ORDER_GET 0x00040006 +#define FB_PIXEL_ORDER_TEST 0x00044006 +#define FB_PIXEL_ORDER_SET 0x00048006 + +#define FB_ALPHA_MODE_GET 0x00040007 +#define FB_ALPHA_MODE_TEST 0x00044007 +#define FB_ALPHA_MODE_SET 0x00048007 + +#define FB_PITCH_GET 0x00040008 + +#define FB_VIR_OFFSET_GET 0x00040009 +#define FB_VIR_OFFSET_TEST 0x00044009 +#define FB_VIR_OFFSET_SET 0x00048009 + +#define FB_OVERSCAN_GET 0x0004000A +#define FB_OVERSCAN_TEST 0x0004400A +#define FB_OVERSCAN_SET 0x0004800A + +#define FB_PALETTE_GET 0x0004000B +#define FB_PALETTE_TEST 0x0004400B +#define FB_PALETTE_SET 0x0004800B + +#define FB_CURSOR_INFO_SET 0x00008010 +#define FB_CURSOR_STATE_SET 0x00008011 diff --git a/lab5/include/initramfs.h b/lab5/include/initramfs.h new file mode 100644 index 000000000..4c82cb598 --- /dev/null +++ b/lab5/include/initramfs.h @@ -0,0 +1,35 @@ +#pragma once + +#include "traps.h" + +// 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; + +typedef struct { + int namesize; + int filesize; + int headsize; + int datasize; + char *pathname; +} ramfsRec; + +void initramfs_ls(); +void initramfs_cat(const char *target); +void initramfs_callback(void *addr, char *property); +void initramfs_run(const char *target); +void initramfs_sys_exec(const char *target, trap_frame *tf); diff --git a/lab5/include/irq.h b/lab5/include/irq.h new file mode 100644 index 000000000..cef36db3a --- /dev/null +++ b/lab5/include/irq.h @@ -0,0 +1,20 @@ +#pragma once + +#include "hardware.h" +#include "traps.h" + +#define ORDER_FIRST 0x0 +#define ORDER_REGULAR 0x8 +#define ORDER_LAST 0xF + +typedef struct irq_task_t { + void (*func)(); + int order; + int busy; // 0 (default) or 1 (busy) + struct irq_task_t *prev; + struct irq_task_t *next; +} irqTask; + +void enable_interrupt(); +void disable_interrupt(); +void irq_entry(trap_frame *tf); \ No newline at end of file diff --git a/lab5/include/mbox.h b/lab5/include/mbox.h new file mode 100644 index 000000000..090e7d72c --- /dev/null +++ b/lab5/include/mbox.h @@ -0,0 +1,11 @@ +#pragma once + +#include "hardware.h" + +extern volatile unsigned int mbox[36]; + +int mbox_call(unsigned char ch, unsigned int *mbox); +// int mailbox_call(unsigned char c); +// int sys_mbox_call(unsigned char ch, unsigned int *_mbox); +int get_board_revision(unsigned int *mbox); +int get_arm_memory_status(unsigned int *mbox); diff --git a/lab5/include/mem.h b/lab5/include/mem.h new file mode 100644 index 000000000..f2f077f1c --- /dev/null +++ b/lab5/include/mem.h @@ -0,0 +1,45 @@ +#pragma once + +#include + +#define BUDDY_MAX_ORDER 16 +#define CACHE_MAX_ORDER 6 +#define PAGE_SIZE 0x1000 +#define MIN_CACHE_SIZE (PAGE_SIZE / (2 << CACHE_MAX_ORDER)) +#define STACK_SIZE 0x1000 + +struct page { + unsigned int order; + unsigned int used; + unsigned int cache_order; + struct page *prev; + struct page *next; +}; + +struct object { + unsigned int order; + struct object *next; +}; + +void init_mem(); +void reserveMemory(uint64_t start, uint64_t end); + +void pushPageToFreeList(struct page **list, struct page *page, + unsigned int order); +struct page *popFreeList(struct page **list_head); +void removePageFromFreeList(struct page **list, struct page *page); +void printFreeListByOrder(unsigned int order); + +struct page *lookupBuddy(struct page *page, unsigned int order); +struct page *allocatePagesByOrder(unsigned int order, int silent); +void freePages(struct page *page, unsigned int order, int silent); +void mergePages(struct page *page, unsigned int order, int echo); + +void pushObjectToList(struct object **list_head, struct object *object, + unsigned int order); +struct object *popObjectFromList(struct object **list_head); +void *allocateCacheMemory(unsigned int index, int silent); +void freeCacheEntry(void *ptr, unsigned int index, int silent); + +void *kmalloc(unsigned int size, int silent); +void kfree(void *ptr, int silent); diff --git a/lab5/include/scheduler.h b/lab5/include/scheduler.h new file mode 100644 index 000000000..0ef41cb11 --- /dev/null +++ b/lab5/include/scheduler.h @@ -0,0 +1,59 @@ +#pragma once + +#include "signals.h" +#include "traps.h" + +struct context_struct { + unsigned long x19; + unsigned long x20; + unsigned long x21; + unsigned long x22; + unsigned long x23; + unsigned long x24; + unsigned long x25; + unsigned long x26; + unsigned long x27; + unsigned long x28; + unsigned long fp; + unsigned long lr; + unsigned long sp; +}; + +enum thread_state { + RUNNING, + DEAD, +}; + +typedef struct thread_struct_t { + struct context_struct context; // callee-saved (don't move) + int pid; + enum thread_state state; + void *stack; + void *user_stack; + + // Signal handling + void (*sig_handlers[NSIG + 1])(); // Signal handlers + int sig_reg; // Pending signals + int sig_busy; // Handling a signal + trap_frame *sig_tf; // tf Saved before signal handling + void *sig_stack; // Stack for signal handling + + struct thread_struct_t *prev; + struct thread_struct_t *next; +} thread_struct; + +// Defined in scheduler.S +extern void switch_to(thread_struct *prev, thread_struct *next); +extern thread_struct *get_current(); // get tpidr_el1, current thread_struct + +thread_struct *get_thread_by_pid(int pid); +void sched_init(); + +thread_struct *kcreate_thread(void (*func)()); +void list_tcircle(); + +void schedule(); + +void kill_current_thread(); +void kill_thread_by_pid(int pid); +void idle(); \ No newline at end of file diff --git a/lab5/include/shell.h b/lab5/include/shell.h new file mode 100644 index 000000000..fccef918e --- /dev/null +++ b/lab5/include/shell.h @@ -0,0 +1,7 @@ +#pragma once + +#define SHELL_BUF_SIZE 1024 + +void run_shell(); +void read_user_input(char *buf); +int exec_command(const char *command); diff --git a/lab5/include/signals.h b/lab5/include/signals.h new file mode 100644 index 000000000..1a4088676 --- /dev/null +++ b/lab5/include/signals.h @@ -0,0 +1,11 @@ +#pragma once + +#include "traps.h" + +#define NSIG 10 // Number of signals + +extern void sigreturn(); // Defined in traps.S + +void signal(int signum, void (*handler)()); +void signal_kill(int pid, int sig); +void do_signal(trap_frame *regs); diff --git a/lab5/include/str.h b/lab5/include/str.h new file mode 100644 index 000000000..fe18f924d --- /dev/null +++ b/lab5/include/str.h @@ -0,0 +1,8 @@ +#pragma once + +int strcmp(const char *str1, const char *str2); +int memcmp(const void *str1, const void *str2, int n); +char *strncpy(char *dest, const char *src, int n); +int strlen(const char *str); +char *strcat(char *dest, const char *src); +void *memcpy(void *dest, const void *src, int n); \ No newline at end of file diff --git a/lab5/include/syscalls.h b/lab5/include/syscalls.h new file mode 100644 index 000000000..ed048898f --- /dev/null +++ b/lab5/include/syscalls.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include "traps.h" + +extern void child_ret_from_fork(); // traps.S + +int sys_getpid(); +size_t sys_uart_read(char *buf, size_t size); +size_t sys_uart_write(const char *buf, size_t size); +int sys_exec(const char *name, trap_frame *tf); +int sys_fork(trap_frame *tf); +void sys_exit(int status); +// void sys_kill(int pid); +// void sys_signal(int signum, void (*handler)()); +// void sys_sigkill(int pid, int sig); +void sys_sigreturn(trap_frame *regs); \ No newline at end of file diff --git a/lab5/include/test.h b/lab5/include/test.h new file mode 100644 index 000000000..37346deb5 --- /dev/null +++ b/lab5/include/test.h @@ -0,0 +1,4 @@ +#pragma once + +void thread_test(); +void run_fork_test(); \ No newline at end of file diff --git a/lab5/include/timer.h b/lab5/include/timer.h new file mode 100644 index 000000000..1dd4d2287 --- /dev/null +++ b/lab5/include/timer.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +typedef struct timerEntry_t { + void (*func)(void *); + void *arg; + int time; + struct timerEntry_t *next; +} timerEntry; + +void init_timer(); + +void enable_timer_interrupt(); +void disable_timer_interrupt(); + +void timer_irq_handler(); +uint64_t timer_get_uptime(); + +void timer_add(void (*callback)(void *), void *arg, int duration); +void set_timer(const char *message, int duration); +void set_timeup(int *timeup); diff --git a/lab5/include/traps.h b/lab5/include/traps.h new file mode 100644 index 000000000..bf940f5d7 --- /dev/null +++ b/lab5/include/traps.h @@ -0,0 +1,38 @@ +#pragma once + +typedef struct { // order is important + unsigned long x0; + unsigned long x1; + unsigned long x2; + unsigned long x3; + unsigned long x4; + unsigned long x5; + unsigned long x6; + unsigned long x7; + unsigned long x8; // system call number + unsigned long x9; + unsigned long x10; + unsigned long x11; + unsigned long x12; + unsigned long x13; + unsigned long x14; + unsigned long x15; + unsigned long x16; + unsigned long x17; + unsigned long x18; + unsigned long x19; + unsigned long x20; + unsigned long x21; + unsigned long x22; + unsigned long x23; + unsigned long x24; + unsigned long x25; + unsigned long x26; + unsigned long x27; + unsigned long x28; + unsigned long x29; + unsigned long x30; // link register + unsigned long spsr_el1; + unsigned long elr_el1; + unsigned long sp_el0; +} trap_frame; diff --git a/lab5/include/uart.h b/lab5/include/uart.h new file mode 100644 index 000000000..6d2b99c09 --- /dev/null +++ b/lab5/include/uart.h @@ -0,0 +1,40 @@ +#pragma once + +#include "hardware.h" + +#define UART_BUF_SIZE 1024 + +#define BACKSPACE '\b' +#define DELETE 127 +#define ESC 27 +#define NEWLINE '\n' +#define TAB '\t' +#define CARRIAGE_RETURN '\r' + +#define INFO 0 +#define WARN 1 +#define TEST 2 +#define BUDD 10 +#define CACH 11 +#define ERR 255 + +void init_uart(); +char uart_getc(); +void uart_putc(char c); +void uart_puts(const char *s); +void uart_clear(); +void uart_hex(unsigned int h); +void uart_simple_hex(unsigned int h); +void uart_dec(unsigned int h); +void uart_log(int type, const char *msg); + +void enable_uart_tx_interrupt(); +void disable_uart_tx_interrupt(); +void enable_uart_rx_interrupt(); +void disable_uart_rx_interrupt(); + +void uart_tx_irq_handler(); +void uart_rx_irq_handler(); + +void uart_async_read(char *buf, int len); +void uart_async_write(const char *s); \ No newline at end of file diff --git a/lab5/include/utils.h b/lab5/include/utils.h new file mode 100644 index 000000000..c2ebcb54b --- /dev/null +++ b/lab5/include/utils.h @@ -0,0 +1,24 @@ +#pragma once + +/** + * @brief Align `n` to be a multiple of 4. + * + * @param n A number + * @return Algined number + */ +int align4(int n); + +int atoi(const char *s); + +/** + * @brief Convert hexadecimal string to int. + * + * @param s: hexadecimal string + * @param n: string length + * @return Converted int number + */ +int hextoi(char *s, int n); + +int memcmp(const void *str1, const void *str2, int n); +void *memcpy(void *dest, const void *src, int n); +void *memset(void *s, int c, int n); \ No newline at end of file diff --git a/lab5/initramfs.cpio b/lab5/initramfs.cpio new file mode 100644 index 0000000000000000000000000000000000000000..586b0671cd1fa358bb44d989af4739434c983052 GIT binary patch literal 498176 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 literal 0 HcmV?d00001 diff --git a/lab5/sender.sh b/lab5/sender.sh new file mode 100755 index 000000000..501756d76 --- /dev/null +++ b/lab5/sender.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# This script sends the kernel image to Rpi3 through UART + +DEST_PATH="/dev/ttyUSB0" +KERNEL_PATH="./kernel8.img" + +# Check the root permission +if [ "$(id -u)" != "0" ]; then + echo "This script must be run as root" + exit 1 +fi + +if [ $1 ] +then + DEST_PATH="$1" +fi + +# Get the size of the kernel image file and send it to Rpi3 +# wc -c: count bytes of a file +# sleep: wait n seconds +wc -c < $KERNEL_PATH > $DEST_PATH | sleep 1 + +# Send the kernel image +# pv: redirect file input to specified tty +# add --rate-limit option to limit the speed +pv $KERNEL_PATH > $DEST_PATH diff --git a/lab5/src/command.c b/lab5/src/command.c new file mode 100644 index 000000000..421100c36 --- /dev/null +++ b/lab5/src/command.c @@ -0,0 +1,234 @@ +#include "command.h" + +#include "initramfs.h" +#include "mem.h" +#include "scheduler.h" +#include "shell.h" +#include "str.h" +#include "syscalls.h" +#include "test.h" +#include "timer.h" +#include "uart.h" +#include "utils.h" + +typedef struct { + void *ptr; + void *next; +} demo_mem_rec; + +static demo_mem_rec *dmr_list = 0; + +void cmd_info() { + uart_log(INFO, "board revision: "); + uart_hex(BOARD_REVISION); + uart_putc(NEWLINE); + uart_log(INFO, "device base memory address: "); + uart_hex(BASE_MEMORY); + uart_putc(NEWLINE); + uart_log(INFO, "device memory size: "); + uart_hex(NUM_PAGES * PAGE_SIZE); + uart_putc(NEWLINE); +} + +void cmd_hello() { + uart_puts("Hello there!"); + uart_putc(NEWLINE); +} + +static void cmd_mem() { + for (int i = BUDDY_MAX_ORDER; i >= 0; i--) { + printFreeListByOrder(i); + } +} + +static void cmd_bd() { + uart_puts("Buddy System: request order (0-"); + uart_dec(BUDDY_MAX_ORDER); + uart_puts("): "); + char *buf = kmalloc(SHELL_BUF_SIZE, 1); + read_user_input(buf); + int order = atoi(buf); + if (buf[0] < '0' || buf[0] > '9' || order < 0 || order > BUDDY_MAX_ORDER) { + uart_puts("Invalid order."); + uart_putc(NEWLINE); + } else { + demo_mem_rec *dmr = kmalloc(sizeof(demo_mem_rec), 1); + dmr->ptr = kmalloc(PAGE_SIZE << order, 0); + dmr->next = dmr_list; + dmr_list = dmr; + } + kfree(buf, 1); +} + +static void cmd_fm() { + if (dmr_list == 0) { + uart_puts("No memory block allocated."); + uart_putc(NEWLINE); + } else { + uart_puts("Freeing all allocated memory blocks..."); + uart_putc(NEWLINE); + do { + demo_mem_rec *dmr = dmr_list; + kfree(dmr->ptr, 0); + dmr_list = dmr->next; + kfree(dmr, 1); + } while (dmr_list != 0); + } +} + +static void cmd_ca() { + uart_log(INFO, "Dynamic Memory Allocator: request byte(s) (1-"); + uart_dec(PAGE_SIZE / 2); + uart_puts("): "); + char *buf = kmalloc(SHELL_BUF_SIZE, 1); + read_user_input(buf); + int size = atoi(buf); + if (buf[0] == '\0' || size <= 0) { + uart_log(WARN, "Invalid size.\n"); + } else { + if (size > PAGE_SIZE / 2) { + uart_log( + INFO, + "Size too large for dynamic allocator. Switching to Buddy System.\n"); + } + demo_mem_rec *dmr = kmalloc(sizeof(demo_mem_rec), 1); + dmr->ptr = kmalloc(size, 0); + dmr->next = dmr_list; + dmr_list = dmr; + } + kfree(buf, 1); +} + +static void cmd_lab() { + uart_puts( + "(1) Lab 3: UART async write\n" + "(2) Lab 3: UART async read\n" + "(3) Lab 5: Thread creation test\n" + "(4) Lab 5: Fork test\n" + ": "); + char *buf = kmalloc(SHELL_BUF_SIZE, 1); + read_user_input(buf); + switch (atoi(buf)) { + case 1: + uart_puts(": "); + strncpy(buf, "[INFO] Async write: ", strlen("[INFO] Async write: ")); + read_user_input(buf + strlen("[INFO] Async write: ")); + // read_user_input(buf); + uart_async_write(buf); + break; + case 2: + uart_puts("(Please type something in 3 sec.)"); + uart_putc(NEWLINE); + // set a timer to 3 sec. + int *timeup = 0; + timer_add((void (*)(void *))set_timeup, (void *)timeup, 3); + enable_uart_rx_interrupt(); + while (!*timeup); + // time's up, get the buf content and print + uart_async_read(buf, SHELL_BUF_SIZE); + uart_puts("[INFO] Async read received: "); + uart_puts(buf); + uart_putc(NEWLINE); + break; + case 3: + for (int i = 0; i < 3; i++) kcreate_thread(thread_test); + idle(); + break; + case 4: + kcreate_thread(run_fork_test); + idle(); + break; + default: + uart_puts("Option not found."); + uart_putc(NEWLINE); + } + kfree(buf, 1); +} + +static void cmd_timer() { + uart_puts("Duration(sec.): "); + char *buf = kmalloc(SHELL_BUF_SIZE, 1); + read_user_input(buf); + int sec = atoi(buf); + + char *msg = kmalloc(SHELL_BUF_SIZE, 0); + uart_puts(": "); + read_user_input(msg); + set_timer(msg, sec); + kfree(buf, 1); +} + +static void cmd_run() { + // Get filename from user input + uart_puts(": "); + char *buf = kmalloc(SHELL_BUF_SIZE, 1); + read_user_input(buf); + initramfs_run(buf); + kfree(buf, 1); +} + +static void cmd_reboot() { + uart_puts("Start Rebooting..."); + uart_putc(NEWLINE); + // Reboot after 0x20000 ticks + *PM_RSTC = PM_PASSWORD | 0x20; // Full reset + *PM_WDOG = PM_PASSWORD | 0x20000; // Number of watchdog ticks +} + +static void cmd_cancel() { + uart_puts("Rebooting Attempt Aborted, if any."); + uart_putc(NEWLINE); + *PM_RSTC = PM_PASSWORD | 0; + *PM_WDOG = PM_PASSWORD | 0; +} + +static void cmd_cat() { + // Get filename from user input + uart_puts(": "); + char *buf = kmalloc(SHELL_BUF_SIZE, 1); + read_user_input(buf); + initramfs_cat(buf); + kfree(buf, 1); +} + +static void cmd_clear() { uart_clear(); } + +static void cmd_help() { + uart_puts("-----------------------------------------"); + uart_putc(NEWLINE); + struct command *cmd = cmd_list; + while (1) { + if (!strcmp(cmd->name, END_OF_COMMAND_LIST)) { + break; + } + uart_puts(cmd->name); + uart_putc(TAB); + uart_puts(": "); + uart_puts(cmd->help); + uart_putc(NEWLINE); + cmd++; + } + uart_puts("-----------------------------------------"); + uart_putc(NEWLINE); +} + +struct command cmd_list[] = { + {.name = "help", .help = "Display this help menu", .func = cmd_help}, + {.name = "hello", .help = "Print 'Hello there!'", .func = cmd_hello}, + {.name = "clear", .help = "Clear the screen", .func = cmd_clear}, + {.name = "reboot", .help = "Reboot the device", .func = cmd_reboot}, + {.name = "cancel", .help = "Cancel a scheduled reboot", .func = cmd_cancel}, + {.name = "info", .help = "Show hardware information", .func = cmd_info}, + {.name = "ls", .help = "List files in ramdisk", .func = initramfs_ls}, + {.name = "cat", .help = "Display content of ramdisk file", .func = cmd_cat}, + {.name = "run", .help = "Run a specified program", .func = cmd_run}, + {.name = "timer", .help = "Set timer with duration", .func = cmd_timer}, + // lab4 + {.name = "mem", .help = "Display free memory blocks", .func = cmd_mem}, + {.name = "bd", .help = "Allocate a memory block", .func = cmd_bd}, + {.name = "ca", .help = "Use the dynamic memory allocator", .func = cmd_ca}, + // lab5 + {.name = "fm", .help = "Free all memory in the demo list", .func = cmd_fm}, + {.name = "pid", .help = "List all running threads", .func = list_tcircle}, + {.name = "lab", .help = "Showcase lab requirements", .func = cmd_lab}, + {.name = END_OF_COMMAND_LIST}}; diff --git a/lab5/src/devtree.c b/lab5/src/devtree.c new file mode 100644 index 000000000..3cefae084 --- /dev/null +++ b/lab5/src/devtree.c @@ -0,0 +1,60 @@ +#include "devtree.h" + +#include "str.h" +#include "uart.h" +#include "utils.h" + +// Assign a non-zero value to be stored in the .data section +void *DTB_BASE = (void *)0xF; +void *DTB_END = (void *)0xF; + +uint32_t be2le(const void *s) { + const uint8_t *bytes = (const uint8_t *)s; + return (uint32_t)bytes[0] << 24 | (uint32_t)bytes[1] << 16 | + (uint32_t)bytes[2] << 8 | (uint32_t)bytes[3]; +} + +void fdt_traverse(void (*callback)(void *, char *)) { + struct fdt_header *header = (struct fdt_header *)(uintptr_t)DTB_BASE; + + // Check the magic number + if (be2le(&(header->magic)) != 0xD00DFEED) { + uart_log(WARN, "Dtb header magic does not match!\n"); + } + DTB_END = DTB_BASE + be2le(&header->totalsize); + + uart_log(INFO, "Dtb loaded at "); + uart_hex((uintptr_t)DTB_BASE); + uart_putc('-'); + uart_hex((uintptr_t)DTB_END); + uart_putc(NEWLINE); + + uintptr_t structure = (uintptr_t)header + be2le(&header->off_dt_struct); + uintptr_t strings = (uintptr_t)header + be2le(&header->off_dt_strings); + uint32_t structure_size = be2le(&header->size_dt_struct); + + // Parse the structure block + uintptr_t ptr = structure; // Point to the beginning of structure block + while (ptr < structure + structure_size) { + uint32_t token = be2le((char *)ptr); + ptr += 4; // Token takes 4 bytes + + switch (token) { + case FDT_BEGIN_NODE: + ptr += align4(strlen((char *)ptr) + 1); + break; + case FDT_PROP: + uint32_t len = be2le((char *)ptr); + ptr += 4; + uint32_t nameoff = be2le((char *)ptr); + ptr += 4; + callback((void *)(uintptr_t)be2le((void *)ptr), + (char *)(strings + nameoff)); + ptr += align4(len); + break; + case FDT_END_NODE: + case FDT_NOP: + case FDT_END: + } + } +} \ No newline at end of file diff --git a/lab5/src/initramfs.c b/lab5/src/initramfs.c new file mode 100644 index 000000000..d753c16b6 --- /dev/null +++ b/lab5/src/initramfs.c @@ -0,0 +1,158 @@ +#include "initramfs.h" + +#include + +#include "mem.h" +#include "scheduler.h" +#include "str.h" +#include "uart.h" +#include "utils.h" + +void *initrd_start; +void *initrd_end; + +void initramfs_callback(void *addr, char *property) { + if (!strcmp(property, "linux,initrd-start")) { + uart_log(INFO, "linux,initrd-start: "); + uart_hex((uintptr_t)addr); + initrd_start = (char *)addr; + uart_putc(NEWLINE); + } else if (!strcmp(property, "linux,initrd-end")) { + uart_log(INFO, "linux,initrd-end: "); + uart_hex((uintptr_t)addr); + initrd_end = (char *)addr; + uart_putc(NEWLINE); + } +} + +static ramfsRec *ramfsNext(char *fptr) { + ramfsRec *rec = kmalloc(sizeof(ramfsRec), 1); + cpio_t *header = (cpio_t *)fptr; + + // New ASCII Format uses 8-byte hexadecimal string for all numbers + rec->namesize = hextoi(header->c_namesize, 8); + rec->filesize = hextoi(header->c_filesize, 8); + + // Total size of (header + pathname) is a multiple of four bytes + // File data is also padded to a multiple of four bytes + rec->headsize = align4(sizeof(cpio_t) + rec->namesize); + rec->datasize = align4(rec->filesize); + + // Get file pathname + rec->pathname = kmalloc(rec->namesize, 1); + strncpy(rec->pathname, fptr + sizeof(cpio_t), rec->namesize); + + return rec; +} + +void initramfs_ls() { + uart_putc(NEWLINE); + + char *fptr = (char *)initrd_start; + while (memcmp(fptr + sizeof(cpio_t), "TRAILER!!!", 10)) { + ramfsRec *rec = ramfsNext(fptr); + uart_puts(rec->pathname); + for (int i = 0; i < 15 - strlen(rec->pathname); i++) { + uart_putc(' '); + } + uart_dec(rec->filesize); + uart_puts(" byte"); + if (rec->filesize > 1) uart_putc('s'); + uart_putc(NEWLINE); + + fptr += rec->headsize + rec->datasize; + kfree(rec->pathname, 1); + kfree(rec, 1); + } +} + +void initramfs_cat(const char *filename) { + char *fptr = (char *)initrd_start; + + while (memcmp(fptr + sizeof(cpio_t), "TRAILER!!!", 10)) { + ramfsRec *rec = ramfsNext(fptr); + if (!strcmp(filename, rec->pathname)) { + // Dump its content + uart_putc(NEWLINE); + for (char *c = fptr + rec->headsize; + c < fptr + rec->headsize + rec->filesize; c++) { + uart_putc(*c); + } + uart_putc(NEWLINE); + uart_putc(NEWLINE); + kfree(rec->pathname, 1); + kfree(rec, 1); + return; + } + fptr += rec->headsize + rec->datasize; + kfree(rec->pathname, 1); + kfree(rec, 1); + } + + uart_puts("File not found."); + uart_putc(NEWLINE); +} + +void initramfs_run(const char *filename) { + char *fptr = (char *)initrd_start; + + while (memcmp(fptr + sizeof(cpio_t), "TRAILER!!!", 10)) { + ramfsRec *rec = ramfsNext(fptr); + if (!strcmp(filename, rec->pathname)) { + // Load the user program + void *program = kmalloc(rec->filesize, 0); + memcpy(program, fptr + rec->headsize, rec->filesize); + + thread_struct *thread = kcreate_thread((void *)program); + thread->user_stack = kmalloc(STACK_SIZE, 0); + asm volatile( + "msr tpidr_el1, %0\n" + "msr spsr_el1, %1\n" + "msr elr_el1, %2\n" + "msr sp_el0, %3\n" + "mov sp, %4\n" + "eret\n" + : + : "r"(thread), // 0 thread_struct + "r"(0x340), // 1 + "r"(thread->context.lr), // 2 link register + "r"(thread->user_stack + STACK_SIZE), // 3 + "r"(thread->context.sp) // 4 + :); + return; + } + + fptr += rec->headsize + rec->datasize; + kfree(rec->pathname, 1); + kfree(rec, 1); + } + + uart_puts("File not found."); + uart_putc(NEWLINE); +} + +// using current thread to run the loaded program +void initramfs_sys_exec(const char *target, trap_frame *tf) { + char *fptr = (char *)initrd_start; + + while (memcmp(fptr + sizeof(cpio_t), "TRAILER!!!", 10)) { + ramfsRec *rec = ramfsNext(fptr); + if (!strcmp(target, rec->pathname)) { + void *program = kmalloc(rec->filesize, 0); + memcpy(program, fptr + rec->headsize, rec->filesize); + thread_struct *thread = get_current(); + while (thread->sig_busy); // wait for signal handling + thread->sig_reg = 0; + memset(thread->sig_handlers, 0, sizeof(thread->sig_handlers)); + thread->context.lr = (unsigned long)program; // change to elr + thread->context.sp = (uintptr_t)thread->stack + STACK_SIZE; + thread->context.fp = (uintptr_t)thread->stack + STACK_SIZE; + tf->elr_el1 = (unsigned long)program; + if (thread->user_stack == 0) thread->user_stack = kmalloc(STACK_SIZE, 0); + tf->sp_el0 = (uintptr_t)thread->user_stack + STACK_SIZE; + return; + } + fptr += rec->headsize + rec->datasize; + } + uart_puts("File not found.\n"); +} \ No newline at end of file diff --git a/lab5/src/irq.c b/lab5/src/irq.c new file mode 100644 index 000000000..01f50c775 --- /dev/null +++ b/lab5/src/irq.c @@ -0,0 +1,96 @@ +#include "irq.h" + +#include "mem.h" +#include "scheduler.h" +#include "timer.h" +#include "uart.h" + +static irqTask *head = 0; + +// Disable interrupt before calling irq_add_task() +void irq_add_task(void (*callback)(), int order) { + irqTask *task = (irqTask *)kmalloc(sizeof(irqTask), 1); + task->func = callback; + task->order = order; + task->busy = 0; + task->prev = 0; + task->next = 0; + + // 0 -> task -> head -> ... + if (head == 0 || task->order < head->order) { + task->next = head; + task->prev = 0; + if (head != 0) head->prev = task; + head = task; + return; + } + + irqTask *current = head; + while (current->next != 0 && current->next->order <= task->order) + current = current->next; + task->next = current->next; + if (current->next != 0) current->next->prev = task; + current->next = task; + task->prev = current; +} + +// to enable interrupts in EL1 +void enable_interrupt() { + asm volatile( + "msr DAIFClr, 0xF\n" // Clear the D, A, I, F bits in the DAIF register + ); +} + +// to disable interrupts in EL1 +void disable_interrupt() { + asm volatile( + "msr DAIFSet, 0xF\n" // Set the D, A, I, F bits to 1 in the DAIF register + ); +} + +void irq_entry(trap_frame *tf) { + disable_interrupt(); // Enter the critical section + + if (*IRQ_PENDING_1 & (1 << 29)) { // UART interrupt + switch (*AUX_MU_IIR & 0x6) { // 0x6 = 0110 -> Get 0x2 and 0x4 + + case 0x2: // 0x2 UART Transmit interrupt + disable_uart_tx_interrupt(); + irq_add_task(uart_tx_irq_handler, ORDER_LAST); + break; + + case 0x4: // 0x4 UART Receive interrupt + disable_uart_rx_interrupt(); + irq_add_task(uart_rx_irq_handler, ORDER_FIRST); + break; + } + } else if (*CORE0_INTERRUPT_SOURCE & 0x2) { + // Core 0 timer interrupt for schedule() + // tell if thread_struct is not the only one in the run_queue + if (get_current() != get_current()->next) schedule(); + + disable_timer_interrupt(); + irq_add_task(timer_irq_handler, ORDER_REGULAR); + } + + enable_interrupt(); // Leave the critical section + + // Preemption: run the task with the highest priority + while (head != 0 && !head->busy) { + disable_interrupt(); + irqTask *task = head; // Get a task from head + task->busy = 1; // Flag the task as under processing + enable_interrupt(); + + task->func(); // Run the tasks with interrupts enabled + + // Remove the task + disable_interrupt(); + if (task->prev != 0) task->prev->next = task->next; + if (task->next != 0) task->next->prev = task->prev; + if (task == head) head = task->next; + kfree((void *)task, 1); + enable_interrupt(); + } + do_signal(tf); +} \ No newline at end of file diff --git a/lab5/src/linker.ld b/lab5/src/linker.ld new file mode 100644 index 000000000..1f2d111af --- /dev/null +++ b/lab5/src/linker.ld @@ -0,0 +1,13 @@ +SECTIONS +{ + . = 0x80000; + .text : { *(.text.boot) *(.text) } + .rodata : { *(.rodata) } + .data : { *(.data) } + .bss : { + __bss_start = .; + *(.bss) + __bss_end = .; + } +} +__bss_size = SIZEOF(.bss); \ No newline at end of file diff --git a/lab5/src/main.c b/lab5/src/main.c new file mode 100644 index 000000000..3fb01ac2f --- /dev/null +++ b/lab5/src/main.c @@ -0,0 +1,26 @@ +#include "devtree.h" +#include "initramfs.h" +#include "irq.h" +#include "mem.h" +#include "scheduler.h" +#include "shell.h" +#include "timer.h" +#include "uart.h" + +int main() { + /* Initialization */ + init_uart(); + fdt_traverse(initramfs_callback); + init_mem(); + + uart_log(INFO, "enable_interrupt()\n"); + enable_interrupt(); + uart_log(INFO, "init_timer()\n"); + init_timer(); + uart_log(INFO, "sched_init()\n"); + sched_init(); + + run_shell(); + + return 0; +} \ No newline at end of file diff --git a/lab5/src/mbox.c b/lab5/src/mbox.c new file mode 100644 index 000000000..281d4cd28 --- /dev/null +++ b/lab5/src/mbox.c @@ -0,0 +1,39 @@ +#include "mbox.h" + +// volatile unsigned int __attribute__((aligned(16))) mbox[36]; + +int mbox_call(unsigned char ch, unsigned int *mbox) { + unsigned int r = (unsigned int)((unsigned long)mbox & ~0xF) | (ch & 0xF); + // Wait until we can write to the mailbox + while (*MAILBOX_REG_STATUS & MAILBOX_FULL); + *MAILBOX_REG_WRITE = r; // Write the request + while (1) { + // Wait for the response + while (*MAILBOX_REG_STATUS & MAILBOX_EMPTY); + if (r == *MAILBOX_REG_READ) return mbox[1] == MAILBOX_RESPONSE; + } + return 0; +} + +int get_board_revision(unsigned int *mbox) { + mbox[0] = 7 * 4; + mbox[1] = REQUEST_CODE; + mbox[2] = TAGS_HARDWARE_BOARD_REVISION; + mbox[3] = 4; + mbox[4] = TAG_REQUEST_CODE; + mbox[5] = 0; + mbox[6] = END_TAG; + return mbox_call(MAILBOX_CH_PROP, mbox); +} + +int get_arm_memory_status(unsigned int *mbox) { + mbox[0] = 8 * 4; + mbox[1] = REQUEST_CODE; + mbox[2] = TAGS_HARDWARE_ARM_MEM; + mbox[3] = 8; + mbox[4] = TAG_REQUEST_CODE; + mbox[5] = 0; + mbox[6] = 0; + mbox[7] = END_TAG; + return mbox_call(MAILBOX_CH_PROP, mbox); +} \ No newline at end of file diff --git a/lab5/src/mem.c b/lab5/src/mem.c new file mode 100644 index 000000000..65f0bd82e --- /dev/null +++ b/lab5/src/mem.c @@ -0,0 +1,374 @@ +#include "mem.h" + +#include "command.h" +#include "devtree.h" +#include "mbox.h" +#include "str.h" +#include "uart.h" +#include "utils.h" + +extern char *__bss_end; +extern void *DTB_BASE; +extern void *DTB_END; +extern void *initrd_start; +extern void *initrd_end; + +unsigned int BOARD_REVISION; +unsigned int BASE_MEMORY; +unsigned int NUM_PAGES; + +static struct page *pageTable; +static struct page *freeList[BUDDY_MAX_ORDER + 1]; +static struct object *objectCache[CACHE_MAX_ORDER + 1]; + +extern char *__bss_end; // `__bss_end` is defined in linker script + +static char *heap_top; + +static void *simple_malloc(int size) { + void *p = (void *)heap_top; + if (size < 0) return 0; + heap_top += size; + return p; +} + +void init_mem() { + // Simple malloc init + // Set heap base address + heap_top = (char *)&__bss_end; + uart_log(INFO, "Simple initialized.\n"); + + unsigned int __attribute__((aligned(16))) mbox[36]; + + // Get board revision + if (BOARD_REVISION == 0) { + get_board_revision(mbox); + BOARD_REVISION = mbox[5]; + } + // Get ARM memory base address and size + if (NUM_PAGES == 0) { + get_arm_memory_status(mbox); + BASE_MEMORY = mbox[5]; + NUM_PAGES = (mbox[6] - BASE_MEMORY) / PAGE_SIZE; + } + + cmd_info(); + + // Initialize the buddy allocator + pageTable = simple_malloc(sizeof(struct page) * NUM_PAGES); + unsigned int current_order = BUDDY_MAX_ORDER; + for (int i = 0; i < NUM_PAGES; i++) { + pageTable[i].order = 0; + pageTable[i].used = 0; + pageTable[i].cache_order = -1; + pageTable[i].prev = 0; + pageTable[i].next = 0; + if (i % (1 << current_order) == 0) { + while (current_order > 0 && NUM_PAGES - i < (1 << current_order)) { + current_order--; + } + pushPageToFreeList(&freeList[current_order], &pageTable[i], + current_order); + } + } + unsigned int page_table = NUM_PAGES * sizeof(struct page) * 2; + + // Reserve memory: + // Spin tables for multicore boot + reserveMemory(0x0, 0x1000); + // Kernel & stack; Simple allocator + reserveMemory(0x80000 - STACK_SIZE, (unsigned long)&__bss_end + page_table); + // Initramfs + reserveMemory((uint64_t)initrd_start, (uint64_t)initrd_end); + // Devicetree + reserveMemory((uint64_t)DTB_BASE, (uint64_t)DTB_END); +} + +void reserveMemory(uint64_t resv_start, uint64_t resv_end) { + // Round the start and end addresses to the page boundary + resv_start = resv_start & ~(PAGE_SIZE - 1); + resv_end = (resv_end + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); + resv_end--; + + uart_log(INFO, "Reserving memory: "); + uart_hex(resv_start); + uart_putc('-'); + uart_hex(resv_end); + uart_putc(NEWLINE); + + for (int order = BUDDY_MAX_ORDER; order >= 0; order--) { + struct page *current = freeList[order]; + while (current != 0) { + struct page *next = current->next; + uint64_t page_start = (current - pageTable) * PAGE_SIZE; + uint64_t page_end = page_start + (PAGE_SIZE << order) - 1; + if (page_start >= resv_start && page_end <= resv_end) { + // [page page] + // Remove the page from the free list + current->used = 1; + removePageFromFreeList(&freeList[order], current); + } else if (resv_start > page_end || resv_end < page_start) { + // ---resv> [page or page] or order = order; + page->used = 0; + page->prev = 0; + page->next = 0; + + if (*list_head == 0 || (*list_head) < page) { + if (*list_head != 0) (*list_head)->prev = page; + page->next = *list_head; + *list_head = page; + return; + } + + struct page *current = *list_head; + while (current->next != 0 && page < current->next) { + current = current->next; + } + page->prev = current; + page->next = current->next; + if (current->next != 0) current->next->prev = page; + current->next = page; +} + +struct page *popFreeList(struct page **list_head) { + if (*list_head == 0) return 0; + + struct page *page = *list_head; + *list_head = page->next; + page->used = 1; + return page; +} + +void removePageFromFreeList(struct page **list_head, struct page *page) { + if (page->prev != 0) page->prev->next = page->next; + if (page->next != 0) page->next->prev = page->prev; + if (page == *list_head) *list_head = page->next; +} + +void printFreeListByOrder(unsigned int order) { + struct page *page = freeList[order]; + if (page > 0) uart_log(BUDD, ""); + while (page != 0) { + uart_putc(TAB); + uart_hex((unsigned int)(page - pageTable) * PAGE_SIZE); + uart_puts("-"); + uart_hex((unsigned int)(page - pageTable + (1 << order)) * PAGE_SIZE - 1); + uart_puts(" ["); + if (order < 10) uart_putc(' '); + uart_dec(order); + uart_puts("]\n"); + page = page->next; + } +} + +struct page *lookupBuddy(struct page *page, unsigned int order) { + unsigned int buddy_pfn = (unsigned int)(page - pageTable) ^ (1 << order); + return &pageTable[buddy_pfn]; +} + +struct page *allocatePagesByOrder(unsigned int order, int silent) { + if (!silent) { + uart_log(BUDD, "memory block requested: "); + uart_dec(1 << order); + uart_puts(" page(s).\n"); + } + + for (int i = order; i <= BUDDY_MAX_ORDER; i++) { + if (freeList[i] == 0) // No free page available + continue; // Try next order + struct page *page = popFreeList(&freeList[i]); + page->order = order; // Update order of the page + + while (i > order) { // requires splitting + i--; + struct page *buddy = lookupBuddy(page, i); + pushPageToFreeList(&freeList[i], buddy, i); + + if (silent) continue; + // Print information + unsigned int pfn = page - pageTable; + unsigned int buddy_pfn = buddy - pageTable; + uart_log(BUDD, "Split "); + uart_hex(pfn * PAGE_SIZE); + uart_puts("-"); + uart_hex((pfn + (1 << i)) * PAGE_SIZE - 1); + uart_puts("//"); + uart_hex(buddy_pfn * PAGE_SIZE); + uart_puts("-"); + uart_hex((buddy_pfn + (1 << i)) * PAGE_SIZE - 1); + uart_puts(" => ["); + uart_dec(i); + uart_puts("]\n"); + } + if (!silent) { + uart_log(BUDD, "Memory allocated: "); + uart_hex((unsigned int)(page - pageTable) * PAGE_SIZE); + uart_puts("-"); + uart_hex((unsigned int)(page - pageTable + (1 << order)) * PAGE_SIZE - 1); + uart_putc(NEWLINE); + } + + return page; + } + return 0; +} + +void freePages(struct page *page, unsigned int order, int silence) { + if (!silence) { + uart_log(BUDD, "Free "); + uart_dec(1 << order); + uart_puts("-page memory block starting from "); + uart_hex((unsigned int)(page - pageTable) * PAGE_SIZE); + uart_putc(NEWLINE); + } + mergePages(page, order, silence); +} + +void mergePages(struct page *page, unsigned int order, int silence) { + struct page *current = page; + while (order < BUDDY_MAX_ORDER) { + struct page *buddy = lookupBuddy(current, order); + if (buddy->order != order || buddy->used == 1) break; + + removePageFromFreeList(&freeList[order], buddy); + + if (current > buddy) { + struct page *tmp = current; + current = buddy; + buddy = tmp; + } + + order++; + if (silence) continue; + unsigned int pfn = current - pageTable; + unsigned int buddy_pfn = buddy - pageTable; + uart_log(BUDD, "Merge "); + uart_hex(pfn * PAGE_SIZE); + uart_puts("-"); + uart_hex((pfn + (1 << (order - 1))) * PAGE_SIZE - 1); + uart_puts("~~"); + uart_hex(buddy_pfn * PAGE_SIZE); + uart_puts("-"); + uart_hex((buddy_pfn + (1 << (order - 1))) * PAGE_SIZE - 1); + uart_puts(" => ["); + uart_dec(order); + uart_puts("]\n"); + } + pushPageToFreeList(&freeList[order], current, order); +} + +/* Cache Allocator */ + +void pushObjectToList(struct object **list_head, struct object *object, + unsigned int order) { + object->order = order; + object->next = *list_head; + *list_head = object; +} + +struct object *popObjectFromList(struct object **list_head) { + if (*list_head == 0) return 0; + + struct object *object = *list_head; + *list_head = object->next; + return object; +} + +void *allocateCacheMemory(unsigned int order, int silent) { + if (!silent) { + uart_log(CACH, "Allocating "); + uart_dec(MIN_CACHE_SIZE << order); + uart_puts(" bytes."); + uart_putc(NEWLINE); + } + + if (objectCache[order] == 0) { + struct page *page = allocatePagesByOrder(0, silent); + page->cache_order = order; + unsigned int page_addr = (page - pageTable) * PAGE_SIZE; + unsigned int cache_size = MIN_CACHE_SIZE << order; + for (int i = 0; i < PAGE_SIZE; i += cache_size) { + struct object *obj = (struct object *)(uintptr_t)(page_addr + i); + pushObjectToList(&objectCache[order], obj, order); + } + } + void *p = popObjectFromList(&objectCache[order]); + if (!silent) { + uart_log(CACH, "Allocated memory starting from "); + uart_hex((uintptr_t)p); + uart_putc(NEWLINE); + } + return p; +} + +void freeCacheEntry(void *ptr, unsigned int index, int silence) { + if (!silence) { + uart_log(CACH, "Free memory cache of "); + uart_dec(MIN_CACHE_SIZE << index); + uart_puts(" bytes starting from "); + uart_hex((uintptr_t)ptr); + uart_putc(NEWLINE); + } + pushObjectToList(&objectCache[index], ptr, index); +} + +/* Dynamic Memory Allocator */ + +void *kmalloc(unsigned int size, int silent) { + if (size == 0) return 0; + + if (!silent) { + uart_log(INFO, "Memory requested: "); + uart_dec(size); + uart_puts(" byte(s)."); + uart_putc(NEWLINE); + } + + if (size > PAGE_SIZE / 2) { + // Buddy Allocator + int order = 0; + while ((PAGE_SIZE << order) < size) order++; + struct page *page = allocatePagesByOrder(order, silent); + return (void *)((page - pageTable) * PAGE_SIZE); + } else { + // Cache Allocator + int power = 0; + while ((1 << power) < size) power++; + int order = (power > 5) ? power - 5 : 0; + return allocateCacheMemory(order, silent); + } +} + +void kfree(void *ptr, int silence) { + // Check if the pointer is page-aligned + struct page *page = &pageTable[(uintptr_t)ptr / PAGE_SIZE]; + if ((uintptr_t)ptr % PAGE_SIZE == 0) { + // Check if the page is allocated by the buddy allocator + if (page->cache_order == -1) { + // Free the page using the buddy allocator + freePages(page, page->order, silence); + return; + } + } + // Free the object using the cache allocator + struct object *object = ptr; + freeCacheEntry(object, page->cache_order, silence); +} diff --git a/lab5/src/scheduler.S b/lab5/src/scheduler.S new file mode 100644 index 000000000..27f0ec45c --- /dev/null +++ b/lab5/src/scheduler.S @@ -0,0 +1,47 @@ +.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 // set current thread_struct + ret + + /* + struct context_struct { + unsigned long x19; + unsigned long x20; + unsigned long x21; + unsigned long x22; + unsigned long x23; + unsigned long x24; + unsigned long x25; + unsigned long x26; + unsigned long x27; + unsigned long x28; + unsigned long fp; + unsigned long lr; + unsigned long sp; + }; + */ + + + +.global get_current +get_current: + mrs x0, tpidr_el1 // current thread_struct + ret + diff --git a/lab5/src/scheduler.c b/lab5/src/scheduler.c new file mode 100644 index 000000000..ac929387a --- /dev/null +++ b/lab5/src/scheduler.c @@ -0,0 +1,128 @@ +#include "scheduler.h" + +#include "mem.h" +#include "uart.h" +#include "utils.h" + +static int IDLE_PROCESS_PID; +static int thread_count = 0; +thread_struct *run_circle; + +void sched_init() { + uart_log(INFO, "Initializing Scheduler...\n"); + IDLE_PROCESS_PID = kcreate_thread(idle)->pid; + asm volatile( + "msr tpidr_el1, %0" ::"r"(run_circle) // Software Thread ID Register + ); + uart_log(INFO, "Scheduler Initialized.\n"); +} + +static void add_thread(thread_struct **queue, thread_struct *thread) { + if (*queue == 0) { + *queue = thread; + thread->next = thread; + thread->prev = thread; + } else { + // prev <- thread -> *queue + thread->next = *queue; + thread->prev = (*queue)->prev; + // prev -> thread <- *queue + (*queue)->prev->next = thread; + (*queue)->prev = thread; + } +} + +thread_struct *kcreate_thread(void (*func)()) { + thread_struct *thread = kmalloc(sizeof(thread_struct), 0); + thread->pid = thread_count++; + thread->state = RUNNING; + thread->stack = kmalloc(STACK_SIZE, 0); + thread->user_stack = 0; // alloc when needed (el0) + memset(thread->sig_handlers, 0, sizeof(thread->sig_handlers)); + thread->sig_reg = 0; + thread->sig_busy = 0; + thread->context.lr = (unsigned long)func; + thread->context.sp = (unsigned long)thread->stack + STACK_SIZE; + thread->context.fp = (unsigned long)thread->stack + STACK_SIZE; + add_thread(&run_circle, thread); + return thread; +} + +static void remove_thread(thread_struct **queue, thread_struct *thread) { + if (*queue == thread) *queue = (thread->next == thread) ? 0 : thread->next; + thread->next->prev = thread->prev; + thread->prev->next = thread->next; +} + +void list_tcircle() { + thread_struct *thread = run_circle; + uart_log(INFO, "All thread(s) in queue:\n"); + do { + uart_log(INFO, "pid = "); + uart_dec(thread->pid); + if (thread->sig_handlers[9] > 0) uart_putc('*'); + uart_puts(", sp: "); + uart_hex((uintptr_t)thread->context.sp); + if (thread->pid == get_current()->pid) uart_puts(" <- current"); + uart_putc(NEWLINE); + thread = thread->next; + } while (thread != run_circle); +} + +void schedule() { switch_to(get_current(), get_current()->next); } + +void kill_zombies() { + thread_struct *next, *thread = run_circle; + do { + next = thread->next; + if (thread->state == DEAD) { + remove_thread(&run_circle, thread); + kfree(thread->stack, 0); + if (thread->user_stack) kfree(thread->user_stack, 0); + kfree(thread, 0); + } + thread = next; + } while (thread != run_circle); +} + +void idle() { + while (1) { + kill_zombies(); + schedule(); + } +} + +thread_struct *get_thread_by_pid(int pid) { + thread_struct *thread = run_circle; + do { + if (thread->pid == pid) return thread; + thread = thread->next; + } while (thread != run_circle); + return 0; +} + +void kill_current_thread() { + get_current()->state = DEAD; + schedule(); +} + +void kill_thread_by_pid(int pid) { + if (pid == IDLE_PROCESS_PID) { + uart_log(WARN, "Cannot kill idle process.\n"); + } else { + thread_struct *thread = run_circle; + do { + if (thread->pid == pid) { + uart_log(INFO, "Killing pid "); + uart_dec(pid); + uart_putc(NEWLINE); + thread->state = DEAD; + schedule(); + return; + } + thread = thread->next; + } while (thread != run_circle); + uart_log(WARN, "Nothing to kill.\n"); + } + schedule(); +} diff --git a/lab5/src/shell.c b/lab5/src/shell.c new file mode 100644 index 000000000..85ed4902d --- /dev/null +++ b/lab5/src/shell.c @@ -0,0 +1,84 @@ +#include "shell.h" + +#include "command.h" +#include "mem.h" +#include "str.h" +#include "uart.h" + +static void welcome_msg() { + cmd_info(); + uart_puts( + "*******************************\n" + "*** YADOS 0.04 for OSC 2024 ***\n" + "*******************************\n" + "Hopefully this will be Yet Another Dope OS!\n"); + uart_putc(NEWLINE); + cmd_hello(); +} + +void run_shell() { + welcome_msg(); + + char *buffer = kmalloc(SHELL_BUF_SIZE, 1); + while (1) { + uart_putc(NEWLINE); + uart_puts("# "); + read_user_input(buffer); + if (exec_command(buffer)) { + uart_log(WARN, "Command not found.\n"); + } + } +} + +void read_user_input(char *buf) { + int idx = 0; + while (idx < SHELL_BUF_SIZE) { + char c = uart_getc(); + switch (c) { + case NEWLINE: + uart_putc(NEWLINE); + buf[idx] = '\0'; + return; + case DELETE: + if (idx > 0) { + idx--; + uart_putc(BACKSPACE); + uart_putc(' '); + uart_putc(BACKSPACE); + } + break; + case ESC: + uart_putc(NEWLINE); + uart_log(WARN, "ESC detected. Clearing buffer...\n"); + uart_log(INFO, "Press Spacebar or Enter to continue.\n"); + while (1) { + char c = uart_getc(); + if (c == ' ' || c == NEWLINE) break; + } + buf[0] = '\0'; + uart_clear(); + return; + default: + if (c >= 32 && c <= 126) { + uart_putc(c); + buf[idx++] = c; + } + } + } + uart_putc(NEWLINE); + uart_log(WARN, "Buffer overflow. Please re-enter your command.\n"); + buf[0] = '\0'; +} + +int exec_command(const char *input) { + if (strlen(input) == 0) return 0; + struct command *cmd = cmd_list; + while (strcmp(cmd->name, END_OF_COMMAND_LIST)) { + if (!strcmp(cmd->name, input)) { + cmd->func(); + return 0; + } + cmd++; + } + return -1; +} \ No newline at end of file diff --git a/lab5/src/signals.c b/lab5/src/signals.c new file mode 100644 index 000000000..3bbf55773 --- /dev/null +++ b/lab5/src/signals.c @@ -0,0 +1,52 @@ +#include "signals.h" + +#include "mem.h" +#include "scheduler.h" +#include "syscalls.h" +#include "traps.h" +#include "uart.h" +#include "utils.h" + +void signal(int SIGNAL, void (*handler)()) { + get_current()->sig_handlers[SIGNAL] = handler; +} + +void signal_kill(int pid, int SIGNAL) { + thread_struct *thread = get_thread_by_pid(pid); + if (thread == 0) { + uart_log(WARN, "No one gets the signal.\n"); + return; + }; + thread->sig_reg |= 1 << (SIGNAL - 1); // Set the signal pending bit +} + +void do_signal(trap_frame *tf) { + // Prevent nested signal handling + if (get_current()->sig_busy) return; + int sig_num = 1; + while (get_current()->sig_reg) { + if (get_current()->sig_reg & (0x1 << (sig_num - 1))) { + get_current()->sig_busy = 1; // block other signal handling + get_current()->sig_reg &= ~(0x1 << (sig_num - 1)); + + if (get_current()->sig_handlers[sig_num] == 0) { + kill_current_thread(); // Default handler: (exit the process) + get_current()->sig_busy = 0; + return; // Jump to the previous context (user program) after eret + } + + // Save the sigframe + get_current()->sig_tf = kmalloc(sizeof(trap_frame), 0); + memcpy(get_current()->sig_tf, tf, sizeof(trap_frame)); + get_current()->sig_stack = kmalloc(STACK_SIZE, 0); + // will be releases in sys_sigreturn() + tf->x30 = (uintptr_t) + sigreturn; // Return to sigreturn (traps.S) after signal handling + tf->spsr_el1 = 0x340; + tf->elr_el1 = (unsigned long)get_current()->sig_handlers[sig_num]; + tf->sp_el0 = (unsigned long)get_current()->sig_stack + STACK_SIZE; + return; // eret to the signal handler (->el0) + } + sig_num++; + } +} \ No newline at end of file diff --git a/lab5/src/start.S b/lab5/src/start.S new file mode 100644 index 000000000..c6b255ecf --- /dev/null +++ b/lab5/src/start.S @@ -0,0 +1,50 @@ +.section ".text.boot" + +.global _start + +_start: + /* get dtb address from x0 */ + ldr x1, =DTB_BASE // defined in devtree.c + str x0, [x1] + + /* get cpu id */ + mrs x1, mpidr_el1 /* Multiprocessor Affinity Register */ + and x1, x1, #3 + cbnz x1, halt // halt if cpu id != 0 + + /* set exception vector table */ + adr x0, exception_vector_table // defined in traps.S + msr vbar_el1, x0 + + /* switch from EL2 to EL1 */ + bl from_el2_to_el1 + + /* set stack pointer */ + ldr x1, =_start + mov sp, x1 + + /* clear bss section */ + ldr x1, =__bss_start + ldr w2, =__bss_size + +bss_reset: + cbz w2, run_main + str xzr, [x1], #8 + sub w2, w2, #1 + cbnz w2, bss_reset + +run_main: + /* branch to main function */ + bl main + +halt: + wfe + b halt + +from_el2_to_el1: + mov x0, (1 << 31) + msr hcr_el2, x0 // EL1 uses aarch64 + mov x0, 0x3C5 // ... 0011 1100 0101 + msr spsr_el2, x0 + msr elr_el2, lr + eret // return to EL1 diff --git a/lab5/src/str.c b/lab5/src/str.c new file mode 100644 index 000000000..e8d54f053 --- /dev/null +++ b/lab5/src/str.c @@ -0,0 +1,30 @@ +#include "str.h" + +int strcmp(const char *str1, const char *str2) { + while (*str1 && *str1 == *str2) str1++, str2++; + return *(unsigned char *)str1 - *(unsigned char *)str2; +} + +char *strncpy(char *dest, const char *src, int n) { + while (n-- && (*dest++ = *src++)); + return dest; +} + +int strlen(const char *str) { + int len = 0; + while (*str++ != '\0') len++; + return len; +} + +char *strcat(char *dest, const char *src) { + char *d = dest; + + // Move the pointer to the end of the dest string + while (*d != '\0') d++; + // Copy the src string to the end of the dest string + while (*src != '\0') *d++ = *src++; + // Add the null terminator + *d = '\0'; + + return dest; +} diff --git a/lab5/src/syscalls.c b/lab5/src/syscalls.c new file mode 100644 index 000000000..4188f4848 --- /dev/null +++ b/lab5/src/syscalls.c @@ -0,0 +1,77 @@ +#include "syscalls.h" + +#include "initramfs.h" +#include "irq.h" +#include "mbox.h" +#include "mem.h" +#include "scheduler.h" +#include "str.h" +#include "traps.h" +#include "uart.h" +#include "utils.h" + +int sys_getpid() { return get_current()->pid; } + +size_t sys_uart_read(char *buf, size_t size) { + int i = 0; + while (i < size) buf[i++] = uart_getc(); + return i; +} + +size_t sys_uart_write(const char *buf, size_t size) { + int i = 0; + while (i < size) uart_putc(buf[i++]); + return i; + return size; +} + +int sys_exec(const char *name, trap_frame *tf) { + initramfs_sys_exec(name, tf); + return 0; +} + +int sys_fork(trap_frame *tf) { + disable_interrupt(); // prevent schedule premature fork + + thread_struct *parent = get_current(); + thread_struct *child = kcreate_thread(0); + + // Handling kernel stack (incl. tf from parent) + memcpy(child->stack, parent->stack, STACK_SIZE); + // get child's trap frame from kstack via parent's offset + size_t ksp_offset = (uintptr_t)tf - (uintptr_t)parent->stack; + trap_frame *child_tf = (trap_frame *)(child->stack + ksp_offset); + child->context.sp = (uintptr_t)child_tf; // set child's ksp + + // Handling user stack + if (parent->user_stack) { + child->user_stack = kmalloc(STACK_SIZE, 0); + memcpy(child->user_stack, parent->user_stack, STACK_SIZE); + size_t usp_offset = tf->sp_el0 - (uintptr_t)parent->user_stack; + child_tf->sp_el0 = (uintptr_t)child->user_stack + usp_offset; + } + + // Copy signal handlers + memcpy(child->sig_handlers, parent->sig_handlers, + sizeof(parent->sig_handlers)); + + child->context.lr = (uintptr_t)child_ret_from_fork; // traps.S + child_tf->x0 = 0; // ret value of fork() for child + + enable_interrupt(); + return child->pid; +} + +void sys_exit(int status) { + uart_log(status ? WARN : INFO, "Exiting process ...\n"); + kill_current_thread(); +} + +void sys_sigreturn(trap_frame *tf) { + // Restore the sigframe + memcpy(tf, get_current()->sig_tf, sizeof(trap_frame)); + kfree(get_current()->sig_tf, 0); + kfree(get_current()->sig_stack, 0); + get_current()->sig_busy = 0; + return; // Jump to the previous context (user program) after eret +} diff --git a/lab5/src/test.c b/lab5/src/test.c new file mode 100644 index 000000000..344d70f74 --- /dev/null +++ b/lab5/src/test.c @@ -0,0 +1,137 @@ +#include "test.h" + +#include "mem.h" +#include "scheduler.h" +#include "syscalls.h" +#include "timer.h" +#include "uart.h" + +/* System Call Test */ + +static int get_pid() { + int pid = -1; + asm volatile( + "mov x7, 255\n" + "mov x8, 0\n" + "svc 0\n" + "mov %0, x0\n" + : "=r"(pid) + : + : "x8", "x0"); + return pid; +} + +static int fork() { + int ret = -1; + asm volatile( + "mov x8, 4\n" + "svc 0\n" + "mov %0, x0\n" + : "=r"(ret) + : + : "x8", "x0"); + return ret; +} + +static void exit() { + asm volatile( + "mov x0, 0\n" + "mov x8, 5\n" + "svc 0\n"); +} + +void thread_test() { + for (int i = 0; i < 10; ++i) { + uart_log(TEST, "Thread id: "); + uart_dec(get_current()->pid); + uart_puts(" "); + uart_dec(i); + uart_putc(NEWLINE); + + int *timeup = 0; + timer_add((void (*)(void *))set_timeup, (void *)timeup, 3); + while (!*timeup); + + schedule(); + } + kill_current_thread(); +} + +static void fork_test() { + uart_log(TEST, "Fork Test, pid "); + uart_dec(get_pid()); + uart_putc(NEWLINE); + int cnt = 1; + int ret = 0; + if ((ret = fork()) == 0) { // child + long long cur_sp; // force 64-bit aka uint64_t + asm volatile("mov %0, sp" : "=r"(cur_sp)); + uart_log(TEST, "first child pid: "); + uart_dec(get_pid()); + uart_puts(", cnt: "); + uart_dec(cnt); + uart_puts(", ptr: "); + uart_hex((uintptr_t)&cnt); + uart_puts(", sp: "); + uart_hex(cur_sp); + uart_putc(NEWLINE); + cnt++; + if ((ret = fork()) != 0) { + asm volatile("mov %0, sp" : "=r"(cur_sp)); + uart_log(TEST, "first child pid: "); + uart_dec(get_pid()); + uart_puts(", cnt: "); + uart_dec(cnt); + uart_puts(", ptr: "); + uart_hex((uintptr_t)&cnt); + uart_puts(", sp: "); + uart_hex(cur_sp); + uart_putc(NEWLINE); + } else { + while (cnt < 5) { + asm volatile("mov %0, sp" : "=r"(cur_sp)); + uart_log(TEST, "second child pid: "); + uart_dec(get_pid()); + uart_puts(", cnt: "); + uart_dec(cnt); + uart_puts(", ptr: "); + uart_hex((uintptr_t)&cnt); + uart_puts(", sp: "); + uart_hex(cur_sp); + uart_putc(NEWLINE); + int *timeup = 0; + timer_add((void (*)(void *))set_timeup, (void *)timeup, 3); + while (!*timeup); + + cnt++; + } + } + exit(); // child + } else { + uart_log(TEST, "parent here, pid "); + uart_dec(get_pid()); + uart_puts(", child "); + uart_dec(ret); + uart_putc(NEWLINE); + } + exit(); // parent +} + +void run_fork_test() { + if (get_current()->user_stack == 0) { + uart_log(TEST, "Allocate user_stack for Fork Test\n"); + get_current()->user_stack = kmalloc(STACK_SIZE, 1); + } + asm volatile( // el1 -> el0 + "msr spsr_el1, %0\n" + "msr elr_el1, %1\n" + "msr sp_el0, %2\n" + "mov sp, %3\n" + "eret\n" + : + : "r"(0x340), // 0 + "r"(fork_test), // 1 + "r"(get_current()->user_stack + STACK_SIZE), // 2 + "r"(get_current()->context.sp) // 3 + : "memory"); +} diff --git a/lab5/src/timer.c b/lab5/src/timer.c new file mode 100644 index 000000000..71a415f14 --- /dev/null +++ b/lab5/src/timer.c @@ -0,0 +1,111 @@ +#include "timer.h" + +#include "mem.h" +#include "str.h" +#include "uart.h" + +static timerEntry *head = 0; + +void init_timer() { + asm volatile( + "mov x0, 1\n" + "msr cntp_ctl_el0, x0\n"); + + uart_log(INFO, "enable_timer_interrupt()\n"); + enable_timer_interrupt(); + + asm volatile( + "mrs x0, cntfrq_el0\n" + "msr cntp_tval_el0, x0\n"); + + // required by lab5 + uint64_t tmp; + asm volatile("mrs %0, cntkctl_el1" : "=r"(tmp)); + tmp |= 1; + asm volatile("msr cntkctl_el1, %0" : : "r"(tmp)); +} + +void enable_timer_interrupt() { + // Unmask the timer interrupt + asm volatile( + "mov x0, 2\n" + "ldr x1, =%0\n" + "str w0, [x1]\n" + : + : "i"(CORE0_TIMER_IRQCNTL) + : "x0", "x1"); +} + +void disable_timer_interrupt() { + // Mask timer interrupt + asm volatile( + "mov x0, 0\n" + "ldr x1, =%0\n" + "str w0, [x1]\n" + : + : "i"(CORE0_TIMER_IRQCNTL) + : "x0", "x1"); +} + +void timer_irq_handler() { + // Set up the next timer interrupt (frequency >> 5, aka. 1/32 of the + // frequency) + asm volatile( + "mrs x0, cntfrq_el0\n" + "lsr x0, x0, 5\n" + "msr cntp_tval_el0, x0\n"); + + // Check the timer queue + while (head != 0 && timer_get_uptime() >= head->time) { + head->func(head->arg); // Execute the callback function + kfree(head, 0); // Free the memory allocated for the timer node + head = head->next; // Remove the head node after func finished + } + + enable_timer_interrupt(); +} + +uint64_t timer_get_uptime() { + uint64_t cntpct_el0 = 0, cntfrq_el0 = 0; + asm volatile( + "mrs %0, cntpct_el0\n" + "mrs %1, cntfrq_el0\n" + : "=r"(cntpct_el0), "=r"(cntfrq_el0)); + return cntpct_el0 / cntfrq_el0; +} + +void timer_add(void (*callback)(void *), void *arg, int duration) { + // Insert a new timer into the linked list (sorted by time) + timerEntry *timer = kmalloc(sizeof(timerEntry), 1); + timer->func = callback; + timer->arg = arg; + timer->time = timer_get_uptime() + duration; + timer->next = 0; + + if (head == 0 || timer->time < head->time) { + // Insert as the head of the list + timer->next = head; + head = timer; + return; + } + + timerEntry *current = head; + while (current->next != 0 && current->next->time <= timer->time) + current = current->next; + timer->next = current->next; + current->next = timer; +} + +static void show_timer_message(void *arg) { + uart_putc(NEWLINE); + uart_log(INFO, "Timer message: "); + uart_puts((char *)arg); + uart_putc(NEWLINE); + kfree(arg, 1); // Free the memory allocated for arg +} + +void set_timer(const char *msg, int duration) { + timer_add((void (*)(void *))show_timer_message, (void *)msg, duration); +} + +void set_timeup(int *timeup) { *timeup = 1; } diff --git a/lab5/src/traps.S b/lab5/src/traps.S new file mode 100644 index 000000000..e2df8226b --- /dev/null +++ b/lab5/src/traps.S @@ -0,0 +1,114 @@ +/* save general registers to stack */ +.macro save_all + sub sp, sp, 16 * 17 + 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 x10, spsr_el1 /* Saved Program Status Register (EL1) */ + mrs x11, elr_el1 /* Exception Link Register (EL1) */ + mrs x12, sp_el0 + stp x30, x10, [sp, 16 * 15] + stp x11, x12, [sp, 16 * 16] +.endm + +/* load general registers from stack */ +.macro load_all + ldp x30, x10, [sp, 16 * 15] + ldp x11, x12, [sp, 16 * 16] + msr spsr_el1, x10 + msr elr_el1, x11 + msr sp_el0, x12 + 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, 16 * 17 +.endm + +exception_handler: + save_all + mov x0, sp // pass the stack pointer to exception_entry + bl exception_entry // defined in traps.c + load_all + eret + +irq_handler: + save_all + mov x0, sp + bl irq_entry // defined in irq.c + load_all + eret + +invalid_exc_handler: + save_all + mrs x0, elr_el1 // Exception Link Register (EL1) + mrs x1, esr_el1 // ESR_EL1, Exception Syndrome Register (EL1) + mrs x2, spsr_el1 // Saved Program Status Register (EL1) + bl invalid_entry // defined in traps.c + load_all + eret + +.global child_ret_from_fork +child_ret_from_fork: + load_all // child's kernel stack (trap frame) + eret + +.global sigreturn +sigreturn: + mov x8, 139 + svc 0 + +.macro v_entry label + b \label +.align 7 +.endm + +/* EL1 Exception Vector table + * vector table should be aligned to 0x800 + * and each entry size is 0x80 */ +.align 11 +.global exception_vector_table +exception_vector_table: + // EL1 -> EL1 while using SP_EL0 + v_entry invalid_exc_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler + // EL1 -> EL1 while using SP_EL1 + v_entry invalid_exc_handler + v_entry irq_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler + // EL0 (AArch64) -> EL1 + v_entry exception_handler + v_entry irq_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler + // EL0 (AArch32) -> EL1 + v_entry invalid_exc_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler diff --git a/lab5/src/traps.c b/lab5/src/traps.c new file mode 100644 index 000000000..8cfcefe43 --- /dev/null +++ b/lab5/src/traps.c @@ -0,0 +1,134 @@ +#include "traps.h" + +#include + +#include "irq.h" +#include "mbox.h" +#include "mem.h" +#include "scheduler.h" +#include "syscalls.h" +#include "uart.h" + +extern thread_struct *run_circle; + +void print_registers(uint64_t elr, uint64_t esr, uint64_t spsr) { + // Print spsr_el1 + uart_log(INFO, "spsr_el1: "); + uart_hex(spsr); + uart_putc(NEWLINE); + + // Print elr_el1 + uart_log(INFO, "elr_el1: "); + uart_hex(elr); + uart_putc(NEWLINE); + + // Print esr_el1 + uart_log(INFO, "esr_el1: "); + uart_hex(esr); + uart_putc(NEWLINE); +} + +void exception_entry(trap_frame *tf) { + unsigned long elr, esr, spsr; + asm volatile( + "mrs %0, elr_el1\n" + "mrs %1, esr_el1\n" + "mrs %2, spsr_el1\n" + : "=r"(elr), "=r"(esr), "=r"(spsr) + : + : "memory"); + if (esr != 0x56000000) { + print_registers(elr, esr, spsr); + while (1); + } + + enable_interrupt(); // Prevent uart blocking the interrupt[?] + + unsigned int syscall_num = tf->x8; + // if (syscall_num > 2) { + // uart_log(INFO, "syscall_num: "); + // uart_dec(syscall_num); + // uart_putc(NEWLINE); + // } + switch (syscall_num) { + case 0: { // int getpid() + unsigned int test = tf->x7; + unsigned int pid = sys_getpid(); + tf->x0 = pid; + if (test != 255) list_tcircle(); + break; + } + case 1: { // size_t uartread(char buf[], size_t size) + char *buf = (char *)tf->x0; + size_t size = tf->x1; + tf->x0 = sys_uart_read(buf, size); + break; + } + case 2: { // size_t uartwrite(const char buf[], size_t size) + char *buf = (char *)tf->x0; + size_t size = tf->x1; + tf->x0 = sys_uart_write(buf, size); + } break; + case 3: // int exec(const char *name, char *const argv[]) + const char *name = (char *)tf->x0; + tf->x0 = sys_exec(name, tf); + break; + case 4: { // int fork() + tf->x0 = sys_fork(tf); + break; + } + case 5: { // void exit(int status) + int status = tf->x0; + sys_exit(status); + break; + } + case 6: { // int mbox_call(unsigned char ch, unsigned int *mbox) + unsigned char ch = tf->x0; + unsigned int *mbox = (unsigned int *)tf->x1; + tf->x0 = mbox_call(ch, mbox); + break; + } + case 7: { // void kill(int pid) + unsigned int pid = tf->x0; + kill_thread_by_pid(pid); + break; + } + case 8: { // signal(int SIGNAL, void (*handler)()) + unsigned int SIGNAL = tf->x0; + void (*handler)() = (void (*)())tf->x1; + signal(SIGNAL, handler); + break; + } + case 9: { // (signal_)kill(int pid, int SIGNAL) + unsigned int pid = tf->x0; + unsigned int SIGNAL = tf->x1; + uart_log(INFO, "Signal "); + uart_dec(SIGNAL); + uart_puts(" sent to pid "); + uart_dec(pid); + uart_putc(NEWLINE); + signal_kill(pid, SIGNAL); + break; + } + case 139: + sys_sigreturn(tf); + break; + default: + uart_log(ERR, "Invalid system call\n"); + } +} + +void invalid_entry(uint64_t elr, uint64_t esr, uint64_t spsr) { + uart_log(ERR, "The exception handler is not implemented\n"); + print_registers(elr, esr, spsr); + + if ((esr & 0xFC000000) == 0x94000000) { + uint32_t svc_number = esr & 0xFFFF; + uart_log(ERR, "SVC Call Exception. SVC Number: "); + uart_dec(svc_number); + uart_putc(NEWLINE); + } else { + uart_log(ERR, "Unknown Exception\n"); + } + while (1); +} \ No newline at end of file diff --git a/lab5/src/uart.c b/lab5/src/uart.c new file mode 100644 index 000000000..f71d0bee7 --- /dev/null +++ b/lab5/src/uart.c @@ -0,0 +1,193 @@ +#include "uart.h" + +#include + +#include "irq.h" +#include "mem.h" +#include "str.h" + +static int uart_read_idx = 0; +static int uart_write_idx = 0; +static char uart_read_buffer[UART_BUF_SIZE]; +static char uart_write_buffer[UART_BUF_SIZE]; + +void init_uart() { + // Configure GPIO pins + register unsigned int r = *GPFSEL1; + r &= ~((7 << 12) | (7 << 15)); + r |= (2 << 12) | (2 << 15); + *GPFSEL1 = r; + + // Disable GPIO pull up/down + *GPPUD = 0; + for (int i = 0; i < 150; i++); + *GPPUDCLK0 = (1 << 14) | (1 << 15); + for (int i = 0; i < 150; i++); + *GPPUD = 0; + *GPPUDCLK0 = 0; + + // Initialize mini UART + *AUX_ENABLE |= 1; // Enable mini UART + *AUX_MU_CNTL = 0; // Disable Tx and Rx during setup + *AUX_MU_IER = 0; // Disable interrupt + *AUX_MU_LCR = 3; // Set data size to 8 bits + *AUX_MU_MCR = 0; // Disable auto flow control + *AUX_MU_BAUD = 270; // Set baud rate to 115200 + *AUX_MU_IIR = 6; // No FIFO + *AUX_MU_CNTL = 3; // Enable Tx and Rx + + // Enable AUX interrupts + *ENABLE_IRQS_1 |= 1 << 29; +} + +char uart_getc() { + // Check the data ready field on bit 0 of AUX_MU_LSR_REG + while (!(*AUX_MU_LSR & 0x01)); + char c = (char)(*AUX_MU_IO); // Read from AUX_MU_IO_REG + return c == CARRIAGE_RETURN ? NEWLINE : c; +} + +void uart_putc(char c) { + // Add CARRIAGE_RETURN before NEWLINE + if (c == NEWLINE) uart_putc(CARRIAGE_RETURN); + + // Check the transmitter empty field on bit 5 of AUX_MU_LSR_REG + while (!(*AUX_MU_LSR & 0x20)); + *AUX_MU_IO = c; // Write to AUX_MU_IO_REG +} + +void uart_puts(const char *s) { + while (*s) { + uart_putc(*s++); + } +} + +void uart_clear() { uart_puts("\033[2J\033[H"); } + +void uart_hex(unsigned int h) { + uart_puts("0x"); + unsigned int n; + for (int c = 28; c >= 0; c -= 4) { + n = (h >> c) & 0xF; + n += n > 9 ? 0x37 : '0'; + uart_putc(n); + } +} + +void uart_simple_hex(unsigned int h) { + uart_puts("0x"); + if (h == 0) { + uart_putc('0'); + return; + } + char buf[10]; + int i = 0; + unsigned int n; + while (h > 0) { + n = h % 16; + buf[i++] = n + (n > 9 ? 0x37 : '0'); + h /= 16; + } + for (int j = i - 1; j >= 0; j--) { + uart_putc(buf[j]); + } +} + +void uart_dec(unsigned int d) { + if (d == 0) { + uart_putc('0'); + return; + } + char buf[10]; + int i = 0; + while (d > 0) { + buf[i++] = d % 10 + '0'; + d /= 10; + } + for (int j = i - 1; j >= 0; j--) { + uart_putc(buf[j]); + } +} + +void uart_log(int type, const char *msg) { + switch (type) { + case INFO: + uart_puts("[INFO] "); + break; + case TEST: + uart_puts("[TEST] "); + break; + case BUDD: + uart_puts("[BUDD] "); + break; + case CACH: + uart_puts("[CACH] "); + break; + case WARN: + uart_puts("[WARN] "); + break; + case ERR: + uart_puts("[ERR!] "); + break; + default: + uart_puts("[LOG?] "); + break; + } + uart_puts(msg); +} + +void enable_uart_tx_interrupt() { *AUX_MU_IER |= 0x02; } + +void disable_uart_tx_interrupt() { *AUX_MU_IER &= 0x01; } + +void enable_uart_rx_interrupt() { *AUX_MU_IER |= 0x01; } + +void disable_uart_rx_interrupt() { *AUX_MU_IER &= 0x2; } + +void uart_tx_irq_handler() { + disable_uart_tx_interrupt(); + // Buffer is not empty -> send characters + if (uart_write_idx < UART_BUF_SIZE && + uart_write_buffer[uart_write_idx] != 0) { + while (!(*AUX_MU_LSR & 0x20)); + *AUX_MU_IO = uart_write_buffer[uart_write_idx++]; + enable_uart_tx_interrupt(); + } +} + +void uart_rx_irq_handler() { + while (!(*AUX_MU_LSR & 0x01)); + char c = (char)(*AUX_MU_IO); + uart_read_buffer[uart_read_idx++] = (c == CARRIAGE_RETURN) ? NEWLINE : c; + if (uart_read_idx >= UART_BUF_SIZE) uart_read_idx = 0; + enable_uart_rx_interrupt(); +} + +void uart_async_read(char *buf, int len) { + disable_uart_rx_interrupt(); + for (int i = 0; i < uart_read_idx && i < len; i++) + buf[i] = uart_read_buffer[i]; + buf[uart_read_idx] = 0; + uart_read_idx = 0; +} + +void uart_async_write(const char *s) { + // Copy string to the write buffer + int len = 0; + while (*s != '\0') { + if (len >= UART_BUF_SIZE) { + uart_log(ERR, "Exceed the UART buffer size\n"); + return; + } + if (*s == NEWLINE) { + // Insert CARRIAGE_RETURN before NEWLINE + uart_write_buffer[len++] = CARRIAGE_RETURN; + uart_write_buffer[len++] = NEWLINE; + } else + uart_write_buffer[len++] = *s; + s++; + } + uart_write_buffer[len] = '\0'; + uart_write_idx = 0; // Reset the buffer index + enable_uart_tx_interrupt(); +} \ No newline at end of file diff --git a/lab5/src/utils.c b/lab5/src/utils.c new file mode 100644 index 000000000..dec35665b --- /dev/null +++ b/lab5/src/utils.c @@ -0,0 +1,71 @@ +#include "utils.h" + +int align4(int n) { return n + (4 - n % 4) % 4; } + +int atoi(const char *s) { + int result = 0; + int sign = 1; + int i = 0; + + // Skip leading spaces + while (s[i] == ' ') { + i++; + } + + // Handle positive and negative sign + if (s[i] == '-') { + sign = -1; + i++; + } else if (s[i] == '+') { + i++; + } + + // Convert string to integer + while (s[i] >= '0' && s[i] <= '9') { + result = result * 10 + (s[i] - '0'); + i++; + } + + return sign * result; +} + +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 memcmp(const void *m1, const void *m2, int n) { + const unsigned char *a = m1, *b = m2; + while (n-- > 0) { + if (*a != *b) return *a - *b; + a++; + b++; + } + return 0; +} + +/** + * @brief Copy `n` bytes from `src` to `dest`. + */ +void *memcpy(void *dest, const void *src, int n) { + char *d = dest; + const char *s = src; + while (n--) *d++ = *s++; + return dest; +} + +/** + * @brief Set `n` bytes of `s` to `c`. + */ +void *memset(void *s, int c, int n) { + unsigned char *p = s; + while (n--) *p++ = (unsigned char)c; + return s; +} \ No newline at end of file From e86a10ddf5482187299e6df613c2a4085c46c5e3 Mon Sep 17 00:00:00 2001 From: "Chang, Chun-yien" Date: Tue, 25 Jun 2024 15:44:00 +0800 Subject: [PATCH 5/7] lab6 --- lab6/.gitignore | 6 + lab6/Makefile | 41 ++++ lab6/bootloader/Makefile | 19 ++ lab6/bootloader/boot.c | 61 ++++++ lab6/bootloader/boot.h | 49 +++++ lab6/bootloader/linker.ld | 16 ++ lab6/bootloader/main.c | 41 ++++ lab6/bootloader/start.S | 40 ++++ lab6/dump.sh | 2 + lab6/include/command.h | 19 ++ lab6/include/devtree.h | 28 +++ lab6/include/hardware.h | 212 +++++++++++++++++++++ lab6/include/initramfs.h | 35 ++++ lab6/include/irq.h | 20 ++ lab6/include/mbox.h | 9 + lab6/include/mem.h | 47 +++++ lab6/include/scheduler.h | 69 +++++++ lab6/include/shell.h | 7 + lab6/include/signals.h | 12 ++ lab6/include/start.h | 42 +++++ lab6/include/str.h | 6 + lab6/include/syscalls.h | 16 ++ lab6/include/test.h | 4 + lab6/include/timer.h | 24 +++ lab6/include/traps.h | 38 ++++ lab6/include/uart.h | 45 +++++ lab6/include/utils.h | 24 +++ lab6/include/virtm.h | 15 ++ lab6/initramfs.cpio | Bin 0 -> 745472 bytes lab6/sender.sh | 27 +++ lab6/src/command.c | 186 +++++++++++++++++++ lab6/src/devtree.c | 61 ++++++ lab6/src/initramfs.c | 185 +++++++++++++++++++ lab6/src/irq.c | 96 ++++++++++ lab6/src/linker.ld | 14 ++ lab6/src/main.c | 33 ++++ lab6/src/mbox.c | 37 ++++ lab6/src/mem.c | 380 ++++++++++++++++++++++++++++++++++++++ lab6/src/scheduler.S | 54 ++++++ lab6/src/scheduler.c | 152 +++++++++++++++ lab6/src/shell.c | 84 +++++++++ lab6/src/signals.c | 56 ++++++ lab6/src/start.S | 139 ++++++++++++++ lab6/src/str.c | 30 +++ lab6/src/syscalls.c | 99 ++++++++++ lab6/src/timer.c | 114 ++++++++++++ lab6/src/traps.S | 117 ++++++++++++ lab6/src/traps.c | 163 ++++++++++++++++ lab6/src/uart.c | 200 ++++++++++++++++++++ lab6/src/utils.c | 71 +++++++ lab6/src/virtm.c | 46 +++++ 51 files changed, 3291 insertions(+) create mode 100644 lab6/.gitignore create mode 100644 lab6/Makefile create mode 100644 lab6/bootloader/Makefile create mode 100644 lab6/bootloader/boot.c create mode 100644 lab6/bootloader/boot.h create mode 100644 lab6/bootloader/linker.ld create mode 100644 lab6/bootloader/main.c create mode 100644 lab6/bootloader/start.S create mode 100755 lab6/dump.sh create mode 100644 lab6/include/command.h create mode 100644 lab6/include/devtree.h create mode 100644 lab6/include/hardware.h create mode 100644 lab6/include/initramfs.h create mode 100644 lab6/include/irq.h create mode 100644 lab6/include/mbox.h create mode 100644 lab6/include/mem.h create mode 100644 lab6/include/scheduler.h create mode 100644 lab6/include/shell.h create mode 100644 lab6/include/signals.h create mode 100644 lab6/include/start.h create mode 100644 lab6/include/str.h create mode 100644 lab6/include/syscalls.h create mode 100644 lab6/include/test.h create mode 100644 lab6/include/timer.h create mode 100644 lab6/include/traps.h create mode 100644 lab6/include/uart.h create mode 100644 lab6/include/utils.h create mode 100644 lab6/include/virtm.h create mode 100644 lab6/initramfs.cpio create mode 100755 lab6/sender.sh create mode 100644 lab6/src/command.c create mode 100644 lab6/src/devtree.c create mode 100644 lab6/src/initramfs.c create mode 100644 lab6/src/irq.c create mode 100644 lab6/src/linker.ld create mode 100644 lab6/src/main.c create mode 100644 lab6/src/mbox.c create mode 100644 lab6/src/mem.c create mode 100644 lab6/src/scheduler.S create mode 100644 lab6/src/scheduler.c create mode 100644 lab6/src/shell.c create mode 100644 lab6/src/signals.c create mode 100644 lab6/src/start.S create mode 100644 lab6/src/str.c create mode 100644 lab6/src/syscalls.c create mode 100644 lab6/src/timer.c create mode 100644 lab6/src/traps.S create mode 100644 lab6/src/traps.c create mode 100644 lab6/src/uart.c create mode 100644 lab6/src/utils.c create mode 100644 lab6/src/virtm.c diff --git a/lab6/.gitignore b/lab6/.gitignore new file mode 100644 index 000000000..eddcca901 --- /dev/null +++ b/lab6/.gitignore @@ -0,0 +1,6 @@ +.vscode +build +rootfs +temp +*.img +*.dtb \ No newline at end of file diff --git a/lab6/Makefile b/lab6/Makefile new file mode 100644 index 000000000..c6dde9a1a --- /dev/null +++ b/lab6/Makefile @@ -0,0 +1,41 @@ +ARMGNU ?= aarch64-linux-gnu + +CFLAGS = -Iinclude -nostdlib -nostartfiles -ffreestanding -mgeneral-regs-only -Wall -g +ASMFLAGS = -Iinclude +QEMUFLAGS = -M raspi3b -display none -serial null -serial stdio +QEMUFLAGSDISP = -M raspi3b -serial null -serial stdio + +SRC_DIR = src +BUILD_DIR = build + +all: clean kernel8.img + +clean: + rm -rf $(BUILD_DIR) *.img + +build: + mkdir -p $(BUILD_DIR) + +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(BUILD_DIR) + $(ARMGNU)-gcc $(CFLAGS) -c $< -o $@ + +$(BUILD_DIR)/_%.o: $(SRC_DIR)/%.S | $(BUILD_DIR) + $(ARMGNU)-gcc $(ASMFLAGS) -c $< -o $@ + +SRC_FILES = $(wildcard $(SRC_DIR)/*.c) +ASM_FILES = $(wildcard $(SRC_DIR)/*.S) +OBJ_FILES = $(SRC_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%.o) +OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/_%.o) + +kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) + $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) + $(ARMGNU)-objcopy -O binary $(BUILD_DIR)/kernel8.elf kernel8.img + +qemu: all initramfs.cpio bcm2710-rpi-3-b-plus.dtb + clear & qemu-system-aarch64 $(QEMUFLAGS) -kernel kernel8.img -initrd initramfs.cpio -dtb bcm2710-rpi-3-b-plus.dtb + +dpqemu: all initramfs.cpio bcm2710-rpi-3-b-plus.dtb + clear & qemu-system-aarch64 $(QEMUFLAGSDISP) -kernel kernel8.img -initrd initramfs.cpio -dtb bcm2710-rpi-3-b-plus.dtb + +debug: all initramfs.cpio bcm2710-rpi-3-b-plus.dtb + qemu-system-aarch64 $(QEMUFLAGS) -kernel kernel8.img -initrd initramfs.cpio -dtb bcm2710-rpi-3-b-plus.dtb -S -s -d int \ No newline at end of file diff --git a/lab6/bootloader/Makefile b/lab6/bootloader/Makefile new file mode 100644 index 000000000..d7882aa27 --- /dev/null +++ b/lab6/bootloader/Makefile @@ -0,0 +1,19 @@ +ARMGNU ?= aarch64-linux-gnu +CFLAGS = -nostdlib -nostartfiles -ffreestanding -mgeneral-regs-only -Wall -g +QEMUFLAGS = -M raspi3b -display none -serial null -serial pty + +all: clean bootloader.img + +clean: + rm -rf build *.img + +bootloader.img: + mkdir -p build + $(ARMGNU)-gcc $(CFLAGS) -c main.c -o build/main.o + $(ARMGNU)-gcc $(CFLAGS) -c start.S -o build/start.o + $(ARMGNU)-gcc $(CFLAGS) -c boot.c -o build/boot.o + $(ARMGNU)-ld -T linker.ld -o build/bootloader.elf build/main.o build/start.o build/boot.o + $(ARMGNU)-objcopy -O binary build/bootloader.elf bootloader.img + +qemu: clean bootloader.img + qemu-system-aarch64 $(QEMUFLAGS) -kernel bootloader.img -initrd ../initramfs.cpio -dtb ../bcm2710-rpi-3-b-plus.dtb \ No newline at end of file diff --git a/lab6/bootloader/boot.c b/lab6/bootloader/boot.c new file mode 100644 index 000000000..d4ff535f5 --- /dev/null +++ b/lab6/bootloader/boot.c @@ -0,0 +1,61 @@ +#include "boot.h" + +void init_uart() { + // Configure GPIO pins + register unsigned int r = *GPFSEL1; + r &= ~((7 << 12) | (7 << 15)); + r |= (2 << 12) | (2 << 15); + *GPFSEL1 = r; + + *GPPUD = 0; + for (int i = 0; i < 150; i++) asm volatile("nop"); + *GPPUDCLK0 = (1 << 14) | (1 << 15); + for (int i = 0; i < 150; i++) asm volatile("nop"); + *GPPUD = 0; + *GPPUDCLK0 = 0; + + *AUX_ENABLE |= 1; + *AUX_MU_CNTL = 0; + *AUX_MU_IER = 0; + *AUX_MU_LCR = 3; + *AUX_MU_MCR = 0; + *AUX_MU_BAUD = 270; + *AUX_MU_IIR = 6; + *AUX_MU_CNTL = 3; +} + +char uart_recv() { + while (!(*AUX_MU_LSR & 0x01)) asm volatile("nop"); + return (char)(*AUX_MU_IO); +} + +void uart_putc(char c) { + if (c == '\n') uart_putc('\r'); + while (!(*AUX_MU_LSR & 0x20)) asm volatile("nop"); + *AUX_MU_IO = c; +} + +void uart_puts(const char *s) { + while (*s) uart_putc(*s++); +} + +int atoi(const char *s) { + int result = 0; + int sign = 1; + int i = 0; + + while (s[i] == ' ') i++; + + if (s[i] == '-') { + sign = -1; + i++; + } else if (s[i] == '+') + i++; + + while (s[i] >= '0' && s[i] <= '9') { + result = result * 10 + (s[i] - '0'); + i++; + } + + return sign * result; +} \ No newline at end of file diff --git a/lab6/bootloader/boot.h b/lab6/bootloader/boot.h new file mode 100644 index 000000000..84cece020 --- /dev/null +++ b/lab6/bootloader/boot.h @@ -0,0 +1,49 @@ +#ifndef BOOT_H +#define BOOT_H + +/* ==================== GPIO ==================== */ +#define MMIO_BASE 0x3F000000 + +#define GPFSEL0 ((volatile unsigned int *)(MMIO_BASE + 0x00200000)) +#define GPFSEL1 ((volatile unsigned int *)(MMIO_BASE + 0x00200004)) +#define GPFSEL2 ((volatile unsigned int *)(MMIO_BASE + 0x00200008)) +#define GPFSEL3 ((volatile unsigned int *)(MMIO_BASE + 0x0020000C)) +#define GPFSEL4 ((volatile unsigned int *)(MMIO_BASE + 0x00200010)) +#define GPFSEL5 ((volatile unsigned int *)(MMIO_BASE + 0x00200014)) +#define GPSET0 ((volatile unsigned int *)(MMIO_BASE + 0x0020001C)) +#define GPSET1 ((volatile unsigned int *)(MMIO_BASE + 0x00200020)) +#define GPCLR0 ((volatile unsigned int *)(MMIO_BASE + 0x00200028)) +#define GPCLR1 ((volatile unsigned int *)(MMIO_BASE + 0x0020002C)) +#define GPLEV0 ((volatile unsigned int *)(MMIO_BASE + 0x00200034)) +#define GPLEV1 ((volatile unsigned int *)(MMIO_BASE + 0x00200038)) +#define GPEDS0 ((volatile unsigned int *)(MMIO_BASE + 0x00200040)) +#define GPEDS1 ((volatile unsigned int *)(MMIO_BASE + 0x00200044)) +#define GPHEN0 ((volatile unsigned int *)(MMIO_BASE + 0x00200064)) +#define GPHEN1 ((volatile unsigned int *)(MMIO_BASE + 0x00200068)) +#define GPPUD ((volatile unsigned int *)(MMIO_BASE + 0x00200094)) +#define GPPUDCLK0 ((volatile unsigned int *)(MMIO_BASE + 0x00200098)) +#define GPPUDCLK1 ((volatile unsigned int *)(MMIO_BASE + 0x0020009C)) + +/* ==================== UART ==================== */ +#define AUX_ENABLE ((volatile unsigned int *)(MMIO_BASE + 0x00215004)) +#define AUX_MU_IO ((volatile unsigned int *)(MMIO_BASE + 0x00215040)) +#define AUX_MU_IER ((volatile unsigned int *)(MMIO_BASE + 0x00215044)) +#define AUX_MU_IIR ((volatile unsigned int *)(MMIO_BASE + 0x00215048)) +#define AUX_MU_LCR ((volatile unsigned int *)(MMIO_BASE + 0x0021504C)) +#define AUX_MU_MCR ((volatile unsigned int *)(MMIO_BASE + 0x00215050)) +#define AUX_MU_LSR ((volatile unsigned int *)(MMIO_BASE + 0x00215054)) +#define AUX_MU_MSR ((volatile unsigned int *)(MMIO_BASE + 0x00215058)) +#define AUX_MU_SCRATCH ((volatile unsigned int *)(MMIO_BASE + 0x0021505C)) +#define AUX_MU_CNTL ((volatile unsigned int *)(MMIO_BASE + 0x00215060)) +#define AUX_MU_STAT ((volatile unsigned int *)(MMIO_BASE + 0x00215064)) +#define AUX_MU_BAUD ((volatile unsigned int *)(MMIO_BASE + 0x00215068)) + +void init_uart(); +char uart_recv(); +void uart_putc(char c); +void uart_puts(const char *s); + +/* ==================== UTILS ==================== */ +int atoi(const char *s); + +#endif // BOOT_H \ No newline at end of file diff --git a/lab6/bootloader/linker.ld b/lab6/bootloader/linker.ld new file mode 100644 index 000000000..556816b11 --- /dev/null +++ b/lab6/bootloader/linker.ld @@ -0,0 +1,16 @@ +SECTIONS +{ + . = 0x60000; + __loader_start = .; + .text : { *(.text.boot) *(.text) } + .rodata : { *(.rodata) } + .data : { *(.data) } + .bss : { + __bss_start = .; + *(.bss) + __bss_end = .; + } + __loader_end = .; +} +__bss_size = SIZEOF(.bss); +__loader_size = __loader_end - __loader_start; \ No newline at end of file diff --git a/lab6/bootloader/main.c b/lab6/bootloader/main.c new file mode 100644 index 000000000..cc8faa0fe --- /dev/null +++ b/lab6/bootloader/main.c @@ -0,0 +1,41 @@ +#include "boot.h" + +int main() { + init_uart(); + uart_puts("\033[2J\033[H"); + uart_puts( + "UART Bootloader\n" + "Waiting for kernel...\n"); + + // Get kernel image size + char buf[16] = {0}; + for (int i = 0; i < 16; i++) { + buf[i] = uart_recv(); + if (buf[i] == '\n') { + buf[i] = '\0'; + break; + } + } + + // Load kernel image + uart_puts("Kernel size: "); + uart_puts(buf); + uart_puts(" bytes.\n"); + uart_puts("Loading the kernel image...\n"); + + unsigned int size = atoi(buf); + char *kernel = (char *)0x80000; + while (size--) *kernel++ = uart_recv(); + + // Restore registers x0 x1 x2 x3 + asm volatile( + "mov x0, x10\n" + "mov x1, x11\n" + "mov x2, x12\n" + "mov x3, x13\n" + "mov x30, 0x80000\n" + "ret\n" // Jump to the new kernel + ); + + return 0; +} \ No newline at end of file diff --git a/lab6/bootloader/start.S b/lab6/bootloader/start.S new file mode 100644 index 000000000..ec8488dde --- /dev/null +++ b/lab6/bootloader/start.S @@ -0,0 +1,40 @@ +.section ".text.boot" + +.global _start + +_start: + /* save registers x0 x1 x2 x3 */ + mov x10, x0 /* dtb_base address */ + mov x11, x1 + mov x12, x2 + mov x12, x3 + + /* relocate bootloader */ + ldr x1, =0x80000 + ldr x2, =__loader_start // 0x60000 + ldr w3, =__loader_size + +relocate: + ldr x4, [x1], #8 + str x4, [x2], #8 + sub w3, w3, #1 + cbnz w3, relocate + + /* set stack pointer */ + ldr x1, =_start + mov sp, x1 + + /* clear bss section */ + ldr x1, =__bss_start + ldr w2, =__bss_size + +bss_reset: + cbz w2, run_main + str xzr, [x1], #8 + sub w2, w2, #1 + cbnz w2, bss_reset + +run_main: + /* branch to main function */ + bl main-0x20000 + b run_main diff --git a/lab6/dump.sh b/lab6/dump.sh new file mode 100755 index 000000000..91ab357ab --- /dev/null +++ b/lab6/dump.sh @@ -0,0 +1,2 @@ +aarch64-linux-gnu-objdump build/kernel8.elf -d > dump.txt + diff --git a/lab6/include/command.h b/lab6/include/command.h new file mode 100644 index 000000000..b48238d64 --- /dev/null +++ b/lab6/include/command.h @@ -0,0 +1,19 @@ +#pragma once + +#define MAX_BUF_SIZE 1024 +#define END_OF_COMMAND_LIST "NULL" + +struct command { + const char *name; + const char *help; + void (*func)(void); +}; + +extern struct command cmd_list[]; +extern unsigned int BOARD_REVISION; +extern unsigned long BASE_MEMORY; +extern unsigned int NUM_PAGES; + +// Commands +void cmd_info(); +void cmd_hello(); \ No newline at end of file diff --git a/lab6/include/devtree.h b/lab6/include/devtree.h new file mode 100644 index 000000000..0a1e5bf65 --- /dev/null +++ b/lab6/include/devtree.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include "hardware.h" + +struct fdt_header { + uint32_t magic; + uint32_t totalsize; + uint32_t off_dt_struct; + uint32_t off_dt_strings; + uint32_t off_mem_rsvmap; + uint32_t version; + uint32_t last_comp_version; + uint32_t boot_cpuid_phys; + uint32_t size_dt_strings; + uint32_t size_dt_struct; +}; + +/** + * @brief Convert a 4-byte big-endian sequence to little-endian. + * + * @param s: big-endian sequence + * @return little-endian sequence + */ +uint32_t be2le(const void *s); + +void fdt_traverse(void (*callback)(void *, char *)); diff --git a/lab6/include/hardware.h b/lab6/include/hardware.h new file mode 100644 index 000000000..d64eabf86 --- /dev/null +++ b/lab6/include/hardware.h @@ -0,0 +1,212 @@ +#pragma once + +#include "start.h" + +#define VM 0xFFFF000000000000 + +#define TO_VIRT(x) ((unsigned long long)(x) | VM) +#define TO_PHYS(x) ((unsigned long long)(x) & ~VM) + +#define MMIO_BASE TO_VIRT(0x3F000000) + +// ARM Interrupt Registers + +#define IRQ_BASIC_PENDING (volatile unsigned int *)(MMIO_BASE + 0x0000B200) +#define IRQ_PENDING_1 (volatile unsigned int *)(MMIO_BASE + 0x0000B204) +#define IRQ_PENDING_2 (volatile unsigned int *)(MMIO_BASE + 0x0000B208) +#define FIQ_CONTROL (volatile unsigned int *)(MMIO_BASE + 0x0000B20C) +#define ENABLE_IRQS_1 (volatile unsigned int *)(MMIO_BASE + 0x0000B210) +#define ENABLE_IRQS_2 (volatile unsigned int *)(MMIO_BASE + 0x0000B214) +#define ENABLE_BASIC_IRQS (volatile unsigned int *)(MMIO_BASE + 0x0000B218) +#define DISABLE_IRQS_1 (volatile unsigned int *)(MMIO_BASE + 0x0000B21C) +#define DISABLE_IRQS_2 (volatile unsigned int *)(MMIO_BASE + 0x0000B220) +#define DISABLE_BASIC_IRQS (volatile unsigned int *)(MMIO_BASE + 0x0000B224) + +// Timers interrupt control registers + +#define CORE0_INTERRUPT_SOURCE (volatile unsigned int *)TO_VIRT(0x40000060) +#define CORE0_TIMER_IRQCNTL TO_VIRT(0x40000040) +#define CORE1_TIMER_IRQCNTL TO_VIRT(0x40000044) +#define CORE2_TIMER_IRQCNTL TO_VIRT(0x40000048) +#define CORE3_TIMER_IRQCNTL TO_VIRT(0x4000004C) + +// Where to route timer interrupt to, IRQ/FIQ +// Setting both the IRQ and FIQ bit gives an FIQ +#define TIMER0_IRQ 0x01 +#define TIMER1_IRQ 0x02 +#define TIMER2_IRQ 0x04 +#define TIMER3_IRQ 0x08 +#define TIMER0_FIQ 0x10 +#define TIMER1_FIQ 0x20 +#define TIMER2_FIQ 0x40 +#define TIMER3_FIQ 0x80 + +// PASSWORD + +#define PM_PASSWORD 0x5A000000 +#define PM_RSTC (volatile unsigned int *)0x3F10001C +#define PM_WDOG (volatile unsigned int *)0x3F100024 + +// DEVICETREE + +#define FDT_BEGIN_NODE 0x00000001 +#define FDT_END_NODE 0x00000002 +#define FDT_PROP 0x00000003 +#define FDT_NOP 0x00000004 +#define FDT_END 0x00000009 + +// GPIO + +#define GPFSEL0 ((volatile unsigned int *)(MMIO_BASE + 0x00200000)) +#define GPFSEL1 ((volatile unsigned int *)(MMIO_BASE + 0x00200004)) +#define GPFSEL2 ((volatile unsigned int *)(MMIO_BASE + 0x00200008)) +#define GPFSEL3 ((volatile unsigned int *)(MMIO_BASE + 0x0020000C)) +#define GPFSEL4 ((volatile unsigned int *)(MMIO_BASE + 0x00200010)) +#define GPFSEL5 ((volatile unsigned int *)(MMIO_BASE + 0x00200014)) +#define GPSET0 ((volatile unsigned int *)(MMIO_BASE + 0x0020001C)) +#define GPSET1 ((volatile unsigned int *)(MMIO_BASE + 0x00200020)) +#define GPCLR0 ((volatile unsigned int *)(MMIO_BASE + 0x00200028)) +#define GPCLR1 ((volatile unsigned int *)(MMIO_BASE + 0x0020002C)) +#define GPLEV0 ((volatile unsigned int *)(MMIO_BASE + 0x00200034)) +#define GPLEV1 ((volatile unsigned int *)(MMIO_BASE + 0x00200038)) +#define GPEDS0 ((volatile unsigned int *)(MMIO_BASE + 0x00200040)) +#define GPEDS1 ((volatile unsigned int *)(MMIO_BASE + 0x00200044)) +// Skip GPRENn, GPFENn +#define GPHEN0 ((volatile unsigned int *)(MMIO_BASE + 0x00200064)) +#define GPHEN1 ((volatile unsigned int *)(MMIO_BASE + 0x00200068)) +// Skip GPLENn, GPARENn +#define GPPUD ((volatile unsigned int *)(MMIO_BASE + 0x00200094)) +#define GPPUDCLK0 ((volatile unsigned int *)(MMIO_BASE + 0x00200098)) +#define GPPUDCLK1 ((volatile unsigned int *)(MMIO_BASE + 0x0020009C)) + +// UART + +#define AUX_ENABLE ((volatile unsigned int *)(MMIO_BASE + 0x00215004)) +#define AUX_MU_IO ((volatile unsigned int *)(MMIO_BASE + 0x00215040)) +#define AUX_MU_IER ((volatile unsigned int *)(MMIO_BASE + 0x00215044)) +#define AUX_MU_IIR ((volatile unsigned int *)(MMIO_BASE + 0x00215048)) +#define AUX_MU_LCR ((volatile unsigned int *)(MMIO_BASE + 0x0021504C)) +#define AUX_MU_MCR ((volatile unsigned int *)(MMIO_BASE + 0x00215050)) +#define AUX_MU_LSR ((volatile unsigned int *)(MMIO_BASE + 0x00215054)) +#define AUX_MU_MSR ((volatile unsigned int *)(MMIO_BASE + 0x00215058)) +#define AUX_MU_SCRATCH ((volatile unsigned int *)(MMIO_BASE + 0x0021505C)) +#define AUX_MU_CNTL ((volatile unsigned int *)(MMIO_BASE + 0x00215060)) +#define AUX_MU_STAT ((volatile unsigned int *)(MMIO_BASE + 0x00215064)) +#define AUX_MU_BAUD ((volatile unsigned int *)(MMIO_BASE + 0x00215068)) + +// MAILBOX + +#define MAILBOX_BASE (MMIO_BASE + 0x0000B880) + +// mailbox register +#define MAILBOX_REG_READ ((volatile unsigned int *)(MAILBOX_BASE + 0x00000000)) +#define MAILBOX_REG_STATUS \ + ((volatile unsigned int *)(MAILBOX_BASE + 0x00000018)) +#define MAILBOX_REG_WRITE ((volatile unsigned int *)(MAILBOX_BASE + 0x00000020)) + +// mailbox channel +// https://github.com/raspberrypi/firmware/wiki/Mailboxes#channels +#define MAILBOX_CH_POWER 0 +#define MAILBOX_CH_FB 1 +#define MAILBOX_CH_VUART 2 +#define MAILBOX_CH_VCHIQ 3 +#define MAILBOX_CH_LEDS 4 +#define MAILBOX_CH_BTNS 5 +#define MAILBOX_CH_TOUCH 6 +#define MAILBOX_CH_COUNT 7 +#define MAILBOX_CH_PROP 8 + +// mailbox status +#define MAILBOX_EMPTY 0x40000000 +#define MAILBOX_FULL 0x80000000 +#define MAILBOX_REQUEST 0x00000000 +#define MAILBOX_RESPONSE 0x80000000 +#define REQUEST_CODE 0x00000000 +#define REQUEST_SUCCEED 0x80000000 +#define REQUEST_FAILED 0x80000001 +#define TAG_REQUEST_CODE 0x00000000 +#define END_TAG 0x00000000 + +// tags +#define TAGS_REQ_CODE 0x00000000 +#define TAGS_REQ_SUCCEED 0x80000000 +#define TAGS_REQ_FAILED 0x80000001 +#define TAGS_END 0x00000000 + +// hardware tags operator +#define TAGS_HARDWARE_BOARD_MODEL 0x00010001 +#define TAGS_HARDWARE_BOARD_REVISION 0x00010002 +#define TAGS_HARDWARE_MAC_ADDR 0x00010003 +#define TAGS_HARDWARE_BOARD_SERIAL 0x00010004 +#define TAGS_HARDWARE_ARM_MEM 0x00010005 +#define TAGS_HARDWARE_VC_MEM 0x00010006 +#define TAGS_HARDWARE_CLOCKS 0x00010007 + +// CLOCK ID + +#define CLOCK_ID_RESERVED 0x000000000 +#define CLOCK_ID_EMMC 0x000000001 +#define CLOCK_ID_UART 0x000000002 +#define CLOCK_ID_ARM 0x000000003 +#define CLOCK_ID_CORE 0x000000004 +#define CLOCK_ID_V3D 0x000000005 +#define CLOCK_ID_H264 0x000000006 +#define CLOCK_ID_ISP 0x000000007 +#define CLOCK_ID_SDRAM 0x000000008 +#define CLOCK_ID_PIXEL 0x000000009 +#define CLOCK_ID_PWM 0x00000000a +#define CLOCK_ID_HEVC 0x00000000b +#define CLOCK_ID_EMMC2 0x00000000c +#define CLOCK_ID_M2MC 0x00000000d +#define CLOCK_ID_PIXEL_BVB 0x00000000e + +// clock tags operator +#define TAGS_GET_CLOCK 0x00030002 +#define TAGS_SET_CLOCK 0x00038002 + +// framebuffer tages operator +#define FB_ALLOC_BUFFER 0x00040001 +#define FB_FREE_BUFFER 0x00048001 + +#define FB_BLANK_SCREEN 0x00040002 + +#define FB_PHY_WID_HEIGHT_GET 0x00040003 +#define FB_PHY_WID_HEIGHT_TEST 0x00044003 +#define FB_PHY_WID_HEIGHT_SET 0x00048003 + +#define FB_VIR_WID_HEIGHT_GET 0x00040004 +#define FB_VIR_WID_HEIGHT_TEST 0x00044004 +#define FB_VIR_WID_HEIGHT_SET 0x00048004 + +#define FB_DEPTH_GET 0x00040005 +#define FB_DEPTH_TEST 0x00044005 +#define FB_DEPTH_SET 0x00048005 + +#define FB_DEPTH_GET 0x00040005 +#define FB_DEPTH_TEST 0x00044005 +#define FB_DEPTH_SET 0x00048005 + +#define FB_PIXEL_ORDER_GET 0x00040006 +#define FB_PIXEL_ORDER_TEST 0x00044006 +#define FB_PIXEL_ORDER_SET 0x00048006 + +#define FB_ALPHA_MODE_GET 0x00040007 +#define FB_ALPHA_MODE_TEST 0x00044007 +#define FB_ALPHA_MODE_SET 0x00048007 + +#define FB_PITCH_GET 0x00040008 + +#define FB_VIR_OFFSET_GET 0x00040009 +#define FB_VIR_OFFSET_TEST 0x00044009 +#define FB_VIR_OFFSET_SET 0x00048009 + +#define FB_OVERSCAN_GET 0x0004000A +#define FB_OVERSCAN_TEST 0x0004400A +#define FB_OVERSCAN_SET 0x0004800A + +#define FB_PALETTE_GET 0x0004000B +#define FB_PALETTE_TEST 0x0004400B +#define FB_PALETTE_SET 0x0004800B + +#define FB_CURSOR_INFO_SET 0x00008010 +#define FB_CURSOR_STATE_SET 0x00008011 diff --git a/lab6/include/initramfs.h b/lab6/include/initramfs.h new file mode 100644 index 000000000..4c82cb598 --- /dev/null +++ b/lab6/include/initramfs.h @@ -0,0 +1,35 @@ +#pragma once + +#include "traps.h" + +// 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; + +typedef struct { + int namesize; + int filesize; + int headsize; + int datasize; + char *pathname; +} ramfsRec; + +void initramfs_ls(); +void initramfs_cat(const char *target); +void initramfs_callback(void *addr, char *property); +void initramfs_run(const char *target); +void initramfs_sys_exec(const char *target, trap_frame *tf); diff --git a/lab6/include/irq.h b/lab6/include/irq.h new file mode 100644 index 000000000..cef36db3a --- /dev/null +++ b/lab6/include/irq.h @@ -0,0 +1,20 @@ +#pragma once + +#include "hardware.h" +#include "traps.h" + +#define ORDER_FIRST 0x0 +#define ORDER_REGULAR 0x8 +#define ORDER_LAST 0xF + +typedef struct irq_task_t { + void (*func)(); + int order; + int busy; // 0 (default) or 1 (busy) + struct irq_task_t *prev; + struct irq_task_t *next; +} irqTask; + +void enable_interrupt(); +void disable_interrupt(); +void irq_entry(trap_frame *tf); \ No newline at end of file diff --git a/lab6/include/mbox.h b/lab6/include/mbox.h new file mode 100644 index 000000000..c40987c13 --- /dev/null +++ b/lab6/include/mbox.h @@ -0,0 +1,9 @@ +#pragma once + +#include "hardware.h" + +extern volatile unsigned int mbox[36]; + +int mbox_call(unsigned char ch, unsigned int *mbox); +int get_board_revision(unsigned int *mbox); +int get_arm_memory_status(unsigned int *mbox); diff --git a/lab6/include/mem.h b/lab6/include/mem.h new file mode 100644 index 000000000..9eab5e6ea --- /dev/null +++ b/lab6/include/mem.h @@ -0,0 +1,47 @@ +#pragma once + +#include + +#include "start.h" + +#define BUDDY_MAX_ORDER 16 +#define CACHE_MAX_ORDER 6 +#define MIN_CACHE_SIZE (PAGE_SIZE / (2 << CACHE_MAX_ORDER)) + +struct page { + unsigned int order; + unsigned int used; + unsigned int cache_order; + struct page *prev; + struct page *next; +}; + +struct object { + unsigned int order; + struct object *next; +}; + +void init_mem(); +void reserveMemory(uint64_t start, uint64_t end); + +uintptr_t page_vaddr(struct page *p); + +void pushPageToFreeList(struct page **list, struct page *page, + unsigned int order); +struct page *popFreeList(struct page **list_head); +void removePageFromFreeList(struct page **list, struct page *page); +void printFreeListByOrder(unsigned int order); + +struct page *lookupBuddy(struct page *page, unsigned int order); +struct page *allocatePagesByOrder(unsigned int order, int silent); +void freePages(struct page *page, unsigned int order, int silent); +void mergePages(struct page *page, unsigned int order, int echo); + +void pushObjectToList(struct object **list_head, struct object *object, + unsigned int order); +struct object *popObjectFromList(struct object **list_head); +void *allocateCacheMemory(unsigned int index, int silent); +void freeCacheEntry(void *ptr, unsigned int index, int silent); + +void *kmalloc(unsigned int size, int silent); +void kfree(void *ptr, int silent); diff --git a/lab6/include/scheduler.h b/lab6/include/scheduler.h new file mode 100644 index 000000000..785c2c28a --- /dev/null +++ b/lab6/include/scheduler.h @@ -0,0 +1,69 @@ +#pragma once + +#include "signals.h" +#include "traps.h" + +struct context_struct { + unsigned long x19; + unsigned long x20; + unsigned long x21; + unsigned long x22; + unsigned long x23; + unsigned long x24; + unsigned long x25; + unsigned long x26; + unsigned long x27; + unsigned long x28; + unsigned long fp; + unsigned long lr; + unsigned long sp; +}; + +enum thread_state { + RUNNING, + DEAD, +}; + +typedef struct thread_struct_t { + struct context_struct context; // callee-saved (don't move) + int pid; + + void *start; + unsigned int size; + + enum thread_state state; + void *stack; + void *user_stack; + + // Signal handling + void (*sig_handlers[NSIG + 1])(); // Signal handlers + int sig_reg; // Pending signals + int sig_busy; // Handling a signal + trap_frame *sig_tf; // tf Saved before signal handling + void *sig_stack; // Stack for signal handling + + void *pgd; + // struct vm_area_struct *mmap; + + struct thread_struct_t *prev; + struct thread_struct_t *next; +} thread_struct; + +// Defined in scheduler.S +extern void switch_to(thread_struct *prev, thread_struct *next); +extern void switch_mm(unsigned long pgd); + +extern thread_struct *get_current(); // get tpidr_el1, current thread_struct + +thread_struct *get_thread_by_pid(int pid); +void sched_init(); + +thread_struct *kcreate_thread(void (*func)()); +thread_struct *kcreate_user_thread(void *program, int filesize); +void list_tcircle(); + +void schedule(); + +void kill_current_thread(); +void kill_thread_by_pid(int pid); +void idle(); \ No newline at end of file diff --git a/lab6/include/shell.h b/lab6/include/shell.h new file mode 100644 index 000000000..fccef918e --- /dev/null +++ b/lab6/include/shell.h @@ -0,0 +1,7 @@ +#pragma once + +#define SHELL_BUF_SIZE 1024 + +void run_shell(); +void read_user_input(char *buf); +int exec_command(const char *command); diff --git a/lab6/include/signals.h b/lab6/include/signals.h new file mode 100644 index 000000000..e3ee32af0 --- /dev/null +++ b/lab6/include/signals.h @@ -0,0 +1,12 @@ +#pragma once + +#include "traps.h" + +#define NSIG 10 // Number of signals +#define SIG_STACK (USER_STACK - STACK_SIZE) + +extern void sigreturn(); // Defined in traps.S + +void signal(int signum, void (*handler)()); +void signal_kill(int pid, int sig); +void do_signal(trap_frame *regs); diff --git a/lab6/include/start.h b/lab6/include/start.h new file mode 100644 index 000000000..b484682c3 --- /dev/null +++ b/lab6/include/start.h @@ -0,0 +1,42 @@ +#define PAGE_SIZE 0x1000 +#define STACK_SIZE 0x4000 + +#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) + +/* + * nGnRnE: Non-Gathering, Non-Reordering, No Early Write Acknowledgement + * Normal memory, Inner Non-cacheable, Outer Non-cacheable + */ +#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 +#define PD_BLOCK 0b01 +#define PD_ENTRY 0b11 + +// Bits[10] The access flag, a page fault is generated if not set. +#define PD_ACCESS (1 << 10) + +// Bits[7] 0 for read-write, 1 or read-only +#define PD_RDONLY (1 << 7) + +// Bits[6] 0 for only kernel access, 1 for user/kernel access +#define PD_UKACCESS (1 << 6) + +// Bits[4:2] The index to MAIR +#define PD_MAIR_DEVICE_nGnRnE (MAIR_IDX_DEVICE_nGnRnE << 2) +#define PD_MAIR_NORMAL_NOCACHE (MAIR_IDX_NORMAL_NOCACHE << 2) + +#define BOOT_PGD_PAGE_FRAME 0x1000 // Boot PGD's page frame +#define BOOT_PUD_PAGE_FRAME 0x2000 // Boot PUD's page frame +#define BOOT_PMD_PAGE_FRAME 0x3000 // Boot PMD's page frame + +#define BOOT_PGD_ATTR PD_TABLE +#define BOOT_PUD_ATTR PD_TABLE +#define BOOT_PUD_DEVICE_ATTR (PD_ACCESS | PD_MAIR_DEVICE_nGnRnE | PD_BLOCK) +#define BOOT_PMD_NORMAL_ATTR (PD_ACCESS | PD_MAIR_NORMAL_NOCACHE | PD_BLOCK) +#define BOOT_PMD_DEVICE_ATTR (PD_ACCESS | PD_MAIR_DEVICE_nGnRnE | PD_BLOCK) \ No newline at end of file diff --git a/lab6/include/str.h b/lab6/include/str.h new file mode 100644 index 000000000..766ff7ea8 --- /dev/null +++ b/lab6/include/str.h @@ -0,0 +1,6 @@ +#pragma once + +int strcmp(const char *str1, const char *str2); +char *strncpy(char *dest, const char *src, int n); +int strlen(const char *str); +char *strcat(char *dest, const char *src); \ No newline at end of file diff --git a/lab6/include/syscalls.h b/lab6/include/syscalls.h new file mode 100644 index 000000000..42fb31d6d --- /dev/null +++ b/lab6/include/syscalls.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +#include "traps.h" + +extern void child_ret_from_fork(); // traps.S + +int sys_getpid(); +size_t sys_uart_read(char *buf, size_t size); +size_t sys_uart_write(const char *buf, size_t size); +int sys_exec(const char *name, trap_frame *tf); +int sys_fork(trap_frame *tf); +void sys_exit(int status); +int sys_mbox_call(unsigned char ch, unsigned int *mbox); +void sys_sigreturn(trap_frame *regs); \ No newline at end of file diff --git a/lab6/include/test.h b/lab6/include/test.h new file mode 100644 index 000000000..37346deb5 --- /dev/null +++ b/lab6/include/test.h @@ -0,0 +1,4 @@ +#pragma once + +void thread_test(); +void run_fork_test(); \ No newline at end of file diff --git a/lab6/include/timer.h b/lab6/include/timer.h new file mode 100644 index 000000000..464beb68f --- /dev/null +++ b/lab6/include/timer.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#define TIMER_SPEED 5 + +typedef struct timerEntry_t { + void (*func)(void *); + void *arg; + int time; + struct timerEntry_t *next; +} timerEntry; + +void init_timer(); + +void enable_timer_interrupt(); +void disable_timer_interrupt(); + +void timer_irq_handler(); +uint64_t timer_get_uptime(); + +void timer_add(void (*callback)(void *), void *arg, int duration); +void set_timer(const char *message, int duration); +void set_timeup(int *timeup); diff --git a/lab6/include/traps.h b/lab6/include/traps.h new file mode 100644 index 000000000..bf940f5d7 --- /dev/null +++ b/lab6/include/traps.h @@ -0,0 +1,38 @@ +#pragma once + +typedef struct { // order is important + unsigned long x0; + unsigned long x1; + unsigned long x2; + unsigned long x3; + unsigned long x4; + unsigned long x5; + unsigned long x6; + unsigned long x7; + unsigned long x8; // system call number + unsigned long x9; + unsigned long x10; + unsigned long x11; + unsigned long x12; + unsigned long x13; + unsigned long x14; + unsigned long x15; + unsigned long x16; + unsigned long x17; + unsigned long x18; + unsigned long x19; + unsigned long x20; + unsigned long x21; + unsigned long x22; + unsigned long x23; + unsigned long x24; + unsigned long x25; + unsigned long x26; + unsigned long x27; + unsigned long x28; + unsigned long x29; + unsigned long x30; // link register + unsigned long spsr_el1; + unsigned long elr_el1; + unsigned long sp_el0; +} trap_frame; diff --git a/lab6/include/uart.h b/lab6/include/uart.h new file mode 100644 index 000000000..6253f2bfa --- /dev/null +++ b/lab6/include/uart.h @@ -0,0 +1,45 @@ +#pragma once + +#include + +#include "hardware.h" + +#define UART_BUF_SIZE 1024 + +// Special characters +#define BACKSPACE '\b' +#define DELETE 127 +#define ESC 27 +#define NEWLINE '\n' +#define TAB '\t' +#define CARRIAGE_RETURN '\r' + +// uart_log types +#define INFO 0 +#define WARN 1 +#define TEST 2 +#define BUDD 10 +#define CACH 11 +#define ERR 255 + +void init_uart(); +char uart_getc(); +void uart_putc(char c); +void uart_puts(const char *s); +void uart_clear(); +void uart_hex(unsigned long h); +void uart_addr_range(uintptr_t start, uintptr_t end); +void uart_simple_hex(unsigned int h); +void uart_dec(unsigned int h); +void uart_log(int type, const char *msg); + +void enable_uart_tx_interrupt(); +void disable_uart_tx_interrupt(); +void enable_uart_rx_interrupt(); +void disable_uart_rx_interrupt(); + +void uart_tx_irq_handler(); +void uart_rx_irq_handler(); + +void uart_async_read(char *buf, int len); +void uart_async_write(const char *s); \ No newline at end of file diff --git a/lab6/include/utils.h b/lab6/include/utils.h new file mode 100644 index 000000000..c2ebcb54b --- /dev/null +++ b/lab6/include/utils.h @@ -0,0 +1,24 @@ +#pragma once + +/** + * @brief Align `n` to be a multiple of 4. + * + * @param n A number + * @return Algined number + */ +int align4(int n); + +int atoi(const char *s); + +/** + * @brief Convert hexadecimal string to int. + * + * @param s: hexadecimal string + * @param n: string length + * @return Converted int number + */ +int hextoi(char *s, int n); + +int memcmp(const void *str1, const void *str2, int n); +void *memcpy(void *dest, const void *src, int n); +void *memset(void *s, int c, int n); \ No newline at end of file diff --git a/lab6/include/virtm.h b/lab6/include/virtm.h new file mode 100644 index 000000000..5b1e8b628 --- /dev/null +++ b/lab6/include/virtm.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include "hardware.h" +#include "scheduler.h" + +#define USER_STACK 0x0000FFFFFFFFB000 + +#define PTE_NORMAL_ATTR \ + (PD_ACCESS | PD_UKACCESS | PD_MAIR_NORMAL_NOCACHE | PD_ENTRY) + +void map_pages(uintptr_t pgd, uintptr_t va, uintptr_t size, uintptr_t pa); + +void mapping_user_thread(thread_struct *thread, int gpu_mem_size); \ No newline at end of file diff --git a/lab6/initramfs.cpio b/lab6/initramfs.cpio new file mode 100644 index 0000000000000000000000000000000000000000..d7eff64c7ea6d6a07c18b02a598f06e45bc1ddd3 GIT binary patch literal 745472 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

2m7n!KHh&P4vy=9Px9mUyY2 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 $DEST_PATH | sleep 1 + +# Send the kernel image +# pv: redirect file input to specified tty +# add --rate-limit option to limit the speed +pv $KERNEL_PATH > $DEST_PATH diff --git a/lab6/src/command.c b/lab6/src/command.c new file mode 100644 index 000000000..a31d53327 --- /dev/null +++ b/lab6/src/command.c @@ -0,0 +1,186 @@ +#include "command.h" + +#include "initramfs.h" +#include "irq.h" +#include "mem.h" +#include "scheduler.h" +#include "shell.h" +#include "start.h" +#include "str.h" +#include "syscalls.h" +#include "test.h" +#include "timer.h" +#include "uart.h" +#include "utils.h" +#include "virtm.h" + +typedef struct { + void *ptr; + void *next; +} demo_mem_rec; + +static demo_mem_rec *dmr_list = 0; + +void cmd_info() { + uart_log(INFO, "board revision: "); + uart_hex(BOARD_REVISION); + uart_putc(NEWLINE); + uart_log(INFO, "device base memory address: "); + uart_hex(BASE_MEMORY); + uart_putc(NEWLINE); + uart_log(INFO, "device memory size: "); + uart_hex(NUM_PAGES * PAGE_SIZE); + uart_putc(NEWLINE); +} + +void cmd_hello() { uart_log(INFO, "Hello there!\n"); } + +static void cmd_mem() { + for (int i = BUDDY_MAX_ORDER; i >= 0; i--) { + printFreeListByOrder(i); + } +} + +static void cmd_bd() { + uart_puts("Buddy System: request order (0-"); + uart_dec(BUDDY_MAX_ORDER); + uart_puts("): "); + char *buf = kmalloc(SHELL_BUF_SIZE, 1); + read_user_input(buf); + int order = atoi(buf); + if (buf[0] < '0' || buf[0] > '9' || order < 0 || order > BUDDY_MAX_ORDER) { + uart_log(WARN, "Invalid order.\n"); + } else { + demo_mem_rec *dmr = kmalloc(sizeof(demo_mem_rec), 0); + dmr->ptr = kmalloc(PAGE_SIZE << order, 1); + dmr->next = dmr_list; + dmr_list = dmr; + } + kfree(buf, 1); +} + +static void cmd_fm() { + if (dmr_list == 0) { + uart_log(WARN, "No memory block allocated.\n"); + } else { + uart_log(INFO, "Freeing all allocated memory blocks...\n"); + do { + demo_mem_rec *dmr = dmr_list; + kfree(dmr->ptr, 0); + dmr_list = dmr->next; + kfree(dmr, 1); + } while (dmr_list != 0); + } +} + +static void cmd_ca() { + uart_log(INFO, "Dynamic Memory Allocator: request byte(s) (1-"); + uart_dec(PAGE_SIZE / 2); + uart_puts("): "); + char *buf = kmalloc(SHELL_BUF_SIZE, 1); + read_user_input(buf); + int size = atoi(buf); + if (buf[0] == '\0' || size <= 0) { + uart_log(WARN, "Invalid size.\n"); + } else { + if (size > PAGE_SIZE / 2) { + uart_log( + WARN, + "Size too large for dynamic allocator. Switching to Buddy System.\n"); + } + demo_mem_rec *dmr = kmalloc(sizeof(demo_mem_rec), 1); + dmr->ptr = kmalloc(size, 0); + dmr->next = dmr_list; + dmr_list = dmr; + } + kfree(buf, 1); +} + +static void cmd_lab() { uart_log(INFO, "Nothing to show in this lab.\n"); } + +static void cmd_timer() { + uart_puts("Duration(sec.): "); + char *buf = kmalloc(SHELL_BUF_SIZE, 1); + read_user_input(buf); + int sec = atoi(buf); + + char *msg = kmalloc(SHELL_BUF_SIZE, 1); + uart_puts(": "); + read_user_input(msg); + set_timer(msg, sec); + kfree(buf, 1); +} + +static void cmd_run() { + // Get filename from user input + uart_puts(": "); + char *buf = kmalloc(SHELL_BUF_SIZE, 1); + read_user_input(buf); + initramfs_run(buf); + kfree(buf, 1); +} + +static void cmd_reboot() { + uart_log(INFO, "Start Rebooting...\n"); + // Reboot after 0x20000 ticks + *PM_RSTC = PM_PASSWORD | 0x20; // Full reset + *PM_WDOG = PM_PASSWORD | 0x20000; // Number of watchdog ticks +} + +static void cmd_cancel() { + uart_log(INFO, "Rebooting Attempt Aborted, if any.\n"); + *PM_RSTC = PM_PASSWORD | 0; + *PM_WDOG = PM_PASSWORD | 0; +} + +static void cmd_cat() { + // Get filename from user input + uart_puts(": "); + char *buf = kmalloc(SHELL_BUF_SIZE, 1); + read_user_input(buf); + initramfs_cat(buf); + kfree(buf, 1); +} + +static void cmd_clear() { uart_clear(); } + +static void cmd_help() { + uart_puts("-----------------------------------------"); + uart_putc(NEWLINE); + struct command *cmd = cmd_list; + while (1) { + if (!strcmp(cmd->name, END_OF_COMMAND_LIST)) { + break; + } + uart_puts(cmd->name); + uart_putc(TAB); + uart_puts(": "); + uart_puts(cmd->help); + uart_putc(NEWLINE); + cmd++; + } + uart_puts("-----------------------------------------"); + uart_putc(NEWLINE); +} + +struct command cmd_list[] = { + {.name = "help", .help = "Display this help menu", .func = cmd_help}, + {.name = "hello", .help = "Print 'Hello there!'", .func = cmd_hello}, + {.name = "clear", .help = "Clear the screen", .func = cmd_clear}, + {.name = "reboot", .help = "Reboot the device", .func = cmd_reboot}, + {.name = "cancel", .help = "Cancel a scheduled reboot", .func = cmd_cancel}, + {.name = "info", .help = "Show hardware information", .func = cmd_info}, + {.name = "ls", .help = "List files in ramdisk", .func = initramfs_ls}, + {.name = "cat", .help = "Display content of ramdisk file", .func = cmd_cat}, + {.name = "run", .help = "Run a specified program", .func = cmd_run}, + {.name = "timer", .help = "Set timer with duration", .func = cmd_timer}, + // lab4 + {.name = "mem", .help = "Display free memory blocks", .func = cmd_mem}, + {.name = "bd", .help = "Allocate a memory block", .func = cmd_bd}, + {.name = "ca", .help = "Use the dynamic memory allocator", .func = cmd_ca}, + // lab5 + {.name = "fm", .help = "Free all memory in the demo list", .func = cmd_fm}, + {.name = "pid", .help = "List all running threads", .func = list_tcircle}, + // retired + {.name = "lab", .help = "Showcase lab requirements", .func = cmd_lab}, + {.name = END_OF_COMMAND_LIST}}; diff --git a/lab6/src/devtree.c b/lab6/src/devtree.c new file mode 100644 index 000000000..ae3900f9a --- /dev/null +++ b/lab6/src/devtree.c @@ -0,0 +1,61 @@ +#include "devtree.h" + +#include "str.h" +#include "uart.h" +#include "utils.h" + +// Assign a non-zero value to be stored in the .data section +void *DTB_BASE = (void *)0xF; +void *DTB_END = (void *)0xF; + +uint32_t be2le(const void *s) { + const uint8_t *bytes = (const uint8_t *)s; + return (uint32_t)bytes[0] << 24 | (uint32_t)bytes[1] << 16 | + (uint32_t)bytes[2] << 8 | (uint32_t)bytes[3]; +} + +void fdt_traverse(void (*callback)(void *, char *)) { + struct fdt_header *header = (struct fdt_header *)(uintptr_t)DTB_BASE; + + // Check the magic number + if (be2le(&(header->magic)) != 0xD00DFEED) { + uart_log(WARN, "Dtb header magic does not match!\n"); + } + DTB_BASE = (void *)TO_VIRT(DTB_BASE); + DTB_END = DTB_BASE + be2le(&header->totalsize); + + uart_log(INFO, "Dtb loaded at "); + uart_hex((uintptr_t)DTB_BASE); + uart_putc('-'); + uart_hex((uintptr_t)DTB_END); + uart_putc(NEWLINE); + + uintptr_t structure = (uintptr_t)header + be2le(&header->off_dt_struct); + uintptr_t strings = (uintptr_t)header + be2le(&header->off_dt_strings); + uint32_t structure_size = be2le(&header->size_dt_struct); + + // Parse the structure block + uintptr_t ptr = structure; // Point to the beginning of structure block + while (ptr < structure + structure_size) { + uint32_t token = be2le((char *)ptr); + ptr += 4; // Token takes 4 bytes + + switch (token) { + case FDT_BEGIN_NODE: + ptr += align4(strlen((char *)ptr) + 1); + break; + case FDT_PROP: + uint32_t len = be2le((char *)ptr); + ptr += 4; + uint32_t nameoff = be2le((char *)ptr); + ptr += 4; + callback((void *)(uintptr_t)be2le((void *)ptr), + (char *)(strings + nameoff)); + ptr += align4(len); + break; + case FDT_END_NODE: + case FDT_NOP: + case FDT_END: + } + } +} \ No newline at end of file diff --git a/lab6/src/initramfs.c b/lab6/src/initramfs.c new file mode 100644 index 000000000..b41b28859 --- /dev/null +++ b/lab6/src/initramfs.c @@ -0,0 +1,185 @@ +#include "initramfs.h" + +#include + +#include "irq.h" +#include "mem.h" +#include "scheduler.h" +#include "start.h" +#include "str.h" +#include "uart.h" +#include "utils.h" +#include "virtm.h" + +void *initrd_start; +void *initrd_end; + +void initramfs_callback(void *addr, char *property) { + if (!strcmp(property, "linux,initrd-start")) { + uart_log(INFO, "linux,initrd-start: "); + initrd_start = (char *)TO_VIRT(addr); + uart_hex((uintptr_t)initrd_start); + uart_putc(NEWLINE); + } else if (!strcmp(property, "linux,initrd-end")) { + uart_log(INFO, "linux,initrd-end: "); + initrd_end = (char *)TO_VIRT(addr); + uart_hex((uintptr_t)initrd_end); + uart_putc(NEWLINE); + } +} + +static ramfsRec *ramfsNext(char *fptr) { + ramfsRec *rec = kmalloc(sizeof(ramfsRec), 1); + cpio_t *header = (cpio_t *)fptr; + + // New ASCII Format uses 8-byte hexadecimal string for all numbers + rec->namesize = hextoi(header->c_namesize, 8); + rec->filesize = hextoi(header->c_filesize, 8); + + // Total size of (header + pathname) is a multiple of four bytes + // File data is also padded to a multiple of four bytes + rec->headsize = align4(sizeof(cpio_t) + rec->namesize); + rec->datasize = align4(rec->filesize); + + // Get file pathname + rec->pathname = kmalloc(rec->namesize, 1); + strncpy(rec->pathname, fptr + sizeof(cpio_t), rec->namesize); + + return rec; +} + +void initramfs_ls() { + uart_putc(NEWLINE); + + char *fptr = (char *)initrd_start; + while (memcmp(fptr + sizeof(cpio_t), "TRAILER!!!", 10)) { + ramfsRec *rec = ramfsNext(fptr); + uart_puts(rec->pathname); + for (int i = 0; i < 15 - strlen(rec->pathname); i++) { + uart_putc(' '); + } + uart_dec(rec->filesize); + uart_puts(" byte"); + if (rec->filesize > 1) uart_putc('s'); + uart_putc(NEWLINE); + + fptr += rec->headsize + rec->datasize; + kfree(rec->pathname, 1); + kfree(rec, 1); + } +} + +void initramfs_cat(const char *filename) { + char *fptr = (char *)initrd_start; + + while (memcmp(fptr + sizeof(cpio_t), "TRAILER!!!", 10)) { + ramfsRec *rec = ramfsNext(fptr); + if (!strcmp(filename, rec->pathname)) { + // Dump its content + uart_putc(NEWLINE); + for (char *c = fptr + rec->headsize; + c < fptr + rec->headsize + rec->filesize; c++) { + uart_putc(*c); + } + uart_putc(NEWLINE); + kfree(rec->pathname, 1); + kfree(rec, 1); + return; + } + fptr += rec->headsize + rec->datasize; + kfree(rec->pathname, 1); + kfree(rec, 1); + } + + uart_puts("File not found."); + uart_putc(NEWLINE); +} + +void initramfs_run(const char *filename) { + char *fptr = (char *)initrd_start; + + while (memcmp(fptr + sizeof(cpio_t), "TRAILER!!!", 10)) { + ramfsRec *rec = ramfsNext(fptr); + + if (!strcmp(filename, rec->pathname)) { + // Load the user program + disable_interrupt(); + + thread_struct *thread = kcreate_thread(0); + + thread->user_stack = kmalloc(STACK_SIZE, 0); + uart_log(INFO, "Acquired thread user stack: "); + uart_hex((uintptr_t)thread->stack); + uart_putc(NEWLINE); + + void *program = kmalloc(rec->filesize, 0); + uart_log(INFO, "Acquired program space: "); + uart_hex((uintptr_t)(thread->start = program)); + uart_putc(NEWLINE); + memcpy(program, fptr + rec->headsize, thread->size = rec->filesize); + + mapping_user_thread(thread, 0); + + switch_mm((uintptr_t)TO_PHYS(thread->pgd)); + + kfree(rec->pathname, 1); + kfree(rec, 1); + + asm volatile( + "msr tpidr_el1, %0\n" + "msr spsr_el1, %1\n" + "msr elr_el1, %2\n" + "msr sp_el0, %3\n" + "mov sp, %4\n" + "eret\n" + : + : "r"(thread), // 0 thread_struct + "r"(0x340), // 1 + "r"(0x0), // 2 link register + "r"(USER_STACK + STACK_SIZE), // 3 + "r"(thread->stack + STACK_SIZE) // 4 + :); + return; + } + + fptr += rec->headsize + rec->datasize; + kfree(rec->pathname, 1); + kfree(rec, 1); + } + + uart_puts("File not found."); + uart_putc(NEWLINE); +} + +// using current thread to run the loaded program +void initramfs_sys_exec(const char *target, trap_frame *tf) { + char *fptr = (char *)initrd_start; + + while (memcmp(fptr + sizeof(cpio_t), "TRAILER!!!", 10)) { + ramfsRec *rec = ramfsNext(fptr); + if (!strcmp(target, rec->pathname)) { + thread_struct *thread = get_current(); + + void *program = kmalloc(rec->filesize, 0); + uart_log(INFO, "Acquired program space: "); + uart_hex((uintptr_t)(thread->start = program)); + uart_putc(NEWLINE); + memcpy(program, fptr + rec->headsize, thread->size = rec->filesize); + + memset(thread->pgd, 0, PAGE_SIZE); + mapping_user_thread(thread, 0); + + // while (thread->sig_busy); // wait for signal handling + thread->sig_reg = 0; + memset(thread->sig_handlers, 0, sizeof(thread->sig_handlers)); + + kfree(rec->pathname, 1); + kfree(rec, 1); + return; + } + fptr += rec->headsize + rec->datasize; + kfree(rec->pathname, 1); + kfree(rec, 1); + } + uart_puts("File not found.\n"); +} \ No newline at end of file diff --git a/lab6/src/irq.c b/lab6/src/irq.c new file mode 100644 index 000000000..eb5e92ad3 --- /dev/null +++ b/lab6/src/irq.c @@ -0,0 +1,96 @@ +#include "irq.h" + +#include "mem.h" +#include "scheduler.h" +#include "timer.h" +#include "uart.h" + +static irqTask *irqTask_head = 0; + +// Disable interrupt before calling irq_add_task() +void irq_add_task(void (*callback)(), int order) { + irqTask *task = kmalloc(sizeof(irqTask), 1); + task->func = callback; + task->order = order; + task->busy = 0; + task->prev = 0; + task->next = 0; + + // 0 -> task -> head -> ... + if (irqTask_head == 0 || task->order < irqTask_head->order) { + task->next = irqTask_head; + task->prev = 0; + if (irqTask_head != 0) irqTask_head->prev = task; + irqTask_head = task; + return; + } + + irqTask *current = irqTask_head; + while (current->next != 0 && current->next->order <= task->order) + current = current->next; + task->next = current->next; + if (current->next != 0) current->next->prev = task; + current->next = task; + task->prev = current; +} + +// to enable interrupts in EL1 +void enable_interrupt() { + asm volatile( + "msr DAIFClr, 0xF\n" // Clear the D, A, I, F bits in the DAIF register + ); +} + +// to disable interrupts in EL1 +void disable_interrupt() { + asm volatile( + "msr DAIFSet, 0xF\n" // Set the D, A, I, F bits to 1 in the DAIF register + ); +} + +void irq_entry(trap_frame *tf) { + disable_interrupt(); // Enter the critical section + + if (*IRQ_PENDING_1 & (1 << 29)) { // UART interrupt + switch (*AUX_MU_IIR & 0x6) { // 0x6 = 0110 -> Get 0x2 and 0x4 + + case 0x2: // 0x2 UART Transmit interrupt + disable_uart_tx_interrupt(); + irq_add_task(uart_tx_irq_handler, ORDER_LAST); + break; + + case 0x4: // 0x4 UART Receive interrupt + disable_uart_rx_interrupt(); + irq_add_task(uart_rx_irq_handler, ORDER_FIRST); + break; + } + } else if (*CORE0_INTERRUPT_SOURCE & 0x2) { + // Core 0 timer interrupt for schedule() + // tell if thread_struct is not the only one in the run_queue + if (get_current() != get_current()->next) schedule(); + + disable_timer_interrupt(); + irq_add_task(timer_irq_handler, ORDER_REGULAR); + } + + enable_interrupt(); // Leave the critical section + + // Preemption: run the task with the highest priority + while (irqTask_head != 0 && !irqTask_head->busy) { + disable_interrupt(); + irqTask *task = irqTask_head; // Get a task from head + task->busy = 1; // Flag the task as under processing + enable_interrupt(); + + task->func(); // Run the tasks with interrupts enabled + + // Remove the task + disable_interrupt(); + if (task->prev != 0) task->prev->next = task->next; + if (task->next != 0) task->next->prev = task->prev; + if (task == irqTask_head) irqTask_head = task->next; + kfree((void *)task, 1); + enable_interrupt(); + } + do_signal(tf); +} \ No newline at end of file diff --git a/lab6/src/linker.ld b/lab6/src/linker.ld new file mode 100644 index 000000000..451d21bfd --- /dev/null +++ b/lab6/src/linker.ld @@ -0,0 +1,14 @@ +SECTIONS +{ + . = 0xFFFF000000000000; + . += 0x80000; + .text : { *(.text.boot) *(.text) } + .rodata : { *(.rodata) } + .data : { *(.data) } + .bss : { + __bss_start = .; + *(.bss) + __bss_end = .; + } +} +__bss_size = (__bss_end - __bss_start) >> 3; /* SIZEOF(.bss); */ diff --git a/lab6/src/main.c b/lab6/src/main.c new file mode 100644 index 000000000..af5faedff --- /dev/null +++ b/lab6/src/main.c @@ -0,0 +1,33 @@ +#include "devtree.h" +#include "initramfs.h" +#include "irq.h" +#include "mem.h" +#include "scheduler.h" +#include "shell.h" +#include "timer.h" +#include "uart.h" + +int main() { + /* Initialization */ + init_uart(); + // uart_log(INFO, "init_uart()\n"); + + uart_log(INFO, "fdt_traverse()\n"); + fdt_traverse(initramfs_callback); + + uart_log(INFO, "init_mem()\n"); + init_mem(); + + uart_log(INFO, "enable_interrupt()\n"); + enable_interrupt(); + + uart_log(INFO, "init_timer()\n"); + init_timer(); + + uart_log(INFO, "sched_init()\n"); + sched_init(); + + run_shell(); + + return 0; +} \ No newline at end of file diff --git a/lab6/src/mbox.c b/lab6/src/mbox.c new file mode 100644 index 000000000..743ecf555 --- /dev/null +++ b/lab6/src/mbox.c @@ -0,0 +1,37 @@ +#include "mbox.h" + +int mbox_call(unsigned char ch, unsigned int *mbox) { + unsigned int r = (unsigned int)((unsigned long)mbox & ~0xF) | (ch & 0xF); + // Wait until we can write to the mailbox + while (*MAILBOX_REG_STATUS & MAILBOX_FULL); + *MAILBOX_REG_WRITE = r; // Write the request + while (1) { + // Wait for the response + while (*MAILBOX_REG_STATUS & MAILBOX_EMPTY); + if (r == *MAILBOX_REG_READ) return mbox[1] == MAILBOX_RESPONSE; + } + return 0; +} + +int get_board_revision(unsigned int *mbox) { + mbox[0] = 7 * 4; + mbox[1] = REQUEST_CODE; + mbox[2] = TAGS_HARDWARE_BOARD_REVISION; + mbox[3] = 4; + mbox[4] = TAG_REQUEST_CODE; + mbox[5] = 0; + mbox[6] = END_TAG; + return mbox_call(MAILBOX_CH_PROP, mbox); +} + +int get_arm_memory_status(unsigned int *mbox) { + mbox[0] = 8 * 4; + mbox[1] = REQUEST_CODE; + mbox[2] = TAGS_HARDWARE_ARM_MEM; + mbox[3] = 8; + mbox[4] = TAG_REQUEST_CODE; + mbox[5] = 0; + mbox[6] = 0; + mbox[7] = END_TAG; + return mbox_call(MAILBOX_CH_PROP, mbox); +} \ No newline at end of file diff --git a/lab6/src/mem.c b/lab6/src/mem.c new file mode 100644 index 000000000..451661496 --- /dev/null +++ b/lab6/src/mem.c @@ -0,0 +1,380 @@ +#include "mem.h" + +#include "command.h" +#include "devtree.h" +#include "hardware.h" +#include "mbox.h" +#include "start.h" +#include "str.h" +#include "uart.h" +#include "utils.h" + +extern char *__bss_end; +extern void *DTB_BASE; +extern void *DTB_END; +extern void *initrd_start; +extern void *initrd_end; + +unsigned int BOARD_REVISION; +unsigned long BASE_MEMORY; +unsigned long TOTAL_MEMORY; +unsigned int NUM_PAGES; + +static struct page *pTable; +static struct page *freeList[BUDDY_MAX_ORDER + 1]; +static struct object *objectCache[CACHE_MAX_ORDER + 1]; + +extern char *__bss_end; // `__bss_end` is defined in linker script + +static char *heap_top; + +static void *simple_malloc(int size) { + void *p = (void *)heap_top; + if (size < 0) return 0; + heap_top += size; + return p; +} + +void init_mem() { + // Simple malloc init: Set heap base address + heap_top = (char *)&__bss_end; + uart_log(INFO, "heap_top = "); + uart_hex((uintptr_t)heap_top); + uart_putc(NEWLINE); + uart_log(INFO, "Simple malloc initialized.\n"); + + unsigned int __attribute__((aligned(16))) mbox[36]; + + // Get board revision + if (BOARD_REVISION == 0) { + get_board_revision(mbox); + BOARD_REVISION = mbox[5]; + } + // Get ARM memory base address and size + if (NUM_PAGES == 0) { + get_arm_memory_status(mbox); + BASE_MEMORY = mbox[5]; + TOTAL_MEMORY = mbox[6]; + NUM_PAGES = (TOTAL_MEMORY - BASE_MEMORY) / PAGE_SIZE; + uart_log(INFO, "Initialized pages: "); + uart_dec(NUM_PAGES); + uart_putc(NEWLINE); + } + + // cmd_info(); + + // Initialize the buddy allocator + int pTableSize = sizeof(struct page) * NUM_PAGES; + pTable = simple_malloc(pTableSize); + uart_log(INFO, "Page Table: "); + uart_hex((uintptr_t)pTable); + uart_putc(NEWLINE); + int current_order = BUDDY_MAX_ORDER; + for (int i = 0; i < NUM_PAGES; i++) { + pTable[i].order = 0; + pTable[i].used = 0; + pTable[i].cache_order = -1; + pTable[i].prev = 0; + pTable[i].next = 0; + if (i % (1 << current_order) == 0) { + while (current_order > 0 && NUM_PAGES - i < (1 << current_order)) { + current_order--; + } + pushPageToFreeList(&freeList[current_order], &pTable[i], current_order); + } + } + + // Reserve memory: + // PGD table + reserveMemory(BOOT_PGD_PAGE_FRAME, BOOT_PGD_PAGE_FRAME + PAGE_SIZE); + // PUD table + reserveMemory(BOOT_PUD_PAGE_FRAME, BOOT_PUD_PAGE_FRAME + PAGE_SIZE); + // PMD table + reserveMemory(BOOT_PMD_PAGE_FRAME, BOOT_PMD_PAGE_FRAME + PAGE_SIZE); + // Kernel & stack; page table + reserveMemory(0x80000 - STACK_SIZE, (uintptr_t)&__bss_end + pTableSize); + // Initramfs + reserveMemory((uintptr_t)initrd_start, (uintptr_t)initrd_end); + // Devicetree + reserveMemory((uintptr_t)DTB_BASE, (uintptr_t)DTB_END); +} + +void reserveMemory(uintptr_t resv_start, uintptr_t resv_end) { + // Round the start and end addresses to the page boundary + resv_start = TO_VIRT(resv_start & ~(PAGE_SIZE - 1)); + resv_end = TO_VIRT((resv_end + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1)); + + uart_log(INFO, "Reserving memory: "); + uart_addr_range(resv_start, resv_end); + uart_putc(NEWLINE); + resv_end--; + + for (int order = BUDDY_MAX_ORDER; order >= 0; order--) { + struct page *current = freeList[order]; + while (current != 0) { + struct page *next = current->next; + uint64_t page_start = page_vaddr(current); + uint64_t page_end = page_start + (PAGE_SIZE << order) - 1; + if (page_start >= resv_start && page_end <= resv_end) { + // [page page] + // Remove the page from the free list + current->used = 1; + removePageFromFreeList(&freeList[order], current); + } else if (resv_start > page_end || resv_end < page_start) { + // ---resv> [page or page] or order = order; + page->used = 0; + page->prev = 0; + page->next = 0; + + if (*list_head == 0 || (*list_head) < page) { + if (*list_head != 0) (*list_head)->prev = page; + page->next = *list_head; + *list_head = page; + return; + } + + struct page *current = *list_head; + while (current->next != 0 && page < current->next) { + current = current->next; + } + page->prev = current; + page->next = current->next; + if (current->next != 0) current->next->prev = page; + current->next = page; +} + +struct page *popFreeList(struct page **list_head) { + if (*list_head == 0) return 0; + + struct page *page = *list_head; + *list_head = page->next; + page->used = 1; + return page; +} + +void removePageFromFreeList(struct page **list_head, struct page *page) { + if (page->prev != 0) page->prev->next = page->next; + if (page->next != 0) page->next->prev = page->prev; + if (page == *list_head) *list_head = page->next; +} + +uintptr_t page_vaddr(struct page *p) { + return TO_VIRT((unsigned long)(p - pTable) * PAGE_SIZE); +} + +uintptr_t page_paddr(struct page *p) { + return TO_PHYS((unsigned long)(p - pTable) * PAGE_SIZE); +} + +void printFreeListByOrder(unsigned int order) { + struct page *page = freeList[order]; + if (page > 0) uart_log(BUDD, ""); + while (page != 0) { + uart_putc(TAB); + uart_addr_range(page_vaddr(page), page_vaddr(page + (1 << order))); + uart_puts(" ["); + if (order < 10) uart_putc(' '); + uart_dec(order); + uart_puts("]\n"); + page = page->next; + } +} + +struct page *lookupBuddy(struct page *page, unsigned int order) { + unsigned int buddy_pfn = (unsigned int)(page - pTable) ^ (1 << order); + return &pTable[buddy_pfn]; +} + +struct page *allocatePagesByOrder(unsigned int order, int silent) { + if (!silent) { + uart_log(BUDD, "Memory block requested: "); + uart_dec(1 << order); + uart_puts(" page(s).\n"); + } + + for (int i = order; i <= BUDDY_MAX_ORDER; i++) { + if (freeList[i] == 0) // No free page available + continue; // Try next order + struct page *page = popFreeList(&freeList[i]); + page->order = order; // Update order of the page + + while (i > order) { // requires splitting + i--; + struct page *buddy = lookupBuddy(page, i); + pushPageToFreeList(&freeList[i], buddy, i); + + if (silent) continue; + // Print information + uart_log(BUDD, "Split "); + uart_addr_range(page_vaddr(page), page_vaddr(page + (1 << i))); + uart_puts("//"); + uart_addr_range(page_vaddr(buddy), page_vaddr(buddy + (1 << i))); + uart_puts(" => ["); + uart_dec(i); + uart_puts("]\n"); + } + if (!silent) { + uart_log(BUDD, "Memory allocated: "); + uart_addr_range(page_vaddr(page), page_vaddr(page + (1 << order))); + uart_putc(NEWLINE); + } + + return page; + } + return 0; +} + +void freePages(struct page *page, unsigned int order, int silence) { + if (!silence) { + uart_log(BUDD, "Free "); + uart_dec(1 << order); + uart_puts("-page memory block starting from "); + uart_hex(page_vaddr(page)); + uart_putc(NEWLINE); + } + mergePages(page, order, silence); +} + +void mergePages(struct page *page, unsigned int order, int silence) { + struct page *current = page; + while (order < BUDDY_MAX_ORDER) { + struct page *buddy = lookupBuddy(current, order); + if (buddy->order != order || buddy->used == 1) break; + + removePageFromFreeList(&freeList[order], buddy); + + if (current > buddy) { + struct page *tmp = current; + current = buddy; + buddy = tmp; + } + + order++; + if (silence) continue; + uart_log(BUDD, "Merge "); + uart_addr_range(page_vaddr(current), + page_vaddr(current + (1 << (order - 1)))); + uart_puts("~~"); + uart_addr_range(page_vaddr(buddy), page_vaddr(buddy + (1 << (order - 1)))); + uart_puts(" => ["); + uart_dec(order); + uart_puts("]\n"); + } + pushPageToFreeList(&freeList[order], current, order); +} + +/* Cache Allocator */ + +void pushObjectToList(struct object **list_head, struct object *object, + unsigned int order) { + object->order = order; + object->next = *list_head; + *list_head = object; +} + +struct object *popObjectFromList(struct object **list_head) { + if (*list_head == 0) return 0; + + struct object *object = *list_head; + *list_head = object->next; + return object; +} + +void *allocateCacheMemory(unsigned int order, int silent) { + if (!silent) { + uart_log(CACH, "Allocating "); + uart_dec(MIN_CACHE_SIZE << order); + uart_puts(" bytes."); + uart_putc(NEWLINE); + } + + if (objectCache[order] == 0) { + struct page *page = allocatePagesByOrder(0, silent); + page->cache_order = order; + uintptr_t page_addr = page_vaddr(page); + int cache_size = MIN_CACHE_SIZE << order; + for (int i = 0; i < PAGE_SIZE; i += cache_size) { + struct object *obj = (struct object *)(uintptr_t)(page_addr + i); + pushObjectToList(&objectCache[order], obj, order); + } + } + void *p = popObjectFromList(&objectCache[order]); + if (!silent) { + uart_log(CACH, "Allocated memory starting from "); + uart_hex((uintptr_t)p); + uart_putc(NEWLINE); + } + return p; +} + +void freeCacheEntry(void *ptr, unsigned int index, int silence) { + if (!silence) { + uart_log(CACH, "Free memory cache of "); + uart_dec(MIN_CACHE_SIZE << index); + uart_puts(" bytes starting from "); + uart_hex((uintptr_t)ptr); + uart_putc(NEWLINE); + } + pushObjectToList(&objectCache[index], ptr, index); +} + +/* Dynamic Memory Allocator */ + +void *kmalloc(unsigned int size, int silent) { + if (size == 0) return 0; + + if (!silent) { + uart_log(INFO, "Memory requested: "); + uart_dec(size); + uart_puts(" byte(s)."); + uart_putc(NEWLINE); + } + + if (size > PAGE_SIZE / 2) { + // Buddy Allocator + int order = 0; + while ((PAGE_SIZE << order) < size) order++; + struct page *page = allocatePagesByOrder(order, silent); + return (void *)page_vaddr(page); + } else { + // Cache Allocator + int power = 0; + while ((1 << power) < size) power++; + int order = (power > 5) ? power - 5 : 0; + return allocateCacheMemory(order, silent); + } +} + +void kfree(void *ptr, int silence) { + // Check if the pointer is page-aligned + struct page *page = &pTable[TO_PHYS(ptr) / PAGE_SIZE]; + if (TO_PHYS(ptr) % PAGE_SIZE == 0) { + // Check if the page is allocated by the buddy allocator + if (page->cache_order == -1) { + // Free the page using the buddy allocator + freePages(page, page->order, silence); + return; + } + } + // Free the object using the cache allocator + struct object *object = ptr; + freeCacheEntry(object, page->cache_order, silence); +} diff --git a/lab6/src/scheduler.S b/lab6/src/scheduler.S new file mode 100644 index 000000000..15fcfbeca --- /dev/null +++ b/lab6/src/scheduler.S @@ -0,0 +1,54 @@ +.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 // set current thread_struct + ret + + /* + struct context_struct { + unsigned long x19; + unsigned long x20; + unsigned long x21; + unsigned long x22; + unsigned long x23; + unsigned long x24; + unsigned long x25; + unsigned long x26; + unsigned long x27; + unsigned long x28; + unsigned long fp; + unsigned long lr; + unsigned long sp; + }; + */ + +.global switch_mm +switch_mm: + dsb ish // ensure write has completed + msr ttbr0_el1, x0 // switch translation based address + tlbi vmalle1is // invalidate all TLB entries + dsb ish // ensure completion of TLB invalidation + isb // clear pipeline + ret + +.global get_current +get_current: + mrs x0, tpidr_el1 // current thread_struct + ret + diff --git a/lab6/src/scheduler.c b/lab6/src/scheduler.c new file mode 100644 index 000000000..c5600fb3a --- /dev/null +++ b/lab6/src/scheduler.c @@ -0,0 +1,152 @@ +#include "scheduler.h" + +#include "irq.h" +#include "mem.h" +#include "start.h" +#include "uart.h" +#include "utils.h" +#include "virtm.h" + +static int IDLE_PROCESS_PID; +static int thread_count = 0; +thread_struct *running_tcircle; + +void sched_init() { + uart_log(INFO, "Initializing Scheduler...\n"); + IDLE_PROCESS_PID = kcreate_thread(idle)->pid; + asm volatile( + "msr tpidr_el1, %0" ::"r"(running_tcircle) // Software Thread ID Register + ); + uart_log(INFO, "Scheduler Initialized.\n"); +} + +static void add_thread(thread_struct **queue, thread_struct *thread) { + if (*queue == 0) { + *queue = thread; + thread->next = thread; + thread->prev = thread; + } else { + // prev <- thread -> *queue + thread->next = *queue; + thread->prev = (*queue)->prev; + // prev -> thread <- *queue + (*queue)->prev->next = thread; + (*queue)->prev = thread; + } +} + +thread_struct *kcreate_thread(void (*func)()) { + thread_struct *thread = kmalloc(sizeof(thread_struct), 0); + uart_log(INFO, "Creating thread PID: "); + uart_dec(thread->pid = thread_count++); + uart_putc(NEWLINE); + + thread->start = func ? func : 0; + thread->size = 0; + thread->state = RUNNING; + + thread->stack = kmalloc(STACK_SIZE, 0); + uart_log(INFO, "Acquired thread kernel stack at "); + uart_hex((uintptr_t)thread->stack); + uart_putc(NEWLINE); + thread->user_stack = 0; // alloc when needed (el0) + + memset(thread->sig_handlers, 0, sizeof(thread->sig_handlers)); + thread->sig_reg = 0; + thread->sig_busy = 0; + + // Added for lab6 + memset(thread->pgd = kmalloc(PAGE_SIZE, 0), 0, PAGE_SIZE); + uart_log(INFO, "Acquired thread pgd at "); + uart_hex((uintptr_t)thread->pgd); + uart_putc(NEWLINE); + + thread->context.lr = (unsigned long)func; + thread->context.sp = (unsigned long)thread->stack + STACK_SIZE; + thread->context.fp = (unsigned long)thread->stack + STACK_SIZE; + add_thread(&running_tcircle, thread); + return thread; +} + +static void remove_thread(thread_struct **queue, thread_struct *thread) { + if (*queue == thread) *queue = (thread->next == thread) ? 0 : thread->next; + thread->next->prev = thread->prev; + thread->prev->next = thread->next; +} + +void list_tcircle() { + thread_struct *thread = running_tcircle; + uart_log(INFO, "All thread(s) in queue:\n"); + do { + uart_log(INFO, "pid = "); + uart_dec(thread->pid); + if (thread->sig_handlers[9] > 0) uart_putc('*'); + uart_puts(", sp: "); + uart_hex((uintptr_t)thread->context.sp); + if (thread->pid == get_current()->pid) uart_puts(" <- current"); + uart_putc(NEWLINE); + thread = thread->next; + } while (thread != running_tcircle); +} + +void schedule() { + switch_mm((unsigned long)TO_PHYS(get_current()->next->pgd)); + switch_to(get_current(), get_current()->next); +} + +void kill_zombies() { + thread_struct *next, *thread = running_tcircle; + do { + next = thread->next; + if (thread->state == DEAD) { + remove_thread(&running_tcircle, thread); + kfree(thread->stack, 0); + if (thread->user_stack) kfree(thread->user_stack, 0); + kfree(thread->pgd, 0); + kfree(thread, 0); + } + thread = next; + } while (thread != running_tcircle); +} + +void idle() { + while (1) { + kill_zombies(); + schedule(); + } +} + +thread_struct *get_thread_by_pid(int pid) { + thread_struct *thread = running_tcircle; + do { + if (thread->pid == pid) return thread; + thread = thread->next; + } while (thread != running_tcircle); + return 0; +} + +void kill_current_thread() { + get_current()->state = DEAD; + schedule(); +} + +void kill_thread_by_pid(int pid) { + if (pid == IDLE_PROCESS_PID) { + uart_log(WARN, "Cannot kill idle process.\n"); + } else { + thread_struct *thread = running_tcircle; + do { + if (thread->pid == pid) { + uart_log(INFO, "Killing pid "); + uart_dec(pid); + uart_putc(NEWLINE); + thread->state = DEAD; + schedule(); + return; + } + thread = thread->next; + } while (thread != running_tcircle); + uart_log(WARN, "Nothing to kill.\n"); + } + schedule(); +} diff --git a/lab6/src/shell.c b/lab6/src/shell.c new file mode 100644 index 000000000..cb9aad578 --- /dev/null +++ b/lab6/src/shell.c @@ -0,0 +1,84 @@ +#include "shell.h" + +#include "command.h" +#include "mem.h" +#include "str.h" +#include "uart.h" + +static void welcome_msg() { + cmd_info(); + uart_puts( + "*******************************\n" + "*** YADOS 0.06 for OSC 2024 ***\n" + "*******************************\n" + "Hopefully this will be Yet Another Dope OS!\n"); + uart_putc(NEWLINE); + cmd_hello(); +} + +void run_shell() { + welcome_msg(); + + char *buffer = kmalloc(SHELL_BUF_SIZE, 1); + while (1) { + uart_putc(NEWLINE); + uart_puts("# "); + read_user_input(buffer); + if (exec_command(buffer)) { + uart_log(WARN, "Command not found.\n"); + } + } +} + +void read_user_input(char *buf) { + int idx = 0; + while (idx < SHELL_BUF_SIZE) { + char c = uart_getc(); + switch (c) { + case NEWLINE: + uart_putc(NEWLINE); + buf[idx] = '\0'; + return; + case DELETE: + if (idx > 0) { + idx--; + uart_putc(BACKSPACE); + uart_putc(' '); + uart_putc(BACKSPACE); + } + break; + case ESC: + uart_putc(NEWLINE); + uart_log(WARN, "ESC detected. Clearing buffer...\n"); + uart_log(INFO, "Press Spacebar or Enter to continue.\n"); + while (1) { + char c = uart_getc(); + if (c == ' ' || c == NEWLINE) break; + } + buf[0] = '\0'; + uart_clear(); + return; + default: + if (c >= 32 && c <= 126) { + uart_putc(c); + buf[idx++] = c; + } + } + } + uart_putc(NEWLINE); + uart_log(WARN, "Buffer overflow. Please re-enter your command.\n"); + buf[0] = '\0'; +} + +int exec_command(const char *input) { + if (strlen(input) == 0) return 0; + struct command *cmd = cmd_list; + while (strcmp(cmd->name, END_OF_COMMAND_LIST)) { + if (!strcmp(cmd->name, input)) { + cmd->func(); + return 0; + } + cmd++; + } + return -1; +} \ No newline at end of file diff --git a/lab6/src/signals.c b/lab6/src/signals.c new file mode 100644 index 000000000..a7294b4de --- /dev/null +++ b/lab6/src/signals.c @@ -0,0 +1,56 @@ +#include "signals.h" + +#include "mem.h" +#include "scheduler.h" +#include "syscalls.h" +#include "traps.h" +#include "uart.h" +#include "utils.h" +#include "virtm.h" + +void signal(int SIGNAL, void (*handler)()) { + get_current()->sig_handlers[SIGNAL] = handler; +} + +void signal_kill(int pid, int SIGNAL) { + thread_struct *thread = get_thread_by_pid(pid); + if (thread == 0) { + uart_log(WARN, "No one gets the signal.\n"); + return; + }; + thread->sig_reg |= 1 << (SIGNAL - 1); // Set the signal pending bit +} + +void do_signal(trap_frame *tf) { + // Prevent nested signal handling + if (get_current()->sig_busy) return; + int sig_num = 1; + while (get_current()->sig_reg) { + if (get_current()->sig_reg & (0x1 << (sig_num - 1))) { + get_current()->sig_busy = 1; // block other signal handling + get_current()->sig_reg &= ~(0x1 << (sig_num - 1)); + + if (get_current()->sig_handlers[sig_num] == 0) { + kill_current_thread(); // Default handler: kill the process + return; + } + + // Save the sigframe + memcpy(get_current()->sig_tf = kmalloc(sizeof(trap_frame), 0), tf, + sizeof(trap_frame)); + get_current()->sig_stack = + kmalloc(STACK_SIZE, 0); // free at sys_sigreturn + + // Map the stack for signal handling + map_pages((unsigned long)get_current()->pgd, SIG_STACK, STACK_SIZE, + (unsigned long)TO_PHYS(get_current()->sig_stack)); + + tf->x30 = (uintptr_t)sigreturn; // Return to sigreturn (traps.S) after + tf->spsr_el1 = 0x340; + tf->elr_el1 = (unsigned long)get_current()->sig_handlers[sig_num]; + tf->sp_el0 = (unsigned long)SIG_STACK + STACK_SIZE; // top of sig_stack + return; // eret to the signal handler (->el0) + } + sig_num++; + } +} \ No newline at end of file diff --git a/lab6/src/start.S b/lab6/src/start.S new file mode 100644 index 000000000..bde3a7215 --- /dev/null +++ b/lab6/src/start.S @@ -0,0 +1,139 @@ +#include + +.section ".text.boot" + +.global _start +_start: + mov x10, x0 // preserve dtb base address + + /* switch from EL2 to EL1 */ + bl from_el2_to_el1 + +/* --- lab6 --- */ + + /* set up kernel page tables */ + ldr x0, =TCR_CONFIG_DEFAULT + msr tcr_el1, x0 // Translation Control Register + + ldr x0, =( \ + (MAIR_DEVICE_nGnRnE << (MAIR_IDX_DEVICE_nGnRnE * 8)) | \ + (MAIR_NORMAL_NOCACHE << (MAIR_IDX_NORMAL_NOCACHE * 8)) \ + ) + msr mair_el1, x0 // Memory Attribute Indirection Register + + mov x0, BOOT_PGD_PAGE_FRAME // PGD's page frame + mov x1, BOOT_PUD_PAGE_FRAME // PUD's page frame + mov x2, BOOT_PMD_PAGE_FRAME // PMD's page frame + + // PGD + ldr x6, =BOOT_PGD_ATTR + orr x6, x1, x6 // set PGD->PUD entries + str x6, [x0] + + // PUD + ldr x6, =BOOT_PUD_ATTR + orr x6, x2, x6 // set PUD[0]->PMD entries + str x6, [x1] + + ldr x6, =BOOT_PUD_DEVICE_ATTR + mov x3, 0x40000000 + orr x6, x3, x6 // set PUD[1]->MMIO device addr + str x6, [x1, 8] + + /* + +-------------------+ 0x00000000 + | PGD[0]->PUD[0] | PGD bottom + | | + |-------------------| 0x00001000 + | PUD[0]->PMD[0] | PUD bottom + | PUD[1]>>0x40000000| PUD to MMIO device + | | + |-------------------| 0x00002000 + | PMD[0]>>0x0 | PMD bottom + | PMD[1]>>0x200000 | PMD to normal memory + | PMD[2]>>0x400000 | ... + | ... | + | PMD[_]>>0x3F000000| PMD to MMIO device + | ... | ... + | PMD[_]>>0x3FE00000| PMD top + | | + |-------------------| 0x00003000 + | ... | + + */ + + + // PMD + mov x3, 0x00000000 // PMD block bottom + mov x4, 0x3F000000 // MMIO device bottom + mov x5, 0x40000000 // PMD block top + +pmd_loop: + cmp x3, x5 // reach PMD top? + beq enable_mmu // leave the loop + cmp x3, x4 // reach MMIO device bottom? + ldr x6, =BOOT_PMD_DEVICE_ATTR // yes, set device PMD entry + bge set_pmd_entry + ldr x6, =BOOT_PMD_NORMAL_ATTR // no, set normal PMD entry + +set_pmd_entry: + orr x6, x3, x6 + str x6, [x2], #8 // x2 += 8 + add x3, x3, 0x200000 + b pmd_loop + +enable_mmu: // mmu: memory management unit + msr ttbr0_el1, x0 // [no need?]will be modified by each user process + msr ttbr1_el1, x0 // also load PGD to the upper translation based register + mrs x2, sctlr_el1 // System Control Register + orr x2, x2, 1 + msr sctlr_el1, x2 // enable MMU, cache remains disabled + + ldr x2, =boot_rest // indirect branch to the virtual address + br x2 + +/* --- lab6^^^ --- */ + +boot_rest: + /* get dtb address from x10 */ + ldr x1, =DTB_BASE // defined in devtree.c + str x10, [x1] + + /* get cpu id */ + mrs x1, mpidr_el1 /* Multiprocessor Affinity Register */ + and x1, x1, #3 + cbnz x1, halt // halt if cpu id != 0 + + /* set exception vector table */ + adr x0, exception_vector_table // defined in traps.S + msr vbar_el1, x0 + + /* set stack pointer */ + ldr x1, =_start + mov sp, x1 + + /* clear bss section */ + ldr x1, =__bss_start + ldr w2, =__bss_size + +bss_reset: + cbz w2, run_main + str xzr, [x1], #8 + sub w2, w2, #1 + cbnz w2, bss_reset + +run_main: + /* branch to main function */ + bl main + +halt: + wfe + b halt + +from_el2_to_el1: + mov x0, (1 << 31) + msr hcr_el2, x0 // EL1 uses aarch64 + mov x0, 0x3C5 // ... 0011 1100 0101 + msr spsr_el2, x0 + msr elr_el2, lr + eret // return to EL1 diff --git a/lab6/src/str.c b/lab6/src/str.c new file mode 100644 index 000000000..e8d54f053 --- /dev/null +++ b/lab6/src/str.c @@ -0,0 +1,30 @@ +#include "str.h" + +int strcmp(const char *str1, const char *str2) { + while (*str1 && *str1 == *str2) str1++, str2++; + return *(unsigned char *)str1 - *(unsigned char *)str2; +} + +char *strncpy(char *dest, const char *src, int n) { + while (n-- && (*dest++ = *src++)); + return dest; +} + +int strlen(const char *str) { + int len = 0; + while (*str++ != '\0') len++; + return len; +} + +char *strcat(char *dest, const char *src) { + char *d = dest; + + // Move the pointer to the end of the dest string + while (*d != '\0') d++; + // Copy the src string to the end of the dest string + while (*src != '\0') *d++ = *src++; + // Add the null terminator + *d = '\0'; + + return dest; +} diff --git a/lab6/src/syscalls.c b/lab6/src/syscalls.c new file mode 100644 index 000000000..4c735a6be --- /dev/null +++ b/lab6/src/syscalls.c @@ -0,0 +1,99 @@ +#include "syscalls.h" + +#include "initramfs.h" +#include "irq.h" +#include "mbox.h" +#include "mem.h" +#include "scheduler.h" +#include "str.h" +#include "traps.h" +#include "uart.h" +#include "utils.h" +#include "virtm.h" + +int sys_getpid() { return get_current()->pid; } + +size_t sys_uart_read(char *buf, size_t size) { + int i = 0; + while (i < size) buf[i++] = uart_getc(); + return i; +} + +size_t sys_uart_write(const char *buf, size_t size) { + int i = 0; + while (i < size) uart_putc(buf[i++]); + return i; + return size; +} + +int sys_exec(const char *name, trap_frame *tf) { + initramfs_sys_exec(name, tf); + return 0; +} + +int sys_fork(trap_frame *tf) { + disable_interrupt(); // prevent schedule premature fork + + thread_struct *parent = get_current(); + thread_struct *child = kcreate_thread(0); + + child->start = parent->start; + uart_log(INFO, "parent->start: "); + uart_hex((uintptr_t)parent->start); + uart_putc(NEWLINE); + + child->size = parent->size; + uart_log(INFO, "parent->size: "); + uart_hex(parent->size); + uart_putc(NEWLINE); + + child->user_stack = kmalloc(STACK_SIZE, 1); + uart_log(INFO, "Acquired thread user stack: "); + uart_hex((uintptr_t)child->stack); + uart_putc(NEWLINE); + + mapping_user_thread(child, 0x4000000); + + // Handling kernel stack (incl. tf from parent) + memcpy(child->stack, parent->stack, STACK_SIZE); + memcpy(child->user_stack, parent->user_stack, STACK_SIZE); + + // Copy signal handlers + memcpy(child->sig_handlers, parent->sig_handlers, + sizeof(parent->sig_handlers)); + + // get child's trap frame from kstack via parent's offset + size_t ksp_offset = (uintptr_t)tf - (uintptr_t)parent->stack; + trap_frame *child_tf = (trap_frame *)(child->stack + ksp_offset); + + child->context.lr = (uintptr_t)child_ret_from_fork; // traps.S + child->context.sp = (uintptr_t)child_tf; // set child's ksp + child->context.fp = (uintptr_t)child_tf; // set child's ksp + child_tf->sp_el0 = tf->sp_el0; // set child's user sp + child_tf->x0 = 0; // ret value of fork() for child + + enable_interrupt(); + return child->pid; +} + +void sys_exit(int status) { + uart_log(status ? WARN : INFO, "Exiting process ...\n"); + kill_current_thread(); +} + +int sys_mbox_call(unsigned char ch, unsigned int *mbox) { + unsigned int *itm = kmalloc(mbox[0], 1); + memcpy(itm, mbox, mbox[0]); + int ret = mbox_call(ch, (unsigned int *)itm); + memcpy(mbox, itm, mbox[0]); + kfree(itm, 1); + return ret; +} + +void sys_sigreturn(trap_frame *tf) { + memcpy(tf, get_current()->sig_tf, sizeof(trap_frame)); + kfree(get_current()->sig_tf, 1); + kfree(get_current()->sig_stack, 1); + get_current()->sig_busy = 0; + return; +} diff --git a/lab6/src/timer.c b/lab6/src/timer.c new file mode 100644 index 000000000..a2ccfa40a --- /dev/null +++ b/lab6/src/timer.c @@ -0,0 +1,114 @@ +#include "timer.h" + +#include "mem.h" +#include "str.h" +#include "uart.h" + +static timerEntry *head = 0; + +void init_timer() { + asm volatile( + "mov x0, 1\n" + "msr cntp_ctl_el0, x0\n"); + + uart_log(INFO, "enable_timer_interrupt()\n"); + enable_timer_interrupt(); + + asm volatile( + "mrs x0, cntfrq_el0\n" + "msr cntp_tval_el0, x0\n"); + + // required by lab5 + uint64_t tmp; + asm volatile("mrs %0, cntkctl_el1" : "=r"(tmp)); + tmp |= 1; + asm volatile("msr cntkctl_el1, %0" : : "r"(tmp)); +} + +void enable_timer_interrupt() { + // Unmask the timer interrupt + asm volatile( + "mov x0, 2\n" + "ldr x1, =%0\n" + "str w0, [x1]\n" + : + : "i"(CORE0_TIMER_IRQCNTL) + : "x0", "x1"); +} + +void disable_timer_interrupt() { + // Mask timer interrupt + asm volatile( + "mov x0, 0\n" + "ldr x1, =%0\n" + "str w0, [x1]\n" + : + : "i"(CORE0_TIMER_IRQCNTL) + : "x0", "x1"); +} + +void timer_irq_handler() { + // Set up the next timer interrupt (frequency >> 5, aka. 1/32 of the + // frequency) + asm volatile( + "mrs x0, cntfrq_el0\n" + "lsr x0, x0, %0\n" + "msr cntp_tval_el0, x0\n" + : + : "i"(TIMER_SPEED) + : "x0"); + + // Check the timer queue + while (head != 0 && timer_get_uptime() >= head->time) { + head->func(head->arg); // Execute the callback function + kfree(head, 1); // Free the memory allocated for the timer node + head = head->next; // Remove the head node after func finished + } + + enable_timer_interrupt(); +} + +uint64_t timer_get_uptime() { + uint64_t cntpct_el0 = 0, cntfrq_el0 = 0; + asm volatile( + "mrs %0, cntpct_el0\n" + "mrs %1, cntfrq_el0\n" + : "=r"(cntpct_el0), "=r"(cntfrq_el0)); + return cntpct_el0 / cntfrq_el0; +} + +void timer_add(void (*callback)(void *), void *arg, int duration) { + // Insert a new timer into the linked list (sorted by time) + timerEntry *timer = kmalloc(sizeof(timerEntry), 1); + timer->func = callback; + timer->arg = arg; + timer->time = timer_get_uptime() + duration; + timer->next = 0; + + if (head == 0 || timer->time < head->time) { + // Insert as the head of the list + timer->next = head; + head = timer; + return; + } + + timerEntry *current = head; + while (current->next != 0 && current->next->time <= timer->time) + current = current->next; + timer->next = current->next; + current->next = timer; +} + +static void show_timer_message(void *arg) { + uart_putc(NEWLINE); + uart_log(INFO, "Timer message: "); + uart_puts((char *)arg); + uart_putc(NEWLINE); + kfree(arg, 1); // Free the memory allocated for arg +} + +void set_timer(const char *msg, int duration) { + timer_add((void (*)(void *))show_timer_message, (void *)msg, duration); +} + +void set_timeup(int *timeup) { *timeup = 1; } diff --git a/lab6/src/traps.S b/lab6/src/traps.S new file mode 100644 index 000000000..f0bfd215c --- /dev/null +++ b/lab6/src/traps.S @@ -0,0 +1,117 @@ +/* save general registers to stack */ +.macro save_all + sub sp, sp, 16 * 17 + 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 x10, spsr_el1 // Saved Program Status Register (EL1) + mrs x11, elr_el1 // Exception Link Register (EL1) + mrs x12, sp_el0 + stp x30, x10, [sp, 16 * 15] + stp x11, x12, [sp, 16 * 16] +.endm + +/* load general registers from stack */ +.macro load_all + ldp x30, x10, [sp, 16 * 15] + ldp x11, x12, [sp, 16 * 16] + msr spsr_el1, x10 + msr elr_el1, x11 + msr sp_el0, x12 + 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, 16 * 17 +.endm + +exception_handler: + save_all + mov x0, sp // pass the stack pointer to exception_entry + bl exception_entry // defined in traps.c + load_all + eret + +irq_handler: + save_all + mov x0, sp + bl irq_entry // defined in irq.c + load_all + eret + +invalid_exc_handler: + save_all + mrs x0, elr_el1 // Exception Link Register (EL1) + mrs x1, esr_el1 // ESR_EL1, Exception Syndrome Register (EL1) + mrs x2, spsr_el1 // Saved Program Status Register (EL1) + mrs x3, ttbr0_el1 // Translation Table Base Register 0 (EL1) + mrs x4, ttbr1_el1 // Translation Table Base Register 1 (EL1) + mrs x5, far_el1 // Fault Address Register, EL1 + bl invalid_entry // defined in traps.c + load_all + eret + +.global child_ret_from_fork +child_ret_from_fork: + load_all // child's kernel stack (trap frame) + eret + +.global sigreturn +sigreturn: + mov x8, 139 + svc 0 + +.macro v_entry label + b \label +.align 7 +.endm + +/* EL1 Exception Vector table + * vector table should be aligned to 0x800 + * and each entry size is 0x80 */ +.align 11 +.global exception_vector_table +exception_vector_table: + // EL1 -> EL1 while using SP_EL0 + v_entry invalid_exc_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler + // EL1 -> EL1 while using SP_EL1 + v_entry invalid_exc_handler + v_entry irq_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler + // EL0 (AArch64) -> EL1 + v_entry exception_handler + v_entry irq_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler + // EL0 (AArch32) -> EL1 + v_entry invalid_exc_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler diff --git a/lab6/src/traps.c b/lab6/src/traps.c new file mode 100644 index 000000000..ec1b82cb8 --- /dev/null +++ b/lab6/src/traps.c @@ -0,0 +1,163 @@ +#include "traps.h" + +#include + +#include "irq.h" +#include "mbox.h" +#include "mem.h" +#include "scheduler.h" +#include "syscalls.h" +#include "uart.h" +#include "virtm.h" + +extern thread_struct *running_tcircle; + +static void print_registers(uint64_t elr, uint64_t esr, uint64_t spsr, + uint64_t ttbr0_el1, uint64_t ttbr1_el1, + uint64_t far_el1) { + // Print spsr_el1 + uart_log(INFO, "spsr_el1: "); + uart_hex(spsr); + uart_putc(NEWLINE); + + // Print elr_el1 + uart_log(INFO, "elr_el1: "); + uart_hex(elr); + uart_putc(NEWLINE); + + // Print esr_el1 + uart_log(INFO, "esr_el1: "); + uart_hex(esr); + uart_putc(NEWLINE); + + // Print ttbr0_el1 + uart_log(INFO, "ttbr0_el1: "); + uart_hex(ttbr0_el1); + uart_putc(NEWLINE); + + // Print ttbr1_el1 + uart_log(INFO, "ttbr1_el1: "); + uart_hex(ttbr1_el1); + uart_putc(NEWLINE); + + // Print far_el1 + uart_log(INFO, "far_el1: "); + uart_hex(far_el1); + uart_putc(NEWLINE); +} + +void exception_entry(trap_frame *tf) { + unsigned long elr, esr, spsr, ttbr0_el1, ttbr1_el1, far_el1; + asm volatile( + "mrs %0, elr_el1\n" + "mrs %1, esr_el1\n" + "mrs %2, spsr_el1\n" + "mrs %3, ttbr0_el1\n" + "mrs %4, ttbr1_el1\n" + "mrs %5, far_el1\n" + : "=r"(elr), // + "=r"(esr), // + "=r"(spsr), // + "=r"(ttbr0_el1), // + "=r"(ttbr1_el1), // + "=r"(far_el1) // + : + : "memory"); + if (esr != 0x56000000) { + print_registers(elr, esr, spsr, ttbr0_el1, ttbr1_el1, far_el1); + while (1); + } + + enable_interrupt(); // Prevent uart blocking the interrupt[?] + + unsigned int syscall_num = tf->x8; + // if (syscall_num > 2) { + // uart_log(INFO, "syscall_num: "); + // uart_dec(syscall_num); + // uart_putc(NEWLINE); + // } + switch (syscall_num) { + case 0: { // int getpid() + unsigned int test = tf->x7; + unsigned int pid = sys_getpid(); + tf->x0 = pid; + if (test != 255) list_tcircle(); + break; + } + case 1: { // size_t uartread(char buf[], size_t size) + char *buf = (char *)tf->x0; + size_t size = tf->x1; + tf->x0 = sys_uart_read(buf, size); + break; + } + case 2: { // size_t uartwrite(const char buf[], size_t size) + char *buf = (char *)tf->x0; + size_t size = tf->x1; + tf->x0 = sys_uart_write(buf, size); + } break; + case 3: // int exec(const char *name, char *const argv[]) + const char *name = (char *)tf->x0; + tf->x0 = sys_exec(name, tf); + tf->elr_el1 = 0x0; + tf->sp_el0 = USER_STACK + STACK_SIZE; + break; + case 4: { // int fork() + tf->x0 = sys_fork(tf); + break; + } + case 5: { // void exit(int status) + int status = tf->x0; + sys_exit(status); + break; + } + case 6: { // int mbox_call(unsigned char ch, unsigned int *mbox) + unsigned char ch = tf->x0; + unsigned int *mbox = (unsigned int *)tf->x1; + tf->x0 = sys_mbox_call(ch, mbox); + break; + } + case 7: { // void kill(int pid) + unsigned int pid = tf->x0; + kill_thread_by_pid(pid); + break; + } + case 8: { // signal(int SIGNAL, void (*handler)()) + unsigned int SIGNAL = tf->x0; + void (*handler)() = (void (*)())tf->x1; + signal(SIGNAL, handler); + break; + } + case 9: { // (signal_)kill(int pid, int SIGNAL) + unsigned int pid = tf->x0; + unsigned int SIGNAL = tf->x1; + uart_log(INFO, "Signal "); + uart_dec(SIGNAL); + uart_puts(" sent to pid "); + uart_dec(pid); + uart_putc(NEWLINE); + signal_kill(pid, SIGNAL); + break; + } + case 139: + sys_sigreturn(tf); + break; + default: + uart_log(ERR, "Invalid system call\n"); + } +} + +void invalid_entry(uint64_t elr, uint64_t esr, uint64_t spsr, + uint64_t ttbr0_el1, uint64_t ttbr1_el1, uint64_t far_el1) { + uart_log(ERR, "The exception handler is not implemented\n"); + print_registers(elr, esr, spsr, ttbr0_el1, ttbr1_el1, far_el1); + + if ((esr & 0xFC000000) == 0x94000000) { + uint32_t svc_number = esr & 0xFFFF; + uart_log(ERR, "SVC Call Exception. SVC Number: "); + uart_dec(svc_number); + uart_putc(NEWLINE); + } else { + uart_log(ERR, "Unknown Exception\n"); + } + while (1); +} \ No newline at end of file diff --git a/lab6/src/uart.c b/lab6/src/uart.c new file mode 100644 index 000000000..e2c050fda --- /dev/null +++ b/lab6/src/uart.c @@ -0,0 +1,200 @@ +#include "uart.h" + +#include + +#include "irq.h" +#include "mem.h" + +static int uart_read_idx = 0; +static int uart_write_idx = 0; +static char uart_read_buffer[UART_BUF_SIZE]; +static char uart_write_buffer[UART_BUF_SIZE]; + +void init_uart() { + // Configure GPIO pins + register unsigned int r = *GPFSEL1; + r &= ~((7 << 12) | (7 << 15)); + r |= (2 << 12) | (2 << 15); + *GPFSEL1 = r; + + // Disable GPIO pull up/down + *GPPUD = 0; + for (int i = 0; i < 150; i++); + *GPPUDCLK0 = (1 << 14) | (1 << 15); + for (int i = 0; i < 150; i++); + *GPPUD = 0; + *GPPUDCLK0 = 0; + + // Initialize mini UART + *AUX_ENABLE |= 1; // Enable mini UART + *AUX_MU_CNTL = 0; // Disable Tx and Rx during setup + *AUX_MU_IER = 0; // Disable interrupt + *AUX_MU_LCR = 3; // Set data size to 8 bits + *AUX_MU_MCR = 0; // Disable auto flow control + *AUX_MU_BAUD = 270; // Set baud rate to 115200 + *AUX_MU_IIR = 6; // No FIFO + *AUX_MU_CNTL = 3; // Enable Tx and Rx + + // Enable AUX interrupts + *ENABLE_IRQS_1 |= 1 << 29; +} + +char uart_getc() { + // Check the data ready field on bit 0 of AUX_MU_LSR_REG + while (!(*AUX_MU_LSR & 0x01)); + char c = (char)(*AUX_MU_IO); // Read from AUX_MU_IO_REG + return c == CARRIAGE_RETURN ? NEWLINE : c; +} + +void uart_putc(char c) { + // Add CARRIAGE_RETURN before NEWLINE + if (c == NEWLINE) uart_putc(CARRIAGE_RETURN); + + // Check the transmitter empty field on bit 5 of AUX_MU_LSR_REG + while (!(*AUX_MU_LSR & 0x20)); + *AUX_MU_IO = c; // Write to AUX_MU_IO_REG +} + +void uart_puts(const char *s) { + while (*s) { + uart_putc(*s++); + } +} + +void uart_clear() { uart_puts("\033[2J\033[H"); } + +void uart_hex(unsigned long h) { + uart_puts("0x"); + unsigned long n; + int length = h > 0xFFFFFFFF ? 60 : 28; + for (int c = length; c >= 0; c -= 4) { + n = (h >> c) & 0xF; + n += n > 9 ? 0x37 : '0'; + uart_putc(n); + if (c / 4 % 8 == 0 && c > 0) uart_putc(' '); + } +} + +void uart_addr_range(uintptr_t start, uintptr_t end) { + uart_hex(start); + uart_puts("-"); + uart_hex(end - 1); +} + +void uart_simple_hex(unsigned int h) { + uart_puts("0x"); + if (h == 0) { + uart_putc('0'); + return; + } + char buf[10]; + int i = 0; + unsigned int n; + while (h > 0) { + n = h % 16; + buf[i++] = n + (n > 9 ? 0x37 : '0'); + h /= 16; + } + for (int j = i - 1; j >= 0; j--) { + uart_putc(buf[j]); + } +} + +void uart_dec(unsigned int d) { + if (d == 0) { + uart_putc('0'); + return; + } + char buf[10]; + int i = 0; + while (d > 0) { + buf[i++] = d % 10 + '0'; + d /= 10; + } + for (int j = i - 1; j >= 0; j--) { + uart_putc(buf[j]); + } +} + +void uart_log(int type, const char *msg) { + switch (type) { + case INFO: + uart_puts("[INFO] "); + break; + case TEST: + uart_puts("[TEST] "); + break; + case BUDD: + uart_puts("[BUDD] "); + break; + case CACH: + uart_puts("[CACH] "); + break; + case WARN: + uart_puts("[WARN] "); + break; + case ERR: + uart_puts("[ERR!] "); + break; + default: + uart_puts("[LOG?] "); + break; + } + uart_puts(msg); +} + +void enable_uart_tx_interrupt() { *AUX_MU_IER |= 0x02; } + +void disable_uart_tx_interrupt() { *AUX_MU_IER &= 0x01; } + +void enable_uart_rx_interrupt() { *AUX_MU_IER |= 0x01; } + +void disable_uart_rx_interrupt() { *AUX_MU_IER &= 0x2; } + +void uart_tx_irq_handler() { + disable_uart_tx_interrupt(); + // Buffer is not empty -> send characters + if (uart_write_idx < UART_BUF_SIZE && + uart_write_buffer[uart_write_idx] != 0) { + while (!(*AUX_MU_LSR & 0x20)); + *AUX_MU_IO = uart_write_buffer[uart_write_idx++]; + enable_uart_tx_interrupt(); + } +} + +void uart_rx_irq_handler() { + while (!(*AUX_MU_LSR & 0x01)); + char c = (char)(*AUX_MU_IO); + uart_read_buffer[uart_read_idx++] = (c == CARRIAGE_RETURN) ? NEWLINE : c; + if (uart_read_idx >= UART_BUF_SIZE) uart_read_idx = 0; + enable_uart_rx_interrupt(); +} + +void uart_async_read(char *buf, int len) { + disable_uart_rx_interrupt(); + for (int i = 0; i < uart_read_idx && i < len; i++) + buf[i] = uart_read_buffer[i]; + buf[uart_read_idx] = 0; + uart_read_idx = 0; +} + +void uart_async_write(const char *s) { + // Copy string to the write buffer + int len = 0; + while (*s != '\0') { + if (len >= UART_BUF_SIZE) { + uart_log(ERR, "Exceed the UART buffer size\n"); + return; + } + if (*s == NEWLINE) { + // Insert CARRIAGE_RETURN before NEWLINE + uart_write_buffer[len++] = CARRIAGE_RETURN; + uart_write_buffer[len++] = NEWLINE; + } else + uart_write_buffer[len++] = *s; + s++; + } + uart_write_buffer[len] = '\0'; + uart_write_idx = 0; // Reset the buffer index + enable_uart_tx_interrupt(); +} \ No newline at end of file diff --git a/lab6/src/utils.c b/lab6/src/utils.c new file mode 100644 index 000000000..dec35665b --- /dev/null +++ b/lab6/src/utils.c @@ -0,0 +1,71 @@ +#include "utils.h" + +int align4(int n) { return n + (4 - n % 4) % 4; } + +int atoi(const char *s) { + int result = 0; + int sign = 1; + int i = 0; + + // Skip leading spaces + while (s[i] == ' ') { + i++; + } + + // Handle positive and negative sign + if (s[i] == '-') { + sign = -1; + i++; + } else if (s[i] == '+') { + i++; + } + + // Convert string to integer + while (s[i] >= '0' && s[i] <= '9') { + result = result * 10 + (s[i] - '0'); + i++; + } + + return sign * result; +} + +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 memcmp(const void *m1, const void *m2, int n) { + const unsigned char *a = m1, *b = m2; + while (n-- > 0) { + if (*a != *b) return *a - *b; + a++; + b++; + } + return 0; +} + +/** + * @brief Copy `n` bytes from `src` to `dest`. + */ +void *memcpy(void *dest, const void *src, int n) { + char *d = dest; + const char *s = src; + while (n--) *d++ = *s++; + return dest; +} + +/** + * @brief Set `n` bytes of `s` to `c`. + */ +void *memset(void *s, int c, int n) { + unsigned char *p = s; + while (n--) *p++ = (unsigned char)c; + return s; +} \ No newline at end of file diff --git a/lab6/src/virtm.c b/lab6/src/virtm.c new file mode 100644 index 000000000..5f3fec0ee --- /dev/null +++ b/lab6/src/virtm.c @@ -0,0 +1,46 @@ +#include "virtm.h" + +#include "mem.h" +#include "start.h" +#include "uart.h" +#include "utils.h" + +extern unsigned long TOTAL_MEMORY; + +static void walk(uintptr_t pt, uintptr_t va, uintptr_t pa) { + uintptr_t *table = (uintptr_t *)pt; + for (int level = 0; level <= 3; level++) { + uintptr_t offset = (va >> (39 - 9 * level)) & 0x1FF; + if (level == 3) { + table[offset] = pa | PTE_NORMAL_ATTR; + return; + } + if (!table[offset]) { + uintptr_t *t = kmalloc(PAGE_SIZE, 0); + uart_log(INFO, "Acquired page table at "); + uart_hex((uintptr_t)t); + uart_puts(" for level "); + uart_dec(level); + uart_putc(NEWLINE); + memset(t, 0, PAGE_SIZE); + table[offset] = TO_PHYS((uintptr_t)t) | PD_TABLE; + } + table = (uintptr_t *)TO_VIRT((table[offset] & ~0xFFF)); + } +} + +void map_pages(uintptr_t pgd, uintptr_t va, uintptr_t size, uintptr_t pa) { + for (int i = 0; i < size; i += PAGE_SIZE) walk(pgd, va + i, pa + i); +} + +void mapping_user_thread(thread_struct *thread, int gpu_mem_size) { + // user code + map_pages((uintptr_t)thread->pgd, 0x0, thread->size, + (uintptr_t)TO_PHYS(thread->start)); + // stack region + map_pages((uintptr_t)thread->pgd, USER_STACK, STACK_SIZE, + (uintptr_t)TO_PHYS(thread->user_stack)); + // GPU + if (gpu_mem_size > 0) + map_pages((uintptr_t)thread->pgd, 0x3C000000, gpu_mem_size, 0x3C000000); +} \ No newline at end of file From c2717a61260d07ca21a5223788f39318a30702be Mon Sep 17 00:00:00 2001 From: "Jonathan Chang, Chun-yien" Date: Thu, 27 Jun 2024 18:35:35 +0800 Subject: [PATCH 6/7] lab7 --- lab7/.gitignore | 7 + lab7/Makefile | 41 ++++ lab7/bootloader/Makefile | 19 ++ lab7/bootloader/boot.c | 61 ++++++ lab7/bootloader/boot.h | 49 +++++ lab7/bootloader/linker.ld | 16 ++ lab7/bootloader/main.c | 41 ++++ lab7/bootloader/start.S | 40 ++++ lab7/include/command.h | 19 ++ lab7/include/devtree.h | 28 +++ lab7/include/hardware.h | 209 +++++++++++++++++++++ lab7/include/initramfs.h | 54 ++++++ lab7/include/irq.h | 20 ++ lab7/include/mbox.h | 24 +++ lab7/include/mem.h | 50 +++++ lab7/include/scheduler.h | 81 ++++++++ lab7/include/shell.h | 7 + lab7/include/signals.h | 12 ++ lab7/include/start.h | 44 +++++ lab7/include/str.h | 6 + lab7/include/syscalls.h | 31 ++++ lab7/include/timer.h | 24 +++ lab7/include/tmpfs.h | 34 ++++ lab7/include/traps.h | 38 ++++ lab7/include/uart.h | 56 ++++++ lab7/include/utils.h | 24 +++ lab7/include/vfs.h | 82 ++++++++ lab7/include/virtm.h | 15 ++ lab7/initramfs.cpio | Bin 0 -> 1149952 bytes lab7/sender.sh | 27 +++ lab7/src/command.c | 225 ++++++++++++++++++++++ lab7/src/devtree.c | 64 +++++++ lab7/src/initramfs.c | 262 ++++++++++++++++++++++++++ lab7/src/irq.c | 102 ++++++++++ lab7/src/linker.ld | 14 ++ lab7/src/main.c | 37 ++++ lab7/src/mbox.c | 160 ++++++++++++++++ lab7/src/mem.c | 380 ++++++++++++++++++++++++++++++++++++++ lab7/src/scheduler.S | 54 ++++++ lab7/src/scheduler.c | 237 ++++++++++++++++++++++++ lab7/src/shell.c | 84 +++++++++ lab7/src/signals.c | 68 +++++++ lab7/src/start.S | 139 ++++++++++++++ lab7/src/str.c | 33 ++++ lab7/src/syscalls.c | 204 ++++++++++++++++++++ lab7/src/timer.c | 117 ++++++++++++ lab7/src/tmpfs.c | 174 +++++++++++++++++ lab7/src/traps.S | 117 ++++++++++++ lab7/src/traps.c | 228 +++++++++++++++++++++++ lab7/src/uart.c | 261 ++++++++++++++++++++++++++ lab7/src/utils.c | 71 +++++++ lab7/src/vfs.c | 309 +++++++++++++++++++++++++++++++ lab7/src/virtm.c | 56 ++++++ 53 files changed, 4555 insertions(+) create mode 100644 lab7/.gitignore create mode 100644 lab7/Makefile create mode 100644 lab7/bootloader/Makefile create mode 100644 lab7/bootloader/boot.c create mode 100644 lab7/bootloader/boot.h create mode 100644 lab7/bootloader/linker.ld create mode 100644 lab7/bootloader/main.c create mode 100644 lab7/bootloader/start.S create mode 100644 lab7/include/command.h create mode 100644 lab7/include/devtree.h create mode 100644 lab7/include/hardware.h create mode 100644 lab7/include/initramfs.h create mode 100644 lab7/include/irq.h create mode 100644 lab7/include/mbox.h create mode 100644 lab7/include/mem.h create mode 100644 lab7/include/scheduler.h create mode 100644 lab7/include/shell.h create mode 100644 lab7/include/signals.h create mode 100644 lab7/include/start.h create mode 100644 lab7/include/str.h create mode 100644 lab7/include/syscalls.h create mode 100644 lab7/include/timer.h create mode 100644 lab7/include/tmpfs.h create mode 100644 lab7/include/traps.h create mode 100644 lab7/include/uart.h create mode 100644 lab7/include/utils.h create mode 100644 lab7/include/vfs.h create mode 100644 lab7/include/virtm.h create mode 100644 lab7/initramfs.cpio create mode 100755 lab7/sender.sh create mode 100644 lab7/src/command.c create mode 100644 lab7/src/devtree.c create mode 100644 lab7/src/initramfs.c create mode 100644 lab7/src/irq.c create mode 100644 lab7/src/linker.ld create mode 100644 lab7/src/main.c create mode 100644 lab7/src/mbox.c create mode 100644 lab7/src/mem.c create mode 100644 lab7/src/scheduler.S create mode 100644 lab7/src/scheduler.c create mode 100644 lab7/src/shell.c create mode 100644 lab7/src/signals.c create mode 100644 lab7/src/start.S create mode 100644 lab7/src/str.c create mode 100644 lab7/src/syscalls.c create mode 100644 lab7/src/timer.c create mode 100644 lab7/src/tmpfs.c create mode 100644 lab7/src/traps.S create mode 100644 lab7/src/traps.c create mode 100644 lab7/src/uart.c create mode 100644 lab7/src/utils.c create mode 100644 lab7/src/vfs.c create mode 100644 lab7/src/virtm.c diff --git a/lab7/.gitignore b/lab7/.gitignore new file mode 100644 index 000000000..36e2308e8 --- /dev/null +++ b/lab7/.gitignore @@ -0,0 +1,7 @@ +.vscode +build +rootfs +temp +dump.* +*.img +*.dtb diff --git a/lab7/Makefile b/lab7/Makefile new file mode 100644 index 000000000..c6dde9a1a --- /dev/null +++ b/lab7/Makefile @@ -0,0 +1,41 @@ +ARMGNU ?= aarch64-linux-gnu + +CFLAGS = -Iinclude -nostdlib -nostartfiles -ffreestanding -mgeneral-regs-only -Wall -g +ASMFLAGS = -Iinclude +QEMUFLAGS = -M raspi3b -display none -serial null -serial stdio +QEMUFLAGSDISP = -M raspi3b -serial null -serial stdio + +SRC_DIR = src +BUILD_DIR = build + +all: clean kernel8.img + +clean: + rm -rf $(BUILD_DIR) *.img + +build: + mkdir -p $(BUILD_DIR) + +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(BUILD_DIR) + $(ARMGNU)-gcc $(CFLAGS) -c $< -o $@ + +$(BUILD_DIR)/_%.o: $(SRC_DIR)/%.S | $(BUILD_DIR) + $(ARMGNU)-gcc $(ASMFLAGS) -c $< -o $@ + +SRC_FILES = $(wildcard $(SRC_DIR)/*.c) +ASM_FILES = $(wildcard $(SRC_DIR)/*.S) +OBJ_FILES = $(SRC_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%.o) +OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/_%.o) + +kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) + $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) + $(ARMGNU)-objcopy -O binary $(BUILD_DIR)/kernel8.elf kernel8.img + +qemu: all initramfs.cpio bcm2710-rpi-3-b-plus.dtb + clear & qemu-system-aarch64 $(QEMUFLAGS) -kernel kernel8.img -initrd initramfs.cpio -dtb bcm2710-rpi-3-b-plus.dtb + +dpqemu: all initramfs.cpio bcm2710-rpi-3-b-plus.dtb + clear & qemu-system-aarch64 $(QEMUFLAGSDISP) -kernel kernel8.img -initrd initramfs.cpio -dtb bcm2710-rpi-3-b-plus.dtb + +debug: all initramfs.cpio bcm2710-rpi-3-b-plus.dtb + qemu-system-aarch64 $(QEMUFLAGS) -kernel kernel8.img -initrd initramfs.cpio -dtb bcm2710-rpi-3-b-plus.dtb -S -s -d int \ No newline at end of file diff --git a/lab7/bootloader/Makefile b/lab7/bootloader/Makefile new file mode 100644 index 000000000..d7882aa27 --- /dev/null +++ b/lab7/bootloader/Makefile @@ -0,0 +1,19 @@ +ARMGNU ?= aarch64-linux-gnu +CFLAGS = -nostdlib -nostartfiles -ffreestanding -mgeneral-regs-only -Wall -g +QEMUFLAGS = -M raspi3b -display none -serial null -serial pty + +all: clean bootloader.img + +clean: + rm -rf build *.img + +bootloader.img: + mkdir -p build + $(ARMGNU)-gcc $(CFLAGS) -c main.c -o build/main.o + $(ARMGNU)-gcc $(CFLAGS) -c start.S -o build/start.o + $(ARMGNU)-gcc $(CFLAGS) -c boot.c -o build/boot.o + $(ARMGNU)-ld -T linker.ld -o build/bootloader.elf build/main.o build/start.o build/boot.o + $(ARMGNU)-objcopy -O binary build/bootloader.elf bootloader.img + +qemu: clean bootloader.img + qemu-system-aarch64 $(QEMUFLAGS) -kernel bootloader.img -initrd ../initramfs.cpio -dtb ../bcm2710-rpi-3-b-plus.dtb \ No newline at end of file diff --git a/lab7/bootloader/boot.c b/lab7/bootloader/boot.c new file mode 100644 index 000000000..d4ff535f5 --- /dev/null +++ b/lab7/bootloader/boot.c @@ -0,0 +1,61 @@ +#include "boot.h" + +void init_uart() { + // Configure GPIO pins + register unsigned int r = *GPFSEL1; + r &= ~((7 << 12) | (7 << 15)); + r |= (2 << 12) | (2 << 15); + *GPFSEL1 = r; + + *GPPUD = 0; + for (int i = 0; i < 150; i++) asm volatile("nop"); + *GPPUDCLK0 = (1 << 14) | (1 << 15); + for (int i = 0; i < 150; i++) asm volatile("nop"); + *GPPUD = 0; + *GPPUDCLK0 = 0; + + *AUX_ENABLE |= 1; + *AUX_MU_CNTL = 0; + *AUX_MU_IER = 0; + *AUX_MU_LCR = 3; + *AUX_MU_MCR = 0; + *AUX_MU_BAUD = 270; + *AUX_MU_IIR = 6; + *AUX_MU_CNTL = 3; +} + +char uart_recv() { + while (!(*AUX_MU_LSR & 0x01)) asm volatile("nop"); + return (char)(*AUX_MU_IO); +} + +void uart_putc(char c) { + if (c == '\n') uart_putc('\r'); + while (!(*AUX_MU_LSR & 0x20)) asm volatile("nop"); + *AUX_MU_IO = c; +} + +void uart_puts(const char *s) { + while (*s) uart_putc(*s++); +} + +int atoi(const char *s) { + int result = 0; + int sign = 1; + int i = 0; + + while (s[i] == ' ') i++; + + if (s[i] == '-') { + sign = -1; + i++; + } else if (s[i] == '+') + i++; + + while (s[i] >= '0' && s[i] <= '9') { + result = result * 10 + (s[i] - '0'); + i++; + } + + return sign * result; +} \ No newline at end of file diff --git a/lab7/bootloader/boot.h b/lab7/bootloader/boot.h new file mode 100644 index 000000000..84cece020 --- /dev/null +++ b/lab7/bootloader/boot.h @@ -0,0 +1,49 @@ +#ifndef BOOT_H +#define BOOT_H + +/* ==================== GPIO ==================== */ +#define MMIO_BASE 0x3F000000 + +#define GPFSEL0 ((volatile unsigned int *)(MMIO_BASE + 0x00200000)) +#define GPFSEL1 ((volatile unsigned int *)(MMIO_BASE + 0x00200004)) +#define GPFSEL2 ((volatile unsigned int *)(MMIO_BASE + 0x00200008)) +#define GPFSEL3 ((volatile unsigned int *)(MMIO_BASE + 0x0020000C)) +#define GPFSEL4 ((volatile unsigned int *)(MMIO_BASE + 0x00200010)) +#define GPFSEL5 ((volatile unsigned int *)(MMIO_BASE + 0x00200014)) +#define GPSET0 ((volatile unsigned int *)(MMIO_BASE + 0x0020001C)) +#define GPSET1 ((volatile unsigned int *)(MMIO_BASE + 0x00200020)) +#define GPCLR0 ((volatile unsigned int *)(MMIO_BASE + 0x00200028)) +#define GPCLR1 ((volatile unsigned int *)(MMIO_BASE + 0x0020002C)) +#define GPLEV0 ((volatile unsigned int *)(MMIO_BASE + 0x00200034)) +#define GPLEV1 ((volatile unsigned int *)(MMIO_BASE + 0x00200038)) +#define GPEDS0 ((volatile unsigned int *)(MMIO_BASE + 0x00200040)) +#define GPEDS1 ((volatile unsigned int *)(MMIO_BASE + 0x00200044)) +#define GPHEN0 ((volatile unsigned int *)(MMIO_BASE + 0x00200064)) +#define GPHEN1 ((volatile unsigned int *)(MMIO_BASE + 0x00200068)) +#define GPPUD ((volatile unsigned int *)(MMIO_BASE + 0x00200094)) +#define GPPUDCLK0 ((volatile unsigned int *)(MMIO_BASE + 0x00200098)) +#define GPPUDCLK1 ((volatile unsigned int *)(MMIO_BASE + 0x0020009C)) + +/* ==================== UART ==================== */ +#define AUX_ENABLE ((volatile unsigned int *)(MMIO_BASE + 0x00215004)) +#define AUX_MU_IO ((volatile unsigned int *)(MMIO_BASE + 0x00215040)) +#define AUX_MU_IER ((volatile unsigned int *)(MMIO_BASE + 0x00215044)) +#define AUX_MU_IIR ((volatile unsigned int *)(MMIO_BASE + 0x00215048)) +#define AUX_MU_LCR ((volatile unsigned int *)(MMIO_BASE + 0x0021504C)) +#define AUX_MU_MCR ((volatile unsigned int *)(MMIO_BASE + 0x00215050)) +#define AUX_MU_LSR ((volatile unsigned int *)(MMIO_BASE + 0x00215054)) +#define AUX_MU_MSR ((volatile unsigned int *)(MMIO_BASE + 0x00215058)) +#define AUX_MU_SCRATCH ((volatile unsigned int *)(MMIO_BASE + 0x0021505C)) +#define AUX_MU_CNTL ((volatile unsigned int *)(MMIO_BASE + 0x00215060)) +#define AUX_MU_STAT ((volatile unsigned int *)(MMIO_BASE + 0x00215064)) +#define AUX_MU_BAUD ((volatile unsigned int *)(MMIO_BASE + 0x00215068)) + +void init_uart(); +char uart_recv(); +void uart_putc(char c); +void uart_puts(const char *s); + +/* ==================== UTILS ==================== */ +int atoi(const char *s); + +#endif // BOOT_H \ No newline at end of file diff --git a/lab7/bootloader/linker.ld b/lab7/bootloader/linker.ld new file mode 100644 index 000000000..556816b11 --- /dev/null +++ b/lab7/bootloader/linker.ld @@ -0,0 +1,16 @@ +SECTIONS +{ + . = 0x60000; + __loader_start = .; + .text : { *(.text.boot) *(.text) } + .rodata : { *(.rodata) } + .data : { *(.data) } + .bss : { + __bss_start = .; + *(.bss) + __bss_end = .; + } + __loader_end = .; +} +__bss_size = SIZEOF(.bss); +__loader_size = __loader_end - __loader_start; \ No newline at end of file diff --git a/lab7/bootloader/main.c b/lab7/bootloader/main.c new file mode 100644 index 000000000..cc8faa0fe --- /dev/null +++ b/lab7/bootloader/main.c @@ -0,0 +1,41 @@ +#include "boot.h" + +int main() { + init_uart(); + uart_puts("\033[2J\033[H"); + uart_puts( + "UART Bootloader\n" + "Waiting for kernel...\n"); + + // Get kernel image size + char buf[16] = {0}; + for (int i = 0; i < 16; i++) { + buf[i] = uart_recv(); + if (buf[i] == '\n') { + buf[i] = '\0'; + break; + } + } + + // Load kernel image + uart_puts("Kernel size: "); + uart_puts(buf); + uart_puts(" bytes.\n"); + uart_puts("Loading the kernel image...\n"); + + unsigned int size = atoi(buf); + char *kernel = (char *)0x80000; + while (size--) *kernel++ = uart_recv(); + + // Restore registers x0 x1 x2 x3 + asm volatile( + "mov x0, x10\n" + "mov x1, x11\n" + "mov x2, x12\n" + "mov x3, x13\n" + "mov x30, 0x80000\n" + "ret\n" // Jump to the new kernel + ); + + return 0; +} \ No newline at end of file diff --git a/lab7/bootloader/start.S b/lab7/bootloader/start.S new file mode 100644 index 000000000..ec8488dde --- /dev/null +++ b/lab7/bootloader/start.S @@ -0,0 +1,40 @@ +.section ".text.boot" + +.global _start + +_start: + /* save registers x0 x1 x2 x3 */ + mov x10, x0 /* dtb_base address */ + mov x11, x1 + mov x12, x2 + mov x12, x3 + + /* relocate bootloader */ + ldr x1, =0x80000 + ldr x2, =__loader_start // 0x60000 + ldr w3, =__loader_size + +relocate: + ldr x4, [x1], #8 + str x4, [x2], #8 + sub w3, w3, #1 + cbnz w3, relocate + + /* set stack pointer */ + ldr x1, =_start + mov sp, x1 + + /* clear bss section */ + ldr x1, =__bss_start + ldr w2, =__bss_size + +bss_reset: + cbz w2, run_main + str xzr, [x1], #8 + sub w2, w2, #1 + cbnz w2, bss_reset + +run_main: + /* branch to main function */ + bl main-0x20000 + b run_main diff --git a/lab7/include/command.h b/lab7/include/command.h new file mode 100644 index 000000000..b48238d64 --- /dev/null +++ b/lab7/include/command.h @@ -0,0 +1,19 @@ +#pragma once + +#define MAX_BUF_SIZE 1024 +#define END_OF_COMMAND_LIST "NULL" + +struct command { + const char *name; + const char *help; + void (*func)(void); +}; + +extern struct command cmd_list[]; +extern unsigned int BOARD_REVISION; +extern unsigned long BASE_MEMORY; +extern unsigned int NUM_PAGES; + +// Commands +void cmd_info(); +void cmd_hello(); \ No newline at end of file diff --git a/lab7/include/devtree.h b/lab7/include/devtree.h new file mode 100644 index 000000000..0a1e5bf65 --- /dev/null +++ b/lab7/include/devtree.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include "hardware.h" + +struct fdt_header { + uint32_t magic; + uint32_t totalsize; + uint32_t off_dt_struct; + uint32_t off_dt_strings; + uint32_t off_mem_rsvmap; + uint32_t version; + uint32_t last_comp_version; + uint32_t boot_cpuid_phys; + uint32_t size_dt_strings; + uint32_t size_dt_struct; +}; + +/** + * @brief Convert a 4-byte big-endian sequence to little-endian. + * + * @param s: big-endian sequence + * @return little-endian sequence + */ +uint32_t be2le(const void *s); + +void fdt_traverse(void (*callback)(void *, char *)); diff --git a/lab7/include/hardware.h b/lab7/include/hardware.h new file mode 100644 index 000000000..c3714e381 --- /dev/null +++ b/lab7/include/hardware.h @@ -0,0 +1,209 @@ +#pragma once + +#include "start.h" + +#define VM 0xFFFF000000000000 + +#define TO_VIRT(x) ((uintptr_t)(x) | VM) +#define TO_PHYS(x) ((uintptr_t)(x) & ~VM) + +#define MMIO_BASE TO_VIRT(0x3F000000) + +// ARM Interrupt Registers + +#define IRQ_BASIC_PENDING (volatile unsigned int *)(MMIO_BASE + 0x0000B200) +#define IRQ_PENDING_1 (volatile unsigned int *)(MMIO_BASE + 0x0000B204) +#define IRQ_PENDING_2 (volatile unsigned int *)(MMIO_BASE + 0x0000B208) +#define FIQ_CONTROL (volatile unsigned int *)(MMIO_BASE + 0x0000B20C) +#define ENABLE_IRQS_1 (volatile unsigned int *)(MMIO_BASE + 0x0000B210) +#define ENABLE_IRQS_2 (volatile unsigned int *)(MMIO_BASE + 0x0000B214) +#define ENABLE_BASIC_IRQS (volatile unsigned int *)(MMIO_BASE + 0x0000B218) +#define DISABLE_IRQS_1 (volatile unsigned int *)(MMIO_BASE + 0x0000B21C) +#define DISABLE_IRQS_2 (volatile unsigned int *)(MMIO_BASE + 0x0000B220) +#define DISABLE_BASIC_IRQS (volatile unsigned int *)(MMIO_BASE + 0x0000B224) + +#define CORE0_INTERRUPT_SOURCE (volatile unsigned int *)TO_VIRT(0x40000060) + +// Timers interrupt control registers + +#define CORE0_TIMER_IRQCNTL TO_VIRT(0x40000040) +#define CORE1_TIMER_IRQCNTL TO_VIRT(0x40000044) +#define CORE2_TIMER_IRQCNTL TO_VIRT(0x40000048) +#define CORE3_TIMER_IRQCNTL TO_VIRT(0x4000004C) + +// Where to route timer interrupt to, IRQ/FIQ +// Setting both the IRQ and FIQ bit gives an FIQ +#define TIMER0_IRQ 0x01 +#define TIMER1_IRQ 0x02 +#define TIMER2_IRQ 0x04 +#define TIMER3_IRQ 0x08 +#define TIMER0_FIQ 0x10 +#define TIMER1_FIQ 0x20 +#define TIMER2_FIQ 0x40 +#define TIMER3_FIQ 0x80 + +// PASSWORD + +#define PM_PASSWORD 0x5A000000 +#define PM_RSTC (volatile unsigned int *)(MMIO_BASE + 0x0010001C) +#define PM_WDOG (volatile unsigned int *)(MMIO_BASE + 0x00100024) +// #define PM_RSTC (volatile unsigned int *)0x3F10001C +// #define PM_WDOG (volatile unsigned int *)0x3F100024 + +// DEVICETREE + +#define FDT_BEGIN_NODE 0x00000001 +#define FDT_END_NODE 0x00000002 +#define FDT_PROP 0x00000003 +#define FDT_NOP 0x00000004 +#define FDT_END 0x00000009 + +// GPIO + +#define GPFSEL0 ((volatile unsigned int *)(MMIO_BASE + 0x00200000)) +#define GPFSEL1 ((volatile unsigned int *)(MMIO_BASE + 0x00200004)) +#define GPFSEL2 ((volatile unsigned int *)(MMIO_BASE + 0x00200008)) +#define GPFSEL3 ((volatile unsigned int *)(MMIO_BASE + 0x0020000C)) +#define GPFSEL4 ((volatile unsigned int *)(MMIO_BASE + 0x00200010)) +#define GPFSEL5 ((volatile unsigned int *)(MMIO_BASE + 0x00200014)) +#define GPSET0 ((volatile unsigned int *)(MMIO_BASE + 0x0020001C)) +#define GPSET1 ((volatile unsigned int *)(MMIO_BASE + 0x00200020)) +#define GPCLR0 ((volatile unsigned int *)(MMIO_BASE + 0x00200028)) +#define GPCLR1 ((volatile unsigned int *)(MMIO_BASE + 0x0020002C)) +#define GPLEV0 ((volatile unsigned int *)(MMIO_BASE + 0x00200034)) +#define GPLEV1 ((volatile unsigned int *)(MMIO_BASE + 0x00200038)) +#define GPEDS0 ((volatile unsigned int *)(MMIO_BASE + 0x00200040)) +#define GPEDS1 ((volatile unsigned int *)(MMIO_BASE + 0x00200044)) +// Skip GPRENn, GPFENn +#define GPHEN0 ((volatile unsigned int *)(MMIO_BASE + 0x00200064)) +#define GPHEN1 ((volatile unsigned int *)(MMIO_BASE + 0x00200068)) +// Skip GPLENn, GPARENn +#define GPPUD ((volatile unsigned int *)(MMIO_BASE + 0x00200094)) +#define GPPUDCLK0 ((volatile unsigned int *)(MMIO_BASE + 0x00200098)) +#define GPPUDCLK1 ((volatile unsigned int *)(MMIO_BASE + 0x0020009C)) + +// UART + +#define AUX_ENABLE ((volatile unsigned int *)(MMIO_BASE + 0x00215004)) +#define AUX_MU_IO ((volatile unsigned int *)(MMIO_BASE + 0x00215040)) +#define AUX_MU_IER ((volatile unsigned int *)(MMIO_BASE + 0x00215044)) +#define AUX_MU_IIR ((volatile unsigned int *)(MMIO_BASE + 0x00215048)) +#define AUX_MU_LCR ((volatile unsigned int *)(MMIO_BASE + 0x0021504C)) +#define AUX_MU_MCR ((volatile unsigned int *)(MMIO_BASE + 0x00215050)) +#define AUX_MU_LSR ((volatile unsigned int *)(MMIO_BASE + 0x00215054)) +#define AUX_MU_MSR ((volatile unsigned int *)(MMIO_BASE + 0x00215058)) +#define AUX_MU_SCRATCH ((volatile unsigned int *)(MMIO_BASE + 0x0021505C)) +#define AUX_MU_CNTL ((volatile unsigned int *)(MMIO_BASE + 0x00215060)) +#define AUX_MU_STAT ((volatile unsigned int *)(MMIO_BASE + 0x00215064)) +#define AUX_MU_BAUD ((volatile unsigned int *)(MMIO_BASE + 0x00215068)) + +// MAILBOX + +#define MAILBOX_BASE (MMIO_BASE + 0x0000B880) + +// mailbox register +#define MAILBOX_REG_READ ((volatile unsigned int *)(MAILBOX_BASE + 0x00000000)) +#define MAILBOX_REG_STATUS \ + ((volatile unsigned int *)(MAILBOX_BASE + 0x00000018)) +#define MAILBOX_REG_WRITE ((volatile unsigned int *)(MAILBOX_BASE + 0x00000020)) + +// mailbox channel +// https://github.com/raspberrypi/firmware/wiki/Mailboxes#channels +#define MAILBOX_CH_POWER 0 +#define MAILBOX_CH_FB 1 +#define MAILBOX_CH_VUART 2 +#define MAILBOX_CH_VCHIQ 3 +#define MAILBOX_CH_LEDS 4 +#define MAILBOX_CH_BTNS 5 +#define MAILBOX_CH_TOUCH 6 +#define MAILBOX_CH_COUNT 7 +#define MAILBOX_CH_PROP 8 + +// mailbox status +#define MAILBOX_EMPTY 0x40000000 +#define MAILBOX_FULL 0x80000000 +#define MAILBOX_REQUEST 0x00000000 +#define MAILBOX_RESPONSE 0x80000000 +#define MAILBOX_FAILED 0x80000001 +#define TAG_REQUEST_CODE 0x00000000 +#define END_TAG 0x00000000 + +// tags +#define TAGS_REQ_CODE 0x00000000 +#define TAGS_REQ_SUCCEED 0x80000000 +#define TAGS_REQ_FAILED 0x80000001 +#define TAGS_END 0x00000000 + +// hardware tags operator +#define TAGS_HARDWARE_BOARD_MODEL 0x00010001 +#define TAGS_HARDWARE_BOARD_REVISION 0x00010002 +#define TAGS_HARDWARE_MAC_ADDR 0x00010003 +#define TAGS_HARDWARE_BOARD_SERIAL 0x00010004 +#define TAGS_HARDWARE_ARM_MEM 0x00010005 +#define TAGS_HARDWARE_VC_MEM 0x00010006 +#define TAGS_HARDWARE_CLOCKS 0x00010007 + +// CLOCK ID + +#define CLOCK_ID_RESERVED 0x000000000 +#define CLOCK_ID_EMMC 0x000000001 +#define CLOCK_ID_UART 0x000000002 +#define CLOCK_ID_ARM 0x000000003 +#define CLOCK_ID_CORE 0x000000004 +#define CLOCK_ID_V3D 0x000000005 +#define CLOCK_ID_H264 0x000000006 +#define CLOCK_ID_ISP 0x000000007 +#define CLOCK_ID_SDRAM 0x000000008 +#define CLOCK_ID_PIXEL 0x000000009 +#define CLOCK_ID_PWM 0x00000000a +#define CLOCK_ID_HEVC 0x00000000b +#define CLOCK_ID_EMMC2 0x00000000c +#define CLOCK_ID_M2MC 0x00000000d +#define CLOCK_ID_PIXEL_BVB 0x00000000e + +// clock tags operator +#define TAGS_GET_CLOCK 0x00030002 +#define TAGS_SET_CLOCK 0x00038002 + +// framebuffer tages operator +#define FB_ALLOC_BUFFER 0x00040001 +#define FB_FREE_BUFFER 0x00048001 + +#define FB_BLANK_SCREEN 0x00040002 + +#define FB_PHY_WID_HEIGHT_GET 0x00040003 +#define FB_PHY_WID_HEIGHT_TEST 0x00044003 +#define FB_PHY_WID_HEIGHT_SET 0x00048003 + +#define FB_VIR_WID_HEIGHT_GET 0x00040004 +#define FB_VIR_WID_HEIGHT_TEST 0x00044004 +#define FB_VIR_WID_HEIGHT_SET 0x00048004 + +#define FB_DEPTH_GET 0x00040005 +#define FB_DEPTH_TEST 0x00044005 +#define FB_DEPTH_SET 0x00048005 + +#define FB_PIXEL_ORDER_GET 0x00040006 +#define FB_PIXEL_ORDER_TEST 0x00044006 +#define FB_PIXEL_ORDER_SET 0x00048006 + +#define FB_ALPHA_MODE_GET 0x00040007 +#define FB_ALPHA_MODE_TEST 0x00044007 +#define FB_ALPHA_MODE_SET 0x00048007 + +#define FB_PITCH_GET 0x00040008 + +#define FB_VIR_OFFSET_GET 0x00040009 +#define FB_VIR_OFFSET_TEST 0x00044009 +#define FB_VIR_OFFSET_SET 0x00048009 + +#define FB_OVERSCAN_GET 0x0004000A +#define FB_OVERSCAN_TEST 0x0004400A +#define FB_OVERSCAN_SET 0x0004800A + +#define FB_PALETTE_GET 0x0004000B +#define FB_PALETTE_TEST 0x0004400B +#define FB_PALETTE_SET 0x0004800B + +#define FB_CURSOR_INFO_SET 0x00008010 +#define FB_CURSOR_STATE_SET 0x00008011 diff --git a/lab7/include/initramfs.h b/lab7/include/initramfs.h new file mode 100644 index 000000000..1ed3dae4a --- /dev/null +++ b/lab7/include/initramfs.h @@ -0,0 +1,54 @@ +#pragma once + +#include "traps.h" +#include "vfs.h" + +// 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; + +typedef struct { + int namesize; + int filesize; + int headsize; + int datasize; + char *pathname; +} ramfsRec; + +typedef struct initramfs_inode { + fsnode_type type; + char *name; + vnode *entry; + char *data; + size_t datasize; +} initramfs_inode; + +int initramfs_setup_mount(filesystem *fs, mount *mnt); + +void initramfs_ls(); +void initramfs_cat(const char *target); +void initramfs_callback(void *addr, char *property); +void initramfs_run(vnode *v); + +int initramfs_read(file *file, void *buf, size_t len); +int initramfs_open(vnode *file_node, file *target); +int initramfs_close(file *file); +long initramfs_lseek64(file *file, long offset, int whence); +long initramfs_getsize(vnode *vn); + +int initramfs_lookup(vnode *dir_node, vnode **target, + const char *component_name); diff --git a/lab7/include/irq.h b/lab7/include/irq.h new file mode 100644 index 000000000..cef36db3a --- /dev/null +++ b/lab7/include/irq.h @@ -0,0 +1,20 @@ +#pragma once + +#include "hardware.h" +#include "traps.h" + +#define ORDER_FIRST 0x0 +#define ORDER_REGULAR 0x8 +#define ORDER_LAST 0xF + +typedef struct irq_task_t { + void (*func)(); + int order; + int busy; // 0 (default) or 1 (busy) + struct irq_task_t *prev; + struct irq_task_t *next; +} irqTask; + +void enable_interrupt(); +void disable_interrupt(); +void irq_entry(trap_frame *tf); \ No newline at end of file diff --git a/lab7/include/mbox.h b/lab7/include/mbox.h new file mode 100644 index 000000000..dbfe23ad3 --- /dev/null +++ b/lab7/include/mbox.h @@ -0,0 +1,24 @@ +#pragma once + +#include "hardware.h" +#include "vfs.h" + +extern volatile unsigned int mbox[36]; + +typedef struct framebuffer_info { + unsigned int width; + unsigned int height; + unsigned int pitch; + unsigned int isrgb; +} framebuffer_info; + +int mbox_call(unsigned char ch, unsigned int *mbox); +int get_board_revision(unsigned int *mbox); +int get_arm_memory_status(unsigned int *mbox); + +file_operations *init_dev_framebuffer(); + +int dev_framebuffer_write(file *f, const void *buf, size_t len); +int dev_framebuffer_open(vnode *file_node, file *target); +int dev_framebuffer_close(file *f); +long dev_framebuffer_lseek64(file *f, long offset, int whence); diff --git a/lab7/include/mem.h b/lab7/include/mem.h new file mode 100644 index 000000000..5f8b7388d --- /dev/null +++ b/lab7/include/mem.h @@ -0,0 +1,50 @@ +#pragma once + +#include + +#include "start.h" + +#define BUDDY_MAX_ORDER 16 +#define CACHE_MAX_ORDER 6 +#define MIN_CACHE_SIZE (PAGE_SIZE / (2 << CACHE_MAX_ORDER)) + +#define VERBAL 0 +#define SILENT 1 + +struct page { + unsigned int order; + unsigned int used; + unsigned int cache_order; + struct page *prev; + struct page *next; +}; + +struct object { + unsigned int order; + struct object *next; +}; + +void init_mem(); +void reserveMemory(uintptr_t resv_start, uintptr_t resv_end); + +uintptr_t page_vaddr(struct page *p); + +void pushPageToFreeList(struct page **list, struct page *page, + unsigned int order); +struct page *popFreeList(struct page **list_head); +void removePageFromFreeList(struct page **list, struct page *page); +void printFreeListByOrder(unsigned int order); + +struct page *lookupBuddy(struct page *page, unsigned int order); +struct page *allocatePagesByOrder(unsigned int order, int silent); +void freePages(struct page *page, unsigned int order, int silent); +void mergePages(struct page *page, unsigned int order, int echo); + +void pushObjectToList(struct object **list_head, struct object *object, + unsigned int order); +struct object *popObjectFromList(struct object **list_head); +void *allocateCacheMemory(unsigned int index, int silent); +void freeCacheEntry(void *ptr, unsigned int index, int silent); + +void *kmalloc(unsigned int size, int silent); +void kfree(void *ptr, int silent); diff --git a/lab7/include/scheduler.h b/lab7/include/scheduler.h new file mode 100644 index 000000000..2907dd1cd --- /dev/null +++ b/lab7/include/scheduler.h @@ -0,0 +1,81 @@ +#pragma once + +#include "signals.h" +#include "traps.h" +#include "vfs.h" + +struct context_struct { + unsigned long x19; + unsigned long x20; + unsigned long x21; + unsigned long x22; + unsigned long x23; + unsigned long x24; + unsigned long x25; + unsigned long x26; + unsigned long x27; + unsigned long x28; + unsigned long fp; + unsigned long lr; + unsigned long sp; +}; + +enum thread_state { + RUNNING, + DEAD, +}; + +typedef struct thread_struct_t { + struct context_struct context; // callee-saved (don't move) + + struct thread_struct_t *prev; + struct thread_struct_t *next; + + int pid; + + void *start; + unsigned int size; + + enum thread_state state; + void *stack; + void *user_stack; + + // Signal handling + void (*sig_handlers[NSIG + 1])(); // Signal handlers + int sig_reg; // Pending signals + int sig_busy; // Handling a signal + trap_frame *sig_tf; // tf Saved before signal handling + void *sig_stack; // Stack for signal handling + + void *pgd; + // struct vm_area_struct *mmap; + + char *cwd; + file *file_descriptor; + unsigned int file_descriptor_index; +} thread_struct; + +int push_file_descriptor(thread_struct *thread, file *fd); + +// Defined in scheduler.S +extern void switch_to(thread_struct *prev, thread_struct *next); +extern void switch_mm(unsigned long pgd); + +extern thread_struct *get_current(); // get tpidr_el1, current thread_struct + +thread_struct *get_thread_by_pid(int pid); +void sched_init(); + +thread_struct *kcreate_thread(void (*func)()); +// thread_struct *kcreate_user_thread(void *program, int filesize); +void list_tcircle(); +void list_fd(thread_struct *thread); + +void schedule(); + +void kill_current_thread(); +void kill_thread_by_pid(int pid); +void idle(); + +file *get_file_descriptor_by_id(thread_struct *thread, int id); +file *get_file_descriptor_by_vnode(thread_struct *thread, vnode *v); \ No newline at end of file diff --git a/lab7/include/shell.h b/lab7/include/shell.h new file mode 100644 index 000000000..fccef918e --- /dev/null +++ b/lab7/include/shell.h @@ -0,0 +1,7 @@ +#pragma once + +#define SHELL_BUF_SIZE 1024 + +void run_shell(); +void read_user_input(char *buf); +int exec_command(const char *command); diff --git a/lab7/include/signals.h b/lab7/include/signals.h new file mode 100644 index 000000000..e3ee32af0 --- /dev/null +++ b/lab7/include/signals.h @@ -0,0 +1,12 @@ +#pragma once + +#include "traps.h" + +#define NSIG 10 // Number of signals +#define SIG_STACK (USER_STACK - STACK_SIZE) + +extern void sigreturn(); // Defined in traps.S + +void signal(int signum, void (*handler)()); +void signal_kill(int pid, int sig); +void do_signal(trap_frame *regs); diff --git a/lab7/include/start.h b/lab7/include/start.h new file mode 100644 index 000000000..f3259bdd0 --- /dev/null +++ b/lab7/include/start.h @@ -0,0 +1,44 @@ +#pragma once + +#define PAGE_SIZE 0x1000 +#define STACK_SIZE 0x4000 + +#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) + +/* + * nGnRnE: Non-Gathering, Non-Reordering, No Early Write Acknowledgement + * Normal memory, Inner Non-cacheable, Outer Non-cacheable + */ +#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 +#define PD_BLOCK 0b01 +#define PD_ENTRY 0b11 + +// Bits[10] The access flag, a page fault is generated if not set. +#define PD_ACCESS (1 << 10) + +// Bits[7] 0 for read-write, 1 or read-only +#define PD_RDONLY (1 << 7) + +// Bits[6] 0 for only kernel access, 1 for user/kernel access +#define PD_UKACCESS (1 << 6) + +// Bits[4:2] The index to MAIR +#define PD_MAIR_DEVICE_nGnRnE (MAIR_IDX_DEVICE_nGnRnE << 2) +#define PD_MAIR_NORMAL_NOCACHE (MAIR_IDX_NORMAL_NOCACHE << 2) + +#define BOOT_PGD_PAGE_FRAME 0x1000 // Boot PGD's page frame +#define BOOT_PUD_PAGE_FRAME 0x2000 // Boot PUD's page frame +#define BOOT_PMD_PAGE_FRAME 0x3000 // Boot PMD's page frame + +#define BOOT_PGD_ATTR PD_TABLE +#define BOOT_PUD_ATTR PD_TABLE +#define BOOT_PUD_DEVICE_ATTR (PD_ACCESS | PD_MAIR_DEVICE_nGnRnE | PD_BLOCK) +#define BOOT_PMD_NORMAL_ATTR (PD_ACCESS | PD_MAIR_NORMAL_NOCACHE | PD_BLOCK) +#define BOOT_PMD_DEVICE_ATTR (PD_ACCESS | PD_MAIR_DEVICE_nGnRnE | PD_BLOCK) \ No newline at end of file diff --git a/lab7/include/str.h b/lab7/include/str.h new file mode 100644 index 000000000..766ff7ea8 --- /dev/null +++ b/lab7/include/str.h @@ -0,0 +1,6 @@ +#pragma once + +int strcmp(const char *str1, const char *str2); +char *strncpy(char *dest, const char *src, int n); +int strlen(const char *str); +char *strcat(char *dest, const char *src); \ No newline at end of file diff --git a/lab7/include/syscalls.h b/lab7/include/syscalls.h new file mode 100644 index 000000000..c1691d6f4 --- /dev/null +++ b/lab7/include/syscalls.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +#include "mbox.h" +#include "traps.h" + +#define GET_INFO 0 + +extern void child_ret_from_fork(); // traps.S + +int sys_getpid(); +size_t sys_uart_read(char *buf, size_t size); +size_t sys_uart_write(const char *buf, size_t size); +int sys_exec(const char *name, trap_frame *tf); +int sys_fork(trap_frame *tf); +void sys_exit(int status); +int sys_mbox_call(unsigned char ch, unsigned int *mbox); +void sys_sigreturn(trap_frame *regs); + +int sys_open(const char *pathname, int flags); +int sys_close(int fd); +int sys_write(int fd, void *buf, size_t count); +int sys_read(int fd, void *buf, size_t count); +int sys_mkdir(const char *pathname, unsigned mode); +int sys_mount(const char *target, const char *filesystem); +int sys_chdir(const char *path); + +long sys_lseek64(int fd, long offset, int whence); +int sys_ioctl(int fd, unsigned long request, void *info); diff --git a/lab7/include/timer.h b/lab7/include/timer.h new file mode 100644 index 000000000..b92678a35 --- /dev/null +++ b/lab7/include/timer.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#define TIMER_SPEED 3 // default 5 + +typedef struct timerEntry_t { + void (*func)(void *); + void *arg; + int time; + struct timerEntry_t *next; +} timerEntry; + +void init_timer(); + +void enable_timer_interrupt(); +void disable_timer_interrupt(); + +void timer_irq_handler(); +uint64_t timer_get_uptime(); + +void timer_add(void (*callback)(void *), void *arg, int duration); +void set_timer(const char *message, int duration); +void set_timeup(int *timeup); diff --git a/lab7/include/tmpfs.h b/lab7/include/tmpfs.h new file mode 100644 index 000000000..de23a12f5 --- /dev/null +++ b/lab7/include/tmpfs.h @@ -0,0 +1,34 @@ +#pragma once + +#include "vfs.h" + +#define MAX_DIR_ENTRY 16 +// #define MAX_FILE_SIZE 4096 + +typedef struct tmpfs_inode { + fsnode_type type; + char *name; + vnode *entry[MAX_DIR_ENTRY]; + char *data; + size_t datasize; +} tmpfs_inode; + +int tmpfs_setup_mount(filesystem *fs, mount *mnt); + +vnode *tmpfs_create_vnode(fsnode_type type); + +int tmpfs_write(file *file, const void *buf, size_t len); + +int tmpfs_read(file *file, void *buf, size_t len); + +// open a file, initailze everything +int tmpfs_open(vnode *file_node, file *target); + +int tmpfs_close(file *file); + +// long seek 64-bit +long tmpfs_lseek64(file *file, long offset, int whence); +long tmpfs_getsize(vnode *vn); +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); \ No newline at end of file diff --git a/lab7/include/traps.h b/lab7/include/traps.h new file mode 100644 index 000000000..bf940f5d7 --- /dev/null +++ b/lab7/include/traps.h @@ -0,0 +1,38 @@ +#pragma once + +typedef struct { // order is important + unsigned long x0; + unsigned long x1; + unsigned long x2; + unsigned long x3; + unsigned long x4; + unsigned long x5; + unsigned long x6; + unsigned long x7; + unsigned long x8; // system call number + unsigned long x9; + unsigned long x10; + unsigned long x11; + unsigned long x12; + unsigned long x13; + unsigned long x14; + unsigned long x15; + unsigned long x16; + unsigned long x17; + unsigned long x18; + unsigned long x19; + unsigned long x20; + unsigned long x21; + unsigned long x22; + unsigned long x23; + unsigned long x24; + unsigned long x25; + unsigned long x26; + unsigned long x27; + unsigned long x28; + unsigned long x29; + unsigned long x30; // link register + unsigned long spsr_el1; + unsigned long elr_el1; + unsigned long sp_el0; +} trap_frame; diff --git a/lab7/include/uart.h b/lab7/include/uart.h new file mode 100644 index 000000000..4d01eaa25 --- /dev/null +++ b/lab7/include/uart.h @@ -0,0 +1,56 @@ +#pragma once + +#include + +#include "hardware.h" +#include "vfs.h" + +#define UART_BUF_SIZE 1024 + +// Special characters +#define BACKSPACE '\b' +#define DELETE 127 +#define ESC 27 +#define NEWLINE '\n' +#define TAB '\t' +#define CARRIAGE_RETURN '\r' + +// uart_log types +#define INFO 0 +#define WARN 1 +#define TEST 2 +#define BUDD 10 +#define CACH 11 +#define VFS 12 +#define MMAP 13 +#define SCALL 253 +#define DEBUG 254 +#define ERR 255 + +void init_uart(); +char uart_getc(); +void uart_putc(char c); +void uart_puts(const char *s); +void uart_clear(); +void uart_hex(unsigned long h); +void uart_addr_range(uintptr_t start, uintptr_t end); +void uart_simple_hex(unsigned int h); +void uart_dec(unsigned int h); +void uart_log(int type, const char *msg); + +void enable_uart_tx_interrupt(); +void disable_uart_tx_interrupt(); +void enable_uart_rx_interrupt(); +void disable_uart_rx_interrupt(); + +void uart_tx_irq_handler(); +void uart_rx_irq_handler(); + +void uart_async_read(char *buf, int len); +void uart_async_write(const char *s); + +file_operations *init_dev_uart(); +int dev_uart_write(file *file, const void *buf, size_t len); +int dev_uart_read(file *file, void *buf, size_t len); +int dev_uart_open(vnode *file_node, file *target); +int dev_uart_close(file *file); \ No newline at end of file diff --git a/lab7/include/utils.h b/lab7/include/utils.h new file mode 100644 index 000000000..c2ebcb54b --- /dev/null +++ b/lab7/include/utils.h @@ -0,0 +1,24 @@ +#pragma once + +/** + * @brief Align `n` to be a multiple of 4. + * + * @param n A number + * @return Algined number + */ +int align4(int n); + +int atoi(const char *s); + +/** + * @brief Convert hexadecimal string to int. + * + * @param s: hexadecimal string + * @param n: string length + * @return Converted int number + */ +int hextoi(char *s, int n); + +int memcmp(const void *str1, const void *str2, int n); +void *memcpy(void *dest, const void *src, int n); +void *memset(void *s, int c, int n); \ No newline at end of file diff --git a/lab7/include/vfs.h b/lab7/include/vfs.h new file mode 100644 index 000000000..1a1f152f5 --- /dev/null +++ b/lab7/include/vfs.h @@ -0,0 +1,82 @@ +#pragma once + +#include +#include + +#include "traps.h" + +#define O_RDONLY 00000000 +#define O_WRONLY 00000001 +#define O_RDWR 00000002 +#define O_ACCMODE 00000003 +#define O_CREAT 00000100 + +#define SEEK_SET 0 +#define SEEK_CUR 1 + +typedef enum fsnode_type { + dir_t, // + file_t // +} fsnode_type; + +typedef struct vnode { + struct mount *mount; + struct vnode_operations *v_ops; + struct file_operations *f_ops; + void *internal; + struct vnode *next; +} vnode; + +// file handle +typedef struct _file { + int id; + vnode *vnode; + size_t f_pos; + struct file_operations *f_ops; + int flags; + struct _file *prev; + struct _file *next; +} file; + +typedef struct mount { + vnode *root; + struct filesystem *fs; +} mount; + +typedef struct filesystem { + char *name; + int (*setup_mount)(struct filesystem *fs, struct mount *mount); + struct filesystem *next; +} filesystem; + +typedef struct file_operations { + int (*write)(file *file, const void *buf, size_t len); + int (*read)(file *file, void *buf, size_t len); + int (*open)(vnode *file_node, file *target); + int (*close)(file *file); + long (*lseek64)(file *file, long offset, int whence); + long (*getsize)(vnode *vd); +} 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); +} vnode_operations; + +void init_vfs(); +filesystem *register_filesystem(char *const name, void *setup_mount); +vnode *vfs_mknod(char *pathname); + +int vfs_open(const char *pathname, int flags, file *target); +int vfs_close(file *file); +int vfs_write(file *file, const void *buf, size_t len); +int vfs_read(file *file, void *buf, size_t len); +int vfs_mkdir(const char *pathname); +int vfs_mount(const char *target, const char *filesystem); +int vfs_lookup(const char *pathname, vnode **target); +void vfs_exec(vnode *target, trap_frame *tf); + +file *create_file(); +char *absolute_path(const char *path, const char *cwd); +int not_supported(); \ No newline at end of file diff --git a/lab7/include/virtm.h b/lab7/include/virtm.h new file mode 100644 index 000000000..5b1e8b628 --- /dev/null +++ b/lab7/include/virtm.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include "hardware.h" +#include "scheduler.h" + +#define USER_STACK 0x0000FFFFFFFFB000 + +#define PTE_NORMAL_ATTR \ + (PD_ACCESS | PD_UKACCESS | PD_MAIR_NORMAL_NOCACHE | PD_ENTRY) + +void map_pages(uintptr_t pgd, uintptr_t va, uintptr_t size, uintptr_t pa); + +void mapping_user_thread(thread_struct *thread, int gpu_mem_size); \ No newline at end of file diff --git a/lab7/initramfs.cpio b/lab7/initramfs.cpio new file mode 100644 index 0000000000000000000000000000000000000000..a25f28e2795aa3d6f1f1a3440584e6a76a0be70c GIT binary patch literal 1149952 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

2m7n!KHh&P4vy=9Px9mUyY2 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%0DO@Fg7AiuX4vS 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 $DEST_PATH | sleep 1 + +# Send the kernel image +# pv: redirect file input to specified tty +# add --rate-limit option to limit the speed +pv $KERNEL_PATH > $DEST_PATH diff --git a/lab7/src/command.c b/lab7/src/command.c new file mode 100644 index 000000000..1dbb23fcf --- /dev/null +++ b/lab7/src/command.c @@ -0,0 +1,225 @@ +#include "command.h" + +#include "initramfs.h" +#include "irq.h" +#include "mem.h" +#include "scheduler.h" +#include "shell.h" +#include "start.h" +#include "str.h" +#include "syscalls.h" +#include "timer.h" +#include "uart.h" +#include "utils.h" +#include "virtm.h" + +typedef struct { + void *ptr; + void *next; +} demo_mem_rec; + +static demo_mem_rec *dmr_list = 0; + +void cmd_info() { + uart_log(INFO, "board revision: "); + uart_hex(BOARD_REVISION); + uart_putc(NEWLINE); + uart_log(INFO, "device base memory address: "); + uart_hex(BASE_MEMORY); + uart_putc(NEWLINE); + uart_log(INFO, "device memory size: "); + uart_hex(NUM_PAGES * PAGE_SIZE); + uart_putc(NEWLINE); +} + +void cmd_hello() { uart_log(INFO, "Hello there!\n"); } + +static void cmd_mem() { + for (int i = BUDDY_MAX_ORDER; i >= 0; i--) { + printFreeListByOrder(i); + } +} + +static void cmd_bd() { + uart_puts("Buddy System: request order (0-"); + uart_dec(BUDDY_MAX_ORDER); + uart_puts("): "); + char *buf = kmalloc(SHELL_BUF_SIZE, SILENT); + read_user_input(buf); + int order = atoi(buf); + if (buf[0] < '0' || buf[0] > '9' || order < 0 || order > BUDDY_MAX_ORDER) { + uart_log(WARN, "Invalid order.\n"); + } else { + demo_mem_rec *dmr = kmalloc(sizeof(demo_mem_rec), SILENT); + dmr->ptr = kmalloc(PAGE_SIZE << order, SILENT); + dmr->next = dmr_list; + dmr_list = dmr; + } + kfree(buf, SILENT); +} + +static void cmd_fm() { + if (dmr_list == 0) { + uart_log(WARN, "No memory block allocated.\n"); + } else { + uart_log(INFO, "Freeing all allocated memory blocks...\n"); + do { + demo_mem_rec *dmr = dmr_list; + kfree(dmr->ptr, SILENT); + dmr_list = dmr->next; + kfree(dmr, SILENT); + } while (dmr_list != 0); + } +} + +static void cmd_ca() { + uart_log(INFO, "Dynamic Memory Allocator: request byte(s) (1-"); + uart_dec(PAGE_SIZE / 2); + uart_puts("): "); + char *buf = kmalloc(SHELL_BUF_SIZE, SILENT); + read_user_input(buf); + int size = atoi(buf); + if (buf[0] == '\0' || size <= 0) { + uart_log(WARN, "Invalid size.\n"); + } else { + if (size > PAGE_SIZE / 2) { + uart_log( + WARN, + "Size too large for dynamic allocator. Switching to Buddy System.\n"); + } + demo_mem_rec *dmr = kmalloc(sizeof(demo_mem_rec), SILENT); + dmr->ptr = kmalloc(size, SILENT); + dmr->next = dmr_list; + dmr_list = dmr; + } + kfree(buf, SILENT); +} + +static void cmd_lab7() { + char *buf = "/initramfs/vfs1.img"; + uart_log(INFO, "run: "); + uart_puts(buf); + uart_putc(NEWLINE); + strcat(buf, "\0"); + vnode *target_file; + int ret = vfs_lookup(buf, &target_file); // assign the vnode to target + if (ret != 0) { + uart_log(WARN, "File not found.\n"); + return; + } + initramfs_run(target_file); +} + +static void cmd_timer() { + uart_puts("Duration(sec.): "); + char *buf = kmalloc(SHELL_BUF_SIZE, SILENT); + read_user_input(buf); + int sec = atoi(buf); + + char *msg = kmalloc(SHELL_BUF_SIZE, SILENT); + uart_puts(": "); + read_user_input(msg); + set_timer(msg, sec); + kfree(buf, SILENT); +} + +static void cmd_run() { + // Get filename from user input + uart_puts(": "); + char *buf = kmalloc(SHELL_BUF_SIZE, SILENT); + read_user_input(buf); + vnode *target_file; + int ret = vfs_lookup(buf, &target_file); // assign the vnode to target + kfree(buf, SILENT); + if (ret != 0) { + uart_log(WARN, "File not found.\n"); + return; + } + initramfs_run(target_file); +} + +static void cmd_ls() { + uart_log(INFO, "Listing initramfs files...\n"); + vnode *dirnode; + if (vfs_lookup("/initramfs", &dirnode)) { + uart_log(WARN, "initramfs not found.\n"); + return; + } + initramfs_inode *dir_inode = dirnode->internal; + uart_puts(dir_inode->name); + uart_putc(NEWLINE); + vnode *ptr = dir_inode->entry; + while (ptr) { + initramfs_inode *inode = ptr->internal; + uart_puts(inode->name); + for (int i = 0; i < 20 - strlen(inode->name); i++) uart_putc(' '); + uart_dec(inode->datasize); + uart_putc(NEWLINE); + ptr = ptr->next; + } +} + +static void cmd_reboot() { + uart_log(INFO, "Start Rebooting...\n"); + // Reboot after 0x20000 ticks + *PM_RSTC = PM_PASSWORD | 0x20; // Full reset + *PM_WDOG = PM_PASSWORD | 0x20000; // Number of watchdog ticks +} + +static void cmd_cancel() { + uart_log(INFO, "Rebooting Attempt Aborted, if any.\n"); + *PM_RSTC = PM_PASSWORD | 0; + *PM_WDOG = PM_PASSWORD | 0; +} + +static void cmd_cat() { + // Get filename from user input + uart_puts(": "); + char *buf = kmalloc(SHELL_BUF_SIZE, SILENT); + read_user_input(buf); + initramfs_cat(buf); + kfree(buf, SILENT); +} + +static void cmd_clear() { uart_clear(); } + +static void cmd_help() { + uart_puts("-----------------------------------------"); + uart_putc(NEWLINE); + struct command *cmd = cmd_list; + while (1) { + if (!strcmp(cmd->name, END_OF_COMMAND_LIST)) { + break; + } + uart_puts(cmd->name); + uart_putc(TAB); + uart_puts(": "); + uart_puts(cmd->help); + uart_putc(NEWLINE); + cmd++; + } + uart_puts("-----------------------------------------"); + uart_putc(NEWLINE); +} + +struct command cmd_list[] = { + {.name = "help", .help = "Display this help menu", .func = cmd_help}, + {.name = "hello", .help = "Print 'Hello there!'", .func = cmd_hello}, + {.name = "clear", .help = "Clear the screen", .func = cmd_clear}, + {.name = "reboot", .help = "Reboot the device", .func = cmd_reboot}, + {.name = "cancel", .help = "Cancel a scheduled reboot", .func = cmd_cancel}, + {.name = "info", .help = "Show hardware information", .func = cmd_info}, + {.name = "ls", .help = "List the content of initramfs", .func = cmd_ls}, + {.name = "cat", .help = "Display content of ramdisk file", .func = cmd_cat}, + {.name = "run", .help = "Run a specified program", .func = cmd_run}, + {.name = "timer", .help = "Set timer with duration", .func = cmd_timer}, + // lab4 + {.name = "mem", .help = "Display free memory blocks", .func = cmd_mem}, + {.name = "bd", .help = "Allocate a memory block", .func = cmd_bd}, + {.name = "ca", .help = "Use the dynamic memory allocator", .func = cmd_ca}, + // lab5 + {.name = "fm", .help = "Free all memory in the demo list", .func = cmd_fm}, + {.name = "pid", .help = "List all running threads", .func = list_tcircle}, + // retired + {.name = "lab7", .help = "Showcase lab7 requirements", .func = cmd_lab7}, + {.name = END_OF_COMMAND_LIST}}; diff --git a/lab7/src/devtree.c b/lab7/src/devtree.c new file mode 100644 index 000000000..a64ea7638 --- /dev/null +++ b/lab7/src/devtree.c @@ -0,0 +1,64 @@ +#include "devtree.h" + +#include "str.h" +#include "uart.h" +#include "utils.h" + +// Assign a non-zero value to be stored in the .data section +void *DTB_BASE = (void *)0xF; +void *DTB_END = (void *)0xF; + +uint32_t be2le(const void *s) { + const uint8_t *bytes = (const uint8_t *)s; + return (uint32_t)bytes[0] << 24 | (uint32_t)bytes[1] << 16 | + (uint32_t)bytes[2] << 8 | (uint32_t)bytes[3]; +} + +void fdt_traverse(void (*callback)(void *, char *)) { + struct fdt_header *header = (struct fdt_header *)(uintptr_t)DTB_BASE; + + // Check the magic number + if (be2le(&(header->magic)) != 0xD00DFEED) { + uart_log(WARN, "Dtb header magic does not match!\n"); + } + DTB_BASE = (void *)TO_VIRT(DTB_BASE); + DTB_END = DTB_BASE + be2le(&header->totalsize); + + uart_log(INFO, "Dtb loaded at "); + uart_addr_range((uintptr_t)DTB_BASE, (uintptr_t)DTB_END); + uart_putc(NEWLINE); + + uintptr_t structure = (uintptr_t)header + be2le(&header->off_dt_struct); + uintptr_t strings = (uintptr_t)header + be2le(&header->off_dt_strings); + uint32_t structure_size = be2le(&header->size_dt_struct); + + // Parse the structure block + uintptr_t ptr = structure; // Point to the beginning of structure block + while (ptr < structure + structure_size) { + uint32_t token = be2le((char *)ptr); + ptr += 4; // Token takes 4 bytes + + switch (token) { + case FDT_BEGIN_NODE: + ptr += align4(strlen((char *)ptr) + 1); + break; + case FDT_PROP: + uint32_t len = be2le((char *)ptr); + ptr += 4; + uint32_t nameoff = be2le((char *)ptr); + ptr += 4; + callback((void *)(uintptr_t)be2le((void *)ptr), + (char *)(strings + nameoff)); + ptr += align4(len); + break; + case FDT_END_NODE: + case FDT_NOP: + case FDT_END: + break; + default: + uart_log(WARN, "Unknown fdt token: "); + uart_simple_hex(token); + uart_putc(NEWLINE); + } + } +} \ No newline at end of file diff --git a/lab7/src/initramfs.c b/lab7/src/initramfs.c new file mode 100644 index 000000000..ecfe73783 --- /dev/null +++ b/lab7/src/initramfs.c @@ -0,0 +1,262 @@ +#include "initramfs.h" + +#include + +#include "irq.h" +#include "mem.h" +#include "scheduler.h" +#include "start.h" +#include "str.h" +#include "syscalls.h" +#include "uart.h" +#include "utils.h" +#include "vfs.h" +#include "virtm.h" + +void *initrd_start; +void *initrd_end; + +void initramfs_callback(void *addr, char *property) { + if (!strcmp(property, "linux,initrd-start")) { + uart_log(INFO, "linux,initrd-start: "); + initrd_start = (char *)TO_VIRT(addr); + uart_hex((uintptr_t)initrd_start); + uart_putc(NEWLINE); + } else if (!strcmp(property, "linux,initrd-end")) { + uart_log(INFO, "linux,initrd-end: "); + initrd_end = (char *)TO_VIRT(addr); + uart_hex((uintptr_t)initrd_end); + uart_putc(NEWLINE); + } +} + +file_operations initramfs_file_operations = { + (void *)not_supported, // write + initramfs_read, // + initramfs_open, // + initramfs_close, // + (void *)not_supported, // lseek64 + initramfs_getsize // +}; + +vnode_operations initramfs_vnode_operations = { + initramfs_lookup, // + (void *)not_supported, // create + (void *)not_supported // mkdir +}; + +static vnode *initramfs_create_vnode(mount *mnt, fsnode_type type) { + vnode *v = kmalloc(sizeof(vnode), SILENT); + v->f_ops = &initramfs_file_operations; + v->v_ops = &initramfs_vnode_operations; + v->mount = mnt; + + initramfs_inode *inode = kmalloc(sizeof(initramfs_inode), SILENT); + memset(inode, 0, sizeof(initramfs_inode)); + inode->type = type; + inode->data = kmalloc(PAGE_SIZE, SILENT); + + v->internal = inode; + return v; +} + +static ramfsRec *ramfsNext(char *fptr) { + ramfsRec *rec = kmalloc(sizeof(ramfsRec), SILENT); + cpio_t *header = (cpio_t *)fptr; + + // New ASCII Format uses 8-byte hexadecimal string for all numbers + rec->namesize = hextoi(header->c_namesize, 8); + rec->filesize = hextoi(header->c_filesize, 8); + + // Total size of (header + pathname) is a multiple of four bytes + // File data is also padded to a multiple of four bytes + rec->headsize = align4(sizeof(cpio_t) + rec->namesize); + rec->datasize = align4(rec->filesize); + + // Get file pathname + rec->pathname = kmalloc(rec->namesize, SILENT); + strncpy(rec->pathname, fptr + sizeof(cpio_t), rec->namesize); + + return rec; +} + +int initramfs_setup_mount(filesystem *fs, mount *mnt) { + mnt->fs = fs; + mnt->root = initramfs_create_vnode(0, dir_t); + + initramfs_inode *ramfs_inode = mnt->root->internal; + ramfs_inode->entry = 0; + + char *fptr = (char *)initrd_start; + while (memcmp(fptr + sizeof(cpio_t), "TRAILER!!!", 10)) { + ramfsRec *rec = ramfsNext(fptr); + vnode *file_vnode = initramfs_create_vnode(0, file_t); + initramfs_inode *file_inode = file_vnode->internal; + file_inode->data = fptr + rec->headsize; + file_inode->datasize = rec->filesize; + file_inode->name = kmalloc(strlen(rec->pathname) + 1, SILENT); + strncpy(file_inode->name, rec->pathname, strlen(rec->pathname) + 1); + + file_vnode->next = ramfs_inode->entry; + ramfs_inode->entry = file_vnode; + + fptr += rec->headsize + rec->datasize; + kfree(rec, SILENT); + } + return 0; +} + +int initramfs_read(file *file, void *buf, size_t len) { + initramfs_inode *inode = file->vnode->internal; + + // if overflow, shrink size + if (len + file->f_pos > inode->datasize) + len = inode->datasize - file->f_pos; + + memcpy(buf, inode->data, len); + file->f_pos += len; + return len; +}; + +int initramfs_open(vnode *file_node, file *target) { + target->vnode = file_node; + target->f_ops = file_node->f_ops; + target->f_pos = 0; + return 0; +}; + +int initramfs_close(file *f) { + kfree(f, SILENT); + return 0; +}; + +long initramfs_getsize(vnode *vn) { + initramfs_inode *file_inode = vn->internal; + return file_inode->datasize; +}; + +int initramfs_lookup(vnode *dir_node, vnode **target, + const char *component_name) { + initramfs_inode *dir_inode = dir_node->internal; + vnode *ptr = dir_inode->entry; + while (ptr) { + initramfs_inode *inode = ptr->internal; + if (strcmp(component_name, inode->name) == 0) { + *target = ptr; + return 0; + } + ptr = ptr->next; + } + return -1; +}; + +void initramfs_ls() { + uart_putc(NEWLINE); + + char *fptr = (char *)initrd_start; + while (memcmp(fptr + sizeof(cpio_t), "TRAILER!!!", 10)) { + ramfsRec *rec = ramfsNext(fptr); + uart_puts(rec->pathname); + for (int i = 0; i < 15 - strlen(rec->pathname); i++) { + uart_putc(' '); + } + uart_dec(rec->filesize); + uart_puts(" byte"); + if (rec->filesize > 1) + uart_putc('s'); + uart_putc(NEWLINE); + + fptr += rec->headsize + rec->datasize; + kfree(rec->pathname, SILENT); + kfree(rec, SILENT); + } +} + +void initramfs_cat(const char *filename) { + char *fptr = (char *)initrd_start; + + while (memcmp(fptr + sizeof(cpio_t), "TRAILER!!!", 10)) { + ramfsRec *rec = ramfsNext(fptr); + if (!strcmp(filename, rec->pathname)) { + // Dump its content + uart_putc(NEWLINE); + for (char *c = fptr + rec->headsize; + c < fptr + rec->headsize + rec->filesize; c++) { + uart_putc(*c); + } + uart_putc(NEWLINE); + kfree(rec->pathname, SILENT); + kfree(rec, SILENT); + return; + } + fptr += rec->headsize + rec->datasize; + kfree(rec->pathname, SILENT); + kfree(rec, SILENT); + } + + uart_puts("File not found."); + uart_putc(NEWLINE); +} + +void initramfs_run(vnode *v) { + // Load the user program + disable_interrupt(); + initramfs_inode *inode = v->internal; + + thread_struct *thread = kcreate_thread(0); + + thread->user_stack = kmalloc(STACK_SIZE, 0); + uart_log(INFO, "Acquired thread user stack: "); + uart_hex((uintptr_t)thread->stack); + uart_putc(NEWLINE); + + void *program = kmalloc(inode->datasize, 0); + uart_log(INFO, "Acquired program space: "); + uart_hex((uintptr_t)(thread->start = program)); + uart_putc(NEWLINE); + memcpy(program, inode->data, thread->size = inode->datasize); + + mapping_user_thread(thread, 0x1000000); + + file *stdin = create_file(); + vfs_open("/dev/uart", 0, stdin); + uart_log(DEBUG, "stdin: "); + uart_dec(push_file_descriptor(thread, stdin)); + uart_putc(NEWLINE); + + file *stdout = create_file(); + vfs_open("/dev/uart", 0, stdout); + uart_log(DEBUG, "stdout: "); + uart_dec(push_file_descriptor(thread, stdout)); + uart_putc(NEWLINE); + + file *stderr = create_file(); + vfs_open("/dev/uart", 0, stderr); + uart_log(DEBUG, "stderr: "); + uart_dec(push_file_descriptor(thread, stderr)); + uart_putc(NEWLINE); + + file *framebuffer = create_file(); + vfs_open("/dev/framebuffer", 0, framebuffer); + uart_log(DEBUG, "framebuffer: "); + uart_dec(push_file_descriptor(thread, framebuffer)); + uart_putc(NEWLINE); + + switch_mm((uintptr_t)TO_PHYS(thread->pgd)); + + // asm volatile("msr tpidr_el1, %0\n" ::"r"(thread)); + asm volatile("msr tpidr_el1, %4\n" + "msr spsr_el1, %0\n" + "msr elr_el1, %1\n" + "msr sp_el0, %2\n" + "mov sp, %3\n" + "eret\n" + : + : "r"(0x340), // 0 + "r"(0x0), // 1 link register + "r"(USER_STACK + STACK_SIZE), // 2 + "r"(thread->stack + STACK_SIZE), // 3 + "r"(thread) // 4 + :); + return; +} diff --git a/lab7/src/irq.c b/lab7/src/irq.c new file mode 100644 index 000000000..51fa3db8a --- /dev/null +++ b/lab7/src/irq.c @@ -0,0 +1,102 @@ +#include "irq.h" + +#include "mem.h" +#include "scheduler.h" +#include "timer.h" +#include "uart.h" + +static irqTask *irqTask_head = 0; + +// Disable interrupt before calling irq_add_task() +void irq_add_task(void (*callback)(), int order) { + irqTask *task = kmalloc(sizeof(irqTask), SILENT); + task->func = callback; + task->order = order; + task->busy = 0; + task->prev = 0; + task->next = 0; + + // 0 -> task -> head -> ... + if (irqTask_head == 0 || task->order < irqTask_head->order) { + task->next = irqTask_head; + task->prev = 0; + if (irqTask_head != 0) + irqTask_head->prev = task; + irqTask_head = task; + return; + } + + irqTask *current = irqTask_head; + while (current->next != 0 && current->next->order <= task->order) + current = current->next; + task->next = current->next; + if (current->next != 0) + current->next->prev = task; + current->next = task; + task->prev = current; +} + +// to enable interrupts in EL1 +void enable_interrupt() { + asm volatile( + "msr DAIFClr, 0xF\n" // Clear the D, A, I, F bits in the DAIF register + ); +} + +// to disable interrupts in EL1 +void disable_interrupt() { + asm volatile( + "msr DAIFSet, 0xF\n" // Set the D, A, I, F bits to 1 in the DAIF register + ); +} + +void irq_entry(trap_frame *tf) { + disable_interrupt(); // Enter the critical section + + if (*IRQ_PENDING_1 & (1 << 29)) { // UART interrupt + switch (*AUX_MU_IIR & 0x6) { // 0x6 = 0110 -> Get 0x2 and 0x4 + + case 0x2: // 0x2 UART Transmit interrupt + disable_uart_tx_interrupt(); + irq_add_task(uart_tx_irq_handler, ORDER_LAST); + break; + + case 0x4: // 0x4 UART Receive interrupt + disable_uart_rx_interrupt(); + irq_add_task(uart_rx_irq_handler, ORDER_REGULAR); + break; + } + } else if (*CORE0_INTERRUPT_SOURCE & 0x2) { + // Core 0 timer interrupt for schedule() + // tell if thread_struct is not the only one in the run_queue + if (get_current() != get_current()->next) + schedule(); + + disable_timer_interrupt(); + irq_add_task(timer_irq_handler, ORDER_FIRST); + } + + enable_interrupt(); // Leave the critical section + + // Preemption: run the task with the highest priority + while (irqTask_head != 0 && !irqTask_head->busy) { + disable_interrupt(); + irqTask *task = irqTask_head; // Get a task from head + task->busy = 1; // Flag the task as under processing + enable_interrupt(); + + task->func(); // Run the tasks with interrupts enabled + + // Remove the task + disable_interrupt(); + if (task->prev != 0) + task->prev->next = task->next; + if (task->next != 0) + task->next->prev = task->prev; + if (task == irqTask_head) + irqTask_head = task->next; + kfree((void *)task, SILENT); + enable_interrupt(); + } + do_signal(tf); +} \ No newline at end of file diff --git a/lab7/src/linker.ld b/lab7/src/linker.ld new file mode 100644 index 000000000..451d21bfd --- /dev/null +++ b/lab7/src/linker.ld @@ -0,0 +1,14 @@ +SECTIONS +{ + . = 0xFFFF000000000000; + . += 0x80000; + .text : { *(.text.boot) *(.text) } + .rodata : { *(.rodata) } + .data : { *(.data) } + .bss : { + __bss_start = .; + *(.bss) + __bss_end = .; + } +} +__bss_size = (__bss_end - __bss_start) >> 3; /* SIZEOF(.bss); */ diff --git a/lab7/src/main.c b/lab7/src/main.c new file mode 100644 index 000000000..797362b14 --- /dev/null +++ b/lab7/src/main.c @@ -0,0 +1,37 @@ +#include "devtree.h" +#include "initramfs.h" +#include "irq.h" +#include "mem.h" +#include "scheduler.h" +#include "shell.h" +#include "timer.h" +#include "uart.h" +#include "vfs.h" + +int main() { + /* Initialization */ + uart_log(INFO, "init_uart()\n"); + init_uart(); + + uart_log(INFO, "fdt_traverse()\n"); + fdt_traverse(initramfs_callback); + + uart_log(INFO, "init_mem()\n"); + init_mem(); + + uart_log(INFO, "init_vfs()\n"); + init_vfs(); + + uart_log(INFO, "enable_interrupt()\n"); + enable_interrupt(); + + uart_log(INFO, "init_timer()\n"); + init_timer(); + + uart_log(INFO, "sched_init()\n"); + sched_init(); + + run_shell(); + + return 0; +} \ No newline at end of file diff --git a/lab7/src/mbox.c b/lab7/src/mbox.c new file mode 100644 index 000000000..06b2e0933 --- /dev/null +++ b/lab7/src/mbox.c @@ -0,0 +1,160 @@ +#include "mbox.h" + +#include "irq.h" +#include "mem.h" +#include "uart.h" +#include "utils.h" +#include "vfs.h" + +int mbox_call(unsigned char ch, unsigned int *mbox) { + unsigned int r = (unsigned int)((unsigned long)mbox & ~0xF) | (ch & 0xF); + // Wait until we can write to the mailbox + while (*MAILBOX_REG_STATUS & MAILBOX_FULL) + ; + *MAILBOX_REG_WRITE = r; // Write the request + while (1) { + // Wait for the response + while (*MAILBOX_REG_STATUS & MAILBOX_EMPTY) + ; + if (r == *MAILBOX_REG_READ) + return mbox[1] == MAILBOX_RESPONSE; + } + return 0; +} + +unsigned int __attribute__((aligned(16))) mbox_fb[36]; +framebuffer_info fb_info; +uintptr_t lfb; // raw frame buffer addess + +int get_board_revision(unsigned int *mbox) { + mbox[0] = 7 * 4; + mbox[1] = MAILBOX_REQUEST; + mbox[2] = TAGS_HARDWARE_BOARD_REVISION; + mbox[3] = 4; + mbox[4] = TAG_REQUEST_CODE; + mbox[5] = 0; + mbox[6] = END_TAG; + return mbox_call(MAILBOX_CH_PROP, mbox); +} + +int get_arm_memory_status(unsigned int *mbox) { + mbox[0] = 8 * 4; + mbox[1] = MAILBOX_REQUEST; + mbox[2] = TAGS_HARDWARE_ARM_MEM; + mbox[3] = 8; + mbox[4] = TAG_REQUEST_CODE; + mbox[5] = 0; + mbox[6] = 0; + mbox[7] = END_TAG; + return mbox_call(MAILBOX_CH_PROP, mbox); +} + +file_operations dev_framebuffer_operation = { + dev_framebuffer_write, // + (void *)not_supported, // read + dev_framebuffer_open, // + dev_framebuffer_close, // + dev_framebuffer_lseek64, // + (void *)not_supported // getsize +}; + +file_operations *init_dev_framebuffer() { + // The following code is for mailbox initialize used in lab7. + mbox_fb[0] = 35 * 4; + mbox_fb[1] = MAILBOX_REQUEST; + + mbox_fb[2] = FB_PHY_WID_HEIGHT_SET; // set phy wh + mbox_fb[3] = 8; + mbox_fb[4] = 8; + mbox_fb[5] = 1024; // FrameBufferInfo.width + mbox_fb[6] = 768; // FrameBufferInfo.height + + mbox_fb[7] = FB_VIR_WID_HEIGHT_SET; // set virt wh + mbox_fb[8] = 8; + mbox_fb[9] = 8; + mbox_fb[10] = 1024; // FrameBufferInfo.virtual_width + mbox_fb[11] = 768; // FrameBufferInfo.virtual_height + + mbox_fb[12] = FB_VIR_OFFSET_SET; // set virt offset + mbox_fb[13] = 8; + mbox_fb[14] = 8; + mbox_fb[15] = 0; // FrameBufferInfo.x_offset + mbox_fb[16] = 0; // FrameBufferInfo.y.offset + + mbox_fb[17] = FB_DEPTH_SET; // set depth + mbox_fb[18] = 4; + mbox_fb[19] = 4; + mbox_fb[20] = 32; // FrameBufferInfo.depth + + mbox_fb[21] = FB_PIXEL_ORDER_SET; // set pixel order + mbox_fb[22] = 4; + mbox_fb[23] = 4; + mbox_fb[24] = 1; // RGB, not BGR preferably + + mbox_fb[25] = FB_ALLOC_BUFFER; // get framebuffer, gets alignment on request + mbox_fb[26] = 8; + mbox_fb[27] = 8; + mbox_fb[28] = 4096; // FrameBufferInfo.pointer + mbox_fb[29] = 0; // FrameBufferInfo.size + + mbox_fb[30] = FB_PITCH_GET; // get pitch + mbox_fb[31] = 4; + mbox_fb[32] = 4; + mbox_fb[33] = 0; // FrameBufferInfo.pitch + + mbox_fb[34] = END_TAG; + + // this might not return exactly what we asked for, could be + // the closest supported resolution instead + if (mbox_call(MAILBOX_CH_PROP, mbox_fb) && mbox_fb[20] == 32 && + mbox_fb[28] != 0) { + mbox_fb[28] &= 0x3FFFFFFF; // convert GPU address to ARM address + fb_info.width = mbox_fb[5]; // get actual physical width + fb_info.height = mbox_fb[6]; // get actual physical height + fb_info.pitch = mbox_fb[33]; // get number of bytes per line + fb_info.isrgb = mbox_fb[24]; // get the actual channel order + lfb = mbox_fb[28]; // get pointer to the framebuffer + } else { + uart_log(ERR, "Unable to set screen resolution to 1024x768x32\n"); + } + + return &dev_framebuffer_operation; +} + +int dev_framebuffer_write(file *f, const void *buf, size_t len) { + disable_interrupt(); + if (len + f->f_pos > fb_info.pitch * fb_info.height) { + uart_log(WARN, "Buffer overflow\n"); + len = fb_info.pitch * fb_info.height - f->f_pos; + } + memcpy((void *)(TO_VIRT(lfb) + f->f_pos), buf, len); + f->f_pos += len; + enable_interrupt(); + return len; +} + +int dev_framebuffer_open(vnode *file_node, file *target) { + target->f_pos = 0; + target->vnode = file_node; + target->f_ops = file_node->f_ops; + return 0; +} + +int dev_framebuffer_close(file *f) { + kfree(f, SILENT); + return 0; +} + +long dev_framebuffer_lseek64(file *f, long offset, int whence) { + if (whence == SEEK_SET) { + f->f_pos = offset; + return f->f_pos; + } else if (whence == SEEK_CUR) { + long size = f->f_ops->getsize(f->vnode); + f->f_pos += offset; + if (f->f_pos > size) + f->f_pos = size; + } else + return not_supported(); + return f->f_pos; +} diff --git a/lab7/src/mem.c b/lab7/src/mem.c new file mode 100644 index 000000000..451661496 --- /dev/null +++ b/lab7/src/mem.c @@ -0,0 +1,380 @@ +#include "mem.h" + +#include "command.h" +#include "devtree.h" +#include "hardware.h" +#include "mbox.h" +#include "start.h" +#include "str.h" +#include "uart.h" +#include "utils.h" + +extern char *__bss_end; +extern void *DTB_BASE; +extern void *DTB_END; +extern void *initrd_start; +extern void *initrd_end; + +unsigned int BOARD_REVISION; +unsigned long BASE_MEMORY; +unsigned long TOTAL_MEMORY; +unsigned int NUM_PAGES; + +static struct page *pTable; +static struct page *freeList[BUDDY_MAX_ORDER + 1]; +static struct object *objectCache[CACHE_MAX_ORDER + 1]; + +extern char *__bss_end; // `__bss_end` is defined in linker script + +static char *heap_top; + +static void *simple_malloc(int size) { + void *p = (void *)heap_top; + if (size < 0) return 0; + heap_top += size; + return p; +} + +void init_mem() { + // Simple malloc init: Set heap base address + heap_top = (char *)&__bss_end; + uart_log(INFO, "heap_top = "); + uart_hex((uintptr_t)heap_top); + uart_putc(NEWLINE); + uart_log(INFO, "Simple malloc initialized.\n"); + + unsigned int __attribute__((aligned(16))) mbox[36]; + + // Get board revision + if (BOARD_REVISION == 0) { + get_board_revision(mbox); + BOARD_REVISION = mbox[5]; + } + // Get ARM memory base address and size + if (NUM_PAGES == 0) { + get_arm_memory_status(mbox); + BASE_MEMORY = mbox[5]; + TOTAL_MEMORY = mbox[6]; + NUM_PAGES = (TOTAL_MEMORY - BASE_MEMORY) / PAGE_SIZE; + uart_log(INFO, "Initialized pages: "); + uart_dec(NUM_PAGES); + uart_putc(NEWLINE); + } + + // cmd_info(); + + // Initialize the buddy allocator + int pTableSize = sizeof(struct page) * NUM_PAGES; + pTable = simple_malloc(pTableSize); + uart_log(INFO, "Page Table: "); + uart_hex((uintptr_t)pTable); + uart_putc(NEWLINE); + int current_order = BUDDY_MAX_ORDER; + for (int i = 0; i < NUM_PAGES; i++) { + pTable[i].order = 0; + pTable[i].used = 0; + pTable[i].cache_order = -1; + pTable[i].prev = 0; + pTable[i].next = 0; + if (i % (1 << current_order) == 0) { + while (current_order > 0 && NUM_PAGES - i < (1 << current_order)) { + current_order--; + } + pushPageToFreeList(&freeList[current_order], &pTable[i], current_order); + } + } + + // Reserve memory: + // PGD table + reserveMemory(BOOT_PGD_PAGE_FRAME, BOOT_PGD_PAGE_FRAME + PAGE_SIZE); + // PUD table + reserveMemory(BOOT_PUD_PAGE_FRAME, BOOT_PUD_PAGE_FRAME + PAGE_SIZE); + // PMD table + reserveMemory(BOOT_PMD_PAGE_FRAME, BOOT_PMD_PAGE_FRAME + PAGE_SIZE); + // Kernel & stack; page table + reserveMemory(0x80000 - STACK_SIZE, (uintptr_t)&__bss_end + pTableSize); + // Initramfs + reserveMemory((uintptr_t)initrd_start, (uintptr_t)initrd_end); + // Devicetree + reserveMemory((uintptr_t)DTB_BASE, (uintptr_t)DTB_END); +} + +void reserveMemory(uintptr_t resv_start, uintptr_t resv_end) { + // Round the start and end addresses to the page boundary + resv_start = TO_VIRT(resv_start & ~(PAGE_SIZE - 1)); + resv_end = TO_VIRT((resv_end + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1)); + + uart_log(INFO, "Reserving memory: "); + uart_addr_range(resv_start, resv_end); + uart_putc(NEWLINE); + resv_end--; + + for (int order = BUDDY_MAX_ORDER; order >= 0; order--) { + struct page *current = freeList[order]; + while (current != 0) { + struct page *next = current->next; + uint64_t page_start = page_vaddr(current); + uint64_t page_end = page_start + (PAGE_SIZE << order) - 1; + if (page_start >= resv_start && page_end <= resv_end) { + // [page page] + // Remove the page from the free list + current->used = 1; + removePageFromFreeList(&freeList[order], current); + } else if (resv_start > page_end || resv_end < page_start) { + // ---resv> [page or page] or order = order; + page->used = 0; + page->prev = 0; + page->next = 0; + + if (*list_head == 0 || (*list_head) < page) { + if (*list_head != 0) (*list_head)->prev = page; + page->next = *list_head; + *list_head = page; + return; + } + + struct page *current = *list_head; + while (current->next != 0 && page < current->next) { + current = current->next; + } + page->prev = current; + page->next = current->next; + if (current->next != 0) current->next->prev = page; + current->next = page; +} + +struct page *popFreeList(struct page **list_head) { + if (*list_head == 0) return 0; + + struct page *page = *list_head; + *list_head = page->next; + page->used = 1; + return page; +} + +void removePageFromFreeList(struct page **list_head, struct page *page) { + if (page->prev != 0) page->prev->next = page->next; + if (page->next != 0) page->next->prev = page->prev; + if (page == *list_head) *list_head = page->next; +} + +uintptr_t page_vaddr(struct page *p) { + return TO_VIRT((unsigned long)(p - pTable) * PAGE_SIZE); +} + +uintptr_t page_paddr(struct page *p) { + return TO_PHYS((unsigned long)(p - pTable) * PAGE_SIZE); +} + +void printFreeListByOrder(unsigned int order) { + struct page *page = freeList[order]; + if (page > 0) uart_log(BUDD, ""); + while (page != 0) { + uart_putc(TAB); + uart_addr_range(page_vaddr(page), page_vaddr(page + (1 << order))); + uart_puts(" ["); + if (order < 10) uart_putc(' '); + uart_dec(order); + uart_puts("]\n"); + page = page->next; + } +} + +struct page *lookupBuddy(struct page *page, unsigned int order) { + unsigned int buddy_pfn = (unsigned int)(page - pTable) ^ (1 << order); + return &pTable[buddy_pfn]; +} + +struct page *allocatePagesByOrder(unsigned int order, int silent) { + if (!silent) { + uart_log(BUDD, "Memory block requested: "); + uart_dec(1 << order); + uart_puts(" page(s).\n"); + } + + for (int i = order; i <= BUDDY_MAX_ORDER; i++) { + if (freeList[i] == 0) // No free page available + continue; // Try next order + struct page *page = popFreeList(&freeList[i]); + page->order = order; // Update order of the page + + while (i > order) { // requires splitting + i--; + struct page *buddy = lookupBuddy(page, i); + pushPageToFreeList(&freeList[i], buddy, i); + + if (silent) continue; + // Print information + uart_log(BUDD, "Split "); + uart_addr_range(page_vaddr(page), page_vaddr(page + (1 << i))); + uart_puts("//"); + uart_addr_range(page_vaddr(buddy), page_vaddr(buddy + (1 << i))); + uart_puts(" => ["); + uart_dec(i); + uart_puts("]\n"); + } + if (!silent) { + uart_log(BUDD, "Memory allocated: "); + uart_addr_range(page_vaddr(page), page_vaddr(page + (1 << order))); + uart_putc(NEWLINE); + } + + return page; + } + return 0; +} + +void freePages(struct page *page, unsigned int order, int silence) { + if (!silence) { + uart_log(BUDD, "Free "); + uart_dec(1 << order); + uart_puts("-page memory block starting from "); + uart_hex(page_vaddr(page)); + uart_putc(NEWLINE); + } + mergePages(page, order, silence); +} + +void mergePages(struct page *page, unsigned int order, int silence) { + struct page *current = page; + while (order < BUDDY_MAX_ORDER) { + struct page *buddy = lookupBuddy(current, order); + if (buddy->order != order || buddy->used == 1) break; + + removePageFromFreeList(&freeList[order], buddy); + + if (current > buddy) { + struct page *tmp = current; + current = buddy; + buddy = tmp; + } + + order++; + if (silence) continue; + uart_log(BUDD, "Merge "); + uart_addr_range(page_vaddr(current), + page_vaddr(current + (1 << (order - 1)))); + uart_puts("~~"); + uart_addr_range(page_vaddr(buddy), page_vaddr(buddy + (1 << (order - 1)))); + uart_puts(" => ["); + uart_dec(order); + uart_puts("]\n"); + } + pushPageToFreeList(&freeList[order], current, order); +} + +/* Cache Allocator */ + +void pushObjectToList(struct object **list_head, struct object *object, + unsigned int order) { + object->order = order; + object->next = *list_head; + *list_head = object; +} + +struct object *popObjectFromList(struct object **list_head) { + if (*list_head == 0) return 0; + + struct object *object = *list_head; + *list_head = object->next; + return object; +} + +void *allocateCacheMemory(unsigned int order, int silent) { + if (!silent) { + uart_log(CACH, "Allocating "); + uart_dec(MIN_CACHE_SIZE << order); + uart_puts(" bytes."); + uart_putc(NEWLINE); + } + + if (objectCache[order] == 0) { + struct page *page = allocatePagesByOrder(0, silent); + page->cache_order = order; + uintptr_t page_addr = page_vaddr(page); + int cache_size = MIN_CACHE_SIZE << order; + for (int i = 0; i < PAGE_SIZE; i += cache_size) { + struct object *obj = (struct object *)(uintptr_t)(page_addr + i); + pushObjectToList(&objectCache[order], obj, order); + } + } + void *p = popObjectFromList(&objectCache[order]); + if (!silent) { + uart_log(CACH, "Allocated memory starting from "); + uart_hex((uintptr_t)p); + uart_putc(NEWLINE); + } + return p; +} + +void freeCacheEntry(void *ptr, unsigned int index, int silence) { + if (!silence) { + uart_log(CACH, "Free memory cache of "); + uart_dec(MIN_CACHE_SIZE << index); + uart_puts(" bytes starting from "); + uart_hex((uintptr_t)ptr); + uart_putc(NEWLINE); + } + pushObjectToList(&objectCache[index], ptr, index); +} + +/* Dynamic Memory Allocator */ + +void *kmalloc(unsigned int size, int silent) { + if (size == 0) return 0; + + if (!silent) { + uart_log(INFO, "Memory requested: "); + uart_dec(size); + uart_puts(" byte(s)."); + uart_putc(NEWLINE); + } + + if (size > PAGE_SIZE / 2) { + // Buddy Allocator + int order = 0; + while ((PAGE_SIZE << order) < size) order++; + struct page *page = allocatePagesByOrder(order, silent); + return (void *)page_vaddr(page); + } else { + // Cache Allocator + int power = 0; + while ((1 << power) < size) power++; + int order = (power > 5) ? power - 5 : 0; + return allocateCacheMemory(order, silent); + } +} + +void kfree(void *ptr, int silence) { + // Check if the pointer is page-aligned + struct page *page = &pTable[TO_PHYS(ptr) / PAGE_SIZE]; + if (TO_PHYS(ptr) % PAGE_SIZE == 0) { + // Check if the page is allocated by the buddy allocator + if (page->cache_order == -1) { + // Free the page using the buddy allocator + freePages(page, page->order, silence); + return; + } + } + // Free the object using the cache allocator + struct object *object = ptr; + freeCacheEntry(object, page->cache_order, silence); +} diff --git a/lab7/src/scheduler.S b/lab7/src/scheduler.S new file mode 100644 index 000000000..15fcfbeca --- /dev/null +++ b/lab7/src/scheduler.S @@ -0,0 +1,54 @@ +.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 // set current thread_struct + ret + + /* + struct context_struct { + unsigned long x19; + unsigned long x20; + unsigned long x21; + unsigned long x22; + unsigned long x23; + unsigned long x24; + unsigned long x25; + unsigned long x26; + unsigned long x27; + unsigned long x28; + unsigned long fp; + unsigned long lr; + unsigned long sp; + }; + */ + +.global switch_mm +switch_mm: + dsb ish // ensure write has completed + msr ttbr0_el1, x0 // switch translation based address + tlbi vmalle1is // invalidate all TLB entries + dsb ish // ensure completion of TLB invalidation + isb // clear pipeline + ret + +.global get_current +get_current: + mrs x0, tpidr_el1 // current thread_struct + ret + diff --git a/lab7/src/scheduler.c b/lab7/src/scheduler.c new file mode 100644 index 000000000..80e500839 --- /dev/null +++ b/lab7/src/scheduler.c @@ -0,0 +1,237 @@ +#include "scheduler.h" + +#include "irq.h" +#include "mem.h" +#include "start.h" +#include "uart.h" +#include "utils.h" +#include "virtm.h" + +static int IDLE_PROCESS_PID; +static int thread_count = 0; +thread_struct *running_tcircle; + +void sched_init() { + uart_log(INFO, "Initializing Scheduler...\n"); + IDLE_PROCESS_PID = kcreate_thread(idle)->pid; + asm volatile( + "msr tpidr_el1, %0" ::"r"(running_tcircle) // Software Thread ID Register + ); + uart_log(INFO, "Scheduler Initialized.\n"); +} + +static void add_thread(thread_struct **queue, thread_struct *thread) { + if (*queue == 0) { + *queue = thread; + thread->next = thread; + thread->prev = thread; + } else { + // prev <- thread -> *queue + thread->next = *queue; + thread->prev = (*queue)->prev; + // prev -> thread <- *queue + (*queue)->prev->next = thread; + (*queue)->prev = thread; + } +} + +thread_struct *kcreate_thread(void (*func)()) { + thread_struct *thread = kmalloc(sizeof(thread_struct), SILENT); + uart_log(INFO, "Creating thread PID: "); + uart_dec(thread->pid = thread_count++); + uart_putc(NEWLINE); + + thread->start = func ? func : 0; + thread->size = 0; + thread->state = RUNNING; + + thread->stack = kmalloc(STACK_SIZE, SILENT); + uart_log(INFO, "Acquired thread kernel stack at "); + uart_hex((uintptr_t)thread->stack); + uart_putc(NEWLINE); + thread->user_stack = 0; // alloc when needed (el0) + + memset(thread->sig_handlers, 0, sizeof(thread->sig_handlers)); + thread->sig_reg = 0; + thread->sig_busy = 0; + + // Added for lab6 + memset(thread->pgd = kmalloc(PAGE_SIZE, SILENT), 0, PAGE_SIZE); + uart_log(INFO, "Acquired thread pgd at "); + uart_hex((uintptr_t)thread->pgd); + uart_putc(NEWLINE); + + // Added for lab7 + thread->cwd = kmalloc(2, SILENT); + thread->cwd[0] = '/'; + thread->cwd[1] = 0; + + thread->file_descriptor = 0; + thread->file_descriptor_index = 0; + + thread->context.lr = (unsigned long)func; + thread->context.sp = (unsigned long)thread->stack + STACK_SIZE; + thread->context.fp = (unsigned long)thread->stack + STACK_SIZE; + add_thread(&running_tcircle, thread); + return thread; +} + +static void remove_thread(thread_struct **queue, thread_struct *thread) { + if (*queue == thread) + *queue = (thread->next == thread) ? 0 : thread->next; + thread->next->prev = thread->prev; + thread->prev->next = thread->next; +} + +void list_tcircle() { + thread_struct *thread = running_tcircle; + uart_log(INFO, "All thread(s) in queue:\n"); + do { + uart_putc(TAB); + uart_puts("pid = "); + uart_dec(thread->pid); + if (thread->sig_handlers[9] > 0) + uart_putc('*'); + uart_puts(", lr: "); + uart_hex((uintptr_t)thread->start); + uart_puts(", ksp: "); + uart_hex((uintptr_t)thread->context.sp); + if (thread->pid == get_current()->pid) + uart_puts(" <- current"); + uart_putc(NEWLINE); + thread = thread->next; + } while (thread != running_tcircle); +} + +void list_fd(thread_struct *thread) { + file *curr = thread->file_descriptor; + uart_log(INFO, "File descriptors from pid "); + uart_dec(thread->pid); + uart_putc(' '); + uart_hex((uintptr_t)thread); + uart_putc(NEWLINE); + while (curr != 0) { + uart_putc(TAB); + uart_dec(curr->id); + uart_putc(' '); + uart_hex((uintptr_t)curr); + if (curr == thread->file_descriptor) + uart_puts(" <- head"); + uart_putc(NEWLINE); + if (curr == curr->next) + break; + curr = curr->next; + } + uart_putc(TAB); + uart_puts("File descriptor index: "); + uart_dec(thread->file_descriptor_index); + uart_putc(NEWLINE); +} + +void schedule() { + switch_mm((unsigned long)TO_PHYS(get_current()->next->pgd)); + switch_to(get_current(), get_current()->next); +} + +void kill_zombies() { + thread_struct *next, *thread = running_tcircle; + do { + next = thread->next; + if (thread->state == DEAD) { + remove_thread(&running_tcircle, thread); + kfree(thread->stack, SILENT); + if (thread->user_stack) + kfree(thread->user_stack, SILENT); + kfree(thread->pgd, SILENT); + kfree(thread, SILENT); + } + thread = next; + } while (thread != running_tcircle); +} + +void idle() { + while (1) { + kill_zombies(); + schedule(); + } +} + +thread_struct *get_thread_by_pid(int pid) { + thread_struct *thread = running_tcircle; + do { + if (thread->pid == pid) + return thread; + thread = thread->next; + } while (thread != running_tcircle); + return 0; +} + +void kill_current_thread() { + get_current()->state = DEAD; + schedule(); +} + +void kill_thread_by_pid(int pid) { + if (pid == IDLE_PROCESS_PID) { + uart_log(WARN, "Cannot kill idle process.\n"); + } else { + thread_struct *thread = running_tcircle; + do { + if (thread->pid == pid) { + uart_log(INFO, "Killing pid "); + uart_dec(pid); + uart_putc(NEWLINE); + thread->state = DEAD; + schedule(); + return; + } + thread = thread->next; + } while (thread != running_tcircle); + uart_log(WARN, "Nothing to kill.\n"); + } + schedule(); +} + +int push_file_descriptor(thread_struct *thread, file *fd) { + fd->id = thread->file_descriptor_index++; + fd->next = thread->file_descriptor; + if (thread->file_descriptor != 0) { + thread->file_descriptor->prev = fd; + } + thread->file_descriptor = fd; + return fd->id; +} + +file *get_file_descriptor_by_id(thread_struct *thread, int id) { + // uart_log(INFO, "Getting file descriptor from pid "); + // uart_dec(thread->pid); + // uart_puts(" with id "); + // uart_dec(id); + // uart_putc(NEWLINE); + file *fd = thread->file_descriptor; + while (fd != 0) { + if (fd->id == id) { + if (fd != 0) { + // uart_log(INFO, "Found file descriptor: "); + // uart_hex((uintptr_t)fd); + // uart_putc(NEWLINE); + return fd; + } + } + fd = fd->next; + } + return 0; +} + +file *get_file_descriptor_by_vnode(thread_struct *thread, vnode *v) { + file *fd = thread->file_descriptor; + while (fd != 0) { + if (fd->vnode == v) { + if (fd != 0) { + return fd; + } + } + fd = fd->next; + } + return 0; +} \ No newline at end of file diff --git a/lab7/src/shell.c b/lab7/src/shell.c new file mode 100644 index 000000000..6f833e53c --- /dev/null +++ b/lab7/src/shell.c @@ -0,0 +1,84 @@ +#include "shell.h" + +#include "command.h" +#include "mem.h" +#include "str.h" +#include "uart.h" + +static void welcome_msg() { + cmd_info(); + uart_puts( + "*******************************\n" + "*** YADOS 0.07 for OSC 2024 ***\n" + "*******************************\n" + "Hopefully this will be Yet Another Dope OS!\n"); + uart_putc(NEWLINE); + cmd_hello(); +} + +void run_shell() { + welcome_msg(); + + char *buffer = kmalloc(SHELL_BUF_SIZE, SILENT); + while (1) { + uart_putc(NEWLINE); + uart_puts("# "); + read_user_input(buffer); + if (exec_command(buffer)) { + uart_log(WARN, "Command not found.\n"); + } + } +} + +void read_user_input(char *buf) { + int idx = 0; + while (idx < SHELL_BUF_SIZE) { + char c = uart_getc(); + switch (c) { + case NEWLINE: + uart_putc(NEWLINE); + buf[idx] = '\0'; + return; + case DELETE: + if (idx > 0) { + idx--; + uart_putc(BACKSPACE); + uart_putc(' '); + uart_putc(BACKSPACE); + } + break; + case ESC: + uart_putc(NEWLINE); + uart_log(WARN, "ESC detected. Clearing buffer...\n"); + uart_log(INFO, "Press Spacebar or Enter to continue.\n"); + while (1) { + char c = uart_getc(); + if (c == ' ' || c == NEWLINE) break; + } + buf[0] = '\0'; + uart_clear(); + return; + default: + if (c >= 32 && c <= 126) { + uart_putc(c); + buf[idx++] = c; + } + } + } + uart_putc(NEWLINE); + uart_log(WARN, "Buffer overflow. Please re-enter your command.\n"); + buf[0] = '\0'; +} + +int exec_command(const char *input) { + if (strlen(input) == 0) return 0; + struct command *cmd = cmd_list; + while (strcmp(cmd->name, END_OF_COMMAND_LIST)) { + if (!strcmp(cmd->name, input)) { + cmd->func(); + return 0; + } + cmd++; + } + return -1; +} \ No newline at end of file diff --git a/lab7/src/signals.c b/lab7/src/signals.c new file mode 100644 index 000000000..7179952f7 --- /dev/null +++ b/lab7/src/signals.c @@ -0,0 +1,68 @@ +#include "signals.h" + +#include "mem.h" +#include "scheduler.h" +#include "syscalls.h" +#include "traps.h" +#include "uart.h" +#include "utils.h" +#include "virtm.h" + +void signal(int SIGNAL, void (*handler)()) { + if (SIGNAL < 1 || SIGNAL > NSIG) { + uart_log(ERR, "Invalid signal number.\n"); + return; + } + uart_log(INFO, "Signal handler set for signal "); + uart_dec(SIGNAL); + uart_putc(NEWLINE); + get_current()->sig_handlers[SIGNAL] = handler; +} + +void signal_kill(int pid, int SIGNAL) { + uart_log(INFO, "Signal "); + uart_dec(SIGNAL); + uart_puts(" sent to pid "); + uart_dec(pid); + uart_putc(NEWLINE); + thread_struct *thread = get_thread_by_pid(pid); + if (thread == 0) { + uart_log(WARN, "No one gets the signal.\n"); + return; + }; + thread->sig_reg |= 1 << (SIGNAL - 1); // Set the signal pending bit +} + +void do_signal(trap_frame *tf) { + // Prevent nested signal handling + if (get_current()->sig_busy) return; + int sig_num = 1; + while (get_current()->sig_reg) { + if (get_current()->sig_reg & (0x1 << (sig_num - 1))) { + get_current()->sig_busy = 1; // block other signal handling + get_current()->sig_reg &= ~(0x1 << (sig_num - 1)); + + if (get_current()->sig_handlers[sig_num] == 0) { + kill_current_thread(); // Default handler: kill the process + return; + } + + // Save the sigframe + memcpy(get_current()->sig_tf = kmalloc(sizeof(trap_frame), SILENT), tf, + sizeof(trap_frame)); + get_current()->sig_stack = + kmalloc(STACK_SIZE, SILENT); // free at sys_sigreturn + + // Map the stack for signal handling + map_pages((unsigned long)get_current()->pgd, SIG_STACK, STACK_SIZE, + (unsigned long)TO_PHYS(get_current()->sig_stack)); + + tf->x30 = (uintptr_t)sigreturn; // Return to sigreturn (traps.S) after + tf->spsr_el1 = 0x340; + tf->elr_el1 = (unsigned long)get_current()->sig_handlers[sig_num]; + tf->sp_el0 = (unsigned long)SIG_STACK + STACK_SIZE; // top of sig_stack + return; // eret to the signal handler (->el0) + } + sig_num++; + } +} \ No newline at end of file diff --git a/lab7/src/start.S b/lab7/src/start.S new file mode 100644 index 000000000..015417aff --- /dev/null +++ b/lab7/src/start.S @@ -0,0 +1,139 @@ +#include "start.h" + +.section ".text.boot" + +.global _start +_start: + mov x10, x0 // preserve dtb base address + + /* switch from EL2 to EL1 */ + bl from_el2_to_el1 + +/* --- lab6 --- */ + + /* set up kernel page tables */ + ldr x0, =TCR_CONFIG_DEFAULT + msr tcr_el1, x0 // Translation Control Register + + ldr x0, =( \ + (MAIR_DEVICE_nGnRnE << (MAIR_IDX_DEVICE_nGnRnE * 8)) | \ + (MAIR_NORMAL_NOCACHE << (MAIR_IDX_NORMAL_NOCACHE * 8)) \ + ) + msr mair_el1, x0 // Memory Attribute Indirection Register + + mov x0, BOOT_PGD_PAGE_FRAME // PGD's page frame + mov x1, BOOT_PUD_PAGE_FRAME // PUD's page frame + mov x2, BOOT_PMD_PAGE_FRAME // PMD's page frame + + // PGD + ldr x6, =BOOT_PGD_ATTR + orr x6, x1, x6 // set PGD->PUD entries + str x6, [x0] + + // PUD + ldr x6, =BOOT_PUD_ATTR + orr x6, x2, x6 // set PUD[0]->PMD entries + str x6, [x1] + + ldr x6, =BOOT_PUD_DEVICE_ATTR + mov x3, 0x40000000 + orr x6, x3, x6 // set PUD[1]->MMIO device addr + str x6, [x1, 8] + + /* + +-------------------+ 0x00000000 + | PGD[0]->PUD[0] | PGD bottom + | | + |-------------------| 0x00001000 + | PUD[0]->PMD[0] | PUD bottom + | PUD[1]>>0x40000000| PUD to MMIO device + | | + |-------------------| 0x00002000 + | PMD[0]>>0x0 | PMD bottom + | PMD[1]>>0x200000 | PMD to normal memory + | PMD[2]>>0x400000 | ... + | ... | + | PMD[_]>>0x3F000000| PMD to MMIO device + | ... | ... + | PMD[_]>>0x3FE00000| PMD top + | | + |-------------------| 0x00003000 + | ... | + + */ + + + // PMD + mov x3, 0x00000000 // PMD block bottom + mov x4, 0x3F000000 // MMIO device bottom + mov x5, 0x40000000 // PMD block top + +pmd_loop: + cmp x3, x5 // reach PMD top? + beq enable_mmu // leave the loop + cmp x3, x4 // reach MMIO device bottom? + ldr x6, =BOOT_PMD_DEVICE_ATTR // yes, set device PMD entry + bge set_pmd_entry + ldr x6, =BOOT_PMD_NORMAL_ATTR // no, set normal PMD entry + +set_pmd_entry: + orr x6, x3, x6 + str x6, [x2], #8 // x2 += 8 + add x3, x3, 0x200000 + b pmd_loop + +enable_mmu: // mmu: memory management unit + msr ttbr0_el1, x0 // [no need?]will be modified by each user process + msr ttbr1_el1, x0 // also load PGD to the upper translation based register + mrs x2, sctlr_el1 // System Control Register + orr x2, x2, 1 + msr sctlr_el1, x2 // enable MMU, cache remains disabled + + ldr x2, =boot_rest // indirect branch to the virtual address + br x2 + +/* --- lab6^^^ --- */ + +boot_rest: + /* get dtb address from x10 */ + ldr x1, =DTB_BASE // defined in devtree.c + str x10, [x1] + + /* get cpu id */ + mrs x1, mpidr_el1 /* Multiprocessor Affinity Register */ + and x1, x1, #3 + cbnz x1, halt // halt if cpu id != 0 + + /* set exception vector table */ + adr x0, exception_vector_table // defined in traps.S + msr vbar_el1, x0 + + /* set stack pointer */ + ldr x1, =_start + mov sp, x1 + + /* clear bss section */ + ldr x1, =__bss_start + ldr w2, =__bss_size + +bss_reset: + cbz w2, run_main + str xzr, [x1], #8 + sub w2, w2, #1 + cbnz w2, bss_reset + +run_main: + /* branch to main function */ + bl main + +halt: + wfe + b halt + +from_el2_to_el1: + mov x0, (1 << 31) + msr hcr_el2, x0 // EL1 uses aarch64 + mov x0, 0x3C5 // ... 0011 1100 0101 + msr spsr_el2, x0 + msr elr_el2, lr + eret // return to EL1 diff --git a/lab7/src/str.c b/lab7/src/str.c new file mode 100644 index 000000000..ac88615dd --- /dev/null +++ b/lab7/src/str.c @@ -0,0 +1,33 @@ +#include "str.h" + +/** + * Compare two strings, return 0 if they are equal, or otherwise. + */ +int strcmp(const char *str1, const char *str2) { + while (*str1 && *str1 == *str2) str1++, str2++; + return *(unsigned char *)str1 - *(unsigned char *)str2; +} + +char *strncpy(char *dest, const char *src, int n) { + while (n-- && (*dest++ = *src++)); + return dest; +} + +int strlen(const char *str) { + int len = 0; + while (*str++ != '\0') len++; + return len; +} + +char *strcat(char *dest, const char *src) { + char *d = dest; + + // Move the pointer to the end of the dest string + while (*d != '\0') d++; + // Copy the src string to the end of the dest string + while (*src != '\0') *d++ = *src++; + // Add the null terminator + *d = '\0'; + + return dest; +} diff --git a/lab7/src/syscalls.c b/lab7/src/syscalls.c new file mode 100644 index 000000000..aa43d245c --- /dev/null +++ b/lab7/src/syscalls.c @@ -0,0 +1,204 @@ +#include "syscalls.h" + +#include "initramfs.h" +#include "irq.h" +#include "mem.h" +#include "scheduler.h" +#include "str.h" +#include "traps.h" +#include "uart.h" +#include "utils.h" +#include "virtm.h" + +int sys_getpid() { return get_current()->pid; } + +size_t sys_uart_read(char *buf, size_t size) { + size_t i = 0; + while (i < size) + buf[i++] = uart_getc(); + return i; +} + +size_t sys_uart_write(const char *buf, size_t size) { + size_t i = 0; + while (i < size) + uart_putc(buf[i++]); + return i; +} + +int sys_exec(const char *name, trap_frame *tf) { + thread_struct *thread = get_current(); + char *p = absolute_path(name, thread->cwd); + vnode *target_file; + vfs_lookup(p, &target_file); // assign the vnode to target + kfree(p, SILENT); + vfs_exec(target_file, tf); + return 0; +} + +extern void *lfb; + +int sys_fork(trap_frame *tf) { + disable_interrupt(); // prevent schedule premature fork + + thread_struct *parent = get_current(); + thread_struct *child = kcreate_thread(0); + + child->start = parent->start; + child->size = parent->size; + + // list_fd(parent); + file *fd = parent->file_descriptor; + while (fd) { + file *f = create_file(); + memcpy(f, fd, sizeof(file)); + push_file_descriptor(child, f); + f->id = fd->id; + fd = fd->next; + } + // list_fd(child); + + child->file_descriptor_index = parent->file_descriptor_index; + + child->user_stack = kmalloc(STACK_SIZE, SILENT); + + mapping_user_thread(child, 0x3000000); + + // Handling kernel stack (incl. tf from parent) + memcpy(child->stack, parent->stack, STACK_SIZE); + memcpy(child->user_stack, parent->user_stack, STACK_SIZE); + + // Copy signal handlers + memcpy(child->sig_handlers, parent->sig_handlers, + sizeof(parent->sig_handlers)); + + // get child's trap frame from kstack via parent's offset + size_t ksp_offset = (uintptr_t)tf - (uintptr_t)parent->stack; + trap_frame *child_tf = (trap_frame *)(child->stack + ksp_offset); + + child->context.lr = (uintptr_t)child_ret_from_fork; // traps.S + child->context.sp = (uintptr_t)child_tf; // set child's ksp + child->context.fp = (uintptr_t)child_tf; // set child's ksp + child_tf->sp_el0 = tf->sp_el0; // set child's user sp + child_tf->x0 = 0; // ret value of fork() for child + + enable_interrupt(); + return child->pid; +} + +void sys_exit(int status) { + uart_log(status ? WARN : INFO, "Exiting process ...\n"); + kill_current_thread(); +} + +int sys_mbox_call(unsigned char ch, unsigned int *mbox) { + unsigned int *itm = kmalloc(mbox[0], SILENT); + memcpy(itm, mbox, mbox[0]); + int ret = mbox_call(ch, (unsigned int *)itm); + memcpy(mbox, itm, mbox[0]); + kfree(itm, SILENT); + return ret; +} + +void sys_sigreturn(trap_frame *tf) { + memcpy(tf, get_current()->sig_tf, sizeof(trap_frame)); + kfree(get_current()->sig_tf, SILENT); + kfree(get_current()->sig_stack, SILENT); + get_current()->sig_busy = 0; + return; +} + +int sys_open(const char *pathname, int flags) { + thread_struct *thread = get_current(); + char *p = absolute_path(pathname, thread->cwd); + file *f = create_file(); + if (vfs_open(p, flags, f)) + return -1; + // list_fd(thread); + file *existing = get_file_descriptor_by_vnode(thread, f->vnode); + // uart_log(DEBUG, "existing: "); + // uart_hex((uintptr_t)existing); + // uart_putc(NEWLINE); + int id = (flags & O_CREAT) || !existing ? push_file_descriptor(thread, f) + : existing->id; + uart_log(VFS, "open: "); + uart_puts(p); + uart_puts(", id: "); + uart_dec(id); + uart_puts(", flags: "); + uart_simple_hex(flags); + uart_putc(NEWLINE); + kfree(p, SILENT); + return id; +} + +int sys_close(int fd) { + thread_struct *thread = get_current(); + file *f = get_file_descriptor_by_id(thread, fd); + // list_fd(thread); + if (f == 0) + return -1; + if (thread->file_descriptor == f) + thread->file_descriptor = f->next; + else { + f->prev->next = f->next; + if (f->next) + f->next->prev = f->prev; + } + // list_fd(thread); + return vfs_close(f); +} + +int sys_write(int fd, void *buf, size_t count) { + file *f = get_file_descriptor_by_id(get_current(), fd); + if (f == 0) + return -1; + return vfs_write(f, buf, count); +} + +int sys_read(int fd, void *buf, size_t count) { + // list_fd(get_current()); + file *f = get_file_descriptor_by_id(get_current(), fd); + if (f == 0) + return -1; + return vfs_read(f, buf, count); +} + +int sys_mkdir(const char *pathname, unsigned mode) { + char *p = absolute_path(pathname, get_current()->cwd); + return vfs_mkdir(p); +} + +int sys_mount(const char *target, const char *filesystem) { + char *p = absolute_path(target, get_current()->cwd); + int ret = vfs_mount(p, filesystem); + return ret; +} + +int sys_chdir(const char *path) { + char *p = absolute_path(path, get_current()->cwd); + char *cwd = get_current()->cwd; + get_current()->cwd = p; + kfree(cwd, SILENT); + return 0; +} + +long sys_lseek64(int fd, long offset, int whence) { + file *f = get_file_descriptor_by_id(get_current(), fd); + if (f == 0) + return -1; + return f->f_ops->lseek64(f, offset, whence); +} + +extern framebuffer_info *fb_info; + +// ioctl 0 will be use to get info +// there will be default value in info +// if it works with default value, you can ignore this syscall +int sys_ioctl(int fd, unsigned long request, void *info) { + if (request == GET_INFO) { + memcpy(info, &fb_info, sizeof(framebuffer_info)); + return 0; + } + return not_supported(); +} diff --git a/lab7/src/timer.c b/lab7/src/timer.c new file mode 100644 index 000000000..ebd9a849c --- /dev/null +++ b/lab7/src/timer.c @@ -0,0 +1,117 @@ +#include "timer.h" + +#include "mem.h" +#include "str.h" +#include "uart.h" + +static timerEntry *head = 0; + +void init_timer() { + asm volatile( + "mov x0, 1\n" + "msr cntp_ctl_el0, x0\n"); + + uart_log(INFO, "enable_timer_interrupt()\n"); + enable_timer_interrupt(); + + asm volatile( + "mrs x0, cntfrq_el0\n" + "msr cntp_tval_el0, x0\n"); + + // required by lab5 + uint64_t tmp; + asm volatile("mrs %0, cntkctl_el1" : "=r"(tmp)); + tmp |= 1; + asm volatile("msr cntkctl_el1, %0" : : "r"(tmp)); + + uart_log(INFO, "Timer IRQ frequency factor = "); + uart_dec(TIMER_SPEED); + uart_putc(NEWLINE); +} + +void enable_timer_interrupt() { + // Unmask the timer interrupt + asm volatile( + "mov x0, 2\n" + "ldr x1, =%0\n" + "str w0, [x1]\n" + : + : "i"(CORE0_TIMER_IRQCNTL) + : "x0", "x1"); +} + +void disable_timer_interrupt() { + // Mask timer interrupt + asm volatile( + "mov x0, 0\n" + "ldr x1, =%0\n" + "str w0, [x1]\n" + : + : "i"(CORE0_TIMER_IRQCNTL) + : "x0", "x1"); +} + +void timer_irq_handler() { + // Set up the next timer interrupt + asm volatile( + "mrs x0, cntfrq_el0\n" + "lsr x0, x0, %0\n" + "msr cntp_tval_el0, x0\n" + : + : "i"(TIMER_SPEED) // frequency >> 5, aka. 1/32 + : "x0"); + + // Check the timer queue + while (head != 0 && timer_get_uptime() >= head->time) { + head->func(head->arg); // Execute the callback function + kfree(head, SILENT); // Free the memory allocated for the timer node + head = head->next; // Remove the head node after func finished + } + + enable_timer_interrupt(); +} + +uint64_t timer_get_uptime() { + uint64_t cntpct_el0 = 0, cntfrq_el0 = 0; + asm volatile( + "mrs %0, cntpct_el0\n" + "mrs %1, cntfrq_el0\n" + : "=r"(cntpct_el0), "=r"(cntfrq_el0)); + return cntpct_el0 / cntfrq_el0; +} + +void timer_add(void (*callback)(void *), void *arg, int duration) { + // Insert a new timer into the linked list (sorted by time) + timerEntry *timer = kmalloc(sizeof(timerEntry), SILENT); + timer->func = callback; + timer->arg = arg; + timer->time = timer_get_uptime() + duration; + timer->next = 0; + + if (head == 0 || timer->time < head->time) { + // Insert as the head of the list + timer->next = head; + head = timer; + return; + } + + timerEntry *current = head; + while (current->next != 0 && current->next->time <= timer->time) + current = current->next; + timer->next = current->next; + current->next = timer; +} + +static void show_timer_message(void *arg) { + uart_putc(NEWLINE); + uart_log(INFO, "Timer message: "); + uart_puts((char *)arg); + uart_putc(NEWLINE); + kfree(arg, SILENT); // Free the memory allocated for arg +} + +void set_timer(const char *msg, int duration) { + timer_add((void (*)(void *))show_timer_message, (void *)msg, duration); +} + +void set_timeup(int *timeup) { *timeup = 1; } diff --git a/lab7/src/tmpfs.c b/lab7/src/tmpfs.c new file mode 100644 index 000000000..8ec1695ae --- /dev/null +++ b/lab7/src/tmpfs.c @@ -0,0 +1,174 @@ +#include "tmpfs.h" + +#include "mem.h" +#include "str.h" +#include "uart.h" +#include "utils.h" +#include "vfs.h" + +file_operations tmpfs_file_operations = { + tmpfs_write, // + tmpfs_read, // + tmpfs_open, // + tmpfs_close, // + tmpfs_lseek64, // + tmpfs_getsize // +}; + +vnode_operations tmpfs_vnode_operations = { + tmpfs_lookup, // + tmpfs_create, // + tmpfs_mkdir // +}; + +int tmpfs_setup_mount(filesystem *fs, mount *mnt) { + mnt->fs = fs; + mnt->root = tmpfs_create_vnode(dir_t); + return 0; +} + +vnode *tmpfs_create_vnode(fsnode_type type) { + vnode *v = kmalloc(sizeof(vnode), SILENT); + v->f_ops = &tmpfs_file_operations; + v->v_ops = &tmpfs_vnode_operations; + v->mount = 0; + + tmpfs_inode *inode = kmalloc(sizeof(tmpfs_inode), SILENT); + memset(inode, 0, sizeof(tmpfs_inode)); + inode->type = type; + inode->data = kmalloc(PAGE_SIZE, SILENT); + v->internal = inode; + + return v; +} + +int tmpfs_write(file *file, const void *buf, size_t len) { + tmpfs_inode *inode = file->vnode->internal; + memcpy(inode->data + file->f_pos, buf, len); + file->f_pos += len; + if (inode->datasize < file->f_pos) + inode->datasize = file->f_pos; + return len; +} + +int tmpfs_read(file *file, void *buf, size_t len) { + tmpfs_inode *inode = file->vnode->internal; + if (len + file->f_pos > inode->datasize) { + len = inode->datasize - file->f_pos; + } + + strncpy(buf, inode->data + file->f_pos, len); + file->f_pos += inode->datasize - file->f_pos; + return len; +} + +int tmpfs_open(vnode *file_node, file *target) { + target->vnode = file_node; + target->f_ops = file_node->f_ops; + target->f_pos = 0; + return 0; +} + +int tmpfs_close(file *f) { + kfree(f, SILENT); + return 0; +} + +// long seek 64-bit +long tmpfs_lseek64(file *f, long offset, int whence) { + // uart_log(DEBUG, "tmpfs_lseek64\n"); + if (whence == SEEK_SET) { + // return current position as offset + f->f_pos = offset; + } else if (whence == SEEK_CUR) { + // return current position + offset + long size = f->f_ops->getsize(f->vnode); + f->f_pos += offset; + if (f->f_pos > size) + f->f_pos = size; + } else + return not_supported(); + return f->f_pos; +} + +long tmpfs_getsize(vnode *vn) { + tmpfs_inode *inode = vn->internal; + return inode->datasize; +} + +int tmpfs_lookup(vnode *dir_node, vnode **target, const char *component_name) { + tmpfs_inode *dir_inode = dir_node->internal; + int child_idx = 0; + + // BFS search tree + for (; child_idx <= MAX_DIR_ENTRY; child_idx++) { + vnode *vnode_it = + dir_inode->entry[child_idx]; // entry stores all sub folder/file + if (!vnode_it) + break; + tmpfs_inode *inode_it = vnode_it->internal; + if (strcmp(component_name, inode_it->name) == 0) { + *target = vnode_it; + return 0; + } + } + return -1; +} + +int tmpfs_create(vnode *dir_node, vnode **target, const char *component_name) { + tmpfs_inode *inode = dir_node->internal; + if (inode->type != dir_t) { + uart_puts("[tmpfs create] not a dir\n"); + return -1; + } + + int child_idx = 0; + for (; child_idx <= MAX_DIR_ENTRY; child_idx++) { + if (!inode->entry[child_idx]) + break; + tmpfs_inode *child_inode = inode->entry[child_idx]->internal; + if (!strcmp(child_inode->name, component_name)) { + uart_log(WARN, "tmpfs_create: file exists\n"); + return -1; + } + } + + vnode *vn = tmpfs_create_vnode(file_t); + inode->entry[child_idx] = vn; + + tmpfs_inode *new_inode = vn->internal; + new_inode->name = kmalloc(strlen(component_name) + 1, SILENT); + strncpy(new_inode->name, component_name, strlen(component_name) + 1); + + *target = vn; + return 0; +} + +int tmpfs_mkdir(vnode *dir_node, vnode **target, const char *component_name) { + tmpfs_inode *inode = dir_node->internal; + if (inode->type != dir_t) { + uart_puts("[tmpfs mkdir] not a directory\n"); + return -1; + } + + int child_idx = 0; + for (; child_idx < MAX_DIR_ENTRY; child_idx++) { + if (!inode->entry[child_idx]) + break; + } + + if (child_idx > MAX_DIR_ENTRY) { + uart_puts("[tmpfs mkdir] dir entry full\n"); + return -1; + } + + vnode *vn = tmpfs_create_vnode(dir_t); + inode->entry[child_idx] = vn; + + tmpfs_inode *new_inode = vn->internal; + new_inode->name = kmalloc(strlen(component_name) + 1, SILENT); + strncpy(new_inode->name, component_name, strlen(component_name) + 1); + + *target = vn; + return 0; +} \ No newline at end of file diff --git a/lab7/src/traps.S b/lab7/src/traps.S new file mode 100644 index 000000000..f0bfd215c --- /dev/null +++ b/lab7/src/traps.S @@ -0,0 +1,117 @@ +/* save general registers to stack */ +.macro save_all + sub sp, sp, 16 * 17 + 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 x10, spsr_el1 // Saved Program Status Register (EL1) + mrs x11, elr_el1 // Exception Link Register (EL1) + mrs x12, sp_el0 + stp x30, x10, [sp, 16 * 15] + stp x11, x12, [sp, 16 * 16] +.endm + +/* load general registers from stack */ +.macro load_all + ldp x30, x10, [sp, 16 * 15] + ldp x11, x12, [sp, 16 * 16] + msr spsr_el1, x10 + msr elr_el1, x11 + msr sp_el0, x12 + 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, 16 * 17 +.endm + +exception_handler: + save_all + mov x0, sp // pass the stack pointer to exception_entry + bl exception_entry // defined in traps.c + load_all + eret + +irq_handler: + save_all + mov x0, sp + bl irq_entry // defined in irq.c + load_all + eret + +invalid_exc_handler: + save_all + mrs x0, elr_el1 // Exception Link Register (EL1) + mrs x1, esr_el1 // ESR_EL1, Exception Syndrome Register (EL1) + mrs x2, spsr_el1 // Saved Program Status Register (EL1) + mrs x3, ttbr0_el1 // Translation Table Base Register 0 (EL1) + mrs x4, ttbr1_el1 // Translation Table Base Register 1 (EL1) + mrs x5, far_el1 // Fault Address Register, EL1 + bl invalid_entry // defined in traps.c + load_all + eret + +.global child_ret_from_fork +child_ret_from_fork: + load_all // child's kernel stack (trap frame) + eret + +.global sigreturn +sigreturn: + mov x8, 139 + svc 0 + +.macro v_entry label + b \label +.align 7 +.endm + +/* EL1 Exception Vector table + * vector table should be aligned to 0x800 + * and each entry size is 0x80 */ +.align 11 +.global exception_vector_table +exception_vector_table: + // EL1 -> EL1 while using SP_EL0 + v_entry invalid_exc_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler + // EL1 -> EL1 while using SP_EL1 + v_entry invalid_exc_handler + v_entry irq_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler + // EL0 (AArch64) -> EL1 + v_entry exception_handler + v_entry irq_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler + // EL0 (AArch32) -> EL1 + v_entry invalid_exc_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler + v_entry invalid_exc_handler diff --git a/lab7/src/traps.c b/lab7/src/traps.c new file mode 100644 index 000000000..8a16345c0 --- /dev/null +++ b/lab7/src/traps.c @@ -0,0 +1,228 @@ +#include "traps.h" + +#include + +#include "irq.h" +#include "mbox.h" +#include "mem.h" +#include "scheduler.h" +#include "syscalls.h" +#include "uart.h" +#include "virtm.h" + +extern thread_struct *running_tcircle; +extern uintptr_t lfb; + +static void print_registers(uint64_t elr, uint64_t esr, uint64_t spsr, + uint64_t ttbr0_el1, uint64_t ttbr1_el1, + uint64_t far_el1) { + // Print spsr_el1 + uart_log(INFO, "spsr_el1: "); + uart_hex(spsr); + uart_putc(NEWLINE); + + // Print elr_el1 + uart_log(INFO, "elr_el1: "); + uart_hex(elr); + uart_putc(NEWLINE); + + // Print esr_el1 + uart_log(INFO, "esr_el1: "); + uart_hex(esr); + uart_putc(NEWLINE); + + // Print ttbr0_el1 + uart_log(INFO, "ttbr0_el1: "); + uart_hex(ttbr0_el1); + uart_putc(NEWLINE); + + // Print ttbr1_el1 + uart_log(INFO, "ttbr1_el1: "); + uart_hex(ttbr1_el1); + uart_putc(NEWLINE); + + // Print far_el1 + uart_log(INFO, "far_el1: "); + uart_hex(far_el1); + uart_putc(NEWLINE); +} + +void exception_entry(trap_frame *tf) { + unsigned long elr, esr, spsr, ttbr0_el1, ttbr1_el1, far_el1; + asm volatile("mrs %0, elr_el1\n" + "mrs %1, esr_el1\n" + "mrs %2, spsr_el1\n" + "mrs %3, ttbr0_el1\n" + "mrs %4, ttbr1_el1\n" + "mrs %5, far_el1\n" + : "=r"(elr), // + "=r"(esr), // + "=r"(spsr), // + "=r"(ttbr0_el1), // + "=r"(ttbr1_el1), // + "=r"(far_el1) // + : + : "memory"); + if (esr != 0x56000000) { + print_registers(elr, esr, spsr, ttbr0_el1, ttbr1_el1, far_el1); + while (1) + ; + } + + enable_interrupt(); // Prevent uart blocking the interrupt[?] + + unsigned int syscall_num = tf->x8; + switch (syscall_num) { + case 0: { // int getpid() + unsigned int test = tf->x7; + unsigned int pid = sys_getpid(); + tf->x0 = pid; + if (test != 255) { + list_tcircle(); + list_fd(get_current()); + } + break; + } + case 1: { // size_t uartread(char buf[], size_t size) + char *buf = (char *)tf->x0; + size_t size = tf->x1; + tf->x0 = sys_uart_read(buf, size); + break; + } + case 2: { // size_t uartwrite(const char buf[], size_t size) + char *buf = (char *)tf->x0; + size_t size = tf->x1; + tf->x0 = sys_uart_write(buf, size); + break; + } + case 3: { // int exec(const char *name, char *const argv[]) + const char *name = (char *)tf->x0; + tf->x0 = sys_exec(name, tf); + tf->elr_el1 = 0x0; + tf->sp_el0 = USER_STACK + STACK_SIZE; + break; + } + case 4: { // int fork() + tf->x0 = sys_fork(tf); + list_tcircle(); + break; + } + case 5: { // void exit(int status) + int status = tf->x0; + sys_exit(status); + break; + } + case 6: { // int mbox_call(unsigned char ch, unsigned int *mbox) + unsigned char ch = tf->x0; + unsigned int *mbox = (unsigned int *)tf->x1; + tf->x0 = sys_mbox_call(ch, mbox); + break; + } + case 7: { // void kill(int pid) + unsigned int pid = tf->x0; + kill_thread_by_pid(pid); + break; + } + case 8: { // signal(int SIGNAL, void (*handler)()) + unsigned int SIGNAL = tf->x0; + void (*handler)() = (void (*)())tf->x1; + signal(SIGNAL, handler); + break; + } + case 9: { // (signal_)kill(int pid, int SIGNAL) + unsigned int pid = tf->x0; + unsigned int SIGNAL = tf->x1; + signal_kill(pid, SIGNAL); + break; + } + case 11: { // int open(const char *pathname, int flags) + const char *pathname = (char *)tf->x0; + int flags = tf->x1; + tf->x0 = sys_open(pathname, flags); + break; + } + case 12: { // int close(int fd) + int fd = tf->x0; + tf->x0 = sys_close(fd); + break; + } + case 13: { // int write(int fd, void *buf, size_t count) + int fd = tf->x0; + void *buf = (void *)tf->x1; + size_t count = tf->x2; + tf->x0 = sys_write(fd, buf, count); + break; + } + case 14: { // int read(int fd, void *buf, size_t count) + int fd = tf->x0; + void *buf = (void *)tf->x1; + size_t count = tf->x2; + tf->x0 = sys_read(fd, buf, count); + break; + } + case 15: { // int mkdir(const char *pathname, unsigned mode) + const char *pathname = (char *)tf->x0; + unsigned mode = tf->x1; + tf->x0 = sys_mkdir(pathname, mode); + break; + } + case 16: { // int mount(const char *src, const char *target, + // const char *filesystem, unsigned long flags, + // const void *data) + // const char *src = (char *)tf->x0; + const char *target = (char *)tf->x1; + const char *fs = (char *)tf->x2; + tf->x0 = sys_mount(target, fs); //, flags, data); + break; + } + case 17: { // int chdir(const char *path) + const char *path = (char *)tf->x0; + tf->x0 = sys_chdir(path); + break; + } + case 18: { // long lseek(int fd, off_t offset, int whence) + int fd = tf->x0; + long offset = tf->x1; + int whence = tf->x2; + tf->x0 = sys_lseek64(fd, offset, whence); + break; + } + case 19: { // int ioctl(int fd, unsigned long request, ...) + int fd = tf->x0; + unsigned long request = tf->x1; + void *info = (void *)tf->x2; + tf->x0 = sys_ioctl(fd, request, info); + if (request == GET_INFO) { + uart_log(INFO, "GET_INFO lfb = "); + uart_hex(lfb); + uart_putc(NEWLINE); + } + break; + } + case 139: { + sys_sigreturn(tf); + break; + } + default: + uart_log(ERR, "Invalid system call "); + uart_dec(syscall_num); + uart_putc(NEWLINE); + } +} + +void invalid_entry(uint64_t elr, uint64_t esr, uint64_t spsr, + uint64_t ttbr0_el1, uint64_t ttbr1_el1, uint64_t far_el1) { + uart_log(ERR, "The exception handler is not implemented\n"); + print_registers(elr, esr, spsr, ttbr0_el1, ttbr1_el1, far_el1); + + if ((esr & 0xFC000000) == 0x94000000) { + uint32_t svc_number = esr & 0xFFFF; + uart_log(ERR, "SVC Call Exception. SVC Number: "); + uart_dec(svc_number); + uart_putc(NEWLINE); + } else { + uart_log(ERR, "Unknown Exception\n"); + } + while (1) + ; +} \ No newline at end of file diff --git a/lab7/src/uart.c b/lab7/src/uart.c new file mode 100644 index 000000000..2924e0d33 --- /dev/null +++ b/lab7/src/uart.c @@ -0,0 +1,261 @@ +#include "uart.h" + +#include + +#include "irq.h" +#include "mem.h" + +static int uart_read_idx = 0; +static int uart_write_idx = 0; +static char uart_read_buffer[UART_BUF_SIZE]; +static char uart_write_buffer[UART_BUF_SIZE]; + +void init_uart() { + // Configure GPIO pins + register unsigned int r = *GPFSEL1; + r &= ~((7 << 12) | (7 << 15)); + r |= (2 << 12) | (2 << 15); + *GPFSEL1 = r; + + // Disable GPIO pull up/down + *GPPUD = 0; + for (int i = 0; i < 150; i++) + ; + *GPPUDCLK0 = (1 << 14) | (1 << 15); + for (int i = 0; i < 150; i++) + ; + *GPPUD = 0; + *GPPUDCLK0 = 0; + + // Initialize mini UART + *AUX_ENABLE |= 1; // Enable mini UART + *AUX_MU_CNTL = 0; // Disable Tx and Rx during setup + *AUX_MU_IER = 0; // Disable interrupt + *AUX_MU_LCR = 3; // Set data size to 8 bits + *AUX_MU_MCR = 0; // Disable auto flow control + *AUX_MU_BAUD = 270; // Set baud rate to 115200 + *AUX_MU_IIR = 6; // No FIFO + *AUX_MU_CNTL = 3; // Enable Tx and Rx + + // Enable AUX interrupts + *ENABLE_IRQS_1 |= 1 << 29; +} + +char uart_getc() { + // Check the data ready field on bit 0 of AUX_MU_LSR_REG + while (!(*AUX_MU_LSR & 0x01)) + ; + char c = (char)(*AUX_MU_IO); // Read from AUX_MU_IO_REG + return c == CARRIAGE_RETURN ? NEWLINE : c; +} + +void uart_putc(char c) { + // Add CARRIAGE_RETURN before NEWLINE + if (c == NEWLINE) + uart_putc(CARRIAGE_RETURN); + + // Check the transmitter empty field on bit 5 of AUX_MU_LSR_REG + while (!(*AUX_MU_LSR & 0x20)) + ; + *AUX_MU_IO = c; // Write to AUX_MU_IO_REG +} + +void uart_puts(const char *s) { + while (*s) { + uart_putc(*s++); + } +} + +void uart_clear() { uart_puts("\033[2J\033[H"); } + +void uart_hex(unsigned long h) { + uart_puts("0x"); + unsigned long n; + int length = h > 0xFFFFFFFF ? 60 : 28; + for (int c = length; c >= 0; c -= 4) { + n = (h >> c) & 0xF; + n += n > 9 ? 0x37 : '0'; + uart_putc(n); + if (c / 4 % 8 == 0 && c > 0) + uart_putc(' '); + } +} + +void uart_addr_range(uintptr_t start, uintptr_t end) { + uart_hex(start); + uart_puts("-"); + uart_hex(end - 1); +} + +void uart_simple_hex(unsigned int h) { + uart_puts("0x"); + if (h == 0) { + uart_putc('0'); + return; + } + char buf[10]; + int i = 0; + unsigned int n; + while (h > 0) { + n = h % 16; + buf[i++] = n + (n > 9 ? 0x37 : '0'); + h /= 16; + } + for (int j = i - 1; j >= 0; j--) { + uart_putc(buf[j]); + } +} + +void uart_dec(unsigned int d) { + if (d == 0) { + uart_putc('0'); + return; + } + char buf[12]; + int i = 0; + while (d > 0) { + buf[i++] = d % 10 + '0'; + d /= 10; + } + for (int j = i - 1; j >= 0; j--) { + uart_putc(buf[j]); + } +} + +void uart_log(int type, const char *msg) { + switch (type) { + case INFO: + uart_puts("[INFO] "); + break; + case TEST: + uart_puts("[TEST] "); + break; + case BUDD: + uart_puts("[BUDD] "); + break; + case CACH: + uart_puts("[CACH] "); + break; + case VFS: + uart_puts("[VFS ] "); + break; + case MMAP: + uart_puts("[MMAP] "); + break; + case SCALL: + uart_puts("[SYSC] "); + break; + case WARN: + uart_puts("[WARN] "); + break; + case DEBUG: + uart_puts("[DBUG] "); + break; + case ERR: + uart_puts("[ERR!] "); + break; + default: + uart_puts("[LOG?] "); + break; + } + uart_puts(msg); +} + +void enable_uart_tx_interrupt() { *AUX_MU_IER |= 0x02; } + +void disable_uart_tx_interrupt() { *AUX_MU_IER &= 0x01; } + +void enable_uart_rx_interrupt() { *AUX_MU_IER |= 0x01; } + +void disable_uart_rx_interrupt() { *AUX_MU_IER &= 0x2; } + +void uart_tx_irq_handler() { + disable_uart_tx_interrupt(); + // Buffer is not empty -> send characters + if (uart_write_idx < UART_BUF_SIZE && + uart_write_buffer[uart_write_idx] != 0) { + while (!(*AUX_MU_LSR & 0x20)) + ; + *AUX_MU_IO = uart_write_buffer[uart_write_idx++]; + enable_uart_tx_interrupt(); + } +} + +void uart_rx_irq_handler() { + while (!(*AUX_MU_LSR & 0x01)) + ; + char c = (char)(*AUX_MU_IO); + uart_read_buffer[uart_read_idx++] = (c == CARRIAGE_RETURN) ? NEWLINE : c; + if (uart_read_idx >= UART_BUF_SIZE) + uart_read_idx = 0; + enable_uart_rx_interrupt(); +} + +void uart_async_read(char *buf, int len) { + disable_uart_rx_interrupt(); + for (int i = 0; i < uart_read_idx && i < len; i++) + buf[i] = uart_read_buffer[i]; + buf[uart_read_idx] = 0; + uart_read_idx = 0; +} + +void uart_async_write(const char *s) { + // Copy string to the write buffer + int len = 0; + while (*s != '\0') { + if (len >= UART_BUF_SIZE) { + uart_log(ERR, "Exceed the UART buffer size\n"); + return; + } + if (*s == NEWLINE) { + // Insert CARRIAGE_RETURN before NEWLINE + uart_write_buffer[len++] = CARRIAGE_RETURN; + uart_write_buffer[len++] = NEWLINE; + } else + uart_write_buffer[len++] = *s; + s++; + } + uart_write_buffer[len] = '\0'; + uart_write_idx = 0; // Reset the buffer index + enable_uart_tx_interrupt(); +} + +file_operations dev_file_operations = { + dev_uart_write, // + dev_uart_read, // + dev_uart_open, // + dev_uart_close, // + (void *)not_supported, // lseek64 + (void *)not_supported // getsize +}; + +file_operations *init_dev_uart() { return &dev_file_operations; } + +int dev_uart_write(file *file, const void *buf, size_t len) { + // uart_log(DEBUG, "dev_uart_write\n"); + const char *char_buf = buf; + for (int i = 0; i < len; i++) + uart_putc(char_buf[i]); + return len; +} + +int dev_uart_read(file *file, void *buf, size_t len) { + // uart_log(DEBUG, "dev_uart_read\n"); + char *char_buf = buf; + for (int i = 0; i < len; i++) + char_buf[i] = uart_getc(); + return len; +} + +int dev_uart_open(vnode *file_node, file *target) { + // uart_log(DEBUG, "dev_uart_open\n"); + target->vnode = file_node; + target->f_ops = file_node->f_ops; //&dev_file_operations; + return 0; +} + +int dev_uart_close(file *file) { + // uart_log(DEBUG, "dev_uart_close\n"); + kfree(file, SILENT); + return 0; +} diff --git a/lab7/src/utils.c b/lab7/src/utils.c new file mode 100644 index 000000000..dec35665b --- /dev/null +++ b/lab7/src/utils.c @@ -0,0 +1,71 @@ +#include "utils.h" + +int align4(int n) { return n + (4 - n % 4) % 4; } + +int atoi(const char *s) { + int result = 0; + int sign = 1; + int i = 0; + + // Skip leading spaces + while (s[i] == ' ') { + i++; + } + + // Handle positive and negative sign + if (s[i] == '-') { + sign = -1; + i++; + } else if (s[i] == '+') { + i++; + } + + // Convert string to integer + while (s[i] >= '0' && s[i] <= '9') { + result = result * 10 + (s[i] - '0'); + i++; + } + + return sign * result; +} + +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 memcmp(const void *m1, const void *m2, int n) { + const unsigned char *a = m1, *b = m2; + while (n-- > 0) { + if (*a != *b) return *a - *b; + a++; + b++; + } + return 0; +} + +/** + * @brief Copy `n` bytes from `src` to `dest`. + */ +void *memcpy(void *dest, const void *src, int n) { + char *d = dest; + const char *s = src; + while (n--) *d++ = *s++; + return dest; +} + +/** + * @brief Set `n` bytes of `s` to `c`. + */ +void *memset(void *s, int c, int n) { + unsigned char *p = s; + while (n--) *p++ = (unsigned char)c; + return s; +} \ No newline at end of file diff --git a/lab7/src/vfs.c b/lab7/src/vfs.c new file mode 100644 index 000000000..4895b3616 --- /dev/null +++ b/lab7/src/vfs.c @@ -0,0 +1,309 @@ +#include "vfs.h" + +#include "initramfs.h" +#include "mbox.h" +#include "mem.h" +#include "scheduler.h" +#include "str.h" +#include "tmpfs.h" +#include "uart.h" +#include "utils.h" +#include "vfs.h" +#include "virtm.h" + +extern file_operations dev_file_operations; + +mount *rootfs; +filesystem *reg_fs = 0; + +void init_vfs() { + register_filesystem("tmpfs", tmpfs_setup_mount); + rootfs = kmalloc(sizeof(mount), SILENT); + reg_fs->setup_mount(reg_fs, rootfs); + + vfs_mkdir("/initramfs"); + register_filesystem("initramfs", initramfs_setup_mount); + vfs_mount("/initramfs", "initramfs"); + + vfs_mkdir("/dev"); + vfs_mknod("/dev/uart")->f_ops = init_dev_uart(); + vfs_mknod("/dev/framebuffer")->f_ops = init_dev_framebuffer(); +} + +vnode *vfs_mknod(char *pathname) { + uart_log(VFS, "Making node: "); + uart_puts(pathname); + uart_putc(NEWLINE); + file *f = create_file(); + vfs_open(pathname, O_CREAT, f); + vnode *node = f->vnode; + vfs_close(f); + return node; +} + +filesystem *register_filesystem(char *name, void *setup_mount) { + // register the file system to the kernel. + // you can also initialize memory pool of the file system here. + uart_log(VFS, "Registering filesystem: "); + uart_puts(name); + uart_putc(NEWLINE); + filesystem *fs = kmalloc(sizeof(filesystem), VERBAL); + fs->name = kmalloc(strlen(name) + 1, SILENT); + strncpy(fs->name, name, strlen(name) + 1); + fs->setup_mount = setup_mount; + fs->next = reg_fs; + reg_fs = fs; + + filesystem *tmp = reg_fs; + while (tmp) { + tmp = tmp->next; + } + return fs; +} + +int vfs_open(const char *pathname, int flags, file *target) { + // 1. Lookup pathname + // 2. Create a new file handle for this vnode if found. + // 3. Create a new file if O_CREAT is specified in flags and vnode not found + // lookup error code shows if file exist or not or other error occurs + // 4. Return error code if fails + int path_len = strlen(pathname); + + vnode *node; + if (vfs_lookup(pathname, &node)) { // file not found + if (flags & O_CREAT) { + int last_slash_idx = 0; + for (int i = 0; i < strlen(pathname); i++) { + if (pathname[i] == '/') + last_slash_idx = i; + } + + char dirname[path_len + 1]; + strncpy(dirname, pathname, strlen(pathname) + 1); + dirname[last_slash_idx] = 0; + + if (vfs_lookup(dirname, &node)) { + uart_log(VFS, "Directory does not exist: "); + uart_puts(dirname); + uart_putc(NEWLINE); + // kfree(dirname, SILENT); + return -1; + } + node->v_ops->create(node, &node, pathname + last_slash_idx + 1); + } else { + uart_log(VFS, "File does not exist: "); + uart_puts(pathname); + uart_putc(NEWLINE); + return -1; + } + } + node = (vnode *)TO_VIRT(node); + node->f_ops->open(node, target); + target->flags = flags; + return 0; +} + +int vfs_close(file *file) { + // 1. release the file handle + // 2. Return error code if fails + uart_log(VFS, "vfs_close: "); + uart_dec(file->id); + uart_putc(NEWLINE); + return file->f_ops->close(file); +} + +int vfs_write(file *file, const void *buf, size_t len) { + // 1. write len byte from buf to the opened file. + // 2. return written size or error code if an error occurs. + if (file->id > 3) { + uart_log(VFS, "vfs_write: "); + uart_dec(file->id); + uart_putc(NEWLINE); + } + return file->f_ops->write(file, buf, len); +} + +int vfs_read(file *file, void *buf, size_t len) { + // 1. read min(len, readable size) byte to buf from the opened file. + // 2. block if nothing to read for FIFO type + // 2. return read size or error code if an error occurs. + if (file->id > 3) { + uart_log(VFS, "vfs_read: "); + uart_dec(file->id); + uart_putc(NEWLINE); + } + return file->f_ops->read(file, buf, len); +} + +int vfs_mkdir(const char *pathname) { + uart_log(VFS, "vfs_mkdir: "); + uart_puts(pathname); + uart_putc(NEWLINE); + + char dirname[strlen(pathname)]; + char newdirname[strlen(pathname)]; + dirname[0] = '\0'; + newdirname[0] = '\0'; + + int last_slash_idx = 0; + for (int i = 0; i < strlen(pathname); i++) { + if (pathname[i] == '/') + last_slash_idx = i; + } + memcpy(dirname, pathname, last_slash_idx); + strncpy(newdirname, pathname + last_slash_idx + 1, + strlen(pathname) - last_slash_idx); + + vnode *node; + if (vfs_lookup(dirname, &node) == 0) { + node->v_ops->mkdir(node, &node, newdirname); + return 0; + } + + uart_log(VFS, "Path not found: "); + uart_puts(dirname); + uart_putc(NEWLINE); + return -1; +} + +int vfs_mount(const char *target, const char *file_sys) { + filesystem *fs = reg_fs; + + uart_log(VFS, "Mounting filesystem: "); + uart_puts(file_sys); + uart_putc(NEWLINE); + + while (!fs || strcmp(fs->name, file_sys)) { + if (!fs) { + uart_log(VFS, "Filesystem not found\n"); + return -1; + } + fs = fs->next; + } + + uart_log(VFS, "Mounting directory: "); + uart_puts(target); + uart_putc(NEWLINE); + + vnode *dirnode; + if (vfs_lookup(target, &dirnode)) { + uart_log(VFS, "Directory not found\n"); + return -1; + } + + dirnode->mount = kmalloc(sizeof(mount), SILENT); + fs->setup_mount(fs, dirnode->mount); + return 0; +} + +int vfs_lookup(const char *pathname, vnode **target) { + if (strlen(pathname) == 0) { + *target = rootfs->root; + return 0; + } + + vnode *dirnode = rootfs->root; + char component_name[strlen(pathname) + 1]; + component_name[0] = '\0'; + int c_idx = 0; + for (int i = 1; i < strlen(pathname); i++) { + if (pathname[i] == '/') { + component_name[c_idx++] = '\0'; + if (dirnode->v_ops->lookup(dirnode, &dirnode, component_name)) { + return -1; + } + // if mount exists, change dirnode to the root of the mounted fs + while (dirnode->mount) + dirnode = dirnode->mount->root; + c_idx = 0; + } else + component_name[c_idx++] = pathname[i]; + } + component_name[c_idx++] = 0; + if (dirnode->v_ops->lookup(dirnode, &dirnode, component_name)) + return -1; + while (dirnode->mount) + dirnode = dirnode->mount->root; + *target = dirnode; + return 0; +} + +void vfs_exec(vnode *target, trap_frame *tf) { + thread_struct *thread = get_current(); + initramfs_inode *inode = target->internal; + void *program = kmalloc(inode->datasize, SILENT); + kfree(thread->start, VERBAL); + thread->start = program; + thread->size = inode->datasize; + uart_log(INFO, "Acquired program space: "); + uart_hex((uintptr_t)program); + uart_putc(NEWLINE); + memcpy(program, inode->data, inode->datasize); + + memset(thread->pgd, 0, PAGE_SIZE); + mapping_user_thread(thread, 0); + + while (thread->sig_busy) + ; // wait for signal handling + thread->sig_reg = 0; + memset(thread->sig_handlers, 0, sizeof(thread->sig_handlers)); + + // thread->context.sp = (uintptr_t)thread->stack + STACK_SIZE; + // thread->context.fp = (uintptr_t)thread->stack + STACK_SIZE; + // tf->elr_el1 = 0x0; + // tf->sp_el0 = USER_STACK + STACK_SIZE; + return; +} + +file *create_file() { + file *new_file = kmalloc(sizeof(file), SILENT); + new_file->vnode = 0; + new_file->f_ops = 0; + new_file->flags = 0; + new_file->f_pos = 0; + new_file->prev = 0; + new_file->next = 0; + return new_file; +} + +char *absolute_path(const char *path, const char *cwd) { + // relative path + char *tmp = kmalloc(strlen(cwd) + strlen(path) + 1, SILENT); + if (path[0] != '/') { + strncpy(tmp, cwd, strlen(cwd) + 1); + if (strcmp(cwd, "/")) { + strcat(tmp, "/\0"); + } + } + strcat(tmp, path); + char *ret = kmalloc(strlen(tmp), SILENT); + *ret = '\0'; + int idx = 0; + for (int i = 0; i < strlen(tmp); i++) { + if (tmp[i] == '/' && tmp[i + 1] == '.') { + if (tmp[i + 2] == '.') { + for (int j = idx; j >= 0; j--) { + if (ret[j] == '/') { + ret[j] = '\0'; + idx = j; + break; + } + } + i += 2; + continue; + } else { + i++; + continue; + } + } + ret[idx++] = tmp[i]; + } + ret[idx] = '\0'; + kfree(tmp, SILENT); + return ret; +} + +int not_supported() { + uart_log(WARN, "Not supported.\n"); + return -1; +} \ No newline at end of file diff --git a/lab7/src/virtm.c b/lab7/src/virtm.c new file mode 100644 index 000000000..c571443ef --- /dev/null +++ b/lab7/src/virtm.c @@ -0,0 +1,56 @@ +#include "virtm.h" + +#include "mem.h" +#include "start.h" +#include "uart.h" +#include "utils.h" + +extern unsigned long TOTAL_MEMORY; + +static void walk(uintptr_t pt, uintptr_t va, uintptr_t pa) { + uintptr_t *table = (uintptr_t *)pt; + for (int level = 0; level <= 3; level++) { + uintptr_t offset = (va >> (39 - 9 * level)) & 0x1FF; + if (level == 3) { + table[offset] = pa | PTE_NORMAL_ATTR; + return; + } + if (!table[offset]) { + uintptr_t *t = kmalloc(PAGE_SIZE, SILENT); + uart_log(INFO, "Acquired page table at "); + uart_hex((uintptr_t)t); + uart_puts(" for level "); + uart_dec(level); + uart_putc(NEWLINE); + memset(t, 0, PAGE_SIZE); + table[offset] = TO_PHYS((uintptr_t)t) | PD_TABLE; + } + table = (uintptr_t *)TO_VIRT((table[offset] & ~0xFFF)); + } +} + +void map_pages(uintptr_t pgd, uintptr_t va, uintptr_t size, uintptr_t pa) { + uart_log(MMAP, "Mapping virtual -> physical: \n"); + uart_putc(TAB); + uart_addr_range(va, va + size); + uart_puts(" -> "); + uart_addr_range(pa, pa + size); + uart_putc(NEWLINE); + for (int i = 0; i < size; i += PAGE_SIZE) + walk(pgd, va + i, pa + i); +} + +extern void *lfb; + +void mapping_user_thread(thread_struct *thread, int gpu_mem_size) { + // user code + map_pages((uintptr_t)thread->pgd, TO_VIRT(0x0), thread->size, + (uintptr_t)TO_PHYS(thread->start)); + // stack region + map_pages((uintptr_t)thread->pgd, TO_VIRT(USER_STACK), STACK_SIZE, + (uintptr_t)TO_PHYS(thread->user_stack)); + // GPU + if (gpu_mem_size > 0) + map_pages((uintptr_t)thread->pgd, (uintptr_t)TO_VIRT(lfb), + MMIO_BASE - TO_VIRT(lfb), (uintptr_t)lfb); +} \ No newline at end of file From 3b6d97f625ddc284efb12971477c133454a3fb04 Mon Sep 17 00:00:00 2001 From: "Jonathan Chang, Chun-yien" Date: Sat, 29 Jun 2024 10:02:30 +0800 Subject: [PATCH 7/7] rm red --- lab5/src/uart.c | 4 +--- lab6/src/uart.c | 4 +--- lab7/src/uart.c | 2 -- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/lab5/src/uart.c b/lab5/src/uart.c index f71d0bee7..e5f85f732 100644 --- a/lab5/src/uart.c +++ b/lab5/src/uart.c @@ -1,7 +1,5 @@ #include "uart.h" -#include - #include "irq.h" #include "mem.h" #include "str.h" @@ -190,4 +188,4 @@ void uart_async_write(const char *s) { uart_write_buffer[len] = '\0'; uart_write_idx = 0; // Reset the buffer index enable_uart_tx_interrupt(); -} \ No newline at end of file +} diff --git a/lab6/src/uart.c b/lab6/src/uart.c index e2c050fda..c2611837b 100644 --- a/lab6/src/uart.c +++ b/lab6/src/uart.c @@ -1,7 +1,5 @@ #include "uart.h" -#include - #include "irq.h" #include "mem.h" @@ -197,4 +195,4 @@ void uart_async_write(const char *s) { uart_write_buffer[len] = '\0'; uart_write_idx = 0; // Reset the buffer index enable_uart_tx_interrupt(); -} \ No newline at end of file +} diff --git a/lab7/src/uart.c b/lab7/src/uart.c index 2924e0d33..f4b6b6341 100644 --- a/lab7/src/uart.c +++ b/lab7/src/uart.c @@ -1,7 +1,5 @@ #include "uart.h" -#include - #include "irq.h" #include "mem.h"