Initial commit

This commit is contained in:
2026-02-04 01:58:49 +01:00
commit 0e01345abf
117 changed files with 5076 additions and 0 deletions
+147
View File
@@ -0,0 +1,147 @@
#include <kernel/assert.h>
#include <kernel/common.h>
#include <kernel/mem.h>
#include <kernel/panic.h>
#include <kernel/print.h>
#include <string.h>
extern uint8_t __ld_text_start[];
extern uint8_t __ld_text_end[];
extern uint8_t __ld_rodata_start[];
extern uint8_t __ld_rodata_end[];
extern uint8_t __ld_data_start[];
extern uint8_t __ld_data_end[];
extern uint8_t __ld_kernel_start[];
struct page_table mem_kernel_table = {0};
void mem_init(struct phys_mem* map, size_t map_len, virt_t kernel_virt, phys_t kernel_phys, virt_t tmp_hhdm) {
// This function creates a kernel page table and initializes all memory managers.
const size_t pgsz = mem_page_size();
kprintf("Memory map:\n");
for (size_t i = 0; i < map_len; i++) {
if (map[i].length == 0)
continue;
const char* label = nullptr;
switch (map[i].usage) {
case PHYS_RESERVED:
label = "Reserved";
break;
case PHYS_USABLE:
label = "Usable";
break;
case PHYS_RECLAIMABLE:
label = "Reclaimable";
break;
}
kprintf("[%p - %p] %s\n", (void*)map[i].address, (void*)(map[i].address + map[i].length - 1), label);
}
// Set up the bootstrap allocator.
struct phys_mem* largest = map;
for (size_t i = 0; i < map_len; i++) {
if (map[i].length > largest->length && map[i].usage == PHYS_USABLE)
largest = &map[i];
}
mem_phys_bootstrap(largest);
kprintf(
"Using region [%p - %p] for bootstrap allocator\n",
(void*)largest->address,
(void*)(largest->address + largest->length - 1)
);
// Set the HHDM base address to the address given by the loader.
// We must not keep any virtual addresses to this region,
// since we're likely going to map it at a different base address.
mem_hhdm_base = tmp_hhdm;
ASSERT(mem_pt_new_kernel(&mem_kernel_table, 0) == 0, "Unable to allocate the kernel page table");
// text
kprintf("Mapping text segment at %p\n", __ld_text_start);
for (uint8_t* p = __ld_text_start; p <= __ld_text_end; p += pgsz) {
zn_status_t status = mem_pt_map(
&mem_kernel_table,
(virt_t)p,
(phys_t)(p - __ld_kernel_start + kernel_phys),
PTE_READ | PTE_EXEC,
CACHE_NONE
);
ASSERT(!status, "Failed to map %p with error %i", p, status);
}
// rodata
kprintf("Mapping rodata segment at %p\n", __ld_rodata_start);
for (uint8_t* p = __ld_rodata_start; p < __ld_rodata_end; p += pgsz) {
zn_status_t status = mem_pt_map(
&mem_kernel_table,
(virt_t)p,
(phys_t)(p - __ld_kernel_start + kernel_phys),
PTE_READ,
CACHE_NONE
);
ASSERT(!status, "Failed to map %p with error %i", p, status);
}
// data
kprintf("Mapping data segment at %p\n", __ld_data_start);
for (uint8_t* p = __ld_data_start; p < __ld_data_end; p += pgsz) {
zn_status_t status = mem_pt_map(
&mem_kernel_table,
(virt_t)p,
(phys_t)(p - __ld_kernel_start + kernel_phys),
PTE_READ | PTE_WRITE,
CACHE_NONE
);
ASSERT(!status, "Failed to map %p with error %i", p, status);
}
// The kernel address space is divided into 3 segments (plus kernel).
// On x86_64 for example, they live at:
// HHDM FFFF'8000'0000'0000
// PFNDB FFFF'A000'0000'0000
// Mappings FFFF'C000'0000'0000
// Map all physical memory to the HHDM address.
tmp_hhdm = mem_hhdm_addr();
for (size_t i = 0; i < map_len; i++) {
for (size_t p = 0; p <= map[i].length; p += pgsz) {
virt_t vaddr = (virt_t)(map[i].address + p + tmp_hhdm);
phys_t paddr = (phys_t)(map[i].address + p);
zn_status_t status = mem_pt_map(&mem_kernel_table, vaddr, paddr, PTE_READ | PTE_WRITE, CACHE_NONE);
ASSERT(!status, "Failed to map HHDM page %p to %p with error %i", (void*)vaddr, (void*)paddr, status);
}
}
mem_hhdm_base = tmp_hhdm;
// Switch to our own page table.
mem_pt_set(&mem_kernel_table);
// We record metadata for every single page of available memory in a large array.
// This array is contiguous in virtual memory, but is sparsely populated.
// Only those array entries which represent usable memory are mapped.
for (size_t i = 0; i < map_len; i++) {
if (map[i].length == 0 || map[i].usage != PHYS_USABLE)
continue;
size_t length = ALIGN_UP((map[i].length / pgsz) * sizeof(struct page), pgsz);
virt_t vaddr = ALIGN_DOWN(mem_pfndb_addr() + (map[i].address / pgsz * sizeof(struct page)), pgsz);
for (size_t page = 0; page < length; page += pgsz) {
phys_t paddr;
ASSERT(!mem_phys_alloc(1, 0, &paddr), "Failed to allocate memory for PFNDB!");
mem_pt_map(&mem_kernel_table, vaddr + page, paddr, PTE_READ | PTE_WRITE, CACHE_NONE);
}
}
mem_pfndb = (struct page*)mem_pfndb_addr();
// We don't need the bootstrap allocator from this point on.
// Initialize the real page allocator.
mem_phys_init(map, map_len);
kprintf("Memory initialization complete\n");
}
+7
View File
@@ -0,0 +1,7 @@
kernel_sources += files(
'init.c',
'pmm.c',
'pt.c',
'slab.c',
'vmm.c',
)
+155
View File
@@ -0,0 +1,155 @@
#include <kernel/assert.h>
#include <kernel/compiler.h>
#include <kernel/mem.h>
#include <kernel/print.h>
#include <kernel/spin.h>
#include <stdint.h>
#include <string.h>
virt_t mem_hhdm_base = 0;
struct page* mem_pfndb = nullptr;
static zn_status_t (*alloc_fn)(size_t num_pages, enum alloc_flags flags, phys_t* out);
static zn_status_t (*free_fn)(phys_t start, size_t num_pages);
zn_status_t mem_phys_alloc(size_t num_pages, enum alloc_flags flags, phys_t* out) {
return alloc_fn(num_pages, flags, out);
}
zn_status_t mem_phys_free(phys_t start, size_t num_pages) {
return free_fn(start, num_pages);
}
static struct phys_mem bump_mem = {0};
static struct phys_mem* bump_region = nullptr;
static zn_status_t bump_alloc(size_t num_pages, enum alloc_flags flags, phys_t* out) {
const size_t bytes = num_pages * mem_page_size();
if (((intptr_t)bump_mem.length - bytes) <= 0)
return ZN_ERR_NO_MEMORY;
*out = bump_mem.address;
bump_mem.length -= bytes;
bump_mem.address += bytes;
if (!(flags & ALLOC_NOZERO)) {
memset(HHDM_PTR(*out), 0, bytes);
}
return 0;
}
static zn_status_t bump_free(phys_t addr, size_t num_pages) {
// We don't free bootstrap memory.
ASSERT(false, "Attempted to free bootstrap memory!");
}
void mem_phys_bootstrap(struct phys_mem* mem) {
alloc_fn = bump_alloc;
free_fn = bump_free;
// Save the region by reference and value.
// We can't directly modify the region by reference, because that
// interferes with e.g. HHDM mapping logic.
bump_mem = *mem;
bump_region = mem;
}
static struct page* pmm_head = nullptr;
static struct spinlock pmm_lock = {0};
static size_t pmm_total_free = 0;
static zn_status_t freelist_alloc(size_t num_pages, enum alloc_flags flags, phys_t* out) {
spin_lock(&pmm_lock);
const size_t bytes = num_pages * mem_page_size();
phys_t limit = UINTPTR_MAX;
if (flags & ALLOC_MEM20) {
limit = (phys_t)1 << 20;
} else if (flags & ALLOC_MEM32) {
limit = (phys_t)1 << 32;
}
struct page* it = pmm_head;
struct page* prev_it = nullptr;
while (it) {
const phys_t page_addr = (((uintptr_t)it - (uintptr_t)mem_pfndb) / sizeof(struct page)) * mem_page_size();
// If the region is too high up or doesn't have enough pages left, continue searching.
if (page_addr + bytes >= limit || __unlikely(it->freelist.count < num_pages)) {
prev_it = it;
it = it->freelist.next;
continue;
}
// If the current block has exactly enough pages left, consume it entirely.
if (__unlikely(it->freelist.count == num_pages)) {
if (prev_it) {
prev_it->freelist.next = it->freelist.next;
} else {
pmm_head = it->freelist.next;
}
it->freelist.next = nullptr;
it->freelist.count = 0;
if (__likely(out))
*out = page_addr;
}
// If not, we can just shrink the block.
else {
it->freelist.count -= num_pages;
if (__likely(out))
*out = page_addr + (it->freelist.count * mem_page_size());
}
goto success;
}
spin_unlock(&pmm_lock);
return ZN_ERR_NO_MEMORY;
success:
// If the NOZERO flag is *not* specified, zero memory.
if (__likely(out) && __likely(!(flags & ALLOC_NOZERO))) {
memcpy(HHDM_PTR(*out), 0, bytes);
}
pmm_total_free -= num_pages;
spin_unlock(&pmm_lock);
return 0;
}
static zn_status_t freelist_free(phys_t addr, size_t num_pages) {
spin_lock(&pmm_lock);
struct page* page = &mem_pfndb[addr / mem_page_size()];
page->freelist.count = num_pages;
page->freelist.next = pmm_head;
pmm_head = page;
spin_unlock(&pmm_lock);
return 0;
}
void mem_phys_init(struct phys_mem* map, size_t length) {
alloc_fn = freelist_alloc;
free_fn = freelist_free;
bump_region->address = bump_mem.address;
bump_region->length = bump_mem.length;
for (size_t i = 0; i < length; i++) {
// Regions smaller than a page are useless.
if (map[i].length < mem_page_size() || map[i].usage != PHYS_USABLE)
continue;
struct page* page = &mem_pfndb[map[i].address / mem_page_size()];
page->freelist.count = map[i].length / mem_page_size();
page->freelist.next = pmm_head;
pmm_head = page;
pmm_total_free += page->freelist.count;
}
const size_t free_bytes = (pmm_total_free * mem_page_size());
kprintf("Total available memory: %zu MiB\n", free_bytes / 1024 / 1024);
}
+82
View File
@@ -0,0 +1,82 @@
#include <kernel/assert.h>
#include <kernel/mem.h>
#include <kernel/print.h>
#include <kernel/spin.h>
zn_status_t mem_pt_new_kernel(struct page_table* pt, enum alloc_flags flags) {
flags &= ~ALLOC_NOZERO;
phys_t addr;
zn_status_t status = mem_phys_alloc(1, flags, &addr);
if (status)
return status;
pt->root = addr;
return 0;
}
zn_status_t mem_pt_new_user(struct page_table* pt, enum alloc_flags flags) {
// TODO
return 0;
}
// Gets a reference to the PTE at the given virtual address.
// If `check_only` is set, only checks if the PTE exists,
// and doesn't allocate new levels if they don't already exist.
// If it can't allocate a page if it has to, returns `nullptr`.
static zn_status_t get_pte(struct page_table* pt, virt_t vaddr, bool is_user, bool check_only, pte_t** ret) {
pte_t* current_head = HHDM_PTR(pt->root);
size_t index = 0;
for (int8_t level = mem_num_levels() - 1; level >= 0; level--) {
const size_t addr_mask = (1 << mem_level_bits()) - 1;
const size_t addr_shift = mem_page_bits() + (mem_level_bits() * level);
const enum pte_flags level_flags = PTE_DIR | (is_user ? PTE_USER : 0);
index = (vaddr >> addr_shift) & addr_mask;
// The last level is used to access the actual PTE, so break the loop then.
// We still need to update the index beforehand, that's why we can't just end early.
if (level == 0)
break;
pte_t* pte = &current_head[index];
if (mem_pte_is_present(pte)) {
// Get the next level.
*pte = mem_pte_build(mem_pte_address(pte), level_flags, CACHE_NONE);
current_head = HHDM_PTR(mem_pte_address(pte));
} else {
// If the current level isn't present, we can skip the rest.
if (check_only)
return ZN_ERR_NO_MEMORY;
phys_t addr;
zn_status_t alloc_status = mem_phys_alloc(1, 0, &addr);
if (alloc_status)
return alloc_status;
*pte = mem_pte_build(addr, level_flags, CACHE_NONE);
current_head = HHDM_PTR(addr);
}
}
*ret = &current_head[index];
return 0;
}
zn_status_t mem_pt_map(struct page_table* pt, virt_t vaddr, phys_t paddr, enum pte_flags flags, enum cache_mode cache) {
spin_lock(&pt->lock);
pte_t* pte;
zn_status_t status = get_pte(pt, vaddr, flags & PTE_USER, false, &pte);
if (status) {
goto fail;
}
*pte = mem_pte_build(paddr, flags, cache);
fail:
spin_unlock(&pt->lock);
return status;
}
+1
View File
@@ -0,0 +1 @@
#include <kernel/mem.h>
View File