/** * HURL, the HOS Unreal Region Locator. * * HURL provides virtual memory management for HULK. */ module hulk.hurl.hurl; public import hulk.pagetable; import hulk.cpu; import hulk.hippo; import hulk.memory; import hulk.klog; import hulk.bootinfo; import hulk.header; import hulk.linker_addresses; import hulk.util; struct Hurl { /** HULK base address. */ enum ulong HULK_BASE = 0xFFFF_8000_0000_0000u; /** HULK stack top address. */ enum ulong HULK_STACK_TOP = 0xFFFF_8001_0000_0000u; /** HULK framebuffer address. */ enum ulong HULK_FRAMEBUFFER = 0xFFFF_8001_0000_0000u; /** A1 allocator range base address. */ enum ulong A1_BASE = 0xFFFF_8002_0000_0000u; /** * Pointer to the base page table. */ private static __gshared PageTable * m_pt_base; /** * Build HULK page tables. * * @param physical_address_limit * Limit of physical memory. */ private static void build_page_tables(HulkHeader * header, size_t physical_address_limit) { m_pt_base = allocate_pt(); /* Identity map all physical RAM. */ map_range(0u, 0u, physical_address_limit, PT_WRITABLE | PT_NO_EXECUTE); ulong phys_address = header.bootinfo.hulk_phys; ulong virt_address = HULK_BASE; /* Map HULK header region. */ map_range(virt_address, phys_address, LinkerAddresses.hulk_header_size, PT_NO_EXECUTE); phys_address += LinkerAddresses.hulk_header_size; virt_address += LinkerAddresses.hulk_header_size; /* Map HULK text region. */ map_range(virt_address, phys_address, LinkerAddresses.hulk_text_size, 0u); phys_address += LinkerAddresses.hulk_text_size; virt_address += LinkerAddresses.hulk_text_size; /* Map HULK rodata region. */ map_range(virt_address, phys_address, LinkerAddresses.hulk_rodata_size, PT_NO_EXECUTE); phys_address += LinkerAddresses.hulk_rodata_size; virt_address += LinkerAddresses.hulk_rodata_size; /* Map HULK data region. */ map_range(virt_address, phys_address, LinkerAddresses.hulk_data_size, PT_WRITABLE | PT_NO_EXECUTE); virt_address += LinkerAddresses.hulk_data_size; /* Map HULK BSS region. */ map_range(virt_address, header.bootinfo.bss_phys, LinkerAddresses.hulk_bss_size, PT_WRITABLE | PT_NO_EXECUTE); /* Map HULK stack. */ map_range(HULK_STACK_TOP - header.stack_size, header.bootinfo.stack_phys, header.stack_size, PT_WRITABLE | PT_NO_EXECUTE); /* Map device framebuffer. */ map_range(HULK_FRAMEBUFFER, cast(ulong)header.bootinfo.fb.buffer, header.bootinfo.fb.height * header.bootinfo.fb.stride * 4u, PT_WRITABLE | PT_NO_EXECUTE); /* Map framebuffer buffer1. */ map_range(cast(ulong)header.bootinfo.fb_buffer1_phys, cast(ulong)header.bootinfo.fb_buffer1_phys, header.bootinfo.fb.height * header.bootinfo.fb.stride * 4u, PT_WRITABLE | PT_NO_EXECUTE); write_cr3(cast(ulong)m_pt_base); } /** * Reclaim the pages that were used for the bootloader page tables. * * @param pt * The page table to reclaim pages from. * @param level * Page table level (internally used while recursing the page tables). * * @return Number of pages reclaimed. */ private static size_t reclaim_bootloader_page_table_pages(PageTable * pt, size_t level = 0u) { map(pt, pt, PT_WRITABLE | PT_NO_EXECUTE); size_t reclaimed_pages; for (size_t i = 0u; i < PageTable.N_ENTRIES; i++) { PageTableEntry pte = (*pt)[i]; if (pte.present && (level < 2u) && !pte.huge) { /* For the first two levels of page tables, first * recurse and free pages from the lower level page * tables before reclaiming this entry. */ reclaimed_pages += reclaim_bootloader_page_table_pages(pte.follow(), level + 1u); } } Hippo.free_page(pt); reclaimed_pages++; return reclaimed_pages; } /** * Initialize HURL. * * @param bootinfo HULK boot information structure. */ public static void initialize(HulkHeader * header) { PageTable * bootloader_pt_base = cast(PageTable *)read_cr3(); /* * Ok, what we do here is iterate through all of the memory map regions * in the bootinfo memory_map array and mark each page that is not in * use in a reserved range as available. Within the available memory * regions, we have to watch out for the following items: * 1) HULK binary (header + text + rodata + data) * 2) Framebuffer * In addition to these ranges, there are also pages for the following * purposes that are not represented in the memory map from the * bootloader: * 3) HULK BSS * 4) HULK stack * 5) bootloader page table pages * 6) framebuffer buffer1 */ size_t usable_memory; size_t physical_address_limit; const(size_t) fb_size = round_up_power_2(header.bootinfo.fb.height * header.bootinfo.fb.stride * 4u, PAGE_SIZE); ulong[2][2] reserved = [ [header.bootinfo.hulk_phys, LinkerAddresses.hulk_binary_size], [cast(ulong)header.bootinfo.fb.buffer, fb_size], ]; for (size_t ri = 0u; ri < reserved.length; ri++) { reserved[ri][1] += reserved[ri][0]; } for (size_t bii = 0u; bii < header.bootinfo.memory_map_count; bii++) { if ((header.bootinfo.memory_map[bii].type == BootInfo.MemoryRegion.Type.Bootloader) || (header.bootinfo.memory_map[bii].type == BootInfo.MemoryRegion.Type.Conventional)) { ulong phys = header.bootinfo.memory_map[bii].base; ulong phys_end = phys + header.bootinfo.memory_map[bii].size; if (phys_end > physical_address_limit) { physical_address_limit = phys_end; } usable_memory += header.bootinfo.memory_map[bii].size; while (phys < phys_end) { bool is_reserved = false; for (size_t ri = 0u; ri < reserved.length; ri++) { if ((reserved[ri][0] <= phys) && (phys < reserved[ri][1])) { is_reserved = true; break; } } if (!is_reserved) { Hippo.free_page(phys); } phys += PAGE_SIZE; } } } /* * Now that we have available physical pages to allocate from, we can * build new page tables to replace the bootloader page tables. */ build_page_tables(header, physical_address_limit); /* * After we have switched to the newly constructed page tables, we can * iterate through and free the bootloader page tables. They are most * likely already mapped because we just identity mapped every page up * to the physical_address_limit determined above. But just in case the * bootloader used the last pages of RAM for the page table pages, we * will make sure to map them as we traverse them. */ size_t reclaimed_bootloader_page_table_memory = reclaim_bootloader_page_table_pages(bootloader_pt_base) << 10u; usable_memory += reclaimed_bootloader_page_table_memory; usable_memory += LinkerAddresses.hulk_bss_size; usable_memory += header.stack_size; usable_memory += fb_size; Klog.writefln("Usable memory: %uKB", usable_memory >> 10u); Klog.writefln("Kernel size: %uKB", (LinkerAddresses.hulk_binary_size + LinkerAddresses.hulk_bss_size + header.stack_size) >> 10u); } public static void map(ulong virtual, ulong physical, ulong flags) { PageTable * pt = m_pt_base; for (size_t level = 0; level < 4u; level++) { PageTableEntry * ppte = pt.entry(virtual, level); if (level < 3u) { PageTableEntry pte = *ppte; if (pte.present) { pt = pte.follow(); } else { PageTable * next_pt = allocate_pt(); *ppte = PageTableEntry(next_pt, PT_WRITABLE | PT_PRESENT); pt = next_pt; } } else { *ppte = PageTableEntry(cast(ulong)physical, flags | PT_PRESENT); } } } public static void map(T, U)(T virtual, U physical, ulong flags) { map(cast(ulong)virtual, cast(ulong)physical, flags); } public static void map_range(ulong virtual, ulong physical, size_t length, ulong flags) { size_t end = virtual + length; while (virtual < end) { map(virtual, physical, flags); virtual += PAGE_SIZE; physical += PAGE_SIZE; } } public static void map_range(T, U)(T virtual, U physical, size_t length, ulong flags) { map_range(cast(ulong)virtual, cast(ulong)physical, length, flags); } public static void identity_map_range(ulong address, size_t length, ulong flags) { size_t end = address + length; for (size_t page = address & 0xFFFF_FFFF_FFFF_F000u; page < end; page += PAGE_SIZE) { map(page, page, flags); } } public static void identity_map_range(T)(T address, size_t length, ulong flags) { identity_map_range(cast(ulong)address, length, flags); } public static void debug_lookup(void * address) { Klog.writefln("Debugging page table lookup of 0x%x", address); PageTable * pt = m_pt_base; for (size_t level = 0; level < 4u; level++) { PageTableEntry pte = *pt.entry(address, level); Klog.writefln("Level %u, index %u, entry = 0x%x", level, pt.index(address, level), pte); if (pte.present) { pt = pte.follow(); } else { break; } if (pte.huge) { break; } } } private static PageTable * allocate_pt() { PageTable * pt = cast(PageTable *)Hippo.allocate_page(); memset64(pt, 0u, PAGE_SIZE / 8u); return pt; } }