;; -*- fundamental -*- ;; ----------------------------------------------------------------------- ;; ;; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved ;; Copyright 2009 Intel Corporation; author: H. Peter Anvin ;; ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, Inc., 53 Temple Place Ste 330, ;; Boston MA 02111-1307, USA; either version 2 of the License, or ;; (at your option) any later version; incorporated herein by reference. ;; ;; ----------------------------------------------------------------------- ;; ;; init16.asm ;; ;; Routine to initialize and to trampoline into 32-bit ;; protected memory. This code is derived from bcopy32.inc and ;; com32.inc in the main SYSLINUX distribution. ;; %include '../version.gen' MY_CS equ 0x0800 ; Segment address to use CS_BASE equ (MY_CS << 4) ; Corresponding address ; Low memory bounce buffer BOUNCE_SEG equ (MY_CS+0x1000) %define DO_WBINVD 0 section .rodata align=16 section .data align=16 section .bss align=16 section .stack align=16 nobits stack resb 512 stack_end equ $ ;; ----------------------------------------------------------------------- ;; Kernel image header ;; ----------------------------------------------------------------------- section .text ; Must be first in image bits 16 cmdline times 497 db 0 ; We put the command line here setup_sects db 0 root_flags dw 0 syssize dw 0 swap_dev dw 0 ram_size dw 0 vid_mode dw 0 root_dev dw 0 boot_flag dw 0xAA55 _start: jmp short start db "HdrS" ; Header signature dw 0x0203 ; Header version number realmode_swtch dw 0, 0 ; default_switch, SETUPSEG start_sys_seg dw 0x1000 ; obsolete version_ptr dw memdisk_version-0x200 ; version string ptr type_of_loader db 0 ; Filled in by boot loader loadflags db 1 ; Please load high setup_move_size dw 0 ; Unused code32_start dd 0x100000 ; 32-bit start address ramdisk_image dd 0 ; Loaded ramdisk image address ramdisk_size dd 0 ; Size of loaded ramdisk bootsect_kludge dw 0, 0 heap_end_ptr dw 0 pad1 dw 0 cmd_line_ptr dd 0 ; Command line ramdisk_max dd 0xffffffff ; Highest allowed ramdisk address ; ; These fields aren't real setup fields, they're poked in by the ; 32-bit code. ; b_esdi dd 0 ; ES:DI for boot sector invocation b_edx dd 0 ; EDX for boot sector invocation b_sssp dd 0 ; SS:SP on boot sector invocation b_csip dd 0 ; CS:IP on boot sector invocation section .rodata memdisk_version: db "MEMDISK ", VERSION_STR, " ", DATE, 0 ;; ----------------------------------------------------------------------- ;; End kernel image header ;; ----------------------------------------------------------------------- ; ; Move ourselves down into memory to reduce the risk of conflicts; ; then canonicalize CS to match the other segments. ; section .text bits 16 start: mov ax,MY_CS mov es,ax movzx cx,byte [setup_sects] inc cx ; Add one for the boot sector shl cx,7 ; Convert to dwords xor si,si xor di,di mov fs,si ; fs <- 0 cld rep movsd mov ds,ax mov ss,ax mov esp,stack_end jmp MY_CS:.next .next: ; ; Copy the command line, if there is one ; copy_cmdline: xor di,di ; Bottom of our own segment (= "boot sector") mov eax,[cmd_line_ptr] and eax,eax jz .endcmd ; No command line mov si,ax shr eax,4 ; Convert to segment and si,0x000F ; Starting offset only mov gs,ax mov cx,496 ; Max number of bytes .copycmd: gs lodsb and al,al jz .endcmd stosb loop .copycmd .endcmd: xor al,al stosb ; ; Now jump to 32-bit code ; sti call init32 ; ; When init32 returns, we have been set up, the new boot sector loaded, ; and we should go and and run the newly loaded boot sector. ; ; The setup function will have poked values into the setup area. ; movzx edi,word [cs:b_esdi] mov es,word [cs:b_esdi+2] mov edx,[cs:b_edx] cli xor esi,esi ; No partition table involved mov ds,si ; Make all the segments consistent mov fs,si mov gs,si lss sp,[cs:b_sssp] movzx esp,sp jmp far [cs:b_csip] ; ; We enter protected mode, set up a flat 32-bit environment, run rep movsd ; and then exit. IMPORTANT: This code assumes cs == MY_CS. ; ; This code is probably excessively anal-retentive in its handling of ; segments, but this stuff is painful enough as it is without having to rely ; on everything happening "as it ought to." ; DummyTSS equ 0x580 ; Hopefully safe place in low mmoery section .data ; desc base, limit, flags %macro desc 3 dd (%2 & 0xffff) | ((%1 & 0xffff) << 16) dd (%1 & 0xff000000) | (%2 & 0xf0000) | ((%3 & 0xf0ff) << 8) | ((%1 & 0x00ff0000) >> 16) %endmacro align 8, db 0 call32_gdt: dw call32_gdt_size-1 ; Null descriptor - contains GDT .adj1: dd call32_gdt+CS_BASE ; pointer for LGDT instruction dw 0 ; 0008: Dummy TSS to make Intel VT happy ; Should never be actually accessed... desc DummyTSS, 103, 0x8089 ; 0010: Code segment, use16, readable, dpl 0, base CS_BASE, 64K desc CS_BASE, 0xffff, 0x009b ; 0018: Data segment, use16, read/write, dpl 0, base CS_BASE, 64K desc CS_BASE, 0xffff, 0x0093 ; 0020: Code segment, use32, read/write, dpl 0, base 0, 4G desc 0, 0xfffff, 0xc09b ; 0028: Data segment, use32, read/write, dpl 0, base 0, 4G desc 0, 0xfffff, 0xc093 call32_gdt_size: equ $-call32_gdt err_a20: db 'ERROR: A20 gate not responding!',13,10,0 section .bss alignb 4 Return resd 1 ; Return value SavedSP resw 1 ; Place to save SP A20Tries resb 1 section .data align 4, db 0 Target dd 0 ; Target address Target_Seg dw 20h ; Target CS A20Type dw 0 ; Default = unknown section .text bits 16 ; ; Routines to enable and disable (yuck) A20. These routines are gathered ; from tips from a couple of sources, including the Linux kernel and ; http://www.x86.org/. The need for the delay to be as large as given here ; is indicated by Donnie Barnes of RedHat, the problematic system being an ; IBM ThinkPad 760EL. ; ; We typically toggle A20 twice for every 64K transferred. ; %define io_delay call _io_delay %define IO_DELAY_PORT 80h ; Invalid port (we hope!) %define disable_wait 32 ; How long to wait for a disable %define A20_DUNNO 0 ; A20 type unknown %define A20_NONE 1 ; A20 always on? %define A20_BIOS 2 ; A20 BIOS enable %define A20_KBC 3 ; A20 through KBC %define A20_FAST 4 ; A20 through port 92h align 2, db 0 A20List dw a20_dunno, a20_none, a20_bios, a20_kbc, a20_fast A20DList dw a20d_dunno, a20d_none, a20d_bios, a20d_kbc, a20d_fast a20_adjust_cnt equ ($-A20List)/2 slow_out: out dx, al ; Fall through _io_delay: out IO_DELAY_PORT,al out IO_DELAY_PORT,al ret enable_a20: pushad mov byte [A20Tries],255 ; Times to try to make this work try_enable_a20: ; ; Flush the caches ; %if DO_WBINVD call try_wbinvd %endif ; ; If the A20 type is known, jump straight to type ; mov bp,[A20Type] add bp,bp ; Convert to word offset .adj4: jmp word [bp+A20List] ; ; First, see if we are on a system with no A20 gate ; a20_dunno: a20_none: mov byte [A20Type], A20_NONE call a20_test jnz a20_done ; ; Next, try the BIOS (INT 15h AX=2401h) ; a20_bios: mov byte [A20Type], A20_BIOS mov ax,2401h pushf ; Some BIOSes muck with IF int 15h popf call a20_test jnz a20_done ; ; Enable the keyboard controller A20 gate ; a20_kbc: mov dl, 1 ; Allow early exit call empty_8042 jnz a20_done ; A20 live, no need to use KBC mov byte [A20Type], A20_KBC ; Starting KBC command sequence mov al,0D1h ; Write output port out 064h, al call empty_8042_uncond mov al,0DFh ; A20 on out 060h, al call empty_8042_uncond ; Apparently the UHCI spec assumes that A20 toggle ; ends with a null command (assumed to be for sychronization?) ; Put it here to see if it helps anything... mov al,0FFh ; Null command out 064h, al call empty_8042_uncond ; Verify that A20 actually is enabled. Do that by ; observing a word in low memory and the same word in ; the HMA until they are no longer coherent. Note that ; we don't do the same check in the disable case, because ; we don't want to *require* A20 masking (SYSLINUX should ; work fine without it, if the BIOS does.) .kbc_wait: push cx xor cx,cx .kbc_wait_loop: call a20_test jnz a20_done_pop loop .kbc_wait_loop pop cx ; ; Running out of options here. Final attempt: enable the "fast A20 gate" ; a20_fast: mov byte [A20Type], A20_FAST ; Haven't used the KBC yet in al, 092h or al,02h and al,~01h ; Don't accidentally reset the machine! out 092h, al .fast_wait: push cx xor cx,cx .fast_wait_loop: call a20_test jnz a20_done_pop loop .fast_wait_loop pop cx ; ; Oh bugger. A20 is not responding. Try frobbing it again; eventually give up ; and report failure to the user. ; dec byte [A20Tries] jnz try_enable_a20 ; Error message time mov si,err_a20 print_err: lodsb and al,al jz die mov bx,7 mov ah,0xe int 10h jmp print_err die: sti .hlt: hlt jmp short .hlt ; ; A20 unmasked, proceed... ; a20_done_pop: pop cx a20_done: popad ret ; ; This routine tests if A20 is enabled (ZF = 0). This routine ; must not destroy any register contents. ; ; This is the INT 1Fh vector, which is standard PCs is used by the ; BIOS when the screen is in graphics mode. Even if it is, it points to ; data, not code, so it should be safe enough to fiddle with. A20Test equ (1Fh*4) a20_test: push ds push es push cx push eax xor ax,ax mov ds,ax ; DS == 0 dec ax mov es,ax ; ES == 0FFFFh mov cx,32 ; Loop count mov eax,[A20Test] cmp eax,[es:A20Test+10h] jne .a20_done push eax .a20_wait: inc eax mov [A20Test],eax io_delay cmp eax,[es:A20Test+10h] loopz .a20_wait pop dword [A20Test] ; Restore original value .a20_done: pop eax pop cx pop es pop ds ret disable_a20: pushad ; ; Flush the caches ; %if DO_WBINVD call try_wbinvd %endif mov bp,[A20Type] add bp,bp ; Convert to word offset .adj5: jmp word [bp+A20DList] a20d_bios: mov ax,2400h pushf ; Some BIOSes muck with IF int 15h popf jmp short a20d_snooze ; ; Disable the "fast A20 gate" ; a20d_fast: in al, 092h and al,~03h out 092h, al jmp short a20d_snooze ; ; Disable the keyboard controller A20 gate ; a20d_kbc: call empty_8042_uncond mov al,0D1h out 064h, al ; Write output port call empty_8042_uncond mov al,0DDh ; A20 off out 060h, al call empty_8042_uncond mov al,0FFh ; Null command/synchronization out 064h, al call empty_8042_uncond ; Wait a bit for it to take effect a20d_snooze: push cx mov cx, disable_wait .delayloop: call a20_test jz .disabled loop .delayloop .disabled: pop cx a20d_dunno: a20d_none: popad ret ; ; Routine to empty the 8042 KBC controller. If dl != 0 ; then we will test A20 in the loop and exit if A20 is ; suddenly enabled. ; empty_8042_uncond: xor dl,dl empty_8042: call a20_test jz .a20_on and dl,dl jnz .done .a20_on: io_delay in al, 064h ; Status port test al,1 jz .no_output io_delay in al, 060h ; Read input jmp short empty_8042 .no_output: test al,2 jnz empty_8042 io_delay .done: ret ; ; Execute a WBINVD instruction if possible on this CPU ; %if DO_WBINVD try_wbinvd: wbinvd ret %endif section .bss alignb 4 PMESP resd 1 ; Protected mode %esp section .idt nobits align=4096 alignb 4096 pm_idt resb 4096 ; Protected-mode IDT, followed by interrupt stubs pm_entry: equ 0x100000 section .rodata align 2, db 0 call32_rmidt: dw 0ffffh ; Limit dd 0 ; Address section .data alignb 2 call32_pmidt: dw 8*256 ; Limit dd 0 ; Address (entered later) section .text ; ; This is the main entrypoint in this function ; init32: mov bx,call32_call_start ; Where to go in PM ; ; Enter protected mode. BX contains the entry point relative to the ; real-mode CS. ; call32_enter_pm: mov ax,cs mov ds,ax movzx ebp,ax shl ebp,4 ; EBP <- CS_BASE movzx ebx,bx add ebx,ebp ; entry point += CS_BASE cli mov [SavedSP],sp cld call enable_a20 mov byte [call32_gdt+8+5],89h ; Mark TSS unbusy o32 lgdt [call32_gdt] ; Set up GDT o32 lidt [call32_pmidt] ; Set up IDT mov eax,cr0 or al,1 mov cr0,eax ; Enter protected mode jmp 20h:strict dword .in_pm+CS_BASE .pm_jmp equ $-6 bits 32 .in_pm: xor eax,eax ; Available for future use... mov fs,eax mov gs,eax lldt ax mov al,28h ; Set up data segments mov es,eax mov ds,eax mov ss,eax mov al,08h ltr ax mov esp,[ebp+PMESP] ; Load protmode %esp if available jmp ebx ; Go to where we need to go ; ; This is invoked before first dispatch of the 32-bit code, in 32-bit mode ; call32_call_start: ; ; Set up a temporary stack in the bounce buffer; ; start32.S will override this to point us to the real ; high-memory stack. ; mov esp, (BOUNCE_SEG << 4) + 0x10000 push dword call32_enter_rm.rm_jmp+CS_BASE push dword call32_enter_pm.pm_jmp+CS_BASE push dword stack_end ; RM size push dword call32_gdt+CS_BASE push dword call32_handle_interrupt+CS_BASE push dword CS_BASE ; Segment base push dword (BOUNCE_SEG << 4) ; Bounce buffer address push dword call32_syscall+CS_BASE ; Syscall entry point call pm_entry-CS_BASE ; Run the program... ; ... fall through to call32_exit ... call32_exit: mov bx,call32_done ; Return to command loop call32_enter_rm: ; Careful here... the PM code may have relocated the ; entire RM code, so we need to figure out exactly ; where we are executing from. If the PM code has ; relocated us, it *will* have adjusted the GDT to ; match, though. call .here .here: pop ebp sub ebp,.here o32 sidt [ebp+call32_pmidt] cli cld mov [ebp+PMESP],esp ; Save exit %esp xor esp,esp ; Make sure the high bits are zero jmp 10h:.in_pm16 ; Return to 16-bit mode first bits 16 .in_pm16: mov ax,18h ; Real-mode-like segment mov es,ax mov ds,ax mov ss,ax mov fs,ax mov gs,ax lidt [call32_rmidt] ; Real-mode IDT (rm needs no GDT) mov eax,cr0 and al,~1 mov cr0,eax jmp MY_CS:.in_rm .rm_jmp equ $-2 .in_rm: ; Back in real mode mov ax,cs mov ds,ax mov es,ax mov fs,ax mov gs,ax mov ss,ax mov sp,[SavedSP] ; Restore stack jmp bx ; Go to whereever we need to go... call32_done: call disable_a20 sti ret ; ; 16-bit support code ; bits 16 ; ; 16-bit interrupt-handling code ; call32_int_rm: pushf ; Flags on stack push cs ; Return segment push word .cont ; Return address push dword edx ; Segment:offset of IVT entry retf ; Invoke IVT routine .cont: ; ... on resume ... mov bx,call32_int_resume jmp call32_enter_pm ; Go back to PM ; ; 16-bit system call handling code ; call32_sys_rm: pop gs pop fs pop es pop ds popad popfd retf ; Invoke routine .return: pushfd pushad push ds push es push fs push gs mov bx,call32_sys_resume jmp call32_enter_pm ; ; 32-bit support code ; bits 32 ; ; This is invoked on getting an interrupt in protected mode. At ; this point, we need to context-switch to real mode and invoke ; the interrupt routine. ; ; When this gets invoked, the registers are saved on the stack and ; AL contains the register number. ; call32_handle_interrupt: movzx eax,al xor ebx,ebx ; Actually makes the code smaller mov edx,[ebx+eax*4] ; Get the segment:offset of the routine mov bx,call32_int_rm jmp call32_enter_rm ; Go to real mode call32_int_resume: popad iret ; ; Syscall invocation. We manifest a structure on the real-mode stack, ; containing the call32sys_t structure from <call32.h> as well as ; the following entries (from low to high address): ; - Target offset ; - Target segment ; - Return offset ; - Return segment (== real mode cs) ; - Return flags ; call32_syscall: pushfd ; Save IF among other things... pushad ; We only need to save some, but... cld call .here .here: pop ebp sub ebp,.here movzx edi,word [ebp+SavedSP] sub edi,54 ; Allocate 54 bytes mov [ebp+SavedSP],di add edi,ebp ; Create linear address mov esi,[esp+11*4] ; Source regs xor ecx,ecx mov cl,11 ; 44 bytes to copy rep movsd movzx eax,byte [esp+10*4] ; Interrupt number ; ecx == 0 here; adding it to the EA makes the ; encoding smaller mov eax,[ecx+eax*4] ; Get IVT entry stosd ; Save in stack frame mov ax,call32_sys_rm.return ; Return offset stosw ; Save in stack frame mov eax,ebp shr eax,4 ; Return segment stosw ; Save in stack frame mov eax,[edi-12] ; Return flags and eax,0x200cd7 ; Mask (potentially) unsafe flags mov [edi-12],eax ; Primary flags entry stosw ; Return flags mov bx,call32_sys_rm jmp call32_enter_rm ; Go to real mode ; On return, the 44-byte return structure is on the ; real-mode stack. call32_enter_pm will leave ebp ; pointing to the real-mode base. call32_sys_resume: movzx esi,word [ebp+SavedSP] mov edi,[esp+12*4] ; Dest regs add esi,ebp ; Create linear address and edi,edi ; NULL pointer? jnz .do_copy .no_copy: mov edi,esi ; Do a dummy copy-to-self .do_copy: xor ecx,ecx mov cl,11 ; 44 bytes rep movsd ; Copy register block add word [ebp+SavedSP],44 ; Remove from stack popad popfd ret ; Return to 32-bit program