Google
 

Trailing-Edge - PDP-10 Archives - BB-P363B-SM_1985 - t20/nmlt20/nmldlw.b36
There is 1 other file named nmldlw.b36 in the archive. Click here to see a list.
! UPD ID= 334, SNARK:<6.1.NML>NMLDLW.B36.32,  10-Jul-85 13:57:34 by MCCOLLUM
! Make DLW_SERVICE_EVENT a global routine and rename it to
! NML$DLW_SERVICE_EVENT
!
! UPD ID= 324, SNARK:<6.1.NML>NMLDLW.B36.23,  10-May-85 15:35:11 by GLINDELL
!  1. When waiting for NI activity, don't set up a timeout value unless
!     the circuit substate is one of the ones indicating a load or dump.
!     If no load or dump is in progress, then there is no need to wake
!     up every MOP_TIME_OUT seconds to post a 'timeout' entry in the
!     LDAQ queue.
!
!  2. To once and for all address the 'On-starting' problem (?), add a
!     new task (DLW_CHECK_DTE_TASK) that runs 30 seconds after each
!     "successful" initialization of protocol with the MCB.  It turns out
!     that we on the 20 run into some race that we do not understand.  The
!     MCB will seem to initialize successfully but will then commit suicide
!     immediately.  ROUTER will then turn off the circuit for a second and
!     we probably fall into that window.  So, add a task that will be run
!     after each successful initialization.  The task will sleep for 30
!     seconds and then post another 'check circuit'.
!
! UPD ID= 290, SNARK:<6.1.NML>NMLDLW.B36.22,  15-Mar-85 13:13:47 by GLINDELL
!  Change DLW_NI_TASK so that we accept 'request program' and 'request dump
!  service' messages that are addressed to us specifically, and is not on the
!  multicast.
!
! UPD ID= 284, SNARK:<6.1.NML>NMLDLW.B36.21,  13-Mar-85 14:19:40 by GLINDELL
!  Don't care if GET_OTHER_INFO runs into any problems in DUMP code - this
!  circumvents PLUTO bug in 'REQUEST DUMP SERVICE' message.  Put back check
!  on GET_OTHER_INFO when PLUTO does the right thing.
!
! UPD ID= 282, SNARK:<6.1.NML>NMLDLW.B36.20,  11-Mar-85 16:02:36 by GLINDELL
!  More problems with NML running out of memory: move setting of substate
!  to AUTOSERVICE from callers (DLW_WORK and DLW_NI_TASK) into DLW_QUEUE.
!
! UPD ID= 280, SNARK:<6.1.NML>NMLDLW.B36.19,  10-Mar-85 14:26:24 by GLINDELL
!  1. Memory wasn't deallocated in a lot of places.  NMLT20 sooner or later
!     would run out of memory.
!  2. Remove edit history prior to phase IV.
!
! UPD ID= 277, SNARK:<6.1.NML>NMLDLW.B36.18,   6-Mar-85 23:10:43 by GLINDELL
!  1. If we receive anything on the multicast and we are already loading
!     or dumping across the NI, then just throw away the multicast message.
!     Anything on the NI that we really should load can wait since the
!     requestor will retry.
!
!  2. Add hardware address as argument to NML$DLW_READ_LDAQ
!
!  3. Expand on DLW_SERVICE_EVENT
!
! UPD ID= 274, SNARK:<6.1.NML>NMLDLW.B36.17,   6-Mar-85 11:35:46 by GLINDELL
!  Work to support loading and dumping when STATE is SERVICE
!
! UPD ID= 266, SNARK:<6.1.NML>NMLDLW.B36.16,   1-Mar-85 10:02:06 by GLINDELL
!  Lots of work to support POSEIDON, fix ETHERNET dump, clean up DTE reload.
!
! UPD ID= 260, SNARK:<6.1.NML>NMLDLW.B36.15,  10-Feb-85 14:49:47 by GLINDELL
!  1. DTE reload does not work properly any more - reinstate call to
!     NML$CALCULATE_CIRCUIT_STATE
!  2. Remove definitions of STATE/SUBSTATE/SERVICE values
!
! UPD ID= 247, SNARK:<6.1.NML>NMLDLW.B36.14,  25-Jan-85 13:58:43 by HALPIN
! New routines READ_REQUEST_PROGRAM and GET_OTHER_INFO, to extract
! needed info from the MOP Request Program messages.
! Moved the DLWQ filed definitions to NMLCOM.REQ
!
! Remove call to NML$CALCULATE_CIRCUIT_STATE because it overwrites
! the NML -LOADING substate, during a LOAD NODE REQUEST, causing reloads
! to fail.
!
! UPD ID= 226, SNARK:<6.1.NML>NMLDLW.B36.13,  11-Jan-85 15:58:47 by HALPIN
! DTE Data Link watcher calls NML$CALCULATE_CIRCUIT_STATE to insure that
! NML's circuit info is up to date.  Don't try any service functions
! if DTE's STATE is ON with no substate value, i.e. ON-RUNNING, so it
! doesn't reload a running MCB.
!
! UPD ID= 219, SNARK:<6.1.NML>NMLDLW.B36.12,  17-Dec-84 10:25:27 by HALPIN
! Reply to a REQUEST_DUMP_SERVICE message on the NI with ASSISTANCE_
! VOLUNTEER before queueing up the DUMP Request to NML.
!
! UPD ID= 216, SNARK:<6.1.NML>NMLDLW.B36.11,  12-Dec-84 18:46:54 by HALPIN
! Fix somemore (and I hope all now!) MOP Buffer Deallocation bugs in
! DLW_NI_TASK.
!
! UPD ID= 212, SNARK:<6.1.NML>NMLDLW.B36.10,  12-Dec-84 10:19:32 by HALPIN
! Change the formating of the 'LOAD NODE' NICE messgae in DLW_QUEUE.  Only
! put the Node name into the Entity Id field, not both name and number.
! In DLW_NI_TASK deallocate the MOP Buffer if there is no active Load or
! Dump requests to send it to.
!
! UPD ID= 203, SNARK:<6.1.NML>NMLDLW.B36.8,  10-Dec-84 15:22:09 by HALPIN
! Get MONSYM Library file out of default directory, not BLI:
!
! UPD ID= 184, SNARK:<6.1.NML>NMLDLW.B36.7,   7-Dec-84 09:28:21 by HALPIN
! Assign an interrupt channel for NI Receives.  Pass PSI channel numbers
! in calls to $NMU_DLX_OPEN.
! New routine DLW_NI_RECEIVER, called when a Receive Interrupt occurs
! from the NI% JSYS.
! DLW_NI_TASK receives all messages on the NI's LOAD_DUMP Portal.  Sends
! any messages for an active LOAD/DUMP request to NML via the Load/Dump
! Assistance Queue (LDAQ). Any received messages on the Load/Dump Assistance
! Multicast Address generates a new Load/Dump Service request.
! New Global routine, NML$DLW_READ_LDAQ, provided for NMLDTL to read
! MOP messages off the LDAQueue.
!
! UPD ID= 176, SNARK:<6.1.NML>NMLDLW.B36.6,  19-Nov-84 15:50:59 by HALPIN
! Fix Byte pointer to NI circuit id in DLW_NI_TASK.
!
! UPD ID= 174, SNARK:<6.1.NML>NMLDLW.B36.5,  19-Nov-84 10:27:06 by HALPIN
! Create new Data Link Watcher Task for the NI.  It will Enable the
! LOAD/DUMP ASSISTANCE Multicast address on NI-0-0 and watch for Request
! Program (LOAD) and Request Dump Service message.
!
! UPD ID= 121, SNARK:<6.1.NML>NMLDLW.B36.4,   5-Oct-84 14:29:36 by HALPIN
! Allow DLW to service a circuit if the substate is STARTING.
!
! UPD ID= 107, SLICE:<6.1.NML>NMLDLW.B36.3,  18-Sep-84 16:23:25 by GUNN
! WORK:<GUNN.NML>NMLDLW.B36.2 24-Aug-84 17:04:33, Edit by GUNN
!
! Add dummy argument in call to $NMU_DLX_OPEN to account for 
! PHYSICAL ADDRESS parameter required for Ethernet.
!
! UPD ID= 34, SNARK:<6.1.NML>NMLDLW.B36.2,  24-May-84 16:20:00 by GLINDELL
! DSKC:NMLDLW.B36[10,5665,SOURCE,TOPS10]  13-Dec-83 12:52:08, Edit by GROSSMAN
!
! Fix simultaneous loading of multiple MCB's.  DLW_QUEUE was using a pointer to
! the circuit id (the id was being kept in a local variable).  Unfortunately,
! when DLW_DEQUEUE was called this would point to the last circuit that
! DLW_QUEUE played with.  Fix it by pointing to a service request (DLWQ) block,
! which is a somewhat more permanent structure.
! Edit=54
!
! DSKC:NMLDLW.B36[10,5665,SOURCE,TOPS10]  1-Dec-83 17:08:44, Edit by GROSSMAN
!
! Add many messages to DLW_WORK so that people can see what is going on.
! Edit=53
!
! DSKC:NMLDLW.B36[10,5665,SOURCE,TOPS10]  1-Dec-83 01:41:25, Edit by GROSSMAN
!
! Clean up the routine DLW_TASK by splitting it into two routines.  The new
! routine is called DLW_WORK.
! Edit=52
!
! DSKC:NMLDLW.B36[10,5665,SOURCE,TOPS10]  13-Oct-83 00:17:16, Edit by GROSSMAN
!
! Implement PPN and password verification.
! Always set RB_PRIV to $TRUE in this module.
! Edit=37
!
! PH4:<BUILD>NMLDLW.B36.5  4-Mar-83 16:46:00, Edit by GUNN
!
! Ident 11.
! Merge DECnet-10 changes as of approximately 7-OCT-82.
! Uncomment the 'DECLARE_JSYS (BOOT)'.
!
module NMLDLW   (                       ! Data link watcher task
                 ident = 'X03.06'
                ) =
begin

!
!                       COPYRIGHT (C) 1981,1985 BY
!    DIGITAL EQUIPMENT CORPORATION, MAYNARD, MASSACHUSETTS  01754
!
! THIS SOFTWARE IS FURNISHED  UNDER A LICENSE FOR USE ONLY ON A SINGLE
! COMPUTER  SYSTEM AND  MAY BE  COPIED ONLY WITH  THE INCLUSION OF THE
! ABOVE COPYRIGHT NOTICE.  THIS SOFTWARE, OR ANY OTHER COPIES  THEREOF
! MAY NOT BE PROVIDED OR  OTHERWISE MADE AVAILABLE TO ANY OTHER PERSON
! EXCEPT FOR USE ON SUCH SYSTEM AND TO ONE WHO AGREES TO THESE LICENSE
! TERMS.  TITLE TO AND  OWNERSHIP OF THE  SOFTWARE  SHALL AT ALL TIMES
! REMAIN IN DEC.
!
! THE INFORMATION IN THIS SOFTWARE IS SUBJECT TO CHANGE WITHOUT NOTICE
! AND SHOULD  NOT BE CONSTRUED  AS A COMMITMENT  BY DIGITAL  EQUIPMENT
! CORPORATION.
!
! DEC ASSUMES  NO  RESPONSIBILITY  FOR  THE USE OR  RELIABILITY OF ITS
! SOFTWARE ON EQUIPMENT WHICH IS NOT SUPPLIED BY DEC.
!
!++
!
! Facility: LSG DECnet Management
!
! Abstract: This module contains the code for the TOPS20
!           Data Link Watcher.  It monitors the DTE devices.
!           On startup, it attempts to initiate protocol on
!           the device.  If it fails it issues a LOAD VIA command.
!           When a topology change interrupt occurs, it polls
!           the DTE devices.  If a DTE that has previously been
!           loaded goes offline, it issues a DUMP VIA command; then
!           a LOAD VIA command is issued.
!
! Environment: TOPS20
!
! Author: Steven M. Jenness	Creation date: 30-May-81
!         James J. Halpin       Phase IV upgrade
!
!--
!
! Include files
!

library 'NMLLIB';                       ! Get all required definitions

library 'MONSYM';			! Monitor symbols

library 'JLNKG';			! JSYS linkage definitions

library 'BLI:TENDEF';                   ! Get MONxxx definitions

!
! Global routines
!

forward routine
    NML$DLW_INITIALIZE : novalue,       ! DLW task creation/initialization
    NML$DLW_SERVICE_EVENT : novalue;    ! Service event generator

!
! Local routines
!

forward routine
%if $tops20
%then
    DLW_CHECK_DTE_TASK : novalue,       ! DTE Check DTE task
%fi
    DLW_DTE_TASK : novalue,             ! DTE Data link watcher task
    DLW_NI_TASK : novalue,              ! NI Data link watcher task
    DLW_WORK,				! Actual work routine for DLW_DTE_TASK
    DLW_QUEUE : novalue,                ! Create load/dump request
    DLW_DEQUEUE : novalue,              ! Post process load/dump response
    REQUEST_SERVICE : novalue,          ! Process a request MOP message
    READ_REQUEST_PROGRAM,               ! Read a Request Program MOP Message
    READ_REQUEST_DUMP_SERVICE,          ! Read a Request Dump Service MOP msg
    GET_OTHER_INFO,                     ! Read 'Other Info' fields of MOP Msgs
    DLW_NI_RECEIVER : NI_RECEIVER_INTERRUPT_LINKAGE novalue;

!
! Define structures for CHECK DTE task
!
$field CHK_DTE_FIELDS =
       set
       CHK_DTE_EVENT = [$sub_block(Q_ENTRY_SIZE)],
       CHK_DTE_CIRCUIT_NAME = [$string(17)]
       tes;

literal
       CHK_DTE_SIZE = $field_set_size,
       CHK_DTE_ALLOCATION = $field_set_units;

macro
     CHK_DTE_BLOCK = block [CHK_DTE_SIZE] field (CHK_DTE_FIELDS) %;

!
! Owned variables
!

own
    NI_RCV_CHANNEL,
    NI_RECEIVE_EVENT : EVENT_BLOCK,
    LOAD_DUMP_ASST_QUEUE : SQ_HEADER,
    LINK_SERVICE_QUEUE : SQ_HEADER,
    CHECK_DTE_QUEUE : SQ_HEADER;

!	CH$ASCIC (STRING, ...)
!
!	    Return a pointer to an ASCIC character position sequence
!           formed by the characters represented by the string-params, 
!           which are interpreted as for the %string lexical function.


macro
     DLW$ASCIC [ ] =
         ch$ptr (uplit (%string (%char (%charcount (%remaining)), %remaining))) %;


macro
     LDAQ_BLOCK = block [LDAQ_SIZE] field (LDAQ_FIELDS) %;

!
! Circuit parameter numbers
!

literal
    CPARM_STATE = 0,                    ! Circuit STATE
    CPARM_SUBSTATE = 1,                 ! Circuit SUBSTATE
    CPARM_SERVICE = 100;                ! Circuit SERVICE

!
! Device type definitions
!

literal
       DEVTYP_NI = 15,
       DEVTYP_DTE = 20;

!
! External routines
!

external routine
    NMU$MEMORY_MANAGER,                 ! Memory get/release routines
    NML$REQUEST_MANAGER,                ! NML NICE function management
    NMU$SCHED_MANAGER,                  ! Scheduler
    NMU$TEXT_MANAGER,                   ! Text processor
    NML$GET_VDB_PARAMETER,              ! Get volatile data base parameter
    NML$SET_VDB_PARAMETER,              ! Set volatile data base parameter
    NML$CLR_VDB_PARAMETER,              ! Clear volatile data base parameter
    NML$FIND_NDB_HDA,                   ! Find Node ID using Hardware Address
    NMU$DLX_DEVICE_TYPE,                ! Return Circuit device type code
    NML$CALCULATE_CIRCUIT_STATE : novalue,      ! Calculate circuit state
    NML$DECLARE_EVENT : novalue;        ! Declare event internally to NML

external
    %debug_data_base;
%global_routine ('NML$DLW_INITIALIZE') : novalue =

!++
! Functional description:
!
!    This routine is called at startup time to initialize the Data
!    Link Watcher tasks.
!
! Formal parameters: none
!
! Routine value: none
!
!--

    begin

%if $tops20
%then
    NMU$SQUEUE_RESET (CHECK_DTE_QUEUE);
    NMU$SCHED_CREATE (DLW_CHECK_DTE_TASK,
                      200,
                      0,
                      ch$asciz ('CHECK_DTE_TASK'));
%fi

    NMU$SQUEUE_RESET (LINK_SERVICE_QUEUE);
    NMU$SCHED_CREATE (DLW_DTE_TASK, 200, 0, ch$asciz ('DTE_LINK-WATCHER'));

    NMU$SCHED_EVENT (NI_RECEIVE_EVENT, $true);
    NMU$SQUEUE_RESET (LOAD_DUMP_ASST_QUEUE);
    NMU$SCHED_CREATE (DLW_NI_TASK, 200, 0, ch$asciz ('NI_LINK-WATCHER'));

    NI_RCV_CHANNEL = ALLOCATE_INTERRUPT_CHANNEL (DLW_NI_RECEIVER);
    ACTIVATE_INTERRUPT (.NI_RCV_CHANNEL);

    end;					! End of NML$DLW_INITIALIZE
%global_routine ('NML$DLW_CHECK_CIRCUIT', CIRCUIT_ID) : novalue =

!++
!
! Functional description:
!
!	This routine is called from the local event reading interface to
!	indicate that CIRCUIT_ID has had a circuit down event.  It will then
!	queue up a request for the data link watcher task.
!
! Formal parameters:
!
!	.CIRCUIT_ID	Byte pointer to counted ASCII string id.
!
!--

begin
    local
	TEMP : ref DLWQ_BLOCK;

    TEMP = NMU$MEMORY_GET (DLWQ_ALLOCATION);
    if .TEMP eql 0 then
       TASK_ERROR ('Unable to allocate memory for DLW service');
	
    ch$move (ch$rchar (.CIRCUIT_ID) + 1,
	     .CIRCUIT_ID,
	     ch$ptr (TEMP [DLWQ_CIRCUIT_NAME],,8));

    NMU$SQUEUE_INSERT (LINK_SERVICE_QUEUE, TEMP [DLWQ_EVENT]);
end;
%if $tops20
%then
%routine ('DLW_CHECK_DTE_TASK') : novalue =

!++
! Functional description:
!
!   This task is scheduled when a successful DTE initialization has
!   occurred.  Because of some race in the DTE code we may miss a
!   "suicide" and leave the circuit in 'On-starting'.  This task will
!   be scheduled when a successful initialization has occurred.  It
!   will sleep 30 seconds and then schedule another check.
!
!--

while $true do
begin

     local PTR : ref CHK_DTE_BLOCK;

     PTR = NMU$SQUEUE_REMOVE (CHECK_DTE_QUEUE);
     NMU$SCHED_SLEEP (30);              ! Sleep 30 seconds before check
     NML$DLW_CHECK_CIRCUIT (ch$ptr (PTR [CHK_DTE_CIRCUIT_NAME],,8));
     NMU$MEMORY_RELEASE (.PTR, CHK_DTE_ALLOCATION);

end;                                    ! End of DLW_CHECK_DTE_TASK
%fi                                     ! End of TOPS20 conditional
%routine ('DLW_DTE_TASK') : novalue =

!++
! Functional description:
!
!    This routine is the main body of the Data Link Watcher task.
!    It is event driven via NMLEVL, which calls NML$DLW_CHECK_CIRCUIT
!    when it gets a circuit down event.
!
!    It tries to initiate protocol on the DTE device.
!    If initialization fails it issues a dump and then tries
!    to load the DN20.
!
!    If the DN20 is determined to be dead, the link watcher will
!    stop service on it's DTE until it sees that the DN20 has
!    been running.
!
! Formal parameters: none
!
! Routine value: none
!
!--

!
! Run this task forever (until NML dies)
!
    begin
    local
	SERVICE_REQUEST : ref DLWQ_BLOCK,
        CHK_DTE_REQ : ref CHK_DTE_BLOCK,
        TEMP_PTR;

    while $true do
	begin
!
! Wait for LINK_SERVICE_QUEUE to become non-empty.
!
	SERVICE_REQUEST = NMU$SQUEUE_REMOVE (LINK_SERVICE_QUEUE);
        if (CHK_DTE_REQ = NMU$MEMORY_GET (CHK_DTE_ALLOCATION)) neqa 0
        then begin
             TEMP_PTR = ch$ptr (SERVICE_REQUEST [DLWQ_CIRCUIT_NAME],,8);
             ch$move (ch$rchar (.TEMP_PTR) + 1,
                      .TEMP_PTR,
                      ch$ptr (CHK_DTE_REQ [CHK_DTE_CIRCUIT_NAME],,8));
             end;

! Process request
        if not DLW_WORK (.SERVICE_REQUEST)
        then begin
             NMU$MEMORY_RELEASE (.SERVICE_REQUEST, DLWQ_ALLOCATION);
             NMU$MEMORY_RELEASE (.CHK_DTE_REQ, CHK_DTE_ALLOCATION);
             end
        else if .CHK_DTE_REQ neqa 0
             then NMU$SQUEUE_INSERT (CHECK_DTE_QUEUE,
                                     CHK_DTE_REQ [CHK_DTE_EVENT]);

	end;                            ! Wnd WHILE $TRUE
    end;						! End of DLW_DTE_TASK
%routine ('DLW_WORK', SERVICE_REQUEST : ref DLWQ_BLOCK) =

!++
!
! This routine is the real worker routine for the data link watcher task.
! The reason for encapsulating this routine on it's own is due to the fact
! that it is simpler to write BLISS code in a non-loop context.  Ie: if an
! error is detected here, it can just exit the routine (DLW_WORK), and return
! to the encapsulating routine (DLW_DTE_TASK) which will simply restart this
! routine again.
!
! Routine value:
!
!	$true		Request is being serviced
!	$false		Request cannot be serviced
!--

begin

    local
	CIRCUIT_STATE,			! NML circuit state parameter
	CIRCUIT_SERVICE,		! NML circuit service parameter
	CIRCUIT_SUBSTATE,		! NML circuit substate
	CIRCUIT_ID,			! Byte pointer to circuit name
	CIRCUIT_BLOCK,                  ! Circuit block from NMU$DLX_OPEN
	RSP_POINTER,			! Pointer to dummy NICE response buffer
	RSP: vector [40];		! Dummy NICE response buffer

    CIRCUIT_ID = ch$ptr (SERVICE_REQUEST [DLWQ_CIRCUIT_NAME],,8);
    RSP_POINTER = ch$ptr (RSP,,8);

    !
    ! If this isn't a front end to the system, then
    ! forget about it.
    !

    if not $NMU_DLX_FRONT_END_DEVICE (.CIRCUIT_ID)
    then
	return $false;

    !
    ! Calculate new circuit state and substate
    !

    NML$CALCULATE_CIRCUIT_STATE (.CIRCUIT_ID);

    !
    ! Get STATE parameter for circuit.
    ! If not set...assume it is on
    !
    ! Note: we get the STATE as known by network management, i.e. the
    !  monitor is not queried.

    if not NML$GET_VDB_PARAMETER (CIRCUIT_,
                                  .CIRCUIT_ID,
                                  CPARM_STATE,
                                  CIRCUIT_STATE)
    then
	CIRCUIT_STATE = CIRCUIT_OFF;

    !
    ! Get SERVICE parameter for circuit.
    ! If not set .. assume it is disabled.
    !

    if not NML$GET_VDB_PARAMETER (CIRCUIT_,
				  .CIRCUIT_ID,
				  CPARM_SERVICE,
				  CIRCUIT_SERVICE)
    then
	CIRCUIT_SERVICE = CIRCUIT_DISABLED;

    !
    ! If SERVICE DISABLE, then go away since we are not allowed to do anything
    !

    if .CIRCUIT_SERVICE neq CIRCUIT_ENABLED
    then
        return $false;

    !
    ! Get circuit substate.  If none, assume no substate.
    !  Again, this is the network management substate, and is not read from
    !  the monitor.
    !

    if not NML$GET_VDB_PARAMETER (CIRCUIT_,
				  .CIRCUIT_ID,
				  CPARM_SUBSTATE,
				  CIRCUIT_SUBSTATE)
    then
	CIRCUIT_SUBSTATE = CIRCUIT_NO_SUBSTATE;

    !
    ! If the circuit has a substate other than STARTING,
    !  (NML currently using it) then just return now.
    !
    !  The LOAD and DUMP commands set a substate, so if a substate other
    !   than STARTING, is present, we know that the device is being
    !   loaded/dumped.

    if (.CIRCUIT_SUBSTATE neq CIRCUIT_STARTING)
    then return $false;

    !
    ! Open the circuit for protocol service
    !
    ! For DTE-20s, the OPEN does nothing

    CIRCUIT_BLOCK = $NMU_DLX_OPEN (DLX_PROTOCOL,
                                   CIRCUIT_,
				   .CIRCUIT_ID,
                                   0,          ! Dummy Arg for Physical Address
                                   -1,         ! PSI Interrupts not used here.
                                   -1,
                                   -1,
                                   .RSP_POINTER);
    if .CIRCUIT_BLOCK leqa 0
    then
	begin
	    %debug (DLW_TRACE,
		    (TRACE_INFO ('Problem servicing %X: %X',
				 .CIRCUIT_ID, .RSP_POINTER)));
	    return $false;
	end;

    !
    ! Process based on current circuit status
    !

    selectoneu .CIRCUIT_STATE of
    set

	[CIRCUIT_ON, CIRCUIT_SERVICE]:
	    begin

		!
		! First check if DECnet protocol is already running
                ! If it is, we are done and it would be wrong to try to
                ! initialize protocol

                if not $NMU_DLX_DECNET_RUNNING (.CIRCUIT_BLOCK, .RSP_POINTER)
                then begin

		!
		! Try to initialize protocol if that's the problem
                ! For DTE-20s, this will try to initialize protocol 4 times.
                ! If all 4 attempts fails, a DUMP request for the device will
                ! be entered. The 4 attempts seem to be needed because of a
                ! timing window.
		!
                ! If we fail to initialize protocol, then queue a dump request

		if not $NMU_DLX_START_PROTOCOL (.CIRCUIT_BLOCK, .RSP_POINTER)
		then DLW_QUEUE (.SERVICE_REQUEST, DUMP_);
                end;
	    end;

	[OTHERWISE]:
            ! For DTE-20s, terminate protocol
	    $NMU_DLX_STOP_PROTOCOL (.CIRCUIT_BLOCK, .RSP_POINTER);

    tes;

    ! The following call does nothing to DTE-20s
    $NMU_DLX_CLOSE (.CIRCUIT_BLOCK);

    return $true;

end;					! End of DLW_WORK
%routine ('DLW_QUEUE', SERVICE_REQUEST : ref DLWQ_BLOCK,
                         FNC_CODE) : novalue =

!++
! Functional description:
!
!    This routine issues a internal NICE function request.  It sends
!    either a LOAD VIA or DUMP VIA command for DTE's or LOAD NODE or
!    DUMP NODE command for NI's. The NICE message constructed
!    and sent is of the form:
!
!        LOAD_, CIRCUIT_, "circuit-id"
!
!        DUMP_, CIRCUIT_, "circuit-id"
!
!        LOAD_, NODE_E, "node-id"
!
!                    <or>
!
!        DUMP_, NODE_E, "node-id"
!
!    The response processing routine is set to DLW_DEQUEUE.
!
! Formal parameters:
!
!    .SERVICE_REQUEST	Address of DLW service request block
!    .FNC_CODE		Either LOAD_ or DUMP_
!
! Routine value: none
!
!--

    begin
    local
         REQ_NO,                        ! NML function request number
         REQ : ref REQUEST_BLOCK,       ! Request block
         ID_LENGTH,                     ! Number of bytes in entity id
         NODE_ADDR,                     ! Node address fron Entity Id
         ENT_PTR,                       ! Local pointer to entity id
         CKT_DEVTYP,                    ! Device Type code
         NICE_PTR,                      ! Nice message pointer
	 CIRCUIT_ID,			! Byte pointer to circuit name
         CIRCUIT_SERVICE;               ! Value of SERVICE parameter (ENABLED?)
    
    CIRCUIT_ID = ch$ptr (SERVICE_REQUEST [DLWQ_CIRCUIT_NAME],,8);

! Check that SERVICE is ENABLED on the circuit
    if not NML$GET_VDB_PARAMETER (CIRCUIT_,
                                  .CIRCUIT_ID,
                                  CPARM_SERVICE,
                                  CIRCUIT_SERVICE)
    then CIRCUIT_SERVICE = CIRCUIT_DISABLED;
    if .CIRCUIT_SERVICE neq CIRCUIT_ENABLED
    then begin
         NMU$MEMORY_RELEASE (.SERVICE_REQUEST, DLWQ_ALLOCATION);
         return;
         end;

!
! Log "automatic line service" event
!
    NML$DLW_SERVICE_EVENT (.FNC_CODE,
                           ch$ptr(SERVICE_REQUEST [DLWQ_HARDWARE_ADDRESS],,8),
                           .SERVICE_REQUEST [DLWQ_PROG_TYPE], .CIRCUIT_ID, 0);
!
! Allocate request block
! Allocate space for a NICE message buffer
! Build pointer to start of NICE message buffer
!
    REQ = NMU$MEMORY_GET (REQUEST_BLOCK_ALLOCATION);
    REQ [RB_NICE_ALLOCATION] = ch$allocation (25, 8);
    REQ [RB_NICE] = NMU$MEMORY_GET (.REQ [RB_NICE_ALLOCATION]);
    REQ [RB_NICE_POINTER] = NICE_PTR = ch$ptr (.REQ [RB_NICE],, 8);
    REQ [RB_PRV_LOCAL] = $TRUE;		! Internal processes always have privs
    REQ [RB_PRV_SERVICE] = $TRUE;	! ...


    CKT_DEVTYP = $NMU_DLX_DEVICE_TYPE (.CIRCUIT_ID);

    SERVICE_REQUEST [DLWQ_NODE_ID] =
        NML$FIND_NDB_HDA (ch$ptr(SERVICE_REQUEST [DLWQ_HARDWARE_ADDRESS],,8));

    ! Do a LOOP CIRCUIT if device type is DTE or if we don't have a node match
    ! Do a LOOP NODE otherwise.

    if (.CKT_DEVTYP eql DEVTYP_DTE) or (.SERVICE_REQUEST [DLWQ_NODE_ID] eql 0)
    then begin

         !
         ! Build NICE message to dump across the circuit
         !

         ENT_PTR = .CIRCUIT_ID;
         PUTB (.FNC_CODE, NICE_PTR);
         PUTB (CIRCUIT_, NICE_PTR);
         ID_LENGTH = GETB (ENT_PTR);
         PUTB (.ID_LENGTH, NICE_PTR);
         ch$move (.ID_LENGTH, .ENT_PTR, .NICE_PTR);
         REQ [RB_NICE_LENGTH] = 3 + .ID_LENGTH;
         end

    else if .CKT_DEVTYP eql DEVTYP_NI
    then begin

              ENT_PTR = .SERVICE_REQUEST [DLWQ_NODE_ID];
              PUTB (.FNC_CODE, NICE_PTR);
              PUTB (NODE_E, NICE_PTR);
              NODE_ADDR = GETW (ENT_PTR);   ! Read/Skip past the Node Address
              ID_LENGTH = GETB (ENT_PTR);
              PUTB (.ID_LENGTH, NICE_PTR);
              ch$move (.ID_LENGTH, .ENT_PTR, .NICE_PTR);
              REQ [RB_NICE_LENGTH] = 3 + .ID_LENGTH;

              end

    else begin                          ! Unknown device type
         NMU$MEMORY_RELEASE (.REQ[RB_NICE], .REQ [RB_NICE_ALLOCATION]);
         NMU$MEMORY_RELEASE (.REQ, REQUEST_BLOCK_ALLOCATION);
         NMU$MEMORY_RELEASE (.SERVICE_REQUEST, DLWQ_ALLOCATION);
         return;
         end;

!
! Setup cells in the request block needed to perform
! a DUMP VIA function
!
    REQ [RB_EXECUTOR] = 0;
    REQ [RB_DATA] = .SERVICE_REQUEST;
    REQ [RB_DATA_ALLOCATION] = 0;
    REQ [RB_COMPLETION] = DLW_DEQUEUE;
    REQ [RB_DLW_DATA] = .SERVICE_REQUEST;
    
!
! Now set substate to AUTOSERVICE and then queue the NICE request
!
    NML$SET_VDB_PARAMETER (CIRCUIT_,
                           .CIRCUIT_ID,
                           CPARM_SUBSTATE,
                           %ref (CIRCUIT_AUTOSERVICE));
    REQ_NO = NML$REQUEST_ENTER (.REQ);

    %debug (DLW_TRACE,
            (if .FNC_CODE eql DUMP_
             then TRACE_INFO ('Auto-DUMP request #%D entered for %#A',
                              .REQ_NO, .ID_LENGTH, .ENT_PTR)
             else TRACE_INFO ('Auto-LOAD request #%D entered for %#A',
                              .REQ_NO, .ID_LENGTH, .ENT_PTR)));

    end;					! End of DLW_QUEUE
%routine ('DLW_DEQUEUE', REQ : ref REQUEST_BLOCK) : novalue =

!++
! Functional description:
!
!    This routine processing the response from a DLW NICE function
!    request.  If it is a DUMP_ request it will issue the corresponding
!    LOAD_ request.  If it is a LOAD_ request it will set the CIRCUIT
!    status to the appropriate value.  It will also free up any
!    memory allocated for the request.
!
! Formal parameters:
!
!    .REQ    Request block address
!
! Routine value: none
!
!--

    begin

    local
         RSP_PTR,                       ! Pointer to response message
         RSP_CODE,                      ! NICE response code
         NICE_PTR,                      ! NICE message pointer
         FNC_CODE,                      ! NICE function code
         CKT_PTR,                       ! Circuit ID pointer
	 SERVICE_REQUEST : ref DLWQ_BLOCK; ! Service request block

!
! Get pointer to auto-serviced circuit
! Get pointer to response message
! Get pointer to nice message
! Get response code
! Get function request was to execute
!
    SERVICE_REQUEST = .REQ [RB_DATA];
    CKT_PTR = ch$ptr (SERVICE_REQUEST [DLWQ_CIRCUIT_NAME],,8);
    RSP_PTR = ch$ptr (.REQ [RB_RESPONSE],, 8);
    NICE_PTR = ch$ptr (.REQ [RB_NICE],, 8);
    FNC_CODE = ch$rchar_a (NICE_PTR);
    NML$DLW_SERVICE_EVENT (.FNC_CODE,
                           ch$ptr(SERVICE_REQUEST [DLWQ_HARDWARE_ADDRESS],,8),
                           .SERVICE_REQUEST [DLWQ_PROG_TYPE], .CKT_PTR,
                           .RSP_PTR);
    RSP_CODE = ch$rchar_a (RSP_PTR);
!
! Finish quickly if request was cancelled
!
    if .REQ [RB_STATE] eql RB$CANCELLED
    then
        begin
            %debug (DLW_TRACE,
                    (begin
                         if .FNC_CODE eql DUMP_
                         then TRACE_INFO ('Auto-DUMP cancelled on %X',
                                          .CKT_PTR)
                         else TRACE_INFO ('Auto-LOAD cancelled on %X',
                                          .CKT_PTR);
                     end));
            NML$CLR_VDB_PARAMETER (CIRCUIT_, .CKT_PTR,
                                   CPARM_SUBSTATE);
            NMU$MEMORY_RELEASE (.SERVICE_REQUEST, DLWQ_ALLOCATION);
            NMU$MEMORY_RELEASE (.REQ [RB_NICE], .REQ [RB_NICE_ALLOCATION]);
            if .REQ [RB_RESPONSE] neqa 0
            then NMU$MEMORY_RELEASE (.REQ [RB_RESPONSE],
				     .REQ [RB_RESPONSE_ALLOCATION]);
            NMU$MEMORY_RELEASE (.REQ, REQUEST_BLOCK_ALLOCATION);
            return;
        end;

!
! Display result of request
!
    %debug (DLW_TRACE,
            (begin
             if .FNC_CODE eql DUMP_
             then
                 if .RSP_CODE neq NICE$_SUC
                 then TRACE_INFO ('Auto-DUMP failed on %X',
                                  .CKT_PTR)
                 else TRACE_INFO ('Auto-DUMP done on %X',
                                  .CKT_PTR)
             else
                 if .RSP_CODE neq NICE$_SUC
                 then TRACE_INFO ('Auto-LOAD failed on %X',
                                  .CKT_PTR)
                 else TRACE_INFO ('Auto-LOAD done on %X',
                                  .CKT_PTR);

             if .RSP_CODE neq NICE$_SUC
             then TRACE_INFO (' Return code = %D  Detail = %D; %X',
                              .RSP_CODE <0, 8, 1>, GETW (RSP_PTR), .RSP_PTR);
             end));
!
! Now release REQ block
    if .REQ [RB_RESPONSE] neqa 0
    then NMU$MEMORY_RELEASE (.REQ [RB_RESPONSE],
                             .REQ [RB_RESPONSE_ALLOCATION]);
    NMU$MEMORY_RELEASE (.REQ [RB_NICE], .REQ [RB_NICE_ALLOCATION]);
    NMU$MEMORY_RELEASE (.REQ, REQUEST_BLOCK_ALLOCATION);

!
! If a dump was just done .. try to do a LOAD
! If a load was just done .. set the circuit substate
!
    if .FNC_CODE eql DUMP_
    then DLW_QUEUE (.SERVICE_REQUEST, LOAD_)
    else ! It was a LOAD
        if .RSP_CODE eql NICE$_SUC
        then NML$CLR_VDB_PARAMETER (CIRCUIT_, .CKT_PTR,
                                    CPARM_SUBSTATE)
        else NML$SET_VDB_PARAMETER (CIRCUIT_, .CKT_PTR,
                                    CPARM_SUBSTATE, %ref (CIRCUIT_FAILED));

!
! Release SERVICE_REQUEST block if we just did a LOAD - if we did a DUMP
! it is queued to be loaded.
!
       if .FNC_CODE eql LOAD_
       then NMU$MEMORY_RELEASE (.SERVICE_REQUEST, DLWQ_ALLOCATION);

    end;					! End of DLW_DEQUEUE
%global_routine ('NML$DLW_SERVICE_EVENT', FUNC_CODE,
                                            HARDWARE_ADDRESS,
                                            PROGRAM_TYPE,
                                            CIRCUIT_PTR,
                                            RSP_PTR) : novalue =

!++
! Functional description:
!
!    This routine initiates an "automatic line service" event
!    for the specified entity.
!
! Formal parameters:
!
!    .FUNC_CODE             Function code (LOAD_, DUMP_)
!    .HARDWARE_ADDRESS      Pointer to hardware address of node (for NI).
!    .PROGRAM_TYPE          Program type for LOAD_ (secondary, tertiary,
!                           system).
!    .CIRCUIT_PTR           Pointer to entity-id string
!    .RSP_PTR               If zero, then function requested,
!                           otherwise pointer to response message
!
! Routine value: none
!
!--

    begin

    local
        DATA : vector [ch$allocation (86, 8)],
        DATA_PTR;

    DATA_PTR = ch$ptr (DATA,, 8);

! First parameter #0 = SERVICE.  It is a CS-1 with values 0 (LOAD) and
!  1 (DUMP).
    begin

    literal
        DATA_ID = 0^15 + 0,
        DATA_TYPE = 1^7 + 0^6 + 1;

    ch$wchar_a (DATA_ID and %o'377', DATA_PTR);
    ch$wchar_a (DATA_ID^-8 and %o'377', DATA_PTR);
    ch$wchar_a (DATA_TYPE, DATA_PTR);
    ch$wchar_a ((if .FUNC_CODE eql LOAD_ then 0 else 1), DATA_PTR);
    end;

! Now do parameter 2500 (TOPS-20 specific).  It is only generated if the
!  circuit is the NI, and it gives the hardware address of the system
!  requesting the service.
!
! It is network management event parameter 2500, type HI-6.

! Only generate physical address if this is on the NI circuit
    if $NMU_DLX_DEVICE_TYPE (.CIRCUIT_PTR) eql DEVTYP_NI
    then begin

         literal
                DATA_ID = 0^15 + 2500,
                DATA_TYPE = 2^4 + 6;    ! HI-6

         local
              ADR_PTR,
              INDEX,
              BYT_VEC : VECTOR [6];

         ch$wchar_a (DATA_ID and %o'377', DATA_PTR);
         ch$wchar_a (DATA_ID^-8 and %o'377', DATA_PTR);
         ch$wchar_a (DATA_TYPE, DATA_PTR);
         
         ! Now write hex image, but in reverse order....
         ADR_PTR = .HARDWARE_ADDRESS;
         incr INDEX from 0 to 5 do BYT_VEC [.INDEX] = GETB (ADR_PTR);
         decr INDEX from 5 to 0 do ch$wchar_a (.BYT_VEC [.INDEX], DATA_PTR);

         end;

! Parameter #1 is the status code.  It is a CM-1 CS-1 containing the NICE
! return code.  I.e. we do not do the CM-2 or CM-3 versions of it.
    begin                               ! Parameter #1

    local
         RESP_PTR, VAL;

    literal
        DATA_ID = 0^15 + 1,
        CM_1_DATA_TYPE = 1^7 + 1^6 + 1,
        C_1_DATA_TYPE = 1^7 + 0^6 + 1;

    ch$wchar_a (DATA_ID and %o'377', DATA_PTR);
    ch$wchar_a (DATA_ID^-8 and %o'377', DATA_PTR);
    ch$wchar_a (CM_1_DATA_TYPE, DATA_PTR);
    ch$wchar_a (C_1_DATA_TYPE, DATA_PTR);

    ! If there was no response pointer supplied in the call, then we
    ! should write a 0 = REQUESTED.  Otherwise write the response byte.
    VAL = (if (RESP_PTR = .RSP_PTR) eql 0
           then 0
           else ch$rchar_a (RESP_PTR));
    ch$wchar_a (.VAL, DATA_PTR);

    end;

! If this is a load request (response pointer 0) then write software type as
! well.  It is parameter 8, C-1 with values 0,1,2 (secondary, tertiary,
! system).
    if (.RSP_PTR eql 0) and (.FUNC_CODE eql LOAD_)
    then begin

         literal
                DATA_ID = 0^15 + 8,
                DATA_TYPE = 1^7 + 0^6 + 1;      ! C-1

         ch$wchar_a (DATA_ID and %o'377', DATA_PTR);
         ch$wchar_a (DATA_ID^-8 and %o'377', DATA_PTR);
         ch$wchar_a (DATA_TYPE, DATA_PTR);
         ch$wchar_a (.PROGRAM_TYPE, DATA_PTR);

         end;

! Now generate the REB to be supplied to the event logger
    begin

    local
        REB : RAW_EVENT_BLOCK;

! Generate a 0.3 (Automatic line service) either if this is a request or
!  if it was a successful operation.  Generate 0.7 (Aborted service requst)
!  if the service failed.

! Assume 0.3
    REB [REB_EVENT_CODE] = 0^6 + 3;
    if .RSP_PTR neq 0
    then begin
         local RESP_PTR;

         RESP_PTR = .RSP_PTR;
         if GETB (RESP_PTR) neq NICE$_SUC then REB [REB_EVENT_CODE] = 0^6 + 7;
         end;

    REB [REB_ENTITY_TYPE] = CIRCUIT_;
    REB [REB_ENTITY_POINTER] = .CIRCUIT_PTR;
    REB [REB_DATA_POINTER] = ch$ptr (DATA,, 8);
    REB [REB_DATA_LENGTH] = ch$diff (.DATA_PTR, ch$ptr (DATA,, 8));
    REB [REB_SOURCE_POINTER] = 0;
    REB [REB_TIME_BLOCK] = 0;
    NML$DECLARE_EVENT (REB);

    end;

    end;					! End of DLW_SERVICE_EVENT
%routine ('DLW_NI_TASK') : novalue =

!++
! Functional description:
!
!    This routine is the main body of the NI Data Link Watcher task.
!
! Formal parameters: none
!
! Routine value: none
!
!--

!
! Run this task forever (until NML dies)
!
begin

literal
       MOP_BUFFER_LENGTH = 1506,         ! MOP message buffer in bytes
       MOP_BUFFER_SIZE = ch$allocation (MOP_BUFFER_LENGTH, 8),
       MOP_BUFFER_ALLOCATION = MOP_BUFFER_SIZE * %upval,
       MOP_TIME_OUT = 4;
       

local
     CIRCUIT_BLOCK : ref CD_BLOCK,
     LDA_MSG : ref LDAQ_BLOCK,
     MOP_BUFFER,
     MOP_BUFFER_PTR,
     MOP_FUNCTION,
     RESULT,
     RSP_POINTER,
     RSP: vector [40],
     SERVICE_REQUEST : ref DLWQ_BLOCK,
     CIRCUIT_POINTER,
     CIRCUIT_ID : vector [ch$allocation (10, 8)],
     TEMP_PTR;

     CIRCUIT_POINTER = DLW$ASCIC ('NI-0-0');

     RSP_POINTER = ch$ptr (RSP,,8);

     TEMP_PTR = .CIRCUIT_POINTER;

     ch$move (ch$rchar (.TEMP_PTR)+1,
              .TEMP_PTR,
              ch$ptr( CIRCUIT_ID,,8));

     while $true
           do begin
              TEMP_PTR = ch$ptr( CIRCUIT_ID,,8);
              CIRCUIT_BLOCK = $NMU_DLX_OPEN (DLX_LDA,
                                             CIRCUIT_,
                                             .TEMP_PTR,
                                             0,
                                             .NI_RCV_CHANNEL,
                                             -1,
                                             -1,
                                             .RSP_POINTER);

              if .CIRCUIT_BLOCK leqa 0
              then NMU$SCHED_SLEEP (60)
              else begin
                        RESULT = $NMU_DLX_ENABLE_MULTICAST (.CIRCUIT_BLOCK,
                                                            .RSP_POINTER);
                        if .RESULT
                        then exitloop
                        else begin
                             RESULT = $NMU_DLX_CLOSE (.CIRCUIT_BLOCK);
                             NMU$SCHED_SLEEP (60);
                             end;
                   end;
              end;


     while $true                    ! Loop forever, Unless there is an
           do begin                 ! error return from DLX.

              MOP_BUFFER = NMU$MEMORY_GET (MOP_BUFFER_ALLOCATION);
              if .MOP_BUFFER eql 0 then
                 TASK_ERROR ('Unable to allocate MOP buffer for DLW service');

              MOP_BUFFER_PTR = ch$ptr (.MOP_BUFFER,,8);

              SERVICE_REQUEST = NMU$MEMORY_GET (DLWQ_ALLOCATION);
              if .SERVICE_REQUEST eql 0 then   
              TASK_ERROR ('Unable to allocate memory for DLW service');

              TEMP_PTR = ch$ptr( CIRCUIT_ID,,8);

              ch$move (ch$rchar (.TEMP_PTR)+1,
                       .TEMP_PTR,
                       ch$ptr( SERVICE_REQUEST[DLWQ_CIRCUIT_NAME],,8));

              CIRCUIT_BLOCK [CD_KLNI_HRDWADR] = ch$ptr (SERVICE_REQUEST [DLWQ_HARDWARE_ADDRESS],,8);
              CIRCUIT_BLOCK [CD_KLNI_MULTICAST] = $false;  ! Clear Multicast received flag

              !
              ! Wait until the NI% JSYS wakes us up.
              !

              if (
                  local CIRCUIT_SUBSTATE, STATUS;

                  TEMP_PTR = ch$ptr (CIRCUIT_ID,,8);
                  if not NML$GET_VDB_PARAMETER (CIRCUIT_,
                                                .TEMP_PTR,
                                                CPARM_SUBSTATE,
                                                CIRCUIT_SUBSTATE)
                  then CIRCUIT_SUBSTATE = CIRCUIT_NO_SUBSTATE;

                  selectone .CIRCUIT_SUBSTATE of
                  set

                  [CIRCUIT_LOADING,
                   CIRCUIT_DUMPING,
                   CIRCUIT_AUTOSERVICE to CIRCUIT_AUTODUMPING]:
                      STATUS = NMU$SCHED_WAIT (NI_RECEIVE_EVENT, MOP_TIME_OUT);

                  [otherwise]:
                      ! We would like to sleep forever here, but NMU$SCHED_WAIT
                      ! doesn't do what we expect it to with a time of 0.
                      ! So set up to wait a "long" time (300 seconds)
                      STATUS = NMU$SCHED_WAIT (NI_RECEIVE_EVENT, 300);

                  tes;
                  .STATUS
              )
              then
              begin

                   RESULT = $NMU_DLX_READ (.CIRCUIT_BLOCK,
                                           DLX_DATA,
                                           .MOP_BUFFER_PTR,
                                           MOP_BUFFER_LENGTH,
                                           0,
                                           .RSP_POINTER);
                   if .RESULT leq 0
                   then begin
                             TEMP_PTR =
                                ch$ptr( SERVICE_REQUEST[DLWQ_CIRCUIT_NAME],,8);
                             %debug (DLW_TRACE,
                                     (TRACE_INFO ('Problem servicing %X: %X',
                                                  .TEMP_PTR, .RSP_POINTER)));
                             NMU$MEMORY_RELEASE (.MOP_BUFFER,
                                                 MOP_BUFFER_ALLOCATION);
                             NMU$MEMORY_RELEASE (.SERVICE_REQUEST,
                                                 DLWQ_ALLOCATION);
                             exitloop;
                        end;
                   
                   if .CIRCUIT_BLOCK [CD_KLNI_MULTICAST]
                   then begin

                        ! If it is a multicast message, then it should be
                        ! a 'REQUEST PROGRAM' or 'REQUEST DUMP SERVICE'
                        ! message.  If not, just discard it.
                        REQUEST_SERVICE (.MOP_BUFFER,
                                         .SERVICE_REQUEST,
                                         .RESULT,
                                         $true);

                        ! Release MOP buffer
                        NMU$MEMORY_RELEASE (.MOP_BUFFER,
                                            MOP_BUFFER_ALLOCATION);

                        end

! Not MULTICAST
                   else begin

                      local TEMP, FCN_CODE, CIRCUIT_SUBSTATE;

                      ! An incoming message not on the multicast can be of
                      ! three categories:
                      ! (i) a message that is part of an ongoing load or dump
                      !     In this case, the circuit substate will be loading
                      !     or dumping.
                      ! (ii) a RPM or RDS that is an ACK of a previous 
                      !      'assistance volounteer'.  The substate will be
                      !      loading or dumping, and we should queue the
                      !      message to NMLDTL
                      ! (iii) a RPM or RDS that is addressed to us specifically
                      !       The circuit substate will NOT be loading or
                      !       dumping.  Queue the request to be serviced.

                      if not NML$GET_VDB_PARAMETER (
                               CIRCUIT_,
                               ch$ptr (SERVICE_REQUEST [DLWQ_CIRCUIT_NAME],,8),
                               CPARM_SUBSTATE,
                               CIRCUIT_SUBSTATE)
                      then CIRCUIT_SUBSTATE = CIRCUIT_NO_SUBSTATE;

                      TEMP = ch$ptr (.MOP_BUFFER,,8);
                      FCN_CODE = GETB (TEMP);

                      if ((.FCN_CODE eql REQUEST_PROGRAM)
                          or (.FCN_CODE eql REQUEST_DUMP_SERVICE))
                      and not
                          ((.CIRCUIT_SUBSTATE eql CIRCUIT_LOADING)
                           or (.CIRCUIT_SUBSTATE eql CIRCUIT_LOADING)
                           or ((.CIRCUIT_SUBSTATE geq CIRCUIT_AUTOSERVICE)
                              and (.CIRCUIT_SUBSTATE leq CIRCUIT_AUTODUMPING)))

                      then begin

                          ! Call common routine to process request message
                          REQUEST_SERVICE (.MOP_BUFFER,
                                           .SERVICE_REQUEST,
                                           .RESULT,
                                           $false);

                          ! Release MOP buffer
                          NMU$MEMORY_RELEASE (.MOP_BUFFER,
                                              MOP_BUFFER_ALLOCATION);
                          end
                      else begin

                          ! Make sure that we are interested in the message.
                          ! That is the case only while we are loading or
                          ! dumping.

                          selectone .CIRCUIT_SUBSTATE of
                          set

                          [CIRCUIT_LOADING,
                           CIRCUIT_DUMPING,
                           CIRCUIT_AUTOSERVICE to CIRCUIT_AUTODUMPING]:

                          ! Yes, we are interested.  Put this message on
                          ! a queue to be read by the appropriate routine
                          ! in NMLDTL.  The NML$DLW_READ_LDAQ is responsible
                          ! for deallocating the MOP buffer.
                          begin
                        
                          if (LDA_MSG = NMU$MEMORY_GET (LDAQ_ALLOCATION)) eql 0
                          then TASK_ERROR (
                               'Unable to allocate memory for DLW service');

                          LDA_MSG [LDAQ_CIRCUIT_HANDLE] = .CIRCUIT_BLOCK;

                          ! Copy hardware address
                          ch$move (
                            6, 
                            ch$ptr(SERVICE_REQUEST [DLWQ_HARDWARE_ADDRESS],,8),
                            ch$ptr( LDA_MSG [LDAQ_SAD],,8));

                          LDA_MSG [LDAQ_BUF_PTR] = ch$ptr (.MOP_BUFFER,,8);
                          LDA_MSG [LDAQ_MSG_LEN] = .RESULT;
                          LDA_MSG [LDAQ_BUFFER_ADDRESS] = .MOP_BUFFER;
                          LDA_MSG [LDAQ_BUFFER_SIZE] = MOP_BUFFER_ALLOCATION;

                          ! Put the MOP message on the LOAD DUMP sched queue
                          NMU$SQUEUE_INSERT (LOAD_DUMP_ASST_QUEUE,
                                             LDA_MSG [LDAQ_EVENT]);
                          end;

                          [otherwise]:

                          ! We are not interested, just release MOP buffer
                          NMU$MEMORY_RELEASE (.MOP_BUFFER,
                                              MOP_BUFFER_ALLOCATION);

                          tes;

                          ! Release the DLWQ block
                          NMU$MEMORY_RELEASE (.SERVICE_REQUEST,
                                              DLWQ_ALLOCATION);
                          end;

                      end;

                   end

! Here when call to NMU$SCHED_WAIT times out

              else begin

                   local
                        CIRCUIT_SUBSTATE;
              
                   TEMP_PTR = ch$ptr (CIRCUIT_ID,,8);

                   if not NML$GET_VDB_PARAMETER (CIRCUIT_,
                                                 .TEMP_PTR,
                                                 CPARM_SUBSTATE,
                                                 CIRCUIT_SUBSTATE)
                   then CIRCUIT_SUBSTATE = CIRCUIT_NO_SUBSTATE;

                   selectone .CIRCUIT_SUBSTATE of
                   set

                   [CIRCUIT_LOADING,
                    CIRCUIT_DUMPING,
                    CIRCUIT_AUTOSERVICE to CIRCUIT_AUTODUMPING]:
                        begin
                   
                        LDA_MSG = NMU$MEMORY_GET (LDAQ_ALLOCATION);
                        if .LDA_MSG eql 0 then   
                            TASK_ERROR (
                                'Unable to allocate memory for DLW service');

                        LDA_MSG [LDAQ_CIRCUIT_HANDLE] = .CIRCUIT_BLOCK;
                        LDA_MSG [LDAQ_MSG_LEN] = -2;
                        LDA_MSG [LDAQ_BUFFER_ADDRESS] = .MOP_BUFFER;
                        LDA_MSG [LDAQ_BUFFER_SIZE] = MOP_BUFFER_ALLOCATION;

                        NMU$SQUEUE_INSERT (LOAD_DUMP_ASST_QUEUE,
                                           LDA_MSG [LDAQ_EVENT]);
                        end;

                   [otherwise]:
                       NMU$MEMORY_RELEASE (.MOP_BUFFER, MOP_BUFFER_ALLOCATION);

                   tes;

              
                   NMU$MEMORY_RELEASE (.SERVICE_REQUEST, DLWQ_ALLOCATION);
                   end;
                   
              end;  ! 2nd while $true

end;                                    ! End of DLW_NI_TASK
%routine ('REQUEST_SERVICE', MOP_BUFFER,
                               SERVICE_REQUEST : ref DLWQ_BLOCK,
                               LENGTH,
                               MULTICAST) : novalue =

!++
! Functional description:
!
!    This routine processes an incoming MOP request for service, i.e. a
!    'request program message' or 'request dump service'.
!
!    The routine is responsible for disposing of the SERVICE_REQUEST (either
!    by deallocation it or queueing it to DLW_QUEUE.
!
!    The routine should NOT deallocate the MOP BUFFER.
!
! Parameters:
!
!    MOP_BUFFER              Address of MOP buffer
!    SERVICE_REQUEST         Address of DLWQ block
!    LENGTH                  # of characters in MOP message
!    MULTICAST               TRUE if the message came in on the multicast

begin

     local
         MOP_BUFFER_PTR,
         CIRCUIT_SUBSTATE;

     ! Check if a substate is set - if so, just discard this message.
     ! We cannot process it because DLW_QUEUE will set the circuit state
     ! to AUTOSERVICE, which would mess up any ongoing load or dump.
     !
     ! The whole queueing of loads and dumps could certainly be improved;
     ! this code was written for DTE's and was twisted into fitting into
     ! the NI scheme.  Time constraints....

     if not NML$GET_VDB_PARAMETER (
                    CIRCUIT_,
                    ch$ptr (SERVICE_REQUEST [DLWQ_CIRCUIT_NAME],,8),
                    CPARM_SUBSTATE,
                    CIRCUIT_SUBSTATE)
     then CIRCUIT_SUBSTATE = CIRCUIT_NO_SUBSTATE;

     if (.CIRCUIT_SUBSTATE geq CIRCUIT_LOADING)
     and (.CIRCUIT_SUBSTATE leq CIRCUIT_AUTOTRIGGERING)
     then begin
          NMU$MEMORY_RELEASE (.SERVICE_REQUEST, DLWQ_ALLOCATION);
          return;                       ! Nothing more to do
          end;

     ! Now dispatch depending on MOP function code
     MOP_BUFFER_PTR = ch$ptr (.MOP_BUFFER,,8);
     selectone GETB (MOP_BUFFER_PTR) of
     set

     [REQUEST_PROGRAM]:

          ! Read the request program message and decompose it.  If it is
          ! OK, then queue it for service.  Otherwise, just release the
          ! DLWQ block.

          if READ_REQUEST_PROGRAM (.MOP_BUFFER_PTR,
                                   .SERVICE_REQUEST,
                                   .LENGTH - 1)     ! (We read 1 byte already)
          then begin

               ! If this came in on the multicast and it is not a request
               ! for a secondary loader, then we should flag that this
               ! request needs an 'ASSISTANCE VOLOUNTEER'

               if (.MULTICAST eql $true)
               and (.SERVICE_REQUEST [DLWQ_PROG_TYPE] neq SECONDARY_LOADER)
               then SERVICE_REQUEST [DLWQ_NEED_RPM] = $true;

               ! Queue the load request
               DLW_QUEUE (.SERVICE_REQUEST, LOAD_);
               end
          else begin

               ! Issue debugging message
               %debug (DLW_TRACE,
                       (TRACE_INFO (
                        'Illegal request program message received on %X',
                        ch$ptr (SERVICE_REQUEST [DLWQ_CIRCUIT_NAME],,8))));

               ! and release the DLWQ_BLOCK
               NMU$MEMORY_RELEASE (.SERVICE_REQUEST, DLWQ_ALLOCATION);
               end;

     [REQUEST_DUMP_SERVICE]:

          ! Process the REQUEST_DUMP_SERVICE message and decompose it.  If it
          ! is OK, queue it for service.  If not, just release the DLWQ block

          if READ_REQUEST_DUMP_SERVICE (.MOP_BUFFER_PTR,
                                        .SERVICE_REQUEST,
                                        .LENGTH - 1)   ! We read 1 byte already
          then begin

               ! If this came in on the multicast, then flag that an
               ! 'ASSISTANCE VOLOUNTEER' is needed.

               if .MULTICAST eql $true
               then SERVICE_REQUEST [DLWQ_NEED_RPM] = $true;

               ! Now queue a dump request
               DLW_QUEUE (.SERVICE_REQUEST, DUMP_);
               end

          else begin

               ! Issue debugging message
               %debug (DLW_TRACE,
                       (TRACE_INFO (
                        'Illegal request dump service message received on %X',
                        ch$ptr (SERVICE_REQUEST [DLWQ_CIRCUIT_NAME],,8))));

               ! and release the DLWQ_BLOCK
               NMU$MEMORY_RELEASE (.SERVICE_REQUEST, DLWQ_ALLOCATION);
               end;

     [otherwise]:
          NMU$MEMORY_RELEASE (.SERVICE_REQUEST, DLWQ_ALLOCATION);

     tes;

end;                                    ! End of REQUEST_SERVICE
%global_routine ('NML$DLW_READ_LDAQ', CD : ref CD_BLOCK,
                                        USAGE,
                                        PTR,
                                        LEN,
                                        RSP_POINTER,
                                        ADDR) =

!++
! Functional description:
!
!        This routine reads a maintenance message from the specified
!        circuit.
!
! Formal parameters:
!
!	.CD			Identifier for circuit
!	.USAGE			What type of read is supposed to be done
!				    (DLX_DATA for MOP data)
!	.PTR			Pointer to message buffer
!	.LEN			Number of bytes in message buffer to write
!	.RSP_POINTER		Pointer to NICE response buffer
!       .ADDR                   Pointer to hardware address we want to read
!                               from
!
! Routine value:
!
!        Number of bytes read on circuit
!
!		or
!
!	-2 for read timeout
!	-1 for any other error
!
!--

begin

local
     LDA_MSG : ref LDAQ_BLOCK,
     TEMP;

while $true do
      begin

      LDA_MSG = NMU$SQUEUE_REMOVE (LOAD_DUMP_ASST_QUEUE);
      if (ch$eql (6, .ADDR, 6, ch$ptr (LDA_MSG [LDAQ_SAD],,8))
         or .LDA_MSG [LDAQ_MSG_LEN] eql -2)
      then exitloop                     ! Have a good message
      else begin
           NMU$MEMORY_RELEASE (.LDA_MSG [LDAQ_BUFFER_ADDRESS],
                               .LDA_MSG [LDAQ_BUFFER_SIZE]);
           NMU$MEMORY_RELEASE (.LDA_MSG, LDAQ_ALLOCATION);
           end;
      end;

if .CD neq .LDA_MSG [LDAQ_CIRCUIT_HANDLE]
then begin
     NMU$MEMORY_RELEASE (.LDA_MSG [LDAQ_BUFFER_ADDRESS],
                         .LDA_MSG [LDAQ_BUFFER_SIZE]);
     NMU$MEMORY_RELEASE (.LDA_MSG, LDAQ_ALLOCATION);
     return -1;
     end;

if (( TEMP = .LDA_MSG [LDAQ_MSG_LEN]) gtr .LEN)
then begin
     NMU$MEMORY_RELEASE (.LDA_MSG [LDAQ_BUFFER_ADDRESS],
                         .LDA_MSG [LDAQ_BUFFER_SIZE]);
     NMU$MEMORY_RELEASE (.LDA_MSG, LDAQ_ALLOCATION);
     return -1;
     end;

if .TEMP eql -2
then begin
     NMU$MEMORY_RELEASE (.LDA_MSG [LDAQ_BUFFER_ADDRESS],
                         .LDA_MSG [LDAQ_BUFFER_SIZE]);
     NMU$MEMORY_RELEASE (.LDA_MSG, LDAQ_ALLOCATION);
     return -2;
     end;

! Everything well, copy message into callers buffer
ch$move (.TEMP,
         .LDA_MSG [LDAQ_BUF_PTR],
         .PTR);

NMU$MEMORY_RELEASE (.LDA_MSG [LDAQ_BUFFER_ADDRESS],
                    .LDA_MSG [LDAQ_BUFFER_SIZE]);
NMU$MEMORY_RELEASE (.LDA_MSG, LDAQ_ALLOCATION);

return .TEMP;
end;           ! Of NML$DLW_READ_LDAQ
%routine ('READ_REQUEST_PROGRAM', MOP_PTR, SERVICE_REQUEST : ref DLWQ_BLOCK, LEN) =

!++
! Functional Description:
!                        This routine reads information out of the Request
!                        Program MOP message and stores it into the appropriate
!                        field of the SERVICE_REQUEST Block.
!
! Formal Parameters:
!
!        MOP_PTR         Character pointer to the second byte of the Request
!                        Program message.
!
!        SERVICE_REQUEST Address of a Data Link Watcher Service Request
!                        Queue Block.
!
!        LEN             Number of bytes left in MOP Message.
!
! Routine Value:
!               $true    If successful.
!               $false   Error return.
!
!--

   begin

   local
        TEMP : $SIGNED_BYTE_VALUE,
        CNT;


   CNT = 0;

   if NEXTB (.LEN, CNT)
   then SERVICE_REQUEST[DLWQ_DEVTYP] = GETB (MOP_PTR)
   else return $false;

   if NEXTB (.LEN, CNT)
   then SERVICE_REQUEST[DLWQ_FORMAT_VER] = GETB (MOP_PTR)
   else return $false;

   if NEXTB (.LEN, CNT)
   then SERVICE_REQUEST[DLWQ_PROG_TYPE] = GETB (MOP_PTR)
   else return $false;

   if NEXTB (.LEN, CNT)
   then TEMP = GETB (MOP_PTR)
   else return $false;

   SERVICE_REQUEST[DLWQ_SID_FORM] = .TEMP[VALUE];

   if .SERVICE_REQUEST[DLWQ_SID_FORM] gtr 0

   then if NEXTN (.LEN, CNT, .SERVICE_REQUEST[DLWQ_SID_FORM])
        then begin
             ch$move (.TEMP,
                      .MOP_PTR,
                      ch$ptr (SERVICE_REQUEST[DLWQ_SOFTWARE_ID],,8));
             MOP_PTR = ch$plus (.MOP_PTR, .TEMP);
             end
        else return $false;

   if NEXTB (.LEN, CNT)
   then SERVICE_REQUEST[DLWQ_PROCESSOR] = GETB (MOP_PTR)
   else return $false;

   return GET_OTHER_INFO (.MOP_PTR, .SERVICE_REQUEST, .LEN, .CNT);

end;
%routine ('GET_OTHER_INFO', MOP_PTR, SERVICE_REQUEST : ref DLWQ_BLOCK, LEN, CNT) =

!++
! Functional Description:
!                        This routine read the 'Other Info' portion of
!                        any MOP message.
!
!
! Formal Parameters:
!
!        MOP_PTR         Character pointer to the second byte of the Request
!                        Program message.
!
!        SERVICE_REQUEST Address of a Data Link Watcher Service Request
!                        Queue Block.
!
!        LEN             Number of bytes in MOP Message.
!
!        CNT             Number of bytes already read from the MOP Message.
!
! Routine Value:
!               $true    If successful.
!               $false   Error return.
!
!--

   begin

   literal DATA_LINK_BUFFER_SIZE = 401;

   while NEXTW (.LEN, CNT) do
   begin
   local INFO_TYPE,
         INFO_LENGTH;

   INFO_TYPE = GETW (MOP_PTR);

   selectone .INFO_TYPE of 
             set

             [0]:
             return $true;

             [DATA_LINK_BUFFER_SIZE]:
             begin

             if NEXTB (.LEN, CNT)
             then INFO_LENGTH = GETB (MOP_PTR)
             else return $false;

             if not .INFO_LENGTH eql 2 then return $false;

             if NEXTW (.LEN, CNT)
             then SERVICE_REQUEST[DLWQ_DL_BUFSIZ] = GETW (MOP_PTR);
             end;

             [otherwise]:
             begin

             if NEXTB (.LEN, CNT)
             then INFO_LENGTH = GETB (MOP_PTR)
             else return $false;

             if NEXTN (.LEN, CNT, .INFO_LENGTH)
             then GETN (MOP_PTR, .INFO_LENGTH)
             else return $false;

             end;

             tes;

   end;

   return $true;

   end;
%routine ('READ_REQUEST_DUMP_SERVICE', MOP_PTR,
                                         SERVICE_REQUEST : ref DLWQ_BLOCK,
                                         LEN) =

!++
!
! Functional description:
!            This routine reads information out of the 'Request dump service'
!            message and stores it in the appropriate field of the SERVICE_
!            REQUEST block.
!
! Parameter:
!           MOP_PTR            Character pointer to MOP message
!           SERVICE_REQUEST    Address of DLWQ block
!           LEN                Number of bytes left in MOP message
!
! Notes:
!      The format of the REQUEST DUMP SERVICE message is:
!
!      CODE    DEVICE    FORMAT    MEMORY    BITS    OTHER INFO
!              TYPE      VERSION   SIZE
!
!      The only valid OTHER INFO parameter is DATA LINK BUFFER SIZE (401)
!--

   begin

   local CNT;

   CNT = 0;

   ! Read device type
   if NEXTB (.LEN, CNT)
   then SERVICE_REQUEST [DLWQ_DEVTYP] = GETB (MOP_PTR)
   else return $false;

   ! Read format version
   if NEXTB (.LEN, CNT)
   then SERVICE_REQUEST [DLWQ_FORMAT_VER] = GETB (MOP_PTR)
   else return $false;

   ! Read memory size
   if NEXTN (.LEN, CNT, 4)
   then SERVICE_REQUEST [DLWQ_MEMORY_SIZE] = GETN (MOP_PTR, 4)
   else return $false;

   ! Skip over bits
   if NEXTB (.LEN, CNT)
   then GETB (MOP_PTR)
   else return $false;

   ! Let the GET_OTHER_INFO routine care about the data link buffer size
   GET_OTHER_INFO (.MOP_PTR, .SERVICE_REQUEST, .LEN, .CNT);
   $true

   end;
%routine ('DLW_NI_RECEIVER') NI_RECEIVER_INTERRUPT_ROUTINE novalue =
!+
! Functional Description:
!        This routine is called whenever a NI% JSYS Receive Completion
!        Interrupt occurs.
!-

begin
     NMU$SCHED_FLAG (NI_RECEIVE_EVENT);
     PROCESS_WAKE;
end;                                    ! End of DLW_NI_RECEIVER
end                                     ! End of NMLDLW
eludom