/*
 * FAT Partition Boot Sector. Loaded at 0x7C00:
 *	8a pbs.s; 8l -o pbs -l -H3 -T0x7C00 pbs.8
 * Will load the target at LOADSEG*16+LOADOFF, so the target
 * should be probably be loaded with LOADOFF added to the
 * -Taddress.
 * If LOADSEG is a multiple of 64KB and LOADOFF is 0 then
 * targets larger than 64KB can be loaded.
 *
 * This code uses the traditional INT13 BIOS interface and can
 * therefore only access the first 8.4GB of the disc.
 *
 * It relies on the _volid field in the FAT header containing
 * the LBA of the root directory.
 */
#include "x16.h"
#include "mem.h"

#define LOADSEG		(0x10000/16)	/* where to load code (64KB) */
#define LOADOFF		0
#define DIROFF		0x0200		/* where to read the root directory */

/*
 * FAT directory entry.
 */
#define Dname		0x00
#define Dext		0x08
#define Dattr		0x0B
#define Dtime		0x16
#define Ddate		0x18
#define Dstart		0x1A
#define Dlengthlo	0x1C
#define Dlengthhi	0x1E

#define Dirsz		0x20

/*
 * Data is kept on the stack, indexed by rBP.
 */
#define Xdap		0x00		/* disc address packet */
#define Xrootsz		0x10		/* file data area */
#define Xdrive		0x12		/* boot drive, passed by BIOS or MBR */
#define Xtotal		0x14		/* sum of allocated data above */

TEXT _magic(SB), $0
	BYTE $0xEB; BYTE $0x3C;		/* jmp .+ 0x3C  (_start0x3E) */
	BYTE $0x90			/* nop */
TEXT _version(SB), $0
	BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x00;
	BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x00
TEXT _sectsize(SB), $0
	BYTE $0x00; BYTE $0x00
TEXT _clustsize(SB), $0
	BYTE $0x00
TEXT _nresrv(SB), $0
	BYTE $0x00; BYTE $0x00
TEXT _nfats(SB), $0
	BYTE $0x00
TEXT _rootsize(SB), $0
	BYTE $0x00; BYTE $0x00
TEXT _volsize(SB), $0
	BYTE $0x00; BYTE $0x00
TEXT _mediadesc(SB), $0
	BYTE $0x00
TEXT _fatsize(SB), $0
	BYTE $0x00; BYTE $0x00
TEXT _trksize(SB), $0
	BYTE $0x00; BYTE $0x00
TEXT _nheads(SB), $0
	BYTE $0x00; BYTE $0x00
TEXT _nhiddenlo(SB), $0
	BYTE $0x00; BYTE $0x00
TEXT _nhiddenhi(SB), $0
	BYTE $0x00; BYTE $0x00;
TEXT _bigvolsize(SB), $0
	BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x00;
TEXT _driveno(SB), $0
	BYTE $0x00
TEXT _reserved0(SB), $0
	BYTE $0x00
TEXT _bootsig(SB), $0
	BYTE $0x00
TEXT _volid(SB), $0
	BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x00;
TEXT _label(SB), $0
	BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x00;
	BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x00
	BYTE $0x00; BYTE $0x00; BYTE $0x00
TEXT _type(SB), $0
	BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x00;
	BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x00;

_start0x3E:
	CLI
	CLR(rAX)
	MTSR(rAX, rSS)			/* 0000 -> rSS */
	MTSR(rAX, rDS)			/* 0000 -> rDS, source segment */
	MTSR(rAX, rES)
	LWI(_magic-Xtotal(SB), rSP)
	MW(rSP, rBP)			/* set the indexed-data pointer */

	SBPB(rDL, Xdrive)		/* save the boot drive */

	/* booting from a CD starts us at 7C0:0.  Move to 0:7C00 */
	PUSHR(rAX)
	LWI(_nxt(SB), rAX)
	PUSHR(rAX)
	BYTE $0xCB	/* FAR RET */

TEXT _nxt(SB), $0
	STI

	LWI(confidence(SB), rSI)	/* for that warm, fuzzy feeling */
	CALL16(BIOSputs(SB))

	CALL16(dreset(SB))

_jmp00:
	LW(_volid(SB), rAX)		/* Xrootlo */
	LW(_volid+2(SB), rDX)		/* Xroothi */

	LWI(_magic+DIROFF(SB), rBX)
	CALL16(BIOSread(SB))		/* read the root directory */

	LWI((512/Dirsz), rBX)

	LWI(_magic+DIROFF(SB), rDI)	/* compare first directory entry */

_cmp00:
	PUSHR(rDI)			/* save for later if it matches */
	LWI(bootfile(SB), rSI)
	LWI(Dattr, rCX)
	REP
	CMPSB
	POPR(rDI)
	JEQ _jmp02

	DEC(rBX)
	JEQ _jmp01

	ADDI(Dirsz, rDI)
	JMP _cmp00
_jmp01:
	CALL16(buggery(SB))

_jmp02:
	CLR(rBX)			/* a handy value */
	LW(_rootsize(SB), rAX)		/* calculate and save Xrootsz */
	LWI(Dirsz, rCX)
	MUL(rCX)
	LW(_sectsize(SB), rCX)
	PUSHR(rCX)
	DEC(rCX)
	ADD(rCX, rAX)
	ADC(rBX, rDX)
	POPR(rCX)			/* _sectsize(SB) */
	DIV(rCX)
	PUSHR(rAX)			/* Xrootsz */

	/*
	 * rDI points to the matching directory entry.
	 */
	LXW(Dstart, xDI, rAX)		/* starting sector address */
	DEC(rAX)			/* that's just the way it is */
	DEC(rAX)
	LB(_clustsize(SB), rCL)
	CLRB(rCH)
	MUL(rCX)
	LW(_volid(SB), rCX)		/* Xrootlo */
	ADD(rCX, rAX)
	LW(_volid+2(SB), rCX)		/* Xroothi */
	ADC(rCX, rDX)
	POPR(rCX)			/* Xrootsz */
	ADD(rCX, rAX)
	ADC(rBX, rDX)

	PUSHR(rAX)			/* calculate how many sectors to read */
	PUSHR(rDX)
	LXW(Dlengthlo, xDI, rAX)
	LXW(Dlengthhi, xDI, rDX)
	LW(_sectsize(SB), rCX)
	PUSHR(rCX)
	DEC(rCX)
	ADD(rCX, rAX)
	ADC(rBX, rDX)
	POPR(rCX)			/* _sectsize(SB) */
	DIV(rCX)
	MW(rAX, rCX)
	POPR(rDX)
	POPR(rAX)

	LWI(LOADSEG, rBX)		/* address to load into (seg+offset) */
	MTSR(rBX, rES)			/*  seg */
	LWI(LOADOFF, rBX)		/*  offset */

_readboot:
	CALL16(BIOSread(SB))		/* read the sector */

	LW(_sectsize(SB), rDI)		/* bump addresses/counts */
	ADD(rDI, rBX)
	JCC _incsecno

	MFSR(rES, rDI)			/* next 64KB segment */
	ADDI(0x1000, rDI)
	MTSR(rDI, rES)

_incsecno:
	CLR(rDI)
	INC(rAX)
	ADC(rDI, rDX)
	LOOP _readboot

	LWI(LOADSEG, rDI)		/* set rDS for loaded code */
	MTSR(rDI, rDS)
	FARJUMP16(LOADSEG, LOADOFF)	/* no deposit, no return */

TEXT buggery(SB), $0
	LWI(error(SB), rSI)
	CALL16(BIOSputs(SB))

_wait:
	CLR(rAX)			/* wait for almost any key */
	BIOSCALL(0x16)

_reset:
	CLR(rBX)			/* set ES segment for BIOS area */
	MTSR(rBX, rES)

	LWI(0x0472, rBX)		/* warm-start code address */
	LWI(0x1234, rAX)		/* warm-start code */
	POKEW				/* MOVW	AX, ES:[BX] */

	FARJUMP16(0xFFFF, 0x0000)	/* reset */

/*
 * Read a sector from a disc. On entry:
 *   rDX:rAX	sector number
 *   rES:rBX	buffer address
 * For BIOSCALL(0x13):
 *   rAH	0x02
 *   rAL	number of sectors to read (1)
 *   rCH	low 8 bits of cylinder
 *   rCL	high 2 bits of cylinder (7-6), sector (5-0)
 *   rDH	head
 *   rDL	drive
 *   rES:rBX	buffer address
 */
TEXT BIOSread(SB), $0
	LWI(5, rDI)			/* retry count (ATAPI ZIPs suck) */
_retry:
	PUSHA				/* may be trashed by BIOSCALL */
	PUSHR(rBX)

	LW(_trksize(SB), rBX)
	LW(_nheads(SB), rDI)
	IMUL(rDI, rBX)
	OR(rBX, rBX)
	JZ _ioerror

_okay:
	DIV(rBX)			/* cylinder -> rAX, track,sector -> rDX */

	MW(rAX, rCX)			/* save cylinder */
	ROLI(0x08, rCX)			/* swap rC[HL] */
	SHLBI(0x06, rCL)		/* move high bits up */

	MW(rDX, rAX)
	CLR(rDX)
	LW(_trksize(SB), rBX)

	DIV(rBX)			/* head -> rAX, sector -> rDX */

	INC(rDX)			/* sector numbers are 1-based */
	ANDI(0x003F, rDX)		/* should not be necessary */
	OR(rDX, rCX)

	MW(rAX, rDX)
	SHLI(0x08, rDX)			/* form head */
	LBPB(Xdrive, rDL)		/* form drive */

	POPR(rBX)
	LWI(0x0201, rAX)		/* form command and sectors */
	BIOSCALL(0x13)			/* CF set on failure */
	JCC _BIOSreadret

	POPA
	DEC(rDI)			/* too many retries? */
	JEQ _ioerror

	CALL16(dreset(SB))
	JMP _retry

_ioerror:
	LWI(ioerror(SB), rSI)
	CALL16(BIOSputs(SB))
	JMP _wait

_BIOSreadret:
	POPA
	RET

TEXT dreset(SB), $0
	PUSHA
	CLR(rAX)			/* rAH == 0 == reset disc system */
	LBPB(Xdrive, rDL)
	BIOSCALL(0x13)
	ORB(rAH, rAH)			/* status (0 == success) */
	POPA
	JNE _ioerror
	RET

/*
 * Output a string to the display.
 * String argument is in rSI.
 */
TEXT BIOSputs(SB), $0
	PUSHA
	CLR(rBX)
_BIOSputs:
	LODSB
	ORB(rAL, rAL)
	JEQ _BIOSputsret

	LBI(0x0E, rAH)
	BIOSCALL(0x10)
	JMP _BIOSputs

_BIOSputsret:
	POPA
	RET

/* "Bad format or I/O error\r\nPress almost any key to reboot..."*/
TEXT error(SB), $0
	BYTE $'B'; BYTE $'a'; BYTE $'d'; BYTE $' ';
	BYTE $'f'; BYTE $'o'; BYTE $'r'; BYTE $'m';
	BYTE $'a'; BYTE $'t'; BYTE $' '; BYTE $'o';
	BYTE $'r'; BYTE $' ';
/* "I/O error\r\nPress almost any key to reboot..." */
TEXT ioerror(SB), $0
	BYTE $'I'; BYTE $'/'; BYTE $'O'; BYTE $' ';
	BYTE $'e'; BYTE $'r'; BYTE $'r'; BYTE $'o';
	BYTE $'r'; BYTE $'\r';BYTE $'\n';
	BYTE $'P'; BYTE $'r'; BYTE $'e'; BYTE $'s';
	BYTE $'s'; BYTE $' '; BYTE $'a'; BYTE $' ';
	BYTE $'k'; BYTE $'e'; BYTE $'y';
	BYTE $' '; BYTE $'t'; BYTE $'o'; BYTE $' ';
	BYTE $'r'; BYTE $'e'; BYTE $'b'; BYTE $'o';
	BYTE $'o'; BYTE $'t';
	BYTE $'.'; BYTE $'.'; BYTE $'.';
	BYTE $'\z';

#ifdef USEBCOM
/* "B       COM" */
TEXT bootfile(SB), $0
	BYTE $'B'; BYTE $' '; BYTE $' '; BYTE $' ';
	BYTE $' '; BYTE $' '; BYTE $' '; BYTE $' ';
	BYTE $'C'; BYTE $'O'; BYTE $'M';
	BYTE $'\z';
#else
/* "9LOAD      " */
TEXT bootfile(SB), $0
	BYTE $'9'; BYTE $'L'; BYTE $'O'; BYTE $'A';
	BYTE $'D'; BYTE $' '; BYTE $' '; BYTE $' ';
	BYTE $' '; BYTE $' '; BYTE $' ';
	BYTE $'\z';
#endif /* USEBCOM */

/* "PBS..." */
TEXT confidence(SB), $0
	BYTE $'P'; BYTE $'B'; BYTE $'S'; BYTE $'1'; 
	BYTE $'.'; BYTE $'.'; BYTE $'.';
	BYTE $'\z';
