-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstart.S
executable file
·204 lines (152 loc) · 5.1 KB
/
start.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
#include "threads/loader.h"
#### Kernel startup code.
#### The loader (in loader.S) loads the kernel at physical address
#### 0x20000 (128 kB) and jumps to "start", defined here. This code
#### switches from real mode to 32-bit protected mode and calls
#### main().
/* Flags in control register 0. */
#define CR0_PE 0x00000001 /* Protection Enable. */
#define CR0_EM 0x00000004 /* (Floating-point) Emulation. */
#define CR0_PG 0x80000000 /* Paging. */
#define CR0_WP 0x00010000 /* Write-Protect enable in kernel mode. */
.section .start
# The following code runs in real mode, which is a 16-bit code segment.
.code16
.func start
.globl start
start:
# The loader called into us with CS = 0x2000, SS = 0x0000, ESP = 0xf000,
# but we should initialize the other segment registers.
mov $0x2000, %ax
mov %ax, %ds
mov %ax, %es
# Set string instructions to go upward.
cld
#### Get memory size, via interrupt 15h function 88h (see [IntrList]),
#### which returns AX = (kB of physical memory) - 1024. This only
#### works for memory sizes <= 65 MB, which should be fine for our
#### purposes. We cap memory at 64 MB because that's all we prepare
#### page tables for, below.
movb $0x88, %ah
int $0x15
addl $1024, %eax # Total kB memory
cmp $0x10000, %eax # Cap at 64 MB
jbe 1f
mov $0x10000, %eax
1: shrl $2, %eax # Total 4 kB pages
addr32 movl %eax, init_ram_pages - LOADER_PHYS_BASE - 0x20000
#### Enable A20. Address line 20 is tied low when the machine boots,
#### which prevents addressing memory about 1 MB. This code fixes it.
# Poll status register while busy.
1: inb $0x64, %al
testb $0x2, %al
jnz 1b
# Send command for writing output port.
movb $0xd1, %al
outb %al, $0x64
# Poll status register while busy.
1: inb $0x64, %al
testb $0x2, %al
jnz 1b
# Enable A20 line.
movb $0xdf, %al
outb %al, $0x60
# Poll status register while busy.
1: inb $0x64, %al
testb $0x2, %al
jnz 1b
#### Create temporary page directory and page table and set page
#### directory base register.
# Create page directory at 0xf000 (60 kB) and fill with zeroes.
mov $0xf00, %ax
mov %ax, %es
subl %eax, %eax
subl %edi, %edi
movl $0x400, %ecx
rep stosl
# Add PDEs to point to page tables for the first 64 MB of RAM.
# Also add identical PDEs starting at LOADER_PHYS_BASE.
# See [IA32-v3a] section 3.7.6 "Page-Directory and Page-Table Entries"
# for a description of the bits in %eax.
movl $0x10007, %eax
movl $0x11, %ecx
subl %edi, %edi
1: movl %eax, %es:(%di)
movl %eax, %es:LOADER_PHYS_BASE >> 20(%di)
addw $4, %di
addl $0x1000, %eax
loop 1b
# Set up page tables for one-to-map linear to physical map for the
# first 64 MB of RAM.
# See [IA32-v3a] section 3.7.6 "Page-Directory and Page-Table Entries"
# for a description of the bits in %eax.
movw $0x1000, %ax
movw %ax, %es
movl $0x7, %eax
movl $0x4000, %ecx
subl %edi, %edi
1: movl %eax, %es:(%di)
addw $4, %di
addl $0x1000, %eax
loop 1b
# Set page directory base register.
movl $0xf000, %eax
movl %eax, %cr3
#### Switch to protected mode.
# First, disable interrupts. We won't set up the IDT until we get
# into C code, so any interrupt would blow us away.
cli
# Protected mode requires a GDT, so point the GDTR to our GDT.
# We need a data32 prefix to ensure that all 32 bits of the GDT
# descriptor are loaded (default is to load only 24 bits).
# The CPU doesn't need an addr32 prefix but ELF doesn't do 16-bit
# relocations.
data32 addr32 lgdt gdtdesc - LOADER_PHYS_BASE - 0x20000
# Then we turn on the following bits in CR0:
# PE (Protect Enable): this turns on protected mode.
# PG (Paging): turns on paging.
# WP (Write Protect): if unset, ring 0 code ignores
# write-protect bits in page tables (!).
# EM (Emulation): forces floating-point instructions to trap.
# We don't support floating point.
movl %cr0, %eax
orl $CR0_PE | CR0_PG | CR0_WP | CR0_EM, %eax
movl %eax, %cr0
# We're now in protected mode in a 16-bit segment. The CPU still has
# the real-mode code segment cached in %cs's segment descriptor. We
# need to reload %cs, and the easiest way is to use a far jump.
# Because we're not running in a 32-bit segment the data32 prefix is
# needed to jump to a 32-bit offset in the target segment.
data32 ljmp $SEL_KCSEG, $1f
# We're now in protected mode in a 32-bit segment.
# Let the assembler know.
.code32
# Reload all the other segment registers and the stack pointer to
# point into our new GDT.
1: mov $SEL_KDSEG, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
mov %ax, %ss
addl $LOADER_PHYS_BASE, %esp
movl $0, %ebp # Null-terminate main()'s backtrace
#### Call main().
call main
# main() shouldn't ever return. If it does, spin.
1: jmp 1b
.endfunc
#### GDT
.align 8
gdt:
.quad 0x0000000000000000 # Null segment. Not used by CPU.
.quad 0x00cf9a000000ffff # System code, base 0, limit 4 GB.
.quad 0x00cf92000000ffff # System data, base 0, limit 4 GB.
gdtdesc:
.word gdtdesc - gdt - 1 # Size of the GDT, minus 1 byte.
.long gdt # Address of the GDT.
#### Physical memory size in 4 kB pages. This is exported to the rest
#### of the kernel.
.globl init_ram_pages
init_ram_pages:
.long 0