Google
 

Trailing-Edge - PDP-10 Archives - BB-P363B-SM_1985 - t20/nmlt20/knit20.b36
There are no other files named knit20.b36 in the archive.
! UPD ID= 336, SNARK:<6.1.NML>KNIT20.B36.8,  23-Jul-85 14:22:46 by MCCOLLUM
!  Set CD_MORE_MOP_RESPONSES in READ_MOP_LOOP if bit LM%MRF (LM_MRF) is set
!   in word .LMCID ($LMCID) of the LLMOP% argument block on return from a
!   .ELRPY ($ELRPY) function. LOOP_CIRCUIT in NMLLBK reads this flag.
!
! UPD ID= 275, SNARK:<6.1.NML>KNIT20.B36.7,   6-Mar-85 11:35:55 by GLINDELL
!  Add typeout of ETHERNET address to TRACE of ETHERNET receive
!
! UPD ID= 182, SNARK:<6.1.NML>KNIT20.B36.6,   7-Dec-84 08:52:45 by HALPIN
! Change all DLX_LOAD and DLX_DUMP references to DLX_LDA. Only one 
! Circuit Block for Load Dump Assistance.
! Rewrite READ_MOP_LOAD_DUMP routine to use non-blocking reads.  Turn
! Receive PSI Interrupts in OPEN_KLNI_DEVICE.
! READ_MOP_LOAD_DUMP chacks for messages to the LOAD/DUMP ASSISTANCE
! MULTICAST ADDRESS, and sets flag in the circuit block.
! Store the Source ETHERNET Address in Circuit Block.
!
! UPD ID= 172, SNARK:<6.1.NML>KNIT20.B36.5,  19-Nov-84 10:20:25 by HALPIN
! Add NI% JSYS Interface routines for LOAD/DUMP and ENABLE MULTICAST
! MOP Functions.
!
! UPD ID= 147, SNARK:<6.1.NML>KNIT20.B36.4,  30-Oct-84 11:33:50 by GUNN
! Add BOOT_REMOTE routine.
!
! UPD ID= 141, SNARK:<6.1.NML>KNIT20.B36.3,  29-Oct-84 11:45:13 by GUNN
! Fix READ_MOP_LOOP to properly build Ethernet address of responding
! node in response message.
!
!
! UPD ID= 105, SLICE:<6.1.NML>KNIT20.B36.2,  18-Sep-84 16:22:01 by GUNN
%sbttl 'TOPS-20 Specific Ethernet KLNI LLMOP Routines'


!
! Definitions needed for debugging
!
external
    %debug_data_base;



!
! Macros
!

macro
     CH$HEX [ ] =
         ch$ptr (uplit (X_CHAR(%explode(%remaining))),,8) %;

macro
     X_CHAR [B1,B2,B3,B4,B5,B6,B7,B8] =
            ((%X B1 ^ (%bpval-4))
              %if not %null(B2)
              %then
                   or (%X B2 ^ (%bpval-8))
              %if not %null(B3)
              %then
                   or (%X B3 ^ (%bpval-12))
              %if not %null(B4)
              %then
                   or (%X B4 ^ (%bpval-16))
              %if not %null(B5)
              %then
                   or (%X B5 ^ (%bpval-20))
              %if not %null(B6)
              %then
                   or (%X B6 ^ (%bpval-24))
              %if not %null(B7)
              %then
                   or (%X B7 ^ (%bpval-28))
              %if not %null(B8)
              %then
                   or (%X B8 ^ (%bpval-32))
              %fi %fi %fi %fi %fi %fi %fi) %;

macro
    NI_ARG_BLOCK = monblock [$EIBMX] volatile %,     ! NI JSYS argument block
    BUFFER_DESCRIPTOR_BLOCK = monblock [$BXBMX] volatile %, ! Buffer Descriptor
    LLMOP_ARG_BLOCK = monblock [10] volatile %;	! LLMOP JSYS argument block

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

       FWORD = %O'777777777777',
       LHALF = %O'777777000000',
       RHALF = %O'777777';

bind
       LOAD_DUMP_ASST_MULTICAST = CH$HEX ('AB0000010000');
%routine ('OPEN_KLNI_DEVICE', CD: ref CD_BLOCK, RSP_POINTER) =

!++
! Functional description:
!
!	This routine is called by NMU$KLNI_OPEN to perform
!	system specific operations for servicing a KLNI.
!
! Formal parameters:
!
!	.CD			Pointer to circuit data block
!	.RSP_POINTER		Pointer to NICE response buffer
!
! Routine value:
!
!	$true			System specific open succeeded
!	$false			Error opening KLNI
!
!--

begin

literal
       LOAD_DUMP_PROTOCOL = %X'6001';

if .CD [CD_USAGE] eql DLX_LDA
then begin

     local
          ERROR_CODE,
          NI_BLOCK : NI_ARG_BLOCK;

     DECLARE_JSYS (NI, GETER);

     incr I from 0 to $EIBMX-1
          do
          NI_BLOCK[.I,FWORD] = 0;

     NI_BLOCK [$EILEN,EILEN] = $EIBMX;       ! Open Function takes 4 words
     NI_BLOCK [$EIFCN,EIFCN] = $EIOPN;
     NI_BLOCK [$EIFLG,EIPAD] = 1;
     NI_BLOCK [$EICHN,EICHN] = .CD [CD_KLNI_CHAN];
     NI_BLOCK [$EIPRO,EIPRO] = LOAD_DUMP_PROTOCOL;
     NI_BLOCK [$EIPSI,EIRCH] = .CD [CD_KLNI_RCV_PSI];
     NI_BLOCK [$EIPSI,EITCH] = .CD [CD_KLNI_XMT_PSI];
     NI_BLOCK [$EIPSI,EISCH] = .CD [CD_KLNI_STS_PSI];

     if not $$NI (NI_BLOCK)
     then begin
          $$GETER($FHSLF; ERROR_CODE);
          $RESPONSE (.RSP_POINTER, NICE$_OPF, 0,
                     'Open failure on %X.  Status = %O',
                     ch$ptr (CD [CD_NAME],,8),
                     .ERROR_CODE);
          return $false;
          end;


     CD [CD_KLNI_PRTLID] = .NI_BLOCK [$EIPID,EIPID];

     if (CD [CD_RCV_BID] = NMU$MEMORY_GET (MOP_BUFFER_ALLOCATION)) eql 0
     then                                ! Allocation failure
     begin
        $RESPONSE (.RSP_POINTER,NICE$_REE,0);
        CLOSE_KLNI_DEVICE (.CD);
	return $false;
     end;

     if not POST_RECEIVE_BUFFER (.CD, .RSP_POINTER)
     then return $false;

     end;

return $true;

end;				! of OPEN_KLNI_DEVICE
%routine ('CLOSE_KLNI_DEVICE', CD: ref CD_BLOCK) =

!++
! Functional description:
!
!	This routine is called by NMU$KLNI_CLOSE to perform
!	system specific operations for releasing a KLNI.
!
! Formal parameters:
!
!	.CD			Pointer to circuit data block
!
! Routine value:
!
!	$true			System specific release succeeded
!	$false			Error releasing KLNI
!
!--

begin

     local
          ERROR_CODE,
          NI_BLOCK : NI_ARG_BLOCK;

     DECLARE_JSYS (NI);

     if .CD [CD_RCV_BID] neq 0
     then NMU$MEMORY_RELEASE (.CD[CD_RCV_BID], MOP_BUFFER_ALLOCATION);

     CD [CD_RCV_BID] = 0;

     incr I from 0 to $EIBMX-1
          do
          NI_BLOCK[.I,FWORD] = 0;

     NI_BLOCK [$EILEN,EILEN] = $EIBMX;       ! Close Function takes 2 words
     NI_BLOCK [$EIFCN,EIFCN] = $EICLO;
     NI_BLOCK [$EIFLG,EIFLG] = 0;
     NI_BLOCK [$EIPID,EIPID] = .CD [CD_KLNI_PRTLID];

     if not $$NI (NI_BLOCK)
     then return $false;

    $true

end;				! of CLOSE_KLNI_DEVICE
%routine ('READ_MOP_LOOP', CD: ref CD_BLOCK, PTR, LEN, RSP_POINTER) =

!++
! Functional description:
!
!	This routine is used by NMU$KLNI_READ to read a MOP loop
!	reply message from a remote node on an Ethernet.
!
! Formal parameters:
!
!	.CD			Pointer to circuit data block
!	.PTR			Pointer to message buffer
!	.LEN			Number of bytes in message buffer to write
!	.RSP_POINTER		Pointer to NICE response buffer
!
! Routine value:
!
!        Number of bytes read on circuit
!
!		or
!
!	-2 for read timeout
!	-1 for any other error
!
!--

begin
forward routine
    BUILD_PARAMETER_DATA;
routine BUILD_PARAMETER_DATA (ENTITY_TYPE, PARMNO, PVALU, LENGTH, POINTER) =

%( Nota Bene:

%routine ('BUILD_PARAMETER_DATA', ENTITY_TYPE, PARMNO, PVALU, LENGTH, POINTER) =

   The code in this routine was lifted from NMLVDB 'READ_PARAMETER_DATA'.
   It is here only temporarily. It should be moved to NMLPRM and made
   to be a global routine. NMLEVD could be changed to make use of this.

)%

!++
! FUNCTIONAL DESCRIPTION:
!
!	Builds the DATA ID, DATA TYPE and PARAMETER DATA fields of the
!       NICE response message into the response message buffer.
!
! FORMAL PARAMETERS
!
!	ENTITY_TYPE     Entity type.
!	PARMNO          The architecturally defined parameter number
!                       for this parameter.
!       PVALU           The value for this parameter.
!       LENGTH          The length of the buffer remaining.
!       POINTER         The address of a character sequence pointer to the
!                       buffer.
!
! IMPLICIT INPUTS
!
!	NONE.
!
! ROUTINE VALUE:
!
!	The number of bytes for this parameter excluding data id
!	and datatype bytes.
!
! SIDE EFFECTS:
!
!	If the parameter will fit in the buffer, it is copied into
!	the buffer.
!
!--

    begin

    external routine
             NML$DATA_TYPE;


    macro
         PUTN (VALUE, PTR_ADR, N) =
             begin
             local L, X;

             X = VALUE;
             ch$wchar_a (.X, PTR_ADR);
             incr L from 256 to ((N - 1) * 256) by 256
             do begin
                X = .X / .L;
                ch$wchar_a (.X, PTR_ADR);
                end;

             .L / 256
             end %;

    local
         BUF_PTR,
         BUF_LEN,
         DATA_ID: block [1] field (DATA_ID_FIELDS), ! Data ID
         DATA_TYPE: block [1] field (DATA_TYPE_FIELDS);

    BUF_PTR = ..POINTER;
    DATA_ID = 0;
    DATA_ID[DI_PARMNO] = .PARMNO;       ! Get parameter number
    DATA_ID[DI_TYPE] = 0;               ! This is a parameter
    DATA_TYPE = NML$DATA_TYPE (.ENTITY_TYPE, .PARMNO);

    if .DATA_TYPE[DT_CODED] eql 0       ! Not coded
    then begin
         PUTW (DATA_ID, BUF_PTR);       ! Write data id into buffer
         PUTB (.DATA_TYPE, BUF_PTR);    ! Write data type into buffer
         if .DATA_TYPE[DT_FTYPE] eql 0  ! Binary number
         then begin
              if .DATA_TYPE[DT_LENGTH] eql 0
              then begin                ! Binary image field
                   BUF_LEN = ch$rchar (ch$ptr (.PVALU,,8)) + 1;
                   if .LENGTH geq .BUF_LEN
                   then BUF_PTR = ch$move (.BUF_LEN,
                                           ch$ptr (.PVALU,,8),
                                           .BUF_PTR);
                   end
              else begin
                   BUF_LEN = .DATA_TYPE[DT_LENGTH];
                   if .DATA_TYPE[DT_LENGTH] leq %bpval/8
                   then begin           ! Data stored in value
                        if .LENGTH geq .BUF_LEN
                        then PUTN (.PVALU,
                                   BUF_PTR,
                                   .DATA_TYPE[DT_LENGTH]);
                        end
                   else begin           ! Data stored in buffer
                        BUF_LEN = .DATA_TYPE[DT_LENGTH];
                        if .LENGTH geq .BUF_LEN
                        then BUF_PTR = ch$move (.BUF_LEN,
                                                ch$ptr (.PVALU,,8),
                                                .BUF_PTR);
                        end;
                   end;
              end
         else begin                     ! ASCII image field
              BUF_LEN = ch$rchar (ch$ptr (.PVALU,,8)) + 1;
              if .LENGTH geq .BUF_LEN
              then BUF_PTR = ch$move (.BUF_LEN,
                                      ch$ptr (.PVALU,,8),
                                      .BUF_PTR);
              end;
         end
    else begin                          ! Coded

         %( N.B. - We have to do special casing here for multiple coded
            fields. )%

         if .DATA_TYPE[DT_FTYPE] eql 0  ! Single field
         then begin
              PUTW (DATA_ID, BUF_PTR);  ! Write data id into buffer
              PUTB (.DATA_TYPE, BUF_PTR); ! Write data type into buffer
              BUF_LEN = .DATA_TYPE[DT_NUMBER];
              if .LENGTH geq .BUF_LEN
              then PUTN (.PVALU, BUF_PTR, .DATA_TYPE[DT_NUMBER]);
              end
         else selectone .DATA_TYPE[DT_NUMBER] of
                  set                   ! Multiple field
                  [CMN] :               ! Node format
                      begin
                      local PTR;

                      PTR = ch$ptr (.PVALU,,8);
                      begin
                      literal DATA_TYPE = 1^7 + 1^6 + 2;
                      PUTW (DATA_ID, BUF_PTR); ! Write data id
                      PUTB (DATA_TYPE, BUF_PTR); ! Write data type
                      end;

                      BUF_LEN = 6 + 5;	! Assume node name of 6 chars
                      if .LENGTH geq .BUF_LEN
                      then
                        begin
                        !
                        ! Node address
                        !

                          begin
                          literal DATA_TYPE = 0^7 + 0^6 + 0^4 + 2;
                          local NODE_ADDRESS;

                          NODE_ADDRESS = GETW (PTR);
                          PUTB (DATA_TYPE, BUF_PTR); ! Write Data Type
                          PUTW (NODE_ADDRESS, BUF_PTR); ! Write address
                          end;

                        !
                        ! Node name
                        !

                          begin
                          literal DATA_TYPE = 0^7 + 1^6;
                          local NODE_NAME_LENGTH;

                          PUTB (DATA_TYPE, BUF_PTR); ! Write Data Type
                          NODE_NAME_LENGTH = GETB (PTR); ! Get I-length
                          PUTB (.NODE_NAME_LENGTH, BUF_PTR);
                          BUF_PTR = ch$move (.NODE_NAME_LENGTH,
                                             .PTR,
                                             .BUF_PTR);
                          BUF_LEN = .NODE_NAME_LENGTH + 5; ! Real length
                          end;
                        end;
                      end;

                  [CMV] :               ! Version format
                      begin
                      external          ! Version numbers defined in NMUSKD
                          NMLVER,       ! Major version
                          DECECO,       ! Minor version
                          USRECO;       ! Customer version

                      begin
                      literal DATA_TYPE = 1^7 + 1^6 + 3;
                      PUTW (DATA_ID, BUF_PTR); ! Write data id
                      PUTB (DATA_TYPE, BUF_PTR); ! Write data type
                      end;
                      BUF_LEN = 6;      ! Six bytes to write excluding
                                        ! data id and data type bytes

                      if .LENGTH geq .BUF_LEN
                      then
                          begin
                          literal DATA_TYPE = 0^7 + 0^6 + 0^4 + 1;

                          PUTB (DATA_TYPE, BUF_PTR); ! Write Data Type
                          PUTB (.NMLVER, BUF_PTR); ! Write version byte
                          PUTB (DATA_TYPE, BUF_PTR); ! Write Data Type
                          PUTB (.DECECO, BUF_PTR); ! Write version byte
                          PUTB (DATA_TYPE, BUF_PTR); ! Write Data Type
                          PUTB (.USRECO, BUF_PTR); ! Write version byte
                          end;

                      end;

                  [otherwise] :         ! Unknown multiple parameter
                      BUF_LEN = 0;
                  tes;
         end;

    .POINTER = .BUF_PTR;

    return .BUF_LEN;                    ! Return number of bytes written
    end;				! End of BUILD_PARAMETER_DATA
    DECLARE_JSYS (LLMOP);

    local
        COUNT,
	LLMOP_BLOCK: LLMOP_ARG_BLOCK;	! LLMOP JSYS argument block

    LLMOP_BLOCK [$LMCID,FWORD] = 0;     ! Clear LLMOP Arg Block
    LLMOP_BLOCK [$LMDST,FWORD] = 0;
    LLMOP_BLOCK [$LMDST+1,FWORD] = 0;
    LLMOP_BLOCK [$LMREQ,FWORD] = 0;
    LLMOP_BLOCK [$LMRBL,FWORD] = 0;

    !
    ! First wait for reply message to arrive. Timeout after 'n' seconds
    !

    LLMOP_BLOCK [$LMCID,FWORD] = .CD[CD_KLNI_CHAN]; ! Pass channel id
    LLMOP_BLOCK [$LMREQ,LM_REQ] = .CD[CD_KLNI_REQNO]; ! Pass request number

    incr COUNT from 1 to 5 do
         begin
              if not $$LLMOP ($ELSTS, LLMOP_BLOCK)
              then
                  begin
                       RSP_POINTER = ch$plus (.RSP_POINTER,
                                              $RESPONSE (.RSP_POINTER,
                                                         NICE$_OPF,
                                                         0,
                                                         'LLMOP Failure',
                                                         ch$ptr (CD [CD_NAME],,
                                                                 8)));
                       return -1;
                  end;

              if .LLMOP_BLOCK [$LMSTF,LM_RTC] eql $LMSUC
              then
                  begin
                  !
                  ! Read MOP data message from KLNI
                  !

                  LLMOP_BLOCK [$LMRBL,LM_MBL] = .LEN; ! Pass length of reply buffer
                  LLMOP_BLOCK [$LMRBP,FWORD] = .PTR; ! Pass pointer to reply buffer

                  if not $$LLMOP ($ELRPY, LLMOP_BLOCK)
                  then
                      begin
                           RSP_POINTER = ch$plus (.RSP_POINTER,
                                                  $RESPONSE (.RSP_POINTER,
                                                             NICE$_OPF,
                                                             0,
                                                             'Failure reading MOP loop reply on %X.  Status = %O',
                                                             ch$ptr (CD [CD_NAME],,8),
                                                             .LLMOP_BLOCK [$LMSTF,LM_RTC]));
                           return -1;
                      end
                  else
                      begin
                           
                      local             ! Buffer for Ethernet reply address
                           RPYADR : vector [ch$allocation(7,8)*%upval];

%( Here put in the Data-Id and Data-Type bytes. See NMARCH and/or NMLPRM?)%

                      RSP_POINTER = ch$plus (.RSP_POINTER,
                                             $RESPONSE (.RSP_POINTER,
                                                        NICE$_SUC,
                                                        0));

                      ! Build ENTITY ID field in response message

                      RSP_POINTER = ch$move(ch$rchar(ch$ptr(CD[CD_NAME],,8))+1,
                                            ch$ptr (CD [CD_NAME],,8),
                                            .RSP_POINTER);

                      ! Build TEST DATA field in response message

                      PUTB (0, RSP_POINTER);
                      PUTB (0, RSP_POINTER);

                      ! Build DATA BLOCK field in response message

                      !
                      ! Conjure up an HI-6 parameter field.
                      !
                      ! Shuffle the 6 byte address forward to make room
                      ! for the I field, then store the I-6 field.
                      !

                      ch$move (6,
                               ch$ptr(LLMOP_BLOCK [$LMSRC,FWORD],,8),
                               ch$ptr(RPYADR,1,8));

                      ch$wchar (6,ch$ptr(RPYADR,0,8));

                      BUILD_PARAMETER_DATA (ENTITY_NODE, ! NODE Entity
                                            010, ! PHYSICAL ADDRESS Parameter
                                            RPYADR,
                                            12, ! Fake this for now
                                            RSP_POINTER);

                      CD[CD_MORE_MOP_RESPONSES] = .LLMOP_BLOCK[$LMCID,LM_MRF];

                      return .LLMOP_BLOCK [$LMRBL,LM_RML];
                      end;
                  end
              else
                  NMU$SCHED_SLEEP(1);        ! Sleep a second before reading
         end;

    $$LLMOP ($ELABT, LLMOP_BLOCK);      ! Timed Out, Abort Request
    return -2                           ! Indicate timeout to caller

end;				! of READ_MOP_LOOP
%routine ('WRITE_MOP_LOOP', CD: ref CD_BLOCK, PTR, LEN, RSP_POINTER) =

!++
! Functional description:
!
!	This routine is used by NMU$KLNI_WRITE to send MOP data
!	messages on the Ethernet using a KLNI.
!
! Formal parameters:
!
!	.CD			Pointer to circuit data block
!	.PTR			Pointer to message buffer
!	.LEN			Number of bytes in message buffer to write
!	.RSP_POINTER		Pointer to NICE response buffer
!
! Routine value:
!
!	$true			If successful
!	$false			If error
!
!--

begin

    DECLARE_JSYS (LLMOP);

    local
	LLMOP_BLOCK: LLMOP_ARG_BLOCK;	! LLMOP JSYS argument block

    !
    ! Set up the LLMOP JSYS argument block
    !

    LLMOP_BLOCK [$LMCID,FWORD] = 0;     ! Clear LLMOP Arg Block
    LLMOP_BLOCK [$LMDST,FWORD] = 0;
    LLMOP_BLOCK [$LMDST+1,FWORD] = 0;
    LLMOP_BLOCK [$LMREQ,FWORD] = 0;
    LLMOP_BLOCK [$LMRBL,FWORD] = 0;

    LLMOP_BLOCK [$LMCID,LM_CID] = .CD[CD_KLNI_CHAN]; ! Pass channel id
    LLMOP_BLOCK [$LMRBL,LM_MBL] = .LEN; ! Pass length of data message
    LLMOP_BLOCK [$LMRBP,FWORD] = .PTR;  ! Pass pointer to data message

    ch$move (6, .CD[CD_KLNI_PHYADR], ch$ptr(LLMOP_BLOCK[$LMDST,FWORD],,8));

    !
    ! Send MOP loop message
    !

    if not $$LLMOP ($ELDIR, LLMOP_BLOCK)
    then
	begin
	    $RESPONSE (.RSP_POINTER, NICE$_OPF, 0,
		       'Failure sending MOP loop data on %X.  Status = %O',
		       ch$ptr (CD [CD_NAME],,8),
		       .LLMOP_BLOCK [$LMSTF,LM_RTC]);
	    return $false;
	end;

    CD[CD_KLNI_REQNO] = .LLMOP_BLOCK [$LMREQ,LM_REQ]; ! Return request number

    $true

end;				! of WRITE_MOP_LOOP
%routine ('BOOT_REMOTE', CD: ref CD_BLOCK, PTR, LEN, RSP_POINTER) =

!++
! Functional description:
!
!	This routine is used by NMU$KLNI_WRITE to send a MOP BOOT
!	message on the Ethernet using a KLNI.
!
! Formal parameters:
!
!	.CD			Pointer to circuit data block
!	.PTR			Pointer to message buffer
!	.LEN			Number of bytes in message buffer to write
!	.RSP_POINTER		Pointer to NICE response buffer
!
! Routine value:
!
!	$true			If successful
!	$false			If error
!
!--

begin

    DECLARE_JSYS (LLMOP);

    local
         L,
         LLMOP_BLOCK: LLMOP_ARG_BLOCK;	! LLMOP JSYS argument block

    if .LEN lss 12                      ! Is it a V3.0 style BOOT message?
    then
        begin
             $RESPONSE (.RSP_POINTER, NICE$_MPE, 0,
                        'Invalid BOOT message length');
             return $false;
        end;

    !
    ! Set up the LLMOP JSYS argument block
    !

    LLMOP_BLOCK [$LMCID,FWORD] = 0;     ! Clear Channel-Id
    LLMOP_BLOCK [$LMDST,FWORD] = 0;     ! Clear Destination Address
    LLMOP_BLOCK [$LMDST+1,FWORD] = 0;
    LLMOP_BLOCK [$LMPWD,FWORD] = 0;     ! Clear Password Verification
    LLMOP_BLOCK [$LMPWD+1,FWORD] = 0;
    LLMOP_BLOCK [$LMCIF,FWORD] = 0;     ! Clear Control Information
    LLMOP_BLOCK [$LMDID,FWORD] = 0;     ! Clear Device Id
    LLMOP_BLOCK [$LMSID,FWORD] = 0;     ! Clear Software Id

    LLMOP_BLOCK [$LMCID,LM_CID] = .CD[CD_KLNI_CHAN]; ! Pass channel id

    if GETB (PTR) neq ENTER_MOP_MODE    ! Is it a BOOT message
    then
        begin
             $RESPONSE (.RSP_POINTER, NICE$_MPE, 0,
                        'Invalid BOOT message function code');
             return $false;
        end;

    PTR = ch$move (8,                   ! Move Password Verification
                   .PTR,                !   From message
                   ch$ptr(LLMOP_BLOCK[$LMPWD,FWORD],,8)); !   To Arg Block

    ch$move (6,                         ! Move Ethernet Destination Address
             .CD[CD_KLNI_PHYADR],       !   From Circuit Data Block
             ch$ptr(LLMOP_BLOCK[$LMDST,FWORD],,8)); !   To Argument Block

    LLMOP_BLOCK [$LMCIF,LM_PRO] = GETB (PTR); ! Set Processor
    LLMOP_BLOCK [$LMCIF,(LM_BDV or LM_BSV)] = GETB (PTR); ! Set Control Info

    if .LLMOP_BLOCK [$LMCIF,LM_BDV]     ! Boot Device present?
    then
        begin
        if (L = GETB (PTR)) lss (12 - .LEN)
        then
            begin
                 $RESPONSE (.RSP_POINTER, NICE$_MPE, 0,
                            'Invalid BOOT message - length');
                 return $false;
            end;

        LLMOP_BLOCK [$LMDID,FWORD] = .PTR; ! Get Device Id pointer
        PTR = ch$plus (.PTR, .L);       ! Update pointer
        end
    else L = 0;

    if .LEN gtr .L + 11
    then
        LLMOP_BLOCK [$LMSID,FWORD] = .PTR; ! Set Software Id pointer

    !
    ! Send MOP BOOT message
    !

    if not $$LLMOP ($RCRBT, LLMOP_BLOCK)
    then
	begin
             $RESPONSE (.RSP_POINTER, NICE$_OPF, 0,
		       'Failure sending MOP message on %X.  Status = %O',
		       ch$ptr (CD [CD_NAME],,8),
		       .LLMOP_BLOCK [$LMSTF,LM_RTC]);
             return $false;
	end;

    $true

end;				! of BOOT_REMOTE
%routine ('WRITE_MOP_LOAD_DUMP', CD: ref CD_BLOCK, PTR, LEN, RSP_POINTER) =

!++
! Functional description:
!
!	This routine is used by NMU$KLNI_WRITE to send MOP data
!	messages on the Ethernet using a KLNI.
!
! Formal parameters:
!
!	.CD			Pointer to circuit data block
!	.PTR			Pointer to message buffer
!	.LEN			Number of bytes in message buffer to write
!	.RSP_POINTER		Pointer to NICE response buffer
!
! Routine value:
!
!	$true			If successful
!	$false			If error
!
!--

begin

     DECLARE_JSYS (NI, GETER);

local
     ERROR_CODE,
     BUFFER_DESCRIPTOR : BUFFER_DESCRIPTOR_BLOCK,
     NI_BLOCK : NI_ARG_BLOCK;

	    %debug (NETWORK_TRACE,
		(begin
		 local COUNT, POINTER, OUTCNT;

                 TRACE_INFO (
                      'Writing MOP message to Link %X, %D Bytes',
                      ch$ptr (CD[CD_NAME],,8),
                      .LEN);
                 TRACE_INFO ('address %6K', .CD [CD_KLNI_PHYADR]);

		 POINTER = .PTR;
		 COUNT = .LEN;

		 while .COUNT gtr 0 do
			begin
			OUTCNT = min (.COUNT, 8);
			TRACE_INFO (' %#B', .OUTCNT, .POINTER);
			POINTER = ch$plus (.POINTER, 8);
			COUNT = .COUNT - 8;
			end;

		 end));

     incr I from 0 to $BXBMX-1
          do
          BUFFER_DESCRIPTOR [.I,FWORD] = 0;

     BUFFER_DESCRIPTOR [$BXLEN,FWORD] = $BXBMX;
     BUFFER_DESCRIPTOR [$BXNXT,BXNXT] = 0; ! No chain here.
     BUFFER_DESCRIPTOR [$BXBSZ,BXBSZ] = .LEN; ! Length of Datagram in bytes
     BUFFER_DESCRIPTOR [$BXBFA,BXBFA] = .PTR; ! Pointer to start of datagram
     ch$move (6, .CD[CD_KLNI_PHYADR], ch$ptr(BUFFER_DESCRIPTOR [$BXDAD,BXDAD],,8));

     incr I from 0 to $EIBMX-1
          do
          NI_BLOCK[.I,FWORD] = 0;

     NI_BLOCK [$EILEN,EILEN] = $EIBMX;   ! XMIT Function takes 3 words
     NI_BLOCK [$EIFCN,EIFCN] = $EIXMT;  ! Transmit a Datagram
     NI_BLOCK [$EIPID,EIPID] = .CD [CD_KLNI_PRTLID]; ! We need the Portal ID
     NI_BLOCK [$EIFLG,EIBLK] = 1;                    ! Blocking Transmits for now.
     NI_BLOCK [$EIBCP,EIBCP] = BUFFER_DESCRIPTOR; !Address of Buffer descriptor

     if not $$NI(NI_BLOCK)
     then begin
          $$GETER($FHSLF; ERROR_CODE);
          $RESPONSE (.RSP_POINTER, NICE$_OPF, 0,
                     'Failure sending MOP load/dump data on %X.  Status = %O',
                     ch$ptr (CD [CD_NAME],,8),
                     .ERROR_CODE);
          return $false;
          end;
     return $true;
end;                         ! of WRITE_MOP_LOAD
%routine ('READ_MOP_LOAD_DUMP', CD: ref CD_BLOCK, PTR, LEN, RSP_POINTER) =

!++
! Functional description:
!
!	This routine is used by NMU$KLNI_READ to read MOP data
!	messages on the Ethernet using a KLNI.
!
! Formal parameters:
!
!	.CD			Pointer to circuit data block
!	.PTR			Pointer to message buffer
!	.LEN			Size of the receive buffer
!	.RSP_POINTER		Pointer to NICE response buffer
!
! Routine value:
!
!        Number of bytes read on circuit
!
!		or
!
!	-2 for read timeout
!	-1 for any other error
!
!--

begin

     DECLARE_JSYS (NI, GETER);

local
     ERROR_CODE,
     BUFFER_DESCRIPTOR : BUFFER_DESCRIPTOR_BLOCK,
     NI_BLOCK : NI_ARG_BLOCK,
     LENGTH;

     incr I from 0 to $BXBMX-1
     do BUFFER_DESCRIPTOR [.I,FWORD] = 0;

     BUFFER_DESCRIPTOR [$BXLEN,FWORD] = $BXBMX;
     BUFFER_DESCRIPTOR [$BXNXT,BXNXT] = 0;

     incr I from 0 to $EIBMX-1
     do NI_BLOCK[.I,FWORD] = 0;

     NI_BLOCK [$EILEN,EILEN] = $EIBMX;
     NI_BLOCK [$EIFCN,EIFCN] = $EIRRQ;
     NI_BLOCK [$EIFLG,EIFLG] = 0;                    ! Don't Block
     NI_BLOCK [$EIPID,EIPID] = .CD [CD_KLNI_PRTLID]; ! We need the Portal ID
     NI_BLOCK [$EIBCP,EIBCP] = BUFFER_DESCRIPTOR;

     if not $$NI (NI_BLOCK)
     then begin
               $$GETER($FHSLF; ERROR_CODE);
               $RESPONSE (.RSP_POINTER, NICE$_OPF, 0,
                          'Failure reading MOP load/dump data on %X.  Status = %O',
                          ch$ptr (CD [CD_NAME],,8),
                          .ERROR_CODE);
               return -1;
          end;

     if ((LENGTH = .BUFFER_DESCRIPTOR [$BXBSZ,BXBSZ]) gtr .LEN)
     then return -1;

     if .BUFFER_DESCRIPTOR [$BXSTA,BXVAL]
     then begin
          local
               TMP_PTR;

          TMP_PTR = ch$ptr (.CD[CD_RCV_BID],2,8);  ! And point past it.
          ch$move (.LENGTH,                          ! Copy data from KLNI
                   .TMP_PTR,                       ! Receive buffer to NML's
                   .PTR);                          ! MOP buffer

          ch$move (6, ch$ptr (BUFFER_DESCRIPTOR [$BXSAD,BXSAD],,8),
                   .CD [CD_KLNI_HRDWADR]);

          TMP_PTR = LOAD_DUMP_ASST_MULTICAST;

          CD [CD_KLNI_MULTICAST] = ch$eql (6, .TMP_PTR,
                      6, ch$ptr ( BUFFER_DESCRIPTOR [$BXDAD,BXDAD],,8));

     %debug (NETWORK_TRACE,
             (if .LENGTH gtr 0
              then
              begin
                   local COUNT, PTR, OUTCNT;

                   TRACE_INFO ('Read MOP data on link %X, %D bytes read',
                       ch$ptr(CD[CD_NAME],,8),
                       .LENGTH,
                   );
                   TRACE_INFO ('address %6K, multicast: %D',
                       ch$ptr (BUFFER_DESCRIPTOR [$BXSAD,BXSAD],,8),
                       .CD [CD_KLNI_MULTICAST]
                   );
                   PTR = ch$ptr(.CD[CD_RCV_BID],2,8);
                   COUNT = .LENGTH;

                   while .COUNT gtr 0 do
                   begin
                        OUTCNT = min (.COUNT, 8);
                        TRACE_INFO (' %#B', .OUTCNT, .PTR);
                        PTR = ch$plus (.PTR, 8);
                        COUNT = .COUNT - 8;
                   end;
              end
              else TRACE_INFO ('Read MOP data failure on link %X, after interrupt',
                               ch$ptr(CD[CD_NAME],,8))));

          if not POST_RECEIVE_BUFFER (.CD, .RSP_POINTER) ! Post the buffer again
          then return -1;

          return .BUFFER_DESCRIPTOR [$BXBSZ,BXBSZ];! And return the msg len
          end;

return -1;

end;
%routine ('POST_RECEIVE_BUFFER', CD: ref CD_BLOCK, RSP_POINTER) =

begin

     DECLARE_JSYS (NI, GETER);

local
     ERROR_CODE,
     BUFFER_DESCRIPTOR : BUFFER_DESCRIPTOR_BLOCK,
     NI_BLOCK : NI_ARG_BLOCK;


     incr I from 0 to $BXBMX-1
          do
          BUFFER_DESCRIPTOR [.I,FWORD] = 0;

     BUFFER_DESCRIPTOR [$BXLEN,FWORD] = $BXBMX;
     BUFFER_DESCRIPTOR [$BXNXT,BXNXT] = 0; ! No chain here.
     BUFFER_DESCRIPTOR [$BXBSZ,BXBSZ] = MOP_BUFFER_LENGTH; ! Buffer size in bytes
     BUFFER_DESCRIPTOR [$BXBFA,BXBFA] = ch$ptr (.CD [CD_RCV_BID],,8);
     BUFFER_DESCRIPTOR [$BXBID,BXBID] = .CD [CD_RCV_BID]; ! Receive Buffer Id

     incr I from 0 to $EIBMX-1
          do
          NI_BLOCK[.I,FWORD] = 0;

     NI_BLOCK [$EILEN,EILEN] = $EIBMX;
     NI_BLOCK [$EIFCN,EIFCN] = $EIRCV;
     NI_BLOCK [$EIPID,EIPID] = .CD [CD_KLNI_PRTLID];
     NI_BLOCK [$EIFLG,EIFLG] = 0;
     NI_BLOCK [$EIBCP,EIBCP] = BUFFER_DESCRIPTOR; !Address of Buffer descriptor

     if not $$NI (NI_BLOCK)
     then begin
          $$GETER($FHSLF; ERROR_CODE);
          $RESPONSE (.RSP_POINTER, NICE$_OPF, 0,
                     'Could not post a reveive buffer on %X.  Status = %O',
                     ch$ptr (CD [CD_NAME],,8),
                     .ERROR_CODE);
          CLOSE_KLNI_DEVICE (.CD);
          return $false;
          end;

     return $true;

end;
%routine ('ENABLE_MULTICAST_ADDRESS', CD: ref CD_BLOCK, RSP_POINTER) =


begin

DECLARE_JSYS (NI, GETER);

local
     ERROR_CODE,
     NI_BLOCK : NI_ARG_BLOCK,
     TMP_PTR;

     incr I from 0 to $EIBMX-1
          do
          NI_BLOCK[.I,FWORD] = 0;

     TMP_PTR = LOAD_DUMP_ASST_MULTICAST;

     NI_BLOCK [$EILEN,EILEN] = $EIBMX;
     NI_BLOCK [$EIFCN,EIFCN] = $EIEMA;
     NI_BLOCK [$EIFLG,EIFLG] = 0;                    ! Don't Block
     NI_BLOCK [$EIPID,EIPID] = .CD [CD_KLNI_PRTLID]; ! We need the Portal ID

     ch$move (6, .TMP_PTR, ch$ptr (NI_BLOCK [$EIAR1,EIAR1],,8));

     if not $$NI (NI_BLOCK)
     then begin
            $$GETER($FHSLF; ERROR_CODE);
	    $RESPONSE (.RSP_POINTER, NICE$_OPF, 0,
		       'Failure enabling load/dump assistance on %X.  Status = %O',
		       ch$ptr (CD [CD_NAME],,8),
                       .ERROR_CODE);
            return $false;
          end;

     return $true;

end;

! Local Modes:
! Mode:BLISS
! Auto Save Mode:0
! Comment Column:40
! Comment Rounding:+1
! End: