;/*static char *SCCSID = "@(#)pinboot.asm       6.3 91/05/11";*/
        page    ,132
        title   pinboot - Pinball boot loader
        name    pinboot

; The ROM in the IBM PC starts the boot process by performing a hardware
; initialization and a verification of all external devices.  If all goes
; well, it will then load from the boot drive the sector from track 0, head 0,
; sector 1.  This sector is placed at physical address 07C00h.
;
; The code in this sector is responsible for locating and rading
; two files:  OS2LDR and the mini-FSD (OS2BOOT).  These files are
; loaded according to the following memory map:
;
;       +--------+
;       | OS2LDR |
;       +--------+ 1000:0000
;       |mini-FSD|
;       +--------+ 0000:07c0
;       +--------+ 0000:0000
;
; Thus, the mini-FSD is loaded in low memory, while OS2LDR is loaded
; at 64K.
;
; To stay out of the way of the loader and mini, the boot code
; relocates itself up near the top of low memory.
;
; The boot sector understands the structure of the Pinball root directory,
; and is capable of reading any file that does not contain an allocation
; B+tree;  that is, as long as the FNODE for a file does not contain node
; pointers, it can be read by the boot sector.  So the OS2LDR and
; OS2BOOT can each be up to 8 extents.
;
; The boot sector does not understand the Pinball file system's hotfixing --
; there isn't enough room.  So the three kernel files should not be hotfixed.
;
;
MASM    equ     1

;
;   TRACE can be used to trace the progress of the file search code, but does
;   not work before the entire boot code is loaded and relocated.

TRACE   equ     0               ; DO NOT TRACE
;TRACE   equ     1               ; DO TRACE

;
;   TRACECHAR is a simple trace mechanism which can be used in the first sector
;   of the boot code.  It prints out 1 character.  The character can be changed
;   as the code progress (if desired).

TRACECHAR   equ 0               ; DO NOT TRACE
;TRACECHAR   equ 1               ; DO TRACE

        .xlist
        .286
        include macro.inc
;
A_DEFINED       equ     1       ; don't "extrn" A_xxxx functions

        .386
        include u_const.inc       ;get the file system's headers.
        include chain.inc
        include u_misc.inc
        include storage.inc
        include fnode.inc
        include dir.inc
        include superb.inc
        .286
        .list

DoubleWord      struc
lsw     dw      ?
msw     dw      ?
DoubleWord      ends

;
; The following are various segments used by the boot loader.  The first
; two are the segments where the boot sector is initially loaded and where
; the boot sector is relocated to.  The others are the static locations
; where the mini-FSD and OS2KRNL are loaded.  There is no segment definition
; for where OS2LDR is loaded, since its position is variable (it comes right
; after the end of OS2KRNL).
;

BootSeg segment at 07c0h        ; This is where the ROM loads us initially.
BootSeg ends

mFSDSeg segment at 07ch         ; This is where the mini-FSD should be loaded.
mFSDSeg ends

KrnlSeg segment at 1000h        ; This is where either the os2krnl(1.2) or
KrnlSeg ends                    ; os2ldr(2.0) will be loaded

;  The main boot code and the micro-FSD together occupy
;  12K--8K reserved for code, followed by a 4K scratch
;  buffer area.

ScrOfs  equ     2000h           ; offset of 4K scratch area.
muLen   equ     3000h           ; size of micro-FSD

MOVEDD  macro   dest, src       ; macro to copy a doubleword memory variable.
        mov     ax, src.lsw
        mov     dest.lsw, ax
        mov     ax, src.msw
        mov     dest.msw, ax
        ENDM

;/********************** START OF SPECIFICATIONS ************************/
;/*                                                                     */
;/* SUBROUTINE NAME: pinboot                                            */
;/*                                                                     */
;/* DESCRIPTIVE NAME: Bootstrap loader                                  */
;/*                                                                     */
;/* FUNCTION:    To load OS2LDR and OS2BOOT into memory.                */
;/*                                                                     */
;/* NOTES:       pinboot is loaded by the ROM BIOS (Int 19H) at         */
;/*                 physical memory location 0000:7C00H.                */
;/*              pinboot runs in real mode.                             */
;/*              This boot record is for Pinball file systems only.     */
;/*              Allocation information for OS2LDR, OS2KRNL, and        */
;/*                 OS2BOOT may not exceed their FNODEs.                */
;/*                                                                     */
;/* ENTRY POINT: pinboot                                                */
;/*    LINKAGE:  Jump (far) from Int 19H                                */
;/*                                                                     */
;/* INPUT:       CS:IP = 0000:7C00H                                     */
;/*              SS:SP = 0030:00FAH (CBIOS dependent)                   */
;/*                                                                     */
;/* EXIT-NORMAL:                                                        */
;/*              DH = 00000100b (mini-FSD exists, local, use            */
;/*                              OS2LDR driver)                         */
;/*              DL = INT 13 drive number we booted from                */
;/*              DS:SI -> the boot media's Extended BPB                 */
;/*              ES:DI -> resource table                                */
;/*              Jmp to main in OS2LDR                                  */
;/*                                                                     */
;/* EXIT-ERROR:  None                                                   */
;/*                                                                     */
;/* EFFECTS:     Pinball mini-FSD is loaded into the physical           */
;/*                memory location 000007C0H                            */
;/*              OS2KRNL is loaded into the physical memory             */
;/*                location 00010000H                                   */
;/*              OS2LDR is loaded at the first paragraph boundary       */
;/*                after the end of OS2KRNL                             */
;/*                                                                     */
;/* MESSAGES:                                                           */
;/*              A disk read error occurred.                            */
;/*              The file OS2LDR cannot be found.                       */
;/*              The file OS2KRNL cannot be found.                      */
;/*              The file OS2BOOT cannot be found.                      */
;/*              Insert a system diskette and restart the system.       */
;/*                                                                     */
;/*********************** END OF SPECIFICATIONS *************************/
BootCode segment        ;would like to use BootSeg here, but LINK flips its lid
        assume  cs:BootCode,ds:nothing,es:nothing,ss:nothing

        org     0               ; start at beginning of segment, not 0100h.

        public  _pinboot
_pinboot proc   far

        jmp     start
;
;       The following is the default BPB for Pinball hard disks.  It may
;       be modified by FORMAT or SYS before being installed on the disk.
;
;       Parameters such as Heads, SectorsPerTrack, and SectorsLong are
;       set up for a 20MB hard disk, so that a binary image of this boot
;       record may be written directly to a test hard disk without having
;       to reformat the drive.
;
Version                 db      "IBM 20.0"
BPB                     label   byte
BytesPerSector          dw      SECSIZE         ; Size of a physical sector
SectorsPerCluster       db      4               ; Sectors per allocation unit
ReservedSectors         dw      1               ; Number of reserved sectors
Fats                    db      2               ; Number of fats
DirectoryEntries        dw      0200h           ; Number of directory entries
Sectors                 dw      0               ; No. of sectors - no. of hidden sectors
Media                   db      0f8h            ; Media byte
FatSectors              dw      0029h           ; Number of fat sectors
SectorsPerTrack         dw      17              ; Sectors per track
Heads                   dw      4               ; Number of surfaces
HiddenSectors           dd      0011h           ; Number of hidden sectors
SectorsLong             dd      0a2c3h          ; Number of sectors iff Sectors = 0
;
; The following is the rest of the Extended BPB for the volume.
; The position and order of DriveNumber and CurrentHead are especially
; important, since those two variables are loaded into a single 16-bit
; register for the BIOS with one instruction.
;
PhysLogDrive    label   word            ; D1189
DriveNumber     db      80h             ; Physical drive number (0 or 80h)
CurrentHead     db      ?               ; Variable to store current head number

BootSignature   db      28h             ; Signature Byte for bootsector
BootID          dd      64d59c15h       ; Boot ID field.
Boot_Vol_Label  db      'C-DRIVE',0,0,0,0       ;volume label.
Boot_System_ID  db      'HPFS    '      ; Identifies the IFS that owns the vol.
;
; The following variables are not part of the Extended BPB;  they're just
; scratch variables for the boot code.
;
SectorBase      dd      ?               ; next sector to read
CurrentTrack    dw      ?               ; current track
CurrentSector   db      ?               ; current sector
SectorCount     dw      ?               ; number of sectors to read
lsnSaveChild    dd      ?               ; sector to continue directory search
NewSeg          dw      ?               ; para addr to relocate to
PhysLog         dw      0               ; D1189 (high=phys, low=logical)
;****************************************************************************
start:
;
;       First of all, set up the segments we need (stack and data).
;
        cli
        xor     ax, ax                  ; Set up the stack to just before
        mov     ss, ax                  ; this code.  It'll be moved after
        mov     sp, 7c00h               ; we relocate.
        sti

        mov     ax, Bootseg             ; Address our BPB with DS.
        mov     ds, ax
        assume  ds:BootCode
        mov     ax, PhysLogDrive        ; D1189  Save the physical
        mov     PhysLog, ax             ; D1189  and logical drive

;       Determine where we should load ourselves

        int     12h                     ; ax = Kbytes in low mem
        sub     ax, 04Ch                ; leave 64K for boot loader,
                                        ; 12K for boot code/micro-FSD
        and     ax, 0FFF0h              ; round down to multiple of 16
        shl     ax, 6                   ; convert Kbytes to paragraphs
        mov     NewSeg, ax

if TRACECHAR
        call    Trace$print             ; print out '0'
        inc     Trace$Char              ; DoReadLL will print out '1'
endif

;
;       Now read the 16-sector boot block into memory just below where
;       we'll load OS2KRNL.  Then jump to that new Boot_OEM of the boot
;       block, starting in the second sector (after the bootrecord sig).
;
        mov     SectorBase.lsw, 0       ; read sector zero.
        mov     SectorBase.msw, 0
        mov     word ptr [SectorCount], SEC_SUPERB+4 ; read boot/superblock.
        mov     ax, NewSeg              ; read it at NewSeg.
        mov     es, ax
        sub     bx, bx                  ; at NewSeg:0000.
        call    DoReadLL                ; Call low-level DoRead routine
;
if TRACECHAR
        call    Trace$print
endif

        push    NewSeg                  ; we'll jump to NewSeg:0200h.
        push    offset mainboot         ; (the second sector).
;
;       D1189 - The following 7 instructions are added to pass the physical
;               and logical drive and the hidden sector count to the new copy
;               of the pinboot code. This code only needs to be executed
;               when using multiboot, but to save save space (no test)
;               it is executed always.
;
        mov     ax,Physlog
        mov     es:Physlog, ax
        mov     es:DriveNumber, al          ; put physcal drive in dl
        mov     ax, HiddenSectors.lsw       ; passed from the MBB
        mov     es:HiddenSectors.lsw, ax    ;
        mov     ax, HiddenSectors.msw
        mov     es:HiddenSectors.msw, ax
;       D1189 - End of changes
        ret                             ; "return" to the second sector.
_pinboot endp

;*******************************************************************************
;
; Low-level read routine that doesn't work across a 64k addr boundary due
; to the limitations of the Model 30/286 DMA hardware.
;
;       Read SectorCount sectors (starting at SectorBase) to es:bx.
;
;       As a side effect, SectorBase is updated (but es:bx are not)
;       and SectorCount is reduced to zero.
;
DoReadLL proc
        push    ax                      ; save important registers
        push    bx
        push    cx
        push    dx
        push    es

DoRead$Loop:

; @90479 removed the following...
;
;        mov     ax, SectorBase.lsw      ; (DX:AX) = start sector of next track
;        mov     dx, SectorBase.msw
;        add     ax, HiddenSectors.lsw   ; adjust for partition's base sector
;        adc     dx, HiddenSectors.msw
;        div     SectorsPerTrack         ; (DX) = sector within track, (AX)=track
;if TRACECHAR
;        call    Trace$print
;endif
;
;        inc     dl                      ; sector numbers are 1-based, not 0
;        mov     CurrentSector, dl
;        xor     dx, dx                  ; prepare for 32-bit divide
;        div     Heads                   ; (DX) = head no., (AX) = cylinder
;if TRACECHAR
;        call    Trace$print
;endif
;
;        mov     CurrentHead, dl
;        mov     CurrentTrack, ax

; @90479 NEW code.  The old code used a divide that could overflow.  This
; code calculates the CYLINDER first, instead of the SECTOR, thereby avoiding
; an overflow.

        mov     ax, SectorBase.lsw      ; (DX:AX) = start sector of next track
        mov     dx, SectorBase.msw
        add     ax, HiddenSectors.lsw   ; adjust for partition's base sector
        adc     dx, HiddenSectors.msw
        push    ax                      ; save lsw of sector number
        mov     ax, Heads               ; get head count
        mul     byte ptr SectorsPerTrack ; AX = sectors per cylinder
        mov     cx, ax                  ; save result in cx
        pop     ax                      ; restore lsw of sector number
        div     cx                      ; ax=cyl number, dx=remainder
        mov     CurrentTrack, ax
        mov     ax,dx                   ; put remainder in ax
if TRACECHAR
        call    Trace$print
endif
        div     byte ptr SectorsPerTrack ; AL=heads, ah=remainder (sector)
        inc     ah                      ; sector numbers are 1-based, not 0
        mov     CurrentSector, ah
        mov     CurrentHead, al
if TRACECHAR
        call    Trace$print
endif

; @90479 ends.

; CurrentHead is the head for this next disk request
; CurrentTrack is the track for this next request
; CurrentSector is the beginning sector number for this request
;
; Compute the number of sectors that we may be able to read in a single ROM
; request.
;
        mov     ax, SectorsPerTrack             ; could read up to this much
        sub     al, CurrentSector               ; offset within this track
        inc     ax                              ; CurrentSector was 1-based
;
; AX is the number of sectors that we may read.
;
        cmp     ax, SectorCount                 ; do we need to read whole trk?
        jbe     DoRead$FullTrack                ; yes we do.
        mov     ax, SectorCount                 ; no, read a partial track.
if TRACECHAR
        inc     Trace$Char              ; partial track read - bump char
endif
;
; AX is now the number of sectors that we SHOULD read.
;
DoRead$FullTrack:
        push    ax                      ; save sector count for later calc.
        mov     ah, 2                   ; "read sectors"
        mov     dx, CurrentTrack        ; at this cylinder
        mov     cl, 6
        shl     dh, cl                  ; high 2 bits of DH = bits 8,9 of DX
        or      dh, CurrentSector       ; (DH)=cyl bits | 6-bit sector no.
        mov     cx, dx                  ; (CX)=cylinder/sector no. combination
        xchg    ch, cl                  ; in the right order
        mov     dx, word ptr DriveNumber        ; drive to read from, head no.
        int     13h                     ; call BIOS.

if TRACECHAR
        call    Trace$print
endif

        pop     ax
        jb      BootErr$he              ; If errors report
        add     SectorBase.lsw, ax      ; increment logical sector position
        adc     SectorBase.msw, 0
        sub     SectorCount, ax         ; exhausted entire sector run?
        jbe     DoRead$Exit             ; yes, we're all done.
        shl     ax, 9 - 4               ; (AX)=paragraphs read from last track
        mov     dx, es                  ; (DX)=segment we last read at
        add     dx, ax                  ; (DX)=segment right after last read
        mov     es, dx                  ; (ES)=segment to read next track at
        jmp     DoRead$Loop
;
DoRead$Exit:
if TRACECHAR
        inc     Trace$Char              ; read done - bump char
        call    Trace$print
endif

        pop     es
        pop     dx
        pop     cx
        pop     bx
        pop     ax
        ret

DoReadLL  endp

;****************************************************************************
;
; BootErr - print error message and hang the system.
;
BootErr proc

BootErr$CPS:
        mov     si,offset TXT_MSG_SYSINIT_CPS_ERROR +2
        jmp     short BootErr2
BootErr$fnf:
        mov     si,offset TXT_MSG_SYSINIT_FILE_NOT_FD +2
        jmp     short BootErr2
BootErr$he:
        mov     si,offset TXT_MSG_SYSINIT_BOOT_ERROR +2
BootErr2:
        call    BootErr$print
        mov     si,offset TXT_MSG_SYSINIT_INSER_DK +2
        call    BootErr$print
        sti
        jmp     $                       ;Wait forever

BootErr$print:
        lodsb                           ; Get next character
        cmp     al, 0
        je      BootErr$Done
        mov     ah,14                   ; Write teletype
        mov     bx,7                    ; Attribute
        int     10h                     ; Print it
        jmp     BootErr$print
BootErr$Done:
        ret
BootErr endp

if TRACECHAR
Trace$print proc
        push    si
        push    ax
        push    bx
        mov     si,offset TXT_MSG_TRACE_CHAR +2
        call    BootErr$print
        pop     bx
        pop     ax
        pop     si
        ret
Trace$print endp
endif ; TRACECHAR

;****************************************************************************

; include message needed in this sector

include pinboot.inc

if TRACECHAR

;   This character can be used (with Trace$print) to trace how far the boot
;   code runs.

TXT_MSG_TRACE_CHAR LABEL WORD
                DW      END_MSG_TRACE_CHAR - TXT_MSG_TRACE_CHAR
Trace$Char      DB      '0'                 ; initial value
END_MSG_TRACE_CHAR LABEL WORD
                DB              0
endif

;
;       Names of the files we look for.  Each consists of a length byte
;       followed by the filename as it should appear in a directory entry.
;
os2krnl db      7, "OS2KRNL"
os2ldr  db      6, "OS2LDR"
os2boot db      7, "OS2BOOT"


ReservedForFuture DB    7 dup(?) ;reserve remaining bytes to prevent NLS
                                 ;messages from using them

Free    equ SECSIZE-2-($-_pinboot)

if Free lt 0
    %out FATAL PROBLEM: first sector is too large
    %out Shrink boot code!!
endif

if Free gt 0
        db      Free dup(0)
endif

db      55h,0aah                ; boot record signature, required by the MBR.

; include the other messages - they don't have to be in the first sector

include pinboot2.inc

if TRACE
notfound        db      " Not"
foundit         db      " Found", 0DH, 0AH, 0
xchar           db      "?"
endif
;****************************************************************************
;
; mainboot -
;
mainboot proc   far
        mov     ax, cs                  ; Get the current CS (= NewSeg)
        mov     ds, ax                  ; Load DS with our new relocated sel
;
;  Relocate our stack from 7c00 to just below NewSeg:0000
;  This should work unless OS2LDR which is at 10000 or 1000:0000 needs space
;  below where the micro-FSD.
;
        mov     bx, 0FFFh               ; Stack ptr = top of 4k page
        sub     ax, 0100h               ; subtract 1 page from NewSeg(=CS =DS)
        cli                             ; Disable interrupts
        mov     ss, ax                  ; load stack selector with new value
        mov     sp, bx                  ; load new stack pointer
        sti                             ; enable interrupts

;       Save NewSeg again -- we lost that piece of data
;       when we relocated!
        mov     NewSeg, cs

;
;       First find the root FNODE on disk and read it into the
;       first scratch buffer
;
        mov     bx, SEC_SUPERB * SECSIZE + SUPERB_lsnRootFNode
        MOVEDD  SectorBase, [bx]        ; SectorBase = sblk.SUPERB_lsnRootFNode.

        mov     ax, 0
        call    ReadOneScratch          ; read root into first scratch sector

;
;       Now find the root DIRBLK on disk and save its address.
;
        mov     bx, ScrOfs
        MOVEDD  RootDB, [bx].FNODE_FST.FILESTORAGE_A.ALLEAF_lsnPhys ; RootDB = f.FNODE_FN_ALREC.ALLEAF_lsnPhys.

;
;       Load the mini-FSD at 07c0h.
;
        mov     si, offset os2boot      ; point to name of OS2BOOT.
        MOVEDD  SectorBase, RootDB      ; start at root dirblk

        call    FindFile                ; find OS2BOOT
        jnc     mbFoundMini             ; found it
        jmp     BootErr$fnf             ; OS2BOOT not found

mbFoundMini:
        mov     ax, mFSDSeg             ; load at this segment.
        mov     filetab.ft_mfsdseg, ax  ; store seg in resource tbl.

        call    LoadFile                ; load OS2BOOT
        MOVEDD  filetab.ft_mfsdlen, FileSize  ; store size in resource tbl.

;       This is where things get a little complicated.  If we're
;       booting OS/2 1.2, we want to load OS2KRNL at 10000h (and put
;       it's location info in the filetable in place of the micro-FSD
;       info) and load OS2LDR above it.  Otherwise, we load OS2LDR
;       at 10000h.
;

        call    IsItSloop
        jc      mbNotSloop

;       it's a 1.2 kernel, so we have to locate OS2KRNL and
;       load it at 10000h.  OS2LDR will go at the next 512
;       byte boundary after the kernel.
;
;       Note that, in Cruiser, the kernel's slot in the
;       resource table was taken over by the micro-FSD,
;       so we can just put the kernel info there (since
;       there is no micro-FSD in 1.2)

;       Load OS2KRNL at 10000h.
;
        mov     si, offset os2krnl      ; point to name of OS2KRNL.
        MOVEDD  SectorBase, RootDB      ; start at root dirblk

        call    FindFile
        jnc     mbFoundKernel
        jmp     BootErr$fnf             ; didn't find kernel

mbFoundKernel:

        mov     ax, KrnlSeg             ; load at this segment.
        mov     filetab.ft_museg, ax    ; store seg in resource table

        call    LoadFile                ; find it and load it.
        MOVEDD  filetab.ft_muLen, FileSize  ;store size in resource tbl

;       Now we need to compute LdrSeg, the paragraph address of the
;       next 512-byte boundary after the kernel in memory.

        add     FileSize.lsw, 512-1     ; round file size up by 1 sector
        adc     FileSize.msw, 0
        and     FileSize.lsw, not (512-1)

        shr     FileSize.lsw, 4         ; convert LSW bytes to paragraphs.
        shl     FileSize.msw, 12        ; convert MSW (64K's) to paragraphs.
        mov     ax, KrnlSeg             ; calculate OS2KRNL base address...
        add     ax, FileSize.msw        ; plus # of paragraphs in OS2KRNL...
        add     ax, FileSize.lsw        ; to get OS2LDR base address.

        mov     LdrSeg, ax
        jmp     mbLoadLoader


mbNotSloop:
        mov     ax, KrnlSeg             ; OS2LDR will be loaded right
        mov     LdrSeg, ax              ;   at 10000h

mbLoadLoader:

;       Load OS2LDR at LdrSeg:0
;

        mov     si, offset os2ldr       ; point to name of OS2LDR.
        MOVEDD  SectorBase, RootDB      ; start at root dirblk

        call    FindFile                ; find OS2LDR
        jnc     mbFoundLoader
        jmp     BootErr$fnf             ; OS2LDR not found

mbFoundLoader:

        mov     ax, LdrSeg
        mov     filetab.ft_loaderseg, ax ; OS2LDR base address
        push    ax                      ; save so we can jump to it.
        call    LoadFile                ; find it and load it.
        MOVEDD  filetab.ft_loaderlen, FileSize  ;store size in resource tbl.

        cmp     fIsSloop, 0
        jne     mbSloopExit

; We're not booting 1.2, so set up the micro-FSD information in
; the resource table

        mov     ax, NewSeg              ; location of muFSD
        mov     filetab.ft_museg, ax    ;
        mov     filetab.ft_muLen.msw, 0
        mov     filetab.ft_mulen.lsw, muLen ; length of muFSD is fixed

; Fill in the addresses of the four micro-FSD entry points

        mov     filetab.ft_muOpen.msw, cs
        mov     filetab.ft_muOpen.lsw, offset muOpen

        mov     filetab.ft_muRead.msw, cs
        mov     filetab.ft_muRead.lsw, offset muRead

        mov     filetab.ft_muClose.msw, cs
        mov     filetab.ft_muClose.lsw, offset muClose

        mov     filetab.ft_muTerminate.msw, cs
        mov     filetab.ft_muTerminate.lsw, offset muTerminate

;
; We've loaded all the files.  Jump to OS2LDR.  Note that OS2LDR's segment
; address was stored on the stack above, so all we need to push is the offset.
;
; Before we go to OS2LDR, set up the registers the way OS2LDR wants them:
;       DH = BF_MINIFSD + BF_MICROFSD
;       DL = INT 13 drive number we booted from
;       DS:SI -> the boot media's Extended BPB
;       ES:DI -> resource table
;

        mov     dx, PhysLog              ; D1189  Pass phys and log
        mov     PhysLogDrive, dx         ; D1189  drive to kernel
;       mov     dl, DriveNumber          ; D1189 done by above
        mov     dh, 0
        or      dh, BF_MINIFSD
        or      dh, BF_MICROFSD
        mov     si, offset BPB
        push    ds
        pop     es                      ; point to our resource table.
        mov     di, offset filetab
        sub     ax, ax                  ; jump to OS2LDR:0.
        push    ax
        ret                             ; "return" to OS2LDR.

;
;  This is the exit activity for 1.2:
;
;
; We've loaded all the files.  Jump to OS2LDR.  Note that OS2LDR's segment
; address was stored on the stack above, so all we need to push is the offset.
;
; Before we go to OS2LDR, set up the registers the way OS2LDR wants them:
;       DH = 00000100b (mini-FSD exists, local, use OS2LDR disk driver)
;       DL = INT 13 drive number we booted from
;       DS:SI -> the boot media's Extended BPB
;       ES:DI -> resource table
;

mbSloopExit:

        mov     dx, PhysLog              ; D1189  Pass phys and log
        mov     PhysLogDrive, dx         ; D1189  drive to kernel
;       mov     dl, DriveNumber          ; D1189 done by above
        mov     dh, 00000100b
        mov     si, offset BPB
        push    ds
        pop     es                      ; point to our resource table.
        mov     di, offset filetab
        sub     ax, ax                  ; jump to OS2LDR:0.
        push    ax
        ret                             ; "return" to OS2LDR.

mainboot endp

;****************************************************************************

;   WriteWord writes a word to the screen
;
;       ENTRY bx = word to write

WriteWord proc

    mov     cx, 4       ; 4 digits

WW1:
    mov     dx, bx
    and     dx, 0F000h  ; highest-order digit
    shr     dx, 12      ; move to lowest position

    cmp     dl, 9
    jg      WW2

    mov     al, '0'
    add     al, dl
    jmp     WW3

WW2:
    mov     al, 'A'
    sub     dx, 0Ah
    add     al, dl

WW3:
    pusha
    mov     ah, 14                  ;  write teletype
    mov     bx, 7                   ;  attribute
    int     10h
    popa

    nop

    shl     bx, 4                   ;  look at the next digit

    loop    WW1

    ret

WriteWord endp

;****************************************************************************

;   WriteLSN write SectorBase to the screen

WriteLSN proc

    pusha
    push    es
    push    ds

    mov     bx, SectorBase.msw
    call    WriteWord

    pusha

    mov     al, ':'
    mov     ah, 14                  ;  write teletype
    mov     bx, 7                   ;  attribute
    int     10h

    mov     al, ' '
    mov     ah, 14                  ;  write teletype
    mov     bx, 7                   ;  attribute
    int     10h

    popa
    nop

    mov     bx, SectorBase.lsw
    call    WriteWord


    pusha

    mov     al, 0Dh
    mov     ah, 14                  ;  write teletype
    mov     bx, 7                   ;  attribute
    int     10h

    mov     al, 0Ah
    mov     ah, 14                  ;  write teletype
    mov     bx, 7                   ;  attribute
    int     10h

    popa
    nop

    pop     ds
    pop     es
    popa
    nop

    ret

WriteLSN endp

;****************************************************************************

DebugPoint proc

    pusha

        mov     si,offset TXT_MSG_SYSINIT_FILE_NOT_FD +2
        call    BootErr$print

    popa
    nop
    ret

DebugPoint endp

;****************************************************************************

DebugPoint2 proc

    pusha

        mov     si,offset TXT_MSG_SYSINIT_DEBUG2 +2
        call    BootErr$print

    popa
    nop
    ret

DebugPoint2 endp

;****************************************************************************
;
; DoRead - read SectorCount sectors into ES:BX starting from sector
;          SectorBase.
;
; NOTE: This code WILL NOT WORK if ES:BX does not point to an address whose
; physical address (ES * 16 + BX) MOD 512 != 0.
;
; DoRead adds to ES rather than BX in the main loop so that runs longer than
; 64K can be read with a single call to DoRead.
;
; Note that DoRead (unlike DoReadLL) saves and restores SectorCount
; and SectorBase
;
DoRead  proc
        push    ax                      ; save important registers
        push    bx
        push    cx
        push    dx
        push    es
        push    si
        push    di
        push    bp
        push    SectorCount             ; save state variables too
        push    SectorBase.lsw
        push    SectorBase.msw

DoRead1:
;
; Calculate how much we can read into what's left of the current 64k
; physical address region.
;
;       SectorCount = remaining sectors in request
;       es:bx = destination address

        mov     ax, SectorCount
        cmp     ax, 0

        jne     DoRead2
        jmp     DoRead$Done             ; all done!


DoRead2:
        mov     dx, es
        shl     dx, 4
        add     dx, bx                  ; dx = (es * 16 + bx) MOD 64K

        xor     ax, ax
        sub     ax, dx                  ; ax = 64K - dx

;       if ax = 0, request is 64K-aligned, which means we
;       can read 64K, or 80h sectors.

        cmp     ax, 0
        jne     DoRead3a

        mov     ax, 80h             ;  Read up to 80h sectors (64K)
        jmp     doRead3

DoRead3a:
        shr     ax, SECSHIFT        ;  maximum number of sectors to
                                    ;  read at once
DoRead3:

;       ax      = max number of sectors to read at once
;       es:bx   = destination address
;       SectorCount = number of sectors remaining in request
;

        push    SectorCount

        cmp     ax, SectorCount
        jg      DoRead4
        mov     SectorCount, ax

DoRead4:
;       SectorCount = number of sectors to read on this pass
;       es:bx   = destination address
;       TOS     = sectors remaining in request (incl. this pass)
;       SectorBase = first sector to read

        push    SectorBase.msw
        push    SectorBase.lsw
        push    SectorCount
        push    es
        push    bx

        call    DoReadLL

        pop     bx
        pop     es                  ;  es:bx = target address
        pop     ax                  ;  ax = sectors read (was SectorCount)
        pop     SectorBase.lsw
        pop     SectorBase.msw

        add     SectorBase.lsw, ax
        adc     SectorBase.msw, 0   ;  update SectorBase

        mov     cx, ax              ;  cx = sectors read

        shl     ax, SECSHIFT - 4    ;  paragraphs read
        mov     dx, es
        add     dx, ax
        mov     es, dx              ;  update es:bx

        pop     SectorCount
        sub     SectorCount, cx     ;  number of sectors remaining in request

;
;       SectorBase  = next LSN to read
;       SectorCount = sectors remaining in request
;       es:bx = user's buffer
;
;       We now are at the sector which may straddle a 64K boundary,
;       If it does, we can't read it directly into the user's buffer.
;
;       Instead, we'll read it into the fifth scratch sector, and
;       then copy the data to the user's buffer.

        cmp     SectorCount, 0
        je      DoRead$Done

;       if es:bx is sector-aligned, we don't do anything here.

        mov     ax, es
        shl     ax, 4
        add     ax, bx              ;  ax = (ES*16 + BX) MOD 64K

        and     ax, SECMSK
        cmp     ax, 0
        je      DoRead1             ;  es:bx is sector-aligned

        push    es
        push    bx
        push    SectorBase.lsw
        push    SectorBase.msw
        push    SectorCount

        mov     ax, 4               ;  use 5th scratch buffer

        call    ReadOneScratch

        pop     SectorCount
        pop     SectorBase.msw
        pop     SectorBase.lsw
        pop     bx
        pop     es

        mov     di, bx              ;  es:di = user's buffer
        mov     si, ScrOfs + 512 * 4;  ds:si = 5th scratch buffer

        mov     cx, SECSIZE
        cld                         ;  clear direction flag--work upward
        rep     movsb               ;  copy one sector's worth of bytes

        dec     SectorCount         ;  one less sector to read

        add     SectorBase.lsw, 1
        adc     SectorBase.msw, 0   ;  SectorBase += 1

        mov     ax, SECSIZE
        shr     ax, 4
        mov     dx, es
        add     dx, ax
        mov     es, dx              ;  es:bx = address for next chunk of read


        jmp     DoRead1


DoRead$Done:
        pop     SectorBase.msw
        pop     SectorBase.lsw
        pop     SectorCount
        pop     bp
        pop     di
        pop     si
        pop     es
        pop     dx
        pop     cx
        pop     bx
        pop     ax

        ret

DoRead  endp
;****************************************************************************
;
; ReadScratch - reads a block of 4 sectors into the scratch area.
;
;       ENTRY:  SectorBase = LSN to read.
;
;       EXIT:   4 sectors at AX read at DS:ScrOfs
;
;       USES:   all
;
ReadScratch proc near

        pusha

        push    es
        push    ds

        mov     word ptr SectorCount, 4         ; read 4 sectors.
        push    ds                              ; address scratch area.
        pop     es
        mov     bx, ScrOfs                      ; with ES:BX.
        call    DoRead

        pop     ds
        pop     es

        popa
        nop

        ret
ReadScratch endp

;****************************************************************************
;
; ReadOneScratch - reads a sector into one of the scratch sectors
;
;       ENTRY:  SectorBase = LSN to read.
;               ax         = sector offset in scratch buffer (zero-based)
;
;       EXIT:   1 sector read at DS:ScrOfs + 512 * sector offset
;
;       USES:   all
;
ReadOneScratch proc near

        push    es
        push    bx

        mov     word ptr SectorCount, 1 ; read 1 sector.
        push    ds                      ; buffer is in our data seg...
        pop     es                      ; ...so we copy ds to es.
        mov     bx, ScrOfs              ; ES:BX addr of scratch buffer
        shl     ax, SECSHIFT
        add     bx, ax                  ; ES:BX = addr of nth scratch sector
        call    DoRead

        pop     bx
        pop     es
        ret
ReadOneScratch endp


;****************************************************************************
;
; FindFile - finds a file in the root directory
;
;       ENTRY:  DS:SI -> name of file to find.
;               SectorBase = LSN of first DirBlk to read
;
;       EXIT:   If found:
;                 'C' clear
;                 ES:BX -> dirent of file
;                 SectorBase = lsn of current DirBlk
;                       (for next directory search)
;
;                If not found:
;                 'C' set
;                 ES:BX = NULL
;
;       USES:   all
;
FindFile proc   near

        push    ds
        pop     es                      ; address data with ES too.

        push    si

        call    ReadScratch             ; read DirBlk (SectorBase already set)

        pop     di                      ; es:di -> search name (count at front)

        sub     cx, cx                  ; prepare to store name length.
        mov     cl, [di]                ; fetch the length byte.
        inc     di                      ; and skip to the name.
                                        ; es:di -> search name
  if TRACE
        mov     si,di                   ; print out search name
        call    TracePrint
        mov     al, ' '                 ; print space after name
        call    TracePrint1
  endif
        mov     dx, cx                  ; dx = search name count (saved)

ff1:    mov     bx,DIRBLK_bFirst + ScrOfs      ; point to first DIRENT, in scratch.
        jmp     short ff12
;
;       bx -> last entry examined
;       cx =  length of the name we're looking for
;       di -> name we're looking for, without the count byte ("search name")
;
ff10:
        add     bx, [bx].DIRENT_cchThisEntry    ; move to next entry.

ff12:
        mov     bp, di                  ; save search name address.

  if TRACE
        push    cx
        mov     cl, [bx].DIRENT_cchName
        lea     si,[bx].DIRENT_bName    ; print out DirEnt name
        call    TracePrint
        pop     cx
  endif

        mov     cx, dx                  ; reload search name length.

        cmp     cl, [bx].DIRENT_cchName ; check if equal length
        jne     ff20                    ; not equal - no point comparing

        lea     si, [bx].DIRENT_bName   ; ds:si -> current DIRENT name.

  if TRACE
        mov     al, ' '                 ; lengths match - print space
        call    TracePrint1
  endif

  ff14: lodsb                           ; al = char from DIRENT name

        cmp     al, 'a'                 ; check for lower-case a
        jb      ff15                    ; less than 'a' - don't upcase

        cmp     al, 'z'                 ; check for lower-case z
        ja      ff15                    ; greater than 'z' - don't upcase

        xor     al,'a'-'A'              ; convert to upper-case

  ff15:

  if TRACE
        push    si

        push    ax
        lea     si, [di]                ; print out byte from search name
        mov     al, [di]
        call    TracePrint1
        pop     ax

        call    TracePrint1             ; print out byte from DirEnt name
        pop     si
  endif

        scasb                           ; compare to search name
        loope   ff14                    ; loop on match

        jne     short ff20              ; no match - search on

  if TRACE
        mov     si, offset foundit      ; print 'found' message
        push    bx
        call    BootErr$print
        pop     bx
  endif

        clc                             ; return success
        ret                             ; equal - Found the file


;       Names don't match.  If the current entry has a downpointer,
;       search it.
;
ff20:

if TRACE
        mov     al, ' '                 ; names did not match - print space
        call    TracePrint1
endif
        mov     di, bp                  ; restore search name address.

        test    byte ptr [bx].DIRENT_fFlags, DF_BTP
        jz      ff30                    ; no downpointer, check for end

;       Follow the DownPointer.
;       Load the child DIRBLK and search it.
;
        add     bx, [bx].DIRENT_cchThisEntry    ; move to next entry.
        MOVEDD  SectorBase, [bx-4]      ; fetch last 4 bytes of prev entry.
        call    ReadScratch             ; read child DIRBLK
        jmp     short ff1               ; search this dirblk

;
;   We don't have a downpointer.
;   If this is the end entry in the dirblk, then we have to go up to the parent,
;   if any.

ff30:   test    byte ptr [bx].DIRENT_fFlags, DF_END
        jz      ff10                    ; not end of dirblk - check next DirEnt
;
;       Check to see if we have a parent (not the top block).  If so, read
;       the parent dirblk and find the downpointer that matches the current
;       sector.  Then continue searching after that point.
;
        mov     bx, ScrOfs              ; point to dirblk header
        test    byte ptr [bx].DIRBLK_culChange, 1    ; 1 means top block
        jz      ff40                        ; not top, continue with parent
        jmp     FileNotFound                ; top block - not found

;
;       read in parent dirblk and find the dirent with this downpointer -
;       then continue after that point
;
ff40:   MOVEDD  lsnSaveChild, SectorBase    ; save this sector number
        MOVEDD  SectorBase, [bx].DIRBLK_lsnParent
        call    ReadScratch             ; read the parent

        mov     bx, DIRBLK_bFirst + ScrOfs      ; start at first entry of child
        push    di                      ; save offset to search name
        jmp     short ff44

;       find our current downpointer

ff42:   add     bx, di                  ; move to the next dirent

ff44:   mov     di, [bx].DIRENT_cchThisEntry    ; downptr is 4 bytes from end of dirent
        mov     ax, [bx+di-4].lsw
        cmp     ax, lsnSaveChild.lsw    ; compare low 2 bytes
        jne     ff42                    ; not equal, try next DirEnt
        mov     ax, [bx+di-4].msw
        cmp     ax, lsnSaveChild.msw    ; compare high 2 bytes
        jne     ff42                    ; not equal, try next DirEnt

        pop     di                      ; di = offset to search name
        jmp     ff30                    ; continue from here

FindFile endp

FileNotFound:

if TRACE
        mov     si,offset notfound      ; print not found message
        call    BootErr$print
endif

        xor     bx, bx
        mov     es, bx      ; es:bx = NULL
        stc                 ;  Set 'C' to indicate error
        ret


;****************************************************************************
;
; LoadFile - reads file in at the specified segment.
;
;       ENTRY:  ES:BX -> dirent of file to load
;               AX    =  segment address to load at.
;
;       USES:   all
;
LoadFile proc   near
        push    ax                      ; save segment to load at.
;
;       Here, we have found the file we want to read.  Fetch relevant info
;       out of the DIRENT:  the file's FNODE number and its size in bytes.
;
        sub     bp, bp                          ; a zero register is handy.
        MOVEDD  FileSize, [bx].DIRENT_cchFSize          ; get file size
        MOVEDD  SectorBase, [bx].DIRENT_lsnFNode                ; prepare to read FNODE
        call    ReadScratch                     ; read in the FNODE
;
        pop     es                              ; restore segment to read at.
        mov     si, ScrOfs + FNODE_fst.FILESTORAGE_a    ; address the FNODE's array.
        mov     bx, ScrOfs + FNODE_fst.FILESTORAGE_alb  ; address the FNODE's ALBLK.

lf_go:
        test    byte ptr [bx].ALBLK_bFlag, ABF_NODE ; are records nodes?
        jnz     lf_donode                       ; yes, go get a child.
;
;       Here, we have a leaf block.  Loop through the ALLEAF records,
;       reading each one's data run.
;
        mov     cl, [bx].ALBLK_cUsed            ; get count of leaf records.
        mov     ch, 0                           ; zero-extend.
lf_loop:
        MOVEDD  SectorBase, [si].ALLEAF_lsnPhys         ; load run start.
        mov     ax, word ptr [si].ALLEAF_csecRun        ; load run length.
        mov     SectorCount, ax
        push    bx                              ; save ALBLK pointer.
        sub     bx, bx                          ; read at ES:0000.
        call    DoRead
        pop     bx                              ; restore ALBLK pointer.
        mov     ax, es                          ; get segment we just used
        shl     SectorCount, 9 - 4              ; cvt sectors to paragraphs
        add     ax, SectorCount                 ; get new segment address
        mov     es, ax                          ; store new segadr in ES
        add     si, size ALLEAF                 ; point to next leaf
        loop    lf_loop                         ; go get another run
;
;       Here, we've exhausted an array of records.  If we exhausted the
;       FNODE, we're done.  Otherwise, we re-read our parent block, restore
;       where we were in it, and advance to the next record.
;
lf_blockdone:
        cmp     word ptr ds:[ScrOfs+FNODE_sig+2], FNSIGVAL shr 16  ; in FNODE?
        je      lf_alldone                      ; yes, we've read the whole file.
        MOVEDD  SectorBase, ds:[ScrOfs+ALSEC_lsnRent] ; fetch parent sector pointer.
        call    ReadScratch                     ; read in our parent.
        pop     si                              ; restore where we left off.
        pop     bx                              ; restore ALBLK pointer.
        add     si, size ALNODE                 ; move to next node.
;
;       Here the block contains downpointers.  Read in the next child
;       block and process it as a node or leaf block, saving where we were
;       in the current block.
;
lf_donode:
        mov     al, [bx].ALBLK_cUsed            ; get number of records.
        mov     ah, 0                           ; zero-extend.
        shl     ax, 3                           ; (AX)=size of array.
        add     ax, bx
        add     ax, size ALBLK                  ; (AX)->after end of array.
        cmp     si, ax                          ; are we done?
        jae     lf_blockdone                    ; yes, we've exhausted this blk.
        push    bx                              ; save ALBLK offset.
        push    si                              ; save current record offset.
        MOVEDD  SectorBase, [si].ALNODE_lsnPhys         ; get child downpointer.
        call    ReadScratch                     ; read the child ALSEC.
        mov     si, size ALSEC + ScrOfs         ; address the ALSEC's array.
        mov     bx, ALSEC_alb + ScrOfs          ; address the ALSEC's ALBLK.
        jmp     short lf_go
;
;       All done, return to caller.
;
lf_alldone:
        ret
LoadFile endp

;******************************************************************************

include  pbexehdr.inc

;
;       IsItSloop -- determines if the OS2KRNL on the disk is for 1.2
;
;       If the OS2KRNL on the disk is a 16-bit new exe, we want
;       to follow the 1.2 boot process;  this procedure determines
;       if this is indeed the case.  Note that although we locate
;       OS2KRNL now, we throw that information away.  (If we're
;       booting 1.2, we'll need to locate it again, but we don't
;       save the DIRENT, which has some information we need.)
;
;       Reads the FNODE into the first scratch buffer, and uses
;       the sixth scratch buffer as a work area.
;
;       ENTRY:  none
;
;       EXIT:
;         if OS2KRNL is a 16-bit exe
;           'C' clear
;           fIsSloop is set to 1
;         else
;           'C' set
;           fIsSloop is set to 0
;
IsItSloop proc near

        mov     si, offset os2krnl
        MOVEDD  SectorBase, RootDB

        call    FindFile            ; look for OS2KRNL
        jnc     iisCheckKernel
        jmp     iisNo               ; didn't find it--can't be sloop.

iisCheckKernel:

;       ES:BX -> file's DIRENT;
;       DS is our segment
;       all other registers roached.
;
;       store the file's size and read its FNODE into the
;       first scratch sector, so we can call muRead.
;
        MOVEDD  FileSize, es:[bx].DIRENT_cchFSize
        MOVEDD  SectorBase, es:[bx].DIRENT_lsnFNode

        mov     ax, 0                   ;  Read to first scratch sector
        call ReadOneScratch

;       Read the first sector of OS2KRNL into the sixth
;       scratch buffer.
;
        push    0                       ;  number of bytes to read
        push    512                     ;    is 512
        push    ds                      ;  read into sixth
        push    ScrOfs+5 * SECSIZE      ;    scratch sector
        push    0
        push    0                       ;  ulSeek = 0L

;       The call to muRead is sleazy--since we relocated, we
;       can't just make a far call, so we simulate one using
;       a near call.

        push    cs
        call    NEAR PTR MuRead         ;  Do the read.
        add     sp, 0Ch                 ;  12 bytes of parameters

;       We assume that the read succeeded.  If it didn't,
;       we'll find out later in the boot process.
;
;       Inspect the e_magic field--if it's an exe, this must be EMAGIC

        mov     bx, ScrOfs+5 * SECSIZE
        cmp     [bx].e_magic, EMAGIC
        jne     iisNo                   ;  It's not an exe

        push    0                       ;  number of bytes to read
        push    512                     ;    is 512
        push    ds                      ;  read into sixth
        push    ScrOfs+5 * SECSIZE      ;    scratch sector
        push    [bx].e_lfanew.msw       ;  seek position is offset
        push    [bx].e_lfanew.lsw       ;    of new exe header

        push    cs                      ;  MuRead uses retf.  Sleazy...
        call    NEAR PTR MuRead         ;  Do the read.
        add     sp, 0Ch                 ;  12 bytes of parameters

;       We assume that the read succeeded.  If it didn't,
;       we'll find out later in the boot process.
;
;       Inspect the ne_magic field--if this is a 16-bit
;       new exe, this field will have the value NEMAGIC.

        mov     bx, ScrOfs+5 * SECSIZE
        cmp     [bx].ne_magic, NEMAGIC
        jne     iisNo                   ;  It's not a 16-bit new exe

;       We've determined that the OS2KRNL on the disk is a 16-bit
;       new exe--i.e. that it's v1.2 kernel.

        mov     fIsSloop, 1

        clc
        ret

iisNo:
        mov     fIsSloop, 0
        stc
        ret

IsItSloop   ENDP


;******************************************************************************

LdrSeg  dw      0                       ; paragraph addr of OS2LDR

fIsSloop dw     0

RootDB  dd      ?                       ; LSN of root DIRBLK.

Flag    db      ?                       ; used to store ALBLK_bFlag.

AllocInfo       db      size ALLEAF * LEAFPERFNODE dup (0) ; copy of FNODE alloc info.

FileSize        dd      ?               ; size of file that was read.

include ..\..\..\bios\filetab.inc

filetab         filetable_s     <3,0,0,0,0,0,0,0,0,0,0,0,0>

;************************************************************
;
;
;   Micro-FSD entry points
;
;   The micro-FSD provides four services to the boot loader:
;
;   muOpen -- open a file
;   muRead -- read from the last file opened
;   muClose -- close the last file opened
;   muTerminate -- die gracefully
;
;   For the pinball micro-FSD, the last two are no-ops.
;
;   The micro-FSD assumes that the boot code has filled
;   in RootDB and the Boot Parameter Block.
;
;
;************************************************************
;
;       BytesToSectors -- convert a byte count to a sector count
;
;
;       ENTRY  dx:ax is a byte count
;       EXIT   dx:ax is a sector count (byte count / SECSIZE)
;
;       USES   ax, cx, dx

BytesToSectors proc near


;       shift dx:ax right SECSHIFT bits

        push    cx

        mov     cx, SECSHIFT    ; we need to shift dx:ax this many bits

bytosec$Loop:
        shr     dx, 1
        rcr     ax, 1
        loop    bytosec$Loop

        pop     cx

        ret

BytesToSectors ENDP


;************************************************************
;
;   muOpen -- open a file
;
;
;   Locates and reads the FNODE for the specified file
;   so that muRead can read its data.
;
;   Note that the file name will have a-z up-cased (no other
;   case-mapping is done).
;
;   The file MUST be in the root directory.
;
;       ENTRY:
;
;           TOS + 8     far pointer to file size (return value)
;           TOS + 4     far pointer to name of file
;           TOS         return address
;
;       EXIT:
;           FNODE is read into first sector of Scratch area
;
;
muOpen proc far

        push    bp
        mov     bp,sp

        push    ds              ; save the caller's DS
        push    si
        push    di

        mov     ax,cs
        mov     ds,ax           ; and load ours.

;       Copy the name of the file to our data segment.
;       We'll put it in the sixth scratch sector

;       Find out how long the name is:
        les     di,ss:[bp+6]    ;  es:di points at first character
        mov     bx,di           ;  es:bx points at first character
        mov     cx,200h         ;  name can't be longer than this

        mov     ax,0            ;  we'll search for null
        repnz   scasb           ;  es:di now points at byte after first null

        dec     di              ;  es:di now points at first null
        sub     di,bx           ;  di = length of string
        mov     cx,di           ;  cx = length of name


;       OK, we know how long the name is--let's copy it
;       cx = number of bytes to copy

        push    ds                  ;  save ds

        mov     bx, ds
        mov     es, bx              ;  destination for copy is...
        mov     di,ScrOfs+(512*5)   ;  ...the 6th scratch sector

        lds     si,ss:[bp+6]        ;  source
        mov     es:[di],cl          ;  store the length byte
        inc     di                  ;  copy to byte after length byte

        rep     movsb               ;  copy the name

        pop     ds                  ;  restore ds


;       Set up the parameters for FindFile, and call it

        mov     si,ScrOfs+(512*5)   ;  ds:si points at name to find
        MOVEDD  SectorBase,RootDB

        push    ds
        push    bp

        call    FindFile

        pop     bp
        pop     ds

        jnc     muopFound               ; found the file
        mov     ax, 5                   ; non-zero for failure
        jmp     muOpenExit              ; return error


muopFound:

;       ES:BX -> file's DIRENT;
;       DS is our segment
;       all other registers roached.
;
;       Get the file size and the FNODE's lsn out
;       of the dirent, and read the FNODE into the
;       first scratch sector.

        MOVEDD  FileSize, es:[bx].DIRENT_cchFSize
        MOVEDD  SectorBase, es:[bx].DIRENT_lsnFNode

        mov     ax, 0                   ;  Read to first scratch sector

        call ReadOneScratch

;
;       All the registers are roached, but the
;       stack should be OK, and all the things we
;       need are in global variables at this point.
;
        les     bx,ss:[bp+0Ah]          ;  Address of return parameter
        MOVEDD  es:[bx], ds:FileSize    ;  Give caller the file size
        mov     ax, 0                   ;  return NO ERROR

muOpenExit:
;       ax = return value (zero indicates success)
;       restore what we saved

        pop     di
        pop     si
        pop     ds
        pop     bp
        ret

muOpen ENDP



;************************************************************
;
;   muCPS -- Compute Physical Sector
;
;   ENTRY:
;       FNode is in first scratch sector
;       DX:AX = file seek position
;
;   EXIT:
;       DX:AX   = first LSN
;       SI:BX   = remaining run length (including first sector)
;
;
;
TXT_MSG_SYSINIT_CPS_ERROR       LABEL   WORD
                DW      END_MSG_SYSINIT_CPS_ERROR - TXT_MSG_SYSINIT_CPS_ERROR - 2
                DB      'The allocation for a '
                DB      'kernel file is corrupt.'
                DB      0DH, 0AH
END_MSG_SYSINIT_CPS_ERROR       LABEL   WORD
                DB              0

muCPS proc near

;       convert seek offset to a sector offset

        call    BytesToSectors

;       dx:ax = file sector number we want

;       Find the storage records in the FNODE

        mov     bx,ScrOfs               ;  bx points at FNODE
        lea     bx,FNODE_fst.FILESTORAGE_alb[bx]            ;  bx points at ALBLK

;       We have an allocation block, followed by leaf or node
;       entries.  (Doesn't matter whether it's an FNODE or ALSEC--
;       the structure is sufficiently similar.)  Check to see if
;       the allocation entries are leaves or nodes
muCPS1:
        test ALBLK_bFlag[bx],ABF_NODE
        jnz     muCPS6                  ;  the entries are nodes


muCPS2:
;       We have an allocation block containing leaf records.
;       Search it for the one we want.
;
;       bx    = address of allocation block
;       dx:ax = file sector number

        mov     cl, ALBLK_cUsed[bx]             ;  cx = count of entries used
        xor     ch,ch

        cmp     cx,0
        je      muCPSErr                ;  No entries--unrecoverable error

        dec     cx                      ;  cx = count of entries - 1
        add     bx,size ALBLK           ;  bx points at 1st leaf

        push    bp                      ;  save bp

        cmp     cx, 0
        je      muCPS5                  ;  There's only one leaf


muCPS3:
        ;  compare file sector number with the _next_ leaf
        ;  entry's LOF.  If the file sector number is the
        ;  lesser of the two, the current leaf is the extent
        ;  we want.  Otherwise, advance to the next leaf and
        ;  try again.  We'll advance up to OCNT-1 times,
        ;  which means we take the last leaf by default.
        ;
        ;  dx:ax = file sector number
        ;  ds:bx points at current leaf
        ;  TOS = saved value of bp
        ;
        ;  This loop uses BP as a base-register, which compromises
        ;  its use as the base pointer for the stack frame.
        ;  The value will be restored after this loop is done.


        mov     bp, bx                  ;
        add     bp, size ALLEAF         ;  ds:bp points at _next_ leaf
        cmp     dx, WORD PTR ds:[bp+2].ALLEAF_lsnLog; compare high-order word
        jb      muCPS5                  ;  current extent is the one we want
        ja      muCPS4                  ;  try next leaf

        cmp     ax, WORD PTR ds:[bp].ALLEAF_lsnLog ;    compare low-order word
        jb      muCPS5                  ;  found our leaf!

muCPS4: add     bx, size ALLEAF         ;  advance to the next leaf
        loop    muCPS3


;       OK, we've found the leaf we want.
;       dx:ax = file sector number
;       bx points at allocation leaf we want
;
;       Compute the physical sector number and remaining
;       run length.

muCPS5:

        pop     bp                      ;  restore stack frame base pointer

;       first LSN = ALLEAF_lsnPhys + (file sector number - ALLEAF_lsnLog)
;       rem. run length = ALLEAF_csecRun - (file sector number - ALLEAF_lsnLog)

        mov     di, WORD PTR ALLEAF_lsnLog[bx+2]        ;  high order word
        mov     cx, WORD PTR ALLEAF_lsnLog[bx]          ;  low order word
        sub     ax,cx
        sbb     dx,di                   ;  dx:ax = sector offset

        mov     cx,ax
        mov     di,dx                   ;  di:cx = sector offset

        mov     dx, WORD PTR ALLEAF_lsnPhys[bx+2]       ;
        mov     ax, WORD PTR ALLEAF_lsnPhys[bx]         ;  dx:ax = ALLEAF_lsnPhys
        add     ax,cx
        adc     dx,di                   ;  dx:ax = sector number desired

        mov     si, WORD PTR ALLEAF_csecRun[bx+2]
        mov     bx, WORD PTR ALLEAF_csecRun[bx]         ;  si:bx = ALLEAF_csecRun

        sub     bx, cx
        sbb     si, di                  ;  si:bx = remaining run length

        ret

;       The allocation block in hand contains nodes, rather
;       than leaves.  Find the first node with a ALNODE_lsnLog field
;       which is greater than the desired file sector number
;
;       ds:bx = address of allocation block
;       dx:ax = file sector number

muCPS6:
        mov     cl, ALBLK_cUsed[bx]             ;  cx = number of entries
        xor     ch,ch
        add     bx, size ALBLK          ;  bx points at first node entry

muCPS7: cmp     dx, WORD PTR ALNODE_lsnLog[bx+2]        ;  high order word
        jb      muCPS9                  ;  file lsn < ALNODE_lsnLog -- take it!
        ja      muCPS8                  ;  file lsn > ALNODE_lsnLog -- try next
        cmp     ax, WORD PTR ALNODE_lsnLog[bx]          ;  low-order word
        jb      muCPS9                  ;  file lsn < ALNODE_lsnLog -- take it!

muCPS8: add     bx, size ALNODE         ;  try next entry
        loop    muCPS7

        jmp     muCPSErr                ;  Didn't find it!


;       Read the child of this node into second scratch sector
;
;       dx:ax = file sector number
;       bx points at node entry to pursue
muCPS9:
        push    dx
        push    ax

        MOVEDD  SectorBase, ALNODE_lsnPhys[bx]  ;  this is the sector to read
        mov     ax, 1                   ;  read to second scratch sector

        call    ReadOneScratch          ;  read a sector to scratch buffer

        pop     ax
        pop     dx
        mov     bx, ScrOfs + 512        ;  bx points at ALSEC
        lea     bx, WORD PTR ALSEC_alb[bx]              ;  bx points at ALBLK
        jmp muCPS1


muCPSErr:
        mov     si,offset TXT_MSG_SYSINIT_CPS_ERROR +2
        jmp     BootErr2


muCPS ENDP


;************************************************************
;
;   muRead -- Read data from the last file opened
;
;   Note that the FNODE of the file to be read must be
;   in the first scratch sector, and FileSize must contain
;   the size (in bytes) of the file.  muOpen will set these
;   global data items.
;
;
;   ENTRY:
;       TOS + 12    ULONG number of bytes to read
;       TOS + 8     far pointer to read destination
;       TOS + 4     ULONG  file seek position
;       TOS         return address
;
;   EXIT:
;       AX = number of bytes read (-1 if error)
;
;
muRead proc far

        push    bp
        mov     bp,sp

        push    ds              ; save the caller's DS
        push    si
        push    di

        mov     ax,cs
        mov     ds,ax           ; and load ours


;       Normalize user's address so we don't accidentally
;       roll the offset over

        mov     es, [bp+0Ch]
        mov     bx, [bp+0Ah]    ; es:bx = user's target address

        mov     cx, bx
        shr     cx, 4           ; divide cx by 16--paragraphs in offset
        mov     ax, es          ; paragraphs in segment
        add     ax, cx          ; this many paragraphs total
        mov     es, ax
        and     bx, 0Fh         ; es:bx has been normalized

        mov     [bp+0Ch],es
        mov     [bp+0Ah],bx     ; user's target address is normalized


;       make sure we don't read past EOF!
;
        mov     ax, FileSize.lsw
        mov     dx, FileSize.msw    ; dx:ax = file size

        mov     bx, [bp+6]
        mov     cx, [bp+8]          ; cx:bx = seek position

        sub     ax, bx
        sbb     dx, cx              ; dx:ax = bytes in file past seek pos.

        jnc     muRead1             ; seek position is not past EOF
        jmp     muRead$EOF          ; seek position is past EOF

;       dx:ax = bytes in file past seek pos.
muRead1:
        mov     bx, [bp+0eh]
        mov     cx, [bp+10h]        ; cx:bx = request amount

        sub     bx, ax
        sbb     cx, dx
        jc      muRead2             ; enough in file to satisfy request

        mov     [bp+0Eh], ax
        mov     [bp+10h], dx        ; truncate request to remainder of file

muRead2:

;       We'll also reserve two words on the stack for the
;       return value--initially zero.

        xor     ax, ax
        push    ax
        push    ax

;       First, we need to handle a non-sector-aligned leader

        mov     bx, [bp+6]      ;  bx = lsw of seek position
        mov     ax, bx
        and     ax, SECMSK      ;  ax = cbytes % SECSIZE

        mov     cx, [bp+0Eh]
        mov     dx, [bp+10h]    ;  dx:cx = number of bytes to read

        cmp     ax, 0
        jz      muRead5         ; request is sector-aligned

;       We have a non-sector-aligned leader.  We'll handle
;       it by reading the sector that contains the unaligned
;       portion into an intermediate buffer (the third scratch
;       buffer) and then copying the part we want.
;
;       dx:cx = number of bytes to read
;       ax = seek pos % SECSIZE
;       TOS = number of bytes read so far (zero) [msw]
;       TOS+2 = number of bytes read so far (zero) [lsw]


        push    cx
        push    dx
        push    ax

        mov     dx, [bp+8]
        mov     ax, [bp+6]      ; dx:ax = file seek position

        push    ax
        push    dx

;       dx:ax = file seek position
;       TOS   = file seek position [msw]
;       TOS+2 = file seek position [lsw]
;       TOS+4 = offset in sector of first byte of request
;       TOS+6 = remaining request length [msw]
;       TOS+8 = remaining request length [lsw]
;       TOS+A = number of bytes read so far (still zero) [msw]
;       TOS+C = number of bytes read so far (still zero) [lsw]

        call    muCPS

;       DX:AX   = LSN of sector to read
;
;       we'll ignore the run length, since we're just reading
;       one sector at this point.  (Inefficient, but effective.)

        mov     SectorBase.lsw, ax
        mov     SectorBase.msw, dx  ; SectorBase = LSN to read

        mov     ax, 2               ; read into third scratch sector
        call    ReadOneScratch      ; do the read

;       The first sector of the request (of which we want only a
;       part) is now in the third scratch sector.
;
;       TOS   = file seek position [msw]
;       TOS+2 = file seek position [lsw]
;       TOS+4 = offset in sector of first byte of request
;       TOS+6 = remaining request length [msw]
;       TOS+8 = remaining request length [lsw]
;       TOS+A = number of bytes read so far (still zero) [msw]
;       TOS+C = number of bytes read so far (still zero) [lsw]
;
        pop     dx
        pop     bx              ; dx:bx = original seek position

        pop     cx              ; cx = offset in sector of 1st byte of req
        mov     si, cx          ; si = offset in sector of 1st byte of req

        mov     ax, SECSIZE
        sub     ax, cx          ; ax = no. of bytes remaining in 1st sector

        pop     di
        pop     cx              ; di:cx = remaining request length

        cmp     di, 0
        jne     muRead3         ; di:cx > 64K -- take remainder of sector
        cmp     cx, ax
        ja      muRead3         ; take entire remainder of sector
        mov     ax, cx          ; only take enough to satisfy request

;       ax = number of bytes to copy from intermediate buffer
;       di:cx = number of bytes remaining in request (including
;               bytes to be copied from intermediate buffer)
;       dx:bx = seek position
;       si    = offset in sector of 1st byte of request
;
;       TOS   = number of bytes read so far (still zero) [msw]
;       TOS+2 = number of bytes read so far (still zero) [lsw]

muRead3:
        sub     cx, ax
        sbb     di, 0           ; di:cx = remaining request len

        mov     [bp+0Eh], cx
        mov     [bp+10h], di    ; [bp+14] = remaining request

        add     bx, ax
        adc     dx, 0           ; dx:bx = new seek position

        mov     [bp+8], dx
        mov     [bp+6], bx      ; DWORD at bp+6 = seek position

;       ax = number of bytes to copy from intermediate buffer
;       si = offset in buffer of 1st byte to copy
;
;       TOS = number of bytes read so far (still zero) [msw]
;       TOS+2 = number of bytes read so far (still zero) [lsw]
;

        mov     cx, ax          ; cx = number of bytes to copy
                                ; from intermediate buffer


        add     si, ScrOfs + 512 * 2    ; intermediate buffer is 3rd scratch

        mov     es, [bp+0Ch]
        mov     di, [bp+0Ah]    ; es:di = user's target address

        rep     movsb           ; copy the bytes
                                ; the rep instruction updates di
                                ; BUGBUG -- can di roll over?

        mov     [bp+0Ch], es
        mov     [bp+0Ah], di    ; target address for next chunk of read.

        pop     di
        pop     cx
        add     cx, ax
        adc     di, 0
        push    cx              ; update number of bytes read so far
        push    di


;       Now we have a request which is sector-aligned at the
;       front.  Read all the complete sectors we have
;
;       TOS = number of bytes read so far [msw]
;       TOS+2 number of bytes read so far [lsw]

muRead5:
        mov     ax, [bp+0Eh]
        mov     dx, [bp+10h]    ; dx:ax = bytes remaining in request

        call    BytesToSectors  ; dx:ax = sectors remaining in request
                                ; excluding remainder.  Note that, since
                                ; the total read cannot exceed 640K,
                                ; dx = 0.

        mov     cx, ax          ; cx = remaining whole sectors to read

muRead6:
;       cx = remaining number of sectors to read
;       TOS = number of bytes read so far [msw]
;       TOS+2 number of bytes read so far [lsw]
;
        cmp     cx, 0
        je      muRead8         ; no more whole sectors to read

        push cx

;       call CPS to figure out where to start reading and how
;       many sectors we can read at once.  We'll read the lesser
;       of:
;           sectors remaining in request, or
;           run length from first sector
;
;       Note that the total request is less than 64K bytes,
;       which will make some of the house cleaning easier.
;
;       TOS   = remaining sectors to read
;       TOS+2 = number of bytes read before this run [msw]
;       TOS+4 = number of bytes read before this run [lsw]

        mov     dx, [bp+8]
        mov     ax, [bp+6]      ;  dx:ax = file seek position

        call muCPS

;       DX:AX   = first LSN to read
;       SI:BX   = remaining run length (including first sector)
;
;       If si is non-zero (run-length is greater than 64K),
;       take an arbitrary large run-length.
;
;       TOS   = remaining sectors to read
;       TOS+2 = number of bytes read before this run [msw]
;       TOS+4 = number of bytes read before this run [lsw]
;
        cmp     si, 0
        je      muRead$ReadRun
        mov     bx, 0A000h      ;  run length > 64K--truncate.

muRead$ReadRun:
        pop     cx              ;  cx = remaining sectors to read
        cmp     bx, cx
        jbe     muRead7         ;  jump if bx is already <= cx
        mov     bx, cx          ;  bx = min(request len, run len)


;       dx:ax = LSN of first sector to read
;       bx = number of sectors to read from this run
;       cx = remaining number of sectors to read
;
;       TOS = number of bytes read before this run [msw]
;       TOS+2 = number of bytes read before this run [lsw]

muRead7:
        push    cx
        push    bx

        mov     SectorCount, bx

        mov     SectorBase.msw, dx
        mov     SectorBase.lsw, ax

        mov     es, [bp+0Ch]
        mov     bx, [bp+0Ah]    ; es:bx = read target address

        call    DoRead          ; read that many sectors

        pop     ax              ; number of sectors we just read
        pop     cx              ; number of sectors to be read

        sub     cx, ax          ; update no. of sectors remaining

        shl     ax, SECSHIFT-4  ; ax = number of paragraphs we just read
                                ; (less than 64K because total < 640K)

        add     [bp+0Ch], ax    ; update segment portion of target address

        mov     dx, ax
        shr     dx, 12
        shl     ax, 4           ; dx:ax = number of bytes we just read

        add     [bp+6], ax
        adc     [bp+8], dx      ; update file seek position

        sub     [bp+0Eh], ax
        sbb     [bp+10h], ax    ; update bytes remaining in request


        pop     di
        pop     bx              ; di:bx = number of bytes before this pass
        add     bx, ax
        adc     di, dx          ; update number of bytes read so far
        push    bx
        push    di

;
;       cx  = sectors remaining in request
;       TOS = number of bytes read before this run [msw]
;       TOS+2 = number of bytes read before this run [lsw]
;
;       which means we're ready for the next run!

        jmp     muRead6


;       All that's left is a non-sector-aligned tail--so we'll
;       read it.
;
;       TOS = number of bytes read before this run [msw]
;       TOS+2 = number of bytes read before this run [lsw]
muRead8:
        mov     cx, [bp+0Eh]    ;  cx = bytes remaining in request
                                ;  less than 1 sector, so < 64K

        cmp     cx, 0
        je      muRead10        ;  no remnant

        push    cx

        mov     dx, [bp+8]
        mov     ax, [bp+6]      ;  dx:ax = file seek position

        call    muCPS


;
;       TOS   = bytes remaining in request (note that cx < SECSIZE)
;       TOS+2 = bytes read so far [msw]
;       TOS+4 = bytes read so far [lsw]
;
;       dx:ax = LSN of sector to read
;
;       since we're only reading one sector, run length doesn't matter.

        mov     SectorBase.msw, dx
        mov     SectorBase.lsw, ax  ;  SectorBase = sector to read

        mov     ax, 2               ;  read into third scratch sector

        call ReadOneScratch

        pop     cx              ; number of bytes remaining in request
        mov     ax, cx          ; ax = number of bytes remaining in request

        mov     es, [bp+0Ch]
        mov     di, [bp+0Ah]    ; es:di = user's target address

        mov     si, ScrOfs + SECSIZE * 2 ; ds:si = addr of 3rd scratch sector

        rep     movsb           ; move the bytes

        pop     di
        pop     cx              ; di:cx = number of bytes read so far

        add     cx, ax
        adc     di, 0           ; update count of bytes read
        push    cx
        push    di              ; and save it.


muRead10:
;       All done!
;
;       TOS = number of bytes read [msw]
;       TOS+2 = number of bytes read [lsw]
        pop     dx
        pop     ax              ;  return value = number of bytes read

;       restore what we saved
        pop     di
        pop     si
        pop     ds
        pop     bp
        ret

muRead$EOF:
        ;  Seek position is past EOF, so we
        ;  read zero bytes.
        push    0
        push    0
        jmp     muRead10

muRead ENDP

;************************************************************

muClose proc far
        ret
muClose ENDP

;************************************************************

muTerminate proc far
        ret
muTerminate ENDP

;************************************************************

if TRACE
;       TracePrint - print string
;
;           ds:si -> text
;           cx    =  count of bytes

TracePrint proc

        push    ax
        push    bx
        push    cx

TracePrintLoop:
        lodsb                           ; Get next character
        cmp     al, 0
        je      TracePrintDone

        mov     ah,14                   ; Write teletype
        mov     bx,7                    ; Attribute
        int     10h                     ; Print it
        loop TracePrintLoop

TracePrintDone:
        pop     cx
        pop     bx
        pop     ax

        ret

TracePrint endp

;       TracePrint1 - Prints 1 character from al (uses xchar)

TracePrint1 proc
        push    si
        push    cx
        mov     cx,1
        mov     si, offset xchar
        mov     [si], al
        call    TracePrint
        pop     cx
        pop     si
        ret
TracePrint1 endp

endif

;************************************************************

Free2   equ (SEC_SUPERB*SECSIZE)-($-_pinboot)

if Free2 lt 0
    %out FATAL PROBLEM: main boot record exceeds available space
    %out Shrink boot code!!
endif

if Free2 gt 0
        db      Free2 dup(0)
endif


BootCode ends

        end
