T. Ment
2020-06-23 03:10:55 UTC
IBM provided source code for vdisk 3.3 but omitted some dependencies as
mentioned in the .asm file:
Message text for VDISK is in module VDISKMSG.
include versiona.inc
In DOS version 4.0 they bundled everything into vdisk.asm, and now it
builds with MASM 4.0.
Hard to find, but PCjs Machines had it. And if that fails, here it is.
PAGE ,132
TITLE VDISK - Virtual Disk Device Driver
;VDISK simulates a disk drive, using Random Access Memory as the storage medium.
;This program is meant to serve as an example of a device driver. It does not
;reflect the current level of VDISK.SYS.
;(C) Copyright IBM Corporation, 1984,1985,1988
;Licensed Material - Program Property of IBM
;Authors: Dick Dievendorff
; Rod Springhetti
; David M. Sewell
;Add the following statement to CONFIG.SYS
; DEVICE=[d:][path]VDISK.SYS bbb sss ddd [/E:m]
; where: bbb is the desired buffer size (in kilobytes)
; minimum 1KB, maximum is size of available memory,
; default is 64KB.
; VDISK will leave at least 64KB of available memory,
; although subsequent device drivers (other than VDISK)
; other programs that make themselves resident, and
; COMMAND.COM will result in less than 64KB as shown
; by CHKDSK.
; Must be large enough for 1 boot sector + FAT sectors
; + 1 directory sector + at least 1 data cluster,
; or the device driver won't be installed.
; sss is the desired sector size (in bytes)
; 128, 256, or 512, default is 128.
; Will be adjusted if number of FAT entries > 0FE0H
; ddd is the desired number of directory entries
; Minimum 2, maximum 512, default 64.
; Will be rounded upward to sector size boundary.
; /E may only be used if extended memory above 1 megabyte
; is to be used. INT 15H functions 87H and 88H are used
; to read and write this extended memory.
; The m parameter in the /E option specifies the maximum
; number of sectors that the VDISK will transfer at a time.
; Optional values are 1,2,3,4,5,6,7 or 8 sectors, the default
; is 8 sectors.
; Brackets indicate optional operands.
; Samples:
; DEVICE=\path\VDISK.SYS 160 512 64
; results in a 160KB VDISK, with 512 byte sectors, 64 directory entries
; DEVICE=VDISK.SYS Buffersize 60 Sectorsize 128 Directory entries 32
; (since only numbers are interpreted, you may comment the line with
; non-numeric characters)
SUBTTL Structure Definitions
PAGE
;-----------------------------------------------------------------------;
; Request Header (Common portion) ;
;-----------------------------------------------------------------------;
RH EQU DS:[BX] ;addressability to Request Header structure
RHC STRUC ;fields common to all request types
DB ? ;length of Request Header (including data)
DB ? ;unit code (subunit)
RHC_CMD DB ? ;command code
RHC_STA DW ? ;status
DQ ? ;reserved for DOS
RHC ENDS ;end of common portion
CMD_INPUT EQU 4 ;RHC_CMD is INPUT request
;status values for RHC_STA
STAT_DONE EQU 01H ;function complete status (high order byte)
STAT_CMDERR EQU 8003H ;invalid command code error
STAT_CRC EQU 8004H ;CRC error
STAT_SNF EQU 8008H ;sector not found error
STAT_BUSY EQU 0200H ;busy bit (9) for Removable Media call
;-----------------------------------------------------------------------;
; Request Header for INIT command ;
;-----------------------------------------------------------------------;
RH0 STRUC
DB (TYPE RHC) DUP (?) ;common portion
RH0_NUN DB ? ;number of units
;set to 1 if installation succeeds,
;set to 0 to cause installation failure
RH0_ENDO DW ? ;offset of ending address
RH0_ENDS DW ? ;segment of ending address
RH0_BPBO DW ? ;offset of BPB array address
RH0_BPBS DW ? ;segment of BPB array address
RH0_DRIV DB ? ;drive code (DOS 3 only)
RH0 ENDS
RH0_BPBA EQU DWORD PTR RH0_BPBO ;offset/segment of BPB array address
;Note: RH0_BPBA at entry to INIT points to all after DEVICE= on CONFIG.SYS stmt
;-----------------------------------------------------------------------;
; Request Header for MEDIA CHECK Command ;
;-----------------------------------------------------------------------;
RH1 STRUC
DB (TYPE RHC) DUP (?) ;common portion
DB ? ;media descriptor
RH1_RET DB ? ;return information
RH1 ENDS
;-----------------------------------------------------------------------;
; Request Header for BUILD BPB Command ;
;-----------------------------------------------------------------------;
RH2 STRUC
DB (TYPE RHC) DUP(?) ;common portion
DB ? ;media descriptor
DW ? ;offset of transfer address
DW ? ;segment of transfer address
RH2_BPBO DW ? ;offset of BPB table address
RH2_BPBS DW ? ;segment of BPB table address
RH2 ENDS
;-----------------------------------------------------------------------;
; Request Header for INPUT, OUTPUT, and OUTPUT with verify ;
;-----------------------------------------------------------------------;
RH4 STRUC
DB (TYPE RHC) DUP (?) ;common portion
DB ? ;media descriptor
RH4_DTAO DW ? ;offset of transfer address
RH4_DTAS DW ? ;segment of transfer address
RH4_CNT DW ? ;sector count
RH4_SSN DW ? ;starting sector number
RH4 ENDS
RH4_DTAA EQU DWORD PTR RH4_DTAO ;offset/segment of transfer address
;-----------------------------------------------------------------------;
; Segment Descriptor (part of Global Descriptor Table) ;
;-----------------------------------------------------------------------;
DESC STRUC ;data segment descriptor
DESC_LMT DW 0 ;segment limit (length)
DESC_BASEL DW 0 ;bits 15-0 of physical address
DESC_BASEH DB 0 ;bits 23-16 of physical address
DB 0 ;access rights byte
DW 0 ;reserved
DESC ENDS
SUBTTL Equates and Macro Definitions
PAGE
MEM_SIZE EQU 12H ;BIOS memory size determination INT
;returns system size in KB in AX
EM_INT EQU 15H ;extended memory BIOS interrupt INT
EM_BLKMOVE EQU 87H ;block move function
EM_MEMSIZE EQU 88H ;memory size determination in KB
BOOT_INT EQU 19H ;bootstrap DOS
DOS EQU 21H ;DOS request INT
DOS_PCHR EQU 02H ;print character function
DOS_PSTR EQU 09H ;print string function
DOS_VERS EQU 30H ;get DOS version
TAB EQU 09H ;ASCII tab
LF EQU 0AH ;ASCII line feed
CR EQU 0DH ;ASCII carriage return
BEL EQU 07H ;ASCII bell
PARA_SIZE EQU 16 ;number of bytes in one 8088 paragraph
DIR_ENTRY_SIZE EQU 32 ;number of bytes per directory entry
MAX_FATE EQU 0FE0H ;largest number of FAT entries allowed
;default values used if parameters are omitted
DFLT_BSIZE EQU 64 ;default VDISK buffer size (KB)
DFLT_SSZ EQU 128 ;default sector size
DFLT_DIRN EQU 64 ;default number of directory entries
DFLT_ESS EQU 8 ;default maximum sectors to transfer
MIN_DIRN EQU 2 ;minimum number of directory entries
MAX_DIRN EQU 512 ;maximum number of directory entries
STACK_SIZE EQU 512 ;length of stack during initialization
;-----------------------------------------------------------------------;
; MSG invokes the console message subroutine ;
;-----------------------------------------------------------------------;
MSG MACRO TEXT
PUSH DX ;;save DX across call
MOV DX,OFFSET TEXT ;;point to message
CALL SHOW_MSG ;;issue message
POP DX
ENDM
SUBTTL Resident Data Area
PAGE
;-----------------------------------------------------------------------;
; Map INT 19H vector in low storage ;
;-----------------------------------------------------------------------;
INT_VEC SEGMENT AT 00H
ORG 4*BOOT_INT
BOOT_VEC LABEL DWORD
BOOT_VECO DW ? ;offset
BOOT_VECS DW ? ;segment
INT_VEC ENDS
CSEG SEGMENT PARA PUBLIC 'CODE'
ASSUME CS:CSEG
;-----------------------------------------------------------------------;
; Resident data area. ;
; ;
; All variables and constants required after initialization ;
; part one are defined here. ;
;-----------------------------------------------------------------------;
START EQU $ ;begin resident VDISK data & code
;DEVICE HEADER - must be at offset zero within device driver
DD -1 ;becomes pointer to next device header
DW 0800H ;attribute (IBM format block device)
;supports OPEN/CLOSE/RM calls
DW OFFSET STRATEGY ;pointer to device "strategy" routine
DW OFFSET IRPT ;pointer to device "interrupt handler"
DB 1 ;number of block devices
DB 7 DUP (?) ;7 byte filler (remainder of 8-byte name)
;END OF DEVICE HEADER
;This volume label is placed into the directory of the new VDISK
;This constant is also used to determine if a previous extended memory VDISK
;has been installed.
VOL_LABEL DB 'VDISK ' ;00-10 volume name (shows program level)
DB 28H ;11-11 attribute (volume label)
DT 0 ;12-21 reserved
DW 6000H ;22-23 time=12:00 noon
DW 0986H ;24-25 date=12/06/84
VOL_LABEL_LEN EQU $-VOL_LABEL ;length of volume label
;The following field, in the first extended memory VDISK device driver,
;is the 24-bit address of the first free byte of extended memory.
;This address is not in the common offset/segment format.
;The initial value, 10 0000H, is 1 megabyte.
AVAIL_LO DW 0 ;address of first free byte of
AVAIL_HI DB 10H ;extended memory
;The INT 19H vector is "stolen" by the first VDISK installed in extended memory.
;The original content of the interrupt vector is saved here.
INTV19 LABEL DWORD
INTV19O DW ? ;offset
INTV19S DW ? ;segment
PARAS_PER_SECTOR DW ? ;number of 16-byte paragraphs in one sector
START_BUFFER_PARA DW ? ;segment address of start of VDISK buffer
;for extended memory, this segment address
;is the end of the VDISK device driver.
EM_SW DB 0 ;non-zero if Extended Memory
EM_STAT DW 0 ;AX from last unsuccessful extended memory I/O
START_EM_LO DW ? ;24-bit address of start of VDISK buffer
START_EM_HI DB ? ;(extended memory only)
WPARA_SIZE DW PARA_SIZE ;number of bytes in one paragraph
MAX_CNT DW ? ;(0FFFFH/BPB_SSZ) truncated, the maximum
;number of sectors that can be transferred
;without worrying about 64KB wrap
SECT_LEFT DW ? ;sectors left to transfer
IO_SRCA LABEL DWORD ;offset/segment of source
IO_SRCO DW ? ;offset
IO_SRCS DW ? ;segment
IO_TGTA LABEL DWORD ;offset/segment of target
IO_TGTO DW ? ;offset
IO_TGTS DW ? ;segment
;-----------------------------------------------------------------------;
; BIOS Parameter Block (BPB) ;
;-----------------------------------------------------------------------;
;This is where the characteristics of the virtual disk are established.
;A copy of this block is moved into the boot record of the virtual disk.
;DEBUG can be used to read sector zero of the virtual disk to examine the
;boot record copy of this block.
BPB LABEL BYTE ;BIOS Parameter Block (BPB)
BPB_SSZ DW 0 ;number of bytes per disk sector
BPB_AUSZ DB 1 ;sectors per allocation unit
BPB_RES DW 1 ;number of reserved sectors (for boot record)
BPB_FATN DB 1 ;number of File Allocation Table (FAT) copies
BPB_DIRN DW 0 ;number of root directory entries
BPB_SECN DW 1 ;total number of sectors
;computed from buffer size and sector size
;(this includes reserved, FAT, directory,
;and data sectors)
BPB_MCB DB 0FEH ;media descriptor byte
BPB_FATSZ DW 1 ;number of sectors occupied by a single FAT
;computed from BPBSSZ and BPBSECN
BPB_LEN EQU $-BPB ;length of BIOS parameter block
BPB_PTR DW BPB ;BIOS Parameter Block pointer array (1 entry)
;-----------------------------------------------------------------------;
; Request Header (RH) address, saved here by "strategy" routine ;
;-----------------------------------------------------------------------;
RH_PTRA LABEL DWORD
RH_PTRO DW ? ;offset
RH_PTRS DW ? ;segment
;-----------------------------------------------------------------------;
; Global Descriptor Table (GDT), used for extended memory moves ;
;-----------------------------------------------------------------------;
;Access Rights Byte (93H) is
; P=1 (segment is mapped into physical memory)
; E=0 (data segment descriptor)
; D=0 (grow up segment, offsets must be <= limit)
; W=1 (data segment may be written into)
; DPL=0 (privilege level 0)
GDT LABEL BYTE ;begin global descriptor table
DESC <> ;dummy descriptor
DESC <> ;descriptor for GDT itself
SRC DESC <,,,93H,> ;source descriptor
TGT DESC <,,,93H,> ;target descriptor
DESC <> ;BIOS CS descriptor
DESC <> ;stack segment descriptor
SUBTTL INT 19H (boot) interrupt handler
PAGE
;-----------------------------------------------------------------------;
; INT 19H Interrupt Handler routine ;
;-----------------------------------------------------------------------;
;The INT 19H vector is altered by VDISK initialization to point to this
;routine within the first extended memory VDISK device driver.
;The vector points to the device driver so that subsequent VDISKs installed
;in extended memory can find the first one to determine what memory has
;already been allocated to VDISKs.
;This routine restores the original INT 19H vector's content, then jumps
;to the original routine.
;INT 19H, the "Boot" INT, is always altered when DOS is booted.
;This routine is entered with interrupts disabled.
VDISK_INT19 PROC ;INT 19H received
PUSH DS ;save registers we're going to alter
PUSH AX
XOR AX,AX
MOV DS,AX ;set DS = 0
ASSUME DS:INT_VEC
MOV AX,CS:INTV19O ;get offset of saved vector
MOV DS:BOOT_VECO,AX ;store offset in interrupt vector
MOV AX,CS:INTV19S ;get segment of saved vector
MOV DS:BOOT_VECS,AX ;store segment in interrupt vector
POP AX
POP DS
JMP CS:INTV19 ;go to original interrupt routine
VDISK_INT19 ENDP
ASSUME DS:NOTHING
SUBTTL Device Strategy & interrupt entry points
PAGE
;-----------------------------------------------------------------------;
; Device "strategy" entry point ;
; ;
; Retain the Request Header address for use by Interrupt routine ;
;-----------------------------------------------------------------------;
STRATEGY PROC FAR
MOV CS:RH_PTRO,BX ;offset
MOV CS:RH_PTRS,ES ;segment
RET
STRATEGY ENDP
;-----------------------------------------------------------------------;
; Table of command processing routine entry points ;
;-----------------------------------------------------------------------;
CMD_TABLE LABEL WORD
DW OFFSET INIT_P1 ; 0 - Initialization
DW OFFSET MEDIA_CHECK ; 1 - Media check
DW OFFSET BLD_BPB ; 2 - Build BPB
DW OFFSET INPUT_IOCTL ; 3 - IOCTL input
DW OFFSET INPUT ; 4 - Input
DW OFFSET INPUT_NOWAIT ; 5 - Non destructive input no wait
DW OFFSET INPUT_STATUS ; 6 - Input status
DW OFFSET INPUT_FLUSH ; 7 - Input flush
DW OFFSET OUTPUT ; 8 - Output
DW OFFSET OUTPUT_VERIFY ; 9 - Output with verify
DW OFFSET OUTPUT_STATUS ;10 - Output status
DW OFFSET OUTPUT_FLUSH ;11 - Output flush
DW OFFSET OUTPUT_IOCTL ;12 - IOCTL output
DW OFFSET DEVICE_OPEN ;13 - Device OPEN
DW OFFSET DEVICE_CLOSE ;14 - Device CLOSE
MAX_CMD EQU ($-CMD_TABLE)/2 ;highest valid command follows
DW OFFSET REMOVABLE_MEDIA ;15 - Removable media
;-----------------------------------------------------------------------;
; Device "interrupt" entry point ;
;-----------------------------------------------------------------------;
IRPT PROC FAR ;device interrupt entry point
PUSH DS ;save all registers modified
PUSH ES
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSH DI
PUSH SI
;BP isn't used, so it isn't saved
CLD ;all moves forward
LDS BX,CS:RH_PTRA ;get RH address passed to "strategy" into DS:BX
MOV AL,RH.RHC_CMD ;command code from Request Header
CBW ;zero AH (if AL > 7FH, next compare will
;catch that error)
CMP AL,MAX_CMD ;if command code is too high
JA IRPT_CMD_HIGH ;jump to error routine
MOV DI,OFFSET IRPT_CMD_EXIT ;return addr from command processor
PUSH DI ;push return address onto stack
;command routine issues "RET"
ADD AX,AX ;double command code for table offset
MOV DI,AX ;put into index register for JMP
XOR AX,AX ;initialize return to "no error"
;At entry to command processing routine:
; DS:BX = Request Header address
; CS = VDISK code segment address
; AX = 0
; top of stack is return address, IRPT_CMD_EXIT
JMP CS:CMD_TABLE[DI] ;call routine to handle the command
IRPT_CMD_ERROR: ;CALLed for unsupported character mode commands
INPUT_IOCTL: ;IOCTL input
INPUT_NOWAIT: ;Non-destructive input no wait
INPUT_STATUS: ;Input status
INPUT_FLUSH: ;Input flush
OUTPUT_IOCTL: ;IOCTL output
OUTPUT_STATUS: ;Output status
OUTPUT_FLUSH: ;Output flush
POP AX ;pop return address off stack
IRPT_CMD_HIGH: ;JMPed to if RHC_CMD > MAX_CMD
MOV AX,STAT_CMDERR ;"invalid command" and error
IRPT_CMD_EXIT: ;return from command routine
;AX = value to OR into status word
LDS BX,CS:RH_PTRA ;restore DS:BX as Request Header pointer
OR AH,STAT_DONE ;add "done" bit to status word
MOV RH.RHC_STA,AX ;store status into request header
POP SI ;restore registers
POP DI
POP DX
POP CX
POP BX
POP AX
POP ES
POP DS
RET
IRPT ENDP
SUBTTL Command Processing routines
PAGE
;-----------------------------------------------------------------------;
; Command Code 1 - Media Check ;
; At entry, DS:BX point to request header, AX = 0 ;
;-----------------------------------------------------------------------;
MEDIA_CHECK PROC
MOV RH.RH1_RET,1 ;indicate media not changed
RET ;AX = zero, no error
MEDIA_CHECK ENDP
;-----------------------------------------------------------------------;
; Command Code 2 - Build BPB ;
; At entry, DS:BX point to request header, AX = 0 ;
;-----------------------------------------------------------------------;
BLD_BPB PROC
MOV RH.RH2_BPBO,OFFSET BPB ;return pointer to our BPB
MOV RH.RH2_BPBS,CS
RET ;AX = zero, no error
BLD_BPB ENDP
;-----------------------------------------------------------------------;
; Command Code 13 - Device Open ;
; Command Code 14 - Device Close ;
; Command Code 15 - Removable media ;
; At entry, DS:BX point to request header, AX = 0 ;
;-----------------------------------------------------------------------;
REMOVABLE_MEDIA PROC
MOV AX,STAT_BUSY ;set status bit 9 (busy)
;indicating non-removable media
DEVICE_OPEN: ;NOP for device open
DEVICE_CLOSE: ;NOP for device close
RET
REMOVABLE_MEDIA ENDP ;fall thru to return
;-----------------------------------------------------------------------;
; Command Code 4 - Input ;
; Command Code 8 - Output ;
; Command Code 9 - Output with verify ;
; At entry, DS:BX point to request header, AX = 0 ;
;-----------------------------------------------------------------------;
INOUT PROC
INPUT:
OUTPUT:
OUTPUT_VERIFY:
;Make sure I/O is entirely within the VDISK sector boundaries
MOV CX,CS:BPB_SECN ;get total sector count
MOV AX,RH.RH4_SSN ;starting sector number
CMP AX,CX ;can't exceed total count
JA INOUT_E1 ;jump if start > total
ADD AX,RH.RH4_CNT ;start + sector count
CMP AX,CX ;can't exceed total count
JNA INOUT_A ;jump if start + count <= total
INOUT_E1: ;I/O not within VDISK sector boundaries
MOV RH.RH4_CNT,0 ;set sectors transferred to zero
MOV AX,STAT_SNF ;indicate 'Sector not found' error
RET ;return with error status in AX
INOUT_A: ;I/O within VDISK bounds
MOV AX,RH.RH4_CNT ;get sector count
MOV CS:SECT_LEFT,AX ;save as sectors left to process
CMP CS:EM_SW,0 ;extended memory mode?
JNE INOUT_EM ;jump to extended memory I/O code
;Compute offset and segment of VDISK buffer for starting segment in CX:SI
MOV AX,RH.RH4_SSN ;starting sector number
MUL CS:PARAS_PER_SECTOR ;* length of one sector in paragraphs
ADD AX,CS:START_BUFFER_PARA ;+ segment of VDISK buffer sector 0
MOV CX,AX ;segment address to CX
XOR SI,SI ;offset is zero
;Compute address of caller's Data Transfer Addr in DX:AX with smallest offset,
;so that there is no possibility of overflowing a 64KB boundary moving MAX_CNT
;sectors.
MOV AX,PARA_SIZE ;16
MUL RH.RH4_DTAS ;* segment of caller's DTA in DX,AX
ADD AX,RH.RH4_DTAO ;+ offset of caller's DTA
ADC DL,0 ;carry in from addition
DIV CS:WPARA_SIZE ;AX is segment of caller's DTA
;DX is smallest offset possible
;AX:DX = DTA address
;AX:DX is caller's DTA segment:offset, CX:SI is VDISK buffer segment:offset
;If this is an OUTPUT request, exchange the source and target addresses
CMP RH.RHC_CMD,CMD_INPUT ;INPUT operation?
JE INOUT_B ;jump if INPUT operation
XCHG AX,CX ;swap source and target segment
XCHG DX,SI ;swap source and target offset
INOUT_B: ;CX:SI is source, AX:DX is target
MOV CS:IO_SRCS,CX ;save source segment
MOV CS:IO_SRCO,SI ;save source offset
MOV CS:IO_TGTS,AX ;save target segment
MOV CS:IO_TGTO,DX ;save target offset
JMP SHORT INOUT_E ;AX := SECT_LEFT, test for zero
INOUT_C: ;SECT_LEFT in AX, non-zero
; Compute number of sectors to transfer in a single move,
; AX = minimum of (SECT_LEFT, MAX_CNT)
; MAX_CNT is the maximum number of sectors that can be moved without
; spanning a 64KB boundary (0FFFFH / Sector size, remainder truncated)
MOV CX,CS:MAX_CNT ;MAX sectors with one move
CMP AX,CX ;if SECT_LEFT cannot span 64KB boundary
JBE INOUT_D ;then move SECT_LEFT sectors
MOV AX,CX ;else move MAX_CNT sectors
INOUT_D:
SUB CS:SECT_LEFT,AX ;reduce number of sectors left to move
;Move AX sectors from source to target
MUL CS:BPB_SSZ ;sectors * sector size = byte count
;(cannot overflow into DX)
SHR AX,1 ;/2 = word count
MOV CX,AX ;word count to CX for REP MOVSW
LDS SI,CS:IO_SRCA ;source segment/offset to DS:SI
LES DI,CS:IO_TGTA ;target segment/offset to ES:DI
REP MOVSW ;move MOV_CNT sectors
;Update source and target paragraph addresses
;AX has number of words moved
SHR AX,1 ;words moved / 8 = paragraphs moved
SHR AX,1
SHR AX,1
ADD CS:IO_SRCS,AX ;add paragraphs moved to source segment
ADD CS:IO_TGTS,AX ;add paragraphs moved to target segment
;Determine if more sectors need to be transferred
INOUT_E: ;do while SECT_LEFT <> zero
MOV AX,CS:SECT_LEFT ;get sectors left to transfer
OR AX,AX ;set flags
JNZ INOUT_C ;go back to transfer some sectors
RET ;AX = zero, all sectors transferred
SUBTTL Extended Memory I/O routine
PAGE
;-----------------------------------------------------------------------;
; Extended Memory I/O routine ;
;-----------------------------------------------------------------------;
INOUT_EM: ;Extended memory I/O routine
;change to larger stack
MOV SI,SS ;save old SS in SI
MOV DX,SP ;save old SP in DX
CLI ;disable interrupts
MOV AX,CS
MOV SS,AX ;set SS = CS
MOV SP,OFFSET EM_STACK ;point to new stack
STI ;enable interrupts
PUSH SI ;save old SS at top of new stack
PUSH DX ;save old SP on new stack
MOV SI,RH.RH4_DTAO ;caller's DTA offset
;Compute 24-bit address of VDISK sector in CX (hi) and SI (low)
MOV AX,RH.RH4_SSN ;starting sector number
MUL CS:BPB_SSZ ;* sector size = offset within buffer
ADD AX,CS:START_EM_LO ;+ base address of this VDISK buffer
ADC DL,CS:START_EM_HI
MOV CX,DX ;save high byte
MOV SI,AX ;save low word
;Compute 24-bit address of caller's DTA in DX (hi) and AX (low)
MOV AX,PARA_SIZE ;16
MUL RH.RH4_DTAS ;* segment of caller's DTA
ADD AX,RH.RH4_DTAO ;+ offset of caller's DTA
ADC DL,0 ;carry in from addition
;Caller's DTA address is in CX,SI, VDISK buffer address is in DX,AX.
;If this is an OUTPUT request, exchange the source and target addresses
CMP RH.RHC_CMD,CMD_INPUT ;INPUT operation?
JE INOUT_EM_B ;jump if INPUT operation
XCHG DX,CX ;swap source and target high byte
XCHG AX,SI ;swap source and target low word
INOUT_EM_B: ;CX,SI is source, DX,AX is target
MOV SRC.DESC_BASEL,SI ;low 16 bits of source address
MOV SRC.DESC_BASEH,CL ;high 8 bits of source address
MOV TGT.DESC_BASEL,AX ;low 16 bits of target address
MOV TGT.DESC_BASEH,DL ;high 8 bits of target address
JMP SHORT INOUT_EM_E ;AX := SECT_LEFT, test for zero
INOUT_EM_C: ;SECT_LEFT in AX, non-zero
; Compute number of sectors to transfer in a single move,
; AX = minimum of (SECT_LEFT, MAX_CNT)
; MAX_CNT is the maximum number of sectors that can be moved without
; spanning a 64KB boundary (0FFFFH / Sector size, remainder truncated)
MOV CX,CS:MAX_CNT ;MAX sectors with one move
CMP AX,CX ;if SECT_LEFT cannot span 64KB boundary
JBE INOUT_EM_D ;then move SECT_LEFT sectors
MOV AX,CX ;else move MAX_CNT sectors
INOUT_EM_D:
SUB CS:SECT_LEFT,AX ;reduce number of sectors left to move
;Move AX sectors from source to target
MUL CS:BPB_SSZ ;sectors * sector size = byte count
;(cannot overflow into DX)
MOV TGT.DESC_LMT,AX ;store segment limit (byte count)
MOV SRC.DESC_LMT,AX
PUSH AX ;preserve byte count on stack
SHR AX,1 ;/2 = word count
MOV CX,AX ;word count to CX
PUSH CS
POP ES ;set ES = CS
MOV SI,OFFSET GDT ;ES:SI point to GDT
MOV AH,EM_BLKMOVE ;function is block move
INT EM_INT ;move an even number of words
POP CX ;get byte count back from stack
OR AH,AH ;get error code
JNZ INOUT_EM_XE ;jump if I/O error encountered
;Update source and target addresses
ADD SRC.DESC_BASEL,CX ;add bytes moved to source
ADC SRC.DESC_BASEH,0 ;pick up any carry
ADD TGT.DESC_BASEL,CX ;add bytes moved to target
ADC TGT.DESC_BASEH,0 ;pick up any carry
;Determine if more sectors need to be transferred
INOUT_EM_E: ;do while SECT_LEFT <> zero
MOV AX,CS:SECT_LEFT ;get sectors left to transfer
OR AX,AX ;set flags
JNZ INOUT_EM_C ;go back to transfer some sectors
INOUT_EM_X2: ;revert to original stack
POP DI ;get old SP
POP SI ;get old SS
CLI ;disable interrupts
MOV SS,SI ;restore old SS
MOV SP,DI ;restore old SP
STI ;enable interrupts
RET ;return to IRPT_EXIT
INOUT_EM_XE: ;some error with INT 15H
MOV CS:EM_STAT,AX ;save error status for debugging
MOV RH.RH4_CNT,0 ;indicate no sectors transferred
MOV AX,STAT_CRC ;indicate CRC error
JMP INOUT_EM_X2 ;fix stack and exit
INOUT ENDP
DW 40 DUP (?) ;stack for extended memory I/O
EM_STACK LABEL WORD
SUBTTL Boot Record
PAGE
;-----------------------------------------------------------------------;
; Adjust the assembly-time instruction counter to a paragraph ;
; boundary ;
;-----------------------------------------------------------------------;
IF ($-START) MOD 16
ORG ($-START) + 16 - (($-START) MOD 16)
ENDIF
VDISK EQU $ ;start of virtual disk buffer
VDISKP EQU ($-START) / PARA_SIZE ;length of program in paragraphs
;-----------------------------------------------------------------------;
; If this VDISK is in extended memory, this address is passed ;
; back to DOS as the end address that is to remain resident. ;
; ;
; It this VDISK is not in extended memory, the VDISK buffer ;
; begins at this address, and the address passed back to DOS ;
; as the end address that is to remain resident is this address ;
; plus the length of the VDISK buffer. ;
;-----------------------------------------------------------------------;
BOOT_RECORD LABEL BYTE ;Format of Boot Record documented in
;DOS Technical Reference Manual
DB 0,0,0 ;3-byte jump to boot code (not bootable)
DB 'VDISK ' ;8-byte vendor identification
BOOT_BPB LABEL BYTE ;boot record copy of BIOS parameter block
DW ? ;number of bytes per disk sector
DB ? ;sectors per allocation unit
DW ? ;number of reserved sectors (for boot record)
DB ? ;number of File Allocation Table (FAT) copies
DW ? ;number of root directory entries
DW ? ;total number of sectors
DB ? ;media descriptor byte
DW ? ;number of sectors occupied by a single FAT
;end of boot record BIOS Parameter block
;The following three words mean nothing to VDISK, they are placed here
;to conform to the DOS standard for boot records.
DW 8 ;sectors per track
DW 1 ;number of heads
DW 0 ;number of hidden sectors
;The following word is the 16-bit kilobyte address of the first byte in
;extended memory that is not occupied by a VDISK buffer
;It is placed into this location so that other users of extended memory
;may find where all the VDISKs end.
;This field may be accessed by moving the boot record of the First extended
;memory VDISK from absolute location 10 0000H. Before assuming that the
;value below is valid, the vendor ID (constant VDISK) should be verified
;to make sure that SOME VDISK has been installed.
;For example, if two VDISKs are installed, one 320KB and one 64KB, the
;address calculations are as follows:
;Extended memory start address = 100000H (1024KB)
;Start addr of 1st VDISK buffer = 100000H (1024KB)
;Length of 1st VDISK buffer = 050000H ( 320KB)
;End addr of 1st VDISK buffer = 14FFFFH
;Start addr of 2nd VDISK buffer = 150000H (1344KB)
;Length of 2nd VDISK buffer = 010000H ( 64KB)
;End addr of 2nd VDISK buffer = 15FFFFH
;First byte after all VDISKs = 160000H (1408KB)
;Divide by 1024 = 0580H (1408D)
;Content of BOOT_EM = 0580H
BOOT_EM_OFF EQU $-BOOT_RECORD ;offset from 10 0000H of the following word
BOOT_EM DW 1024 ;KB addr of first free byte of extended memory
;-----------------------------------------------------------------------;
; Part 2 of Initialization (executed last) ;
;-----------------------------------------------------------------------;
;Initialization is divided into two parts.
;INIT_P1 is overlaid by the virtual disk buffer
;INIT_P1 is executed first, then jumps to INIT_P2. INIT_P2 returns to caller.
;Exercise caution if extending the initialization part 2 code.
;It overlays the area immediately following the boot sector.
;If this section of code must be expanded, make sure it fits into the minimum
;sector size of 128 bytes.
;Label TEST_LENGTH must equate to a non-negative value (TEST_LENGTH >= 0).
;If this code it must be extended beyond the 128 byte length of the boot sector,
;move all of INIT_P2 before label VDISK.
;Registers at entry to INIT_P2 (set up at end of INIT_P1):
; BL = media control byte from BPB (for FAT)
; CX = number of FAT copies
; DX = number of bytes in one FAT - 3
; SI = OFFSET of Volume Label field
; ES:DI = VDISK buffer address of first FAT sector
; CS = DS = VDISK code segment
INIT_P2 PROC ;second part of initialization
ASSUME DS:CSEG ;DS set in INIT_P1
;Initialize File Allocation Table(s) (FATs)
INIT_P2_FAT: ;set up one FAT, sector number in AX
PUSH CX ;save loop counter on stack
MOV AL,BL ;media control byte
STOSB ;store media control byte, increment DI
MOV AX,0FFFFH ;bytes 2 and 3 of FAT are 0FFH
STOSW
MOV CX,DX ;FAT size in bytes - 3
XOR AX,AX ;value to store in remainder of FAT
REP STOSB ;clear remainder of FAT
POP CX ;get loop counter off stack
LOOP INIT_P2_FAT ;loop for all copies of the FAT
;Put the volume label in the first directory entry
MOV CX,VOL_LABEL_LEN ;length of volume directory entry
REP MOVSB ;move volume id to directory
;Zero the remainder of the directory
MOV AX,DIR_ENTRY_SIZE ;length of 1 directory entry
MUL BPB_DIRN ;* number entries = bytes of directory
SUB AX,VOL_LABEL_LEN ;less length of volume label
MOV CX,AX ;length of rest of directory
XOR AX,AX
REP STOSB ;clear directory to nulls
RET ;return with AX=0
INIT_P2 ENDP
PATCH_AREA DB 5 DUP ('PATCH AREA ')
TEST_LENGTH EQU 128-($-VDISK) ;if negative, boot record has too much
;data area, move some fields below VDISK
;-----------------------------------------------------------------------;
; All fields that must remain resident after device driver ;
; initialization must be defined before this point. ;
;-----------------------------------------------------------------------;
DB 'IBM DOS Version 4.00, Virtual Disk Device Driver'
DB '(C)Copyright IBM Corp 1984,1985,1988'
DB 'Licensed Material - Program Property of IBM. '
DB 'Author: Dick Dievendorff, '
DB 'Rod Springhetti, & '
DB 'David M. Sewell'
MAXSEC_TRF DW 0 ;maximum number of sectors to transfer when
;in extended memory
BUFF_SIZE DW 0 ;desired VDISK buffer size in kilobytes
MIN_MEMORY_LEFT DW 64 ;minimum amount of system memory (kilobytes)
;that must remain after VDISK is installed
FIRST_EM_SW DB ? ;0FFH if this is the first device driver
;to be installed in extended memory
;00H if another VDISK extended memory driver
;has been installed
FIRST_VDISK DW ? ;segment address of 1st VDISK device driver
PARA_PER_KB DW 1024/PARA_SIZE ;paragraphs in one kilobyte
C1024 DW 1024 ;bytes in one kilobyte
DIRE_SIZE DW DIR_ENTRY_SIZE ;bytes in one directory entry
DIR_SECTORS DW ? ;number of sectors of directory
ERR_FLAG DB 0 ;error indicators to condition messages
ERR_BSIZE EQU 80H ;buffer size adjusted
ERR_SSZ EQU 40H ;sector size adjusted
ERR_DIRN EQU 20H ;number of directory entries adjusted
ERR_PASS EQU 10H ;some adjustment made that requires
;recomputation of values previously computed
ERR_SSZB EQU ERR_SSZ+ERR_PASS ;sector size altered this pass
ERR_SYSSZ EQU 08H ;system storage too small for VDISK
ERR_SWTCH EQU 04H ;invalid switch character
ERR_EXTSW EQU 02H ;extender card switches don't match memory size
ERR_ESIZE EQU 01H ;Transfer size adjusted
; additional errors added - kwc
major_version equ 4 ;Major DOS version
minor_version equ 00 ;Minor DOS Version
expected_version equ (MINOR_VERSION SHL 8)+MAJOR_VERSION
err_flag2 db 0
err_baddos equ 01h ; Invalid DOS Version
SUBTTL Initialization, Part one
PAGE
;-----------------------------------------------------------------------;
; Command Code 0 - Initialization ;
; At entry, DS:BX point to request header, AX = 0 ;
;-----------------------------------------------------------------------;
;Initialization is divided into two parts.
;This part, executed first, is later overlaid by the VDISK buffer.
INIT_P1 PROC ;first part of initialization
MOV DX,SS ;save stack segment register
MOV CX,SP ;save stack pointer register
CLI ;inhibit interrupts while changing SS:SP
MOV AX,CS ;move CS to SS through AX
MOV SS,AX
MOV SP,OFFSET MSGEND ;end of VDISKMSG
ADD SP,STACK_SIZE ;+ length of our stack
STI ;allow interrupts
PUSH DX ;save old SS register on new stack
PUSH CX ;save old SP register on new stack
push bx ;secure registers before DOS int
push cx ;secure registers before DOS int
; add version check - kwc
mov ah,030h
int 21h
pop cx ;restore pointer values
pop bx ;restore pointer values
cmp ax,expected_version
je okdos
or cs:err_flag2,err_baddos
okdos:
CALL GET_PARMS ;get parameters from CONFIG.SYS line
PUSH CS
POP DS ;set DS = CS
ASSUME DS:CSEG
CALL APPLY_DEFAULTS ;supply any values not specified
CALL DETERMINE_START ;compute start address of VDISK buffer
CALL VALIDATE ;validate parameters
CALL COPY_BPB ;Copy BIOS Parameter Block to boot record
CALL VERIFY_EXTENDER ;Verify that extender card switches are right
TEST ERR_FLAG,ERR_EXTSW ;are switches wrong?
JNZ INIT_P1_A ;if so, exit with messages
test CS:err_flag2,err_baddos
jnz init_p1_a
CMP EM_SW,0 ;extended memory requested?
JE INIT_P1_A ;jump if not
TEST ERR_FLAG,ERR_SYSSZ ;is system too small for VDISK?
JNZ INIT_P1_A ;if so, don't do extended memory init
CALL UPDATE_AVAIL ;update AVAIL_HI and AVAIL_LO to reflect
;addition of extended memory VDISK
CALL FORMAT_VDISK ;construct a boot record, FATs and
;directory in storage immediately
;following this device driver
CALL MOVE_VDISK ;move formatted boot record, FATs,
;and directory to extended memory
CALL UPDATE_BOOT ;place the end address of ALL VDISKs
;in the boot record of the first VDISK
CMP FIRST_EM_SW,0 ;is this the first extended memory VDISK?
JE INIT_P1_A ;no, exit
CALL STEAL_INT19 ;point INT 19H to this VDISK
INIT_P1_A:
CALL FILL_RH ;fill in INIT request header
CALL WRITE_MESSAGES ;display all messages
POP CX ;get old SP from stack
POP DX ;get old SS from stack
CLI ;disable interrupts while changing SS:SP
MOV SS,DX ;restore stack segment register
MOV SP,CX ;restore stack pointer register
STI ;enable interrupts
;-----------------------------------------------------------------------;
; INIT_P2 must be short enough to fit into the boot sector ;
; (minimum size of boot sector is 128 bytes), so we set up ;
; as many pointers as we can to help keep INIT_P2 short. ;
; ;
; ES:DI = storage address of first FAT sector ;
; BL = media control byte ;
; CX = number of FAT copies ;
; DX = number of bytes in one FAT, less 3 ;
; SI = offset of VOL label field ;
;-----------------------------------------------------------------------;
MOV ES,START_BUFFER_PARA ;start paragraph of VDISK buffer
MOV AX,BPB_RES ;number of reserved sectors
MUL BPB_SSZ ;* sector size
MOV DI,AX ;ES:DI point to FAT start
MOV BL,BPB_MCB ;media control byte
MOV CL,BPB_FATN ;number of FAT copies
XOR CH,CH
MOV AX,BPB_FATSZ ;FAT size in sectors
MUL BPB_SSZ ;* sector size = total FAT bytes
SUB AX,3 ;-3 (FEFFFF stored by code)
MOV DX,AX
MOV SI,OFFSET VOL_LABEL ;point to VOL label directory entry
JMP INIT_P2 ;jump to second part of initialization
;this is redundant if the VDISK is in
;extended memory, but is executed anyway
SUBTTL GET_PARMS Parameter Line Scan
PAGE
;-----------------------------------------------------------------------;
;GET_PARMS gets the parameters from the CONFIG.SYS statement ;
; ;
;Register usage: ;
; DS:SI indexes parameter string ;
; AL contains character from parameter string ;
; CX value from GET_NUMBER ;
;-----------------------------------------------------------------------;
ASSUME DS:NOTHING ;DS:BX point to Request Header
GET_PARMS PROC ;get parameters from CONFIG.SYS line
PUSH DS ;save DS
LDS SI,RH.RH0_BPBA ;DS:SI point to all after DEVICE=
;in CONFIG.SYS line
XOR AL,AL ;not at end of line
;Skip until first delimiter is found. There may be digits in the path string.
;DS:SI points to \pathstring\VDISK.SYS nn nn nn
;The character following VDISK.SYS may have been changed to a null (00H).
;All letters have been changed to uppercase.
GET_PARMS_A: ;skip to DOS delimiter character
CALL GET_PCHAR ;get parameter character into AL
JZ GET_PARMS_X ;get out if end of line encountered
OR AL,AL ;test for null
JZ GET_PARMS_C ;
CMP AL,' '
JE GET_PARMS_C ;
CMP AL,','
JE GET_PARMS_C ;
CMP AL,';'
JE GET_PARMS_C ;
CMP AL,'+'
JE GET_PARMS_C ;
CMP AL,'='
JE GET_PARMS_C ;
CMP AL,TAB
JNE GET_PARMS_A ;skip until delimiter or CR
GET_PARMS_C:
PUSH SI ;save to rescan
MOV CS:EM_SW,0 ;indicate no /E found
JMP GET_SLASH ;see if current character is an slash
GET_PARMS_D: ;scan for /
CALL GET_PCHAR
JZ GET_PARMS_B ;exit if end of line
GET_SLASH: ;check for slash
CMP AL,'/' ;found slash?
JNE GET_PARMS_D ;no, continue scan
CALL GET_PCHAR ;get char following slash
CMP AL,'E' ;don't have to test for lower case E,
;letters have been changed to upper case
JNE GET_PARMS_E ;not 'E'
MOV CS:EM_SW,AL ;indicate /E found
CALL GET_PCHAR ;get char following E
CMP AL,':' ;is it a delimeter ?
JNE GET_PARMS_D ;not a ':'
CALL GET_MAXSIZE ;get maximum sector size
JMP GET_PARMS_D ;continue forward scan
GET_PARMS_E: ;/ found, not 'E'
OR CS:ERR_FLAG,ERR_SWTCH ;indicate invalid switch character
JMP GET_PARMS_D ;continue scan
GET_PARMS_B: ;now pointing to first delimiter
POP SI ;get pointer, used to rescan for /E
XOR AL,AL ;not at EOL now
CALL GET_PCHAR ;get first character
CALL SKIP_TO_DIGIT ;skip to first digit
JZ GET_PARMS_X ;found EOL, no digits remain
CALL GET_NUMBER ;extract digits, convert to binary
MOV CS:BUFF_SIZE,CX ;store buffer size
CALL SKIP_TO_DIGIT ;skip to next digit
JZ GET_PARMS_X ;found EOL, no digits remain
CALL GET_NUMBER ;extract digits, convert to binary
MOV CS:BPB_SSZ,CX ;store sector size
CALL SKIP_TO_DIGIT ;skip to next digit
JZ GET_PARMS_X ;found EOL, no digits remain
CALL GET_NUMBER ;extract digits, convert to binary
MOV CS:BPB_DIRN,CX ;store number of directory entries
GET_PARMS_X: ;premature end of line
POP DS ;restore DS
RET
GET_MAXSIZE PROC ;get maximum sector size
CALL GET_PCHAR ;get next character
CALL CHECK_NUM ;is it a number ?
JZ GET_NEXTNUM ;yes, go get next number
OR CS:ERR_FLAG,ERR_ESIZE ;indicate invalid sector size
RET ;
GET_NEXTNUM: ;get next number
CALL GET_NUMBER ;extract digits and convert to binary
MOV CS:MAXSEC_TRF,CX ;save maximum sector size to transfer
RET
GET_MAXSIZE ENDP
GET_PCHAR PROC ;internal proc to get next character into AL
CMP AL,CR ;carriage return already encountered?
JE GET_PCHAR_X ;don't read past end of line
CMP AL,LF ;line feed already encountered?
JE GET_PCHAR_X ;don't read past end of line
LODSB ;get char from DS:SI, increment SI
CMP AL,CR ;is the char a carriage return?
JE GET_PCHAR_X ;yes, set Z flag at end of line
CMP AL,LF ;no, is it a line feed?
GET_PCHAR_X: ;attempted read past end of line
RET
GET_PCHAR ENDP ;returns char in AL
CHECK_NUM PROC ;check AL for ASCII digit
CMP AL,'0' ;< '0'?
JB CHECK_NUM_X ;exit if it is
CMP AL,'9' ;> '9'?
JA CHECK_NUM_X ;exit if it is
CMP AL,AL ;set Z flag to indicate numeric
CHECK_NUM_X:
RET ;Z set if numeric, NZ if not numeric
CHECK_NUM ENDP
SKIP_TO_DIGIT PROC ;skip to first numeric character
CALL CHECK_NUM ;is current char a digit?
JZ SKIP_TO_DIGIT_X ;if so, skip is complete
CALL GET_PCHAR ;get next character from line
JNZ SKIP_TO_DIGIT ;loop until first digit or CR or LF
RET ;character is CR or LF
SKIP_TO_DIGIT_X:
CMP AL,0 ;digit found, force NZ
RET
SKIP_TO_DIGIT ENDP
C10 DW 10
GN_ERR DB ? ;zero if no overflow in accumulation
GET_NUMBER PROC ;convert string of digits to binary value
XOR CX,CX ;accumulate number in CX
MOV CS:GN_ERR,CL ;no overflow yet
GET_NUMBER_A: ;accumulate next digit
SUB AL,'0' ;convert ASCII to binary
CBW ;clear AH
XCHG AX,CX ;previous accumulation in AX, new digit in CL
MUL CS:C10 ;DX:AX := AX*10
OR CS:GN_ERR,DL ;set GN_ERR <> 0 if overflow
ADD AX,CX ;add new digit from
XCHG AX,CX ;number now in CX
DEC SI ;back up to prior entry
MOV AL,' ' ;blank out prior entry
MOV [SI],AL ;
INC SI ;set to current entry
CALL GET_PCHAR ;get next character
CALL CHECK_NUM ;see if it was numeric
JZ GET_NUMBER_A ;continue accumulating
CMP CS:GN_ERR,0 ;did we overflow?
JE GET_NUMBER_B ;if not, we're done
XOR CX,CX ;return zero (always invalid) if overflow
GET_NUMBER_B:
RET ;number in CX, next char in AL
GET_NUMBER ENDP
GET_PARMS ENDP
SUBTTL APPLY_DEFAULTS
PAGE
;-----------------------------------------------------------------------;
; APPLY_DEFAULTS supplies any parameter values that the user ;
; failed to specify ;
;-----------------------------------------------------------------------;
ASSUME DS:CSEG
APPLY_DEFAULTS PROC
XOR AX,AX
CMP BUFF_SIZE,AX ;is buffer size zero?
JNE APPLY_DEFAULTS_A ;no, user specified something
MOV BUFF_SIZE,DFLT_BSIZE ;supply default buffer size
OR ERR_FLAG,ERR_BSIZE ;indicate buffersize adjusted
APPLY_DEFAULTS_A:
CMP BPB_SSZ,AX ;is sector size zero?
JNE APPLY_DEFAULTS_B ;no, user specified something
MOV BPB_SSZ,DFLT_SSZ ;supply default sector size
OR ERR_FLAG,ERR_SSZ ;indicate sector size adjusted
APPLY_DEFAULTS_B:
CMP BPB_DIRN,AX ;are directory entries zero?
JNE APPLY_DEFAULTS_C ;no, user specified something
MOV BPB_DIRN,DFLT_DIRN ;supply default directory entries
OR ERR_FLAG,ERR_DIRN ;indicate directory entries adjusted
APPLY_DEFAULTS_C: ;
CMP EM_SW,0 ;extended memory ?
JE APPLY_DEFAULTS_D ;no, jump around
CMP MAXSEC_TRF,AX ;is maximum sectors zero?
JNE APPLY_DEFAULTS_D ;no, user specified something
MOV MAXSEC_TRF,DFLT_ESS ;supply default maximum number of
;sector to transfer
OR ERR_FLAG,ERR_ESIZE ;indicate transfer size adjusted
APPLY_DEFAULTS_D:
RET
APPLY_DEFAULTS ENDP
SUBTTL DETERMINE_START address of VDISK buffer
PAGE
;-----------------------------------------------------------------------;
; DETERMINE_START figures out the starting address of the VDISK ;
; buffer ;
;-----------------------------------------------------------------------;
ASSUME DS:CSEG
DETERMINE_START PROC
;If extended memory is NOT being used, the VDISK buffer immediately
;follows the resident code.
;If extended memory IS being used, START_BUFFER_PARA becomes the
;end of device driver address passed back to DOS.
MOV AX,CS ;start para of VDISK code
ADD AX,VDISKP ;+ length of resident code
MOV START_BUFFER_PARA,AX ;save as buffer start para
CMP EM_SW,0 ;is extended memory requested?
JE DETERMINE_START_X ;if not, we're done here
;If this is the first extended memory VDISK device driver to be installed,
;the start address for I/O is 1 megabyte.
;If one or more extended memory VDISK device drivers have been installed,
;the start address for I/O for THIS device driver is acquired from the
;fields AVAIL_LO and AVAIL_HI in the FIRST VDISK device driver.
;The first extended memory VDISK device driver is located by INT 19H's vector.
MOV FIRST_EM_SW,0FFH ;indicate first VDISK device driver
MOV FIRST_VDISK,CS ;segment addr of first VDISK
PUSH DS ;preserve DS
XOR AX,AX
MOV DS,AX ;set DS = 0
ASSUME DS:INT_VEC
MOV AX,DS:BOOT_VECS ;get segment addr of INT 19H routine
MOV DS,AX ;to DS
ASSUME DS:NOTHING
PUSH CS
POP ES ;set ES = CS
MOV SI,OFFSET VOL_LABEL ;DS:SI point to VOL label field
;in first VDISK (if present)
MOV DI,SI ;ES:DI point to VOL label field of
;this VDISK
MOV CX,VOL_LABEL_LEN ;length of volume label
REP CMPSB ;does INT 19H vector point to a VDISK
;device driver?
JNE DETERMINE_START_A ;jump if this is the first VDISK
;Another extended memory VDISK device driver has been installed.
;Its AVAIL_LO and AVAIL_HI are the first free byte of extended memory.
MOV CS:FIRST_EM_SW,0 ;indicate not first device driver
MOV CS:FIRST_VDISK,DS ;save pointer to 1st device driver
;Copy AVAIL_LO and AVAIL_HI from first VDISK to this VDISK
MOV SI,OFFSET AVAIL_LO ;DS:SI point to AVAIL_LO in first VDISK
MOV DI,SI ;ES:DI point to AVAIL_LO in this VDISK
MOVSW ;copy AVAIL_LO from first to this VDISK
MOVSB ;copy AVAIL_HI
DETERMINE_START_A: ;copy AVAIL_LO and AVAIL_HI to START_EM
POP DS ;set DS = CS
MOV SI,OFFSET AVAIL_LO ;source offset
MOV DI,OFFSET START_EM_LO ;destination offset
MOVSW ;move AVAIL_LO to START_EM_LO
MOVSB ;move AVAIL_HI to START_EM_HI
DETERMINE_START_X:
RET
DETERMINE_START ENDP
SUBTTL VALIDATE parameters
PAGE
;-----------------------------------------------------------------------;
; VALIDATE adjusts parameters as necessary ;
;-----------------------------------------------------------------------;
VAL_SSZ_TBL LABEL WORD ;table of valid sector sizes
VAL_SSZ_S DW 128 ;smallest valid sector size
DW 256
VAL_SSZ_L DW 512 ;largest valid sector size
VAL_SSZ_N EQU ($-VAL_SSZ_TBL)/2 ;number of table entries
ASSUME DS:CSEG
VALIDATE PROC ;validate parameters
MOV BPB_AUSZ,1 ;initial allocation unit is 1 sector
CALL VAL_BSIZE ;validate buffer size
CALL VAL_SSZ ;validate (adjust if necessary) BPB_SSZ
VALIDATE_A:
AND ERR_FLAG,255-ERR_PASS ;indicate nothing changed this pass
MOV AX,BPB_SSZ ;sector size
CWD ;clear DX for division
DIV WPARA_SIZE ;sector size/para size
MOV PARAS_PER_SECTOR,AX ;number of paragraphs/sector
MOV AX,BUFF_SIZE ;requested buffersize in KB
MUL C1024 ;DX:AX = buffer size in bytes
DIV BPB_SSZ ;/sector size = # sectors
MOV BPB_SECN,AX ;store number of sectors
CALL VAL_DIRN ;validate number of directory entries
TEST ERR_FLAG,ERR_PASS ;may have reset sector size
JNZ VALIDATE_A ;recompute directory & FAT sizes
CALL VAL_FAT ;compute FAT entries, validity test
TEST ERR_FLAG,ERR_PASS ;if cluster size altered this pass
JNZ VALIDATE_A ;recompute directory & FAT sizes
;Make certain buffer size is large enough to contain:
; boot sector(s)
; FAT sector(s)
; directory sector(s)
; at least 1 data cluster
MOV AL,BPB_FATN ;number of FAT copies
CBW ;clear AH
MUL BPB_FATSZ ;* sectors for 1 FAT = FAT sectors
ADD AX,BPB_RES ;+ reserved sectors
ADD AX,DIR_SECTORS ;+ directory sectors
MOV CL,BPB_AUSZ ;get sectors/cluster
XOR CH,CH ;CX = sectors in one cluster
ADD AX,CX ;+ one data cluster
CMP BPB_SECN,AX ;compare with sectors available
JAE VALIDATE_X ;jump if enough sectors
CMP DIR_SECTORS,1 ;down to 1 directory sector?
JBE VALIDATE_C ;can't let it go below 1
MOV AX,BPB_SSZ ;sector size
CWD ;clear DX for division
DIV DIRE_SIZE ;sectorsize/dir entry size = entries/sector
SUB BPB_DIRN,AX ;reduce directory entries by 1 sector
OR ERR_FLAG,ERR_DIRN ;indicate directory entries adjusted
JMP VALIDATE_A ;retry with new directory entries number
VALIDATE_C: ;not enough space for any VDISK
OR ERR_FLAG,ERR_SYSSZ
VALIDATE_X:
RET
SUBTTL VAL_BSIZE Validate buffer size
PAGE
;-----------------------------------------------------------------------;
; VAL_BSIZE adjusts the buffer size as necessary ;
;-----------------------------------------------------------------------;
VAL_BSIZE PROC
CALL GET_MSIZE ;determine memory available to VDISK
;returns available KB in AX
OR AX,AX ;is any memory available at all?
JNZ VAL_BSIZE_B ;yes, continue
OR ERR_FLAG,ERR_SYSSZ ;indicate system too small for VDISK
MOV BUFF_SIZE,1 ;set up minimal values to continue init
MOV AX,VAL_SSZ_S ;smallest possible sector size
MOV BPB_SSZ,AX
MOV BPB_DIRN,4 ;4 directory entries
RET
VAL_BSIZE_B: ;some memory is available
CMP AX,BUFF_SIZE ;is available memory >= requested?
JAE VAL_BSIZE_C ;if so, we're done
MOV BUFF_SIZE,AX ;give all available memory
OR ERR_FLAG,ERR_BSIZE ;indicate buffersize adjusted
VAL_BSIZE_C:
RET
GET_MSIZE PROC ;determine memory available to VDISK
;returns KB available in AX
CMP EM_SW,0 ;extended memory?
JE GET_MSIZE_2 ;use non-extended memory routine
MOV AH,EM_MEMSIZE ;function code to AH
INT EM_INT ;get extended memory size in AX
JC GET_MSIZE_Z ;if error, no extended memory installed
MUL C1024 ;DX,AX = bytes of extended memory
ADD DX,10H ;DX,AX = high addr of extended memory+1
SUB AX,AVAIL_LO ;- address of first available byte
SBB DL,AVAIL_HI ;is number of free bytes
DIV C1024 ;AX = number of whole free kilobytes
RET
GET_MSIZE_2: ;non-extended memory size determination
;Compute AX = total system size, - (VDISK end address + 64KB)
MOV AX,START_BUFFER_PARA ;paragraph end of VDISK code
XOR DX,DX ;clear for division
DIV PARA_PER_KB ;KB address of load point
ADD DX,0FFFFH ;round upward to KB boundary
ADC AX,MIN_MEMORY_LEFT ;pick up CY and the 64KB we should leave
PUSH AX ;save across interrupt
INT MEM_SIZE ;get total system size
POP DX ;amount of total that we can't use
SUB AX,DX ;available space to VDISK
JNC GET_MSIZE_X ;exit if positive
GET_MSIZE_Z:
XOR AX,AX ;indicate no memory available
GET_MSIZE_X: ;exit from memory size determination
RET
GET_MSIZE ENDP
VAL_BSIZE ENDP
SUBTTL VAL_SSZ Validate Sector Size
PAGE
;-----------------------------------------------------------------------;
; VAL_SSZ validates sector size, adjusting if necessary ;
;-----------------------------------------------------------------------;
VAL_SSZ PROC ;validate sector size
CMP CS:EM_SW,0 ;extended memory ?
JE VAL_SSZ_ST ;no,go check sector size
MOV BX,MAXSEC_TRF ;move number of sectors to transfer
CMP BX,1 ;> or equal to 1 ?
JB DFLT_TRF ;set default if it is
CMP BX,8 ;> than 8 ?
JA DFLT_TRF ;set default if it is
JMP VAL_SSZ_ST ;continue processing
DFLT_TRF: ;set default
MOV MAXSEC_TRF,DFLT_ESS ;
MOV BX,MAXSEC_TRF ;
OR CS:ERR_FLAG,ERR_ESIZE ;indicate transfer size adjusted
VAL_SSZ_ST: ;validate sector size
MOV MAX_CNT,BX ;initialize maximum number of sectors
;to transfer for extended memory case
MOV BX,BPB_SSZ ;requested sector size
MOV CX,VAL_SSZ_N ;number of table entries
MOV SI,OFFSET VAL_SSZ_TBL ;DS:SI point to table start
VAL_SSZ_A:
LODSW ;get table entry, step table pointer
CMP AX,BX ;is value in table?
JE VAL_SSZ_X ;exit if value found
LOOP VAL_SSZ_A ;loop until table end
MOV BX,DFLT_SSZ ;get default sector size
MOV BPB_SSZ,BX ;set sector size to default value
OR ERR_FLAG,ERR_SSZ ;indicate sector size adjusted
VAL_SSZ_X:
;Compute the maximum number of sectors that can be moved in 64KB (less one)
;Restricting moves to this amount avoids 64KB boundary problems.
CMP CS:EM_SW,0 ;extended memory ?
JNE SIZE_DONE ;yes, we are done
XOR DX,DX
MOV AX,0FFFFH ;64KB - 1
DIV BX ;/sector size
MOV MAX_CNT,AX ;max sectors in one move
SIZE_DONE:
RET
VAL_SSZ ENDP
SUBTTL VAL_DIRN Validate number of directory entries
PAGE
;-----------------------------------------------------------------------;
; VAL_DIRN validates and adjusts the number of directory entries. ;
; ;
; Minimum is MIN_DIRN, maximum is MAX_DIRN. If outside these ;
; limits, DFLT_DIRN is used. ;
; ;
; The number of directory entries is rounded upward to fill ;
; a sector ;
;-----------------------------------------------------------------------;
VAL_DIRN PROC
MOV AX,BPB_DIRN ;requested directory entries
CMP AX,MIN_DIRN ;if less than minimum
JB VAL_DIRN_A ;use default instead
CMP AX,MAX_DIRN ;if <= maximum
JBE VAL_DIRN_B ;accept value as provided
VAL_DIRN_A:
MOV AX,DFLT_DIRN ;use default directory entries
OR ERR_FLAG,ERR_DIRN ;indicate directory entries adjusted
VAL_DIRN_B: ;AX is number of directory entries
MUL DIRE_SIZE ;* 32 = bytes of directory requested
DIV BPB_SSZ ;/ sector size = # of directory sectors
OR DX,DX ;test remainder for zero
JZ VAL_DIRN_C ;jump if exact fit
INC AX ;increment directory sectors
OR ERR_FLAG,ERR_DIRN ;indicate directory entries adjusted
VAL_DIRN_C: ;make sure enough sectors available
MOV DX,BPB_SECN ;total sectors on media
SUB DX,BPB_RES ;less reserved sectors
SUB DX,2 ;less minimum FAT and 1 data sector
CMP AX,DX ;if directory sectors <= available
JLE VAL_DIRN_D ;use requested amount
MOV AX,1 ;use only one directory sector
OR ERR_FLAG,ERR_DIRN ;indicate directory entries adjusted
VAL_DIRN_D:
MOV DIR_SECTORS,AX ;save number of directory sectors
MUL BPB_SSZ ;dir sectors * sector size = dir bytes
DIV DIRE_SIZE ;dir bytes / entry size = entries
MOV BPB_DIRN,AX ;store adjusted directory entries
RET
VAL_DIRN ENDP
SUBTTL VAL_FAT Validate File Allocation Table (FAT)
PAGE
;-----------------------------------------------------------------------;
;VAL_FAT computes: ;
;BPB_FATSZ, the number of sectors required per FAT copy ;
; ;
;Each FAT entry is 12 bits long, for a maximum of 4095 FAT entries. ;
;(A few FAT entries are reserved, so the highest number of FAT entries ;
;we permit is 0FE0H.) With large buffer sizes and small sector sizes, ;
;we have more allocation units to describe than a 12-bit entry will ;
;describe. If the number of FAT entries is too large, the sector size ;
;is increased (up to a maximum of 512 bytes), and then the allocation ;
;unit (cluster) size is doubled, until we have few enough allocation ;
;units to be properly described in 12 bits. ;
; ;
;This computation is slightly conservative in that the FAT entries ;
;necessary to describe the FAT sectors are included in the computation. ;
;-----------------------------------------------------------------------;
VAL_FAT PROC
MOV AX,BPB_SECN ;total number of sectors
SUB AX,BPB_RES ;don't count boot sector(s)
SUB AX,DIR_SECTORS ;don't count directory sectors
JG VAL_FAT_A ;jump if some remaining
MOV BPB_SSZ,DFLT_SSZ ;force default sector size
OR ERR_FLAG,ERR_SSZ+ERR_PASS ;indicate sector size adjusted
JMP SHORT VAL_FAT_X ;recompute all values
VAL_FAT_A:
XOR DX,DX ;clear DX for division
MOV CL,BPB_AUSZ ;CX = sectors/cluster
XOR CH,CH
DIV CX ;whole number of clusters in AX
ADD DX,0FFFFH ;set carry if remainder
ADC AX,0 ;increment AX if remainder
CMP AX,MAX_FATE ;number of FAT entries too large?
JBE VAL_FAT_C ;no, continue
MOV AX,BPB_SSZ ;pick up current sector size
CMP AX,VAL_SSZ_L ;already at largest permitted?
JE VAL_FAT_B ;yes, can't make it any larger
SHL BPB_SSZ,1 ;double sector size
OR ERR_FLAG,ERR_SSZB ;indicate sector size adjusted
JMP SHORT VAL_FAT_X ;recompute all sizes with new BPBSSZ
VAL_FAT_B: ;sector size is at maximum
SHL BPB_AUSZ,1 ;double allocation unit size
OR ERR_FLAG,ERR_PASS ;indicate another pass required
JMP SHORT VAL_FAT_X ;recompute values
VAL_FAT_C: ;FAT size = 1.5 * number of clusters
MOV CX,AX ;number of clusters
SHL AX,1 ;* 2
ADD AX,CX ;* 3
SHR AX,1 ;* 1.5
ADC AX,3 ;add 3 bytes for first 2 FAT entries
;(media descriptor and FFFFH), and CY
XOR DX,DX ;clear DX for division
DIV BPB_SSZ ;FAT size/sector size
ADD DX,0FFFFH ;set carry if remainder
ADC AX,0 ;round upward
MOV BPB_FATSZ,AX ;number of sectors for 1 FAT copy
VAL_FAT_X:
RET
VAL_FAT ENDP
VALIDATE ENDP
SUBTTL COPY_BPB Copy BPB to Boot Record
PAGE
;-----------------------------------------------------------------------;
; COPY_BPB copies the BIOS Parameter Block (BPB) ;
; to the VDISK Boot Record ;
;-----------------------------------------------------------------------;
ASSUME DS:CSEG
COPY_BPB PROC ;Copy BBP to Boot Record
PUSH DS
POP ES ;set ES = DS
MOV CX,BPB_LEN ;length of BPB
MOV SI,OFFSET BPB ;source offset
MOV DI,OFFSET BOOT_BPB ;target offset
REP MOVSB ;copy BPB to boot record
RET
COPY_BPB ENDP
SUBTTL VERIFY_EXTENDER
PAGE
;-----------------------------------------------------------------------;
; VERIFY_EXTENDER makes sure that if an Expansion Unit is ;
; installed, the memory size switches on the Extender Card ;
; are correctly set. ;
;-----------------------------------------------------------------------;
ASSUME DS:CSEG
EXT_P210 EQU 0210H ;write to latch expansion bus data
;read to verify expansion bus data
EXT_P213 EQU 0213H ;Expansion Unit status
VERIFY_EXTENDER PROC
NOP
MOV DX,EXT_P210 ;Expansion bus data port address
MOV AX,5555H ;set data pattern
OUT DX,AL ;write 55H to control port
PUSH DX
POP DX
JMP SHORT $+2 ;Let the I/O circuits catch up
IN AL,020h ;Clear the CMOS bus drivers!
IN AL,DX ;recover data
CMP AH,AL ;did we recover the same data?
JNE VERIFY_EXTENDER_X ;if not, no extender card
NOT AX ;set AX = 0AAAAH
OUT DX,AL ;write 0AAH to control port
PUSH DX ;load data line
POP DX ;load data line
JMP SHORT $+2 ;Let the I/O circuits catch up
IN AL,020h ;Clear the CMOS bus drivers!
IN AL,DX ;recover data
CMP AH,AL ;did we recover the same data?
JNE VERIFY_EXTENDER_X ;if not, no extender card
;Expansion Unit is present.
;Determine what the switch settings should be on the Extender Card
INT MEM_SIZE ;get system memory size in KB in AX
ADD AX,63D ;memory size + 63K
MOV CL,6 ;2^6 = 64
SHR AX,CL ;divide by 64
;AX is highest segment address
MOV AH,AL ;save number of segments
;Read Expander card switch settings
MOV DX,EXT_P213 ;expansion unit status
IN AL,DX ;read status
;bits 7-4 (hi nibble) are switches
MOV CL,4 ;shift count
SHR AL,CL ;shift switches to bits 3-0 of AL
CMP AH,AL ;do switches match memory size?
JE VERIFY_EXTENDER_X ;yes, exit normally
OR ERR_FLAG,ERR_EXTSW ;indicate switch settings are wrong
VERIFY_EXTENDER_X:
RET
VERIFY_EXTENDER ENDP
SUBTTL UPDATE_AVAIL
PAGE
;-----------------------------------------------------------------------;
; UPDATE_AVAIL updates the address of the first byte in extended ;
; memory not used by any VDISK buffer ;
;-----------------------------------------------------------------------;
UPDATE_AVAIL PROC ;update AVAIL_LO and AVAIL_HI of first VDISK
MOV AX,BUFF_SIZE ;number of KB of VDISK buffer
MUL C1024 ;DX,AX = number of bytes of VDISK buffer
PUSH DS
MOV DS,FIRST_VDISK ;set DS to first VDISK
ADD DS:AVAIL_LO,AX ;update first available byte location
ADC DS:AVAIL_HI,DL
POP DS
RET
UPDATE_AVAIL ENDP
SUBTTL FORMAT_VDISK
PAGE
;-----------------------------------------------------------------------;
; This Request Header is used by MOVE_VDISK to move the ;
; first few sectors of the virtual disk (boot, FAT, and ;
; Directory) into extended memory. ;
;-----------------------------------------------------------------------;
MOVE_RH DB MOVE_RH_L ;length of request header
DB 0 ;sub unit
DB 8 ;output operation
DW 0 ;status
DQ ? ;reserved for DOS
DB ? ;media descriptor byte
MOVE_RHO DW ? ;offset of data transfer address
MOVE_RHS DW ? ;segment of data transfer address
MOVE_RHCNT DW ? ;count of sectors to transfer
DW 0 ;starting sector number
MOVE_RH_L EQU $-MOVE_RH ;length of request header
;-----------------------------------------------------------------------;
; FORMAT_VDISK formats the boot sector, FAT, and directory of an ;
; extended memory VDISK in storage immediately following ;
; VDISK code, in preparation for moving to extended memory. ;
;-----------------------------------------------------------------------;
FORMAT_VDISK PROC ;format boot record, FATs and directory
MOV AX,CS ;compute 20-bit address
MUL WPARA_SIZE ;16 * segment
ADD AX,OFFSET MSGEND ;+ offset
ADC DL,0 ;pick up carry
ADD AX,STACK_SIZE ;plus stack size
ADC DL,0 ;pick up carry
DIV WPARA_SIZE ;split into segment(AX)&offset(DX)
MOV MOVE_RHS,AX ;save in Request Header for move
MOV MOVE_RHO,DX
MOV DI,DX ;offset to DI
MOV ES,AX ;segment to ES
;copy the boot record
MOV SI,OFFSET BOOT_RECORD ;point to source field
MOV AX,BPB_RES ;number of reserved sectors
MUL BPB_SSZ ;* sector size = length of boot records
MOV CX,AX ;length to CX for move
REP MOVSB ;move boot record(s)
;format the FAT(s)
MOV CL,BPB_FATN ;number of FATs
XOR CH,CH
FORMAT_VDISK_A: ;set up one FAT
PUSH CX ;save loop counter on stack
MOV AL,BPB_MCB ;media control byte
STOSB ;store media control byte, increment DI
MOV AX,0FFFFH ;bytes 2 and 3 of FAT are 0FFH
STOSW
MOV AX,BPB_FATSZ ;number of sectors per FAT
MUL BPB_SSZ ;* sector size = length of FAT in bytes
SUB AX,3 ;less the 3 bytes we've stored
MOV CX,AX ;count to CX
XOR AX,AX
REP STOSB ;clear remainder of FAT
POP CX ;get loop counter off stack
LOOP FORMAT_VDISK_A ;loop for all copies of the FAT
;Format the directory
MOV SI,OFFSET VOL_LABEL ;point to volume label
MOV CX,VOL_LABEL_LEN ;length of volume directory entry
REP MOVSB ;move volume id to directory
MOV AX,DIR_ENTRY_SIZE ;length of 1 directory entry
MUL BPB_DIRN ;* number entries = bytes of directory
SUB AX,VOL_LABEL_LEN ;less length of volume label
MOV CX,AX ;CX = length of rest of directory
XOR AX,AX
REP STOSB ;clear directory to nulls
RET
FORMAT_VDISK ENDP
SUBTTL MOVE_VDISK
PAGE
;-----------------------------------------------------------------------;
; MOVE_VDISK moves the formatted boot sector, FAT, and directory ;
; into extended memory. ;
;-----------------------------------------------------------------------;
MOVE_VDISK PROC
MOV AL,BPB_FATN ;number of FAT copies
CBW ;clear AH
MUL BPB_FATSZ ;number of FAT sectors
ADD AX,BPB_RES ;+ reserved sectors
ADD AX,DIR_SECTORS ;+ directory sectors
MOV MOVE_RHCNT,AX ;store as I/O length
MOV BX,OFFSET MOVE_RH ;DS:BX point to request header
PUSH DS ;make sure DS gets preserved
CALL INOUT ;move to extended memory
POP DS
RET
MOVE_VDISK ENDP
SUBTTL UPDATE_BOOT
PAGE
;-----------------------------------------------------------------------;
; UPDATE_BOOT updates the BOOT_EM word in the first extended ;
; memory VDISK (address 10 001EH) to show the kilobyte address ;
; of the first extended memory byte not used by any VDISK buffer. ;
;-----------------------------------------------------------------------;
UPDATE_BOOT PROC
PUSH DS
MOV DS,FIRST_VDISK ;set DS to first VDISK
MOV AX,DS:AVAIL_LO ;24-bit end address of all VDISKs
MOV DL,DS:AVAIL_HI
XOR DH,DH
POP DS
DIV C1024 ;address / 1024
MOV BOOT_EM,AX ;store in temporary location
MOV AX,2 ;length of block move is 2 bytes
MOV TGT.DESC_LMT,AX
MOV SRC.DESC_LMT,AX
MOV AX,PARA_SIZE ;16
MOV CX,CS ;our segment address
MUL CX ;16 * segment address
ADD AX,OFFSET BOOT_EM ;+ offset of source data
ADC DL,0 ;pick up any carry
MOV SRC.DESC_BASEL,AX ;store source base address
MOV SRC.DESC_BASEH,DL
MOV TGT.DESC_BASEL,BOOT_EM_OFF ;offset of BOOT_EM
MOV TGT.DESC_BASEH,10H ;1 megabyte
MOV CX,1 ;move 1 word
PUSH CS
POP ES
MOV SI,OFFSET GDT ;ES:DI point to global descriptor table
MOV AH,EM_BLKMOVE ;function code
INT EM_INT ;move BOOT_EM to 10 001EH
RET
UPDATE_BOOT ENDP
SUBTTL STEAL_INT19
PAGE
;-----------------------------------------------------------------------;
; STEAL_INT19 changes the INT 19H vector to point to this VDISK ;
; so that subsequent extended memory VDISKS may locate the ;
; AVAIL_HI and AVAIL_LO fields to determine their buffer start ;
; addresses. ;
;-----------------------------------------------------------------------;
STEAL_INT19 PROC
PUSH DS
XOR AX,AX
MOV DS,AX ;set DS = 0
ASSUME DS:INT_VEC
CLI ;disable interrupts
LES DI,DS:BOOT_VEC ;get original vector's content
MOV CS:INTV19O,DI ;save original vector
MOV CS:INTV19S,ES
MOV DS:BOOT_VECO,OFFSET VDISK_INT19 ;offset of new INT routine
MOV DS:BOOT_VECS,CS ;segment of new INT routine
STI ;enable interrupts again
POP DS ;restore DS
RET
STEAL_INT19 ENDP
SUBTTL FILL_RH Fill in Request Header
PAGE
;-----------------------------------------------------------------------;
; FILL_RH fills in the Request Header returned to DOS ;
;-----------------------------------------------------------------------;
ASSUME DS:CSEG
FILL_RH PROC ;fill in INIT Request Header fields
MOV CX,START_BUFFER_PARA ;segment end of VDISK resident code
MOV AX,PARAS_PER_SECTOR ;paragraphs per sector
MUL BPB_SECN ;* number of sectors
ADD AX,CX ;+ starting segment
MOV DX,AX ;DX is segment of end VDISK buffer
CMP EM_SW,0 ;if extended memory not requested
JE FILL_RH_A ;skip DX adjustment
MOV DX,CX ;end of code segment addr
FILL_RH_A: ;DX is proper ending segment address
MOV AL,1 ;number of units
test CS:err_flag2,err_baddos
jnz dont_install
TEST ERR_FLAG,ERR_SYSSZ+ERR_EXTSW ;if bypassing install
JZ FILL_RH_B ;jump if installing driver
dont_install:
MOV DX,CS ;segment of end address
XOR AL,AL ;number of units is zero
FILL_RH_B:
PUSH DS ;preserve DS
LDS BX,RH_PTRA ;get Request Header addr in DS:BX
MOV RH.RH0_NUN,AL ;store number of units (0 or 1)
MOV RH.RH0_ENDO,0 ;end offset is always zero
MOV RH.RH0_ENDS,DX ;end of VDISK or end of buffer
MOV RH.RH0_BPBO,OFFSET BPB_PTR
MOV RH.RH0_BPBS,CS ;BPB array address
POP DS ;restore DS
RET
FILL_RH ENDP
SUBTTL WRITE_MESSAGES and associated routines
PAGE
;-----------------------------------------------------------------------;
; WRITE_MESSAGE writes a series of messages to the standard ;
; output device showing the VDISK parameter values actually used. ;
;-----------------------------------------------------------------------;
CHAR4 DB 'nnnn$' ;build 4 ASCII decimal digits
ASSUME DS:CSEG
WRITE_MESSAGES PROC ;display all messages
MSG IMSG ;'VDISK virtual disk $'
test CS:err_flag2,err_baddos
jz check_dos_version
msg errm8
ret
;If DOS Version 3.x is in use, the Request Header contains a drive code
;that is displayed to show which drive letter was assigned to this
;VDISK. This field is not present in the DOS Version 2 Request Header.
check_dos_version:
MOV AH,DOS_VERS ;get DOS version call
INT DOS ;invoke DOS
CMP AL,3 ;DOS Version 3 or greater?
JB WRITE_MESSAGES_A ;no, bypass drive letter
PUSH DS ;preserve DS
LDS BX,RH_PTRA ;get Request Header Address
MOV DL,RH.RH0_DRIV ;get drive code
ADD DL,'A' ;convert to drive letter
POP DS ;restore DS
MOV AH,DOS_PCHR ;function code to write character in DL
INT DOS ;display drive letter
MOV DL,':' ;display trailing colon
INT DOS
WRITE_MESSAGES_A:
MSG MSGCRLF ;end the first line
;If any of the user specified values has been adjusted, issue an
;appropriate message
TEST ERR_FLAG,ERR_BSIZE ;was buffersize adjusted?
JZ WRITE_MESSAGES_B ;if not, skip message
MSG ERRM1 ;buffer size adjusted
WRITE_MESSAGES_B:
TEST ERR_FLAG,ERR_SSZ ;was sector size adjusted?
JZ WRITE_MESSAGES_C ;if not, skip message
MSG ERRM2 ;sector size adjusted
WRITE_MESSAGES_C:
TEST ERR_FLAG,ERR_DIRN ;were directory entries adjusted?
JZ WRITE_MESSAGES_D0 ;if not, skip message
MSG ERRM3 ;directory entries adjusted
WRITE_MESSAGES_D0:
TEST ERR_FLAG,ERR_ESIZE ;was transfer size adjusted?
JZ WRITE_MESSAGES_D ;if not, skip message
MSG ERRM7 ;transfer size adjusted
WRITE_MESSAGES_D:
TEST ERR_FLAG,ERR_SWTCH ;was an invalid switch character found?
JZ WRITE_MESSAGES_E ;if not, skip message
MSG ERRM5 ;invalid switch character
WRITE_MESSAGES_E:
TEST ERR_FLAG,ERR_SYSSZ ;is system size too small to install?
JZ WRITE_MESSAGES_F ;if not, bypass error message
MSG ERRM4 ;too large for system storage
RET ;skip messages showing adjusted sizes
WRITE_MESSAGES_F:
TEST ERR_FLAG,ERR_EXTSW ;extender card switches wrong?
JZ WRITE_MESSAGES_G ;if not, bypass error message
MSG ERRM6 ;extender card switches wrong msg
RET ;skip remaining messages
WRITE_MESSAGES_G: ;display adjusted size messages
MSG MSG1 ;buffer size:
MOV DX,BUFF_SIZE ;buffer size in binary
CALL STOR_SIZE ;convert binary to ASCII decimal
MSG CHAR4 ;print 4 decimals
MSG MSG2 ;KB,CR,LF
MSG MSG3 ;sector size:
MOV DX,BPB_SSZ
CALL STOR_SIZE ;convert binary to ASCII decimal
MSG CHAR4 ;print 4 decimals
MSG MSGCRLF ;finish off line
MSG MSG4 ;directory entries:
MOV DX,BPB_DIRN ;number of directory entries
CALL STOR_SIZE
MSG CHAR4 ;print 4 decimals
MSG MSGCRLF ;finish off the line
CMP CS:EM_SW,0 ;extended memory ?
JE END_LINE ;
MSG MSG5 ;transfer size:
MOV DX,MAXSEC_TRF
CALL STOR_SIZE ;convert binary to ASCII decimal
MSG CHAR4 ;print 4 decimals
MSG MSGCRLF ;finish off line
END_LINE:
MSG MSGCRLF ;one more blank line to set it off
RET ;return to INIT_P1
;SHOW_MSG displays a string at DS:DX on the standard output device
;String is terminated by a $
SHOW_MSG PROC ;display string at DS:DX
PUSH AX ;preserve AX across call
MOV AH,DOS_PSTR ;DOS function code
INT DOS ;invoke DOS print string function
POP AX ;restore AX
RET
SHOW_MSG ENDP
;STOR_SIZE converts the content of DX to 4 decimal characters in CHAR4
;(DX must be <= 9999)
STOR_SIZE PROC ;convert DX to 4 decimals in CHAR4
;develop 4 packed decimal digits in AX
XOR AX,AX ;clear result register
MOV CX,16 ;shift count
STOR_SIZE_B:
SHL DX,1 ;shift high bit into carry
ADC AL,AL ;double AL, carry in
DAA ;adjust for packed decimal
XCHG AL,AH
ADC AL,AL ;double high byte, carry in
DAA
XCHG AL,AH
LOOP STOR_SIZE_B ;AX contains 4 packed decimal digits
PUSH CS
POP ES ;point ES:DI to output string
MOV DI,OFFSET CHAR4
MOV CX,1310H ;10H in CL is difference between blank and zero
;13H in CH is decremented and ANDed to force
;last character not to be zero suppressed
PUSH AX ;save AX on stack
MOV DL,AH ;2 decimals to DL
CALL STOR_SIZE_2 ;display DL as 2 decimal characters
POP DX ;bring low 2 decimals into DL
STOR_SIZE_2: ;display DL as 2 decimal characters
MOV DH,DL ;save 2 decimals in DH
SHR DL,1 ;shift high order decimal right to low position
SHR DL,1
SHR DL,1
SHR DL,1
CALL STOR_SIZE_1 ;display low nibble of DL
MOV DL,DH ;get low decimal from pair
STOR_SIZE_1: ;display low nibble of DL as 1 decimal char
AND DL,0FH ;clear high nibble
JZ STOR_SIZE_Z ;if digit is significant,
XOR CL,CL ;defeat zero suppression
STOR_SIZE_Z:
DEC CH ;decrement zero suppress counter
AND CL,CH ;always display least significant digit
OR DL,'0' ;convert packed decimal to ASCII
SUB DL,CL ;zero suppress (nop or change '0' to ' ')
MOV AL,DL ;char to DL
STOSB ;store char at ES:DI, increment DI
RET
STOR_SIZE ENDP
WRITE_MESSAGES ENDP
INIT_P1 ENDP ;end of INIT part one
;-----------------------------------------------------------------------;
; VDISK Message definitions ;
;-----------------------------------------------------------------------;
IMSG DB 'VDISK virtual disk ','$'
ERRM1 DB ' Buffer size adjusted',CR,LF,'$'
ERRM2 DB ' Sector size adjusted',CR,LF,'$'
ERRM3 DB ' Directory entries adjusted',CR,LF,'$'
ERRM4 DB ' VDISK not installed - insufficient memory'
DB CR,LF,CR,LF,BEL,'$'
ERRM5 DB ' Invalid switch character',CR,LF,'$'
ERRM6 DB ' VDISK not installed - Extender Card switches'
DB CR,LF
DB ' do not match system memory size'
DB CR,LF,CR,LF,BEL,'$'
ERRM7 DB ' Transfer size adjusted',CR,LF,'$'
ERRM8 DB ' VDISK not installed - Incorrect DOS version'
DB CR,LF,CR,LF,BEL,'$'
MSG1 DB ' Buffer size: $'
MSG2 DB ' KB'
MSGCRLF DB CR,LF,'$'
MSG3 DB ' Sector size: $'
MSG4 DB ' Directory entries: $'
MSG5 DB ' Transfer size: $'
MSGEND LABEL BYTE ; End of message text
CSEG ENDS
END
mentioned in the .asm file:
Message text for VDISK is in module VDISKMSG.
include versiona.inc
In DOS version 4.0 they bundled everything into vdisk.asm, and now it
builds with MASM 4.0.
Hard to find, but PCjs Machines had it. And if that fails, here it is.
PAGE ,132
TITLE VDISK - Virtual Disk Device Driver
;VDISK simulates a disk drive, using Random Access Memory as the storage medium.
;This program is meant to serve as an example of a device driver. It does not
;reflect the current level of VDISK.SYS.
;(C) Copyright IBM Corporation, 1984,1985,1988
;Licensed Material - Program Property of IBM
;Authors: Dick Dievendorff
; Rod Springhetti
; David M. Sewell
;Add the following statement to CONFIG.SYS
; DEVICE=[d:][path]VDISK.SYS bbb sss ddd [/E:m]
; where: bbb is the desired buffer size (in kilobytes)
; minimum 1KB, maximum is size of available memory,
; default is 64KB.
; VDISK will leave at least 64KB of available memory,
; although subsequent device drivers (other than VDISK)
; other programs that make themselves resident, and
; COMMAND.COM will result in less than 64KB as shown
; by CHKDSK.
; Must be large enough for 1 boot sector + FAT sectors
; + 1 directory sector + at least 1 data cluster,
; or the device driver won't be installed.
; sss is the desired sector size (in bytes)
; 128, 256, or 512, default is 128.
; Will be adjusted if number of FAT entries > 0FE0H
; ddd is the desired number of directory entries
; Minimum 2, maximum 512, default 64.
; Will be rounded upward to sector size boundary.
; /E may only be used if extended memory above 1 megabyte
; is to be used. INT 15H functions 87H and 88H are used
; to read and write this extended memory.
; The m parameter in the /E option specifies the maximum
; number of sectors that the VDISK will transfer at a time.
; Optional values are 1,2,3,4,5,6,7 or 8 sectors, the default
; is 8 sectors.
; Brackets indicate optional operands.
; Samples:
; DEVICE=\path\VDISK.SYS 160 512 64
; results in a 160KB VDISK, with 512 byte sectors, 64 directory entries
; DEVICE=VDISK.SYS Buffersize 60 Sectorsize 128 Directory entries 32
; (since only numbers are interpreted, you may comment the line with
; non-numeric characters)
SUBTTL Structure Definitions
PAGE
;-----------------------------------------------------------------------;
; Request Header (Common portion) ;
;-----------------------------------------------------------------------;
RH EQU DS:[BX] ;addressability to Request Header structure
RHC STRUC ;fields common to all request types
DB ? ;length of Request Header (including data)
DB ? ;unit code (subunit)
RHC_CMD DB ? ;command code
RHC_STA DW ? ;status
DQ ? ;reserved for DOS
RHC ENDS ;end of common portion
CMD_INPUT EQU 4 ;RHC_CMD is INPUT request
;status values for RHC_STA
STAT_DONE EQU 01H ;function complete status (high order byte)
STAT_CMDERR EQU 8003H ;invalid command code error
STAT_CRC EQU 8004H ;CRC error
STAT_SNF EQU 8008H ;sector not found error
STAT_BUSY EQU 0200H ;busy bit (9) for Removable Media call
;-----------------------------------------------------------------------;
; Request Header for INIT command ;
;-----------------------------------------------------------------------;
RH0 STRUC
DB (TYPE RHC) DUP (?) ;common portion
RH0_NUN DB ? ;number of units
;set to 1 if installation succeeds,
;set to 0 to cause installation failure
RH0_ENDO DW ? ;offset of ending address
RH0_ENDS DW ? ;segment of ending address
RH0_BPBO DW ? ;offset of BPB array address
RH0_BPBS DW ? ;segment of BPB array address
RH0_DRIV DB ? ;drive code (DOS 3 only)
RH0 ENDS
RH0_BPBA EQU DWORD PTR RH0_BPBO ;offset/segment of BPB array address
;Note: RH0_BPBA at entry to INIT points to all after DEVICE= on CONFIG.SYS stmt
;-----------------------------------------------------------------------;
; Request Header for MEDIA CHECK Command ;
;-----------------------------------------------------------------------;
RH1 STRUC
DB (TYPE RHC) DUP (?) ;common portion
DB ? ;media descriptor
RH1_RET DB ? ;return information
RH1 ENDS
;-----------------------------------------------------------------------;
; Request Header for BUILD BPB Command ;
;-----------------------------------------------------------------------;
RH2 STRUC
DB (TYPE RHC) DUP(?) ;common portion
DB ? ;media descriptor
DW ? ;offset of transfer address
DW ? ;segment of transfer address
RH2_BPBO DW ? ;offset of BPB table address
RH2_BPBS DW ? ;segment of BPB table address
RH2 ENDS
;-----------------------------------------------------------------------;
; Request Header for INPUT, OUTPUT, and OUTPUT with verify ;
;-----------------------------------------------------------------------;
RH4 STRUC
DB (TYPE RHC) DUP (?) ;common portion
DB ? ;media descriptor
RH4_DTAO DW ? ;offset of transfer address
RH4_DTAS DW ? ;segment of transfer address
RH4_CNT DW ? ;sector count
RH4_SSN DW ? ;starting sector number
RH4 ENDS
RH4_DTAA EQU DWORD PTR RH4_DTAO ;offset/segment of transfer address
;-----------------------------------------------------------------------;
; Segment Descriptor (part of Global Descriptor Table) ;
;-----------------------------------------------------------------------;
DESC STRUC ;data segment descriptor
DESC_LMT DW 0 ;segment limit (length)
DESC_BASEL DW 0 ;bits 15-0 of physical address
DESC_BASEH DB 0 ;bits 23-16 of physical address
DB 0 ;access rights byte
DW 0 ;reserved
DESC ENDS
SUBTTL Equates and Macro Definitions
PAGE
MEM_SIZE EQU 12H ;BIOS memory size determination INT
;returns system size in KB in AX
EM_INT EQU 15H ;extended memory BIOS interrupt INT
EM_BLKMOVE EQU 87H ;block move function
EM_MEMSIZE EQU 88H ;memory size determination in KB
BOOT_INT EQU 19H ;bootstrap DOS
DOS EQU 21H ;DOS request INT
DOS_PCHR EQU 02H ;print character function
DOS_PSTR EQU 09H ;print string function
DOS_VERS EQU 30H ;get DOS version
TAB EQU 09H ;ASCII tab
LF EQU 0AH ;ASCII line feed
CR EQU 0DH ;ASCII carriage return
BEL EQU 07H ;ASCII bell
PARA_SIZE EQU 16 ;number of bytes in one 8088 paragraph
DIR_ENTRY_SIZE EQU 32 ;number of bytes per directory entry
MAX_FATE EQU 0FE0H ;largest number of FAT entries allowed
;default values used if parameters are omitted
DFLT_BSIZE EQU 64 ;default VDISK buffer size (KB)
DFLT_SSZ EQU 128 ;default sector size
DFLT_DIRN EQU 64 ;default number of directory entries
DFLT_ESS EQU 8 ;default maximum sectors to transfer
MIN_DIRN EQU 2 ;minimum number of directory entries
MAX_DIRN EQU 512 ;maximum number of directory entries
STACK_SIZE EQU 512 ;length of stack during initialization
;-----------------------------------------------------------------------;
; MSG invokes the console message subroutine ;
;-----------------------------------------------------------------------;
MSG MACRO TEXT
PUSH DX ;;save DX across call
MOV DX,OFFSET TEXT ;;point to message
CALL SHOW_MSG ;;issue message
POP DX
ENDM
SUBTTL Resident Data Area
PAGE
;-----------------------------------------------------------------------;
; Map INT 19H vector in low storage ;
;-----------------------------------------------------------------------;
INT_VEC SEGMENT AT 00H
ORG 4*BOOT_INT
BOOT_VEC LABEL DWORD
BOOT_VECO DW ? ;offset
BOOT_VECS DW ? ;segment
INT_VEC ENDS
CSEG SEGMENT PARA PUBLIC 'CODE'
ASSUME CS:CSEG
;-----------------------------------------------------------------------;
; Resident data area. ;
; ;
; All variables and constants required after initialization ;
; part one are defined here. ;
;-----------------------------------------------------------------------;
START EQU $ ;begin resident VDISK data & code
;DEVICE HEADER - must be at offset zero within device driver
DD -1 ;becomes pointer to next device header
DW 0800H ;attribute (IBM format block device)
;supports OPEN/CLOSE/RM calls
DW OFFSET STRATEGY ;pointer to device "strategy" routine
DW OFFSET IRPT ;pointer to device "interrupt handler"
DB 1 ;number of block devices
DB 7 DUP (?) ;7 byte filler (remainder of 8-byte name)
;END OF DEVICE HEADER
;This volume label is placed into the directory of the new VDISK
;This constant is also used to determine if a previous extended memory VDISK
;has been installed.
VOL_LABEL DB 'VDISK ' ;00-10 volume name (shows program level)
DB 28H ;11-11 attribute (volume label)
DT 0 ;12-21 reserved
DW 6000H ;22-23 time=12:00 noon
DW 0986H ;24-25 date=12/06/84
VOL_LABEL_LEN EQU $-VOL_LABEL ;length of volume label
;The following field, in the first extended memory VDISK device driver,
;is the 24-bit address of the first free byte of extended memory.
;This address is not in the common offset/segment format.
;The initial value, 10 0000H, is 1 megabyte.
AVAIL_LO DW 0 ;address of first free byte of
AVAIL_HI DB 10H ;extended memory
;The INT 19H vector is "stolen" by the first VDISK installed in extended memory.
;The original content of the interrupt vector is saved here.
INTV19 LABEL DWORD
INTV19O DW ? ;offset
INTV19S DW ? ;segment
PARAS_PER_SECTOR DW ? ;number of 16-byte paragraphs in one sector
START_BUFFER_PARA DW ? ;segment address of start of VDISK buffer
;for extended memory, this segment address
;is the end of the VDISK device driver.
EM_SW DB 0 ;non-zero if Extended Memory
EM_STAT DW 0 ;AX from last unsuccessful extended memory I/O
START_EM_LO DW ? ;24-bit address of start of VDISK buffer
START_EM_HI DB ? ;(extended memory only)
WPARA_SIZE DW PARA_SIZE ;number of bytes in one paragraph
MAX_CNT DW ? ;(0FFFFH/BPB_SSZ) truncated, the maximum
;number of sectors that can be transferred
;without worrying about 64KB wrap
SECT_LEFT DW ? ;sectors left to transfer
IO_SRCA LABEL DWORD ;offset/segment of source
IO_SRCO DW ? ;offset
IO_SRCS DW ? ;segment
IO_TGTA LABEL DWORD ;offset/segment of target
IO_TGTO DW ? ;offset
IO_TGTS DW ? ;segment
;-----------------------------------------------------------------------;
; BIOS Parameter Block (BPB) ;
;-----------------------------------------------------------------------;
;This is where the characteristics of the virtual disk are established.
;A copy of this block is moved into the boot record of the virtual disk.
;DEBUG can be used to read sector zero of the virtual disk to examine the
;boot record copy of this block.
BPB LABEL BYTE ;BIOS Parameter Block (BPB)
BPB_SSZ DW 0 ;number of bytes per disk sector
BPB_AUSZ DB 1 ;sectors per allocation unit
BPB_RES DW 1 ;number of reserved sectors (for boot record)
BPB_FATN DB 1 ;number of File Allocation Table (FAT) copies
BPB_DIRN DW 0 ;number of root directory entries
BPB_SECN DW 1 ;total number of sectors
;computed from buffer size and sector size
;(this includes reserved, FAT, directory,
;and data sectors)
BPB_MCB DB 0FEH ;media descriptor byte
BPB_FATSZ DW 1 ;number of sectors occupied by a single FAT
;computed from BPBSSZ and BPBSECN
BPB_LEN EQU $-BPB ;length of BIOS parameter block
BPB_PTR DW BPB ;BIOS Parameter Block pointer array (1 entry)
;-----------------------------------------------------------------------;
; Request Header (RH) address, saved here by "strategy" routine ;
;-----------------------------------------------------------------------;
RH_PTRA LABEL DWORD
RH_PTRO DW ? ;offset
RH_PTRS DW ? ;segment
;-----------------------------------------------------------------------;
; Global Descriptor Table (GDT), used for extended memory moves ;
;-----------------------------------------------------------------------;
;Access Rights Byte (93H) is
; P=1 (segment is mapped into physical memory)
; E=0 (data segment descriptor)
; D=0 (grow up segment, offsets must be <= limit)
; W=1 (data segment may be written into)
; DPL=0 (privilege level 0)
GDT LABEL BYTE ;begin global descriptor table
DESC <> ;dummy descriptor
DESC <> ;descriptor for GDT itself
SRC DESC <,,,93H,> ;source descriptor
TGT DESC <,,,93H,> ;target descriptor
DESC <> ;BIOS CS descriptor
DESC <> ;stack segment descriptor
SUBTTL INT 19H (boot) interrupt handler
PAGE
;-----------------------------------------------------------------------;
; INT 19H Interrupt Handler routine ;
;-----------------------------------------------------------------------;
;The INT 19H vector is altered by VDISK initialization to point to this
;routine within the first extended memory VDISK device driver.
;The vector points to the device driver so that subsequent VDISKs installed
;in extended memory can find the first one to determine what memory has
;already been allocated to VDISKs.
;This routine restores the original INT 19H vector's content, then jumps
;to the original routine.
;INT 19H, the "Boot" INT, is always altered when DOS is booted.
;This routine is entered with interrupts disabled.
VDISK_INT19 PROC ;INT 19H received
PUSH DS ;save registers we're going to alter
PUSH AX
XOR AX,AX
MOV DS,AX ;set DS = 0
ASSUME DS:INT_VEC
MOV AX,CS:INTV19O ;get offset of saved vector
MOV DS:BOOT_VECO,AX ;store offset in interrupt vector
MOV AX,CS:INTV19S ;get segment of saved vector
MOV DS:BOOT_VECS,AX ;store segment in interrupt vector
POP AX
POP DS
JMP CS:INTV19 ;go to original interrupt routine
VDISK_INT19 ENDP
ASSUME DS:NOTHING
SUBTTL Device Strategy & interrupt entry points
PAGE
;-----------------------------------------------------------------------;
; Device "strategy" entry point ;
; ;
; Retain the Request Header address for use by Interrupt routine ;
;-----------------------------------------------------------------------;
STRATEGY PROC FAR
MOV CS:RH_PTRO,BX ;offset
MOV CS:RH_PTRS,ES ;segment
RET
STRATEGY ENDP
;-----------------------------------------------------------------------;
; Table of command processing routine entry points ;
;-----------------------------------------------------------------------;
CMD_TABLE LABEL WORD
DW OFFSET INIT_P1 ; 0 - Initialization
DW OFFSET MEDIA_CHECK ; 1 - Media check
DW OFFSET BLD_BPB ; 2 - Build BPB
DW OFFSET INPUT_IOCTL ; 3 - IOCTL input
DW OFFSET INPUT ; 4 - Input
DW OFFSET INPUT_NOWAIT ; 5 - Non destructive input no wait
DW OFFSET INPUT_STATUS ; 6 - Input status
DW OFFSET INPUT_FLUSH ; 7 - Input flush
DW OFFSET OUTPUT ; 8 - Output
DW OFFSET OUTPUT_VERIFY ; 9 - Output with verify
DW OFFSET OUTPUT_STATUS ;10 - Output status
DW OFFSET OUTPUT_FLUSH ;11 - Output flush
DW OFFSET OUTPUT_IOCTL ;12 - IOCTL output
DW OFFSET DEVICE_OPEN ;13 - Device OPEN
DW OFFSET DEVICE_CLOSE ;14 - Device CLOSE
MAX_CMD EQU ($-CMD_TABLE)/2 ;highest valid command follows
DW OFFSET REMOVABLE_MEDIA ;15 - Removable media
;-----------------------------------------------------------------------;
; Device "interrupt" entry point ;
;-----------------------------------------------------------------------;
IRPT PROC FAR ;device interrupt entry point
PUSH DS ;save all registers modified
PUSH ES
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSH DI
PUSH SI
;BP isn't used, so it isn't saved
CLD ;all moves forward
LDS BX,CS:RH_PTRA ;get RH address passed to "strategy" into DS:BX
MOV AL,RH.RHC_CMD ;command code from Request Header
CBW ;zero AH (if AL > 7FH, next compare will
;catch that error)
CMP AL,MAX_CMD ;if command code is too high
JA IRPT_CMD_HIGH ;jump to error routine
MOV DI,OFFSET IRPT_CMD_EXIT ;return addr from command processor
PUSH DI ;push return address onto stack
;command routine issues "RET"
ADD AX,AX ;double command code for table offset
MOV DI,AX ;put into index register for JMP
XOR AX,AX ;initialize return to "no error"
;At entry to command processing routine:
; DS:BX = Request Header address
; CS = VDISK code segment address
; AX = 0
; top of stack is return address, IRPT_CMD_EXIT
JMP CS:CMD_TABLE[DI] ;call routine to handle the command
IRPT_CMD_ERROR: ;CALLed for unsupported character mode commands
INPUT_IOCTL: ;IOCTL input
INPUT_NOWAIT: ;Non-destructive input no wait
INPUT_STATUS: ;Input status
INPUT_FLUSH: ;Input flush
OUTPUT_IOCTL: ;IOCTL output
OUTPUT_STATUS: ;Output status
OUTPUT_FLUSH: ;Output flush
POP AX ;pop return address off stack
IRPT_CMD_HIGH: ;JMPed to if RHC_CMD > MAX_CMD
MOV AX,STAT_CMDERR ;"invalid command" and error
IRPT_CMD_EXIT: ;return from command routine
;AX = value to OR into status word
LDS BX,CS:RH_PTRA ;restore DS:BX as Request Header pointer
OR AH,STAT_DONE ;add "done" bit to status word
MOV RH.RHC_STA,AX ;store status into request header
POP SI ;restore registers
POP DI
POP DX
POP CX
POP BX
POP AX
POP ES
POP DS
RET
IRPT ENDP
SUBTTL Command Processing routines
PAGE
;-----------------------------------------------------------------------;
; Command Code 1 - Media Check ;
; At entry, DS:BX point to request header, AX = 0 ;
;-----------------------------------------------------------------------;
MEDIA_CHECK PROC
MOV RH.RH1_RET,1 ;indicate media not changed
RET ;AX = zero, no error
MEDIA_CHECK ENDP
;-----------------------------------------------------------------------;
; Command Code 2 - Build BPB ;
; At entry, DS:BX point to request header, AX = 0 ;
;-----------------------------------------------------------------------;
BLD_BPB PROC
MOV RH.RH2_BPBO,OFFSET BPB ;return pointer to our BPB
MOV RH.RH2_BPBS,CS
RET ;AX = zero, no error
BLD_BPB ENDP
;-----------------------------------------------------------------------;
; Command Code 13 - Device Open ;
; Command Code 14 - Device Close ;
; Command Code 15 - Removable media ;
; At entry, DS:BX point to request header, AX = 0 ;
;-----------------------------------------------------------------------;
REMOVABLE_MEDIA PROC
MOV AX,STAT_BUSY ;set status bit 9 (busy)
;indicating non-removable media
DEVICE_OPEN: ;NOP for device open
DEVICE_CLOSE: ;NOP for device close
RET
REMOVABLE_MEDIA ENDP ;fall thru to return
;-----------------------------------------------------------------------;
; Command Code 4 - Input ;
; Command Code 8 - Output ;
; Command Code 9 - Output with verify ;
; At entry, DS:BX point to request header, AX = 0 ;
;-----------------------------------------------------------------------;
INOUT PROC
INPUT:
OUTPUT:
OUTPUT_VERIFY:
;Make sure I/O is entirely within the VDISK sector boundaries
MOV CX,CS:BPB_SECN ;get total sector count
MOV AX,RH.RH4_SSN ;starting sector number
CMP AX,CX ;can't exceed total count
JA INOUT_E1 ;jump if start > total
ADD AX,RH.RH4_CNT ;start + sector count
CMP AX,CX ;can't exceed total count
JNA INOUT_A ;jump if start + count <= total
INOUT_E1: ;I/O not within VDISK sector boundaries
MOV RH.RH4_CNT,0 ;set sectors transferred to zero
MOV AX,STAT_SNF ;indicate 'Sector not found' error
RET ;return with error status in AX
INOUT_A: ;I/O within VDISK bounds
MOV AX,RH.RH4_CNT ;get sector count
MOV CS:SECT_LEFT,AX ;save as sectors left to process
CMP CS:EM_SW,0 ;extended memory mode?
JNE INOUT_EM ;jump to extended memory I/O code
;Compute offset and segment of VDISK buffer for starting segment in CX:SI
MOV AX,RH.RH4_SSN ;starting sector number
MUL CS:PARAS_PER_SECTOR ;* length of one sector in paragraphs
ADD AX,CS:START_BUFFER_PARA ;+ segment of VDISK buffer sector 0
MOV CX,AX ;segment address to CX
XOR SI,SI ;offset is zero
;Compute address of caller's Data Transfer Addr in DX:AX with smallest offset,
;so that there is no possibility of overflowing a 64KB boundary moving MAX_CNT
;sectors.
MOV AX,PARA_SIZE ;16
MUL RH.RH4_DTAS ;* segment of caller's DTA in DX,AX
ADD AX,RH.RH4_DTAO ;+ offset of caller's DTA
ADC DL,0 ;carry in from addition
DIV CS:WPARA_SIZE ;AX is segment of caller's DTA
;DX is smallest offset possible
;AX:DX = DTA address
;AX:DX is caller's DTA segment:offset, CX:SI is VDISK buffer segment:offset
;If this is an OUTPUT request, exchange the source and target addresses
CMP RH.RHC_CMD,CMD_INPUT ;INPUT operation?
JE INOUT_B ;jump if INPUT operation
XCHG AX,CX ;swap source and target segment
XCHG DX,SI ;swap source and target offset
INOUT_B: ;CX:SI is source, AX:DX is target
MOV CS:IO_SRCS,CX ;save source segment
MOV CS:IO_SRCO,SI ;save source offset
MOV CS:IO_TGTS,AX ;save target segment
MOV CS:IO_TGTO,DX ;save target offset
JMP SHORT INOUT_E ;AX := SECT_LEFT, test for zero
INOUT_C: ;SECT_LEFT in AX, non-zero
; Compute number of sectors to transfer in a single move,
; AX = minimum of (SECT_LEFT, MAX_CNT)
; MAX_CNT is the maximum number of sectors that can be moved without
; spanning a 64KB boundary (0FFFFH / Sector size, remainder truncated)
MOV CX,CS:MAX_CNT ;MAX sectors with one move
CMP AX,CX ;if SECT_LEFT cannot span 64KB boundary
JBE INOUT_D ;then move SECT_LEFT sectors
MOV AX,CX ;else move MAX_CNT sectors
INOUT_D:
SUB CS:SECT_LEFT,AX ;reduce number of sectors left to move
;Move AX sectors from source to target
MUL CS:BPB_SSZ ;sectors * sector size = byte count
;(cannot overflow into DX)
SHR AX,1 ;/2 = word count
MOV CX,AX ;word count to CX for REP MOVSW
LDS SI,CS:IO_SRCA ;source segment/offset to DS:SI
LES DI,CS:IO_TGTA ;target segment/offset to ES:DI
REP MOVSW ;move MOV_CNT sectors
;Update source and target paragraph addresses
;AX has number of words moved
SHR AX,1 ;words moved / 8 = paragraphs moved
SHR AX,1
SHR AX,1
ADD CS:IO_SRCS,AX ;add paragraphs moved to source segment
ADD CS:IO_TGTS,AX ;add paragraphs moved to target segment
;Determine if more sectors need to be transferred
INOUT_E: ;do while SECT_LEFT <> zero
MOV AX,CS:SECT_LEFT ;get sectors left to transfer
OR AX,AX ;set flags
JNZ INOUT_C ;go back to transfer some sectors
RET ;AX = zero, all sectors transferred
SUBTTL Extended Memory I/O routine
PAGE
;-----------------------------------------------------------------------;
; Extended Memory I/O routine ;
;-----------------------------------------------------------------------;
INOUT_EM: ;Extended memory I/O routine
;change to larger stack
MOV SI,SS ;save old SS in SI
MOV DX,SP ;save old SP in DX
CLI ;disable interrupts
MOV AX,CS
MOV SS,AX ;set SS = CS
MOV SP,OFFSET EM_STACK ;point to new stack
STI ;enable interrupts
PUSH SI ;save old SS at top of new stack
PUSH DX ;save old SP on new stack
MOV SI,RH.RH4_DTAO ;caller's DTA offset
;Compute 24-bit address of VDISK sector in CX (hi) and SI (low)
MOV AX,RH.RH4_SSN ;starting sector number
MUL CS:BPB_SSZ ;* sector size = offset within buffer
ADD AX,CS:START_EM_LO ;+ base address of this VDISK buffer
ADC DL,CS:START_EM_HI
MOV CX,DX ;save high byte
MOV SI,AX ;save low word
;Compute 24-bit address of caller's DTA in DX (hi) and AX (low)
MOV AX,PARA_SIZE ;16
MUL RH.RH4_DTAS ;* segment of caller's DTA
ADD AX,RH.RH4_DTAO ;+ offset of caller's DTA
ADC DL,0 ;carry in from addition
;Caller's DTA address is in CX,SI, VDISK buffer address is in DX,AX.
;If this is an OUTPUT request, exchange the source and target addresses
CMP RH.RHC_CMD,CMD_INPUT ;INPUT operation?
JE INOUT_EM_B ;jump if INPUT operation
XCHG DX,CX ;swap source and target high byte
XCHG AX,SI ;swap source and target low word
INOUT_EM_B: ;CX,SI is source, DX,AX is target
MOV SRC.DESC_BASEL,SI ;low 16 bits of source address
MOV SRC.DESC_BASEH,CL ;high 8 bits of source address
MOV TGT.DESC_BASEL,AX ;low 16 bits of target address
MOV TGT.DESC_BASEH,DL ;high 8 bits of target address
JMP SHORT INOUT_EM_E ;AX := SECT_LEFT, test for zero
INOUT_EM_C: ;SECT_LEFT in AX, non-zero
; Compute number of sectors to transfer in a single move,
; AX = minimum of (SECT_LEFT, MAX_CNT)
; MAX_CNT is the maximum number of sectors that can be moved without
; spanning a 64KB boundary (0FFFFH / Sector size, remainder truncated)
MOV CX,CS:MAX_CNT ;MAX sectors with one move
CMP AX,CX ;if SECT_LEFT cannot span 64KB boundary
JBE INOUT_EM_D ;then move SECT_LEFT sectors
MOV AX,CX ;else move MAX_CNT sectors
INOUT_EM_D:
SUB CS:SECT_LEFT,AX ;reduce number of sectors left to move
;Move AX sectors from source to target
MUL CS:BPB_SSZ ;sectors * sector size = byte count
;(cannot overflow into DX)
MOV TGT.DESC_LMT,AX ;store segment limit (byte count)
MOV SRC.DESC_LMT,AX
PUSH AX ;preserve byte count on stack
SHR AX,1 ;/2 = word count
MOV CX,AX ;word count to CX
PUSH CS
POP ES ;set ES = CS
MOV SI,OFFSET GDT ;ES:SI point to GDT
MOV AH,EM_BLKMOVE ;function is block move
INT EM_INT ;move an even number of words
POP CX ;get byte count back from stack
OR AH,AH ;get error code
JNZ INOUT_EM_XE ;jump if I/O error encountered
;Update source and target addresses
ADD SRC.DESC_BASEL,CX ;add bytes moved to source
ADC SRC.DESC_BASEH,0 ;pick up any carry
ADD TGT.DESC_BASEL,CX ;add bytes moved to target
ADC TGT.DESC_BASEH,0 ;pick up any carry
;Determine if more sectors need to be transferred
INOUT_EM_E: ;do while SECT_LEFT <> zero
MOV AX,CS:SECT_LEFT ;get sectors left to transfer
OR AX,AX ;set flags
JNZ INOUT_EM_C ;go back to transfer some sectors
INOUT_EM_X2: ;revert to original stack
POP DI ;get old SP
POP SI ;get old SS
CLI ;disable interrupts
MOV SS,SI ;restore old SS
MOV SP,DI ;restore old SP
STI ;enable interrupts
RET ;return to IRPT_EXIT
INOUT_EM_XE: ;some error with INT 15H
MOV CS:EM_STAT,AX ;save error status for debugging
MOV RH.RH4_CNT,0 ;indicate no sectors transferred
MOV AX,STAT_CRC ;indicate CRC error
JMP INOUT_EM_X2 ;fix stack and exit
INOUT ENDP
DW 40 DUP (?) ;stack for extended memory I/O
EM_STACK LABEL WORD
SUBTTL Boot Record
PAGE
;-----------------------------------------------------------------------;
; Adjust the assembly-time instruction counter to a paragraph ;
; boundary ;
;-----------------------------------------------------------------------;
IF ($-START) MOD 16
ORG ($-START) + 16 - (($-START) MOD 16)
ENDIF
VDISK EQU $ ;start of virtual disk buffer
VDISKP EQU ($-START) / PARA_SIZE ;length of program in paragraphs
;-----------------------------------------------------------------------;
; If this VDISK is in extended memory, this address is passed ;
; back to DOS as the end address that is to remain resident. ;
; ;
; It this VDISK is not in extended memory, the VDISK buffer ;
; begins at this address, and the address passed back to DOS ;
; as the end address that is to remain resident is this address ;
; plus the length of the VDISK buffer. ;
;-----------------------------------------------------------------------;
BOOT_RECORD LABEL BYTE ;Format of Boot Record documented in
;DOS Technical Reference Manual
DB 0,0,0 ;3-byte jump to boot code (not bootable)
DB 'VDISK ' ;8-byte vendor identification
BOOT_BPB LABEL BYTE ;boot record copy of BIOS parameter block
DW ? ;number of bytes per disk sector
DB ? ;sectors per allocation unit
DW ? ;number of reserved sectors (for boot record)
DB ? ;number of File Allocation Table (FAT) copies
DW ? ;number of root directory entries
DW ? ;total number of sectors
DB ? ;media descriptor byte
DW ? ;number of sectors occupied by a single FAT
;end of boot record BIOS Parameter block
;The following three words mean nothing to VDISK, they are placed here
;to conform to the DOS standard for boot records.
DW 8 ;sectors per track
DW 1 ;number of heads
DW 0 ;number of hidden sectors
;The following word is the 16-bit kilobyte address of the first byte in
;extended memory that is not occupied by a VDISK buffer
;It is placed into this location so that other users of extended memory
;may find where all the VDISKs end.
;This field may be accessed by moving the boot record of the First extended
;memory VDISK from absolute location 10 0000H. Before assuming that the
;value below is valid, the vendor ID (constant VDISK) should be verified
;to make sure that SOME VDISK has been installed.
;For example, if two VDISKs are installed, one 320KB and one 64KB, the
;address calculations are as follows:
;Extended memory start address = 100000H (1024KB)
;Start addr of 1st VDISK buffer = 100000H (1024KB)
;Length of 1st VDISK buffer = 050000H ( 320KB)
;End addr of 1st VDISK buffer = 14FFFFH
;Start addr of 2nd VDISK buffer = 150000H (1344KB)
;Length of 2nd VDISK buffer = 010000H ( 64KB)
;End addr of 2nd VDISK buffer = 15FFFFH
;First byte after all VDISKs = 160000H (1408KB)
;Divide by 1024 = 0580H (1408D)
;Content of BOOT_EM = 0580H
BOOT_EM_OFF EQU $-BOOT_RECORD ;offset from 10 0000H of the following word
BOOT_EM DW 1024 ;KB addr of first free byte of extended memory
;-----------------------------------------------------------------------;
; Part 2 of Initialization (executed last) ;
;-----------------------------------------------------------------------;
;Initialization is divided into two parts.
;INIT_P1 is overlaid by the virtual disk buffer
;INIT_P1 is executed first, then jumps to INIT_P2. INIT_P2 returns to caller.
;Exercise caution if extending the initialization part 2 code.
;It overlays the area immediately following the boot sector.
;If this section of code must be expanded, make sure it fits into the minimum
;sector size of 128 bytes.
;Label TEST_LENGTH must equate to a non-negative value (TEST_LENGTH >= 0).
;If this code it must be extended beyond the 128 byte length of the boot sector,
;move all of INIT_P2 before label VDISK.
;Registers at entry to INIT_P2 (set up at end of INIT_P1):
; BL = media control byte from BPB (for FAT)
; CX = number of FAT copies
; DX = number of bytes in one FAT - 3
; SI = OFFSET of Volume Label field
; ES:DI = VDISK buffer address of first FAT sector
; CS = DS = VDISK code segment
INIT_P2 PROC ;second part of initialization
ASSUME DS:CSEG ;DS set in INIT_P1
;Initialize File Allocation Table(s) (FATs)
INIT_P2_FAT: ;set up one FAT, sector number in AX
PUSH CX ;save loop counter on stack
MOV AL,BL ;media control byte
STOSB ;store media control byte, increment DI
MOV AX,0FFFFH ;bytes 2 and 3 of FAT are 0FFH
STOSW
MOV CX,DX ;FAT size in bytes - 3
XOR AX,AX ;value to store in remainder of FAT
REP STOSB ;clear remainder of FAT
POP CX ;get loop counter off stack
LOOP INIT_P2_FAT ;loop for all copies of the FAT
;Put the volume label in the first directory entry
MOV CX,VOL_LABEL_LEN ;length of volume directory entry
REP MOVSB ;move volume id to directory
;Zero the remainder of the directory
MOV AX,DIR_ENTRY_SIZE ;length of 1 directory entry
MUL BPB_DIRN ;* number entries = bytes of directory
SUB AX,VOL_LABEL_LEN ;less length of volume label
MOV CX,AX ;length of rest of directory
XOR AX,AX
REP STOSB ;clear directory to nulls
RET ;return with AX=0
INIT_P2 ENDP
PATCH_AREA DB 5 DUP ('PATCH AREA ')
TEST_LENGTH EQU 128-($-VDISK) ;if negative, boot record has too much
;data area, move some fields below VDISK
;-----------------------------------------------------------------------;
; All fields that must remain resident after device driver ;
; initialization must be defined before this point. ;
;-----------------------------------------------------------------------;
DB 'IBM DOS Version 4.00, Virtual Disk Device Driver'
DB '(C)Copyright IBM Corp 1984,1985,1988'
DB 'Licensed Material - Program Property of IBM. '
DB 'Author: Dick Dievendorff, '
DB 'Rod Springhetti, & '
DB 'David M. Sewell'
MAXSEC_TRF DW 0 ;maximum number of sectors to transfer when
;in extended memory
BUFF_SIZE DW 0 ;desired VDISK buffer size in kilobytes
MIN_MEMORY_LEFT DW 64 ;minimum amount of system memory (kilobytes)
;that must remain after VDISK is installed
FIRST_EM_SW DB ? ;0FFH if this is the first device driver
;to be installed in extended memory
;00H if another VDISK extended memory driver
;has been installed
FIRST_VDISK DW ? ;segment address of 1st VDISK device driver
PARA_PER_KB DW 1024/PARA_SIZE ;paragraphs in one kilobyte
C1024 DW 1024 ;bytes in one kilobyte
DIRE_SIZE DW DIR_ENTRY_SIZE ;bytes in one directory entry
DIR_SECTORS DW ? ;number of sectors of directory
ERR_FLAG DB 0 ;error indicators to condition messages
ERR_BSIZE EQU 80H ;buffer size adjusted
ERR_SSZ EQU 40H ;sector size adjusted
ERR_DIRN EQU 20H ;number of directory entries adjusted
ERR_PASS EQU 10H ;some adjustment made that requires
;recomputation of values previously computed
ERR_SSZB EQU ERR_SSZ+ERR_PASS ;sector size altered this pass
ERR_SYSSZ EQU 08H ;system storage too small for VDISK
ERR_SWTCH EQU 04H ;invalid switch character
ERR_EXTSW EQU 02H ;extender card switches don't match memory size
ERR_ESIZE EQU 01H ;Transfer size adjusted
; additional errors added - kwc
major_version equ 4 ;Major DOS version
minor_version equ 00 ;Minor DOS Version
expected_version equ (MINOR_VERSION SHL 8)+MAJOR_VERSION
err_flag2 db 0
err_baddos equ 01h ; Invalid DOS Version
SUBTTL Initialization, Part one
PAGE
;-----------------------------------------------------------------------;
; Command Code 0 - Initialization ;
; At entry, DS:BX point to request header, AX = 0 ;
;-----------------------------------------------------------------------;
;Initialization is divided into two parts.
;This part, executed first, is later overlaid by the VDISK buffer.
INIT_P1 PROC ;first part of initialization
MOV DX,SS ;save stack segment register
MOV CX,SP ;save stack pointer register
CLI ;inhibit interrupts while changing SS:SP
MOV AX,CS ;move CS to SS through AX
MOV SS,AX
MOV SP,OFFSET MSGEND ;end of VDISKMSG
ADD SP,STACK_SIZE ;+ length of our stack
STI ;allow interrupts
PUSH DX ;save old SS register on new stack
PUSH CX ;save old SP register on new stack
push bx ;secure registers before DOS int
push cx ;secure registers before DOS int
; add version check - kwc
mov ah,030h
int 21h
pop cx ;restore pointer values
pop bx ;restore pointer values
cmp ax,expected_version
je okdos
or cs:err_flag2,err_baddos
okdos:
CALL GET_PARMS ;get parameters from CONFIG.SYS line
PUSH CS
POP DS ;set DS = CS
ASSUME DS:CSEG
CALL APPLY_DEFAULTS ;supply any values not specified
CALL DETERMINE_START ;compute start address of VDISK buffer
CALL VALIDATE ;validate parameters
CALL COPY_BPB ;Copy BIOS Parameter Block to boot record
CALL VERIFY_EXTENDER ;Verify that extender card switches are right
TEST ERR_FLAG,ERR_EXTSW ;are switches wrong?
JNZ INIT_P1_A ;if so, exit with messages
test CS:err_flag2,err_baddos
jnz init_p1_a
CMP EM_SW,0 ;extended memory requested?
JE INIT_P1_A ;jump if not
TEST ERR_FLAG,ERR_SYSSZ ;is system too small for VDISK?
JNZ INIT_P1_A ;if so, don't do extended memory init
CALL UPDATE_AVAIL ;update AVAIL_HI and AVAIL_LO to reflect
;addition of extended memory VDISK
CALL FORMAT_VDISK ;construct a boot record, FATs and
;directory in storage immediately
;following this device driver
CALL MOVE_VDISK ;move formatted boot record, FATs,
;and directory to extended memory
CALL UPDATE_BOOT ;place the end address of ALL VDISKs
;in the boot record of the first VDISK
CMP FIRST_EM_SW,0 ;is this the first extended memory VDISK?
JE INIT_P1_A ;no, exit
CALL STEAL_INT19 ;point INT 19H to this VDISK
INIT_P1_A:
CALL FILL_RH ;fill in INIT request header
CALL WRITE_MESSAGES ;display all messages
POP CX ;get old SP from stack
POP DX ;get old SS from stack
CLI ;disable interrupts while changing SS:SP
MOV SS,DX ;restore stack segment register
MOV SP,CX ;restore stack pointer register
STI ;enable interrupts
;-----------------------------------------------------------------------;
; INIT_P2 must be short enough to fit into the boot sector ;
; (minimum size of boot sector is 128 bytes), so we set up ;
; as many pointers as we can to help keep INIT_P2 short. ;
; ;
; ES:DI = storage address of first FAT sector ;
; BL = media control byte ;
; CX = number of FAT copies ;
; DX = number of bytes in one FAT, less 3 ;
; SI = offset of VOL label field ;
;-----------------------------------------------------------------------;
MOV ES,START_BUFFER_PARA ;start paragraph of VDISK buffer
MOV AX,BPB_RES ;number of reserved sectors
MUL BPB_SSZ ;* sector size
MOV DI,AX ;ES:DI point to FAT start
MOV BL,BPB_MCB ;media control byte
MOV CL,BPB_FATN ;number of FAT copies
XOR CH,CH
MOV AX,BPB_FATSZ ;FAT size in sectors
MUL BPB_SSZ ;* sector size = total FAT bytes
SUB AX,3 ;-3 (FEFFFF stored by code)
MOV DX,AX
MOV SI,OFFSET VOL_LABEL ;point to VOL label directory entry
JMP INIT_P2 ;jump to second part of initialization
;this is redundant if the VDISK is in
;extended memory, but is executed anyway
SUBTTL GET_PARMS Parameter Line Scan
PAGE
;-----------------------------------------------------------------------;
;GET_PARMS gets the parameters from the CONFIG.SYS statement ;
; ;
;Register usage: ;
; DS:SI indexes parameter string ;
; AL contains character from parameter string ;
; CX value from GET_NUMBER ;
;-----------------------------------------------------------------------;
ASSUME DS:NOTHING ;DS:BX point to Request Header
GET_PARMS PROC ;get parameters from CONFIG.SYS line
PUSH DS ;save DS
LDS SI,RH.RH0_BPBA ;DS:SI point to all after DEVICE=
;in CONFIG.SYS line
XOR AL,AL ;not at end of line
;Skip until first delimiter is found. There may be digits in the path string.
;DS:SI points to \pathstring\VDISK.SYS nn nn nn
;The character following VDISK.SYS may have been changed to a null (00H).
;All letters have been changed to uppercase.
GET_PARMS_A: ;skip to DOS delimiter character
CALL GET_PCHAR ;get parameter character into AL
JZ GET_PARMS_X ;get out if end of line encountered
OR AL,AL ;test for null
JZ GET_PARMS_C ;
CMP AL,' '
JE GET_PARMS_C ;
CMP AL,','
JE GET_PARMS_C ;
CMP AL,';'
JE GET_PARMS_C ;
CMP AL,'+'
JE GET_PARMS_C ;
CMP AL,'='
JE GET_PARMS_C ;
CMP AL,TAB
JNE GET_PARMS_A ;skip until delimiter or CR
GET_PARMS_C:
PUSH SI ;save to rescan
MOV CS:EM_SW,0 ;indicate no /E found
JMP GET_SLASH ;see if current character is an slash
GET_PARMS_D: ;scan for /
CALL GET_PCHAR
JZ GET_PARMS_B ;exit if end of line
GET_SLASH: ;check for slash
CMP AL,'/' ;found slash?
JNE GET_PARMS_D ;no, continue scan
CALL GET_PCHAR ;get char following slash
CMP AL,'E' ;don't have to test for lower case E,
;letters have been changed to upper case
JNE GET_PARMS_E ;not 'E'
MOV CS:EM_SW,AL ;indicate /E found
CALL GET_PCHAR ;get char following E
CMP AL,':' ;is it a delimeter ?
JNE GET_PARMS_D ;not a ':'
CALL GET_MAXSIZE ;get maximum sector size
JMP GET_PARMS_D ;continue forward scan
GET_PARMS_E: ;/ found, not 'E'
OR CS:ERR_FLAG,ERR_SWTCH ;indicate invalid switch character
JMP GET_PARMS_D ;continue scan
GET_PARMS_B: ;now pointing to first delimiter
POP SI ;get pointer, used to rescan for /E
XOR AL,AL ;not at EOL now
CALL GET_PCHAR ;get first character
CALL SKIP_TO_DIGIT ;skip to first digit
JZ GET_PARMS_X ;found EOL, no digits remain
CALL GET_NUMBER ;extract digits, convert to binary
MOV CS:BUFF_SIZE,CX ;store buffer size
CALL SKIP_TO_DIGIT ;skip to next digit
JZ GET_PARMS_X ;found EOL, no digits remain
CALL GET_NUMBER ;extract digits, convert to binary
MOV CS:BPB_SSZ,CX ;store sector size
CALL SKIP_TO_DIGIT ;skip to next digit
JZ GET_PARMS_X ;found EOL, no digits remain
CALL GET_NUMBER ;extract digits, convert to binary
MOV CS:BPB_DIRN,CX ;store number of directory entries
GET_PARMS_X: ;premature end of line
POP DS ;restore DS
RET
GET_MAXSIZE PROC ;get maximum sector size
CALL GET_PCHAR ;get next character
CALL CHECK_NUM ;is it a number ?
JZ GET_NEXTNUM ;yes, go get next number
OR CS:ERR_FLAG,ERR_ESIZE ;indicate invalid sector size
RET ;
GET_NEXTNUM: ;get next number
CALL GET_NUMBER ;extract digits and convert to binary
MOV CS:MAXSEC_TRF,CX ;save maximum sector size to transfer
RET
GET_MAXSIZE ENDP
GET_PCHAR PROC ;internal proc to get next character into AL
CMP AL,CR ;carriage return already encountered?
JE GET_PCHAR_X ;don't read past end of line
CMP AL,LF ;line feed already encountered?
JE GET_PCHAR_X ;don't read past end of line
LODSB ;get char from DS:SI, increment SI
CMP AL,CR ;is the char a carriage return?
JE GET_PCHAR_X ;yes, set Z flag at end of line
CMP AL,LF ;no, is it a line feed?
GET_PCHAR_X: ;attempted read past end of line
RET
GET_PCHAR ENDP ;returns char in AL
CHECK_NUM PROC ;check AL for ASCII digit
CMP AL,'0' ;< '0'?
JB CHECK_NUM_X ;exit if it is
CMP AL,'9' ;> '9'?
JA CHECK_NUM_X ;exit if it is
CMP AL,AL ;set Z flag to indicate numeric
CHECK_NUM_X:
RET ;Z set if numeric, NZ if not numeric
CHECK_NUM ENDP
SKIP_TO_DIGIT PROC ;skip to first numeric character
CALL CHECK_NUM ;is current char a digit?
JZ SKIP_TO_DIGIT_X ;if so, skip is complete
CALL GET_PCHAR ;get next character from line
JNZ SKIP_TO_DIGIT ;loop until first digit or CR or LF
RET ;character is CR or LF
SKIP_TO_DIGIT_X:
CMP AL,0 ;digit found, force NZ
RET
SKIP_TO_DIGIT ENDP
C10 DW 10
GN_ERR DB ? ;zero if no overflow in accumulation
GET_NUMBER PROC ;convert string of digits to binary value
XOR CX,CX ;accumulate number in CX
MOV CS:GN_ERR,CL ;no overflow yet
GET_NUMBER_A: ;accumulate next digit
SUB AL,'0' ;convert ASCII to binary
CBW ;clear AH
XCHG AX,CX ;previous accumulation in AX, new digit in CL
MUL CS:C10 ;DX:AX := AX*10
OR CS:GN_ERR,DL ;set GN_ERR <> 0 if overflow
ADD AX,CX ;add new digit from
XCHG AX,CX ;number now in CX
DEC SI ;back up to prior entry
MOV AL,' ' ;blank out prior entry
MOV [SI],AL ;
INC SI ;set to current entry
CALL GET_PCHAR ;get next character
CALL CHECK_NUM ;see if it was numeric
JZ GET_NUMBER_A ;continue accumulating
CMP CS:GN_ERR,0 ;did we overflow?
JE GET_NUMBER_B ;if not, we're done
XOR CX,CX ;return zero (always invalid) if overflow
GET_NUMBER_B:
RET ;number in CX, next char in AL
GET_NUMBER ENDP
GET_PARMS ENDP
SUBTTL APPLY_DEFAULTS
PAGE
;-----------------------------------------------------------------------;
; APPLY_DEFAULTS supplies any parameter values that the user ;
; failed to specify ;
;-----------------------------------------------------------------------;
ASSUME DS:CSEG
APPLY_DEFAULTS PROC
XOR AX,AX
CMP BUFF_SIZE,AX ;is buffer size zero?
JNE APPLY_DEFAULTS_A ;no, user specified something
MOV BUFF_SIZE,DFLT_BSIZE ;supply default buffer size
OR ERR_FLAG,ERR_BSIZE ;indicate buffersize adjusted
APPLY_DEFAULTS_A:
CMP BPB_SSZ,AX ;is sector size zero?
JNE APPLY_DEFAULTS_B ;no, user specified something
MOV BPB_SSZ,DFLT_SSZ ;supply default sector size
OR ERR_FLAG,ERR_SSZ ;indicate sector size adjusted
APPLY_DEFAULTS_B:
CMP BPB_DIRN,AX ;are directory entries zero?
JNE APPLY_DEFAULTS_C ;no, user specified something
MOV BPB_DIRN,DFLT_DIRN ;supply default directory entries
OR ERR_FLAG,ERR_DIRN ;indicate directory entries adjusted
APPLY_DEFAULTS_C: ;
CMP EM_SW,0 ;extended memory ?
JE APPLY_DEFAULTS_D ;no, jump around
CMP MAXSEC_TRF,AX ;is maximum sectors zero?
JNE APPLY_DEFAULTS_D ;no, user specified something
MOV MAXSEC_TRF,DFLT_ESS ;supply default maximum number of
;sector to transfer
OR ERR_FLAG,ERR_ESIZE ;indicate transfer size adjusted
APPLY_DEFAULTS_D:
RET
APPLY_DEFAULTS ENDP
SUBTTL DETERMINE_START address of VDISK buffer
PAGE
;-----------------------------------------------------------------------;
; DETERMINE_START figures out the starting address of the VDISK ;
; buffer ;
;-----------------------------------------------------------------------;
ASSUME DS:CSEG
DETERMINE_START PROC
;If extended memory is NOT being used, the VDISK buffer immediately
;follows the resident code.
;If extended memory IS being used, START_BUFFER_PARA becomes the
;end of device driver address passed back to DOS.
MOV AX,CS ;start para of VDISK code
ADD AX,VDISKP ;+ length of resident code
MOV START_BUFFER_PARA,AX ;save as buffer start para
CMP EM_SW,0 ;is extended memory requested?
JE DETERMINE_START_X ;if not, we're done here
;If this is the first extended memory VDISK device driver to be installed,
;the start address for I/O is 1 megabyte.
;If one or more extended memory VDISK device drivers have been installed,
;the start address for I/O for THIS device driver is acquired from the
;fields AVAIL_LO and AVAIL_HI in the FIRST VDISK device driver.
;The first extended memory VDISK device driver is located by INT 19H's vector.
MOV FIRST_EM_SW,0FFH ;indicate first VDISK device driver
MOV FIRST_VDISK,CS ;segment addr of first VDISK
PUSH DS ;preserve DS
XOR AX,AX
MOV DS,AX ;set DS = 0
ASSUME DS:INT_VEC
MOV AX,DS:BOOT_VECS ;get segment addr of INT 19H routine
MOV DS,AX ;to DS
ASSUME DS:NOTHING
PUSH CS
POP ES ;set ES = CS
MOV SI,OFFSET VOL_LABEL ;DS:SI point to VOL label field
;in first VDISK (if present)
MOV DI,SI ;ES:DI point to VOL label field of
;this VDISK
MOV CX,VOL_LABEL_LEN ;length of volume label
REP CMPSB ;does INT 19H vector point to a VDISK
;device driver?
JNE DETERMINE_START_A ;jump if this is the first VDISK
;Another extended memory VDISK device driver has been installed.
;Its AVAIL_LO and AVAIL_HI are the first free byte of extended memory.
MOV CS:FIRST_EM_SW,0 ;indicate not first device driver
MOV CS:FIRST_VDISK,DS ;save pointer to 1st device driver
;Copy AVAIL_LO and AVAIL_HI from first VDISK to this VDISK
MOV SI,OFFSET AVAIL_LO ;DS:SI point to AVAIL_LO in first VDISK
MOV DI,SI ;ES:DI point to AVAIL_LO in this VDISK
MOVSW ;copy AVAIL_LO from first to this VDISK
MOVSB ;copy AVAIL_HI
DETERMINE_START_A: ;copy AVAIL_LO and AVAIL_HI to START_EM
POP DS ;set DS = CS
MOV SI,OFFSET AVAIL_LO ;source offset
MOV DI,OFFSET START_EM_LO ;destination offset
MOVSW ;move AVAIL_LO to START_EM_LO
MOVSB ;move AVAIL_HI to START_EM_HI
DETERMINE_START_X:
RET
DETERMINE_START ENDP
SUBTTL VALIDATE parameters
PAGE
;-----------------------------------------------------------------------;
; VALIDATE adjusts parameters as necessary ;
;-----------------------------------------------------------------------;
VAL_SSZ_TBL LABEL WORD ;table of valid sector sizes
VAL_SSZ_S DW 128 ;smallest valid sector size
DW 256
VAL_SSZ_L DW 512 ;largest valid sector size
VAL_SSZ_N EQU ($-VAL_SSZ_TBL)/2 ;number of table entries
ASSUME DS:CSEG
VALIDATE PROC ;validate parameters
MOV BPB_AUSZ,1 ;initial allocation unit is 1 sector
CALL VAL_BSIZE ;validate buffer size
CALL VAL_SSZ ;validate (adjust if necessary) BPB_SSZ
VALIDATE_A:
AND ERR_FLAG,255-ERR_PASS ;indicate nothing changed this pass
MOV AX,BPB_SSZ ;sector size
CWD ;clear DX for division
DIV WPARA_SIZE ;sector size/para size
MOV PARAS_PER_SECTOR,AX ;number of paragraphs/sector
MOV AX,BUFF_SIZE ;requested buffersize in KB
MUL C1024 ;DX:AX = buffer size in bytes
DIV BPB_SSZ ;/sector size = # sectors
MOV BPB_SECN,AX ;store number of sectors
CALL VAL_DIRN ;validate number of directory entries
TEST ERR_FLAG,ERR_PASS ;may have reset sector size
JNZ VALIDATE_A ;recompute directory & FAT sizes
CALL VAL_FAT ;compute FAT entries, validity test
TEST ERR_FLAG,ERR_PASS ;if cluster size altered this pass
JNZ VALIDATE_A ;recompute directory & FAT sizes
;Make certain buffer size is large enough to contain:
; boot sector(s)
; FAT sector(s)
; directory sector(s)
; at least 1 data cluster
MOV AL,BPB_FATN ;number of FAT copies
CBW ;clear AH
MUL BPB_FATSZ ;* sectors for 1 FAT = FAT sectors
ADD AX,BPB_RES ;+ reserved sectors
ADD AX,DIR_SECTORS ;+ directory sectors
MOV CL,BPB_AUSZ ;get sectors/cluster
XOR CH,CH ;CX = sectors in one cluster
ADD AX,CX ;+ one data cluster
CMP BPB_SECN,AX ;compare with sectors available
JAE VALIDATE_X ;jump if enough sectors
CMP DIR_SECTORS,1 ;down to 1 directory sector?
JBE VALIDATE_C ;can't let it go below 1
MOV AX,BPB_SSZ ;sector size
CWD ;clear DX for division
DIV DIRE_SIZE ;sectorsize/dir entry size = entries/sector
SUB BPB_DIRN,AX ;reduce directory entries by 1 sector
OR ERR_FLAG,ERR_DIRN ;indicate directory entries adjusted
JMP VALIDATE_A ;retry with new directory entries number
VALIDATE_C: ;not enough space for any VDISK
OR ERR_FLAG,ERR_SYSSZ
VALIDATE_X:
RET
SUBTTL VAL_BSIZE Validate buffer size
PAGE
;-----------------------------------------------------------------------;
; VAL_BSIZE adjusts the buffer size as necessary ;
;-----------------------------------------------------------------------;
VAL_BSIZE PROC
CALL GET_MSIZE ;determine memory available to VDISK
;returns available KB in AX
OR AX,AX ;is any memory available at all?
JNZ VAL_BSIZE_B ;yes, continue
OR ERR_FLAG,ERR_SYSSZ ;indicate system too small for VDISK
MOV BUFF_SIZE,1 ;set up minimal values to continue init
MOV AX,VAL_SSZ_S ;smallest possible sector size
MOV BPB_SSZ,AX
MOV BPB_DIRN,4 ;4 directory entries
RET
VAL_BSIZE_B: ;some memory is available
CMP AX,BUFF_SIZE ;is available memory >= requested?
JAE VAL_BSIZE_C ;if so, we're done
MOV BUFF_SIZE,AX ;give all available memory
OR ERR_FLAG,ERR_BSIZE ;indicate buffersize adjusted
VAL_BSIZE_C:
RET
GET_MSIZE PROC ;determine memory available to VDISK
;returns KB available in AX
CMP EM_SW,0 ;extended memory?
JE GET_MSIZE_2 ;use non-extended memory routine
MOV AH,EM_MEMSIZE ;function code to AH
INT EM_INT ;get extended memory size in AX
JC GET_MSIZE_Z ;if error, no extended memory installed
MUL C1024 ;DX,AX = bytes of extended memory
ADD DX,10H ;DX,AX = high addr of extended memory+1
SUB AX,AVAIL_LO ;- address of first available byte
SBB DL,AVAIL_HI ;is number of free bytes
DIV C1024 ;AX = number of whole free kilobytes
RET
GET_MSIZE_2: ;non-extended memory size determination
;Compute AX = total system size, - (VDISK end address + 64KB)
MOV AX,START_BUFFER_PARA ;paragraph end of VDISK code
XOR DX,DX ;clear for division
DIV PARA_PER_KB ;KB address of load point
ADD DX,0FFFFH ;round upward to KB boundary
ADC AX,MIN_MEMORY_LEFT ;pick up CY and the 64KB we should leave
PUSH AX ;save across interrupt
INT MEM_SIZE ;get total system size
POP DX ;amount of total that we can't use
SUB AX,DX ;available space to VDISK
JNC GET_MSIZE_X ;exit if positive
GET_MSIZE_Z:
XOR AX,AX ;indicate no memory available
GET_MSIZE_X: ;exit from memory size determination
RET
GET_MSIZE ENDP
VAL_BSIZE ENDP
SUBTTL VAL_SSZ Validate Sector Size
PAGE
;-----------------------------------------------------------------------;
; VAL_SSZ validates sector size, adjusting if necessary ;
;-----------------------------------------------------------------------;
VAL_SSZ PROC ;validate sector size
CMP CS:EM_SW,0 ;extended memory ?
JE VAL_SSZ_ST ;no,go check sector size
MOV BX,MAXSEC_TRF ;move number of sectors to transfer
CMP BX,1 ;> or equal to 1 ?
JB DFLT_TRF ;set default if it is
CMP BX,8 ;> than 8 ?
JA DFLT_TRF ;set default if it is
JMP VAL_SSZ_ST ;continue processing
DFLT_TRF: ;set default
MOV MAXSEC_TRF,DFLT_ESS ;
MOV BX,MAXSEC_TRF ;
OR CS:ERR_FLAG,ERR_ESIZE ;indicate transfer size adjusted
VAL_SSZ_ST: ;validate sector size
MOV MAX_CNT,BX ;initialize maximum number of sectors
;to transfer for extended memory case
MOV BX,BPB_SSZ ;requested sector size
MOV CX,VAL_SSZ_N ;number of table entries
MOV SI,OFFSET VAL_SSZ_TBL ;DS:SI point to table start
VAL_SSZ_A:
LODSW ;get table entry, step table pointer
CMP AX,BX ;is value in table?
JE VAL_SSZ_X ;exit if value found
LOOP VAL_SSZ_A ;loop until table end
MOV BX,DFLT_SSZ ;get default sector size
MOV BPB_SSZ,BX ;set sector size to default value
OR ERR_FLAG,ERR_SSZ ;indicate sector size adjusted
VAL_SSZ_X:
;Compute the maximum number of sectors that can be moved in 64KB (less one)
;Restricting moves to this amount avoids 64KB boundary problems.
CMP CS:EM_SW,0 ;extended memory ?
JNE SIZE_DONE ;yes, we are done
XOR DX,DX
MOV AX,0FFFFH ;64KB - 1
DIV BX ;/sector size
MOV MAX_CNT,AX ;max sectors in one move
SIZE_DONE:
RET
VAL_SSZ ENDP
SUBTTL VAL_DIRN Validate number of directory entries
PAGE
;-----------------------------------------------------------------------;
; VAL_DIRN validates and adjusts the number of directory entries. ;
; ;
; Minimum is MIN_DIRN, maximum is MAX_DIRN. If outside these ;
; limits, DFLT_DIRN is used. ;
; ;
; The number of directory entries is rounded upward to fill ;
; a sector ;
;-----------------------------------------------------------------------;
VAL_DIRN PROC
MOV AX,BPB_DIRN ;requested directory entries
CMP AX,MIN_DIRN ;if less than minimum
JB VAL_DIRN_A ;use default instead
CMP AX,MAX_DIRN ;if <= maximum
JBE VAL_DIRN_B ;accept value as provided
VAL_DIRN_A:
MOV AX,DFLT_DIRN ;use default directory entries
OR ERR_FLAG,ERR_DIRN ;indicate directory entries adjusted
VAL_DIRN_B: ;AX is number of directory entries
MUL DIRE_SIZE ;* 32 = bytes of directory requested
DIV BPB_SSZ ;/ sector size = # of directory sectors
OR DX,DX ;test remainder for zero
JZ VAL_DIRN_C ;jump if exact fit
INC AX ;increment directory sectors
OR ERR_FLAG,ERR_DIRN ;indicate directory entries adjusted
VAL_DIRN_C: ;make sure enough sectors available
MOV DX,BPB_SECN ;total sectors on media
SUB DX,BPB_RES ;less reserved sectors
SUB DX,2 ;less minimum FAT and 1 data sector
CMP AX,DX ;if directory sectors <= available
JLE VAL_DIRN_D ;use requested amount
MOV AX,1 ;use only one directory sector
OR ERR_FLAG,ERR_DIRN ;indicate directory entries adjusted
VAL_DIRN_D:
MOV DIR_SECTORS,AX ;save number of directory sectors
MUL BPB_SSZ ;dir sectors * sector size = dir bytes
DIV DIRE_SIZE ;dir bytes / entry size = entries
MOV BPB_DIRN,AX ;store adjusted directory entries
RET
VAL_DIRN ENDP
SUBTTL VAL_FAT Validate File Allocation Table (FAT)
PAGE
;-----------------------------------------------------------------------;
;VAL_FAT computes: ;
;BPB_FATSZ, the number of sectors required per FAT copy ;
; ;
;Each FAT entry is 12 bits long, for a maximum of 4095 FAT entries. ;
;(A few FAT entries are reserved, so the highest number of FAT entries ;
;we permit is 0FE0H.) With large buffer sizes and small sector sizes, ;
;we have more allocation units to describe than a 12-bit entry will ;
;describe. If the number of FAT entries is too large, the sector size ;
;is increased (up to a maximum of 512 bytes), and then the allocation ;
;unit (cluster) size is doubled, until we have few enough allocation ;
;units to be properly described in 12 bits. ;
; ;
;This computation is slightly conservative in that the FAT entries ;
;necessary to describe the FAT sectors are included in the computation. ;
;-----------------------------------------------------------------------;
VAL_FAT PROC
MOV AX,BPB_SECN ;total number of sectors
SUB AX,BPB_RES ;don't count boot sector(s)
SUB AX,DIR_SECTORS ;don't count directory sectors
JG VAL_FAT_A ;jump if some remaining
MOV BPB_SSZ,DFLT_SSZ ;force default sector size
OR ERR_FLAG,ERR_SSZ+ERR_PASS ;indicate sector size adjusted
JMP SHORT VAL_FAT_X ;recompute all values
VAL_FAT_A:
XOR DX,DX ;clear DX for division
MOV CL,BPB_AUSZ ;CX = sectors/cluster
XOR CH,CH
DIV CX ;whole number of clusters in AX
ADD DX,0FFFFH ;set carry if remainder
ADC AX,0 ;increment AX if remainder
CMP AX,MAX_FATE ;number of FAT entries too large?
JBE VAL_FAT_C ;no, continue
MOV AX,BPB_SSZ ;pick up current sector size
CMP AX,VAL_SSZ_L ;already at largest permitted?
JE VAL_FAT_B ;yes, can't make it any larger
SHL BPB_SSZ,1 ;double sector size
OR ERR_FLAG,ERR_SSZB ;indicate sector size adjusted
JMP SHORT VAL_FAT_X ;recompute all sizes with new BPBSSZ
VAL_FAT_B: ;sector size is at maximum
SHL BPB_AUSZ,1 ;double allocation unit size
OR ERR_FLAG,ERR_PASS ;indicate another pass required
JMP SHORT VAL_FAT_X ;recompute values
VAL_FAT_C: ;FAT size = 1.5 * number of clusters
MOV CX,AX ;number of clusters
SHL AX,1 ;* 2
ADD AX,CX ;* 3
SHR AX,1 ;* 1.5
ADC AX,3 ;add 3 bytes for first 2 FAT entries
;(media descriptor and FFFFH), and CY
XOR DX,DX ;clear DX for division
DIV BPB_SSZ ;FAT size/sector size
ADD DX,0FFFFH ;set carry if remainder
ADC AX,0 ;round upward
MOV BPB_FATSZ,AX ;number of sectors for 1 FAT copy
VAL_FAT_X:
RET
VAL_FAT ENDP
VALIDATE ENDP
SUBTTL COPY_BPB Copy BPB to Boot Record
PAGE
;-----------------------------------------------------------------------;
; COPY_BPB copies the BIOS Parameter Block (BPB) ;
; to the VDISK Boot Record ;
;-----------------------------------------------------------------------;
ASSUME DS:CSEG
COPY_BPB PROC ;Copy BBP to Boot Record
PUSH DS
POP ES ;set ES = DS
MOV CX,BPB_LEN ;length of BPB
MOV SI,OFFSET BPB ;source offset
MOV DI,OFFSET BOOT_BPB ;target offset
REP MOVSB ;copy BPB to boot record
RET
COPY_BPB ENDP
SUBTTL VERIFY_EXTENDER
PAGE
;-----------------------------------------------------------------------;
; VERIFY_EXTENDER makes sure that if an Expansion Unit is ;
; installed, the memory size switches on the Extender Card ;
; are correctly set. ;
;-----------------------------------------------------------------------;
ASSUME DS:CSEG
EXT_P210 EQU 0210H ;write to latch expansion bus data
;read to verify expansion bus data
EXT_P213 EQU 0213H ;Expansion Unit status
VERIFY_EXTENDER PROC
NOP
MOV DX,EXT_P210 ;Expansion bus data port address
MOV AX,5555H ;set data pattern
OUT DX,AL ;write 55H to control port
PUSH DX
POP DX
JMP SHORT $+2 ;Let the I/O circuits catch up
IN AL,020h ;Clear the CMOS bus drivers!
IN AL,DX ;recover data
CMP AH,AL ;did we recover the same data?
JNE VERIFY_EXTENDER_X ;if not, no extender card
NOT AX ;set AX = 0AAAAH
OUT DX,AL ;write 0AAH to control port
PUSH DX ;load data line
POP DX ;load data line
JMP SHORT $+2 ;Let the I/O circuits catch up
IN AL,020h ;Clear the CMOS bus drivers!
IN AL,DX ;recover data
CMP AH,AL ;did we recover the same data?
JNE VERIFY_EXTENDER_X ;if not, no extender card
;Expansion Unit is present.
;Determine what the switch settings should be on the Extender Card
INT MEM_SIZE ;get system memory size in KB in AX
ADD AX,63D ;memory size + 63K
MOV CL,6 ;2^6 = 64
SHR AX,CL ;divide by 64
;AX is highest segment address
MOV AH,AL ;save number of segments
;Read Expander card switch settings
MOV DX,EXT_P213 ;expansion unit status
IN AL,DX ;read status
;bits 7-4 (hi nibble) are switches
MOV CL,4 ;shift count
SHR AL,CL ;shift switches to bits 3-0 of AL
CMP AH,AL ;do switches match memory size?
JE VERIFY_EXTENDER_X ;yes, exit normally
OR ERR_FLAG,ERR_EXTSW ;indicate switch settings are wrong
VERIFY_EXTENDER_X:
RET
VERIFY_EXTENDER ENDP
SUBTTL UPDATE_AVAIL
PAGE
;-----------------------------------------------------------------------;
; UPDATE_AVAIL updates the address of the first byte in extended ;
; memory not used by any VDISK buffer ;
;-----------------------------------------------------------------------;
UPDATE_AVAIL PROC ;update AVAIL_LO and AVAIL_HI of first VDISK
MOV AX,BUFF_SIZE ;number of KB of VDISK buffer
MUL C1024 ;DX,AX = number of bytes of VDISK buffer
PUSH DS
MOV DS,FIRST_VDISK ;set DS to first VDISK
ADD DS:AVAIL_LO,AX ;update first available byte location
ADC DS:AVAIL_HI,DL
POP DS
RET
UPDATE_AVAIL ENDP
SUBTTL FORMAT_VDISK
PAGE
;-----------------------------------------------------------------------;
; This Request Header is used by MOVE_VDISK to move the ;
; first few sectors of the virtual disk (boot, FAT, and ;
; Directory) into extended memory. ;
;-----------------------------------------------------------------------;
MOVE_RH DB MOVE_RH_L ;length of request header
DB 0 ;sub unit
DB 8 ;output operation
DW 0 ;status
DQ ? ;reserved for DOS
DB ? ;media descriptor byte
MOVE_RHO DW ? ;offset of data transfer address
MOVE_RHS DW ? ;segment of data transfer address
MOVE_RHCNT DW ? ;count of sectors to transfer
DW 0 ;starting sector number
MOVE_RH_L EQU $-MOVE_RH ;length of request header
;-----------------------------------------------------------------------;
; FORMAT_VDISK formats the boot sector, FAT, and directory of an ;
; extended memory VDISK in storage immediately following ;
; VDISK code, in preparation for moving to extended memory. ;
;-----------------------------------------------------------------------;
FORMAT_VDISK PROC ;format boot record, FATs and directory
MOV AX,CS ;compute 20-bit address
MUL WPARA_SIZE ;16 * segment
ADD AX,OFFSET MSGEND ;+ offset
ADC DL,0 ;pick up carry
ADD AX,STACK_SIZE ;plus stack size
ADC DL,0 ;pick up carry
DIV WPARA_SIZE ;split into segment(AX)&offset(DX)
MOV MOVE_RHS,AX ;save in Request Header for move
MOV MOVE_RHO,DX
MOV DI,DX ;offset to DI
MOV ES,AX ;segment to ES
;copy the boot record
MOV SI,OFFSET BOOT_RECORD ;point to source field
MOV AX,BPB_RES ;number of reserved sectors
MUL BPB_SSZ ;* sector size = length of boot records
MOV CX,AX ;length to CX for move
REP MOVSB ;move boot record(s)
;format the FAT(s)
MOV CL,BPB_FATN ;number of FATs
XOR CH,CH
FORMAT_VDISK_A: ;set up one FAT
PUSH CX ;save loop counter on stack
MOV AL,BPB_MCB ;media control byte
STOSB ;store media control byte, increment DI
MOV AX,0FFFFH ;bytes 2 and 3 of FAT are 0FFH
STOSW
MOV AX,BPB_FATSZ ;number of sectors per FAT
MUL BPB_SSZ ;* sector size = length of FAT in bytes
SUB AX,3 ;less the 3 bytes we've stored
MOV CX,AX ;count to CX
XOR AX,AX
REP STOSB ;clear remainder of FAT
POP CX ;get loop counter off stack
LOOP FORMAT_VDISK_A ;loop for all copies of the FAT
;Format the directory
MOV SI,OFFSET VOL_LABEL ;point to volume label
MOV CX,VOL_LABEL_LEN ;length of volume directory entry
REP MOVSB ;move volume id to directory
MOV AX,DIR_ENTRY_SIZE ;length of 1 directory entry
MUL BPB_DIRN ;* number entries = bytes of directory
SUB AX,VOL_LABEL_LEN ;less length of volume label
MOV CX,AX ;CX = length of rest of directory
XOR AX,AX
REP STOSB ;clear directory to nulls
RET
FORMAT_VDISK ENDP
SUBTTL MOVE_VDISK
PAGE
;-----------------------------------------------------------------------;
; MOVE_VDISK moves the formatted boot sector, FAT, and directory ;
; into extended memory. ;
;-----------------------------------------------------------------------;
MOVE_VDISK PROC
MOV AL,BPB_FATN ;number of FAT copies
CBW ;clear AH
MUL BPB_FATSZ ;number of FAT sectors
ADD AX,BPB_RES ;+ reserved sectors
ADD AX,DIR_SECTORS ;+ directory sectors
MOV MOVE_RHCNT,AX ;store as I/O length
MOV BX,OFFSET MOVE_RH ;DS:BX point to request header
PUSH DS ;make sure DS gets preserved
CALL INOUT ;move to extended memory
POP DS
RET
MOVE_VDISK ENDP
SUBTTL UPDATE_BOOT
PAGE
;-----------------------------------------------------------------------;
; UPDATE_BOOT updates the BOOT_EM word in the first extended ;
; memory VDISK (address 10 001EH) to show the kilobyte address ;
; of the first extended memory byte not used by any VDISK buffer. ;
;-----------------------------------------------------------------------;
UPDATE_BOOT PROC
PUSH DS
MOV DS,FIRST_VDISK ;set DS to first VDISK
MOV AX,DS:AVAIL_LO ;24-bit end address of all VDISKs
MOV DL,DS:AVAIL_HI
XOR DH,DH
POP DS
DIV C1024 ;address / 1024
MOV BOOT_EM,AX ;store in temporary location
MOV AX,2 ;length of block move is 2 bytes
MOV TGT.DESC_LMT,AX
MOV SRC.DESC_LMT,AX
MOV AX,PARA_SIZE ;16
MOV CX,CS ;our segment address
MUL CX ;16 * segment address
ADD AX,OFFSET BOOT_EM ;+ offset of source data
ADC DL,0 ;pick up any carry
MOV SRC.DESC_BASEL,AX ;store source base address
MOV SRC.DESC_BASEH,DL
MOV TGT.DESC_BASEL,BOOT_EM_OFF ;offset of BOOT_EM
MOV TGT.DESC_BASEH,10H ;1 megabyte
MOV CX,1 ;move 1 word
PUSH CS
POP ES
MOV SI,OFFSET GDT ;ES:DI point to global descriptor table
MOV AH,EM_BLKMOVE ;function code
INT EM_INT ;move BOOT_EM to 10 001EH
RET
UPDATE_BOOT ENDP
SUBTTL STEAL_INT19
PAGE
;-----------------------------------------------------------------------;
; STEAL_INT19 changes the INT 19H vector to point to this VDISK ;
; so that subsequent extended memory VDISKS may locate the ;
; AVAIL_HI and AVAIL_LO fields to determine their buffer start ;
; addresses. ;
;-----------------------------------------------------------------------;
STEAL_INT19 PROC
PUSH DS
XOR AX,AX
MOV DS,AX ;set DS = 0
ASSUME DS:INT_VEC
CLI ;disable interrupts
LES DI,DS:BOOT_VEC ;get original vector's content
MOV CS:INTV19O,DI ;save original vector
MOV CS:INTV19S,ES
MOV DS:BOOT_VECO,OFFSET VDISK_INT19 ;offset of new INT routine
MOV DS:BOOT_VECS,CS ;segment of new INT routine
STI ;enable interrupts again
POP DS ;restore DS
RET
STEAL_INT19 ENDP
SUBTTL FILL_RH Fill in Request Header
PAGE
;-----------------------------------------------------------------------;
; FILL_RH fills in the Request Header returned to DOS ;
;-----------------------------------------------------------------------;
ASSUME DS:CSEG
FILL_RH PROC ;fill in INIT Request Header fields
MOV CX,START_BUFFER_PARA ;segment end of VDISK resident code
MOV AX,PARAS_PER_SECTOR ;paragraphs per sector
MUL BPB_SECN ;* number of sectors
ADD AX,CX ;+ starting segment
MOV DX,AX ;DX is segment of end VDISK buffer
CMP EM_SW,0 ;if extended memory not requested
JE FILL_RH_A ;skip DX adjustment
MOV DX,CX ;end of code segment addr
FILL_RH_A: ;DX is proper ending segment address
MOV AL,1 ;number of units
test CS:err_flag2,err_baddos
jnz dont_install
TEST ERR_FLAG,ERR_SYSSZ+ERR_EXTSW ;if bypassing install
JZ FILL_RH_B ;jump if installing driver
dont_install:
MOV DX,CS ;segment of end address
XOR AL,AL ;number of units is zero
FILL_RH_B:
PUSH DS ;preserve DS
LDS BX,RH_PTRA ;get Request Header addr in DS:BX
MOV RH.RH0_NUN,AL ;store number of units (0 or 1)
MOV RH.RH0_ENDO,0 ;end offset is always zero
MOV RH.RH0_ENDS,DX ;end of VDISK or end of buffer
MOV RH.RH0_BPBO,OFFSET BPB_PTR
MOV RH.RH0_BPBS,CS ;BPB array address
POP DS ;restore DS
RET
FILL_RH ENDP
SUBTTL WRITE_MESSAGES and associated routines
PAGE
;-----------------------------------------------------------------------;
; WRITE_MESSAGE writes a series of messages to the standard ;
; output device showing the VDISK parameter values actually used. ;
;-----------------------------------------------------------------------;
CHAR4 DB 'nnnn$' ;build 4 ASCII decimal digits
ASSUME DS:CSEG
WRITE_MESSAGES PROC ;display all messages
MSG IMSG ;'VDISK virtual disk $'
test CS:err_flag2,err_baddos
jz check_dos_version
msg errm8
ret
;If DOS Version 3.x is in use, the Request Header contains a drive code
;that is displayed to show which drive letter was assigned to this
;VDISK. This field is not present in the DOS Version 2 Request Header.
check_dos_version:
MOV AH,DOS_VERS ;get DOS version call
INT DOS ;invoke DOS
CMP AL,3 ;DOS Version 3 or greater?
JB WRITE_MESSAGES_A ;no, bypass drive letter
PUSH DS ;preserve DS
LDS BX,RH_PTRA ;get Request Header Address
MOV DL,RH.RH0_DRIV ;get drive code
ADD DL,'A' ;convert to drive letter
POP DS ;restore DS
MOV AH,DOS_PCHR ;function code to write character in DL
INT DOS ;display drive letter
MOV DL,':' ;display trailing colon
INT DOS
WRITE_MESSAGES_A:
MSG MSGCRLF ;end the first line
;If any of the user specified values has been adjusted, issue an
;appropriate message
TEST ERR_FLAG,ERR_BSIZE ;was buffersize adjusted?
JZ WRITE_MESSAGES_B ;if not, skip message
MSG ERRM1 ;buffer size adjusted
WRITE_MESSAGES_B:
TEST ERR_FLAG,ERR_SSZ ;was sector size adjusted?
JZ WRITE_MESSAGES_C ;if not, skip message
MSG ERRM2 ;sector size adjusted
WRITE_MESSAGES_C:
TEST ERR_FLAG,ERR_DIRN ;were directory entries adjusted?
JZ WRITE_MESSAGES_D0 ;if not, skip message
MSG ERRM3 ;directory entries adjusted
WRITE_MESSAGES_D0:
TEST ERR_FLAG,ERR_ESIZE ;was transfer size adjusted?
JZ WRITE_MESSAGES_D ;if not, skip message
MSG ERRM7 ;transfer size adjusted
WRITE_MESSAGES_D:
TEST ERR_FLAG,ERR_SWTCH ;was an invalid switch character found?
JZ WRITE_MESSAGES_E ;if not, skip message
MSG ERRM5 ;invalid switch character
WRITE_MESSAGES_E:
TEST ERR_FLAG,ERR_SYSSZ ;is system size too small to install?
JZ WRITE_MESSAGES_F ;if not, bypass error message
MSG ERRM4 ;too large for system storage
RET ;skip messages showing adjusted sizes
WRITE_MESSAGES_F:
TEST ERR_FLAG,ERR_EXTSW ;extender card switches wrong?
JZ WRITE_MESSAGES_G ;if not, bypass error message
MSG ERRM6 ;extender card switches wrong msg
RET ;skip remaining messages
WRITE_MESSAGES_G: ;display adjusted size messages
MSG MSG1 ;buffer size:
MOV DX,BUFF_SIZE ;buffer size in binary
CALL STOR_SIZE ;convert binary to ASCII decimal
MSG CHAR4 ;print 4 decimals
MSG MSG2 ;KB,CR,LF
MSG MSG3 ;sector size:
MOV DX,BPB_SSZ
CALL STOR_SIZE ;convert binary to ASCII decimal
MSG CHAR4 ;print 4 decimals
MSG MSGCRLF ;finish off line
MSG MSG4 ;directory entries:
MOV DX,BPB_DIRN ;number of directory entries
CALL STOR_SIZE
MSG CHAR4 ;print 4 decimals
MSG MSGCRLF ;finish off the line
CMP CS:EM_SW,0 ;extended memory ?
JE END_LINE ;
MSG MSG5 ;transfer size:
MOV DX,MAXSEC_TRF
CALL STOR_SIZE ;convert binary to ASCII decimal
MSG CHAR4 ;print 4 decimals
MSG MSGCRLF ;finish off line
END_LINE:
MSG MSGCRLF ;one more blank line to set it off
RET ;return to INIT_P1
;SHOW_MSG displays a string at DS:DX on the standard output device
;String is terminated by a $
SHOW_MSG PROC ;display string at DS:DX
PUSH AX ;preserve AX across call
MOV AH,DOS_PSTR ;DOS function code
INT DOS ;invoke DOS print string function
POP AX ;restore AX
RET
SHOW_MSG ENDP
;STOR_SIZE converts the content of DX to 4 decimal characters in CHAR4
;(DX must be <= 9999)
STOR_SIZE PROC ;convert DX to 4 decimals in CHAR4
;develop 4 packed decimal digits in AX
XOR AX,AX ;clear result register
MOV CX,16 ;shift count
STOR_SIZE_B:
SHL DX,1 ;shift high bit into carry
ADC AL,AL ;double AL, carry in
DAA ;adjust for packed decimal
XCHG AL,AH
ADC AL,AL ;double high byte, carry in
DAA
XCHG AL,AH
LOOP STOR_SIZE_B ;AX contains 4 packed decimal digits
PUSH CS
POP ES ;point ES:DI to output string
MOV DI,OFFSET CHAR4
MOV CX,1310H ;10H in CL is difference between blank and zero
;13H in CH is decremented and ANDed to force
;last character not to be zero suppressed
PUSH AX ;save AX on stack
MOV DL,AH ;2 decimals to DL
CALL STOR_SIZE_2 ;display DL as 2 decimal characters
POP DX ;bring low 2 decimals into DL
STOR_SIZE_2: ;display DL as 2 decimal characters
MOV DH,DL ;save 2 decimals in DH
SHR DL,1 ;shift high order decimal right to low position
SHR DL,1
SHR DL,1
SHR DL,1
CALL STOR_SIZE_1 ;display low nibble of DL
MOV DL,DH ;get low decimal from pair
STOR_SIZE_1: ;display low nibble of DL as 1 decimal char
AND DL,0FH ;clear high nibble
JZ STOR_SIZE_Z ;if digit is significant,
XOR CL,CL ;defeat zero suppression
STOR_SIZE_Z:
DEC CH ;decrement zero suppress counter
AND CL,CH ;always display least significant digit
OR DL,'0' ;convert packed decimal to ASCII
SUB DL,CL ;zero suppress (nop or change '0' to ' ')
MOV AL,DL ;char to DL
STOSB ;store char at ES:DI, increment DI
RET
STOR_SIZE ENDP
WRITE_MESSAGES ENDP
INIT_P1 ENDP ;end of INIT part one
;-----------------------------------------------------------------------;
; VDISK Message definitions ;
;-----------------------------------------------------------------------;
IMSG DB 'VDISK virtual disk ','$'
ERRM1 DB ' Buffer size adjusted',CR,LF,'$'
ERRM2 DB ' Sector size adjusted',CR,LF,'$'
ERRM3 DB ' Directory entries adjusted',CR,LF,'$'
ERRM4 DB ' VDISK not installed - insufficient memory'
DB CR,LF,CR,LF,BEL,'$'
ERRM5 DB ' Invalid switch character',CR,LF,'$'
ERRM6 DB ' VDISK not installed - Extender Card switches'
DB CR,LF
DB ' do not match system memory size'
DB CR,LF,CR,LF,BEL,'$'
ERRM7 DB ' Transfer size adjusted',CR,LF,'$'
ERRM8 DB ' VDISK not installed - Incorrect DOS version'
DB CR,LF,CR,LF,BEL,'$'
MSG1 DB ' Buffer size: $'
MSG2 DB ' KB'
MSGCRLF DB CR,LF,'$'
MSG3 DB ' Sector size: $'
MSG4 DB ' Directory entries: $'
MSG5 DB ' Transfer size: $'
MSGEND LABEL BYTE ; End of message text
CSEG ENDS
END