;  first.S  -  LILO first stage boot loader with LBA32 support
;
;  Copyright 1992-1998 Werner Almesberger
;  Copyright 1999-2005 John Coffman
;  Copyright 2009-2011 Joachim Wiedorn
;  All rights reserved.
;
;  Licensed under the terms contained in the file 'COPYING'
;  in the source directory.
;

#define LILO_ASM
#include "lilo.h"
get common.s		/* as86 "include" will bypass the CPP */

#define DEBUG 0

#if VERSION_MINOR>=50
# define DEBUG_NEW

# undef VIDEO_ENABLE
# define VIDEO_ENABLE 3

# define VALIDATE !DEBUG	/* adds 0Dh bytes */
# define SECOND_CHECK !DEBUG	/* adds  5h bytes */
# define CYL1023 DEBUG		/* subs  8h bytes */
# define GEOMETRIC !DEBUG	/* adds  1h byte  */

# if DEBUG
#  define DEBUG_LARGE
# endif
#else

# define VALIDATE 1		/* adds 0Dh bytes */
# define SECOND_CHECK 1		/* adds  5h bytes */
# define CYL1023 0		/* subs  8h bytes */
# define GEOMETRIC 1		/* adds  1h byte  */
#endif


!  VIDEO_ENABLE	for those systems that disable the video on boot
!	= 0		first stage does not enable video
!	= 1		use get vid mode/set vid mode to enable
!	= 2		use VGA enable call to enable video
!			(cannot use, as code gets too big)
!	= 3		use direct mode set (mode 3, CGA, EGA, VGA)
!	= 7		use direct mode set (mode 7, MDA)
!
#ifndef VIDEO_ENABLE
# if VALIDATE==0
#  define VIDEO_ENABLE 2
# else
#  define VIDEO_ENABLE 2
# endif
#endif

! do not change the following -- it must correspond to the code in bsect.c
#define RELOCATABLE -1


	.text

	.globl	_main

	.org	0

zero:
_main:	cli			! NT 4 blows up if this is missing
	jmp	start

stage:	.byte	STAGE_FIRST
	.org	4
reloc:
#if RELOCATABLE
	.word	theend-zero	! size of the code & params
#else
	.word	0		! no size indication
#endif
	.org	6

! Boot device parameters. They are set by the installer.

sig:	.ascii	"LILO"
vers:	.word	VERSION
mapstamp: .long 0		! map timestamp

length	=  *-sig		! for the stage 1 vs stage 2 comparison

raid:	.long	0		! raid sector offset
tstamp:	.long	0		! timestamp
map_serial_no:	.long	0	! volume S/N containing map file
prompt:	.word	0		! indicates whether to always enter prompt
				! contains many other flags, too

d_dev:	.byte	0x80		! map file device code
d_flag:	.byte	0		! disk addressing flags
d_addr:	.long	0		! disk addr of second stage index sector


edd_packet	=  0
;;;	.word	16		! size of packet
;;;	.word	1		! count of bytes to read

edd_addr	=  4
;;;	.word	map2		! where to read
;;;	.word	*-*		! segment where to read

edd_d_addr	=  8
;;;	.long	1		! low address or CX,DX (geometric)
				! start at sector 1 for search in geo mode

;;;	.long	0		! hi address

#if 0
!  These locations are referenced as EX_OFF 
!					(they used to be at CODE_START_1)
ext_si:	.word	0		! external interface
ext_es:	.word	0		! these locations are referenced in second.S
ext_bx:	.word	0		! do not disturb the ordering
ext_dl:	.byte	0		! second.S will check this magic number
ext_dh:	.byte	0		! not referenced, but must align stack
ext_stack:
#endif
	
/***************************************************/
!	The following instruction MUST be
!	first instruction after the CLI/JMP short
!	at the start of the file; otherwise
!	the boot sector relocation fails.
!
start:
	mov	ax,#BOOTSEG	! use DS,ES,SS = 0x07C0
/***************************************************/

	mov	ss,ax
	mov	sp,#SETUP_STACKSIZE	! set the stack for First Stage
	sti			! now it is safe

	push	dx		! set ext_dl (and ext_dh, which is not used)
	push	bx		! WATCH the order of pushes
	push	es		! set ext_es
	push	si		! set ext_si

#ifdef DEBUG_NEW
	push	ds
	push	es	! just not enough space with debug turned on
#endif

#define JRC_DS_EQ_SS

	cld			! do not forget to do this !!!
	mov	ds,ax		! address data area
	xor	bp,bp		! shorted addressing

#if VIDEO_ENABLE
! a BIOS has been found where the video interrupt (0x10) trashes DX
!   so, we had better be very paranoid about DX
!
# if VIDEO_ENABLE==2
	pusha			! protect DX
# endif
# if VIDEO_ENABLE > 2
	mov	ax,#VIDEO_ENABLE	! set video mode 3 or 7
# elif VIDEO_ENABLE==1
	mov	ah,#15		! get video mode
	int	0x10
	cbw
# else  /* VIDEO_ENABLE==2 */
	mov	ax,#0x1200	! enable video (VGA)
	mov	bl,#0x36	! (probably a nop on EGA or MDA)
# endif
	int	0x10		! video call
# if VIDEO_ENABLE==2
	popa			! restore DX
# endif
#endif

#if (VIDEO_ENABLE&1) == 0
	mov	al,#0x0d	! gimme a CR ...
	call	display
; the suspect call for trashing DX on one BIOS:
	mov	al,#0x0a	! ... an LF ...
	call	display
#endif

#if defined(DEBUG_NEW)
	mov	ah,dl
	call	bout		! code in AH
#endif
	mov	al,#0x4c	! ... an 'L' ...
	call	display

lagain:
	pusha			! preserve all the registers for restart

	push	ds
	pop	es		! use buffer at end of boot sector

	cmp	dl,#EX_DL_MAG	! possible boot command line (chain.S)
	jne	boot_in_dl
	mov	dl,dh		! code passed in DH instead
boot_in_dl:

	mov	bx,#map2	! buffer for volume search
	mov	dh,[d_dev](bp)  ! map device to DH

#if VALIDATE
	mov	ax,dx		! copy device code to AL
	and	ah,#0x80	! AH = 00 or 80
	xor	al,ah		! hi-bits must be the same
	js	use_installed
	cmp	al,#MAX_BIOS_DEVICES	! limit the device code
	jae	use_installed	! jump if DL is not valid
#endif

! map is on boot device for RAID1, and if so marked; viz.,

	test	byte ptr [prompt](bp),#FLAG_MAP_ON_BOOT
	jnz	use_boot	! as passed in from BIOS or MBR loader

use_installed:
	mov	dl,dh		! device code to DL
	mov	esi,[map_serial_no](bp)	! to search for
	or	esi,esi
	jz	done_search

	push	dx		! save flags

	mov	ah,#8		! get number of hard disks
	mov	dl,#0x80
	push	bx		! paranoia
	int	0x13
	pop	bx
	jc	error

	movzx	cx,dl		! extend to word in CX

#if GEOMETRIC
	mov	dx,#0x80-1	! device 80, flags=0
#else
	mov	dx,#LBA32_FLAG*256+0x80-1	! device 80, flags=LBA32
#endif

vagain:
	inc	dx
	xor	eax,eax
#if GEOMETRIC
	inc	ax		! geometric addressing
#endif
	call	disk_read	! read 

	cmp	esi,[PART_TABLE_OFFSET-6](bx)
	je	vol_found
	loop	vagain

	pop	dx		! restore specified BIOS code
				! AX and DX are identical at this point

vol_found:
		! uses value in DX, stack may have extra value

done_search:	
use_boot:
	push	bx		! save map2 for later

	mov	dh,[d_flag](bp)	! get device flags to DH
	mov	si,#d_addr
	call	pread		! increments BX

	mov	ah,#0x99	! possible error code
	cmp	dword (bx-4),#EX_MAG_HL	! "LILO"
	jne	error

	pop	si		! point at #map2

#if 1
	push	#SETUP_STACKSIZE/16 + BOOTSEG + SECTOR_SIZE/16*2
	pop	es
#else
	mov	ax,ds		! get segment
	add	ax,#SETUP_STACKSIZE/16    !   + SECTOR_SIZE/16*2
	mov	es,ax
#endif
	xor	bx,bx

sload:
	call	pread		! read using map at DS:SI
	jnz	sload		! into memory at ES:BX (auto increment)

! Verify second stage loader signature
	
	mov	si,#sig		! pointer to signature area
	mov	di,si
	mov	cx,#length	! number of bytes to compare
	mov	ah,#0x9A	! possible error code
	repe
	  cmpsb			! check Signature 1 & 2
	jne	error	! check Signature 2

#if SECOND_CHECK
/* it would be nice to re-incorporate this check */
	mov	al,#STAGE_SECOND	! do not touch AH (error code)
	scasb
	jne	error
#endif

! Start the second stage loader     DS=location of Params

	push	es		! segment of second stage
	push	bp		! BP==0

	mov	al,#0x49	! display an 'I'
	call	display

	retf			! Jump to ES:BP




disk_error2:
	mov	ah,#0x40	; signal seek error

! no return from error
error:

#ifndef LCF_NO1STDIAG
        mov	al,#32          ! display a space
	call	display0

	call	bout
#endif

#ifndef DEBUG_LARGE
	dec	byte [zero](bp)		!  CLI == 0xFA == 250
	jz	zzz

#ifndef DEBUG_NEW
	mov	sp,#SETUP_STACKSIZE-4*2-8*2	! set the stack for First Stage
#else
	mov	sp,#SETUP_STACKSIZE-4*2-2*2-8*2	! set the stack for First Stage
#endif
	popa				! restore registers for restart
	jmp	near lagain		! redo from start
#endif


zzz:
#ifndef DEBUG_NEW
	hlt
#endif
	jmp	zzz		! spin; wait for Ctrl-Alt-Del




! packet read routine

disk_read:
#ifndef JRC_DS_EQ_SS
	push	ds
#endif
	pusha

#ifndef JRC_DS_EQ_SS
	push	ss
	pop	ds
#endif

	push	bp		! BP==0
	push	bp		! BP==0

	push	eax		! low order disk address
#ifdef DEBUG_LARGE
	xchg	ax,dx
	call	wout
	xchg	ax,dx
	call	dout		! print out disk address
#endif
	push	es		! memory segment ES
	push	bx		! memory offset BX
	push	#1		! sector count
	push	#16		! size of packet = 16 bytes
	mov	si,sp		! address of packet  DS:SI

	push	bx

	test	dh,#LINEAR_FLAG|LBA32_FLAG
	jz	disk_geometric
	
	test	dh,#LBA32_FLAG
	jz	disk_convert	; it must be LINEAR

	mov	bx,#0x55AA	;magic number
	mov	ah,#0x41
	int	0x13
	jc	disk_convert
	cmp	bx,#0xAA55	;changed?
	jne	disk_convert
	test	cl,#EDD_PACKET	;EDD packet calls supported
	jnz	disk_edd

disk_convert:
	push	dx
	push	es		! protect on floppies
	mov	ah,#8		! get geometry
	int	0x13
	pop	es
disk_error3:			! transfer through on CF=1
	jc	error		! disk_error12

#if !CYL1023
	push	cx
	shr	cl,#6		;;;;
	xchg	cl,ch	   ;CX is max cylinder number
	mov	di,cx	   ;DI saves it
	pop	cx
#endif
	shr	dx,#8
	xchg	ax,dx		;AX <- DX
	inc	ax		;AX is number of heads (256 allowed)

; compensate for Davide BIOS bug
	dec	cx		; 1..63 -> 0..62;  0->63
	and	cx,#0x003f	;CX is number of sectors
	inc	cx		; allow Sectors==0 to mean 64

	mul	cx		; kills DX also
	xchg	ax,bx	   	;save in BX

	mov	ax,[edd_d_addr](si)	;low part of address
	mov	dx,[edd_d_addr+2](si)	;hi part of address

	cmp	dx,bx
	jae	disk_error2	;prevent division error

	div	bx		;AX is cyl, DX is head/sect
#if CYL1023
	cmp	ax,#1023
#else
	cmp	ax,di
#endif
	ja	disk_error2	;cyl is too big

	shl	ah,#6		; save hi 2 bits
	xchg	al,ah
	xchg	ax,dx
	div	cl		;AH = sec-1, AL = head
	or	dl,ah	   ;form Cyl/Sec
	mov	cx,dx
	inc	cx		; sector is 1 based

	pop	dx		! restore device code
	mov	dh,al		! set head#
	jmp	disk_read2



disk_edd:
	mov	ah,#0x42
disk_int13:
	pop	bx

	mov	bp,#5
disk_retry:
	pusha
	int	0x13
#if 0
	stc
	mov	ah,#0x9C
#endif
	jnc	disk_okay

	dec	bp		! does not alter CF, already 0
	jz	disk_error3	! go to "jc" with CF=1 & ZF=1

	xor	ax,ax		! reset the disk controller
	int	0x13
	popa			! reset AX,BX,CX,DX,SI
	dec	bp		! fix up BP count
	jmp	disk_retry


disk_geometric:
	push	eax
	pop	cx
	pop	ax
	mov	dh,ah

disk_read2:
	mov	ax,#0x201	;read, count of 1
	jmp	disk_int13


disk_okay:
; the pusha block is implicitly removed below
;;;	mov	(si+2*16-1),ah	! set error code
;   the error code is never checked
	lea	sp,(si+16)	! do not touch carry; 
	popa
#ifndef JRC_DS_EQ_SS
	pop	ds
#endif
	ret



! Pointer Read -- read using pointer in DS:SI

pread:
	lodsd			! get address
	or	eax,eax
	jz	done
	add	eax,[raid](bp)	! reloc is 0 on non-raid
	call	disk_read	

	add	bh,#SECTOR_SIZE/256    	! next sector
done:
	ret




#if !defined(LCF_NO1STDIAG) || defined(DEBUG_NEW)
bout:	rol     ax,#4		! bring hi-nibble to position
	call	nout
	rol     ax,#4		! bring lo-nibble to position
nout:	and	al,#0x0F	! display one nibble
	daa			! shorter conversion routine
	add	al,#0xF0
	adc	al,#0x40	! is now a hex char 0..9A..F
#endif
; display - write byte in AL to console
;	preserves all register contents
; 
display0:
#ifndef LCF_NOVGA
display:
#endif
	pusha		! make sure no register is changed
	mov	bx,#7		!  BH=0, BL=07
	mov	ah,#14
	int	0x10
	popa		! restore all the registers
#ifdef LCF_NOVGA
display:
#endif
	ret

#ifdef DEBUG_LARGE

dout:	pushad
	ror	eax,#16
	call	wout
	ror	eax,#16
	call	wout
	mov	al,#0x20	! space
	call	display
	popad
	ret

wout:	push	ax
	call	bout	! put out AH
	pop	ax
	push	ax
	xchg	al,ah
	call	bout	! put out AL (now in AH)
	pop	ax
	ret
#endif

theend:

!
!   If 'first' loads as the MBR, then there must be space for the partition
!   table.  If 'first' loads as the boot record of some partition, then
!   the space reserved below is not used.  But we must reserve the area
!   as a hedge against the first case.
!
!
	.org	MAX_BOOT_SIZE	!
	.word	0,0,0,0		! space for NT, DRDOS, and LiLO volume S/N

!	.org	0x1be		! spot for the partition table
p_table:
	.blkb	16
	.blkb	16
	.blkb	16
	.blkb	16
#ifdef FIRST
	.org	*-2
	.long	FIRST		! boot block check
#else
	.word	0xAA55		! boot block signature
#endif

! Better be exactly 0x200

map2	equ	*		! addressed as ES:[map2]
