;; -*- fundamental -*-
;; -----------------------------------------------------------------------
;;
;;   Copyright 1994-2008 H. Peter Anvin - All Rights Reserved
;;
;;   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.
;;

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

%define STACK_HEAP_SIZE	(128*1024)

		section .rodata align=16
		section .data   align=16
		section .bss    align=16

;; -----------------------------------------------------------------------
;;  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

		section .rodata
memdisk_version:
		db "MEMDISK ", VERSION, " ", 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
		xor esp,esp		; Stack at top of 64K segment
		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
		mov ss,si
		mov esp,0x7C00		; Good place for SP to start out
		call 0:0x7C00
		int 18h			; A far return -> INT 18h

;
; 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."
;
		section .rodata

	; 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: Code segment, use16, readable, dpl 0, base CS_BASE, 64K
		desc CS_BASE, 0xffff, 0x009b

		; 0010: Data segment, use16, read/write, dpl 0, base CS_BASE, 64K
		desc CS_BASE, 0xffff, 0x0093

		; 0018: Data segment, use16, read/write, dpl 0, base 0, 4G
		desc 0, 0xfffff, 0x809b

		; 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
SavedSSSP	resd 1			; Place to save SS:SP
Return		resd 1			; Return value
A20Test		resw 1			; Space to test A20
A20Tries	resb 1

		section .data
		alignb 4
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			; Command write
		out 064h, al
		call empty_8042_uncond

		mov al,0DFh			; A20 on
		out 060h, 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.
;
a20_test:
		push es
		push cx
		push ax
		mov cx,0FFFFh		; HMA = segment 0FFFFh
		mov es,cx
		mov cx,32		; Loop count
		mov ax,[A20Test]
.a20_wait:	inc ax
		mov [A20Test],ax
		io_delay		; Serialize, and fix delay
		cmp ax,[es:A20Test+CS_BASE+10h]
		loopz .a20_wait
.a20_done:	pop ax
		pop cx
		pop es
		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		; Command write
		call empty_8042_uncond
		mov al,0DDh		; A20 off
		out 060h, 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 4, db 0
call32_pmidt:
		dw 8*256		; Limit
		dd pm_idt+CS_BASE	; Address

call32_rmidt:
		dw 0ffffh		; Limit
		dd 0			; Address

		section .text
;
; This is the main entrypoint in this function
;
init32:
		mov ebx,call32_call_start+CS_BASE	; Where to go in PM

call32_enter_pm:
		mov ax,cs
		mov ds,ax
		cli
		mov [SavedSSSP],sp
		mov [SavedSSSP+2],ss
		cld
		call a20_test
		jnz .a20ok
		call enable_a20

.a20ok:
		lgdt [call32_gdt]	; Set up GDT
		lidt [call32_pmidt]	; Set up the IDT
		mov eax,cr0
		or al,1
		mov cr0,eax		; Enter protected mode
		jmp 20h:dword .in_pm+CS_BASE

		bits 32
.in_pm:
		xor eax,eax		; Available for future use...
		mov fs,eax
		mov gs,eax

		mov al,28h		; Set up data segments
		mov es,eax
		mov ds,eax
		mov ss,eax

		mov esp,[PMESP+CS_BASE]	; 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:
		;
		; Point the stack into low memory
		; We have: this segment, bounce buffer, then stack+heap
		;
		mov esp, CS_BASE + 0x20000 + STACK_HEAP_SIZE
		and esp, ~0xf

		;
		; Set up the protmode IDT and the interrupt jump buffers
		;
		mov edi,pm_idt+CS_BASE

		; Form an interrupt gate descriptor
		; WARNING: This is broken if pm_idt crosses a 64K boundary;
		; however, it can't because of the alignment constraints.
		mov ebx,pm_idt+CS_BASE+8*256
		mov eax,0x0020ee00
		xchg ax,bx
		xor ecx,ecx
		inc ch				; ecx <- 256

		push ecx
.make_idt:
		stosd
		add eax,8
		xchg eax,ebx
		stosd
		xchg eax,ebx
		loop .make_idt

		pop ecx

		; Each entry in the interrupt jump buffer contains
		; the following instructions:
		;
		; 00000000 60                pushad
		; 00000001 B0xx              mov al,<interrupt#>
		; 00000003 E9xxxxxxxx        jmp call32_handle_interrupt

		mov eax,0xe900b060
		mov ebx,call32_handle_interrupt+CS_BASE
		sub ebx,edi

.make_ijb:
		stosd
		sub [edi-2],cl			; Interrupt #
		xchg eax,ebx
		sub eax,8
		stosd
		xchg eax,ebx
		loop .make_ijb

		; Now everything is set up for interrupts...

		push dword (BOUNCE_SEG << 4)	; Bounce buffer address
		push dword call32_syscall+CS_BASE ; Syscall entry point
		sti				; Interrupts OK now
		call pm_entry-CS_BASE		; Run the program...

		; ... on return ...
		mov [Return+CS_BASE],eax

		; ... fall through to call32_exit ...

call32_exit:
		mov bx,call32_done	; Return to command loop

call32_enter_rm:
		cli
		cld
		mov [PMESP+CS_BASE],esp	; Save exit %esp
		xor esp,esp		; Make sure the high bits are zero
		jmp 08h:.in_pm16	; Return to 16-bit mode first

		bits 16
.in_pm16:
		mov ax,10h		; 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

.in_rm:					; Back in real mode
		mov ax,cs		; Set up sane segments
		mov ds,ax
		mov es,ax
		mov fs,ax
		mov gs,ax
		lss sp,[SavedSSSP]	; Restore stack
		jmp bx			; Go to whereever we need to go...

call32_done:
		call disable_a20
		sti
		mov ax,[Return]
		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 ebx,call32_int_resume+CS_BASE
		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 ebx,call32_sys_resume+CS_BASE
		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

		movzx edi,word [SavedSSSP+CS_BASE]
		movzx eax,word [SavedSSSP+CS_BASE+2]
		sub edi,54		; Allocate 54 bytes
		mov [SavedSSSP+CS_BASE],di
		shl eax,4
		add edi,eax		; 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 eax,call32_sys_rm.return + (MY_CS << 16) ; Return seg:offs
		stosd			; 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_sys_resume:
		movzx esi,word [SavedSSSP+CS_BASE]
		movzx eax,word [SavedSSSP+CS_BASE+2]
		mov edi,[esp+12*4]	; Dest regs
		shl eax,4
		add esi,eax		; 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 dword [SavedSSSP+CS_BASE],44	; Remove from stack

		popad
		popfd
		ret			; Return to 32-bit program
