TITLE 'MEX CONCORD DATA SYSTEMS 224 AD OVERLAY V1.0' ; ; (DELETE ABOVE TITLE LINE IF ASSEMBLING WITH ASM) ; ; MXM-CD10.ASM Current version 27 Nov 84 ; ; Concord Data Systems 224 AD modem overlay for MEX v1.x ; Written 27 Nov 84 by Dave Mabry ; Adapted using information and excerps from Ron Fowler's ; overlay for the PMMI modem. ; ; Please leave any bug reports or requests on TCBBS in Dearborn, ; Michigan (313-846-6127) or Royal Oak RCPM (313-759-6569) ; or call me at 313-956-5703 (voice). mabry ; ; Updates: ; MXM-CD10.ASM ; 11/27/84...Original release. ; ; Note that unlike the Hayes modem, the Concord does not ; have an ascii command that will cause a disconnect. Therefore ; for that feature of MEX to work properly, your normal overlay ; for your machine must be able to toggle DTR in the DISCV routine. ; ; This overlay will work with any modem overlay that terminates ; prior to 0900H ; VERS EQU 10 ; Version number FALSE EQU 0 TRUE EQU 0FFH ; ; ; SYSTEM CONSTANTS ; DIALV EQU 0162H ;LOCATION OF DIAL VECTOR IN OVERLAY DISCV EQU 0165H ;LOCATION OF DISCONNECT VECTOR IN OVERLAY DIALOC EQU 0900H ;DIALING CODE GOES HERE SMINIT EQU 0D55H ; Vector to modem initialization routine ; ; ; MEX service processor stuff ... MEX supports an overlay service ; processor, located at 0D00H (and maintained at this address from ; version to version). If your overlay needs to call BDOS for any ; reason, it should call MEX instead; function calls below about ; 240 are simply passed on to the BDOS (console and list I/O calls ; are specially handled to allow modem port queueing, which is why ; you should call MEX instead of BDOS). MEX uses function calls ; above about 244 for special overlay services (described below). ; ; Some sophisticated overlays may need to do file I/O; if so, use ; the PARSFN MEX call with a pointer to the FCB in DE to parse out ; the name. This FCB should support a spare byte immediately pre- ; ceeding the actual FCB (to contain user # information). If you've ; used MEX-10 for input instead of BDOS-10 (or you're parsing part ; of a SET command line that's already been input), then MEX will ; take care of DU specs, and set up the FCB accordingly. There- ; after all file I/O calls done through the MEX service processor ; will handle drive and user with no further effort necessary on ; the part of the programmer. ; MEX EQU 0D00H ;address of the service processor INMDM EQU 255 ;get char from port to A, CY=no more in 100 ms TIMER EQU 254 ;delay 100ms * reg B TMDINP EQU 253 ;B=# secs to wait for char, cy=no char CHEKCC EQU 252 ;check for ^C from KBD, Z=present SNDRDY EQU 251 ;test for modem-send ready RCVRDY EQU 250 ;test for modem-receive ready SNDCHR EQU 249 ;send a character to the modem (after sndrdy) RCVCHR EQU 248 ;recv a char from modem (after rcvrdy) LOOKUP EQU 247 ;table search: see CMDTBL comments for info PARSFN EQU 246 ;parse filename from input stream BDPARS EQU 245 ;parse baud-rate from input stream SBLANK EQU 244 ;scan input stream to next non-blank EVALA EQU 243 ;evaluate numeric from input stream LKAHED EQU 242 ;get nxt char w/o removing from input GNC EQU 241 ;get char from input, cy=1 if none ILP EQU 240 ;inline print DECOUT EQU 239 ;decimal output PRBAUD EQU 238 ;print baud rate PRNTBL EQU 237 ;print MEX format table (HL) PRIN EQU 236 ;print MEX ID string on console ; ; CONOUT EQU 2 ;simulated BDOS function 2: console char out PRINT EQU 9 ;simulated BDOS function 9: print string INBUF EQU 10 ;input buffer, same structure as BDOS 10 ; ; CR EQU 0DH LF EQU 0AH ESC EQU 1BH ; ; MSPEED EQU 107H ; Modem speed byte B1200 EQU 5 ; Byte for 1200 baud B2400 EQU 6 ; Byte for 2400 baud NEWBDV EQU 16EH ; Entry point for new baud routine ; ; ORG SMINIT ; Overlay the initialization vector DW INIT ; Just the address ORG DIALV ;OVERLAY THE DIALING VECTOR JMP DIAL ; ; ; This is the DIAL routine called by MEX to dial a digit. The digit ; to be dialed is passed in the A register. Note that two special ; codes must be intercepted as non-digits: 254 (start dial sequence) ; and 255 (end-dial sequence). Mex will always call DIAL with 254 ; in the accumulator prior to dialing a number. Mex will also call ; dial with 255 in A as an indication that dialing is complete. Thus, ; the overlay may use these values to "block" the number, holding it ; in a buffer until it is completely assembled (in fact, that's the ; scheme employed here). ; ; After the 254-start-dial sequence, MEX will call the overlay with ; digits, one-at-a-time. MEX will make no assumptions about the dig- ; its, and will send each to the DIAL routine un-inspected (some modems, ; like the Smartmodem, allow special non-numeric characters in the ; phone number, and MEX may make no assumptions about these). ; ; After receiving the end-dial sequence (255) the overlay must take ; whatever end-of-dial actions are necessary *including* waiting for ; carrier at the distant end. The overlay should monitor the keyboard ; during this wait (using the MEX keystat service call), and return ; an exit code to MEX in the A register, as follows: ; ; 0 - Carrier detected, connection established ; 1 - Far end busy (only for modems that can detect this condition) ; 2 - No answer (or timed out waiting for modem response) ; 3 - Keyboard abort (^C only: all others should be ignored) ; 4 - Error reported by modem ; 5 - No ring reported by modem ; 6 - No dial tone reported by modem ; ; ; ; The overlay should not loop forever in the carrier-wait routine, but ; instead use either the overlay timer vector, or the INMDMV (timed 100 ; ms character wait) service call routine. ; ; The DIAL routine is free to use any of the registers, but must return ; the above code after an end-dial sequence ; ORG DIALOC INIT: ; Just to print a signon message MVI C,PRINT LXI D,SIGNON CALL MEX RET ; DIAL: LHLD DIALPT ;FETCH POINTER CPI 254 ;START DIAL? JZ STDIAL ;JUMP IF SO CPI 255 ;END DIAL? JZ ENDIAL ;JUMP IF SO ; ; Not start or end sequence, must be a digit to be sent to the modem ; MOV M,A ;PUT CHAR IN BUFFER INX H ;ADVANCE POINTER SHLD DIALPT ;STUFF PNTR RET ;ALL DONE ; ; Here on a start-dial sequence ; STDIAL: LXI H,DIALBF ;SET UP BUFFER POINTER SHLD DIALPT RET ; ; Here on an end-dial sequence ; ENDIAL: MVI M,0 ; Terminate phone number string XRA A ; Initialize baud flag STA BDFLAG ; ; Since the modem may not be at the same rate as the hardware and ; we really have no way of knowing, we try the baud rate the ; hardware is at first, and if the modem fails to respond in ; about a half a second then assume it is at the other baud rate. ; So try that baud rate. If the modem still fails to respond, then ; return to mex with a MODEM ERROR code. ; CLEAR: MVI C,INMDM ; Clear out the data buffer in USART CALL MEX JNC CLEAR ; Get them all LXI H,WAKEUP ; Get modem's attention CALL CDSEND MVI C,5 ; ~.5 second wait CALL CATCHR ; Catch response from modem ORA A ; Check response JZ AROUND ; If there was a response, then modem ; and system were at same baud rate CPI 3 ; User typed ^C ? RZ ; If yes, abort to mex LXI H,BDFLAG ; Check to see if we have tried this MOV A,M ; before ORA A ; True means yes JNZ ERRET MVI M,TRUE ; If false, then make it true now LDA MSPEED ; Get original baud rate STA MSSAVE ; Save it temporarily CPI B1200 ; Was first try at 1200 baud ? LXI H,BDCMD+1 ; Point to second char of baud set command JZ CLR05 ; Yes, go try 2400 CPI B2400 ; Was first try at 2400 baud ? JNZ ERRET ; If not one of the two, then error MVI A,B1200 MVI M,'H' ; If changing baud to 1200, need to change JMP CLR10 ; it back to 2400 later CLR05: MVI A,B2400 ; Now set to 2400 baud MVI M,'L' ; Later must set back to 1200 CLR10: CALL NEWBDV ; ***This must be in hardware overlay*** JMP CLEAR AROUND: LDA BDFLAG ORA A ; Need to reset baud rate ? JZ CLR15 ; No, just continue LXI H,BDCMD ; Send modem speed command CALL CDSEND ; Now must wait for last character to be fully shifted out of USART MVI C,TIMER MVI B,2 ; 200ms should be plenty of time CALL MEX LDA MSSAVE ; Get original baud rate CALL NEWBDV ; Set hardware to old baud rate MVI C,INMDM ; Clear out any characters we might CALL MEX ; have missed while changing baud rates JNC $-5 ; Loop til all bytes removed from USART CLR15: LXI H,DCMD ; Send "dial" command and # to modem CALL CDSEND CALL SENDCR ; Send to terminate phone number ; ; Here starts the main loop waiting for messages from the modem ; and interpreting them. Some are just passed to the console ; and others trigger a specific action. ; DLOOP: MVI C,255 ; Long wait here CALL CATCHR ; Catch response from modem ORA A ; Zero return code ? JNZ IDLE ; No, disconnect and return error code MOV A,B ; Get character count from CATCHR ORA A ; Hope it isn't zero JZ DLOOP ; If it is, then ignore this response LXI H,RESPBF ; Point to string from modem LXI D,RSPTBL ; Point to valid response table CALL COMPR ; Find out which response we got MOV A,B ; Get return value CPI 255 ; No response match JNZ DIAL05 ERRET: MVI A,4 ; Error return to MEX JMP IDLE ; Disconnect and return error code DIAL05: CPI 3 ; OK ? JZ DLOOP ; Ignore this one ORA A ; Need to know if 0 ==> connected RZ ; Only this code can return w/o disconnecting CPI 7 ; If <= 6 then let it return to MEX JC IDLE JNZ DIAL10 ; If not 7 (7 = voice) MVI A,1 JMP IDLE ; Return BUSY code to MEX DIAL10: CPI 8 ; Call fail ? JZ ERRET ; Return modem error code CPI 13 ; Between 9 and 12 (inclusive) ? JNC DLOOP ; Ignore 13 and above (13 = dial tone) ; At this point the match code from COMPR is between 9 and 12 inclusive. ; Use this number as an offset into a table of messages, print the ; appropriate message and continue to loop. SUI 9 ; Now between 0 and 3 RLC ; *2 RLC ; *4 RLC ; *8 MVI B,0 MOV C,A ; BC contain offset LXI H,MSGS ; Point to messages table DAD B ; Index into it XCHG ; Pointer to message into DE MVI C,9 ; Let mex to string print CALL MEX LXI D,COMSP ; Print ", " MVI C,9 CALL MEX JMP DLOOP ; And continue to loop ; IDLE: ; ; Forces modem into idle state by sending "idle" command and ; then toggling DTR using the user's DISCV entry. ; Preserves any return code that may be in register A. ; PUSH PSW ; Preserve any return code LXI H,IDLCMD ; Send abort character to modem CALL CDSEND CALL DISCV ; Make sure modem disconnects POP PSW ; Retrieve return code RET SENDCR: ; ; Sends a carriage return character to the modem. ; LXI H,CRMSG ; Send to modem JMP CDSEND ; Table of valid responses from modem RSPTBL: DB 'IN',0 ; Initiating DB 'BSY',0 ; Busy DB 'NOA',0 ; No answer DB 'OK',0 ; Signon DB 'NOT',0 ; No answer tone DB 'NOR',0 ; No ring DB 'NDT',0 ; No dial tone DB 'VDT',0 ; Voice detected DB 'CFL',0 ; Call failed DB 'DG',0 ; Dialing DB 'RG',0 ; Ringing DB 'AT',0 ; Answer tone DB 'AR',0 ; Answer DB 'DT',0 ; Dial tone DB 0 ; Table terminator ; Table of messages for some of the entries in the above table. ; Each entry must be exactly eight characters including the "$". MSGS: DB 'dialing$' DB 'ringing$' DB 'anstone$' DB 'answer$ ' COMSP: DB ', $' ; To delimit messages COMPR: ; Compares a string of characters in memory to a table of ; strings. Each entry of the table must be terminated by ; a zero byte, and the table must be terminated by another ; zero byte. ; Inputs: HL points to the string to look for ; DE points to the table ; Output: B contains the number of the table entry that ; matches the string, and a 255 if no entry matched. ; MVI B,0 ; Init index CMPR02: PUSH H ; Save pointer to string in question CMPR05: LDAX D ; Fetch char from table ORA A ; Is it end of table entry ? JNZ CMPR10 ; Not end of entry POP H RET ; Return with match code in B CMPR10: CMP M ; Same as char in string ? JNZ CMPR15 ; Jump if not this entry in table INX H ; Else, point to next char in string INX D ; and next char in table entry JMP CMPR05 ; Check next char CMPR15: INR B ; Increment index CMPR20: INX D ; Next char in table LDAX D ; Get next char from table ORA A ; Is it last char in that entry ? JNZ CMPR20 ; No, keep looking INX D ; Yes, then point to first char of next one LDAX D ; Now check for end of table ORA A ; Zero delimits table POP H ; Point to original string again JNZ CMPR02 ; Go check next table entry MVI B,255 ; No match return code RET CATCHR: ; ; Catch response from modem. ; Input: C delay allowed before timeout (multiples of 100ms) ; Output: Buffer (RESPBF) contains the string received from modem with ; any control characters filtered out. ; C count of characters received before ; A error code: 0 ==> normal return ; 2 ==> time out occurred ; 3 ==> user typed ^C ; LXI H,RESPBF PUSH H MVI B,BUFLEN ; Length of character buffer CRL10: MVI M,0 ; Fill buffer with zeros INX H DCR B JNZ CRL10 POP H ; Get original pointer MVI B,0 ; Character counter CRLOOP: MOV D,C ; Initialize count down timer CRL05: PUSH B ; Save character counter PUSH D ; Save time out count down PUSH H ; Save buffer pointer MVI C,CHEKCC ; See if user typed ^C CALL MEX JZ UABORT ; If yes, jump to user abort code MVI C,INMDM ; Else, continue CALL MEX ; Get char in 1ms if available POP H ; Restore working registers POP D POP B JC NOCHAR ; If no char in 1ms, handle it ANI 7FH ; Mask any parity bit CPI CR ; End of line from modem ? JZ EOL ; If then end of line MOV M,A ; Here we have a valid character INX H ; so save it and bump pointer INR B ; Increment character counter also JMP CRLOOP ; Go get the next one ; ; Handle no character from modem in 1ms ; NOCHAR: DCR D ; Decrement time out counter JNZ CRL05 ; If not to zero, wait some more MOV C,B ; Else, return with what we have now MVI A,2 ; Time-out error code RET ; ; End of line from modem ; EOL: MOV C,B ; Character count XRA A ; Return zero RET ; ; User abort from ^C ; UABORT: CALL IDLE ; Force disconnect POP H ; Since these are still on the stack POP D POP B MOV C,B ; Count of characters up to now MVI A,3 ; User abort code RET ; ; Sends a string of characters pointed to by HL terminated by zero ; CDSEND: MVI C,SNDRDY ; Get modem send status CALL MEX JNZ CDSEND ; Wait for modem ready MOV A,M ; Get character INX H ; Point to next character ORA A ; Is this the terminator ? RZ ; If yes, done MOV B,A ; Pass character in B MVI C,SNDCHR ; Let MEX send to modem CALL MEX JMP CDSEND ; ; DATA AREA ; SIGNON: DB '(Modem overlay for Concord Data Systems 224 AutoDial V' DB VERS/10 + '0', '.', (VERS MOD 10) + '0' DB ')$' ; Wakeup string to put modem into "interactive DTE" mode WAKEUP: DB ESC,CR,CR,'B',CR,0 ; String to wake up modem IDLCMD: DB ESC,0 ; Idle command BDCMD: DB 'SL' ; Set baud command CRMSG: DB CR,0 ; Note, this is end of LSCMD also DCMD: DB 'DM' ; Dial command to modem DIALBF: DS 30 ; Phone number buffer BUFLEN EQU 8 ; Length of response buffer RESPBF: DS BUFLEN ; Response from modem DIALPT: DS 2 ;DIAL POSITION POINTER MSSAVE: DS 1 ; Save location for original baud rate BDFLAG: DS 1 ; Flag for baud rate changed ; END