diff options
-rw-r--r-- | .gitignore | 5 | ||||
-rw-r--r-- | documentation/memory.txt | 7 | ||||
-rw-r--r-- | include/mercury/kernel/framebuffer.hpp | 27 | ||||
-rw-r--r-- | include/mercury/kernel/paging.hpp | 34 | ||||
-rw-r--r-- | include/mercury/kernel/terminal.hpp | 30 | ||||
-rw-r--r-- | include/mercury/kernel/utility.hpp | 124 | ||||
-rw-r--r-- | kernel/entry.cpp | 212 | ||||
-rw-r--r-- | kernel/framebuffer.cpp | 59 | ||||
-rw-r--r-- | kernel/link.ld | 47 | ||||
-rw-r--r-- | kernel/paging.asm | 16 | ||||
-rw-r--r-- | kernel/paging.cpp | 115 | ||||
-rw-r--r-- | kernel/terminal.cpp | 106 | ||||
-rw-r--r-- | kernel/utility.cpp | 45 | ||||
-rw-r--r-- | license.txt | 13 | ||||
-rw-r--r-- | limine.cfg | 5 | ||||
-rw-r--r-- | makefile | 48 | ||||
-rw-r--r-- | qemu.gdb | 6 | ||||
-rw-r--r-- | readme.txt | 6 | ||||
-rw-r--r-- | terminus/license.txt | 94 | ||||
-rw-r--r-- | terminus/readme.txt | 5 | ||||
-rw-r--r-- | terminus/ter-u16b.psf | bin | 0 -> 5140 bytes |
21 files changed, 1004 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..24524ee --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.vscode/ +include/mercury/kernel/limine.hpp +limine +obj +out diff --git a/documentation/memory.txt b/documentation/memory.txt new file mode 100644 index 0000000..e820034 --- /dev/null +++ b/documentation/memory.txt @@ -0,0 +1,7 @@ +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. + +if any of these facts change, the linker script and paging code should be +checked closely. diff --git a/include/mercury/kernel/framebuffer.hpp b/include/mercury/kernel/framebuffer.hpp new file mode 100644 index 0000000..5c6ca23 --- /dev/null +++ b/include/mercury/kernel/framebuffer.hpp @@ -0,0 +1,27 @@ +#ifndef MERCURY_KERNEL_FRAMEBUFFER_HPP +#define MERCURY_KERNEL_FRAMEBUFFER_HPP + +#include <cstdint> + +namespace mercury::kernel::framebuffer { + + extern int width; + extern int height; + + void init_framebuffer(uint64_t vaddr, uint64_t width, uint64_t height, uint64_t pitch); + + typedef uint32_t color; + color encode_color(uint8_t r, uint8_t g, uint8_t b); + + void set_pixel(int x, int y, color c); + + //[from_start_x, from_end_x) x [from_start_y, from_end_y) -> [to_start_x, ...) x [to_start_y, ...) + //we assume from_start_x < from_end_x and from_start_y < from_end_y + void move_region(int from_start_x, int from_start_y, int from_end_x, int from_end_y, int to_start_x, int to_start_y); + + //[start_x, end_x) x [start_y, end_y) + void fill_region(int start_x, int start_y, int end_x, int end_y, color c); + +} + +#endif diff --git a/include/mercury/kernel/paging.hpp b/include/mercury/kernel/paging.hpp new file mode 100644 index 0000000..9627162 --- /dev/null +++ b/include/mercury/kernel/paging.hpp @@ -0,0 +1,34 @@ +#ifndef MERCURY_KERNEL_PAGING_HPP +#define MERCURY_KERNEL_PAGING_HPP + +#include <cstdint> + +//in paging.asm +extern "C" [[noreturn]] void switch_to_kernel_p4(void (*and_then_jump_to)()); + +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); + + void init_kernel_page_tables(uint64_t kernel_offset); + + void map_kernel_stack(); + + //assumes page-alignment + void map_kernel_page( + uint64_t paddr, uint64_t vaddr, bool write, bool execute); + + uint64_t get_used_vram_page_count(); + uint64_t get_free_pram_page_count(); + +} + +#endif diff --git a/include/mercury/kernel/terminal.hpp b/include/mercury/kernel/terminal.hpp new file mode 100644 index 0000000..b9d9976 --- /dev/null +++ b/include/mercury/kernel/terminal.hpp @@ -0,0 +1,30 @@ +#ifndef MERCURY_KERNEL_TERMINAL_HPP +#define MERCURY_KERNEL_TERMINAL_HPP + +#include <cstdint> + +namespace mercury::kernel::terminal { + + extern uint8_t *termfont; + extern uint64_t termfont_len; + + void init_terminal(); + + extern int width; + extern int height; + + extern int cursor_x; + extern int cursor_y; + + extern framebuffer::color bg_color; + extern framebuffer::color fg_color; + + void put_char(char ch); + + void put_string_sz(const char *str); + + void put_int_decimal(uint64_t n, bool with_commas = true); + +} + +#endif diff --git a/include/mercury/kernel/utility.hpp b/include/mercury/kernel/utility.hpp new file mode 100644 index 0000000..dbf0cf9 --- /dev/null +++ b/include/mercury/kernel/utility.hpp @@ -0,0 +1,124 @@ +#ifndef MERCURY_KERNEL_UTILITY_HPP +#define MERCURY_KERNEL_UTILITY_HPP + +#include <optional> +#include <cstdint> + +namespace mercury::kernel::utility { + + template <class t> + static inline t min(t a, t b) { + return a < b ? a : b; + } + + //includes start_i, does not include end_i + void mark_bitmap_region_zero(uint64_t *bitmap, uint64_t start_i, uint64_t end_i); + //includes start_i, does not include end_i + void mark_bitmap_region_one(uint64_t *bitmap, uint64_t start_i, uint64_t end_i); + + struct uuid { + uint8_t bytes[16]; + }; + + template <class value_t> + struct string_tree { + + std::optional<value_t> value_here = {}; + string_tree<value_t> *parent = 0; + string_tree<value_t> *subtrees[256] = {}; + + //if there is a node whose key has a prefix in common with str, then this + //returns the deepest such node, and sets leftover_out to point to the + //character of str after the common prefix with that returned node. if + //there is no such node, this returns the current node and sets + //leftover_out to a null pointer. + string_tree &max_common_prefix( + const char *str, size_t str_len, const char *&leftover_out) { + + string_tree<value_t> *last_with_value = 0; + const char *leftover_at_last_with_value = 0; + string_tree<value_t> *on = this; + size_t len_so_far = 0; + + while (true) { + + if (on->value_here) { + last_with_value = on; + leftover_at_last_with_value = str + len_so_far; + } + + if (len_so_far == str_len) + break; + + on = on->subtrees[(uint8_t)str[len_so_far]]; + if (!on) + break; + + ++len_so_far; + + } + + if (last_with_value) { + leftover_out = leftover_at_last_with_value; + return *last_with_value; + } + + leftover_out = 0; + return *this; + + } + + bool try_insert(const char *str, size_t str_len, value_t value) { + + string_tree<value_t> *on = this; + for (size_t i = 0; i < str_len; ++i) { + string_tree<value_t> *&subtree = on->subtrees[(uint8_t)str[i]]; + if (!subtree) { + subtree = new string_tree<value_t>(); + subtree->parent = on; + } + on = subtree; + } + + if (on->value_here) + return false; + on->value_here = value; + return true; + + } + + }; + + //if c appears in str, this returns the index of the first time it appears. + //otherwise, this returns len. + static inline size_t find(const char *str, size_t len, char c) { + for (size_t i = 0; i < len; ++i) + if (str[i] == c) + return i; + return len; + } + + template <class value_t> + struct flist { + + struct node { + value_t value; + node *next; + }; + + node *first; + + flist() : first(0) {} + + void insert(value_t value) { + first = new node { + .value = value, + .next = first + }; + } + + }; + +} + +#endif diff --git a/kernel/entry.cpp b/kernel/entry.cpp new file mode 100644 index 0000000..c8f74c8 --- /dev/null +++ b/kernel/entry.cpp @@ -0,0 +1,212 @@ +#include <mercury/kernel/framebuffer.hpp> +#include <mercury/kernel/terminal.hpp> +#include <mercury/kernel/limine.hpp> +#include <mercury/kernel/paging.hpp> + +using namespace mercury::kernel; + +LIMINE_BASE_REVISION(1) + +static volatile limine_memmap_request memmap_request { + .id = LIMINE_MEMMAP_REQUEST, + .revision = 0, + .response = 0 +}; + +static volatile limine_kernel_address_request kernel_address_request { + .id = LIMINE_KERNEL_ADDRESS_REQUEST, + .revision = 0, + .response = 0 +}; + +static volatile limine_framebuffer_request framebuffer_request { + .id = LIMINE_FRAMEBUFFER_REQUEST, + .revision = 0, + .response = 0 +}; + +static volatile limine_hhdm_request hhdm_request { + .id = LIMINE_HHDM_REQUEST, + .revision = 0, + .response = 0 +}; + +static limine_internal_module initfs_module = { + .path = "initfs.tgz", + .cmdline = "initfs", + .flags = LIMINE_INTERNAL_MODULE_REQUIRED | LIMINE_INTERNAL_MODULE_COMPRESSED +}; + +static limine_internal_module termfont_module = { + .path = "termfont.psf", + .cmdline = "termfont", + .flags = LIMINE_INTERNAL_MODULE_REQUIRED +}; + +static limine_internal_module *internal_modules[] = { + &initfs_module, &termfont_module +}; + +static volatile limine_module_request module_request = { + .id = LIMINE_MODULE_REQUEST, + .revision = 2, + .response = 0, + .internal_module_count = 2, + .internal_modules = internal_modules +}; + +bool try_map_module_by_cmdline( + const char *cmdline, void *&vaddr_out, uint64_t &len_out +) { + auto response = module_request.response; + for (uint64_t i = 0; i < response->module_count; ++i) { + limine_file *file = response->modules[i]; + for (uint64_t j = 0; cmdline[j] == file->cmdline[j]; ++j) + if (!cmdline[j]) { + + //module start is guaranteed to be page-aligned, end is not. + uint64_t start_paddr = + (uint64_t)file->address - hhdm_request.response->offset; + uint64_t end_paddr = + ((start_paddr + file->size - 1) / 4096 + 1) * 4096; + + uint64_t start_vaddr = + paging::find_unmapped_vram_region((end_paddr - start_paddr) / 4096); + for (uint64_t i = 0; i < end_paddr - start_paddr; i += 4096) + paging::map_kernel_page( + start_paddr + i, start_vaddr + i, true, false); + + vaddr_out = (void *)start_vaddr; + len_out = file->size; + + return true; + } + } + return false; +} + +//defined in linker script, page-aligned: +extern uint8_t __kernel_start; +extern uint8_t __kernel_end; +extern uint8_t __kernel_rx_start; +extern uint8_t __kernel_rx_end; +extern uint8_t __kernel_ro_start; +extern uint8_t __kernel_ro_end; +extern uint8_t __kernel_rw_start; +extern uint8_t __kernel_rw_end; + +uint8_t *initfs; +uint64_t initfs_len; + +[[noreturn]] static void with_kernel_p4(); + +extern "C" [[noreturn]] void entry() { + + //TODO?: maybe we should check if the limine requests were + // fulfilled and display some error message if not + + //set up the physical memory usage bitmap: + + 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 + //reclaimable are guaranteed by limine spec to be page-aligned. + auto entry = memmap->entries[i]; + if (entry->type == LIMINE_MEMMAP_USABLE || + entry->type == LIMINE_MEMMAP_BOOTLOADER_RECLAIMABLE) + paging::mark_pram_region_free(entry->base, entry->base + entry->length); + } + + //set up page mappings: + + auto kernel_address = kernel_address_request.response; + uint64_t kernel_offset = kernel_address->virtual_base + - kernel_address->physical_base; + + uint64_t hhdm = hhdm_request.response->offset; + + //framebuffer might not be page-aligned + auto framebuffer = framebuffer_request.response->framebuffers[0]; + uint64_t fb_start = ((uint64_t)framebuffer->address / 4096) * 4096 - hhdm; + uint64_t fb_end = (uint64_t)framebuffer->address + + framebuffer->pitch * framebuffer->height - hhdm; + fb_end = ((fb_end - 1) / 4096 + 1) * 4096; + + paging::init_kernel_page_tables(kernel_offset); + + //kernel image rx + for (uint64_t vaddr = (uint64_t)&__kernel_rx_start; + vaddr < (uint64_t)&__kernel_rx_end; vaddr += 4096) + paging::map_kernel_page(vaddr - kernel_offset, vaddr, false, true); + + //kernel image ro + for (uint64_t vaddr = (uint64_t)&__kernel_ro_start; + vaddr < (uint64_t)&__kernel_ro_end; vaddr += 4096) + paging::map_kernel_page(vaddr - kernel_offset, vaddr, false, false); + + //kernel image rw + for (uint64_t vaddr = (uint64_t)&__kernel_rw_start; + vaddr < (uint64_t)&__kernel_rw_end; vaddr += 4096) + paging::map_kernel_page(vaddr - kernel_offset, vaddr, true, false); + + //framebuffer + uint64_t fb_vaddr = + paging::find_unmapped_vram_region((fb_end - fb_start) / 4096); + for (uint64_t i = 0; i < fb_end - fb_start; i += 4096) + paging::map_kernel_page(fb_start + i, fb_vaddr + i, true, false); + + //initfs and termfont - these are required modules + //so there is no worry about them not being present. + try_map_module_by_cmdline("initfs", (void *&)initfs, initfs_len); + try_map_module_by_cmdline( + "termfont", (void *&)terminal::termfont, terminal::termfont_len); + + //set up framebuffer and terminal: + //TODO: assumes framebuffer is 32-bpp rgb + + framebuffer::init_framebuffer( + fb_vaddr, framebuffer->width, framebuffer->height, framebuffer->pitch); + + //switch to kernel p4 + + paging::map_kernel_stack(); + switch_to_kernel_p4(&with_kernel_p4); + +} + +[[noreturn]] static void with_kernel_p4() { + + terminal::init_terminal(); + 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; + + 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"); + + terminal::put_string_sz("initfs first sector:"); + for (int y = 0; y < 8; ++y) { + terminal::put_string_sz("\n "); + for (int x = 0; x < 64; ++x) + terminal::put_char((char)initfs[y * 64 + x]); + } + + terminal::put_string_sz("\ninitfs second sector:"); + for (int y = 0; y < 8; ++y) { + terminal::put_string_sz("\n "); + for (int x = 0; x < 64; ++x) + terminal::put_char((char)initfs[512 + y * 64 + x]); + } + + while (1) + ; + + //TODO + +} diff --git a/kernel/framebuffer.cpp b/kernel/framebuffer.cpp new file mode 100644 index 0000000..a115e0b --- /dev/null +++ b/kernel/framebuffer.cpp @@ -0,0 +1,59 @@ +#include <mercury/kernel/framebuffer.hpp> + +namespace mercury::kernel::framebuffer { + + uint32_t *vaddr; + int width; + int height; + int dword_pitch; + + void init_framebuffer( + uint64_t vaddr, uint64_t width, uint64_t height, uint64_t pitch + ) { + //TODO: assumes 32-bpp rgb + framebuffer::vaddr = (uint32_t *)vaddr; + framebuffer::width = width; + framebuffer::height = height; + dword_pitch = pitch / 4; + } + + color encode_color(uint8_t r, uint8_t g, uint8_t b) { + return ((uint32_t)r << 16) | ((uint32_t)g << 8) | (uint32_t)b; + } + + void set_pixel(int x, int y, color c) { + vaddr[y * dword_pitch + x] = c; + } + + void move_region( + int from_start_x, int from_start_y, int from_end_x, + int from_end_y, int to_start_x, int to_start_y + ) { + + int region_width = from_end_x - from_start_x; + int region_height = from_end_y - from_start_y; + + int from_start_offset = from_start_y * dword_pitch + from_start_x; + int to_start_offset = to_start_y * dword_pitch + to_start_x; + + if (from_start_offset > to_start_offset) + for (int y = 0; y < region_height; ++y) + for (int x = 0; x < region_width; ++x) + vaddr[to_start_offset + y * dword_pitch + x] = + vaddr[from_start_offset + y * dword_pitch + x]; + + else if (from_start_offset < to_start_offset) + for (int y = region_height - 1; y >= 0; --y) + for (int x = region_width - 1; x >= 0; --x) + vaddr[to_start_offset + y * dword_pitch + x] = + vaddr[from_start_offset + y * dword_pitch + x]; + + } + + void fill_region(int start_x, int start_y, int end_x, int end_y, color c) { + for (int y = start_y; y < end_y; ++y) + for (int x = start_x; x < end_x; ++x) + vaddr[y * dword_pitch + x] = c; + } + +} diff --git a/kernel/link.ld b/kernel/link.ld new file mode 100644 index 0000000..884e633 --- /dev/null +++ b/kernel/link.ld @@ -0,0 +1,47 @@ +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 */ + . = 0xffffffffc0000000; + __kernel_rx_start = .; + + .text : { + *(.text .text.*) + } : rx + + . = ALIGN(4096); + __kernel_rx_end = .; + __kernel_ro_start = .; + + .rodata : { + *(.rodata .rodata.*) + } : ro + + . = ALIGN(4096); + __kernel_ro_end = .; + __kernel_rw_start = .; + + .data : { + *(.data .data.*) + } : rw + + . = ALIGN(4096); + .bss : { + *(.bss .bss.*) + *(COMMON) + } : rw + + . = ALIGN(4096); + __kernel_rw_end = .; + +} diff --git a/kernel/paging.asm b/kernel/paging.asm new file mode 100644 index 0000000..f1047a9 --- /dev/null +++ b/kernel/paging.asm @@ -0,0 +1,16 @@ +bits 64 + +;see also ../documentation/memory.txt + +global switch_to_kernel_p4 + +;from paging.cpp: +extern __kernel_p4_paddr + +section .text + +switch_to_kernel_p4: + mov rax, qword [__kernel_p4_paddr] + mov cr3, rax + mov rsp, 0xfffffffffffff000 + jmp rdi diff --git a/kernel/paging.cpp b/kernel/paging.cpp new file mode 100644 index 0000000..3bd27d0 --- /dev/null +++ b/kernel/paging.cpp @@ -0,0 +1,115 @@ +#include <mercury/kernel/utility.hpp> +#include <mercury/kernel/paging.hpp> + +//see also ../documentation/memory.txt + +extern "C" { + uint64_t __kernel_p4_paddr; +} + +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 pram_pages = 1 << 23; + static uint64_t pram_usage_bitmap[pram_pages / 64]; + + void mark_all_pram_used() { + utility::mark_bitmap_region_one(pram_usage_bitmap, 0, pram_pages); + } + + void mark_pram_region_free(uint64_t start_addr, uint64_t end_addr) { + utility::mark_bitmap_region_zero( + pram_usage_bitmap, start_addr / 4096, + utility::min(end_addr / 4096, pram_pages)); + } + + [[gnu::aligned(4096)]] uint64_t kernel_p4[512]; + [[gnu::aligned(4096)]] uint64_t kernel_p3[512]; + [[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) { + return (addr & 0x0000ffffffffffff) | (execute ? 0 : (1ULL << 63)) + | (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); + 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); + for (int i = 0; i < 512; ++i) + kernel_p2[i] = encode_pte( + (uint64_t)kernel_p1s + 4096 * i - kernel_offset, false, true, true); + for (int i = 0; i < 512 * 512; ++i) + kernel_p1s[i] = 0; + } + + 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); + } + + static uint64_t take_pram_page() { + for (uint64_t i = 0; i < pram_pages / 64; ++i) + if (pram_usage_bitmap[i] != 0xffffffffffffffff) + for (int j = 0; j < 64; ++j) + if (!(pram_usage_bitmap[i] & (1ULL << j))) { + pram_usage_bitmap[i] |= (1ULL << j); + return 4096 * (i * 64 + j); + } + //TODO: handle error + return 0; + } + + void map_kernel_stack() { + for (uint64_t vaddr = kernel_stack_bottom; + vaddr < kernel_stack_top; vaddr += 4096) + map_kernel_page(take_pram_page(), vaddr, true, false); + } + + uint64_t find_unmapped_vram_region(uint64_t page_count) { + uint64_t start = 0; + uint64_t len = 0; + for (uint64_t i = 0; i < kernel_vram_pages; ++i) + if (kernel_p1s[i] == 0) { + ++len; + if (len == page_count) + return start * 4096 + kernel_vram_start; + } + else { + start = i + 1; + len = 0; + } + //TODO: handle error + return 0; + } + + uint64_t get_used_vram_page_count() { + uint64_t count = 0; + for (uint64_t i = 0; i < kernel_vram_pages; ++i) + if (kernel_p1s[i] != 0) + ++count; + return count; + } + + uint64_t get_free_pram_page_count() { + uint64_t used_count = 0; + for (uint64_t i = 0; i < pram_pages / 64; ++i) + for (uint64_t j = 0; j < 64; ++j) + used_count += (pram_usage_bitmap[i] >> j) & 1; + return pram_pages - used_count; + } + +} diff --git a/kernel/terminal.cpp b/kernel/terminal.cpp new file mode 100644 index 0000000..f017cad --- /dev/null +++ b/kernel/terminal.cpp @@ -0,0 +1,106 @@ +#include <mercury/kernel/framebuffer.hpp> +#include <mercury/kernel/terminal.hpp> + +namespace mercury::kernel::terminal { + + uint8_t *termfont; + uint64_t termfont_len; + + int width; + int height; + + int cursor_x; + int cursor_y; + + framebuffer::color bg_color; + framebuffer::color fg_color; + + static uint8_t glyph_height; + + void init_terminal() { + //TODO - verify that termfont fits inside termfont_len (i.e. that no other + // functions in this file will try to access memory outside termfont) + //TODO - check magic header to verify that this is actually a font and to + // see whether this is a psf1 font or a psf2 font. + //TODO - support psf2 fonts. currently psf1 is assumed. + //TODO - read unicode table if there is one. currently it is assumed that + // all 256 codepoints have glyphs, and that they appear in order. + + glyph_height = termfont[3]; + width = framebuffer::width / 8; + height = framebuffer::height / glyph_height; + cursor_x = 0; + cursor_y = 0; + bg_color = framebuffer::encode_color(0, 0, 0); + fg_color = framebuffer::encode_color(255, 255, 255); + + } + + static void cursor_down() { + if (++cursor_y == height) { + --cursor_y; + framebuffer::move_region( + 0, glyph_height, width * 8, height * glyph_height, 0, 0); + framebuffer::fill_region(0, (height - 1) * glyph_height, + width * 8, height * glyph_height, bg_color); + } + } + + static void cursor_right() { + if (++cursor_x == width) { + cursor_x = 0; + cursor_down(); + } + } + + void draw_char(char ch, int x, int y) { + const uint8_t *glyph = termfont + 4 + glyph_height * (unsigned)ch; + for (int i = 0; i < glyph_height; ++i) + for (int j = 0; j < 8; ++j) + framebuffer::set_pixel(x * 8 + j, y * glyph_height + i, + ((glyph[i] << j) & 0x80) ? fg_color : bg_color); + } + + void put_char(char ch) { + switch (ch) { + case '\n': + cursor_x = 0; + cursor_down(); + break; + default: + draw_char(ch, cursor_x, cursor_y); + cursor_right(); + break; + } + } + + void put_string_sz(const char *str) { + for (int i = 0; str[i]; ++i) + put_char(str[i]); + } + + void put_int_decimal(uint64_t n, bool with_commas) { + + if (n == 0) { + put_char('0'); + return; + } + + uint64_t d = 1; + int i = 0; + while (d <= n / 10) { + d *= 10; + ++i; + } + + while (d) { + put_char('0' + ((n / d) % 10)); + d /= 10; + if (with_commas && (i % 3 == 0) && (i != 0)) + put_char(','); + --i; + } + + } + +} diff --git a/kernel/utility.cpp b/kernel/utility.cpp new file mode 100644 index 0000000..865b817 --- /dev/null +++ b/kernel/utility.cpp @@ -0,0 +1,45 @@ +#include <mercury/kernel/utility.hpp> + +namespace mercury::kernel::utility { + + void mark_bitmap_region_zero( + uint64_t *bitmap, uint64_t start_i, uint64_t end_i) { + + if (start_i % 64 != 0) { + uint64_t keep = (1 << (start_i % 64)) - 1; + bitmap[start_i / 64] &= keep; + start_i = (start_i / 64 + 1) * 64; + } + + if (end_i % 64 != 0) { + uint64_t replace = (1 << (end_i % 64)) - 1; + bitmap[end_i / 64] &= ~replace; + end_i = (end_i / 64) * 64; + } + + for (uint64_t i = start_i / 64; i < end_i / 64; ++i) + bitmap[i] = 0; + + } + + void mark_bitmap_region_one( + uint64_t *bitmap, uint64_t start_i, uint64_t end_i) { + + if (start_i % 64 != 0) { + uint64_t keep = (1 << (start_i % 64)) - 1; + bitmap[start_i / 64] |= ~keep; + start_i = (start_i / 64 + 1) * 64; + } + + if (end_i % 64 != 0) { + uint64_t replace = (1 << (end_i % 64)) - 1; + bitmap[end_i / 64] |= replace; + end_i = (end_i / 64) * 64; + } + + for (uint64_t i = start_i / 64; i < end_i / 64; ++i) + bitmap[i] = 0xffffffffffffffff; + + } + +} diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..9699d94 --- /dev/null +++ b/license.txt @@ -0,0 +1,13 @@ +Copyright 2024 Benji Dial + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/limine.cfg b/limine.cfg new file mode 100644 index 0000000..505c8f4 --- /dev/null +++ b/limine.cfg @@ -0,0 +1,5 @@ +TIMEOUT=0 + +:Mercury +PROTOCOL=limine +KERNEL_PATH=boot:///kernel.elf diff --git a/makefile b/makefile new file mode 100644 index 0000000..9497d2c --- /dev/null +++ b/makefile @@ -0,0 +1,48 @@ +GPP_ARGS = -Wall -Wextra -O3 -ggdb -I include -ffreestanding -mno-sse +KERNEL_OBJECTS = entry.cpp framebuffer.cpp paging.asm paging.cpp terminal.cpp utility.cpp + +all: out/disk.iso + +run: out/disk.iso + gdb -x qemu.gdb + +clean: + rm -rf obj out + +dist-clean: + rm -rf limine + rm -f include/mercury/kernel/limine.hpp + +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 $@ + +obj/kernel/%.cpp.o: kernel/%.cpp + @mkdir -p $(@D) + g++ -c ${GPP_ARGS} $< -o $@ + +obj/kernel/%.asm.o: kernel/%.asm + @mkdir -p $(@D) + nasm -f elf64 $< -o $@ + +obj/kernel.elf: ${KERNEL_OBJECTS:%=obj/kernel/%.o} + ld -T kernel/link.ld $^ -o $@ + +obj/initfs.tgz: + @mkdir -p obj/initfs + echo test > obj/initfs/test.txt + cd obj/initfs && tar czf ../initfs.tgz * + +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 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 $@ + limine/bin/limine bios-install $@ + rm -rf obj/iso diff --git a/qemu.gdb b/qemu.gdb new file mode 100644 index 0000000..d9081bf --- /dev/null +++ b/qemu.gdb @@ -0,0 +1,6 @@ +target remote | qemu-system-x86_64 -gdb stdio -cdrom out/disk.iso -boot d +symbol-file obj/kernel.elf +set disassembly-flavor intel +set print asm-demangle on +break entry +layout split diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..7f4a2e2 --- /dev/null +++ b/readme.txt @@ -0,0 +1,6 @@ +mercury is a 64-bit hobby operating system. to build and test it, you will need +some dependencies. these can be installed on debian with: + apt install g++ gcc gdb git make nasm qemu-system-x86 xorriso +then, just run "make -jx", replacing x with the number of threads to use while +building. this will create a bios-bootable disk image in out/disk.iso. you can +then test it in qemu with gdb attached by running "make run". diff --git a/terminus/license.txt b/terminus/license.txt new file mode 100644 index 0000000..5168372 --- /dev/null +++ b/terminus/license.txt @@ -0,0 +1,94 @@ +Copyright (C) 2020 Dimitar Toshkov Zhekov,
+with Reserved Font Name "Terminus Font".
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/terminus/readme.txt b/terminus/readme.txt new file mode 100644 index 0000000..dcf0163 --- /dev/null +++ b/terminus/readme.txt @@ -0,0 +1,5 @@ +the file ter-u16b.psf contains the terminus font, version 4.49.1, at 8x16 +bold with the td1 patch applied. terminus is licensed under the sil open +font license, version 1.1, which is in the file license.txt. + +terminus home page: https://terminus-font.sourceforge.net/ diff --git a/terminus/ter-u16b.psf b/terminus/ter-u16b.psf Binary files differnew file mode 100644 index 0000000..3215b16 --- /dev/null +++ b/terminus/ter-u16b.psf |