From 4130562b1555cabe441efe9420cebe12e7ed8d39 Mon Sep 17 00:00:00 2001 From: Benji Dial Date: Sat, 13 Jan 2024 16:43:49 -0500 Subject: application loading --- applications/init/main.cpp | 24 +++ applications/link.ld | 38 +++++ documentation/memory.txt | 17 ++- documentation/syscalls.txt | 27 ++++ include/mercury/kernel/application.hpp | 72 +++++++++ include/mercury/kernel/framebuffer.hpp | 2 + include/mercury/kernel/paging.hpp | 17 ++- include/mercury/kernel/terminal.hpp | 2 + include/mercury/kernel/vfile.hpp | 5 + include/mercury/syscall.hpp | 26 ++++ kernel/application.cpp | 266 +++++++++++++++++++++++++++++++++ kernel/entry.cpp | 140 +++++++---------- kernel/paging.cpp | 72 +++++++-- kernel/syscall.asm | 160 ++++++++++++++++++++ kernel/syscall.cpp | 46 ++++++ kernel/terminal.cpp | 10 ++ kernel/vfile.cpp | 7 + makefile | 58 +++++-- qemu.gdb | 2 + stdlib/entry.cpp | 14 ++ stdlib/syscall.asm | 35 +++++ 21 files changed, 917 insertions(+), 123 deletions(-) create mode 100644 applications/init/main.cpp create mode 100644 applications/link.ld create mode 100644 documentation/syscalls.txt create mode 100644 include/mercury/kernel/application.hpp create mode 100644 include/mercury/syscall.hpp create mode 100644 kernel/application.cpp create mode 100644 kernel/syscall.asm create mode 100644 kernel/syscall.cpp create mode 100644 stdlib/entry.cpp create mode 100644 stdlib/syscall.asm diff --git a/applications/init/main.cpp b/applications/init/main.cpp new file mode 100644 index 0000000..7aa2a38 --- /dev/null +++ b/applications/init/main.cpp @@ -0,0 +1,24 @@ +#include + +using mercury::syscall::encoded_color; +using mercury::syscall::color; + +encoded_color *fb; +uint32_t fb_width; +uint32_t fb_height; +uint32_t fb_pitch; + +int main(int, char **) { + mercury::syscall::get_framebuffer(fb, fb_width, fb_height, fb_pitch); + for (uint32_t y = 0; y < fb_height; ++y) + for (uint32_t x = 0; x < fb_width; ++x) { + color c = { + .r = 0, + .g = (uint8_t)(y * 255 / fb_height), + .b = (uint8_t)(x * 255 / fb_width) + }; + fb[y * fb_pitch + x] = mercury::syscall::encode_color(c); + } + mercury::syscall::draw_framebuffer(); + return 0; +} diff --git a/applications/link.ld b/applications/link.ld new file mode 100644 index 0000000..eaba220 --- /dev/null +++ b/applications/link.ld @@ -0,0 +1,38 @@ +OUTPUT_FORMAT(elf64-x86-64) +OUTPUT_ARCH(i386:x86-64) + +ENTRY(_entry) + +PHDRS { + rx PT_LOAD FLAGS(5); + ro PT_LOAD FLAGS(4); + rw PT_LOAD FLAGS(6); +} + +SECTIONS { + + /* see also ../documentation/memory.txt */ + . = 0x200000; + + .text : { + *(.text .text.*) + } : rx + + . = ALIGN(0x200000); + + .rodata : { + *(.rodata .rodata.*) + } : ro + + . = ALIGN(0x200000); + + .data : { + *(.data .data.*) + } : rw + + .bss : { + *(.bss .bss.*) + *(COMMON) + } : rw + +} diff --git a/documentation/memory.txt b/documentation/memory.txt index e820034..581bd71 100644 --- a/documentation/memory.txt +++ b/documentation/memory.txt @@ -1,7 +1,14 @@ only the first 32GiB of physical memory are considered. this can be changed -with pram_pages in paging.cpp. as for virtual memory, the top 1GiB is reserved -for the kernel. the top 1MiB of that is reserved for the kernel stack, with an -unmapped guard page at the top and bottom of the kernel stack. +with pram_pages in paging.cpp. vram layout is as follows: -if any of these facts change, the linker script and paging code should be -checked closely. +0x0000.0000.0000 - 0x0000.001f.ffff: always unmapped +0x0000.0020.0000 - 0x0000.3fbf.ffff: available to user process +0x0000.3fc0.0000 - 0x0000.3fdf.ffff: always unmapped +0x0000.3fe0.0000 - 0x0000.3fff.ffff: user stack +0x0000.4000.0000 - 0xffff.bfff.ffff: always unmapped +0xffff.c000.0000 - 0xffff.ffdf.ffff: available to kernel +0xffff.ffe0.0000 - 0xffff.ffe0.0fff: always unmapped +0xffff.ffe0.1000 - 0xffff.ffef.efff: interrupt stack +0xffff.ffef.f000 - 0xffff.fff0.0fff: always unmapped +0xffff.fff0.1000 - 0xffff.ffff.efff: syscall stack / kernel init stack +0xffff.ffff.f000 - 0xffff.ffff.ffff: always unmapped diff --git a/documentation/syscalls.txt b/documentation/syscalls.txt new file mode 100644 index 0000000..cbba5e3 --- /dev/null +++ b/documentation/syscalls.txt @@ -0,0 +1,27 @@ +on application entry: + there is a 1MiB - 8KiB area mapped writable and not + executable with guard pages on either side, and rsp is + set to the top of that. all other registers are set to 0. + +for all system calls: + rax, rdi, rsi, rdx are in/out paramters. + rbx, rbp, rsp, r12-r15 are preserved. + rcx, rflags, r8-r11 are clobbered. + +encode color: + rax in: 0 + edi in: r + g * 256 + b * 65536 + eax out: encoded color + +get framebuffer: + rax in: 1 + rax out: pointer to framebuffer + rdi out: width + height * 2 ** 32 + esi out: pitch + framebuffer is always 32 bpp. use the encode color syscall + to encode colors. pitch is in dwords, not in bytes. + +draw framebuffer: + rax in: 2 + this draws changes to the framebuffer gotten by the get framebuffer + system call. (currently, the entire thing is copied, not just changes.) diff --git a/include/mercury/kernel/application.hpp b/include/mercury/kernel/application.hpp new file mode 100644 index 0000000..bc4a763 --- /dev/null +++ b/include/mercury/kernel/application.hpp @@ -0,0 +1,72 @@ +#ifndef MERCURY_KERNEL_APPLICATION_HPP +#define MERCURY_KERNEL_APPLICATION_HPP + +#include +#include + +namespace mercury::kernel::application { + + enum class app_state { + running, + paused, + zombie + }; + + struct app_instance { + + app_state state; + + uint64_t *p4; + uint64_t *p3; + uint64_t *p2; + + bool *p2es_to_free_on_exit; + + uint64_t p4_paddr; + + //set to 0 if none + uint64_t framebuffer_vaddr; + + //only valid if state is zombie + int exit_code; + + //only valid if state is paused + struct { + uint64_t rip; + uint64_t rsp; + //TODO: etc. + } saved_regs; + + app_instance(); + ~app_instance(); + + //2MiB page. vaddr and paddr must be aligned, and vaddr in valid range. + void map_page(uint64_t vaddr, uint64_t paddr, + bool write, bool execute, bool free_pram_on_exit); + + //2MiB pages. returns start of first page. + uint64_t get_free_vaddr_pages(uint64_t count); + + void create_stack(); + + void set_instruction_pointer(uint64_t vaddr); + + //2MiB pages; only lower half. + uint64_t count_mapped_vram_pages(); + + }; + + extern app_instance *running_app; + + enum class create_app_result { + success, + device_error, + app_corrupt, + fs_corrupt + }; + + create_app_result create_app(const vfile::vfile &file, app_instance *&out); + +} + +#endif diff --git a/include/mercury/kernel/framebuffer.hpp b/include/mercury/kernel/framebuffer.hpp index 5c6ca23..677a42a 100644 --- a/include/mercury/kernel/framebuffer.hpp +++ b/include/mercury/kernel/framebuffer.hpp @@ -5,8 +5,10 @@ namespace mercury::kernel::framebuffer { + extern uint32_t *vaddr; extern int width; extern int height; + extern int dword_pitch; void init_framebuffer(uint64_t vaddr, uint64_t width, uint64_t height, uint64_t pitch); diff --git a/include/mercury/kernel/paging.hpp b/include/mercury/kernel/paging.hpp index 4ba5fdf..f6d400a 100644 --- a/include/mercury/kernel/paging.hpp +++ b/include/mercury/kernel/paging.hpp @@ -11,27 +11,36 @@ namespace mercury::kernel::paging { void mark_all_pram_used(); void mark_all_vram_free(); - //assumes page-alignment void mark_pram_region_free(uint64_t start_addr, uint64_t end_addr); - //assumes page-alignment void mark_vram_region_used(uint64_t start_addr, uint64_t end_addr); uint64_t find_unmapped_vram_region(uint64_t page_count); + uint64_t encode_pte( + uint64_t addr, bool user, bool write, bool execute, bool ps); + void init_kernel_page_tables(uint64_t kernel_offset); - void map_kernel_stack(); + void map_kernel_stacks(); - //assumes page-alignment void map_kernel_page( uint64_t paddr, uint64_t vaddr, bool write, bool execute); + void unmap_kernel_page(uint64_t vaddr); + //maps writable and not executable void *map_new_kernel_pages(uint64_t count); + //maps writable and not executable + void map_new_kernel_page(uint64_t &vaddr_out, uint64_t &paddr_out); + uint64_t get_used_vram_page_count(); uint64_t get_free_pram_page_count(); + extern uint64_t kernel_p4e; + + uint64_t take_2mib_pram_page(); + } #endif diff --git a/include/mercury/kernel/terminal.hpp b/include/mercury/kernel/terminal.hpp index 8efbcac..7b2d27a 100644 --- a/include/mercury/kernel/terminal.hpp +++ b/include/mercury/kernel/terminal.hpp @@ -28,6 +28,8 @@ namespace mercury::kernel::terminal { void put_int_decimal(uint64_t n, bool with_commas = true); + void put_int_hex(uint64_t n, int digits, bool with_dots = true); + } #endif diff --git a/include/mercury/kernel/vfile.hpp b/include/mercury/kernel/vfile.hpp index 43dff96..ce72bb1 100644 --- a/include/mercury/kernel/vfile.hpp +++ b/include/mercury/kernel/vfile.hpp @@ -48,6 +48,11 @@ namespace mercury::kernel::vfile { //dir_entry.type is assumed to be directory. out must be empty on entry. storage::fs_result get_children(utility::vector &out) const; + //assumes file is a regular file and [start, start + length) + //is in bounds of file. start and length are in bytes. + storage::fs_result read_file( + uint64_t start, uint64_t length, void *into) const; + }; //path must be absolute. follows symlinks on all but the last node. diff --git a/include/mercury/syscall.hpp b/include/mercury/syscall.hpp new file mode 100644 index 0000000..f434d91 --- /dev/null +++ b/include/mercury/syscall.hpp @@ -0,0 +1,26 @@ +#ifndef MERCURY_SYSCALL_HPP +#define MERCURY_SYSCALL_HPP + +#include + +namespace mercury::syscall { + + typedef uint32_t encoded_color; + + struct [[gnu::packed]] color { + uint8_t r; + uint8_t g; + uint8_t b; + }; + + extern "C" encoded_color encode_color(color c); + + extern "C" void get_framebuffer(encoded_color *&framebuffer_out, + uint32_t &width_out, uint32_t &height_out, uint32_t &pitch_out + ); + + extern "C" void draw_framebuffer(); + +} + +#endif diff --git a/kernel/application.cpp b/kernel/application.cpp new file mode 100644 index 0000000..19b1dbc --- /dev/null +++ b/kernel/application.cpp @@ -0,0 +1,266 @@ +#include +#include + +//TODO - scheduling. + +namespace mercury::kernel::application { + + app_instance::app_instance() : state(app_state::paused) { + + framebuffer_vaddr = 0; + + uint64_t p3_paddr; + uint64_t p2_paddr; + + uint64_t p4_vaddr; + uint64_t p3_vaddr; + uint64_t p2_vaddr; + + paging::map_new_kernel_page(p4_vaddr, p4_paddr); + paging::map_new_kernel_page(p3_vaddr, p3_paddr); + paging::map_new_kernel_page(p2_vaddr, p2_paddr); + + p4 = (uint64_t *)p4_vaddr; + p3 = (uint64_t *)p3_vaddr; + p2 = (uint64_t *)p2_vaddr; + + for (int i = 1; i < 511; ++i) + p4[i] = 0; + p4[511] = paging::kernel_p4e; + p4[0] = paging::encode_pte(p3_paddr, true, true, true, false); + + for (int i = 1; i < 512; ++i) + p3[i] = 0; + p3[0] = paging::encode_pte(p2_paddr, true, true, true, false); + + for (int i = 0; i < 512; ++i) + p2[i] = 0; + + p2es_to_free_on_exit = new bool[512]; + + } + + app_instance::~app_instance() { + + for (int i = 1; i < 512; ++i) + if (p2[i] != 0 && p2es_to_free_on_exit[i]) { + uint64_t paddr = p2[i] & ~0x1fffffULL; + paging::mark_pram_region_free(paddr, paddr + 0x200000); + } + + delete[] p2es_to_free_on_exit; + + uint64_t p2_paddr = p3[0] & ~0x1fffffULL; + paging::unmap_kernel_page((uint64_t)p2); + paging::mark_pram_region_free(p2_paddr, p2_paddr + 4096); + + uint64_t p3_paddr = p4[0] & ~0x1fffffULL; + paging::unmap_kernel_page((uint64_t)p3); + paging::mark_pram_region_free(p3_paddr, p3_paddr + 4096); + + paging::unmap_kernel_page((uint64_t)p4); + paging::mark_pram_region_free(p4_paddr, p4_paddr + 4096); + + } + + void app_instance::map_page(uint64_t vaddr, uint64_t paddr, + bool write, bool execute, bool free_pram_on_exit + ) { + uint64_t i = vaddr / 0x200000; + p2[i] = paging::encode_pte(paddr, true, write, execute, true); + p2es_to_free_on_exit[i] = free_pram_on_exit; + } + + uint64_t app_instance::get_free_vaddr_pages(uint64_t count) { + uint64_t start = 1; + uint64_t length = 0; + while (start + length < 510) { + if (length == count) + return start * 0x200000; + if (p2[start + length] == 0) + ++length; + else { + start += length + 1; + length = 0; + } + } + //TODO: handle out of memory + return 0; + } + + void app_instance::create_stack() { + uint64_t stack_paddr = paging::take_2mib_pram_page(); + map_page(0x3fe00000, stack_paddr, true, false, true); + for (int i = 0; i < 512; ++i) { + uint64_t vaddr = paging::find_unmapped_vram_region(1); + paging::map_kernel_page(stack_paddr + 512 * i, vaddr, true, false); + for (int j = 0; j < 4096 / 8; ++j) + *(uint64_t *)(vaddr + j * 8) = 0; + paging::unmap_kernel_page(vaddr); + } + saved_regs.rsp = 0x40000000; + } + + void app_instance::set_instruction_pointer(uint64_t vaddr) { + saved_regs.rip = vaddr; + } + + uint64_t app_instance::count_mapped_vram_pages() { + uint64_t count = 0; + for (int i = 1; i < 512; ++i) + if (p2[i] != 0) + ++count; + return count; + } + + app_instance *running_app; + + static uint8_t correct_magic[16] = { + 0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, + 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00 + }; + +#define READ(a, b, c) \ + { \ + storage::fs_result _result = file.read_file(a, b, c); \ + if (_result == storage::fs_result::device_error) \ + return create_app_result::device_error; \ + if (_result == storage::fs_result::fs_corrupt) \ + return create_app_result::fs_corrupt; \ + } + + struct load_info { + uint64_t foffset; + uint64_t fsize; + uint64_t vaddr; + uint64_t vpages; + bool writable; + bool executable; + }; + + create_app_result create_app(const vfile::vfile &file, app_instance *&out) { + + uint8_t magic[16]; + if (file.dir_entry.length < 64) + return create_app_result::app_corrupt; + READ(0, 8, magic) + READ(16, 8, magic + 8) + for (int i = 0; i < 16; ++i) + if (magic[i] != correct_magic[i]) + return create_app_result::app_corrupt; + + uint64_t entry_point; + uint64_t phead_start; + uint16_t phead_entry_size; + uint16_t phead_entry_count; + + READ(24, 8, &entry_point) + READ(32, 8, &phead_start) + READ(54, 2, &phead_entry_size) + READ(56, 2, &phead_entry_count) + + if (file.dir_entry.length < + phead_start + phead_entry_size * phead_entry_count) + return create_app_result::app_corrupt; + + utility::vector load_infos; + + for (uint16_t i = 0; i < phead_entry_count; ++i) { + + uint64_t entry_start = phead_start + phead_entry_size * i; + + uint32_t seg_type; + READ(entry_start, 4, &seg_type) + if (seg_type != 1) + continue; + + uint64_t foffset; + uint64_t vaddr; + uint64_t fsize; + uint64_t vsize; + uint32_t flags; + + READ(entry_start + 8, 8, &foffset) + READ(entry_start + 16, 8, &vaddr) + READ(entry_start + 32, 8, &fsize) + READ(entry_start + 40, 8, &vsize) + READ(entry_start + 4, 4, &flags) + + if (vaddr & 0x1fffff) + return create_app_result::app_corrupt; + if (file.dir_entry.length < foffset + fsize) + return create_app_result::app_corrupt; + if (fsize > vsize) + return create_app_result::app_corrupt; + if (vaddr == 0) + return create_app_result::app_corrupt; + + uint64_t vpages = (vsize - 1) / 0x200000 + 1; + + if (vaddr + vpages * 0x200000 > ((1 << 30) - (4 << 20))) + return create_app_result::app_corrupt; + + load_info info = { + .foffset = foffset, + .fsize = fsize, + .vaddr = vaddr, + .vpages = vpages, + .writable = (flags & 2) == 2, + .executable = (flags & 1) == 1 + }; + load_infos.add_end(info); + + } + + out = new app_instance(); + + for (unsigned i = 0; i < load_infos.count; ++i) { + const auto &info = load_infos.buffer[i]; + for (uint64_t j = 0; j < info.vpages; ++j) { + uint64_t paddr = paging::take_2mib_pram_page(); + out->map_page(info.vaddr + j * 0x200000, paddr, + info.writable, info.executable, true); + for (int k = 0; k < 512; ++k) { + uint64_t offset_in_segment = j * 0x200000 + k * 4096; + uint64_t kvaddr = paging::find_unmapped_vram_region(1); + paging::map_kernel_page(paddr + k * 4096, kvaddr, true, false); + storage::fs_result result = storage::fs_result::success; + if (info.fsize > offset_in_segment) { + if (info.fsize >= offset_in_segment + 4096) + result = file.read_file( + info.foffset + offset_in_segment, 4096, (void *)kvaddr); + else { + int to_read = info.fsize - offset_in_segment; + result = file.read_file( + info.foffset + offset_in_segment, to_read, (void *)kvaddr); + uint8_t *blank = (uint8_t *)(kvaddr + to_read); + for (int i = 0; i < 4096 - to_read; ++i) + blank[i] = 0; + } + } + else { + uint8_t *blank = (uint8_t *)kvaddr; + for (int i = 0; i < 4096; ++i) + blank[i] = 0; + } + paging::unmap_kernel_page(kvaddr); + if (result == storage::fs_result::device_error) { + delete out; + return create_app_result::device_error; + } + if (result == storage::fs_result::fs_corrupt) { + delete out; + return create_app_result::fs_corrupt; + } + } + } + } + + out->create_stack(); + out->set_instruction_pointer(entry_point); + + return create_app_result::success; + + } + +} diff --git a/kernel/entry.cpp b/kernel/entry.cpp index 445d560..f7ba71d 100644 --- a/kernel/entry.cpp +++ b/kernel/entry.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -103,6 +104,8 @@ uint64_t initfs_len; [[noreturn]] static void with_kernel_p4(); +extern "C" void load_gdt_and_idt(); + extern "C" [[noreturn]] void entry() { //TODO?: maybe we should check if the limine requests were @@ -113,9 +116,9 @@ extern "C" [[noreturn]] void entry() { paging::mark_all_pram_used(); auto memmap = memmap_request.response; for (uint64_t i = 0; i < memmap->entry_count; ++i) { - //we don't start allocating physical pages until after we are done using - //limine structures (specifically at the call to paging::map_kernel_stack), - //so we consider bootloader reclaimable to be free. usable and bootloader + //we don't allocate any physical pages until after we are done using limine + //structures (specifically at the call to paging::map_kernel_stacks), so + //we consider bootloader reclaimable to be free. usable and bootloader //reclaimable are guaranteed by limine spec to be page-aligned. auto entry = memmap->entries[i]; if (entry->type == LIMINE_MEMMAP_USABLE || @@ -175,79 +178,29 @@ extern "C" [[noreturn]] void entry() { //switch to kernel p4 - paging::map_kernel_stack(); + paging::map_kernel_stacks(); + load_gdt_and_idt(); switch_to_kernel_p4(&with_kernel_p4); } +static void print_mem() { + uint64_t used_vram_mib = (paging::get_used_vram_page_count() + 128) / 256; + uint64_t free_pram_mib = (paging::get_free_pram_page_count() + 128) / 256; + terminal::put_int_decimal(used_vram_mib); + terminal::put_string_sz(" MiB kernel memory mapped.\n"); + terminal::put_int_decimal(free_pram_mib); + terminal::put_string_sz(" MiB physical memory free.\n"); +} + [[noreturn]] static void print_and_halt(const char *msg) { terminal::put_string_sz(msg); while (1) asm ("hlt"); } -static void draw_indent(unsigned indent) { - for (unsigned i = 0; i < indent; ++i) - terminal::put_char(' '); -} - -static void dir_tree( - const vfile::vfile &root, const vfile::vfile &f, unsigned indent -) { - - draw_indent(indent); - terminal::put_string(f.dir_entry.name); - terminal::put_char('\n'); - - std::optional followed; - utility::vector children; - - switch (f.dir_entry.type) { - - case storage::file_type::regular_file: - draw_indent(indent + 2); - terminal::put_string_sz("type: regular file\n"); - draw_indent(indent + 2); - terminal::put_string_sz("length: "); - terminal::put_int_decimal(f.dir_entry.length); - terminal::put_string_sz(" bytes\n"); - break; - - case storage::file_type::symlink: - draw_indent(indent + 2); - terminal::put_string_sz("type: symlink\n"); - draw_indent(indent + 2); - terminal::put_string_sz("target: "); - terminal::put_string(f.dir_entry.target); - terminal::put_char('\n'); - if (f.follow_symlinks(root, followed) != storage::fs_result::success) - print_and_halt("failed to follow symlink."); - if (!followed) { - draw_indent(indent + 4); - terminal::put_string_sz("[broken]\n"); - } - dir_tree(root, *followed, indent + 4); - break; - - case storage::file_type::directory: - if (f.get_children(children) != storage::fs_result::success) - print_and_halt("failed to get children."); - draw_indent(indent + 2); - terminal::put_string_sz("type: directory\n"); - draw_indent(indent + 2); - terminal::put_string_sz("children:\n"); - if (children.count == 0) { - draw_indent(indent + 4); - terminal::put_string_sz("[empty]\n"); - } - else - for (unsigned i = 0; i < children.count; ++i) - dir_tree(root, children.buffer[i], indent + 4); - break; - - } - -} +extern "C" [[noreturn]] void start_user_mode( + uint64_t rip, uint64_t rsp, uint64_t p4_paddr); [[noreturn]] static void with_kernel_p4() { @@ -257,27 +210,46 @@ static void dir_tree( storage::fs::tarfs_instance initfs_fs(&initfs_bd); initfs_bd.mounted_as = &initfs_fs; - terminal::put_string_sz("kernel initialization complete.\n"); - - int used_vram_kib = paging::get_used_vram_page_count() * 4; - int free_pram_kib = paging::get_free_pram_page_count() * 4; + vfile::vfile vfs_root; + vfs_root.bd = &initfs_bd; + vfs_root.dir_entry.type = storage::file_type::directory; + vfs_root.path.absolute = true; - terminal::put_int_decimal(used_vram_kib); - terminal::put_string_sz(" kiB kernel memory mapped.\n"); - terminal::put_int_decimal(free_pram_kib); - terminal::put_string_sz(" kiB physical memory free.\n"); + if (initfs_fs.get_root_node(vfs_root.dir_entry.node) != + storage::fs_result::success) + print_and_halt("failed to get root node of initfs."); - vfile::vfile root; - root.bd = &initfs_bd; - root.dir_entry.type = storage::file_type::directory; - root.path.absolute = true; + utility::string init_path_string("/bin/init.elf", 13); + vfile::canon_path init_path; + vfile::canonize_path(init_path_string, init_path); - if (initfs_fs.get_root_node(root.dir_entry.node) != + std::optional init_file; + if (vfile::lookup_path(vfs_root, init_path, init_file) != storage::fs_result::success) - print_and_halt("failed to get root node."); - - terminal::put_string_sz("initfs"); - dir_tree(root, root, 0); - print_and_halt("halting."); + print_and_halt("failed to look up /bin/init.elf."); + if (!init_file) + print_and_halt("/bin/init.elf does not exist."); + + terminal::put_string_sz("/bin/init.elf is "); + terminal::put_int_decimal(init_file->dir_entry.length); + terminal::put_string_sz(" bytes long.\n"); + + application::app_instance *init; + if (application::create_app(*init_file, init) != + application::create_app_result::success) + print_and_halt("failed to parse /bin/init.elf."); + + terminal::put_string_sz("/bin/init.elf loaded:\n instruction pointer 0x"); + terminal::put_int_hex(init->saved_regs.rip, 8); + terminal::put_string_sz("\n stack pointer 0x"); + terminal::put_int_hex(init->saved_regs.rsp, 8); + terminal::put_string_sz("\n "); + terminal::put_int_decimal(init->count_mapped_vram_pages() * 2); + terminal::put_string_sz(" MiB userspace memory used\n"); + + print_mem(); + terminal::put_string_sz("switching to /bin/init.elf.\n"); + application::running_app = init; + start_user_mode(init->saved_regs.rip, init->saved_regs.rsp, init->p4_paddr); } diff --git a/kernel/paging.cpp b/kernel/paging.cpp index 8c27abc..17b61f4 100644 --- a/kernel/paging.cpp +++ b/kernel/paging.cpp @@ -10,9 +10,13 @@ extern "C" { namespace mercury::kernel::paging { static constexpr uint64_t kernel_vram_start = 0xffffffffc0000000; - static constexpr uint64_t kernel_vram_pages = 261888; - static constexpr uint64_t kernel_stack_bottom = 0xfffffffffff01000; - static constexpr uint64_t kernel_stack_top = 0xfffffffffffff000; + static constexpr uint64_t kernel_vram_end = 0xffffffffffe00000; + static constexpr uint64_t kernel_vram_pages = + (kernel_vram_end - kernel_vram_start) / 4096; + static constexpr uint64_t syscall_stack_bottom = 0xfffffffffff01000; + static constexpr uint64_t syscall_stack_top = 0xfffffffffffff000; + static constexpr uint64_t interrupt_stack_bottom = 0xffffffffffe01000; + static constexpr uint64_t interrupt_stack_top = 0xffffffffffeff000; static constexpr uint64_t pram_pages = 1 << 23; static uint64_t pram_usage_bitmap[pram_pages / 64]; @@ -32,25 +36,29 @@ namespace mercury::kernel::paging { [[gnu::aligned(4096)]] uint64_t kernel_p2[512]; [[gnu::aligned(4096)]] uint64_t kernel_p1s[512 * 512]; - static uint64_t encode_pte( - uint64_t addr, bool user, bool write, bool execute) { + uint64_t kernel_p4e; + + uint64_t encode_pte( + uint64_t addr, bool user, bool write, bool execute, bool ps + ) { return (addr & 0x0000ffffffffffff) | (execute ? 0 : (1ULL << 63)) - | (user << 2) | (write << 1) | 1; + | (ps << 7) | (user << 2) | (write << 1) | 1; } void init_kernel_page_tables(uint64_t kernel_offset) { __kernel_p4_paddr = (uint64_t)kernel_p4 - kernel_offset; for (int i = 0; i < 511; ++i) kernel_p4[i] = 0; - kernel_p4[511] = encode_pte( - (uint64_t)kernel_p3 - kernel_offset, false, true, true); + kernel_p4e = encode_pte( + (uint64_t)kernel_p3 - kernel_offset, false, true, true, false); + kernel_p4[511] = kernel_p4e; for (int i = 0; i < 511; ++i) kernel_p3[i] = 0; kernel_p3[511] = encode_pte( - (uint64_t)kernel_p2 - kernel_offset, false, true, true); + (uint64_t)kernel_p2 - kernel_offset, false, true, true, false); for (int i = 0; i < 512; ++i) kernel_p2[i] = encode_pte( - (uint64_t)kernel_p1s + 4096 * i - kernel_offset, false, true, true); + (uint64_t)kernel_p1s + 4096 * i - kernel_offset, false, true, true, false); for (int i = 0; i < 512 * 512; ++i) kernel_p1s[i] = 0; } @@ -58,14 +66,22 @@ namespace mercury::kernel::paging { void map_kernel_page( uint64_t paddr, uint64_t vaddr, bool write, bool execute) { uint64_t i = (vaddr - kernel_vram_start) / 4096; - kernel_p1s[i] = encode_pte(paddr, false, write, execute); + kernel_p1s[i] = encode_pte(paddr, false, write, execute, false); + } + + void unmap_kernel_page(uint64_t vaddr) { + uint64_t i = (vaddr - kernel_vram_start) / 4096; + kernel_p1s[i] = 0; + asm volatile ( + "mov %%cr3, %%rax\nmov %%rax, %%cr3" ::: "%rax" + ); } static uint64_t take_pram_page() { for (uint64_t i = 0; i < pram_pages / 64; ++i) - if (pram_usage_bitmap[i] != 0xffffffffffffffff) + if (~pram_usage_bitmap[i] != 0) for (int j = 0; j < 64; ++j) - if (!(pram_usage_bitmap[i] & (1ULL << j))) { + if (~pram_usage_bitmap[i] & (1ULL << j)) { pram_usage_bitmap[i] |= (1ULL << j); return 4096 * (i * 64 + j); } @@ -73,9 +89,27 @@ namespace mercury::kernel::paging { return 0; } - void map_kernel_stack() { - for (uint64_t vaddr = kernel_stack_bottom; - vaddr < kernel_stack_top; vaddr += 4096) + uint64_t take_2mib_pram_page() { + for (uint64_t i = 0; i < pram_pages / 512; ++i) { + for (int j = 0; j < 8; ++j) + if (pram_usage_bitmap[i * 8 + j] != 0) + goto next_i; + for (int j = 0; j < 8; ++j) + pram_usage_bitmap[i * 8 + j] = ~0ULL; + return 0x200000 * i; + next_i: + ; + } + //TODO: handle error + return 0; + } + + void map_kernel_stacks() { + for (uint64_t vaddr = syscall_stack_bottom; + vaddr < syscall_stack_top; vaddr += 4096) + map_kernel_page(take_pram_page(), vaddr, true, false); + for (uint64_t vaddr = interrupt_stack_bottom; + vaddr < interrupt_stack_top; vaddr += 4096) map_kernel_page(take_pram_page(), vaddr, true, false); } @@ -103,6 +137,12 @@ namespace mercury::kernel::paging { return (void *)vaddr; } + void map_new_kernel_page(uint64_t &vaddr_out, uint64_t &paddr_out) { + vaddr_out = find_unmapped_vram_region(1); + paddr_out = take_pram_page(); + map_kernel_page(paddr_out, vaddr_out, true, false); + } + uint64_t get_used_vram_page_count() { uint64_t count = 0; for (uint64_t i = 0; i < kernel_vram_pages; ++i) diff --git a/kernel/syscall.asm b/kernel/syscall.asm new file mode 100644 index 0000000..56be8a4 --- /dev/null +++ b/kernel/syscall.asm @@ -0,0 +1,160 @@ +bits 64 + +global load_gdt_and_idt +global start_user_mode + +section .rodata + +;0x28 picked to align with limine choice +;0x28 - kernel code +;0x30 - kernel data +;0x38 - user data +;0x40 - user code + +gdtr: + dw 0x47 + dq gdt + +gdt: + dq 0 + dq 0 + dq 0 + dq 0 + dq 0 + dq 0x00209b0000000000 + dq 0x00009b0000000000 + dq 0x0000fb0000000000 + dq 0x0020fb0000000000 + +idtr: + dw 4095 + dq idt + +idt: + times 256 - ($ - idt) / 16 dq 0 + +section .text + +load_gdt_and_idt: + lgdt [gdtr] + lidt [idtr] + ret + +extern syscall_encode_color + +encode_color_syscall: + call syscall_encode_color + mov edi, eax + xor rax, rax + mov eax, edi + xor rdi, rdi + xor rsi, rsi + xor rdx, rdx + jmp syscall_return + +extern syscall_get_fb_vaddr +extern syscall_get_fb_dims +extern syscall_get_fb_pitch + +get_framebuffer_syscall: + call syscall_get_fb_vaddr + push rax + call syscall_get_fb_dims + push rax + call syscall_get_fb_pitch + xor rsi, rsi + mov esi, eax + pop rdi + pop rax + xor rdx, rdx + jmp syscall_return + +extern syscall_copy_framebuffer + +draw_framebuffer_syscall: + call syscall_copy_framebuffer + xor rax, rax + xor rdi, rdi + xor rsi, rsi + xor rdx, rdx + jmp syscall_return + +bad_syscall: + xor rax, rax + xor rdi, rdi + xor rsi, rsi + xor rdx, rdx + jmp syscall_return + +syscall_entry: + mov r11, rsp + mov rsp, 0xfffffffffffff000 + push r11 + push rcx + + cmp rax, 0 + je encode_color_syscall + cmp rax, 1 + je get_framebuffer_syscall + cmp rax, 2 + je draw_framebuffer_syscall + jmp bad_syscall + +syscall_return: + xor r8, r8 + xor r9, r9 + xor r10, r10 + xor r11, r11 + or r11, 0x200 + pop rcx + pop rsp + + o64 sysret + +start_user_mode: +;intended rip in rdi +;intended rsp in rsi +;intended p4_paddr in rdx + + mov rax, rdx + mov cr3, rax + + ;efer <- efer | 0x1 + mov rcx, 0xc0000080 + rdmsr + or al, 1 + wrmsr + + ;lstar <- syscall_entry + mov rdx, syscall_entry + mov eax, edx + shr rdx, 32 + mov ecx, 0xc0000082 + wrmsr + + ;star <- 0x0030.0028.0000.0000 + mov edx, 0x00300028 + xor eax, eax + mov ecx, 0xc0000081 + wrmsr + + mov rcx, rdi + mov rsp, rsi + xor r11, r11 + or r11, 0x200 + + xor rax, rax + xor rbx, rbx + xor rdx, rdx + xor rdi, rdi + xor rsi, rsi + xor rbp, rbp + xor r8, r8 + xor r9, r9 + xor r10, r10 + xor r12, r12 + xor r13, r13 + xor r14, r14 + xor r15, r15 + + o64 sysret diff --git a/kernel/syscall.cpp b/kernel/syscall.cpp new file mode 100644 index 0000000..e91d12d --- /dev/null +++ b/kernel/syscall.cpp @@ -0,0 +1,46 @@ +#include +#include +#include + +using namespace mercury::kernel; + +extern "C" uint32_t syscall_encode_color(uint32_t c) { + return (uint32_t)framebuffer::encode_color( + c & 0xff, (c >> 8) & 0xff, (c >> 16) & 0xff + ); +} + +extern "C" uint64_t syscall_get_fb_vaddr() { + auto *app = application::running_app; + if (app->framebuffer_vaddr != 0) + return app->framebuffer_vaddr; + uint64_t fb_len = framebuffer::dword_pitch * framebuffer::height * 4; + uint64_t fb_pages = (fb_len - 1) / 0x200000 + 1; + uint64_t vaddr = app->get_free_vaddr_pages(fb_pages); + for (uint64_t i = 0; i < fb_pages; ++i) { + uint64_t paddr = paging::take_2mib_pram_page(); + app->map_page(vaddr + i * 0x200000, paddr, true, false, true); + } + app->framebuffer_vaddr = vaddr; + return vaddr; +} + +extern "C" uint64_t syscall_get_fb_dims() { + return (uint64_t)(uint32_t)framebuffer::width + + ((uint64_t)(uint32_t)framebuffer::height << 32); +} + +extern "C" uint32_t syscall_get_fb_pitch() { + return (uint32_t)framebuffer::dword_pitch; +} + +extern "C" void syscall_copy_framebuffer() { + auto *app = application::running_app; + if (app->framebuffer_vaddr != 0) { + const uint32_t *source = (const uint32_t *)app->framebuffer_vaddr; + for (int y = 0; y < framebuffer::height; ++y) + for (int x = 0; x < framebuffer::width; ++x) + framebuffer::vaddr[y * framebuffer::dword_pitch + x] + = source[y * framebuffer::dword_pitch + x]; + } +} diff --git a/kernel/terminal.cpp b/kernel/terminal.cpp index 59e1ee5..1074fe7 100644 --- a/kernel/terminal.cpp +++ b/kernel/terminal.cpp @@ -108,4 +108,14 @@ namespace mercury::kernel::terminal { } + static char hex_digits[] = "0123456789abcdef"; + + void put_int_hex(uint64_t n, int digits, bool with_dots) { + for (int digit = digits - 1; digit >= 0; --digit) { + put_char(hex_digits[(n >> (digit * 4)) & 0xf]); + if (with_dots && digit % 4 == 0 && digit != 0) + put_char('.'); + } + } + } diff --git a/kernel/vfile.cpp b/kernel/vfile.cpp index 9f549e5..3b9dcb2 100644 --- a/kernel/vfile.cpp +++ b/kernel/vfile.cpp @@ -163,6 +163,13 @@ namespace mercury::kernel::vfile { } + storage::fs_result vfile::read_file( + uint64_t start, uint64_t length, void *into + ) const { + return bd->mounted_as->read_bytes_from_file( + dir_entry.node, start, length, into); + } + storage::fs_result lookup_path( const vfile &root, const canon_path &path, std::optional &out ) { diff --git a/makefile b/makefile index 82734bc..7b14369 100644 --- a/makefile +++ b/makefile @@ -1,6 +1,12 @@ -GPP_ARGS = -Wall -Wextra -O3 -ggdb -I include -ffreestanding -fno-exceptions -fno-rtti -mno-sse -KERNEL_OBJECTS = allocator.cpp entry.cpp framebuffer.cpp paging.asm paging.cpp storage.cpp \ - storage/bd/memory.cpp storage/fs/tarfs.cpp terminal.cpp utility.cpp vfile.cpp +GPP_ARGS = -Wall -Wextra -O3 -ggdb -I include \ + -ffreestanding -fno-exceptions -fno-rtti -mno-sse +KGPP_ARGS = ${GPP_ARGS} +AGPP_ARGS = ${GPP_ARGS} + +LD_ARGS = -z noexecstack +KLD_ARGS = -T kernel/link.ld ${LD_ARGS} +ALD_ARGS = -T applications/link.ld ${LD_ARGS} +LLD_ARGS = ${LD_ARGS} all: out/disk.iso @@ -15,37 +21,61 @@ dist-clean: rm -f include/mercury/kernel/limine.hpp limine: - git clone --depth=1 -b v6.x-branch https://github.com/limine-bootloader/limine.git limine + git clone --depth=1 -b v6.x-branch \ + https://github.com/limine-bootloader/limine.git limine cd limine && ./bootstrap && ./configure -q --enable-bios --enable-bios-cd +make -C limine cp limine/limine.h include/mercury/kernel/limine.hpp obj/kernel/entry.cpp.o: kernel/entry.cpp limine @mkdir -p $(@D) - g++ -c ${GPP_ARGS} $< -o $@ + g++ -c ${KGPP_ARGS} $< -o $@ obj/kernel/%.cpp.o: kernel/%.cpp @mkdir -p $(@D) - g++ -c ${GPP_ARGS} $< -o $@ + g++ -c ${KGPP_ARGS} $< -o $@ obj/kernel/%.asm.o: kernel/%.asm @mkdir -p $(@D) nasm -f elf64 $< -o $@ +KERNEL_OBJECTS = allocator.cpp application.cpp entry.cpp framebuffer.cpp \ + paging.asm paging.cpp storage.cpp storage/bd/memory.cpp terminal.cpp \ + storage/fs/tarfs.cpp utility.cpp vfile.cpp syscall.asm syscall.cpp obj/kernel.elf: ${KERNEL_OBJECTS:%=obj/kernel/%.o} - ld -T kernel/link.ld $^ -o $@ + ld ${KLD_ARGS} $^ -o $@ + +obj/stdlib/%.asm.o: stdlib/%.asm + @mkdir -p $(@D) + nasm -f elf64 $< -o $@ + +obj/stdlib/%.cpp.o: stdlib/%.cpp + @mkdir -p $(@D) + g++ -c ${AGPP_ARGS} $< -o $@ + +STDLIB_OBJECTS = entry.cpp syscall.asm +obj/stdlib.o: ${STDLIB_OBJECTS:%=obj/stdlib/%.o} + ld -r ${LLD_ARGS} $^ -o $@ + +obj/%.cpp.o: applications/%.cpp + @mkdir -p $(@D) + g++ -c ${AGPP_ARGS} $< -o $@ + +INIT_OBJECTS = main.cpp +obj/initfs/bin/init.elf: ${INIT_OBJECTS:%=obj/init/%.o} obj/stdlib.o + @mkdir -p $(@D) + ld ${ALD_ARGS} $^ -o $@ -obj/initfs.tgz: - @mkdir -p obj/initfs - echo test > obj/initfs/test.txt - mkdir -p obj/initfs/dir/dir2 - ln -s ../test.txt obj/initfs/dir/test2.txt +APPLICATIONS = init +obj/initfs.tgz: ${APPLICATIONS:%=obj/initfs/bin/%.elf} tar czf obj/initfs.tgz -C obj/initfs . out/disk.iso: obj/kernel.elf obj/initfs.tgz limine mkdir -p obj/iso out - cp obj/kernel.elf obj/initfs.tgz limine/bin/limine-bios.sys limine/bin/limine-bios-cd.bin limine.cfg obj/iso/ + cp obj/kernel.elf obj/initfs.tgz limine/bin/limine-bios.sys \ + limine/bin/limine-bios-cd.bin limine.cfg obj/iso/ cp terminus/ter-u16b.psf obj/iso/termfont.psf - xorriso -as mkisofs -quiet -no-emul-boot -boot-info-table -boot-load-size 4 -b limine-bios-cd.bin obj/iso -o $@ + xorriso -as mkisofs -quiet -no-emul-boot -boot-info-table \ + -boot-load-size 4 -b limine-bios-cd.bin obj/iso -o $@ limine/bin/limine bios-install $@ rm -rf obj/iso diff --git a/qemu.gdb b/qemu.gdb index cbd1734..d27659b 100644 --- a/qemu.gdb +++ b/qemu.gdb @@ -1,6 +1,8 @@ target remote | qemu-system-x86_64 -gdb stdio -cdrom out/disk.iso -boot d symbol-file obj/kernel.elf +add-symbol-file obj/initfs/bin/init.elf set disassembly-flavor intel set print asm-demangle on break entry +break main layout src diff --git a/stdlib/entry.cpp b/stdlib/entry.cpp new file mode 100644 index 0000000..5557c8a --- /dev/null +++ b/stdlib/entry.cpp @@ -0,0 +1,14 @@ +int main(int argc, char **argv); + +extern "C" [[noreturn]] void _entry() { + //TODO: get command line via system call and populate argc and argv. + int argc = 0; + char **argv = 0; + + int result = main(argc, argv); + + //TODO: exit via system call and return result. + (void)result; + while (1) + ; +} diff --git a/stdlib/syscall.asm b/stdlib/syscall.asm new file mode 100644 index 0000000..eba7ec0 --- /dev/null +++ b/stdlib/syscall.asm @@ -0,0 +1,35 @@ +bits 64 + +global encode_color +global get_framebuffer +global draw_framebuffer + +section .text + +encode_color: + mov rax, 0 + syscall + ret + +get_framebuffer: + push rcx + push rdx + push rsi + push rdi + mov rax, 1 + syscall + pop rcx + mov qword [rcx], rax + pop rcx + mov dword [rcx], edi + shr rdi, 32 + pop rcx + mov dword [rcx], edi + pop rcx + mov dword [rcx], esi + ret + +draw_framebuffer: + mov rax, 2 + syscall + ret -- cgit v1.2.3