/* -----------------------------------------------------------------------
*
*   Copyright 2007-2009 H. Peter Anvin - All Rights Reserved
*   Copyright 2009-2010 Intel Corporation; author: H. Peter Anvin
*
*   Permission is hereby granted, free of charge, to any person
*   obtaining a copy of this software and associated documentation
*   files (the "Software"), to deal in the Software without
*   restriction, including without limitation the rights to use,
*   copy, modify, merge, publish, distribute, sublicense, and/or
*   sell copies of the Software, and to permit persons to whom
*   the Software is furnished to do so, subject to the following
*   conditions:
*
*   The above copyright notice and this permission notice shall
*   be included in all copies or substantial portions of the Software.
*
*   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
*   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
*   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
*   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
*   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
*   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
*   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
*   OTHER DEALINGS IN THE SOFTWARE.
*
* ----------------------------------------------------------------------- */

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

#ifdef CTRL_80
       .macro ADJUST_DRIVE
       testb   $0x04, BIOS_kbdflags
       jz      1f
       movb    $0x80, %dl
1:
       .endm
#elif defined(FORCE_80)
       .macro ADJUST_DRIVE
       movb    $0x80, %dl
       .endm
#else
       .macro ADJUST_DRIVE
       .endm
#endif

       .code16
       .text

       .globl  bootsec
stack           = 0x7c00

/* Partition table header here */
phdr            = stack         /* Above the stack, overwritten by bootsect */
/* Partition table sector here */
/* To handle > 32K we need to play segment tricks... */
psec            = _phdr + 512

/* Where we put DS:SI */
dssi_out        = start + 0x1be

BIOS_kbdflags   = 0x417
BIOS_page       = 0x462

       /* gas/ld has issues with doing this as absolute addresses... */
       .section ".bootsec", "a", @nobits
       .globl  bootsec
bootsec:
       .space  512

       .text
       .globl  start
start:
       cli
       xorw    %ax, %ax
       movw    %ax, %ds
       movw    %ax, %ss
       movw    $stack, %sp
       movw    %sp, %si
       pushw   %es             /* 4(%bp) es:di -> $PnP header */
       pushw   %di             /* 2(%bp) */
       movw    %ax, %es
       sti
       cld

       /* Copy down to 0:0x600 */
       movw    $start, %di
       movw    $(512/2), %cx
       rep; movsw

       ljmpw   $0, $next
next:

       ADJUST_DRIVE
       pushw   %dx             /* 0(%bp) = %dl -> drive number */

       /* Check to see if we have EBIOS */
       pushw   %dx             /* drive number */
       movb    $0x41, %ah      /* %al == 0 already */
       movw    $0x55aa, %bx
       xorw    %cx, %cx
       xorb    %dh, %dh
       stc
       int     $0x13
       jc      1f
       cmpw    $0xaa55, %bx
       jne     1f
       shrw    %cx             /* Bit 0 = fixed disk subset */
       jnc     1f

       /* We have EBIOS; patch in the following code at
          read_sector_cbios: movb $0x42, %ah ;  jmp read_common */
       movl    $0xeb42b4+((read_common-read_sector_cbios-4) << 24), \
               (read_sector_cbios)

1:
       popw    %dx

       /* Get (C)HS geometry */
       movb    $0x08, %ah
       int     $0x13
       andw    $0x3f, %cx      /* Sector count */
       movw    %sp, %bp        /* %bp -> frame pointer: LEAVE UNCHANGED */
       pushw   %cx             /* -2(%bp) Save sectors on the stack */
       movzbw  %dh, %ax        /* dh = max head */
       incw    %ax             /* From 0-based max to count */
       mulw    %cx             /* Heads*sectors -> sectors per cylinder */

       /* Save sectors/cylinder on the stack */
       pushw   %dx             /* -4(%bp) High word */
       pushw   %ax             /* -6(%bp) Low word */

       /* Load partition table header */
       xorl    %eax,%eax
       cltd
       incw    %ax             /* %edx:%eax = 1 */
       movw    $phdr, %bx
       pushw   %bx             /* -8(%bp) phdr == bootsect */
       call    read_sector

       /* Number of partition sectors */
       /* We assume the partition table is 32K or less, and that
          the sector size is 512. */
       /* Note: phdr == 6(%bp) */
       movw    (80+6)(%bp),%cx         /* NumberOfPartitionEntries */
       movw    (84+6)(%bp),%ax         /* SizeOfPartitionEntry */
       pushw   %ax
       pushw   %cx
       mulw    %cx
       shrw    $9,%ax
       xchgw   %ax,%cx
       incw    %cx

       /* Starting LBA of partition array */
       movl    (72+6)(%bp),%eax
       movl    (76+6)(%bp),%edx

       pushw   %bx
get_ptab:
       call    read_sector
       call    inc64
       loop    get_ptab

       /* Find the boot partition */
       xorw    %si,%si                 /* Nothing found yet */
       popw    %di                     /* Partition table in memory */
       popw    %cx                     /* NumberOfPartitionEntries */
       popw    %ax                     /* SizeOfPartitionEntry */

find_part:
       /* If the PartitionTypeGUID is all zero, it's an empty slot */
       movl      (%di),%edx
       orl      4(%di),%edx
       orl      8(%di),%edx
       orl     12(%di),%edx
       jz      not_this
       testb   $0x04,48(%di)
       jz      not_this
       andw    %si,%si
       jnz     found_multiple
       movw    %di,%si
not_this:
       addw    %ax,%di
       loop    find_part

       andw    %si,%si
       jnz     found_part

missing_os:
       call    error
       .ascii  "Missing OS\r\n"

found_multiple:
       call    error
       .ascii  "Multiple active partitions\r\n"

found_part:
       xchgw   %ax,%cx         /* Set up %cx for rep movsb further down */

       movw    $dssi_out,%di
       pushw   %di

       /* 80 00 00 00 ee 00 00 00
          - bootable partition, type EFI (EE), no CHS information */
       xorl    %eax,%eax
       movb    $0x80,%al
       stosl
       movb    $0xed,%al
       stosl
       movl    32(%si),%eax
       movl    36(%si),%edx
       call    saturate_stosl          /* Partition start */

       movl    40(%si),%eax
       movl    44(%si),%edx
       subl    32(%si),%eax
       sbbl    36(%si),%edx
       call    inc64
       call    saturate_stosl          /* Partition length */

       movzwl  %cx,%eax                /* Length of GPT entry */
       stosl

       rep; movsb                      /* GPT entry follows MBR entry */
       popw    %si

/*
* boot: invoke the actual bootstrap. %ds:%si points to the
* partition information in memory.  The top word on the stack
* is phdr == 0x7c00 == the address of the boot sector.
*/
boot:
       movl    (32+20)(%si),%eax
       movl    (36+20)(%si),%edx
       popw    %bx
       call    read_sector
       cmpw    $0xaa55, -2(%bx)
       jne     missing_os      /* Not a valid boot sector */
       movw    %bp, %sp        /* driveno == bootsec-6 */
       popw    %dx             /* dl -> drive number */
       popw    %di             /* es:di -> $PnP vector */
       popw    %es
       movl    $0x54504721,%eax /* !GPT magic number */
       cli
       jmpw    *%sp            /* %sp == bootsec */

/*
* Store the value in %eax to %di iff %edx == 0, otherwise store -1.
* Returns the value that was actually written in %eax.
*/
saturate_stosl:
       andl    %edx,%edx
       jz 1f
       orl     $-1,%eax
1:      stosl
       ret

/*
* Increment %edx:%eax
*/
inc64:
       addl    $1,%eax
       adcl    $0,%edx
       ret

/*
* read_sector: read a single sector pointed to by %edx:%eax to
* %es:%bx.  CF is set on error.  All registers saved.
*/
read_sector:
       pushal
       pushl   %edx    /* MSW of LBA */
       pushl   %eax    /* LSW of LBA */
       pushw   %es     /* Buffer segment */
       pushw   %bx     /* Buffer offset */
       pushw   $1      /* Sector count */
       pushw   $16     /* Size of packet */
       movw    %sp, %si

       /* This chunk is skipped if we have ebios */
       /* Do not clobber %es:%bx or %edx:%eax before this chunk! */
read_sector_cbios:
       divl    -6(%bp) /* secpercyl */
       shlb    $6, %ah
       movb    %ah, %cl
       movb    %al, %ch
       xchgw   %dx, %ax
       divb    -2(%bp) /* sectors */
       movb    %al, %dh
       orb     %ah, %cl
       incw    %cx     /* Sectors are 1-based */
       movw    $0x0201, %ax

read_common:
       movb    (%bp), %dl /* driveno */
       int     $0x13
       leaw    16(%si), %sp    /* Drop DAPA */
       popal
       jc      disk_error
       addb    $2, %bh         /* bx += 512: point to the next buffer */
       ret

disk_error:
       call    error
       .ascii  "Disk error on boot\r\n"

/*
* Print error messages.  This is invoked with "call", with the
* error message at the return address.
*/
error:
       popw    %si
2:
       lodsb
       movb    $0x0e, %ah
       movb    (BIOS_page), %bh
       movb    $0x07, %bl
       int     $0x10           /* May destroy %bp */
       cmpb    $10, %al        /* Newline? */
       jne     2b

       int     $0x18           /* Boot failure */
die:
       hlt
       jmp     die

mbr_space = end - .
       . = MBR_DSN_OFFSET
end: