/*	$NetBSD: gpt.S,v 1.3 2018/05/19 18:19:37 jakllsch Exp $ */

/*
 * Copyright (c) 2009 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to the NetBSD Foundation
 * by Mike M. Volokhov, based on the mbr.S code by Wolfgang Solfrank,
 * Frank van der Linden, and David Laight.
 * Development of this software was supported by the
 * Google Summer of Code program.
 * The GSoC project was mentored by Allen Briggs and Joerg Sonnenberger.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * x86 PC BIOS master boot code - GUID partition table format
 */

/* Compile options:
 * COM_PORT	- do serial io to specified port number
 *		  0..3 => bios port, otherwise actual io_addr
 * COM_BAUD	- initialise serial port baud rate
 *
 * NO_LBA_CHECK	- no check if bios supports LBA reads
 * NO_CRC_CHECK	- disable crc checks for GPT
 * NO_BANNER	- do not output title line 'banner'
 */

#ifdef COM_PORT
#if COM_PORT < 4
/* The first 4 items in the 40:xx segment are the serial port base addresses */
#define COM_PORT_VAL (0x400 + (COM_PORT * 2))
#else
#define COM_PORT_VAL $COM_PORT
#endif

#if !defined(COM_FREQ)
#define COM_FREQ 1843200
#endif
#endif

#include <machine/asm.h>
#include <sys/bootblock.h>

#define BOOTADDR	0x7c00
#define GPTBUFADDR	0xa000		/* GPT buffer (both header & array) */

/*
 * GPT header structure, offsets
 */
#define GPTHDR_SIG_O	0		/* header signature, 8 b */
#define GPTHDR_REV_O	8		/* GPT revision, 4 b */
#define GPTHDR_SIZE_O	12		/* header size, 4 b */
#define GPTHDR_CRC_O	16		/* header CRC32, 4 b */
#define GPTHDR_RSVD_O	20		/* reserved, 4 b */
#define GPTHDR_MYLBA_O	24		/* this header LBA, 8 b */
#define GPTHDR_ALTLBA_O	32		/* alternate header LBA, 8 b */
#define GPTHDR_SLBA_O	40		/* first usable LBA, 8 b */
#define GPTHDR_ELBA_O	48		/* last usable LBA, 8 b */
#define GPTHDR_GUID_O	56		/* disk GUID, 16 b */
#define GPTHDR_PTNLBA_O	72		/* ptn. entry LBA, 8 b */
#define GPTHDR_PTNN_O	80		/* number of ptns, 4 b */
#define GPTHDR_PTNSZ_O	84		/* partition size, 4 b */
#define GPTHDR_PTNCRC_O	88		/* ptn. array CRC32, 4 b */

/*
 * GPT partition entry structure, offsets
 */
#define GPTPTN_TYPE_O	0		/* ptn. type GUID, 16 b */
#define GPTPTN_GUID_O	16		/* ptn. unique GUID, 16 b */
#define GPTPTN_SLBA_O	32		/* ptn. starting LBA, 8 b */
#define GPTPTN_ELBA_O	40		/* ptn. ending LBA, 8 b */
#define GPTPTN_ATTR_O	48		/* ptn. attributes, 8 b */
#define GPTPTN_NAME_O	56		/* ptn. name, UTF-16, 72 b */

/*
 * Default values of generic disk partitioning
 */
#ifndef SECTOR_SIZE
#define SECTOR_SIZE		512		/* 8192 bytes max */
#endif
#define GPTHDR_BLKNO		1
#define GPTHDR_SIZE		92		/* size of GPT header */
#define GPTHDR_LBASZ		1		/* default LBAs for header */
#define GPTHDR_PTNSZ		128		/* size of a partition entry */
#define GPTHDR_PTNN_MIN		128		/* minimum number of ptns */
#define GPTPTN_SIZE		(GPTHDR_PTNSZ * GPTHDR_PTNN_MIN)
#if GPTPTN_SIZE % SECTOR_SIZE == 0
#define GPTPTN_LBASZ		(GPTPTN_SIZE / SECTOR_SIZE)
#else
#define GPTPTN_LBASZ		(GPTPTN_SIZE / SECTOR_SIZE + 1)
#endif
#define GPT_LBASZ		(GPTHDR_LBASZ + GPTPTN_LBASZ)

/*
 * Minimum and maximum drive number that is considered to be valid.
 */
#define MINDRV		0x80
#define MAXDRV		0x8f

/*
 * Error codes. Done this way to save space.
 */
#define ERR_INVPART	'P'		/* Invalid partition table */
#define ERR_READ	'R'		/* Read error */
#define ERR_NOOS	'S'		/* Magic no. check failed for part. */
#define ERR_NO_LBA	'L'		/* Sector above chs limit */

#define set_err(err)	movb	$err, %al

	.text
	.code16
/*
 * Move ourselves out of the way first.
 * (to the address we are linked at)
 * and zero our bss
 */
ENTRY(start)
	xor	%ax, %ax
	mov	%ax, %ss
	movw	$BOOTADDR, %sp
	mov	%ax, %es
	mov	%ax, %ds
	movw	$mbr, %di
	mov	$BOOTADDR + (mbr - start), %si
	push	%ax			/* zero for %cs of lret */
	push	%di
	movw	$(bss_start - mbr), %cx
	rep
	movsb				/* relocate code (zero %cx on exit) */
	mov	$(bss_end - bss_start + 511)/512, %ch
	rep
	stosw				/* zero bss */
	lret				/* Ensures %cs == 0 */

/*
 * Sanity check the drive number passed by the BIOS. Some BIOSs may not
 * do this and pass garbage.
 */
mbr:
	cmpb	$MAXDRV, %dl		/* relies on MINDRV being 0x80 */
	jle	1f
	movb	$MINDRV, %dl		/* garbage in, boot disk 0 */
1:
	push	%dx			/* save drive number */
	push	%dx			/* twice - for err_msg loop */

#if defined(COM_PORT) && defined(COM_BAUD)
	mov	$com_args, %si
	mov	$num_com_args, %cl	/* %ch is zero from above */
	mov	COM_PORT_VAL, %dx
1:	lodsw
	add	%ah, %dl
	outb	%dx
	loop	1b
#endif

#ifndef NO_BANNER
	mov	$banner, %si
	call	message
#endif

/*
 * Read and validate GUID partition tables
 *
 * Register use:
 * %ax		temp
 * %bx		temp
 * %cx		counters (%ch was preset to zero via %cx before)
 * %dx		disk pointer (inherited from BIOS or the check above)
 * %bp		GPT header pointer
 */
#ifndef NO_LBA_CHECK
/*
 * Determine whether we have int13-extensions, by calling int 13, function 41.
 * Check for the magic number returned, and the disk packet capability.
 * On entry %dx should contain BIOS disk number.
 */
lba_check:
	movw	$0x55aa, %bx
	movb	$0x41, %ah
	int	$0x13
	set_err(ERR_NO_LBA)
	jc	1f			/* no int13 extensions */
	cmpw	$0xaa55, %bx
	jnz	1f
	testb	$1, %cl
	jnz	gpt
1:	jmp	err_msg
#endif

gpt:
	/*
	 * Read GPT header
	 */
	movw	$GPTBUFADDR, %bp	/* start from primary GPT layout */
read_gpt:
	call	do_lba_read		/* read GPT header */
	jc	try_gpt2

	/*
	 * Verify GPT header
	 */
	movw	$efihdr_sign, %si	/* verify GPT signature... */
	movw	%bp, %di		/* ptn signature is at offset 0 */
	movb	$sizeof_efihdr_sign, %cl /* signature length */
	repe
	cmpsb				/* compare */
	jne	try_gpt2		/* if not equal - try GPT2 */

#ifndef NO_CRC_CHECK
	mov	%bp, %si		/* verify CRC32 of the header... */
	mov	$GPTHDR_SIZE, %di	/* header boundary */
	add	%bp, %di
	xor	%eax, %eax
	xchgl	%eax, GPTHDR_CRC_O(%bp)	/* save and reset header CRC */
	call	crc32
	cmp	%eax, %ebx		/* is CRC correct? */
	jne	try_gpt2
#endif

#if 0	/* XXX: weak check - safely disabled due to space constraints */
	movw	$lba_sector, %si	/* verify GPT location... */
	mov	$GPTHDR_MYLBA_O, %di
	add	%bp, %di
	movb	$8, %cl			/* LBA size */
	repe
	cmpsb				/* compare */
	jne	try_gpt2		/* if not equal - try GPT2 */
#endif

	/*
	 * All header checks passed - now verify GPT partitions array
	 */
#ifndef NO_CRC_CHECK
	movl	GPTHDR_PTNCRC_O(%bp), %eax	/* original array checksum */
	push	%bp			/* save header pointer for try_gpt2 */
#endif

	/*
	 * point %bp to GPT partitions array location
	 */
	cmp	$GPTBUFADDR, %bp
	je	1f
	mov	$GPTBUFADDR, %bp
	jmp	2f
1:	mov	$GPTBUFADDR + GPTPTN_LBASZ * SECTOR_SIZE, %bp
2:

#ifndef NO_CRC_CHECK
	mov	%bp, %si		/* array location for CRC32 check */
	mov	$GPTPTN_SIZE, %di	/* array boundary */
	add	%bp, %di
	call	crc32
	cmp	%eax, %ebx		/* is CRC correct? */
	jne	1f			/* if no - try GPT2 */
	pop	%ax			/* restore stack consistency */
#endif
	jmp	gpt_parse

#ifndef NO_CRC_CHECK
1:	pop	%bp			/* restore after unsucc. array check */
#endif
try_gpt2:
	cmp	$GPTBUFADDR, %bp	/* is this GPT1? */
	set_err(ERR_INVPART)		
	jne	err_msg			/* if no - we just tried GPT2. Stop. */

	mov	%bp, %si		/* use [%bp] as tmp buffer */
	movb	$0x1a, (%si)		/* init buffer size (per v.1.0) */
	movb	$0x48, %ah		/* request extended LBA status */
	int	$0x13			/* ... to get GPT2 location */
	set_err(ERR_NO_LBA)
	jc	err_msg			/* on error - stop */
#define LBA_DKINFO_OFFSET 16		/* interested offset in out buffer */
	addw	$LBA_DKINFO_OFFSET, %si	/* ... contains number of disk LBAs */
#undef LBA_DKINFO_OFFSET
	movw	$lba_sector, %di
	movb	$8, %cl			/* LBA size */
	rep
	movsb				/* do get */
	subl	$GPT_LBASZ, lba_sector	/* calculate location of GPT2 */
	sbbl	$0, lba_sector + 4	/* 64-bit LBA correction */

	movw	$GPTBUFADDR + GPTPTN_LBASZ * SECTOR_SIZE, %bp
					/* the GPT2 header location */
	jmp	read_gpt		/* try once again */

/*
 * GPT header validation done.
 * Now parse GPT partitions and try to boot from appropriate.
 * Register use:
 * %bx		partition counter
 * %bp		partition entry pointer (already initialized on entry)
 * %di		partition entry moving pointer
 * %si		variables pointer
 * %cx		counter
 */

gpt_parse:
	movw	$BOOTADDR, lba_rbuff	/* from now we will read boot code */
	movb	$1, lba_count		/* read PBR only */
	mov	$GPTHDR_PTNN_MIN, %bx	/* number of GUID partitions to parse */
do_gpt_parse:
	movw	$bootptn_guid, %si	/* lookup the boot partition GUID */
	movb	$0x10, %cl		/* sizeof GUID */
	movw	%bp, %di		/* set pointer to partition entry */
	add	%cx, %di		/* partition GUID at offset 16 */
	repe
	cmpsb				/* do compare */
	jne	try_nextptn		/* doesn't seem appropriate ptn */

	/*
	 * Read partition boot record
	 */
	mov	$GPTPTN_SLBA_O, %si	/* point %si to partition LBA */
	add	%bp, %si
	movw	$lba_sector, %di
	movb	$8, %cl			/* LBA size */
	rep
	movsb				/* set read pointer to LBA of PBR */
	call	do_lba_read		/* read PBR */
	jz	try_nextptn

	/*
	 * Check signature for valid bootcode and try to boot
	 */
	movb	BOOTADDR, %al		/* first byte non-zero */
	testb	%al, %al
	jz	1f
	movw	BOOTADDR + MBR_MAGIC_OFFSET, %ax
1:	cmp	$MBR_MAGIC, %ax
	jne	try_nextptn
do_boot:
	pop	%dx			/* ... %dx - drive # */
	movw	$lba_sector, %sp	/* ... %ecx:%ebx - boot partition LBA */
	pop	%ebx
	pop	%ecx
	movl 	crc32_poly, %eax	/* X86_MBR_GPT_MAGIC */
	jmp	BOOTADDR
					/* THE END */

try_nextptn:
	addw	$GPTHDR_PTNSZ, %bp	/* move to next partition */
	dec	%bx			/* ptncounter-- */
	jnz	do_gpt_parse
	set_err(ERR_NOOS)		/* no bootable partitions were found */
	/* jmp	err_msg */		/* stop */

/* Something went wrong...
 * Output error code,
 * reset disk subsystem - needed after read failure,
 * and wait for user key
 */
err_msg:
	movb	%al, errcod
	movw	$errtxt, %si
	call	message
	pop	%dx			/* drive we errored on */
	xor	%ax,%ax			/* only need %ah = 0 */
	int	$0x13			/* reset disk subsystem */
	int	$0x18			/* BIOS might ask for a key */
					/* press and retry boot seq. */
1:	sti
	hlt
	jmp	1b

/*
 * I hate #including source files, but the stuff below has to be at
 * the correct absolute address.
 * Clearly this could be done with a linker script.
 */

#if defined(COM_PORT) && defined(COM_BAUD)
message:
	pusha
message_1:
	lodsb
	test	%al, %al
	jz	3f
	mov	COM_PORT_VAL, %dx
	outb	%al, %dx
	add	$5, %dl
2:	inb	%dx
	test	$0x40, %al
	jz	2b
	jmp	message_1
3:	popa
	ret
#else
#include <message.S>
#endif

#if 0
#include <dump_eax.S>
#endif

#ifndef NO_CRC_CHECK
/*
 * The CRC32 calculation
 *
 * %si		address of block to hash
 * %di		stop address
 * %ax		scratch (but restored after exit)
 * %dx		scratch (but restored after exit)
 * %cx		counter
 * %ebx		crc (returned)
 */
crc32:
	orl	 $-1, %ebx		/* init value */
1:
	xorb	(%si), %bl		/* xoring next message byte with previous result */
	inc	%si
	movb	$8, %cl			/* set bit counter */
2:
	shrl	$1, %ebx
	jnc	3f
crc32_poly = . + 3			/* gross, but saves a few bytes */
	xorl	$0xedb88320, %ebx	/* EFI CRC32 Polynomial */
3:
	loop	2b			/* loop over bits */
	cmp	%di, %si		/* do we reached end of message? */
	jne	1b
	notl	%ebx			/* result correction */

	ret
#endif

do_lba_read:
	movw	$lba_dap, %si
	movb	$0x42, %ah
	int	$0x13			/* read */
	ret

/*
 * Data definition block
 */

errtxt: .ascii	"Error "		/* runs into crlf if errcod set */
errcod: .byte	0
crlf:	.asciz	"\r\n"

#ifndef NO_BANNER
banner:	.asciz	"NetBSD GPT\r\n"
#endif

#if defined(COM_PORT) && defined(COM_BAUD)
#define COM_DIVISOR (((COM_FREQ / COM_BAUD) + 8) / 16)
com_args:
	.byte	0x80			/* divisor latch enable */
	.byte	+3			/* io_port + 3 */
	.byte	COM_DIVISOR & 0xff
	.byte	-3			/* io_port */
	.byte	COM_DIVISOR >> 8	/* high baud */
	.byte	+1			/* io_port + 1 */
	.byte	0x03			/* 8 bit no parity */
	.byte	+2			/* io_port + 3 */
num_com_args = (. - com_args)/2
#endif

/*
 * Control block for int-13 LBA read - Disk Address Packet
 */
lba_dap:
	.byte	0x10			/* control block length */
	.byte	0			/* reserved */
lba_count:
	.word	GPT_LBASZ		/* sector count */
lba_rbuff:
	.word	GPTBUFADDR		/* offset in segment */
	.word	0			/* segment */
lba_sector:
	.quad	GPTHDR_BLKNO		/* sector # goes here... */

efihdr_sign:
	.ascii	"EFI PART"		/* GPT header signature */
	.long	0x00010000		/* GPT header revision */
sizeof_efihdr_sign = . - efihdr_sign

/*
 * Stuff from here on is overwritten by gpt/fdisk - the offset must not change
 *
 * Get amount of space to makefile can report it.
 * (Unfortunately I can't seem to get the value reported when it is -ve)
 */
mbr_space = bootptn_guid - .

/*
 * GUID of the bootable partition. Patchable area.
 * Default GUID used by installer for safety checks.
 */
	. = start + MBR_GPT_GUID_OFFSET
bootptn_guid:
	/* MBR_GPT_GUID_DEFAULT */
	.long 0xeee69d04
	.word 0x02f4
	.word 0x11e0
	.byte 0x8f,0x5d
	.byte 0x00,0xe0,0x81,0x52,0x9a,0x6b

/* space for mbr_dsn */
	. = start + MBR_DSN_OFFSET
	.long	0

/* mbr_bootsel_magic */
	. = start + MBR_BS_MAGIC_OFFSET
	.word	0

/* mbr partition table */
	. = start + MBR_PART_OFFSET
parttab:
	.fill	0x40, 0x01, 0x00

	. = start + MBR_MAGIC_OFFSET
	.word	MBR_MAGIC

/* zeroed data space */
bss_off = 0
bss_start = .
#define BSS(name, size) name = bss_start + bss_off; bss_off = bss_off + size
	BSS(dump_eax_buff, 16)
	BSS(bss_end, 0)
