; FINDBAD.ASM ver. 5.2 ; (revised 04/10/81) ; ;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 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 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) ; ;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 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 (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 0 ;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.2' 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 creadt 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 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 ; 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 '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