;
;       NAME:  TIMEIN.M68
;
       OBJNAM  .SBR
;
;       FUNCTION:  This routine is used to obtain input from the job's
;       terminal input buffer, with the option of timing out if a given
;       number of seconds passes following the last received character.
;
;       CALLING SEQUENCE:
;       XCALL TIMEIN,OPTION,BUFFER,LENGTH   where
;       Name    Type    Use
;       OPTION  Float   describes type of timing to be done--
;                         -1 => don't timeout, i.e., wait until the buffer
;                         is completely full or hell freezes over
;                         0 => return as soon as the input buffer is empty
;                         (or the caller's buffer is full)
;                         >0 => timeout if this many seconds passes without
;                         a character being received
;       BUFFER  Any     This is where the characters are returned.  The
;                       size of this field implies the maximum # of
;                       bytes that can be returned.
;       LENGTH  Float   The # of characters received is returned in this
;                       field.
;
;       AUTHOR:  Tom Dahlquist
;
;       EDIT HISTORY:
;         When   Who What
;       11/16/84 TAD Written.
;       05/03/85 TAD Use timer queue rather than looping on SLEEP command
;                       to do timing.
;
       SEARCH  SYS
       SEARCH  SYSSYM
       SEARCH  TRM
;
;       Map of timer queue block usage
;
       ASECT
TLINK:  BLKL    1                       ; used by system...-> next in q
TCOUNT: BLKL    1                       ; used by system...timer count
TADDR:  BLKL    1                       ; address of routine to execute
TJCB:   BLKL    1                       ; address of our JCB
TFLAG:  BLKL    1                       ; address of flag to set on timeout
       PSECT

       VMAJOR=1
       VMINOR=0
       VEDIT=0

TIMEIN: PHDR    -1,PV$RSM!PV$WSM,PH$REU!PH$REE
       CMPW    (A3)+,#3                        ; must be exactly three args...
       JNE     RETURN                  ; die if not.

       MOVW    (A3)+,D6                ; first arg must be float...
       ANDW    #7,D6
       CMPW    D6,#4
       JNE     RETURN
       MOV     @A3,A0                  ; A0 -> float #
       FFTOL   @A0,D0                  ; D0 used from here on to contain
                                       ; value of option...
       LEA     A3,12(A3)               ; skip to address of buffer...
       MOV     (A3)+,A0                ; A0 -> buffer...
       MOV     (A3)+,D2                ; D2 = length of buffer...
       DEC     D2                      ; D2 = length of buffer - 1...

       MOVW    (A3)+,D6                ; test type of last arg...
       ANDW    #7,D6                   ; must be float...
       CMPW    D6,#4
       JNE     RETURN

       MOV     D2,D6                   ; D6 = length of buffer - 1...
       MOV     A0,A6                   ; A6 -> buffer...
CLEAR:  CLRB    (A6)+                   ; clear out return buffer...
       DBF     D6,CLEAR

       CLR     D3                      ; D3 used to count chars received...

       JOBIDX  A6                      ; keep address of trmdef in A5...
       MOV     JOBTRM(A6),A5

       TST     D0                      ; OK, go to routine for each type of call
       BMI     FOREVR                  ; don't return until buffer full...
       BEQ     NOWAIT                  ; return as soon as input buffer emtpy...
;
;       OK, we are timing.  We set up a timer queue block.  Then we loop
;       looking for characters.  When the input buffer is empty, we go to
;       sleep via a JWAIT.  When we are woken up from it, one of two things
;       has happened:  1) we have received a character, or 2) we have timed
;       out.  We can tell which happened by checking a flag byte.  If we timed
;       out, we must check how much time has elapsed since the last
;       character was received.  If it is greater than our timeout number,
;       then we return to the caller.  If less, we requeue the timer block
;       with a new value.
;
       CALL    SETQ                    ; initiate timer Q block...

TIMTCK: TST     T.ICC(A5)               ; any chars for us?
       BEQ     NOPE                    ; br if not...
TIMKBD: KBD                             ; if so, get it,
       MOVB    D1,(A0)+                ; store it,
       INC     D3                      ; count it,
       DBF     D2,TIMTCK               ; and see if buffer full.

       CALL    UNSETQ                  ; get rid of timer Q block...
       BR      STOREL                  ; and leave.

NOPE:   GTIMEI  D4                      ; D4 = current time...
NOPE2:  TST     T.ICC(A5)               ; check again, if anything now there
       BNE     TIMKBD                  ; go get it.
JWAIT:  JWAIT   J.TIW                   ; sleepy time...
       TSTB    @A4                     ; did we time out?
       BEQ     TIMKBD                  ; go get char if not, else
       GTIMEI  D6                      ; D6 = current time
       SUB     D4,D6                   ; D6 = time elapsed since last char
       MOV     D0,D7                   ; D7 = timeout seconds
       SUB     D6,D7                   ; D7 = difference
       BLE     TIMOUT                  ; if zero or neg, we timed out
       MUL     D7,#10000.              ; else, requeue block with
       MOV     D7,TCOUNT(A1)           ; new count...
       CLRB    @A4                     ; clear out flag byte...
       TIMER   @A1                     ; requeue it...
       BR      NOPE2                   ; go back to sleep.
TIMOUT: QRET    A1                      ; get rid of Q block...
       BR      NOWAIT                  ; and leave.
;
;       This routine just waits until the caller's buffer is full.
;
FOREVR: KBD                             ; get next char,
       MOVB    D1,(A0)+                ; store it,
       INC     D3                      ; increment counter,
       DBF     D2,FOREVR               ; and loop until full.
       BR      STOREL
;
;       This routine just returns whatever is in the input buffer.
;
NOWAIT: TST     T.ICC(A5)               ; anything there?
       BEQ     STOREL                  ; return if not...
       KBD                             ; ok, get it,
       MOVB    D1,(A0)+                ; store it,
       INC     D3                      ; count it,
       DBF     D2,NOWAIT               ; and loop
       BR      STOREL
;
;       Store character count and return.
;
STOREL: MOV     @A3,A0                  ; A0 -> length variable
       FLTOF   D3,@A0                  ; store counter...
RETURN: RTN
;
;       Set up a timer Q block and start the clock ticking.....
;
SETQ:   QGET    A1                      ; get a system Q block...
       BNE     DIEQ                    ; die if none...
       JOBIDX  A6                      ; A6 -> our JCB...
       MOV     A6,TJCB(A1)
       MOV     D0,D7
       MUL     D7,#10000.              ; convert secs to timer ticks...
       MOV     D7,TCOUNT(A1)
       LEA     A6,WAKEME               ; A6 -> timer exit routine...
       MOV     A6,TADDR(A1)
       MOV     A4,TFLAG(A1)            ; A4 -> flag byte (in BASIC area)
       CLRB    @A4                     ; clear flag byte...
       TIMER   @A1                     ; start timer...
       RTN
DIEQ:   EXIT
;
;       Come here when we have received enough characters and want to get
;       rid of our timer Q entry.  This is complicated due to three
;       possibilities:  1) we don't find it in the timer Q--don't do
;       anything; 2) we find it and it is the first entry--we stick a
;       substitute exit routine into the Q block itself which just returns
;       the block to the system when called; 3) we find it and it is not the
;       first block in the Q--we remove it, add its time to the next block
;       in the Q, and return it to the system.
;
UNSETQ: SUPVR
       SVLOK
       MOV     TIMQUE,D6               ; A6 -> first timer Q block...
       BEQ     DONEDQ                  ; if none, all done.
       MOV     D6,A6
       CMP     A6,A1                   ; ours first?
       BEQ     CHGQ                    ; go change it if so...
DQLOOP: MOV     A6,A2                   ; A2 -> previous Q block...
       MOV     @A6,D6
       BEQ     DONEDQ                  ; if no more, all done.
       MOV     D6,A6
       CMP     A6,A1                   ; ours?
       BNE     DQLOOP                  ; if not, go get next...
       MOV     @A1,A6                  ; A6 -> next block in Q...
       MOV     A6,@A2                  ; remove us from Q, and
       BEQ     1$                      ; if there is a next block we
       MOV     TCOUNT(A1),D6
       ADD     D6,TCOUNT(A6)           ; add our timer count to its.
1$:     QRET    A1                      ; return Q block to system...
DONEDQ: LSTS    #0                      ; user mode, allow interrupts
       RTN

CHGQ:   MOV     #DEQL-1,D1              ; length of dummy exit routine...
       LEA     A6,TJCB(A1)             ; where to move it...
       MOV     A6,TADDR(A1)
       LEA     A2,DEQSTF
CQLOOP: MOVB    (A2)+,(A6)+
       DBF     D1,CQLOOP
       LSTS    #0                      ; user mode, allow interrupts
       RTN

DEQSTF: SUB     #14,A0
       QRET    A0
       RTN
DEQL    =       .-DEQSTF
;
;       This is the timer exit routine.  It sets the flag byte and returns.
;
WAKEME: MOV     TFLAG-14(A0),A6         ; A6 -> flag byte...
       SETB    @A6                     ; set flag...
       MOV     TJCB-14(A0),A0          ; A0 -> our JCB...
       JRUN    J.TIW+J.NXT             ; run job...
       RTN

       END