; Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
;
; @APPLE_LICENSE_HEADER_START@
; 
; Portions Copyright (c) 1999 Apple Computer, Inc.  All Rights
; Reserved.  This file contains Original Code and/or Modifications of
; Original Code as defined in and that are subject to the Apple Public
; Source License Version 1.1 (the "License").  You may not use this file
; except in compliance with the License.  Please obtain a copy of the
; License at http://www.apple.com/publicsource and read it before using
; this file.
; 
; The Original Code and all software distributed under the License are
; distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
; EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
; INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE OR NON- INFRINGEMENT.  Please see the
; License for the specific language governing rights and limitations
; under the License.
; 
; @APPLE_LICENSE_HEADER_END@
;
; Boot Loader: boot0
;
; A small boot sector program written in x86 assembly whose only
; responsibility is to locate the booter partition, load the
; booter into memory, and jump to the booter's entry point.
; The booter partition can be a primary or a logical partition.
; But the booter partition must reside within the 8GB limit
; imposed by CHS addressing + translation.
; 
; This boot loader can be placed at any of the following places:
; * Master Boot Record (MBR)
; * Boot sector of an extended partition
; * Boot sector of a primary partition
; * Boot sector of a logical partition
;
; In order to coexist with a fdisk partition table (64 bytes), and
; leave room for a two byte signature (0xAA55) in the end, boot0 is
; restricted to 446 bytes (512 - 64 - 2). If boot0 did not have to
; live in the MBR, then we would have 510 bytes to play with.
;
; boot0 is always loaded by the BIOS or another first level booter
; to 0:7C00h.
;
; This code is written for the NASM assembler.
;   nasm boot0.s -o boot0

;--------------------------------------------------------------------------
; Constants.

DEBUG           EQU  0                  ; enable debugging output

BOOTSEG         EQU  0x0                ; our sole segment
BOOTSP          EQU  0xFFF0             ; stack pointer
BOOTLOAD        EQU  0x7C00             ; booter load address
BOOTRELOC       EQU  0xE000             ; booter is relocated here
BOOTSIG         EQU  0xAA55             ; booter signature

BOOT2_SIZE      EQU  88                 ; load this many blocks for boot2
BOOT2_ADDR      EQU  0x3000             ; where to load boot2

DRIVE_NUM       EQU  0x80               ; "C" drive
SECTOR_BYTES    EQU  512                ; sector size in bytes

BUF_MBR         EQU  0x1000             ; memory buffer for MBR
BUF_EXT         EQU  0x1200             ; memory buffer for extended partition

TABLE_MAIN      EQU  BUF_MBR + 0x1be    ; location of main partition table
TABLE_EXT       EQU  BUF_EXT + 0x1be    ; location of ext partition table
ENTRY_SIZE      EQU  16                 ; size of each fdisk partition entry
TYPE_BOOT       EQU  0xab               ; partition type we are looking for
TYPE_EXT        EQU  0x05               ; extended partition type
TYPE_EXT_1      EQU  0x0f               ; Windows extended partition
TYPE_EXT_2      EQU  0x85               ; Linux extended partition
EXT_LEVELS_MAX  EQU  128                ; max extended partition levels

; Disk parameters gathered through INT13/F8 call.
;
max_sectors     db   0                  ; number of sectors per track
max_heads       db   0                  ; number of heads

; Parameters to our load function.
;
chs_cx          dw   0                  ; cx register for INT13/F2 call
chs_dx          dw   0                  ; dx register for INT13/F2 call


;--------------------------------------------------------------------------
; Start of text segment.

    SEGMENT .text

    ORG     0xE000              ; must match BOOTRELOC

;--------------------------------------------------------------------------
; Loaded at 0:7c00h.
;
start
    ; Set up the stack to grow down from BOOTSEG:BOOTSP.
    ; Interrupts should be off while the stack is being manipulated.
    ;
    cli                         ; interrupts off
    mov     ax, BOOTSEG         ;
    mov     ss, ax              ; ss <- BOOTSEG
    mov     sp, BOOTSP          ; sp <- BOOTSP
    sti                         ; reenable interrupts

    ; Relocate the booter code from DS:SI to ES:DI,
    ; or from 0:7C00h to BOOTSEG:BOOTRELOC.
    ;
    mov     es, ax              ; es <- BOOTSEG
    xor     ax, ax
    mov     ds, ax              ; ds <- 0
    mov     si, BOOTLOAD        ; si <- BOOTLOAD (source)
    mov     di, BOOTRELOC       ; di <- BOOTRELOC (destination)
    ;
    cld                         ; auto-increment SI and/or DI registers
    mov     cx, 256             ; copy 256 words (512 bytes)
    repnz   movsw               ; repeat string move (word) operation

    ; Code relocated, jump to start_reloc in relocated location.
    ;
    jmp     BOOTSEG:start_reloc

;--------------------------------------------------------------------------
; Start execution from the relocated location.
;
start_reloc
    mov     ax, BOOTSEG
    mov     ds, ax              ; ds <- BOOTSEG

    mov     al, '='             ; indicate execution start
    call    putchar

    ; Get disk parameters (CHS) using INT13/F8 call.
    ;
    mov     dl, DRIVE_NUM       ; boot drive is drive C
    mov     ah, 8               ; Read Disk Driver Parameter function
    int     0x13
    and     cl, 0x3f            ; sectors/track
    mov     [max_sectors], cl
    mov     [max_heads], dh
    jc      error

    mov     al, '>'             ; indicate INT13/F8 success
    call    putchar

    ; Since this code may not always reside in the MBR, we will always
    ; start by loading the MBR to BUF_MBR.
    ;
    mov     WORD [chs_cx], 0x0001       ; cyl = 0, sect = 1
    mov     BYTE [chs_dx + 1], 0        ; head = 0
    xor     cx, cx                      ; skip 0 sectors
    mov     ax, 1                       ; read 1 sector
    mov     bx, BUF_MBR                 ; load buffer
    call    load
    jc      error

    mov     di, TABLE_MAIN              ; argument for find_booter
    cmp     WORD [di + 64], BOOTSIG     ; correct signature found?
    jne     error                       ; Oops! no signature!
    mov     bl, TYPE_BOOT               ; look for this partition type
    mov     bh, 0                       ; initial nesting level is 0
    call    find_booter

error
    mov     si, load_error
    call    message
hang_1
    jmp     hang_1

;--------------------------------------------------------------------------
; Locate the booter partition and load the booter.
;
; Arguments:
;   di - pointer to fdisk partition table.
;   bl - partition type to look for.
;
; The following registers are modified:
;   ax, bh
;
find_booter
    push    cx
    push    si

    mov     si, di              ; si <- pointer to partition table
    mov     cx, 4               ; 4 partition entries per table

find_booter_pri
    ;
    ; Hunt for a fdisk partition type that matches the value in bl.
    ;
%IF DEBUG
    mov     al, bh              ; log partition type seen
    call    putspace
    mov     al, [si + 4]
    call    display_byte
%ENDIF

    cmp     BYTE [si + 4], bl   ; Is this the booter partition?
    je      load_booter         ; yes, load the booter

    add     si, ENTRY_SIZE      ; si <- next partition entry
    loop    find_booter_pri     ; loop while cx is not zero

    ; No primary (or perhaps logical) booter partition found in the
    ; current partition table. Restart and look for extended partitions.
    ;
    mov     si, di              ; si <- pointer to partition table
    mov     cx, 4               ; 4 partition entries per table

find_booter_ext
    ;
    ; Look for extended partition entries in the partition table.
    ;
%IF DEBUG
    mov     al, bh              ; log partition type seen
    call    putspace
    mov     al, 'E'
    call    putchar
    mov     al, [si + 4]
    call    display_byte
%ENDIF

    cmp     BYTE [si + 4], TYPE_EXT     ; Is this an extended partition?
    je      find_booter_ext_2           ; yes, load its partition table

    cmp     BYTE [si + 4], TYPE_EXT_1   ; Is this an extended partition?
    je      find_booter_ext_2           ; yes, load its partition table
	
    cmp     BYTE [si + 4], TYPE_EXT_2   ; Is this an extended partition?
    je      find_booter_ext_2           ; yes, load its partition table

find_booter_ext_1
    ;
    ; si is not pointing to an extended partition entry,
    ; try the next entry in the partition table.
    ;
    add     si, ENTRY_SIZE      ; si <- next partition entry
    loop    find_booter_ext     ; loop while cx is not zero

    jmp     find_booter_end     ; give up

find_booter_ext_2
    cmp     bh, EXT_LEVELS_MAX
    ja      find_booter_end     ; in too deep!

    inc     bh                  ; increment nesting level counter

    ; Prepare the arguments for the load function call to
    ; load the extended partition table into memory.
    ; Note that si points to the extended partition entry.
    ;
    mov     ax, [si]            ; DH/DL
    mov     [chs_dx], ax
    mov     ax, [si + 2]        ; CH/CL
    mov     [chs_cx], ax
    pusha
    xor     cx, cx              ; skip 0 sectors
    mov     ax, 1               ; read 1 sector
    mov     bx, BUF_EXT         ; load to BUF_EXT
    call    load
    popa

    jc      find_booter_ext_3   ; bail out if load failed

    mov     di, TABLE_EXT       ; di <- pointer to new partition table
    cmp     WORD [di + 64], BOOTSIG
    jne     find_booter_ext_3   ; OhOh! no signature!

    call    find_booter         ; recursion...

find_booter_ext_3
    dec     bh                  ; decrement nesting level counter

    ; If we got here, then we know there isn't a booter
    ; partition linked from this partition entry.

    test    bh, bh              ; if we are at level 0, then
    jz      find_booter_ext_1   ; look for next extended partition entry

find_booter_end
    pop     si
    pop     cx
    ret

;--------------------------------------------------------------------------
; Yeah! Found the booter partition. The first sector in this partition
; is reserved for the boot sector code (us). So load the booter
; starting from the second sector in the partition, then jump to the
; start of the booter.
;
load_booter
    mov     ax, [si]            ; DH/DL
    mov     [chs_dx], ax
    mov     ax, [si + 2]        ; CH/CL
    mov     [chs_cx], ax

    mov     cx, 1               ; skip the initial boot sector
    mov     ax, BOOT2_SIZE      ; read BOOT2_SIZE sectors
    mov     bx, BOOT2_ADDR      ; where to place boot2 code
    call    load                ; load it...

    xor     edx, edx            ; argument for boot2 (hard drive boot)
    jmp     BOOTSEG:BOOT2_ADDR  ; there is no going back now!

;--------------------------------------------------------------------------
; Load sectors from disk using INT13/F2 call. The sectors are loaded
; one sector at a time to avoid any BIOS bugs, and eliminate
; complexities with crossing track boundaries, and other gotchas.
;
; Arguments:
;   cx - number of sectors to skip
;   ax - number of sectors to read
;   bx - pointer to the memory buffer (must not cross a segment boundary)
;   [chs_cx][chs_dx] - CHS starting position
;
; Returns:
;   CF = 0  success
;   CF = 1  error
;
; The caller must save any registers it needs.
;
load
    jcxz    load_sectors
    call    next_sector         ; [chs_cx][chs_dx] <- next sector
    loop    load

load_sectors
    mov     cx, ax              ; cx <- number of sectors to read

load_loop
    call    read_sector         ; load a single sector
    jc      load_exit           ; abort if carry flag is set
    add     bx, SECTOR_BYTES    ; increment buffer pointer
    call    next_sector         ; [chs_cx][chs_dx] <- next sector
    loop    load_loop
    clc                         ; successful exit
load_exit
    ret

;--------------------------------------------------------------------------
; Read a single sector from the hard disk.
;
; Arguments:
;   [chs_cx][chs_dx] - CHS starting position
;   bx - pointer to the sector memory buffer
;        (must not cross a segment boundary)
;
; Returns:
;   CF = 0  success
;   CF = 1  error
;
; Caller's cx register is preserved.
;
read_sector
    push    cx
    mov     cx, 5               ; try 5 times to read the sector

read_sector_1
    mov     bp, cx              ; save cx

    mov     cx, [chs_cx]
    mov     dx, [chs_dx]
    mov     dl, DRIVE_NUM       ; drive number
    mov     ax, 0x0201          ; Func 2, read 1 sector
    int     0x13                ; read sector
    jnc     read_sector_ok      ; CF = 0 indicates success

    mov     al, '*'             ; sector read error indicator
    call    putchar

    xor     ax, ax              ; Reset the drive and retry the read
    int     0x13

    mov     cx, bp
    loop    read_sector_1       ; retry while cx is not zero

    stc                         ; set carry flag to indicate error
    pop     cx
    ret

read_sector_ok
    mov     al, '.'             ; successful sector read indicator
    call    putchar
    clc                         ; success, clear carry flag
    pop     cx
    ret

;--------------------------------------------------------------------------
; Given the current CHS position stored in [chs_cx][chs_dx], update
; it so that the value in [chs_cx][chs_dx] points to the following
; sector.
;
; Arguments:
;   [chs_cx][chs_dx] - CHS position
;
;   [max_sectors] and [max_heads] must be valid.
;
; Caller's ax and bx registers are preserved.
;
next_sector
    push    ax
    push    bx

    ; Extract the CHS values from the packed register values in memory.
    ;
    mov     al, [chs_cx]
    and     al, 0x3f            ; al <- sector number (1-63)

    mov     bx, [chs_cx]
    rol     bl, 2
    ror     bx, 8
    and     bx, 0x03ff          ; bx <- cylinder number

    mov     ah, [chs_dx + 1]    ; ah <- head number

    inc     al                  ; Increment CHS by one sector.
    cmp     al, [max_sectors]
    jbe     next_sector_done

    inc     ah
    cmp     ah, [max_heads]
    jbe     next_sector_new_head

    inc     bx

next_sector_new_cyl
    xor     ah, ah              ; head number starts at 0

next_sector_new_head
    mov     al, 1               ; sector number starts at 1

next_sector_done
    ; Reassemble the CHS values back into the packed representation
    ; in memory.
    ;
    mov     [chs_cx + 1], bl    ; lower 8-bits of the 10-bit cylinder
    ror     bh, 2
    or      bh, al
    mov     [chs_cx], bh        ; cylinder & sector number
    mov     [chs_dx + 1], ah    ; head number

    pop     bx
    pop     ax
    ret

;--------------------------------------------------------------------------
; Write a string to the console.
;
; Arguments:
;   ds:si   pointer to a NULL terminated string.
;
; The following registers are modified:
;   ax, bx, si
;
message
    mov     bx, 1               ; bh=0, bl=1 (blue)
    cld                         ; increment SI after each lodsb call
message_loop
    lodsb                       ; load a byte from DS:SI into al
    cmp     al, 0               ; Is it a NULL?
    je      message_done        ; yes, all done
    mov     ah, 0xE             ; bios INT10 Func 0xE
    int     0x10                ; bios display a byte in tty mode
    jmp     short message_loop
message_done
    ret

;--------------------------------------------------------------------------
; Write a ASCII character to the console.
;
; Arguments:
;   al   contains the ASCII character printed.
;
putchar
    push    bx
    mov     bx, 1               ; bh=0, bl=1 (blue)
    mov     ah, 0x0e            ; bios int 10, function 0xe
    int     0x10                ; bios display a byte in tty mode
    pop     bx
    ret

%IF DEBUG
;==========================================================================
; DEBUG FUNCTION START
;
; If DEBUG is set to 1, this booter will become too large for the MBR,
; but it will still be less than 510 bytes, which is fine for a partition's
; boot sector.
;==========================================================================

;--------------------------------------------------------------------------
; Write a variable number of spaces to the console.
;
; Arguments:
;   al   number to spaces to display
;
putspace
    push    cx
    xor     cx, cx
    mov     cl, al              ; use cx as the loop counter
    mov     al, ' '             ; character to print
putspace_loop
    jcxz    putspace_done
    call    putchar
    loop    putspace_loop
putspace_done
    pop     cx
    ret

;--------------------------------------------------------------------------
; Write the hex byte value to the console.
;
; Arguments:
;   al   contains the byte to be displayed. e.g. if al is 0x3f, then 3F
;        will be displayed on screen.
;
display_byte
    push    ax
    ror     al, 4
    call    display_nibble      ; display upper nibble
    pop     ax
    call    display_nibble      ; display lower nibble
    ;
    mov     ax, 10              ; display carriage return
    call    putchar
    mov     ax, 13
    call    putchar
    ret

display_nibble
    and     al, 0x0f
    add     al, '0'
    cmp     al, '9'
    jna     display_nibble_1
    add     al, 'A' - '9' - 1
display_nibble_1
    call    putchar
    ret

;==========================================================================
; DEBUG FUNCTION END
;==========================================================================
%ENDIF

;--------------------------------------------------------------------------
; NULL terminated strings.
;
load_error   db  10, 13, 'Load Error', 0

;--------------------------------------------------------------------------
; Pad the rest of the 512 byte sized booter with zeroes. The last
; two bytes is the mandatory boot sector signature.
;
; If the booter code becomes too large, then nasm will complain
; that the 'times' argument is negative.

pad_boot
	times 446-($-$$) db 0

pad_table_and_sig
    times 510-($-$$) db 0
    dw BOOTSIG

    END
