Skip to content

Commit 491d208

Browse files
committed
pvh: Add code to transition from 32-bit to 64-bit
PVH starts in 32-bit mode, so we have to transition to 64-bit mode before we can start running Rust code. As we have not yet initialized the stack, we can only use registers and static memory. This transition does the following: - Sets up page tables to identity map 2 MiB - Loads page tables into CR3 - Sets CR4.PAE, EFER.LME, and CRO.PG - Sets up a 64-bit GDT - Long Jumps to 64-bit code Signed-off-by: Joe Richey <[email protected]>
1 parent 654fb22 commit 491d208

File tree

2 files changed

+78
-0
lines changed

2 files changed

+78
-0
lines changed

src/asm/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
global_asm!(include_str!("ram32.s"));
12
global_asm!(include_str!("ram64.s"));

src/asm/ram32.s

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
.section .text, "ax"
2+
.code32
3+
4+
pvh32_start:
5+
# Stash the PVH start_info struct in %rdi.
6+
movl %ebx, %edi
7+
# Zero out %rsi, its value is unspecificed in the PVH Boot Protocol.
8+
xorl %esi, %esi
9+
10+
ram32_start:
11+
# Indicate (via serial) that we are executing out of RAM
12+
movw $0x3f8, %dx
13+
movb $'R', %al
14+
outb %al, %dx
15+
16+
setup_page_tables:
17+
# First L2 entry identity maps [0, 2 MiB)
18+
movl $0b10000011, (L2_TABLES) # huge (bit 7), writable (bit 1), present (bit 0)
19+
# First L3 entry points to L2 table
20+
movl $L2_TABLES, %eax
21+
orb $0b00000011, %al # writable (bit 1), present (bit 0)
22+
movl %eax, (L3_TABLE)
23+
# First L4 entry points to L3 table
24+
movl $L3_TABLE, %eax
25+
orb $0b00000011, %al # writable (bit 1), present (bit 0)
26+
movl %eax, (L4_TABLE)
27+
28+
enable_paging:
29+
# Load page table root into CR3
30+
movl $L4_TABLE, %eax
31+
movl %eax, %cr3
32+
33+
# Set CR4.PAE (Physical Address Extension)
34+
movl %cr4, %eax
35+
orb $0b00100000, %al # Set bit 5
36+
movl %eax, %cr4
37+
# Set EFER.LME (Long Mode Enable)
38+
movl $0xC0000080, %ecx
39+
rdmsr
40+
orb $0b00000001, %ah # Set bit 8
41+
wrmsr
42+
# Set CRO.PG (Paging)
43+
movl %cr0, %eax
44+
orl $(1 << 31), %eax
45+
movl %eax, %cr0
46+
47+
# Indicate (via serial) that we have enabled paging
48+
movw $0x3f8, %dx
49+
movb $'P', %al
50+
outb %al, %dx
51+
52+
jump_to_64bit:
53+
# We are now in 32-bit compatibility mode. To enter 64-bit mode, we need to
54+
# load a 64-bit code segment into our GDT.
55+
lgdtl gdt64_ptr
56+
# Set CS to a 64-bit segment and jump to 64-bit code.
57+
ljmpl $(code64_desc - gdt64_start), $ram64_start
58+
59+
gdt64_ptr:
60+
.short gdt64_end - gdt64_start - 1 # GDT length is actually (length - 1)
61+
.long gdt64_start
62+
gdt64_start:
63+
# First descriptor is null
64+
.quad 0
65+
code64_desc:
66+
# For 64-bit code descriptors, all bits except the following are ignored:
67+
# - CS.A=1 (bit 40) segment is accessed, prevents a write on first use.
68+
# - CS.R=1 (bit 41) segment is readable. (this might not be necessary)
69+
# - CS.C=1 (bit 42) segment is conforming. (this might not be necessary)
70+
# - CS.E=1 (bit 43) required, we are a executable code segment.
71+
# - CS.S=1 (bit 44) required, we are not a system segment.
72+
# - CS.DPL=0 (bits 45/46) we are using this segment in Ring 0.
73+
# - CS.P=1 (bit 47) required, the segment is present.
74+
# - CS.L=1 (bit 53) required, we are a 64-bit (long mode) segment.
75+
# - CS.D=0 (bit 54) required, CS.L=1 && CS.D=1 is resevered for future use.
76+
.quad (1<<40) | (1<<41) | (1<<42) | (1<<43) | (1<<44) | (1<<47) | (1<<53)
77+
gdt64_end:

0 commit comments

Comments
 (0)