root/stand/boot/pc32/boot0/boot0.S
/*
 * Copyright (c) 2003,2004 The DragonFly Project.  All rights reserved.
 * 
 * This code is derived from software contributed to The DragonFly Project
 * by Matthew Dillon <dillon@backplane.com>
 * 
 * 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.
 * 3. Neither the name of The DragonFly Project nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific, prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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
 * COPYRIGHT HOLDERS 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.
 * 
 * Copyright (c) 1998 Robert Nordier
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are freely
 * permitted provided that the above copyright notice and this
 * paragraph and the following disclaimer are duplicated in all
 * such forms.
 *
 * This software is provided "AS IS" and without any express or
 * implied warranties, including, without limitation, the implied
 * warranties of merchantability and fitness for a particular
 * purpose.
 *
 *
 * $FreeBSD: src/sys/boot/i386/boot0/boot0.s,v 1.26 2003/06/01 20:41:04 obrien Exp $
 */

#include "../bootasm.h"

#ifdef SIO
/* ... using a serial console on COM1. */
#endif

                /*
                 * A 512-byte boot manager.
                 */
                .set PRT_OFF,0x1be              # Partition table

                .set TBL0SZ,0x3                 # Table 0 size
                .set TBL1SZ,0xb                 # Table 1 size

                .set MAGIC,0xaa55               # Magic: bootable
                .set B0MAGIC,0xbb66             # Identification

                .set KEY_ENTER,0x1c             # Enter key scan code
                .set KEY_F1,0x3b                # F1 key scan code
                .set KEY_1,0x02                 # #1 key scan code

                .set ASCII_BEL,0x07             # ASCII code for <BEL>
                .set ASCII_CR,0x0D              # ASCII code for <CR>

                /*
                 * Addresses in the sector of embedded data values.  Accessed
                 * with negative offsets from the end of the relocated sector
                 * (%ebp).
                 *
                 * Note that %ebp is the base of our variable space and 
                 * points at the end of the sector (base + 0x200).  The
                 * fake partition and menu option is thus stored in the
                 * memory just after the boot0 sector.
                 */
                .set _NXTDRV,-0x48              # Next drive
                .set _OPT,-0x47                 # Default option
                .set _SETDRV,-0x46              # Drive to force
                .set _FLAGS,-0x45               # Flags
                .set _TICKS,-0x44               # Timeout ticks
                .set _FAKE,0x0                  # Fake partition entry
                .set _MNUOPT,0xc                # Menu options

                .set _SECTOR_FIELD_OFF,-0xe     # Offset to Sector field
                                                # in the fake partition
                .set _DATA_OFF,PRT_OFF+_SECTOR_FIELD_OFF
                                                # Offset to boot0 variables
                                                # from partition table

                .globl start                    # Entry point
                .code16                         # This runs in real mode

                /*
                 * Initialise segments and registers to known values.  
                 * segments start at 0.  The stack is immediately below the
                 * address we were loaded to.
                 */
start:          cld                             # String ops inc
                xorw %ax,%ax                    # Zero
                movw %ax,%es                    # Address
                movw %ax,%ds                    #  data
                movw %ax,%ss                    # Set up
                movw $MEM_BIOS_LADDR,%sp        #  stack
        
                /*
                 * Copy this code to the address it was linked for.
                 * Base address after relocation is - 0x600.
                 */
                movw %sp,%si                    # Source
                movw $start,%di                 # Destination
                movw $0x100,%cx                 # Word count
                rep                             # Relocate
                movsw                           #  code

                /*
                 * Set address for variable space beyond code, and clear it.
                 * Notice that this is also used to point to the values 
                 * embedded in the block, by using negative offsets.
                 */
                movw %di,%bp                    # Address variables
                movb $0x8,%cl                   # Words to clear
                rep                             # Zero
                stosw                           #  them

                /*
                 * Set C:H:S to 0:0:1 and relocate to the new copy of
                 * the code.  Do not make assumptions with regard to
                 * a relative-PC near jump capability.
                 */
                incb _SECTOR_FIELD_OFF(%di)     # Sector number
                pushw $main                     # Jump to relocated code
                retw

main:
#if defined(SIO) && COMSPEED != 0
                /*
                 * Initialize the serial port.
                 * bioscom preserves the drive number in DX.
                 */
                movw $COMSPEED,%ax              # defined by Makefile
                callw bioscom
#endif
                /*
                 * Check what flags were loaded with us, specifically, Use a
                 * predefined Drive.  If what the bios gives us is bad, use
                 * the '0' in the block instead, as well.
                 */
                testb $0x20,_FLAGS(%bp)         # Set number drive?
                jnz main.1                      # Yes
                testb %dl,%dl                   # Drive number valid?
                js main.2                       # Possibly (0x80 set)

main.1:         movb _SETDRV(%bp),%dl           # Drive number to use

                /*
                 * Whatever we decided to use, now store it into the fake
                 * partition entry that lives in the data space above us.
                 */
main.2:         movb %dl,_FAKE(%bp)             # Save drive number
                callw putn                      # To new line
                pushw %dx                       # Save drive number

                /*
                 * Start out with a pointer to the 4th byte of the first
                 * table entry so that after 4 iterations it's beyond the
                 * end of the sector.  and beyond a 256 byte boundary and
                 * has overflowed 8 bits (see next comment).  (remember
                 * that the table starts 2 bytes earlier than you would
                 * expect as the bootable flag is after it in the block)
                 */
                movw $(partbl+0x4),%bx          # Partition table (+4 - type)
                xorw %dx,%dx                    # Partition entry item number

                /*
                 * Loop around on the partition table, printing values until
                 * we pass a 256 (4x 64 byte partitions) byte boundary. The
                 * end of loop test is at
                 * main.5.
                 */
main.3:         movb %ch,-0x4(%bx)              # Zero active flag (ch == 0)
                btw %dx,_FLAGS(%bp)             # Entry enabled?
                jnc main.5                      # No

                /*
                 * If any of the entries in the table are the same as the
                 * 'type' in the slice table entry, then this is an empty
                 * or non bootable partition. Skip it.
                 */
                movb (%bx),%al                  # Load type
                movw $tables,%di                # Lookup tables
                movb $TBL0SZ,%cl                # Number of entries
                repne                           # Exclude
                scasb                           #  partition?
                je main.5                       # Yes

                /*
                 * Now scan the table of known types
                 */
                movb $TBL1SZ,%cl                # Number of entries
                repne                           # Known
                scasb                           #  type?
                jne main.4                      # No

                /*
                 * If it matches get the matching element in the
                 * next array. if it doesn't, we are already
                 * pointing at its first element which points to a "?".
                 */
                addw $TBL1SZ,%di                # Adjust
main.4:         movb (%di),%cl                  # Partition
                addw %cx,%di                    #  description
                callw putx                      # Display it
main.5:         incw %dx                        # Next item 
                addb $0x10,%bl                  # Next entry
                jnc main.3                      # Till done

                /*
                 * Passed a 256 byte boundary..
                 * table is finished.
                 * Add one to the drive number and check it is valid, 
                 */
                popw %ax                        # Drive number
                subb $0x80-0x1,%al              # Does next
                cmpb BDA_NHRDRV,%al             #  drive exist? (from BIOS?)
                jb main.6                       # Yes

                /*
                 * If not then if there is only one drive,
                 * Don't display drive as an option.
                 */
                decw %ax                        # Already drive 0?
                jz main.7                       # Yes

                /*
                 * If it was illegal or we cycled through them,
                 * then go back to drive 0.
                 */
                xorb %al,%al                    # Drive 0

                /*
                 * Whatever drive we selected, make it an ascii digit and
                 * save it back to the "next drive" location in the loaded
                 * block in case we want to save it for next time.  This also
                 * is part of the printed drive string so add 0x80 to indicate
                 * end of string.
                 */
main.6:         addb $'0'|0x80,%al              # Save next
                movb %al,_NXTDRV(%bp)           #  drive number
                movw $drive,%di                 # Display
                callw putx                      #  item

                /*
                 * Now that we've printed the drive (if we needed to), 
                 * display a prompt.  Get ready for the input by noting the
                 * time.
                 */
main.7:         movw $prompt,%si                # Display
                callw putstr                    #  prompt
                movb _OPT(%bp),%dl              # Display
                decw %si                        #  default
                callw putkey                    #  key
                xorb %ah,%ah                    # BIOS: Get
                int $0x1a                       #  system time
                movw %dx,%di                    # Ticks when
                addw _TICKS(%bp),%di            #  timeout

                /* 
                 * Busy loop, looking for keystrokes but
                 * keeping one eye on the time.
                 */
main.8:
#ifndef SIO
                movb $0x1,%ah                   # BIOS: Check
                int $0x16                       #  for keypress
                jnz main.11                     # Have one
#else  /* SIO */
                movb $0x03,%ah                  # BIOS: Read COM
                call bioscom
                testb $0x01,%ah                 # Check line status
                jnz main.11                     # (bit 1 indicates input)
#endif /* SIO */
                xorb %ah,%ah                    # BIOS: Get
                int $0x1a                       #  system time
                cmpw %di,%dx                    # Timeout?
                jb main.8                       # No

                /*
                 * If timed out or defaulting, come here.
                 */
main.9:         movb _OPT(%bp),%al              # Load default
                jmp main.12                     # Join common code

                /*
                 * User's last try was bad, beep in displeasure.
                 * Since nothing was printed, just continue on as if the
                 * user hadn't done anything. This gives the effect of the
                 * user getting a beep for all bad keystrokes but no action
                 * until either the timeout occurs or the user hits a good
                 * key.
                 */
main.10:        movb $ASCII_BEL,%al             # Signal
                callw putchr                    #  error

                /*
                 * Get the keystroke.
                 */
main.11:
#ifndef SIO
                xorb %ah,%ah                    # BIOS: Get
                int $0x16                       #  keypress
                movb %ah,%al                    # Scan code
#else
                movb $0x02,%ah                  # BIOS: Receive
                call bioscom
#endif

                /*
                 * If it's CR act as if timed out.
                 */
#ifndef SIO
                cmpb $KEY_ENTER,%al             # Enter pressed?
#else
                cmpb $ASCII_CR,%al              # Enter pressed?
#endif
                je main.9                       # Yes

                /*
                 * Otherwise check if legal
                 * If not ask again.
                 */
#ifndef SIO
                subb $KEY_F1,%al                # Less F1 scan code
                cmpb $0x4,%al                   # F1..F5?
                jna main.12                     # Yes
                subb $(KEY_1 - KEY_F1),%al      # Less #1 scan code
#else
                subb $'1',%al                   # Less '1' ascii character
#endif
                cmpb $0x4,%al                   # #1..#5?
                ja main.10                      # No

                /*
                 * We have a selection.  But if it's a bad selection go back
                 * to complain.  The bits in MNUOPT were set when the options
                 * were printed.  Anything not printed is not an option.
                 */
main.12:        cbtw                            # Option
                btw %ax,_MNUOPT(%bp)            #  enabled?
                jnc main.10                     # No

                /*
                 * Save the info in the original tables
                 * for rewriting to the disk.
                 */
                movb %al,_OPT(%bp)              # Save option
                lea _FAKE(%bp),%si              # Partition for write
                movb (%si),%dl                  # Drive number
                movw %si,%bx                    # Partition for read
                cmpb $0x4,%al                   # F5 pressed?
                pushf                           # Save
                je main.13                      # Yes
                shlb $0x4,%al                   # Point to
                addw $partbl,%ax                #  selected
                xchgw %bx,%ax                   #  partition
                movb $0x80,(%bx)                # Flag active

                /*
                 * If not asked to do a write-back (flags 0x40) don't do one.
                 */
main.13:        pushw %bx                       # Save
                testb $0x40,_FLAGS(%bp)         # No updates?
                jnz main.14                     # Yes
                movw $start,%bx                 # Data to write
                movb $0x3,%ah                   # Write sector
                callw intx13                    #  to disk
main.14:        popw %si                        # Restore partition
                popf                            # Restore eflags

                /*
                 * If going to next drive, replace drive with selected one.
                 * Remember to un-ascii it. Hey 0x80 is already set, cool!
                 */
                jne main.15                     # If not F5
                movb _NXTDRV(%bp),%dl           # Next drive
                subb $'0',%dl                   #  number

                /* 
                 * load  selected bootsector to the MEM_BIOS_LADDR location
                 * in RAM.  If it fails to read or isn't marked bootable,
                 * treat it as a bad selection.
                 * XXX what does %si carry?
                 */
main.15:        movw $MEM_BIOS_LADDR,%bx        # Address for read
                movb $0x2,%ah                   # Read sector
                callw intx13                    #  from disk
                jc main.10                      # If error
                cmpw $MAGIC,0x1fe(%bx)          # Bootable?
                jne main.10                     # No
                pushw %si                       # Save
                movw $crlf,%si                  # Leave some
                callw puts                      #  space
                popw %si                        # Restore
                jmp *%bx                        # Invoke bootstrap

                /*
                 * Display routines
                 */
putkey:         
#ifndef SIO
                movb $'F',%al                   # Display
                callw putchr                    #  'F'
#endif
                movb $'1',%al                   # Prepare
                addb %dl,%al                    #  digit
                jmp putstr.1                    # Display the rest

                /*
                 * Display the option and note that it is a valid option.
                 * That last point is a bit tricky..
                 */
putx:           btsw %dx,_MNUOPT(%bp)           # Enable menu option
                movw $item,%si                  # Display
                callw putkey                    #  key
                movw %di,%si                    # Display the rest

puts:           callw putstr                    # Display string

putn:           movw $crlf,%si                  # To next line

putstr:         lodsb                           # Get byte
                testb $0x80,%al                 # End of string?
                jnz putstr.2                    # Yes
putstr.1:       callw putchr                    # Display char
                jmp putstr                      # Continue
putstr.2:       andb $~0x80,%al                 # Clear MSB

#ifndef SIO
putchr:         
                pusha                           # Save
                movw $0x7,%bx                   # Page:attribute
                movb $0xe,%ah                   # BIOS: Display
                int $0x10                       #  character
                popa                            # Restore
                retw                            # To caller
#else /* SIO */
putchr:         
                movb $0x01,%ah                  # BIOS: Send
bioscom:
                pusha                           # Save drive number
                xorw %dx,%dx                    # Use COM1
                int $0x14                       #  Character
                popa                            # Restore drive number
                retw                            # To caller
#endif
                

                /*
                 * One-sector disk I/O routine
                 *
                 * Setup for both the packet and non-packet interfaces 
                 * then select one or the other.
                 *
                 * Don't trust the BIOS to keep registers intact across 
                 * the call, use pusha/popa.
                 */
intx13:         movb 0x1(%si),%dh               # (nonpkt) head
                movw 0x2(%si),%cx               # (nonpkt) cylinder:sector
                movb $0x1,%al                   # (nonpkt) Sector count
                pusha                           # save: do not trust the bios
                pushl $0x0                      # (pkt) LBA address
                pushl 0x8(%si)                  # (pkt)
                pushw %es                       # (pkt) xfer buffer address
                pushw %bx                       # (pkt)
                pushw $1                        # (pkt) Block count
                pushw $16                       # (pkt) Packet size
                testb $0x80,_FLAGS(%bp)         # Use packet interface?
                jz intx13.1
                movw %sp,%si                    # Yes, set packet pointer
                decw %ax                        # Verify off
                orb $0x40,%ah                   # Set pkt mode in command
intx13.1:       int $0x13                       # BIOS: Disk I/O
                # WARNING: RETAIN CARRY AFTER BIOS CALL
                movw %sp,%si
                lea 16(%si),%sp                 # cleanup the stack
                popa                            # Restore registers
                retw

                /*
                 * Menu strings
                 */

item:           .ascii "  ";         .byte ' '|0x80
prompt:         .ascii "\nDefault:"; .byte ' '|0x80
crlf:           .ascii "\r";         .byte '\n'|0x80

                /*
                 * Partition type tables
                 */

tables:
                /*
                 * These entries identify invalid or NON BOOT types and
                 * partitions.
                 */
                .byte 0x0, 0x5, 0xf

                /*
                 * These values indicate bootable types we know the names of.
                 */
                .byte 0x1, 0x6, 0x7, 0xc, 0xe, 0x6c, 0x83, 0x9f
                .byte 0xa5, 0xa6, 0xa9

                /*
                 * These are offsets that match the known names above and
                 * point to the strings that will be printed.
                 */
                .byte os_misc-.                 # Unknown
                .byte os_dos-.                  # DOS
                .byte os_dos-.                  # DOS
                .byte os_dos-.                  # Windows
                .byte os_dos-.                  # Windows
                .byte os_dos-.                  # Windows
                .byte os_dfbsd-.                # DragonFly (new)
                .byte os_linux-.                # Linux
                .byte os_bsd-.                  # BSD/OS
                .byte os_dfbsd-.                # DragonFly (old) / FreeBSD
                .byte os_bsd-.                  # OpenBSD
                .byte os_bsd-.                  # NetBSD

                /*
                 * And here are the strings themselves. 0x80 or'd into a
                 * byte indicates the end of the string. (not so great for
                 * Russians but...)
                 */
os_misc:        .ascii "?";    .byte '?'|0x80
os_dos:         .ascii "DO";   .byte 'S'|0x80
os_linux:       .ascii "Linu"; .byte 'x'|0x80
os_dfbsd:       .ascii "DF/F"
os_bsd:         .ascii "BS";   .byte 'D'|0x80

                .org _DATA_OFF,0x90

                .word B0MAGIC                   # Magic number

                /*
                 * These values are sometimes changed before writing back 
                 * to the drive.  Be especially careful that nxtdrv: must
                 * come after drive:, as it is part of the same string.
                 */
drive:          .ascii "Drive "
nxtdrv:         .byte 0x0                       # Next drive number
opt:            .byte 0x0                       # Option
setdrv:         .byte 0x80                      # Drive to force
flags:          .byte FLAGS                     # Flags
ticks:          .word TICKS                     # Delay

                /*
                 * here is the 64 byte partition table that fdisk would 
                 * fiddle with.
                 */
partbl:         .fill 0x40,0x1,0x0              # Partition table
                .word MAGIC                     # Magic number