From c4683d06f7b1d3aae33fcbaa15c6444329dbc173 Mon Sep 17 00:00:00 2001 From: Shin-Yan Date: Mon, 19 Jun 2023 23:50:04 +0800 Subject: [PATCH 01/10] Implement Mapping kernel space and user space to virtual address --- Makefile | 4 +- inc/arm.h | 28 ++++ inc/builtin.h | 6 +- inc/dt17.h | 4 +- inc/entry.h | 9 ++ inc/exec.h | 2 +- inc/mem.h | 8 +- inc/mini_uart.h | 10 +- inc/mmu.h | 59 ++++++++ inc/panic.h | 6 + inc/sched.h | 4 +- inc/task.h | 3 +- inc/trapframe.h | 41 +++++- inc/utils.h | 15 ++ initramfs.cpio_lab5 | Bin 0 -> 247296 bytes src/bootloader/linker.ld | 4 +- src/bootloader/main.c | 19 +-- src/kernel/boot.S | 11 ++ src/kernel/linker.ld | 10 +- src/kernel/main.c | 10 +- src/lib/builtin.c | 17 ++- src/lib/dt17.c | 7 +- src/lib/exec.S | 8 +- src/lib/exec.c | 14 +- src/lib/irq.c | 2 +- src/lib/mbox.c | 7 +- src/lib/mini_uart.c | 305 +++++++++++++++++++-------------------- src/lib/mm.c | 6 +- src/lib/mmu.c | 154 ++++++++++++++++++++ src/lib/panic.c | 18 +++ src/lib/reboot.c | 12 +- src/lib/sched.S | 9 ++ src/lib/signal.c | 2 + src/lib/syscall.c | 52 +++++-- src/lib/task.c | 5 + src/lib/timer.c | 6 +- test/initramfs_lab5.cpio | Bin 0 -> 247296 bytes 37 files changed, 637 insertions(+), 240 deletions(-) create mode 100644 inc/arm.h create mode 100644 inc/entry.h create mode 100644 inc/mmu.h create mode 100644 inc/panic.h create mode 100755 initramfs.cpio_lab5 create mode 100644 src/lib/mmu.c create mode 100644 src/lib/panic.c create mode 100755 test/initramfs_lab5.cpio diff --git a/Makefile b/Makefile index 03c48347f..85d33cb21 100644 --- a/Makefile +++ b/Makefile @@ -96,13 +96,13 @@ qemub: all $(INITRAMFS_CPIO) $(RPI3_DTB) qemuk: all $(INITRAMFS_CPIO) $(RPI3_DTB) qemu-system-aarch64 -M raspi3 -kernel $(KERNEL_IMG) -display none \ -dtb $(RPI3_DTB) \ - -initrd $(INITRAMFS_CPIO) \ + -initrd test/initramfs_lab5.cpio \ -serial null -serial stdio qemutest: all $(INITRAMFS_CPIO) $(RPI3_DTB) qemu-system-aarch64 -M raspi3 -kernel $(KERNEL_IMG) -display none \ -dtb $(RPI3_DTB) \ - -initrd test/initramfs.cpio \ + -initrd $(INITRAMFS_CPIO) \ -serial null -serial stdio diff --git a/inc/arm.h b/inc/arm.h new file mode 100644 index 000000000..099d420eb --- /dev/null +++ b/inc/arm.h @@ -0,0 +1,28 @@ +#ifndef _ARM_H +#define _ARM_H + +/* ==== PAR_EL1 related ==== */ +#define PAR_FAILED(par) (par & 1) +#define PAR_PA(par) (par & 0x0000fffffffff000) + +/* ==== ESR_EL1 related ==== */ +#define EC_SVC_64 0x15 +#define EC_IA_LE 0x20 +#define EC_DA_LE 0x24 + +#define ISS_FSC(esr) (esr->iss & 0x3f) + +#define FSC_TF_L0 0b000100 +#define FSC_TF_L1 0b000101 +#define FSC_TF_L2 0b000110 +#define FSC_TF_L3 0b000111 + +#define ISS_WnR(esr) (esr->iss & 0x40) + +typedef struct{ + unsigned int iss:25, + il:1, + ec:6; +} esr_el1_t; + +#endif \ No newline at end of file diff --git a/inc/builtin.h b/inc/builtin.h index f31ef56e6..78244b46a 100644 --- a/inc/builtin.h +++ b/inc/builtin.h @@ -6,11 +6,11 @@ void _hello(void); void _hwinfo(void); void _reboot(void); void _echo(char *shell_buf); -void _ls(uint64 _initramfs_addr); -void _cat(uint64 _initramfs_addr, char *filename); +void _ls(void); +void _cat(char *filename); void _parsedtb(char *fdt_base); void *_malloc(char *size); -void _exec(uint64 _initramfs_addr, char *filename); +void _exec(char *filename); void _chmod_uart(); int _setTimeout(char *shell_buf); void _thread_test(); diff --git a/inc/dt17.h b/inc/dt17.h index e952d5b91..291b8ef0d 100644 --- a/inc/dt17.h +++ b/inc/dt17.h @@ -11,8 +11,8 @@ int initramfs_parse_fdt(int level, char *cur, char *dt_strings); void initramfs_init(char *fdt_base); void dtb_traverse(char *fdt_base); -uint64 _initramfs_addr; -uint64 _initramfs_end; +void *_initramfs_addr; +void *_initramfs_end; #define FDT_MAGIC_NUM 0xd00dfeed #define FDT_BEGIN_NODE 0x00000001 diff --git a/inc/entry.h b/inc/entry.h new file mode 100644 index 000000000..e73598294 --- /dev/null +++ b/inc/entry.h @@ -0,0 +1,9 @@ +#ifndef _ENTRY_H +#define _ENTRY_H + +#include +#include + +void el0_sync_handler(trapframe *regs, uint32 syn); + +#endif \ No newline at end of file diff --git a/inc/exec.h b/inc/exec.h index 539a729d7..a084523b0 100644 --- a/inc/exec.h +++ b/inc/exec.h @@ -4,7 +4,7 @@ void user_prog_start(void); // pass the user stack pointer and process memory location (program loaded) -void sched_new_user_prog(char *cpio, char *file_name); +void sched_new_user_prog(char *file_name); void exit_user_prog(void); diff --git a/inc/mem.h b/inc/mem.h index 6478d5846..629a0f76e 100644 --- a/inc/mem.h +++ b/inc/mem.h @@ -3,11 +3,11 @@ #include -extern char start_mem; -extern char end_mem; +extern char _early_mem_base; +extern char _early_mem_end; -#define SMEM (&start_mem) -#define EMEM (&end_mem) +#define SMEM (&_early_mem_base) +#define EMEM (&_early_mem_end) void *simple_malloc(uint32 size); diff --git a/inc/mini_uart.h b/inc/mini_uart.h index d6340690d..85534fdbb 100644 --- a/inc/mini_uart.h +++ b/inc/mini_uart.h @@ -1,19 +1,21 @@ #ifndef _MINI_UART_H #define _MINI_UART_H #include +#include char uart_recv (void); void uart_recvn(char *buff, int n); void uart_send (char c); -void uart_send_string(char *str); -void uart_send_hex(unsigned int d); +void uart_printf(const char *fmt, ...); +void uart_sync_printf(const char *fmt, ...); +void uart_sync_vprintf(const char *fmt, va_list args); + int uart_recv_line(char *buf, int maxline); uint32 uart_recv_uint(); -void uart_printf(const char *fmt, ...); + void uart_sendn(const char *str, int n); void uart_init (void); int uart_irq_add(void); -void uart_irq_handler(void); int uart_switch_mode(void); #endif /*_MINI_UART_H */ \ No newline at end of file diff --git a/inc/mmu.h b/inc/mmu.h new file mode 100644 index 000000000..9045c4a90 --- /dev/null +++ b/inc/mmu.h @@ -0,0 +1,59 @@ +#ifndef _MMU_H +#define _MMU_H + +#include +#include +#include +#include + +#define PAGE_TABLE_SIZE 0x1000 + +#define PT_R 0x0001 +#define PT_W 0x0002 +#define PT_X 0x0004 + +#define VMA_R PT_R +#define VMA_W PT_W +#define VMA_X PT_X + +#define VMA_PA 0x0008 +#define VMA_KVA 0x0010 +#define VMA_AMON 0x0020 + +#define PROT_NOTE 0 +#define PROT_READ 1 +#define PROT_WRITE 2 +#define PROT_EXEC 4 + +#define MAP_ANONYMOUS 0x8000 + +typedef uint64 pd_t; + +typedef struct _vm_area_t{ + struct list_head list; + uint64 va_begin; + uint64 va_end; + uint64 flag; + uint64 kva; +} vm_area_t; + +typedef struct{ + struct list_head vma; +} vm_area_meta_t; + +void mmu_init(void); + +pd_t *pt_create(void); +void pt_free(pd_t *pt); + +void pt_map(pd_t *pt, void *va, uint64 size, void *pa, uint64 flag); + +vm_area_meta_t *vma_meta_create(void); +void vma_meta_free(vm_area_meta_t *vma_mata, pd_t *page_table); +void vma_meta_copy(vm_area_meta_t *to, vm_area_meta_t *from, pd_t *page_table); +void vma_map(vm_area_meta_t *vma_meta, void *va, uint64 size, uint64 flag, void *addr); +void mem_abort(esr_el1_t *esr); + +void syscall_mmap(trapframe *frame, void *addr, size_t len, int prot, int flags, int fd, int file_offset); + +#endif \ No newline at end of file diff --git a/inc/panic.h b/inc/panic.h new file mode 100644 index 000000000..9f4ec5a2d --- /dev/null +++ b/inc/panic.h @@ -0,0 +1,6 @@ +#ifndef _PANIC_H +#define _PANIC_H + +void panic(const char *fmt, ...); + +#endif \ No newline at end of file diff --git a/inc/sched.h b/inc/sched.h index df0355681..ce3ed21a8 100644 --- a/inc/sched.h +++ b/inc/sched.h @@ -3,6 +3,7 @@ #include #include +#include struct pt_regs { void *x19; @@ -23,8 +24,9 @@ struct pt_regs { struct signal_head_t; struct sighand_t; typedef struct _task_struct { - /* This must be the first element */ struct pt_regs regs; + pd_t *page_table; + /* The order of the above elements cannot be changed*/ void *kernel_stack; void *user_stack; /* TODO: Update to address_space*/ diff --git a/inc/task.h b/inc/task.h index 87184d40a..9c5eb125a 100644 --- a/inc/task.h +++ b/inc/task.h @@ -4,8 +4,9 @@ #include #include #include +#include -#define STACK_SIZE ( 2 * PAGE_SIZE) +#define STACK_SIZE ( 4 * PAGE_SIZE) /* Task status */ diff --git a/inc/trapframe.h b/inc/trapframe.h index f72d99e50..3542ea157 100644 --- a/inc/trapframe.h +++ b/inc/trapframe.h @@ -2,7 +2,7 @@ #define _TRAPFRAME_H #include - +#include typedef struct { uint64 x0; uint64 x1; @@ -40,4 +40,43 @@ typedef struct { void *spsr_el1; } trapframe; +static inline void show_trapframe(trapframe *regs) +{ + uart_sync_printf("\r\n[*] Trapframe:\r\n"); + uart_sync_printf("\tx0: %llx\r\n", regs->x0); + uart_sync_printf("\tx1: %llx\r\n", regs->x1); + uart_sync_printf("\tx2: %llx\r\n", regs->x2); + uart_sync_printf("\tx3: %llx\r\n", regs->x3); + uart_sync_printf("\tx4: %llx\r\n", regs->x4); + uart_sync_printf("\tx5: %llx\r\n", regs->x5); + uart_sync_printf("\tx6: %llx\r\n", regs->x6); + uart_sync_printf("\tx7: %llx\r\n", regs->x7); + uart_sync_printf("\tx8: %llx\r\n", regs->x8); + uart_sync_printf("\tx9: %llx\r\n", regs->x9); + uart_sync_printf("\tx10: %llx\r\n", regs->x10); + uart_sync_printf("\tx11: %llx\r\n", regs->x11); + uart_sync_printf("\tx12: %llx\r\n", regs->x12); + uart_sync_printf("\tx13: %llx\r\n", regs->x13); + uart_sync_printf("\tx14: %llx\r\n", regs->x14); + uart_sync_printf("\tx15: %llx\r\n", regs->x15); + uart_sync_printf("\tx16: %llx\r\n", regs->x16); + uart_sync_printf("\tx17: %llx\r\n", regs->x17); + uart_sync_printf("\tx18: %llx\r\n", regs->x18); + uart_sync_printf("\tx19: %llx\r\n", regs->x19); + uart_sync_printf("\tx20: %llx\r\n", regs->x20); + uart_sync_printf("\tx21: %llx\r\n", regs->x21); + uart_sync_printf("\tx22: %llx\r\n", regs->x22); + uart_sync_printf("\tx23: %llx\r\n", regs->x23); + uart_sync_printf("\tx24: %llx\r\n", regs->x24); + uart_sync_printf("\tx25: %llx\r\n", regs->x25); + uart_sync_printf("\tx26: %llx\r\n", regs->x26); + uart_sync_printf("\tx27: %llx\r\n", regs->x27); + uart_sync_printf("\tx28: %llx\r\n", regs->x28); + uart_sync_printf("\tx29: %llx\r\n", regs->x29); + uart_sync_printf("\tx30: %llx\r\n", regs->x30); + uart_sync_printf("\tsp_el0 : %llx\r\n", regs->sp_el0); + uart_sync_printf("\telr_el1 : %llx\r\n", regs->elr_el1); + uart_sync_printf("\tspsr_el1: %llx\r\n", regs->spsr_el1); +} + #endif /* _TRAPFRAME_H */ \ No newline at end of file diff --git a/inc/utils.h b/inc/utils.h index 684b41d8e..8d60cf3b8 100644 --- a/inc/utils.h +++ b/inc/utils.h @@ -3,6 +3,8 @@ #include +#define PA2VA(x) (((uint64)(x)) | 0xffff000000000000) +#define VA2PA(x) (((uint64)(x)) & 0x0000ffffffffffff) #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) #define enable_interrupt(){ \ @@ -13,6 +15,19 @@ asm volatile("msr DAIFSet, 0xf"); \ } +#define set_page_table(task) do { \ + asm volatile( \ + "mov x9, %0\n" \ + "and x9, x9, #0x0000ffffffffffff\n" \ + "dsb ish\n" \ + "msr ttbr0_el1, x9\n" \ + "tlbi vmalle1is\n" \ + "dsb ish\n" \ + "isb\n" \ + :: "r" (task->page_table) \ + ); \ +} while (0) + #define get_elem_idx(elem, array) \ (((char *)elem - (char *)array) / sizeof(array[0])) diff --git a/initramfs.cpio_lab5 b/initramfs.cpio_lab5 new file mode 100755 index 0000000000000000000000000000000000000000..0676fb1584617bc4d73624b3b212a12133e97d8e GIT binary patch literal 247296 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 5) - _cat(_initramfs_addr, &shell_buf[4]); + _cat(&shell_buf[4]); else uart_printf("usage: cat \r\n"); } @@ -58,7 +58,7 @@ void shell_interact(void){ _parsedtb(fdt_base); else if (!strncmp("exec",shell_buf,4)){ if(cnt >= 6 && shell_buf[4]==' ') - _exec(_initramfs_addr,&shell_buf[5]); + _exec(&shell_buf[5]); else uart_printf("usage: exec \r\n"); } @@ -98,8 +98,8 @@ void kernel_main(char *fdt){ uart_printf("[*] Kernel start running!\r\n"); uart_printf("[*] fdt base: %x\r\n", fdt); - kthread_create(shell_interact); - // sched_new_user_prog(fdt, "syscall.img"); + // kthread_create(shell_interact); + sched_new_user_prog("vm.img"); enable_irqs1(); enable_interrupt(); diff --git a/src/lib/builtin.c b/src/lib/builtin.c index fcae038cb..4dc5c3e78 100644 --- a/src/lib/builtin.c +++ b/src/lib/builtin.c @@ -65,7 +65,7 @@ void _hwinfo(void){ void _reboot(void){ uart_printf("Rebooting...\r\n\r\n"); - // delay(10000); + delay(10000); reset(10); // while(1); } @@ -74,11 +74,11 @@ void _echo(char *shell_buf){ uart_printf(shell_buf); } -void _ls(uint64 _initramfs_addr){ +void _ls(void){ cpio_ls((char*)_initramfs_addr); } -void _cat(uint64 _initramfs_addr, char *filename){ +void _cat(char *filename){ cpio_cat((char*)_initramfs_addr, filename); } @@ -96,7 +96,7 @@ void *_malloc(char *size){ return kmalloc(int_size); } -void _exec(uint64 _initramfs_addr,char *filename){ +void _exec(char *filename){ void *data; uint32 datalen; @@ -107,15 +107,18 @@ void _exec(uint64 _initramfs_addr,char *filename){ current->user_stack = kmalloc(STACK_SIZE); current->data = data; current->datalen = datalen; + pt_map(current->page_table, (void *)0, datalen, (void *)VA2PA(current->data), PT_R | PT_W | PT_X); + pt_map(current->page_table, (void *)0xffffffffb000, STACK_SIZE,(void *)VA2PA(current->user_stack), PT_R | PT_W); + pt_map(current->page_table, (void *)0x3c000000, 0x04000000, (void *)0x3c000000, PT_R | PT_W); user_prog_start(); } void _chmod_uart(){ int mode = uart_switch_mode(); if(mode==0) - uart_printf("[*] Use synchronous uart now!\r\n"); + uart_sync_printf("[*] Use synchronous uart now!\r\n"); else - uart_printf("[*] Use asynchronous uart now!\r\n"); + uart_sync_printf("[*] Use asynchronous uart now!\r\n"); } int _setTimeout(char *shell_buf){ @@ -152,7 +155,7 @@ int _setTimeout(char *shell_buf){ static void foo(){ for (int i = 0; i < 10; ++i) { - uart_printf("Thread id: %d %d\r\n", current->tid, i); + uart_sync_printf("Thread id: %d %d\r\n", current->tid, i); delay(1000000); schedule(); } diff --git a/src/lib/dt17.c b/src/lib/dt17.c index 5c0c33ca4..ded139cfe 100644 --- a/src/lib/dt17.c +++ b/src/lib/dt17.c @@ -2,6 +2,7 @@ #include #include #include +#include uint32 initramfs_check_cnt = 0; @@ -91,12 +92,12 @@ int initramfs_parse_fdt(int level, char *cur, char *dt_strings){ struct fdt_property *prop =(struct fdt_property *) cur; cur += sizeof(struct fdt_property); if(!strcmp("linux,initrd-start", dt_strings + fdtp_nameoff(prop))){ - _initramfs_addr = fdt32_ld((fdt32_t*)cur); + _initramfs_addr = (void*)PA2VA(fdt32_ld((fdt32_t*)cur)); uart_printf("[*] initrd addr: %x\r\n", _initramfs_addr); initramfs_check_cnt++; } else if(!strcmp("linux,initrd-end", dt_strings + fdtp_nameoff(prop))){ - _initramfs_end = fdt32_ld((fdt32_t*)cur); + _initramfs_end = (void*)PA2VA(fdt32_ld((fdt32_t*)cur)); uart_printf("[*] initrd end: %x\r\n", _initramfs_end); initramfs_check_cnt++; } @@ -107,7 +108,7 @@ int initramfs_parse_fdt(int level, char *cur, char *dt_strings){ } void initramfs_init(char *fdt_base){ - _initramfs_addr = 0; + _initramfs_addr = NULL; parse_dtb(fdt_base, initramfs_parse_fdt); if(!_initramfs_addr) uart_printf("[x] Cannot find initrd address in dtb!\r\n"); diff --git a/src/lib/exec.S b/src/lib/exec.S index bdd23303d..0fbc8a517 100644 --- a/src/lib/exec.S +++ b/src/lib/exec.S @@ -2,14 +2,14 @@ .global enter_el0_run_user_prog enter_el0_run_user_prog: + // set elr_el1 to the program start address // Set sp_el0 to the user program's stack pointer - msr sp_el0, x0 - + msr elr_el1, x0 + msr sp_el0, x1 // set spsr_el1 to 0 to enable interrupt set to 0x3c0 to disable interrupt mov x0, 0x0 msr spsr_el1, x0 - // set elr_el1 to the program start address - msr elr_el1, x1 + // return to EL0 eret diff --git a/src/lib/exec.c b/src/lib/exec.c index d47401708..8284c60e7 100644 --- a/src/lib/exec.c +++ b/src/lib/exec.c @@ -5,15 +5,16 @@ #include #include #include +#include void enter_el0_run_user_prog(void *entry, void *user_sp); void user_prog_start(void){ - char *user_sp; + // char *user_sp; - user_sp = (char *)current->user_stack + STACK_SIZE - 0x10; + // user_sp = (char *)current->user_stack + STACK_SIZE - 0x10; - enter_el0_run_user_prog(user_sp, current->data); + enter_el0_run_user_prog((void *)0, (char*)0xffffffffeff0); // User program should call exit() to terminate } @@ -33,12 +34,12 @@ static inline void pt_regs_init(struct pt_regs *regs){ regs->lr = user_prog_start; } -void sched_new_user_prog(char* cpio, char *filename){ +void sched_new_user_prog(char *filename){ task_struct *task; void *data; uint32 datalen; - datalen = cpio_load_prog(cpio, filename, (char **)&data); + datalen = cpio_load_prog(_initramfs_addr, filename, (char **)&data); if(datalen == 0) goto EXEC_USER_PROG_END; @@ -52,6 +53,9 @@ void sched_new_user_prog(char* cpio, char *filename){ task->regs.sp = (char *)task->kernel_stack + STACK_SIZE -0x10; pt_regs_init(&task->regs); + pt_map(task->page_table, (void *)0, datalen, (void *)VA2PA(task->data), PT_R | PT_W | PT_X); + pt_map(task->page_table, (void *)0xffffffffb000, STACK_SIZE,(void *)VA2PA(task->user_stack), PT_R | PT_W); + pt_map(task->page_table, (void *)0x3c000000, 0x04000000, (void *)0x3c000000, PT_R | PT_W); sched_add_task(task); diff --git a/src/lib/irq.c b/src/lib/irq.c index 0f3265bac..f3de05d98 100644 --- a/src/lib/irq.c +++ b/src/lib/irq.c @@ -155,5 +155,5 @@ void irq_handler(){ } void enable_irqs1(){ - put32(ENABLE_IRQS1, 1 << 29); // Enable UART1 IRQ + put32(PA2VA(ENABLE_IRQS1), 1 << 29); // Enable UART1 IRQ } \ No newline at end of file diff --git a/src/lib/mbox.c b/src/lib/mbox.c index 84a3c0683..b43ffae69 100644 --- a/src/lib/mbox.c +++ b/src/lib/mbox.c @@ -1,12 +1,13 @@ #include #include +#include #define MMIO_BASE 0x3F000000 /* mailbox message buffer */ volatile unsigned int __attribute__((aligned(16))) mbox[36]; -#define VIDEOCORE_MBOX (MMIO_BASE+0x0000B880) +#define VIDEOCORE_MBOX PA2VA(MMIO_BASE+0x0000B880) #define MBOX_READ ((volatile unsigned int*)(VIDEOCORE_MBOX+0x0)) #define MBOX_POLL ((volatile unsigned int*)(VIDEOCORE_MBOX+0x10)) #define MBOX_SENDER ((volatile unsigned int*)(VIDEOCORE_MBOX+0x14)) @@ -60,7 +61,7 @@ void get_board_revision(unsigned int *revision){ if(mbox_call(MBOX_CH_PROP, mbox)!=0) *revision = mbox[5]; else{ - uart_send_string("Unable to get borad revision!"); + uart_printf("Unable to get borad revision!"); *revision = 0; } } @@ -80,5 +81,5 @@ void get_arm_memory(arm_info *arm_mem){ arm_mem->size = mbox[6]; } else - uart_send_string("Unable to get arm memory information!"); + uart_printf("Unable to get arm memory information!"); } \ No newline at end of file diff --git a/src/lib/mini_uart.c b/src/lib/mini_uart.c index 6eb3dd03b..e975bdd1b 100644 --- a/src/lib/mini_uart.c +++ b/src/lib/mini_uart.c @@ -21,40 +21,29 @@ static char w_buffer[BUFSIZE]; static char r_buffer[BUFSIZE]; static int w_head, w_tail, r_head, r_tail; - -char uart_recv(void){ - return (uart_recv_fp)(); -} - -void uart_recvn(char *buff, int n){ - while(n--) - *buff++ = (uart_recv_fp)(); -} - -void uart_send(char c){ - (uart_send_fp)(c); -} static inline void enable_R_interrupt(){ - uint32 ier = get32(AUX_MU_IER_REG); + uint32 ier = get32(PA2VA(AUX_MU_IER_REG)); ier |= 0x01; - put32(AUX_MU_IER_REG, ier); + put32(PA2VA(AUX_MU_IER_REG), ier); } static inline void enable_W_interrupt(){ - uint32 ier = get32(AUX_MU_IER_REG); + uint32 ier = get32(PA2VA(AUX_MU_IER_REG)); ier |= 0x02; - put32(AUX_MU_IER_REG, ier); + put32(PA2VA(AUX_MU_IER_REG), ier); } static inline void disable_RW_interrupt(){ - put32(AUX_MU_IER_REG, 0); + put32(PA2VA(AUX_MU_IER_REG), 0); } static inline void enable_RW_interrupt(){ - put32(AUX_MU_IER_REG, 3); + put32(PA2VA(AUX_MU_IER_REG), 3); } static char uart_async_recv(void){ + // Enable R interrupt + enable_R_interrupt(); while(1){ if(r_head != r_tail) break; @@ -76,78 +65,32 @@ static void uart_async_send(char c) w_buffer[w_tail] = c; w_tail = (w_tail + 1) % BUFSIZE; - // Enable transmit interrupt - uint32 ier = get32(AUX_MU_IER_REG); - ier = ier | 0x02; - put32(AUX_MU_IER_REG, ier); - - enable_interrupt(); + // Enable W interrupt + enable_W_interrupt(); } static char uart_sync_recv (void){ while(1) { - if(get32(AUX_MU_LSR_REG)&0x01) + if(get32(PA2VA(AUX_MU_LSR_REG))&0x01) break; } - return(get32(AUX_MU_IO_REG)&0xFF); + return(get32(PA2VA(AUX_MU_IO_REG))&0xFF); } static void uart_sync_send (char c){ while(1) { - if(get32(AUX_MU_LSR_REG)&0x20) // hang until can read + if(get32(PA2VA(AUX_MU_LSR_REG))&0x20) // hang until can read break; } - put32(AUX_MU_IO_REG,c); + put32(PA2VA(AUX_MU_IO_REG),c); } -void uart_send_string(char *str){ +static void uart_send_string(sendfp _send_fp, char *str){ for (int i = 0; str[i] != '\0'; i ++) - uart_send(str[i]); -} - -void uart_send_hex(unsigned int d) { - unsigned int n; - int c; - uart_send_string("0x"); - for(c=28;c>=0;c-=4) { - // get highest tetrad - n=(d>>c)&0xF; - // 0-9 => '0'-'9', 10-15 => 'A'-'F' - n+=n>9?0x37:0x30; - uart_send(n); - } -} - -int uart_recv_line(char *buf, int maxline){ - int cnt = 0; - maxline--; - - while(maxline){ - char c = uart_recv(); - if(c == '\r') - break; - uart_send(c); - *buf = c; - buf++; - cnt++; - maxline--; - } - - *buf = 0; - return cnt; -} - -uint32 uart_recv_uint(void){ - char buf[4]; - - for (int i = 0; i < 4; ++i) { - buf[i] = uart_recv(); - } - - return *((uint32*)buf); + (_send_fp)(str[i]); } -static void uart_send_num(int32 num, int base, int type) +static void uart_send_num(sendfp _send_fp, int64 num, int base, int type) { static const char digits[16] = "0123456789ABCDEF"; char tmp[66]; @@ -155,7 +98,7 @@ static void uart_send_num(int32 num, int base, int type) if (type & 1) { if (num < 0) { - uart_send('-'); + (_send_fp)('-'); } } @@ -165,30 +108,27 @@ static void uart_send_num(int32 num, int base, int type) tmp[i++] = '0'; } else { while (num != 0) { - uint8 r = (uint32)num % base; - num = (uint32)num / base; + uint8 r = (uint64)num % base; + num = (uint64)num / base; tmp[i++] = digits[r]; } } while (--i >= 0) { - uart_send(tmp[i]); + (_send_fp)(tmp[i]); } } -void uart_printf(const char *fmt, ...){ +static void _uart_printf(sendfp _send_fp, const char *fmt, va_list args){ const char *s; char c; uint32 num; - char width; - - va_list args; - va_start(args, fmt); + char width; for (; *fmt; ++fmt) { if (*fmt != '%') { - uart_send(*fmt); + (_send_fp)(*fmt); continue; } @@ -203,80 +143,32 @@ void uart_printf(const char *fmt, ...){ switch (*fmt) { case 'c': c = va_arg(args, uint32) & 0xff; - uart_send(c); + (_send_fp)(c); continue; case 'd': if(width) num = va_arg(args, int64); else num = va_arg(args, int32); - uart_send_num(num, 10, 1); + uart_send_num(_send_fp, num, 10, 1); continue; case 's': s = va_arg(args, char *); - uart_send_string((char*)s); + uart_send_string(_send_fp,(char*)s); continue; case 'x': if (width) num = va_arg(args, uint64); else num = va_arg(args, uint32); - uart_send_num(num, 16, 0); + uart_send_num(_send_fp, num, 16, 0); continue; } } } -void uart_sendn(const char *str, int n){ - while (n--) - uart_send(*str++); -} - -void uart_init (void) -{ - unsigned int selector; - - selector = get32(GPFSEL1); - selector &= ~(7<<12); // clean gpio14 (rx) - selector |= 2<<12; // set alt5 for gpio14 - selector &= ~(7<<15); // clean gpio15 (tx) - selector |= 2<<15; // set alt5 for gpio15 - put32(GPFSEL1,selector); - - put32(GPPUD,0); //Disable pull up/down, floating input pin - delay(150); - put32(GPPUDCLK0,(1<<14)|(1<<15)); - delay(150); - put32(GPPUDCLK0,0); - - put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) - put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) - put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts - put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode - put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high - put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 - put32(AUX_MU_IIR_REG, 6); //Clear the Rx/Tx FIFO - put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver - - // UART start from synchronous mode - uart_sync_mode = 0; - uart_recv_fp = uart_sync_recv; - uart_send_fp = uart_sync_send; -} -int uart_irq_add(){ - uint32 iir = get32(AUX_MU_IIR_REG); - // No interrupt - if(iir & 0x01) - return 0; - - disable_RW_interrupt(); - if(irq_add_task((void (*)(void *))uart_irq_handler, NULL,uart_irq_fini, UART_PRIO)) - enable_RW_interrupt(); - - return 1; -} -void uart_irq_handler(void){ - uint32 iir = get32(AUX_MU_IIR_REG), ier = 0; +static void uart_irq_handler(void *_){ + uint32 iir = get32(PA2VA(AUX_MU_IIR_REG)); // Transmit holding register empty if(iir & 0x02){ @@ -285,7 +177,7 @@ void uart_irq_handler(void){ /* if head not equals to tail ==> means write buffer is not empty Then we need to write the buffer to IO register and move the buffer pointer backward*/ if(w_head != w_tail){ - put32(AUX_MU_IO_REG, w_buffer[w_head]); + put32(PA2VA(AUX_MU_IO_REG), w_buffer[w_head]); w_head = (w_head+1)%BUFSIZE; } } @@ -294,12 +186,17 @@ void uart_irq_handler(void){ else if(iir & 0x04){ // if head not equals to tail+1 ==> means read buffer still have some place if(r_head != (r_tail+1)%BUFSIZE){ - r_buffer[r_tail] = get32(AUX_MU_IO_REG) & 0xff; + r_buffer[r_tail] = get32(PA2VA(AUX_MU_IO_REG)) & 0xff; r_tail = (r_tail+1)%BUFSIZE; } } +} - if (r_head != (r_tail + 1) % BUFSIZE) { +static void uart_irq_fini(){ + uint32 ier = get32(PA2VA(AUX_MU_IER_REG)); + ier &= ~(0x03); + + if (r_head != (r_tail + 1) % BUFSIZE) { ier = ier | 0x01; } @@ -307,27 +204,125 @@ void uart_irq_handler(void){ ier = ier | 0x02; } - put32(AUX_MU_IER_REG, ier); + put32(PA2VA(AUX_MU_IER_REG), ier); } -static void uart_irq_fini(){ - uint32 ier = get32(AUX_MU_IER_REG); - ier &= ~(0x03); +char uart_recv(void){ + return (uart_recv_fp)(); +} - if (r_head != (r_tail + 1) % BUFSIZE) { - ier = ier | 0x01; - } +void uart_recvn(char *buff, int n){ + while(n--) + *buff++ = (uart_recv_fp)(); +} - if (w_head != w_tail) { - ier = ier | 0x02; +void uart_send(char c){ + (uart_send_fp)(c); +} + +void uart_printf(const char *fmt, ...){ + va_list args; + va_start(args, fmt); + + _uart_printf(uart_send_fp, fmt, args); + + va_end(args); +} + +void uart_sync_printf(const char *fmt, ...){ + va_list args; + va_start(args, fmt); + + _uart_printf(uart_sync_send, fmt, args); + + va_end(args); +} + +void uart_sync_vprintf(const char *fmt, va_list args){ + _uart_printf(uart_sync_send, fmt, args); +} + +int uart_recv_line(char *buf, int maxline){ + int cnt = 0; + maxline--; + + while(maxline){ + char c = uart_recv(); + if(c == '\r') + break; + uart_send(c); + *buf = c; + buf++; + cnt++; + maxline--; + } + + *buf = 0; + return cnt; +} + +uint32 uart_recv_uint(void){ + char buf[4]; + + for (int i = 0; i < 4; ++i) { + buf[i] = uart_recv(); } - put32(AUX_MU_IER_REG, ier); + return *((uint32*)buf); +} + +void uart_sendn(const char *str, int n){ + while (n--) + uart_send(*str++); +} + +void uart_init (void) +{ + unsigned int selector; + + selector = get32(PA2VA(GPFSEL1)); + selector &= ~(7<<12); // clean gpio14 (rx) + selector |= 2<<12; // set alt5 for gpio14 + selector &= ~(7<<15); // clean gpio15 (tx) + selector |= 2<<15; // set alt5 for gpio15 + put32(PA2VA(GPFSEL1),selector); + + put32(PA2VA(GPPUD),0); //Disable pull up/down, floating input pin + delay(150); + put32(PA2VA(GPPUDCLK0),(1<<14)|(1<<15)); + delay(150); + put32(PA2VA(GPPUDCLK0),0); + + put32(PA2VA(AUX_ENABLES),1); //Enable mini uart (this also enables access to its registers) + put32(PA2VA(AUX_MU_CNTL_REG),0); //Disable auto flow control and disable receiver and transmitter (for now) + put32(PA2VA(AUX_MU_IER_REG),0); //Disable receive and transmit interrupts + put32(PA2VA(AUX_MU_LCR_REG),3); //Enable 8 bit mode + put32(PA2VA(AUX_MU_MCR_REG),0); //Set RTS line to be always high + put32(PA2VA(AUX_MU_BAUD_REG),270); //Set baud rate to 115200 + put32(PA2VA(AUX_MU_IIR_REG), 6); //Clear the Rx/Tx FIFO + put32(PA2VA(AUX_MU_CNTL_REG),3); //Finally, enable transmitter and receiver + + // UART start from synchronous mode + uart_sync_mode = 0; + uart_recv_fp = uart_sync_recv; + uart_send_fp = uart_sync_send; +} +int uart_irq_add(){ + uint32 iir = get32(PA2VA(AUX_MU_IIR_REG)); + // No interrupt + if(iir & 0x01) + return 0; + + disable_RW_interrupt(); + if(irq_add_task(uart_irq_handler, NULL,uart_irq_fini, UART_PRIO)) + enable_RW_interrupt(); + + return 1; } int uart_switch_mode(void){ uart_sync_mode = !uart_sync_mode; - uint32 ier = get32(AUX_MU_IER_REG); + uint32 ier = get32(PA2VA(AUX_MU_IER_REG)); // set bit 0 and bit 1 to zero ier &= ~(0x03); @@ -337,7 +332,7 @@ int uart_switch_mode(void){ uart_send_fp = uart_sync_send; // disable interrupt; - put32(AUX_MU_IER_REG, ier); + put32(PA2VA(AUX_MU_IER_REG), ier); } else{ // asynchronous mode @@ -345,12 +340,12 @@ int uart_switch_mode(void){ uart_send_fp = uart_async_send; // clear Rx/Tx FIFO - put32(AUX_MU_IIR_REG, 6); + put32(PA2VA(AUX_MU_IIR_REG), 6); // enable receive interrupt ier |= 0x01; - put32(AUX_MU_IER_REG, ier); + put32(PA2VA(AUX_MU_IER_REG), ier); } return uart_sync_mode; diff --git a/src/lib/mm.c b/src/lib/mm.c index fefb747e3..5a26e19d3 100644 --- a/src/lib/mm.c +++ b/src/lib/mm.c @@ -70,17 +70,17 @@ void mm_init(char *fdt_base){ if(!memory_end) uart_printf("[x] Cannot find memory end addr in fdt.\r\n"); - page_allocator_early_init((void *) 0,(void *) memory_end); + page_allocator_early_init((void *)PA2VA(0),(void *)PA2VA(memory_end)); sc_early_init(); // Spin tables for multicore boot - mem_reserve((void *)0, (void *)0x1000); + mem_reserve((void *)PA2VA(0), (void *)PA2VA(0x4000)); // Kernel image in the physical memory mem_reserve(_start, (void *)&_stack_top); // Initramfs - mem_reserve((void *)_initramfs_addr, (void *)_initramfs_end); + mem_reserve(_initramfs_addr, _initramfs_end); // Simple malloc mem_reserve(SMEM, EMEM); diff --git a/src/lib/mmu.c b/src/lib/mmu.c new file mode 100644 index 000000000..288c15839 --- /dev/null +++ b/src/lib/mmu.c @@ -0,0 +1,154 @@ +#include +#include +#include + +#define TCR_CONFIG_REGION_48bit (((64 - 48) << 0) | ((64 - 48) << 16)) +#define TCR_CONFIG_4KB ((0b00 << 14) | (0b10 << 30)) +#define TCR_CONFIG_DEFAULT (TCR_CONFIG_REGION_48bit | TCR_CONFIG_4KB) + +#define MAIR_DEVICE_nGnRnE 0b00000000 +#define MAIR_NORMAL_NOCACHE 0b01000100 +#define MAIR_IDX_DEVICE_nGnRnE 0 +#define MAIR_IDX_NORMAL_NOCACHE 1 + +#define PD_TABLE 0b11 +#define PD_BLOCK 0b01 +#define PD_ACCESS (1 << 10) +#define PD_PXN ((uint64)1 << 53) +#define PD_NSTABLE ((uint64)1 << 63) +#define PD_UXNTABLE ((uint64)1 << 60) +#define PD_MAIR_DEVICE_IDX (MAIR_IDX_DEVICE_nGnRnE << 2) +#define PD_MAIR_NORMAL_IDX (MAIR_IDX_NORMAL_NOCACHE << 2) +// Block Entry +#define PD_BE PD_ACCESS | PD_BLOCK +// Level 3 Block Entry +#define PD_L3BE PD_ACCESS | PD_TABLE + +#define BOOT_PGD ((pd_t *)0x1000) +#define BOOT_PUD ((pd_t *)0x2000) +#define BOOT_PMD ((pd_t *)0x3000) + +void mmu_init(void) +{ + uint32 sctlr_el1; + + // Set Translation Control Register + write_sysreg(TCR_EL1, TCR_CONFIG_DEFAULT); + + // Set Memory Attribute Indirection Register + write_sysreg(MAIR_EL1, + (MAIR_DEVICE_nGnRnE << (MAIR_IDX_DEVICE_nGnRnE * 8)) | + (MAIR_NORMAL_NOCACHE << (MAIR_IDX_NORMAL_NOCACHE * 8))); + + // Set Identity Paging + // 0x00000000 ~ 0x3f000000: Normal + // 0x3f000000 ~ 0x40000000: Device + // 0x40000000 ~ 0x80000000: Device + /* TODO: Support run user program in EL0 */ + BOOT_PGD[0] = (uint64)BOOT_PUD | PD_NSTABLE | PD_UXNTABLE | PD_TABLE; + + BOOT_PUD[0] = (uint64)BOOT_PMD | PD_TABLE; + BOOT_PUD[1] = 0x40000000 | PD_MAIR_DEVICE_IDX | PD_BE; + + for (int i = 0; i < 504; ++i) { + BOOT_PMD[i] = (i * (1 << 21)) | PD_MAIR_NORMAL_IDX | PD_BE; + } + + for (int i = 504; i < 512; ++i) { + BOOT_PMD[i] = (i * (1 << 21)) | PD_MAIR_DEVICE_IDX | PD_BE; + } + + write_sysreg(TTBR0_EL1, BOOT_PGD); + write_sysreg(TTBR1_EL1, BOOT_PGD); + + // Enable MMU + sctlr_el1 = read_sysreg(SCTLR_EL1); + write_sysreg(SCTLR_EL1, sctlr_el1 | 1); +} + +pd_t *pt_create(void) +{ + pd_t *pt = kmalloc(PAGE_TABLE_SIZE); + + for (int i = 0; i < PAGE_TABLE_SIZE / sizeof(pt[0]); ++i) { + pt[i] = 0; + } + + return pt; +} + +void pt_free(pd_t *pt) +{ + // TODO +} + +static void _pt_map(pd_t *pt, void *va, void *pa, uint64 flag) +{ + pd_t pd; + int idx; + + // 47 ~ 39, 38 ~ 30, 29 ~ 21, 20 ~ 12 + for (int layer = 3; layer > 0; --layer) { + idx = ((uint64)va >> (12 + 9 * layer)) & 0b111111111; + pd = pt[idx]; + + if (!(pd & 1)) { + // Invalid entry + pd_t *tmp = pt_create(); + pt[idx] = VA2PA(tmp) | PD_TABLE; + pt = tmp; + continue; + } + + // Must be a table entry + pt = (pd_t *)PA2VA(pd & ~((uint64)0xfff)); + } + + idx = ((uint64)va >> 12) & 0b111111111; + pd = pt[idx]; + + if (!(pd & 1)) { + // Invalid entry + // Access permissions + uint64 ap; + uint64 uxn; + + if (flag & PT_R) { + if (flag & PT_W) { + ap = 0b01; + } else { + ap = 0b11; + } + } else { + ap = 0b00; + } + + if (flag & PT_X) { + uxn = 0; + } else { + uxn = 1; + } + + pt[idx] = (uint64)pa | (uxn << 54) | PD_PXN | + PD_MAIR_NORMAL_IDX | (ap << 6) | PD_L3BE; + } + + // TODO: Already mapping, do nothing? +} + +void pt_map(pd_t *pt, void *va, uint64 size, void *pa, uint64 flag) +{ + if ((uint64)va & (PAGE_SIZE - 1)) { + return; + } + + if ((uint64)pa & (PAGE_SIZE - 1)) { + return; + } + + size = ALIGN(size, PAGE_SIZE); + + for (uint64 i = 0; i < size; i += PAGE_SIZE) { + _pt_map(pt, (void *)((uint64)va + i), (void *)((uint64)pa + i), flag); + } +} \ No newline at end of file diff --git a/src/lib/panic.c b/src/lib/panic.c new file mode 100644 index 000000000..f58fe335a --- /dev/null +++ b/src/lib/panic.c @@ -0,0 +1,18 @@ +#include +#include + +void panic(const char* fmt, ...){ + va_list args; + va_start(args, fmt); + + uart_sync_printf("\r\n"); + uart_sync_vprintf(fmt, args); + + va_end(args); + uart_sync_printf("\r\n"); + + // TODO: Show more information + + // Never return + while(1){} +} \ No newline at end of file diff --git a/src/lib/reboot.c b/src/lib/reboot.c index edb02b67d..92d7d3790 100644 --- a/src/lib/reboot.c +++ b/src/lib/reboot.c @@ -1,4 +1,5 @@ #include +#include void set(long addr, unsigned int value) { volatile unsigned int *point = (unsigned int*)addr; @@ -6,11 +7,14 @@ void set(long addr, unsigned int 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 + set(PA2VA(PM_RSTC), PM_PASSWORD | 0x20); // full reset + set(PA2VA(PM_WDOG), PM_PASSWORD | tick); // number of watchdog tick + + // Never return + while(1) {} } void cancel_reset() { - set(PM_RSTC, PM_PASSWORD | 0); // full reset - set(PM_WDOG, PM_PASSWORD | 0); // number of watchdog tick + set(PA2VA(PM_RSTC), PM_PASSWORD | 0); // full reset + set(PA2VA(PM_WDOG), PM_PASSWORD | 0); // number of watchdog tick } \ No newline at end of file diff --git a/src/lib/sched.S b/src/lib/sched.S index 0fd3da883..17572a71d 100644 --- a/src/lib/sched.S +++ b/src/lib/sched.S @@ -21,4 +21,13 @@ switch_to: // set_current msr tpidr_el1, x1 + // Switch page table 0 + ldr x9, [x1, 8*13] + and x9, x9, #0x0000ffffffffffff + dsb ish // ensure write has completed + msr ttbr0_el1, x9 // switch translation based address + tlbi vmalle1is // invalidate all TLB entries + dsb ish // ensure completion of TLB invalidatation + isb // clear pipeline + ret diff --git a/src/lib/signal.c b/src/lib/signal.c index 6267da1ac..13466bd8d 100644 --- a/src/lib/signal.c +++ b/src/lib/signal.c @@ -100,6 +100,7 @@ void handle_signal(trapframe *frame){ frame->sp_el0 = user_sp; frame->x0 = signal->signum; frame->elr_el1 = sigaction->sighand; + // TODO: Map sigreturn to user address space and allow user to execute frame->x30 = (uint64)sig_return; } @@ -143,6 +144,7 @@ static inline void user_sighand_copy(struct sigaction_t *from, struct sigaction_ to->sighand = (sighandler_t)((char *)from->sighand - offset); } +/* Copy current signal handler to @sighand*/ void sighand_copy(struct sighand_t *sighand, void *addrbase) { struct sighand_t *currhand; diff --git a/src/lib/syscall.c b/src/lib/syscall.c index 4003568c8..7deed31a3 100644 --- a/src/lib/syscall.c +++ b/src/lib/syscall.c @@ -11,6 +11,7 @@ #include #include #include +#include syscall_funcp syscall_table[] = { (syscall_funcp) syscall_getpid, // 0 @@ -45,10 +46,13 @@ void syscall_handler(trapframe regs, uint32 syn) { esr_el1 *esr = (esr_el1 *)&syn; uint64 syscall_num; - + + // uart_sync_printf("In syscall handler!\r\n"); // SVC instruction execution - if(esr->ec != 0x15) - return; + if(esr->ec != 0x15){ + show_trapframe(®s); + panic("[X] Panic: esr->ec: %x", esr->ec); + } syscall_num = regs.x8; @@ -87,7 +91,7 @@ void syscall_uart_write(trapframe *_, const char buf[], size_t size){ void syscall_exec(trapframe *_, const char* name, char *const argv[]){ void *data; char *kernel_sp; - char *user_sp; + // char *user_sp; uint32 datalen; datalen = cpio_load_prog((char *)_initramfs_addr, name, (char **)&data); @@ -100,12 +104,23 @@ void syscall_exec(trapframe *_, const char* name, char *const argv[]){ current->datalen = datalen; kernel_sp = (char *)current->kernel_stack + STACK_SIZE - 0x10; - user_sp = (char *)current->user_stack + STACK_SIZE - 0x10; + // user_sp = (char *)current->user_stack + STACK_SIZE - 0x10; // Reset signal signal_head_reset(current->signal); sighand_reset(current->sighand); - exec_user_prog(current->data, user_sp, kernel_sp); + // exec_user_prog(current->data, user_sp, kernel_sp); + + pt_free(current->page_table); + current->page_table = pt_create(); + pt_map(current->page_table, (void *)0, datalen,(void *)VA2PA(data), PT_R | PT_W | PT_X); + pt_map(current->page_table, (void *)0xffffffffb000, STACK_SIZE,(void *)VA2PA(current->user_stack), PT_R | PT_W); + + // TODO: Why is this needed for the vm.img to run? + pt_map(current->page_table, (void *)0x3c000000, 0x04000000,(void *)0x3c000000, PT_R | PT_W); + + set_page_table(current); + exec_user_prog((void *)0, (char *)0xffffffffeff0, kernel_sp); } void syscall_fork(trapframe *frame){ @@ -123,6 +138,12 @@ void syscall_fork(trapframe *frame){ memncpy(child->user_stack, current->user_stack, STACK_SIZE); memncpy(child->data, current->data, current->datalen); + pt_map(child->page_table, (void *)0, child->datalen,(void *)VA2PA(child->data), PT_R | PT_W | PT_X); + pt_map(child->page_table, (void *)0xffffffffb000, STACK_SIZE,(void *)VA2PA(child->user_stack), PT_R | PT_W); + + // TODO: Why is this needed for the vm.img to run? + pt_map(child->page_table, (void *)0x3c000000, 0x04000000,(void *)0x3c000000, PT_R | PT_W); + // Copy the signal handler sighand_copy(child->sighand, child->data); @@ -142,14 +163,15 @@ void syscall_fork(trapframe *frame){ // Adjust child trapframe child_frame = KSTACK_VARIABLE(frame); + // return value of fork for child process is 0 child_frame->x0 = 0; - child_frame->x30 = (uint64)DATA_VARIABLE(frame->x30); - child_frame->sp_el0 = USTACK_VARIABLE(frame->sp_el0); - child_frame->elr_el1 = DATA_VARIABLE(frame->elr_el1); + // child_frame->x30 = (uint64)DATA_VARIABLE(frame->x30); + // child_frame->sp_el0 = USTACK_VARIABLE(frame->sp_el0); + // child_frame->elr_el1 = DATA_VARIABLE(frame->elr_el1); sched_add_task(child); - // Set return value + // Set return value of parent process frame->x0 = child->tid; SYSCALL_FORK_END: @@ -165,7 +187,15 @@ void syscall_exit(trapframe *_) void syscall_mbox_call(trapframe *_, unsigned char ch, unsigned int *mbox) { - mbox_call(ch, mbox); + // copy data to kernel since mbox can't access the user space memory + int mbox_size = (int)mbox[0]; + if(mbox_size <= 0) + return; + char *kmbox = kmalloc(mbox_size); + memncpy(kmbox, (char *)mbox, mbox_size); + mbox_call(ch, (unsigned int*)kmbox); + memncpy((char *)mbox, kmbox, mbox_size); + kfree(kmbox); } void syscall_kill_pid(trapframe *_, int pid) diff --git a/src/lib/task.c b/src/lib/task.c index 334f66d72..e7503ca7d 100644 --- a/src/lib/task.c +++ b/src/lib/task.c @@ -23,14 +23,17 @@ task_struct *task_create(void){ task_struct *task; struct signal_head_t *signal; struct sighand_t *sighand; + pd_t *page_table; task = kmalloc(sizeof(task_struct)); signal = signal_head_create(); sighand = sighand_create(); + page_table = pt_create(); task->kernel_stack = NULL; task->user_stack = NULL; task->data = NULL; + task->page_table = page_table; INIT_LIST_HEAD(&task->list); list_add_tail(&task->task_list, &task_queue); task->status = TASK_NEW; @@ -59,6 +62,8 @@ void task_free(task_struct *task){ signal_head_free(task->signal); sighand_free(task->sighand); + pt_free(task->page_table); + kfree(task); } diff --git a/src/lib/timer.c b/src/lib/timer.c index 418c76472..06b70f016 100644 --- a/src/lib/timer.c +++ b/src/lib/timer.c @@ -20,12 +20,12 @@ int timer_show_enable; static void timer_enable(){ // Enable core0 cntp timer - put32(CORE0_TIMER_IRQ_CTRL,2); + put32(PA2VA(CORE0_TIMER_IRQ_CTRL),2); } static void timer_disable(){ // Disable core0 cntp timer - put32(CORE0_TIMER_IRQ_CTRL,0); + put32(PA2VA(CORE0_TIMER_IRQ_CTRL),0); } static void timer_set_boot_cnt(){ @@ -143,7 +143,7 @@ static void add_timer(timer_node *tn){ } } int timer_irq_add(){ - uint32 core0_irq_src = get32(CORE0_IRQ_SOURCE); + uint32 core0_irq_src = get32(PA2VA(CORE0_IRQ_SOURCE)); if (!(core0_irq_src & 0x02)) { return 0; diff --git a/test/initramfs_lab5.cpio b/test/initramfs_lab5.cpio new file mode 100755 index 0000000000000000000000000000000000000000..0676fb1584617bc4d73624b3b212a12133e97d8e GIT binary patch literal 247296 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 Date: Tue, 20 Jun 2023 00:36:02 +0800 Subject: [PATCH 02/10] fix signal related problem by Virtual Memory mapping --- inc/task.h | 9 +++++++++ inc/text_user_shared.h | 25 +++++++++++++++++++++++++ src/bootloader/linker.ld | 5 +++++ src/kernel/linker.ld | 10 ++++++++++ src/lib/exec.c | 7 ++++--- src/lib/mmu.c | 2 +- src/lib/signal.c | 19 ++++++------------- src/lib/syscall.c | 15 +++++++-------- src/lib/task.c | 9 +++++++++ 9 files changed, 76 insertions(+), 25 deletions(-) create mode 100644 inc/text_user_shared.h diff --git a/inc/task.h b/inc/task.h index 9c5eb125a..725517631 100644 --- a/inc/task.h +++ b/inc/task.h @@ -33,4 +33,13 @@ task_struct *task_create(void); void task_free(task_struct *task); task_struct *task_get_by_tid(uint32 tid); +/* + * Create initial mapping for user program + * + * 0x00003c000000 ~ 0x00003f000000: rw-: Mailbox address + * 0x7f0000000000 ~ : r-x: Kernel functions exposed to users + * 0xffffffffb000 ~ : rw-: Stack + */ +void task_init_map(task_struct *task); + #endif \ No newline at end of file diff --git a/inc/text_user_shared.h b/inc/text_user_shared.h new file mode 100644 index 000000000..3be42d0ed --- /dev/null +++ b/inc/text_user_shared.h @@ -0,0 +1,25 @@ +#ifndef _TEXT_USER_SHARED_H +#define _TEXT_USER_SHARED_H + +/* + * See the comment of task_init_map in task.h: + * + * 0x7f0000000000 ~ : r-x: Kernel functions exposed to users + * + * The kernel functions with the SECTION_TUS attribute will be mapped into + * this user address space. + */ + +#define SECTION_TUS __attribute__ ((section (".text.user.shared"))) + +#define TUS2VA(x) ((((uint64)x) - TEXT_USER_SHARED_BASE) + 0x7f0000000000) + +/* From linker.ld */ +extern char _stext_user_shared; +extern char _etext_user_shared; + +#define TEXT_USER_SHARED_BASE (uint64)(&_stext_user_shared) +#define TEXT_USER_SHARED_END (uint64)(&_etext_user_shared) +#define TEXT_USER_SHARED_LEN (TEXT_USER_SHARED_END - TEXT_USER_SHARED_BASE) + +#endif /* _TEXT_USER_SHARED_H */ \ No newline at end of file diff --git a/src/bootloader/linker.ld b/src/bootloader/linker.ld index e2a558690..08c3e55a1 100644 --- a/src/bootloader/linker.ld +++ b/src/bootloader/linker.ld @@ -1,8 +1,13 @@ _bootloader = 0x60000; _kernel = 0x80000; _stack_top = 0x400000; + +/* Define these just to prevent the linker from complaining */ +_etext_user_shared = 0; +_stext_user_shared = 0; _early_mem_base = 0x7000000; _early_mem_end = 0x7ffffff; + SECTIONS { . = _bootloader; diff --git a/src/kernel/linker.ld b/src/kernel/linker.ld index be4927e7b..5f2366d55 100644 --- a/src/kernel/linker.ld +++ b/src/kernel/linker.ld @@ -15,6 +15,16 @@ SECTIONS *(.text) } + . = ALIGN(0x1000); + + _stext_user_shared = .; + .text.user.shared : { + *(.text.user.shared) + } + + . = ALIGN(0x1000); + _etext_user_shared = .; + .rodata : { *(.rodata) } diff --git a/src/lib/exec.c b/src/lib/exec.c index 8284c60e7..c12f6341a 100644 --- a/src/lib/exec.c +++ b/src/lib/exec.c @@ -53,9 +53,10 @@ void sched_new_user_prog(char *filename){ task->regs.sp = (char *)task->kernel_stack + STACK_SIZE -0x10; pt_regs_init(&task->regs); - pt_map(task->page_table, (void *)0, datalen, (void *)VA2PA(task->data), PT_R | PT_W | PT_X); - pt_map(task->page_table, (void *)0xffffffffb000, STACK_SIZE,(void *)VA2PA(task->user_stack), PT_R | PT_W); - pt_map(task->page_table, (void *)0x3c000000, 0x04000000, (void *)0x3c000000, PT_R | PT_W); + task_init_map(task); + + // 0x000000000000 ~ : rwx: Code + pt_map(task->page_table, (void *)0, datalen,(void *)VA2PA(task->data), PT_R | PT_W | PT_X); sched_add_task(task); diff --git a/src/lib/mmu.c b/src/lib/mmu.c index 288c15839..7997cbe6b 100644 --- a/src/lib/mmu.c +++ b/src/lib/mmu.c @@ -44,7 +44,7 @@ void mmu_init(void) // 0x00000000 ~ 0x3f000000: Normal // 0x3f000000 ~ 0x40000000: Device // 0x40000000 ~ 0x80000000: Device - /* TODO: Support run user program in EL0 */ + BOOT_PGD[0] = (uint64)BOOT_PUD | PD_NSTABLE | PD_UXNTABLE | PD_TABLE; BOOT_PUD[0] = (uint64)BOOT_PMD | PD_TABLE; diff --git a/src/lib/signal.c b/src/lib/signal.c index 13466bd8d..0ff41736f 100644 --- a/src/lib/signal.c +++ b/src/lib/signal.c @@ -5,6 +5,7 @@ #include #include #include +#include #define DATA_OFF(x) ((uint64)current->data - (uint64)x) @@ -21,6 +22,7 @@ static void sig_ignore(int _){ return; } +void sig_return(void) SECTION_TUS; void sig_return(void){ asm volatile( "mov x8, 11\n" @@ -101,7 +103,7 @@ void handle_signal(trapframe *frame){ frame->x0 = signal->signum; frame->elr_el1 = sigaction->sighand; // TODO: Map sigreturn to user address space and allow user to execute - frame->x30 = (uint64)sig_return; + frame->x30 = TUS2VA(sig_return); } signal_del(signal); @@ -134,27 +136,18 @@ void sighand_reset(struct sighand_t *sighand) sighand->sigactions[SIGKILL].sighand = sig_terminate; } -static inline void kernel_sighand_copy(struct sigaction_t *from, struct sigaction_t *to){ - to->kernel_hand = 1; +static inline void _sighand_copy(struct sigaction_t *to, struct sigaction_t *from){ + to->kernel_hand = from->kernel_hand; to->sighand = from->sighand; } -static inline void user_sighand_copy(struct sigaction_t *from, struct sigaction_t *to, uint64 offset){ - to->kernel_hand = 0; - to->sighand = (sighandler_t)((char *)from->sighand - offset); -} - /* Copy current signal handler to @sighand*/ void sighand_copy(struct sighand_t *sighand, void *addrbase) { struct sighand_t *currhand; currhand = current->sighand; for (int i = 1; i < MAX_SIG_NUM; ++i) { - if (currhand->sigactions[i].kernel_hand) { - kernel_sighand_copy(&currhand->sigactions[i], &sighand->sigactions[i]); - } else { - user_sighand_copy(&currhand->sigactions[i], &sighand->sigactions[i], DATA_OFF(addrbase)); - } + _sighand_copy(&sighand->sigactions[i], &currhand->sigactions[i]); } } diff --git a/src/lib/syscall.c b/src/lib/syscall.c index 7deed31a3..1c25d5026 100644 --- a/src/lib/syscall.c +++ b/src/lib/syscall.c @@ -111,13 +111,13 @@ void syscall_exec(trapframe *_, const char* name, char *const argv[]){ sighand_reset(current->sighand); // exec_user_prog(current->data, user_sp, kernel_sp); + // Reset page table pt_free(current->page_table); current->page_table = pt_create(); - pt_map(current->page_table, (void *)0, datalen,(void *)VA2PA(data), PT_R | PT_W | PT_X); - pt_map(current->page_table, (void *)0xffffffffb000, STACK_SIZE,(void *)VA2PA(current->user_stack), PT_R | PT_W); + task_init_map(current); - // TODO: Why is this needed for the vm.img to run? - pt_map(current->page_table, (void *)0x3c000000, 0x04000000,(void *)0x3c000000, PT_R | PT_W); + // 0x000000000000 ~ : rwx: Code + pt_map(current->page_table, (void *)0, datalen,(void *)VA2PA(data), PT_R | PT_W | PT_X); set_page_table(current); exec_user_prog((void *)0, (char *)0xffffffffeff0, kernel_sp); @@ -138,11 +138,10 @@ void syscall_fork(trapframe *frame){ memncpy(child->user_stack, current->user_stack, STACK_SIZE); memncpy(child->data, current->data, current->datalen); - pt_map(child->page_table, (void *)0, child->datalen,(void *)VA2PA(child->data), PT_R | PT_W | PT_X); - pt_map(child->page_table, (void *)0xffffffffb000, STACK_SIZE,(void *)VA2PA(child->user_stack), PT_R | PT_W); + task_init_map(child); - // TODO: Why is this needed for the vm.img to run? - pt_map(child->page_table, (void *)0x3c000000, 0x04000000,(void *)0x3c000000, PT_R | PT_W); + // 0x000000000000 ~ : rwx: Code + pt_map(child->page_table, (void *)0, child->datalen,(void *)VA2PA(child->data), PT_R | PT_W | PT_X); // Copy the signal handler sighand_copy(child->sighand, child->data); diff --git a/src/lib/task.c b/src/lib/task.c index e7503ca7d..94c5f083a 100644 --- a/src/lib/task.c +++ b/src/lib/task.c @@ -1,6 +1,8 @@ #include #include #include +#include +#include static struct list_head task_queue; @@ -67,6 +69,13 @@ void task_free(task_struct *task){ kfree(task); } +void task_init_map(task_struct *task){ + // TODO: map the return addres of mailbox_call + pt_map(task->page_table, (void *)0x3c000000, 0x03000000,(void *)0x3c000000, PT_R | PT_W); + pt_map(task->page_table, (void *)0x7f0000000000, TEXT_USER_SHARED_LEN,(void *)VA2PA(TEXT_USER_SHARED_BASE), PT_R | PT_X); + pt_map(task->page_table, (void *)0xffffffffb000, STACK_SIZE,(void *)VA2PA(task->user_stack), PT_R | PT_W); +} + task_struct *task_get_by_tid(uint32 tid){ task_struct *task; From b684453bf228aaa5aca1b510b4b9a33cacf67f1f Mon Sep 17 00:00:00 2001 From: Shin-Yan Date: Wed, 21 Jun 2023 00:33:03 +0800 Subject: [PATCH 03/10] add demand paging and mmap --- Makefile | 7 +- inc/mm.h | 2 +- inc/mmu.h | 17 +- inc/sched.h | 7 +- inc/signal.h | 2 +- inc/syscall.h | 8 +- inc/task.h | 2 + inc/utils.h | 10 +- rootfs/Makefile | 31 ---- rootfs/key.txt | 1 - rootfs/linker.ld | 26 --- rootfs/test.txt | 1 - rootfs/test1.txt | 4 - rootfs/userprog2 | Bin 36 -> 0 bytes rootfs/userprog2.s | 19 --- src/kernel/boot.S | 2 +- src/lib/builtin.c | 8 +- src/lib/entry.c | 24 +++ src/lib/exec.c | 8 +- src/lib/mmu.c | 394 ++++++++++++++++++++++++++++++++++++++++++++- src/lib/panic.c | 2 +- src/lib/signal.c | 4 +- src/lib/syscall.c | 61 +++---- src/lib/task.c | 31 ++-- 24 files changed, 496 insertions(+), 175 deletions(-) delete mode 100644 rootfs/Makefile delete mode 100644 rootfs/key.txt delete mode 100644 rootfs/linker.ld delete mode 100644 rootfs/test.txt delete mode 100644 rootfs/test1.txt delete mode 100755 rootfs/userprog2 delete mode 100644 rootfs/userprog2.s create mode 100644 src/lib/entry.c diff --git a/Makefile b/Makefile index 85d33cb21..7390e9336 100644 --- a/Makefile +++ b/Makefile @@ -47,7 +47,6 @@ endif # @$(ECHO) "lib object file is $(LIB_OBJ_FILE)" all:$(KERNEL_IMG) $(BOOTLOADER_IMG) - cd rootfs && make $(KERNEL_IMG): $(KERNEL_ELF) $(CROSS_COMPILER)objcopy -O binary $^ $(KERNEL_IMG) @@ -92,7 +91,7 @@ qemub: all $(INITRAMFS_CPIO) $(RPI3_DTB) qemu-system-aarch64 -M raspi3 -kernel $(BOOTLOADER_IMG) -display none \ -dtb $(RPI3_DTB) \ -initrd $(INITRAMFS_CPIO) \ - -serial null -serial pty + -serial null -serial stdio qemuk: all $(INITRAMFS_CPIO) $(RPI3_DTB) qemu-system-aarch64 -M raspi3 -kernel $(KERNEL_IMG) -display none \ -dtb $(RPI3_DTB) \ @@ -103,7 +102,7 @@ qemutest: all $(INITRAMFS_CPIO) $(RPI3_DTB) qemu-system-aarch64 -M raspi3 -kernel $(KERNEL_IMG) -display none \ -dtb $(RPI3_DTB) \ -initrd $(INITRAMFS_CPIO) \ - -serial null -serial stdio + -serial null -serial stdio -s -S qemutty: $(KERNEL_IMG) @@ -112,10 +111,8 @@ qemutty: $(KERNEL_IMG) .PHONY: clean clean: - cd rootfs && make clean rm -f $(KERNEL_OBJ_FILE) $(KERNEL_ELF) $(BOOTLOADER_OBJ_FILE) $(BOOTLOADER_ELF) $(LIB_OBJ_FILE) $(INITRAMFS_CPIO) .PHONY: clean-all clean-all: clean - cd rootfs && make clean-all rm -f $(KERNEL_IMG) $(BOOTLOADER_IMG) \ No newline at end of file diff --git a/inc/mm.h b/inc/mm.h index 317fe6bcd..1b91424d3 100644 --- a/inc/mm.h +++ b/inc/mm.h @@ -2,7 +2,7 @@ #define _MM_H #ifndef __ASSEMBLER__ -void memzero(unsigned long src, unsigned long n); +void memzero(char* src, unsigned long n); void memncpy(char *dst, char *src, unsigned long n); void mm_init(char *fdt_base); diff --git a/inc/mmu.h b/inc/mmu.h index 9045c4a90..ae91f6a95 100644 --- a/inc/mmu.h +++ b/inc/mmu.h @@ -18,14 +18,7 @@ #define VMA_PA 0x0008 #define VMA_KVA 0x0010 -#define VMA_AMON 0x0020 - -#define PROT_NOTE 0 -#define PROT_READ 1 -#define PROT_WRITE 2 -#define PROT_EXEC 4 - -#define MAP_ANONYMOUS 0x8000 +#define VMA_ANON 0x0020 typedef uint64 pd_t; @@ -41,6 +34,14 @@ typedef struct{ struct list_head vma; } vm_area_meta_t; +#define PROT_NOTE 0 +#define PROT_READ 1 +#define PROT_WRITE 2 +#define PROT_EXEC 4 + +#define MAP_ANONYMOUS 0x0020 +#define MAP_POPULATE 0x8000 + void mmu_init(void); pd_t *pt_create(void); diff --git a/inc/sched.h b/inc/sched.h index ce3ed21a8..fc5e02b04 100644 --- a/inc/sched.h +++ b/inc/sched.h @@ -27,11 +27,12 @@ typedef struct _task_struct { struct pt_regs regs; pd_t *page_table; /* The order of the above elements cannot be changed*/ + vm_area_meta_t *address_space; void *kernel_stack; - void *user_stack; + // void *user_stack; /* TODO: Update to address_space*/ - void *data; - uint32 datalen; + // void *data; + // uint32 datalen; struct list_head list; struct list_head task_list; uint16 status; diff --git a/inc/signal.h b/inc/signal.h index 028439f5a..a5cecba47 100644 --- a/inc/signal.h +++ b/inc/signal.h @@ -69,7 +69,7 @@ void handle_signal(trapframe *_); struct sighand_t *sighand_create(void); void sighand_free(struct sighand_t *sighand); void sighand_reset(struct sighand_t *sighand); -void sighand_copy(struct sighand_t *sighand, void *addrbase); +void sighand_copy(struct sighand_t *sighand); void syscall_signal(trapframe *_, uint32 signal, void (*handler)(int)); void syscall_kill(trapframe *_, int pid, int signal); void syscall_sigreturn(trapframe *_); diff --git a/inc/syscall.h b/inc/syscall.h index 90939d93a..ca6c7cf64 100644 --- a/inc/syscall.h +++ b/inc/syscall.h @@ -2,12 +2,6 @@ #define _SYSCALL_H #include -typedef struct { - unsigned int iss:25, // Instruction specific syndrome - il:1, // Instruction length bit - ec:6; // Exception class -} esr_el1; - #define KSTACK_VARIABLE(x) \ (void *)((uint64)x - \ (uint64)current->kernel_stack + \ @@ -25,7 +19,7 @@ typedef struct { typedef void *(*syscall_funcp)(); -void syscall_handler(trapframe regs, uint32 syn); +void syscall_handler(trapframe *regs); void syscall_getpid(trapframe *frame); void syscall_uart_read(trapframe *_, char buf[], size_t size); diff --git a/inc/task.h b/inc/task.h index 725517631..c74998e92 100644 --- a/inc/task.h +++ b/inc/task.h @@ -42,4 +42,6 @@ task_struct *task_get_by_tid(uint32 tid); */ void task_init_map(task_struct *task); +void task_reset_mm(task_struct *task); + #endif \ No newline at end of file diff --git a/inc/utils.h b/inc/utils.h index 8d60cf3b8..e3c04a07b 100644 --- a/inc/utils.h +++ b/inc/utils.h @@ -15,7 +15,7 @@ asm volatile("msr DAIFSet, 0xf"); \ } -#define set_page_table(task) do { \ +#define set_page_table(page_table) do { \ asm volatile( \ "mov x9, %0\n" \ "and x9, x9, #0x0000ffffffffffff\n" \ @@ -24,10 +24,16 @@ "tlbi vmalle1is\n" \ "dsb ish\n" \ "isb\n" \ - :: "r" (task->page_table) \ + :: "r" (page_table) \ ); \ } while (0) +#define get_page_table() ({ \ + uint64 __val; \ + __val = PA2VA(read_sysreg(TTBR0_EL1)); \ + __val; \ +}) + #define get_elem_idx(elem, array) \ (((char *)elem - (char *)array) / sizeof(array[0])) diff --git a/rootfs/Makefile b/rootfs/Makefile deleted file mode 100644 index 3d9b5cb83..000000000 --- a/rootfs/Makefile +++ /dev/null @@ -1,31 +0,0 @@ -CROSS_COMPILE ?= aarch64-linux-gnu- -CC := $(CROSS_COMPILE)gcc -LD := $(CROSS_COMPILE)ld -CFLAGS := -Wall -nostdlib -nostartfiles -ffreestanding -mgeneral-regs-only -all: userprog2 - -# userprog1: userprog1.elf -# $(CROSS_COMPILE)objcopy -O binary $^ $@ - -# userprog1.elf: linker.ld userprog1.o -# $(LD) $(LDFLAGS) -T -Iinc $< -o $@ userprog1.o - -# userprog1.o: userprog1.c -# $(CC) $(CFLAGS) -c $< -o $@ - -userprog2: userprog2.elf - $(CROSS_COMPILE)objcopy -O binary $^ $@ - -userprog2.elf: linker.ld userprog2.o - $(LD) $(LDFLAGS) -T $< -o $@ userprog2.o - -userprog2.o: userprog2.s - $(CC) $(CFLAGS) -c $< -o $@ - -.PHONY: clean -clean: - rm -f *.o *.elf - -.PHONY: clean-all -clean-all: clean - rm -f userprog1 userprog2 \ No newline at end of file diff --git a/rootfs/key.txt b/rootfs/key.txt deleted file mode 100644 index adbfe0be4..000000000 --- a/rootfs/key.txt +++ /dev/null @@ -1 +0,0 @@ -fdsafhdjsklafhdjkslahnmer,.wqbnjtvuicx8734y7 \ No newline at end of file diff --git a/rootfs/linker.ld b/rootfs/linker.ld deleted file mode 100644 index ac633d555..000000000 --- a/rootfs/linker.ld +++ /dev/null @@ -1,26 +0,0 @@ -SECTIONS -{ - .start :{ - *(.start) - } - - .text : { - *(.text) - } - - .rodata : { - *(.rodata) - } - - .data : { - *(.got) - *(.got.plt) - *(.data*) - } - - _sbss = .; - .bss : { - *(.bss*) - } - _ebss = .; -} \ No newline at end of file diff --git a/rootfs/test.txt b/rootfs/test.txt deleted file mode 100644 index 329c63806..000000000 --- a/rootfs/test.txt +++ /dev/null @@ -1 +0,0 @@ -hello test \ No newline at end of file diff --git a/rootfs/test1.txt b/rootfs/test1.txt deleted file mode 100644 index f5fc2e3bf..000000000 --- a/rootfs/test1.txt +++ /dev/null @@ -1,4 +0,0 @@ -AAA -BBB -CCC -EndOfTest \ No newline at end of file diff --git a/rootfs/userprog2 b/rootfs/userprog2 deleted file mode 100755 index 237765563c33740a73f2b6917a89ccccc207970c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36 pcmZQzXt>0{!Z6W;vEdRU1H%=05r&W5|Nn=q0E&b7|Nj3M2LRVl4m$t< diff --git a/rootfs/userprog2.s b/rootfs/userprog2.s deleted file mode 100644 index 4e8787985..000000000 --- a/rootfs/userprog2.s +++ /dev/null @@ -1,19 +0,0 @@ -.section ".text" -.global _start -_start: - mov x0, 0 -1: - add x0, x0, 1 - - // syscall_test - mov x8, 10 - svc 0 - - cmp x0, 5 - blt 1b -1: - // syscall exit - mov x8, 5 - svc 0 - - b 1b \ No newline at end of file diff --git a/src/kernel/boot.S b/src/kernel/boot.S index 263b6f253..6d71d9c3b 100644 --- a/src/kernel/boot.S +++ b/src/kernel/boot.S @@ -196,7 +196,7 @@ l64_syn_eh: mov x0, sp // Get exception class ({EC[31:26]}) mrs x1, esr_el1 - bl syscall_handler + bl el0_sync_handler mov x0, sp bl exit_to_user_mode diff --git a/src/lib/builtin.c b/src/lib/builtin.c index 4dc5c3e78..c930bb2d1 100644 --- a/src/lib/builtin.c +++ b/src/lib/builtin.c @@ -104,12 +104,8 @@ void _exec(char *filename){ if(datalen == 0) return; - current->user_stack = kmalloc(STACK_SIZE); - current->data = data; - current->datalen = datalen; - pt_map(current->page_table, (void *)0, datalen, (void *)VA2PA(current->data), PT_R | PT_W | PT_X); - pt_map(current->page_table, (void *)0xffffffffb000, STACK_SIZE,(void *)VA2PA(current->user_stack), PT_R | PT_W); - pt_map(current->page_table, (void *)0x3c000000, 0x04000000, (void *)0x3c000000, PT_R | PT_W); + task_init_map(current); + vma_map(current->address_space, (void *)0, datalen, VMA_R | VMA_W | VMA_X | VMA_KVA, data); user_prog_start(); } diff --git a/src/lib/entry.c b/src/lib/entry.c new file mode 100644 index 000000000..229ffaf83 --- /dev/null +++ b/src/lib/entry.c @@ -0,0 +1,24 @@ +#include +#include +#include +#include +#include + +void el0_sync_handler(trapframe *regs, uint32 syn){ + esr_el1_t *esr; + + esr = (esr_el1_t *)&syn; + + switch(esr->ec){ + case EC_SVC_64: + syscall_handler(regs); + break; + case EC_IA_LE: + case EC_DA_LE: + mem_abort(esr); + break; + default: + show_trapframe(regs); + panic("esr->ec: %x", esr->ec); + } +} \ No newline at end of file diff --git a/src/lib/exec.c b/src/lib/exec.c index c12f6341a..0e1044c8c 100644 --- a/src/lib/exec.c +++ b/src/lib/exec.c @@ -47,16 +47,16 @@ void sched_new_user_prog(char *filename){ task = task_create(); task->kernel_stack = kmalloc(STACK_SIZE); - task->user_stack = kmalloc(STACK_SIZE); - task->data = data; - task->datalen = datalen; + // task->user_stack = kmalloc(STACK_SIZE); + // task->data = data; + // task->datalen = datalen; task->regs.sp = (char *)task->kernel_stack + STACK_SIZE -0x10; pt_regs_init(&task->regs); task_init_map(task); // 0x000000000000 ~ : rwx: Code - pt_map(task->page_table, (void *)0, datalen,(void *)VA2PA(task->data), PT_R | PT_W | PT_X); + vma_map(task->address_space, (void *)0, datalen, VMA_R | VMA_W | VMA_X | VMA_KVA, data); sched_add_task(task); diff --git a/src/lib/mmu.c b/src/lib/mmu.c index 7997cbe6b..cc8549c66 100644 --- a/src/lib/mmu.c +++ b/src/lib/mmu.c @@ -1,5 +1,12 @@ #include +#include #include +#include +#include +#include +#include +#include +#include #include #define TCR_CONFIG_REGION_48bit (((64 - 48) << 0) | ((64 - 48) << 16)) @@ -28,6 +35,154 @@ #define BOOT_PUD ((pd_t *)0x2000) #define BOOT_PMD ((pd_t *)0x3000) +static void segmentation_fault(void) +{ + uart_sync_printf("[Segmentation fault]: Kill Process\r\n"); + exit_user_prog(); + + // Never reach +} + +static vm_area_t *vma_create(void *va, uint64 size, uint64 flag, void *addr) +{ + vm_area_t *vma; + + vma = kmalloc(sizeof(vm_area_t)); + size = ALIGN(size, PAGE_SIZE); + + vma->va_begin = (uint64)va; + vma->va_end = (uint64)va + size; + vma->flag = flag; + + if (vma->flag & VMA_ANON) { + vma->kva = 0; + } else if (vma->flag & VMA_PA) { + vma->kva = PA2VA(addr); + } else if (vma->flag & VMA_KVA) { + vma->kva = (uint64)addr; + } else { + // Unexpected + panic("vma_create flag error"); + } + + return vma; +} + +static void clone_uva_region(uint64 uva_begin, uint64 uva_end, + pd_t *pt, uint64 flag) +{ + for (uint64 addr = uva_begin; addr < uva_end; addr += PAGE_SIZE) { + void *new_kva; + uint64 par; + + // try to get the PA of UVA + asm volatile ( + "at s1e0r, %0" + :: "r" (addr) + ); + + par = read_sysreg(PAR_EL1); + + if (PAR_FAILED(par)) { + // VA to PA conversion aborted + continue; + } + + // convert PA to KVA + par = PA2VA(PAR_PA(par)); + + // Allocate a page and copy content to this page + new_kva = kmalloc(PAGE_SIZE); + memncpy(new_kva, (void *)par, PAGE_SIZE); + + // map @new_kva into @pt + pt_map(pt, (void *)addr, PAGE_SIZE, (void *)VA2PA(new_kva), flag); + } +} + +static vm_area_t *vma_clone(vm_area_t *vma, pd_t *page_table) +{ + vm_area_t *new_vma; + + new_vma = kmalloc(sizeof(vm_area_t)); + + new_vma->va_begin = vma->va_begin; + new_vma->va_end = vma->va_end; + new_vma->flag = vma->flag; + + if (vma->flag & VMA_ANON) { + clone_uva_region(vma->va_begin, vma->va_end, page_table, vma->flag); + new_vma->kva = 0; + } else if (vma->flag & VMA_PA) { + new_vma->kva = vma->kva; + } else if (vma->flag & VMA_KVA) { + void *new_kva; + + new_kva = kmalloc(vma->va_end - vma->va_begin); + memncpy(new_kva, (void *)vma->kva, vma->va_end - vma->va_begin); + + new_vma->kva = (uint64)new_kva; + } else { + // Unexpected + panic("vma_clone flag error"); + } + + return new_vma; +} + +static void free_uva_region(uint64 uva_begin, uint64 uva_end) +{ + for (uint64 addr = uva_begin; addr < uva_end; addr += PAGE_SIZE) { + uint64 par; + + // try to get the PA of UVA + asm volatile ( + "at s1e0r, %0" + :: "r" (addr) + ); + + par = read_sysreg(PAR_EL1); + + if (PAR_FAILED(par)) { + // VA to PA conversion aborted + continue; + } + + // convert PA to KVA + par = PA2VA(PAR_PA(par)); + + // free KVA + kfree((void *)par); + } +} + +static void vma_free(vm_area_t *vma) +{ + if (vma->kva && vma->flag & VMA_KVA) { + kfree((void *)vma->kva); + } else if (vma->flag & VMA_ANON) { + free_uva_region(vma->va_begin, vma->va_end); + } else if (!(vma->flag & VMA_PA)){ + // Unexpected + panic("vma_free flag error"); + } + + kfree(vma); +} + +static vm_area_t *vma_find(vm_area_meta_t *vma_meta, uint64 addr) +{ + vm_area_t *vma; + + list_for_each_entry(vma, &vma_meta->vma, list) { + if (vma->va_begin <= addr && addr < vma->va_end) { + return vma; + } + } + + return NULL; +} + void mmu_init(void) { uint32 sctlr_el1; @@ -44,7 +199,6 @@ void mmu_init(void) // 0x00000000 ~ 0x3f000000: Normal // 0x3f000000 ~ 0x40000000: Device // 0x40000000 ~ 0x80000000: Device - BOOT_PGD[0] = (uint64)BOOT_PUD | PD_NSTABLE | PD_UXNTABLE | PD_TABLE; BOOT_PUD[0] = (uint64)BOOT_PMD | PD_TABLE; @@ -151,4 +305,242 @@ void pt_map(pd_t *pt, void *va, uint64 size, void *pa, uint64 flag) for (uint64 i = 0; i < size; i += PAGE_SIZE) { _pt_map(pt, (void *)((uint64)va + i), (void *)((uint64)pa + i), flag); } +} + +vm_area_meta_t *vma_meta_create(void) +{ + vm_area_meta_t *vma_meta; + + vma_meta = kmalloc(sizeof(vm_area_meta_t)); + INIT_LIST_HEAD(&vma_meta->vma); + + return vma_meta; +} + +void vma_meta_free(vm_area_meta_t *vma_meta, pd_t *page_table) +{ + uint64 old_page_table; + vm_area_t *vma, *safe; + + old_page_table = get_page_table(); + set_page_table(page_table); + + preempt_disable(); + + list_for_each_entry_safe(vma, safe, &vma_meta->vma, list) { + vma_free(vma); + } + + preempt_enable(); + + kfree(vma_meta); + + set_page_table(old_page_table); +} + +void vma_meta_copy(vm_area_meta_t *to, vm_area_meta_t *from, pd_t *page_table) +{ + uint64 old_page_table; + vm_area_t *vma, *new_vma; + + old_page_table = get_page_table(); + set_page_table(page_table); + + preempt_disable(); + + list_for_each_entry(vma, &from->vma, list) { + new_vma = vma_clone(vma, (pd_t *)old_page_table); + + list_add_tail(&new_vma->list, &to->vma); + } + + preempt_enable(); + + set_page_table(old_page_table); +} + +void vma_map(vm_area_meta_t *vma_meta, void *va, uint64 size, + uint64 flag, void *addr) +{ + vm_area_t *vma; + int cnt = 0; + + if (flag & VMA_PA) cnt++; + if (flag & VMA_KVA) cnt++; + if (flag & VMA_ANON) cnt++; + + if (cnt != 1) { + return; + } + + if ((uint64)va & (PAGE_SIZE - 1)) { + return; + } + + vma = vma_find(vma_meta, (uint64)va); + if (vma) { + return; + } + + vma = vma_find(vma_meta, (uint64)va + size - 1); + if (vma) { + return; + } + + vma = vma_create(va, size, flag, addr); + + list_add_tail(&vma->list, &vma_meta->vma); +} + +static void do_page_fault(esr_el1_t *esr) +{ + uint64 far; + uint64 va; + uint64 fault_perm; + vm_area_t *vma; + + far = read_sysreg(FAR_EL1); + + vma = vma_find(current->address_space, far); + + if (!vma) { + segmentation_fault(); + // Never reach + } + + // Check permission + if (esr->ec == EC_IA_LE) { + // Execute abort + fault_perm = VMA_X; + } else if (ISS_WnR(esr)) { + // Write abort + fault_perm = VMA_W; + } else { + // Read abort + fault_perm = VMA_R; + } + + if (!(vma->flag & fault_perm)) { + goto PAGE_FAULT_INVALID; + } + + va = far & ~(PAGE_SIZE - 1); + + if (vma->kva) { + uint64 offset; + + offset = va - vma->va_begin; + + pt_map(current->page_table, (void *)va, PAGE_SIZE, + (void *)VA2PA(vma->kva + offset), vma->flag); + } else if (vma->flag & VMA_ANON) { + void *kva = kmalloc(PAGE_SIZE); + + memzero(kva, PAGE_SIZE); + + pt_map(current->page_table, (void *)va, PAGE_SIZE, + (void *)VA2PA(kva), vma->flag); + } else { + // Unexpected result + goto PAGE_FAULT_INVALID; + } + + return; + +PAGE_FAULT_INVALID: + segmentation_fault(); + + // Never reach +} + +void mem_abort(esr_el1_t *esr) +{ + int fsc; +#ifdef DEMANDING_PAGE_DEBUG + uint64 addr; + + addr = read_sysreg(FAR_EL1); +#endif + + fsc = ISS_FSC(esr); + + switch (fsc) { + case FSC_TF_L0: + case FSC_TF_L1: + case FSC_TF_L2: + case FSC_TF_L3: +#ifdef DEMANDING_PAGE_DEBUG + uart_sync_printf("[Translation fault]: 0x%llx\r\n", addr); +#endif + do_page_fault(esr); + break; + default: + segmentation_fault(); + + // Never reach + } +} + +void syscall_mmap(trapframe *frame, void *addr, size_t len, int prot, + int flags, int fd, int file_offset) +{ + vm_area_t *vma; + int mapflag; + + // do some initial work + len = ALIGN(len, PAGE_SIZE); + + if (addr == NULL) { + addr = (void *)0x550000000000; + } + + while (1) { + if ((uint64)addr > 0x0000ffffffffffff) { + frame->x0 = 0; + return; + } + + vma = vma_find(current->address_space, (uint64)addr); + if (vma) { + addr = (void *)((uint64)addr + 0x10000000); + continue; + } + + vma = vma_find(current->address_space, (uint64)addr + len - 1); + if (vma) { + addr = (void *)((uint64)addr + 0x10000000); + continue; + } + + break; + } + + mapflag = 0; + + if (prot & PROT_READ) mapflag |= VMA_R; + if (prot & PROT_WRITE) mapflag |= VMA_W; + if (prot & PROT_EXEC) mapflag |= VMA_X; + + if (flags & MAP_POPULATE) { + void *kva; + + mapflag |= VMA_KVA; + + kva = kmalloc(len); + memzero(kva, len); + + vma_map(current->address_space, addr, len, mapflag, kva); + + pt_map(current->page_table, addr, len, (void *)VA2PA(kva), mapflag); + } else if (flags & MAP_ANONYMOUS) { + mapflag |= VMA_ANON; + + vma_map(current->address_space, addr, len, mapflag, NULL); + } else { + // Unexpected. + frame->x0 = 0; + return; + } + + frame->x0 = (uint64)addr; } \ No newline at end of file diff --git a/src/lib/panic.c b/src/lib/panic.c index f58fe335a..a4298a845 100644 --- a/src/lib/panic.c +++ b/src/lib/panic.c @@ -5,7 +5,7 @@ void panic(const char* fmt, ...){ va_list args; va_start(args, fmt); - uart_sync_printf("\r\n"); + uart_sync_printf("\r\n[Kernel Panic] \r\n"); uart_sync_vprintf(fmt, args); va_end(args); diff --git a/src/lib/signal.c b/src/lib/signal.c index 0ff41736f..ce6979b46 100644 --- a/src/lib/signal.c +++ b/src/lib/signal.c @@ -7,8 +7,6 @@ #include #include -#define DATA_OFF(x) ((uint64)current->data - (uint64)x) - #define SIG_DFL (sighandler_t)0 #define SIG_IGN (sighandler_t)1 @@ -142,7 +140,7 @@ static inline void _sighand_copy(struct sigaction_t *to, struct sigaction_t *fro } /* Copy current signal handler to @sighand*/ -void sighand_copy(struct sighand_t *sighand, void *addrbase) +void sighand_copy(struct sighand_t *sighand) { struct sighand_t *currhand; currhand = current->sighand; diff --git a/src/lib/syscall.c b/src/lib/syscall.c index 1c25d5026..00a824b13 100644 --- a/src/lib/syscall.c +++ b/src/lib/syscall.c @@ -11,7 +11,7 @@ #include #include #include -#include +#include syscall_funcp syscall_table[] = { (syscall_funcp) syscall_getpid, // 0 @@ -24,8 +24,9 @@ syscall_funcp syscall_table[] = { (syscall_funcp) syscall_kill_pid, // 7 (syscall_funcp) syscall_signal, // 8 (syscall_funcp) syscall_kill, // 9 - (syscall_funcp) syscall_test, // 10 + (syscall_funcp) syscall_mmap, // 10 (syscall_funcp) syscall_sigreturn, // 11 + (syscall_funcp) syscall_test, // 12 }; static inline void copy_regs(struct pt_regs *regs) @@ -42,19 +43,14 @@ static inline void copy_regs(struct pt_regs *regs) regs->x28 = current->regs.x28; } -void syscall_handler(trapframe regs, uint32 syn) +void syscall_handler(trapframe *regs) { - esr_el1 *esr = (esr_el1 *)&syn; uint64 syscall_num; - - // uart_sync_printf("In syscall handler!\r\n"); - // SVC instruction execution - if(esr->ec != 0x15){ - show_trapframe(®s); - panic("[X] Panic: esr->ec: %x", esr->ec); - } - syscall_num = regs.x8; + syscall_num = regs->x8; + + if(syscall_num == 10) + uart_sync_printf("addr: 0x%x, len: 0x%x, prot: 0x%x, flags: 0x%x, fd: 0x%x, file_offset: 0x%x\r\n", regs->x0, regs->x1, regs->x2, regs->x3, regs->x4, regs->x5); if (syscall_num >= ARRAY_SIZE(syscall_table)) { // Invalid syscall @@ -64,13 +60,13 @@ void syscall_handler(trapframe regs, uint32 syn) enable_interrupt(); // TODO: bring the arguments to syscall (syscall_table[syscall_num])( - ®s, - regs.x0, - regs.x1, - regs.x2, - regs.x3, - regs.x4, - regs.x5 + regs, + regs->x0, + regs->x1, + regs->x2, + regs->x3, + regs->x4, + regs->x5 ); disable_interrupt(); } @@ -99,27 +95,20 @@ void syscall_exec(trapframe *_, const char* name, char *const argv[]){ if(datalen == 0) return; - kfree(current->data); - current->data = data; - current->datalen = datalen; - kernel_sp = (char *)current->kernel_stack + STACK_SIZE - 0x10; // user_sp = (char *)current->user_stack + STACK_SIZE - 0x10; // Reset signal signal_head_reset(current->signal); sighand_reset(current->sighand); - // exec_user_prog(current->data, user_sp, kernel_sp); - - // Reset page table - pt_free(current->page_table); - current->page_table = pt_create(); + //Rest addr space & page table + task_reset_mm(current); task_init_map(current); // 0x000000000000 ~ : rwx: Code - pt_map(current->page_table, (void *)0, datalen,(void *)VA2PA(data), PT_R | PT_W | PT_X); + vma_map(current->address_space, (void *)0, datalen, VMA_R | VMA_W | VMA_X | VMA_KVA, data); - set_page_table(current); + set_page_table(current->page_table); exec_user_prog((void *)0, (char *)0xffffffffeff0, kernel_sp); } @@ -130,21 +119,15 @@ void syscall_fork(trapframe *frame){ child = task_create(); child->kernel_stack = kmalloc(STACK_SIZE); - child->user_stack = kmalloc(STACK_SIZE); - child->data = kmalloc(current->datalen); - child->datalen = current->datalen; memncpy(child->kernel_stack, current->kernel_stack, STACK_SIZE); - memncpy(child->user_stack, current->user_stack, STACK_SIZE); - memncpy(child->data, current->data, current->datalen); - task_init_map(child); + // TODO: Implement copy on write - // 0x000000000000 ~ : rwx: Code - pt_map(child->page_table, (void *)0, child->datalen,(void *)VA2PA(child->data), PT_R | PT_W | PT_X); + vma_meta_copy(child->address_space, current->address_space, current->page_table); // Copy the signal handler - sighand_copy(child->sighand, child->data); + sighand_copy(child->sighand); // Save regs SAVE_REGS(current); diff --git a/src/lib/task.c b/src/lib/task.c index 94c5f083a..f8ebae55b 100644 --- a/src/lib/task.c +++ b/src/lib/task.c @@ -26,15 +26,16 @@ task_struct *task_create(void){ struct signal_head_t *signal; struct sighand_t *sighand; pd_t *page_table; + vm_area_meta_t *as; task = kmalloc(sizeof(task_struct)); signal = signal_head_create(); sighand = sighand_create(); page_table = pt_create(); + as = vma_meta_create(); task->kernel_stack = NULL; - task->user_stack = NULL; - task->data = NULL; + task->address_space = as; task->page_table = page_table; INIT_LIST_HEAD(&task->list); list_add_tail(&task->task_list, &task_queue); @@ -52,18 +53,13 @@ task_struct *task_create(void){ void task_free(task_struct *task){ if(task->kernel_stack) kfree(task->kernel_stack); - - if (task->user_stack) - kfree(task->user_stack); - - if (task->data) - kfree(task->data); list_del(&task->task_list); signal_head_free(task->signal); sighand_free(task->sighand); + vma_meta_free(task->address_space, task->page_table); pt_free(task->page_table); kfree(task); @@ -71,9 +67,22 @@ void task_free(task_struct *task){ void task_init_map(task_struct *task){ // TODO: map the return addres of mailbox_call - pt_map(task->page_table, (void *)0x3c000000, 0x03000000,(void *)0x3c000000, PT_R | PT_W); - pt_map(task->page_table, (void *)0x7f0000000000, TEXT_USER_SHARED_LEN,(void *)VA2PA(TEXT_USER_SHARED_BASE), PT_R | PT_X); - pt_map(task->page_table, (void *)0xffffffffb000, STACK_SIZE,(void *)VA2PA(task->user_stack), PT_R | PT_W); + vma_map(task->address_space, (void *)0x3c000000, 0x03000000, + VMA_R | VMA_W | VMA_PA, (void *)0x3c000000); + + vma_map(task->address_space, (void *)0x7f0000000000, TEXT_USER_SHARED_LEN, + VMA_R | VMA_X | VMA_PA, (void *)VA2PA(TEXT_USER_SHARED_BASE)); + + vma_map(task->address_space, (void *)0xffffffffb000, STACK_SIZE, + VMA_R | VMA_W | VMA_ANON, NULL); +} + +void task_reset_mm(task_struct *task){ + vma_meta_free(task->address_space, task->page_table); + pt_free(task->page_table); + + task->page_table = pt_create(); + task->address_space = vma_meta_create(); } task_struct *task_get_by_tid(uint32 tid){ From 33cafe06f668ff07dacdd43d45f5b70dd353ede4 Mon Sep 17 00:00:00 2001 From: Shin-Yan Date: Fri, 23 Jun 2023 11:58:45 +0800 Subject: [PATCH 04/10] implement tmpfs cpiofs & vfs --- .gitignore | 1 + Makefile | 9 +- inc/cpio.h | 1 + inc/cpiofs.h | 54 +++++++ inc/fsinit.h | 10 ++ inc/mm.h | 2 +- inc/sched.h | 7 + inc/string.h | 1 + inc/tmpfs.h | 50 +++++++ inc/vfs.h | 87 ++++++++++++ initramfs.cpio_lab5 | Bin 247296 -> 0 bytes src/kernel/main.c | 5 +- src/lib/cpio.c | 8 +- src/lib/cpiofs.c | 337 ++++++++++++++++++++++++++++++++++++++++++++ src/lib/exec.c | 37 ++--- src/lib/fsinit.c | 21 +++ src/lib/irq.c | 2 + src/lib/signal.c | 2 +- src/lib/string.c | 16 +++ src/lib/syscall.c | 43 ++++-- src/lib/task.c | 9 ++ src/lib/tmpfs.c | 282 ++++++++++++++++++++++++++++++++++++ src/lib/vfs.c | 329 ++++++++++++++++++++++++++++++++++++++++++ 23 files changed, 1270 insertions(+), 43 deletions(-) create mode 100644 inc/cpiofs.h create mode 100644 inc/fsinit.h create mode 100644 inc/tmpfs.h create mode 100644 inc/vfs.h delete mode 100755 initramfs.cpio_lab5 create mode 100644 src/lib/cpiofs.c create mode 100644 src/lib/fsinit.c create mode 100644 src/lib/tmpfs.c create mode 100644 src/lib/vfs.c diff --git a/.gitignore b/.gitignore index 19d8aa640..1be6a19fb 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.img obj/ img/ +core .vscode initramfs.cpio \ No newline at end of file diff --git a/Makefile b/Makefile index 7390e9336..bc14bf050 100644 --- a/Makefile +++ b/Makefile @@ -93,17 +93,10 @@ qemub: all $(INITRAMFS_CPIO) $(RPI3_DTB) -initrd $(INITRAMFS_CPIO) \ -serial null -serial stdio qemuk: all $(INITRAMFS_CPIO) $(RPI3_DTB) - qemu-system-aarch64 -M raspi3 -kernel $(KERNEL_IMG) -display none \ - -dtb $(RPI3_DTB) \ - -initrd test/initramfs_lab5.cpio \ - -serial null -serial stdio - -qemutest: all $(INITRAMFS_CPIO) $(RPI3_DTB) qemu-system-aarch64 -M raspi3 -kernel $(KERNEL_IMG) -display none \ -dtb $(RPI3_DTB) \ -initrd $(INITRAMFS_CPIO) \ - -serial null -serial stdio -s -S - + -serial null -serial stdio qemutty: $(KERNEL_IMG) qemu-system-aarch64 -M raspi3 -kernel $(KERNEL_IMG) -display none \ diff --git a/inc/cpio.h b/inc/cpio.h index 1d1e87468..721103e70 100644 --- a/inc/cpio.h +++ b/inc/cpio.h @@ -28,5 +28,6 @@ void cpio_cat(char *cpio, char *filename); * Return the size of the file, return 0 if no such file. */ uint32 cpio_load_prog(char *cpio, const char *filename, char **output_data); +uint32 cpio_read_hex(char *p); #endif \ No newline at end of file diff --git a/inc/cpiofs.h b/inc/cpiofs.h new file mode 100644 index 000000000..3dc9994f5 --- /dev/null +++ b/inc/cpiofs.h @@ -0,0 +1,54 @@ +#ifndef _CPIOFS_H +#define _CPIOFS_H + +#include +#include + +#define CPIO_TYPE_MASK 0060000 +#define CPIO_TYPE_DIR 0040000 +#define CPIO_TYPE_FILE 0000000 + +#define CPIOFS_TYPE_UNDEFINE 0x0 +#define CPIOFS_TYPE_FILE 0x1 +#define CPIOFS_TYPE_DIR 0x2 + +struct cpiofs_file_t { + const char *data; + int size; +}; + +struct cpiofs_dir_t { + /* Link cpiofs_internal */ + struct list_head list; +}; + +struct cpiofs_internal { + const char *name; + int type; + union { + struct cpiofs_file_t file; + struct cpiofs_dir_t dir; + }; + struct vnode *node; + struct list_head list; +}; + +int cpiofs_mount(struct filesystem *fs, struct mount *mount); +int cpiofs_lookup(struct vnode *dir_node, struct vnode **target, + const char *component_name); +int cpiofs_create(struct vnode *dir_node, struct vnode **target, + const char *component_name); +int cpiofs_mkdir(struct vnode *dir_node, struct vnode **target, + const char *component_name); +int cpiofs_isdir(struct vnode *dir_node); +int cpiofs_getname(struct vnode *dir_node, const char **name); +int cpiofs_getsize(struct vnode *dir_node); +int cpiofs_write(struct file *file, const void *buf, size_t len); +int cpiofs_read(struct file *file, void *buf, size_t len); +int cpiofs_open(struct vnode *file_node, struct file *target); +int cpiofs_close(struct file *file); +long cpiofs_lseek64(struct file *file, long offset, int whence); + +struct filesystem *cpiofs_init(void); + +#endif \ No newline at end of file diff --git a/inc/fsinit.h b/inc/fsinit.h new file mode 100644 index 000000000..19860e74d --- /dev/null +++ b/inc/fsinit.h @@ -0,0 +1,10 @@ +#ifndef _FSINIT_H +#define _FSINIT_H + +#include +#include + +void fs_early_init(void); +void fs_init(void); + +#endif \ No newline at end of file diff --git a/inc/mm.h b/inc/mm.h index 1b91424d3..e539c7c09 100644 --- a/inc/mm.h +++ b/inc/mm.h @@ -3,7 +3,7 @@ #ifndef __ASSEMBLER__ void memzero(char* src, unsigned long n); -void memncpy(char *dst, char *src, unsigned long n); +void memncpy(char *dst, const char *src, unsigned long n); void mm_init(char *fdt_base); void *kmalloc(int size); diff --git a/inc/sched.h b/inc/sched.h index fc5e02b04..d0162f37c 100644 --- a/inc/sched.h +++ b/inc/sched.h @@ -4,6 +4,9 @@ #include #include #include +#include + +#define TASK_MAX_FD 0x10 struct pt_regs { void *x19; @@ -42,6 +45,10 @@ typedef struct _task_struct { /* Signal */ struct signal_head_t *signal; struct sighand_t *sighand; + /*files*/ + int maxfd; + struct file fds[TASK_MAX_FD]; + struct vnode *work_dir; } task_struct; void switch_to(task_struct *from, task_struct *to); diff --git a/inc/string.h b/inc/string.h index fe84b2523..c4a770785 100644 --- a/inc/string.h +++ b/inc/string.h @@ -4,6 +4,7 @@ int strcmp(const char *X, const char *Y); int strncmp(const char *X, const char *Y, int n); int strlen(const char *str); +int strcpy(char *dst, const char *src); int atoi(const char *str); #endif /* _STRING_H */ \ No newline at end of file diff --git a/inc/tmpfs.h b/inc/tmpfs.h new file mode 100644 index 000000000..35c8be57b --- /dev/null +++ b/inc/tmpfs.h @@ -0,0 +1,50 @@ +#ifndef _TMPFS_H +#define _TMPFS_H + +#include + +#define TMPFS_FILE_MAXSIZE PAGE_SIZE +#define TMPFS_DIR_MAXSIZE 0x10 +#define TMPFS_TYPE_UNDEFINE 0x0 +#define TMPFS_TYPE_FILE 0x1 +#define TMPFS_TYPE_DIR 0x2 +#define TMPFS_NAME_MAXLEN 0x10 + +struct tmpfs_file_t{ + char *data; + int size; + int capacity; +}; + +struct tmpfs_dir_t{ + int size; + struct vnode *entries[TMPFS_DIR_MAXSIZE]; +}; + +struct tmpfs_internal{ + char name[TMPFS_NAME_MAXLEN]; + int type; + union { + struct tmpfs_file_t *file; + struct tmpfs_dir_t *dir; + }; + struct vnode *oldnode; +}; + +int tmpfs_mount(struct filesystem *fs, struct mount *mount); +int tmpfs_alloc_vnode(struct filesystem *fs, struct vnode **target); +int tmpfs_lookup(struct vnode *dir_node, struct vnode **target, const char *component_name); +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_isdir(struct vnode *dir_node); +int tmpfs_getname(struct vnode *dir_node, const char **name); +int tmpfs_getsize(struct vnode *dir_node); +int tmpfs_write(struct file *file, const void *buf, size_t len); +int tmpfs_read(struct file *file, void *buf, size_t len); +int tmpfs_open(struct vnode *file_node, struct file *target); +int tmpfs_close(struct file *file); +long tmpfs_lseek64(struct file *file, long offset, int whence); + +struct filesystem *tmpfs_init(void); + +#endif \ No newline at end of file diff --git a/inc/vfs.h b/inc/vfs.h new file mode 100644 index 000000000..75d3aaecd --- /dev/null +++ b/inc/vfs.h @@ -0,0 +1,87 @@ +#ifndef _VFS_H +#define _VFS_H + +#include +#include +#include + +#define O_CREATE 00000100 + +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 + +struct vnode { + struct mount *mount; + struct vnode_operations *v_ops; + struct file_operations *f_ops; + struct vnode *parent; + void *internal; +}; + +struct mount { + struct vnode *root; + struct filesystem *fs; +}; + +struct filesystem { + const char *name; + /* Link all filesystems */ + struct list_head fs_list; + int (*mount)(struct filesystem *fs, struct mount *mount); + int (*alloc_vnode)(struct filesystem *fs, struct vnode **target); +}; + +struct vnode_operations { + int (*lookup)(struct vnode *dir_node, struct vnode **target, + const char *component_name); + int (*create)(struct vnode *dir_node, struct vnode **target, + const char *component_name); + int (*mkdir)(struct vnode *dir_node, struct vnode **target, + const char *component_name); + int (*isdir)(struct vnode *dir_node); + int (*getname)(struct vnode *dir_node, const char **name); + int (*getsize)(struct vnode *dir_node); +}; + +struct file { + struct vnode *vnode; + size_t f_pos; // RW position of this file handle + struct file_operations *f_ops; + int flags; +}; + +struct file_operations { + int (*write)(struct file *file, const void *buf, size_t len); + int (*read)(struct file *file, void *buf, size_t len); + int (*open)(struct vnode *file_node, struct file *target); + int (*close)(struct file *file); + long (*lseek64)(struct file *file, long offset, int whence); +}; + +extern struct mount *rootmount; + +void vfs_init(void); +void vfs_init_rootmount(struct filesystem *fs); + +int register_filesystem(struct filesystem *fs); +int vfs_open(const char *pathname, int flags, struct file *target); +int vfs_close(struct file *target); +int vfs_write(struct file *file, const void *buf, size_t len); +int vfs_read(struct file *file, void *buf, size_t len); +int vfs_mkdir(const char *pathname); +int vfs_mount(const char *mountpath, const char *filesystem); +int vfs_lookup(const char *pathname, struct vnode **target); + +void syscall_open(trapframe *frame, const char *pathname, int flags); +void syscall_close(trapframe *frame, int fd); +void syscall_write(trapframe *frame, int fd, const void *buf, uint64 count); +void syscall_read(trapframe *frame, int fd, void *buf, uint64 count); +void syscall_mkdir(trapframe *frame, const char *pathname, uint32 mode); +void syscall_mount(trapframe *frame, const char *src, const char *target, + const char *filesystem, uint64 flags, const void *data); +void syscall_chdir(trapframe *frame, const char *path); +void syscall_lseek64(trapframe *frame, int fd, int64 offset, int whence); +void syscall_ioctl(trapframe *frame, int fd, uint64 request, ...); + +#endif \ No newline at end of file diff --git a/initramfs.cpio_lab5 b/initramfs.cpio_lab5 deleted file mode 100755 index 0676fb1584617bc4d73624b3b212a12133e97d8e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 247296 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 #include #include +#include #define BUFSIZE 0x100 char shell_buf[BUFSIZE]; @@ -92,14 +93,16 @@ void kernel_main(char *fdt){ mm_init(fdt); timer_init(); task_init(); + fs_early_init(); scheduler_init(); + fs_init(); kthread_init(); uart_printf("[*] Kernel start running!\r\n"); uart_printf("[*] fdt base: %x\r\n", fdt); // kthread_create(shell_interact); - sched_new_user_prog("vm.img"); + sched_new_user_prog("/initramfs/vfs1.img"); enable_irqs1(); enable_interrupt(); diff --git a/src/lib/cpio.c b/src/lib/cpio.c index 0385242cb..af1f973ca 100644 --- a/src/lib/cpio.c +++ b/src/lib/cpio.c @@ -4,7 +4,7 @@ #include #include -static uint32 cpio_read_hex(char *p){ +uint32 cpio_read_hex(char *p){ uint32 result = 0; for(int i = 0 ; i < 8 ; i++){ @@ -28,7 +28,7 @@ void cpio_ls(char *cpio){ while(1){ struct cpio_newc_header *pheader = (struct cpio_newc_header *)cur; cur += sizeof(struct cpio_newc_header); - if(!strcmp(pheader->c_magic, "070701")){ + if(strncmp(pheader->c_magic, "070701", 6)){ uart_printf("Only support new ASCII format for cpio. \r\n"); return; } @@ -59,7 +59,7 @@ void cpio_cat(char *cpio, char *filename){ while(1){ struct cpio_newc_header *pheader = (struct cpio_newc_header *) cur; cur += sizeof(struct cpio_newc_header); - if(!strcmp(pheader->c_magic, "070701")){ + if(strncmp(pheader->c_magic, "070701", 6)){ uart_printf("Only support new ASCII format for cpio. \r\n"); return; } @@ -97,7 +97,7 @@ uint32 cpio_load_prog(char *cpio, const char *filename, char **output_data){ while(1){ struct cpio_newc_header *pheader = (struct cpio_newc_header *) cur; cur += sizeof(struct cpio_newc_header); - if(!strcmp(pheader->c_magic, "070701")){ + if(strncmp(pheader->c_magic, "070701", 6)){ uart_printf("Only support new ASCII format for cpio. \r\n"); return 0; } diff --git a/src/lib/cpiofs.c b/src/lib/cpiofs.c new file mode 100644 index 000000000..f8d26aa5a --- /dev/null +++ b/src/lib/cpiofs.c @@ -0,0 +1,337 @@ +#include +#include +#include +#include +#include +#include + +static struct vnode cpio_root_node; +static struct vnode mount_old_node; +static int cpio_mounted; + +static struct filesystem cpiofs = { + .name = "cpiofs", + .mount = cpiofs_mount +}; + +static struct vnode_operations cpiofs_v_ops = { + .lookup = cpiofs_lookup, + .create = cpiofs_create, + .mkdir = cpiofs_mkdir, + .isdir = cpiofs_isdir, + .getname = cpiofs_getname, + .getsize = cpiofs_getsize +}; + +static struct file_operations cpiofs_f_ops = { + .write = cpiofs_write, + .read = cpiofs_read, + .open = cpiofs_open, + .close = cpiofs_close, + .lseek64 = cpiofs_lseek64 +}; + +static struct vnode *get_dir_vnode(struct vnode *dir_node, const char **pathname){ + struct vnode *result; + const char *start; + const char *end; + char buf[0x100]; + + start = end = *pathname; + + if(*start == '/') + result = &cpio_root_node; + else + result = dir_node; + + while(1){ + if(!strncmp("./", start, 2)){ + start += 2; + end = start; + continue; + } + else if(!strncmp("../", start, 3)){ + if(result->parent){ + result = result->parent; + } + + start += 3; + end = start; + continue; + } + while(*end != '\0' && *end != '/') + end++; + if(*end == '/'){ + int ret; + if(start == end){ + end++; + start = end; + continue; + } + + // TODO: Check if the length is less than 0x100 + memncpy(buf, start, end - start); + buf[end - start] = 0; + ret = result->v_ops->lookup(result, &result, buf); + if(ret < 0) + return NULL; + end++; + start = end; + } + else{ + break; + } + } + *pathname = *start ? start : NULL; + return result; +} + +static void cpio_init_mkdir(const char *pathname){ + const char *curname; + struct vnode *dir_node; + struct vnode *newdir_node; + struct cpiofs_internal *internal, *dirint; + + curname = pathname; + dir_node = get_dir_vnode(&cpio_root_node, &curname); + + if(!dir_node) + return; + if(!curname) + return; + dirint = dir_node->internal; + if(dirint->type != CPIOFS_TYPE_DIR) + return; + internal = kmalloc(sizeof(struct cpiofs_internal)); + newdir_node = kmalloc(sizeof(struct vnode)); + + internal->name = curname; + internal->type = CPIOFS_TYPE_DIR; + INIT_LIST_HEAD(&internal->dir.list); + internal->node = newdir_node; + list_add_tail(&internal->list, &dirint->dir.list); + + newdir_node->mount = NULL; + newdir_node->v_ops = &cpiofs_v_ops; + newdir_node->f_ops = &cpiofs_f_ops; + newdir_node->parent = dir_node; + newdir_node->internal = internal; + return; +} + +static void cpio_init_create(const char *pathname, const char *data, uint64 size){ + const char *curname; + struct vnode *dir_node; + struct vnode *newdir_node; + struct cpiofs_internal *internal, *dirint; + + curname = pathname; + dir_node = get_dir_vnode(&cpio_root_node, &curname); + + if(!dir_node) + return; + if(!curname) + return; + dirint = dir_node->internal; + if(dirint->type != CPIOFS_TYPE_DIR) + return; + internal = kmalloc(sizeof(struct cpiofs_internal)); + newdir_node = kmalloc(sizeof(struct vnode)); + + internal->name = curname; + internal->type = CPIOFS_TYPE_FILE; + internal->file.data = data; + internal->file.size = size; + internal->node = newdir_node; + list_add_tail(&internal->list, &dirint->dir.list); + + newdir_node->mount = NULL; + newdir_node->v_ops = &cpiofs_v_ops; + newdir_node->f_ops = &cpiofs_f_ops; + newdir_node->parent = dir_node; + newdir_node->internal = internal; + return; +} + +struct filesystem *cpiofs_init(void){ + char *cur; + struct cpiofs_internal *internal = kmalloc(sizeof(struct cpiofs_internal)); + internal->name = NULL; + internal->type = CPIOFS_TYPE_DIR; + INIT_LIST_HEAD(&internal->dir.list); + internal->node = &cpio_root_node; + INIT_LIST_HEAD(&internal->list); + + cpio_root_node.mount = NULL; + cpio_root_node.v_ops = &cpiofs_v_ops; + cpio_root_node.f_ops = &cpiofs_f_ops; + cpio_root_node.parent = NULL; + cpio_root_node.internal = internal; + + cur = _initramfs_addr; + while(1){ + char *component_name, *content; + + struct cpio_newc_header *pheader = (struct cpio_newc_header *)cur; + cur += sizeof(struct cpio_newc_header); + if(strncmp(pheader->c_magic, "070701", 6)){ + panic("[*] Only support new ASCII format for cpio. \r\n"); + } + + uint32 namesize = cpio_read_hex(pheader->c_namesize); + uint32 filesize = cpio_read_hex(pheader->c_filesize); + uint32 type = cpio_read_hex(pheader->c_mode) & CPIO_TYPE_MASK; + + uint32 adj_namesize = ALIGN(namesize + sizeof(struct cpio_newc_header), 4) -sizeof(struct cpio_newc_header); + uint32 adj_filesize = ALIGN(filesize, 4); + + component_name = cur; + cur += adj_namesize; + content = cur; + cur += adj_filesize; + + if(type == CPIO_TYPE_DIR) + cpio_init_mkdir(component_name); + else if(type == CPIO_TYPE_FILE) + cpio_init_create(component_name, content, filesize); + + if(namesize == 0xb && !strcmp(component_name, "TRAILER!!!")) + break; + } + return &cpiofs; +} + +int cpiofs_mount(struct filesystem *fs, struct mount *mount){ + struct vnode *oldnode; + struct cpiofs_internal *internal; + const char *name; + if(cpio_mounted) + return -1; + + oldnode = mount->root; + oldnode->v_ops->getname(oldnode, &name); + internal = cpio_root_node.internal; + internal->name = name; + + mount_old_node.mount = oldnode->mount; + mount_old_node.v_ops = oldnode->v_ops; + mount_old_node.f_ops = oldnode->f_ops; + mount_old_node.parent = oldnode->parent; + mount_old_node.internal = oldnode->internal; + + oldnode->mount = mount; + oldnode->v_ops = cpio_root_node.v_ops; + oldnode->f_ops = cpio_root_node.f_ops; + oldnode->internal = internal; + + return 0; +} +int cpiofs_lookup(struct vnode *dir_node, struct vnode **target, const char *component_name){ + struct cpiofs_internal *internal, *entry; + internal = dir_node->internal; + + if (internal->type != CPIOFS_TYPE_DIR) { + return -1; + } + + list_for_each_entry(entry, &internal->dir.list, list) { + if (!strcmp(entry->name, component_name)) { + break; + } + } + + if (&entry->list == &internal->dir.list) { + return -1; + } + + *target = entry->node; + return 0; +} + +int cpiofs_create(struct vnode *dir_node, struct vnode **target, const char *component_name){ + // TODO + return -1; +} +int cpiofs_mkdir(struct vnode *dir_node, struct vnode **target, const char *component_name){ + // TODO + return -1; +} +int cpiofs_isdir(struct vnode *dir_node){ + struct cpiofs_internal *internal = dir_node->internal; + if(internal->type != CPIOFS_TYPE_DIR) + return 0; + + return 1; +} +int cpiofs_getname(struct vnode *dir_node, const char **name){ + struct cpiofs_internal *internal = dir_node->internal; + *name = internal->name; + return 0; +} +int cpiofs_getsize(struct vnode *dir_node){ + struct cpiofs_internal *internal = dir_node->internal; + if(internal->type != CPIOFS_TYPE_FILE) + return -1; + + return internal->file.size; +} +int cpiofs_write(struct file *file, const void *buf, size_t len){ + // TODO + return -1; +} +int cpiofs_read(struct file *file, void *buf, size_t len){ + struct cpiofs_internal *internal = file->vnode->internal; + if(internal->type != CPIOFS_TYPE_FILE) + return -1; + + if(len > internal->file.size - file->f_pos) + len = internal->file.size - file->f_pos; + + if(!len) + return 0; + memncpy(buf, &internal->file.data[file->f_pos], len); + file->f_pos += len; + return len; +} +int cpiofs_open(struct vnode *file_node, struct file *target){ + target->vnode = file_node; + target->f_pos = 0; + target->f_ops = file_node->f_ops; + + return 0; +} +int cpiofs_close(struct file *file){ + file->vnode = NULL; + file->f_pos = 0; + file->f_ops = NULL; + + return 0; +} +long cpiofs_lseek64(struct file *file, long offset, int whence){ + int filesize; + int base; + + filesize = file->vnode->v_ops->getsize(file->vnode); + if(filesize < 0) + return -1; + + switch(whence){ + case SEEK_SET: + base = 0; + break; + case SEEK_CUR: + base = file->f_pos; + break; + case SEEK_END: + base = filesize; + break; + default: + return -1; + } + + if(base + offset > filesize) + return -1; + + file->f_pos = base + offset; + return 0; +} \ No newline at end of file diff --git a/src/lib/exec.c b/src/lib/exec.c index 0e1044c8c..2ce405e5a 100644 --- a/src/lib/exec.c +++ b/src/lib/exec.c @@ -6,6 +6,7 @@ #include #include #include +#include void enter_el0_run_user_prog(void *entry, void *user_sp); @@ -34,34 +35,38 @@ static inline void pt_regs_init(struct pt_regs *regs){ regs->lr = user_prog_start; } -void sched_new_user_prog(char *filename){ +void sched_new_user_prog(char *pathname){ task_struct *task; + struct file f; void *data; - uint32 datalen; - - datalen = cpio_load_prog(_initramfs_addr, filename, (char **)&data); - - if(datalen == 0) - goto EXEC_USER_PROG_END; + int datalen, adj_datalen, ret; + ret = vfs_open(pathname, 0, &f); + if(ret < 0) + return; + + datalen = f.vnode->v_ops->getsize(f.vnode); + if(datalen<0) + return; + adj_datalen = ALIGN(datalen, PAGE_SIZE); + data = kmalloc(adj_datalen); + memzero(data, adj_datalen); + ret = vfs_read(&f, data, datalen); + if(ret < 0){ + kfree(data); + return; + } + vfs_close(&f); task = task_create(); - task->kernel_stack = kmalloc(STACK_SIZE); - // task->user_stack = kmalloc(STACK_SIZE); - // task->data = data; - // task->datalen = datalen; - task->regs.sp = (char *)task->kernel_stack + STACK_SIZE -0x10; pt_regs_init(&task->regs); task_init_map(task); // 0x000000000000 ~ : rwx: Code - vma_map(task->address_space, (void *)0, datalen, VMA_R | VMA_W | VMA_X | VMA_KVA, data); + vma_map(task->address_space, (void *)0, adj_datalen, VMA_R | VMA_W | VMA_X | VMA_KVA, data); sched_add_task(task); - -EXEC_USER_PROG_END: - return; } diff --git a/src/lib/fsinit.c b/src/lib/fsinit.c new file mode 100644 index 000000000..603733c69 --- /dev/null +++ b/src/lib/fsinit.c @@ -0,0 +1,21 @@ +#include +#include +#include +#include + +void fs_early_init(void){ + struct filesystem *tmpfs, *cpiofs; + + vfs_init(); + tmpfs = tmpfs_init(); + cpiofs = cpiofs_init(); + register_filesystem(tmpfs); + register_filesystem(cpiofs); + + vfs_init_rootmount(tmpfs); +} + +void fs_init(void){ + vfs_mkdir("/initramfs"); + vfs_mount("/initramfs", "cpiofs"); +} \ No newline at end of file diff --git a/src/lib/irq.c b/src/lib/irq.c index f3de05d98..f90512d11 100644 --- a/src/lib/irq.c +++ b/src/lib/irq.c @@ -132,6 +132,8 @@ void irq_init(){ void default_exception_handler(uint32 n){ uart_printf("[exception] %d\r\n", n); + // never reach + while(1){} } void irq_handler(){ diff --git a/src/lib/signal.c b/src/lib/signal.c index ce6979b46..5be9546ad 100644 --- a/src/lib/signal.c +++ b/src/lib/signal.c @@ -23,7 +23,7 @@ static void sig_ignore(int _){ void sig_return(void) SECTION_TUS; void sig_return(void){ asm volatile( - "mov x8, 11\n" + "mov x8, 20\n" "svc 0\n" ); } diff --git a/src/lib/string.c b/src/lib/string.c index 0d5a0405c..2cca8d293 100644 --- a/src/lib/string.c +++ b/src/lib/string.c @@ -26,6 +26,22 @@ int strlen(const char *str){ return ret; } +int strcpy(char *dst, const char *src) +{ + int ret = 0; + + while (*src) { + *dst = *src; + dst++; + src++; + ret++; + } + + *dst = '\0'; + + return ret; +} + int atoi(const char *str){ int i = 0 , j = 0; while(*str){ diff --git a/src/lib/syscall.c b/src/lib/syscall.c index 00a824b13..3973b96df 100644 --- a/src/lib/syscall.c +++ b/src/lib/syscall.c @@ -12,6 +12,7 @@ #include #include #include +#include syscall_funcp syscall_table[] = { (syscall_funcp) syscall_getpid, // 0 @@ -25,8 +26,17 @@ syscall_funcp syscall_table[] = { (syscall_funcp) syscall_signal, // 8 (syscall_funcp) syscall_kill, // 9 (syscall_funcp) syscall_mmap, // 10 - (syscall_funcp) syscall_sigreturn, // 11 - (syscall_funcp) syscall_test, // 12 + (syscall_funcp) syscall_open, // 11 + (syscall_funcp) syscall_close, // 12 + (syscall_funcp) syscall_write, // 13 + (syscall_funcp) syscall_read, // 14 + (syscall_funcp) syscall_mkdir, // 15 + (syscall_funcp) syscall_mount, // 16 + (syscall_funcp) syscall_chdir, // 17 + (syscall_funcp) syscall_lseek64, // 18 + (syscall_funcp) syscall_ioctl, // 19 + (syscall_funcp) syscall_sigreturn, // 20 + (syscall_funcp) syscall_test, // 21 }; static inline void copy_regs(struct pt_regs *regs) @@ -49,8 +59,8 @@ void syscall_handler(trapframe *regs) syscall_num = regs->x8; - if(syscall_num == 10) - uart_sync_printf("addr: 0x%x, len: 0x%x, prot: 0x%x, flags: 0x%x, fd: 0x%x, file_offset: 0x%x\r\n", regs->x0, regs->x1, regs->x2, regs->x3, regs->x4, regs->x5); + if(syscall_num > 2) + // uart_sync_printf("syscall number:%d\n", syscall_num); if (syscall_num >= ARRAY_SIZE(syscall_table)) { // Invalid syscall @@ -85,18 +95,27 @@ void syscall_uart_write(trapframe *_, const char buf[], size_t size){ } void syscall_exec(trapframe *_, const char* name, char *const argv[]){ + struct file f; void *data; char *kernel_sp; - // char *user_sp; - uint32 datalen; + int datalen, adj_datalen, ret; - datalen = cpio_load_prog((char *)_initramfs_addr, name, (char **)&data); - - if(datalen == 0) + ret = vfs_open(name, 0, &f); + if(ret < 0) return; - + datalen = f.vnode->v_ops->getsize(f.vnode); + if(datalen < 0) + return; + adj_datalen = ALIGN(datalen, PAGE_SIZE); + data = kmalloc(adj_datalen); + memzero(data, adj_datalen); + ret = vfs_read(&f, data, datalen); + if(ret < 0){ + kfree(data); + return; + } + vfs_close(&f); kernel_sp = (char *)current->kernel_stack + STACK_SIZE - 0x10; - // user_sp = (char *)current->user_stack + STACK_SIZE - 0x10; // Reset signal signal_head_reset(current->signal); @@ -106,7 +125,7 @@ void syscall_exec(trapframe *_, const char* name, char *const argv[]){ task_init_map(current); // 0x000000000000 ~ : rwx: Code - vma_map(current->address_space, (void *)0, datalen, VMA_R | VMA_W | VMA_X | VMA_KVA, data); + vma_map(current->address_space, (void *)0, adj_datalen, VMA_R | VMA_W | VMA_X | VMA_KVA, data); set_page_table(current->page_table); exec_user_prog((void *)0, (char *)0xffffffffeff0, kernel_sp); diff --git a/src/lib/task.c b/src/lib/task.c index f8ebae55b..b1ec083d6 100644 --- a/src/lib/task.c +++ b/src/lib/task.c @@ -47,6 +47,9 @@ task_struct *task_create(void){ task->signal = signal; task->sighand = sighand; + task->maxfd = -1; + task->work_dir = rootmount->root; + return task; } @@ -62,6 +65,12 @@ void task_free(task_struct *task){ vma_meta_free(task->address_space, task->page_table); pt_free(task->page_table); + for(int i = 0 ; i <= task->maxfd ;++i){ + if(task->fds[i].vnode != NULL){ + task->fds[i].f_ops->close(&task->fds[i]); + } + } + kfree(task); } diff --git a/src/lib/tmpfs.c b/src/lib/tmpfs.c new file mode 100644 index 000000000..18f307b1a --- /dev/null +++ b/src/lib/tmpfs.c @@ -0,0 +1,282 @@ +#include +#include +#include + +static struct filesystem tmpfs = { + .name = "tmpfs", + .mount = tmpfs_mount, + .alloc_vnode = tmpfs_alloc_vnode +}; + +static struct vnode_operations tmpfs_v_ops = { + .lookup = tmpfs_lookup, + .create = tmpfs_create, + .mkdir = tmpfs_mkdir, + .isdir = tmpfs_isdir, + .getname = tmpfs_getname, + .getsize = tmpfs_getsize +}; + +static struct file_operations tmpfs_f_ops = { + .write = tmpfs_write, + .read = tmpfs_read, + .open = tmpfs_open, + .close = tmpfs_close, + .lseek64 = tmpfs_lseek64 +}; + +int tmpfs_mount(struct filesystem *fs, struct mount *mount){ + struct vnode *node, *oldnode; + struct tmpfs_internal *internal; + struct tmpfs_dir_t *dir; + const char *name; + + oldnode = mount->root; + oldnode->v_ops->getname(oldnode, &name); + if(strlen(name) >= TMPFS_NAME_MAXLEN) + return -1; + + node = kmalloc(sizeof(struct vnode)); + internal = kmalloc(sizeof(struct tmpfs_internal)); + dir = kmalloc(sizeof(struct tmpfs_dir_t)); + + dir->size = 0; + node->mount = oldnode->mount; + node->v_ops = oldnode->v_ops; + node->f_ops = oldnode->f_ops; + node->internal =oldnode->internal; + + strcpy(internal->name, name); + internal->type = TMPFS_TYPE_DIR; + internal->dir = dir; + internal->oldnode = node; + + oldnode->mount = mount; + oldnode->v_ops = &tmpfs_v_ops; + oldnode->f_ops = &tmpfs_f_ops; + oldnode->internal = internal; + + return 0; +} +int tmpfs_alloc_vnode(struct filesystem *fs, struct vnode **target){ + struct vnode *node; + struct tmpfs_internal *internal; + struct tmpfs_dir_t *dir; + node = kmalloc(sizeof(struct vnode)); + internal = kmalloc(sizeof(struct tmpfs_internal)); + dir = kmalloc(sizeof(struct tmpfs_dir_t)); + + dir->size = 0; + + internal->name[0] = '\0'; + internal->type = TMPFS_TYPE_DIR; + internal->dir = dir; + internal->oldnode = NULL; + + node->mount = NULL; + node->v_ops = &tmpfs_v_ops; + node->f_ops = &tmpfs_f_ops; + node->internal = internal; + *target = node; + + return 0; +} +int tmpfs_lookup(struct vnode *dir_node, struct vnode **target, const char *component_name){ + struct tmpfs_internal *internal; + struct tmpfs_dir_t *dir; + int i; + + internal = dir_node->internal; + if(internal->type != TMPFS_TYPE_DIR) + return -1; + + dir = internal->dir; + for(i = 0; i < dir->size ;++i){ + struct vnode *node; + const char *name; + int ret; + node = dir->entries[i]; + ret = node->v_ops->getname(node, &name); + if(ret < 0) + continue; + if(!strcmp(name, component_name)) + break; + } + + if(i >= dir->size) + return -1; + *target = dir->entries[i]; + return 0; +} +int tmpfs_create(struct vnode *dir_node, struct vnode **target, const char *component_name){ + struct tmpfs_internal *internal, *newint; + struct tmpfs_file_t *file; + struct tmpfs_dir_t *dir; + struct vnode *node; + int ret; + if(strlen(component_name) >= 0x10) + return -1; + internal = dir_node->internal; + if(internal->type != TMPFS_TYPE_DIR) + return -1; + dir = internal->dir; + if(dir->size >= TMPFS_DIR_MAXSIZE) + return -1; + ret = tmpfs_lookup(dir_node, &node, component_name); + if(!ret) + return -1; + node = kmalloc(sizeof(struct vnode)); + newint = kmalloc(sizeof(struct tmpfs_internal)); + file = kmalloc(sizeof(struct tmpfs_file_t)); + + file->data = kmalloc(TMPFS_FILE_MAXSIZE); + file->size = 0; + file->capacity = TMPFS_FILE_MAXSIZE; + + strcpy(newint->name, component_name); + newint->type = TMPFS_TYPE_FILE; + newint->file = file; + newint->oldnode = NULL; + + node->mount = dir_node->mount; + node->v_ops = &tmpfs_v_ops; + node->f_ops = &tmpfs_f_ops; + node->parent = dir_node; + node->internal = newint; + + dir->entries[dir->size] = node; + dir->size++; + *target = node; + return 0; +} +int tmpfs_mkdir(struct vnode *dir_node, struct vnode **target, const char *component_name){ + struct tmpfs_internal *internal, *newint; + struct tmpfs_dir_t *dir, *newdir; + struct vnode *node; + int ret; + if(strlen(component_name) >= 0x10) + return -1; + internal = dir_node->internal; + if(internal->type != TMPFS_TYPE_DIR) + return -1; + + dir = internal->dir; + if(dir->size >= TMPFS_DIR_MAXSIZE) + return -1; + ret = tmpfs_lookup(dir_node, &node, component_name); + if(!ret) + return -1; + node = kmalloc(sizeof(struct vnode)); + newint = kmalloc(sizeof(struct tmpfs_internal)); + newdir = kmalloc(sizeof(struct tmpfs_dir_t)); + + newdir->size = 0; + strcpy(newint->name, component_name); + newint->type = TMPFS_TYPE_DIR; + newint->dir = newdir; + newint->oldnode = NULL; + + node->mount = dir_node->mount; + node->v_ops = &tmpfs_v_ops; + node->f_ops = &tmpfs_f_ops; + node->parent = dir_node; + node->internal = newint; + + dir->entries[dir->size] = node; + dir->size++; + + *target = node; + return 0; +} +int tmpfs_isdir(struct vnode *dir_node){ + struct tmpfs_internal *internal = dir_node->internal; + if(internal->type != TMPFS_TYPE_DIR) + return 0; + return 1; +} +int tmpfs_getname(struct vnode *dir_node, const char **name){ + struct tmpfs_internal *internal = dir_node->internal; + *name = internal->name; + return 0; +} +int tmpfs_getsize(struct vnode *dir_node){ + struct tmpfs_internal *internal = dir_node->internal; + if(internal->type != TMPFS_TYPE_FILE) + return -1; + return internal->file->size; +} +int tmpfs_write(struct file *file, const void *buf, size_t len){ + struct tmpfs_internal *internal = file->vnode->internal; + struct tmpfs_file_t *f; + if(internal->type != TMPFS_TYPE_FILE) + return -1; + f = internal->file; + if(len > f->capacity - file->f_pos) + len = f->capacity - file->f_pos; + + if(!len) + return 0; + memncpy(&f->data[file->f_pos], buf, len); + file->f_pos += len; + if(file->f_pos > f->size) + f->size = file->f_pos; + + return len; +} +int tmpfs_read(struct file *file, void *buf, size_t len){ + struct tmpfs_internal *internal = file->vnode->internal; + struct tmpfs_file_t *f; + if(internal->type != TMPFS_TYPE_FILE) + return -1; + f = internal->file; + if(len > f->size - file->f_pos) + len = f->size - file->f_pos; + if(!len) + return 0; + memncpy(buf, &f->data[file->f_pos], len); + file->f_pos += len; + return len; +} +int tmpfs_open(struct vnode *file_node, struct file *target){ + // TODO: verify file access permission + target->vnode = file_node; + target->f_pos = 0; + target->f_ops = file_node->f_ops; + + return 0; +} +int tmpfs_close(struct file *file){ + file->vnode = NULL; + file->f_pos = 0; + file->f_ops = NULL; + + return 0; +} +long tmpfs_lseek64(struct file *file, long offset, int whence){ + int filesize, base; + filesize = file->vnode->v_ops->getsize(file->vnode); + if(filesize < 0) + return -1; + switch(whence){ + case SEEK_SET: + base = 0; + break; + case SEEK_CUR: + base = file->f_pos; + break; + case SEEK_END: + base = filesize; + break; + default: + return -1; + } + + if(base + offset > filesize) + return -1; + file->f_pos = base + offset; + return 0; +} + +struct filesystem *tmpfs_init(void){ + return &tmpfs; +} \ No newline at end of file diff --git a/src/lib/vfs.c b/src/lib/vfs.c new file mode 100644 index 000000000..237aea389 --- /dev/null +++ b/src/lib/vfs.c @@ -0,0 +1,329 @@ +#include +#include +#include +#include +#include +#include +#include + +struct mount *rootmount; + +static struct list_head filesystems; + +static struct vnode *get_dir_vnode(struct vnode *dir_node, const char **pathname){ + struct vnode *result; + const char *start; + const char *end; + char buf[0x100]; + + start = end = *pathname; + + if(*start == '/') + result = rootmount->root; + else + result = dir_node; + + while(1){ + if(!strncmp("./", start, 2)){ + start += 2; + end = start; + continue; + } + else if(!strncmp("../", start, 3)){ + if(result->parent){ + result = result->parent; + } + + start += 3; + end = start; + continue; + } + while(*end != '\0' && *end != '/') + end++; + if(*end == '/'){ + int ret; + if(start == end){ + end++; + start = end; + continue; + } + + // TODO: Check if the length is less than 0x100 + memncpy(buf, start, end - start); + buf[end - start] = 0; + ret = result->v_ops->lookup(result, &result, buf); + if(ret < 0) + return NULL; + end++; + start = end; + } + else{ + break; + } + } + *pathname = *start ? start : NULL; + return result; +} + +static struct filesystem *find_filesystem(const char *filesystem){ + struct filesystem *fs; + list_for_each_entry(fs, &filesystems, fs_list){ + if(!strcmp(fs->name, filesystem)) + return fs; + } + return NULL; +} + +void vfs_init(void){ + INIT_LIST_HEAD(&filesystems); +} + +void vfs_init_rootmount(struct filesystem *fs){ + struct vnode *n; + int ret; + + ret = fs->alloc_vnode(fs, &n); + if(ret < 0) + panic("vfs_init_rootmount failed"); + + rootmount = kmalloc(sizeof(struct mount)); + rootmount->root = n; + rootmount->fs = fs; + n->mount = rootmount; + n->parent = NULL; +} + +int register_filesystem(struct filesystem *fs){ + list_add_tail(&fs->fs_list, &filesystems); + return 0; +} + +int vfs_open(const char *pathname, int flags, struct file *target){ + const char *curname; + struct vnode *dir_node; + struct vnode *file_node; + int ret; + curname = pathname; + dir_node = get_dir_vnode(current->work_dir, &curname); + if(!dir_node) + return -1; + if(!curname) + return -1; + ret = dir_node->v_ops->lookup(dir_node, &file_node, curname); + if(flags & O_CREATE){ + if(ret == 0) + return -1; + + ret = dir_node->v_ops->create(dir_node, &file_node, curname); + } + if(ret<0) + return ret; + if(!file_node) + return -1; + ret = file_node->f_ops->open(file_node, target); + if(ret < 0) + return ret; + target->flags = 0; + return 0; +} + +int vfs_close(struct file *file){ + return file->f_ops->close(file); +} + +int vfs_write(struct file *file, const void *buf, size_t len){ + return file->f_ops->write(file, buf, len); +} + +int vfs_read(struct file *file, void *buf, size_t len){ + return file->f_ops->read(file, buf, len); +} + +int vfs_mkdir(const char *pathname){ + const char *curname; + struct vnode *dir_node; + struct vnode *newdir_node; + int ret; + + curname = pathname; + dir_node = get_dir_vnode(current->work_dir, &curname); + if(!dir_node) + return -1; + if(!curname) + return -1; + + ret = dir_node->v_ops->mkdir(dir_node, &newdir_node, curname); + return ret; +} + +int vfs_mount(const char *mountpath, const char *filesystem){ + const char *curname; + struct vnode *dir_node; + struct filesystem *fs; + struct mount *mo; + int ret; + + curname = mountpath; + dir_node = get_dir_vnode(current->work_dir, &curname); + if(!dir_node) + return -1; + + if(curname){ + ret = dir_node->v_ops->lookup(dir_node, &dir_node, curname); + if(ret < 0) + return ret; + } + + if(!dir_node->v_ops->isdir(dir_node)) + return -1; + fs = find_filesystem(filesystem); + if(!fs) + return -1; + mo = kmalloc(sizeof(struct mount)); + mo->root = dir_node; + mo->fs = fs; + ret = fs->mount(fs, mo); + + if(ret < 0){ + kfree(mo); + return ret; + } + + return 0; +} + +int vfs_lookup(const char *pathname, struct vnode **target){ + const char *curname; + struct vnode *dir_node; + struct vnode *file_node; + int ret; + curname = pathname; + dir_node = get_dir_vnode(current->work_dir, &curname); + if(!dir_node) + return -1; + if(!curname){ + *target = dir_node; + return 0; + } + ret = dir_node->v_ops->lookup(dir_node, &file_node, curname); + if(ret < 0) + return ret; + *target = file_node; + return 0; +} + +static int do_open(const char *pathname, int flags){ + int i, ret; + for(i = 0 ; i <= current->maxfd ;++i){ + if(current->fds[i].vnode == NULL) + break; + } + if(i > current->maxfd){ + if(current->maxfd >= TASK_MAX_FD){ + return -1; + } + current->maxfd += 1; + i = current->maxfd; + } + ret = vfs_open(pathname, flags, ¤t->fds[i]); + if(ret < 0) + return ret; + return i; +} + +static int do_close(int fd){ + int ret; + if(fd > current->maxfd) + return -1; + if(current->fds[fd].vnode == NULL) + return -1; + ret = vfs_close(¤t->fds[fd]); + if(ret < 0) + return ret; + return 0; +} + +static int do_write(int fd, const void *buf, uint64 count){ + int ret; + if(fd > current->maxfd) + return -1; + if(current->fds[fd].vnode == NULL) + return -1; + ret = vfs_write(¤t->fds[fd], buf, count); + return ret; +} + +static int do_read(int fd, void *buf, uint64 count){ + int ret; + if(fd > current->maxfd) + return -1; + if(current->fds[fd].vnode == NULL) + return -1; + ret = vfs_read(¤t->fds[fd], buf, count); + return ret; +} + +static int do_mkdir(const char *pathname, uint32 mode){ + int ret; + ret = vfs_mkdir(pathname); + return ret; +} + +static int do_mount(const char *target, const char *filesystem){ + int ret; + ret = vfs_mount(target, filesystem); + return ret; +} + +static int do_chdir(const char *path){ + struct vnode *result; + int ret; + + ret = vfs_lookup(path, &result); + if(ret < 0) + return ret; + if(!result->v_ops->isdir(result)) + return -1; + current->work_dir = result; + return 0; +} + +void syscall_open(trapframe *frame, const char *pathname, int flags){ + int fd = do_open(pathname, flags); + frame->x0 = fd; +} +void syscall_close(trapframe *frame, int fd){ + int ret = do_close(fd); + frame->x0 = ret; +} +void syscall_write(trapframe *frame, int fd, const void *buf, uint64 count){ + // uart_sync_printf("count is %d\r\n", count); + int ret = do_write(fd, buf, count); + frame->x0 = ret; +} +void syscall_read(trapframe *frame, int fd, void *buf, uint64 count){ + int ret = do_read(fd, buf, count); + frame->x0 = ret; +} +void syscall_mkdir(trapframe *frame, const char *pathname, uint32 mode){ + int ret = do_mkdir(pathname, mode); + frame->x0 = ret; +} +void syscall_mount(trapframe *frame, const char *src, const char *target, const char *filesystem, uint64 flags, const void *data){ + int ret = do_mount(target, filesystem); + frame->x0 = ret; +} +void syscall_chdir(trapframe *frame, const char *path){ + int ret = do_chdir(path); + frame->x0 = ret; +} +void syscall_lseek64(trapframe *frame, int fd, int64 offset, int whence){ + // TODO + int ret = -1; + frame->x0 = ret; +} +void syscall_ioctl(trapframe *frame, int fd, uint64 request, ...){ + // TODO + int ret = -1; + frame->x0 = ret; +} \ No newline at end of file From 03e55cd7108de67d69edd89f83594f032ffaa4b6 Mon Sep 17 00:00:00 2001 From: Shin-Yan Date: Fri, 23 Jun 2023 13:17:53 +0800 Subject: [PATCH 05/10] implement uartfs --- inc/fsinit.h | 1 - inc/kthread.h | 2 + inc/uartfs.h | 26 ++++++++++ src/kernel/main.c | 7 ++- src/lib/fsinit.c | 12 +++-- src/lib/kthread.c | 4 ++ src/lib/task.c | 7 ++- src/lib/uartfs.c | 125 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib/vfs.c | 18 +++++-- 9 files changed, 190 insertions(+), 12 deletions(-) create mode 100644 inc/uartfs.h create mode 100644 src/lib/uartfs.c diff --git a/inc/fsinit.h b/inc/fsinit.h index 19860e74d..f283a3fbe 100644 --- a/inc/fsinit.h +++ b/inc/fsinit.h @@ -5,6 +5,5 @@ #include void fs_early_init(void); -void fs_init(void); #endif \ No newline at end of file diff --git a/inc/kthread.h b/inc/kthread.h index 927fed04a..4d7c303dc 100644 --- a/inc/kthread.h +++ b/inc/kthread.h @@ -9,6 +9,8 @@ void kthread_create(void (*start)(void)); void kthread_fini(void); +void kthread_early_init(void); + void kthread_add_wait_queue(task_struct *task); void kthread_kill_zombies(void); diff --git a/inc/uartfs.h b/inc/uartfs.h new file mode 100644 index 000000000..55fc7364d --- /dev/null +++ b/inc/uartfs.h @@ -0,0 +1,26 @@ +#ifndef _UARTFS_H +#define _UARTFS_H + +#include + +struct uartfs_internal{ + const char *name; + struct vnode oldnode; +}; + +int uartfs_mount(struct filesystem *fs, struct mount *mount); +int uartfs_lookup(struct vnode *dir_node, struct vnode **target, const char *component_name); +int uartfs_create(struct vnode *dir_node, struct vnode **target, const char *component_name); +int uartfs_mkdir(struct vnode *dir_node, struct vnode **target, const char *component_name); +int uartfs_isdir(struct vnode *dir_node); +int uartfs_getname(struct vnode *dir_node, const char **name); +int uartfs_getsize(struct vnode *dir_node); +int uartfs_write(struct file *file, const void *buf, size_t len); +int uartfs_read(struct file *file, void *buf, size_t len); +int uartfs_open(struct vnode *file_node, struct file *target); +int uartfs_close(struct file *file); +long uartfs_lseek64(struct file *file, long offset, int whence); + +struct filesystem *uartfs_init(void); + +#endif \ No newline at end of file diff --git a/src/kernel/main.c b/src/kernel/main.c index eda79e1bb..ebbbcc203 100644 --- a/src/kernel/main.c +++ b/src/kernel/main.c @@ -93,10 +93,13 @@ void kernel_main(char *fdt){ mm_init(fdt); timer_init(); task_init(); - fs_early_init(); + scheduler_init(); - fs_init(); + + kthread_early_init(); + fs_early_init(); kthread_init(); + uart_printf("[*] Kernel start running!\r\n"); uart_printf("[*] fdt base: %x\r\n", fdt); diff --git a/src/lib/fsinit.c b/src/lib/fsinit.c index 603733c69..1a4938a4b 100644 --- a/src/lib/fsinit.c +++ b/src/lib/fsinit.c @@ -2,20 +2,26 @@ #include #include #include +#include void fs_early_init(void){ - struct filesystem *tmpfs, *cpiofs; + struct filesystem *tmpfs, *cpiofs, *uartfs; vfs_init(); tmpfs = tmpfs_init(); cpiofs = cpiofs_init(); + uartfs = uartfs_init(); register_filesystem(tmpfs); register_filesystem(cpiofs); + register_filesystem(uartfs); vfs_init_rootmount(tmpfs); -} -void fs_init(void){ vfs_mkdir("/initramfs"); vfs_mount("/initramfs", "cpiofs"); + + vfs_mkdir("/dev"); + + vfs_mkdir("/dev/uart"); + vfs_mount("/dev/uart", "uartfs"); } \ No newline at end of file diff --git a/src/lib/kthread.c b/src/lib/kthread.c index 061efa4cf..189c8ff5c 100644 --- a/src/lib/kthread.c +++ b/src/lib/kthread.c @@ -73,6 +73,10 @@ void kthread_fini(void) schedule(); } +void kthread_early_init(void){ + set_current(NULL); +} + void kthread_add_wait_queue(task_struct *task){ wq_add_task(task, wait_queue); } diff --git a/src/lib/task.c b/src/lib/task.c index b1ec083d6..0d4bec13e 100644 --- a/src/lib/task.c +++ b/src/lib/task.c @@ -47,9 +47,14 @@ task_struct *task_create(void){ task->signal = signal; task->sighand = sighand; - task->maxfd = -1; task->work_dir = rootmount->root; + vfs_open("/dev/uart", 0, &task->fds[0]); + vfs_open("/dev/uart", 0, &task->fds[1]); + vfs_open("/dev/uart", 0, &task->fds[2]); + + task->maxfd = 2; + return task; } diff --git a/src/lib/uartfs.c b/src/lib/uartfs.c new file mode 100644 index 000000000..03ee7e297 --- /dev/null +++ b/src/lib/uartfs.c @@ -0,0 +1,125 @@ +#include +#include +#include + +static struct filesystem uartfs = { + .name = "uartfs", + .mount = uartfs_mount +}; + +static struct vnode_operations uartfs_v_ops = { + .lookup = uartfs_lookup, + .create = uartfs_create, + .mkdir = uartfs_mkdir, + .isdir = uartfs_isdir, + .getname = uartfs_getname, + .getsize = uartfs_getsize +}; + +static struct file_operations uartfs_f_ops = { + .write = uartfs_write, + .read = uartfs_read, + .open = uartfs_open, + .close = uartfs_close, + .lseek64 = uartfs_lseek64 +}; + +/* filesystem methods */ + +int uartfs_mount(struct filesystem *fs, struct mount *mount){ + struct vnode *oldnode; + struct uartfs_internal *internal; + const char *name; + + internal = kmalloc(sizeof(struct uartfs_internal)); + + oldnode = mount->root; + + oldnode->v_ops->getname(oldnode, &name); + + internal->name = name; + internal->oldnode.mount = oldnode->mount; + internal->oldnode.v_ops = oldnode->v_ops; + internal->oldnode.f_ops = oldnode->f_ops; + internal->oldnode.parent = oldnode->parent; + internal->oldnode.internal = oldnode->internal; + + oldnode->mount = mount; + oldnode->v_ops = &uartfs_v_ops; + oldnode->f_ops = &uartfs_f_ops; + oldnode->internal = internal; + + return 0; +} + +/* vnode_operations methods */ + +int uartfs_lookup(struct vnode *dir_node, struct vnode **target, const char *component_name){ + return -1; +} + +int uartfs_create(struct vnode *dir_node, struct vnode **target, const char *component_name){ + return -1; +} + +int uartfs_mkdir(struct vnode *dir_node, struct vnode **target, const char *component_name){ + return -1; +} + +int uartfs_isdir(struct vnode *dir_node){ + return 0; +} + +int uartfs_getname(struct vnode *dir_node, const char **name){ + struct uartfs_internal *internal; + + internal = dir_node->internal; + + *name = internal->name; + + return 0; +} + +int uartfs_getsize(struct vnode *dir_node){ + return -1; +} + +/* file_operations methods */ + +int uartfs_write(struct file *file, const void *buf, size_t len){ + uart_sendn(buf, len); + + return len; +} + +int uartfs_read(struct file *file, void *buf, size_t len){ + uart_recvn(buf, len); + + return len; +} + +int uartfs_open(struct vnode *file_node, struct file *target){ + target->vnode = file_node; + target->f_pos = 0; + target->f_ops = file_node->f_ops; + + return 0; +} + +int uartfs_close(struct file *file){ + file->vnode = NULL; + file->f_pos = 0; + file->f_ops = NULL; + + return 0; +} + +long uartfs_lseek64(struct file *file, long offset, int whence){ + return -1; +} + +/* Others */ + +struct filesystem *uartfs_init(void){ + return &uartfs; +} \ No newline at end of file diff --git a/src/lib/vfs.c b/src/lib/vfs.c index 237aea389..b181271e1 100644 --- a/src/lib/vfs.c +++ b/src/lib/vfs.c @@ -146,7 +146,12 @@ int vfs_mkdir(const char *pathname){ int ret; curname = pathname; - dir_node = get_dir_vnode(current->work_dir, &curname); + + if (current) + dir_node = get_dir_vnode(current->work_dir, &curname); + else + dir_node = get_dir_vnode(rootmount->root, &curname); + if(!dir_node) return -1; if(!curname) @@ -164,7 +169,10 @@ int vfs_mount(const char *mountpath, const char *filesystem){ int ret; curname = mountpath; - dir_node = get_dir_vnode(current->work_dir, &curname); + if (current) + dir_node = get_dir_vnode(current->work_dir, &curname); + else + dir_node = get_dir_vnode(rootmount->root, &curname); if(!dir_node) return -1; @@ -233,7 +241,7 @@ static int do_open(const char *pathname, int flags){ static int do_close(int fd){ int ret; - if(fd > current->maxfd) + if(fd < 0 || current->maxfd < fd) return -1; if(current->fds[fd].vnode == NULL) return -1; @@ -245,7 +253,7 @@ static int do_close(int fd){ static int do_write(int fd, const void *buf, uint64 count){ int ret; - if(fd > current->maxfd) + if(fd < 0 || current->maxfd < fd) return -1; if(current->fds[fd].vnode == NULL) return -1; @@ -255,7 +263,7 @@ static int do_write(int fd, const void *buf, uint64 count){ static int do_read(int fd, void *buf, uint64 count){ int ret; - if(fd > current->maxfd) + if(fd < 0 || current->maxfd < fd) return -1; if(current->fds[fd].vnode == NULL) return -1; From dd696548648e3f7909550f645750deee84b7ff56 Mon Sep 17 00:00:00 2001 From: Shin-Yan Date: Fri, 23 Jun 2023 14:32:27 +0800 Subject: [PATCH 06/10] implement frame buffer fs --- inc/cpiofs.h | 1 + inc/framebufferfs.h | 39 +++++++ inc/mbox.h | 21 ++++ inc/tmpfs.h | 1 + inc/uartfs.h | 1 + inc/vfs.h | 7 +- src/lib/cpiofs.c | 17 ++- src/lib/framebufferfs.c | 229 ++++++++++++++++++++++++++++++++++++++++ src/lib/fsinit.c | 8 +- src/lib/mbox.c | 21 ---- src/lib/task.c | 2 +- src/lib/tmpfs.c | 7 +- src/lib/uartfs.c | 7 +- src/lib/vfs.c | 41 ++++++- 14 files changed, 368 insertions(+), 34 deletions(-) create mode 100644 inc/framebufferfs.h create mode 100644 src/lib/framebufferfs.c diff --git a/inc/cpiofs.h b/inc/cpiofs.h index 3dc9994f5..67340c73e 100644 --- a/inc/cpiofs.h +++ b/inc/cpiofs.h @@ -48,6 +48,7 @@ int cpiofs_read(struct file *file, void *buf, size_t len); int cpiofs_open(struct vnode *file_node, struct file *target); int cpiofs_close(struct file *file); long cpiofs_lseek64(struct file *file, long offset, int whence); +int cpiofs_ioctl(struct file *file, uint64 request, va_list args); struct filesystem *cpiofs_init(void); diff --git a/inc/framebufferfs.h b/inc/framebufferfs.h new file mode 100644 index 000000000..cb5227fb8 --- /dev/null +++ b/inc/framebufferfs.h @@ -0,0 +1,39 @@ +#ifndef _FRAMEBUFFERFS_H +#define _FRAMEBUFFERFS_H + +#include +#include + +struct fb_info{ + uint32 width; + uint32 height; + uint32 pitch; + uint32 isrgb; +}; + +struct fbfs_internal{ + const char *name; + struct vnode oldnode; + uint8 *lfb; + uint32 lfbsize; + int isopened; + int isinit; +}; + +int fbfs_mount(struct filesystem *fs, struct mount *mount); +int fbfs_lookup(struct vnode *dir_node, struct vnode **target, const char *component_name); +int fbfs_create(struct vnode *dir_node, struct vnode **target, const char *component_name); +int fbfs_mkdir(struct vnode *dir_node, struct vnode **target, const char *component_name); +int fbfs_isdir(struct vnode *dir_node); +int fbfs_getname(struct vnode *dir_node, const char **name); +int fbfs_getsize(struct vnode *dir_node); +int fbfs_write(struct file *file, const void *buf, size_t len); +int fbfs_read(struct file *file, void *buf, size_t len); +int fbfs_open(struct vnode *file_node, struct file *target); +int fbfs_close(struct file *file); +long fbfs_lseek64(struct file *file, long offset, int whence); +int fbfs_ioctl(struct file *file, uint64 request, va_list args); + +struct filesystem *framebufferfs_init(void); + +#endif \ No newline at end of file diff --git a/inc/mbox.h b/inc/mbox.h index de3910d83..1d8e46040 100644 --- a/inc/mbox.h +++ b/inc/mbox.h @@ -17,6 +17,27 @@ extern volatile unsigned int mbox[36]; #define MBOX_CH_COUNT 7 #define MBOX_CH_PROP 8 +/* MMIO */ +#define MMIO_BASE 0x3F000000 +#define VIDEOCORE_MBOX PA2VA(MMIO_BASE+0x0000B880) +#define MBOX_READ ((volatile unsigned int*)(VIDEOCORE_MBOX+0x0)) +#define MBOX_POLL ((volatile unsigned int*)(VIDEOCORE_MBOX+0x10)) +#define MBOX_SENDER ((volatile unsigned int*)(VIDEOCORE_MBOX+0x14)) +#define MBOX_STATUS ((volatile unsigned int*)(VIDEOCORE_MBOX+0x18)) +#define MBOX_CONFIG ((volatile unsigned int*)(VIDEOCORE_MBOX+0x1C)) +#define MBOX_WRITE ((volatile unsigned int*)(VIDEOCORE_MBOX+0x20)) +#define MBOX_RESPONSE 0x80000000 +#define MBOX_FULL 0x80000000 +#define MBOX_EMPTY 0x40000000 + +/* Mailbox tags */ +#define GET_BOARD_REVISION 0x00010002 +#define GET_ARM_MEMORY 0x00010005 + +#define REQUEST_CODE 0x00000000 +#define TAG_REQUEST_CODE 0x00000000 +#define END_TAG 0x00000000 + /* tags */ #define MBOX_TAG_GETSERIAL 0x10004 #define MBOX_TAG_LAST 0 diff --git a/inc/tmpfs.h b/inc/tmpfs.h index 35c8be57b..2dba12fc3 100644 --- a/inc/tmpfs.h +++ b/inc/tmpfs.h @@ -44,6 +44,7 @@ int tmpfs_read(struct file *file, void *buf, size_t len); int tmpfs_open(struct vnode *file_node, struct file *target); int tmpfs_close(struct file *file); long tmpfs_lseek64(struct file *file, long offset, int whence); +int tmpfs_ioctl(struct file *file, uint64 request, va_list args); struct filesystem *tmpfs_init(void); diff --git a/inc/uartfs.h b/inc/uartfs.h index 55fc7364d..f1f538623 100644 --- a/inc/uartfs.h +++ b/inc/uartfs.h @@ -20,6 +20,7 @@ int uartfs_read(struct file *file, void *buf, size_t len); int uartfs_open(struct vnode *file_node, struct file *target); int uartfs_close(struct file *file); long uartfs_lseek64(struct file *file, long offset, int whence); +int uartfs_ioctl(struct file *file, uint64 request, va_list args); struct filesystem *uartfs_init(void); diff --git a/inc/vfs.h b/inc/vfs.h index 75d3aaecd..f99cdca5f 100644 --- a/inc/vfs.h +++ b/inc/vfs.h @@ -4,6 +4,7 @@ #include #include #include +#include #define O_CREATE 00000100 @@ -57,6 +58,7 @@ struct file_operations { int (*open)(struct vnode *file_node, struct file *target); int (*close)(struct file *file); long (*lseek64)(struct file *file, long offset, int whence); + int (*ioctl)(struct file *file, uint64 request, va_list args); }; extern struct mount *rootmount; @@ -72,14 +74,15 @@ int vfs_read(struct file *file, void *buf, size_t len); int vfs_mkdir(const char *pathname); int vfs_mount(const char *mountpath, const char *filesystem); int vfs_lookup(const char *pathname, struct vnode **target); +long vfs_lseek64(struct file *file, long offset, int whence); +int vfs_ioctl(struct file *file, uint64 request, va_list args); void syscall_open(trapframe *frame, const char *pathname, int flags); void syscall_close(trapframe *frame, int fd); void syscall_write(trapframe *frame, int fd, const void *buf, uint64 count); void syscall_read(trapframe *frame, int fd, void *buf, uint64 count); void syscall_mkdir(trapframe *frame, const char *pathname, uint32 mode); -void syscall_mount(trapframe *frame, const char *src, const char *target, - const char *filesystem, uint64 flags, const void *data); +void syscall_mount(trapframe *frame, const char *src, const char *target, const char *filesystem, uint64 flags, const void *data); void syscall_chdir(trapframe *frame, const char *path); void syscall_lseek64(trapframe *frame, int fd, int64 offset, int whence); void syscall_ioctl(trapframe *frame, int fd, uint64 request, ...); diff --git a/src/lib/cpiofs.c b/src/lib/cpiofs.c index f8d26aa5a..6bfedb5fe 100644 --- a/src/lib/cpiofs.c +++ b/src/lib/cpiofs.c @@ -4,6 +4,7 @@ #include #include #include +#include static struct vnode cpio_root_node; static struct vnode mount_old_node; @@ -28,7 +29,8 @@ static struct file_operations cpiofs_f_ops = { .read = cpiofs_read, .open = cpiofs_open, .close = cpiofs_close, - .lseek64 = cpiofs_lseek64 + .lseek64 = cpiofs_lseek64, + .ioctl = cpiofs_ioctl }; static struct vnode *get_dir_vnode(struct vnode *dir_node, const char **pathname){ @@ -205,8 +207,15 @@ int cpiofs_mount(struct filesystem *fs, struct mount *mount){ struct vnode *oldnode; struct cpiofs_internal *internal; const char *name; - if(cpio_mounted) + preempt_disable(); + + if(cpio_mounted){ + preempt_enable(); return -1; + } + + cpio_mounted = 1; + preempt_enable(); oldnode = mount->root; oldnode->v_ops->getname(oldnode, &name); @@ -334,4 +343,8 @@ long cpiofs_lseek64(struct file *file, long offset, int whence){ file->f_pos = base + offset; return 0; +} + +int cpiofs_ioctl(struct file *file, uint64 request, va_list args){ + return -1; } \ No newline at end of file diff --git a/src/lib/framebufferfs.c b/src/lib/framebufferfs.c new file mode 100644 index 000000000..0582f159e --- /dev/null +++ b/src/lib/framebufferfs.c @@ -0,0 +1,229 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +static struct filesystem fbfs = { + .name = "framebufferfs", + .mount = fbfs_mount +}; + +static struct vnode_operations fbfs_v_ops = { + .lookup = fbfs_lookup, + .create = fbfs_create, + .mkdir = fbfs_mkdir, + .isdir = fbfs_isdir, + .getname = fbfs_getname, + .getsize = fbfs_getsize +}; + +static struct file_operations fbfs_f_ops = { + .write = fbfs_write, + .read = fbfs_read, + .open = fbfs_open, + .close = fbfs_close, + .lseek64 = fbfs_lseek64 +}; + + +int fbfs_mount(struct filesystem *fs, struct mount *mount){ + struct vnode *oldnode; + struct fbfs_internal *internal; + const char *name; + + internal = kmalloc(sizeof(struct fbfs_internal)); + + oldnode = mount->root; + + oldnode->v_ops->getname(oldnode, &name); + + internal->name = name; + internal->oldnode.mount = oldnode->mount; + internal->oldnode.v_ops = oldnode->v_ops; + internal->oldnode.f_ops = oldnode->f_ops; + internal->oldnode.parent = oldnode->parent; + internal->oldnode.internal = oldnode->internal; + internal->lfb = NULL; + internal->isopened = 0; + internal->isinit = 0; + + oldnode->mount = mount; + oldnode->v_ops = &fbfs_v_ops; + oldnode->f_ops = &fbfs_f_ops; + oldnode->internal = internal; + + return 0; +} +int fbfs_lookup(struct vnode *dir_node, struct vnode **target, const char *component_name){ + return -1; +} +int fbfs_create(struct vnode *dir_node, struct vnode **target, const char *component_name){ + return -1; +} +int fbfs_mkdir(struct vnode *dir_node, struct vnode **target, const char *component_name){ + return -1; +} +int fbfs_isdir(struct vnode *dir_node){ + return 0; +} +int fbfs_getname(struct vnode *dir_node, const char **name){ + struct fbfs_internal *internal = dir_node->internal; + *name = internal->name; + return 0; +} +int fbfs_getsize(struct vnode *dir_node){ + return -1; +} +int fbfs_write(struct file *file, const void *buf, size_t len){ + struct fbfs_internal *internal = file->vnode->internal; + if(!internal->isinit) + return -1; + + if(file->f_pos + len > internal->lfbsize) + return -1; +} +int fbfs_read(struct file *file, void *buf, size_t len){ + return -1; +} +int fbfs_open(struct vnode *file_node, struct file *target){ + struct fbfs_internal *internal; + preempt_disable(); + internal = file_node->internal; + if(internal->isopened){ + preempt_enable(); + return -1; + } + internal->isopened = 1; + preempt_enable(); + target->vnode = file_node; + target->f_pos = 0; + target->f_ops = file_node->f_ops; + + return 0; +} +int fbfs_close(struct file *file){ + struct fbfs_internal *internal = file->vnode->internal; + + file->vnode = NULL; + file->f_pos = 0; + file->f_ops = NULL; + + internal->isopened = 0; + return 0; +} +long fbfs_lseek64(struct file *file, long offset, int whence){ + struct fbfs_internal *internal = file->vnode->internal; + int base; + switch(whence){ + case SEEK_SET: + base = 0; + break; + case SEEK_CUR: + base = file->f_pos; + break; + case SEEK_END: + base = internal->lfbsize; + default: + return -1; + } + + if(base + offset > internal->lfbsize) + return -1; + file->f_pos = base + offset; + return 0; +} + +int fbfs_ioctl(struct file *file, uint64 request, va_list args) +{ + struct fb_info *user_fb_info; + struct fbfs_internal *internal; + /* dimensions and channel order */ + uint32 width, height, pitch, isrgb; + + if (request != 0) { + return -1; + } + + internal = file->vnode->internal; + + mbox[0] = 35 * 4; + mbox[1] = REQUEST_CODE; + + mbox[2] = 0x48003; // set phy wh + mbox[3] = 8; + mbox[4] = 8; + mbox[5] = 1024; // FrameBufferInfo.width + mbox[6] = 768; // FrameBufferInfo.height + + mbox[7] = 0x48004; // set virt wh + mbox[8] = 8; + mbox[9] = 8; + mbox[10] = 1024; // FrameBufferInfo.virtual_width + mbox[11] = 768; // FrameBufferInfo.virtual_height + + mbox[12] = 0x48009; // set virt offset + mbox[13] = 8; + mbox[14] = 8; + mbox[15] = 0; // FrameBufferInfo.x_offset + mbox[16] = 0; // FrameBufferInfo.y.offset + + mbox[17] = 0x48005; // set depth + mbox[18] = 4; + mbox[19] = 4; + mbox[20] = 32; // FrameBufferInfo.depth + + mbox[21] = 0x48006; // set pixel order + mbox[22] = 4; + mbox[23] = 4; + mbox[24] = 1; // RGB, not BGR preferably + + mbox[25] = 0x40001; // get framebuffer, gets alignment on request + mbox[26] = 8; + mbox[27] = 8; + mbox[28] = 4096; // FrameBufferInfo.pointer + mbox[29] = 0; // FrameBufferInfo.size + + mbox[30] = 0x40008; // get pitch + mbox[31] = 4; + mbox[32] = 4; + mbox[33] = 0; // FrameBufferInfo.pitch + + mbox[34] = END_TAG; + + // this might not return exactly what we asked for, could be + // the closest supported resolution instead + + mbox_call(MBOX_CH_PROP, mbox); + + if (mbox[20] == 32 && mbox[28] != 0) { + mbox[28] &= 0x3FFFFFFF; // convert GPU address to ARM address + width = mbox[5]; // get actual physical width + height = mbox[6]; // get actual physical height + pitch = mbox[33]; // get number of bytes per line + isrgb = mbox[24]; // get the actual channel order + internal->lfb = (void *)PA2VA(mbox[28]); + internal->lfbsize = mbox[29]; + } else { + // Unable to set screen resolution to 1024x768x32 + return -1; + } + + user_fb_info = va_arg(args, void *); + + user_fb_info->width = width; + user_fb_info->height = height; + user_fb_info->pitch = pitch; + user_fb_info->isrgb = isrgb; + + internal->isinit = 1; + + return 0; +} + +struct filesystem *framebufferfs_init(void){ + return &fbfs; +} \ No newline at end of file diff --git a/src/lib/fsinit.c b/src/lib/fsinit.c index 1a4938a4b..8cdc7b681 100644 --- a/src/lib/fsinit.c +++ b/src/lib/fsinit.c @@ -3,17 +3,20 @@ #include #include #include +#include void fs_early_init(void){ - struct filesystem *tmpfs, *cpiofs, *uartfs; + struct filesystem *tmpfs, *cpiofs, *uartfs, *fbfs; vfs_init(); tmpfs = tmpfs_init(); cpiofs = cpiofs_init(); uartfs = uartfs_init(); + fbfs = framebufferfs_init(); register_filesystem(tmpfs); register_filesystem(cpiofs); register_filesystem(uartfs); + register_filesystem(fbfs); vfs_init_rootmount(tmpfs); @@ -24,4 +27,7 @@ void fs_early_init(void){ vfs_mkdir("/dev/uart"); vfs_mount("/dev/uart", "uartfs"); + + vfs_mkdir("/dev/framebuffer"); + vfs_mount("/dev/framebuffer", "framebufferfs"); } \ No newline at end of file diff --git a/src/lib/mbox.c b/src/lib/mbox.c index b43ffae69..6a4002a87 100644 --- a/src/lib/mbox.c +++ b/src/lib/mbox.c @@ -2,30 +2,9 @@ #include #include -#define MMIO_BASE 0x3F000000 - /* mailbox message buffer */ volatile unsigned int __attribute__((aligned(16))) mbox[36]; -#define VIDEOCORE_MBOX PA2VA(MMIO_BASE+0x0000B880) -#define MBOX_READ ((volatile unsigned int*)(VIDEOCORE_MBOX+0x0)) -#define MBOX_POLL ((volatile unsigned int*)(VIDEOCORE_MBOX+0x10)) -#define MBOX_SENDER ((volatile unsigned int*)(VIDEOCORE_MBOX+0x14)) -#define MBOX_STATUS ((volatile unsigned int*)(VIDEOCORE_MBOX+0x18)) -#define MBOX_CONFIG ((volatile unsigned int*)(VIDEOCORE_MBOX+0x1C)) -#define MBOX_WRITE ((volatile unsigned int*)(VIDEOCORE_MBOX+0x20)) -#define MBOX_RESPONSE 0x80000000 -#define MBOX_FULL 0x80000000 -#define MBOX_EMPTY 0x40000000 - -/* Mailbox tags */ -#define GET_BOARD_REVISION 0x00010002 -#define GET_ARM_MEMORY 0x00010005 - -#define REQUEST_CODE 0x00000000 -#define TAG_REQUEST_CODE 0x00000000 -#define END_TAG 0x00000000 - /** * Make a mailbox call. Returns 0 on failure, non-zero on success */ diff --git a/src/lib/task.c b/src/lib/task.c index 0d4bec13e..9e3583145 100644 --- a/src/lib/task.c +++ b/src/lib/task.c @@ -72,7 +72,7 @@ void task_free(task_struct *task){ for(int i = 0 ; i <= task->maxfd ;++i){ if(task->fds[i].vnode != NULL){ - task->fds[i].f_ops->close(&task->fds[i]); + vfs_close(&task->fds[i]); } } diff --git a/src/lib/tmpfs.c b/src/lib/tmpfs.c index 18f307b1a..f9f798181 100644 --- a/src/lib/tmpfs.c +++ b/src/lib/tmpfs.c @@ -22,7 +22,8 @@ static struct file_operations tmpfs_f_ops = { .read = tmpfs_read, .open = tmpfs_open, .close = tmpfs_close, - .lseek64 = tmpfs_lseek64 + .lseek64 = tmpfs_lseek64, + .ioctl = tmpfs_ioctl }; int tmpfs_mount(struct filesystem *fs, struct mount *mount){ @@ -277,6 +278,10 @@ long tmpfs_lseek64(struct file *file, long offset, int whence){ return 0; } +int tmpfs_ioctl(struct file *file, uint64 request, va_list args){ + return -1; +} + struct filesystem *tmpfs_init(void){ return &tmpfs; } \ No newline at end of file diff --git a/src/lib/uartfs.c b/src/lib/uartfs.c index 03ee7e297..663598a45 100644 --- a/src/lib/uartfs.c +++ b/src/lib/uartfs.c @@ -21,7 +21,8 @@ static struct file_operations uartfs_f_ops = { .read = uartfs_read, .open = uartfs_open, .close = uartfs_close, - .lseek64 = uartfs_lseek64 + .lseek64 = uartfs_lseek64, + .ioctl = uartfs_ioctl }; /* filesystem methods */ @@ -118,6 +119,10 @@ long uartfs_lseek64(struct file *file, long offset, int whence){ return -1; } +int uartfs_ioctl(struct file *file, uint64 request, va_list args){ + return -1; +} + /* Others */ struct filesystem *uartfs_init(void){ diff --git a/src/lib/vfs.c b/src/lib/vfs.c index b181271e1..2754639d9 100644 --- a/src/lib/vfs.c +++ b/src/lib/vfs.c @@ -220,6 +220,13 @@ int vfs_lookup(const char *pathname, struct vnode **target){ return 0; } +long vfs_lseek64(struct file *file, long offset, int whence){ + return file->f_ops->lseek64(file, offset, whence); +} +int vfs_ioctl(struct file *file, uint64 request, va_list args){ + return file->f_ops->ioctl(file, request, args); +} + static int do_open(const char *pathname, int flags){ int i, ret; for(i = 0 ; i <= current->maxfd ;++i){ @@ -234,8 +241,10 @@ static int do_open(const char *pathname, int flags){ i = current->maxfd; } ret = vfs_open(pathname, flags, ¤t->fds[i]); - if(ret < 0) + if(ret < 0){ + current->fds[i].vnode = NULL; return ret; + } return i; } @@ -296,6 +305,26 @@ static int do_chdir(const char *path){ return 0; } +static long do_lseek64(int fd, int64 offset, int whence){ + long ret; + if(fd < 0 || current->maxfd < fd) + return -1; + if(current->fds[fd].vnode == NULL) + return -1; + ret = vfs_lseek64(¤t->fds[fd], offset, whence); + return ret; +} + +static int do_ioctl(int fd, uint64 request, va_list args){ + int ret; + if(fd < 0 || current->maxfd < fd) + return -1; + if(current->fds[fd].vnode == NULL) + return -1; + ret = vfs_ioctl(¤t->fds[fd], request, args); + return ret; +} + void syscall_open(trapframe *frame, const char *pathname, int flags){ int fd = do_open(pathname, flags); frame->x0 = fd; @@ -326,12 +355,14 @@ void syscall_chdir(trapframe *frame, const char *path){ frame->x0 = ret; } void syscall_lseek64(trapframe *frame, int fd, int64 offset, int whence){ - // TODO - int ret = -1; + long ret = do_lseek64(fd, offset, whence); frame->x0 = ret; } void syscall_ioctl(trapframe *frame, int fd, uint64 request, ...){ - // TODO - int ret = -1; + int ret ; + va_list args; + va_start(args, request); + ret = do_ioctl(fd, request, args); + va_end(args); frame->x0 = ret; } \ No newline at end of file From fbc4670374a3fa896d1b2c01534245c4b7536197 Mon Sep 17 00:00:00 2001 From: Shin-Yan Date: Fri, 23 Jun 2023 15:12:59 +0800 Subject: [PATCH 07/10] fix bugs in framebuffer callback function --- src/lib/framebufferfs.c | 8 +++++++- src/lib/syscall.c | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/lib/framebufferfs.c b/src/lib/framebufferfs.c index 0582f159e..d18116d54 100644 --- a/src/lib/framebufferfs.c +++ b/src/lib/framebufferfs.c @@ -26,7 +26,8 @@ static struct file_operations fbfs_f_ops = { .read = fbfs_read, .open = fbfs_open, .close = fbfs_close, - .lseek64 = fbfs_lseek64 + .lseek64 = fbfs_lseek64, + .ioctl = fbfs_ioctl }; @@ -85,6 +86,11 @@ int fbfs_write(struct file *file, const void *buf, size_t len){ if(file->f_pos + len > internal->lfbsize) return -1; + + memncpy((void *)(internal->lfb + file->f_pos), buf, len); + file->f_pos += len; + + return len; } int fbfs_read(struct file *file, void *buf, size_t len){ return -1; diff --git a/src/lib/syscall.c b/src/lib/syscall.c index 3973b96df..fd5c21283 100644 --- a/src/lib/syscall.c +++ b/src/lib/syscall.c @@ -59,8 +59,8 @@ void syscall_handler(trapframe *regs) syscall_num = regs->x8; - if(syscall_num > 2) - // uart_sync_printf("syscall number:%d\n", syscall_num); + // if(syscall_num > 2) + // uart_sync_printf("syscall number:%d\n", syscall_num); if (syscall_num >= ARRAY_SIZE(syscall_table)) { // Invalid syscall From 2a538d295143d870c3b4dfe116faf1fa27924343 Mon Sep 17 00:00:00 2001 From: Shin-Yan Date: Mon, 26 Jun 2023 11:51:26 +0800 Subject: [PATCH 08/10] add some comment --- inc/arm.h | 4 ++++ inc/mmu.h | 3 ++- inc/utils.h | 5 +++++ src/lib/entry.c | 2 ++ src/lib/mmu.c | 11 ++++++++--- src/lib/syscall.c | 4 ++-- 6 files changed, 23 insertions(+), 6 deletions(-) diff --git a/inc/arm.h b/inc/arm.h index 099d420eb..8b8f4c239 100644 --- a/inc/arm.h +++ b/inc/arm.h @@ -7,16 +7,20 @@ /* ==== ESR_EL1 related ==== */ #define EC_SVC_64 0x15 +// instruction abort #define EC_IA_LE 0x20 +// data abort #define EC_DA_LE 0x24 #define ISS_FSC(esr) (esr->iss & 0x3f) +// Translation faults #define FSC_TF_L0 0b000100 #define FSC_TF_L1 0b000101 #define FSC_TF_L2 0b000110 #define FSC_TF_L3 0b000111 +// write abort #define ISS_WnR(esr) (esr->iss & 0x40) typedef struct{ diff --git a/inc/mmu.h b/inc/mmu.h index ae91f6a95..8a1943363 100644 --- a/inc/mmu.h +++ b/inc/mmu.h @@ -34,7 +34,8 @@ typedef struct{ struct list_head vma; } vm_area_meta_t; -#define PROT_NOTE 0 +// the file is not accessible +#define PROT_NONE 0 #define PROT_READ 1 #define PROT_WRITE 2 #define PROT_EXEC 4 diff --git a/inc/utils.h b/inc/utils.h index e3c04a07b..868d81f7e 100644 --- a/inc/utils.h +++ b/inc/utils.h @@ -15,6 +15,11 @@ asm volatile("msr DAIFSet, 0xf"); \ } + +// ttbr0_el1 only uses the lower 48 bits of register +// dsb: data synchronization barrier +// ttbr0_el1: holds the base address of the translation table for stage 1 memory management for exception level1 +// tlbi vmalle1is: This invalidates all entries in the TLB corresponding to the current value of TTBR0_EL1. #define set_page_table(page_table) do { \ asm volatile( \ "mov x9, %0\n" \ diff --git a/src/lib/entry.c b/src/lib/entry.c index 229ffaf83..ed93a54a8 100644 --- a/src/lib/entry.c +++ b/src/lib/entry.c @@ -13,7 +13,9 @@ void el0_sync_handler(trapframe *regs, uint32 syn){ case EC_SVC_64: syscall_handler(regs); break; + // instruction abort case EC_IA_LE: + // data abort case EC_DA_LE: mem_abort(esr); break; diff --git a/src/lib/mmu.c b/src/lib/mmu.c index cc8549c66..961292a91 100644 --- a/src/lib/mmu.c +++ b/src/lib/mmu.c @@ -9,6 +9,7 @@ #include #include +// translation control register #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) @@ -22,7 +23,10 @@ #define PD_BLOCK 0b01 #define PD_ACCESS (1 << 10) #define PD_PXN ((uint64)1 << 53) +// none secure #define PD_NSTABLE ((uint64)1 << 63) + +// unprivileged access will never execute the code #define PD_UXNTABLE ((uint64)1 << 60) #define PD_MAIR_DEVICE_IDX (MAIR_IDX_DEVICE_nGnRnE << 2) #define PD_MAIR_NORMAL_IDX (MAIR_IDX_NORMAL_NOCACHE << 2) @@ -135,7 +139,7 @@ static void free_uva_region(uint64 uva_begin, uint64 uva_end) for (uint64 addr = uva_begin; addr < uva_end; addr += PAGE_SIZE) { uint64 par; - // try to get the PA of UVA + // try to get the PA of UVA, address translation, stage1, el0 read translation of a virtual address asm volatile ( "at s1e0r, %0" :: "r" (addr) @@ -208,6 +212,7 @@ void mmu_init(void) BOOT_PMD[i] = (i * (1 << 21)) | PD_MAIR_NORMAL_IDX | PD_BE; } + // 0x3F000000 to 0x3FFFFFFF for peripherals for (int i = 504; i < 512; ++i) { BOOT_PMD[i] = (i * (1 << 21)) | PD_MAIR_DEVICE_IDX | PD_BE; } @@ -215,7 +220,7 @@ void mmu_init(void) write_sysreg(TTBR0_EL1, BOOT_PGD); write_sysreg(TTBR1_EL1, BOOT_PGD); - // Enable MMU + // Enable MMU, sctlr: system control register sctlr_el1 = read_sysreg(SCTLR_EL1); write_sysreg(SCTLR_EL1, sctlr_el1 | 1); } @@ -398,7 +403,7 @@ static void do_page_fault(esr_el1_t *esr) uint64 va; uint64 fault_perm; vm_area_t *vma; - + // fault address register far = read_sysreg(FAR_EL1); vma = vma_find(current->address_space, far); diff --git a/src/lib/syscall.c b/src/lib/syscall.c index fd5c21283..716beb8f0 100644 --- a/src/lib/syscall.c +++ b/src/lib/syscall.c @@ -59,8 +59,8 @@ void syscall_handler(trapframe *regs) syscall_num = regs->x8; - // if(syscall_num > 2) - // uart_sync_printf("syscall number:%d\n", syscall_num); + if(syscall_num != 2) + uart_sync_printf("syscall number:%d\n", syscall_num); if (syscall_num >= ARRAY_SIZE(syscall_table)) { // Invalid syscall From 90ba93d3a9fc91dcf9748b4fa9c1412848c778fb Mon Sep 17 00:00:00 2001 From: Shin-Yan Date: Tue, 27 Jun 2023 17:39:17 +0800 Subject: [PATCH 09/10] add fat32 filesystem with read and write --- Makefile | 10 +- createimg.sh | 40 +++ inc/fat32fs.h | 167 ++++++++++++ inc/list.h | 16 ++ inc/mm.h | 3 + inc/sdhost.h | 66 +++++ inc/string.h | 3 + src/kernel/main.c | 2 +- src/lib/fat32fs.c | 679 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib/fsinit.c | 11 +- src/lib/mm.S | 7 + src/lib/sdhost.c | 164 +++++++++++ src/lib/string.c | 58 ++++ src/lib/syscall.c | 4 +- 14 files changed, 1224 insertions(+), 6 deletions(-) create mode 100755 createimg.sh create mode 100644 inc/fat32fs.h create mode 100644 inc/sdhost.h create mode 100644 src/lib/fat32fs.c create mode 100644 src/lib/sdhost.c diff --git a/Makefile b/Makefile index bc14bf050..eec37122f 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,7 @@ KERNEL_IMG := $(IMG_DIR)/kernel8.img KERNEL_ELF := $(OBJ_DIR)/$(KERNEL_DIR)/kernel8.elf BOOTLOADER_IMG := $(IMG_DIR)/bootloader.img BOOTLOADER_ELF := $(OBJ_DIR)/$(BOOT_DIR)/bootloader.elf +IMG_NAME := sdcard.img RPI3_DTB := $(IMG_DIR)/bcm2710-rpi-3-b-plus.dtb INITRAMFS_CPIO := $(IMG_DIR)/initramfs.cpio @@ -87,15 +88,20 @@ $(OBJ_DIR)/$(LIB_DIR)/%_c.o: $(SRC_DIR)/$(LIB_DIR)/%.c $(INITRAMFS_CPIO): $(INITRAMFS_FILE) cd rootfs; find . | cpio -o -H newc > ../$(INITRAMFS_CPIO) +$(IMG_NAME): + ./createimg.sh $(IMG_NAME) + qemub: all $(INITRAMFS_CPIO) $(RPI3_DTB) qemu-system-aarch64 -M raspi3 -kernel $(BOOTLOADER_IMG) -display none \ -dtb $(RPI3_DTB) \ -initrd $(INITRAMFS_CPIO) \ -serial null -serial stdio -qemuk: all $(INITRAMFS_CPIO) $(RPI3_DTB) + +qemuk: all $(INITRAMFS_CPIO) $(RPI3_DTB) $(IMG_NAME) qemu-system-aarch64 -M raspi3 -kernel $(KERNEL_IMG) -display none \ -dtb $(RPI3_DTB) \ -initrd $(INITRAMFS_CPIO) \ + -drive if=sd,file=$(IMG_NAME),format=raw \ -serial null -serial stdio qemutty: $(KERNEL_IMG) @@ -108,4 +114,4 @@ clean: .PHONY: clean-all clean-all: clean - rm -f $(KERNEL_IMG) $(BOOTLOADER_IMG) \ No newline at end of file + rm -f $(KERNEL_IMG) $(BOOTLOADER_IMG) $(IMG_NAME) \ No newline at end of file diff --git a/createimg.sh b/createimg.sh new file mode 100755 index 000000000..c29258f6b --- /dev/null +++ b/createimg.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +IMG_NAME=sdcard.img + +truncate -s 64M $IMG_NAME + +( +echo o # Create a new empty DOS partition table +echo n # Add a new partition +echo p # Primary partition +echo 1 # Partition number +echo # First sector (Accept default: 1) +echo # Last sector (Accept default: varies) +echo t # Change partition type +echo c # Master Boot Record primary partitions type:LBA +echo w # Write changes +) | sudo fdisk $IMG_NAME + +LOOPBACK=`sudo losetup --partscan --show --find $IMG_NAME` + +echo ${LOOPBACK} | grep --quiet "/dev/loop" + +if [ $? = 1 ] +then + echo "[!] losetup failed!" + exit 1 +fi + +sudo mkfs.vfat -F 32 ${LOOPBACK}p1 + +mkdir -p mnt + +sudo mount -t vfat ${LOOPBACK}p1 mnt + +sudo cp -r img/* mnt + +sudo umount mnt + +sudo losetup -d ${LOOPBACK} +# dd if=./sdcard.img of=/dev/ \ No newline at end of file diff --git a/inc/fat32fs.h b/inc/fat32fs.h new file mode 100644 index 000000000..1f487a39e --- /dev/null +++ b/inc/fat32fs.h @@ -0,0 +1,167 @@ +#ifndef _FAT32FS_H +#define _FAT32FS_H + +#include +#include + +#define BLOCK_SIZE 512 +#define CLUSTER_ENTRY_PER_BLOCK (BLOCK_SIZE / sizeof(struct cluster_entry_t)) +#define DIR_PER_BLOCK (BLOCK_SIZE / sizeof(struct dir_t)) +#define FAT_DIR 1 +#define FAT_FILE 2 + +#define ATTR_READ_ONLY 0x01 +#define ATTR_HIDDEN 0x02 +#define ATTR_SYSTEM 0x04 +#define ATTR_VOLUME_ID 0x08 +#define ATTR_DIRECTORY 0x10 +#define ATTR_ARCHIVE 0x20 +#define ATTR_FILE_DIR_MASK (ATTR_DIRECTORY | ATTR_ARCHIVE) + +struct partition_t { + uint8 status; + uint8 chss_head; + uint8 chss_sector; + uint8 chss_cylinder; + uint8 type; + uint8 chse_head; + uint8 chse_sector; + uint8 chse_cylinder; + uint32 lba; + uint32 sectors; +} __attribute__((packed)); + +struct boot_sector_t { + uint8 jmpboot[3]; + uint8 oemname[8]; + uint16 bytes_per_sector; + uint8 sector_per_cluster; + uint16 reserved_sector_cnt; + uint8 fat_cnt; + uint16 root_entry_cnt; + uint16 old_sector_cnt; + uint8 media; + uint16 sector_per_fat16; + uint16 sector_per_track; + uint16 head_cnt; + uint32 hidden_sector_cnt; + uint32 sector_cnt; + uint32 sector_per_fat32; + uint16 extflags; + uint16 ver; + uint32 root_cluster; + uint16 info; + uint16 bkbooksec; + uint8 reserved[12]; + uint8 drvnum; + uint8 reserved1; + uint8 bootsig; + uint32 volid; + uint8 vollab[11]; + uint8 fstype[8]; +} __attribute__((packed)); + +struct dir_t { + uint8 name[11]; + uint8 attr; + uint8 ntres; + uint8 crttimetenth; + uint16 crttime; + uint16 crtdate; + uint16 lstaccdate; + uint16 ch; + uint16 wrttime; + uint16 wrtdate; + uint16 cl; + uint32 size; +} __attribute__((packed)); + +struct long_dir_t { + uint8 order; + uint8 name1[10]; + uint8 attr; + uint8 type; + uint8 checksum; + uint8 name2[12]; + uint16 fstcluslo; + uint8 name3[4]; +} __attribute__((packed)); + +struct cluster_entry_t{ + union + { + uint32 val; + struct { + uint32 idx:28; + uint32 reserved: 4; + }; + }; +}; + +struct filename_t{ + union { + uint8 fullname[256]; + struct{ + uint8 name[13]; + } part[20]; + }; +} __attribute__((packed)); + + +struct fat_file_block_t{ + struct list_head list; + uint32 oid; + uint32 cid; + /* Already read the data into buf*/ + uint32 read; + uint8 buf[BLOCK_SIZE]; +}; + +struct fat_file_t{ + /* Head of fat_file_block_t chain */ + struct list_head list; + uint32 size; +}; + +struct fat_dir_t { + /* Head of fat_internal chain */ + struct list_head list; +}; + +struct fat_info_t{ + struct boot_sector_t bs; + uint32 fat_lba; + uint32 cluster_lba; +}; + +struct fat_internal{ + const char *name; + struct vnode *node; + struct fat_info_t *fat; + struct list_head list; + uint32 cid; + uint32 type; + union { + struct fat_dir_t *dir; + struct fat_file_t *file; + }; +}; + +int fat32fs_mount(struct filesystem *fs, struct mount *mount); + +int fat32fs_lookup(struct vnode *dir_node, struct vnode **target, const char *component_name); +int fat32fs_create(struct vnode *dir_node, struct vnode **target, const char *component_name); +int fat32fs_mkdir(struct vnode *dir_node, struct vnode **target, const char *component_name); +int fat32fs_isdir(struct vnode *dir_node); +int fat32fs_getname(struct vnode *dir_node, const char **name); +int fat32fs_getsize(struct vnode *dir_node); + +int fat32fs_write(struct file *file, const void *buf, size_t len); +int fat32fs_read(struct file *file, void *buf, size_t len); +int fat32fs_open(struct vnode *file_node, struct file *target); +int fat32fs_close(struct file *file); +long fat32fs_lseek64(struct file *file, long offset, int whence); +int fat32fs_ioctl(struct file *file, uint64 request, va_list args); +struct filesystem *fat32fs_init(void); + +#endif \ No newline at end of file diff --git a/inc/list.h b/inc/list.h index b84117a7e..60aeea8e4 100644 --- a/inc/list.h +++ b/inc/list.h @@ -399,6 +399,22 @@ static inline void list_move_tail(struct list_head *node, entry = list_entry(entry->member.next, __typeof__(*entry), member)) #endif +/** + * iter_for_each_entry - iterate over list entries from @iter + * @entry: pointer used as iterator + * @iter: pointer to the start of this iteration + * @head: pointer to the head of the list + * @member: name of the list_head member variable in struct type of @entry + * + * FIXME: remove dependency of __typeof__ extension + */ +#ifdef __LIST_HAVE_TYPEOF +#define iter_for_each_entry(entry, iter, head, member) \ + for (entry = list_entry(iter, __typeof__(*entry), member); \ + &entry->member != (head); \ + entry = list_entry(entry->member.next, __typeof__(*entry), member)) +#endif /* __LIST_HAVE_TYPEOF */ + /** * list_for_each_safe - Iterate over list nodes and allow deletions * @node: list_head pointer used as iterator diff --git a/inc/mm.h b/inc/mm.h index e539c7c09..ad31d9dbb 100644 --- a/inc/mm.h +++ b/inc/mm.h @@ -2,8 +2,11 @@ #define _MM_H #ifndef __ASSEMBLER__ + +#include void memzero(char* src, unsigned long n); void memncpy(char *dst, const char *src, unsigned long n); +void memset(void *ptr, uint8 value, uint64 bum); void mm_init(char *fdt_base); void *kmalloc(int size); diff --git a/inc/sdhost.h b/inc/sdhost.h new file mode 100644 index 000000000..839599257 --- /dev/null +++ b/inc/sdhost.h @@ -0,0 +1,66 @@ +#ifndef _SDHOST_H +#define _SDHOST_H + +#include +#define BLOCK_SIZE 512 + +// 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 + +// sdhost +#define SDHOST_BASE PA2VA(PERIPHERALS_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) + +void sd_init(void); +void sd_readblock(int block_idx, void *buf); +void sd_writeblock(int block_idx, const void *buf); + +#endif \ No newline at end of file diff --git a/inc/string.h b/inc/string.h index c4a770785..ce3533dbb 100644 --- a/inc/string.h +++ b/inc/string.h @@ -2,9 +2,12 @@ #define _STRING_H int strcmp(const char *X, const char *Y); +int strcasecmp(const char *X, const char *Y); int strncmp(const char *X, const char *Y, int n); int strlen(const char *str); int strcpy(char *dst, const char *src); +char *strcat(char *dest, const char *src); +char *strncat(char *dest, const char *src, int n); int atoi(const char *str); #endif /* _STRING_H */ \ No newline at end of file diff --git a/src/kernel/main.c b/src/kernel/main.c index ebbbcc203..934beb3b4 100644 --- a/src/kernel/main.c +++ b/src/kernel/main.c @@ -105,7 +105,7 @@ void kernel_main(char *fdt){ uart_printf("[*] fdt base: %x\r\n", fdt); // kthread_create(shell_interact); - sched_new_user_prog("/initramfs/vfs1.img"); + sched_new_user_prog("/initramfs/vfs2.img"); enable_irqs1(); enable_interrupt(); diff --git a/src/lib/fat32fs.c b/src/lib/fat32fs.c new file mode 100644 index 000000000..b341a164a --- /dev/null +++ b/src/lib/fat32fs.c @@ -0,0 +1,679 @@ +#include +#include +#include +#include +#include + +static struct filesystem fat32fs = { + .name = "fat32fs", + .mount = fat32fs_mount +}; + +static struct vnode_operations fat32fs_v_ops = { + .lookup = fat32fs_lookup, + .create = fat32fs_create, + .mkdir = fat32fs_mkdir, + .isdir = fat32fs_isdir, + .getname = fat32fs_getname, + .getsize = fat32fs_getsize +}; + +static struct file_operations fat32fs_f_ops = { + .write = fat32fs_write, + .read = fat32fs_read, + .open = fat32fs_open, + .close = fat32fs_close, + .lseek64 = fat32fs_lseek64, + .ioctl = fat32fs_ioctl +}; + +static struct vnode *create_vnode(struct vnode *parent, const char *name, uint32 type, uint32 cid, uint32 size){ + struct vnode *node; + struct fat_internal *info, *data; + char *buf; + int len; + + info = parent->internal; + len = strlen(name); + buf = kmalloc(len+1); + node = kmalloc(sizeof(struct vnode)); + data = kmalloc(sizeof(struct fat_internal)); + + strcpy(buf, name); + data->name = buf; + data->node = node; + data->fat = info->fat; + data->cid = cid; + data->type = type; + + if(type == FAT_DIR){ + struct fat_dir_t *dir; + dir = kmalloc(sizeof(struct fat_dir_t)); + INIT_LIST_HEAD(&dir->list); + data->dir = dir; + } + else{ + struct fat_file_t *file; + file = kmalloc(sizeof(struct fat_file_t)); + INIT_LIST_HEAD(&file->list); + file->size = size; + data->file = file; + } + + node->mount = parent->mount; + node->v_ops = &fat32fs_v_ops; + node->f_ops = &fat32fs_f_ops; + node->parent = parent; + node->internal = data; + + list_add(&data->list, &info->dir->list); + + return node; +} + +static int get_next_cluster(uint32 fat_lba, uint32 cluster_id){ + struct cluster_entry_t *ce; + uint32 cid; + uint8 buf[BLOCK_SIZE]; + + if(cluster_id >= 0x0ffffff8) + return cluster_id; + + fat_lba += cluster_id / CLUSTER_ENTRY_PER_BLOCK; + cid = cluster_id % CLUSTER_ENTRY_PER_BLOCK; + + // TODO: Cache FAT + sd_readblock(fat_lba, buf); + + // get cluster entry with cid and put the pointer in ce + ce = &(((struct cluster_entry_t *)buf)[cid]); + + return ce->val; +} + +static int lookup_cache(struct vnode *dir_node, struct vnode **target, const char *component_name){ + struct fat_internal *data, *entry; + struct fat_dir_t *dir; + int found = 0; + + data = dir_node->internal; + dir = data->dir; + list_for_each_entry(entry, &dir->list, list){ + // TODO: modify it to achieve case sensity name checking + if(!strcasecmp(component_name, entry->name)){ + found = 1; + break; + } + } + if(!found) + return -1; + + *target = entry->node; + return 0; +} + +static struct dir_t *lookup_fat32(struct vnode *dir_node, const char *component_name, uint8 *buf){ + struct dir_t *dir; + struct fat_internal *data; + struct fat_info_t *fat; + struct filename_t name; + uint32 cid; + int found, dirend, lfn; + + data = dir_node->internal; + fat = data->fat; + cid = data->cid; + + found = 0; + dirend = 0; + memset(&name, 0, sizeof(struct filename_t)); + + while(1){ + int lba; + lba = fat->cluster_lba + (cid-2) * fat->bs.sector_per_cluster; + + sd_readblock(lba, buf); + for(int i = 0; i < DIR_PER_BLOCK; ++i){ + uint8 len; + dir = (struct dir_t *)(&buf[sizeof(struct dir_t) * i]); + if(dir->name[0] == 0){ + dirend = 1; + break; + } + if((dir->attr & 0xf) == 0xf){ + struct long_dir_t *ldir; + int n; + lfn = 1; + ldir = (struct long_dir_t *)dir; + n = ldir->order -1; + for(int i = 0 ; ldir->name1[i] != 0xff && i < 10 ; i+=2){ + name.part[n].name[i/2] = ldir->name1[i]; + } + for(int i = 0 ; ldir->name2[i] != 0xff && i < 12 ; i+=2){ + name.part[n].name[5 + i/2] = ldir->name2[i]; + } + for(int i = 0 ; ldir->name3[i] != 0xff && i < 4 ; i+=2){ + name.part[n].name[11 + i/2] = ldir->name3[i]; + } + continue; + } + + if(lfn == 1){ + if(!strcasecmp(component_name, (void *)name.fullname)){ + found = 1; + break; + } + lfn = 0; + memset(&name, 0, sizeof(struct filename_t)); + continue; + } + + lfn = 0; + len = 8; + while(len){ + if(dir->name[len-1] != 0x20) + break; + len -= 1; + } + + memncpy((void *)name.fullname, (void *)dir->name, len); + name.fullname[len] = 0; + + len = 3; + while(len){ + if(dir->name[8+len-1] != 0x20) + break; + len -= 1; + } + + if(len >= 0){ + strcat((void *)name.fullname,"."); + strncat((void *)name.fullname, (void *)&dir->name[8], len); + } + + if(!strcasecmp(component_name, (void *)name.fullname)){ + found = 1; + break; + } + + memset(&name, 0, sizeof(struct filename_t)); + } + if(found || dirend) + break; + + cid = get_next_cluster(fat->fat_lba, cid); + if(cid >= 0x0ffffff8) + break; + } + if(!found) + return NULL; + return dir; +} + +static int lookup__fat32(struct vnode *dir_node,struct vnode **target, const char *component_name){ + struct vnode *node; + struct dir_t *dir; + // struct fat_internal *data, *nodedata; + uint32 type, cid; + uint8 buf[BLOCK_SIZE]; + + dir = lookup_fat32(dir_node, component_name, buf); + if(!dir) + return -1; + if(!(dir->attr & ATTR_FILE_DIR_MASK)) + return -1; + cid = (dir->ch << 16) | dir->cl; + if(dir->attr & ATTR_ARCHIVE) + type = FAT_FILE; + else + type = FAT_DIR; + node = create_vnode(dir_node, component_name, type, cid, dir->size); + // data = dir_node->internal; + // nodedata = node->internal; + + // list_add(&nodedata->list, &data->dir->list); + *target = node; + return 0; +} + +static int readfile_cache(struct fat_internal *data, uint64 bckoff, uint8 *buf, uint64 bufoff, uint32 oid, uint32 size, struct list_head **iter, uint32 *cid){ + struct list_head *tmpiter; + struct fat_file_t *file; + struct fat_file_block_t *entry; + int rsize; + + tmpiter = *iter; + file = data->file; + iter_for_each_entry(entry, tmpiter, &file->list, list){ + if(oid <= entry->oid) + break; + } + + if(&entry->list == &file->list){ + *iter = &entry->list; + return -1; + } + + if(oid < entry->oid){ + *iter = &entry->list; + return -2; + } + rsize = size > BLOCK_SIZE - bckoff ? BLOCK_SIZE - bckoff : size; + memncpy((void *)&buf[bufoff], (void *)&entry->buf[bckoff], rsize); + *cid = entry->cid; + *iter = &entry->list; + return rsize; +} + +static int readfile_fat32(struct fat_internal *data, uint64 bckoff, uint8 *buf, uint32 bufoff, uint32 oid, uint32 size, struct list_head *list, uint32 cid){ + struct fat_info_t *info; + struct fat_file_block_t *block; + int lba; + int rsize; + info = data->fat; + block = kmalloc(sizeof(struct fat_file_block_t)); + rsize = size > BLOCK_SIZE - bckoff ? BLOCK_SIZE - bckoff : size; + lba = info->cluster_lba + (cid -2) * info->bs.sector_per_cluster; + sd_readblock(lba, block->buf); + memncpy((void *)&buf[bufoff], (void *)&block->buf[bckoff], rsize); + block->oid = oid; + block->cid = cid; + list_add_tail(&block->list, list); + return rsize; +} + +static int readfile(void *buf, struct fat_internal *data, uint64 fileoff, uint64 len){ + struct list_head *iter; + uint32 oid, cid; + uint64 bufoff, result; + int cache_end; + + iter = &data->file->list; + oid = fileoff / BLOCK_SIZE; + cid = 0; + bufoff = 0; + result = 0; + cache_end = list_empty(iter); + + if(!cache_end) + iter = iter->next; + + while(len){ + uint64 bckoff; + int cache_hit, ret; + bckoff = (fileoff + result) % BLOCK_SIZE; + cache_hit = 0; + + if(!cache_end){ + ret = readfile_cache(data, bckoff, buf, bufoff, oid, len, &iter, &cid); + + if(ret == -1) + cache_end = 1; + else if(ret > 0) + cache_hit = 1; + } + + if(!cache_hit){ + if(!cid) + cid = data->cid; + else + cid = get_next_cluster(data->fat->fat_lba, cid); + if(cid > 0x0ffffff8) + break; + ret = readfile_fat32(data, bckoff, buf, bufoff, oid, len, iter, cid); + } + if(ret < 0) + break; + + bufoff += ret; + result += ret; + oid += 1; + len -= ret; + } + return result; +} + +static int writefile_seek_cache(struct fat_internal *data, uint32 foid, struct fat_file_block_t **block){ + struct fat_file_block_t *entry; + struct list_head *head; + head = &data->file->list; + + if(list_empty(head)) + return -1; + list_for_each_entry(entry, head, list){ + *block = entry; + if(foid == entry->oid) + return 0; + } + return -1; +} + +static int writefile_seek_fat32(struct fat_internal *data, uint32 foid, uint32 fcid, struct fat_file_block_t **block){ + struct fat_info_t *info; + uint32 curoid, curcid; + info = data->fat; + if(*block){ + curoid = (*block)->oid; + curcid = (*block)->cid; + if(curoid == foid) + return 0; + curoid ++; + curcid = get_next_cluster(info->fat_lba, curcid); + } + else{ + curoid = 0; + curcid = fcid; + } + while(1){ + struct fat_file_block_t *newblock; + newblock = kmalloc(sizeof(struct fat_file_block_t)); + newblock->oid = curoid; + newblock->cid = curcid; + newblock->read = 0; + + list_add_tail(&newblock->list, &data->file->list); + *block = newblock; + if(curoid == foid) + return 0; + curoid++; + curcid = get_next_cluster(info->fat_lba, curcid); + } +} + +static int writefile_cache(struct fat_internal *data, uint64 bckoff, const uint8 *buf, uint64 bufoff, uint32 size, struct fat_file_block_t *block){ + int wsize; + if(!block->read){ + struct fat_info_t *info; + int lba; + info = data->fat; + lba = info->cluster_lba + (block->cid -2 ) * info->bs.sector_per_cluster; + sd_readblock(lba, block->buf); + block->read = 1; + } + wsize = size > BLOCK_SIZE - bckoff ? BLOCK_SIZE - bckoff : size; + memncpy((void *)&block->buf[bckoff], (void *)&buf[bufoff], wsize); + return wsize; +} + +static int writefile_fat32(struct fat_internal *data, uint64 bckoff, const uint8 *buf, uint32 bufoff, uint32 size, uint32 oid, uint32 cid){ + struct list_head *head; + struct fat_info_t *info; + struct fat_file_block_t *block; + int lba; + int wsize; + head = &data->file->list; + info = data->fat; + block = kmalloc(sizeof(struct fat_file_block_t)); + + wsize = size > BLOCK_SIZE - bckoff ? BLOCK_SIZE - bckoff : size; + if(cid >= 0x0ffffff8) + memset(block->buf, 0 , BLOCK_SIZE); + else{ + lba = info->cluster_lba + (cid -2) * info->bs.sector_per_cluster; + sd_readblock(lba, block->buf); + } + memncpy((void *)&block->buf[bufoff], (void *)&buf[bufoff], wsize); + block->oid = oid; + block->cid = cid; + block->read = 1; + + list_add_tail(&block->list, head); + + return wsize; +} + +static int writefile(const void *buf, struct fat_internal *data, uint64 fileoff, uint64 len){ + struct fat_file_block_t *block; + struct list_head *head; + uint32 foid, coid, cid; + uint64 bufoff, result; + int ret; + + block = NULL; + head = &data->file->list; + foid = fileoff / BLOCK_SIZE; + coid = 0; + cid = data->cid; + bufoff = 0; + result = 0; + + ret = writefile_seek_cache(data, foid, &block); + if(ret < 0) + ret = writefile_seek_fat32(data, foid, cid, &block); + if(ret < 0) + return 0; + + while(len){ + uint64 bckoff; + bckoff = (fileoff + result)%BLOCK_SIZE; + if(&block->list != head){ + ret = writefile_cache(data, bckoff, buf, bufoff, len, block); + cid = block->cid; + block = list_first_entry(&block->list, struct fat_file_block_t, list); + } + else{ + cid = get_next_cluster(data->fat->fat_lba, cid); + ret = writefile_fat32(data, bckoff, buf, bufoff, len, coid, cid); + } + if(ret < 0) + break; + bufoff += ret; + result += ret; + coid += 1; + len -= ret; + } + return result; +} + +int fat32fs_mount(struct filesystem *fs, struct mount *mount){ + struct partition_t *partition; + struct fat_info_t *fat; + struct fat_dir_t *dir; + struct fat_internal *data; + struct vnode *oldnode, *node; + const char *name; + uint32 lba; + uint8 buf[BLOCK_SIZE]; + + sd_readblock(0, buf); + partition = (struct partition_t *)&buf[0x1be]; + if(buf[510] != 0x55 || buf[511] != 0xaa) + return -1; + if(partition[0].type != 0xb && partition[0].type != 0xc) + return -1; + lba = partition[0].lba; + + sd_readblock(partition[0].lba, buf); + + node = kmalloc(sizeof(struct vnode)); + data = kmalloc(sizeof(struct fat_internal)); + fat = kmalloc(sizeof(struct fat_info_t)); + dir = kmalloc(sizeof(struct fat_dir_t)); + + memncpy((void *)&fat->bs,(void *)buf,sizeof(fat->bs)); + fat->fat_lba = lba + fat->bs.reserved_sector_cnt; + fat->cluster_lba = fat->fat_lba + fat->bs.fat_cnt * fat->bs.sector_per_fat32; + INIT_LIST_HEAD(&dir->list); + + oldnode = mount->root; + oldnode->v_ops->getname(oldnode, &name); + + node->mount = oldnode->mount; + node->v_ops = oldnode->v_ops; + node->f_ops = oldnode->f_ops; + node->parent = oldnode->parent; + node->internal = oldnode->internal; + + data->name = name; + data->node = node; + data->fat = fat; + data->cid = 2; + data->type = FAT_DIR; + data->dir = dir; + + oldnode->mount = mount; + oldnode->v_ops = &fat32fs_v_ops; + oldnode->f_ops = &fat32fs_f_ops; + oldnode->internal = data; + + return 0; +} +int fat32fs_lookup(struct vnode *dir_node, struct vnode **target, const char *component_name){ + struct fat_internal *data; + int ret; + + data = dir_node->internal; + if(data->type != FAT_DIR) + return -1; + ret = lookup_cache(dir_node, target, component_name); + if(ret >= 0) + return ret; + return lookup__fat32(dir_node, target, component_name); +} +int fat32fs_create(struct vnode *dir_node, struct vnode **target, const char *component_name){ + struct vnode *node; + struct fat_internal *internal; + int ret; + + internal = dir_node->internal; + if(internal->type != FAT_DIR) + return -1; + ret = fat32fs_lookup(dir_node, target, component_name); + + if(!ret) + return -1; + + node = create_vnode(dir_node, component_name, FAT_FILE, -1 , 0); + *target = node; + return 0; +} +int fat32fs_mkdir(struct vnode *dir_node, struct vnode **target, const char *component_name){ + struct vnode *node; + struct fat_internal *internal; + int ret; + internal = dir_node->internal; + if(internal->type != FAT_DIR) + return -1; + ret = fat32fs_lookup(dir_node, target, component_name); + if(!ret) + return -1; + + node = create_vnode(dir_node, component_name, FAT_DIR, -1, 0); + *target = node; + return 0; +} +int fat32fs_isdir(struct vnode *dir_node){ + struct fat_internal *internal; + internal = dir_node->internal; + if(internal->type != FAT_DIR) + return 0; + return 1; +} +int fat32fs_getname(struct vnode *dir_node, const char **name){ + struct fat_internal *internal; + internal = dir_node->internal; + *name = internal->name; + return 0; +} +int fat32fs_getsize(struct vnode *dir_node){ + struct fat_internal *internal; + internal = dir_node->internal; + if(internal->type == FAT_DIR) + return 0; + return internal->file->size; +} +int fat32fs_write(struct file *file, const void *buf, size_t len){ + struct fat_internal *data; + int filesize, ret; + if(fat32fs_isdir(file->vnode)) + return -1; + if(!len) + return len; + filesize = fat32fs_getsize(file->vnode); + data = file->vnode->internal; + ret = writefile(buf, data, file->f_pos, len); + if(ret <= 0) + return ret; + file->f_pos += ret; + if(file->f_pos > filesize) + data->file->size = file->f_pos; + return ret; +} +int fat32fs_read(struct file *file, void *buf, size_t len){ + struct fat_internal *data; + int filesize; + int ret; + + if(fat32fs_isdir(file->vnode)) + return -1; + + filesize = fat32fs_getsize(file->vnode); + data = file->vnode->internal; + if(file->f_pos + len > filesize) + len = filesize - file->f_pos; + + if(!len) + return len; + ret = readfile(buf, data, file->f_pos, len); + if(ret <= 0) + return ret; + file->f_pos += ret; + return ret; +} +int fat32fs_open(struct vnode *file_node, struct file *target){ + target->vnode = file_node; + target->f_pos = 0; + target->f_ops = file_node->f_ops; + + return 0; +} +int fat32fs_close(struct file *file){ + file->vnode = NULL; + file->f_pos = 0; + file->f_ops = NULL; + + return 0; +} +long fat32fs_lseek64(struct file *file, long offset, int whence){ + int filesize; + int base; + + if (!fat32fs_isdir(file->vnode)) { + return -1; + } + + filesize = fat32fs_getsize(file->vnode); + + if (filesize < 0) { + return -1; + } + + switch (whence) { + case SEEK_SET: + base = 0; + break; + case SEEK_CUR: + base = file->f_pos; + break; + case SEEK_END: + base = filesize; + break; + default: + return -1; + } + + if (base + offset > filesize) { + return -1; + } + + file->f_pos = base + offset; + + return 0; +} +int fat32fs_ioctl(struct file *file, uint64 request, va_list args){ + return -1; +} + +struct filesystem *fat32fs_init(void){ + return &fat32fs; +} \ No newline at end of file diff --git a/src/lib/fsinit.c b/src/lib/fsinit.c index 8cdc7b681..7e196be6c 100644 --- a/src/lib/fsinit.c +++ b/src/lib/fsinit.c @@ -4,19 +4,25 @@ #include #include #include +#include +#include void fs_early_init(void){ - struct filesystem *tmpfs, *cpiofs, *uartfs, *fbfs; + struct filesystem *tmpfs, *cpiofs, *uartfs, *fbfs, *fat32fs; vfs_init(); + sd_init(); + tmpfs = tmpfs_init(); cpiofs = cpiofs_init(); uartfs = uartfs_init(); fbfs = framebufferfs_init(); + fat32fs = fat32fs_init(); register_filesystem(tmpfs); register_filesystem(cpiofs); register_filesystem(uartfs); register_filesystem(fbfs); + register_filesystem(fat32fs); vfs_init_rootmount(tmpfs); @@ -30,4 +36,7 @@ void fs_early_init(void){ vfs_mkdir("/dev/framebuffer"); vfs_mount("/dev/framebuffer", "framebufferfs"); + + vfs_mkdir("/boot"); + vfs_mount("/boot", "fat32fs"); } \ No newline at end of file diff --git a/src/lib/mm.S b/src/lib/mm.S index aba5d83f4..1e8e035f6 100644 --- a/src/lib/mm.S +++ b/src/lib/mm.S @@ -12,4 +12,11 @@ memncpy: strb w3, [x0], #1 subs x2, x2, #1 b.gt memncpy + ret + +.globl memset +memset: + strb w1, [x0], #1 + subs x2, x2, #1 + b.gt memset ret \ No newline at end of file diff --git a/src/lib/sdhost.c b/src/lib/sdhost.c new file mode 100644 index 000000000..7ee4bb116 --- /dev/null +++ b/src/lib/sdhost.c @@ -0,0 +1,164 @@ +#include +#include +#include +static int is_hcs; // high capcacity(SDHC) + +static void pin_setup() { + put32(PA2VA(GPFSEL4), 0x24000000); + put32(PA2VA(GPFSEL5), 0x924); + put32(PA2VA(GPPUD), 0); + delay(15000); + put32(PA2VA(GPPUDCLK1), 0xffffffff); + delay(15000); + put32(PA2VA(GPPUDCLK1), 0); +} + +static void sdhost_setup() { + unsigned int tmp; + put32(SDHOST_PWR, 0); + put32(SDHOST_CMD, 0); + put32(SDHOST_ARG, 0); + put32(SDHOST_TOUT, SDHOST_TOUT_DEFAULT); + put32(SDHOST_CDIV, 0); + put32(SDHOST_HSTS, SDHOST_HSTS_MASK); + put32(SDHOST_CFG, 0); + put32(SDHOST_CNT, 0); + put32(SDHOST_SIZE, 0); + tmp = get32(SDHOST_DBG); + tmp &= ~SDHOST_DBG_MASK; + tmp |= SDHOST_DBG_FIFO; + put32(SDHOST_DBG, tmp); + delay(250000); + put32(SDHOST_PWR, 1); + delay(250000); + put32(SDHOST_CFG, SDHOST_CFG_SLOW | SDHOST_CFG_INTBUS | SDHOST_CFG_DATA_EN); + put32(SDHOST_CDIV, SDHOST_CDIV_DEFAULT); +} + +static int wait_sd(void){ + int cnt = 1000000; + unsigned int cmd; + do{ + if(cnt == 0) + return -1; + cmd = get32(SDHOST_CMD); + --cnt; + }while(cmd & SDHOST_NEW_CMD); + return 0; +} + +static int sd_cmd(unsigned cmd, unsigned int arg){ + put32(SDHOST_ARG, arg); + put32(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); + tmp = get32(SDHOST_RESP0); + 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); + tmp = get32(SDHOST_RESP0); + 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); + tmp = get32(SDHOST_RESP0); + sd_cmd(SELECT_CARD, tmp); + sd_cmd(SET_BLOCKLEN, BLOCK_SIZE); + return 0; +} + +static int wait_fifo() { + int cnt = 1000000; + unsigned int hsts; + do { + if (cnt == 0) { + return -1; + } + hsts = get32(SDHOST_HSTS); + --cnt; + } while ((hsts & SDHOST_HSTS_DATA) == 0); + return 0; +} + +static void set_block(int size, int cnt) { + put32(SDHOST_SIZE, size); + put32(SDHOST_CNT, cnt); +} + +static void wait_finish() { + unsigned int dbg; + do { + dbg = get32(SDHOST_DBG); + } while ((dbg & SDHOST_DBG_FSM_MASK) != SDHOST_HSTS_DATA); +} + +void sd_init(void){ + pin_setup(); + sdhost_setup(); + sdcard_setup(); +} + +void sd_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(BLOCK_SIZE, 1); + sd_cmd(READ_SINGLE_BLOCK | SDHOST_READ, block_idx); + for (int i = 0; i < 128; ++i) { + wait_fifo(); + buf_u[i] = get32(SDHOST_DATA); + } + unsigned int hsts = get32(SDHOST_HSTS); + if (hsts & SDHOST_HSTS_ERR_MASK) { + put32(SDHOST_HSTS, SDHOST_HSTS_ERR_MASK); + sd_cmd(STOP_TRANSMISSION | SDHOST_BUSY, 0); + } else { + succ = 1; + } + } while(!succ); + wait_finish(); +} + +void sd_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(BLOCK_SIZE, 1); + sd_cmd(WRITE_SINGLE_BLOCK | SDHOST_WRITE, block_idx); + for (int i = 0; i < 128; ++i) { + wait_fifo(); + put32(SDHOST_DATA, buf_u[i]); + } + unsigned int hsts = get32(SDHOST_HSTS); + if (hsts & SDHOST_HSTS_ERR_MASK) { + put32(SDHOST_HSTS, SDHOST_HSTS_ERR_MASK); + sd_cmd(STOP_TRANSMISSION | SDHOST_BUSY, 0); + } else { + succ = 1; + } + } while(!succ); + wait_finish(); +} \ No newline at end of file diff --git a/src/lib/string.c b/src/lib/string.c index 2cca8d293..6199ff50d 100644 --- a/src/lib/string.c +++ b/src/lib/string.c @@ -9,6 +9,34 @@ int strcmp(const char *X,const char *Y){ return *(const unsigned char*)X - *(const unsigned char*)Y; } +int strcasecmp(const char *X, const char *Y) +{ + char c1, c2; + + while (1) { + c1 = *X++; + c2 = *Y++; + + if (!c1 || !c2) { + break; + } + + if ('A' <= c1 && c1 <= 'Z') { + c1 |= 0x20; + } + + if ('A' <= c2 && c2 <= 'Z') { + c2 |= 0x20; + } + + if (c1 != c2) { + break; + } + } + + return c1 - c2; +} + int strncmp(const char *X, const char *Y, int n){ while(n && *X && (*X == *Y)){ X++; @@ -42,6 +70,36 @@ int strcpy(char *dst, const char *src) return ret; } +char *strcat(char *dest, const char *src){ + char *t; + t = dest; + while(*t){ + t++; + } + while(*src){ + *t = *src; + t++; + src++; + } + *t = '\0'; + return dest; +} + +char *strncat(char *dest, const char *src, int n){ + char *t; + t = dest; + while(*t) + t++; + while(n > 0 && *src){ + *t = *src; + t++; + src++; + n--; + } + *t = '\0'; + return dest; +} + int atoi(const char *str){ int i = 0 , j = 0; while(*str){ diff --git a/src/lib/syscall.c b/src/lib/syscall.c index 716beb8f0..fd5c21283 100644 --- a/src/lib/syscall.c +++ b/src/lib/syscall.c @@ -59,8 +59,8 @@ void syscall_handler(trapframe *regs) syscall_num = regs->x8; - if(syscall_num != 2) - uart_sync_printf("syscall number:%d\n", syscall_num); + // if(syscall_num > 2) + // uart_sync_printf("syscall number:%d\n", syscall_num); if (syscall_num >= ARRAY_SIZE(syscall_table)) { // Invalid syscall From b92af081d6e92ce9204aecb76527d41bb66298ac Mon Sep 17 00:00:00 2001 From: Shin-Yan Date: Wed, 28 Jun 2023 13:46:42 +0800 Subject: [PATCH 10/10] complete sync system call --- inc/cpiofs.h | 1 + inc/fat32fs.h | 10 + inc/framebufferfs.h | 1 + inc/tmpfs.h | 1 + inc/uartfs.h | 1 + inc/vfs.h | 3 + src/lib/cpiofs.c | 7 +- src/lib/fat32fs.c | 698 ++++++++++++++++++++++++++++++++++++---- src/lib/framebufferfs.c | 7 +- src/lib/syscall.c | 5 +- src/lib/tmpfs.c | 7 +- src/lib/uartfs.c | 7 +- src/lib/vfs.c | 84 ++++- 13 files changed, 744 insertions(+), 88 deletions(-) diff --git a/inc/cpiofs.h b/inc/cpiofs.h index 67340c73e..bd2a75ee3 100644 --- a/inc/cpiofs.h +++ b/inc/cpiofs.h @@ -34,6 +34,7 @@ struct cpiofs_internal { }; int cpiofs_mount(struct filesystem *fs, struct mount *mount); +int cpiofs_sync(struct filesystem *fs); int cpiofs_lookup(struct vnode *dir_node, struct vnode **target, const char *component_name); int cpiofs_create(struct vnode *dir_node, struct vnode **target, diff --git a/inc/fat32fs.h b/inc/fat32fs.h index 1f487a39e..d786977a6 100644 --- a/inc/fat32fs.h +++ b/inc/fat32fs.h @@ -9,11 +9,13 @@ #define DIR_PER_BLOCK (BLOCK_SIZE / sizeof(struct dir_t)) #define FAT_DIR 1 #define FAT_FILE 2 +#define INVALID_CID 0x0ffffff8 #define ATTR_READ_ONLY 0x01 #define ATTR_HIDDEN 0x02 #define ATTR_SYSTEM 0x04 #define ATTR_VOLUME_ID 0x08 +#define ATTR_LFN 0x0f #define ATTR_DIRECTORY 0x10 #define ATTR_ARCHIVE 0x20 #define ATTR_FILE_DIR_MASK (ATTR_DIRECTORY | ATTR_ARCHIVE) @@ -114,6 +116,7 @@ struct fat_file_block_t{ uint32 cid; /* Already read the data into buf*/ uint32 read; + uint32 dirty; uint8 buf[BLOCK_SIZE]; }; @@ -147,6 +150,12 @@ struct fat_internal{ }; }; +struct fat_mount_t { + /* Link fat_mount_t */ + struct list_head list; + struct mount *mount; +}; + int fat32fs_mount(struct filesystem *fs, struct mount *mount); int fat32fs_lookup(struct vnode *dir_node, struct vnode **target, const char *component_name); @@ -162,6 +171,7 @@ int fat32fs_open(struct vnode *file_node, struct file *target); int fat32fs_close(struct file *file); long fat32fs_lseek64(struct file *file, long offset, int whence); int fat32fs_ioctl(struct file *file, uint64 request, va_list args); +int fat32fs_sync(struct filesystem *fs); struct filesystem *fat32fs_init(void); #endif \ No newline at end of file diff --git a/inc/framebufferfs.h b/inc/framebufferfs.h index cb5227fb8..e50ba0e4c 100644 --- a/inc/framebufferfs.h +++ b/inc/framebufferfs.h @@ -33,6 +33,7 @@ int fbfs_open(struct vnode *file_node, struct file *target); int fbfs_close(struct file *file); long fbfs_lseek64(struct file *file, long offset, int whence); int fbfs_ioctl(struct file *file, uint64 request, va_list args); +int fbfs_sync(struct filesystem *fs); struct filesystem *framebufferfs_init(void); diff --git a/inc/tmpfs.h b/inc/tmpfs.h index 2dba12fc3..ca090f18c 100644 --- a/inc/tmpfs.h +++ b/inc/tmpfs.h @@ -45,6 +45,7 @@ int tmpfs_open(struct vnode *file_node, struct file *target); int tmpfs_close(struct file *file); long tmpfs_lseek64(struct file *file, long offset, int whence); int tmpfs_ioctl(struct file *file, uint64 request, va_list args); +int tmpfs_sync(struct filesystem *fs); struct filesystem *tmpfs_init(void); diff --git a/inc/uartfs.h b/inc/uartfs.h index f1f538623..c881b5c77 100644 --- a/inc/uartfs.h +++ b/inc/uartfs.h @@ -21,6 +21,7 @@ int uartfs_open(struct vnode *file_node, struct file *target); int uartfs_close(struct file *file); long uartfs_lseek64(struct file *file, long offset, int whence); int uartfs_ioctl(struct file *file, uint64 request, va_list args); +int uartfs_sync(struct filesystem *fs); struct filesystem *uartfs_init(void); diff --git a/inc/vfs.h b/inc/vfs.h index f99cdca5f..9c3f30f1c 100644 --- a/inc/vfs.h +++ b/inc/vfs.h @@ -31,6 +31,7 @@ struct filesystem { struct list_head fs_list; int (*mount)(struct filesystem *fs, struct mount *mount); int (*alloc_vnode)(struct filesystem *fs, struct vnode **target); + int (*sync)(struct filesystem *fs); }; struct vnode_operations { @@ -74,6 +75,7 @@ int vfs_read(struct file *file, void *buf, size_t len); int vfs_mkdir(const char *pathname); int vfs_mount(const char *mountpath, const char *filesystem); int vfs_lookup(const char *pathname, struct vnode **target); +int vfs_sync(struct filesystem *fs); long vfs_lseek64(struct file *file, long offset, int whence); int vfs_ioctl(struct file *file, uint64 request, va_list args); @@ -86,5 +88,6 @@ void syscall_mount(trapframe *frame, const char *src, const char *target, const void syscall_chdir(trapframe *frame, const char *path); void syscall_lseek64(trapframe *frame, int fd, int64 offset, int whence); void syscall_ioctl(trapframe *frame, int fd, uint64 request, ...); +void syscall_sync(trapframe *frame); #endif \ No newline at end of file diff --git a/src/lib/cpiofs.c b/src/lib/cpiofs.c index 6bfedb5fe..0399e5941 100644 --- a/src/lib/cpiofs.c +++ b/src/lib/cpiofs.c @@ -12,7 +12,8 @@ static int cpio_mounted; static struct filesystem cpiofs = { .name = "cpiofs", - .mount = cpiofs_mount + .mount = cpiofs_mount, + .sync = cpiofs_sync }; static struct vnode_operations cpiofs_v_ops = { @@ -347,4 +348,8 @@ long cpiofs_lseek64(struct file *file, long offset, int whence){ int cpiofs_ioctl(struct file *file, uint64 request, va_list args){ return -1; +} + +int cpiofs_sync(struct filesystem *fs){ + return 0; } \ No newline at end of file diff --git a/src/lib/fat32fs.c b/src/lib/fat32fs.c index b341a164a..ed5d9dc42 100644 --- a/src/lib/fat32fs.c +++ b/src/lib/fat32fs.c @@ -3,10 +3,15 @@ #include #include #include +#include +#include + +static struct list_head mounts; static struct filesystem fat32fs = { .name = "fat32fs", - .mount = fat32fs_mount + .mount = fat32fs_mount, + .sync = fat32fs_sync }; static struct vnode_operations fat32fs_v_ops = { @@ -27,6 +32,73 @@ static struct file_operations fat32fs_f_ops = { .ioctl = fat32fs_ioctl }; +static int invalid_cid(uint32 cid){ + if (cid >= INVALID_CID) { + return 1; + } + + return 0; +} + +static uint32 alloc_cluster(struct fat_info_t *fat, uint32 prev_cid){ + struct cluster_entry_t *ce; + uint32 fat_lba; + uint32 cid; + int found; + uint8 buf[BLOCK_SIZE]; + + fat_lba = fat->fat_lba; + cid = 0; + + while (fat_lba < fat->cluster_lba) { + found = 0; + + // TODO: Cache FAT + sd_readblock(fat_lba, buf); + + for (int i = 0; i < CLUSTER_ENTRY_PER_BLOCK; ++i) { + ce = &(((struct cluster_entry_t *)buf)[i]); + + if (!ce->val) { + found = 1; + break; + } + + ++cid; + } + + if (found) { + break; + } + + fat_lba += 1; + } + + if (found && prev_cid) { + uint32 target_lba; + uint32 target_idx; + + target_lba = fat_lba + prev_cid / CLUSTER_ENTRY_PER_BLOCK; + target_idx = prev_cid % CLUSTER_ENTRY_PER_BLOCK; + + // TODO: Cache FAT + sd_readblock(target_lba, buf); + + ce = &(((struct cluster_entry_t *)buf)[target_idx]); + + ce->val = cid; + + sd_writeblock(target_lba, buf); + } + + if (!found) { + panic("fat32 alloc_cluster: No space!"); + return -1; + } + + return cid; +} + static struct vnode *create_vnode(struct vnode *parent, const char *name, uint32 type, uint32 cid, uint32 size){ struct vnode *node; struct fat_internal *info, *data; @@ -71,22 +143,23 @@ static struct vnode *create_vnode(struct vnode *parent, const char *name, uint32 return node; } -static int get_next_cluster(uint32 fat_lba, uint32 cluster_id){ +static uint32 get_next_cluster(uint32 fat_lba, uint32 cluster_id) +{ struct cluster_entry_t *ce; - uint32 cid; + uint32 idx; uint8 buf[BLOCK_SIZE]; - if(cluster_id >= 0x0ffffff8) + if (invalid_cid(cluster_id)) { return cluster_id; + } fat_lba += cluster_id / CLUSTER_ENTRY_PER_BLOCK; - cid = cluster_id % CLUSTER_ENTRY_PER_BLOCK; - + idx = cluster_id % CLUSTER_ENTRY_PER_BLOCK; + // TODO: Cache FAT sd_readblock(fat_lba, buf); - // get cluster entry with cid and put the pointer in ce - ce = &(((struct cluster_entry_t *)buf)[cid]); + ce = &(((struct cluster_entry_t *)buf)[idx]); return ce->val; } @@ -99,7 +172,6 @@ static int lookup_cache(struct vnode *dir_node, struct vnode **target, const cha data = dir_node->internal; dir = data->dir; list_for_each_entry(entry, &dir->list, list){ - // TODO: modify it to achieve case sensity name checking if(!strcasecmp(component_name, entry->name)){ found = 1; break; @@ -112,7 +184,7 @@ static int lookup_cache(struct vnode *dir_node, struct vnode **target, const cha return 0; } -static struct dir_t *lookup_fat32(struct vnode *dir_node, const char *component_name, uint8 *buf){ +static struct dir_t *lookup_fat32(struct vnode *dir_node, const char *component_name, uint8 *buf, int *buflba){ struct dir_t *dir; struct fat_internal *data; struct fat_info_t *fat; @@ -133,6 +205,9 @@ static struct dir_t *lookup_fat32(struct vnode *dir_node, const char *component_ lba = fat->cluster_lba + (cid-2) * fat->bs.sector_per_cluster; sd_readblock(lba, buf); + if (buflba) { + *buflba = lba; + } for(int i = 0; i < DIR_PER_BLOCK; ++i){ uint8 len; dir = (struct dir_t *)(&buf[sizeof(struct dir_t) * i]); @@ -140,12 +215,12 @@ static struct dir_t *lookup_fat32(struct vnode *dir_node, const char *component_ dirend = 1; break; } - if((dir->attr & 0xf) == 0xf){ + if((dir->attr & ATTR_LFN) == ATTR_LFN){ struct long_dir_t *ldir; int n; lfn = 1; ldir = (struct long_dir_t *)dir; - n = ldir->order -1; + n = (dir->name[0] & 0x3f) - 1; for(int i = 0 ; ldir->name1[i] != 0xff && i < 10 ; i+=2){ name.part[n].name[i/2] = ldir->name1[i]; } @@ -202,7 +277,7 @@ static struct dir_t *lookup_fat32(struct vnode *dir_node, const char *component_ break; cid = get_next_cluster(fat->fat_lba, cid); - if(cid >= 0x0ffffff8) + if(invalid_cid(cid)) break; } if(!found) @@ -217,7 +292,7 @@ static int lookup__fat32(struct vnode *dir_node,struct vnode **target, const cha uint32 type, cid; uint8 buf[BLOCK_SIZE]; - dir = lookup_fat32(dir_node, component_name, buf); + dir = lookup_fat32(dir_node, component_name, buf, NULL); if(!dir) return -1; if(!(dir->attr & ATTR_FILE_DIR_MASK)) @@ -228,108 +303,205 @@ static int lookup__fat32(struct vnode *dir_node,struct vnode **target, const cha else type = FAT_DIR; node = create_vnode(dir_node, component_name, type, cid, dir->size); - // data = dir_node->internal; - // nodedata = node->internal; - - // list_add(&nodedata->list, &data->dir->list); + *target = node; return 0; } -static int readfile_cache(struct fat_internal *data, uint64 bckoff, uint8 *buf, uint64 bufoff, uint32 oid, uint32 size, struct list_head **iter, uint32 *cid){ - struct list_head *tmpiter; - struct fat_file_t *file; +static int readfile_seek_cache(struct fat_internal *data, uint32 foid, + struct fat_file_block_t **block){ struct fat_file_block_t *entry; - int rsize; + struct list_head *head; - tmpiter = *iter; - file = data->file; - iter_for_each_entry(entry, tmpiter, &file->list, list){ - if(oid <= entry->oid) - break; - } + head = &data->file->list; - if(&entry->list == &file->list){ - *iter = &entry->list; + if (list_empty(head)) { return -1; } - if(oid < entry->oid){ - *iter = &entry->list; - return -2; + list_for_each_entry(entry, head, list) { + *block = entry; + + if (foid == entry->oid) { + return 0; + } + } + + return -1; +} + +static int readfile_seek_fat32(struct fat_internal *data, + uint32 foid, uint32 fcid, + struct fat_file_block_t **block){ + struct fat_info_t *info; + uint32 curoid, curcid; + + info = data->fat; + + if (*block) { + curoid = (*block)->oid; + curcid = (*block)->cid; + + if (curoid == foid) { + return 0; + } + + curoid++; + curcid = get_next_cluster(info->fat_lba, curcid); + + if (invalid_cid(curcid)) { + return -1; + } + } else { + curoid = 0; + curcid = fcid; + } + + while (1) { + struct fat_file_block_t *newblock; + + newblock = kmalloc(sizeof(struct fat_file_block_t)); + + newblock->oid = curoid; + newblock->cid = curcid; + newblock->read = 0; + newblock->dirty = 0; + + list_add_tail(&newblock->list, &data->file->list); + + *block = newblock; + + if (curoid == foid) { + return 0; + } + + curoid++; + curcid = get_next_cluster(info->fat_lba, curcid); + + if (invalid_cid(curcid)) { + return -1; + } } +} + +static int readfile_cache(struct fat_internal *data, uint64 bckoff, uint8 *buf, uint64 bufoff, uint32 size, struct fat_file_block_t *block){ + int rsize; + + if (!block->read) { + // read the data from sdcard + struct fat_info_t *info; + int lba; + + info = data->fat; + lba = info->cluster_lba + + (block->cid - 2) * info->bs.sector_per_cluster; + + sd_readblock(lba, block->buf); + + block->read = 1; + } + rsize = size > BLOCK_SIZE - bckoff ? BLOCK_SIZE - bckoff : size; - memncpy((void *)&buf[bufoff], (void *)&entry->buf[bckoff], rsize); - *cid = entry->cid; - *iter = &entry->list; + + memncpy((void *)&buf[bufoff], (void *)&block->buf[bckoff], rsize); + return rsize; } -static int readfile_fat32(struct fat_internal *data, uint64 bckoff, uint8 *buf, uint32 bufoff, uint32 oid, uint32 size, struct list_head *list, uint32 cid){ +static int readfile_fat32(struct fat_internal *data, uint64 bckoff, uint8 *buf, uint32 bufoff, uint32 size, uint32 oid, uint32 cid){ + struct list_head *head; struct fat_info_t *info; struct fat_file_block_t *block; int lba; int rsize; + + head = &data->file->list; info = data->fat; + block = kmalloc(sizeof(struct fat_file_block_t)); + rsize = size > BLOCK_SIZE - bckoff ? BLOCK_SIZE - bckoff : size; - lba = info->cluster_lba + (cid -2) * info->bs.sector_per_cluster; + lba = info->cluster_lba + (cid - 2) * info->bs.sector_per_cluster; + sd_readblock(lba, block->buf); + memncpy((void *)&buf[bufoff], (void *)&block->buf[bckoff], rsize); + block->oid = oid; block->cid = cid; - list_add_tail(&block->list, list); + block->read = 1; + block->dirty = 0; + + list_add_tail(&block->list, head); + return rsize; } static int readfile(void *buf, struct fat_internal *data, uint64 fileoff, uint64 len){ - struct list_head *iter; - uint32 oid, cid; + struct fat_file_block_t *block; + struct list_head *head; + uint32 foid; // first block id + uint32 coid; // current block id + uint32 cid; // target cluster id uint64 bufoff, result; - int cache_end; + int ret; - iter = &data->file->list; - oid = fileoff / BLOCK_SIZE; - cid = 0; + block = NULL; + head = &data->file->list; + foid = fileoff / BLOCK_SIZE; + coid = 0; + cid = data->cid; bufoff = 0; result = 0; - cache_end = list_empty(iter); - if(!cache_end) - iter = iter->next; - - while(len){ + // Seek + + ret = readfile_seek_cache(data, foid, &block); + + if (ret < 0) { + ret = readfile_seek_fat32(data, foid, cid, &block); + } + + if (ret < 0) { + return 0; + } + + // if (block->oid != foid) error! + + // Read + + while (len) { uint64 bckoff; - int cache_hit, ret; + bckoff = (fileoff + result) % BLOCK_SIZE; - cache_hit = 0; - if(!cache_end){ - ret = readfile_cache(data, bckoff, buf, bufoff, oid, len, &iter, &cid); - - if(ret == -1) - cache_end = 1; - else if(ret > 0) - cache_hit = 1; - } + if (&block->list != head) { + ret = readfile_cache(data, bckoff, buf, bufoff, len, block); + + cid = block->cid; + block = list_first_entry(&block->list, + struct fat_file_block_t, list); + } else { + // Read block from sdcard, create cache + cid = get_next_cluster(data->fat->fat_lba, cid); - if(!cache_hit){ - if(!cid) - cid = data->cid; - else - cid = get_next_cluster(data->fat->fat_lba, cid); - if(cid > 0x0ffffff8) + if (invalid_cid(cid)) { break; - ret = readfile_fat32(data, bckoff, buf, bufoff, oid, len, iter, cid); + } + + ret = readfile_fat32(data, bckoff, buf, bufoff, len, coid, cid); } - if(ret < 0) + + if (ret < 0) { break; - + } + bufoff += ret; result += ret; - oid += 1; + coid += 1; len -= ret; } + return result; } @@ -370,6 +542,7 @@ static int writefile_seek_fat32(struct fat_internal *data, uint32 foid, uint32 f newblock->oid = curoid; newblock->cid = curcid; newblock->read = 0; + newblock->dirty = 1; list_add_tail(&newblock->list, &data->file->list); *block = newblock; @@ -406,7 +579,7 @@ static int writefile_fat32(struct fat_internal *data, uint64 bckoff, const uint8 block = kmalloc(sizeof(struct fat_file_block_t)); wsize = size > BLOCK_SIZE - bckoff ? BLOCK_SIZE - bckoff : size; - if(cid >= 0x0ffffff8) + if(invalid_cid(cid)) memset(block->buf, 0 , BLOCK_SIZE); else{ lba = info->cluster_lba + (cid -2) * info->bs.sector_per_cluster; @@ -416,6 +589,7 @@ static int writefile_fat32(struct fat_internal *data, uint64 bckoff, const uint8 block->oid = oid; block->cid = cid; block->read = 1; + block->dirty = 1; list_add_tail(&block->list, head); @@ -465,12 +639,374 @@ static int writefile(const void *buf, struct fat_internal *data, uint64 fileoff, return result; } +static void _do_sync_dir(struct vnode *dirnode) +{ + struct fat_internal *data, *entry; + struct list_head *head; + struct dir_t *dir; + struct long_dir_t *ldir; + uint32 cid; + int lba, idx, lfnidx; + uint8 buf[BLOCK_SIZE]; + + data = dirnode->internal; + head = &data->dir->list; + cid = data->cid; + idx = 0; + lfnidx = 1; + + if (invalid_cid(cid)) { + panic("fat32 _do_sync_dir: invalid dirnode->data->cid"); + } + + lba = data->fat->cluster_lba + + (cid - 2) * data->fat->bs.sector_per_cluster; + + // TODO: Cache data block of directory + sd_readblock(lba, buf); + + list_for_each_entry(entry, head, list) { + struct dir_t *origindir; + const char *name; + const char *ext; + int lfn, namelen, extpos, i, buflba; + uint8 lookupbuf[BLOCK_SIZE]; + + name = entry->name; + + // If entry is a old file, update its size + origindir = lookup_fat32(dirnode, name, lookupbuf, &buflba); + + if (origindir) { + if (entry->type == FAT_FILE) { + origindir->size = entry->file->size; + sd_writeblock(buflba, lookupbuf); + } + + continue; + } + + // Else if entry is a new file + ext = NULL; + extpos = -1; + + do { + namelen = strlen(name); + + if (namelen >= 13) { + lfn = 1; + break; + } + + for (i = 0; i < namelen; ++i) { + if (name[namelen - 1 - i] == '.') { + break; + } + } + + if (i < namelen) { + ext = &name[namelen - i]; + extpos = namelen - 1 - i; + } + + if (i >= 4) { + lfn = 1; + break; + } + + if (namelen - 1 - i > 8) { + lfn = 1; + break; + } + + lfn = 0; + } while(0); + + // Seek idx to the end of dir + while (1) { + dir = (struct dir_t *)(&buf[sizeof(struct dir_t) * idx]); + + if (dir->name[0] == 0) { + break; + } + + idx += 1; + + if (idx >= 16) { + uint32 newcid; + + sd_writeblock(lba, buf); + + newcid = get_next_cluster(data->fat->fat_lba, cid); + if (invalid_cid(newcid)) { + newcid = alloc_cluster(data->fat, cid); + } + + cid = newcid; + + lba = data->fat->cluster_lba + + (cid - 2) * data->fat->bs.sector_per_cluster; + + // TODO: Cache data block of directory + sd_readblock(lba, buf); + + idx = 0; + } + } + + // Write LFN + if (lfn) { + int ord; + int first; + + ord = ((namelen - 1) / 13) + 1; + first = 0x40; + + for (; ord > 0; --ord) { + int end; + + ldir = (struct long_dir_t *) + (&buf[sizeof(struct long_dir_t) * idx]); + + ldir->order = first | ord; + ldir->attr = ATTR_LFN; + ldir->type = 0; + // TODO: Calculate checksum + ldir->checksum = 0; + ldir->fstcluslo = 0; + + first = 0; + end = 0; + + for (i = 0; i < 10; i += 2) { + if (end) { + ldir->name1[i] = 0xff; + ldir->name1[i + 1] = 0xff; + } else { + ldir->name1[i] = name[(ord - 1) * 13 + i / 2]; + ldir->name1[i + 1] = 0; + if (ldir->name1[i] == 0) { + end = 1; + } + } + } + for (i = 0; i < 12; i += 2) { + if (end) { + ldir->name2[i] = 0xff; + ldir->name2[i + 1] = 0xff; + } else { + ldir->name2[i] = name[(ord - 1) * 13 + 5 + i / 2]; + ldir->name2[i + 1] = 0; + if (ldir->name2[i] == 0) { + end = 1; + } + } + } + for (i = 0; i < 4; i += 2) { + if (end) { + ldir->name3[i] = 0xff; + ldir->name3[i + 1] = 0xff; + } else { + ldir->name3[i] = name[(ord - 1) * 13 + 11 + i / 2]; + ldir->name3[i + 1] = 0; + if (ldir->name3[i] == 0) { + end = 1; + } + } + } + + idx += 1; + + if (idx >= 16) { + uint32 newcid; + + sd_writeblock(lba, buf); + + newcid = get_next_cluster(data->fat->fat_lba, cid); + if (invalid_cid(newcid)) { + newcid = alloc_cluster(data->fat, cid); + } + + cid = newcid; + + lba = data->fat->cluster_lba + + (cid - 2) * data->fat->bs.sector_per_cluster; + + // TODO: Cache data block of directory + sd_readblock(lba, buf); + + idx = 0; + } + } + } + + // Write SFN + dir = (struct dir_t *)(&buf[sizeof(struct dir_t) * idx]); + + // TODO: Set these properties properly + dir->ntres = 0; + dir->crttimetenth = 0; + dir->crttime = 0; + dir->crtdate = 0; + dir->lstaccdate = 0; + dir->wrttime = 0; + dir->wrtdate = 0; + + if (entry->type == FAT_DIR) { + dir->attr = ATTR_DIRECTORY; + dir->size = 0; + } else { + dir->attr = ATTR_ARCHIVE; + dir->size = entry->file->size; + } + + if (invalid_cid(entry->cid)) { + entry->cid = alloc_cluster(data->fat, 0); + } + + dir->ch = (entry->cid >> 16) & 0xffff; + dir->cl = entry->cid & 0xffff; + + if (lfn) { + int lfni; + + // TODO: handle lfnidx + for (i = 7, lfni = lfnidx; i >= 0 && lfni;) { + dir->name[i--] = '0' + lfni % 10; + lfni /= 10; + } + + lfnidx++; + + dir->name[i--] = '~'; + + // TODO: handle letter case + memncpy((void *)dir->name, name, i + 1); + } else { + // TODO: handle letter case + for (i = 0; i != extpos && name[i]; ++i) { + dir->name[i] = name[i]; + } + + for (; i < 8; ++i) { + dir->name[i] = ' '; + } + } + + // TODO: handle letter case + for (i = 0; i < 3 && ext[i]; ++i) { + dir->name[8 + i] = ext[i]; + } + + for (; i < 3; ++i) { + dir->name[8 + i] = ' '; + } + + idx += 1; + + if (idx >= 16) { + int newcid; + + sd_writeblock(lba, buf); + + newcid = get_next_cluster(data->fat->fat_lba, cid); + if (invalid_cid(newcid)) { + newcid = alloc_cluster(data->fat, cid); + } + + cid = newcid; + + lba = data->fat->cluster_lba + + (cid - 2) * data->fat->bs.sector_per_cluster; + + // TODO: Cache data block of directory + sd_readblock(lba, buf); + + idx = 0; + } + } + + if (idx) { + sd_writeblock(lba, buf); + } +} + +static void _do_sync_file(struct vnode *filenode) +{ + struct fat_file_block_t *entry; + struct fat_internal *data; + struct list_head *head; + uint32 cid; + + data = filenode->internal; + head = &data->file->list; + cid = data->cid; + + if (invalid_cid(cid)) { + panic("fat32 _do_sync_file: invalid cid"); + } + + list_for_each_entry(entry, head, list) { + int lba; + + if (entry->oid == 0) { + if (!invalid_cid(entry->cid) && data->cid != entry->cid) { + panic("_do_sync_file: cid isn't sync"); + } + + entry->cid = data->cid; + } + + if (invalid_cid(entry->cid)) { + entry->cid = alloc_cluster(data->fat, cid); + } + + if (!entry->dirty) { + continue; + } + + if (!entry->read) { + memset(entry->buf, 0, BLOCK_SIZE); + entry->read = 1; + } + + lba = data->fat->cluster_lba + + (entry->cid - 2) * data->fat->bs.sector_per_cluster; + + sd_writeblock(lba, entry->buf); + + entry->dirty = 0; + + cid = entry->cid; + } +} + +static void _sync_dir(struct vnode *dirnode) +{ + struct fat_internal *data, *entry; + struct list_head *head; + + data = dirnode->internal; + head = &data->dir->list; + + _do_sync_dir(dirnode); + + list_for_each_entry(entry, head, list) { + if (entry->type == FAT_DIR) { + _sync_dir(entry->node); + } else { + _do_sync_file(entry->node); + } + } +} + int fat32fs_mount(struct filesystem *fs, struct mount *mount){ struct partition_t *partition; struct fat_info_t *fat; struct fat_dir_t *dir; struct fat_internal *data; struct vnode *oldnode, *node; + struct fat_mount_t *newmount; const char *name; uint32 lba; uint8 buf[BLOCK_SIZE]; @@ -489,6 +1025,7 @@ int fat32fs_mount(struct filesystem *fs, struct mount *mount){ data = kmalloc(sizeof(struct fat_internal)); fat = kmalloc(sizeof(struct fat_info_t)); dir = kmalloc(sizeof(struct fat_dir_t)); + newmount = kmalloc(sizeof(struct fat_mount_t)); memncpy((void *)&fat->bs,(void *)buf,sizeof(fat->bs)); fat->fat_lba = lba + fat->bs.reserved_sector_cnt; @@ -516,6 +1053,14 @@ int fat32fs_mount(struct filesystem *fs, struct mount *mount){ oldnode->f_ops = &fat32fs_f_ops; oldnode->internal = data; + preempt_disable(); + + list_add(&newmount->list, &mounts); + + preempt_enable(); + + newmount->mount = mount; + return 0; } int fat32fs_lookup(struct vnode *dir_node, struct vnode **target, const char *component_name){ @@ -674,6 +1219,17 @@ int fat32fs_ioctl(struct file *file, uint64 request, va_list args){ return -1; } +int fat32fs_sync(struct filesystem *fs){ + struct fat_mount_t *entry; + + list_for_each_entry(entry, &mounts, list) { + _sync_dir(entry->mount->root); + } + + return 0; +} + struct filesystem *fat32fs_init(void){ + INIT_LIST_HEAD(&mounts); return &fat32fs; } \ No newline at end of file diff --git a/src/lib/framebufferfs.c b/src/lib/framebufferfs.c index d18116d54..fe9ceeee4 100644 --- a/src/lib/framebufferfs.c +++ b/src/lib/framebufferfs.c @@ -9,7 +9,8 @@ static struct filesystem fbfs = { .name = "framebufferfs", - .mount = fbfs_mount + .mount = fbfs_mount, + .sync = fbfs_sync, }; static struct vnode_operations fbfs_v_ops = { @@ -230,6 +231,10 @@ int fbfs_ioctl(struct file *file, uint64 request, va_list args) return 0; } +int fbfs_sync(struct filesystem *fs){ + return 0; +} + struct filesystem *framebufferfs_init(void){ return &fbfs; } \ No newline at end of file diff --git a/src/lib/syscall.c b/src/lib/syscall.c index fd5c21283..2b2dc1488 100644 --- a/src/lib/syscall.c +++ b/src/lib/syscall.c @@ -35,8 +35,9 @@ syscall_funcp syscall_table[] = { (syscall_funcp) syscall_chdir, // 17 (syscall_funcp) syscall_lseek64, // 18 (syscall_funcp) syscall_ioctl, // 19 - (syscall_funcp) syscall_sigreturn, // 20 - (syscall_funcp) syscall_test, // 21 + (syscall_funcp) syscall_sync, // 20 + (syscall_funcp) syscall_sigreturn, // 21 + (syscall_funcp) syscall_test, // 22 }; static inline void copy_regs(struct pt_regs *regs) diff --git a/src/lib/tmpfs.c b/src/lib/tmpfs.c index f9f798181..4d2c1a8ba 100644 --- a/src/lib/tmpfs.c +++ b/src/lib/tmpfs.c @@ -5,7 +5,8 @@ static struct filesystem tmpfs = { .name = "tmpfs", .mount = tmpfs_mount, - .alloc_vnode = tmpfs_alloc_vnode + .alloc_vnode = tmpfs_alloc_vnode, + .sync = tmpfs_sync }; static struct vnode_operations tmpfs_v_ops = { @@ -282,6 +283,10 @@ int tmpfs_ioctl(struct file *file, uint64 request, va_list args){ return -1; } +int tmpfs_sync(struct filesystem *fs){ + return 0; +} + struct filesystem *tmpfs_init(void){ return &tmpfs; } \ No newline at end of file diff --git a/src/lib/uartfs.c b/src/lib/uartfs.c index 663598a45..26b3ab5df 100644 --- a/src/lib/uartfs.c +++ b/src/lib/uartfs.c @@ -4,7 +4,8 @@ static struct filesystem uartfs = { .name = "uartfs", - .mount = uartfs_mount + .mount = uartfs_mount, + .sync = uartfs_sync }; static struct vnode_operations uartfs_v_ops = { @@ -123,6 +124,10 @@ int uartfs_ioctl(struct file *file, uint64 request, va_list args){ return -1; } +int uartfs_sync(struct filesystem *fs){ + return 0; +} + /* Others */ struct filesystem *uartfs_init(void){ diff --git a/src/lib/vfs.c b/src/lib/vfs.c index 2754639d9..01e66c839 100644 --- a/src/lib/vfs.c +++ b/src/lib/vfs.c @@ -98,6 +98,10 @@ int register_filesystem(struct filesystem *fs){ return 0; } +int vfs_sync(struct filesystem *fs){ + return fs->sync(fs); +} + int vfs_open(const char *pathname, int flags, struct file *target){ const char *curname; struct vnode *dir_node; @@ -325,44 +329,102 @@ static int do_ioctl(int fd, uint64 request, va_list args){ return ret; } -void syscall_open(trapframe *frame, const char *pathname, int flags){ +static int do_sync(void){ + struct filesystem *entry; + + list_for_each_entry(entry, &filesystems, fs_list) { + vfs_sync(entry); + } + + return 0; +} + +void syscall_open(trapframe *frame, const char *pathname, int flags) +{ int fd = do_open(pathname, flags); + frame->x0 = fd; + + uart_sync_printf("[open] (\"%s\", 0x%x) = %d\r\n", pathname, flags, fd); } -void syscall_close(trapframe *frame, int fd){ + +void syscall_close(trapframe *frame, int fd) +{ int ret = do_close(fd); + frame->x0 = ret; + + uart_sync_printf("[close] (%d) = %d\r\n", fd, ret); } -void syscall_write(trapframe *frame, int fd, const void *buf, uint64 count){ - // uart_sync_printf("count is %d\r\n", count); + +void syscall_write(trapframe *frame, int fd, const void *buf, uint64 count) +{ int ret = do_write(fd, buf, count); + frame->x0 = ret; + + uart_sync_printf("[write] (%d, \"%s\", 0x%x) = %d\r\n", fd, buf, count, ret); } -void syscall_read(trapframe *frame, int fd, void *buf, uint64 count){ + +void syscall_read(trapframe *frame, int fd, void *buf, uint64 count) +{ int ret = do_read(fd, buf, count); + frame->x0 = ret; + + uart_sync_printf("[read] (%d, \"%s\", 0x%x) = %d\r\n", fd, buf, count, ret); } -void syscall_mkdir(trapframe *frame, const char *pathname, uint32 mode){ + +void syscall_mkdir(trapframe *frame, const char *pathname, uint32 mode) +{ int ret = do_mkdir(pathname, mode); + frame->x0 = ret; } -void syscall_mount(trapframe *frame, const char *src, const char *target, const char *filesystem, uint64 flags, const void *data){ + +void syscall_mount(trapframe *frame, const char *src, const char *target, + const char *filesystem, uint64 flags, const void *data) +{ int ret = do_mount(target, filesystem); + frame->x0 = ret; } -void syscall_chdir(trapframe *frame, const char *path){ + +void syscall_chdir(trapframe *frame, const char *path) +{ int ret = do_chdir(path); + frame->x0 = ret; } -void syscall_lseek64(trapframe *frame, int fd, int64 offset, int whence){ + +void syscall_lseek64(trapframe *frame, int fd, int64 offset, int whence) +{ long ret = do_lseek64(fd, offset, whence); + frame->x0 = ret; + + uart_sync_printf("[lseek64] (%d, 0x%x, 0x%x) = %d\r\n", fd, offset, whence, ret); } -void syscall_ioctl(trapframe *frame, int fd, uint64 request, ...){ - int ret ; + +void syscall_ioctl(trapframe *frame, int fd, uint64 request, ...) +{ + int ret; va_list args; + va_start(args, request); + ret = do_ioctl(fd, request, args); + va_end(args); + frame->x0 = ret; +} + +void syscall_sync(trapframe *frame) +{ + int ret = do_sync(); + + frame->x0 = ret; + + uart_sync_printf("[sync] = %d\r\n", ret); } \ No newline at end of file