**************************************************************** * * * L I N K 9 0 * * * * Copyright (C) 1983 by David C. Norris * * Maranatha Software Systems * * 500 Catalina Road #305, Cocoa Beach, FL 32931 * * Phone (305) 783-1963 * * * * This version 2344 16 February 83 * * * **************************************************************** ; ORG 100H JMP START ;skip version number ; DB 0DH SMSG: DB 'LINK90 ' VERS: DB 'Version ' MARK: DB '1.' ; Major revision number ( Protocol change ) VERSION:DB '0' ; Version number ( Update from series of ) ; ( patch levels or new command implemented ) PATCH: DB '1' ; Patch level ( minor updates or fixes ) DB CR,LF DB 'Copyright (C) 1983',0DH,0AH DB 'Maranatha Software Systems',0DH,0AH DB '$',8,' ',CR,LF DB 1AH ; LINK90.COM was not meant to be printed ; ; all rights reserved ; ; note: ; This program has been written so that CP/M users ; may exchange programs between each other using a ; carefully tested intercommunication protocol. ; The source listing is provided as a courtesy ; to the user and so that non CP/M users may adapt LINK90 ; to their own computers. Major additions or ; deletions other than configuration defeat the ; purpose of this program. LINK90 has been designed ; with as many practical features necessary as well ; as some fancy additions. ; ; THIS IS COPYRIGHTED SOFTWARE! ; ; Please restrain yourself from making any additions ; to LINK90 other than those required to get the ; program up on your system. Should you find any ; bugs in the system, whether particular to your machine ; or not, or have any helpful suggestions please ; contact me and I will try to incorporate them in a ; future release of LINK90. Thanks. ; ; - Dave Norris ; ; Change Summary: ; ; 1.01 (16 Feb 83) - Recv prints record number after all bytes have been ; input so link may run at faster speeds. Exit from Co mode to ; wait state puts cursor on bottom of screen. ; **************************************************************** * System equates * **************************************************************** ; FCB: EQU 5CH ;File control block area FCBDN: EQU FCB+0 ;Disk name FCBFN: EQU FCB+1 ;File name FCBFT: EQU FCB+9 ;File type FCBRL: EQU FCB+12 ;Reel number FCBRC: EQU FCB+15 ;Record count FCBCR: EQU FCB+32 ;Current (next) record number) FCBLN: EQU FCB+33 ;FCB length ; **************************************************************** * Ascii equates * **************************************************************** ; NULL: EQU 0 ;Null SOH: EQU 1 ;Start of header STX: EQU 2 ;Start of text ETX: EQU 3 ;End of text EOT: EQU 4 ;End of transmission ENQ: EQU 5 ;Inquiry ACK: EQU 6 ;Acknowledge BEL: EQU 7 ;Bell BS: EQU 8 ;Back space LF: EQU 10 ;Line feed CR: EQU 13 ;Carriage return DLE: EQU 16 ;Data link escape DC1: EQU 17 ;Direct control 1 DC2: EQU 18 ;Direct control 2 DC3: EQU 19 ;Direct control 3 DC4: EQU 20 ;Direct control 4 NAK: EQU 21 ;Negative Acknowledge can: equ 24 ;Cancel ESC: EQU 27 ;Escape DEL: EQU 7FH ;Delete ; **************************************************************** * System initialization * **************************************************************** ; START: lhld 6 sphl ; Reset stack pointer call clear ; clear crt LXI D,SMSG ; Startup message CALL PRMSG CALL INIT ;Go to user provided initialization CALL CLRCOM ;Clear the communications channel MVI A,'A' CALL LOGIN ;Start on drive A ; **************************************************************** * Initial prompt and command input for the INITIAL state * **************************************************************** ; PRMPT: lhld 6 sphl ; Reinitialize stack (just in case) CALL CRLF MVI A,ACK STA MFLG ;Clear the messages waiting flag LDA DISK MOV E,A CALL PRCHR ;Print the disk we are now on MVI E,':' CALL PRCHR ;Print colon MVI C,2 MVI E,'*' CALL 5 ;Initial state prompt ; CALL CLRBUF ;Clear and zero input buffer LXI D,INBUF ;Point to input buffer MVI C,10 CALL 5 ;Input command string CALL JUSLF LDA INBUF+1 ;Get input string length ORA A ;Null string dropout JZ PRMPT ; ; We have initial state command input, process now... ; LXI H,INTBL CALL PARSE ;Process and execute command string ; ; Only errors on command string allowed past here ; LXI D,ERMSG CALL PRMSG ;Print "What?" JMP PRMPT ; **************************************************************** * INITIAL state disk reset * **************************************************************** ; ireset: call reset jmp prmpt ; **************************************************************** * INITIAL state conversational mode * **************************************************************** ; iconv: call conv$mode jmp prmpt ; **************************************************************** * INITIAL state directory * **************************************************************** ; IDIR: CALL DIR CALL CRLF JMP PRMPT ; **************************************************************** * Print help menu for the initial mode * **************************************************************** ; INHEP: LXI D,INHEMS CALL PRMSG JMP PRMPT ; **************************************************************** * STDISK - log on to disk A or B * **************************************************************** ; STDISK: LDA INBUF+2 CALL LOGIN JMP PRMPT ; **************************************************************** * TERMINAL mode * * Main subroutine * **************************************************************** ; TERM: LXI D,TEMSG CALL PRMSG ;Inform user he is entering mode ; TERM1: CALL terminal ; ; User has typed the escape key, process TERMINAL mode function ; LXI D,TAMSG CALL PRMSG ;Print auto-linefeed string LDA AFLG ORA A JZ TO1 LXI D,ONMSG ;Auto-linefeed on message JMP TO2 TO1: LXI D,OFMSG ;Auto-linefeed off message TO2: CALL PRMSG LXI D,TCMSG CALL PRMSG ;Print duplex string LDA PLXFLG ORA A JZ TO3 LXI D,HAFMSG;Half-duplex message JMP TO4 TO3: LXI D,FULMSG;Full-duplex message TO4: CALL PRMSG LXI D,TBMSG CALL PRMSG ;Print rest of menu MVI C,1 CALL 5 ;Get command PUSH PSW call CRLF ;CRLF after input if menu mode POP PSW CALL CTUC ;convert to upper case CPI 'B' JZ TERMB ;Reinitialize baud rate CPI 'A' JZ TERMA ;Toggle auto-line feed CPI 'D' JZ TERMD ;Toggle duplex CPI 'S' JZ TERMS ;Send program CPI 'I' JZ TERMI ;Input program CPI 'R' JZ TRESET ;Reset disks CPI ESC JZ TERME ;Exit TERMINAL mode CPI 'E' JNZ TERM1 ;Nonexistent command, continue MVI A,ESC CALL CMOUT ;Output an escape to the remote mainframe JMP TERM1 ;Continue ; ; Re-initialize user hardware ; TERMB: CALL INIT ;Reinitialize baud rate CALL CRLF JMP TERMR ; ; Toggle auto line-feed ; TERMA: LDA AFLG CMA ;Complement flag STA AFLG JMP TERMR ; ; Toggle Duplex switch ; TERMD: LDA PLXFLG CMA ;Complement flag STA PLXFLG JMP TERMR ; ; Reset disks ; TRESET: CALL RESET ;Reset disks JMP TERMR ; ; Send Ascii file ; TERMS: CALL INPFLN ;Input file name to send ORA A JNZ TERMS7 ;Bad file name if not zero LDA WILD ORA A JZ TERMS5 ;Only unambiguous file names allowed LXI D,UFOMSG CALL PRMSG JMP TERMR ;Re-enter terminal mode TERMS7: LXI D,BFMSG CALL PRMSG ;Tell him he messed up JMP TERMR TERMS5: LXI D,FCB MVI C,15 CALL 5 ;Open file CPI 255 ;File opened ok? JNZ TERMS1 LXI D,FOEMSG CALL PRMSG ;Print no such file CALL PRFLNM JMP TERMR ; ; choose file send protocol ; TERMS1: lxi d,promsg call prmsg mvi c,1 call 5 push psw call crlf pop psw cpi '1' jz terms2 cpi '2' jnz terms1 ; ; send file using cpmug protocol ; xra a sta recnum call crlf trmsn0: mvi b,80 call timot ; wait for initial nak jc trmsn1 ; time out - bomb out cpi nak jz trmsn2 ; got initial nak - go jmp trmsn0 ; keep waiting trmsn1: lxi d,rermsg call prmsg ; remote error - abort jmp termr ; trmsn2: mvi e,'!' ; give user busy indication call prchr lda recnum inr a ; bump record count sta recnum xra a sta pass ; clear error count lxi d,fcb mvi c,20 call 5 ; input record ora a jnz trmsn5 ; jump if end of file ; ; send (or resend) record ; trmsn7: mvi a,soh call cmout ; tell remote record is coming lda recnum call cmout ; output block # cma call cmout ; output -block # ; lxi h,80h mvi b,0 ; initialize checksum trmsn3: mov a,m call cmout ; output data bytes(s) add b mov b,a ; update checksum inr l jnz trmsn3 ; call cmout ; output checksum mvi b,20 call timot ; wait for response jc trmsn4 ; if none, error cpi ack jz trmsn2 ; ok, bump rec # and continue cpi can jnz trmsn4 call crlf ; remote cancelled jmp trmsn1 ; trmsn4: lda pass inr a ; error, bump pass # sta pass cpi 10 jc trmsn6 ; keep trying call crlf lxi d,crcmsg ; too many errors call prmsg jmp termr ; trmsn6: mvi e,'?' ; error on last record call prchr ; give user busy indication jmp trmsn7 ; go resend it ; trmsn5: mvi a,eot call cmout ; send end-of-text mvi b,10 call timot ; wait for response jc trmsn5 ; none? keep sending eot until abort cpi ack jnz trmsn1 lxi d,eofmsg ; file xfer successful call prmsg jmp termr ; ; send ascii file ; TERMS2: LXI D,FCB MVI C,20 CALL 5 ;Read 1 record ORA A JZ TERMS3 ;Read ok CPI 1 JZ TERMS21 ;End of file LXI D,FIEMSG CALL PRMSG TERMS21:LXI D,FCB MVI C,16 CALL 5 ;Close the file JMP TERMR ;Return to TERMINAL mode TERMS3: LXI H,80H MVI C,80H TERMS4: PUSH H ;Save pointer PUSH B ;Save count MVI C,11 CALL 5 ;Check for console input ORA A POP B ;Restore count POP H ;Restore pointer JZ TERMS6 ;No input PUSH H ;User hit a key, resave pointer PUSH B ;Resave count MVI C,1 CALL 5 ;Get rid of strike key LXI D,ABMSG CALL PRMSG ;Ask user if he wants to abort MVI C,1 CALL 5 CPI ESC ;Does he want to? POP B ;Restore count POP H ;Restore pointer at any rate JNZ TERMS6 ;No abort, continue LXI D,TIEMSG CALL PRMSG ;Print abort message JMP TERMR ;Return to Terminal mode TERMS6: MOV A,M CPI 'Z'-40H ;Check for CP/M end of file JZ TERMS2 ;Get next record (which doesn't exist) CPI LF ;Dont send line feeds, delay a bit instead JZ TERMS8 CALL CMOUT ;Output program byte MOV E,A CALL PRCHR ;Print the character we are sending TERMS9: INX H DCR C ;Decrement byte count JNZ TERMS4 ;Not done with this record yet JMP TERMS2 ;Get next record TERMS8: CALL JUSLF ;Do line feed but don't send it PUSH B PUSH H lda delay ; <-- Lookie, Jeff. New and improved. (I'll bet he never sees this...) mov b,a ;Delay for (wait) sec for remote host CALL TIMOT ;to keep up with LINK90 amazing speed POP H ; (Thanks, Jeff) POP B JC TERMS9 ;Timed out, bump pointer & send next line MOV E,A LDA PLXFLG ORA A ;Check duplex JZ TERMS8+3;If full duplex we don't want to echo program CALL PRCHR ;Print character recieved from remote JMP TERMS8+3 ; ; Input Ascii file ; TERMI: CALL INPFLN ;Get file name ORA A JNZ TERMS7 LDA WILD ORA A JZ TERMI6 ;Only unambiguous file names allowed LXI D,UFOMSG CALL PRMSG JMP TERMR ;Re-enter terminal mode TERMI6: CALL PRPFIL ;Prepare file for writing JNC TERMI1 TERMIQ: LXI D,TIEMSG CALL PRMSG ;Error, print abort message JMP TERMR ; ; file set up for writing - how did he want to get it? ; TERMI1: lxi d,promsg call prmsg mvi c,1 call 5 push psw call crlf pop psw cpi '1' jz termi2 cpi '2' jnz termi1 ; ; input file via cpmug protocol ; xra a sta recnum ; initialize block number to 0 mvi a,NAK call cmout ; send initial NAK ; trmin2: xra a sta pass ; clear error count trmin3: mvi e,'!' mvi c,2 call 5 ; print ok trmin4: mvi b,10 call timot ; wait for a few seconds jc trmnak ; bomb if we didn't get a character cpi soh ; got character - was it soh? jz trmsoh cpi null ; skip nulls jz trmin4 cpi eot jz trmeot trmnak: mvi b,5 call timot jnc trmnak ; wait for sender to shut up mvi a,nak call cmout ; tell him there was a mistake lda pass inr a ; bump error count sta pass cpi 10 jnc trmabo ; else abort mvi e,'?' mvi c,2 call 5 ; print dot jmp trmin4 ; continue if there wasn't too many errors ; ; get block header ; trmsoh: mvi b,1 call timot ; get block number jc trmnak mov c,a mvi b,1 call timot ; get complement of block number jc trmnak cma cmp c jnz trmnak ; ; get data ; mov a,c sta recnum+1 ; store received block number xra a sta cksum ; initialize checksum lxi h,80h ; initialize data pointer trmdat: mvi b,1 call timot ; get data byte jc trmnak mov b,a mov m,a ; stuff lda cksum add b ; update checksum sta cksum inr l ; loop thru 128 bytes jnz trmdat ; ; get checksum ; mov c,a ; put computed checksum in c mvi b,1 call timot ; get received ckecksum jc trmnak cmp c ; identical? jnz trmnak ; no, bomb out ; ; check record number ; lda recnum+1 ; get received block number mov b,a lda recnum cmp b ; compare to internal block number jz trmold ; same? he xmitted old record inr a cmp b jz trmack ; new, correct record? ; ; abort transmission ; trmabo: mvi a,can call cmout jmp termr ; ; got old block, send ack to get next ; trmold: mvi a,ack call cmout ; ack, more data please jmp trmin2 ; ; ok - write to file ; trmack: lxi d,fcb mvi c,21 call 5 ; write data ora a jz trmupd call crlf ; disk error - what was it? cpi 1 lxi d,extmsg jz trmack1 ; extend error cpi 2 lxi d,eddmsg jz trmack1 ; end of disk data cpi 255 lxi d,dfmsg jz trmack1 ; directory full lxi d,flermg ; file error trmack1:call prmsg mvi a,can call cmout jmp trmabo ; ; update record number and continue ; trmupd: lxi h,recnum inr m mvi a,ack call cmout jmp trmin2 ; ; end of transmission ; trmeot: call crlf lxi d,fcb mvi c,16 call 5 ; close file lxi d,eofmsg call prmsg mvi a,ack call cmout jmp termr ; ; input file via XON-XOFF protocol ; termi2: lhld BUFFER ; Point to start of user input buffer SHLD BPOINT ; Where we are LXI D,TIMSG CALL PRMSG ; Tell user what we are doing call free$space ; print amount of free space left ; ; check for remote input and console input ; TERMI3: CALL CMCHK JNZ TERMI5 ; Input from remote, go process MVI C,6 mvi e,0ffh CALL 5 ; Check console for user input.... ORA A JZ TERMI3 ; No user input, continue loop ; ; process user input ; mov e,a ; save input character lda plxflg ora a ; check duplex jz termui1 termui: push d ; save input ch mvi c,2 call 5 ; half duplex - fetch character (and print) pop d ; recover input ch lhld bpoint mov m,e ; stuff this character into buffer inx h shld bpoint ; ; check for break character ; termui1:mov a,e ; recover character cpi 3 ; control-C? jz termiq ; yes, abort cpi 1 ; control-A? jz termdump call cmout ; output regular character ; ; print free space ; cpi lf ; lf? cz free$space ; yes, print amount of free space left jmp termi3 ; ; dump buffer to disk ; termdump: LXI D,TIDMSG CALL PRMSG CALL TDUMP JC TICLS ; Error in dump; close file & quit lhld buffer xchg MVI C,26 CALL 5 ; Reset DMA to start of buffer LHLD BPOINT MVI M,'Z'-40H ; Insert CP/M end-of-file LXI D,FCB MVI C,21 CALL 5 ; Dump 1 more record to disk TICLS: LXI D,FCB MVI C,16 CALL 5 ; Close the file JMP TERMR ; ; continue input ; TPGO: LXI D,TCNTMG CALL PRMSG ; Inform user we are continuing mvi a,dc1 call cmout ; request continue print JMP TERMI3 ; Continue input ; ; process remote input character ; TERMI5: LHLD BPOINT CALL CMIN ; Get the byte MOV M,A ; stuff into buffer INX H SHLD BPOINT ; resave buffer pointer MOV E,A CALL PRCHR ; Print the character so user can see cpi lf cz free$space ; print space remaining if lf ; ; check for buffer overflow ; LDA 7 ;Get hi byte of BDOS address DCR A!DCR A ;Leave some room (just in case) DCR A!DCR A ; leave 1/2K of memory for overrun CMP H JNZ TERMI3 ;Haven't reached buffer limit yet... CALL THALT ;Tell remote to shut up while we work... CALL TDUMP ;Dump what we have JC TICLS ;Check for error indicator JMP TPGO ;Continue load ; ; inform user he is re-entering the terminal mode ; TERMR: LXI D,TREMSG CALL PRMSG ;Inform user he is re-entering terminal mode JMP TERM1 ; ; Exit terminal mode ; TERME: LXI D,80H MVI C,26 CALL 5 ;Reset DMA buffer JMP PRMPT ;Exit terminal mode ; **************************************************************** * INQ - check status of remote user, enter command mode * **************************************************************** ; INQ: MVI A,ENQ CALL CMOUT ;Throw an to the remote computer MVI B,1 ;Set wait byte for TIMOT to 1/4 second CALL TIMOT ;Call CMIN but do a time out JNC INQ1 ;No carry = routine didn't time out ; ; No response from remote - bomb back to INITIAL state ; INQ2: LXI D,NOTMSG ;Print link not established CALL PRMSG JMP PRMPT ; ; Remote sent a byte in response to our ; evaluate and respond accordingly ; INQ1: CPI ACK ;Acknowledge? JZ ESRL ;Establish the link CPI BEL ;Bell? (message waiting) JNZ INQ2 ;remote must send ACK or BEL ; LXI D,MSWTMS CALL PRMSG ;Print messages waiting ESRL: LXI D,RLEMSG ;Print remote link established CALL PRMSG JMP CNPMT ; **************************************************************** * Enter message and go into WAIT state * **************************************************************** ; LVMSG: LXI D,PMTMSG CALL PRMSG ;Request message CALL MSGIN ;Input the message CALL CRLF MVI A,BEL STA MFLG ;Set the messages waiting flag ; ; Wait for remote input or keyboard depress ; wt: LXI D,WATMSG CALL PRMSG ; **************************************************************** * Enter the WAIT state * **************************************************************** ; WAIT: lhld 6 sphl ;Reinitialize stack (just in case) CALL CMCHK JNZ WAITIN ;Remote input MVI C,11 CALL 5 RRC JNC WAIT ;No keyboard input MVI C,1 CALL 5 CPI ESC ;Did he hit an escape? JNZ WAIT ;No, keep looping LDA MFLG CPI ACK ;Did he have a message waiting? JZ PRMPT LXI D,MWFCMG CALL PRMSG ;He doesn't now JMP PRMPT ; WAITIN: lxi h,wait push h ; save a "return address" call cmin ; Get character mov b,a lxi h,wtstcmtbl waitin1: mov a,m ora a jz wtnak ; invalid command if 0 reached inx h mov e,m ; inx h ; get command routine address mov d,m ; inx h cmp b ; did command match? jnz waitin1 xchg pchl ; yes, go to routine ; ; unrecognizable command ; WTNAK: MVI A,NAK ;Negative acknowledge (what?) CALL CMOUT ret ; ; wait state command table ; wtstcmtbl: db enq ; enq? (establish remote link) dw stlink db stx ; stx? (message waiting) dw rcvmsg db soh ; soh? (incoming program) dw rdpgm db dc1 ; dc1? (transfer control) dw ackcn db dle ; dle? (end remote link) dw enlnk1 db bel ; bel? (wants message we left) dw snlvms db dc2 ; dc2? (remote wants our directory) dw sndir db dc3 ; dc3? (remote wants a program) dw wtsnpm db dc4 ; dc4? (remote wants to select a disk) dw wtdsk db esc ; esc? (conversational mode) dw wtcon db can ; can? (abort some command) dw wtcan db 0 ; **************************************************************** * Cancel command * **************************************************************** ; wtcan: mvi a,can call cmout ret ; **************************************************************** * Enter conversational mode * **************************************************************** ; wtcon: mvi a,esc call cmout ; acknowledge mode call conv$mode ret ; **************************************************************** * Receive disk from remote, log on * **************************************************************** ; WTDSK: MVI A,ACK CALL CMOUT ;acknowledge command MVI B,1 ;He should be ready to send byte now CALL TIMOT ;get disk letter (A,B,C,D) JC WTNAK PUSH PSW LXI D,RSDMSG CALL PRMSG ;Tell user what is happening POP PSW MOV E,A CALL PRCHR ;Print disk remote is trying CALL CRLF CALL LOGIN ;log onto that disk JC WTNAK ;Error on disk logon, send NAK MVI A,ACK CALL CMOUT ;Gotcha, 'pardner! ret ; **************************************************************** * Remote has requested a program * * send ACK, get file name and type * * fall into the SNDPGM routine when set up * **************************************************************** ; WTSNPM: CALL RSTFCB ;Reset the file control block parameters MVI A,ACK LXI H,FCBFN ;Point to area for file name/type MVI C,11 ;# characters in name CALL CMOUT ;request name WTSNP1: CALL CMIN MOV M,A INX H DCR C JNZ WTSNP1 ;get 11 characters from remote LXI D,FCB MVI C,15 CALL 5 ;See if the file exists CPI 255 JNZ WOPNOK ;file opened ok LXI D,FOEMSG CALL PRMSG ;error in file open MVI A,NAK CALL CMOUT ;tell remote to forget it JMP WAIT WOPNOK: MVI A,ACK CALL CMOUT ;tell remote file is on its way, CALL CMIN ;remote sends an ACK when it is setup ;(ignore response) LXI D,SRFMSG CALL PRMSG ;Tell user we are sending remote a pgm CALL PRFLNM ;Print file name CALL SNFLNM ; enter SNDPGM to avoid unneeded stuff LXI D,WATMSG CALL PRMSG ret ; **************************************************************** * Send the remote computer our directory * **************************************************************** ; SNDIR: MVI A,0FFH STA VECTOR ;Tell PRCHR to output dir to remote LXI D,DIMSG CALL PRMSG ;Tell user what is going on LXI H,RDDMSG CALL PRHL0 ;Tell remote what drive he is on LDA DISK MOV E,A CALL PRCHR ;send remote what disk letter CALL CRLF CALL CRLF CALL DIR CALL CRLF CALL CRLF XRA A STA VECTOR ;Reset PRCHR routine MVI A,DC2 CALL CMOUT ;Tell remote we are done LXI D,WATMSG CALL PRMSG ret ; **************************************************************** * Establish the remote link * **************************************************************** ; STLINK: LDA MFLG ;Get the message waiting flag CALL CMOUT ;Acknowledge control by remote LXI D,RLEMSG CALL PRMSG ;Print remote link established ret ; **************************************************************** * Receive remote message * **************************************************************** ; RCVMSG: MVI A,ACK CALL CMOUT ;Acknowledge input of message LXI D,IMMSG ;Print incoming message CALL PRMSG LXI H,INBUF+2 MVI C,0 ;(C) has number of characters NXTCHR: CALL CMIN CPI ETX ;End? JZ PRNMSG ;Yes, print message MOV M,A INX H INR C JMP NXTCHR PRNMSG: LXI H,INBUF+2 ;Pointer to start of text MOV B,C ;B has # characters in message PRMS1: MOV A,M MOV E,A MVI C,2 PUSH H PUSH B CALL 5 ;Print character in string POP B POP H INX H DCR B ;Out of characters? JNZ PRMS1 ret ; **************************************************************** * Send the message we left to the remote computer * **************************************************************** ; SNLVMS: LDA MFLG CPI BEL ;Did we in fact leave a message? JZ SNLVM2 LXI H,NMMSG MVI C,21 ;Remote wants a message - send him ;"No messages" message! JMP SNLMSG SNLVM2: LXI D,RRMMSG CALL PRMSG ;Tell user what is happening LXI H,INBUF+2 LDA INBUF+1 MOV C,A ;HL points to message we left SNLMSG: MOV A,M CALL CMOUT DCR C INX H JNZ SNLMSG ;Send another character MVI A,ETX CALL CMOUT ;All done MVI A,ACK STA MFLG ;Clear the messages waiting flag ret ; **************************************************************** * End remote link * **************************************************************** ; ENLNK1: MVI A,DLE ; Data link escape CALL CMOUT ENLNK: LXI D,ERLMSG ; Tell user he is done CALL PRMSG JMP 0 ; Exit to CP/M ; **************************************************************** * Acknowledge control * **************************************************************** ; ACKCN: MVI A,ACK CALL CMOUT pop h ; throw away return address to wait ; ; Print command prompt and process command state input ; CNPMT: lhld 6 sphl ;Reinitialize stack (just in case) CALL CRLF LDA DISK ;Print disk we are on MOV E,A CALL PRCHR LXI D,CPMSG CALL PRMSG ;Print command mode prompt ; CALL CLRBUF ;Clear and zero input buffer LXI D,INBUF MVI C,10 CALL 5 ;Get command string CALL JUSLF LDA INBUF+1 ORA A JZ CNPMT ; CALL CLRCOM ;Clear garbage out of buffer LXI H,CMTBL ;Point to command table CALL PARSE ;Decode and execute command string ; ; Only errors in command string past here... ; LXI D,ERMSG CALL PRMSG JMP CNPMT ; **************************************************************** * Print directory * **************************************************************** ; CDIR: CALL DIR CALL CRLF JMP CNPMT ; **************************************************************** * Command state disk reset * **************************************************************** ; CRESET: CALL RESET JMP CNPMT ; **************************************************************** * Send the remote a program using the SNDPGM routine * **************************************************************** ; CSNDPGM:CALL INPFLN ;Get the file name ORA A JNZ CNPMT ;Error (INPFLN prints error message) LDA WILD ;Get wild card flag ORA A JNZ CSNDWLD LXI D,FCB MVI C,15 ;Try to open file CALL 5 CPI 255 JNZ CSNDPM1 ;File opened ok CALL PTNSF ;File not opened, print error JMP CNPMT CSNDPM1:CALL SNDPGM ;Send program in FCB JMP CNPMT CSNDWLD:CALL COMDIR ;Compile the directory using afn LDA NFILES ;Get number of files matching afn ORA A JNZ CSWD1 LXI D,DMTMSG CALL PRMSG ;No files match the afn he gave us JMP CNPMT CSWD1: lhld BUFFER ;Point to directory entries CSWD2: MOV A,M CPI 0FFH ;Is this entry an extent? JZ CSWDNX ;Do next entry PUSH H ;Save pointer to file name we want CALL RSTFCB ;Reset the file control block POP H LXI D,FCBFN MVI C,11 CSWD3: MOV A,M ;Move file name to file control block STAX D INX H!INX D ;Bump pointers DCR C JNZ CSWD3 SHLD BPOINT ;Save pointer to directory table for later LXI D,FCB MVI C,15 CALL 5 ;Try to find program (it should be there) CPI 255 JNZ CSWD5 CSWD4: LXI D,SIEMSG CALL PRMSG ;Print file transfer aborted JMP CNPMT CSWD5: LXI D,SNGMSG CALL PRMSG ;Print "sending....." CALL PRFLNM ;Print file name we are sending CALL SNDPGM ;Send the program LDA XERROR ;Check for errors CPI ACK JNZ CSWD4 CSWDNX: LHLD BPOINT ;File xfered ok or was extent - do next CALL BUMPHL ;Point to next directory entry SHLD BPOINT ;if extent, save so next bump will point correctly LDA NFILES DCR A ;Check number of files in table STA NFILES JNZ CSWD2 ;Send next file JMP CNPMT ; **************************************************************** * Get a program from the remote computer * * send the remote a file name, then fall into the RDPGM routine* **************************************************************** ; GTRMPM: CALL INPFLN ;input the file we want ORA A JNZ CNPMT ;goof on file name LDA WILD ;Get wild card flag ORA A JNZ GTRNA CALL CLRCOM ;Clear garbage out of buffer MVI A,DC3 CALL CMOUT ;tell remote we want a program MVI B,1 CALL TIMOT ;time out on response JC CMBOM ;BOMB OUT! GTRMP1: CPI ACK JNZ CMNAK ;Remote did not respond with a NAK LXI H,FCBFN MVI C,11 ;# characters in name GTRMP2: MOV A,M CALL CMOUT INX H DCR C JNZ GTRMP2 ; MVI B,16 CALL TIMOT ;allow remote enough time to check disk JC CMBOM CPI ACK JNZ GTRNE ;sorry! CALL RDPGM ;remote will start sending name JMP CNPMT GTRNE: CALL PTNSF ;print remote file does not exist JMP CNPMT GTRNA: LXI D,UFOMSG CALL PRMSG ;Unambiguous file name only JMP CNPMT ; **************************************************************** * Print command menu * **************************************************************** ; COHEP: LXI D,COHEMS CALL PRMSG JMP CNPMT ; **************************************************************** * Enter conversational mode * **************************************************************** ; cogo: mvi a,esc call cmout ; request conversational mode mvi b,1 call timot jc cmbom ; bomb out if no response cpi esc jnz cmnak ; check for escape call conv$mode jmp cnpmt ; **************************************************************** * Print NAK from remote, re-enter Command state * **************************************************************** ; CMNAK: LXI D,NAKMSG;Remote is active, sent a NAK CALL PRMSG JMP CNPMT ; **************************************************************** * Print no response from remote, bomb back to initial state * **************************************************************** ; CMBOM: LXI D,NOMSG ;Remote dead, no response... CALL PRMSG JMP PRMPT ; **************************************************************** * Print the remote directory * **************************************************************** ; GTRMDR: LXI D,DIRMSG CALL PRMSG lhld buffer ;Point to dir string MVI A,DC2 CALL CMOUT ;Say we are ready MVI B,6 ;He has to get to the directory track CALL TIMOT ;Should start sending directory ASAP JC CMBOM CPI NAK ;did he ACK or NAK? JNZ GTRD2 JMP CMNAK ;must not have this capability GTRD1: CALL CMIN ;Get character of directory GTRD2: MOV M,A CPI DC2 ;Check for end of dir JZ GTRD3 ;Done, go print dir INX H LDA 7 ;Get high addr of BDOS CMP H ;Have we run out of buffer space? JNZ GTRD1 ;No, continue getting dir characters DCX H ;Yes, back up a byte MVI M,DC2 ;Fake end of directory LXI D,OVMSG CALL PRMSG ;Inform user we had an overflow GTRD4: MVI B,1 ;See if remote is still sending dir CALL TIMOT JC GTRD3 ;Timed out, go print dir CPI DC2 JNZ GTRD4 ;Remote ain't done yet (ho hum) GTRD3: lhld buffer CALL PRHL0 ;Print the directory now JMP CNPMT ; **************************************************************** * CNDISK selects disk A,B,C,D * **************************************************************** ; CNDISK: LDA INBUF+2 CALL LOGIN JMP CNPMT ; **************************************************************** * STRMDR selects disk on the remote computer * **************************************************************** ; STRMDR: MVI A,DC4 ;tell remote to select disk CALL CMOUT MVI B,4 ;Not long wait CALL TIMOT JC CMBOM CPI ACK JC CMNAK ;insure remote knows what we are doing LDA INBUF+3 CALL CMOUT ;send the remote the disk number MVI B,20 ;Give him 5 seconds to log on CALL TIMOT JC CMNAK ;We timed out - bomb out! CPI ACK JZ CNPMT ;We did ok LXI D,DSEMSG CALL PRMSG ;We screwed up JMP CNPMT ; **************************************************************** * Get the message remote left us * **************************************************************** ; INWMSG: MVI A,BEL LXI H,INBUF+2 MVI C,0 ;HL has buffer area, C has count CALL CMOUT ;Signal remote we want message INWMS1: MVI B,4 CALL TIMOT ;Give him a seconds to respond JC CMBOM ;No response JMP INWMS4 CALL CMIN INWMS4: CPI ETX ;End of text? JZ INWMS2 MOV M,A ;Stuff letter INX H INR C ;Advance pointers JMP INWMS1 INWMS2: LXI H,INBUF+2 MOV E,C ;Put the length into DE MVI D,0 DAD D ;HL points to last character in string INX H MVI M,'$' ;Stuff CP/M end-of-string LXI D,INBUF+2 CALL PRMSG ;Print string CALL CRLF JMP CNPMT ; **************************************************************** * Transfer control to remote * **************************************************************** ; TRCON: MVI A,DC1 ;Transfer control CALL CMOUT MVI B,1 ;Should ACK this right away CALL TIMOT JC CMBOM CPI ACK ;Control acknowledged? JNZ CNT LXI D,CTMSG CALL PRMSG ;Control transferred message jmp wt ; CNT: LXI D,TDCMSG CALL PRMSG JMP CMNAK ;Print control not xferd, NAK! ; **************************************************************** * Send a message to remote * **************************************************************** ; SNDMSG: MVI A,STX CALL CMOUT MVI B,1 ;Just acknowledge command here CALL TIMOT JC CMBOM ;remote timed out CPI ACK JNZ CMNAK ;Received ACK ok? SM1: LXI D,PMTMSG CALL PRMSG CALL MSGIN ;Get message CALL CRLF ; ; Now to send message ; LXI H,INBUF+2 LDA INBUF+1 ;Get length of message MOV C,A SMLUP: MOV A,M INX H CALL CMOUT DCR C JNZ SMLUP MVI A,0DH CALL CMOUT MVI A,0AH CALL CMOUT MVI A,ETX CALL CMOUT JMP CNPMT ; **************************************************************** * * * MAIN LINK90 SUBROUTINES * * * **************************************************************** ; **************************************************************** * Command parser * **************************************************************** ; ; HL is assumed to point to the start of the appropriate ; command table, organized as follows: ; ; ,,,..............,,, ; <0> - signifies end of table ; PARSE: MOV A,M ;Length of this entry ORA A JZ TRYDSK ;End of table, try disk command MOV B,A LXI D,INBUF+1 LDAX D ;Get length of input string CMP B JZ CHECK ;Equal, check characters NEXT: MOV E,B MVI D,0 DAD D ;Add length to pointer INX H ;Past length byte INX H!INX H ;Past routine address JMP PARSE CHECK: INX H INX D ;Do next character LDAX D CPI 61H ;<"z"? JM CHECK1 SUI 20H ;Adjust lower case CHECK1: CMP M ;Equal? JZ CNTCHK DCR B ;Fixup character count JMP NEXT ;Do next command CNTCHK: DCR B JNZ CHECK ;Not done yet INX H ;Strings equal! GTOHL: MOV E,M INX H ;Get routine address MOV D,M XCHG ;HL has address to go to POP D ;Dummy pop, we called this routine PCHL ;Go to routine ; TRYDSK: INX H ;HL points to disk select routine PUSH H ;Save for later LXI H,INBUF+1 ;Point to start of input string MOV A,M ;Get length MOV E,A MVI D,0 ;DE has length DAD D ;Point to last character in string MOV A,M CPI ':' ;Disk select command? POP H ;Restore stack RNZ ;Input error, return LDA INBUF+1 ;Get length CPI 2 ;Disk select "d:"? JZ GTOHL ;HL has vector, exit as above CPI 3 ;Disk select "Rd:"? RNZ ;Input error, return LDA INBUF+2 CPI 'R' ;Disk select "Rd:"? RNZ ;Input error, return JMP STRMDR ;Go select remote drive ; **************************************************************** * TERMINAL mode subroutines * **************************************************************** ; ; Send string pointed to by HL to remote computer ; TOUT: MOV A,M ;Get the byte ORA A RZ ;Done on a zero byte CALL CMOUT ;Output INX H JMP TOUT ;Next ; ; Output halt string to remote computer, wait until it is ready ; THALT: mvi a,dc3 call cmout ; output x-off (dc3) THLUP: MVI B,8 CALL TIMOT ;Delay 2 seconds - once we time out we can assume ;that remote output has stopped RC ;We timed out, return LHLD BPOINT MOV M,A ;Stuff another input byte... INX H ;Increment pointer SHLD BPOINT JMP THLUP ;Continue process ; ; Dump what we have to the disk and move the excess (That which is not ; enough to put on a record) down to the start of the buffer. Reset the ; buffer pointer to the end of this data and resume. ; TDUMP: lhld BUFFER ;Where to start TDUMP1: LDA BPOINT+1 CMP H ;Last page yet? JNZ TDUMPIT ;No, dump this record to disk LDA BPOINT ;Check hi or lo part of page SUB L ANI 80H ;Enough for another record? JZ TDUMP2 ;Go move excess down if less than 80H; ;If less than a page but more than a record ;(80H-FFH) dump a record THEN move excess TDUMPIT:MOV D,H MOV E,L ;DE has DMA address PUSH H MVI C,26 CALL 5 ;Set DMA address LXI D,FCB MVI C,21 CALL 5 ;Write sequential record POP H ORA A ;Was write successful? JZ TDUMPC ;Yes, continue dumping records LXI D,TBDMSG CALL PRMSG ;Inform user of a bad write STC ;Set error indicator RET TDUMPC: LXI D,80H DAD D ;Advance to next page JMP TDUMP1 TDUMP2: LDA BPOINT ;Get lower byte of address pointer ANI 7FH ORA A push h lhld buffer xchg ; start at beginning if even boundary pop h JZ TDUMP5 ;We are on an even boundary LHLD BPOINT ;HL has "from" address ;DE has "to" address (BUFFER) MOV A,L ANI 7FH ;Get the number of excess bytes MOV C,A MOV A,L ANI 80H ;Round the from pointer to start of page MOV L,A ;HL points to start of page TDUMP3: MOV A,M STAX D ;Move the excess data down INX D INX H DCR C ;Loop thru data JNZ TDUMP3 TDUMP5: XCHG SHLD BPOINT ;Where we will start new data STC CMC ;Reset error indicator RET ; **************************************************************** * Print directory * **************************************************************** ; DIR: LXI H,FCBFN MVI C,12 DIR5: MVI M,'?' ;Look for all files INX H DCR C JNZ DIR5 XRA A STA FCBDN CALL COMDIR ;Compile the directory lhld BUFFER ;Point to entries LDA NFILES MOV C,A ;Save in C as counter ORA A JNZ DIR1 LXI H,DMTMSG MVI B,9 CALL PRHL ;Print directory empty RET DIR1: MVI B,3 ;3 entries per line DIR2: MOV A,M CPI 0FFH ;Blanked out? JZ DIR3 PUSH B ;Save counter MVI B,8 CALL PRHL ;Print name CALL SPACE MVI B,3 CALL PRHL ;Print type INX H!INX H!INX H ;Bump to length MOV E,M INX H MOV D,M ;Stuff length to DE CALL PRDE ;Print length POP B ;Recover counters DCR C RZ ;Check for last file CALL BUMPHL ;Go to next file at any rate DCR B PUSH PSW CZ CRLF ;Last file in line, new line POP PSW JZ DIR1 ;Reset # files in line CALL PRCOL ;Not last file in line, print colon JMP DIR2 DIR3: DCR C RZ CALL BUMPHL ;Point to next entry JMP DIR2 ; *************************************************************** * Bump HL to next even 16 bytes * *************************************************************** ; BUMPHL: MOV A,L ANI 0F0H ;Strip down to lower even 16 MOV L,A LXI D,16 ;Add 16 DAD D RET ; **************************************************************** * Prepare file for writing * **************************************************************** ; PRPFIL: LXI H,FCBRL XRA A ;Set file extent to 0 MOV M,A INX H MOV M,A INX H MOV M,A INX H MOV M,A ;Set record count to 0 LXI H,FCBCR MOV M,A ;Set next record (R/W) to 0 LXI D,FCB MVI C,17 CALL 5 CPI 255 ;Was there an existing program? JZ CRFL ;No, create one ; ; File already exists, see if user wants to create new ; file name or write over old file ; LXI D,FEMSG CALL PRMSG MVI C,1 ;Get "D" for delete or any other char CALL 5 PUSH PSW ;Save input CALL CRLF ;New line, please! POP PSW ;Recover input CPI 'D' ;Write over old file? JZ OPNRD ; ; Input new file name ; CALL CRLF PRPFL1: CALL INPFLN ORA A ;Did he botch it? JNZ RDPNAK LDA WILD ;Check for wild card ORA A JZ CRFL ;Ok, open file LXI D,UFOMSG CALL PRMSG ;No wild cards here JMP PRPFL1 ;Try again ; RDPNAK: STC RET ;Return an error condition ; CRFL: LXI D,FNEMSG CALL PRMSG ;Print new file LXI D,FCB MVI C,22 CALL 5 ;Create file in directory CPI 255 ;Error? JNZ OPNRD ; ; Directory full (or other error) ; LXI D,DFMSG CALL PRMSG JMP RDPNAK ; ; File ready for opening.... ; OPNRD: LXI D,FCB MVI C,15 CALL 5 ;Open file for writing STC CMC ;Reset error condition RET ; **************************************************************** * Recieve program from remote * **************************************************************** ; RDPGM: LXI D,PGMMSG CALL PRMSG ;Print incoming program MVI A,ACK LXI H,FCBFN ;Read in the file name MVI C,11 CALL CMOUT ;Tell remote we want name RDPM1: CALL CMIN MOV M,A INX H DCR C JNZ RDPM1 CALL PRFLNM ;Print name of incoming program ; ; File name is in FCB, see if it is on disk... ; CALL PRPFIL ;Prepare file for writing JNC RDPM2 ;No errors, send ACK MVI A,NAK CALL CMOUT ;Error in file preparation RET ; ; Send to remote ; RDPM2: mvi a,10 sta pass LXI H,1 ;Start at record 1 SHLD RECNUM MVI A,ACK CALL CMOUT ; ; File is set up for writing. Remote will now send one ; of three command bytes: ; - one 128 byte data block with CRC ; - remote file error, crash! ; - end of transmission, file transfer complete ; - cancel transmission ; RDBLOK: CALL CMIN CPI SOH JZ RDATA ; Get data block cpi can jz rdcrsh ; read crash, close everthing down... CPI NAK JZ RDCRSH ; ditto CPI EOT JZ ENDRD ; End read, close file ; ; remote sent something we didn't understand. wait till he shuts up ; then send him a ; rdwhat: mvi b,2 call timot jnc rdwhat ; he sent something. ignore it mvi a,nak call cmout ; we timed out, send jmp rdblok ; ok, go wait for instructions ; ; error. delete input file and exit ; RDCRSH: LXI D,FCB MVI C,19 CALL 5 ;Delete that file LXI D,RERMSG CALL PRMSG ;Signal remote error RET ; ; end of transmission ; ENDRD: CALL CRLF ;Done, next line after record number LXI D,FCB MVI C,16 CALL 5 ;Close file LXI D,EOFMSG CALL PRMSG ;Signal file xfer successful MVI A,ACK CALL CMOUT ;Acknowledge end of file RET ; ; Read program data. Remote is not waiting for us, so do this FASTFASTFAST ; If we end up stuck waiting for a byte, the remote will recover by sending ; nulls ; RDATA: call cmin1 ; get lsb record # push psw ; save block # lxi h,80h ; set dma mvi c,80h ; # bytes to read in a data record RDAT1: CALL CMIN ; get data MOV M,A ; stuff it INX H dcr c JNZ RDAT1 ; ; get crc ; call cmin ; get crc lo byte mov e,a call cmin ; get crc hi byte mov d,a push d ; save crc ; ; ok, remote is finished. print record #, compute crc, check block # ; CALL PRRNM ; Print record number and junk pop d ; recover recieved crc call crck ; return data crc in hl pop psw mov c,a ; recover block # in c mov a,h cmp d jnz rdater ; compare hi bytes of crc mov a,l cmp e jnz rdater ; error if lo bytes of crc don't match ; ; compare block # ; lda recnum cmp c JZ STDAT ; if block # matched, store data dcr l cmp l ; did he send last block? jz datgd1 ; yes, send dummy jmp hdrder ; hard error, block number is screwed up ; ; crc error on last data block ; rdater: LDA PASS DCR A ; Last pass? JZ HDRDER STA PASS MVI A,NAK ; Signal remote we want last rec again CALL CMOUT JMP RDBLOK ; ; Failed sending record 10 times, bomb out ; HDRDER: CALL CRLF LXI D,FCB MVI C,16 CALL 5 ;Delete file LXI D,CRCMSG CALL PRMSG ;Signal remote link unstable MVI A,NAK CALL CMOUT ; to remote RET ; ; record arrived intact, save on disk ; STDAT: LXI D,FCB MVI C,21 CALL 5 ;Write this record onto disk ORA A JZ DATGUD ;Successful write ; ; Did not successfully write this record - report error ; CALL CRLF CPI 1 LXI D,EXTMSG JZ RDERRP ;Report extend error CPI 2 LXI D,EDDMSG JZ RDERRP ;Report end of disk data error CPI 255 LXI D,DFMSG JZ RDERRP ;Report no directory space LXI D,FLERMG RDERRP: CALL PRMSG ;Report error LXI D,FCB MVI C,19 CALL 5 ;Delete file MVI A,NAK CALL CMOUT ;Signal remote we had an error RET ; ; data stored on disk ok. bump record number and get next packet ; DATGUD: MVI A,10 STA PASS LDA DISK CALL LOGIN ;Re-login disk LHLD RECNUM INX H SHLD RECNUM ;Increment record number datgd1: MVI A,ACK CALL CMOUT ; good data JMP RDBLOK ;Wait for next command byte from remote ; **************************************************************** * Print no such file and filename in FCB * **************************************************************** ; PTNSF: LXI D,FOEMSG CALL PRMSG ;Print no such file CALL PRFLNM ;Print file name RET ; **************************************************************** * Send program to remote computer * **************************************************************** ; SNDPGM: CALL CLRCOM ;Clear garbage out of buffer MVI A,SOH CALL CMOUT MVI B,8 ;Give him a few seconds to print message CALL TIMOT JC OPNOK1 ;Remote time-out CPI ACK ;Remote acknowledged input? JZ SNFLNM OPNOK1: LXI D,NAKMSG CALL PRMSG ;Print from remote JMP CLSFLE ;Close file and return ; ; Send filename to remote computer ; SNFLNM: MVI A,NAK ;Assume error, routine STA XERROR ; must have correct CRC & force non-error LXI H,FCBFN ;Point to filename MVI C,11 ;Number of characters in file (name+type) NMLP: MOV A,M ANI 7FH ;CP/M 2.0 does funny things to bits! CALL CMOUT ; send a character in file name INX H DCR C JNZ NMLP ; LXI H,0 ;Start at 1 (1-1) SHLD RECNUM ; ; Wait for remote to acknowledge file name ; mvi b,40 ; give him a while - he may want to rename it CALL timot jc snpgm0 ; time out? bomb out. CPI ACK jz snpgm1 ; ok - go snpgm0: lxi d,nakmsg call prmsg ; tell user remote screwed up jmp snlp5 ; close file and ; ; Send program in 128 byte blocks plus CRC ; SNPGM1: mvi a,10 sta pass ; set error/retry count LHLD RECNUM INX H SHLD RECNUM ; Increment record number ; LXI D,FCB MVI C,20 CALL 5 ; Read one record CPI 0 JZ SNPM2 ; No errors, send program CPI 1 JZ ENDSND ; End program ; LXI D,FIEMSG CALL PRMSG ; File input error jmp snlp5 ; close file, send ; ; send record. print record number we are sending, check for user ; abort (esc key) ; SNPM2: CALL PRRNM ;Print record number and junk MVI C,11 CALL 5 ;Check for user input ORA A JZ SNPM3 ;No input, continue ; ; process user keystrike ; LXI D,SABMSG CALL PRMSG ; Print abort message MVI C,1 CALL 5 ; Get input CPI ESC PUSH PSW CALL CRLF ; CRLF at any rate POP PSW JNZ SNPM3 ; Continue, he didn't hit escape ; LXI D,SIEMSG CALL PRMSG ; Print xfer aborted jmp snlp5 ; close file, send ; ; One record in, send: ; ; block # ; 128 data bytes ; CRC ; wait for ; SNPM3: MVI A,SOH CALL CMOUT ; Send ; lda recnum call cmout ; send lsb block # ; LXI H,80H MVI C,80H ; number of bytes to send per record SNLP1: MOV A,M CALL CMOUT ; Send program byte INX H DCR C JNZ SNLP1 ; call crck ; compute crc on data mov a,l call cmout ; send crc lo byte mov a,h call cmout ; send crc hi byte CALL CLRCOM ; Get rid of any junk we may have recieved ; ; give remote time to compute crc and stuff data on disk ; MVI B,32 ;He has to stuff on disk, leave 8 seconds CALL TIMOT ;Get the remote sent (hopefully) JNC SNLP2 ;We have something... ; ; We timed out on a wait for remote acknowledge of this ; record. Continue to send nulls until a is received ; If something else comes in, BOMB OUT! ; SNWT: XRA A CALL CMOUT ;Send a null MVI B,1 ;He should give a NAK as soon as bad data ;is found, don't wait long CALL TIMOT JC SNWT ;Loop until remote sends us something CPI nak JZ SNLP3 ;We can recover, remote sent jmp snlp5 ; close file, send ; ; Remote sent us a byte after the record and CRC. ; Check to see what it is and process accordingly. ; SNLP2: CPI ACK ; See if the character is an JZ snpgm1 ; Last 128 received OK ; ; Remote didn't get last record - see if we can try again... ; SNLP3: LXI H,PASS DCR M ; Decrement pass count jnz SNPM2 ; Go try again lxi d,crcmsg call prmsg ; we tried, but we couldn't do it.... ; ; output cancels until remote acknowledges us ; snlp5: call CLSFLE ; Close the file snlp6: mvi a,can call cmout ; output cancel mvi b,1 call timot ; don't wait too long jc snlp6 cpi can jnz snlp6 ; keep trying if we didn't get back ret ; ; finished sending file - send ; ENDSND: MVI A,EOT ; End of transmission CALL CMOUT mvi b,30 ; give remote time to close disk file CALL timot jc snlp5 ; if time out, CPI ACK ; Acknowledge? JZ CLSFL ; ok - set non error condition and exit ; CALL CRLF lxi d,nakmsg ; remote crapped out... call prmsg jmp snlp5 ; ; CLSFL: MVI A,ACK STA XERROR ; Signal non error condition CALL CRLF LXI D,EOFMSG CALL PRMSG CLSFLE: LXI D,FCB MVI C,16 CALL 5 ; Close the FCB file (ignore error) RET ; **************************************************************** * compute cyclic redundancy check for data at 80h, using * * a generator polynomial of : * * 16 15 2 * * G(x) = x + x + x + 1 * * * * returns crc in hl. * **************************************************************** ; crck: push b ; save regs push d lxi h,80h lxi b,0 ; initialize crc mov d,m ; d contains first data byte mvi e,9 ; e contains # bits to process (+1) ; ; shift in bit ; shiftin: ; ; get next data bit to carry ; dcr e jnz getbit1 ; check for end of byte mvi e,9 ; reset number of bits to munch on (+1) inx h ; bump to next byte mov a,l ora a ; reached end of buffer? jz crcdun ; yes, we are done mov d,m ; nop, fetch next byte ; ; rotate next available bit into carry ; getbit1: mov a,d ral mov d,a ; ; shift crc byte ; mov a,b rar ; put input bit in bit 15 mov b,a mov a,c rar mov c,a ; ; bit 15 := (input bit) xor (carry) ; rar ; put carry in bit 7 ani 80h ; mask other bits xra b ; xor with bit 15 of crc (input bit) mov b,a ; store ; ; bit 13 := (bit 13) xor (bit 15) ; ani 80h ; mask other bits rrc rrc ; rotate bit 15 to bit 13 xra b ; xor bit 15 with bit 13 mov b,a ; store ; ; bit 0 := (bit 0) xor (bit 15) ; ani 80h ; mask other bits rlc ; rotate bit 15 into bit 0 xra c ; xor with bit 0 mov c,a ; store jmp shiftin ; process next input bit ; crcdun: mov h,b mov l,c pop d pop b ret ; **************************************************************** * Compile directory list using ambiguous file name in FCB * * Stuff number of files in NFILES, names in BUFFER * **************************************************************** ; COMDIR: XRA A STA FCBDN ;We want current disk STA NFILES ;Clear number of files lhld BUFFER SHLD BPOINT ;Buffer pointer MVI A,3FH STA 68H LXI D,5CH MVI C,11H CALL 5 ;Find first file CPI 0FFH ;Did any files exist? RZ ;Return with NFILES set to 0 ; CMDR1: LXI H,NFILES INR M ;Increment number of files MVI B,0 ;Convert byte address to pointer to name ANI 3 RRC!RRC!RRC INR A ;Compensate for disk drive MOV C,A ;Stuff offset into BC LXI H,80H DAD B ;HL now points to name XCHG LHLD BPOINT ;Get address of buffer pointer MVI C,16 ;Number of characters per file name CMDR2: LDAX D MOV M,A ;Transfer a byte INX D INX H ;Knock pointers DCR C JNZ CMDR2 ;Loop thru 16 characters DCX H MVI M,0 ;Zero upper length byte INX H SHLD BPOINT ;Resave our buffer pointer LXI D,5CH MVI C,12H CALL 5 ;Find next file CPI 0FFH JNZ CMDR1 ;There was a file, stuff it ; ; All files now in memory, add extent lengths and delete them ; lhld BUFFER ;Point to start of buffer LDA NFILES MOV C,A ;C has number of files to work on CMDR3: LXI D,11 ;Offset to extent DAD D MOV A,M ;Get extent LXI D,-11 DAD D ;Restore pointer to file name ORA A PUSH B ;Save number of files left to process JZ CMDR7 ; ; Entry is an extent, find extent 0, add lengths, delete this entry ; LXI D,14 ;Offset to length DAD D MOV B,M ;Get length LXI D,-14 DAD D ;Restore pointer push h lhld buffer xchg ; de := buffer pop h LDA NFILES MOV C,A ;Number of files to check for match CMDR4: LDAX D CMP M ;Bytes equal? JNZ CMDR5 INX H INX D MOV A,E ANI 0FH ;Mask upper nibble CPI 11 ;Done checking yet? JNZ CMDR4 LDAX D ;Get extent (should be zero) ORA A JNZ CMDR5 ;This isn't extent zero, bomb out MOV A,L ANI 0F0H MOV L,A MVI M,0FFH ;Scratch the extent PUSH H INX D!INX D!INX D ;Point to # records field PUSH D ;Save pointer to # records XCHG MOV E,M INX H MOV D,M ;DE has length of the 0th extent XCHG ;Transfer to HL MOV E,B MVI D,0 ;Stuff DE with length of extent DAD D ;Add the extent to the base XCHG POP H ;HL has pointer to # records MOV M,E INX H MOV M,D ;Stuff new length POP H ;Recover working file pointer JMP CMDR7 ;Done fixing, next file CMDR5: DCR C ;Bump number of files left to check JZ CMDR8 ;We have checked all files, no extent 0 PUSH H ;Save pointer XCHG ;HL has pointer to test record CALL BUMPHL ;Reset pointer to start of next record XCHG ;DE should have it POP H MOV A,L ANI 0F0H MOV L,A ;Restart at beginning of record JMP CMDR4 ; CMDR7: MOV A,L ANI 0F0H MOV L,A ;Restore to start of record LXI D,16 DAD D ;Point to next record POP B ;Recover number of files left to work on DCR C JNZ CMDR3 ;Go do next file RET ; ; Hmmmm - extent 0 not found for this extent - someone is wierd. ; Process this extent by itself, make it 0, add number of records ; CMDR8: MOV A,L ANI 0F0H ;Restore to start of record MOV L,A LXI D,11 DAD D MOV A,M ;Get this extent number MVI M,0 ;While we are here, erase extent number INX H!INX H!INX H ;Bump to length PUSH H ;Save pointer to length LXI H,0 LXI D,80H CMDR9: DAD D DCR A ;Compute number of records for this extent JNZ CMDR9 PUSH H POP B ;BC=#records POP H ;Recover pointer to length PUSH H ;Resave MOV E,M INX H MOV D,M ;DE=leftover records XCHG DAD B ;HL has total number of records XCHG ;To DE POP H ;Recover pointer to length MOV M,E ; INX H ;Stuff actual # of records MOV M,D ; JMP CMDR7 ; **************************************************************** * Print DE in decimal in a 5 digit, right justified field * * (Adapted from 8080/8085 Software Design, Chris Titus) * **************************************************************** ; PRDE: PUSH H!PUSH B ;Save pointers LXI H,UNITS ;Temporary storage area for ASCII string MVI C,5 XRA A STA ZGONFLG ;Leading zero blanking flag PRDE1: MVI M,' ' INX H DCR C ;Blank ASCII field JNZ PRDE1 DCX H ;Point to tens of thousands LXI B,-10000 CALL DIGIT ;Figure tens of thousands LXI B,-1000 CALL DIGIT ;Figure thousands LXI B,-100 CALL DIGIT ;Figure hundreds LXI B,-10 CALL DIGIT ;Figure tens MOV A,E ORI '0' ;Remainder is ones, make ASCII MOV M,A ;and stuff LXI H,UNITS+4 MVI C,5 PRDE2: MOV E,M ;Get character CALL PRCHR DCX H ;Bump pointer DCR C JNZ PRDE2 ;Loop thru digits POP B!POP H ;Restore pointer RET DIGIT: PUSH H ;Save pointer to 5 digit area XCHG DAD B ;Subtract by adding 2's complement JNC ADDIT ;NC indicates less than test value XCHG POP H ;Restore pointer INR M ;Increment digit MVI A,0FFH STA ZGONFLG ;Kill leading zeroes flag JMP DIGIT ADDIT: MOV A,C ;Change sign of test value in BC CMA MOV E,A MOV A,B CMA MOV D,A ;DE = ABS(BC) INX D DAD D ;Restore value XCHG ;To DE POP H LDA ZGONFLG ORA A JZ DRET ;Non-zero has been encountered MOV A,M ORI '0' ;Convert to ASCII MOV M,A DRET: DCX H ;Do next digit RET ; **************************************************************** * Print the character in the E register * **************************************************************** ; PRCHR: PUSH H!PUSH D!PUSH B!PUSH PSW MOV A,E ;Get character ANI 7FH ;Get rid of 2.0 bits MOV E,A ;Restore LDA VECTOR ORA A ;Check output vector JZ PRCHR1 ;Remote doesn't get this one MOV A,E CALL CMOUT ;Call comout routine PRCHR1: MVI C,2 CALL 5 POP PSW!POP B!POP D!POP H RET ; **************************************************************** * Print a space * **************************************************************** ; SPACE: MVI E,20H CALL PRCHR RET ; **************************************************************** * Input a character from console with no echo * **************************************************************** ; EKONI: mvi c,12 call 5 mov a,h ora a jnz ekoni1 mov a,l ora a jnz ekoni1 ; ; user has cp/m 1.4 or earlier, do bios call to get character ; LHLD 1 ;Get warm start address from CBIOS LXI D,6 ;Offset to console input DAD D ;HL has address of console input routine PCHL ;Use routine's RET to get back ; ; user has cp/m 2.0 or greater or mp/m, use direct console i/o ; ekoni1: mvi c,6 mvi e,0ffh call 5 ora a ; was a character waiting? jz ekoni1 ; no, loop ret ; **************************************************************** * Print memory at (HL) for the number of bytes in (B) * **************************************************************** ; PRHL: MOV E,M CALL PRCHR INX H DCR B JNZ PRHL RET ; **************************************************************** * Print message at DE to $ using CP/M * **************************************************************** ; PRMSG: MVI C,9 CALL 5 ;Go to CP/M and print it RET ; **************************************************************** * Print memory at (HL) until DC2 * **************************************************************** ; PRHL0: MOV E,M MOV A,E CPI DC2 RZ CALL PRCHR INX H JMP PRHL0 ; **************************************************************** * Print a colon * **************************************************************** ; PRCOL: CALL SPACE MVI E,3AH ;Ascii colon CALL PRCHR CALL SPACE RET ; **************************************************************** * print a as 2 hex characters * **************************************************************** ; prhex: push psw rar rar rar rar call prnyb pop psw ; ; print lower nybble of a as 1 hex character ; prnyb: ani 0fh ; mask other bits cpi 10 ; A-F? jc prnyb1 ; no, just add offset ('0') adi 7 ; yes, bump past ascii characters to 'A' prnyb1: adi '0' mov e,a mvi c,2 call 5 ret ; **************************************************************** * Returns zero if buffer at M(DE) equals input buffer * **************************************************************** ; BFCHK: LXI H,INBUF+2 LDA INBUF+1 ;Get the number of characters CPI 2 JNZ NMTCH MOV C,A XCHG ;Move input pointer to DE BFCHK1: LDAX D CPI 61H JM BFCHK2 SUI 20H ;upper case, please! BFCHK2: CMP M JNZ NMTCH DCR C JZ MATCH INX H INX D JMP BFCHK1 NMTCH: XRA A INR A RET MATCH: XRA A RET ; **************************************************************** * Reset the file control block for a new file * **************************************************************** ; RSTFCB: MVI C,11 LXI H,FCBFN ;Fill file name and type with blanks RSTFCB1:MVI M,' ' INX H DCR C JNZ RSTFCB1 ; ; Insert zeroes into the FCB area ; XRA A STA FCB ;Use disk we are on LXI H,FCBRL ;Start on reel number MVI C,21 INZLP: MOV M,A INX H DCR C JNZ INZLP RET ; **************************************************************** * Input a file name, parse into file control block * * If name is preceded by disk number (A:), parse that * * out and log into that disk. * **************************************************************** ; ; First get a string to the buffer area ; INPFLN: LXI D,FNMMSG CALL PRMSG ;Prompt for file name LXI D,INBUF MVI C,10 CALL 5 ;Input string to buffer area CALL CRLF ;Next line, please! CALL RSTFCB ;Reset the file control block LXI H,INBUF+1 MOV B,M ;B has length of input string LDA INBUF+3 ;Get second character in string CPI ':' ;Disk specified? LXI H,INBUF+2 JNZ PRSNAM ;No, HL points to start of name MOV A,M CALL LOGIN ;Log in to disk DCR B DCR B ;Compensate for disk name LXI H,INBUF+4 ; ; HL points to start of file name in buffer ; B has length of input ; PRSNAM: XRA A STA FCBDN ;Record number = 0 STA WILD ;Reset wildcard LXI D,FCBFN MVI C,9 PSNM1: MOV A,M ;Transfer filename (up to 8 chars) INX H CPI '.' JZ PRSTYP ;It was a '.', parse filetype CPI '?' JZ PSNM3 ;It was a '?', set wildcard and stuff CPI '*' JNZ PSNM4 ;Check for asterisk wildcard option STA WILD ;Wildcard set DCR C ;C has number of characters to stuff MVI A,'?' PSNM5: STAX D INX D DCR C JNZ PSNM5 DCR B JZ PSTP2 ;Didn't specify file type MOV A,M INX H CPI '.' JNZ PSNM2 ;Error in filename, should be a '.' here JMP PRSTYP ;Go parse file type PSNM3: STA WILD ;Stuff wildcard option PSNM4: STAX D INX D DCR B JZ PSTP2 ;Didn't specify file type, leave it blank DCR C JNZ PSNM1 PSNM2: LXI D,NTLMSG ;Error in filename CALL PRMSG MVI A,255 ;Set error return RET PRSTYP: DCR B ;B has number of characters in type MVI C,4 ;# characters in file type (-1) LXI D,FCBFT PSTP1: MOV A,M INX H CPI '?' ;Check for wildcard JZ PSTP3 CPI '*' ;Check for wildcard JNZ PSTP4 DCR C ;C has number of characters to stuff STA WILD MVI A,'?' PSTP5: STAX D ;Finish stuffing filetype with '?' INX D DCR C JNZ PSTP5 DCR B JZ PSTP2 ;* should be last character in string JMP PSNM2 PSTP3: STA WILD PSTP4: STAX D INX D DCR B JNZ PSTP1 PSTP2: XRA A ;Zero error flag RET ; **************************************************************** * Input a message terminated by escape into buffer at INBUF * * Character count at INBUF+1, message starts at INBUF+2 * **************************************************************** ; MSGIN: LXI H,INBUF+1 ;HL points to length MVI M,0 ;Zero to start LXI D,INBUF+2 ;DE points to message MSGIN1: PUSH H PUSH D MVI C,1 CALL 5 ;Read in a character POP D POP H CPI ESC ;End of string? RZ CPI BS ;Check for each kind of backspace JZ BKMSG CPI DEL JZ BKMSG CPI DEL-20H ;Cap lock delete! JZ BKMSG CPI CR ;If carriage return, add line feed JNZ MSGIN2 STAX D INX D INR M MVI A,LF PUSH H PUSH D PUSH PSW MOV E,A MVI C,2 CALL 5 POP PSW POP D POP H MSGIN2: STAX D INX D INR M JMP MSGIN1 BKMSG: LDA INBUF+1 ;Get present string length ORA A JZ MSGIN1 ;Already zero, start over DCX D DCR M JMP MSGIN1 ;Decrement pointer, counter ; **************************************************************** * Wait for input from remote - after timeout, set carry flag=0 * * 0 if we got data, carry=1 if time out. * * B=1 is about 1/4 second for 2 Mhz... * **************************************************************** ; TIMOT: PUSH H ;Save timing registers PUSH B LHLD SPEED ;Get CPU speed from LINKSBS MOV A,M CPI 2 ;2 Mhz? JZ TIMLUP ;B has correct value MOV A,B RAL ;Double B for 4 Mhz MOV B,A ;Restore B ; TIMLUP: LXI H,13 ;Timing value for wait loop TMLP1: CALL CMCHK ANA A ;Set flags JNZ TIMED ;We have some input DCR H JNZ TMLP1 ;Loop thru H DCR L JNZ TMLP1 ;Loop thru L DCR B JNZ TIMLUP ;Loop thru B XRA A ;Clear data STC ;Set error byte POP B POP H RET ;Return from timing loop TIMED: POP B ;Restore BC POP H CALL CMIN ;Get the data STC CMC ;Reset error byte RET ; **************************************************************** * Print the file name in the FCB * **************************************************************** ; PRFLNM: MVI B,8 LXI H,FCBFN PRFLN1: MOV E,M MOV A,E CPI 20H JZ PRFLN2 CALL PRCHR DCR B INX H JNZ PRFLN1 PRFLN2: MVI E,'.' CALL PRCHR MVI B,3 LXI H,FCBFT ;print file type PRFLN3: MOV E,M CALL PRCHR DCR B INX H JNZ PRFLN3 CALL CRLF RET ; **************************************************************** * Clear the communications channel * **************************************************************** ; CLRCOM: PUSH PSW!PUSH B!PUSH D!PUSH H CALL CLRCM1 POP H!POP D!POP B!POP PSW RET ; **************************************************************** * Output a byte to the remote computer * **************************************************************** ; CMOUT: PUSH PSW!PUSH B!PUSH D!PUSH H CALL CMOUT1 POP H!POP D!POP B!POP PSW RET ; **************************************************************** * Check remote input * **************************************************************** ; CMCHK: PUSH B!PUSH D!PUSH H CALL CMCHK1 POP H!POP D!POP B RET ; **************************************************************** * Input a byte from the remote computer * **************************************************************** ; CMIN: PUSH B!PUSH D!PUSH H CALL CMIN1 POP H!POP D!POP B RET ; **************************************************************** * CRLF * **************************************************************** ; CRLF: MVI E,0DH CALL PRCHR JUSLF: MVI E,0AH CALL PRCHR RET ; **************************************************************** * Login to disk in (A) * **************************************************************** ; LOGIN: PUSH B PUSH D PUSH H call ctuc ; convert drive letter to upper case lxi h,maxdrive ; Point to maximum drive number mov b,a mov a,m sub b jp LOGIN1 ; We have a good drive letter ; login2: LXI D,DSEMSG CALL PRMSG ; Print drive select error STC ; Return error code JMP LOGIN3 ; LOGIN1: mov a,b ; recover drive letter STA DISK ; We have a good drive letter, log into it ANI 1FH ; <-- change 1.68 - mask disk letter A-P SBI 1 MOV E,A MVI C,14 CALL 5 ; set drive # STC CMC ; LOGIN3: POP H POP D POP B RET ; **************************************************************** * Clear and zero input buffer * **************************************************************** ; CLRBUF: LXI H,INBUF+1 MVI C,254 XRA A CLR1: MOV M,A INX H DCR C JNZ CLR1 RET ; **************************************************************** * Print the record number we are on and an asterisk (*) * * for each entry we have had to resend (max is ten stars) * **************************************************************** ; PRRNM: LXI D,RECMSG CALL PRMSG ;Print "Record number" LHLD RECNUM XCHG CALL PRDE ;Print record number CALL SPACE LDA PASS ;Get the pass count MOV B,A MVI A,10 SUB B ;A has number of * to print MOV B,A MVI C,10 ;Number of chars to print PLUP: MOV A,B ORA A JZ PSPC PUSH B MVI E,'*' CALL PRCHR ;Print the star POP B DAST: MOV A,B ORA A JZ DAST1 DCR B DAST1: DCR C JNZ PLUP RET PSPC: PUSH B CALL SPACE POP B JMP DAST ; **************************************************************** * Make cp/m system act like a terminal * **************************************************************** ; terminal: CALL CMCHK ; Character waiting from remote? JNZ GTCHR mvi c,6 mvi e,0ffh ; check for console input call 5 ora a ; Keyboard input? JZ terminal ; ; Have character, check for control-A ; CPI SOH ; check for control-A rz ; exit if so ; ; check for printer toggle (cntl-p) ; cpi dle ; control-p? jnz trmeko lda pflg cma sta pflg jmp terminal ; ; Echo character on screen if half duplex ; trmeko: mov e,a ; save input LDA PLXFLG ORA A jz cred2 ; full duplex - dont echo character MVI C,2 CALL bdos ; echo on screen ; ; output character to printer if half duplex and pflg ; cred2: lda pflg ora a jz cred3 ; jump if printer flag off lda plxflg ora a jz cred3 ; full duplex - don't print character mvi c,5 call bdos ; send character to printer ; ; output character to remote, do auto-line feed ; CRED3: mov a,e ; recover character CALL CMOUT ; Output the character CPI CR JNZ terminal ; Not a CR LDA AFLG ORA A CNZ JUSLF ; output a line feed if auto-lf set JMP terminal ; continue processing ; ; Process remote input ; GTCHR: CALL CMIN ani 7fh ; mask parity mvi c,2 mov e,a call bdos ; ; check for list output ; lda pflg ora a jz gtchr1 ; jump if print flag off mvi c,5 call bdos ; else print character ; ; check for auto-line feed with cr ; gtchr1: mov a,e ; recover character CPI CR JNZ terminal ;Not a CR, don't worry about LF LDA AFLG ORA A JZ terminal ;Terminal mode didn't want auto-line feed GTCHR2: CALL JUSLF ;Print the linefeed JMP terminal ; **************************************************************** * Reset both disks * **************************************************************** ; RESET: MVI C,13 CALL 5 ;Initialize BDOS MVI A,'A' STA DISK ;Current disk is A RET ; **************************************************************** * Convert character in A to upper case * **************************************************************** ; CTUC: CPI 'a' RM CPI 'z'+1 RP SUI ' ' RET ; **************************************************************** * Print free buffer space * **************************************************************** ; free$space: lda columns ; get # columns ora a rz ; return if crt cursor positioning not implemented sui 16 ; get column # to start message mov l,a mvi h,0 call cursxy ; move cursor to start ; mvi e,'<' mvi c,2 call 5 ; ; compute # free pages left ; lhld bpoint ; get buffer pointer xchg lxi h,0 dad sp ; get end of buffer mov a,h sub d ; subtract current page from start page ; ; print # free pages ; call prhex ; print # free pages in hex lxi d,fremsg call prmsg ; ; restore cursor to bottom of screen ; lda rows mov h,a ; get last row mvi l,0 ; first column call cursxy ret ; **************************************************************** * Conversational mode * **************************************************************** ; ; set up screen ; conv$mode: call clear ; clear screen call prbox ; print receive box lda columns sui 3 ; a := #col - 3 rar ; a := (#c - 3) / 2 ani 7fh sui 12 ; get # characters to print before "esc" msg cnvmd1: mvi e,' ' mvi c,2 call bdos dcr a jnz cnvmd1 lxi d,cnvmsg call prmsg ; print center line call prbox ; print transmit box ; ; initialize variables ; mvi a,1 sta rxrow sta rxcol ; start rx row, col at corner sta txcol lda rows sui 5 rar ani 1fh adi 4 sta txrow ; start tx row, col at corner xra a sta rxtxtog ; start with us talking... call poscur ; position cursor ; ; juggle characters until escape is received ; conv$loop: CALL CMCHK ; Character waiting from remote? ora a JNZ convrx mvi c,6 mvi e,0ffh ; check for console input call 5 ORA A ; Keyboard input? JZ conv$loop ; ; process keyboard input ; call cmout ; output to remote first cpi esc jz conv$exit ; if escape, exit ; ; filter unwanted control characters ; cpi ' ' jnc cnvtxok ; ascii printable characters ok cpi bs jz cnvtxok ; backspace ok cpi cr jnz conv$loop ; anything other than cr or backspace illegal cnvtxok: mov e,a lxi h,rxtxtog mov a,m ; get toggle mvi m,0 ; set to us ora a ; was it us in the first place? cnz poscur ; no, position cursor ; mvi c,2 call bdos ; print character ; ; update tx variables ; mov a,e cpi cr ; check for carraige return jz cnvtxcr cpi bs ; check for backspace jz cnvtxbs cpi del ; check for delete jz cnvtxbs ; lda txcol inr a ; bump rx column sta txcol mov b,a lda columns sui 4 cmp b ; have we reached last column yet? jnz conv$loop ; no, keep looping jmp cnvtxcr ; ; tx backspace ; cnvtxbs: lda txcol dcr a ; backup tx column # sta txcol ora a jnz conv$loop ; ok if not backed over beginning of line mvi a,1 sta txcol ; if we did, set cursor to start of line call poscur ; put the cursor there jmp conv$loop ; ; "crlf" from end of line or ; cnvtxcr:mvi a,1 sta txcol ; do "crlf" lda txrow inr a ; bump row sta txrow call poscur ; go to next line ; ; check for end of box ; lda rows sui 5 rar ani 1fh ral adi 4 mov b,a lda txrow cmp b jnz cnvclr ; not end of box, erase this line ; ; put cursor at top of tx box ; mvi a,1 sta txcol lda rows sui 5 rar ani 1fh adi 4 sta txrow ; start tx row, col at corner call poscur ; position cursor ; ; erase line ; cnvclr: lda columns sui 5 ; a has # characters inside box cnvcl1: mvi e,' ' mvi c,2 call bdos ; space out to other side dcr a jnz cnvcl1 call poscur ; go back to beginning of line jmp conv$loop ; loop ; ; process remote input ; convrx: call cmin ani 7fh ; mask parity cpi esc jz conv$exit ; exit if escape ; ; filter unwanted control characters ; cpi ' ' jnc cnvrxok ; ascii printable characters ok cpi bs jz cnvrxok ; backspace ok cpi cr jnz conv$loop ; anything other than cr or backspace illegal cnvrxok: mov e,a lxi h,rxtxtog mov a,m ; get toggle mvi m,0ffh ; set to rx ora a ; was it rx in the first place? cz poscur ; no, position cursor ; mvi c,2 call bdos ; print character ; ; update rx variables ; mov a,e cpi cr ; check for carraige return jz cnvrxcr cpi bs ; check for backspace jz cnvrxbs cpi del ; check for delete jz cnvrxbs ; lda rxcol inr a ; bump rx column sta rxcol mov b,a lda columns sui 4 cmp b ; have we reached last column yet? jnz conv$loop ; no, keep looping jmp cnvrxcr ; ; rx backspace ; cnvrxbs: lda rxcol dcr a ; backup tx column # sta rxcol ora a jnz conv$loop ; ok if not backed over beginning of line mvi a,1 sta rxcol ; if we did, set cursor to start of line call poscur ; put the cursor there jmp conv$loop ; ; "crlf" from end of line or ; cnvrxcr:mvi a,1 sta rxcol ; do "crlf" lda rxrow inr a ; bump row sta rxrow call poscur ; go to next line ; ; check for end of box ; lda rows ; get # rows sui 5 ; subtract 5 rar ; divide by two ani 1fh inr a ; add one (a has last row of first box) mov b,a lda rxrow cmp b jnz cnvclr ; not end of box, erase this line ; ; put cursor at top of rx box ; mvi a,1 sta rxcol sta rxrow ; start rx row, col at corner call poscur ; position cursor jmp cnvclr ; then erase top line ; ; exit from conversational mode ; conv$exit: mvi l,0 ; set column # lda rows dcr a mov h,a ; set row # call cursxy call crlf ; crlf past boxes ret ; ; position cursor at txrow,txcol or rxrow,rxcol depending on rxtxtog ; poscur: push d ; save character lhld txcol ; get tx row, col lda rxtxtog ora a ; check flag jz poscur1 ; use tx if 0 lhld rxcol ; else use rx poscur1:call cursxy ; position cursor pop d ret ; ; print a box for the user's screen size ; prbox: call prline ; print top line lda rows sui 5 rar ; a := (rows-5)/2 ani 31 ; can't be more than 32 lines...mask bits mov b,a prbox1: mvi e,'|' mvi c,2 call bdos ; print side character lda columns sui 5 ; a has # characters inside box prbox2: mvi e,' ' mvi c,2 call bdos ; space out to other side dcr a jnz prbox2 push b lxi d,boxmsg call prmsg ; print side character (+crlf) pop b dcr b ; loop thru box size jnz prbox1 ; fall into prline routine ; ; print bottom line ; prline: mvi e,'+' mvi c,2 call bdos ; print corner character lda columns sui 5 ; a has # characters inside box prlin1: mvi e,'-' mvi c,2 call bdos ; print side characters dcr a jnz prlin1 lxi d,box1msg call prmsg ; print corner character (+crlf) ret ; **************************************************************** * call bdos, saving registers * **************************************************************** ; bdos: PUSH PSW!PUSH B!PUSH D!PUSH H call 5 POP H!POP D!POP B!POP PSW ret ; **************************************************************** * * * LINK90 messages * * * **************************************************************** ; CPMSG: DB ':LINK>$' boxmsg: db '|',cr,lf,'$' box1msg:db '+',cr,lf,'$' cnvmsg: db ' ** Hit to exit **',cr,lf,'$' NAKMSG: DB 'Negative acknowledgement of command from remote',CR,LF,'$' NOMSG: DB 'No response from remote',CR,LF DB 'Returning to INITIAL state',CR,LF,'$' CTMSG: DB 'Control transferred to remote',CR,LF,'$' RERMSG: DB 'Remote error, transfer aborted',CR,LF,'$' FNMMSG: DB 'File name: $' FIEMSG: DB 'File access error - Transfer aborted',CR,LF,'$' promsg: db cr,lf,'Choose protocol (1 - XON/XOFF, 2 - CPMUG) :$' TIEMSG: DB CR,LF,'',CR,LF,'$' TIMSG: DB CR,LF,'',CR,LF db '',cr,lf db '',cr,lf,'$' SABMSG: DB CR,LF,'User break in file transfer',CR,LF DB 'Hit the escape key to abort file transfer',CR,LF,'$' ABMSG: DB CR,LF,'',CR,LF DB '',CR,LF db '',cr,lf,'$' TABMSG: DB CR,LF,'',CR,LF DB '',CR,LF DB '',CR,LF,'$' TBDMSG: DB CR,LF,'',CR,LF,'$' OVMSG: DB CR,LF,'',CR,LF,'$' TCNTMG: DB CR,LF,'',CR,LF,'$' TIDMSG: DB CR,LF,'',CR,LF,'$' TREMSG: DB '',CR,LF,'$' FEMSG: DB 'File already exists',CR,LF DB 'Delete old file (D) or create new file (N) ? $' CRCMSG: DB 'LINK90 connection unstable',CR,LF SIEMSG: DB 'File transfer aborted',0AH,0DH,'$' EXTMSG: DB 'Error on extending file',CR,LF,'$' EDDMSG: DB 'Disk full',CR,LF,'$' FLERMG: DB 'File error',CR,LF,'$' UFOMSG: DB 'Only unambiguous file names allowed',CR,LF,'$' DSEMSG: DB CR,LF,'Drive select error',CR,LF,'$' PMTMSG: DB CR,LF,'Enter message terminated by "ESC":',CR,LF,LF,'$' DIMSG: DB 'Sending remote directory of logged-on disk',CR,LF,'$' DIRMSG: DB CR,LF,'Requesting remote directory',CR,LF,'$' MWFCMG: DB 'Previous message left has been erased',CR,LF,'$' NOTMSG: DB 'Remote link not established',CR,LF,'$' SRFMSG: DB 'Sending remote file:$' MSWTMS: DB 'Messages waiting',CR,LF,'$' RRMMSG: DB 'Remote requesting message',CR,LF,'$' NMMSG: DB 'No messages waiting',CR,LF RLEMSG: DB 'Remote link established',CR,LF,'$' ERMSG: DB 'What?',CR,LF,'$' BFMSG: DB CR,LF,'',CR,LF,'$' RDDMSG: DB CR,LF,'Remote directory - drive ',DC2 IMMSG: DB 'Incoming message',CR,LF,'$' ERLMSG: DB 'End remote link',CR,LF,'$' FOEMSG: DB 'No file present - $' RSDMSG: DB 'Remote selecting drive $' DFMSG: DB 'Directory full',CR,LF,'$' NTLMSG: DB 'File name error',CR,LF,'$' TDCMSG: DB 'Control not transferred',CR,LF,'$' PGMMSG: DB 'Incoming program - $' FNEMSG: DB 'New file',CR,LF,'$' SNGMSG: DB 'Sending $' DMTMSG: DB 'No file',CR,LF,'$' EOFMSG: DB 'File transfer successful',CR,LF,'$' RLBMSG: DB CR,LF,'Remote link broken',CR,LF,'$' WATMSG: DB 'Entering wait state',CR,LF,'$' TEMSG: DB CR,LF,'Entering TERMINAL mode',CR,LF DB 'Hit "Control-A" for LINK90 attention',CR,LF,LF,'$' ONMSG: DB '(on)$' OFMSG: DB '(off)$' HAFMSG: DB '(Half)$' FULMSG: DB '(Full)$' TAMSG: DB CR,LF,LF,'A - Auto-line feed toggle $' TCMSG: DB CR,LF,'D - Duplex toggle $' fremsg: db ' pages free>$' ; ; terminal mode menu ; TBMSG: DB CR,LF,'B - Reinitialize serial port',CR,LF DB 'S - Output a file to remote mainframe',CR,LF DB 'R - Reset disks',CR,LF DB 'I - Input a file from remote mainframe',CR,LF DB 'E - Output the "control-A" character to the remote mainframe',CR,LF DB 'Hit escape to return to the INITIAL state',CR,LF DB 'Hit any other key to continue',CR,LF,LF DB 'Command? $' ; ; initial state help menu ; INHEMS: DB CR,LF,'You are in the INITIAL state -',CR,LF DB 'Commands available in this state are:',CR,LF DB ' WAIT - Enter the wait state',CR,LF DB ' MESS - Leave message and enter wait state',CR,LF DB ' INQ - Establish remote link',CR,LF DB ' DIR - Print directory of logged-on disk',CR,LF DB ' HELP - Print this menu',CR,LF DB ' TERM - Enter Terminal mode',CR,LF db ' CO - Enter Conversational mode',cr,lf db 'RESET - Reset disks',cr,lf DB ' d: - Log onto disk ',CR,LF DB '$' ; ; command state help menu ; COHEMS: DB CR,LF,'You are in the COMMAND state -',CR,LF DB 'Commands available in this state are:',CR,LF DB ' INIT - Enter the initial state',CR,LF DB ' MESS - Send message terminated by "ESC" to remote',CR,LF DB ' TR - Transfer control to remote',CR,LF db ' CO - Enter Conversational mode',cr,lf DB ' SEND - Send program to remote',CR,LF DB ' DIR - Print directory of current disk',CR,LF DB ' RDIR - Print remote directory',CR,LF DB ' BYE - End remote link',CR,LF DB ' ? - Get message left by remote',CR,LF DB ' GET - Get program from remote',CR,LF DB 'RESET - Reset disks',CR,LF DB ' d: - Log onto disk ',CR,LF DB ' Rd: - Log onto remote disk ',CR,LF DB '$' RECMSG: DB 0DH,'Record number $' ; **************************************************************** * INITIAL state command table * **************************************************************** ; INTBL: DB 3,'INQ' DW INQ ; Inquire remote DB 4,'WAIT' DW wt ; Enter wait state DB 4,'MESS' DW LVMSG ; Leave message and enter wait state DB 4,'HELP' DW INHEP ; Print menu DB 4,'TERM' DW TERM ; Enter TERMINAL mode DB 3,'DIR' DW IDIR ; Print directory db 5,'RESET' dw ireset ; reset disks db 2,'CO' dw iconv ; conversational mode DB 0 ; End of table DW STDISK ; Variable disk select command "d:" ; **************************************************************** * COMMAND state command table * **************************************************************** ; CMTBL: DB 2,'TR' DW TRCON ;Transfer control to remote db 2,'CO' dw cogo ; Enter Conversational mode DB 4,'MESS' DW SNDMSG ;Send message DB 4,'SEND' DW CSNDPGM;Send program to remote DB 1,'?' DW INWMSG ;List waiting message DB 3,'BYE' DW ENLNK1 ;End remote link DB 4,'RDIR' DW GTRMDR ;List remote directory DB 3,'GET' DW GTRMPM ;Get program from remote DB 4,'INIT' DW PRMPT ;Enter initial state DB 4,'HELP' DW COHEP ;Print menu DB 3,'DIR' DW CDIR ;Print directory DB 5,'RESET' DW CRESET ;Reset disks DB 0 ;End of table DW CNDISK ;Variable disk select command "d:" ; ; STORAGE AREAS ; XERROR: DB NAK ;Error flag for file transmit routine WILD: DB 0 ;Wildcard; =ff when wildcard file name entered NFILES: DB 0 ;Number of files indicated by wildcard VECTOR: DB 0 ;Vector for PRCHR ; If 0, output on screen ; If not, output to screen and remote ZGONFLG:DB 0 ;Leading zero blank flag for PRDE pflg: db 0 ; Printer on flag UNITS: DS 5 ;Storage areas for PRDE RECNUM: DW 0 ;Number of records LINK90 has sent cksum: db 0 ; checksum storage DISK: DB 'A' ;Current disk PASS: DB 10 ;Number of passes before file xfer aborts BPOINT: ds 2 ;Pointer to buffer for TERM mode rxtxtog:db 0 ; 0 if cursor is in tx window, ff if in rx window rxcol: db 0 ; row, col for conversational mode windows rxrow: db 0 ; txcol: db 0 ; txrow: db 0 ; NUM: DS 1 ;Number crunching area for DIR routine NUM1: DS 1 NUM2: DS 1 ; ; INPUT BUFFERS ; INBUF: DB 255 ;255 character input buffer DB 0 ;Current length of input buffer DS 255 ;Space for input buffer MFLG: DB ACK ;Message waiting flag ; **************************************************************** * User provided system dependent routines * **************************************************************** ; ORG 2400H ; Start of LINKSBS INIT: EQU $ ; System initialization (baud rate, etc.) CLRCM1: EQU $+3 ; Clear channel CMCHK1: EQU $+6 ; Channel check CMIN1: EQU $+9 ; Channel input CMOUT1: EQU $+12 ; Channel output clear: equ $+15 ; Clear CRT cursxy: equ $+18 ; Cursor positioning ; ; After the above jumps follow these designated positions ; SPEED: EQU $+21 ; CPU speed (2 or 4) maxdrive:equ $+22 ; Maximum drive letter aflg: equ $+23 ; auto-line feed toggle (0 = off, 0ffh = on) plxflg: equ $+24 ; duplex flag (0 = full dux, 0ffh = half dux) delay: equ $+25 ; delay in ascii file send mode when cr encountered rows: equ $+26 ; number of crt rows (0 if no crt) columns:equ $+27 ; number of crt columns (0 if no crt) buffer: equ $+28 ; space for incoming directory and programs ; END