;*DDK*************************************************************************/
;
; COPYRIGHT (C) Microsoft Corporation, 1989
; COPYRIGHT    Copyright (C) 1995 IBM Corporation
;
;    The following IBM OS/2 WARP source code is provided to you solely for
;    the purpose of assisting you in your development of OS/2 WARP device
;    drivers. You may use this code in accordance with the IBM License
;    Agreement provided in the IBM Device Driver Source Kit for OS/2. This
;    Copyright statement may not be removed.;
;*****************************************************************************/
title   Protected Mode VDisk for CP/DOS 2.0 (uses DWORD moves).

page    60,132

;/*****************************************************************************
;*
;* SOURCE FILE NAME =  VDisk.asm
;*
;* DESCRIPTIVE NAME =  VDisk Device Driver
;*
;*
;* VERSION      V2.0
;*
;* DATE
;*
;* DESCRIPTION  Implements a virtual disk in RAM. Can be loaded via
;*              the following CONFIG.SYS command line:
;*
;*           device = ramdrive.sys [bbbb] [ssss] [dddd]
;*
;*           bbbb  First numeric argument, if present, is disk size
;*                   in K bytes. Default value is 64. Min is 16. Max
;*                   is 4096 (4 Meg).
;*
;*           ssss  Second numeric argument, if present, is sector size
;*                   in bytes. Default value is 512. Allowed values are  ;@mfa
;*                   128, 256, 512, 1024.
;*
;*           dddd  Third numeric argument, if present, is the number of
;*                   root directory entries. Default is 64. Min is 2
;*                   max is 1024. The value is rounded up to the nearest
;*                   sector size boundary.
;*                 NOTE: In the event that there is not enough memory
;*                   to create the VDisk volume, VDisk will try to make
;*                   a DOS volume with 16 directory entries. This may
;*                   result in a volume with a different number of
;*                   directory entries than the dddd parameter specifies.
;*
;* FUNCTIONS    VDisk_Strategy - Strategy entry point
;*
;*
;* NOTES        NONE
;*
;* STRUCTURES   NONE
;*
;* EXTERNAL REFERENCES
;*
;*              NONE
;*
;* EXTERNAL FUNCTIONS
;*
;*              NONE
;*
;* CHANGE ACTIVITY =
;*   DATE      FLAG        APAR   CHANGE DESCRIPTION
;*   --------  ----------  -----  --------------------------------------
;*   mm/dd/yy  @Vr.mpppxx  xxxxx  xxxxxxx
;*   02/27/91             B718419 Special case of above ptrs
;*   07/18/91             B724590 Trap 6 in VDISK when running XGA demo
;*   08/31/92              D24793 VDISK > 4MB
;*   03/01/93              D62998 Device type & Attributes are wrong
;*   03/09/93              D63149 Sector size defaults to 512
;*   04/21/93              D64016 Allocate low, if fails then try high.
;*   08/04/93              D72412 Increase print space for disk size
;*
;*****************************************************************************/

.386p

.xlist
        include mi.inc
        include devhlp.inc
        include basemaca.inc
        include osmaca.inc
        include devsym.inc
        include struc.inc
        include utilmid.inc
        include error.inc
        include strat2.inc
.list


extrn    DOSGETMESSAGE:FAR
extrn    DOSPUTMESSAGE:FAR
extrn    DOS32FLATDS:ABS


TCYieldFlag     equ  8    ; Offset into DOSVARSEG for TCResched Flag
CHUNKDWORDS     equ  8192 ; Max for S/G before a Yield check
MAX_SG_ENTRIES  equ  16   ; Mem Mgmt can only handle SG <= 17 entries;          
attr_volume_id  EQU  8    ; FAT Attribute for volume ID

VDiskData       segment  word public 'DATA'
VDiskData       ends

VDiskCode       segment  word public 'CODE'
VDiskCode       ends

.386p

BREAK   <VDiskData Data Segment>

;/*
;**  VDiskData is the Data Segment for the VDisk driver. It contains the
;**  Device Header, Dummy Boot Record and BPB for the device, plus other
;**  global variables to the driver.
;*/

VDiskData segment

VDisk_Attrib    EQU     DEV_NON_IBM + DEVLEV_3

VDiskHDR        label   word
                dw      -1, -1          ; Pointer to next device header
                dw      VDisk_Attrib    ; Attributes of the device
                dw      VDisk_Strategy  ; Strategy entry point
                dw      0               ; Interrupt entry point
                db      1               ; 1 Unit
                db      7 dup (0)       ; Remaining Name Chars
                dw      0               ; Protect mode CS selctor of strategy
                dw      0               ; Protect-mode DS selector
                dw      0               ; Real-mode CS segment of strategy
                dw      0               ; Real-mode DS segment
                dd      1               ; Support physical address > 16M

;/*
;**  This is the device driver command dispatch table.
;*/

TBLSize equ     29                      ; Command Dispatch Table Size

VDiskTBL  label   word
        dw      Init                    ;  0 = Init
        dw      Media_Check             ;  1 = Media Check
        dw      Get_BPB                 ;  2 = Build BPB
        dw      CmdErr                  ;  3 = Reserved
        dw      Read                    ;  4 = Read
        dw      CmdErr                  ;  5 = Non-Destruct Read NoWait
        dw      CmdErr                  ;  6 = Input Status
        dw      CmdErr                  ;  7 = Input Flush
        dw      Write                   ;  8 = Write
        dw      Write                   ;  9 = Write w/Verify
        dw      CmdErr                  ;  A = Output Status
        dw      CmdErr                  ;  B = Output Flush
        dw      CmdErr                  ;  C = Reserved
        dw      RetOK                   ;  D = Device Open (R/M)
        dw      RetOK                   ;  E = Device Close (R/M)
        dw      Rem_Media               ;  F = Removable Media (R/M)
        dw      Ioctl                   ; 10 = Generic Ioctl
        dw      RetOK                   ; 11 = Reset Media
        dw      MapLogical              ; 12 = Get Logical Drive Map
        dw      MapLogical              ; 13 = Set Logical Drive Map
        dw      CmdErr                  ; 14 = DeInstall
        dw      CmdErr                  ; 15 = Reserved
        dw      CmdErr                  ; 16 = Get # Partitions
        dw      CmdErr                  ; 17 = Get Unit map
        dw      Read                    ; 18 = No caching read
        dw      Write                   ; 19 = No caching write
        dw      Write                   ; 1A = No caching write w/Verify
        dw      CmdErr                  ; 1B = Initialize
        dw      CmdErr                  ; 1C = Reserved
        dw      Get_DCS_VCS             ; 1D = Get DCS/VCS



;/*
;**  VDisk Bios Parameter Block and Bogus Boot Sector
;**
;**  This region is a valid CP/DOS "boot sector" which contains
;**  the BPB. This is used for storage of the relevant BPB parameters.
;**
;**  The BOOT_START code is a very simple stub which does nothing
;**  except go into an infinite loop. THIS "CODE" SHOULD NEVER
;**  BE EXECUTED BY ANYONE.
;*/

assume CS:VDiskData
BOOT_SECTOR     LABEL   BYTE
        JMP     BOOT_START
        DB      "OS2VDISK"

VDISKBPB label word
SSIZE   dw      512                     ; Physical sector size in bytes ;          
CSIZE   db      0                       ; Sectors/allocation unit
RESSEC  dw      1                       ; Reserved sectors for DOS
FATNUM  db      1                       ; No. allocation tables
DIRNUM  dw      64                      ; Number root directory entries
SECLIM  dw      0                       ; Number sectors
        db      0F8H                    ; Media descriptor
FATSEC  dw      1                       ; Number of sectors per FAT (of FAT ?)
        dw      1                       ; Number of sectors per track
        dw      1                       ; Number of heads
        dd      0                       ; Number of hidden sectors
BIGSEC  dd      0                       ; Big Number of sectors      ;          


SEC_SHIFT db     7                      ; Shifting number of        ;          
                                        ;  sectors LEFT by this
                                        ;  many bits yields #dwords
                                        ;  in that many sectors.
                                        ;  128   5
                                        ;  256   6
                                        ;  512   7
                                        ;  1024  8

BOOT_START label near
        JMP     BOOT_START

BOOT_SIG        LABEL BYTE
        DB      (512 - (OFFSET BOOT_SIG - OFFSET BOOT_SECTOR)) DUP ("A") ;          

;/*
;**  The following label is used to determine the size of the boot record
;**             OFFSET BOOT_END - OFFSET BOOT_SECTOR
;*/

BOOT_END LABEL BYTE

DevHelp         DD      0               ; DevHelp Function Router Address
VDisk_Base      DD      0               ; Physical address of base of VDisk RAM
TCReschedFlag   DD      0               ; Time Critical Thread Flag Address
TotalSectors    DD      0               ; Total number of sectors    ;          

;/*
;**  Device capabilities to be returned from command 1D.
;*/

VDisk_Caps LABEL BYTE
        DW      0                       ; reserved, set to zero
        DB      1                       ; major version of interface supported
        DB      1                       ; minor version of interface supported
        DD      GDC_DD_No_Block ; driver capabilties
        DW      offset VDisk_Strat2, VDiskCode  ;  entry point for strategy-2
        DD      0                       ; entry point for DD_EndOfInt
        DD      0                       ; entry point for DD_ChgPriority
        DD      0                       ; entry point for DD_SetRestPos
        DD      0                       ; entry point for DD_GetBoundary

;/*
;**  Volume characteristics to be returned for command 1D
;*/

NUMBER_CYLINDERS  EQU  1                                           ;           
VDisk_VolChars LABEL BYTE
        DW      VC_RAM_DISK             ; Volume descriptor
        DW      0                       ; Avg. seek time, milliseconds
        DW      0                       ; Avg latency, milliseconds
        DW      0FFh                    ; # blocks on smallest track
        DW      0FFh                    ; # blocks on largest track
        DW      1                       ; # Heads / cylinder
        DD      NUMBER_CYLINDERS        ; # cylinders on volume    ;           
        DD      0                       ; block in center of volume (for seek)
        DW      0FFh                    ; Max S/G list size.


NORMAL_READ     EQU     0
NORMAL_WRITE    EQU     1
LINEAR_READ     EQU     2
LINEAR_WRITE    EQU     4

.errnz (PB_READ_X - 01Eh)
.errnz (PB_WRITE_X - 01Fh)
.errnz (PB_WRITEV_X - 020h)
.errnz (PB_PREFETCH_X - 021h)
SG_VDisk_RW LABEL BYTE
        DB      LINEAR_READ
        DB      LINEAR_WRITE
        DB      LINEAR_WRITE
        DB      LINEAR_READ

DATA_END        label   word    ; End of Data Segment after Init

BREAK   <Disposable Init Data>

;/*
;**  INIT data which need not be part of resident image
;*/

DEV_SIZE        DD      64      ; Size in K of this device

NUM_ARG         DB      1       ; Counter for order dependent numeric arguments
                                ; bbbb,ssss,dddd seperated by commas.

NUM_NUM         DB      0       ; Counter for order dependent numeric arguments
                                ; bbbb ssss dddd seperated by spaces.

DIRSEC          DW      ?       ; Number of directory sectors

;/*
;**  Define and Statically Initialize Dummy_ReqPacket (WRITE)
;**  Used for fornatting the VDisk at Init time.
;*/

Dummy_ReqPacket label   word
                db      0               ; Unused packet size
                db      0               ; subunit number of block device
                db      CMDOUTPUT       ; command code
                dw      0               ; status word
                dd      0               ; reserved
                dd      0               ; device multiple-request link
                db      0               ; Unused
                dd      0               ; Filled at Init Time
                dw      1               ; 1 sector/write
                dw      0               ; start sector

;/*
;**  Message texts and common data
;*/

MAXMSG  EQU     1024
IvCount EQU     4

MsgFile db      "OSO001.MSG", 0
MsgLen  dw      0
MsgBuff db      MAXMSG DUP(" ")

IvTable dd      Var1
        dd      Var2
        dd      Var3
        dd      Var4

Var1    db      "A", 0
Var2    db      6 DUP(" "), 0    ;           increase from 4 DUP to 6 DUP
                                 ;    so we can print "524288"
                                 ;    (before, we only printed up to 4096)
Var3    db      4 DUP(" "), 0
Var4    db      4 DUP(" "), 0


;/*
;**  Volume Label and Sector Buffer for Formatting the VDisk at Init
;*/

VOLID   DB      'OS2VDISK   ',ATTR_VOLUME_ID
        DB      10 DUP (0)
        DW      1100000000000000B       ;12:00:00
        DW      0000101011001001B       ;JUN 9, 1985
        DW      0,0,0

SECTOR_BUFFER   DB      1024 DUP(0)

INIT_BPB        DD      offset VDISKBPB, VDiskData

VDiskData ends

BREAK   <VDiskCode Code Segment>

VDiskCode segment

;/***************************************************************************
;*
;* FUNCTION NAME = VDisk_Strategy
;*
;* DESCRIPTION   = VDisk Strategy Entry Point
;*
;*                 Entry point called by File System to request service.
;*                 Validates request block and dispatches to the correct
;*                 service routine in VDiskCode.
;*
;*                 Called in protect mode only.
;*
;* INPUT         = ES: BX = ptr to request block
;*                 DS = VDiskData data segment
;*
;* OUTPUT        = ES: BX = ptr to request block
;*
;* RETURN-NORMAL = Status set to OK in request block
;*
;* RETURN-ERROR  = Status set in request block to indicate error
;*
;**************************************************************************/

Procedure VDisk_Strategy,FAR
assume  CS:VDiskCode,DS:VDiskData,ES:NOTHING,SS:NOTHING

        mov     al, es:[bx].ReqFunc     ; Command code
        mov     ah, TBLSize             ; Valid range
        .if < al be ah >
                cbw                     ; Make command code a word
                mov     si, ax
                add     si, ax          ; (si) = command offset
                call    VDiskTBL[si]    ; go do request
        .else
                call    CmdErr
        .endif

        mov     es:[bx].ReqStat, ax     ; Set return status

        or      es:[bx].ReqStat, STDON  ; Set DONE bit at Init Time

        ret

EndProc VDisk_Strategy


BREAK   <VDisk_Strat2>
;/***************************************************************************
;*
;* FUNCTION NAME = VDisk_Strat2
;*
;* DESCRIPTION   = VDisk Strategy2 Entry Point
;*
;*                 Entry point called by File System to request Strategy2
;*                 service.
;*
;*                 Called in protect mode only.
;*
;*                 This is only excepted to be called from FAT, and does not
;*                 support the full interface. If changes are made to the manner
;*                 in which Strat2 FAT calls are made, VDisk should be examined
;*                 for possible errors.
;*
;*
;* INPUT         = ES:BX = ptr to Strat2 request list header (RLH)
;* OUTPUT        = ES:BX = ptr to Strat2 request list header (RLH)
;*
;* RETURN-NORMAL =
;* RETURN-ERROR  =
;*
;**************************************************************************/

Procedure VDisk_Strat2, FAR

LocalVar vs2_RLH, DWORD                 ; Request List Header
                                        ; Start req after last sector done
LocalVar Running_PB_Start_Block,     DWORD                           ;          
                                        ; Start after last SG entry processed
LocalVar Running_PB_SG_Array_Offset, DWORD                           ;          

        EnterProc

        SaveReg <ds>

        mov     vs2_RLH.Segmt, es
        mov     vs2_RLH.Offst, bx
        mov     ax, VDiskData
        mov     ds, ax

;/*
;**  Set up status/error fields of RLH
;*/

        mov     es:[bx].RLH_Lst_Status, RLH_Seq_In_Process
        mov     es:[bx].RLH_y_Done_Count, 0


        mov     ecx,es:[bx].RLH_Count           ; ECX = # Requests
        or      ecx,ecx                         ; Requests to process?
        jz      Strat2_Done                     ;  No. Return

        add     bx, size Req_List_Header        ; ES:BX = Request Header (RH)

;/*
;**  ECX   = # requests to process (> 0)
;**  ES:BX = Current request
;*/

request_loop:

        SaveReg <ecx>

;/*
;**  Setup random fields
;*/
        mov     es:[bx].RH_Status, RH_PROCESSING

        movzx   ecx, es:[bx].PB_SG_Desc_Count ; ECX = # S/G Descriptors

        xor     eax, eax                                             ;          
        push    eax                           ; SG entries done so far          
        mov     Running_PB_Start_Block, 0     ; Start at zero        ;          
        mov     Running_PB_SG_Array_Offset, 0 ; Start at zero        ;          
SG_loop:                                      ; More SG entries      ;          
        RestoreReg <ecx>                      ; SG entries done so far          

        cmp     cx, es:[bx].PB_SG_Desc_Count  ; All SG entries done ?;          
                                              ; Yes, try another List entry
        jge     vst30                                                ;          
        mov     ax, cx                        ; SG entries done so far          
        add     ax, MAX_SG_ENTRIES            ; If we attempt MAX    ;          
       .IF <ax gt es:[bx].PB_SG_Desc_Count>   ; Is that > SG list ?  ;          
           mov  ax, cx                        ; Yes                  ;          
                                              ; Do only as many as we need
           sub  ax, es:[bx].PB_SG_Desc_Count                         ;          
           neg  ax                            ; Make positive        ;          
                                              ; Alrdy done + entries needed
           add  cx, ax                                               ;          
           xchg ecx, eax                      ; Do this many (ecx)   ;          
       .ELSE                                  ; No, do MAX           ;          
           mov  cx, MAX_SG_ENTRIES                                   ;          
       .ENDIF                                                        ;          
                                              ; Alrdy done + about to do
        SaveReg  <eax>                                               ;          

        mov     esi, ecx
        dec     esi
        .errnz (8 - size SG_Descriptor)
        shl     esi,3
        movzx   ebx,bx
                                                ; ECX entries into SG lst
        mov     eax, esi                                             ;          
        add     eax, size SG_Descriptor         ; Next entry         ;          
        add     esi,ebx
        add     esi, PB_SG_Array_Offset         ; ES:SI = Last SG Descriptor
                                                ; Plus SG entries prvly done
        add     esi, Running_PB_SG_Array_Offset                      ;          
                                                ; Update the ones done alrdy
        add     Running_PB_SG_Array_Offset, eax                      ;          
                                                ; EAX = Total Byte count
        xor     eax,eax                                              ;          

;/*
;**  Calculate total size of request, for the PageListToLin call
;*/

vst1:   add     eax, es:[si].SG_BufferSize
        sub     esi, size SG_Descriptor
        loop    vst1

;/*
;**  ECX = Total bytes in S/G request
;*/
        mov     ecx, eax
        add     esi, size SG_Descriptor         ; ES:ESI = S/G PageList

;/*
;**  Get the linear address of the S/G page list
;*/

        mov     ax,es                           ; AX:ESI = (sel,offset)
        mov     dl,DevHlp_VirtToLin
        call    DevHelp                         ; EAX = (linear)

;/*
;**  Convert the page list to a contiguous linear region
;*/

        mov     edi,eax                         ; EDI = Lin address of S/G
                                                ;       pagelist
        mov     dl,DevHlp_PageListToLin
        call    DevHelp                         ; EAX = Linear address of
                                                ;  Region mapping pages in
                                                ;  paglist.

        mov     edi, eax                        ; EDI = Linear address of
                                                ;       xfer region
        mov     edx, ecx                        ; bytes count        ;          
        shr     edx, 2                          ; bytes => DWORDS    ;          
                                                ; Shift factor DWORDS => sectors
        mov     cl,  SEC_SHIFT                                       ;          
                                                ; Get number of sectors
        shr     edx, cl                                              ;          
        mov     ecx, edx

;             mov     ecx, es:[bx].PB_Block_Count     ; ECX = # Sectors
        mov     edx, Running_PB_Start_Block     ; Previous iterations           
                                                ; Previous iterations+# sectors
        add     Running_PB_Start_Block, ecx     ;     to do now      ;          
        add     edx, es:[bx].PB_Start_Block     ; EDX = First sector            

        movzx   esi, es:[bx].RH_Command_Code
        sub     esi, PB_READ_X
        movzx   esi, byte ptr [esi].SG_VDisk_RW ; ESI = Read/Write

;/*
;** Call VDisk_IO to perform the actual I/O
;*/
        SaveReg <bx>
        call    VDisk_IO_Lin
        RestoreReg <bx>
        or      ah,ah                           ; Successful?
;                 je      short vst30
        jnz      short error_found                                   ;          
        jmp     sg_loop                     ; See if more SG entries ;          
error_found:                                                         ;           
        mov     es:[bx].RH_Status, RH_UNREC_ERROR
        jmp     short Strat2_Done

vst30:  mov     es:[bx].RH_Status, RH_DONE
;                  cmp     es:[bx].RH_Length, RH_LAST_REQ ; Done w/all requests?
;                  je      short Strat2_Done              ;  Yes.
        add     bx, es:[bx].RH_Length           ; ES:BX = Next Request Header
        RestoreReg <ecx>                        ; ECX = RH Count

        dec     ecx
        jnz     request_loop                    ; Do the next one

Strat2_Done:

;/*
;**  Call notify_address, if appropriate
;*/

        les     bx, vs2_RLH                     ; ES:BX = RLH
        mov     es:[bx].RLH_Lst_Status, RLH_All_Req_Done
        neg     ecx
        add     ecx, es:[bx].RLH_Count          ; ECX = # Completed requests
        mov     es:[bx].RLH_y_Done_Count, ecx
        test    es:[bx].RLH_Request_Control, RLH_Notify_Done
        jz      short vst40
        call    es:[bx].RLH_Notify_Address

vst40:  RestoreReg <ds>

        LeaveProc
        ret

EndProc VDisk_Strat2

BREAK   <Media_Check>
;/***************************************************************************
;*
;* FUNCTION NAME = Media_Check
;*
;* DESCRIPTION   = VDisk Media Check Routine
;*                 Checks state of media for block devices
;*
;*                 Always returns Media Unchanged
;*
;* INPUT         = ES:BX = request packet address
;*                 DS = VDiskData
;*
;* OUTPUT        =
;*
;* RETURN-NORMAL = AX = status to return to DOS
;*                 Return Code set in Request Packet
;*
;* RETURN-ERROR  = None
;*
;**************************************************************************/

Procedure Media_Check,NEAR,LOCAL
assume  DS:VDiskData,ES:NOTHING,SS:NOTHING,DS:NOTHING

        mov     es:[bx.MedChkflag], 1   ; Always not changed
        xor     ah, ah                  ; NOERROR
        ret

EndProc Media_Check


BREAK   <Get_BPB>
;/***************************************************************************
;*
;* FUNCTION NAME = Get_BPB
;*
;* DESCRIPTION   = VDisk Build BPB Routine
;*                 Returns pointer to BPB at VDISKBPB
;*
;* INPUT         = ES:BX = request packet address
;*                 DS = VDiskData
;*
;* OUTPUT        =
;*
;* RETURN-NORMAL = AX = status to return to DOS
;*                 BPB Address set in Request Packet
;*
;* RETURN-ERROR  = NONE
;*
;**************************************************************************/

Procedure Get_BPB,NEAR,LOCAL
assume  DS:VDiskData,ES:NOTHING,SS:NOTHING

        mov     word ptr es:[bx.BldBPBpBPB], offset VDISKBPB
        mov     word ptr es:[bx.BldBPBpBPB+2], ds
        xor     ah, ah
        ret

EndProc Get_BPB


BREAK   <Read>
;/***************************************************************************
;*
;* FUNCTION NAME = Read
;*
;* DESCRIPTION   = VDisk Read Routine
;*                 Performs Read Operation by calling VDisk_IO
;*
;* INPUT         = ES:BX = request packet address
;*                 DS = VDiskData
;*
;* OUTPUT        =
;*
;* RETURN-NORMAL = AX = status to return to DOS
;*
;* RETURN-ERROR  = NONE
;*
;**************************************************************************/

Procedure Read,NEAR,LOCAL
assume  DS:VDiskData,ES:NOTHING,SS:NOTHING

        mov     si, NORMAL_READ
        call    VDisk_IO
        ret

EndProc Read


BREAK   <Write>
;/***************************************************************************
;*
;* FUNCTION NAME = Write
;*
;* DESCRIPTION   = VDisk Write Routine
;*                 Performs Write Operation by calling VDisk_IO
;*
;* INPUT         = ES:BX = request packet address
;*                 DS = VDiskData
;*
;* OUTPUT        =
;*
;* RETURN-NORMAL = AX = status to return to DOS
;*
;* RETURN-ERROR  = NONE
;*
;**************************************************************************/

Procedure Write,NEAR,LOCAL
assume  DS:VDiskData,ES:NOTHING,SS:NOTHING

        mov     si, NORMAL_WRITE
        call    VDisk_IO
        ret

EndProc Write


BREAK   <Rem_Media>
;/***************************************************************************
;*
;* FUNCTION NAME = Rem_Media
;*
;* DESCRIPTION   = VDisk Removable Media Routine
;*                 Checks if Media is Removable
;*
;*                 Always returns media not removable
;*
;* INPUT         = ES:BX = request packet address
;*                 DS = VDiskData
;*
;* OUTPUT        =
;*
;* RETURN-NORMAL = AX = status to return to DOS
;*
;* RETURN-ERROR  = NONE
;*
;**************************************************************************/

Procedure Rem_Media,NEAR,LOCAL
assume  DS:VDiskData,ES:NOTHING,SS:NOTHING

        mov     ax, STBUI       ; Media NOT Removable (Busy = 1)
        ret

EndProc Rem_Media


BREAK   <MapLogical>
;/***************************************************************************
;*
;* FUNCTION NAME = MapLogical
;*
;* DESCRIPTION   = VDisk Get/Set Logical Drive Routine
;*                 Get/Set the logical drive specified
;*
;*                 Since we only have one drive, always returns zero (0)
;*                 in Logical Unit field
;*
;* INPUT         = ES:BX = request packet address
;*                 DS = VDiskData
;*
;* OUTPUT        =
;*
;* RETURN-NORMAL = AX = status to return to DOS
;*
;* RETURN-ERROR  = NONE
;*
;**************************************************************************/

Procedure MapLogical,NEAR,LOCAL
assume  DS:VDiskData,ES:NOTHING,SS:NOTHING


;       mov     es:[bx.Logical_Drive], 0
        mov     es:[bx].PktUnit, 0
        xor     ah, ah
        ret

EndProc MapLogical

BREAK   <Get_DCS_VCS>
;/***************************************************************************
;*
;* FUNCTION NAME = Get_DCS_VCS
;*
;* DESCRIPTION   = VDisk Get Device/Volume characteristics
;*                 Returns pointers to DCS and VCS
;*
;* INPUT         = ES:BX = request packet address
;*                 DS = VDiskData
;*
;* OUTPUT        =
;*
;* RETURN-NORMAL = AX = status to return to DOS
;*                 DCS Address set in Request Packet
;*                 VCS Address set in Request Packet
;*
;* RETURN-ERROR  = NONE
;*
;**************************************************************************/

ASSUME DS:VDiskData, ES:NOTHING, FS:NOTHING

Procedure Get_DCS_VCS

        mov     es:[bx].Pkt_1d_DCS_Addr.Segmt,VDiskData
        mov     es:[bx].Pkt_1d_DCS_Addr.Offst,offset VDisk_Caps
        mov     es:[bx].Pkt_1d_VCS_Addr.Segmt,VDiskData
        mov     es:[bx].Pkt_1d_VCS_Addr.Offst,offset VDisk_VolChars

        xor     ah,ah
        ret

EndProc Get_DCS_VCS



BREAK   <RetOK>
;/***************************************************************************
;*
;* FUNCTION NAME = RetOK
;*
;* DESCRIPTION   = Returns OK status
;*                 Returns OK status for supported do-nothing entry points
;*
;*                 Called in place of Open, Close, Reset Media Routines
;*
;* INPUT         = ES:BX = request packet address
;*                 DS = VDiskData
;*
;* OUTPUT        =
;*
;* RETURN-NORMAL = AX = status to return to DOS
;*
;* RETURN-ERROR  = NONE
;*
;**************************************************************************/

Procedure RetOK,NEAR,LOCAL
assume  DS:VDiskData,ES:NOTHING,SS:NOTHING

        xor     ah, ah          ; NOERROR
        ret

EndProc RetOK


BREAK   <CmdErr>
;/***************************************************************************
;*
;* FUNCTION NAME = CmdErr
;*
;* DESCRIPTION   = Command Error
;*                 Returns error condition of Unknown Command
;*
;*                 Called when out of range request comes to VDisk_Strategy,
;*                 or when an unsupported function request is made.
;*
;* INPUT         = ES:BX = request packet address
;*                 DS = VDiskData
;*
;* OUTPUT        =
;*
;* RETURN-NORMAL = AX = Status to return to DOS (ERROR + UnKnown Command Error)
;*
;* RETURN-ERROR  = NONE
;*
;**************************************************************************/

Procedure CmdErr,NEAR,LOCAL
assume  DS:VDiskData, ES:NOTHING, SS:NOTHING

        mov     ax, STERR + ERROR_I24_BAD_COMMAND
        ret

EndProc CmdErr


BREAK   <Ioctl>
;/***************************************************************************
;*
;* FUNCTION NAME = Ioctl
;*
;* DESCRIPTION   = Generic Ioctl Support Routine
;*                 Supports CHKDSK use of Function 63. Get Device Parameters
;*
;*                 Called when an application desires the BPB of the Device.
;*
;* INPUT         = ES:BX = request packet address
;*                 DS = VDiskData
;*
;* OUTPUT        =
;*
;* RETURN-NORMAL = AX = Status to return to DOS
;*
;* RETURN-ERROR  = AX = ERROR_I24_GEN_FAILURE
;*                 (when packet addresses not verified)
;*
;**************************************************************************/

Procedure Ioctl,NEAR
assume  DS:VDiskData, ES:NOTHING, SS:NOTHING

        .if < es:[bx+GIOCategory] eq 8H > AND
        .if < es:[bx+GIOFunction] eq 63H >

                SaveReg <ds,es,bx,cx,si,di>

                mov     di, word ptr es:[bx+GIODataPack]    ; (di) = offset
                mov     ax, word ptr es:[bx+GIODataPack+2]  ; (ax) = seg/sel
                mov     cx, 36                              ; length
                mov     dh, 1                               ; read/write
                mov     dl, DevHlp_VerifyAccess

                call    DevHelp
                .if c                                       ; Access OK ?
                    mov     ax, STERR+ERROR_I24_GEN_FAILURE
                    jmp     eioctl
                .endif

                les     di, es:[bx+GIODataPack] ; Target = User Buffer
                movzx   edi,di
                lea     esi, VDISKBPB           ; Source = VDiskData:VDISKBPB

;                           mov     cx, 21
                mov     ecx, 25                                      ;          
                rep     movsb                   ; Move BPB

;                           mov     ax, SECLIM      ; 1st word of Large Sectors
;                           stosw

;                           mov     ecx, 5          ; Last word of Large Sectors
;                           xor     ax, ax          ; + 3 Reserved words
;                           rep     stosw           ; + 1 Word # Cylinders (0)


                mov     ecx, 3          ; Last word of Large Sectors ;          
                xor     ax, ax          ; + 3 Reserved words
                rep     stosw           ; + 1 Word # Cylinders (0)

                mov     ecx, 1                  ;                               
                mov     ax,  NUMBER_CYLINDERS   ; # cylinders                   
                rep     stosw                   ;                               

                mov     al, 7           ; Device Type = Other
                stosb

                mov     ax, 1+4         ; Device Attributes =                   
                                        ; NonRemovable+NoChangeStatus+>16MB
                stosw

                xor     ax, ax          ; NOERROR

eioctl:
                RestoreReg      <di,si,cx,bx,es,ds>
        .else
                call    CmdErr
        .endif

        ret

EndProc Ioctl

BREAK   <VDisk_IO>
;/***************************************************************************
;*
;* FUNCTION NAME = VDisk_IO
;*
;* DESCRIPTION   = VDisk I/O Routine
;*
;*                 Performs I/O Operations to VDisk. Common worker routine for
;*                 Read, Write, Write w/Verify.
;*
;*           This routine first takes the count parameters out of the request
;*           packet. It then checks the I/O parameters for validity, then sets
;*           up the parameters for the block move operation. It converts the
;*           SectorCnt in CX to the # of words in that many sectors or 8000H,
;*           which ever is less. It also converts the StartSector in DX into
;*           a 32 bit byte offset (IO_Start) equal to that many sectors.
;*
;*           NOTE that we convert the number of sectors to transfer
;*           to a number of words to transfer.
;*                   Sector size is always a power of two, therefore a
;*                   multiple of two so there are no "half word" problems.
;*           DOS NEVER asks for a transfer larger than 64K bytes except
;*                   in one case where we can ignore the extra anyway.
;*                   or when an unsupported function request is made.
;*
;*           This routine also does all address translation for accessing
;*           the VDisk, and the Time Critical considerations. Note the
;*           main I/O loop is optimized for PRTECTED MODE, and we take care
;*           of allowing Time Critical threads run.
;*
;* INPUT         = ES:BX = request packet address
;*                 DS = VDiskData
;*                 SI = Read/Write Flag ( 0 => READ)
;*
;* OUTPUT        =
;*
;* RETURN-NORMAL = AX = status to return to DOS
;*                 Request packet has # of sectors transferred
;*
;* RETURN-ERROR  = None
;*
;**************************************************************************/

Procedure VDisk_IO,NEAR,LOCAL
assume  DS:VDiskData,ES:NOTHING,SS:NOTHING

        movzx   ecx, es:[bx.IOcount]            ; ECX = SectorCnt    ;          
        movzx   edx, es:[bx.IOstart]            ; EDX = StartSector  ;          

        mov     eax, edx                        ; EAX = Tmp          ;          
        add     eax, ecx                        ; EAX = EndSector    ;          

;                  .if < dx ae SECLIM > OR                 ; Validate Request
;                  .if < ax a SECLIM >
        .if < edx ae TotalSectors > OR          ; Validate Request
        .if < eax a TotalSectors >
                mov     ax, STERR + 8           ; ERROR + SECTOR NOT FOUND
        .else NEAR

                mov es:[bx.IOcount], cx         ; Set Request.Sectors Moved

Entry VDisk_IO_Lin,NEAR                         ; S/G Entry Point


                localvar        RWFlag, WORD
                localvar        DWordLimit, DWORD
                localvar        ChunkSize, DWORD
                localvar        ChunkBytes, DWORD
                localvar        t_es, WORD
                localvar        t_ds, WORD
                localvar        t_bx, WORD
                localvar        t_DTA, DWORD
                localvar        t_DH, DWORD
                localvar        t_TCReschedFlag, DWORD
                localvar        VDiskIO, DWORD

                EnterProc
;/*
;**  Convert sector count to WordLimit : Bytes/Sector = SEC_SHIFT
;*/

                movzx   eax, cx         ; EAX = SectorCount
                mov     cl, SEC_SHIFT
                shl     eax, cl         ; EAX = DWordLimit
                mov     DWordLimit, eax ; Save It
;/*
;**  Compute the start offset of I/O from sector 0 of the VDisk
;*/

                movzx   eax, dx         ; EAX = Starting Sector #
                movzx   edx, SSIZE      ; EDX = Bytes/Sector
                mul     edx             ; EDX:EAX is byte offset of start
;/*
;**  Compute the 32 bit address of start of I/O on the VDisk
;*/

                add     eax, VDisk_Base ; EAX = Starting linear address
;/*
;**  Save the DevHelp Function Router on the stack along with any
;**  other variables needed from the data segment VDiskData. This is
;**  because the loading of DS and ES for the transfer will destroy
;**  addressibility to the VDiskData segment and the request packet.
;**  Remember to restore the t_registers BEFORE removing the frame.
;*/


                mov     RWFlag, si
                mov     VDiskIO, eax                    ; VDiskIO
                mov     eax,DevHelp
                mov     t_DH, eax
                mov     eax, TCReschedFlag
                mov     t_TCReschedFlag, eax
                test    si, LINEAR_READ or LINEAR_WRITE
                jnz     short vdi30
                mov     edi, es:[bx.IOpData]
vdi30:          mov     ChunkSize, CHUNKDWORDS
                mov     t_DTA, edi                      ; t_DTA = DTA
                mov     t_es, es
                mov     t_ds, ds                        ; Addressibility
                mov     t_bx, bx


;/*
;**  Main Transfer Loop
;*/
                .while < DWordLimit a 0 > NEAR
;/*
;**  Adjust ChunkSize to handle case of left overs smaller
;**  than a Chunk. Also set up cx to correct size of Segment
;**  to be mapped for this transfer. This should only be true
;**  on the LAST iteration of the Loop(s).
;*/

                        mov     ecx, ChunkSize
                        .if < DWordLimit b ecx >
                                mov     ecx, DWordLimit
                                mov     ChunkSize, ecx
                        .endif

                        shl     ecx,2           ; DWords to Bytes
                        mov     ChunkBytes, ecx ; Save it
;/*
;**  Set up Virtual Target and Source addresses depending on function
;**  Note here the physical address for IO on the VDisk is in the
;**  variable VDisk_IO. The users DTA is in the local variable t_DTA as
;**  a 32 bit physical address. Both addresses must be converted from
;**  Physical to Virtual 32 bit addresses. The operation determines
;**  which register the virtual addresses are left in. The source is
;**  always left in DS:SI and the target in ES:DI. BE CAREFUL OF
;**  REGISTERS BEING DESTROYED BY PHYSTOVIRT (CX?)
;*/

                        mov     dl, DevHlp_PhysToVirt

                        .if < RWFlag eq NORMAL_READ >
                                mov     ax, DOS32FLATDS
                                mov     ds, ax
                                mov     esi, VDiskIO
                                mov     ecx, ChunkBytes
                                mov     ax, t_DTAh      ; Map DTA
                                mov     bx, t_DTAl
                                mov     dh, 1           ; DTA Target
                                call    t_DH            ; Call DevHelp
                                movzx   edi,di

                        ;/*
                        ;**  DS:ESI = VirtSource = VDiskIO
                        ;**  ES:EDI = VirtTarget = DTA
                        ;*/

                        .elseif < RWFlag eq NORMAL_WRITE >
                                mov     ax, DOS32FLATDS
                                mov     es, ax
                                mov     edi, VDiskIO
                                mov     ecx, ChunkBytes
                                mov     ax, t_DTAh      ; Map DTA
                                mov     bx, t_DTAl
                                xor     dh, dh          ; DTA Source
                                call    t_DH            ; Call DevHelp
                                movzx   esi,si

                        ;/*
                        ;**  DS:ESI = VirtSource = DTA
                        ;**  ES:EDI = VirtTarget = VDiskIO
                        ;*/

                        .else                           ; Linear R or W
                                mov     ax, DOS32FLATDS
                                mov     ds, ax
                                mov     es, ax
                                mov     esi, VDiskIO
                                mov     edi, t_DTA

                                .if <RWFlag eq LINEAR_WRITE>
                                        xchg    esi, edi
                                .endif

                        .endif
;/*
;**  Addresses are mapped, so now we move a Chunk
;*/
                        mov     ecx, ChunkSize
                        cld
                        rep     movs dword ptr [esi],dword ptr [edi]
                        db      MI_ADDRESSSIZE
                        nop
;/*
;**  Update Counters
;*/

                        mov     eax, ChunkSize  ; EAX = ChunkSize
                        sub     DWordLimit, eax
                        shl     eax,2           ; EAX = ChunkSize in Bytes
                        add     t_DTA, eax      ; Move Addresses
                        add     VDiskIO, eax
;/*
;**  Test TCResched
;*/

                        lds     bx, t_TCReschedFlag
                        .if < <byte ptr ds:[bx]> ne 0 >         ; VOLUNTARY
                                mov     dl, DevHlp_TCYield      ; PREEMPTION
                                call    t_DH                    ; Call DevHelp
                        .endif

                .endwhile
;/*
;**  Set the Status to NOERROR
;*/

                xor     ah, ah          ; Status = NOERROR
;/*
;**  Set Registers back to before the frame
;*/

                mov     es, t_es
                mov     ds, t_ds
                mov     bx, t_bx

                LeaveProc               ; Remove the local Var Frame

        .endif

        ret

EndProc VDisk_IO

BREAK   <End OF Resident Code>
;/*
;**
;**  The following label defines the end of the VDisk resident code.
;**
;*/
DEVICE_END      LABEL   BYTE


BREAK   <Init>
;/***************************************************************************
;*
;* FUNCTION NAME = Init
;*
;* DESCRIPTION   = VDisk Initialization Routine
;*                 Initializes the VDisk Data Segment and VDisk itself.
;*
;*         VDisk Initialization routine. Its jobs are to:
;*
;*             1.  Initialize various global values
;*             2.  Parse the command line and set values accordingly
;*             3.  Allocate the memory for the VDisk
;*             4.  Initialize the DOS volume in the VDIsk RAM
;*             5.  Print out report of RAMDrive parameters
;*             6.  Set the return INIT I/O packet values
;*
;*         At any time during the above steps an error may be detected. When
;*         this happens one of the error messages is printed and VDisk
;*         "de-installs" itself by returning a unit count of 0 in the INIT
;*         device I/O packet. The DOS device installation code is responsible
;*         for taking care of the details of re-claiming the memory used by
;*         the device driver.
;*
;*         Step 1 initializes the DevHelp function router address.
;*
;*         Step 2 uses the "DEVICE = xxxxxxxxx" line pointer provided by
;*         DOS to look for the various device parameters.
;*         First we skips over the device name field
;*         to get to the arguments. We then parse the arguments as they are
;*         encountered. All parameter errors are detected here. NOTE THAT
;*         THIS ROUTINE IS NOT RESPONSIBLE FOR SETTING DEFAULT VALUES OF
;*         PARAMETER VARIABLES. This is accomplished by static initialization
;*         of the parameter variables.
;*
;*         Step 3 alloactes RAM for the VDisk. It must set up the following
;*         global variables:
;*
;*                         DEV_SIZE   set to TRUE size of device
;*                         VDisk_Base set to TRUE start of device so VDisk_IO
;*                                         can be called
;*
;*         Step 4 initializes the virtual disk. The BPB must be set, the
;*         RESERVED (boot) sector, FAT sectors, and root directory sectors
;*         must be initialized and written out to the VDisk. The first step
;*         is to initialize all of the BPB values. The code is a typical piece
;*         of PC-DOS code which given BYTES/SECTOR, TOTAL DISK SIZE
;*         and NUMBER OF ROOT DIRECTORY ENTRIES inputs figures out reasonable
;*         values for SEC/CLUSTER and SECTORS/FAT and TOTAL NUMBER OF CLUSTERS.
;*         NOTE THAT THIS CODE IS TUNED AND SPECIFIC TO 12 BIT FATS. Don't
;*         expect it to work AT ALL with a 16 bit FAT. The next step is to write
;*         out the BOOT record containing the BPB to sector 0, write out
;*         a FAT with all of the clusters free, and write out a root directory
;*         with ONE entry (the Volume ID at VOLID).
;*
;*         Step 5 makes the status report display of DEVICE SIZE, SECTOR SIZE,
;*         CLUSTER SIZE, and DIRECTORY SIZE by simply printing out the values
;*         from the BPB.
;*
;*         Step 6 sets the INIT I/O packet return values for # of units,
;*         Break address, and BPB array pointer and returns via DEVEXIT.
;*
;* INPUT         = ES:BX = request packet address
;*                 DS = VDiskData
;*
;* OUTPUT        =
;*
;* RETURN-NORMAL = Request Packet Set
;*                         Units Set
;*                         BPB address set
;*                         Terminating Code Address Set
;*                         Terminating Data Address Set
;*                 AX = status to return to DOS
;*
;* RETURN-ERROR  = NONE
;*
;**************************************************************************/

Procedure Init,NEAR,LOCAL
assume  DS:VDiskData,ES:NOTHING,SS:NOTHING

;/*
;**  Save DvHelp Function Router Address in VDiskData
;*/

        mov     eax, es:[bx.InitpEnd]
        mov     DevHelp, eax

;/*
;**  Parse Command Line and Set Values Accordingly
;*/

        mov     al, es:[bx.Initdrv]     ; DOS drive letter
        add     Var1, al                ; Add into Global Var


;/*
;**  Create a local stack frame since we are going to use
;**  DS for string operations. Create local variables for those
;**  values in the VDiskData segment we need to Read/Write to.
;**  These rules apply from here to end of Init.
;*/

        localvar        t_ds, WORD
        localvar        t_es, WORD
        localvar        t_bx, WORD

        EnterProc

        mov     t_ds, ds
        mov     t_es, es
        mov     t_bx, bx

;/*
;**  Check For Out of Drives
;*/
        .if <al a 25>
            mov     dx, msg_vdisk_no_drives
            call    print
            jmp     devabort
        .endif

;/*
;**  Get Address of TCResched Flag
;*/

        mov     al, TCYieldFlag
        mov     dl, DevHlp_GetDOSVar    ; GetDOSVar
        call    DevHelp                 ; Call Device_Help

        mov     word ptr TCReschedFlag, bx
        mov     word ptr TCReschedFlag + 2, ax

        mov     bx, t_bx                ; Restore BX

;/*
;**  Since the LODSB instruction works with the DS register, we
;**  save the values of both seg registers, and set up ES to point
;**  to the VDiskData segment. The pointer to the request packet
;**  is save in temp local variables.
;*/

        lds     si, es:[bx.InitpBPB]    ; DS:SI points to config.sys
        mov     es, t_ds                ; ES points to VDiskData

assume  ds:NOTHING, es:VDiskData, ss:NOTHING

;/*
;**  Skip to start of name
;*/

        .repeat
                lodsb
        .until < al ne " " > AND
        .until < al ne 09h > AND
        .until < al ne "," >

;/*
;**  Skip over Device Name
;*/

        .while < al ne 0 > AND
        .while < al ne " " > AND
        .while < al ne 09h > AND
        .while < al ne ",">

                .if < al eq 0Dh > OR
                .if < al eq 0Ah >
                        jmp     args_donej
                .endif

                lodsb

        .endwhile

;/*
;**  Process Arguments
;*/

        .while < al ne 0 > NEAR AND
        .while < al ne 0Dh > NEAR AND
        .while < al ne 0Ah > NEAR

                .if < al eq " " > OR
                .if < al eq 09h >
                        lodsb
                .else NEAR

                        .if < al eq "," >
                                inc     es:NUM_ARG
                                jmp     next_parm
                        .endif

                        .if < al b "0" > OR
                        .if < al a "9" >
                                jmp     bad_parm
                        .endif
;/*
;**  P1795 START
;*/
                        inc     es:NUM_NUM
                        push    ax
                        mov     al,es:NUM_ARG
                        .if <al gt es:NUM_NUM>
                            mov  es:NUM_NUM,al
                        .elseif <es:NUM_NUM gt al>
                            inc  es:NUM_ARG
                        .endif
                        pop     ax
;/*
;**  P1795 END
;*/
                        dec     si
                        call    getnum
                        .if < es:NUM_ARG a 3>
                                jmp     bad_parm
                        .endif
                        .if z                           ; Dir Parm
                                .if < bx b 2> OR
                                .if < bx a 1024>
                                        jmp     bad_parm
                                .endif

;/*
;**  NOTE: Since DIRNUM is the 3rd numeric arg and SSIZE is the first,
;**       we know the desired sector size has been given.
;*/

                                mov     di, es:SSIZE
                                mov     cl, 5          ; 32 bytes per dir ent
                                                       ; DI is number of dirents
                                shr     di, cl         ;    in a sector
                                mov     ax, bx
                                xor     dx, dx
                                div     di             ; Rem in DX is partial
                                or      dx, dx         ;    dir sector
                                .if ne         ; User Specified OK number
                                                       ; Figure how much user
                                        sub     di, dx ;    goofed by
                                        add     bx, di ; Round UP by DI entries
                                .endif

                                mov     es:DIRNUM, bx
                        .endif

                        .if < es:NUM_ARG eq 2 >        ; Sector Parm
                                mov     al, es:SEC_SHIFT

                                .if < bx ne 128 > AND
                                .if < bx ne 256 > AND
                                .if < bx ne 512 > AND
                                .if < bx ne 1024 >
                                        jmp     bad_parm
                                .endif

                                .if <bx eq 1024>
                                        mov     bx, 512
                                .endif

                                mov     es:SSIZE, bx

                                ; 512 is the default & 1024 is not allowed
                               .if<bx eq 128>                       ;          
                                   mov   es:SEC_SHIFT, 5            ;          
                               .elseif<bx eq 256>                   ;          
                                   mov   es:SEC_SHIFT, 6            ;          
                               .endif                               ;          

                        .endif

                        .if < es:NUM_ARG eq 1 >
                                .if < ebx b 16 > OR      ; 16KB      ;          
                                .if < ebx a 524288 >     ; 512MB     ;          
                                        jmp     bad_parm
                                .endif

                                mov     es:DEV_SIZE, ebx             ;          
                        .endif

next_parm:              lodsb
                .endif
        .endwhile

args_donej:
        jmp     args_done

bad_parm:
        mov     dx, msg_vdisk_inv_parm  ; Select Invalid Parameter Msg
        mov     ds, t_ds                ; ds = VDiskData
        call    print
devabort:                       ; here the correct message is printed already
        xor     ax, ax                  ;Indicate no devices
        jmp     setbpb                  ;and return

args_done:

        mov     ds, t_ds                ; DS = VDiskData
        assume ds:VDIskData, es:NOTHING, SS:NOTHING
;/*
;**       Allocate RAM for the virtual disk. If allocate FAILs print msg
;**       and goto devabort. Otherwise es:VDisk_Base is set to base
;**       address of VDisk RAM. Also DEV_SIZE must reflect the
;**       size of the VDisk in K. (in case size was adjusted to 16K)
;**       Can use most registers here. Optimize based on AllocPhys
;**       Register Usage.
;*/

        mov     eax, DEV_SIZE                   ; User's Size        ;          
        call    GetRAM
        .if c
                mov     eax, 16                 ; Try 16K
                mov     dh, 1                   ; Above 1M ; Does this work?
                call    GetRAM
                .if c                           ; Insufficient Memory ?
                        mov     dx, msg_vdisk_insuff_mem
                        call    print
                        jmp     devabort
                .else
                        mov     DEV_SIZE, 16
                .endif
        .endif

        mov     VDisk_Base, eax                 ; save VDisk_Base
;/*
;**  5.  Initialize the DOS volume in the VDisk memory
;**
;**
;**  We must figure out what to do.
;**  All values are set so we can call VDisk_IO to read and write disk
;**  SSIZE is user sector size in bytes
;**  DIRNUM is user directory entries
;**  DEV_SIZE is size of device in K bytes
;**
;**
;**  Figure out total number of sectors in logical image
;*/

        mov     eax, DEV_SIZE                                        ;          
        mov     ecx, 1024                                            ;          
        mul     ecx             ; DX:AX is size in bytes of image    ;          
        movzx   ecx,SSIZE                                            ;          
        div     ecx             ; EAX is total sectors               ;          
                                ; Any remainder in DX is ignored     ;          
        .if <eax b 10000h>                                           ;          
           mov     SECLIM, ax                                        ;          
           mov     TotalSectors, eax                                 ;          
        .else                                                        ;          
           mov     BIGSEC, eax                                       ;          
           mov     TotalSectors, eax                                 ;          
        .endif                                                       ;          
;/*                                                                  ;          
;**  Compute # of directory sectors
;*/

        mov     ax, DIRNUM
        shl     ax, 5           ; Mult by 32 bytes per entry
                                ; Don't need to worry about overflow, # ents
                                ;     is at most 1024
        xor     dx, dx
        div     SSIZE
        .if < dx ne 0 >
                inc     ax
        .endif

        mov     DIRSEC, ax      ; AX is # sectors for root dir
        add     ax, 2           ; One reserved, At least one FAT sector???
        cwde                    ; sign extend eax                    ;          

        .if < eax ae TotalSectors >                                  ;          

                mov     DIRNUM, 16      ; Smallest reasonable number
                xor     dx, dx
                mov     ax, 512         ; 16*32 = 512 bytes for dir
                div     SSIZE
                .if < dx ne 0 >
                        inc     ax
                .endif

                mov     DIRSEC, ax      ; AX is # sectors for root dir

                add     ax, 2           ; 0ne reserved, At least one FAT sector

                .if < eax ae TotalSectors >                          ;          
                        mov     dx, msg_vdisk_insuff_mem
                        call    print
                        jmp     devabort
                .endif
        .endif

;/*
;**  This is the rest of the code that sets up the BPB. It is
;**  tuned for 12 bit FAT table format. DO NOT TOUCH
;*/


CLUSHOME:

;/*
;**  Figure a reasonable cluster size
;*/

        mov     eax, TotalSectors; AX is total sectors on disk       ;          
        movzx   ebx, RESSEC
        sub     eax, ebx      ; Sub off reserved sectors             ;          
        mov     cl, FATNUM      ; CX is number of FATs
        xor     ch, ch
FATSUB:
        movzx   ebx, FATSEC                                          ;          
        sub     eax, ebx        ; Sub off FAT sectors                ;          
        loop    FATSUB                                               ;          
        movzx   ebx, DIRSEC                                          ;          
                               ; Sub off directory sectors, AX is # data sectors
        sub     eax, ebx                                             ;          
        mov     ebx, 1           ; Start at 1 sec per alloc unit     ;          
        cmp     eax, 4096-10                                         ;          
        jb      cset            ; 1 sector per cluster is OK         ;          
        mov     ebx, 2                                               ;          
        cmp     eax, (4096-10) * 2                                   ;          
        jb      cset            ; 2 sector per cluster is OK         ;          
        mov     ebx, 4                                               ;          
        cmp     eax, (4096-10) * 4                                   ;          
        jb      cset            ; 4 sector per cluster is OK         ;          
        mov     ebx, 8                                               ;          
        cmp     eax, (4096-10) * 8                                   ;          
        jb      cset            ; 8 sector per cluster is OK         ;          
        mov     ebx, 16          ;                                   ;          
        cmp     eax, (4096-10) * 16                                  ;          
        jb      cset            ; 16 sector per cluster is OK        ;          
        mov     ebx, 32          ;                                   ;          
        cmp     eax, (4096-10) * 32                                  ;          
        jb      cset            ; 32 sector per cluster is OK        ;          
        mov     ebx, 64          ;                                   ;          
        cmp     eax, (4096-10) * 64                                  ;          
        jb      cset            ; 64 sector per cluster is OK        ;          
        mov     ebx, 128         ;                                   ;          
        cmp     eax, (4096-10) * 128                                 ;          
        jb      cset            ; 128 sector per cluster is OK       ;          
        mov     ebx, 256         ;                                   ;          
        cmp     eax, (4096-10) * 256                                 ;          
        jb      cset            ; 256 sector per cluster is OK       ;          
        mov     ebx, 512        ;                                    ;          
        cmp     eax, (4096-10) * 512                                 ;          
        jb      cset            ; 512 sector per cluster is OK       ;          
        mov     ebx, 1024       ;                                    ;          
        cmp     eax, (4096-10) * 1024                                ;          
        jb      cset            ; 1024 sector per cluster is OK      ;          
        mov     ebx, 2048       ; 2048 sector per cluster is OK      ;          
CSET:

;/*
;**  Figure FAT size. AX is reasonable approx to number of DATA sectors
;**  BX is reasonable sec/cluster
;*/


        xor     edx, edx                                             ;          
        div     ebx         ; AX is total clusters, ignore remainder ;          
                                ;  can't have a "partial" cluster
        mov     ecx, eax                                             ;          
        shr     ecx, 1                                               ;          
        .if c                                                        ;          
                inc     ecx                                          ;          
        .endif                                                       ;          

        add     eax, ecx   ; AX is Bytes for fat (1.5 * # of clusters)          
        add     eax, 3          ; Plus two reserved clusters         ;          
        xor     edx, edx                                             ;          
        movzx   ecx, SSIZE                                           ;          
        div     ecx             ; AX is # sectors for a FAT this size;          
        .if <edx ne 0>          ; if remainder                       ;          
                inc     eax     ; Round UP                           ;          
        .endif                  ;                                    ;          

                                ; AX is # sectors for FAT
        xchg    ax, FATSEC      ; Set newly computed value
        xchg    bl, CSIZE       ; Set newly computed value
        cmp     bl, CSIZE       ; Did we compute a different size?
        jnz     CLUSHOME        ; Keep performing FATSEC and CSIZE computation
                                ;   until the values don't change.
        cmp     ax, FATSEC      ; Did we compute a different size?
        jb      CLUSHOME        ; Keep performing FATSEC and CSIZE computation
                                ;   until the values don't change.   ;          
        mov     FATSEC, ax                                           ;          

;/*
;**  BPB is now all set !!!
;**
;**  FORMAT THE VDISK IN MEMORY !!!
;**
;**  Here DS = VDiskData and ES is unused.
;**  In order to use VDisk_IO for formating the VDisk, we need
;**  to set ES:BX to point to a dummy write request packet called
;**  Dummy_ReqPacket in the VDiskData segment, and DS to VDiskData.
;**  DTAs are set from either the DUMMY BOOT RECORD or SECTOR_BUFFER
;**  in the VDiskData segment.
;**  Dummy_ReqPacket is a write packet that is completely intialized
;**  except for the DTA and StartSector.
;*/

        mov     ds, t_ds                ; DS = VDiskData
        mov     es, t_ds                ; ES = VDiskData
assume ds:VDiskData, es:VDiskData, ss:NOTHING

        mov     bx, offset Dummy_ReqPacket
                                        ; ES:BX = Dummy_ReqPacket

;/*
;**  WRITE BOOT SECTOR
;*/

        mov     [bx.IOstart], 0         ; Sector 0
        mov     esi,offset BOOT_SECTOR  ; DS:SI = DTA
        call    STUFF                   ; Do Request

;/*
;**  WRITE FIRST FAT SECTOR
;*/

        mov     edi, offset SECTOR_BUFFER
        xor     eax, eax                ; Initialize Buffer for FAT
        mov     ecx, 256
        cld
        rep     stosd                   ; EMPTY FAT

        mov     edi, offset SECTOR_BUFFER
        mov     dword ptr [di], 0FFFFF8H   ; PLUS 2 Directories

        inc     word ptr [bx.IOstart]   ; Sector 1
        mov     si, offset SECTOR_BUFFER
        call    STUFF

        inc     [bx.IOstart]            ; Setup Next sector
        mov     di, offset SECTOR_BUFFER
        mov     dword ptr [di], 0       ; Fix Buffer to EMPTY FAT

        mov     cx, FATSEC
        dec     cx                      ; First FAT sector already written
        jcxz    FATDONE
FATZERO:
        SaveReg <cx>
        mov     si, NORMAL_WRITE
        call    iniVDisk_IO
        inc     [bx.IOstart]            ; Next Sector (still from SECTOR_BUFFER)
        RestoreReg      <cx>
        loop    FATZERO
FATDONE:

        mov     si, offset VOLID        ; Volume Label Dir Ent ???
        call    STUFF

        inc     [bx.IOstart]            ; Next Sector
        mov     cx, DIRSEC
        dec     cx                      ; First Sector Dir Already Written
        jcxz    drive_set
DIRZERO:
        SaveReg <cx>
        mov     si, offset SECTOR_BUFFER
        call    STUFF
        inc     [bx.IOstart]            ; Next Sector(still from VOLID)
        RestoreReg      <cx>
        loop    DIRZERO
DRIVE_SET:

;/*
;**  Print out Report of VDisk parameters
;*/

assume ds:VDiskData, es:NOTHING, ss:NOTHING

        mov     eax, DEV_SIZE                                        ;          
        mov     bx, offset Var2
        call    itoa
        movzx   eax, SSIZE                                           ;          
        mov     bx, offset Var3
        call    itoa
        movzx   eax, DIRNUM                                          ;          
        mov     bx, offset Var4
        call    itoa

        mov     dx, msg_vdisk_report
        call    print

        mov     al, 1                   ; Number of ramdrives
;/*
;**  NOTE FALL THROUGH!!!!!!!
;*/

;/*
;**  SETBPB - Set INIT packet I/O return values
;**
;**  This entry is used in ERROR situations to return
;**  a unit count of 0 by jumping here with AL = 0.
;**  The successful code path falls through to here
;**  with AL = 1
;**
;**  ENTRY
;**          AL = INIT packet unit count
;**          DS = VDiskData
;**  EXIT
;**          AX = status to return to DOS
;**          Request Packet Set up for return from Init
;*/

SETBPB:
assume  ds:VDiskData, es:NOTHING, ss:NOTHING

;/*
;**  Restore ES:BX pointer to Original Init Request Packet and
;**  remove local variable stack frame.
;*/

        mov     ds, t_ds
        mov     bx, t_bx
        mov     es, t_es

        LeaveProc

;/*
;**  Set Units, End of CODE and DATA Segment, Address of BPB Array
;**  in Request packet
;*/

        mov     es:[bx.InitcUnit], al

        .if <al eq 0>
                mov     es:[bx.InitpEnd], 0
        .else
                mov     word ptr es:[bx.InitpEnd], offset DEVICE_END
                mov     word ptr es:[bx.InitpEnd+2], offset DATA_END
                mov     word ptr es:[bx.InitpBPB], offset INIT_BPB
                mov     word ptr es:[bx.InitpBPB+2], ds
        .endif

        ret

EndProc Init


BREAK   <GetRAM>
;/***************************************************************************
;*
;* FUNCTION NAME = GetRAM
;*
;* DESCRIPTION   = VDisk RAM Allocater
;*                 Calls DevHelp(AllocPhys) to acquire RAM for VDisk
;*
;* INPUT         = AX = Size in K Desired
;*
;* OUTPUT        =
;*
;* RETURN-NORMAL = 'C' Clear
;*                 EAX = Linear Base Address of RAM
;*
;* RETURN-ERROR  = NONE
;*
;**************************************************************************/

Procedure GetRAM,NEAR,LOCAL

        mov     ecx, 1024
        mul     ecx
        mov     ecx,eax                 ; ECX = Size of region in bytes
        push    ecx                                               ;          
                                        ; 0800h == Unpublished flag that will
                                        ;          allow VDISK to request more
                                        ;          than 4MB at init time.
        mov     eax,2                   ; EAX = Flags (VMDHA_FIXED)
        mov     dl, DevHlp_VMAlloc
        call    DevHelp
        pop     ecx                                               ;          
                                                                  ; Try high
       .IF c                                                      ;          
          mov     eax, 2+0800h                                    ;          
          mov     dl, DevHlp_VMAlloc                              ;          
          call    DevHelp                                         ;          
       .ENDIF                                                     ;          

        ret

EndProc GetRAM


BREAK   <iniVDisk_IO>
;/***************************************************************************
;*
;* FUNCTION NAME = iniVDisk_IO
;*
;* DESCRIPTION   = VDisk VDisk_IO Caller for Init
;*                 Saves Registers and calls VDisk_IO
;*
;*                 VDisk_IO is very register destructive, all this routine
;*                 does is provide a less destructive way to call VDisk_IO.
;*
;* INPUT         = Same as VDisk_IO
;*
;* OUTPUT        =
;*
;* RETURN-NORMAL = Same as VDisk_IO
;*
;* RETURN-ERROR  = NONE
;*
;**************************************************************************/

Procedure iniVDisk_IO,NEAR,LOCAL
assume ds:VDiskData, es:NOTHING, ss:NOTHING

        SaveReg <es,ds>
        pushad
        call    VDisk_IO
        popad
        RestoreReg      <ds,es>

        ret

EndProc iniVDisk_IO


BREAK   <GetNum>
;/***************************************************************************
;*
;* FUNCTION NAME = GetNum
;*
;* DESCRIPTION   = Get Number
;*                 Read an unsigned integer
;*
;*            This routine looks at DS:SI for a decimal unsigned integer.
;*            It is up to the caller to make sure DS:SI points to the start
;*            of a number. If it is called without DS:SI pointing to a valid
;*            decimal digit the routine will return 0. Any non decimal digit
;*            defines the end of the number and SI is advanced over the
;*            digits which composed the number. Leading "0"s are OK.
;*
;*            THIS ROUTINE DOES NOT CHECK FOR NUMBERS LARGER THAN WILL FIT
;*            IN 16 BITS. If it is passed a pointer to a number larger than
;*            16 bits it will return the low 16 bits of the number.
;*
;*            This routine uses the MUL instruction to multiply the running
;*            number by 10 (initial value is 0) and add the numeric value
;*            of the current digit. Any overflow on the MUL or ADD is ignored.
;*
;* INPUT         = DS:SI -> ASCII text of number
;*
;* OUTPUT        =
;*
;* RETURN-NORMAL = BX is binary for number
;*                 SI advanced to point to char after number
;*
;* RETURN-ERROR  = NONE
;*
;**************************************************************************/

Procedure getnum,NEAR,LOCAL
assume  DS:NOTHING,ES:NOTHING,SS:NOTHING

        xor     ebx, ebx                                             ;          
getnum1:
        lodsb
        sub     al, "0"
        .if ae
                .if < al be 9 >
                        cbw
                        cwde                                         ;          
                        xchg    eax, ebx                             ;          
                        mov     edx, 10                              ;          
                        mul     edx                                  ;          
                        add     ebx, eax                             ;          
                        jmp     getnum1
                .endif
        .endif

        dec     si
        ret

EndProc getnum


BREAK   <Print>
;/***************************************************************************
;*
;* FUNCTION NAME = Print
;*
;* DESCRIPTION   = Print a Message to StdOut
;*                 Prints a message using the MSG Retriever API
;*
;*                 This routine gets a message from the system message file
;*                 and prints it on StdOut.
;*
;* INPUT         = DS = VDiskData
;*                 DX  = ID of message to be printed:
;*                         msg_vdisk_inval_parm
;*                         msg_vdisk_insuff_mem
;*                         msg_vdisk_report
;*
;* OUTPUT        =
;*
;* RETURN-NORMAL = BX is binary for number
;*                 SI advanced to point to char after number
;*
;* RETURN-ERROR  = NONE
;*
;**************************************************************************/

Procedure print,NEAR,LOCAL
assume  DS:VDiskData,ES:NOTHING,SS:NOTHING


        .if < dx eq msg_vdisk_report >
                push    ds
                push    offset IvTable  ; Table of Variables
                push    IvCount         ; Number of Variables
        .else
                push    0
                push    0               ; No IvTable
                push    0               ; Number of Variables
        .endif

        push    ds
        push    offset MsgBuff          ; Message Buffer
        push    MAXMSG                  ; Buffer Length
        push    dx                      ; Message ID
        push    ds
        push    offset MsgFile          ; Msg File Name
        push    ds
        push    offset MsgLen           ; Address of MsgLen
        call    DOSGETMESSAGE

        push    1                       ; StdOut
        push    MsgLen                  ; Msg Length
        push    ds
        push    offset MsgBuff          ; Address of Msg Buffer
        call    DOSPUTMESSAGE

        ret

EndProc print


BREAK   <Itoa>
;/***************************************************************************
;*
;* FUNCTION NAME = Itoa
;*
;* DESCRIPTION   = Integer to ASCII Routine
;*                 Convert an unsigned 16 bit value as a decimal integer
;*                 with leading zero supression. Prints from 1 to 5 digits.
;*                 Value 0 is "0".
;*
;*                 Routine uses divide instruction and a recursive call. Maximum
;*                 recursion is four (five digit number) plus one word on stack
;*                 for each level.
;*
;* INPUT         = AX = Binary Value to Convert
;*                 DS:BX = Address for Destination
;*
;* OUTPUT        =
;*
;* RETURN-NORMAL = Destination Address filled in with string
;*
;* RETURN-ERROR  = NONE
;*
;**************************************************************************/

Procedure itoa,NEAR,LOCAL
assume  DS:NOTHING,ES:NOTHING,SS:NOTHING

        mov     cx, 10
        xor     dx, dx
        div     cx                      ; DX is low digit, AX is higher digits
        or      ax, ax
        .if nz
                SaveReg <dx>            ; Save this digit
                call    itoa            ; Print higher digits first
                RestoreReg      <dx>    ; Recover this digit
        .endif
        add     dl, "0"                 ; Convert to ASCII
        mov     [bx], dl                ; Move Digit to Target
        inc     bx                      ; Advance Target Pointer

        ret

EndProc itoa


BREAK   <Stuff>
;/***************************************************************************
;*
;* FUNCTION NAME = Stuff
;*
;* DESCRIPTION   = Converts DTA to physical address, stuffs
;*                 it into the Request packet, and calls iniVDisk_IO
;*
;* INPUT         = DS:SI = Virtual DTA
;*                 ES:BX = Virtual Request Packet
;*                         w/All fields filled except DTA
;*
;* OUTPUT        = NONE
;*
;* RETURN-NORMAL =
;* RETURN-ERROR  = NONE
;*
;**************************************************************************/

Procedure stuff,NEAR,LOCAL
assume  DS:VDiskData, ES:VDiskData, SS:NOTHING

        mov     di,bx                   ; save bx
        mov     dl,DevHlp_VirtToPhys
        call    DevHelp                 ; Convert to physical address
        xchg    di,bx                   ; Get Address of request packet

        mov     word ptr [bx.IOpData+2], ax ; Stuff Physical DTA
        mov     word ptr [bx.IOpData], di
        mov     si, NORMAL_WRITE        ; Write Flag to VDisk_IO
        call    iniVDisk_IO

        ret

EndProc stuff


VDisk_End       LABEL   BYTE

VDiskCode       ends
        end
