2 ;; -----------------------------------------------------------------------
4 ;; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved
5 ;; Copyright 2009 Intel Corporation; author: H. Peter Anvin
7 ;; This program is free software; you can redistribute it and/or modify
8 ;; it under the terms of the GNU General Public License as published by
9 ;; the Free Software Foundation, Inc., 53 Temple Place Ste 330,
10 ;; Boston MA 02111-1307, USA; either version 2 of the License, or
11 ;; (at your option) any later version; incorporated herein by reference.
13 ;; -----------------------------------------------------------------------
18 ;; Routine to initialize and to trampoline into 32-bit
19 ;; protected memory. This code is derived from bcopy32.inc and
20 ;; com32.inc in the main SYSLINUX distribution.
23 %include '../version.gen'
25 MY_CS
equ 0x0800 ; Segment address to use
26 CS_BASE
equ (MY_CS
<< 4) ; Corresponding address
28 ; Low memory bounce buffer
29 BOUNCE_SEG
equ (MY_CS
+0x1000)
33 section .rodata
align=16
34 section .data
align=16
36 section .stack
align=16 nobits
40 ;; -----------------------------------------------------------------------
41 ;; Kernel image header
42 ;; -----------------------------------------------------------------------
44 section .text
; Must be first in image
47 cmdline times
497 db 0 ; We put the command line here
57 _start: jmp short start
59 db "HdrS" ; Header signature
60 dw 0x0203 ; Header version number
62 realmode_swtch
dw 0, 0 ; default_switch, SETUPSEG
63 start_sys_seg
dw 0x1000 ; obsolete
64 version_ptr
dw memdisk_version
-0x200 ; version string ptr
65 type_of_loader
db 0 ; Filled in by boot loader
66 loadflags
db 1 ; Please load high
67 setup_move_size
dw 0 ; Unused
68 code32_start
dd 0x100000 ; 32-bit start address
69 ramdisk_image
dd 0 ; Loaded ramdisk image address
70 ramdisk_size
dd 0 ; Size of loaded ramdisk
71 bootsect_kludge
dw 0, 0
74 cmd_line_ptr
dd 0 ; Command line
75 ramdisk_max
dd 0xffffffff ; Highest allowed ramdisk address
78 ; These fields aren't real setup fields, they're poked in by the
81 b_esdi
dd 0 ; ES:DI for boot sector invocation
82 b_edx
dd 0 ; EDX for boot sector invocation
83 b_sssp
dd 0 ; SS:SP on boot sector invocation
84 b_csip
dd 0 ; CS:IP on boot sector invocation
88 db "MEMDISK ", VERSION_STR
, " ", DATE
, 0
90 ;; -----------------------------------------------------------------------
91 ;; End kernel image header
92 ;; -----------------------------------------------------------------------
95 ; Move ourselves down into memory to reduce the risk of conflicts;
96 ; then canonicalize CS to match the other segments.
103 movzx cx,byte [setup_sects
]
104 inc cx ; Add one for the boot sector
105 shl cx,7 ; Convert to dwords
118 ; Copy the command line, if there is one
121 xor di,di ; Bottom of our own segment (= "boot sector")
122 mov eax,[cmd_line_ptr
]
124 jz .endcmd
; No command line
126 shr eax,4 ; Convert to segment
127 and si,0x000F ; Starting offset only
129 mov cx,496 ; Max number of bytes
141 ; Now jump to 32-bit code
146 ; When init32 returns, we have been set up, the new boot sector loaded,
147 ; and we should go and and run the newly loaded boot sector.
149 ; The setup function will have poked values into the setup area.
151 movzx edi,word [cs:b_esdi
]
152 mov es,word [cs:b_esdi
+2]
156 xor esi,esi ; No partition table involved
157 mov ds,si ; Make all the segments consistent
165 ; We enter protected mode, set up a flat 32-bit environment, run rep movsd
166 ; and then exit. IMPORTANT: This code assumes cs == MY_CS.
168 ; This code is probably excessively anal-retentive in its handling of
169 ; segments, but this stuff is painful enough as it is without having to rely
170 ; on everything happening "as it ought to."
172 DummyTSS
equ 0x580 ; Hopefully safe place in low mmoery
176 ; desc base, limit, flags
178 dd (%2 & 0xffff) |
((%1 & 0xffff) << 16)
179 dd (%1 & 0xff000000) |
(%2 & 0xf0000) |
((%3 & 0xf0ff) << 8) |
((%1 & 0x00ff0000) >> 16)
183 call32_gdt: dw call32_gdt_size
-1 ; Null descriptor - contains GDT
184 .
adj1: dd call32_gdt
+CS_BASE
; pointer for LGDT instruction
187 ; 0008: Dummy TSS to make Intel VT happy
188 ; Should never be actually accessed...
189 desc DummyTSS
, 103, 0x8089
191 ; 0010: Code segment, use16, readable, dpl 0, base CS_BASE, 64K
192 desc CS_BASE
, 0xffff, 0x009b
194 ; 0018: Data segment, use16, read/write, dpl 0, base CS_BASE, 64K
195 desc CS_BASE
, 0xffff, 0x0093
197 ; 0020: Code segment, use32, read/write, dpl 0, base 0, 4G
198 desc
0, 0xfffff, 0xc09b
200 ; 0028: Data segment, use32, read/write, dpl 0, base 0, 4G
201 desc
0, 0xfffff, 0xc093
203 call32_gdt_size: equ $
-call32_gdt
205 err_a20: db 'ERROR: A20 gate not responding!',13,10,0
209 Return resd
1 ; Return value
210 SavedSP resw
1 ; Place to save SP
215 Target
dd 0 ; Target address
216 Target_Seg
dw 20h ; Target CS
218 A20Type
dw 0 ; Default = unknown
223 ; Routines to enable and disable (yuck) A20. These routines are gathered
224 ; from tips from a couple of sources, including the Linux kernel and
225 ; http://www.x86.org/. The need for the delay to be as large as given here
226 ; is indicated by Donnie Barnes of RedHat, the problematic system being an
227 ; IBM ThinkPad 760EL.
229 ; We typically toggle A20 twice for every 64K transferred.
231 %define io_delay
call _io_delay
232 %define IO_DELAY_PORT
80h ; Invalid port (we hope!)
233 %define disable_wait
32 ; How long to wait for a disable
235 %define A20_DUNNO
0 ; A20 type unknown
236 %define A20_NONE
1 ; A20 always on?
237 %define A20_BIOS
2 ; A20 BIOS enable
238 %define A20_KBC
3 ; A20 through KBC
239 %define A20_FAST
4 ; A20 through port 92h
242 A20List
dw a20_dunno
, a20_none
, a20_bios
, a20_kbc
, a20_fast
243 A20DList
dw a20d_dunno
, a20d_none
, a20d_bios
, a20d_kbc
, a20d_fast
244 a20_adjust_cnt
equ ($
-A20List
)/2
246 slow_out: out dx, al ; Fall through
248 _io_delay: out IO_DELAY_PORT
,al
254 mov byte [A20Tries
],255 ; Times to try to make this work
266 ; If the A20 type is known, jump straight to type
269 add bp,bp ; Convert to word offset
270 .
adj4: jmp word [bp+A20List
]
273 ; First, see if we are on a system with no A20 gate
277 mov byte [A20Type
], A20_NONE
282 ; Next, try the BIOS (INT 15h AX=2401h)
285 mov byte [A20Type
], A20_BIOS
287 pushf ; Some BIOSes muck with IF
295 ; Enable the keyboard controller A20 gate
298 mov dl, 1 ; Allow early exit
300 jnz a20_done
; A20 live, no need to use KBC
302 mov byte [A20Type
], A20_KBC
; Starting KBC command sequence
304 mov al,0D1h
; Write output port
306 call empty_8042_uncond
310 call empty_8042_uncond
312 ; Apparently the UHCI spec assumes that A20 toggle
313 ; ends with a null command (assumed to be for sychronization?)
314 ; Put it here to see if it helps anything...
315 mov al,0FFh
; Null command
317 call empty_8042_uncond
319 ; Verify that A20 actually is enabled. Do that by
320 ; observing a word in low memory and the same word in
321 ; the HMA until they are no longer coherent. Note that
322 ; we don't do the same check in the disable case, because
323 ; we don't want to *require* A20 masking (SYSLINUX should
324 ; work fine without it, if the BIOS does.)
334 ; Running out of options here. Final attempt: enable the "fast A20 gate"
337 mov byte [A20Type
], A20_FAST
; Haven't used the KBC yet
340 and al,~
01h ; Don't accidentally reset the machine!
353 ; Oh bugger. A20 is not responding. Try frobbing it again; eventually give up
354 ; and report failure to the user.
379 ; A20 unmasked, proceed...
386 ; This routine tests if A20 is enabled (ZF = 0). This routine
387 ; must not destroy any register contents.
390 ; This is the INT 1Fh vector, which is standard PCs is used by the
391 ; BIOS when the screen is in graphics mode. Even if it is, it points to
392 ; data, not code, so it should be safe enough to fiddle with.
403 mov es,ax ; ES == 0FFFFh
404 mov cx,32 ; Loop count
406 cmp eax,[es:A20Test
+10h]
413 cmp eax,[es:A20Test
+10h]
415 pop dword [A20Test
] ; Restore original value
433 add bp,bp ; Convert to word offset
434 .
adj5: jmp word [bp+A20DList
]
438 pushf ; Some BIOSes muck with IF
441 jmp short a20d_snooze
444 ; Disable the "fast A20 gate"
450 jmp short a20d_snooze
453 ; Disable the keyboard controller A20 gate
456 call empty_8042_uncond
459 out 064h, al ; Write output port
460 call empty_8042_uncond
462 mov al,0DDh
; A20 off
464 call empty_8042_uncond
466 mov al,0FFh
; Null command/synchronization
468 call empty_8042_uncond
470 ; Wait a bit for it to take effect
474 .
delayloop: call a20_test
484 ; Routine to empty the 8042 KBC controller. If dl != 0
485 ; then we will test A20 in the loop and exit if A20 is
496 in al, 064h ; Status port
500 in al, 060h ; Read input
509 ; Execute a WBINVD instruction if possible on this CPU
519 PMESP resd
1 ; Protected mode %esp
521 section .idt nobits
align=4096
523 pm_idt resb
4096 ; Protected-mode IDT, followed by interrupt stubs
528 pm_entry: equ 0x100000
540 dd 0 ; Address (entered later)
544 ; This is the main entrypoint in this function
547 mov bx,call32_call_start
; Where to go in PM
550 ; Enter protected mode. BX contains the entry point relative to the
557 shl ebp,4 ; EBP <- CS_BASE
559 add ebx,ebp ; entry point += CS_BASE
564 mov byte [call32_gdt
+8+5],89h ; Mark TSS unbusy
565 o32
lgdt [call32_gdt
] ; Set up GDT
566 o32
lidt [call32_pmidt
] ; Set up IDT
569 mov cr0
,eax ; Enter protected mode
570 jmp 20h:strict
dword .in_pm
+CS_BASE
576 xor eax,eax ; Available for future use...
581 mov al,28h ; Set up data segments
589 mov esp,[ebp+PMESP
] ; Load protmode %esp if available
590 jmp ebx ; Go to where we need to go
593 ; This is invoked before first dispatch of the 32-bit code, in 32-bit mode
597 ; Set up a temporary stack in the bounce buffer;
598 ; start32.S will override this to point us to the real
601 mov esp, (BOUNCE_SEG
<< 4) + 0x10000
603 push dword call32_enter_rm.rm_jmp
+CS_BASE
604 push dword call32_enter_pm.pm_jmp
+CS_BASE
605 push dword stack_end
; RM size
606 push dword call32_gdt
+CS_BASE
607 push dword call32_handle_interrupt
+CS_BASE
608 push dword CS_BASE
; Segment base
609 push dword (BOUNCE_SEG
<< 4) ; Bounce buffer address
610 push dword call32_syscall
+CS_BASE
; Syscall entry point
612 call pm_entry
-CS_BASE
; Run the program...
614 ; ... fall through to call32_exit ...
617 mov bx,call32_done
; Return to command loop
620 ; Careful here... the PM code may have relocated the
621 ; entire RM code, so we need to figure out exactly
622 ; where we are executing from. If the PM code has
623 ; relocated us, it *will* have adjusted the GDT to
628 o32
sidt [ebp+call32_pmidt
]
631 mov [ebp+PMESP
],esp ; Save exit %esp
632 xor esp,esp ; Make sure the high bits are zero
633 jmp 10h:.in_pm16
; Return to 16-bit mode first
637 mov ax,18h ; Real-mode-like segment
644 lidt [call32_rmidt
] ; Real-mode IDT (rm needs no GDT)
651 .
in_rm: ; Back in real mode
658 mov sp,[SavedSP
] ; Restore stack
659 jmp bx ; Go to whereever we need to go...
667 ; 16-bit support code
672 ; 16-bit interrupt-handling code
675 pushf ; Flags on stack
676 push cs ; Return segment
677 push word .cont
; Return address
678 push dword edx ; Segment:offset of IVT entry
679 retf ; Invoke IVT routine
680 .
cont: ; ... on resume ...
681 mov bx,call32_int_resume
682 jmp call32_enter_pm
; Go back to PM
685 ; 16-bit system call handling code
694 retf ; Invoke routine
702 mov bx,call32_sys_resume
706 ; 32-bit support code
711 ; This is invoked on getting an interrupt in protected mode. At
712 ; this point, we need to context-switch to real mode and invoke
713 ; the interrupt routine.
715 ; When this gets invoked, the registers are saved on the stack and
716 ; AL contains the register number.
718 call32_handle_interrupt:
720 xor ebx,ebx ; Actually makes the code smaller
721 mov edx,[ebx+eax*4] ; Get the segment:offset of the routine
723 jmp call32_enter_rm
; Go to real mode
730 ; Syscall invocation. We manifest a structure on the real-mode stack,
731 ; containing the call32sys_t structure from <call32.h> as well as
732 ; the following entries (from low to high address):
736 ; - Return segment (== real mode cs)
740 pushfd ; Save IF among other things...
741 pushad ; We only need to save some, but...
747 movzx edi,word [ebp+SavedSP
]
748 sub edi,54 ; Allocate 54 bytes
750 add edi,ebp ; Create linear address
752 mov esi,[esp+11*4] ; Source regs
754 mov cl,11 ; 44 bytes to copy
757 movzx eax,byte [esp+10*4] ; Interrupt number
758 ; ecx == 0 here; adding it to the EA makes the
760 mov eax,[ecx+eax*4] ; Get IVT entry
761 stosd ; Save in stack frame
762 mov ax,call32_sys_rm.return
; Return offset
763 stosw ; Save in stack frame
765 shr eax,4 ; Return segment
766 stosw ; Save in stack frame
767 mov eax,[edi-12] ; Return flags
768 and eax,0x200cd7 ; Mask (potentially) unsafe flags
769 mov [edi-12],eax ; Primary flags entry
773 jmp call32_enter_rm
; Go to real mode
775 ; On return, the 44-byte return structure is on the
776 ; real-mode stack. call32_enter_pm will leave ebp
777 ; pointing to the real-mode base.
779 movzx esi,word [ebp+SavedSP
]
780 mov edi,[esp+12*4] ; Dest regs
781 add esi,ebp ; Create linear address
782 and edi,edi ; NULL pointer?
784 .
no_copy: mov edi,esi ; Do a dummy copy-to-self
785 .
do_copy: xor ecx,ecx
787 rep movsd ; Copy register block
789 add word [ebp+SavedSP
],44 ; Remove from stack
793 ret ; Return to 32-bit program