; $Id: bs3-bootsector.asm 96407 2022-08-22 17:43:14Z vboxsync $
;; @file
; Generic bootsector for BS3.
;
; This sets up stack at %fff0 and loads the next sectors from the floppy at
; %10000 (1000:0000 in real mode), then starts executing at cs:ip=1000:0000.
;

;
; Copyright (C) 2007-2022 Oracle and/or its affiliates.
;
; This file is part of VirtualBox base platform packages, as
; available from https://www.virtualbox.org.
;
; This program is free software; you can redistribute it and/or
; modify it under the terms of the GNU General Public License
; as published by the Free Software Foundation, in version 3 of the
; License.
;
; This program is distributed in the hope that it will be useful, but
; WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
; General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with this program; if not, see <https://www.gnu.org/licenses>.
;
; The contents of this file may alternatively be used under the terms
; of the Common Development and Distribution License Version 1.0
; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
; in the VirtualBox distribution, in which case the provisions of the
; CDDL are applicable instead of those of the GPL.
;
; You may elect to license modified versions of this file under the
; terms and conditions of either the GPL or the CDDL or both.
;
; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
;



;*********************************************************************************************************************************
;*      Header Files                                                                                                             *
;*********************************************************************************************************************************
%include "bs3kit.mac"
%include "iprt/asmdefs.mac"
%include "iprt/x86.mac"


;*********************************************************************************************************************************
;*      Defined Constants And Macros                                                                                             *
;*********************************************************************************************************************************
;; Enabled faster loading.
%define BS3KIT_BOOTSECTOR_FASTER_LOAD
;; Enabled load progress dots.
%define BS3KIT_BOOTSECTOR_LOAD_DOTS

;; Halts on failure location. For debugging.
;%define HLT_ON_FAILURE 1


%ifdef __YASM__
[map all]
%endif

;
; Start with a jump just to follow the convention.
; Also declare all segments/sections to establish them and their order.
;
        ORG 07c00h

BITS 16
CPU 8086
start:
        jmp short bs3InitCode
        db 0ah                          ; Should be nop, but this looks better.
g_OemId:                                ; 003h
        db 'BS3Kit', 0ah, 0ah

;
; DOS 4.0 Extended Bios Parameter Block:
;
g_cBytesPerSector:                      ; 00bh
        dw 512
g_cSectorsPerCluster:                   ; 00dh
        db 1
g_cReservedSectors:                     ; 00eh
        dw 1
g_cFATs:                                ; 010h
        db 0
g_cRootDirEntries:                      ; 011h
        dw 0
g_cTotalSectors:                        ; 013h
        dw 0
g_bMediaDescriptor:                     ; 015h
        db 0
g_cSectorsPerFAT:                       ; 016h
        dw 0
g_cPhysSectorsPerTrack:                 ; 018h
        dw 18
g_cHeads:                               ; 01ah
        dw 2
g_cHiddentSectors:                      ; 01ch
        dd 1
g_cLargeTotalSectors:                   ; 020h - We (ab)use this to indicate the number of sectors to load.
        dd 0
g_bBootDrv:                             ; 024h
        db 80h
g_bFlagsEtc:                            ; 025h
        db 0
g_bExtendedSignature:                   ; 026h
        db 0x29
g_dwSerialNumber:                       ; 027h
        dd 0x0a458634
g_abLabel:                              ; 02bh
        db 'VirtualBox', 0ah
g_abFSType:                             ; 036h
        db 'RawCode', 0ah
g_BpbEnd:                               ; 03ch


;
; Where to real init code starts.
;
bs3InitCode:
        cli

        ; save the registers.
        mov     [cs:BS3_ADDR_REG_SAVE + BS3REGCTX.rax], ax
        mov     [cs:BS3_ADDR_REG_SAVE + BS3REGCTX.ds], ds

        ; set up the DS segment reister so we can skip the CS prefix when saving more prefixes..
        mov     ax, 0
        mov     ds, ax

        mov     [BS3_ADDR_REG_SAVE + BS3REGCTX.rdi], di
        mov     di, BS3_ADDR_REG_SAVE
        mov     [di + BS3REGCTX.rsp], sp
        mov     [di + BS3REGCTX.ss], ss
        mov     [di + BS3REGCTX.rcx], cx
        mov     [di + BS3REGCTX.es], es
        mov     [di + BS3REGCTX.rbp], bp

        ; set up the stack.
        mov     ss, ax
        mov     sp, BS3_ADDR_STACK

        ; Load es and setup bp frame.
        mov     es, ax
        mov     bp, sp
%if 0
        mov     [bp], ax                ; clear the first 8 bytes (terminates the ebp chain)
        mov     [bp + 02h], ax
        mov     [bp + 04h], ax
        mov     [bp + 06h], ax
%else
        mov     di, sp                  ; Combine clearing the rbp chain and register save area.
%endif

        ; Save flags now that we know that there's a valid stack.
        pushf

        ;
        ; Clear the register area.
        ;
%if 0
        mov     di, BS3_ADDR_REG_SAVE
        mov     cx, BS3REGCTX_size/2
%else
        mov     cx, (BS3_ADDR_LOAD - BS3_ADDR_STACK) / 2
%endif
        cld
        rep stosw

        ;
        ; Do basic CPU detection.
        ;

        ; 0. Load the register save area address into DI to avoid absolute addressing
        ;    when saving additional state.  To avoid disp16, offset the address.
        mov     di, BS3_ADDR_REG_SAVE + 0x70

        ; 1. bit 15-bit was fixed to 1 in pre-286 CPUs, and fixed to 0 in 286+.
        mov     ax, [bp - 2]
        test    ah, 080h                ; always set on pre 286, clear on 286 and later
        jnz     .pre_80286

        ; 2. On a 286 you cannot popf IOPL and NT from real mode.
.detect_286_or_386plus:
CPU 286
        mov     ah, (X86_EFL_IOPL | X86_EFL_NT) >> 8
        push    ax
        popf
        pushf
        cmp     ah, [bp - 3]
        pop     ax
        je      .is_386plus
.is_80286:
CPU 286
        smsw    [di + BS3REGCTX.cr0 - 0x70]
.pre_80286:
CPU 8086
        mov     [di - 0x70 + BS3REGCTX.rbx], bx
        mov     [di - 0x70 + BS3REGCTX.rdx], dx
        mov     [di - 0x70 + BS3REGCTX.rsi], si
        jmp     .do_load

        ; Save 386 registers. We can now skip the CS prefix as DS is flat.
CPU 386
.is_386plus:
        shr     eax, 16
        mov     [di - 0x70 + BS3REGCTX.rax+2], ax
        mov     eax, esp
        shr     eax, 16
        mov     [di - 0x70 + BS3REGCTX.rsp+2], ax
        mov     eax, ebp
        shr     eax, 16
        mov     [di - 0x70 + BS3REGCTX.rbp+2], ax
        mov     eax, edi
        shr     eax, 16
        mov     [di - 0x70 + BS3REGCTX.rdi+2], ax
        shr     ecx, 16
        mov     [di - 0x70 + BS3REGCTX.rcx+2], cx
        mov     [di - 0x70 + BS3REGCTX.fs], fs
        mov     [di - 0x70 + BS3REGCTX.gs], gs
        mov     [di - 0x70 + BS3REGCTX.rbx], ebx
        mov     [di - 0x70 + BS3REGCTX.rdx], edx
        mov     [di - 0x70 + BS3REGCTX.rsi], esi
        mov     eax, cr2
        mov     [di - 0x70 + BS3REGCTX.cr2], eax
        mov     eax, cr3
        mov     [di - 0x70 + BS3REGCTX.cr3], eax
        mov     byte [di - 0x70 + BS3REGCTX.bMode], BS3_MODE_RM
        mov     [di - 0x70 + BS3REGCTX.cs], cs
        xor     eax, eax
        mov     ax, start
        mov     [di - 0x70 + BS3REGCTX.rip], eax

        ; Pentium/486+: CR4 requires VME/CPUID, so we need to detect that before accessing it.
        mov     [di - 0x70 + BS3REGCTX.cr4], eax
        popf                            ; (restores IOPL+NT)
        pushfd
        pop     eax
        mov     [di - 0x70 + BS3REGCTX.rflags], eax
        xor     eax, X86_EFL_ID
        push    eax
        popfd
        pushfd
        pop     ebx
        cmp     ebx, eax
        jne     .no_cr4
        mov     eax, cr4
        mov     [di - 0x70 + BS3REGCTX.cr4], eax
.no_cr4:
        ; Make sure caching is enabled and alignment is off.
        mov     eax, cr0
        mov     [di - 0x70 + BS3REGCTX.cr0], eax
        and     eax, ~(X86_CR0_NW | X86_CR0_CD | X86_CR0_AM)
        mov     cr0, eax

        ; Load all the code.
.do_load
        mov     [g_bBootDrv], dl
        call    NAME(bs3InitLoadImage)
%if 0
        mov     al, '='
        call    bs3PrintChrInAl
%endif

        ;
        ; Call the user 'main' procedure (shouldn't return).
        ;
        cld
        call    BS3_SEL_TEXT16:0000h

        ; Panic/hang.
Bs3Panic:
        cli
        jmp     Bs3Panic


;; For debug and error handling.
; @uses ax
bs3PrintHexInAl:
CPU 286
        push    ax
        shr     al, 4
        call    bs3PrintHexDigitInAl
        pop     ax
bs3PrintHexDigitInAl:
        and     al, 0fh
        cmp     al, 10
        jb      .decimal
        add     al, 'a' - '0' - 10
.decimal:
        add     al, '0'
bs3PrintChrInAl:
        push    bx
        mov     ah, 0eh
        mov     bx, 0ff00h
        int     10h
        pop     bx
        ret


;;
; Loads the image off the floppy.
;
; This uses g_cLargeTotalSectors to figure out how much to load.
;
; Clobbers everything except ebp and esp.  Panics on failure.
;
; @param    dl          The boot drive number (from BIOS).
; @uses     ax, cx, bx, esi, di
;
BEGINPROC bs3InitLoadImage
        push    bp
        mov     bp, sp
        push    es
%define bSavedDiskNo    byte [bp - 04h]
        push    dx
%define bMaxSector      byte [bp - 06h]
%define wMaxSector      word [bp - 06h]
        xor     ax, ax
        push    ax
%define bMaxHead        byte [bp - 08h]
        push    ax

        ;
        ; Try figure the geometry.
        ;
        mov     ah, 08h
        int     13h
%ifndef HLT_ON_FAILURE
        jc      .failure
%else
        jnc     .ok_geometry_call
        cli
        hlt
.ok_geometry_call:
%endif
        and     cl, 63                  ; only the sector count.
        mov     bMaxSector, cl
        mov     bMaxHead, dh
        mov     dl, bSavedDiskNo

%if 0 ; bMaxSector=0x12 (18); bMaxHead=0x01; bMaxCylinder=0x4f (79)
        mov al, 'S'
        call bs3PrintChrInAl
        mov al, bMaxSector
        call bs3PrintHexInAl
        mov al, 'H'
        call bs3PrintChrInAl
        mov al, bMaxHead
        call bs3PrintHexInAl
        mov al, 'C'
        call bs3PrintChrInAl
        mov al, ch                      ; first 8-bit of cylinder count.
        call bs3PrintHexInAl
        mov al, ';'
        call bs3PrintChrInAl
%endif

%ifndef BS3KIT_BOOTSECTOR_FASTER_LOAD
        ;
        ; Load the sectors following the boot sector one at a time (avoids problems).
        ;
        mov     si, [g_cLargeTotalSectors] ; 16-bit sector count ==> max 512 * 65 535 = 33 553 920 bytes.
        dec     si                       ; Practically max: ca 575 KB, or 1150 sectors.  Linker set BS3_MAX_SIZE to 480KB.

        mov     di, BS3_ADDR_LOAD / 16  ; The current load segment.
        mov     cx, 0002h               ; ch/cylinder=0 (0-based); cl/sector=2 (1-based)
        xor     dh, dh                  ; dh/head=0
.the_load_loop:
 %if 0
        mov al, 'c'
        call bs3PrintChrInAl
        mov al, ch
        call bs3PrintHexInAl
        mov al, 's'
        call bs3PrintChrInAl
        mov al, cl
        call bs3PrintHexInAl
        mov al, 'h'
        call bs3PrintChrInAl
        mov al, dh
        call bs3PrintHexInAl
        mov al, ';'
        call bs3PrintChrInAl
 %elifdef BS3KIT_BOOTSECTOR_LOAD_DOTS
        mov     al, '.'
        call bs3PrintChrInAl
 %endif
        xor     bx, bx
        mov     es, di                  ; es:bx -> buffer
        mov     ax, 0201h               ; al=1 sector; ah=read function
        int     13h
%ifndef HLT_ON_FAILURE
        jc      .failure
%else
        jnc     .read_ok
        cli
        hlt
.read_ok:
%endif

        ; advance to the next sector/head/cylinder.
        inc     cl
        cmp     cl, bMaxSector
        jbe     .adv_addr

        mov     cl, 1
        inc     dh
        cmp     dh, bMaxHead
        jbe     .adv_addr

        mov     dh, 0
        inc     ch

.adv_addr:
        add     di, 512 / 16
        dec     si
        jnz     .the_load_loop

%else ; BS3KIT_BOOTSECTOR_FASTER_LOAD
        ;
        ; Load the sectors following the boot sector, trying to load a whole
        ; side in each bios call, falling back on single sector reads if we
        ; run into DMA 64KB boundrary issues (BIOS must tell us).
        ;
        mov     si, [g_cLargeTotalSectors] ; 16-bit sector count ==> max 512 * 65 535 = 33 553 920 bytes.
        dec     si                      ; Skip the boot sector, it's not part of the test image we execute.
        mov     di, BS3_ADDR_LOAD / 16  ; The current load segment.
        mov     cx, 0002h               ; ch/cylinder=0 (0-based); cl/sector=0 (1-based)
        xor     dh, dh                  ; dh/head=0
.the_load_loop:
 %if 0
        mov al, 'c'
        call bs3PrintChrInAl
        mov al, ch
        call bs3PrintHexInAl
        mov al, 's'
        call bs3PrintChrInAl
        mov al, cl
        call bs3PrintHexInAl
        mov al, 'h'
        call bs3PrintChrInAl
        mov al, dh
        call bs3PrintHexInAl
        mov al, ';'
        call bs3PrintChrInAl
 %elifdef BS3KIT_BOOTSECTOR_LOAD_DOTS
        mov     al, '.'
        call bs3PrintChrInAl
 %endif
        mov     ax, wMaxSector          ; read to the end of the side by default.
        sub     al, cl
        inc     al
.read_again:
        cmp     si, ax
        jae     .do_read
        mov     ax, si
.do_read:
        mov     ah, 02h                 ; ah=read function
        xor     bx, bx
        mov     es, di                  ; es:bx -> buffer
        int     13h
        jnc     .advance_sector

        cmp     ah, 9                   ; DMA 64KB crossing error
%if 0 ; This hack doesn't work. If the FDC is in single sided mode we end up with a garbled image. Probably "missing" sides.
        je      .read_one

        cmp     ah, 20h                 ; Controller error, probably because we're reading side 1 on a single sided floppy
        jne     .failure
        cmp     bMaxHead, 0
        je      .failure
        cmp     dh, 1
        jne     .failure
        xor     dh, dh
        mov     bMaxHead, dh
        inc     ch
        jmp     .the_load_loop
.read_one
%else
        jne     .failure
%endif
        mov     ax, 1                   ; Retry reading a single sector.
        jmp     .read_again

        ; advance to the next sector/head/cylinder and address.
.advance_sector:
        inc     cl
        cmp     cl, bMaxSector
        jbe     .adv_addr

        mov     cl, 1
        inc     dh
        cmp     dh, bMaxHead
        jbe     .adv_addr

        mov     dh, 0
        inc     ch

.adv_addr:
        dec     si
        jz      .done_reading
        add     di, 512 / 16
        dec     al
        jnz     .advance_sector
        jmp     .the_load_loop

.done_reading:
%endif ; BS3KIT_BOOTSECTOR_FASTER_LOAD
%if 0
        mov     al, 'D'
        call bs3PrintChrInAl
%elifdef BS3KIT_BOOTSECTOR_LOAD_DOTS
        mov     al, 13
        call bs3PrintChrInAl
        mov     al, 10
        call bs3PrintChrInAl
%endif

        add     sp, 2*2
        pop     dx
        pop     es
        pop     bp
        ret

%ifndef HLT_ON_FAILURE
        ;
        ; Something went wrong, display a message.
        ;
.failure:
 %if 1 ; Disable to save space for debugging.
  %if 1
        push    ax
  %endif

        ; print message
        mov     si, .s_szErrMsg
.failure_next_char:
        lodsb
        call    bs3PrintChrInAl
        cmp     si, .s_szErrMsgEnd
        jb      .failure_next_char

        ; panic
  %if 1
        pop     ax
        mov     al, ah
        push    bs3PrintHexInAl
  %endif
        call    Bs3Panic
.s_szErrMsg:
        db 13, 10, 'rd err! '
 %else
        hlt
        jmp .failure
 %endif
%endif
.s_szErrMsgEnd:
;ENDPROC bs3InitLoadImage - don't want the padding.


;
; Pad the remainder of the sector with int3's and end it with the DOS signature.
;
bs3Padding:
        times ( 510 - ( (bs3Padding - start) % 512 ) ) db 0cch
        db      055h, 0aah

