I'm working through the motions of getting a proper 64 bit environment set up for my kernel. The Limine bootloader dumps me into long mode, but I want to set up my own structures as much as possible. I have a basic bitmap-based physical frame allocator that distributes physical addresses and zeroes the memory out if specified. I'm flip flopping between triple faulting after setting CR3 (QEMU log shows that my mapping isn't correct, as we continually fault on the instruction after setting CR3) or a page fault when trying to write to my structures (just a skill issue).
If I understand the 64 bit paging structure properly, CR3 will be loaded according to the AMD64 spec, where:
[63:52] are zero
[51:12] is the physical memory address for the PML4 table, and
[11:0] somewhat reserved (though I am leaving unchanged),
meaning that any update to CR3 should be structured as such (some tutorials and example code I have seen do a direct move into CR3 of their PML4 pointer, but the AMD documentation doesn't seem to support that?)
The PML4 pointer is a 4kB page such that every 8 bytes is structured according to the document (some status and flag bits, bitfield for pointer to PDP table), and so on and so forth until we get down to PTEs who point to an actual physical frame. I have a couple clarification questions:
- Are the pointers to the next-level page structure specified according to AMD's document physical addresses that are right shifted by 12, or the raw physical address?
- I should be shifting the final physical address >> 12 before storing it into the PTE, correct?
I think I have some logic issues somewhere in my mapping code, which I have pasted below. I've been staring at this for a while and believe it's something simple I am missing but I haven't been able to nail it down.
My initialization code:
Code: Select all
void initialize_paging(struct limine_memmap_response* r, struct limine_file* kr) {
// We'll have to put PML4 in some dedicated physical address.
// Initially zeroed-out.
// PML4 itself has to be physically mapped as well.
pml4_addr = frame_alloc(true);
pml4e* table = (pml4e*)(pml4_addr + get_hhdmoff());
map_page((uint64_t)table, pml4_addr, false, true, true, false);
for (uint8_t i = 0; i < r->entry_count; i++) {
struct limine_memmap_entry* e = r->entries[i];
// Will map kernel VMA + xxx -> kernel PA
if (e->type == LIMINE_MEMMAP_KERNEL_AND_MODULES) {
for (uint64_t bytes = 0; bytes < e->length; bytes += FRAME_ALLOCATION_SIZE) {
map_page(KERNEL_VMA + bytes, e->base + bytes, true, true, false, false);
map_page(get_hhdmoff() + bytes, e->base + bytes, true, true, false, false);
}
}
// We will say the framebuffer starts at the beginning of the page following end of kernel
if (e->type == LIMINE_MEMMAP_FRAMEBUFFER) {
for (uint64_t bytes = 0; bytes < e->length; bytes += FRAME_ALLOCATION_SIZE) {
map_page(pg_round_up((uint64_t)&KERNEL_END) + bytes, e->base + bytes, false, true, true, true);
}
}
}
set_cr3(pml4_addr);
logf(INFO, "Initialized kernel and framebuffer page tables. PML4 beginning at 0x%lx\n", pml4_addr);
}
(EDIT: the initial page fault crash has been fixed, I was incorrectly referencing KERNEL_END.
Code: Select all
// Maps a single page vaddr -> paddr
void map_page(uint64_t vaddr, uint64_t paddr, bool is_supervisor, bool writable, bool no_execute, bool writethru) {
pml4e* pml4table = (pml4e*)(pml4_addr + get_hhdmoff());
uint64_t pdpt;
if (!pml4table[PML4_INDEX(vaddr)].present) {
pdpt = frame_alloc(true);
pml4table[PML4_INDEX(vaddr)].present = true;
pml4table[PML4_INDEX(vaddr)].pdpe_ptr = pdpt;
pdpt += get_hhdmoff();
} else {
pdpt = pml4table[PML4_INDEX(vaddr)].pdpe_ptr + get_hhdmoff();
}
pdpe* pdptable = (pdpe*)pdpt;
uint64_t pd;
if (!pdptable[PDPT_INDEX(vaddr)].present) {
pd = frame_alloc(true);
pdptable[PDPT_INDEX(vaddr)].present = true;
pdptable[PDPT_INDEX(vaddr)].pde_ptr = pd;
pd += get_hhdmoff();
} else {
pd = pdptable[PDPT_INDEX(vaddr)].pde_ptr + get_hhdmoff();
}
pde* pdetable = (pde*)pd;
uint64_t pt;
if (!pdetable[PD_INDEX(vaddr)].present) {
pt = frame_alloc(true);
pdetable[PDPT_INDEX(vaddr)].present = true;
pdetable[PDPT_INDEX(vaddr)].pte_ptr = pt;
pd += get_hhdmoff();
} else {
pt = pdetable[PD_INDEX(vaddr)].pte_ptr + get_hhdmoff();
}
pte* ptable = (pte*)pt;
ptable[PT_INDEX(vaddr)].present = 1;
ptable[PT_INDEX(vaddr)].rw = writable;
ptable[PT_INDEX(vaddr)].us = is_supervisor;
ptable[PT_INDEX(vaddr)].pwt = writethru;
ptable[PT_INDEX(vaddr)].frame = paddr >> 12;
}
This current iteration of the code page faults on a physical address (my earlier mentioned skill issue), but some prior commits made it through initial setup and began page fault -> triple faulting on writing CR3, which is why I believe my mapping to be incorrect.
Are there any glaring issues with this code?
Thanks!