root/sys/arch/i386/stand/mbr/gptmbr.S
/* -----------------------------------------------------------------------
 *
 *   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: