Xeno wrote:
Maybe im doing it wrong
With that structure, definitely.
The nice thing about page tables is that each of them takes up a full page. So if you just request a page from your PMM, you can use the entire thing. So that is how I deal with page tables: Each of them is exactly one page, interpreted as array of 64-bit numbers. I just allocate them as needed.
The thing you're stuck on is what everyone stumbles over sooner or later. You need to access physical memory to implement paging, but can only access virtual memory. The most common solutions are to map all of physical memory linearly to some place (which is easy on x86_64, where by definition virtual memory is always at least 4 times larger than physical memory), or to use a known temporary mapping, or to use recursive paging. All of these you initialize when you initialize paging.
My
bootloader (running without paging in 32-bit mode) has this:
Code:
#define PF_PRESENT 1
#define PF_WRITE 2
#define PF_USER 4
#define PF_NX 0x8000000000000000ull
static void mmap(uint64_t vaddr, uint64_t paddr, size_t len, uint64_t flags)
{
static uint64_t *pml4;
static uint32_t watermark;
int idx[4], i;
uint64_t *p;
if (!pml4)
{
pml4 = (void*)0x1000;
memset(pml4, 0, 0x1000);
watermark = 0x2000;
}
if (vaddr & 0xfff)
{
len += vaddr & 0xfff;
vaddr &= 0xfffffffffffff000;
paddr &= 0xfffffffffffff000;
}
len = (len + 4095) & 0xfffffffffffff000;
while (len >= 4096)
{
p = pml4;
idx[0] = (vaddr >> 39) & 0x1ff;
idx[1] = (vaddr >> 30) & 0x1ff;
idx[2] = (vaddr >> 21) & 0x1ff;
idx[3] = (vaddr >> 12) & 0x1ff;
for (i = 0; i < 3 + (paddr & 1); i++)
{
if (!(p[idx[i]] & PF_PRESENT))
{
if (watermark >= 0xA0000)
panic("Out of memory for page table");
p[idx[i]] = watermark | PF_PRESENT | PF_WRITE;
memset((void*)watermark, 0, 0x1000);
watermark += 0x1000;
}
p = (void*)(uintptr_t)(p[idx[i]] & 0xfffff000);
}
if (!(paddr & 1))
p[idx[3]] = paddr | flags;
len -= 4096;
vaddr += 4096;
paddr += 4096;
}
}
With this, I add all the page maps I need to the initial page table, then jump to the kernel. And you can implement all of the above schemes in almost this way. With the linear map I just do
Code:
if (mmap_len)
{
uint32_t *mm = (uint32_t*)(mmap_ptr - 4);
for (int i = 0; i < mmap_len; i++)
{
if (mm[5] == MULTIBOOT_MEMORY_AVAILABLE)
mmap(0xffff800000000000 | mm[1] | ((uint64_t)mm[2] << 32), mm[1] | ((uint64_t)mm[2] << 32), mm[3] | ((uint64_t)mm[4]), PF_PRESENT | PF_WRITE);
mm = (uint32_t*)((char*)mm + *mm);
}
}
With the known temporary mapping, you map one specific page table into virtual memory, and that page table has one entry that does not get shared to other CPUs, leading to one address you can always use for temporary physical mappings. So you have two constants for the scheme: The address of the known page table and the temporary address. In the above scheme, it might be best to do something like
Code:
mmap(TEMPORARY_ADDRESS, -1, 0x1000, PF_PRESENT|PF_WRITE);
uint64_t pt = pml4[(TEMPORARY_ADDRESS >> 39) & 0x1ff];
pt = ((uint64_t*)(pt & 0xfffff000))[(TEMPORARY_ADDRESS >> 30) & 0x1ff];
pt = ((uint64_t*)(pt & 0xfffff000))[(TEMPORARY_ADDRESS >> 21) & 0x1ff];
mmap(PAGE_TABLE_ADDRESS, pt & 0xfffff000, 0x1000, PF_PRESENT | PF_WRITE);
Something along these lines anyway.
With a recursive mapping, you set one particular entry of the PML4 to point back at the PML4. I will not provide code for this, because I dislike the approach. You end up adding uninitialized memory to the paging structures, which can lead to the processor adding all sorts of nonsense to the TLB that would need to be cleared out once the page is initialized.
I will say that of these options, the linear mapping is by far the easiest to use, because every address in physical RAM just has a virtual address associated with it, and it is unchanging and easy to calculate. With the know temporary, you end up having to juggle the address around constantly. It's not that it doesn't work, it is just harder than it needs to be. And the recursive mapping I already criticized above.