-#### switch from real to protected mode
-#### The segments in GDT allow all of physical memory to be accessed.
-#### Furthermore, the segments have base addresses of 0, so that the
-#### segment translation is a NOP, ie. virtual addresses are identical to
-#### their physical addresses. With this setup, it appears to this code
-#### that it is running directly on physical memory.
+#### Create temporary page directory and page table and set page
+#### directory base register.
+
+# Create page directory at 64 kB and fill with zeroes.
+ mov ax, 0x1000
+ mov es, ax
+ sub eax, eax
+ sub edi, edi
+ mov ecx, 0x400
+ rep stosd
+
+# Add PDEs to point to PTEs for the first 64 MB of RAM.
+# Also add identical PDEs starting at LOADER_PHYS_BASE.
+# See [IA32-v3] section 3.7.6 for a description of the bits in eax.
+
+# A bug in some versions of GAS prevents us from using the straightforward
+# mov es:[di + LOADER_PHYS_BASE / 1024 / 1024], eax
+# so we calculate the displacement in bx instead.
+
+ mov eax, 0x11007
+ mov ecx, 0x11
+ sub di, di
+ mov ebx, LOADER_PHYS_BASE
+ shr ebx, 20
+1: mov es:[di], eax
+ mov es:[bx + di], eax
+ add di, 4
+ add eax, 0x1000
+ loop 1b
+
+# Set up one-to-map linear to physical map for the first 64 MB of RAM.
+# See [IA32-v3] section 3.7.6 for a description of the bits in eax.
+
+ mov ax, 0x1100
+ mov es, ax
+ mov eax, 0x7
+ mov cx, 0x4000
+ sub di, di
+1: mov es:[di], eax
+ add di, 4
+ add eax, 0x1000
+ loop 1b
+
+# Set page directory base register.
+
+ mov eax, 0x10000
+ mov cr3, eax