316 lines
11 KiB
D
316 lines
11 KiB
D
/**
|
|
* 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;
|
|
}
|
|
}
|