TITLE 'FINDBAD Ver 1.0 - Universal Version for CP/M-86'
;
; FINDBAD.A86 Ver. 1.0
;
; Modified for CP/M-86 from FINDBAD52.ASM of 02/16/82
;
;FINDBAD will find all bad blocks on a disk and build a file
;named [UNUSED].BAD to allocate them, thus "locking out" the
;bad blocks so CP/M-86 will not use them.
;
;Originally written by Gene Cotton, published in "Interface
;Age", September 1980 issue, page 80.
;
; SYSTST and BADUSR options:
;
; Many double-density disk systems have single-density system
;tracks. If this is true with your system, you can change the
;program to skip the system tracks, without re-assembling it.
;To do this, set the byte at 103H to a 0 if you don't want the
;system tracks tested, otherwise leave it 0. This is also
;necessary if you have a "blocked" disk system; that is, when
;the same physical disk is seperated into logical disks by use
;of the SYSTRK word in the disk parameter block.
;
; If you are a CP/M-86 1.x user, you may assign the user
;number where [UNUSED.BAD] will be created by changing the
;byte at 104H to the desired user number. If you want it
;in the default user, then leave it 0FFH.
;
;
;-------------------------------------------------------------
;NOTE: If you want to update this program, make sure you have
;the latest version first. After adding your changes, please
;modem a copy of the new file to CP/M-NET in Simi Valley,
;California - phone (805) 527-9321 (50, 110, 300, 450 or 600
;baud). Use the filename FNDBDA86.NEW. (K. Smith)
;
;Modifications/updates:
;
;02/03/82 Changed to CP/M-86 compatibility with the help of
; DR's XLT86 utility, made changes for goofs made by
; XLT86, removed ANYTHING to do with CP/M-80 Ver.
; 1.4. (K. Smith, CP/M-Net 'SYSOP')
;
; Note: You must assemble with ASM86.CMD as follows:
;
; ASM86 FINDBAD<cr>
;
; Then create a '.CMD' as,
;
; GENCMD FINDBAD 8080<cr>
;
;04/10/81 Changed extent DB from -1 to 0FFH so program can be
; assembled by ASM. Added BADUSR info to instructions
; for altering with DDT. (KBP)
;
;04/09/81 Changed sign-on message, added control-c abort test,
; added '*' to console once each track (RGF)
;
;04/07/81 Re-wrote to add the following features:
; 1) "universal" operation
; 2) DDT-changeable "SYSTRK" boolean (see above)
; 3) Report to console when bad blocks are detected
; 4) Changed the method of printing the number of
; bad blocks found (at end of run)...the old
; method used too much code, and was too cum-
; bersome.
; 5) Made several cosmetic changes
;
; Ron Fowler
; Westland, Mich
;
;03/23/81 Set equates to standard drive and not double-sided. (KBP)
;
;03/01/81 Corrected error for a Horizon with double sided drive.
; This uses 32k extents, which code did not take into account.
; (Bob Clyne)
;
;02/05/81 Merged 2/2/81 and 1/24/81 changes, which were done
; independently by Clyne and Mack. (KBP)
;
;02/02/81 Added equates for North Star Horizon - 5.25" drives,
; double density, single and double sided. (Bob Clyne)
;
;01/24/81 Added equates for Jade DD disk controller
; (Pete H. Mack)
;
;01/19/81 Added equates for Icom Microfloppy 5.25" drives.
; (Eddie Currie)
;
;01/05/81 Added equates for Heath H-17 5.25" drives.
; (Ben Goldfarb)
;
;12/08/80 Added equates for National Multiplex D3S/D4S
; double-density board in various formats.
; (David Fiedler)
;
;09/22/80 Added equates for Morrow Disk Jockey 2D/SS, 256,
; 512 and 1024-byte sector options. Fix 'S2' update
; flag for larger max number of extents. Cleaned up
; file. (Ben Bronson and KBP)
;
;09/14/80 Corrected DGROUP equate for MMDBL. Added new routine
; to correct for IMDOS group allocation. Corrected
; error in instructions for using TEST routine.
; (CHS) (AJ) (KBP) - (a group effort)
;
;09/08/80 Fixed several errors in Al Jewer's mods. Changed
; return to CP/M to warm boot so bitmap in memory will
; be properly updated. Added conditional assembly for
; testing program. (KBP)
;
;09/02/80 Added IMDOS double-density equates & modified for
; more then 256 blocks per disk. (Al Jewer)
;
;09/01/80 Changed equates so that parameters are automatically
; set for each disk system conditional assembly (KBP)
;
;08/31/80 Add conditional assembly for Digital Microsystems FDC3
; controller board in double-density format and fix to
; do 256 blocks in one register. (Thomas V. Churbuck)
;
;08/31/80 Correct MAXB equate - MAXB must include the directory
; blocks as well as the data blocks. Fix to make sure
; any [UNUSED].BAD file is erased before data area is
; checked. (KBP)
;
;08/30/80 Added conditional assembly for Micromation
; double-density format. (Charles H. Strom)
;
;08/27/80 Fix missing conditional assembly in FINDB routine.
; Put version number in sign-on message. (KBP)
;
;08/26/80 Modified by Keith Petersen, W8SDZ, to:
; (1) Add conditional assembly for 1k/2k groups
; (2) Add conditional assembly for standard drives
; and Micropolis MOD II
; (3) Make compatible with CP/M-2.x
; (4) Remove unneeded code to check for drive name
; (CP/M does it for you and returns it in the FCB)
; (5) Changed to open additional extents as needed for
; overflow, instead of additional files
; (6) Add conditional assembly for system tracks check
; (some double-density disks have single-density
; system tracks which cannot be read by this program)
; (7) Increased stack area (some systems use more than
; others).
;
;08/06/80 Added comments and crunched some code.
; KELLY SMITH. 805-527-9321 (Modem, 300 Baud)
; 805-527-0518 (Verbal)
;
;
; Using the Program
;
; Before using this program to "reclaim" a diskette, it is
;recommended that the diskette be reformatted. If this is not
;possible, at least assure yourself that any existing files
;on the diskette do not contain unreadable sectors. If you
;have changed disks since the last warm-boot, you must warm-
;boot again before running this program.
;
; To use the program, insert both the disk containing the
;program FINDBAD.CMD and the diskette to be checked into the
;disk drives. It is possible that the diskette containing the
;program is the one to be checked. Assume that the program is
;on drive "A" and the suspected bad disk is on drive "B". In
;response to the CP/M prompt "A>", type in FINDBAD B:. This
;will load the file FINDBAD.CMD from drive "A" and test the
;diskette on drive "B" for unreadable sectors. The only
;allowable parameter after the program name is a drive
;specification (of the form " N:") for up to four (A to D)
;disk drives. If no drive is specified, the currently logged
;in drive is assumed to contain the diskette to check.
;
; The program first checks the CP/M System tracks (0 and 1),
;and any errors here prohibit the disk from being used on
;drive "A", since all "warm boots" occur using the system
;tracks from the "A" drive.
;
; The program next checks the first two data blocks (groups
;to some of us) containing the directory of the diskette. If
;errors occur here, the program terminates and control
;returns to CP/M (no other data blocks are checked since
;errors in the directory render the disk useless).
;
; Finally, all the remaining data blocks are checked. Any
;sectors which are unreadable cause the data block which
;contains them to be stored temporarily as a "bad block". At
;the end of this phase, the message "XX bad blocks found" is
;displayed (where XX is replaced by the number of bad blocks,
;or "No" if no read errors occur). If bad blocks occur, the
;filname [UNUSED].BAD is created, the list of "bad blocks" is
;placed in the allocation map of the directory entry for
;[UNUSED].BAD, and the file is closed. Note, that when the
;number of "bad blocks" exceeds 16, the program will open
;additional extents as required to hold the overflow. I
;suggest that if the diskette has more than 32 "bad blocks",
;perhaps it should be sent to the "big disk drive in the sky"
;for the rest it deserves.
;
; The nifty part of all this is that if any "bad blocks" do
;occur, they are allocated to [UNUSED].BAD and no longer will
;be available to CP/M-86 for future allocation... bad sectors
;are logically locked out on the diskette
;
;
; Using the TEST conditional assembly
;
;A conditional assembly has been added to allow testing this
;program to make sure it is reading all sectors on your disk
;that are accessible to CP/M. The program reads the disk on a
;block by block basis, so it is necessary to first determine the
;number of blocks present. To start, we must know the number of
;sectors/block (8 sectors/block for standard IBM single density
;format). If this value is not known, it can easily be
;determined by saving one page in a test file and interrogating
;using the STAT command:
;
;For standard single-density STAT will report this file as being
;1k. The file size reported (in bytes) is the size of a block.
;This value divided by 128 bytes/sector (the standard CP/M
;sector size) will give sectors/block. For our IBM single
;density example, we have:
;
; (1024 bytes/block) / (128 bytes/sector) = 8 sectors/block.
;
;We can now calculate blocks/track (assuming we know the number
;sectors/track). In our example:
;
; (26 sectors/track) / (8 sectors/block) = 3.25 blocks/track
;
;Now armed with the total number of data tracks (75 in our IBM
;single density example), we get total blocks accessible:
;
; 75 (tracks/disk) x (3.25 blocks/track) = 243.75 blocks/disk
;
;CP/M cannot access a fractional block, so we round down (to 243
;blocks in our example). Now multiplying total blocks by
;sectors/block results in total sectors as should be reported
;when TEST is set TRUE and a good disk is read. For our example,
;this value is 1944 sectors.
;
;Finally, note that if SYSTEM is set TRUE, the sectors present
;on the first two tracks must be added in as well. In the
;previous example, this results in 1944 + 52 = 1996 sectors
;reported by the TEST conditional.
;
;Run the program on a KNOWN-GOOD disk. It should report that it
;has read the correct number of sectors. The test conditional
;assembly should then be set FALSE and the program re-assembled.
;The test routines cannot be left in because this program does
;not read all the sectors in a block that is found to be bad and
;thus will report an inaccurate number of sectors read.
;
;
;Define TRUE and FALSE
;
FALSE EQU 0
TRUE EQU NOT FALSE
;
;******************************************************************
;
;Conditional assembly switch for testing this program
;(for initial testing phase only - see remarks above)
;
TEST EQU FALSE ;TRUE FOR TESTING ONLY
;
;******************************************************************
;
;System equates
;
BASE EQU 0 ;STANDARD CP/M BASE ADDRESS
FCB EQU BASE+5CH ;CP/M DEFAULT FCB LOCATION
;
;Define ASCII characters used
;
CR EQU 0DH ;CARRIAGE RETURN CHARACTER
LF EQU 0AH ;LINE FEED CHARACTER
TAB EQU 09H ;TAB CHARACTER
BEL EQU 07H ;BELL CHARACTER
;
M EQU Byte Ptr 0[BX]
;
cseg
ORG BASE+100H
;
JMPS START ;JMP AROUND OPTION BYTES
;
;If you want the system tracks tested, then put a 1 here, otherwise 0.
;
SYSTST DB 1 ;0 IF NO SYS TRACKS, OTHERWISE 1
;
; Change this byte to the user number you want [UNUSED].BAD
;to reside in. If you want it in the default user, then leave
;it 0FFH.
;
BADUSR DB 0FFH ;USER # WHERE [UNUSED.BAD] GOES
;0FFH = DEFAULT USER
;
START: CALL START2 ;GO PRINT SIGNON
;
DB CR,LF,'FINDBAD - Ver 1.0, Bad Sector Lockout Utility'
DB CR,LF
DB '------- Universal Version for CP/M-86 -------'
DB CR,LF,CR,LF,'Type CTL-C to abort',CR,LF,'$'
;
START2: POP DX ;GET MSG ADRS
MOV CL,9 ;bdos PRINT BUFFER function
INT 224 ;bdos PRINT SIGN-ON MSG
CALL SETUP ;SET BIOS ENTRY, AND CHECK DRIVE
CALL FINDB ;ESTABLISH ALL BAD BLOCKS
JZ NOBAD ;SAY NO BAD BLOCKS, IF SO
CALL SETDM ;FIX DM BYTES IN FCB
;
NOBAD: CALL CRLF
MOV AL,TAB
CALL DISPLAY
MOV DX,(Offset NOMSG) ;POINT FIRST TO 'NO'
MOV BX,Word Ptr BADBKS ;PICK UP # BAD BLOCKS
MOV AL,BH ;CHECK FOR ZERO
OR AL,BL
JZ PMSG1 ;JUMP IF NONE
CALL DECOUT ;OOPS..HAD SOME BAD ONES, REPORT
JMPS PMSG2
;
PMSG1: MOV CL,9 ;bdos PRINT BUFFER function
INT 224
;
PMSG2: MOV DX,(Offset ENDMSG) ;REST OF EXIT MESSAGE
;
PMSG: MOV CL,9
INT 224
;
MOV CL,0 ;EXIT TO CP/M WARM BOOT
MOV DL,0
INT 224
;
;Get actual address of BIOS routines
;
SETUP EQU $ ;Check for drive specification
;
GDRIV: MOV AL,Byte Ptr .FCB ;GET DRIVE NAME
MOV CL,AL
OR AL,AL ;ZERO?
JNZ GD2 ;IF NOT,THEN GO SPECIFY DRIVE
MOV CL,25 ;GET LOGGED-IN DRIVE
INT 224
INC AL ;MAKE 1-RELATIVE
MOV CL,AL
;
GD2: CMP AL,15+1 ;CHECK FOR HIGHEST DRIVE NUMBER
JNAE GD3
JMP SELERR ;SELECT ERROR
;
GD3: DEC CL ;BACK OFF FOR CP/M
PUSH CX ;SAVE DISK SELECTION
MOV DL,CL ;ALIGN FOR BDOS
MOV CL,14 ;SELECT DISK function
INT 224
POP CX ;GET BACK DISK NUMBER
;
SETDSK: MOV Byte Ptr FUNC,9 ;bios select disk function
MOV Word Ptr BIOS_DESC,CX ;pass disk number to bios descriptor
MOV Word Ptr BIOS_DESC+2,0 ;fill remaining descriptor (DX) zero
MOV DX,(Offset FUNC) ;point to function parameter block
MOV CL,50 ;direct bios call
INT 224 ;do it, to it...
;
MOV DL,ES: M ;GET SECTOR TABLE PNTR
LAHF
INC BX
SAHF
MOV DH,ES: M
LAHF
INC BX
SAHF
XCHG BX,DX
MOV Word Ptr SECTBL,BX ;STORE IT AWAY
MOV BX,8 ;OFFSET TO DPB POINTER
LAHF
ADD BX,DX
RCR SI,1
SAHF
RCL SI,1
MOV AL,ES: M ;PICK UP DPB POINTER
LAHF ; TO USE
INC BX
SAHF
MOV BH,ES: M ; AS PARAMETER
MOV BL,AL ; TO LOGIT
;
DOLOG: CALL LOGIT ;LOG IN DRIVE, GET DISK PARMS
CALL GETDIR ;CALCULATE DIRECTORY INFORMATION
;
;
HOMDSK: MOV Byte Ptr FUNC,8 ;bios HOME DISK function
MOV Word Ptr BIOS_DESC,0 ;pass 'nothing' number to bios descriptor
MOV Word Ptr BIOS_DESC+2,0 ;fill remaining descriptor (DX) zero
MOV DX,(Offset FUNC) ;point to function parameter block
MOV CL,50 ;direct bios call
INT 224 ;do it, to it...
;
;Now set the required user number
;
MOV AL,Byte Ptr BADUSR ;GET THE USER NUMBER
CMP AL,0FFH ;IF IT IS 0FFH, THEN RETURN
JNZ L_6
RET
L_6:
MOV CL,32 ;GET/SET USER CODE
INT 224
RET
;
;Look for bad blocks
;
FINDB: MOV AL,Byte Ptr SYSTST
OR AL,AL
JZ DODIR ;JUMP IF NO SYS TRACKS TO BE TESTED
CALL CHKSYS ;CHECK FOR BAD BLOCKS ON TRACK 0 AND 1
;
DODIR: CALL CHKDIR ;CHECK FOR BAD BLOCKS IN DIRECTORY
CALL TELL1
;
DB CR,LF,'Testing data area...',CR,LF,'$'
;
TELL1: POP DX
MOV CL,9 ;bdos PRINT STRING function
INT 224
CALL ERAB ;ERASE ANY [UNUSED].BAD FILE
MOV BX,Word Ptr DIRBKS ;START AT FIRST DATA BLOCK
MOV CX,BX ;PUT INTO [CX]
;
FINDBA: CALL READB ;READ THE BLOCK
JZ L_7
CALL SETBD ;IF BAD, ADD BLOCK TO LIST
L_7:
LAHF ;BUMP TO NEXT BLOCK
INC CX
SAHF
MOV BX,Word Ptr DSM
MOV DX,CX ;SET UP FOR (MAXGRP - CURGRP)
SBB BX,DX ;DO SUBTRACT: (MAXGRP - CURGRP)
JNB FINDBA ;UNTIL CURGRP>MAXGRP
CALL CRLF
MOV BX,Word Ptr DMCNT ;GET NUMBER OF BAD SECTORS
MOV AL,BH
OR AL,BL ;SET ZERO FLAG, IF NO BAD BLOCKS
RET ;RETURN FROM "FINDB"
;
;Check system tracks, notify user if bad, but continue
;
CHKSYS: CALL CHSY1 ;bdos PRINT MESSAGE
;
DB CR,LF,'Testing system tracks...',CR,LF,'$'
;
CHSY1: POP DX
MOV CL,9 ;bdos PRINT STRING function
INT 224
MOV BX,0 ;SET TRACK 0, SECTOR 1
MOV Word Ptr TRACK,BX
LAHF
INC BX
SAHF
MOV Word Ptr SECTOR,BX
;
CHKSY1: CALL READS ;READ A SECTOR
JNZ SYSERR ;NOTIFY, IF BAD BLOCKS HERE
MOV BX,Word Ptr SYSTRK ;SET UP (TRACK-SYSTRK)
XCHG BX,DX
MOV BX,Word Ptr TRACK
SBB BX,DX ;DO THE SUBTRACT
JB CHKSY1 ;LOOP WHILE TRACK < SYSTRK
RET ;RETURN FROM "CHKSYS"
;
SYSERR: MOV DX,(Offset ERMSG5) ;SAY NO GO, AND BAIL OUT
MOV CL,9 ;bdos PRINT BUFFER function
INT 224
RET ;RETURN FROM "SYSERR"
;
;Check for bad blocks in directory area
;
CHKDIR: CALL CHKD1
;
DB CR,LF,'Testing directory area...',CR,LF,'$'
;
CHKD1: POP DX
MOV CL,9 ;bdos PRINT STRING function
INT 224
MOV CX,0 ;START AT BLOCK 0
;
CHKDI1: CALL READB ;READ A BLOCK
JZ L_8
JMP ERROR6 ;IF BAD, INDICATE ERROR IN DIRECTORY AREA
L_8:
LAHF ;BUMP FOR NEXT BLOCK
INC
CX
SAHF
MOV BX,Word Ptr DIRBKS ;SET UP (CURGRP - DIRBKS)
LAHF ;MAKE 0-RELATIVE
DEC BX
SAHF
MOV DX,CX
SBB BX,DX ;DO THE SUBTRACT
JNB CHKDI1 ;LOOP UNTIL CURGRP > DIRGRP
RET ;RETURN FROM "CHKDIR"
;
;Read all sectors in block, and return zero flag set if none bad
;
READB: CALL CNVRTB ;CONVERT TO TRACK/SECTOR IN H&L REGS.
MOV AL,Byte Ptr BLM
INC AL ;NUMBER OF SECTORS/BLOCK
MOV DH,AL ; IN D REG
;
READBA: PUSH DX
CALL READS ;READ SKEWED SECTOR
POP DX
JZ L_9
RET ;ERROR IF NOT ZERO...
L_9:
DEC DH ;DEBUMP SECTOR/BLOCK
JNZ READBA ;DO NEXT, IF NOT FINISHED
RET ;RETURN FROM "READBA"
;
;Convert block number to track and skewed sector number
;
CNVRTB: PUSH CX ;SAVE CURRENT GROUP
MOV BX,CX ;NEED IT IN [BX], FOR EASY SHIFTING
MOV AL,Byte Ptr BSH ;DPB VALUE THAT TELLS HOW TO
;
SHIFT: SHL BX,1 ; SHIFT GROUP NUMBER TO GET
DEC AL ; DISK-DATA-AREA RELATIVE
JNZ SHIFT ; SECTOR NUMBER
XCHG BX,DX ;REL SECTOR # INTO DE
MOV BX,Word Ptr SPT ;SECTORS PER TRACK FROM DPB
NOT BX ;1ST 1'S COMPLEMENT...
INC BX ;...THEN 2'S COMPLEMENT
XCHG BX,DX
MOV CX,0 ;INITIALIZE QUOTIENT
;
;Divide by number of sectors
; quotient = track
; mod = sector
;
DIVLP: LAHF ;DIRTY DIVISION
INC CX
SAHF
LAHF
ADD BX,DX
RCR SI,1
SAHF
RCL SI,1
JB DIVLP
LAHF ;FIXUP LAST
DEC CX
SAHF
XCHG BX,DX
MOV BX,Word Ptr SPT
LAHF
ADD BX,DX
SAHF
LAHF
INC BX
SAHF
MOV Word Ptr SECTOR,BX ;NOW HAVE LOGICAL SECTOR
MOV BX,Word Ptr SYSTRK ;BUT BEFORE WE HAVE TRACK #,
LAHF ; WE HAVE TO ADD SYS TRACK OFFSET
ADD BX,CX
RCR SI,1
SAHF
RCL SI,1
MOV Word Ptr TRACK,BX
POP CX ;THIS WAS OUR GROUP NUMBER
RET
;
;READS reads a logical sector (if it can)
;and returns zero flag set if no error.
;
READS: PUSH CX ;SAVE THE GROUP NUMBER
;
CALL LTOP ;CONVERT LOGICAL TO PHYSICAL
MOV BX,Word Ptr PHYSEC ;GET PHYSICAL SECTOR
MOV CX,BX ;INTO [CX]
;
SETSEC: MOV BYTE PTR FUNC,11 ;bios SET SECTOR function
MOV WORD PTR BIOS_DESC,CX ;pass sector number to bios descriptor
MOV WORD PTR BIOS_DESC+2,0 ;fill remaining descriptor (DX) zero
MOV DX,(Offset FUNC) ;point to function parameter block
MOV CL,50 ;direct bios call
INT 224 ;do it, to it...
;
MOV BX,Word Ptr TRACK ;NOW SET THE TRACK
MOV CX,BX ;CP/M WANTS IT IN [CX]
;
SETTRK: MOV BYTE PTR FUNC,10 ;bios SELECT TRACK function
MOV WORD PTR BIOS_DESC,CX ;pass track number to bios descriptor
MOV WORD PTR BIOS_DESC+2,0 ;fill remaining descriptor (DX) zero
MOV DX,(Offset FUNC) ;point to function parameter block
MOV CL,50 ;direct bios call
INT 224 ;do it, to it...
;
;Now do the sector read
;
DREAD: MOV BYTE PTR FUNC,13 ;bios READ SECTOR function
MOV WORD PTR BIOS_DESC,0 ;pass 'nothing' number to bios descriptor
MOV WORD PTR BIOS_DESC+2,0 ;fill remaining descriptor (DX) zero
MOV DX,(Offset FUNC) ;point to function parameter block
MOV CL,50 ;direct bios call
INT 224 ;do it, to it...
;
OR AL,AL ;SET FLAGS
LAHF ;SAVE ERROR FLAG
XCHG AL,AH
PUSH AX
MOV BX,Word Ptr SECTOR ;GET LOGICAL SECTOR #
LAHF ;WE WANT TO INCREMENT TO NEXT
INC BX
SAHF
XCHG BX,DX ;BUT FIRST...CHECK OVERFLOW
MOV BX,Word Ptr SPT ; BY DOING (SECPERTRK-SECTOR)
SBB BX,DX ;DO THE SUBTRACTION
XCHG BX,DX
JNB NOOVF ;JUMP IF NOT SECTOR>SECPERTRK
;
;Sector overflow...bump track number, reset sector
;
MOV BX,Word Ptr TRACK
LAHF
INC BX
SAHF
MOV Word Ptr TRACK,BX
MOV AL,'*' ;TELL CONSOLE ANOTHER TRACK DONE
CALL DISPLAY
CALL STOP ;SEE IF CONSOLE WANTS TO QUIT
MOV BX,1 ;NEW SECTOR NUMBER ON NEXT TRACK
;
NOOVF: MOV Word Ptr SECTOR,BX ;PUT SECTOR AWAY
POP AX ;GET BACK ERROR FLAGS
XCHG AL,AH
SAHF
POP CX ;RESTORE GROUP NUMBER
RET
;
;Convert logical sector # to physical
;
LTOP: MOV BX,Word Ptr SECTBL ;SET UP PARAMETERS
XCHG BX,DX ; FOR CALL TO SECTRAN
MOV BX,Word Ptr SECTOR
MOV CX,BX
DEC CX ;ALWAYS CALL SECTRAN W/ZERO-REL SEC #
;
SECT1: CALL SECTRN ;DO THE SECTOR TRANSLATION
MOV AL,Byte Ptr SPT+1 ;CHECK IF BIG TRACKS
OR AL,AL ;SET FLAGS (TRACKS > 256 SECTORS)
JNZ LTOP1 ;NO SO SKIP
MOV BH,AL ;ZERO OUT UPPER 8 BITS
;
LTOP1: MOV Word Ptr PHYSEC,BX ;PUT AWAY PHYSICAL SECTOR
RET
;
;Sector translation vector
;
SECTRN: MOV BYTE PTR FUNC,16 ;bios SECTOR TRANSLATE function
MOV WORD PTR BIOS_DESC,CX ;pass sector to bios descriptor
MOV WORD PTR BIOS_DESC+2,DX ;translate table offset
MOV DX,(Offset FUNC) ;point to function parameter block
MOV CL,50 ;direct bios call
INT 224 ;do it, to it...
ret ;return from 'sectrn'
;
;Put bad block in bad block list
;
SETBD: PUSH CX
CALL SETBD1
DB CR,LF,BEL,'Bad block: $'
;
SETBD1: POP DX ;RETRIEVE ARG
MOV CL,9 ;bdos PRINT STRING
INT 224
POP CX ;GET BACK BLOCK NUMBER
MOV AL,CH
CALL HEXO ;bdos PRINT IN HEX
MOV AL,CL
CALL HEXO
CALL CRLF
MOV BX,Word Ptr DMCNT ;GET NUMBER OF SECTORS
MOV AL,Byte Ptr BLM ;GET BLOCK SHIFT VALUE
INC AL ;MAKES SECTOR/GROUP VALUE
MOV DL,AL ;WE WANT 16 BITS
MOV DH,0
ADD BX,DX ;BUMP BY NUMBER IN THIS BLOCK
MOV Word Ptr DMCNT,BX ;UPDATE NUMBER OF SECTORS
MOV BX,Word Ptr BADBKS ;INCREMENT NUMBER OF BAD BLOCKS
INC BX
MOV Word Ptr BADBKS,BX
MOV BX,Word Ptr DMPTR ;GET POINTER INTO DM
MOV M,CL ;...AND PUT BAD BLOCK NUMBER
INC BX ;BUMP TO NEXT AVAILABLE EXTENT
MOV AL,Byte Ptr DSM+1 ;CHECK IF 8 OR 16 BIT BLOCK SIZE
OR AL,AL
JZ SMGRP ;JUMP IF 8 BIT BLOCKS
MOV M,CH ;ELSE STORE HI BYTE OF BLOCK #
LAHF ;AND BUMP POINTER
INC BX
SAHF
;
SMGRP: MOV Word Ptr DMPTR,BX ;SAVE DM POINTER, FOR NEXT TIME THROUGH HERE
RET ;RETURN FROM "SETBD"
;
;Eliminate any previous [UNUSED].BAD entries
;
ERAB: MOV DX,(Offset BFCB) ;POINT TO BAD FCB
MOV CL,19 ;bdos DELETE FILE function
INT 224
RET
;
;Create [UNUSED].BAD file entry
;
OPENB: MOV DX,(Offset BFCB) ;POINT TO BAD FCB
MOV CL,22 ;bdos MAKE FILE function
INT 224
CMP AL,0FFH ;CHECK FOR OPEN ERROR
JZ L_10
RET ;RETURN FROM "OPENB", IF NO ERROR
L_10:
JMP ERROR7 ;BAIL OUT...CAN'T CREATE [UNUSED].BAD
;
CLOSEB: XOR AL,AL
MOV AL,Byte Ptr BFCB+14 ;GET CP/M 'S2' BYTE
AND AL,1FH ;ZERO UPDATE FLAGS
MOV Byte Ptr BFCB+14,AL ;RESTORE IT TO OUR FCB
MOV DX,(Offset BFCB) ;FCB FOR [UNUSED].BAD
MOV CL,16 ;bdos CLOSE FILE function
INT 224
RET ;RETURN FROM "CLOSEB"
;
;Move bad area DM to BFCB
;
SETDM: MOV BX,(Offset DM) ;GET DM
MOV Word Ptr DMPTR,BX ;SAVE AS NEW POINTER
MOV AL,Byte Ptr EXM ;GET THE EXTENT SHIFT FACTOR
MOV CL,0 ;INIT BIT COUNT
CALL COLECT ;GET SHIFT VALUE
MOV BX,128 ;STARTING EXTENT SIZE
MOV AL,CL ;FIRST SEE IF ANY SHIFTS TO DO
OR AL,AL
JZ NOSHFT ;JUMP IF NONE
;
ESHFT: SHL BX,1 ;SHIFT
DEC AL ;BUMP
JNZ ESHFT ;LOOP
;
NOSHFT: PUSH BX ;SAVE THIS, IT IS RECORDS PER EXTENT
MOV AL,Byte Ptr BSH ;GET BLOCK SHIFT
MOV CH,AL
;
BSHFT: CLC ;CLEAR CARRY FLAG
RCR BX,1 ;SHIFT RIGHT
DEC CH
JNZ BSHFT ;TO GET BLOCKS PER EXTENT
MOV AL,BL ;IT'S IN [BL] (CAN'T BE >16)
MOV Byte Ptr BLKEXT,AL ;SET BLOCK EXTENT, WILL NEED THIS LATER
POP BX ;GET BACK REC/EXT
;
SET1: XCHG BX,DX ;NOW HAVE REC/EXTENT IN [DX]
MOV BX,Word Ptr DMCNT ;COUNT OF BAD SECTORS
;
SETDMO: PUSH BX ;SET FLAGS ON (DMCNT-BADCNT)
SBB BX,DX ;HAVE TO SUBTRACT FIRST
MOV CX,BX ;SAVE RESULT IN [CX]
POP BX ;THIS POP MAKES IT COMPARE ONLY
JB SETDME ;JUMP IF LESS THAN 1 EXTENT WORTH
MOV AL,CH
OR AL,CL ;TEST IF SUBTRACT WAS 0
JZ EVENEX ;EXTENT IS EXACTLY FILLED (SPL CASE)
MOV BX,CX ;RESTORE RESULT TO [BX]
PUSH BX ;SAVE TOTAL
PUSH DX ;AND SECTORS/EXTENT
XCHG BX,DX
CALL SETDME ;PUT AWAY ONE EXTENT
XCHG BX,DX
MOV Word Ptr DMPTR,BX ;PUT BACK NEW DM POINTER
POP DX ;GET BACK SECTORS/EXTENT
POP BX ;AND COUNT OF BAD SECTORS
JMPS SETDMO ;AND LOOP
;
;Handle the special case of a file that ends on an extent
;boundary. CP/M requires that such a file have a succeeding
;empty extent in order for the BDOS to properly access the file.
;
EVENEX: XCHG BX,DX ;FIRST SET EXTENT W/BAD BLOCKS
CALL SETDME
XCHG BX,DX
MOV Word Ptr DMPTR,BX
MOV BX,0 ;NOW SET ONE WITH NO DATA BLOCKS
;
;Fill in an extent's worth of bad sectors/block numbers.
;Also fill in the extent number in the FCB.
;
SETDME: PUSH BX ;SAVE RECORD COUNT
MOV AL,Byte Ptr EXTNUM ;UPDATE EXTENT BYTE
INC AL
MOV Byte Ptr EXTNUM,AL ;SAVE FOR LATER
MOV Byte Ptr BFCB+12,AL ; AND PUT IN FCB
CALL OPENB ;OPEN THIS EXTENT
POP BX ;RETRIEVE REC COUNT
;
;Divide record count by 128 to get the number
;of logical extents to put in the EX field
;
MOV CH,0 ;INIT QUOTIENT
MOV DX,-128 ;-DIVISOR
MOV AL,BH ;TEST FOR SPL CASE
OR AL,BL ; OF NO RECORDS
JZ SKIP
;
DIVLOP: ADD BX,DX ;SUBTRACT
INC CH ;BUMP QUOTIENT
JB DIVLOP
MOV DX,128 ;FIX UP OVERSHOOT
ADD BX,DX
DEC CH
MOV AL,BH ;TEST FOR WRAPAROUND
OR AL,BL
JNZ SKIP
MOV BL,80H ;RECORD LENGTH
DEC CH
;
SKIP: MOV AL,Byte Ptr EXTNUM ;NOW FIX UP EXTENT NUM
ADD AL,CH
MOV Byte Ptr EXTNUM,AL
MOV Byte Ptr BFCB+12,AL
MOV AL,BL ;MOD IS RECORD COUNT
MOV Byte Ptr BFCB+15,AL ;THAT GOES IN RC BYTE
;
MOVDM: MOV AL,Byte Ptr BLKEXT ;GET BLOCKS PER EXTENT
MOV CH,AL ;INTO B
;
SETD1: MOV BX,Word Ptr DMPTR ;POINT TO BAD ALLOCATION MAP
XCHG BX,DX
MOV BX,(Offset BFCB)+16 ;DISK ALLOC MAP IN FCB
;
SETDML: MOV SI,DX
MOV AL,[SI]
MOV M,AL
INC BX
INC DX
;
;Now see if 16 bit groups...if so,
;we have to move another byte
;
MOV AL,Byte Ptr DSM+1 ;THIS TELLS US
OR AL,AL
JZ BUMP1 ;IF ZERO, THEN NOT
MOV SI,DX ;IS 16 BITS, SO DO ANOTHER
MOV AL,[SI]
MOV M,AL
LAHF
INC BX
SAHF
LAHF
INC DX
SAHF
;
BUMP1: DEC CH ;COUNT DOWN
JNZ SETDML
PUSH DX
CALL CLOSEB ;CLOSE THIS EXTENT
POP DX
RET
;
;Error messages
;
SELERR: MOV DX,(Offset SELEMS) ;SAY NO GO, AND BAIL OUT
JMP PMSG
;
SELEMS DB CR,LF,'Drive specifier out of range$'
;
ERMSG5 DB CR,LF,BEL,'+++ Warning...System tracks'
DB ' bad +++',CR,LF,CR,LF,'$'
;
ERROR6: MOV DX,(Offset ERMSG6) ;OOPS...CLOBBERED DIRECTORY
JMP PMSG
;
ERMSG6 DB CR,LF,BEL,'Bad directory area, try reformatting$'
;
ERROR7: MOV DX,(Offset ERMSG7) ;SAY NO GO, AND BAIL OUT
JMP PMSG
;
ERMSG7 DB CR,LF,'Can''t create [UNUSED].BAD$'
;
;
;==== SUBROUTINES ====
;
;Decimal output routine
;
DECOUT: PUSH CX
PUSH DX
PUSH BX
MOV CX,-10
MOV DX,-1
;
DECOU2: ADD BX,CX
INC DX
JB DECOU2
MOV CX,10
ADD BX,CX
XCHG BX,DX
MOV AL,BH
OR AL,BL
JZ L_17
CALL DECOUT
L_17:
MOV AL,DL
ADD AL,'0'
CALL DISPLAY
POP BX
POP DX
POP CX
RET
;
;Carriage-return/line-feed to console
;
CRLF: MOV AL,CR
CALL DISPLAY
MOV AL,LF ;FALL INTO 'DISPLAY'
;
DISPLAY:PUSH CX
PUSH DX
PUSH BX
MOV DL,AL ;CHARACTER TO [DX] FOR CP/M
MOV CL,2 ;bdos PRINT CONSOLE function
INT 224 ;bdos PRINT CHARACTER
POP BX
POP DX
POP CX
RET
;
;Subroutine to test console for control-c abort
;
STOP: MOV BYTE PTR FUNC,2 ;BIOS CONSOLE STATUS FUNCTION
MOV WORD PTR BIOS_DESC,0 ;PASS 'NOTHING' NUMBER TO BIOS DESCRIPTOR
MOV WORD PTR BIOS_DESC+2,0 ;FILL REMAINING DESCRIPTOR (DX) ZERO
MOV DX,(OFFSET FUNC) ;POINT TO FUNCTION PARAMETER BLOCK
MOV CL,50 ;direct bios call
INT 224 ;do it, to it...
;
OR AL,AL ;TEST FLAGS ON ZERO
JNZ L_18
RET ;RETURN IF NO CHAR
L_18: MOV BYTE PTR FUNC,3 ;BIOS READ CONSOLE CHARACTER FUNCTION
MOV WORD PTR BIOS_DESC,0 ;PASS 'NOTHING' NUMBER TO BIOS DESCRIPTOR
MOV WORD PTR BIOS_DESC+2,0 ;FILL REMAINING DESCRIPTOR (DX) ZERO
MOV DX,(OFFSET FUNC) ;POINT TO FUNCTION PARAMETER BLOCK
MOV CL,50 ;direct bios call
INT 224 ;do it, to it...
;
CMP AL,'C'-40H ;IS IT CONTROL-C?
JZ L_19
RET ;RETURN IF NOT
L_19:
MOV DX,(Offset ABORTM) ;EXIT WITH MESSAGE
JMP PMSG
;
ABORTM DB CR,LF
DB 'Test aborted by control-C'
DB CR,LF,'$'
;
;Move from [BX] to [DX], Count in [CX]
;
MOVE: MOV AL,ES: M
MOV SI,DX
MOV [SI],AL
LAHF
INC BX
SAHF
LAHF
INC DX
SAHF
DEC CH
JNZ MOVE
RET
;
;Print byte in accumulator in hex
;
HEXO: LAHF ;SAVE FOR SECOND HALF
XCHG AL,AH
PUSH AX
XCHG AL,AH
ROR AL,1 ;MOVE INTO POSITION
ROR AL,1
ROR AL,1
ROR AL,1
CALL NYBBLE ;bdos PRINT MS NYBBLE
POP AX
XCHG AL,AH
;
NYBBLE: AND AL,0FH ;LO NYBBLE ONLY
ADD AL,90H
DAA
ADC AL,40H
DAA
JMP DISPLAY ;bdos PRINT IN HEX
;
;Subroutine to determine the number
;of groups reserved for the directory
;
GETDIR: MOV CL,0 ;INIT BIT COUNT
MOV AL,Byte Ptr AL0 ;READ DIR GRP BITS
CALL COLECT ;COLLECT COUNT OF DIR GRPS..
MOV AL,Byte Ptr AL1 ;..IN REGISTER [CL]
CALL COLECT
MOV BL,CL
MOV BH,0 ;[CX] NOW HAS A DEFAULT START GRP #
MOV Word Ptr DIRBKS,BX ;SAVE FOR LATER
RET
;
;Collect the number of '1' bits in A as a count in [CL]
;
COLECT: MOV CH,8
;
COLOP: RCL AL,1
JNB COSKIP
INC CL
;
COSKIP: DEC CH
JNZ COLOP
RET
;
;Routine to fill in disk parameters
;
LOGIT: MOV DX,(Offset DPB) ; THEN MOVE TO LOCAL
MOV CH,(Offset DPBLEN) ; WORKSPACE
CALL MOVE
RET
;
L_22 EQU $
dseg $
ORG Offset L_22
;
;--------------------------------------------------
;The disk parameter block is moved here from CP/M-86
;
DPB EQU (Offset $) ;DISK PARAMETER BLOCK (COPY)
;
SPT RS 2 ;SECTORS PER TRACK
BSH RS 1 ;BLOCK SHIFT
BLM RS 1 ;BLOCK MASK
EXM RS 1 ;EXTENT MASK
DSM RS 2 ;MAXIMUM BLOCK NUMBER
DRM RS 2 ;MAXIMUM DIRECTORY BLOCK NUMBER
AL0 RS 1 ;DIRECTORY ALLOCATION VECTOR
AL1 RS 1 ;DIRECTORY ALLOCATION VECTOR
CKS RS 2 ;CHECKED DIRECTORY ENTRIES
SYSTRK RS 2 ;SYSTEM TRACKS
;
;End of disk parameter block
;
DPBLEN EQU (Offset $)-(Offset DPB) ;LENGTH OF DISK PARM BLOCK
;
;--------------------------------------------------
BLKEXT DB 0 ;BLOCKS PER EXTENT
DIRBKS DW 0 ;CALCULATED # OF DIR BLOCKS
;
BFCB DB 0,'[UNUSED]BAD',0,0,0,0
FCBDM RS 17
;
NOMSG DB 'No$'
ENDMSG DB ' bad blocks found',CR,LF,'$'
;
BADBKS DW 0 ;COUNT OF BAD BLOCKS
SECTOR DW 0 ;CURRENT SECTOR NUMBER
TRACK DW 0 ;CURRENT TRACK NUMBER
PHYSEC DW 0 ;CURRENT PHYSICAL SECTOR NUMBER
SECTBL DW 0 ;SECTOR SKEW TABLE POINTER
;
EXTNUM DB 0FFH ;USED FOR UPDATING EXTENT NUMBER
DMCNT DW 0 ;NUMBER OF BAD SECTORS
DMPTR DW (Offset DM) ;POINTER TO NEXT BLOCK ID
;
;storage for 5 byte BIOS function descriptor
;
FUNC RS 1 ;bios function code goes here
BIOS_DESC RS 2 ;[CX] data goes here
RS 2 ;[DX] data goes here
;
DM EQU (Offset $) ;BAD BLOCK ALLOCATION MAP
;
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
DW 0 ;RESERVE 2 BYTES
;
END