diff --git a/conf/make/amd64.mk b/conf/make/amd64.mk index 8e0a57c..541c8c5 100644 --- a/conf/make/amd64.mk +++ b/conf/make/amd64.mk @@ -21,7 +21,8 @@ OBJS+=$(O)/arch/amd64/kernel.o \ $(O)/arch/amd64/acpi/tables.o \ $(O)/arch/amd64/acpi/hpet.o \ $(O)/arch/amd64/mm/phys.o \ - $(O)/arch/amd64/mm/heap.o + $(O)/arch/amd64/mm/heap.o \ + $(O)/arch/amd64/mm/vmalloc.o kernel_OBJS=$(O)/arch/amd64/entry.o \ $(OBJS) kernel_LINKER=$(S)/arch/amd64/link.ld diff --git a/include/arch/amd64/mm/map.h b/include/arch/amd64/mm/map.h new file mode 100644 index 0000000..9b5cba1 --- /dev/null +++ b/include/arch/amd64/mm/map.h @@ -0,0 +1,46 @@ +/** vim: set ft=cpp.doxygen : + * @file arch/amd64/mm/map.h + * @brief amd64-specific virtual memory space functions + */ +#pragma once +#include "sys/types.h" +#include "sys/mm.h" + +/** + * @brief Translate a virtual address in `space' to its physical mapping (if one exists). + * May optionally return access flags in `flags' if non-NULL + * @param space Virtual memory space + * @param vaddr Virtual address + * @param flags Access flags storage pointer or NULL + * @return Physical address corresponding to `vaddr' in `space' if mapping exists, + * MM_NADDR otherwise. + */ +uintptr_t amd64_map_get(const mm_space_t space, uintptr_t vaddr, uint64_t *flags); + +/** + * @brief Remove a virtual address mapping from memory space, optionally checking if it + * is of a specific page size. + * @param space Virtual memory space + * @param vaddr Virtual address + * @param size Expected page size: + * * 1 for 4KiB + * * 2 for 2MiB + * * 3 for 1GiB + * * 0 for any size + * @return Physical address of the unmapped page (in case it has to be freed), + * MM_NADDR in case the mapping does not exist or the size does not match. + */ +uintptr_t amd64_map_umap(mm_space_t space, uintptr_t vaddr, uint32_t size); + +/** + * @brief Add a single page mapping to `pml4' + * @param pml4 Virtual memory space + * @param virt_addr Source virtual address + * @param phys Destination physical address + * @param flags Access flags: + * * TODO: size-specific + * * AMD64_MAP_WRITE + * * AMD64_MAP_USER + * @return 0 on success, -1 otherwise + */ +int amd64_map_single(mm_space_t pml4, uintptr_t virt_addr, uintptr_t phys, uint32_t flags); diff --git a/include/arch/amd64/mm/phys.h b/include/arch/amd64/mm/phys.h index 5bd542d..bd7877f 100644 --- a/include/arch/amd64/mm/phys.h +++ b/include/arch/amd64/mm/phys.h @@ -5,4 +5,6 @@ void amd64_phys_memory_map(const multiboot_memory_map_t *mmap, size_t length); +void amd64_phys_free(uintptr_t page); +uintptr_t amd64_phys_alloc_page(void); uintptr_t amd64_phys_alloc_contiguous(size_t count); diff --git a/include/sys/vmalloc.h b/include/sys/vmalloc.h index f4f9415..9380c4d 100644 --- a/include/sys/vmalloc.h +++ b/include/sys/vmalloc.h @@ -5,6 +5,9 @@ #pragma once #include "sys/mm.h" +#define VM_ALLOC_USER (1 << 0) +#define VM_ALLOC_WRITE (1 << 1) + /** * @brief Find a free contiguous memory range inside a given one * in a virtual memory space. @@ -15,7 +18,7 @@ * @return Address of the resulting range on success, * MM_NADDR if such range cannot be allocated */ -uintptr_t vmfind(mm_space_t pd, uintptr_t from, uintptr_t to, size_t npages); +uintptr_t vmfind(const mm_space_t pd, uintptr_t from, uintptr_t to, size_t npages); /** * @brief Allocate a contiguous virtual memory range in a space diff --git a/src/arch/amd64/mm/map.c b/src/arch/amd64/mm/map.c index 7fd40f1..8aad3ca 100644 --- a/src/arch/amd64/mm/map.c +++ b/src/arch/amd64/mm/map.c @@ -4,7 +4,118 @@ #include "sys/mem.h" #include "arch/amd64/mm/pool.h" -static int amd64_mm_map_single(mm_space_t pml4, uintptr_t virt_addr, uintptr_t phys, uint32_t flags) { +uintptr_t amd64_map_get(const mm_space_t pml4, uintptr_t vaddr, uint64_t *flags) { + size_t pml4i = (vaddr >> 39) & 0x1FF; + size_t pdpti = (vaddr >> 30) & 0x1FF; + size_t pdi = (vaddr >> 21) & 0x1FF; + size_t pti = (vaddr >> 12) & 0x1FF; + + mm_pdpt_t pdpt; + mm_pagedir_t pd; + mm_pagetab_t pt; + + if (!(pml4[pml4i] & 1)) { + return MM_NADDR; + } + + if (pml4[pml4i] & (1 << 7)) { + panic("NYI\n"); + } + + pdpt = (mm_pdpt_t) MM_VIRTUALIZE(pml4[pml4i] & ~0xFFF); + + if (!(pdpt[pdpti] & 1)) { + return MM_NADDR; + } + + if (pdpt[pdpti] & (1 << 7)) { + if (flags) { + *flags = 2; + } + return (pdpt[pdpti] & ~0xFFF) | (vaddr & ((1 << 30) - 1)); + } + + pd = (mm_pagedir_t) MM_VIRTUALIZE(pdpt[pdpti] & ~0xFFF); + + if (!(pd[pdi] & 1)) { + return MM_NADDR; + } + + if (pd[pdi] & (1 << 7)) { + if (flags) { + *flags = 1; + } + return (pd[pti] & ~0xFFF) | (vaddr & ((1 << 21) - 1)); + } + + pt = (mm_pagetab_t) MM_VIRTUALIZE(pd[pdi] & ~0xFFF); + + if (!(pt[pti] & 1)) { + return MM_NADDR; + } + + if (flags) { + *flags = 0; + } + + return (pt[pti] & ~0xFFF) | (vaddr & 0xFFF); +} + +uintptr_t amd64_map_umap(mm_space_t pml4, uintptr_t vaddr, uint32_t size) { + // TODO: support page sizes other than 4KiB + // (Though I can't think of any reason to use it) + size_t pml4i = (vaddr >> 39) & 0x1FF; + size_t pdpti = (vaddr >> 30) & 0x1FF; + size_t pdi = (vaddr >> 21) & 0x1FF; + size_t pti = (vaddr >> 12) & 0x1FF; + + mm_pdpt_t pdpt; + mm_pagedir_t pd; + mm_pagetab_t pt; + + if (!(pml4[pml4i] & 1)) { + return MM_NADDR; + } + + if (pml4[pml4i] & (1 << 7)) { + panic("NYI\n"); + } + + pdpt = (mm_pdpt_t) MM_VIRTUALIZE(pml4[pml4i] & ~0xFFF); + + if (!(pdpt[pdpti] & 1)) { + return MM_NADDR; + } + + if (pdpt[pdpti] & (1 << 7)) { + panic("NYI\n"); + } + + pd = (mm_pagedir_t) MM_VIRTUALIZE(pdpt[pdpti] & ~0xFFF); + + if (!(pd[pdi] & 1)) { + return MM_NADDR; + } + + if (pd[pdi] & (1 << 7)) { + panic("NYI\n"); + } + + pt = (mm_pagetab_t) MM_VIRTUALIZE(pd[pdi] & ~0xFFF); + + if (!(pt[pti] & 1)) { + return MM_NADDR; + } + + uint64_t old = pt[pti] & ~0xFFF; + pt[pti] = 0; + asm volatile("invlpg (%0)"::"a"(vaddr):"memory"); + return old; +} + +int amd64_map_single(mm_space_t pml4, uintptr_t virt_addr, uintptr_t phys, uint32_t flags) { + // TODO: support page sizes other than 4KiB + // (Though I can't think of any reason to use it) size_t pml4i = (virt_addr >> 39) & 0x1FF; size_t pdpti = (virt_addr >> 30) & 0x1FF; size_t pdi = (virt_addr >> 21) & 0x1FF; @@ -64,7 +175,7 @@ int mm_map_pages_contiguous(mm_space_t pml4, uintptr_t virt_base, uintptr_t phys for (size_t i = 0; i < count; ++i) { uintptr_t virt_addr = virt_base + (i << 12); - if (amd64_mm_map_single(pml4, virt_addr, phys_base + (i << 12), flags) != 0) { + if (amd64_map_single(pml4, virt_addr, phys_base + (i << 12), flags) != 0) { return -1; } } diff --git a/src/arch/amd64/mm/mm.c b/src/arch/amd64/mm/mm.c index efa7a70..a4c5dbb 100644 --- a/src/arch/amd64/mm/mm.c +++ b/src/arch/amd64/mm/mm.c @@ -1,5 +1,6 @@ #include "arch/amd64/mm/pool.h" #include "arch/amd64/mm/phys.h" +#include "sys/vmalloc.h" #include "sys/debug.h" #include "sys/panic.h" #include "sys/heap.h" diff --git a/src/arch/amd64/mm/phys.c b/src/arch/amd64/mm/phys.c index 8d380e5..71ad09b 100644 --- a/src/arch/amd64/mm/phys.c +++ b/src/arch/amd64/mm/phys.c @@ -50,6 +50,7 @@ void amd64_phys_free(uintptr_t page) { if (page < PHYS_ALLOWED_BEGIN) { panic("Tried to free kernel physical pages\n"); } + page -= PHYS_ALLOWED_BEGIN; // Address is too high if (PHYS_TRACK_INDEX(page) >= PHYS_MAX_INDEX) { panic("Tried to free non-available physical page\n"); diff --git a/src/arch/amd64/mm/vmalloc.c b/src/arch/amd64/mm/vmalloc.c new file mode 100644 index 0000000..402512e --- /dev/null +++ b/src/arch/amd64/mm/vmalloc.c @@ -0,0 +1,84 @@ +#include "arch/amd64/mm/phys.h" +#include "arch/amd64/mm/map.h" +#include "sys/vmalloc.h" +#include "sys/debug.h" +#include "sys/panic.h" + +uintptr_t vmfind(const mm_space_t pml4, uintptr_t from, uintptr_t to, size_t npages) { + // XXX: The slowest approach I could think of + // Though the easiest one + size_t page_index = from >> 12; + + while ((page_index + npages) <= (to >> 12)) { + for (size_t i = 0; i < npages; ++i) { + if (amd64_map_get(pml4, (page_index + i) << 12, 0) != MM_NADDR) { + goto no_match; + } + } + + return page_index << 12; +no_match: + ++page_index; + continue; + } + + return MM_NADDR; +} + +uintptr_t vmalloc(mm_space_t pml4, uintptr_t from, uintptr_t to, size_t npages, int flags) { + uintptr_t addr = vmfind(pml4, from, to, npages); + uintptr_t virt_page, phys_page; + uint32_t rflags = 0; + + if (flags & VM_ALLOC_WRITE) { + rflags |= 1 << 1; + } + if (flags & VM_ALLOC_USER) { + rflags |= 1 << 2; + } + + if (addr == MM_NADDR) { + return MM_NADDR; + } + + for (size_t i = 0; i < npages; ++i) { + virt_page = addr + (i << 12); + phys_page = amd64_phys_alloc_page(); + + // Allocation of physical page failed, clean up + if (phys_page == MM_NADDR) { + // Unmap previously allocated pages + for (size_t j = 0; j < i; ++j) { + virt_page = addr + (j << 12); + // Deallocate physical pages that've already been mapped + // We've mapped only 4KiB pages, so expect to unmap only + // 4KiB pages + // TODO: use assert + if ((phys_page = amd64_map_umap(pml4, virt_page, 1)) == MM_NADDR) { + panic("Failed to deallocate page after a failed allocation\n"); + } + + amd64_phys_free(phys_page); + } + return MM_NADDR; + } + + // Succeeded, map the page + // TODO: use assert + if (amd64_map_single(pml4, virt_page, phys_page, 0) != 0) { + panic("Failed to map page\n"); + } + } + + return addr; +} + +void vmfree(mm_space_t pml4, uintptr_t addr, size_t npages) { + uintptr_t phys; + for (size_t i = 0; i < npages; ++i) { + if ((phys = amd64_map_umap(pml4, addr + (i << 12), 1)) == MM_NADDR) { + panic("Double vmfree error: %p is not an allocated page\n", addr + (i << 12)); + } + amd64_phys_free(phys); + } +}