; FBAD.ASM ver. 70 ; (revised 11/20/88) ; NON-DESTRUCTIVE DISK CHECK PROGRAM ; ; FBAD will find all bad blocks on a disk and build a file [UNUSED].BAD ; to allocate them, thus "locking out" the bad blocks so CP/M will not ; use them. This allows continued use of the disk as though it had no ; bad areas. ; ; If an [UNUSED].BAD file is found on the disk before the test you will ; be prompted to keep the existing file (and all currently flagged bad ; blocks) or erase it and only flag the bad blocks found on the current ; pass. ; ; Originally written by Gene Cotton, published in "Interface Age" for ; September 1980, page 80. ; ;======================================================================= ; current update ; ; 11/20/88 Revised request for disk drive to work properly. Previously ; v70 if you just used the current drive, then asked the program ; to continue with the next disk it would be all messed up, ; and quit after reading only 42 tracks of 160 on a DSDD disk. ; Can now use FBAD B: or FBAD B or FBAD. Also now shows ; the drive that is being checked. Other cosmetic changes. ; - Irv Hoff ; ;======================================================================= ; ; SYSTST, BADUSR and DRIVE options: ; ; A few 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, via SYSTST. The program tests if you have a ; "blocked" disk system, that is, if the same physical disk is separated ; into logical disks by use of the SYSTRK word in the disk parameter ; block. If more than 5 tracks are specified, the program skips the ; system tracks. ; ; BADUSER is used to assign the user number where the [UNUSED.BAD] file ; will be stored. This is normally user 0, but some prefer user 15. ; ; DRIVES can be set for the maximum number of drives you have (or want ; checked. This prevents hangups should somebody ask for a drive you ; don't have (or don't want checked), or if they accidently hit a wrong ; key. ; ;======================================================================= ; ; USING THE PROGRAM ; ; To use the program, insert both the disk containing FBAD.COM and the ; diskette to be checked into the disk drives. The diskette containing ; FBAD.COM can be the one that is to be checked. Assume that the pro- ; gram is on drive "A" and the suspected bad disk is on drive "B". In ; response to the CP/M prompt "A>", type in FBAD B: (or FBAD B). This ; will load the file FBAD.COM from drive "A" and test the diskette on ; drive "B" for unreadable records. ; ; The program first checks the CP/M System tracks (up to 5). Any errors ; here will prohibit the diskette from being used on drive "A:", since ; all "warm boots" occur using the system tracks from the "A:" drive. ; Floppy diskettes normally use 2 tracks for the system; Winchester hard ; disks may use one or more. ; ; FBAD automatically skips the system check if 5 or more tracks are re- ; served for the system. This allows the program to be used on BOTH ; floppy and Winchester systems without patching. ; ; The program next checks the first two data blocks containing the ; diskette directory. If errors occur here, the program terminates with ; the control returning to CP/M. No other data blockes are checked as ; errors in the directory render the diskette useless. ; ; Finally, all the remaining data blocks are checked. Any records that ; 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 ; "nn bad blocks found" is displayed (where nn is replaced by the number ; of bad blocks, or "No" if no read errors occur). If bad blocks occur, ; the filename [UNUSED].BAD is created, the list of "bad blocks" is put ; in the allocation map of the directory entry for [UNUSED].BAD and the ; file is closed. When the number of "bad blocks" exceeds 16, the pro- ; gram will open additional extents as required to hold the overflow. ; 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. ; ; If any "bad blocks" do occur, they are allocated to [UNUSED].BAD and ; no longer will be available to CP/M for future allocation. This ef- ; fectively locks out bad records on the diskette allowing its continued ; use. You can place this on any user area desired - normally either 0 ; or 15. ; ; If bad blocks do appear and you have files on the disk, one or more of ; those files may be bad. FBAD does not identify those files. There ; are at least two ways of doing that. (1) Run CHEK15.COM on the disk: ; ; B>CHEK *.* ; ; and if it encounters a file with a bad record, it will stop, display ; bad record, and wait for you to hit to continue. At that time ; you can see what file it stopped on. You can then attempt to replace ; it if you have a backup, or otherwise try to correct the problem with ; your editor if an ASCII file. This makes a new copy of the same pro- ; gram elsewhere, aborting the bum copy which can then be erased. FBAD ; retains the bad sectors in the [UNUSED].BAD file. (2) Run BD04.COM, ; which is very similar to FBAD, but a) does not check the system tracks ; and b) does mention any bad files while checking for bad records. It ; is a somewhat more versatile program than FBAD, but does not check the ; system tracks, which indicate if the diskette can be used to boot the ; system or not. ; ;======================================================================= ; ; 03/28/85 Cleaned up some code that sends messages. Modified STOP rou- ; v60 tine so that you really can abort the program with CTL-C now. ; - Dave Mabry ; ; 03/16/85 Program will ask you if you want to continue checking on the ; v59 same drive as originally entered - added a byte at the end of ; the code (ORIGDR) to store the value from the FCB. ; - Ken Kaplan ; ; 12/12/84 Added the ability to keep bad blocks that were flagged in a ; v58 previous [UNUSED].BAD file. If a block was ever flagged as ; bad by this program, it is probably weak. If on a subse- ; quent test, it makes it through the BIOS retires and is read ; successfully, I want the block to stay in the [UNUSED].BAD ; file. Removed the coded in LTOP which cleared the high byte ; of HL after a call to RECTRN. My BIOS (Morrow DJDMA) sets ; the high bit of HL to indicated side 1 of a double-sided ; drive. - Ron Schwabel ; ; 11/29/84 Integrated Mike Webbs idea to display Track-Nr. Changed DOC ; v57 up front accordingly. - BGE ; ; 07/04/84 Added Ted Shapin's fixes from 1981 that were not included in ; v56 the 06/07/84 version. Reformatted. ; - Irv Hoff ; ; 06/07/84 Added code at CHKSYS to skip system tracks if more than 5 ; v55 are present (most systems use 1 or at most 2 tracks for the ; system). This makes the program practical for both floppy ; and Winchester systems. Cosmetic change for printer logging ; to add CR/LF after 76 *'s. Fixed problem in DECOUT to give ; correct total for max size Winchester disks. ; - DHR ; ;======================================================================= ; NO EQU 0 YES EQU 0FFH ; ;======================================================================= ; ; System equates ; WBOOT EQU 0 ; CP/M warm boot entry BDOS EQU 0005H ; BDOS entry point FCB EQU 005CH ; CP/M default FCB location ; ; ; Define ASCII characters used ; BELL EQU 07H CR EQU 0DH ; Carriage return character LF EQU 0AH ; Line feed character TAB EQU 09H ; Tab character VERS EQU 70 ; Version number ; DPBOFF EQU 3AH ; CP/M 1.4 offset to DPB within BDOS TRNOFF EQU 15 ; CP/M 1.4 offset to record Xlate routine ; ; ORG 0100H ; ; JMP START ; Jmp around option bytes ; ; If you want the system tracks tested, then put a 1 here, otherwise 0. ; SYSTST: DB YES ; ; If using CP/M 2.x change this byte to the user number you want ; [UNUSED].BAD to reside in. ; BADUSR: DB 0 ; User # where [UNUSED.BAD] goes ; ; Number of drives on system that may be checked. (Can say 16, but this ; prevents asking for a non-existant drive.) ; DRIVES: DB 2 ; Any number from 1-16 drives ; ;======================================================================= ; ; PROGRAM STARTS HERE ; ;======================================================================= ; START: LXI SP,STACK ; Make new stack LXI D,SIGNON ; Introduce ourself CALL PSTRNG LXI H,0 ; Zero bad record count LHLD DMCNT 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 LXI D,BELLS CALL PSTRNG LXI H,BADBKS CALL DECOUT ; Oops..had some bad ones, report LXI D,BELLS CALL PSTRNG JMP PMSG2 ; PMSG1: CALL PSTRNG ; PMSG2: LXI D,ENDMSG ; Rest of exit message ; PMSG: CALL PSTRNG MVI A,TAB ; Get a tab CALL TYPE ; Print it LXI H,RECCNT CALL DECOUT ; Print it LXI D,RECMSG ; Point to message CALL PSTRNG ; LXI D,CMSG1 ; Do you want to continue? CALL PSTRNG MVI C,1 ; BDOS read console function CALL BDOS ANI 5FH ; Convert lower to upper case CPI 'C' ; If C or c entered continue JNZ WBOOT ; Exit MVI C,0DH ; Do a disk reset to avoid BDOS R/O errors CALL BDOS CALL CRLF ; Turn up a line CALL CRLF ; Turn up a 2nd line LDA ORIGDR ; Get drive value STA FCB ; Put it back in FCB JMP START ; Start over ; ; Get actual address of BIOS routines ; ; WARNING...Program modification takes place here...do not change. ; SETUP: LHLD 1 ; Get pointer to warm boot 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 'SETREC' DAD D SHLD SETREC+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 RECTRAN DAD D SHLD RECTRN+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 RECTRAN LHLD BDOS+1 ; Set up jump to 1.4 RECTRAN MVI L,0 DAD D SHLD RECTRN+1 ; GDRIV: LDA PRIOR ; Are we running from prior drive? ORA A JZ GDR1 ; If not, exit ; LDA ORIGDR ; Use the original drive, if continuing JMP GDR3 ; GDR1: LDA 80H ; Any drive specified? ORA A JNZ GDR2 ; If yes, exit ; MVI C,25 ; Else get logged-in drive CALL BDOS INR A ; Make 1-relative JMP GDR3 ; Go store it ; GDR2: LDA DRIVES INR A ; Go one past for later "JNC" MOV B,A ; Store value LDA 80H+2 ; Get the drive CPI 'A' JC ERROR ; Must be A-P CPI 'P'+1 JNC ERROR SUI 40H ; Convert from ASCII to binary CMP B JNC ERROR1 ; GDR3: STA ORIGDR ; Store for use later STA FCB ; Make this the active drive PUSH PSW LXI D,DRVMSG ; Point to message CALL PSTRNG ; POP PSW ; Get the drive back PUSH PSW ; Save it again ADI 40H ; Convert from binary to ASCII CALL TYPE CALL CRLF POP PSW STA PRIOR ; Show we have been this way DCR A MOV C,A 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 BIOS call is necessary in ; order to get the necessary pointer back from CP/M (2.x) to find the ; record translate table. The BDOS call is necessary to keep CP/M in ; step with the BIOS. Later the file [UNUSED].BAD may need to be ; created and CP/M must know which drive is being used. ; CALL SETDSK ; Direct BIOS call 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 record table pointer INX H MOV D,M INX H XCHG SHLD RECTBL ; 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 MOV E,A ; BDOS call needs user # in 'E' MVI C,32 ; Get/set user code CALL BDOS RET ;..... ; ; If a drive other than A-P was selected, show error and exit ; ERROR: LXI D,ERRMSG ; Show error message CALL PSTRNG JMP WBOOT ; Exit ;..... ; ; Shows error message if asked for drive not available ; ERROR1: LXI D,ERRDRV ; Show error message CALL PSTRNG LDA DRIVES ADI 40H STA ERRDRW LXI D,ERRDRW CALL PSTRNG JMP WBOOT ; Exit ;..... ; ; Look for bad blocks ; FINDB: LDA SYSTST ORA A JZ DODIR ; Jump if no system tracks to be tested CALL CHKSYS ; Check for bad blocks on track 0 and 1 ; DODIR: CALL CHKDIR ; Check for bad blocks in directory LXI D,TDAMSG ; Testing data area message CALL PSTRNG 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 records 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: LHLD SYSTRK ; Get # system tracks MOV A,H ; Get high part ORA A JNZ SKPSYS ; Skip system track check if non-zero MOV A,L ; Get low part CPI 5 ; >5 tracks? JNC SKPSYS ; Skip check if so LXI D,TSTMSG ; Testing system tracks message CALL PSTRNG LXI H,0 ; Set track 0,record 1 SHLD TRACK INX H SHLD RECORD ; CHKSY1: CALL READS ; Read a record JNZ SYSERR ; Notify, if bad blocks here LHLD SYSTRK ; Set up XCHG LHLD TRACK CALL SUBDE ; Do the subtract JC CHKSY1 ; Loop while track < SYSTRK ; MVI A,TAB ; Get a tab CALL TYPE ; Print it LXI H,RECCNT ; Get # records so far CALL DECOUT ; Print it LXI D,SYSMSG ; Point to message CALL PSTRNG RET ; Return from "CHKSYS" ;..... ; SKPSYS: LXI D,SKPMSG ; Say skipping system tracks JMP PSTRNG ; SKPMSG: DB CR,LF,'Skipping system tracks...',CR,LF,'$' ; SYSERR: LXI D,ERMSG5 ; Say no go, and bail out ; PSTRNG: MVI C,9 ; BDOS print string function CALL BDOS RET ; Return from "SYSERR" or subroutine ;..... ; ; Check for bad blocks in directory area ; CHKDIR: LXI D,TDRMSG ; Testing directory area message CALL PSTRNG LXI B,0 ; Start at block 0 ; CHKDI1: CALL READB ; Read a block JNZ ERROR6 ; If bad, show 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 records in block, and return zero flag set if none bad ; READB: CALL CNVRTB ; Convert to track/record in 'HL' regs. LDA BLM INR A ; Number of records/block MOV D,A ; In 'D' register ; READBA: PUSH D CALL READS ; Read skewed record POP D RNZ ; Error if not zero DCR D ; Debump record/block JNZ READBA ; Do next, if not finished RET ; Return from 'READBA' ;..... ; ; Convert block number to track and skewed record 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 ; Record number XCHG ; Rel record # into 'DE' LHLD SPT ; Records per track from DPB CALL NEG ; Faster to DAD than call SUB D XCHG LXI B,0 ; Initialize quotient ; ; Divide by number of records ; quotient = track ; mod = record ; DIVLP: INX B ; Dirty division DAD D JC DIVLP DCX B ; Fixup last XCHG LHLD SPT DAD D INX H SHLD RECORD ; Now have logical record LHLD SYSTRK ; But before we have track #, DAD B ; We have to add system track offset SHLD TRACK POP B ; This was our group number RET ;..... ; ; Reads a logical record (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 PHYREC ; Get physical record MOV B,H ; Into 'BC' MOV C,L CALL SETREC ; BIOS set record call ; ; QUICK NOTE OF EXPLANATION: This code appears as if we skipped the ; SETREC routine for 1.4 CP/M users. That is not true. In CP/M 1.4, ; the call within the LTOP routine to RECTRAN actually does the set ; record, so no need to do it twice. ; NOTCP2: LHLD TRACK ; Now set the track MOV B,H ; CP/M wants it in 'BC' MOV C,L CALL SETTRK ; BIOS set track ; ; Now do the record read ; CALL DREAD ; BIOS disk read ORA A ; Set flags PUSH PSW ; Save error flag CALL INCREC ; Increment record count LHLD RECORD ; Get logical record # INX H ; We want to increment to next XCHG ; But first,check overflow LHLD SPT ; By doing (recpertrk-record) CALL SUBDE ; Do the subtraction XCHG JNC NOOVF ; Jump if not record > recpertrk ; ; Record overflow...bump track number, reset record ; LHLD TRACK INX H SHLD TRACK SHLD DECWRK ; Decout destroys LXI D,TRKMSG CALL PSTRNG LXI H,DECWRK CALL DECOUT MVI A,' ' CALL TYPE CALL STOP ; See if console wants to quit LXI H,1 ; New record number on next track ; NOOVF: SHLD RECORD ; Put record away POP PSW ; Get back error flags POP B ; Restore group number RET ;..... ; ; Convert logical record # to physical ; LTOP: LHLD RECTBL ; Set up parameters XCHG ; For call to RECTRAN LHLD RECORD MOV B,H MOV C,L DCX B ; Always call RECTRN w/zero-rel sec # ; RECT1: CALL RECTRN ; Do the record translation LDA SPT+1 ; Check if big tracks ORA A ; Set flags (tracks > 256 records) JNZ LTOP1 ; No so skip LDA TRACK ; Check for track 0 MOV B,A LDA TRACK+1 ; High order ORA B JNZ LTOP1 ; Not track 0 ;;; MOV H,A ; Zero out upper 8 bits ; LTOP1: SHLD PHYREC ; Put away physical record RET ;..... ; ; Direct BIOS calling is done here... ; SETDSK: JMP $-$ ; Filled in by SETUP SETTRK: JMP $-$ ; Filled in by SETUP SETREC: JMP $-$ ; Filled in by SETUP DREAD: JMP $-$ ; Filled in by SETUP RECTRN: JMP $-$ ; Filled in by SETUP ; ; Put bad block in bad block list ; SETBD: PUSH B LXI D,BBMSG ; Bad block message CALL PSTRNG POP B ; Get back block number MOV A,B CALL HEXO ; Print in hex MOV A,C CALL HEXO CALL CRLF LXI H,DM ; Point to exitsing bad blocks ; SETBD2: MOV A,M ; Get first 8 bits of bad map entry INX H CMP C ; Is new entry already there ? JZ SETBD4 ; Maybe LDA DSM+1 ; Check size of block entries ORA A JZ SETBD3 ; Small blocks INX H ; Skip over high order half ; SETBD3: PUSH H XCHG ; Save 'HL' LHLD DMPTR XCHG CALL SUBDE ; Scan pointer-(DMPTR) POP H ; Restore scan pointer JC SETBD2 ; Continue searching JMP SETBD9 ; Put it away SETBD4: LDA DSM+1 ORA A RZ ; Small blocks = done MOV A,M ; Get high order half of existing block CMP B ; Compare with block to be added RZ ; Really already there INX H ; Point to low order of next block JMP SETBD3 ; Check if done ; SETBD9: LHLD DMCNT ; Get number of records LDA BLM ; Get block shift value INR A ; Makes record/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 records 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 RET ; Return from 'SETBD' ;..... ; ; Eliminate any previous [UNUSED].BAD entries ; ERAB: LXI D,BFCB ; Bad FCB XRA A STA BFCB+12 ; Clear extent MVI C,15 ; Open file CALL BDOS ; Try to open file CPI 0FFH ; Not found ? RZ ; Yes, no need to delete it STA DIROFS ; Directory offset in DMA buffer ; ERAB0: LXI D,ERABMS CALL PSTRNG MVI C,1 ; Console input CALL BDOS CPI 'a' JC ERAB1 ; If less, already in upper case ANI 05FH ; Force upper case ; ERAB1: CPI 'Y' JZ ERAB4 ; Do it CPI 'N' JNZ ERAB0 ; Invalid response CALL ERAB2 ; Load tables, then erase old file ; ERAB4: CALL CRLF LXI D,BFCB ; Point to bad FCB MVI C,19 ; BDOS delete file function CALL BDOS RET ;..... ; ; Flag prior bad blocks for this run ; ERABMS: DB 'Erase Existing [UNUSED].BAD file ? (Y/N) $' ; ;Move bad allocated blocks to DM area ; ERAB2: 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 ERAB2B ; Jump if none ; ERAB2A: DAD H ; Shift DCR A ; Bump JNZ ERAB2A ; Loop ; ERAB2B: PUSH H ; Save this, it is records per extent LDA BSH ; Get block shift MOV B,A ; ERAB2C: CALL ROTRHL ; Shift right DCR B JNZ ERAB2C ; 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 recrds/extent ; ERAB2D: XCHG ; Now have records/extent in 'DE' LHLD DMCNT ; Count of bad records ; ERAB2E: PUSH H PUSH D LDA BFCB+15 ; Record count MOV L,A MVI H,0 MOV B,H ; Save record count in 'BC' MOV C,L POP D CALL SUBDE ; Have to subtract first POP H ; This pop makes it compare only PUSH PSW DAD B ; New count of bad records SHLD DMCNT ; Save count of bad records POP PSW JC ERAB2G ; Jump if less than 1 extent worth MOV A,B ORA C ; Test if subtract was 0 RZ ; Extent is empty (special case) PUSH H ; Save total PUSH D ; And records/extent CALL ERAB2G ; Get next extent POP D ; Get back records/extent POP H ; And count of bad records CPI 0FFH ; Check return from search next RZ JMP ERAB2E ; And loop ; ; Load an extent's worth of bad records/block numbers. 'BC' contains ; the number of records in this extent ; ERAB2G: MOV A,B ORA C ; Check record count MVI A,0FFH ; Assume zero RZ ; No more to process PUSH B ; Save records in this extent LDA BLM ; Block mask INR A ; Make records/block CMA ; Make negative MOV E,A MVI D,0FFH INX D ; Make 2's complement POP H ; Hl = number of records LXI B,0 ; ; Divide records in this extent by recs/block giving blocks ; ERAB2I: DAD D INX B ; Bump quotient JC ERAB2I ; Not done yet DCX B ; Account for overshoot LHLD BADBKS ; Bad block count DAD B SHLD BADBKS ; ERAB2K: LXI H,BFCB+16 ; Disk alloc map in FCB XCHG LHLD DMPTR ; Point to bad allocation map ; ERAB2L: 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 ERAB2M ; If zero, then not LDAX D ; Is 16 bits, so do another MOV M,A INX H INX D ; ERAB2M: DCR C ; Count down JNZ ERAB2L SHLD DMPTR LDA BFCB+12 INR A STA BFCB+12 ; Next extent number LXI D,BFCB MVI C,15 ; Open next extent CALL BDOS STA DIROFS 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...cannot 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 ; Initialize 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 is in 'L' (cannot be 16) STA BLKEXT ; SETDME will need this later POP H ; Get back rececord/extent ; SET1: XCHG ; Now have records/extent in 'DE' LHLD DMCNT ; Count of bad records ; 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 (special case) MOV H,B ; Restore result to 'HL' MOV L,C PUSH H ; Save total PUSH D ; And records/extent XCHG CALL SETDME ; Put away one extent XCHG SHLD DMPTR ; Put back new DM pointer POP D ; Get back records/extent POP H ; And count of bad records 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 with 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 records/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 record count ; ; Divide record count by 128 to get the number of logical extents to put ; in the EX field ; MVI B,0 ; Initialize quotient LXI D,-128 MOV A,H ; Test for special 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 receive 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,BELL,'+++ Warning...system tracks' DB ' bad +++',BELL,CR,LF,'$' ERROR6: LXI D,ERMSG6 ; Oops...clobbered directory JMP PMSG ERMSG6: DB CR,LF,BELL,'Bad directory area, try reformatting' DB CR,LF,BELL,'$' ERROR7: LXI D,ERMSG7 ; Say no go, and bail out JMP PMSG ERMSG7: DB CR,LF,BELL,'Can''t create [UNUSED].BAD$' BELLS: DB BELL,BELL,BELL,'$' ; ; ;======================================================================= ; ; SUBROUTINES ; ;======================================================================= ; ADD24: PUSH H ORA A ; Clear carry MOV A,M ; Do 24-bit add ADC C MOV M,A INX H MOV A,M ADC B MOV M,A INX H MOV A,M ADC B MOV M,A POP H RET ;..... ; ; CR/LF to the 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 ;..... ; ; Decimal output routine - If the disk size is at or near the maximum of ; 65,536 records, the record addition overflows 16 bits. The counter ; has been increased to 24 bits. This routine destroys the counter- ; value it is called with. ; DECOUT: PUSH B PUSH D LXI B,-10 LXI D,-1 ; DECOU2: CALL ADD24 INX D JC DECOU2 LXI B,10 CALL ADD24 MOV A,D ORA E PUSH H MOV A,M MOV M,E MOV E,A INX H MOV A,M MOV M,D MOV D,A INX H MVI M,0 POP H CNZ DECOUT ; Recursive call.. MOV A,E ADI '0' CALL TYPE POP D POP B 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 ;..... ; ; Subroutine to determine the number of groups reserved for the directory ; GETDIR: MVI C,0 ; Initialize bit count LDA AL0 ; Read directory group bits CALL COLECT ; Collect count of directory groups.. LDA AL1 ; In 'C' CALL COLECT MOV L,C MVI H,0 ; 'BC' now has a default start group # SHLD DIRBKS ; Save for later RET ;..... ; ; Print byte in accumulator in hex ; HEXO: PUSH PSW ; Save for second half RRC ; Move into position RRC RRC RRC CALL NYBBLE ; Print most significant nybble POP PSW ; NYBBLE: ANI 0FH ; Low nybble only ADI 90H DAA ACI 40H DAA JMP TYPE ; Print in hex ;..... ; ; Total record count routine (24-bit) ; INCREC: LXI H,RECCNT ; Point to record count MVI A,1 ; Add 1 ORA A ; Clear carry ADC M ; Add current total MOV M,A ; And save 1st byte RNC ; Return if no carry INX H ; Increment pointer MVI A,0 ; Reset 'A' ADC M ; Add 2nd byte MOV M,A ; And save RNC ; Return if no carry INX H ; Increment pointer MVI A,0 ; And again for 3rd byte ADC M MOV M,A 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 parameter 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 ;..... ; ; Move from (HL) to (DE), with count in (BC) ; MOVE: MOV A,M STAX D INX H INX D DCR B JNZ MOVE RET ;..... ; ; Negate HL ; NEG: MOV A,L CMA MOV L,A MOV A,H CMA MOV H,A INX H RET ;..... ; ; Shift HL right one place ; ROTRHL: ORA A ; Clear carry MOV A,H ; Get hihg byte RAR ; Shift right MOV H,A ; Put back MOV A,L ; Get low byte RAR ; Shift with carry MOV L,A ; Put back RET ;..... ; ; Subroutine to test console for CTL-C abort ; STOP: CALL CSTS ; Get console status ORA A ; Test flags on zero RZ ; Return if no character CALL CI ; Get the character ANI 7FH ; Some teminals set parity bit CPI 'C'-40H ; Is it CTL-C? RNZ ; Return if not LXI D,ABORTM ; Exit with message CALL PSTRNG JMP WBOOT ; Then leave ; CSTS: LDA VER2FL ; One way for 2.x or higher... ORA A ; Check for non-zero JZ CSTS14 MVI C,11 JMP BDOS ; CSTS14: LHLD 0001H ; Find BIOS in memory MVI L,6 ; Offset to console status PCHL ; CI: LDA VER2FL ORA A JZ CI14 MVI C,1 JMP BDOS ; CI14: LHLD 1 ; Now find console input MVI L,9 ; Offset for CONIN PCHL ;..... ; ; Subtract DE from HL ; SUBDE: MOV A,L SUB E MOV L,A MOV A,H SBB D MOV H,A RET ;..... ; ; Zero all of memory to hold DM values ; ZMEM: LHLD BDOS+1 ; Get top-of-memory 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 ;..... ; ; Messages ; SIGNON: DB 'FBAD v',(VERS/10 + '0'),(VERS MOD 10)+'0' DB ' - Bad record lockout program - (^C to abort)',CR,LF,'$' ABORTM: DB CR,LF,LF,'Test aborted by CTL-C',CR,LF,'$' BBMSG: DB CR,LF,'Bad block: $' DIROFS: DB 0 ; Offset of directory entry in DMA buffer DRVMSG: DB CR,LF,'Checking drive: ','$' ENDMSG: DB ' bad blocks found',CR,LF,'$' ERRMSG: DB CR,LF,'+++ Drive must be A-P +++',CR,LF,'$' ERRDRV: DB CR,LF,'+++ Drives only go to ','$' ERRDRW: DB 'P',': +++',CR,LF,'$' NOMSG: DB 'No','$' RECMSG: DB ' directory/data records read',CR,LF,'$' SYSMSG: DB ' system records read','$' TDAMSG: DB CR,LF,'Testing data area...',CR,LF,'$' TDRMSG: DB CR,LF,'Testing directory area...',CR,LF,'$' TRKMSG: DB CR,' track ','$' TSTMSG: DB CR,LF,'Testing system tracks...',CR,LF,'$' BFCB: DB 0,'[UNUSED]BAD',0,0,0,0 FCBDM: DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 CMSG1: DB CR,LF,'Press C to continue on the same drive' DB CR,LF,'Any other key aborts to CP/M $' ; ; The disk parameter block is moved here from CP/M ; DPB EQU $ ; Disk parameter block (copy) SPT: DS 2 ; Records 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 DPBLEN EQU $-DPB ; Length of disk parameter block ; ; Data area ; BLKEXT: DB 0 ; Blocks per extent DIRBKS: DW 0 ; Calculated # of directory blocks VER2FL: DB 0 ; Version 2.x flag, non-0 ==> 2.x or higher BADBKS: DB 0,0,0 ; Count of bad blocks RECORD: DW 0 ; Current record number TRACK: DB 0,0 ; Current track number PHYREC: DW 0 ; Current physical record number RECTBL: DW 0 ; Record skew table pointer EXTNUM: DB 0FFH ; Used for updating extent number DMCNT: DW 0 ; Number of bad records DMPTR: DW DM ; Pointer to next block identification RECCNT: DB 0,0,0 ; Number of records read DECWRK: DB 0,0,0 ; Track-storage for decout PRIOR: DB 0 ; Shows we have a logged-in drive already ORIGDR: DS 1 ; Original drive spec from FCB stored here ; DS 200 ; Room for 100 level stack ; ORG ($+255)/256*256 ; DM EQU $ ; Bad block allocation map STACK EQU DM-2 ; Our stack ; END