summaryrefslogtreecommitdiffstats
path: root/arch/x86/boot/bootloader/setup.s
blob: 2afc974be8e5c42a2aea54f2f167454b64d112d6 (plain)
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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
/*
	setup.s is second stage bootloader loaded at 0x90200 (by 
	first stage), is responsible for getting the system data
	from the BIOS.

	System data puts at special place: 0x90000-0x901FF.
*/

.code16							# Tell GAS to generate 16 bit code

.include "bios.inc"
.include "rm_seg.inc"

.set ENDSEG, KERNSEG + KERNSIZE	# Where to stop loading kernel

# Define some constants for the GDT segment descriptor offsets
.set CODESEG, gdt_code - gdt_start
.set DATASEG, gdt_data - gdt_start

# Keyboard Controller commands: 
.set READ_OUTP, 0xD0			# Read Output Port
.set WRITE_OUTP, 0xD1			# Write Output Port

.global _start					# Make the symbol visible to ld
_start:
	mov $SDATASEG, %ax
	mov %ax, %ds
	mov %ax, %ss
	mov $KERNSEG, %ax
	mov %ax, %es

	mov $0xFF00, %bp			# Set up the stack at 0x9ff00
	mov %bp, %sp

	BIOS_PRINT $get_data_msg

	# Get current cursor position
	mov $0x3, %ah
	xor	%bh, %bh				# Page Number = 0
	int $0x10
	add $3, %dh					# Add number of next BIOS_PRINTs
	mov %dx, (2)				# Save it

	# Get disk drive parameters
	xor %ax, %ax
	mov %ax, %es				# ES:DI = 0x0000:0x0000 to guard
	mov %ax, %di				# against BIOS bugs
	mov (0), %dl				# Set drive boot
	mov $0x8, %ah
	int $0x13
	jc disk_error

	# Interrupt return:
	# - CH = low eight bits of maximum cylinder number
	# - CL = maximum sector number (bits 5-0)
	#        high two bits of maximum cylinder number (bits 7-6)
	# - DH = maximum head number
	xor %ch, %ch
	and $0b00111111, %cl
	xor %dl, %dl
	mov %dx, heads
	mov %cx, sectors

	# TODO: get memory size
	# TODO: get video infos

load_kernel:					# Load our kernel
	BIOS_PRINT $boot_load_kern_msg

	# Load the system at $KERNSEG address:
	mov $KERNSEG, %ax
	mov %ax, %es				# ES - starting address segment
	xor %bx, %bx				# BX is offset within segment

	# A few words about the algorithm:
	# We read 0x10000 bytes (64 kB) and overflow BX (16 bytes register),
	# then add 0x1000 to ES reg, after that compare with $ENDSEG
	#
	# If KERNSIZE != 0x10000 * N we read some unnecessary data, but
	# i think it's not a problem
repeat_read:
	mov %es, %ax
	cmp $ENDSEG, %ax
	jae enable_a20				# Jump if AX >= $ENDSEG
get_sects_for_read:
	mov sectors, %ax			# AX = amount of sectors - current sector
	sub csect, %ax				# AX has 6 significant bytes
	mov %ax, %cx				# Calculate how many bytes we get by
								# reading AX sectors
	shl $9, %cx					# One sector = 2^9 = 512
	add %bx, %cx				# CX = 0@@@.@@@0.0000.0000 + BX
	jnc read_sects				# if not overflow, then jump
	jz read_sects				# if CX = 0, then jump
	xor %ax, %ax				# AX = 0
	sub %bx, %ax				# AX = amount of sectors that we must
	shr $9, %ax					# read for overflow BX
read_sects:
	call read_track				# INPUT: AX
	mov %ax, %cx				# CX = amount of sectors that we read
	add csect, %ax
	cmp sectors, %ax			# Current sector = amount of sectors?
	jne check_read				# If not equal, jump
	mov chead, %ax
	cmp heads, %ax				# Current head = amount of heads?
	jne inc_chead				# If not equal, jump
	movw $0xffff, chead			# Current head will overflow and equal 0
								# after INC instuction in inc_chead
	incw ctrack					# Go to next cylinder
								# We don't check cylinder overflow
								# because it makes no sense
inc_chead:
	incw chead
	xor %ax, %ax
check_read:
	mov %ax, csect				# Calculate how many bytes we get by
	shl $9, %cx					# reading AX sectors
	add %cx, %bx				# Add it to BX
	jnc repeat_read				# If BX not overflow, jjmp
	mov %es, %ax
	add $0x1000, %ax			# We read 0x10000 = 65536 bytes
	mov %ax, %es
	xor %bx, %bx
	jmp repeat_read

# INPUT:
# AX - amount of sectors that we want to read
read_track:
	push %ax
	push %bx
	push %cx
	push %dx
	mov ctrack, %dx
	mov csect, %cx				# Set sector
	inc %cx						# Add +1 because sector has a base of 1
	mov %dl, %ch				# Set cylinder
	mov chead, %dh				# Set head
	mov %dl, %dh
	mov (0), %dl				# Set boot
	mov $0x02, %ah				# Set BIOS read sector routine
	int $0x13
	jc .						# Error :(
	pop %dx
	pop %cx
	pop %bx
	pop %ax
	ret

enable_a20:
	BIOS_PRINT $enable_a20_msg

	cli							# Switch of interrupt until we have set
								# up the protected mode interrupt vector

	call wait_input
	mov $READ_OUTP, %al
	out %al, $0x64 
	call wait_output

	in $0x60, %al				# Read input buffer and store on stack
	push %ax
	call wait_input

	mov $WRITE_OUTP, %al
	out %al, $0x64
	call wait_input

	pop %ax						# Pop the output port data from stack
	or $2, %al					# Set bit 1 (A20) to enable
	out %al, $0x60				# Write the data to the output port
	call wait_input

switch_to_pm:
	BIOS_PRINT $boot_prot_mode_msg
	lgdt gdt_descriptor			# Load our global descriptor table

	mov %cr0, %eax				# Set the first bit of CR0
	or $0x01, %eax				# to make the switch to protected mode
	mov %eax, %cr0

	# Make a far jump to our 32-bit code.
	# This also forces the CPU to flush its cache of pre-fetched
	# and real-mode decoded instructions, which can cause problems
	jmp $CODESEG, $KERNADDR

disk_error:
	BIOS_PRINT $disk_error_msg
	jmp .

wait_input:
	in $0x64, %al				# Read status
	test $2, %al				# Is input buffer full?
	jnz wait_input				# yes - continue waiting
	ret

wait_output:
	in $0x64, %al
	test $1, %al				# Is output buffer full?
	jz wait_output				# no - continue waiting
	ret

# Global Descriptor Table (contains 8-byte entries)
gdt_start:
gdt_null:						# The mandatory null descriptor
	.quad 0x0

gdt_code:						# The code segment descriptor
	# Base = 0x0, limit = 0xfffff
	# 1st flags: (present)1 (privilege)00 (descriptor type)1 -> b1001
	# Type flags: (code)1 (conforming)0 (readable)1 (accessed)0 -> b1010
	# 2nd flags: (granularity)1 (size)1 (64-bit seg)0 (AVL)0 -> b1100
	.word 0xffff				# Limit (bits 0-15)
	.word 0x0					# Base (bits 0-15)
	.byte 0x0					# Base (bits 16-23)
	.byte 0b10011010			# 1st flags, type flags
	.byte 0b11001111			# 2nd flags, limit (bits 16-19)
	.byte 0x0					# Base (bits 24-31)

gdt_data: 						# the data segment descriptor
	# Same as code segment except for the type flags:
	# Type flags: (code)0 (direction)0 (writable)1, (accessed)0 -> b0010
	# P.S: direction bit: 0 the segment grows up
	.word 0xffff				# Limit (bits 0-15)
	.word 0x0					# Base (bits 0-15)
	.byte 0x0					# Base (bits 16-23)
	.byte 0b10010010			# 1st flags, type flags
	.byte 0b11001111			# 2nd flags, limit (bits 16-19)
	.byte 0x0					# Base (bits 24-31)
gdt_end:

# Global variables

# Total amount of HDD components:
heads:							# 8 significant bytes
	.word 0x0
sectors:						# 6 significant bytes
	.word 0x0

# The number of the current component with which we interact:
ctrack:							# track/cylinder
	.word 0x0
chead:
	.word 0x0
csect:
	.word 1 + SETUPLEN

gdt_descriptor:
	# The 6-byte GDT structure containing:
	# - GDT size, 2 bytes (size always less one of the real size):
	.word gdt_end - gdt_start - 1
	# - GDT address, 4 bytes:
	.word gdt_start, 0x9

get_data_msg:
	.asciz "Getting the system data from the BIOS\r\n"

boot_prot_mode_msg:
	.asciz "Entering 32-bit protected mode\r\n"

boot_load_kern_msg:
	.asciz "Loading kernel into memory\r\n"

enable_a20_msg:
	.asciz "Enabling A20 line\r\n"

disk_error_msg:
	.asciz "Disk read error!"

.space (512 * SETUPLEN) - (. - _start), 0