root/src/apps/bootmanager/bootman.S
;
; Copyright 2007, Dengg David, david-d@gmx.at. All rights reserved.
; Copyright 2008, Michael Pfeiffer, laplace@users.sourceforge.net. All rights reserved.
; Copyright 2005, Ingo Weinhold, bonefish@users.sf.net.
; Copyright 2011, Axel Dörfler, axeld@pinc-software.de.
; Distributed under the terms of the MIT License.


%assign USE_TEST_MENU                           0

%assign BOOT_BLOCK_START_ADDRESS        0x7c00

%assign MBR_SIGNATURE                           0xAA55

; BIOS calls

%assign BIOS_VIDEO_SERVICES                     0x10
%assign BIOS_DISK_SERVICES                      0x13
%assign BIOS_KEYBOARD_SERVICES          0x16
%assign BIOS_REBOOT                                     0x19            ; dl - boot drive number
%assign BIOS_TIME_SERVICES                      0x1A

; video services
%assign SET_VIDEO_MODE                                  0x00    ; al - mode

%assign SET_CURSOR_SHAPE                                0x01    ; ch - starting scan line (5 bits)
                                                                                                ; cl - ending scan line (5 bits)

%assign SET_CURSOR                                              0x02    ; dl - column
                                                                                                ; dh - row
                                                                                                ; bh - page


%assign GET_CURSOR                                              0x03    ; bh - page
                                                                                                ; -> dl - column
                                                                                                ;       dh - row
                                                                                                ;       Cursor shape:
                                                                                                ;       ch - starting scan line
                                                                                                ;       cl - ending scan line

%assign SCROLL_UP                                               0x06    ; al - lines (0: clear screen)
                                                                                                ; bh - attribute
                                                                                                ; ch - upper line
                                                                                                ; cl - left column
                                                                                                ; dh - lower line
                                                                                                ; dl - right column

%assign WRITE_CHAR                                              0x09    ; al - char
                                                                                                ; bh - page
                                                                                                ; bl - attribute
                                                                                                ; cx - count

;%assign WRITE_CHAR                                             0x0e    ; al - char
                                                                                                ; bh - page
                                                                                                ; bl - foreground color (graphics mode only)

; disk services
%assign READ_DISK_SECTORS                               0x02    ; dl    - drive
                                                                                                ; es:bx - buffer
                                                                                                ; dh    - head (0 - 15)
                                                                                                ; ch    - track 7:0 (0 - 1023)
                                                                                                ; cl    - track 9:8,
                                                                                                ;                sector (1 - 17)
                                                                                                ; al    - sector count
                                                                                                ; -> al - sectors read
%assign READ_DRIVE_PARAMETERS                   0x08    ; dl - drive
                                                                                                ; -> cl - max cylinder 9:8
                                                                                                ;          - sectors per track
                                                                                                ;       ch - max cylinder 7:0
                                                                                                ;       dh - max head
                                                                                                ;       dl - number of drives (?)
%assign CHECK_DISK_EXTENSIONS_PRESENT   0x41    ; bx - 0x55aa
                                                                                                ; dl - drive
                                                                                                ; -> success: carry clear
                                                                                                ;       ah - extension version
                                                                                                ;       bx - 0xaa55
                                                                                                ;       cx - support bit mask
                                                                                                ; -> error: carry set
%assign EXTENDED_READ                                   0x42    ; dl - drive
                                                                                                ; ds:si - address packet
                                                                                                ; -> success: carry clear
                                                                                                ; -> error: carry set

%assign FIXED_DISK_SUPPORT                              0x1             ; flag indicating fixed disk
                                                                                                ; extension command subset

; keyboard services
%assign READ_CHAR                                               0x00    ; -> al - ASCII char
                                                                                                ;       ah - scan code

%assign PROBE_CHAR                                              0x01    ; -> zf = 0
                                                                                                ;       al - ASCII char
                                                                                                ;       ah - scan code

%assign GET_MODIFIER_KEYS                               0x02    ;-> al - modifier key bitmask

; timer services
%assign READ_CLOCK                                              0x00    ; -> cx - high word
                                                                                                ;       dx - low word
                                                                                                ;       one tick = 1/18.2s

%assign TICKS_PER_SECOND                                19

; video modes
%assign GRAPHIC_MODE_80x25                              0x12    ; 640 x 480 graphic mode

%assign TEXT_COLUMNS                                    80              ; Number of columns
%assign TEXT_ROWS                                               25              ; Number of rows

; Colors
%assign BLACK                                                   0
%assign BLUE                                                    1
%assign GREEN                                                   2
%assign CYAN                                                    3
%assign RED                                                             4
%assign MAGENTA                                                 5
%assign BROWN                                                   6
%assign LIGHT_GRAY                                              7
%assign DARK_GRAY                                               8
%assign LIGHT_BLUE                                              9
%assign LIGHT_GREEN                                             10
%assign LIGHT_CYAN                                              11
%assign LIGHT_RED                                               12
%assign LIGHT_MAGENTA                                   13
%assign YELLOW                                                  14
%assign WHITE                                                   15

%assign BRIGHT_COLOR_MASK                               8

; Characters
%assign TRIANGLE_TO_RIGHT                               16
%assign TRIANGLE_TO_LEFT                                17

; Key codes
%assign KEY_DOWN                                                0x50
%assign KEY_UP                                                  0x48
%assign KEY_PAGE_DOWN                                   0x51
%assign KEY_PAGE_UP                                             0x49
%assign KEY_HOME                                                0x47
%assign KEY_END                                                 0x4f
%assign KEY_RETURN                                              0x1C

; Modifier key bitmasks
%assign MODIFIER_RIGHT_SHIFT_KEY                0x01
%assign MODIFIER_LEFT_SHIFT_KEY                 0x02
%assign MODIFIER_CONTROL_KEY                    0x04
%assign MODIFIER_ALT_KEY                                0x08
%assign MODIFIER_SCROLL_LOCK_KEY                0x10
%assign MODIFIER_NUM_LOCK_KEY                   0x20
%assign MODIFIER_CAPS_LOCK_KEY                  0x40
%assign MODIFIER_INSERT_KEY                             0x80

; String constants with their length
%define TITLE                                                   'Haiku Boot Manager'
%strlen TITLE_LENGTH                                    TITLE
%define SELECT_OS_MESSAGE                               'Select an OS from the menu'
%strlen SELECT_OS_MESSAGE_LENGTH                SELECT_OS_MESSAGE

; 16 bit code
SECTION .text
BITS 16


; nicer way to get the size of a structure
%define sizeof(s)       s %+ _size

; using a structure in a another structure definition
%macro  nstruc  1-2      1
                                        resb    sizeof(%1) * %2
%endmacro

; Variables on stack
struc   Locals
        selection               resw    1
        firstLine               resb    2 ; low byte used only
        timeoutTicks    resd    1
        cursorX                 resb    1
        cursorY                 resb    1
        cursorShape             resw    1
        biosDrive               resb    1
endstruc

cursorPosition          equ cursorX

%macro DEBUG_PAUSE 0
        push    ax
        mov             ah, READ_CHAR
        int             BIOS_KEYBOARD_SERVICES
        pop             ax
%endmacro

%macro CLEAR_SCREEN 0
        mov             ah, SCROLL_UP
        xor             al, al
        mov             bh, WHITE
        xor             cx, cx
        mov             dx, (TEXT_ROWS-1) * 0x100 + (TEXT_COLUMNS-1)
        int             BIOS_VIDEO_SERVICES
%endmacro

; Prints a null terminated string
; bl ... color
; si ... offset to string
%macro PRINT_STRING 0
        push    ax
        push    bx
        push    cx
        push    dx
        xor             bh, bh                                                          ; write on page 0
        jmp             .loop_condition
.loop:
        mov             dx, [bp + cursorPosition]
        mov             ah, SET_CURSOR
        int             BIOS_VIDEO_SERVICES

        inc             byte [bp + cursorX]

        mov             cx, 1
        mov             ah, WRITE_CHAR
        int             BIOS_VIDEO_SERVICES
.loop_condition:
        lodsb
        cmp             al, 0
        jnz             .loop
        pop             dx
        pop             cx
        pop             bx
        pop             ax
        ret
%endmacro

; 64 bit value
struc   quadword
        .lower                  resd    1
        .upper                  resd    1
endstruc

; address packet as required by the EXTENDED_READ BIOS call
struc   AddressPacket
        .packet_size    resb    1
        .reserved1              resb    1
        .block_count    resb    1
        .reserved2              resb    1
        .buffer                 resd    1
        .offset                 nstruc  quadword
endstruc

struc   BootLoaderAddress
        .device                 resb    1                       ; hard drive number
        .offset                 nstruc  quadword        ; LBA of start start sector
endstruc

; use code available in stage 1
%define printstr printStringStage1

stage1:
        mov             ax, 0x07c0                                              ; BOOT_BLOCK_START_ADDRESS / 16
        mov             ds, ax                                                  ; Setup segment registers
        mov             es, ax
        mov             ss, ax

        mov             sp, 0xFFFF - sizeof(Locals)             ; Make stack empty
        mov             bp, sp

        mov             [bp + biosDrive], dl                    ; Store boot drive
        cld                                                                             ; String operations increment index
                                                                                        ; registers
        CLEAR_SCREEN
        call    hideCursor

        mov             bh, 0                                                   ; Text output on page 0

        ; Print title centered at row 2
        mov             dx, 1 * 0x100 + (40 - TITLE_LENGTH / 2)
        mov             [bp + cursorPosition], dx

        mov             si, kTitle
        mov             bl, WHITE
        call    printstr

        ; Print message centered at second last row
        mov             dx, (TEXT_ROWS-2) * 0x100 + (40 - SELECT_OS_MESSAGE_LENGTH / 2)
        mov             [bp + cursorPosition], dx

        mov             bl, LIGHT_GRAY
        mov             si, kSelectOSMessage
        call    printstr

        ; Chain load rest of boot loader
        mov             ah, EXTENDED_READ                               ; Load 3 more sectors
        mov             dl, [bp + biosDrive]
        mov             si, nextStageDAP
        int             BIOS_DISK_SERVICES
        jc              .error                                                  ; I/O error
        jmp             stage2                                                  ; Continue in loaded stage 2

.error:
        call    showCursor
        mov             si, kError
        mov             bl, RED
        call    printstr

        mov             ah, READ_CHAR
        int             BIOS_KEYBOARD_SERVICES

        mov             dl, [bp + biosDrive]
        int             BIOS_REBOOT

printStringStage1:
        PRINT_STRING

hideCursor:
        mov             ah, GET_CURSOR
        int             BIOS_VIDEO_SERVICES
        mov             [bp + cursorShape], cx

        mov             ah, SET_CURSOR_SHAPE
        mov             cx, 0x2000
        int             BIOS_VIDEO_SERVICES
        ret

showCursor:
        mov             cx, [bp + cursorShape]
        mov             ah, SET_CURSOR_SHAPE
        int             BIOS_VIDEO_SERVICES
        ret

nextStageDAP:
        istruc AddressPacket
                at AddressPacket.packet_size,   db              0x10
                at AddressPacket.block_count,   db              0x03
                at AddressPacket.buffer,                dw              0x0200, 0x07c0
                at AddressPacket.offset,                dw              1
        iend

kTitle:
        db              TITLE, 0x00
kSelectOSMessage:
        db              SELECT_OS_MESSAGE, 0x00
kError:
        db              'Error loading sectors!', 0x00

kStage1UnusedSpace      equ     440 - ($-$$)
        ; Fill the missing space to reach byte 440
        times kStage1UnusedSpace db 'B'

kDiskSignature:
        dw              0, 0
kReserved:
        dw              0
kPartitionTable:
        times   64 db 0

kMBRSignature:
        ; Magic marker "AA55" (to identify a valid boot record)
        dw              MBR_SIGNATURE

; ======================================================================
; ======================= SECOND SECTOR ================================
; ======================================================================

; Use code available in stage 2
%define printstr printStringStage2

%assign TIMEOUT_OFF             0xffff


stage2:
        mov             ax, [defaultItem]                                       ; Select default item
        mov             [bp + selection], ax

        mov             ax, TICKS_PER_SECOND                            ; Calculate timeout ticks
        mul             word [timeout]
        mov             bx, dx
        push    ax

        mov             ah, READ_CLOCK
        int             BIOS_TIME_SERVICES

        pop             ax                                                                      ; Add current ticks
        add             ax, dx
        adc             bx, cx
        mov             [bp + timeoutTicks], ax
        mov             [bp + timeoutTicks + 2], bx

        mov             al, [listItemCount]                                     ; Calculate start row for menu
        shr             al, 1
        mov             bl, TEXT_ROWS / 2
        sub             bl, al                                                          ; y = TEXT_ROWS / 2 - number of items / 2
        mov             [bp + firstLine], bl

        mov             ah, GET_MODIFIER_KEYS                           ; Disable timeout if ALT key is pressed
        int             BIOS_KEYBOARD_SERVICES
        and             al, MODIFIER_ALT_KEY
        jz              showMenu
        mov             word [timeout], TIMEOUT_OFF

showMenu:
        call    printMenu

        cmp             word [timeout], TIMEOUT_OFF
        je              inputLoop

timeoutLoop:
        mov             ah, PROBE_CHAR
        int             BIOS_KEYBOARD_SERVICES
        jnz             inputLoop                                                       ; cancel timeout if key is pressed
        call    isTimeoutReached
        jnc             timeoutLoop
        jmp             bootSelectedPartition

isTimeoutReached:
        mov             ah, READ_CLOCK
        int             BIOS_TIME_SERVICES
        cmp             cx, [bp + timeoutTicks + 2]
        jb              .returnFalse
        ja              .returnTrue
        cmp             dx, [bp + timeoutTicks]
        ja              .returnTrue
.returnFalse:
        clc
        ret
.returnTrue:
        stc
        ret

; ================== Wait for a key and do something with it ==================
mainLoop:
        call    printMenu

inputLoop:
        mov             ah, READ_CHAR
        int             BIOS_KEYBOARD_SERVICES                          ; AL = ASCII Code, AH = Scancode

        cmp             ah, KEY_DOWN
        je              selectNextPartition

        cmp             ah, KEY_PAGE_DOWN
        je              selectLastPartition
        cmp             ah, KEY_END
        je              selectLastPartition

        cmp             ah, KEY_UP
        je              selectPreviousPartition

        cmp             ah, KEY_PAGE_UP
        je              selectFirstPartition
        cmp             ah, KEY_HOME
        je              selectFirstPartition

        cmp             ah, KEY_RETURN
        jne             inputLoop
        jmp             bootSelectedPartition

selectNextPartition:
        mov             ax, [bp + selection]
        inc             ax
        cmp             ax, [listItemCount]
        jne             .done                                                           ; At end of list?
        xor             ax, ax                                                          ; Then jump to first entry
.done:
        mov             [bp + selection], ax
        jmp             mainLoop

selectLastPartition:
        mov             ax, [listItemCount]
        dec             ax
        mov             [bp + selection], ax
        jmp             mainLoop

selectPreviousPartition:
        mov             ax, [bp + selection]
        or              ax, ax
        jnz             .done                                                           ; At top of list?
        mov             ax, [listItemCount]                                     ; Then jump to last entry
.done:
        dec             ax
        mov             [bp + selection], ax
        jmp             mainLoop

selectFirstPartition:
        xor             ax, ax
        mov             [bp + selection], ax
        jmp             mainLoop


; ======================= Print the OS list ============================
printMenu:
        mov             al, [bp + firstLine]
        mov             [bp + cursorY], al

        mov             si, list                                                        ; Start at top of list
        xor             cx, cx                                                          ; The index of the current item

.loop:
        lodsb                                                                           ; String length incl. 0-terminator
        add             al, 3                                                           ; center menu item
        shr             al, 1                                                           ; x = TEXT_COLUMNS / 2 - length / 2
        mov             dl, TEXT_COLUMNS / 2
        sub             dl, al
        mov             [bp + cursorX], dl

        mov             al, TRIANGLE_TO_RIGHT
        call    updateMarker
        inc             byte [bp + cursorX]

        mov             di, cx
        and             di, 3
        mov             bl, [kColorTable + di]                          ; Text color

        cmp             cx, [bp + selection]
        jne             .print                                                          ; Selected item reached?
        xor             bl, BRIGHT_COLOR_MASK                           ; Highlight it

.print:
        call    printstr
        add             si, sizeof(BootLoaderAddress)

        add             byte [bp + cursorX], 1
        mov             al, TRIANGLE_TO_LEFT
        call    updateMarker

        inc             byte [bp + cursorY]
        inc             cx

        cmp             cx, [listItemCount]
        jne             .loop
        ret

updateMarker:
        cmp             cx, [bp + selection]
        je              .print
        mov             al, ' '                                                         ; Clear marker
.print:
        mov             bl, WHITE
        jmp             printChar                                                       ; return from subroutine


; ========================== Chainload ==========================

bootSelectedPartition:

        call    showCursor

        call    getSelectedBootLoaderAddress
        lodsb                                                                           ; Set boot drive
        mov             dl, al

        mov             di, bootSectorDAP+AddressPacket.offset  ; Copy start sector
        mov             cx, 4                                                           ; It is stored in a quad word
.copy_start_sector:
        lodsw
        stosw
        loop .copy_start_sector

        mov             ah, EXTENDED_READ                                       ; Now read start sector from HD
        mov             si, bootSectorDAP
        int             BIOS_DISK_SERVICES
        mov             si, kReadError
        jc              printAndHalt                                            ; Failed to read sector

        mov             ax, [kMBRSignature]
        cmp             ax, MBR_SIGNATURE
        mov             si, kNoBootablePartitionError
        jne             printAndHalt                                            ; Missing signature

        CLEAR_SCREEN

        ; Print "Loading <name>" at top of screen
        mov             word [bp + cursorPosition], 0
        mov             si, kLoadingMessage
        mov             bl, LIGHT_GRAY
        call    printstr

        inc             byte [bp + cursorX]
        call    getSelectedMenuItem
        inc             si                                                                      ; Skip string length byte
        call    printstr

        mov             dx, 0x100
        xor             bh, bh
        mov             ah, SET_CURSOR
        int             BIOS_VIDEO_SERVICES

        call    getSelectedBootLoaderAddress
        mov             dl, [si]                                                        ; drive number in dl

        jmp             $$                                                                      ; Start loaded boot loader


printAndHalt:
        mov             dx, (TEXT_ROWS-4) * 0x100 + (TEXT_COLUMNS / 3)
        mov             [bp + cursorPosition], dx

        mov             bx, 0x0F                                                        ; Page number and foreground color
        call    printstr
        mov             ah, READ_CHAR
        int             BIOS_KEYBOARD_SERVICES
        mov             dl, [bp + biosDrive]
        int             BIOS_REBOOT

; Output:
;       si      address of selected menu item
; Trashes:
;       ax, cx
getSelectedMenuItem:
        mov             si, list                                                        ; Search address of start sector
                                                                                                ; of the selected item.
        mov             cx, [bp + selection]
        inc             cx                                                                      ; Number of required iterations

        xor             ah, ah                                                          ; The high-byte of the string length
                                                                                                ; see loop body
        jmp             .entry

.loop:
        lodsb                                                                           ; Length of menu item name
        add             si, ax                                                          ; Skip name to BootLoaderAddess
        add             si, sizeof(BootLoaderAddress)

.entry:
        loop    .loop
        ret

getSelectedBootLoaderAddress:
        call    getSelectedMenuItem
        lodsb
        xor             ah, ah
        add             si, ax                                                          ; Skip name
        mov             dl, [si]
        test    dl, 0                                                           ; if drive is 0, use boot drive
        jz              .takeOverBootDrive
        ret
.takeOverBootDrive:
        mov             dl, [bp + biosDrive]
        mov             [si], dl
        ret

printStringStage2:
        PRINT_STRING

; al ... ASCII character
; bl ... color
printChar:
        push    ax
        push    bx
        push    cx
        push    dx

        xor             bh, bh                                                          ; Write on page 0

        mov             dx, [bp + cursorPosition]
        mov             ah, SET_CURSOR
        int             BIOS_VIDEO_SERVICES

        inc             byte [bp + cursorX]

        mov             cx, 1
        mov             ah, WRITE_CHAR
        int             BIOS_VIDEO_SERVICES

        pop             dx
        pop             cx
        pop             bx
        pop             ax
        ret

; ================================ DATA ===========================

bootSectorDAP:
        istruc AddressPacket
                at AddressPacket.packet_size,   db              0x10
                at AddressPacket.block_count,   db              0x01
                at AddressPacket.buffer,                dw              0x0000, 0x07c0
        iend

kColorTable:
        db BLUE, RED, GREEN, CYAN
kReadError:
        db              'Error loading sectors', 0x00
kNoBootablePartitionError:
        db              'Not a bootable partition', 0x00
kLoadingMessage:
        db              'Loading', 0x00


listItemCount:
defaultItem                     equ             listItemCount + 2
timeout                         equ             defaultItem + 2
list                            equ             timeout + 2

; dw number of entries
; dw the default entry
; dw the timeout (-1 for none)
; entry:
; db size of partition name 0-terminated string
; db 0-terminated string with partition name
; db hard drive number
; quadword start sector

%if USE_TEST_MENU
        dw              0x06

        dw              2

        dw              5

        db              0x06
        db              'HAIKU', 0
        db              0x80
        dw              1, 0, 0, 0

        db              0x08
        db              'FreeBSD', 0
        db              0x80
        dw              0x003F, 0, 0, 0

        db              0x04
        db              'DOS', 0
        db              0x80
        dw              0x003E, 0, 0, 0

        db              0x06
        db              'LINUX', 0
        db              0x80
        dw              0x003F, 0, 0, 0

        db              0x08
        db              'BeOS R5', 0
        db              0x80
        dw              0x003F, 0, 0, 0

        db              0x07
        db              'OpenBSD', 0
        db              0x80
        dw              0xAAAA, 0, 0, 0

        dw              kStage1UnusedSpace
%endif