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