Google
 

Trailing-Edge - PDP-10 Archives - SRI_NIC_PERM_SRC_1_19910112 - 6-1-monitor/enet.mac
There are 2 other files named enet.mac in the archive. Click here to see a list.
;SRC:<6.1.MONITOR.STANFORD>ENET.MAC.124, 26-Nov-86 13:19:56, Edit by BILLW
; Make IPRCI1-2 more lenient of mismatched hardware/software data lengths
;[SU-SIERRA.ARPA]FS1:<DEC.6.1.MERGE>ENET.MAC.123, 20-Jun-86 16:13:28, Edit by GROSSMAN
; ETHINI - ignore NCTs if NTLADR is invalid.  This prevents the setting of
; a bogus PUP host address.
;<6-1-MONITOR>ENET.MAC.121,  5-May-85 14:27:44, Edit by LOUGHEED
; Type packet length on BADIP bugchk.
; Detect, report, and discard trailer encapsulated IP datagrams
;<6-1-MONITOR>ENET.MAC.120, 28-Apr-85 15:59:20, Edit by LOUGHEED
; Change PSKED reference to PSKD1 to minimize scheduler thrashing
;<6-1-MONITOR>ENET.MAC.119,  8-Apr-85 22:48:03, Edit by LOUGHEED
; SUBNTF flag controls use of subnets
;<6-1-MONITOR>ENET.MAC.118, 18-Mar-85 12:36:43, Edit by LOUGHEED
;<6-1-MONITOR>ENET.MAC.116, 15-Mar-85 14:54:31, Edit by LOUGHEED
;Move code into extended code section
;Flush previous edit history
SEARCH PROLOG,PHYPAR,ANAUNV
TTITLE (ENET,ENET,< - Ethernet Routines for PUP and IP Protocols>)
SUBTTL Kirk Lougheed / Stanford University / 26-February-83

; Copyright (c) 1983, 1984, 1985
; The Board of Trustees, Leland Stanford Junior University, Stanford, CA 94305
; All Rights Reserved.

; The information in this software is subject to change without notice
; and should not be construed as a commitment by Stanford University.
; Stanford assumes no responsibility for the use or reliability of
; this software.
SUBTTL Parameter Definitions

IFNDEF STANSW,<STANSW==0>	;Define Stanford site switch
IFNDEF PUPSW,<PUPSW==0>		;Define switch for PUP protocol support
IFNDEF REL6,<REL6==1>		;Default assembly for Release 6.x

IFE REL6,<DEFINE XSWAPCD <SWAPCD>>
IFE REL6,<DEFINE XRESCD <RESCD>>

EXTN <PKORCI,PKOWAI>		;Symbols defined in PKOPR.MAC
EXTN <ARPENQ,RESOLV>		;Symbols defined in ARP.MAC

IFN STANSW,<

COMMENT \
	At Stanford we split our network into subnets and (at present) use
the PUP routing mechanisms to maintain knowledge of the SU-Net's topology.
The gateways do not speak ICMP, but do know how to forward datagrams for a
variety of protocols.  We borrow from the Stanford PUP code to figure out how
to set up the Ethernet encapsulation.
\

;DEFSTR's from PUP.MAC describing an entry in the PUPROU routing table

DEFSTR ROUACC,PUPROU-1,0,1	;Bit is set if network inaccessible
DEFSTR ROUBRD,PUPROU-1,1,1	;Bit is set if broadcasts allowed
DEFSTR ROUNET,PUPROU-1,9,8	;Net to which packets should be routed
DEFSTR ROUHST,PUPROU-1,17,8	;Host to which packets should be routed
DEFSTR NETADR,PUPROU-1,35,18	;Our address on this net (0 =) not on net)

IFE PUPSW,<
	PRINTX ? Must have PUP code for SUNet routing at Stanford
	PASS2
	END
>
>;IFN STANSW
;Some useful local macros

;ETHHST - extract subnet host number from Internet address

DEFSTR ETHHST,,35,8		;Ethernet subnet host number (all classes)


;SUBNET - extract an Ethernet subnet number from an Internet address.
;Both SRC and DST must be AC's.

DEFINE SUBNET(DST,SRC) <
	TXNN SRC,.NETCA		;Class A network?
	 JRST [ LDB DST,[POINT 8,SRC,19]
		 JRST .+2]	;Yes, get the subnet number
	LDB DST,[POINT 8,SRC,27] ;No, must be class B or C
>

;SETSUB - set an Ethernet subnet field in an Internet address.
;Both SRC and DST must be AC's.

DEFINE SETSUB(SRC,DST) <
	TXNN DST,.NETCA		;Class A network?
	 JRST [ DPB SRC,[POINT 8,DST,19]
		 JRST .+2]	;Yes, get the subnet number
	DPB SRC,[POINT 8,DST,27] ;No, must be class B or C
>
;Iorb parameters

NPIRB==^D20			;Number of iorbs
PUIRBL==IRBLLN*NPIRB		;Length of iorb buffer
MINIRC==NPIRB/4			;Reserve these many iorbs for interrupt level
IFL MINIRC-2,<MINIRC==4>	;Minimum is two iorbs/MEIS. Assume two MEIS's.
IRBTMO==^D5000			;Wait five seconds to declare a MEIS hung

;Ethernet encapsulation - offsets from LCLPKT storage offset.
; Offset zero is a fake free storage header -- see MAXLDR definition.

OFF3MB==<PKTELI-1-LCLPKT>	;Offset of start of 3MB encapsulation
OFFETH==<PKTELI-4-LCLPKT>	;Offset of start of 10MB encapsulation

;Sanity check in case someone changes MAXLDR again

IFL MAXLDR-5 <PRINTX MAXLDR too small for 10MB Ethernet operation>
IFL MAXLDR-2 <PRINTX MAXLDR too small for 3MB Ethernet operation>

DEFSTR E3DST,OFF3MB+0,7,8	;3MB Destination host
DEFSTR E3SRC,OFF3MB+0,15,8	;3MB Source host
DEFSTR E3TYP,OFF3MB+0,31,16	;3MB Type code

DEFSTR ETDH0,OFFETH+0,31,16	;10MB First 16 bits of Destination host
DEFSTR ETDH1,OFFETH+1,31,32	;10MB Last 32 bits of Destination host
DEFSTR ETSH0,OFFETH+2,31,32	;10MB First 32 bits of Source host
DEFSTR ETSH1,OFFETH+3,15,16	;10MB Last 16 bits of Source host
DEFSTR ETTYP,OFFETH+3,31,16	;10MB Type code

;Ethernet packet types

ET%PUP==1000			;PUP
ET%3IP==1001			;IP on 3MB net
ET%IP==4000			;IP on 10MB
ET%ARP==4006			;RFC826 Address Resolution
ET%XRX==1001			;Xerox PUP Address Resolution (cf. ET%3IP)
ET%TRL==10000			;Trailer encapsulated IP

;Miscellaneous parameters

MINDAT==^D46			;Minimum data portion for a 10MB datagram
;Resident Storage definitions

RS PUIRBF,PUIRBL		;IORB storage
RS IORBQ			;Free iorbs - linked through IRBSTS offset
RS IORBC			;Count of free iorbs

RS STAIMS			;No. process level iorb misses
RS STAIPR			;No. IP packets received
RS STAIBR			;No. IP bytes received
RS STAIRE			;No. IP reception errors
RS STAIPX			;No. IP packets transmitted
RS STAIBX			;No. IP bytes transmitted
RS STAIXE			;No. IP transmission errors
RS STAIRF			;No. malformed IP packets received
RS STAIBF			;No. packets dropped because no buffers
RS STAIRS			;No. short IP packets received
RS STAERC			;No. of misc. Ethernet packets processed
RS STAIBC			;No. IP broadcasts received
RS STATRL			;No. of trailer encapsulated IP's received

RSI MEIBUG,<0>			;-1 if MEIS software debugging in effect
RSI IPBRDF,<-1>			;-1 if receiving IP broadcasts, 0 if flushing
RSI SUBNTF,<-1>			;-1 if using subnets, 0 if otherwise

	XSWAPCD			;Start out swappable
SUBTTL IORB Allocation and Construction

;ASGIRB - assign an iorb block
;Call from process level only
;We *may* block if a free iorb can't be found.
;If we unblock because iorbs become free or the timer goes off, we return
; failure if we are still unable to get an iorb.  This prevents jobs from
; hanging should the PHYSIO routines get confused.
;Returns +1, no IORB and either pup fork or net went down
;	 +2, T1/ iorb pointer

	XRESCD			;We go IOPIOFF while NOSKED

ASGIRB::TDZA T4,T4		;Set flag meaning first time through
ASGIR0:	SETO T4,		;Set flag for second time through
	NOSKED			;Prevent races
	IOPIOFF			;Disable the relevant interrupts
	MOVE T1,IORBC		;Get free iorb count
	CAILE T1,MINIRC		;Always leave a few for interrupt level rtns
	 CALL ASGIRI		;Go get an iorb, return pointer in T1
	  JRST ASGIR1		;None found or supply low, must block
	IOPION			;Re-enable interrupts
	OKSKED			;Allow scheduling again
	RETSKP			;Return to caller
	
ASGIR1:	IOPION			;Enable interrupts
	OKSKED			;Allow scheduling
	JUMPN T4,R		;Punt if second time through
	SKIPE IORBQ		;Uninitialized iorb queue?
	IFSKP.
	  CALL IRBINI		;Yes, probably a PUP process running early
	  JRST ASGIRB		;Init queue and try again
	ENDIF.
	AOS STAIMS		;Count a process level iorb miss
	SKIPN JOBNO		;Are we a job zero process?
	 RET			;Yes, possible PUP or Internet bkgnd.  Punt.
	SKIPN NSKED		;NOSKED?
	 SKIPE INSKED		;Or in scheduler?
	  RET			;Yes, can't block.
	MOVE T1,TODCLK		;Get present time
	ADDI T1,IRBTMO		;Make it a little bit in the future
	HRLZ T1,T1		;Set up for scheduler test
	HRRI T1,IORBQT		;Wait for free iorb, timeout, or no Ethernet
	HDISMS(^D200)		;Wait for condition to be true
	SKIPN NETSUP		;Did network service go away?
	 RET			;Yes, leave this test
	JRST ASGIR0		;No, maybe we now have some buffers

	XSWAPCD
;IORBQT - test for free iorbs
;Argument is timeout interval  (right half of TODCLK)
;Callers are: ASGIRB

	RESCD

IORBQT:	SKIPN NETSUP		;Networks still up?
	 JRST 1(T4)		;No, wakeup so connection will be killed
	MOVE T2,IORBC		;Get free iorb count
	CAILE T2,MINIRC		;Sufficient count for process level activity?
	 JRST 1(T4)		;Yes, wakeup
	HRRZ T2,TODCLK		;Get some of now
	SUB T1,T2		;Is timeout still in the future?
	JUMPGE T1,0(T4)		;Yes, keep on sleeping
	BUG.(CHK,IORBQX,ENET,SOFT,<ENET - no free iorbs>)
	SETZM MEITIM		;Force a scan of the MEIS units
	JRST 1(T4)		;Now wakeup

	XSWAPCD
;ASGIRI - get a free iorb at interrupt level
;Call at interrupt level or NOSKED and IOPIOFF
;Returns +1 failure, no free iorbs
;	 +2 success, T1/ pointer to iorb block 
;Clobbers T1-T4
	
	XRESCD

ASGIRI::MOVEI T1,IORBQ		;Queue header
	CALL LREMTQ		;Remove item at head of queue
	IFNSK.
	  SETZM IORBC		;Nothing there, ensure consistent count
	  RET			;Single return to caller
	ENDIF.
	SOSGE IORBC		;Decrement iorb count
	 SETZM IORBC		;Fix it up if we have overly decremented it
	MOVE T1,T2		;Get pointer into place for caller
	SETZM IRBSTS(T1)	;Clear out any status flags
	RETSKP			;Return to caller

	XSWAPCD

;RELIRB - Append an iorb to the free iorb queue
;Call at interrupt level only
;Takes	T1/ pointer to iorb block
;Returns +1 always
;Clobbers T1-T3

	XRESCD

RELIRB::AOS IORBC		;Increment free iorb count
	MOVEI T2,IRBSTS(T1)	;T2/ link word
	MOVEI T1,IORBQ		;T1/ queue header
	CALLRET LAPPTQ		;append item to queue

	XSWAPCD
;IRBINI - initialize queue of free iorbs
;Called from ETHINI before any MEIS is turned on.
;Returns +1 always
;Clobbers T1-T4

IRBINI:	MOVEI T1,IORBQ		;Get address of queue header
	HRLI T1,(T1)		;Form a nil queue pointer
	MOVEM T1,IORBQ		;Make sure queue header is correct
	SETZM IORBC		;No iorbs yet
	MOVE T4,[XWD -NPIRB,PUIRBF] ;Form an aobjn-style pointer
IRBIN0:	MOVEI T1,IORBQ		;T1/ Queue header
	MOVEI T2,IRBSTS(T4)	;T2/ Link word is IRBSTS offset
	CALL LAPPTQ		;Append this packet
	AOS IORBC		;Count another iorb
	ADD T4,[XWD 1,IRBLLN]	;Increment the pointer
	JUMPL T4,IRBIN0		;Loop over all packets
	RET			;Return to caller
;LREMTQ - Remove item from head of queue (assumes local addresses)
;Takes	T1/ Address of queue header
;Returns +1  Queue empty
;	 +2  T2/ Address of item removed
;	     T3/ Address of new head item
;Clobbers T2,T3

	XRESCD

LREMTQ:	HRRZ T2,(T1)		;Get current head
	CAIN T2,(T1)		;Empty?
	 RET			;Yes, fail
	HRRZ T3,(T2)		;No, get successor
	HRRM T3,(T1)		;Queue head _ successor
	HRLM T1,(T3)		;Successor's predecessor _ queue header
	SETZM (T2)		;Clear links in removed item
	RETSKP			;Skip return

	XSWAPCD

;LAPPTQ - Append item to queue
;	T1/ Address of queue header
;	T2/ Address of item to append
;Returns +1:
;	T3/ Address of previous tail item
;Clobbers T3

	XRESCD

LAPPTQ:	PUSH P,T4		;Save callers T4
	HRRZ T4,T1		;In section pointer
	HLRZ T3,(T4)		;Get current tail
	HRLM T2,(T4)		;Queue tail _ new PB
	HRRZ T4,T2		;In section pointer
	HRRZM T1,(T4)		;New PB's successor _ queue header
	HRLM T3,(T4)		;New PB's predecessor _ old tail
	HRRM T2,(T3)		;Old tail's successor _ new PB
	POP P,T4
	RET    

	XSWAPCD
;BLDCCW - Compute and set up CCW words for a datagram
;Takes	T1/ pointer to iorb
;	T2/ starting address of datagram (including Ethernet encapsulation)
;	T3/ number of 36-bit words in datagram
;	T4/ CDB,,UDB entry for MEIS
;Returns +1 always, CCW offsets set in IORB, T1/ pointer to iorb
;Clobbers T1-T4

	XRESCD

BLDCCW::SAVEAC <Q1>		;We use Q1 as the iorb pointer
	STKVAR <BLDOFF,BLDADR,BLD2ND>	;Declare local storage
	MOVE Q1,T1		;Set up iorb pointer
	HLRZM T4,BLDOFF		;Save only the CDB address
	MOVEM T2,BLDADR		;Set up starting address of datagram
	ANDI T2,777		;Want just address bits for start of datagram
	ADD T2,T3		;Calculate address of last word
	SUBI T2,1		; ...
	TRNN T2,777000		;Did we cross a page boundary?
	 JRST BLDCC0		;No, calculate just one CCW
	ANDI T2,777		;Want just address bits again
	ADDI T2,1		;Correct count (word 0 is counts as 1)
	MOVEM T2,BLD2ND		;Number of words spilled into other page
	MOVE T1,T3		;Get total word count
	SUB T1,T2		;Subtract number of words on other page
	HRLI T1,IRMWRD		;T1/ Data mode,,word count
	MOVE T2,BLDADR		;Get address of packet buffer
	MAP T2,(T2)		;T2/ Physical address of datagram start
	TLZ T2,777760		;Mask off mapping information
	MOVE T3,BLDOFF		;T3/ CDB address
	CALL PHYCCW		;Calculate first CCW
	MOVEM T1,IRBCC0(Q1)	;Stash it
	MOVE T1,BLD2ND		;Get number of words on second page
	HRLI T1,IRMWRD		;T1/ data mode,,word count
	MOVE T2,BLDADR		;Start of packet buffer
	XMOVEI T2,1000(T2)	;Increment page count
	ANDCMI T2,777		;Mask off address bits
	MAP T2,(T2)		;T2/ physical address of second page
	TLZ T2,777760		;Mask off mapping information
	MOVE T3,BLDOFF		;T3/ CDB address
	CALL PHYCCW		;Calculate second CCW
	MOVEM T1,IRBCC1(Q1)	;Stash it
	SETZM IRBCC2(Q1)	;End list with a zero
	MOVEI T1,IRBCC0(Q1)	;List header
	HRLI T1,IRBCC2(Q1)	;List tail
	JRST BLDCC1		;Join common exit code
BLDCC0:	MOVE T1,T3		;Set up word count
	HRLI T1,IRMWRD		;T1/ word data mode,,word count
	MOVE T2,BLDADR		;Get address of packet buffer
	MAP T2,(T2)		;Convert virtual address to physical address
	TLZ T2,777760		;T2/ mask out extraneous mapping information
	MOVE T3,BLDOFF		;T3/ address of CDB
	CALL PHYCCW		;Compute CCW data
	MOVEM T1,IRBCC0(Q1)	;Stash it
	SETZM IRBCC1(Q1)	;Only one CCW, terminate the list
	MOVEI T1,IRBCC0(Q1)	;Head of transfer list
	HRLI T1,IRBCC1(Q1)	;Tail of transfer list
BLDCC1:	MOVEM T1,IRBXFL(Q1)	;set up transfer pointers
	MOVE T1,Q1		;Return iorb pointer in T1
	RET			;Return to caller

	XSWAPCD
;BLDIOR - build an iorb for reading an Ethernet datagram 
;Takes	T1/ byte count
;	T2/ buffer pointer
;	T3/ MEIS data mode
;	T4/ done interrupt routine addresses: reception error,,reception okay
;	P1/ NCT address
;Returns +1 failure, insufficient resources
;	 +2 success, T1/ pointer to IORB

	XRESCD

BLDIOR::ASUBR <IORBYT,IORBUF,IORMOD,IORDUN>	;Declare local storage
	CALL ASGIRI		;Get an iorb
	IFNSK.
	  MOVE T1,IORBUF	;Get pointer to buffer
	  JRST RETBUF		;Flush buffer and return
	ENDIF.
	MOVE T4,T1		;Put iorb pointer into T4
	MOVE T2,IORBUF		;Get back buffer pointer
	STOR T2,IRBUF,(T4)	;Remember buffer address
	MOVX T2,IRFRED		;Function is read
	STOR T2,ISFCN,(T4)	; ...
	MOVEI T2,ETHRIN		;Address of done interrupt handling
	MOVEM T2,IRBIVA(T4)	; ...
	MOVE T2,IORDUN		;Addresses of buffer disposal routines
	MOVEM T2,IRBDUN(T4)	; ...
	SETZRO IRTRF,(T4)	;No trailer
	SETONE IRHDR,(T4)	;32-bit headers
	MOVE T1,IORBYT		;Set packet length
	MOVEM T1,IRBCNT(T4)	; ...
	MOVE T2,IORMOD		;Set data data mode
	STOR T2,IRPMD,(T4)	; ...
	MOVE T3,NTCAPC(P1)	;Get header count from NCT 
	STOR T3,IRHLN,(T4)	;Store it in the iorb, use as arg to WRDCNV 
	CALL WRDCNV		;Turn bytes into 36-bit words
	MOVE T3,T1		;T3/ Count of 36-bit words in datagram
	MOVE T1,T4		;T1/ Pointer to iorb
	LOAD T2,IRBUF,(T1)	;T2/ Address of packet buffer
	ADD T2,NTOFF(P1)	;Add offset to start of encapsulation data
	MOVE T4,NTENCU(P1)	;T4/ Retrieve CDB,,UDB from NCT
	CALL BLDCCW		;Set up channel control words
	RETSKP			;Read this packet, T1/ iorb address

	XSWAPCD
;BLDIOW - build an iorb for writing an Ethernet datagram 
;Takes	T1/ byte count
;	T2/ buffer address
;	T3/ MEIS data mode
;	T4/ Address of done handling routine
;	P1/ NCT pointer
;Returns +1 always, packet disposed off

BLDIOW::ASUBR <IOWBYT,IOWBUF,IOWMOD,IOWDUN>	;Declare local storage
	CALL ASGIRB		;No, try for an iorb
	IFNSK.
	  MOVE T1,IOWBUF	;No iorbs, get buffer address
	  JRST RETOUT		;Release buffer as if we sent it
	ENDIF.
	MOVE T4,T1		;Use T4 to address iorb
	MOVE T2,IOWBUF		;Get address of buffer
	STOR T2,IRBUF,(T4)	;Store buffer pointer in iorb
	SETZRO IRTRF,(T4)	;No trailer mode
	MOVX T2,IRFWRT
	STOR T2,ISFCN,(T4)	;Set function to writing
	MOVEI T2,ETHWIN		;Done interrupt routine
	MOVEM T2,IRBIVA(T4)	;Set done interrupt address
	MOVE T1,IOWDUN		;Get address of buffer handling routine
	MOVEM T1,IRBDUN(T4)	;Set it
	SETONE IRHDR,(T4)	;Use 32-bit header mode
	SKIPN NTETHR(P1)	;Skip if 10MB Ethernet
	IFSKP.
	  MOVN T1,IOWBYT	;Get negative of data portion
	  ADDI T1,MINDAT	;Add smallest allowable data portion
	  SKIPLE T1		;Skip if packet is larger than minimum
	   ADDM T1,IOWBYT	;Else pad the byte count for 10MB hardware
	ENDIF.
	MOVE T1,IOWBYT		;Get packet length in octets
	ADDM T1,STAIBX		;Increment total byte count
	AOS STAIPX		;Count another packet queued for output
	MOVEM T1,IRBCNT(T4)	;Stash in iorb	
	MOVE T2,IOWMOD		;Get data mode
	STOR T2,IRPMD,(T4)	;Stash in iorb
	MOVE T3,NTCAPC(P1)	;Get header count from NCT
	STOR T3,IRHLN,(T4)	;Stash in iorb, use as arg for WRDCNV
	CALL WRDCNV		;Compute number of 36-bit words in packet
	MOVE T3,T1		;T3/ number of words in datagram
	MOVE T2,IOWBUF		;Get back buffer pointer
	ADD T2,NTOFF(P1)	;Add offset to start of encapsulation data
	MOVE T1,T4		;T1/ pointer to iorb
	MOVE T4,NTENCU(P1)	;T4/ Get CDB,,UDB for this NI from NCT 
	CALL BLDCCW		;Assemble CCW words for the packet
	MOVE T2,NTENCU(P1)	;Set up CDB,,UDB for call to physio routines
	NOSKED			;Turn off scheduler
	CALL PHYSIO		;Queue up packet for output
	OKSKED			;Resume scheduling
	RET			;Return to caller
;ETHRIN - Ethernet input done routine
;We are invoked by PHYSIO/PHYMEI on a done interrupt for a read
;Buffer release routines must expect buffer pointer in T1
;Takes	T1/ pointer to iorb
;Returns +1 always

	XRESCD

IFE REL6,<ETHRIN: EA.ENT>
IFN REL6,<XRENT ETHRIN>
	PUSH P,T1		;Save pointer to iorb
	MOVE T4,T1		;Copy iorb pointer
	LOAD T1,IRBUF,(T4)	;Get buffer address
	LOAD T2,IRDUN,(T4)	;Get done routine address
	MOVE T3,IRBSTS(T4)	;Get status bits
	TXNN T3,IS.ERR		;Everything okay?
	IFSKP.
	  AOS STAIRE		;No, count number of reception errors
	  LOAD T2,IRBAD,(T4)	;Get address of buffer release routine
	ENDIF.
	CALL 0(T2)		;Take care of buffer
	POP P,T1		;Restore iorb pointer
	CALLRET RELIRB		;Release iorb and return to caller

	XSWAPCD
;ETHWIN - Ethernet output done routine
;We are invoked by PHYSIO/PHYMEI on a done interrupt from the MEIS
;Buffer release routines must expect buffer pointer in T1
;Takes	T1/ pointer to iorb
;Returns +1 always
;Clobbers T1-T4

	XRESCD

IFE REL6,<ETHWIN: EA.ENT>
IFN REL6,<XRENT ETHWIN>
	PUSH P,T1		;Save pointer to iorb
	MOVE T2,IRBSTS(T1)	;Get iorb status flags
	TXNE T2,IS.ERR		;Any error during the transfer?
	 AOS STAIXE		;Yes, just count it
	LOAD T2,IRDUN,(T1)	;Get address of buffer disposal routine
	LOAD T1,IRBUF,(T1)	;Get address of buffer 
	CALL 0(T2)		;Get rid of the buffer
	POP P,T1		;Retore iorb pointer
	CALLRET RELIRB		;Put iorb back on free queue and exit

	XSWAPCD
SUBTTL IP/Multinet Interface

;ETHINI (NTINI) - Network Initialization
;Set up the NCT for each MEIS on the system.
;Takes	P1/ pointer to NCT
;Returns +1 always
;Clobbers T1-T4

ETHINI::SKIPG NTLADR(P1)	;NCT address initialized?
	 RET			;No, leave it alone
	SKIPN IORBQ		;If iorb queue not yet set up
	 CALL IRBINI		;Then initialize pool of free iorbs
	MOVE T1,DBUGSW		;Get setting of debug switch
	CAIN T1,2		;Standalone system?
	 SETOM MEIBUG		;Yes, turn on all bug reporting
	MOVE T3,ENIUTP		;Get aobjn pointer for MEIS table
ETHIN0:	SKIPN ENCUTB(T3)	;Do we have a MEIS here?
	 JRST ETHIN1		;No, try next table entry
	HRRZ T1,ENCUTB(T3)	;T1/ pointer to UDB
	SKIPE MEUNCT(T1)	;Initialized yet wrt Internet?
ETHIN1:	 AOBJN T3,ETHIN0	;Loop over all NI's
	JUMPGE T3,R		;Just quit if no MEIS for this NCT 
	MOVE T1,ENCUTB(T3)	;Get CDB,,UDB of that MEIS
	CALL MEITYP		;What type of MEIS?
	 SKIPA T4,[NT.3MB]	;3MB experimental Ethernet
	MOVX T4,NT.ETH		;10MB standard Ethernet
	LOAD T2,NTTYP,(P1)	;Get network type from NCT
	CAIE T4,(T2)		;NCT matches hardware?
	 JRST ETHIN1		;No, keep on looking.
	MOVE T2,ENCUTB(T3)	;Get UDB,,CDB address pair
	MOVEM T2,NTENCU(P1)	;Remember in NCT
	HRRZS T2		;Isolate UDB pointer
	MOVEM P1,MEUNCT(T2)	;Save pointer to NCT in UDB
	MOVE T3,NTLADR(P1)	;Get our Internet address
	SUBNET T2,T3		;Get our Ethernet subnet number
	TXNE T3,.NETCA		;Test for a Class A address
	 TXNN T3,.NETCB		;Test for a Class B address
	  TRNA			;Class A or B, subnets are possible
	   SETO T2,		;Can't have subnets for Class C addresses
	SKIPN SUBNTF		;Allowing the use of subnets?
	 SETO T2,		;No, say not possible
	MOVEM T2,NTSUBN(P1)	;Stash subnet number or -1 in the NCT
IFN PUPSW,<
	CAILE T2,NPNETS		;Within range?
	IFSKP.
	  HRRZ T1,PUPPAR+2	;Yes, get default PUP subnet number
	  SKIPN T1		;Skip if already set
	   HRRM T2,PUPPAR+2	;Else set the default now
	ENDIF.
>;IFN PUPSW
	LOAD T2,ETHHST,NTLADR(P1) ;Get our subnet (3MB Ethernet) host number
	MOVEM T2,NTHSTN(P1)	;Stash it in the NCT
	CAIE T4,NT.ETH		;Is this 10MB?
	IFSKP.
	  SETOM NTETHR(P1)	;Set to -1 for testing convenience
	  MOVEI T2,^D8		;Need 8 16-bit words for 10MB encapsulation
	  MOVEI T3,OFFETH	;Offset from LCLPKT (start of encaps.)
	ELSE.
	  SETZM NTETHR(P1)	;Set to 0 for testing convenience
	  MOVEI T2,^D2		;Need only 2 such words for the 3MB net
	  MOVEI T3,OFF3MB	;Offset from LCLPKT (start of encaps.)
	ENDIF.
	MOVEM T2,NTCAPC(P1)	;Stash in NCT
	MOVEM T3,NTOFF(P1)	; ...
	LSH T2,1		;Compute number of encapsulation bytes
	MOVEM T2,NTCAPB(P1)	;Remember that number as well
	ADDI T2,4		;Four bytes into IP is minimum for sniffing
	MOVEM T2,NTIPMN(P1)	; ...
	ADDI T2,^D20+^D20+^D8-4	;IP header + TCP header + data is max for sniff
	MOVEM T2,NTIPMX(P1)	; ...
	RET			;All done, return to caller
;ETHON (NTRSRT) - Network restart instruction
;Set address mask and enable reception
;Takes	P1/ pointer to NCT
;Returns +1 always
;Clobbers T1-T4

ETHON::	SKIPN T1,NTENCU(P1)	;Load CDB,,UDB for this interface
	 JRST ETHONX		;We never found the MEIS for this NCT
 	CALL MEISDN		;Is the MEIS already enabled?
	IFNSK.
	  SKIPE NTORDY(P1)	;Yes, is output on?
	   RET			;Yes, to both, just quit
	  JRST ETHON1		;MEIS on, output off. Go restart things.
	ENDIF.
	MOVE T1,NTENCU(P1)	;T1/ CDB,,UDB of MEIS unit
	SKIPN NTETHR(P1)	;10MB Ethernet?
	IFSKP.
	  MOVEI T2,1		;Yes, use FSM 1 as our primary address slot
	  LOAD T3,HRDW0,(P1)	;Get first 32 bits of hardware address
	  LOAD T4,HRDW1,(P1)	;Get last 16 bits of hardware address
	ELSE.
	  MOVE T2,NTHSTN(P1)	;Fetch our 3MB Ethernet host number
	  SETZB T3,T4		;Clear these AC's, just in case
	ENDIF.
	CALL MEIADR		;Set address mask for this host
	MOVE T1,NTENCU(P1)	;T1/ UDB,,CDB
	CALL MEION		;Enable reception
IFN PUPSW,<
	SKIPGE T1,NTSUBN(P1)	;Pick out our Ethernet subnet number
	 JRST ETHON1		;Strange.  PUP, but no subnets.
	MOVE T2,NTHSTN(P1)	;Pick out our PUP (3MB) host number
	TXO T2,1B1		;Broadcasts allowed on local subnet
	MOVEM T2,PUPROU-1(T1)	;Set PUP routing entry for this host
>;IFN PUPSW
ETHON1:	SETOM NTSTCH(P1)	;Set change of state flag
	SETOM NTORDY(P1)	;Ouput is possible
	SETOM NTRDY(P1)		;Net is ready
	RET			;Return to caller

;Here for a non-existent MEIS

ETHONX:	SKIPE NETON(P1)		;Is the net alleged to be on?
	 RET			;No, it is off and that is good
	SETOM NTSTCH(P1)	;Signal change of state
	SETZM NETON(P1)		;Net is off
	SETZM NTORDY(P1)	;Output is not possible
	RET			;Return to caller
;ETHOFF (NTKILL) - Shutdown the interface
;This routine is a no-op if the MEIS is doesn't exist, is already off,
; or if someone has issued a SHUTDOWN command from the console.
;Returns +1 always
;Clobbers T1-T4

ETHOFF::MOVE T1,SCTLW		;Get scheduler control word
	TXNN T1,1B3		;Doing a SHUTDOWN? (We might be continued!)
	 SKIPN T1,NTENCU(P1)	;Load UDB,,CDB for this NI
	   RET			;Shutdown, or was never on.  Do nothing.
	CALL MEISDN		;Is the MEIS already down?
	 SKIPN T1,NTENCU(P1)	;No, load up UDB,,CDB again
	  RET			;Already off, do nothing
	SETOM NTSTCH(P1)	;Set change of state flag
	SETZM NETON(P1)		;State of network is off
	SETZM NTRDY(P1)		;State of interface is off
	SETZM NTORDY(P1)	;Output is not possible, don't queue packets
	CALL MEIOFF		;Disable reception
	RET			;Return to caller
Subttl Ethernet Reception Routines

;ETHRCI - Receive an Ethernet Datagram
;Called at interrupt level from PHYMEI 
;Takes	T1/ length of Ethernet packet in octets
;	T2/ Ethernet packet type
;	P3/ UDB of interrupting MEIS
;Returns +1 flush packet


RS RCILEN,1			;Length seen by hardware
RS RCITYP,1			;Type seen by hardware

;P1/ points to NCT
;P4/ points to buffer

	XRESCD

IFE REL6,<ETHRCI::EA.ENT>	;Run in non-zero section (PHYSIO still in zero)
IFN REL6,<XRENT ETHRCI,G>
	SKIPN INTON		;IP layer must be initialized
	 RET			;Punt if not
	DMOVEM T1,RCILEN	;Save hardware length and type
	SAVEP			;Preserve these AC'S
	MOVE P1,MEUNCT(P3)	;Set up pointer to NCT
	XMOVEI T3,PKORCI	;Default datagram handling routine
IFN PUPSW,<
	CAIN T2,ET%PUP		;PUP?  (Same code on both types of net)
	XMOVEI T3,PUPRCI	;Yes.
>;IFN PUPSW
	CAIN T2,ET%TRL		;IP trailer encapsulated?
	XMOVEI T3,TRLRCI	;Yes.
	SKIPN NTETHR(P1)	;Branch on type of interface
	IFSKP.
	  CAIN T2,ET%IP		;10MB interface. Is it 10MB IP?
	  XMOVEI T3,IPRCI	;Yes.
	  CAIE T2,ET%XRX	;Xerox style ARP?
	  CAIN T2,ET%ARP	;RFC826 style ARP?
	  XMOVEI T3,ARPRCI	;Yes.
	ELSE.
	  CAIN T2,ET%3IP	;3MB interface.  Is it 3MB IP?
	  XMOVEI T3,IPRCI	;Yes.
	ENDIF.
ETHRC0:	CALL (T3)		;Dispatch to appropriate handler
	 RET			;Flush datagram sitting in RAM
	RETSKP			;Start transfer, T1/ iorb pointer

	XSWAPCD
;TRLRCI - Trailer encapsulated IP reception interrupt
;Called at interrupt level, assumes environment set up by ETHRCI
; Some paranoia code in case a UNIX system tries to use IP trailers
; to talk to us.  We absolutely can't cope with this type of IP datagram.
;Returns +1 always, flush the datagram

	XRESCD

TRLRCI:	AOS STATRL		;Count number of these datagrams
	SKIPE MEIBUG		;Skip if not debugging
	 BUG.(INF,IPTRLR,ENET,SOFT,<ENET - Received trailer encapsulated IP>)
	RET			;Return to caller, flush datagram

	XSWAPCD
;IPRCI - IP Reception Interrupt
;Called at interrupt level, assumes environment set up by ETHRCI
;Returns +1 flush packet
;	 +2 receive packet, T1/ pointer to iorb

	XRESCD

IPRCI:	CALL GETBFF		;Get a buffer for the packet
	 RET			;Can't, must flush it
	MOVE P4,T1		;Save pointer to packet
	MOVE T4,RCILEN		;Get number of bytes in packet
	CAML T4,NTIPMN(P1)	;If smaller than IP mininum
	 CAMLE T4,NTIPMX(P1)	;Or greater than the "small" IP cutoff
	  MOVE T4,NTIPMN(P1)	;Then "sniff" the minimum number of bytes
	MOVE T1,NTOFF(P1)	;Get encapsulation offset from NCT
	ADD T1,P4		;Set up start address of packet
	MOVE T2,NTENCU(P1)	;Set up CDB,,UDB of MEIS unit
	CALL MEIRHD		;"Sniff" some bytes from packet into buffer
	CALL CHKBRD		;Check for broadcast packet
	 JRST IPRBRD		;It is.  Go think about it.
IPRCI0:	LOAD T2,PIPL,-LCLPKT(P4) ;Get datagram length
	CAIL T2,MINIHS		;Range check the length
	 CAMLE T2,INTXPB	; ...
	  JRST IPRCX0		;Go flush the packet -- bad length
	SKIPE NTETHR(P1)	;If 10MB interface and the software length...
	 CAIL T2,MINDAT		;...is less than the minimum hardware length
	  TRNA			;Neither, sanity test is valid
	   JRST IPRCI1		;Then skip the usual sanity test
	MOVE T1,RCILEN		;Get back length of packet seen by hardware
	SUB T1,NTCAPB(P1)	;Compute hardware length less encapsulation
	SUBI T1,(T2)		;Compare software and hardware counts
IFE STANSW,<
	CAILE T1,1		;Should differ at most by one byte (3MB only)
>;IFE STANSW
IFN STANSW,<;;; Some machines (eg symbolics 3600) send out "bogus" packets
	CAILE T1,20		;do SOME sanity checking
>;IFN STANSW
	 JRST IPRCX0		;Bad packet, flush it
IPRCI1:	ADDM T2,STAIBR		;Increment count of bytes received
	AOS STAIPR		;Increment count of packets received
	MOVE T1,RCILEN		;Get back total packet size
	CAMLE T1,NTIPMX(P1)	;Small packet? (optimize channel activity)
	IFSKP.
	  AOS STAIRS		;Count another small IP received
	  MOVE T1,P4		;Get back buffer address 
	  JRST APPIP		;Append to IP input queue and flush packet
	ENDIF.
	MOVE T1,T2		;T1/ Must read over Massbus.  Get byte count.
	MOVE T2,P4		;T2/ Buffer address
	MOVX T3,.PM32		;T3/ 32-bit data mode
	MOVE T4,[XWD RETBUF, APPIP] ;T4/ Buffer handling routines
	CALL BLDIOR		;Construct iorb for reading
	 RET			;Some failure, flush packet
	RETSKP			;Success, pass iorb back to physio
;Here when an IP packet with a bogus length is detected
;Should figure out a way of reporting the sender, net, etc.

IPRCX0:	AOS STAIRF		;Count a bad packet
	SKIPE MEIBUG		;MEIS debugging?
IFE STANSW,<
	 BUG.(CHK,BADIP,ENET,HARD,<ENET - Bad IP packet>) ;Yes, complain
>;IFE STANSW
IFN STANSW,<
	 BUG.(CHK,BADIP,ENET,HARD,<ENET - Bad IP packet>,<<T2,LENGTH>>) ;Yes, complain
>;IFN STANSW
	MOVE T1,P4		;Get pointer to buffer
	CALL RETBUF		;Release it
	RET			;Return and flush the packet
	
;IPRBRD - here to handle an IP broadcast.  We have special code here since
; we occasionally want to disable IP broadcast reception when some other
; host has a brain damaged implementation that is flooding us with datagrams.

IPRBRD:	AOS STAIBR		;Count a broadcast received
	SKIPE IPBRDF		;Flushing IP broadcasts?
	 JRST IPRCI0		;No, go read it in using normal code
	MOVE T1,P4		;Yes, get pointer in place
	CALL RETBUF		;Release storage
	CALL PKOWAI		;Is the waif listener interested?
	 RET			;No, flush the datagram
	RETSKP			;Yes, T1/ iorb pointer

	XSWAPCD
;APPIP - append an IP packet to the IP input queue
;Call at interrupt level
;Takes T1/ pointer to buffer
;Clobbers T1-T2

	XRESCD

APPIP:	PIOFF			;Make sure PI system is off (lock out AN20)
	MOVE T2,INTIBI		;Get input queue tail pointer
	JUMPN T2,APPIP0		;Queue not empty, go append packet
	MOVEM T1,INTIBO		;Was empty.  This is only item now.
	SKIPA			;Make head and tail be same packet
APPIP0:	 STOR T1,NBQUE,(T2)	;Make old packet point to newest packet
	MOVEM T1,INTIBI		;Set new tail pointer
	AOS INTFLG		;Cause Internet process to notice it
IFN STANSW,<	;NULJBF is Release 6 and Stanford Release 5.3+
IFE REL6,<	
	SKIPE NULJBF		;Is scheduler running the null job?
	AOS PSKED		;Yes, wake it up
>;IFE REL6
>;IFN STANSW
IFN REL6,<AOS PSKD1		;Minimize scheduler latency>
	PION			;Turn PI system back on
	RET			;Return to caller

	XSWAPCD
;CHKBRD - check if datagram is a broadcast
;Call at interrupt or process level
;Takes	P1/ NCT pointer
;	P4/ buffer pointer
;Returns +1 broadcast packet
;	 +2 not broadcast (may be multicast, though)
;Clobbers T1-T4

	XRESCD

CHKBRD:	SKIPN NTETHR(P1)	;10MB Ethernet?
	IFSKP.
	  LOAD T1,ETDH0,(P4)	;Yes, get first 16-bits of destination
	  TRC T1,177777		;Complement those bits
	  JUMPN T1,RSKP		;If some still set, can't be broadcast
	  LOAD T1,ETDH1,(P4)	;Get last 32-bits of destination
	  TLO T1,740000		;Set low four bits
	  CAME T1,[-1]		;Skip if all bits now set (broadcast)
	   RETSKP		;Not broadcast, skip return
	ELSE.
	  LOAD T1,E3DST,(P4)	;Get 3MB destination
	  JUMPN T1,RSKP		;Zero is broadcast on 3MB net
	ENDIF.
	RET			;And take a single return 

	XSWAPCD
;ARPRCI - process an ARP packet
;Called at interrupt level, assumes environment set up by ETHRCI
;For now we "sniff" the entire packet into the buffer and append the buffer
; to the ARP input queue.  The assumption is that ARP datagrams are very
; small, making it inefficient to fire up the channel for a transfer.
;Takes	P1/ NCT pointer
;Returns +1 flush packet
;	 +2 read packet, T1/ iorb pointer

	XRESCD

ARPRCI:	SUB T1,NTCAPB(P1)	;Discount encapsulation bytes
	CAIL T1,^D12		;Smallest ARP assuming 8-bit hard/prot address
	 CAMLE T1,INTXPB	;Fits within an IP buffer?
	  RET			;Bad packet size, return to caller
	CALL GETBFF		;Get an Internet buffer
	 RET			;No buffers, must flush packet
	MOVE P4,T1		;Stash buffer pointer in P4
	ADD T1,NTOFF(P1)	;Add encapsulation offset into buffer
	MOVE T2,NTENCU(P1)	;Get CDB,,UDB of MEIS unit
	MOVE T4,RCILEN		;Get byte count
	CALL MEIRHD		;Read entire (short) packet into buffer
	MOVE T1,P4		;Get back buffer pointer
	MOVE T2,P1		;NCT pointer is argument as well
	CALL ARPENQ		;Hang buffer on ARP input queue
	RET			;Flush the packet

	XSWAPCD
;GETBFF - claim and check a free Internet buffer
;Call at interrupt level
;The pointer returned by GETBFF points at the PKTELI offset, the beginning of
; the data.  LCLPKT is the preceding offset that marks the beginning of the
; four words of encapsulation data.
;Returns +1 no free buffers, Internet fork prodded
;	 +2 good buffer claimed, T1/ pointer to buffer
;Clobbers T1-T4

	XRESCD

GETBFF::PIOFF			;Lock out rest of machine
	SOSGE INTNFI		;Any buffers?
	 AOSA INTNFI		;No, fix count
	  TRNA			;Yes, skip into main code
	   JRST GETBFX		;Go poke Internet fork
	MOVE T1,INTFRI		;Get first buffer on queue
	TRNN T1,-1		;Null queue is a bug
	 BUG.(HLT,ENTBUF,ENET,SOFT,<ENET - free buffer queue fouled>)
	LOAD T2,NBQUE,(T1)	;Get pointer to next buffer
	SKIPE T2		;If non-zero
	 SETSEC T2,INTSEC	;Then make an extended address
	MOVEM T2,INTFRI		;Update head of free queue
	PION			;Let rest of machine run again
	SETZRO NBQUE,(T1)	;Clear pointer to next buffer
	CAML T1,[XWD INTSEC,INTFRE]	;Range check the pointer 
	 CAMLE T1,[XWD INTSEC,INTFRE+INTFSZ-1]	; ...
	  BUG.(HLT,ENTBF1,ENET,SOFT,<ENET - bad Internet buffer>)
	RETSKP			;Good return to caller

GETBFX:	PION			;Turn PI system back on
	AOS INTFLG		;Signal work to do
	AOS STAIBF		;Increment count of buffer misses
	SKIPE MEIBUG		;Debugging?
	 BUG.(INF,ETHBFX,ENET,SOFT,<ENET - no free IP buffers>)	;Yes, complain
	RET			;Return to caller

	XSWAPCD
;RETBUF - release an Internet buffer to the Internet free pool
;Call at interrupt level
;Takes	T1/ buffer pointer
;Returns +1 always
;Clobbers T1-T2

	XRESCD

RETBUF::CAML T1,[XWD INTSEC,INTFRE]	;Range check the pointer
	CAMLE T1,[XWD INTSEC,INTFRE+INTFSZ-1]	; ...
	IFNSK.
	  BUG.(CHK,RETBFZ,ENET,SOFT,<ENET - bad buffer pointer at RETBUF>)
	  RET			;Don't foul free queue if bad pointer
	ENDIF.
	OPSTR <SKIPN>,PFSIZ,-LCLPKT(T1)	;Full size packet?
	 BUG.(CHK,RETBFX,ENET,SOFT,<ENET - Bad packet size>)
	MOVE T2,T1		;Copy for indexing below
	PIOFF			;Maintain queue integrity (lock out AN20)
	EXCH T1,INTFRI		;Release to Internet
	STOR T1,NBQUE,(T2)	; ...
	AOS INTNFI		;Count another free buffer
	PION			;Let rest of machine run
	RET			;Return to caller

	XSWAPCD
Subttl IP Output Routines

;ETHOOK (NTOTOK) - net output okay
;Called T1/ (first hop) destination address
;	T2/ pointing to an Internet buffer
;	P1/ address of NCT
;Returns +1 no can do, T1/ ICMP error code
;	 +2 success, Ethernet encapsulation done
;Clobbers T1,T3,T4, does NOT clobber T2
;Note that we do the Ethernet encapsulation at this point rather than in
; the ETHHDR (NTLLDR) routine.  We have all the information here and it
; makes little sense to substantially repeat this work later.

ETHOOK::SAVEAC<T2>		;Preserve this AC
	STKVAR<DEST>		;Declare local storage
	MOVEM T1,DEST		;Store destination address
	MOVX T3,ET%IP		;Get type code for 10MB IP
	SKIPN NTETHR(P1)	;Skip if 10MB Ethernet
	MOVX T3,ET%3IP		;Get type code for 3MB IP
	CALL ENCAPS		;Encapsulate the datagram
	 JRST ETHOKX		;Can't send packet, generate ICMP error
	RETSKP			;Good return to caller

;Here when we can't do anything

ETHOKX: SKIPE JOBNO		;Never complain if job zero (ping code) 
	 SKIPN MEIBUG		;Do complain if MEIS debugging
	  TRNA			;No complaints if not debugging or job zero
	   BUG.(INF,ETHRTX,ENET,SOFT,<ENET - IP routing failure>) ;complain
	JUMPE T1,ETHOK1		;Jump if temporary failure because of ARP
	MOVE T1,DEST		;Get back destination
	CALL HSTDWN		;Signal that host is inaccessible
ETHOK1:	MOVX T1,<ICM%SQ>	;ICMP error is "source quench"
	RET			;Take failure return

	ENDSV.

;HSTDWN - signal upper levels that a host is not accessible
;Should define another status bit meaning "Gateway is down, host inaccessible"
;Takes	T1/ internet address
;Returns +1 always

HSTDWN:	JUMPE T1,R		;Quit now if no specific host
	CALL HSTHSH		;Find hash index for host
	 RET			;No index, just return
	MOVX T1,HS%VAL		;Get valid flag
	IORM T1,HSTSTS(T2)	;Set it
	MOVX T1,HS%UP		;Get host up flag
	ANDCAM T1,HSTSTS(T2)	;Be sure to clear it
	RET			;Return to caller
;ETHOUT (NTOSRT) - Start output on the MEIS
;Examine queued Internet packets and send one if possible
;Takes	P1/ pointer to NCT
;Returns +1 always

ETHOUT::SKIPE NTIOBO(P1)	;Anything at head of Internet output queue?
	 CALL GETOUT		;Yes, pull it off the queue
	   RET			;Nothing there or lost timing race
	MOVE T2,T1		;T2/ Get buffer pointer into place	
	LOAD T1,PIPL,-LCLPKT(T2) ;T1/ Get packet length in octets
	MOVX T3,.PM32		;T3/ 32-bit data mode
	MOVEI T4,RETOUT		;T4/ Buffer disposal routine
	CALL BLDIOW		;Construct and queue up an Ethernet write
	 NOP			;Ignore any errors
	RET			;Single return always
;GETOUT - pull a packet off the Internet output queue
;Takes	P1/ pointer to NCT
;Returns +1 no packets to be sent
;	 +2 success, T1/ address of packet to send
;Clobbers T1-T2

	XRESCD

GETOUT:	PIOFF			;Interlock with PI system
	SKIPN T1,NTIOBO(P1)	;Load pointer to head of Internet queue
	 JRST GETOU1		;Nothing to do, quit
	LOAD T2,NBQUE,(T1)	;Get successor packet
	SKIPE T2		;Don't extend if nil
	 SETSEC T2,INTSEC	;Make extended address
	MOVEM T2,NTIOBO(P1)	;Update queue header
	SKIPN T2		;Now a null queue?
	 SETZM NTIOBI(P1)	;Yes, clear tail pointer as well
	SETZRO NBQUE,(T1)	;Clear links in dequeued packet
	AOS (P)			;Prepare skip return
GETOU1:	PION			;Reallow PI now that queues are safe
	RET			;Good return to caller

	XSWAPCD
;RETOUT - Release an IP output buffer
;Called from interrupt level
;This routine makes no assumptions about the size of the buffer and thus
; hooks it to the general free list that the background fork looks at.
;Takes	T1/ buffer pointer
;Returns +1 always
;Clobbers T1-T2

	XRESCD

RETOUT:	CAML T1,[XWD INTSEC,INTFRE]	;Range check the pointer 
	CAMLE T1,[XWD INTSEC,INTFRE+INTFSZ-1]	; ...
	IFNSK.
	  BUG.(CHK,RETOTX,ENET,SOFT,<ENET - bad buffer pointer at RETOUT>)
	  RET			;Don't foul free queue if bad pointer
	ENDIF.
	MOVE T2,T1		;Copy buffer pointer for indexing
	PIOFF			;Make sure no one can manipulate free list
	EXCH T1,INTNFB		;Put on free list
	STOR T1,NBQUE,(T2)	;Hang old list off of this new head
	PION			;Reallow PI activity
	AOS INTFLG		;Get Internet gateway to notice it
	RET			;Return to caller

	XSWAPCD
SUBTTL Subnet Routing and Ethernet Encapsulation

;ENCAPS - perform IP and PUP encapsulation
;Takes	T1/ address - if IP, right justified 32-bits of first hop address
;		      if PUP, 1B0+subnet,,host of ultimate destination
;	T2/ buffer pointer with zero point at start of 10MB encapsulation field
;	T3/ protocol type code
;	P1/ NCT address
;Returns +1 failure, T1/ -1 if no path, 0 if ARP needed
;	 +2 success, network dependent datagram encapsulation performed
;Clobbers T1-T4

;Q1 - scratch AC for SUBROU
;Q2 - buffer pointer
;Q3 - type code

ENCAPS::SAVEQ			;We use these AC's
	DMOVEM T2,Q2		;Load up buffer pointer, datagram type
	XMOVEI T4,ENCETH	;Assume we want standard 10MB encapsulation
	SKIPN NTETHR(P1)	;Standard 10MB Ethernet?
	XMOVEI T4,ENC3MB	;No, we want 3MB encapsulation
	CALL 0(T4)		;Do the output okay, encapsulation checks
	 RET			;Failure
	RETSKP			;Success return to caller
;ENC3MB - perform 3MB datagram encapsulation
;Set source and destination host addresses
;Takes	T1/ PUP or IP address
;	Q2/ buffer pointer
;	Q3/ protocol type
;	P1/ NCT address
;Returns +1 routing failure, T1/ -1 if hard error, 0 if temporary (ARP)
;	 +2 success, encapsulation data set up
;Clobbers T1,T3-T4, Preserves T2

ENC3MB:	CAIE Q3,ET%3IP		;Is the protocol 3MB IP?
	 JRST ENC3M0		;No, go straight to subnet routing
	LOAD T3,ETHHST,T1	;Get IP destination
	CAIE T3,377		;Host 377 is IP broadcast address
	 JRST ENC3M0		;Standard IP, do subnet routing
	MOVE T3,NTHSTN(P1)	;Get source host number from NCT
	SETZ T4,		;Destination address is broadcast
	JRST ENC3M1		;Set up broadcast addresses

ENC3M0: CALL SUBROU		;Do subnet routing
	 JRST RETO		;Not possible, return hard failure
ENC3M1:	STOR T3,E3SRC,(Q2)	;Set source host number
	STOR T4,E3DST,(Q2)	;Set destination host number
	STOR Q3,E3TYP,(Q2)	;Set packet type
	RETSKP			;Skip return to caller
;ENCETH - perform 10MB datagram encapsulation
;Set source and destination host addresses
;Takes	T1/ PUP or IP address
;	Q2/ buffer pointer
;	Q3/ protocol type
;	P1/ NCT address
;Returns +1 routing failure, T1/ -1 if hard error, 0 if temporary (ARP)
;	 +2 success, encapsulation data set up
;Clobbers T1,T3-T4, Preserves T2

ENCETH:	CAIE Q3,ET%PUP		;PUP packet?
	IFSKP.
	  TRNN T1,-1		;Host 0 is PUP broadcast address
	   JRST ENCET1		;Doing a broadcast
	ELSE.
	  LOAD T2,ETHHST,T1	;Get IP destination
	  CAIN T2,377		;Host 377 is IP broadcast address
	   JRST ENCET1		;Doing a broadcast
	ENDIF.
	SKIPGE NTSUBN(P1)	;Do we have subnets?
	 JRST ENCET2		;No, skip the subnet routing stuff
	CALL SUBROU		;Do subnet routing
	 JRST RETO		;Not possible.  Return hard failure.
	MOVE T3,NTSUBN(P1)	;Get our subnet number ready
	CAIE Q3,ET%PUP		;PUP packet?
	IFSKP.
	  HRRZ T1,T4		;Resolve just the first hop host number
	  HRLI T1,(T3)		;Form subnet,,host
	ELSE.
	  STOR T4,ETHHST,T1	;Set first hop host number
	  SETSUB T3,T1		;Set first hop subnet number
	ENDIF.
ENCET2:	MOVE T2,Q3		;T2/ protocol type
	CALL RESOLV		;Turn protocol address into hardware address
	 JRST RETZ		;Can't do it, return soft error
	STOR T1,ETDH0,(Q2)	;First 16 bits of destination host
	STOR T2,ETDH1,(Q2)	;Last 32 bits of destination
ENCET0:	LOAD T1,HRDW0,(P1)	;Get first 32 bits of our hardware address
	STOR T1,ETSH0,(Q2)	;Set in datagram
	LOAD T1,HRDW1,(P1)	;Get last 16 bits of our hardware address
	STOR T1,ETSH1,(Q2)	;Set in datagram
	STOR Q3,ETTYP,(Q2)	;Set type field 
	RETSKP			;Skip return

;Here to set hardware broadcast destination address

ENCET1:	SETONE ETDH0,(Q2)	;-1 is broadcast address
	SETONE ETDH1,(Q2)	; ...
	JRST ENCET0		;Join rest of encapsulation code
;SUBROU - do subnet routing
;Takes	T1/ network address:  PUP: 1B0+subnet,,host
;			       IP: 32 bits right justified
;	Q2/ buffer pointer
;Returns +1 destination inaccessible
;	 +2 success, T3/ source host (8 bits)
;		     T4/ destination host (8 bits)
;Clobbers Q1, T2-T4

;At Stanford we must consult the PUP routing table to figure out if we need
; to use a local gateway to get to the destination host.  Other sites that
; want to use subnet routing need to implement some other scheme.

IFE STANSW,<
SUBROU:	BUG.(INF,SUBRTX,ENET,SOFT,<ENET - Subnet routing scheme not defined>)
	RET			;Complain and return
>;IFE STANSW
IFN STANSW,<
SUBROU:	IFL. T1			;If trying to route PUP
	  LDB Q1,[POINT 8,T1,17] ;Get subnet from left half
	ELSE.
	  SUBNET Q1,T1		;Otherwise get destination subnet from IP addr.
	ENDIF.
	CAILE Q1,NPNETS		;Within range of routing table?
	 RET			;No, call it inaccessible
	SKIPE Q1		;Allow subnet zero to mean connected subnet
	CAMN Q1,NTSUBN(P1)	;On our subnet?
	IFNSK.
	  MOVE T3,NTHSTN(P1)	;Yes, get our subnet host number
	  CALL USEDST		;Default host according to PUP or IP argument
	  RETSKP		;Take good return
	ENDIF.
	JN ROUACC,(Q1),R	;Punt if subnet is inaccessible
	LOAD T4,ROUHST,(Q1)	;Get host we route to on dest subnet
	SKIPN T2,T4		;Zero means route directly
	 CALL USEDST		;So use destination we were given
	SKIPE T2		;Were we routing?
	 LOAD Q1,ROUNET,(Q1)	;Yes, get subnet we are routing to
	LOAD T3,NETADR,(Q1)	;Get our address on that subnet
	JUMPE T3,R		;We're not there.  Punt.
	RETSKP			;Return with T3, T4 set up.
	  
;Helper routine.  Get 8 bits of destination host from network address.

USEDST:	SKIPL T4,T1		;Skip if PUP, load destination
	 LOAD T4,ETHHST,T1	;Load destination from IP address
	HRRZS T4		;Clear flag bits in case PUP
	RET			;Return to caller
>;IFN STANSW
IFN STANSW,<
;FNDINT - find best interface after net has been selected.
;This routine addresses the problem of having two interfaces onto one network.
; Normally the Multinet code only sees one interface, not necessarily the
; optimal one. We are called by GWYLUK to ensure the best interface is chosen.
; This code assumes Stanford-style subnet routing and handles multiple MEIS
; devices only.
;Takes	P1/ GWYLUK's best guess at an interface
;	T1/ first hop address (either ultimate host or a local gateway)
;Returns +1 always
;	P1 changed if a better interface exists
;	T1,T3 preserved

FNDINT::TDZA T2,T2		;Find best interface that is up
FNDINS::SETO T2,		;Find best interface, don't care if it's down
	LOAD T4,NTDEV,(P1)	;Get type of hardware on this net
	CAIN T4,NT.MEI		;If MEIS, then we do something special
	 SKIPGE NTSUBN(P1)	;Possible to have subnets?
	  RET			;No MEIS or no subnets, just return now
	STKVAR <STRICTF>	;Declare local storage
	MOVEM T2,STRICTF	;Set up flag word
	SUBNET T4,T1		;Get destination subnet
	CAIGE T4,NPNETS		;Check if it's within range
	 SKIPG PUPROU-1(T4)	;Is that subnet at all accessible?
	  RET			;No, quietly return having done nothing
	XMOVEI T2,NCTVT		;Point to the NCT vector table
FNDIN0:	LOAD T2,NTLNK,(T2)	;Get next NCT in the chain
	JUMPE T2,FNDIN2		;Not directly connected to that subnet
	CAME T4,NTSUBN(T2)	;Are we on that subnet?
	 JRST FNDIN0		;No, try again
	SKIPN NTORDY(T2)	;Is the interface up?
	 SKIPE STRICTF		;Yes, do we care if interface is down?
	  TRNA			;Up or we don't care if it's down.  Use it
	   JRST FNDIN0		;No, go look for another
FNDIN1:	MOVE P1,T2		;Found a directly connected interface
	RET			;Return to caller

;No direct connections to that subnet.  Pick best interface for routing.

FNDIN2:	LOAD T4,ROUNET,(T4)	;Get subnet number of host I should route thru.
	XMOVEI T2,NCTVT		;Point to the NCT vector table
FNDIN3:	LOAD T2,NTLNK,(T2)	;Get next NCT in the chain
	JUMPE T2,R		;Not directly connected to that subnet
	CAME T4,NTSUBN(T2)	;Are we on that subnet?
	 JRST FNDIN3		;No, try again
	SKIPN NTORDY(T2)	;Is interface up?
	 SKIPE STRICTF		;No, do we care?
	  JRST FNDIN1		;Up or we don't care if it's down.  Use it.
	   JRST FNDIN3		;Interface is down, try for a better one
	ENDSV.
>;IFN STANSW
	TNXEND
	END