Setting up a provisional identical mapping for long mode, using 1G huge page on PDP(PUD).
x86_64 long mode mandates paging. Here we set up a provisional identical mapping for the early booting.
Math
Each PUD entry (on the 3rd level) maps to 1G memory region. We could set up
512 GiB mapping with only one single 3rd level table (i.e. no need for page
directory or page table), this is ideal for a early boot provisional mapping.
We need:
- one PML4 table (and only 1 single entry pointing to the PUD table), that's 4KiB in size
- one PUD table, populated with 512 entries, each pointing to 1G memory region, this is another 4KiB in size.
- in total, we need to setup 2 tables and 1 + 512 = 513 entries.
assembly example
[SECTION .text]
[BITS 32]
; skipping other code.
setup_paging:
; PML4 (Page Map Level 4 / 1st level)
mov eax, pdp
or eax, 0xf
mov dword [pml4+0], eax
mov dword [pml4+4], 0
; PDPE flags
; start-address bytes bit [30:31] + flags
mov eax, 0x0 | 0x87
; start-address bytes bit [32:38]
mov ebx, 0
mov ecx, 0
fill_tables2:
; fill one single PDP table, with 1G pages,
; 512 PDPE maps to 512 GB
cmp ecx, MAX_MEM
je fill_tables2_done
; low bytes
mov dword [pdp + 8*ecx + 0], eax
; high bytes
mov dword [pdp + 8*ecx + 4], ebx
; 1G per entry
add eax, 0x40000000
; increment bit[32:38] when eax overflows
adc ebx, 0
inc ecx
ja fill_tables2
fill_tables2_done:
; set base pointer to PML4
mov eax, pml4
mov cr3, eax
ret
[SECTION .pagetables]
[GLOBAL pml4]
[GLOBAL pdp]
pml4:
resb 4096
alignb 4096
pdp:
resb 4096
alignb 4096
Note that, each entry is 64bit, but when we are setting up the page tables, we
are not in long mode yet, meaning that only 32bit registers/instructions are
available. See how the code use both eax
and ebx
to keep the address with
the carry flag (adc
).
Also make sure to link and align the pagetables section properly. e.g.
/* global page table for 64-bit long mode */
.global_pagetable ALIGN(4096) (NOLOAD) :
{
*(".global_pagetable")
}