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