; COPYFAST.ASM Version 4.3 ;MINOR REVISIONS ; ; Note: to save people time, don't bother to change ; the DOC file just to update the change history. ; ORG 0100H ; ; ; Equates ; FALSE EQU 0 ; define false TRUE EQU NOT FALSE; define true ; EXITCP EQU 0 ; warm start return to CP/M FCB EQU 5CH ; default FCB address ; CR EQU 0DH ; ASCII Carriage return LF EQU 0AH ; ASCII line feed CTRLC EQU 3 ; ASCII control-C ; ; User-modifiable switches ; SINGLE EQU FALSE ; TRUE for single drive copy program ; NOCOMP EQU FALSE ; TRUE if no read checking at all ; ; (DOCOMP MUST BE FALSE) ; ; FALSE if read checking is done, ; ; check DOCOMP DOCOMP EQU TRUE ; TRUE if byte-by-byte comparison ; ; desired on read-after-write check ; ; Must be FALSE if NOCOMP is TRUE IF NOCOMP AND DOCOMP DOCOMP SET FALSE ; cause error ENDIF ; NUMERR EQU 4 ; number of error retries done ; BUFFNU EQU 0 ; the number of full track buffers ; that will fit in your system. This figure includes ; the space used by the read-back buffers, if used ; (minimum 2). If zero, the number of buffers will ; be automatically computed at execution. ; ; The next two values specify the copy range, and the program ; can be run in other ways by the parameter (first character ; of the first filename) given when COPYFAST is first invoked: ; (Note: only complete tracks are copied) ; ; All 0-(Lastrk-1) *** Entire disk ; Data Firstrk-(Lastrk-1) CP/M data area ; First Firstrk CP/M directory track ; Last (Lastrk-1) Last track on disk ; One 1 Track one, UCSD directory ; Pascal 1-(Lastrk-1) UCSD Pascal data area ; System 0-(Firstrk-1) *** CP/M bootstrap ; Zero 0 *** Track zero, UCSD bootstrap ; nn nn One track, as specified ; n1-n2 n1-n2 A specified range ; *** NOTE: this option parameter is ; functional only if CPM is TRUE. ; ; The default range, currently Firstrk to Lastrk-1, is given ; in the two values at TRKSRT. ; FIRSTRK EQU 2 ; the first data track copied. ; ; The bootstrap is assumed to be ; ; on tracks 0 to Firstrk-1 LASTRK EQU 76 + 1 ; the last track copied plus one ; DIFFTRK EQU 0 ; difference between first source ; ; track and the first object track. ; ; (applies only when default range ; ; is used) ; ; Only one of the following should be TRUE ; CPM EQU TRUE ; TRUE for CP/M copy (thru BIOS) DJ2D EQU FALSE ; TRUE for Disk Jockey 2D (thru eprom) MMATION EQU FALSE ; TRUE for Micromation Doubler at double ; ; density (thru eprom) CCS2422 EQU FALSE ; TRUE for CCS 2422 (thru eprom) ; ; IF CPM ; currently set up for fast controller SDLAST EQU 26 ; the number of sectors per track ; ; Also determines the lengths of ; ; WRTAB, READTAB, and WRITAB ; ; CP/M 2 users: this must be the ; ; value in the first byte of the ; ; disk parameter block. SDZERO EQU 26 ; the number of 128-byte sectors on ; ; track zero. This is usually 26 ; ; even on double-density disks, ; ; per the IBM standard. RSKEW EQU FALSE ; TRUE if read interleaving needed ; ; Note: change READTAB if TRUE SLOW EQU FALSE ; TRUE if slower interleaving wanted TSKEW EQU 5 ; Amount of track-to-track skew ; ; (if RSKEW is FALSE) ; ; Should be less than SDLAST WRSWCH EQU FALSE ; TRUE if CP/M 2.2 block/deblock ; ; routines need various values in ; ; reg. C during writes. See WRTAB WRCODE EQU 2 ; value passed to sector write rtn ; ; in reg. C if WRSWCH is FALSE DDEN EQU FALSE ; not used in CP/M DSIDE EQU FALSE ; not used in CP/M SECSIZ EQU 128 ; Note: 128 if CP/M BIOS is used ENDIF IF NOT CPM WRSWCH EQU FALSE ; just to eliminate assembly errors WRCODE EQU 2 SDZERO EQU 26 SLOW EQU FALSE ENDIF ; IF DJ2D ;SDLAST ; will be set later RSKEW EQU FALSE ; No read interleave needed ; ; with version 1.5 board @ 4mhz ; NOTE for pre-revision B boards, RSKEW may be TRUE ; ; when SECSIZ is either 128 ; ; (DDEN FALSE), or 256, 512, ; ; or 1024 (DDEN TRUE). TSKEW EQU 2 ; Amount of track-to-track skew ; ; Should be less than SDLAST DSIDE EQU FALSE ; TRUE if double-sided drive DDEN EQU FALSE ; TRUE if double density ; ; The following value can also be 256, 512, or 1024 SECSIZ EQU 128 ; number of bytes per sector. DJPROM EQU 0F800H ; EPROM address (board version 1.5) ENDIF ; IF MMATION ; NOTE: Check WRTAB and change, if necessary ;SDLAST ; Will be set later RSKEW EQU FALSE ; No read interleaving needed TSKEW EQU 3 ; Amount of track-to-track skew SECSIZ EQU 128 ; number of bytes per sector. DSIDE EQU FALSE ; TRUE if double sided DDEN EQU TRUE ; always true (for SD copies, use CPM) MMPROM EQU 0F800H ; EPROM address ENDIF ; IF CCS2422 ;SDLAST ; Will be set later RSKEW EQU TRUE ; read interleave needed TSKEW EQU 0 DSIDE EQU FALSE ; not yet implemented DDEN EQU FALSE ; TRUE if double density ; ; The following value can also be 256, 512, or 1024 SECSIZ EQU 128 ; number of bytes per sector. CCSPROM EQU 0F000H ; EPROM address (MOSS version 1.1) ENDIF ; ; ; the following shennanigans are because ASM does not ; have an EQ operator for comparisons, and neither ASM ; nor MAC will perform an IF exactly as described in ; the manual. Therefor, a TRUE value is constructed ; with AND's and shift's and OR's. ; XEC1024:EQU (SECSIZ-1024) AND 0FC00H SEC1024:EQU NOT ((XEC1024) OR (XEC1024 SHR 5) OR (XEC1024 SHR 10)) ; SEC1024 is TRUE if 1024 byte sectors XEC512: EQU ((SECSIZ-512) OR SEC1024) AND 0FE00H SEC512: EQU NOT ((XEC512) OR (XEC512 SHR 5) OR (XEC512 SHR 10)) ; SEC512 is TRUE if 512 byte sectors XEC256: EQU ((SECSIZ-256) OR SEC1024 OR SEC512) AND 0FF00H SEC256: EQU NOT ((XEC256) OR (XEC256 SHR 8)) ; SEC256 is TRUE if 256 byte sectors SEC128: EQU NOT (SEC1024 OR SEC512 OR SEC256) ; SEC128 is TRUE if not 256, 512, or 1024 ; XXXSKW: EQU (0-TSKEW) AND 0FF00H TRSKW: EQU ((XXXSKW) OR (XXXSKW SHR 8)) AND (NOT RSKEW) ; IF MMATION AND (NOT DSIDE) SDLAST EQU 52 ENDIF IF MMATION AND DSIDE SDLAST EQU 104 ENDIF ; DJ2SS EQU DJ2D AND (NOT DSIDE) AND (NOT DDEN) DJ2SD EQU DJ2D AND (NOT DSIDE) AND DDEN DJ2DS EQU DJ2D AND DSIDE AND (NOT DDEN) DJ2DD EQU DJ2D AND DSIDE AND DDEN IF DJ2SS AND SEC128 SDLAST EQU 26 ENDIF IF DJ2SD AND SEC128 SDLAST EQU 51 ENDIF IF DJ2SS AND SEC256 SDLAST EQU 16 ENDIF IF DJ2SD AND SEC256 SDLAST EQU 26 ENDIF IF DJ2SS AND SEC512 SDLAST EQU 8 ENDIF IF DJ2SD AND SEC512 SDLAST EQU 15 ENDIF IF DJ2SS AND SEC1024 SDLAST EQU 4 ENDIF IF DJ2SD AND SEC1024 SDLAST EQU 8 ENDIF IF DJ2DS AND SEC128 SDLAST EQU 52 ENDIF IF DJ2DD AND SEC128 ; error, not supported ENDIF IF DJ2DS AND SEC256 SDLAST EQU 32 ENDIF IF DJ2DD AND SEC256 SDLAST EQU 52 ENDIF IF DJ2DS AND SEC512 SDLAST EQU 16 ENDIF IF DJ2DD AND SEC512 SDLAST EQU 30 ENDIF IF DJ2DS AND SEC1024 SDLAST EQU 8 ENDIF IF DJ2DD AND SEC1024 SDLAST EQU 16 ENDIF ; IF CCS2422 AND SEC128 AND (NOT DDEN) SDLAST EQU 26 ENDIF IF CCS2422 AND SEC128 AND DDEN ; error, not supported ENDIF IF CCS2422 AND SEC256 AND (NOT DDEN) SDLAST EQU 15 ENDIF IF CCS2422 AND SEC256 AND DDEN SDLAST EQU 26 ENDIF IF CCS2422 AND SEC512 AND (NOT DDEN) SDLAST EQU 8 ENDIF IF CCS2422 AND SEC512 AND DDEN SDLAST EQU 15 ENDIF IF CCS2422 AND SEC1024 AND (NOT DDEN) ; error, not supported ENDIF IF CCS2422 AND SEC1024 AND DDEN SDLAST EQU 8 ENDIF ; ; A set of dummy branch points to the CBIOS that are ; filled in by the VECTOR routine. ; START: JMP VECTOR ; go initialize the branches WBOOT: JMP $-$ ; not used CONST: JMP $-$ CONIN: JMP $-$ CONOUT: JMP $-$ LIST: JMP $-$ ; not used PUNCH: JMP $-$ ; not used READER: JMP $-$ ; not used HOME: JMP $-$ SELDIS: JMP $-$ SETRAK: JMP $-$ SETSCT: JMP $-$ SETDMA: JMP $-$ READ: JMP $-$ WRITE: JMP $-$ LISTST: JMP $-$ ; not used SECTRAN: JMP $-$ ; only CPM 2.2 ; ; Useful constants placed here for finding easily ; These can be changed using DDT to alter some of ; the characteristics of the program to suit your ; taste. ; TRKSRT: ; default first and last+1 track numbers ; ; Can be changed at run time DB FIRSTRK DB LASTRK BUFFNMB: ; max. number of buffers DB BUFFNU SRCTRAK: ; source track - object track DB DIFFTRK ; ; This is the point where the program returns to repeat the ; copy. Everything is re-initialized. ; REPEAT: LXI SP,STKTOP ; se-initialize stack LXI D,SOURCE CALL PRINT ; ask for source drive SRCELU: CALL CONIN ; read response (upper case) CPI CTRLC JZ EXIT ; CTRL-C means abort ANI 5FH CPI 'A' ;41H JC SRCELU ; bad value - less than A CPI 'F' ;46H JZ SETSOU JC SETSOU JMP SRCELU ; cad value - greater than F SETSOU: STA SRCEME ; save the source drive IF SINGLE STA OBJMES ENDIF SUI 'A' ;41H STA SRCEDR ; convert value to CP/M number LDA SRCEME MOV C,A CALL CONOUT ; echo value to console IF NOT SINGLE LXI D,OBJECT ; prompt for destination disk CALL PRINT OBJLUP: ; read response CALL CONIN CPI CTRLC ; CTRL-C means abort JZ EXIT ANI 5FH ; convert to upper case CPI 'A' ;41H JC OBJLUP ; bad value - less than A CPI 'F' ;46H JZ SETOBJ JC SETOBJ JMP OBJLUP ; bad value - greater than F SETOBJ: LXI H,SRCEME ; Cannot have a one drive copy CMP M JZ OBJLUP STA OBJMES ; save the destination drive SUI 'A' ;41H STA OBJDRI ; convert value to CP/M number LDA OBJMES MOV C,A CALL CONOUT ; echo object drive ENDIF LXI D,SIGNON CALL PRINT ; now give chance to change disks ; ; or give up AGIN: CALL CONIN ; read response from keyboard CPI CTRLC JZ EXIT ; ctrl-C means quit CPI CR JNZ AGIN ; CR means go. Ignore anything else ; ; now go do it ! ; LXI D,CRLF CALL PRINT ; now start actual copy CALL COPY LXI D,DONMSG CALL PRINT ; copy is now done, say so ; ; end of this copy ; EXIT: LXI SP,STKTOP ; re-initialize stack LXI H,0FFFFH ; and maybe flush buffers (MP/M) CALL SETDMA LDA SRCEDR ; first, select source drive MOV C,A CALL SELDSK CALL HOME ; home the disk in case IF NOT SINGLE LDA OBJDRI MOV C,A ; now, select destination drive CALL SELDSK CALL HOME ; and home that disk, in case ENDIF EXIT1: LXI D,REPMES ; ask if another copy is desired CALL PRINT CALL CONIN ; read response, upper case ANI 5FH CPI 'R' ; R means repeat JZ REPEAT CPI CR ; carriage return means back to CP/M JNZ EXIT1 MVI C,0 ; set default disk back to A MVI E,01 CALL SELDSK JMP EXITCP ; and warmstart back to CP/M ; ; convert value in A reg. to ASCII hex and print it ; PRTHEX: PUSH PSW ; save for LSN RAR RAR ; shift MSN nibble to LSN RAR RAR CALL PRTNBL ; now print it POP PSW ; and then do LSN PRTNBL: ANI 0FH ADI '0' ;convert to ASCII value CPI '0'+10 ; over 9 ? JC SML ADI 7 ; convert 10 to A, etc. SML: MOV C,A ; move to C for BDOS call CALL CONOUT RET ; ; ; this is the main copy routine ; COPY: LDA SRCEDR ; first, select source drive MOV C,A IF CPM MVI E,0 ; logon request (2.2 deblocking) ENDIF CALL SELDSK CALL HOME ; home the disk first, in case ; ; the controller requires it. ; ; (this might be the first time ; ; the drive has been used) LDA TRKSRT CALL SETTRK ; now start with first track IF NOT SINGLE LDA OBJDRI MOV C,A ; now, select destination drive ENDIF IF CPM AND (NOT SINGLE) MVI E,0 ; logon request (2.2 deblocking) ENDIF IF NOT SINGLE CALL SELDSK CALL HOME ; and home that disk, in case ENDIF ; ; return here to continue copy ; RDLOOP: LDA TRK ; note current track STA TRKSAV XRA A ; reset error counter STA CMPERR LXI D,TRKM ; print the current starting track CALL PRINT ; being copied LDA TRKSAV CALL PRTHEX TRYRDA: IF SINGLE LXI D,SIGNON ; now give operator chance to change disk ENDIF LDA SRCEDR ; select source drive ; ; read loop ; CALL STARTL ; start the copy loop (reading source) LOOP1: CALL READT ; read one track JZ LOOP4 ; if all tracks read, go check errors LDA ERR1 ORA A ; not all done, but see if error already JNZ LOOP1 ; and go try another track ; ; now see if any errors in the previous operations ; LOOP4: LDA ERR1 ; now check if any errors ORA A JNZ RDSKIP ; jump if no errors at all MVI A,10H STA ERR1 ; reset error flag ; ; allow NUMERR errors before giving up ; LDA CMPERR ; check the retry counter INR A STA CMPERR CPI NUMERR ; normally ten retries max JNZ LOOP1 ; WAS TRYRDA LXI D,MESGC ; if maximum error count, CALL PRINT ; print message XRA A STA CMPERR ; full track error, reset error counter CALL ENDLUP JNZ LOOP1 ; now bump up track and see if done ; ; write loop ; RDSKIP: XRA A ; reset error counter STA CMPERR TRYAGA: IF SINGLE LXI D,OBJMSG ; give chance to put in object disk ENDIF LDA OBJDRI ; now select destination disk CALL STARTL ; start the write loop LOOP2: CALL WRITET ; write one track (and readback check) JZ LOOP3 ; if all tracks written, go check errors LDA ERR1 ORA A ; not all done, but see if error already JNZ LOOP2 ; ; now see if any errors in the previous operations ; LOOP3: LDA ERR1 ; now check if any errors ORA A JNZ SKIP ; jump if no errors at all ; ; allow NUMERR errors before giving up ; LDA CMPERR ; check the retry counter INR A STA CMPERR CPI NUMERR ; normally ten retries max JNZ TRYAGA LXI D,MESGC ; if maximum error count, CALL PRINT ; print message LDA BUFFNMB MOV H,A LDA TRK ; and set next track INR A ; past track in error SUB H STA TRKSAV ; ; copied all tracks correctly (or NUMERR errors) ; SKIP: LDA BUFFNMB ; get number of buffers MOV H,A LDA TRKSAV ; bump up track counter ADD H STA TRK LXI H,TRKSRT+1 ; see if copy operation is done CMP M RNC JNZ RDLOOP ; go back and do more RET ; ; This routine selects the disk, and initializes the buffer ; address, buffer counter, and track counter,and seeks to the ; right track. ; STARTL: IF SINGLE PUSH D ; Preserve register LXI H,0FFFFH ; and maybe flush buffers (MP/M) CALL SETDMA CALL HOME ; Home the disk for a deblocking CBIOS ; ; to get a chance to flush the buffer POP D ; Restore register CALL PRINT ; now give chance to change disks ; ; or give up AGIN1: CALL CONIN ; read response from keyboard CPI CTRLC JZ EXIT ; CTRL-C means quit CPI CR JNZ AGIN1 ; CR means go. Ignore anything else ENDIF IF NOT SINGLE MOV C,A ; select the disk first ENDIF IF CPM AND NOT SINGLE MVI E,1 ; no logon here (2.2 deblocking) ENDIF IF NOT SINGLE CALL SELDSK ENDIF IF TRSKW XRA A ; zero out track sector skew STA TSECT STA TBUFF ; zero out coresponding buffer addr STA TBUFF+1 ENDIF LXI H,BUF0 ; load address of first buffer SHLD BUF0SA MVI A,10H ; reset error flag STA ERR1 LDA BUFFNMB ; load number of buffers STA BUFFCO LDA TRKSAV ; load first track copied ; ; set the track to be used, and add offset if source ; drive. Save track number for error routine. ; SETTRK: STA TRK ; save current track IF (NOT SINGLE) LDA CURRDI ; check drive MOV C,A LDA SRCEDR ; is it source CMP C LDA TRK ; if object, skip JNZ SETTR0 MOV C,A ; now get difference LDA SRCTRAK ADD C ; and do correction SETTR0: ENDIF MOV C,A ; now go set track JMP SETRAK ; ; set the DMA address (in HL) ; DMASET: MOV C,L ; move HL to BC MOV B,H PUSH B ; save result and call CBIOS CALL SETDMA POP B RET ; ; these are the disk error handling routines ; FAILR: LXI D,MESGD ; read error message JMP DIE FAILW: LXI D,MESGE ; write error message DIE: CALL PRINT ; print the main error message LXI D,ERM CALL PRINT LDA TRK ; print the track number CALL PRTHEX LXI D,MESGB ; print sector message CALL PRINT LDA SECTOR ; and print sector CALL PRTHEX LXI D,DRIVE ; print drive message CALL PRINT LDA CURRDI ADI 'A' ; convert drive number to ASCII MOV C,A CALL CONOUT ; and finally print drive XRA A STA ERR1 ; note the error so this track is retried CALL CONST ORA A ; see if any console input present JZ ENDLUP CALL CONIN ; yes, see if aborting CPI CTRLC JZ EXIT ; die if CTRL-C was hit JMP ENDLUP ; ; read the full track now, no interleaving ; READT: CALL CONST ORA A ; see if any console input present JZ READT0 CALL CONIN ; yes, see if aborting CPI CTRLC JZ EXIT ; die if CTRL-C was hit READT0: IF (NOT RSKEW) AND (NOT TRSKW) LHLD BUF0SA ; first, get beginning of buffer SHLD DMAAD ENDIF IF TRSKW LHLD BUF0SA ; first, get beginning of buffer XCHG LHLD TBUFF ; and correct for skew DAD D SHLD DMAAD LDA TSECT ; initialize first sector MOV C,A ENDIF IF (NOT TRSKW) MVI C,0 ; initialize first sector ENDIF MVI B,SDLAST ; initialize sector count RT3: IF TRSKW MOV A,C ; check for skew too big CPI SDLAST JC RT4 ; jump if sector within range XRA A MOV C,A ; out of range, back to sector 1 LHLD BUF0SA SHLD DMAAD RT4: ENDIF IF RSKEW INR C ; increment sector counter PUSH B LXI H,READTAB-1 ; find the interleaved sector number MVI B,0 DAD B ; using the READTAB MOV C,M CALL SETSEC ; and set the sector MVI H,0 DCR C ; now compute the buffer location MOV L,C CALL SHIFT ; and multiply by sector size XCHG LHLD BUF0SA ; and then adding to the buffer start DAD D CALL DMASET ; set the DMA and do the read ENDIF IF (NOT RSKEW) INR C ; increment sector counter PUSH B CALL SETSEC ; set the sector LHLD DMAAD CALL DMASET ; set the DMA LXI H,SECSIZ DAD B ; bump up the DMA for next time SHLD DMAAD ENDIF IF CPM LDA TRK ; see if track 0 ORA A JNZ ZER2 ; jump if not LDA SECTOR CPI SDZERO+1 ; see if sector is on track JNC ZER28 ZER2: ENDIF CALL READ ; now read one sector RAR CC FAILR ; if returned 01, read error ZER28: POP B DCR B ; see if all sectors read JNZ RT3 IF TRSKW LHLD TBUFF ; bump up skewed buffer LXI D,SECSIZ*TSKEW DAD D ; add the skew SHLD TBUFF LDA TSECT ; now bump starting sector ADI TSKEW STA TSECT ; and put it back SBI SDLAST JC ENDLUP ; jump if sector within range STA TSECT LHLD TBUFF LXI D,-SDLAST*SECSIZ; correct sector start and DAD D SHLD TBUFF ; buffer skew address ENDIF JMP ENDLUP ; return with complete track read ; ; Write the full track, with interleaving, and then check it ; by reading it all back in. ; WRITET: CALL CONST ORA A ; see if any console input present JZ WRITE0 CALL CONIN ; yes, see if aborting CPI CTRLC JZ EXIT ; die if CTRL-C was hit WRITE0: LHLD BUF0SA ; first, get the beginning of buffer SHLD DMAAD MVI C,0 MVI B,SDLAST ; initialize sector counter WT3: PUSH B LXI H,WRITAB ; find the interleaved sector number MVI B,0 DAD B ; using the WRITAB MOV C,M CALL SETSEC ; and set the sector MVI H,0 DCR C ; now compute the buffer location MOV L,C CALL SHIFT ; and multiply by sector size XCHG LHLD DMAAD ; and then adding to the buffer start DAD D CALL DMASET ; set the DMA and do the write IF (NOT WRSWCH) AND CPM MVI C,WRCODE ; value for CP/M 2.2 routine ENDIF IF WRSWCH AND CPM POP B ; get sector number PUSH B LXI H,WRTAB-1 ; find the C reg. value for this MVI B,0 DAD B ; sector using the WRTAB MOV C,M ENDIF IF CPM LDA TRK ; see if track 0 ORA A JNZ ZER1 ; jump if not LDA SECTOR CPI SDZERO+1 ; see if sector is on track JNC ZER18 ZER1: ENDIF CALL WRITE RAR ; if 01 returned, write error CC FAILW ZER18: POP B INR C ; increment sector count DCR B JNZ WT3 ; and loop back if not done IF DOCOMP AND (NOT RSKEW) LXI H,BUF1 ; first, get beginning of buffer SHLD DMAAD ENDIF MVI C,0 MVI B,SDLAST ; reinitialize sector counts for read WT4: INR C ; bump up sector counter PUSH B IF RSKEW LXI H,READTAB-1 ; find the interleaved sector number MVI B,0 DAD B ; using the READTAB MOV C,M CALL SETSEC ; and set the sector ENDIF IF RSKEW AND DOCOMP MVI H,0 DCR C ; now compute the buffer location MOV L,C CALL SHIFT ; and multiply by sector size XCHG LXI H,BUF1 ; and then adding to the buffer start DAD D CALL DMASET ; now set the read buffer ENDIF IF (NOT RSKEW) AND DOCOMP CALL SETSEC ; set the sector LHLD DMAAD CALL DMASET ; set the DMA LXI H,SECSIZ DAD B ; bump up the DMA for next time SHLD DMAAD ENDIF IF RSKEW AND (NOT DOCOMP) LXI H,BUF1 ; load the buffer address CALL DMASET ; and set the read buffer ENDIF IF (NOT RSKEW) AND (NOT DOCOMP) CALL SETSEC ; now set the sector LXI H,BUF1 CALL DMASET ; and set the read buffer ENDIF IF CPM LDA TRK ; see if track 0 ORA A JNZ ZER3 ; jump if not LDA SECTOR CPI SDZERO+1 ; see if sector is on track JNC ZER4 ZER3: ENDIF IF NOT NOCOMP CALL READ RAR ; was bit 0 set by disk error? CC FAILR ENDIF IF CPM ZER4: ENDIF POP B ; no error, see if all sectors read DCR B JNZ WT4 ; if not all done, go back IF DOCOMP LXI B,SECSIZ*SDLAST ; now, compare the track read in ENDIF IF CPM AND DOCOMP LDA TRK ; see if track 0 ORA A JNZ ZER5 ; jump if not LXI B,SECSIZ*SDZERO ZER5: ENDIF IF DOCOMP LHLD BUF0SA LXI D,BUF1 CMPLP: LDAX D ; get read data CMP M JNZ CERR ; and if not what was written, error INX H INX D ; bump counters DCX B MOV A,C ; and count BC down to zero ORA B JNZ CMPLP ; if all done, return JMP ENDLUP ; ; print read verify compare error ; CERR: PUSH H ; save the goodies PUSH D PUSH B LXI D,MESGA ; start the error message CALL PRINT LDA TRK ; print the track number CALL PRTHEX LXI D,MESGB ; print more CALL PRINT POP H ; pop the down counter DCX H ENDIF IF SEC128 AND DOCOMP DAD H ; multiply by 2 to get sectors left ENDIF IF SEC512 AND DOCOMP MOV A,H ORA A ; shift right to get sectors left RAR MOV H,A ; and back to H ENDIF IF SEC1024 AND DOCOMP MOV A,H ORA A ; clear carry RAR ORA A ; shift right 2 to get sectors left RAR MOV H,A ; and back to H ENDIF IF DOCOMP MVI A,SDLAST SUB H ; subtract from total number of sectors CALL PRTHEX ; to get sector number, and print it LXI D,MEM CALL PRINT ; print second line POP H MOV A,M ; get byte read STA DATA1 ; and save it PUSH H MOV A,H ; print high order byte of address CALL PRTHEX POP H MOV A,L ; print low order byte of address CALL PRTHEX MVI C,',' CALL CONOUT ; comma POP H MOV A,M ; get byte written STA DATA2 ; and save it PUSH H MOV A,H ; print high order byte of address CALL PRTHEX POP H MOV A,L ; print low order byte of address CALL PRTHEX LXI D,DATAM ; print data header CALL PRINT LDA DATA1 ; print byte read CALL PRTHEX MVI C,',' ; comma CALL CONOUT LDA DATA2 ; print byte written CALL PRTHEX XRA A STA ERR1 ; note the error so this track is retried ENDIF ; ; This routine is used to check if another track is to be ; read/written: it increments buffer address and track ; counter, and decrements the buffer counter. Then, it ; terminates the loop if all buffers are full or the last ; track has been processed (Z flag set). ; ENDLUP: LDA ERR1 ; now check if any errors ORA A ; and return if so RZ LDA TRK ; increment track INR A LXI H,TRKSRT+1 ; check if last track CMP M RZ ; return if last track CALL SETTRK LXI H,BUFFCO ; decrement buffer counter DCR M RZ ; return if all buffers full/empty LXI D,SECSIZ*SDLAST LHLD BUF0SA ; increment buffer address DAD D SHLD BUF0SA ORI 255 ; non-zero to indicate more RET ; ; this routine writes messages to the console. Message ; address is in DE, and terminates on a $. The BDOS call is ; not used here because BDOS may be destroyed by the track ; buffers ; PRINT: LDAX D ; get the character CPI '$' ;24H RZ ; quit if $ PUSH D MOV C,A ; send it to the console CALL CONOUT POP D ; go check next character INX D JMP PRINT ; ; set the next sector to be used, and save that ; number for the error routine, in case ; SETSEC: MOV A,C ; save the sector number STA SECTOR PUSH B ; save regs, in case IF CPM LXI D,0 ; if CP/M 2.2, no translation DCR C CALL SECTRAN POP B PUSH B ENDIF IF DJ2D AND DSIDE SUI SDLAST/2 ; see if on other side JZ DJR1 JC DJR1 MOV C,A ; correct sector number PUSH B MVI C,1 CALL DJPROM+30H ; set correct side JMP DJR2 DJR1: PUSH B ; save C (sector) MVI C,0 CALL DJPROM+30H ; set side 0 DJR2: POP B ENDIF CALL SETSCT ; now go set the sector POP B RET ; ; set the disk to be used, and save that ; for the error routine, in case ; SELDSK: MOV A,C ; save the disk number STA CURRDI JMP SELDIS ; now select the disk ; ; Routine to multiple value in HL by SECSIZ ; This routine currently valid for 128, 256, ; 512, and 1024 bytes/sector. ; SHIFT: DAD H DAD H ; The number of DAD H instructions DAD H DAD H ; MUST correspond to the buffer size DAD H DAD H ; i.e. 7 DADs means 128 byte (2^7) DAD H IF SEC1024 DAD H DAD H ; 1024 = 2 ^ 10 DAD H ENDIF ; IF SEC512 DAD H ; 512 = 2 ^ 9 DAD H ENDIF ; IF SEC256 DAD H ; 256 = 2 ^ 8 ENDIF ; RET ; ; IF CCS2422 CCSDISK: STA 40H ; DISK NUMBER MVI A,0D0H STA 43H ; SIDE ZERO RET CCSTRAK: STA 41H ; TRACK NUMBER RET CCSSECT: STA 42H ; SECTOR NUMBER RET CCSDMA: MOV H,B MOV L,C SHLD 4CH ; DMA ADDRESS RET CCSHOME: XRA A ; HOME THE DISK STA 41H JMP CCSPROM+73BH ENDIF ; ; all messages here for convenience in disassembling ; DONMSG: DB CR,LF,'*** COPY COMPLETE ***$' DRIVE: DB ', DRIVE $' ERM: DB CR,LF,'+ ERROR ON TRACK (HEX)$' MESGB: DB ' SECTOR (HEX)$' MESGC: DB CR,LF,'++PERMANENT $' MESGD: DB CR,LF,'+ READ ERROR $' MESGE: DB CR,LF,'+ WRITE ERROR $' SIGNON: DB CR,LF,'SOURCE ON ' SRCEME: DB 0 ; will be filled in later IF NOT SINGLE DB ': OBJECT ON ' OBJMES: DB 0 ; will be filled in later DB ':' ENDIF SINOFF: DB CR,LF,'HIT TO CONTINUE, OR TO EXIT: $' IF SINGLE OBJMSG: DB CR,LF,'OBJECT ON ' OBJMES: DB 0 ; will be filled in later DB ':' DB CR,LF,'HIT TO CONTINUE, OR TO EXIT: $' ENDIF REPMES: DB CR,LF,' TO CP/M, OR EPEAT COPY: $' CRLF: DB CR,LF,'$' SOURCE: DB CR,LF,'SOURCE DRIVE (A THRU F): $' IF NOT SINGLE OBJECT: DB CR,LF,'OBJECT DRIVE (A THRU F): $' ENDIF TRKM: DB CR,LF,'COPYING TRACK $' ; IF DOCOMP MESGA: DB CR,LF,'+ MEMORY COMPARE ERROR ON TRACK (HEX)$' MEM: DB CR,LF,'+ MEMORY ADDRESS $' DATAM: DB ' (OBJ,SRC) DATA $' ENDIF ; ; This is the sector interleave table. If you want the ; program to work, all sector numbers must be here somewhere. ; WRITAB: ; IF CPM AND (NOT RSKEW) AND (NOT SLOW) ; Interleave table for very fast controllers ; gives time to switch between write and read. DB 25,26,1,2,3,4,5,6,7,8,9,10,11,12 DB 13,14,15,16,17,18,19,20,21,22,23,24 ENDIF IF CPM AND (NOT RSKEW) AND SLOW ; Interleave table for slower controllers DB 25,1,3,5,7,9,11,13,15,17,19,21,23 DB 26,2,4,6,8,10,12,14,16,18,20,22,24 ENDIF IF CPM AND RSKEW AND (NOT SLOW) ; Interleave table for slower controllers DB 25,1,3,5,7,9,11,13,15,17,19,21,23 DB 26,2,4,6,8,10,12,14,16,18,20,22,24 ENDIF IF CPM AND RSKEW AND SLOW ; Interleave table for very slow controllers DB 1,4,7,10,13,16,19,22,25,2,5,8,11 DB 14,17,20,23,26,3,6,9,12,15,18,21,24 ENDIF ; IF MMATION ; this table supplied by C.S. for CP/M 2.2 DB 1,27,10,36,6,32,2,28,11,37,7,33,3,29 DB 12,38,8,34,4,30,13,39,9,35,5,31,14 DB 40,23,49,19,45,15,41,24,50,20,46,16,42 DB 25,51,21,47,17,43,26,52,22,48,18,44 ; this half used only on double sided DB 53,79,62,88,58,84,54,80,63,89,59,85,55,81 DB 64,90,60,86,56,82,65,91,61,87,57,83,66 DB 92,75,101,71,97,67,93,76,102,72,98,68,94 DB 77,103,73,99,69,95,78,104,74,100,70,96 ;; original table by C.W. for CP/M 1.4 @ 4mHz ;; DB 51,1,3,5,7,9,11,13,15,17,19,21,23,25 ;; DB 27,29,31,33,35,37,39,41,43,45,47,49 ;; DB 52,2,4,6,8,10,12,14,16,18,20,22,24,26 ;; DB 28,30,32,34,36,38,40,42,44,46,48,50 ; this half used only on double sided ;; DB 103,53,55,57,59,61,63,65,67,69,71,73,75 ;; DB 77,79,81,83,85,87,89,91,93,95,97,99,101 ;; DB 104,54,56,58,60,62,64,66,68,70,72,74,76 ;; DB 78,80,82,84,86,88,90,92,94,96,98,100,102 ENDIF ; IF DJ2D AND RSKEW AND (SEC128 OR SEC256) ; this is for pre-revision B boards READTAB: DB 1,3,5,7,9,11,13,15,17,19,21,23,25 DB 2,4,6,8,10,12,14,16,18,20,22,24,26 ; used for double sided only DB 27,29,31,33,35,37,39,41,43,45,47,49,51 DB 28,30,32,34,36,38,40,42,44,46,48,50,52 ENDIF IF DJ2D AND RSKEW AND SEC512 ; this is for pre-revision B boards READTAB: DB 1,3,5,7,9,11,13,15 DB 2,4,6,8,10,12,14,16 ; used for double sided only DB 17,19,21,23,25,27,29,31 DB 18,20,22,24,26,28,30,32 ENDIF IF DJ2D AND RSKEW AND SEC1024 ; this is for pre-revision B boards READTAB: DB 1,3,5,7,2,4,6,8 ; used for double sided only DB 9,11,13,15,10,12,14,16 ENDIF ; IF DJ2D AND (NOT RSKEW) ; this is a universal table - no write skewing is ; needed at 4mhz and version 1.5 board B version ; from Roy J Lipscomb DB 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 DB 17,18,19,20,21,22,23,24,25,26,27,28,29 DB 30,31,32,33,34,35,36,37,38,39,40,41,42 DB 43,44,45,46,47,48,49,50,51,52 ENDIF ; IF CCS2422 AND SEC128 AND (NOT DDEN) READTAB: DB 1,3,5,7,9,11,13,15,17,19,21,23,25 DB 2,4,6,8,10,12,14,16,18,20,22,24,26 ENDIF IF CCS2422 AND SEC256 AND (NOT DDEN) READTAB: DB 1,3,5,7,9,11,13,15 DB 2,4,6,8,10,12,14 ENDIF IF CCS2422 AND SEC256 AND DDEN READTAB: DB 1,3,5,7,9,11,13,15,17,19,21,23,25 DB 2,4,6,8,10,12,14,16,18,20,22,24,26 ENDIF IF CCS2422 AND SEC512 AND (NOT DDEN) READTAB: DB 1,3,5,7,2,4,6,8 ENDIF IF CCS2422 AND SEC512 AND DDEN READTAB: DB 1,3,5,7,9,11,13,15 DB 2,4,6,8,10,12,14 ENDIF IF CCS2422 AND SEC1024 AND DDEN READTAB: DB 1,3,5,7,2,4,6,8 ENDIF ; ; fancy skewing for output ; DB 01,02,07,08 ; DB 13,14,19,20 ; DB 25,26,05,06 ; DB 11,12,17,18 ; DB 23,24,03,04 ; DB 09,10,15,16 ; DB 21,22 ; ; ; This is the read skew table, if needed. The same general ; considerations as the write skew table apply here also, but ; the table should start with sector 1. Both the read and the ; read-after write use this table. As you can see, the write ; and read interleaving doesn't have to be the same. ; IF RSKEW AND CPM READTAB: DB 1,3,5,7,9,11,13,15,17,19,21,23,25 DB 2,4,6,8,10,12,14,16,18,20,22,24,26 ENDIF ; ; DJ2D uses no skew (revision B), or same as write ; CCS2422 uses same tables on read and write ; ; ; ; This is the write switch table. The values in this table ; are passed to the sector write routine of CP/M 2.2 in ; reg. C when each write occurs. This table is modified if ; and only if some particular pattern is needed for your ; blocking routine to work as fast or as well as possible. ; Refer to the CP/M 2.2 Alteration Guide for more details. ; IF WRSWCH AND CPM WRTAB: DB 2,2,2,2,2,2,2,2,2,2,2,2,2 DB 2,2,2,2,2,2,2,2,2,2,2,2,2 ENDIF ; ; This is the initialization code, and occupies the lowest area ; of the stack, and may be clobbered by the stack during operation, ; but it is used only once. (The stack is about 40 bytes long) ; VECTOR: LHLD 1 ; get bottom of CBIOS MOV B,H LXI D,SECSIZ*SDLAST ; get size of buffers LXI H,BUF0 ; start checking where buffer starts VECT0: DAD D ; add buffer size to buffer addr JC VECT1 ; stop if at end of core MOV A,H CMP B ; check hi order byte if high JZ VECT1 ; or equal JNC VECT1 MOV A,M ; gonna see if got memory CMA MOV M,A ; store complement in memory CMP M ; and see if it is a good spot JNZ VECT1 LDA BUFTMP ; buffer fits, add one to count INR A STA BUFTMP ; and store JMP VECT0 ; ; the stack ; DS 16 STKTOP: DB 0 ; ; variables ; BUF0SA: ; buffer address DB 0,0 TRKSAV: ; track save area during read and write DB 0 BUFFCO: ; buffer counter DB 0 CMPERR: ; number of disk errors DB 0 TRK: ; current track DB 0 SRCEDR: ; source drive IF NOT SINGLE DB 0 ENDIF OBJDRI: ; destination drive DB 0 CURRDI: ; drive for current operation DB 0 DMAAD: ; DMA address for current operation DB 0,0 ERR1: ; error flag (0 = error) DB 0 SECTOR: ; sector number for current operation DB 0 ; IF TRSKW TSECT: DB 0 ; skewed sector start for track TBUFF: DB 0,0 ; skewed buffer address ENDIF ; ; the track buffers. BUFEND must not overlay the BIOS ! ; ; BUF1 is where the read-after-write is performed ; IF DOCOMP DATA1: DS 1 ; used in compare DATA2: DS 1 BUF1: BUF1END EQU BUF1+(SECSIZ*SDLAST) ; space for a full track read ENDIF ; IF (NOT DOCOMP) AND (NOT NOCOMP) BUF1: BUF1END EQU BUF1+SECSIZ ; just one sector for CRC only ENDIF IF NOCOMP BUF1: BUF1END: ENDIF ; ; BUF0 is where all input tracks are read ; Tho space for only one track is allocated here, ; the program will use BUFFNU track buffers, or ; up to the CBIOS, whichever is smaller ; BUF0: EQU BUF1END BUFEND: EQU BUF0+(SECSIZ*SDLAST) ; ; This is one-time code to initialize the branch table to ; the CBIOS vectors. Only those vectors used are initialized. ; Placed here so that it wont get clobbered by the stack ; VECT1: LHLD 1 ; get warm boot address SPHL ; and save it in SP for DAD LXI H,3 DAD SP SHLD CONST+1 ; LXI H,6 DAD SP SHLD CONIN+1 ; LXI H,9 DAD SP SHLD CONOUT+1 ; IF CPM LXI H,15H ; home disk DAD SP SHLD HOME+1 ; LXI H,18H ; select disk DAD SP SHLD SELDIS+1 ; LXI H,1BH ; set track DAD SP SHLD SETRAK+1 ; LXI H,1EH ; set sector DAD SP SHLD SETSCT+1 ; LXI H,21H ; set dma DAD SP SHLD SETDMA+1 ; LXI H,24H ; read disk DAD SP SHLD READ+1 ; LXI H,27H ; write disk DAD SP SHLD WRITE+1 ; MVI C,12 ; see if got CP/M 2.2 CALL 5 MOV A,H ; check for non-zero ORA L JNZ GRUNJ1 MVI A,RET ; no SECTRAN for CP/M 1.4 STA SECTRAN JMP GRUNJ2 GRUNJ1: LXI H,2DH ; sector translate DAD SP SHLD SECTRAN+1 GRUNJ2: ENDIF ; IF DJ2D ; LXI H,DJPROM+09H ; track zero SHLD HOME+1 ; LXI H,DJPROM+0CH ; select track SHLD SETRAK+1 ; LXI H,DJPROM+0FH ; select sector SHLD SETSCT+1 ; LXI H,DJPROM+012H ; set dma SHLD SETDMA+1 ; LXI H,DJPROM+015H ; read disk SHLD READ+1 ; LXI H,DJPROM+018H ; write disk SHLD WRITE+1 ; LXI H,DJPROM+01BH ; select drive SHLD SELDIS+1 ENDIF ; IF CCS2422 ; LXI H,CCSHOME ; track zero SHLD HOME+1 ; LXI H,CCSTRAK ; select track SHLD SETRAK+1 ; LXI H,CCSSECT ; select sector SHLD SETSCT+1 ; LXI H,CCSDMA ; set dma SHLD SETDMA+1 ; LXI H,CCSPROM+6EAH ; read disk SHLD READ+1 ; LXI H,CCSPROM+6EBH ; write disk SHLD WRITE+1 ; LXI H,CCSDISK ; select drive SHLD SELDIS+1 ENDIF ; IF MMATION ; LXI H,MMPROM+03H ; track zero SHLD HOME+1 ; LXI H,MMPROM+06H ; select drive SHLD SELDIS+1 ; LXI H,MMPROM+09H ; select track SHLD SETRAK+1 ; LXI H,MMPROM+0CH ; select sector SHLD SETSCT+1 ; LXI H,MMPROM+0FH ; set dma SHLD SETDMA+1 ; LXI H,MMPROM+012H ; read disk SHLD READ+1 ; LXI H,MMPROM+015H ; write disk SHLD WRITE+1 ENDIF ; ; Now check what kind of copy is wanted ; LXI SP,STKTOP ; initial stack LXI D,INIT CALL PRINT ; start program LHLD TRKSRT ; LDA FCB+1 ; get character of parameter ANI 5FH CPI 0 ; check for default JZ COPYDEF MOV B,A XRA A ; no track shift STA SRCTRAK MOV A,B CPI 'D' ; check for Data JZ COPYDAT CPI 'F' ; check for First JZ COPYFIR CPI 'L' ; check for Last JZ COPYLAS CPI 'O' ; check for One JZ COPYONE CPI 'P' ; check for Pascal JZ COPYPAS IF CPM CPI 'A' ; check for All JZ COPYALL CPI 'S' ; check for System JZ COPYSYS CPI 'Z' ; check for Zero JZ COPYZER ENDIF MVI A,' ' ; initl end of FCB STA FCB+8 LXI H,FCB+1 CALL GETNUM ; go check for number JNC COPYNUM COPYERR: LXI D,CALLERR ; got a bad value CALL PRINT JMP EXITCP ; ; routine to decode a numeric value or range ; COPYNUM: MOV D,A ; put in lastrk+1 DCR A MOV E,A ; put in first track MOV A,M XCHG CPI ' ' ; check if only one parameter JZ COPYDEF XCHG CPI '-' ; check for minus JNZ COPYERR INX H ; get another number CALL GETNUM JC COPYERR MOV D,A ; put in last track CMP E JC COPYERR MVI A,' ' ; check for last character CMP M JNZ COPYERR XCHG ; all OK, go do it JMP COPYDEF ; GETNUM: MVI A,'0' ; valid digit ? CMP M CMC ; Carry flag if No RC MVI A,'9'+1 CMP M RC SUB A ; initial the number MOV B,A GETLUP: MOV A,B ADD A ; * 2 JC GETER ADD A ; * 4 JC GETER ADD B ; * 5 JC GETER ADD A ; * 10 JC GETER MOV B,A MOV A,M ; get digit SUI '0' ADD B ; add to shifted number JC GETER MOV B,A INX H ; get next character MOV A,M CPI '0' ; check if digit JC GETDUN CPI '9'+1 JC GETLUP GETDUN: INR B ; add 1 (for last track) MVI A,LASTRK CMP B ; check for valid range JC GETER MOV A,B ; all done OK RET GETER: POP H ; gonna leave abnormally JMP COPYERR ; ; implement the alphabetic abbreviations for range ; COPYDAT: MVI H,LASTRK ; Data MVI L,FIRSTRK JMP COPYDEF COPYFIR: MVI H,FIRSTRK+1 ; First MVI L,FIRSTRK JMP COPYDEF COPYLAS: MVI H,LASTRK ; Last MVI L,LASTRK-1 JMP COPYDEF COPYONE: MVI H,2 ; One MVI L,1 JMP COPYDEF COPYPAS: MVI H,LASTRK ; Pascal MVI L,1 JMP COPYDEF IF CPM COPYALL: MVI H,LASTRK ; All MVI L,0 JMP COPYDEF COPYSYS: MVI H,FIRSTRK ; System MVI L,0 JMP COPYDEF COPYZER: MVI H,1 ; Zero MVI L,0 ENDIF ; ; The one time finish - up routine ; COPYDEF: SHLD TRKSRT LXI D,BGMES1 ; Now print message giving copy range CALL PRINT LDA TRKSRT CALL PRTHEX ; print first track LXI D,BGMES2 CALL PRINT LDA TRKSRT+1 ; print last track DCR A CALL PRTHEX LDA BUFFNMB ; load desired buffer number ORA A JZ VECT3 ; if no autosize, put in IF DOCOMP DCR A ; subtract one for compare buffer STA BUFFNMB ENDIF LXI H,BUFTMP CMP M ; compare against number found JZ VECT2 JC VECT2 ; branch if smaller LXI D,BUFERR CALL PRINT ; print out error msg LDA BUFTMP CALL PRTHEX ; print out buffer number VECT3: LDA BUFTMP STA BUFFNMB ; put in smaller buffer number VECT2: LXI H,REPEAT ; go to mainline code now SHLD START+1 PCHL ; BUFTMP: DB 0 ; temporary storage for buffer counter INIT: DB CR,LF,'FAST DISKETTE COPY PROGRAM, VER. 4.3$' BUFERR: DB CR,LF,'CP/M IS TOO SMALL - BUFFER SPACE REDUCED: $' CALLERR: DB CR,LF,'INVALID PARAMETER .. VALID COPYFAST PARAMETERS ARE' DB CR,LF IF CPM DB 'ALL, ' ENDIF DB 'DATA, FIRST, LAST, ONE, PASCAL' IF CPM DB ', SYSTEM, ZERO' ENDIF DB ', N1, N1-N2$' BGMES1: DB CR,LF,'COPYING FROM TRACK $' BGMES2: DB ' TO TRACK $' ; ; END