/* Released under GPL version 2
**
** How to read/update MultiBoot MBR:
** 1. Check that first 16 bytes are "xxxMultiBoot 0.4"
** 2. Update fields at 0x13..0x20 as you need.
** 3. offsets[0] (i.e. 2-byte field at 1a) holds a pointer to
**    the text label of 1st partition, do not change it,
**    but use it's value as a starting address of new labels.
** 4. offsets[1..3] can (should) be modified
** 5. Labels should be written as null-terminated strings
**    in the offsets[0]...0x1b5 range.
** 6. NB: you need to adjust offsets[i] values by 0x600
**    (i.e. offsets[i]==0x740 means "offset 0x140 from the start of MBR")
** 7. Do not touch stuff at and after 0x1b6 - it may be used
**    Windows/whatever
**
** A few special cases:
** if default_char doesn't match any of chars[], there is no default
**	and also there is no automatic boot timeout.
** if char[i] = '\0', the partition is unselectable.
** delay = 0 suppresses any output, and boots default partition
** with no delay; but if default_char doesn't match any of chars[], then
**	user will need BLINDLY type the desired partition char
**	in order to boot.
*/
		.code16
		.text

std_boot_addr = 0x7c00
part_tbl = 0x7c00 + 0x200 - 2 - 4*16

		.org	0x600
start0:
		cli	# lilo 22.5.8: "NT 4 blows up if this is missing"
		jmp	start1
banner:		.ascii	"MultiBoot 0.4"

# The above is exactly 16 bytes and may be used as a signature
# for tools trying to directly manipulate the MBR

crlf:		.asciz	"\r\n"		# 10
default_char:	.byte	'1'		# 13
chars:		.byte	'1','2','3','4'	# 14
delay:		.word 	5*18		# 18
offsets:	.word	part1_msg	# 1a
		.word	part2_msg	# 1c
		.word	part3_msg	# 1e
		.word	part4_msg	# 20

// The disk I/O packet
edd_packet:	.word   16		# size of packet
		.word   1		# count of sectors to transfer
addr:		.word   std_boot_addr	# address offset to transfer to
// space-saving trick, see (*) below
//		.word   0		# address segment to transfer to
//daddr:	.long   0		# low order disk address
//		.long   0		# high order disk address
daddr = addr + 4

start1:

// Move ourself to 0x600
		mov	$std_boot_addr,%si
		xor	%ax,%ax
		mov	%ax,%es
		mov	%ax,%ds
		mov	%ax,%ss
		mov	%si,%sp
		sti	
		cld	
		mov	$start0,%di
		mov	$0x200/2,%cx
		rep movsw
		jmp	$0,$next
next:

// Zero out the remaining part of disk I/O packet (*)
		mov	$addr+2,%di
		mov	$5,%cl	# %ax is still == 0
		rep stosw	# zero out addr_seg,daddr_lo,daddr_hi

// Autoboot or 'blind selection'?
		mov	delay,%cx
		jcxz	use_default

// Print banner
		mov	$banner,%si
		call	print_crlf

// Print list of partitions
		xor	%bx,%bx
next_part:	mov	chars(%bx),%al
		test	%al,%al
		jz	skip_part
		mov	$delim,%si
		call	putc_and_print
		add	%bx,%bx
		mov	offsets(%bx),%si
		shr	$1,%bx
		call	print_crlf
skip_part:	inc	%bx
		cmp	$4,%bx
		jb	next_part

// 'Choose:'
		mov	$choose,%si	
		call	print

// Calculate timeout deadline
		mov	$0,%ah		# get time
		int	$0x1a
		add	delay,%dx	# cx:dx += delay
		adc	$0,%cx
		mov	%dx,%bx		# si:bx = deadline
		mov	%cx,%si

wait_for_interrupt:
		hlt	
		mov	$0,%ah		# get time
		int	$0x1a
		sub	%bx,%dx		# timed out? (cx:dx - si:bx > 0?)
		sbb	%si,%cx
		jae	use_default	# yes, bail out, use default
		mov	$0x1,%ah	# is a key pressed?
		int	$0x16
		je	wait_for_interrupt	# no, repeat
getchar:
		mov	$0,%ah
		int	$0x16
		cmp	$0xd,%al	# if <enter> - take default
		jne	not_default
use_default:
		mov	default_char,%al
not_default:	mov	$4,%cx		# look up pressed char
		mov	$chars,%di
		repne scasb
		jne	getchar		# not found? - repeat
		sub	$chars+1,%di	# di -= (offset chars+1)

// The partition has been selected in %di
		mov	$crlf,%si	# display the char
		call	putc_and_print
		shl	$4,%di		# di *= 16
		lea	part_tbl(%di),%si # si => part_tbl[i]
		mov	$0x80,%dx
		cmp	%dl,(%si)	# is it already marked bootable?
		je	skip_rewrite

// Update the MBR (write bootable flag to selected partition)
		xor	%ax,%ax
		mov	%al,part_tbl
		mov	%al,part_tbl+0x10
		mov	%al,part_tbl+0x20
		mov	%al,part_tbl+0x30
		mov	%dl,(%si)
		mov	$1,%cx
		mov	$std_boot_addr,%bx
		mov	$0x301,%ax
		int	$0x13
skip_rewrite:

// Ok, read the bootsect and jump to it
		call	disk_read
		cmpw	$0xaa55,std_boot_addr + 0x200 - 2
		jne	missing_os
// By convention, ds:si must point to partition table entry
// which was used for booting
		lea	part_tbl_copy(%di),%si
		mov	%dl,(%si)	# "bootable" marker
		jmp	*%sp		# jmp to 0x7c00

read_error:
		mov	$read_error_msg,%si
		jmp	do_print
missing_os:
		mov	$missing_os_msg,%si
do_print:	call	print
hang:		jmp	hang


/* Disk read subroutine
**
//** %si guaranteed to not be clobbered
*/
disk_read:
		mov	8(%si),%eax	# copy part_start into disk addr
		mov	%eax,daddr

		mov	$0x80,%dl	# hdd0
		mov	$0x55aa,%bx	# magic number
		mov	$0x41,%ah
		int	$0x13
		jc	disk_read_old	# error?
		cmp	$0xaa55,%bx	# is magic number changed?
		jne	disk_read_old
		test	$1,%cl		# are EDD packet calls supported?
		jz	disk_read_old
		//push	%si
		mov	$edd_packet,%si	# ds:si => packet
		mov	$0x42,%ah	# edd packet read
		int	$0x13
		//pop	%si
		jmp	chk_carry

disk_read_old:
		mov	(%si),%dx	# disk,head
		mov	2(%si),%cx	# cyl,sect
		mov	$0x7c00,%bx	# es:bx => buffer
		mov	$0x201,%ax	# read, 1 sect
		int	$0x13
chk_carry:
		jc	read_error
		ret


/* Printing subroutines
*/
putc_and_print:
		mov	$0xe,%ah
		int	$0x10
print:
		lodsb
		test	%al,%al
		jnz	putc_and_print
		ret	
print_crlf:
		call	print
		mov	$crlf,%si
		jmp	print


choose:		.ascii	"\r\nChoose"
delim:		.asciz	": "
read_error_msg:	.asciz	"Read error"
missing_os_msg:	.asciz	"Missing OS"
part1_msg:	.asciz	"Partition 1"
part2_msg:	.asciz	"Partition 2"
part3_msg:	.asciz	"Partition 3"
part4_msg:	.asciz	"Partition 4"

// Pad the .bin to the exact size of MBR code
			//   sector - sig - ptbl - pad - vol - pad
		.org	0x600 + 0x200 - 2 - 4*16 - 2 - 4 - 2
end_mbr_code:

// Volume serial#
/*		.word	?
vol_serial_no:	.long	?
		.word	?
*/

part_tbl_copy = end_mbr_code + 2 + 4 + 2
// Partition table entry
/*		.byte	?	# +0: 0x80/0x00 - bootable/not bootable
		.byte	?	# +1: head (start)
		.word	?	# +2: cyl+sect (start)
		.byte	?	# +4: type
		.byte	?	# +5: head (end)
		.word	?	# +6: cyl+sec (end)
		.dword	?	# +8: offset in sectors
		.dword	?	# +12: size in sectors
				# +16: size of entry is 16 bytes
*/
