; ---------------------------- ; RCVHNDLR TTY Receive Handler ; ---------------------------- ; ; Two routines are provided that maintain an interrupt-driven ; TTY-receive queue. Appropriate PASCAL declarations are: ; ; CONST RCVQSIZE = maximum number of queued characters ; ; TYPE QUEUE = RECORD (* These are order-dependent !!! *) ; QSIZE: INTEGER; ; INP: INTEGER; ; OUTP: INTEGER; ; MAXCHAR: INTEGER; ; DATA: PACKED ARRAY [0..RCVQSIZE] OF CHAR; ; END; ; VAR RCVQ: QUEUE; (* must be declared in outermost block *) ; ; PROCEDURE RCVINIT (VAR Q: QUEUE; SIZE:INTEGER); EXTERNAL; ; PROCEDURE RCVFINIT; EXTERNAL; ; ; RCVINIT (RCVQ,RCVQSIZE); (* initialize the queue handler *) ; ; WHILE TRUE DO ; WITH RCVQ DO ; IF INP <> OUTP THEN (* characters available *) ; BEGIN ; CH := DATA[OUTP]; ; OUTP := OUTP+1; IF OUTP > QSIZE THEN OUTP := 0; ; ... ; END; ; ; RCVFINIT; (* terminate the queue handler *) ; ; The RECORD declaration for the queue must appear exactly as it ; does above except that you can of course use any names you like. ; Do NOT attempt to lump the first four integer variables together ; into a single group of the form list:INTEGER. In that case, ; the compiler allocates them in reverse order, so that your code ; and the interrupt handler will not agree about which words have ; what meaning. ; ; The queue handler runs continuously as an interrupt-driven task ; at high priority. As characters come in, it advances the queue ; INP pointer and keeps track of the maximum number of characters in ; the queue in the MAXCHAR variable. Queue overflow is indicated ; by MAXCHAR > QSIZE. You must terminate by calling RCVFINIT, or ; the TTY receive interrupts will be left enabled and you will end ; up crashing the system by executing garbage code when the next ; character is received. (RCVFINIT also repairs the interrupt ; vectors for breakpoints and the clock, so failing to call it will ; quite likely crash the system even in the absence of incoming ; TTY characters.) ; ; The manipulation of the clock and BPT interrupt vectors is borrowed ; from UCSD's old communications program. The purpose is to allow ; the clock handler to be interrupted by incoming TTY characters. ; RDB .EQU 177522 ; Receive Data Buffer absolute address RSR .EQU 177520 ; Receive Status Register absolute address RCVINTV .EQU 120 ; Receiver Interrupt Vector address CLKINTV .EQU 100 ; Clock interrupt vector address BPTINTV .EQU 14 ; BPT interrupt vector address QXCINTV .EQU 250 ; QX controller interrupt vector ; .PROC RCVINIT,2 ; (VAR Q:QUEUE, SIZE:INTEGER) ; .DEF BPTLOC ; used to save BPT interrupt handler adrs .DEF BPTPR ; used to save BPT handler priority Q .EQU 4 ; stack offset for Q address SIZE .EQU 2 ; stack offset for QSIZE value ; MOV Q(SP),R0 ; obtain the Q record address MOV R0,RCVQADRS ; remember Q address MOV SIZE(SP),(R0)+ ; store size in QSIZE word MOV #0,(R0)+ ; clear INP, OUTP, and MAXCHAR MOV #0,(R0)+ MOV #0,(R0) ; MOV @#BPTINTV,BPTLOC ; save old BPT handler address MOV @#BPTINTV+2,BPTPR ; and old BPT handler priority MOV @#CLKINTV,@#BPTINTV ; make BPT vector point to old clock MOV #0,@#BPTINTV+2 ; and let it run at low priority MOV #CLKHNDLR,@#CLKINTV ; and replace clock handler with ours MOV #0,@#QXCINTV+2 ; make floppy interruptable ; MOV #RCVHNDLR,@#RCVINTV ; store interrupt handler address MOV #200,@#RCVINTV+2 ; set interrupt priority 4 for TTY input MOV #100,@#RSR ; enable interrupts for TTY input ; MOV (SP)+,R0 ; pop return address from stack ADD #4,SP ; discard 2 parameters (4 bytes) JMP @R0 ; and return to PASCAL interpreter ; RCVQADRS .WORD 0 ; holds Q address for handler BPTLOC .WORD 0 ; saves old BPT handler location BPTPR .WORD 0 ; saves old BPT handler priority ; QSIZE .EQU 0 ; offset from Q INP .EQU 2 ; likewise OUTP .EQU 4 MAXCHAR .EQU 6 DATA .EQU 10 ; RCVHNDLR: MOV R0,-(SP) ; free registers R0, R1, R2 for use MOV R1,-(SP) MOV R2,-(SP) MOV RCVQADRS,R2 ; fetch Q address saved by RCVINIT MOV INP(R2),R0 ; fetch INP value MOV R0,R1 ; make a working copy ADD R2,R1 ; R1 = address (Q) + value (INP) MOVB @#RDB,DATA(R1) ; DATA[INP] := input character BICB #200,DATA(R1) ; clear bit 8 (parity) BEQ EXIT ; ignore nulls (do not bump INP) INC R0 ; INP := INP+1 CMP QSIZE(R2),R0 BPL NOWRAP ; if QSIZE >= INP then no wraparound CLR R0 ; else INP := 0 NOWRAP MOV R0,INP(R2) ; restore INP ; SUB OUTP(R2),R0 BMI INOUT BEQ INOUT BR OUTIN ; if INP > OUTP, # char = INP - OUTP INOUT ADD QSIZE(R2),R0 ; otherwise, # char = QSIZE+1 + INP - OUTP ADD #1,R0 OUTIN CMP MAXCHAR(R2),R0 BPL EXIT ; if MAXCHAR >= # char, exit MOV R0,MAXCHAR(R2) ; otherwise, store new MAXCHAR ; EXIT MOV (SP)+,R2 ; restore registers for caller MOV (SP)+,R1 MOV (SP)+,R0 RTT ; return from interrupt ; CLKHNDLR: COM CLKFLG ; do not reexecute BPT if BPT handler BEQ CLKEXIT ; takes so long that clock ticks again BPT ; let breakpoint transfer to old clock CLKEXIT COM CLKFLG ; reset flag RTI ; and exit ; CLKFLG .WORD 0 ; flags reentry before BPT exit ; .PROC RCVFINIT .REF BPTLOC ; old BPT handler loc, saved by RCVINIT .REF BPTPR ; old BPT handler priority, likewise ; MOV #0,@#RSR ; disable receive interrupt MOV @#BPTINTV,@#CLKINTV ; repair clock interrupt vector MOV @#BPTPR,@#BPTINTV+2 ; reestablish BPT handler priority MOV @#BPTLOC,@#BPTINTV ; repair BPT handler address MOV #340,@#QXCINTV+2 ; repair QX controller vector RTS PC ; and return ; .END