-
Notifications
You must be signed in to change notification settings - Fork 0
/
stage_3.s
208 lines (174 loc) · 5.75 KB
/
stage_3.s
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# Forked from bootloader 0.9.22, copyright 2018 Philipp Oppermann.
#
# Use of the original source code is governed by the MIT
# license that can be found in the LICENSE.orig file.
#
# Subsequent work copyright 2022 The Firefly Authors.
#
# Use of new and modified source code is governed by a BSD 3-clause
# license that can be found in the LICENSE file.
.section .boot, "awx"
.code32
# This stage performs some checks on the CPU (cpuid, long mode), sets up an
# initial page table mapping (identity map the bootloader, map the P4
# recursively, map the kernel blob to 4MB), enables paging, switches to long
# mode, and jumps to stage_4.
stage_3:
mov bx, 0x10
mov ds, bx # set data segment
mov es, bx # set extra segment
mov ss, bx # set stack segment
mov si, offset boot_third_stage_str
call uart_println
check_cpu:
call check_cpuid
call check_long_mode
cli # disable interrupts
lidt zero_idt # Load a zero length IDT so that any NMI causes a triple fault.
# enter long mode
set_up_page_tables:
# zero out buffer for page tables
mov edi, offset __page_table_start
mov ecx, offset __page_table_end
sub ecx, edi
shr ecx, 2 # one stosd zeros 4 bytes -> divide by 4
xor eax, eax
rep stosd
# p4
mov eax, offset _p3
or eax, (1 | 2)
mov [_p4], eax
# p3
mov eax, offset _p2
or eax, (1 | 2)
mov [_p3], eax
# p2
mov eax, offset _p1
or eax, (1 | 2)
mov [_p2], eax
mov eax, (0x400000 | 1 | 2 | (1 << 7))
mov ecx, 2
mov edx, offset _kernel_size_addr # Load the address of the kernel size.
mov edx, [edx] # Load the kernel size by dereferencing the address.
add edx, 0x400000 # start address
add edx, 0x200000 - 1 # align up
shr edx, 12 + 9 # end huge page number
map_p2_table:
mov [_p2 + ecx * 8], eax
add eax, 0x200000
add ecx, 1
cmp ecx, edx
jb map_p2_table
# p1
# start mapping from __page_table_start, as we need to be able to access
# the p4 table from rust. stop mapping at __bootloader_end
mov eax, offset __page_table_start
and eax, 0xfffff000
or eax, (1 | 2)
mov ecx, offset __page_table_start
shr ecx, 12 # start page number
mov edx, offset __bootloader_end
add edx, 4096 - 1 # align up
shr edx, 12 # end page number
map_p1_table:
mov [_p1 + ecx * 8], eax
add eax, 4096
add ecx, 1
cmp ecx, edx
jb map_p1_table
map_framebuffer:
enable_paging:
# Write back cache and add a memory fence. I'm not sure if this is
# necessary, but better be on the safe side.
wbinvd
mfence
# load P4 to cr3 register (cpu uses this to access the P4 table)
mov eax, offset _p4
mov cr3, eax
# enable PAE-flag in cr4 (Physical Address Extension)
mov eax, cr4
or eax, (1 << 5)
mov cr4, eax
# set the long mode bit in the EFER MSR (model specific register)
mov ecx, 0xC0000080
rdmsr
or eax, (1 << 8)
wrmsr
# enable paging in the cr0 register
mov eax, cr0
or eax, (1 << 31)
mov cr0, eax
load_64bit_gdt:
lgdt gdt_64_pointer # Load GDT.Pointer defined below.
jump_to_long_mode:
push 0x8
mov eax, offset stage_4
push eax
retf # Load CS with 64 bit segment and flush the instruction cache
spin_here:
jmp spin_here
check_cpuid:
# Check if CPUID is supported by attempting to flip the ID bit (bit 21)
# in the FLAGS register. If we can flip it, CPUID is available.
# Copy FLAGS in to EAX via stack
pushfd
pop eax
# Copy to ECX as well for comparing later on
mov ecx, eax
# Flip the ID bit
xor eax, (1 << 21)
# Copy EAX to FLAGS via the stack
push eax
popfd
# Copy FLAGS back to EAX (with the flipped bit if CPUID is supported)
pushfd
pop eax
# Restore FLAGS from the old version stored in ECX (i.e. flipping the
# ID bit back if it was ever flipped).
push ecx
popfd
# Compare EAX and ECX. If they are equal then that means the bit
# wasn't flipped, and CPUID isn't supported.
cmp eax, ecx
je no_cpuid
ret
no_cpuid:
mov esi, offset no_cpuid_str
call uart_println
no_cpuid_spin:
hlt
jmp no_cpuid_spin
check_long_mode:
# test if extended processor info in available
mov eax, 0x80000000 # implicit argument for cpuid
cpuid # get highest supported argument
cmp eax, 0x80000001 # it needs to be at least 0x80000001
jb no_long_mode # if it's less, the CPU is too old for long mode
# use extended info to test if long mode is available
mov eax, 0x80000001 # argument for extended processor info
cpuid # returns various feature bits in ecx and edx
test edx, (1 << 29) # test if the LM-bit is set in the D-register
jz no_long_mode # If it's not set, there is no long mode
ret
no_long_mode:
mov esi, offset no_long_mode_str
call uart_println
no_long_mode_spin:
hlt
jmp no_long_mode_spin
.align 4
zero_idt:
.word 0
.byte 0
gdt_64:
.quad 0x0000000000000000 # Null Descriptor - should be present.
.quad 0x00209A0000000000 # 64-bit code descriptor (exec/read).
.quad 0x0000920000000000 # 64-bit data descriptor (read/write).
.align 4
.word 0 # Padding to make the "address of the GDT" field aligned on a 4-byte boundary
gdt_64_pointer:
.word gdt_64_pointer - gdt_64 - 1 # 16-bit Size (Limit) of GDT.
.long gdt_64 # 32-bit Base Address of GDT. (CPU will zero extend to 64-bit)
boot_third_stage_str: .asciz "Booting (third stage)..."
no_cpuid_str: .asciz "Error: CPU does not support CPUID"
no_long_mode_str: .asciz "Error: CPU does not support long mode"