[FREELDR]
[reactos.git] / boot / freeldr / freeldr / arch / i386 / arch.S
1 /*
2 * FreeLoader
3 * Copyright (C) 1998-2002 Brian Palmer <brianp@sginet.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 .intel_syntax noprefix
21 #define HEX(y) 0x##y
22
23 #define ASM
24 #include <arch.h>
25 #include <multiboot.h>
26
27 .code16
28
29 EXTERN(RealEntryPoint)
30
31 cli
32
33 /* Setup segment registers */
34 xor ax, ax
35 mov ds, ax
36 mov es, ax
37 mov fs, ax
38 mov gs, ax
39 mov ss, ax
40
41 /* Setup a stack */
42 mov sp, word ptr ds:stack16
43
44 sti
45
46 /* Init pmode */
47 call switch_to_prot
48
49 .code32
50
51 /* Zero BootDrive and BootPartition */
52 xor eax, eax
53 mov dword ptr [_BootDrive], eax
54 mov dword ptr [_BootPartition], eax
55
56 /* Store the boot drive */
57 mov byte ptr [_BootDrive], dl
58
59 /* Store the boot partition */
60 mov byte ptr [_BootPartition], dh
61
62 /* GO! */
63 push eax
64 call _BootMain
65
66 call switch_to_real
67 .code16
68
69 int HEX(19)
70
71 /* We should never get here */
72 stop:
73 jmp stop
74 nop
75 nop
76
77 /*
78 * Switches the processor to protected mode
79 * it destroys eax
80 */
81 EXTERN(switch_to_prot)
82
83 .code16
84
85 cli /* None of these */
86
87 /* We don't know what values are currently */
88 /* in the segment registers. So we are */
89 /* going to reload them with sane values. */
90 /* Of course CS has to already be valid. */
91 /* We are currently in real-mode so we */
92 /* need real-mode segment values. */
93 xor ax, ax
94 mov ds, ax
95 mov es, ax
96 mov fs, ax
97 mov gs, ax
98 mov ss, ax
99
100 /* Get the return address off the stack */
101 pop word ptr ds:[code32ret]
102
103 /* Save 16-bit stack pointer */
104 mov word ptr ds:[stack16], sp
105
106 /* Load the GDT */
107 lgdt gdtptr
108 /* Load the IDT */
109 lidt i386idtptr
110
111 /* Enable Protected Mode */
112 mov eax, cr0
113 or eax, CR0_PE_SET
114 mov cr0, eax
115
116 /* Clear prefetch queue & correct CS */
117 //ljmp PMODE_CS, inpmode
118 jmp far ptr PMODE_CS:inpmode
119
120 .code32
121
122 inpmode:
123 /* Setup segment selectors */
124 mov ax, PMODE_DS
125 mov ds, ax
126 mov es, ax
127 mov fs, ax
128 mov gs, ax
129 mov ss, ax
130 mov esp, dword ptr [stack32]
131
132 /* Put the return address back onto the stack */
133 push dword ptr [code32ret]
134
135 /* Now return in p-mode! */
136 ret
137
138 /*
139 * Switches the processor back to real mode
140 * it destroys eax
141 */
142 EXTERN(switch_to_real)
143
144 .code32
145
146 /* We don't know what values are currently */
147 /* in the segment registers. So we are */
148 /* going to reload them with sane values. */
149 /* Of course CS has to already be valid. */
150 /* We are currently in protected-mode so we */
151 /* need protected-mode segment values. */
152 mov ax, PMODE_DS
153 mov ds, ax
154 mov es, ax
155 mov fs, ax
156 mov gs, ax
157 mov ss, ax
158
159 /* Get the return address off the stack */
160 pop dword ptr [code16ret]
161
162 /* Save 32-bit stack pointer */
163 mov dword ptr [stack32], esp
164
165 /* jmp to 16-bit segment to set the limit correctly */
166 ljmp RMODE_CS, switch_to_real16
167
168 switch_to_real16:
169 .code16
170
171 /* Restore segment registers to correct limit */
172 mov ax, RMODE_DS
173 mov ds, ax
174 mov es, ax
175 mov fs, ax
176 mov gs, ax
177 mov ss, ax
178
179 /* Disable Protected Mode */
180 mov eax, cr0
181 and eax, CR0_PE_CLR
182 mov cr0, eax
183
184 /* Clear prefetch queue & correct CS */
185 //ljmp $0, $inrmode
186 jmp far ptr 0:inrmode
187
188 inrmode:
189 mov ax, cs
190 mov ds, ax
191 mov es, ax
192 mov fs, ax
193 mov gs, ax
194 mov ss, ax
195
196 /* Clear out the high 16-bits of ESP */
197 /* This is needed because I have one */
198 /* machine that hangs when booted to dos if */
199 /* anything other than 0x0000 is in the high */
200 /* 16-bits of ESP. Even though real-mode */
201 /* code should only use SP and not ESP. */
202 xor esp, esp
203
204 mov sp, word ptr ds:[stack16]
205
206 /* Put the return address back onto the stack */
207 push word ptr ds:[code16ret]
208
209 /* Load IDTR with real mode value */
210 lidt rmode_idtptr
211
212 sti /* These are ok now */
213
214 /* Now return in r-mode! */
215 ret
216
217
218 /*
219 * Needed for enabling the a20 address line
220 */
221 .code16
222 empty_8042:
223 .word 0x00eb,0x00eb // jmp $+2, jmp $+2
224 in al, HEX(64)
225 cmp al, HEX(ff) // legacy-free machine without keyboard
226 jz empty_8042_ret // controllers on Intel Macs read back 0xFF
227 test al, 2
228 jnz empty_8042
229 empty_8042_ret:
230 ret
231
232 /*
233 * Enable the A20 address line (to allow access to over 1mb)
234 */
235 EXTERN(_EnableA20)
236 .code32
237
238 pusha
239
240 call switch_to_real
241 .code16
242
243 call empty_8042
244 mov al, HEX(D1) // command write
245 out HEX(64), al
246 call empty_8042
247 mov al, HEX(DF) // A20 on
248 out HEX(60), al
249 call empty_8042
250 call switch_to_prot
251 .code32
252
253 popa
254
255 ret
256
257 /*
258 * Disable the A20 address line
259 */
260 EXTERN(_DisableA20)
261 .code32
262
263 pusha
264
265 call switch_to_real
266 .code16
267
268 call empty_8042
269 mov al, HEX(D1) // command write
270 out HEX(64), al
271 call empty_8042
272 mov al, HEX(DD) // A20 off
273 out HEX(60), al
274 call empty_8042
275 call switch_to_prot
276 .code32
277
278 popa
279
280 ret
281
282 /* Multiboot support
283 *
284 * Allows freeldr to be loaded as a "multiboot kernel" by
285 * other boot loaders like Grub
286 */
287
288 #define MB_INFO_SIZE 90
289 #define MB_INFO_FLAGS_OFFSET 0
290 #define MB_INFO_BOOT_DEVICE_OFFSET 12
291 #define MB_INFO_COMMAND_LINE_OFFSET 16
292 #define CMDLINE_SIZE 256
293
294 /*
295 * We want to execute at 0x8000 (to be compatible with bootsector
296 * loading), but Grub only allows loading of multiboot kernels
297 * above 1MB. So we let Grub load us there and then relocate
298 * ourself to 0x8000
299 */
300 #define FREELDR_BASE HEX(8000)
301 #define INITIAL_BASE HEX(200000)
302
303 /* Align 32 bits boundary */
304 .align 4
305
306 /* Multiboot header */
307 MultibootHeader:
308 /* magic */
309 .long MULTIBOOT_HEADER_MAGIC
310 /* flags */
311 .long MULTIBOOT_HEADER_FLAGS
312 /* checksum */
313 .long -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS)
314 /* header_addr */
315 .long INITIAL_BASE + MultibootHeader - FREELDR_BASE
316 /* load_addr */
317 .long INITIAL_BASE
318 /* load_end_addr */
319 .long INITIAL_BASE + __bss_start__ - FREELDR_BASE
320 /* bss_end_addr */
321 .long INITIAL_BASE + __bss_end__ - FREELDR_BASE
322 /* entry_addr */
323 .long INITIAL_BASE + MultibootEntry - FREELDR_BASE
324
325 MultibootEntry:
326 cli /* Even after setting up the our IDT below we are
327 * not ready to handle hardware interrupts (no entries
328 * in IDT), so there's no sti here. Interrupts will be
329 * enabled in due time */
330
331 /* Although the multiboot spec says we should be called with the
332 * segment registers set to 4GB flat mode, let's be sure and set up
333 * our own */
334 lgdt gdtptrhigh + INITIAL_BASE - FREELDR_BASE
335 /* Reload segment selectors */
336 //ljmp $PMODE_CS, $(mb1 + INITIAL_BASE - FREELDR_BASE)
337 jmp far ptr PMODE_CS: (mb1 + INITIAL_BASE - FREELDR_BASE)
338 mb1:
339 mov dx, PMODE_DS
340 mov ds, dx
341 mov es, dx
342
343 /* Check for valid multiboot signature */
344 cmp eax, MULTIBOOT_BOOTLOADER_MAGIC
345 jne mbfail
346
347 /* Store multiboot info in a safe place */
348 mov esi, ebx
349 mov edi, offset mb_info + INITIAL_BASE - FREELDR_BASE
350 mov ecx, MB_INFO_SIZE
351 rep movsb
352
353 /* Save commandline */
354 mov edx, [ebx + MB_INFO_FLAGS_OFFSET]
355 test dword ptr [ebx + MB_INFO_FLAGS_OFFSET], MB_INFO_FLAG_COMMAND_LINE
356 jz mb3
357 mov esi, [ebx + MB_INFO_COMMAND_LINE_OFFSET]
358 mov edi, offset cmdline + INITIAL_BASE - FREELDR_BASE
359 mov ecx, CMDLINE_SIZE
360 mb2: lodsb
361 stosb
362 test al, al
363 jz mb3
364 dec ecx
365 jnz mb2
366 mb3:
367
368 /* Copy to low mem */
369 mov esi, INITIAL_BASE
370 mov edi, FREELDR_BASE
371 mov ecx, (offset __bss_end__ - FREELDR_BASE)
372 add ecx, 3
373 shr ecx, 2
374 rep movsd
375
376 /* Load the GDT and IDT */
377 lgdt gdtptr
378 lidt i386idtptr
379
380 /* Clear prefetch queue & correct CS,
381 * jump to low mem */
382 //ljmp $PMODE_CS, $mb4
383 jmp far ptr PMODE_CS:mb4
384 mb4:
385 /* Reload segment selectors */
386 mov dx, PMODE_DS
387 mov ds, dx
388 mov es, dx
389 mov fs, dx
390 mov gs, dx
391 mov ss, dx
392 mov esp, STACK32ADDR
393
394 mov ebx, offset mb_info
395 /* See if the boot device was passed in */
396 mov edx, [ebx + MB_INFO_FLAGS_OFFSET]
397 test edx, MB_INFO_FLAG_BOOT_DEVICE
398 jz mb5
399 /* Retrieve boot device info */
400 mov eax, [ebx + MB_INFO_BOOT_DEVICE_OFFSET]
401 shr eax, 16
402 inc al
403 mov byte ptr _BootPartition, al
404 mov byte ptr _BootDrive, ah
405 jmp mb6
406 mb5: /* No boot device known, assume first partition of first harddisk */
407 mov byte ptr _BootDrive, HEX(80)
408 mov byte ptr _BootPartition, 1
409 mb6:
410 /* Check for command line */
411 mov eax, offset cmdline
412 test dword ptr [ebx + MB_INFO_FLAGS_OFFSET], MB_INFO_FLAG_COMMAND_LINE
413 jnz mb7
414 xor eax, eax
415 mb7:
416
417 /* GO! */
418 push eax
419 call _BootMain
420
421 mbfail:
422 call switch_to_real
423 .code16
424 int 0x19
425 mbstop: jmp mbstop /* We should never get here */
426
427 .code32
428
429 /* 16-bit stack pointer */
430 stack16:
431 .word STACK16ADDR
432
433 /* 32-bit stack pointer */
434 stack32:
435 .long STACK32ADDR
436
437 /* 16-bit return address */
438 code16ret:
439 .long 0
440
441 /* 32-bit return address */
442 code32ret:
443 .long 0
444
445
446 .align 4 /* force 4-byte alignment */
447 gdt:
448 /* NULL Descriptor */
449 .word HEX(0000)
450 .word HEX(0000)
451 .word HEX(0000)
452 .word HEX(0000)
453
454 /* 32-bit flat CS */
455 .word HEX(FFFF)
456 .word HEX(0000)
457 .word HEX(9A00)
458 .word HEX(00CF)
459
460 /* 32-bit flat DS */
461 .word HEX(FFFF)
462 .word HEX(0000)
463 .word HEX(9200)
464 .word HEX(00CF)
465
466 /* 16-bit real mode CS */
467 .word HEX(FFFF)
468 .word HEX(0000)
469 .word HEX(9E00)
470 .word HEX(0000)
471
472 /* 16-bit real mode DS */
473 .word HEX(FFFF)
474 .word HEX(0000)
475 .word HEX(9200)
476 .word HEX(0000)
477
478 /* GDT table pointer */
479 gdtptr:
480 .word HEX(27) /* Limit */
481 .long gdt /* Base Address */
482
483 /* Initial GDT table pointer for multiboot */
484 gdtptrhigh:
485 .word HEX(27) /* Limit */
486 .long gdt + INITIAL_BASE - FREELDR_BASE /* Base Address */
487
488 /* Real-mode IDT pointer */
489 rmode_idtptr:
490 .word HEX(3ff) /* Limit */
491 .long 0 /* Base Address */
492
493 mb_info:
494 .fill MB_INFO_SIZE, 1, 0
495
496 cmdline:
497 .fill CMDLINE_SIZE, 1, 0
498
499 EXTERN(_BootDrive)
500 .long 0
501
502 EXTERN(_BootPartition)
503 .long 0