;                        ----------------------------
;                        KBDHNDLR TTY Receive Handler
;                        ----------------------------
;
;    Two routines are provided that maintain an interrupt-driven
;    TTY-receive queue.  Appropriate PASCAL declarations are:
;
;        CONST KQSIZE = 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 KQ: QUEUE;     (* must be declared in outermost block *)
;
;        PROCEDURE KBDINIT (VAR Q: QUEUE; SIZE:INTEGER); EXTERNAL;
;        PROCEDURE KBDFINIT; EXTERNAL;
;
;        KBDINIT (KQ,KQSIZE);    (* initialize the queue handler *)
;
;        WHILE TRUE DO
;          WITH KQ DO
;           IF INP <> OUTP THEN    (* characters available *)
;             BEGIN
;               CH := DATA[OUTP];
;               OUTP := OUTP+1; IF OUTP > QSIZE THEN OUTP := 0;
;                ...
;             END;
;
;        KBDFINIT;       (* 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 KBDFINIT, 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.  (KBDFINIT 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.
;
KDB       .EQU   177562          ; Receive Data Buffer absolute address
KSR       .EQU   177560          ; Receive Status Register absolute address
KINTV     .EQU       60          ; 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  KBDINIT,2         ; (VAR Q:QUEUE, SIZE:INTEGER)
;
          .DEF   KBDLOC          ; holds vector address
          .DEF   KBDPR           ; holds old 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,KQADRS       ; 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    @#KINTV,KBDLOC      ; save old interrupt vector
          MOV    @#KINTV+2,KBDPR     ; and old priority

          MOV    #KHNDLR,@#KINTV     ; store interrupt handler address
          MOV    #200,@#KINTV+2      ; set interrupt priority 4 for TTY input
          ;MOV    #100,@#KSR          ; 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
;
KQADRS    .WORD   0              ; holds Q address for handler
KBDLOC    .WORD   0              ; holds old interrupt vector
KBDPR     .WORD   0              ; holds old interrupt priority
;
QSIZE     .EQU    0              ; offset from Q
INP       .EQU    2              ; likewise
OUTP      .EQU    4
MAXCHAR   .EQU    6
DATA      .EQU   10
;
KHNDLR:   MOV    R0,-(SP)        ; free registers R0, R1, R2 for use
          MOV    R1,-(SP)
          MOV    R2,-(SP)
          MOV    KQADRS,R2       ; fetch Q address saved by KBDINIT
          MOV    INP(R2),R0      ; fetch INP value
          MOV    R0,R1           ; make a working copy
          ADD    R2,R1           ; R1 = address (Q) + value (INP)
          MOVB   @#KDB,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  KBDFINIT
          .REF   KBDLOC          ; old interrupt vector saved by KBDINIT
          .REF   KBDPR           ; old kbd priority saved by KBDINIT
;
           MOV    @#KBDPR,@#KINTV+2    ; restore interrupt priority
           MOV    @#KBDLOC,@#KINTV     ; and interrupt vector
           RTS    PC                   ; and return
;
           .END


