summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore5
-rw-r--r--documentation/memory.txt7
-rw-r--r--include/mercury/kernel/framebuffer.hpp27
-rw-r--r--include/mercury/kernel/paging.hpp34
-rw-r--r--include/mercury/kernel/terminal.hpp30
-rw-r--r--include/mercury/kernel/utility.hpp124
-rw-r--r--kernel/entry.cpp212
-rw-r--r--kernel/framebuffer.cpp59
-rw-r--r--kernel/link.ld47
-rw-r--r--kernel/paging.asm16
-rw-r--r--kernel/paging.cpp115
-rw-r--r--kernel/terminal.cpp106
-rw-r--r--kernel/utility.cpp45
-rw-r--r--license.txt13
-rw-r--r--limine.cfg5
-rw-r--r--makefile48
-rw-r--r--qemu.gdb6
-rw-r--r--readme.txt6
-rw-r--r--terminus/license.txt94
-rw-r--r--terminus/readme.txt5
-rw-r--r--terminus/ter-u16b.psfbin0 -> 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
new file mode 100644
index 0000000..3215b16
--- /dev/null
+++ b/terminus/ter-u16b.psf
Binary files differ