From 416a05b1c6f9764fe57a7b9abd7146eb19ca213d Mon Sep 17 00:00:00 2001 From: Hsiang-Chieh Tsou Date: Sun, 15 May 2022 13:08:50 +0800 Subject: [PATCH 1/9] lab6 init --- lab6/bcm2710-rpi-3-b-plus.dtb | Bin 0 -> 31790 bytes lab6/config.txt | 3 + lab6/include/cpio.h | 49 ++++ lab6/include/devtree.h | 64 +++++ lab6/include/entry.h | 39 +++ lab6/include/exception.h | 7 + lab6/include/fork.h | 26 ++ lab6/include/mailbox.h | 7 + lab6/include/math.h | 7 + lab6/include/memory.h | 8 + lab6/include/mini_uart.h | 10 + lab6/include/mm.h | 55 ++++ lab6/include/peripherals/base.h | 6 + lab6/include/peripherals/exception.h | 25 ++ lab6/include/peripherals/gpio.h | 12 + lab6/include/peripherals/mailbox.h | 23 ++ lab6/include/peripherals/mini_uart.h | 19 ++ lab6/include/printf.h | 31 +++ lab6/include/reboot.h | 7 + lab6/include/sched.h | 72 +++++ lab6/include/shell.h | 6 + lab6/include/string.h | 8 + lab6/include/syscall.h | 28 ++ lab6/include/timer.h | 14 + lab6/include/utils.h | 8 + lab6/initramfs.cpio | Bin 0 -> 247296 bytes lab6/kernel/Makefile | 49 ++++ lab6/kernel/boot_kernel.S | 41 +++ lab6/kernel/kernel.c | 26 ++ lab6/kernel/linker.ld | 21 ++ lab6/lib/Makefile | 29 +++ lab6/lib/cpio.c | 232 +++++++++++++++++ lab6/lib/devtree.c | 92 +++++++ lab6/lib/entry.S | 195 ++++++++++++++ lab6/lib/exception.c | 49 ++++ lab6/lib/fork.c | 83 ++++++ lab6/lib/mailbox.c | 49 ++++ lab6/lib/math.c | 19 ++ lab6/lib/memory.c | 22 ++ lab6/lib/mini_uart.c | 69 +++++ lab6/lib/mm.S | 6 + lab6/lib/mm.c | 376 +++++++++++++++++++++++++++ lab6/lib/printf.c | 152 +++++++++++ lab6/lib/reboot.c | 18 ++ lab6/lib/sched.S | 24 ++ lab6/lib/sched.c | 107 ++++++++ lab6/lib/shell.c | 223 ++++++++++++++++ lab6/lib/string.c | 39 +++ lab6/lib/syscall.S | 49 ++++ lab6/lib/syscall.c | 143 ++++++++++ lab6/lib/timer.c | 62 +++++ lab6/lib/utils.S | 15 ++ lab6/send_img.py | 31 +++ 53 files changed, 2755 insertions(+) create mode 100644 lab6/bcm2710-rpi-3-b-plus.dtb create mode 100644 lab6/config.txt create mode 100644 lab6/include/cpio.h create mode 100644 lab6/include/devtree.h create mode 100644 lab6/include/entry.h create mode 100644 lab6/include/exception.h create mode 100644 lab6/include/fork.h create mode 100644 lab6/include/mailbox.h create mode 100644 lab6/include/math.h create mode 100644 lab6/include/memory.h create mode 100644 lab6/include/mini_uart.h create mode 100644 lab6/include/mm.h create mode 100644 lab6/include/peripherals/base.h create mode 100644 lab6/include/peripherals/exception.h create mode 100644 lab6/include/peripherals/gpio.h create mode 100644 lab6/include/peripherals/mailbox.h create mode 100644 lab6/include/peripherals/mini_uart.h create mode 100644 lab6/include/printf.h create mode 100644 lab6/include/reboot.h create mode 100644 lab6/include/sched.h create mode 100644 lab6/include/shell.h create mode 100644 lab6/include/string.h create mode 100644 lab6/include/syscall.h create mode 100644 lab6/include/timer.h create mode 100644 lab6/include/utils.h create mode 100644 lab6/initramfs.cpio create mode 100644 lab6/kernel/Makefile create mode 100644 lab6/kernel/boot_kernel.S create mode 100644 lab6/kernel/kernel.c create mode 100644 lab6/kernel/linker.ld create mode 100644 lab6/lib/Makefile create mode 100644 lab6/lib/cpio.c create mode 100644 lab6/lib/devtree.c create mode 100644 lab6/lib/entry.S create mode 100644 lab6/lib/exception.c create mode 100644 lab6/lib/fork.c create mode 100644 lab6/lib/mailbox.c create mode 100644 lab6/lib/math.c create mode 100644 lab6/lib/memory.c create mode 100644 lab6/lib/mini_uart.c create mode 100644 lab6/lib/mm.S create mode 100644 lab6/lib/mm.c create mode 100644 lab6/lib/printf.c create mode 100644 lab6/lib/reboot.c create mode 100644 lab6/lib/sched.S create mode 100644 lab6/lib/sched.c create mode 100644 lab6/lib/shell.c create mode 100644 lab6/lib/string.c create mode 100644 lab6/lib/syscall.S create mode 100644 lab6/lib/syscall.c create mode 100644 lab6/lib/timer.c create mode 100644 lab6/lib/utils.S create mode 100644 lab6/send_img.py diff --git a/lab6/bcm2710-rpi-3-b-plus.dtb b/lab6/bcm2710-rpi-3-b-plus.dtb new file mode 100644 index 0000000000000000000000000000000000000000..3934b3a26eb82fd65dbcdfca6f6916da427a3fae GIT binary patch literal 31790 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 literal 0 HcmV?d00001 diff --git a/lab6/config.txt b/lab6/config.txt new file mode 100644 index 000000000..49fc25695 --- /dev/null +++ b/lab6/config.txt @@ -0,0 +1,3 @@ +kernel=bootloader.img +arm_64bit=1 +initramfs initramfs.cpio 0x20000000 diff --git a/lab6/include/cpio.h b/lab6/include/cpio.h new file mode 100644 index 000000000..1735f5fe5 --- /dev/null +++ b/lab6/include/cpio.h @@ -0,0 +1,49 @@ +#ifndef __CPIO_H__ +#define __CPIO_H__ + +/* +Each file system object in a cpio archive comprises a header record with +basic numeric metadata followed by the full pathname of the entry and the +file data. The header record stores a series of integer values that gen- +erally follow the fields in struct stat. (See stat(2) for details.) The +variants differ primarily in how they store those integers (binary, oc- +tal, or hexadecimal). The header is followed by the pathname of the en- +try (the length of the pathname is stored in the header) and any file +data. The end of the archive is indicated by a special record with the +pathname "TRAILER!!!" +*/ + +#define CPIO_HEADER_MAGIC "070701" +#define CPIO_FOOTER_MAGIC "TRAILER!!!" +#define PI_CPIO_BASE ((void*) (0x20000000)) +#define QEMU_CPIO_BASE ((void*) (0x8000000)) + +#include "devtree.h" + +extern void *DEVTREE_CPIO_BASE; + +struct cpio_newc_header { + char c_magic[6]; // 070701 + 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]; // ignored by readers +}; + +void initramfs_callback (char *, char *, struct fdt_prop *); +void cpio_ls (); +void cpio_cat (); +void cpio_exec (); + +unsigned int hexstr_to_uint(char *s, unsigned int len); + +#endif \ No newline at end of file diff --git a/lab6/include/devtree.h b/lab6/include/devtree.h new file mode 100644 index 000000000..d8225b11c --- /dev/null +++ b/lab6/include/devtree.h @@ -0,0 +1,64 @@ +#ifndef _DEVTREE_H +#define _DEVTREE_H + +/* + magic; 0xd00dfeed (big-endian) + + totalsize; total size in bytes of the devicetree + data structure + + off_dt_struct; offset in bytes of the structure block + from the beginning of the header + + off_dt_strings; offset in bytes of the strings block + from the beginning of the header + + off_mem_rsvmap; offset in bytes of the memory reservation + block from the beginning of the header + + version; version of the devicetree data structure + + last_comp_version; lowest version of the devicetree data + structure with which the version used is backwards compatible + + boot_cpuid_phys; physical ID of the system’s boot CPU + + size_dt_strings; length in bytes of the strings block + section of the devicetree blob + + size_dt_struct; length in bytes of the structure block + section of the devicetree blob +*/ + +#define FDT_HEADER_MAGIC 0xd00dfeed +#define FDT_BEGIN_NODE 0x00000001 +#define FDT_END_NODE 0x00000002 +#define FDT_PROP 0x00000003 +#define FDT_NOP 0x00000004 +#define FDT_END 0x00000009 + +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; +}; + +struct fdt_prop { + unsigned int len; + unsigned int nameoff; +}; + +void devtree_getaddr (); +void fdt_traverse ( void (*callback)(char *, char *, struct fdt_prop *) ); + +// ARM uses little endian +unsigned int to_lendian (unsigned int); + +#endif \ No newline at end of file diff --git a/lab6/include/entry.h b/lab6/include/entry.h new file mode 100644 index 000000000..1a22d0284 --- /dev/null +++ b/lab6/include/entry.h @@ -0,0 +1,39 @@ +#ifndef _ENTRY_H +#define _ENTRY_H + +#define S_FRAME_SIZE 272 // size of all saved registers +#define S_X0 0 + +#define SYNC_INVALID_EL1t 0 +#define IRQ_INVALID_EL1t 1 +#define FIQ_INVALID_EL1t 2 +#define ERROR_INVALID_EL1t 3 + +#define SYNC_INVALID_EL1h 4 +#define IRQ_INVALID_EL1h 5 +#define FIQ_INVALID_EL1h 6 +#define ERROR_INVALID_EL1h 7 + +#define SYNC_INVALID_EL0_64 8 +#define IRQ_INVALID_EL0_64 9 +#define FIQ_INVALID_EL0_64 10 +#define ERROR_INVALID_EL0_64 11 + +#define SYNC_INVALID_EL0_32 12 +#define IRQ_INVALID_EL0_32 13 +#define FIQ_INVALID_EL0_32 14 +#define ERROR_INVALID_EL0_32 15 + +#define SYNC_ERROR 16 +#define SYSCALL_ERROR 17 + +#define ESR_ELx_EC_SHIFT 26 +#define ESR_ELx_EC_SVC64 0x15 + +#ifndef __ASSEMBLER__ + +void ret_from_fork(); + +#endif + +#endif \ No newline at end of file diff --git a/lab6/include/exception.h b/lab6/include/exception.h new file mode 100644 index 000000000..40f4ba532 --- /dev/null +++ b/lab6/include/exception.h @@ -0,0 +1,7 @@ +#ifndef _EXCEPTION_H +#define _EXCEPTION_H + +void enable_interrupt(); +void disable_interrupt(); + +#endif \ No newline at end of file diff --git a/lab6/include/fork.h b/lab6/include/fork.h new file mode 100644 index 000000000..7b018dc68 --- /dev/null +++ b/lab6/include/fork.h @@ -0,0 +1,26 @@ +#ifndef _FORK_H +#define _FORK_H + +#include "sched.h" + +#define PSR_MODE_EL0t 0x00000000 +#define PSR_MODE_EL1t 0x00000004 +#define PSR_MODE_EL1h 0x00000005 +#define PSR_MODE_EL2t 0x00000008 +#define PSR_MODE_EL2h 0x00000009 +#define PSR_MODE_EL3t 0x0000000c +#define PSR_MODE_EL3h 0x0000000d + +int copy_process(unsigned long, unsigned long, unsigned long, unsigned long); +int move_to_user_mode(unsigned long); +struct pt_regs *task_pt_regs(struct task_struct *); +void new_user_process(unsigned long); + +struct pt_regs { + unsigned long regs[31]; + unsigned long sp; + unsigned long pc; + unsigned long pstate; +}; + +#endif \ No newline at end of file diff --git a/lab6/include/mailbox.h b/lab6/include/mailbox.h new file mode 100644 index 000000000..eeecaa9de --- /dev/null +++ b/lab6/include/mailbox.h @@ -0,0 +1,7 @@ +#ifndef _MAILBOX_H +#define _MAILBOX_H + +void get_board_revision (); +void get_arm_memory (); + +#endif \ No newline at end of file diff --git a/lab6/include/math.h b/lab6/include/math.h new file mode 100644 index 000000000..353ccfb26 --- /dev/null +++ b/lab6/include/math.h @@ -0,0 +1,7 @@ +#ifndef _MATH_H +#define _MATH_H + +int log(int, int); +int pow(int, int); + +#endif \ No newline at end of file diff --git a/lab6/include/memory.h b/lab6/include/memory.h new file mode 100644 index 000000000..956f6e955 --- /dev/null +++ b/lab6/include/memory.h @@ -0,0 +1,8 @@ +#ifndef _MEMORY_H +#define _MEMORY_H + +#define MAX_HEAP_SIZE 0x10000000 + +void* simple_malloc(unsigned int); + +#endif \ No newline at end of file diff --git a/lab6/include/mini_uart.h b/lab6/include/mini_uart.h new file mode 100644 index 000000000..453ec93f2 --- /dev/null +++ b/lab6/include/mini_uart.h @@ -0,0 +1,10 @@ +#ifndef _MINI_UART_H +#define _MINI_UART_H + +void uart_init ( void ); +char uart_recv ( void ); +void uart_send ( char c ); +void uart_send_string ( char* str ); +void printf ( char *fmt, ... ); + +#endif /*_MINI_UART_H */ \ No newline at end of file diff --git a/lab6/include/mm.h b/lab6/include/mm.h new file mode 100644 index 000000000..df97f2b89 --- /dev/null +++ b/lab6/include/mm.h @@ -0,0 +1,55 @@ +#ifndef _MM_H +#define _MM_H + +#define MEM_REGION_BEGIN 0x0 +#define MEM_REGION_END 0x3C000000 +#define PAGE_SIZE 4096 +#define MAX_ORDER 8 // largest: PAGE_SIZE*2^(MAX_ORDER) + +#define ALLOCABLE 0 +#define ALLOCATED -1 +#define C_NALLOCABLE -2 +#define RESERVED -3 + +#define NULL 0 + +#define MAX_POOL_PAGES 8 +#define MAX_POOLS 8 +#define MIN_CHUNK_SIZE 8 + +#define MAX_RESERVABLE 8 + +struct frame { + unsigned int index; + int val; + int state; + struct frame *prev, *next; +}; + +struct node { + struct node *next; +}; + +struct dynamic_pool { + unsigned int chunk_size; + unsigned int chunks_per_page; + unsigned int chunks_allocated; + unsigned int page_new_chunk_off; + unsigned int pages_used; + void *page_base_addrs[MAX_POOL_PAGES]; + struct node *free_head; +}; + +void *malloc(unsigned int); +void free(void *); +void init_mm(); +void init_pool(struct dynamic_pool*, unsigned int); +int register_chunk(unsigned int); +void *chunk_alloc(unsigned int); +void chunk_free(void *); +void memory_reserve(void*, void*); +void init_mm_reserve(); + +void memzero(unsigned long, unsigned long); + +#endif /*_MM_H */ \ No newline at end of file diff --git a/lab6/include/peripherals/base.h b/lab6/include/peripherals/base.h new file mode 100644 index 000000000..8f66cd5fe --- /dev/null +++ b/lab6/include/peripherals/base.h @@ -0,0 +1,6 @@ +#ifndef _P_BASE_H +#define _P_BASE_H + +#define PBASE 0x3F000000 + +#endif /*_P_BASE_H */ \ No newline at end of file diff --git a/lab6/include/peripherals/exception.h b/lab6/include/peripherals/exception.h new file mode 100644 index 000000000..bc64c7c53 --- /dev/null +++ b/lab6/include/peripherals/exception.h @@ -0,0 +1,25 @@ +#ifndef _P_EXCEPTION_H +#define _P_EXCEPTION_H + +#include "peripherals/base.h" + +#define IRQ_BASIC_PENDING (PBASE+0x0000B200) +#define IRQ_PENDING_1 (PBASE+0x0000B204) +#define IRQ_PENDING_2 (PBASE+0x0000B208) +#define FIQ_CONTROL (PBASE+0x0000B20C) +#define ENABLE_IRQS_1 (PBASE+0x0000B210) +#define ENABLE_IRQS_2 (PBASE+0x0000B214) +#define ENABLE_BASIC_IRQS (PBASE+0x0000B218) +#define DISABLE_IRQS_1 (PBASE+0x0000B21C) +#define DISABLE_IRQS_2 (PBASE+0x0000B220) +#define DISABLE_BASIC_IRQS (PBASE+0x0000B224) + +#define CNTPCT_EL0 (0x4000001C) +#define CNTP_CTL_EL0 (0x40000040) +#define CORE0_INTERRUPT_SRC (0x40000060) + +#define IRQ_PENDING_1_AUX_INT (1<<29) +#define INTERRUPT_SOURCE_GPU (1<<8) +#define INTERRUPT_SOURCE_CNTPNSIRQ (1<<1) + +#endif \ No newline at end of file diff --git a/lab6/include/peripherals/gpio.h b/lab6/include/peripherals/gpio.h new file mode 100644 index 000000000..c5d7d138f --- /dev/null +++ b/lab6/include/peripherals/gpio.h @@ -0,0 +1,12 @@ +#ifndef _P_GPIO_H +#define _P_GPIO_H + +#include "peripherals/base.h" + +#define GPFSEL1 (PBASE+0x00200004) +#define GPSET0 (PBASE+0x0020001C) +#define GPCLR0 (PBASE+0x00200028) +#define GPPUD (PBASE+0x00200094) +#define GPPUDCLK0 (PBASE+0x00200098) + +#endif /*_P_GPIO_H */ \ No newline at end of file diff --git a/lab6/include/peripherals/mailbox.h b/lab6/include/peripherals/mailbox.h new file mode 100644 index 000000000..0351bd714 --- /dev/null +++ b/lab6/include/peripherals/mailbox.h @@ -0,0 +1,23 @@ +#ifndef _P_MAILBOX_H +#define _P_MAILBOX_H + +#define MMIO_BASE 0x3f000000 +#define MAILBOX_BASE MMIO_BASE + 0xb880 + +#define MAILBOX_READ (volatile unsigned int*) (MAILBOX_BASE) +#define MAILBOX_STATUS (volatile unsigned int*) (MAILBOX_BASE + 0x18) +#define MAILBOX_WRITE (volatile unsigned int*) (MAILBOX_BASE + 0x20) + +#define MAILBOX_EMPTY 0x40000000 +#define MAILBOX_FULL 0x80000000 + +#define GET_BOARD_REVISION 0x00010002 +#define GET_ARM_MEMORY 0x00010005 + +#define REQUEST_CODE 0x00000000 +#define REQUEST_SUCCEED 0x80000000 +#define REQUEST_FAILED 0x80000001 +#define TAG_REQUEST_CODE 0x00000000 +#define END_TAG 0x00000000 + +#endif \ No newline at end of file diff --git a/lab6/include/peripherals/mini_uart.h b/lab6/include/peripherals/mini_uart.h new file mode 100644 index 000000000..386b6fade --- /dev/null +++ b/lab6/include/peripherals/mini_uart.h @@ -0,0 +1,19 @@ +#ifndef _P_MINI_UART_H +#define _P_MINI_UART_H + +#include "peripherals/base.h" + +#define AUX_ENABLES (PBASE+0x00215004) +#define AUX_MU_IO_REG (PBASE+0x00215040) +#define AUX_MU_IER_REG (PBASE+0x00215044) +#define AUX_MU_IIR_REG (PBASE+0x00215048) +#define AUX_MU_LCR_REG (PBASE+0x0021504C) +#define AUX_MU_MCR_REG (PBASE+0x00215050) +#define AUX_MU_LSR_REG (PBASE+0x00215054) +#define AUX_MU_MSR_REG (PBASE+0x00215058) +#define AUX_MU_SCRATCH (PBASE+0x0021505C) +#define AUX_MU_CNTL_REG (PBASE+0x00215060) +#define AUX_MU_STAT_REG (PBASE+0x00215064) +#define AUX_MU_BAUD_REG (PBASE+0x00215068) + +#endif /*_P_MINI_UART_H */ \ No newline at end of file diff --git a/lab6/include/printf.h b/lab6/include/printf.h new file mode 100644 index 000000000..dfedbe301 --- /dev/null +++ b/lab6/include/printf.h @@ -0,0 +1,31 @@ +/* + * 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. + * + */ +#ifndef _PRINTF_H +#define _PRINTF_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/include/reboot.h b/lab6/include/reboot.h new file mode 100644 index 000000000..df4d63a86 --- /dev/null +++ b/lab6/include/reboot.h @@ -0,0 +1,7 @@ +#ifndef _REBOOT_H +#define _REBOOT_H + +void reset ( int ); +void cancel_reset (); + +#endif \ No newline at end of file diff --git a/lab6/include/sched.h b/lab6/include/sched.h new file mode 100644 index 000000000..a05e6ae25 --- /dev/null +++ b/lab6/include/sched.h @@ -0,0 +1,72 @@ +#ifndef _SCHED_H +#define _SCHED_H + +#define THREAD_CPU_CONTEXT 0 + +#ifndef __ASSEMBLER__ + +#define THREAD_SIZE 4096 + +#define NR_TASKS 64 + +#define FIRST_TASK task[0] +#define LAST_TASK task[NR_TASKS-1] + +#define TASK_RUNNING 0 +#define TASK_INTERRUPTIBLE 1 +#define TASK_UNINTERRUPTIBLE 2 +#define TASK_ZOMBIE 3 +#define TASK_STOPPED 4 + +#define PF_KTHREAD 0x00000002 + +extern struct task_struct *current; +extern struct task_struct *task[NR_TASKS]; +extern int nr_tasks; + +struct cpu_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 sp; + unsigned long pc; +}; + +struct task_struct { + struct cpu_context cpu_context; + long state; + long counter; + long priority; + long preempt_count; + unsigned long stack; + unsigned long flags; + long id; +}; + +extern void sched_init(); +extern void schedule(); +extern void timer_tick(); +extern void preempt_disable(); +extern void preempt_enable(); +extern void switch_to(struct task_struct *); +extern void cpu_switch_to(struct task_struct *, struct task_struct *); +extern void exit_process(); +extern void kill_zombies(); + +#define INIT_TASK \ +{ \ +{0,0,0,0,0,0,0,0,0,0,0,0,0},\ +0, 0, 1, 0, 0, PF_KTHREAD, 0 \ +} + +#endif + +#endif \ No newline at end of file diff --git a/lab6/include/shell.h b/lab6/include/shell.h new file mode 100644 index 000000000..828c56763 --- /dev/null +++ b/lab6/include/shell.h @@ -0,0 +1,6 @@ +#ifndef _SHELL_H +#define _SHELL_H + +void shell_loop (); + +#endif \ No newline at end of file diff --git a/lab6/include/string.h b/lab6/include/string.h new file mode 100644 index 000000000..abf24c93f --- /dev/null +++ b/lab6/include/string.h @@ -0,0 +1,8 @@ +#ifndef _STRING_H +#define _STRING_H + +int stringcmp (const char *, const char *); +int stringncmp (const char *, const char *, unsigned int); +unsigned int strlen(const char *); + +#endif \ No newline at end of file diff --git a/lab6/include/syscall.h b/lab6/include/syscall.h new file mode 100644 index 000000000..b738fcff9 --- /dev/null +++ b/lab6/include/syscall.h @@ -0,0 +1,28 @@ +#ifndef _SYSCALL_H +#define _SYSCALL_H + +#define __NR_SYSCALLS 8 + +#define SYS_GETPID_NUM 0 +#define SYS_UARTREAD_NUM 1 +#define SYS_UARTWRITE_NUM 2 +#define SYS_EXEC_NUM 3 +#define SYS_FORK_NUM 4 +#define SYS_EXIT_NUM 5 +#define SYS_MBOXCALL_NUM 6 +#define SYS_KILL_NUM 7 + +#ifndef __ASSEMBLER__ + +int sys_getpid(); +unsigned sys_uartread(char buf[], unsigned size); +unsigned sys_uartwrite(const char buf[], unsigned size); +int sys_exec(const char *name, char *const argv[]); +int sys_fork(); +void sys_exit(int status); +int sys_mbox_call(unsigned char ch, unsigned int *mbox); +void sys_kill(int pid); + +#endif + +#endif \ No newline at end of file diff --git a/lab6/include/timer.h b/lab6/include/timer.h new file mode 100644 index 000000000..9812a457a --- /dev/null +++ b/lab6/include/timer.h @@ -0,0 +1,14 @@ +#ifndef _TIMER_H +#define _TIMER_H + +#define CORE0_TIMER_IRQ_CTRL 0x40000040 + +void timer_init(); +void handle_timer_irq(); +void core_timer_enable(); +void core_timer_disable(); +void set_timer(unsigned int rel_time); +unsigned int read_timer(); +unsigned int read_freq(); + +#endif \ No newline at end of file diff --git a/lab6/include/utils.h b/lab6/include/utils.h new file mode 100644 index 000000000..9d950d477 --- /dev/null +++ b/lab6/include/utils.h @@ -0,0 +1,8 @@ +#ifndef _BOOT_H +#define _BOOT_H + +extern void delay ( unsigned long ); +extern void put32 ( unsigned long, unsigned int ); +extern unsigned int get32 ( unsigned long ); + +#endif /*_BOOT_H */ \ No newline at end of file diff --git a/lab6/initramfs.cpio b/lab6/initramfs.cpio new file mode 100644 index 0000000000000000000000000000000000000000..0676fb1584617bc4d73624b3b212a12133e97d8e GIT binary patch literal 247296 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 '0' && s[i] <= '9') { + n += s[i] - '0'; + } else if (s[i] >= 'A' && s[i] <= 'F') { + n += s[i] - 'A' + 10; + } + } + + return n; + +} + +void initramfs_callback(char *node_name, char *prop_name, struct fdt_prop *prop) { + + if (stringncmp(node_name, "chosen", 7) == 0 && + stringncmp(prop_name, "linux,initrd-start", 19) == 0) { + + DEVTREE_CPIO_BASE = (void*)to_lendian(*((unsigned int*)(prop + 1))); + + } + +} + +void cpio_ls() { + + struct cpio_newc_header *header; + unsigned int filesize; + unsigned int namesize; + unsigned int offset; + char *filename; + + header = DEVTREE_CPIO_BASE; + + while (1) { + + filename = ((void*)header) + sizeof(struct cpio_newc_header); + + if (stringncmp((char*)header, CPIO_HEADER_MAGIC, 6) != 0) { + uart_send_string("invalid magic\n"); + break; + } + if (stringncmp(filename, CPIO_FOOTER_MAGIC, 11) == 0) break; + + uart_send_string(filename); + uart_send('\n'); + + namesize = hexstr_to_uint(header->c_namesize, 8); + filesize = hexstr_to_uint(header->c_filesize, 8); + + offset = sizeof(struct cpio_newc_header) + namesize; + if (offset % 4 != 0) + offset = ((offset/4) + 1) * 4; + + if (filesize % 4 != 0) + filesize = ((filesize/4) + 1) * 4; + + offset = offset + filesize; + + header = ((void*)header) + offset; + + } + +} + +void cpio_cat() { + + char input[256]; + char c = '\0'; + int idx = 0; + + struct cpio_newc_header *header; + unsigned int filesize; + unsigned int namesize; + unsigned int offset; + char *filename; + + header = DEVTREE_CPIO_BASE; + + uart_send_string("Filename: "); + + while (1) { + c = uart_recv(); + if (c == '\r' || c == '\n') { + uart_send_string("\n"); + + if (idx < 256) input[idx] = '\0'; + else input[255] = '\0'; + + break; + } else { + uart_send(c); + input[idx++] = c; + } + } + + while (1) { + + filename = ((void*)header) + sizeof(struct cpio_newc_header); + + if (stringncmp((char*)header, CPIO_HEADER_MAGIC, 6) != 0) { + uart_send_string("invalid magic\n"); + break; + } + if (stringncmp(filename, CPIO_FOOTER_MAGIC, 11) == 0) { + uart_send_string("file does not exist!\n"); + break; + } + + namesize = hexstr_to_uint(header->c_namesize, 8); + + offset = sizeof(struct cpio_newc_header) + namesize; + if (offset % 4 != 0) + offset = ((offset/4) + 1) * 4; + + filesize = hexstr_to_uint(header->c_filesize, 8); + + if (stringncmp(filename, input, namesize) == 0) { + + char *content = ((void*)header) + offset; + + for (int i=0; ic_namesize, 8); + + offset = sizeof(struct cpio_newc_header) + namesize; + if (offset % 4 != 0) + offset = ((offset/4) + 1) * 4; + + filesize = hexstr_to_uint(header->c_filesize, 8); + + if (stringncmp(filename, input, namesize) == 0) { + + char *code_loc = ((void*)header) + offset; + unsigned int sp_val = 0x600000; + asm volatile( + "msr elr_el1, %0\n\t" + "msr spsr_el1, xzr\n\t" + "msr sp_el0, %1\n\t" + "eret\n\t" + :: + "r" (code_loc), + "r" (sp_val) + ); + + break; + } + + if (filesize % 4 != 0) + filesize = ((filesize/4) + 1) * 4; + + offset = offset + filesize; + + header = ((void*)header) + offset; + + } + +} diff --git a/lab6/lib/devtree.c b/lab6/lib/devtree.c new file mode 100644 index 000000000..18d763c21 --- /dev/null +++ b/lab6/lib/devtree.c @@ -0,0 +1,92 @@ +#include "devtree.h" +#include "mini_uart.h" +#include "string.h" + +static void *DEVTREE_ADDRESS = 0; + +unsigned int to_lendian(unsigned int n) { + return ((n>>24)&0x000000FF) | + ((n>>8) &0x0000FF00) | + ((n<<8) &0x00FF0000) | + ((n<<24)&0xFF000000) ; +} + +void devtree_getaddr() { + + asm volatile("MOV %0, x20" : "=r"(DEVTREE_ADDRESS)); + + char magic[4] = {0xd0, 0x0d, 0xfe, 0xed}; + if(stringncmp((char*)DEVTREE_ADDRESS, magic, 4) != 0) { + uart_send_string("magic failed\n"); + } else { + uart_send_string("devtree magic succeed\n"); + } + +} + +void fdt_traverse( void (*callback)(char *, char *, struct fdt_prop *) ) { + + char magic[4] = {0xd0, 0x0d, 0xfe, 0xed}; + struct fdt_header *devtree_header = DEVTREE_ADDRESS; + + if(stringncmp((char*)devtree_header, magic, 4) != 0) { + uart_send_string("devtree magic failed\n"); + return; + } + + void *dt_struct_addr = DEVTREE_ADDRESS + to_lendian(devtree_header->off_dt_struct); + char *dt_string_addr = DEVTREE_ADDRESS + to_lendian(devtree_header->off_dt_strings); + + char *node_name; + char *prop_name; + unsigned int token; + unsigned int off; + + while (1) { + + token = to_lendian(*((unsigned int *)dt_struct_addr)); + + if (token == FDT_BEGIN_NODE) { + + node_name = dt_struct_addr + 4; + off = 4 + strlen(node_name) + 1; + + if (off%4 != 0) + off = ((off/4)+1)*4; + + dt_struct_addr += off; + + } + else if (token == FDT_END_NODE) { + dt_struct_addr += 4; + } + else if (token == FDT_PROP) { + + struct fdt_prop *prop = (struct fdt_prop*)(dt_struct_addr + 4); + + off = 4 + 8 + to_lendian(prop->len); + if (off%4 != 0) + off = ((off/4)+1)*4; + + dt_struct_addr += off; + + prop_name = dt_string_addr + to_lendian(prop->nameoff); + + callback(node_name, prop_name, prop); + + } + else if (token == FDT_NOP) { + dt_struct_addr += 4; + } + else if (token == FDT_END) { + dt_struct_addr += 4; + break; + } + else { + uart_send_string("TOKEN NOT MATCHED\n"); + break; + } + + } + +} \ No newline at end of file diff --git a/lab6/lib/entry.S b/lab6/lib/entry.S new file mode 100644 index 000000000..e564be0fe --- /dev/null +++ b/lab6/lib/entry.S @@ -0,0 +1,195 @@ +#include "entry.h" +#include "syscall.h" + +.macro handle_invalid_entry el, type + kernel_entry \el + mov x0, #\type + mrs x1, esr_el1 + mrs x2, elr_el1 + bl show_invalid_entry_message + b err_hang +.endm + +.macro ventry label + .align 7 + b \label +.endm + +// save general registers to stack +.macro kernel_entry, el + sub sp, sp, #S_FRAME_SIZE + 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] + + .if \el == 0 + mrs x21, sp_el0 + .else + add x21, sp, #S_FRAME_SIZE + .endif + + mrs x22, elr_el1 + mrs x23, spsr_el1 + + stp x30, x21, [sp, 16 * 15] + stp x22, x23, [sp, 16 * 16] +.endm + +// load general registers from stack +.macro kernel_exit, el + ldp x22, x23, [sp, 16 * 16] + ldp x30, x21, [sp, 16 * 15] + + .if \el == 0 + msr sp_el0, x21 + .endif + + msr elr_el1, x22 + msr spsr_el1, x23 + + ldp x0, x1, [sp ,16 * 0] + ldp x2, x3, [sp ,16 * 1] + ldp x4, x5, [sp ,16 * 2] + ldp x6, x7, [sp ,16 * 3] + ldp x8, x9, [sp ,16 * 4] + ldp x10, x11, [sp ,16 * 5] + ldp x12, x13, [sp ,16 * 6] + ldp x14, x15, [sp ,16 * 7] + ldp x16, x17, [sp ,16 * 8] + ldp x18, x19, [sp ,16 * 9] + ldp x20, x21, [sp ,16 * 10] + ldp x22, x23, [sp ,16 * 11] + ldp x24, x25, [sp ,16 * 12] + ldp x26, x27, [sp ,16 * 13] + ldp x28, x29, [sp ,16 * 14] + add sp, sp, #S_FRAME_SIZE + eret +.endm + +.align 11 // vector table should be aligned to 0x800 +.globl exception_vector_table +exception_vector_table: + ventry sync_invalid_el1t // Synchronous EL1t + ventry irq_invalid_el1t // IRQ EL1t + ventry fiq_invalid_el1t // FIQ EL1t + ventry error_invalid_el1t // Error EL1t + + ventry sync_invalid_el1h // Synchronous EL1h + ventry el1_irq // IRQ EL1h + ventry fiq_invalid_el1h // FIQ EL1h + ventry error_invalid_el1h // Error EL1h + + ventry el0_sync // Synchronous 64-bit EL0 + ventry el0_irq // IRQ 64-bit EL0 + ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 + ventry error_invalid_el0_64 // Error 64-bit EL0 + + ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 + ventry irq_invalid_el0_32 // IRQ 32-bit EL0 + ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 + ventry error_invalid_el0_32 // Error 32-bit EL0 + +sync_invalid_el1t: + handle_invalid_entry 1, SYNC_INVALID_EL1t + +irq_invalid_el1t: + handle_invalid_entry 1, IRQ_INVALID_EL1t + +fiq_invalid_el1t: + handle_invalid_entry 1, FIQ_INVALID_EL1t + +error_invalid_el1t: + handle_invalid_entry 1, ERROR_INVALID_EL1t + +sync_invalid_el1h: + handle_invalid_entry 1, SYNC_INVALID_EL1h + +el1_irq: + kernel_entry 1 + bl handle_irq + kernel_exit 1 + +fiq_invalid_el1h: + handle_invalid_entry 1, FIQ_INVALID_EL1h + +error_invalid_el1h: + handle_invalid_entry 1, ERROR_INVALID_EL1h + +el0_sync: + kernel_entry 0 + mrs x25, esr_el1 + lsr x24, x25, #ESR_ELx_EC_SHIFT + cmp x24, #ESR_ELx_EC_SVC64 + b.eq el0_svc + handle_invalid_entry 0, SYNC_ERROR + +el0_irq: + kernel_entry 0 + bl handle_irq + kernel_exit 0 + +fiq_invalid_el0_64: + handle_invalid_entry 0, FIQ_INVALID_EL0_64 + +error_invalid_el0_64: + handle_invalid_entry 0, ERROR_INVALID_EL0_64 + +sync_invalid_el0_32: + handle_invalid_entry 0, SYNC_INVALID_EL0_32 + +irq_invalid_el0_32: + handle_invalid_entry 0, IRQ_INVALID_EL0_32 + +fiq_invalid_el0_32: + handle_invalid_entry 0, FIQ_INVALID_EL0_32 + +error_invalid_el0_32: + handle_invalid_entry 0, ERROR_INVALID_EL0_32 + +sc_nr .req x25 +scno .req x26 +stbl .req x27 + +el0_svc: + adr stbl, sys_call_table + uxtw scno, w8 + mov sc_nr, #__NR_SYSCALLS + bl enable_interrupt + cmp scno, sc_nr + b.hs ni_sys + + ldr x16, [stbl, scno, lsl #3] + blr x16 + b ret_from_syscall +ni_sys: + handle_invalid_entry 0, SYSCALL_ERROR +ret_from_syscall: + bl disable_interrupt + str x0, [sp, #S_X0] + kernel_exit 0 + +.globl ret_from_fork +ret_from_fork: + bl schedule_tail + cbz x19, ret_to_user + mov x0, x20 + blr x19 // in theory should not return + //b err_hang // hang fail-safe +ret_to_user: + bl disable_interrupt + kernel_exit 0 + +.globl err_hang +err_hang: b err_hang diff --git a/lab6/lib/exception.c b/lab6/lib/exception.c new file mode 100644 index 000000000..a820ba662 --- /dev/null +++ b/lab6/lib/exception.c @@ -0,0 +1,49 @@ +#include "exception.h" +#include "mini_uart.h" +#include "mailbox.h" +#include "utils.h" +#include "timer.h" +#include "peripherals/exception.h" +#include "peripherals/mini_uart.h" + +void enable_interrupt() {asm volatile("msr DAIFClr, 0xf");} +void disable_interrupt() {asm volatile("msr DAIFSet, 0xf");} + +const char *entry_error_messages[] = { + "SYNC_INVALID_EL1t", + "IRQ_INVALID_EL1t", + "FIQ_INVALID_EL1t", + "ERROR_INVALID_EL1t", + + "SYNC_INVALID_EL1h", + "IRQ_INVALID_EL1h", + "FIQ_INVALID_EL1h", + "ERROR_INVALID_EL1h", + + "SYNC_INVALID_EL0_64", + "IRQ_INVALID_EL0_64", + "FIQ_INVALID_EL0_64", + "ERROR_INVALID_EL0_64", + + "SYNC_INVALID_EL0_32", + "IRQ_INVALID_EL0_32", + "FIQ_INVALID_EL0_32", + "ERROR_INVALID_EL0_32", + + "SYNC_ERROR", + "SYSCALL_ERROR" +}; + +void show_invalid_entry_message(int type, unsigned long esr, unsigned long addr) { + printf("%s, ESR: 0x%x, address: 0x%x\n", entry_error_messages[type], esr, addr); +} + +void handle_irq() { + + if (get32(CORE0_INTERRUPT_SRC)&INTERRUPT_SOURCE_CNTPNSIRQ) { + handle_timer_irq(); + } else { + printf("unknown irq encountered"); + } + +} \ No newline at end of file diff --git a/lab6/lib/fork.c b/lab6/lib/fork.c new file mode 100644 index 000000000..5d8893126 --- /dev/null +++ b/lab6/lib/fork.c @@ -0,0 +1,83 @@ +#include "fork.h" +#include "mm.h" +#include "mini_uart.h" +#include "../include/sched.h" +#include "../include/entry.h" + +int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg, unsigned long stack) { + + preempt_disable(); + struct task_struct *p; + + p = (struct task_struct *) malloc(PAGE_SIZE); + if (p == NULL) + return -1; + + struct pt_regs *childregs = task_pt_regs(p); + memzero((unsigned long)childregs, sizeof(struct pt_regs)); + memzero((unsigned long)&p->cpu_context, sizeof(struct cpu_context)); + + if (clone_flags & PF_KTHREAD) { + p->cpu_context.x19 = fn; + p->cpu_context.x20 = arg; + } else { + struct pt_regs *cur_regs = task_pt_regs(current); + // *childregs = *cur_regs; (object file generates memcpy) + // therefore the for loop is used below + for(int i=0; iregs[0] = 0; // return value 0 + childregs->sp = stack + PAGE_SIZE; + p->stack = stack; + } + + p->flags = clone_flags; + p->priority = current->priority; + p->state = TASK_RUNNING; + p->counter = p->priority; + p->preempt_count = 1; + + p->cpu_context.pc = (unsigned long)ret_from_fork; + p->cpu_context.sp = (unsigned long)childregs; + + int pid = nr_tasks++; + task[pid] = p; + p->id = pid; + preempt_enable(); + + return pid; + +} + +int move_to_user_mode(unsigned long pc) { + + struct pt_regs *regs = task_pt_regs(current); + memzero((unsigned long)regs, sizeof(*regs)); + regs->pc = pc; + regs->pstate = PSR_MODE_EL0t; + unsigned long stack = (unsigned long)malloc(PAGE_SIZE); + + if (stack == NULL) + return -1; + + regs->sp = stack + PAGE_SIZE; + current->stack = stack; + return 0; + +} + +struct pt_regs *task_pt_regs(struct task_struct *tsk) { + + unsigned long p = (unsigned long)tsk + THREAD_SIZE - sizeof(struct pt_regs); + return (struct pt_regs *)p; + +} + +void new_user_process(unsigned long func){ + printf("Kernel process started, moving to user mode.\n"); + int err = move_to_user_mode(func); + if (err < 0){ + printf("Error while moving process to user mode\n\r"); + } +} \ No newline at end of file diff --git a/lab6/lib/mailbox.c b/lab6/lib/mailbox.c new file mode 100644 index 000000000..e593af037 --- /dev/null +++ b/lab6/lib/mailbox.c @@ -0,0 +1,49 @@ +#include "peripherals/mailbox.h" +#include "mailbox.h" +#include "mini_uart.h" + +int mailbox_call (volatile unsigned int *mailbox) { + unsigned int msg = ((unsigned long)mailbox & ~0xF) | (0x8 & 0xF); + while (*MAILBOX_STATUS & MAILBOX_FULL) ; + *MAILBOX_WRITE = msg; + while (1) { + while (*MAILBOX_STATUS & MAILBOX_EMPTY) {} + if (msg == *MAILBOX_READ) { + return mailbox[1]; + } + } +} + +void get_board_revision () +{ + volatile unsigned int __attribute__((aligned(16))) mailbox[7]; + mailbox[0] = 7 * 4; + mailbox[1] = REQUEST_CODE; + mailbox[2] = GET_BOARD_REVISION; + mailbox[3] = 4; + mailbox[4] = TAG_REQUEST_CODE; + mailbox[5] = 0; + mailbox[6] = END_TAG; + + if (mailbox_call(mailbox) == REQUEST_SUCCEED) { + printf("Board Revision:\t\t%x\n", mailbox[5]); + } +} + +void get_arm_memory () +{ + volatile unsigned int __attribute__((aligned(16))) mailbox[8]; + mailbox[0] = 8 * 4; + mailbox[1] = REQUEST_CODE; + mailbox[2] = GET_ARM_MEMORY; + mailbox[3] = 8; + mailbox[4] = TAG_REQUEST_CODE; + mailbox[5] = 0; + mailbox[6] = 0; + mailbox[7] = END_TAG; + + if (mailbox_call(mailbox) == REQUEST_SUCCEED) { + printf("Memory Base Addresss:\t%x\n", mailbox[5]); + printf("Memory Size:\t\t%x\n", mailbox[6]); + } +} \ No newline at end of file diff --git a/lab6/lib/math.c b/lab6/lib/math.c new file mode 100644 index 000000000..6b6919044 --- /dev/null +++ b/lab6/lib/math.c @@ -0,0 +1,19 @@ +#include "math.h" + +int log(int n, int base) { + int x = 1; + int ret = 0; + while (x <= n) { + x *= base; + ret++; + } + return ret; +} + +int pow(int base, int pow) { + int ret = 1; + for (int i=0; i avail) { + uart_send_string("not enough memory\n"); + } else { + __heap_ptr += size; + avail -= size; + } + + return ptr; + +} \ No newline at end of file diff --git a/lab6/lib/mini_uart.c b/lab6/lib/mini_uart.c new file mode 100644 index 000000000..c209f3a90 --- /dev/null +++ b/lab6/lib/mini_uart.c @@ -0,0 +1,69 @@ +#include "utils.h" +#include "printf.h" +#include "peripherals/mini_uart.h" +#include "peripherals/gpio.h" +#include "peripherals/exception.h" + +void uart_send ( char c ) +{ + if (c == '\n') uart_send('\r'); + + while (1) { + if (get32(AUX_MU_LSR_REG)&0x20) + break; + } + + put32(AUX_MU_IO_REG,c); +} + +char uart_recv ( void ) +{ + while(1) { + if (get32(AUX_MU_LSR_REG)&0x01) + break; + } + return (get32(AUX_MU_IO_REG)&0xFF); +} + +void uart_send_string ( char* str ) +{ + for (int i = 0; str[i] != '\0'; i++) { + uart_send((char)str[i]); + } +} + +void printf(char *fmt, ...) { + char temp[128]; + __builtin_va_list args; + __builtin_va_start(args, fmt); + vsprintf(temp,fmt,args); + uart_send_string(temp); +} + +void uart_init ( void ) +{ + unsigned int selector; + + selector = get32(GPFSEL1); + selector &= ~(7<<12); // clean gpio14 + selector |= 2<<12; // set alt5 for gpio14 + selector &= ~(7<<15); // clean gpio15 + selector |= 2<<15; // set alt5 for gpio 15 + put32(GPFSEL1,selector); + + put32(GPPUD,0); + delay(150); + put32(GPPUDCLK0,(1<<14)|(1<<15)); + delay(150); + put32(GPPUDCLK0,0); + + put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) + put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) + put32(AUX_MU_IER_REG,0); //Enable receive and disable transmit interrupts + put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode + put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high + put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 + put32(AUX_MU_IIR_REG,6); //Interrupt identify no fifo + put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver + +} diff --git a/lab6/lib/mm.S b/lab6/lib/mm.S new file mode 100644 index 000000000..263de4251 --- /dev/null +++ b/lab6/lib/mm.S @@ -0,0 +1,6 @@ +.globl memzero +memzero: + str xzr, [x0], #8 + subs x1, x1, #8 + b.gt memzero + ret \ No newline at end of file diff --git a/lab6/lib/mm.c b/lab6/lib/mm.c new file mode 100644 index 000000000..05a5f6f50 --- /dev/null +++ b/lab6/lib/mm.c @@ -0,0 +1,376 @@ +#include "mm.h" +#include "math.h" +#include "memory.h" +#include "mini_uart.h" + +static unsigned int n_frames = 0; +static unsigned int max_size = 0; +static struct frame* frame_list[MAX_ORDER] = {NULL}; +static struct frame frame_array[(MEM_REGION_END-MEM_REGION_BEGIN)/PAGE_SIZE]; +static struct dynamic_pool pools[MAX_POOLS] = { {ALLOCABLE, 0, 0, 0, 0, {NULL}, NULL} }; +static unsigned int reserved_num = 0; +static void* reserved_se[MAX_RESERVABLE][2] = {{0x0, 0x0}}; // expects to be sorted and addresses [,) +extern char __kernel_end; +static char *__kernel_end_ptr = &__kernel_end; + +void *malloc(unsigned int size) { + + if (size > max_size) { + printf("[error] Request exceeded allocable continuous size %d.\n", (int)max_size); + return NULL; + } + + int req_order = 0; + for(unsigned int i=PAGE_SIZE; i= MAX_ORDER) { + printf("[error] No memory allocable.\n"); + return NULL; + } + + while (t != req_order) { + struct frame* l_tmp = frame_list[t]; + frame_list[t] = l_tmp->next; + frame_list[t]->prev = NULL; + //printf("[info] Split at order %d, new head is 0x%x.\n", t+1, frame_list[t]); + + unsigned int off = pow(2, l_tmp->val-1); + struct frame* r_tmp = &frame_array[l_tmp->index+off]; + + l_tmp->val -= 1; + l_tmp->state = ALLOCABLE; + l_tmp->prev = NULL; + l_tmp->next = r_tmp; + + r_tmp->val = l_tmp->val; + r_tmp->state = ALLOCABLE; + r_tmp->prev = l_tmp; + r_tmp->next = NULL; + + t--; + if (frame_list[t] != NULL) + frame_list[t]->prev = r_tmp; + r_tmp->next = frame_list[t]; + frame_list[t] = l_tmp; + } + + struct frame* ret = frame_list[req_order]; + frame_list[req_order] = ret->next; + frame_list[req_order]->prev = NULL; + + ret->val = ret->val; + ret->state = ALLOCATED; + ret->prev = NULL; + ret->next = NULL; + + //printf("[info] allocated address: 0x%x\n", MEM_REGION_BEGIN+PAGE_SIZE*ret->index); + + return (void*)MEM_REGION_BEGIN+PAGE_SIZE*ret->index; + +} + +void free(void *address) { + + unsigned int idx = ((unsigned long long)address-MEM_REGION_BEGIN) / PAGE_SIZE; + struct frame* target = &frame_array[idx]; + + if (target->state == ALLOCABLE || target->state == C_NALLOCABLE) { + printf("[error] invalid free of already freed memory.\n"); + return; + } + //printf("=========================================================\n"); + //printf("[info] Now freeing address 0x%x with frame index %d.\n", address, (int)idx); + + for (int i=target->val; ival+1, fr_buddy->state); + + if (i < MAX_ORDER-1 && fr_buddy->state == ALLOCABLE && i== fr_buddy->val) { + + //printf("[info] Merging from order %d. Frame indices %d, %d.\n", i+1, (int)buddy, (int)idx); + + if (fr_buddy->prev != NULL) { + fr_buddy->prev->next = fr_buddy->next; + } else { + frame_list[fr_buddy->val] = fr_buddy->next; + } + + if (fr_buddy->next != NULL) { + fr_buddy->next->prev = fr_buddy->prev; + } + + fr_buddy->prev = NULL; + fr_buddy->next = NULL; + fr_buddy->val = C_NALLOCABLE; + fr_buddy->state = C_NALLOCABLE; + target->val = C_NALLOCABLE; + target->state = C_NALLOCABLE; + + if (fr_buddy->index < target->index) { + idx = fr_buddy->index; + target = fr_buddy; + } + + //printf("[info] Frame index of next merge target is %d.\n", (int)idx); + + } else { + + target->val = i; + target->state = ALLOCABLE; + target->prev = NULL; + target->next = frame_list[i]; + if (frame_list[i] != NULL) + frame_list[i]->prev = target; + frame_list[i] = target; + //printf("[info] Frame index %d pushed to frame list of order %d.\n", + // (int)target->index, (int)i+1); + break; + + } + + } + + //printf("[info] Free finished.\n"); + /*for (int i=0; i < MAX_ORDER; i++) { + if (frame_list[i] != NULL) + printf("[info] Head of order %d has frame array index %d.\n",i+1,frame_list[i]->index); + else + printf("[info] Head of order %d has frame array index null.\n",i+1); + }*/ + +} + +void init_mm() { + + n_frames = (MEM_REGION_END-MEM_REGION_BEGIN) / PAGE_SIZE; + unsigned int mul = (unsigned int)pow(2, MAX_ORDER-1); + printf("[info] Frame array start address 0x%x.\n", frame_array); + for (unsigned int i=0; ichunk_size = size; + pool->chunks_per_page = PAGE_SIZE / size; + pool->chunks_allocated = 0; + pool->page_new_chunk_off = 0; + pool->pages_used = 0; + pool->free_head = NULL; +} + +int register_chunk(unsigned int size) { + + unsigned int nsize = 0; + if (size <= 8) nsize = 8; + else { + int rem = size % 4; + if (rem != 0) nsize = (size/4 + 1)*4; + else nsize = size; + } + + if (nsize >= PAGE_SIZE) { + printf("[error] Normalized chunk size request leq page size.\n"); + return -1; + } + + for (int i=0; ifree_head != NULL) { + void *ret = (void*) pool->free_head; + pool->free_head = pool->free_head->next; + //printf("[info] allocate address 0x%x from pool free list.\n", ret); + return ret; + } + + if (pool->chunks_allocated >= MAX_POOL_PAGES*pool->chunks_per_page) { + //printf("[error] Pool maximum reached.\n"); + return NULL; + } + + + if (pool->chunks_allocated >= pool->pages_used*pool->chunks_per_page) { + pool->page_base_addrs[pool->pages_used] = malloc(PAGE_SIZE); + //printf("[info] allocate new page for pool with base address 0x%x.\n", + // pool->page_base_addrs[pool->pages_used]); + pool->pages_used++; + pool->page_new_chunk_off = 0; + } + + void *ret = pool->page_base_addrs[pool->pages_used - 1] + + pool->chunk_size*pool->page_new_chunk_off; + pool->page_new_chunk_off++; + pool->chunks_allocated++; + + //printf("[info] allocate new address 0x%x from pool.\n", ret); + + return ret; + +} + +void chunk_free(void *address) { + + int target = -1; + + void *prefix_addr = (void *)((unsigned long long)address & ~0xFFF); + + for (unsigned int i=0; ifree_head; + pool->free_head = (struct node*) address; + pool->free_head->next = old_head; + pool->chunks_allocated--; + +} + +void memory_reserve(void* start, void* end) { + if (reserved_num >= MAX_RESERVABLE) { + printf("[error] Max reservable locations already reached.\n"); + return; + } + reserved_se[reserved_num][0] = start; + reserved_se[reserved_num][1] = end; + reserved_num++; +} + +void init_mm_reserve() { + + max_size = PAGE_SIZE * pow(2, MAX_ORDER-1); + n_frames = (MEM_REGION_END-MEM_REGION_BEGIN) / PAGE_SIZE; + + memory_reserve((void*)0x0, __kernel_end_ptr); // spin tables, kernel image + memory_reserve((void*)0x20000000, (void*)0x20010000); // hard code reserve initramfs + + for (unsigned int i=0; i= reserved_se[i][0] && addr < reserved_se[i][1]) { + frame_array[j].state = RESERVED; + } + if (addr >= reserved_se[i][1]) break; + } + } + + for (int i=0; istate == RESERVED) continue; + if (target->state == C_NALLOCABLE) continue; + + for (int i=target->val; istate == ALLOCABLE && i== fr_buddy->val) { + + if (fr_buddy->prev != NULL) { + fr_buddy->prev->next = fr_buddy->next; + } else { + frame_list[fr_buddy->val] = fr_buddy->next; + } + + if (fr_buddy->next != NULL) { + fr_buddy->next->prev = fr_buddy->prev; + } + + fr_buddy->prev = NULL; + fr_buddy->next = NULL; + fr_buddy->val = C_NALLOCABLE; + fr_buddy->state = C_NALLOCABLE; + target->val = C_NALLOCABLE; + target->state = C_NALLOCABLE; + + if (fr_buddy->index < target->index) { + idx = fr_buddy->index; + target = fr_buddy; + } + + } else { + + target->val = i; + target->state = ALLOCABLE; + target->prev = NULL; + target->next = frame_list[i]; + if (frame_list[i] != NULL) + frame_list[i]->prev = target; + frame_list[i] = target; + break; + + } + + } + + } + +} \ No newline at end of file diff --git a/lab6/lib/printf.c b/lab6/lib/printf.c new file mode 100644 index 000000000..794a37dff --- /dev/null +++ b/lab6/lib/printf.c @@ -0,0 +1,152 @@ +/* + * 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 "printf.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) { + // 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_start(args, fmt): "..." is pointed by args + //__builtin_va_arg(args,int): ret=(int)*args;args++;return ret; + __builtin_va_list args; + __builtin_va_start(args, fmt); + return vsprintf(dst,fmt,args); +} \ No newline at end of file diff --git a/lab6/lib/reboot.c b/lab6/lib/reboot.c new file mode 100644 index 000000000..043a09fb7 --- /dev/null +++ b/lab6/lib/reboot.c @@ -0,0 +1,18 @@ +#define PM_PASSWORD 0x5a000000 +#define PM_RSTC 0x3F10001c +#define PM_WDOG 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 +} diff --git a/lab6/lib/sched.S b/lab6/lib/sched.S new file mode 100644 index 000000000..03f9c56b1 --- /dev/null +++ b/lab6/lib/sched.S @@ -0,0 +1,24 @@ +#include "sched.h" + +.global cpu_switch_to +cpu_switch_to: + mov x10, #THREAD_CPU_CONTEXT + add x8, x0, x10 + mov x9, sp + stp x19, x20, [x8], #16 // store callee-saved registers + stp x21, x22, [x8], #16 + stp x23, x24, [x8], #16 + stp x25, x26, [x8], #16 + stp x27, x28, [x8], #16 + stp x29, x9, [x8], #16 + str x30, [x8] + add x8, x1, x10 + ldp x19, x20, [x8], #16 // restore callee-saved registers + ldp x21, x22, [x8], #16 + ldp x23, x24, [x8], #16 + ldp x25, x26, [x8], #16 + ldp x27, x28, [x8], #16 + ldp x29, x9, [x8], #16 + ldr x30, [x8] + mov sp, x9 + ret diff --git a/lab6/lib/sched.c b/lab6/lib/sched.c new file mode 100644 index 000000000..2ffc32acd --- /dev/null +++ b/lab6/lib/sched.c @@ -0,0 +1,107 @@ +#include "../include/sched.h" +#include "mm.h" +#include "exception.h" +#include "mini_uart.h" + +static struct task_struct init_task = INIT_TASK; +struct task_struct *current = &(init_task); +struct task_struct *task[NR_TASKS] = {&(init_task), }; +int nr_tasks = 1; + +void preempt_disable() { + current->preempt_count++; +} + +void preempt_enable() { + current->preempt_count--; +} + +void _schedule() { + + int next, c; + struct task_struct *p; + while (1) { + c = -1; + next = 0; + for (int i=0; istate == TASK_RUNNING && p->counter > c) { + c = p->counter; + next = i; + } + } + if (c) { + break; + } + for (int i=0; icounter = (p->counter >> 1) + p->priority; + } + } + preempt_disable(); // should be fine, if anything breaks move this to the top + switch_to(task[next]); + preempt_enable(); + +} + +void schedule() { + current->counter = 0; + _schedule(); +} + +void switch_to(struct task_struct *next) { + if (current == next) + return; + struct task_struct *prev = current; + current = next; + cpu_switch_to(prev, next); +} + +void schedule_tail() { + preempt_enable(); +} + +void timer_tick() { + + --current->counter; + if (current->counter > 0 || current->preempt_count > 0) + return; + + current->counter = 0; + enable_interrupt(); + _schedule(); + disable_interrupt(); + +} + +void exit_process() { + // should only be accessed using syscall + // preempt_disable(); + current->state = TASK_ZOMBIE; + free((void*)current->stack); + // preempt_enable(); + schedule(); +} + +void kill_zombies() { + + struct task_struct *p; + for (int i=0; istate == TASK_ZOMBIE) { + printf("Zombie found with pid: %d.\n", p->id); + free(p); + task[i] = NULL; + } + + } + +} \ No newline at end of file diff --git a/lab6/lib/shell.c b/lab6/lib/shell.c new file mode 100644 index 000000000..75a630b9e --- /dev/null +++ b/lab6/lib/shell.c @@ -0,0 +1,223 @@ +#include "shell.h" +#include "mini_uart.h" +#include "utils.h" +#include "mailbox.h" +#include "reboot.h" +#include "string.h" +#include "../include/cpio.h" +#include "memory.h" +#include "timer.h" +#include "exception.h" +#include "math.h" +#include "mm.h" +#include "../include/sched.h" +#include "syscall.h" +#include "peripherals/mailbox.h" +#include "fork.h" + + +#define MAX_BUFFER_SIZE 256u + +static char buffer[MAX_BUFFER_SIZE]; +static int shared = 1; + +void foo() { + for(int i = 0; i < 10; ++i) { + printf("Thread id: %d %d\n", current->id, i); + delay(1000000); + schedule(); + } + + current->state = TASK_ZOMBIE; + while(1); +} + +void user_foo() { + + printf("User thread id: %d\n", getpid()); + + volatile unsigned int __attribute__((aligned(16))) mailbox[7]; + mailbox[0] = 7 * 4; + mailbox[1] = REQUEST_CODE; + mailbox[2] = GET_BOARD_REVISION; + mailbox[3] = 4; + mailbox[4] = TAG_REQUEST_CODE; + mailbox[5] = 0; + mailbox[6] = END_TAG; + mbox_call(0x8, mailbox); + printf("Board Revision:\t\t%x\n", mailbox[5]); + + int pid = fork(); + if (pid == 0) { + printf("Child says hello!\n"); + while(1) { + printf("Please don't kill me :(\n"); + shared++; + } + } else if (pid > 0) { + printf("Parent says, \"My child has pid %d\"\n", pid); + printf("Shared? %d\n", shared); + delay(10000000); + printf("Kill my own child :(\n"); + kill(pid); + delay(10000000); + printf("shared %d\n", shared); + } + + //char buf[4] = {0}; + //uart_read(buf, 3); + //uart_write(buf, 3); + + exit(); + +} + +void start_video() { + // ... go to cpio, find location and size of syscall.img + // allocate pages + // move syscall.img to allocated memory + // preempt disable + // change this shell thread to not runnable + // start sycall.img user process + // preempt enable + struct cpio_newc_header *header; + unsigned int filesize; + unsigned int namesize; + unsigned int offset; + char *filename; + void *code_loc; + + header = DEVTREE_CPIO_BASE; + while (1) { + + filename = ((void*)header) + sizeof(struct cpio_newc_header); + + if (stringncmp((char*)header, CPIO_HEADER_MAGIC, 6) != 0) { + uart_send_string("invalid magic\n"); + break; + } + if (stringncmp(filename, CPIO_FOOTER_MAGIC, 11) == 0) { + uart_send_string("file does not exist!\n"); + break; + } + + namesize = hexstr_to_uint(header->c_namesize, 8); + + offset = sizeof(struct cpio_newc_header) + namesize; + if (offset % 4 != 0) + offset = ((offset/4) + 1) * 4; + + filesize = hexstr_to_uint(header->c_filesize, 8); + + if (stringncmp(filename, "syscall.img", namesize) == 0) { + code_loc = ((void*)header) + offset; + break; + } + + if (filesize % 4 != 0) + filesize = ((filesize/4) + 1) * 4; + + offset = offset + filesize; + + header = ((void*)header) + offset; + + } + printf("syscall.img found in cpio at location 0x%x.\n", code_loc); + printf("syscall.img has size of %d bytes.\n", (int)filesize); + + void *move_loc = malloc(filesize + 4096); // an extra page for bss just in case + if(move_loc == NULL) return; + for (int i=0; istate = TASK_STOPPED; + unsigned long long tmp; + asm volatile("mrs %0, cntkctl_el1" : "=r"(tmp)); + tmp |= 1; + asm volatile("msr cntkctl_el1, %0" : : "r"(tmp)); + copy_process(PF_KTHREAD, (unsigned long)&new_user_process, (unsigned long)move_loc, 0); + preempt_enable(); + +} + +void read_cmd() +{ + unsigned int idx = 0; + char c = '\0'; + + while (1) { + c = uart_recv(); + if (c == '\r' || c == '\n') { + uart_send_string("\n"); + + if (idx < MAX_BUFFER_SIZE) buffer[idx] = '\0'; + else buffer[MAX_BUFFER_SIZE-1] = '\0'; + + break; + } else { + uart_send(c); + buffer[idx++] = c; + } + } + +} + +void parse_cmd() +{ + + if (stringcmp(buffer, "\0") == 0) + uart_send_string("\n"); + else if (stringcmp(buffer, "hello") == 0) + uart_send_string("Hello World!\n"); + else if (stringcmp(buffer, "reboot") == 0) { + uart_send_string("rebooting...\n"); + reset(100); + } + else if (stringcmp(buffer, "hwinfo") == 0) { + get_board_revision(); + get_arm_memory(); + } + else if (stringcmp(buffer, "ls") == 0) { + cpio_ls(); + } + else if (stringcmp(buffer, "cat") == 0) { + cpio_cat(); + } + else if (stringcmp(buffer, "execute") == 0) { + cpio_exec(); + } + else if (stringcmp(buffer, "thread_test") == 0) { + for (int i=0; i<10; i++) { + copy_process(PF_KTHREAD, (unsigned long)&foo, 0, 0); + } + } + else if (stringcmp(buffer, "to_user") == 0) { + copy_process(PF_KTHREAD, (unsigned long)&new_user_process, (unsigned long)&user_foo, 0); + } + else if (stringcmp(buffer, "video") == 0) { + start_video(); + } + else if (stringcmp(buffer, "help") == 0) { + uart_send_string("help:\t\tprint list of available commands\n"); + uart_send_string("hello:\t\tprint Hello World!\n"); + uart_send_string("reboot:\t\treboot device\n"); + uart_send_string("hwinfo:\t\tprint hardware information\n"); + uart_send_string("ls:\t\tlist initramfs files\n"); + uart_send_string("cat:\t\tprint file content in initramfs\n"); + uart_send_string("execute:\trun program from cpio\n"); + } + else + uart_send_string("Command not found! Type help for commands.\n"); + +} + +void shell_loop() +{ + while (1) { + uart_send_string("% "); + read_cmd(); + parse_cmd(); + } +} \ No newline at end of file diff --git a/lab6/lib/string.c b/lab6/lib/string.c new file mode 100644 index 000000000..df9ea89ca --- /dev/null +++ b/lab6/lib/string.c @@ -0,0 +1,39 @@ +#include "string.h" + +int stringcmp(const char *p1, const char *p2) +{ + const unsigned char *s1 = (const unsigned char *) p1; + const unsigned char *s2 = (const unsigned char *) p2; + unsigned char c1, c2; + + do { + c1 = (unsigned char) *s1++; + c2 = (unsigned char) *s2++; + if (c1 == '\0') + return c1 - c2; + } while (c1 == c2); + + return c1 - c2; +} + +int stringncmp(const char *p1, const char *p2, unsigned int n) +{ + for (int i=0; iid; +} + +unsigned sys_uartread(char buf[], unsigned size) { + for(unsigned int i=0; ic_namesize, 8); + + offset = sizeof(struct cpio_newc_header) + namesize; + if (offset % 4 != 0) + offset = ((offset/4) + 1) * 4; + + filesize = hexstr_to_uint(header->c_filesize, 8); + + if (stringncmp(filename, name, namesize) == 0) { + code_loc = ((void*)header) + offset; + break; + } + + if (filesize % 4 != 0) + filesize = ((filesize/4) + 1) * 4; + + offset = offset + filesize; + + header = ((void*)header) + offset; + + } + + void *move_loc = malloc(filesize + 4096); // an extra page for bss just in case + if(move_loc == NULL) return -1; + for (int i=0; ipc = (unsigned long)move_loc; // move to beginning of program + p->sp = current->stack+PAGE_SIZE; + + preempt_enable(); + + return -1; // only on failure +} + +int sys_fork() { + return copy_process(0, 0, 0, (unsigned long)malloc(4*4096)); +} + +void sys_exit(int status) { + exit_process(); +} + +int sys_mbox_call(unsigned char ch, unsigned int *mbox) { + unsigned int r = (((unsigned int)((unsigned long)mbox)&~0xF) | (ch&0xF)); + while(*MAILBOX_STATUS & MAILBOX_FULL); + *MAILBOX_WRITE = r; + while (1) { + while (*MAILBOX_STATUS & MAILBOX_EMPTY) {} + if (r == *MAILBOX_READ) { + return mbox[1]==REQUEST_SUCCEED; + } + } + return 0; +} + +void sys_kill(int pid) { + + struct task_struct *p; + for (int i=0; iid == (long)pid) { + preempt_disable(); + printf("Kill target acquired.\n"); + p->state = TASK_ZOMBIE; + free((void *)p->stack); + preempt_enable(); + break; + } + + } + +} + +void * const sys_call_table[] = +{ + sys_getpid, + sys_uartread, + sys_uartwrite, + sys_exec, + sys_fork, + sys_exit, + sys_mbox_call, + sys_kill +}; \ No newline at end of file diff --git a/lab6/lib/timer.c b/lab6/lib/timer.c new file mode 100644 index 000000000..ae102a2b0 --- /dev/null +++ b/lab6/lib/timer.c @@ -0,0 +1,62 @@ +#include "timer.h" +#include "../include/sched.h" +#include "mini_uart.h" + +void timer_init() { + core_timer_enable(); + set_timer(read_freq()); +} + +void handle_timer_irq() { + //printf("Timer interrupt.\n"); + set_timer(read_freq()>>5); + timer_tick(); +} + +void core_timer_enable() { + + asm volatile( + "mov x0, 1\n\t" + "msr cntp_ctl_el0, x0\n\t" // enable + "mov x0, 2\n\t" + "ldr x1, =0x40000040\n\t" // CORE0_TIMER_IRQ_CTRL + "str w0, [x1]\n\t" // unmask timer interrupt + ); + +} + +void core_timer_disable() { + + asm volatile( + "mov x0, 0\n\t" + "ldr x1, =0x40000040\n\t" + "str w0, [x1]\n\t" + ); + +} + +void set_timer(unsigned int rel_time) { + + asm volatile( + "msr cntp_tval_el0, %0\n\t" + : + : "r" (rel_time) + ); + +} + +unsigned int read_timer() { + + unsigned int time; + asm volatile("mrs %0, cntpct_el0\n\t" : "=r" (time) : : "memory"); + return time; + +} + +unsigned int read_freq() { + + unsigned int freq; + asm volatile("mrs %0, cntfrq_el0\n\t": "=r" (freq) : : "memory"); + return freq; + +} diff --git a/lab6/lib/utils.S b/lab6/lib/utils.S new file mode 100644 index 000000000..aa0e55aa9 --- /dev/null +++ b/lab6/lib/utils.S @@ -0,0 +1,15 @@ +.globl put32 +put32: + str w1,[x0] + ret + +.globl get32 +get32: + ldr w0,[x0] + ret + +.globl delay +delay: + subs x0, x0, #1 + bne delay + ret \ No newline at end of file diff --git a/lab6/send_img.py b/lab6/send_img.py new file mode 100644 index 000000000..b2261950d --- /dev/null +++ b/lab6/send_img.py @@ -0,0 +1,31 @@ +import argparse +import os +import time +import math +import serial + +parser = argparse.ArgumentParser(description='*.img uart sender') +parser.add_argument('-i', '--img', default='kernel8.img', type=str) +parser.add_argument('-d', '--device', default='/dev/ttyUSB0', type=str) +parser.add_argument('-b', '--baud', default=115200, type=int) + +args = parser.parse_args() + +img_size = os.path.getsize(args.img) + +with open(args.img, 'rb') as f: + with serial.Serial(args.device, args.baud) as tty: + + print(f'{args.img} is {img_size} bytes') + print('img file is now sending') + + tty.write(img_size.to_bytes(4, 'big')) + + input() + + for i in range(img_size): + tty.write(f.read(1)) + tty.flush() + #time.sleep(0.0001) + + print('img sent') From 0857d47189b538f5582070180ccf8599e625d84b Mon Sep 17 00:00:00 2001 From: Hsiang-Chieh Tsou Date: Tue, 17 May 2022 08:02:50 +0800 Subject: [PATCH 2/9] lab6 basic1 --- lab6/include/mmu.h | 30 +++++++++++++++++ lab6/include/peripherals/base.h | 5 ++- lab6/kernel/boot_kernel.S | 57 ++++++++++++++++++++++++++++++--- lab6/kernel/linker.ld | 7 ++-- 4 files changed, 91 insertions(+), 8 deletions(-) create mode 100644 lab6/include/mmu.h diff --git a/lab6/include/mmu.h b/lab6/include/mmu.h new file mode 100644 index 000000000..bab649e8d --- /dev/null +++ b/lab6/include/mmu.h @@ -0,0 +1,30 @@ +#ifndef _MMU_H +#define _MMU_H + +#define VA_START 0xffff000000000000 +#define VA_MASK 0x0000ffffffffffff + +#define TCR_CONFIG_REGION_48bit (((64 - 48) << 0) | ((64 - 48) << 16)) +#define TCR_CONFIG_4KB ((0b00 << 14) | (0b10 << 30)) +#define TCR_CONFIG_DEFAULT (TCR_CONFIG_REGION_48bit | TCR_CONFIG_4KB) + +#define MAIR_DEVICE_nGnRnE 0b00000000 +#define MAIR_NORMAL_NOCACHE 0b01000100 +#define MAIR_IDX_DEVICE_nGnRnE 0 +#define MAIR_IDX_NORMAL_NOCACHE 1 +#define MAIR_VALUE \ +( \ + (MAIR_DEVICE_nGnRnE << (MAIR_IDX_DEVICE_nGnRnE * 8)) | \ + (MAIR_NORMAL_NOCACHE << (MAIR_IDX_NORMAL_NOCACHE * 8)) \ +) + +#define PD_TABLE 0b11 +#define PD_BLOCK 0b01 +#define PD_ACCESS (1 << 10) +#define BOOT_PGD_ATTR PD_TABLE +#define BOOT_PUD_ATTR (PD_ACCESS | (MAIR_IDX_DEVICE_nGnRnE << 2) | PD_BLOCK) + +#define SCTLR_MMU_DISABLED 0 +#define SCTLR_MMU_ENABLED 1 + +#endif \ No newline at end of file diff --git a/lab6/include/peripherals/base.h b/lab6/include/peripherals/base.h index 8f66cd5fe..aeb57bd92 100644 --- a/lab6/include/peripherals/base.h +++ b/lab6/include/peripherals/base.h @@ -1,6 +1,9 @@ #ifndef _P_BASE_H #define _P_BASE_H -#define PBASE 0x3F000000 +#include "mmu.h" + +#define DEVICE_BASE 0x3F000000 +#define PBASE (VA_START+DEVICE_BASE) #endif /*_P_BASE_H */ \ No newline at end of file diff --git a/lab6/kernel/boot_kernel.S b/lab6/kernel/boot_kernel.S index 649bc4c69..6f9950f8c 100644 --- a/lab6/kernel/boot_kernel.S +++ b/lab6/kernel/boot_kernel.S @@ -1,3 +1,6 @@ +#include "mmu.h" +#include "peripherals/base.h" + .section ".text.boot" .globl _start @@ -9,19 +12,32 @@ _start: bl set_exception_vector_table master: + mov x2, #VA_START ldr x0, =__bss_begin + sub x0, x0, x2 ldr x1, =__bss_end + sub x1, x1, x2 sub x1, x1, x0 memzero: - cbz x1, movesp + cbz x1, mmu str xzr, [x0], #8 subs x1, x1, #8 cbnz x1, memzero -movesp: - mov sp, #0x80000 - bl kernel_main +mmu: + mov x0, #VA_START + add sp, x0, #0x80000 + + bl set_tcr + bl set_mair + bl make_page_tables + + mov x0, #SCTLR_MMU_ENABLED + msr sctlr_el1, x0 + + ldr x0, =kernel_main + br x0 proc_hang: wfe @@ -38,4 +54,35 @@ from_el2_to_el1: mov x0, 0x3c5 // EL1h (SPSel = 1) with interrupt disabled msr spsr_el2, x0 msr elr_el2, lr - eret // return to EL1 \ No newline at end of file + eret // return to EL1 + +set_tcr: + ldr x0, =TCR_CONFIG_DEFAULT + msr tcr_el1, x0 + ret + +set_mair: + ldr x0, =(MAIR_VALUE) + msr mair_el1, x0 + ret + +make_page_tables: + ldr x0, =pg_dir + and x0, x0, #VA_MASK + add x1, x0, 0x1000 + + ldr x2, =BOOT_PGD_ATTR + orr x2, x1, x2 + str x2, [x0] + + ldr x2, =BOOT_PUD_ATTR + mov x3, 0x00000000 + orr x3, x2, x3 + str x3, [x1] + mov x3, 0x40000000 + orr x3, x2, x3 + str x3, [x1, 8] + + msr ttbr0_el1, x0 + msr ttbr1_el1, x0 + ret \ No newline at end of file diff --git a/lab6/kernel/linker.ld b/lab6/kernel/linker.ld index 7ae6ad8c6..48804b7fb 100644 --- a/lab6/kernel/linker.ld +++ b/lab6/kernel/linker.ld @@ -1,6 +1,7 @@ SECTIONS { - . = 0x80000; + . = 0xffff000000000000; + . += 0x80000; .text.boot : { *(.text.boot) } .text : { *(.text) } @@ -13,7 +14,9 @@ SECTIONS .bss : { *(.bss* .bss.*) } __bss_end = .; - . = ALIGN(16); + . = ALIGN(0x00001000); + pg_dir = .; + .data.pgd : { . += (3 * (1 << 12)); } __heap_start = .; __kernel_end = .; From 433da8fec2730db4a4a2ea84030695b280ac5345 Mon Sep 17 00:00:00 2001 From: Hsiang-Chieh Tsou Date: Thu, 19 May 2022 21:29:42 +0800 Subject: [PATCH 3/9] lab 6 basic complete --- lab6/include/devtree.h | 2 +- lab6/include/fork.h | 5 +- lab6/include/mm.h | 6 ++ lab6/include/mmu.h | 21 +++++ lab6/include/peripherals/exception.h | 7 +- lab6/include/peripherals/mailbox.h | 4 +- lab6/include/sched.h | 22 ++++- lab6/include/syscall.h | 11 ++- lab6/kernel/boot_kernel.S | 4 +- lab6/kernel/kernel.c | 2 +- lab6/lib/cpio.c | 3 +- lab6/lib/devtree.c | 2 +- lab6/lib/fork.c | 125 +++++++++++++++++++++++---- lab6/lib/mm.S | 8 ++ lab6/lib/mm.c | 24 ++++- lab6/lib/mmu.c | 74 ++++++++++++++++ lab6/lib/sched.S | 8 ++ lab6/lib/sched.c | 13 ++- lab6/lib/shell.c | 96 +++----------------- lab6/lib/syscall.c | 28 +++--- 20 files changed, 335 insertions(+), 130 deletions(-) create mode 100644 lab6/lib/mmu.c diff --git a/lab6/include/devtree.h b/lab6/include/devtree.h index d8225b11c..1c3c27899 100644 --- a/lab6/include/devtree.h +++ b/lab6/include/devtree.h @@ -59,6 +59,6 @@ void devtree_getaddr (); void fdt_traverse ( void (*callback)(char *, char *, struct fdt_prop *) ); // ARM uses little endian -unsigned int to_lendian (unsigned int); +unsigned long to_lendian (unsigned long); #endif \ No newline at end of file diff --git a/lab6/include/fork.h b/lab6/include/fork.h index 7b018dc68..06bc62382 100644 --- a/lab6/include/fork.h +++ b/lab6/include/fork.h @@ -11,10 +11,11 @@ #define PSR_MODE_EL3t 0x0000000c #define PSR_MODE_EL3h 0x0000000d -int copy_process(unsigned long, unsigned long, unsigned long, unsigned long); -int move_to_user_mode(unsigned long); +int copy_process(unsigned long, unsigned long, unsigned long/*, unsigned long*/); +int move_to_user_mode(unsigned long, unsigned long, unsigned long); struct pt_regs *task_pt_regs(struct task_struct *); void new_user_process(unsigned long); +int copy_virt_memory(struct task_struct *); struct pt_regs { unsigned long regs[31]; diff --git a/lab6/include/mm.h b/lab6/include/mm.h index df97f2b89..f74bfd242 100644 --- a/lab6/include/mm.h +++ b/lab6/include/mm.h @@ -40,6 +40,11 @@ struct dynamic_pool { struct node *free_head; }; +#include "sched.h" + +unsigned long allocate_user_page(struct task_struct *, unsigned long); +unsigned long allocate_kernel_page(); + void *malloc(unsigned int); void free(void *); void init_mm(); @@ -50,6 +55,7 @@ void chunk_free(void *); void memory_reserve(void*, void*); void init_mm_reserve(); +void memcpy(unsigned long dst, unsigned long src, unsigned long n); void memzero(unsigned long, unsigned long); #endif /*_MM_H */ \ No newline at end of file diff --git a/lab6/include/mmu.h b/lab6/include/mmu.h index bab649e8d..4733595ef 100644 --- a/lab6/include/mmu.h +++ b/lab6/include/mmu.h @@ -4,6 +4,8 @@ #define VA_START 0xffff000000000000 #define VA_MASK 0x0000ffffffffffff +#define PAGE_MASK 0xfffffffffffff000 + #define TCR_CONFIG_REGION_48bit (((64 - 48) << 0) | ((64 - 48) << 16)) #define TCR_CONFIG_4KB ((0b00 << 14) | (0b10 << 30)) #define TCR_CONFIG_DEFAULT (TCR_CONFIG_REGION_48bit | TCR_CONFIG_4KB) @@ -20,11 +22,30 @@ #define PD_TABLE 0b11 #define PD_BLOCK 0b01 +#define PT_ENTRY 0b11 #define PD_ACCESS (1 << 10) #define BOOT_PGD_ATTR PD_TABLE #define BOOT_PUD_ATTR (PD_ACCESS | (MAIR_IDX_DEVICE_nGnRnE << 2) | PD_BLOCK) +#define PTE_USR_RO_PTE (PD_ACCESS | (0b11<<6) | (MAIR_IDX_NORMAL_NOCACHE<<2) | PT_ENTRY) +#define PTE_USR_RW_PTE (PD_ACCESS | (0b01<<6) | (MAIR_IDX_NORMAL_NOCACHE<<2) | PT_ENTRY) + #define SCTLR_MMU_DISABLED 0 #define SCTLR_MMU_ENABLED 1 +#define PGD_SHIFT (12 + 3*9) +#define PUD_SHIFT (12 + 2*9) +#define PMD_SHIFT (12 + 9) + +#ifndef __ASSEMBLER__ + +#include "sched.h" + +void map_page(struct task_struct *task, unsigned long va, unsigned long page); +unsigned long map_table(unsigned long *table, unsigned long shift, unsigned long va, int* new_table); +void map_table_entry(unsigned long *pte, unsigned long va, unsigned long pa); +unsigned long va2phys(unsigned long va); + +#endif + #endif \ No newline at end of file diff --git a/lab6/include/peripherals/exception.h b/lab6/include/peripherals/exception.h index bc64c7c53..713d05418 100644 --- a/lab6/include/peripherals/exception.h +++ b/lab6/include/peripherals/exception.h @@ -2,6 +2,7 @@ #define _P_EXCEPTION_H #include "peripherals/base.h" +#include "mmu.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) @@ -14,9 +15,9 @@ #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) -#define CNTPCT_EL0 (0x4000001C) -#define CNTP_CTL_EL0 (0x40000040) -#define CORE0_INTERRUPT_SRC (0x40000060) +#define CNTPCT_EL0 (VA_START + 0x4000001C) +#define CNTP_CTL_EL0 (VA_START + 0x40000040) +#define CORE0_INTERRUPT_SRC (VA_START + 0x40000060) #define IRQ_PENDING_1_AUX_INT (1<<29) #define INTERRUPT_SOURCE_GPU (1<<8) diff --git a/lab6/include/peripherals/mailbox.h b/lab6/include/peripherals/mailbox.h index 0351bd714..62225bd47 100644 --- a/lab6/include/peripherals/mailbox.h +++ b/lab6/include/peripherals/mailbox.h @@ -1,7 +1,9 @@ #ifndef _P_MAILBOX_H #define _P_MAILBOX_H -#define MMIO_BASE 0x3f000000 +#include "mmu.h" + +#define MMIO_BASE VA_START + 0x3f000000 #define MAILBOX_BASE MMIO_BASE + 0xb880 #define MAILBOX_READ (volatile unsigned int*) (MAILBOX_BASE) diff --git a/lab6/include/sched.h b/lab6/include/sched.h index a05e6ae25..1c0d56c0d 100644 --- a/lab6/include/sched.h +++ b/lab6/include/sched.h @@ -40,15 +40,31 @@ struct cpu_context { unsigned long pc; }; +#define MAX_PROCESS_PAGES 128 + +struct user_page { + unsigned long phys_addr; + unsigned long virt_addr; +}; + +struct mm_struct { + unsigned long pgd; + int user_pages_count; + struct user_page user_pages[MAX_PROCESS_PAGES]; + int kernel_pages_count; + unsigned long kernel_pages[MAX_PROCESS_PAGES]; +}; + struct task_struct { struct cpu_context cpu_context; long state; long counter; long priority; long preempt_count; - unsigned long stack; + //unsigned long stack; //remove unsigned long flags; long id; + struct mm_struct mm; }; extern void sched_init(); @@ -60,11 +76,13 @@ extern void switch_to(struct task_struct *); extern void cpu_switch_to(struct task_struct *, struct task_struct *); extern void exit_process(); extern void kill_zombies(); +extern void update_pgd(unsigned long); #define INIT_TASK \ { \ {0,0,0,0,0,0,0,0,0,0,0,0,0},\ -0, 0, 1, 0, 0, PF_KTHREAD, 0 \ +0, 0, 1, 0, PF_KTHREAD, 0, \ +{0,0,{{0}},0,{0}}\ } #endif diff --git a/lab6/include/syscall.h b/lab6/include/syscall.h index b738fcff9..a1c2215f5 100644 --- a/lab6/include/syscall.h +++ b/lab6/include/syscall.h @@ -14,13 +14,22 @@ #ifndef __ASSEMBLER__ +int getpid(); +unsigned uart_read(char buf[], unsigned size); +unsigned uart_write(const char buf[], unsigned size); +int exec(const char *name, char *const argv[]); +int fork(); +void exit(int status); +int mbox_call(unsigned char ch, volatile unsigned int *mbox); +void kill(int pid); + int sys_getpid(); unsigned sys_uartread(char buf[], unsigned size); unsigned sys_uartwrite(const char buf[], unsigned size); int sys_exec(const char *name, char *const argv[]); int sys_fork(); void sys_exit(int status); -int sys_mbox_call(unsigned char ch, unsigned int *mbox); +int sys_mbox_call(unsigned char ch, volatile unsigned int *mbox); void sys_kill(int pid); #endif diff --git a/lab6/kernel/boot_kernel.S b/lab6/kernel/boot_kernel.S index 6f9950f8c..27ccfa7f1 100644 --- a/lab6/kernel/boot_kernel.S +++ b/lab6/kernel/boot_kernel.S @@ -44,7 +44,7 @@ proc_hang: b proc_hang set_exception_vector_table: - adr x0, exception_vector_table + ldr x0, =exception_vector_table msr vbar_el1, x0 ret @@ -62,7 +62,7 @@ set_tcr: ret set_mair: - ldr x0, =(MAIR_VALUE) + ldr x0, =MAIR_VALUE msr mair_el1, x0 ret diff --git a/lab6/kernel/kernel.c b/lab6/kernel/kernel.c index fa49b2a6a..490719083 100644 --- a/lab6/kernel/kernel.c +++ b/lab6/kernel/kernel.c @@ -17,7 +17,7 @@ void kernel_main(void) enable_interrupt(); uart_send_string("OSDI 2022 Spring\n"); - copy_process(PF_KTHREAD, (unsigned long)&shell_loop, 0, 0); + copy_process(PF_KTHREAD, (unsigned long)&shell_loop, 0/*, 0*/); while (1) { kill_zombies(); diff --git a/lab6/lib/cpio.c b/lab6/lib/cpio.c index db3cc41fc..5e7d2cb6e 100644 --- a/lab6/lib/cpio.c +++ b/lab6/lib/cpio.c @@ -3,6 +3,7 @@ #include "../include/sched.h" #include "fork.h" #include "string.h" +#include "mmu.h" void *DEVTREE_CPIO_BASE = 0; @@ -28,7 +29,7 @@ void initramfs_callback(char *node_name, char *prop_name, struct fdt_prop *prop) if (stringncmp(node_name, "chosen", 7) == 0 && stringncmp(prop_name, "linux,initrd-start", 19) == 0) { - DEVTREE_CPIO_BASE = (void*)to_lendian(*((unsigned int*)(prop + 1))); + DEVTREE_CPIO_BASE = (void*)to_lendian(*((unsigned int*)(prop + 1))) + VA_START; } diff --git a/lab6/lib/devtree.c b/lab6/lib/devtree.c index 18d763c21..691dc1a7e 100644 --- a/lab6/lib/devtree.c +++ b/lab6/lib/devtree.c @@ -4,7 +4,7 @@ static void *DEVTREE_ADDRESS = 0; -unsigned int to_lendian(unsigned int n) { +unsigned long to_lendian(unsigned long n) { return ((n>>24)&0x000000FF) | ((n>>8) &0x0000FF00) | ((n<<8) &0x00FF0000) | diff --git a/lab6/lib/fork.c b/lab6/lib/fork.c index 5d8893126..acdf566d6 100644 --- a/lab6/lib/fork.c +++ b/lab6/lib/fork.c @@ -3,19 +3,19 @@ #include "mini_uart.h" #include "../include/sched.h" #include "../include/entry.h" +#include "mmu.h" -int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg, unsigned long stack) { +int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg/*, unsigned long stack*/) { preempt_disable(); struct task_struct *p; - p = (struct task_struct *) malloc(PAGE_SIZE); + p = (struct task_struct *) allocate_kernel_page(); if (p == NULL) return -1; + memzero((unsigned long)p, sizeof(struct task_struct)); struct pt_regs *childregs = task_pt_regs(p); - memzero((unsigned long)childregs, sizeof(struct pt_regs)); - memzero((unsigned long)&p->cpu_context, sizeof(struct cpu_context)); if (clone_flags & PF_KTHREAD) { p->cpu_context.x19 = fn; @@ -28,8 +28,7 @@ int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg, ((char*)childregs)[i] = ((char*)cur_regs)[i]; } childregs->regs[0] = 0; // return value 0 - childregs->sp = stack + PAGE_SIZE; - p->stack = stack; + copy_virt_memory(p); } p->flags = clone_flags; @@ -50,19 +49,71 @@ int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg, } -int move_to_user_mode(unsigned long pc) { +int move_to_user_mode(unsigned long start, unsigned long size, unsigned long pc) { struct pt_regs *regs = task_pt_regs(current); - memzero((unsigned long)regs, sizeof(*regs)); - regs->pc = pc; + regs->pc = pc; // virt pc regs->pstate = PSR_MODE_EL0t; - unsigned long stack = (unsigned long)malloc(PAGE_SIZE); - - if (stack == NULL) + regs->sp = 0xfffffffff000; + + unsigned long pages = (unsigned long)malloc(size); // phys + if (pages == NULL) return -1; - - regs->sp = stack + PAGE_SIZE; - current->stack = stack; + + unsigned long va; // might need to map more? bss? currently mapping just enough + for(va=0; vasp; va+=PAGE_SIZE) { + map_page(current, va, stack_bot+(i*PAGE_SIZE)); + i++; + } + + // vc identity mapping + //printf("user page count %d\n", current->mm.user_pages_count); + for(unsigned long va=0x3c000000; va<0x3f000000; va+=PAGE_SIZE) { + unsigned long pgd; + if (!current->mm.pgd) { + pgd = (unsigned long)malloc(PAGE_SIZE); + //printf("pgd not found, created at phys address 0x%x\n", pgd); + memzero(pgd+VA_START, PAGE_SIZE); + current->mm.pgd = pgd; + current->mm.kernel_pages[++current->mm.kernel_pages_count] = current->mm.pgd; + } + pgd = current->mm.pgd; + //printf("pgd at 0x%x\n", pgd); + int new_table; + unsigned long pud = map_table((unsigned long *)(pgd + VA_START), PGD_SHIFT, va, &new_table); + //printf("pud at 0x%x\n", pud); + if (new_table) { + current->mm.kernel_pages[++current->mm.kernel_pages_count] = pud; + } + unsigned long pmd = map_table((unsigned long *)(pud + VA_START), PUD_SHIFT, va, &new_table); + //printf("pmd at 0x%x\n", pmd); + if (new_table) { + current->mm.kernel_pages[++current->mm.kernel_pages_count] = pmd; + } + unsigned long pte = map_table((unsigned long *)(pmd + VA_START), PMD_SHIFT, va, &new_table); + //printf("pte at 0x%x\n", pte); + if (new_table) { + current->mm.kernel_pages[++current->mm.kernel_pages_count] = pte; + } + map_table_entry((unsigned long *)(pte + VA_START), va, va); + //struct user_page p = {page, va}; + //task->mm.user_pages[task->mm.user_pages_count++] = p; + //if (va == 0x3c25e000) printf("0x%x, 0x%x, 0x%x, 0x%x\n", pgd, pud, pmd, pte); + } + //printf("user page count %d\n", current->mm.user_pages_count); + //memcpy(pages+VA_START, start, size); // move code to pages + for(int i=0; imm.pgd); return 0; } @@ -76,8 +127,50 @@ struct pt_regs *task_pt_regs(struct task_struct *tsk) { void new_user_process(unsigned long func){ printf("Kernel process started, moving to user mode.\n"); - int err = move_to_user_mode(func); + int err = move_to_user_mode(func, 4096, func); if (err < 0){ printf("Error while moving process to user mode\n\r"); } +} + +int copy_virt_memory(struct task_struct *dst) { + struct task_struct* src = current; + for (int i=0; imm.user_pages_count; i++) { + unsigned long kernel_va = allocate_user_page(dst, src->mm.user_pages[i].virt_addr); + if(kernel_va == 0) return -1; + memcpy(kernel_va, src->mm.user_pages[i].virt_addr, PAGE_SIZE); + } + for(unsigned long va=0x3c000000; va<0x3f000000; va+=PAGE_SIZE) { + unsigned long pgd; + if (!dst->mm.pgd) { + pgd = (unsigned long)malloc(PAGE_SIZE); + //printf("pgd not found, created at phys address 0x%x\n", pgd); + memzero(pgd+VA_START, PAGE_SIZE); + dst->mm.pgd = pgd; + dst->mm.kernel_pages[++dst->mm.kernel_pages_count] = dst->mm.pgd; + } + pgd = dst->mm.pgd; + //printf("pgd at 0x%x\n", pgd); + int new_table; + unsigned long pud = map_table((unsigned long *)(pgd + VA_START), PGD_SHIFT, va, &new_table); + //printf("pud at 0x%x\n", pud); + if (new_table) { + dst->mm.kernel_pages[++dst->mm.kernel_pages_count] = pud; + } + unsigned long pmd = map_table((unsigned long *)(pud + VA_START), PUD_SHIFT, va, &new_table); + //printf("pmd at 0x%x\n", pmd); + if (new_table) { + dst->mm.kernel_pages[++dst->mm.kernel_pages_count] = pmd; + } + unsigned long pte = map_table((unsigned long *)(pmd + VA_START), PMD_SHIFT, va, &new_table); + //printf("pte at 0x%x\n", pte); + if (new_table) { + dst->mm.kernel_pages[++dst->mm.kernel_pages_count] = pte; + } + map_table_entry((unsigned long *)(pte + VA_START), va, va); + //struct user_page p = {page, va}; + //task->mm.user_pages[task->mm.user_pages_count++] = p; + if (va == 0x3c25e000) printf("0x%x, 0x%x, 0x%x, 0x%x\n", pgd, pud, pmd, pte); + } + return 0; } \ No newline at end of file diff --git a/lab6/lib/mm.S b/lab6/lib/mm.S index 263de4251..56eef8286 100644 --- a/lab6/lib/mm.S +++ b/lab6/lib/mm.S @@ -1,3 +1,11 @@ +.globl memcpy +memcpy: + ldr x3, [x1], #8 + str x3, [x0], #8 + subs x2, x2, #8 + b.gt memcpy + ret + .globl memzero memzero: str xzr, [x0], #8 diff --git a/lab6/lib/mm.c b/lab6/lib/mm.c index 05a5f6f50..7c0ef41e6 100644 --- a/lab6/lib/mm.c +++ b/lab6/lib/mm.c @@ -1,4 +1,5 @@ #include "mm.h" +#include "mmu.h" #include "math.h" #include "memory.h" #include "mini_uart.h" @@ -13,6 +14,21 @@ static void* reserved_se[MAX_RESERVABLE][2] = {{0x0, 0x0}}; // expects to be sor extern char __kernel_end; static char *__kernel_end_ptr = &__kernel_end; +unsigned long allocate_user_page(struct task_struct *task, unsigned long va) { + unsigned long page = (unsigned long)malloc(PAGE_SIZE); + if (page == 0) + return NULL; + map_page(task, va, page); + return page + VA_START; +} + +unsigned long allocate_kernel_page() { + unsigned long page = (unsigned long)malloc(PAGE_SIZE); + if(page == NULL) + return NULL; + return page + VA_START; +} + void *malloc(unsigned int size) { if (size > max_size) { @@ -36,7 +52,8 @@ void *malloc(unsigned int size) { while (t != req_order) { struct frame* l_tmp = frame_list[t]; frame_list[t] = l_tmp->next; - frame_list[t]->prev = NULL; + if (frame_list[t] != NULL) + frame_list[t]->prev = NULL; //printf("[info] Split at order %d, new head is 0x%x.\n", t+1, frame_list[t]); unsigned int off = pow(2, l_tmp->val-1); @@ -61,7 +78,8 @@ void *malloc(unsigned int size) { struct frame* ret = frame_list[req_order]; frame_list[req_order] = ret->next; - frame_list[req_order]->prev = NULL; + if (frame_list[req_order] != NULL) + frame_list[req_order]->prev = NULL; ret->val = ret->val; ret->state = ALLOCATED; @@ -293,7 +311,7 @@ void init_mm_reserve() { max_size = PAGE_SIZE * pow(2, MAX_ORDER-1); n_frames = (MEM_REGION_END-MEM_REGION_BEGIN) / PAGE_SIZE; - memory_reserve((void*)0x0, __kernel_end_ptr); // spin tables, kernel image + memory_reserve((void*)0x0, (void*)((unsigned long long)__kernel_end_ptr&VA_MASK)); // spin tables, kernel image memory_reserve((void*)0x20000000, (void*)0x20010000); // hard code reserve initramfs for (unsigned int i=0; imm.pgd) { + pgd = (unsigned long)malloc(PAGE_SIZE); + //printf("pgd not found, created at phys address 0x%x\n", pgd); + memzero(pgd+VA_START, PAGE_SIZE); + task->mm.pgd = pgd; + task->mm.kernel_pages[++task->mm.kernel_pages_count] = task->mm.pgd; + } + pgd = task->mm.pgd; + //printf("pgd at 0x%x\n", pgd); + int new_table; + unsigned long pud = map_table((unsigned long *)(pgd + VA_START), PGD_SHIFT, va, &new_table); + //printf("pud at 0x%x\n", pud); + if (new_table) { + task->mm.kernel_pages[++task->mm.kernel_pages_count] = pud; + } + unsigned long pmd = map_table((unsigned long *)(pud + VA_START), PUD_SHIFT, va, &new_table); + //printf("pmd at 0x%x\n", pmd); + if (new_table) { + task->mm.kernel_pages[++task->mm.kernel_pages_count] = pmd; + } + unsigned long pte = map_table((unsigned long *)(pmd + VA_START), PMD_SHIFT, va, &new_table); + //printf("pte at 0x%x\n", pte); + if (new_table) { + task->mm.kernel_pages[++task->mm.kernel_pages_count] = pte; + } + map_table_entry((unsigned long *)(pte + VA_START), va, page); + struct user_page p = {page, va}; + task->mm.user_pages[task->mm.user_pages_count++] = p; + //printf("user pages count %d\n", task->mm.user_pages_count-1); +} + +unsigned long map_table(unsigned long *table, unsigned long shift, unsigned long va, int* new_table) { + unsigned long index = va >> shift; + index = index & (512 - 1); + if (!table[index]) { + *new_table = 1; + unsigned long next_level_table = (unsigned long)malloc(PAGE_SIZE); + memzero(next_level_table+VA_START, PAGE_SIZE); + unsigned long entry = next_level_table | PD_TABLE; + table[index] = entry; + return next_level_table; + } else { + *new_table = 0; + } + return table[index] & PAGE_MASK; +} + +void map_table_entry(unsigned long *pte, unsigned long va, unsigned long pa) { + unsigned long index = va >> 12; + index = index & (512 - 1); + unsigned long entry = pa | PTE_USR_RW_PTE; + pte[index] = entry; + //printf("0x%x[%d] contains 0x%x\n", pte, index, entry); +} + +unsigned long va2phys(unsigned long va) { + printf("translate va: 0x%x\n", va); + unsigned long pgd; + asm volatile("mrs %0, ttbr0_el1\n\t": "=r" (pgd) :: "memory"); + unsigned long pud = ((unsigned long*)(pgd+VA_START))[va>>PGD_SHIFT&(512-1)]&PAGE_MASK; + unsigned long pmd = ((unsigned long*)(pud+VA_START))[va>>PUD_SHIFT&(512-1)]&PAGE_MASK; + unsigned long pte = ((unsigned long*)(pmd+VA_START))[va>>PMD_SHIFT&(512-1)]&PAGE_MASK; + unsigned long phys = ((unsigned long*)(pte+VA_START))[va>>12&(512-1)]&PAGE_MASK; + printf("0x%x, 0x%x, 0x%x, 0x%x, 0x%x\n", pgd, pud, pmd, pte, phys); + return phys; +} diff --git a/lab6/lib/sched.S b/lab6/lib/sched.S index 03f9c56b1..7e4392f23 100644 --- a/lab6/lib/sched.S +++ b/lab6/lib/sched.S @@ -22,3 +22,11 @@ cpu_switch_to: ldr x30, [x8] mov sp, x9 ret + +.global update_pgd +update_pgd: + 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 \ No newline at end of file diff --git a/lab6/lib/sched.c b/lab6/lib/sched.c index 2ffc32acd..7f1b3e08a 100644 --- a/lab6/lib/sched.c +++ b/lab6/lib/sched.c @@ -1,5 +1,6 @@ #include "../include/sched.h" #include "mm.h" +#include "mmu.h" #include "exception.h" #include "mini_uart.h" @@ -57,6 +58,7 @@ void switch_to(struct task_struct *next) { return; struct task_struct *prev = current; current = next; + update_pgd(next->mm.pgd); cpu_switch_to(prev, next); } @@ -81,7 +83,7 @@ void exit_process() { // should only be accessed using syscall // preempt_disable(); current->state = TASK_ZOMBIE; - free((void*)current->stack); + //free((void*)current->stack); // free resources // preempt_enable(); schedule(); } @@ -98,7 +100,14 @@ void kill_zombies() { p = task[i]; if (p && p->state == TASK_ZOMBIE) { printf("Zombie found with pid: %d.\n", p->id); - free(p); + // for loop free user pages and kernel pages + for (int i=0; imm.user_pages_count; i++) { + free((void*)(p->mm.user_pages[i].phys_addr+VA_START)); + } + for (int i=0; imm.kernel_pages_count; i++) { + free((void*)(p->mm.kernel_pages[i]+VA_START)); + } + free(p); // free task struct task[i] = NULL; } diff --git a/lab6/lib/shell.c b/lab6/lib/shell.c index 75a630b9e..cf79ec988 100644 --- a/lab6/lib/shell.c +++ b/lab6/lib/shell.c @@ -14,72 +14,15 @@ #include "syscall.h" #include "peripherals/mailbox.h" #include "fork.h" +#include "mmu.h" #define MAX_BUFFER_SIZE 256u static char buffer[MAX_BUFFER_SIZE]; -static int shared = 1; - -void foo() { - for(int i = 0; i < 10; ++i) { - printf("Thread id: %d %d\n", current->id, i); - delay(1000000); - schedule(); - } - - current->state = TASK_ZOMBIE; - while(1); -} - -void user_foo() { - - printf("User thread id: %d\n", getpid()); - - volatile unsigned int __attribute__((aligned(16))) mailbox[7]; - mailbox[0] = 7 * 4; - mailbox[1] = REQUEST_CODE; - mailbox[2] = GET_BOARD_REVISION; - mailbox[3] = 4; - mailbox[4] = TAG_REQUEST_CODE; - mailbox[5] = 0; - mailbox[6] = END_TAG; - mbox_call(0x8, mailbox); - printf("Board Revision:\t\t%x\n", mailbox[5]); - - int pid = fork(); - if (pid == 0) { - printf("Child says hello!\n"); - while(1) { - printf("Please don't kill me :(\n"); - shared++; - } - } else if (pid > 0) { - printf("Parent says, \"My child has pid %d\"\n", pid); - printf("Shared? %d\n", shared); - delay(10000000); - printf("Kill my own child :(\n"); - kill(pid); - delay(10000000); - printf("shared %d\n", shared); - } - - //char buf[4] = {0}; - //uart_read(buf, 3); - //uart_write(buf, 3); - - exit(); - -} void start_video() { - // ... go to cpio, find location and size of syscall.img - // allocate pages - // move syscall.img to allocated memory - // preempt disable - // change this shell thread to not runnable - // start sycall.img user process - // preempt enable + struct cpio_newc_header *header; unsigned int filesize; unsigned int namesize; @@ -88,6 +31,7 @@ void start_video() { void *code_loc; header = DEVTREE_CPIO_BASE; + printf("devtree base: 0x%x\n", DEVTREE_CPIO_BASE); while (1) { filename = ((void*)header) + sizeof(struct cpio_newc_header); @@ -125,20 +69,9 @@ void start_video() { printf("syscall.img found in cpio at location 0x%x.\n", code_loc); printf("syscall.img has size of %d bytes.\n", (int)filesize); - void *move_loc = malloc(filesize + 4096); // an extra page for bss just in case - if(move_loc == NULL) return; - for (int i=0; istate = TASK_STOPPED; - unsigned long long tmp; - asm volatile("mrs %0, cntkctl_el1" : "=r"(tmp)); - tmp |= 1; - asm volatile("msr cntkctl_el1, %0" : : "r"(tmp)); - copy_process(PF_KTHREAD, (unsigned long)&new_user_process, (unsigned long)move_loc, 0); - preempt_enable(); + int err = move_to_user_mode((unsigned long)code_loc, filesize, 0); + if (err<0) + printf("failed to start video program\n"); } @@ -188,16 +121,15 @@ void parse_cmd() else if (stringcmp(buffer, "execute") == 0) { cpio_exec(); } - else if (stringcmp(buffer, "thread_test") == 0) { - for (int i=0; i<10; i++) { - copy_process(PF_KTHREAD, (unsigned long)&foo, 0, 0); - } - } - else if (stringcmp(buffer, "to_user") == 0) { - copy_process(PF_KTHREAD, (unsigned long)&new_user_process, (unsigned long)&user_foo, 0); - } else if (stringcmp(buffer, "video") == 0) { - start_video(); + preempt_disable(); + current->state = TASK_STOPPED; + unsigned long long tmp; + asm volatile("mrs %0, cntkctl_el1" : "=r"(tmp)); + tmp |= 1; + asm volatile("msr cntkctl_el1, %0" : : "r"(tmp)); + copy_process(PF_KTHREAD, (unsigned long)&start_video, 0/*, 0*/); + preempt_enable(); } else if (stringcmp(buffer, "help") == 0) { uart_send_string("help:\t\tprint list of available commands\n"); diff --git a/lab6/lib/syscall.c b/lab6/lib/syscall.c index bd3ee6726..3673ad4bd 100644 --- a/lab6/lib/syscall.c +++ b/lab6/lib/syscall.c @@ -5,6 +5,7 @@ #include "mm.h" #include "../include/cpio.h" #include "string.h" +#include "mmu.h" #include "mini_uart.h" int sys_getpid() { @@ -19,14 +20,13 @@ unsigned sys_uartread(char buf[], unsigned size) { } unsigned sys_uartwrite(const char buf[], unsigned size) { - //printf("%s", buf); for(int i=0; ipc = (unsigned long)move_loc; // move to beginning of program - p->sp = current->stack+PAGE_SIZE; + p->pc = 0; // move to beginning of program + p->sp = 0xfffffffff000; preempt_enable(); - return -1; // only on failure -} + return -1; // only on failure*/ + // not real exec, only a restart of current process +} // fix int sys_fork() { - return copy_process(0, 0, 0, (unsigned long)malloc(4*4096)); + return copy_process(0, 0, 0/*, 0*/); } void sys_exit(int status) { exit_process(); } -int sys_mbox_call(unsigned char ch, unsigned int *mbox) { - unsigned int r = (((unsigned int)((unsigned long)mbox)&~0xF) | (ch&0xF)); +int sys_mbox_call(unsigned char ch, volatile unsigned int *mbox) { + printf("mbox: 0x%x\n", mbox); + unsigned long ka_mbox = va2phys((unsigned long)mbox) + ((unsigned long)mbox&0xFFF); + printf("mbox kernel addr location: 0x%x\n", ka_mbox); + unsigned int r = (((unsigned int)((unsigned long)ka_mbox)&~0xF) | (ch&0xF)); while(*MAILBOX_STATUS & MAILBOX_FULL); *MAILBOX_WRITE = r; while (1) { while (*MAILBOX_STATUS & MAILBOX_EMPTY) {} if (r == *MAILBOX_READ) { - return mbox[1]==REQUEST_SUCCEED; + return ((unsigned int *)(ka_mbox+VA_START))[1]==REQUEST_SUCCEED; } } return 0; @@ -121,7 +125,7 @@ void sys_kill(int pid) { preempt_disable(); printf("Kill target acquired.\n"); p->state = TASK_ZOMBIE; - free((void *)p->stack); + //free((void *)p->stack); preempt_enable(); break; } From 308b01df758cc50eb6bf5e042f1dd8897b70c902 Mon Sep 17 00:00:00 2001 From: Hsiang-Chieh Tsou Date: Mon, 23 May 2022 11:41:00 +0800 Subject: [PATCH 4/9] lab 6 vm.img --- lab6/initramfs.cpio | Bin 247296 -> 248320 bytes lab6/lib/shell.c | 6 +++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lab6/initramfs.cpio b/lab6/initramfs.cpio index 0676fb1584617bc4d73624b3b212a12133e97d8e..e1b3477cedccc5f111bdbcd4215c1c3e7bad1e11 100644 GIT binary patch delta 1989 zcmd^9Z)h837=P|AS+mq6TbAxFe`szkm92yAlGH4jA9`t5R3<1PpHytKrk1*;bhEa~ zoV|2Hha0%pw@_hVGVz0!jTWM45N3*O;0KYGZLm+fw(OIihM)wA@p}4qQtCibfIul5Xp#d zpeOQF6!R1%5lQtJXlx*{?W!n|cofwV$^gb={qcSS#%kNuq$6frQKs^T#`2?ptHOit zYw~bdYP6`U4FQskMsHFVfP`h?4#r6NMeaGy4lWWE=*N8bfdUWn`eC&KHrTBnhDwU< zmmsMxsmbtuIKA>)BcpO!db<3Oe}?BS(4;+QlU?LKy=o76Aw&Sw%D#6Xh{cy-SBU;- z-|JOFL@p6d%)Y6{NUE~-C^DtdVM}BWC7^y>U(u*_E-wRJZ+hyp(V_%?>kBu*6#?k5(P;~+D`S`+3f>)}+z zEG&{G%=@-EuO!lM9D)5+k-U%eaxyF^@Pm#6WX^DbFK0wi8bEYa8 zW+tu7tYKVBzl#+%(eNy2Ys?TgB+=oWT_TImVLro7a2BRXw7By?|2f2_o3*Q^_VZh= z%%)uCmhlYn+{ciAvY8Kl+Uh(w<0r?_<;%^;kl%2rY`GL5BiOWb2WV*yX3^p7+AFB0 zUxC{C71f*!%}UKk*qhZAj=w!qH|Ex@p2= znJ7GFLR)*9nJA)(;v*9hGr{(MV`8_3?K2((Uk&78{q0U2NRrUVw)21hc0^b_S2qW) zV4fRxB7E1?Y+7bKesRO%vGX0ndmD0$PN)I$Dr`;oZ8qT@IN~>q5&x383jPGoQ~+44!$6#lrIB$QzS##Ntf<11Cx^`#R|tK6!`v*`pi26LC`+Ae+mf ze%D)0<`7p`&b%{locq_`%ulem>GynJ+gx88hkyR7zBVDS?5*As7z`g-*V3(p@teYj Qc-;T#`oGtI;a~3l9}x@A$^ZZW delta 1079 zcmZ8hT}V@57=FKVrp~|3dFB?V+c7Wv3G~dF&Tcwlqk_66qQ`m;Bv5TG!|CpVFsZgLSx~ zUROt6&D+=M%c=gaR@Y~UOEY(7Muw)Q+9s}D1%OK&*av8OBsN9-q(~{IpE&k{Yi()e zAfmR*aWH{RZHM@Gg7pUUs2g&RyBVgp53)g-C;>56%Ae*)C%aHIYFaI(%Pd}0cR~~> zfE;hQ3$^I62~w^WwR+aopW>la?}r2`0z6O$;kSWf)?ybY3bFR$0pc)ZP6A9W{mKS) zh$yf}C0z@KqBh$9kqs&pO5p6DY*gJbBJV&vrG3}j<(#{QeUr`xC$m{+X8!e)3=fzR z=b;js28h7z#efer^qyKmzhTcU^1GS&?X?6y_%Sp8G|#C8(M)e?dolua0ZCtQ)rdqk zvm0i|LdZ-%Ab$?Po*#);s`A|WV}!c_rU`*yv$oA{5>L5%jjUFB5oN~0F=N>#GFTR3j4YsR8b!W zI8!JnSLwlL4ZR_Q+k~3?_`B~JVMs{B@#_bfEL$qAcWP%yLLCh=OmcyVQpL7PTl!jX zCKh@mavtXnGwU9(LgYFs;yJ59yuA<)Hr7~ntlma{;x?ObqNfkp=#~6qd(V>PE_1?0 zzhdh;OP7yw2^;ILc*V)pF`;sfSVGlQ6^yf&sE$P~dnUFfRI+Jdi8b5zstVqfwz8WO zqpY_^h`DRZh-oZ5F{@SVnWKztIT|blP)x!;JBF9S&ii2}!JFF(JNYAc_filesize, 8); - if (stringncmp(filename, "syscall.img", namesize) == 0) { + if (stringncmp(filename, "vm.img", namesize) == 0) { code_loc = ((void*)header) + offset; break; } @@ -66,8 +66,8 @@ void start_video() { header = ((void*)header) + offset; } - printf("syscall.img found in cpio at location 0x%x.\n", code_loc); - printf("syscall.img has size of %d bytes.\n", (int)filesize); + printf("vm.img found in cpio at location 0x%x.\n", code_loc); + printf("vm.img has size of %d bytes.\n", (int)filesize); int err = move_to_user_mode((unsigned long)code_loc, filesize, 0); if (err<0) From 6f8ba2ec865a72f36d70521d278e270431c36cc2 Mon Sep 17 00:00:00 2001 From: Hsiang-Chieh Tsou Date: Mon, 23 May 2022 11:41:51 +0800 Subject: [PATCH 5/9] lab 7 init --- lab7/bcm2710-rpi-3-b-plus.dtb | Bin 0 -> 31790 bytes lab7/config.txt | 3 + lab7/include/cpio.h | 49 ++++ lab7/include/devtree.h | 64 +++++ lab7/include/entry.h | 39 +++ lab7/include/exception.h | 7 + lab7/include/fork.h | 27 ++ lab7/include/mailbox.h | 7 + lab7/include/math.h | 7 + lab7/include/memory.h | 8 + lab7/include/mini_uart.h | 10 + lab7/include/mm.h | 61 +++++ lab7/include/mmu.h | 51 ++++ lab7/include/peripherals/base.h | 9 + lab7/include/peripherals/exception.h | 26 ++ lab7/include/peripherals/gpio.h | 12 + lab7/include/peripherals/mailbox.h | 25 ++ lab7/include/peripherals/mini_uart.h | 19 ++ lab7/include/printf.h | 31 +++ lab7/include/reboot.h | 7 + lab7/include/sched.h | 90 ++++++ lab7/include/shell.h | 6 + lab7/include/string.h | 8 + lab7/include/syscall.h | 37 +++ lab7/include/timer.h | 14 + lab7/include/utils.h | 8 + lab7/initramfs.cpio | Bin 0 -> 248320 bytes lab7/kernel/Makefile | 49 ++++ lab7/kernel/boot_kernel.S | 88 ++++++ lab7/kernel/kernel.c | 26 ++ lab7/kernel/linker.ld | 24 ++ lab7/lib/Makefile | 29 ++ lab7/lib/cpio.c | 233 ++++++++++++++++ lab7/lib/devtree.c | 92 +++++++ lab7/lib/entry.S | 195 +++++++++++++ lab7/lib/exception.c | 49 ++++ lab7/lib/fork.c | 176 ++++++++++++ lab7/lib/mailbox.c | 49 ++++ lab7/lib/math.c | 19 ++ lab7/lib/memory.c | 22 ++ lab7/lib/mini_uart.c | 69 +++++ lab7/lib/mm.S | 14 + lab7/lib/mm.c | 394 +++++++++++++++++++++++++++ lab7/lib/mmu.c | 74 +++++ lab7/lib/printf.c | 152 +++++++++++ lab7/lib/reboot.c | 18 ++ lab7/lib/sched.S | 32 +++ lab7/lib/sched.c | 116 ++++++++ lab7/lib/shell.c | 155 +++++++++++ lab7/lib/string.c | 39 +++ lab7/lib/syscall.S | 49 ++++ lab7/lib/syscall.c | 147 ++++++++++ lab7/lib/timer.c | 62 +++++ lab7/lib/utils.S | 15 + lab7/send_img.py | 31 +++ 55 files changed, 3043 insertions(+) create mode 100644 lab7/bcm2710-rpi-3-b-plus.dtb create mode 100644 lab7/config.txt create mode 100644 lab7/include/cpio.h create mode 100644 lab7/include/devtree.h create mode 100644 lab7/include/entry.h create mode 100644 lab7/include/exception.h create mode 100644 lab7/include/fork.h create mode 100644 lab7/include/mailbox.h create mode 100644 lab7/include/math.h create mode 100644 lab7/include/memory.h create mode 100644 lab7/include/mini_uart.h create mode 100644 lab7/include/mm.h create mode 100644 lab7/include/mmu.h create mode 100644 lab7/include/peripherals/base.h create mode 100644 lab7/include/peripherals/exception.h create mode 100644 lab7/include/peripherals/gpio.h create mode 100644 lab7/include/peripherals/mailbox.h create mode 100644 lab7/include/peripherals/mini_uart.h create mode 100644 lab7/include/printf.h create mode 100644 lab7/include/reboot.h create mode 100644 lab7/include/sched.h create mode 100644 lab7/include/shell.h create mode 100644 lab7/include/string.h create mode 100644 lab7/include/syscall.h create mode 100644 lab7/include/timer.h create mode 100644 lab7/include/utils.h create mode 100644 lab7/initramfs.cpio create mode 100644 lab7/kernel/Makefile create mode 100644 lab7/kernel/boot_kernel.S create mode 100644 lab7/kernel/kernel.c create mode 100644 lab7/kernel/linker.ld create mode 100644 lab7/lib/Makefile create mode 100644 lab7/lib/cpio.c create mode 100644 lab7/lib/devtree.c create mode 100644 lab7/lib/entry.S create mode 100644 lab7/lib/exception.c create mode 100644 lab7/lib/fork.c create mode 100644 lab7/lib/mailbox.c create mode 100644 lab7/lib/math.c create mode 100644 lab7/lib/memory.c create mode 100644 lab7/lib/mini_uart.c create mode 100644 lab7/lib/mm.S create mode 100644 lab7/lib/mm.c create mode 100644 lab7/lib/mmu.c create mode 100644 lab7/lib/printf.c create mode 100644 lab7/lib/reboot.c create mode 100644 lab7/lib/sched.S create mode 100644 lab7/lib/sched.c create mode 100644 lab7/lib/shell.c create mode 100644 lab7/lib/string.c create mode 100644 lab7/lib/syscall.S create mode 100644 lab7/lib/syscall.c create mode 100644 lab7/lib/timer.c create mode 100644 lab7/lib/utils.S create mode 100644 lab7/send_img.py diff --git a/lab7/bcm2710-rpi-3-b-plus.dtb b/lab7/bcm2710-rpi-3-b-plus.dtb new file mode 100644 index 0000000000000000000000000000000000000000..3934b3a26eb82fd65dbcdfca6f6916da427a3fae GIT binary patch literal 31790 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 literal 0 HcmV?d00001 diff --git a/lab7/config.txt b/lab7/config.txt new file mode 100644 index 000000000..49fc25695 --- /dev/null +++ b/lab7/config.txt @@ -0,0 +1,3 @@ +kernel=bootloader.img +arm_64bit=1 +initramfs initramfs.cpio 0x20000000 diff --git a/lab7/include/cpio.h b/lab7/include/cpio.h new file mode 100644 index 000000000..1735f5fe5 --- /dev/null +++ b/lab7/include/cpio.h @@ -0,0 +1,49 @@ +#ifndef __CPIO_H__ +#define __CPIO_H__ + +/* +Each file system object in a cpio archive comprises a header record with +basic numeric metadata followed by the full pathname of the entry and the +file data. The header record stores a series of integer values that gen- +erally follow the fields in struct stat. (See stat(2) for details.) The +variants differ primarily in how they store those integers (binary, oc- +tal, or hexadecimal). The header is followed by the pathname of the en- +try (the length of the pathname is stored in the header) and any file +data. The end of the archive is indicated by a special record with the +pathname "TRAILER!!!" +*/ + +#define CPIO_HEADER_MAGIC "070701" +#define CPIO_FOOTER_MAGIC "TRAILER!!!" +#define PI_CPIO_BASE ((void*) (0x20000000)) +#define QEMU_CPIO_BASE ((void*) (0x8000000)) + +#include "devtree.h" + +extern void *DEVTREE_CPIO_BASE; + +struct cpio_newc_header { + char c_magic[6]; // 070701 + 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]; // ignored by readers +}; + +void initramfs_callback (char *, char *, struct fdt_prop *); +void cpio_ls (); +void cpio_cat (); +void cpio_exec (); + +unsigned int hexstr_to_uint(char *s, unsigned int len); + +#endif \ No newline at end of file diff --git a/lab7/include/devtree.h b/lab7/include/devtree.h new file mode 100644 index 000000000..1c3c27899 --- /dev/null +++ b/lab7/include/devtree.h @@ -0,0 +1,64 @@ +#ifndef _DEVTREE_H +#define _DEVTREE_H + +/* + magic; 0xd00dfeed (big-endian) + + totalsize; total size in bytes of the devicetree + data structure + + off_dt_struct; offset in bytes of the structure block + from the beginning of the header + + off_dt_strings; offset in bytes of the strings block + from the beginning of the header + + off_mem_rsvmap; offset in bytes of the memory reservation + block from the beginning of the header + + version; version of the devicetree data structure + + last_comp_version; lowest version of the devicetree data + structure with which the version used is backwards compatible + + boot_cpuid_phys; physical ID of the system’s boot CPU + + size_dt_strings; length in bytes of the strings block + section of the devicetree blob + + size_dt_struct; length in bytes of the structure block + section of the devicetree blob +*/ + +#define FDT_HEADER_MAGIC 0xd00dfeed +#define FDT_BEGIN_NODE 0x00000001 +#define FDT_END_NODE 0x00000002 +#define FDT_PROP 0x00000003 +#define FDT_NOP 0x00000004 +#define FDT_END 0x00000009 + +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; +}; + +struct fdt_prop { + unsigned int len; + unsigned int nameoff; +}; + +void devtree_getaddr (); +void fdt_traverse ( void (*callback)(char *, char *, struct fdt_prop *) ); + +// ARM uses little endian +unsigned long to_lendian (unsigned long); + +#endif \ No newline at end of file diff --git a/lab7/include/entry.h b/lab7/include/entry.h new file mode 100644 index 000000000..1a22d0284 --- /dev/null +++ b/lab7/include/entry.h @@ -0,0 +1,39 @@ +#ifndef _ENTRY_H +#define _ENTRY_H + +#define S_FRAME_SIZE 272 // size of all saved registers +#define S_X0 0 + +#define SYNC_INVALID_EL1t 0 +#define IRQ_INVALID_EL1t 1 +#define FIQ_INVALID_EL1t 2 +#define ERROR_INVALID_EL1t 3 + +#define SYNC_INVALID_EL1h 4 +#define IRQ_INVALID_EL1h 5 +#define FIQ_INVALID_EL1h 6 +#define ERROR_INVALID_EL1h 7 + +#define SYNC_INVALID_EL0_64 8 +#define IRQ_INVALID_EL0_64 9 +#define FIQ_INVALID_EL0_64 10 +#define ERROR_INVALID_EL0_64 11 + +#define SYNC_INVALID_EL0_32 12 +#define IRQ_INVALID_EL0_32 13 +#define FIQ_INVALID_EL0_32 14 +#define ERROR_INVALID_EL0_32 15 + +#define SYNC_ERROR 16 +#define SYSCALL_ERROR 17 + +#define ESR_ELx_EC_SHIFT 26 +#define ESR_ELx_EC_SVC64 0x15 + +#ifndef __ASSEMBLER__ + +void ret_from_fork(); + +#endif + +#endif \ No newline at end of file diff --git a/lab7/include/exception.h b/lab7/include/exception.h new file mode 100644 index 000000000..40f4ba532 --- /dev/null +++ b/lab7/include/exception.h @@ -0,0 +1,7 @@ +#ifndef _EXCEPTION_H +#define _EXCEPTION_H + +void enable_interrupt(); +void disable_interrupt(); + +#endif \ No newline at end of file diff --git a/lab7/include/fork.h b/lab7/include/fork.h new file mode 100644 index 000000000..06bc62382 --- /dev/null +++ b/lab7/include/fork.h @@ -0,0 +1,27 @@ +#ifndef _FORK_H +#define _FORK_H + +#include "sched.h" + +#define PSR_MODE_EL0t 0x00000000 +#define PSR_MODE_EL1t 0x00000004 +#define PSR_MODE_EL1h 0x00000005 +#define PSR_MODE_EL2t 0x00000008 +#define PSR_MODE_EL2h 0x00000009 +#define PSR_MODE_EL3t 0x0000000c +#define PSR_MODE_EL3h 0x0000000d + +int copy_process(unsigned long, unsigned long, unsigned long/*, unsigned long*/); +int move_to_user_mode(unsigned long, unsigned long, unsigned long); +struct pt_regs *task_pt_regs(struct task_struct *); +void new_user_process(unsigned long); +int copy_virt_memory(struct task_struct *); + +struct pt_regs { + unsigned long regs[31]; + unsigned long sp; + unsigned long pc; + unsigned long pstate; +}; + +#endif \ No newline at end of file diff --git a/lab7/include/mailbox.h b/lab7/include/mailbox.h new file mode 100644 index 000000000..eeecaa9de --- /dev/null +++ b/lab7/include/mailbox.h @@ -0,0 +1,7 @@ +#ifndef _MAILBOX_H +#define _MAILBOX_H + +void get_board_revision (); +void get_arm_memory (); + +#endif \ No newline at end of file diff --git a/lab7/include/math.h b/lab7/include/math.h new file mode 100644 index 000000000..353ccfb26 --- /dev/null +++ b/lab7/include/math.h @@ -0,0 +1,7 @@ +#ifndef _MATH_H +#define _MATH_H + +int log(int, int); +int pow(int, int); + +#endif \ No newline at end of file diff --git a/lab7/include/memory.h b/lab7/include/memory.h new file mode 100644 index 000000000..956f6e955 --- /dev/null +++ b/lab7/include/memory.h @@ -0,0 +1,8 @@ +#ifndef _MEMORY_H +#define _MEMORY_H + +#define MAX_HEAP_SIZE 0x10000000 + +void* simple_malloc(unsigned int); + +#endif \ No newline at end of file diff --git a/lab7/include/mini_uart.h b/lab7/include/mini_uart.h new file mode 100644 index 000000000..453ec93f2 --- /dev/null +++ b/lab7/include/mini_uart.h @@ -0,0 +1,10 @@ +#ifndef _MINI_UART_H +#define _MINI_UART_H + +void uart_init ( void ); +char uart_recv ( void ); +void uart_send ( char c ); +void uart_send_string ( char* str ); +void printf ( char *fmt, ... ); + +#endif /*_MINI_UART_H */ \ No newline at end of file diff --git a/lab7/include/mm.h b/lab7/include/mm.h new file mode 100644 index 000000000..f74bfd242 --- /dev/null +++ b/lab7/include/mm.h @@ -0,0 +1,61 @@ +#ifndef _MM_H +#define _MM_H + +#define MEM_REGION_BEGIN 0x0 +#define MEM_REGION_END 0x3C000000 +#define PAGE_SIZE 4096 +#define MAX_ORDER 8 // largest: PAGE_SIZE*2^(MAX_ORDER) + +#define ALLOCABLE 0 +#define ALLOCATED -1 +#define C_NALLOCABLE -2 +#define RESERVED -3 + +#define NULL 0 + +#define MAX_POOL_PAGES 8 +#define MAX_POOLS 8 +#define MIN_CHUNK_SIZE 8 + +#define MAX_RESERVABLE 8 + +struct frame { + unsigned int index; + int val; + int state; + struct frame *prev, *next; +}; + +struct node { + struct node *next; +}; + +struct dynamic_pool { + unsigned int chunk_size; + unsigned int chunks_per_page; + unsigned int chunks_allocated; + unsigned int page_new_chunk_off; + unsigned int pages_used; + void *page_base_addrs[MAX_POOL_PAGES]; + struct node *free_head; +}; + +#include "sched.h" + +unsigned long allocate_user_page(struct task_struct *, unsigned long); +unsigned long allocate_kernel_page(); + +void *malloc(unsigned int); +void free(void *); +void init_mm(); +void init_pool(struct dynamic_pool*, unsigned int); +int register_chunk(unsigned int); +void *chunk_alloc(unsigned int); +void chunk_free(void *); +void memory_reserve(void*, void*); +void init_mm_reserve(); + +void memcpy(unsigned long dst, unsigned long src, unsigned long n); +void memzero(unsigned long, unsigned long); + +#endif /*_MM_H */ \ No newline at end of file diff --git a/lab7/include/mmu.h b/lab7/include/mmu.h new file mode 100644 index 000000000..4733595ef --- /dev/null +++ b/lab7/include/mmu.h @@ -0,0 +1,51 @@ +#ifndef _MMU_H +#define _MMU_H + +#define VA_START 0xffff000000000000 +#define VA_MASK 0x0000ffffffffffff + +#define PAGE_MASK 0xfffffffffffff000 + +#define TCR_CONFIG_REGION_48bit (((64 - 48) << 0) | ((64 - 48) << 16)) +#define TCR_CONFIG_4KB ((0b00 << 14) | (0b10 << 30)) +#define TCR_CONFIG_DEFAULT (TCR_CONFIG_REGION_48bit | TCR_CONFIG_4KB) + +#define MAIR_DEVICE_nGnRnE 0b00000000 +#define MAIR_NORMAL_NOCACHE 0b01000100 +#define MAIR_IDX_DEVICE_nGnRnE 0 +#define MAIR_IDX_NORMAL_NOCACHE 1 +#define MAIR_VALUE \ +( \ + (MAIR_DEVICE_nGnRnE << (MAIR_IDX_DEVICE_nGnRnE * 8)) | \ + (MAIR_NORMAL_NOCACHE << (MAIR_IDX_NORMAL_NOCACHE * 8)) \ +) + +#define PD_TABLE 0b11 +#define PD_BLOCK 0b01 +#define PT_ENTRY 0b11 +#define PD_ACCESS (1 << 10) +#define BOOT_PGD_ATTR PD_TABLE +#define BOOT_PUD_ATTR (PD_ACCESS | (MAIR_IDX_DEVICE_nGnRnE << 2) | PD_BLOCK) + +#define PTE_USR_RO_PTE (PD_ACCESS | (0b11<<6) | (MAIR_IDX_NORMAL_NOCACHE<<2) | PT_ENTRY) +#define PTE_USR_RW_PTE (PD_ACCESS | (0b01<<6) | (MAIR_IDX_NORMAL_NOCACHE<<2) | PT_ENTRY) + +#define SCTLR_MMU_DISABLED 0 +#define SCTLR_MMU_ENABLED 1 + +#define PGD_SHIFT (12 + 3*9) +#define PUD_SHIFT (12 + 2*9) +#define PMD_SHIFT (12 + 9) + +#ifndef __ASSEMBLER__ + +#include "sched.h" + +void map_page(struct task_struct *task, unsigned long va, unsigned long page); +unsigned long map_table(unsigned long *table, unsigned long shift, unsigned long va, int* new_table); +void map_table_entry(unsigned long *pte, unsigned long va, unsigned long pa); +unsigned long va2phys(unsigned long va); + +#endif + +#endif \ No newline at end of file diff --git a/lab7/include/peripherals/base.h b/lab7/include/peripherals/base.h new file mode 100644 index 000000000..aeb57bd92 --- /dev/null +++ b/lab7/include/peripherals/base.h @@ -0,0 +1,9 @@ +#ifndef _P_BASE_H +#define _P_BASE_H + +#include "mmu.h" + +#define DEVICE_BASE 0x3F000000 +#define PBASE (VA_START+DEVICE_BASE) + +#endif /*_P_BASE_H */ \ No newline at end of file diff --git a/lab7/include/peripherals/exception.h b/lab7/include/peripherals/exception.h new file mode 100644 index 000000000..713d05418 --- /dev/null +++ b/lab7/include/peripherals/exception.h @@ -0,0 +1,26 @@ +#ifndef _P_EXCEPTION_H +#define _P_EXCEPTION_H + +#include "peripherals/base.h" +#include "mmu.h" + +#define IRQ_BASIC_PENDING (PBASE+0x0000B200) +#define IRQ_PENDING_1 (PBASE+0x0000B204) +#define IRQ_PENDING_2 (PBASE+0x0000B208) +#define FIQ_CONTROL (PBASE+0x0000B20C) +#define ENABLE_IRQS_1 (PBASE+0x0000B210) +#define ENABLE_IRQS_2 (PBASE+0x0000B214) +#define ENABLE_BASIC_IRQS (PBASE+0x0000B218) +#define DISABLE_IRQS_1 (PBASE+0x0000B21C) +#define DISABLE_IRQS_2 (PBASE+0x0000B220) +#define DISABLE_BASIC_IRQS (PBASE+0x0000B224) + +#define CNTPCT_EL0 (VA_START + 0x4000001C) +#define CNTP_CTL_EL0 (VA_START + 0x40000040) +#define CORE0_INTERRUPT_SRC (VA_START + 0x40000060) + +#define IRQ_PENDING_1_AUX_INT (1<<29) +#define INTERRUPT_SOURCE_GPU (1<<8) +#define INTERRUPT_SOURCE_CNTPNSIRQ (1<<1) + +#endif \ No newline at end of file diff --git a/lab7/include/peripherals/gpio.h b/lab7/include/peripherals/gpio.h new file mode 100644 index 000000000..c5d7d138f --- /dev/null +++ b/lab7/include/peripherals/gpio.h @@ -0,0 +1,12 @@ +#ifndef _P_GPIO_H +#define _P_GPIO_H + +#include "peripherals/base.h" + +#define GPFSEL1 (PBASE+0x00200004) +#define GPSET0 (PBASE+0x0020001C) +#define GPCLR0 (PBASE+0x00200028) +#define GPPUD (PBASE+0x00200094) +#define GPPUDCLK0 (PBASE+0x00200098) + +#endif /*_P_GPIO_H */ \ No newline at end of file diff --git a/lab7/include/peripherals/mailbox.h b/lab7/include/peripherals/mailbox.h new file mode 100644 index 000000000..62225bd47 --- /dev/null +++ b/lab7/include/peripherals/mailbox.h @@ -0,0 +1,25 @@ +#ifndef _P_MAILBOX_H +#define _P_MAILBOX_H + +#include "mmu.h" + +#define MMIO_BASE VA_START + 0x3f000000 +#define MAILBOX_BASE MMIO_BASE + 0xb880 + +#define MAILBOX_READ (volatile unsigned int*) (MAILBOX_BASE) +#define MAILBOX_STATUS (volatile unsigned int*) (MAILBOX_BASE + 0x18) +#define MAILBOX_WRITE (volatile unsigned int*) (MAILBOX_BASE + 0x20) + +#define MAILBOX_EMPTY 0x40000000 +#define MAILBOX_FULL 0x80000000 + +#define GET_BOARD_REVISION 0x00010002 +#define GET_ARM_MEMORY 0x00010005 + +#define REQUEST_CODE 0x00000000 +#define REQUEST_SUCCEED 0x80000000 +#define REQUEST_FAILED 0x80000001 +#define TAG_REQUEST_CODE 0x00000000 +#define END_TAG 0x00000000 + +#endif \ No newline at end of file diff --git a/lab7/include/peripherals/mini_uart.h b/lab7/include/peripherals/mini_uart.h new file mode 100644 index 000000000..386b6fade --- /dev/null +++ b/lab7/include/peripherals/mini_uart.h @@ -0,0 +1,19 @@ +#ifndef _P_MINI_UART_H +#define _P_MINI_UART_H + +#include "peripherals/base.h" + +#define AUX_ENABLES (PBASE+0x00215004) +#define AUX_MU_IO_REG (PBASE+0x00215040) +#define AUX_MU_IER_REG (PBASE+0x00215044) +#define AUX_MU_IIR_REG (PBASE+0x00215048) +#define AUX_MU_LCR_REG (PBASE+0x0021504C) +#define AUX_MU_MCR_REG (PBASE+0x00215050) +#define AUX_MU_LSR_REG (PBASE+0x00215054) +#define AUX_MU_MSR_REG (PBASE+0x00215058) +#define AUX_MU_SCRATCH (PBASE+0x0021505C) +#define AUX_MU_CNTL_REG (PBASE+0x00215060) +#define AUX_MU_STAT_REG (PBASE+0x00215064) +#define AUX_MU_BAUD_REG (PBASE+0x00215068) + +#endif /*_P_MINI_UART_H */ \ No newline at end of file diff --git a/lab7/include/printf.h b/lab7/include/printf.h new file mode 100644 index 000000000..dfedbe301 --- /dev/null +++ b/lab7/include/printf.h @@ -0,0 +1,31 @@ +/* + * 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. + * + */ +#ifndef _PRINTF_H +#define _PRINTF_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/lab7/include/reboot.h b/lab7/include/reboot.h new file mode 100644 index 000000000..df4d63a86 --- /dev/null +++ b/lab7/include/reboot.h @@ -0,0 +1,7 @@ +#ifndef _REBOOT_H +#define _REBOOT_H + +void reset ( int ); +void cancel_reset (); + +#endif \ No newline at end of file diff --git a/lab7/include/sched.h b/lab7/include/sched.h new file mode 100644 index 000000000..1c0d56c0d --- /dev/null +++ b/lab7/include/sched.h @@ -0,0 +1,90 @@ +#ifndef _SCHED_H +#define _SCHED_H + +#define THREAD_CPU_CONTEXT 0 + +#ifndef __ASSEMBLER__ + +#define THREAD_SIZE 4096 + +#define NR_TASKS 64 + +#define FIRST_TASK task[0] +#define LAST_TASK task[NR_TASKS-1] + +#define TASK_RUNNING 0 +#define TASK_INTERRUPTIBLE 1 +#define TASK_UNINTERRUPTIBLE 2 +#define TASK_ZOMBIE 3 +#define TASK_STOPPED 4 + +#define PF_KTHREAD 0x00000002 + +extern struct task_struct *current; +extern struct task_struct *task[NR_TASKS]; +extern int nr_tasks; + +struct cpu_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 sp; + unsigned long pc; +}; + +#define MAX_PROCESS_PAGES 128 + +struct user_page { + unsigned long phys_addr; + unsigned long virt_addr; +}; + +struct mm_struct { + unsigned long pgd; + int user_pages_count; + struct user_page user_pages[MAX_PROCESS_PAGES]; + int kernel_pages_count; + unsigned long kernel_pages[MAX_PROCESS_PAGES]; +}; + +struct task_struct { + struct cpu_context cpu_context; + long state; + long counter; + long priority; + long preempt_count; + //unsigned long stack; //remove + unsigned long flags; + long id; + struct mm_struct mm; +}; + +extern void sched_init(); +extern void schedule(); +extern void timer_tick(); +extern void preempt_disable(); +extern void preempt_enable(); +extern void switch_to(struct task_struct *); +extern void cpu_switch_to(struct task_struct *, struct task_struct *); +extern void exit_process(); +extern void kill_zombies(); +extern void update_pgd(unsigned long); + +#define INIT_TASK \ +{ \ +{0,0,0,0,0,0,0,0,0,0,0,0,0},\ +0, 0, 1, 0, PF_KTHREAD, 0, \ +{0,0,{{0}},0,{0}}\ +} + +#endif + +#endif \ No newline at end of file diff --git a/lab7/include/shell.h b/lab7/include/shell.h new file mode 100644 index 000000000..828c56763 --- /dev/null +++ b/lab7/include/shell.h @@ -0,0 +1,6 @@ +#ifndef _SHELL_H +#define _SHELL_H + +void shell_loop (); + +#endif \ No newline at end of file diff --git a/lab7/include/string.h b/lab7/include/string.h new file mode 100644 index 000000000..abf24c93f --- /dev/null +++ b/lab7/include/string.h @@ -0,0 +1,8 @@ +#ifndef _STRING_H +#define _STRING_H + +int stringcmp (const char *, const char *); +int stringncmp (const char *, const char *, unsigned int); +unsigned int strlen(const char *); + +#endif \ No newline at end of file diff --git a/lab7/include/syscall.h b/lab7/include/syscall.h new file mode 100644 index 000000000..a1c2215f5 --- /dev/null +++ b/lab7/include/syscall.h @@ -0,0 +1,37 @@ +#ifndef _SYSCALL_H +#define _SYSCALL_H + +#define __NR_SYSCALLS 8 + +#define SYS_GETPID_NUM 0 +#define SYS_UARTREAD_NUM 1 +#define SYS_UARTWRITE_NUM 2 +#define SYS_EXEC_NUM 3 +#define SYS_FORK_NUM 4 +#define SYS_EXIT_NUM 5 +#define SYS_MBOXCALL_NUM 6 +#define SYS_KILL_NUM 7 + +#ifndef __ASSEMBLER__ + +int getpid(); +unsigned uart_read(char buf[], unsigned size); +unsigned uart_write(const char buf[], unsigned size); +int exec(const char *name, char *const argv[]); +int fork(); +void exit(int status); +int mbox_call(unsigned char ch, volatile unsigned int *mbox); +void kill(int pid); + +int sys_getpid(); +unsigned sys_uartread(char buf[], unsigned size); +unsigned sys_uartwrite(const char buf[], unsigned size); +int sys_exec(const char *name, char *const argv[]); +int sys_fork(); +void sys_exit(int status); +int sys_mbox_call(unsigned char ch, volatile unsigned int *mbox); +void sys_kill(int pid); + +#endif + +#endif \ No newline at end of file diff --git a/lab7/include/timer.h b/lab7/include/timer.h new file mode 100644 index 000000000..9812a457a --- /dev/null +++ b/lab7/include/timer.h @@ -0,0 +1,14 @@ +#ifndef _TIMER_H +#define _TIMER_H + +#define CORE0_TIMER_IRQ_CTRL 0x40000040 + +void timer_init(); +void handle_timer_irq(); +void core_timer_enable(); +void core_timer_disable(); +void set_timer(unsigned int rel_time); +unsigned int read_timer(); +unsigned int read_freq(); + +#endif \ No newline at end of file diff --git a/lab7/include/utils.h b/lab7/include/utils.h new file mode 100644 index 000000000..9d950d477 --- /dev/null +++ b/lab7/include/utils.h @@ -0,0 +1,8 @@ +#ifndef _BOOT_H +#define _BOOT_H + +extern void delay ( unsigned long ); +extern void put32 ( unsigned long, unsigned int ); +extern unsigned int get32 ( unsigned long ); + +#endif /*_BOOT_H */ \ No newline at end of file diff --git a/lab7/initramfs.cpio b/lab7/initramfs.cpio new file mode 100644 index 0000000000000000000000000000000000000000..e1b3477cedccc5f111bdbcd4215c1c3e7bad1e11 GIT binary patch literal 248320 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 H6oG#O><1&u literal 0 HcmV?d00001 diff --git a/lab7/kernel/Makefile b/lab7/kernel/Makefile new file mode 100644 index 000000000..831618b19 --- /dev/null +++ b/lab7/kernel/Makefile @@ -0,0 +1,49 @@ +AARCH64_PREFIX = aarch64-linux-gnu- +CC = $(AARCH64_PREFIX)gcc +LD = $(AARCH64_PREFIX)ld +OBJCPY = $(AARCH64_PREFIX)objcopy +GDB = $(AARCH64_PREFIX)gdb + +CFLAGS = -Wall -nostdlib -nostartfiles -ffreestanding -I../include -mgeneral-regs-only +ASMFLAGS = -I../include + +BUILD_DIR = ../build + +.PHONY: all clean qemu gdb_start + +all: kernel8.img + +clean: + rm kernel8.img $(OBJ_FILES) $(BUILD_DIR)/$(DEP_FILES) \ + ../build/kernel8.elf + +$(BUILD_DIR)/%_c.o: %.c + mkdir -p $(@D) + $(CC) $(CFLAGS) -MMD -c $< -o $@ + +$(BUILD_DIR)/%_s.o: %.S + $(CC) $(ASMFLAGS) -MMD -c $< -o $@ + +C_FILES = $(wildcard *.c) +ASM_FILES = $(wildcard *.S) +OBJ_FILES = $(C_FILES:%.c=$(BUILD_DIR)/%_c.o) +OBJ_FILES += $(ASM_FILES:%.S=$(BUILD_DIR)/%_s.o) + +LIBC_FILES = $(wildcard ../lib/*.c) +LIBASM_FILES = $(wildcard ../lib/*.S) +LIBOBJ_FILES = $(LIBC_FILES:../lib/%.c=$(BUILD_DIR)/%_c.o) +LIBOBJ_FILES += $(LIBASM_FILES:../lib/%.S=$(BUILD_DIR)/%_s.o) + +DEP_FILES = $(OBJ_FILES:%.o=%.d) +-include $(DEP_FILES) + +kernel8.img: linker.ld $(OBJ_FILES) $(LIBOBJ_FILES) + $(LD) -T linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(LIBOBJ_FILES) + $(OBJCPY) $(BUILD_DIR)/kernel8.elf -O binary $@ + +qemu: # all in one qemu command + qemu-system-aarch64 -M raspi3b -kernel kernel8.img -S -s -serial null -serial pty \ + -initrd ../initramfs.cpio -dtb ../bcm2710-rpi-3-b-plus.dtb + +gdb_start: + $(GDB) $(BUILD_DIR)/kernel8.elf diff --git a/lab7/kernel/boot_kernel.S b/lab7/kernel/boot_kernel.S new file mode 100644 index 000000000..27ccfa7f1 --- /dev/null +++ b/lab7/kernel/boot_kernel.S @@ -0,0 +1,88 @@ +#include "mmu.h" +#include "peripherals/base.h" + +.section ".text.boot" + +.globl _start +_start: + mrs x0, mpidr_el1 + and x0, x0, #3 // Check processor id + cbnz x0, proc_hang // Hang for all non-primary CPU + bl from_el2_to_el1 + bl set_exception_vector_table + +master: + mov x2, #VA_START + ldr x0, =__bss_begin + sub x0, x0, x2 + ldr x1, =__bss_end + sub x1, x1, x2 + sub x1, x1, x0 + +memzero: + cbz x1, mmu + str xzr, [x0], #8 + subs x1, x1, #8 + cbnz x1, memzero + +mmu: + mov x0, #VA_START + add sp, x0, #0x80000 + + bl set_tcr + bl set_mair + bl make_page_tables + + mov x0, #SCTLR_MMU_ENABLED + msr sctlr_el1, x0 + + ldr x0, =kernel_main + br x0 + +proc_hang: + wfe + b proc_hang + +set_exception_vector_table: + ldr x0, =exception_vector_table + msr vbar_el1, x0 + ret + +from_el2_to_el1: + mov x0, (1 << 31) // EL1 uses aarch64 + msr hcr_el2, x0 + mov x0, 0x3c5 // EL1h (SPSel = 1) with interrupt disabled + msr spsr_el2, x0 + msr elr_el2, lr + eret // return to EL1 + +set_tcr: + ldr x0, =TCR_CONFIG_DEFAULT + msr tcr_el1, x0 + ret + +set_mair: + ldr x0, =MAIR_VALUE + msr mair_el1, x0 + ret + +make_page_tables: + ldr x0, =pg_dir + and x0, x0, #VA_MASK + add x1, x0, 0x1000 + + ldr x2, =BOOT_PGD_ATTR + orr x2, x1, x2 + str x2, [x0] + + ldr x2, =BOOT_PUD_ATTR + mov x3, 0x00000000 + orr x3, x2, x3 + str x3, [x1] + mov x3, 0x40000000 + orr x3, x2, x3 + str x3, [x1, 8] + + msr ttbr0_el1, x0 + msr ttbr1_el1, x0 + ret \ No newline at end of file diff --git a/lab7/kernel/kernel.c b/lab7/kernel/kernel.c new file mode 100644 index 000000000..490719083 --- /dev/null +++ b/lab7/kernel/kernel.c @@ -0,0 +1,26 @@ +#include "mini_uart.h" +#include "shell.h" +#include "devtree.h" +#include "cpio.h" +#include "mm.h" +#include "timer.h" +#include "exception.h" +#include "fork.h" + +void kernel_main(void) +{ + uart_init(); + devtree_getaddr(); + fdt_traverse(initramfs_callback); + init_mm_reserve(); + timer_init(); + enable_interrupt(); + uart_send_string("OSDI 2022 Spring\n"); + + copy_process(PF_KTHREAD, (unsigned long)&shell_loop, 0/*, 0*/); + + while (1) { + kill_zombies(); + schedule(); + } +} \ No newline at end of file diff --git a/lab7/kernel/linker.ld b/lab7/kernel/linker.ld new file mode 100644 index 000000000..48804b7fb --- /dev/null +++ b/lab7/kernel/linker.ld @@ -0,0 +1,24 @@ +SECTIONS +{ + . = 0xffff000000000000; + . += 0x80000; + + .text.boot : { *(.text.boot) } + .text : { *(.text) } + .rodata : { *(.rodata) } + .data : { *(.data) } + + . = ALIGN(8); + + __bss_begin = .; + .bss : { *(.bss* .bss.*) } + __bss_end = .; + + . = ALIGN(0x00001000); + pg_dir = .; + .data.pgd : { . += (3 * (1 << 12)); } + + __heap_start = .; + __kernel_end = .; + +} diff --git a/lab7/lib/Makefile b/lab7/lib/Makefile new file mode 100644 index 000000000..66d9bcea3 --- /dev/null +++ b/lab7/lib/Makefile @@ -0,0 +1,29 @@ +AARCH64_PREFIX = aarch64-linux-gnu- +CC = $(AARCH64_PREFIX)gcc + +CFLAGS = -Wall -nostdlib -nostartfiles -ffreestanding -I../include -mgeneral-regs-only +ASMFLAGS = -I../include + +BUILD_DIR = ../build + +.PHONY: all clean + +$(BUILD_DIR)/%_c.o: %.c + mkdir -p $(@D) + $(CC) $(CFLAGS) -MMD -c $< -o $@ + +$(BUILD_DIR)/%_s.o: %.S + $(CC) $(ASMFLAGS) -MMD -c $< -o $@ + +C_FILES = $(wildcard *.c) +ASM_FILES = $(wildcard *.S) +OBJ_FILES = $(C_FILES:%.c=$(BUILD_DIR)/%_c.o) +OBJ_FILES += $(ASM_FILES:%.S=$(BUILD_DIR)/%_s.o) + +DEP_FILES = $(OBJ_FILES:%.o=%.d) +-include $(DEP_FILES) + +all: $(OBJ_FILES) + +clean: + rm $(OBJ_FILES) $(BUILD_DIR)/$(DEP_FILES) diff --git a/lab7/lib/cpio.c b/lab7/lib/cpio.c new file mode 100644 index 000000000..5e7d2cb6e --- /dev/null +++ b/lab7/lib/cpio.c @@ -0,0 +1,233 @@ +#include "../include/cpio.h" +#include "mini_uart.h" +#include "../include/sched.h" +#include "fork.h" +#include "string.h" +#include "mmu.h" + +void *DEVTREE_CPIO_BASE = 0; + +unsigned int hexstr_to_uint(char *s, unsigned int len) { + + unsigned int n = 0; + + for (int i=0; i= '0' && s[i] <= '9') { + n += s[i] - '0'; + } else if (s[i] >= 'A' && s[i] <= 'F') { + n += s[i] - 'A' + 10; + } + } + + return n; + +} + +void initramfs_callback(char *node_name, char *prop_name, struct fdt_prop *prop) { + + if (stringncmp(node_name, "chosen", 7) == 0 && + stringncmp(prop_name, "linux,initrd-start", 19) == 0) { + + DEVTREE_CPIO_BASE = (void*)to_lendian(*((unsigned int*)(prop + 1))) + VA_START; + + } + +} + +void cpio_ls() { + + struct cpio_newc_header *header; + unsigned int filesize; + unsigned int namesize; + unsigned int offset; + char *filename; + + header = DEVTREE_CPIO_BASE; + + while (1) { + + filename = ((void*)header) + sizeof(struct cpio_newc_header); + + if (stringncmp((char*)header, CPIO_HEADER_MAGIC, 6) != 0) { + uart_send_string("invalid magic\n"); + break; + } + if (stringncmp(filename, CPIO_FOOTER_MAGIC, 11) == 0) break; + + uart_send_string(filename); + uart_send('\n'); + + namesize = hexstr_to_uint(header->c_namesize, 8); + filesize = hexstr_to_uint(header->c_filesize, 8); + + offset = sizeof(struct cpio_newc_header) + namesize; + if (offset % 4 != 0) + offset = ((offset/4) + 1) * 4; + + if (filesize % 4 != 0) + filesize = ((filesize/4) + 1) * 4; + + offset = offset + filesize; + + header = ((void*)header) + offset; + + } + +} + +void cpio_cat() { + + char input[256]; + char c = '\0'; + int idx = 0; + + struct cpio_newc_header *header; + unsigned int filesize; + unsigned int namesize; + unsigned int offset; + char *filename; + + header = DEVTREE_CPIO_BASE; + + uart_send_string("Filename: "); + + while (1) { + c = uart_recv(); + if (c == '\r' || c == '\n') { + uart_send_string("\n"); + + if (idx < 256) input[idx] = '\0'; + else input[255] = '\0'; + + break; + } else { + uart_send(c); + input[idx++] = c; + } + } + + while (1) { + + filename = ((void*)header) + sizeof(struct cpio_newc_header); + + if (stringncmp((char*)header, CPIO_HEADER_MAGIC, 6) != 0) { + uart_send_string("invalid magic\n"); + break; + } + if (stringncmp(filename, CPIO_FOOTER_MAGIC, 11) == 0) { + uart_send_string("file does not exist!\n"); + break; + } + + namesize = hexstr_to_uint(header->c_namesize, 8); + + offset = sizeof(struct cpio_newc_header) + namesize; + if (offset % 4 != 0) + offset = ((offset/4) + 1) * 4; + + filesize = hexstr_to_uint(header->c_filesize, 8); + + if (stringncmp(filename, input, namesize) == 0) { + + char *content = ((void*)header) + offset; + + for (int i=0; ic_namesize, 8); + + offset = sizeof(struct cpio_newc_header) + namesize; + if (offset % 4 != 0) + offset = ((offset/4) + 1) * 4; + + filesize = hexstr_to_uint(header->c_filesize, 8); + + if (stringncmp(filename, input, namesize) == 0) { + + char *code_loc = ((void*)header) + offset; + unsigned int sp_val = 0x600000; + asm volatile( + "msr elr_el1, %0\n\t" + "msr spsr_el1, xzr\n\t" + "msr sp_el0, %1\n\t" + "eret\n\t" + :: + "r" (code_loc), + "r" (sp_val) + ); + + break; + } + + if (filesize % 4 != 0) + filesize = ((filesize/4) + 1) * 4; + + offset = offset + filesize; + + header = ((void*)header) + offset; + + } + +} diff --git a/lab7/lib/devtree.c b/lab7/lib/devtree.c new file mode 100644 index 000000000..691dc1a7e --- /dev/null +++ b/lab7/lib/devtree.c @@ -0,0 +1,92 @@ +#include "devtree.h" +#include "mini_uart.h" +#include "string.h" + +static void *DEVTREE_ADDRESS = 0; + +unsigned long to_lendian(unsigned long n) { + return ((n>>24)&0x000000FF) | + ((n>>8) &0x0000FF00) | + ((n<<8) &0x00FF0000) | + ((n<<24)&0xFF000000) ; +} + +void devtree_getaddr() { + + asm volatile("MOV %0, x20" : "=r"(DEVTREE_ADDRESS)); + + char magic[4] = {0xd0, 0x0d, 0xfe, 0xed}; + if(stringncmp((char*)DEVTREE_ADDRESS, magic, 4) != 0) { + uart_send_string("magic failed\n"); + } else { + uart_send_string("devtree magic succeed\n"); + } + +} + +void fdt_traverse( void (*callback)(char *, char *, struct fdt_prop *) ) { + + char magic[4] = {0xd0, 0x0d, 0xfe, 0xed}; + struct fdt_header *devtree_header = DEVTREE_ADDRESS; + + if(stringncmp((char*)devtree_header, magic, 4) != 0) { + uart_send_string("devtree magic failed\n"); + return; + } + + void *dt_struct_addr = DEVTREE_ADDRESS + to_lendian(devtree_header->off_dt_struct); + char *dt_string_addr = DEVTREE_ADDRESS + to_lendian(devtree_header->off_dt_strings); + + char *node_name; + char *prop_name; + unsigned int token; + unsigned int off; + + while (1) { + + token = to_lendian(*((unsigned int *)dt_struct_addr)); + + if (token == FDT_BEGIN_NODE) { + + node_name = dt_struct_addr + 4; + off = 4 + strlen(node_name) + 1; + + if (off%4 != 0) + off = ((off/4)+1)*4; + + dt_struct_addr += off; + + } + else if (token == FDT_END_NODE) { + dt_struct_addr += 4; + } + else if (token == FDT_PROP) { + + struct fdt_prop *prop = (struct fdt_prop*)(dt_struct_addr + 4); + + off = 4 + 8 + to_lendian(prop->len); + if (off%4 != 0) + off = ((off/4)+1)*4; + + dt_struct_addr += off; + + prop_name = dt_string_addr + to_lendian(prop->nameoff); + + callback(node_name, prop_name, prop); + + } + else if (token == FDT_NOP) { + dt_struct_addr += 4; + } + else if (token == FDT_END) { + dt_struct_addr += 4; + break; + } + else { + uart_send_string("TOKEN NOT MATCHED\n"); + break; + } + + } + +} \ No newline at end of file diff --git a/lab7/lib/entry.S b/lab7/lib/entry.S new file mode 100644 index 000000000..e564be0fe --- /dev/null +++ b/lab7/lib/entry.S @@ -0,0 +1,195 @@ +#include "entry.h" +#include "syscall.h" + +.macro handle_invalid_entry el, type + kernel_entry \el + mov x0, #\type + mrs x1, esr_el1 + mrs x2, elr_el1 + bl show_invalid_entry_message + b err_hang +.endm + +.macro ventry label + .align 7 + b \label +.endm + +// save general registers to stack +.macro kernel_entry, el + sub sp, sp, #S_FRAME_SIZE + 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] + + .if \el == 0 + mrs x21, sp_el0 + .else + add x21, sp, #S_FRAME_SIZE + .endif + + mrs x22, elr_el1 + mrs x23, spsr_el1 + + stp x30, x21, [sp, 16 * 15] + stp x22, x23, [sp, 16 * 16] +.endm + +// load general registers from stack +.macro kernel_exit, el + ldp x22, x23, [sp, 16 * 16] + ldp x30, x21, [sp, 16 * 15] + + .if \el == 0 + msr sp_el0, x21 + .endif + + msr elr_el1, x22 + msr spsr_el1, x23 + + ldp x0, x1, [sp ,16 * 0] + ldp x2, x3, [sp ,16 * 1] + ldp x4, x5, [sp ,16 * 2] + ldp x6, x7, [sp ,16 * 3] + ldp x8, x9, [sp ,16 * 4] + ldp x10, x11, [sp ,16 * 5] + ldp x12, x13, [sp ,16 * 6] + ldp x14, x15, [sp ,16 * 7] + ldp x16, x17, [sp ,16 * 8] + ldp x18, x19, [sp ,16 * 9] + ldp x20, x21, [sp ,16 * 10] + ldp x22, x23, [sp ,16 * 11] + ldp x24, x25, [sp ,16 * 12] + ldp x26, x27, [sp ,16 * 13] + ldp x28, x29, [sp ,16 * 14] + add sp, sp, #S_FRAME_SIZE + eret +.endm + +.align 11 // vector table should be aligned to 0x800 +.globl exception_vector_table +exception_vector_table: + ventry sync_invalid_el1t // Synchronous EL1t + ventry irq_invalid_el1t // IRQ EL1t + ventry fiq_invalid_el1t // FIQ EL1t + ventry error_invalid_el1t // Error EL1t + + ventry sync_invalid_el1h // Synchronous EL1h + ventry el1_irq // IRQ EL1h + ventry fiq_invalid_el1h // FIQ EL1h + ventry error_invalid_el1h // Error EL1h + + ventry el0_sync // Synchronous 64-bit EL0 + ventry el0_irq // IRQ 64-bit EL0 + ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 + ventry error_invalid_el0_64 // Error 64-bit EL0 + + ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 + ventry irq_invalid_el0_32 // IRQ 32-bit EL0 + ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 + ventry error_invalid_el0_32 // Error 32-bit EL0 + +sync_invalid_el1t: + handle_invalid_entry 1, SYNC_INVALID_EL1t + +irq_invalid_el1t: + handle_invalid_entry 1, IRQ_INVALID_EL1t + +fiq_invalid_el1t: + handle_invalid_entry 1, FIQ_INVALID_EL1t + +error_invalid_el1t: + handle_invalid_entry 1, ERROR_INVALID_EL1t + +sync_invalid_el1h: + handle_invalid_entry 1, SYNC_INVALID_EL1h + +el1_irq: + kernel_entry 1 + bl handle_irq + kernel_exit 1 + +fiq_invalid_el1h: + handle_invalid_entry 1, FIQ_INVALID_EL1h + +error_invalid_el1h: + handle_invalid_entry 1, ERROR_INVALID_EL1h + +el0_sync: + kernel_entry 0 + mrs x25, esr_el1 + lsr x24, x25, #ESR_ELx_EC_SHIFT + cmp x24, #ESR_ELx_EC_SVC64 + b.eq el0_svc + handle_invalid_entry 0, SYNC_ERROR + +el0_irq: + kernel_entry 0 + bl handle_irq + kernel_exit 0 + +fiq_invalid_el0_64: + handle_invalid_entry 0, FIQ_INVALID_EL0_64 + +error_invalid_el0_64: + handle_invalid_entry 0, ERROR_INVALID_EL0_64 + +sync_invalid_el0_32: + handle_invalid_entry 0, SYNC_INVALID_EL0_32 + +irq_invalid_el0_32: + handle_invalid_entry 0, IRQ_INVALID_EL0_32 + +fiq_invalid_el0_32: + handle_invalid_entry 0, FIQ_INVALID_EL0_32 + +error_invalid_el0_32: + handle_invalid_entry 0, ERROR_INVALID_EL0_32 + +sc_nr .req x25 +scno .req x26 +stbl .req x27 + +el0_svc: + adr stbl, sys_call_table + uxtw scno, w8 + mov sc_nr, #__NR_SYSCALLS + bl enable_interrupt + cmp scno, sc_nr + b.hs ni_sys + + ldr x16, [stbl, scno, lsl #3] + blr x16 + b ret_from_syscall +ni_sys: + handle_invalid_entry 0, SYSCALL_ERROR +ret_from_syscall: + bl disable_interrupt + str x0, [sp, #S_X0] + kernel_exit 0 + +.globl ret_from_fork +ret_from_fork: + bl schedule_tail + cbz x19, ret_to_user + mov x0, x20 + blr x19 // in theory should not return + //b err_hang // hang fail-safe +ret_to_user: + bl disable_interrupt + kernel_exit 0 + +.globl err_hang +err_hang: b err_hang diff --git a/lab7/lib/exception.c b/lab7/lib/exception.c new file mode 100644 index 000000000..a820ba662 --- /dev/null +++ b/lab7/lib/exception.c @@ -0,0 +1,49 @@ +#include "exception.h" +#include "mini_uart.h" +#include "mailbox.h" +#include "utils.h" +#include "timer.h" +#include "peripherals/exception.h" +#include "peripherals/mini_uart.h" + +void enable_interrupt() {asm volatile("msr DAIFClr, 0xf");} +void disable_interrupt() {asm volatile("msr DAIFSet, 0xf");} + +const char *entry_error_messages[] = { + "SYNC_INVALID_EL1t", + "IRQ_INVALID_EL1t", + "FIQ_INVALID_EL1t", + "ERROR_INVALID_EL1t", + + "SYNC_INVALID_EL1h", + "IRQ_INVALID_EL1h", + "FIQ_INVALID_EL1h", + "ERROR_INVALID_EL1h", + + "SYNC_INVALID_EL0_64", + "IRQ_INVALID_EL0_64", + "FIQ_INVALID_EL0_64", + "ERROR_INVALID_EL0_64", + + "SYNC_INVALID_EL0_32", + "IRQ_INVALID_EL0_32", + "FIQ_INVALID_EL0_32", + "ERROR_INVALID_EL0_32", + + "SYNC_ERROR", + "SYSCALL_ERROR" +}; + +void show_invalid_entry_message(int type, unsigned long esr, unsigned long addr) { + printf("%s, ESR: 0x%x, address: 0x%x\n", entry_error_messages[type], esr, addr); +} + +void handle_irq() { + + if (get32(CORE0_INTERRUPT_SRC)&INTERRUPT_SOURCE_CNTPNSIRQ) { + handle_timer_irq(); + } else { + printf("unknown irq encountered"); + } + +} \ No newline at end of file diff --git a/lab7/lib/fork.c b/lab7/lib/fork.c new file mode 100644 index 000000000..acdf566d6 --- /dev/null +++ b/lab7/lib/fork.c @@ -0,0 +1,176 @@ +#include "fork.h" +#include "mm.h" +#include "mini_uart.h" +#include "../include/sched.h" +#include "../include/entry.h" +#include "mmu.h" + +int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg/*, unsigned long stack*/) { + + preempt_disable(); + struct task_struct *p; + + p = (struct task_struct *) allocate_kernel_page(); + if (p == NULL) + return -1; + + memzero((unsigned long)p, sizeof(struct task_struct)); + struct pt_regs *childregs = task_pt_regs(p); + + if (clone_flags & PF_KTHREAD) { + p->cpu_context.x19 = fn; + p->cpu_context.x20 = arg; + } else { + struct pt_regs *cur_regs = task_pt_regs(current); + // *childregs = *cur_regs; (object file generates memcpy) + // therefore the for loop is used below + for(int i=0; iregs[0] = 0; // return value 0 + copy_virt_memory(p); + } + + p->flags = clone_flags; + p->priority = current->priority; + p->state = TASK_RUNNING; + p->counter = p->priority; + p->preempt_count = 1; + + p->cpu_context.pc = (unsigned long)ret_from_fork; + p->cpu_context.sp = (unsigned long)childregs; + + int pid = nr_tasks++; + task[pid] = p; + p->id = pid; + preempt_enable(); + + return pid; + +} + +int move_to_user_mode(unsigned long start, unsigned long size, unsigned long pc) { + + struct pt_regs *regs = task_pt_regs(current); + regs->pc = pc; // virt pc + regs->pstate = PSR_MODE_EL0t; + regs->sp = 0xfffffffff000; + + unsigned long pages = (unsigned long)malloc(size); // phys + if (pages == NULL) + return -1; + + unsigned long va; // might need to map more? bss? currently mapping just enough + for(va=0; vasp; va+=PAGE_SIZE) { + map_page(current, va, stack_bot+(i*PAGE_SIZE)); + i++; + } + + // vc identity mapping + //printf("user page count %d\n", current->mm.user_pages_count); + for(unsigned long va=0x3c000000; va<0x3f000000; va+=PAGE_SIZE) { + unsigned long pgd; + if (!current->mm.pgd) { + pgd = (unsigned long)malloc(PAGE_SIZE); + //printf("pgd not found, created at phys address 0x%x\n", pgd); + memzero(pgd+VA_START, PAGE_SIZE); + current->mm.pgd = pgd; + current->mm.kernel_pages[++current->mm.kernel_pages_count] = current->mm.pgd; + } + pgd = current->mm.pgd; + //printf("pgd at 0x%x\n", pgd); + int new_table; + unsigned long pud = map_table((unsigned long *)(pgd + VA_START), PGD_SHIFT, va, &new_table); + //printf("pud at 0x%x\n", pud); + if (new_table) { + current->mm.kernel_pages[++current->mm.kernel_pages_count] = pud; + } + unsigned long pmd = map_table((unsigned long *)(pud + VA_START), PUD_SHIFT, va, &new_table); + //printf("pmd at 0x%x\n", pmd); + if (new_table) { + current->mm.kernel_pages[++current->mm.kernel_pages_count] = pmd; + } + unsigned long pte = map_table((unsigned long *)(pmd + VA_START), PMD_SHIFT, va, &new_table); + //printf("pte at 0x%x\n", pte); + if (new_table) { + current->mm.kernel_pages[++current->mm.kernel_pages_count] = pte; + } + map_table_entry((unsigned long *)(pte + VA_START), va, va); + //struct user_page p = {page, va}; + //task->mm.user_pages[task->mm.user_pages_count++] = p; + //if (va == 0x3c25e000) printf("0x%x, 0x%x, 0x%x, 0x%x\n", pgd, pud, pmd, pte); + } + //printf("user page count %d\n", current->mm.user_pages_count); + //memcpy(pages+VA_START, start, size); // move code to pages + for(int i=0; imm.pgd); + return 0; + +} + +struct pt_regs *task_pt_regs(struct task_struct *tsk) { + + unsigned long p = (unsigned long)tsk + THREAD_SIZE - sizeof(struct pt_regs); + return (struct pt_regs *)p; + +} + +void new_user_process(unsigned long func){ + printf("Kernel process started, moving to user mode.\n"); + int err = move_to_user_mode(func, 4096, func); + if (err < 0){ + printf("Error while moving process to user mode\n\r"); + } +} + +int copy_virt_memory(struct task_struct *dst) { + struct task_struct* src = current; + for (int i=0; imm.user_pages_count; i++) { + unsigned long kernel_va = allocate_user_page(dst, src->mm.user_pages[i].virt_addr); + if(kernel_va == 0) return -1; + memcpy(kernel_va, src->mm.user_pages[i].virt_addr, PAGE_SIZE); + } + for(unsigned long va=0x3c000000; va<0x3f000000; va+=PAGE_SIZE) { + unsigned long pgd; + if (!dst->mm.pgd) { + pgd = (unsigned long)malloc(PAGE_SIZE); + //printf("pgd not found, created at phys address 0x%x\n", pgd); + memzero(pgd+VA_START, PAGE_SIZE); + dst->mm.pgd = pgd; + dst->mm.kernel_pages[++dst->mm.kernel_pages_count] = dst->mm.pgd; + } + pgd = dst->mm.pgd; + //printf("pgd at 0x%x\n", pgd); + int new_table; + unsigned long pud = map_table((unsigned long *)(pgd + VA_START), PGD_SHIFT, va, &new_table); + //printf("pud at 0x%x\n", pud); + if (new_table) { + dst->mm.kernel_pages[++dst->mm.kernel_pages_count] = pud; + } + unsigned long pmd = map_table((unsigned long *)(pud + VA_START), PUD_SHIFT, va, &new_table); + //printf("pmd at 0x%x\n", pmd); + if (new_table) { + dst->mm.kernel_pages[++dst->mm.kernel_pages_count] = pmd; + } + unsigned long pte = map_table((unsigned long *)(pmd + VA_START), PMD_SHIFT, va, &new_table); + //printf("pte at 0x%x\n", pte); + if (new_table) { + dst->mm.kernel_pages[++dst->mm.kernel_pages_count] = pte; + } + map_table_entry((unsigned long *)(pte + VA_START), va, va); + //struct user_page p = {page, va}; + //task->mm.user_pages[task->mm.user_pages_count++] = p; + if (va == 0x3c25e000) printf("0x%x, 0x%x, 0x%x, 0x%x\n", pgd, pud, pmd, pte); + } + return 0; +} \ No newline at end of file diff --git a/lab7/lib/mailbox.c b/lab7/lib/mailbox.c new file mode 100644 index 000000000..e593af037 --- /dev/null +++ b/lab7/lib/mailbox.c @@ -0,0 +1,49 @@ +#include "peripherals/mailbox.h" +#include "mailbox.h" +#include "mini_uart.h" + +int mailbox_call (volatile unsigned int *mailbox) { + unsigned int msg = ((unsigned long)mailbox & ~0xF) | (0x8 & 0xF); + while (*MAILBOX_STATUS & MAILBOX_FULL) ; + *MAILBOX_WRITE = msg; + while (1) { + while (*MAILBOX_STATUS & MAILBOX_EMPTY) {} + if (msg == *MAILBOX_READ) { + return mailbox[1]; + } + } +} + +void get_board_revision () +{ + volatile unsigned int __attribute__((aligned(16))) mailbox[7]; + mailbox[0] = 7 * 4; + mailbox[1] = REQUEST_CODE; + mailbox[2] = GET_BOARD_REVISION; + mailbox[3] = 4; + mailbox[4] = TAG_REQUEST_CODE; + mailbox[5] = 0; + mailbox[6] = END_TAG; + + if (mailbox_call(mailbox) == REQUEST_SUCCEED) { + printf("Board Revision:\t\t%x\n", mailbox[5]); + } +} + +void get_arm_memory () +{ + volatile unsigned int __attribute__((aligned(16))) mailbox[8]; + mailbox[0] = 8 * 4; + mailbox[1] = REQUEST_CODE; + mailbox[2] = GET_ARM_MEMORY; + mailbox[3] = 8; + mailbox[4] = TAG_REQUEST_CODE; + mailbox[5] = 0; + mailbox[6] = 0; + mailbox[7] = END_TAG; + + if (mailbox_call(mailbox) == REQUEST_SUCCEED) { + printf("Memory Base Addresss:\t%x\n", mailbox[5]); + printf("Memory Size:\t\t%x\n", mailbox[6]); + } +} \ No newline at end of file diff --git a/lab7/lib/math.c b/lab7/lib/math.c new file mode 100644 index 000000000..6b6919044 --- /dev/null +++ b/lab7/lib/math.c @@ -0,0 +1,19 @@ +#include "math.h" + +int log(int n, int base) { + int x = 1; + int ret = 0; + while (x <= n) { + x *= base; + ret++; + } + return ret; +} + +int pow(int base, int pow) { + int ret = 1; + for (int i=0; i avail) { + uart_send_string("not enough memory\n"); + } else { + __heap_ptr += size; + avail -= size; + } + + return ptr; + +} \ No newline at end of file diff --git a/lab7/lib/mini_uart.c b/lab7/lib/mini_uart.c new file mode 100644 index 000000000..c209f3a90 --- /dev/null +++ b/lab7/lib/mini_uart.c @@ -0,0 +1,69 @@ +#include "utils.h" +#include "printf.h" +#include "peripherals/mini_uart.h" +#include "peripherals/gpio.h" +#include "peripherals/exception.h" + +void uart_send ( char c ) +{ + if (c == '\n') uart_send('\r'); + + while (1) { + if (get32(AUX_MU_LSR_REG)&0x20) + break; + } + + put32(AUX_MU_IO_REG,c); +} + +char uart_recv ( void ) +{ + while(1) { + if (get32(AUX_MU_LSR_REG)&0x01) + break; + } + return (get32(AUX_MU_IO_REG)&0xFF); +} + +void uart_send_string ( char* str ) +{ + for (int i = 0; str[i] != '\0'; i++) { + uart_send((char)str[i]); + } +} + +void printf(char *fmt, ...) { + char temp[128]; + __builtin_va_list args; + __builtin_va_start(args, fmt); + vsprintf(temp,fmt,args); + uart_send_string(temp); +} + +void uart_init ( void ) +{ + unsigned int selector; + + selector = get32(GPFSEL1); + selector &= ~(7<<12); // clean gpio14 + selector |= 2<<12; // set alt5 for gpio14 + selector &= ~(7<<15); // clean gpio15 + selector |= 2<<15; // set alt5 for gpio 15 + put32(GPFSEL1,selector); + + put32(GPPUD,0); + delay(150); + put32(GPPUDCLK0,(1<<14)|(1<<15)); + delay(150); + put32(GPPUDCLK0,0); + + put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) + put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) + put32(AUX_MU_IER_REG,0); //Enable receive and disable transmit interrupts + put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode + put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high + put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 + put32(AUX_MU_IIR_REG,6); //Interrupt identify no fifo + put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver + +} diff --git a/lab7/lib/mm.S b/lab7/lib/mm.S new file mode 100644 index 000000000..56eef8286 --- /dev/null +++ b/lab7/lib/mm.S @@ -0,0 +1,14 @@ +.globl memcpy +memcpy: + ldr x3, [x1], #8 + str x3, [x0], #8 + subs x2, x2, #8 + b.gt memcpy + ret + +.globl memzero +memzero: + str xzr, [x0], #8 + subs x1, x1, #8 + b.gt memzero + ret \ No newline at end of file diff --git a/lab7/lib/mm.c b/lab7/lib/mm.c new file mode 100644 index 000000000..7c0ef41e6 --- /dev/null +++ b/lab7/lib/mm.c @@ -0,0 +1,394 @@ +#include "mm.h" +#include "mmu.h" +#include "math.h" +#include "memory.h" +#include "mini_uart.h" + +static unsigned int n_frames = 0; +static unsigned int max_size = 0; +static struct frame* frame_list[MAX_ORDER] = {NULL}; +static struct frame frame_array[(MEM_REGION_END-MEM_REGION_BEGIN)/PAGE_SIZE]; +static struct dynamic_pool pools[MAX_POOLS] = { {ALLOCABLE, 0, 0, 0, 0, {NULL}, NULL} }; +static unsigned int reserved_num = 0; +static void* reserved_se[MAX_RESERVABLE][2] = {{0x0, 0x0}}; // expects to be sorted and addresses [,) +extern char __kernel_end; +static char *__kernel_end_ptr = &__kernel_end; + +unsigned long allocate_user_page(struct task_struct *task, unsigned long va) { + unsigned long page = (unsigned long)malloc(PAGE_SIZE); + if (page == 0) + return NULL; + map_page(task, va, page); + return page + VA_START; +} + +unsigned long allocate_kernel_page() { + unsigned long page = (unsigned long)malloc(PAGE_SIZE); + if(page == NULL) + return NULL; + return page + VA_START; +} + +void *malloc(unsigned int size) { + + if (size > max_size) { + printf("[error] Request exceeded allocable continuous size %d.\n", (int)max_size); + return NULL; + } + + int req_order = 0; + for(unsigned int i=PAGE_SIZE; i= MAX_ORDER) { + printf("[error] No memory allocable.\n"); + return NULL; + } + + while (t != req_order) { + struct frame* l_tmp = frame_list[t]; + frame_list[t] = l_tmp->next; + if (frame_list[t] != NULL) + frame_list[t]->prev = NULL; + //printf("[info] Split at order %d, new head is 0x%x.\n", t+1, frame_list[t]); + + unsigned int off = pow(2, l_tmp->val-1); + struct frame* r_tmp = &frame_array[l_tmp->index+off]; + + l_tmp->val -= 1; + l_tmp->state = ALLOCABLE; + l_tmp->prev = NULL; + l_tmp->next = r_tmp; + + r_tmp->val = l_tmp->val; + r_tmp->state = ALLOCABLE; + r_tmp->prev = l_tmp; + r_tmp->next = NULL; + + t--; + if (frame_list[t] != NULL) + frame_list[t]->prev = r_tmp; + r_tmp->next = frame_list[t]; + frame_list[t] = l_tmp; + } + + struct frame* ret = frame_list[req_order]; + frame_list[req_order] = ret->next; + if (frame_list[req_order] != NULL) + frame_list[req_order]->prev = NULL; + + ret->val = ret->val; + ret->state = ALLOCATED; + ret->prev = NULL; + ret->next = NULL; + + //printf("[info] allocated address: 0x%x\n", MEM_REGION_BEGIN+PAGE_SIZE*ret->index); + + return (void*)MEM_REGION_BEGIN+PAGE_SIZE*ret->index; + +} + +void free(void *address) { + + unsigned int idx = ((unsigned long long)address-MEM_REGION_BEGIN) / PAGE_SIZE; + struct frame* target = &frame_array[idx]; + + if (target->state == ALLOCABLE || target->state == C_NALLOCABLE) { + printf("[error] invalid free of already freed memory.\n"); + return; + } + //printf("=========================================================\n"); + //printf("[info] Now freeing address 0x%x with frame index %d.\n", address, (int)idx); + + for (int i=target->val; ival+1, fr_buddy->state); + + if (i < MAX_ORDER-1 && fr_buddy->state == ALLOCABLE && i== fr_buddy->val) { + + //printf("[info] Merging from order %d. Frame indices %d, %d.\n", i+1, (int)buddy, (int)idx); + + if (fr_buddy->prev != NULL) { + fr_buddy->prev->next = fr_buddy->next; + } else { + frame_list[fr_buddy->val] = fr_buddy->next; + } + + if (fr_buddy->next != NULL) { + fr_buddy->next->prev = fr_buddy->prev; + } + + fr_buddy->prev = NULL; + fr_buddy->next = NULL; + fr_buddy->val = C_NALLOCABLE; + fr_buddy->state = C_NALLOCABLE; + target->val = C_NALLOCABLE; + target->state = C_NALLOCABLE; + + if (fr_buddy->index < target->index) { + idx = fr_buddy->index; + target = fr_buddy; + } + + //printf("[info] Frame index of next merge target is %d.\n", (int)idx); + + } else { + + target->val = i; + target->state = ALLOCABLE; + target->prev = NULL; + target->next = frame_list[i]; + if (frame_list[i] != NULL) + frame_list[i]->prev = target; + frame_list[i] = target; + //printf("[info] Frame index %d pushed to frame list of order %d.\n", + // (int)target->index, (int)i+1); + break; + + } + + } + + //printf("[info] Free finished.\n"); + /*for (int i=0; i < MAX_ORDER; i++) { + if (frame_list[i] != NULL) + printf("[info] Head of order %d has frame array index %d.\n",i+1,frame_list[i]->index); + else + printf("[info] Head of order %d has frame array index null.\n",i+1); + }*/ + +} + +void init_mm() { + + n_frames = (MEM_REGION_END-MEM_REGION_BEGIN) / PAGE_SIZE; + unsigned int mul = (unsigned int)pow(2, MAX_ORDER-1); + printf("[info] Frame array start address 0x%x.\n", frame_array); + for (unsigned int i=0; ichunk_size = size; + pool->chunks_per_page = PAGE_SIZE / size; + pool->chunks_allocated = 0; + pool->page_new_chunk_off = 0; + pool->pages_used = 0; + pool->free_head = NULL; +} + +int register_chunk(unsigned int size) { + + unsigned int nsize = 0; + if (size <= 8) nsize = 8; + else { + int rem = size % 4; + if (rem != 0) nsize = (size/4 + 1)*4; + else nsize = size; + } + + if (nsize >= PAGE_SIZE) { + printf("[error] Normalized chunk size request leq page size.\n"); + return -1; + } + + for (int i=0; ifree_head != NULL) { + void *ret = (void*) pool->free_head; + pool->free_head = pool->free_head->next; + //printf("[info] allocate address 0x%x from pool free list.\n", ret); + return ret; + } + + if (pool->chunks_allocated >= MAX_POOL_PAGES*pool->chunks_per_page) { + //printf("[error] Pool maximum reached.\n"); + return NULL; + } + + + if (pool->chunks_allocated >= pool->pages_used*pool->chunks_per_page) { + pool->page_base_addrs[pool->pages_used] = malloc(PAGE_SIZE); + //printf("[info] allocate new page for pool with base address 0x%x.\n", + // pool->page_base_addrs[pool->pages_used]); + pool->pages_used++; + pool->page_new_chunk_off = 0; + } + + void *ret = pool->page_base_addrs[pool->pages_used - 1] + + pool->chunk_size*pool->page_new_chunk_off; + pool->page_new_chunk_off++; + pool->chunks_allocated++; + + //printf("[info] allocate new address 0x%x from pool.\n", ret); + + return ret; + +} + +void chunk_free(void *address) { + + int target = -1; + + void *prefix_addr = (void *)((unsigned long long)address & ~0xFFF); + + for (unsigned int i=0; ifree_head; + pool->free_head = (struct node*) address; + pool->free_head->next = old_head; + pool->chunks_allocated--; + +} + +void memory_reserve(void* start, void* end) { + if (reserved_num >= MAX_RESERVABLE) { + printf("[error] Max reservable locations already reached.\n"); + return; + } + reserved_se[reserved_num][0] = start; + reserved_se[reserved_num][1] = end; + reserved_num++; +} + +void init_mm_reserve() { + + max_size = PAGE_SIZE * pow(2, MAX_ORDER-1); + n_frames = (MEM_REGION_END-MEM_REGION_BEGIN) / PAGE_SIZE; + + memory_reserve((void*)0x0, (void*)((unsigned long long)__kernel_end_ptr&VA_MASK)); // spin tables, kernel image + memory_reserve((void*)0x20000000, (void*)0x20010000); // hard code reserve initramfs + + for (unsigned int i=0; i= reserved_se[i][0] && addr < reserved_se[i][1]) { + frame_array[j].state = RESERVED; + } + if (addr >= reserved_se[i][1]) break; + } + } + + for (int i=0; istate == RESERVED) continue; + if (target->state == C_NALLOCABLE) continue; + + for (int i=target->val; istate == ALLOCABLE && i== fr_buddy->val) { + + if (fr_buddy->prev != NULL) { + fr_buddy->prev->next = fr_buddy->next; + } else { + frame_list[fr_buddy->val] = fr_buddy->next; + } + + if (fr_buddy->next != NULL) { + fr_buddy->next->prev = fr_buddy->prev; + } + + fr_buddy->prev = NULL; + fr_buddy->next = NULL; + fr_buddy->val = C_NALLOCABLE; + fr_buddy->state = C_NALLOCABLE; + target->val = C_NALLOCABLE; + target->state = C_NALLOCABLE; + + if (fr_buddy->index < target->index) { + idx = fr_buddy->index; + target = fr_buddy; + } + + } else { + + target->val = i; + target->state = ALLOCABLE; + target->prev = NULL; + target->next = frame_list[i]; + if (frame_list[i] != NULL) + frame_list[i]->prev = target; + frame_list[i] = target; + break; + + } + + } + + } + +} \ No newline at end of file diff --git a/lab7/lib/mmu.c b/lab7/lib/mmu.c new file mode 100644 index 000000000..ac1978b14 --- /dev/null +++ b/lab7/lib/mmu.c @@ -0,0 +1,74 @@ +#include "mmu.h" +#include "mm.h" +#include "mini_uart.h" + +void map_page(struct task_struct *task, unsigned long va, unsigned long page) { + //printf("===================\n"); + //printf("map virtual address 0x%x to phys address 0x%x\n", va, page); + unsigned long pgd; + if (!task->mm.pgd) { + pgd = (unsigned long)malloc(PAGE_SIZE); + //printf("pgd not found, created at phys address 0x%x\n", pgd); + memzero(pgd+VA_START, PAGE_SIZE); + task->mm.pgd = pgd; + task->mm.kernel_pages[++task->mm.kernel_pages_count] = task->mm.pgd; + } + pgd = task->mm.pgd; + //printf("pgd at 0x%x\n", pgd); + int new_table; + unsigned long pud = map_table((unsigned long *)(pgd + VA_START), PGD_SHIFT, va, &new_table); + //printf("pud at 0x%x\n", pud); + if (new_table) { + task->mm.kernel_pages[++task->mm.kernel_pages_count] = pud; + } + unsigned long pmd = map_table((unsigned long *)(pud + VA_START), PUD_SHIFT, va, &new_table); + //printf("pmd at 0x%x\n", pmd); + if (new_table) { + task->mm.kernel_pages[++task->mm.kernel_pages_count] = pmd; + } + unsigned long pte = map_table((unsigned long *)(pmd + VA_START), PMD_SHIFT, va, &new_table); + //printf("pte at 0x%x\n", pte); + if (new_table) { + task->mm.kernel_pages[++task->mm.kernel_pages_count] = pte; + } + map_table_entry((unsigned long *)(pte + VA_START), va, page); + struct user_page p = {page, va}; + task->mm.user_pages[task->mm.user_pages_count++] = p; + //printf("user pages count %d\n", task->mm.user_pages_count-1); +} + +unsigned long map_table(unsigned long *table, unsigned long shift, unsigned long va, int* new_table) { + unsigned long index = va >> shift; + index = index & (512 - 1); + if (!table[index]) { + *new_table = 1; + unsigned long next_level_table = (unsigned long)malloc(PAGE_SIZE); + memzero(next_level_table+VA_START, PAGE_SIZE); + unsigned long entry = next_level_table | PD_TABLE; + table[index] = entry; + return next_level_table; + } else { + *new_table = 0; + } + return table[index] & PAGE_MASK; +} + +void map_table_entry(unsigned long *pte, unsigned long va, unsigned long pa) { + unsigned long index = va >> 12; + index = index & (512 - 1); + unsigned long entry = pa | PTE_USR_RW_PTE; + pte[index] = entry; + //printf("0x%x[%d] contains 0x%x\n", pte, index, entry); +} + +unsigned long va2phys(unsigned long va) { + printf("translate va: 0x%x\n", va); + unsigned long pgd; + asm volatile("mrs %0, ttbr0_el1\n\t": "=r" (pgd) :: "memory"); + unsigned long pud = ((unsigned long*)(pgd+VA_START))[va>>PGD_SHIFT&(512-1)]&PAGE_MASK; + unsigned long pmd = ((unsigned long*)(pud+VA_START))[va>>PUD_SHIFT&(512-1)]&PAGE_MASK; + unsigned long pte = ((unsigned long*)(pmd+VA_START))[va>>PMD_SHIFT&(512-1)]&PAGE_MASK; + unsigned long phys = ((unsigned long*)(pte+VA_START))[va>>12&(512-1)]&PAGE_MASK; + printf("0x%x, 0x%x, 0x%x, 0x%x, 0x%x\n", pgd, pud, pmd, pte, phys); + return phys; +} diff --git a/lab7/lib/printf.c b/lab7/lib/printf.c new file mode 100644 index 000000000..794a37dff --- /dev/null +++ b/lab7/lib/printf.c @@ -0,0 +1,152 @@ +/* + * 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 "printf.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) { + // 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_start(args, fmt): "..." is pointed by args + //__builtin_va_arg(args,int): ret=(int)*args;args++;return ret; + __builtin_va_list args; + __builtin_va_start(args, fmt); + return vsprintf(dst,fmt,args); +} \ No newline at end of file diff --git a/lab7/lib/reboot.c b/lab7/lib/reboot.c new file mode 100644 index 000000000..043a09fb7 --- /dev/null +++ b/lab7/lib/reboot.c @@ -0,0 +1,18 @@ +#define PM_PASSWORD 0x5a000000 +#define PM_RSTC 0x3F10001c +#define PM_WDOG 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 +} diff --git a/lab7/lib/sched.S b/lab7/lib/sched.S new file mode 100644 index 000000000..7e4392f23 --- /dev/null +++ b/lab7/lib/sched.S @@ -0,0 +1,32 @@ +#include "sched.h" + +.global cpu_switch_to +cpu_switch_to: + mov x10, #THREAD_CPU_CONTEXT + add x8, x0, x10 + mov x9, sp + stp x19, x20, [x8], #16 // store callee-saved registers + stp x21, x22, [x8], #16 + stp x23, x24, [x8], #16 + stp x25, x26, [x8], #16 + stp x27, x28, [x8], #16 + stp x29, x9, [x8], #16 + str x30, [x8] + add x8, x1, x10 + ldp x19, x20, [x8], #16 // restore callee-saved registers + ldp x21, x22, [x8], #16 + ldp x23, x24, [x8], #16 + ldp x25, x26, [x8], #16 + ldp x27, x28, [x8], #16 + ldp x29, x9, [x8], #16 + ldr x30, [x8] + mov sp, x9 + ret + +.global update_pgd +update_pgd: + 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 \ No newline at end of file diff --git a/lab7/lib/sched.c b/lab7/lib/sched.c new file mode 100644 index 000000000..7f1b3e08a --- /dev/null +++ b/lab7/lib/sched.c @@ -0,0 +1,116 @@ +#include "../include/sched.h" +#include "mm.h" +#include "mmu.h" +#include "exception.h" +#include "mini_uart.h" + +static struct task_struct init_task = INIT_TASK; +struct task_struct *current = &(init_task); +struct task_struct *task[NR_TASKS] = {&(init_task), }; +int nr_tasks = 1; + +void preempt_disable() { + current->preempt_count++; +} + +void preempt_enable() { + current->preempt_count--; +} + +void _schedule() { + + int next, c; + struct task_struct *p; + while (1) { + c = -1; + next = 0; + for (int i=0; istate == TASK_RUNNING && p->counter > c) { + c = p->counter; + next = i; + } + } + if (c) { + break; + } + for (int i=0; icounter = (p->counter >> 1) + p->priority; + } + } + preempt_disable(); // should be fine, if anything breaks move this to the top + switch_to(task[next]); + preempt_enable(); + +} + +void schedule() { + current->counter = 0; + _schedule(); +} + +void switch_to(struct task_struct *next) { + if (current == next) + return; + struct task_struct *prev = current; + current = next; + update_pgd(next->mm.pgd); + cpu_switch_to(prev, next); +} + +void schedule_tail() { + preempt_enable(); +} + +void timer_tick() { + + --current->counter; + if (current->counter > 0 || current->preempt_count > 0) + return; + + current->counter = 0; + enable_interrupt(); + _schedule(); + disable_interrupt(); + +} + +void exit_process() { + // should only be accessed using syscall + // preempt_disable(); + current->state = TASK_ZOMBIE; + //free((void*)current->stack); // free resources + // preempt_enable(); + schedule(); +} + +void kill_zombies() { + + struct task_struct *p; + for (int i=0; istate == TASK_ZOMBIE) { + printf("Zombie found with pid: %d.\n", p->id); + // for loop free user pages and kernel pages + for (int i=0; imm.user_pages_count; i++) { + free((void*)(p->mm.user_pages[i].phys_addr+VA_START)); + } + for (int i=0; imm.kernel_pages_count; i++) { + free((void*)(p->mm.kernel_pages[i]+VA_START)); + } + free(p); // free task struct + task[i] = NULL; + } + + } + +} \ No newline at end of file diff --git a/lab7/lib/shell.c b/lab7/lib/shell.c new file mode 100644 index 000000000..5569fafb3 --- /dev/null +++ b/lab7/lib/shell.c @@ -0,0 +1,155 @@ +#include "shell.h" +#include "mini_uart.h" +#include "utils.h" +#include "mailbox.h" +#include "reboot.h" +#include "string.h" +#include "../include/cpio.h" +#include "memory.h" +#include "timer.h" +#include "exception.h" +#include "math.h" +#include "mm.h" +#include "../include/sched.h" +#include "syscall.h" +#include "peripherals/mailbox.h" +#include "fork.h" +#include "mmu.h" + + +#define MAX_BUFFER_SIZE 256u + +static char buffer[MAX_BUFFER_SIZE]; + +void start_video() { + + struct cpio_newc_header *header; + unsigned int filesize; + unsigned int namesize; + unsigned int offset; + char *filename; + void *code_loc; + + header = DEVTREE_CPIO_BASE; + printf("devtree base: 0x%x\n", DEVTREE_CPIO_BASE); + while (1) { + + filename = ((void*)header) + sizeof(struct cpio_newc_header); + + if (stringncmp((char*)header, CPIO_HEADER_MAGIC, 6) != 0) { + uart_send_string("invalid magic\n"); + break; + } + if (stringncmp(filename, CPIO_FOOTER_MAGIC, 11) == 0) { + uart_send_string("file does not exist!\n"); + break; + } + + namesize = hexstr_to_uint(header->c_namesize, 8); + + offset = sizeof(struct cpio_newc_header) + namesize; + if (offset % 4 != 0) + offset = ((offset/4) + 1) * 4; + + filesize = hexstr_to_uint(header->c_filesize, 8); + + if (stringncmp(filename, "vm.img", namesize) == 0) { + code_loc = ((void*)header) + offset; + break; + } + + if (filesize % 4 != 0) + filesize = ((filesize/4) + 1) * 4; + + offset = offset + filesize; + + header = ((void*)header) + offset; + + } + printf("vm.img found in cpio at location 0x%x.\n", code_loc); + printf("vm.img has size of %d bytes.\n", (int)filesize); + + int err = move_to_user_mode((unsigned long)code_loc, filesize, 0); + if (err<0) + printf("failed to start video program\n"); + +} + +void read_cmd() +{ + unsigned int idx = 0; + char c = '\0'; + + while (1) { + c = uart_recv(); + if (c == '\r' || c == '\n') { + uart_send_string("\n"); + + if (idx < MAX_BUFFER_SIZE) buffer[idx] = '\0'; + else buffer[MAX_BUFFER_SIZE-1] = '\0'; + + break; + } else { + uart_send(c); + buffer[idx++] = c; + } + } + +} + +void parse_cmd() +{ + + if (stringcmp(buffer, "\0") == 0) + uart_send_string("\n"); + else if (stringcmp(buffer, "hello") == 0) + uart_send_string("Hello World!\n"); + else if (stringcmp(buffer, "reboot") == 0) { + uart_send_string("rebooting...\n"); + reset(100); + } + else if (stringcmp(buffer, "hwinfo") == 0) { + get_board_revision(); + get_arm_memory(); + } + else if (stringcmp(buffer, "ls") == 0) { + cpio_ls(); + } + else if (stringcmp(buffer, "cat") == 0) { + cpio_cat(); + } + else if (stringcmp(buffer, "execute") == 0) { + cpio_exec(); + } + else if (stringcmp(buffer, "video") == 0) { + preempt_disable(); + current->state = TASK_STOPPED; + unsigned long long tmp; + asm volatile("mrs %0, cntkctl_el1" : "=r"(tmp)); + tmp |= 1; + asm volatile("msr cntkctl_el1, %0" : : "r"(tmp)); + copy_process(PF_KTHREAD, (unsigned long)&start_video, 0/*, 0*/); + preempt_enable(); + } + else if (stringcmp(buffer, "help") == 0) { + uart_send_string("help:\t\tprint list of available commands\n"); + uart_send_string("hello:\t\tprint Hello World!\n"); + uart_send_string("reboot:\t\treboot device\n"); + uart_send_string("hwinfo:\t\tprint hardware information\n"); + uart_send_string("ls:\t\tlist initramfs files\n"); + uart_send_string("cat:\t\tprint file content in initramfs\n"); + uart_send_string("execute:\trun program from cpio\n"); + } + else + uart_send_string("Command not found! Type help for commands.\n"); + +} + +void shell_loop() +{ + while (1) { + uart_send_string("% "); + read_cmd(); + parse_cmd(); + } +} \ No newline at end of file diff --git a/lab7/lib/string.c b/lab7/lib/string.c new file mode 100644 index 000000000..df9ea89ca --- /dev/null +++ b/lab7/lib/string.c @@ -0,0 +1,39 @@ +#include "string.h" + +int stringcmp(const char *p1, const char *p2) +{ + const unsigned char *s1 = (const unsigned char *) p1; + const unsigned char *s2 = (const unsigned char *) p2; + unsigned char c1, c2; + + do { + c1 = (unsigned char) *s1++; + c2 = (unsigned char) *s2++; + if (c1 == '\0') + return c1 - c2; + } while (c1 == c2); + + return c1 - c2; +} + +int stringncmp(const char *p1, const char *p2, unsigned int n) +{ + for (int i=0; iid; +} + +unsigned sys_uartread(char buf[], unsigned size) { + for(unsigned int i=0; ic_namesize, 8); + + offset = sizeof(struct cpio_newc_header) + namesize; + if (offset % 4 != 0) + offset = ((offset/4) + 1) * 4; + + filesize = hexstr_to_uint(header->c_filesize, 8); + + if (stringncmp(filename, name, namesize) == 0) { + code_loc = ((void*)header) + offset; + break; + } + + if (filesize % 4 != 0) + filesize = ((filesize/4) + 1) * 4; + + offset = offset + filesize; + + header = ((void*)header) + offset; + + } + + void *move_loc = malloc(filesize + 4096); // an extra page for bss just in case + if(move_loc == NULL) return -1; + for (int i=0; ipc = 0; // move to beginning of program + p->sp = 0xfffffffff000; + + preempt_enable(); + + return -1; // only on failure*/ + // not real exec, only a restart of current process +} // fix + +int sys_fork() { + return copy_process(0, 0, 0/*, 0*/); +} + +void sys_exit(int status) { + exit_process(); +} + +int sys_mbox_call(unsigned char ch, volatile unsigned int *mbox) { + printf("mbox: 0x%x\n", mbox); + unsigned long ka_mbox = va2phys((unsigned long)mbox) + ((unsigned long)mbox&0xFFF); + printf("mbox kernel addr location: 0x%x\n", ka_mbox); + unsigned int r = (((unsigned int)((unsigned long)ka_mbox)&~0xF) | (ch&0xF)); + while(*MAILBOX_STATUS & MAILBOX_FULL); + *MAILBOX_WRITE = r; + while (1) { + while (*MAILBOX_STATUS & MAILBOX_EMPTY) {} + if (r == *MAILBOX_READ) { + return ((unsigned int *)(ka_mbox+VA_START))[1]==REQUEST_SUCCEED; + } + } + return 0; +} + +void sys_kill(int pid) { + + struct task_struct *p; + for (int i=0; iid == (long)pid) { + preempt_disable(); + printf("Kill target acquired.\n"); + p->state = TASK_ZOMBIE; + //free((void *)p->stack); + preempt_enable(); + break; + } + + } + +} + +void * const sys_call_table[] = +{ + sys_getpid, + sys_uartread, + sys_uartwrite, + sys_exec, + sys_fork, + sys_exit, + sys_mbox_call, + sys_kill +}; \ No newline at end of file diff --git a/lab7/lib/timer.c b/lab7/lib/timer.c new file mode 100644 index 000000000..ae102a2b0 --- /dev/null +++ b/lab7/lib/timer.c @@ -0,0 +1,62 @@ +#include "timer.h" +#include "../include/sched.h" +#include "mini_uart.h" + +void timer_init() { + core_timer_enable(); + set_timer(read_freq()); +} + +void handle_timer_irq() { + //printf("Timer interrupt.\n"); + set_timer(read_freq()>>5); + timer_tick(); +} + +void core_timer_enable() { + + asm volatile( + "mov x0, 1\n\t" + "msr cntp_ctl_el0, x0\n\t" // enable + "mov x0, 2\n\t" + "ldr x1, =0x40000040\n\t" // CORE0_TIMER_IRQ_CTRL + "str w0, [x1]\n\t" // unmask timer interrupt + ); + +} + +void core_timer_disable() { + + asm volatile( + "mov x0, 0\n\t" + "ldr x1, =0x40000040\n\t" + "str w0, [x1]\n\t" + ); + +} + +void set_timer(unsigned int rel_time) { + + asm volatile( + "msr cntp_tval_el0, %0\n\t" + : + : "r" (rel_time) + ); + +} + +unsigned int read_timer() { + + unsigned int time; + asm volatile("mrs %0, cntpct_el0\n\t" : "=r" (time) : : "memory"); + return time; + +} + +unsigned int read_freq() { + + unsigned int freq; + asm volatile("mrs %0, cntfrq_el0\n\t": "=r" (freq) : : "memory"); + return freq; + +} diff --git a/lab7/lib/utils.S b/lab7/lib/utils.S new file mode 100644 index 000000000..aa0e55aa9 --- /dev/null +++ b/lab7/lib/utils.S @@ -0,0 +1,15 @@ +.globl put32 +put32: + str w1,[x0] + ret + +.globl get32 +get32: + ldr w0,[x0] + ret + +.globl delay +delay: + subs x0, x0, #1 + bne delay + ret \ No newline at end of file diff --git a/lab7/send_img.py b/lab7/send_img.py new file mode 100644 index 000000000..b2261950d --- /dev/null +++ b/lab7/send_img.py @@ -0,0 +1,31 @@ +import argparse +import os +import time +import math +import serial + +parser = argparse.ArgumentParser(description='*.img uart sender') +parser.add_argument('-i', '--img', default='kernel8.img', type=str) +parser.add_argument('-d', '--device', default='/dev/ttyUSB0', type=str) +parser.add_argument('-b', '--baud', default=115200, type=int) + +args = parser.parse_args() + +img_size = os.path.getsize(args.img) + +with open(args.img, 'rb') as f: + with serial.Serial(args.device, args.baud) as tty: + + print(f'{args.img} is {img_size} bytes') + print('img file is now sending') + + tty.write(img_size.to_bytes(4, 'big')) + + input() + + for i in range(img_size): + tty.write(f.read(1)) + tty.flush() + #time.sleep(0.0001) + + print('img sent') From 958fc61a36df78325eb81b8dc8f3de8f1ee0c90f Mon Sep 17 00:00:00 2001 From: Hsiang-Chieh Tsou Date: Thu, 2 Jun 2022 11:36:54 +0800 Subject: [PATCH 6/9] lab7_novm init --- lab7_novm/bcm2710-rpi-3-b-plus.dtb | Bin 0 -> 31790 bytes lab7_novm/config.txt | 3 + lab7_novm/include/cpio.h | 49 +++ lab7_novm/include/devtree.h | 64 ++++ lab7_novm/include/entry.h | 39 +++ lab7_novm/include/exception.h | 7 + lab7_novm/include/fork.h | 26 ++ lab7_novm/include/mailbox.h | 7 + lab7_novm/include/math.h | 7 + lab7_novm/include/memory.h | 8 + lab7_novm/include/mini_uart.h | 10 + lab7_novm/include/mm.h | 55 ++++ lab7_novm/include/peripherals/base.h | 6 + lab7_novm/include/peripherals/exception.h | 25 ++ lab7_novm/include/peripherals/gpio.h | 12 + lab7_novm/include/peripherals/mailbox.h | 23 ++ lab7_novm/include/peripherals/mini_uart.h | 19 ++ lab7_novm/include/printf.h | 31 ++ lab7_novm/include/reboot.h | 7 + lab7_novm/include/sched.h | 72 +++++ lab7_novm/include/shell.h | 6 + lab7_novm/include/string.h | 8 + lab7_novm/include/syscall.h | 28 ++ lab7_novm/include/timer.h | 14 + lab7_novm/include/utils.h | 8 + lab7_novm/initramfs.cpio | Bin 0 -> 404992 bytes lab7_novm/kernel/Makefile | 49 +++ lab7_novm/kernel/boot_kernel.S | 41 +++ lab7_novm/kernel/kernel.c | 26 ++ lab7_novm/kernel/linker.ld | 21 ++ lab7_novm/lib/Makefile | 29 ++ lab7_novm/lib/cpio.c | 232 +++++++++++++ lab7_novm/lib/devtree.c | 92 ++++++ lab7_novm/lib/entry.S | 195 +++++++++++ lab7_novm/lib/exception.c | 49 +++ lab7_novm/lib/fork.c | 83 +++++ lab7_novm/lib/mailbox.c | 49 +++ lab7_novm/lib/math.c | 19 ++ lab7_novm/lib/memory.c | 22 ++ lab7_novm/lib/mini_uart.c | 69 ++++ lab7_novm/lib/mm.S | 6 + lab7_novm/lib/mm.c | 376 ++++++++++++++++++++++ lab7_novm/lib/printf.c | 152 +++++++++ lab7_novm/lib/reboot.c | 18 ++ lab7_novm/lib/sched.S | 24 ++ lab7_novm/lib/sched.c | 107 ++++++ lab7_novm/lib/shell.c | 223 +++++++++++++ lab7_novm/lib/string.c | 39 +++ lab7_novm/lib/syscall.S | 49 +++ lab7_novm/lib/syscall.c | 143 ++++++++ lab7_novm/lib/timer.c | 62 ++++ lab7_novm/lib/utils.S | 15 + lab7_novm/send_img.py | 31 ++ 53 files changed, 2755 insertions(+) create mode 100644 lab7_novm/bcm2710-rpi-3-b-plus.dtb create mode 100644 lab7_novm/config.txt create mode 100644 lab7_novm/include/cpio.h create mode 100644 lab7_novm/include/devtree.h create mode 100644 lab7_novm/include/entry.h create mode 100644 lab7_novm/include/exception.h create mode 100644 lab7_novm/include/fork.h create mode 100644 lab7_novm/include/mailbox.h create mode 100644 lab7_novm/include/math.h create mode 100644 lab7_novm/include/memory.h create mode 100644 lab7_novm/include/mini_uart.h create mode 100644 lab7_novm/include/mm.h create mode 100644 lab7_novm/include/peripherals/base.h create mode 100644 lab7_novm/include/peripherals/exception.h create mode 100644 lab7_novm/include/peripherals/gpio.h create mode 100644 lab7_novm/include/peripherals/mailbox.h create mode 100644 lab7_novm/include/peripherals/mini_uart.h create mode 100644 lab7_novm/include/printf.h create mode 100644 lab7_novm/include/reboot.h create mode 100644 lab7_novm/include/sched.h create mode 100644 lab7_novm/include/shell.h create mode 100644 lab7_novm/include/string.h create mode 100644 lab7_novm/include/syscall.h create mode 100644 lab7_novm/include/timer.h create mode 100644 lab7_novm/include/utils.h create mode 100644 lab7_novm/initramfs.cpio create mode 100644 lab7_novm/kernel/Makefile create mode 100644 lab7_novm/kernel/boot_kernel.S create mode 100644 lab7_novm/kernel/kernel.c create mode 100644 lab7_novm/kernel/linker.ld create mode 100644 lab7_novm/lib/Makefile create mode 100644 lab7_novm/lib/cpio.c create mode 100644 lab7_novm/lib/devtree.c create mode 100644 lab7_novm/lib/entry.S create mode 100644 lab7_novm/lib/exception.c create mode 100644 lab7_novm/lib/fork.c create mode 100644 lab7_novm/lib/mailbox.c create mode 100644 lab7_novm/lib/math.c create mode 100644 lab7_novm/lib/memory.c create mode 100644 lab7_novm/lib/mini_uart.c create mode 100644 lab7_novm/lib/mm.S create mode 100644 lab7_novm/lib/mm.c create mode 100644 lab7_novm/lib/printf.c create mode 100644 lab7_novm/lib/reboot.c create mode 100644 lab7_novm/lib/sched.S create mode 100644 lab7_novm/lib/sched.c create mode 100644 lab7_novm/lib/shell.c create mode 100644 lab7_novm/lib/string.c create mode 100644 lab7_novm/lib/syscall.S create mode 100644 lab7_novm/lib/syscall.c create mode 100644 lab7_novm/lib/timer.c create mode 100644 lab7_novm/lib/utils.S create mode 100644 lab7_novm/send_img.py diff --git a/lab7_novm/bcm2710-rpi-3-b-plus.dtb b/lab7_novm/bcm2710-rpi-3-b-plus.dtb new file mode 100644 index 0000000000000000000000000000000000000000..3934b3a26eb82fd65dbcdfca6f6916da427a3fae GIT binary patch literal 31790 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 literal 0 HcmV?d00001 diff --git a/lab7_novm/config.txt b/lab7_novm/config.txt new file mode 100644 index 000000000..49fc25695 --- /dev/null +++ b/lab7_novm/config.txt @@ -0,0 +1,3 @@ +kernel=bootloader.img +arm_64bit=1 +initramfs initramfs.cpio 0x20000000 diff --git a/lab7_novm/include/cpio.h b/lab7_novm/include/cpio.h new file mode 100644 index 000000000..1735f5fe5 --- /dev/null +++ b/lab7_novm/include/cpio.h @@ -0,0 +1,49 @@ +#ifndef __CPIO_H__ +#define __CPIO_H__ + +/* +Each file system object in a cpio archive comprises a header record with +basic numeric metadata followed by the full pathname of the entry and the +file data. The header record stores a series of integer values that gen- +erally follow the fields in struct stat. (See stat(2) for details.) The +variants differ primarily in how they store those integers (binary, oc- +tal, or hexadecimal). The header is followed by the pathname of the en- +try (the length of the pathname is stored in the header) and any file +data. The end of the archive is indicated by a special record with the +pathname "TRAILER!!!" +*/ + +#define CPIO_HEADER_MAGIC "070701" +#define CPIO_FOOTER_MAGIC "TRAILER!!!" +#define PI_CPIO_BASE ((void*) (0x20000000)) +#define QEMU_CPIO_BASE ((void*) (0x8000000)) + +#include "devtree.h" + +extern void *DEVTREE_CPIO_BASE; + +struct cpio_newc_header { + char c_magic[6]; // 070701 + 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]; // ignored by readers +}; + +void initramfs_callback (char *, char *, struct fdt_prop *); +void cpio_ls (); +void cpio_cat (); +void cpio_exec (); + +unsigned int hexstr_to_uint(char *s, unsigned int len); + +#endif \ No newline at end of file diff --git a/lab7_novm/include/devtree.h b/lab7_novm/include/devtree.h new file mode 100644 index 000000000..d8225b11c --- /dev/null +++ b/lab7_novm/include/devtree.h @@ -0,0 +1,64 @@ +#ifndef _DEVTREE_H +#define _DEVTREE_H + +/* + magic; 0xd00dfeed (big-endian) + + totalsize; total size in bytes of the devicetree + data structure + + off_dt_struct; offset in bytes of the structure block + from the beginning of the header + + off_dt_strings; offset in bytes of the strings block + from the beginning of the header + + off_mem_rsvmap; offset in bytes of the memory reservation + block from the beginning of the header + + version; version of the devicetree data structure + + last_comp_version; lowest version of the devicetree data + structure with which the version used is backwards compatible + + boot_cpuid_phys; physical ID of the system’s boot CPU + + size_dt_strings; length in bytes of the strings block + section of the devicetree blob + + size_dt_struct; length in bytes of the structure block + section of the devicetree blob +*/ + +#define FDT_HEADER_MAGIC 0xd00dfeed +#define FDT_BEGIN_NODE 0x00000001 +#define FDT_END_NODE 0x00000002 +#define FDT_PROP 0x00000003 +#define FDT_NOP 0x00000004 +#define FDT_END 0x00000009 + +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; +}; + +struct fdt_prop { + unsigned int len; + unsigned int nameoff; +}; + +void devtree_getaddr (); +void fdt_traverse ( void (*callback)(char *, char *, struct fdt_prop *) ); + +// ARM uses little endian +unsigned int to_lendian (unsigned int); + +#endif \ No newline at end of file diff --git a/lab7_novm/include/entry.h b/lab7_novm/include/entry.h new file mode 100644 index 000000000..1a22d0284 --- /dev/null +++ b/lab7_novm/include/entry.h @@ -0,0 +1,39 @@ +#ifndef _ENTRY_H +#define _ENTRY_H + +#define S_FRAME_SIZE 272 // size of all saved registers +#define S_X0 0 + +#define SYNC_INVALID_EL1t 0 +#define IRQ_INVALID_EL1t 1 +#define FIQ_INVALID_EL1t 2 +#define ERROR_INVALID_EL1t 3 + +#define SYNC_INVALID_EL1h 4 +#define IRQ_INVALID_EL1h 5 +#define FIQ_INVALID_EL1h 6 +#define ERROR_INVALID_EL1h 7 + +#define SYNC_INVALID_EL0_64 8 +#define IRQ_INVALID_EL0_64 9 +#define FIQ_INVALID_EL0_64 10 +#define ERROR_INVALID_EL0_64 11 + +#define SYNC_INVALID_EL0_32 12 +#define IRQ_INVALID_EL0_32 13 +#define FIQ_INVALID_EL0_32 14 +#define ERROR_INVALID_EL0_32 15 + +#define SYNC_ERROR 16 +#define SYSCALL_ERROR 17 + +#define ESR_ELx_EC_SHIFT 26 +#define ESR_ELx_EC_SVC64 0x15 + +#ifndef __ASSEMBLER__ + +void ret_from_fork(); + +#endif + +#endif \ No newline at end of file diff --git a/lab7_novm/include/exception.h b/lab7_novm/include/exception.h new file mode 100644 index 000000000..40f4ba532 --- /dev/null +++ b/lab7_novm/include/exception.h @@ -0,0 +1,7 @@ +#ifndef _EXCEPTION_H +#define _EXCEPTION_H + +void enable_interrupt(); +void disable_interrupt(); + +#endif \ No newline at end of file diff --git a/lab7_novm/include/fork.h b/lab7_novm/include/fork.h new file mode 100644 index 000000000..7b018dc68 --- /dev/null +++ b/lab7_novm/include/fork.h @@ -0,0 +1,26 @@ +#ifndef _FORK_H +#define _FORK_H + +#include "sched.h" + +#define PSR_MODE_EL0t 0x00000000 +#define PSR_MODE_EL1t 0x00000004 +#define PSR_MODE_EL1h 0x00000005 +#define PSR_MODE_EL2t 0x00000008 +#define PSR_MODE_EL2h 0x00000009 +#define PSR_MODE_EL3t 0x0000000c +#define PSR_MODE_EL3h 0x0000000d + +int copy_process(unsigned long, unsigned long, unsigned long, unsigned long); +int move_to_user_mode(unsigned long); +struct pt_regs *task_pt_regs(struct task_struct *); +void new_user_process(unsigned long); + +struct pt_regs { + unsigned long regs[31]; + unsigned long sp; + unsigned long pc; + unsigned long pstate; +}; + +#endif \ No newline at end of file diff --git a/lab7_novm/include/mailbox.h b/lab7_novm/include/mailbox.h new file mode 100644 index 000000000..eeecaa9de --- /dev/null +++ b/lab7_novm/include/mailbox.h @@ -0,0 +1,7 @@ +#ifndef _MAILBOX_H +#define _MAILBOX_H + +void get_board_revision (); +void get_arm_memory (); + +#endif \ No newline at end of file diff --git a/lab7_novm/include/math.h b/lab7_novm/include/math.h new file mode 100644 index 000000000..353ccfb26 --- /dev/null +++ b/lab7_novm/include/math.h @@ -0,0 +1,7 @@ +#ifndef _MATH_H +#define _MATH_H + +int log(int, int); +int pow(int, int); + +#endif \ No newline at end of file diff --git a/lab7_novm/include/memory.h b/lab7_novm/include/memory.h new file mode 100644 index 000000000..956f6e955 --- /dev/null +++ b/lab7_novm/include/memory.h @@ -0,0 +1,8 @@ +#ifndef _MEMORY_H +#define _MEMORY_H + +#define MAX_HEAP_SIZE 0x10000000 + +void* simple_malloc(unsigned int); + +#endif \ No newline at end of file diff --git a/lab7_novm/include/mini_uart.h b/lab7_novm/include/mini_uart.h new file mode 100644 index 000000000..453ec93f2 --- /dev/null +++ b/lab7_novm/include/mini_uart.h @@ -0,0 +1,10 @@ +#ifndef _MINI_UART_H +#define _MINI_UART_H + +void uart_init ( void ); +char uart_recv ( void ); +void uart_send ( char c ); +void uart_send_string ( char* str ); +void printf ( char *fmt, ... ); + +#endif /*_MINI_UART_H */ \ No newline at end of file diff --git a/lab7_novm/include/mm.h b/lab7_novm/include/mm.h new file mode 100644 index 000000000..df97f2b89 --- /dev/null +++ b/lab7_novm/include/mm.h @@ -0,0 +1,55 @@ +#ifndef _MM_H +#define _MM_H + +#define MEM_REGION_BEGIN 0x0 +#define MEM_REGION_END 0x3C000000 +#define PAGE_SIZE 4096 +#define MAX_ORDER 8 // largest: PAGE_SIZE*2^(MAX_ORDER) + +#define ALLOCABLE 0 +#define ALLOCATED -1 +#define C_NALLOCABLE -2 +#define RESERVED -3 + +#define NULL 0 + +#define MAX_POOL_PAGES 8 +#define MAX_POOLS 8 +#define MIN_CHUNK_SIZE 8 + +#define MAX_RESERVABLE 8 + +struct frame { + unsigned int index; + int val; + int state; + struct frame *prev, *next; +}; + +struct node { + struct node *next; +}; + +struct dynamic_pool { + unsigned int chunk_size; + unsigned int chunks_per_page; + unsigned int chunks_allocated; + unsigned int page_new_chunk_off; + unsigned int pages_used; + void *page_base_addrs[MAX_POOL_PAGES]; + struct node *free_head; +}; + +void *malloc(unsigned int); +void free(void *); +void init_mm(); +void init_pool(struct dynamic_pool*, unsigned int); +int register_chunk(unsigned int); +void *chunk_alloc(unsigned int); +void chunk_free(void *); +void memory_reserve(void*, void*); +void init_mm_reserve(); + +void memzero(unsigned long, unsigned long); + +#endif /*_MM_H */ \ No newline at end of file diff --git a/lab7_novm/include/peripherals/base.h b/lab7_novm/include/peripherals/base.h new file mode 100644 index 000000000..8f66cd5fe --- /dev/null +++ b/lab7_novm/include/peripherals/base.h @@ -0,0 +1,6 @@ +#ifndef _P_BASE_H +#define _P_BASE_H + +#define PBASE 0x3F000000 + +#endif /*_P_BASE_H */ \ No newline at end of file diff --git a/lab7_novm/include/peripherals/exception.h b/lab7_novm/include/peripherals/exception.h new file mode 100644 index 000000000..bc64c7c53 --- /dev/null +++ b/lab7_novm/include/peripherals/exception.h @@ -0,0 +1,25 @@ +#ifndef _P_EXCEPTION_H +#define _P_EXCEPTION_H + +#include "peripherals/base.h" + +#define IRQ_BASIC_PENDING (PBASE+0x0000B200) +#define IRQ_PENDING_1 (PBASE+0x0000B204) +#define IRQ_PENDING_2 (PBASE+0x0000B208) +#define FIQ_CONTROL (PBASE+0x0000B20C) +#define ENABLE_IRQS_1 (PBASE+0x0000B210) +#define ENABLE_IRQS_2 (PBASE+0x0000B214) +#define ENABLE_BASIC_IRQS (PBASE+0x0000B218) +#define DISABLE_IRQS_1 (PBASE+0x0000B21C) +#define DISABLE_IRQS_2 (PBASE+0x0000B220) +#define DISABLE_BASIC_IRQS (PBASE+0x0000B224) + +#define CNTPCT_EL0 (0x4000001C) +#define CNTP_CTL_EL0 (0x40000040) +#define CORE0_INTERRUPT_SRC (0x40000060) + +#define IRQ_PENDING_1_AUX_INT (1<<29) +#define INTERRUPT_SOURCE_GPU (1<<8) +#define INTERRUPT_SOURCE_CNTPNSIRQ (1<<1) + +#endif \ No newline at end of file diff --git a/lab7_novm/include/peripherals/gpio.h b/lab7_novm/include/peripherals/gpio.h new file mode 100644 index 000000000..c5d7d138f --- /dev/null +++ b/lab7_novm/include/peripherals/gpio.h @@ -0,0 +1,12 @@ +#ifndef _P_GPIO_H +#define _P_GPIO_H + +#include "peripherals/base.h" + +#define GPFSEL1 (PBASE+0x00200004) +#define GPSET0 (PBASE+0x0020001C) +#define GPCLR0 (PBASE+0x00200028) +#define GPPUD (PBASE+0x00200094) +#define GPPUDCLK0 (PBASE+0x00200098) + +#endif /*_P_GPIO_H */ \ No newline at end of file diff --git a/lab7_novm/include/peripherals/mailbox.h b/lab7_novm/include/peripherals/mailbox.h new file mode 100644 index 000000000..0351bd714 --- /dev/null +++ b/lab7_novm/include/peripherals/mailbox.h @@ -0,0 +1,23 @@ +#ifndef _P_MAILBOX_H +#define _P_MAILBOX_H + +#define MMIO_BASE 0x3f000000 +#define MAILBOX_BASE MMIO_BASE + 0xb880 + +#define MAILBOX_READ (volatile unsigned int*) (MAILBOX_BASE) +#define MAILBOX_STATUS (volatile unsigned int*) (MAILBOX_BASE + 0x18) +#define MAILBOX_WRITE (volatile unsigned int*) (MAILBOX_BASE + 0x20) + +#define MAILBOX_EMPTY 0x40000000 +#define MAILBOX_FULL 0x80000000 + +#define GET_BOARD_REVISION 0x00010002 +#define GET_ARM_MEMORY 0x00010005 + +#define REQUEST_CODE 0x00000000 +#define REQUEST_SUCCEED 0x80000000 +#define REQUEST_FAILED 0x80000001 +#define TAG_REQUEST_CODE 0x00000000 +#define END_TAG 0x00000000 + +#endif \ No newline at end of file diff --git a/lab7_novm/include/peripherals/mini_uart.h b/lab7_novm/include/peripherals/mini_uart.h new file mode 100644 index 000000000..386b6fade --- /dev/null +++ b/lab7_novm/include/peripherals/mini_uart.h @@ -0,0 +1,19 @@ +#ifndef _P_MINI_UART_H +#define _P_MINI_UART_H + +#include "peripherals/base.h" + +#define AUX_ENABLES (PBASE+0x00215004) +#define AUX_MU_IO_REG (PBASE+0x00215040) +#define AUX_MU_IER_REG (PBASE+0x00215044) +#define AUX_MU_IIR_REG (PBASE+0x00215048) +#define AUX_MU_LCR_REG (PBASE+0x0021504C) +#define AUX_MU_MCR_REG (PBASE+0x00215050) +#define AUX_MU_LSR_REG (PBASE+0x00215054) +#define AUX_MU_MSR_REG (PBASE+0x00215058) +#define AUX_MU_SCRATCH (PBASE+0x0021505C) +#define AUX_MU_CNTL_REG (PBASE+0x00215060) +#define AUX_MU_STAT_REG (PBASE+0x00215064) +#define AUX_MU_BAUD_REG (PBASE+0x00215068) + +#endif /*_P_MINI_UART_H */ \ No newline at end of file diff --git a/lab7_novm/include/printf.h b/lab7_novm/include/printf.h new file mode 100644 index 000000000..dfedbe301 --- /dev/null +++ b/lab7_novm/include/printf.h @@ -0,0 +1,31 @@ +/* + * 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. + * + */ +#ifndef _PRINTF_H +#define _PRINTF_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/lab7_novm/include/reboot.h b/lab7_novm/include/reboot.h new file mode 100644 index 000000000..df4d63a86 --- /dev/null +++ b/lab7_novm/include/reboot.h @@ -0,0 +1,7 @@ +#ifndef _REBOOT_H +#define _REBOOT_H + +void reset ( int ); +void cancel_reset (); + +#endif \ No newline at end of file diff --git a/lab7_novm/include/sched.h b/lab7_novm/include/sched.h new file mode 100644 index 000000000..a05e6ae25 --- /dev/null +++ b/lab7_novm/include/sched.h @@ -0,0 +1,72 @@ +#ifndef _SCHED_H +#define _SCHED_H + +#define THREAD_CPU_CONTEXT 0 + +#ifndef __ASSEMBLER__ + +#define THREAD_SIZE 4096 + +#define NR_TASKS 64 + +#define FIRST_TASK task[0] +#define LAST_TASK task[NR_TASKS-1] + +#define TASK_RUNNING 0 +#define TASK_INTERRUPTIBLE 1 +#define TASK_UNINTERRUPTIBLE 2 +#define TASK_ZOMBIE 3 +#define TASK_STOPPED 4 + +#define PF_KTHREAD 0x00000002 + +extern struct task_struct *current; +extern struct task_struct *task[NR_TASKS]; +extern int nr_tasks; + +struct cpu_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 sp; + unsigned long pc; +}; + +struct task_struct { + struct cpu_context cpu_context; + long state; + long counter; + long priority; + long preempt_count; + unsigned long stack; + unsigned long flags; + long id; +}; + +extern void sched_init(); +extern void schedule(); +extern void timer_tick(); +extern void preempt_disable(); +extern void preempt_enable(); +extern void switch_to(struct task_struct *); +extern void cpu_switch_to(struct task_struct *, struct task_struct *); +extern void exit_process(); +extern void kill_zombies(); + +#define INIT_TASK \ +{ \ +{0,0,0,0,0,0,0,0,0,0,0,0,0},\ +0, 0, 1, 0, 0, PF_KTHREAD, 0 \ +} + +#endif + +#endif \ No newline at end of file diff --git a/lab7_novm/include/shell.h b/lab7_novm/include/shell.h new file mode 100644 index 000000000..828c56763 --- /dev/null +++ b/lab7_novm/include/shell.h @@ -0,0 +1,6 @@ +#ifndef _SHELL_H +#define _SHELL_H + +void shell_loop (); + +#endif \ No newline at end of file diff --git a/lab7_novm/include/string.h b/lab7_novm/include/string.h new file mode 100644 index 000000000..abf24c93f --- /dev/null +++ b/lab7_novm/include/string.h @@ -0,0 +1,8 @@ +#ifndef _STRING_H +#define _STRING_H + +int stringcmp (const char *, const char *); +int stringncmp (const char *, const char *, unsigned int); +unsigned int strlen(const char *); + +#endif \ No newline at end of file diff --git a/lab7_novm/include/syscall.h b/lab7_novm/include/syscall.h new file mode 100644 index 000000000..b738fcff9 --- /dev/null +++ b/lab7_novm/include/syscall.h @@ -0,0 +1,28 @@ +#ifndef _SYSCALL_H +#define _SYSCALL_H + +#define __NR_SYSCALLS 8 + +#define SYS_GETPID_NUM 0 +#define SYS_UARTREAD_NUM 1 +#define SYS_UARTWRITE_NUM 2 +#define SYS_EXEC_NUM 3 +#define SYS_FORK_NUM 4 +#define SYS_EXIT_NUM 5 +#define SYS_MBOXCALL_NUM 6 +#define SYS_KILL_NUM 7 + +#ifndef __ASSEMBLER__ + +int sys_getpid(); +unsigned sys_uartread(char buf[], unsigned size); +unsigned sys_uartwrite(const char buf[], unsigned size); +int sys_exec(const char *name, char *const argv[]); +int sys_fork(); +void sys_exit(int status); +int sys_mbox_call(unsigned char ch, unsigned int *mbox); +void sys_kill(int pid); + +#endif + +#endif \ No newline at end of file diff --git a/lab7_novm/include/timer.h b/lab7_novm/include/timer.h new file mode 100644 index 000000000..9812a457a --- /dev/null +++ b/lab7_novm/include/timer.h @@ -0,0 +1,14 @@ +#ifndef _TIMER_H +#define _TIMER_H + +#define CORE0_TIMER_IRQ_CTRL 0x40000040 + +void timer_init(); +void handle_timer_irq(); +void core_timer_enable(); +void core_timer_disable(); +void set_timer(unsigned int rel_time); +unsigned int read_timer(); +unsigned int read_freq(); + +#endif \ No newline at end of file diff --git a/lab7_novm/include/utils.h b/lab7_novm/include/utils.h new file mode 100644 index 000000000..9d950d477 --- /dev/null +++ b/lab7_novm/include/utils.h @@ -0,0 +1,8 @@ +#ifndef _BOOT_H +#define _BOOT_H + +extern void delay ( unsigned long ); +extern void put32 ( unsigned long, unsigned int ); +extern unsigned int get32 ( unsigned long ); + +#endif /*_BOOT_H */ \ No newline at end of file diff --git a/lab7_novm/initramfs.cpio b/lab7_novm/initramfs.cpio new file mode 100644 index 0000000000000000000000000000000000000000..18985b13cb5e921deee575337d249e0d7a76fdf5 GIT binary patch literal 404992 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 e8;i z_T^4@MSqL*qPlsaovbMX&}2f!_&;uPzyvX0f6<*}!al6?0JN&?;s?0rw6?vjG2v04U=B8$b4UC@|Bw^e zmLKEy{)`y9$iZVhD9{c+&OVNT`tYO+RyJS@QOxNSxwR2Q938q;Ll_F#R2ltTFkzJ` zwzD`3k%(&vxl&aot*(%T@^GM;1THd(E@7Y4rzT-htP#euanZF~E7CIV}}cZ@Sx13@_Q9=@Xv~B9 zYVqK$x8rv`lWtoDExfQOsqz*N<2slBGYJlS0gU0POe(Ikl8rV~!q z@5_rYXojQ5VOOIg^mp$|{S7}TF45GM1IEW5Yi2)od=bsVo(U|F)v;6{Sh*ma6MXCs zdX*AqI4z^!J@=u^+ghE)7QP|#V_tv_?~^-kV|H{f+>+{pIOYIz3RMpDEIt(5f&iEO zi@R^e!~WVXUDMc_Xk#CIbpO=xzCrUj{YaI?0@63DG;soXx_tq#!P+JRRZ z^KuUQ&(D#zW2;ybX#dpP|GlUGzMM#=M1!siJigI}a)`mh7wT2|y_*+v&hvdQ(|aPu z3;K#x#}D2+_HUwfZ%33i{iY8cbe;N$WKT?{`fiRnmW|NKzL>L(J-SchoTptHmb{&! zopqnN&|i?jXrs1}ecZO4**R!I-w@Z~>|po;84u|5yZsi{x5T;IrtR+SiJ_mwBei?@ ziZgu?pXeu3yd9MZ)VX$>uya=~^u}8~?um@#%qqikF|vCkEBAwbF~rGu4e~nrmrMt^ z?Z#61`H?6vxQp0Tk@ z9CUj$K8S(yav}1Fm%nD~@_z5o_y&c^cOux({=Ck5}(P-!ON=g;0N>9^`c{o^M1umV|lXa#FHo#BWhtCW`~4HD0TT-z5Bu z;=$mzV{w96#Hg_NLHOv2-$fbx0*Av4c&iI+0k|}+5oy_2i)xd@x=Wf{cX1e-x2ez0 z3JkvA@&VhSV(apO6Ra<)nUbb3Y*HiKPsX}?z|)8ZJDjviMq<^U#Ep%N^*9B@4gx{I z+Lz9t3coPc=7j!itkKyLOGc0~5py5q4Zf8IX&cK_Mo2>#53ey)Fhuo7J|m-o;5m)>v;B zi{#)rE$LHc@p^r&<*-n)B%nOg8lP3*&ze5&VN8X*sDU|J`9P(C@<8n^AF0+6vxwMP zTZ3$yz`&Y>)j~I$3NY~?;p6q-TB)|ZJYvw8pEfw|$9tQ=Y`yKm&afsM)|WHkR}JW5 zWFBqKd*QHB4Ajkj$C@F|lVW@HwNxbs5La8p=Q}k_lgXt^_}Ii{gcp%s?VvV{J<6J#�SRARRoaUB%scHk}sXt4`vBb(t9sf_VtGH;FPp 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%0D|p+m=B2Vqz1 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 c?tZ_1+UEU4;2WpQ&c|= '0' && s[i] <= '9') { + n += s[i] - '0'; + } else if (s[i] >= 'A' && s[i] <= 'F') { + n += s[i] - 'A' + 10; + } + } + + return n; + +} + +void initramfs_callback(char *node_name, char *prop_name, struct fdt_prop *prop) { + + if (stringncmp(node_name, "chosen", 7) == 0 && + stringncmp(prop_name, "linux,initrd-start", 19) == 0) { + + DEVTREE_CPIO_BASE = (void*)to_lendian(*((unsigned int*)(prop + 1))); + + } + +} + +void cpio_ls() { + + struct cpio_newc_header *header; + unsigned int filesize; + unsigned int namesize; + unsigned int offset; + char *filename; + + header = DEVTREE_CPIO_BASE; + + while (1) { + + filename = ((void*)header) + sizeof(struct cpio_newc_header); + + if (stringncmp((char*)header, CPIO_HEADER_MAGIC, 6) != 0) { + uart_send_string("invalid magic\n"); + break; + } + if (stringncmp(filename, CPIO_FOOTER_MAGIC, 11) == 0) break; + + uart_send_string(filename); + uart_send('\n'); + + namesize = hexstr_to_uint(header->c_namesize, 8); + filesize = hexstr_to_uint(header->c_filesize, 8); + + offset = sizeof(struct cpio_newc_header) + namesize; + if (offset % 4 != 0) + offset = ((offset/4) + 1) * 4; + + if (filesize % 4 != 0) + filesize = ((filesize/4) + 1) * 4; + + offset = offset + filesize; + + header = ((void*)header) + offset; + + } + +} + +void cpio_cat() { + + char input[256]; + char c = '\0'; + int idx = 0; + + struct cpio_newc_header *header; + unsigned int filesize; + unsigned int namesize; + unsigned int offset; + char *filename; + + header = DEVTREE_CPIO_BASE; + + uart_send_string("Filename: "); + + while (1) { + c = uart_recv(); + if (c == '\r' || c == '\n') { + uart_send_string("\n"); + + if (idx < 256) input[idx] = '\0'; + else input[255] = '\0'; + + break; + } else { + uart_send(c); + input[idx++] = c; + } + } + + while (1) { + + filename = ((void*)header) + sizeof(struct cpio_newc_header); + + if (stringncmp((char*)header, CPIO_HEADER_MAGIC, 6) != 0) { + uart_send_string("invalid magic\n"); + break; + } + if (stringncmp(filename, CPIO_FOOTER_MAGIC, 11) == 0) { + uart_send_string("file does not exist!\n"); + break; + } + + namesize = hexstr_to_uint(header->c_namesize, 8); + + offset = sizeof(struct cpio_newc_header) + namesize; + if (offset % 4 != 0) + offset = ((offset/4) + 1) * 4; + + filesize = hexstr_to_uint(header->c_filesize, 8); + + if (stringncmp(filename, input, namesize) == 0) { + + char *content = ((void*)header) + offset; + + for (int i=0; ic_namesize, 8); + + offset = sizeof(struct cpio_newc_header) + namesize; + if (offset % 4 != 0) + offset = ((offset/4) + 1) * 4; + + filesize = hexstr_to_uint(header->c_filesize, 8); + + if (stringncmp(filename, input, namesize) == 0) { + + char *code_loc = ((void*)header) + offset; + unsigned int sp_val = 0x600000; + asm volatile( + "msr elr_el1, %0\n\t" + "msr spsr_el1, xzr\n\t" + "msr sp_el0, %1\n\t" + "eret\n\t" + :: + "r" (code_loc), + "r" (sp_val) + ); + + break; + } + + if (filesize % 4 != 0) + filesize = ((filesize/4) + 1) * 4; + + offset = offset + filesize; + + header = ((void*)header) + offset; + + } + +} diff --git a/lab7_novm/lib/devtree.c b/lab7_novm/lib/devtree.c new file mode 100644 index 000000000..18d763c21 --- /dev/null +++ b/lab7_novm/lib/devtree.c @@ -0,0 +1,92 @@ +#include "devtree.h" +#include "mini_uart.h" +#include "string.h" + +static void *DEVTREE_ADDRESS = 0; + +unsigned int to_lendian(unsigned int n) { + return ((n>>24)&0x000000FF) | + ((n>>8) &0x0000FF00) | + ((n<<8) &0x00FF0000) | + ((n<<24)&0xFF000000) ; +} + +void devtree_getaddr() { + + asm volatile("MOV %0, x20" : "=r"(DEVTREE_ADDRESS)); + + char magic[4] = {0xd0, 0x0d, 0xfe, 0xed}; + if(stringncmp((char*)DEVTREE_ADDRESS, magic, 4) != 0) { + uart_send_string("magic failed\n"); + } else { + uart_send_string("devtree magic succeed\n"); + } + +} + +void fdt_traverse( void (*callback)(char *, char *, struct fdt_prop *) ) { + + char magic[4] = {0xd0, 0x0d, 0xfe, 0xed}; + struct fdt_header *devtree_header = DEVTREE_ADDRESS; + + if(stringncmp((char*)devtree_header, magic, 4) != 0) { + uart_send_string("devtree magic failed\n"); + return; + } + + void *dt_struct_addr = DEVTREE_ADDRESS + to_lendian(devtree_header->off_dt_struct); + char *dt_string_addr = DEVTREE_ADDRESS + to_lendian(devtree_header->off_dt_strings); + + char *node_name; + char *prop_name; + unsigned int token; + unsigned int off; + + while (1) { + + token = to_lendian(*((unsigned int *)dt_struct_addr)); + + if (token == FDT_BEGIN_NODE) { + + node_name = dt_struct_addr + 4; + off = 4 + strlen(node_name) + 1; + + if (off%4 != 0) + off = ((off/4)+1)*4; + + dt_struct_addr += off; + + } + else if (token == FDT_END_NODE) { + dt_struct_addr += 4; + } + else if (token == FDT_PROP) { + + struct fdt_prop *prop = (struct fdt_prop*)(dt_struct_addr + 4); + + off = 4 + 8 + to_lendian(prop->len); + if (off%4 != 0) + off = ((off/4)+1)*4; + + dt_struct_addr += off; + + prop_name = dt_string_addr + to_lendian(prop->nameoff); + + callback(node_name, prop_name, prop); + + } + else if (token == FDT_NOP) { + dt_struct_addr += 4; + } + else if (token == FDT_END) { + dt_struct_addr += 4; + break; + } + else { + uart_send_string("TOKEN NOT MATCHED\n"); + break; + } + + } + +} \ No newline at end of file diff --git a/lab7_novm/lib/entry.S b/lab7_novm/lib/entry.S new file mode 100644 index 000000000..e564be0fe --- /dev/null +++ b/lab7_novm/lib/entry.S @@ -0,0 +1,195 @@ +#include "entry.h" +#include "syscall.h" + +.macro handle_invalid_entry el, type + kernel_entry \el + mov x0, #\type + mrs x1, esr_el1 + mrs x2, elr_el1 + bl show_invalid_entry_message + b err_hang +.endm + +.macro ventry label + .align 7 + b \label +.endm + +// save general registers to stack +.macro kernel_entry, el + sub sp, sp, #S_FRAME_SIZE + 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] + + .if \el == 0 + mrs x21, sp_el0 + .else + add x21, sp, #S_FRAME_SIZE + .endif + + mrs x22, elr_el1 + mrs x23, spsr_el1 + + stp x30, x21, [sp, 16 * 15] + stp x22, x23, [sp, 16 * 16] +.endm + +// load general registers from stack +.macro kernel_exit, el + ldp x22, x23, [sp, 16 * 16] + ldp x30, x21, [sp, 16 * 15] + + .if \el == 0 + msr sp_el0, x21 + .endif + + msr elr_el1, x22 + msr spsr_el1, x23 + + ldp x0, x1, [sp ,16 * 0] + ldp x2, x3, [sp ,16 * 1] + ldp x4, x5, [sp ,16 * 2] + ldp x6, x7, [sp ,16 * 3] + ldp x8, x9, [sp ,16 * 4] + ldp x10, x11, [sp ,16 * 5] + ldp x12, x13, [sp ,16 * 6] + ldp x14, x15, [sp ,16 * 7] + ldp x16, x17, [sp ,16 * 8] + ldp x18, x19, [sp ,16 * 9] + ldp x20, x21, [sp ,16 * 10] + ldp x22, x23, [sp ,16 * 11] + ldp x24, x25, [sp ,16 * 12] + ldp x26, x27, [sp ,16 * 13] + ldp x28, x29, [sp ,16 * 14] + add sp, sp, #S_FRAME_SIZE + eret +.endm + +.align 11 // vector table should be aligned to 0x800 +.globl exception_vector_table +exception_vector_table: + ventry sync_invalid_el1t // Synchronous EL1t + ventry irq_invalid_el1t // IRQ EL1t + ventry fiq_invalid_el1t // FIQ EL1t + ventry error_invalid_el1t // Error EL1t + + ventry sync_invalid_el1h // Synchronous EL1h + ventry el1_irq // IRQ EL1h + ventry fiq_invalid_el1h // FIQ EL1h + ventry error_invalid_el1h // Error EL1h + + ventry el0_sync // Synchronous 64-bit EL0 + ventry el0_irq // IRQ 64-bit EL0 + ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 + ventry error_invalid_el0_64 // Error 64-bit EL0 + + ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 + ventry irq_invalid_el0_32 // IRQ 32-bit EL0 + ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 + ventry error_invalid_el0_32 // Error 32-bit EL0 + +sync_invalid_el1t: + handle_invalid_entry 1, SYNC_INVALID_EL1t + +irq_invalid_el1t: + handle_invalid_entry 1, IRQ_INVALID_EL1t + +fiq_invalid_el1t: + handle_invalid_entry 1, FIQ_INVALID_EL1t + +error_invalid_el1t: + handle_invalid_entry 1, ERROR_INVALID_EL1t + +sync_invalid_el1h: + handle_invalid_entry 1, SYNC_INVALID_EL1h + +el1_irq: + kernel_entry 1 + bl handle_irq + kernel_exit 1 + +fiq_invalid_el1h: + handle_invalid_entry 1, FIQ_INVALID_EL1h + +error_invalid_el1h: + handle_invalid_entry 1, ERROR_INVALID_EL1h + +el0_sync: + kernel_entry 0 + mrs x25, esr_el1 + lsr x24, x25, #ESR_ELx_EC_SHIFT + cmp x24, #ESR_ELx_EC_SVC64 + b.eq el0_svc + handle_invalid_entry 0, SYNC_ERROR + +el0_irq: + kernel_entry 0 + bl handle_irq + kernel_exit 0 + +fiq_invalid_el0_64: + handle_invalid_entry 0, FIQ_INVALID_EL0_64 + +error_invalid_el0_64: + handle_invalid_entry 0, ERROR_INVALID_EL0_64 + +sync_invalid_el0_32: + handle_invalid_entry 0, SYNC_INVALID_EL0_32 + +irq_invalid_el0_32: + handle_invalid_entry 0, IRQ_INVALID_EL0_32 + +fiq_invalid_el0_32: + handle_invalid_entry 0, FIQ_INVALID_EL0_32 + +error_invalid_el0_32: + handle_invalid_entry 0, ERROR_INVALID_EL0_32 + +sc_nr .req x25 +scno .req x26 +stbl .req x27 + +el0_svc: + adr stbl, sys_call_table + uxtw scno, w8 + mov sc_nr, #__NR_SYSCALLS + bl enable_interrupt + cmp scno, sc_nr + b.hs ni_sys + + ldr x16, [stbl, scno, lsl #3] + blr x16 + b ret_from_syscall +ni_sys: + handle_invalid_entry 0, SYSCALL_ERROR +ret_from_syscall: + bl disable_interrupt + str x0, [sp, #S_X0] + kernel_exit 0 + +.globl ret_from_fork +ret_from_fork: + bl schedule_tail + cbz x19, ret_to_user + mov x0, x20 + blr x19 // in theory should not return + //b err_hang // hang fail-safe +ret_to_user: + bl disable_interrupt + kernel_exit 0 + +.globl err_hang +err_hang: b err_hang diff --git a/lab7_novm/lib/exception.c b/lab7_novm/lib/exception.c new file mode 100644 index 000000000..a820ba662 --- /dev/null +++ b/lab7_novm/lib/exception.c @@ -0,0 +1,49 @@ +#include "exception.h" +#include "mini_uart.h" +#include "mailbox.h" +#include "utils.h" +#include "timer.h" +#include "peripherals/exception.h" +#include "peripherals/mini_uart.h" + +void enable_interrupt() {asm volatile("msr DAIFClr, 0xf");} +void disable_interrupt() {asm volatile("msr DAIFSet, 0xf");} + +const char *entry_error_messages[] = { + "SYNC_INVALID_EL1t", + "IRQ_INVALID_EL1t", + "FIQ_INVALID_EL1t", + "ERROR_INVALID_EL1t", + + "SYNC_INVALID_EL1h", + "IRQ_INVALID_EL1h", + "FIQ_INVALID_EL1h", + "ERROR_INVALID_EL1h", + + "SYNC_INVALID_EL0_64", + "IRQ_INVALID_EL0_64", + "FIQ_INVALID_EL0_64", + "ERROR_INVALID_EL0_64", + + "SYNC_INVALID_EL0_32", + "IRQ_INVALID_EL0_32", + "FIQ_INVALID_EL0_32", + "ERROR_INVALID_EL0_32", + + "SYNC_ERROR", + "SYSCALL_ERROR" +}; + +void show_invalid_entry_message(int type, unsigned long esr, unsigned long addr) { + printf("%s, ESR: 0x%x, address: 0x%x\n", entry_error_messages[type], esr, addr); +} + +void handle_irq() { + + if (get32(CORE0_INTERRUPT_SRC)&INTERRUPT_SOURCE_CNTPNSIRQ) { + handle_timer_irq(); + } else { + printf("unknown irq encountered"); + } + +} \ No newline at end of file diff --git a/lab7_novm/lib/fork.c b/lab7_novm/lib/fork.c new file mode 100644 index 000000000..5d8893126 --- /dev/null +++ b/lab7_novm/lib/fork.c @@ -0,0 +1,83 @@ +#include "fork.h" +#include "mm.h" +#include "mini_uart.h" +#include "../include/sched.h" +#include "../include/entry.h" + +int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg, unsigned long stack) { + + preempt_disable(); + struct task_struct *p; + + p = (struct task_struct *) malloc(PAGE_SIZE); + if (p == NULL) + return -1; + + struct pt_regs *childregs = task_pt_regs(p); + memzero((unsigned long)childregs, sizeof(struct pt_regs)); + memzero((unsigned long)&p->cpu_context, sizeof(struct cpu_context)); + + if (clone_flags & PF_KTHREAD) { + p->cpu_context.x19 = fn; + p->cpu_context.x20 = arg; + } else { + struct pt_regs *cur_regs = task_pt_regs(current); + // *childregs = *cur_regs; (object file generates memcpy) + // therefore the for loop is used below + for(int i=0; iregs[0] = 0; // return value 0 + childregs->sp = stack + PAGE_SIZE; + p->stack = stack; + } + + p->flags = clone_flags; + p->priority = current->priority; + p->state = TASK_RUNNING; + p->counter = p->priority; + p->preempt_count = 1; + + p->cpu_context.pc = (unsigned long)ret_from_fork; + p->cpu_context.sp = (unsigned long)childregs; + + int pid = nr_tasks++; + task[pid] = p; + p->id = pid; + preempt_enable(); + + return pid; + +} + +int move_to_user_mode(unsigned long pc) { + + struct pt_regs *regs = task_pt_regs(current); + memzero((unsigned long)regs, sizeof(*regs)); + regs->pc = pc; + regs->pstate = PSR_MODE_EL0t; + unsigned long stack = (unsigned long)malloc(PAGE_SIZE); + + if (stack == NULL) + return -1; + + regs->sp = stack + PAGE_SIZE; + current->stack = stack; + return 0; + +} + +struct pt_regs *task_pt_regs(struct task_struct *tsk) { + + unsigned long p = (unsigned long)tsk + THREAD_SIZE - sizeof(struct pt_regs); + return (struct pt_regs *)p; + +} + +void new_user_process(unsigned long func){ + printf("Kernel process started, moving to user mode.\n"); + int err = move_to_user_mode(func); + if (err < 0){ + printf("Error while moving process to user mode\n\r"); + } +} \ No newline at end of file diff --git a/lab7_novm/lib/mailbox.c b/lab7_novm/lib/mailbox.c new file mode 100644 index 000000000..e593af037 --- /dev/null +++ b/lab7_novm/lib/mailbox.c @@ -0,0 +1,49 @@ +#include "peripherals/mailbox.h" +#include "mailbox.h" +#include "mini_uart.h" + +int mailbox_call (volatile unsigned int *mailbox) { + unsigned int msg = ((unsigned long)mailbox & ~0xF) | (0x8 & 0xF); + while (*MAILBOX_STATUS & MAILBOX_FULL) ; + *MAILBOX_WRITE = msg; + while (1) { + while (*MAILBOX_STATUS & MAILBOX_EMPTY) {} + if (msg == *MAILBOX_READ) { + return mailbox[1]; + } + } +} + +void get_board_revision () +{ + volatile unsigned int __attribute__((aligned(16))) mailbox[7]; + mailbox[0] = 7 * 4; + mailbox[1] = REQUEST_CODE; + mailbox[2] = GET_BOARD_REVISION; + mailbox[3] = 4; + mailbox[4] = TAG_REQUEST_CODE; + mailbox[5] = 0; + mailbox[6] = END_TAG; + + if (mailbox_call(mailbox) == REQUEST_SUCCEED) { + printf("Board Revision:\t\t%x\n", mailbox[5]); + } +} + +void get_arm_memory () +{ + volatile unsigned int __attribute__((aligned(16))) mailbox[8]; + mailbox[0] = 8 * 4; + mailbox[1] = REQUEST_CODE; + mailbox[2] = GET_ARM_MEMORY; + mailbox[3] = 8; + mailbox[4] = TAG_REQUEST_CODE; + mailbox[5] = 0; + mailbox[6] = 0; + mailbox[7] = END_TAG; + + if (mailbox_call(mailbox) == REQUEST_SUCCEED) { + printf("Memory Base Addresss:\t%x\n", mailbox[5]); + printf("Memory Size:\t\t%x\n", mailbox[6]); + } +} \ No newline at end of file diff --git a/lab7_novm/lib/math.c b/lab7_novm/lib/math.c new file mode 100644 index 000000000..6b6919044 --- /dev/null +++ b/lab7_novm/lib/math.c @@ -0,0 +1,19 @@ +#include "math.h" + +int log(int n, int base) { + int x = 1; + int ret = 0; + while (x <= n) { + x *= base; + ret++; + } + return ret; +} + +int pow(int base, int pow) { + int ret = 1; + for (int i=0; i avail) { + uart_send_string("not enough memory\n"); + } else { + __heap_ptr += size; + avail -= size; + } + + return ptr; + +} \ No newline at end of file diff --git a/lab7_novm/lib/mini_uart.c b/lab7_novm/lib/mini_uart.c new file mode 100644 index 000000000..c209f3a90 --- /dev/null +++ b/lab7_novm/lib/mini_uart.c @@ -0,0 +1,69 @@ +#include "utils.h" +#include "printf.h" +#include "peripherals/mini_uart.h" +#include "peripherals/gpio.h" +#include "peripherals/exception.h" + +void uart_send ( char c ) +{ + if (c == '\n') uart_send('\r'); + + while (1) { + if (get32(AUX_MU_LSR_REG)&0x20) + break; + } + + put32(AUX_MU_IO_REG,c); +} + +char uart_recv ( void ) +{ + while(1) { + if (get32(AUX_MU_LSR_REG)&0x01) + break; + } + return (get32(AUX_MU_IO_REG)&0xFF); +} + +void uart_send_string ( char* str ) +{ + for (int i = 0; str[i] != '\0'; i++) { + uart_send((char)str[i]); + } +} + +void printf(char *fmt, ...) { + char temp[128]; + __builtin_va_list args; + __builtin_va_start(args, fmt); + vsprintf(temp,fmt,args); + uart_send_string(temp); +} + +void uart_init ( void ) +{ + unsigned int selector; + + selector = get32(GPFSEL1); + selector &= ~(7<<12); // clean gpio14 + selector |= 2<<12; // set alt5 for gpio14 + selector &= ~(7<<15); // clean gpio15 + selector |= 2<<15; // set alt5 for gpio 15 + put32(GPFSEL1,selector); + + put32(GPPUD,0); + delay(150); + put32(GPPUDCLK0,(1<<14)|(1<<15)); + delay(150); + put32(GPPUDCLK0,0); + + put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) + put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) + put32(AUX_MU_IER_REG,0); //Enable receive and disable transmit interrupts + put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode + put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high + put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 + put32(AUX_MU_IIR_REG,6); //Interrupt identify no fifo + put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver + +} diff --git a/lab7_novm/lib/mm.S b/lab7_novm/lib/mm.S new file mode 100644 index 000000000..263de4251 --- /dev/null +++ b/lab7_novm/lib/mm.S @@ -0,0 +1,6 @@ +.globl memzero +memzero: + str xzr, [x0], #8 + subs x1, x1, #8 + b.gt memzero + ret \ No newline at end of file diff --git a/lab7_novm/lib/mm.c b/lab7_novm/lib/mm.c new file mode 100644 index 000000000..05a5f6f50 --- /dev/null +++ b/lab7_novm/lib/mm.c @@ -0,0 +1,376 @@ +#include "mm.h" +#include "math.h" +#include "memory.h" +#include "mini_uart.h" + +static unsigned int n_frames = 0; +static unsigned int max_size = 0; +static struct frame* frame_list[MAX_ORDER] = {NULL}; +static struct frame frame_array[(MEM_REGION_END-MEM_REGION_BEGIN)/PAGE_SIZE]; +static struct dynamic_pool pools[MAX_POOLS] = { {ALLOCABLE, 0, 0, 0, 0, {NULL}, NULL} }; +static unsigned int reserved_num = 0; +static void* reserved_se[MAX_RESERVABLE][2] = {{0x0, 0x0}}; // expects to be sorted and addresses [,) +extern char __kernel_end; +static char *__kernel_end_ptr = &__kernel_end; + +void *malloc(unsigned int size) { + + if (size > max_size) { + printf("[error] Request exceeded allocable continuous size %d.\n", (int)max_size); + return NULL; + } + + int req_order = 0; + for(unsigned int i=PAGE_SIZE; i= MAX_ORDER) { + printf("[error] No memory allocable.\n"); + return NULL; + } + + while (t != req_order) { + struct frame* l_tmp = frame_list[t]; + frame_list[t] = l_tmp->next; + frame_list[t]->prev = NULL; + //printf("[info] Split at order %d, new head is 0x%x.\n", t+1, frame_list[t]); + + unsigned int off = pow(2, l_tmp->val-1); + struct frame* r_tmp = &frame_array[l_tmp->index+off]; + + l_tmp->val -= 1; + l_tmp->state = ALLOCABLE; + l_tmp->prev = NULL; + l_tmp->next = r_tmp; + + r_tmp->val = l_tmp->val; + r_tmp->state = ALLOCABLE; + r_tmp->prev = l_tmp; + r_tmp->next = NULL; + + t--; + if (frame_list[t] != NULL) + frame_list[t]->prev = r_tmp; + r_tmp->next = frame_list[t]; + frame_list[t] = l_tmp; + } + + struct frame* ret = frame_list[req_order]; + frame_list[req_order] = ret->next; + frame_list[req_order]->prev = NULL; + + ret->val = ret->val; + ret->state = ALLOCATED; + ret->prev = NULL; + ret->next = NULL; + + //printf("[info] allocated address: 0x%x\n", MEM_REGION_BEGIN+PAGE_SIZE*ret->index); + + return (void*)MEM_REGION_BEGIN+PAGE_SIZE*ret->index; + +} + +void free(void *address) { + + unsigned int idx = ((unsigned long long)address-MEM_REGION_BEGIN) / PAGE_SIZE; + struct frame* target = &frame_array[idx]; + + if (target->state == ALLOCABLE || target->state == C_NALLOCABLE) { + printf("[error] invalid free of already freed memory.\n"); + return; + } + //printf("=========================================================\n"); + //printf("[info] Now freeing address 0x%x with frame index %d.\n", address, (int)idx); + + for (int i=target->val; ival+1, fr_buddy->state); + + if (i < MAX_ORDER-1 && fr_buddy->state == ALLOCABLE && i== fr_buddy->val) { + + //printf("[info] Merging from order %d. Frame indices %d, %d.\n", i+1, (int)buddy, (int)idx); + + if (fr_buddy->prev != NULL) { + fr_buddy->prev->next = fr_buddy->next; + } else { + frame_list[fr_buddy->val] = fr_buddy->next; + } + + if (fr_buddy->next != NULL) { + fr_buddy->next->prev = fr_buddy->prev; + } + + fr_buddy->prev = NULL; + fr_buddy->next = NULL; + fr_buddy->val = C_NALLOCABLE; + fr_buddy->state = C_NALLOCABLE; + target->val = C_NALLOCABLE; + target->state = C_NALLOCABLE; + + if (fr_buddy->index < target->index) { + idx = fr_buddy->index; + target = fr_buddy; + } + + //printf("[info] Frame index of next merge target is %d.\n", (int)idx); + + } else { + + target->val = i; + target->state = ALLOCABLE; + target->prev = NULL; + target->next = frame_list[i]; + if (frame_list[i] != NULL) + frame_list[i]->prev = target; + frame_list[i] = target; + //printf("[info] Frame index %d pushed to frame list of order %d.\n", + // (int)target->index, (int)i+1); + break; + + } + + } + + //printf("[info] Free finished.\n"); + /*for (int i=0; i < MAX_ORDER; i++) { + if (frame_list[i] != NULL) + printf("[info] Head of order %d has frame array index %d.\n",i+1,frame_list[i]->index); + else + printf("[info] Head of order %d has frame array index null.\n",i+1); + }*/ + +} + +void init_mm() { + + n_frames = (MEM_REGION_END-MEM_REGION_BEGIN) / PAGE_SIZE; + unsigned int mul = (unsigned int)pow(2, MAX_ORDER-1); + printf("[info] Frame array start address 0x%x.\n", frame_array); + for (unsigned int i=0; ichunk_size = size; + pool->chunks_per_page = PAGE_SIZE / size; + pool->chunks_allocated = 0; + pool->page_new_chunk_off = 0; + pool->pages_used = 0; + pool->free_head = NULL; +} + +int register_chunk(unsigned int size) { + + unsigned int nsize = 0; + if (size <= 8) nsize = 8; + else { + int rem = size % 4; + if (rem != 0) nsize = (size/4 + 1)*4; + else nsize = size; + } + + if (nsize >= PAGE_SIZE) { + printf("[error] Normalized chunk size request leq page size.\n"); + return -1; + } + + for (int i=0; ifree_head != NULL) { + void *ret = (void*) pool->free_head; + pool->free_head = pool->free_head->next; + //printf("[info] allocate address 0x%x from pool free list.\n", ret); + return ret; + } + + if (pool->chunks_allocated >= MAX_POOL_PAGES*pool->chunks_per_page) { + //printf("[error] Pool maximum reached.\n"); + return NULL; + } + + + if (pool->chunks_allocated >= pool->pages_used*pool->chunks_per_page) { + pool->page_base_addrs[pool->pages_used] = malloc(PAGE_SIZE); + //printf("[info] allocate new page for pool with base address 0x%x.\n", + // pool->page_base_addrs[pool->pages_used]); + pool->pages_used++; + pool->page_new_chunk_off = 0; + } + + void *ret = pool->page_base_addrs[pool->pages_used - 1] + + pool->chunk_size*pool->page_new_chunk_off; + pool->page_new_chunk_off++; + pool->chunks_allocated++; + + //printf("[info] allocate new address 0x%x from pool.\n", ret); + + return ret; + +} + +void chunk_free(void *address) { + + int target = -1; + + void *prefix_addr = (void *)((unsigned long long)address & ~0xFFF); + + for (unsigned int i=0; ifree_head; + pool->free_head = (struct node*) address; + pool->free_head->next = old_head; + pool->chunks_allocated--; + +} + +void memory_reserve(void* start, void* end) { + if (reserved_num >= MAX_RESERVABLE) { + printf("[error] Max reservable locations already reached.\n"); + return; + } + reserved_se[reserved_num][0] = start; + reserved_se[reserved_num][1] = end; + reserved_num++; +} + +void init_mm_reserve() { + + max_size = PAGE_SIZE * pow(2, MAX_ORDER-1); + n_frames = (MEM_REGION_END-MEM_REGION_BEGIN) / PAGE_SIZE; + + memory_reserve((void*)0x0, __kernel_end_ptr); // spin tables, kernel image + memory_reserve((void*)0x20000000, (void*)0x20010000); // hard code reserve initramfs + + for (unsigned int i=0; i= reserved_se[i][0] && addr < reserved_se[i][1]) { + frame_array[j].state = RESERVED; + } + if (addr >= reserved_se[i][1]) break; + } + } + + for (int i=0; istate == RESERVED) continue; + if (target->state == C_NALLOCABLE) continue; + + for (int i=target->val; istate == ALLOCABLE && i== fr_buddy->val) { + + if (fr_buddy->prev != NULL) { + fr_buddy->prev->next = fr_buddy->next; + } else { + frame_list[fr_buddy->val] = fr_buddy->next; + } + + if (fr_buddy->next != NULL) { + fr_buddy->next->prev = fr_buddy->prev; + } + + fr_buddy->prev = NULL; + fr_buddy->next = NULL; + fr_buddy->val = C_NALLOCABLE; + fr_buddy->state = C_NALLOCABLE; + target->val = C_NALLOCABLE; + target->state = C_NALLOCABLE; + + if (fr_buddy->index < target->index) { + idx = fr_buddy->index; + target = fr_buddy; + } + + } else { + + target->val = i; + target->state = ALLOCABLE; + target->prev = NULL; + target->next = frame_list[i]; + if (frame_list[i] != NULL) + frame_list[i]->prev = target; + frame_list[i] = target; + break; + + } + + } + + } + +} \ No newline at end of file diff --git a/lab7_novm/lib/printf.c b/lab7_novm/lib/printf.c new file mode 100644 index 000000000..794a37dff --- /dev/null +++ b/lab7_novm/lib/printf.c @@ -0,0 +1,152 @@ +/* + * 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 "printf.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) { + // 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_start(args, fmt): "..." is pointed by args + //__builtin_va_arg(args,int): ret=(int)*args;args++;return ret; + __builtin_va_list args; + __builtin_va_start(args, fmt); + return vsprintf(dst,fmt,args); +} \ No newline at end of file diff --git a/lab7_novm/lib/reboot.c b/lab7_novm/lib/reboot.c new file mode 100644 index 000000000..043a09fb7 --- /dev/null +++ b/lab7_novm/lib/reboot.c @@ -0,0 +1,18 @@ +#define PM_PASSWORD 0x5a000000 +#define PM_RSTC 0x3F10001c +#define PM_WDOG 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 +} diff --git a/lab7_novm/lib/sched.S b/lab7_novm/lib/sched.S new file mode 100644 index 000000000..03f9c56b1 --- /dev/null +++ b/lab7_novm/lib/sched.S @@ -0,0 +1,24 @@ +#include "sched.h" + +.global cpu_switch_to +cpu_switch_to: + mov x10, #THREAD_CPU_CONTEXT + add x8, x0, x10 + mov x9, sp + stp x19, x20, [x8], #16 // store callee-saved registers + stp x21, x22, [x8], #16 + stp x23, x24, [x8], #16 + stp x25, x26, [x8], #16 + stp x27, x28, [x8], #16 + stp x29, x9, [x8], #16 + str x30, [x8] + add x8, x1, x10 + ldp x19, x20, [x8], #16 // restore callee-saved registers + ldp x21, x22, [x8], #16 + ldp x23, x24, [x8], #16 + ldp x25, x26, [x8], #16 + ldp x27, x28, [x8], #16 + ldp x29, x9, [x8], #16 + ldr x30, [x8] + mov sp, x9 + ret diff --git a/lab7_novm/lib/sched.c b/lab7_novm/lib/sched.c new file mode 100644 index 000000000..2ffc32acd --- /dev/null +++ b/lab7_novm/lib/sched.c @@ -0,0 +1,107 @@ +#include "../include/sched.h" +#include "mm.h" +#include "exception.h" +#include "mini_uart.h" + +static struct task_struct init_task = INIT_TASK; +struct task_struct *current = &(init_task); +struct task_struct *task[NR_TASKS] = {&(init_task), }; +int nr_tasks = 1; + +void preempt_disable() { + current->preempt_count++; +} + +void preempt_enable() { + current->preempt_count--; +} + +void _schedule() { + + int next, c; + struct task_struct *p; + while (1) { + c = -1; + next = 0; + for (int i=0; istate == TASK_RUNNING && p->counter > c) { + c = p->counter; + next = i; + } + } + if (c) { + break; + } + for (int i=0; icounter = (p->counter >> 1) + p->priority; + } + } + preempt_disable(); // should be fine, if anything breaks move this to the top + switch_to(task[next]); + preempt_enable(); + +} + +void schedule() { + current->counter = 0; + _schedule(); +} + +void switch_to(struct task_struct *next) { + if (current == next) + return; + struct task_struct *prev = current; + current = next; + cpu_switch_to(prev, next); +} + +void schedule_tail() { + preempt_enable(); +} + +void timer_tick() { + + --current->counter; + if (current->counter > 0 || current->preempt_count > 0) + return; + + current->counter = 0; + enable_interrupt(); + _schedule(); + disable_interrupt(); + +} + +void exit_process() { + // should only be accessed using syscall + // preempt_disable(); + current->state = TASK_ZOMBIE; + free((void*)current->stack); + // preempt_enable(); + schedule(); +} + +void kill_zombies() { + + struct task_struct *p; + for (int i=0; istate == TASK_ZOMBIE) { + printf("Zombie found with pid: %d.\n", p->id); + free(p); + task[i] = NULL; + } + + } + +} \ No newline at end of file diff --git a/lab7_novm/lib/shell.c b/lab7_novm/lib/shell.c new file mode 100644 index 000000000..75a630b9e --- /dev/null +++ b/lab7_novm/lib/shell.c @@ -0,0 +1,223 @@ +#include "shell.h" +#include "mini_uart.h" +#include "utils.h" +#include "mailbox.h" +#include "reboot.h" +#include "string.h" +#include "../include/cpio.h" +#include "memory.h" +#include "timer.h" +#include "exception.h" +#include "math.h" +#include "mm.h" +#include "../include/sched.h" +#include "syscall.h" +#include "peripherals/mailbox.h" +#include "fork.h" + + +#define MAX_BUFFER_SIZE 256u + +static char buffer[MAX_BUFFER_SIZE]; +static int shared = 1; + +void foo() { + for(int i = 0; i < 10; ++i) { + printf("Thread id: %d %d\n", current->id, i); + delay(1000000); + schedule(); + } + + current->state = TASK_ZOMBIE; + while(1); +} + +void user_foo() { + + printf("User thread id: %d\n", getpid()); + + volatile unsigned int __attribute__((aligned(16))) mailbox[7]; + mailbox[0] = 7 * 4; + mailbox[1] = REQUEST_CODE; + mailbox[2] = GET_BOARD_REVISION; + mailbox[3] = 4; + mailbox[4] = TAG_REQUEST_CODE; + mailbox[5] = 0; + mailbox[6] = END_TAG; + mbox_call(0x8, mailbox); + printf("Board Revision:\t\t%x\n", mailbox[5]); + + int pid = fork(); + if (pid == 0) { + printf("Child says hello!\n"); + while(1) { + printf("Please don't kill me :(\n"); + shared++; + } + } else if (pid > 0) { + printf("Parent says, \"My child has pid %d\"\n", pid); + printf("Shared? %d\n", shared); + delay(10000000); + printf("Kill my own child :(\n"); + kill(pid); + delay(10000000); + printf("shared %d\n", shared); + } + + //char buf[4] = {0}; + //uart_read(buf, 3); + //uart_write(buf, 3); + + exit(); + +} + +void start_video() { + // ... go to cpio, find location and size of syscall.img + // allocate pages + // move syscall.img to allocated memory + // preempt disable + // change this shell thread to not runnable + // start sycall.img user process + // preempt enable + struct cpio_newc_header *header; + unsigned int filesize; + unsigned int namesize; + unsigned int offset; + char *filename; + void *code_loc; + + header = DEVTREE_CPIO_BASE; + while (1) { + + filename = ((void*)header) + sizeof(struct cpio_newc_header); + + if (stringncmp((char*)header, CPIO_HEADER_MAGIC, 6) != 0) { + uart_send_string("invalid magic\n"); + break; + } + if (stringncmp(filename, CPIO_FOOTER_MAGIC, 11) == 0) { + uart_send_string("file does not exist!\n"); + break; + } + + namesize = hexstr_to_uint(header->c_namesize, 8); + + offset = sizeof(struct cpio_newc_header) + namesize; + if (offset % 4 != 0) + offset = ((offset/4) + 1) * 4; + + filesize = hexstr_to_uint(header->c_filesize, 8); + + if (stringncmp(filename, "syscall.img", namesize) == 0) { + code_loc = ((void*)header) + offset; + break; + } + + if (filesize % 4 != 0) + filesize = ((filesize/4) + 1) * 4; + + offset = offset + filesize; + + header = ((void*)header) + offset; + + } + printf("syscall.img found in cpio at location 0x%x.\n", code_loc); + printf("syscall.img has size of %d bytes.\n", (int)filesize); + + void *move_loc = malloc(filesize + 4096); // an extra page for bss just in case + if(move_loc == NULL) return; + for (int i=0; istate = TASK_STOPPED; + unsigned long long tmp; + asm volatile("mrs %0, cntkctl_el1" : "=r"(tmp)); + tmp |= 1; + asm volatile("msr cntkctl_el1, %0" : : "r"(tmp)); + copy_process(PF_KTHREAD, (unsigned long)&new_user_process, (unsigned long)move_loc, 0); + preempt_enable(); + +} + +void read_cmd() +{ + unsigned int idx = 0; + char c = '\0'; + + while (1) { + c = uart_recv(); + if (c == '\r' || c == '\n') { + uart_send_string("\n"); + + if (idx < MAX_BUFFER_SIZE) buffer[idx] = '\0'; + else buffer[MAX_BUFFER_SIZE-1] = '\0'; + + break; + } else { + uart_send(c); + buffer[idx++] = c; + } + } + +} + +void parse_cmd() +{ + + if (stringcmp(buffer, "\0") == 0) + uart_send_string("\n"); + else if (stringcmp(buffer, "hello") == 0) + uart_send_string("Hello World!\n"); + else if (stringcmp(buffer, "reboot") == 0) { + uart_send_string("rebooting...\n"); + reset(100); + } + else if (stringcmp(buffer, "hwinfo") == 0) { + get_board_revision(); + get_arm_memory(); + } + else if (stringcmp(buffer, "ls") == 0) { + cpio_ls(); + } + else if (stringcmp(buffer, "cat") == 0) { + cpio_cat(); + } + else if (stringcmp(buffer, "execute") == 0) { + cpio_exec(); + } + else if (stringcmp(buffer, "thread_test") == 0) { + for (int i=0; i<10; i++) { + copy_process(PF_KTHREAD, (unsigned long)&foo, 0, 0); + } + } + else if (stringcmp(buffer, "to_user") == 0) { + copy_process(PF_KTHREAD, (unsigned long)&new_user_process, (unsigned long)&user_foo, 0); + } + else if (stringcmp(buffer, "video") == 0) { + start_video(); + } + else if (stringcmp(buffer, "help") == 0) { + uart_send_string("help:\t\tprint list of available commands\n"); + uart_send_string("hello:\t\tprint Hello World!\n"); + uart_send_string("reboot:\t\treboot device\n"); + uart_send_string("hwinfo:\t\tprint hardware information\n"); + uart_send_string("ls:\t\tlist initramfs files\n"); + uart_send_string("cat:\t\tprint file content in initramfs\n"); + uart_send_string("execute:\trun program from cpio\n"); + } + else + uart_send_string("Command not found! Type help for commands.\n"); + +} + +void shell_loop() +{ + while (1) { + uart_send_string("% "); + read_cmd(); + parse_cmd(); + } +} \ No newline at end of file diff --git a/lab7_novm/lib/string.c b/lab7_novm/lib/string.c new file mode 100644 index 000000000..df9ea89ca --- /dev/null +++ b/lab7_novm/lib/string.c @@ -0,0 +1,39 @@ +#include "string.h" + +int stringcmp(const char *p1, const char *p2) +{ + const unsigned char *s1 = (const unsigned char *) p1; + const unsigned char *s2 = (const unsigned char *) p2; + unsigned char c1, c2; + + do { + c1 = (unsigned char) *s1++; + c2 = (unsigned char) *s2++; + if (c1 == '\0') + return c1 - c2; + } while (c1 == c2); + + return c1 - c2; +} + +int stringncmp(const char *p1, const char *p2, unsigned int n) +{ + for (int i=0; iid; +} + +unsigned sys_uartread(char buf[], unsigned size) { + for(unsigned int i=0; ic_namesize, 8); + + offset = sizeof(struct cpio_newc_header) + namesize; + if (offset % 4 != 0) + offset = ((offset/4) + 1) * 4; + + filesize = hexstr_to_uint(header->c_filesize, 8); + + if (stringncmp(filename, name, namesize) == 0) { + code_loc = ((void*)header) + offset; + break; + } + + if (filesize % 4 != 0) + filesize = ((filesize/4) + 1) * 4; + + offset = offset + filesize; + + header = ((void*)header) + offset; + + } + + void *move_loc = malloc(filesize + 4096); // an extra page for bss just in case + if(move_loc == NULL) return -1; + for (int i=0; ipc = (unsigned long)move_loc; // move to beginning of program + p->sp = current->stack+PAGE_SIZE; + + preempt_enable(); + + return -1; // only on failure +} + +int sys_fork() { + return copy_process(0, 0, 0, (unsigned long)malloc(4*4096)); +} + +void sys_exit(int status) { + exit_process(); +} + +int sys_mbox_call(unsigned char ch, unsigned int *mbox) { + unsigned int r = (((unsigned int)((unsigned long)mbox)&~0xF) | (ch&0xF)); + while(*MAILBOX_STATUS & MAILBOX_FULL); + *MAILBOX_WRITE = r; + while (1) { + while (*MAILBOX_STATUS & MAILBOX_EMPTY) {} + if (r == *MAILBOX_READ) { + return mbox[1]==REQUEST_SUCCEED; + } + } + return 0; +} + +void sys_kill(int pid) { + + struct task_struct *p; + for (int i=0; iid == (long)pid) { + preempt_disable(); + printf("Kill target acquired.\n"); + p->state = TASK_ZOMBIE; + free((void *)p->stack); + preempt_enable(); + break; + } + + } + +} + +void * const sys_call_table[] = +{ + sys_getpid, + sys_uartread, + sys_uartwrite, + sys_exec, + sys_fork, + sys_exit, + sys_mbox_call, + sys_kill +}; \ No newline at end of file diff --git a/lab7_novm/lib/timer.c b/lab7_novm/lib/timer.c new file mode 100644 index 000000000..ae102a2b0 --- /dev/null +++ b/lab7_novm/lib/timer.c @@ -0,0 +1,62 @@ +#include "timer.h" +#include "../include/sched.h" +#include "mini_uart.h" + +void timer_init() { + core_timer_enable(); + set_timer(read_freq()); +} + +void handle_timer_irq() { + //printf("Timer interrupt.\n"); + set_timer(read_freq()>>5); + timer_tick(); +} + +void core_timer_enable() { + + asm volatile( + "mov x0, 1\n\t" + "msr cntp_ctl_el0, x0\n\t" // enable + "mov x0, 2\n\t" + "ldr x1, =0x40000040\n\t" // CORE0_TIMER_IRQ_CTRL + "str w0, [x1]\n\t" // unmask timer interrupt + ); + +} + +void core_timer_disable() { + + asm volatile( + "mov x0, 0\n\t" + "ldr x1, =0x40000040\n\t" + "str w0, [x1]\n\t" + ); + +} + +void set_timer(unsigned int rel_time) { + + asm volatile( + "msr cntp_tval_el0, %0\n\t" + : + : "r" (rel_time) + ); + +} + +unsigned int read_timer() { + + unsigned int time; + asm volatile("mrs %0, cntpct_el0\n\t" : "=r" (time) : : "memory"); + return time; + +} + +unsigned int read_freq() { + + unsigned int freq; + asm volatile("mrs %0, cntfrq_el0\n\t": "=r" (freq) : : "memory"); + return freq; + +} diff --git a/lab7_novm/lib/utils.S b/lab7_novm/lib/utils.S new file mode 100644 index 000000000..aa0e55aa9 --- /dev/null +++ b/lab7_novm/lib/utils.S @@ -0,0 +1,15 @@ +.globl put32 +put32: + str w1,[x0] + ret + +.globl get32 +get32: + ldr w0,[x0] + ret + +.globl delay +delay: + subs x0, x0, #1 + bne delay + ret \ No newline at end of file diff --git a/lab7_novm/send_img.py b/lab7_novm/send_img.py new file mode 100644 index 000000000..b2261950d --- /dev/null +++ b/lab7_novm/send_img.py @@ -0,0 +1,31 @@ +import argparse +import os +import time +import math +import serial + +parser = argparse.ArgumentParser(description='*.img uart sender') +parser.add_argument('-i', '--img', default='kernel8.img', type=str) +parser.add_argument('-d', '--device', default='/dev/ttyUSB0', type=str) +parser.add_argument('-b', '--baud', default=115200, type=int) + +args = parser.parse_args() + +img_size = os.path.getsize(args.img) + +with open(args.img, 'rb') as f: + with serial.Serial(args.device, args.baud) as tty: + + print(f'{args.img} is {img_size} bytes') + print('img file is now sending') + + tty.write(img_size.to_bytes(4, 'big')) + + input() + + for i in range(img_size): + tty.write(f.read(1)) + tty.flush() + #time.sleep(0.0001) + + print('img sent') From 8f4bb97534b50725cf84a8416106d9abe7be66ed Mon Sep 17 00:00:00 2001 From: Hsiang-Chieh Tsou Date: Mon, 6 Jun 2022 20:29:45 +0800 Subject: [PATCH 7/9] lab7 --- .vscode/settings.json | 3 +- lab7/include/sched.h | 8 +- lab7/include/string.h | 2 + lab7/include/syscall.h | 24 +++- lab7/include/tmpfs.h | 34 ++++++ lab7/include/vfs.h | 75 ++++++++++++ lab7/initramfs.cpio | Bin 248320 -> 404992 bytes lab7/kernel/kernel.c | 2 + lab7/lib/shell.c | 6 +- lab7/lib/string.c | 16 +++ lab7/lib/syscall.S | 42 +++++++ lab7/lib/syscall.c | 97 +++++++++++++++- lab7/lib/tmpfs.c | 156 +++++++++++++++++++++++++ lab7/lib/vfs.c | 146 +++++++++++++++++++++++ lab7_novm/include/initramfs.h | 35 ++++++ lab7_novm/include/sched.h | 8 +- lab7_novm/include/string.h | 2 + lab7_novm/include/syscall.h | 33 +++++- lab7_novm/include/tmpfs.h | 33 ++++++ lab7_novm/include/vfs.h | 81 +++++++++++++ lab7_novm/initramfs.cpio | Bin 404992 -> 404992 bytes lab7_novm/kernel/kernel.c | 3 + lab7_novm/lib/fork.c | 2 + lab7_novm/lib/initramfs.c | 211 ++++++++++++++++++++++++++++++++++ lab7_novm/lib/shell.c | 47 ++------ lab7_novm/lib/string.c | 16 +++ lab7_novm/lib/syscall.S | 42 +++++++ lab7_novm/lib/syscall.c | 83 ++++++++++++- lab7_novm/lib/tmpfs.c | 154 +++++++++++++++++++++++++ lab7_novm/lib/vfs.c | 197 +++++++++++++++++++++++++++++++ lab7_novm/rootfs/msg | 1 + 31 files changed, 1514 insertions(+), 45 deletions(-) create mode 100644 lab7/include/tmpfs.h create mode 100644 lab7/include/vfs.h create mode 100644 lab7/lib/tmpfs.c create mode 100644 lab7/lib/vfs.c create mode 100644 lab7_novm/include/initramfs.h create mode 100644 lab7_novm/include/tmpfs.h create mode 100644 lab7_novm/include/vfs.h create mode 100644 lab7_novm/lib/initramfs.c create mode 100644 lab7_novm/lib/tmpfs.c create mode 100644 lab7_novm/lib/vfs.c create mode 100644 lab7_novm/rootfs/msg diff --git a/.vscode/settings.json b/.vscode/settings.json index c4c50aba3..31c221e68 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "files.associations": { - "fork.h": "c" + "fork.h": "c", + "string.h": "c" } } \ No newline at end of file diff --git a/lab7/include/sched.h b/lab7/include/sched.h index 1c0d56c0d..64f456387 100644 --- a/lab7/include/sched.h +++ b/lab7/include/sched.h @@ -5,6 +5,8 @@ #ifndef __ASSEMBLER__ +#include "vfs.h" + #define THREAD_SIZE 4096 #define NR_TASKS 64 @@ -65,6 +67,8 @@ struct task_struct { unsigned long flags; long id; struct mm_struct mm; + struct fd_table files; + struct vnode *cwd; }; extern void sched_init(); @@ -82,7 +86,9 @@ extern void update_pgd(unsigned long); { \ {0,0,0,0,0,0,0,0,0,0,0,0,0},\ 0, 0, 1, 0, PF_KTHREAD, 0, \ -{0,0,{{0}},0,{0}}\ +{0,0,{{0}},0,{0}},\ +{0, {0}},\ +0 \ } #endif diff --git a/lab7/include/string.h b/lab7/include/string.h index abf24c93f..c71840033 100644 --- a/lab7/include/string.h +++ b/lab7/include/string.h @@ -4,5 +4,7 @@ int stringcmp (const char *, const char *); int stringncmp (const char *, const char *, unsigned int); unsigned int strlen(const char *); +void strcpy(char *dest, const char *src); +void strncpy(char *dest, const char *src, int n); #endif \ No newline at end of file diff --git a/lab7/include/syscall.h b/lab7/include/syscall.h index a1c2215f5..d81250876 100644 --- a/lab7/include/syscall.h +++ b/lab7/include/syscall.h @@ -1,7 +1,7 @@ #ifndef _SYSCALL_H #define _SYSCALL_H -#define __NR_SYSCALLS 8 +#define __NR_SYSCALLS 18 #define SYS_GETPID_NUM 0 #define SYS_UARTREAD_NUM 1 @@ -12,6 +12,14 @@ #define SYS_MBOXCALL_NUM 6 #define SYS_KILL_NUM 7 +#define SYS_OPEN_NUM 11 +#define SYS_CLOSE_NUM 12 +#define SYS_WRITE_NUM 13 +#define SYS_READ_NUM 14 +#define SYS_MKDIR_NUM 15 +#define SYS_MOUNT_NUM 16 +#define SYS_CHDIR_NUM 17 + #ifndef __ASSEMBLER__ int getpid(); @@ -22,6 +30,13 @@ int fork(); void exit(int status); int mbox_call(unsigned char ch, volatile unsigned int *mbox); void kill(int pid); +int open(const char *pathname, int flags); +int close(int fd); +long write(int fd, const void *buf, unsigned long count); +long read(int fd, void *buf, unsigned long count); +int mkdir(const char *pathname, unsigned mode); +int mount(const char *src, const char *target, const char *fs, unsigned long flags, const void *data); +int chdir(const char *path); int sys_getpid(); unsigned sys_uartread(char buf[], unsigned size); @@ -31,6 +46,13 @@ int sys_fork(); void sys_exit(int status); int sys_mbox_call(unsigned char ch, volatile unsigned int *mbox); void sys_kill(int pid); +int sys_open(const char *pathname, int flags); +int sys_close(int fd); +long sys_write(int fd, const void *buf, unsigned long count); +long sys_read(int fd, void *buf, unsigned long count); +int sys_mkdir(const char *pathname, unsigned mode); +int sys_mount(const char *src, const char *target, const char *fs, unsigned long flags, const void *data); +int sys_chdir(const char *path); #endif diff --git a/lab7/include/tmpfs.h b/lab7/include/tmpfs.h new file mode 100644 index 000000000..369622cc2 --- /dev/null +++ b/lab7/include/tmpfs.h @@ -0,0 +1,34 @@ +#ifndef _TMPFS_H +#define _TMPFS_H + +#include "vfs.h" + +#define REGULAR_FILE 0 +#define DIRECTORY 1 + +#define TMPFS_COMP_LEN 16 +#define MAX_DIRENT 16 +#define MAX_FILESIZE 0x1000 + +struct tmpfs_internal { + char name[TMPFS_COMP_LEN]; + int type; + struct tmpfs_internal *parent; + struct tmpfs_internal *child[MAX_DIRENT]; + struct vnode *vnode; + int size; // use for child count and data size + void *data; +}; + +int tmpfs_setup_mount(); +int tmpfs_register(); + +int tmpfs_open(struct vnode *file_node, struct file **target); +int tmpfs_close(struct file *file); +int tmpfs_write(struct file *file, const void *buf, unsigned len); +int tmpfs_read(struct file *file, void *buf, unsigned len); +int tmpfs_create(struct vnode *dir_node, struct vnode **target, const char *component_name); +int tmpfs_mkdir(struct vnode *dir_node, struct vnode **target, const char *component_name); +int tmpfs_lookup(struct vnode *dir_node, struct vnode **target, const char *component_name); + +#endif \ No newline at end of file diff --git a/lab7/include/vfs.h b/lab7/include/vfs.h new file mode 100644 index 000000000..05dc0cd35 --- /dev/null +++ b/lab7/include/vfs.h @@ -0,0 +1,75 @@ +#ifndef _VFS_H +#define _VFS_H + +struct vnode { + struct mount* mount; + struct vnode_operations* v_ops; + struct file_operations* f_ops; + void* internal; +}; + +// file handle +struct file { + struct vnode* vnode; + unsigned f_pos; // RW position of this file handle + struct file_operations* f_ops; + int flags; +}; + +struct mount { + struct vnode* root; + struct filesystem* fs; +}; + +struct filesystem { + char* name; + int (*setup_mount)(struct filesystem* fs, struct mount* mount); +}; + +struct file_operations { + int (*write)(struct file* file, const void* buf, unsigned len); + int (*read)(struct file* file, void* buf, unsigned len); + int (*open)(struct vnode* file_node, struct file** target); + int (*close)(struct file* file); +}; + +struct vnode_operations { + int (*lookup)(struct vnode* dir_node, struct vnode** target, + const char* component_name); + int (*create)(struct vnode* dir_node, struct vnode** target, + const char* component_name); + int (*mkdir)(struct vnode* dir_node, struct vnode** target, + const char* component_name); +}; + +struct fd_table { + int count; + struct file *fds[16]; +}; + +#define VFS_PATHMAX 256 + +#define O_CREAT 00000100 +#define SUCCESS 0 +#define FAIL -1 +#define EOF -1 + +extern struct mount *rootfs; + +void rootfs_init(); + +int register_fs(struct filesystem *fs); +int vfs_open(const char *pathname, int flags, struct file **target); +int vfs_close(struct file *file); +int vfs_write(struct file *file, const void *buf, unsigned len); +int vfs_read(struct file *file, void *buf, unsigned len); + +int vfs_mkdir(const char *pathname); +int vfs_mount(const char *target, const char *filesystem); +int vfs_lookup(const char *pathname, struct vnode **target); +int vfs_chdir(const char *pathname); + +void traverse(const char* pathname, struct vnode** target_node, char *target_path); +void r_traverse(struct vnode *node, const char *path, struct vnode **target_node, char *target_path); + +#endif \ No newline at end of file diff --git a/lab7/initramfs.cpio b/lab7/initramfs.cpio index e1b3477cedccc5f111bdbcd4215c1c3e7bad1e11..18985b13cb5e921deee575337d249e0d7a76fdf5 100644 GIT binary patch literal 404992 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 e8;i z_T^4@MSqL*qPlsaovbMX&}2f!_&;uPzyvX0f6<*}!al6?0JN&?;s?0rw6?vjG2v04U=B8$b4UC@|Bw^e zmLKEy{)`y9$iZVhD9{c+&OVNT`tYO+RyJS@QOxNSxwR2Q938q;Ll_F#R2ltTFkzJ` zwzD`3k%(&vxl&aot*(%T@^GM;1THd(E@7Y4rzT-htP#euanZF~E7CIV}}cZ@Sx13@_Q9=@Xv~B9 zYVqK$x8rv`lWtoDExfQOsqz*N<2slBGYJlS0gU0POe(Ikl8rV~!q z@5_rYXojQ5VOOIg^mp$|{S7}TF45GM1IEW5Yi2)od=bsVo(U|F)v;6{Sh*ma6MXCs zdX*AqI4z^!J@=u^+ghE)7QP|#V_tv_?~^-kV|H{f+>+{pIOYIz3RMpDEIt(5f&iEO zi@R^e!~WVXUDMc_Xk#CIbpO=xzCrUj{YaI?0@63DG;soXx_tq#!P+JRRZ z^KuUQ&(D#zW2;ybX#dpP|GlUGzMM#=M1!siJigI}a)`mh7wT2|y_*+v&hvdQ(|aPu z3;K#x#}D2+_HUwfZ%33i{iY8cbe;N$WKT?{`fiRnmW|NKzL>L(J-SchoTptHmb{&! zopqnN&|i?jXrs1}ecZO4**R!I-w@Z~>|po;84u|5yZsi{x5T;IrtR+SiJ_mwBei?@ ziZgu?pXeu3yd9MZ)VX$>uya=~^u}8~?um@#%qqikF|vCkEBAwbF~rGu4e~nrmrMt^ z?Z#61`H?6vxQp0Tk@ z9CUj$K8S(yav}1Fm%nD~@_z5o_y&c^cOux({=Ck5}(P-!ON=g;0N>9^`c{o^M1umV|lXa#FHo#BWhtCW`~4HD0TT-z5Bu z;=$mzV{w96#Hg_NLHOv2-$fbx0*Av4c&iI+0k|}+5oy_2i)xd@x=Wf{cX1e-x2ez0 z3JkvA@&VhSV(apO6Ra<)nUbb3Y*HiKPsX}?z|)8ZJDjviMq<^U#Ep%N^*9B@4gx{I z+Lz9t3coPc=7j!itkKyLOGc0~5py5q4Zf8IX&cK_Mo2>#53ey)Fhuo7J|m-o;5m)>v;B zi{#)rE$LHc@p^r&<*-n)B%nOg8lP3*&ze5&VN8X*sDU|J`9P(C@<8n^AF0+6vxwMP zTZ3$yz`&Y>)j~I$3NY~?;p6q-TB)|ZJYvw8pEfw|$9tQ=Y`yKm&afsM)|WHkR}JW5 zWFBqKd*QHB4Ajkj$C@F|lVW@HwNxbs5La8p=Q}k_lgXt^_}Ii{gcp%s?VvV{J<6J#�SRARRoaUB%scHk}sXt4`vBb(t9sf_VtGH;FPp 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%0D|p+m=B2Vqz1 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 H6oG#O><1&u diff --git a/lab7/kernel/kernel.c b/lab7/kernel/kernel.c index 490719083..e6b890d3f 100644 --- a/lab7/kernel/kernel.c +++ b/lab7/kernel/kernel.c @@ -6,6 +6,7 @@ #include "timer.h" #include "exception.h" #include "fork.h" +#include "vfs.h" void kernel_main(void) { @@ -14,6 +15,7 @@ void kernel_main(void) fdt_traverse(initramfs_callback); init_mm_reserve(); timer_init(); + rootfs_init(); enable_interrupt(); uart_send_string("OSDI 2022 Spring\n"); diff --git a/lab7/lib/shell.c b/lab7/lib/shell.c index 5569fafb3..2dacd410b 100644 --- a/lab7/lib/shell.c +++ b/lab7/lib/shell.c @@ -53,7 +53,7 @@ void start_video() { filesize = hexstr_to_uint(header->c_filesize, 8); - if (stringncmp(filename, "vm.img", namesize) == 0) { + if (stringncmp(filename, "vfs1.img", namesize) == 0) { code_loc = ((void*)header) + offset; break; } @@ -66,8 +66,8 @@ void start_video() { header = ((void*)header) + offset; } - printf("vm.img found in cpio at location 0x%x.\n", code_loc); - printf("vm.img has size of %d bytes.\n", (int)filesize); + printf("vfs1.img found in cpio at location 0x%x.\n", code_loc); + printf("vfs1.img has size of %d bytes.\n", (int)filesize); int err = move_to_user_mode((unsigned long)code_loc, filesize, 0); if (err<0) diff --git a/lab7/lib/string.c b/lab7/lib/string.c index df9ea89ca..f74819f4d 100644 --- a/lab7/lib/string.c +++ b/lab7/lib/string.c @@ -36,4 +36,20 @@ unsigned int strlen(const char *s) { return l; +} + +void strcpy(char *dest, const char *src) { + int i = 0; + while (src[i] != '\0') { + dest[i] = src[i]; + i++; + } + dest[i] = '\0'; +} + +void strncpy(char *dest, const char *src, int n) { + for (int i=0; iroot); + int res = vfs_open(pathname, flags, &f); + struct pt_regs *cur_regs = task_pt_regs(current); + + if (res == FAIL) { + cur_regs->regs[0] = -1; + return; + } + + int fd_num = current->files.count; + + current->files.fds[fd_num] = f; + current->files.count++; + cur_regs->regs[0] = fd_num; + printf("[debug] end of syscall open\n"); +} + +int sys_close(int fd) { + printf("[debug] start of syscall close\n"); + struct pt_regs *cur_regs = task_pt_regs(current); + if (fd < 0) { + cur_regs->regs[0] = -1; + return; + } + struct file *f = current->files.fds[fd]; + cur_regs->regs[0] = vfs_close(f); + current->files.fds[fd] = 0; + printf("[debug] end of syscall close\n"); +} + +long sys_write(int fd, const void *buf, unsigned long count) { + printf("[debug] start of syscall write\n"); + struct pt_regs *cur_regs = task_pt_regs(current); + if (fd < 0) { + cur_regs->regs[0] = -1; + return; + } + struct file *f = current->files.fds[fd]; + if (f == 0) { + cur_regs->regs[0] = 0; + return; + } + cur_regs->regs[0] = vfs_write(f, buf, count); + printf("[debug] end of syscall write\n"); +} + +long sys_read(int fd, void *buf, unsigned long count) { + printf("[debug] start of syscall read\n"); + struct pt_regs *cur_regs = task_pt_regs(current); + if (fd < 0) { + cur_regs->regs[0] = -1; + return; + } + struct file *f = current->files.fds[fd]; + if (f == 0) { + cur_regs->regs[0] = 0; + return; + } + cur_regs->regs[0] = vfs_read(f, buf, count); + printf("[debug] end of syscall read\n"); +} + +int sys_mkdir(const char *pathname, unsigned mode) { + printf("[debug] start of syscall mkdir\n"); + struct pt_regs *cur_regs = task_pt_regs(current); + cur_regs->regs[0] = vfs_mkdir(pathname); + printf("[debug] end of syscall mkdir\n"); +} + +int sys_mount(const char *src, const char *target, const char *fs, unsigned long flags, const void *data) { + printf("[debug] start of syscall mount\n"); + printf("[debug] end of syscall mount\n"); +} + +int sys_chdir(const char *path) { + printf("[debug] start of syscall chdir\n"); + struct pt_regs *cur_regs = task_pt_regs(current); + cur_regs->regs[0] = vfs_chdir(path); + printf("[debug] end of syscall chdir\n"); +} + void * const sys_call_table[] = { sys_getpid, @@ -143,5 +228,15 @@ void * const sys_call_table[] = sys_fork, sys_exit, sys_mbox_call, - sys_kill + sys_kill, + 0, + 0, + 0, + sys_open, + sys_close, + sys_write, + sys_read, + sys_mkdir, + sys_mount, + sys_chdir }; \ No newline at end of file diff --git a/lab7/lib/tmpfs.c b/lab7/lib/tmpfs.c new file mode 100644 index 000000000..234e3e598 --- /dev/null +++ b/lab7/lib/tmpfs.c @@ -0,0 +1,156 @@ +#include "tmpfs.h" +#include "mm.h" +#include "string.h" +#include "mini_uart.h" + + +struct vnode_operations *tmpfs_v_ops; +struct file_operations *tmpfs_f_ops; + +int tmpfs_setup_mount(struct filesystem* fs, struct mount* mount) { + + struct vnode *root_node = (struct vnode *)chunk_alloc(sizeof(struct vnode)); + struct tmpfs_internal *root_internal = (struct tmpfs_internal *)chunk_alloc(sizeof(struct tmpfs_internal)); + + root_internal->parent = root_internal; + root_internal->size = 0; + root_internal->type = DIRECTORY; + root_internal->vnode = root_node; + root_internal->data = 0; + + root_node->f_ops = tmpfs_f_ops; + root_node->v_ops = tmpfs_v_ops; + root_node->internal = (void *)root_internal; + root_node->mount = mount; + + mount->root = root_node; + mount->fs = fs; + + printf("[debug] setup root: 0x%x\n", mount); + printf("[debug] setup root vnode: 0x%x\n", root_node); + + return 0; +} + +int tmpfs_register(struct mount *mnt, struct vnode *root) { + + root->v_ops = (struct vnode_operations *) chunk_alloc(sizeof(struct vnode_operations)); + root->f_ops = (struct file_operations *) chunk_alloc(sizeof(struct file_operations)); + + root->v_ops->lookup = tmpfs_lookup; + root->v_ops->create = tmpfs_create; + root->v_ops->mkdir = tmpfs_mkdir; + + root->f_ops->open = tmpfs_open; + root->f_ops->read = tmpfs_read; + root->f_ops->write = tmpfs_write; + root->f_ops->close = tmpfs_close; + + return 0; +} + +int tmpfs_open(struct vnode* file_node, struct file** target) { + return SUCCESS; +} + +int tmpfs_close(struct file *file) { + if (file) + return SUCCESS; + else + return FAIL; +} + +int tmpfs_write(struct file *file, const void *buf, unsigned len) { + struct tmpfs_internal *internal = (struct tmpfs_internal*)file->vnode->internal; + if (((struct tmpfs_internal *)file->vnode->internal)->type != REGULAR_FILE) + return FAIL; + + char *dest = &((char *)internal->data)[file->f_pos]; + char *src = (char *)buf; + int i = 0; + for (; i < len && src[i] != '\0'; i++) { + dest[i] = src[i]; + } + dest[i] = EOF; + + return i; +} + +int tmpfs_read(struct file *file, void *buf, unsigned len) { + struct tmpfs_internal *internal = (struct tmpfs_internal*)file->vnode->internal; + if (internal->type != REGULAR_FILE) + return FAIL; + + char *dest = (char*)buf; + char *src = &((char *)internal->data)[file->f_pos]; + int i = 0; + for (; idata)+MAX_FILESIZE; i++) { + dest[i] = src[i]; + } + + return i; +} + +int tmpfs_create(struct vnode* dir_node, struct vnode** target, const char* component_name) { + struct tmpfs_internal *file_node = (struct tmpfs_internal *)chunk_alloc(sizeof(struct tmpfs_internal)); + struct vnode *new_node = (struct vnode *)chunk_alloc(sizeof(struct vnode)); + + strcpy(file_node->name, component_name); + file_node->type = REGULAR_FILE; + file_node->parent = (struct tmpfs_internal *)dir_node->internal; + file_node->vnode = new_node; + file_node->size = 0; + file_node->data = malloc(MAX_FILESIZE); + + file_node->vnode->f_ops = tmpfs_f_ops; + file_node->vnode->internal = (void *)file_node; + file_node->vnode->mount = 0; + file_node->vnode->v_ops = 0; + + struct tmpfs_internal *parent_internal = (struct tmpfs_internal *)dir_node->internal; + parent_internal->child[parent_internal->size] = file_node; + parent_internal->size++; + + *target = file_node->vnode; + return SUCCESS; +} + +int tmpfs_mkdir(struct vnode* dir_node, struct vnode** target, const char* component_name) { + struct tmpfs_internal *dir_internal = (struct tmpfs_internal *)chunk_alloc(sizeof(struct tmpfs_internal)); + struct vnode *new_node = (struct vnode *)chunk_alloc(sizeof(struct vnode)); + + strcpy(dir_internal->name, component_name); + dir_internal->type = DIRECTORY; + dir_internal->parent = (struct tmpfs_internal *)dir_node->internal; + dir_internal->vnode = new_node; + dir_internal->size = 0; + dir_internal->data = 0; + + dir_internal->vnode->f_ops = tmpfs_f_ops; + dir_internal->vnode->internal = (void *)dir_internal; + dir_internal->vnode->mount = 0; + dir_internal->vnode->v_ops = tmpfs_v_ops; + + struct tmpfs_internal *parent_internal = (struct tmpfs_internal *)dir_node->internal; + parent_internal->child[parent_internal->size] = dir_internal; + parent_internal->size++; + + *target = dir_internal->vnode; + return SUCCESS; +} + +int tmpfs_lookup(struct vnode* dir_node, struct vnode** target, const char* component_name) { + struct tmpfs_internal *internal = (struct tmpfs_internal *)dir_node->internal; + if (internal->type != DIRECTORY) { + return FAIL; + } + + for (int i=0; isize; i++) { + if(stringcmp(internal->child[i]->name, component_name) == 0) { + *target = internal->child[i]->vnode; + return i; + } + } + + return FAIL; +} diff --git a/lab7/lib/vfs.c b/lab7/lib/vfs.c new file mode 100644 index 000000000..ba581ea97 --- /dev/null +++ b/lab7/lib/vfs.c @@ -0,0 +1,146 @@ +#include "vfs.h" +#include "tmpfs.h" +#include "string.h" +#include "mm.h" +#include "mini_uart.h" + +struct mount *rootfs; + +void rootfs_init() { + + struct filesystem *tmpfs = (struct filesystem *)chunk_alloc(sizeof(struct filesystem)); + tmpfs->name = (char *)chunk_alloc(TMPFS_COMP_LEN); + strcpy(tmpfs->name, "tmpfs"); + tmpfs->setup_mount = tmpfs_setup_mount; + register_fs(tmpfs); + + rootfs = (struct mount *)chunk_alloc(sizeof(struct mount)); + tmpfs->setup_mount(tmpfs, rootfs); + + printf("[debug] confirm root vnode: 0x%x\n", rootfs->root); + +} + +int register_fs(struct filesystem *fs) { + if (stringcmp(fs->name, "tmpfs")) { + return tmpfs_register(); + } + return -1; +} + +int vfs_open(const char *pathname, int flags, struct file **target) { + printf("[debug] start of vfs_open\n"); + *target = 0; + struct vnode *target_dir; + char target_path[VFS_PATHMAX]; + traverse(pathname, &target_dir, target_path); + printf("[debug] traverse complete\n"); + struct vnode *target_file; + if (target_dir->v_ops->lookup(target_dir, &target_file, target_path) == 0) { + printf("[debug] vfs_open: open\n"); + *target = (struct file *)chunk_alloc(sizeof(struct file)); + (*target)->vnode = target_file; + (*target)->f_pos = 0; + (*target)->f_ops = target_file->f_ops; + (*target)->flags = flags; + return (*target)->f_ops->open(target_file, target); + } else if (flags & O_CREAT) { + printf("[debug] vfs_open: create and open\n"); + int res = target_dir->v_ops->create(target_dir, &target_file, target_path); + if (res < 0) return FAIL; + *target = (struct file *)chunk_alloc(sizeof(struct file)); + (*target)->vnode = target_file; + (*target)->f_pos = 0; + (*target)->f_ops = target_file->f_ops; + (*target)->flags = flags; + return (*target)->f_ops->open(target_file, target); + } else return FAIL; +} + +int vfs_close(struct file *file) { + int code = file->f_ops->close(file); + if (code == SUCCESS) + chunk_free(file); + return code; +} + +int vfs_write(struct file *file, const void *buf, unsigned len) { + return file->f_ops->write(file, buf, len); +} + +int vfs_read(struct file *file, void *buf, unsigned len) { + return file->f_ops->read(file, buf, len); +} + +int vfs_mkdir(const char *pathname) { + struct vnode *target_dir; + char child_name[VFS_PATHMAX]; + traverse(pathname, &target_dir, child_name); + struct vnode *child_dir; + int res = target_dir->v_ops->mkdir(target_dir, &child_dir, child_name); + if (res < 0) return res; + return SUCCESS; +} + +int vfs_mount(const char *target, const char *filesystem) { + return SUCCESS; +} + +int vfs_lookup(const char *pathname, struct vnode **target) { + return SUCCESS; // unused +} + +int vfs_chdir(const char *pathname) { + struct vnode *target_dir; + char path_remain[VFS_PATHMAX]; + traverse(pathname, &target_dir, path_remain); + if (stringcmp(path_remain, "") == 0) { + return FAIL; + } else { + current->cwd = target_dir; + return SUCCESS; + } +} + +void traverse(const char* pathname, struct vnode **target_node, char *target_path) { + if (pathname[0] == '/') { + printf("[debug] traverse absolute path: %s\n", pathname); + printf("[debug] root: 0x%x\n", rootfs); + printf("[debug] root vnode: 0x%x\n", rootfs->root); + struct vnode *rootnode = rootfs->root; + printf("[debug] here -1\n"); + r_traverse(rootnode, pathname + 1, target_node, target_path); + } else { + printf("[debug] traverse relative path\n"); + } +} + +void r_traverse(struct vnode *node, const char *path, struct vnode **target_node, char *target_path) { + printf("[debug] begin r_traverse\n"); + int i = 0; + while (path[i]) { + if (path[i] == '/') break; + target_path[i] = path[i]; + i++; + } + printf("[debug] here\n"); + target_path[i++] = '\0'; + *target_node = node; + + printf("[debug] r_traverse target path: %s\n", target_path); + + if (stringcmp(target_path, "") == 0) { + return; + } + else if (stringcmp(target_path, ".") == 0) { + + } + else if (stringcmp(target_path, "..") == 0) { + + } + + int res = node->v_ops->lookup(node, target_node, target_path); + if (res != FAIL) + r_traverse(*target_node, path+i, target_node, target_path); + +} diff --git a/lab7_novm/include/initramfs.h b/lab7_novm/include/initramfs.h new file mode 100644 index 000000000..80e9c9733 --- /dev/null +++ b/lab7_novm/include/initramfs.h @@ -0,0 +1,35 @@ +#ifndef _INITRAMFS_H +#define _INITRAMFS_H + +#include "vfs.h" + +#define INITRAMFS_COMP_LEN 16 +#define MAX_DIRENT 16 +#define MAX_FILESIZE 0x1000 + +struct initramfs_internal { + char name[INITRAMFS_COMP_LEN]; + int type; + struct initramfs_internal *parent; + struct initramfs_internal *child[MAX_DIRENT]; + struct vnode *vnode; + int size; // use for child count and data size + void *data; +}; + +int initramfs_setup_mount(struct filesystem* fs, struct mount* mount); +int initramfs_register(); + +struct vnode* initramfs_new_node(struct initramfs_internal *parent, const char *name, int type); + +int initramfs_open(struct vnode *file_node, struct file **target); +int initramfs_close(struct file *file); +int initramfs_write(struct file *file, const void *buf, unsigned len); +int initramfs_read(struct file *file, void *buf, unsigned len); +int initramfs_create(struct vnode *dir_node, struct vnode **target, const char *component_name); +int initramfs_mkdir(struct vnode *dir_node, struct vnode **target, const char *component_name); +int initramfs_lookup(struct vnode *dir_node, struct vnode **target, const char *component_name); + +void parse_initramfs(); + +#endif \ No newline at end of file diff --git a/lab7_novm/include/sched.h b/lab7_novm/include/sched.h index a05e6ae25..d36d6e8ac 100644 --- a/lab7_novm/include/sched.h +++ b/lab7_novm/include/sched.h @@ -5,6 +5,8 @@ #ifndef __ASSEMBLER__ +#include "vfs.h" + #define THREAD_SIZE 4096 #define NR_TASKS 64 @@ -49,6 +51,8 @@ struct task_struct { unsigned long stack; unsigned long flags; long id; + struct fd_table files; + struct vnode *cwd; }; extern void sched_init(); @@ -64,7 +68,9 @@ extern void kill_zombies(); #define INIT_TASK \ { \ {0,0,0,0,0,0,0,0,0,0,0,0,0},\ -0, 0, 1, 0, 0, PF_KTHREAD, 0 \ +0, 0, 1, 0, 0, PF_KTHREAD, 0, \ +{0, {0}},\ +0 \ } #endif diff --git a/lab7_novm/include/string.h b/lab7_novm/include/string.h index abf24c93f..c71840033 100644 --- a/lab7_novm/include/string.h +++ b/lab7_novm/include/string.h @@ -4,5 +4,7 @@ int stringcmp (const char *, const char *); int stringncmp (const char *, const char *, unsigned int); unsigned int strlen(const char *); +void strcpy(char *dest, const char *src); +void strncpy(char *dest, const char *src, int n); #endif \ No newline at end of file diff --git a/lab7_novm/include/syscall.h b/lab7_novm/include/syscall.h index b738fcff9..429be1390 100644 --- a/lab7_novm/include/syscall.h +++ b/lab7_novm/include/syscall.h @@ -1,7 +1,7 @@ #ifndef _SYSCALL_H #define _SYSCALL_H -#define __NR_SYSCALLS 8 +#define __NR_SYSCALLS 18 #define SYS_GETPID_NUM 0 #define SYS_UARTREAD_NUM 1 @@ -12,8 +12,32 @@ #define SYS_MBOXCALL_NUM 6 #define SYS_KILL_NUM 7 +#define SYS_OPEN_NUM 11 +#define SYS_CLOSE_NUM 12 +#define SYS_WRITE_NUM 13 +#define SYS_READ_NUM 14 +#define SYS_MKDIR_NUM 15 +#define SYS_MOUNT_NUM 16 +#define SYS_CHDIR_NUM 17 + #ifndef __ASSEMBLER__ +int getpid(); +unsigned uart_read(char buf[], unsigned size); +unsigned uart_write(const char buf[], unsigned size); +int exec(const char *name, char *const argv[]); +int fork(); +void exit(int status); +int mbox_call(unsigned char ch, volatile unsigned int *mbox); +void kill(int pid); +int open(const char *pathname, int flags); +int close(int fd); +long write(int fd, const void *buf, unsigned long count); +long read(int fd, void *buf, unsigned long count); +int mkdir(const char *pathname, unsigned mode); +int mount(const char *src, const char *target, const char *fs, unsigned long flags, const void *data); +int chdir(const char *path); + int sys_getpid(); unsigned sys_uartread(char buf[], unsigned size); unsigned sys_uartwrite(const char buf[], unsigned size); @@ -22,6 +46,13 @@ int sys_fork(); void sys_exit(int status); int sys_mbox_call(unsigned char ch, unsigned int *mbox); void sys_kill(int pid); +int sys_open(const char *pathname, int flags); +int sys_close(int fd); +long sys_write(int fd, const void *buf, unsigned long count); +long sys_read(int fd, void *buf, unsigned long count); +int sys_mkdir(const char *pathname, unsigned mode); +int sys_mount(const char *src, const char *target, const char *fs, unsigned long flags, const void *data); +int sys_chdir(const char *path); #endif diff --git a/lab7_novm/include/tmpfs.h b/lab7_novm/include/tmpfs.h new file mode 100644 index 000000000..f01824281 --- /dev/null +++ b/lab7_novm/include/tmpfs.h @@ -0,0 +1,33 @@ +#ifndef _TMPFS_H +#define _TMPFS_H + +#include "vfs.h" + +#define TMPFS_COMP_LEN 16 +#define MAX_DIRENT 16 +#define MAX_FILESIZE 0x1000 + +struct tmpfs_internal { + char name[TMPFS_COMP_LEN]; + int type; + struct tmpfs_internal *parent; + struct tmpfs_internal *child[MAX_DIRENT]; + struct vnode *vnode; + int size; // use for child count and data size + void *data; +}; + +int tmpfs_setup_mount(); +int tmpfs_register(); + +struct vnode* tmpfs_new_node(struct tmpfs_internal *parent, const char *name, int type); + +int tmpfs_open(struct vnode *file_node, struct file **target); +int tmpfs_close(struct file *file); +int tmpfs_write(struct file *file, const void *buf, unsigned len); +int tmpfs_read(struct file *file, void *buf, unsigned len); +int tmpfs_create(struct vnode *dir_node, struct vnode **target, const char *component_name); +int tmpfs_mkdir(struct vnode *dir_node, struct vnode **target, const char *component_name); +int tmpfs_lookup(struct vnode *dir_node, struct vnode **target, const char *component_name); + +#endif \ No newline at end of file diff --git a/lab7_novm/include/vfs.h b/lab7_novm/include/vfs.h new file mode 100644 index 000000000..d6b59781a --- /dev/null +++ b/lab7_novm/include/vfs.h @@ -0,0 +1,81 @@ +#ifndef _VFS_H +#define _VFS_H + +struct vnode { + struct vnode* parent; + struct mount* mount; + struct vnode_operations* v_ops; + struct file_operations* f_ops; + void* internal; +}; + +// file handle +struct file { + struct vnode* vnode; + unsigned f_pos; // RW position of this file handle + struct file_operations* f_ops; + int flags; +}; + +struct mount { + struct vnode* root; + struct filesystem* fs; +}; + +struct filesystem { + char* name; + int (*setup_mount)(struct filesystem* fs, struct mount* mount); +}; + +struct file_operations { + int (*write)(struct file* file, const void* buf, unsigned len); + int (*read)(struct file* file, void* buf, unsigned len); + int (*open)(struct vnode* file_node, struct file** target); + int (*close)(struct file* file); +}; + +struct vnode_operations { + int (*lookup)(struct vnode* dir_node, struct vnode** target, + const char* component_name); + int (*create)(struct vnode* dir_node, struct vnode** target, + const char* component_name); + int (*mkdir)(struct vnode* dir_node, struct vnode** target, + const char* component_name); +}; + +struct fd_table { + int count; + struct file *fds[16]; +}; + +#define VFS_PATHMAX 256 + +#define REGULAR_FILE -2 +#define DIRECTORY -3 + +#define O_CREAT 00000100 +#define SUCCESS 0 +#define FAIL -1 +#define EOF -1 + +extern struct mount *rootfs; + +void rootfs_init(); +void initramfs_init(); + +int register_fs(struct filesystem *fs); +struct file* create_fd(struct vnode* target, int flags); +int vfs_open(const char *pathname, int flags, struct file **target); +int vfs_close(struct file *file); +int vfs_write(struct file *file, const void *buf, unsigned len); +int vfs_read(struct file *file, void *buf, unsigned len); + +int vfs_mkdir(const char *pathname); +int vfs_mount(const char *target, const char *filesystem); +int vfs_lookup(const char *pathname, struct vnode **target); +int vfs_chdir(const char *pathname); + +void traverse(const char* pathname, struct vnode** target_node, char *target_path); +void r_traverse(struct vnode *node, const char *path, struct vnode **target_node, char *target_path); + +#endif \ No newline at end of file diff --git a/lab7_novm/initramfs.cpio b/lab7_novm/initramfs.cpio index 18985b13cb5e921deee575337d249e0d7a76fdf5..6fcebfdf9878463fe2435811037012b3c1d75bc5 100644 GIT binary patch delta 128 zcmZp;Bhhe2g3rJl2n`Jkj4cez&DCtsmFzbGd~kqgL2Hq_j(v97g_v9*q=wT`*9j-|DZb!#2l GKOF!j(IM^t delta 88 zcmZp;Bhhe2g3rJl2n`JkjLi(pT^uL!Df5_{n;4lHK)_@}MvaN7!fXa6hOTBt6YG?l fRTstack = stack; } + p->files = current->files; + p->cwd = current->cwd; p->flags = clone_flags; p->priority = current->priority; p->state = TASK_RUNNING; diff --git a/lab7_novm/lib/initramfs.c b/lab7_novm/lib/initramfs.c new file mode 100644 index 000000000..6d6953500 --- /dev/null +++ b/lab7_novm/lib/initramfs.c @@ -0,0 +1,211 @@ +#include "initramfs.h" +#include "mm.h" +#include "string.h" +#include "mini_uart.h" +#include "vfs.h" +#include "string.h" +#include "../include/cpio.h" + +struct vnode_operations *initramfs_v_ops; +struct file_operations *initramfs_f_ops; +int initramfs_registered = 0; + +int initramfs_setup_mount(struct filesystem* fs, struct mount* mount) { + + mount->fs = fs; + mount->root = initramfs_new_node(NULL, "/", DIRECTORY); + + printf("[debug] setup root: 0x%x\n", mount); + printf("[debug] setup root vnode: 0x%x\n", mount->root); + + return 0; + +} + +int initramfs_register() { + + if (initramfs_registered) return -1; + initramfs_registered = 1; + + initramfs_v_ops = (struct vnode_operations *) chunk_alloc(sizeof(struct vnode_operations)); + initramfs_f_ops = (struct file_operations *) chunk_alloc(sizeof(struct file_operations)); + + initramfs_v_ops->lookup = initramfs_lookup; + initramfs_v_ops->create = initramfs_create; + initramfs_v_ops->mkdir = initramfs_mkdir; + + initramfs_f_ops->open = initramfs_open; + initramfs_f_ops->read = initramfs_read; + initramfs_f_ops->write = initramfs_write; + initramfs_f_ops->close = initramfs_close; + + return 0; + +} + +struct vnode* initramfs_new_node(struct initramfs_internal *parent, const char *name, int type) { + + struct vnode *new_node = (struct vnode *)chunk_alloc(sizeof(struct vnode)); + struct initramfs_internal *new_internal = (struct initramfs_internal *)chunk_alloc(sizeof(struct initramfs_internal)); + + strcpy(new_internal->name, name); + new_internal->type = type; + new_internal->parent = parent; + new_internal->vnode = new_node; + new_internal->size = 0; + if (type == REGULAR_FILE) + new_internal->data = malloc(MAX_FILESIZE); + else + new_internal->data = 0; + + if (parent != NULL) + new_node->parent = parent->vnode; + new_node->f_ops = initramfs_f_ops; + new_node->v_ops = initramfs_v_ops; + new_node->mount = 0; + new_node->internal = (void *)new_internal; + + return new_node; + +} + +int initramfs_open(struct vnode *file_node, struct file **target) { + return SUCCESS; +} + +int initramfs_close(struct file *file) { + if (file) + return SUCCESS; + else + return FAIL; +} + +int initramfs_write(struct file *file, const void *buf, unsigned len) { + return FAIL; +} + +int initramfs_read(struct file *file, void *buf, unsigned len) { + + struct initramfs_internal *internal = (struct initramfs_internal*)file->vnode->internal; + if (internal->type != REGULAR_FILE) + return FAIL; + + char *dest = (char*)buf; + char *src = &((char *)internal->data)[file->f_pos]; + int i = 0; + for (; isize; i++) { + dest[i] = src[i]; + } + + return i; +} + +int initramfs_create(struct vnode *dir_node, struct vnode **target, const char *component_name) { + return FAIL; +} + +int initramfs_mkdir(struct vnode *dir_node, struct vnode **target, const char *component_name) { + return FAIL; +} + +int initramfs_lookup(struct vnode *dir_node, struct vnode **target, const char *component_name) { + + if (stringcmp(component_name, "") == 0) { + *target = dir_node; + return 0; + } + + struct initramfs_internal *internal = (struct initramfs_internal *)dir_node->internal; + + for (int i=0; isize; i++) { + if(stringcmp(internal->child[i]->name, component_name) == 0) { + *target = internal->child[i]->vnode; + return internal->child[i]->type; + } + } + + return FAIL; + +} + +void parse_initramfs() { + + vfs_chdir("/initramfs"); + + struct cpio_newc_header *header; + unsigned int filesize; + unsigned int namesize; + unsigned int offset; + unsigned int c_mode; + void *data; + char *filename; + + header = DEVTREE_CPIO_BASE; + + while (1) { + + struct vnode *target_node; + char target_path[VFS_PATHMAX]; + + filename = ((void*)header) + sizeof(struct cpio_newc_header); + + if (stringncmp((char*)header, CPIO_HEADER_MAGIC, 6) != 0) { + uart_send_string("invalid magic\n"); + break; + } + if (stringncmp(filename, CPIO_FOOTER_MAGIC, 11) == 0) break; + + //uart_send_string(filename); + //uart_send('\n'); + + namesize = hexstr_to_uint(header->c_namesize, 8); + filesize = hexstr_to_uint(header->c_filesize, 8); + c_mode = hexstr_to_uint(header->c_mode, 5); + offset = sizeof(struct cpio_newc_header) + namesize; + if (offset % 4 != 0) + offset = ((offset/4) + 1) * 4; + data = ((void *)header) + offset; + + if (stringncmp(header->c_mode, "00004", 5) == 0) { //dir + + printf("[debug] parse cpio mkdir %s\n", filename); + if (stringcmp(filename, ".") != 0 && stringcmp(filename, "..") != 0) { + traverse(filename, &target_node, target_path); + struct initramfs_internal *parent_internal = (struct initramfs_internal *)target_node->internal; + struct vnode *new_node = initramfs_new_node(parent_internal, target_path, DIRECTORY); + + parent_internal->child[parent_internal->size] = (struct initramfs_internal *)new_node->internal; + parent_internal->size++; + } else { + printf("[debug] mkdir skipped\n"); + } + + } else if (stringncmp(header->c_mode, "00008", 5) == 0) { //reg file + + printf("[debug] parse cpio create file %s, data at 0x%x with size 0x%d\n", filename, data, filesize); + traverse(filename, &target_node, target_path); + struct initramfs_internal *parent_internal = (struct initramfs_internal *)target_node->internal; + struct vnode *new_node = initramfs_new_node(parent_internal, target_path, REGULAR_FILE); + + parent_internal->child[parent_internal->size] = (struct initramfs_internal *)new_node->internal; + parent_internal->size++; + + ((struct initramfs_internal *)new_node->internal)->data = data; + ((struct initramfs_internal *)new_node->internal)->size = filesize; + + } else { + printf("[error] failed to parse cmode %d\n", c_mode); + } + + if (filesize % 4 != 0) + filesize = ((filesize/4) + 1) * 4; + + offset = offset + filesize; + + header = ((void*)header) + offset; + + } + + vfs_chdir("/"); + +} diff --git a/lab7_novm/lib/shell.c b/lab7_novm/lib/shell.c index 75a630b9e..5adf9634c 100644 --- a/lab7_novm/lib/shell.c +++ b/lab7_novm/lib/shell.c @@ -19,7 +19,6 @@ #define MAX_BUFFER_SIZE 256u static char buffer[MAX_BUFFER_SIZE]; -static int shared = 1; void foo() { for(int i = 0; i < 10; ++i) { @@ -35,40 +34,18 @@ void foo() { void user_foo() { printf("User thread id: %d\n", getpid()); + char *msg = "hello world\n"; + int fd; + char buf[15]; + buf[14] = '\0'; - volatile unsigned int __attribute__((aligned(16))) mailbox[7]; - mailbox[0] = 7 * 4; - mailbox[1] = REQUEST_CODE; - mailbox[2] = GET_BOARD_REVISION; - mailbox[3] = 4; - mailbox[4] = TAG_REQUEST_CODE; - mailbox[5] = 0; - mailbox[6] = END_TAG; - mbox_call(0x8, mailbox); - printf("Board Revision:\t\t%x\n", mailbox[5]); - - int pid = fork(); - if (pid == 0) { - printf("Child says hello!\n"); - while(1) { - printf("Please don't kill me :(\n"); - shared++; - } - } else if (pid > 0) { - printf("Parent says, \"My child has pid %d\"\n", pid); - printf("Shared? %d\n", shared); - delay(10000000); - printf("Kill my own child :(\n"); - kill(pid); - delay(10000000); - printf("shared %d\n", shared); - } + fd = open("/initramfs/msg", 0); + read(fd, buf, 13); + close(fd); - //char buf[4] = {0}; - //uart_read(buf, 3); - //uart_write(buf, 3); + printf("%s", buf); - exit(); + exit(0); } @@ -109,7 +86,7 @@ void start_video() { filesize = hexstr_to_uint(header->c_filesize, 8); - if (stringncmp(filename, "syscall.img", namesize) == 0) { + if (stringncmp(filename, "vfs1.img", namesize) == 0) { code_loc = ((void*)header) + offset; break; } @@ -122,8 +99,8 @@ void start_video() { header = ((void*)header) + offset; } - printf("syscall.img found in cpio at location 0x%x.\n", code_loc); - printf("syscall.img has size of %d bytes.\n", (int)filesize); + printf("vfs1.img found in cpio at location 0x%x.\n", code_loc); + printf("vfs1.img has size of %d bytes.\n", (int)filesize); void *move_loc = malloc(filesize + 4096); // an extra page for bss just in case if(move_loc == NULL) return; diff --git a/lab7_novm/lib/string.c b/lab7_novm/lib/string.c index df9ea89ca..f74819f4d 100644 --- a/lab7_novm/lib/string.c +++ b/lab7_novm/lib/string.c @@ -36,4 +36,20 @@ unsigned int strlen(const char *s) { return l; +} + +void strcpy(char *dest, const char *src) { + int i = 0; + while (src[i] != '\0') { + dest[i] = src[i]; + i++; + } + dest[i] = '\0'; +} + +void strncpy(char *dest, const char *src, int n) { + for (int i=0; ifiles.count; + + if (fd_num >= 16) { + for (int i=0; i<16; i++) { + if (current->files.fds[i] == 0) fd_num = i; + } + } + + if (fd_num >= 16) return -1; + + current->files.fds[fd_num] = f; + current->files.count++; + + return fd_num; +} + +int sys_close(int fd) { + printf("[debug] start of syscall close with fd %d\n", fd); + if (fd < 0) return -1; + + struct file *f = current->files.fds[fd]; + current->files.fds[fd] = 0; + + return vfs_close(f); +} + +long sys_write(int fd, const void *buf, unsigned long count) { + printf("[debug] start of syscall write with fd %d\n", fd); + + if (fd < 0) return -1; + + struct file *f = current->files.fds[fd]; + if (f == 0) return 0; + + return vfs_write(f, buf, count); +} + +long sys_read(int fd, void *buf, unsigned long count) { + printf("[debug] start of syscall read with fd %d\n", fd); + + if (fd < 0) return -1; + + struct file *f = current->files.fds[fd]; + if (f == 0) return 0; + + return vfs_read(f, buf, count); +} + +int sys_mkdir(const char *pathname, unsigned mode) { + printf("[debug] start of syscall mkdir\n"); + + return vfs_mkdir(pathname); +} + +int sys_mount(const char *src, const char *target, const char *fs, unsigned long flags, const void *data) { + printf("[debug] start of syscall mount\n"); + return vfs_mount(target, fs); +} + +int sys_chdir(const char *path) { + printf("[debug] start of syscall chdir: %s\n", path); + + return vfs_chdir(path); +} + void * const sys_call_table[] = { sys_getpid, @@ -139,5 +210,15 @@ void * const sys_call_table[] = sys_fork, sys_exit, sys_mbox_call, - sys_kill + sys_kill, + 0, + 0, + 0, + sys_open, + sys_close, + sys_write, + sys_read, + sys_mkdir, + sys_mount, + sys_chdir }; \ No newline at end of file diff --git a/lab7_novm/lib/tmpfs.c b/lab7_novm/lib/tmpfs.c new file mode 100644 index 000000000..b1f51196e --- /dev/null +++ b/lab7_novm/lib/tmpfs.c @@ -0,0 +1,154 @@ +#include "tmpfs.h" +#include "mm.h" +#include "string.h" +#include "mini_uart.h" + + +struct vnode_operations *tmpfs_v_ops; +struct file_operations *tmpfs_f_ops; +int tmpfs_registered = 0; + +int tmpfs_setup_mount(struct filesystem* fs, struct mount* mount) { + + mount->fs = fs; + mount->root = tmpfs_new_node(NULL, "/", DIRECTORY); + + printf("[debug] setup root: 0x%x\n", mount); + printf("[debug] setup root vnode: 0x%x\n", mount->root); + + return 0; +} + +int tmpfs_register() { + + if (tmpfs_registered) return -1; + tmpfs_registered = 1; + + tmpfs_v_ops = (struct vnode_operations *) chunk_alloc(sizeof(struct vnode_operations)); + tmpfs_f_ops = (struct file_operations *) chunk_alloc(sizeof(struct file_operations)); + + tmpfs_v_ops->lookup = tmpfs_lookup; + tmpfs_v_ops->create = tmpfs_create; + tmpfs_v_ops->mkdir = tmpfs_mkdir; + + tmpfs_f_ops->open = tmpfs_open; + tmpfs_f_ops->read = tmpfs_read; + tmpfs_f_ops->write = tmpfs_write; + tmpfs_f_ops->close = tmpfs_close; + + return 0; +} + +struct vnode* tmpfs_new_node(struct tmpfs_internal *parent, const char *name, int type) { + + struct vnode *new_node = (struct vnode *)chunk_alloc(sizeof(struct vnode)); + struct tmpfs_internal *new_internal = (struct tmpfs_internal *)chunk_alloc(sizeof(struct tmpfs_internal)); + + strcpy(new_internal->name, name); + new_internal->type = type; + new_internal->parent = parent; + new_internal->vnode = new_node; + new_internal->size = 0; + if (type == REGULAR_FILE) + new_internal->data = malloc(MAX_FILESIZE); + else + new_internal->data = 0; + + if (parent != NULL) + new_node->parent = parent->vnode; + new_node->f_ops = tmpfs_f_ops; + new_node->v_ops = tmpfs_v_ops; + new_node->mount = 0; + new_node->internal = (void *)new_internal; + + return new_node; + +} + +int tmpfs_open(struct vnode* file_node, struct file** target) { + return SUCCESS; +} + +int tmpfs_close(struct file *file) { + if (file) + return SUCCESS; + else + return FAIL; +} + +int tmpfs_write(struct file *file, const void *buf, unsigned len) { + struct tmpfs_internal *internal = (struct tmpfs_internal*)file->vnode->internal; + if (((struct tmpfs_internal *)file->vnode->internal)->type != REGULAR_FILE) + return FAIL; + + char *dest = &((char *)internal->data)[file->f_pos]; + char *src = (char *)buf; + int i = 0; + for (; i < len && internal->size+i < MAX_FILESIZE; i++) { + dest[i] = src[i]; + } + + internal->size += i; + + return i; +} + +int tmpfs_read(struct file *file, void *buf, unsigned len) { + + struct tmpfs_internal *internal = (struct tmpfs_internal*)file->vnode->internal; + if (internal->type != REGULAR_FILE) + return FAIL; + + char *dest = (char*)buf; + char *src = &((char *)internal->data)[file->f_pos]; + int i = 0; + for (; isize; i++) { + dest[i] = src[i]; + } + + return i; +} + +int tmpfs_create(struct vnode* dir_node, struct vnode** target, const char* component_name) { + + struct tmpfs_internal *parent_internal = (struct tmpfs_internal *)dir_node->internal; + struct vnode *new_node = tmpfs_new_node(parent_internal, component_name, REGULAR_FILE); + + parent_internal->child[parent_internal->size] = (struct tmpfs_internal *)new_node->internal; + parent_internal->size++; + + *target = new_node; + return SUCCESS; +} + +int tmpfs_mkdir(struct vnode* dir_node, struct vnode** target, const char* component_name) { + + struct tmpfs_internal *parent_internal = (struct tmpfs_internal *)dir_node->internal; + struct vnode *new_node = tmpfs_new_node(parent_internal, component_name, DIRECTORY); + + parent_internal->child[parent_internal->size] = (struct tmpfs_internal *)new_node->internal; + parent_internal->size++; + + *target = new_node; + return SUCCESS; +} + +int tmpfs_lookup(struct vnode* dir_node, struct vnode** target, const char* component_name) { + + if (stringcmp(component_name, "") == 0) { + *target = dir_node; + return 0; + } + + struct tmpfs_internal *internal = (struct tmpfs_internal *)dir_node->internal; + + for (int i=0; isize; i++) { + if(stringcmp(internal->child[i]->name, component_name) == 0) { + *target = internal->child[i]->vnode; + return internal->child[i]->type; + } + } + + return FAIL; + +} diff --git a/lab7_novm/lib/vfs.c b/lab7_novm/lib/vfs.c new file mode 100644 index 000000000..0492dfc26 --- /dev/null +++ b/lab7_novm/lib/vfs.c @@ -0,0 +1,197 @@ +#include "vfs.h" +#include "tmpfs.h" +#include "initramfs.h" +#include "string.h" +#include "mm.h" +#include "sched.h" +#include "mini_uart.h" + +struct mount *rootfs; + +void rootfs_init() { + + struct filesystem *tmpfs = (struct filesystem *)chunk_alloc(sizeof(struct filesystem)); + tmpfs->name = (char *)chunk_alloc(16); + strcpy(tmpfs->name, "tmpfs"); + tmpfs->setup_mount = tmpfs_setup_mount; + register_fs(tmpfs); + + rootfs = (struct mount *)chunk_alloc(sizeof(struct mount)); + tmpfs->setup_mount(tmpfs, rootfs); + +} + +void initramfs_init() { + + vfs_mkdir("/initramfs"); + vfs_mount("/initramfs", "initramfs"); + parse_initramfs(); + +} + +int register_fs(struct filesystem *fs) { + if (stringcmp(fs->name, "tmpfs") == 0) { + return tmpfs_register(); + } else if (stringcmp(fs->name, "initramfs") == 0) { + return initramfs_register(); + } + return -1; +} + +struct file* create_fd(struct vnode* target, int flags) { + struct file* fd = (struct file*)chunk_alloc(sizeof(struct file)); + fd->f_ops = target->f_ops; + fd->vnode = target; + fd->f_pos = 0; + fd->flags = flags; + return fd; +} + +int vfs_open(const char *pathname, int flags, struct file **target) { + + *target = 0; + struct vnode *target_dir; + char target_path[VFS_PATHMAX]; + traverse(pathname, &target_dir, target_path); + + struct vnode *target_file; + if (target_dir->v_ops->lookup(target_dir, &target_file, target_path) == REGULAR_FILE) { + + *target = create_fd(target_file, flags); + return (*target)->f_ops->open(target_file, target); + + } else if (flags & O_CREAT) { + + int res = target_dir->v_ops->create(target_dir, &target_file, target_path); + if (res < 0) return FAIL; + *target = create_fd(target_file, flags); + return (*target)->f_ops->open(target_file, target); + + } else return FAIL; + +} + +int vfs_close(struct file *file) { + int code = file->f_ops->close(file); + if (code == SUCCESS) + chunk_free(file); + return code; +} + +int vfs_write(struct file *file, const void *buf, unsigned len) { + return file->f_ops->write(file, buf, len); +} + +int vfs_read(struct file *file, void *buf, unsigned len) { + return file->f_ops->read(file, buf, len); +} + +int vfs_mkdir(const char *pathname) { + struct vnode *target_dir; + char child_name[VFS_PATHMAX]; + traverse(pathname, &target_dir, child_name); + struct vnode *child_dir; + int res = target_dir->v_ops->mkdir(target_dir, &child_dir, child_name); + if (res < 0) return res; + return SUCCESS; +} + +int vfs_mount(const char *target, const char *filesystem) { + + struct vnode *mount_dir; + char path_remain[VFS_PATHMAX]; + + traverse(target, &mount_dir, path_remain); + + struct mount *mt = (struct mount *)chunk_alloc(sizeof(struct mount)); + if (stringcmp(filesystem, "tmpfs") == 0) { + + struct filesystem *tmpfs = (struct filesystem *)chunk_alloc(sizeof(struct filesystem)); + tmpfs->name = (char *)chunk_alloc(16); + strcpy(tmpfs->name, "tmpfs"); + tmpfs->setup_mount = tmpfs_setup_mount; + register_fs(tmpfs); + tmpfs->setup_mount(tmpfs, mt); + mount_dir->mount = mt; + mt->root->parent = mount_dir->parent; + + } else if (stringcmp(filesystem, "initramfs") == 0) { + + struct filesystem *initramfs = (struct filesystem *)chunk_alloc(sizeof(struct filesystem)); + initramfs->name = (char *)chunk_alloc(16); + strcpy(initramfs->name, "initramfs"); + initramfs->setup_mount = initramfs_setup_mount; + register_fs(initramfs); + initramfs->setup_mount(initramfs, mt); + mount_dir->mount = mt; + mt->root->parent = mount_dir->parent; + + } + + return SUCCESS; +} + +int vfs_lookup(const char *pathname, struct vnode **target) { + return SUCCESS; +} + +int vfs_chdir(const char *pathname) { + struct vnode *target_dir; + char path_remain[VFS_PATHMAX]; + traverse(pathname, &target_dir, path_remain); + if (stringcmp(path_remain, "") != 0) { + return FAIL; + } else { + + current->cwd = target_dir; + return SUCCESS; + } +} + +void traverse(const char* pathname, struct vnode **target_node, char *target_path) { + if (pathname[0] == '/') { + printf("[debug] traverse absolute path: %s\n", pathname); + struct vnode *rootnode = rootfs->root; + r_traverse(rootnode, pathname + 1, target_node, target_path); + } else { + printf("[debug] traverse relative path: %s\n", pathname); + struct vnode *rootnode = current->cwd; + r_traverse(rootnode, pathname, target_node, target_path); + } +} + +void r_traverse(struct vnode *node, const char *path, struct vnode **target_node, char *target_path) { + + int i = 0; + while (path[i]) { + if (path[i] == '/') break; + target_path[i] = path[i]; + i++; + } + target_path[i++] = '\0'; + *target_node = node; + + if (stringcmp(target_path, "") == 0) { + return; + } + else if (stringcmp(target_path, ".") == 0) { + r_traverse(node, path + i, target_node, target_path); + return; + } + else if (stringcmp(target_path, "..") == 0) { + if (node->parent == NULL) return; + r_traverse(node->parent, path + i, target_node, target_path); + return; + } + + int res = node->v_ops->lookup(node, target_node, target_path); + if ((*target_node)->mount != NULL) { + printf("[debug] mountpoint found during lookup: vnode 0x%x\n", (*target_node)->mount->root); + r_traverse((*target_node)->mount->root, path+i, target_node, target_path); + } + else if (res == DIRECTORY) + r_traverse(*target_node, path+i, target_node, target_path); + else if (res == REGULAR_FILE) + *target_node = node; + +} diff --git a/lab7_novm/rootfs/msg b/lab7_novm/rootfs/msg new file mode 100644 index 000000000..a04238969 --- /dev/null +++ b/lab7_novm/rootfs/msg @@ -0,0 +1 @@ +hello world! From 9323c11743d00090b8137bdc9020691f5b4d758f Mon Sep 17 00:00:00 2001 From: Hsiang-Chieh Tsou Date: Tue, 14 Jun 2022 14:46:55 +0800 Subject: [PATCH 8/9] lab8 init --- lab8/FAT_R.TXT | 1 + lab8/bcm2710-rpi-3-b-plus.dtb | Bin 0 -> 31790 bytes lab8/config.txt | 3 + lab8/include/cpio.h | 49 ++++ lab8/include/devtree.h | 64 +++++ lab8/include/entry.h | 39 +++ lab8/include/exception.h | 7 + lab8/include/fork.h | 26 ++ lab8/include/initramfs.h | 35 +++ lab8/include/mailbox.h | 7 + lab8/include/math.h | 7 + lab8/include/memory.h | 8 + lab8/include/mini_uart.h | 10 + lab8/include/mm.h | 55 ++++ lab8/include/peripherals/base.h | 6 + lab8/include/peripherals/exception.h | 25 ++ lab8/include/peripherals/gpio.h | 12 + lab8/include/peripherals/mailbox.h | 23 ++ lab8/include/peripherals/mini_uart.h | 19 ++ lab8/include/printf.h | 31 +++ lab8/include/reboot.h | 7 + lab8/include/sched.h | 78 ++++++ lab8/include/shell.h | 6 + lab8/include/string.h | 10 + lab8/include/syscall.h | 59 +++++ lab8/include/timer.h | 14 + lab8/include/tmpfs.h | 33 +++ lab8/include/utils.h | 8 + lab8/include/vfs.h | 81 ++++++ lab8/initramfs.cpio | Bin 0 -> 404992 bytes lab8/kernel/Makefile | 49 ++++ lab8/kernel/boot_kernel.S | 41 +++ lab8/kernel/kernel.c | 29 +++ lab8/kernel/linker.ld | 21 ++ lab8/lib/Makefile | 29 +++ lab8/lib/cpio.c | 232 +++++++++++++++++ lab8/lib/devtree.c | 92 +++++++ lab8/lib/entry.S | 195 ++++++++++++++ lab8/lib/exception.c | 49 ++++ lab8/lib/fork.c | 85 ++++++ lab8/lib/initramfs.c | 211 +++++++++++++++ lab8/lib/mailbox.c | 49 ++++ lab8/lib/math.c | 19 ++ lab8/lib/memory.c | 22 ++ lab8/lib/mini_uart.c | 69 +++++ lab8/lib/mm.S | 6 + lab8/lib/mm.c | 376 +++++++++++++++++++++++++++ lab8/lib/printf.c | 152 +++++++++++ lab8/lib/reboot.c | 18 ++ lab8/lib/sched.S | 24 ++ lab8/lib/sched.c | 107 ++++++++ lab8/lib/shell.c | 200 ++++++++++++++ lab8/lib/string.c | 55 ++++ lab8/lib/syscall.S | 91 +++++++ lab8/lib/syscall.c | 224 ++++++++++++++++ lab8/lib/timer.c | 62 +++++ lab8/lib/tmpfs.c | 154 +++++++++++ lab8/lib/utils.S | 15 ++ lab8/lib/vfs.c | 197 ++++++++++++++ lab8/rootfs/msg | 1 + lab8/send_img.py | 31 +++ 61 files changed, 3628 insertions(+) create mode 100644 lab8/FAT_R.TXT create mode 100644 lab8/bcm2710-rpi-3-b-plus.dtb create mode 100644 lab8/config.txt create mode 100644 lab8/include/cpio.h create mode 100644 lab8/include/devtree.h create mode 100644 lab8/include/entry.h create mode 100644 lab8/include/exception.h create mode 100644 lab8/include/fork.h create mode 100644 lab8/include/initramfs.h create mode 100644 lab8/include/mailbox.h create mode 100644 lab8/include/math.h create mode 100644 lab8/include/memory.h create mode 100644 lab8/include/mini_uart.h create mode 100644 lab8/include/mm.h create mode 100644 lab8/include/peripherals/base.h create mode 100644 lab8/include/peripherals/exception.h create mode 100644 lab8/include/peripherals/gpio.h create mode 100644 lab8/include/peripherals/mailbox.h create mode 100644 lab8/include/peripherals/mini_uart.h create mode 100644 lab8/include/printf.h create mode 100644 lab8/include/reboot.h create mode 100644 lab8/include/sched.h create mode 100644 lab8/include/shell.h create mode 100644 lab8/include/string.h create mode 100644 lab8/include/syscall.h create mode 100644 lab8/include/timer.h create mode 100644 lab8/include/tmpfs.h create mode 100644 lab8/include/utils.h create mode 100644 lab8/include/vfs.h create mode 100644 lab8/initramfs.cpio create mode 100644 lab8/kernel/Makefile create mode 100644 lab8/kernel/boot_kernel.S create mode 100644 lab8/kernel/kernel.c create mode 100644 lab8/kernel/linker.ld create mode 100644 lab8/lib/Makefile create mode 100644 lab8/lib/cpio.c create mode 100644 lab8/lib/devtree.c create mode 100644 lab8/lib/entry.S create mode 100644 lab8/lib/exception.c create mode 100644 lab8/lib/fork.c create mode 100644 lab8/lib/initramfs.c create mode 100644 lab8/lib/mailbox.c create mode 100644 lab8/lib/math.c create mode 100644 lab8/lib/memory.c create mode 100644 lab8/lib/mini_uart.c create mode 100644 lab8/lib/mm.S create mode 100644 lab8/lib/mm.c create mode 100644 lab8/lib/printf.c create mode 100644 lab8/lib/reboot.c create mode 100644 lab8/lib/sched.S create mode 100644 lab8/lib/sched.c create mode 100644 lab8/lib/shell.c create mode 100644 lab8/lib/string.c create mode 100644 lab8/lib/syscall.S create mode 100644 lab8/lib/syscall.c create mode 100644 lab8/lib/timer.c create mode 100644 lab8/lib/tmpfs.c create mode 100644 lab8/lib/utils.S create mode 100644 lab8/lib/vfs.c create mode 100644 lab8/rootfs/msg create mode 100644 lab8/send_img.py diff --git a/lab8/FAT_R.TXT b/lab8/FAT_R.TXT new file mode 100644 index 000000000..5d2252cd1 --- /dev/null +++ b/lab8/FAT_R.TXT @@ -0,0 +1 @@ +fat_r test \ No newline at end of file diff --git a/lab8/bcm2710-rpi-3-b-plus.dtb b/lab8/bcm2710-rpi-3-b-plus.dtb new file mode 100644 index 0000000000000000000000000000000000000000..3934b3a26eb82fd65dbcdfca6f6916da427a3fae GIT binary patch literal 31790 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 literal 0 HcmV?d00001 diff --git a/lab8/config.txt b/lab8/config.txt new file mode 100644 index 000000000..49fc25695 --- /dev/null +++ b/lab8/config.txt @@ -0,0 +1,3 @@ +kernel=bootloader.img +arm_64bit=1 +initramfs initramfs.cpio 0x20000000 diff --git a/lab8/include/cpio.h b/lab8/include/cpio.h new file mode 100644 index 000000000..1735f5fe5 --- /dev/null +++ b/lab8/include/cpio.h @@ -0,0 +1,49 @@ +#ifndef __CPIO_H__ +#define __CPIO_H__ + +/* +Each file system object in a cpio archive comprises a header record with +basic numeric metadata followed by the full pathname of the entry and the +file data. The header record stores a series of integer values that gen- +erally follow the fields in struct stat. (See stat(2) for details.) The +variants differ primarily in how they store those integers (binary, oc- +tal, or hexadecimal). The header is followed by the pathname of the en- +try (the length of the pathname is stored in the header) and any file +data. The end of the archive is indicated by a special record with the +pathname "TRAILER!!!" +*/ + +#define CPIO_HEADER_MAGIC "070701" +#define CPIO_FOOTER_MAGIC "TRAILER!!!" +#define PI_CPIO_BASE ((void*) (0x20000000)) +#define QEMU_CPIO_BASE ((void*) (0x8000000)) + +#include "devtree.h" + +extern void *DEVTREE_CPIO_BASE; + +struct cpio_newc_header { + char c_magic[6]; // 070701 + 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]; // ignored by readers +}; + +void initramfs_callback (char *, char *, struct fdt_prop *); +void cpio_ls (); +void cpio_cat (); +void cpio_exec (); + +unsigned int hexstr_to_uint(char *s, unsigned int len); + +#endif \ No newline at end of file diff --git a/lab8/include/devtree.h b/lab8/include/devtree.h new file mode 100644 index 000000000..d8225b11c --- /dev/null +++ b/lab8/include/devtree.h @@ -0,0 +1,64 @@ +#ifndef _DEVTREE_H +#define _DEVTREE_H + +/* + magic; 0xd00dfeed (big-endian) + + totalsize; total size in bytes of the devicetree + data structure + + off_dt_struct; offset in bytes of the structure block + from the beginning of the header + + off_dt_strings; offset in bytes of the strings block + from the beginning of the header + + off_mem_rsvmap; offset in bytes of the memory reservation + block from the beginning of the header + + version; version of the devicetree data structure + + last_comp_version; lowest version of the devicetree data + structure with which the version used is backwards compatible + + boot_cpuid_phys; physical ID of the system’s boot CPU + + size_dt_strings; length in bytes of the strings block + section of the devicetree blob + + size_dt_struct; length in bytes of the structure block + section of the devicetree blob +*/ + +#define FDT_HEADER_MAGIC 0xd00dfeed +#define FDT_BEGIN_NODE 0x00000001 +#define FDT_END_NODE 0x00000002 +#define FDT_PROP 0x00000003 +#define FDT_NOP 0x00000004 +#define FDT_END 0x00000009 + +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; +}; + +struct fdt_prop { + unsigned int len; + unsigned int nameoff; +}; + +void devtree_getaddr (); +void fdt_traverse ( void (*callback)(char *, char *, struct fdt_prop *) ); + +// ARM uses little endian +unsigned int to_lendian (unsigned int); + +#endif \ No newline at end of file diff --git a/lab8/include/entry.h b/lab8/include/entry.h new file mode 100644 index 000000000..1a22d0284 --- /dev/null +++ b/lab8/include/entry.h @@ -0,0 +1,39 @@ +#ifndef _ENTRY_H +#define _ENTRY_H + +#define S_FRAME_SIZE 272 // size of all saved registers +#define S_X0 0 + +#define SYNC_INVALID_EL1t 0 +#define IRQ_INVALID_EL1t 1 +#define FIQ_INVALID_EL1t 2 +#define ERROR_INVALID_EL1t 3 + +#define SYNC_INVALID_EL1h 4 +#define IRQ_INVALID_EL1h 5 +#define FIQ_INVALID_EL1h 6 +#define ERROR_INVALID_EL1h 7 + +#define SYNC_INVALID_EL0_64 8 +#define IRQ_INVALID_EL0_64 9 +#define FIQ_INVALID_EL0_64 10 +#define ERROR_INVALID_EL0_64 11 + +#define SYNC_INVALID_EL0_32 12 +#define IRQ_INVALID_EL0_32 13 +#define FIQ_INVALID_EL0_32 14 +#define ERROR_INVALID_EL0_32 15 + +#define SYNC_ERROR 16 +#define SYSCALL_ERROR 17 + +#define ESR_ELx_EC_SHIFT 26 +#define ESR_ELx_EC_SVC64 0x15 + +#ifndef __ASSEMBLER__ + +void ret_from_fork(); + +#endif + +#endif \ No newline at end of file diff --git a/lab8/include/exception.h b/lab8/include/exception.h new file mode 100644 index 000000000..40f4ba532 --- /dev/null +++ b/lab8/include/exception.h @@ -0,0 +1,7 @@ +#ifndef _EXCEPTION_H +#define _EXCEPTION_H + +void enable_interrupt(); +void disable_interrupt(); + +#endif \ No newline at end of file diff --git a/lab8/include/fork.h b/lab8/include/fork.h new file mode 100644 index 000000000..7b018dc68 --- /dev/null +++ b/lab8/include/fork.h @@ -0,0 +1,26 @@ +#ifndef _FORK_H +#define _FORK_H + +#include "sched.h" + +#define PSR_MODE_EL0t 0x00000000 +#define PSR_MODE_EL1t 0x00000004 +#define PSR_MODE_EL1h 0x00000005 +#define PSR_MODE_EL2t 0x00000008 +#define PSR_MODE_EL2h 0x00000009 +#define PSR_MODE_EL3t 0x0000000c +#define PSR_MODE_EL3h 0x0000000d + +int copy_process(unsigned long, unsigned long, unsigned long, unsigned long); +int move_to_user_mode(unsigned long); +struct pt_regs *task_pt_regs(struct task_struct *); +void new_user_process(unsigned long); + +struct pt_regs { + unsigned long regs[31]; + unsigned long sp; + unsigned long pc; + unsigned long pstate; +}; + +#endif \ No newline at end of file diff --git a/lab8/include/initramfs.h b/lab8/include/initramfs.h new file mode 100644 index 000000000..80e9c9733 --- /dev/null +++ b/lab8/include/initramfs.h @@ -0,0 +1,35 @@ +#ifndef _INITRAMFS_H +#define _INITRAMFS_H + +#include "vfs.h" + +#define INITRAMFS_COMP_LEN 16 +#define MAX_DIRENT 16 +#define MAX_FILESIZE 0x1000 + +struct initramfs_internal { + char name[INITRAMFS_COMP_LEN]; + int type; + struct initramfs_internal *parent; + struct initramfs_internal *child[MAX_DIRENT]; + struct vnode *vnode; + int size; // use for child count and data size + void *data; +}; + +int initramfs_setup_mount(struct filesystem* fs, struct mount* mount); +int initramfs_register(); + +struct vnode* initramfs_new_node(struct initramfs_internal *parent, const char *name, int type); + +int initramfs_open(struct vnode *file_node, struct file **target); +int initramfs_close(struct file *file); +int initramfs_write(struct file *file, const void *buf, unsigned len); +int initramfs_read(struct file *file, void *buf, unsigned len); +int initramfs_create(struct vnode *dir_node, struct vnode **target, const char *component_name); +int initramfs_mkdir(struct vnode *dir_node, struct vnode **target, const char *component_name); +int initramfs_lookup(struct vnode *dir_node, struct vnode **target, const char *component_name); + +void parse_initramfs(); + +#endif \ No newline at end of file diff --git a/lab8/include/mailbox.h b/lab8/include/mailbox.h new file mode 100644 index 000000000..eeecaa9de --- /dev/null +++ b/lab8/include/mailbox.h @@ -0,0 +1,7 @@ +#ifndef _MAILBOX_H +#define _MAILBOX_H + +void get_board_revision (); +void get_arm_memory (); + +#endif \ No newline at end of file diff --git a/lab8/include/math.h b/lab8/include/math.h new file mode 100644 index 000000000..353ccfb26 --- /dev/null +++ b/lab8/include/math.h @@ -0,0 +1,7 @@ +#ifndef _MATH_H +#define _MATH_H + +int log(int, int); +int pow(int, int); + +#endif \ No newline at end of file diff --git a/lab8/include/memory.h b/lab8/include/memory.h new file mode 100644 index 000000000..956f6e955 --- /dev/null +++ b/lab8/include/memory.h @@ -0,0 +1,8 @@ +#ifndef _MEMORY_H +#define _MEMORY_H + +#define MAX_HEAP_SIZE 0x10000000 + +void* simple_malloc(unsigned int); + +#endif \ No newline at end of file diff --git a/lab8/include/mini_uart.h b/lab8/include/mini_uart.h new file mode 100644 index 000000000..453ec93f2 --- /dev/null +++ b/lab8/include/mini_uart.h @@ -0,0 +1,10 @@ +#ifndef _MINI_UART_H +#define _MINI_UART_H + +void uart_init ( void ); +char uart_recv ( void ); +void uart_send ( char c ); +void uart_send_string ( char* str ); +void printf ( char *fmt, ... ); + +#endif /*_MINI_UART_H */ \ No newline at end of file diff --git a/lab8/include/mm.h b/lab8/include/mm.h new file mode 100644 index 000000000..df97f2b89 --- /dev/null +++ b/lab8/include/mm.h @@ -0,0 +1,55 @@ +#ifndef _MM_H +#define _MM_H + +#define MEM_REGION_BEGIN 0x0 +#define MEM_REGION_END 0x3C000000 +#define PAGE_SIZE 4096 +#define MAX_ORDER 8 // largest: PAGE_SIZE*2^(MAX_ORDER) + +#define ALLOCABLE 0 +#define ALLOCATED -1 +#define C_NALLOCABLE -2 +#define RESERVED -3 + +#define NULL 0 + +#define MAX_POOL_PAGES 8 +#define MAX_POOLS 8 +#define MIN_CHUNK_SIZE 8 + +#define MAX_RESERVABLE 8 + +struct frame { + unsigned int index; + int val; + int state; + struct frame *prev, *next; +}; + +struct node { + struct node *next; +}; + +struct dynamic_pool { + unsigned int chunk_size; + unsigned int chunks_per_page; + unsigned int chunks_allocated; + unsigned int page_new_chunk_off; + unsigned int pages_used; + void *page_base_addrs[MAX_POOL_PAGES]; + struct node *free_head; +}; + +void *malloc(unsigned int); +void free(void *); +void init_mm(); +void init_pool(struct dynamic_pool*, unsigned int); +int register_chunk(unsigned int); +void *chunk_alloc(unsigned int); +void chunk_free(void *); +void memory_reserve(void*, void*); +void init_mm_reserve(); + +void memzero(unsigned long, unsigned long); + +#endif /*_MM_H */ \ No newline at end of file diff --git a/lab8/include/peripherals/base.h b/lab8/include/peripherals/base.h new file mode 100644 index 000000000..8f66cd5fe --- /dev/null +++ b/lab8/include/peripherals/base.h @@ -0,0 +1,6 @@ +#ifndef _P_BASE_H +#define _P_BASE_H + +#define PBASE 0x3F000000 + +#endif /*_P_BASE_H */ \ No newline at end of file diff --git a/lab8/include/peripherals/exception.h b/lab8/include/peripherals/exception.h new file mode 100644 index 000000000..bc64c7c53 --- /dev/null +++ b/lab8/include/peripherals/exception.h @@ -0,0 +1,25 @@ +#ifndef _P_EXCEPTION_H +#define _P_EXCEPTION_H + +#include "peripherals/base.h" + +#define IRQ_BASIC_PENDING (PBASE+0x0000B200) +#define IRQ_PENDING_1 (PBASE+0x0000B204) +#define IRQ_PENDING_2 (PBASE+0x0000B208) +#define FIQ_CONTROL (PBASE+0x0000B20C) +#define ENABLE_IRQS_1 (PBASE+0x0000B210) +#define ENABLE_IRQS_2 (PBASE+0x0000B214) +#define ENABLE_BASIC_IRQS (PBASE+0x0000B218) +#define DISABLE_IRQS_1 (PBASE+0x0000B21C) +#define DISABLE_IRQS_2 (PBASE+0x0000B220) +#define DISABLE_BASIC_IRQS (PBASE+0x0000B224) + +#define CNTPCT_EL0 (0x4000001C) +#define CNTP_CTL_EL0 (0x40000040) +#define CORE0_INTERRUPT_SRC (0x40000060) + +#define IRQ_PENDING_1_AUX_INT (1<<29) +#define INTERRUPT_SOURCE_GPU (1<<8) +#define INTERRUPT_SOURCE_CNTPNSIRQ (1<<1) + +#endif \ No newline at end of file diff --git a/lab8/include/peripherals/gpio.h b/lab8/include/peripherals/gpio.h new file mode 100644 index 000000000..c5d7d138f --- /dev/null +++ b/lab8/include/peripherals/gpio.h @@ -0,0 +1,12 @@ +#ifndef _P_GPIO_H +#define _P_GPIO_H + +#include "peripherals/base.h" + +#define GPFSEL1 (PBASE+0x00200004) +#define GPSET0 (PBASE+0x0020001C) +#define GPCLR0 (PBASE+0x00200028) +#define GPPUD (PBASE+0x00200094) +#define GPPUDCLK0 (PBASE+0x00200098) + +#endif /*_P_GPIO_H */ \ No newline at end of file diff --git a/lab8/include/peripherals/mailbox.h b/lab8/include/peripherals/mailbox.h new file mode 100644 index 000000000..0351bd714 --- /dev/null +++ b/lab8/include/peripherals/mailbox.h @@ -0,0 +1,23 @@ +#ifndef _P_MAILBOX_H +#define _P_MAILBOX_H + +#define MMIO_BASE 0x3f000000 +#define MAILBOX_BASE MMIO_BASE + 0xb880 + +#define MAILBOX_READ (volatile unsigned int*) (MAILBOX_BASE) +#define MAILBOX_STATUS (volatile unsigned int*) (MAILBOX_BASE + 0x18) +#define MAILBOX_WRITE (volatile unsigned int*) (MAILBOX_BASE + 0x20) + +#define MAILBOX_EMPTY 0x40000000 +#define MAILBOX_FULL 0x80000000 + +#define GET_BOARD_REVISION 0x00010002 +#define GET_ARM_MEMORY 0x00010005 + +#define REQUEST_CODE 0x00000000 +#define REQUEST_SUCCEED 0x80000000 +#define REQUEST_FAILED 0x80000001 +#define TAG_REQUEST_CODE 0x00000000 +#define END_TAG 0x00000000 + +#endif \ No newline at end of file diff --git a/lab8/include/peripherals/mini_uart.h b/lab8/include/peripherals/mini_uart.h new file mode 100644 index 000000000..386b6fade --- /dev/null +++ b/lab8/include/peripherals/mini_uart.h @@ -0,0 +1,19 @@ +#ifndef _P_MINI_UART_H +#define _P_MINI_UART_H + +#include "peripherals/base.h" + +#define AUX_ENABLES (PBASE+0x00215004) +#define AUX_MU_IO_REG (PBASE+0x00215040) +#define AUX_MU_IER_REG (PBASE+0x00215044) +#define AUX_MU_IIR_REG (PBASE+0x00215048) +#define AUX_MU_LCR_REG (PBASE+0x0021504C) +#define AUX_MU_MCR_REG (PBASE+0x00215050) +#define AUX_MU_LSR_REG (PBASE+0x00215054) +#define AUX_MU_MSR_REG (PBASE+0x00215058) +#define AUX_MU_SCRATCH (PBASE+0x0021505C) +#define AUX_MU_CNTL_REG (PBASE+0x00215060) +#define AUX_MU_STAT_REG (PBASE+0x00215064) +#define AUX_MU_BAUD_REG (PBASE+0x00215068) + +#endif /*_P_MINI_UART_H */ \ No newline at end of file diff --git a/lab8/include/printf.h b/lab8/include/printf.h new file mode 100644 index 000000000..dfedbe301 --- /dev/null +++ b/lab8/include/printf.h @@ -0,0 +1,31 @@ +/* + * 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. + * + */ +#ifndef _PRINTF_H +#define _PRINTF_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/lab8/include/reboot.h b/lab8/include/reboot.h new file mode 100644 index 000000000..df4d63a86 --- /dev/null +++ b/lab8/include/reboot.h @@ -0,0 +1,7 @@ +#ifndef _REBOOT_H +#define _REBOOT_H + +void reset ( int ); +void cancel_reset (); + +#endif \ No newline at end of file diff --git a/lab8/include/sched.h b/lab8/include/sched.h new file mode 100644 index 000000000..d36d6e8ac --- /dev/null +++ b/lab8/include/sched.h @@ -0,0 +1,78 @@ +#ifndef _SCHED_H +#define _SCHED_H + +#define THREAD_CPU_CONTEXT 0 + +#ifndef __ASSEMBLER__ + +#include "vfs.h" + +#define THREAD_SIZE 4096 + +#define NR_TASKS 64 + +#define FIRST_TASK task[0] +#define LAST_TASK task[NR_TASKS-1] + +#define TASK_RUNNING 0 +#define TASK_INTERRUPTIBLE 1 +#define TASK_UNINTERRUPTIBLE 2 +#define TASK_ZOMBIE 3 +#define TASK_STOPPED 4 + +#define PF_KTHREAD 0x00000002 + +extern struct task_struct *current; +extern struct task_struct *task[NR_TASKS]; +extern int nr_tasks; + +struct cpu_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 sp; + unsigned long pc; +}; + +struct task_struct { + struct cpu_context cpu_context; + long state; + long counter; + long priority; + long preempt_count; + unsigned long stack; + unsigned long flags; + long id; + struct fd_table files; + struct vnode *cwd; +}; + +extern void sched_init(); +extern void schedule(); +extern void timer_tick(); +extern void preempt_disable(); +extern void preempt_enable(); +extern void switch_to(struct task_struct *); +extern void cpu_switch_to(struct task_struct *, struct task_struct *); +extern void exit_process(); +extern void kill_zombies(); + +#define INIT_TASK \ +{ \ +{0,0,0,0,0,0,0,0,0,0,0,0,0},\ +0, 0, 1, 0, 0, PF_KTHREAD, 0, \ +{0, {0}},\ +0 \ +} + +#endif + +#endif \ No newline at end of file diff --git a/lab8/include/shell.h b/lab8/include/shell.h new file mode 100644 index 000000000..828c56763 --- /dev/null +++ b/lab8/include/shell.h @@ -0,0 +1,6 @@ +#ifndef _SHELL_H +#define _SHELL_H + +void shell_loop (); + +#endif \ No newline at end of file diff --git a/lab8/include/string.h b/lab8/include/string.h new file mode 100644 index 000000000..c71840033 --- /dev/null +++ b/lab8/include/string.h @@ -0,0 +1,10 @@ +#ifndef _STRING_H +#define _STRING_H + +int stringcmp (const char *, const char *); +int stringncmp (const char *, const char *, unsigned int); +unsigned int strlen(const char *); +void strcpy(char *dest, const char *src); +void strncpy(char *dest, const char *src, int n); + +#endif \ No newline at end of file diff --git a/lab8/include/syscall.h b/lab8/include/syscall.h new file mode 100644 index 000000000..429be1390 --- /dev/null +++ b/lab8/include/syscall.h @@ -0,0 +1,59 @@ +#ifndef _SYSCALL_H +#define _SYSCALL_H + +#define __NR_SYSCALLS 18 + +#define SYS_GETPID_NUM 0 +#define SYS_UARTREAD_NUM 1 +#define SYS_UARTWRITE_NUM 2 +#define SYS_EXEC_NUM 3 +#define SYS_FORK_NUM 4 +#define SYS_EXIT_NUM 5 +#define SYS_MBOXCALL_NUM 6 +#define SYS_KILL_NUM 7 + +#define SYS_OPEN_NUM 11 +#define SYS_CLOSE_NUM 12 +#define SYS_WRITE_NUM 13 +#define SYS_READ_NUM 14 +#define SYS_MKDIR_NUM 15 +#define SYS_MOUNT_NUM 16 +#define SYS_CHDIR_NUM 17 + +#ifndef __ASSEMBLER__ + +int getpid(); +unsigned uart_read(char buf[], unsigned size); +unsigned uart_write(const char buf[], unsigned size); +int exec(const char *name, char *const argv[]); +int fork(); +void exit(int status); +int mbox_call(unsigned char ch, volatile unsigned int *mbox); +void kill(int pid); +int open(const char *pathname, int flags); +int close(int fd); +long write(int fd, const void *buf, unsigned long count); +long read(int fd, void *buf, unsigned long count); +int mkdir(const char *pathname, unsigned mode); +int mount(const char *src, const char *target, const char *fs, unsigned long flags, const void *data); +int chdir(const char *path); + +int sys_getpid(); +unsigned sys_uartread(char buf[], unsigned size); +unsigned sys_uartwrite(const char buf[], unsigned size); +int sys_exec(const char *name, char *const argv[]); +int sys_fork(); +void sys_exit(int status); +int sys_mbox_call(unsigned char ch, unsigned int *mbox); +void sys_kill(int pid); +int sys_open(const char *pathname, int flags); +int sys_close(int fd); +long sys_write(int fd, const void *buf, unsigned long count); +long sys_read(int fd, void *buf, unsigned long count); +int sys_mkdir(const char *pathname, unsigned mode); +int sys_mount(const char *src, const char *target, const char *fs, unsigned long flags, const void *data); +int sys_chdir(const char *path); + +#endif + +#endif \ No newline at end of file diff --git a/lab8/include/timer.h b/lab8/include/timer.h new file mode 100644 index 000000000..9812a457a --- /dev/null +++ b/lab8/include/timer.h @@ -0,0 +1,14 @@ +#ifndef _TIMER_H +#define _TIMER_H + +#define CORE0_TIMER_IRQ_CTRL 0x40000040 + +void timer_init(); +void handle_timer_irq(); +void core_timer_enable(); +void core_timer_disable(); +void set_timer(unsigned int rel_time); +unsigned int read_timer(); +unsigned int read_freq(); + +#endif \ No newline at end of file diff --git a/lab8/include/tmpfs.h b/lab8/include/tmpfs.h new file mode 100644 index 000000000..f01824281 --- /dev/null +++ b/lab8/include/tmpfs.h @@ -0,0 +1,33 @@ +#ifndef _TMPFS_H +#define _TMPFS_H + +#include "vfs.h" + +#define TMPFS_COMP_LEN 16 +#define MAX_DIRENT 16 +#define MAX_FILESIZE 0x1000 + +struct tmpfs_internal { + char name[TMPFS_COMP_LEN]; + int type; + struct tmpfs_internal *parent; + struct tmpfs_internal *child[MAX_DIRENT]; + struct vnode *vnode; + int size; // use for child count and data size + void *data; +}; + +int tmpfs_setup_mount(); +int tmpfs_register(); + +struct vnode* tmpfs_new_node(struct tmpfs_internal *parent, const char *name, int type); + +int tmpfs_open(struct vnode *file_node, struct file **target); +int tmpfs_close(struct file *file); +int tmpfs_write(struct file *file, const void *buf, unsigned len); +int tmpfs_read(struct file *file, void *buf, unsigned len); +int tmpfs_create(struct vnode *dir_node, struct vnode **target, const char *component_name); +int tmpfs_mkdir(struct vnode *dir_node, struct vnode **target, const char *component_name); +int tmpfs_lookup(struct vnode *dir_node, struct vnode **target, const char *component_name); + +#endif \ No newline at end of file diff --git a/lab8/include/utils.h b/lab8/include/utils.h new file mode 100644 index 000000000..9d950d477 --- /dev/null +++ b/lab8/include/utils.h @@ -0,0 +1,8 @@ +#ifndef _BOOT_H +#define _BOOT_H + +extern void delay ( unsigned long ); +extern void put32 ( unsigned long, unsigned int ); +extern unsigned int get32 ( unsigned long ); + +#endif /*_BOOT_H */ \ No newline at end of file diff --git a/lab8/include/vfs.h b/lab8/include/vfs.h new file mode 100644 index 000000000..d6b59781a --- /dev/null +++ b/lab8/include/vfs.h @@ -0,0 +1,81 @@ +#ifndef _VFS_H +#define _VFS_H + +struct vnode { + struct vnode* parent; + struct mount* mount; + struct vnode_operations* v_ops; + struct file_operations* f_ops; + void* internal; +}; + +// file handle +struct file { + struct vnode* vnode; + unsigned f_pos; // RW position of this file handle + struct file_operations* f_ops; + int flags; +}; + +struct mount { + struct vnode* root; + struct filesystem* fs; +}; + +struct filesystem { + char* name; + int (*setup_mount)(struct filesystem* fs, struct mount* mount); +}; + +struct file_operations { + int (*write)(struct file* file, const void* buf, unsigned len); + int (*read)(struct file* file, void* buf, unsigned len); + int (*open)(struct vnode* file_node, struct file** target); + int (*close)(struct file* file); +}; + +struct vnode_operations { + int (*lookup)(struct vnode* dir_node, struct vnode** target, + const char* component_name); + int (*create)(struct vnode* dir_node, struct vnode** target, + const char* component_name); + int (*mkdir)(struct vnode* dir_node, struct vnode** target, + const char* component_name); +}; + +struct fd_table { + int count; + struct file *fds[16]; +}; + +#define VFS_PATHMAX 256 + +#define REGULAR_FILE -2 +#define DIRECTORY -3 + +#define O_CREAT 00000100 +#define SUCCESS 0 +#define FAIL -1 +#define EOF -1 + +extern struct mount *rootfs; + +void rootfs_init(); +void initramfs_init(); + +int register_fs(struct filesystem *fs); +struct file* create_fd(struct vnode* target, int flags); +int vfs_open(const char *pathname, int flags, struct file **target); +int vfs_close(struct file *file); +int vfs_write(struct file *file, const void *buf, unsigned len); +int vfs_read(struct file *file, void *buf, unsigned len); + +int vfs_mkdir(const char *pathname); +int vfs_mount(const char *target, const char *filesystem); +int vfs_lookup(const char *pathname, struct vnode **target); +int vfs_chdir(const char *pathname); + +void traverse(const char* pathname, struct vnode** target_node, char *target_path); +void r_traverse(struct vnode *node, const char *path, struct vnode **target_node, char *target_path); + +#endif \ No newline at end of file diff --git a/lab8/initramfs.cpio b/lab8/initramfs.cpio new file mode 100644 index 0000000000000000000000000000000000000000..6fcebfdf9878463fe2435811037012b3c1d75bc5 GIT binary patch literal 404992 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 e8;i_T^4@MSqL* zqPp)yJ6Tf(pvi=c@qg$b-nj<THZdra@3x!MoR6UPnA8z1%z%#krsSCO2}Ilc%> zz!#>U049j}`it%)6ZT=92cT7D7eBx~r?u^MjR}wX1aq+2oIBzN`iGpzw)_~s_h-b= zMGhY8L4kJoarSWx)Q2Zsu(APLh+rpoB&f(ffkv7NQbP4;UJ~au8VvR7Kjf<|`;$4nhAK>EKdJz5v2%H%ZQRc>| z(b?{R4NXh2P%b`GmeW#E^`^TG#qg5tIS#rasNlB4$b@0&4^j#ohsHdpuNDv9dOLpC zL*Do~#!2MghY`YdVveacI)yB+=;`{}XU1AXt2{(Zp36SrSH*?J$^hf%KAbj+{_g0) zcJ;mRJul@jme|q1$7&CF0eHAs4op?emfCCT%ah&3(4}Sd7EOZaZ93s({l0e*2F-Bv zIP7Y4g#PY*xxe8D#U+~Ba=`f5W6kWxjxVBl*fW6zvO1Ou1S=PWbApfkL61`A45wxE zyXQWXd0VTq*upow{FoPD!~5*c+n60447a5EAdWe}oI;fYrNxJ0TM*!~e{lEBc-UXt zB)9nj@4c5NV?>6giP=9cLoR@RZe}0a< z9b3hkK>Jj0|M#B$``$z{B^q>H;PH(%ltT<2zEH2y@7=tZbDr;en4S|cUeH&pI)3op zv40b-dpn}M={J4opzG8}Bzs~q)pv8uv227+_QjlSEa^UvbDnm2Sn_s?cGgXEp}!!5 z(MD|{`?zg8vvbgbz9Fu|*}?DyG9J+9cl#}@Z;5laP21hu6GK0VM{4)*6=(V)KG9F6 zcsnW+sB`T$Vdt(~=!v&_+!GnenN^18Vr2J7R_+J=Vu+LR8sv5KFPRQ-+l{62^CMA! z@<1!K*PxB!^guUHv1yFwU0Y6azY*bdwwpKcox7cy`xFuJE6N4!4Z! zbiQi~=o)?KK%5o+x;kdRiHDtTAb-s3#^Ygc zT#-QMJth&K>c(r~Q6^s&zX>}<5;rDs-$#cX;kgm%0AnzcgL|NyABhjrKRf8%gsZUe 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 WzMlvK<8;{t_|31M#TWkv5%^zAqp8*a literal 0 HcmV?d00001 diff --git a/lab8/kernel/Makefile b/lab8/kernel/Makefile new file mode 100644 index 000000000..831618b19 --- /dev/null +++ b/lab8/kernel/Makefile @@ -0,0 +1,49 @@ +AARCH64_PREFIX = aarch64-linux-gnu- +CC = $(AARCH64_PREFIX)gcc +LD = $(AARCH64_PREFIX)ld +OBJCPY = $(AARCH64_PREFIX)objcopy +GDB = $(AARCH64_PREFIX)gdb + +CFLAGS = -Wall -nostdlib -nostartfiles -ffreestanding -I../include -mgeneral-regs-only +ASMFLAGS = -I../include + +BUILD_DIR = ../build + +.PHONY: all clean qemu gdb_start + +all: kernel8.img + +clean: + rm kernel8.img $(OBJ_FILES) $(BUILD_DIR)/$(DEP_FILES) \ + ../build/kernel8.elf + +$(BUILD_DIR)/%_c.o: %.c + mkdir -p $(@D) + $(CC) $(CFLAGS) -MMD -c $< -o $@ + +$(BUILD_DIR)/%_s.o: %.S + $(CC) $(ASMFLAGS) -MMD -c $< -o $@ + +C_FILES = $(wildcard *.c) +ASM_FILES = $(wildcard *.S) +OBJ_FILES = $(C_FILES:%.c=$(BUILD_DIR)/%_c.o) +OBJ_FILES += $(ASM_FILES:%.S=$(BUILD_DIR)/%_s.o) + +LIBC_FILES = $(wildcard ../lib/*.c) +LIBASM_FILES = $(wildcard ../lib/*.S) +LIBOBJ_FILES = $(LIBC_FILES:../lib/%.c=$(BUILD_DIR)/%_c.o) +LIBOBJ_FILES += $(LIBASM_FILES:../lib/%.S=$(BUILD_DIR)/%_s.o) + +DEP_FILES = $(OBJ_FILES:%.o=%.d) +-include $(DEP_FILES) + +kernel8.img: linker.ld $(OBJ_FILES) $(LIBOBJ_FILES) + $(LD) -T linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(LIBOBJ_FILES) + $(OBJCPY) $(BUILD_DIR)/kernel8.elf -O binary $@ + +qemu: # all in one qemu command + qemu-system-aarch64 -M raspi3b -kernel kernel8.img -S -s -serial null -serial pty \ + -initrd ../initramfs.cpio -dtb ../bcm2710-rpi-3-b-plus.dtb + +gdb_start: + $(GDB) $(BUILD_DIR)/kernel8.elf diff --git a/lab8/kernel/boot_kernel.S b/lab8/kernel/boot_kernel.S new file mode 100644 index 000000000..649bc4c69 --- /dev/null +++ b/lab8/kernel/boot_kernel.S @@ -0,0 +1,41 @@ +.section ".text.boot" + +.globl _start +_start: + mrs x0, mpidr_el1 + and x0, x0, #3 // Check processor id + cbnz x0, proc_hang // Hang for all non-primary CPU + bl from_el2_to_el1 + bl set_exception_vector_table + +master: + ldr x0, =__bss_begin + ldr x1, =__bss_end + sub x1, x1, x0 + +memzero: + cbz x1, movesp + str xzr, [x0], #8 + subs x1, x1, #8 + cbnz x1, memzero + +movesp: + mov sp, #0x80000 + bl kernel_main + +proc_hang: + wfe + b proc_hang + +set_exception_vector_table: + adr x0, exception_vector_table + msr vbar_el1, x0 + ret + +from_el2_to_el1: + mov x0, (1 << 31) // EL1 uses aarch64 + msr hcr_el2, x0 + mov x0, 0x3c5 // EL1h (SPSel = 1) with interrupt disabled + msr spsr_el2, x0 + msr elr_el2, lr + eret // return to EL1 \ No newline at end of file diff --git a/lab8/kernel/kernel.c b/lab8/kernel/kernel.c new file mode 100644 index 000000000..951b63c64 --- /dev/null +++ b/lab8/kernel/kernel.c @@ -0,0 +1,29 @@ +#include "mini_uart.h" +#include "shell.h" +#include "devtree.h" +#include "cpio.h" +#include "mm.h" +#include "timer.h" +#include "exception.h" +#include "fork.h" +#include "vfs.h" + +void kernel_main(void) +{ + uart_init(); + devtree_getaddr(); + fdt_traverse(initramfs_callback); + init_mm_reserve(); + timer_init(); + rootfs_init(); + initramfs_init(); + enable_interrupt(); + uart_send_string("OSDI 2022 Spring\n"); + + copy_process(PF_KTHREAD, (unsigned long)&shell_loop, 0, 0); + + while (1) { + kill_zombies(); + schedule(); + } +} \ No newline at end of file diff --git a/lab8/kernel/linker.ld b/lab8/kernel/linker.ld new file mode 100644 index 000000000..7ae6ad8c6 --- /dev/null +++ b/lab8/kernel/linker.ld @@ -0,0 +1,21 @@ +SECTIONS +{ + . = 0x80000; + + .text.boot : { *(.text.boot) } + .text : { *(.text) } + .rodata : { *(.rodata) } + .data : { *(.data) } + + . = ALIGN(8); + + __bss_begin = .; + .bss : { *(.bss* .bss.*) } + __bss_end = .; + + . = ALIGN(16); + + __heap_start = .; + __kernel_end = .; + +} diff --git a/lab8/lib/Makefile b/lab8/lib/Makefile new file mode 100644 index 000000000..66d9bcea3 --- /dev/null +++ b/lab8/lib/Makefile @@ -0,0 +1,29 @@ +AARCH64_PREFIX = aarch64-linux-gnu- +CC = $(AARCH64_PREFIX)gcc + +CFLAGS = -Wall -nostdlib -nostartfiles -ffreestanding -I../include -mgeneral-regs-only +ASMFLAGS = -I../include + +BUILD_DIR = ../build + +.PHONY: all clean + +$(BUILD_DIR)/%_c.o: %.c + mkdir -p $(@D) + $(CC) $(CFLAGS) -MMD -c $< -o $@ + +$(BUILD_DIR)/%_s.o: %.S + $(CC) $(ASMFLAGS) -MMD -c $< -o $@ + +C_FILES = $(wildcard *.c) +ASM_FILES = $(wildcard *.S) +OBJ_FILES = $(C_FILES:%.c=$(BUILD_DIR)/%_c.o) +OBJ_FILES += $(ASM_FILES:%.S=$(BUILD_DIR)/%_s.o) + +DEP_FILES = $(OBJ_FILES:%.o=%.d) +-include $(DEP_FILES) + +all: $(OBJ_FILES) + +clean: + rm $(OBJ_FILES) $(BUILD_DIR)/$(DEP_FILES) diff --git a/lab8/lib/cpio.c b/lab8/lib/cpio.c new file mode 100644 index 000000000..db3cc41fc --- /dev/null +++ b/lab8/lib/cpio.c @@ -0,0 +1,232 @@ +#include "../include/cpio.h" +#include "mini_uart.h" +#include "../include/sched.h" +#include "fork.h" +#include "string.h" + +void *DEVTREE_CPIO_BASE = 0; + +unsigned int hexstr_to_uint(char *s, unsigned int len) { + + unsigned int n = 0; + + for (int i=0; i= '0' && s[i] <= '9') { + n += s[i] - '0'; + } else if (s[i] >= 'A' && s[i] <= 'F') { + n += s[i] - 'A' + 10; + } + } + + return n; + +} + +void initramfs_callback(char *node_name, char *prop_name, struct fdt_prop *prop) { + + if (stringncmp(node_name, "chosen", 7) == 0 && + stringncmp(prop_name, "linux,initrd-start", 19) == 0) { + + DEVTREE_CPIO_BASE = (void*)to_lendian(*((unsigned int*)(prop + 1))); + + } + +} + +void cpio_ls() { + + struct cpio_newc_header *header; + unsigned int filesize; + unsigned int namesize; + unsigned int offset; + char *filename; + + header = DEVTREE_CPIO_BASE; + + while (1) { + + filename = ((void*)header) + sizeof(struct cpio_newc_header); + + if (stringncmp((char*)header, CPIO_HEADER_MAGIC, 6) != 0) { + uart_send_string("invalid magic\n"); + break; + } + if (stringncmp(filename, CPIO_FOOTER_MAGIC, 11) == 0) break; + + uart_send_string(filename); + uart_send('\n'); + + namesize = hexstr_to_uint(header->c_namesize, 8); + filesize = hexstr_to_uint(header->c_filesize, 8); + + offset = sizeof(struct cpio_newc_header) + namesize; + if (offset % 4 != 0) + offset = ((offset/4) + 1) * 4; + + if (filesize % 4 != 0) + filesize = ((filesize/4) + 1) * 4; + + offset = offset + filesize; + + header = ((void*)header) + offset; + + } + +} + +void cpio_cat() { + + char input[256]; + char c = '\0'; + int idx = 0; + + struct cpio_newc_header *header; + unsigned int filesize; + unsigned int namesize; + unsigned int offset; + char *filename; + + header = DEVTREE_CPIO_BASE; + + uart_send_string("Filename: "); + + while (1) { + c = uart_recv(); + if (c == '\r' || c == '\n') { + uart_send_string("\n"); + + if (idx < 256) input[idx] = '\0'; + else input[255] = '\0'; + + break; + } else { + uart_send(c); + input[idx++] = c; + } + } + + while (1) { + + filename = ((void*)header) + sizeof(struct cpio_newc_header); + + if (stringncmp((char*)header, CPIO_HEADER_MAGIC, 6) != 0) { + uart_send_string("invalid magic\n"); + break; + } + if (stringncmp(filename, CPIO_FOOTER_MAGIC, 11) == 0) { + uart_send_string("file does not exist!\n"); + break; + } + + namesize = hexstr_to_uint(header->c_namesize, 8); + + offset = sizeof(struct cpio_newc_header) + namesize; + if (offset % 4 != 0) + offset = ((offset/4) + 1) * 4; + + filesize = hexstr_to_uint(header->c_filesize, 8); + + if (stringncmp(filename, input, namesize) == 0) { + + char *content = ((void*)header) + offset; + + for (int i=0; ic_namesize, 8); + + offset = sizeof(struct cpio_newc_header) + namesize; + if (offset % 4 != 0) + offset = ((offset/4) + 1) * 4; + + filesize = hexstr_to_uint(header->c_filesize, 8); + + if (stringncmp(filename, input, namesize) == 0) { + + char *code_loc = ((void*)header) + offset; + unsigned int sp_val = 0x600000; + asm volatile( + "msr elr_el1, %0\n\t" + "msr spsr_el1, xzr\n\t" + "msr sp_el0, %1\n\t" + "eret\n\t" + :: + "r" (code_loc), + "r" (sp_val) + ); + + break; + } + + if (filesize % 4 != 0) + filesize = ((filesize/4) + 1) * 4; + + offset = offset + filesize; + + header = ((void*)header) + offset; + + } + +} diff --git a/lab8/lib/devtree.c b/lab8/lib/devtree.c new file mode 100644 index 000000000..18d763c21 --- /dev/null +++ b/lab8/lib/devtree.c @@ -0,0 +1,92 @@ +#include "devtree.h" +#include "mini_uart.h" +#include "string.h" + +static void *DEVTREE_ADDRESS = 0; + +unsigned int to_lendian(unsigned int n) { + return ((n>>24)&0x000000FF) | + ((n>>8) &0x0000FF00) | + ((n<<8) &0x00FF0000) | + ((n<<24)&0xFF000000) ; +} + +void devtree_getaddr() { + + asm volatile("MOV %0, x20" : "=r"(DEVTREE_ADDRESS)); + + char magic[4] = {0xd0, 0x0d, 0xfe, 0xed}; + if(stringncmp((char*)DEVTREE_ADDRESS, magic, 4) != 0) { + uart_send_string("magic failed\n"); + } else { + uart_send_string("devtree magic succeed\n"); + } + +} + +void fdt_traverse( void (*callback)(char *, char *, struct fdt_prop *) ) { + + char magic[4] = {0xd0, 0x0d, 0xfe, 0xed}; + struct fdt_header *devtree_header = DEVTREE_ADDRESS; + + if(stringncmp((char*)devtree_header, magic, 4) != 0) { + uart_send_string("devtree magic failed\n"); + return; + } + + void *dt_struct_addr = DEVTREE_ADDRESS + to_lendian(devtree_header->off_dt_struct); + char *dt_string_addr = DEVTREE_ADDRESS + to_lendian(devtree_header->off_dt_strings); + + char *node_name; + char *prop_name; + unsigned int token; + unsigned int off; + + while (1) { + + token = to_lendian(*((unsigned int *)dt_struct_addr)); + + if (token == FDT_BEGIN_NODE) { + + node_name = dt_struct_addr + 4; + off = 4 + strlen(node_name) + 1; + + if (off%4 != 0) + off = ((off/4)+1)*4; + + dt_struct_addr += off; + + } + else if (token == FDT_END_NODE) { + dt_struct_addr += 4; + } + else if (token == FDT_PROP) { + + struct fdt_prop *prop = (struct fdt_prop*)(dt_struct_addr + 4); + + off = 4 + 8 + to_lendian(prop->len); + if (off%4 != 0) + off = ((off/4)+1)*4; + + dt_struct_addr += off; + + prop_name = dt_string_addr + to_lendian(prop->nameoff); + + callback(node_name, prop_name, prop); + + } + else if (token == FDT_NOP) { + dt_struct_addr += 4; + } + else if (token == FDT_END) { + dt_struct_addr += 4; + break; + } + else { + uart_send_string("TOKEN NOT MATCHED\n"); + break; + } + + } + +} \ No newline at end of file diff --git a/lab8/lib/entry.S b/lab8/lib/entry.S new file mode 100644 index 000000000..e564be0fe --- /dev/null +++ b/lab8/lib/entry.S @@ -0,0 +1,195 @@ +#include "entry.h" +#include "syscall.h" + +.macro handle_invalid_entry el, type + kernel_entry \el + mov x0, #\type + mrs x1, esr_el1 + mrs x2, elr_el1 + bl show_invalid_entry_message + b err_hang +.endm + +.macro ventry label + .align 7 + b \label +.endm + +// save general registers to stack +.macro kernel_entry, el + sub sp, sp, #S_FRAME_SIZE + 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] + + .if \el == 0 + mrs x21, sp_el0 + .else + add x21, sp, #S_FRAME_SIZE + .endif + + mrs x22, elr_el1 + mrs x23, spsr_el1 + + stp x30, x21, [sp, 16 * 15] + stp x22, x23, [sp, 16 * 16] +.endm + +// load general registers from stack +.macro kernel_exit, el + ldp x22, x23, [sp, 16 * 16] + ldp x30, x21, [sp, 16 * 15] + + .if \el == 0 + msr sp_el0, x21 + .endif + + msr elr_el1, x22 + msr spsr_el1, x23 + + ldp x0, x1, [sp ,16 * 0] + ldp x2, x3, [sp ,16 * 1] + ldp x4, x5, [sp ,16 * 2] + ldp x6, x7, [sp ,16 * 3] + ldp x8, x9, [sp ,16 * 4] + ldp x10, x11, [sp ,16 * 5] + ldp x12, x13, [sp ,16 * 6] + ldp x14, x15, [sp ,16 * 7] + ldp x16, x17, [sp ,16 * 8] + ldp x18, x19, [sp ,16 * 9] + ldp x20, x21, [sp ,16 * 10] + ldp x22, x23, [sp ,16 * 11] + ldp x24, x25, [sp ,16 * 12] + ldp x26, x27, [sp ,16 * 13] + ldp x28, x29, [sp ,16 * 14] + add sp, sp, #S_FRAME_SIZE + eret +.endm + +.align 11 // vector table should be aligned to 0x800 +.globl exception_vector_table +exception_vector_table: + ventry sync_invalid_el1t // Synchronous EL1t + ventry irq_invalid_el1t // IRQ EL1t + ventry fiq_invalid_el1t // FIQ EL1t + ventry error_invalid_el1t // Error EL1t + + ventry sync_invalid_el1h // Synchronous EL1h + ventry el1_irq // IRQ EL1h + ventry fiq_invalid_el1h // FIQ EL1h + ventry error_invalid_el1h // Error EL1h + + ventry el0_sync // Synchronous 64-bit EL0 + ventry el0_irq // IRQ 64-bit EL0 + ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 + ventry error_invalid_el0_64 // Error 64-bit EL0 + + ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 + ventry irq_invalid_el0_32 // IRQ 32-bit EL0 + ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 + ventry error_invalid_el0_32 // Error 32-bit EL0 + +sync_invalid_el1t: + handle_invalid_entry 1, SYNC_INVALID_EL1t + +irq_invalid_el1t: + handle_invalid_entry 1, IRQ_INVALID_EL1t + +fiq_invalid_el1t: + handle_invalid_entry 1, FIQ_INVALID_EL1t + +error_invalid_el1t: + handle_invalid_entry 1, ERROR_INVALID_EL1t + +sync_invalid_el1h: + handle_invalid_entry 1, SYNC_INVALID_EL1h + +el1_irq: + kernel_entry 1 + bl handle_irq + kernel_exit 1 + +fiq_invalid_el1h: + handle_invalid_entry 1, FIQ_INVALID_EL1h + +error_invalid_el1h: + handle_invalid_entry 1, ERROR_INVALID_EL1h + +el0_sync: + kernel_entry 0 + mrs x25, esr_el1 + lsr x24, x25, #ESR_ELx_EC_SHIFT + cmp x24, #ESR_ELx_EC_SVC64 + b.eq el0_svc + handle_invalid_entry 0, SYNC_ERROR + +el0_irq: + kernel_entry 0 + bl handle_irq + kernel_exit 0 + +fiq_invalid_el0_64: + handle_invalid_entry 0, FIQ_INVALID_EL0_64 + +error_invalid_el0_64: + handle_invalid_entry 0, ERROR_INVALID_EL0_64 + +sync_invalid_el0_32: + handle_invalid_entry 0, SYNC_INVALID_EL0_32 + +irq_invalid_el0_32: + handle_invalid_entry 0, IRQ_INVALID_EL0_32 + +fiq_invalid_el0_32: + handle_invalid_entry 0, FIQ_INVALID_EL0_32 + +error_invalid_el0_32: + handle_invalid_entry 0, ERROR_INVALID_EL0_32 + +sc_nr .req x25 +scno .req x26 +stbl .req x27 + +el0_svc: + adr stbl, sys_call_table + uxtw scno, w8 + mov sc_nr, #__NR_SYSCALLS + bl enable_interrupt + cmp scno, sc_nr + b.hs ni_sys + + ldr x16, [stbl, scno, lsl #3] + blr x16 + b ret_from_syscall +ni_sys: + handle_invalid_entry 0, SYSCALL_ERROR +ret_from_syscall: + bl disable_interrupt + str x0, [sp, #S_X0] + kernel_exit 0 + +.globl ret_from_fork +ret_from_fork: + bl schedule_tail + cbz x19, ret_to_user + mov x0, x20 + blr x19 // in theory should not return + //b err_hang // hang fail-safe +ret_to_user: + bl disable_interrupt + kernel_exit 0 + +.globl err_hang +err_hang: b err_hang diff --git a/lab8/lib/exception.c b/lab8/lib/exception.c new file mode 100644 index 000000000..a820ba662 --- /dev/null +++ b/lab8/lib/exception.c @@ -0,0 +1,49 @@ +#include "exception.h" +#include "mini_uart.h" +#include "mailbox.h" +#include "utils.h" +#include "timer.h" +#include "peripherals/exception.h" +#include "peripherals/mini_uart.h" + +void enable_interrupt() {asm volatile("msr DAIFClr, 0xf");} +void disable_interrupt() {asm volatile("msr DAIFSet, 0xf");} + +const char *entry_error_messages[] = { + "SYNC_INVALID_EL1t", + "IRQ_INVALID_EL1t", + "FIQ_INVALID_EL1t", + "ERROR_INVALID_EL1t", + + "SYNC_INVALID_EL1h", + "IRQ_INVALID_EL1h", + "FIQ_INVALID_EL1h", + "ERROR_INVALID_EL1h", + + "SYNC_INVALID_EL0_64", + "IRQ_INVALID_EL0_64", + "FIQ_INVALID_EL0_64", + "ERROR_INVALID_EL0_64", + + "SYNC_INVALID_EL0_32", + "IRQ_INVALID_EL0_32", + "FIQ_INVALID_EL0_32", + "ERROR_INVALID_EL0_32", + + "SYNC_ERROR", + "SYSCALL_ERROR" +}; + +void show_invalid_entry_message(int type, unsigned long esr, unsigned long addr) { + printf("%s, ESR: 0x%x, address: 0x%x\n", entry_error_messages[type], esr, addr); +} + +void handle_irq() { + + if (get32(CORE0_INTERRUPT_SRC)&INTERRUPT_SOURCE_CNTPNSIRQ) { + handle_timer_irq(); + } else { + printf("unknown irq encountered"); + } + +} \ No newline at end of file diff --git a/lab8/lib/fork.c b/lab8/lib/fork.c new file mode 100644 index 000000000..75de715e0 --- /dev/null +++ b/lab8/lib/fork.c @@ -0,0 +1,85 @@ +#include "fork.h" +#include "mm.h" +#include "mini_uart.h" +#include "../include/sched.h" +#include "../include/entry.h" + +int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg, unsigned long stack) { + + preempt_disable(); + struct task_struct *p; + + p = (struct task_struct *) malloc(PAGE_SIZE); + if (p == NULL) + return -1; + + struct pt_regs *childregs = task_pt_regs(p); + memzero((unsigned long)childregs, sizeof(struct pt_regs)); + memzero((unsigned long)&p->cpu_context, sizeof(struct cpu_context)); + + if (clone_flags & PF_KTHREAD) { + p->cpu_context.x19 = fn; + p->cpu_context.x20 = arg; + } else { + struct pt_regs *cur_regs = task_pt_regs(current); + // *childregs = *cur_regs; (object file generates memcpy) + // therefore the for loop is used below + for(int i=0; iregs[0] = 0; // return value 0 + childregs->sp = stack + PAGE_SIZE; + p->stack = stack; + } + + p->files = current->files; + p->cwd = current->cwd; + p->flags = clone_flags; + p->priority = current->priority; + p->state = TASK_RUNNING; + p->counter = p->priority; + p->preempt_count = 1; + + p->cpu_context.pc = (unsigned long)ret_from_fork; + p->cpu_context.sp = (unsigned long)childregs; + + int pid = nr_tasks++; + task[pid] = p; + p->id = pid; + preempt_enable(); + + return pid; + +} + +int move_to_user_mode(unsigned long pc) { + + struct pt_regs *regs = task_pt_regs(current); + memzero((unsigned long)regs, sizeof(*regs)); + regs->pc = pc; + regs->pstate = PSR_MODE_EL0t; + unsigned long stack = (unsigned long)malloc(PAGE_SIZE); + + if (stack == NULL) + return -1; + + regs->sp = stack + PAGE_SIZE; + current->stack = stack; + return 0; + +} + +struct pt_regs *task_pt_regs(struct task_struct *tsk) { + + unsigned long p = (unsigned long)tsk + THREAD_SIZE - sizeof(struct pt_regs); + return (struct pt_regs *)p; + +} + +void new_user_process(unsigned long func){ + printf("Kernel process started, moving to user mode.\n"); + int err = move_to_user_mode(func); + if (err < 0){ + printf("Error while moving process to user mode\n\r"); + } +} \ No newline at end of file diff --git a/lab8/lib/initramfs.c b/lab8/lib/initramfs.c new file mode 100644 index 000000000..6d6953500 --- /dev/null +++ b/lab8/lib/initramfs.c @@ -0,0 +1,211 @@ +#include "initramfs.h" +#include "mm.h" +#include "string.h" +#include "mini_uart.h" +#include "vfs.h" +#include "string.h" +#include "../include/cpio.h" + +struct vnode_operations *initramfs_v_ops; +struct file_operations *initramfs_f_ops; +int initramfs_registered = 0; + +int initramfs_setup_mount(struct filesystem* fs, struct mount* mount) { + + mount->fs = fs; + mount->root = initramfs_new_node(NULL, "/", DIRECTORY); + + printf("[debug] setup root: 0x%x\n", mount); + printf("[debug] setup root vnode: 0x%x\n", mount->root); + + return 0; + +} + +int initramfs_register() { + + if (initramfs_registered) return -1; + initramfs_registered = 1; + + initramfs_v_ops = (struct vnode_operations *) chunk_alloc(sizeof(struct vnode_operations)); + initramfs_f_ops = (struct file_operations *) chunk_alloc(sizeof(struct file_operations)); + + initramfs_v_ops->lookup = initramfs_lookup; + initramfs_v_ops->create = initramfs_create; + initramfs_v_ops->mkdir = initramfs_mkdir; + + initramfs_f_ops->open = initramfs_open; + initramfs_f_ops->read = initramfs_read; + initramfs_f_ops->write = initramfs_write; + initramfs_f_ops->close = initramfs_close; + + return 0; + +} + +struct vnode* initramfs_new_node(struct initramfs_internal *parent, const char *name, int type) { + + struct vnode *new_node = (struct vnode *)chunk_alloc(sizeof(struct vnode)); + struct initramfs_internal *new_internal = (struct initramfs_internal *)chunk_alloc(sizeof(struct initramfs_internal)); + + strcpy(new_internal->name, name); + new_internal->type = type; + new_internal->parent = parent; + new_internal->vnode = new_node; + new_internal->size = 0; + if (type == REGULAR_FILE) + new_internal->data = malloc(MAX_FILESIZE); + else + new_internal->data = 0; + + if (parent != NULL) + new_node->parent = parent->vnode; + new_node->f_ops = initramfs_f_ops; + new_node->v_ops = initramfs_v_ops; + new_node->mount = 0; + new_node->internal = (void *)new_internal; + + return new_node; + +} + +int initramfs_open(struct vnode *file_node, struct file **target) { + return SUCCESS; +} + +int initramfs_close(struct file *file) { + if (file) + return SUCCESS; + else + return FAIL; +} + +int initramfs_write(struct file *file, const void *buf, unsigned len) { + return FAIL; +} + +int initramfs_read(struct file *file, void *buf, unsigned len) { + + struct initramfs_internal *internal = (struct initramfs_internal*)file->vnode->internal; + if (internal->type != REGULAR_FILE) + return FAIL; + + char *dest = (char*)buf; + char *src = &((char *)internal->data)[file->f_pos]; + int i = 0; + for (; isize; i++) { + dest[i] = src[i]; + } + + return i; +} + +int initramfs_create(struct vnode *dir_node, struct vnode **target, const char *component_name) { + return FAIL; +} + +int initramfs_mkdir(struct vnode *dir_node, struct vnode **target, const char *component_name) { + return FAIL; +} + +int initramfs_lookup(struct vnode *dir_node, struct vnode **target, const char *component_name) { + + if (stringcmp(component_name, "") == 0) { + *target = dir_node; + return 0; + } + + struct initramfs_internal *internal = (struct initramfs_internal *)dir_node->internal; + + for (int i=0; isize; i++) { + if(stringcmp(internal->child[i]->name, component_name) == 0) { + *target = internal->child[i]->vnode; + return internal->child[i]->type; + } + } + + return FAIL; + +} + +void parse_initramfs() { + + vfs_chdir("/initramfs"); + + struct cpio_newc_header *header; + unsigned int filesize; + unsigned int namesize; + unsigned int offset; + unsigned int c_mode; + void *data; + char *filename; + + header = DEVTREE_CPIO_BASE; + + while (1) { + + struct vnode *target_node; + char target_path[VFS_PATHMAX]; + + filename = ((void*)header) + sizeof(struct cpio_newc_header); + + if (stringncmp((char*)header, CPIO_HEADER_MAGIC, 6) != 0) { + uart_send_string("invalid magic\n"); + break; + } + if (stringncmp(filename, CPIO_FOOTER_MAGIC, 11) == 0) break; + + //uart_send_string(filename); + //uart_send('\n'); + + namesize = hexstr_to_uint(header->c_namesize, 8); + filesize = hexstr_to_uint(header->c_filesize, 8); + c_mode = hexstr_to_uint(header->c_mode, 5); + offset = sizeof(struct cpio_newc_header) + namesize; + if (offset % 4 != 0) + offset = ((offset/4) + 1) * 4; + data = ((void *)header) + offset; + + if (stringncmp(header->c_mode, "00004", 5) == 0) { //dir + + printf("[debug] parse cpio mkdir %s\n", filename); + if (stringcmp(filename, ".") != 0 && stringcmp(filename, "..") != 0) { + traverse(filename, &target_node, target_path); + struct initramfs_internal *parent_internal = (struct initramfs_internal *)target_node->internal; + struct vnode *new_node = initramfs_new_node(parent_internal, target_path, DIRECTORY); + + parent_internal->child[parent_internal->size] = (struct initramfs_internal *)new_node->internal; + parent_internal->size++; + } else { + printf("[debug] mkdir skipped\n"); + } + + } else if (stringncmp(header->c_mode, "00008", 5) == 0) { //reg file + + printf("[debug] parse cpio create file %s, data at 0x%x with size 0x%d\n", filename, data, filesize); + traverse(filename, &target_node, target_path); + struct initramfs_internal *parent_internal = (struct initramfs_internal *)target_node->internal; + struct vnode *new_node = initramfs_new_node(parent_internal, target_path, REGULAR_FILE); + + parent_internal->child[parent_internal->size] = (struct initramfs_internal *)new_node->internal; + parent_internal->size++; + + ((struct initramfs_internal *)new_node->internal)->data = data; + ((struct initramfs_internal *)new_node->internal)->size = filesize; + + } else { + printf("[error] failed to parse cmode %d\n", c_mode); + } + + if (filesize % 4 != 0) + filesize = ((filesize/4) + 1) * 4; + + offset = offset + filesize; + + header = ((void*)header) + offset; + + } + + vfs_chdir("/"); + +} diff --git a/lab8/lib/mailbox.c b/lab8/lib/mailbox.c new file mode 100644 index 000000000..e593af037 --- /dev/null +++ b/lab8/lib/mailbox.c @@ -0,0 +1,49 @@ +#include "peripherals/mailbox.h" +#include "mailbox.h" +#include "mini_uart.h" + +int mailbox_call (volatile unsigned int *mailbox) { + unsigned int msg = ((unsigned long)mailbox & ~0xF) | (0x8 & 0xF); + while (*MAILBOX_STATUS & MAILBOX_FULL) ; + *MAILBOX_WRITE = msg; + while (1) { + while (*MAILBOX_STATUS & MAILBOX_EMPTY) {} + if (msg == *MAILBOX_READ) { + return mailbox[1]; + } + } +} + +void get_board_revision () +{ + volatile unsigned int __attribute__((aligned(16))) mailbox[7]; + mailbox[0] = 7 * 4; + mailbox[1] = REQUEST_CODE; + mailbox[2] = GET_BOARD_REVISION; + mailbox[3] = 4; + mailbox[4] = TAG_REQUEST_CODE; + mailbox[5] = 0; + mailbox[6] = END_TAG; + + if (mailbox_call(mailbox) == REQUEST_SUCCEED) { + printf("Board Revision:\t\t%x\n", mailbox[5]); + } +} + +void get_arm_memory () +{ + volatile unsigned int __attribute__((aligned(16))) mailbox[8]; + mailbox[0] = 8 * 4; + mailbox[1] = REQUEST_CODE; + mailbox[2] = GET_ARM_MEMORY; + mailbox[3] = 8; + mailbox[4] = TAG_REQUEST_CODE; + mailbox[5] = 0; + mailbox[6] = 0; + mailbox[7] = END_TAG; + + if (mailbox_call(mailbox) == REQUEST_SUCCEED) { + printf("Memory Base Addresss:\t%x\n", mailbox[5]); + printf("Memory Size:\t\t%x\n", mailbox[6]); + } +} \ No newline at end of file diff --git a/lab8/lib/math.c b/lab8/lib/math.c new file mode 100644 index 000000000..6b6919044 --- /dev/null +++ b/lab8/lib/math.c @@ -0,0 +1,19 @@ +#include "math.h" + +int log(int n, int base) { + int x = 1; + int ret = 0; + while (x <= n) { + x *= base; + ret++; + } + return ret; +} + +int pow(int base, int pow) { + int ret = 1; + for (int i=0; i avail) { + uart_send_string("not enough memory\n"); + } else { + __heap_ptr += size; + avail -= size; + } + + return ptr; + +} \ No newline at end of file diff --git a/lab8/lib/mini_uart.c b/lab8/lib/mini_uart.c new file mode 100644 index 000000000..c209f3a90 --- /dev/null +++ b/lab8/lib/mini_uart.c @@ -0,0 +1,69 @@ +#include "utils.h" +#include "printf.h" +#include "peripherals/mini_uart.h" +#include "peripherals/gpio.h" +#include "peripherals/exception.h" + +void uart_send ( char c ) +{ + if (c == '\n') uart_send('\r'); + + while (1) { + if (get32(AUX_MU_LSR_REG)&0x20) + break; + } + + put32(AUX_MU_IO_REG,c); +} + +char uart_recv ( void ) +{ + while(1) { + if (get32(AUX_MU_LSR_REG)&0x01) + break; + } + return (get32(AUX_MU_IO_REG)&0xFF); +} + +void uart_send_string ( char* str ) +{ + for (int i = 0; str[i] != '\0'; i++) { + uart_send((char)str[i]); + } +} + +void printf(char *fmt, ...) { + char temp[128]; + __builtin_va_list args; + __builtin_va_start(args, fmt); + vsprintf(temp,fmt,args); + uart_send_string(temp); +} + +void uart_init ( void ) +{ + unsigned int selector; + + selector = get32(GPFSEL1); + selector &= ~(7<<12); // clean gpio14 + selector |= 2<<12; // set alt5 for gpio14 + selector &= ~(7<<15); // clean gpio15 + selector |= 2<<15; // set alt5 for gpio 15 + put32(GPFSEL1,selector); + + put32(GPPUD,0); + delay(150); + put32(GPPUDCLK0,(1<<14)|(1<<15)); + delay(150); + put32(GPPUDCLK0,0); + + put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) + put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) + put32(AUX_MU_IER_REG,0); //Enable receive and disable transmit interrupts + put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode + put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high + put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 + put32(AUX_MU_IIR_REG,6); //Interrupt identify no fifo + put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver + +} diff --git a/lab8/lib/mm.S b/lab8/lib/mm.S new file mode 100644 index 000000000..263de4251 --- /dev/null +++ b/lab8/lib/mm.S @@ -0,0 +1,6 @@ +.globl memzero +memzero: + str xzr, [x0], #8 + subs x1, x1, #8 + b.gt memzero + ret \ No newline at end of file diff --git a/lab8/lib/mm.c b/lab8/lib/mm.c new file mode 100644 index 000000000..05a5f6f50 --- /dev/null +++ b/lab8/lib/mm.c @@ -0,0 +1,376 @@ +#include "mm.h" +#include "math.h" +#include "memory.h" +#include "mini_uart.h" + +static unsigned int n_frames = 0; +static unsigned int max_size = 0; +static struct frame* frame_list[MAX_ORDER] = {NULL}; +static struct frame frame_array[(MEM_REGION_END-MEM_REGION_BEGIN)/PAGE_SIZE]; +static struct dynamic_pool pools[MAX_POOLS] = { {ALLOCABLE, 0, 0, 0, 0, {NULL}, NULL} }; +static unsigned int reserved_num = 0; +static void* reserved_se[MAX_RESERVABLE][2] = {{0x0, 0x0}}; // expects to be sorted and addresses [,) +extern char __kernel_end; +static char *__kernel_end_ptr = &__kernel_end; + +void *malloc(unsigned int size) { + + if (size > max_size) { + printf("[error] Request exceeded allocable continuous size %d.\n", (int)max_size); + return NULL; + } + + int req_order = 0; + for(unsigned int i=PAGE_SIZE; i= MAX_ORDER) { + printf("[error] No memory allocable.\n"); + return NULL; + } + + while (t != req_order) { + struct frame* l_tmp = frame_list[t]; + frame_list[t] = l_tmp->next; + frame_list[t]->prev = NULL; + //printf("[info] Split at order %d, new head is 0x%x.\n", t+1, frame_list[t]); + + unsigned int off = pow(2, l_tmp->val-1); + struct frame* r_tmp = &frame_array[l_tmp->index+off]; + + l_tmp->val -= 1; + l_tmp->state = ALLOCABLE; + l_tmp->prev = NULL; + l_tmp->next = r_tmp; + + r_tmp->val = l_tmp->val; + r_tmp->state = ALLOCABLE; + r_tmp->prev = l_tmp; + r_tmp->next = NULL; + + t--; + if (frame_list[t] != NULL) + frame_list[t]->prev = r_tmp; + r_tmp->next = frame_list[t]; + frame_list[t] = l_tmp; + } + + struct frame* ret = frame_list[req_order]; + frame_list[req_order] = ret->next; + frame_list[req_order]->prev = NULL; + + ret->val = ret->val; + ret->state = ALLOCATED; + ret->prev = NULL; + ret->next = NULL; + + //printf("[info] allocated address: 0x%x\n", MEM_REGION_BEGIN+PAGE_SIZE*ret->index); + + return (void*)MEM_REGION_BEGIN+PAGE_SIZE*ret->index; + +} + +void free(void *address) { + + unsigned int idx = ((unsigned long long)address-MEM_REGION_BEGIN) / PAGE_SIZE; + struct frame* target = &frame_array[idx]; + + if (target->state == ALLOCABLE || target->state == C_NALLOCABLE) { + printf("[error] invalid free of already freed memory.\n"); + return; + } + //printf("=========================================================\n"); + //printf("[info] Now freeing address 0x%x with frame index %d.\n", address, (int)idx); + + for (int i=target->val; ival+1, fr_buddy->state); + + if (i < MAX_ORDER-1 && fr_buddy->state == ALLOCABLE && i== fr_buddy->val) { + + //printf("[info] Merging from order %d. Frame indices %d, %d.\n", i+1, (int)buddy, (int)idx); + + if (fr_buddy->prev != NULL) { + fr_buddy->prev->next = fr_buddy->next; + } else { + frame_list[fr_buddy->val] = fr_buddy->next; + } + + if (fr_buddy->next != NULL) { + fr_buddy->next->prev = fr_buddy->prev; + } + + fr_buddy->prev = NULL; + fr_buddy->next = NULL; + fr_buddy->val = C_NALLOCABLE; + fr_buddy->state = C_NALLOCABLE; + target->val = C_NALLOCABLE; + target->state = C_NALLOCABLE; + + if (fr_buddy->index < target->index) { + idx = fr_buddy->index; + target = fr_buddy; + } + + //printf("[info] Frame index of next merge target is %d.\n", (int)idx); + + } else { + + target->val = i; + target->state = ALLOCABLE; + target->prev = NULL; + target->next = frame_list[i]; + if (frame_list[i] != NULL) + frame_list[i]->prev = target; + frame_list[i] = target; + //printf("[info] Frame index %d pushed to frame list of order %d.\n", + // (int)target->index, (int)i+1); + break; + + } + + } + + //printf("[info] Free finished.\n"); + /*for (int i=0; i < MAX_ORDER; i++) { + if (frame_list[i] != NULL) + printf("[info] Head of order %d has frame array index %d.\n",i+1,frame_list[i]->index); + else + printf("[info] Head of order %d has frame array index null.\n",i+1); + }*/ + +} + +void init_mm() { + + n_frames = (MEM_REGION_END-MEM_REGION_BEGIN) / PAGE_SIZE; + unsigned int mul = (unsigned int)pow(2, MAX_ORDER-1); + printf("[info] Frame array start address 0x%x.\n", frame_array); + for (unsigned int i=0; ichunk_size = size; + pool->chunks_per_page = PAGE_SIZE / size; + pool->chunks_allocated = 0; + pool->page_new_chunk_off = 0; + pool->pages_used = 0; + pool->free_head = NULL; +} + +int register_chunk(unsigned int size) { + + unsigned int nsize = 0; + if (size <= 8) nsize = 8; + else { + int rem = size % 4; + if (rem != 0) nsize = (size/4 + 1)*4; + else nsize = size; + } + + if (nsize >= PAGE_SIZE) { + printf("[error] Normalized chunk size request leq page size.\n"); + return -1; + } + + for (int i=0; ifree_head != NULL) { + void *ret = (void*) pool->free_head; + pool->free_head = pool->free_head->next; + //printf("[info] allocate address 0x%x from pool free list.\n", ret); + return ret; + } + + if (pool->chunks_allocated >= MAX_POOL_PAGES*pool->chunks_per_page) { + //printf("[error] Pool maximum reached.\n"); + return NULL; + } + + + if (pool->chunks_allocated >= pool->pages_used*pool->chunks_per_page) { + pool->page_base_addrs[pool->pages_used] = malloc(PAGE_SIZE); + //printf("[info] allocate new page for pool with base address 0x%x.\n", + // pool->page_base_addrs[pool->pages_used]); + pool->pages_used++; + pool->page_new_chunk_off = 0; + } + + void *ret = pool->page_base_addrs[pool->pages_used - 1] + + pool->chunk_size*pool->page_new_chunk_off; + pool->page_new_chunk_off++; + pool->chunks_allocated++; + + //printf("[info] allocate new address 0x%x from pool.\n", ret); + + return ret; + +} + +void chunk_free(void *address) { + + int target = -1; + + void *prefix_addr = (void *)((unsigned long long)address & ~0xFFF); + + for (unsigned int i=0; ifree_head; + pool->free_head = (struct node*) address; + pool->free_head->next = old_head; + pool->chunks_allocated--; + +} + +void memory_reserve(void* start, void* end) { + if (reserved_num >= MAX_RESERVABLE) { + printf("[error] Max reservable locations already reached.\n"); + return; + } + reserved_se[reserved_num][0] = start; + reserved_se[reserved_num][1] = end; + reserved_num++; +} + +void init_mm_reserve() { + + max_size = PAGE_SIZE * pow(2, MAX_ORDER-1); + n_frames = (MEM_REGION_END-MEM_REGION_BEGIN) / PAGE_SIZE; + + memory_reserve((void*)0x0, __kernel_end_ptr); // spin tables, kernel image + memory_reserve((void*)0x20000000, (void*)0x20010000); // hard code reserve initramfs + + for (unsigned int i=0; i= reserved_se[i][0] && addr < reserved_se[i][1]) { + frame_array[j].state = RESERVED; + } + if (addr >= reserved_se[i][1]) break; + } + } + + for (int i=0; istate == RESERVED) continue; + if (target->state == C_NALLOCABLE) continue; + + for (int i=target->val; istate == ALLOCABLE && i== fr_buddy->val) { + + if (fr_buddy->prev != NULL) { + fr_buddy->prev->next = fr_buddy->next; + } else { + frame_list[fr_buddy->val] = fr_buddy->next; + } + + if (fr_buddy->next != NULL) { + fr_buddy->next->prev = fr_buddy->prev; + } + + fr_buddy->prev = NULL; + fr_buddy->next = NULL; + fr_buddy->val = C_NALLOCABLE; + fr_buddy->state = C_NALLOCABLE; + target->val = C_NALLOCABLE; + target->state = C_NALLOCABLE; + + if (fr_buddy->index < target->index) { + idx = fr_buddy->index; + target = fr_buddy; + } + + } else { + + target->val = i; + target->state = ALLOCABLE; + target->prev = NULL; + target->next = frame_list[i]; + if (frame_list[i] != NULL) + frame_list[i]->prev = target; + frame_list[i] = target; + break; + + } + + } + + } + +} \ No newline at end of file diff --git a/lab8/lib/printf.c b/lab8/lib/printf.c new file mode 100644 index 000000000..794a37dff --- /dev/null +++ b/lab8/lib/printf.c @@ -0,0 +1,152 @@ +/* + * 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 "printf.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) { + // 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_start(args, fmt): "..." is pointed by args + //__builtin_va_arg(args,int): ret=(int)*args;args++;return ret; + __builtin_va_list args; + __builtin_va_start(args, fmt); + return vsprintf(dst,fmt,args); +} \ No newline at end of file diff --git a/lab8/lib/reboot.c b/lab8/lib/reboot.c new file mode 100644 index 000000000..043a09fb7 --- /dev/null +++ b/lab8/lib/reboot.c @@ -0,0 +1,18 @@ +#define PM_PASSWORD 0x5a000000 +#define PM_RSTC 0x3F10001c +#define PM_WDOG 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 +} diff --git a/lab8/lib/sched.S b/lab8/lib/sched.S new file mode 100644 index 000000000..03f9c56b1 --- /dev/null +++ b/lab8/lib/sched.S @@ -0,0 +1,24 @@ +#include "sched.h" + +.global cpu_switch_to +cpu_switch_to: + mov x10, #THREAD_CPU_CONTEXT + add x8, x0, x10 + mov x9, sp + stp x19, x20, [x8], #16 // store callee-saved registers + stp x21, x22, [x8], #16 + stp x23, x24, [x8], #16 + stp x25, x26, [x8], #16 + stp x27, x28, [x8], #16 + stp x29, x9, [x8], #16 + str x30, [x8] + add x8, x1, x10 + ldp x19, x20, [x8], #16 // restore callee-saved registers + ldp x21, x22, [x8], #16 + ldp x23, x24, [x8], #16 + ldp x25, x26, [x8], #16 + ldp x27, x28, [x8], #16 + ldp x29, x9, [x8], #16 + ldr x30, [x8] + mov sp, x9 + ret diff --git a/lab8/lib/sched.c b/lab8/lib/sched.c new file mode 100644 index 000000000..2ffc32acd --- /dev/null +++ b/lab8/lib/sched.c @@ -0,0 +1,107 @@ +#include "../include/sched.h" +#include "mm.h" +#include "exception.h" +#include "mini_uart.h" + +static struct task_struct init_task = INIT_TASK; +struct task_struct *current = &(init_task); +struct task_struct *task[NR_TASKS] = {&(init_task), }; +int nr_tasks = 1; + +void preempt_disable() { + current->preempt_count++; +} + +void preempt_enable() { + current->preempt_count--; +} + +void _schedule() { + + int next, c; + struct task_struct *p; + while (1) { + c = -1; + next = 0; + for (int i=0; istate == TASK_RUNNING && p->counter > c) { + c = p->counter; + next = i; + } + } + if (c) { + break; + } + for (int i=0; icounter = (p->counter >> 1) + p->priority; + } + } + preempt_disable(); // should be fine, if anything breaks move this to the top + switch_to(task[next]); + preempt_enable(); + +} + +void schedule() { + current->counter = 0; + _schedule(); +} + +void switch_to(struct task_struct *next) { + if (current == next) + return; + struct task_struct *prev = current; + current = next; + cpu_switch_to(prev, next); +} + +void schedule_tail() { + preempt_enable(); +} + +void timer_tick() { + + --current->counter; + if (current->counter > 0 || current->preempt_count > 0) + return; + + current->counter = 0; + enable_interrupt(); + _schedule(); + disable_interrupt(); + +} + +void exit_process() { + // should only be accessed using syscall + // preempt_disable(); + current->state = TASK_ZOMBIE; + free((void*)current->stack); + // preempt_enable(); + schedule(); +} + +void kill_zombies() { + + struct task_struct *p; + for (int i=0; istate == TASK_ZOMBIE) { + printf("Zombie found with pid: %d.\n", p->id); + free(p); + task[i] = NULL; + } + + } + +} \ No newline at end of file diff --git a/lab8/lib/shell.c b/lab8/lib/shell.c new file mode 100644 index 000000000..5adf9634c --- /dev/null +++ b/lab8/lib/shell.c @@ -0,0 +1,200 @@ +#include "shell.h" +#include "mini_uart.h" +#include "utils.h" +#include "mailbox.h" +#include "reboot.h" +#include "string.h" +#include "../include/cpio.h" +#include "memory.h" +#include "timer.h" +#include "exception.h" +#include "math.h" +#include "mm.h" +#include "../include/sched.h" +#include "syscall.h" +#include "peripherals/mailbox.h" +#include "fork.h" + + +#define MAX_BUFFER_SIZE 256u + +static char buffer[MAX_BUFFER_SIZE]; + +void foo() { + for(int i = 0; i < 10; ++i) { + printf("Thread id: %d %d\n", current->id, i); + delay(1000000); + schedule(); + } + + current->state = TASK_ZOMBIE; + while(1); +} + +void user_foo() { + + printf("User thread id: %d\n", getpid()); + char *msg = "hello world\n"; + int fd; + char buf[15]; + buf[14] = '\0'; + + fd = open("/initramfs/msg", 0); + read(fd, buf, 13); + close(fd); + + printf("%s", buf); + + exit(0); + +} + +void start_video() { + // ... go to cpio, find location and size of syscall.img + // allocate pages + // move syscall.img to allocated memory + // preempt disable + // change this shell thread to not runnable + // start sycall.img user process + // preempt enable + struct cpio_newc_header *header; + unsigned int filesize; + unsigned int namesize; + unsigned int offset; + char *filename; + void *code_loc; + + header = DEVTREE_CPIO_BASE; + while (1) { + + filename = ((void*)header) + sizeof(struct cpio_newc_header); + + if (stringncmp((char*)header, CPIO_HEADER_MAGIC, 6) != 0) { + uart_send_string("invalid magic\n"); + break; + } + if (stringncmp(filename, CPIO_FOOTER_MAGIC, 11) == 0) { + uart_send_string("file does not exist!\n"); + break; + } + + namesize = hexstr_to_uint(header->c_namesize, 8); + + offset = sizeof(struct cpio_newc_header) + namesize; + if (offset % 4 != 0) + offset = ((offset/4) + 1) * 4; + + filesize = hexstr_to_uint(header->c_filesize, 8); + + if (stringncmp(filename, "vfs1.img", namesize) == 0) { + code_loc = ((void*)header) + offset; + break; + } + + if (filesize % 4 != 0) + filesize = ((filesize/4) + 1) * 4; + + offset = offset + filesize; + + header = ((void*)header) + offset; + + } + printf("vfs1.img found in cpio at location 0x%x.\n", code_loc); + printf("vfs1.img has size of %d bytes.\n", (int)filesize); + + void *move_loc = malloc(filesize + 4096); // an extra page for bss just in case + if(move_loc == NULL) return; + for (int i=0; istate = TASK_STOPPED; + unsigned long long tmp; + asm volatile("mrs %0, cntkctl_el1" : "=r"(tmp)); + tmp |= 1; + asm volatile("msr cntkctl_el1, %0" : : "r"(tmp)); + copy_process(PF_KTHREAD, (unsigned long)&new_user_process, (unsigned long)move_loc, 0); + preempt_enable(); + +} + +void read_cmd() +{ + unsigned int idx = 0; + char c = '\0'; + + while (1) { + c = uart_recv(); + if (c == '\r' || c == '\n') { + uart_send_string("\n"); + + if (idx < MAX_BUFFER_SIZE) buffer[idx] = '\0'; + else buffer[MAX_BUFFER_SIZE-1] = '\0'; + + break; + } else { + uart_send(c); + buffer[idx++] = c; + } + } + +} + +void parse_cmd() +{ + + if (stringcmp(buffer, "\0") == 0) + uart_send_string("\n"); + else if (stringcmp(buffer, "hello") == 0) + uart_send_string("Hello World!\n"); + else if (stringcmp(buffer, "reboot") == 0) { + uart_send_string("rebooting...\n"); + reset(100); + } + else if (stringcmp(buffer, "hwinfo") == 0) { + get_board_revision(); + get_arm_memory(); + } + else if (stringcmp(buffer, "ls") == 0) { + cpio_ls(); + } + else if (stringcmp(buffer, "cat") == 0) { + cpio_cat(); + } + else if (stringcmp(buffer, "execute") == 0) { + cpio_exec(); + } + else if (stringcmp(buffer, "thread_test") == 0) { + for (int i=0; i<10; i++) { + copy_process(PF_KTHREAD, (unsigned long)&foo, 0, 0); + } + } + else if (stringcmp(buffer, "to_user") == 0) { + copy_process(PF_KTHREAD, (unsigned long)&new_user_process, (unsigned long)&user_foo, 0); + } + else if (stringcmp(buffer, "video") == 0) { + start_video(); + } + else if (stringcmp(buffer, "help") == 0) { + uart_send_string("help:\t\tprint list of available commands\n"); + uart_send_string("hello:\t\tprint Hello World!\n"); + uart_send_string("reboot:\t\treboot device\n"); + uart_send_string("hwinfo:\t\tprint hardware information\n"); + uart_send_string("ls:\t\tlist initramfs files\n"); + uart_send_string("cat:\t\tprint file content in initramfs\n"); + uart_send_string("execute:\trun program from cpio\n"); + } + else + uart_send_string("Command not found! Type help for commands.\n"); + +} + +void shell_loop() +{ + while (1) { + uart_send_string("% "); + read_cmd(); + parse_cmd(); + } +} \ No newline at end of file diff --git a/lab8/lib/string.c b/lab8/lib/string.c new file mode 100644 index 000000000..f74819f4d --- /dev/null +++ b/lab8/lib/string.c @@ -0,0 +1,55 @@ +#include "string.h" + +int stringcmp(const char *p1, const char *p2) +{ + const unsigned char *s1 = (const unsigned char *) p1; + const unsigned char *s2 = (const unsigned char *) p2; + unsigned char c1, c2; + + do { + c1 = (unsigned char) *s1++; + c2 = (unsigned char) *s2++; + if (c1 == '\0') + return c1 - c2; + } while (c1 == c2); + + return c1 - c2; +} + +int stringncmp(const char *p1, const char *p2, unsigned int n) +{ + for (int i=0; iid; +} + +unsigned sys_uartread(char buf[], unsigned size) { + for(unsigned int i=0; ic_namesize, 8); + + offset = sizeof(struct cpio_newc_header) + namesize; + if (offset % 4 != 0) + offset = ((offset/4) + 1) * 4; + + filesize = hexstr_to_uint(header->c_filesize, 8); + + if (stringncmp(filename, name, namesize) == 0) { + code_loc = ((void*)header) + offset; + break; + } + + if (filesize % 4 != 0) + filesize = ((filesize/4) + 1) * 4; + + offset = offset + filesize; + + header = ((void*)header) + offset; + + } + + void *move_loc = malloc(filesize + 4096); // an extra page for bss just in case + if(move_loc == NULL) return -1; + for (int i=0; ipc = (unsigned long)move_loc; // move to beginning of program + p->sp = current->stack+PAGE_SIZE; + + preempt_enable(); + + return -1; // only on failure +} + +int sys_fork() { + return copy_process(0, 0, 0, (unsigned long)malloc(4*4096)); +} + +void sys_exit(int status) { + exit_process(); +} + +int sys_mbox_call(unsigned char ch, unsigned int *mbox) { + unsigned int r = (((unsigned int)((unsigned long)mbox)&~0xF) | (ch&0xF)); + while(*MAILBOX_STATUS & MAILBOX_FULL); + *MAILBOX_WRITE = r; + while (1) { + while (*MAILBOX_STATUS & MAILBOX_EMPTY) {} + if (r == *MAILBOX_READ) { + return mbox[1]==REQUEST_SUCCEED; + } + } + return 0; +} + +void sys_kill(int pid) { + + struct task_struct *p; + for (int i=0; iid == (long)pid) { + preempt_disable(); + printf("Kill target acquired.\n"); + p->state = TASK_ZOMBIE; + free((void *)p->stack); + preempt_enable(); + break; + } + + } + +} + +int sys_open(const char *pathname, int flags) { + printf("[debug] start of syscall open: %s %d\n", pathname, flags); + struct file *f; + int res = vfs_open(pathname, flags, &f); + if (res < 0) return -1; + + int fd_num = current->files.count; + + if (fd_num >= 16) { + for (int i=0; i<16; i++) { + if (current->files.fds[i] == 0) fd_num = i; + } + } + + if (fd_num >= 16) return -1; + + current->files.fds[fd_num] = f; + current->files.count++; + + return fd_num; +} + +int sys_close(int fd) { + printf("[debug] start of syscall close with fd %d\n", fd); + if (fd < 0) return -1; + + struct file *f = current->files.fds[fd]; + current->files.fds[fd] = 0; + + return vfs_close(f); +} + +long sys_write(int fd, const void *buf, unsigned long count) { + printf("[debug] start of syscall write with fd %d\n", fd); + + if (fd < 0) return -1; + + struct file *f = current->files.fds[fd]; + if (f == 0) return 0; + + return vfs_write(f, buf, count); +} + +long sys_read(int fd, void *buf, unsigned long count) { + printf("[debug] start of syscall read with fd %d\n", fd); + + if (fd < 0) return -1; + + struct file *f = current->files.fds[fd]; + if (f == 0) return 0; + + return vfs_read(f, buf, count); +} + +int sys_mkdir(const char *pathname, unsigned mode) { + printf("[debug] start of syscall mkdir\n"); + + return vfs_mkdir(pathname); +} + +int sys_mount(const char *src, const char *target, const char *fs, unsigned long flags, const void *data) { + printf("[debug] start of syscall mount\n"); + return vfs_mount(target, fs); +} + +int sys_chdir(const char *path) { + printf("[debug] start of syscall chdir: %s\n", path); + + return vfs_chdir(path); +} + +void * const sys_call_table[] = +{ + sys_getpid, + sys_uartread, + sys_uartwrite, + sys_exec, + sys_fork, + sys_exit, + sys_mbox_call, + sys_kill, + 0, + 0, + 0, + sys_open, + sys_close, + sys_write, + sys_read, + sys_mkdir, + sys_mount, + sys_chdir +}; \ No newline at end of file diff --git a/lab8/lib/timer.c b/lab8/lib/timer.c new file mode 100644 index 000000000..ae102a2b0 --- /dev/null +++ b/lab8/lib/timer.c @@ -0,0 +1,62 @@ +#include "timer.h" +#include "../include/sched.h" +#include "mini_uart.h" + +void timer_init() { + core_timer_enable(); + set_timer(read_freq()); +} + +void handle_timer_irq() { + //printf("Timer interrupt.\n"); + set_timer(read_freq()>>5); + timer_tick(); +} + +void core_timer_enable() { + + asm volatile( + "mov x0, 1\n\t" + "msr cntp_ctl_el0, x0\n\t" // enable + "mov x0, 2\n\t" + "ldr x1, =0x40000040\n\t" // CORE0_TIMER_IRQ_CTRL + "str w0, [x1]\n\t" // unmask timer interrupt + ); + +} + +void core_timer_disable() { + + asm volatile( + "mov x0, 0\n\t" + "ldr x1, =0x40000040\n\t" + "str w0, [x1]\n\t" + ); + +} + +void set_timer(unsigned int rel_time) { + + asm volatile( + "msr cntp_tval_el0, %0\n\t" + : + : "r" (rel_time) + ); + +} + +unsigned int read_timer() { + + unsigned int time; + asm volatile("mrs %0, cntpct_el0\n\t" : "=r" (time) : : "memory"); + return time; + +} + +unsigned int read_freq() { + + unsigned int freq; + asm volatile("mrs %0, cntfrq_el0\n\t": "=r" (freq) : : "memory"); + return freq; + +} diff --git a/lab8/lib/tmpfs.c b/lab8/lib/tmpfs.c new file mode 100644 index 000000000..b1f51196e --- /dev/null +++ b/lab8/lib/tmpfs.c @@ -0,0 +1,154 @@ +#include "tmpfs.h" +#include "mm.h" +#include "string.h" +#include "mini_uart.h" + + +struct vnode_operations *tmpfs_v_ops; +struct file_operations *tmpfs_f_ops; +int tmpfs_registered = 0; + +int tmpfs_setup_mount(struct filesystem* fs, struct mount* mount) { + + mount->fs = fs; + mount->root = tmpfs_new_node(NULL, "/", DIRECTORY); + + printf("[debug] setup root: 0x%x\n", mount); + printf("[debug] setup root vnode: 0x%x\n", mount->root); + + return 0; +} + +int tmpfs_register() { + + if (tmpfs_registered) return -1; + tmpfs_registered = 1; + + tmpfs_v_ops = (struct vnode_operations *) chunk_alloc(sizeof(struct vnode_operations)); + tmpfs_f_ops = (struct file_operations *) chunk_alloc(sizeof(struct file_operations)); + + tmpfs_v_ops->lookup = tmpfs_lookup; + tmpfs_v_ops->create = tmpfs_create; + tmpfs_v_ops->mkdir = tmpfs_mkdir; + + tmpfs_f_ops->open = tmpfs_open; + tmpfs_f_ops->read = tmpfs_read; + tmpfs_f_ops->write = tmpfs_write; + tmpfs_f_ops->close = tmpfs_close; + + return 0; +} + +struct vnode* tmpfs_new_node(struct tmpfs_internal *parent, const char *name, int type) { + + struct vnode *new_node = (struct vnode *)chunk_alloc(sizeof(struct vnode)); + struct tmpfs_internal *new_internal = (struct tmpfs_internal *)chunk_alloc(sizeof(struct tmpfs_internal)); + + strcpy(new_internal->name, name); + new_internal->type = type; + new_internal->parent = parent; + new_internal->vnode = new_node; + new_internal->size = 0; + if (type == REGULAR_FILE) + new_internal->data = malloc(MAX_FILESIZE); + else + new_internal->data = 0; + + if (parent != NULL) + new_node->parent = parent->vnode; + new_node->f_ops = tmpfs_f_ops; + new_node->v_ops = tmpfs_v_ops; + new_node->mount = 0; + new_node->internal = (void *)new_internal; + + return new_node; + +} + +int tmpfs_open(struct vnode* file_node, struct file** target) { + return SUCCESS; +} + +int tmpfs_close(struct file *file) { + if (file) + return SUCCESS; + else + return FAIL; +} + +int tmpfs_write(struct file *file, const void *buf, unsigned len) { + struct tmpfs_internal *internal = (struct tmpfs_internal*)file->vnode->internal; + if (((struct tmpfs_internal *)file->vnode->internal)->type != REGULAR_FILE) + return FAIL; + + char *dest = &((char *)internal->data)[file->f_pos]; + char *src = (char *)buf; + int i = 0; + for (; i < len && internal->size+i < MAX_FILESIZE; i++) { + dest[i] = src[i]; + } + + internal->size += i; + + return i; +} + +int tmpfs_read(struct file *file, void *buf, unsigned len) { + + struct tmpfs_internal *internal = (struct tmpfs_internal*)file->vnode->internal; + if (internal->type != REGULAR_FILE) + return FAIL; + + char *dest = (char*)buf; + char *src = &((char *)internal->data)[file->f_pos]; + int i = 0; + for (; isize; i++) { + dest[i] = src[i]; + } + + return i; +} + +int tmpfs_create(struct vnode* dir_node, struct vnode** target, const char* component_name) { + + struct tmpfs_internal *parent_internal = (struct tmpfs_internal *)dir_node->internal; + struct vnode *new_node = tmpfs_new_node(parent_internal, component_name, REGULAR_FILE); + + parent_internal->child[parent_internal->size] = (struct tmpfs_internal *)new_node->internal; + parent_internal->size++; + + *target = new_node; + return SUCCESS; +} + +int tmpfs_mkdir(struct vnode* dir_node, struct vnode** target, const char* component_name) { + + struct tmpfs_internal *parent_internal = (struct tmpfs_internal *)dir_node->internal; + struct vnode *new_node = tmpfs_new_node(parent_internal, component_name, DIRECTORY); + + parent_internal->child[parent_internal->size] = (struct tmpfs_internal *)new_node->internal; + parent_internal->size++; + + *target = new_node; + return SUCCESS; +} + +int tmpfs_lookup(struct vnode* dir_node, struct vnode** target, const char* component_name) { + + if (stringcmp(component_name, "") == 0) { + *target = dir_node; + return 0; + } + + struct tmpfs_internal *internal = (struct tmpfs_internal *)dir_node->internal; + + for (int i=0; isize; i++) { + if(stringcmp(internal->child[i]->name, component_name) == 0) { + *target = internal->child[i]->vnode; + return internal->child[i]->type; + } + } + + return FAIL; + +} diff --git a/lab8/lib/utils.S b/lab8/lib/utils.S new file mode 100644 index 000000000..aa0e55aa9 --- /dev/null +++ b/lab8/lib/utils.S @@ -0,0 +1,15 @@ +.globl put32 +put32: + str w1,[x0] + ret + +.globl get32 +get32: + ldr w0,[x0] + ret + +.globl delay +delay: + subs x0, x0, #1 + bne delay + ret \ No newline at end of file diff --git a/lab8/lib/vfs.c b/lab8/lib/vfs.c new file mode 100644 index 000000000..0492dfc26 --- /dev/null +++ b/lab8/lib/vfs.c @@ -0,0 +1,197 @@ +#include "vfs.h" +#include "tmpfs.h" +#include "initramfs.h" +#include "string.h" +#include "mm.h" +#include "sched.h" +#include "mini_uart.h" + +struct mount *rootfs; + +void rootfs_init() { + + struct filesystem *tmpfs = (struct filesystem *)chunk_alloc(sizeof(struct filesystem)); + tmpfs->name = (char *)chunk_alloc(16); + strcpy(tmpfs->name, "tmpfs"); + tmpfs->setup_mount = tmpfs_setup_mount; + register_fs(tmpfs); + + rootfs = (struct mount *)chunk_alloc(sizeof(struct mount)); + tmpfs->setup_mount(tmpfs, rootfs); + +} + +void initramfs_init() { + + vfs_mkdir("/initramfs"); + vfs_mount("/initramfs", "initramfs"); + parse_initramfs(); + +} + +int register_fs(struct filesystem *fs) { + if (stringcmp(fs->name, "tmpfs") == 0) { + return tmpfs_register(); + } else if (stringcmp(fs->name, "initramfs") == 0) { + return initramfs_register(); + } + return -1; +} + +struct file* create_fd(struct vnode* target, int flags) { + struct file* fd = (struct file*)chunk_alloc(sizeof(struct file)); + fd->f_ops = target->f_ops; + fd->vnode = target; + fd->f_pos = 0; + fd->flags = flags; + return fd; +} + +int vfs_open(const char *pathname, int flags, struct file **target) { + + *target = 0; + struct vnode *target_dir; + char target_path[VFS_PATHMAX]; + traverse(pathname, &target_dir, target_path); + + struct vnode *target_file; + if (target_dir->v_ops->lookup(target_dir, &target_file, target_path) == REGULAR_FILE) { + + *target = create_fd(target_file, flags); + return (*target)->f_ops->open(target_file, target); + + } else if (flags & O_CREAT) { + + int res = target_dir->v_ops->create(target_dir, &target_file, target_path); + if (res < 0) return FAIL; + *target = create_fd(target_file, flags); + return (*target)->f_ops->open(target_file, target); + + } else return FAIL; + +} + +int vfs_close(struct file *file) { + int code = file->f_ops->close(file); + if (code == SUCCESS) + chunk_free(file); + return code; +} + +int vfs_write(struct file *file, const void *buf, unsigned len) { + return file->f_ops->write(file, buf, len); +} + +int vfs_read(struct file *file, void *buf, unsigned len) { + return file->f_ops->read(file, buf, len); +} + +int vfs_mkdir(const char *pathname) { + struct vnode *target_dir; + char child_name[VFS_PATHMAX]; + traverse(pathname, &target_dir, child_name); + struct vnode *child_dir; + int res = target_dir->v_ops->mkdir(target_dir, &child_dir, child_name); + if (res < 0) return res; + return SUCCESS; +} + +int vfs_mount(const char *target, const char *filesystem) { + + struct vnode *mount_dir; + char path_remain[VFS_PATHMAX]; + + traverse(target, &mount_dir, path_remain); + + struct mount *mt = (struct mount *)chunk_alloc(sizeof(struct mount)); + if (stringcmp(filesystem, "tmpfs") == 0) { + + struct filesystem *tmpfs = (struct filesystem *)chunk_alloc(sizeof(struct filesystem)); + tmpfs->name = (char *)chunk_alloc(16); + strcpy(tmpfs->name, "tmpfs"); + tmpfs->setup_mount = tmpfs_setup_mount; + register_fs(tmpfs); + tmpfs->setup_mount(tmpfs, mt); + mount_dir->mount = mt; + mt->root->parent = mount_dir->parent; + + } else if (stringcmp(filesystem, "initramfs") == 0) { + + struct filesystem *initramfs = (struct filesystem *)chunk_alloc(sizeof(struct filesystem)); + initramfs->name = (char *)chunk_alloc(16); + strcpy(initramfs->name, "initramfs"); + initramfs->setup_mount = initramfs_setup_mount; + register_fs(initramfs); + initramfs->setup_mount(initramfs, mt); + mount_dir->mount = mt; + mt->root->parent = mount_dir->parent; + + } + + return SUCCESS; +} + +int vfs_lookup(const char *pathname, struct vnode **target) { + return SUCCESS; +} + +int vfs_chdir(const char *pathname) { + struct vnode *target_dir; + char path_remain[VFS_PATHMAX]; + traverse(pathname, &target_dir, path_remain); + if (stringcmp(path_remain, "") != 0) { + return FAIL; + } else { + + current->cwd = target_dir; + return SUCCESS; + } +} + +void traverse(const char* pathname, struct vnode **target_node, char *target_path) { + if (pathname[0] == '/') { + printf("[debug] traverse absolute path: %s\n", pathname); + struct vnode *rootnode = rootfs->root; + r_traverse(rootnode, pathname + 1, target_node, target_path); + } else { + printf("[debug] traverse relative path: %s\n", pathname); + struct vnode *rootnode = current->cwd; + r_traverse(rootnode, pathname, target_node, target_path); + } +} + +void r_traverse(struct vnode *node, const char *path, struct vnode **target_node, char *target_path) { + + int i = 0; + while (path[i]) { + if (path[i] == '/') break; + target_path[i] = path[i]; + i++; + } + target_path[i++] = '\0'; + *target_node = node; + + if (stringcmp(target_path, "") == 0) { + return; + } + else if (stringcmp(target_path, ".") == 0) { + r_traverse(node, path + i, target_node, target_path); + return; + } + else if (stringcmp(target_path, "..") == 0) { + if (node->parent == NULL) return; + r_traverse(node->parent, path + i, target_node, target_path); + return; + } + + int res = node->v_ops->lookup(node, target_node, target_path); + if ((*target_node)->mount != NULL) { + printf("[debug] mountpoint found during lookup: vnode 0x%x\n", (*target_node)->mount->root); + r_traverse((*target_node)->mount->root, path+i, target_node, target_path); + } + else if (res == DIRECTORY) + r_traverse(*target_node, path+i, target_node, target_path); + else if (res == REGULAR_FILE) + *target_node = node; + +} diff --git a/lab8/rootfs/msg b/lab8/rootfs/msg new file mode 100644 index 000000000..a04238969 --- /dev/null +++ b/lab8/rootfs/msg @@ -0,0 +1 @@ +hello world! diff --git a/lab8/send_img.py b/lab8/send_img.py new file mode 100644 index 000000000..b2261950d --- /dev/null +++ b/lab8/send_img.py @@ -0,0 +1,31 @@ +import argparse +import os +import time +import math +import serial + +parser = argparse.ArgumentParser(description='*.img uart sender') +parser.add_argument('-i', '--img', default='kernel8.img', type=str) +parser.add_argument('-d', '--device', default='/dev/ttyUSB0', type=str) +parser.add_argument('-b', '--baud', default=115200, type=int) + +args = parser.parse_args() + +img_size = os.path.getsize(args.img) + +with open(args.img, 'rb') as f: + with serial.Serial(args.device, args.baud) as tty: + + print(f'{args.img} is {img_size} bytes') + print('img file is now sending') + + tty.write(img_size.to_bytes(4, 'big')) + + input() + + for i in range(img_size): + tty.write(f.read(1)) + tty.flush() + #time.sleep(0.0001) + + print('img sent') From 8261fcf67abd5786a826a63d82a77b259c59c296 Mon Sep 17 00:00:00 2001 From: Hsiang-Chieh Tsou Date: Tue, 21 Jun 2022 21:58:44 +0800 Subject: [PATCH 9/9] lab8 basic --- .vscode/settings.json | 4 +- lab8/config.txt | 5 +- lab8/include/fat32.h | 106 ++++++ lab8/include/mbr.h | 15 + lab8/include/sdhost.h | 81 +++++ lab8/include/vfs.h | 2 + lab8/{initramfs.cpio => initrmfs.cpo} | Bin 404992 -> 405504 bytes lab8/kernel/kernel.c | 3 + lab8/lib/fat32.c | 377 +++++++++++++++++++++ lab8/lib/initramfs.c | 4 +- lab8/lib/sdhost.c | 172 ++++++++++ lab8/lib/shell.c | 6 +- lab8/lib/tmpfs.c | 1 + lab8/lib/vfs.c | 30 +- lab8/rootfs/msg | 1 - lab8/{bcm2710-rpi-3-b-plus.dtb => sfn.dtb} | Bin 16 files changed, 796 insertions(+), 11 deletions(-) create mode 100644 lab8/include/fat32.h create mode 100644 lab8/include/mbr.h create mode 100644 lab8/include/sdhost.h rename lab8/{initramfs.cpio => initrmfs.cpo} (98%) create mode 100644 lab8/lib/fat32.c create mode 100644 lab8/lib/sdhost.c delete mode 100644 lab8/rootfs/msg rename lab8/{bcm2710-rpi-3-b-plus.dtb => sfn.dtb} (100%) diff --git a/.vscode/settings.json b/.vscode/settings.json index 31c221e68..8494957d4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,8 @@ { "files.associations": { "fork.h": "c", - "string.h": "c" + "string.h": "c", + "mbr.h": "c", + "vfs.h": "c" } } \ No newline at end of file diff --git a/lab8/config.txt b/lab8/config.txt index 49fc25695..86adbbb88 100644 --- a/lab8/config.txt +++ b/lab8/config.txt @@ -1,3 +1,4 @@ -kernel=bootloader.img +kernel=btl.img arm_64bit=1 -initramfs initramfs.cpio 0x20000000 +initramfs initrmfs.cpo 0x20000000 +device_tree=sfn.dtb diff --git a/lab8/include/fat32.h b/lab8/include/fat32.h new file mode 100644 index 000000000..443c5080e --- /dev/null +++ b/lab8/include/fat32.h @@ -0,0 +1,106 @@ +#ifndef _FAT32_H +#define _FAT32_H + +#include "vfs.h" + +#define BLOCK_SIZE 512 +#define FAT_ENTRY_PER_BLOCK (BLOCK_SIZE / sizeof(int)) +#define EOC 0xFFFFFFF + +#define FAT_MAX_DIRENT 16 +#define SFN_COMP_LEN 13 + +typedef unsigned char uint8_t; +typedef unsigned short int uint16_t; +typedef unsigned int uint32_t; + +struct fat32_boot_sector { + char jmp[3]; + char oem[8]; + + // BIOS Parameter Block + uint16_t bytes_per_logical_sector; // 0xB-0xC + uint8_t logical_sector_per_cluster; // 0xD + uint16_t n_reserved_sectors; // 0xE-0xF + uint8_t n_file_alloc_tabs; // 0x10 + uint16_t n_max_root_dir_entries_16; // 0x11-0x12 + uint16_t n_logical_sectors_16; // 0x13-0x14 + uint8_t media_descriptor; // 0x15 + uint16_t logical_sector_per_fat_16; // 0x16-0x17 + + // DOS3.31 BPB + uint16_t physical_sector_per_track; // 0x18-0x19 + uint16_t n_heads; // 0x1A-0x1B + uint32_t n_hidden_sectors; // 0x1C-0x1F + uint32_t n_sectors_32; // 0x20-0x23 + + // FAT32 Extended BIOS Parameter Block + uint32_t n_sectors_per_fat_32; // 0x24-0x27 + uint16_t mirror_flag; // 0x28-0x29 + uint16_t version; // 0x2A-0x2B + uint32_t root_dir_start_cluster_num; // 0x2C-0x2F + uint16_t fs_info_sector_num; // 0x30-0x31 + uint16_t boot_sector_bak_first_sector_num; // 0x32-0x33 + uint32_t reserved[3]; // 0x34-0x3F + uint8_t physical_drive_num; // 0x40 + uint8_t unused; // 0x41 + uint8_t extended_boot_signature; // 0x42 + uint32_t volume_id; // 0x43-0x46 + uint8_t volume_label[11]; // 0x47-0x51 + uint8_t fat_system_type[8]; // 0x52-0x59 + +} __attribute__((packed)); + +struct fat32_dirent { + uint8_t name[8]; // 0x0-0x7 + uint8_t ext[3]; // 0x8-0xA + uint8_t attr; // 0xB + uint8_t reserved; // 0xC + uint8_t create_time[3]; // 0xD-0xF + uint16_t create_date; // 0x10-0x11 + uint16_t last_access_date; // 0x12-0x13 + uint16_t cluster_high; // 0x14-0x15 + uint32_t ext_attr; // 0x16-0x19 + uint16_t cluster_low; // 0x1A-0x1B + uint32_t size; // 0x1C-0x1F +} __attribute__((packed)); + +struct fat32_metadata { + uint32_t fat_region_blk_idx; + uint32_t n_fat; + uint32_t sector_per_fat; + uint32_t data_region_blk_idx; + uint32_t first_cluster; + uint8_t sector_per_cluster; +}; + +struct fat32_internal { + char name[SFN_COMP_LEN]; + int type; + struct fat32_internal *parent; + struct fat32_internal *child[FAT_MAX_DIRENT]; + struct vnode *vnode; + //int size; // use for child count and data size + //void *data; + uint32_t first_cluster; + uint32_t dirent_cluster; + uint32_t size; +}; + +int fat32_setup_mount(struct filesystem* fs, struct mount* mount); +int fat32_register(); + +struct vnode* fat32_new_node(struct fat32_internal *parent, const char *name, int type); + +int fat32_open(struct vnode *file_node, struct file **target); +int fat32_close(struct file *file); +int fat32_write(struct file *file, const void *buf, unsigned len); +int fat32_read(struct file *file, void *buf, unsigned len); +int fat32_create(struct vnode *dir_node, struct vnode **target, const char *component_name); +int fat32_mkdir(struct vnode *dir_node, struct vnode **target, const char *component_name); +int fat32_lookup(struct vnode *dir_node, struct vnode **target, const char *component_name); +int fat32_load_vnode(struct vnode *dir_node, char *component_name); + +void parse_fat32sd(); + +#endif \ No newline at end of file diff --git a/lab8/include/mbr.h b/lab8/include/mbr.h new file mode 100644 index 000000000..5b9e0cd69 --- /dev/null +++ b/lab8/include/mbr.h @@ -0,0 +1,15 @@ +#ifndef _MBR_H +#define _MBR_H + +struct mbr_partition_tb { + unsigned char status_flag; //0x0 + unsigned char partition_begin_head; //0x1 + unsigned short partition_begin_sector; //0x2-0x3 + unsigned char partition_type; //0x4 + unsigned char partition_end_head; //0x5 + unsigned short partition_end_sector; //0x6-0x7 + unsigned int starting_sector; //0x8-0xB + unsigned int number_of_sector; //0xC-0xF +}; + +#endif \ No newline at end of file diff --git a/lab8/include/sdhost.h b/lab8/include/sdhost.h new file mode 100644 index 000000000..6273636a5 --- /dev/null +++ b/lab8/include/sdhost.h @@ -0,0 +1,81 @@ +#ifndef _SDHOST_H +#define _SDHOST_H + +// mmio +#define KVA 0x0000000000000000 // vmem not used +#define MMIO_BASE (KVA + 0x3f000000) + +// SD card command +#define GO_IDLE_STATE 0 +#define SEND_OP_CMD 1 +#define ALL_SEND_CID 2 +#define SEND_RELATIVE_ADDR 3 +#define SELECT_CARD 7 +#define SEND_IF_COND 8 +#define VOLTAGE_CHECK_PATTERN 0x1aa +#define STOP_TRANSMISSION 12 +#define SET_BLOCKLEN 16 +#define READ_SINGLE_BLOCK 17 +#define WRITE_SINGLE_BLOCK 24 +#define SD_APP_OP_COND 41 +#define SDCARD_3_3V (1 << 21) +#define SDCARD_ISHCS (1 << 30) +#define SDCARD_READY (1 << 31) +#define APP_CMD 55 + +// gpio +#define GPIO_BASE (MMIO_BASE + 0x200000) +#define GPIO_GPFSEL4 (GPIO_BASE + 0x10) +#define GPIO_GPFSEL5 (GPIO_BASE + 0x14) +#define GPIO_GPPUD (GPIO_BASE + 0x94) +#define GPIO_GPPUDCLK1 (GPIO_BASE + 0x9c) + +// sdhost +#define SDHOST_BASE (MMIO_BASE + 0x202000) +#define SDHOST_CMD (SDHOST_BASE + 0) +#define SDHOST_READ 0x40 +#define SDHOST_WRITE 0x80 +#define SDHOST_LONG_RESPONSE 0x200 +#define SDHOST_NO_REPONSE 0x400 +#define SDHOST_BUSY 0x800 +#define SDHOST_NEW_CMD 0x8000 +#define SDHOST_ARG (SDHOST_BASE + 0x4) +#define SDHOST_TOUT (SDHOST_BASE + 0x8) +#define SDHOST_TOUT_DEFAULT 0xf00000 +#define SDHOST_CDIV (SDHOST_BASE + 0xc) +#define SDHOST_CDIV_MAXDIV 0x7ff +#define SDHOST_CDIV_DEFAULT 0x148 +#define SDHOST_RESP0 (SDHOST_BASE + 0x10) +#define SDHOST_RESP1 (SDHOST_BASE + 0x14) +#define SDHOST_RESP2 (SDHOST_BASE + 0x18) +#define SDHOST_RESP3 (SDHOST_BASE + 0x1c) +#define SDHOST_HSTS (SDHOST_BASE + 0x20) +#define SDHOST_HSTS_MASK (0x7f8) +#define SDHOST_HSTS_ERR_MASK (0xf8) +#define SDHOST_HSTS_DATA (1 << 0) +#define SDHOST_PWR (SDHOST_BASE + 0x30) +#define SDHOST_DBG (SDHOST_BASE + 0x34) +#define SDHOST_DBG_FSM_DATA 1 +#define SDHOST_DBG_FSM_MASK 0xf +#define SDHOST_DBG_MASK (0x1f << 14 | 0x1f << 9) +#define SDHOST_DBG_FIFO (0x4 << 14 | 0x4 << 9) +#define SDHOST_CFG (SDHOST_BASE + 0x38) +#define SDHOST_CFG_DATA_EN (1 << 4) +#define SDHOST_CFG_SLOW (1 << 3) +#define SDHOST_CFG_INTBUS (1 << 1) +#define SDHOST_SIZE (SDHOST_BASE + 0x3c) +#define SDHOST_DATA (SDHOST_BASE + 0x40) +#define SDHOST_CNT (SDHOST_BASE + 0x50) + +// helper +#define set(io_addr, val) \ + asm volatile("str %w1, [%0]" ::"r"(io_addr), "r"(val) : "memory"); + +#define get(io_addr, val) \ + asm volatile("ldr %w0, [%1]" : "=r"(val) : "r"(io_addr) : "memory"); + +void sd_init(); +void readblock(int block_idx, void* buf); +void writeblock(int block_idx, const void* buf); + +#endif \ No newline at end of file diff --git a/lab8/include/vfs.h b/lab8/include/vfs.h index d6b59781a..cfa9d6c2d 100644 --- a/lab8/include/vfs.h +++ b/lab8/include/vfs.h @@ -41,6 +41,7 @@ struct vnode_operations { const char* component_name); int (*mkdir)(struct vnode* dir_node, struct vnode** target, const char* component_name); + int (*load_vnode)(struct vnode* dir_node, char *component_name); }; struct fd_table { @@ -62,6 +63,7 @@ extern struct mount *rootfs; void rootfs_init(); void initramfs_init(); +void fat32_init(); int register_fs(struct filesystem *fs); struct file* create_fd(struct vnode* target, int flags); diff --git a/lab8/initramfs.cpio b/lab8/initrmfs.cpo similarity index 98% rename from lab8/initramfs.cpio rename to lab8/initrmfs.cpo index 6fcebfdf9878463fe2435811037012b3c1d75bc5..cd77ea77a75a2c48d0b967ce5d91e0a2493accc4 100644 GIT binary patch delta 3373 zcmZ`*ZERFk8h+2*+hORmr3FMxEpvfrS&GbbKH6y^atD<}qi!-xWLJd_l-f4NEz;3y zQSSvm6Jy}yvThKSLH7riUAD>W#*Cj+ji@ndso+Qay3X6>g*uoD^%+tWr1gvntNJlBr1ex!5UE)R;5F9Qh~hyX7VxXq#7~K zS0J%T&lizA9?|m?t$O}DMOi+fc$SYVWO-46@HR@-f7O0J4c88N)2jq)vED5gxxKlYZl`3Ka&bk$t6$-K7+!+H*{4kX= ztIXt%^HYDbnR8Yu%%+ru>9>qoORhJY=@^!X_{=268V0+W{=%w@y1R%u#@2`yrp!+T zFP`vr7BK|N>T>?CTATa}suc-ghY#>$f3>`xk0f4&?0;D)49j5b8R>j)#xf!N& zc&E@pHcX#qmfY|Vp{JOfYf+2Pf@5)uYcU<+7C&U`O~T>`<5asrkdzZ1Kaep0wyJ$0 zgOSnXzKfXr(lgWKXE^7Evk`g+Pr$_Ti8^@-5iCE}3)r_g_9`U8 zB6mI;`N$U?)Asr9E+_Eq=ZWz6l`rWbFN}s{8 zjrlLZJ%yLc5WCL4w)Ry;4Gi)#nYlqv<9vF9wc-tACSP^coAk2*4Nl9a@MIakDD$;A z)scVrclg%<8aPoF;26$DtK;+|9J332a>vm_1*PND$sMn}gcmX`btN08J6VB@eXa!F zGrRa0tu8~W1X2^2``Xf(iSu1QdG5kpe+1Pkqzh%pQJ?AH4dKAu^;#RfT>)dS9e{jE zB;7{sfS&JVM*Mi2;CLe+JEo*t40D-ri#=6J`7I~qGQn&c)mbdm+u|k#Mmrb0NfJ!p z_!(Dp#muH<%0#zaAo?cHQf%yY+JnzwU}utI)%aSWopzQuBZmssKdWmESVd3aqTLDf zTLg~dHvGu#>T zw`I0Ai`zWzus8FDn#mVXWdhUQC6qB8hEs-ab8svjRv!G(q8VJX8%f@7Oyn8wFvdr& zL7lFSlC&V-=#Uq?1~cnd=2o?n02srvEidD5VIP!q}~h@o9QV-U;ipH z-YI&BI)P7{WnVCxXSUYy#d`fda+b*$O<>w2Ok8Z?`QwIr4s-zuNuVM88CoC*N()S* zggJ_sY8hh+%pwW%1!79?LfP3A3+r5*#}8Slx_bXGKqWnit3RH%2J1RKW~=pW-D~PF zn3cH$b2FqC>s3kZc_8`){@l{}4YR7A@R(x+;5fSit=08nQS zCnLt&dNZBQa&Q0k?k#M!5<8#UQQu8S*qXR}b7yc%UtcD;E|Ka;u1x(Y1$S)DbZpl$ zUHusZ;@|Z14W;J;7k|E<0|br78R+LgYfZyTB%J$S!+(gu!?$bYf4Ce8YumdvZ{>`Y zl?*rVd$@t^y_qf<*na_11e^wH1HG9ZtzWtSFP+x20nGzoy%Kn90k?yWxt#mm|L~yp o_@GAc>E$_&UM0Rizv^Z03h$NVH-#1`QUCw| delta 2597 zcmZuzZERCj7=CYW*TFstV|+~6=<^xP?GnKD&o_qEV(gU{z*>1%0Ti}p%eAhagX*oe&?Hh$L9jPF$1`I+DE(|<$$9ac2C?(MPxw< zUl7L@i7vsaxUiMAS<8x47rseY03~c;o2~xXTX`^?+5pL;0E<&Dn3>){ssLXBv?G9= zbPmCCZNE_8OME}DV*RuTZtYxdM+x9PwTigh5$76MfMSqF4+zR6KNHkyR5!`V_xZU8X82m6wLrx( zIQ*^HP>bkVvvBSNHAn$NHa5={Fnh!LG^o_UDP zEjcz{&DeC~-`Z!&qzHPoEQ*^3gsMiCX`CiID3;lFA&*VAEr#ucZo919WU7dhjNHlg z19q-(S!}5Zk>Zo>k!Ktd+&L{ke4Cu#B9K;iZj2gP^^itR3)%B_llGZ9kJsW+UT6C`Q;cI; zjEzu-FPdYhdW`!Isqn>|7~jxdOHi)Er${3cFHwg*JYMlZQ@mbwwa_O^HTX%6%uz!I z>hU9EE!U%yBzb)*Y1ZiS8KIdAZ)2gNrfL!y$`Ki){oIt)(+#DBJJ`vhG7j#|0q-z_ z)q0c(+{nQZ0>7LC-bj1Bjb40#*8Fy!-S@-UadFf2*?r%AJ}c_ofv6XsZZH)>7H0we*eLaz}mGMEL7;h0I|C8n!2m73jO7G#bSN4}?8@!ON9 z!^=Z=Cf=ko)g>fVS18GOandJl3^P|^R41QLlbr_H2lxvodxWHu9*3$YlsrTNirHX7 zH@L(|PrtU~0`)>7PCwxX%y?DD&!1hbI}K4Vxnt!u)k_Y&REZ%=DGjLpbc7&Mafi2= z1>~Sskr{Sp;qoQjB(I*JXXgodMw^jn5IdN&QdnbF7}phkPp7+I zgotR3hv};k7T^GNQkm@Jx~E9@)SNDrh0bW7SU}?@2Qn@bL41@=lsIESEFp}OVrBJ4 zhZM(geW}?Ov}&1sk;d^}H`2P;jFEKAXC}-sD!uk}wDEr)JRmHLgWQH{De$rpxNU57Nku-JDsLU@s(4Kh z8rbNH&1}$dMratTkj@L%?!lb{?5d-b^;PDx!OHQ2>#7c~s}ksCp_fQ6E4}hAtgEsO e7A{J-$qfArrr#C!g&Xy@Cxqj#du?(4r}IC+-6YWf diff --git a/lab8/kernel/kernel.c b/lab8/kernel/kernel.c index 951b63c64..2b8609119 100644 --- a/lab8/kernel/kernel.c +++ b/lab8/kernel/kernel.c @@ -7,6 +7,7 @@ #include "exception.h" #include "fork.h" #include "vfs.h" +#include "sdhost.h" void kernel_main(void) { @@ -17,6 +18,8 @@ void kernel_main(void) timer_init(); rootfs_init(); initramfs_init(); + sd_init(); + fat32_init(); enable_interrupt(); uart_send_string("OSDI 2022 Spring\n"); diff --git a/lab8/lib/fat32.c b/lab8/lib/fat32.c new file mode 100644 index 000000000..133ac3438 --- /dev/null +++ b/lab8/lib/fat32.c @@ -0,0 +1,377 @@ +#include "fat32.h" +#include "mbr.h" +#include "sdhost.h" +#include "mm.h" +#include "sched.h" +#include "string.h" +#include "mini_uart.h" + +struct vnode_operations *fat32_v_ops; +struct file_operations *fat32_f_ops; +int fat32_registered = 0; + +static struct fat32_metadata fat32_metadata; + +static uint32_t get_cluster_blk_idx(uint32_t cluster_idx) { + return fat32_metadata.data_region_blk_idx + + (cluster_idx - fat32_metadata.first_cluster) * fat32_metadata.sector_per_cluster; +} + +static uint32_t get_fat_blk_idx(uint32_t cluster_idx) { + return fat32_metadata.fat_region_blk_idx + (cluster_idx / FAT_ENTRY_PER_BLOCK); +} + +int fat32_setup_mount(struct filesystem* fs, struct mount* mount) { + + mount->fs = fs; + mount->root = fat32_new_node(NULL, "/", DIRECTORY); + + return 0; + +} + +int fat32_register() { + + if (fat32_registered) return -1; + fat32_registered = 1; + + fat32_v_ops = (struct vnode_operations *) chunk_alloc(sizeof(struct vnode_operations)); + fat32_f_ops = (struct file_operations *) chunk_alloc(sizeof(struct file_operations)); + + fat32_v_ops->lookup = fat32_lookup; + fat32_v_ops->create = fat32_create; + fat32_v_ops->mkdir = fat32_mkdir; + fat32_v_ops->load_vnode = fat32_load_vnode; + + fat32_f_ops->open = fat32_open; + fat32_f_ops->read = fat32_read; + fat32_f_ops->write = fat32_write; + fat32_f_ops->close = fat32_close; + + return 0; + +} + +struct vnode* fat32_new_node(struct fat32_internal *parent, const char *name, int type) { + + struct vnode *new_node = (struct vnode *)chunk_alloc(sizeof(struct vnode)); + struct fat32_internal *new_internal = (struct fat32_internal *)chunk_alloc(sizeof(struct fat32_internal)); + + strcpy(new_internal->name, name); + new_internal->type = type; + new_internal->parent = parent; + new_internal->vnode = new_node; + new_internal->size = 0; + + if (parent != NULL) + new_node->parent = parent->vnode; + new_node->f_ops = fat32_f_ops; + new_node->v_ops = fat32_v_ops; + new_node->mount = 0; + new_node->internal = (void *)new_internal; + + return new_node; + +} + +int fat32_open(struct vnode *file_node, struct file **target) { + return SUCCESS; +} + +int fat32_close(struct file *file) { + if (file) + return SUCCESS; + else + return FAIL; +} + +int fat32_write(struct file *file, const void *buf, unsigned len) { + struct fat32_internal *file_node = (struct fat32_internal *)file->vnode->internal; + unsigned int f_pos_ori = file->f_pos; + int fat[FAT_ENTRY_PER_BLOCK]; + char write_buf[BLOCK_SIZE]; + + uint32_t current_cluster = file_node->first_cluster; + int remain_offset = file->f_pos; + while (remain_offset > 0 && current_cluster >= fat32_metadata.first_cluster && current_cluster != EOC) { + remain_offset -= BLOCK_SIZE; + if (remain_offset > 0) { + readblock(get_fat_blk_idx(current_cluster), fat); + current_cluster = fat[current_cluster % FAT_ENTRY_PER_BLOCK]; + } + } + + int buf_idx, f_pos_offset = file->f_pos % BLOCK_SIZE; + readblock(get_cluster_blk_idx(current_cluster), write_buf); + for (buf_idx = 0; buf_idx < BLOCK_SIZE - f_pos_offset && buf_idx < len; buf_idx++) { + write_buf[buf_idx + f_pos_offset] = ((char *)buf)[buf_idx]; + } + writeblock(get_cluster_blk_idx(current_cluster), write_buf); + file->f_pos += buf_idx; + + int remain_len = len - buf_idx; + while (remain_len > 0 && current_cluster >= fat32_metadata.first_cluster && current_cluster != EOC) { + writeblock(get_cluster_blk_idx(current_cluster), buf + buf_idx); + file->f_pos += (remain_len < BLOCK_SIZE) ? remain_len : BLOCK_SIZE; + remain_len -= BLOCK_SIZE; + buf_idx += BLOCK_SIZE; + + if (remain_len > 0) { + readblock(get_fat_blk_idx(current_cluster), fat); + current_cluster = fat[current_cluster % FAT_ENTRY_PER_BLOCK]; + } + } + + if (file->f_pos > file_node->size) { + file_node->size = file->f_pos; + + uint8_t sector[BLOCK_SIZE]; + readblock(file_node->dirent_cluster, sector); + struct fat32_dirent *sector_dirent = (struct fat32_dirent*)sector; + + for (int i=0; sector_dirent[i].name[0] != '\0'; i++) { + if (sector_dirent[i].name[0] == 0xE5) continue; + + if (((sector_dirent[i].cluster_high << 16) | sector_dirent[i].cluster_low) == file_node->first_cluster) { + sector_dirent[i].size = (uint32_t)file->f_pos; + } + } + writeblock(file_node->dirent_cluster, sector); + } + + return file->f_pos - f_pos_ori; +} + +int fat32_read(struct file *file, void *buf, unsigned len) { + + struct fat32_internal *file_node = (struct fat32_internal *)file->vnode->internal; + unsigned int f_pos_ori = file->f_pos; + uint32_t current_cluster = file_node->first_cluster; + int remain_len = len; + int fat[FAT_ENTRY_PER_BLOCK]; + char tmp[512]; + + while (remain_len > 0 && current_cluster >= fat32_metadata.first_cluster && current_cluster != EOC) { + readblock(get_cluster_blk_idx(current_cluster), tmp+file->f_pos); + for (int i=0; i<512; i++) { + if (tmp[i] == '\0' || remain_len--<0) break; + ((char*)buf)[file->f_pos++] = tmp[i]; + } + + if (remain_len > 0) { + readblock(get_fat_blk_idx(current_cluster), fat); + current_cluster = fat[current_cluster % FAT_ENTRY_PER_BLOCK]; + } + } + + printf("[debug] fat32_read buf cont, len: %s %d\n", buf, file->f_pos - f_pos_ori); + return (file->f_pos - f_pos_ori); +} + +int fat32_create(struct vnode *dir_node, struct vnode **target, const char *component_name) { + + uint8_t sector[BLOCK_SIZE]; + struct fat32_internal *dir_internal = (struct fat32_internal *)dir_node->internal; + readblock(get_cluster_blk_idx(dir_internal->first_cluster), sector); + + struct fat32_dirent *sector_dirent = (struct fat32_dirent *)sector; + printf("[debug] 0\n"); + int idx=0; + while (sector_dirent[idx].name[0] != '\0' && sector_dirent[idx].name[0] != 0xE5) { + printf("[debug] first char: 0x%x\n", sector_dirent[idx].name[0]); + idx++; + } + printf("[debug] 1\n"); + int t=0; + while (component_name[t] != '.') { + sector_dirent[idx].name[t] = component_name[t]; + t++; + } + + int e=t+1; + while (t < 8) { + sector_dirent[idx].name[t] = ' '; + t++; + } + while (component_name[e] != '\0') { + sector_dirent[idx].ext[t-8] = component_name[e]; + e++; + t++; + } + printf("[debug] 2\n"); + // find empty usable cluster + int fat[FAT_ENTRY_PER_BLOCK]; + int found = 0; + uint32_t iter_cluster = fat32_metadata.first_cluster; + while (found != 1) { + readblock(get_fat_blk_idx(iter_cluster), fat); + if (fat[iter_cluster%FAT_ENTRY_PER_BLOCK] == 0x0) { + printf("[debug] create at empty cluster %d\n", iter_cluster); + found = 1; + sector_dirent[idx].cluster_high = iter_cluster >> 16; + sector_dirent[idx].cluster_low = iter_cluster & 0xFFFF; + break; + } + iter_cluster++; + } + if (found == 0) return FAIL; + sector_dirent[idx].attr = 0x20; + sector_dirent[idx].size = 0; + //sector_dirent[idx].cluster_high = 0; + //sector_dirent[idx].cluster_low = 0; + writeblock(get_cluster_blk_idx(dir_internal->first_cluster), sector_dirent); + + struct vnode *new_node = fat32_new_node(dir_internal, component_name, REGULAR_FILE); + struct fat32_internal *new_internal = (struct fat32_internal *)new_node->internal; + + new_internal->dirent_cluster = get_cluster_blk_idx(dir_internal->first_cluster); + new_internal->first_cluster = ((sector_dirent[idx].cluster_high) << 16) | (sector_dirent[idx].cluster_low); + + dir_internal->child[dir_internal->size] = new_internal; + dir_internal->size++; + + *target = new_node; + return SUCCESS; + +} + +int fat32_mkdir(struct vnode *dir_node, struct vnode **target, const char *component_name) { + return FAIL; +} + +int fat32_lookup(struct vnode *dir_node, struct vnode **target, const char *component_name) { + printf("[debug] in fat32 lookup\n"); + if (stringcmp(component_name, "") == 0) { + *target = dir_node; + return 0; + } + + struct fat32_internal *internal = (struct fat32_internal *)dir_node->internal; + + for (int i=0; isize; i++) { + printf("[debug] filename: %s\n", internal->child[i]->name); + if(stringcmp(internal->child[i]->name, component_name) == 0) { + *target = internal->child[i]->vnode; + return internal->child[i]->type; + } + } + + return FAIL; + +} + +int fat32_load_vnode(struct vnode *dir_node, char *component_name) { + printf("[debug] load vnodes\n"); + struct fat32_internal *dir_internal = (struct fat32_internal *)dir_node->internal; + uint8_t sector[BLOCK_SIZE]; + uint32_t dirent_cluster = get_cluster_blk_idx(dir_internal->first_cluster); + readblock(dirent_cluster, sector); + + struct fat32_dirent *sector_dirent = (struct fat32_dirent *)sector; + + int found = FAIL; + for (int i=0; sector_dirent[i].name[0] != '\0'; i++) { + + if (sector_dirent[i].name[0] == 0xE5) continue; + if (sector_dirent[i].name[0] == 0x2E) continue; + + char filename[13]; + int len=0; + for (int j=0; j < 8; j++) { + char c = sector_dirent[i].name[j]; + if (c == ' ') break; + filename[len++] = c; + } + filename[len++] = '.'; + for (int j=0; j < 3; j++) { + char c = sector_dirent[i].ext[j]; + if (c == ' ') break; + filename[len++] = c; + } + filename[len++] = '\0'; + + for (int i=0; isize; i++) { + if (stringcmp(filename, dir_internal->child[i]->name) == 0) { + printf("[debug] %s already loaded\n", filename); + continue; + } + } + + printf("[debug] load file: %s\n", filename); + if (stringcmp(filename, component_name) == 0) found = SUCCESS; + + struct vnode* new_node; + struct fat32_internal *p_internal = (struct fat32_internal *)dir_node->internal; + if (sector_dirent[i].attr == 0x10) { + new_node = fat32_new_node(p_internal, filename, DIRECTORY); + + struct fat32_internal *internal = (struct fat32_internal *)new_node->internal; + internal->first_cluster = ((sector_dirent[i].cluster_high) << 16) | (sector_dirent[i].cluster_low); + internal->dirent_cluster = dirent_cluster; + + p_internal->child[p_internal->size] = internal; + } else { + new_node = fat32_new_node(p_internal, filename, REGULAR_FILE); + + struct fat32_internal *internal = (struct fat32_internal *)new_node->internal; + internal->first_cluster = ((sector_dirent[i].cluster_high) << 16) | (sector_dirent[i].cluster_low); + internal->dirent_cluster = dirent_cluster; + internal->size = sector_dirent[i].size; + + p_internal->child[p_internal->size] = internal; + } + + p_internal->size++; + + } + + return found; + +} + +void parse_fat32sd() { + + char buf[BLOCK_SIZE]; + readblock(0, buf); + + if (buf[510] != 0x55 || buf[511] != 0xAA) return; + + struct mbr_partition_tb pt1; + for (int i=0; in_sectors_per_fat_32 * boot_sector->n_file_alloc_tabs + + boot_sector->n_reserved_sectors; + fat32_metadata.fat_region_blk_idx = pt1.starting_sector + boot_sector->n_reserved_sectors; + fat32_metadata.n_fat = boot_sector->n_file_alloc_tabs; + fat32_metadata.sector_per_fat = boot_sector->n_sectors_per_fat_32; + fat32_metadata.first_cluster = boot_sector->root_dir_start_cluster_num; + fat32_metadata.sector_per_cluster = boot_sector->logical_sector_per_cluster; + + struct fat32_internal *root_internal = (struct fat32_internal *)current->cwd->internal; + root_internal->first_cluster = boot_sector->root_dir_start_cluster_num; + + printf("[debug] fat32_metadata:\n"); + printf(" data_region_blk_idx: 0x%x\n", fat32_metadata.data_region_blk_idx); + printf(" fat_region_blk_idx: 0x%x\n", fat32_metadata.fat_region_blk_idx); + printf(" n_fat: %d\n", fat32_metadata.n_fat); + printf(" sector_per_fat: %d\n", fat32_metadata.sector_per_fat); + printf(" first_cluster: 0x%x\n", fat32_metadata.first_cluster); + printf(" sector_per_cluster: %d\n", fat32_metadata.sector_per_cluster); + + vfs_chdir("/"); + + } + + return; + +} \ No newline at end of file diff --git a/lab8/lib/initramfs.c b/lab8/lib/initramfs.c index 6d6953500..8c2e60b0f 100644 --- a/lab8/lib/initramfs.c +++ b/lab8/lib/initramfs.c @@ -14,9 +14,6 @@ int initramfs_setup_mount(struct filesystem* fs, struct mount* mount) { mount->fs = fs; mount->root = initramfs_new_node(NULL, "/", DIRECTORY); - - printf("[debug] setup root: 0x%x\n", mount); - printf("[debug] setup root vnode: 0x%x\n", mount->root); return 0; @@ -33,6 +30,7 @@ int initramfs_register() { initramfs_v_ops->lookup = initramfs_lookup; initramfs_v_ops->create = initramfs_create; initramfs_v_ops->mkdir = initramfs_mkdir; + initramfs_v_ops->load_vnode = NULL; initramfs_f_ops->open = initramfs_open; initramfs_f_ops->read = initramfs_read; diff --git a/lab8/lib/sdhost.c b/lab8/lib/sdhost.c new file mode 100644 index 000000000..21a11a0de --- /dev/null +++ b/lab8/lib/sdhost.c @@ -0,0 +1,172 @@ +#include "sdhost.h" + +static inline void delay(unsigned long tick) { + while (tick--) { + asm volatile("nop"); + } +} + +static int is_hcs; // high capcacity(SDHC) + +static void pin_setup() { + set(GPIO_GPFSEL4, 0x24000000); + set(GPIO_GPFSEL5, 0x924); + set(GPIO_GPPUD, 0); + delay(15000); + set(GPIO_GPPUDCLK1, 0xffffffff); + delay(15000); + set(GPIO_GPPUDCLK1, 0); +} + +static void sdhost_setup() { + unsigned int tmp; + set(SDHOST_PWR, 0); + set(SDHOST_CMD, 0); + set(SDHOST_ARG, 0); + set(SDHOST_TOUT, SDHOST_TOUT_DEFAULT); + set(SDHOST_CDIV, 0); + set(SDHOST_HSTS, SDHOST_HSTS_MASK); + set(SDHOST_CFG, 0); + set(SDHOST_CNT, 0); + set(SDHOST_SIZE, 0); + get(SDHOST_DBG, tmp); + tmp &= ~SDHOST_DBG_MASK; + tmp |= SDHOST_DBG_FIFO; + set(SDHOST_DBG, tmp); + delay(250000); + set(SDHOST_PWR, 1); + delay(250000); + set(SDHOST_CFG, SDHOST_CFG_SLOW | SDHOST_CFG_INTBUS | SDHOST_CFG_DATA_EN); + set(SDHOST_CDIV, SDHOST_CDIV_DEFAULT); +} + +static int wait_sd() { + int cnt = 1000000; + unsigned int cmd; + do { + if (cnt == 0) { + return -1; + } + get(SDHOST_CMD, cmd); + --cnt; + } while (cmd & SDHOST_NEW_CMD); + return 0; +} + +static int sd_cmd(unsigned cmd, unsigned int arg) { + set(SDHOST_ARG, arg); + set(SDHOST_CMD, cmd | SDHOST_NEW_CMD); + return wait_sd(); +} + +static int sdcard_setup() { + unsigned int tmp; + sd_cmd(GO_IDLE_STATE | SDHOST_NO_REPONSE, 0); + sd_cmd(SEND_IF_COND, VOLTAGE_CHECK_PATTERN); + get(SDHOST_RESP0, tmp); + if (tmp != VOLTAGE_CHECK_PATTERN) { + return -1; + } + while (1) { + if (sd_cmd(APP_CMD, 0) == -1) { + // MMC card or invalid card status + // currently not support + continue; + } + sd_cmd(SD_APP_OP_COND, SDCARD_3_3V | SDCARD_ISHCS); + get(SDHOST_RESP0, tmp); + if (tmp & SDCARD_READY) { + break; + } + delay(1000000); + } + + is_hcs = tmp & SDCARD_ISHCS; + sd_cmd(ALL_SEND_CID | SDHOST_LONG_RESPONSE, 0); + sd_cmd(SEND_RELATIVE_ADDR, 0); + get(SDHOST_RESP0, tmp); + sd_cmd(SELECT_CARD, tmp); + sd_cmd(SET_BLOCKLEN, 512); + return 0; +} + +static int wait_fifo() { + int cnt = 1000000; + unsigned int hsts; + do { + if (cnt == 0) { + return -1; + } + get(SDHOST_HSTS, hsts); + --cnt; + } while ((hsts & SDHOST_HSTS_DATA) == 0); + return 0; +} + +static void set_block(int size, int cnt) { + set(SDHOST_SIZE, size); + set(SDHOST_CNT, cnt); +} + +static void wait_finish() { + unsigned int dbg; + do { + get(SDHOST_DBG, dbg); + } while ((dbg & SDHOST_DBG_FSM_MASK) != SDHOST_HSTS_DATA); +} + +void readblock(int block_idx, void* buf) { + unsigned int* buf_u = (unsigned int*)buf; + int succ = 0; + if (!is_hcs) { + block_idx <<= 9; + } + do{ + set_block(512, 1); + sd_cmd(READ_SINGLE_BLOCK | SDHOST_READ, block_idx); + for (int i = 0; i < 128; ++i) { + wait_fifo(); + get(SDHOST_DATA, buf_u[i]); + } + unsigned int hsts; + get(SDHOST_HSTS, hsts); + if (hsts & SDHOST_HSTS_ERR_MASK) { + set(SDHOST_HSTS, SDHOST_HSTS_ERR_MASK); + sd_cmd(STOP_TRANSMISSION | SDHOST_BUSY, 0); + } else { + succ = 1; + } + } while(!succ); + wait_finish(); +} + +void writeblock(int block_idx, const void* buf) { + unsigned int* buf_u = (unsigned int*)buf; + int succ = 0; + if (!is_hcs) { + block_idx <<= 9; + } + do{ + set_block(512, 1); + sd_cmd(WRITE_SINGLE_BLOCK | SDHOST_WRITE, block_idx); + for (int i = 0; i < 128; ++i) { + wait_fifo(); + set(SDHOST_DATA, buf_u[i]); + } + unsigned int hsts; + get(SDHOST_HSTS, hsts); + if (hsts & SDHOST_HSTS_ERR_MASK) { + set(SDHOST_HSTS, SDHOST_HSTS_ERR_MASK); + sd_cmd(STOP_TRANSMISSION | SDHOST_BUSY, 0); + } else { + succ = 1; + } + } while(!succ); + wait_finish(); +} + +void sd_init() { + pin_setup(); + sdhost_setup(); + sdcard_setup(); +} \ No newline at end of file diff --git a/lab8/lib/shell.c b/lab8/lib/shell.c index 5adf9634c..d717325cb 100644 --- a/lab8/lib/shell.c +++ b/lab8/lib/shell.c @@ -86,7 +86,7 @@ void start_video() { filesize = hexstr_to_uint(header->c_filesize, 8); - if (stringncmp(filename, "vfs1.img", namesize) == 0) { + if (stringncmp(filename, "vfs2.img", namesize) == 0) { code_loc = ((void*)header) + offset; break; } @@ -99,8 +99,8 @@ void start_video() { header = ((void*)header) + offset; } - printf("vfs1.img found in cpio at location 0x%x.\n", code_loc); - printf("vfs1.img has size of %d bytes.\n", (int)filesize); + printf("vfs2.img found in cpio at location 0x%x.\n", code_loc); + printf("vfs2.img has size of %d bytes.\n", (int)filesize); void *move_loc = malloc(filesize + 4096); // an extra page for bss just in case if(move_loc == NULL) return; diff --git a/lab8/lib/tmpfs.c b/lab8/lib/tmpfs.c index b1f51196e..91ec6c447 100644 --- a/lab8/lib/tmpfs.c +++ b/lab8/lib/tmpfs.c @@ -30,6 +30,7 @@ int tmpfs_register() { tmpfs_v_ops->lookup = tmpfs_lookup; tmpfs_v_ops->create = tmpfs_create; tmpfs_v_ops->mkdir = tmpfs_mkdir; + tmpfs_v_ops->load_vnode = NULL; tmpfs_f_ops->open = tmpfs_open; tmpfs_f_ops->read = tmpfs_read; diff --git a/lab8/lib/vfs.c b/lab8/lib/vfs.c index 0492dfc26..fdac169ed 100644 --- a/lab8/lib/vfs.c +++ b/lab8/lib/vfs.c @@ -1,6 +1,7 @@ #include "vfs.h" #include "tmpfs.h" #include "initramfs.h" +#include "fat32.h" #include "string.h" #include "mm.h" #include "sched.h" @@ -29,11 +30,21 @@ void initramfs_init() { } +void fat32_init() { + + vfs_mkdir("/boot"); + vfs_mount("/boot", "fat32"); + parse_fat32sd(); + +} + int register_fs(struct filesystem *fs) { if (stringcmp(fs->name, "tmpfs") == 0) { return tmpfs_register(); } else if (stringcmp(fs->name, "initramfs") == 0) { return initramfs_register(); + } else if (stringcmp(fs->name, "fat32") == 0) { + return fat32_register(); } return -1; } @@ -126,6 +137,17 @@ int vfs_mount(const char *target, const char *filesystem) { mount_dir->mount = mt; mt->root->parent = mount_dir->parent; + } else if (stringcmp(filesystem, "fat32") == 0) { + + struct filesystem *fat32 = (struct filesystem *)chunk_alloc(sizeof(struct filesystem)); + fat32->name = (char *)chunk_alloc(16); + strcpy(fat32->name, "fat32"); + fat32->setup_mount = fat32_setup_mount; + register_fs(fat32); + fat32->setup_mount(fat32, mt); + mount_dir->mount = mt; + mt->root->parent = mount_dir->parent; + } return SUCCESS; @@ -184,7 +206,7 @@ void r_traverse(struct vnode *node, const char *path, struct vnode **target_node return; } - int res = node->v_ops->lookup(node, target_node, target_path); + int res = node->v_ops->lookup(node, target_node, target_path); printf("[debug] lookup ret: %d\n", res); if ((*target_node)->mount != NULL) { printf("[debug] mountpoint found during lookup: vnode 0x%x\n", (*target_node)->mount->root); r_traverse((*target_node)->mount->root, path+i, target_node, target_path); @@ -193,5 +215,11 @@ void r_traverse(struct vnode *node, const char *path, struct vnode **target_node r_traverse(*target_node, path+i, target_node, target_path); else if (res == REGULAR_FILE) *target_node = node; + else if (res == FAIL && node->v_ops->load_vnode != NULL) { + int ret = node->v_ops->load_vnode(node, target_path); + if (ret == SUCCESS) { + r_traverse(node, path, target_node, target_path); + } + } } diff --git a/lab8/rootfs/msg b/lab8/rootfs/msg deleted file mode 100644 index a04238969..000000000 --- a/lab8/rootfs/msg +++ /dev/null @@ -1 +0,0 @@ -hello world! diff --git a/lab8/bcm2710-rpi-3-b-plus.dtb b/lab8/sfn.dtb similarity index 100% rename from lab8/bcm2710-rpi-3-b-plus.dtb rename to lab8/sfn.dtb