; 
;  lt_routines : LAT Application Port maniulation routines.
;
;                                     Submitted by Robert Simon
;                                     Network Equipment Technologies
;                                     Santa Barbara, CA
;                                     November 6, 1989
;
;     Abstract:  When a program wants to utilize a serial port on
;                a terminal server for general purpose communication,
;                it uses LAT Application Ports.  Before a program
;                can use use the application port, it must successfully
;                connect the application port to the appropriate
;                terminal server port.  The routines in this submission
;                provide a simple interface for programs written in C,
;                FORTRAN, or almost any VMS programming language
;                to manipulate LAT application ports.
;                These routines hide the QIO interface provided
;                by VMS and provide user friendly procedure calls.
;                Some C and FORTRAN examples are included.
;
;

;
;    These routines are provided as part of the session notes for 
;    DECUS Fall 1989 Symposia, Anaheim, CA, session NE009, 
;    "VAX/VMS LAT Programming Made Simple".  Included are some excerpts
;    from that session which provide documentation for the routines.
;
;
;  The routines:
;
;       lt_init - allocates/opens the port and handle
;       lt_map - sets up the destination for connection
;       lt_connect - performs connection
;       lt_hangup_ast - set up AST to be called on unexpected disconnect
;       lt_conn_info - gets name of connected server port
;       lt_disconnect - performs disconnection
;       lt_close - closes/deallocates port and handle
;
;
;   The lt_ routines:
;
;       Handle busy conditions transparently
;       Give detailed error messages
;       Send error messages to SYS$OUTPUT (this can be changed by 
;          modifying the routine PUT_OUTPUT)
;       Use special routines for descriptor oriented languages (for 
;          example FORTRAN)  - lt_initd, lt_mapd, and lt_conn_infod
;       Return 0 for failure, positive number for success
;
;
;
;   lt_init:
;
;       Opens a channel to the port
;       Returns a handle for use with the other routines
;       Must be called before any other lt_ routine
;       Has alternate for lt_initd for FORTRAN
;       example:  
;           handle = lt_init("LTA100:");
;           HANDLE = LT_INITD('LTA100:')
;
;    parameters for lt_init:
;
;        port_name - string passed by reference
;
;    parameters for lt_initd:
;
;        port_name - string passed by descriptor
;
;
;
;   lt_map:
;
;       Maps destination port information
;       Not required, mapping can be done from LATCP
;       Alternate for lt_mapd for FORTRAN
;       Also sets port Queued or Not-Queued
;       Returns success (Non-zero) or failure (Zero)
;       Requires phy_io privilege
;       For LAT, requires NODE NAME and either SERVICE NAME or PORT NAME
;       format:
;          status = lt_map(handle,node,service,port,link,queued)
;       example:
;          status = lt_map(handle,"MYSERVER",0,"PORT_2",0,0);
;
;       parameters for lt_map:
;
;          handle - longword passed by value or reference, returned 
;                   by lt_init or lt_initd
;          node - string passed by reference or NULL passed by value
;          service - string passed by reference or NULL passed by value
;          port - string passed by reference or NULL passed by value
;          link - string passed by reference or NULL passed by value
;          queued - integer (0 or 1) passed by value or reference.
;          all parameters are read only
;
;       parameters for lt_mapd:
;
;          handle - longword passed by value or reference, returned 
;                   by lt_init or lt_initd
;          node - string passed by descriptor or NULL passed by value or reference
;          service - string passed by descriptor or NULL passed by value or reference
;          port - string passed by descriptor or NULL passed by value or reference
;          link - string passed by descriptor or NULL passed by value or reference
;          queued - integer (0 or 1) passed by value or reference.
;          all parameters are read only
;
;
;   lt_connect:
;
;       Performs connection
;       Returns success (non-zero) or failure (zero)
;       example:
;          status = lt_connect(handle);
;
;   parameters for lt_connect:
;
;          handle - longword passed by value or reference, returned 
;                   by lt_init or lt_initd
;         
;   lt_disconnect:
;
;       Performs disconnection
;       Returns success (non-zero) even if port was not connected
;       example:
;          status = lt_disconnect(handle);
;
;   parameters for lt_disconnect:
;
;          handle - longword passed by value or reference, returned 
;                   by lt_init or lt_initd
;
;
;
;
;
;   lt_hangup_ast:
;
;         Sets up a hangup AST to be called when unsolicited disconnect 
;               occurs
;         Must be reset each time the AST is called
;         Returns success (non-zero) or failure (zero)
;         Actually sets up a control-y AST for the port, so the
;             AST parameter should be checked for SS$_HANGUP
;         A separate AST routine is required for each LAT port used, as there
;             is no indication of which port disconnected.
;         example:
;             int ast_function();
;             sts = lt_hangup_ast(handle, ast_function)
; 
;  parameters for lt_hangup_ast:
;
;          handle - longword passed by value or reference, returned 
;                   by lt_init or lt_initd
;          ast_function - routine entry point passed by reference
;
;
;
;
;  lt_conn_info:
;
;          Returns server port name to which LAT is connected
;          Returns string NODE/PORT, or blank string if no connection
;          Return value is length of string, 0 if no connection
;          Alternate form lt_conn_infod for FORTRAN
;          Buffer is assumed to be 34 characters long in lt_conn_info
;            Server_name (up to 16 chars), '/' char,
;            port_name (up to 16 chars), terminating NULL.
;          Buffer will be NULL terminated
;          example:
;            len = lt_conn_info(handle,namebuf)
;            LEN = LT_CONN_INFOD(HANDLE,NAMEBUF)
;
;  parameters for lt_conn_info:
;
;          handle - longword passed by value or reference, returned 
;                   by lt_init or lt_initd
;          namebuf - char array, passed by reference (will be modified)
;                   assumed to be at least 34 chars long
;
;  parameters for lt_conn_infod:
;
;          handle - longword passed by value or reference, returned 
;                   by lt_init or lt_initd
;          namebuf - char variable, passed by descriptor (any length,
;                   length is passed in as part of the descriptor) 
;                   will be modified.
;
;
;
;
;   lt_close:
;
;          Closes and deallocates the port
;          Frees up a slot to open another port (number of slots is
;             assembly time parameter for lt_routines).
;          Must be the last routine called for the port
;          It is not necessary to call lt_close if the program is
;             about to terminate anyway, image rundown will do it.
;          example:
;             status = lt_close(handle)
;          parameter: handle is longword returned by lt_init or lt_initd
;
;
;
;

;
;
;
;   Sample Program 1
;
;
;    main()
;    {
;        int handle1, sts, len;
;        char buf [34];  /* buffer for lt_conn_info */
;
;        handle1 = lt_init("LTA100:");  /* Open the port */
;        sts = lt_map(handle1,"SERVER1","XPRINT",0,0,0);
;        if (!sts) exit(); /* Just exit, error message was sent by lt_map */
;        sts = lt_connect(handle1);
;        if (!sts) exit();
;        sts = lt_hangup_ast(handle1,disc_ast);
;        if (!sts) exit();
;        len = lt_conn_info(handle1,buf);
;        printf("Remote port =%s (len=%d)\n",buf,len);
;           /* Here's where all the reads and writes get done */
;           /* IE the real work */
;        sts = lt_disconnect(handle1);
;        sts = lt_close(handle1);
;    }
;    
;
;
;   Sample Program 2, shows multiple LAT ports used
;
;    int disc_ast1(int stat), disc_ast2(int stat);
;    main()
;    {
;        int handle1, handle2;
;        handle1 = lt_init("LTA100:");
;        handle2 = lt_init("LTA101:");
;
;        lt_connect(handle1);
;        lt_connect(handle2);
;
;        lt_hangup_ast(handle1,disc_ast1);
;        lt_hangup_ast(handle2,disc_ast2);
;
;           ...
;     }
;
;   Sample Program 3, FORTRAN
;
;    PROGRAM LAST_TEST
;    CHARACTER*34 BUF
;    INTEGER HANDLE1,STS,LEN
;    INTEGER LT_INITD,LT_MAPD,LT_CONNECT,LT_DISCONNECT,LT_CONN_INFOD,LT_CLOSE
;    EXTERNAL LT_INITD,LT_MAPD,LT_CONNECT,LT_DISCONNECT,LT_CONN_INFOD,LT_CLOSE
;    EXTERNAL HANGUP_AST_SUBR
;
;    HANDLE1 = LT_INITD('LTA100:')
;    STS = LT_MAPD(HANDLE1,'SERVER1','XPRINT',,,)
;    STS = LT_CONNECT(HANDLE1)
;    STS = LT_HANGUP_AST(HANDLE1,HANGUP_AST_SUBR)
;    LEN = LT_CONN_INFOD(HANDLE1,BUF)
;    TYPE *,' PORT=',BUF,' LEN=',LEN
;             C  DO READS AND WRITES AND USEFUL WORK HERE
;    STS = LT_DISCONNECT(HANDLE1)
;    STS = LT_CLOSE(HANDLE1)
;    STOP
;    END
;
;   SUBROUTINE HANGUP_AST_SUBR(PAR)
;   INTEGER PAR
;   IF (PAR .EQ. SS$_HANGUP) TYPE *,' Hangup Seen'
;   RETURN
;   END
;
;

;        
;        
;  lt_ routines for LAT application port manipulation
;
;   Revision 1, November 3, 1989
;   Robert Simon
;   Network Equipment Technologies (formerly Comdesign)
;   Santa Barbara, CA 93117
;
;
;  lt_ routines.  These routines are used to manipulate LAT and CCA
;    applicaton ports.  They are set up to be callable from common 
;    VAX/VMS languages and have been tested from FORTRAN and C.  
;    In the case where a string is passed in, the "d" form of the 
;    routine can be used from languages which pass in descriptors.
;
;    A trick is used when the port "handle" is passed in and when
;    the queuing flag is passed in to determine if the argument is
;    being passed by value or by reference.  Since these arguments
;    should be less than 512 in all cases, if the argument is
;    less than 512 it is presumed to have been passed by value,
;    if it is larger, then it is assumed to have been passed by
;    reference and it is dereferenced.
;
;  These routines are provided for instructional purposes only.
;  Network Equipment Technologies assumes no responsibility for
;  the use of these routines or their fitness for use in any
;  application.
;




        $IODEF GLOBAL

	.SBTTL Assembly Time Parameters

;
; Assembly Time Parameters
;
;     MAX_PORTS - maximum number of LAT ports open at the same
;                 time.  Should never exceed 511, otherwise the
;                 routines which detect if they're being called by
;                 reference or by value won't work right.
;
        MAX_PORTS=16

	.SBTTL Static Data
;
;
;  Static Data Variables
;
;     CHAN_LIST - a channel for each open LAT port.  Each channel
;              is one word (2 bytes).  The array of words is actually 
;              MAX_PORTS+1 long, since the first element (channel
;              number 0) is never used, since returning a channel
;              number of 0 would look like an error return.
;
        .psect ltr_data long,noexe,wrt
CHAN_LIST:
        .BLKW   <MAX_PORTS+1>    ; never give out slot 0

;
;  Static Data Constants
;
;     Lists of messages used to print out a message when the
;     connect QIO request returns status of SS$_ABORT
;
ABT_LIST:
        .long   rsn0            ; array of pointers to descriptors
        .long   rsn1
        .long   rsn2
        .long   rsn3
        .long   rsn4
        .long   rsn5
        .long   rsn6
        .long   rsn7
        .long   rsn8
        .long   rsn9
        .long   rsn10
        .long   rsn11
        .long   rsn12
        .long   rsn13
        .long   rsn14
        .long   rsn15
        .long   rsn16
        .long   rsn17
        .long   rsn18
        .long   rsn19

;  Descriptors for reason messages
;
;
rsn0:   .ascid /Unknown failure reason=0/
rsn1:   .ascid /Unknown failure reason=1/
rsn2:   .ascid /System shutdown in progress/
rsn3:   .ascid /Unknown failure reason=3/
rsn4:   .ascid /Unknown failure reason=4/
rsn5:   .ascid /Insufficient resources at server/
rsn6:   .ascid /Port or service in use/
rsn7:   .ascid /No such service/
rsn8:   .ascid /Service is disabled/
rsn9:   .ascid /Service is not offered on the requested port/
rsn10:  .ascid /Port name is unknown/
rsn11:  .ascid /Unknown Failure Reason=11/
rsn12:  .ascid /Unknown Failure Reason=12/
rsn13:  .ascid /Immediate access rejected, invalid password/
rsn14:  .ascid /Access denied, group code conflict/
rsn15:  .ascid /Corrupted request/
rsn16:  .ascid /Requested function is not supported/
rsn17:  .ascid /Session cannot be started/
rsn18:  .ascid /Queue entry deleted by server/
rsn19:  .ascid /Illegal request parameters/

;  Message to print out when Connect QIO returns status SS$_TIMEOUT
;
to_err: .ascid /Connect timeout, requested resource not found/
        .psect ltr_code long,exe,nowrt
;
;  NAME_SIZE = how long a name may be
;
NAME_SIZE=16



	.SBTTL  lt_init, open and allocate port

;
;  lt_init -    This is the first routine called.  It allocates a 
;               channel for the port and opens the channel.
;  Inputs:
;           lt_init:  Null terminated string with port name DDCU:
;           lt_initd: Descriptor of string with port name DDCU:
;
;

init_id: .ascid /LT_INIT/           ; Name string for error messages
chan_full: .ascid /LT_INIT: No free channel slots/  ; error message
LI_PNAME=4                          ; offset of port name argument

.global LT_INIT                     ; Entry for lt_init
.entry LT_INIT,^M<R2,R3,R4,R5,R6>
        MOVAB   GET_STRING,R6       ; use GET_STRING() to get args
        BRB     lt_init_xxx
.global LT_INITD                    ; Entry for lt_initd
.entry LT_INITD,^M<R2,R3,R4,R5,R6>
        MOVAB   GET_DESC,R6         ; use GET_DESC() to get arguments
        BRB     lt_init_xxx

lt_init_xxx:
        CLRQ    -(SP)        ; quadword to build descriptor on stack
        MOVAB   (SP),R4         ; descriptor address
        MOVZBL  #1,R5           ; return value - assume success
        ;; try to allocate a free channel
        MOVZBL  #1,R3           ; search free channel list
1$:     TSTW    CHAN_LIST[R3]   ; free channel?
        BEQL    10$             ; yes
        AOBLEQ  #MAX_PORTS,R3,1$ ; no, loop and try again
        PUSHAB  chan_full       ; no free chans, show error msg
        CALLS   #1,PUT_OUTPUT   ; write it out 
        CLRL    R5              ; set return code to error status
        BRW     99$             ; and be done
10$:    MOVL    R3,R5           ; set up to return chan number
        ;; build a descriptor for the name and assign the channel
        PUSHL   LI_PNAME(AP)    ; get port name argument 
        CALLS   #1,(R6)         ; call string arg processing routine
        MOVQ    R0,(R4)         ; move descriptor onto local stack
        $ASSIGN_S       DEVNAM=(R4),chan=CHAN_LIST[R3] ; assign chan
        ; check assign status
        BLBS    R0,99$          ; we did it, we're done
        CLRL    R5              ; failed, set failure return status
        PUSHL   R0              ; push bad status
        PUSHAB  init_id         ; push our routine name
        CALLS   #1,PUT_OUTPUT   ; print our routine name
        CALLS   #1,PUT_MESSAGE  ; format our bad status
99$:    MOVL    R5,R0           ; return status into R0
        RET                     ; and be done




	.SBTTL lt_connect, perform connection
;
;  lt_connect - called to initiate a connection on a LAT 
;               application port.  Called with a handle which 
;               has been allocated by lt_init
;   Input:
;           Handle which was returned by lt_init or lt_initd
;
;  Note that if the connection fails with SS$_DEVOFFLINE, the
;  connection will be tried once per second for nine additional
;  seconds, since sometimes the port is still disconnecting
;  from last time when a new connection is attempted.

LTC_HANDLE=4                            ; handleis first arguement
connect_id: .ascid /LT_CONNECT/         ; name for error messages
.global LT_CONNECT
.entry LT_CONNECT,^M<R2,R3,R4,R5>
        MOVZBL  #1,R5           ; return value - assume success
        CLRQ    -(SP)           ; Allocate an IOSB on the stack
        MOVAB   (SP),R2         ; save address of IOSB in R2

        MOVL    #10,R3          ; retry counter, 10 times
1$:   ;; loop: try 10 times to connect, wait 1 second between tries
        MOVL    LTC_HANDLE(AP),R1 ; get the handle
        CMPL    R1,#512         ; is it a handle or an addres?
        BLSSU   4$              ; it's less than 512, it's a handle
        MOVL    (R1),R1         ; it's an address, get the handle
4$:     MOVZBL  R1,R1           ; Mask unwanted bytes
        $QIOW_S -               ; initiate connection
                IOSB=(R2),-
                FUNC=#<IO$_TTY_PORT!IO$M_LT_CONNECT>,-
                CHAN=CHAN_LIST[R1]
        BLBC    R0,20$          ; check return status - bad status
        MOVZWL  (R2),R0         ; return status ok, get IOSB status
        BLBS    R0,99$          ; ok IOSB status, all done
20$:    ; failure, some kind of error
        CMPW    R0,#SS$_DEVOFFLINE ; special case?, wait and retry
        BNEQ    21$             ; no, not spec case, some other error
        DECL    R3              ; decrement retry counter
        BLSS    21$             ; branch if ran out of retries
        CALLS   #0,WAIT_SEC     ; still retrying,wait one second
        BRW     1$              ; and go try again
21$:    ; didn't connect, return failure status, show error msg
        MOVZWL  R0,R4           ; save error status into R4
        CLRL    R5              ; set return code to failure status
        PUSHAB  connect_id      ; routine name string for error msg
        CALLS   #1,PUT_OUTPUT   ; print out routine name
        CMPW    R4,#SS$_TIMEOUT ; timeout? (If so then special msg)
        BNEQ    25$             ; no, go check for other errors
        PUSHAB  to_err          ; yes, use special timeout msg
        CALLS   #1,PUT_OUTPUT   ; print it out
        BRW     99$             ; and be done

25$:    CMPW    R4,#SS$_ABORT   ; abort status? (If so, show reason)
        BNEQ    30$             ; no, skip reason message
        MOVZBL  2(R2),R0        ; get abort reason from IOSB+2
        PUSHL   ABT_LIST[R0]    ; get descriptor for message
        CALLS   #1,PUT_OUTPUT   ; show it
30$:    PUSHL   R4              ; get error status
        CALLS   #1,PUT_MESSAGE  ; format error message
99$:    MOVL    R5,R0           ; put return status into R0
        RET                     ; done


	.SBTTL lt_close, close and deallocate port
;
; lt_close - deallocate the handle, close the channel
;
;  Input:  Handle returned by lt_init
;

close_id: .ascid /LT_CLOSE/
.global lt_close
.entry lt_close,^M<R2,R3,R4,R5>
        MOVL    LC_HANDLE(AP),R2; get the handle
        CMPL    R2,#512         ; is it a handle or an addres?
        BLSSU   4$              ; less than 512, it's a handle
        MOVL    (R2),R2         ; it's an address, get handle
4$:     MOVZBL  R2,R2           ; Mask unwanted bytes
        MOVZBL  #1,R5           ; assume success
        $DASSGN_S  CHAN=CHAN_LIST[R2] ; deassign the channel
        BLBC    R0,20$          ; failure, go print message
        CLRW    CHAN_LIST[R2]   ; success, free channel ind
        BRW     99$             ; done
20$:    CLRL    R5              ; failure, set return code
        PUSHL   R0              ; push bad status
        PUSHAB  close_id        ; push routine name
        CALLS   #1,PUT_OUTPUT   ; show routine name
        CALLS   #1,PUT_MESSAGE  ; format bad status
99$:    MOVL    R5,R0           ; return status into R0
        RET                     ; done



	.SBTTL lt_disconnect, perform disconnection
;
;  lt_disconnect - initiate disconnect 
;
;   Inputs:   Handle which was returned by lt_init
;

LTD_HANDLE=4                    ; handle is first argument

disc_id: .ascid /LT_DISCONNECT/

.global LT_DISCONNECT
.entry LT_DISCONNECT,^M<R2,R3,R4,R5>
        MOVZBL  #1,R5           ; return status - assume success
        CLRQ    -(SP)           ; allocate IOSB on local stack
        MOVAB   (SP),R2         ; IOSB address into R2
        MOVL    LTD_HANDLE(AP),R1 ; get the handle
        CMPL    R1,#512         ; is it a handle or an addres?
        BLSSU   4$              ; less than 512, it's a handle
        MOVL    (R1),R1         ; it's an address, get the handle
4$:     MOVZBL  R1,R1           ; Mask unwanted bytes
        $QIOW_S -               ; initiate disconnect
                IOSB=(R2),-
                FUNC=#<IO$_TTY_PORT!IO$M_LT_DISCON>,-
                CHAN=CHAN_LIST[R1]
        BLBC    R0,20$          ; check return status - bad status
        MOVZWL  (R2),R0         ; return ok, get IOSB status
        BLBS    R0,99$          ; ok IOSB status, all done
20$:    ;; some kind of error
        CLRL    R5              ; failure return status
        PUSHL   R0              ; bad status
        PUSHAB  disc_id         ; routine name string
        CALLS   #1,PUT_OUTPUT   ; show routine name string
        CALLS   #1,PUT_MESSAGE  ; format bad status
99$:    MOVL    R5,R0           ; return status into R0
        RET                     ; done




	.SBTTL lt_map, set destination and queuing option

; lt_map - set up connection destination and queuing option.
;          Calling the QIO requires phy_io privilege.
;
; format: lt_map(handle, node, serv, port, link, q_option)
;    or  lt_mapd(handle, node, serv, port, link, q_option)
;
;   String inputs may be passed as strings, zeroes or omitted 
;   args. C language zeroes and FORTRAN omitted args are 0
;   passed by value which is checked.  FORTRAN 0 is 0 passed
;   by reference which looks like a 0 length descriptor.
;
map_id: .ascid /LT_MAP/         ; routine name for messages
def_link: .ascid /LAT$LINK/     ; default link in case caller
                                ; omits it (QIO requires it)

I3_BUFLEN=0                     ; format of itemlist type 3
I3_ITM=2        ; item code
I3_PTR=4        ; string pointer
I3_RETLEN=8     ; return length (not used for map_port QIO)
I3_ENTRY=12     ; size of one entry
LM_HANDLE=4                     ; argument list
LM_NOD=8        ; node name
LM_SRV=12       ; service name
LM_POR=16       ; port name
LM_LNK=20       ; link name
LM_QUE=24       ; queuing option

.global lt_map,lt_mapd
.entry lt_map,^M<R2,R3,R4,R5,R6>
        MOVAB   GET_STRING,R6           ; null term string routine
        BRW     lt_map_xxx
.entry lt_mapd,^M<R2,R3,R4,R5,R6>
        MOVAB   GET_DESC,R6             ; desc string routine
        BRW     lt_map_xxx
lt_map_xxx:                             ; make room for itemlist
        SUBL    #<I3_ENTRY*5>,SP        ; with 5 entries on stack 
        CLRQ    -(SP)                   ; room for IOSB
        MOVAB   (SP),R4                 ; address of IOSB & item list
        MOVAB   8(R4),R5                ; pointer to 1st itmlst entry
        ; process link entry - use default if none given
        PUSHL   LM_LNK(AP)              ; get link addr
        CALLS   #1,(R6)                 ; process the arg
        TSTL    R0                      ; any link specified?
        BNEQ    10$                     ; yes
        MOVQ    def_link,R0             ; No, use default link desc
10$:    MOVQ    R0,(R5)                 ; fill in buflen and pointer
        MOVW    #IO$V_LT_MAP_LNKNAM,I3_ITM(R5) ; fill in itemcode
        CLRL    I3_RETLEN(R5)           ; retlen address is not used
        MOVAB   I3_ENTRY(R5),R5         ; advance to next entry
        ; process node if present
        PUSHL   LM_NOD(AP)              ; get node argument
        CALLS   #1,(R6)                 ; process it
        TSTL    R0                      ; any node name?
        BEQL    20$                     ; no, skip it
        MOVQ    R0,(R5)                 ; fill in buflen and pointer

        MOVW    #IO$V_LT_MAP_NODNAM,I3_ITM(R5) ; fill in item code
        CLRL    I3_RETLEN(R5)           ; retlen isn't used
        MOVAB   I3_ENTRY(R5),R5         ; advance to next entry
20$:    ; process port if present
        PUSHL   LM_POR(AP)              ; get port argument
        CALLS   #1,(R6)                 ; process it
        TSTL    R0                      ; any port name?
        BEQL    30$                     ; no, skip it
        MOVQ    R0,(R5)                 ; fill in buflen and pointer
        MOVW    #IO$V_LT_MAP_PORNAM,I3_ITM(R5) ; fill in item code
        CLRL    I3_RETLEN(R5)           ; retlen isn't used
        MOVAB   I3_ENTRY(R5),R5         ; advance to next entry
30$:    ; process service if present 
        PUSHL   LM_SRV(AP)              ; get service name argument
        CALLS   #1,(R6)                 ; process it
        TSTL    R0                      ; any serv name?
        BEQL    40$                     ; no
        MOVQ    R0,(R5)                 ; fill in buflen and pointer
        MOVW    #IO$V_LT_MAP_SRVNAM,I3_ITM(R5) ; fill in item code
        CLRL    I3_RETLEN(R5)           ; retlen isn't used
        MOVAB   I3_ENTRY(R5),R5         ; advance to next entry
40$:    ; terminate list
        CLRL    (R5)                    ; longword of 0
        ; get the handle argument
        MOVL    LM_HANDLE(AP),R1        ; get the handle
        CMPL    R1,#512                 ; is it a handle or an addres?
        BLSSU   44$                     ; less than 512, it's a handle
        MOVL    (R1),R1                 ; it's an address, get handle
44$:    MOVZBL  R1,R1                   ; Mask unwanted bytes
        ; get the queuing option
        MOVL    LM_QUE(AP),R2           ; get queueing arg
        CMPL    R1,#512                 ; Is it q bit or an address?
        BLSSU   45$                     ; less than 512, it's q bit
        MOVL    (R2),R2                 ; it's an address, get q bit
45$:    MOVZBL  R2,R2                   ; Mask unwanted bits
        $QIOW_S P2=R2,-                 ; queuing option
                P1=8(R4),-              ; itemlist
                IOSB=(R4),-
                FUNC=#<IO$_TTY_PORT!IO$M_LT_MAP_PORT>,-
                CHAN=CHAN_LIST[R1]
        MOVZBL  #1,R5                   ; ret status,assume success
        BLBC    R0,80$                  ; return value, failure
        MOVZWL  (R4),R0                 ; ok return, get IOSB status
        BLBS    R0,99$                  ; IOSB ok, done
80$:    PUSHL   R0                      ; push error code
        PUSHAB  map_id                  ; push routine name
        CALLS   #1,PUT_OUTPUT           ; print routine name
        CALLS   #1,PUT_MESSAGE          ; format error code
        CLRL    R5                      ; show failure ret code
99$:    MOVL    R5,R0                   ; ret code into R0
        RET                             ; done



	.SBTTL lt_conn_info, get remote port name
;
;  lt_conn_info - returns name of terminal server port to which 
;                 this port is connected.
;
;   Inputs:  Handle - returned by lt_init
;            Dest   - buffer (lt_conn_info) or descriptor
;            pointing to buffer (lt_conn_infod)
;
CI_DEF_BUF_SIZE=<<NAME_SIZE*2>+2> ; room for server name, /, port name, and NULL
LC_HANDLE=4     ; argument list - handle
LC_DEST=8       ; dest string or buffer
conn_info_id: .ascid /LT_CONN_INFO/     ; routine name for error msg

.global lt_conn_info,lt_conn_infod
.entry lt_conn_info,^M<R2,R3,R4,R5,R6,R7,R8> ; entry for string 
        CLRL    R8                      ; R8: 0=str, 1=desc
        BRW     lt_conn_info_xxx
.entry lt_conn_infod,^M<R2,R3,R4,R5,R6,R7,R8> ; entry for desc
        MOVZBL  #1,R8                   ; R8: 0=str, 1=desc
        BRW     lt_conn_info_xxx
lt_conn_info_xxx:
        SUBL    #<I3_ENTRY*2>,SP        ; room for 2 entry itemlist
        MOVAB   (SP),R4                 ; address of itemlist in R4
        SUBL    #64,SP                  ; room for getdvi return
        MOVAB   (SP),R5                 ;   string, put address in R5
        CLRQ    -(SP)                   ; alloc IOSB on local stack
        MOVAB   (SP),R6                 ; address of IOSB into R6
        ; build itemlist
        MOVL    #64,I3_BUFLEN(R4)       ; buffer len (max return is 64 chars)
        MOVW    #DVI$_TT_ACCPORNAM,I3_ITM(R4)   ; item code 
        MOVAB   (R5),I3_PTR(R4)         ; point to buffer
        MOVAB   I3_BUFLEN(R4),I3_RETLEN(R4) ; use buflen for ret len
        CLRL    I3_ENTRY(R4)            ; longword of 0 at end
        MOVL    LC_HANDLE(AP),R1        ; get the handle
        CMPL    R1,#512                 ; is it a handle or an addres?
        BLSSU   4$                      ; less than 512, it's a handle
        MOVL    (R1),R1                 ; it's an address, get handle 
4$:     MOVZBL  R1,R1                   ; Mask unwanted bytes
        $GETDVIW_S  CHAN=CHAN_LIST[R1],ITMLST=(R4),IOSB=(R6)
        BLBC    R0,20$                  ; return status failed
        MOVZWL  (R6),R0                 ; return ok, check IOSB
        BLBC    R0,20$                  ; IOSB failed
        MOVZWL  (R4),R0                 ; string 0 len?
        BLEQ    30$                     ; yes, no error but
                                        ; return blank string
        BRW     80$                     ; go copy the string
20$:    ; an error, show error message
        PUSHL   R0                      ; push error code
        PUSHAB  conn_info_id            ; push routine name
        CALLS   #1,PUT_OUTPUT           ; print routine name
        CALLS   #1,PUT_MESSAGE          ; format error message
        ; error or 0 len string, set up for 0 len copy
30$:    CLRL    R0                      ; No string, copy 0 chars
80$:    MOVL    #CI_DEF_BUF_SIZE,R1     ; assume user buffer is default size(34)
        MOVL    LC_DEST(AP),R2          ; assume pointer to user buf
        BLBC    R8,81$                  ; using strings, no user len
        MOVZWL  (R2),R1                 ; use len from desc
        ; fill in user's buffer, null padded
        MOVL    4(R2),R2                ; use desc pointer
81$:    PUSHR   #^M<R0,R1,R2,R3,R4,R5>
        MOVC5   R0,(R5),#0,R1,(R2)      ; copy to user's buffer
        POPR    #^M<R0,R1,R2,R3,R4,R5>
        MOVL    R0,R0                   ; return length that's in R0
        RET



	.SBTTL lt_hangup_ast, set up a hangup AST 
;
;
; lt_hangup_ast -  set up a hangup AST to be called if there's an
;                  unexpected hangup
;  Inputs:
;          handle - returned from lt_init
;           ast address - address of function to call
;

LH_AST=8                ; argument list, hangup AST
LH_HANDLE=4             ; handle

hangup_id: .ascid /LT_HANGUP/           ; routine name for error msgs
.global lt_hangup_ast
.entry lt_hangup_ast,^M<R2,R3,R4,R5,R6,R7,R8>
        MOVZBL  #1,R5           ; return status, assume success
        CLRQ    -(SP)           ; allocate IOSB on local stack
        MOVAB   (SP),R2         ; address of IOSB into R2
        MOVL    LH_HANDLE(AP),R1 ; get the handle
        CMPL    R1,#512         ; is it a handle or an addres?
        BLSSU   4$              ; less than 512, it's a handle
        MOVL    (R1),R1         ; it's an address, get the handle
4$:     MOVZBL  R1,R1           ; Mask unwanted bytes
        $QIOW_S -
                P1=@LH_AST(AP),-        ;P1=AST
                IOSB=(R2),-
                FUNC=#<IO$_SETMODE!IO$M_CTRLYAST>,-
                CHAN=CHAN_LIST[R1]
        BLBC    R0,20$          ; return stat, bad status
        MOVZWL  (R2),R0         ; return ok, check IOSB
        BLBS    R0,99$          ; ok IOSB status
20$:    CLRL    R5              ; failure status
        PUSHL   R0              ; push bad status
        PUSHAB  hangup_id       ; push routine name
        CALLS   #1,PUT_OUTPUT   ; show routine name
        CALLS   #1,PUT_MESSAGE  ; format bad status
99$:    MOVL    R5,R0           ; return status into R0
        RET                     ; done



	.SBTTL UTILITY ROUTINES
;
;
;  *** Utility Routines ****
;
;
;

; get string len - returns length of null terminated string

.entry GET_STR_LEN,^M<R2,R3,R4,R5>
        LOCC    #0,#24,@4(AP)   ; look for null char
        SUBL3   R0,#24,R0       ; len = 24-chars left
        RET


;
;  PUT_MESSAGE - formats an error message and writes it out
;  Inputs:
;      VMS error code
;
; local stack
PM_SDESC=0              ; descriptor for output string
PM_STRING=8             ; buffer for string
conv_err: .ascid /Put Message conversion error/
.entry PUT_MESSAGE,^M<R2,R3,R4,R5>
        SUBL    #<128+8>,SP     ; make room for local variables
        MOVAB   (SP),R4         ; local stack addressability 
        MOVZBL  #128,PM_SDESC(R4)               ; desc.buflen
        MOVAB   PM_STRING(R4),PM_SDESC+4(R4)    ; desc.ptr
        $GETMSG_S  MSGID=4(AP),-                ; error code
                   MSGLEN=PM_SDESC(R4),-        ; out put len
                   BUFADR=PM_SDESC(R4),-        ; input descriptor
                   FLAGS=#^X0F                  ; flags (show all)

                   
        MOVAB   PM_SDESC(R4),R2 ; assume use of formatted string
        BLBS    R0,10$          ; formatted OK
        MOVAB   conv_err,R2     ; error, use generic error string
10$:    PUSHL   R2              ; push string
        CALLS   #1,PUT_OUTPUT   ; show string
        RET                     ; done


;
; PUT_OUTPUT - writes output of error messages to the user.
;
;  This is a jacket routine, all output goes through this 
;  routine.  It can be modified to send output other places.
;
;  Inputs: descriptor of string to be printed
;
.entry PUT_OUTPUT,^M<>                  ; routine to write to output device
        PUSHL   4(AP)                   ; push our one arg
        CALLS   #1,G^LIB$PUT_OUTPUT     ; call output routine
        RET                             ; done


;
; WAIT_SEC - waits one second
;
wait_err: .ascid /WAIT_SEC_ERR/
.global WAIT_SEC
.entry WAIT_SEC,^M<R2,R3,R4,R5>
        CLRQ    -(SP)           ; place for local vars (time and ef)
        MOVAB   (SP),R4         ; address of local vars
        PUSHAL  (R4)            ; use local for EF return value
        CALLS   #1,G^LIB$GET_EF ; get an event flag
        BLBC    R0,101$         ; failed, skip it
        MOVZBL  (R4),R2         ; put EF into R2
        MOVL    #-1000*1000*10,(R4) ; use local var for time, 1 sec
        MOVL    #-1,4(R4)       ; high part
        $SETIMR_S       EFN=R2,daytim=(R4) ; set the timer
        BLBC    R0,101$         ; failed, skip it
        $WAITFR_S       EFN=R2  ; wait for the flag
        BLBC    R0,101$         ; failed, skip it
        MOVL    R2,(R4)         ; set up addr of ef
        PUSHAB  (R4)            ; push it
        CALLS   #1,G^LIB$FREE_EF ; free the ef
99$:    RET                     ; done
101$:   ; error, show it
        PUSHL   R0              ; error code
        PUSHAB  wait_err        ; routine name
        CALLS   #1,PUT_OUTPUT   ; show routine name
        CALLS   #1,PUT_MESSAGE  ; show error code
        BRW     99$             ; done

;  GET_STRING and GET_DESC are used to get string arguments
;  to functions.  Both return a descriptor in R0-R1
;
.global GET_STRING                      ; get a null term string
.entry GET_STRING,^M<R2>
        CLRQ    R0                      ; assume no string
        MOVL    4(AP),R2                ; get the arg
        BEQL    99$                     ; no arg, done
        PUSHL   R2                      ; got an arg
        CALLS   #1,GET_STR_LEN          ; get the length in R0
        MOVL    R2,R1                   ; put adress into R1
99$:    RET                             ; done

.global GET_DESC                        ; get descriptor string
.entry GET_DESC,^M<R2>
        CLRQ    R0                      ; assume no string
        TSTL    4(AP)                   ; test the arg
        BEQL    99$                     ; no arg, done
        MOVQ    @4(AP),R0               ; get the desc into R0,R1
99$:    RET                             ; done


.end    
        
