title 'COPYDISK 1 Feb 82' ver equ 20 ; Version 2.0 -jrp ; COPYDISK duplicates entire diskettes using all of the ; available storage as a multiple track buffer. ; This program must be built with a large extra segment ; as follows: ; ASM86 COPYDISK ; GENCMD COPYDISK EXTRA[M0200,XFF00] ; This allows COPYDISK to utilize all the left-over memory in your ; system as its buffer. cseg ; code segment start: mov si,offset signon_message call pmsg ; print signon message next_copy: ; reload local stack for retries. pushf ; save interrupt flag in stack pop bx ; put it in bx cli ; disable interrupts mov ax,ds ; get our data segment mov ss,ax ; and use as stack segment mov sp,offset stack_end ; set stack pointer push bx ; flags back into stack popf ; restore interrupt status mov si,offset source_message ; prompt for source drive call get_drive ; request drive code cmp al, cr - 'A' ; see if it was a jne next_1 ; no, continue jmp exit ; yes, we should exit next_1: cmp al,drive_cnt ; see if valid drive code jb save_source ; yes, go save it drive_err: mov si,offset bad_drive ; else, print call pmsg ; a bad drive message jmp another ; and try to get another save_source: mov source,al ; save it as the source drive add al,'A' ; make ascii drive code mov source_ascii,al ; save for messages mov cl,source ; get source drive mov dl,0 ; force first time selection call seldsk ; select it test bx,bx ; insure the drive exists jz drive_err ; no, fatal mov si,ES:word ptr 10[bx] ; get pointer to DPB ; create local copy of disk definition push ds ! push es ; save segments pop ds ! pop es ; and exchange them mov di,offset disk_def ; point to local disk def table mov cx,15 ; diskdef is 15 bytes long cld ! rep movsb ; copy it push ds ! push es ; swap back pop ds ! pop es ; segment registers mov dx,es:word ptr 00[bx] ; get secttran table address mov cx,0 ; logical sector zero call sectran ; find out first sector number mov base_sector,bx ; save this for track reads mov si,offset dest_message ; prompt for destination call get_drive ; get the drive code cmp al,source ; see if same as source jne not_same ; no, go see if > max mov si,offset same_message ; can't have same drive call pmsg ; so we print message jmp another ; and go try again not_same: cmp al,drive_cnt ; is dest > max? jnb drive_err ; go print invalid drive error mov dest,al ; save destination drive add al,'A' ; make ascii drive code mov dest_ascii,al ; save for messages mov cl,dest ; get destination drive mov dl,0 ; force first time selection call seldsk ; select destination drive test bx,bx ; insure drive exists jz drive_err ; no, go error out mov di,ES:word ptr 10[bx] ; point to destination DPB mov si,offset disk_def ; point to source drives def mov cx,15 ; length of a definition cld ! rep cmpsb ; compare the definitions je next_2 ; must be equal. jmp different_type ; else go print error next_2: mov dx,es:word ptr 00[bx] ; get secttran table address mov cx,0 ; logical sector zero call sectran ; find out first sector number cmp bx,base_sector ; make sure its the same je next_3 ; the same is OK, jmp different_type ; no, fatal error next_3: ; determine track capacity mov ax,spt ; get the sectors per track mov dl,128 ; length of a sector mul dl ; get track capacity mov track_size,ax ; save it ; determine number of tracks on diskette mov al,blm ; get block mask inc al ; plus 1 gives sectors/block mov ah,0 ; make 16 bits mov dx,0 ; make 32 bits mul dsm ; total sectors/data area add ax,spt ; force round up dec ax ; by adding SPT-1 div spt ; compute number of tracks add ax,off ; add in operating sys tracks dec ax ; number of last track mov max_track,ax ; save value ; compute number of tracks that will ; fit in our data segment mov ax,extra_length ; get low 16 bits of DS length mov dl,extra_length_H ; get other 8 bits mov dh,0 ; zero rest of 32 bit divisor div track_size ; divide buff_len / track_size mov NTS,ax ; save this value. cmp ax,2 ; must be at least 2 tracks jae adequite_memory ; ok, continue mov si,offset memory_message ; else print call pmsg ; error message aborting: ; here if exiting because of error mov si,offset abort_message call pmsg jmp another adequite_memory: mov si,offset ready_message ; insure this is correct call get_yn ; print request jc aborting ; if NO, then get new input mov si,offset copy_message ; else tell user OK. call pmsg mov ax,max_track ; get maximum track number mov base_track,ax ; save as outer loop index next_block: mov ax,base_track ; get loop index cmp ax,0 ; see if we are outside limits jl next_block_x ; yes, terminate loop sub ax,nts ; lower by number of tracks per buffer inc ax ; and adjust by 1. jns not_neg ; check to make sure its positive mov ax,0 ; no, zero is floor not_neg: mov btr,ax ; save as last track for this copy pass mov si,offset reading_message ; point to read message mov ax,offset read ; and proper subroutine mov cl,source ; select source diskette call track_block mov si,offset writing_message ; point to write message mov ax,offset write ; and proper subroutine mov cl,dest ; select destination call track_block mov si,offset verify_message ; point to verify message mov ax,offset verify mov cl,dest ; reselect destination call track_block mov ax,base_track sub ax,nts ; adjust base track by number we copied mov base_track,ax jmps next_block next_block_x: ; here, we are done with a sucessful copy mov si,offset done_message ; print message call pmsg ; announcing success another: mov si,offset another_message call getyn ; see if we want another copy jc exit ; no, go exit next_copy_v: ; convienient for short jumps jmp next_copy ; back for another disk copy exit: ; here, we are exiting mov si,offset exit_message call pmsg mov cl,13 int BDOS mov cl,0 ; reboot mov dl,0 ; CP/M-86 int BDOS different_type: mov si,offset type_error_message call pmsg jmp aborting track_block: mov message_pointer,si ; save pointer to message mov disk_function,ax ; save pointer to read/write mov selected_disk,cl ; save drive select code mov dl,1 ; tell bios have selected before call seldsk ; select diskette drive mov ax,base_track ; get base track number for this pass mov trk,ax ; save as loop index next_track: ; first inner loop... mov ax,trk ; get the index cmp ax,btr ; see if less than last jnl next_4 ; no, continue jmp next_track_x ; yeah, exit loop next_4: mov si,message_pointer call pmsg ; print " Track " mov ax,trk ; get the track number call pdec ; print as decimal mov si,offset space_message ; then, print some call pmsg ; spaces to blank any garbage mov cx,trk ; get desired track address call settrk ; give to bios mov cx,0 ; start with logical sector 0 next_sector: push cx ; save current sector number mov sector,cx ; save sector number (0 org) add cx,base_sector ; correct for first sector number call setsec ; pass to cbios ; compute dma address and segment mov ax,base_track ; get starting track number sub ax,trk ; gives relative track of pass mov dx,0 ; make dword mul spt ; compute sector of track add ax,sector ; add in current logical sector adc dx,0 ; make double precision add mov cx,128 ; length of a sector mul cx ; gives base offset as 32 bits mov dma_offset,ax ; gives base dma offset mov cl,12 ! shl dx,cl ; move to high nibble add dx,extra_base ; offset extra segment mov dma_segment,dx ; save DMA segment mov cx,dx ; put in argument register call setdmab ; pass to CBIOS mov cx,dma_offset ; get the offset again call setdma ; and pass it to BIOS also mov ax,sector ; fetch current sector number inc ax ; add one to it cmp ax,spt ; see if last sector on track mov cl,0 ; might be then normal write jb execute_function ; skip if not last mov cl,1 ; if last, then treating as ; dir write forces flush execute_function: call disk_function ; read/write/verify a sector test al,al ; check error return code jz no_disk_error ; see if we got a bad sector ; got fatal disk error mov si,offset disk_error_message call pmsg ; print disk error mov si,message_pointer ; get pointer to read/write inc si ; skip leading call pmsg ; print that mov ax,trk ; get track number call pdec ; print it mov si,offset sector_message ; print ", Sector " call pmsg mov ax,sector ; get sector number (0 org) add ax,base_sector ; correct if 1 origin call pdec ; print the sector number mov si,offset continue_message ; see if user wants to ignore call get_yn ; ask for a Y/N response jnc next_5 ; yes, continue jmp aborting ; NO, go ask for new disks next_5: call crlf no_disk_error: pop cx ; recover sector number inc cx ; sector = sector + 1 cmp cx,spt ; see if past last sector jae next_6 ; done, exit jmp next_sector ; else, continue next_6: dec trk ; track = track - 1 jmp next_track ; continue loading buffer next_track_x: ret verify: ; verify a 128 byte sector mov cx,offset verify_buffer ; point at our 128 byte buffer call setdma ; make it the dma buffer mov cx,ds ; get our data segment call setdmab ; point dma base address call read ; read 128 byte record push ax ; save read error in stack mov si,offset verify_buffer ; point to sector we just read les di,dword ptr dma_offset ; get pointer to one we wrote mov cx,128 ; 128 byte record cld ! rep cmpsb ; compare them mov al,0 ; no error je verify_good ; so we are ok mov al,0FFh ; else, we got compare error verify_good: pop dx ; recover read error byte or al,dl ; merge into return code ret ; back to caller ; *********************************** ; * ; * BIOS direct entry points ; * ; *********************************** home: mov al,8 jmps bios_call seldsk: mov al,9 jmps bios_call settrk: mov al,10 jmps bios_call setsec: mov al,11 jmps bios_call setdma: mov al,12 jmps bios_call setdmab: mov al,17 jmps bios_call read: mov al,13 jmps bios_call write: mov al,14 jmps bios_call sectran: mov al,16 jmps bios_call bios_call: mov bios_function,al mov bios_CX,cx mov bios_DX,DX mov cl,50 mov dx,offset bios_function int BDOS ret ; **************************************** ; * ; * Operator interaction subroutines ; * ; **************************************** get_drive: ; print message and read drive code call pmsg ; first, print the message call get_upper ; then get an upper case letter sub al,'A' ; and normalize to 0... ret get_upper: ; get upper case console input w/ echo call get_char ; get console character cmp al,'a' ; see if lower case jb get_upper_x ; below, leave cmp al,'z' ; insure not > z ja get_upper_x ; not lower at all sub al,'a'-'A' ; make upper get_upper_x: ret get_yn: ; print message and get Y or N push si ; save message pointer call pmsg ; print the message call get_upper ; get a upper case letter pop si ; recover message pointer cmp al,'Y' ; see if response was 'Y' je get_yn_x ; yes, return w/ no carry cmp al,'N' ; see if was 'N' jne get_yn ; no, invalid. reprompt stc ; set carry for a NO get_yn_x: ret get_char: ; read a line from CONIN and return first char mov cl,10 ; function for line in mov dx,offset line_buff ; point at buffer int BDOS ; read the line mov al,line_buff+1 ; get input length cmp al,1 ; insure single char entered jb get_char_1 ; no, go mov al,line_buff+2 ; get the character ret get_char_1: mov al,cr ; null string, return a ret crlf: ; print a mov si,offset crlf_message ; fall into pmsg pmsg: ; print message at [SI] to zero lods al ; get a character test al,al ; see if zero terminator jz pmsg_x ; yes, exit push si ; save pointer call conout ; not done, print character pop si ; recover pointer jmp pmsg ; and loop pmsg_x: ret conin: ; get character from console into AL mov cl,6 mov dl,0FFh int BDOS test al,al jz conin ret conout: ; output character in AL to console mov dl,al mov cl,6 int BDOS ret pdec: ; print unsigned 16 bits in AX as decimal ; with zero suppresion mov cx,0 ; cx is digit counter pdec_1: ; here to divide out next digit sub dx,dx ; Zero DX mov bx,10 ; constant 10 div bx ; quotient to AX, remainder to DX add dl,'0' ; make remainder ascii digit push dx ; and stick onto stack inc cx ; bump digit counter test ax,ax ; see if any quotient left jnz pdec_1 ; yes, continue stacking digits pdec_2: pop ax ; get a digit from the stack push cx ; save count call conout ; print it pop cx ; restore count loop pdec_2 ; and continue if more in stack ret ; done. . . ; ** DATA SEGMENT ** dseg cr equ 0dh ; ascii carriage return lf equ 0ah ; ascii line feed drive_cnt equ 16 ; CP/M currently supports up to 16 drives BDOS equ 224 ; system call interrupt number ; CP/M-86 Page Zero code_length rw 1 ; low 16 bits code length code_length_H rb 1 ; high 8 bits '' '' code_base rw 1 ; base of the code segment model_8080 rb 1 ; 8080 memory model flag data_length rw 1 ; low 16 bits data length data_length_H rb 1 ; high 8 bits '' '' data_base rw 1 ; base of the data segment rs 1 ; not used extra_length rw 1 ; low 16 bits extra length extra_length_H rb 1 ; high 8 bits '' '' extra_base rw 1 ; base of the extra segment org 005Ch default_FCB rs 35 ; default File Control Block org 0080h default_buffer rs 128 ; default record buffer org 0100h ; start of user data segment ; MESSAGES signon_message db cr,lf,'CP/M-86 Full Disk COPY Utility' db cr,lf,' Version ' db ver/10+'0', '.', (ver mod 10)+'0',cr,lf,0 source_message db cr,lf,cr,lf,'Enter Source Disk Drive (A-D) ? ',0 dest_message db cr,lf,cr,lf,' Destination Disk Drive (A-D) ? ',0 ready_message db cr,lf,cr,lf,'Copying Disk ' source_ascii rb 1 db ': to Disk ' dest_ascii rb 1 db ':',cr,lf db 'Is this what you want to do (Y/N) ? ',0 memory_message db cr,lf,'Insufficient memory available for copy',0 abort_message db cr,lf,cr,lf,'Copy aborted',cr,lf,0 done_message db cr,lf,'Copy completed.',0 another_message db cr,lf,cr,lf,'Copy another disk (Y/N) ? ',0 copy_message db cr,lf,'Copy started',cr,lf,0 reading_message db cr,' Reading Track ',0 writing_message db cr,' Writing Track ',0 verify_message db cr,'Verifying Track ',0 space_message db ' ',0 bad_drive db cr,lf,'Illegal Diskette Drive',cr,lf,0 same_message db cr,lf,'Source and Destination cannot be the same' db cr,lf,0 type_error_message db cr,lf,'Source and Destination disks must be' db cr,lf,'the same type',cr,lf,0 continue_message db cr,lf,'Ignore error (Y/N) ? ',0 sector_message db ', Sector ',0 disk_error_message db cr,lf,'Permanent Error ',0 crlf_message db cr,lf,0 exit_message db cr,lf,'COPY program exiting',cr,lf,0 ; VARIABLES source rb 1 ; source drive select code dest rb 1 ; destination drive select code selected_disk rb 1 ; current drive select code trk rw 1 ; current track number sector rw 1 ; current sector number (0 origin) nts rw 1 ; number of tracks per buffer full base_track rw 1 ; starting track number for this pass track_size rw 1 ; size of each track in bytes btr rw 1 ; last track number of this pass dma_offset rw 1 ; current buffer offset dma_segment rw 1 ; current buffer segment base message_pointer rw 1 ; points to appropriate read/write message disk_function rw 1 ; points to '' '' '' subroutine base_sector rw 1 ; either 0 or 1 normally max_track rw 1 ; total number of tracks on disk line_buff db 80 db 0 rb 80 bios_function rb 1 ; bios function number bios_cx rw 1 ; first argument for BIOS call bios_dx rw 1 ; second argument for BIOS call disk_def rs 0 ; disk definition table gets copied here spt rw 1 ; 128 byte sectors per track bsh rb 1 ; block shift factor blm rb 1 ; block mask exm rb 1 ; extent mask dsm rw 1 ; disk size in blocks drm rw 1 ; directory size al0 rb 1 ; alloc 0 al1 rb 1 ; alloc 1 cks rw 1 ; checksum size off rw 1 ; directory offset rw 128 stack_end rw 0 verify_buffer rs 128 ; sector buffer for verify function db 0 ; force out data segment end