
TITLE EDISK - INSTALLABLE DEVICE DRIVER FOR A HIGH MEMORY VIRTUAL DISK
;11/10/85
;       This ram disk uses the 128K portion of memory above 640K be-
;       ginning at segment D000:0000 and ending at F000:0000.
;       The original code comes from the DOS 2.0 Technical Manual, later
;       modified by Rex McAnally to allow variable sizes. (I have
;       included his original notes below.)
;       The code has been modified in accordence with the POWER USER
;       column in PC Magazine, Oct. 15, 1985.  It includes the following
;       features:
;                a. It will assemble with both versions 1 & 2 MACRO
;                   Assenbler
;
;                b. The program initializes the 128K RAM to prevent the
;                   dreaded PARITY error (DOS won't do it)
;
;                c. The contents of the Ram disk will survive a warm boot
;                   (Ctl-Alt-Del)
;
;       You have to provide the 128K ram memory addressable at segment D000
;       Note: This 128K was reserved for ROM based programs like those found
;             for the PC jr.  None are available for the PC/PC-XT so the
;             memory space is normally unusable & wasted.  It can't be added
;             to DOS as it is'nt continuous with the 640K user's area.
;
;       The easiest way to add this memory to your PC is to buy an IBM
;       64/256K Memory Expansion Option board, install 128K on it, and
;       set its DIP switches for a start address of D000 like so:
;
;                 switch     1   2   3   4   5   6   7   8
;                 position  off off  on off off  on off off
;
;       Not all memory or combo memory boards will do.  Most don't allow
;       you to choose the absolute starting segment address.
;
;       To use this program:
;
;                1.  Assemble this program using the MACRO Assembler
;
;                2.  Link the resultant .OBJ file using LINK (DOS utility)
;
;                3.  Convert the resultant .EXE file to binary form using
;                    EXE2BIN (also a DOS utility), and name it EDISK.SYS
;
;                4.  Copy the EDISK.SYS file to your BOOT/Root directory
;
;                5.  Add DEVICE=EDISK.SYS as the **FIRST** line of your
;                    CONFIG.SYS file.  It   M-u-s-t  be first as the program
;                    has to know whether it sees a first or a subsequent
;                    Boot.  Some Devive drivers cause a double Boot and
;                    if they precede EDISK.SYS they will cause EDISK to
;                    think an initial cold Boot is a warm Boot.  This
;                    screws up the Ram Disk data preserve function and
;                    will most likely cause your system to hang up.
;
;       A driver that adds 64K above the 640K user area to DOS (IF you have
;       Ram there), such as MEMM.SYS works OK as long as it follows the EDISK
;       driver in CONFIG.SYS.
;
;       I have marked the lines I modified with  EM  and those I added
;       with  EM-A.  By now this code has been pretty well hacked and
;       could stand some cleaning up, but it works fine as is.  Feel free
;       to clean it up or improve it further.
;
;                         ENJOY,
;                                  Bill Wacker
;
;
;Rex McNally's unmodified notes follow:
;
;       This was originally the virtual disk device contained in
;       the DOS 2.0 manual (listing only).
;
;       The code has been modified to allow a ram disk of more than
;       180k.  This can be specified in 2 ways.
;       (1). Changing the RAMDISK equate statment to the desired size in
;              1 K increments, reassembling, linking, using EXE2BIN,
;              to convert to "com" file format. (see DOS 2.0 manual on
;              device drivers.
;       (2). Rename the "VDISK.COM" file to "VDISK.XXX",
;            Using DEBUG you can modify the size by changing offsets
;            29-30 hex in the file. offset 29 = low order byte, 30 = h/o
;            These 2 bytes contain the size in 1K increments.
;
;       The current maximum limit is 512K (0200 hex).
;
;       All modified lines of code have been labeled with <RM> comments.
;       Please feel free to play with this, I am considering enhancing it
;       to ask the operator for a size before completing the device
;       installation.
;       Rex McAnally
;
              PAGE
CSEG          SEGMENT PARA PUBLIC 'CODE'
;
;***              MACRO DEFINITIONS
;
STATUS        MACRO    STATE,ERR,RC
              IFIDN    <STATE>,<DONE>
                OR     ES:WORD PTR SRH_STA_FLD[BX],0100H
              ENDIF
              IFIDN    <STATE>,<BUSY>
                OR     ES:WORD PTR SRH_STA_FLD[BX],0200H
              ENDIF
              IFIDN    <ERR>,<ERROR>
                OR     ES:WORD PTR SRH_STA_FLD[BX],1000H
              ENDIF
              IFNB     <RC>
                OR     ES:WORD PTR SRH_STA_FLD[BX],RC
              ENDIF
              ENDM
;
;***Equates
;
;***Size
;
SECT          EQU      512         ; size of a sector                  <RM>
RAMDISK       EQU      128         ; size of ramdisk in 1k increments <RM> <EM>
FATSIZE       EQU      1           ; # sect in 1 copy of fat           <RM>
;
;***Read/Write
;
SRH           EQU      0           ;Static request header start
SRH_LEN       EQU      13          ;  "       "      "    length
SRH_LEN_FLD   EQU      SRH         ;  "       "      "    length field
SRH_UCD_FLD   EQU      SRH+1       ;  "       "      "    unit code field
SRH_CCD_FLD   EQU      SRH+2       ;  "       "      "    command code fld
SRH_STA_FLD   EQU      SRH+3       ;  "       "      "    status field
SRH_RES_FLD   EQU      SRH+5       ;  "       "      "    reserved area fld
MD            EQU      SRH+SRH_LEN ;Media descriptor byte
MD_LEN        EQU      1           ;  "       "        "  length
DTA           EQU      MD+MD_LEN   ;Disk transfer address
DTA_LEN       EQU      4           ;DTA length
COUNT         EQU      DTA+DTA_LEN ;Byte/Sector count
COUNT_LEN     EQU      2           ;      "       "   length
SSN           EQU      COUNT+COUNT_LEN ;Starting sector number
SSN_LEN       EQU      2               ;   "       "       "   length
;
;***Media check
;
RET_BYTE      EQU      MD+MD_LEN   ;Byte returned from driver
;
;***Build BPB
;
BPBA_PTR      EQU      DTA+DTA_LEN
BPBA_PTA_LEN  EQU      4
;
;***Init
;
UNITS         EQU      SRH+SRH_LEN
UNITS_LEN     EQU      1
BR_ADDR_0     EQU      UNITS+UNITS_LEN
BR_ADDR_1     EQU      BR_ADDR_0+2
BR_ADDR_LEN   EQU      4
BPB_PTR_OFF   EQU      BR_ADDR_0+BR_ADDR_LEN
BPB_PTR_SEG   EQU      BPB_PTR_OFF+2
;
;
VDSK          PROC     FAR
              ASSUME   CS:CSEG,ES:CSEG,DS:CSEG
BEGIN:
START         EQU      $
;
;***Special device header
;
NEXT_DEV      DD       -1          ;Pointer to next device
ATTRIBUTE     DW       2000H       ;Block device (NON-IBM FORMAT)
STATEGY       DW       DEV_STATEGY ;Pointer to device stategy
INTERRUPT     DW       DEV_INT     ;Pointer to device interrupt handler
DEV_NAME      DB       1           ;Number of block devices
              DB       7 DUP(?)    ;7 bytes of filler
RH_OFF        DW       ?           ;Request header offset
RH_SEG        DW       ?           ;Request header segment
;
;***BIOS parameter block
;
BPB           EQU      $
              DW       SECT                   ;Sector size             <RM>
              DB       1                      ;Sectors/allocation unit
              DW       1                      ;Number of reserved sectors
              DB       2                      ;Number of FATS
              DW       64                     ;# of directory entries  <RM>
RAMSECT1      DW       256                    ;Total number of sectors     <EM>
              DB       0FEH                   ;Media descriptor
FATSIZE1      DW       FATSIZE                ;# of sectors used by FAT<RM>
BPB_PTR       DW       BPB                    ;BIOS parameter blk ptr array
;
;***Current virtual disk information
;
              DB       'SIZE'      ;                                   <RM>
RAMSIZEK      DW       RAMDISK     ; size in 1k increments             <RM>
RAMSIZEP      DW       RAMDISK*(1024/16)     ; size in paragraphs      <RM>
;
TOTAL         DW       ?           ;Total sectors to transfer
VERIFY        DB       0           ;Verify 1=YES, 0=NO
START_SEC     DW       0           ;Starting sector number
VDISK_PTR     DW       0           ;Starting sector of virtual disk
USER_DTA      DW       ?,?         ;Pointer to callers DTA                <EM>
BOOT_REC      EQU      $           ;Dummy DOS boot record
              DB       3 DUP(0)    ;3 byte jump to boot code (not bootable)
              DB       'IBM  2.0'
              DW       SECT        ;Number of bytes in a sector        <RM>
              DB       1           ;1 sector per allocation unit
              DW       1           ;1 reserved sector
              DB       2           ;2 FATS
              DW       64          ;Number of directory entries
RAMSECT2      DW       256                  ; total sectors in image  <RM><EM>
              DB       0FEH        ;Single sided 9 sector
FATSIZE2      DW       FATSIZE     ;Number of sectors in FAT           <RM>
;
;***Function table
;
FUNTAB        LABEL    BYTE
              DW       INIT        ;Initialization
              DW       MEDIA_CHECK ;Media check (block only)
              DW       BUILD_BPB   ;Build BPB
              DW       IOCTL_IN    ;IOCTL input
              DW       INPUT       ;Input (read)
              DW       ND_INPUT    ;Non destructive input no wait (ch only)
              DW       IN_STAT     ;Input status
              DW       IN_FLUSH    ;Input flush
              DW       OUTPUT      ;Output (write)
              DW       OUT_VERIFY  ;Output (write) with verify
              DW       OUT_STAT    ;Output status
              DW       OUT_FLUSH   ;Output flush
              DW       IOCTL_OUT   ;IOCTL output
;
;***Local procedures
;
IN_SAVE       PROC     NEAR
              MOV      AX,ES:WORD PTR DTA[BX] ;Save callers DTA
              MOV      CS:USER_DTA,AX
              MOV      AX,ES:WORD PTR DTA+2[BX]
              MOV      CS:USER_DTA+2,AX
              MOV      AX,ES:WORD PTR COUNT[BX]
              XOR      AH,AH
              MOV      CS:TOTAL,AX
              RET
IN_SAVE       ENDP
;
;
CALC_ADDR     PROC     NEAR
              MOV      AX,CS:START_SEC       ;Get starting sector number
              MOV      CX,SECT/16            ;Move 512 to CX segment style
              MUL      CX                    ;Multiply to get actual sector
              MOV      DX,CS:VDISK_PTR       ;Get segment of virtual disk
              ADD      DX,AX                 ;Add that seg to initial seg
              MOV      DS,DX                 ;Save that as actual segment
              XOR      SI,SI                 ;It's on a para boundary
              MOV      AX,CS:TOTAL           ;Total # of sectors to read
              MOV      CX,SECT               ;Bytes per sector         <RM>
              MUL      CX                    ;Multiply to get copy length
              OR       AX,AX                 ;Check for greater than 64K
              JNZ      MOVE_IT
              MOV      AX,0FFFFH             ;Move in for 64K
MOVE_IT:      XCHG     CX,AX                 ;Move length to CX
              RET
CALC_ADDR     ENDP


SECTOR_READ   PROC     NEAR
              CALL     CALC_ADDR             ;Calculate the starting sector
              MOV      ES,CS:USER_DTA+2      ;Set dest (ES:DI) to point
              MOV      DI,CS:USER_DTA        ;**to callers DTA
;
;***Check for DTA wrap in case we came through via verify
;
              MOV      AX,DI       ;Get offset of DTA
              ADD      AX,CX       ;Add copy length to it
              JNC      RC          ;Carry flag=0, no wrap
              MOV      AX,0FFFFH   ;Max length
              SUB      AX,DI       ;Subtract DTA offset from max
              MOV      CX,AX       ;Use that as copy length to avoid wrap
RC:     REP   MOVSB                ;Do the read
              RET
SECTOR_READ   ENDP


SECTOR_WRITE  PROC     NEAR
              CALL     CALC_ADDR              ;Calculate starting sector
              PUSH     DS
              POP      ES                     ;Establish addressability
              MOV      DI,SI                  ;ES:DI point to disk
              MOV      DS,CS:USER_DTA+2       ;DS:SI point to callers DTA
              MOV      SI,CS:USER_DTA
;
;***Check for DTA wrap
;
              MOV      AX,SI       ;Move DTA offset to AX
              ADD      AX,CX       ;Add copy length to offset
              JNC      WC          ;Carry flag=0, no segment wrap
              MOV      AX,0FFFFH   ;Move in max copy length
              SUB      AX,SI       ;Subtract DTA offset from max
              MOV      CX,AX       ;Use as new copy length to avoid wrap
WC:     REP   MOVSB                ;Do the write
              RET
SECTOR_WRITE  ENDP
              PAGE
;
;***Device strategy
;
DEV_STATEGY:  MOV      CS:RH_SEG,ES ;Save segment of request header ptr
              MOV      CS:RH_OFF,BX ;Save offset of     "      "     "
              RET
;
;***Device interrupt handler
;
DEV_INT:      CLD
;
;***Save state of machine
;
              PUSH     DS
              PUSH     ES
              PUSH     AX
              PUSH     BX
              PUSH     CX
              PUSH     DX
              PUSH     DI
              PUSH     SI
;
;***Do branch according to function passed
;
              MOV      AL,ES:[BX]+2          ;Get function byte
              ROL      AL,1                  ;Get offset into table
              LEA      DI,FUNTAB             ;Get address of function table
              XOR      AH,AH
              ADD      DI,AX
              JMP      WORD PTR[DI]
;
;***Initialization
;
INIT:
              MOV      AX,CS:RAMSIZEK        ; compute size in # sect  <RM>
              CMP      AX,171                ;                         <RM>
              JB       INIT20                ;                         <RM>
              MOV      CS:WORD PTR FATSIZE1,2 ;                        <RM>
              MOV      CS:WORD PTR FATSIZE2,2 ;                        <RM>
              CMP      AX,341                ;                         <RM>
              JB       INIT20                ;                         <RM>
              MOV      CS:WORD PTR FATSIZE1,3 ;                        <RM>
              MOV      CS:WORD PTR FATSIZE2,3 ;                        <RM>
INIT20:                                      ;                         <RM>
              SHL      AX,1                  ;                         <RM>
              MOV      CS:RAMSECT1,AX        ;                         <RM>
              MOV      CS:RAMSECT2,AX        ;                         <RM>
              MOV      CL,5                  ; compute size in paragr  <RM>
              SHL      AX,CL                 ;                         <RM>
              MOV      CS:RAMSIZEP,AX        ;                         <RM>
;                                                                      <RM>
              PUSH     CS
              POP      DX                    ;Current CS to DX
              LEA      AX,CS:VDISK           ;Get address of virtual disk
              MOV      CL,4
              ROR      AX,CL                 ;Divide by 16 (paragraph form)
              ADD      DX,AX                 ;Add to current CS value
              MOV      CS:VDISK_PTR,0D000H   ;Save as start seg of vdisk   <EM>
           ;  MOV      AX,CS:RAMSIZEP        ;Add size of vdisk in para<RM><EM>
           ;  ADD      DX,AX                  ;**segment of virtual disk   <EM>
              MOV      ES:WORD PTR BR_ADDR_0[BX],0
              MOV      ES:BR_ADDR_1[BX],DX    ;Make that the break address
              MOV      ES:BYTE PTR UNITS[BX],1 ;Number of diskette units
              LEA      DX,BPB_PTR            ;Get addr of BPB ptr array
              MOV      ES:BPB_PTR_OFF[BX],DX  ;Save offset in data packet
              MOV      ES:BPB_PTR_SEG[BX],CS  ;Save segment in data packet

              PUSH     DS                     ;Save DS                   <EM-A>
              MOV      AX,40H                 ;BIOS data segment         <EM-A>
              MOV      DS,AX                  ;Set up segment register   <EM-A>
              CMP      WORD PTR DS:[0072H],1234H  ;Check reset mode      <EM-A>
              POP      DS                     ;Restore DS                <EM-A>
              JNZ      COLD_START             ;Initialize Vdisk          <EM-A>
              JMP      HOT_START              ;Skip if boot from keyboard<EM-A>


COLD_START:          ; Initialize memory to avoid PARITY errors          <EM-A>
              SUB      AX,AX                 ;Byte to write (null)       <EM-A>
              MOV      ES,CS:VDISK_PTR       ;Get start addr of 1st 64K sector
              MOV      CX,8000H              ;Number of bytes to write   <EM-A>
              REP      STOSW                 ;Repeat no. of times in CX  <EM-A>
              MOV      CX,ES                 ;Move to next sector by     <EM-A>
              ADD      CX,1000H              ;adding 1000H to ES         <EM-A>
              MOV      ES,CX                 ;New start addr at E000H    <EM-A>
              MOV      CX,8000H              ;Reset CX counter to 8000H  <EM-A>
              REP      STOSW                 ;Repeat write in second sect<EM-A>
              MOV      ES,CS:VDISK_PTR       ;Restore addr to 1st seg    <EM-A>

              XOR      DI,DI                  ;Zero out DI (Boot record)
              LEA      SI,BOOT_REC            ;Address of boot record
              MOV      CX,24
        REP   MOVSB                           ;Copy 24 bytes of boot record
              MOV      CS:WORD PTR START_SEC,1
              MOV      AX,FATSIZE1           ;                         <RM>
              MOV      CS:WORD PTR TOTAL,AX  ;                         <RM>
              CALL     CALC_ADDR              ;Calc addr of logical sect 1
              PUSH     DS
              POP      ES
              MOV      DI,SI                  ;Move that address to ES:DI
              XOR      AL,AL
        REP   STOSB                           ;Zero out FAT area
              MOV      DS:BYTE PTR[SI],0FEH   ;Set the first FAT entry
              MOV      DS:BYTE PTR 1[SI],0FFH
              MOV      DS:BYTE PTR 2[SI],0FFH
              PUSH     DS                     ;Save pointer to FAT on
              PUSH     SI                     ;**the stack
              MOV      AX,CS:WORD PTR START_SEC ;                      <RM>
              ADD      AX,CS:WORD PTR TOTAL     ;                      <RM>
              MOV      CS:WORD PTR START_SEC,AX ;                      <RM>
;DEL          MOV      CS:WORD PTR TOTAL,2                             <RM>
              CALL     CALC_ADDR              ;Calc addr of logical sect 3
              PUSH     DS
              POP      ES
              MOV      DI,SI                  ;Move that address to ES:DI
              POP      SI
              POP      DS                     ;Restore addr of 1st entry
        REP   MOVSB                           ;Copy first FAT to second FAT
              MOV      AX,CS:WORD PTR START_SEC ;                      <RM>
              ADD      AX,CS:WORD PTR TOTAL     ;                      <RM>
              MOV      CS:WORD PTR START_SEC,AX ;                      <RM>
;DEL          MOV      CS:WORD PTR START_SEC,5                         <RM>
              MOV      CS:WORD PTR TOTAL,4
              CALL     CALC_ADDR              ;Calc addr of LS 5 (start dir
              XOR      AL,AL
              PUSH     DS
              POP      ES                     ;Set up ES:DI to point to it
              XOR      DI,DI
        REP   STOSB                           ;Zero out directory
              MOV      ES,CS:RH_SEG           ;Restore ES:BX to req header
              MOV      BX,CS:RH_OFF
              STATUS   DONE,NOERROR,0         ;Set status done (noerror)

              LEA      DX,COLD_MESSAGE        ;Choose initialize msg    <EM-A>
              JMP      SHORT MESSAGE          ;Go display it            <EM-A>
COLD_MESSAGE  DB       "Hi-RAM disk installed.",13,"$"        ;         <EM-A>
HOT_MESSAGE   DB       "Hi RAM disk preserved.",13,"$"        ;         <EM-A>
HOT_START:    LEA      DX,HOT_MESSAGE         ;Choose preserve msg      <EM-A>
MESSAGE:      PUSH     CS                     ;Current segment          <EM-A>
              POP      DS                     ;Set DS to it             <EM-A>
              MOV      AH,9                   ;DOS print string call    <EM-A>
              INT      21H                    ;Print the string         <EM-A>

              JMP      EXIT
;
;***Media check
;
MEDIA_CHECK:  MOV      ES:BYTE PTR RET_BYTE[BX],1
              STATUS   DONE,NOERROR,0         ;turn on the done bit
              JMP      EXIT
;
;***Build BIOS parameter block
;
BUILD_BPB:    PUSH     ES                     ;Save SRH segment
              PUSH     BX                     ;Save SRH offset
              MOV      CS:WORD PTR START_SEC,0
              MOV      CS:WORD PTR TOTAL,1
              CALL     CALC_ADDR             ;Calc addr of 1st sector
              PUSH     CS
              POP      ES
              LEA      DI,BPB                 ;Addr of BIOS parameter block
              ADD      SI,11
              MOV      CX,13                  ;Length of BPB
        REP   MOVSB
              POP      BX                     ;Restore offset of SRH
              POP      ES                     ;Restore segment of SRH
              LEA      DX,BPB                 ;Get BPB array pointer
              MOV      ES:BPBA_PTR[BX],DX     ;Save ptr to BPB table
              MOV      ES:BPBA_PTR+2[BX],CS
              MOV      ES:DTA[BX],DX          ;Offset to sector buffer
              MOV      ES:DTA+2[BX],CS
              STATUS   DONE,NOERROR,0
              JMP      EXIT
;
;***The following entries are not supported by this device
;
IOCTL_IN:
IOCTL_OUT:
ND_INPUT:                          ;Non destructive input no wait (ch only)
IN_STAT:                           ;Input status      "     "
IN_FLUSH:                          ;Input flush
OUT_STAT:                          ;Output status
OUT_FLUSH:                         ;Output flush
;
;***Disk read
;
INPUT:        CALL     IN_SAVE                ;Call the initial save rtn
              MOV      AX,ES:WORD PTR SSN[BX] ;Set starting sector number
              MOV      CS:START_SEC,AX        ;Save starting sector number
              MOV      AX,ES:WORD PTR COUNT[BX]
              MOV      CS:TOTAL,AX            ;Save # sectors to transfer
              CALL     SECTOR_READ            ;Read in that many sectors
              MOV      BX,CS:RH_OFF           ;Restore ES:BX as req header
              MOV      ES,CS:RH_SEG
              STATUS   DONE,NOERROR,0         ;Set status word (done, noerr
              JMP      EXIT
;
;***Disk write
;
OUTPUT:       CALL     IN_SAVE                ;Output (write)
              MOV      AX,ES:WORD PTR SSN[BX] ;Get starting sector number
              MOV      CS:START_SEC,AX        ;Set    "        "      "
              MOV      AX,ES:WORD PTR COUNT[BX]
              MOV      CS:TOTAL,AX            ;Save total sectors to write
              CALL     SECTOR_WRITE
              MOV      BX,CS:RH_OFF           ;Restore ES:BX as req header
              MOV      ES,CS:RH_SEG
              CMP      CS:BYTE PTR VERIFY,0   ;Write verify set
              JZ       NO_VERIFY              ;No, no write verify
              MOV      CS:BYTE PTR VERIFY,0   ;Reset verify indicator
              JMP      INPUT
NO_VERIFY:    STATUS   DONE,NOERROR,0         ;Set status done, no error
              JMP      EXIT
OUT_VERIFY:   MOV      CS:BYTE PTR VERIFY,1   ;Set verify flag
              JMP      OUTPUT                 ;Branch to output routine
;
;***Common exit
;
EXIT:         POP      SI
              POP      DI
              POP      DX
              POP      CX
              POP      BX
              POP      AX
              POP      ES
              POP      DS
              RET
;
;
E_O_P:
;***Macro to align the virtual disk on a paragraph boundary
IF ($-START) MOD 16
ORG           ($-START)+16-(($-START) MOD 16)
ENDIF
VDISK         EQU      $
VDSK          ENDP
CSEG          ENDS
              END      BEGIN
                                                                                                 
