home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
CP/M
/
CPM_CDROM.iso
/
simtel
/
sigm
/
vols000
/
vol067
/
findbd54.asm
< prev
next >
Wrap
Assembly Source File
|
1984-04-29
|
33KB
|
1,203 lines
; FINDBAD.ASM ver. 5.4
; (revised 05/21/81)
;
; NON-DESTRUCTIVE DISK TEST PROGRAM
;
;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 will not use them.
;
;Originally written by Gene Cotton, published in "Interface
;Age", September 1980 issue, page 80.
;
;See notes below concerning 'TEST' conditional assembly option,
;SYSTST and BADUSR directives.
;
;********************************************************
;* *
;* NOTE *
;* *
;* This program has been re-written to allow it to *
;* work with (hopefully) all CP/M 2.x systems, and *
;* most 1.4 CP/M systems. It has been tested on sev- *
;* eral different disk systems, including Northstar, *
;* Micropolis, DJ2D, and Keith Petersen's 10 MByte *
;* hard disk system. I have tested it personally on *
;* my "modified" Northstar, under several different *
;* formats (including >16K per extent), and have ob- *
;* no difficulties. *
;* If you have have difficulties getting this pro- *
;* gram to run, AND if you are using CP/M 2.x, AND *
;* if you know your CBIOS to be bug-free, leave *
;* me a message on the CBBS mentioned below ... I am *
;* interested in making this program as "universal" *
;* as possible. *
;* I can't help with any version of CP/M 1.4, other *
;* than "standard" versions (whatever that means), *
;* because there are just too many heavily modified *
;* versions available. *
;* One possible problem you may find is with the *
;* system tracks of your diskettes...if they are of *
;* a different density than the data tracks, then *
;* see the note regarding the "SYSTST" equate. *
;* *
;* Ron Fowler *
;* Westland, Mich *
;* 7 April, 1981 *
;* *
;********************************************************
;
;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 1. 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 2.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. CP/M 1.4 users can ignore
;this byte altogether.
;
;Note that these changes can be done with DDT as follows:
;
; A>DDT FINDBAD.COM
; -S103
; 103 01 0 ;DON'T TEST SYSTEM TRACKS
; 104 FF F ;PUT [UNUSED.BAD] IN USER 15
; 105 31 . ;DONE WITH CHANGES
; -^C
; A>SAVE XX FINDBAD.COM
;
;----------------------------------------------------------------
;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 "TECHNICAL CBBS" in Dearborn,
;Michigan - phone 313-846-6127 (110, 300, 450 or 600 baud).
;Use the filename FINDBAD.NEW. (KBP)
;
;Modifications/updates: (in reverse order to minimize reading time)
;
;05/21/81 Corrected error in description of how to set SYSTST
; byte at 103h. Added CRLF to block error message. (KBP)
;
;05/19/81 Corrected omission in DOLOG routine so that BADUSR
; will work correctly. Thanks to Art Larky. (CHS)
;
;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.COM 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.COM 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 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:
;
; A>SAVE 1 TEST.SIZ
; A>STAT TEST.SIZ
;
;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 SYSTST is set to 0, 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 (4200H FOR ALTCPM)
BDOS EQU BASE+5 ;CP/M WARM BOOT ENTRY
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
;
DPBOFF EQU 3AH ;CP/M 1.4 OFFSET TO DPB WITHIN BDOS
TRNOFF EQU 15 ;CP/M 1.4 OFFSET TO SECTOR XLATE ROUTINE
;
;
ORG BASE+100H
;
JMP 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
;
;If you are a CP/M 2.x user, 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. CP/M 1.4 users
;can ignore this byte altogether.
;
BADUSR: DB 0FFH ;USER # WHERE [UNUSED.BAD] GOES
;0FFH = DEFAULT USER
;
START: LXI SP,NEWSTK ;MAKE NEW STACK
CALL START2 ;GO PRINT SIGNON
DB CR,LF,'FINDBAD - ver 5.4'
DB CR,LF,'Bad sector lockout '
DB 'program',CR,LF
DB 'Universal version',CR,LF
DB CR,LF,'Type CTL-C to abort',CR,LF,'$'
;
START2: POP D ;GET MSG ADRS
MVI C,9 ;BDOS PRINT BUFFER FUNCTION
CALL BDOS ;PRINT SIGN-ON MSG
CALL SETUP ;SET BIOS ENTRY, AND CHECK DRIVE
CALL ZMEM ;ZERO ALL AVAILABLE MEMORY
CALL FINDB ;ESTABLISH ALL BAD BLOCKS
JZ NOBAD ;SAY NO BAD BLOCKS, IF SO
CALL SETDM ;FIX DM BYTES IN FCB
;
NOBAD: CALL CRLF
MVI A,TAB
CALL TYPE
LXI D,NOMSG ;POINT FIRST TO 'NO'
LHLD BADBKS ;PICK UP # BAD BLOCKS
MOV A,H ;CHECK FOR ZERO
ORA L
JZ PMSG1 ;JUMP IF NONE
CALL DECOUT ;OOPS..HAD SOME BAD ONES, REPORT
JMP PMSG2
;
PMSG1: MVI C,9 ;BDOS PRINT BUFFER FUNCTION
CALL BDOS
;
PMSG2: LXI D,ENDMSG ;REST OF EXIT MESSAGE
;
PMSG: MVI C,9
CALL BDOS
;
IF TEST
MVI A,TAB ;GET A TAB
CALL TYPE ;PRINT IT
LHLD SECCNT ;GET NUMBER OF SECTORS READ
CALL DECOUT ;PRINT IT
LXI D,SECMSG ;POINT TO MESSAGE
MVI C,9 ;BDOS PRINT BUFFER FUNCTION
CALL BDOS ;PRINT IT
ENDIF ;TEST
;
JMP BASE ;EXIT TO CP/M WARM BOOT
;
;Get actual address of BIOS routines
;
SETUP: LHLD BASE+1 ;GET BASE ADDRESS OF BIOS VECTORS
;
;WARNING...Program modification takes place here...do not change.
;
LXI D,24 ;OFFSET TO "SETDSK"
DAD D
SHLD SETDSK+1 ;FIX OUR CALL ADDRESS
LXI D,3 ;OFFSET TO "SETTRK"
DAD D
SHLD SETTRK+1 ;FIX OUR CALL ADDRESS
LXI D,3 ;OFFSET TO "SETSEC"
DAD D
SHLD SETSEC+1 ;FIX OUR CALL ADDRESS
LXI D,6 ;OFFSET TO "DREAD"
DAD D
SHLD DREAD+1 ;FIX OUR CALL ADDRESS
LXI D,9 ;OFFSET TO CP/M 2.x SECTRAN
DAD D
SHLD SECTRN+1 ;FIX OUR CALL ADDRESS
MVI C,12 ;GET VERSION FUNCTION
CALL BDOS
MOV A,H ;SAVE AS FLAG
ORA L
STA VER2FL
JNZ GDRIV ;SKIP 1.4 STUFF IF IS 2.x
LXI D,TRNOFF ;CP/M 1.4 OFFSET TO SECTRAN
LHLD BDOS+1 ;SET UP JUMP TO 1.4 SECTRAN
MVI L,0
DAD D
SHLD SECTRN+1
;
;Check for drive specification
;
GDRIV: LDA FCB ;GET DRIVE NAME
MOV C,A
ORA A ;ZERO?
JNZ GD2 ;IF NOT,THEN GO SPECIFY DRIVE
MVI C,25 ;GET LOGGED-IN DRIVE
CALL BDOS
INR A ;MAKE 1-RELATIVE
MOV C,A
;
GD2: LDA VER2FL ;IF CP/M VERSION 2.x
ORA A
JNZ GD3 ; SELDSK WILL RETURN SEL ERR
;
;Is CP/M 1.4, which doesn't return a select
;error, so we have to do it here
;
MOV A,C
CPI 4+1 ;CHECK FOR HIGHEST DRIVE NUMBER
JNC SELERR ;SELECT ERROR
;
GD3: DCR C ;BACK OFF FOR CP/M
PUSH B ;SAVE DISK SELECTION
MOV E,C ;ALIGN FOR BDOS
MVI C,14 ;SELECT DISK FUNCTION
CALL BDOS
POP B ;GET BACK DISK NUMBER
;
;EXPLANATION: WHY WE DO THE SAME THING TWICE
;
; You might notice that we are
; doing the disk selection twice,
; once by a BDOS call and once by
; direct BIOS call. The reason for this:
;
; The BIOS call is necessary in order to
; get the necessary pointer back from CP/M
; (2.x) to find the sector translate table.
; The BDOS call is necessary to keep CP/M
; in step with the BIOS...we may later
; have to create a [UNUSED].BAD file, and
; CP/M must know which drive we are using.
; (RGF)
;
SETDSK: CALL $-$ ;DIRECT BIOS VEC FILLED IN AT INIT
LDA VER2FL
ORA A
JZ DOLOG ;JUMP IF CP/M 1.4
MOV A,H
ORA L ;CHECK FOR 2.x
JZ SELERR ;JUMP IF SELECT ERROR
MOV E,M ;GET SECTOR TABLE PNTR
INX H
MOV D,M
INX H
XCHG
SHLD SECTBL ;STORE IT AWAY
LXI H,8 ;OFFSET TO DPB POINTER
DAD D
MOV A,M ;PICK UP DPB POINTER
INX H ; TO USE
MOV H,M ; AS PARAMETER
MOV L,A ; TO LOGIT
;
DOLOG: CALL LOGIT ;LOG IN DRIVE, GET DISK PARMS
CALL GETDIR ;CALCULATE DIRECTORY INFORMATION
;
;Now set the required user number
;
LDA VER2FL
ORA A
RZ ;NO USERS IN CP/M 1.4
LDA BADUSR ;GET THE USER NUMBER
CPI 0FFH ;IF IT IS 0FFH, THEN RETURN
RZ
MOV E,A ;BDOS CALL NEEDS USER # IN E
MVI C,32 ;GET/SET USER CODE
CALL BDOS
RET
;
;Look for bad blocks
;
FINDB: LDA SYSTST
ORA A
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 D
MVI C,9 ;BDOS PRINT STRING FUNCTION
CALL BDOS
CALL ERAB ;ERASE ANY [UNUSED].BAD FILE
LHLD DIRBKS ;START AT FIRST DATA BLOCK
MOV B,H ;PUT INTO BC
MOV C,L
;
FINDBA: CALL READB ;READ THE BLOCK
CNZ SETBD ;IF BAD, ADD BLOCK TO LIST
INX B ;BUMP TO NEXT BLOCK
LHLD DSM
MOV D,B ;SET UP FOR (MAXGRP - CURGRP)
MOV E,C
CALL SUBDE ;DO SUBTRACT: (MAXGRP - CURGRP)
JNC FINDBA ;UNTIL CURGRP>MAXGRP
CALL CRLF
LHLD DMCNT ;GET NUMBER OF BAD SECTORS
MOV A,H
ORA L ;SET ZERO FLAG, IF NO BAD BLOCKS
RET ;RETURN FROM "FINDB"
;
;Check system tracks, notify user if bad, but continue
;
CHKSYS: CALL CHSY1 ;PRINT MESSAGE
DB CR,LF,'Testing system tracks...',CR,LF,'$'
;
CHSY1: POP D
MVI C,9 ;PRINT STRING FUNCTION
CALL BDOS
LXI H,0 ;SET TRACK 0, SECTOR 1
SHLD TRACK
INX H
SHLD SECTOR
;
CHKSY1: CALL READS ;READ A SECTOR
JNZ SYSERR ;NOTIFY, IF BAD BLOCKS HERE
LHLD SYSTRK ;SET UP (TRACK-SYSTRK)
XCHG
LHLD TRACK
CALL SUBDE ;DO THE SUBTRACT
JC CHKSY1 ;LOOP WHILE TRACK < SYSTRK
RET ;RETURN FROM "CHKSYS"
;
SYSERR: LXI D,ERMSG5 ;SAY NO GO, AND BAIL OUT
MVI C,9 ;BDOS PRINT BUFFER FUNCTION
CALL BDOS
RET ;RETURN FROM "SYSERR"
;
;Check for bad blocks in directory area
;
CHKDIR: CALL CHKD1
DB CR,LF,'Testing directory area...',CR,LF,'$'
;
CHKD1: POP D
MVI C,9 ;BDOS PRINT STRING FUNCTION
CALL BDOS
LXI B,0 ;START AT BLOCK 0
;
CHKDI1: CALL READB ;READ A BLOCK
JNZ ERROR6 ;IF BAD, INDICATE ERROR IN DIRECTORY AREA
INX B ;BUMP FOR NEXT BLOCK
LHLD DIRBKS ;SET UP (CURGRP - DIRBKS)
DCX H ;MAKE 0-RELATIVE
MOV D,B
MOV E,C
CALL SUBDE ;DO THE SUBTRACT
JNC 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.
LDA BLM
INR A ;NUMBER OF SECTORS/BLOCK
MOV D,A ; IN D REG
;
READBA: PUSH D
CALL READS ;READ SKEWED SECTOR
POP D
RNZ ;ERROR IF NOT ZERO...
DCR D ;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 B ;SAVE CURRENT GROUP
MOV H,B ;NEED IT IN HL
MOV L,C ; FOR EASY SHIFTING
LDA BSH ;DPB VALUE THAT TELLS HOW TO
;
SHIFT: DAD H ; SHIFT GROUP NUMBER TO GET
DCR A ; DISK-DATA-AREA RELATIVE
JNZ SHIFT ; SECTOR NUMBER
XCHG ;REL SECTOR # INTO DE
LHLD SPT ;SECTORS PER TRACK FROM DPB
CALL NEG ;FASTER TO DAD THAN CALL SUBDE
XCHG
LXI B,0 ;INITIALIZE QUOTIENT
;
;Divide by number of sectors
; quotient = track
; mod = sector
;
DIVLP: INX B ;DIRTY DIVISION
DAD D
JC DIVLP
DCX B ;FIXUP LAST
XCHG
LHLD SPT
DAD D
INX H
SHLD SECTOR ;NOW HAVE LOGICAL SECTOR
LHLD SYSTRK ;BUT BEFORE WE HAVE TRACK #,
DAD B ; WE HAVE TO ADD SYS TRACK OFFSET
SHLD TRACK
POP B ;THIS WAS OUR GROUP NUMBER
RET
;
;READS reads a logical sector (if it can)
;and returns zero flag set if no error.
;
READS: PUSH B ;SAVE THE GROUP NUMBER
CALL LTOP ;CONVERT LOGICAL TO PHYSICAL
LDA VER2FL ;NOW CHECK VERSION
ORA A
JZ NOTCP2 ;SKIP THIS STUFF IF CP/M 1.4
LHLD PHYSEC ;GET PHYSICAL SECTOR
MOV B,H ;INTO BC
MOV C,L
;
SETSEC: CALL $-$ ;ADDRS FILLED IN AT INIT
;
;QUICK NOTE OF EXPLANATION: This code appears
;as if we skipped the SETSEC routine for 1.4
;CP/M users. That's not true; in CP/M 1.4, the
;call within the LTOP routine to SECTRAN ac-
;tually does the set sector, so no need to do
;it twice. (RGF)
;
NOTCP2: LHLD TRACK ;NOW SET THE TRACK
MOV B,H ;CP/M WANTS IT IN BC
MOV C,L
;
SETTRK: CALL $-$ ;ADDRS FILLED IN AT INIT
;
;Now do the sector read
;
DREAD: CALL $-$ ;ADDRS FILLED IN AT INIT
ORA A ;SET FLAGS
PUSH PSW ;SAVE ERROR FLAG
;
IF TEST
LHLD SECCNT ;GET SECTOR COUNT
INX H ;ADD ONE
SHLD SECCNT ;SAVE NEW COUNT
ENDIF ;TEST
;
LHLD SECTOR ;GET LOGICAL SECTOR #
INX H ;WE WANT TO INCREMENT TO NEXT
XCHG ;BUT FIRST...CHECK OVERFLOW
LHLD SPT ; BY DOING (SECPERTRK-SECTOR)
CALL SUBDE ;DO THE SUBTRACTION
XCHG
JNC NOOVF ;JUMP IF NOT SECTOR>SECPERTRK
;
;Sector overflow...bump track number, reset sector
;
LHLD TRACK
INX H
SHLD TRACK
MVI A,'*' ;TELL CONSOLE ANOTHER TRACK DONE
CALL TYPE
CALL STOP ;SEE IF CONSOLE WANTS TO QUIT
LXI H,1 ;NEW SECTOR NUMBER ON NEXT TRACK
;
NOOVF: SHLD SECTOR ;PUT SECTOR AWAY
POP PSW ;GET BACK ERROR FLAGS
POP B ;RESTORE GROUP NUMBER
RET
;
;Convert logical sector # to physical
;
LTOP: LHLD SECTBL ;SET UP PARAMETERS
XCHG ; FOR CALL TO SECTRAN
LHLD SECTOR
MOV B,H
MOV C,L
DCX B ;ALWAYS CALL SECTRAN W/ZERO-REL SEC #
;
SECT1: CALL SECTRN ;DO THE SECTOR TRANSLATION
LDA SPT+1 ;CHECK IF BIG TRACKS
ORA A ;SET FLAGS (TRACKS > 256 SECTORS)
JNZ LTOP1 ;NO SO SKIP
MOV H,A ;ZERO OUT UPPER 8 BITS
;
LTOP1: SHLD PHYSEC ;PUT AWAY PHYSICAL SECTOR
RET
;
;Sector translation vector
;
SECTRN: JMP $-$ ;FILLED IN AT INIT
;
;Put bad block in bad block list
;
SETBD: PUSH B
CALL SETBD1
DB CR,LF,'Bad block: $'
;
SETBD1: POP D ;RETRIEVE ARG
MVI C,9 ;PRINT STRING
CALL BDOS
POP B ;GET BACK BLOCK NUMBER
MOV A,B
CALL HEXO ;PRINT IN HEX
MOV A,C
CALL HEXO
CALL CRLF
LHLD DMCNT ;GET NUMBER OF SECTORS
LDA BLM ;GET BLOCK SHIFT VALUE
INR A ;MAKES SECTOR/GROUP VALUE
MOV E,A ;WE WANT 16 BITS
MVI D,0
DAD D ;BUMP BY NUMBER IN THIS BLOCK
SHLD DMCNT ;UPDATE NUMBER OF SECTORS
LHLD BADBKS ;INCREMENT NUMBER OF BAD BLOCKS
INX H
SHLD BADBKS
LHLD DMPTR ;GET POINTER INTO DM
MOV M,C ;...AND PUT BAD BLOCK NUMBER
INX H ;BUMP TO NEXT AVAILABLE EXTENT
LDA DSM+1 ;CHECK IF 8 OR 16 BIT BLOCK SIZE
ORA A
JZ SMGRP ;JUMP IF 8 BIT BLOCKS
MOV M,B ;ELSE STORE HI BYTE OF BLOCK #
INX H ;AND BUMP POINTER
;
SMGRP: SHLD DMPTR ;SAVE DM POINTER, FOR NEXT TIME THROUGH HERE
RET ;RETURN FROM "SETBD"
;
;Eliminate any previous [UNUSED].BAD entries
;
ERAB: LXI D,BFCB ;POINT TO BAD FCB
MVI C,19 ;BDOS DELETE FILE FUNCTION
CALL BDOS
RET
;
;Create [UNUSED].BAD file entry
;
OPENB: LXI D,BFCB ;POINT TO BAD FCB
MVI C,22 ;BDOS MAKE FILE FUNCTION
CALL BDOS
CPI 0FFH ;CHECK FOR OPEN ERROR
RNZ ;RETURN FROM "OPENB", IF NO ERROR
JMP ERROR7 ;BAIL OUT...CAN'T CREATE [UNUSED].BAD
;
CLOSEB: XRA A
LDA BFCB+14 ;GET CP/M 2.x 'S2' BYTE
ANI 1FH ;ZERO UPDATE FLAGS
STA BFCB+14 ;RESTORE IT TO OUR FCB (WON'T HURT 1.4)
LXI D,BFCB ;FCB FOR [UNUSED].BAD
MVI C,16 ;BDOS CLOSE FILE FUNCTION
CALL BDOS
RET ;RETURN FROM "CLOSEB"
;
;Move bad area DM to BFCB
;
SETDM: LXI H,DM ;GET DM
SHLD DMPTR ;SAVE AS NEW POINTER
LDA EXM ;GET THE EXTENT SHIFT FACTOR
MVI C,0 ;INIT BIT COUNT
CALL COLECT ;GET SHIFT VALUE
LXI H,128 ;STARTING EXTENT SIZE
MOV A,C ;FIRST SEE IF ANY SHIFTS TO DO
ORA A
JZ NOSHFT ;JUMP IF NONE
;
ESHFT: DAD H ;SHIFT
DCR A ;BUMP
JNZ ESHFT ;LOOP
;
NOSHFT: PUSH H ;SAVE THIS, IT IS RECORDS PER EXTENT
LDA BSH ;GET BLOCK SHIFT
MOV B,A
;
BSHFT: CALL ROTRHL ;SHIFT RIGHT
DCR B
JNZ BSHFT ;TO GET BLOCKS PER EXTENT
MOV A,L ;IT'S IN L (CAN'T BE >16)
STA BLKEXT ;SETDME WILL NEED THIS LATER
POP H ;GET BACK REC/EXT
;
SET1: XCHG ;NOW HAVE REC/EXTENT IN DE
LHLD DMCNT ;COUNT OF BAD SECTORS
;
SETDMO: PUSH H ;SET FLAGS ON (DMCNT-BADCNT)
CALL SUBDE ;HAVE TO SUBTRACT FIRST
MOV B,H ;SAVE RESULT IN BC
MOV C,L
POP H ;THIS POP MAKES IT COMPARE ONLY
JC SETDME ;JUMP IF LESS THAN 1 EXTENT WORTH
MOV A,B
ORA C ;TEST IF SUBTRACT WAS 0
JZ EVENEX ;EXTENT IS EXACTLY FILLED (SPL CASE)
MOV H,B ;RESTORE RESULT TO HL
MOV L,C
PUSH H ;SAVE TOTAL
PUSH D ;AND SECTORS/EXTENT
XCHG
CALL SETDME ;PUT AWAY ONE EXTENT
XCHG
SHLD DMPTR ;PUT BACK NEW DM POINTER
POP D ;GET BACK SECTORS/EXTENT
POP H ;AND COUNT OF BAD SECTORS
JMP 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 ;FIRST SET EXTENT W/BAD BLOCKS
CALL SETDME
XCHG
SHLD DMPTR
LXI H,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 H ;SAVE RECORD COUNT
LDA EXTNUM ;UPDATE EXTENT BYTE
INR A
STA EXTNUM ;SAVE FOR LATER
STA BFCB+12 ; AND PUT IN FCB
CALL OPENB ;OPEN THIS EXTENT
POP H ;RETRIEVE REC COUNT
;
;Divide record count by 128 to get the number
;of logical extents to put in the EX field
;
MVI B,0 ;INIT QUOTIENT
LXI D,-128 ;-DIVISOR
MOV A,H ;TEST FOR SPL CASE
ORA L ; OF NO RECORDS
JZ SKIP
;
DIVLOP: DAD D ;SUBTRACT
INR B ;BUMP QUOTIENT
JC DIVLOP
LXI D,128 ;FIX UP OVERSHOOT
DAD D
DCR B
MOV A,H ;TEST FOR WRAPAROUND
ORA L
JNZ SKIP
MVI L,80H ;RECORD LENGTH
DCR B
;
SKIP: LDA EXTNUM ;NOW FIX UP EXTENT NUM
ADD B
STA EXTNUM
STA BFCB+12
MOV A,L ;MOD IS RECORD COUNT
STA BFCB+15 ;THAT GOES IN RC BYTE
;
MOVDM: LDA BLKEXT ;GET BLOCKS PER EXTENT
MOV B,A ;INTO B
;
SETD1: LHLD DMPTR ;POINT TO BAD ALLOCATION MAP
XCHG
LXI H,BFCB+16 ;DISK ALLOC MAP IN FCB
;
SETDML: LDAX D
MOV M,A
INX H
INX D
;
;Now see if 16 bit groups...if so,
;we have to move another byte
;
LDA DSM+1 ;THIS TELLS US
ORA A
JZ BUMP1 ;IF ZERO, THEN NOT
LDAX D ;IS 16 BITS, SO DO ANOTHER
MOV M,A
INX H
INX D
;
BUMP1: DCR B ;COUNT DOWN
JNZ SETDML
PUSH D
CALL CLOSEB ;CLOSE THIS EXTENT
POP D
RET
;
;Error messages
;
SELERR: LXI D,SELEMS ;SAY NO GO, AND BAIL OUT
JMP PMSG
;
SELEMS: DB CR,LF,'Drive specifier out of range$'
;
ERMSG5: DB CR,LF,'+++ Warning...System tracks'
DB ' bad +++',CR,LF,CR,LF,'$'
;
ERROR6: LXI D,ERMSG6 ;OOPS...CLOBBERED DIRECTORY
JMP PMSG
;
ERMSG6: DB CR,LF,'Bad directory area, try reformatting$'
;
ERROR7: LXI D,ERMSG7 ;SAY NO GO, AND BAIL OUT
JMP PMSG
;
ERMSG7: DB CR,LF,'Can''t create [UNUSED].BAD$'
;
;
;==== SUBROUTINES ====
;
;Decimal output routine
;
DECOUT: PUSH B
PUSH D
PUSH H
LXI B,-10
LXI D,-1
;
DECOU2: DAD B
INX D
JC DECOU2
LXI B,10
DAD B
XCHG
MOV A,H
ORA L
CNZ DECOUT
MOV A,E
ADI '0'
CALL TYPE
POP H
POP D
POP B
RET
;
;Carriage-return/line-feed to console
;
CRLF: MVI A,CR
CALL TYPE
MVI A,LF ;FALL INTO 'TYPE'
;
TYPE: PUSH B
PUSH D
PUSH H
MOV E,A ;CHARACTER TO E FOR CP/M
MVI C,2 ;PRINT CONSOLE FUNCTION
CALL BDOS ;PRINT CHARACTER
POP H
POP D
POP B
RET
;
;Subroutine to test console for control-c abort
;
STOP: LHLD 1 ;FIND BIOS IN MEMORY
MVI L,6 ;OFFSET TO CONSOLE STATUS
CALL GOHL ;THANKS TO BRUCE RATOFF FOR THIS TRICK
ORA A ;TEST FLAGS ON ZERO
RZ ;RETURN IF NO CHAR
LHLD 1 ;NOW FIND CONSOLE INPUT
MVI L,9 ;OFFSET FOR CONIN
CALL GOHL
CPI 'C'-40H ;IS IT CONTROL-C?
RNZ ;RETURN IF NOT
LXI D,ABORTM ;EXIT WITH MESSAGE
MVI C,9 ;PRINT MESSAGE FUNCTION
CALL BDOS ;SAY GOODBYE
JMP 0 ;THEN LEAVE
;
ABORTM: DB CR,LF
DB 'Test aborted by control-C'
DB CR,LF,'$'
;
;A thing to allow a call to @HL
;
GOHL: PCHL
;
;Zero all of memory to hold DM values
;
ZMEM: LHLD BDOS+1 ;GET TOP-OF-MEM POINTER
LXI D,DM ;STARTING POINT
CALL SUBDE ;GET NUMBER OF BYTES
MOV B,H
MOV C,L
XCHG ;BEGIN IN HL, COUNT IN BC
;
ZLOOP: MVI M,0 ;ZERO A BYTE
INX H ;POINT PAST
DCX B ;COUNT DOWN
MOV A,B
ORA C
JNZ ZLOOP
RET
;
;Subtract DE from HL
;
SUBDE: MOV A,L
SUB E
MOV L,A
MOV A,H
SBB D
MOV H,A
RET
;
;Negate HL
;
NEG: MOV A,L
CMA
MOV L,A
MOV A,H
CMA
MOV H,A
INX H
RET
;
;Move from (HL) to (DE)
;Count in BC
;
MOVE: MOV A,M
STAX D
INX H
INX D
DCR B
JNZ MOVE
RET
;
;Print byte in accumulator in hex
;
HEXO: PUSH PSW ;SAVE FOR SECOND HALF
RRC ;MOVE INTO POSITION
RRC
RRC
RRC
CALL NYBBLE ;PRINT MS NYBBLE
POP PSW
;
NYBBLE: ANI 0FH ;LO NYBBLE ONLY
ADI 90H
DAA
ACI 40H
DAA
JMP TYPE ;PRINT IN HEX
;
;Subroutine to determine the number
;of groups reserved for the directory
;
GETDIR: MVI C,0 ;INIT BIT COUNT
LDA AL0 ;READ DIR GRP BITS
CALL COLECT ;COLLECT COUNT OF DIR GRPS..
LDA AL1 ;..IN REGISTER C
CALL COLECT
MOV L,C
MVI H,0 ;BC NOW HAS A DEFAULT START GRP #
SHLD DIRBKS ;SAVE FOR LATER
RET
;
;Collect the number of '1' bits in A as a count in C
;
COLECT: MVI B,8
;
COLOP: RAL
JNC COSKIP
INR C
;
COSKIP: DCR B
JNZ COLOP
RET
;
;Shift HL right one place
;
ROTRHL: ORA A ;CLEAR CARRY
MOV A,H ;GET HI BYTE
RAR ;SHIFT RIGHT
MOV H,A ;PUT BACK
MOV A,L ;GET LO
RAR ;SHIFT WITH CARRY
MOV L,A ;PUT BACK
RET
;
;Routine to fill in disk parameters
;
LOGIT: LDA VER2FL
ORA A ;IF NOT CP/M 2.x THEN
JZ LOG14 ; DO IT AS 1.4
LXI D,DPB ; THEN MOVE TO LOCAL
MVI B,DPBLEN ; WORKSPACE
CALL MOVE
RET
;
LOG14: LHLD BDOS+1 ;FIRST FIND 1.4 BDOS
MVI L,0
LXI D,DPBOFF ;THEN OFFSET TO 1.4'S DPB
DAD D
MVI D,0 ;SO 8 BIT PARMS WILL BE 16
MOV E,M ;NOW MOVE PARMS
INX H ; DOWN FROM BDOS DISK PARM BLOCK
XCHG ; TO OURS
SHLD SPT
XCHG
MOV E,M
INX H
XCHG
SHLD DRM
XCHG
MOV A,M
INX H
STA BSH
MOV A,M
INX H
STA BLM
MOV E,M
INX H
XCHG
SHLD DSM
XCHG
MOV E,M
INX H
XCHG
SHLD AL0
XCHG
MOV E,M
XCHG
SHLD SYSTRK
RET
;
;--------------------------------------------------
;The disk parameter block
;is moved here from CP/M
;
DPB EQU $ ;DISK PARAMETER BLOCK (COPY)
;
SPT: DS 2 ;SECTORS PER TRACK
BSH: DS 1 ;BLOCK SHIFT
BLM: DS 1 ;BLOCK MASK
EXM: DS 1 ;EXTENT MASK
DSM: DS 2 ;MAXIMUM BLOCK NUMBER
DRM: DS 2 ;MAXIMUM DIRECTORY BLOCK NUMBER
AL0: DS 1 ;DIRECTORY ALLOCATION VECTOR
AL1: DS 1 ;DIRECTORY ALLOCATION VECTOR
CKS: DS 2 ;CHECKED DIRECTORY ENTRIES
SYSTRK: DS 2 ;SYSTEM TRACKS
;
;End of disk parameter block
;
DPBLEN EQU $-DPB ;LENGTH OF DISK PARM BLOCK
;
;--------------------------------------------------
BLKEXT: DB 0 ;BLOCKS PER EXTENT
DIRBKS: DW 0 ;CALCULATED # OF DIR BLOCKS
VER2FL: DB 0 ;VERSION 2.X FLAG
;
BFCB: DB 0,'[UNUSED]BAD',0,0,0,0
FCBDM: DS 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 DM ;POINTER TO NEXT BLOCK ID
;
SECMSG: DB ' total sectors read',CR,LF,'$'
;
SECCNT: DW 0 ;NUMBER OF SECTORS READ
;
DS 64 ;ROOM FOR 32 LEVEL STACK
NEWSTK EQU $ ;OUR STACK
DM EQU $ ;BAD BLOCK ALLOCATION MAP
;
END