;; This is a PC/AT style bootstrap program for sector #0 of discs partitioned
;; with the EFI partition table.  It is the PC/AT style bootstrap program
;; that is written to EFI partitioned discs by the NEWMBR command within the
;; DASDPART utility from the TAU system utilities.
;;
;; Unlike older bootstrap programs, such as the one supplied by the NEWMBR
;; command from the OS/2 Command Line Utilities, this program uses INT 13h
;; extensions unconditionally, without checking for their presence.
;;
;; Copyright 2011 Jonathan de Boyne Pollard.  "Moral" rights asserted.
;;
;; Permission is hereby granted to copy, modify, and redistribute this code
;; as long as the author is not held responsible for any consequences of its
;; possession or use, as long as this permission notice is retained, and as
;; long as you agree that it comes with no guarantee.

		ASSUME	CS:EFI_BOOT_TEXT, DS:EFI_BOOT_TEXT

;;
;;	This code must not be combined with any other segment.	It must be
;;	located at offset 0100H.  Combining would break that.
;;
EFI_BOOT_TEXT_GROUP GROUP EFI_BOOT_TEXT

;;
;;	The structure of a partition table entry
;;
EFITableHeader				STRUC
EFITableHeaderSig			dq ?
EFITableHeaderRevision		dd ?
EFITableHeaderHeaderSize	dd ?
EFITableHeaderHeaderCRC32	dd ?
							dd ?
EFITableHeaderMyLBA 		dq ?
EFITableHeaderAltLBA		dq ?
EFITableHeaderFirstLBA		dq ?
EFITableHeaderLastLBA		dq ?
EFITableHeaderDiscGUID		db 16 dup (?)
EFITableHeaderTableLBA		dq ?
EFITableHeaderEntries		dd ?
EFITableHeaderEntrySize 	dd ?
EFITableHeaderTableCRC32	dd ?
EFITableHeader				ENDS

EFITableEntry				STRUC
EFITableEntryTypeGUID		db 16 dup (?)
EFITableEntryPartitionGUID	db 16 dup (?)
EFITableEntryStartLBA		dq ?
EFITableEntryEndLBA 		dq ?
EFITableEntryAttributes 	dq ?
EFITableEntry				ENDS

EFI_BOOT_TEXT			SEGMENT PUBLIC BYTE USE16 'CODE'

;;	************************************************************************
;;
;;	Uninitialized variables
;;
;;	************************************************************************

;;
;;	These must come before the code.  WASM crashes, otherwise.	Since they
;;	are uninitialized, we can put them in an area that is logically outside
;;	of the boot sector, giving us a few precious extra bytes to play with
;;	within the boot sector.  None of these variables are used after the
;;	VBR is given control.
;;
BSS:

;;
;;	An LBA control block
;;
LBAPacketSize		dw	?
LBABlocks			dw	?
LBABufferOff		dw	?
LBABufferSeg		dw	?
LBABlockLo			dd	?
LBABlockHi			dd	?
LBAEnd				EQU $

DriveNumber 		db	00h
PNPOff				dw	0000h
PNPSeg				dw	0000h

;;
;;	This, and subsequent, padding are not required to have any particular
;;	values.  We choose this so that there is contrast between the actual
;;	code/data and the padding in a hexadecimal viewer.
;;
					db 0100h - ($ - BSS) DUP (0a5h)

					ORG 0100h

;;
;;	The stack resides in the area immediately preceding the boot sector.
;;
StackTop			EQU $

;;	************************************************************************
;;
;;	Start of the MBR bootstrap program
;;
;;	************************************************************************

;;
;;	Entry :
;;		  DL	  = Firmware drive number
;;		  ES:DI   = Pointer to firmware's '$PNP' Plug-and-Play record
;;		  CS:IP   = 0000:7C00
;;		  FS	  = 0000
;;	Uses  : All.
;;	Exit  : Loads and jumps to a VBR
;;	Errors: Terminates by calling INT 18h if anything goes wrong.
;;

					PUBLIC BeginEFIMasterBootRecord
BeginEFIMasterBootRecord	PROC

;;	************************************************************************
;;
;;	Start of the main boot code
;;
;;	************************************************************************

start:
					mov ax, 07B0h
					mov ds, ax

					mov [ds:DriveNumber], dl
					mov [ds:PNPOff], di
					mov [ds:PNPSeg], es

					mov ax, 07D0h
					mov es, ax

;;	The stack is immediately below the OLD copy of the program at 0x07B0:0x0100
					cli
					mov ax, ds
					mov ss, ax
					mov sp, StackTop
					sti

;;	_fmemcpy(0x07D0:0x0100, 0x07B0:0x0100, 0x0200) ;
					cld
					mov di, 0100h
					mov si, di
					mov cx, 0200h/4
					rep movsd

;;	Jump to the following label in the copy just made
					push es
					push offset start2

					retf

start2:
;;	Read the EFI partition table header to 0x0800:0x0000
					push 0x0800
					pop es
					xor eax, eax
					mov di, ax	;; ES:DI = 0x0800:0x0000
					mov si, ax	;; ES:SI = 0x0800:0x0000
					mov [ds:LBABlockHi], eax
					inc eax
					mov [ds:LBABlockLo], eax
					mov cx, ax	;; CX = 0x0001
					call ReadSectors

;;	Load the EFI partition table proper.
					mov ax, word ptr [es:si].EFITableHeaderEntries
					mul word ptr [es:si].EFITableHeaderEntrySize
					shr ax, 9
					shl dx, 7
					mov cx, ax
					or cx, dx		;; CX = entries * entry size / 512
					mov eax, dword ptr [es:si].EFITableHeaderTableLBA+0
					mov [ds:LBABlockLo], eax
					mov eax, dword ptr [es:si].EFITableHeaderTableLBA+4
					mov [ds:LBABlockHi], eax
					mov di, 0x0200	;; ES:DI = 0x0800:0x0200
					mov si, di		;; ES:SI = 0x0800:0x0200
					call ReadSectors

;;	Scan for an active partition.
					mov di, 0x0000	;; ES:DI = 0x0800:0x0000
					mov cx, word ptr [es:di].EFITableHeaderEntries
					mov bx, word ptr [es:di].EFITableHeaderEntrySize

CheckTableEntry:
					mov eax, dword ptr [es:si].EFITableEntryTypeGUID
					or eax, dword ptr [es:si].EFITableEntryTypeGUID+4
					or eax, dword ptr [es:si].EFITableEntryTypeGUID+8
					or eax, dword ptr [es:si].EFITableEntryTypeGUID+12
					jz UnusedEntry
					mov ax, word ptr [es:si].EFITableEntryAttributes
					test ax, 0x0004 ;; defined by the UEFI specification 2.3.1
					jnz FoundActivePartition
UnusedEntry:
					add si, bx		;; ES:SI += sizeof entry
					loop CheckTableEntry
					mov si, offset NoActivePartitionMsg
					jmp short DisplayErrorAndReboot
FoundActivePartition:

;;	Load and execute the chosen VBR at 0x07B0:0x0100
					mov eax, dword ptr [es:si].EFITableEntryStartLBA+0
					mov [ds:LBABlockLo], eax
					mov eax, dword ptr [es:si].EFITableEntryStartLBA+4
					mov [ds:LBABlockHi], eax
					mov cx, 1
					push 0x07B0
					pop es
					mov di, 0x0100
					call ReadSectors

					mov ax, word ptr [es:di + 0x1FE]
					cmp ax, 0xAA55
					jz IsBootSector
					mov si, offset NotBootSectorMsg
					jmp short DisplayErrorAndReboot
IsBootSector:

					mov dl, [ds:DriveNumber]
					push es
					push di
					les di, [ds:PNPOff]

					retf

BeginEFIMasterBootRecord			ENDP

;;	************************************************************************
;;
;;	Read blocks into a given buffer (LBA version)
;;
;;	************************************************************************

;;	Entry:
;;		ES:DI	= Buffer address
;;		CX		= sector count
;;	Modifies: AX, BX, CX, DX

ReadSectors 		PROC

					push es
					push si
					push di

					mov [ds:LBABufferOff], di	;; Buffer address
					mov [ds:LBABufferSeg], es	;; """"""""""""""
					mov [ds:LBABlocks], cx		;; Block count
					mov bx, LBAEnd - LBAPacketSize
					mov [ds:LBAPacketSize], bx	;; Packet size
					mov si, offset LBAPacketSize
					mov ah, 42h
					mov dl, [ds:DriveNumber]
					int 13h

					pop di
					pop si
					pop es
					jc ReadSectorError

					ret

ReadSectors 		ENDP

;;	************************************************************************
;;
;;	Failure at any stage prints a message and reboots.
;;
;;	************************************************************************


ReadSectorError:	mov si, offset ReadErrorMsg

DisplayErrorAndReboot:
					call DisplayMessage
					mov si, offset RebootPrompt
					call DisplayMessage
Reboot:
					mov ah, 10h
					int 16h 			;; read keyboard
					int 18h 			;; Next boot device, or ROM BASIC
					int 19h 			;; Just in case it returns

DisplayMessageLoop	PROC
					call DisplayChar
DisplayMessage:
					lodsb
					or al, al
					jnz DisplayMessageLoop
					ret
DisplayMessageLoop	ENDP

;Dot:				mov al, 0x2e

DisplayChar 		PROC
					pusha
					mov ah, 0Eh
					mov bx, 0007h
					int 10h
					popa
					ret
DisplayChar 		ENDP

;;	************************************************************************
;;
;;	The messages
;;
;;	************************************************************************

;;	These are the standard IBM OS/2 messages.
NoActivePartitionMsg	db	'SYS1462',0
ReadErrorMsg			db	'SYS1463',0
NotBootSectorMsg		db	'SYS1464',0

RebootPrompt			db	', press a key!',0Dh,0Ah,0

;;	************************************************************************
;;
;;	Compatibility data structures
;;
;;	************************************************************************

;;
;;	Note that we don't pre-load the dummy old-style partition table with an
;;	0EEh entry.  This is the part of the MBR template that is not used.
;;	Programs must substitute for this part of the template either what was
;;	already in sector #0 or some entries that they calculate themselves.
;;

					db (018Ah - ($ - BeginEFIMasterBootRecord)) DUP (0A5h)

;;	A dummy old style partition table
					ORG 028Ah
DummyOldStyleBootManagerNames:
					db 00
					db 8 dup (' ')
					db 00
					db 8 dup (' ')
					db 00
					db 8 dup (' ')
					db 00
					db 8 dup (' ')

					db (01B8h - ($ - BeginEFIMasterBootRecord)) DUP (0A5h)

;;	EFI 32-bit disc signature
					ORG 02B8h
EFISignature:		dd	0

					db (01BEh - ($ - BeginEFIMasterBootRecord)) DUP (0A5h)

;;	A dummy old style partition table
					ORG 02BEh
DummyOldStylePartitionTable:
					db 7Fh
					db 0Fh dup (0FFh)
					db 7Fh
					db 0Fh dup (0FFh)
					db 7Fh
					db 0Fh dup (0FFh)
					db 7Fh
					db 0Fh dup (0FFh)

;;	************************************************************************
;;
;;	Boot record signature for the firmware
;;
;;	************************************************************************

					db (01FEh - ($ - BeginEFIMasterBootRecord)) DUP (0A5h)

					ORG 02FEh

Signature			dw	0AA55h

					PUBLIC EndEFIMasterBootRecord
EndEFIMasterBootRecord LABEL BYTE

EFI_BOOT_TEXT		ENDS

					END
