title 'Code Downoad program For CP/M Plus' ; ;---------------------------------------------------------------- ; ; Written By Richard Holmes 08-12-87 ; Last Update By Richard Holmes 11-12-87 ;---------------------------------------------------------------- ; maclib z80 ; ; CP/M equates ; bdos equ 5 ;; Operating system entry dirio equ 6 cr equ 0dh lf equ 0ah esc equ 01bh ; ; lxi sp,stack lxi d,signon call ptxt ; loop: lxi d,prompt call ptxt loop$wait: call con$inp cpi ' ' jrz loop$wait cpi cr jrz loop cpi lf jrz loop ; cpi esc jz exit cpi 3 ; Control C jz exit ; ; All else we echo, test and use.... call con$out ; Echo to screen call caps ; cpi 'D' jz down$load ; ; Connect as a terminal ? cpi 'C' jz connect ; Quit ? cpi 'Q' jz exit ; cpi '?' jz help ; call bell lxi d,what call ptxt call bell jmp loop ; what: db cr,lf,'What ?',0 ; ;---------------------------------------------------------------- ; Down load a file to the remote computer and display the ; results to the user. ;---------------------------------------------------------------- ; down$load: mvi a,esc ; End anything going on down there call aux$out lxi d,5 call delay ; Wait for ESC to get there and be used mvi b,50 ; Characters to get before another Esc ; ; Clear the external machine(s) buffer. down$load1: call aux$ist ; Look for anything down the line jrz dl$begin call aux$inp lxi d,2 call delay djnz down$load1 ; ; Look for a user abort call con$ist jrz down$load call con$inp cpi esc jz dload$end ; Exit on Esc. cpi 3 jz dload$end ; Exit on control C jr down$load ; dl$begin: lxi d,dl$name call ptxt ; Now get the name call get$name ; Open the file call open$file jz dl1 lxi d,nf$msg call ptxt ; Terminate the process. No file. jmp loop ; ; Get start address dl$1: lxi d,dl$start call ptxt call ihhl ; Into HL shld start ; ; Get the Kilobytes to be loaded lxi d,kb$msg call ptxt call idhl mov a,l ora h jz dload$end ; Terminate if 00 dad h ; Times 2 dad h ; 4 dad h ; 8 for sectors in a k byte shld size ; lxi d,dl$working call ptxt ; ; Setup DMA Address for disk i/o lxi d,buffer mvi c,26 ; SET DMA Address call bdos ; ; For each record; ; Fetch a record of 1024 bytes from disk. ; Down load the command, start address and size to the ; remote unit, then the data then calculate the checksum ; and compare it to the returned checksum. ; dload$record: ; Read a sector lxi d,fcb mvi c,20 ; Read sequential call bdos ora a jz dload$do ; mov c,a ; Save lxi d,re$msg call ptxt lxi d,err1 mov a,c ; C = main error code cpi 1 jrz dload$err ; lxi d,err9 cpi 9 jrz dload$err ; lxi d,10 cpi 10 jrz dload$err ; lxi d,err255 cpi 255 jrz dload$err ; None of these error codes lxi d,err$err call ptxt mov a,c call prhex jmp dload$end ; ; Print the error code and look to see if an extended error code ; dload$err: call ptxt mov a,h ora a jz dload$end ; No extended error then ; ; An extended error here. Print the message and terminate ; mov a,h lxi d,per1 cpi 1 jrz print$per lxi d,per4 cpi 4 jrz print$per lxi d,err$err print$per: call ptxt jmp dload$end ; re$msg: db cr,lf,' Disk Error : ',0 ; err$err db 'Unknown Error Code ',0 ; err1: db 'End of File',0 err9: db 'Invalid FCB',0 err10: db 'Media Change',0 err255: db 'Physical Error : ',0 per1: db ' Disk I/O',0 per4: db ' Invalid Drive',0 ; Loop to this label if the record needs to be re-send in the ; event of an error. It will re-send the data. dload$do: mvi a,'@' call aux$out ; Command Prefix lxi d,5 call delay ; Small delay after start command dload$clr1: call aux$ist jrz dload$do1 call aux$inp call con$out ; Display any data sent back. lxi d,5 call delay jr dload$clr1 ; ; dload$do1: mvi a,'L' call aux$out ; Load command dload$clr2: call aux$ist jrz dload$do2 call aux$inp call con$out lxi d,5 call delay jr dload$clr2 ; ; Start address dload$do2: lhld start ; Start address in hex. mov a,l call aux$out ; Done, start address low mov a,h call aux$out ; ; Tell receiver how many bytes to receive now lxi b,128 ; Each record is 128 bytes always (1 sector) mov a,c call aux$out ; Low size mov a,b call aux$out ; High size ; mvi d,0 ; Checksum lxi h,buffer ; -> data buffer dload$loop: mov a,m ; Get the data from the buffer call aux$out mov a,m add d mov d,a ; Save checksum inx h ; -> next buffer byte dcx b ; One less byte mov a,b ora c jrnz dload$loop ; ; Send a CR to get the checksum back mvi a,cr call aux$out ; Get the returned checksum call aux$inp cmp d jz dl$pass ; Pass = print a 'p' ; Failed to match. mvi a,'f' call con$out ; See if a user abort call con$ist jz skip$uab call con$inp cpi esc jz dload$end ; skip$uab: jmp dload$do ; Re-send the record. ; dl$pass: mvi a,'k' call con$out ; ; Bump the start address now lhld start lxi d,128 ; Bytes to be sent dad d shld start ; ; Decrement the number of 1kb blocks to be sent. lhld size dcx h shld size ; One less kilobyte mov a,l ora h jnz dload$record ; ; dload$end: lxi d,fcb mvi c,16 ; Close file call bdos ; mvi a,cr call aux$out ; Flush any data back from the chip. dl$end$clr: call aux$ist jz loop call aux$inp lxi d,5 call delay jr dl$end$clr ; dl$working: db cr,lf db cr,lf,'DownLoading >',0 ; dl$name: db cr,lf db cr,lf,'Enter File NAME to download : ',0 ; kb$msg: db cr,lf db cr,lf,' Enter KILOBYTES to load : ',0 ; dl$start: db cr,lf db cr,lf,' Enter Download Address : ',0 ; nf$msg db cr,lf,'File NOT FOUND',0 ; get$hex: ret ; open$file: xra a lxi h,fcb mov m,a lxi d,fcb + 1 lxi b,35 ldir ; Clear the FCB to all nulls ; Make it a COM file mvi a,'C' sta fcb + 9 mvi a,'O' sta fcb + 10 mvi a,'M' sta fcb + 11 ; ; Now move the file name into it. lxi h,fname ; -> users file name lxi d,fcb + 1 ; -> start of file name area in the FCB mvi b,8 ; Characters gnl: mov a,m call caps stax d inx h inx d djnz gnl ; ; Now try to open it. lxi d,fcb mvi c,15 ; Open request call bdos ora a ret ; ; Get the file name to be laoded from the user. ; get$name: mvi a,8 sta fmax ; Filename max characters ; lxi h,fsiz lxi d,fsiz + 1 lxi b,8 mvi m,' ' ; Load spaces ldir ; Clear the name buffer lxi d,fmax ; -> the console string for dos to use mvi c,10 ; Formatted input request call bdos ; Load the file name ret ; ;---------------------------------------------------------------- ; Display a help screen. ;---------------------------------------------------------------- ; help: lxi d,help$msg call ptxt jmp loop ; ;---------------------------------------------------------------- ; Connect console as a virtual terminal to the aux port ;---------------------------------------------------------------- ; connect: lxi d,con$msg call ptxt ; connect$loop: call con$ist jz check$aux call con$inp cpi '\' and 01fh jz loop ; Terminate call aux$out ; Send to remote. check$aux: call aux$ist jz connect$loop call aux$inp call con$out jmp connect$loop ; ; exit: jmp 0 ; help$msg: db cr,lf,'------ The DownLoad / Connect Program ------' db cr,lf db cr,lf,'This program provides a virtual terminal AND' db cr,lf,'a down loading facility whereby a disk file' db cr,lf,'can be sent to a remote computer for running' db cr,lf,'and debugging. Please refer to the CodeLoad' db cr,lf,'documentation for a complete description.' db cr,lf db cr,lf,'---------------- Commands ----------------' db cr,lf db cr,lf,'C Connect to AUX port as a Virtual terminal' db cr,lf,'D Down load a COM file' db cr,lf,'Q Quit back to dos' db cr,lf db 0 ; con$msg: db cr,lf,'Connected to AUX Port. Press Control-\ to exit' db cr,lf,0 ; prompt: db cr,lf,'Dload>',0 ; signon: db cr,lf,'+---------------------------------+' db cr,lf,'| CodeLoad / Connect program |' db cr,lf,'| |' db cr,lf,'| Written by Richard Holmes |' db cr,lf,'| |' db cr,lf,'| Version 1.0 Dated : 11/12/1987 |' db cr,lf,'+---------------------------------+' db cr,lf db 0 ; ; ; -- Print string at mDE til a null -- ; ptxt: ldax d ora a rz call con$out inx d jr ptxt ; ;================================================================ ; ; ---- Console Output ---- ; ; On Entry ; A = character to send ;================================================================ ; con$out: push h push b push d push psw mov e,a ; Load the ascii character mvi c,dirio ; Direct console output call bdos pop psw pop d ; Restore output pop b pop h ora a ret ; ;================================================================ ; ; Get a console character into A ; ;================================================================ ; con$inp: push h push b push d cie$wait: call con$ist jrz cie$wait ; mvi e,0fdh ; Wait and fetch mvi c,dirio call bdos pop d pop b pop h ret ; ;================================================================ ; ; Get the console status. ; ; On Exit ; A = 0 if not ready, ; 1 if got a character ;================================================================ ; con$ist: push h push b push d mvi e,0feh ; Status only code mvi c,dirio ; Direct console I/O call bdos pop d pop b pop h ora a ret ; ;---------------------------------------------------------------- ; Read a character into A and return it to the user. Wait for it ; for ever if needed. Not a smart routine at all. ; ; On Entry ; ; On Exit ; A = character read from AUX port ; All other registers preserved ;---------------------------------------------------------------- ; aux$inp: push h push b push d ; mvi c,3 ; AUX Input code call bdos ; pop d pop b pop h ret ; ;---------------------------------------------------------------- ; Write the character in A to the AUX port. ; ; On Entry ; A = character to write ; ; On Exit ; All other registers preserved ;---------------------------------------------------------------- ; aux$out: push psw push h push b push d ; mvi c,4 ; Aux out code mov e,a ; Load data call bdos ; pop d pop b pop h pop psw ret ; ;---------------------------------------------------------------- ; Return the input status. ; ; On Exit ; A = FF if a character ready ; A = 00 if no character ready ;---------------------------------------------------------------- ; aux$ist: push h push b push d ; mvi c,07 call bdos ; pop d pop b pop h ora a ; Set the zero flags ret ; ;---------------------------------------------------------------- ; Return the output status of the console port. ; ; On Exit ; A = FF if port ready for character output ; A = 00 for device busy ;---------------------------------------------------------------- ; aux$ost: push h push b push d ; mvi c,8 call bdos ; pop d pop b pop h ret ; ;---------------------------------------------------------------- ; -- Input a decimal from the keyboard into HL -- ;---------------------------------------------------------------- ; idhl: call get$buf ; load the buffer from console lxi h,0 lda bufsiz ora a rz ; quit if nothing read ; Now read the buffer, condition, put into HL. push b ; save push d mov b,a ; use as a counter idhl2: call get$chr ; Get a character ; Convert to a binary value now of 0..9 sui '0' jrc inp$err ; Error since a non number cpi 9 + 1 ; Check if greater than 9 jrnc inp$err ; Now shift the result to the right by multiplying by 10 then add in this digit mov d,h ; copy HL -> DE mov e,l dad h ; * 2 dad h ; * 4 dad d ; * 5 dad h ; * 10 total now ; Now add in the digit from the buffer mov e,a mvi d,00 dad d ; all done now ; Loop on till all characters done djnz idhl2 ; do next character from buffer jr inp$end ; all done ; ; ;---------------------------------------------------------------- ; Read a HEX number into HL from the keyboard. ;---------------------------------------------------------------- ; ihhl: call get$buf lxi h,00 lda bufsiz ora a rz ; return if no character read ; push b push d ; save mov b,a ; ihhl2: call get$chr ; get a character ; Now convert the nibble to a hex digit 0..F sui '0' cpi 9 + 1 jrc ihhl3 ; mask in then sui 'A'-'0'-10 cpi 16 jrnc inp$err ; ; Shift the result left 4 bits and MASK in the digit in A ihhl3: dad h dad h dad h dad h ; shifted right 4 now ora l ; mask in the digit mov l,a ; put back djnz ihhl2 ; keep on till all digits done ; inp$end: xra a ; Zero is a goo exit inp$end2: pop d pop b ret ; inp$err: ; Here when a non digit is encountered lda buftmp jr inp$end2 ; ; Subroutines for shared code etc.... ; get$buf: ; Load the buffer from the screen via CBUFF. push d mvi a,6 sta bufdat ; Set up ready for user xra a sta bufdat+1 ; clear buffer original value lxi d,bufdat call get$txt ; Get a text buffer full pop d lxi h,buftxt ; point to the start of text shld bufadr ; set up a pointer lxi h,00 ; clear the result register ret ; ; Get a character from the buffer, capitalize it on the way ; get$chr: push h lhld bufadr mov a,m ; get the character sta buftmp ; save the character inx h ; point to next character shld bufadr pop h ; restore ; Now capitalize it jmp caps ; bell: mvi a,7 jmp con$out ;================================================================ ; ---- Read a text string ---- ; ; This routine reads a line of input from the console and puts it into ; a standard CP/M console buffer pointed to by DE on entry. This is ; a little nicer that CP/M as it allows buffers to be pre-initialized ; so that it is printed when the buffer is input so that defaults can ; be loaded before entry of data. ; ; On Entry ; DE -> console buffer max size byte ; ; On Exit ; buffer filled from console to max size limit ; All registers preserved ; ;================================================================ ; get$txt: push psw ldax d ; get buffer size in bytes ora a jz cbuff$end push h push b push d xchg ; put string address into HL mov c,a ; Now C = buffer maximum size init: mvi b,00 ; characters read = 0 inx h ; hl -> size of character read now ; ; Here we detect if there is some data in the buffer to be pre printed ; and if there is the we print it. ; mov a,m ; get number of chars. in the buffer inx h ; point to string space now. ora a jrz rdloop ; Print the initialized character string, save the size for later mov b,a push b ; save init2: mov a,m ; get the character inx h ; point to next string space byte call dspchr ; print it, maybe control character djnz init2 ; print all characters pop b ; restore # of characters ; ; ; On entry here HL-> string space, next free byte, B = number of characters ; in the string. C = number of bytes in the buffer. ; rdloop: call con$inp ; Fetch a character cpi 0dh ; end if carriage return jrz exitrd ; exit cpi 0ah jrz exitrd cpi 08 ; backspace ?? jrnz rdlp1 ; if not then continue call backsp ; else backspace jr rdloop ; keep on backspacing rdlp1: cpi 018h ; delete line ? jrnz rdlp2 del1: call backsp ; delete a character jrnz del1 ; keep on till all character deaded jr rdloop ; start again ebonettes ; ; If here we check if the buffer is full. If so we ring the bell rdlp2: mov e,a ; save the character mov a,b ; load byte count cmp c ; is it equal to the maximum ? jrc strch ; store the character if not full call bell ; else ring the bell jr rdloop ; get more characters ; ; Buffer not full so save the character strch: mov a,e ; get character mov m,a ; save it inx h ; point to next buffer byte inr b ; increment byte count call dspchr ; display the (maybe control) character jr rdloop ; do again, more characters ; ; Display a control character by preceeding it with a '^' ; dspchr: cpi 020h ; was it a space ? jnc con$out ; if not then print & return mov e,a ; else save character mvi a,'^' ; indicate a control character call con$out mov a,e ; restore character adi 040h ; make printable jmp con$out ; ; Send a backspace and detect if at the start of the line. ; backsp: mov a,b ; get character count ora a rz ; return if line empty dcx h ; decrement byte pointer mov a,m ; get the character cpi 020h ; is it a control character ? jrnc bsp1 ; if not then delete 1 char only call bsp ; send a backspace bsp1: call bsp ; backspace 1 dcr b ; one less string byte ret ; ; Send the backspace bsp: mvi a,08 call con$out ; Go back a char not req-r for cp/m mvi a,' ' ; erase the character call con$out mvi a,08 jmp con$out ; send and return ; ; Set the number of bytes read into the buffer byte at DE + 1. ; exitrd: pop d ; restore all registers (buffer addr) mov a,b ; get # of characters inx d stax d ; save in characters read byte dcx d ; restore de ; pop b pop h cbuff$end: pop psw ora a ; Clear carry ret ; ;================================================================ ; ; ---- Print the hex digits in A ---- ; ;================================================================ ; prhex: push psw rrc rrc rrc rrc call phexl pop psw phexl: ani 0fh adi 90h daa aci 40h daa jmp con$out ; ;================================================================ ; ; Delay the number of milliseconds in DE ; ;================================================================ ; delay: mov a,e ora d rz ; push d ; save it delay2: call delay3 dcx d ; one less millisecond less overhead mov a,d ora e jrnz delay2 ; keep on till DE = 0 pop d ; restore users initial value ret ; back to user ; ; Delay 1 millisecond less the overhead involved in the above code. ; ; This routine must delay 3957 t-states ; delay3: push b ; 11 mvi b,224 ; 7 delay4: ; This loop does (4 + 13) * 230 - 5 = 3905 t nop ; 4 djnz delay4 ; 13 ; Fudge 14 machine cycles lxi b,0 ; 10 nop ; 4 pop b ; 10 ; ret ; ;================================================================ ; ; ---- Capitalize the accumulator ---- ; ;================================================================ ; caps: cpi 'a' ; Convert lower case to upper rc cpi 'z'+1 rnc ani 5fh cpi 03 jz 0h ; Exit if control C ret ; dseg ; ?binnum ds 10 ?result ds 10 buftmp db 00 ; A temporary character store bufadr: db 00,00 bufdat: db 6 ; maximum characters bufsiz: db 00 ; characters read buftxt: db 00,00,00,00,00,00 ; text buffer ; ds 256 stack: ; buffer ds 128 ; Bytes per disk read sector ; fcb ds 36 ; File control block to read into ; fmax db 8 ; Max characters in the name fsiz db 0 ; Actual characters in the name fname db 8 ; Actual name ; start ds 2 size ds 2 ; ; end ;