; KISS TNC FOR THE TNC-2 AND CLONES ; ; K3MC 30 SEP 86 - ORIGINAL VERSION ; ; 1 MAR 87. FIXED ALL KNOWN BUGS. RE-ARRANGE CODE TO ALLOW ROMING (THIS ; MEANS THAT DATA AREAS NEED TO BE INITIALIZED FROM THE CODE). FIGURE OUT THE ; STACK POINTER GIVEN THE AMOUNT OF AVAILABLE RAM. INCLUDE THE CODES 05 00 ; AND 05 01 TO MEAN FULL DUPLEX OFF AND FULL DUPLEX ON, RESPECTIVELY. ; CLEAR OUT ALL AVAILABLE RAM. DO A "DANCE" WITH LEDS WHEN INITIALLY BOOTED: ; FLASH THE LED(S) FOR ABOUT 5 SECONDS SUCH THAT CON ONLY FLASHES IF YOU HAVE ; 8K RAM, STA ONLY FLASHES IF 16K RAM, AND STA AND CON FLASH IF 32K RAM. ; ; 29 MAR 87. ADD CODE TO DISCARD BREAK CHARS, AND CHARS WITH FRAMING ERRORS. ; FIX BUG IN IB_RCA WHICH DID NOT DISCARD NULL RECEIVED FRAMES. ; Modified by John Wiseman, G8BPQ for use with ZILOG assembler, ; and to reset on error conditions (underrun or buffer overload) ; ; CWID added - june 1990 ; ; AUGUST 90 - FIX FOR 'FLAKY DCD' PROBLEM - IF DCD DROPS DURING A FRAME, ; THEN THE FIRST PART IS JUNKED, AND THE REST TREATED AS A VALID FRAME ; ;PROM EQU 0 ; DOWN LINE LOAD PROM EQU 1 ; PROM VERSION COND PROM-1 ORG 8000H ; FOR DOWN LINE LOAD ENDC FALSE EQU 0 TRUE EQU 0FFH I_REGISTER EQU $/256 SIO EQU 0DCH ;ACTUALLY, ONLY A5 IS USED FOR SIO -CS A_DAT EQU SIO+0 ;MODEM PORT A_CTL EQU SIO+1 ;MODEM PORT B_DAT EQU SIO+2 ;USER SERIAL PORT B_CTL EQU SIO+3 ;USER SERIAL PORT DCD EQU 8 ;BIT IN RR0, USED IN CH A TBE EQU 4 ;TX BUFFER EMPTY BIT RTS EQU 2 ;REQUEST TO SEND (PTT BIT IN WR5 OF CHAN A) FRAMING_ERROR EQU 40H ;BIT IN RR1 FOR ASYNC FRAMING ERROR BREAK_ABORT EQU 80H ;BIT IN RR0 FOR ASYNC BREAK DETECTION FEND EQU 300O ;300 OCTAL FESC EQU 333O ;333 OCTAL TFEND EQU 334O ;334 OCTAL TFESC EQU 335O ;335 OCTAL ALEDON EQU 69H ;BITS FOR WR5 TO TURN ON STA LED ALEDOFF EQU 0E9H ;BITS FOR WR5 TO TURN OFF STA LED ALED EQU 80H ;THE DTR BIT IN CH A WR5, WE WILL SOON REMOVE ;PREVIOUS 2 DEFINITIONS & USE THE MEMORY LOC. ;A_WR5 TO HOLD CH A WR5'S VALUE, BECAUSE WE ;NEED TO BE AWARE WHEN WE ARE TRANSMITTING! BLEDON EQU 6AH ;BITS FOR WR5 TO TURN ON CON LED BLEDOFF EQU 0EAH ;BITS FOR WR5 TO TURN OFF CON LED BLED EQU 80H N_EVENTS EQU 4 ; SO FAR, ONLY 3 REAL-TIME EVENTS ; + CLOCK FOR CWID START: JP CODE_START ;GO AROUND THIS DATA AREA VERSION: DEFM 'G8BPQ 16SEP90' ;13 BYTES (EXACTLY!) HERE FOR VERSION STRING DEFW IB_TBE ;CH B TRANSMITTER BUFFER EMPTY INTERRUPT/USER DEFW IB_EXT ;CH B EXT/STATUS CHANGE/USER DEFW IB_RCA ;CH B RECEIVED CHAR AVAILABLE/USER DEFW IB_SPECIAL ;CH B SPECIAL RECEIVE CONDITION/USER DEFW IA_TBE ;CH A TRANSMITTER BUFFER EMPTY INTERRUPT/MODEM DEFW IA_EXT ;CH A EXT/STATUS CHANGE/MODEM DEFW IA_RCA ;CH A RECEIVED CHAR AVAILABLE/MODEM DEFW IA_SPECIAL ;CH A SPECIAL RECEIVE CONDITION/MODEM CHANNEL DEFB 0 ; DUMMY FOR JKISSP CWID DEFS 512 ; PATCH ID IN HERE - 20 BYTES PER SECOND, ; SO ENOUGH ROOM FOR 25 SECS OF ID! ; A_INIT: DEFB 18H,4,20H,1,1BH,7,7EH,5,0E9H,3,0C9H ;FOR MODEM A_SIZE EQU $-A_INIT B_INIT: DEFB 18H,4,44H,2,10H,3,0C1H,5,0EAH,1,1FH ;FOR TTY B_SIZE EQU $-B_INIT ;THIS IS THE DATA AREA WHICH GETS BLASTED INTO RAM UPON STARTUP: DATA_INIT: ;NBUFFERS: DEFB 0 ;UP TO 255 BUFFERS ;FREE: DEFW 0 ;ADDRESS OF 1ST BUFFER ON FREE LIST ;RX_BUF: DEFW 0 ;ADDRESS OF CURRENT RECEIVE BUFFER ;RX_HEAD: DEFW 0 ;ADDRESS OF 1ST RX BUFFER ;RX_ALLOCATED_BUFFER: DEFB 0 ;SET NON-ZERO IF WE'RE IN RX STATE ;RX_FLUSHING: DEFB 0 ;IS NON-0 IF WE RAN OUT OF BUFFER ;SPACE AND ARE CURRENTLY FLUSHING THIS ;FRAME BEING RECEIVED. USED BY ;IA_RCA AND RESET BY IA_EXT. ;IN_BUFFER: DEFW 0 ;ADDR OF CURRENT INPUT BUFFER ;IN_HEAD: DEFW 0 ;ADDR OF 1ST INPUT BUFFER ;IN_ALLOCATED_BUFFER: DEFB 0 ;IS NOT 0 IF WE'VE ALREADY ALLOC'D BUF ;IN_STATE: DEFB 1 ;CONVERT BACK TO 1 IN V.32 CODE ;INPUT STATE MACHINE STATE ;4 MAR 8: MAKE IT 0 (FROM 1) BECUZ ;NOISE ON LINE IS FIRST TRIGGERING THE ;CODE TO ASSUME THAT A FRAME IS COMING ;FROM THE HOST..... COMMENT BELOW WAS ;APPROPRIATE BEFORE ;ASSUME THAT WE'VE SEEN AN FEND FROM ;(NON-EXISTENT) "PREVIOUS" FRAME. THIS ;MEANS THAT WHEN WE ARE RECEIVING DATA ;FROM USER, THERE NEED BE ONLY THE ;FEND CHAR AT THE END OF A FRAME, AND ;NOT AT THE BEGINNING (ALTHOUGH IF A ;FEND IS AT THE BEGINNING, IT IS ;IGNORED.) ;OUT_STARTED: DEFB 0 ;OUTPUT NOT STARTED YET (LOGICAL VAR) ;OUT_HEAD_CBUF: DEFW OUT_TOP ;ADDRESS OF BUFFER TO BE OUTPUT RS232 ;OUT_TAIL_CBUF: DEFW OUT_TOP ;POINTER TO NEXT FREE OUTPUT BUFFER ;OUT_CHAIN_HEAD: DEFW 0 ;ADDR OF BUFFER WE ARE NOW OUTPUTTING ;TX_STARTED: DEFB 0 ;NON-ZERO IF WE'VE BEGUN TXING CHARS ;TX_HEAD_CBUF: DEFW TX_TOP ;CURRENT ACTIVE CBUF ENTRY (IF ACTIVE) ;TX_TAIL_CBUF: DEFW TX_TOP ;NEXT FREE CBUF ENTRY ;TX_CHAIN_HEAD: DEFW 0 ;HOLDS ADDRESS OF THE CURRENT BUFFER ;CHAIN HEAD THAT WE ARE TRANSMITTING ;TX_OUTSTANDING: DEFB 0 ;NUMBER OF TX CBUFS QUEUED UP FOR TX ;DCD_STATE: DEFB 0 ;IS NON 0 IF DCD LED IS ON ;THESE NEXT TWO ARE USED BY THE IB_TBE INTERRUPT ROUTINE. ;IB_ESC_MODE: DEFB 0 ; NOT IN ESCAPED MODE ;IB_CHAR: DEFS 1 ; NEXT CHAR TO SEND IF ESCAPED MODE ;IN_BREAK: DEFB 0 ; NON-ZERO IF WE ARE IN A BREAK DETECT ; SEQUENCE ON THE ASYNC PORT ;FULL_DUPLEX: DEFB 0 ;NOT FULL DUPLEX TO START ;A_WR5: DEFB ALEDOFF ;STATE OF STA LED & RTS (PTT) LINE, ;MAINLY... (FOR CH A ONLY [MODEM] ) ;B_WR5: DEFB BLEDOFF DATA_SIZE EQU $-DATA_INIT ;*************************************************************************** CODE_START: DI ;NO INTERRUPTS FOR THE MOMENT... ;INIT SIO. THIS IS REQUIRED EVEN IF WE WANNA FLASH LEDS... IN A,(A_CTL) ;ASSURE WE ARE TALKING TO CH 0 LD C,A_CTL LD B,A_SIZE LD HL,A_INIT OTIR ;INIT SYNC (MODEM) PORT ;INIT ASYNC PORT, ALSO TO ALLOW FLASHING LEDS IN A,(B_CTL) ;ASSURE WE ARE TALKING TO CH 0 LD C,B_CTL LD B,B_SIZE LD HL,B_INIT OTIR ;INIT ASYNC PORT & INTERRUPT VECTOR ; ; FIGURE OUT WHERE TOP OF STACK IS, SET STACK POINTER. ; SILLY TNC-2 DOES NOT DO COMPLETE ADDRESS DECODING FOR THE RAMS IF YOU ARE ; USING ONLY THE TWO 8K X 8 CHIPS. HACK TO FIGURE OUT TOP OF MEMORY SO WE CAN ; SET STACK POINTER. NEWER HACK TO SEE IF WE'VE ONLY GOT 8K RAM. LD A,(9FFFH) ;TOP OF RAM IF ONLY 8K CPL LD B,A LD (9FFFH),A ;WRITE ONE'S COMPLEMENT INTO MEM LD A,(9FFFH) CP B ;SEE IF IT "TOOK" JP Z,OK_8 ;YES, WE HAVE AT LEAST 8K OF RAM HALT ;ELSE THERE IS NO RAM, SO STOP OK_8: LD A,(0BFFFH) ;TOP OF RAM IF 16K CPL LD B,A LD (0BFFFH),A ;SAME ONE'S COMPLEMENT HACK LD A,(0BFFFH) CP B JP Z,OK_16 ;WE HAVE AT LEAST 16K OF RAM LD SP,0A000H LD D,0FFH ;BLINK CON LED LD E,0 ;BUT NOT STA LED (I.E., WE HAVE 8K) JP STACK_LOADED ;ELSE WE ONLY HAVE 8K OF RAM ;BECAUSE PREVIOUS COMPARE FAILED ;HERE IF WE'VE GOT AT LEAST 16K RAM OK_16: LD A,55H ;ONE VALUE LD (0BFFFH),A LD A,0AAH LD (0FFFFH),A ;OTHER VALUE LD A,(0BFFFH) ;GET WHAT SHOULD BE 55H IF 32K CP 55H LD SP,0 LD DE,0FFFFH ;BLINK BOTH CON AND STA LEDS (IF 32K) JR Z,STACK_LOADED ;IF IS 55H, THEN WE'VE GOT 32 K, ELSE 16 K LD SP,0C000H ;FORCE STACK VALUE. LD D,0 ;DO NOT BLINK CON LED IF 16K RAM STACK_LOADED: PUSH DE ;DE HAS LOGICAL VALUES WHICH TELL US WHICH ;LEDS TO FLASH (WHICH WE DO LATER...) EXX POP HL ;TEMP. STORE THIS INFO IN OTHER REG SET EXX ;CLEAR OUT RAM. LD HL,0 ADD HL,SP ;NOW HL HAS VALUE OF SP (THAT IS, TOP OF ;MEMORY + 1) DEC HL ;NOW HL HAS TOP OF MEMORY ADDRESS LD DE,FREE_RAM ;GET START OF AVAILABLE FREE RAM XOR A ;CLEAR CARRY AND SET A TO 0 LD (DE),A ;FIRST FREE RAM LOCATION IS ZEROED.... SBC HL,DE ;GET INTO HL # OF BYTES OF FREE RAM. IF WE ;ARE IN ROM, THEN ALL RAM IS FREE, ELSE IF WE ;ARE RUNNING FROM RAM, THE CODE PART IS NOT ;FREE, AND THIS COMPENSATES FOR THIS. DEC HL ;ONE FEWER BYTES FOR NUMBER TO MOVE... LD B,H LD C,L ;GET BYTE COUNT INTO BC LD H,D LD L,E ;GET "SOURCE" ADDRESS = FREE_RAM INC DE ;SET "DESTINATION" ADDRESS = FREE_RAM + 1 LDIR ;ZERO MEMORY. ;THIS SEQUENCE LOADS UP OUR DATA AREA IN RAM: LD HL,DATA_INIT LD DE,NBUFFERS LD BC,DATA_SIZE LDIR ; SET STACK SIZE AND INIT FREE BUFFER LIST. LD HL,0 ADD HL,SP ;GET VALUE OF SP, HIGH MEMORY LD DE,100 ;50 WORDS FOR STACK OR A ;CLEAR CARRY SBC HL,DE ;NOW HL HAS "PSEUDO TOP OF MEMORY" LD DE,BOTTOM ;"PSEUDO BOTTOM OF MEMORY" OR A SBC HL,DE ;HL NOW HAS SIZE OF AVAILABLE MEMORY RL L ;PUT MSB INTO CARRY RL H ;PUT CARRY INTO LSB ;NOW H HAS NUMBER OF BUFFERS AVAILABLE LD A,H LD (NBUFFERS),A ;SAVE THIS NUMBER IN MEMORY LD HL,BOTTOM ;BEGINNING OF BUFFER SPACE LD (FREE),HL ;NOW IT'S ALSO TOP OF FREE LIST ; INIT BUFFER FREE LIST LD B,A ;GET NBUFFERS (SEE ABOVE) DEC B ;BECAUSE LAST ONE HAS 0 AS "NEXT" IBLOOP: PUSH HL LD DE,128 ADD HL,DE ;HL HAS "NEXT" POINTER EX DE,HL ;DE HAS "NEXT" POINTER POP HL ;HL NOW HAS POINTER TO CURRENT BUFFER LD (HL),E ;LOW BYTE OF "NEXT" POINTER FIRST INC HL LD (HL),D ;NOW HI BYTE INC HL XOR A LD (HL),A ;ZERO OUT COUNT FIELD INC HL LD (HL),A ;ZERO OUT # OF BYTES READ FIELD EX DE,HL ;HL IS NOW POINTER TO NEXT BUFFER DJNZ IBLOOP ;AND INIT ALL THE AVAILABLE BUFFERS XOR A LD (HL),A ;LAST "NEXT" ADDRESS IS 0 INC HL LD (HL),A ;DITTO INC HL LD (HL),A ;ZERO OUT COUNT FIELD INC HL LD (HL),A ;ZERO OUT # OF BYTES READ FIELD ;INIT REGS FOR IB_EXT INTERRUPT EXX LD BC,0 ;SET PREV STATE OF SYNC PIN,FOR 1200HZ LD DE,0 ;COUNT OF # OF INTERRUPTS INIT EXX XOR A LD (RX_ALLOCATED_BUFFER),A ;NOT RECEIVING AT THIS TIME LD HL,TXQ_ENABLES LD B,N_EVENTS E_CLEAR: LD (HL),A ; TURN OFF ALL THE ENABLES OF ALL ... INC HL ; ... POSSIBLE EVENTS. DJNZ E_CLEAR ;INIT THE ROUTINE ADDRESSES IN OUR EVENT TABLE LD HL,R_DELAY LD (TXQ_ADDRESSES),HL LD HL,R_SLOTTIME LD (TXQ_ADDRESSES+2),HL LD HL,R_TAIL LD (TXQ_ADDRESSES+4),HL LD A,(CWID) CP 0FFH JR Z,SKIPID ; NO CWID DEFINED LD HL,RTCLOCK LD (TXQ_ADDRESSES+6),HL ; REAL TIME CLOCK FOR CWID ; ; ENABLE RTC FOR CWID ; LD HL,6000 ; MINUTE IN 1/100THS OF SEC LD (TXQT_CWID),HL ; GET TIMER VALUE INTO TIMER SLOT LD A,1 LD (TXQE_CWID),A ; ENABLE THIS EVENT LD (MINUTES),A ; SEND FIRST AFTER 1 MINUTE SKIPID: LD A,50 LD (TXDELAY),A ; TX DELAY DEFAULT IS 500 MS LD A,64 LD (PERSISTENCE),A ; SET DEFAULT VALUE FOR PERSISTENCE LD A,10 LD (SLOTTIME),A ; AND SLOT TIME DEFAULTS TO 100 MS LD A,3 LD (TAILTIME),A ;TAIL TIMER DEFAULT ; NOW HAVE THE CON AND STA LEDS DO A "DANCE". EXX PUSH HL EXX POP DE ;WE SAVED THE LOGICALS TELLING US WHICH LEDS ;TO FLASH WHEN WE FIGURED OUT THE STACKSIZE. ;THIS IS HOW WE KNOW WHICH LEDS TO BLINK. LD B,6 ;DO IT 6 TIMES (ARBITRARY AS HELL, BUT SHOULD ;BE AN EVEN NUMBER SO THAT THE LEDS ARE OFF AT ;THE END OF THIS MESS...) LD HL,0 ;USE HL AS DOWNCOUNTER DANCE0: LD A,D OR A CALL NZ,CON_FLIP LD A,E OR A CALL NZ,STA_FLIP DANCE1: DEC HL LD A,H OR L JP NZ,DANCE1 DJNZ DANCE0 ;DO THIS 6 TIMES (3 "CYCLES") ;PREVIOUS STUFF SHOWED THAT THE DOWNLOAD OR BOOT WORKED PROPERLY... ;WE RE-INITIALIZE THE SIO PORTS SO THAT WE FLUSH GARBAGE CHARS THAT MAY HAVE ;COME IN WHILE WE WERE DIDDLING THE LEDS. THIS IS NECESSARY BECAUSE UNLESS WE ;DO THIS, THEN THE A CHANNEL (MODEM) GET RX OVERRUN (ESP IF TNC WAS LISTENING ;TO NOISE) AND RX OVERRUN IS VERY BAD - SO BAD, IN FACT, THAT I TURN ON BOTH ;CON AND STA AND HALT, BECAUSE THIS SITUATION SHOULD NEVER HAPPEN IN NORMAL ;USE. I FLUSH THE B (TTY) CHANNEL IN CASE ANYTHING WAS SENT TO IT IN MID- ;STREAM. ;RE-INIT SIO. IN A,(A_CTL) ;ASSURE WE ARE TALKING TO CH 0 LD C,A_CTL LD B,A_SIZE LD HL,A_INIT OTIR ;INIT SYNC (MODEM) PORT ;RE-INIT ASYNC PORT. IN A,(B_CTL) ;ASSURE WE ARE TALKING TO CH 0 LD C,B_CTL LD B,B_SIZE LD HL,B_INIT OTIR ;INIT ASYNC PORT & INTERRUPT VECTOR ; PREPARE TO LOAD HI BITS OF INTERRUPT VECTOR LD A,I_REGISTER LD I,A ;SET INTERRUPT PAGE FOR MODE 2 INTS IM 2 EI ;LET 'EM RIP! ;----------------------------------------------------------------------------- ; THIS IS THE BACKGROUND PROGRAM. ; NOTE THAT SINCE EVERYTHING ELSE IS INTERRUPT DRIVEN, AND SAVES REGISTERS, ; THIS PART OF THE CODE CAN USE REGISTERS & EXPECT VALUES TO STAY. COMMUTATOR_LOOP: ; ; SEE IF ID NEEDED ; LD A,(CWIDFLAG) OR A JR NZ,TRY_ID LD A,(TX_OUTSTANDING) ;IF THERE ARE NO OUTSTANDING TX... OR A ;...FRAMES, THEN WE DON'T HAVE TO... JP Z,SCAN_CHECK ;...WORRY ABOUT TRANSMITTER TRY_ID: ; ; IF THERE ARE FRAMES TO TRANSMIT, WE MAY HAVE TURNED ON TXDELAY, OR WE MAY BE ; TRANSMITTING A FRAME SO CHECK FIRST. ; (THIS BUG FOUND LATE ON 30 SEP 86) THE CLEANEST WAY TO DO ; THIS IS TO CHECK IF WE ARE KEYED UP. IF SO, NOTHING ELSE TO DO FOR NOW ; HERE. THIS IS THE "LAST BUG!" FOUND AT 11:59PM EDT ON 30 SEP. LD A,(A_WR5) AND RTS JP NZ,SCAN_CHECK ;IF TX KEYED UP, NOTHING FOR US TO ;DO HERE! ; ; ELSE WE'VE NOTICED THAT WE'VE GOT SOME FRAME(S) TO SEND. ; TRY TO KEYUP TX LD A,(FULL_DUPLEX) OR A JP NZ,KEY_UP ;IF FULL DUPLEX, THEN THERE IS NO ;NEED TO WORRY ABOUT ALL THIS SILLY ;SLOT TIME AND PERSISTENCE STUFF! LD A,(TXQE_SLOTTIME) ;GET SLOTTIME TIMER ENABLE OR A JP NZ,SCAN_CHECK ;IF WE'RE WAITING, KEEP WAITING! ;CHECK IF CARRIER DETECT IS ACTIVE LD A,(DCD_STATE) ;DCD_STATE IS SET IN INTERRUPT ROUTINE OR A JP NZ,SCAN_CHECK ;IF CARRIER ACTIVE, WAIT IT OUT ;SO, DCD IS INACTIVE; DO PERSISTENCE ALGORITHM LD A,R ;GRAB THE Z-80 REFRESH REGISTER ADD A,A ;DOUBLE;NOW 0 <= A REG <= 254 LD B,A ;B HOLDS OUR "RANDOM" NUMBER LD A,(PERSISTENCE) SUB B ;A REG = PERSISTENCE - RANDOM # JP C,NO_PTT ;IF (P-R) < 0 THEN NO PTT NOW ; NOTE THAT P=255 MEANS ALWAYS KEY UP ;OK, SO WE'VE WON WITH THE RANDOM NUMBER GENERATOR. KEYUP TX AND START THE ;TXDELAY TIMER KEY_UP: ; ; SEE IF ID NEEDED ; LD A,(CWIDFLAG) OR A JP Z,NO_ID CALL STARTID JP SCAN_CHECK NO_ID: LD A,(TXDELAY) LD H,0 LD L,A ;HL IS 16-BIT VALUE OF TXDELAY LD (TXQT_DELAY),HL ;GET TIMER VALUE INTO TIMER SLOT LD A,1 LD (TXQE_DELAY),A ;ENABLE THIS EVENT LD A,5 DI ;WE NEED QUITE TIME HERE. OUT (A_CTL),A ;;;READY TO WRITE INTO WR5 OF CH A LD A,(A_WR5) OR RTS ;;;TURN ON THE PTT BIT... LD (A_WR5),A ;;;...IN THE MEMORY COPY OF WR5 OUT (A_CTL),A ;;; KEYUP TRANSMITTER EI JP SCAN_CHECK ;THAT'S ALL WE DO FOR NOW, WE AWAIT ;TXDELAY EVENT NO_PTT: ; ; SINCE WE LOST ON RANDOM #, WAIT SLOTTIME BEFORE TRYING AGAIN ; LD A,(SLOTTIME) LD H,0 LD L,A ;HL HAS 16-BIT VERSION OF SLOTTIME LD (TXQT_SLOTTIME),HL ;SET UP THE TIMER VALUE OF THIS EVENT LD A,1 LD (TXQE_SLOTTIME),A ;AND ENABLE THIS EVENT ; NOTE THAT THIS CODE DOES NOT HAVE TO BE INTERRUPT PROTECTED BECAUSE WE ; REALLY DON'T CARE IF THE SLOT TIMER IS DECREMENTED BETWEEN BEING LOADED ; AND BEING ENABLED. SCAN_CHECK: LD HL,TXQ_ENABLES ; GEAR UP TO CHECK TIMER ROUTINES LD IX,TXQ_TIMERS LD IY,TXQ_ADDRESSES LD DE,2 ;BUMP IX & IY BY TWOS LD B,N_EVENTS ;NUMBER OF POSSIBLE EVENTS SCAN_TOP: LD A,(HL) OR A JP Z,SCAN_BOTTOM ;IF NOT ENABLED, CHECK NEXT ONE ;ELSE IS ENABLED. TIMER EXPIRED? LD A,(IX+1) LD C,A ;SAVE MS BYTE FOR POSSIBLE USE LATER OR (IX) JR Z,SCAN_FIRE ;FIRE THIS IF WE ARE AT 0 COUNT LD A,C OR A ; SAVES US SOME TIME DOING IT THIS WAY JP P,SCAN_BOTTOM ; OR FIRE IF WE ARE NEGATIVE SCAN_FIRE: XOR A LD (HL),A ;DISABLE THIS EVENT AS IT FIRES PUSH HL LD HL,SCAN_RETURN ;LOAD UP ROUTINE RETURN ADDRESS PUSH HL ;SAVE AS RETURN ADDRESS ON STACK LD H,(IY+1) LD L,(IY) ;GET ADDRESS OF ROUTINE TO "CALL" JP (HL) ;"CALL" THIS ROUTINE SCAN_RETURN: ;WHERE ALL ROUTINES RETURN POP HL ;GET ORIGINAL HL BACK SCAN_BOTTOM: INC HL ;INCREMENT ENABLE TABLE POINTER ADD IX,DE ;KEEP TIMER TABLE POINTER IN STEP ADD IY,DE ;KEEP ROUTINE TABLE POINTER IN STEP DJNZ SCAN_TOP ;LOOK AT ALL ENTRIES IN TABLES ;NOW SEE IF WE NEED TO START AN OUTPUT TO RS-232 (HOST) PORT LD A,(OUT_STARTED) OR A ;ALSO CLEARS CARRY (SEE BELOW) JP NZ,COMMUTATOR_LOOP ;IF OUTPUT STARTED, NOTHING TO DO ; ELSE WE SHOULD CHECK TO SEE IF WE NEED TO START AN OUTPUT DI CALL CON_OFF ;;; LD HL,(OUT_HEAD_CBUF) ;;;GRAB CURRENT TOP OF CIRC BUF PTR LD DE,(OUT_TAIL_CBUF) ;;;AND WHERE THE NEXT FREE BUF PTR IS EI ;INTERRUPT PROTECT THE PICKUP OF THE ;TWO POINTERS 3 FEB 87 OR A SBC HL,DE JP Z,COMMUTATOR_LOOP ;IF THE SAME, NOTHING TO DO ;ELSE WE NEED TO START AN OUTPUT DI ;INTERRUPT PROTECT THIS SECTION, ;ALTHOUGH I'M NOT SURE IT NEEDS IT... ;3 FEB 87 ;NOTE: IT SHOULD ALREADY BE DONE! LD HL,(OUT_HEAD_CBUF) ;;;GET POINTER TO NEXT CBUF TO OUTPUT LD E,(HL) INC HL LD D,(HL) ;;;DE HAS POINTER TO BUFFER CHAIN LD (OUT_CHAIN_HEAD),DE ;;;SET IN INTERRUPT ROUTINE'S PLACE LD A,1 LD (OUT_STARTED),A ;;;YES, OUTPUT STARTED CALL CON_ON CL_0: IN A,(B_CTL) ;;;LOOK AT RR0 AND TBE ;;;ISOLATE THE TBE BIT JR Z,CL_0 ;;;WAIT FOR TRANSMITTER TO GET DONE LD A,FEND OUT (B_DAT),A ;;;SEND FEND CHARACTER (START TXING) EI JP COMMUTATOR_LOOP ;KEEP LOOKING FOR NEW OPPORTUNITY RTCLOCK: ; ; COUNT DOWN MINUTES TO KICK OFF CWID ; LD HL,6000 ; MINUTE IN 1/100THS OF SEC LD (TXQT_CWID),HL ; GET TIMER VALUE INTO TIMER SLOT LD A,1 LD (TXQE_CWID),A ; ENABLE THIS EVENT LD A,(MINUTES) DEC A LD (MINUTES),A RET NZ ; INC A LD (CWIDFLAG),A ; SHOW WE NEED AN ID RET ; STARTID: ; ; SET TIMER TO GO TO NEXT CWID STATE ; LD HL,IDEVENT LD (TXQ_ADDRESSES+6),HL ; REAL TIME EVENT FOR CWID ; LD HL,6 ; 20HZ BAUD RATE FOR CWID LD (TXQT_CWID),HL ; GET TIMER VALUE INTO TIMER SLOT LD A,1 LD (TXQE_CWID),A ; ENABLE THIS EVENT ; ; RAISE RTS - NEXT TIMER EVENT WILL SEND FIRST BIT ; LD HL,CWID LD (CWIDPTR),HL XOR A LD (CWIDFLAG),A LD (LASTBIT),A ; ; ENTER SYNC MODE SO WE CAN CONTROL TONES ; DI LD A,1 OUT (A_CTL),A XOR A OUT (A_CTL),A ; INTERRUPTS OFF LD A,4 OUT (A_CTL),A LD A,00010000B ; SYNC OUT (A_CTL),A LD A,6 OUT (A_CTL),A LD A,0FFH OUT (A_CTL),A ; SYNC CHARS LD A,7 OUT (A_CTL),A LD A,0FFH OUT (A_CTL),A ; SYNC CHARS LD A,5 OUT (A_CTL),A LD A,11101011B ; RAISE RTS + TXENABLE LD (A_WR5),A OUT (A_CTL),A ; EI RET IDEVENT: ; ; 60 MS HAS PASSED - GO TO NEXT STATE ; LD HL,6 ; 20HZ BAUD RATE FOR CWID LD (TXQT_CWID),HL ; GET TIMER VALUE INTO TIMER SLOT LD A,1 LD (TXQE_CWID),A ; ENABLE THIS EVENT ; LD HL,(CWIDPTR) LD A,(HL) INC HL LD (CWIDPTR),HL CP 0FFH JP Z,ENDID LD HL,LASTBIT CP (HL) RET Z ; NO CHANGE LD (HL),A ; SAVE NEW STATE LD A,7FH OUT (A_DAT),A ; TOGGLE TONE RET ; ENDID: ; ; RESET REAL TIME EVENT TO COUNT DOWN FOR NEXT ID (29 MINS) ; PUSH BC DI IN A,(A_CTL) ;ASSURE WE ARE TALKING TO CH 0 LD C,A_CTL LD B,A_SIZE LD HL,A_INIT OTIR ;INIT SYNC (MODEM) PORT LD A,0E9H LD (A_WR5),A EI POP BC LD HL,RTCLOCK LD (TXQ_ADDRESSES+6),HL ; REAL TIME CLOCK FOR CWID ; LD HL,6000 ; MINUTE IN 1/100THS OF SEC LD (TXQT_CWID),HL ; GET TIMER VALUE INTO TIMER SLOT LD A,1 LD (TXQE_CWID),A ; ENABLE THIS EVENT LD A,29 LD (MINUTES),A RET ;***************************************************************************** ; TIMER-DRIVEN EVENTS ;***************************************************************************** ;----------------------------------------------------------------------------- R_DELAY: ; THIS ROUTINE EXECUTES WHEN THE TX DELAY TIMER EXPIRES. PUSH AF PUSH BC PUSH DE PUSH HL DI CALL TXNEXT_CBUF ;GETS HL TO POINT TO BUFFER CHAIN, AND ;SETS TX_CHAIN_HEAD FOR THE INTERRUPT ;ROUTINE LD A,80H OUT (A_CTL),A ;;; RESET TX CRC CALL GETCHAR ;;; GETCHAR NEEDS INT. PROTECTION OUT (A_DAT),A ;;; SHIP THIS CHAR TO TX MODEM LD A,1 LD (TX_STARTED),A ;;; AND, YES VIRGINA, WE'VE STARTED TX LD A,0C0H OUT (A_CTL),A ;;; RESET TX UNDERRUN/EOM LATCH POP HL POP DE POP BC POP AF EI RET ;----------------------------------------------------------------------------- R_SLOTTIME: ;WHEN SLOTTIME EVENT TIMER EXPIRES, COME HERE. RET ; WE WERE JUST WAITING, SO NOTHING ; ELSE TO DO HERE (!) ;----------------------------------------------------------------------------- R_TAIL: ;WHEN TAIL TIMER TIMES OUT, TURN OFF THE TX PUSH AF LD A,5 ;READY TO WRITE TO WR5 OF CH A DI ;;;MUST HAVE ATOMIC USE OF A_WR5 & SIO OUT (A_CTL),A ;;;NEXT CHAR TO A_CTL GOES TO WR5 LD A,(A_WR5) ;;;GRAB A_WR5 AND 0FFH-RTS ;;;TURN OFF RTS BIT THERE LD (A_WR5),A ;;;KEEP MEMORY COPY UPDATED OUT (A_CTL),A ;;;AND TURN OFF TX NOW EI POP AF RET ; INCLUDE IA.MAC ;MODEM INTERRUPT CATCHERS ;;;--------------------------------------------------------------------------- IA_TBE: PUSH AF PUSH HL LD A,(TX_STARTED) OR A JP Z,IA_T2 ;;; PREVIOUS FRAME FINISHED LD HL,(TX_CHAIN_HEAD) CALL GETCHAR LD (TX_CHAIN_HEAD),HL ;;; MUST KEEP THIS POINTER UPDATED JR Z,IA_T1 ;;; NO MORE TO SEND OUT (A_DAT),A ;;; ELSE SHIP THIS CHAR OUT IA_T9: POP HL POP AF EI RETI ;;; JUST RETURN FROM THESE INTERRUPTS IA_T1: ; HALT ;;;IF IT GETS HERE, HALT XOR A LD (TX_STARTED),A ;;; TX IS NOT STARTED LD HL,TX_OUTSTANDING ;;; MAKE IS SO THAT ONE FEWER FRAMES ;;; NOT "(TX_OUTSTANDING)" (!) 29 SEP DEC (HL) ;;; ARE OUTSTANDING LD A,28H OUT (A_CTL),A ;;; RESET TX INTERRUPT PENDING JP IA_T9 ;;;PREVIOUS FRAME IS DONE, SIO NOW SENDING A FLAG. MORE? IA_T2: LD A,(TX_OUTSTANDING) OR A JP NZ,IA_T21 ;;;IF MORE TO SEND, GO THERE ;;; ELSE WE'RE DONE HERE, CLEAN UP. LD A,28H OUT (A_CTL),A ;;; RESET TX INTERRUPT PENDING ;START TAIL TIMER EVENT LD A,(TAILTIME) ;;; { BUG FOUND 30 SEP. IT WAS: LD H,0 ;;; "LD HL,(TAILTIME)" LD L,A ;;; [OUCH!] } LD (TXQT_TAIL),HL ;;; WAIT FOR CRC TO CLEAR TX LD A,1 ;;; 8.33 MS/CHAR AT 1200 BPS LD (TXQE_TAIL),A ;;; TAILTIME VALUE SHOULD BE >=2. JP IA_T9 IA_T21: ;START UP NEXT FRAME CALL TXNEXT_CBUF ;;; GET THE NEXT BUFFER CHAIN POINTER ;;; SETUP HL AND TX_CHAIN_HEAD LD A,80H OUT (A_CTL),A ;;; RESET TX CRC GENERATOR CALL GETCHAR OUT (A_DAT),A ;;;GET 1ST CHAR OF NEXT FRAME LD A,1 LD (TX_STARTED),A ;;; TX STARTED AGAIN LD A,0C0H OUT (A_CTL),A ;;; RESET TX UNDERRUN/EOM LATCH JP IA_T9 ;;;--------------------------------------------------------------------------- ;;; GOT A CHARACTER FROM THE SIO RX INTERRUPT, DEAL WITH IT ;;; EXTENSIVE MODS 3 FEB 87 TO BE IN LINE WITH WHAT I NOW KNOW ABOUT SIO... IA_RCA: PUSH AF PUSH HL LD A,(RX_ALLOCATED_BUFFER) OR A JP NZ,IA_RC7 ;;; GO THERE IF WE ARE IN "RECEIVING" STATE ;ELSE WE ARE NOT YET RECEIVING, SO ALLOCATE BUFFER & MAKE US "RECEIVING" CALL ALLOCATE_BUFFER ;;; GET A NEW BUFFER JP Z,RESTART ; BPQ IA_RC5 ;;; NO ROOM, FLUSH THIS FRAME ;;; IF GOT A BUFFER, INSERT THIS CHARACTER. ;;; AFTER DOING INITIAL BUFFER SETUP. IA_RC6: LD (RX_HEAD),HL ;;; SAVE CHAIN HEAD ADDRESS (1ST BUFFER) LD (RX_BUF),HL ;;; TUCK AWAY ADDR OF OUR CURRENT BUFFER LD A,TRUE LD (RX_ALLOCATED_BUFFER),A ;;; AND MARK THAT WE ARE RECEIVING XOR A CALL PUTCHAR ;;; SLIP' FRAME "TYPE" FIELD HERE (ALWAYS 0) IA_RC7: LD HL,(RX_BUF) ;;; LOAD UP ADDRESS OF OUR CURRENT RX BUFFER IN A,(A_DAT) ;;; GRAB THE PENDING CHARACTER CALL PUTCHAR ;;; AND STUFF IN THIS PARTICULAR BUFFER LD (RX_BUF),HL ;;; HL MIGHT HAVE CHANGED IN PUTCHAR() ;;;*** NOTE! THERE IS A PROBLEM HERE! IF PUTCHAR() HAS NO MORE ROOM, THEN ;;; WE NEED TO FLUSH ALL FRAMES SO FAR ACCUMULATED & GO INTO RX_FLUSHING ;;; STATE !!! 3 FEB 87 IA_RC9: POP HL POP AF EI RETI ;;; NOTHING ELSE TO DO HERE ;;; IF NO ROOM, FLUSH THIS FRAME (SIGH) ;IA_RC5: ; LD A,TRUE ; LD (RX_FLUSHING),A ;;; WE ARE IN THE MIDST OF FLUSHING THIS FRAME IA_RC2: ; CALL STA_ON ;;;DDD NOTE THAT WE ARE IN FLUSHING STATE ; IN A,(A_DAT) ; IN A,(A_DAT) ; IN A,(A_DAT) ; IN A,(A_DAT) ;;; EMPTY SIO SILO ; ; JP IA_RC9 ;;;--------------------------------------------------------------------------- ;;; FROM OUT POINT OF VIEW, THIS INTERRUPT IS ONLY INTERESTING BECAUSE IT ;;; TELLS US IF WE'RE AT END OF FRAME. IA_SPECIAL: PUSH AF PUSH HL ;;; REGS WE'LL NEED LD A,1 OUT (A_CTL),A ;;; READY TO READ RR1 IN A,(A_CTL) ;;; OK, GRAB RR1 ;;; FIRST CHECK IF RX OVERRUN. THIS IS VERY BAD, SO HALT. BIT 5,A JP Z,IA_SP0 ;;; MOST OF THE TIME (ALL THE TIME?) GO THERE CALL CON_ON CALL STA_ON JP RESTART ; BPQ IA_SP0: BIT 7,A ;;; CHECK STATE OF END OF FRAME BIT JP Z,IA_SP8 ;;; ELSE SOMETHING WEIRD HAPPENED - PROBABLY ;;; RX OVERRUN. IN ANY CASE, FLUSH THIS FRAME. ;;; ERROR RESET & THEN EXIT ;;; THAT IS, TREAT LIKE IT WAS A CRC ERROR ;;; IF END OF FRAME, CHECK CRC BIT FOR VALID. IA_SP1: BIT 6,A ;;; CHECK CRC ERROR BIT JP NZ,IA_SP8 ;;; IF CRC ERROR BIT IS ON, THEN WAS CRC ERROR ;;; FIRST ENSURE THAT WE INDEED HAVE A BUFFER ALLOCATED... LD A,(RX_ALLOCATED_BUFFER) OR A JP Z,IA_SP9 ;;; IF NO BUFFER ALLOCATED, IGNORE THIS. ;;; ELSE THIS WAS A GOOD FRAME, AND WE SHOULD SHIP IT OUT TO HOST ;;; LEAVE THE FIRST CRC CHARACTER AT END OF BUFFER CHAIN IN THE BUFFER, AS ;;; GETCHAR() WILL FLUSH IT. LD HL,(RX_HEAD) CALL OUT_QUEUE_INSERT ;;; SHOVE THIS BUFFER STRING ONTO ;;; OUTPUT QUEUE XOR A LD (RX_ALLOCATED_BUFFER),A ;;; WE DON'T HAVE A BUFFER ALLOCATED ;;; FOR THE NEXT FRAME... JP IA_SP9 ;;; GET HERE IF THERE WAS A BAD CRC IA_SP8: LD A,(RX_ALLOCATED_BUFFER) ;;; IF WE DON'T HAVE ANY BUFFERS ;;; ALLOCATED, THEN OR A ;;;8 FEB - SET CONDITION CODES !!!!!! JP Z,IA_SP9 ;;; WE MUST NOT "RELEASE" THEM !!! 10 SEP 86 ;;; IF THEY ARE NOT ALLOCATED !!! IA_SPF: XOR A LD (RX_ALLOCATED_BUFFER),A ;;; NOT RECEIVING IF WE HAVE BAD CRC LD HL,(RX_HEAD) CALL FREE_CHAIN ;;; FREE UP ALL BUFFER(S) IA_SP9: LD A,30H ;;; ERROR RESET OUT (A_CTL),A IN A,(A_DAT) ;;; AVOID SPURIOUS RCA INTERRUPT ; IN A,(A_DAT) ;;; AVOID SPURIOUS RCA INTERRUPT ; IN A,(A_DAT) ;;; AVOID SPURIOUS RCA INTERRUPT ; IN A,(A_DAT) ;;; AVOID SPURIOUS RCA INTERRUPT ; ;;; AND FLUSH SILO POP HL POP AF EI RETI ;;;--------------------------------------------------------------------------- ;;; FOR EXT/STATUS INTERRUPTS ON MODEM, GET DCD STATE INTO MEMORY, AND ;;; DEALLOCATE ANY SPURIOUS BUFFERS (BUFFER STUFF DONE 30 SEP 86). IA_EXT: PUSH AF LD A,10H ;;; RESET EXT/STATUS INTERRUPTS OUT (A_CTL),A IN A,(A_CTL) ;;; GRAB RR0 AND DCD LD (DCD_STATE),A ;;;SAVE FOR TX KEYUP DCD DETECT. IS 0 IF DCD ;;;IS NOT ACTIVE, OR NON-ZERO IF IT IS ACTIVE. ; LD A,(RX_ALLOCATED_BUFFER) ;;; IF WE ARE NOT IN THE ; ;;; RECEIVING STATE... OR A ;;; THEN THERE ARE NO ALLOCATED BUFFERS AND... JP Z,IA_EX9 ;;; WE MUST NOT "RELEASE" THEM !!! 10 SEP 86 ; ;;; IF NO BUFFERS ALLOCATED !!! XOR A LD (RX_ALLOCATED_BUFFER),A ;;; NOT RECEIVING PUSH HL LD HL,(RX_HEAD) CALL FREE_CHAIN ;;; FREE UP ALL BUFFER(S) POP HL ; ; WE HAVE JUST JUNKED THE FIRST PART OF THE FRAME, BUT THE RECEIVER ; MAY WELL STILL BE RUNNING (IF DCD HAS DROPPED WITHOUT LOSS OF ; DATA, WHICH CAN HAPPEN WITH STATE MACHINE DCD. IT SEEMS A GOOD ; TO RE-ENTER HUNT MODE ; LD A,33H ; ERROR RESET, WR3 OUT (A_CTL),A LD A,0D9H ; ENTER HUNT OUT (A_CTL),A ; IA_EX9: POP AF EI RETI ; INCLUDE IB.MAC ;TTY INTERRUPT CATCHERS ;;;--------------------------------------------------------------------------- ;;; WE GET HERE WHENEVER -CTS, -DCD OR -SYNC INPUTS CHANGE, AS WELL AS BREAK ;;; DETECTION. SINCE -DCD ;;; IS ALWAYS TIED TO +5 VOLTS, WE NEED ONLY WORRY ABOUT -CTS AND -SYNC. ;;; -CTS IS WIRED TO PIN 20, DTR, OF THE RS232 CONNECTOR, AND IS SUPPOSED TO ;;; BE USED FOR HOST TO TNC HANDSHAKING; WE IGNORE THIS TRANSITION (WE ASSUME ;;; THAT THE HOST IS ALWAYS READY). WE ALSO IGNORE BREAK DETECTION. WE ARE ;;; ONLY INTERESTED IN -SYNC TRANSITIONS, SO WE CAN KEEP TIME. ;;; NOTE! THIS IS THE ONLY ROUTINE THAT IS ALLOWED TO USE THE OTHER REG SET!! ;;; DEAL WITH BREAK DETECTION... SYNC_HUNT EQU 10H IB_EXT: EX AF,AF' EXX ;;; WE WANT THE OTHER REGISTERS LD A,10H OUT (B_CTL),A ;;; RESET EXT/STATUS INTERRUPTS IN A,(B_CTL) ;;; GRAB RR0 LD D,A ;;; HOLD IT FOR A MOMENT... AND SYNC_HUNT ;;; ISOLATE THIS BIT JP Z,IB_S0 ;ELSE SYNC/HUNT IS A 1 LD A,C OR A JP Z,IB_S1 ;;; GO HERE IF STATE OF SYNC/HUNT CHANGED ;;; HERE IF SYNC/HUNT BIT DID NOT CHANGE - MAYBE SOMETHING ELSE DID.... IB_S9: LD A,D ;;; RETREIVE RRO FROM ABOVE AND BREAK_ABORT ;;; CHECK IF WE ARE DOING A BREAK/ABORT THING JP Z,IB_NBA ;;; THERE IF NO BREAK/ABORT ;;; ELSE BREAK/ABORT BIT ON, NOTE STATE CHANGE... LD A,1 LD (IN_BREAK),A ;;; SAVE IN MEM (PROBABLY CAN USE E REG...) IN A,(B_DAT) ;;; CLEAR OUT ANY NULL CHARACTER FROM BUFFER JP IB_BOK ;;; BREAK OK FOR NOW... IB_NBA: ;;;IF NO BREAK/ABORT, CHECK IF WE ARE IN BREAK/ABORT STATE. LD A,(IN_BREAK) OR A JP Z,IB_BOK ;;; NOTHING GOING ON, BREAK OK ;;; ELSE WE WERE IN BREAK MODE, AND THIS IS THE TAIL END OF A BREAK. XOR A LD (IN_BREAK),A IN A,(B_DAT) ;;; DISCARD THE SINGLE EXTRANEOUS NULL IB_BOK: IB_S99: EX AF,AF' EXX EI RETI ;;; ELSE SOMETHING ELSE & WE DON'T CARE IB_S0: ;;; SYNC/HUNT IS A 0 LD A,C OR A JP NZ,IB_S1A ;;; GO HERE IF SYNC/HUNT CHANGED JP IB_S9 ;;; ELSE NOT INTERESTED, FORGET IT ;GET HERE IF STATE OF SYNC/HUNT CHANGED IB_S1: LD C,1 JP IB_S1B IB_S1A: ;;; FIRST FIX UP C FOR NEXT TICK LD C,0 IB_S1B: ;;; HERE WHEN WE'VE SEEN A REAL "CLOCK TICK" & DEALT WITH C REG INC B LD A,B CP 12 JP NZ,IB_S99 ;;; WE ACT ON EVERY 12TH CLOCK TICK... LD B,0 ;;; SO RELOAD DIVISOR. THIS GIVE US AN ;;; EFFECTIVE INTERRUPT RATE OF 100 HZ ;;; DECREMENT ALL THE TIMERS LD HL,(TXQ_TIMERS) ;;; GET FIRST TIMER VALUE, AND ... DEC HL ;;; ... DECREMENT IT AS REQUIRED. LD (TXQ_TIMERS),HL LD HL,(TXQ_TIMERS+2) ;;; GET SECOND TIMER VALUE, AND ... DEC HL ;;; ... DECREMENT IT AS REQUIRED. LD (TXQ_TIMERS+2),HL LD HL,(TXQ_TIMERS+4) ;;; GET THIRD TIMER VALUE, AND ... DEC HL ;;; ... DECREMENT IT AS REQUIRED. LD (TXQ_TIMERS+4),HL LD HL,(TXQ_TIMERS+6) ;;; GET THIRD TIMER VALUE, AND ... DEC HL ;;; ... DECREMENT IT AS REQUIRED. LD (TXQ_TIMERS+6),HL JP IB_S99 ;;;--------------------------------------------------------------------------- IB_SPECIAL: PUSH AF IB_SP9: ;;; NORMAL EXIT LD A,30H ;;; ERROR RESET OUT (B_CTL),A POP AF EI RETI ;;;--------------------------------------------------------------------------- ;;; THE TX HAS BECOME EMPTY, SHOVE A NEW CHARACTER OUT IB_TBE: PUSH AF ;;; NEW CHAR WILL RETURN IN A PUSH HL LD A,(IB_ESC_MODE) OR A JP Z,IB_T1 ;;; NOT ESCAPED, SO GO HERE ;;; ELSE WE ARE ESCAPED, SO SEND ESCAPED CHAR LD A,(IB_CHAR) ;;; CHAR WHICH FOLLOWS ESCAPE OR A JP Z,IB_T2 ;;; SPECIAL CASE IF AT END OF FRAME, CLEAN UP OUT (B_DAT),A XOR A LD (IB_ESC_MODE),A ;;; GET OUT OF ESCAPED MODE JP IB_T9 ;;; ALL FOR NOW... IB_T1: LD HL,(OUT_CHAIN_HEAD) ;;; WE ARE CURRENTLY ON THIS BUFFER, AS... CALL GETCHAR ;;; GETCHAR() NEEDS TO KNOW LD (OUT_CHAIN_HEAD),HL ;;; MAYBE HL CHANGED, SO SAVE IT IN CASE JP Z,IB_TDONE ;;; IF NO MORE CHARS, DEAL WITH THIS CP FESC JP Z,IB_T1A ;;; DEAL WITH FESC CHAR IN DATA STREAM CP FEND JP Z,IB_T1B ;;; DEAL WITH FEND CHAR IN DATA STREAM ;;; ELSE THIS CHAR IS NOTHING SPECIAL, SO SHOVE IT OUT OUT (B_DAT),A ;;; SHOVE IT OUT JP IB_T9 ;;; IF THIS IS NOT LAST CHAR, ALL FOR NOW ;;; ELSE THIS IS LAST CHAR, SEND FEND IB_TDONE: LD A,FEND OUT (B_DAT),A LD A,1 LD (IB_ESC_MODE),A ;;; SET SPECIAL ESCAPED MODE BY... XOR A LD (IB_CHAR),A ;;;... MAKING ESCAPED CHAR A 0 JP IB_T9 ;;; ALL TILL TX BUFFER GOES EMPTY AGAIN. ; HERE IF ARE COMPLETELY DONE SENDING FRAME IB_T2: PUSH DE ;;; NEED THIS FOR A MOMENT LD HL,(OUT_HEAD_CBUF) INC HL INC HL LD DE,OUT_BOTTOM OR A PUSH HL SBC HL,DE POP HL ;;; THIS MAY BE THE ONE WE WANT POP DE JP NZ,IB_T2A ;;; YES IT IS! LD HL,OUT_TOP ;;; ELSE, MAKE A CIRCULAR BUFFER IB_T2A: LD (OUT_HEAD_CBUF),HL ;;; WE WILL WORK ON THIS ONE NEXT XOR A LD (OUT_STARTED),A ;;; NOT DOING OUTPUTS ANYMORE LD (IB_ESC_MODE),A ;;; !!! NOT IN ESCAPED MODE ANYMORE !!! LD A,28H ;;; NEEDED FOR ASYNC OUT (B_CTL),A ;;; RESET TX INTERRUPT PENDING IB_T9: POP HL POP AF EI RETI ;;; NOW GET OUR BUTTS OUT OF HERE... ;;; HERE IS FESC IN DATA STREAM IB_T1A: OUT (B_DAT),A ;;; SHIP FESC CHARACTER TO PORT LD A,TFESC ;;; READY WHAT WILL BE NEXT CHAR IB_T1Z: LD (IB_CHAR),A ;;; SET CHAR FOR NEXT TIME LD A,1 LD (IB_ESC_MODE),A ;;; WE ARE IN ESCAPED MODE JP IB_T9 ;;; ALL FOR NOW ;;; HERE IS FEND IN DATA STREAM IB_T1B: LD A,FESC OUT (B_DAT),A LD A,TFEND JP IB_T1Z ;;; REST IS SAME AS FESC CASE ;;;--------------------------------------------------------------------------- ;;; GOT A CHAR FROM THE TTY PORT, DEAL WITH IT. IB_RCA: PUSH AF IN A,(B_CTL) ;;; READ RR0; FORCE REG POINTER TO BE 0 LD A,1 OUT (B_CTL),A ;;; READY TO READ RR1 IN A,(B_CTL) ;;; GRAB RR1 AND FRAMING_ERROR ;;; ISOLATE THE FE BIT JP Z,IB_RTOP ;;; NO FRAMING ERROR, SO PROCESS THIS CHAR ;;; ELSE WE HAVE A FRAMING ERROR - IGNORE THIS CHAR & FLUSH THIS FRAME... CALL STA_OFF ;;; OFF WITH THE LED! IN A,(B_DAT) ;;; FLUSH ERRONEOUS CHARACTER XOR A LD (IN_STATE),A ;;; FORCE RECEIVER TO LOOK FOR FEND LD A,(IN_ALLOCATED_BUFFER) OR A JP Z,IB_RC9 ;;; IF NO BUFFER IS ALLOCATED, DONE; EXIT. ;;; ELSE WE WERE RECEIVING A DATA SLIP FRAME, SO FLUSH IT. PUSH HL LD HL,(IN_HEAD) CALL FREE_CHAIN ;;; DUMP THESE BUFFERS BACK TO FREE LIST POP HL JP IB_RC9 ;;; AND GET OUT OF HERE! IB_RTOP: LD A,(IN_STATE) ;;; GET OUR STATE MACHINE VALUE OR A JR Z,IB_R0 ;;; IN STATE 0, WAITING FOR FEND CP 1 JR Z,IB_R1 ;;; IN STATE 1, SAW FEND CP 2 JP Z,IB_R2 ;;; IN STATE 2, DATA TO FOLLOW CP 3 JP Z,IB_R3 ;;; SAW FESC, EXPECTING TFESC OR TFEND CP 10 JP Z,IB_R10 ;;; EXPECTING TXDELAY CP 20 JP Z,IB_R20 ;;; EXPECTING P VALUE CP 30 JP Z,IB_R30 ;;; EXPECTING SLOTTIME VALUE CP 40 JP Z,IB_R40 ;;; EXPECTING TAILTIME VALUE CP 50 JP Z,IB_R50 ;;; EXPECTING FULL/HALF DUPLEX VALUE ;ELSE WE DON'T KNOW WHAT HAPPENED, IGNORE IT. IB_RCJUNK: IN A,(B_DAT) XOR A LD (IN_STATE),A ;;;GO INTO IN_STATE 0, FEND HUNT IB_RC9: POP AF ;;; THROW IT AWAY, WE DON'T NEED JUNK EI RETI ;;; HERE IF WE ARE HUNTING FOR FEND CHARACTER IB_R0: CALL STA_OFF IN A,(B_DAT) CP FEND JP NZ,IB_RC9 ;;; IF WE DIDN'T SEE AN FEND, KEEP LOOKING ;;; ELSE IS AN FEND, CHANGE STATE LD A,1 LD (IN_STATE),A JP IB_RC9 ;;; GET HERE IF WE'VE SEEN FEND CHARACTER; LOOK FOR COMMAND BYTE IB_R1: CALL STA_OFF IN A,(B_DAT) CP FEND JP Z,IB_RC9 ;;; JUST ANOTHER FEND, KEEP LOOKING FOR CMD CALL STA_ON ;;;GETTING VALID SLIP; SHOW IN STA LED ;;; HERE IF WE DO NOT HAVE AN FEND (EXPECTING COMMAND BYTE) OR A JP Z,IB_R1A ;;; 0 COMMAND MEANS DATA WILL FOLLOW CP 1 JP Z,IB_R1B ;;; 1 COMMAND MEANS TXDELAY WILL FOLLOW CP 2 JP Z,IB_R1C ;;; 2 COMMAND MEANS P(PERSISTENCE) WILL FOLLOW CP 3 JP Z,IB_R1D ;;; 3 COMMAND MEANS SLOT TIME WILL FOLLOW CP 4 JP Z,IB_R1E ;;; 4 COMMAND MEANS TAILTIME TO FOLLOW CP 5 JP Z,IB_R1F ;;; 5 COMMAND MEANS FULL/HALF DUPLEX TO COME ;;; HERE IF WE RECEIVE BOGUS COMMAND BYTE, FLUSH REST OF FRAME CALL STA_OFF ;;;BOGOSITY, SO TURN OFF STA LED XOR A LD (IN_STATE),A ;;; GO TO STATE WHICH LOOKS FOR FEND JP IB_RC9 ;;; DATA ARE EXPECTED, CHANGE STATE IB_R1A: LD A,2 LD (IN_STATE),A JP IB_RC9 ;;; TXDELAY TO FOLLOW, CHANGE STATE IB_R1B: LD A,10 LD (IN_STATE),A JP IB_RC9 ;;; P TO FOLLOW, CHANGE STATE IB_R1C: LD A,20 LD (IN_STATE),A JP IB_RC9 ;;; SLOTTIME TO FOLLOW, CHANGE STATE IB_R1D: LD A,30 LD (IN_STATE),A JP IB_RC9 ;;; TAILTIME TO FOLLOW, CHANGE STATE IB_R1E: LD A,40 LD (IN_STATE),A JP IB_RC9 ;;; FULL/HALF DUPLEX TO FOLLOW, CHANGE STATE IB_R1F: LD A,50 LD (IN_STATE),A JP IB_RC9 ;;; THESE BYTES ARE DATA IB_R2: IN A,(B_DAT) CP FEND JR Z,IB_R2B ;;; FEND MEANS TO QUEUE THIS BUFFER PUSH AF ;;; SAVE THE CHAR WE READ ON STACK FOR A BIT.. LD A,(IN_ALLOCATED_BUFFER) OR A JP NZ,IB_R2C ;;; IF WE ALREADY ALLOCATED BUFFER PUSH HL CALL ALLOCATE_BUFFER ;;; GET OUR INITIAL BUFFER TO MESS WITH JP NZ,IB_R22 ;;;ELSE NO ROOM, FLUSH THIS FRAME POP HL ;;; KEEP STACK TIDY XOR A LD (IN_STATE),A JP IB_RC9 IB_R22: LD A,1 LD (IN_ALLOCATED_BUFFER),A ;;; MAKE OURSELVES ACTIVE LD (IN_BUFFER),HL LD (IN_HEAD),HL ;;; SAVE CURRENT & HEAD OF CHAIN POINTERS POP HL IB_R2C: POP AF ;;; RETREIVE THE DATA CHAR WE JUST GOT... CP FESC JR Z,IB_R2A ;;; IF FESC IN DATA STREAM, SWITCH STATE PUSH HL LD HL,(IN_BUFFER) CALL PUTCHAR ;;; SHOVE THIS CHARACTER INTO OUR BUFFER LD (IN_BUFFER),HL ;;; SAVE IN CASE HL CHANGED POP HL JP IB_RC9 ;;; DONE SO FAR ;;; FESC CHARACTER SEEN WHILE GRABBING DATA IB_R2A: LD A,3 LD (IN_STATE),A ;;; GO TO THIS OTHER STATE JP IB_RC9 ;;; FEND CHARACTER SEEN WHILE GRABBING DATA IB_R2B: LD A,(IN_ALLOCATED_BUFFER) OR A JR Z,IB_R2Z ;;; NO BYTES ACCUMULATED, SO IS NULL FRAME ;;; ELSE WE MUST SHIP THIS FRAME TO TX PUSH HL ;;; THIS BUG FOUND 29 SEP (MUST SAVE HL !!!) LD HL,(IN_BUFFER) CALL PUTCHAR ;;; PUT A GARBAGE CHARACTER AT THE END OF ;;; LAST BUFFER BECAUSE GETCHAR() WILL STRIP ;;; IT. HACK NEEDED BECAUSE OF RX USE OF ;;; PUTCHAR/GETCHAR. LD HL,(IN_HEAD) CALL TX_QUEUE_INSERT POP HL XOR A LD (IN_ALLOCATED_BUFFER),A ;;; INPUT NO LONGER ACTIVE IB_R2Z: ;;; ENTRY POINT FOR NULL FRAME LD A,1 ;;; KEEP AS WAS, FENDS ONLY AT END IN V.32 LD (IN_STATE),A ;;; GO LOOK FOR ANOTHER FRAME CALL STA_OFF ;;;DONE GETTING THIS FRAME, TURN STA LED OFF JP IB_RC9 ;;; HERE IF WE'VE SEEN FESC IN DATA STREAM IB_R3: IN A,(B_DAT) CP TFESC JR Z,IB_R3A CP TFEND JR Z,IB_R3B ;;; ELSE WE DON'T KNOW WHAT THE HELL IT IS, SO IGNORE & KEEP COLLECTING BYTES LD A,2 LD (IN_STATE),A ;;; GO BACK INTO "DATA RECEIVING" STATE JP IB_RC9 ;;; HERE IF WE'VE SEEN TFESC AFTER AN FESC IN DATA STREAM; WRITE AN FESC IB_R3A: LD A,FESC IB_R3Z: PUSH HL LD HL,(IN_BUFFER) CALL PUTCHAR LD (IN_BUFFER),HL POP HL LD A,2 LD (IN_STATE),A ;;; GET OUT OF ESCAPED MODE JP IB_RC9 ;;; HERE IF WE'VE SEEN TFEND AFTER FESC IN DATA STREAM; WRITE FEND IB_R3B: LD A,FEND JP IB_R3Z ;;; REST IS SAME AS FOR TFESC CASE ;;; THIS CHARACTER IS INTERPRETED AS TXDELAY IB_R10: IN A,(B_DAT) LD (TXDELAY),A XOR A LD (IN_STATE),A ;;; GO BACK TO FEND HUNT STATE JP IB_RC9 ;;; THIS CHARCTER IS P, PERSISTENCE VALUE IB_R20: IN A,(B_DAT) LD (PERSISTENCE),A XOR A LD (IN_STATE),A ;;; GO BACK TO FEND HUNT STATE JP IB_RC9 ;;; THIS CHARACTER IS SLOTTIME VALUE IB_R30: IN A,(B_DAT) LD (SLOTTIME),A XOR A LD (IN_STATE),A ;;; GO BACK TO FEND HUNT STATE JP IB_RC9 ;;; THIS CHARACTER IS TAILTIME VALUE IB_R40: IN A,(B_DAT) LD (TAILTIME),A XOR A LD (IN_STATE),A ;;; GO BACK TO FEND HUNT STATE JP IB_RC9 ;;; THIS CHARACTER IS FULL/HALF DUPLEX VALUE ;;; 0 MEANS HALF DUPLEX, NON-ZERO MEANS FULL DUPLEX IB_R50: IN A,(B_DAT) LD (FULL_DUPLEX),A XOR A LD (IN_STATE),A ;;; GO BACK TO FEND HUNT STATE JP IB_RC9 ; INCLUDE BUFFERS.MAC ;ALL BUFFER-RELATED STUFF IN HERE ;PLUS ALL (EVENTUALLY) VARIABLES ; ; THE BUFFER LIST IS KEPT FROM "BOTTOM" TO THE END OF RAM. THE FORMAT OF THE ; BUFFERS IS: ;+------+--------+-------+---------------------------------------------------+ ;| NEXT | NBYTES | NREAD | DATA | ;+------+--------+-------+---------------------------------------------------+ ; ; 2 BYTES 1 BYTE 1 BYTE 124 BYTES (TOTAL 128 BYTES) ; NEXT POINTER TO NEXT BUFFER ON THIS BUFFER CHAIN (OR 0 IF NO MORE) ; NBYTES NUMBER OF BYTES IN THIS BUFFER THAT ARE VALID ; NREAD NUMBER OF BYTES READ FROM THIS BUFFER (USED BY GETCHAR) ; DATA 124 BYTES OF DATA (NOT ALL IS NECESSARILY VALID, SEE NBYTES FIELD) ; ; THE BUFFER POOL IS ALL HERE, AND AS PROCESSES NEED BUFFER SPACE, IT IS ALL ; ALLOCATED OUT OF THIS POOL. SEE ALLOCATE_BUFFER AND FREE_BUFFER CODE. ;;;--------------------------------------------------------------------------- ;;; RETURN IN HL A POINTER TO A FREE BUFFER. IF THERE ARE NOT MORE BUFFERS, ;;; RETURN WITH Z FLAG SET. ;;; DESTROYS NO REGISTERS EXCEPT RETURN VALUE HL. ;;; IS CALLED FROM AN INTERRUPT ROUTINE, SO THIS OPERATION IS ATOMIC. ALLOCATE_BUFFER: PUSH BC PUSH AF LD HL,(FREE) ;;;GET POINTER TO HEAD OF FREE LIST LD A,H OR L JP NZ,OK_ALLOCATE_BUFFER ;;; ASSURE WE'RE NOT OFF THE END ;GET HERE IF NO MORE BUFFERS. RETURN Z SET - DO NOT DISTURB A. POP AF LD B,A ;;; TUCK A AWAY FOR A MOMENT... XOR A ;;; TURN ON Z BIT LD A,B ;;; RETREIVE ORIGINAL A POP BC RET OK_ALLOCATE_BUFFER: XOR A LD C,(HL) ;;;GRAB LO BYTE OF NEXT FREE BUFFER LD (HL),A ;;; CLEAR IT OUT INC HL LD B,(HL) ;;; "LD BC,(HL)" NOW HI BYTE LD (HL),A ;;; CLEAR IT OUT, TOO LD (FREE),BC ;;; UPDATE WITH NEW FREE LIST POINTER DEC HL ;;; NOW HL IS AT HEAD OF NEW BUFFER POP AF LD B,A ;;; TUCK A AWAY FOR A MOMENT... LD A,1 OR A ;;; TURN Z BIT OFF (I.E., ALL OK) LD A,B ;;; RETREIVE ORIGINAL A POP BC RET ;;;--------------------------------------------------------------------------- ;;; FREE_BUFFER GETS PASSED A POINTER (IN HL) TO A BUFFER TO BE FREED. THE ;;; BUFFER IS PLACED ON THE HEAD OF THE FREE LIST. THE NBYTES & NREAD FIELDS ;;; ARE MADE 0 BEFORE PLACING ON FREE LIST. ;;; THIS ROUTINE IS CALLED AT INTERRUPT LEVEL, SO RESULTS ARE ATOMIC. ;;; NO REGISTERS ARE DISTURBED AT ALL. THE FREE POINTER IS UPDATED, HOWEVER. ;;; 159 T STATES [ 63.6 USEC @ 2.5 MHZ ] FREE_BUFFER: PUSH AF PUSH BC ;;;WE'LL USE THESE PUSH HL ;;;THIS WILL BE NEW HEAD OF FREE LIST LD BC,(FREE) ;;;GET OLD FREE HEAD LD (HL),C ;;;PUT ON FREE CHAIN, FIRST LOW BYTE... INC HL LD (HL),B ;;; ...NOW HI BYTE XOR A INC HL LD (HL),A ;;; ZERO OUT NBYTES FIELD INC HL LD (HL),A ;;; AND THE NREAD FIELD OF NEW HEAD OF FREE POP HL ;;;GET NEW HEAD OF FREE LIST BACK LD (FREE),HL ;;;AND SAVE IT IN MEMORY WHERE IT BELONGS POP BC POP AF RET ;;; -------------------------------------------------------------------------- ;;; PUTCHAR - HL CONTAINS POINTER TO BUFFER, A CONTAINS THE CHARACTER TO PUT ;;; INTO THE BUFFER. UPON RETURN, CHAR IS PUT INTO THIS BUFFER IF THER IS ;;; ROOM, ELSE ANOTHER BUFFER IS ALLOCATED AND HL IS UPDATED TO POINT TO THIS ;;; NEW BUFFER. THE NEW BUFFER IS CHAINED ONTO THE OLD BUFFER IN THIS CASE. ;;; THE CALLING ROUTINE IS RESPONSIBLE FOR MAINTAING BOTH THE HEAD OF A ;;; PARTICULAR BUFFER CHAIN (IF IT NEEDS IT), AND THE CURRENT BUFFER BEING ;;; MANIPULATED. THIS ROUTINE IS CALLED AT INTERRUPT LEVEL, SO IS ATOMIC. NO ;;; REGISTERS DISTURBED, EXCEPT THAT HL MAY HAVE A NEW VALUE. ;;; 211 T STATES [ 84.4 USEC @ 2.5 MHZ ] NO NEW BUFFER REQUIRED ;;; 338 T STATES [ 135.2 USEC @ 2.5 MHZ ] NEW BUFFER NEEDED PUTCHAR: PUSH BC PUSH IX PUSH AF PUSH HL ;;;DO IT THIS WAY FOR A REASON... POP IX ;;;GET BUFFER POINTER INTO IX LD A,(IX+2) ;;;GRAB NBYTES FIELD CP 124 ;;;MAX OF 124 CHARS IN A BUFFER CALL Z,PUTC_NEED_NEW_BUFFER ;;; IF IT TAKES THIS CALL, IT RETURNS WITH A NEW BUFFER, WITH HL POINTING TO ;;; IT (AS WELL AS IX), AND WITH A REG SET TO 0. ;;; ELSE JUST PLUNK INTO BUFFER INC (IX+2) ;;;ONE MORE CHAR WILL GO INTO THIS BUFFER LD C,A ;;;GET PREVIOUS NBYTES XOR A LD B,A ;;; BC <- NBYTES, FILLED OUT TO 16 BITS ADD IX,BC ;;; UPDATE IX TO POINT TO WHERE CHAR GOES POP AF ;;; RETREIVE THE CHAR WE WANT TO SAVE LD (IX+4),A ;;; SAVE IT IN THIS BUFFER POP IX POP BC RET ;;;DONE FOR THE MOMENT ;;; 127 T STATES [ 50.8 USEC @ 2.5 MHZ ] (REALLY PART OF PREV ROUTINE) PUTC_NEED_NEW_BUFFER: ;;;PREV BUFFER FILLED, GET A NEW ONE PUSH DE ;;; WORKING REGISTERS PUSH HL ;;; SAVE CURRENT BUFFER POINTER CALL ALLOCATE_BUFFER ;;; GRAB A NEW BUFFER, ADDR IS IN HL JP Z,RESTART ; NO BUFFERS - RESET WHOLE SYSTEM EX DE,HL ;;; "LD DE,HL" - GET NEW ADDR INTO DE FOR NOW POP HL LD (HL),E ;;; LINK NEW BUFFER ONTO CHAIN, LO BYTE FIRST INC HL LD (HL),D ;;; NOW HI BYTE, CHAINING DONE EX DE,HL ;;; UPDATE HL FOR ORIG. CALLING ROUTINE'S USE PUSH HL POP IX ;;; UPPER ROUTINE NEEDS IX POINTING TO NEW BUF XOR A ;;; AND A IS NBYTES IN CALLING ROUTINE, MAKE.. ;;; ZERO FOR A NEW BUFFER POP DE ;;; DONE WITH THIS WORKING REGISTER RET ;;; ALL DONE HERE, LET CALLING ROUTINE FINISH ;;; -------------------------------------------------------------------------- ;;; GETCHAR - GRAB A CHARACTER FROM THE BUFFER POINTED AT BY HL, RETURN IN A. ;;; IF THE "NREAD" FIELD OF THIS BUF = "NBYTES" THEN THIS BUFFER IS EXHAUSTED, ;;; SO FOLLOW THE CHAIN ON TO THE NEXT BUFFER & RELEASE OLD BUFFER. IF THE ;;; NEXT CHAIN IS 0, OR IF THE NBYTES FIELD IS >= NREAD FIELD, THEN THERE ARE ;;; NO MORE BYTES. IN THIS CASE, RETURN WITH Z BIT SET; NORMALLY RETURN WITH ;;; Z BIT RESET (THAT IS, NON-ZERO) INDICATING A VALID CHAR IS IN A. NOTE ;;; THAT IF WE NEED TO FOLLOW THE CHAIN TO A NEW BUFFER, HL WILL BE UPDATED, ;;; TOO, SO THAT THE CALLING ROUTINE NEEDS TO DEAL WITH THIS. ;;; NO REGISTERS CHANGED EXCEPT AF AND POSSIBLY HL. ;;; CALLED AT INTERRUPT LEVEL, SO OPERATION IS ATOMIC. ;;; 212 T STATES [ 84.8 USEC @ 2.5 MHZ ] NO NEW BUFFER NEEDED ;;; 493 T STATES [ 197.2 USEC @ 2.5 MHZ ] IF FOLLOWING CHAIN GETCHAR: PUSH IX ;;; SAVE BECAUSE IS WORKING REG PUSH BC ;;; WORKING REGS HERE PUSH HL POP IX ;;; IX POINTS TO THIS BUFFER LD A,(IX+3) ;;; GRAB NREAD CP (IX+2) ;;; COMPARE WITH NBYTES CALL Z,GETC_NEW_BUF ;;; IF THEY ARE SAME, THIS BUFFER IS SPENT INC (IX+3) ;;; WE ARE READING ONE MORE CHAR, UPDATE NREAD INC A CP (IX+2) JP NZ,GETC_PLUCK_CHARACTER ;;; IF NOT LOOKING AT LAST CHARACTER ;;; ELSE, IS THE "NEXT" POINTER 0? PUSH HL LD B,A ;;; !!!!! SAVE A REG !!!!!!! 4 JAN 87 LD A,(HL) INC HL OR (HL) LD A,B ;;; !!!! RESTORE A REG (GASP!) POP HL JR NZ,GETC_PLUCK_CHARACTER ;;; ELSE NEXT IS 0 AND WE ARE ON LAST CHAR - FLUSH IT & QUIT CALL FREE_BUFFER POP BC POP IX RET ;;; NOTE THAT Z BIT IS SET (FROM ABOVE) ;;; ELSE WE CAN JUST PLUCK A CHARACTER OUT OF THIS BUFFER GETC_PLUCK_CHARACTER: DEC A ;;; FIX A FROM ABOVE MUCKING AROUND... LD C,A ;;; GET OLD NREAD INTO BC LD B,0 ;;; DITTO ADD IX,BC ;;; FIX BUFFER POINTER LD A,1 OR A ;;; MAKE Z BIT RESET LD A,(IX+4) ;;; GET THE DESIRED BYTE POP BC POP IX RET ;;; ALL FOR THIS SIMPLE CASE ;;; OLD BUFFER IS SPENT, GET NEW ONE (IF ANY) GETC_NEW_BUF: PUSH DE ;;; NEED THIS REG HERE LD E,(HL) ;;; GET LO BYTE OF NEXT POINTER INC HL LD D,(HL) ;;; HI BYTE OF NEXT POINTER (NOW ALL IN DE) DEC HL ;;; HL NOW BACK TO POINT AT SPENT BUFFER CALL FREE_BUFFER ;;; GIVE THE BUFFER BACK EX DE,HL ;;; "LD HL,DE" - FOLLOW CHAIN PUSH HL POP IX ;;; INIT NEW IX (SAME AS HL IN THIS ROUTINE) XOR A ;;; A HOLDS NREAD (NEEDED ABOVE) POP DE ;;; RELEASE DE FROM USE BY THIS EXCURSION RET ;;; -------------------------------------------------------------------------- ;;; FREE_CHAIN - MUST BE CALLED FROM INTERRUPT ROUTINE TO GUARANTEE ;;; ATOMICITY. TAKES BUFFER CHAIN POINTED AT BY HL AND RETURNS THEM TO FREE ;;; BUFFER LIST ;;; 303 T STATES + (N_ON_CHAIN-1)*238 T STATES ;;; [ 121.2 USEC + (N_ON_CHAIN-1)*95.2 USEC ] FREE_CHAIN: PUSH AF PUSH DE PUSH HL ;;; WE WILL MUCK WITH THESE FC_0: LD E,(HL) ;;; GET LO PART OF NEXT BUFFER POINTER INC HL LD D,(HL) ;;; NOW HI PART OF NEXT BUFFER POINTER DEC HL CALL FREE_BUFFER ;;; RELEASE THIS BUFFER LD A,D OR E JP Z,FC_9 ;;; IF "NEXT" ADDRESS IS 0, WE ARE AT END ;;; ELSE WE'VE GOT MORE ON THIS CHAIN - DEAL WITH THEM. EX DE,HL ;;; "LD HL,DE" - HL POINTS TO "NEXT" JP FC_0 FC_9: POP HL POP DE POP AF RET ;;; -------------------------------------------------------------------------- ;;; OUT_QUEUE_INSERT - PLACES THE JUST-RECEIVED BUFFER ON THE OUTPUT QUEUE. ;;; THE ADDRESS OF THE RX BUFFER JUST RECEIVED IS IN HL. ;;; THE OUTPUT QUEUE IS A CIRCULAR BUFFER. THE OUTPUT ROUTINE KEEPS SENDING ;;; OUT BUFFERS UNTIL ITS OUT_HEAD_CBUF POINTER EQUALS ITS OUT_TAIL_CBUF ;;; POINTER. THE OUTPUT ROUTINE NEVER MUCKS WITH THE OUT_TAIL_CBUF POINTER; ;;; SIMILARLY, THIS ROUTINE NEVER CHANGES THE OUT_HEAD_CBUF POINTER. SO IT ;;; IS POSSIBLE TO ;;; INSERT NEW ENTRIES INTO THE OUTPUT CIRCULAR BUFFER QUEUE WITHOUT ;;; DISTURBING THE ENTRY WHICH IS BEING SENT TO THE OUTPUT PORT. OUT_QUEUE_INSERT: PUSH AF PUSH DE PUSH HL ;;; USE THESE EX DE,HL ;;; "LD DE,HL" - MOVE BUFFER TO LINK ADDR LD HL,(OUT_TAIL_CBUF) ;;; GRAB NEXT FREE LOCATION LD (HL),E ;;; SET LO ADDR 1ST INC HL LD (HL),D ;;; NOW HI ADDR INC HL ;;; NOW HL POINTS TO NEXT FREE ENTRY IN... LD DE,OUT_BOTTOM ;;; ...CIRC BUF, UNLESS WE'RE AT END OR A ;;; CLEAR CARRY PUSH HL ;;; (MAY BE BE NEEDED ADDRESS) SBC HL,DE POP HL ;;; GET BACK WHAT WE THINK IS GOOD JP NZ,OQI_0 LD HL,OUT_TOP ;;; GET HERE IF WE'RE AT END OF CIRC BUFFER. OQI_0: LD (OUT_TAIL_CBUF),HL POP HL POP DE POP AF ;;; KEEP CLEAN RET ;;;--------------------------------------------------------------------------- ;;; TX_QUEUE_INSERT - SIMILAR TO OUT_QUEUE_INSERT, BUT WITH DIFFERENT QUEUE. ;;; ALSO, INCREMENTS THE BYTE TX_OUTSTANDING (WHICH COUNTS THE NUMBER OF ;;; FRAMES READY TO BE DUMPED TO THE MODEM PORT). THIS ROUTINE, LIKE ;;; OUT_QUEUE_INSERT, DOES NOT NEED TO WORRY ABOUT QUEUE WRAP-AROUND BECAUSE ;;; THERE ARE MORE ENTRIES IN EACH OF THESE QUEUES THAN THERE ARE BUFFERS ;;; AVAILABLE. YES, I KNOW THIS IS A HACK, AND WASTES SOME RAM SPACE, BUT IT ;;; MEANS I DON'T HAVE TO CHECK FOR OVERFLOWS HERE. ;;; THE QUEUE IS CIRCULAR, AND SOMETIMES I CALL IT A "CBUF" - CIRCULAR BUFFER TX_QUEUE_INSERT: PUSH AF PUSH DE PUSH HL EX DE,HL ;;; "LD DE,HL" - SAVE CHAIN HEAD IN DE LD HL,(TX_TAIL_CBUF) ;;; NEXT FREE LOCATION IN TX CBUF LD (HL),E INC HL LD (HL),D ;;; PUT THIS CHAIN INTO TX QUEUE INC HL ;;; HL IS NEXT AVAILBLE TX QUEUE ... LD DE,TX_BOTTOM ;;; ... UNLESS WE ARE AT BOTTOM OF ... OR A ;;; ... THE TX QUEUE PUSH HL SBC HL,DE POP HL JP NZ,TQI_0 ;;; GO THERE IF NOT AT BUFFER BOTTOM LD HL,TX_TOP ;;; ELSE RELOAD WITH TOP OF QUEUE VAL TQI_0: LD (TX_TAIL_CBUF),HL ;;; SAVE NEXT FREE QUEUE SLOT LD HL,TX_OUTSTANDING INC (HL) ;;; +1 MORE FRAME OUTSTANDING NOW POP HL POP DE POP AF RET ;----------------------------------------------------------------------------- ; SETUP HL & TX_CHAIN_HEAD FOR TRANSMISSION OF NEXT CHAIN. TXNEXT_CBUF: PUSH AF PUSH DE LD HL,(TX_HEAD_CBUF) LD E,(HL) INC HL LD D,(HL) ; DE -> NEXT CHAIN TO TRANSMIT INC HL ; HL MIGHT BE NEXT CBUF ENTRY POINTER PUSH DE LD DE,TX_BOTTOM OR A ;CLEAR CARRY PUSH HL ;SAVE WHAT MIGHT BE CORRECT VALUE SBC HL,DE POP HL POP DE JP NZ,TXN_1 ;GO THERE IF NOT AT END OF CIRC. BUF LD HL,TX_TOP ;ELSE WE WRAP AROUNE TXN_1: LD (TX_HEAD_CBUF),HL ;SAVE NEXT CIRC BUF POINTER IN MEM EX DE,HL ;RETURN PTR TO NEXT CHAIN TO TX IN HL LD (TX_CHAIN_HEAD),HL ;TX RCA ROUTINE NEEDS THIS POP DE POP AF RET ;----------------------------------------------------------------------------- STA_ON: ;TURN THE STA LED ON. ASSUMES THAT INTERRUPTS ARE DISABLED! PUSH AF LD A,5 OUT (A_CTL),A ;;; READY TO WRITE WR5 LD A,(A_WR5) ;;; GET MEMORY COPY AND 0FFH-ALED ;;; SET DTR BIT TO 0 SO LED GOES ON OUT (A_CTL),A ;;; ACTUALLY TURN ON STA LED NOW... LD (A_WR5),A ;;; UPDATE MEMORY COPY POP AF RET ;----------------------------------------------------------------------------- STA_OFF: ;TURN THE STA LED OFF. ASSUMES THAT INTERRUPTS ARE DISABLED! PUSH AF LD A,5 OUT (A_CTL),A ;;; READY TO WRITE WR5 LD A,(A_WR5) ;;; GET MEMORY COPY OR ALED ;;; SET DTR BIT TO 1 SO LED GOES OFF OUT (A_CTL),A ;;; ACTUALLY TURN OFF STA LED NOW... LD (A_WR5),A ;;; UPDATE MEMORY COPY POP AF RET ;THESE ROUTINES MUST BE CALLED WITH INTERRUPTS DISABLED! ;----------------------------------------------------------------------------- STA_FLIP: PUSH AF PUSH BC IN A,(A_CTL) ;;;ASSURE WE ARE TALKING TO CH 0 LD A,5 OUT (A_CTL),A ;;; READY TO WRITE WR5 LD A,(A_WR5) ;;; GET MEMORY COPY LD B,A ;;; SAVE ORIGINAL FOR A MOMENT... AND ALED ;;; CHECK THE STA LED BIT LD A,B ;;; RETREIVE ORIGINAL JP Z,STA_F0 ;;; BIT IS A 0, SO LED IS ON, MAKE OFF ;ELSE MAKE IT GO ON (BECAUSE IT IS NOW OFF) AND 0FFH-ALED ;;; SET DTR BIT TO 0 SO LED GOES ON JP STA_F1 STA_F0: OR ALED ;;; SET DTR BIT TO 1 SO LED GOES OFF STA_F1: OUT (A_CTL),A ;;; ACTUALLY TURN OFF STA LED NOW... LD (A_WR5),A ;;; UPDATE MEMORY COPY POP BC POP AF RET ;----------------------------------------------------------------------------- CON_ON: PUSH AF LD A,5 OUT (B_CTL),A LD A,BLEDON LD (B_WR5),A ;;; SAVE IN MEM FOR FLIP ROUTINE OUT (B_CTL),A POP AF RET ;----------------------------------------------------------------------------- CON_OFF: PUSH AF LD A,5 OUT (B_CTL),A LD A,BLEDOFF LD (B_WR5),A ;;; SAVE IN MEM FOR FLIP ROUTINE OUT (B_CTL),A POP AF RET ;----------------------------------------------------------------------------- CON_FLIP: PUSH AF PUSH BC IN A,(B_CTL) ;;;ASSURE WE ARE TALKING TO CH 0 LD A,5 OUT (B_CTL),A ;;; READY TO WRITE WR5 LD A,(B_WR5) ;;; GET MEMORY COPY LD B,A ;;; SAVE ORIGINAL FOR A MOMENT... AND BLED ;;; CHECK THE CON LED BIT LD A,B ;;; RETREIVE ORIGINAL JP Z,CON_F0 ;;; BIT IS A 0, SO LED IS ON, MAKE OFF ;ELSE MAKE IT GO ON (BECAUSE IT IS NOW OFF) AND 0FFH-BLED ;;; SET DTR BIT TO 0 SO LED GOES ON JP CON_F1 CON_F0: OR BLED ;;; SET DTR BIT TO 1 SO LED GOES OFF CON_F1: OUT (B_CTL),A ;;; ACTUALLY TURN OFF CON LED NOW... LD (B_WR5),A ;;; UPDATE MEMORY COPY POP BC POP AF RET ; ; IF CATASTROPHIC ERROR OCCURS RESET - IF RUNNING WITH BPQ ; SWITCH CODE, WE CANT REALLY AFFORT TO HANG UP ; RESTART: DI LD HL,0 RSLOOP: DEC HL LD A,H OR L JR NZ,RSLOOP ; DELAY A BIT JP 0 ; START AGAIN COND PROM DEFS START+8000H-$ ENDC FREE_RAM DEFS 0 ;----------------------------------------------------------------------------- ; THESE ARE THE TX REAL-TIME ROUTINE DATA STRUCTURES. THEY ARE USED FOR ; TIMING REQUIRED WITH TX CONTROL. THERE ARE 3 ACTIONS THAT MUST BE TIMED: ; 1) TXR_DELAY TX DELAY TIMER (FOR TXDELAY FUNCTION) ; 2) TXR_SLOTTIME PART OF P-PERSISTENCE BACKOFF ; 3) TXR_TAIL TIMER TO BE SENDING SYNCS BEFORE DROPPING RTS ; THE DATA STRUCTURE CAN BE THOUGHT OF LOGICALLY AS THIS: ; ; +------------------------+ ; | ROUTINE ENABLED (BYTE) | IS 0 IF NOT ENABLED, NON ZERO IF ENABLED ; +------------------------+--------------------------------------+ ; | POINTER TO ROUTINE TO EXECUTE WHEN TIMER EXPIRES (WORD) | ; +---------------------------------------------------------------+ ; | 16-BIT DOWNCOUNTER TIMER VALUE, IN 10S OF MILLISECONDS (WORD) | ; +---------------------------------------------------------------+ ; ; THE DATA STRUCTURE HAS ONE ENTRY FOR EACH OF THE 3 TIMER EVENTS. PHYSICALLY ; IT IS ORGANIZED AS 3 SEPARATE LISTS, ONE FOR EACH OF THE ENABLES, ONE FOR ; EACH OF THE ROUTINE POINTERS, AND ONE FOR EACH OF THE TIMER VALUES. ; ; AN INTERUPT ROUTINE, RUNNING AT 10 MILLISECOND TICKS, DECREMENTS THE VALUES ; IN EACH OF THE DOWNCOUNT TIMER WHETHER A ROUTINE IS ENABLED OR NOT. WHEN ; DOWNCOUNT VALUE GOES TO 0 (OR NEGATIVE) THEN THE ROUTINE "FIRES". THIS ; CHECKING FOR "FIRING" HAPPENS AT NON-INTERRUPT LEVEL IN THE COMMUTATOR LOOP. ; WITH THIS SCHEME, THE MINIMUM TIME BEFORE FIRING IS 10 MILLISECONDS, AND THE ; MAXIMUM TIME IS 327.67 SECONDS (OVER 5 MINUTES). FOR EXAMPLE, FOR A ; TXDELAY OF 600 MILLISECONDS, THE TIMER WOULD GET LOADED WITH DECIMAL 60. ; ; WHEN A ROUTINE FIRES, IT GETS MARKED AS "DISABLED", SO YOU'D NEED TO ; EXPLICITLY RE-ENABLE IT IF THIS IS REQUIRED ; NOTE TOO THAT A CLOCK COULD BE EASILY IMPLEMENTED. IF WE INSERTED ANOTHER ; EVENT INTO OUR LIST WITH A TIMEOUT OF 100, THEN EVERY SECOND A ROUTINE WOULD ; BE CALLED. IN THAT ROUTINE, WE COULD INCREMENT THE SECONDS FIELD (AND ; POSSIBLY MINUTES, HOURS, DAYS, YEARS FIELDS) OF A TIME-OF-DAY CLOCK. WE ; WOULD IMMEDIATELY RE-ACTIVATE THIS TIMER TO GET THE NEXT TICK, ETC. TXQ_ENABLES DEFS 4 ; 4 BYTES FOR THE ENABLES TXQ_ADDRESSES DEFS 8 ; 4 WORDS FOR THE ROUTINE POINTERS TXQ_TIMERS DEFS 8 ; 4 WORDS FOR THE ROUTINE TIMERS ; NOTE THE LAST SLOT IN THIS TABLE IS FOR R_TEST ROUTINE, WHICH BLINKS STA LED ; IT IS NOT USED NORMALLY, JUST FOR HELPING ME DEBUG THIS! ; SOME EQUATES TO SAVE US FROM DOING CONTORTED THINGS WHEN WE WANT TO CHECK IF ; A ROUTINE IS ENABLED IN PLACES OTHER THAN THE COMMUTATOR LOOP, OR FOR ; ENABLING ROUTINES, ETC. TXQE_DELAY EQU TXQ_ENABLES+0 TXQE_SLOTTIME EQU TXQ_ENABLES+1 TXQE_TAIL EQU TXQ_ENABLES+2 TXQE_CWID EQU TXQ_ENABLES+3 ; SAME IDEA, BUT FOR THE TIMER VALUES TXQT_DELAY EQU TXQ_TIMERS+0 TXQT_SLOTTIME EQU TXQ_TIMERS+2 TXQT_TAIL EQU TXQ_TIMERS+4 TXQT_CWID EQU TXQ_TIMERS+6 ; WE DON'T DO THIS FOR THE ROUTINE ADDRESSES, SINCE THEY DON'T CHANGE ONCE ; THEY ARE INITIALIZED. TXDELAY DEFS 1 ; TRANSMITTER DELAY TIME VALUE PERSISTENCE DEFS 1 ; PERSISTENCE VALUE SLOTTIME DEFS 1 ; SLOT TIME VALUE TAILTIME DEFS 1 ; TX TAIL TIME VALUE NBUFFERS DEFS 1 ; UP TO 255 BUFFERS FREE DEFS 2 ; ADDRESS OF 1ST BUFFER ON FREE LIST RX_BUF DEFS 2 ; ADDRESS OF CURRENT RECEIVE BUFFER RX_HEAD DEFS 2 ; ADDRESS OF 1ST RX BUFFER RX_ALLOCATED_BUFFER DEFS 1 ; SET NON-ZERO IF WE'RE IN RX STATE RX_FLUSHING DEFS 1 ; IS NON-0 IF WE RAN OUT OF BUFFER ; SPACE AND ARE CURRENTLY FLUSHING THIS ; FRAME BEING RECEIVED. USED BY ; IA_RCA AND RESET BY IA_EXT. IN_BUFFER DEFS 2 ; ADDR OF CURRENT INPUT BUFFER IN_HEAD DEFS 2 ; ADDR OF 1ST INPUT BUFFER IN_ALLOCATED_BUFFER DEFS 1 ; IS NOT 0 IF WE'VE ALREADY ALLOC'D BUF IN_STATE DEFS 1 ; 1 ; INPUT STATE MACHINE STATE ; ASSUME THAT WE'VE SEEN AN FEND FROM ; (NON-EXISTENT) "PREVIOUS" FRAME. THIS ; MEANS THAT WHEN WE ARE RECEIVING DATA ; FROM USER, THERE NEED BE ONLY THE ; FEND CHAR AT THE END OF A FRAME, AND ; NOT AT THE BEGINNING (ALTHOUGH IF A ; FEND IS AT THE BEGINNING, IT IS ; IGNORED.) OUT_STARTED DEFS 1 ; OUTPUT NOT STARTED YET (LOGICAL VAR) OUT_HEAD_CBUF DEFS 2 ; OUT_TOP ; ADDRESS OF BUFFER TO BE OUTPUT RS232 OUT_TAIL_CBUF DEFS 2 ; OUT_TOP ; POINTER TO NEXT FREE OUTPUT BUFFER OUT_CHAIN_HEAD DEFS 2 ; ADDR OF BUFFER WE ARE NOW OUTPUTTING TX_STARTED DEFS 1 ; NON-ZERO IF WE'VE BEGUN TXING CHARS TX_HEAD_CBUF DEFS 2 ; TX_TOP ; CURRENT ACTIVE CBUF ENTRY (IF ACTIVE) TX_TAIL_CBUF DEFS 2 ; TX_TOP ; NEXT FREE CBUF ENTRY TX_CHAIN_HEAD DEFS 2 ; HOLDS ADDRESS OF THE CURRENT BUFFER ; CHAIN HEAD THAT WE ARE TRANSMITTING TX_OUTSTANDING DEFS 1 ; NUMBER OF TX CBUFS QUEUED UP FOR TX DCD_STATE DEFS 1 ; IS NON 0 IF DCD LED IS ON ;THESE NEXT TWO ARE USED BY THE IB_TBE INTERRUPT ROUTINE. IB_ESC_MODE DEFS 1 ; NOT IN ESCAPED MODE IB_CHAR DEFS 1 ; NEXT CHAR TO SEND IF ESCAPED MODE IN_BREAK DEFS 1 ; NON-ZERO IF WE ARE IN A BREAK DETECT ; ON THE ASYNC PORT FULL_DUPLEX DEFS 1 ; NOT INITIALLY FULL DUPLEX A_WR5 DEFS 1 ; ALEDOFF ; STATE OF STA LED & RTS (PTT) LINE, ; MAINLY... (FOR CH A ONLY [MODEM] ) B_WR5 DEFS 1 ; BLEDOFF DEFS 1 ; WHY?? MINUTES DEFS 1 ; FOR CWID TIMING CWIDFLAG DEFS 1 ; ID OUTSTANDING FLAG CWIDPTR DEFS 2 ; NEXT CWID BIT TO SEND LASTBIT DEFS 1 ; CURRENT CWID STATE OUT_TOP DEFS 2*255 ; "TOP" OF OUTPUT CIRCULAR BUFFER ; 255 OUT BUFFER CHAINS PENDING, MAX OUT_BOTTOM DEFS 2 ; "BOTTOM" OF OUTPUT CIRCULAR BUFFER TX_TOP DEFS 255*2 TX_BOTTOM DEFS 2 BOTTOM DEFS 0 ; END OF ALL CODE & PREDEFINED DATA ; NOTES ON NOMENCLATURE: ; OUT = TO TTY PORT; IN = FROM TTY PORT ; TX = TO MODEM; RX = FROM MODEM ; ; ;;; MEANS THAT THAT CODE EXECUTES WITHOUT INTERRUPTS ENABLED (EXCEPT ; FOR THE INITIALIZATION CODE) ; ; ; I HAVE BEEN CAREFUL WITH JR/JP USE. I USE JP WHEN THE JUMP IS LIKELY AND ; WHERE SPEED IS IMPORTANT. I USE JR WHEN THE JUMP IS UNLIKELY SO THAT I CAN ; SAVE A FEW CYCLES. JP ALWAYS USES 10 CYCLES WHETHER IT JUMPS OR NOT, BUT ; JR USES EITHER 7 OR 12 T STATES, NO JUMP/JUMP, RESPECTIVELY. ; BUFFERS KEPT HERE AT END. END START