PAGE 60,132 ; Floppy disk I/O System for MS-DOS version 2.00 and later INCLUDE IODEF.ASM IO GROUP CODE CODE SEGMENT BYTE PUBLIC 'IOSYS' ASSUME CS:IO,DS:IO EXTRN EXIT:NEAR, CMDERR:NEAR, BUS$EXIT:NEAR, PTRSAV:DWORD EXTRN ERR$EXIT:NEAR EXTRN SELECT:BYTE IF LARGDRV PUBLIC LDSKTBL LDSKTBL: DW LDSK$INIT DW LMEDIA$CHK DW GET$BPB DW CMDERR DW LDSK$READ DW BUS$EXIT DW EXIT DW EXIT DW LDSK$WRIT DW LDSK$WRIT ENDIF IF SMALLDRV PUBLIC SDSKTBL SDSKTBL: DW SDSK$INIT DW SMEDIA$CHK DW GET$BPB DW CMDERR DW SDSK$READ DW BUS$EXIT DW EXIT DW EXIT DW SDSK$WRIT DW SDSK$WRIT ENDIF ; ************ 1793-type controller disk I/O ***************** READCOM EQU 80H WRITECOM EQU 0A0H IF SCP SMALLBIT EQU 10H BACKBIT EQU 04H DDENBIT EQU 08H DONEBIT EQU 01H DISK EQU 0E0H ENDIF IF TARBELL BACKBIT EQU 40H DDENBIT EQU 08H DONEBIT EQU 80H DISK EQU 78H DLYTIM EQU 10 ; 24 usec delay after force interrupt ENDIF CURDRV DB -1 ; SIDE has media byte ; Bit 7=1 for 5", 0 for 8" drives ; Bit 6=1 for two-side, 0 for one-side drives ; DDENBIT set for double density, reset for single density SIDE DB 0 ; Explanation of tables below. ; DRVTAB is a table of bytes which are sent to the disk controller as drive- ; select bytes to choose which physical drive is selected for each disk I/O ; driver. Always select side 0 in the drive-select byte if ; a side-select bit is available. Exactly which bits in the drive-select byte ; do what depends on which disk controller is used. ; TRKTAB is a table of bytes used to store which track the read/write ; head of each drive is on. Each physical drive should have its own ; entry in TRKTAB. ; TRKPT is a table of bytes which indicates which TRKTAB entry each ; disk I/O driver should use. Drives such as PerSci 277s which use ; the same head positioner for more than one drive should share entrys ; in TRKTAB. .SALL BYTELST MACRO INITVAL,COUNT,INC,DUPCNT LOCAL X X = INITVAL REPT COUNT DB DUPCNT DUP(X) X = X+INC ENDM ENDM DRVTAB LABEL BYTE IF SCP BYTELST 0,LDRVMAX,1,1 BYTELST 10H,SDRVMAX,1,1 ENDIF IF TARBELL BYTELST 0,LDRVMAX,10H,1 ENDIF TRKPT LABEL BYTE IF PERSCI BYTELST 0,LDRVMAX/2,1,2 BYTELST LDRVMAX/2,SDRVMAX,1,1 TRKTAB DB (LDRVMAX/2+SDRVMAX) DUP (-1) ELSE BYTELST 0,LDRVMAX+SDRVMAX,1,1 TRKTAB DB (LDRVMAX+SDRVMAX) DUP (-1) ENDIF IF LARGDRV LINITTAB LABEL WORD IF LARGEDS OR SIDECHK DW LDRVMAX DUP(LDSDRIVE) ELSE DW LDRVMAX DUP(LSSDRIVE) ENDIF ENDIF IF SMALLDRV SINITTAB LABEL WORD DW SDRVMAX DUP(SDSDRIVE) ENDIF IF SMALLDRV SSSDRIVE: ; This is the IBM Personal Computer DW 512 ; disk format. DB 1 DW 1 DB 2 DW 64 DW 320 DB 80H+DDENBIT DW 1 DW 512 SDSDRIVE: ; The IBM PC format for double sided disks DW 512 DB 2 DW 1 DB 2 DW 112 ; # of directory entries DW 640 DB 0C0H+DDENBIT ; Media DW 1 ; Sectors for 1 fat DW 512 ; Reserve space ENDIF IF LARGDRV LSDRIVE: ; Single density / single sided DW 128 ;SECTOR SIZE DB 4 ;SECTORS/ALLOC UNIT DW 1 ;NUMBER OF RESERVED SECTORS DB 2 ;NUMBER OF FATS DW 68 ;NUMBER OF DIRECTORY ENTRIES DW 77*26 ;TOTAL NUMBER OF SECTORS DB 0 ;MEDIA BYTE DW 6 ;SECTORS FOR ONE FAT IF SIDECHK OR LARGEDS LDSDRIVE: ; Double density / double sided DW 1024 DB 1 DW 1 DB 2 DW 192 DW 77*8*2 DB 40H+DDENBIT ; Media byte DW 2 ENDIF IF SIDECHK OR (LARGEDS-1) LSSDRIVE: ; Double density / single sided DW 1024 DB 1 DW 1 DB 2 DW 96 DW 77*8 DB DDENBIT ; Media byte DW 1 ENDIF ENDIF ;********************************************************* ; Disk change function. ; On entry: ; AL = disk unit number. ; AH = media byte ; On exit: ; AX = status (done or error code) ; [TRANS] set as follows: ; = -1 (FF hex) if disk is changed. ; = 0 if don't know. ; = 1 if not changed. ; [SIDE] has media byte for GETBPB call SMEDIA$CHK: IF COMBIN ADD AL,LDRVMAX ; Convert unit # ENDIF LMEDIA$CHK: MOV [SIDE],AH MOV AH,0 MOV SI,AX XCHG [SELECT+SI],AH ; Has drive been selected before? OR AH,AH JNZ DENCHECK ; If no, go check density ; First try head load test. If head is loaded on the drive we want, ; then disk must not have been changed. CMP AL,[CURDRV] ; Same drive? JNZ CHGCHK ; Try disk change signal if not PUSH AX ; Save unit number IF SCP IN AL,DISK+4 ; Head load byte for SCP controller ELSE IN AL,DISK ; Head load byte for Tarbell ENDIF AND AL,20H ; Look at head load bit POP AX MOV AH,1 ; AH = 1, disk not changed. JNZ MEDIA$EXIT CHGCHK: ; Now look at disk change bit if enabled and not a 5" drive. IF SMALLDRV MOV AH,0 ; Not sure if disk changed TEST [SIDE],80H JNZ MEDIA$EXIT ; If small drive, don't use disk change signal ENDIF IF DSKCHG CALL CHKNEW MOV DL,AL MOV BX,OFFSET IO:DRVTAB XLAT ; Get drive select byte OUT DISK+4,AL IN AL,DISK+1 ; Get current track number OUT DISK+3,AL ; Make it the track to seek to MOV AL,18H ; Seek and load head CALL DCOM CHKLOOP: IN AL,DISK ; Read type I status TEST AL,20H ; Check head load bit JZ CHKLOOP ; Wait until head-load time out TEST AL,80H ; Is disk ready? 1= not ready JNZ NOTRDY IN AL,DISK+4 ; Bit 7=1 if disk not changed TEST AL,80H MOV AH,1 JNZ MEDIA$EXIT MOV AL,DL ; Restore unit number ENDIF MOV AH,0 ; Disk may not have been changed ; Perform density check on 8" drive ; AH has current disk changed status, either 0 or -1 DENCHECK: IF LARGDRV IF COMBIN TEST [SIDE],80H JNZ MEDIA$EXIT ; If small drive, don't need density check ENDIF MOV DH,AH CALL CHKNEW ; Unload head if selecting new drive. MOV BX,OFFSET IO:DRVTAB XLAT ; Get drive select byte MOV DL,[SIDE] AND DL,[DDENBIT] OR DL,AL MOV CX,4 ; Try each density twice MOV AH,0 ; Disk may not have been changed CHKDENS: MOV AL,DL OUT DISK+4,AL ; Select disk MOV AL,0C4H ; READ ADDRESS command CALL DCOM PUSH AX IN AL,DISK+3 ; Eat last byte to reset DRQ POP AX AND AL,98H JZ HAVDENS ; Jump if no error in reading address. NOT AH XOR DL,DDENBIT ; Try other density LOOP CHKDENS MOV AH,AL JMP ERROR HAVDENS: AND DL,DDENBIT IF SIDECHK IN AL,DISK+4 AND AL,40H ; Get side bit (1=two sided) OR DL,AL ENDIF MOV [SIDE],DL OR AH,DH ENDIF ;IF LARGDRV MEDIA$EXIT: LDS BX,[PTRSAV] MOV BYTE PTR DS:[BX.TRANS],AH MOV AH,1 ;No error RET NOTRDY: MOV AX,8102H ;Return NOT READY error RET CHKNEW: MOV AH,AL ; Save disk drive number in AH. XCHG AL,[CURDRV] ; Make new drive current, AL = previous CMP AL,AH ; Changing drives? JZ RET3 ; Changing drives, unload head so the head load delay one-shot ; will fire again. Do it by seeking to same track the H bit reset. IN AL,DISK+1 ; Get current track number OUT DISK+3,AL ; Make it the track to seek to MOV AL,10H ; Seek and unload head CALL DCOM MOV AL,AH ; Restore current drive number RET3: RET GET$BPB: ; This is the equivalant of the old MAPDEV routine in MS-DOS 1.25. ; Using the media byte and/or FAT ID byte ,set the proper DPT for ; the driver. IF COMBIN TEST [SIDE],80H ; Is it 5" drive ? JZ GET8INCH ENDIF IF SMALLDRV MOV SI,OFFSET IO:SDSDRIVE MOV AL,ES:[DI] ; Get FAT ID byte TEST AL,1 ; Is it double sided? JNZ SETBPB MOV SI,OFFSET IO:SSSDRIVE JMP SHORT SETBPB ENDIF IF LARGDRV GET8INCH: TEST [SIDE],DDENBIT ; 0=single density, 1=double density MOV SI,OFFSET IO:LSDRIVE JZ SETBPB IF SIDECHK OR LARGEDS MOV SI,OFFSET IO:LDSDRIVE ENDIF IF SIDECHK TEST [SIDE],40H JNZ SETBPB ENDIF IF SIDECHK OR LARGEDS-1 MOV SI,OFFSET IO:LSSDRIVE ENDIF ENDIF ;IF LARGDRV SETBPB: LDS BX,[PTRSAV] SETBPBPT: MOV [BX.COUNT],SI MOV [BX.COUNT+2],CS XOR CX,CX ; Don't change count field MOV AH,1 ;No error RET ; Disk read function. ; ; On entry: ; AL = Disk I/O driver number ; AH = Media byte ; ES:DI = Disk transfer address ; CX = Number of sectors to transfer ; DX = Logical record number of transfer ; On exit: ; CX = number of sectors transferred. ; AH = 1 if success, 81H if fail ; AL = disk error code ; 0 = write protect error ; 2 = not ready error ; 4 = CRC error ; 6 = seek error ; 8 = sector not found ; 10 = write fault ; 12 = "data error" (any other error) SDSK$READ: IF COMBIN ADD AL,LDRVMAX ENDIF LDSK$READ: CALL SEEK ; Position head JC ERROR RDLP: PUSH CX CALL READSECT ;Perform sector read POP CX JC ERROR INC DH ;Next sector number LOOP RDLP ;Read each sector requested MOV AH,1 ;No error RET ; Disk write function. ; Registers same on entry and exit as read above. SDSK$WRIT: IF COMBIN ADD AL,LDRVMAX ENDIF LDSK$WRIT: CALL SEEK ;Position head JC ERROR WRTLP: PUSH CX CALL WRITESECT ; Perform sector write POP CX JC ERROR INC DH ; Bump sector counter LOOP WRTLP ; Write CX sectors MOV AH,1 ; No error RET ERROR: MOV BL,[CURDRV] MOV BH,0 MOV DL,-1 MOV [BX+SELECT],DL MOV [SI],DL ; Indicate we don't know where head is. MOV SI,OFFSET IO:ERRTAB GETCOD: INC DL ; Increment to next error code. LODSB TEST AH,AL ; See if error code matches disk status. JZ GETCOD ; Try another if not. MOV AL,DL ; Now we've got the code. SHL AL,1 ; Multiply by two. MOV AH,81H ; Return error code RET ERRTAB DB 40H ; Write protect error DB 80H ; Not ready error DB 8 ; CRC error DB 2 ; Seek error DB 10H ; Sector not found DB 20H ; Write fault DB 7 ; Data error ; Function: ; Seeks to proper track. ; On entry: ; Same as for disk read or write above. ; On exit: ; AH = Drive select byte ; DL = Track number ; DH = Sector number ; DI = Disk transfer address in ES ; SI = pointer to drive's track counter in DS ; CX unchanged (number of sectors) SEEK: MOV [SIDE],AH MOV BL,AL MOV BH,0 CALL CHKNEW MOV AL,[SIDE] AND AL,DDENBIT OR AL,[DRVTAB+BX] ;Get drive select byte OUT DISK+4,AL ;Select drive MOV AH,AL ; Save drive-select byte in AH. XCHG AX,DX ; AX = logical sector number. MOV DL,26 ; 26 sectors/track unless changed below TEST DH,DDENBIT ; Check for double-density. JZ HAVSECT MOV DL,16 TEST [SIDE],40H JNZ HAVSECT ; Disk is double-sided MOV DL,8 ; Disk is single sided HAVSECT: DIV DL ; AL = track, AH = sector. XCHG AX,DX ; AH has drive-select byte, DX = track & sector. INC DH ; Sectors start at one, not zero. MOV BL,[BX+TRKPT] ; Get this drive's displacement into track table. ADD BX,OFFSET IO:TRKTAB ; BX now points to track counter for this drive. MOV SI,BX MOV AL,DL ; Move new track number into AL. XCHG AL,[SI] ; Xchange current track with desired track OUT DISK+1,AL ; Inform controller chip of current track CMP AL,DL ; See if we're at the right track. JZ RET4 MOV BH,2 ; Seek retry count CMP AL,-1 ; Head position known? JNZ NOHOME ; If not, home head TRYSK: CALL HOME JC SEEKERR NOHOME: MOV AL,DL ; AL = new track number. OUT DISK+3,AL MOV AL,1CH+STPSPD ; Seek command. CALL MOVHEAD AND AL,98H ; Accept not ready, seek, & CRC error bits. JZ RET4 JS SEEKERR ; No retries if not ready DEC BH JNZ TRYSK SEEKERR: MOV AH,AL ; Put status in AH. TEST AL,80H ; See if it was a Not Ready error. STC JNZ RET4 ; Status is OK for Not Ready error. MOV AH,2 ; Everything else is seek error. RET4: RET SETUP: MOV BL,DH ; Move sector number to BL to play with TEST AH,DDENBIT ; Check for double density. JZ CHECK26 ; Not DD. MOV AL,AH ; Select front side of disk. OUT DISK+4,AL CMP BL,8 ; See if legal DD sector number. JBE PUTSEC ; Jump if ok. TEST [SIDE],40H ; Get media byte to see if double sided JZ STEP ; Must step SUB BL,8 ; Find true sector for back side. CMP BL,8 ; See if ok now. JA STEP ; Have to step if still too big. MOV AL,AH ; Move drive select byte into AL. OR AL,BACKBIT ; Select back side. OUT DISK+4,AL JMP SHORT PUTSEC CHECK26: CMP BL,26 ; See if legal large SD/SS sector. JBE PUTSEC ; Jump if ok. STEP: INC DL ; Increment track number. MOV AL,58H ; Step in with update. CALL DCOM INC BYTE PTR CS:[SI] ; Increment the track pointer. MOV DH,1 ; After step, do first sector. MOV BL,DH ; Fix temporary sector number also. PUTSEC: MOV AL,BL ; Output sector number to controller. OUT DISK+2,AL RET5: RET READSECT: CALL SETUP MOV BL,10 PUSH DX MOV DX,DISK+3 RDAGN: MOV AL,READCOM CLI OUT DISK,AL MOV BP,DI JMP SHORT RLOOPENTRY RLOOP: STOSB RLOOPENTRY: IF SCP IN AL,DISK+5 SHR AL,1 IN AL,DX JNC RLOOP ENDIF IF TARBELL IN AL,DISK+4 SHL AL,1 IN AL,DX JC RLOOP ENDIF STI ; Interrupts OK now CALL GETSTAT AND AL,9CH JZ RDPOP MOV DI,BP MOV BH,AL ; Save error status for report DEC BL JNZ RDAGN MOV AH,BH ; Put error status in AH. STC RDPOP: POP DX IF TARBELL FORCINT: MOV AL,0D0H ; Tarbell controllers need this force interrupt OUT DISK,AL ; so that Type I status is always available MOV AL,DLYTIM ; at the 1793 status port so we can find out INTDLY: ; if the head is loaded. SCP controllers have DEC AL ; head load status available at the DISK+4 JNZ INTDLY ; status port. ENDIF RET WRITESECT: CALL SETUP MOV BL,10 XCHG SI,DI PUSH ES POP DS ;DS must have transfer segment ASSUME DS:NOTHING PUSH DX MOV DX,DISK+3 WRTAGN: MOV AL,WRITECOM CLI OUT DISK,AL MOV BP,SI WRLOOP: IF SCP IN AL,DISK+5 SHR AL,1 LODSB OUT DX,AL JNC WRLOOP ENDIF IF TARBELL IN AL,DISK+4 SHL AL,1 LODSB OUT DX,AL JC WRLOOP ENDIF STI DEC SI CALL GETSTAT AND AL,0FCH JZ WRPOP MOV SI,BP MOV BH,AL DEC BL JNZ WRTAGN MOV AH,BH ; Error status to AH. STC WRPOP: POP DX PUSH CS POP DS ;Restore local DS ASSUME DS:IO XCHG SI,DI IF TARBELL JMP FORCINT ELSE RET ENDIF HOME: MOV BL,3 TRYHOM: MOV AL,0CH+STPSPD CALL DCOM AND AL,98H JZ RET6 JS HOMERR ; No retries if not ready MOV AL,58H+STPSPD ; Step in with update CALL DCOM DEC BL JNZ TRYHOM HOMERR: STC RET6: RET MOVHEAD: DCOM: OUT DISK,AL PUSH AX AAM ; Delay 10 microseconds POP AX GETSTAT: IN AL,DISK+4 TEST AL,DONEBIT IF SCP JZ GETSTAT ENDIF IF TARBELL JNZ GETSTAT ENDIF IN AL,DISK RET IF LARGDRV LDSK$INIT: MOV AL,LDRVMAX MOV SI,OFFSET IO:LINITTAB ENDIF SETDRV: LDS BX,[PTRSAV] MOV [BX.MEDIA],AL JMP SETBPBPT IF SMALLDRV SDSK$INIT: MOV AL,SDRVMAX MOV SI,OFFSET IO:SINITTAB JMP SETDRV ENDIF CODE ENDS END