diff options
Diffstat (limited to 'kc/core/memory.c')
| -rw-r--r-- | kc/core/memory.c | 534 |
1 files changed, 534 insertions, 0 deletions
diff --git a/kc/core/memory.c b/kc/core/memory.c new file mode 100644 index 0000000..62f3a38 --- /dev/null +++ b/kc/core/memory.c @@ -0,0 +1,534 @@ +#include "memory.h" +#include "kprint.h" +#include "panic.h" + +#include <stdint.h> + +#include <kernel/entry.h> +#include <kernel/memory/paging.h> + +/* kernel virtual space guarantees + * + * the loader must set up the address space as follows + * 1. kernel virtual space is 2GiB in size on 2GiB alignment. + * 2. mappings begin at +0x7fc00000. this mapping space is sparse and its + * own mapping tables begin at +0x7fffe000. + */ + +#define kpm1_index(v) (((uint64_t)v >> pte_index_bits(1)) & 0x7ffff) +#define kpm2_index(v) (((uint64_t)v >> pte_index_bits(2)) & 0x3ff) + +#define align_next(v, a) (((uint64_t)v + a - 1) & ~(a - 1)) + +// a conservative 128MB for kernel heap +#define KERNEL_HEAP_SPACE_SIZE (128 << 20) + +typedef int (*page_fault_handler_func)(uint32_t code, void *address); + +enum memory_space_flags +{ + SYSTEM_MEMORY_SPACE, // memory that cannot fault + ANONYMOUS_MEMORY_SPACE, // memory that can fault +}; + +struct memory_space +{ + enum memory_space_flags flags; + page_fault_handler_func handler; + void *base; + void *head; + struct memory_space *next; + struct memory_space *prev; +}; + +struct page +{ + uint32_t next: 31; + uint32_t used: 1; +}; + +extern char k_virt_base; +extern char k_text_begin; +extern char k_text_end; +extern char k_data_begin; +extern char k_data_end; + +static struct page *const page_array = (struct page *)0xffffffd800000000; +static int first_free_page_index = -1; +static size_t page_array_entries = 0; +static size_t free_pages = 0; + +// the temporary mapping place. never use this permanently. +static void *const temp = (void *)0xffffffffffa00000; +static uint64_t *const kernel_pm1 = (uint64_t *)0xffffffffffc00000; +static uint64_t *const kernel_pm2 = (uint64_t *)0xffffffffffffe000; + +static uint64_t *get_kernel_pm1e(void *vaddr); +static uint64_t *get_kernel_pm2e(void *vaddr); +static void *get_virtual_page(enum page_map_flags flags); +static void *page_map_at(void *vaddr, phys_addr_t paddr, enum page_map_flags flags); + +// handlers for memory space types +int anonymous_page_handler(uint32_t code, void *address); + +// the system memory_space's that are always present +static struct memory_space kernel_image_space; +static struct memory_space kernel_stack_space; +static struct memory_space kernel_pagemap_space; +// the kernel's own heap space +static struct memory_space kernel_heap_space; + +// NULL if the system address spaces are not set up +static struct memory_space *root_memory_space; + +static struct memory_range init_grab_pages(struct memory_range *ranges, + int count, + size_t size) +{ + struct memory_range request = {SYSTEM_MEMORY, + 0, + align_next(size, PAGE_SIZE)}; + + for (int i = 0; i < count; i++) + { + if (ranges[i].type != AVAILABLE_MEMORY || + ranges[i].base < (1 << 20)) // leave pages below 1MiB alone. + { + continue; + } + else if (ranges[i].size > request.size) + { + request.base = ranges[i].base; + ranges[i].base += request.size; + ranges[i].size -= request.size; + return request; + } + } + + request.type = INVALID_MEMORY; + return request; +} + +static size_t init_get_max_paddr(struct memory_range *ranges, int count) +{ + phys_addr_t max_paddr = 0; + + // get the highest usable physical address in all memory ranges. + for (int i = 0; i < count; i++) + { + if (ranges[i].type != AVAILABLE_MEMORY) + { + continue; + } + + if ((ranges[i].base + ranges[i].size - 1) > max_paddr) + { + max_paddr = ranges[i].base + ranges[i].size - 1; + } + } + + return max_paddr; +} + +static phys_addr_t get_kernel_pm4_phys(void) +{ + phys_addr_t pm4_phys; + __asm__ ( + "mov %%cr3, %0\n\t" + : "=r"(pm4_phys) + ); + + return page_address(pm4_phys, 1); +} + +static phys_addr_t get_kernel_pm3_phys(void) +{ + phys_addr_t pm3_phys; + // make a temporary mapping to read pm4 + uint64_t *pm4 = page_map_at(temp, + get_kernel_pm4_phys(), + CONTENT_RODATA|SIZE_2M); + + // pm3_phys is in pm4. + pm3_phys = page_address(pm4[pte_index(&k_virt_base, 4)], 1); + // never leave a temporary mapping + page_unmap(pm4); + + return pm3_phys; +} + +static void init_map_page_array_pm2(struct memory_range *pm2_pages) +{ + // now we need to write the physical address of each pm2 page for the + // page_array in the kernel's pm3. + uint64_t *pm3 = page_map_at(temp, + get_kernel_pm3_phys(), + CONTENT_RWDATA|SIZE_2M); + + // the first index is NOT zero! + for (size_t i = 0; i < (pm2_pages->size / PAGE_SIZE); i++) + { + pm3[pte_index(page_array, 3) + i] = (pm2_pages->base + i * + PAGE_SIZE)|PAGE_NX|PAGE_WR|PAGE_PR; + } + // never forget to unmap temporary mappings. + page_unmap(pm3); +} + +static void init_create_page_array_map(struct memory_range *pages, + struct memory_range *maps) +{ + // indices for page_array always start at 0 + size_t entry_count = pages->size / PAGE_SIZE; + + // every 512 entries we need to re-map and clean. + for (size_t i = 0; i < entry_count; i += PAGE_TABLE_INDEX_MASK) + { + uint64_t *pm1 = page_map_at(temp, + maps->base + i * PAGE_SIZE, + CONTENT_RWDATA|SIZE_2M); + memset(pm1, 0, PAGE_SIZE); + for (size_t j = 0; + (j < PAGE_TABLE_INDEX_MASK) && ((j + i) < entry_count); + j++) + { + pm1[j] = (pages->base + (i + j) * PAGE_SIZE)| + PAGE_NX|PAGE_WR|PAGE_PR; + } + + page_unmap(pm1); + } +} + +static void init_set_memory_range(struct memory_range *range) +{ + for (size_t i = 0; i < range->size / PAGE_SIZE; i++) + { + if (range->base / PAGE_SIZE + i > page_array_entries) + { + return; + } + + if (range->type == AVAILABLE_MEMORY) + { + page_free(range->base + PAGE_SIZE * i); + } + else + { + struct page *page = &page_array[range->base / PAGE_SIZE + i]; + page->used = 1; + } + } +} + +static void init_populate_page_array(struct memory_range *ranges, int count) +{ + for (int i = 0; i < count; i++) + { + init_set_memory_range(&ranges[i]); + } +} + +static void init_create_page_array(struct memory_range *ranges, int count) +{ + // the highest actual physical memory address. + size_t max_paddr = init_get_max_paddr(ranges, count); + page_array_entries = max_paddr / page_size(1); + size_t page_array_size = page_array_entries * sizeof(struct page); + + // the physical pages that will contain page_array + struct memory_range pa_pages = init_grab_pages(ranges, count, page_array_size); + + // the number of page tables needed to map pa_pages + // this will be 1 on systems with less than 2GB of memory. + size_t pm1_count = page_count(pa_pages.size, 2); + struct memory_range pm1_pages = init_grab_pages(ranges, + count, + pm1_count * page_size(1)); + + init_create_page_array_map(&pa_pages, &pm1_pages); + + // the number of page directories needed to map pa_pages + // this will be 1 on systems with less than 1TB of memory. + // who the fuck has 1TB of memory lol + size_t pm2_count = page_count(pa_pages.size, 3); + struct memory_range pm2_pages = init_grab_pages(ranges, + count, + pm2_count * page_size(1)); + + init_create_page_array_map(&pm1_pages, &pm2_pages); + + // ok here goes + init_map_page_array_pm2(&pm2_pages); + + memset(page_array, 0, page_array_size); + + init_populate_page_array(ranges, count); + init_populate_page_array(&pa_pages, 1); + init_populate_page_array(&pm1_pages, 1); + init_populate_page_array(&pm2_pages, 1); +} + +static struct memory_space init_system_space(void *base, void *head) +{ + struct memory_space space = {SYSTEM_MEMORY_SPACE, NULL, base, head, NULL, NULL}; + return space; +} + +static struct memory_space init_anonymous_space(void *base, void *head) +{ + struct memory_space space = {ANONYMOUS_MEMORY_SPACE, + &anonymous_page_handler, + base, + head, + NULL, + NULL}; + + return space; +} + +void memory_init(struct memory_range *ranges, int count) +{ + init_create_page_array(ranges, count); + // initialize always-present memory spaces for the vmm + kernel_image_space = init_system_space((void *)&k_text_begin, + (void *)align_next(&k_data_end, PAGE_SIZE)); + kernel_stack_space = init_system_space((void *)KERNEL_ENTRY_STACK_BASE, + (void *)KERNEL_ENTRY_STACK_HEAD); + kernel_pagemap_space = init_system_space(kernel_pm1, (void *)-1LL); + + // initialize the kernel heap space + kernel_heap_space = init_anonymous_space(kernel_image_space.head, + (char *)kernel_image_space.head + + KERNEL_HEAP_SPACE_SIZE); + + // set up the list links + kernel_image_space.next = &kernel_heap_space; + kernel_heap_space.prev = &kernel_image_space; + kernel_heap_space.next = &kernel_stack_space; + kernel_stack_space.prev = &kernel_heap_space; + kernel_stack_space.next = &kernel_pagemap_space; + kernel_pagemap_space.prev = &kernel_stack_space; + + root_memory_space = &kernel_image_space; +} + +static void *page_map_at(void *vaddr, + phys_addr_t paddr, + enum page_map_flags flags) +{ + uint64_t entry = PAGE_PR; + + switch (flags & CONTENT_MASK) + { + case CONTENT_RODATA: + entry |= PAGE_NX; + break; + case CONTENT_RWDATA: + entry |= PAGE_NX|PAGE_WR; + break; + default: + break; + } + + size_t offset; + uint64_t *pte; + + switch (flags & SIZE_MASK) + { + case SIZE_2M: + entry |= page_address(paddr, 2)|PAGE_LG; + offset = page_offset(paddr, 2); + pte = get_kernel_pm2e(vaddr); + break; + case SIZE_4K: + entry |= page_address(paddr, 1); + offset = page_offset(paddr, 1); + pte = get_kernel_pm1e(vaddr); + break; + default: + offset = 0; + pte = NULL; + } + + if (!pte) + { + return NULL; + } + + *pte = entry; + return (char *)vaddr + offset; +} + +void *page_map(phys_addr_t paddr, enum page_map_flags flags) +{ + return page_map_at(get_virtual_page(flags), paddr, flags); +} + +void page_unmap(void *vaddr) +{ + uint64_t *pte = get_kernel_pm2e(vaddr); + + if (pte && !(*pte & PAGE_LG)) + { + pte = get_kernel_pm1e(vaddr); + } + + if (pte) + { + *pte = 0; + } + __asm__ ("invlpg (%0)" :: "r"(vaddr)); +} + +phys_addr_t page_alloc(void) +{ + int index = first_free_page_index; + + if (index > 0) + { + page_array[index].used = 1; + first_free_page_index = page_array[index].next; + free_pages--; + } + + return (phys_addr_t)index * page_size(1); +} + +void page_free(phys_addr_t paddr) +{ + int index = paddr / page_size(1); + + if ((size_t)index > page_array_entries) + { + return; + } + + page_array[index].used = 0; + page_array[index].next = first_free_page_index; + first_free_page_index = index; + free_pages++; +} + +void *heap_alloc(size_t size) +{ + (void)size; + return NULL; +} + +void heap_free(void *block) +{ + (void)block; +} + +void *memory_alloc(size_t size) +{ + (void)size; + return NULL; +} + +void memory_free(void *block) +{ + (void)block; +} + +static uint64_t *get_kernel_pm1e(void *vaddr) +{ + return &kernel_pm1[kpm1_index(vaddr)]; +} + +static uint64_t *get_kernel_pm2e(void *vaddr) +{ + return &kernel_pm2[kpm2_index(vaddr)]; +} + +static void *get_virtual_page(enum page_map_flags flags) +{ + (void)flags; + return NULL; +} + +static struct memory_space *get_memory_space(void *address) +{ + // walk the memory space list + struct memory_space *space = root_memory_space; + + while (space) + { + if (space->base >= address && address <= space->head) + { + return space; + } + space = space->next; + } + + return NULL; +} + +int anonymous_page_handler(uint32_t code, void *address) +{ + if (code & PAGE_PR) + { + // there's no reason a protection violation should happen + // in anonymous space + panic(UNHANDLED_FAULT); + } + kputs("anonymous fault\n"); + // kernel page mappings are built different + if (address >= (void *)&k_virt_base) + { + uint64_t *pm2e = get_kernel_pm2e(address); + + if (!page_address(*pm2e, 1)) + { + // create a page table and install it + phys_addr_t pm1_phys = page_alloc(); + if (pm1_phys) + { + uint64_t *pm1; + pm1 = page_map_at(temp, pm1_phys, CONTENT_RWDATA|SIZE_2M); + // zero the page + memset(pm1, 0, PAGE_SIZE); + // put the table where it goes + *pm2e = page_address(pm1_phys, 1)|PAGE_WR|PAGE_PR; + page_unmap(pm1); + } + else + { + panic(OUT_OF_MEMORY); + } + } + + uint64_t *pm1e = get_kernel_pm1e(address); + + if (pm1e) + { + phys_addr_t page_phys = page_alloc(); + if (page_phys) + { + *pm1e = page_address(page_phys, 1)|PAGE_WR|PAGE_PR; + // clear a potentially dirty page. + memset((void *)page_address(address, 1), 0, page_size(1)); + } + } + } + + return 0; +} + +int page_fault_handler(uint32_t code, void *address) +{ + kputs("page faulmt\n"); + struct memory_space *space = get_memory_space(address); + if (space && space->handler) + { + int result = space->handler(code, address); + + if (result) + { + panic(UNHANDLED_FAULT); + } + } + + return 0; +} |
