/*
 *	bootsect.S		Copyright (C) 1991, 1992 Linus Torvalds
 *
 *	modified by Drew Eckhardt
 *	modified by Bruce Evans (bde)
 *	modified by Chris Noe (May 1999) (as86 -> gas)
 *
 * 360k/720k disk support: Andrzej Krzysztofowicz <ankry@green.mif.pg.gda.pl>
 *
 * BIG FAT NOTE: We're in real mode using 64k segments.  Therefore segment
 * addresses must be multiplied by 16 to obtain their respective linear
 * addresses. To avoid confusion, linear addresses are written using leading
 * hex while segment addresses are written as segment:offset.
 *
 * bde - should not jump blindly, there may be systems with only 512K low
 * memory.  Use int 0x12 to get the top of memory, etc.
 *
 * It then loads 'setup' directly after itself (0x90200), and the system
 * at 0x10000, using BIOS interrupts. 
 *
 * NOTE! currently system is at most (8*65536-4096) bytes long. This should 
 * be no problem, even in the future. I want to keep it simple. This 508 kB
 * kernel size should be enough, especially as this doesn't contain the
 * buffer cache as in minix (and especially now that the kernel is 
 * compressed :-)
 *
 * The loader has been made as simple as possible, and continuous
 * read errors will result in a unbreakable loop. Reboot by hand. It
 * loads pretty fast by getting whole tracks at a time whenever possible.
 */

#include <asm/boot.h>

SETUPSECTS	= 4			/* default nr of setup-sectors */
BOOTSEG		= 0x07C0		/* original address of boot-sector */
INITSEG		= DEF_INITSEG		/* we move boot here - out of the way */
SETUPSEG	= DEF_SETUPSEG		/* setup starts here */
SYSSEG		= DEF_SYSSEG		/* system loaded at 0x10000 (65536) */
SYSSIZE		= DEF_SYSSIZE		/* system size: # of 16-byte clicks */
					/* to be loaded */
ROOT_DEV	= 0 			/* ROOT_DEV is now written by "build" */
SWAP_DEV	= 0			/* SWAP_DEV is now written by "build" */

#ifndef SVGA_MODE
#define SVGA_MODE ASK_VGA
#endif

#ifndef RAMDISK
#define RAMDISK 0
#endif

#ifndef ROOT_RDONLY
#define ROOT_RDONLY 1
#endif

.code16
.text

.global _start
_start:

# First things first. Move ourself from 0x7C00 -> 0x90000 and jump there.

	movw	$BOOTSEG, %ax
	movw	%ax, %ds		# %ds = BOOTSEG
	movw	$INITSEG, %ax
	movw	%ax, %es		# %ax = %es = INITSEG
	movw	$256, %cx
	subw	%si, %si
	subw	%di, %di
	cld
	rep
	movsw
	ljmp	$INITSEG, $go

# bde - changed 0xff00 to 0x4000 to use debugger at 0x6400 up (bde).  We
# wouldn't have to worry about this if we checked the top of memory.  Also
# my BIOS can be configured to put the wini drive tables in high memory
# instead of in the vector table.  The old stack might have clobbered the
# drive table.

go:	movw	$0x4000-12, %di		# 0x4000 is an arbitrary value >=
					# length of bootsect + length of
					# setup + room for stack;
					# 12 is disk parm size.
	movw	%ax, %ds		# %ax and %es already contain INITSEG
	movw	%ax, %ss
	movw	%di, %sp		# put stack at INITSEG:0x4000-12.

# Many BIOS's default disk parameter tables will not recognize
# multi-sector reads beyond the maximum sector number specified
# in the default diskette parameter tables - this may mean 7
# sectors in some cases.
#
# Since single sector reads are slow and out of the question,
# we must take care of this by creating new parameter tables
# (for the first disk) in RAM.  We will set the maximum sector
# count to 36 - the most we will encounter on an ED 2.88.  
#
# High doesn't hurt.  Low does.
#
# Segments are as follows: %cs = %ds = %es = %ss = INITSEG, %fs = 0,
# and %gs is unused.

	movw	%cx, %fs		# %fs = 0
	movw	$0x78, %bx		# %fs:%bx is parameter table address
	pushw	%ds
	ldsw	%fs:(%bx), %si		# %ds:%si is source
	movb	$6, %cl			# copy 12 bytes
	pushw	%di			# %di = 0x4000-12.
	rep				# don't worry about cld
	movsw				# already done above
	popw	%di
	popw	%ds
	movb	$36, 0x4(%di)		# patch sector count
	movw	%di, %fs:(%bx)
	movw	%es, %fs:2(%bx)

# Get disk drive parameters, specifically number of sectors/track.

# It seems that there is no BIOS call to get the number of sectors.
# Guess 36 sectors if sector 36 can be read, 18 sectors if sector 18
# can be read, 15 if sector 15 can be read.  Otherwise guess 9.
# Note that %cx = 0 from rep movsw above.

	movw	$disksizes, %si		# table of sizes to try
probe_loop:
	lodsb
	cbtw				# extend to word
	movw	%ax, sectors
	cmpw	$disksizes+4, %si
	jae	got_sectors		# If all else fails, try 9

	xchgw	%cx, %ax		# %cx = track and sector
	xorw	%dx, %dx		# drive 0, head 0
	movw	$0x0200, %bx		# address = 512, in INITSEG (%es = %cs)
	movw	$0x0201, %ax		# service 2, 1 sector
	int	$0x13
	jc	probe_loop		# try next value

got_sectors:
	movb	$0x03, %ah		# read cursor pos
	xorb	%bh, %bh
	int	$0x10
	movw	$9, %cx
	movb	$0x07, %bl		# page 0, attribute 7 (normal)
					# %bh is set above; int10 doesn't
					# modify it
	movw	$msg1, %bp
	movw	$0x1301, %ax		# write string, move cursor
	int	$0x10			# tell the user we're loading..

# Load the setup-sectors directly after the moved bootblock (at 0x90200).
# We should know the drive geometry to do it, as setup may exceed first
# cylinder (for 9-sector 360K and 720K floppies).

	movw	$0x0001, %ax		# set sread (sector-to-read) to 1 as
	movw	$sread, %si		# the boot sector has already been read
	movw	%ax, (%si)

	xorw	%ax, %ax		# reset FDC
	xorb	%dl, %dl
	int	$0x13
	movw	$0x0200, %bx		# address = 512, in INITSEG
next_step:
	movb	setup_sects, %al
	movw	sectors, %cx
	subw	(%si), %cx		# (%si) = sread
	cmpb	%cl, %al
	jbe	no_cyl_crossing
	movw	sectors, %ax
	subw	(%si), %ax		# (%si) = sread
no_cyl_crossing:
	call	read_track
	pushw	%ax			# save it
	call	set_next		# set %bx properly; it uses %ax,%cx,%dx
	popw	%ax			# restore
	subb	%al, setup_sects	# rest - for next step
	jnz	next_step

	pushw	$SYSSEG
	popw	%es			# %es = SYSSEG
	call	read_it
	call	kill_motor
	call	print_nl

# After that we check which root-device to use. If the device is
# defined (!= 0), nothing is done and the given device is used.
# Otherwise, one of /dev/fd0H2880 (2,32) or /dev/PS0 (2,28) or /dev/at0 (2,8)
# depending on the number of sectors we pretend to know we have.

# Segments are as follows: %cs = %ds = %ss = INITSEG,
#	%es = SYSSEG, %fs = 0, %gs is unused.

	movw	root_dev, %ax
	orw	%ax, %ax
	jne	root_defined

	movw	sectors, %bx
	movw	$0x0208, %ax		# /dev/ps0 - 1.2Mb
	cmpw	$15, %bx
	je	root_defined

	movb	$0x1c, %al		# /dev/PS0 - 1.44Mb
	cmpw	$18, %bx
	je	root_defined

	movb	$0x20, %al		# /dev/fd0H2880 - 2.88Mb
	cmpw	$36, %bx
	je	root_defined

	movb	$0, %al			# /dev/fd0 - autodetect
root_defined:
	movw	%ax, root_dev

# After that (everything loaded), we jump to the setup-routine
# loaded directly after the bootblock:

	ljmp	$SETUPSEG, $0

# These variables are addressed via %si register as it gives shorter code.

sread:	.word 0				# sectors read of current track
head:	.word 0				# current head
track:	.word 0				# current track

# This routine loads the system at address SYSSEG, making sure
# no 64kB boundaries are crossed. We try to load it as fast as
# possible, loading whole tracks whenever we can.

read_it:
	movw	%es, %ax		# %es = SYSSEG when called
	testw	$0x0fff, %ax
die:	jne	die			# %es must be at 64kB boundary
	xorw	%bx, %bx		# %bx is starting address within segment
rp_read:
#ifdef __BIG_KERNEL__
					# look in setup.S for bootsect_kludge
	bootsect_kludge = 0x220		# 0x200 + 0x20 which is the size of the
	lcall	bootsect_kludge		# bootsector + bootsect_kludge offset
#else
	movw	%es, %ax
	subw	$SYSSEG, %ax
	movw	%bx, %cx
	shr	$4, %cx
	add	%cx, %ax		# check offset
#endif
	cmpw	syssize, %ax		# have we loaded everything yet?
	jbe	ok1_read

	ret

ok1_read:
	movw	sectors, %ax
	subw	(%si), %ax		# (%si) = sread
	movw	%ax, %cx
	shlw	$9, %cx
	addw	%bx, %cx
	jnc	ok2_read

	je	ok2_read

	xorw	%ax, %ax
	subw	%bx, %ax
	shrw	$9, %ax
ok2_read:
	call	read_track
	call	set_next
	jmp	rp_read

read_track:
	pusha
	pusha	
	movw	$0xe2e, %ax 		# loading... message 2e = .
	movw	$7, %bx
 	int	$0x10
	popa		

# Accessing head, track, sread via %si gives shorter code.

	movw	4(%si), %dx		# 4(%si) = track
	movw	(%si), %cx		# (%si)  = sread
	incw	%cx
	movb	%dl, %ch
	movw	2(%si), %dx		# 2(%si) = head
	movb	%dl, %dh
	andw	$0x0100, %dx
	movb	$2, %ah
	pushw	%dx			# save for error dump
	pushw	%cx
	pushw	%bx
	pushw	%ax
	int	$0x13
	jc	bad_rt

	addw	$8, %sp
	popa
	ret

set_next:
	movw	%ax, %cx
	addw	(%si), %ax		# (%si) = sread
	cmp	sectors, %ax
	jne	ok3_set
	movw	$0x0001, %ax
	xorw	%ax, 2(%si)		# change head
	jne	ok4_set
	incw	4(%si)			# next track
ok4_set:
	xorw	%ax, %ax
ok3_set:
	movw	%ax, (%si)		# set sread
	shlw	$9, %cx
	addw	%cx, %bx
	jnc	set_next_fin
	movw	%es, %ax
	addb	$0x10, %ah
	movw	%ax, %es
	xorw	%bx, %bx
set_next_fin:
	ret

bad_rt:
	pushw	%ax			# save error code
	call	print_all		# %ah = error, %al = read
	xorb	%ah, %ah
	xorb	%dl, %dl
	int	$0x13
	addw	$10, %sp
	popa
	jmp read_track

# print_all is for debugging purposes.  
#
# it will print out all of the registers.  The assumption is that this is
# called from a routine, with a stack frame like
#
#	%dx 
#	%cx
#	%bx
#	%ax
#	(error)
#	ret <- %sp
 
print_all:
	movw	$5, %cx			# error code + 4 registers
	movw	%sp, %bp
print_loop:
	pushw	%cx			# save count remaining
	call	print_nl		# <-- for readability
	cmpb	$5, %cl
	jae	no_reg			# see if register name is needed
	
	movw	$0xe05 + 'A' - 1, %ax
	subb	%cl, %al
	int	$0x10
	movb	$'X', %al
	int	$0x10
	movb	$':', %al
	int	$0x10
no_reg:
	addw	$2, %bp			# next register
	call	print_hex		# print it
	popw	%cx
	loop	print_loop
	ret

print_nl:
	movw	$0xe0d, %ax		# CR
	int	$0x10
	movb	$0xa, %al		# LF
	int 	$0x10
	ret

# print_hex is for debugging purposes, and prints the word
# pointed to by %ss:%bp in hexadecimal.

print_hex:
	movw	$4, %cx			# 4 hex digits
	movw	(%bp), %dx		# load word into %dx
print_digit:
	rolw	$4, %dx			# rotate to use low 4 bits
	movw	$0xe0f, %ax		# %ah = request
	andb	%dl, %al		# %al = mask for nybble
	addb	$0x90, %al		# convert %al to ascii hex
	daa				# in only four instructions!
	adc	$0x40, %al
	daa
	int	$0x10
	loop	print_digit
	ret

# This procedure turns off the floppy drive motor, so
# that we enter the kernel in a known state, and
# don't have to worry about it later.
# NOTE: Doesn't save %ax or %dx; do it yourself if you need to.

kill_motor:
#if 1
	xorw	%ax, %ax		# reset FDC
	xorb	%dl, %dl
	int	$0x13
#else
	movw	$0x3f2, %dx
	xorb	%al, %al
	outb	%al, %dx
#endif
	ret

sectors:	.word 0
disksizes:	.byte 36, 18, 15, 9
msg1:		.byte 13, 10
		.ascii "Loading"

# XXX: This is a fairly snug fit.

.org 497
setup_sects:	.byte SETUPSECTS
root_flags:	.word ROOT_RDONLY
syssize:	.word SYSSIZE
swap_dev:	.word SWAP_DEV
ram_size:	.word RAMDISK
vid_mode:	.word SVGA_MODE
root_dev:	.word ROOT_DEV
boot_flag:	.word 0xAA55
