Programming samples by Robert Elton Maas Assembly language (Intel 8080) These are excerpts from the file IO1.M80 (41k bytes total): ;; Version for the CROSS assembler that runs on PDP-10 ; -C- Copyright 1982 by Robert Elton Maas, all rights reserved. ; New (1980 Nov 02) I/O software for REM's Altair 8800A system. ; It includes software for both the 88-2SIO board and the MM-103 "modem" ; board, plus general I/O that is mapped to the selected device&channel. ; General description of software is on the rest of this page, ; system constants that may need changing are at the start of next page. ; Each major module (88-2SIO, MM-103, HIGH-LEVEL I/O, ETC.) has a single entry ; point, making it unnecessary to have a linking loader or OBLIST nor to ; keep track of the entry address for every routine that is added to ; modules and called from elsewhere. Before calling a module, a register ; is loaded with the index of the subroutine to be called, and inside the ; module this register is used to locate the routine in a dispatch table. ; Note that I am NOT implementing the IOCB method that Gragg&Pearce use ; and which CP/M allegedly uses. Details of register usage for ; intermodule calls will be given below. ; Routines that are very low-level and device-specific are defined in ; the individual device modules (88-2SIO and MM-103). As much as possible, ; all code that is the same for all devices is given just once in the ; device-independent high-level I/O module. Entry points are also given ; in the high-level I/O module for low-level functions. The high-level ; module checks the channel number to see which device module needs to ; be called, and passes control to it. Thus in general things work like this: ; High-level module called: ; Low-level routine selected -- dispatch to appropriate module; ; High-level routine selected -- call low-level routines in a uniform way ; independent of device, which in turn dispatch as above. ; Device-specific module called: ; Low-level routine selected -- perform device-dependent operations; ; High-level routine selected -- make sure channel number will specify ; a legal channel within this particular device, then pass control ; to high-level module which will indirectly call the low-level ; routines in this module as explained above; ; When calling a routine, register C is loaded with the command number ; as listed below, and if additional values are needed they are loaded ; into register B (single-byte) or HL (16-bit number). Then the module ; dispatch routine (SIODSP, PMIDSP or HIODSP) is called. Upon return, ; register C contains the error byte (0 if no error occurred) and B ; contains the result if any. No registers are changed except those ; used for passing arguments to the routine, those used to return the ; error number and/or main returned value, A which is used as a temporary ; register throughout the code, and PSW that on return is set to agree ; with the main result for boolean functions (0=false, nonzero=true) and ; the error byte for all other functions (0=ok, nonzero=error). ;System-dependent constants which may need to be changed... BKPTRS=3 ;RST 3 for calling a breakpoint IOOPRS=6 ;RST 6 for interfacing to this I/O package SIOJMP=$0100 ;Entry-jump for 88-2SIO module PMIJMP=$0103 ;Pun, get it? Entry-jump for MM-103 module HIOJMP=$0106 ;Entry-jump for high-level I/O module PRGORG=$1100 ;Start of main body of read-only code RAMORG=$5FE0 ;Start of variables that must be in RAM SIO00=$10 ;DIP-selected 8080 I/O address for 88-2SIO PMI00=$C0 ;DIP-selected 8080 I/O address for MM-103 NSIOCH=2 ;Number of 88-2SIO channels NPIOCH=1 ;Number of PMMI channels ;Derived constants BKPTOP=BKPTRS*8+$C7 ;Opcode for RST to enter debugger IOOP =IOOPRS*8+$C7 ;Opcode for RST to call IO module ;Command codes used by this program (warning, be sure this table agrees ; with sequence of entries in dispatch tables). ;To call one of these functions, (1) load register C with the code ; given in the following table, and load other registers with arguments ; specific to the particular function (shown on the left of the -->), ; (2) execute an IOOP, (3) test or fetch results from registers specific ; to the particular functin (shown on the right of the -->): ; Routines that deal with initialization or mapping... NINIT =$00 ;no registers used or modified except A,PSW NSELCH=$01 ;B=channel# to be selected NGETCH=$0F ;--> B=channel# currently selected ; Device-specific routines that make sense for most UART/ACIAs... NISTRR=$02 ;ISTRR --> B==True(nz)/False(zero) NRCVBT=$03 ;RCVBT --> B=data, C==error(nz)/ok(zero) NISTTR=$04 ;ISTTR --> B==True(nz)/False(zero) NSNDBT=$05 ;SNDBT(B=data) --> C==error(nz)/ok(zero) NISTOR=$06 ;ISTOR --> B==True(nz)/False(zero) NISTFE=$07 ;ISTFE --> B==True(nz)/False(zero) ; The following functions are device-independent except insofar as they ; call the above device-dependent routines: NRCWBT=$10 ;RCWBT --> B=data, C==error(nz)/ok(zero) NSDWBT=$11 ;SDWBT(B=data) --> C==error(nz)/ok(zero) NRCEBT=$12 ;RCEBT --> B=data, C==error(nz)/ok(zero) NR7WBT=$13 ;R7WBT --> B=data, C==error(nz)/ok(zero) NR7EBT=$14 ;R7EBT --> B=data, C==error(nz)/ok(zero) NS1WBT=$15 ;S1WBT(B=data) --> C==error(nz)/ok(zero) NS1WST=$16 ;S1WST(HL=ptr-to-str-of-bytes) --> C==error(nz)/ok(zero) ; (HL is preserved) NWAIEV=$17 ;WAIEV --> B==event# ;Event numbers returned by WAIEV: NRING=1 ;PMMI detected ring (incoming cal) NTTYI=2 ;Key pressed on local keyboard (SIO port 0) NMODI=3 ;Data arrived on second SIO port, probably an acoustic coupler NCLKI=4 ;Clock changed state in PMMI ;Error codes used by this program: NERRCA=1 ;Lost carrier NERRTO=2 ;Timeout, no data within 10 seconds NERROR=3 ;Over-run (lost 1+ bytes of incoming data) NERRFE=4 ;Framing-error (garbaged byte) ;Routine to fetch status of selected 88-2SIO channel and return in A. ;Various bits can then be tested by the calling routine. SIOSTA: LDA IOCHAN ORA A JZ SIOST0 DCR A JZ SIOST1 BKPTOP ;Illegal channel number, crash to HEXDDT SIOST0: IN SIOCT0 JMP SIOST9 SIOST1: IN SIOCT1 SIOST9: RET ;Return value from selected 88-2SIO control port ;Toplevel entry point for 88-2SIO hardware-dependent routines SIODSP: LDA IOCHAN ;Coerce channel# to be in range... CPI SIOC0 JM SIODS6 CPI SIOCZ JM SIODS7 SIODS6: MVI A,SIOC0 ;Out of range, change to in range STA IOCHAN SIODS7: PUSH H PUSH B ;Save old C containing function index LXI H,SIOTAB CALL DSPINX POP B JNZ SIOCAL ;Success, now patch address and jump BKPTOP ;Failure, out of range of legal codes SIOCAL: CALL HLPAT ;Patch HL into JMP address POP H ;Restore original HL in case needed by routine JMP XCTXCT ;JCALL whatever routine it was ; HL=ptr to start of telephone number --> no return, assumes it worked DILTN: PUSH H PUSH B DILTNL: MOV A,M CPI '- JZ DIALPAUSE CPI '0+10 JNC DILTNZ CPI '0 JC DILTNZ JNZ DILTN1 ADI 10 ;In U.S.A. "0" is ten pulses DILTN1: SUI '0 ;Others are exactly their numerical value MOV D,A ;Digit (1 to 10.) to dial LDA PPSCODE ;Temporarily set timer for dial pulse rate OUT RPORT DILTN4: CALL TS ;Result in B JZ DILTN4 ;Ok, idle (1) before down pulse DILTN5: CALL TS JNZ DILTN5 ;Wait for start of down pulse LDA CURRT ANI $FF-ORIGBIT OUT TPORT DILTN6: CALL TS JZ DILTN6 ;Wait for end of down pulse, return to 1 LDA CURRT ORI ORIGBIT OUT TPORT DCR D ;One pulse done, see if any remaining JNZ DILTN5 ;If more to do here DIALPAUSE: ;That digit done, now pause before next digit or returning LDA PAUSEN MOV B,A CALL QDELAY INX H JMP DILTNL ;Go back to fetch next digit or end-of-TN flag DILTNZ: POP B POP H JMP C0RET ;Always success (no error) return These are excerpts from the file BOOT1.M80 (11k bytes total): ;; Software for 8080A computer: boot ;; Copyright (C) 1980 by Robert Elton Maas ;Addresses of I/O devices CTL0 =$10 DATA0 =$11 ;Device-dependent data constants ACIARS=$17 ;Reset ACIAMD=$15 ;8 transparant data bits, one stop bit ;; -- BOOT 1 -- ;This program is small enough that it can be entered via the Altair ; front-panel switches in just a few minutes. It initializes the ; 88-2SIO board port 0 to talk at 2400 baud to a local terminal. ; It then accepts pairs of characters from the keyboard, echoes each ; one, and adds three times the first character plus the second character ; to get an 8-bit value that is deposited ; in the next consecutive memory location. Before typing data ; into this program, each byte must be converted from hexadecimal (or octal) ; notation into the 3x+y notation using the reference ; table I've compiled. ;Operation of program: Be sure terminal is connected to port 0, turned on, ; and in fullduplex mode; Stop&reset, set address at 0006, press EXAMINE, ; set data to low-byte of address where loading is to occur (presumably ; BOOT2), press DEPOSIT, set data to high-byte of address where loading ; is to occur, press DEPOSIT, set address to start of BOOT1, press EXAMINE, ; press RUN; Type pairs of characters for program to be loaded (presumably ; BOOT2); Stop program when done. .=$2000 BOOT1: MVI A,ACIARS ;Reset OUT CTL0 MVI A,ACIAMD ;Mode for normal operation OUT CTL0 LHLD 6 LXI SP,$7FFF BOOT1L: CALL RECHR MOV M,A CALL RECHR ADD M ADD M ADD M MOV M,A INX H JMP BOOT1L ;Subroutine to read and echo one character RECHR: IN CTL0 RAR JNC RECHR IN DATA0 OUT DATA0 RET ;Conversion table from hexadecimal to 3x+y notation -- For the hexadecimal ; value at the left margin, any of the combinations given are equivalent ; 3x+y representations, and all will work regardless of the parity setting ; of your terminal providing that it is consistantly one of the standard ; settings (even, odd, mark, space). You may select which ever involves ; the least amount of shifting, or which is easiest to remember or which ; spells some word or which looks pretty etc. Note that the table includes ; only letters (upper and lower case), numbers, and the six punctuation ; characters that don't require shifting on TTY keyboards (,./;:-). ; Innumerable other combinations are possible if other characters may be used. ;00 E1 C7 ;O 8X 4d 1m /s B: 9U ;01 F/ E2 C8 2k 1n D5 :S 9V 0q .w ;02 ;Q /u G- D6 5c 3i 0r .x ... ;FD C4 ;L 7X 4a 2g /p ,y A: ;FE J F, E/ 4b 2h 1k ,z :P 9S 3e .t -w ;FF 8W D3 B9 3f 0o -x .END BOOT1 These are excerpts from the file BOOT3.M80: ;; -- BOOT 3 -- ;This downloader was translated from MOS 6502 where it was known as V0024. ; It talks through a modem to a large computer where L6502B is running. ; All the smarts are in the large computer program. This program ; merely echoes back to the large computer everything sent to it, and ; obeys three simple commands (whose initial characters must be in ; upper case): ; A -- set 16-bit address ; D -- deposit one byte of data and increment address ; B ... -- verify n and check ; are 1's complements of each other, and if so then repeat n times the ; step of waiting for one byte of data then depositing and incrementing. ; If n+check .NEQ. $FF then abort the command (the large computer ; program executes B with echo-compare after each, ; and will abort itself if the echo is bad. Once those three bytes ; are verified, the n bytes of data will be sent in FDX-overlap mode). ; When the state-machine is waiting for the start of the next command, ; all characters except A,B,D will be treated as single-character NO-OPs. ; The first character of each valid command, and all no-ops, are not ; only echoed to the large computer, but printed on the local terminal. ; The large-computer program may thus punctuate the transcript and/or ; send comments to the operator, providing those three reserved letters ; don't appear in comments or punctuation. An easy way to assure this ; is to send comments in lower case. L6502B uses this mechanism to ; provide the operator with a running listing of memory address of ; major blocks of data and a message indicating when it is all done. .=$2100 BOOT3: LXI SP,$7FFF CALL INISIO MVI A,$21 ;'!' CALL WMOD8 ;Send ! to tell other program we're ready CMD: CALL EMOD8 ;Echo everything here, command or comment ANI $7F CALL OUTCHR CPI $41 ;'A' JNZ NOTADR ;Address set... CALL EMOD8 ;Wait for low byte of address MOV L,A CALL EMOD8 ;Wait for high byte of address MOV H,A JMP CMD NOTADR: MVI C,1 ;Count is 1 (assume D command) unless changed CPI $44 ;'D' JZ DEPLOOP ;If indeed Deposit, jump into loop now CPI $42 ;'B' JNZ CMD ;If neither Deposit nor Block, no-op ;Block command... CALL EMOD8 ;Wait for N MOV C,A CALL EMOD8 STC ;Set carry to add N+CHECK+1 which should equal 0 ADC C JNZ CMD ;Oops, doesn't add, abort DEPLOOP: CALL EMOD8 ;Wait for one byte of data MOV M,A ;Deposit it here INX H ;Increment address DCR C ;Count one, see if done... JNZ DEPLOOP JMP CMD ;Done ;Subroutine to echo 8-bit byte from modem EMOD8: CALL RMOD8 ;First wait for a byte to arrive, read it. ;Then fall thru to write it to modem... ;Subroutine to write one 8-bit byte to modem WMOD8: MOV B,A ;Save character WMOD8L: IN CTL1 ANI 2 JZ WMOD8L MOV A,B OUT DATA1 RET ;Subroutine to read one 8-bit byte from modem RMOD8: IN CTL1 RAR JNC RMOD8 IN DATA1 RET .END TTY ;BOOT3 is started after L6502B is ready These are excerpts from the file PCN80B.M80 (163k bytes total): ; To adapt this for running on another 8080/Z80 machine, only the section ; on memory layout and lines marked /CUSTOMIZE/ need to be changed. ; Software for PCNET and personal secretary, ; using the new IO1.M80 serial I/O module to do all modem and local I/O. ; -C- Copyright 1982 by Robert Elton Maas, all rights reserved. ;/U/ Recent changes listed here in reverse chronological order for benefit of ; Rick Myob (KAWALA at MIT-AI) and Ed Huang (EH at MIT-AI). Date&time PST: ;year.mth.dt hr -- description -- status&version# ;1982.Mar.04 08 -- Updated phone numbers -- v.271 ;1982.Feb.16 16 -- Bugfix-copy, avoid ovfl when computing (A+B)/2 -- v.269 ;1982.Feb.16 16 -- MYID changed to use ' to delimit 60ths of degree -- v.269 ; (A+B)/2 BUGFIX, INSTALLED ON ED HUANG'S TRS-80, WORKING AT UPGRADE #2; ;1982.Jan.03 13 -- Clarification and update of copyright notice -- v.268 ;1981.Dec.25 14 -- Minor fix, changing to (errmsg) -- v.267 ;1981.Dec.25 13 -- Starting upgrade #2, Radix-41 characterset -- v.266 ;1981.Dec.25 13 -- Bugfix, check for R41 overflow needs swap too -- v.265 ;1981.Dec.25 02 -- Starting upgrade #1, swap bytes -- bad v.264 ; MAJOR VERSION SAVED FOR MYOB V.263, LAST VERSION WITH 1980 PROTOCOL ;1981.Dec.17 07 -- Renumbered level numbers to agree with PCNSR6 -- v.263 ;1981.Dec.15 07 -- Deleted extraneous "-" from Myob's phone number -- v.262 ;1981.Dec.02 23 -- ID&MAIL handling of multiple messages -- v.259 ;1981.Dec.02 22 -- Added MM Append cmd -- v.259 ;1981.Dec.02 22 -- Fixed MM Forward cmd to prevent overrun -- v.259 ;1981.Nov.19 07 -- Added command to deliver mail in reverse Bell-103a -- v.256 ;1981.Nov.10 00 -- Added host Gragg&ABBS -- v.254 ;1981.Nov.05 18 -- Uploader fixed to not expect echo of ctrl chars -- v.253 ; MAJOR VERSION SNARFED BY MYOB V.252 ;1981.Oct.25 16 -- Bad code (LXI C,...; Oct 03) --> (MVI C,...) -- fixed v.251 ;1981.Oct.25 16 -- Add Myob node-id in table, null JCL -- ok v.251 ;;T;; Toplevel loop, alternates between waiting for event and handling it P0: LXI SP,STACK0 ;Set up stack ptr before anything else! MVI C,NINIT ;Initialize all I/O channels... IOOP CALL SELS LXI H,P0TXT ;Ask REM if he wants to reset all pointers CALL CONFRM JZ P1 ;If no ;Initialize message buffers... LXI H,MSGBUF ;Initialize message buffer to empty SHLD MSGCUR LXI H,SNDBUF ;Initialize outgoing-message buffer to empty SHLD PSNDBU ;Initialize user-modifiable telephone number... LXI H,UNKNUZ+1 SHLD DE2 ;DE2=End of place in RAM where number goes + 1 LXI D,UNKNUM ;DE=Start of place in RAM where number goes LXI H,UNKN0Z+1 SHLD HL2 ;HL2=End of ROM dummy phone number LXI H,UNKN0M ;HL=Start of ROM dummy phone number MVI A,2 ;Truncate if insufficient room (should not occur) CALL MAYBLT JZ P10 BKPTOP ;If anything went wrong, bugtrap P10: MVI A,$30 STA MAXUPB ;Default window size for overlapped-I/O uploader P1: CALL OREM ;Always set this first IDLELP: CALL SELS LXI H,IDLETX ;Print idle message... MVI C,NS1WST IDLELQ: IOOP MVI C,NWAIEV ;Wait for event... IOOP CALL HANDEV ;Handle the event completely. JZ IDLELQ ;Done with trace flag clear, quietly loop JMP IDLELP ;Done, back to idle wait loop with trace ;After it has been established that a human on a terminal is calling, ; this is the toplevel human interface to ask what he/she wants. ANSHUM: CALL SELS ;Tell REM it is a human LXI H,ISHUM MVI C,NS1WST IOOP CALL SELP ;First tell user the status of REM LXI H,REMBAN MVI C,NS1WST IOOP LHLD HLREM MVI C,NS1WST IOOP LXI H,LEVTXT ;Then ask caller what level protocol to use MVI C,NS1AST IOOP MVI C,NR7WUC ;Wait for his answer IOOP JZ ANSHU7 ;If no error, go to process the byte ;Error or 10-second timeout... MOV A,C ;Move error code to test for lost carrier CPI NERRCA RZ ;If lost carrier, abort now MVI C,NR7WUC ;Allow another ten seconds IOOP JZ ANSHU7 ;If no error this time ;Second try also error or timeout (20 seconds now)... MOV A,C CPI NERRCA RZ ;If lost carrier the second time, abort JMP ANSHUM ;Other error, ignore data ;;1;; PCNET protocol -- half-duplex turnaround ;A half-duplex (HDX) communication cycle repeats over and over the following ; sequence of operations: ; (1) Transmit proper @packet@; ; (2) Send turnaround and wait only 10 seconds for atsigns to come back ; before giving up and repeating the send&wait operation; ; (3) Receive proper @packet@ until turnaround seen, allowing 50 seconds; ; (4) Send atsigns immediately (within 10 seconds) to ack turnaround; ; (5) All the higher levels of protocol (translation, validation, directing, ; and user-level stuff such as disk access) which can take up to 50 seconds. ; XMTHDX does steps (1) and (2) while RCVHDX does steps (3) and (4). ; In this test loop, a trivial (5) is done in the toplevel loop, wheras ; in more complete tests and actual working nodes (5) consists of all ; the routines that call XMTHDX and RCVHDX from the toplevel user process ; down to the routines that directly call XMTHDX or RCVHDX. Note that ; when initiating a connection the originate node waits for end-of-HELLO ; brackets and then begins in the middle of (5), continuing (1) (2) ... ; while the answer node sends end-of-HELLO brackets and then omits (1) ; and (2) the very first time continuing (3) (4) ... . ;HDX CYCle, transmits what is in HL:DE-1 then receives and puts the result ; in HL:DE-1. If MYRCV is true, the transmit part is omitted and the ; original contents of HL,DE are ignored. HDXCYC: LDA MYRCV ORA A JNZ HDXCY1 ;If MYRCV, omit transmit, just do receive CALL XMTHDX RNZ ;If fatal error or abort HDXCY1: JMP RCVHDX ;Always do RCV half ;Special version for echo test that flushes atsigns HDXCYE: CALL HDXCYC RNZ MOV A,M CPI '@ JNZ HDXCEU INX H ;Flush leading atsign HDXCEU: DCX D ;Back pointer to trailing atsign and flush it? LDAX D CPI '@ JZ HDXCEX INX D ;Unflush trailing atsign HDXCEX: CALL CPHLDE ;Be sure we didn't leave bytecount negative! JC HDXCEZ ;If positive length, leave as-is PUSH H ;Negative or zero length, clobber DE to equal HL... POP D HDXCEZ: XRA A ;Set zero status before returning RET ;Originate-mode level 1 (hdx) test loop O1TEST: CALL MHELLO ;Connect at modem level RNZ ;If failed to connect CALL O0ECHO ;Manually log in and start server CALL INI1OR ;Init globals for HDX originate mode CALL SELP ;Make sure all the stuff below uses the modem! CALL SENDAT ;Acknowledge end-of-HELLO turnaround immediately ;Init of protocol done, now interface to generic originate-echotest MVI B,57 ;Maximum number of data bytes per packet LXI H,HDXCYE CALL ORIECH RET ;Answer-mode level-1 (HDX) echoback loop (pseudo-server) A1ECHO: CALL SELS ;Tell operator LXI H,A1TRCT MVI C,NS1WST IOOP CALL SELP ;Select PMMI for everything else in test... ; LXI H,A1TTXT ;Print greeting banner ; MVI C,NS1WST ; IOOP CALL INI1AN ;Initialize HDX globals for answer mode MVI B,MAXTB LXI H,HDXCYC CALL ANSECH JMP ANSHUM A1TRCT: .ASCII / (1=HDX) / .BYTE 0 ;A1TTXT:.ASCII / HDX echoback test loop / ; .BYTE 0 ;Initialize level 1 (HDX turnaround) in originate mode INI1OR: MVI A,OPENBR ;[ STA HETOME MVI A,CLOSBR ;] STA METOHE XRA A STA MYRCV ;I do full cycle, transmit-first, always, MYRCV=0 JMP INI1CO ;Initialize level 1 (HDX turnaround) in answer mode INI1AN: MVI A,CLOSBR ;] STA HETOME MVI A,OPENBR ;[ STA METOHE STA MYRCV ;First-time flag, just do receive, MYRCV nonzero=true INI1CO: XRA A STA NOACK ;Normal receive, this flag set by HANG-UP protocol RET ;Send turnaround brackets (clobbers B,C,E) then clear input buffer ;PMMI must be selected before calling this (except in local test mode) ;On return, C==(0=ok nonzero=fatalerror) SENDBR: MVI E,5 SENDB1: LDA METOHE MOV B,A MVI C,NS1WBT IOOP RNZ ;If lost carrier, abort, C==errorcode DCR E JNZ SENDB1 MVI C,NS1WBT MVI B,CR ;Simple mode requires this IOOP RNZ ;If lost carrier, abort, C==errorcode MVI B,LF ;This is to avoid overprinting during debugging IOOP RNZ ;If lost carrier, abort, C==errorcode MVI C,NCLRRR ;Clear input buffer to flush very old data IOOP JMP C0RET ;Successful return, C==0 ;Send atsigns to ack turnaround (clobbers B,C,E) ;PMMI must be selected before calling this (except during local test) SENDAT: MVI E,5 SENDA1: MVI B,'@ MVI C,NS1WBT IOOP RNZ ;If lost carrier, abort DCR E JNZ SENDA1 RET ;Success return ;Receive turnaround ack within ten seconds (C==0) or time out ; (C==NERRTO) or manual or lost-carrier abort (C==NERRCA) RCVATS: CALL SELS ;Tell SYSOP we're waiting for atsigns (turnaround ack) LXI H,ATSTRC MVI C,NS1WST IOOP RCVATL: CALL SELP ;Now actually wait for an atsign to come via modem MVI C,NR7WBT IOOP JZ RCVAT7 ;If no error, go to see if it was atsign MOV A,C ;Error, move error code to A so we can test it CPI NERRCA JZ CRET ;If carrier lost, abort now, C==NERRCA CPI NERRTO JZ CRET ;If timeout, abort now, C==NERRTO JMP RCVATL ;Some random error (framing, overrun), ignore data ;B = char received without error... RCVAT7: CALL SELS ;Trace char on TTY MVI C,NS1WBT IOOP ; CALL SELP ; CALL BABORT ;See if fourth abort char [This code never worked] ; JZ RCVAT8 ;If not abort ; MVI C,NERRCA;Yes, abort, pretend it was lost carrier (fatal) ; JMP CRET ;C==NERRCA ;RCVAT8: MOV A,B ;Move char to A to test for atsign CPI '@ JNZ RCVATL ;If not atsign, keep looping until timeout or atsign CALL SELS ;Final trace on TTY LXI H,OKTRC MVI C,NS1WST IOOP CALL SELP JMP C0RET ;Atsign, return with C==0 ATSTRC: .ASCII / RCVATS.../ .BYTE 0 OKTRC: .ASCII /ok. / .BYTE 0 ;Initial atsigns to ack turnaround were sent long ago. Send the main ; data now, which is located from HL to DE-1. It should begin with and ; end with an atsign. Finally send turnaround brackets and , and wait ; for atsigns (ack of turnaround) to come back before returning. ;XMT then perform HDX turnaround (select PMMI port before calling this) XMTHDX: CALL SELS ;Trace on TTY PUSH H LXI H,XMTHTR MVI C,NS1WST IOOP POP H XMTHD1: CALL SELP CALL CPHLDE ;Top-tested loop, see if we're done JZ XMTHD3 ;If done with translated data, go to do turnaround MOV B,M ;Fetch next byte of data to send MVI C,NS1WBT ;(We can't just use S1WST because the buffer might IOOP ; have zero bytes we want to send if in 8-bit mode) RNZ ;If lost carrier, abort, C==errorcode INX H JMP XMTHD1 ;Loop until exhausted bufferfull of characters ;This next piece of code executed ONLY on retries, i.e. when HDX turnaround ; isn't acknowledged with a reasonable time... XMTHD2: MVI B,'@ ;Send one leading atsign on retries MVI C,NS1WBT IOOP RNZ ;If lost carrier, abort, C==errorcode ;First time and also retries, normal send brackets and wait for ack... XMTHD3: MVI A,$FF STA MYRCV ;Set this flag, MY turn to RCV next CALL SENDBR ;Send turnaround and RNZ ;If lost carrier, abort, C==errorcode LDA NOACK ;If that was last packet before hanging up, don't wait ORA A ; for turnaround to be acknowledged. RNZ CALL RCVATS ;Expect atsigns to immediately come back RZ ;If got atsigns, all done here, C==0 MOV A,C ;Get error code to test CPI NERRTO JNZ CRET ;Any error other than timeout is fatal. CALL SELS ;Timeout -- tell operator, then retransmit turnaround. LXI H,XMTH3T MVI C,NS1WST IOOP CALL SELP JMP XMTHD2 ;Go to retransmit turnaround now. XMTH3T: .ASCII / (Turnaround-ack timeout) / .BYTE 0 XMTHTR: .ASCII / Transmitting.../ .BYTE 0 ;HDX turnaround ack has already been seen. Flush extra atsigns, then receive ; data until turnaround received. Ack turnaround immediately (send atsigns) ; then return HL pointing to first byte (the last atsign before real data ; begins) and DE-1 pointing to the last byte (usually the atsign just before ; the turnaround brackets). ;RCV until HDX turnaround is seen ;Select PMMI port before calling this, except for local test RCVHDX: CALL SELS ;Trace progress on TTY LXI H,RCVHTR MVI C,NS1WST IOOP CALL SELP MVI A,5 ;Allow 50 seconds for everything STA TIMCNT MVI A,0 STA HDXCNT ;0 brackets so far seen LXI H,HDXBUF ;Internally HL points to next unused byte, LXI D,HDXBUZ ; while DE-1 points to end of allowed buffer MVI M,'@ ;My goodness what an instruction the 8080 has! INX H ;Buffer passed up to higher layer starts with atsign RCVHDL: MVI C,NR7WBT ;Expect a character to arrive... IOOP JZ RCVHD7 MOV A,C ;Move error code to A for testing CPI NERRCA JNZ RCVHD3 ORA C ;Lost carrier, turn nonzero status back on RET RCVHD3: CPI NERRTO ;Error, see if it was timeout JNZ RCVHDL ;Ignore bad char (framing/overrun) ;Note, if overrun ever occurs here, it indicates a bug in the ; software, that it isn't fast enough to keep up with the modem. ;(The only place overrun makes sense is in RCVATS.) ;Note, if framing error occurs here, the block it is in can be ; marked as invalid, except that this level of protocol doesn't know ; whether the error is inside a block or in atsigns or brackets which ; occur outside a block, thus the error must be ignored here) ;Timeout (10 seconds) -- see if count exhausted... CALL SELS ;Trace partial (10 sec) timeout on TTY MVI B,'t MVI C,NS1WBT IOOP CALL SELP LDA TIMCNT DCR A STA TIMCNT JNZ RCVHDL ;Still have some timeouts to go ;Got ack of hdx but then seemed to die (50 seconds is just too long) ORI $FF RET ;Give up totally, fatal-error return ;Got good character, no error, in B... RCVHD7: CALL SELS ;Trace all rcvd data on tty currently MVI C,NS1WBT IOOP CALL SELP LDA HETOME ;Got good character, see if bracket... CMP B JZ RCVHDB ;If bracket ;Non-bracket seen, decrement bracket count and store in buffer... LDA HDXCNT ;Decrement 1/5 of a bracket DCR A JM RCVHD8 ;Don't let it go negative STA HDXCNT RCVHD8: CALL CPHLDE ;See if there is room in the buffer JNC RCVHDL ;If too big, just ignore it, look for turnaround MVI A,'@ ;See if char=atsign and HL points at second byte CMP B ;(the spot after the initial atsign stuck in earlier) JNZ RCVHD9 ;If not atsign PUSH D LXI D,HDXBUF+1 CALL CPHLDE POP D JZ RCVHDL ;If leading atsign, ignore it, so there will be only ; one atsign at the very start of the buffer passed up ;If it falls thru here, it is an atsign but not at very start of xmsn RCVHD9: MOV M,B ;Store in buffer INX H JMP RCVHDL ;Saw turnaround bracket, see if it is 3 out of 5... RCVHDB: LDA HDXCNT ADI 5 ;Count 5/5 of a bracket STA HDXCNT CPI 13 ;0 +5 +5 -1 -1 +5 = 13 (3 out of 5) JM RCVHDL ;If not 3 out of 5 yet ;Saw 3 out of 5 brackets! CALL SENDAT ;Acknowledge turnaround immediately, ignore error ; return such as lost carrier XCHG ;DE = ptr to last location used plus one CALL SELS ;Final trace on TTY LXI H,OKTRC MVI C,NS1WST IOOP CALL SELP LXI H,HDXBUF ;HL = ptr to first location used, for other routines XRA A ;Success return! STA MYRCV ;Buffer has data, not MY turn to RCV any more, RET ; this flag used by RCVTRA and VALCYC