Initial commit
This commit is contained in:
@@ -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");
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
kernel_sources += files(
|
||||
'init.c',
|
||||
'pmm.c',
|
||||
'pt.c',
|
||||
'slab.c',
|
||||
'vmm.c',
|
||||
)
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 = ¤t_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 = ¤t_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;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
#include <kernel/mem.h>
|
||||
Reference in New Issue
Block a user