diff --git a/arch/x86/boot/boot.c b/arch/x86/boot/boot.c index fd86aa1..79f3757 100644 --- a/arch/x86/boot/boot.c +++ b/arch/x86/boot/boot.c @@ -65,8 +65,6 @@ static void fb_init(enum vga_color fg, enum vga_color bg); static void print_gay_propaganda(void); -/** @brief Translate a physical memory address to a virtual (mapped) one. */ -#define phys_to_virt(ptr) ( (typeof(ptr))( (void *)(ptr) + CFG_KERNEL_RELOCATE ) ) static struct mb2_tag *next_tag(struct mb2_tag *tag); static void handle_tag(struct mb2_tag *tag); static void handle_mmap_tag(struct mb2_tag_mmap *tag); @@ -85,8 +83,6 @@ void _boot(u32 magic, void *address) return; } - //kprintf("%p\n", address); - print_gay_propaganda(); /* @@ -94,7 +90,7 @@ void _boot(u32 magic, void *address) * so we need to be careful to translate all pointers to virtual * addresses before accessing them. */ - address = phys_to_virt(address); + address += CFG_KERNEL_RELOCATE; for (struct mb2_tag *tag = address + 8; tag != NULL; tag = next_tag(tag)) handle_tag(tag); @@ -126,7 +122,7 @@ static inline void handle_mmap_tag(struct mb2_tag_mmap *tag) while ((void *)entry < (void *)tag + tag->tag.size) { kprintf("[%p-%p] %s\n", (void *)entry->addr, - (void *)entry->len, + (void *)entry->addr + entry->len - 1, mmap_type_name(entry->type)); if (entry->type == 1 && entry->len > region_len) { @@ -142,10 +138,10 @@ static inline void handle_mmap_tag(struct mb2_tag_mmap *tag) while (1); } - // if (kmalloc_init(region, region + region_len) != 0) { - // kprintf("kmalloc_init() failed! Aborting.\n"); - // while (1); - // } + if (kmalloc_init(region, region + region_len) != 0) { + kprintf("kmalloc_init() failed! Aborting.\n"); + while (1); + } } static inline struct mb2_tag *next_tag(struct mb2_tag *tag) diff --git a/arch/x86/boot/multiboot.S b/arch/x86/boot/multiboot.S index dd83afd..95ebff0 100644 --- a/arch/x86/boot/multiboot.S +++ b/arch/x86/boot/multiboot.S @@ -119,9 +119,8 @@ header_end: asmfn_begin(_start) /* - * 1023 of the 1024 pages in the page table are mapped to the low memory - * starting at 1 MiB, the address where the kernel image is loaded - * ($_image_start_phys). We currently assume the kernel is < 4 MiB + * The kernel image starts at 1 MiB into physical memory. + * We currently assume the kernel is < 3 MiB * and therefore can be mapped within a single page table. * As the kernel gets more and more bloated, this might not be the case * in the future anymore, so we should ideally add support for multiple @@ -147,8 +146,8 @@ asmfn_begin(_start) loop 1b /* - * Conveniently, the VGA character framebuffer fits exactly into one - * page. The physical address range + * Conveniently, the full VGA character framebuffer fits into one page + * and even starts at a page aligned address. The physical range * 0x000b8000 - 0x000b8fff * gets mapped to the virtual address range * 0xc03ff000 - 0xc03fffff @@ -184,6 +183,17 @@ asmfn_begin(_start) movl $(phys_addr(pt0) + 0x003), phys_addr(pd0) + 0 * 4 /* 0x00000000 */ movl $(phys_addr(pt0) + 0x003), phys_addr(pd0) + 768 * 4 /* 0xc0000000 */ + /* + * The last entry in the page directory points to itself. + * This has the effect of mapping all page tables in the page directory to + * 0xffc00000 - 0xffffefff + * and the page directory itself to + * 0xfffff000 - 0xffffffff + * because the page directory is being interpreted as a page table. + * This allows us to manipulate the table while we are in virtual memory. + */ + movl $(phys_addr(pd0) + 0x003), phys_addr(pd0) + 1023 * 4 /* 0xffc00000 */ + /* put the (physical) address of pd0 into cr3 so it will be used */ mov $phys_addr(pd0), %ecx mov %ecx, %cr3 @@ -194,9 +204,9 @@ asmfn_begin(_start) mov %ecx, %cr0 /* - * Alright, we are on virtual addresses! - * Now, we are going to do an absolute jump to the mapped kernel code - * somewhere at 0xc01*****. + * Alright, we are in virtual address space! But %eip still points to + * low memory (making use of the identity mapping), so we are going to + * do an absolute jump to the mapped kernel code somewhere at 0xc01*****. */ lea 4f, %ecx jmp *%ecx @@ -210,6 +220,8 @@ asmfn_end(_start) .text +asmfn_begin(_start_virtual) + /* * Now that we've completely transitioned to high memory, we can remove * the identity mapping because we don't need it anymore. diff --git a/arch/x86/include/arch/page.h b/arch/x86/include/arch/page.h index 40ce7bd..f179bcb 100644 --- a/arch/x86/include/arch/page.h +++ b/arch/x86/include/arch/page.h @@ -35,7 +35,10 @@ struct x86_page_table_entry { * This may be used outside of `/arch/x86` for ensuring page alignment. * Regular code, except for the memory allocator, should never need this. */ -#define PAGE_SIZE (1 << PAGE_SIZE_LOG2) +#define PAGE_SIZE (1lu << PAGE_SIZE_LOG2) +#define PAGE_MASK (~(PAGE_SIZE - 1)) + +#define PAGE_ALIGN(ptr) ((typeof(ptr))( (uintptr_t)(ptr) & PAGE_MASK )) struct x86_page_table { struct x86_page_table_entry entries[1024]; @@ -67,6 +70,14 @@ struct x86_page_directory { */ typedef struct x86_page_directory vm_info_t; +/** + * @brief Get the physical address a virtual one is currently mapped to. + * + * @param virt virtual address + * @returns The physical address, or `NULL` if there is no mapping + */ +void *virt_to_phys(void *virt); + /* * This file is part of GayBSD. * Copyright (c) 2021 fef <owo@fef.moe>. diff --git a/arch/x86/mm/page.c b/arch/x86/mm/page.c index b2df676..04c9d15 100644 --- a/arch/x86/mm/page.c +++ b/arch/x86/mm/page.c @@ -6,6 +6,13 @@ * If the bit is set, the page is in use. kmalloc() also just always hands * out entire pages, so let's just hope we never need more than PAGE_SIZE bytes * of contiguous memory lmao + * + * To manipulate the page directory once paging is enabled, we abuse the + * structural similarity between page directory and page table by mapping the + * last entry in the page directory to itself. This makes the MMU interpret the + * page directory as if it were a page table, giving us access to the individual + * directory entries at `0xffc00000-0xffffffff` virtual. The last page, at + * address `0xfffff000-0xffffffff`, then points to the page directory itself. */ #include <arch/page.h> @@ -23,22 +30,33 @@ extern void _image_start_phys; extern void _image_end_phys; -/* 0 = free, 1 = allocated */ +/** + * @brief Page allocation bitmap. + * 0 = free, 1 = allocated. + * + * The pagemap manipulation code below is specifically kept agnostic to + * the type of the page map (u8/u16/u32 etc) so we can easily change it later + * if it has performance benefits (which it almost certainly has) + */ static u8 *pagemap; static size_t pagemap_len; -static void *page_start; -static void *page_end; + +/* first and last dynamic page address (watch out, these are physical) */ +static void *dynpage_start; +static void *dynpage_end; /** * @brief First page table for low memory (0 - 4 M). * This is initialized by the early boot routine in assembly so that paging - * can be enabled (the kernel itself is mapped to `0xc0000000` by default). + * can be enabled (the kernel itself is mapped to `0xc0100000` by default). */ struct x86_page_table pt0; /** @brief First page directory for low memory. */ struct x86_page_directory pd0; -int kmalloc_init(void *start, void *end) +static void setup_pagemap(void); + +int kmalloc_init(void *start_phys, void *end_phys) { /* * if the kernel image is loaded within the paging region (which is @@ -46,46 +64,74 @@ int kmalloc_init(void *start, void *end) * to the end of the kernel image so we won't hand out pages that * actually store kernel data */ - if (&_image_start_phys >= start && &_image_start_phys <= end) - start = &_image_end_phys; + if (&_image_start_phys >= start_phys && &_image_start_phys <= end_phys) + start_phys = &_image_end_phys; - page_start = ptr_align(start, PAGE_SIZE_LOG2); - page_end = ptr_align(end, -PAGE_SIZE_LOG2); + dynpage_start = ptr_align(start_phys, PAGE_SIZE_LOG2); + dynpage_end = ptr_align(end_phys, -PAGE_SIZE_LOG2); - if (page_end - page_start < 1024 * PAGE_SIZE) { + if (dynpage_end - dynpage_start < 1024 * PAGE_SIZE) { kprintf("We have < 1024 pages for kmalloc(), this wouldn't go well\n"); return -1; } + /* + * Add an arbitrary offset to where dynpages actually start. + * I have no idea if this is necessary, but i think it might be possible + * that grub stores its info tags right after the kernel image which + * would blow up _boot(). Until this is resolved, we just throw away + * a couple KiB of RAM to be on the safe side. Techbros cope. + */ + dynpage_start += 32 * PAGE_SIZE; - pagemap = start; - pagemap_len = ((page_end - page_start) / PAGE_SIZE) / 8; - while (page_start - (void *)pagemap < pagemap_len) { - page_start += 8 * PAGE_SIZE; - pagemap_len--; - } + setup_pagemap(); - kprintf("Kernel image: %p - %p\n", &_image_start_phys, &_image_end_phys); - kprintf("Page bitmap: %p - %p\n", pagemap, pagemap + pagemap_len); - kprintf("Paging area: %p - %p\n", page_start, page_end); kprintf("Available memory: %u bytes (%u pages)\n", - page_end - page_start, (page_end - page_start) / PAGE_SIZE); + dynpage_end - dynpage_start, (dynpage_end - dynpage_start) / PAGE_SIZE); return 0; } -// int mem_init(void) -// { -// struct x86_page_directory *map = get_page(); -// if (map == NULL) -// return -ENOMEM; -// memset(map, 0, sizeof(*map)); - - -// } - -void map_page(vm_info_t *map, void *physical, void *virtual, enum mm_flags flags) +int map_page(void *phys, void *virt, enum mm_page_flags flags) { +# ifdef DEBUG + if (phys != PAGE_ALIGN(phys)) + kprintf("map_page(): unaligned physical address %p!\n", phys); + if (virt != PAGE_ALIGN(virt)) + kprintf("map_page(): unaligned virtual address %p!\n", virt); +# endif + struct x86_page_directory *pd = (struct x86_page_directory *)0xfffff000; + + size_t pd_index = ((uintptr_t)virt >> PAGE_SIZE_LOG2) / 1024; + size_t pt_index = ((uintptr_t)virt >> PAGE_SIZE_LOG2) % 1024; + + struct x86_page_directory_entry *pd_entry = &pd->entries[pd_index]; + if (!pd_entry->present) { + void *page = get_page(); + if (page == NULL) + return -ENOMEM; + memset(page, 0, PAGE_SIZE); + pd_entry->shifted_address = (uintptr_t)page >> X86_PAGE_DIRECTORY_ADDRESS_SHIFT; + pd_entry->rw = 1; + pd_entry->present = 1; + vm_flush(); + } + + struct x86_page_table *pt = &((struct x86_page_table *)0xffc00000)[pd_index]; + struct x86_page_table_entry *pt_entry = &pt->entries[pt_index]; + pt_entry->rw = (flags & MM_PAGE_RW) != 0; + pt_entry->user = (flags & MM_PAGE_USER) != 0; + pt_entry->write_through = 0; + pt_entry->cache_disabled = 0; + pt_entry->accessed = 0; + pt_entry->dirty = 0; + pt_entry->global = 0; + pt_entry->shifted_address = (uintptr_t)virt >> X86_PAGE_TABLE_ADDRESS_SHIFT; + + pt_entry->present = 1; + vm_flush(); + + return 0; } static inline int find_zero_bit(u8 bitfield) @@ -107,13 +153,14 @@ void *get_page(void) for (size_t i = 0; i < pagemap_len; i++) { if (pagemap[i] != 0xff) { int bit = find_zero_bit(pagemap[i]); - if (bit == 8) { + if (bit <= 8) { + page = dynpage_start + (i * 8 + bit) * PAGE_SIZE; + pagemap[i] |= (1 << bit); + } else { kprintf("Throw your computer in the garbage\n"); - break; } - page = page_start + (i * 8 + bit) * PAGE_SIZE; - pagemap[i] |= (1 << bit); + break; } } @@ -132,7 +179,7 @@ void put_page(void *page) kprintf("Unaligned ptr %p passed to put_page()!\n", page); return; } - if (page < page_start || page >= page_end) { + if (page < dynpage_start || page >= dynpage_end) { kprintf("Page %p passed to put_page() is not in the dynamic area!\n", page); return; } @@ -142,7 +189,7 @@ void put_page(void *page) memset(page, 'A', PAGE_SIZE); # endif - size_t page_number = (page - page_start) / PAGE_SIZE; + size_t page_number = (page - dynpage_start) / PAGE_SIZE; size_t index = page_number / 8; int bit = page_number % 8; if ((pagemap[index] & (1 << bit)) == 0) @@ -151,6 +198,109 @@ void put_page(void *page) pagemap[index] &= ~(1 << bit); } +void *virt_to_phys(void *virt) +{ + size_t pd_index = ((uintptr_t)virt >> PAGE_SIZE_LOG2) / 1024; + size_t pt_index = ((uintptr_t)virt >> PAGE_SIZE_LOG2) % 1024; + + struct x86_page_directory *pd = (struct x86_page_directory *)0xfffff000; + if (!pd->entries[pd_index].present) + return NULL; + + struct x86_page_table *pt = &((struct x86_page_table *)0xffc00000)[pd_index]; + if (!pt->entries[pt_index].present) + return NULL; + + uintptr_t address = pt->entries[pt_index].shifted_address << X86_PAGE_TABLE_ADDRESS_SHIFT; + return (void *)(address + ((uintptr_t)virt & ~PAGE_MASK)); +} + +void vm_flush(void) +{ + __asm__ volatile( +" mov %%cr3, %%eax \n" +" mov %%eax, %%cr3 \n" + ::: "eax" + ); +} + +/** + * So, this is going to be a little awkward. Pretty much the entire mm code + * depends on the page bitmap, so we can't use any of it before the bitmap is + * actually present. This means we have to do *everything* by hand here. + */ +static void setup_pagemap(void) +{ + /* + * If we blow up the pagemap we blow up the entire system, so we give + * it its very own page table and map it somewhere far, far away from + * anything else. A page table takes up exactly one page, so we cut + * that away from the usable dynamic page area. So these two lines are + * basically a replacement for a call to get_page(). + */ + void *pt_phys = dynpage_start; + dynpage_start += PAGE_SIZE; + + /* + * As described in multiboot.S, the entry in the page directory points + * to the page directory itself so we can still manipulate it while we + * are in virtual address space. The second-last entry in the page + * directory is still free, so we put the page table for the bitmap there. + * If you do the math, the page table therefore maps addresses + * 0xff800000-0xffbfffff, which is where we start off with the bitmap. + */ + pagemap = (u8 *)0xff800000; + + /* + * Now that we have a physical page for the page table, we need to + * map it to a virtual address so we can fill its entries. + * So this is basically a replacement for a call to map_page(). + */ + struct x86_page_directory *pd = (struct x86_page_directory *)0xfffff000; + pd->entries[1022].shifted_address = (uintptr_t)pt_phys >> X86_PAGE_DIRECTORY_ADDRESS_SHIFT; + pd->entries[1022].rw = 1; + pd->entries[1022].present = 1; + vm_flush(); + + struct x86_page_table *pt = &((struct x86_page_table *)0xffc00000)[1022]; + memset(pt, 0, sizeof(*pt)); + + /* + * Alright, now we can actually fill the page table with entries for + * the bitmap. Again, we just take away pages from the dynpage area, + * until there is enough space. We also need to map those pages to the + * virtual address, of course. + */ + void *pagemap_phys = dynpage_start; + size_t pt_index = 0; + do { + /* + * take one page away from the dynamic area and reserve it for + * the bitmap, and recalculate the required bitmap size in bytes + */ + dynpage_start += PAGE_SIZE; + pagemap_len = ((dynpage_end - dynpage_start) / PAGE_SIZE) / 8; + + /* now add a page table entry for that page */ + struct x86_page_table_entry *pt_entry = &pt->entries[pt_index]; + uintptr_t address = (uintptr_t)pagemap_phys + pt_index * PAGE_SIZE; + pt_entry->shifted_address = address >> X86_PAGE_TABLE_ADDRESS_SHIFT; + pt_entry->present = 1; + pt_entry->rw = 1; + + pt_index++; + } while (pagemap_len > (dynpage_start - pagemap_phys) / 8); + + /* + * Great! We have enough space for the bitmap, and it is mapped + * correctly (at least i hope so). Now all that's left is to flush + * the TLB once again to make the updated entries take effect, and + * clear the bitmap. + */ + vm_flush(); + memset(pagemap, 0, pagemap_len); +} + /* * This file is part of GayBSD. * Copyright (c) 2021 fef <owo@fef.moe>. diff --git a/cmake/config.cmake b/cmake/config.cmake index bff171f..6ff9b1a 100644 --- a/cmake/config.cmake +++ b/cmake/config.cmake @@ -8,7 +8,7 @@ set_property(CACHE ARCH PROPERTY STRINGS ) include("${CMAKE_CURRENT_LIST_DIR}/config-${ARCH}.cmake") -set(KERNEL_ORIGIN "0x100000" CACHE STRING "Physical address where the kernel is loaded") +set(KERNEL_ORIGIN "0x100000" CACHE STRING "Physical address where the kernel is loaded (don't touch this)") set(KERNEL_RELOCATE "0xc0000000" CACHE STRING "Virtual address the kernel is mapped to (don't touch this)") diff --git a/include/gay/mm.h b/include/gay/mm.h index 45a18eb..3a972b9 100644 --- a/include/gay/mm.h +++ b/include/gay/mm.h @@ -45,13 +45,23 @@ void *kmalloc(size_t size, enum mm_flags flags); */ void kfree(void *ptr); +enum mm_page_flags { + MM_PAGE_PRESENT = (1 << 0), + MM_PAGE_RW = (1 << 1), + MM_PAGE_USER = (1 << 2), + MM_PAGE_ACCESSED = (1 << 3), + MM_PAGE_DIRTY = (1 << 4), + MM_PAGE_GLOBAL = (1 << 5), + MM_PAGE_NOCACHE = (1 << 6), +}; + /** * @brief Get a free memory page. * * This is only called internally by `kmalloc()`, don't use. * Must be deallocated with `put_page()` after use. * - * @returns A pointer to the beginning of the page, or `NULL` if OOM + * @returns A pointer to the beginning of the (physical) page address, or `NULL` if OOM */ void *get_page(void); @@ -64,6 +74,22 @@ void *get_page(void); */ void put_page(void *page); +/** + * @brief Map a page in physical memory to a virtual address. + * Remember that if `vm` is the memory map currently in use, you will most + * likely need to call `vm_update()` when you've finished mapping everything + * to flush the TLB. + * + * @param phys Physical address of the page + * @param virt Virtual address to map the page to + * @param flags Flags to apply to the page + * @returns 0 on success, or `-ENOMEM?` if OOM (for allocating new page tables) + */ +int map_page(void *phys, void *virt, enum mm_page_flags flags); + +/** @brief Flush the TLB. */ +void vm_flush(void); + /** * @brief Initialize the memory allocator. *