From 3130b0c86851f57e3e2f1ddae9a8d6354149b88a Mon Sep 17 00:00:00 2001 From: PiPi Yang <145743019+PiPiYang@users.noreply.github.com> Date: Wed, 19 Jun 2024 16:48:41 +0800 Subject: [PATCH] lab6 --- lab6/initramfs.cpio | Bin 0 -> 248320 bytes lab6/kernel/Makefile | 57 +++++ lab6/kernel/include/cpio.h | 34 +++ lab6/kernel/include/current.h | 27 ++ lab6/kernel/include/dtb.h | 33 +++ lab6/kernel/include/gpio.h | 27 ++ lab6/kernel/include/irq.h | 94 +++++++ lab6/kernel/include/list.h | 46 ++++ lab6/kernel/include/malloc.h | 52 ++++ lab6/kernel/include/mbox.h | 39 +++ lab6/kernel/include/mmu.h | 68 +++++ lab6/kernel/include/registers.h | 11 + lab6/kernel/include/sched.h | 98 ++++++++ lab6/kernel/include/shell.h | 9 + lab6/kernel/include/signal.h | 17 ++ lab6/kernel/include/sprintf.h | 8 + lab6/kernel/include/stddef.h | 16 ++ lab6/kernel/include/string.h | 21 ++ lab6/kernel/include/syscall.h | 20 ++ lab6/kernel/include/system.h | 11 + lab6/kernel/include/task.h | 21 ++ lab6/kernel/include/timer.h | 48 ++++ lab6/kernel/include/uart.h | 52 ++++ lab6/kernel/include/uint.h | 26 ++ lab6/kernel/include/utils.h | 9 + lab6/kernel/kernel8.elf | Bin 0 -> 159672 bytes lab6/kernel/kernel8.img | Bin 0 -> 35704 bytes lab6/kernel/lib/boot.S | 103 ++++++++ lab6/kernel/lib/cpio.c | 217 ++++++++++++++++ lab6/kernel/lib/dtb.c | 142 +++++++++++ lab6/kernel/lib/evt.S | 208 ++++++++++++++++ lab6/kernel/lib/irq.c | 184 ++++++++++++++ lab6/kernel/lib/list.c | 64 +++++ lab6/kernel/lib/main.c | 43 ++++ lab6/kernel/lib/malloc.c | 423 ++++++++++++++++++++++++++++++++ lab6/kernel/lib/mbox.c | 27 ++ lab6/kernel/lib/mmu.c | 191 ++++++++++++++ lab6/kernel/lib/sched.S | 62 +++++ lab6/kernel/lib/sched.c | 197 +++++++++++++++ lab6/kernel/lib/shell.c | 162 ++++++++++++ lab6/kernel/lib/signal.c | 61 +++++ lab6/kernel/lib/sprintf.c | 160 ++++++++++++ lab6/kernel/lib/string.c | 161 ++++++++++++ lab6/kernel/lib/syscall.c | 253 +++++++++++++++++++ lab6/kernel/lib/system.c | 74 ++++++ lab6/kernel/lib/task.c | 75 ++++++ lab6/kernel/lib/timer.c | 268 ++++++++++++++++++++ lab6/kernel/lib/uart.c | 274 +++++++++++++++++++++ lab6/kernel/linker.ld | 25 ++ lab6/kernel/send.py | 25 ++ 50 files changed, 4243 insertions(+) create mode 100644 lab6/initramfs.cpio create mode 100644 lab6/kernel/Makefile create mode 100644 lab6/kernel/include/cpio.h create mode 100644 lab6/kernel/include/current.h create mode 100644 lab6/kernel/include/dtb.h create mode 100644 lab6/kernel/include/gpio.h create mode 100644 lab6/kernel/include/irq.h create mode 100644 lab6/kernel/include/list.h create mode 100644 lab6/kernel/include/malloc.h create mode 100644 lab6/kernel/include/mbox.h create mode 100644 lab6/kernel/include/mmu.h create mode 100644 lab6/kernel/include/registers.h create mode 100644 lab6/kernel/include/sched.h create mode 100644 lab6/kernel/include/shell.h create mode 100644 lab6/kernel/include/signal.h create mode 100644 lab6/kernel/include/sprintf.h create mode 100644 lab6/kernel/include/stddef.h create mode 100644 lab6/kernel/include/string.h create mode 100644 lab6/kernel/include/syscall.h create mode 100644 lab6/kernel/include/system.h create mode 100644 lab6/kernel/include/task.h create mode 100644 lab6/kernel/include/timer.h create mode 100644 lab6/kernel/include/uart.h create mode 100644 lab6/kernel/include/uint.h create mode 100644 lab6/kernel/include/utils.h create mode 100644 lab6/kernel/kernel8.elf create mode 100644 lab6/kernel/kernel8.img create mode 100644 lab6/kernel/lib/boot.S create mode 100644 lab6/kernel/lib/cpio.c create mode 100644 lab6/kernel/lib/dtb.c create mode 100644 lab6/kernel/lib/evt.S create mode 100644 lab6/kernel/lib/irq.c create mode 100644 lab6/kernel/lib/list.c create mode 100644 lab6/kernel/lib/main.c create mode 100644 lab6/kernel/lib/malloc.c create mode 100644 lab6/kernel/lib/mbox.c create mode 100644 lab6/kernel/lib/mmu.c create mode 100644 lab6/kernel/lib/sched.S create mode 100644 lab6/kernel/lib/sched.c create mode 100644 lab6/kernel/lib/shell.c create mode 100644 lab6/kernel/lib/signal.c create mode 100644 lab6/kernel/lib/sprintf.c create mode 100644 lab6/kernel/lib/string.c create mode 100644 lab6/kernel/lib/syscall.c create mode 100644 lab6/kernel/lib/system.c create mode 100644 lab6/kernel/lib/task.c create mode 100644 lab6/kernel/lib/timer.c create mode 100644 lab6/kernel/lib/uart.c create mode 100644 lab6/kernel/linker.ld create mode 100644 lab6/kernel/send.py diff --git a/lab6/initramfs.cpio b/lab6/initramfs.cpio new file mode 100644 index 0000000000000000000000000000000000000000..0ef5cf90b9c2f80f52deccb31c1177f6b922ae0e GIT binary patch literal 248320 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 literal 0 HcmV?d00001 diff --git a/lab6/kernel/Makefile b/lab6/kernel/Makefile new file mode 100644 index 000000000..07b0d0a5f --- /dev/null +++ b/lab6/kernel/Makefile @@ -0,0 +1,57 @@ +OS := $(shell uname) + +ifeq ($(OS), Linux) + ARMGNU = aarch64-linux-gnu + MACHINE = raspi3b +endif + +ifeq ($(OS), Darwin) + ARMGNU = aarch64-unknown-linux-gnu + MACHINE = raspi3b +endif + +CC = $(ARMGNU)-gcc +LK = $(ARMGNU)-ld +OBJCPY = $(ARMGNU)-objcopy +QEMU = qemu-system-aarch64 + +A_SRCS = $(wildcard lib/*.S) +C_SRCS = $(wildcard lib/*.c) +SRC_DIR = lib +OBJ_DIR = build +OBJS = $(A_SRCS:$(SRC_DIR)/%.S=$(OBJ_DIR)/%_s.o) $(C_SRCS:$(SRC_DIR)/%.c=$(OBJ_DIR)/%_c.o) +INCLUDE = include +CFLAGS = -ggdb -Wno-implicit -Wno-int-conversion -ffreestanding -nostdlib -nostartfiles -I$(INCLUDE) +CPIO_FILE = ../initramfs.cpio +DTB_FILE = ../bcm2710-rpi-3-b-plus.dtb + +IMAGE = kernel8 + +all: $(IMAGE).img + +$(IMAGE).img: $(IMAGE).elf + $(OBJCPY) -O binary $(IMAGE).elf $(IMAGE).img + +$(IMAGE).elf: $(OBJS) + $(LK) $(OBJS) -T linker.ld -o $(IMAGE).elf + +$(OBJ_DIR)/%_s.o: lib/%.S + @if [ ! -d "$(OBJ_DIR)" ]; then mkdir $(OBJ_DIR); fi + $(CC) -c $< $(CFLAGS) -o $@ + +$(OBJ_DIR)/%_c.o: lib/%.c + @if [ ! -d "$(OBJ_DIR)" ]; then mkdir $(OBJ_DIR); fi + $(CC) -c $< $(CFLAGS) -o $@ + +run: $(IMAGE).img + $(QEMU) -machine $(MACHINE) -kernel $(IMAGE).img -initrd $(CPIO_FILE) -dtb $(DTB_FILE) -display none -serial null -serial stdio + +run_display: $(IMAGE).img + $(QEMU) -machine $(MACHINE) -kernel $(IMAGE).img -initrd $(CPIO_FILE) -dtb $(DTB_FILE) -serial null -serial stdio + +dbg: $(IMAGE).img + $(QEMU) -machine $(MACHINE) -kernel $(IMAGE).img -initrd $(CPIO_FILE) -dtb $(DTB_FILE) -display none -serial null -serial stdio -s -S + +.PHONY: clean +clean: + rm -rf $(OBJ_DIR) *.img *.elf */*.elf \ No newline at end of file diff --git a/lab6/kernel/include/cpio.h b/lab6/kernel/include/cpio.h new file mode 100644 index 000000000..002692b92 --- /dev/null +++ b/lab6/kernel/include/cpio.h @@ -0,0 +1,34 @@ +#ifndef CPIO_H +#define CPIO_H + +#define CPIO_NEWC_HEADER_MAGIC "070701" // big endian + +struct cpio_newc_header +{ + 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]; +}; + +/* write pathname,data,next header into corresponding parameter*/ +int cpio_newc_parse_header(struct cpio_newc_header *this_header_pointer, + char **pathname, unsigned int *filesize, char **data, + struct cpio_newc_header **next_header_pointer); + +int ls(char* working_dir); +int cat(char* thefilepath); +int execfile(char *thefilepath); +char *get_file_start(char *thefilepath); +unsigned int get_file_size(char *thefilepath); +#endif \ No newline at end of file diff --git a/lab6/kernel/include/current.h b/lab6/kernel/include/current.h new file mode 100644 index 000000000..ab97d777b --- /dev/null +++ b/lab6/kernel/include/current.h @@ -0,0 +1,27 @@ +#ifndef CURRENT_H +#define CURRENT_H + +#include "sched.h" + +// tpidr_el1 : 64 bit system register +// get current thread data structure +// msb 2 bit : thread id +static inline thread_context_t *get_current_ctx(void) +{ + thread_context_t *cur; + __asm__ __volatile__( + "mrs %0, tpidr_el1\n\t" + : "=r"(cur)); + return cur; +} + +static inline void set_current_ctx(thread_context_t *thread_context) +{ + __asm__ __volatile__( + "msr tpidr_el1, %0\n\t" + ::"r"(thread_context)); +} + +#define current_ctx get_current_ctx() + +#endif \ No newline at end of file diff --git a/lab6/kernel/include/dtb.h b/lab6/kernel/include/dtb.h new file mode 100644 index 000000000..212a818d5 --- /dev/null +++ b/lab6/kernel/include/dtb.h @@ -0,0 +1,33 @@ +#ifndef DTB_H +#define DTB_H +#include "uint.h" + + +struct fdt_header { + unsigned int magic; + unsigned int totalsize; + unsigned int off_dt_struct; + unsigned int off_dt_strings; + unsigned int off_mem_rsvmap; + unsigned int version; + unsigned int last_comp_version; + unsigned int boot_cpuid_phys; + unsigned int size_dt_strings; + unsigned int size_dt_struct; +}; +extern char* cpio_start; +extern char* cpio_end; + +#define FDT_BEGIN_NODE 0x1 /* Start node: full name */ +#define FDT_END_NODE 0x2 /* End node */ +#define FDT_PROP 0x3 /* Property: name off, size, content */ +#define FDT_NOP 0x4 /* nop */ +#define FDT_END 0x9 +typedef void (*dtb_callback)(unsigned int node_type, char *name, void *value, unsigned int name_size); + +unsigned int endian_big2little(unsigned int x); +void fdt_traverse(dtb_callback callback); +void dtb_callback_show_tree(uint32_t node_type, char *name, void *value, uint32_t name_size); +void initramfs_callback(unsigned int node_type, char *name, void *value, unsigned int name_size); + +#endif \ No newline at end of file diff --git a/lab6/kernel/include/gpio.h b/lab6/kernel/include/gpio.h new file mode 100644 index 000000000..42e1cc5c1 --- /dev/null +++ b/lab6/kernel/include/gpio.h @@ -0,0 +1,27 @@ +#include + +#ifndef GPIO_H +#define GPIO_H + +#define MMIO_BASE PHYS_TO_VIRT(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 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)) + +#endif \ No newline at end of file diff --git a/lab6/kernel/include/irq.h b/lab6/kernel/include/irq.h new file mode 100644 index 000000000..2f5f400a3 --- /dev/null +++ b/lab6/kernel/include/irq.h @@ -0,0 +1,94 @@ +#ifndef IRQ_H +#define IRQ_H + + +#include "mmu.h" + +#define CORE0_INTERRUPT_SOURCE ((volatile unsigned int *)(PHYS_TO_VIRT(0x40000060))) + +#define PERIPHERAL_INTERRUPT_BASE PHYS_TO_VIRT(0x3F000000) +#define IRQ_BASIC_PENDING ((volatile unsigned int *)(PERIPHERAL_INTERRUPT_BASE + 0x0000B200)) + +#define INTERRUPT_SOURCE_TIMER (1 << 1) +#define INTERRUPT_SOURCE_CNTPNSIRQ 1<<1 +#define INTERRUPT_SOURCE_GPU (1 << 8) +#define IRQ_PENDING_1_AUX_INT (1 << 29) + +#define IRQ_PENDING_1 ((volatile unsigned int *)(PERIPHERAL_INTERRUPT_BASE + 0x0000B204)) +#define IRQ_PENDING_2 ((volatile unsigned int *)(PERIPHERAL_INTERRUPT_BASE + 0x0000B208)) + + +#define FIQ_CONTROL ((volatile unsigned int *)(PERIPHERAL_INTERRUPT_BASE + 0x0000B20C)) +#define ENABLE_IRQS_1 ((volatile unsigned int *)(PERIPHERAL_INTERRUPT_BASE + 0x0000B210)) +#define ENABLE_IRQS_2 ((volatile unsigned int *)(PERIPHERAL_INTERRUPT_BASE + 0x0000B214)) +#define ENABLE_BASIC_IRQS ((volatile unsigned int *)(PERIPHERAL_INTERRUPT_BASE + 0x0000B218)) +#define DISABLE_IRQS_1 ((volatile unsigned int *)(PERIPHERAL_INTERRUPT_BASE + 0x0000B21C)) +#define DISABLE_IRQS_2 ((volatile unsigned int *)(PERIPHERAL_INTERRUPT_BASE + 0x0000B220)) +#define DISABLE_BASIC_IRQS ((volatile unsigned int *)(PERIPHERAL_INTERRUPT_BASE + 0x0000B224)) + + + #define DATA_ABORT_LOWER 0b100100 + #define INS_ABORT_LOWER 0b100000 + + #define TF_LEVEL0 0b000100 + #define TF_LEVEL1 0b000101 + #define TF_LEVEL2 0b000110 + #define TF_LEVEL3 0b000111 + + + + +// trapframe register +typedef struct trapframe +{ + 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; + 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; + unsigned long spsr_el1; + unsigned long elr_el1; + unsigned long sp_el0; + +} trapframe_t; + +void enable_interrupt(); +void disable_interrupt(); +void irq_handler(); +void invalid_exception_handler(unsigned long long x0); +void cpacr_el1_off(); +void sync_el0_64_handler(trapframe_t *tpf, unsigned long x1); + +void highp(); +void lowp(); +void test_preemption(); + +void lock(); +void unlock(); +#endif \ No newline at end of file diff --git a/lab6/kernel/include/list.h b/lab6/kernel/include/list.h new file mode 100644 index 000000000..94abf2b9a --- /dev/null +++ b/lab6/kernel/include/list.h @@ -0,0 +1,46 @@ +#ifndef LIST_H +#define LIST_H + + + +typedef struct list_head { + struct list_head *next, *prev; +} list_head_t; + +extern list_head_t task_list; +extern list_head_t *timer_event_list; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } +#define LIST_HEAD(name) list_head_t name = LIST_HEAD_INIT(name) + +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) +#define list_for_each_prev(pos, head) \ + for (pos = (head)->prev; prefetch(pos->prev), pos != (head); pos = pos->prev) + +#define offsetof(TYPE, MEMBER) ((unsigned int) &((TYPE *)0)->MEMBER) + +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) + +#define list_entry(ptr,type,member) \ + container_of(ptr, type, member) + +void INIT_LIST_HEAD(list_head_t *head); +int list_empty(const list_head_t *head); +static void __list_add(list_head_t *new_lst, list_head_t *prev, list_head_t *next); +void list_add(list_head_t *new_lst, list_head_t *head); +void list_add_tail( list_head_t *new_lst, list_head_t *head); +void list_insert(list_head_t *new_lst, list_head_t *prev, list_head_t *next); +static void __list_del(list_head_t * prev, list_head_t * next); +void list_del(list_head_t * entry); +int list_size(const list_head_t *head); + +static inline int list_is_head(const struct list_head *list, const struct list_head *head) +{ + return list == head; +} + + +#endif \ No newline at end of file diff --git a/lab6/kernel/include/malloc.h b/lab6/kernel/include/malloc.h new file mode 100644 index 000000000..5f1d9359f --- /dev/null +++ b/lab6/kernel/include/malloc.h @@ -0,0 +1,52 @@ +#ifndef MALLOC_H +#define MALLOC_H + +#include "list.h" +#include "uart.h" +#include "irq.h" +#include "dtb.h" + +#define MAXORDER 6 +#define MAXCACHEORDER 4 // 32, 64, 128, 256, 512 (for every 32bytes) + +// simple_malloc +void *smalloc(unsigned int size); + +#define BUDDYSYSTEM_START PHYS_TO_VIRT(0x0) //0x10000000L +#define BUDDYSYSTEM_PAGE_COUNT 0x3C000 +//buddy system (for >= 4K pages) +void *allocpage(unsigned int size); +void freepage(void *ptr); + +//Basic Exercise 2 - Dynamic Memory Allocator - 30% +//For (< 4K) +//small memory allocation +//store listhead in cache first 16 bytes +void *alloccache(unsigned int size); +void freecache(void *ptr); +void page2caches(int order); + +void *malloc(unsigned int size); +void free(void *ptr); + +typedef struct frame +{ + list_head_t listhead; + int val; // val is order + int isused; + int cacheorder; // -1 means isn't used for cache + unsigned int idx; +} frame_t; + +void init_allocator(); +frame_t *release_redundant(frame_t *frame); +frame_t *get_buddy(frame_t *frame); +frame_t *coalesce(frame_t* f); +void dump_freelist_info(); +void dump_cachelist_info(); +void memory_reserve(unsigned long long start, unsigned long long end); +void alloctest(); +void *memcpy(void *dest, const void *src, unsigned long long n); +#endif + + diff --git a/lab6/kernel/include/mbox.h b/lab6/kernel/include/mbox.h new file mode 100644 index 000000000..8e4dc9772 --- /dev/null +++ b/lab6/kernel/include/mbox.h @@ -0,0 +1,39 @@ +#ifndef MAILBOX_H +#define MAILBOX_H +#include "gpio.h" + +extern volatile unsigned int mbox[72]; + +#define MBOX_REQUEST 0 + +/* channels */ +#define MBOX_CH_POWER 0 +#define MBOX_CH_FB 1 +#define MBOX_CH_VUART 2 +#define MBOX_CH_VCHIQ 3 +#define MBOX_CH_LEDS 4 +#define MBOX_CH_BTNS 5 +#define MBOX_CH_TOUCH 6 +#define MBOX_CH_COUNT 7 +#define MBOX_CH_PROP 8 + +#define VIDEOCORE_MBOX (MMIO_BASE+0x0000B880) +#define MBOX_READ ((volatile unsigned int*)(VIDEOCORE_MBOX+0x0)) +#define MBOX_POLL ((volatile unsigned int*)(VIDEOCORE_MBOX+0x10)) +#define MBOX_SENDER ((volatile unsigned int*)(VIDEOCORE_MBOX+0x14)) +#define MBOX_STATUS ((volatile unsigned int*)(VIDEOCORE_MBOX+0x18)) +#define MBOX_CONFIG ((volatile unsigned int*)(VIDEOCORE_MBOX+0x1C)) +#define MBOX_WRITE ((volatile unsigned int*)(VIDEOCORE_MBOX+0x20)) +#define MBOX_RESPONSE 0x80000000 // mbox[1] = 0x80000000 -> request successful +#define MBOX_FULL 0x80000000 +#define MBOX_EMPTY 0x40000000 + +#define GET_BOARD_REVISION 0x10002 +#define MBOX_TAG_GETSERIAL 0x10004 +#define GET_ARM_MEMORY 0x10005 +#define MBOX_TAG_LAST 0 + +int mbox_call(unsigned char ch); + + +#endif \ No newline at end of file diff --git a/lab6/kernel/include/mmu.h b/lab6/kernel/include/mmu.h new file mode 100644 index 000000000..c465f3381 --- /dev/null +++ b/lab6/kernel/include/mmu.h @@ -0,0 +1,68 @@ +#ifndef MMU_H +#define MMU_H + +#include "stddef.h" +// user address translation [0 : 5] +// kernel address translation [16 : 21] +#define TCR_CONFIG_REGION_48bit (((64 - 48) << 0) | ((64 - 48) << 16)) +#define TCR_CONFIG_4KB ((0b00 << 14) | (0b10 << 30)) +// user space 0b00 [14 : 15] +// kernel space 0b10 [30 : 31] +#define TCR_CONFIG_DEFAULT (TCR_CONFIG_REGION_48bit | TCR_CONFIG_4KB) + +#define MAIR_DEVICE_nGnRnE 0b00000000 +#define MAIR_NORMAL_NOCACHE 0b01000100 +#define MAIR_IDX_DEVICE_nGnRnE 0 +#define MAIR_IDX_NORMAL_NOCACHE 1 + +// next level is block/page/invalid +#define PD_TABLE 0b11L +#define PD_BLOCK 0b01L +#define PD_UNX (1L << 54) +#define PD_KNX (1L << 53) +// access flag (page fault generated if not set) +#define PD_ACCESS (1L << 10) +// only kernel access +#define PD_UK_ACCESS (1L << 6) +// 0 for read-write 1 for read-only +#define PD_RDONLY (1L << 7) +#define BOOT_PGD_ATTR PD_TABLE +#define BOOT_PUD_ATTR (PD_ACCESS | (MAIR_IDX_DEVICE_nGnRnE << 2) | PD_BLOCK) + +// linear mapping to simplify +#define PHYS_TO_VIRT(x) (x + 0xffff000000000000) +#define VIRT_TO_PHYS(x) (x - 0xffff000000000000) +#define ENTRY_ADDR_MASK 0x0000fffffffff000L + +#ifndef __ASSEMBLER__ + +//#define kernel_pgd_addr 0x1000 +//#define kernel_pud_addr 0x2000 + +#define PGD 0x1000 +#define PUD 0x2000 + +#include "sched.h" + +void *set_2M_kernel_mmu(void *x0); +void map_one_page(size_t *pgd_p, size_t va, size_t pa, size_t flag); +void add_vma(thread_t *t, size_t va, size_t size, size_t pa, size_t rwx, int is_alloced); +void free_page_tables(size_t *page_table, int level); +void handle_abort(esr_el1_t* esr_el1); +void seg_fault(); +void map_one_page_rwx(size_t *pgd_p, size_t va, size_t pa, size_t rwxflag); + +typedef struct vm_area_struct +{ + list_head_t listhead; + unsigned long virt_addr; + unsigned long phys_addr; + unsigned long area_size; + unsigned long rwx; // 1, 2, 4 + int is_alloced; // malloc or not + +} vm_area_struct_t; + +#endif //__ASSEMBLER__ + +#endif \ No newline at end of file diff --git a/lab6/kernel/include/registers.h b/lab6/kernel/include/registers.h new file mode 100644 index 000000000..4e6634222 --- /dev/null +++ b/lab6/kernel/include/registers.h @@ -0,0 +1,11 @@ +#ifndef REGISTERS_H +#define REGISTERS_H + +// CPACR_EL1, Architectural Feature Access Control Register +#define CPACR_EL1_FPEN (0b11 << 20) +#define CPACR_EL1_VALUE (CPACR_EL1_FPEN) +#define IRQS1 ((volatile unsigned int *)(PHYS_TO_VIRT(0x3f00b210))) + + + +#endif \ No newline at end of file diff --git a/lab6/kernel/include/sched.h b/lab6/kernel/include/sched.h new file mode 100644 index 000000000..18b3f5db4 --- /dev/null +++ b/lab6/kernel/include/sched.h @@ -0,0 +1,98 @@ +#ifndef SCHED_H +#define SCHED_H + +#include "list.h" + +#define PIDMAX 32768 +#define USTACK_SIZE 0x10000 +#define KSTACK_SIZE 0x10000 +#define SIGNAL_MAX 64 + +typedef void (*signal_handler_t)(void); + +typedef enum thread_status { + NEW, + RUNNING, + DEAD, +} thread_status_t; + +// callee-saved reg +// other reg are already on stack +typedef struct thread_context +{ + 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; + void *ttbr0_el1; +} thread_context_t; + +typedef struct thread +{ + /* Run queue, wait queue*/ + list_head_t listhead; + + /* Context*/ + // el0 sp -> user_sp + // el1 sp -> kernel_sp + // context sp : sp of this thread + // context switch may use context sp so it stores kernel_sp + thread_context_t context; + + /* Data info */ + // function or program location + char *data; + unsigned int datasize; + + /* Thread status*/ + int pid; + thread_status_t status; + + /* Stack pointers*/ + // el0 call function + char *user_sp; + // el1 call function, load all | save all + char *kernel_sp; + + /* Signal */ + // registered signal call back function + signal_handler_t signal_handler[SIGNAL_MAX + 1]; + // registered signal number + int sigcount[SIGNAL_MAX + 1]; + signal_handler_t curr_signal_handler; + // prevent nested running signal handler + // set true when kernel checking signal + int signal_is_checking; + // before running handler + // save origin context + thread_context_t signal_saved_context; + + /* VMA */ + list_head_t vma_list; +} thread_t; + +// track curr_thread +extern thread_t *curr_thread; +extern list_head_t *run_queue; +extern list_head_t *wait_queue; +extern thread_t threads[PIDMAX + 1]; + +void schedule_timer(char *s); +void init_thread_sched(); +void idle(); +void schedule(); +void kill_zombies(); +void thread_exit(); +thread_t *thread_create(void *start, unsigned int filesize); +int exec_thread(char *data, unsigned int filesize); + +#endif \ No newline at end of file diff --git a/lab6/kernel/include/shell.h b/lab6/kernel/include/shell.h new file mode 100644 index 000000000..1e25f3cc0 --- /dev/null +++ b/lab6/kernel/include/shell.h @@ -0,0 +1,9 @@ +#ifndef SHELL_H +#define SHELL_H + + +extern char *dtb_base; +void cmd(char *s1); +void shell(); +void print_system_messages(); +#endif \ No newline at end of file diff --git a/lab6/kernel/include/signal.h b/lab6/kernel/include/signal.h new file mode 100644 index 000000000..47a3418c7 --- /dev/null +++ b/lab6/kernel/include/signal.h @@ -0,0 +1,17 @@ +#ifndef SIGNAL_H +#define SIGNAL_H + +#define SIGKILL_NO 9 + +#define USER_SIG_WRAPPER_VIRT_ADDR_ALIGNED 0xffffffff9000L + +#include "syscall.h" +#include "sched.h" +#include "malloc.h" + +void signal_default_handler(); +void check_signal(trapframe_t *tpf); +void run_signal(trapframe_t* tpf,int signal); +void signal_handler_wrapper(); + +#endif \ No newline at end of file diff --git a/lab6/kernel/include/sprintf.h b/lab6/kernel/include/sprintf.h new file mode 100644 index 000000000..042148da0 --- /dev/null +++ b/lab6/kernel/include/sprintf.h @@ -0,0 +1,8 @@ + +#ifndef SPRINTF_H +#define SPRINTF_H + +unsigned int sprintf(char *dst, char* fmt, ...); +unsigned int vsprintf(char *dst,char* fmt, __builtin_va_list args); + +#endif \ No newline at end of file diff --git a/lab6/kernel/include/stddef.h b/lab6/kernel/include/stddef.h new file mode 100644 index 000000000..d3f418ebb --- /dev/null +++ b/lab6/kernel/include/stddef.h @@ -0,0 +1,16 @@ +#ifndef STDDEF_H +#define STDDEF_H + +#define size_t unsigned long + +#ifndef __ASSEMBLER__ + +typedef struct{ + unsigned int iss : 25, // Instruction specific syndrome + il : 1, // Instruction length bit + ec : 6; // Exception class +} esr_el1_t; + +#endif //__ASSEMBLER__ + +#endif \ No newline at end of file diff --git a/lab6/kernel/include/string.h b/lab6/kernel/include/string.h new file mode 100644 index 000000000..a0cd764f8 --- /dev/null +++ b/lab6/kernel/include/string.h @@ -0,0 +1,21 @@ +#ifndef STRING_H +#define STRING_H +#include "uint.h" +#include "stddef.h" +unsigned long long strlen(const char *str); +char *strcpy(char *dest, const char *src); +void _strcpy(char *dest, char *src); +int strcmp(const char *s1, const char *s2); +int strncmp(const char *s1, const char *s2, int n); +char* strtok(char* str, const char* delimiters); + +int isalpha(char c); +int isdigit(int c); +int toupper(int c); +int isxdigit(int c); +long long atoi(char *str); +unsigned int parse_hex_str(char *arr, int size); +char* strchr (register const char *s, int c); +void *memset(void *s, int c, size_t n); + +#endif \ No newline at end of file diff --git a/lab6/kernel/include/syscall.h b/lab6/kernel/include/syscall.h new file mode 100644 index 000000000..6cd4c13c3 --- /dev/null +++ b/lab6/kernel/include/syscall.h @@ -0,0 +1,20 @@ +#ifndef SYSCALL_H +#define SYSCALL_H + +#include "stddef.h" +#include "irq.h" + +int getpid(trapframe_t *tpf); +size_t uartread(trapframe_t *tpf,char buf[], size_t size); +size_t uartwrite(trapframe_t *tpf,const char buf[], size_t size); +int exec(trapframe_t *tpf,const char *name, char *const argv[]); +int fork(trapframe_t *tpf); +void exit(trapframe_t *tpf,int status); +int syscall_mbox_call(trapframe_t *tpf, unsigned char ch, unsigned int *mbox); +void kill(trapframe_t *tpf,int pid); +void signal_register(int signal, void (*handler)()); +void signal_kill(int pid, int signal); +void sigreturn(trapframe_t *tpf); +void *sys_mmap(trapframe_t *tpf, void *addr, size_t len, int prot, int flags, int fd, int file_offset); + +#endif \ No newline at end of file diff --git a/lab6/kernel/include/system.h b/lab6/kernel/include/system.h new file mode 100644 index 000000000..48064121a --- /dev/null +++ b/lab6/kernel/include/system.h @@ -0,0 +1,11 @@ +#ifndef SYSTEM_H +#define SYSTEM_H + + +void set(long addr, unsigned int value); +void reset(int tick); +void cancel_reset(); +void reboot(); +int get_board_revision(unsigned int* board_revision); +int get_arm_memory_info(unsigned int* base_addr,unsigned int* size); +#endif \ No newline at end of file diff --git a/lab6/kernel/include/task.h b/lab6/kernel/include/task.h new file mode 100644 index 000000000..f289360bc --- /dev/null +++ b/lab6/kernel/include/task.h @@ -0,0 +1,21 @@ + +#ifndef TASK_H +#define TASK_H + +#include "list.h" + +typedef void (*task_callback_t)(void); + +typedef struct task { + list_head_t listhead; + int priority; + task_callback_t callback; +} task_t; + +extern list_head_t task_list; +void task_list_init() ; +void add_task(task_callback_t callback, int priority); +void pop_task(); + + +#endif \ No newline at end of file diff --git a/lab6/kernel/include/timer.h b/lab6/kernel/include/timer.h new file mode 100644 index 000000000..52ae473eb --- /dev/null +++ b/lab6/kernel/include/timer.h @@ -0,0 +1,48 @@ +#ifndef TIMER_H +#define TIMER_H + +#include "list.h" + +//https://github.com/Tekki/raspberrypi-documentation/blob/master/hardware/raspberrypi/bcm2836/QA7_rev3.4.pdf p13 +#define CORE0_TIMER_IRQ_CTRL PHYS_TO_VIRT(0x40000040) +#define STR(x) #x +#define XSTR(x) STR(x) + +typedef void (*timer_callback_t)(char *); + +typedef struct timer_event +{ + list_head_t listhead; + unsigned long long expire_time; + timer_callback_t callback; + char *args; +} timer_event_t; + + + +void core_timer_enable(); +void core_timer_disable(); +void core_timer_interrupt_enable(); +void core_timer_interrupt_disable(); +void cpu_timer_enable(); +void set_core_timer_interrupt(unsigned long long expire_time); +void set_core_timer_interrupt_by_tick(unsigned long long tick); +void set_core_timer_interrupt_first(); +void add_Node_timer(list_head_t *head, timer_event_t *entry); +void add_timer(timer_callback_t callback,void* arg, unsigned long long expire_time, int bytick); +void pop_timer() ; +int timer_list_size(); + +unsigned long long get_clock_tick(); +unsigned long long get_clock_freq(); +unsigned long long get_clock_time(); + +void two_second_alert(const char *str); +void core_timer_interrupt_disable_alternative(); +void core_timer_handler(); +void timer_event_callback(timer_event_t *timer_event); +void timer_list_init(); +unsigned long long get_tick_plus_s(unsigned long long second); +void print_timer(); + +#endif \ No newline at end of file diff --git a/lab6/kernel/include/uart.h b/lab6/kernel/include/uart.h new file mode 100644 index 000000000..42eec3b8f --- /dev/null +++ b/lab6/kernel/include/uart.h @@ -0,0 +1,52 @@ +#ifndef UART_H +#define UART_H + +#include "gpio.h" +#include "irq.h" +#include "sprintf.h" + +#define MAX_BUF_SIZE 0x1000 + +#define INT_SRC_0 +#define INT_AUX_RECV 0b00000100 +#define INT_AUX_TRAN 0b00000010 +#define INT_AUX_MASK 0b00000110 + +/* Auxilary mini UART registers */ +#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)) + +extern int echo; + +void init_idx(); +void uart_init(); +void uart_send(unsigned int c); +char uart_getc(); +void uart_puts(char *s); +void uart_hex(unsigned int d); +void uart_dec(int d); +int uart_printf(char *fmt, ...); + +void uart_async_putc(char c); +char uart_async_getc(); +int uart_async_printf(char *fmt, ...); + +void enable_mini_uart_interrupt(); +void enable_mini_uart_rx_interrupt(); +void disable_mini_uart_rx_interrupt(); +void enable_mini_uart_tx_interrupt(); +void disable_mini_uart_tx_interrupt(); + +void uart_tx_interrupt_handler(); +void uart_rx_interrupt_handler(); +#endif \ No newline at end of file diff --git a/lab6/kernel/include/uint.h b/lab6/kernel/include/uint.h new file mode 100644 index 000000000..4912f1a92 --- /dev/null +++ b/lab6/kernel/include/uint.h @@ -0,0 +1,26 @@ +#include "stddef.h" +#ifndef __int8_t_defined +# define __int8_t_defined +typedef signed char int8_t; +typedef short int int16_t; +typedef int int32_t; +# if __WORDSIZE == 64 +typedef long int int64_t; +# else +__extension__ +typedef long long int int64_t; +# endif +#endif + +/* Unsigned. */ +typedef unsigned char uint8_t; +typedef unsigned short int uint16_t; +#ifndef __uint32_t_defined +typedef unsigned int uint32_t; +# define __uint32_t_defined +#endif +#if __WORDSIZE == 64 +typedef unsigned long int uint64_t; +#else +typedef unsigned long long int uint64_t; +#endif \ No newline at end of file diff --git a/lab6/kernel/include/utils.h b/lab6/kernel/include/utils.h new file mode 100644 index 000000000..f363c397f --- /dev/null +++ b/lab6/kernel/include/utils.h @@ -0,0 +1,9 @@ +#ifndef UTILS_H +#define UTILS_H + +#define STR(x) #x +#define XSTR(x) STR(x) + + + +#endif \ No newline at end of file diff --git a/lab6/kernel/kernel8.elf b/lab6/kernel/kernel8.elf new file mode 100644 index 0000000000000000000000000000000000000000..a80bde8a29c182178762b56deeebcd9a2b24dc60 GIT binary patch literal 159672 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 z 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 UBMtmc^S;yI+iu=1AH~oA3pY;KEC2ui literal 0 HcmV?d00001 diff --git a/lab6/kernel/lib/boot.S b/lab6/kernel/lib/boot.S new file mode 100644 index 000000000..e95b57f22 --- /dev/null +++ b/lab6/kernel/lib/boot.S @@ -0,0 +1,103 @@ +#include "mmu.h" + +.section ".text.boot" + +.global _start + +_start: + // read cpu id, stop slave cores + // system register need use mrs + mrs x1, mpidr_el1 + and x1, x1, #3 + // switch to el1 before master + cbz x1, from_el2_to_el1 + // cpu id > 0, stop + +busyloop: + wfe + b busyloop + +from_el2_to_el1: + bl _from_el2_to_el1 + + +setup_kernel_vm: + // set paging configuration (up : 0xffffxxxxxxxxxxxx low : 0x0000xxxxxxxxxxxx) + ldr x4, = TCR_CONFIG_DEFAULT + + msr tcr_el1, x4 + + // set Used Memory Attributes + ldr x4, =((MAIR_DEVICE_nGnRnE << (MAIR_IDX_DEVICE_nGnRnE * 8)) | (MAIR_NORMAL_NOCACHE << (MAIR_IDX_NORMAL_NOCACHE * 8))) + msr mair_el1, x4 + + // set and enable MMU + mov x4, 0x1000 // PGD's page frame at 0x1000 + mov x1, 0x2000 // PUD's page frame at 0x2000 + + // PGD 12 ~ 2 ignore + // set up PGD_attribute, pd table , 0b11 + ldr x2, = BOOT_PGD_ATTR + // physical | address | attribute | + orr x2, x1, x2 // combine the physical address of next level page with attribute. + // make first entry in PGD points to PUD + str x2, [x4] + + // set stack pointer location + mov sp, 0x3c000000 // startup stack + bl set_2M_kernel_mmu + + // load PGD to the bottom (user-space) translation-based register. + msr ttbr0_el1, x4 + // load PGD to the upper (kernel-space) translation based register. + msr ttbr1_el1, x4 + + // MMU enable for EL1 & 0 stage 1 address translation + // cache remains disable + // bits 0 : M + mrs x2, sctlr_el1 + orr x2 , x2, 1 + msr sctlr_el1, x2 + + ldr x2, =master + br x2 + +master: + ldr x1, =(3 << 20) // close advanced SIMD exception + msr CPACR_EL1, x1 // printf builtin_va might use + adr x1, exception_vector_table // set exception_vector_table + msr vbar_el1, x1 + + // set top of stack at 0xffff00003c000000 (last usable memory) + movz x1, 0x0000 + movk x1, 0x3c00, lsl 16 + movk x1, 0xffff, lsl 48 + mov sp, x1 + + ldr x1, =__bss_start + ldr w2, =__bss_size + +bssinitloop: + cbz w2, kernel + // save 0 and plus 8 + str xzr, [x1], #8 + sub w2, w2, #1 + cbnz w2, bssinitloop + +kernel: + // bl save next command to lr register + bl main + b busyloop + +_from_el2_to_el1: + // enable RW + mov x1, (1 << 31) // EL1 uses aarch64 + // decide execution state of el1 and el0 + msr hcr_el2, x1 + // 3c daif disabled + // 5 exception level + mov x1, 0x3c5 + // EL1h (SPSel = 1) with interrupt disabled + msr spsr_el2, x1 + msr elr_el2, lr // eret location + eret // return to EL1 ,str in elr_el2 \ No newline at end of file diff --git a/lab6/kernel/lib/cpio.c b/lab6/kernel/lib/cpio.c new file mode 100644 index 000000000..a6ca192ff --- /dev/null +++ b/lab6/kernel/lib/cpio.c @@ -0,0 +1,217 @@ +#include "cpio.h" +#include "uart.h" +#include "string.h" +#include "utils.h" +#include "sched.h" + +char *cpio_start; +char *cpio_end; + +// ubuntu man page cpio +/* Parse one file system object */ +/* write pathname,data,next header into corresponding parameter */ +/* if no next header, next_header_pointer = 0 */ +/* return -1 if parse error*/ +int cpio_newc_parse_header(struct cpio_newc_header *this_header_pointer, char **pathname, unsigned int *filesize, char **data, struct cpio_newc_header **next_header_pointer) +{ + // Ensure magic header 070701 + // new ascii format + if (strncmp(this_header_pointer->c_magic, CPIO_NEWC_HEADER_MAGIC, sizeof(this_header_pointer->c_magic)) != 0) + return -1; + + // transfer big endian 8 byte hex string to unsinged int + // data size + *filesize = parse_hex_str(this_header_pointer->c_filesize, 8); + + // end of header is the pathname + // header | pathname str | data + *pathname = ((char *)this_header_pointer) + sizeof(struct cpio_newc_header); + + // get file data, file data is just after pathname + // header | pathname str | data + // check picture on hackmd note + unsigned int pathname_length = parse_hex_str(this_header_pointer->c_namesize, 8); + // get the offset to start of data + // | offset | data + unsigned int offset = pathname_length + sizeof(struct cpio_newc_header); + // pathname and data might be zero padding + // section % 4 ==0 + offset = offset % 4 == 0 ? offset : (offset + 4 - offset % 4); // padding + // header pointer + offset = start of data + // h| offset | data + *data = (char *)this_header_pointer + offset; + + // get next header pointer + if (*filesize == 0) + // hardlinked files handeld by setting filesize to zero + *next_header_pointer = (struct cpio_newc_header *)*data; + else + { + // data size + offset = *filesize; + // move pointer to the end of data + *next_header_pointer = (struct cpio_newc_header *)(*data + (offset % 4 == 0 ? offset : (offset + 4 - offset % 4))); + } + + // if filepath is TRAILER!!! means there is no more files. + // end of archieve + // empty filename : TRAILER!!! + if (strncmp(*pathname, "TRAILER!!!", sizeof("TRAILER!!!")) == 0) + *next_header_pointer = 0; + + return 0; +} + +int execfile(char *thefilepath) +{ + char* filepath; + char* filedata; + unsigned int filesize; + struct cpio_newc_header *header_pointer = (struct cpio_newc_header *)cpio_start; + + while(header_pointer!=0) + { + int error = cpio_newc_parse_header(header_pointer,&filepath,&filesize,&filedata,&header_pointer); + //if parse header error + if(error) + { + uart_printf("error"); + break; + } + + // traverse until find the filepath + // program start address is data + // header | pathname str | data + if(!strcmp(thefilepath,filepath)) + { + // + exec_thread(filedata, filesize); + break; + } + + //if this is TRAILER!!! (last of file) + if (header_pointer == 0) + { + uart_printf("execfile: %s: No such file or directory\n", thefilepath); + return -1; + } + } + return 0; +} + +int cat(char *thefilepath) +{ + char *filepath; + char *filedata; + unsigned int filesize; + // current header pointer, cpio start + struct cpio_newc_header *header_pointer = (struct cpio_newc_header *)cpio_start; + + while (header_pointer) + { + int error = cpio_newc_parse_header(header_pointer, &filepath, &filesize, &filedata, &header_pointer); + // if parse header error + if (error) + { + uart_printf("error\n"); + break; + } + // parse until filepath is same as cat input + // print the content of input file + if (!strcmp(thefilepath, filepath)) + { + for (unsigned int i = 0; i < filesize; i++) + uart_printf("%c", filedata[i]); + uart_printf("\n"); + break; + } + // end of cpio, cannot find input file + if (header_pointer == 0) + uart_printf("cat: %s: No such file or directory\r\n", thefilepath); + } + return 0; +} + +int ls(char *working_dir) +{ + char *filepath; + char *filedata; + unsigned int filesize; + // current pointer + struct cpio_newc_header *header_pointer = (struct cpio_newc_header *)cpio_start; + + // print every cpio pathname + while (header_pointer) + { + int error = cpio_newc_parse_header(header_pointer, &filepath, &filesize, &filedata, &header_pointer); + // if parse header error + if (error) + { + uart_printf("error\n"); + break; + } + + // if this is not TRAILER!!! (last of file) + if (header_pointer != 0) + uart_printf("%s\n", filepath); + } + return 0; +} + +char* get_file_start(char *thefilepath) +{ + char *filepath; + char *filedata; + unsigned int filesize; + struct cpio_newc_header *header_pointer = (struct cpio_newc_header *)cpio_start; + + while (header_pointer != 0) + { + int error = cpio_newc_parse_header(header_pointer, &filepath, &filesize, &filedata, &header_pointer); + //if parse header error + if (error) + { + uart_puts("error"); + break; + } + + if (strcmp(thefilepath, filepath) == 0) + { + return filedata; + } + + //if this is TRAILER!!! (last of file) + if (header_pointer == 0) + uart_printf("execfile: %s: No such file or directory\r\n", thefilepath); + } + return 0; +} + +unsigned int get_file_size(char *thefilepath) +{ + char *filepath; + char *filedata; + unsigned int filesize; + struct cpio_newc_header *header_pointer = (struct cpio_newc_header *)cpio_start; + + while (header_pointer != 0) + { + int error = cpio_newc_parse_header(header_pointer, &filepath, &filesize, &filedata, &header_pointer); + //if parse header error + if (error) + { + uart_puts("error"); + break; + } + + if (strcmp(thefilepath, filepath) == 0) + { + return filesize; + } + + //if this is TRAILER!!! (last of file) + if (header_pointer == 0) + uart_printf("execfile: %s: No such file or directory\r\n", thefilepath); + } + return 0; +} \ No newline at end of file diff --git a/lab6/kernel/lib/dtb.c b/lab6/kernel/lib/dtb.c new file mode 100644 index 000000000..695faed83 --- /dev/null +++ b/lab6/kernel/lib/dtb.c @@ -0,0 +1,142 @@ +#include "dtb.h" +#include "uart.h" +#include "string.h" +#include "uart.h" +#include "string.h" +#include "mmu.h" + +char *dtb_base; +extern char *cpio_start; +extern char *cpio_end; +// arm is little endian, we need to change big to little +unsigned int endian_big2little(unsigned int x) { + return (x >> 24) | ((x >> 8) & 0xff00) | ((x << 8) & 0xff0000) | (x << 24); +} +// a tree data structure which indicating what devices are on a computer system. +// only find out node of initramfs and get the address +void fdt_traverse(dtb_callback callback) +{ + struct fdt_header *header = (struct fdt_header *)dtb_base; + // fdt header magic 0xD00DFEED (big-endian) + if (endian_big2little(header->magic) != 0xD00DFEED) + { + uart_puts("fdt_traverse: wrong magic in fdt_traverse\n"); + uart_printf("expect: 0XD00DFEED, get: %x\n", endian_big2little(header->magic)); + + return; + } + + // length in bytes of structure block section of dtb + unsigned int struct_size = endian_big2little(header->size_dt_struct); + + // check hackmd notes about the picture of DTB structure + // header is address of fdt_header, so we need (char *) + // offset in bytes of the structure block from beginning of header + // to locate struct start + char *dt_struct_ptr = (char *)((char *)header + endian_big2little(header->off_dt_struct)); + // offset in bytes of strings block from beginning of header + // to locate string start + // fdt_prop use string_ptr + nameoff to get the pathname + char *dt_strings_ptr = (char *)((char *)header + endian_big2little(header->off_dt_strings)); + + // parse from struct begin to end + char *end = (char *)dt_struct_ptr + struct_size; + char *pointer = dt_struct_ptr; + + // according to lexical structure + while (pointer < end) + { + // lexical big-endian-32-bit integer + // all tokens shall be alligned on 32-bit boundary + unsigned int token_type = endian_big2little(*(unsigned int *)pointer); + pointer += 4; + + // lexical structure + switch (token_type) + { + // begin of node's representation + case FDT_BEGIN_NODE: + // move node's unit name + callback(token_type, pointer, 0, 0); + pointer += strlen(pointer); + // node name is followed by zeroed padding bytes + // allign + pointer += (4 - (unsigned long long)pointer % 4); + break; + + // end of node's representation + case FDT_END_NODE: + //callback(token_type, 0, 0, 0); + break; + + case FDT_PROP: + + // len | name offset | address + // uint32_t + // length of prop values in byte + unsigned int len = endian_big2little(*(unsigned int *)pointer); + pointer += 4; + + // nameoff save offset of string blocks + // strings_ptr + nameoff get the name + char *name = (char *)dt_strings_ptr + endian_big2little(*(unsigned int *)pointer); + pointer += 4; + + // check node is initrd-start/end and set cpio_start/end address + callback(token_type, name, pointer, len); + // address, byte string of length len + pointer += len; + // followed by zeroed padding bytes + if ((unsigned long long)pointer % 4 != 0) + pointer += 4 - (unsigned long long)pointer % 4; // alignment 4 byte + break; + // ignore NOP + case FDT_NOP: + //callback(token_type, 0, 0, 0); + break; + // marks end of structures block + case FDT_END: + //callback(token_type, 0, 0, 0); + break; + default: + uart_printf("error type:%x\n", token_type); + return; + } + } +} + +void dtb_callback_show_tree(uint32_t node_type, char *name, void *data, uint32_t name_size) +{ + static int level = 0; + if (node_type == FDT_BEGIN_NODE) + { + for (int i = 0; i < level; i++) + uart_async_printf(" "); + uart_async_printf("%s{\n", name); + level++; + } + else if (node_type == FDT_END_NODE) + { + level--; + for (int i = 0; i < level; i++) + uart_async_printf(" "); + uart_async_printf("}\n"); + } + else if (node_type == FDT_PROP) + { + for (int i = 0; i < level; i++) + uart_async_printf(" "); + uart_async_printf("%s\n", name); + } +} + + +void initramfs_callback(unsigned int node_type, char *name, void *value, unsigned int name_size) +{ + if (node_type == FDT_PROP && strcmp(name, "linux,initrd-start") == 0) + cpio_start = (void *)PHYS_TO_VIRT((unsigned long long)endian_big2little(*(uint32_t *)value)); + + if (node_type == FDT_PROP && strcmp(name, "linux,initrd-end") == 0) + cpio_end = (void *)PHYS_TO_VIRT((unsigned long long)endian_big2little(*(uint32_t *)value)); + +} \ No newline at end of file diff --git a/lab6/kernel/lib/evt.S b/lab6/kernel/lib/evt.S new file mode 100644 index 000000000..8128c7bd0 --- /dev/null +++ b/lab6/kernel/lib/evt.S @@ -0,0 +1,208 @@ +// 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] + + //using for nested interrupt + mrs x0, spsr_el1 + str x0, [sp, 16 * 15 + 8] + mrs x0, elr_el1 + str x0, [sp, 16 * 16] + mrs x0, sp_el0 + str x0, [sp, 16 * 16 + 8] + + ldp x0, x1, [sp ,16 * 0] // restore x0 +.endm + +// load general registers from stack +.macro load_all + 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] + + //using for nested interrupt + ldr x0, [sp, 16 * 15 + 8] + msr spsr_el1,x0 + ldr x0, [sp, 16 * 16] + msr elr_el1, x0 + ldr x0, [sp, 16 * 16 + 8] + msr sp_el0, x0 + + ldp x0, x1, [sp ,16 * 0] // restore x0 + + add sp, sp, 32 * 9 +.endm + +.macro ventry label + .align 7 + b \label +.endm + +.align 11 // vector table should be aligned to 0x800 +.global exception_vector_table +exception_vector_table: + + //Exception from the current EL while using SP_EL0 + ventry sync_el1t // Synchronous EL1t + ventry irq_el1t // IRQ EL1t + ventry fiq_el1t // FIQ EL1t + ventry error_el1t // Error EL1t + + //Exception from the current EL while using SP_ELx + ventry sync_el1h // Synchronous EL1h + ventry irq_el1h // IRQ EL1h + ventry fiq_el1h // FIQ EL1h + ventry error_el1h // Error EL1h + + //Exception from a lower EL and at least one lower EL is AArch64 + ventry sync_el0_64 // Synchronous 64-bit EL0 + ventry irq_el0_64 // IRQ 64-bit EL0 + ventry fiq_el0_64 // FIQ 64-bit EL0 + ventry error_el0_64 // Error 64-bit EL0 + + //Exception from a lower EL and at least all lower EL are AArch32 + ventry sync_el0_32 // Synchronous 32-bit EL0 + ventry irq_el0_32 // IRQ 32-bit EL0 + ventry fiq_el0_32 // FIQ 32-bit EL0 + ventry error_el0_32 // Error 32-bit EL0 + +// Exception handlers +sync_el1t: + save_all + mov x0,0 // for debug + bl invalid_exception_handler + load_all + eret +irq_el1t: + save_all + mov x0,1 // for debug + bl invalid_exception_handler + load_all + eret +fiq_el1t: + save_all + mov x0,2 // for debug + bl invalid_exception_handler + load_all + eret +error_el1t: + save_all + mov x0,3 // for debug + bl invalid_exception_handler + load_all + eret + + + +sync_el1h: + save_all + mov x0,4 // for debug + bl invalid_exception_handler + load_all + eret +irq_el1h: + save_all + mov x0,sp + bl irq_handler + load_all + eret +fiq_el1h: + save_all + mov x0,6 // for debug + bl invalid_exception_handler + load_all + eret +error_el1h: + save_all + mov x0,7 // for debug + bl invalid_exception_handler + load_all + eret + + + +sync_el0_64: + save_all + mov x0,sp + mrs x1, esr_el1 + bl sync_el0_64_handler + + mov x0,sp // check signal when going back to user mode (el0) + bl check_signal + load_all + eret +irq_el0_64: + save_all + bl irq_handler + + mov x0,sp // check signal when going back to user mode (el0) + bl check_signal + load_all + eret +fiq_el0_64: + save_all + mov x0,10 // for debug + bl invalid_exception_handler + load_all + eret +error_el0_64: + save_all + mov x0,11 // for debug + bl invalid_exception_handler + load_all + eret + + + +sync_el0_32: + save_all + mov x0,12 // for debug + bl invalid_exception_handler + load_all + eret +irq_el0_32: + save_all + mov x0,13 // for debug + bl invalid_exception_handler + load_all + eret +fiq_el0_32: + save_all + mov x0,14 // for debug + bl invalid_exception_handler + load_all + eret +error_el0_32: + save_all + mov x0,15 // for debug + bl invalid_exception_handler + load_all + eret \ No newline at end of file diff --git a/lab6/kernel/lib/irq.c b/lab6/kernel/lib/irq.c new file mode 100644 index 000000000..87f73d921 --- /dev/null +++ b/lab6/kernel/lib/irq.c @@ -0,0 +1,184 @@ + +#include "irq.h" +#include "uart.h" +#include "sched.h" +#include "timer.h" +#include "task.h" +#include "signal.h" +#include "syscall.h" +#include "mmu.h" +#include "stddef.h" + + +#define UART_IRQ_PRIORITY 4 +#define TIMER_IRQ_PRIORITY 3 + +// unmask specific interrupt +void enable_interrupt() { + //uart_puts("ei daif\n"); + asm volatile("msr daifclr, 0xf"); + //uart_puts("eiii daif\n"); +} +// mask specific interrupt +void disable_interrupt() { + asm volatile("msr daifset, 0xf"); +} + +void irq_handler() +{ + // core0_int_src : 0x40000060 + // from aux && from GPU0 -> uart exception + // determine GPU interrupt : bit 8 + // arm peripherals interrupt table + // bit 29 AUX interrupt + if (*IRQ_PENDING_1 & IRQ_PENDING_1_AUX_INT && *CORE0_INTERRUPT_SOURCE & INTERRUPT_SOURCE_GPU) + { + // bit[2:1] 01 tx + if (*AUX_MU_IIR & (0b01 << 1)) // TX can get data from tx buffer + { + disable_mini_uart_tx_interrupt(); + add_task(uart_tx_interrupt_handler, UART_IRQ_PRIORITY); + pop_task(); + } + // bit[2:1] 10 rx + else if (*AUX_MU_IIR & (0b10 << 1)) // kernel can get data from rx buffer + { + disable_mini_uart_rx_interrupt(); + add_task(uart_rx_interrupt_handler, UART_IRQ_PRIORITY); + pop_task(); + } + else + uart_printf("uart handler error\n"); + } + // check bit 1 to determine timer interrupt + else if (*CORE0_INTERRUPT_SOURCE & INTERRUPT_SOURCE_CNTPNSIRQ) + { + core_timer_interrupt_disable(); + //uart_printf("a1\n"); + add_task(core_timer_handler, TIMER_IRQ_PRIORITY); + //uart_printf("a2\n"); + pop_task(); + //uart_printf("a3\n"); + core_timer_enable(); + + // timer interrupt to be round robin + if (run_queue->next->next != run_queue) // runqueue size > 1 + schedule(); + } +} +void invalid_exception_handler(unsigned long long x0) { + uart_printf("invalid exception : 0x%x\n", x0); + uart_getc(); +} + +void cpacr_el1_off(){ + // bit 21 20 set 11 + asm volatile( + "mov x20, (3 << 20)\n\t" + "msr CPACR_EL1, x20\n\t" + ); +} + +void sync_el0_64_handler(trapframe_t *tpf, unsigned long x1) { + + esr_el1_t *esr = (esr_el1_t *)&x1; + // ec 0x20(instruction abort) 0x24(data abort) + // iss [5 : 0] 0b001LL (translation fault) + // ec : reason of exception + // iss : detailed reason of exception + if (esr->ec == DATA_ABORT_LOWER || esr->ec == INS_ABORT_LOWER) + { + handle_abort(esr); + return; + } + + + enable_interrupt(); + // get trapframe x8 + // which is system call number + unsigned long long syscall_no = tpf->x8; + + // by lab given spec + // arguments store in x0 x1 x2 ... + switch (syscall_no) + { + case 0: + getpid(tpf); + break; + case 1: + uartread(tpf, (char *)tpf->x0, tpf->x1); + break; + case 2: + uartwrite(tpf, (char *)tpf->x0, tpf->x1); + break; + case 3: + exec(tpf, (char *)tpf->x0, (char **)tpf->x1); + break; + case 4: + fork(tpf); + break; + case 5: + exit(tpf, tpf->x0); + break; + case 6: + syscall_mbox_call(tpf, (unsigned char)tpf->x0, (unsigned int *)tpf->x1); + break; + case 7: + kill(tpf, (int)tpf->x0); + break; + case 8: + signal_register(tpf->x0, (signal_handler_t)tpf->x1); + break; + case 9: + signal_kill(tpf->x0, tpf->x1); + break; + case 10: + sys_mmap(tpf, (void *)tpf->x0, tpf->x1, tpf->x2, tpf->x3, tpf->x4, tpf->x5); + break; + case 50: + // self-defined signal return + sigreturn(tpf); + break; + } +} + + +void highp() { + uart_async_printf("high prior start\n"); + uart_async_printf("high prior end\n"); + +} + +void lowp() { + uart_async_printf("low prior start\n"); + add_task(highp, 0); + uart_async_putc('\r'); // to trigger pop_task + for(int i = 0; i < 100000; ++i); + uart_async_printf("low prior end\n"); + for(int i = 0; i < 100000; ++i); +} + +void test_preemption() { + uart_async_printf("Starting test :\n"); + add_task(lowp, 9); + uart_async_putc('\r'); // to trigger pop_task +} + +static unsigned long long lock_count = 0; +void lock() +{ + disable_interrupt(); + lock_count++; +} + +void unlock() +{ + lock_count--; + if (lock_count<0) + { + uart_printf("lock error !!!\r\n"); + while(1); + } + if (lock_count == 0) + enable_interrupt(); +} \ No newline at end of file diff --git a/lab6/kernel/lib/list.c b/lab6/kernel/lib/list.c new file mode 100644 index 000000000..5acb8d1dd --- /dev/null +++ b/lab6/kernel/lib/list.c @@ -0,0 +1,64 @@ +#include "list.h" + +//list_head_t *list = (list_head_t*)smalloc(sizeof(list_head_t)); + + +void INIT_LIST_HEAD(list_head_t *head) +{ + head->next = head; + head->prev = head; +} + +int list_empty(const list_head_t *head) +{ + return head->next == head; +} + +static void __list_add(list_head_t *new_lst, list_head_t *prev, list_head_t *next) +{ + + next->prev = new_lst; + new_lst->next = next; + new_lst->prev = prev; + prev->next = new_lst; +} + +void list_insert(list_head_t *new_lst, list_head_t *prev, list_head_t *next) +{ + next->prev = new_lst; + new_lst->next = next; + new_lst->prev = prev; + prev->next = new_lst; +} + +void list_add(list_head_t *new_lst, list_head_t *head) +{ + __list_add(new_lst, head, head->next); +} + +void list_add_tail( list_head_t *new_lst, list_head_t *head) +{ + __list_add(new_lst, head->prev, head); +} + +static void __list_del(list_head_t * prev, list_head_t * next ) +{ + next->prev = prev; + prev->next = next; +} + +void list_del(list_head_t * entry) +{ + __list_del(entry->prev, entry->next); + +} + + +int list_size(const list_head_t *head) { + list_head_t *pos; + int i= 0; + list_for_each(pos, head) { + i++; + } + return i; +} diff --git a/lab6/kernel/lib/main.c b/lab6/kernel/lib/main.c new file mode 100644 index 000000000..fabef0695 --- /dev/null +++ b/lab6/kernel/lib/main.c @@ -0,0 +1,43 @@ +#include "dtb.h" +#include "uart.h" +#include "irq.h" +#include "cpio.h" +#include "list.h" +#include "timer.h" +#include "task.h" +#include "sched.h" +#include "mmu.h" +#include "mbox.h" + + +extern char *dtb_base; + + + +void main(char *arg) +{ + dtb_base = PHYS_TO_VIRT(arg); + // init list + init_idx(); + uart_init(); + task_list_init(); + fdt_traverse(initramfs_callback); + init_allocator(); + timer_list_init(); + + enable_interrupt(); + //enable_mini_uart_interrupt(); + + init_thread_sched(); + + core_timer_enable(); + + + + + uart_printf("Lab6 :\n"); + //fork_test(); + //kernel_main(); + execfile("vm.img"); + //shell(); +} \ No newline at end of file diff --git a/lab6/kernel/lib/malloc.c b/lab6/kernel/lib/malloc.c new file mode 100644 index 000000000..ee54d9487 --- /dev/null +++ b/lab6/kernel/lib/malloc.c @@ -0,0 +1,423 @@ +#include "malloc.h" +#include "cpio.h" +#include "mmu.h" +extern char _heap_start; +extern char _end; +static char *simple_top = &_heap_start; +static char *kernel_end = &_end; +extern char *dtb_base; + +// simple_malloc +void *smalloc(unsigned int size) +{ + char *r = simple_top + 0x10; + if (size < 0x18) + size = 0x18; // minimum size 0x20 //like ptmalloc + size = size + 0x7; + size = 0x10 + size - size % 0x10; + *(unsigned int *)(r - 0x8) = size; + simple_top += size; + return r; +} + +//buddy system allocator +//https://oscapstone.github.io/labs/lab4. +// use val(size) and isused with freelist is enough +//page size = 4K +/* For val (-2 ~ 6) +>=0 -> There is an allocable, contiguous memory that starts from the idx’th frame with size = 2**val * 4kb. +-1 -> allocated (deprecated) +-2 -> free, but it belongs to a larger contiguous memory block (deprecated) +*/ + +static frame_t* framearray; +static list_head_t freelist[MAXORDER + 1]; // 4K * (idx**ORDER) (for every 4K) (page) +static list_head_t cachelist[MAXCACHEORDER + 1]; // 32, 64, 128, 256, 512 (for every 32bytes) + +void init_allocator() +{ + // The usable memory region is from 0x00 to 0x3C000000, you can get this information from the memory node in devicetree. + // Advanced Exercise 3 - Startup Allocation - 20% + framearray = smalloc(BUDDYSYSTEM_PAGE_COUNT * sizeof(frame_t)); + + // init framearray + for (int i = 0; i < BUDDYSYSTEM_PAGE_COUNT; i++) + { + if (i % (1 << MAXORDER) == 0) + { + framearray[i].isused = 0; + framearray[i].val = 6; + } + } + + //init frame freelist + for (int i = 0; i <= MAXORDER; i++) + { + INIT_LIST_HEAD(&freelist[i]); + } + + for (int i = 0; i < BUDDYSYSTEM_PAGE_COUNT; i++) + { + //init listhead for each frame + INIT_LIST_HEAD(&framearray[i].listhead); + framearray[i].idx = i; + framearray[i].cacheorder = -1; + + //add init frame into freelist + if (i % (1 << MAXORDER) == 0) + { + list_add(&framearray[i].listhead, &freelist[MAXORDER]); + } + } + + //init cachelist + for (int i = 0; i <= MAXCACHEORDER; i++) + { + INIT_LIST_HEAD(&cachelist[i]); + } + + + /* should reserve these memory region + Spin tables for multicore boot (0x0000 - 0x1000) + Kernel image in the physical memory + Initramfs + Devicetree (Optional, if you have implement it) + Your simple allocator (startup allocator) + stack + */ + memory_reserve(PHYS_TO_VIRT(0x0000), PHYS_TO_VIRT(0x1000)); + memory_reserve(PHYS_TO_VIRT(0x1000), PHYS_TO_VIRT(0x5000)); // // PGD's page frame at 0x1000 // PUD's page frame at 0x2000 PMD 0x3000-0x5000 + memory_reserve(PHYS_TO_VIRT(0x80000), (unsigned long long)kernel_end); //kernel + memory_reserve((unsigned long long)&_heap_start, (unsigned long long)simple_top); //simple + memory_reserve((unsigned long long)cpio_start, (unsigned long long)cpio_end); + memory_reserve(PHYS_TO_VIRT(0x2c000000), PHYS_TO_VIRT(0x3c000000)); //0x2c000000L - 0x3c000000L (stack) +} + +//smallest 4K +void *allocpage(unsigned int size) +{ + // get real val size + //int allocsize; + int val; + for (int i = 0; i <= MAXORDER; i++) + { + + if (size <= (0x1000 << i)) + { + val = i; + break; + } + + if (i == MAXORDER) + { + uart_puts("Too large size for malloc!!!!\r\n"); + while (1); + return smalloc(size); + } + } + + // find the smallest larger frame in freelist + int target_list_val; + for (target_list_val = val; target_list_val <= MAXORDER; target_list_val++) + { + if (!list_empty(&freelist[target_list_val])) + break; + } + + if (target_list_val > MAXORDER) + { + uart_puts("malloc ERROR (all lists are empty?)!!!!\r\n"); + while (1); + return smalloc(size); + } + + //get the frame + frame_t *target_frame_ptr = (frame_t *)freelist[target_list_val].next; + list_del((struct list_head *)target_frame_ptr); + + // Release redundant memory block + for (int j = target_list_val; j > val; j--) + { + release_redundant(target_frame_ptr); + } + target_frame_ptr->isused = 1; +#ifdef DEBUG + uart_printf("allocpage ret : 0x%x, val : %d\r\n", BUDDYSYSTEM_START + (0x1000 * (target_frame_ptr->idx)), target_frame_ptr->val); +#endif + return (void *)BUDDYSYSTEM_START + (0x1000 * (target_frame_ptr->idx)); +} + +void freepage(void *ptr) +{ + frame_t *target_frame_ptr = &framearray[((unsigned long long)ptr - BUDDYSYSTEM_START) >> 12]; + +#ifdef DEBUG + uart_printf("freepage 0x%x, val = %d\r\n", ptr, target_frame_ptr->val); +#endif + + target_frame_ptr->isused = 0; + frame_t* temp; + while ((temp = coalesce(target_frame_ptr)) != (frame_t *)-1)target_frame_ptr = temp; + + list_add(&target_frame_ptr->listhead, &freelist[target_frame_ptr->val]); +} + +frame_t *release_redundant(frame_t *frame_ptr) +{ + frame_ptr->val -= 1; + frame_t *buddyptr = get_buddy(frame_ptr); + buddyptr->val = frame_ptr->val; + buddyptr->isused = 0; + list_add(&buddyptr->listhead, &freelist[buddyptr->val]); +#ifdef DEBUG + uart_printf("release_redundant idx = %d,%d\r\n", frame_ptr->idx, buddyptr->idx); +#endif + return frame_ptr; +} + +frame_t *get_buddy(frame_t *frame) +{ + return &framearray[frame->idx ^ (1 << frame->val)]; +} + +//return 0 -> success +//return -1 -> cannot coalesce +frame_t* coalesce(frame_t *frame_ptr) +{ + frame_t *buddy = get_buddy(frame_ptr); + + // MAXORDER + if (frame_ptr->val == MAXORDER) + return (frame_t*)-1; + + // val not the same (there is some chunks in the buddy used) + if (frame_ptr->val != buddy->val) + return (frame_t *)-1; + + //buddy is used + if (buddy->isused == 1) + return (frame_t *)-1; + + list_del((struct list_head *)buddy); + frame_ptr->val += 1; + buddy->val += 1; + +#ifdef DEBUG + uart_printf("coalesce idx = %d,%d\r\n", frame_ptr->idx, buddy->idx); +#endif + return buddy> 12]; + pageframe_ptr->cacheorder = order; + + // split page into a lot of caches and push them into cachelist + int cachesize = (32 << order); + for (int i = 0; i < 0x1000; i += cachesize) + { + list_head_t *c = (list_head_t *)(page + i); + list_add(c, &cachelist[order]); + } +} + +void freecache(void *ptr) +{ + list_head_t *c = (list_head_t *)ptr; + frame_t *pageframe_ptr = &framearray[((unsigned long long)ptr - BUDDYSYSTEM_START) >> 12]; + list_add(c, &cachelist[pageframe_ptr->cacheorder]); +#ifdef DEBUG + uart_printf("freecache 0x%x, order : %d\r\n", ptr, pageframe_ptr->cacheorder); +#endif +} +// malloc -> virtual -> 0xffff... +// because of linker script +void *malloc(unsigned int size) +{ + lock(); +#ifdef DEBUG + uart_printf("malloc size: %d\r\n", size); +#endif + //For page + if (size > (32 << MAXCACHEORDER)) + { + void *r = allocpage(size); +#ifdef DEBUG + dump_freelist_info(); + dump_cachelist_info(); +#endif + unlock(); + return r; + } + + void *r = alloccache(size); + +#ifdef DEBUG + dump_freelist_info(); + dump_cachelist_info(); + uart_printf("malloc ret 0x%x\r\n", r); +#endif + //For cache + unlock(); + return r; +} + +void free(void *ptr) +{ + lock(); +#ifdef DEBUG + uart_printf("free 0x%x\r\n", ptr); +#endif + //For page + if ((unsigned long long)ptr % 0x1000 == 0 && framearray[((unsigned long long)ptr - BUDDYSYSTEM_START) >> 12].cacheorder == -1) + { + freepage(ptr); +#ifdef DEBUG + dump_freelist_info(); + dump_cachelist_info(); +#endif + unlock(); + return; + } + + //For cache + freecache(ptr); +#ifdef DEBUG + dump_freelist_info(); + dump_cachelist_info(); +#endif + unlock(); +} + +void dump_freelist_info() +{ + for (int i = 0; i <= MAXORDER; i++) + uart_printf("freelist %d : %d\r\n", i, list_size(&freelist[i])); +} + +void dump_cachelist_info() +{ + for (int i = 0; i <= MAXCACHEORDER; i++) + uart_printf("cachelist %d : %d\r\n", i, list_size(&cachelist[i])); +} + +void memory_reserve(unsigned long long start, unsigned long long end) +{ + uart_printf("reserve -> start : 0x%x end : 0x%x\r\n", start, end); + start -= start % 0x1000; // floor (align 0x1000) + end = end % 0x1000 ? end + 0x1000 - (end % 0x1000) : end; // ceiling (align 0x1000) + + //delete page from freelist + for (int order = MAXORDER; order >= 0; order--) + { + list_head_t *pos; + list_for_each(pos, &freelist[order]) + { + unsigned long long pagestart = ((frame_t *)pos)->idx * 0x1000L + BUDDYSYSTEM_START; + unsigned long long pageend = pagestart + (0x1000L << order); + + if (start <= pagestart && end >= pageend) // if page all in reserved memory -> delete it from freelist + { + ((frame_t *)pos)->isused = 1; + list_del(pos); +#ifdef DEBUG + uart_printf("del order %d\r\n",order); + //dump_freelist_info(); +#endif + } + else if (start >= pageend || end <= pagestart) // no intersection + { + continue; + } + else // partial intersection (or reversed memory all in the page) + { + list_del(pos); + list_head_t *temppos = pos -> prev; + list_add(&release_redundant((frame_t *)pos)->listhead, &freelist[order - 1]); + pos = temppos; +#ifdef DEBUG + //dump_freelist_info(); +#endif + } + } + } +} + +void alloctest() +{ + uart_printf("alloc test\r\n"); + + //memory_reserve(0x1FFFAddb, 0x1FFFFdda); + + char *a = malloc(0x10); + char *b = malloc(0x100); + char *c = malloc(0x1000); + + free(a); + free(b); + free(c); + + a = malloc(32); + char *aa = malloc(50); + b = malloc(64); + char *bb = malloc(64); + c = malloc(128); + char *cc = malloc(129); + char *d = malloc(256); + char *dd = malloc(256); + char *e = malloc(512); + char *ee = malloc(999); + + free(a); + free(aa); + free(b); + free(bb); + free(c); + free(cc); + free(dd); + free(d); + free(e); + free(ee); +} + +void *memcpy(void *dest, const void *src, unsigned long long n) { + char *cdest = dest; + const char *csrc = src; + + while (n--) + { + *cdest++ = *csrc++; + } + + return dest; +} \ No newline at end of file diff --git a/lab6/kernel/lib/mbox.c b/lab6/kernel/lib/mbox.c new file mode 100644 index 000000000..03aa76daa --- /dev/null +++ b/lab6/kernel/lib/mbox.c @@ -0,0 +1,27 @@ +#include "mbox.h" +#include "uart.h" +/* mailbox message buffer */ +volatile unsigned int __attribute__((aligned(16))) mbox[72]; + +/** + * Make a mailbox call. Returns 0 on failure, non-zero on success + */ +int mbox_call(unsigned char ch) // Mailbox 0 define several channels, but we only use channel 8 (CPU->GPU) for communication. +{ + unsigned long r = (((unsigned long)((unsigned long)mbox) & ~0xF) | (ch & 0xF)); + /* wait until we can write to the mailbox */ + do{asm volatile("nop");}while(*MBOX_STATUS & MBOX_FULL); + /* write the address of our message to the mailbox with channel identifier */ + *MBOX_WRITE = r; + /* now wait for the response */ + while(1) { + /* is there a response? */ + do{asm volatile("nop");}while(*MBOX_STATUS & MBOX_EMPTY); + /* is it a response to our message? */ + if(r == PHYS_TO_VIRT(*MBOX_READ)) + /* is it a valid successful response? */ + return mbox[1]==MBOX_RESPONSE; + } + return 0; +} + diff --git a/lab6/kernel/lib/mmu.c b/lab6/kernel/lib/mmu.c new file mode 100644 index 000000000..906574738 --- /dev/null +++ b/lab6/kernel/lib/mmu.c @@ -0,0 +1,191 @@ +#include "mmu.h" +#include "malloc.h" +#include "sched.h" +#include "string.h" +#include "irq.h" +// PGD's page frame at 0x1000 +// PUD's page frame at 0x2000 +// PMD1's page frame at 0x3000 +// PMD2's page frame at 0x4000 +// two-level translation (1G) -> three-level translation (2MB) + +void *set_2M_kernel_mmu(void *x0) +{ + // setting up first PMD which maps to 0x0000000 ~ 0x3fffffff + + unsigned long *PMD = (unsigned long *)0x3000; + // 512 entries per page table + // 0x80000000 / 2MB = 1024 = 512 * 2, -> 2 PMD + for (int i = 0; i < 512; i++) + { + // 0x00000000 ~ 0x3F000000 normal RAM -> without cache + // normal RAM doesnt need PD_UKAccess or UNX or KNX + // gathering, reordering , speculative execution are possible + // if RAM set them, then el0 el1 cannot execute + // 0x3F000000 to 0x3FFFFFFF for GPU peripherals -> Device memory nGnRnE + // nG no gathering : memory access cannot merge + // nR no Reordering : strictly accroding to program order + // nE transaction ack must from end point + // every 2MB -> 3 level + if ((0x00000000 + (0x200000L) * i) >= 0x3F000000L) + PMD[i] = PD_ACCESS | PD_BLOCK | (0x00000000 + (0x200000L) * i) | (MAIR_IDX_DEVICE_nGnRnE << 2) | PD_UK_ACCESS | PD_UNX | PD_KNX; + else + PMD[i] = PD_ACCESS | PD_BLOCK | (0x00000000 + (0x200000L) * i) | (MAIR_IDX_NORMAL_NOCACHE << 2); + // 0x3c0000000 ~ 0x3f0000000 is GPU (mailbox ..etc) memory + } + + // setting up second PMD which maps to 0x40000000 ~ 0x7fffffff + unsigned long *PMD2 = (unsigned long *)0x4000; + for (int i = 0; i < 512; i++) + // 0x40000000 ~ 0x7fffffff ARM local peripherals -> Device memory nGnRnE + PMD2[i] = PD_ACCESS | PD_BLOCK | (0x40000000 + (0x200000L) * i) | (MAIR_IDX_DEVICE_nGnRnE << 2) | PD_UK_ACCESS | PD_UNX | PD_KNX; + + // PUD first entry point to first PMD + *(unsigned long *)(PUD) = PD_ACCESS | PD_TABLE | (unsigned long)PMD; + // PUD second entry point to second PMD + *(unsigned long *)(PUD + 0x8) = PD_ACCESS | PD_TABLE | (unsigned long)PMD2; + + return x0; +} + +// pa,va aligned to 4K +void map_one_page(size_t *virt_pgd_p, size_t va, size_t pa, size_t flag) +{ + size_t *table_p = virt_pgd_p; + // 4 level page table + for (int level = 0; level < 4; level++) + { + //get table index according descriptor + // 0x1ff first 9 bits + // shift right va to get to bit corresponding to current table + unsigned int idx = (va >> (39 - level * 9)) & 0x1ff; + // page level + if (level == 3) + { + // setting page + table_p[idx] = pa; + table_p[idx] |= PD_ACCESS | PD_TABLE | (MAIR_IDX_NORMAL_NOCACHE << 2) | PD_KNX | flag; + return; + } + + if (!table_p[idx]) + { + size_t *newtable_p = malloc(0x1000); + memset(newtable_p, 0, 0x1000); + // arm walk get virtual address + // change virtual newtable_p to physical + table_p[idx] = VIRT_TO_PHYS((size_t)newtable_p); + // normal memory no cache + table_p[idx] |= PD_ACCESS | PD_TABLE | (MAIR_IDX_NORMAL_NOCACHE << 2); + } + // page table is set on linear mapped kernel space + table_p = (size_t *)PHYS_TO_VIRT((size_t)(table_p[idx] & ENTRY_ADDR_MASK)); + } +} + +void add_vma(thread_t *t, size_t va, size_t size, size_t pa, size_t rwx, int is_alloced) +{ + // alignment 4k + size = size % 0x1000 ? size + (0x1000 - size % 0x1000) : size; + // malloc this page + // but the page table of user doesnt have an entry corresponding this page + vm_area_struct_t *new_area = malloc(sizeof(vm_area_struct_t)); + new_area->rwx = rwx; + new_area->area_size = size; + new_area->virt_addr = va; + new_area->phys_addr = pa; + new_area->is_alloced = is_alloced; + // memorize this vma, demand paging -> when use this vma, map to memory space + list_add_tail((list_head_t *)new_area, &t->vma_list); +} + +void free_page_tables(size_t *page_table, int level) +{ + size_t *table_virt = (size_t *)PHYS_TO_VIRT((char *)page_table); + // traverse page tables + for (int i = 0; i < 512; i++) + { + if (table_virt[i] != 0) + { + size_t *next_table = (size_t *)(table_virt[i] & ENTRY_ADDR_MASK); + if (table_virt[i] & PD_TABLE) + { + // recursion free + if (level != 2) + free_page_tables(next_table, level + 1); + table_virt[i] = 0L; + free(PHYS_TO_VIRT((char *)next_table)); + } + } + } +} + +void handle_abort(esr_el1_t *esr_el1) +{ + /* + Holds the faulting Virtual Address for all synchronous Instruction or Data Abort, + PC alignment fault and Watchpoint exceptions that are taken to EL1. + get current fault vma + */ + unsigned long long far_el1; + __asm__ __volatile__("mrs %0, FAR_EL1\n\t" + : "=r"(far_el1)); + + // check this page fault page is in vma or not + list_head_t *pos; + vm_area_struct_t *the_area_ptr = 0; + list_for_each(pos, &curr_thread->vma_list) + { + vm_area_struct_t *vma_tmp = (vm_area_struct_t *)pos; + if (vma_tmp->virt_addr <= far_el1 && (vma_tmp->virt_addr + vma_tmp->area_size) >= far_el1) + { + the_area_ptr = vma_tmp; + break; + } + } + + // if not in vma -> seg_fault + if (!the_area_ptr) + seg_fault(); + + // is in vma and iss is translation fault + // -> map this page + if ((esr_el1->iss & 0x3f) == TF_LEVEL0 || (esr_el1->iss & 0x3f) == TF_LEVEL1 || (esr_el1->iss & 0x3f) == TF_LEVEL2 || (esr_el1->iss & 0x3f) == TF_LEVEL3) + { + // current address + uart_printf("[Translation fault]: 0x%x\n", far_el1); + + size_t addr_offset = (far_el1 - the_area_ptr->virt_addr); + // allign + addr_offset = (addr_offset % 0x1000) == 0 ? addr_offset : addr_offset - (addr_offset % 0x1000); + map_one_page_rwx(PHYS_TO_VIRT(curr_thread->context.ttbr0_el1), the_area_ptr->virt_addr + addr_offset, the_area_ptr->phys_addr + addr_offset, the_area_ptr->rwx); + } + // iss is other fault -> seg_fault + else // For other Fault (permisson ...etc) + seg_fault(); +} + +void map_one_page_rwx(size_t *pgd_p, size_t va, size_t pa, size_t rwxflag) +{ + size_t flag = 0; + //set rwx and then map + // execute + if (!(rwxflag & 0b100)) + flag |= PD_UNX; + + // write + if (!(rwxflag & 0b10)) + flag |= PD_RDONLY; + + // read + if (rwxflag & 0b1) + flag |= PD_UK_ACCESS; + + map_one_page(pgd_p, va, pa, flag); +} + +void seg_fault() +{ + uart_printf("[Segmentation fault]: Kill Process\r\n"); + thread_exit(); +} \ No newline at end of file diff --git a/lab6/kernel/lib/sched.S b/lab6/kernel/lib/sched.S new file mode 100644 index 000000000..2513e1890 --- /dev/null +++ b/lab6/kernel/lib/sched.S @@ -0,0 +1,62 @@ + +// tpidr_el1 : per cpu offset +// MSB 2 bit : thread id + +.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] + // get translation based address + ldp x9, x0, [x1, 16 * 6] + mov sp, x9 + + msr tpidr_el1, x1 + + // remember to switch ttbr0_el1 + // need memory barriers + // gurantee previos instructions are finished + // TLB invalidation -> old value are staled + 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 invalidatation + isb // clear pipeline + + ret + +.global store_context +store_context: + stp x19, x20, [x0, 16 * 0] + stp x21, x22, [x0, 16 * 1] + stp x23, x24, [x0, 16 * 2] + stp x25, x26, [x0, 16 * 3] + stp x27, x28, [x0, 16 * 4] + stp fp, lr, [x0, 16 * 5] + mov x9, sp + str x9, [x0, 16 * 6] + ret + +.global load_context +load_context: + ldp x19, x20, [x0, 16 * 0] + ldp x21, x22, [x0, 16 * 1] + ldp x23, x24, [x0, 16 * 2] + ldp x25, x26, [x0, 16 * 3] + ldp x27, x28, [x0, 16 * 4] + ldp fp, lr, [x0, 16 * 5] + ldr x9, [x0, 16 * 6] + mov sp, x9 + ret \ No newline at end of file diff --git a/lab6/kernel/lib/sched.c b/lab6/kernel/lib/sched.c new file mode 100644 index 000000000..58cb3c8ba --- /dev/null +++ b/lab6/kernel/lib/sched.c @@ -0,0 +1,197 @@ +#include "sched.h" +#include "irq.h" +#include "malloc.h" +#include "timer.h" +#include "uart.h" +#include "mmu.h" +#include "current.h" +#include "signal.h" +#include "string.h" + +thread_t *curr_thread; +list_head_t *run_queue; +list_head_t *wait_queue; +thread_t threads[PIDMAX + 1]; + +void init_thread_sched() +{ + lock(); + // malloc run & wait queue + run_queue = malloc(sizeof(list_head_t)); + wait_queue = malloc(sizeof(list_head_t)); + INIT_LIST_HEAD(run_queue); + INIT_LIST_HEAD(wait_queue); + + // init pids + // 類似 thread pool + for (int i = 0; i <= PIDMAX; i++) + { + threads[i].pid = i; + threads[i].status = NEW; + } + // set current tmp thread + thread_t *tmp = malloc(sizeof(thread_t)); + // set tpidr_el1 + set_current_ctx(&tmp->context); + curr_thread = tmp; + + // create idle + thread_create(idle, 0x1000); + unlock(); +} + +void idle() { + while (1) { + // reclaim threads marked as DEAD + kill_zombies(); + // switch to next thread in run_queue + schedule(); + } +} + +void schedule() { + lock(); + do { + curr_thread = (thread_t *)curr_thread->listhead.next; + } while (list_is_head(&curr_thread->listhead, run_queue) || curr_thread->status == DEAD); + // switch to next thread + + unlock(); + switch_to(current_ctx, &curr_thread->context); +} + +void kill_zombies() +{ + lock(); + list_head_t *tmp; + list_for_each(tmp, run_queue) + { + thread_t *tmp_thread = (thread_t *)tmp; + if (tmp_thread->status == DEAD) + { + list_del(tmp); + // free(tmp_thread->user_sp); // free stack + free(tmp_thread->kernel_sp); // free stack + free_page_tables(tmp_thread->context.ttbr0_el1, 0); + // free(tmp_thread->data); // free data + + list_head_t *vma_tmp = tmp_thread->vma_list.next; + while (vma_tmp != &tmp_thread->vma_list) + { + // if is_alloced free malloced vma + if (((vm_area_struct_t *)vma_tmp)->is_alloced) + free((void *)PHYS_TO_VIRT(((vm_area_struct_t *)vma_tmp)->phys_addr)); + + list_head_t *next_vma = vma_tmp->next; + free(vma_tmp); + vma_tmp = next_vma; + } + + free(PHYS_TO_VIRT(tmp_thread->context.ttbr0_el1)); // free PGD + + tmp_thread->status = NEW; + } + } + unlock(); +} + +int exec_thread(char *data, unsigned int filesize) +{ + thread_t *t = thread_create(data, filesize); + + add_vma(t, 0x3C000000L, 0x3000000L, 0x3C000000L, 3, 0); // device + add_vma(t, 0xffffffffb000, 0x4000, (size_t)VIRT_TO_PHYS(t->user_sp), 7, 1); // stack + add_vma(t, 0x0, filesize, (size_t)VIRT_TO_PHYS(t->data), 7, 1); // text + add_vma(t, USER_SIG_WRAPPER_VIRT_ADDR_ALIGNED, 0x2000, (size_t)VIRT_TO_PHYS(signal_handler_wrapper), 5, 0); // for signal wrapper + + t->context.ttbr0_el1 = VIRT_TO_PHYS(t->context.ttbr0_el1); + t->context.sp = 0xfffffffff000; + t->context.fp = 0xfffffffff000; + t->context.lr = 0L; + + memcpy(t->data, data, filesize); + + // disable echo when going to userspace + curr_thread = t; + // different timer + add_timer(schedule_timer, "", 1, 0); + // eret to exception level 0 + __asm__ __volatile__("msr tpidr_el1, %0\n\t" + "msr elr_el1, %1\n\t" + "msr spsr_el1, xzr\n\t" // enable interrupt in EL0. You can do it by setting spsr_el1 to 0 before returning to EL0. + "msr sp_el0, %2\n\t" + "mov sp, %3\n\t" + "mov fp, sp\n\t" + "dsb ish\n\t" // ensure write has completed + "msr ttbr0_el1, %4\n\t" + // need memory barriers + // gurantee previos instructions are finished + // TLB invalidation -> old value are staled + "tlbi vmalle1is\n\t" // invalidate all TLB entries + "dsb ish\n\t" // ensure completion of TLB invalidatation + "isb\n\t" // clear pipeline" + "eret\n\t" ::"r"(&t->context), + "r"(t->context.lr), "r"(t->context.sp), "r"(t->kernel_sp + KSTACK_SIZE), "r"(t->context.ttbr0_el1)); + + return 0; +} + +thread_t *thread_create(void *start, unsigned int filesize) +{ + lock(); + + thread_t *r; + for (int i = 0; i <= PIDMAX; i++) + { + if (threads[i].status == NEW) + { + r = &threads[i]; + break; + } + } + INIT_LIST_HEAD(&r->vma_list); + r->status = RUNNING; + r->context.lr = (unsigned long long)start; + r->user_sp = malloc(USTACK_SIZE); + r->kernel_sp = malloc(KSTACK_SIZE); + r->signal_is_checking = 0; + r->data = malloc(filesize); + r->datasize = filesize; + r->context.sp = (unsigned long long)r->kernel_sp + KSTACK_SIZE; + r->context.fp = r->context.sp; + // create one PGD (page table for this process) + // set PGD to 0 + // context.ttbr0_el1 point to this PGD + r->context.ttbr0_el1 = malloc(0x1000); + memset(r->context.ttbr0_el1, 0, 0x1000); + + // initial signal handler with signal_default_handler (kill thread) + for (int i = 0; i < SIGNAL_MAX; i++) + { + r->signal_handler[i] = signal_default_handler; + r->sigcount[i] = 0; + } + + list_add(&r->listhead, run_queue); + unlock(); + return r; +} + +void thread_exit() { + // set dead + lock(); + curr_thread->status = DEAD; + unlock(); + // move on + schedule(); +} + +// timer +// set expired time +void schedule_timer(char *notuse) { + unsigned long long cntfrq_el0; + // Set the expired time as core timer frequency shift right 5 bits. + __asm__ __volatile__("mrs %0, cntfrq_el0\n\t" + : "=r"(cntfrq_el0)); // tick frequency + add_timer(schedule_timer, "", cntfrq_el0 >> 5, 1); +} \ No newline at end of file diff --git a/lab6/kernel/lib/shell.c b/lab6/kernel/lib/shell.c new file mode 100644 index 000000000..9965ef5c0 --- /dev/null +++ b/lab6/kernel/lib/shell.c @@ -0,0 +1,162 @@ +#include "uart.h" +#include "string.h" +#include "shell.h" +#include "mbox.h" +#include "system.h" +#include "cpio.h" +#include "dtb.h" +#include "timer.h" +#include "task.h" + +void cmd(char *s1) { + + char *token; + char arg[20][50]; + + for(int i = 0; i < 20; ++i) + for(int j = 0; j < 50; ++j) + arg[i][j] = '\0'; + + int i = 0; + token = strtok(s1, " "); + + while(token != 0) { + strcpy(arg[i], token); + //uart_puts(arg[i]); + ++i; + //uart_send('0'+i); + token = strtok(0, " "); + } + + if(!strcmp(arg[0], "help") && i == 1) { + uart_async_printf("cat \t\t\t: cat\n"); + uart_async_printf("clear \t\t\t: clear all\n"); + uart_async_printf("exec \t\t\t: execute test file\n"); + uart_async_printf("help \t\t\t: print this help menu\n"); + uart_async_printf("hello \t\t\t: print Hello World!\n"); + uart_async_printf("ls \t\t\t: ls\n"); + uart_async_printf("mailbox\t\t\t: show infos of board revision and ARM memory\n"); + uart_async_printf("malloc \t\t\t: allocate string abc\n"); + uart_async_printf("reboot \t\t\t: reboot the device\n"); + uart_async_printf("sto [MESSAGE] [SECONDS]\t: print message after [SECONDS] seconds\n"); + uart_async_printf("tsa [MESSAGE] \t\t: set an alarm that alert every two seconds\n"); + uart_async_printf("pfa \t\t\t: test page frame allocator\n"); + uart_async_printf("csa \t\t\t: test chunk slot allocator\n"); + } + else if(!strcmp(arg[0], "hello") && i == 1) { + uart_puts("Hello World!\n"); + } + else if(!strcmp(arg[0], "mailbox") && i == 1) { + print_system_messages(); + } + else if(!strcmp(arg[0], "reboot") && i == 1) { + reset(1); + } + else if(!strcmp(arg[0], "cat") && i == 2) { + + cat(arg[1]); + //uart_puts(arg[1]); + } + else if(!strcmp(arg[0], "clear") && i == 1) { + uart_puts("\x1b[2J\x1b[H"); + } + else if(!strcmp(arg[0], "ls") && i == 1) { + ls("."); + //uart_puts(arg[1]); + } + else if(!strcmp(arg[0], "malloc")) { + uart_puts("Allocating string abc\n"); + char *st = (char*)malloc(16); + // uart_puts("test1\n"); + st[0] = 'a'; + st[1] = 'b'; + st[2] = 'c'; + st[3] = '\0'; + uart_printf("%s\n", st); + } + else if(!strcmp(arg[0], "exec") && i == 1) { + execfile(arg[1]); + } + else if (!strcmp(arg[0], "sto") && i == 3) { + // setTimeout MESSAGE SECONDS + + uart_async_printf("'%s' Set Timeout : %d\n", arg[1], atoi(arg[2]) * get_clock_freq() + get_clock_tick()); + add_timer(uart_puts, arg[1], atoi(arg[2]), 0); + } + else if (!strcmp(arg[0], "tsa") && i == 2) { + // tsa MESSAGE + + add_timer(two_second_alert, arg[1], 2, 0); + + } + else if (!strcmp(arg[0], "async") && i == 1) { + char c = ' '; + uart_async_printf("Press enter to exit\n"); + while(c != '\n' && c != '\r') { + c = uart_async_getc(); + uart_async_putc(c); + } + uart_async_printf("\n"); + + } + else if(!strcmp(arg[0], "preempt") && i == 1) { + test_preemption(); + } + else if(!strcmp(arg[0], "pfa") && i == 1) { + alloctest(); + } + else if(!strcmp(arg[0], "csa") && i == 1) { + alloctest(); + } + else { + uart_async_printf("Unknown command: %s\n", s1); + } +} + +void shell() { + + while(1) { + uart_puts("# "); + + int i = 0; + char str[50] = {}; + char c = ' '; + + while( c != '\n') { + c = uart_getc(); + + if(c == '\n') { + uart_puts("\n"); + } + else { + uart_send(c); + } + if(c == 0x08 || c == 0x7f && i > 0) { + uart_send('\b'); + uart_send(' '); + uart_send('\b'); + str[strlen(str) - 1] = '\0'; + --i; + } + + if(c != '\n' && c != 0x08 && c != 0x7f) + str[i++] = c; + + } + cmd(str); + + } +} +void print_system_messages() +{ + unsigned int board_revision; + get_board_revision(&board_revision); + uart_async_printf("Board revision is : 0x%x\n", board_revision); + + unsigned int arm_mem_base_addr; + unsigned int arm_mem_size; + + get_arm_memory_info(&arm_mem_base_addr, &arm_mem_size); + uart_async_printf("ARM memory base address in bytes : 0x%x\n", arm_mem_base_addr); + uart_async_printf("ARM memory size in bytes : 0x%x\n", arm_mem_size); +} \ No newline at end of file diff --git a/lab6/kernel/lib/signal.c b/lab6/kernel/lib/signal.c new file mode 100644 index 000000000..ce92a64d9 --- /dev/null +++ b/lab6/kernel/lib/signal.c @@ -0,0 +1,61 @@ +#include "signal.h" + +void check_signal(trapframe_t *tpf) +{ + lock(); + if (curr_thread->signal_is_checking) + { + unlock(); + return; + } + // prevent nested running signal handler + curr_thread->signal_is_checking = 1; + unlock(); + for (int i = 0; i <= SIGNAL_MAX; i++) + { + store_context(&curr_thread->signal_saved_context); + if (curr_thread->sigcount[i] > 0) + { + lock(); + curr_thread->sigcount[i]--; + unlock(); + run_signal(tpf, i); + } + } + lock(); + curr_thread->signal_is_checking = 0; + unlock(); +} + +void run_signal(trapframe_t *tpf, int signal) +{ + curr_thread->curr_signal_handler = curr_thread->signal_handler[signal]; + + // run default handler in kernel + if (curr_thread->curr_signal_handler == signal_default_handler) + { + signal_default_handler(); + return; + } + + asm("msr elr_el1, %0\n\t" + "msr sp_el0, %1\n\t" + "msr spsr_el1, %2\n\t" + "mov x0, %3\n\t" + "eret\n\t" ::"r"(USER_SIG_WRAPPER_VIRT_ADDR_ALIGNED + ((size_t)signal_handler_wrapper % 0x1000)), + "r"(tpf->sp_el0), + "r"(tpf->spsr_el1), "r"(curr_thread->curr_signal_handler)); +} + +void signal_handler_wrapper() +{ + // call function and system call sigreturn + asm("blr x0\n\t" + "mov x8,50\n\t" + "svc 0\n\t"); +} + +void signal_default_handler() +{ + kill(0, curr_thread->pid); +} \ No newline at end of file diff --git a/lab6/kernel/lib/sprintf.c b/lab6/kernel/lib/sprintf.c new file mode 100644 index 000000000..63533303f --- /dev/null +++ b/lab6/kernel/lib/sprintf.c @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2018 bzt (bztsrc@github) + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +/** + * minimal sprintf implementation + */ + +#include "uart.h" +#include "string.h" + +unsigned int vsprintf(char *dst, char* fmt, __builtin_va_list args) +{ + long int arg; + int len, sign, i; + char *p, *orig=dst, tmpstr[19]; + + // failsafes + if(dst==(void*)0 || fmt==(void*)0) { + return 0; + } + + // main loop + arg = 0; + while(*fmt) { + if (strlen(fmt) > MAX_BUF_SIZE - 0x10 || dst - orig > MAX_BUF_SIZE - 0x10) + { + uart_puts("Error!!! format string too long!!!!"); + *dst = 0; + return dst - orig; + } + // argument access + if(*fmt=='%') { + fmt++; + // literal % + if(*fmt=='%') { + goto put; + } + len=0; + // size modifier + while(*fmt>='0' && *fmt<='9') { + len *= 10; + len += *fmt-'0'; + fmt++; + } + // skip long modifier + if(*fmt=='l') { + fmt++; + } + // character + if(*fmt=='c') { + arg = __builtin_va_arg(args, int); + *dst++ = (char)arg; + fmt++; + continue; + } else + // decimal number + if(*fmt=='d') { + arg = __builtin_va_arg(args, int); + // check input + sign=0; + if((int)arg<0) { + arg*=-1; + sign++; + } + if(arg>99999999999999999L) { + arg=99999999999999999L; + } + // convert to string + i=18; + tmpstr[i]=0; + do { + tmpstr[--i]='0'+(arg%10); + arg/=10; + } while(arg!=0 && i>0); + if(sign) { + tmpstr[--i]='-'; + } + // padding, only space + if(len>0 && len<18) { + while(i>18-len) { + tmpstr[--i]=' '; + } + } + p=&tmpstr[i]; + goto copystring; + } else + // hex number + if(*fmt=='x') { + arg = __builtin_va_arg(args, long int); + // convert to string + i=16; + tmpstr[i]=0; + do { + char n=arg & 0xf; + // 0-9 => '0'-'9', 10-15 => 'A'-'F' + tmpstr[--i]=n+(n>9?0x37:0x30); + arg>>=4; + } while(arg!=0 && i>0); + // padding, only leading zeros + if(len>0 && len<=16) { + while(i>16-len) { + tmpstr[--i]='0'; + } + } + p=&tmpstr[i]; + goto copystring; + } else + // string + if(*fmt=='s') { + p = __builtin_va_arg(args, char*); +copystring: if(p==(void*)0) { + p="(null)"; + } + while(*p) { + *dst++ = *p++; + } + } + } else { +put: *dst++ = *fmt; + } + fmt++; + } + *dst=0; + // number of bytes written + return dst-orig; +} + +/** + * Variable length arguments + */ +unsigned int sprintf(char *dst, char* fmt, ...) +{ + __builtin_va_list args; + __builtin_va_start(args, fmt); + unsigned int r = vsprintf(dst,fmt,args); + __builtin_va_end(args); + return r; +} \ No newline at end of file diff --git a/lab6/kernel/lib/string.c b/lab6/kernel/lib/string.c new file mode 100644 index 000000000..47ba7548f --- /dev/null +++ b/lab6/kernel/lib/string.c @@ -0,0 +1,161 @@ +#include +#define NULL ((void *)0) + + +int isalpha(char c){ + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); +} + +int isdigit(int c) { + return c >= '0' && c <= '9'; +} + +int toupper(int c) { + if (c >= 'a' && c <= 'z') { + return c - 'a' + 'A'; + } else { + return c; + } +} + +int isxdigit(int c) { + return isdigit(c) || (toupper(c) >= 'A' && toupper(c) <= 'F'); +} + +unsigned long long strlen(const char *str) { + + unsigned long long len = 0; + while((unsigned char)*str++) { + ++len; + } + + return len; +} + + +char *strcpy(char *dest, const char *src) { + char *ret = dest; + while ((*dest++ = *src++)); + return ret; +} + +void _strcpy(char *dest, char *src){ + if (dest==NULL||src==NULL) + return; + + while(*src!=NULL){ + *dest = *src; + dest++; + src++; + } +} + +int strcmp(const char *s1, const char *s2) { + while (*s1 && *s2 && (*s1 == *s2)) { + s1++; + s2++; + } + return *s1 - *s2; +} + +int strncmp(const char *s1, const char *s2, int n) { + while (n-- && *s1 && (*s1 == *s2)) { + s1++; + s2++; + } + if (n == (int) -1) { + return 0; + } + return *(const unsigned char *) s1 - *(const unsigned char *) s2; +} + +char* strtok(char* str, const char* delimiters) { + static char* buffer = 0; + if (str != 0) { + buffer = str; + } + if (buffer == 0) { + return 0; + } + char* start = buffer; + while (*buffer != '\0') { + const char* delim = delimiters; + while (*delim != '\0') { + if (*buffer == *delim) { + *buffer = '\0'; + buffer++; + if (start != buffer) { + return start; + } else { + start++; + break; + } + } + delim++; + } + if (*delim == '\0') { + buffer++; + } + } + if (start == buffer) { + return 0; + } else { + return start; + } +} + +long long atoi(char* str) { + long long result = 0; + long long sign = 1; + long long i = 0; + + if (str[0] == '-') { + sign = -1; + i++; + } + + for (; str[i] != '\0'; ++i) { + if (str[i] >= '0' && str[i] <= '9') { + result = result * 10 + (str[i] - '0'); + } + else { + + return 0; + } + } + + return sign * result; +} + +unsigned int parse_hex_str(char *arr, int size) { + unsigned int result = 0; + + for (int i = 0; i < size; i++) { + char c = arr[i]; + if (isxdigit(c)) { + int val = isdigit(c) ? c - '0' : toupper(c) - 'A' + 10; + result = (result << 4) + val; + } + } + + return result; +} + +char* strchr (register const char *s, int c) { + do { + if (*s == c) { + return (char*)s; + } + } while (*s++); + return (0); +} + + +void *memset(void *s, int c, size_t n) { + char *start = s; + for (size_t i = 0; i < n; i++) { + start[i] = c; + } + + return s; +} \ No newline at end of file diff --git a/lab6/kernel/lib/syscall.c b/lab6/kernel/lib/syscall.c new file mode 100644 index 000000000..92fefbb63 --- /dev/null +++ b/lab6/kernel/lib/syscall.c @@ -0,0 +1,253 @@ +#include "syscall.h" +#include "sched.h" +#include "stddef.h" +#include "uart.h" +#include "cpio.h" +#include "irq.h" +#include "malloc.h" +#include "mbox.h" +#include "signal.h" +#include "current.h" +#include "mmu.h" +#include "string.h" + + +int getpid(trapframe_t *tpf) +{ + tpf->x0 = curr_thread->pid; + return curr_thread->pid; +} + +size_t uartread(trapframe_t *tpf, char buf[], size_t size) +{ + int i = 0; + for (int i = 0; i < size; i++) + buf[i] = uart_getc(); + + tpf->x0 = i; + return i; +} + +size_t uartwrite(trapframe_t *tpf, const char buf[], size_t size) +{ + int i = 0; + for (int i = 0; i < size; i++) + uart_send(buf[i]); + + tpf->x0 = i; + return i; +} + +// In this lab, you won’t have to deal with argument passing +int exec(trapframe_t *tpf, const char *name, char *const argv[]) +{ + // free alloced area and vma struct + list_head_t *pos = curr_thread->vma_list.next; + while (pos != &curr_thread->vma_list) + { + // is_alloced -> malloc + if (((vm_area_struct_t *)pos)->is_alloced) + free((void *)PHYS_TO_VIRT(((vm_area_struct_t *)pos)->phys_addr)); + + list_head_t *next_pos = pos->next; + free(pos); + pos = next_pos; + } + + // exec program + INIT_LIST_HEAD(&curr_thread->vma_list); + curr_thread->datasize = get_file_size((char *)name); + char *new_data = get_file_start((char *)name); + curr_thread->data = malloc(curr_thread->datasize); + curr_thread->user_sp = malloc(USTACK_SIZE); + + // flush tlb + // malloc new ttbr0_el1 + __asm__ __volatile__("dsb ish\n\t"); // ensure write has completed + free_page_tables(curr_thread->context.ttbr0_el1, 0); + // new ttbr0_el1 + memset(PHYS_TO_VIRT(curr_thread->context.ttbr0_el1), 0, 0x1000); + __asm__ __volatile__("tlbi vmalle1is\n\t" // invalidate all TLB entries + "dsb ish\n\t" // ensure completion of TLB invalidatation + "isb\n\t"); // clear pipeline + + // remap code + add_vma(curr_thread, 0, curr_thread->datasize, (size_t)VIRT_TO_PHYS(curr_thread->data), 7, 1); + // remap stack + add_vma(curr_thread, 0xffffffffb000, 0x4000, (size_t)VIRT_TO_PHYS(curr_thread->user_sp), 7, 1); + // videocore memory (mailbox) + add_vma(curr_thread, 0x3C000000L, 0x3000000L, 0x3C000000L, 3, 0); + // for signal wrapper + add_vma(curr_thread, USER_SIG_WRAPPER_VIRT_ADDR_ALIGNED, 0x2000, (size_t)VIRT_TO_PHYS(signal_handler_wrapper), 5, 0); + + // copy file into data + memcpy(curr_thread->data, new_data, curr_thread->datasize); + + // clear signal handler + for (int i = 0; i <= SIGNAL_MAX; i++) + curr_thread->signal_handler[i] = signal_default_handler; + + // set elr sp in user space + tpf->elr_el1 = 0; + tpf->sp_el0 = 0xfffffffff000; + tpf->x0 = 0; + return 0; +} + +int fork(trapframe_t *tpf) +{ + // addvma : thread + // code : 0x0 + // user stack : 0xffffffffb000 + lock(); + thread_t *child_thread = thread_create(curr_thread->data, curr_thread->datasize); + + // copy signal handler + for (int i = 0; i <= SIGNAL_MAX; i++) + child_thread->signal_handler[i] = curr_thread->signal_handler[i]; + + // copy parent's vma except signal wrapper and video core memory + list_head_t *pos; + list_for_each(pos, &curr_thread->vma_list) + { + vm_area_struct_t *cur_vma = (vm_area_struct_t *)pos; + if (cur_vma->virt_addr == USER_SIG_WRAPPER_VIRT_ADDR_ALIGNED || cur_vma->virt_addr == 0x3C000000) + continue; + + char *new_alloc = malloc(cur_vma->area_size); // original buddy system buggy. + add_vma(child_thread, cur_vma->virt_addr, cur_vma->area_size, (size_t)VIRT_TO_PHYS(new_alloc), cur_vma->rwx, 1); + + memcpy(new_alloc, (void *)PHYS_TO_VIRT(cur_vma->phys_addr), cur_vma->area_size); + } + // video core and signal wrapper are not malloc + // every video core memory and signal wrapper of thread point to the same memory + // videocore memory (mailbox) + add_vma(child_thread, 0x3C000000L, 0x3000000L, 0x3C000000L, 3, 0); + // for signal wrapper + add_vma(child_thread, USER_SIG_WRAPPER_VIRT_ADDR_ALIGNED, 0x2000, (size_t)VIRT_TO_PHYS(signal_handler_wrapper), 5, 0); // for signal wrapper + + int parent_pid = curr_thread->pid; + + // copy stack into new process + memcpy(child_thread->kernel_sp, curr_thread->kernel_sp, KSTACK_SIZE); + + store_context(current_ctx); + // for child + if (parent_pid != curr_thread->pid) + goto child; + + child_thread->context.x19 = curr_thread->context.x19; + child_thread->context.x20 = curr_thread->context.x20; + child_thread->context.x21 = curr_thread->context.x21; + child_thread->context.x22 = curr_thread->context.x22; + child_thread->context.x23 = curr_thread->context.x23; + child_thread->context.x24 = curr_thread->context.x24; + child_thread->context.x25 = curr_thread->context.x25; + child_thread->context.x26 = curr_thread->context.x26; + child_thread->context.x27 = curr_thread->context.x28; + child_thread->context.x28 = curr_thread->context.x28; + child_thread->context.fp = child_thread->kernel_sp + curr_thread->context.fp - curr_thread->kernel_sp; // move fp + child_thread->context.lr = curr_thread->context.lr; + // set up child thread (not copy from parent) + child_thread->context.sp = child_thread->kernel_sp + curr_thread->context.sp - curr_thread->kernel_sp; // move kernel sp + child_thread->context.ttbr0_el1 = VIRT_TO_PHYS(child_thread->context.ttbr0_el1); + + unlock(); + + tpf->x0 = child_thread->pid; + return child_thread->pid; + +child: + tpf->x0 = 0; + return 0; +} + +void exit(trapframe_t *tpf, int status) +{ + thread_exit(); +} + +int syscall_mbox_call(trapframe_t *tpf, unsigned char ch, unsigned int *mbox_user) +{ + lock(); + // move user space mbox message to kernel space + unsigned int size_of_mbox = mbox_user[0]; + memcpy((char *)mbox, mbox_user, size_of_mbox); + mbox_call(ch); + memcpy(mbox_user, (char *)mbox, size_of_mbox); + + tpf->x0 = 8; + unlock(); + return 0; +} + +void kill(trapframe_t *tpf, int pid) +{ + lock(); + if (pid >= PIDMAX || pid < 0 || threads[pid].status == NEW) + { + unlock(); + return; + } + threads[pid].status = DEAD; + unlock(); + schedule(); +} + +void signal_register(int signal, void (*handler)()) +{ + if (signal > SIGNAL_MAX || signal < 0) + return; + + curr_thread->signal_handler[signal] = handler; +} + +void signal_kill(int pid, int signal) +{ + if (pid > PIDMAX || pid < 0 || threads[pid].status == NEW) + return; + + lock(); + threads[pid].sigcount[signal]++; + unlock(); +} + +void sigreturn(trapframe_t *tpf) +{ + load_context(&curr_thread->signal_saved_context); +} + +// only need to implement the anonymous page mapping in this Lab. +void *sys_mmap(trapframe_t *tpf, void *addr, size_t len, int prot, int flags, int fd, int file_offset) +{ + // relocate to zero + if (len + (unsigned long)addr >= 0xfffffffff000L) + addr = 0L; + + // allign + len = len % 0x1000 ? len + (0x1000 - len % 0x1000) : len; // rounds up + addr = (unsigned long)addr % 0x1000 ? addr + (0x1000 - (unsigned long)addr % 0x1000) : addr; + + // check if any vma overlap the page + list_head_t *pos; + vm_area_struct_t *the_area_ptr = 0; + list_for_each(pos, &curr_thread->vma_list) + { + if (!(((vm_area_struct_t *)pos)->virt_addr >= (unsigned long)(addr + len) || ((vm_area_struct_t *)pos)->virt_addr + ((vm_area_struct_t *)pos)->area_size <= (unsigned long)addr)) + { + the_area_ptr = (vm_area_struct_t *)pos; + break; + } + } + + // overlapped -> make the end as start + if (the_area_ptr) + { + tpf->x0 = (unsigned long)sys_mmap(tpf, (void *)(the_area_ptr->virt_addr + the_area_ptr->area_size), len, prot, flags, fd, file_offset); + return (void *)tpf->x0; + } + // if not overlapped add this page to vma + add_vma(curr_thread, (unsigned long)addr, len, VIRT_TO_PHYS((unsigned long)malloc(len)), prot, 1); + tpf->x0 = (unsigned long)addr; + return (void *)tpf->x0; +} \ No newline at end of file diff --git a/lab6/kernel/lib/system.c b/lab6/kernel/lib/system.c new file mode 100644 index 000000000..55efb6401 --- /dev/null +++ b/lab6/kernel/lib/system.c @@ -0,0 +1,74 @@ +#include "system.h" +#include "uart.h" +#include "mbox.h" + +#define PM_PASSWORD 0x5a000000 +#define PM_RSTC PHYS_TO_VIRT(0x3F10001c) +#define PM_WDOG PHYS_TO_VIRT(0x3F100024) + +void set(long addr, unsigned int value) { + volatile unsigned int* point = (unsigned int*)addr; + *point = value; +} + +void reset(int tick) { // reboot after watchdog timer expire + set(PM_RSTC, PM_PASSWORD | 0x20); // full reset + set(PM_WDOG, PM_PASSWORD | tick); // number of watchdog tick +} + +void cancel_reset() { + set(PM_RSTC, PM_PASSWORD | 0); // full reset + set(PM_WDOG, PM_PASSWORD | 0); // number of watchdog tick +} +void reboot() +{ + //disable_uart(); + reset(1); // timeout = 1/16th of a second? (whatever) +} +int get_board_revision(unsigned int* board_revision) +{ + /* + GET_BOARD_REVISION + */ + mbox[0] = 7*4; // length of the message + mbox[1] = MBOX_REQUEST; // request code + mbox[2] = GET_BOARD_REVISION; // tag identifier + mbox[3] = 4; // value buffer size in bytes + mbox[4] = 0; // request codes : b31 clear, b30-b0 reversed + mbox[5] = 0; // clear output buffer + mbox[6] = MBOX_TAG_LAST; // end tag + // send the message to the GPU and receive answer + if (mbox_call(MBOX_CH_PROP)) { + *board_revision = mbox[5]; + return 0; + } else { + uart_puts("Unable to query serial!"); + *board_revision = mbox[5] = -1; + return -1; + } +} + +int get_arm_memory_info(unsigned int* base_addr,unsigned int* size) +{ + /* + GET arm_memory address and size + */ + mbox[0] = 8*4; // length of the message + mbox[1] = MBOX_REQUEST; // request code + mbox[2] = GET_ARM_MEMORY; // tag identifier + mbox[3] = 8; // value buffer size in bytes + mbox[4] = 0; // request codes : b31 clear, b30-b0 reversed + mbox[5] = 0; // clear output buffer ( u32: base address in bytes ) + mbox[6] = 0; // clear output buffer ( u32: size in bytes ) + mbox[7] = MBOX_TAG_LAST; // end tag + + // send the message to the GPU and receive answer + if (mbox_call(MBOX_CH_PROP)) { + *base_addr = mbox[5]; + *size = mbox[6]; + return 0; + } else { + uart_puts("Unable to query serial!"); + return -1; + } +} diff --git a/lab6/kernel/lib/task.c b/lab6/kernel/lib/task.c new file mode 100644 index 000000000..ea3c1526b --- /dev/null +++ b/lab6/kernel/lib/task.c @@ -0,0 +1,75 @@ +#include "task.h" +#include "irq.h" +#include "string.h" +#include "malloc.h" +#include "uart.h" +#include "timer.h" + +list_head_t task_list; +int cur_priority = 10; + +void task_list_init() { + INIT_LIST_HEAD(&task_list); +} +void add_task(task_callback_t callback, int priority){ + // init task + //uart_puts("t1\n"); + task_t *t = malloc(sizeof(task_t)); + //uart_puts("t2\n"); + t->priority = priority; + + //uart_puts("t3\n"); + t->callback = callback; + //uart_puts("t4\n"); + + INIT_LIST_HEAD(&t->listhead); + + disable_interrupt(); + // insert based on priority + if(list_empty(&task_list)) { + //uart_puts("f\n"); + list_add_tail(&t->listhead, &task_list); + } + else { + task_t *now; + list_head_t *listptr; + int inst = 0; + list_for_each(listptr, &task_list) { + now = list_entry(listptr, task_t, listhead); + + if(priority < now->priority) { + list_insert(&t->listhead, listptr->prev, listptr); + inst = 1; + break; + } + } + if(!inst) { + list_add_tail(&t->listhead, &task_list); + } + } + + enable_interrupt(); +} + +void pop_task() { + while (!list_empty(&task_list)) { + disable_interrupt(); + task_t *first = (task_t *)list_entry(task_list.next, task_t, listhead); + if(first->priority > cur_priority) { + enable_interrupt(); + return; + } + + list_del(&first->listhead); + int tmp_priority = cur_priority; + cur_priority = first->priority; + enable_interrupt(); + + first->callback(); + + disable_interrupt(); + cur_priority = tmp_priority; + enable_interrupt(); + } +} + diff --git a/lab6/kernel/lib/timer.c b/lab6/kernel/lib/timer.c new file mode 100644 index 000000000..06c6db231 --- /dev/null +++ b/lab6/kernel/lib/timer.c @@ -0,0 +1,268 @@ +#include "timer.h" +#include "uart.h" +#include "uint.h" +#include "irq.h" +#include "malloc.h" +#include "registers.h" +#include "string.h" +#include "utils.h" + + +struct list_head *timer_event_list; + + +/* + Enable timer interrupt: + 1. cntp_ctl_el0 to 1 (bit 0) + 2. CORE0_TIMER_IRQ_CTRL set to 2 + 3. enable cpu core interrupt + +*/ + +void timer_list_init() +{ + cpu_timer_enable(); + timer_event_list = malloc(sizeof(list_head_t)); + INIT_LIST_HEAD(timer_event_list); +} + +void cpu_timer_enable() { + uint64_t tmp; + asm volatile("mrs %0, cntkctl_el1" : "=r"(tmp)); + tmp |= 1; + asm volatile("msr cntkctl_el1, %0" ::"r"(tmp)); +} + +void core_timer_enable(){ + // according to given assembly + + // set register cntp_ctl_el1 to 1 + asm volatile("msr cntp_ctl_el0, %0"::"r"(1)); // enable timer + + + // exception 04 + core_timer_interrupt_enable(); + + +} + +void core_timer_disable(){ + // set register cntp_ctl_el0 to zero + // asm volatile("msr cntp_ctl_el0, %0"::"r"(0)); // disable + core_timer_interrupt_disable(); +} + +void core_timer_interrupt_enable(){ + + asm volatile ( + "mov x2, 2\n\t" + "ldr x1, =" XSTR(CORE0_TIMER_IRQ_CTRL) "\n\t" + "str w2, [x1]\n\t" // unmask timer interrupt + :::"x1","x2"); + + //*CORE0_TIMER_IRQ_CTRL = 2; +} +void core_timer_interrupt_disable(){ + + __asm__ __volatile__( + "mov x2, 0\n\t" + "ldr x1, =" XSTR(CORE0_TIMER_IRQ_CTRL) "\n\t" + "str w2, [x1]\n\t" // unmask timer interrupt + :::"x1","x2"); + +} + + +// relative time from now (tval) +void set_core_timer_interrupt(unsigned long long expired_time) +{ + __asm__ __volatile__( + "mrs x1, cntfrq_el0\n\t" + "mul x1, x1, %0\n\t" + "msr cntp_tval_el0, x1\n\t" + ::"r"(expired_time) + : "x1"); +} + +// absolute time (cval) +void set_core_timer_interrupt_by_tick(unsigned long long tick) +{ + __asm__ __volatile__( + "msr cntp_cval_el0, %0\n\t" + ::"r"(tick)); +} + + +void set_core_timer_interrupt_first() { + + + timer_event_t *first = list_entry(timer_event_list->next, timer_event_t, listhead); + + asm volatile("msr cntp_cval_el0, %0"::"r"(first->expire_time)); + +} + +void timer_event_callback(timer_event_t *timer_event) { + ((void (*)(char *))timer_event->callback)(timer_event->args); // call the callback store in event + list_del((struct list_head *)timer_event); // delete the event + free(timer_event->args); // free the arg space + free(timer_event); + + // set interrupt to next time_event if existing + if (!list_empty(timer_event_list)) + set_core_timer_interrupt_by_tick(((timer_event_t *)timer_event_list->next)->expire_time); + else + set_core_timer_interrupt(10000); // alternative disable (large value) +} + +void add_Node_timer(list_head_t *head, timer_event_t *entry){ + + struct list_head *listptr; + + list_for_each(listptr, head) { + timer_event_t *now; + now = list_entry(listptr, timer_event_t, listhead); + if(entry->expire_time < now->expire_time) { + list_insert(&entry->listhead, listptr->prev, listptr); + return; + } + } + list_add_tail(&entry->listhead, head); + +} + +void print_timer(){ + uart_async_printf("Now time event list :\n"); + list_head_t *listptr; + list_for_each(listptr, timer_event_list) { + timer_event_t *now; + now = list_entry(listptr, timer_event_t, listhead); + uart_async_printf("address: %x, expire time:%d : %s\n",&now->listhead, now->expire_time, now->args); + } +} + +void add_timer(timer_callback_t callback, void* arg, unsigned long long expire_time, int bytick){ + + // disable_interrupt(); + struct timer_event *entry = malloc(sizeof(struct timer_event)); + + //uart_printf("size : %d\n",n); + entry->args = malloc(strlen(arg) + 1); + strcpy(entry->args, (char *)arg); + + // argument expire time is tick or second + if (bytick == 0) + entry->expire_time = get_tick_plus_s(expire_time); // store interrupt time into timer_event + else + entry->expire_time = get_tick_plus_s(0) + expire_time; + + entry->callback = callback; + entry->listhead.next = &(entry->listhead); + entry->listhead.prev = &(entry->listhead); + + + + + lock(); + + if(list_empty(timer_event_list)) { + list_add_tail(&entry->listhead, timer_event_list); + } + else { + add_Node_timer(timer_event_list, entry); + } + + set_core_timer_interrupt_first(); + + unlock(); + + +} + + + +void pop_timer() { + + + timer_event_t *first = list_entry(timer_event_list->next, timer_event_t, listhead); + + disable_interrupt(); + list_del(&first->listhead); // del first node + enable_interrupt(); + + first->callback(first->args); // exec callback func + + // check empty + disable_interrupt(); + + if(list_empty(timer_event_list)) { + core_timer_interrupt_disable_alternative(); // turn off timer interrupt + } + else { + set_core_timer_interrupt_first(); + } + enable_interrupt(); +} + + +unsigned long long get_tick_plus_s(unsigned long long second) +{ + return (get_clock_tick() + get_clock_freq() * second); +} + +unsigned long long get_clock_tick() +{ + unsigned long long cntpct_el0; + __asm__ __volatile__( + "mrs %0, cntpct_el0\n\t" + : "=r"(cntpct_el0)); // tick now + + return cntpct_el0; +} + +unsigned long long get_clock_freq() +{ + unsigned long long cntfrq_el0; + __asm__ __volatile__( + "mrs %0, cntfrq_el0\n\t" + : "=r"(cntfrq_el0)); // tick frequency + return cntfrq_el0; +} + +unsigned long long get_clock_time() +{ + return get_clock_tick() / get_clock_freq(); +} + + +void core_timer_interrupt_disable_alternative() { + set_core_timer_interrupt(10000); +} + +void core_timer_handler() { + lock(); + if(list_empty(timer_event_list)) { + set_core_timer_interrupt(10000); + unlock(); + return; + } + timer_event_callback((timer_event_t *)timer_event_list->next); // do callback and set new interrupt + //uart_puts("core_timer_handler...\n"); + unlock(); +} + +void two_second_alert(const char *str) { + //int n = strlen(str); + //uart_printf("tsa : size : %d\n",n); + uart_printf("'%s': seconds after booting : %d\n", str, get_clock_time()); + add_timer(two_second_alert, str, 2, 0); +} +int timer_list_size() +{ + int r = 0; + struct list_head *curr; + list_for_each(curr, timer_event_list) { + r++; + } + return r; +} \ No newline at end of file diff --git a/lab6/kernel/lib/uart.c b/lab6/kernel/lib/uart.c new file mode 100644 index 000000000..192da10df --- /dev/null +++ b/lab6/kernel/lib/uart.c @@ -0,0 +1,274 @@ +#include "uart.h" + + +char uart_tx_buffer[MAX_BUF_SIZE] = {}; +char uart_rx_buffer[MAX_BUF_SIZE] = {}; + + +static unsigned int uart_tx_buffer_r_idx; +static unsigned int uart_tx_buffer_w_idx; +static unsigned int uart_rx_buffer_r_idx; +static unsigned int uart_rx_buffer_w_idx; + +/** + * Set baud rate and characteristics (115200 8N1) and map to GPIO + */ +void init_idx() { + uart_tx_buffer_r_idx = 0; + uart_tx_buffer_w_idx = 0; + uart_rx_buffer_r_idx = 0; + uart_rx_buffer_w_idx = 0; +} +void uart_init() +{ + register unsigned int r; + + r = *GPFSEL1; + //gpio15 can be both used for mini UART and PL011 UART + r &= ~((7<<12)|(7<<15)); // gpio14 least bit is 12, gpio15 least bit is 15 + r |= (2<<12)|(2<<15); // set alt5 for gpio14 and gpio15 + *GPFSEL1 = r; // control gpio pin 10~19 + *GPPUD = 0; // enable pins 14 and 15 + r=150; while(r--) { asm volatile("nop"); } + *GPPUDCLK0 = (1<<14)|(1<<15); + r=150; while(r--) { asm volatile("nop"); } + *GPPUDCLK0 = 0; // flush GPIO setup + + /* initialize UART */ + *AUX_ENABLE |= 1; // enable UART1, AUX mini uart + *AUX_MU_CNTL = 0; // disable tx,rx during configuration + *AUX_MU_IER = 0; // disable tx/rx interrupts + *AUX_MU_LCR = 3; // set data size 8 bits + *AUX_MU_MCR = 0; // don't need auto flow control + *AUX_MU_BAUD = 270; // 115200 baud, system clock 250MHz + *AUX_MU_IIR = 0x6; // clear FIFO + *AUX_MU_CNTL = 3; // enable Tx, Rx + + /* + for(int i = 0; i < MAX_BUF_SIZE; ++i) { + uart_tx_buffer[i] = '\0'; + uart_rx_buffer[i] = '\0'; + }*/ +} + +/** + * Send a character + */ +void uart_send(unsigned int c) { + /* wait until we can send */ + do{ + asm volatile("nop"); + }while(!(*AUX_MU_LSR & 0x20)); + /* write the character to the buffer */ + *AUX_MU_IO = (unsigned int)c; +} + + +char uart_getc() { + char r; + /* wait until something is in the buffer */ + //uart_puts("getc\n"); + do{ + asm volatile("nop"); + }while(!(*AUX_MU_LSR & 0x01)); + //uart_puts("read\n"); + /* read it and return */ + r = (char)(*AUX_MU_IO); + /* convert carriage return to newline */ + return r=='\r'?'\n':r; +} + + +void uart_puts(char *s) { + while(*s) { + /* convert newline to carriage return + newline */ + if(*s=='\n') + uart_send('\r'); + uart_send(*s++); + } +} + +void uart_hex(unsigned int d) { + unsigned int n; + int c; + for(c=28;c>=0;c-=4) { + // get highest tetrad + n=(d>>c)&0xF; + // 0-9 => '0'-'9', 10-15 => 'A'-'F' + n+=n>9?0x37:0x30; + uart_send(n); + } +} + +void uart_dec(int d) { + if (d < 0) { + uart_send('-'); + d = -d; + } + int divisor = 1; + while (divisor <= d / 10) { + divisor *= 10; + } + while (divisor > 0) { + uart_send((d / divisor) + '0'); + d %= divisor; + divisor /= 10; + } +} + +extern char _end; + +int uart_printf(char *fmt, ...) +{ + __builtin_va_list args; + __builtin_va_start(args, fmt); + char buf[MAX_BUF_SIZE]; + char *s = (char *)buf; + int count = vsprintf(s, fmt, args); + while (*s) + { + if (*s == '\n') + uart_send('\r'); + uart_send(*s++); + } + __builtin_va_end(args); + return count; +} + +void uart_async_putc(char c) { + // is full + // send a byte to transmit fifo + while((uart_tx_buffer_w_idx + 1) % MAX_BUF_SIZE == uart_tx_buffer_r_idx) + enable_mini_uart_tx_interrupt(); + + disable_interrupt(); + + // put a byte to tx buffer + //if(c == '\n') + // uart_tx_buffer[uart_tx_buffer_w_idx++] = '\r'; + uart_tx_buffer[uart_tx_buffer_w_idx++] = c; + uart_tx_buffer_w_idx %= MAX_BUF_SIZE; + + enable_interrupt(); + + enable_mini_uart_tx_interrupt(); +} + +char uart_async_getc() { + // is empty + // if empty rx buffer get a byte from IO + //uart_puts("call rx\n"); + // fix test + enable_mini_uart_rx_interrupt(); + while((uart_rx_buffer_w_idx == uart_rx_buffer_r_idx)) + enable_mini_uart_rx_interrupt(); + + //uart_puts("agetc\n"); + disable_interrupt(); + // get a byte from rx buffer + char c = uart_rx_buffer[uart_rx_buffer_r_idx++]; + uart_rx_buffer_r_idx %= MAX_BUF_SIZE; + + enable_interrupt(); + + + return c; +} + +// char sync uart to async +int uart_async_printf(char *fmt, ...) +{ + __builtin_va_list args; + __builtin_va_start(args, fmt); + char buf[MAX_BUF_SIZE]; + char *s = (char *)buf; + int count = vsprintf(s, fmt, args); + while (*s) + { + if (*s == '\n') + uart_async_putc('\r'); + uart_async_putc(*s++); + } + __builtin_va_end(args); + return count; +} + +void enable_mini_uart_interrupt() { + //uart_puts("a1\n"); + enable_mini_uart_rx_interrupt(); + //uart_puts("a2\n"); + enable_mini_uart_tx_interrupt(); + + // second level interrupt controller + // might be block + //uart_puts("a5\n"); + *ENABLE_IRQS_1 |= 1 << 29; + uart_puts("a4\n"); +} + +// rx interrupt bit 1 +void enable_mini_uart_rx_interrupt() { + *AUX_MU_IER |= 1; +} +// clear bit 1 +void disable_mini_uart_rx_interrupt(){ + // 11111...10 + *AUX_MU_IER &= ~1; +} + +// tx interrupt bit 2 +void enable_mini_uart_tx_interrupt() { + *AUX_MU_IER |= 1 << 1; +} +// clear bit 2 +void disable_mini_uart_tx_interrupt(){ + // 111111...01 + *AUX_MU_IER &= ~(1<<1); +} + +void unmask_aux_interrupt(){ + *ENABLE_IRQS_1 |= IRQ_PENDING_1_AUX_INT; +} + +void mask_aux_int(){ + *ENABLE_IRQS_1 &= ~IRQ_PENDING_1_AUX_INT; +} + +void uart_tx_interrupt_handler() { + // check empty + // empty then return + if(uart_tx_buffer_r_idx == uart_tx_buffer_w_idx) { + disable_mini_uart_tx_interrupt(); + return; + } + + // sent a byte to transmit fifo + disable_interrupt(); + // *AUX_MU_IO = (unsigned int)uart_tx_buffer[uart_tx_buffer_r_idx++]; + uart_send(uart_tx_buffer[uart_tx_buffer_r_idx++]); + uart_tx_buffer_r_idx %= MAX_BUF_SIZE; + enable_interrupt(); + + enable_mini_uart_tx_interrupt(); +} + +void uart_rx_interrupt_handler() { + // is holding byte + //uart_puts("rx handler 111\n"); + if((uart_rx_buffer_w_idx + 1) % MAX_BUF_SIZE == uart_rx_buffer_r_idx) { + disable_mini_uart_rx_interrupt(); + return; + } + + // get a byte from receive fifo + //uart_puts("rx handler\n"); + disable_interrupt(); + uart_rx_buffer[uart_rx_buffer_w_idx++] = (char)(*AUX_MU_IO); + //uart_send(uart_rx_buffer[uart_rx_buffer_w_idx++]) + uart_rx_buffer_w_idx %= MAX_BUF_SIZE; + enable_interrupt(); + + // enable_mini_uart_rx_interrupt(); +} + + diff --git a/lab6/kernel/linker.ld b/lab6/kernel/linker.ld new file mode 100644 index 000000000..d3b29f730 --- /dev/null +++ b/lab6/kernel/linker.ld @@ -0,0 +1,25 @@ +SECTIONS +{ + . = 0xffff000000000000; + . += 0x80000; + /* gnu linkonce : the linker will only keep one copy and discard the + other. */ + .text : { KEEP(*(.text.boot)) *(.text .text.* .gnu.linkonce.t*) } + /* read only data */ + .rodata : { *(.rodata .rodata.* .gnu.linkonce.r*) } + /* avoid redefinition */ + PROVIDE(_data = .); + .data : { *(.data .data.* .gnu.linkonce.d*) } + .bss (NOLOAD) : { + . = ALIGN(16); + __bss_start = .; + *(.bss .bss.*) + *(COMMON) + __bss_end = .; + } + _heap_start = .; + _end = .; + /* input section不會被輸出成output section */ + /DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) } +} +__bss_size = (__bss_end - __bss_start)>>3; \ No newline at end of file diff --git a/lab6/kernel/send.py b/lab6/kernel/send.py new file mode 100644 index 000000000..2e73fd246 --- /dev/null +++ b/lab6/kernel/send.py @@ -0,0 +1,25 @@ +import serial +import os,sys +import time + + +# tty.usbserial-0001 +# /dev/ttys002 +tty = serial.Serial("/dev/ttys003", 115200, timeout=0.5) +file_stats = os.stat("kernel8.img") + +# issue request and tell the size of img to rec +tty.write(str(file_stats.st_size).encode('utf-8')) +# size sended +tty.write("\n") + + +# send img byte-by-byte +# delay to ensure no loss +with open("kernel8.img", "rb") as fp: + byte = fp.read(1) + while byte: + tty.write(byte) + byte = fp.read(1) + #print(byte) + time.sleep(0.00001) \ No newline at end of file