/*
 **************************************************************************
 *
 * Boot-ROM-Code to load an operating system across a TCP/IP network.
 *
 * Module:  floppy.S86
 * Purpose: Floppy boot sector to load the rest of the boot rom code
 * Entries: called by BIOS at offset 0
 *
 **************************************************************************
 *
 * Copyright (C) 1995-2007 Gero Kuhlmann <gero@gkminix.han.de>
 *
 *  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; either version 2 of the License, or
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: floppy.S86,v 1.7 2007/01/06 18:30:47 gkminix Exp $
 *
 **************************************************************************
 */

#include <common.i86>
#include <romconfig.i86>
#include <system/bootrom.i86>
#include <system/bios.i86>
#include <system/pnp.i86>
#include <system/partition.i86>
#include "ldram.i86"

.file __FILE__
.line __LINE__



/*
 **************************************************************************
 *
 * Equates local to this module:
 */

#define LOADADR		FLOPMEM			/* boot code load segment */
#define LOADSIZE	(FLOPEND - FLOPMEM)	/* required load memory size */
#define SECTSIZE	512			/* size of one floppy sector */
#define SECTBITS	9			/* binary log of SECTSIZE */




.line __LINE__
/*
 **************************************************************************
 *
 * Here the loader program starts. Some ideas are taken from the Linux
 * boot sector. Note that this program has to be completely seperate
 * from all other segments of the boot rom code. It can contain data
 * since its getting loaded into the ram.
 * This program gets either loaded by the BIOS directly off a floppy
 * boot sector, or from a a hard disk boot sector. These cases can
 * be distinguished by the value of DL, which contains the BIOS boot
 * drive ID. If it got loaded from a hard disk, DS:SI points to the
 * current partition table entry.
 * The loader works by first patching the floppy disk parameter table,
 * and then trying to read the last sector of the floppy in order to
 * find out about the disk size. Then it will load 128 kB from the
 * diskette into the OS loading area.
 */

	.text				# begin loader text segment


	.global	_start

_start:	call	1f
1:	pop	ax			# determine the offset
	sub	ax,offset 1b
	sar	ax,4
	mov	bx,cs			# normalize the CS register so that
	add	bx,ax			# this program always starts at offset
	push	bx			# 0x7C00.
	push	offset 2f
	lret

/*
 * Adjust the stack. This is necessary, because on some older systems the
 * BIOS puts the stack pointer to 0:0400 instead of 0:7C00. But this will
 * destroy some of the interrupt vectors when we call the bootrom.
 * Note that the following code assumes that the linker correctly linked
 * us to start at offset 0x7C00.
 */

2:	or	bx,bx
	jnz	6f
	cli
	mov	ss,bx
	mov	sp,offset _start
	sti

/*
 * Check if we were loaded off a hard disk.
 */

6:	cmp	dl,0x80
	jb	3f

/*
 * For hard disks, we can use the BIOS to determine the number of sectors
 * per track and the number of heads. For floppies, this doesnt work.
 */

	mov	ecx,part_first_abs_sect[si]
	mov	ds,bx
	mov	[startsect],ecx
	mov	[drivenum],dl

	xor	di,di
	mov	es,di
	mov	ah,0x08			# ask the BIOS about the disk
	int	INT_DISK		# parameters
	mov	si,offset errask
	jc	lderr
	
	movzx	ax,dh
	inc	ax			# save number of heads
	mov	[heads],ax
	and	cx,0x003F		# save number of sectors per track
	mov	bx,LOADADR
	mov	es,bx			# set load address
	jmp	4f

/*
 * Next determine the number of sectors per track on the floppy. To do
 * this the disk parameter table has first to be changed, so that the
 * BIOS is able to access at least 18 sectors in a track.
 * Note that we dont check if the system really has enough base memory
 * for loading everything. Is there any systems on the market which still
 * dont have 640kB in the lower memory area?
 */

3:	cld
	mov	es,bx
	xor	ax,ax
	mov	ds,ax			# first save the old disk parameter
	lds	si,ds:[DPB_VECT]	# table
	mov	word ptr es:[oldvect + 0],si
	mov	word ptr es:[oldvect + 2],ds
	mov	di,offset newdpb
	mov	cx,DPB_SIZE
	rep	movsb
	mov	byte ptr es:[newdpb + dpb_sptrack],18
	mov	word ptr ds:[DPB_VECT + 0],offset newdpb
	mov	word ptr ds:[DPB_VECT + 2],bx
	mov	ds,bx

/*
 * Next try to find the number of sectors per track. To do this, the
 * last sector for each disk format is read. This idea has been taken
 * from the Linux boot sector.
 */

	xor	dx,dx			# reset drive 0 (AX is already zero
	int 	INT_DISK		# from the code above)

	mov	ax,LOADADR
	mov	es,ax			# set load address
	xor	bx,bx
	mov	cx,1			# load one sector to initialize drive
	mov	ax,0x0201
	int	INT_DISK

	mov	cx,18			# sector 18, track 0
	mov	ax,0x0201		# read one sector
	int	INT_DISK
	jnc	4f
	mov	cx,15			# sector 15
	mov	ax,0x0201		# read one sector
	int	INT_DISK
	jnc	4f
	mov	cx,9
4:	mov	[sects],cx		# save number of sectors per track

/*
 * Now load the next 128 kB starting at the second sector of track 0. In
 * this loop, CX contains the number of sectors to load, and AX contains
 * the logical number of the next sector to load.
 */

	push	es				# save load segment for later
	mov	cx,LOADSIZE / (SECTSIZE / 16)	# number of sectors to load
	mov	ax,1				# starting sector
	
5:	push	ax
	push	cx
	mov	bx,cx
	call	cvtsec			# compute sector information
	jc	8f
	
	movzx	ax,byte ptr [sects]
	sub	al,cl			# compute the number of sectors
	and	al,0x3F			# to load with one BIOS call
	inc	ax
	cmp	bx,ax
	jae	6f
	mov	ax,bx
6:	push	ax			# save number of sectors to load
	xor	bx,bx			# starting offset of load memory
	mov	ah,0x02			# load sectors
	int	INT_DISK
	pop	bx			# get number of sectors loaded
	jnc	7f			# there was no error

	cmp	ah,9
	jne	8f
	mov	bx,es			# that error we can handle
	mov	ax,bx
	or	ax,0x0FFF
	inc	ax
	sub	ax,bx			# we can load ax * 16 more bytes
	shr	ax,SECTBITS - 4
	xor	ah,ah
	jmp	6b
	
8:	stc
7:	pop	cx
	pop	ax
	jc	9f			# check for error inside inner loop
	sub	cx,bx			# decrement sector count
	jbe	loaded			# check if we loaded all sectors

	add	ax,bx			# increment sector number
	shl	bx,SECTBITS - 4
	mov	dx,es
	add	dx,bx			# increment load pointer
	mov	es,dx
	jmp	5b

9:	pop	es
	mov	si,offset dskmsg	# print error message


/*
 * Print an error message and reboot
 */

lderr:	call	prnstr
reboot:	mov	si,offset botmsg
	call	prnstr
	xor	ah,ah			# wait for a key press
	int	INT_KBD
	int	INT_BOOTFAIL
	jmp	reboot


/*
 * Restore the floppy DPB.
 */

loaded:	xor	ax,ax
	mov	es,ax
	cmp	byte ptr [drivenum],0x80
	jae	1f
	mov	edx,[oldvect]		# restore old vector
	mov	es:[DPB_VECT],edx
	xor	dx,dx			# reset disk system
	int	INT_DISK


/*
 * The boot code is in the memory now. We cant use the bootrom initialization
 * routine because it gets confused when we start this floppy boot on a system
 * which has a netboot bootrom already installed. Instead, we just setup the
 * bootrom RAM area and then call it directly via the PnP header structure.
 * For a standard bootrom the ram data area is all empty, and we just have
 * to set the signature and checksum.
 */

1:	mov	ds,ax
	mov	di,RAM_START		# clear bootrom data area
	mov	cx,RAM_SIZE / 2
	rep	stosw
	mov	word ptr ds:[RAM_START + ram_id],RAM_SIGNATURE
	mov	word ptr ds:[RAM_START + ram_pcipfa],0xFFFF
	mov	word ptr ds:[RAM_START + ram_pnpcsn],0xFFFF
	mov	byte ptr ds:[RAM_START + ram_chksum],RAM_DFLTCHK

	mov	ax,cs
	or	ax,ax
	jz	2f
	int3				# debugger hook

2:	pop	ds			# restore load segment
	mov	si,ds:[ROMPNPPTROFS]
	push	cs			# push return address
	push	offset reboot
	push	ds			# push call address
	push	word ptr pnphdr_bev[si]
	lret				# call the bootrom



/*
 **************************************************************************
 *
 * Routine to convert an absolute sector into a sector/track/head information.
 * Input:  AX  -  relative sector number
 * Output: CL  -  sector number
 *         CH  -  track number
 *         DH  -  head number
 *         DL  -  drive number
 *         Carry flag set if error
 * Registers changed: AX, CX, DX
 */
cvtsec:	xor	dx,dx
	add	ax,word ptr [startsect + 0]
	adc	dx,word ptr [startsect + 2]
	cmp	[sects],dx		# in case of error this will set the
	jb	9f			# carry flag
	push	bx
	div	word ptr [sects]	# compute sector number in a track
	inc	dl			# sector numbers always start with 1
	mov	bl,dl
	xor	dx,dx
	div	word ptr [heads]	# divide by number of heads
	mov	dh,dl			# set head number
	mov	bh,al			# set track number
	shl	ah,6			# isolate highest 2 bits of 10-bit
	mov	cx,bx			# track number and move them into
	or	cl,ah			# CL
	mov	dl,[drivenum]		# set drive number
	pop	bx
	clc
9:	ret



/*
 **************************************************************************
 *
 * Routine to print a string onto the console.
 * Input:  CS:SI  -  address of NULL terminated string
 * Output: None
 * Registers changed: AX, BX, SI
 */
1:	mov	ah,0x0E			# BIOS command for character output
	mov	bx,0x0007		# screen page and colors
	int	INT_VIDEO		# send character to screen
	inc	si
prnstr:	mov	al,cs:[si]		# get next character
	or	al,al
	jnz	1b
	ret




/*
 **************************************************************************
 *
 * Strings and messages:
 */
dskmsg:	.ascii	"Read error"
	.byte	0x0D,0x0A,0
	
errask:	.ascii	"BIOS error"
	.byte	0x0D,0x0A,0

botmsg:	.ascii	"Press a key..."
	.byte	0




/*
 **************************************************************************
 *
 * Data area
 */
sects:		.word	0		# number of sectors per track
heads:		.word	2		# number of heads
startsect:	.long	0		# logical start sector
drivenum:	.byte	0		# BIOS drive number
oldvect:	.long	0		# old dpt vector


		.org	SECTSIZE - 2

		.word	0xAA55
newdpb:					# changed disk parameter table comes
					# right at the end of everything



/*
 **************************************************************************
 */
		.end

