;JS note, 8/1/83:  This version of the variable-size virtual disk
;   can handle up to 112 files.  There seems not to be any need 
;   to change to a "double-sided" drive, but one must take care
;   to zero out 7 sectors for the directory, rather than just the usual 4.

        PAGE    66,132
;
;       ******************************************************
;	*                 PROLOG                             *
;       *  THIS IS AN INSTALLABLE DEVICE DIVER FOR AN        *
;       *  IN STORAGE DISKETTE (VIRTUAL) WITH 180K CAPACITY. *
;       ******************************************************
CSEG	SEGMENT	PARA PUBLIC 'CODE'
;
;	M A C R O ( S )
;
STATUS	MACRO	STATE,ERR,RC
	IFIDN	<STATE>,<DONE>
	 OR	ES:WORD PTR SRH_STA_FLD[BX],0100H
   	ENDIF
	IFIDN	<STATE>,<BUSY>
 	 OR  	ES:WORD PTR SRH_STA_FLD[BX],0200H
	ENDIF
	IFIDN	<ERR>,<ERROR>
	 OR	ES:WORD PTR SRH_STA_FLD[BX],0100H
	ENDIF
	IFNB	<RC>
	 OR   	ES:WORD PTR SRH_STA_FLD[BX],RC
	ENDIF
	ENDM
;
;	E Q U A T E S
;
;  READ/WRITE
;
SRH	     EQU    0		;STATIC REQUEST HEADER START
SRH_LEN	     EQU   13		;  "      "       "    LENGTH
SRH_LEN_FLD  EQU   SRH		;  "      "       "      "    FIELD
SRH_UCD_FLD  EQU   SRH+1	;  "      "       "    UNIT CODE FIELD
SRH_CCD_FLD  EQU   SRH+2	;  "      "       "    COMMAND CODE FIELD
SRH_STA_FLD  EQU   SRH+3	;  "      "       "    STATUS FIELD
SRH_RES_FLD  EQU   SRH+5	;  "      "       "    RESERVED AREA FIELD
;
MD	 EQU   SRH+SRH_LEN	;MEDIA DESCRIPTOR BYTE
MD_LEN	 EQU   1		;  "      "        "   LENGTH
DTA	 EQU   MD+MD_LEN	;DISK TRANSFER ADDRESS
DTA_LEN  EQU   4           	; DTA LENGTH
COUNT	 EQU   DTA+DTA_LEN	;BYTE/SECTOR COUNT
COUNT_LEN EQU  2 		; "     "      "    LENGTH
SSN	 EQU   COUNT+COUNT_LEN  ;STARTING SECTOR NUMBER
SSN_LEN	 EQU   2		;   "       "      "     LENGTH
;
;  MEDIA CHECK
;
RET_BYTE  EQU	MD+MD_LEN	;BYTE RETURNED FROM DRIVER
;
; BUILD BPB
;
BPBA_PTR     EQU  DTA+DTA_LEN	;POINTER TO BPB
BPBA_PTR_LEN EQU  4		;   "     "  "  LENGTH
;
;  INIT
;
UNITS       EQU   SRH+SRH_LEN
UNITS_LEN   EQU   1
BR_ADDR_0   EQU   UNITS+UNITS_LEN
BR_ADDR_1   EQU   BR_ADDR_0+2
BR_ADDR_LEN EQU   4
BPB_PTR_OFF EQU   BR_ADDR_0+BR_ADDR_LEN
BPB_PTR_SEG EQU   BPB_PTR_OFF+2
;
;
VDSK     PROC	FAR
         ASSUME	CS:CSEG,ES:CSEG,DS:CSEG
BEGIN:
START		EQU	$
;	S P E C I A L  D E V I C E  H E A D E R
NEXT_DEV	DD	-1		;POINTER TO NEXT DEVICE
ATTRIBUTE	DW	2000H		;BLOCK DEVICE (NON-IBM FORMAT)
STRATEGY	DW	DEV_STRATEGY	;POINTER TO DEVICE STRATEGY
INTERRUPT	DW	DEV_INT		;POINTER TO DEVICE INTERRUPT HANDLER
DEV_NAME	DB	1		;NUMBER OF BLOCK DEVICES
		DB	7 DUP (?)	;7 BYTES OF FILLER
;
RH_OFF		DW	?		;REQUEST HEADER OFFSET
RH_SEG		DW	?		;REQUEST HEADER SEGMENT
;  BIOS PARAMETER BLOCK
BPB		EQU	$
		DW	512		;SECTOR SIZE
		DB	2;(JS change)   ;SECTOR/ALLOCATION UNIT
		DW	1		;NUMBER OF RESERVED SECTORS
		DB	2		;NUMBER OF FATS
		DW	112;(JS change)	;NUMBER OF DIRECTORY ENTRIES
SECTCT1		DW	?		;TOTAL NUMBER OF SECTORS
		DB	0FCH;         	;MEDIA DESCRIPTOR
		DW	2		;NUMBER OF SECTORS OCCUPIED BY FAT
;
BPB_PTR		DW	BPB		;BIOS PARAMETER BLOCK POINTER ARRAY (1 ENTR)
;  CURRENT VIRTUAL DISK INFORMATION
TOTAL		DW	?		;TOTAL SECTORS TO TRANSFER
VERIFY		DB	0		;VERIFY 1=YES, 0=NO
START_SEC	DW	0		;STARTING SECTOR NUMBER
VDISK_PTR	DW	0		;STARTING SEGMENT OF VIRTUAL DISK
USER_DTA	DD	?		;POINTER TO CALLSER DISK TRANSFER ADDRESS
BOOT_REC	EQU	$		;DUMMY DOS BOOT RECORD
		DB	3 DUP (0)	;3 BYTE JUMP TO BOOT CODE (NOT BOOTABLE)
		DB	'IBM  2.0'	;VENDOR IDENTIFICATION
		DW	512		;NUMBER OF BYTES IN A SECTOR
		DB	2;(JS change)  	;1 SECTOR PER ALLOCATION UNIT
		DW	1		;1 RESERVED SECTOR
		DB	2		;2 FATS
		DW	112;(JS change)	;NUMBER OF DIRECTORY ENTRIES
SECTCT2		DW	?		;TOTAL SECTORS IN IMAGE
		DB	0FCH;        	;TELLS DOS THIS IS A SINGLE SIDED 9 SECTOR
		DW	2		;NUMBER OF SECTORS IN FAT
;
;	FUNCTION TABLE
;
FUNTAB		LABEL	BYTE
		DW	INIT		;INITIALIZATION
		DW	MEDIA_CHECK	;MEDIA CHECK (BLOCK ONLY)
		DW	BUILD_BPB	;BUILD BPB      "     "
		DW	IOCTL_IN	;IOCTL INPUT
		DW	INPUT		;INPUT (READ)
		DW	ND_INPUT	;NON_DESTRUCTIVE INPUT NO WAIT (CHAR ONLY)
                DW      IN_STAT		;INPUT STATUS  (CHAR ONLY)
		DW	IN_FLUSH        ;INPUT FLUSH      "   "
		DW	OUTPUT		;OUTPUT (WRITE)
		DW	OUT_VERIFY	;OUTPUT (WRITE) WITH VERIFY
		DW	OUT_STAT	;OUTPUT STATUS  (CHAR ONLY)
		DW	OUT_FLUSH	;OUTPUT FLUSH     "   "
		DW	IOCTL_OUT	;IOCTL OUTPUT
;
;  L O C A L  P R O C E D U R E S
;
IN_SAVE		PROC	NEAR
		MOV	AX,ES:WORD PTR DTA[BX]   ;SAVE CALLERS DTA
		MOV	CS:USER_DTA,AX
		MOV	AX,ES:WORD PTR DTA+2[BX]
		MOV	CS:USER_DTA+2,AX
		MOV	AX,ES:WORD PTR COUNT[BX]  ;GET NUMBER OF SECTOR TO TOTAL
		XOR	AH,AH
		MOV	CS:TOTAL,AX		  ;MOVE NUMBER OF SECTORS TO TOTAL
            	RET
IN_SAVE		ENDP
;
CALC_ADDR  	PROC	NEAR
		MOV	AX,CS:START_SEC	;GET STARTING SECTOR NUMBER
                MOV 	CX,20H		;MOV 512 TO CX SEGMENT STYLE
                MUL	CX		;MULTIPLY TO GET ACTUAL SECTOR
		MOV	DX,CS:VDISK_PTR ;GET SEGMENT OF VIRTUAL DISK
		ADD	DX,AX		;ADD THAT SEGMENT TO INITIAL SEGMENT
                MOV	DS,DX		;SAVE THAT AS THE ACTUAL SEGMENT
                XOR	SI,SI		;IT'S ON A PARAGRAPH BOUNDRY
		MOV	AX,CS:TOTAL	;TOTAL NUMBER OF SECTOR TO READ
		MOV	CX,512		;BYTES PER SECTOR
		MUL	CX		;MULTIPLY TO GET COPY LENGTH
		OR	AX,AX		;CHECK FOR GREATER THAN 64K
		JNZ	MOVE_IT
		MOV	AX,0FFFFH       ;MOVE IN FOR 64K
MOVE_IT:
		XCHG	CX,AX		;MOVE LENGTH TO CX
		RET
CALC_ADDR       ENDP
;
SECTOR_READ	PROC	NEAR
		CALL	CALC_ADDR       ;CALCULATE THE STARTING 'SECTOR'
                MOV	ES,CS:USER_DTA+2 ;SET DESTINATION (ES:DI) TO POINT
                MOV	DI,CS:USER_DTA	 ;TO CALLERS DTA
;
;  CHECK FOR DTA WRAP IN CASE WE CAME THROUGH VIA VERIFY
;
		MOV	AX,DI		;GET OFFSET OF DTA
		ADD	AX,CX		;ADD COPY LENGTH TO IT
		JNC	READ_COPY       ;CARRY FLAG = 0, NO WRAP
		MOV	AX,0FFFFH	;MAX LENGTH
		SUB	AX,DI		;SUBTRACT DTA OFFSET FROM MAX
		MOV	CX,AX		;USE THAT AS COPY LENGTH TO AVOID WRAP
READ_COPY:
REP		MOVSB			;DO THE 'READ'
		RET
SECTOR_READ     ENDP
;
SECTOR_WRITE PROC	NEAR
		CALL	CALC_ADDR       ;CALCULATE STARTING 'SECTOR'
		PUSH	DS
		POP	ES		;ESTABLISH ADDRESSABILITY
		MOV	DI,SI		; ES:DI POINT TO 'DISK`
		MOV	DS,CS:USER_DTA+2 ; DS:SI POINT TO CALLERS DTA
		MOV	SI,CS:USER_DTA
;
; CHECK FOR DTA WRAP
;
		MOV	AX,SI		;MOVE DTA OFFSET TO AX
		ADD	AX,CX		;ADD COPY LENGTH TO OFFSET
		JNC	WRITE_COPY	;CARRY FLAG = 0, NO SEGMENT WRAP
                MOV	AX,0FFFFH	;MOVE IN MAX COPY LENGTH
		SUB	AX,SI		;SUTRACT DTA OFFSET FROM MAX
		MOV	CX,AX		;USE AS NEW COPY LENGTH TO AVOID WRAP
WRITE_COPY:
REP		MOVSB			;DO THE 'WRITE'
		RET
SECTOR_WRITE	ENDP
;
;  D E V I C E  S T R A T E G Y
;
DEV_STRATEGY:
		MOV	CS:RH_SEG,ES	;SAVE SEGMENT OF REQUEST HEADER POINTER
                MOV	CS:RH_OFF,BX	;SAVE OFFSET OF   "       "...
		RET
;
;  D E V I C E  I N T E R R U P T  H A N D L E R
;
DEV_INT:
;  PRESERVE MACHINE STATE ON ENTRY
		CLD
		PUSH	DS
		PUSH	ES
		PUSH	AX
		PUSH	BX
		PUSH	CX
		PUSH	DX
		PUSH	DI
		PUSH	SI
;
;  DO THE BRANCH ACCORDING TO THE FUNCTION PASSED
;
		MOV	AL,ES:[BX]+2	;GET FUNCTION BYTE
		ROL	AL,1		;GET OFFSET INTO TABLE
		LEA	DI,FUNTAB	;GET ADDRESS OF FUNCTION TABLE
		XOR	AH,AH
		ADD	DI,AX
                MOV     BX,CS:RH_OFF    ;See PC Age 2.6,
                MOV     ES,CS:RH_SEG    ;  p. 65.  (JS)
		JMP	WORD PTR[DI]
;
;   INIT
;
INIT:
		PUSH	CS
		POP	DX		;CURRENT CS TO DX
;
                PUSH    ES              ;JS modification:
                MOV     AX,0            ;  Load number of kilobytes
                MOV     ES,AX           ;   from last half of last
                MOV     AX,ES:3FEH      ;   "interrupt" slot, convert
                SAL     AX,1            ;   to number of sectors,
                MOV     CS:SECTCT1,AX   ;   and store result.
                MOV     CS:SECTCT2,AX
                POP     ES
;
		LEA	AX,CS:VDISK	;GET ADDRESS OF VIRTUAL DISK
		MOV	CL,4
        	ROR	AX,CL		;DIVIDE BY 16 (PARAGRAPH FORM)
		ADD	DX,AX		;ADD TO CURRENT CS VALUE
		MOV	CS:VDISK_PTR,DX ;SAVE AS STARTING SEGMENT OF VIRTUAL DISK
                MOV	AX,SECTCT1	; ADD ENOUGH PARAGRAPHS TO STARTING
                MOV     CL,5            ;    SEGMENT OF VIRTUAL DISK
                SAL     AX,CL
                ADD	DX,AX		
		MOV	ES:WORD PTR BR_ADDR_0[BX],0
		MOV	ES:BR_ADDR_1[BX],DX      ;MAKE THAT THE BREAK ADDRESS
                MOV	ES:BYTE PTR UNITS[BX],1  ;NUMBER OF DISKETTE UNITS
                LEA	DX,BPB_PTR	       ;GET ADDRESS OF BPB POINTER ARRAY
                MOV	ES:BPB_PTR_OFF[BX],DX  ;SAVE OFFSET IN DATA PACKET
                MOV	ES:BPB_PTR_SEG[BX],CS  ;SAVE SEGMENT IN DATA PACKET
                MOV	ES,CS:VDISK_PTR        ;GET STARTING SECTOR OF VIRTUAL DISK
                XOR	DI,DI		       ;ZERO OUT DI (BOOT RECORD)
                LEA	SI,BOOT_REC	       ;ADDRESS OF BOOT RECORD
                MOV	CX,24
REP		MOVSB			       ;COPY 24 BYTES OF BOOT RECORD
		MOV	CS:WORD PTR START_SEC,1
		MOV	CS:WORD PTR TOTAL,2
 		CALL	CALC_ADDR	;CALCULATE ADDRESS OF LOGICAL SECTOR 1
                PUSH	DS
		POP	ES
		MOV	DI,SI		;MOVE THAT ADDRES TO ES:DI
		XOR     AL,AL
REP		STOSB                           ;ZERO OUT FAT AREA
		MOV	DS:BYTE PTR [SI],0FCH	;SET THE FIRST FAT ENTRY
                MOV	DS:BYTE PTR 1[SI],0FFH
		MOV	DS:BYTE PTR 2[SI],0FFH
		PUSH	DS			;SAVE POINTER TO FAT
		PUSH	SI			;          ON THE STACK
		MOV	CS:WORD PTR START_SEC,3
		MOV	CS:WORD PTR TOTAL,2
		CALL	CALC_ADDR		;CALCULATE ADDRESS OF LOGICAL SECTOR 3
                PUSH	DS
		POP	ES
		MOV	DI,SI			;MOVE THAT ADDRESS TO ES:DI
                POP	SI
		POP	DS			;RESTORE ADDRESS TO FIRST FAT
REP		MOVSB 			;COPY FIRST FAT TO SECOND FAT
                MOV	CS:WORD PTR START_SEC,5
		MOV	CS:WORD PTR TOTAL,7;(JS change)
		CALL	CALC_ADDR		;CALCULATE ADDR OF L.S. 5 (START OF DIR)
                XOR	AL,AL
		PUSH	DS
		POP	ES			;SET UP ES:DI TO POINT T
    		XOR	DI,DI
REP		STOSB				;ZERO OUT DIRECTORY
		MOV	ES,CS:RH_SEG		;RESTORE ES:BX TO REQUEST HEADER
                MOV	BX,CS:RH_OFF
		STATUS  DONE,NOERROR,0		;SET STATUS WORD (DONE, NOERROR)
		JMP	EXIT
;
;  MEDIA CHECK
;
MEDIA_CHECK:					;MEDIA CHECK (BLOCK ONLY)
;
;  SET MEDIA NOT CHANGED
;
		MOV	ES:BYTE PTR RET_BYTE[BX],1  ;STORE IN RETURN BYTE
                STATUS  DONE,NOERROR,0
		JMP	EXIT
;
;  BUILD BIOS PARAMETER BLOCK
;
BUILD_BPB:
		PUSH	ES		;SAVE SRH SEGMENT
		PUSH	BX		;SAVE SRH OFFSET
		MOV	CS:WORD PTR START_SEC,0
		MOV	CS:WORD PTR TOTAL,1
		CALL	CALC_ADDR
		PUSH	CS
		POP	ES
		LEA	DI,BPB		;ADDRESS OF BIOS PARAMETER BLOCK
                ADD	SI,11		;ADD 11 TO SI
  		MOV	CX,13		;LENGTH OF BPB
REP		MOVSB
		POP	BX		;RESTORE OFFSET OF SRH
		POP	ES		;RESTORE SEGMENT OF SRH
		LEA	DX,BPB		;GET BPB ARRAY POINTER
		MOV	ES:BPBA_PTR[BX],DX  ;SAVE PTR TO BPB TABLE
		MOV	ES:BPBA_PTR+2[BX],CS
		MOV	ES:DTA[BX],DX ;OFFSET OF SECTOR BUFFER
		MOV	ES:DTA+2[BX],CS
		STATUS	DONE,NOERROR,0
		JMP	EXIT
;
;  THE FOLLOWING ENTRIES ARE FOR NOT SUPPORTED BY THIS DEVICE
;
IOCTL_IN:
IOCTL_OUT:
ND_INPUT:			;NON_DESTRUCTIVE INPUT NO WAIT (CHAR ONLY)
IN_STAT:			;INPUT STATUS "    "....
IN_FLUSH:			;INPUT FLUSH  "    "....
OUT_STAT: 			;OUTPUT STATUS"    "....
OUT_FLUSH:			;OUTPUT FLUSH "    "....
;
;  DISK READ
;
INPUT:
		CALL	IN_SAVE		;CALL THE INITIAL SAVE ROUTINE
		MOV	AX,ES:WORD PTR SSN[BX]  ;SET STARTING SECTOR NUMBER
                MOV	CS:START_SEC,AX		;SAVE STARTING SECTOR NUMBER
                MOV	AX,ES:WORD PTR COUNT[BX]
		MOV	CS:TOTAL,AX		;SAVE TOTAL SECTORS TO TRANSFER
                CALL	SECTOR_READ		;READ IN THAT MANY SECTORS
                MOV	BX,CS:RH_OFF		;RESTORE ES:BX AS REQUEST HEADER POINTER
                MOV	ES,CS:RH_SEG
		STATUS	DONE,NOERROR,0
		JMP	EXIT
;
;  DISK WRITE
;
OUTPUT:						;OUTPUT (WRITE)
		CALL	IN_SAVE
		MOV	AX,ES:WORD PTR SSN[BX]  ;GET STARTING SECTOR NUMBER
                MOV	CS:START_SEC,AX		;SET "        "......
		MOV	AX,ES:WORD PTR COUNT[BX]
		MOV	CS:TOTAL,AX		;SAVE TOTAL SECTORS TO WRITE
                CALL	SECTOR_WRITE		;WRITE OUT THOSE SECTORS
		MOV	BX,CS:RH_OFF		;RESTORE ES:BX AS REQUEST HEADER POINTER
                MOV	ES,CS:RH_SEG
		CMP	CS:BYTE PTR VERIFY,0    ;WRITE VERIFY SET
		JZ NO_VERIFY		;NO, NO WRITE VERIFY
		MOV	CS:BYTE PTR VERIFY,0	;RESET VERIFY INDICATOR
		JMP	INPUT			;READ THOSE SECTORS BACK IN
NO_VERIFY:
		STATUS	DONE,NOERROR,0		;SET DONE, NO EROR IN STATUS WORD
          	JMP	EXIT
OUT_VERIFY:					;OUTPUT (WRITE) WITH VERIFY
              	MOV	CS:BYTE PTR VERIFY,1    ;SET THE VERIFY FLAG
		JMP	OUTPUT			;BRANCH TO OUTPUT ROUTINE
;
;  COMMON EXIT
;
EXIT:
		POP	SI		;RESTORE ALL OF THE REGISTERS
		POP	DI
		POP	DX
		POP	CX
		POP	BX
		POP	AX
		POP	ES
		POP	DS
		RET
E_O_P:
; MACRO TO ALIGN THE VIRTUAL DISK ON A PARAGRAPH BOUNDARY
IF ($-START) MOD 16
ORG     ($-START)+16-(($-START) MOD 16)
ENDIF
VDISK		EQU	$
VDSK		ENDP
CSEG		ENDS
		END	BEGIN
