Google
 

Trailing-Edge - PDP-10 Archives - SRI_NIC_PERM_SRC_1_19910112 - 6-1-monitor/pup.mac
There are 2 other files named pup.mac in the archive. Click here to see a list.
;[SU-SCORE.ARPA]PS:<6-1-MONITOR>PUP.MAC.747,  6-Dec-85 23:27:19, Edit by BILLW
; Fix call to SETPOF referenceing wrong section.
;<6-1-MONITOR>PUP.MAC.746, 30-Jul-85 15:30:54, Edit by WHP4
; Make sure T1/ scheduler test doesn't get clobbered by call to
; ULKTTY at PNVCL3
;<6-1-MONITOR>PUP.MAC.745, 21-May-85 00:46:07, Edit by LOUGHEED
; Tweek ASGPBI to assign short packets on 10MB interfaces.
;<6-1-MONITOR>PUP.MAC.744, 17-May-85 00:53:59, Edit by LOUGHEED
; Make sure dying PNV goes ECSKED after BSP port is dead
;<6-1-MONITOR>PUP.MAC.743, 12-May-85 14:09:42, Edit by LOUGHEED
; Change use of G1BPT in some cases.  You must have a recent version
;  of MACSYM with the G1BPT definition fixed to assemble correctly.
;<6-1-MONITOR>PUP.MAC.742, 12-May-85 13:33:50, Edit by LOUGHEED
;<6-1-MONITOR>PUP.MAC.741,  8-May-85 14:16:33, Edit by LOUGHEED
; ASPNVT releases NVTLCK if it needs to wait for DEVL0K
;<6-1-MONITOR>PUP.MAC.740,  5-May-85 13:03:40, Edit by LOUGHEED
; Resurrect PNVCOB's ability to release output buffers
;<6-1-MONITOR>PUP.MAC.739, 30-Apr-85 00:58:35, Edit by LOUGHEED
; Fix inconsistent settings of BLKF and ERRF after PUP MTOPR%
;<6-1-MONITOR>PUP.MAC.738, 29-Apr-85 00:28:13, Edit by LOUGHEED
; PUPOUT returns ETHRX2 error if ARP fails
;<6-1-MONITOR>PUP.MAC.737, 28-Apr-85 16:03:40, Edit by LOUGHEED
; Change PSKED to PSKD1 to minimize scheduler thrashing
;<6-1-MONITOR>PUP.MAC.736, 27-Apr-85 14:06:53, Edit by LOUGHEED
; PNVBUF honors hangup flag (HUREQF) by not allocating output buffers
;<6-1-MONITOR>PUP.MAC.735,  2-Apr-85 12:27:12, Edit by LOUGHEED
; Fix page faulting lossage in PUPOP6
;<6-1-MONITOR>PUP.MAC.734,  1-Apr-85 17:40:48, Edit by LOUGHEED
; Fix bug in PUPMTP that would cause erroring MTOPR%'s to loop
;<6-1-MONITOR>PUP.MAC.733, 30-Mar-85 16:25:45, Edit by LOUGHEED
;<6-1-MONITOR>PUP.MAC.732, 30-Mar-85 15:44:34, Edit by LOUGHEED
; Execute GTOKM code in section 0/1
;<6-1-MONITOR>PUP.MAC.731, 27-Mar-85 00:56:20, Edit by LOUGHEED
;<6-1-MONITOR>PUP.MAC.730, 27-Mar-85 00:36:58, Edit by LOUGHEED
; Remove unecessary call to FUNLKI in CLZWAT
; Rework MTOPR% support to handle BLKF correctly
;<6-1-MONITOR>PUP.MAC.729, 25-Mar-85 22:51:45, Edit by LOUGHEED
; PNVCOB is now a stub that does nothing
; PU7NVT tries to flush lines with temporary dynamic data blocks
;<6-1-MONITOR>PUP.MAC.728, 24-Mar-85 01:28:00, Edit by LOUGHEED
; Avoid BLKF2 bugchks by clearing BLKF appropriately
;<6-1-MONITOR>PUP.MAC.727, 24-Mar-85 00:35:49, Edit by LOUGHEED
; Much tedious hacking to realize edit 705
;<6-1-MONITOR>PUP.MAC.705, 16-Mar-85 20:16:42, Edit by LOUGHEED
; Move into extended code section
; Rework funky blocking done in JFN code: use BLKF instead of restarting.
; Clean up some of the JSYSA/F routines (DOBE%, DIBE%, SOBE%, SIBE%, SOBF%)
; Flush previous edit history
	SEARCH PROLOG,PHYPAR,ANAUNV,PUPSYM
	TTITLE PUP
	SALL

	SUBTTL PUP I/O driver for PUP  /  E. A. Taft	March 1975

COMMENT \

     The original version of PUP was written by Ed Taft of Xerox
PARC for the Tenex operating system.  Frank Gilmurray, Andy
Sweer, and the staff of the SUMEX-AIM project at Stanford
University modified PUP to run under TOPS-20 on a DECSYSTEM-2020.
Aaron Wohl of Carnegie-Mellon University modified PUP to run on
an extended DECSYSTEM-2060 and started the conversion to TOPS-20
coding style.  Kirk Lougheed and Len Bosack of Stanford
University are responsible for the present incarnation of PUP, a
version that utilizes the Massbus Ethernet Interface Subsystem
(MEIS) designed by George Schnurle, also of Stanford University.
Mark Crispin of Stanford aided in debugging the initial production
version of the MEIS based PUP Ethernet and implemented duplex Pup
JFN's.

					-- July 1982

"It's a dog!"				-- MRC July '82
\
SUBTTL Assembly Switches

IFNDEF REL6,<REL6==1>		;Assume Release 6.1 monitor

IFN REL6,<SEARCH TTYDEF>

;XSWAPCD and XRESCD map to SWAPCD and RESCD under Release 5 

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

;Can't use IFIW in dispatch tables in Release 6.1

IFE REL6,<DEFINE DSP(ARG) <IFIW!ARG>>
IFN REL6,<DEFINE DSP(ARG) <XWD XCDSEC,ARG>>

;CALLM - call a routine in MSEC1

IFN REL6,<DEFINE CALLM (RTN) <
	EA.ENT
	CALL @[XWD MSEC1, RTN]
>>
IFE REL6,<DEFINE CALLM (RTN) <CALL (RTN)>>

;XNENT and XRENT macros for Release 5 

IFE REL6,<DEFINE XNENT(ARG,G) <
	IFB <G>,<ARG:>
	IFNB <G>,<ARG::>
>>

IFE REL6,<DEFINE XRENT(ARG,G) <
	IFB <G>,<ARG:>
	IFNB <G>,<ARG::>
>>

;Set up debugging flags

IFNDEF DEBUGF,<DEBUGF==0>	;State of debugging code
IFN DEBUGF,<IF1,<PRINTX %%Assembling debugging code>>
SUBTTL PUP Parameter Definitions

;Accumulator definitions

DEFAC(UNIT,Q1)			;Pup port number
DEFAC(IOS,Q2)			;I/O status bits
DEFAC(E,Q3)			;Scratch accumulator
DEFAC(STS,P1)			;Status bits
DEFAC(JFN,P2)			;Pointer to JFN block
DEFAC(BSP,P3)			;Index into BSP data block
DEFAC(PB,P4)			;Pointer to current packet buffer
DEFAC(F1,P5)			;GTJFN% flags

P1=10				;Used to refer to the NCT

;STRPTR - Given a structure defined with DEFSTR return a byte pointer
; STR - Structure name
; Y   - (optional) additional specification of data location

DEFINE STRPTR(STR,Y)<..STR0 (MAKPTR,0,STR,Y)>
DEFINE MAKPTR(AC,LOC,MSK)<<POINTR (LOC,MSK)>>
;Parameters and byte pointers defining the structure of a Packet Buffer (PB)

PBFLAG==0
  PBSMF==1B0			;A short packet buffer
  PBBGF==1B1			;A large packet buffer
  DEFSTR PUCHK,PBFLAG(PB),35,18	;Pup checksum

PBTIME==1			;Time stamp (TODCLK format) used for BSP output

PBBSBC==PBTIME			;Byte count for data (used by BSP input)

PBLINK==2			;Packet buffer link doubleword
				;For output handling, the first word is 0 if
				; not owned by BSP or is -1 if owned by BSP,
				; but not on any queue.  Otherwise the link
				; doubleword contains queue pointers.

;Keep the next two fields together and in order.  RCVACK and SNDACK use them
; both to store a doubleword global byte pointer.  Some of the checksumming
; routines use these locations as well for storing various pointers.

PBIPTR==4			;Packet buffer input OWG byte pointer
PBOPTR==5			;Packet buffer output OWG byte pointer


PBBSID==6			;Byte ID, right-justified (used by BSP input)
PBXMTC==PBBSID			;Retransmission count (used by BSP output)

PBDFLT==7			;Used on packet reception
  DEFSTR PUPRT,PBDFLT,7,8	;Local port index
  DEFSTR PUDHS,PBDFLT,15,8	;Destination host
  DEFSTR PUSRC,PBDFLT,23,8	;Source net
  DEFSTR PUDNT,PBDFLT,31,8	;Destination net
  DEFSTR PUDFT,PBDFLT,35,1	;PUP header fields have been defaulted

PBPHYS==7			;Start of datagram encapsulation (this offset
				; matches the pseudo free-space header in
				; the Multinet code)

PBHEAD==:PBPHYS+MAXLDR		;Start of Pup Header

PBCONT==PBHEAD+5		;Start of Pup Contents

MNPBLX==PBHEAD+MNPBLN-1		;Minimum size of PB, in words
MXPBLX==PBHEAD+MXPBLN		;Maximum size of PB, in words
; Definitions for Byte Stream Protocol (BSP) data block

;	0	PBFLAG		packet flag word (always zero)
;	1	no name		unused
;	2 	PBLINK		first link word (successor)
;	3	PBLINK+1	second link word (predecessor)
;	4	BSPSZ0		start of data

;Total length of a BSP data block is BSPSZ0+BSPSIZ

BSPSZ0==PBLINK+2		;Same definitions as PB up to this point
BSPSIZ==0			;Start at zero

DEFINE BSWRD(LABEL,SIZ) <
	IFB <SIZ>,<..SIZ==1>
	IFNB <SIZ>,<..SIZ==SIZ>
	LABEL==BSPSIZ
	BSPSIZ==BSPSIZ+..SIZ
>

BSWRD(BSPHDR)			;Header word
 DEFSTR PBMRK,BSPHDR(BSP),7,8	;Value of most recent Mark
 DEFSTR PBTMO,BSPHDR(BSP),15,8	;Error timeout interval / 2^12 ms
 ;B18-35			;Size of BSP data block (words)

BSWRD(BSPTIM)			;BSP timing parameters
   DEFSTR BSPRTM,BSPTIM(BSP),17,18 ;Estimated round-trip delay (ms)
   DEFSTR BSPACK,BSPTIM(BSP),35,18 ;25% of window size offered by last ACK

;Timer words (TODCLK format) - for flow control and retransmission
BSWRD(BSPACT)		;Time of most recent activity
BSWRD(BSPDTM)		;Time at which to do BSP retransmissions
BSWRD(BSPATM)		;Time last AData was sent
BSWRD(BSPITM)		;Time at which to retransmit Interrupt
BSWRD(BSPFTM)		;Time at which to check FSM
BSWRD(BSPSTM)		;Time of most recent FSM state change
BSWRD(BSPLST)		;Time we last were in DOBSP

;Packet buffer pointers and queues
BSWRD(BSPCIP)		;Current input buffer (zero if none)
BSWRD(BSPCOP)		;Current output pointer (zero if none)
BSWRD(BSPSIP)		;BSP Send Interrupt pointer
BSWRD(BSPABP)		;BSP Abort pointer
BSWRD(BSPIBQ,2)		;BSP Input queue (linked thru PBLINK)
BSWRD(BSPOBQ,2)		;BSP Output queue (linked thru PBLINK)

;Sequence numbers, allocations, counters, etc.
BSWRD(BSPCID)		;BSP Connection ID (-1 =) no ID yet - listening)
BSWRD(BSPRII)		;BSP Receive Interrupt ID
BSWRD(BSPSII)		;BSP Send Interrupt ID (c.f. BSPSIP)
BSWRD(BSPILW)		;BSP Input left window edge
			;ID of next byte to be removed by
			; inputting process
BSWRD(BSPIQL)		;BSP Input queue length
 ;B0-17			;# Pups in BSP input queue
 ;B18-35		;# bytes from BSPILW to first hole
BSWRD(BSPIAL)		;BSP Input allocation
 DEFSTR PBSIAP,BSPIAL(BSP),7,8	   ;Total input Pups allowed	
 DEFSTR PBSIBP,BSPIAL(BSP),17,10   ;Maximum data bytes/Pup
 ;B18-35		;Maximum # bytes allowed
;BSP definitions (cont'd)

BSWRD(BSPOLW)		;BSP Output left window edge (ID of last received ACK)
BSWRD(BSPOQL)		;BSP Output queue length
 ;B0-17			;# Pups in BSP output queue
 ;B18-35		;# bytes spanned by BSP output queue
BSWRD(BSPOAL)		;BSP Output allocation
 DEFSTR PBSOAP,BSPOAL(BSP),7,8	;Additional Pups allowed
 DEFSTR PBSOBP,BSPOAL(BSP),17,10 ;Maximum data bytes/Pup
 ;B18-35		;Additional bytes allowed

;Statistics
BSWRD(BSPOPG)		;BSP Output packets generated
BSWRD(BSPOPR)		;BSP Output packets retransmitted
BSWRD(BSPPPG)		;BSP Probe packets generated
BSWRD(BSPTCK)		;No. of packets processed by GETBSP while port locked
BSWRD(BSPAKG)		;No. of ACK's generated
;BSP-related parameters

MAXQDI==5			;Max Pups we allow on interrupt level queue
MXBSIP==5			;Max Pups we allow on BSP input queue
MXBSOP==5			;Max Pups we allow on BSP output queue

MXBSIB==<MXPLEN-MNPLEN>*MXBSIP	;Max data bytes on BSP input queue
MXBSOB==<MXPLEN-MNPLEN>*MXBSOP	;Max data bytes on BSP output queue

MXPTXT==^D75		;Max # bytes of text in Interrupt, Abort

MAXTCK==^D20		;Max number of packets GETBSP will process without
			; blocking.  Keeps one busy port from starving the
			; the other ports.

MAXTSK==^D1000		;Max number of tasks for the pup process to run
			; without blocking.  Reaching this number will
			; cause a bugchk and force a dismiss.

IBWDLY==^D500		;Input background wakeup delay (ms)
			;When an input packet arrives, wakeup of the
			;background process is delayed this long in
			;hopes that the user fork will process the input
RETINT==^D250		;Nominal retransmission interval (ms)
MINRET==^D10		;Minimum retransmission interval
MAXRET==^D2500		;Maximum retransmission interval
PNVRET==^D100		;Retransmission interval for PNV (Ethertip) connections
HLDINT==^D1000		;Hold interval (expiration forces AData)
PRBINT==^D15000		;Probe interval for idle connection (ms)
DETINT==^D120		;Default error timeout interval (sec)

MINBYT==:^D8		;CHKBSO says no if fewer than MINBYT of byte alloc.
			;This number also used by TTPNDV for minimum buffer
			; capacity. If you have a true lower allocation, we
			; don't want to have to talk to you anyway.

MXRTMC==^D50		;Abort a BSP connection if we retransmit a single
			; packet more than this number of times.  This prevents
			; an insane Ethertip from overwhelming the system.

MINDAT==^D46-MNPLEN	;Minimum number of data bytes for a 10MB datagram
;Miscellaneous parameters

NPNETS==:^D256			;Maximum number of networks in routing table
NPUPPG==24			;Number of pages of Pup packet buffer storage
HDRMOD==.PM16			;Pup header mode
NILCHK==177777			;Nil checksum
SNFCNT==^D8			;Number of data bytes to sniff from MEIS
GATPRT==NPUPUN			;Gateway input port is hardwired
MAXHOP==^D16			;Maximum hop count for a pup
PUPBKN==5			;Number of background tasks
;NPUPSK==<NPUPUN/^D36>+1	;Number of words in the PSI bit table
;NPUPPN==<NTTPNV/^D36>+1	;Number of words in PNV scan bit table
COMMENT	\

This is a description of to compute the values of NSMPB and NBGPB
(see the next page).

Desired values:

	S = number of small packets
	B = number of large packets

Parameters:

	s = words in a small packet
	b = words in a large packet
	t = total available words

	n = number of small packets per port
	m = number of large packets per port

Constraints:
	
	Ss + Bb = t		-> total number of words is a constant
	Sm - Bn = 1		-> the ratio of large to small packets
				   is the same for the total as well as
				   for a port.

Solution (number of large and small packets):

		S = (b + tn)/(sn + bm)

		B = (Sm - 1)/n

\
;PUP section storage definitions

BSPBEG==600000			;Base of BSP storage
NBSP==2*NPUPUN			;Number of BSP buffers (BSP block, address tbl)
PUPBFZ==:2*<BSPSIZ+BSPSZ0>	;Words of BSP storage per port

PBSTGB==650000			;First valid buffer address
PBSTGE==:PBSTGB+<NPUPPG*PGSIZ>	;Last valid buffer address
				;PUPNM uses pages after it for net dir

IFL <777000-PBSTGE>,<
	PRINTX ? PUP section storage crosses section boundary
	PASS2
	END
>

HEAD==0				;First word of link doubleword is header
TAIL==1				;Second word is tail

NSMPKT==1			;No. of small packets in our ratio 
NBGPKT==1			;No. of large packets in our ratio

SMPBLN==MNPBLX+7		;Words per small packet buffer (note that a
				; minimum sized 10MB pkt must fit)
BGPBLN==MXPBLX			;Words per big packet buffer

TTPBLN==NPUPPG*PGSIZ		;Total number of words for packet buffers

NSMPB==<BGPBLN+<TTPBLN*NSMPKT>>/<<SMPBLN*NSMPKT>+<BGPBLN*NBGPKT>>
				;Number of small packet buffers
NBGPB==<<NSMPB*NBGPKT>-1>/NSMPKT ;Number of large packet buffers

;Minimum number of free packets for process level allocation

BGPBMN==NBGPB/5			;20% of total large buffers
SMPBMN==NSMPB/5			;20% of total small buffers
;PUP storage definitions

;RS PUPIBC,NPUPUN+1		;Input buffer counts (0,,<no. buffers>)
;RS PUPSTS,NPUPUN+1		;Port status word (mostly for BSP)
;RS PUPLCK,NPUPUN+1		;Lock word for the port
;RS PUPLKF,NPUPUN+1		;Fork number of current port locker
;RS PUPLSK,NPUPUN		;Local socket number table (0 free, -1 deleted)
;RS PUPPSI,NPUPUN		;Port PSI assignments
;RS PUPBSP,NPUPUN		;Pointer to BSP data block, if any
;RS PUPLNH,NPUPUN		;Local net,,local host
;RS PUPFPT,NPUPUN		;Foreign port table pointer, zero if all wild
;RS PUPSKD,NPUPSK		;Reception PSI bit table
;RS PUPPNV,NPUPPN		;PNV scan request bit table
;RS PUPPND,NPUPPN		;Deferred scan bit table

RS PUPROU,NPNETS		;Network routing table
RS SMPBC			;Count of free small buffers
RS BGPBC			;Count of free large buffers
RS BSPC				;Count of free BSP buffers
RS PNVTTY			;TTY number of first PNV (for PNV scanning)
RS PNVFLG			;PNV scan request flag
RS PNVFL0			;PNV scan secondary flag word
RS PUPFLG			;Background process request flag
RS PUPFL1 			;Background secondary flag
RS PUPFL2			;Defered requests flag word
RS PUPFL3			;Count of tasks run since last block time
RS PBPTIM			;Time background process started last task
RS P7INTC			;Flag word used by PU7PSI and PUPINT
RS PRTLCK			;Lock for changes to port tables
RS LSKNDL			;Count of deleted entries in local socket table
RS PUPFRK			;FORKX of Pup background process
RS SYNTIM			;Time for next sync timeout check
RS DEFTIM			;Timeout for doing deferred task scan

;Doubleword Queue headers

;RS PBQBEG,0			;Start of queue header block
;RS PUPIBQ,2*<NPUPUN+1>		;Input buffer queue, one per port.
;RS SMPBQ,2			;Small packet buffers
;RS BGPBQ,2			;Large packet buffers
;RS BSPQ,2			;BSP swappable storage
;RS PBQEND,0			;End of queue header block

;Timer queue (keep in order)

;RS PUPTQH			;Timer queue head
;RS PUPTMQ,NPUPUN		;Timer queue linkage
;RS PUPFTM			;Time word corresponding to PUPTQH
;RS PUPTIM,NPUPUN		;BSP timer - TODCLK for next retransmission
; PUPTQD==:NPUPUN+1		;PUPTIM-PUPTMQ displacement
;Pup parameters - the PUPPAR GETAB% table

RS PUPPAR,3			;Tenex portion of PUPPAR
RS PUPON			;-1 if PUP code is enabled
RS PUPBGF			;-1 if logging pup bugs
RS NTDVER			;Network Directory version number
RS PUPPA0,0			;End of PUPPAR table
NPUPPR==:<PUPPA0-PUPPAR>	;Length of this table

;Definitions for the Tenex portion of the parameter table

.PPPNV==0			;-# of Pup NVT's, TTY# of first NVT
.PPSTG==1			;Start of pup free storage
.PPFLG==:2			;Various flags

;.PPFLG bits and fields

PP%GAT==1B0			;We're a gateway (must be sign bit)
PP%NDC==:1B1			;Net directory is cached, not mapped
PP%TNX==:1B2			;Packet headers are in 32-bit mode
PP%MMD==1B3			;All MEIS data modes possible (new hardware)
PP%10M==1B4			;There are 10MB interfaces (so no ASCII mode)

DEFSTR DEFNET,PUPPAR+.PPFLG,35,18 ;Default directly connected net
;Pup Statistics - the PUPSTA GETAB% table

RS PUPSTA,0			;GETAB table of Pup statistics

;Pup Background Process Statistics
RS PBKCNT,PUPBKN		;Number of executions of each task
RS PBKTIM,PUPBKN		;Time spent executing each task
RS PBKRT			;Total runtime consumed by Pup process

;Pup Transmission Statistics
RS STAXMT			;Total transmitted
RS STARTM			;Total retransmissions
RS STAXBD			;No. transmission errors

;Pup Reception Statistics
RS STARCV			;Total received
RS STARBD			;No. reception errors
RS STAFRM			;No. badly formatted
RS STAWAI			;No. waifs
RS STAGAT			;No. gateway
RS STAIQL			;No. discarded  - interrupt level q full
RS STAIOB			;No. discarded  - no free iorbs
RS STASHT			;No. of short pups processed
RS STABUG			;No. of bad pups detected at process level

;Pup Storage Statistics (interrupt level)
RS STABGT			;No. tries for large buffers
RS STABGM			;No. misses for large buffers
RS STASMT			;No. tries for small buffers
RS STASMM			;No. misses for small buffers

;Pup Storage Statistics (process level)
RS STABPT			;No. tries for large buffers
RS STABPM			;No. misses for large buffers
RS STASPT			;No. tries for small buffers
RS STASPM			;No. misses for small buffers

;Miscellaneous Statistics
RS STAPBG			;No. packets processed by background process
RS STAPPR			;No. packets handled by user processes
RS PUPST0,0			;End of PUPSTA table
 NPUPST==:<PUPST0-PUPSTA>	;Length of PUPSTA GETAB table
;Network routing - PUPROU table

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
				; (0 net and host means route directly)
DEFSTR NETADR,PUPROU-1,35,18	;Our address on this net (0 =) not on net)

  INACCF==1B0			;Network inaccessible (must be sign)
  BROADF==1B1			;Broadcast packets allowed on net
;Local Net and Host information - PUPLNH table

 DEFSTR PRTLN,PUPLNH,17,18	;Local net - zero implies wildcard
 DEFSTR PRTLH,PUPLNH,35,18	;Local host - zero implies wildcard

;Port PSI assignments - PUPPSI table

 DEFSTR INTPSI,PUPPSI(UNIT),5,6	;BSP Interrupt PSI channel
 DEFSTR RECPSI,PUPPSI(UNIT),11,6 ;Received Packet PSI channel
 DEFSTR STCPSI,PUPPSI(UNIT),17,6 ;BSP State Change PSI channel
 DEFSTR FRKPSI,PUPPSI(UNIT),35,18 ;Fork to interrupt or tty designator or -1 

;Definitions for port status word - PUPSTS table

 DEFSTR PUPBSZ,PUPSTS(UNIT),27,4 ;Byte size of this port
 DEFSTR PUPBZ,IOS,27,4		;Byte size of this port in IOS 
 DEFSTR PUPMOD,PUPSTS(UNIT),31,4 ;Data mode for this port
 DEFSTR PUPMD,IOS,31,4		;Data mode for this port in IOS
 DEFSTR PBSTM,PUPSTS(UNIT),35,4	;State number in memory
 DEFSTR PBSTA,PUPSTS(T1),35,4	;State number in memory, indexed in T1
 DEFSTR PBSTT,IOS,35,4		;State number in IOS

;BSLCKF==1B0			;Port is locked (no longer used)
 BSWAKF==1B1			;BSP wakeup request pending
 BSINPF==1B2			;Input available

 BSOUTF==1B3			;Output possible
 BSMRKF==1B4			;Mark encountered in stream
 BSENDF==1B5			;End encountered in stream

 BSTIMF==1B6			;Timeout
 BSNCHK==1B7			;Supress checksumming
 BSOPNR==1B8			;Port open for reading

 BSOPNW==1B9			;Port open for writing
;DSCRNF==1B10			;Random discard enabled (not used)
 BSSAKF==1B11			;Need to send ACK

 BSRAKF==1B12			;Received ACK
 BSTAKF==1B13			;Sent an ACK
 BSINTF==1B14			;Interrupt outstanding

 BSLISF==1B15			;Port is or has been listening
 BSNOQF==1B16			;BSP output queue is non-empty
 BSERRF==1B17			;Net went off, or port is closed or aborted

 BSETHF==1B18			;Data mode is .PM32, byte size ^D8
 BSNVTF==1B19			;This is an NVT connection
 BSFTPF==1B20			;This is a high speed/volume FTP connection
;Macro to assemble bits corresponding to up to 8 listed port states
;Call by:	STTBTS(STATE1,STATE2, ...)

DEFINE STTBTS(A,B,C,D,E,F,G,H) <<$S(A)+$S(B)+$S(C)+$S(D)+$S(E)+$S(F)+$S(G)+$S(H)>>

DEFINE $S(STATE) <IFNB <STATE>,<1B<S.'STATE>>>


;Macro to assemble code to skip if current state is among those listed
;Call by:	CHKSTT(<STATE1,STATE2, ...>,AC)
;Assumes UNIT setup, clobbers T1 unless AC specified

DEFINE CHKSTT(STATES,AC<T1>) <
	LOAD AC,PBSTT		;Get current state
	MOVE AC,BITS(AC)	;Get bit corresponding to state
	TXNN AC,STTBTS(STATES)  ;Skip if among those listed
>
;Pup NVT data

;The TTYPUP word is stored in TTDEV in the dynamic data

  PNVASF==1B0			;Set if PNV is assigned (must be sign)
 DEFSTR PSYNCT,TTYPUP(T2),3,3	;Sync count (Int's - DM's)
  SYNCNT==7B3			;Mask of bits in sync count
 DEFSTR PSYNTM,TTYPUP(T2),6,3	;Synchronization timer
 DEFSTR PNVSTT,TTYPUP(T2),8,2	;NVT state for input processing
 DEFSTR PNVMRK,TTYPUP(T2),12,4	;Pending Mark type if any
  HUREQF==1B13			;Hangup request already made
 DEFSTR PTMKCT,TTYPUP(T2),16,3	;Count of incoming timing marks pending
  TMKPNF==1B17			;Outgoing timing mark pending
 ;B18-35			;Pup unit # of attached port

INTERN HUREQF			;Ignore TTMSG if hanging up (TTYSRV)

;Possible PNV states (referenced by PNVSTT)
 PN.DAT==0
 PN.MRK==1
 PN.SYN==2

;Mark types for PNV data streams
 MK.DAT==1			;Data Mark
 MK.WID==2			;Line Width
 MK.LEN==3			;Page Length
 MK.TYP==4			;Terminal Type
 MK.TIM==5			;Timing Mark
 MK.TMR==6			;Timing Mark Reply

SYNCHI==^D10000			;Sync check interval (ms)
SYNTMO==^D20000/SYNCHI		;Sync timeout interval (20 seconds)
;JSYS error macros

DEFINE ERROR(ERRORN,EXTRA,WHERE,SECTION) <
IFB <ERRORN'EXTRA>,<JRST WHERE>
IFNB <ERRORN'EXTRA>,<
	JRST [	IFNB <EXTRA>,<EXTRA>
		IFNB <ERRORN>,<MOVEI A,ERRORN>
IFE REL6,<	JRST WHERE] >
IFN REL6,<	IFB <SECTION>,<JRST WHERE]>
		IFNB <SECTION>,<XJRST [XWD SECTION, WHERE]]>
>;IFN REL6
>>

;Store error code, unlock JFN, and give error return from JSYS
DEFINE ERUNLK(ERRORN,EXTRA) <RETERR(ERRORN,<EXTRA
			<CALL UNLCKM>>)>

;Store error code, unlock JFN, and generate instruction trap
DEFINE FILABT(ERRORN,EXTRA) <ITERR(ERRORN,<EXTRA
		<CALL UNLCKM>>)>

;Store error code, unlock JFN, and give i/o data error psi
DEFINE FILINT(ERRORN,EXTRA) <ERROR(ERRORN,<EXTRA>,DOINT,MSEC1)>
COMMENT \

SIGPBP - Signal pup background process.
SIGDEF - Request defered signal to pup process.

	SIGPBP(XXX) causes bit PBFXXX to be set in PUPFLG.  This causes
the Pup background process to wakeup and call the routine implementing the
requested background task.  The macro optionally takes two more arguments:
SIGPBP(FLAG,<INSTRUCTION>,AC).  If <INSTRUCTION> is specified, the flag is
set iff the instruction does not skip.  If AC is specified, it (instead of
T1) is the accumulator clobbered by the generated code.

\

DEFINE SIGPBP(FLAG,INST,AC<T1>) <
	MOVX AC,PBF'FLAG	;;Get bit corresponding to task
	INST			;;Optional instruction
	IORM AC,PUPFLG		;;Request a task be run
>

DEFINE SIGDEF(FLAG,INST,AC<T1>) <
	MOVX AC,PBF'FLAG	;;Get bit corresponding to defered task
	INST			;;Optional instruction
	IORM AC,PUPFL2		;;Request defered processing
>
COMMENT	\

Notes on handling of locks:

PRTLCK should be locked while attempting to lock a port, then unlocked
once the port is locked.  NVTLCK should be locked while doing anything to
an NVT.  No lock should be held while waiting for a port lock to become
free.  If both NVTLCK and PRTLCK need be locked simultaneously, NVTLCK
must be locked first to prevent deadlocks.

If you fail to obtain a lock, use the SIGDEF macro to request the task
be done later.  Using the SIGPBP macro will often result in a deadlock.
See the comment concerning the PUPFLx flags near the pup fork code.

   **** NEVER let the background fork block for any reason ****

\
SUBTTL Miscellaneous Utility Routines


;GETPID - Return PUP ID
;Takes	PB/ pointer to packet buffer
;Returns +1 always, PUP ID in T1, right justified
;Clobbers T1,T2

	XRESCD

GETPID::LOAD T1,PUPI0
	LOAD T2,PUPI1
	LSH T1,^D16
	IORI T1,(T2)
	RET


;SETPID - Set PUP ID
;Takes	PB/ pointer to packet buffer
;	T1/ Pup ID, right justified
;Returns +1 always
;Clobbers T1

SETPID::STOR T1,PUPI1
	LSH T1,-^D16
	STOR T1,PUPI0
	RET

	XSWAPCD
;GETPSS - Return PUP Source Socket
;Takes	PB/ pointer to packet buffer
;Returns +1 always, PUP ID in T1, right justified
;Clobbers T1,T2

	XRESCD

GETPSS:	LOAD T1,PUPS0
	LOAD T2,PUPS1
	LSH T1,^D16
	IORI T1,(T2)
	RET


;SETPSS - Set PUP Source Socket
;Takes	PB/ pointer to packet buffer
;	T1/ PUP socket, right justified
;Returns +1 always
;Clobbers T1

SETPSS:	STOR T1,PUPS1
	LSH T1,-^D16
	STOR T1,PUPS0
	RET

	XSWAPCD
;GETPDS - Return PUP Destination Socket
;Takes	PB/ pointer to packet buffer
;Returns +1 always, PUP ID in T1, right justified
;Clobbers T1,T2

	XRESCD

GETPDS:	LOAD T1,PUPD0
	LOAD T2,PUPD1
	LSH T1,^D16
	IORI T1,(T2)
	RET


;SETPDS - Set PUP Destination Socket
;Takes	PB/ pointer to packet buffer
;	T1/ PUP socket, right justified
;Returns +1 always
;Clobbers T1

SETPDS::STOR T1,PUPD1
	LSH T1,-^D16
	STOR T1,PUPD0
	RET

	XSWAPCD
;GETMOD - return data mode for this pup on this port
;Takes	PB/ packet buffer pointer
;	UNIT/ port number
;Returns +1 always, T1/ 0 if control pup, positive if data pup
;		    T2/ hardware data mode we want
;Clobbers T1,T2

	XRESCD

GETMOD:	LOAD T2,PUPMOD		;Get data mode of port
	CAIL T2,.PM16		;Range check
	 CAILE T2,.PM9
	  MOVX T2,.PM32		;Data mode out of range, use 32 bit mode
	LOAD T1,PUPTYP		;Get pup type
	CAIE T1,PT.DAT		;Is it Data?
	 CAIN T1,PT.ADA		;Or AData?
	  RET			;Yes, use port's data mode
	MOVX T2,.PM32		;Control pups use 32-bit mode
	SETZ T1,		;Set flag for control pup
	RET			;Return to caller

	XSWAPCD
;GETLEN - return number of 36-bit words in a pup (header and data portions)
;Takes	PB/ packet buffer pointer
;	UNIT/ port number
;Enter at GETLN with T1/ total number of bytes, T2/ data mode
;Returns +1 always, T1/ pup length in words
;Clobbers T1-T2
	
	XRESCD

GETLEN:	CALL GETMOD		;Get data mode for this port, this pup
	LOAD T1,PUPLEN		;Get number of ethernet bytes
GETLN:	SUBI T1,MNPLEN		;Discount header and checksum bytes
	PUSH P,T3		;We should save this
	SETZ T3,		;Compute number of data bytes
	CALL WRDCNV		;Change bytes to 36-bit words, T1/ result
	POP P,T3		;Restore AC
	ADDI T1,5		;Five words of header bytes
	RET			;Return to caller

;NIBTAB - data nibbles per word as a function of hardware mode
;Note well - use TO36 and FRM36 to calculating in the 36-bit mode

NIBTAB:	EXP ^D8			;.PM16
	EXP ^D8			;.PM32
	EXP ^D1			;.PM36 - A non-zero number, just in case
	EXP ^D10		;.PMASC
	EXP ^D8			;.PM16S
	EXP ^D8			;.PM9

	XSWAPCD
;PILOCK - coroutine for going NOSKED and interlocking with I/O PI channel.
;Takes no arguments.  Supports +1 and +2 returns.  Subroutine must be
;resident since we can't risk page faulting with the I/O turned off.
;May be called from process or scheduler context.

	XRESCD

PILOCK:
IFN DEBUGF,<
	CONSZ PI,1B<PHYCHN+^D20> ;At interrupt level?
	 BUG.(HLT,PILCKX,PUP,SOFT,<PUP - invalid call to PILOCK>)
>;IFN DEBUGF
	NOSKD1			;Go NOSKED	
	IOPIOFF			;Interlock by turning off I/O channel
	CALL @(P)		;Call coroutine
	 TRNA			;Single return
	  AOS -1(P)		;Skip return, bump return address
	IOPION			;Unlock
	OKSKD1			;Resume scheduling
	ADJSP P,-1		;Trim stack
	RET			;Return to caller of our caller

	XSWAPCD
;PUPBUG - jacket routine for logging a PUP protocol bug and releasing packet
;PUPBG entry point will not release the packet buffer
;Must be called at scheduler or process level only
;Takes PB/ packet buffer pointer
;Sets up T4 with <unit>1B17+<net>1B26+<host>1B35
;Returns +1 want to execute the buginf/bugchk
;	 +2 skip over the buginf/bugchk
;Clobbers T4, PB

PUPBUG:	PUSH P,[0]		;Want to discard packet buffer
	 TRNA			;Skip over second entry point into main routine
PUPBG:	PUSH P,[-1]		;Want to keep packet buffer
	AOS STABUG		;Record a bad pup detected at process level
	CALL PUPBG0		;Set up T4 
	SKIPL 0(P)		;Skip if keeping packet buffer 
	 CALL RELBUG		;Same as RELPKT, only saves temporaries
	ADJSP P,-1		;Adjust stack
	SKIPE PUPBGF		;Logging pupbugs?
	 RET			;Yes, take single return
	RETSKP			;No, skip over the bugchk/buginf

;PUPBG0 - Set up T4 with PUPBUG data

PUPBG0:	PUSH P,T1		;Save a scratch AC
	LOAD T4,PUPSN		;Get net of foreign host
	LSH T4,^D9		;Slide it over
	LOAD T1,PUPSH		;Get foreign host number
	IORI T4,(T1)		;Have net,,host in right half of T4
	HRLI T4,(UNIT)		;Remember the local port number in left half
	POP P,T1		;Restore scratch AC
	RET			;Return to caller
;OWGBYT - byte sizes corresponding to the byte indices

OWGBYT:	^D6			; 0
	^D7			; 1
	^D8			; 2
	^D9			; 3
	^D18			; 4

;OWGTAB - one word global byte pointers left-aligned on a word boundary

OWGTAB:	G1BPT(PUPSEC,^D6)	; 0
	G1BPT(PUPSEC,^D7)	; 1
	G1BPT(PUPSEC,^D8)	; 2
	G1BPT(PUPSEC,^D9)	; 3
	G1BPT(PUPSEC,^D18)	; 4

;OWGLOT - smallest value of PS field for a byte index

OWGLOT:	45			; 0
	61			; 1
	54			; 2
	67			; 3
	74			; 4

;OWGHIT - largest value of PS field for a byte index

OWGHIT:	53			; 0
	66			; 1
	60			; 2
	73			; 3
	76			; 4
SUBTTL Standard JSYS Routines for Pup

;Pup device dispatch table
;Must be in monitor section for JFN I/O to find offsets correctly

	SWAPCD

PUPDTB::
IFN REL6,<PUPDTL>		;Length of DTB
	DTBDSP (PUPSET)		;Directory setup
	DTBDSP (PUPNAM)		;Name lookup
	DTBDSP (PUPEXT)		;Extension lookup
	DTBDSP (PUPVER)		;Version lookup
	DTBBAD (DESX9)		;Insert protection
	DTBBAD (DESX9)		;Insert account
	DTBBAD (DESX9)		;Insert status
	DTBDSP (PUPOPN)		;Open
	DTBDSP (PUSQIX)		;Sequential input
	DTBDSP (PUSQOX)		;Sequential output
	DTBDSP (PUPCLZ)		;Close
	DTBBAD (DESX9)		;Rename
	DTBBAD (DESX9)		;Delete
	DTBBAD (DESX9)		;Dump input
	DTBBAD (DESX9)		;Dump output
	DTBBAD (DESX9)		;Mount
	DTBBAD (DESX9)		;Dismount
	DTBBAD (DESX9)		;Initialize directory
	DTBDSP (PUPMTP)		;MTOPR%
	DTBDSP (PUPGST)		;Get status
	DTBDSP (PUPSST)		;Set status
	DTBDSP (PUPREC)		;Record out - SOUTR%
	DTBDSP (RFTADN)		;Read TAD
	DTBDSP (SFTADN)		;Set TAD
	DTBDSP (PUSFI)		;Set JFN for input
	DTBDSP (PUSFO)		;Set JFN for output
	DTBBAD (GJFX49)		;Check attribute
	DTBSKP			;Release JFN
PUPDTL==.-PUPDTB

	XSWAPCD
;GTJFN routines

;Directory setup routine

XNENT PUPSET
	TQNE <STEPF>		;Want to step?
	RETBAD (GJFX17)		;Yes. can't do it
	NOINT
IFE REL6,<JRST SK2RET		;Always successful>
IFN REL6,<RETSKP>		;Always successful>

;Name lookup routine

XNENT PUPNAM
	JUMPE T1,[ERRJMP(GJFX31,GJERRX)] ;* not allowed
	HRLI T1,(POINT 7,,35)	;Make string pointer
	CALL PNMDEC		;Decode name string
	 ERRJMP(,GJERRX)	;Bad, say no such name
	JRST GJ2RET		;Ok, take success return


;Extension lookup routine

XNENT PUPEXT
	JUMPE T1,[ERRJMP(GJFX31,GJERRX)] ;* not allowed
	HRLI T1,(POINT 7,,35)	;Make string pointer
	CALL PEXDEC		;Decode extension string
	 ERRJMP(,GJERRX)	;Bad, say no such extension
	CALL RELBSP		;Release the address block
	JRST GJ2RET		;Take success return

;Version lookup routine (always succeeds, does nothing)

IFE REL6,<
PUPVER: JRST GJSRET
>;IFE REL6
IFN REL6,<
XNENT PUPVER
	JRST GJ2RET
>;IFN REL6

;Returns from GTJFN routines
IFE REL6,<
GJ2RET:	AOS 0(P)		;Double skip
GJSRET:	AOS 0(P)		;Single skip
>;IFE REL6
IFN REL6,<
GJ2RET:	AOS 0(P)		;Skip return
GJSRET:				;Single return
>;IFN REL6
	TQNE <UNLKF>		;Should we unlock?
	 RET			;No
GJERRX:	OKINT			;Yes
	RET    
;OPENF - Open Pup network file

DEFINE OPNERR(ERRORN,EXTRA) <ERROR(ERRORN,<EXTRA>,OPNERX)>

;Check access mode, data mode, and byte size

XNENT PUPOPN
	SKIPN PUPON		;PUP ready?
	 RETBAD(ETHRX1)		;No, "Ethernet service not available"
	TQNN <XCTF,RNDF>	;Xct and append are illegal
	TQNN <READF,WRTF>	;Must be reading or writing
	 RETBAD(OPNX13)		;Illegal access
	TXNE STS,10		;Raw (no BSP) pups wanted?
	 JRST PUPOP0		;Yes, skip some checks
	LDB T1,[POINT 6,FILBYT(JFN),11] ;Get byte size
	CALL ETHBYT		;Verify it
	 RETBAD(SFBSX2)		;Invalid byte size

;Re-parse the name and extension strings to yield addresses
PUPOP0:	HRRZ T1,FILNEN(JFN)	;Make byte ptr to extension
	HRLI T1,(POINT 7,,35)
	CALL PEXDEC		;Decode Pup extension string
	 RETBAD()		;Failed (maybe net dir changed?)
	MOVE E,T1		;Save address table pointer
	HLRZ T1,FILNEN(JFN)	;Make byte ptr to filename
	HRLI T1,(POINT 7,,35)
	CALL PNMDEC		;Decode Pup name string
	 OPNERR()		;Failed (maybe net dir changed?)
;Now have T1/ net,,host T2/ socket for local port. E points to address table.
;First verify that the specified local socket is legal.
;Then if we're not listening, ensure the foreign port is not multiple or wild.
;If the foreign host is multi-homed, choose the best address.
;Default local net and host if necessary.

	CALL PUPOPS		;Check if we can have this socket
	 OPNERR(OPNX13)		;No, illegal access to socket
	TXNE STS,12		;Okay if mode 2, 3, or 16
	 JRST PUPOP3
	MOVE T3,0(E)		;Get length of address block
	CAIE T3,2		;A multi-homed host?
	 CALL PUPOPM		;Yes, discover best one
PUPOP2:	SKIPE 1(E)		;Disallow wildcards in net or host
	 SKIPN 2(E)		;Disallow wildcards in socket
	  OPNERR(PUPX22)	;Yes, "Invalid wildcard or multiple dest."
	HLRZ T3,1(E)		;Get foreign network 
	CAIL T3,1		;Range check the network number
	 CAILE T3,NPNETS	; ...
	  OPNERR(PUPX20)	;"Network number out of range"
	SKIPGE T4,PUPROU-1(T3)	;Is that host reachable?
	 OPNERR(PUPX21)		;No, "Destination host inaccessible"
	JUMPN T1,PUPOP3		;Local net and host specified?
	TXNN T4,.RHALF		;No, are we on destination net?
	 LDB T3,[POINT 8,PUPROU-1(T3),9] ;No, use net of gateway
	HRRZ T1,PUPROU-1(T3)	;Get our address on that net
	HRL T1,T3		;Set up net number
;OPENF (cont'd)

;Now have:
; local  -)  T1/ net,,host, T2/ socket
; foreign -) 1(E) <net>,,<host>, 2(E)/ socket
;If the ACJ says okay, attempt to assign the local port

PUPOP3:	MOVE T4,CAPMSK		;Get capabilities word
	TXNE T4,SC%WHL!SC%OPR!SC%ENA	;WOPR or Ethernet Access?
	IFSKP.
IFE REL6,<GTOKM(.GOENA,<1(E),2(E)>,[OPNERR(GOKER2)])>
IFN REL6,<
	  TOSWAPCD
	  GTOKM(.GOENA,<1(E),2(E)>,<[XJRST [XCDSEC,,ACJERX]]>)
	  TOXSWAPCD
>;IFN REL6
	ENDIF.
	CALL ASGPRT		;Assign local port
	 OPNERR(MONX02)		;Failed, "Insufficient system resources"
	  JRST PUPOP6		;Duplicate, check for legal cases
	HRLM UNIT,FILSKT(JFN)
	MOVE T1,FORKX		;Record fork owning port
	STOR T1,FRKPSI
	MOVEM E,PUPFPT(UNIT)	;Save address table pointer
	MOVX T1,.PM32		;Default data mode is 32 bits
	LDB T2,[POINT 6,FILBYT(JFN),11]	;Get user's byte size
	CAIN T2,^D7		;7 bit bytes?
	 MOVX T1,.PMASC		;Yes, set ASCII mode
	CAIN T2,^D18		;18 bit bytes?
	 MOVX T1,.PM36		;Yes, set PDP-10 36 bit mode
	STOR T1,PUPMOD		;Store the data mode
	TXNN STS,10		;Raw packets wanted?
	 JRST PUPOP4		;No, more to do for BSP case
	UNLOCK(PRTLCK)		;Unlock local socket table
	SKIPN 1(E)		;Is foreign port fully wildcard?
	 SKIPE 2(E)
	  RETSKP		;No, done
	SETZM PUPFPT(UNIT)	;Yes, remember so
	MOVE T1,E		;Set up pointer to address block
	CALL RELBSP		;Release the storage
	RETSKP			;Done, skip return
;OPENF (cont'd)

;Here if BSP.  Lock port and setup BSP data block
PUPOP4:	CALL BLDBSP		;Allocate and build BSP data block
	 OPNERR(OPNX10,<UNLOCK(PRTLCK)>) ;Failed
	MOVEM BSP,PUPBSP(UNIT)	;Store pointer in standard place
	LDB T1,[POINT 6,FILBYT(JFN),11]	;Get OPENF% byte size (already checked)
	CALL ETHBYT		;Figure out index
	 MOVEI T2,2		;Impossible return - assume 8-bit bytes
	STOR T2,PUPBSZ		;Store byte size index
	MOVX IOS,BSOPNR		;Assume opening for reading
	TQNE <WRTF>		;Writing?
	 MOVX IOS,BSOPNW	;Yes
	IORB IOS,PUPSTS(UNIT)	;Initialize port status word
	CALL LCKBSA		;Lock the port
	 NOP			;Impossible return
	CALL BSP8DT		;Check if classic Ethernet data mode (BSETHF)
	UNLOCK(PRTLCK)		;Unlock local socket table
	XCTU [HLRZ T1,T2]	;Get timeout interval info from user
	TRNE T1,377		;Skip if none given
	 STOR T1,PBTMO		;Else, set it

;Initiate appropriate operations to open a connection
	TXNE STS,2		;Listening?
	 JRST [	TXO IOS,BSLISF  ;Yes, remember so
		MOVEI T1,E.OPNL	;Generate event OPENF(L)
		JRST PUPOP5]
	TXNE STS,4		;Direct open (no rendezvous)?
	 JRST [	UMOVE T1,T3	;Yes, get Connection ID from user
		CALL SETCID	;Set it in data block
		MOVEI T1,E.OPNN	;Generate event OPENF(N)
		JRST PUPOP5]
	MOVEI T1,E.OPNC		;Initiating rendezvous, OPENF(C)
PUPOP5:	CALL PUPFSM		;Activate the FSM appropriately
	JRST PUPOP7		;Go wait if necessary

;Here if local port already in use, check for legal case
PUPOP6:	SKIPE BSP,PUPBSP(UNIT)	;Get BSP pointer, skip if already raw open
	TXNE STS,10		;Opening in raw modes?
	 OPNERR(OPNX9,<UNLOCK(PRTLCK)>) ;Yes, fail
	LOAD T1,FRKPSI		;No, get fork that did first open
	CAIL T1,NFKS		;Make sure it is a fork (not -1 or tty desig.)
	 OPNERR(OPNX9,<UNLOCK(PRTLCK)>) ;Not legal for PNV's or dead ports.
	HLRZ T1,FKJOB(T1)	;Get job
	CAME T1,JOBNO		;Same as me?
	 OPNERR(OPNX9,<UNLOCK(PRTLCK)>) ;No, fail
	MOVE T1,E		;Ok, get rid of address table
	CALL RELBSP
	CALL LCKBSA		;Attempt to lock the port
	IFNSK.
	  UNLOCK(PRTLCK)	;Can't, unlock the table
	  MOVSI T1,(UNIT)	;Set scheduler test
	  HRRI T1,BSLCKT	; for port unlocked
	  MDISMS		;Wait until unlocked
	  MOVE T1,PUPSTS(UNIT)	;Get port status flags
	  TXNE T1,BSERRF!BSTIMF	;Error condition?
	   RETBAD(OPNX9)	;Yes, abort
	  JRST PUPOP0		;Try again
	ENDIF.
	UNLOCK(PRTLCK)		;Ok, unlock the table
	CALL SETRWB		;Setup read/write bit for this opening
	TDOE IOS,T1		;Flag opening, check for conflict
	 RETBAD(OPNX9,<CALL ULKBSP>) ;Already open, fail
	HRLM UNIT,FILSKT(JFN)
;OPENF (cont'd)

;Here to wait for completion if necessary
PUPOP7:	CHKSTT <OPEN,ENDI,ENDO,DALY,ABOR,CLOS> ;Beyond LIST/RFCO state?
	TXNE STS,5		;Don't wait for a connection?
	 JRST PUPOP8		;Yes, bypass waiting
	CALL ULKBSP		;Unlock port
	TQO <OPNF>		;Mark JFN as being open
	MOVSI T1,1		;Fix reference count
	IORM T1,FILLFW(JFN)
	CALL UNLCKM		;Unlock file, go OKINT
	MOVX T1,<ALLSTT-STTBTS(LIST,RFCO)>
	CALL WATSTT		;In desired state now?
	MDISMS			;No, wait until get there
	SKIPE PUPON		;PUP vanished? (Servers often block here)
	 TXNE IOS,BSERRF!BSTIMF	;Or error condition?
	  RETBAD(OPNX9)		;Yes, bomb us
;Check whether the connection was opened successfully
;This is somewhat hairy since a PSI or another fork could have
; released and re-used the JFN in the meantime
	PUSH P,JFN+1
	IDIVI JFN,MLJFN		;Convert from internal format
	POP P,JFN+1
	CALLM CHKJFN		;NOINT, lock JFN again
	 RETERR()		;Huh? (released by PSI maybe)
	 ERUNLK(DESX4)
	 ERUNLK(DESX4)
	HRRZ T1,FILDEV(JFN)	;Get DTB
	CAIE T1,PUPDTB		;Is it a PUP
	 RETBAD(DESX5)
	HLRZ T1,FILSKT(JFN)	;Make sure JFN still refers to
	CAIE T1,(UNIT)
	 RETBAD(DESX5)		;Not, fail
	TQNE <OPNF>		;Make sure JFN still open
	 CALL LCKBSQ		;Lock the port again
	  RETBAD(DESX5)		;Not open or not BSP
	MOVSI T1,1		;Undo reference count diddle
	ANDCAM T1,FILLFW(JFN)

;Come directly here if an immediate return is desired
PUPOP8:	CHKSTT <CLOS,ABOR>	;Connection closed or aborted?
	 TDZA T1,T1		;No, assume no error
	 MOVEI T1,OPNX21	;Yes, assume rejected by foreign host
	TXNE IOS,BSTIMF  	;Timed out?
	 MOVEI T1,OPNX20	;Yes, assume nobody there
	JUMPN T1,PUPOP9		;Jump if failed to open
	CALL ULKBSP		;Succeeded, unlock port
	RETSKP			;Give success return
;OPENF (cont'd)

;Here if rendezvous attempt failed
;Clean up and give error return, T1/ error code
PUPOP9:	PUSH P,T1		;Save error code
	MOVEI T1,E.CLST		;Generate CLOSF(T) event
	HRROI T3,0		;Set code for Abort if any
	HRROI T4,[ASCIZ /Connection attempt timed out/]
	CALL PUPFSM		;Force state to closed
	CALL PUPCL3		;Delete port if appropriate
	 BUG.(HLT,PUPOPA,PUP,SOFT,<PUP - Impossible +1 return from PUPCL3>)
	TQZ <OPNF>		;JFN no longer open
	POP P,T1		;Recover error code
	RET    			;Fail return from OPENF



;Here to fail return from early parts of the OPENF when we have
; address table assigned but port not open yet (OPNERR macro)
;T1/ error code, E/ address table pointer

ACJERX:	MOVEI T1,GOKER2		;Return here on an ACJ access er ror
OPNERX:	PUSH P,T1		;Save error code
	MOVE T1,E		;Pointer to address table
	CALL RELBSP		;Release address table
	POP P,T1		;Restore code
	RET			;Return to caller
;OPENF (cont'd)

;PUPOPS - verify that access to local socket is permitted
;Note that for user-relative sockets we use the login directory instead
;  of the connected directory because of multiple structures on TOPS-20.
;  The exception is not-logged in jobs.
;Takes	T1/ local net,,host
;	T2/ socket
;Returns +1 failure
;	 +2 success
;Clobbers T3, T4

PUPOPS:	SAVEAC <T1,T2>		;Preserve these registers
	MOVE T3,CAPENB		;Get privilege bits
	TXNE T3,SC%WHL!SC%OPR	;Wheel or operator?
	 RETSKP			;Yes, can have any socket
	MOVE T3,T2		;No, get high 17 bits
	LSH T3,-^D15
	MOVE T4,JOBNO		;Get local job number
	SKIPN T4,JOBDIR(T4)	;Get logged in directory number
	 HRRZ T4,JSBSDN		;Not logged in, get connected directory number
	CAIN T3,(T4)		;User-relative socket?
	 RETSKP			;Yes, ok
	CAIL T3,^D50000		;Socket in free-for-all range?
	 CAILE T3,^D99999	;Can't use connected directory on TOPS-20
	  SKIPA			;Because of multiple structures.
	   RETSKP		;Either of those, socket ok
IFE REL6,<
	MOVE T4,JOBNO		;No, get job number
>;IFE REL6
IFN REL6,<
	MOVE T4,GBLJNO		;No, get global job number
>;IFN REL6
	CAIE T3,^D100000(T4)	;Job-relative for this job?
	  RET			;No, illegal access to socket
	RETSKP
;OPENF (cont'd)

;PUPOPM - select "best" address for a multi-homed host.
;Returns +1 failure, host is down, T1/ error code
;	 +2 success, address table updated appropriately
;Clobbers T3,T4

PUPOPM:	SAVEAC <T1,T2>		;Preserve some registers
	MOVE T4,E		;Copy address table pointer
	SETZ T3,		;Pointer to best address so far
	MOVE T2,0(E)		;Length of address table
PUPPM0:	HLRZ T1,1(T4)		;Get a net from table
	CAIL T1,1		;In range?
	 CAILE T1,NPNETS
	  JRST PUPPM1		;No, go look at next entry
	SKIPGE T1,PUPROU-1(T1)	;Yes, get routing table entry
	 JRST PUPPM1		;Inaccessible, go look at next entry
	TXNE T1,177777B17	;Direct routing available?
	 SKIPN T3		;No, do we have an index yet?
	  MOVE T3,T4		;Either no index or a direct route
	TXNN T1,177777B17	;Index set.  Now, can we route directly?
	 JRST PUPPM2		;Yes, use this entry
PUPPM1:	ADDI T4,2		;Advance table pointer (2 words)
	SUBI T2,2		;Decrement length of unexamined table
	JUMPG T2,PUPPM0		;Go look again if there is still something left
PUPPM2:	SKIPN T3		;Skip if a route was found
	 OPNERR(PUPX21)		;"Destination host is inaccessible"
	MOVEI T1,2
	MOVEM T1,0(E)		;Store new length
	MOVE T1,1(T3)
	MOVEM T1,1(E)		;Store net,,host
	MOVE T1,2(T3)
	MOVEM T1,2(E)		;Store socket
	RETSKP			;Success return
;CLOSF - Close Pup network file

XNENT PUPCLZ
	SETZRO <BLKF>		;Not blocking now
	HLRZ UNIT,FILSKT(JFN)	;Setup Pup unit #
	CALL LCKBSQ		;Lock port if BSP
	 JRST PUPCL8		;Not BSP, jump around BSP logic
	SETZM FILBCI(JFN)	;Allow no further I/O
	SETZM FILBCO(JFN)	;?? Is this necessary ??
	TXZN IOS,BSTIMF		;Timed out?
	IFSKP.
	  MOVEI T1,E.CLST	;Yes, generate CLOSF(T) event
	  HRROI T3,0		;Abort Code = 0
	  HRROI T4,[ASCIZ/Connection timed out/]
	  CALL PUPFSM		;Fire up finite state machine 
	  TQO <ERRF>		;Set JFN error flag
	  RETBAD(IOX5,<CALL ULKBSP>) ;Give error return
	ENDIF.
	TQNE <WRTF>		;Closing output JFN?
	IFSKP.
	  TXNN IOS,BSOPNW	;No, is port also open for writing?
	   JRST PUPCL2		;No, generate close event
	  JRST PUPCL3		;Yes, do nothing for input close
	ENDIF.
	MOVEI T1,E.CLST		;Setup FSM code for an abort
	UMOVE T2,1		;Get flags from call
	TXNN T2,CZ%ABT  	;Was this an abort?
	 TXNE IOS,BSERRF	;Or violent death?
	  JRST PUPCL4		;Yes, don't chance blocking
	CHKSTT <OPEN,ENDI>	;State ok for BSP output?
	 JRST PUPCL2		;No, skip this
	MOVE T1,FILBFO(JFN)	;Get pointer to last data
	CALL FRCBSP		;Force out remaining data if any
	CALL CHKBOQ		;Any output pending?
	 JRST CLZWAT		;Yes, back out and wait til done
PUPCL2:	MOVEI T1,E.CLSN		;Generate CLOSF(N) event
PUPCL4:	HRROI T3,0		;Need registered code
	HRROI T4,[ASCIZ/Connection attempt abandoned/] ;In case RFC Out
	CALL PUPFSM
	MOVX T1,<STTBTS(CLOS,ABOR)> ;Specify desired states
	CALL WATSTT		;Now closed or aborted?
	 JRST CLZWAT		;No, back out and wait until it is
	LOAD T1,PBSTT		;Yes, get current state
	CAIE T1,S.CLOS		;Now closed?
	 RETBAD(IOX5,<CALL ULKBSP>) ;No, give error return
PUPCL3:	CALL SETRWB		;Setup status bit for this opening
	ANDCM IOS,T1		;Mark no longer open this way
	TXNN IOS,BSOPNR!BSOPNW	;Still open the other way?
	IFSKP.
	  CALL ULKBSP		;Yes, don't delete port yet
	  RETSKP		;Success return
	ENDIF.
	CALL FLSBSQ		;Now closed both ways, flush queues
	ECSKED			;Leave critical section (got there via LCKBSQ)
PUPCL8:	CALL DELPRT		;Delete the port
	RETSKP			;Done, skip return
;CLOSF (cont'd)

;Here when we need to block while closing the pup connection

CLZWAT:	MOVSI T2,1
	IORM T2,FILLFW(JFN)	;Say that one page is mapped
	CALLRET ULKWAT		;Unlock BSP port and dismiss

;SETRWB - Setup read/write bit appropriately for this opening of port
;Takes	STS/ File status
;Returns +1 always, T1/ BSOPNR or BSOPNW set

SETRWB:	MOVX T1,BSOPNR		;Assume opening for reading
	TQNE <WRTF>		;Writing?
	 MOVSI T1,(BSOPNW)	;Yes, say so
	RET    
;KFORK - clean up pup tables upon fork termination

;Pup kill fork
;Returns +1
;Clobbers T1, UNIT

XNENT PUPKFK,G
	MOVSI UNIT,-NPUPUN	;For all ports:
PUPKF1:	LOAD T1,FRKPSI		;Get fork to be interrupted
	CAMN T1,FORKX		;Same as fork being killed?
	 SETOM PUPPSI(UNIT)	;Yes, deassign interrupt for port
	AOBJN UNIT,PUPKF1	;Loop over all ports
	RET			;Return to caller

;PSIRQF
;Initiate PSI on for a fork
;takes	t1/ PSI channel number

PSIRQF:	NOSKED
	CALL PSIRQ
	OKSKED
	RET
;BIN and friends

;Pup sequential byte input
;	JFN, DEV, STS setup
;Returns +1 always, T1/ the next byte (if no error)
;Clobbers T1-T4, UNIT, BSP, PB

XNENT PUSQIX
	SETZRO <BLKF>		;Not blocking now
	PUSH P,BSP		;Save ACs needed by device independent routines
	PUSH P,PB
	CALL PUPSQI		;Call regular sequential byte input routine
	POP P,PB		;Restore ACs
	POP P,BSP
	RET			;Back to device independent routine


PUPSQI:	SOSGE FILBCI(JFN)	;Decrement and test byte count
	 JRST PUPSI1		;Pup exhausted, get another
	ILDB T1,FILBFI(JFN)	;Bytes remain, load next into T1
	AOS FILBNI(JFN)		;Advance byte number
	RET    			;Return
;PUPSQI (cont'd)

;Here when input Pup used up, attempt to get next

PUPSI1:	SKIPN PUPON		;PUP on?
	 FILABT(ETHRX1)		;No, shut down this connection
	HLRZ UNIT,FILSKT(JFN)	;Get Pup port number
	CALL LCKBSQ		;Lock port, check for BSP
	 FILABT(IOX1)		;Not BSP, sequential I/O illegal
	CHKSTT <OPEN,ENDI,ENDO,DALY> ;Check for legal state
	 JRST PUPSQE		;Not good, set error bit
	TXNE IOS,BSMRKF!BSENDF!BSTIMF!BSERRF	;EOF or errors?
	 JRST PUPSI2		;Yes, don't try for more Pups
	CALL GETBSP		;No, get next Pup from stream
	IFNSK.
	  TXNE IOS,BSERRF!BSTIMF ;Empty or Error, check for error
	   JRST PUPSI2		;Error condition, go handle it
	  CHKSTT(<ENDI,DALY>,T2) ;Empty, has End been received?
	   JRST ULKWAT		;No, back out and wait for data
	  TXO IOS,BSENDF	;Yes, set End encountered flag
	  JRST PUPSI2		;Do EOF handling
	ENDIF.
	LOAD T1,PUPTYP		;Got one, get Pup Type
	CAIE T1,PT.MRK		;Mark?
	 CAIN T1,PT.AMA		;AMark?
	  JRST [ILDB T1,PBIPTR(PB) ;Yes, get the byte
		STOR T1,PBMRK	;Store in status word
		CALL RELPKT	;Release the packet
		SETZM BSPCIP(BSP) ;Note no current packet
		TXO IOS,BSMRKF	;Set Mark encountered flag
		JRST PUPSI2]	;Do EOF handling
	CALL ULKBSP		;Data or AData, unlock port

;Some of this code assumes that each pup carries a unique portion of the
; data, i.e., that there are no pups with overlapping data bytes.  The
; original code does not make this assumption.  With the MEIS data modes,
; however, we no longer have an easily determined correspondence between
; Ethernet bytes and the position of the data bytes in our buffers.

	MOVE T1,PBBSBC(PB)	;Get count of 8-bit bytes
	MOVE T2,PBIPTR(PB)	;Get byte pointer
	TXNE IOS,BSETHF		;Classic Ethernet data mode?
	IFSKP.
	  LOAD T1,PUPLEN	;No, special hacks. Get no. of Ethernet bytes
	  SUBI T1,MNPLEN	;Subtract off header and checksum bytes
	  LOAD T2,PUPMOD	;Get this port's data mode
	  LOAD T3,PUPBSZ	;Get this port's byte size index
	  XCT SQITAB(T2)	;Compute the number of bits in this mode
	  IDIV T1,OWGBYT(T3)	;Divide by OPENF% byte size to get byte count
	  MOVE T2,OWGTAB(T3)	;Get global P/S field for this byte size
	  HRRI T2,PBCONT(PB)	;Set PUPSEC address of packet buffer
	ENDIF.
	MOVEM T1,FILBCI(JFN)	;Store new byte count
	ADDM T1,FILLEN(JFN)	;Update file length
	MOVEM T2,FILBFI(JFN)	;Stash new pointer
	JRST PUPSQI		;Back to get first byte
;PUPSQI (cont'd)

;Here if any error flags are set

PUPSI2:	TXNE IOS,BSMRKF!BSENDF	;Mark or End encountered?
	 TQO <EOFF>		;Yes, set EOF flag for JFN
	TXNN IOS,BSTIMF!BSERRF	;Timeout or Error condition?
	IFSKP.
PUPSQE:	 TXO IOS,BSENDF		;Yes, pretend end received
	 TQO <ERRF>		;And set error flag for JFN
	ENDIF.
	CALLRET ULKBSP		;Unlock port and return without data

;SQITAB
;Given a count of Ethernet bytes in T1, return the number of bits the MEIS
;has deposited in memory.  Note that only Ascii mode compresses Ethernet bytes.
;The 16-bit and 9-bit modes add a garbage bit to each Ethernet byte.
 
SQITAB:	CALL SQI16		;.PM16
	LSH T1,3		;.PM32
	LSH T1,3		;.PM36
	IMULI T1,7		;.PMASC
	CALL SQI16		;.PM16S
	CALL SQI16		;.PM9

SQI16:	MOVE T2,T1		;Remember number of bytes
	LSH T1,3		;Multiply by eight
	ADD T1,T2		;Add number of garbage bits (one per byte)
	RET			;Return to caller
;BOUT and friends

;Pup sequential byte output
;	T1/ Byte to be output
;	JFN, DEV, STS setup
;Returns +1 always
;Clobbers T1-T4, UNIT, BSP, PB

XNENT PUSQOX
	SETZRO <BLKF>		;Not blocking now
	PUSH P,BSP		;Save ACs needed by device independent routines
	PUSH P,PB
	CALL PUPSQO		;Call regular sequential byte output routine
	POP P,PB		;Restore ACs
	POP P,BSP
	RET			;Back to device independent routine

PUPSQO:	STKVAR <PUPSOB>		;Byte to output
PUPSQL:	SOSGE FILBCO(JFN)	;Decrement and test byte count
	 JRST PUPSO1		;Pup full, attempt to send
	IDPB T1,FILBFO(JFN)	;Store byte in T1
	AOS FILBNO(JFN)		;Advance byte number
	RET    			;Return

;Here when output Pup full, attempt to send it and start another
PUPSO1:	HRRZM T1,PUPSOB		;Save the byte to be output
	SKIPN PUPON		;Is PUP on?
	 FILABT(ETHRX1)		;No, kill the connection
	HLRZ UNIT,FILSKT(JFN)	;Get Pup port number
	CALL LCKBSQ		;Lock port, check for BSP
	 FILABT(IOX2)		;Not BSP, sequential i/o illegal
	CHKSTT <OPEN,ENDI>	;Check for reasonable state
	 JRST PUPSQE		;Not good, set error bit
	TXNE IOS,BSTIMF!BSERRF	;Timeout or error condition?
	 JRST PUPSQE		;Yes, set error flag
	MOVE T1,FILBFO(JFN)	;Get pointer to last data
	CALL DMPBSP		;Force out current Pup if any
	CALL CHKBSO		;See if more BSP output possible
	 JRST ULKWAT		;No, back out and wait for Ack
	CALL BLDDAT		;Yes, build virgin Data packet
	 JRST ULKWAT		;Failed to allocate space
	MOVEM T1,FILBCO(JFN)	;Store byte count
	MOVEM T2,FILBFO(JFN)	;Store byte pointer
	CALL ULKBSP		;Unlock port
	HRRZ T1,PUPSOB		;Recover the new byte
	JRST PUPSQL		;Back to store it
;SOUTR%

;PUPREC - force output of current PUP buffer
;This is the RECOUT code for PUP.  It is invoked after the BYTBLT in the
; sequential output code to force the current output buffer.  This allows us
; to use SOUTR% instead of a SOUT% followed by a .MOPFC MTOPR%.
;Returns +1 need to block, T1/ scheduler test, and BLKF set
;	 +2 success

XNENT PUPREC
	SAVEAC <BSP,PB>		;Don't clobber these AC's (used by IO)
	HLRZ UNIT,FILSKT(JFN)	;Get Pup port number
	CALL LCKBSQ		;Lock port, check for BSP
	 FILABT(IOX2)		;Not BSP, sequential I/O illegal
	CHKSTT <OPEN,ENDI>	;Check for reasonable state
	 JRST PUPRE0		;Not good, unlock and leave
	TXNE IOS,BSTIMF!BSERRF	;Timeout or error condition?
	 JRST PUPRE0		;Yes, unlock and leave
	MOVE T1,FILBFO(JFN)	;Get pointer to last data
	CALL FRCBSP		;Force out current Pup if any
	SETZM FILBCO(JFN)	;Zero the byte count
	SKIPA			;Don't set error flag
PUPRE0:	 TQO <ERRF>		;Set error flag
	CALL ULKBSP		;Unlock the port
	RETSKP			;Good return to caller
;GDSTS 

;Get status
;	User 3/ Size ,, address of block to return foreign port
;		address table in (see PUPNM), or 0 to omit table
;Returns +1:
;	T1/ BSP status word (to be returned to user ac2)
;	User 3/ size of address table ,, unchanged

XNENT PUPGST
	TQNN <OPNF>		;Make sure open
	 JRST [	SETZ T1,	;Not open, return zero status
		XCTU [HRRZS 3]	;Zero address count
		RET]
	HLRZ UNIT,FILSKT(JFN)	;Get Pup unit number
	CALL LCKBSQ		;Lock port if BSP
	 NOP			;Not BSP
	UMOVE E,3		;Get user block pointer
	JUMPLE E,PUPGS2		;Omit if none
	HLRZ T4,E		;Get count
	SKIPE T2,PUPFPT(UNIT)	;T2/ Get pointer to address table
	IFSKP.
	  XCTU [ SETZM 0(3) ]	;Wildcard address.  Zero first word (net,,host)
	  XCTU [ SETZM 1(3) ]	;Zero second word (socket)
	  MOVEI T3,2		;Count is two
	  XCTU [ HRLM T3,3]	;Return count
	  JRST PUPGS2		;Join end code
	ENDIF.
	MOVE T1,0(T2)		;T1/ Get length of address table
	XCTU [HRLM T1,3]	;Return count
	CAILE T1,(T4)		;Have more than user wants?
	 MOVEI T1,(T4)		;Yes, take minimum
	XMOVEI T2,1(T2)		;T2/ Point to start of address table
	HRRZ T3,E		;T3/ Destination address
	CALL BLTMU1		;Copy from monitor to user
PUPGS2:	MOVE T1,PUPSTS(UNIT)	;Get status
	CALL ULKBSQ		;Unlock port if BSP
	RET    
;SDSTS

;Set status
;	T1/ BSP status word to set (from user ac2)
;Returns +1

XNENT PUPSST
	HLRZ UNIT,FILSKT(JFN)	;Get Pup unit #
	CALL LCKBSQ		;Lock port if BSP
	 NOP			;Not BSP
	MOVE T2,T1		;Copy new status
	AND T1,BSSETB		;Mask bits user may set
	IOR IOS,T1		;Set them
	ANDCA T2,BSCLRB		;Mask bits user may clear
	ANDCM IOS,T2		;Clear them
	TXNN IOS,BSMRKF!BSENDF	;Mark and end flags now clear?
	 TQZ <EOFF>		;Yes, clear jfn EOF flag
	TXNN IOS,BSTIMF  	;Timeout flag now clear?
	 TQZ <ERRF>		;Yes, clear jfn error flag
	CALL ULKBSQ		;Unlock port if BSP
	RET    

BSSETB:	BSNCHK!BSFTPF		;Bits that user may set
BSCLRB:	BSMRKF!BSTIMF!BSNCHK!BSFTPF	;Bits that user may clear
;Set Pup JFN for input
;Returns +1 always

XNENT PUSFI
	TQOE FILINP		;Already doing input?
	 RET			;Yes, nothing to do
	TQZ FILOUP		;Not doing output any more
	SETZRO FILNO,(JFN)	;Not doing new output any more
	RET

;Set Pup JFN for output
;Returns +1 always

XNENT PUSFO
	TQOE FILOUP		;Already doing output?
	 RET			;Yes, nothing to do
	TQZ FILINP		;Not doing input any more
	SETONE FILNO,(JFN)	;Doing new output now
	RET
;CVSKT - convert jfn to socket number

;Convert jfn to absolute network socket number
;Call:	1	;Jfn
;	CVSKT
;Returns
;	+1	;Error
;	+2	;Ok, in 2 the absolute socket number

XNENT .CVSKT,G
	MCENT
	UMOVE JFN,1
	CALLM CHKJFN
	 JRST CVSER0
	 JRST CVSER0
	 JRST CVSER0
	HLRZ T1,FILNEN(JFN)
	HRLI T1,(<POINT 7,0,35>)
	HRRZ T2,FILDEV(JFN)	;Get device
	CAIN T2,PUPDTB		;Is it a PUP JFN?
	 JRST CVPSKT		;Yes, convert it
CVSER1:	CALL UNLCKM
	SKIPA T1,[CVSKX2]
CVSER0:	MOVEI T1,CVSKX1
	JRST MRETNE

;Return local port address (including absolute socket #) (CVSKT)
;	JFN (etc.)/ already setup (see .CVSKT in NETWRK)
;	T1/ string ptr to filename string
;Returns +1 to user:  Unsuccessful, 1/ error #
;	+2 to user:  Successful,
;		user 2/ net ,, host
;		user 3/ socket

CVPSKT:	TQNN <OPNF>		;Open?
	 JRST [	CALL PNMDEC	;No, just decode filename
		 ERUNLK()	;Failed (net dir changed?)
		JRST CVPSK1]
	HLRZ UNIT,FILSKT(JFN)	;Yes, get Pup unit #
	MOVE T1,PUPLNH(UNIT)	;Pick up local net/host
	MOVE T2,PUPLSK(UNIT)	; and socket
CVPSK1:	UMOVEM T2,3		;Return socket in 3
	UMOVEM T1,2		;Return net,,host in 2
	CALL UNLCKM
	SMRETN			;Skip return to user
;ATNVT%

;Attach a full duplex BSP JFN to a PUP NVT
;Takes	AC1/	flags,,JFN
;	ATNVT%
;Returns +1 failure, T1/ error code
;	 +2 success, JFN released, T1/ line number of NVT

PATNVT::XCTU [HRRZ JFN,1]	;Get the jfn from the user
	CALLM CHKJFN		;Lock and verify jfn
	 RETERR()		;Bogus JFN
	  RETERR(ATNX7)		;TTY
	   RETERR(ATNX7)	;Byte pointer or NUL:
	CALL ATPNV		;Call workhorse routine
	IFNSK.
	  CALL UNLCKM		;Some error, unlock the JFN
	  JRST MRETNE		;Take a single, error return to caller
	ENDIF.
	CALLM RELJFN		;Release JFN
	SMRETN			;Skip return to caller
;ATNVT% (cont'd)

;ATPNV - work routine for PUP ATNVT%
;Call with JFN locked, but arguments not verified
;Returns +1 error, T1/ error code
;	 +2 success, PNV attached to BSP port

ATPNV:	HRRZ T1,FILDEV(JFN)	;Get DTB
	CAIE T1,PUPDTB		;Is it PUP:
	 RETBAD(PUPX8)		;"JFN does not refer to device PUP:"
	TQNN OPNF		;JFN is open?
	 RETBAD(ATNX17)		;"PUP connection not open"
	TQNE READF		;Open for read?
	 TQNN WRTF		;And open for write?
	  RETBAD(OPNX15)	;"Read/write access required"
	HLRZ UNIT,FILSKT(JFN)	;Get port number
	CALL LCKBSQ		;Yes, attempt to lock it
	 RETBAD(ATNX16)		;"PUP JFN does not refer to BSP port"
	CHKSTT <OPEN>		;Connection must be open
	 RETBAD(ATNX17,<CALL ULKBSP>)	;"PUP connection not open"
	TXNE IOS,BSTIMF!BSERRF	;Connection must not be timed out or in error
	 RETBAD(ATNX17,<CALL ULKBSP>)	;"PUP connection not open"
	CALL ASPNVT		;Assign Pup NVT
	 RETBAD(,<CALL ULKBSP>)	;Take failure return, error in T1
	MOVEI T1,.TTDES(T2)	;Convert line number to TTY designator
	UMOVEM T1,1		;Return it to user
	MOVX T1,.PM32		;Mode is 32-bit data mode
	STOR T1,PUPMD		;Set port's data modek
	MOVX T1,^D<8-6>		;Compute byte index for 8-bit bytes
	STOR T1,PUPBZ		;Set byte index to 8-bit bytes
	TXO IOS,BSETHF!BSNVTF	;Set "Classic Ethernet" and "NVT" flags
	CALL ULKBSP		;Unlock port
	RETSKP			;Skip return to user
;DIBE

;PUDIBE - dismiss until input buffer empty
;Takes	JFN/ already setup (see .DIBE in JSYSA)
;Returns +1 always

XNENT PUDIBE,G
PUDIB0:	CALL CHKPUP		;Check for PUP: device, lock JFN if yes
	 RET			;No, return to caller
	CALL CHKBSP		;Check BSP port, unlock JFN if bad port
	 ERROR(IOX5,,DOINT,MSEC1) ;"Device or data error"
	HRRZ T1,BSPIQL(BSP)	;Get number of input bytes queued
	CALL ULKBSP		;Unlock the BSP port
	CALL UNLCKM		;Unlock the JFN
	JUMPE T1,R		;Return now if nothing there
	MOVEI T1,PDIBET		;Input is queued, set scheduler test
	HRLI T1,(UNIT)		;Set port number 
	TXO IOS,BSINPF  	;Flag that input is queued
	MDISMS			;Block for a while
	JRST PUDIB0		;Try again

	RESCD

;PDIBET - test for input buffer empty
;Arg is Pup unit index
;Callers are: PUDIBE

PDIBET:	MOVE T2,PUPSTS(T1)	;Get port status word
	TXNE T2,BSINPF  	;Input buffer empty?
	 TXNE T2,BSTIMF!BSERRF	;No, error?
	  JRST 1(T4)		;Yes, wakeup
	JRST 0(T4)		;No, wait

	XSWAPCD
;DOBE

;PUDOBE - dismiss until output buffer empty
;Takes	JFN (etc.)/ already setup (see .DOBE in JSYSA)
;Returns +1 always

XNENT PUDOBE,G
PUDOB0:	CALL CHKPUP		;Check for PUP: device, lock JFN if yes
	 RET			;No, return
	CALL CHKBSP		;Check for good BSP JFN, unlock if bad
	 ERROR(IOX5,,DOINT,MSEC1) ;"Device or data error"
	TQNN <WRTF>		;Open for writing?
	 FILABT(IOX2,<CALL ULKBSP>) ;No, generate instruction trap
	CHKSTT <OPEN,ENDI>	;Check for reasonable state
	 JRST [	TQO <ERRF>	;Bad, give error
		RET]
	MOVE T1,FILBFO(JFN)	;Get pointer to last data
	CALL FRCBSP		;Force out partial Pup if any
	SETZM FILBCO(JFN)	;Zero byte count
	CALL CHKBOQ		;Check for empty output queue
	IFNSK.
	  CALL ULKBSP		;Not empty, unlock BSP port
	  CALL UNLCKM		;Unlock JFN
	  MDISMS		;Block
	  JRST PUDOB0		;Try again
	ENDIF.
	CALL ULKBSP		;Empty, done
	CALLRET UNLCKM		;Unlock file and return
;SIBE

;PUSIBE - Skip if input buffer empty
;Takes	JFN/ already setup (see .SIBE in JSYSF)
;Returns +1 Not a Pup JFN
;	 +2 Not empty, T1/ # of buffered bytes
;	 +3 Empty

XNENT PUSIBE,G
	CALL CHKPUP		;Check for PUP: device, lock JFN if yes
	 RET			;No, return
	AOS (P)			;At least a 1 skip return
	CALL CHKBSP		;Check for good BSP JFN, unlock if bad
	 RETSKP			;Not, give empty +3 return
	HRRZ T1,BSPIQL(BSP)	;Get # of input bytes available
	SKIPL FILBCI(JFN)	;Any bytes in current buffer?
	 ADD T1,FILBCI(JFN)	;Yes, include those too
	CALL ULKBSP		;Unlock port
	CALL UNLCKM		;Unlock JFN
	JUMPN T1,R		;+2 return if we have bytes
	RETSKP			;+3 return if empty 
;SOBE

;Skip if output buffer empty
;	JFN (etc.)/ already setup (see .SOBE in JSYSA)
;Returns +1 Empty or not a PUP JFN
;	 +2 Not empty, T1/ # of buffered bytes

XNENT PUSOBE,G
	CALL CHKPUP		;Check for PUP: device, lock JFN if yes
	 JRST RETZ		;No, return
	CALL CHKBSP		;Check for good BSP JFN, unlock if bad
	 JRST RETZ		;Not, give empty return
	HRRZ T1,BSPOQL(BSP)	;Get # of output bytes pending
	CALL ULKBSP		;Unlock BSP port
	CALL UNLCKM		;Unlock JFN and return
	JUMPE T1,R		;+1 return if empty
	RETSKP			;+2 return if not empty, T1/ byte count
;SOBF

;PUSOBF - Skip if output buffer full
;Takes	JFN/ already setup (see .SOBF in JSYSA)
;Returns +1 Not full or bad JFN/port, T1/ byte count
;	 +2 Full (next byte output would block you), T1/ byte count

IFE REL6,<PUSOBF::>
IFN REL6,<XNENT PUSBF,G>	;Name shortened.  Conflicts with PUSOBE.
	CALL CHKPUP		;Check for PUP: device, lock JFN if yes
	 JRST RETZ		;No, return, T1/ zero
	CALL CHKBSP		;Check for good BSP JFN, unlock if bad
	 JRST RETZ		;Not, give not full return, T1/ zero
IFE REL6,<AOS (P)		;Release 5.X wants triple skips>
	SKIPLE FILBCO(JFN)	;Room in current buffer?
	IFSKP.
	  CALL CHKBSO		;No, check for BSP output possible
	   AOS 0(P)		;Not possible, preset skip return
	ENDIF.
	HRRZ T1,BSPOQL(BSP)	;Return # of output bytes pending
	CALL ULKBSP		;Unlock port
	CALLRET UNLCKM		;Unlock JFN and and return to caller
;CHKPUP - check if valid pup jfn
;Used by SIBE,SOBE,DIBE,DOBE, and SOBF pup routines
;Returns +1 not a PUP JFN
;	 +2 PUP JFN, JFN locked

CHKPUP:	SAVEAC<T1>		;Preserve T1
	UMOVE JFN,1		;Get user's designator
	CALLM CHKJFN		;See if valid PUP: JFN
	 JRST UNLCKM		;No
	  JRST UNLCKM		; ...
	   JRST UNLCKM		; ...
	HRRZ T1,FILDEV(JFN)	;Get DTB
	CAIE T1,PUPDTB		;Is it PUP?
	 JRST UNLCKM		;No
	RETSKP			;Yes.

UNLCKM:	CALLM UNLCKF		;Unlock the JFN
	RET			;And return
;SMON%

;Set Pup routing table entry (function .SFROU of SMON)
;	T2/ Net number
;	T3/ Mask of bits to change
;	T4/ New value of those bits
;Returns +1:  always, 
;	T4/ contains the updated value of the routing table entry

XNENT SETRTE,G
	UMOVE T2,2
	UMOVE T3,3
	UMOVE T4,4
	CAIL T2,1		;Make sure net number in range
	CAILE T2,NPNETS
	 ITERR(SMONX2)
	NOSKED
	AND T4,T3		;Mask bits to be changed
	ANDCA T3,PUPROU-1(T2)	;Mask bits to be retained
	IOR T4,T3		;Combine
	MOVEM T4,PUPROU-1(T2)	;Put back in routing table
	OKSKED
	UMOVEM T4,4		;Give back to caller
	MRETNG			;Succeed
;SMON (cont'd)

;SETGAT - Enable/disable gateway processing (function .SFGAT)
;Previous state of gateway queue doesn't matter - if there are packets
; waiting to be transmitted, they will eventually be flushed (transmitted)
; by the background process.  Since the gateway data structure's are
; always initialized by PUPINI, starting the gateway "cold" doesn't matter.
;Takes	T2/ 0 to disable gateway, -1 to enable gateway

XNENT SETGAT,G
	UMOVE T2,T2		;Get user's flag
	MOVX T1,PP%GAT		;Get internal flag
	SKIPN T2		;Skip if non-zero
	 ANDCAM T1,PUPPAR+.PPFLG ;Zero means clear the flag
	SKIPE T2		;Skip if we just cleared the flag 
	 IORM T1,PUPPAR+.PPFLG	;Non-zero means set the flag
	MRETNG			;Good return to user
;SMON (con't)

;SETPUP - enable/disable the PUP code (.SFPUP function)
;Used to shut down PUP protocol activity under timesharing
;Note that this function does NOT shut off the MEIS!
;Takes	user T2/ on/off flags

XNENT SETPUP,G
	XCTU [SKIPN 2]		;Test user flag
	IFSKP.			;If non-zero, wants Pup on
	  SETOM PUPON		;Set PUP in service flag
	ELSE.
;	  CALL SETPOF		;Invoke protocol shutdown routine
	  CALL XSETPOF		;Invoke protocol shutdown routine
	ENDIF.
	MRETNG			;Return to user

;Here to shut off Ethernet service

XNENT SETPOF,G
	NOINT			;Don't interrupt out of this code
	SETZM PUPON		;Clear PUP on flag
	MOVSI UNIT,-NPUPUN	;Set up aobjn pointer
SETPF0: SKIPN PUPLSK(UNIT)	;Does the port exist?  (Is there a socket?)
	 JRST SETPF1		;If nothing there, try next port number
	MOVX T1,BSERRF!BSENDF	;Else get error and end received flags
	IORM T1,PUPSTS(UNIT)	;Set it (shuts down all I/O)
	HRRE T2,PUPPSI(UNIT)	;Does connection have a controlling fork?
	JUMPL T2,SETPF1		;No, go on to next connection
	LOAD T1,RECPSI		;Else get channel for PSI on packet reception
	CAIGE T1,^D36		;Is it enabled?
	 CALL PSIRQF		;Yes, wakeup process to the bad news
SETPF1:	AOBJN UNIT,SETPF0	;Loop over all connections
	OKINT			;Reallow PSI
	RET			;Return to caller
;MTOPR

;PUPMTP - PUP device-dependent operations
;Takes	JFN, STS, DEV/ already setup
;	T2/ function code
;Returns +1 need to block or error (BLKF or ERRF set)
;	 +2 sucess, results depend on operation.

XNENT PUPMTP
	TQNN <OPNF>		;Open?
	 RETSKP			;No, do nothing
	SETZRO <BLKF>		;Not blocking 
	CAIN T2,.MOEOF		;Only old code uses this function
	 MOVEI T2,.MOPEF	;Translate it for compatibility
	CAIL T2,.MOPEF		;Defined operation?
	 CAIL T2,.MOPEF+PUPMTN	; ...
	  RETSKP		;No, do nothing
	HLRZ UNIT,FILSKT(JFN)	;Setup Pup unit number
	MOVE T1,PUPMTT-.MOPEF(T2) ;Get dispatch address and flags
	HRRZ T2,T1		;Copy address, make sure it's in-section
	IFGE. T1
	  CALL (T2)		;Dispatch now if BSP not required
	  JRST PUPMT0		;Go return to caller
	ENDIF.
	CALL LCKBSQ		;See if BSP port, lock if so
	 RETSKP			;Not, do nothing
	IFXE. T1,1B1		;Do errors matter?
	  CALL 0(T2)		;No, just dispatch
	  CALL ULKBSP		;Unlock BSP port
	  JRST PUPMT0		;Go return to caller
	ENDIF.
	TXNN IOS,BSTIMF!BSERRF	;Errors matter, timeout or error?
	 CALL 0(T2)		;No, do operation
	TXNE IOS,BSTIMF!BSERRF 	;Timeout or error?
	 TQO <ERRF>		;Yes, set error flag
	CALL ULKBSP		;Unlock port
PUPMT0:	TQNN <ERRF,BLKF>	;Error or need to block?
	 RETSKP			;Neither, must have been successful
	TQNN <ERRF>		;Was there an error?
	 RET			;No, must need to block
	SETZRO <BLKF>		;Don't try to block if there is an error
	MOVEI T1,IOX5		;Code is "I/O data error"
	RET			;Take error return

;Common error return for PUP MTOPR% functions

PUPMTX:	SETONE <ERRF>		;Set error flag
	SETZRO <BLKF>		;Make sure blocking flag is cleared
	RET			;Return to caller
;MTOPR (cont'd)

;PUP MTOPR dispatch table
;B0 set =) port required to be open in BSP mode
;B1 set =) generate error PSI on timeout or error

PUPMTT:	1B0+1B1+MTSMRK		;.MOPEF Send Mark
	1B0+1B1+MTFORC		;.MOPFC Force transmission of partial Pup
	1B0+1B1+MTSINT		;.MOPIS Send Interrupt
	1B0+MTGMRK		;.MOPRM Return most recent Mark byte
	EXP MTAINT		;.MOPIN Assign interrupt channels
	1B0+MTSABT		;.MOPAB Abort connection
	1B0+MTGABT		;.MOPRA Return Abort data
	EXP MTSMOD		;.MOPSD Set hardware data mode
	EXP MTRMOD		;.MOPRD Read hardware data mode
PUPMTN==.-PUPMTT		;Number of defined functions
;MTOPR (cont'd)

;Send Mark (MTOPR function .MOPEF)
;	T3/ Mark byte

MTSMRK:	TQNN <WRTF>		;Open for writing?
	 JRST PUPMTX		;No, declare an I/O error
	CHKSTT <OPEN,ENDI>	;Check for reasonable state
	 JRST PUPMTX		;Bad, declare an I/O error
	MOVE T1,FILBFO(JFN)	;Get pointer to last data
	CALL DMPBSP		;Dump partial Pup if any
	SETZM FILBCO(JFN)	;Zero byte count
	UMOVE T1,3		;Get Mark byte from user
	CALL SNDMRK		;Send Mark
	 TQO <BLKF>		;Need to block
	RET

;Get content byte of most recently received Mark (MTOPR function .MOPRM)
;Returns user T3/ Mark byte

MTGMRK:	LOAD T1,PBMRK		;Get the byte
	UMOVEM T1,3		;Return byte to user
	RET

;Force out partial Pup (MTOPR function .MOPFC)

MTFORC:	TQNN <WRTF>		;Open for writing?
	 JRST PUPMTX		;No, declare an I/O error
	CHKSTT <OPEN,ENDI>	;Check for reasonable state
	 JRST PUPMTX		;Bad, declare an I/O error
	MOVE T1,FILBFO(JFN)	;Get pointer to last data
	CALL FRCBSP		;Force out partial Pup if any
	SETZM FILBCO(JFN)	;Zero byte count
	RET
;MTOPR (cont'd)

;Send Interrupt (MTOPR function .MOPIS)
;	T3/ Interrupt code
;	T4/ If nonzero, string ptr to Interrupt text

MTSINT:	CHKSTT <OPEN,ENDI,ENDO>	;Check for reasonable state
	 JRST PUPMTX		;Bad state, go set ERRF and return
	UMOVE T1,3		;Get code
	UMOVE T2,4		;Get string ptr if any
	TXZ T1,1B0		;1B0 is used as a flag by BLDIAB 
	CALL SNDINT		;Send Interrupt
	 TQO <BLKF>		;Need to block, T1/ scheduler test
	RET


;Assign interrupt channels (MTOPR function .MOPIN)
;      T3/ B0-5:   "Interrupt" PSI channel ( )35 =) disable)
;	   B6-11:  "Received Pup" PSI channel
;	   B12-17: "State Change" PSI channel

MTAINT:	UMOVE T1,T3		;Get user arg
	MOVEM T1,PUPPSI(UNIT)	;Store in table
	MOVE T1,FORKX		;Get currently running fork
	STOR T1,FRKPSI		;Set fork to be interrupted
	RET			;Return to caller


;Abort connection (MTOPR function .MOPAB)
;	T3/ Abort code
;	T4/ If nonzero, string ptr to Abort text

MTSABT:	MOVEI T1,E.CLST		;Generate CLOSF(T) event
	XCTU [HRRZ T3,3]	;Get Abort Code (lh=0 =) user call)
	UMOVE T4,4		;Get string ptr to text if any
	CALL PUPFSM		;Activate the FSM
	SETZM FILBCI(JFN)	;No further I/O
	SETZM FILBCO(JFN)	;?? Is this necessary ??
	RET
;MTOPR (cont'd)

;Get Abort data (MTOPR function .MOPRA)
;	T4/ If nonzero, string ptr to store Abort text
;Returns T3/ Abort code, T4/ updated pointer

MTGABT:	SKIPN PB,BSPABP(BSP)	;Get ptr to saved Abort
	 RET			;Do nothing if none
	LDB T1,[POINT 16,PBCONT(PB),15]	;Get Abort Code
	UMOVEM T1,3		;Give to user
	UMOVE T4,4		;Get user string ptr
	JUMPE T4,R		;Stop here if none
	TLC T4,-1		;Fix -1 lh
	TLCN T4,-1
	 HRLI T4,(POINT 7)
	XMOVEI T1,PBCONT(PB)	;Get address of buffer
IFE REL6,<HLL T1,[G1BPT(PUPSEC,^D8,,24)] ;"POINT 8,,15">
IFN REL6,<TXO T1,.P0815		;Set "POINT 8,,15">
	MOVEM T1,PBIPTR(PB)	;Stash extended byte pointer
	LOAD T3,PUPLEN		;Get Pup Length
	SUBI T3,MNPLEN+2	;Subtract overhead
	JUMPLE T3,MTGAB2	;Jump if none
MTGAB1:	ILDB T1,PBIPTR(PB)	;Get byte from packet
	XCTBU [IDPB T1,T4]	;Give to user
	 ERJMP MTGAB2		;Beware of ill mem refs
	SOJG T3,MTGAB1		;Repeat until exhausted
MTGAB2:	UMOVEM T4,4		;Return updated pointer
	XCTBU [IDPB T3,T4]	;Append null
	 ERJMP R		;Punt if ill mem ref
	RET
;MTOPR (Cont'd)

;MTSMOD - set hardware data mode and byte size (MTOPR function .MOPSD)
;Assumes byte size,,data mode in user AC3.  Byte size of zero means
; leave byte size alone.  If an argument is illegal, no action is
; taken for that argument.

MEIMDF:	EXP 0			;Default is no special modes

MTSMOD:	SKIPN MEIMDF		;MEIS modes okay on this system?
	 RET			;No, do nothing
	HLRZ UNIT,FILSKT(JFN)	;Get Pup unit number
	CALL LCKBSQ		;Lock port
	 JRST MTSMO0		;Not BSP, don't worry about output queue
	MOVE T1,FILBFO(JFN)	;Get pointer to last data
	CALL FRCBSP		;Force out current Pup if any
	CALL CHKBOQ		;Is output queue empty?
	IFNSK.
	  SETONE <BLKF>		;No, set blocking flag
	  CALL ULKBSP		;Unlock BSP port
	  RET			;Return to caller
	ENDIF.
MTSMO0:	XCTU [HRRZ T1,T3]	;Get user's argument
	MOVE T2,PUPPAR+.PPFLG	;Get PUP flag word
	CAIN T1,.PMASC		;ASCII data mode?
	TXNN T2,PP%10M		;And 10MB interfaces?
	 TRNA			;No to either, keep on going
	  JRST MTSMO1		;Can't use ASCII mode over 10MB interfaces
	CAIL T1,.PM16		;Range check the data mode
	 CAILE T1,.PM9		; ...
	  JRST MTSMO1		;If range check fails, leave it alone
	STOR T1,PUPMD		;Set it 
	JUMPE BSP,R		;Quit now if not BSP
	XCTU [HLRZ T1,T3]	;Get byte size
	SKIPE T1		;None give, leave as is
	 CALL ETHBYT		;Verify byte size
	  TRNA			;Bad byte size, leave as it 
	STOR T2,PUPBZ		;Set new byte size
	CALL BSP8DT		;Make sure setting of BSETHF is correct
MTSMO1:	CALL ULKBSQ		;Unlock port if it is BSP
	RET			;Return to caller

;MTRMOD - read current hardware data mode and byte size (MTOPR function .MOPRD)
;Returns byte size,,data mode in user AC3

MTRMOD:	HLRZ UNIT,FILSKT(JFN)	;Get Pup unit number
	LOAD T1,PUPMOD		;Get the Hardware data mode
	LOAD T2,PUPBSZ		;Get byte size index
	HRL T1,OWGBYT(T2)	;Get corresponding byte size
	UMOVEM T1,T3		;Pass it back to the user
	RET			;Return to caller
;GETAB%

;GTBPSI - return a word from the PUPPSI table
;We replace the fork number with the job number since only the later
; is of interest to the user programs.
;Takes	T2/ offset (already checked)
;Returns T1/ modified PUPPSI word

XNENT GTBPSI,G
	MOVE T1,PUPPSI(T2)	;Get PSI settings and PNV/Fork number
	HRRE T3,T1		;Extend sign bit if PNV
	JUMPLE T3,R		;Done if zero, -1, or a PNV
IFE REL6,<
	HLR T1,FKJOB(T3)	;Replace fork number with job number
>;IFE REL6
IFN REL6,<
	HLRZ T1,FKJOB(T3)	;Get local job number
	CALLM LCL2GL		;Convert to global index
	 SETO T1,		;Shouldn't fail, say that port is unassigned
	HLL T1,PUPPSI(T2)	;Put PSI setting in with global job number
>;IFN REL6
	RET			;Return to caller
;The following routines allows user programs to not notice that PUPBUF
; has been moved into the PUP section and that the associated monitor data
; structures have changed.

;GTBBUF - return a word from the PUPBUF table
;Takes  T2/ offset (already range checked)
;Returns T1/ PUPBUF word

XNENT GTBBUF,G
	ADD T2,[XWD PUPSEC, BSPBEG]	;Point into PUP section
	MOVE T1,(T2)		;Fetch word
	RET			;Return to caller

;GTBLNH - return a word from the PUPLNH table
;Takes  T2/ offset (already range checked)
;Returns T1/ PUPBUF word

XNENT GTBLNH,G
	LOAD T1,PRTLN,(T2)	;Get local net
	LSH T1,^D28		;Shift into place
	LOAD T3,PRTLH,(T2)	;Get local host
	LSH T3,^D20		;Shift into place
	IOR T1,T3		;Form <net>B7+<host>B15
	HRR T1,PUPBSP(T2)	;Insert BSP data block offset
	RET			;Return to caller

;GTBFPT - return a word from the PUPFPT table
;Takes  T2/ offset (already range checked)
;Returns T1/ PUPBUF word

XNENT GTBFPT,G
	MOVE T1,PUPFPT(T2)	;Get pointer into PUP section
	JUMPE T1,R		;Quit now if fully wild
	MOVN T2,0(T1)		;Get negated length
	HRLI T1,(T2)		;Form -length,,address
	RET			;Return to caller
;PNMDEC - decode pup name string

;Decode Pup name string
;	T1/ String pointer to name
;Returns +1  Unsuccessful, T1/ error code
;	 +2  Successful:
;		T1/ net,,host (0 =) wildcard)
;		T2/ socket (right-justified)
;Clobbers T1-T4

PNMDEC:	STKVAR <PNMPTR,<ADRTBL,BSPSIZ>>	;Declare local storage
	MOVEM T1,PNMPTR		;Save string ptr
	MOVEI T2,ADRTBL		;Set address tabel location for PUPNM
	MOVEI T4,"U"		;Default mode is user-relative
	ILDB T3,T1		;Get first char
	CAIE T3,0		;Empty string?
	CAIN T3,"!"		;Or just mode specifier?
	 JRST [	SETZM 0(T2)	;Yes, default all fields
		SETZM 1(T2)
		HRLI T2,2	;Say just one address input
		JRST PNMDE1]	;Handle mode if any
	MOVE T1,PNMPTR		;Non-null, recover string ptr
	HRLI T2,(PN%NAM+<BSPSIZ>B17) ;Name to address, set size
	PUPNM%			;Translate string to address(es)
	 RETBAD()		;Error
	LDB T3,T1		;Ok, get terminator
PNMDE1:	CAIE T3,"!"		;Mode being given?
	 JRST PNMDE2		;No
	ILDB T4,T1		;Yes, get mode specifier
	SKIPN T4		;Make sure not null
PNMDE0:	 RETBAD(PUPX7)		;"Source address incorrect"
	ILDB T3,T1		;Get terminator
PNMDE2:	JUMPN T3,PNMDE0		;Error if non-null
	HLRZ T3,T2		;Ok, get returned adr tbl length
	CAIG T3,BSPSIZ		;Make sure block was big enough
	 JRST PNMD20		;Yes
	BUG.(CHK,PNMDEA,PUP,SOFT,<PUP - BSPSIZ too small for address table>)
	MOVEI T3,BSPSIZ		;Use only what we have
PNMD20:	MOVN T3,T3		;Negate
	HRLI T2,(T3)		;Make AOBJN ptr to address table
;PNMDEC (cont'd)

;Now have T2/ -length,,address of address table
;	  T4/ Mode character for local socket defaulting (not yet checked).
;We now determine the local socket number.
;The original Tenex code used JFN*8 for the lower 15 bits of a relative
; socket number.  If a process on the local host quickly reuses a JFN
; in reconnecting to a foreign host, it is possible for the foreign ICP
; server to consider the resulting RFC to be a duplicate.  We avoid
; that problem by using the lower 15 bits from the JFN and a piece
; of TODCLK to form the local socket number. The TODCLK portion is in units
; of roughly a half second and repeats itself about once every four minutes.

	MOVE T1,1(T2)		;Get local socket from first entry
	CAIN T4,"A"		;Absolute socket wanted?
	 JRST PNMDE5		;Yes, skip the relative socket gyrations
	IFE. T1			;If no socket specified, must cobble up one
	  LDB T1,[POINT 9,TODCLK,26]	;Get about four minutes worth of TODCLK
	  PUSH P,T2		;Preserve T2
	  MOVEI T2,(JFN)	;Get internal format JFN
	  IDIVI T2,MLJFN	;Convert to external format (T3 clobbered!)
	  ANDI T2,77		;Ensure only six bits
	  LSH T2,^D9		;Slide JFN over a bit
	  IORI T1,(T2)		;Compose low 15 bits of local socket
	  POP P,T2		;Restore clobbered AC
	ENDIF.
	CAIE T4,"J"		;Want job or user-relative?
	CAIN T4,"U"		; ...
	 CAILE T1,77777		;Yes, can only specify these bits
	  RETBAD(PUPX7)		;Bad bits or mode char - "Src addr incorrect"
	CAIE T4,"J"		;Job or user relative socket wanted?
	IFSKP.
IFN REL6,<
	  MOVE T3,GBLJNO	;Job relative, get global job number
>;IFN REL6
IFE REL6,<
	  MOVE T3,JOBNO		;Job relative, get job number
>;IFE REL6
	  ADDI T3,^D100000	;Add offset
	ELSE.
	  MOVE T3,JOBNO		;User relative, get local job number
	  SKIPN T3,JOBDIR(T3)	;Get logged in directory number
	   HRRZ T3,JSBSDN	;Not logged in, use connected directory
	ENDIF.
	LSH T3,^D15		;Use this for high-order bits
	IOR T1,T3		;Compose local socket number
;PNMDEC (cont'd)

;Now T1/ absolute local socket, T2/ -length,,adr of address table.
;Scan the address table and (1) make sure that any nonzero
; net/host entries specify a real local address, (2) make sure
; all socket specifications are the same, and (3) make net/host
; wildcard if appropriate.

PNMDE5:	MOVE T3,1(T2)		;Get socket # of this entry
	CAME T3,1+ADRTBL	;Consistent with first?
	 RETBAD(PUPX24)		;No, "Invalid source socket"
	SKIPN T3,0(T2)		;Get specified net/host
	 JRST PNMDE7		;Zero means default, always ok
	HLRZ T4,T3		;Get net
	CAIL T4,1		;Check bounds
	 CAILE T4,NPNETS
	  RETBAD(PUPX20)	; "Network number out of range"
	HRRZ T4,PUPROU-1(T4)	;Ok, get our address on that net
	SKIPN T4		;Skip if we're on the net
	 RETBAD(PUPX26)		;Not - "Invalid source net"
	TXNN T3,.RHALF		;Host specified?
	 JRST [	HRRM T4,0(T2)	;No, substitute default
		JRST PNMDE7]
	CAIE T4,(T3)		;Yes, correct?
	 RETBAD(PUPX25)		;No, "Invalid source host"
PNMDE7:	MOVE T3,0(T2)		;This net/host same as first?
	CAME T3,0+ADRTBL
	 SETZM 0+ADRTBL		;No, make fully wildcard
	AOBJN T2,.+1		;Repeat for all adr tbl entries
	AOBJN T2,PNMDE5
	MOVE T2,T1		;Copy socket into T2
	MOVE T1,0+ADRTBL	;Get net,,host into T1
	RETSKP			;Skip return
;PEXDEC - decode pup extension string

;Decode Pup extension string
;	T1/ String pointer to name
;Returns +1  Unsuccessful, T1/ error code
;	 +2  Successful, T1/ address of address block
;		Note that the first word of the address block contains
;		the negated length of the table.  The actual table
;		starts at 1(T1).
;Clobbers T1-T4

PEXDEC:	STKVAR <PEXPTR,<PEXSTG,BSPSIZ>>	;Declare local storage
	MOVEM T1,PEXPTR		;Save string ptr
	SETZM PEXSTG		;Clear first word of temporary storage
	MOVSI T1,PEXSTG		;Set up BLT pointer to clear entire block
	HRRI T1,1+PEXSTG	; ...
	BLT T1,BSPSIZ-1+PEXSTG	;Zero it.
	MOVE T1,PEXPTR		;T1/ string pointer
	MOVEI T2,1+PEXSTG	;T2/ address of data block
	MOVE T3,T1		;Make a copy of the string pointer
	ILDB T3,T3		;Get first character
	JUMPE T3,PEXDE0		;Take a short cut if null
	HRLI T2,(PN%NAM+<BSPSIZ>B17) ;Name to address, set size of data block
	PUPNM%			;Translate string to address(es)
	 ERJMP R		;Error, return with T1/ error code
	LDB T3,T1		;Ok, get terminator
	SKIPE T3		;Skip if null
	 RETBAD(GJFX4)		;"Invalid character in filename"
PEXDE1:	HLRZ T2,T2		;Get returned address table length
	CAIG T2,BSPSIZ		;Make sure block was big enough
	 JRST PEXDE2		;Yes
	  BUG.(CHK,PEXDEA,PUP,SOFT,<PUP - BSPSIZ too small for address table>)
	SKIPA T2,[BSPSIZ]	;Use only what we have
PEXDE0:	 MOVEI T2,2		;Say one address input if null string 
PEXDE2:	MOVEM T2,0+PEXSTG	;Store in "header" word
	CALL ASGBSP		;Assign some swappable storage
	 RETBAD(MONX01)		;No room, "Insufficient system resources"
	MOVE T4,T1		;Copy block pointer into T4 for safety
	MOVEI T1,BSPSIZ		;T1/ size of block
	XMOVEI T2,PEXSTG	;T2/ source address
	MOVE T3,T4		;T3/ destination address
	CALL XBLTA		;Copy the block
	MOVE T1,T4		;T1/ address of address block
	RETSKP			;Done, take skip return
;Miscellaneous JSYS routines

;CHKBSP - Check for open BSP port in good state
;Takes	JFN/ locked PUP JFN
;Returns +1  Not open, not BSP, or timed out or aborted.  JFN is unlocked
;	 +2  Ok, port locked

CHKBSP:	HLRZ UNIT,FILSKT(JFN)	;Get Pup unit #
	TQNE <OPNF>		;File open?
	 CALL LCKBSQ		;Yes, check for BSP and lock it
	  JRST UNLCKM		;Not open or not BSP, return +1, JFN unlocked
	TXNN IOS,BSTIMF!BSERRF	;Open, in good state?
	 RETSKP			;Yes, return +2
	CALL ULKBSP		;No, unlock port
	TQO <ERRF>		;Report error
	CALLRET UNLCKM		;Unlock the JFN and return 

;Unlock BSP port and set JFN blocking flag
;Returns +1 always, T1/ scheduler test, BLKF set

ULKWAT:	CALL ULKBSP		;Unlock the port
	SETONE <BLKF>		;Request blocking at higher levels
	RET			;Take a single return, T1/ scheduler test
SUBTTL Raw Packet I/O 

;PUPI

;Input Pup in raw packet mode
;	1/	B0: Never dismiss for I/O, give PUPX3 error instead
;		B1: Check Pup Checksum, give PUPX5 error if bad
;		B2: Perform source address check, give PUPX7 error
;		    if incorrect
;		B3: MEIS headers
;		B4: block for up to 50 secs if no input available
;		RH: JFN for port open in raw packet mode
;	2/	LH: Length of user block (36-bit words)
;		RH: Address of user block
;Returns +1 Failure, T1/ Error code
;	 +2 Success

XNENT .PUPI,G
	MCENT			;Enter jsys code
	STKVAR <PUSRL,PUERRC>	;Declare local storage
.PUPI1:	CALL SETRAW		;Setup and check arguments
	TQNN <READF>
	 ERUNLK(IOX1)		;Not open for reading
	MOVEM T4,PUSRL		;Remember length of user buffer
	CALL GETPUP		;Get Pup from input queue
	 JRST .PUPI2		;Empty, back out and wait for input
	SETZM PUERRC		;No error code yet
	TXNN E,PU%CHK  		;Want checksum checked?
	IFSKP.
	  CALL CHKCKS		;Check the checksum
	  IFNSK.
	    MOVEI T1,PUPX5	;Bad checksum, get error code
	    MOVEM T1,PUERRC	;Save it 
	  ENDIF.
	ENDIF.
	TXNN E,PU%SRC  		;Want source address check?
	IFSKP.
	  CALL CHKSRC		;Yes, check for correct source adr
	  IFNSK.
	    MOVEI T1,PUPX7	;Bad, save error code
	    MOVEM T1,PUERRC	; ...
	  ENDIF.
	ENDIF.
	CALL GETLEN		;T1/ Get pup length in words
	CAMLE T1,PUSRL		;Will we fit in the user's buffer?
	 ERUNLK(PUPX1,<CALL RELPKT>) ;No, give error and quit.  Can't recover
	XMOVEI T2,PBHEAD(PB)	;T2/ Source address in monitor space
	HRRZ T3,E		;T3/ Destination address in user space
	TXNE E,PU%MEI		;User wants 32-bit mode headers?
	 CALL TNXT20		;No, convert to 16-bit mode headers
	CALL BLTMU1		;Transfer to user
	CALL RELPKT		;All ok, release the buffer
	CALL UNLCKM		;Unlock file
	SKIPE T1,PUERRC		;Load error code, skip if none
	 RETERR()		;Take an error return back to the user
	SMRETN			;Skip return to user
;PUPI (cont'd)

;Here if the input queue was empty.  Check if the user specified a timeout.

.PUPI2:	CALL UNLCKM		;Unlock the JFN
	TXNE E,PU%NOW		;Want return on failure?
	 RETERR(PUPX3)		;Yes, do so.
	SETZ T1,		;Clear this in case we go to PUPI3
	TXNN E,PU%TIM		;Timeout specified?
	 JRST .PUPI3		;No, assume waiting forever
	TXO E,PU%NOW		;Yes, set to fail immediately on next call
	XCTUU [HLLM E,1]	; (this implements the timeout)
	UMOVE T1,3		;Get timeout
	CAILE T1,^D50000	;Limit to 50 seconds
	 MOVEI T1,^D50000	; ...
	ADD T1,TODCLK		;Compute ending time
	ADDI T1,177		;Round up to next unit of 128 ms
	TRZ T1,177		; ...
	LSH T1,^D20		;B0-8 := (ending time / 128) mod 512
.PUPI3:	TLO T1,(UNIT)		;Insert Pup port index
	HRRI T1,PUPIWT		;Scheduler test routine
	MDISMS			;Block for a while
	JRST .PUPI1		;Try again
;PUPIWT - test for for PUPI% input ready or timeout
;Arg is timeout interval and port number
;Callers are: .PUPI

	RESCD

PUPIWT:	LDB T2,[POINT 9,T1,35]	;Get Pup port index
	LSH T2,1		;PUPIBQ is a doubleword table
	XMOVEI T3,PUPIBQ(T2)	;Get address of queue head
IFN REL6,<HRLI T3,XCDSEC	;Get address of queue head>
	CAME T3,PUPIBQ(T2)	;Empty?
	 JRST 1(T4)		;No, wakeup
	TRZ T1,777		;Flush port number
	JUMPE T1,0(T4)		;Keep blocking if no timeout specified
	LSH T1,-2		;Shift (ending time/128) mod 512 into position
	SUB T1,TODCLK		;Compute (then - now) mod 512
	ANDI T1,177600		; ...
	CAIG T1,^D50000		;Expired?
	 JRST 0(T4)		;No
	JRST 1(T4)		;Yes, awaken

	XSWAPCD
;PUPO

;.PUPO - Output Pup in raw packet mode
;	T1/	B0: Never dismiss for I/O, give PUPX3 error instead
;		B1: Compute Pup Checksum
;		B3: Data already in MEIS format
;		RH: JFN for port open in raw packet mode
;	T2/	LH: Length of user block (36-bit words)
;		RH: Address of user block
;Returns +1  Unsuccessful, T1/ Error number
;	 +2  Successful

XNENT .PUPO,G
	MCENT			;Enter jsys code
	STKVAR <USRLEN>		;Declare local storage
.PUPO0:	CALL SETRAW		;Setup and check arguments
	TQNN <WRTF>		;Open for write?
	 ERUNLK(IOX2)		;No, return error
	MOVE T2,[XCTUU [HLRZ T1,(T2)]]	;Section zero instruction
	XSFM T1			;Get PC flags,,previous context section
	TXNE T1,.RHALF		;Any section bits?
	 MOVE T2,[XCTUU [HLRZ T1,@[IFIW!T2]]]	;Yes, use non-zero section ins
	XCT T2			;Get first 16-bit word of header (length)
	TXNN E,PU%MEI		;MEIS-style headers?
	LSH T1,-2		;No, Tenex-style - must slide right two bits
	CAIL T1,MNPLEN		;Check for legal length
	 CAILE T1,MXPLEN
	  ERUNLK(PUPX1)		;Size error
	LOAD T2,PUPMOD		;Get data mode of this port
	CALL GETLN		;Compute number of words in the pup
	CAILE T1,(T4)		;Check length consistency
	 ERUNLK(PUPX1)		;User block too short for Pup length
	MOVEM T1,USRLEN		;Stash number of words in pup (header and data)
	ADDI T1,PBHEAD		;Ok, include overhead in size
	CALL ASGPKT		;Allocate packet buffer
	IFNSK.
	  CALL UNLCKM		;Can't.  Unlock the JFN
	  TXNE E,PU%NOW		;Are we allowed to block?
	   RETERR(PUPX3)	;No.
	  MDISMS		;Yes, block for a bit
	  JRST .PUPO0		;Try again
	ENDIF.
	MOVE T1,USRLEN		;Number of words in pup
	HRRZ T2,E		;Source (user)
	XMOVEI T3,PBHEAD(PB)	;Destination PUP buffer
	CALL BLTUM1		;Copy it
	TXNE E,PU%MEI		;MEIS-style headers?  (16-bit mode)
	CALL T20TNX		;No, convert to internal format (32-bit mode)
;PUPO (cont'd)

;Substitute defaults for zero elements in the Pup destination
	SKIPN T4,PUPFPT(UNIT)	;Get foreign port descriptor
	 MOVEI T4,[EXP 0,0]-1	;None, default all zeroes
	LOAD T1,PUPDN		;Destination net
	JUMPN T1,PUPO1		;Jump if specified
	HLRZ T1,1(T4)		;Unspecified, get default
	SKIPN T1		;Skip if we have a default net
	 LOAD T1,DEFNET		;We don't, use system default
	STOR T1,PUPDN		;Store replacement value
PUPO1:	CAIL T1,1		;Net number in bounds?
	 CAILE T1,NPNETS	; ...
	  ERUNLK(PUPX20,<CALL RELPKT>)	;Give network no out of range error 
	LOAD T2,PUPDH		;Destination host
	JUMPN T2,PUPO3		;Jump if specified
	HRRZ T2,1(T4)		;Unspecified, get default
REPEAT 0,<	;;Always allow broadcasts onto other subnets.
	JUMPN T2,PUPO2		;Use default if not wildcard
	MOVE T3,PUPROU-1(T1)	;Wildcard host. Must check net table...
	TXNN T3,BROADF		;Broadcast allowed on that network?
	 ERUNLK(PUPX23,<CALL RELPKT>)	;No, give error
	SKIPA			;Leave field zero if broadcasting
>;REPEAT 0
PUPO2:	 STOR T2,PUPDH		;Store replacement value
PUPO3:	CALL GETPDS		;Destination socket
	JUMPN T1,PUPO4		;Jump if specified
	MOVE T1,2(T4)		;Unspecified, get default
	JUMPE T1,[ERUNLK(PUPX22,<CALL RELPKT>)]	;Error if multiple or wildcard
	CALL SETPDS		;Store replacement value
;PUPO (cont'd)

;Check that the Pup source is consistent with the local port
;and our network address, and default elements where necessary.
PUPO4:	LOAD T1,PUPSN		;Get source net from Pup
	LOAD T2,PRTLN,(UNIT)	;Get local net from port
	JUMPN T1,PUPO5		;Net specified in Pup?
	SKIPE T1,T2		;No, get from port specification
	IFSKP.
	  LOAD T1,PUPDN		;None there either.  Get dest net
	  MOVE T2,PUPROU-1(T1)	;Get entire routing entry
	  TXNN T2,.RHALF	;Directly connected?
	   LOAD T1,ROUNET,(T1)	;No, use immediate gateway net
	  SKIPGE T2		;Network is accessible?
	   LOAD T1,DEFNET	;No, use default directly-connected net
	  STOR T1,PUPSN		;Set up source net
	  JRST PUPO6		;Go range check our net number
	ENDIF.
	STOR T1,PUPSN		;Default source net in Pup
PUPO5:	CAME T1,T2		;Pup and port agree or port wildcard?
	 JUMPN T2,[ERUNLK(PUPX26,<CALL RELPKT>)] ;No, invalid source net
PUPO6:	CAIL T1,1		;Perform range check on network number
	 CAILE T1,NPNETS	; ...
	  ERUNLK(PUPX20,<CALL RELPKT>)	;Network number out of range
	LOAD T3,NETADR,(T1)	;Get our site's address on this net
	SKIPN T3		;Skip if we're on the net
	 ERUNLK(PUPX21,<CALL RELPKT>)	;Destination host inaccessible
	LOAD T1,PUPSH		;Get source host from Pup
	LOAD T2,PRTLH,(UNIT)	;Get local host from port
	JUMPE T1,PUPO7		;Host specified in Pup?
	CAME T1,T3		;Yes, agree with our host address?
	 ERUNLK(PUPX25,<CALL RELPKT>)	;No, invalid source host
PUPO7:	JUMPE T2,PUPO8		;Host specified in port?
	CAME T2,T3		;Yes, agree with our host address?
	 ERUNLK(PUPX25,<CALL RELPKT>)	;No, invalid source host
PUPO8:	STOR T3,PUPSH		;Deposit required sending host number
	CALL GETPSS		;Get source socket from Pup
	JUMPN T1,PUPO9		;Socket specified in Pup?
	MOVE T1,PUPLSK(UNIT)	;No, get local socket from port
	CALL SETPSS		;Default source socket in Pup
	JRST PUP10		;Join code to send pup
PUPO9:	CAME T1,PUPLSK(UNIT)	;Pup and port agree?
	 ERUNLK(PUPX24,<CALL RELPKT>)	;No, invalid source socket
;PUPO (cont'd)

;Done defaulting address fields - compute checksum and send the pup
PUP10:	MOVX T1,NILCHK		;Default checksum is nil
	TXNE E,PU%CHK  		;Want checksum generated?
	 CALL PUPCKS		;Yes, do so
	STOR T1,PUCHK		;Store the checksum
	CALL PUTPUP		;Queue packet for output
	 ERUNLK()		;Error, code already in T1, packet released
	CALL UNLCKM		;Unlock file
	SMRETN			;Skip return to user
;SETRAW - Common setup code for PUPI and PUPO
;Takes	T1/ User's ac1
;Returns +1
;	UNIT/ Pup unit number
;	E/ lh: Flags (from lh of user's ac1)
;	   rh: Block location (from rh of user's ac2)
;	T4/ Block size (lh of user's ac2)
;Returns +1 success, does not return if error

SETRAW:	SKIPN PUPON		;PUP on?
	 RETERR(ETHRX1)		;No, blow up connection
	XCTU [HRRZ JFN,1]	;Get JFN
	CALLM CHKJFN		;Check it
	 RETERR()		;Bad JFN
	 ERUNLK(DESX4)		;TTY not legal
	 ERUNLK(DESX4)		;String pointer not legal
	TQNN <OPNF>		;Test file status
	 ERUNLK(DESX5)		;Not open
	HRRZ T1,FILDEV(JFN)	;Check device
	CAIE T1,PUPDTB
	 ERUNLK(PUPX8)		;Not device PUP:
	MOVE T1,STS
	ANDI T1,17
	CAIE T1,16		;Mode 16?
	 ERUNLK(PUPX4)		;No, user said BSP processing
	UMOVE E,2		;Get user block length,,adr
	HLRE T4,E		;Isolate length
	XCTU [HLL E,1]		;Put flags in lh of E
	MOVEI T1,(E)		;Copy block address
	ADDI T1,-1(T4)		;Compute last address
	CAIL T4,<MNPLEN+3>/4	;Error if smaller than Pup header
	CAILE T1,777777		;Error if cross end of memory
	 ERUNLK(PUPX1)		;Size error
	HLRZ UNIT,FILSKT(JFN)	;Get Pup unit
	RET			;Return to caller
;TNXT20 - convert pup header from .PM32 to .PM16
;For compatibility with Tenex software
;Takes PB/ pointer to packet buffer
;Returns +1 always
;Clobbers nothing

TNXT20:	SAVET			;Save temporaries
	MOVEI T3,5		;Set up count
	XMOVEI T4,PBHEAD(PB)	;Address of first header word
TNXT2:	LDB T1,[POINT 16,(T4),15]	;Pick them up
	LDB T2,[POINT 16,(T4),31]
	SETZM (T4)		;Clear the word
	DPB T1,[POINT 16,(T4),17]	;Set them down
	DPB T2,[POINT 16,(T4),35]
	ADDI T4,1		;Point to next word
	SOJG T3,TNXT2		;Loop over header
	RET			;Return to caller


;T20TNX - convert pup header from .PM16 to .PM32
;For compatibility with Tenex software
;Takes PB/ pointer to packet buffer
;Returns +1 always
;Clobbers nothing

T20TNX:	SAVET			;Save temporaries
	MOVEI T3,5		;Set up count of header words
	XMOVEI T4,PBHEAD(PB)	;Address of first word
T20TN:	LDB T1,[POINT 16,(T4),17] ;Pick them up
	LDB T2,[POINT 16,(T4),35] ; ...
	SETZM (T4)		;Clear the word
	DPB T1,[POINT 16,(T4),15] ;Put them down
	DPB T2,[POINT 16,(T4),31] ; ...
	ADDI T4,1		;Point to next word
	SOJG T3,T20TN		;Loop over header
	RET			;Return to caller
SUBTTL Pup Background Process, Initialization Code

;Routine to start Pup background process
;Called only once during system initialization
;Returns +1
;Clobbers T1, T2

XNENT PUPBEG,G
	MOVX T1,CR%CAP		;Transmit capabilities
	CFORK%			;Create fork
	 BUG.(HLT,PUPBEA,PUP,SOFT,<PUP - Can't create Pup background fork>)
	XMOVEI T2,PUPBAK	;Start address of background fork
	MSFRK%			;Start in monitor mode
	RET			;Return to caller

;Background process starts here.  Note that we are running this fork in
;queue zero.  This means that this fork will not be descheduled until
;it blocks -- that means **NO ONE** will be scheduled to run until the fork
;blocks.  There is defensive code to detect and correct such bugs, but
;the programmer should be aware of the dangers of a queue zero fork when
;working on the background tasks and locks.

;Note that JP%SYS is inadequate for determining the background fork's priority.
;On highly loaded systems a fork with such priority can end up be scheduled
;out for long periods of time -- long enough for connections to time out!

PUPBAK:
IFE REL6,<
	MOVX T1,UMODF		;Need to set up fork's context
	MOVEM T1,FPC		;Fake a return PC
>;IFE REL6
IFN REL6,<
	MOVX T1,USRCTX		;Init context
	MOVEM T1,FFL		;Set up flags (user mode)
	SETZM FPC		;Set up PC word (location 0)
>;IFN REL6
	MCENTR			;Establish JSYS context
IFE REL6,<SE1ENT		;Run in section one>
IFE REL6,<MOVE T1,[XWD ITFPC,PUPUXI] ;Trap fatal interrupts>
IFN REL6,<MOVE T1,[XWD XCDSEC,PUPUXI] ;Trap fatal interrupts>
	MOVEM T1,MONBK
	MOVE T1,CHNSON
	MOVEM T1,MONCHN
	MOVX T1,.FHSLF
	MOVEI T2,1		;Run in queue zero
	SPRIW%
	CALL PUPINI		;Initialize all Pup data
	MOVE T1,FORKX		;Record our fork number
	MOVEM T1,PUPFRK
	CALL GETIRT		;Get and discard incremental runtime
;Main loop of background process

PUPBK1:	SETZ T1,		;New value for PUPFLG
	EXCH T1,PUPFLG		;Exchange new for old
	MOVEM T1,PUPFL1		;Set requests to do this time around
PUPBK0:	MOVE T1,PUPFL1		;Get requests to do this run
	JFFO T1,PUPBK2		;Any requests in?
	JSP T4,PUPBKT		;Re-check timers
	 TRNA			;Nothing to do, skip into dismiss
	  JRST PUPBK1		;Back to do next request
PUPBK3:	SKIPN PUPFL2		;Deferred scans to be done after we wakeup?
	IFSKP.
	  MOVE T1,TODCLK	;Yes, get present time
	  ADDI T1,^D400		;Set timer to a little in the future
	  MOVEM T1,DEFTIM	;Wakeup to look at those deferred flags
	ENDIF.
	MOVEI T1,PUPBKT		;Scheduler test
	HDISMS (^D500)		;Dismiss, but stay in balance set for a while
	SKIPN T1,PUPFL2		;Get any deferred task flags
	IFSKP.
	  IORM T1,PUPFLG	;Do those tasks this pass
	  SETZM PUPFL2		;Clear the deferred task flag word
	  HRLOI T1,377777	;Get +INF
	  MOVEM T1,DEFTIM	;Reset timeout for deferred scans
	  TXNN T1,PBFNVT	;Was one of those a deferred NVT scan?
	  IFSKP.
	    MOVSI T2,-NPUPPN	;Yes, loop over all words in table
PUPBK4:	    MOVE T1,PUPPND(T2)	;Get deferred word
	    SETZM PUPPND(T2)	;Clear table entry
	    IORM T1,PUPPNV(T2)	;OR it into the current word
	    AOBJN T2,PUPBK4	;Loop until done
	  ENDIF.
	ENDIF.
	SETZM PUPFL3		;Clear count of tasks run since last block
	JRST PUPBK1		;Back to top of loop

;Here when have a request to process, T2 contains flag position

PUPBK2:	AOS T1,PUPFL3		;Count a task
	CAIGE T1,MAXTSK		;Run too many tasks without blocking?
	IFSKP.
	  BUG.(CHK,PUPRUN,PUP,SOFT,<PUP - background fork monopolizing system>)
	  JRST PUPBK3		;Force a dismiss to let other forks run
	ENDIF.
	MOVE T1,BITS(T2)	;Clear the bit
	ANDCAM T1,PUPFL1	;In the flag register
	CAIL T2,PUPBKN		;Make sure task number in range
	 JRST [ BUG.(CHK,PUPBKA,PUP,SOFT,<PUP - task number out of range>)
		JRST PUPBK0]	;Generate a bugchk and loop
	MOVE T1,TODCLK		;Get present time
	MOVEM T1,PBPTIM		;Save it
	PUSH P,T2		;Save task number
	CALL @PBKTAB(T2)	;Perform the task
	CALL GETIRT		;Get incremental runtime
	POP P,T2		;Restore task number
	ADDM T1,PBKTIM(T2)	;Accumulate time used by task
	AOS PBKCNT(T2)		;Count number of task executions
	MOVE T3,TODCLK		;Get present time
	SUB T3,PBPTIM		;Find out how long we were in the task
	CAIL T3,^D2000		;Should never have been more than two seconds
	BUG.(CHK,PUPSTL,PUP,SOFT,<PUP - background fork stalled>)
	JRST PUPBK0		;Loop back for next task
;Here on fatal interrupt

PUPUXI:	BUG.(CHK,PUPBKB,PUP,SOFT,<PUP - Fatal error interrupt, continuing>)
IFE REL6,<SE1ENT		;Make sure we're in section one>
	MCENTR			;Re-establish monitor context
	JRST PUPBK1		;Restart at top of loop

COMMENT \

The PUPFLx flag words and counters are used for the following purposes.

PUPFLG - Normal request that a task be run.

PUPFL1 - Copy of PUPFLG.  Used to make sure that all tasks requested in
	 PUPFLG are run and that one task does not lock out the others.
	 It is possible, for example, that an active FTP connection could
	 keep setting the BSP bit and end up locking out the NVT processing.

PUPFL2 - Deferred task request.  Since the background process runs in queue
	 zero, it will be descheduled only when it blocks.  If, for example,
	 the pup fork failed to obtain the NVTLCK and wanted to defer the task,
	 setting the NVT task request in PUPFLG would result in a deadlock.
	 The pup fork would keep trying to obtain the lock and as a result
	 never block.  The fork holding the lock would never release the lock
	 since it would never be scheduled to run.  The use of PUPFL2 is
	 intended to prevent this type of deadly embrace.

PUPFL3 - Count of tasks run since the pup fork last dismissed.  Used to
	 detect and recover from bugs that would resulting from the pup
	 fork running too long in queue zero.
\
;PUPBKT - Scheduler test for Pup background process wakeup
;Call JSP T4,PUPBKT
;Returns +1 no work or too early to work
;	 +2 work to be done

	RESCD

PUPBKT:	MOVE T2,TODCLK		;Get now
	HRRZ T3,PUPTQH		;Get head of timer queue
	CAML T2,PUPTQD(T3)	;Time to service request?
	 JRST [	SIGPBP(BSP)	;Yes, set flag
		JRST 1(T4)]	;Wakeup
	CAML T2,SYNTIM		;Time to check syncs?
	 JRST [	SIGPBP(SYN)	;Yes, set flag
		JRST 1(T4)]	;Wakeup
	SKIPE PNVFLG		;PNV I/O scans requested?
	 JRST [ SIGPBP(NVT)	;Yes, set flag
		JRST 1(T4) ]	;Wakeup
	CAML T2,DEFTIM		;Time to look at deferred requests? 
	 JRST 1(T4)		;Yes, wakeup
	SKIPN PUPFLG		;Any requests in?
	 JRST 0(T4)		;No
	JRST 1(T4)		;Yes, wakeup

	XSWAPCD
;Definition of background task requests

DEFINE PBTASK(BIT,ROUTINE) <
	PBF'BIT==1B<.-PBKTAB>	;; Assign bit number
	DSP(ROUTINE)		;; Assemble dispatch
>

;Dispatch table
;Make sure none of these routines can block for a long period of time.

PBKTAB:	PBTASK(GCS,GCPLSK)	;GC local socket table
	PBTASK(BSP,BSPBAK)	;Do BSP background processing
	PBTASK(NVT,PU7NVT)	;Scan for NVT input/output
	PBTASK(SYN,SYNCHK)	;Check for sync timeout errors
	PBTASK(GAT,GATCHK)	;Process Gateway input queue
IFN <.-PBKTAB>-PUPBKN,<PRINTX PUPBKN value doesn't match table length>

;GETIRT -  get incremental runtime (since last call)
;Returns +1, T1/ Runtime since last call (ms)
;Clobbers T1

	XRESCD

GETIRT:	NOSKED
	MOVE T1,FKRT		;Get total runtime charged to fork
	SUBM T1,PBKRT		;Compute time since last update
	EXCH T1,PBKRT		;Save new total, get delta time
	OKSKED
	RET    

	XSWAPCD


;BSPBAK - Process requests on timer queue
;Called from background process
;Returns +1 always
;Clobbers nearly everything

BSPBAK:	CALL REMTQP		;Remove request from head of queue
	 RET			;No more pending requests
	CALL DOBSP		;Got one, do BSP processing for it
	JRST BSPBAK		;Repeat until run out
;GATCHK - process the gateway queue
;Called from the background task
;Returns +1 always
;Clobbers nearly everything

GATCHK:	STKVAR <GATLIM>		;Declare local storage
	MOVEI T1,MAXQDI		;Maximum number of packets we will process
	MOVEM T1,GATLIM		;Prevents gateway from hogging the machine
	MOVEI UNIT,GATPRT	;Set up port number for the gateway queue
GATCH0:	CALL GETPUP		;Get a packet
	 RET			;Queue is empty, return
	LOAD T1,PUPDN		;Get destination network
	SKIPGE PUPROU-1(T1)	;Get routing information, skip if accessible
	 JRST GATCHX		;Yes, discard packet if inaccessible
	LOAD T1,PUPTCB		;Get TC byte
	TRZ T1,360		;Isolate hop count (bits 0-3)
	ADDI T1,1		;Increment hop count
	CAIL T1,MAXHOP		;Hop count exceeded?
	 JRST GATCHX		;Yes, discard the packet
	DMOVE T2,[STRPTR<PUPTCB> ;Pointer to header field
		  STRPTR<PUCHK>] ;Address of checksum
	CALL UPDCKS		;Update the checksum
	CALL PUTPUP		;Send packet to physical I/O routines
	 NOP			;Some error, ignore it (PUTPUP RELPKT's)
	SKIPA
GATCHX:	 CALL RELPKT		;Release packet
GATCH1:	SOSLE GATLIM		;Mark another packet processed
	 JRST GATCH0		;Loop over the gateway queue
	SIGDEF(GAT)		;Set flag that there is still more to do
	RET			;Time to quit, return to caller
;ADDTQP - add port to timer queue
;Scan timer queue from head to find place for new request.
;Note that no end test is needed since the PUPTIM corresponding
; to the queue header word contains infinity.
;ADDTQI - interrupt level entry point
;Takes	T1/ timeout interval
;	UNIT/ port number
;Returns +1 always
;Takes T1-T4

	XRESCD

ADDTQP:	CAMN T1,[377777777777]	;Timeout at infinity?
	 RET			;Yes, do nothing
	CALL PILOCK		;Enter interlock coroutine
ADDTQI:	SKIPN T3,PUPTMQ(UNIT)	;Port already on timer queue?
	 JRST ADDTQ1		;No, just put new request on
	CAML T1,PUPTIM(UNIT)	;Yes, new request sooner than old?
	 RET			;No, done
	HRRZ T2,T3		;Remove this port from queue
	HLLM T3,(T2)
	HLRZ T2,T3		;Yes, get predecessor ptr in rh
	HRRM T3,(T2)
ADDTQ1:	MOVEM T1,PUPTIM(UNIT)	;Store new time for port
	MOVEI T2,PUPTQH		;Start at header of timer queue
ADDTQ2:	HRRZ T2,0(T2)		;Get successor
	CAMLE T1,PUPTQD(T2)	;Compare new time to one on queue
	 JRST ADDTQ2		;Still later, keep searching
	MOVEI T1,PUPTMQ(UNIT)	;Found place, compute address of new item
	HLRZ T3,0(T2)		;Get new predecessor
	HRLZM T3,0(T1)		;Link item into queue
	HRRM T2,0(T1)
	HRLM T1,0(T2)
	HRRM T1,0(T3)
	RET    			;Done

	XSWAPCD
;DELTQP - Delete port from timer queue
;Call from process level
;Takes	UNIT/ Pup unit number
;Returns +1
;Clobbers T1-T2

	XRESCD

DELTQP:	CALL PILOCK		;Enter interlock coroutine
	SKIPN T1,PUPTMQ(UNIT)	;Port now on timer queue
	 RET			;No, do nothing
	HLRZ T2,T1		;Yes, get predecessor ptr in rh	
	HRRM T1,(T2)		;Make it point to successor
	HRRZ T2,T1		;Get successor
	HLLM T1,(T2)		;Make it point to predecessor
	SETZM PUPTMQ(UNIT)	;Mark no longer queued
	RET			;Return to caller

	XSWAPCD
;REMTQP - Remove request from front of timer queue
;Called only from Pup background fork
;Returns +1  No more
;	 +2  UNIT/ Pup unit number of request
;Clobbers T1, UNIT

	XRESCD			;We go IOPIOFF while NOSKED

REMTQP:	CALL PILOCK		;Enter interlock coroutine
	HRRZ UNIT,PUPTQH	;Get head of queue
	MOVE T1,PUPTQD(UNIT)	;Get time of first request
	CAMLE T1,TODCLK		;Now due?
	 RET			;No (or empty), return +1
	MOVE T1,0(UNIT)		;Yes, get predecessor ,, successor
	HRRM T1,PUPTQH		;Remove this port from queue
	PUSH P,T2		;Save T2
	HRRZ T2,T1		;Make sure of an in-section address
	HLLM T1,0(T2)
	POP P,T2
	SETZM 0(UNIT)		;Mark no longer queued
	SUBI UNIT,PUPTMQ	;Convert pointer to index
	RETSKP			;Good return to caller

	XSWAPCD
;PUPINI - Initialize Pup queues and data structures
;Returns +1
;Clobbers T1-T4, UNIT, PB

PUPINI:	MOVEI T1,BSPBEG		;Get address of start of BSP free storage
	MOVEM T1,PUPPAR+.PPSTG	;Stash in PUPPAR GETAB% table
	HRRZ T1,PUPPAR+.PPPNV	;Get TTY number of first PNV
	MOVEM T1,PNVTTY		;Remember it
	MOVX T1,PP%TNX!PP%MMD	;32-bit mode headers, all data modes
	SKIPN MEIMDF		;Does this system allow all data modes?
	 TXZ T1,PP%MMD		;No, clear that flag
	XMOVEI T2,NCTVT		;Set up pointer to NCT vector table
PUPIN0:	LOAD T2,NTLNK,(T2)	;Get NCT pointer
	JUMPE T2,PUPIN1		;If nil, then done
	LOAD T3,NTTYP,(T2)	;Get network type code
	CAIN T3,NT.ETH		;10MB Ethernet?
	 TXO T1,PP%10M		;Yes, set the flag
	JRST PUPIN0		;Loop over all NCT's

PUPIN1:	IORM T1,PUPPAR+.PPFLG	;Set flags
	XMOVEI T2,PBQEND-1	;Last address of double-word queues
	XMOVEI T1,PBQBEG	;Initialize queues to empty
PUPIN2:	MOVEM T1,HEAD(T1)	; ...
	MOVEM T1,TAIL(T1)	; ...
	ADDI T1,2		;Move on to next queue
	CAMGE T1,T2		;More?
	 JRST PUPIN2		;Yes, repeat for all

	MOVSI UNIT,-NPUPUN	;Initialize all ports
	CALL INIPRT
	AOBJN UNIT,.-1

	MOVX T1,INACCF		;Initialize routing table to all empty
	MOVEM T1,PUPROU
	MOVE T1,[XWD PUPROU,PUPROU+1]
	BLT T1,PUPROU+NPNETS-1
	CALL STGINI		;Initialize queue of permanent packet buffers
	CALL GATINI		;Initialize gateway data structures
;PUPINI (cont'd)

	MOVE T1,[XWD PUPTQH, PUPTQH] ;Initialize timer queue to empty
	MOVEM T1,PUPTQH
	HRLOI T1,377777		;Make corresponding time infinite
	MOVEM T1,PUPFTM		; ...
	MOVEM T1,DEFTIM		;Initial deferred scan timeout is also +INF
	SETOM PRTLCK		;Unlock port table lock
	SETOM NVTLCK		;Unlock NVT assignment lock
	SETZM PUPBGF		;No protocol error logging 
	MOVE T1,DBUGSW		;Get system status
	CAIL T1,2		;If we are standalone
	 SETOM PUPBGF		;Then always debugging
	SETOM NTDLCK		;Unlock network directory
	MOVX T1,.SFDIR		;Initialize PUPNM directory or cache
	SETZ T2,		;Flush cache
	SMON%
	 ERCAL [MOVE T1,LSTERR	;Fetch code for last error
		BUG.(CHK,PUPDIR,PUP,SOFT,<No PUP net directory>,<<T1,LSTERR>>)
		RET ]
	MOVX T1,PBFBSP		;Run some background task(s)
	IORM T1,PUPFLG		; ...
	SETOM PUPON		;PUP is up
	RET			;Return to caller
;GATINI - Initialize gateway data structures
;Called from PUPINI
;PUPIBQ, PUPIBC, PUPSTS must be NPUPUN+1 ports long.
;Returns +1 always
;Clobbers T1, UNIT

GATINI:	MOVEI UNIT,2*GATPRT	;We have a special port =) GATPRT = NPUPUN
	XMOVEI T1,PUPIBQ(UNIT)	;Set up input queue pointers
	MOVEM T1,HEAD(T1)	; ...
	MOVEM T1,TAIL(T1)	; ...
	SETZM PUPIBC(UNIT)	;No input count
	SETZM PUPSTS(UNIT)	;No status bits set
	SETOM PUPLCK(UNIT)	;Port is unlocked
	MOVEI T1,.PM16		;Data mode is .PM16 - no frobbing short pups
	STOR T1,PUPMOD		;Set hardware data mode
	RET			;Return to caller
SUBTTL Byte Stream Protocol (BSP)

;Note:  In the calling sequences, "Assumes port is locked"
;means that UNIT, BSP, and IOS must be setup before the call,
;and the subroutine updates IOS appropriately.

;GETBSP - get pup from BSP input queue
;Assumes port is locked
;Returns +1  Input empty, or error encountered, T1/ argument word for MDISMS
;	 +2  Success, PB/ Packet buffer pointer
;	        PBBSBC(PB)/ Count of Ethernet bytes
;	  	PBIPTR(PB)/ 8-bit global byte pointer to Ethernet bytes
;			    (ILDB gets first byte)
;Note that the returned packet may be a Data, AData, or Mark
;PBIPTR is calculated assuming the 32-bit data mode.  It is correct for control
; pups always and data pups only if the port's data mode is .PM32, byte size 8.
;PBBSBC is used primarily in BSP processing at lower levels.  It represents the
; the number of available Ethernet bytes in this packet.
;The caller assumes responsibility for setting up a byte pointer and count
;correct for the port's data mode and OPENF% byte size if other than 8-bit
;bytes in 32-bit mode is desired.
;Clobbers T1-T4, PB

GETBSP:	SKIPN PB,BSPCIP(BSP)	;Is there a current input packet?
	 JRST GETBS1		;No
	CALL RELPKT		;Yes, release it
	SETZM BSPCIP(BSP)	;Clear current input packet ptr
	
;Get next packet if there is one
GETBS1:	TXNE IOS,BSERRF!BSTIMF	;Error condition exists?
	 JRST GETBS2		;Yes, exit and let caller kill us
	HRRZ T1,BSPIQL(BSP)	;Get available bytes 
	IFE. T1			;Anything in the queue?
	  TXZ IOS,BSINPF	;If none, clear input flag
	  CALL DOBSPL		;Make sure BSP data up-to-date
	  TXNE IOS,BSINPF	;New input available?
	   JRST GETBS1		;Yes, try again
	  TXNN IOS,BSNVTF!BSTAKF ;Unless NVT or an ACK was just sent...
	   CALL SNDACK		;Send back updated allocations
GETBS2:	  SETZM BSPTCK(BSP)	;Zero count of packets processed
	  MOVSI T1,(UNIT)	;Set scheduler test
	  HRRI T1,BSITST	; ...
	  RET			;Take fail return
	ENDIF.
	AOS T1,BSPTCK(BSP)	;Count another packet processed
	CAIGE T1,MAXTCK		;Too many?
	IFSKP.
	  TXO IOS,BSINPF	;Yes, make sure input available flag is lit
	  JRST GETBS2		;Then pretend input was exhausted
	ENDIF.
	XMOVEI T1,BSPIBQ(BSP)	;Data available, get queue header
	CALL REMITQ		;Remove item from BSP input queue
	IFNSK.
	  BUG.(CHK,GETBSA,PUP,SOFT,<PUP - BSP queue fouled>,<<UNIT,UNIT>>)
	  SETZM BSPIQL(BSP)	;This info is bogus, zap it
	  JRST GETBS2		;Join the exit code
	ENDIF.
	XMOVEI PB,-PBLINK(T2)	;Set pointer to head of PB
	MOVEM PB,BSPCIP(BSP)	;Save as current input packet
	MOVSI T1,-1		;Decrement count of packets
	ADDB T1,BSPIQL(BSP)
	JUMPGE T1,GETBS3	;Continue if not overly decremented
	BUG.(CHK,GETBSB,PUP,SOFT,<PUP - BSP input queue fouled>,<<UNIT,UNIT>>)
	SETZM BSPIQL(BSP)	;Zap bogus information
	JRST GETBSP		;Discard this packet
;GETBSP (cont'd)

;We now compute the number of bytes from the beginning of the packet to
; the left window edge.  The byte at the left window edge is the next byte
; to be input.  In most cases the distance will be zero.  A negative distance
; indicates that we have lost data.  A positive distance means that two or
; more pups were sent with overlapping data.  If the distance is positive
; we need to calculate the offset into the pup of the beginning of the left
; window edge.  After determining how many bytes of the pup we have not seen
; before, we add that number to the left window edge.

GETBS3:	MOVE T1,BSPILW(BSP)	;Get left window edge
	SUB T1,PBBSID(PB)	;Compute number of bytes we've already seen
	JUMPGE T1,GETBS4	;Jump if reasonable distance to left window
	BUG.(CHK,GETBSC,PUP,SOFT,<PUP - BSP input queue fouled>,<<UNIT,UNIT>>)
	JRST GETBSP		;Discard packet

GETBS4:	TXZ T1,-1B3  		;Modulo 2^32
	CAML T1,PBBSBC(PB)	;Any useful bytes in this pup?
	 JRST GETBSP		;No, discard (shouldn't happen)
	MOVN T2,T1		;Negate count of already seen bytes
	ADDB T2,PBBSBC(PB)	;Decrease count by first byte number
	MOVN T3,T2		;Negate number of useful bytes in this pup
	ADDB T3,BSPIQL(BSP)	;Decrement count of available bytes
	TXNN T3,400000		;Check for over-decrementing
	IFSKP.
	  BUG.(CHK,GETBSE,PUP,SOFT,<PUP - BSP input fouled>,<<UNIT,UNIT>>)
	  JRST GETBSP		;And flush packet
	ENDIF.
	ADD T2,BSPILW(BSP)	;Add left window edge and last useful byte
	TXZ T2,-1B3  		;modulo 2^32
	MOVEM T2,BSPILW(BSP)	;Store updated left window edge
;GETBSP (cont'd)

;Initialize byte pointer to start of byte stream
;T1 contains offset into packet in 8-bit bytes.

	MOVEI T2,PBCONT(PB)	;Make byte ptr to start of
	HRLI T2,(POINT 8)	; Pup Contents
	ROT T1,-2		;Separate word and byte numbers
	ADDI T2,(T1)		;Advance word index in byte ptr
	HLLZS T1		;Clear word except byte # in B0-1
	LSH T1,-1		;Byte # to B1-2, i.e. 8*# in B0-5
	SUB T2,T1		;Modify byte ptr for starting byte

;Here we have a single word local byte pointer.  Now for the magic.
;The formula for creating the P/S field of single word global byte pointer of
; byte size BYTE at position POS is:
;
;	ENC + <SIZE - <POS/SIZE>>
;
; where ENC is a function of the byte size.  In the case of 8-bit bytes,
; ENC has the value 50 octal.  Now we transmogrify the pointer.
	
	MOVE T1,T2		;Copy byte pointer
;	LSH T1,-^D30		;Isolate P field of local pointer (POS)
;	LSH T1,-3		;Divide by byte size (SIZE)
	LSH T1,-^D33		;(Combine above two instructions)
	MOVNI T1,(T1)		;Negate resulting quotient
	ADDI T1,50+10		;Add encoding factor and byte size 
	LSH T1,^D30		;Shift resulting P/S field into place
	TLO T1,PUPSEC		;Set up section number
	HRR T1,T2		;Copy word address
	MOVEM T1,PBIPTR(PB)	;Store the resulting OWGBP.

;Do any necessary BSP processing before returning
	CALL DOBSPQ		;Do BSP processing
	MOVE PB,BSPCIP(BSP)	;Recover packet buffer pointer
	RETSKP			;Take skip return
;BSITST - test for BSP input available
;Arg is Pup unit number
;Callers are: GETBSP (PUPSQI is rtn that blocks)

	RESCD

BSITST:	MOVX T2,BSINPF!BSTIMF!BSERRF!BSWAKF ;Anything to do?
	TDNN T2,PUPSTS(T1)
	 JRST 0(T4)		;No
	JRST 1(T4)		;Yes, wakeup

	XSWAPCD
;CHKBSO - Check for BSP output possible
;Assumes port is locked
;If fewer than MINBYT bytes, pretend we don't have any buffer space.
; This reduces silly window nonsense and ensures that we can set the
; minimum value of the TOMAX field in the PNV dynamic data to the
; value MINBYT.
;Returns +1  Output not possible, T1/ argument word for MDISMS
;	 +2  Output possible, T1/ Max number of bytes in next pup
;Clobbers T1-T4, PB

CHKBSO:	TXNN IOS,BSOUTF		;Is output possible?
	 JRST CHKBO3		;No, go see if some buffers can be found
CHKBO1:	HRRZ T1,BSPOAL(BSP)	;Get additional bytes allowed
	CAIL T1,MINBYT		;Very small number?
	IFSKP.
	  TXZ IOS,BSOUTF	;Yes, clear output available flag
	  JRST CHKBO4		;Say we don't have any bytes, send AData
	ENDIF.
	LOAD T2,PBSOBP		;Get max bytes/pup
	JUMPE T2,CHKBO4		;Fail if none
	CAILE T1,(T2)		;More than one pup's worth?
	 MOVEI T1,(T2)		;Yes, cut down to max pup length
	RETSKP			;Success, skip return

;Here when output not possible

CHKBO3:	CALL DOBSPL		;Make sure BSP data up-to-date
	TXNE IOS,BSOUTF  	;Output possible now?
	 JRST CHKBO1		;Yes, go get allocation
CHKBO4:	MOVE T1,BSPATM(BSP)	;Get time last AData sent
	ADDI T1,RETINT		;Add nominal retransmission time
	CAMGE T1,TODCLK		;Has an AData been sent recently?
	 CALL SNDADA		;No, request alloc info from other side
	MOVSI T1,(UNIT)		;Set scheduler test
	HRRI T1,BSOTST
	RET    			;Take fail return


;BSOTST - test for BSP output possible
;Arg is Pup unit number
;Callers are: CHKBSO (blocking rtns are PUPSQO and callers of SNDAMA, SNDMRK)

	RESCD

BSOTST:	MOVX T2,BSOUTF!BSTIMF!BSERRF!BSWAKF ;Anything to do?
	TDNN T2,PUPSTS(T1)	; ...
	 JRST 0(T4)		;No, stay blocked
	  JRST 1(T4)		;Yes, wakeup

	XSWAPCD
;UPDBSO  - update setting of BSOUTF
;This routine is responsible for Silly Window Syndrome (SWS) prevention on
; the sender's side.  Output is suppressed if:
;   - no PUP's available or no usable window allocation
;   - usable window is .lt. 25% of offered window,
;   - usable window is .lt. 25% of a max PUP
;   - for high volume (BSFTPF) connections, usable window is .lt. a max PUP
;Takes	BSP/ pointer to BSP block
;	IOS/ PUPSTS word
;Returns +1 always, IOS updated
;Clobbers T1-T3

UPDBSO:	TXO IOS,BSOUTF		;Assume output possible
	LOAD T1,PBSOAP		;Get number of PUPs allowed
	JUMPE T1,UPDBS1		;Jump if none
	HRRZ T1,BSPOAL(BSP)	;Get usable window
	JUMPE T1,UPDBS1		;Jump if no output window at all
	LOAD T2,PBSOBP		;Get bytes/PUP
	TXNN IOS,BSFTPF		;High volume connection?
	IFSKP.
	  CAMGE T1,T2		;Can we send a maximum size PUP?
	   TXZ IOS,BSOUTF	;No, clear output flag
	  RET			;Return to caller
	ENDIF.
	LOAD T3,BSPACK		;Fetch <offered window>/4
	CAMGE T1,T3		;Is usable window more than 25% of offered?
	 JRST UPDBS1		;No, go clear flag
	LSH T2,-2		;Compute 25% of bytes/PUP
	CAMGE T1,T2		;Is usable window more than 25% of a max Pup?
UPDBS1:	 TXZ IOS,BSOUTF		;Don't try to send anything
	RET			;Return to caller
;BLDDAT - Build BSP Data packet
;Takes	T1/  Number of 8-bit data bytes in pup (header bytes NOT included)
;	UNIT/ port number
;Assumes port is locked
;Returns +1  Failed, T1/ argument word for MDISMS
;	 +2  Succeeded:
;		T1/ Byte count for the OPENF% byte size
;		T2/ Byte pointer (IDPB stores first data byte)
;		PB/ Packet buffer pointer
;Clobbers T1-T4, PB

BLDDAT:	STKVAR <DATWRD>		;Declare local storage
	LOAD T2,PUPMOD		;Get hardware data mode for this port	
	SETZ T3,		;Say no header bytes
	CALL WRDCNV		;Change bytes into words per this data mode
	CAIGE T1,1		;Make sure at least one word
	 MOVEI T1,1		; ...
	CAILE T1,BGPBLN-PBCONT	;Reasonable number of words?
	 MOVEI T1,BGPBLN-PBCONT ;No, limit to max that fit in a large buffer
	MOVEM T1,DATWRD		;Remember number of data words in buffer
	ADDI T1,PBCONT		;Add in overhead and header words
	CALL ASGPKT		;Allocate packet buffer
	 RET			;Failed
	MOVEM PB,BSPCOP(BSP)	;Ok, save current buffer ptr
	MOVEI T1,PT.DAT		;Set Type = Data
	STOR T1,PUPTYP
	MOVE T1,DATWRD		;Get back number of data words
	MOVEI T2,^D36		;Bits per word
	LOAD T4,PUPBSZ		;Get byte index
	IDIV T2,OWGBYT(T4)	;Calculate bytes per word (divide by bits/byte)
	IMUL T1,T2		;Calculate total number of bytes
	MOVE T2,OWGTAB(T4)	;Fetch PS and section fields of byte pointer
	HRRI T2,PBCONT(PB)	;Set address of buffer
	MOVEM T2,PBOPTR(PB)	;Store byte pointer
	RETSKP			;Done, skip return

;DMPBSP - Finish up and send current Data Pup, if any
;Assumes port is locked
;We assume BLDDAT has already set the pup type for ENDPUP's calculations
;Takes	T1/ (global) byte pointer to last data written
;Returns +1 always
;Clobbers T1-T4, PB

DMPBSP:	SKIPN PB,BSPCOP(BSP)	;Get ptr to current output PB
	 RET			;Do nothing if none
	SETZM BSPCOP(BSP)	;Zero pointer in data block
	CALL ENDPUP		;Compute length, trim excess
	CALL SNDBSP		;Send it on its way
	RET    
;FRCBSP - "Force" BSP output, i.e. send the current Pup as an AData,
; or send a null AData
;Takes	T1/ (Global) byte pointer to last byte stored in current Pup (if any)
;Assumes port is locked
;Returns +1 always
;Clobbers T1-T4, PB

FRCBSP:	SKIPN PB,BSPCOP(BSP)	;Get ptr to current output PB
	 JRST FRCBS1		;Jump if none
	SETZM BSPCOP(BSP)	;Zero pointer in data block
	MOVEI T4,PT.ADA		;Set Type = AData
	STOR T4,PUPTYP
	CALL ENDPUP		;Compute length, trim excess
	CALL SNDBSP		;Send it on its way
	RET    

;Here when there is no current output packet
FRCBS1:	CALL DOBSPQ		;Do housekeeping
	MOVE T1,BSPOBQ+TAIL(BSP) ;Get tail of output queue
	XMOVEI T2,BSPOBQ(BSP)
	CAMN T1,T2		;Queue empty?
	 RET			;Yes, nothing to do
	XMOVEI PB,-PBLINK(T1)	;No, point to head of tail packet
	LOAD T1,PUPTYP		;Get Pup Type
	CAIE T1,PT.ADA		;An AData?
	 CAIN T1,PT.AMA		;An AMark?
	  RET			;Yes, nothing more to do
	CALLRET RETADA		;No, retransmit it as AData/AMark and return
;CHKBOQ - Check for empty BSP output queue
;Assumes port is locked
;Returns +1  Not empty, T1/ Scheduler test, T2/ # buffered bytes
;	 +2  Empty

CHKBOQ:	MOVE T1,BSPOBQ+HEAD(BSP) ;Get queue head
	XMOVEI T2,BSPOBQ(BSP)	; ...
	CAMN T1,T2		;Empty?
	 RETSKP			;Yes, skip return
	TXO IOS,BSNOQF  	;No, set nonempty output queue flag
	CALL DOBSPL		;Do any necessary processing
	TXNN IOS,BSNOQF  	;Now empty?
	 JRST CHKBOQ		;Maybe, check again
	MOVSI T1,(UNIT)		;No, set scheduler test
	HRRI T1,BSEOQT
	HRRZ T2,BSPOQL(BSP)	;Return # buffered bytes
	RET    			;Take non-skip return


;BSEOQT -  test for empty BSP output queue
;Arg is Pup unit number
;Callers are: CHKBOQ

	RESCD

BSEOQT:	MOVE T2,PUPSTS(T1)	;Get port status
	TXNE T2,BSNOQF  	;Output queue now empty?
	TXNE T2,BSTIMF!BSERRF!BSWAKF   ;Error or work to do?
	 JRST 1(T4)		;Yes, wakeup
	JRST 0(T4)		;No, wait

	XSWAPCD
;SNDBSP - Send BSP data packet (including AData, Mark, or AMark)
;Takes	PB/ Packet buffer ptr
;Assumes port is locked
;Returns +1 always
;Caller is expected to have checked for allocation beforehand
; (by calling CHKBSO)
;Caller should set up Length and Type, we do the rest
;Clobbers T1-T4, PB

SNDBSP:	XMOVEI T1,BSPOBQ(BSP)	;Point to output queue header
	XMOVEI T2,PBLINK(PB)	;Point to link word in this PB
	CALL APPITQ		;Append new Pup to BSP queue
	SETZM BSPCOP(BSP)	;Clear current output PB ptr
	MOVE T1,TODCLK		;Time stamp the pup
	MOVEM T1,PBTIME(PB)	; ...
	SETZM PBXMTC(PB)	;Clear retransmission count

;Compute and store Pup ID
	HRRZ T1,BSPOQL(BSP)	;Get current # bytes queued
	ADD T1,BSPOLW(BSP)	;Compute ID for this Pup
	CALL SETPID		;Set Pup ID

;Update Pup and byte counts for this port
	LOAD T1,PUPLEN		;Get Pup Length
	SUBI T1,MNPLEN		;Subtract overhead
	HRLI T1,1		;Count 1 Pup
	ADDM T1,BSPOQL(BSP)	;Update Pups and bytes queued
	LOAD T3,PBSOAP		;Get additional Pups allowed
	SOSGE T3		;Decrement
	 SETZ T3,		;Overly decremented, set to zero
	STOR T3,PBSOAP		;Store back
	HRRZ T4,BSPOAL(BSP)	;Get additional bytes allowed
	SUBI T4,(T1)		;Decrement by # bytes in new Pup
	SKIPGE T4		;Make sure non-negative
	 SETZ T4,
	HRRM T4,BSPOAL(BSP)	;Store back size of usable window
	CALL UPDBSO		;Decide whether further output is possible

;Check allocation and change Data to AData or Mark to AMark if appropriate
	LOAD T1,PUPTYP		;Get Pup Type
	CAIE T1,PT.ADA		;Already an AData?
	 CAIN T1,PT.AMA		;Already an AMark?
	  JRST SNDBS5		;Yes, nothing more needed
	CALL CHKADA		;Check hold time and allocation
	IFNSK.
	  MOVE T4,BSPDTM(BSP)	;Still ok, save timer
	  CALL SETTMH		;Set timer to hold time
	  CAMGE T4,BSPDTM(BSP)	;Compare to previous timer
	   MOVEM T4,BSPDTM(BSP) ;Use earlier
	  JRST SNDBS6		;Go finish up
	ENDIF.
	LOAD T1,PUPTYP		;Time to send AData, get current type
	CAIN T1,PT.DAT		;Data?
	 MOVEI T1,PT.ADA	;Yes, change to AData
	CAIN T1,PT.MRK		;Mark?
	 MOVEI T1,PT.AMA	;Yes, change to AMark
	STOR T1,PUPTYP
SNDBS5:	MOVE T1,TODCLK		;Record sending AData now
	MOVEM T1,BSPATM(BSP)
	CALL SETTMR		;Set timer for retransmission
;Finish up and send the Pup
SNDBS6:	SETZ T1,		;Clear transport control byte
	STOR T1,PUPTCB
	CALL SETPRT		;Set up source and dest ports
	CALL SETCHK		;Set Pup Checksum appropriately
	CALL PUTPUP		;Queue Pup for output
	 NOP			;Some error, pretend we really sent it
	CALL DOBSPQ		;Do housekeeping if needed
	CALL SETPTM		;Put port on timer queue
	AOS BSPOPG(BSP)		;Count packets generated
	RET    			;Done
;SNDINT - Send an Interrupt
;Takes	T1/ Interrupt code
;	T2/ If nonzero, string ptr to Interrupt text (see BLDIAB)
;Assumes port is locked
;Returns +1  Can't (interrupt already outstanding), scheduler test in T1
;	 +2  Successfully sent
;Clobbers T1-T4, PB

SNDINT:	SKIPN BSPSIP(BSP)	;Have a pointer to an outstanding Interrupt?
	IFSKP.
	  TXO IOS,BSINTF	;Yes, ensure flag is set
	  PUSH P,T1		;Save code and string ptr
	  PUSH P,T2
          CALL DOBSPL		;Ensure BSP data up-to-date
	  POP P,T2
	  POP P,T1
	  SKIPN BSPSIP(BSP)	;Still outstanding?
	   JRST SNDIN0		;No, continue
	  MOVSI T1,(UNIT)	;Yes, set scheduler test
	  HRRI T1,INOTST
	  RET			;Take fail return
	ENDIF.
SNDIN0:	CALL BLDIAB		;Ok, build the Pup
	 RET			;Failed, take non-skip return
	MOVEM PB,BSPSIP(BSP)	;Save pointer to Interrrupt Pup
	MOVEI T1,PT.INT		;Set Type = Interrupt
	STOR T1,PUPTYP
	SETZ T1,		;Zero out Transport control byte
	STOR T1,PUPTCB
	MOVE T1,BSPSII(BSP)	;Get Interrupt ID
	CALL SETPID		;Store in Pup
	CALL SETPRT		;Set up source and dest ports
	CALL SETCHK		;Set Pup Checksum appropriately
	SETOM PBLINK(PB)	;Mark PB as owned by BSP process
	MOVE T1,TODCLK		;Time stamp
	MOVEM T1,PBTIME(PB)
	LOAD T2,BSPRTM		;Get round-trip delay 
	LSH T2,1		;Double to make retransmission timeout
	CAILE T2,PRBINT		;Too big?
	 MOVEI T2,PRBINT	;Yes, use maximum delay
	ADDI T1,(T2)		;Compute time for next check
	MOVEM T1,BSPITM(BSP)	;Store in data block
	CALL PUTPUP		;Queue Pup for output
	 NOP			;Some error, pretend we really sent it
	TXO IOS,BSINTF  	;Ensure flag is set
	CALL DOBSPQ		;Check for work to be done
	CALL SETPTM		;Put port on timer queue
	RETSKP			;Finished, skip return
;INOTST - test for Interrupt no longer outstanding
;Arg is Pup unit number
;Callers are: SNDINT

	RESCD

INOTST:	MOVE T2,PUPSTS(T1)	;Get port status
	TXNE T2,BSINTF  	;Interrupt no longer outstanding?
	TXNE T2,BSTIMF!BSERRF!BSWAKF   ;Error or work to do?
	 JRST 1(T4)		;Yes, wakeup
	JRST 0(T4)		;No, wait

	XSWAPCD
;SNDABT - Send an Abort
;Takes	T1/ Abort code
;	T2/ If nonzero, string ptr to Abort text (see BLDIAB)
;Assumes port is locked
;Returns +1 always
;Clobbers T1-T4, PB

SNDABT:	CALL BLDIAB		;Build the Pup
	 RET			;Failed, forget it
	SKIPGE T1,BSPCID(BSP)	;Get Connection ID
	 SETZ T1,		;None set, use zero
	CALL SETPID		;Store as Pup ID
	MOVEI T1,PT.ABT		;Set Type = Abort
	CALL SNDPUP		;Finish up and send the Pup
	 NOP			;Ignore failure
	RET    			;Done
;BLDIAB - Build Interrupt or Abort (common code)
;Takes	T1/ B0 clear if called from user, set if from monitor
;	    RH: Interrupt or Abort Code
;	T2/ If nonzero, string ptr to text in appropriate space
;Assumes port is locked
;Returns +1  Can't (no room), T1/ scheduler test
;	 +2  Successful, PB/ ptr to packet buffer

BLDIAB:	STKVAR <BLDIAS>		;Space indicator
	MOVEM T1,BLDIAS		;Save address space indicator
	PUSH P,T2		;Save string ptr to text
	PUSH P,T1		;Save code
	MOVEI T1,PBHEAD+<MNPLEN+MXPTXT+2+3>/4
	CALL ASGPKT		;Assign packet buffer
	 JRST [	ADJSP P,-2	;Can't, flush args
		RET]		;Fail return
	POP P,T2		;Recover code
	DPB T2,[POINT 16,PBCONT(PB),15]	;Store in Pup
	XMOVEI T1,PBCONT(PB)	;Get address of buffer
IFE REL6,<HLL T1,[G1BPT(PUPSEC,^D8,,24)] ;"POINT 8,,15">
IFN REL6,<TXO T1,.P0815		;Set "POINT 8,,15">
	MOVEM T1,PBIPTR(PB)	;Store pointer for storing text
	POP P,T2		;Recover string ptr to text
	JUMPE T2,BLDIA2		;Jump if no text
	TLC T2,-1		;Left half = -1?
	TLCN T2,-1
	 HRLI T2,(POINT 7)	;Yes, change to string ptr
	MOVEI T3,MXPTXT		;Init byte counter
BLDIA1:	SKIPGE BLDIAS		;Check address space indicator
	 ILDB T4,T2		;Get byte from monitor
	SKIPL BLDIAS
	 XCTBU .-2		;Get byte from user
	  ERJMP BLDIA2		;Quit if ill mem troubles from user
	JUMPE T4,BLDIA2		;Jump if end
	IDPB T4,PBIPTR(PB)	;Put byte in Pup
	SOJG T3,BLDIA1		;Repeat if still room
	MOVX T2,PT.ABT		;Pup type is abort
	STOR T2,PUPTYP		;Set it now for ENDPUP calculations
BLDIA2:	MOVE T1,PBIPTR(PB)	;Get pointer to last byte
	CALL ENDPUP		;Compute length and set it
	RETSKP			;Skip return
;SNDAMA, SNDMRK - Send an AMark or Mark
;Assumes port is locked
;Note caller is expected to have finished any preceding data Pup
;Takes	T1/ Mark byte (right-justified)
;Returns +1  Can't, T1/ scheduler test
;	 +2  Successfully sent
;Clobbers T1-T4, PB, BSP, IOS

SNDAMA:	IORI T1,400000		;Flag to send Amark
SNDMRK:	STKVAR <SNDMSV>		;Mark byte
	MOVEM T1,SNDMSV		;Save Mark byte
	CALL CHKBSO		;Check for BSP output possible
	 RET			;Not now, take fail return
	MOVEI T1,PBHEAD+<MNPLEN+1+3>/4
	CALL ASGPKT		;Allocate packet buffer
	 RET			;Couldn't, take fail return
	MOVE T1,SNDMSV		;Ok, get back the byte
	MOVEI T2,PT.MRK		;Assume want to send Mark
	TXZE T1,400000		;AMark flag set?
	 MOVEI T2,PT.AMA	;Yes
	DPB T1,[POINT 8,PBCONT(PB),7]	;Store Mark byte
	STOR T2,PUPTYP		;Set Pup Type
	MOVEI T1,MNPLEN+1	;Set Pup Length appropriately
	STOR T1,PUPLEN
	CALL SNDBSP		;Send it on its way
	RETSKP			;Done, skip return
;DOBSP - Perform BSP processing for a port, if possible
;Called only from background
;Takes	UNIT/ Pup port number
;Returns +1
;Clobbers most everything besides UNIT

DOBSP:	LOCK(PRTLCK,<JRST DOBSPX>) ;Lock out changes to port table
	SKIPN BSP,PUPBSP(UNIT)	;Get BSP linkage, skip if really BSP
	 JRST [ UNLOCK(PRTLCK)	;Do nothing if not BSP port
		RET]
	CALL LCKBSA		;Attempt to lock BSP port
	 JRST DOBSP0 		;Can't, defer processing
	UNLOCK(PRTLCK)		;Ok, unlock the table
	CALL DOBSPB		;Do the real work
	TXNE IOS,BSNVTF		;Skip if not an NVT connection
	 CALL PNVINT		;Else schedule NVT processing
	CALL ULKBSP		;Update status, unlock BSP port
	RET    			;Done

;here if we couldn't lock the port or the port table

DOBSP0:	UNLOCK(PRTLCK)		;Can't, unlock table
DOBSPX:	MOVE T1,TODCLK		;Request delayed background processing
	ADDI T1,IBWDLY		;Input background wakeup delay interval
	CALLRET ADDTQP		;Add port to timer queue and return
;DOBSPQ - Perform BSP processing for port locked by caller
;	UNIT/ Pup unit #
;	BSP/ BSP data block ptr
;	IOS/ Port status
;Returns +1 always, does not unlock port
;Clobbers T1-T4, PB

;Enter here to perform processing only if requested
DOBSPQ:	TXNE IOS,BSWAKF  	;Specifically woken up?
	 JRST DOBSPL		;Yes, always do it
	MOVE T1,TODCLK		;No, get now
	SKIPE PUPTMQ(UNIT)	;Is there a timer request in?
	 CAMGE T1,PUPTIM(UNIT)	;Yes, is it due?
	 RET			;No, do nothing

;Enter here to reset possibly pending requests first
DOBSPL:	CALL DELTQP		;Delete timer request if pending

;Enter here from DOBSP (background only)
DOBSPB:	TXZ IOS,BSWAKF!BSTAKF  	;Cancel wakeup request and ACK transmitted
	MOVEM IOS,PUPSTS(UNIT)	;Update in core too
	TXNE IOS,BSERRF		;Dead connection (Closed or Aborted)?
	 RET			;Yes, don't come here anymore
	MOVE T1,TODCLK		;Get now
	SUB T1,BSPLST(BSP)	;Subtract last time we were here
	CAIG T1,2*PRBINT	;Have we been here recently? (~10 seconds?)
	IFSKP.
	 BUG.(CHK,BSPSTL,PUP,SOFT,<PUP - BSP processing stalled>)
	ENDIF.
	MOVE T1,TODCLK		;Get now again
	MOVEM T1,BSPLST(BSP)	;And reset our timer
;Process packets on input queue
DOBSP1:	CALL GETPUP		;Get packet from input queue
	 JRST DOBSP2		;Queue empty
	MOVE T1,FORKX		;Get our system fork number
	CAMN T1,PUPFRK		;Background process?
	 AOSA STAPBG		;Yes, count a packet
	  AOS STAPPR		;Else count a hit at process level
	CALL DOBSPI		;Process the packet
	JRST DOBSP1		;Repeat until queue empty

;Send Ack if needed
DOBSP2:	TXZE IOS,BSSAKF  	;Need to send Ack?
	 CALL SNDACK		;Yes, do so

;See if time yet to check for BSP retransmissions
	MOVE T1,TODCLK		;Get now
	CAMGE T1,BSPDTM(BSP)	;Time for check?
	TXNE IOS,BSRAKF  	;Received Ack?
	 CALL DOBSPO		;Yes, do output processing

;Check outstanding Interrupts
	MOVE T1,TODCLK		;Get now
	CAML T1,BSPITM(BSP)	;Time for check?
	 CALL CKPINT		;Yes, do so

;Check FSM if required
	MOVE T1,TODCLK		;Get now
	CAML T1,BSPFTM(BSP)	;Time for FSM check?
	 CALL [	MOVEI T1,E.TIMO  ;Yes, generate timeout event
		CALLRET PUPFSC]

;Compute time for next service
	CALLRET SETPTM		;Set time and return
;DOBSPI - Process input packet for BSP port
;	UNIT/ Pup unit #
;	BSP/ BSP data block ptr
;	IOS/ Port status
;	PB/ Packet buffer pointer
;Returns +1
;Clobbers T1-T4
;Updates IOS where appropriate

DOBSPI:	SAVEAC <E>		;Don't clobber this AC
	TXNE IOS,BSNCHK  	;Checksumming inhibited?
	 JRST DOBSI0		;Yes, bypass
	CALL CHKCKS		;Validate checksum
	IFNSK.
	  CALL PUPBUG		;Bad checksum
	   BUG.(INF,BSPCHK,PUP,SOFT,<PUP - incorrect checksum>,<<T4,HOST>>)
	  RET
	ENDIF.
DOBSI0:	LOAD E,PUPTYP		;Get Pup Type
	CAIGE E,NBSDSP		;Within bounds?
	 SKIPN BSPDSP(E)	;Have a dispatch entry?
	  IFNSK.
	    CALL PUPBUG
	     BUG.(INF,BSPTYP,PUP,SOFT,<PUP - Unknown BSP type>,<<E,D>,<T4,D>>)
	    RET
	  ENDIF.
	CAIE E,PT.RFC		;Request for Connection?
	 CAIN E,PT.ERR		;Or Error Pup?
	  JRST DOBSI1		;Yes, don't do source port check
	CALL CHKSRC		;Perform source port check
	IFNSK.
	  CALL PUPBUG		;Check failed
	   BUG.(INF,BSPISP,PUP,SOFT,<PUP - Illegal source port>,<<T4,HOST>>)
	  RET
	ENDIF.
DOBSI1:	MOVE E,BSPDSP(E)	;Get flags and dispatch for this pup type
	TXNN E,1B17  		;Check Pup ID? (For Abort, End, EndReply)
	 JRST DOBSI2		;No
	CALL GETPID		;Yes, get PUP ID
	CAMN T1,BSPCID(BSP)	;Same as Connection ID?
	IFSKP.
	  SKIPGE T2,BSPCID(BSP)	;Mismatch, get our connection ID
	   JRST DOBSI2		;Was never set, bad ID doesn't matter
	  CALL PUPBG		;Just complain about the protocol violation
	   BUG.(INF,BSPID,PUP,SOFT,<PUP - Invalid ID>,<<T1,D>,<T2,D>,<T4,D>>)
	ENDIF.
DOBSI2:	LOAD T1,PBSTT		;Get current state
	TDNE E,BITS(T1)		;Reasonable pup type for this state?
	IFSKP.
	  CAIN T1,S.LIST	;Is the port listening? (a server?)
	   JRST RELPKT		;Yes, discard. (see comment near FORERR)
	  LOAD T2,PUPTYP	;Bad state
	  CALL PUPBUG
	   BUG.(INF,BSPSTT,PUP,SOFT,<PUP - Bad state>,<<T1,S>,<T2,T>,<T4,H>>)
	 RET
	ENDIF.
	MOVE T1,TODCLK		;Get now
	MOVEM T1,BSPACT(BSP)	;Remember time of last activity
	HRRZ T1,E		;Ensure in-section address
	CALLRET 0(T1)		;Dispatch to processing routine
;BSP dispatch table, indexed by Pup Type ;Flags:
;	B0 - max state #: Proper port states for this type
;	B17: Check for Pup ID = Connection ID before dispatch

BSPDSP:	0			;(0)
	ALLSTT+RCVECH		;(1) Echo Me
	ALLSTT+RCVIEC		;(2) I'm An Echo
	ALLSTT+RCVBEC		;(3) I'm A Bad Echo
	ALLSTT+RCVERR		;(4) Error
	0			;(5)
	0			;(6)
	0			;(7)
	STTBTS(RFCO,LIST,OPEN,ENDO)+RCVRFC	;(10) Request for Connection
	ALLSTT+1B17+RCVABT	;(11) Abort
	ALLSTT+1B17+RCVEND	;(12) End
	ALLSTT+1B17+RCVENR	;(13) End Reply
	0			;(14)
	0			;(15)
	0			;(16)
	0			;(17)
	STTBTS(OPEN,ENDO)+RCVDAT ;(20) Data
	STTBTS(OPEN,ENDO)+RCVADA ;(21) AData
	STTBTS(OPEN,ENDI,ENDO)+RCVACK ;(22) Acknowledgment
	STTBTS(OPEN,ENDO)+RCVMRK ;(23) Mark
	STTBTS(OPEN,ENDI,ENDO)+RCVINT ;(24) Interrupt
	STTBTS(OPEN,ENDI,ENDO)+RCVINR ;(25) Interrupt Reply
	STTBTS(OPEN,ENDO)+RCVAMA ;(26) AMark

NBSDSP==.-BSPDSP		;Length of the dispatch table
;Individual BSP Pup input processing routines
;All routines have the following calling sequence:
;	PB/ Packet buffer ptr
;	UNIT/ Pup unit #
;	BSP/ BSP data block ptr
;	IOS/ BSPSTS(BSP)
;Returns +1 always
;Routine is expected to dispose of the packet
;Clobbers T1-T4
;Routines to handle RFC, Abort, End, and End Reply are located
; near the FSM routines

;Reflect Echo's

RCVECH:	MOVEI T1,PT.IEC		;Set type = "I'm an Echo"
	STOR T1,PUPTYP
	CALL SWPPRT		;Swap source and destination ports
	CALL SNDPU1		;Setup and send
	 NOP			;Ignore if failed
	RET 			;Done, packet buffer re-used


;Discard EchoMe packets (I'm a good echo, I'm a bad echo)

RCVIEC:
RCVBEC:	CALLRET RELPKT		;Discard


;Handle an error pup

RCVERR:	LOAD T1,PUPERR		;Get registered error code
	CAIE T1,3		;Port input queue overflow?
	CAIN T1,1007		;Gateway output queue overflow?
	 JRST RCVER1		;Yes
	CAIN T1,2		;No such port?
	 CALL CHKSRC		;Yes, make sure source correct
	 JRST RELPKT		;Not, discard
	CALLRET RCVABT		;Yes, treat same as Abort

RCVER1:	LOAD T1,BSPRTM		;Get retransmission timeout
	IMULI T1,9		;Increase by 1/8
	LSH T1,-3
	CAIG T1,MAXRET		;Within maximum?
	 STOR T1,BSPRTM		;Yes, update
	CALLRET RELPKT		;Discard packet and return
;Input Pup processing routines (cont'd)

;RCVAMA, RCVMRK - process a Mark pup

RCVAMA:	TXO IOS,BSSAKF 	;Request that an Ack be sent
RCVMRK:	LOAD T1,PUPLEN		;Make sure it contains just 1 byte
	CAIN T1,MNPLEN+1	;Correct length?
	 JRST RCVDAT		;Yes, go handle Mark like Data
	CALL PUPBUG		;Bad length, log it
	 BUG.(INF,BSPMRK,PUP,SOFT,<PUP - Bad Mark length>,<<T1,SIZE>,<T4,H>>)
	RET

;RCVADA, RCVDAT - process a Data pup


RCVADA:	TXO IOS,BSSAKF  	;Request that an Ack be sent
RCVDAT:	STKVAR <RCVDPB>		;PB Pointer
	MOVEM PB,RCVDPB		;Save PB pointer
	CALL GETPID		;Get pup ID
	CAMGE T1,BSPILW(BSP)	;Is ID less than left window?
	 JRST RELPKT		;Yes, we've seen it before.  Discard it.
	MOVEM T1,PBBSID(PB)	;Store ID
	LOAD T2,PUPLEN		;Get Pup Length
	SUBI T2,MNPLEN		;Compute number data bytes
	JUMPE T2,RELPKT		;Flush packet and exit if no data
	MOVEM T2,PBBSBC(PB)	;Store in more convenient form
	MOVE T3,BSPILW(BSP)	;Get left window edge
	HRRZ T4,BSPIAL(BSP)	;Get width of window
	LSH T4,2		;Make window very wide
	CALL CMPIVL		;Compare intervals
	 JRST RCVDA1		;Identical (ok but most unlikely)
	 JRST RCVDA1		;Pup subinterval of window
	 JRST [ CALL PUPBUG	;Received data pup larger than window
		 BUG.(INF,BSPDAT,PUP,SOFT,<PUP - Data outside window>,<<T4,H>>)
		RET ]
	 JRST RCVDA1		;Intersect but not contained
	 JRST RELPKT		;Outside window, discard quietly
;Scan the BSP input queue and find where this packet belongs
RCVDA1:	XMOVEI T3,BSPIBQ(BSP)	;Start at tail of input queue
	XMOVEI T4,BSPIBQ(BSP)	;Get address of head of queue
RCVDA2:	MOVE T3,TAIL(T3)	;Get predecessor
	CAMN T3,T4		;Reached head of queue?
	 JRST [ MOVE T1,HEAD(T3)	;Yes, must belong here
		JRST RCVDA4 ]
	MOVE T1,PBBSID(PB)	;Get back ID of new pup
	SUB T1,PBBSID-PBLINK(T3)	;Compare to ID of queued pup
	JUMPL T1,RCVDA2		;Repeat if existing ID larger

;Now T3 points to PBLINK of last old Pup with ID (= new Pup's ID
	MOVE T1,PBBSID(PB)	;Get back ID of new Pup
	MOVE T2,PBBSBC(PB)	;Get back number of data bytes in pup
	XMOVEI PB,-PBLINK(T3)	;Point to head of existing packet
	MOVE T3,PBBSID(PB)	;Get its ID
	MOVE T4,PBBSBC(PB)	;Get # data bytes in it
	CALL CMPIVL		;Compare intervals
	 NOP			;+1: Identical, discard new packet
	 JRST [	MOVE PB,RCVDPB	;+2: New packet is contained by
		JRST RELPKT]	; existing one, discard new
	 JRST [	XMOVEI T1,PBLINK(PB) ;+3: Existing contained by
		JRST RCVDA4]	;   new, insert new before it
	 NOP			;+4: Intersect without containment
	MOVE T1,PBLINK+HEAD(PB)	;+5: Disjoint, get successor
;Now ready to put the new packet on the input queue.
;T1 points to PBLINK word of packet before which the new packet
; is to be inserted (i.e. to be the new packet's successor).
RCVDA4:	MOVE PB,RCVDPB		;Recover new PB pointer
	HLRZ T3,BSPIQL(BSP)	;Get number of PUP's already on queue
	LOAD T2,PBSIAP		;Get maximum PUP's allowed
	CAIL T3,(T2)		;Over the limit?
	 JRST RELPKT		;Yes, just flush packet
	MOVSI T3,1		;Increment number of pups on input queue
	ADDM T3,BSPIQL(BSP)	; ...
	XMOVEI T2,PBLINK(PB)	;Make ptr to queue link word
	CALL APPITQ		;Append to input queue

;Scan successor Pups for ones completely swallowed by the new one
RCVDA5:	XMOVEI T1,BSPIBQ(BSP)	;Get queue head
	CAMN T1,PBLINK(PB)	;Compare with sucesssor of this pup
	 JRST RCVDA6		;None, skip this
	MOVE T3,PBBSID-PBLINK(T1) ;Get ID of existing Pup
	MOVE T4,PBBSBC-PBLINK(T1) ;Get # data bytes in it
	MOVE T1,PBBSID(PB)	;Get ID of new Pup
	MOVE T2,PBBSBC(PB)	;Get # data bytes in it
	CALL CMPIVL		;Compare intervals
	 JRST DELBIQ		;+1: Identical? Flush new packet.
	 JRST DELBIQ		;+2: New contained in old?  Flush new packet.
	 JRST [	MOVE T1,PBLINK(PB) ;+3: Existing contained by
		XMOVEI PB,-PBLINK(T1) ; new, delete existing
		CALL DELBIQ
		MOVE PB,RCVDPB	;Recover new PB pointer
		JRST RCVDA5]	;Repeat for new successor
	 NOP			;+4: Intersect without containment
				;+5: Disjoint
;If we filled in a hole, update byte count
RCVDA6:	MOVE T1,PBBSID(PB)	;Get ID of new Pup
	SUB T1,BSPILW(BSP)	;Compute bytes from left edge
	TXZ T1,-1B3  		; mod 2^32
	HRRZ T2,BSPIQL(BSP)	;Get # bytes to first hole
	CAILE T1,(T2)		;New Pup starts before or at hole?
	 JRST RCVDA7		;No
	ADD T1,PBBSBC(PB)	;Yes, add # bytes in new Pup
	CAILE T1,(T2)		;Now past start of hole?
	 HRRM T1,BSPIQL(BSP)	;Yes, update # bytes available
	MOVE T1,PBLINK+HEAD(PB)	;Get pointer to successor pup
	XMOVEI T2,BSPIBQ(BSP)	;Get address of queue end
	CAMN T1,T2		;Ran off end of queue?
	 JRST RCVDA7		;Yes, stop here
	XMOVEI PB,-PBLINK(T1)
	JRST RCVDA6		;Repeat until hit new hole

RCVDA7:	HRRZ T2,BSPIQL(BSP)	;New number of bytes to first hole
	SKIPE T2		;Bytes now available?
	 TXO IOS,BSINPF		;Yes, set input available flag
	RET    			;Done
;Acknowledgment
;--------------

RCVACK:	STKVAR <RCVACV>		;Old PB
	LOAD T1,PUPLEN		;Get Pup Length
	CAIL T1,MNPLEN+6	;Make sure big enough
	IFSKP.
	  CALL PUPBUG		;Too small for an ACK
	   BUG.(INF,BSPAK1,PUP,SOFT,<PUP - bad ACK size>,<<T1,SIZE>,<T4,HOST>>)
	  RET
	ENDIF.
	CALL GETPID		;Get Pup ID
	MOVE T2,T1		;Copy it
	SUB T2,BSPOLW(BSP)	;Compute distance from left window edge
;;;	TXZ T2,-1B3  		; modulo 2^32
;;;	TRNE T2,400000		;If distance is negative
;;;	 JRST RELPKT		;We've seen this ACK before, so flush it
	JUMPL T2,RELPKT		;@#$@! EtherTip.  Flush packet with bad ID.
	HRRZ T3,BSPOQL(BSP)	;Get width of window
	CAILE T2,(T3)		;Pup ID in window?
	IFNSK.
	  CALL PUPBUG		;No, ID is unreasonable
	   BUG.(INF,BSPAK2,PUP,SOFT,<Bad ACK ID>,<<T1,I>,<T2,D>,<T3,W>,<T4,H>>)
	  RET
	ENDIF.
	MOVEM T1,BSPOLW(BSP)	;Yes, store new left window edge
	SUBI T3,(T2)		;Compute updated width
	HRRM T3,BSPOQL(BSP)
	MOVEM PB,RCVACV		;Save pointer to Ack package
	TXO IOS,BSRAKF  	;Note that we received an Ack
	
;Scan BSP output queue and discard packets now lying entirely
; outside the updated window.  Ignore Pos/NegAcks ****************
RCVAC1:	MOVE T1,BSPOBQ+HEAD(BSP) ;Get BSP output queue head
	XMOVEI T2,BSPOBQ(BSP)	; ...
	CAMN T1,T2		;Now empty?
	 JRST RCVAC5		;Yes
	XMOVEI PB,-PBLINK(T1)	;No, make ptr to head PB
	CALL GETPID		;Get Pup ID
	LOAD T2,PUPLEN		;Get Pup Length
	SUBI T2,MNPLEN+1	;Compute ID of last byte in Pup
	ADD T1,T2
	SUB T1,BSPOLW(BSP)	;Compare to left window edge
	LSH T1,4
	JUMPGE T1,RCVAC5	;Jump if in window
	CALL DELBOQ		;Outside, release packet buffer
	JRST RCVAC1		;Continue with next
;Update the allocations after receiving an ACK

RCVAC5:	MOVE PB,RCVACV		;Recover ptr to Ack packet
	MOVSI T3,442040		;"POINT 16" with global bit set
	XMOVEI T4,PBCONT(PB)	;Set gobal address
	DMOVEM T3,PBIPTR(PB)	;Use both pointer fields in packet buffer
	ILDB T1,PBIPTR(PB)	;Get "Max bytes/Pup" field
	CAILE T1,MXPLEN-MNPLEN	;More than what we allow anyway?
	 MOVEI T1,MXPLEN-MNPLEN	;Yes, cut down
	STOR T1,PBSOBP		;Store updated bytes/Pup
	ILDB T1,PBIPTR(PB)	;Get "Number of Pups" field
	CAILE T1,MXBSOP		;More than maximum we allow?
	 MOVEI T1,MXBSOP	;Yes, limit
	HLRZ T2,BSPOQL(BSP)	;Get # Pups already queued
	SUBI T1,(T2)		;Compute additional Pups
	SKIPGE T1		;Make sure positive
	 SETZ T1,		;Else force to zero
	STOR T1,PBSOAP		;Store # additional Pups allowed
	ILDB T1,PBIPTR(PB)	;Get "Number of bytes" field
	CAILE T1,MXBSOB		;More than maximum we allow?
	 MOVEI T1,MXBSOB	;Yes, limit
	MOVE T3,T1		;Copy offered window size
	LSH T3,-2		;Divide by four
	STOR T3,BSPACK		;Remember it for SNDBSP calculations
	HRRZ T2,BSPOQL(BSP)	;Get # bytes already queued
	SUBI T1,(T2)		;Compute usable window size
	SKIPGE T1		;Make sure positive
	 SETZ T1,		;Force to zero otherwise
	HRRM T1,BSPOAL(BSP)	;Store # additional bytes allowed
	CALL UPDBSO		;Decide if further output is possible
; Revise running estimate of round-trip delay.
; new estimate := (7 * old estimate + new sample) / 8.
	MOVE T1,TODCLK
	SUB T1,BSPATM(BSP)	;Actual time since most recent AData sent
	CAIGE T1,MINRET		;Keep within reasonable bounds
	 MOVEI T1,MINRET
	CAILE T1,MAXRET
	 MOVEI T1,MAXRET
	LOAD T2,BSPRTM		;Get old estimate
	IMULI T2,7		;Compute new estimate
	ADD T2,T1
	LSH T2,-3
	STOR T2,BSPRTM		;Store new estimate

;Update status and return
	MOVE T1,BSPOBQ+HEAD(BSP) ;Get output queue head
	XMOVEI T2,BSPOBQ(BSP)	; ...
	CAMN T1,T2		;Now empty?
	 TXZ IOS,BSNOQF  	;Yes, notify anyone watching this
	CALLRET RELPKT		;Discard the Ack and return
;Input Pup processing routines (cont'd)

;Interrupt
;---------

RCVINT:	CALL GETPID		;Get Pup ID
	CAME T1,BSPRII(BSP)	;Same as next expected?
	 AOJA T1,RCVIN2		;No
	HRRE T2,PUPPSI(UNIT)	;Yes, see what type of beast we are
	SKIPGE T2		;We have a real fork number? 
	IFSKP.
	  LOAD T1,INTPSI	;Yes, get PSI channel to interrupt on
	  CAIGE T1,^D36		;Armed?
	  CALL PSIRQF		;Yes, initiate PSI on channel
	  JRST RCVIN1		;Go advance ID, etc.
	ENDIF.
	AOJE T2,RCVIN1		;Jump if no assignment
	MOVEI T2,-<.TTDES+1>(T2) ;NVT, get TTY #
	CALL LCKTTY		;Lock tty , get dynamic data
	 JRST RCVI1		;No tty, shouldnt happen
	PUSH P,T2		;Save data
	CALL NVTINT		;Process the interrupt
	POP P,T2
RCVI1:	CALL ULKTTY		;Unlock tty
RCVIN1:	AOS BSPRII(BSP)		;Advance receive interrupt ID
	MOVX T1,-1B3		; modulo 2^32
	ANDCAM T1,BSPRII(BSP)
	JRST RCVIN3		;Go send reply

;Here if Pup ID is not the same as expected.
;If it is the expected ID -1, it is a duplicate which we should acknowledge
; but not generate an interrupt.  We bend the protocol slightly be
; acknowledging incorrect ID's so as to not zap people with buggy PUP
; implementations.  We have ID+1 in T1.

RCVIN2:	TXZ T1,-1B3  		;Make +1 modulo 2^32
	CAMN T1,BSPRII(BSP)	;Was this a duplicate?
	IFSKP.
	  MOVE T2,BSPRII(BSP)	;No, incorrect ID.  Get expected ID.
	  CALL PUPBG		;Bug action, but don't release packet buffer
	   BUG.(INF,BSPINT,PUP,SOFT,<Bad Interrupt ID>,<<T1,I>,<T2,E>,<T4,H>>)
	  MOVE T1,BSPRII(BSP)	;Get expected ID again
	  CALL SETPID		;Set correct ID for InterruptReply
	ENDIF.
RCVIN3:	MOVEI T1,PT.INR		;Set type = "Interrupt Reply"
	CALL SNDPUP		;Setup and send it back
	 NOP			;Ignore if failed
	RET    			;Done, packet buffer re-used
;Interrupt Reply
;---------------

RCVINR:	CALL GETPID		;Get pup ID in T1
	SKIPE BSPSIP(BSP)	;Check for pointer to outstanding Interrupt
	IFSKP.
	  ADDI T1,1		;Unexpected.  May be a retransmission.
	  TXZ T1,-1B3		;Increment ID modulo 2^32
	  CAMN T1,BSPSII(BSP)	;Is ID+1 same as current ID?
	   JRST RELPKT		;Yes, just quietly release the packet
	  CALL PUPBUG		;Unexpected InterruptReply
	   BUG.(INF,BSPNR1,PUP,SOFT,<PUP - unexpected InterruptReply>,<<T4,H>>)
	  RET
	ENDIF.
	CAMN T1,BSPSII(BSP)	;Save as expected?
	IFSKP.
REPEAT 0,<
	  CALL PUPBUG		;No, interrupt reply with bad ID
	   BUG.(INF,BSPNR2,PUP,SOFT,<Bad Int. Reply ID>,<<T1,S>,<T2,W>,<T4,H>>)
	  RET
>;REPEAT 0
REPEAT 1,<
;;IF1,<PRINTX InterruptReply kludge for SU-AI is still here>
;InterruptReplys from SU-AI are always missing the high 16 bits from their ID.
;It is not known why this happens. 23-Feb-83, -KSL
	  CALL PUPBG		;No, interrupt reply with bad ID
	   BUG.(INF,BSPNR2,PUP,SOFT,<Bad Int. Reply ID>,<<T1,S>,<T2,W>,<T4,H>>)
>;REPEAT 1
	ENDIF.
	CALL RELPKT		;Yes, release the Interrupt Reply
	AOS BSPSII(BSP)		;Increment Interrupt ID
	MOVX T1,-1B3		;Modulo 2^32
	ANDCAM T1,BSPSII(BSP)	; ...
	MOVE PB,BSPSIP(BSP)	;Get pointr to Interrupt pup
	CALL RELPKT		;Release packet buffer
	SETZM BSPSIP(BSP)	;Clear the pointer
	HRLOI T1,377777		;Set Interrupt timer to infinity
	MOVEM T1,BSPITM(BSP)
	TXZ IOS,BSINTF  	;Clear Interrupt outstanding flag
	RET    			;Done, packet disposed of
;CKPINT - Check for retransmission of Interrupt Pup
;Assumes port locked and ac's setup
;If we get a timeout for the Interrupt, pretend we just received an
; InterruptReply and advance the Interrupt ID.  If the host is really
; dead, the main BSP loop will notice the inactivity and time us out.
; There seem to be a number of hosts that don't handle Interrupts correctly.
;Returns +1 always
;Clobbers T1-T4, PB

CKPINT:	TXNN IOS,BSTIMF!BSERRF	;Timed out or error condition?
	 SKIPN PB,BSPSIP(BSP)	;Or no outstanding Interrupt?
	  JRST CKPIN0		;Yes, go reset timer and exit
	MOVE T1,PBTIME(PB)	;Get time of original transmission
	CALL NXTTIM		;Compute time of next check
	MOVEM T1,BSPITM(BSP)	;Store new time
	TXNN IOS,BSTIMF		;Timed out?
	 JRST RETPUP		;No, retransmit the packet and return
	AOS BSPSII(BSP)		;Increment Interrupt ID
	MOVX T1,-1B3		;Modulo 2^32
	ANDCAM T1,BSPSII(BSP)	; ...
	SKIPE T1,PUPFPT(UNIT)	;Get pointer to foreign port table
	MOVE T1,1(T1)		;Fetch net,,host for the pupbug
	CALL PUPBUG		;Complain bitterly, flush packet
	 BUG.(CHK,INTTMO,PUP,SOFT,<PUP - Interrupt timeout>,<<T1,N>,<UNIT,U>>)
	SETZM BSPSIP(BSP)	;Zero pointer to current interrupt pup
	TXZ IOS,BSINTF!BSTIMF	;Clear timeout and Interrrupt outstanding flags
CKPIN0:	HRLOI T1,377777		;Reset timer to infinity
	MOVEM T1,BSPITM(BSP)	; ....
	RET			;Return to caller
;DOBSPO - do BSP output processing -- flow control and retransmission
;Assumes port locked and ac's setup
;Returns +1 always
;Clobbers T1-T4, PB

DOBSPO:	CHKSTT <OPEN,ENDI>	;Reasonable state for BSP output?
	IFNSK.
	  HRLOI T1,377777	;No, set timer to infinity
	  MOVEM T1,BSPDTM(BSP)
	  TXZ IOS,BSRAKF	;Clear rec'd Ack flag
	  RET			;Return having done nothing
	ENDIF.
	TXZN IOS,BSRAKF  	;Received Ack?
	 JRST DOBSO6		;No, just check timers

;Ack was received - see if we have any packets to retransmit
	MOVE T1,BSPOBQ+HEAD(BSP) ;Get head of queue
	XMOVEI T2,BSPOBQ(BSP)	; ...
	CAMN T1,T2		;Empty?
	 JRST SETTMI		;Yes, set idle interval and return
	LOAD T4,BSPRTM		;No, get round-trip time
	TXNE IOS,BSNVTF		;Connection belongs to a PNV?
	 MOVEI T4,PNVRET	;Yes, use constant round trip time
	LSH T4,1		;Double that time
	ADD T4,PBTIME-PBLINK(T1) ;Check time stamp of first packet
	CAMG T4,TODCLK		;Older than round-trip time?
	 JRST DOBSO1		;Yes, join retransmission code
	CALL CHKADA		;No retransmittable packets,want to send AData?
	 JRST SETTMH		;No, set hold interval and return
	CALL SETTMR		;Yes, compute time of next probe
	CALLRET SNDADA		;Send null AData and return

;Retransmit unacknowledged packets older than the estimated round-trip delay
DOBSO1:	XMOVEI PB,-PBLINK(T1)	;Make pointer to head of PB
	LOAD T4,BSPRTM		;Get round-trip time
	TXNE IOS,BSNVTF		;Connection belongs to a PNV?
	 MOVEI T4,PNVRET	;Yes, use constant round trip time
	LSH T4,1		;Double it
	MOVN T4,T4		;Make negative
	ADD T4,TODCLK		;Compute cutoff time
	MOVE T1,PBLINK+HEAD(PB)	;Get successor of current packet
	XMOVEI T2,BSPOBQ(BSP)	; ...
	CAME T1,T2		;Is there one?
	 CAMGE T4,PBTIME-PBLINK(T1)	;And is it retransmittable?
	  JRST DOBSO2		;No, possibly want to send AData
	CALL RETDAT		;Yes, retransmit current as Data
	MOVE T1,PBLINK+HEAD(PB)	;Get successor again
	JRST DOBSO1		;Repeat for it

;Here when have last retransmittable packet
DOBSO2:	CALL CHKADA		;Timer expired or allocation low?
	IFNSK.
	  CALL RETDAT		;No, retransmit as Data
	  JRST SETTMH		;Set hold interval, return
	ENDIF.
	CALL RETADA		;Yes, retransmit as AData
	CALLRET SETTMR		;Set retransmission interval, exit
;Here if no Ack was received
;If data timer ran out, send an AData if necessary
;Also check error timer
DOBSO6:	MOVE T1,TODCLK		;Get now
	CAMGE T1,BSPDTM(BSP)	;Timed out?
	 RET			;No, done
	SUB T1,BSPACT(BSP)	;Yes, compute time since last activity
	LOAD T2,PBTMO		;Get error timeout interval
	LSH T2,^D12		;Convert to ms
	CAML T1,T2		;Too long?
	 CALL BSPTMO		;Yes, signal error timeout
	CALL SETTMI		;Compute time for next probe
	CALLRET SNDADA		;Send null AData and return

;CHKADA - Determine whether want to (re)transmit AData.
;Send an AData if we haven't sent an AData recently and,
; we can't do further output or,
; we have an old packet on the retransmision queue.
;Assumes port is locked and ac's setup
;Returns +1  Don't send AData
;	 +2  Send AData
;Clobbers T1, T2

CHKADA:	TXNN IOS,BSOUTF		;More output possible?
	 RETSKP			;No, must send AData
	MOVE T1,BSPATM(BSP)	;Get time of last AData
	ADDI T1,RETINT		;A reasonable delay
	CAML T1,TODCLK		;Sent one recently?
	 RET			;Yes, don't send another
	MOVE T1,BSPOBQ+HEAD(BSP) ;Get head of output queue
	XMOVEI T2,BSPOBQ(BSP)	; ...
	CAMN T1,T2		;Empty?
	 RET			;Yes, don't need an allocation update
	MOVE T1,PBTIME-PBLINK(T1) ;No, get packet's time stamp
	ADDI T1,HLDINT		;Add hold time
	CAMGE T1,TODCLK		;Packet older than hold time?
	 RETSKP			;Yes, skip return.  Send AData
	RET			;No, don't need another AData
;Routines to compute and store new timeout
;All return +1 and clobber T1, T2

;Enter here to set timer to Time[Oldest]+HoldTime
;If the queue is empty, the idle probe interval is used
SETTMH:	MOVE T2,BSPOBQ+HEAD(BSP) ;Get head of queue
	XMOVEI T1,BSPOBQ(BSP)	; ...
	CAMN T1,T2		;Empty?
	 JRST SETTM4		;Yes, use idle interval
	MOVE T1,PBTIME-PBLINK(T2) ;Get age of packet
	ADDI T1,HLDINT		;Compute remaining hold time
	SUB T1,TODCLK
	LOAD T2,BSPRTM		;Get retransmission timeout
	LSH T2,1		;Make retransmission timeout be double that
	CAIGE T1,(T2)		;Hold at least that long
	 MOVEI T1,(T2)
	JRST SETTM3		;Go set new time

;Enter here to set timer to Now + Retransmission interval
SETTMR:	LOAD T1,BSPRTM		;Get retransmission timeout
	LSH T1,1		;Make retransmission timeout be double that
	JRST SETTM3		;Go set new time

;Enter here to set timer to idle probe interval, i.e.:
; if allocation exhausted then Now+HLDINT else Now+PRBINT

SETTMI:	MOVEI T1,PRBINT		;Assume idle interval
	TXNN IOS,BSOUTF		;Sufficient allocation?
	 MOVEI T1,HLDINT	;No, use hold interval instead
	JRST SETTM5		;Go set timer
	
SETTM3:	CAILE T1,PRBINT		;Is interval within the probe interval?
SETTM4:	 MOVEI T1,PRBINT	;No, set probe interval instead
SETTM5:	ADD T1,TODCLK		;Add interval to now
	MOVEM T1,BSPDTM(BSP)	;Set time of next check
	RET    
;RETADA - Retransmit Data/Mark Pup as AData/AMark
;Takes	PB/ Packet buffer ptr
;Assumes port is locked and ac's setup
;Returns +1 always
;Clobbers T1-T4

RETADA:	LOAD T1,PUPTYP		;Get existing type
	CAIN T1,PT.DAT		;Data?
	 MOVEI T1,PT.ADA	;Yes, change to AData
	CAIN T1,PT.MRK		;Mark?
	 MOVEI T1,PT.AMA	;Yes, change to AMark
	MOVE T2,TODCLK		;Save time we sent it
	MOVEM T2,BSPATM(BSP)
	JRST RETDA1		;Join common code


;RETDAT - Retransmit Data/Mark Pup as Data/Mark
;Note there is a potential race here whose worst effect would
;be sending a Pup with a bad checksum.
;Takes	PB/ Packet buffer ptr
;Assumes port is locked and ac's setup
;Returns +1 always
;Clobbers T1-T4

RETDAT:	LOAD T1,PUPTYP		;Get existing type
	CAIN T1,PT.ADA		;AData?
	 MOVEI T1,PT.DAT	;Yes, change to Data
	CAIN T1,PT.AMA		;AMark?
	 MOVEI T1,PT.MRK	;Yes, change to Mark
RETDA1:	MOVE T2,[STRPTR<PUPTYP>] ;Get byte ptr to existing type
	MOVE T3,[STRPTR<PUCHK>]	;Get pointer to checksum
	LDB T4,T2		;Get existing Pup Type
	CAIE T1,(T4)		;Changing type?
	 CALL UPDCKS		;Yes, do so and fix checksum
	AOS T1,PBXMTC(PB)	;Bump BSP retransmission count
	CAIGE T1,MXRTMC		;Too many retransmissions?
	IFSKP.
	  SKIPE PUPBGF		;Complain only if logging pup bugs
	   BUG.(INF,RETTMO,PUP,SOFT,<Too many BSP retransmissions>)
	  TXO IOS,BSERRF	;Set abort flag and continue
	ENDIF.
RETPUP:	AOS BSPOPR(BSP)		;Count retransmitted packets, this connection
	AOS STARTM		;Count total retransmitted packets
	CALL PUTPUP		;Send the pup
	 NOP			;Some error, ignore it
	RET			;Return to caller
;Compute time of next check (for retransmissions)
;Retransmission intervals start at the nominal interval
; given by BSPRTM and double for each retransmission,
; with a maximum given by the constant PRBINT (~15 seconds)
;	T1/ Time of previous check
;Assumes port is locked and ac's setup
;Returns +1:  T1/ Time of next check
;Generates timeout error if appropriate
;Clobbers T1, T2

NXTTIM:	SUB T1,TODCLK		;Get -(now-then)
	MOVNS T1		;Make positive
	PUSH P,T1		;Save it
	LOAD T2,PBTMO		;Get error timeout interval
	LSH T2,^D12		;Convert to ms
	CAML T1,T2		;Too long?
	 CALL BSPTMO		;Yes, signal timeout error
	POP P,T1		;Recover interval
	ASH T1,1		;Double it
	LOAD T2,BSPRTM		;Get retransmission timeout
	LSH T2,1		;Double it
	CAIGE T1,(T2)		;Use whichever is greater
	 MOVEI T1,(T2)
	CAILE T1,PRBINT		;But never greater than
	 MOVEI T1,PRBINT	; probe interval while idle
	ADD T1,TODCLK		;Add offset to now
	RET    


;Set timer for port
;Call after changing BSPDTM, BSPITM, or BSPFTM
;Returns +1
;Clobbers T1-T4

SETPTM:	CALL DELTQP		;Delete from timer queue if on it
	MOVE T1,BSPDTM(BSP)	;Get time to send next AData
	CAMLE T1,BSPITM(BSP)	;Retransmit Interrupt sooner?
	 MOVE T1,BSPITM(BSP)	;Yes, use it
	CAMLE T1,BSPFTM(BSP)	;Check FSM sooner?
	 MOVE T1,BSPFTM(BSP)	;Yes, use that
	MOVE T2,TODCLK		;Get current time
	ADDI T2,PRBINT		;Compute maximum delay
	CAMLE T1,T2		;Within range?
	 MOVE T1,T2		;No, reset to maximum delay
	CALLRET ADDTQP		;Add port to timer queue and return
;SNDADA - Send a null AData (for probing)
;Takes	BSP/ BSP data block ptr
;Returns +1 always
;Clobbers T1-T4, PB

SNDADA:	MOVEI T1,MNPBLX		;Allocate minimum-length Pup
	CALL ASGPKT
	 RET			;Can't, forget it
	MOVEI T1,MNPLEN		;Set Pup Length
	STOR T1,PUPLEN
	HRRZ T1,BSPOQL(BSP)	;Get # of bytes queued
	ADD T1,BSPOLW(BSP)	;Compute ID of first byte not sent
	CALL SETPID		;Get Pup ID
	MOVEI T1,PT.ADA		;Pup Type = AData
	CALL SNDPUP		;Send it
	 NOP			;Ignore failure
	MOVE T1,TODCLK		;Remember time of last AData
	MOVEM T1,BSPATM(BSP)
	AOS T1,BSPPPG(BSP)	;Count probe packets generated
	IDIV T1,BSPOPG(BSP)	;Compute probes/output packet
	CAIGE T1,^D100		;.ge. 100 probes/output packet means hung
	IFSKP.
	  SKIPE PUPBGF		;Complain only if logging pup bugs
	   BUG.(INF,ADARET,PUP,SOFT,<Hung connection freed>)
	  TXO IOS,BSERRF	;Destroy the connection
	ENDIF.
	RET			;Return to caller
;SNDACK - Send an Ack
;We do a cumulative Ack.
;Takes	UNIT/ Pup unit number
;	BSP/ BSP data block pointer
;	IOS/ BSPSTS(BSP)
;Returns +1 always
;Clobbers T1-T4, PB

SNDACK:	MOVEI T1,PBHEAD+<MNPLEN+6+3>/4	;Length of a cumulative ACK
	CALL ASGPKT		;Assign packet buffer
	 JRST SNDAC0		;Failed, note ACK still needed
	HRRZ T1,BSPIQL(BSP)	;Get offset from left edge to hole
	ADD T1,BSPILW(BSP)	;Compute ID of first hole
	CALL SETPID		;Set Pup ID
	MOVSI T1,442040		;"POINT 16" with global bit set
	XMOVEI T2,PBCONT(PB)	;Set global address
	DMOVEM T1,PBIPTR(PB)	;We steal this field in packet buffer
	LOAD T1,PBSIBP		;Get maximum bytes/Pup
	IDPB T1,PBIPTR(PB)	;Store in Pup
	LOAD T1,PBSIAP		;Get maximum # Pups allowed
	HLRZ T3,BSPIQL(BSP)	;Get # Pups already on queue
	SUBI T1,(T3)		;Compute remainder
	SKIPGE T1		;Make sure not negative
	 SETZ T1,		;Make zero if negative
	HRRZ T2,BSPIAL(BSP)	;Get maximum number bytes allowed
	SUB T2,BSPIQL(BSP)	;Subtract number bytes used
	TRNE T2,400000		;Make sure not negative
	 SETZ T2,		;Make zero if negative
	SKIPE T1		;Ran out of pups?
	 SKIPN T2		;Or ran out of bytes?
	  SETZB T1,T2		;Ran out of at least one, say ran out of both
	IDPB T1,PBIPTR(PB)	;Store number Pups allowed into Pup
	IDPB T2,PBIPTR(PB)	;Store number bytes allowed into Pup
	MOVEI T1,MNPLEN+6	;Length of a cumulative Ack
	STOR T1,PUPLEN		;Store Pup Length
	MOVEI T1,PT.ACK		;Pup type = "Ack"
	CALL SNDPUP		;Setup header and send it
SNDAC0:	 TXOA IOS,BSSAKF  	;Couldn't, note Ack still needed
	TXO IOS,BSTAKF		;An ACK was transmitted
	AOS BSPAKG(BSP)		;Count ACK's generated
	RET    			;Done
;ENDPUP - Compute Pup Length given byte pointer, and trim excess
;The pup type MUST be set before calling this routine.
;The pup length computed is in 8-bit bytes.
;Takes  PB/ Packet buffer pointer
;	T1/ global byte pointer to last data written
;Returns +1 always
;Clobbers T1-T4

ENDPUP:	MOVE T3,T1		;Get address of last word
	TLZ T3,770000		;Make sure extraneous bits are cleared
	XMOVEI T4,PBCONT-1(PB)	;Compute address of first word
	SUB T3,T4		;Compute number of 36-bit words used for data
	PUSH P,T1		;Save a copy of the pointer
	CALL GETMOD		;Get data mode in T2
	CAIE T2,.PM36		;36-bit mode?
	IFSKP.
	  ADJSP P,-1		;Yes, flush byte poiner
	  MOVE T1,T3		;FRM36 wants word count in T1
	  CALL FRM36		;Compute number of Ethernet data bytes
	  MOVE T3,T1		;More data shuffling
	  JRST ENDPU0		;Go set pup size
	ENDIF.
	LOAD T4,PUPBSZ		;Get port's data byte size index
	SKIPN T1		;Is this a data or control pup?
	 MOVEI T4,2		;Control pup, assume 8-bit bytes (index = 2)
	IMUL T3,NIBTAB(T2)	;Convert to total number of nibbles
	POP P,T1		;Restore pointer word containing P and S fields
 	LSH T1,-^D30		;Isolate P/S field
	CAML T1,OWGLOT(T4)	;Range check our pointer
	 CAMLE T1,OWGHIT(T4)	; ....
	  JRST ENDPU1		;Lose badly, must abort connection
	SUB T1,OWGLOT(T4)	;Number of bytes we've used
	IMUL T1,OWGBYT(T4)	;Convert to number of bits
	LSH T1,-2		;Convert to nibbles, rounding down
	MOVNI T1,(T1)		;Negate count of used nibbles
	ADD T1,NIBTAB(T2)	;Calculate unused nibbles
	SUB T3,T1		;Subtract unused nibbles
	ADDI T3,1		;Round up for bytes
	LSH T3,-1		;Divide by two to get number of 8-bit bytes
ENDPU0:	ADDI T3,MNPLEN		;Include header and checksum bytes
	STOR T3,PUPLEN		;Store resulting pup length
	RET			;Return to caller

;Here if the P field of the byte pointer didn't correspond to the data
; mode of the connection.  Rather than risk an illegal mem ref later on
; (we use the packet size in computing the checksum) we send this pup
; with a bogus, but safe, length and set the error bit in the PUPSTS word.

ENDPU1: BUG.(CHK,PUPBYT,PUP,SOFT,<PUP - Illegal global byte pointer>)
	TXO IOS,BSERRF		;Disallow further I/O -- kill connection
	SETZ T3,		;Say no data bytes
	JRST ENDPU0		;Rejoin main code
;SNDPUP - Set up header and send a Pup
;SNDPU1 - entry point for SNDPUP if type and ports already setup
;Takes	T1/ Pup Type to be stored in packet
;	PB/ Packet buffer pointer
;	UNIT/ Pup unit number
;	IOS/ BSPSTS(BSP)
;Caller is expected to setup Pup Length and ID
;Returns +1  Failed, packet buffer discarded
;	 +2  Succeeded, packet buffer queued for output
;Clobbers T1-T4

SNDPUP:	STOR T1,PUPTYP		;Store Pup Type in packet
	CALL SETPRT		;Set up source and dest ports
SNDPU1:	SETZB T1,PBLINK(PB)	;Clear BSP queue linkages
	STOR T1,PUPTCB		;Zero out Transport Control byte
	CALL SETCHK		;Set Pup Checksum appropriately
	CALL PUTPUP		;Queue Pup for output
	 RET			;Some failure, single return
	RETSKP			;Done, skip return
;SETPRT - Set up Source and Destination Ports in Pup
;	PB/ Packet buffer pointer
;	UNIT/ Pup unit #
;Returns +1 always

SETPRT:	SKIPN T3,PUPFPT(UNIT)	;Get foreign port descriptor
	 BUG.(HLT,SETPRA,PUP,SOFT,<PUP - Attempt to send Pup to wildcard port>)
	HLRZ T1,1(T3)
	STOR T1,PUPDN		;Set destination network
	HRRZ T1,1(T3)
	STOR T1,PUPDH		;Set destination host
	MOVE T1,2(T3)
	CALL SETPDS		;Set destination socket
	LOAD T1,PRTLN,(UNIT)
	STOR T1,PUPSN		;Set source net
	LOAD T1,PRTLH,(UNIT)
	STOR T1,PUPSH		;Set source host
	MOVE T1,PUPLSK(UNIT)
	CALL SETPSS		;Set source socket
	RET    
;CMPIVL - Compare sequence number intervals
;	T1/ Left edge of interval 1
;	T2/ Length of interval 1
;	T3/ Left edge of interval 2
;	T4/ Length of interval 2
;Returns +1:  Intervals are identical
;	+2:  Interval 1 is a subinterval of 2
;	+3:  Interval 2 is a subinterval of 1
;	+4:  Intervals intersect but neither contains the other
;	+5:  Intervals are disjoint
;Clobbers T1, C

CMPIVL:	SUBM T3,T1		;A _ (L2 - L1) mod 2^32
	TXZ T1,-1B3  
	MOVN T3,T1		;C _ (L1 - L2) mod 2^32
	TXZ T3,-1B3  
	CAMN T2,T4		;Lengths same?
	 JUMPE T1,R		;Yes, return +1 if left edges same
	CAMGE T1,T2		;Check for overlaps
	 JRST CMPIV1		;Overlap, maybe 2 subinterval of 1
	CAMGE T3,T4
	 JRST CMPIV2		;Overlap, maybe 1 subinterval of 2
	POP P,T1		;No overlap, return +5
	JRST 4(T1)

;Here on overlap with possibility of 2 being a subinterval of 1
CMPIV1:	ADD T1,T4		;Add 2's size to its offset from 1
	CAMLE T1,T2		;2 completely contained by 1?
	 JRST SK3RET		;No, return +4
	JRST SK2RET		;Yes, return +3

;Here on overlap with possibility of 1 being a subinterval of 2
CMPIV2:	ADD T3,T2		;Add 1's size to its offset from 2
	CAMLE T3,T4		;1 completely contained by 2?
	 JRST SK3RET		;No, return +4
	RETSKP			;Yes, return +2
;Flush all packets from BSP queues
;Assumes port is locked
;Returns +1 always
;Clobbers T1-T4, PB

FLSBSQ:	SKIPE PB,BSPCIP(BSP)	;Delete current input PB if any
	 CALL RELPKT
	SKIPE PB,BSPCOP(BSP)	;Delete current output PB if any
	 CALL RELPKT
	SETZM BSPCIP(BSP)	;Mark no current PB's
	SETZM BSPCOP(BSP)	; ...
FLSBS1:	MOVE T1,BSPIBQ+HEAD(BSP) ;Get head of BSP input queue
	XMOVEI T2,BSPIBQ+HEAD(BSP) ; ...
	CAMN T1,T2		;Empty?
	 JRST FLSBS2		;Yes
	XMOVEI PB,-PBLINK(T1)	;No, get ptr to head of PB
	CALL DELBIQ		;Delete from queue
	JRST FLSBS1		;Repeat until empty

FLSBS2:	MOVE T1,BSPOBQ+HEAD(BSP) ;Get head of BSP output queue
	XMOVEI T2,BSPOBQ(BSP)	; ...
	CAMN T1,T2		;Empty?
	 JRST FLSBS3		;Yes
	XMOVEI PB,-PBLINK(T1)	;No, get ptr to head of PB
	CALL DELBOQ		;Delete from queue
	JRST FLSBS2		;Repeat until empty

FLSBS3:	SKIPE PB,BSPSIP(BSP)	;Have pending send Interrupt?
	 CALL RELPKT		;And release packet buffer
	SETZM BSPSIP(BSP)	;Clear pointer
	SKIPE PB,BSPABP(BSP)	;Have a saved Abort packet?
	 CALL RELPKT		;Yes, release it
	SETZM BSPABP(BSP)	;Clear pointer
	RET			;Return to caller
;DELBIQ - Delete Pup from BSP input queue
;Takes	PB/ Packet buffer ptr
;Assumes port is locked
;Returns +1, releases packet buffer, updates Pup count
;Clobbers T1-T4

DELBIQ:	XMOVEI T1,PBLINK(PB)	;Make ptr to link word
	CALL DELITQ		;Delete item from queue
	CALL RELPKT		;Release packet buffer
	MOVSI T1,-1		;Decrement # Pups in BSP queue
	ADDB T1,BSPIQL(BSP)
	SKIPGE T1		;Check for over-decrementing
	 BUG.(CHK,DELBIZ,PUP,SOFT,<PUP - Over-decrementing BSP input count>)
	RET    


;DELBOQ - delete Pup from BSP output queue
;Takes	PB/ Packet buffer ptr
;Assumes port is locked
;Returns +1, releases packet buffer, updates Pup count
;Clobbers T1-T4

DELBOQ:	MOVE T1,PBLINK+TAIL(PB)	;Get predecessor
	MOVE T2,PBLINK+HEAD(PB)	;Get successor
	MOVEM T1,TAIL(T2)	;Fix links between predecessor
	MOVEM T2,HEAD(T1)	; and successor
	CALL RELPKT		;Release packet buffer storage
	MOVSI T1,-1		;Decrement number of pups in BSP queue
	ADDB T1,BSPOQL(BSP)
	SKIPGE T1		;Check for over-decrementing
	 BUG.(CHK,DELBOZ,PUP,SOFT,<PUP - Over-decrementing BSP output count>)
	RET    
;Routines to lock/unlock BSP port
;Note:  While a port is locked, the up-to-date flags are
; carried in IOS, and are stored in PUPSTS(UNIT) when the
; port is unlocked.  Exception:  the BSWAKF flag is always
; updated in core (while the port is locked by someone else).

;LCKBSQ - Lock port iff it is a BSP port
;Takes	UNIT/ Pup unit number
;Returns +1  Not a BSP port, BSP/ 0
;	 +2  Port locked, with BSP/ BSP data block pointer
;			       IOS/ PUPSTS(UNIT)
;Clobbers nothing else

LCKBSQ:	SKIPN BSP,PUPBSP(UNIT)	;Get BSP linkage
	 RET			;Return +1 if not BSP port
	CALL LCKBSP		;Call locking return
	RETSKP			;Success return


;LCKBSP - Lock BSP port (wait if already locked)
;Don't call this routine from the pup background fork
;Takes	UNIT/ Pup unit number
;Returns +1 always, with BSP/ BSP data block pointer
;			 IOS/ PUPSTS(UNIT)
;Clobbers nothing else

LCKBSP:	SAVEAC <T1>		;We sometimes clobber this AC
LCKBS0:	LOCK(PRTLCK)		;Lock out changes to port table
	CALL LCKBSA		;Attempt to lock port
	IFNSK.
	  UNLOCK(PRTLCK)	;Can't, unlock table
	  MOVSI T1,(UNIT)	;Set scheduler test
	  HRRI T1,BSLCKT
	  MDISMS		;Wait until port unlocked
	  MOVE T1,PUPSTS(UNIT)	;Get port status flags
	  TXNE T1,BSERRF!BSTIMF	;Or wakeup because of error or timeout?
	   RET			;Yes, let caller handle the badness
	  JRST LCKBS0		;Try again to lock
	ENDIF.
	UNLOCK(PRTLCK)		;Ok, unlock table
	RET 			;Return
;BSLCKT - test for BSP port unlocked
;Argument is port number
;Callers are: PUPOPN, LCKBSP

	RESCD

BSLCKT:	MOVE T2,PUPSTS(T1)	;Get port status flags
	SKIPL PUPLCK(T1)	;Lock is free?
	 TXNE T2,BSTIMF!BSERRF	;Time out or error condition?
	  JRST 1(T4)		;Yes, wakeup
	   JRST 0(T4)		;Keep waiting

	XSWAPCD

;LCKBSA - Attempt to lock BSP port
;PRTLCK must be locked by caller
;Takes	UNIT/ Pup unit number
;Returns +1 failure, already locked, IOS clobbered
;	 +2 sucess, we have locked it:
;		BSP/ BSP data block pointer
;		IOS/ PUPSTS(UNIT)
;Clobbers CX

LCKBSA:	SKIPN BSP,PUPBSP(UNIT)	;Get linkage
	 BUG.(HLT,LCKBSZ,PUP,SOFT,<PUP - Attempt to lock non-BSP port>)
	NOSKED			;Turn off scheduling
	MOVE IOS,PUPSTS(UNIT)	;Always set up IOS
	AOSN PUPLCK(UNIT)	;Try to lock the port
	 JRST LCKBS1		;Got the lock, go finish up
	MOVE CX,PUPLKF(UNIT)	;Failed to lock, get owner's fork number
	CAMN CX,FORKX		;Do we already own the lock?
	 JRST LCKBS2		;Yes, take success return
	SOS PUPLCK(UNIT)	;Decrement lock count
	OKSKED			;Resume scheduling
	RET			;Take a failure return

LCKBS1: MOVE CX,FORKX		;Get our fork number
	MOVEM CX,PUPLKF(UNIT)	;Set it
LCKBS2:	OKSKED			;Resume scheduling
	CSKED			;Run with high priority
	RETSKP			;Give success return
;ULKBSP - Unlock BSP port
;ULKBSQ - Unlock port iff it is a BSP port
;Takes	UNIT/ Pup unit number
;	BSP/ BSP data block pointr
;	IOS/ Status
;Returns +1, Clobbers nothing

ULKBSQ:	JUMPE BSP,R		;Do nothing if not BSP port
ULKBSP:	SKIPL PUPLCK(UNIT)	;Skip if we would overly decrement lock count
	 SOS PUPLCK(UNIT)	;Else decrement the lock count
	EXCH IOS,PUPSTS(UNIT)	;Store updated status word
	AND IOS,[BSWAKF]	;If wakeup pending,
	IORB IOS,PUPSTS(UNIT)	; be sure bit stays on
	SKIPLE CRSKED		;Skip if port was locked, but no CSKED
	 ECSKED			;Else leave the critical section
	RET			;Return to caller
IFN DEBUGF,<
;PUPLOK - Sanity checking for the PUP locking code
;Used when we're loosing buffers for some reason

PUPLOK:	SAVET			;Preserve some registers
	SKIPL UNIT		;Sanity check value of Pup port
	 CAIL UNIT,NPUPUN	; ...
	  RET			;Doesn't look like a port number
	SKIPN PUPBSP(UNIT)	;Is this BSP?
	 RET			;No, don't expect port to be locked.
	MOVE T1,PUPLKF(UNIT)	;Get FORKX of last locker
	MOVE T3,PUPLCK(UNIT)	;Load T3 just in case we are OKINT
	SKIPL T2,INTDF		;Make sure we are NOINT.
	 SKIPGE T3,PUPLCK(UNIT)	;Is the port locked?
	  TRNA			;We've found a bug
	   RET			;Both NOINT and port is locked
	MOVE CX,-5(P)		;Get PC of our location
	BUG.(CHK,KSLQQQ,PUP,SOFT,<PUP - bad port status>,<<CX,PC>>)
	MOVE T4,NVTLCK		;Get NVT lock word
	DMOVEM T1,QFORKX	;Stash forkx of last locker, INTDF word
	DMOVEM T3,QPUPLC	;Stash lock count of port, NVT lock word
	MOVEM UNIT,QUNIT	;Stash UNIT number of port
	MOVEM IOS,QIOS		;Stash IOS word of port
	MOVEI T1,QSTACK		;Destination of stack contents
	HRLI T1,-20(P)		;Location of first stack address
	BLT T1,QSTACK+30-1	;Copy lots of stack into our area
	RET			;Return to caller

RS QSTACK,50			;Contents of stack
RS QFORKX,1			;FORKX of port locker
RS QINTDF,1			;State of INTDF
RS QPUPLC,1			;Lock count of port
RS QNVTLC,1			;Lock count of NVT resource
RS QUNIT,1			;UNIT
RS QIOS,1			;IOS
>;IFN DEBUGF
;BSPTMO - Generate BSP error timeout
;Assumes port is locked and ac's setup
;Returns +1
;Clobbers T1,T2

BSPTMO:	TXON IOS,BSTIMF  	;Ignore if already timed out
	 CALL PUPSTC		;Generate state change interrupt
	RET    
;ETHBYT - range check a byte size, return byte table index
;Takes	T1/ byte size
;Returns +1 illegal byte size
;	 +2 good byte size, T2/ index into byte table
;Clobbers T1-T2

ETHBYT:	SETO T2,		;Use T2 as flag for bad byte size
	CAIN T1,^D6		;6
	 MOVEI T2,0
	CAIN T1,^D7		;7
	 MOVEI T2,1
	CAIN T1,^D8		;8
	 MOVEI T2,2
	CAIN T1,^D9		;9
	 MOVEI T2,3
	CAIN T1,^D18		;18
	 MOVEI T2,4
	JUMPL T2,R		;Jump if bad byte size
	SKIPN MEIMDF		;Skip if MEIS modes not allowed
	 CAIN T2,2		;No special modes.  8-bits only.
	  RETSKP		;Good return otherwise
	   RET			;Bad return if not 8-bit and no MEIS modes
;BSP8DT - set BSETHF if classic Ethernet data mode
;Assumes port is locked and ac's setup
;Clobbers T1-T3, IOS

BSP8DT:	LOAD T1,PUPMD		;Get data mode
	LOAD T2,PUPBZ		;Get byte index
	MOVE T2,OWGBYT(T2)	;Get byte size
	CAIN T1,.PM32		;32-bit data mode?
	CAIE T2,^D8		;And 8-bit bytes?
	IFNSK.
	  TXZ IOS,BSETHF	;No, clear the flag
	ELSE.
	  TXO IOS,BSETHF	;Set the flag for sequential input
	ENDIF.
	RET			;Return to caller


;WAKBSI - Awaken BSP input if necessary
;Assumes port is locked and ac's setup
;Returns +1
;Clobbers T1, T2

WAKBSI:	TXO IOS,BSINPF  	;Flag input possible now
	CALL PNVINT		;Request PNV scan if necessary
	RET    
;BLDBSP - Build BSP data block
;Returns +1  failure (no room)
;	 +2  success, BSP/ pointer to block
;Clobbers T1-T4, BSP

BLDBSP:	CALL ASGBSP		;Assign a BSP data block
	 RET			;Can't, fail
	MOVE BSP,T1		;Put pointer in standard ac

	MOVE T2,[<DETINT*^D1000/10000>B15+BSPSIZ] ;Set header word
	MOVEM T2,BSPHDR(BSP)
	MOVX T2,RETINT		;Set retransmission interval
	STOR T2,BSPRTM

	MOVE T2,TODCLK		;Set time to now
	MOVEM T2,BSPACT(BSP)
	MOVEM T2,BSPSTM(BSP)
	MOVEM T2,BSPLST(BSP)

	HRLOI T2,377777		;Set timeout to infinity
	MOVEM T2,BSPDTM(BSP)
	MOVEM T2,BSPITM(BSP)
	MOVEM T2,BSPFTM(BSP)

	SETOM BSPCID(BSP)	;No connection ID yet

	MOVE T2,[<MXBSIP>B7+<MXPLEN-MNPLEN>B17+MXBSIB]
	MOVEM T2,BSPIAL(BSP)	;Default allocation parameters

	XMOVEI T2,BSPIBQ(BSP)	;Initialize input queue
	MOVEM T2,HEAD(T2)
	MOVEM T2,TAIL(T2)
	XMOVEI T2,BSPOBQ(BSP)	;Initialize output queue
	MOVEM T2,HEAD(T2)
	MOVEM T2,TAIL(T2)
	RETSKP			;Skip return to caller
SUBTTL Rendevous Termination Protocol (RTP)

;Port states

S.CLOS==0	;Closed
S.RFCO==1	;RFC Outstanding
S.LIST==2	;Listening
S.OPEN==3	;Open
S.ENDI==4	;End In
S.ENDO==5	;End Outstanding
S.DALY==6	;Dallying
S.ABOR==7	;Abort

NPSTAT==10	;Number of states
ALLSTT==-1B<NPSTAT-1> ;Bit mask corresponding to all states


;Events

E.OPNC==0	;OPENF mode 0 or 1 (connect)
E.OPNL==1	;OPENF mode 2 or 3 (listen)
E.OPNN==2	;OPENF mode 4 (no rendezvous)
E.CLSN==3	;Normal CLOSF
E.CLST==4	;CLOSF after timeout
E.RRFC==5	;Received RFC
E.RABT==6	;Received Abort
E.REND==7	;Received End
E.RENR==10	;Received End Reply
E.TIMO==11	;Timeout (for retransmissions)


;Actions

A.NOOP==0	;No action
A.SRF1==1	;Send RFC (initiate connection)
A.SRF2==2	;Send RFC (respond to incoming RFC)
A.OPNC==3	;Open connection
A.SEND==4	;Send End
A.SENR==5	;Send End Reply
A.SABT==6	;Send Abort
A.LERR==7	;Local error (improper locally-generated event)
A.FERR==10	;Foreign error (improper Pup type received)


A.==<S.==0>	;For unused fields in action/transition matrices
;Routines to generate events
;All assume the port is locked and ac's UNIT, BSP, IOS setup.
;If the event is generated by an incoming Pup, PB points to the
; packet buffer.


;Routine called from DOBSPI upon receipt of an RFC
;	PB/ Packet buffer pointer
;Returns +1 always
;Clobbers T1-T4, releases packet buffer always

RCVRFC:	LOAD T1,PUPLEN		;Get Pup Length
	CAIL T1,MNPLEN+6	;Make sure big enough
	IFSKP.
	  CALL PUPBUG		;Too small
	   BUG.(INF,RTPRF1,PUP,SOFT,<PUP - RFC too small>,<<T1,SIZE>,<T4,H>>)
	  RET
	ENDIF.
	CALL GTCPRT		;Get Connection Port
	IFNSK.
	  CALL PUPBUG		;Incorrect connection port
	   BUG.(INF,RTPRF2,PUP,SOFT,<PUP - Bad RFC>,<<T4,HOST>>)
	  RET
	ENDIF.
	LOAD T4,PBSTT		;Get current port state
	CAIN T4,S.RFCO		;RFC Out?
	 JRST RCVRF1		;Yes
	CAIN T4,S.LIST		;Listening?
	 JRST RCVRF2		;Yes

;Open (or End Out), probably a retransmission
	CALL CHKSR1		;See if matches foreign port
	IFNSK.
	  CALL PUPBUG		;Non-matching connection port
	   BUG.(INF,RTPRF3,PUP,SOFT,<PUP - Bad RFC>,<<T4,HOST>>)
	  RET
	ENDIF.
	CALL GETPID		;Get Pup ID
	CAMN T1,BSPCID(BSP)	;Compare to Connection ID
	IFSKP.
	  CALL PUPBUG		;Retransmitted RFC with incorrect ID
	   BUG.(INF,RTPRF4,PUP,SOFT,<PUP - Bad RFC>,<<T4,HOST>>)
	  RET
	ENDIF.
	TXNN IOS,BSLISF  	;Formerly in listening state?
	 JRST RCVRF4		;No, discard packet and ignore
	JRST RCVRF3		;Yes, go generate event

;RFC Out, ID must match our Connection ID
RCVRF1:	CALL GETPID		;Get Pup ID
	CAMN T1,BSPCID(BSP)	;Compare to Connection ID
	IFSKP.
	  CALL PUPBUG		;RFC with incorrect ID
	   BUG.(INF,RTPRF5,PUP,SOFT,<PUP - RFC with incorrect ID>,<<T4,HOST>>)
	  RET
	ENDIF.

;Listening or RFC Out, Source Port must pass address filter
RCVRF2:	CALL CHKSRC		;Check Source Port
	IFNSK.
	  CALL PUPBUG		;RFC from incorrect source port
	   BUG.(INF,RTPRF6,PUP,SOFT,<PUP - Bad RFC>,<<T4,HOST>>)
	  RET
	ENDIF.
RCVRF3:	MOVEI T1,E.RRFC		;Ok, generate Received RFC event
	CALL PUPFSM
RCVRF4:	CALLRET RELPKT		;Release packet buffer and return
;Routines called from DOBSPI upon receipt of an End or End Reply
;Takes	PB/ Packet buffer pointer
;Returns +1 always
;Clobbers T1-T4, releases packet buffer always

RCVEND:	SKIPA T1,[E.REND]	;Generate Received End event
RCVENR:	MOVEI T1,E.RENR		;Generate Received End Reply event
	CALL PUPFSM
	CALLRET RELPKT		;Release packet buffer and return


;Routine called from DOBSPI upon receipt of an Abort
;	PB/ Packet buffer pointer
;Returns +1 always
;Clobbers T1-T4, disposes of packet buffer always

RCVABT:	MOVEI T1,E.RABT		;Generate Received Abort event
	CALL PUPFSM
	SKIPE BSPABP(BSP)	;See if already have Abort packet
	 JRST RELPKT		;If so, just discard this
	MOVEM PB,BSPABP(BSP)	;Don't have one, store this
	RET    
;Routine to step FSM for port
;	T1/ Event number
;	T3, T4/ Arguments for action routine, if appropriate
;	UNIT/ Pup unit #
;	BSP/ BSP data block ptr
;	IOS/ Port status word
;	PB/ Pointer to incoming packet, if any
;Returns +1
;Clobbers T1-T4, updates others where appropriate

PUPFSM:	CALL PUPFSC		;Do the work
	CALL SETPTM		;Set timer appropriately
	RET    

;Enter here from DOBSP only
PUPFSC:	PUSH P,PB		;Save packet buffer ptr if any
	PUSH P,T1		;Save event number
	LOAD T2,PBSTT		;Get current port state
	LDB T1,PFSACT(T2)	;Get action index
	XCT EVNACT(T1)		;Do action associated with event
	POP P,T1		;Recover event number
	LOAD T2,PBSTT		;Get current port state
	LDB T1,PFSTRN(T2)	;Get successor from transition tbl
	STOR T1,PBSTT		;Store in port status word
	STOR T1,PBSTM		;Store in memory also
	CAIE T1,S.CLOS		;Entering Closed or Abort state?
	 CAIN T1,S.ABOR
	 TXO IOS,BSERRF  	;Yes, signal bad state for BSP
	CAIN T1,(T2)		;State changed?
	 JRST PUPFS2		;No
	MOVE T2,TODCLK		;Yes, record time of state change
	MOVEM T2,BSPSTM(BSP)
	CAIE T1,S.OPEN		;Entering Open state?
	 JRST PUPFS1		;No, skip around this
	MOVEM T2,BSPDTM(BSP)	;Yes, activate data probing
	MOVEM T2,BSPATM(BSP)	;Sent an AData very soon
PUPFS1:	CALL PUPSTC		;Generate state change psi
PUPFS2:	POP P,PB		;Restore packet buffer ptr
	LOAD T1,PBSTT		;Get new state
	CAIE T1,S.RFCO		;RFC or End Outstanding?
	 CAIN T1,S.ENDO
	  IFNSK.
  	    MOVE T1,BSPSTM(BSP) ;Yes, get time of last state change
	    CALL NXTTIM		;Compute time for next check
	    JRST PUPFS3		;Go set it
	   ENDIF.
	CAIE T1,S.DALY		;No, dallying?
	IFSKP.
	  LOAD T1,BSPRTM	;Yes, get retransmission timeout
	  IMULI T1,5		;Dally this long
	  CAILE T1,PRBINT	;Too long?
	   MOVEI T1,PRBINT	;Yes, use maximum delay
	  ADD T1,TODCLK		;Compute time of next check
	  JRST PUPFS3		;Go set it
	ENDIF.
	HRLOI T1,377777		;Set timer to infinity for other states
PUPFS3:	MOVEM T1,BSPFTM(BSP)	;Store time of next check
	RET    
;Byte pointers for accessing action and transition tables
;Indexed by current state #, expects event # in A

PFSACT:
REPEAT NPSTAT,<
	POINT 4,FSMACT(T1),4*<.-PFSACT>+3
>


PFSTRN:
REPEAT NPSTAT,<
	POINT 4,FSMTRN(T1),4*<.-PFSTRN>+3
>
;Action table - Event yields row, current state yields column

DEFINE XX ($1,$2,$3,$4,$5,$6,$7,$8,$9) <
	BYTE(4) A.'$1, A.'$2, A.'$3, A.'$4, A.'$5, A.'$6, A.'$7, A.'$8, A.'$9
>

;Current state						;Event
;	   CLOS RFCO LIST OPEN ENDI ENDO DALY ABOR

FSMACT:	XX SRF1,LERR,LERR,LERR,LERR,LERR,LERR,LERR	;OPNC
	XX NOOP,LERR,LERR,LERR,LERR,LERR,LERR,LERR	;OPNL
	XX NOOP,LERR,LERR,LERR,LERR,LERR,LERR,LERR	;OPNN
	XX NOOP,SABT,NOOP,SEND,SENR,NOOP,NOOP,NOOP	;CLSN
	XX NOOP,SABT,NOOP,SABT,SABT,SABT,NOOP,NOOP	;CLST
	XX FERR,OPNC,SRF2,SRF2,FERR,SRF2,FERR,FERR	;RRFC
	XX FERR,NOOP,FERR,NOOP,NOOP,NOOP,NOOP,NOOP	;RABT
	XX FERR,FERR,FERR,NOOP,NOOP,SENR,SENR,FERR	;REND
	XX NOOP,FERR,FERR,FERR,FERR,SENR,NOOP,FERR	;RENR
	XX NOOP,SRF1,NOOP,NOOP,NOOP,SEND,NOOP,NOOP	;TIMO

;Transition table
;Event yields row, current state yields column

DEFINE XX ($1,$2,$3,$4,$5,$6,$7,$8,$9) <
	BYTE(4) S.'$1, S.'$2, S.'$3, S.'$4, S.'$5, S.'$6, S.'$7, S.'$8, S.'$9
>

;Current state						;Event
;	   CLOS RFCO LIST OPEN ENDI ENDO DALY ABOR

FSMTRN:	XX RFCO,RFCO,LIST,OPEN,ENDI,ENDO,DALY,ABOR	;OPNC
	XX LIST,RFCO,LIST,OPEN,ENDI,ENDO,DALY,ABOR	;OPNL
	XX OPEN,RFCO,LIST,OPEN,ENDI,ENDO,DALY,ABOR	;OPNN
	XX CLOS,CLOS,CLOS,ENDO,DALY,ENDO,DALY,CLOS	;CLSN
	XX CLOS,CLOS,CLOS,CLOS,CLOS,CLOS,CLOS,CLOS	;CLST
	XX CLOS,OPEN,OPEN,OPEN,ENDI,ENDO,DALY,ABOR	;RRFC
	XX CLOS,ABOR,LIST,ABOR,ABOR,ABOR,ABOR,ABOR	;RABT
	XX CLOS,RFCO,LIST,ENDI,ENDI,DALY,DALY,ABOR	;REND
	XX CLOS,RFCO,LIST,OPEN,ENDI,CLOS,CLOS,ABOR	;RENR
	XX CLOS,RFCO,LIST,OPEN,ENDI,ENDO,CLOS,ABOR	;TIMO

;Table of actions in event/action matrix

EVNACT:	NOP			;NOOP - No action
	CALL SNDIRF		;SRF1 - Send initiating RFC
	CALL SNDARF		;SRF2 - Send answering RFC
	CALL OPNCON		;OPNC - Open connection
	CALL SNDEND		;SEND - Send End
	CALL SNDENR		;SENR - Send End Reply
	CALL ABORT		;SABT - Send Abort
	CALL LCLERR		;LERR - Local error
	CALL FORERR		;FERR - Foreign error
;Action routines
;All have the following calling sequence:
;	UNIT/ Pup unit #
;	BSP/ BSP data block ptr
;	IOS/ BSP status
;	PB/ Pointer to received packet buffer, if appropriate
;Returns +1 always
;May clobber T1-T4, PB


;SNDIRF - Send initiating RFC

SNDIRF:	MOVEI T1,PBHEAD+<MNPLEN+6+3>/4
	CALL ASGPKT		;Allocate packet buffer for RFC
	 RET			;Failed, forget it
	MOVEI T1,MNPLEN+6	;Set Pup Length
	STOR T1,PUPLEN
	SKIPL T1,BSPCID(BSP)	;Connection ID already assigned?
	 JRST SNDIR1		;Yes, use it (retransmission)
	MOVE T1,TODCLK		;No, get now in milliseconds
	LSH T1,7		;Convert to units of ~8 usec
	CALL SETCID		;Set Connection ID
SNDIR1:	CALL SETPID		;Set Pup ID
	CALL STCPRT		;Set Connection Port = local port
	MOVEI T1,PT.RFC		;Type = RFC
	CALL SNDPUP		;Finish up and send the Pup
	 NOP			;Ignore failure
	RET    
;SNDARF - Send answering RFC

SNDARF:	SKIPL BSPCID(BSP)	;Connection parameters already set?
	 JRST SNDAR1		;Yes, bypass this
	CALL GETPID		;Get Pup ID from incoming RFC
	CALL SETCID		;Set Connection ID
	LOAD T1,PUPDN		;Copy Destination net/host to
	STOR T1,PRTLN,(UNIT)	; local net/host in case wildcard
	LOAD T1,PUPDH
	STOR T1,PRTLH,(UNIT)
	CALL OPNCON		;Set foreign port for connection
SNDAR1:	STKVAR <SNDPBO>
	MOVEM PB,SNDPBO		;Save ptr to received RFC
	MOVEI T1,PBHEAD+<MNPLEN+6+3>/4
	CALL ASGPKT		;Allocate packet buffer for reply
	 RET			;Failed, forget it
	MOVE T1,SNDPBO		;Ok, recover ptr to received RFC
	MOVSI T1,PBHEAD(T1)	;Copy the header
	HRRI T1,PBHEAD(PB)	; into the answering RFC
	BLT T1,PBCONT-1(PB)
	CALL SWPPRT		;Swap source and dest ports in RFC
	CALL STCPRT		;Set Connection Port = local port
	CALL SNDPU1		;Finish up and send the Pup
	 NOP			;Ignore failure
	RET    
;Action routines (cont'd)

;Open connection in response to answering RFC

OPNCON:	CALL GTCPRT		;Get Connection Port from RFC
	 BUG.(HLT,OPNCOZ,PUP,SOFT,<PUP - GTCPRT failed unaccountably>)
	CALL STFPRT		;Set foreign port for connection
	RET    

;Send End

SNDEND:	MOVEI T1,PT.END		;Type = End
	JRST SNDEN1		;Jump to common code


;Send End Reply

SNDENR:	MOVEI T1,PT.ENR		;Type = End Reply
SNDEN1:	STKVAR <SNDENT>		;Type
	MOVEM T1,SNDENT		;Save type
	MOVEI T1,MNPBLX		;Minimum length
	CALL ASGPKT		;Allocate packet buffer
	 RET			;Failed, forget it
	MOVEI T1,MNPLEN		;Set Pup Length
	STOR T1,PUPLEN
	MOVE T1,BSPCID(BSP)	;Get Connection ID
	CALL SETPID		;Set Pup ID
	MOVE T1,SNDENT		;Recover Pup Type
	CALL SNDPUP		;Finish up and send the Pup
	 NOP			;Ignore failure
	RET			;Return to caller
;Send Abort
;	T3/ Abort Code (B0 set =) call from monitor)
;	T4/ If nonzero, string ptr to Abort Text in caller space

ABORT:	MOVE T1,T3		;Copy args to proper ac's
	MOVE T2,T4
	CALL SNDABT		;Build and send the Abort
	RET    


;Report local error

LCLERR:	CALL PUPBG		;Skip over bug if not logging
	 BUG.(INF,FSMLER,PUP,SOFT,<PUP - Bad lcl state>,<<T1,I>,<T2,S>,<T4,H>>)
	RET

;Report foreign error.

FORERR:	CAIN T2,S.LIST		;Are we listening?
	 RET			;Yes, probably a server confused with a user
	CALL PUPBG		;Skip over bug if not logging
	 BUG.(INF,FSMFER,PUP,SOFT,<PUP - Bad fgn state>,<<T1,I>,<T2,S>,<T4,H>>)
	RET
;GTCPRT - Get Connection Port parameters from RFC Pup
;	PB/ Packet buffer pointer
;Returns +1:  Error, illegal address
;	+2:  Ok,  T1/ Net, T2/ Host, T3/ Socket, right-justified

GTCPRT:	MOVE T1,PBCONT(PB)	;Get net/host/high socket
	LSHC T1,-^D28		;Right-justify net
	LSH T2,-^D12		;Right-justify host/high socket
	MOVE T3,PBCONT+1(PB)	;Get low socket
	LSHC T2,-^D16		;Right-justify host
	LSH T3,-4		;Concatenate, right-justify socket
	SKIPN T1		;Net specified?
	 LOAD T1,PUPSN		;No, assume same as Rendezvous
	JUMPE T2,R		;Error if zero host
	JUMPE T3,R		;Error if zero socket
	RETSKP			;Ok, take success return


;STCPRT - Set Connection Port = local port in outgoing RFC
;	PB/ Packet buffer pointer
;	UNIT/ Pup unit #
;Returns +1 always
;Clobbers T1

STCPRT:	LOAD T1,PRTLN,(UNIT)	;Get local net
	LSH T1,^D28		;Bump it into place
	LOAD T2,PRTLH,(UNIT)	;Get local host
	LSH T2,^D20		;Shift it over as well
	IOR T1,T2		;OR them together
	SKIPN T1		;Make sure local port fully specified
	 BUG.(HLT,STCPRZ,PUP,SOFT,<PUP - STCPRT called for wildcard port>)
	MOVEM T1,PBCONT(PB)	;Store in Connection Port field	
	MOVE T1,PUPLSK(UNIT)	;Get local socket
	ROT T1,-^D16		;Right-justify high 16 bits
	DPB T1,[POINT 16,PBCONT(PB),31] ;Store in the RFC
	MOVEM T1,PBCONT+1(PB)	;Store low 16 bits
	RET			;Return to caller
;STFPRT - Store foreign connection port
;	T1/ Net, T2/ Host, T3/ Socket, right-justified
;Assumes port is locked
;Returns +1
;Clobbers T1-T4

STFPRT:	SKIPN T4,PUPFPT(UNIT)	;Get pointer to address table
	 BUG.(HLT,STFPRZ,PUP,SOFT,<PUP - No address table assigned>)
	HRLM T1,1(T4)		;Store foreign net
	HRRM T2,1(T4)		;Store foreign host
	MOVEM T3,2(T4)		;Store foreign socket
	MOVEI T1,2		;Just one address in table
	MOVEM T1,0(T4)		;Stash it
	RET			;Return to caller

;SETCID - Set Connection ID
;	T1/ Connection ID, right-justified
;Assumes port is locked
;Returns +1

SETCID:	TXZ T1,-1B3  		;Truncate to 32 bits
	MOVEM T1,BSPCID(BSP)	;Set Connection ID
	MOVEM T1,BSPILW(BSP)	;Init input Byte ID
	MOVEM T1,BSPOLW(BSP)	;Init output Byte ID
	MOVEM T1,BSPRII(BSP)	;Init receive Interrupt ID
	MOVEM T1,BSPSII(BSP)	;Init send Interrupt ID
	RET    
;PUPSTC - Generate Pup state change interrupt
;	T1/ New state
;	UNIT/ Pup unit #
;	BSP/ BSP data block ptr
;Returns +1 always
;Clobbers T1, B

PUPSTC:	HRRE T2,PUPPSI(UNIT)	;Get port owner
	JUMPL T2,PUPST1		;Jump if not fork
	LOAD T1,STCPSI		;Get state change PSI channel
	CAIGE T1,^D36		;Assigned?
	 CALL PSIRQF		;Yes, initiate PSI
PUPST1:	CALL WAKBSI		;Awaken fork or NVT processor
	RET    


;WATSTT - Setup to wait for port to enter a specified state (or time out)
;	T1/ Bit mask of desired state(s) [STTBTS macro]
;Assumes port is locked
;Returns +1:  Port not yet in specified state, T1/ MDISMS argument
;	+2:  Port already in specified state
;Clobbers T1, T2

WATSTT:	LOAD T2,PBSTT		;Get current state
	TDNN T1,BITS(T2)	;Already in specified state?
	 TXNE IOS,BSTIMF!BSERRF	;Timed out or violent death?
	  RETSKP		;Yes, give skip return
	HRRI T1,STTTST		;No, set scheduler test
	TLO T1,(UNIT)  		;Set Pup unit index
	RET    			;Non-skip return



;STTTST - test for entering specified state(s)
;Arg is state bit mask in B18-26, Pup unit number in B27-35
;Callers are: WATSTT (PUPOPN and PUPCLZ are blocking routines)

	RESCD

STTTST:	MOVSI T2,(T1)		;Copy bit mask
	ANDI T1,777		;Isolate Pup unit number
	MOVE T3,PUPSTS(T1)	;Get status word
	LOAD T1,PBSTA		;Get port state
	TDNN T2,BITS(T1)	;Now in specified state(s)?
	 TXNE T3,BSTIMF!BSERRF	;Timeout or error?
	 JRST 1(T4)		;Yes, wakeup
	JRST 0(T4)		;No, wait

	XSWAPCD
SUBTTL PUP NVT handling

;PU7NVT- Handle PNV I/O after BSP processing has been done
;Called from pup background fork (despite the name)
;Note on flushing unassigned PNV's.  If TTYPUP word is zero, we assume
; an ASND% has been done and that we shouldn't molest the PNV.
;Returns +1 always
;Clobbers T1-T4,E,UNIT

PU7NVT:	SETZM PNVFLG		;Clear PNVFLG
	CALL GTNVLK		;Get NVT lock
	 RET			;Can't, quit having set deferred scan flag
	MOVSI E,-NPUPPN		;Set up bit table word counter 
PU7NV0:	SETZ T1,		;Get a zero word ready 
	EXCH T1,PUPPNV(E)	;Swap zero for a possible bit mask
	JUMPE T1,PU7NV3		;Try next word if no bits set 
	MOVEM T1,PNVFL0		;Save bit mask
PU7NV4: JFFO T1,PU7NV1		;Jump if we find a bit set
	 JRST PU7NV3		;No bits set, try next word
PU7NV1:	MOVE T1,BITS(T2)	;Get the appropriate bit
	ANDCAM T1,PNVFL0	;Clear the flag
	MOVEI T1,^D36		;One word worth of offset
	IMULI T1,(E)		;Times number of words
	ADDI T2,(T1)		;Add offset onto jffo bit to get PNV number
	CAIL T2,NTTPNV		;A valid PNV offset?
	 JRST PU7NV5		;No, ignore it
	ADD T2,PNVTTY		;Add TTY number of first PNV
  	CALL LCKTTY		;Lock down dynamic data
	 JUMPE T2,PU7NV2	;Nothing interesting there, go unlock
	SKIPG TTYPUP(T2)	;Abandoned PNV?
	IFSKP.
	  PUSH P,T2		;Save address of dynamic data
	  CALL DYNSTA		;Get back static line number
	  EXCH T2,0(P)		;Swap static and dynamic
	  CALL ULKTTY		;Unlock dynamic data
	  POP P,T2		;Restore static line number
	  LOKK(DEVLKK,<JRST PU7NV5>)	;Try for device lock 
	  CALLM TTYDAS		;Deassign the TTY dynamic data block
	   NOP			;Ignore an error return
	  UNLOKK DEVLKK		;Unlock the device lock
	  JRST PU7NV5		;On to next PNV
	ENDIF.
	SKIPE TTYPUP(T2)	;No NVT processing if held by ASND%
	 CALL DONVTP		;Do NVT processing
PU7NV2:	CALL ULKTTY		;Unlock dynamic data
PU7NV5:	MOVE T1,PNVFL0		;Get back mask of lines to look at
	JUMPN T1,PU7NV4		;Loop back if more lines to process
PU7NV3:	AOBJN E,PU7NV0		;Advance to next word in the bit table
	CALL RLNVLK		;Release the NVT lock
	RET			;All done, return to caller
;PNVINT - set bit table flags for PNV input/output
;Call at interrupt, scheduler, or process level
;Takes	UNIT/ port to interrupt
;Enter at PNVIN0 with static line number in T3 
;Clobbers T1,T3,T4; preserves T2!

	XRESCD

PNVINT:	HRRE T1,PUPPSI(UNIT)	;Extend sign of NVT or fork assignment
	AOJGE T1,R		;Punt if we belong to a fork or are unassigned
	MOVEI T3,<-.TTDES-1>(T1) ;Isolate TTY number
PNVIN0::SUB T3,PNVTTY		;Convert to PNV number
	CAIL T3,NTTPNV		;Range check
	 JRST PNVINZ		;We were handed garbage
	IDIVI T3,^D36		;Calculate word (T3) and bit (T4) offsets
	MOVE T1,BITS(T4)	;Get the bit
	IORM T1,PUPPNV(T3)	;And set it in the correct word
	AOS PNVFLG		;Set flag for scheduler
	RET			;Return to caller

;Here to set up an request in the deferred table

PNVIND:	HRRE T1,PUPPSI(UNIT)	;Extend sign of NVT or fork assignment
	AOJGE T1,R		;Punt if we belong to a fork or are unassigned
	MOVEI T3,<-.TTDES-1>(T1) ;Isolate TTY number
PNVND0:	SUB T3,PNVTTY		;Convert to PNV number
	CAIL T3,NTTPNV		;Range check
	 JRST PNVINZ		;We were handed garbage
	IDIVI T3,^D36		;Calculate word (T3) and bit (T4) offsets
	MOVE T1,BITS(T4)	;Get the bit
	IORM T1,PUPPND(T3)	;And set it in the correct word
	SIGDEF(NVT)		;Deferred NVT scan
	RET			;Return to caller

;Here for PNVIBE and friends

PNVIN1:	SAVET			;Don't clobber any AC's at all
	CALL DYNSTA		;Convert to static line number
	MOVE T3,T2		;Get line number in place 
	CALL PNVIN0		;Call main routine
	RET			;Return to caller

PNVINZ: BUG.(CHK,PNVINX,PUP,SOFT,<PUP - bad PNV offset>,<<T3,D>,<UNIT,D>>)
	RET			;Just punt if we were handed garbage

	XSWAPCD
;DONVTP - Do Pup NVT processing for one line
;Note that doing input first and then output generally ensures
; that echos will be transmitted immediately.
;Takes	T2/ Dynamic data pointer
;Returns +1 always
;Clobbers T1, T3, T4, BSP, IOS, UNIT;  preserves T2

DONVTP:	SAVEAC <T2>		;Preserve dynamic data
	SKIPL UNIT,TTYPUP(T2)	;Assigned?
	 RET			;No, do nothing 
	HRRZ UNIT,UNIT		;Flush unwanted bits
	LOCK(PRTLCK,<JRST DONVP1>) ;Lock out changes to port table
	CALL LCKBSA		;Attempt to lock the port
	 JRST DONVP0		;Failed, forget it
	UNLOCK(PRTLCK)		;Ok, unlock table
	MOVX T1,HUREQF		;Get the flag for a hangup
	TDNN T1,TTYPUP(T2)	;Hangup occuring?
	 TXNE IOS,BSERRF!BSTIMF	;Or errors?
	  JRST DONVP2		;Yes, see if we can close the connection
	TXNE IOS,BSINPF  	;Input pending?
	 CALL DONVTI		;Yes, process it
	CALL PPSOUT		;Output pending?
	 CALL DONVTO		;Yes, process it
	  NOP			;Ignore failure return
	CALL ULKBSP		;Unlock the BSP port
	RET			;Return to caller

;Here if we failed to obtain a lock.  Schedule another (deferred) pass.

DONVP0: UNLOCK(PRTLCK)		;Unlock port
DONVP1:	CALL PNVIND		;Request another pass at this PNV
	RET			;Return to caller

;Here if the PNV is going away.  If we are unable to close the connection
; right away, don't schedule a deferred scan since we can't do anything
; until the other side sends a packet which then will wake us up anyway.

DONVP2:	CALL CLPNVT		;Try to close connection
	 JRST ULKBSP		;Port not closed, unlock it and quit
	ECSKED			;Leave critical section (got there by LCKBSA)
	CALL DYNSTA		;Get static line number into T2
	CALL NTYCOF		;Generate a carrier-off PSI
	RET			;All done
;DONVTI - Process NVT input
;Takes	T2/ Pointer to dynamic data
;	UNIT/ Pup unit number
;Assumes port is locked
;Returns +1 always
;Clobbers T1, T3, T4, PB;  preserves T2

DONVTI:	LOAD T1,PNVSTT		;Get current NVT input state
	JRST @NVTSTD(T1)	;Dispatch on state

;Dispatch table for current NVT state
NVTSTD:	DSP (NVTDAT)		;PN.DAT - Normal data
	DSP (NVTMRK)		;PN.MRK - Mark pending
	DSP (NVTSYN)		;PN.SYN - Sync in progress

;Dispatched-to code should jump to one of these labels when done

NVTST0:	MOVX T1,PN.DAT		;PN.DAT - state 0 - normal data
	JRST DONVTX
NVTST1:	MOVX T1,PN.MRK		;PN.MRK - state 1 - Mark pending
	JRST DONVTX
NVTST2:	MOVX T1,PN.SYN		;PN.SYN - state 2 - Sync in progess
DONVTX:	STOR T1,PNVSTT		;Update PNV state
	RET			;Return to caller of DONVTI
;NVTDAT - process normal data for a PNV
;Dispatch to the routine according the PNV state

NVTDAT:	CALL GNVBYT		;No, get next byte
	 JRST NVTST0		;No more (state := 0)
	  JRST NVTMR1		;Mark encountered, go process it
	NOSKED			;Ensure TTY data block consistency
	CALL TTCHID		;Stuff character into TTY input buffer
	 NOP			;Ignore error return
	OKSKED			;Reallow scheduling
	JRST NVTDAT		;Repeat
;NVTSYN - here for sync in progress and after processing a Mark

NVTSYN:	LOAD T1,PSYNCT		;Check sync counter
	JUMPE T1,NVTDAT		;Resume processing if now balanced
	CAIE T1,SYNCNT/<SYNCNT&-SYNCNT> ;Skip if -1 (waiting for Int)
NVTSY1:	 CALL GNVBYT		;Scanning for DM, get next byte
	 JRST NVTST2		;No more (state := 2)
	 JRST NVTMR1		;Mark encountered, process it
	JRST NVTSY1		;Normal data, flush it


;NVTMRK - process a pending Mark
;Enter at NVTMR1 when the Mark is first encountered, T1/ Mark byte

NVTMRK:	LOAD T1,PNVMRK		;Recover the Mark byte
NVTMR1:	CAIL T1,NNVMRK		;See if in range
	 JRST NVTSYN		;No, ignore
	JRST @MRKDSP(T1)	;Yes, perform operation

;Dispatch table for received Mark types
;Code should finish by going to NVTST1 if must defer,
; NVTSYN normally (to check sync count)

MRKDSP:	DSP (NVTSYN)		;(0) Unassigned
	DSP (NVTDM)		;(1) MK.DAT - Data Mark
	DSP (NVTLW)		;(2) MK.WID - Line Width
	DSP (NVTPL)		;(3) MK.LEN - Page Length
	DSP (NVTTYP)		;(4) MK.TYP - Terminal Type
	DSP (NVTTMK)		;(5) MK.TIM - Timing Mark
	DSP (NVTTMR)		;(6) MK.TMR - Timing Mark Reply
NNVMRK==.-MRKDSP		;Number of Mark types recognized
;DONVTI (cont'd)
;Code to process specific Mark types

;Process Data Mark (MK.DAT, Mark type 1)
NVTDM:	MOVEI T1,SYNTMO		;Reset sync timer
	STOR T1,PSYNTM
	LOAD T1,PSYNCT		;Get sync count
	SUBI T1,1		;DM counts as -1
	STOR T1,PSYNCT		;Put it back
	CALL TTCIBF		;Clear TTY input buffer
	JRST NVTSYN		;Go reconsider state

;Set Line Width (MK.WID, Mark type 2)
NVTLW:	CALL GNVBYT		;Get argument byte
	 JRST NVTST1		;Can't, defer (state _ 1)
	 JRST NVTMR1		;Another Mark, ignore this
	CALLM TTSWID		;Store in TTY status
	 JFCL			;Not a legal width, leave as is
	JRST NVTSYN		;Done

;Set Page Length (MK.LEN, Mark type 3)
NVTPL:	CALL GNVBYT		;Get argument byte
	 JRST NVTST1		;Can't, defer (state _ 1)
	 JRST NVTMR1		;Another Mark, ignore this
	CALLM TTSLEN		;Store in TTY status
	 JFCL			;Length is illegal leave as is
	JRST NVTSYN		;Done


;Set Terminal Type (MK.TYP, Mark type 4)
NVTTYP:	CALL GNVBYT		;Get argument byte
	 JRST NVTST1		;Can't, defer (state _ 1)
	 JRST NVTMR1		;Another Mark, ignore this
	CALL PUPSTP		;Set terminal type
	JRST NVTSYN		;Done
;Timing Mark (MK.TIM, Mark type 5)
NVTTMK:	PUSH P,T2		;Save dynamic data pointer
	CALL DYNSTA		;Convert dynamic to static for GTWFRK
	CALL GTWFRK		;Get fork waiting for input
	CAIN T1,-1		;Is there one?
	 JRST NVTTM1		;No.
	MOVEI T1,MK.TMR		;Generate immediate timing mark reply
	CALL SNDAMA
	 JRST [	POP P,T2	;Can't, defer processing of timing mark
		JRST NVTST1]	;(State := 1)
	POP P,T2		;Ok, recover dynamic data
	JRST NVTSYN		;Done

;Here if no fork waiting.  Increment count so as to cause reply
;to be generated when somebody exhausts the input buffer.
NVTTM1:	POP P,T2		;Restore dynamic data pointer
	LOAD T1,PTMKCT		;Get current count of timing marks
	ADDI T1,1		;Increment it
	STOR T1,PTMKCT
	JRST NVTSYN		;Done

;Timing Mark Reply (MK.TMR, Mark type 6)
NVTTMR:	MOVX T1,TMKPNF		;Clear timing mark pending flag
	ANDCAM T1,TTYPUP(T2)	;This wakes up anywone waiting
	JRST NVTSYN		;Done
;GNVBYT - Get input byte for NVT
;Takes	T2/ pointer to line's dynamic data block
;Assumes port is locked and ac's setup
;This routine assumes the port has already been set up in .PM32 mode and so
;uses the PBIPTR and PBBSBC fields in the packet buffer in place of the
;analogous FILBYT and FILCNT fields used by the PUPSQI/PUPSQO routines.
;Returns +1:  Input exhausted
;	 +2:  Mark encountered, T1/ the byte (also stored via PNVMRK)
;	 +3:  Normal, T1/ the byte
;Clobbers T1,T3,T4, PB;  does not clobber T2

GNVBYT:	SKIPE PB,BSPCIP(BSP)	;Get current buffer ptr if any
	 JRST GNVBY2		;Jump if already have a buffer
GNVBY1:	PUSH P,T2		;Save dynamic data
	CALL GETBSP		;Get the next packet
	 JRST [ POP P,T2
		RET ]		;Input exhausted, return +1
	POP P,T2		;Restore dynamic data
	LOAD T1,PUPTYP		;Get Pup Type
	CAIE T1,PT.MRK		;Mark?
	 CAIN T1,PT.AMA		;AMark?
	  IFNSK.
	    ILDB T1,PBIPTR(PB)	;Yes, get the byte
	    STOR T1,PNVMRK	;Save away in case need to defer
	    SETZM PBBSBC(PB)	;No more bytes in this packet
	    RETSKP		;Return +2
	  ENDIF.
GNVBY2:	SOSGE PBBSBC(PB)	;Count down Ethernet bytes in packet
	 JRST GNVBY1		;Exhausted, try next packet
	ILDB T1,PBIPTR(PB)	;Get the next Ethernet byte
	JRST SK2RET		;Return +3
;DONVTO - Do NVT output processing for one line
;Takes	T2/ pointer to dynamic data for NVT
;Assumes port is locked
;Returns +1 can't send buffer
;	 +2 output sent, buffer is empty
;Clobbers T1, T3, T4, PB;  preserves T2

	XRESCD

DONVTO:	SAVEAC <T2>		;Preserve line number
	STKVAR <DONVTT>		;Declare local storage
	MOVEM T2,DONVTT		;Save line number for us
	CHKSTT <OPEN>		;Connection still open?
	 RET			;No, do nothing
	MOVE T2,DONVTT		;Get dynamic line number
	CALL PUPACT		;Set output active flag (lock out scheduler)
	NOSKED			;No timing races, please
	CHNOFF DLSCHN		;No TTY scanning
	CALL PPSOBE		;Normal TTY output?
	 JRST DONVT1		;No, go look for sendall output
DONVT0: CALL PNVOBP		;Get pointer to last character
	CALL DMPBSP		;Finish up, send the packet
	MOVE T2,DONVTT		;Get packet dynamic data pointer
	CALL PNVREL		;"Release" TTY buffer for this PNV
DONVT1:	CALL PPSALL		;Something still in the sendall buffer?
	 JRST DONVT3		;No, exit
	CALL PNVBFF		;Get another TTY buffer
	 JRST DONVT2		;Can't, must exit
	CALL PNVSAL		;Load up TTY buffer
	JRST DONVT0		;Go dump buffer

DONVT2:	CALL PNVIND		;Schedule a deferred PNV pass
DONVT3:	CALL PUPIAC		;Output is no longer active
	CHNON DLSCHN		;Reallow TTY scanning
	OKSKED			;Reallow scheduling
	RETSKP			;Return to caller

	XSWAPCD
;PNVDMP - dump an PNV output buffer from a user process
;Called from the TCOUT code when TCOUT would have failed for lack of
; output buffer space.
;Takes	T2/ dynamic data pointer
;Returns +1 buffer(s) sent, can do more output
;	 +2 must block for TCOTST

	XRESCD

XRENT PNVDMP,G
	SAVEAC <T1>		;Must preserve T1 for TCOUT code
	CALL LCKPN		;Lock the PNV
	 RETSKP			;Can't, take a *failure* return
	CALL DONVTO		;Dump the output
	IFNSK.
	  CALL ULKPNV		;No go, unlock the PNV
	  RETSKP		;Take failure return
	ENDIF.
	CALL ULKPNV		;Unlock the PNV
	RET			;Take a success return

	XSWAPCD
;PNVBUF - get a PUP buffer for TTY output
;Assumes caller is NOSKED with CHNOFF DLSCHN
;Takes	T2/ dynamic data pointer
;Returns +1 failure, T1/ scheduler test, or zero if dead connection
;	 +2 success, dynamic data block set up for fresh output
;Preserves T2

	XRESCD

XRENT PNVBUF,G
	CALL PNVSFG		;Check if we are in a page hold
	 RET			;We are, return with test in T1
	CALL LCKPN		;Lock the PNV's BSP data block
	 RET			;No go.  T1/ scheduler test or zero
	MOVE T1,TTYPUP(T2)	;Get status flags
	TXNN T1,HUREQF		;Hangup in progress?
	IFSKP.
	  CALL ULKPNV		;Yes.  Don't assign more output buffers.
	  JRST RETZ		;Take dead connection return
	ENDIF.
	CALL PNVBFF		;Call our helper routine
	IFNSK.
	  CALL ULKPNV		;Failure, must block.  Unlock BSP data block.
	  RET			;Take a single return
	ENDIF.
	CALL ULKPNV		;Unlock data block
	RETSKP			;Success return


;PNVBFF - helper routine for PNVBUF
;Called when the BSP connection is already locked
;Accumulators associated with BSP are already set up
;Takes T2/ dynamic data pointer
;Returns +1 failure, T1/ scheduler test
;	 +2 success, PNV output buffer set up
;Preserves T2

PNVBFF:	SAVEAC <T2>		;Preserve dynamic data
	STKVAR <DYNBFF,BYTBFF>	;Local storage
	MOVEM T2,DYNBFF		;Remember dynamic data pointer
	SKIPN PB,BSPCOP(BSP)	;Get pointer to current buffer
	IFSKP.
	  CALL RELPKT		;Release buffer (left over from CFOBF%, etc.)
	  SETZM BSPCOP(BSP)	;Clear old pointer
	ENDIF.
	CALL CHKBSO		;Check our allocation  
	 RET			;Return, T1/ scheduler test
	MOVEM T1,BYTBFF		;Remember byte allocation
	ADDI T1,3		;Round up
	LSH T1,-2		;Convert to words
	ADDI T1,PBCONT		;Add in overhead and header words
	CALL ASGPKT		;Allocate packet buffer
	 RET			;Return, T1/ scheduler test
	MOVEM PB,BSPCOP(BSP)	;Ok, save current buffer ptr
	MOVEI T1,PT.DAT		;Set Type = Data
	STOR T1,PUPTYP		; ...
	MOVE T1,BYTBFF		;T1/ Get back byte allocation
	MOVE T2,DYNBFF		;T2/ Get back dynamic data pointer
	MOVE T3,OWGTAB+2	;Fetch PS and section fields of 8-bit BP
	HRRI T3,PBCONT(PB)	;T3/ set up OWG byte pointer
	CALL PNVDYM		;Set dynamic data for this line
	RETSKP			;Done, skip return

	XSWAPCD
;NVTINT - Routine called upon receipt of an Interrupt for a port
; attached to an NVT
;Takes	T2/ Dynamic data pointer
;	PB/ Packet buffer pointer
;Assumes port is locked
;Returns +1 always
;Clobbers T1-T4

NVTINT:	CALL GTNVLK		;No messing with NVT table
	 RET			;Someone else has it, try again later
	MOVEI T1,SYNTMO		;Reset sync timer
	STOR T1,PSYNTM
	LOAD T1,PSYNCT		;Get sync count (Int's - DM's)
	ADDI T1,1		;Interrupt counts as +1
	STOR T1,PSYNCT		;Put it back
	JUMPE T1,[ CALL RLNVLK	;Done if Ints and DMs now balance
		   CALLRET WAKBSI]
	LOAD T1,PNVSTT		;Get current input state
	SKIPN T1		;Normal input?
	 MOVEI T1,2		;Yes, change to sync in progress
	STOR T1,PNVSTT		;Put it back
	CALL RLNVLK
	CALL TTCIBF		;Clear TTY input buffer
	CALLRET WAKBSI		;Make NVT processor notice change and return
;SYNCHK - Background routine to time out syncs
;Called periodically from Pup background process
;Returns +1
;Clobbers T1-T4, UNIT, BSP, IOS

SYNCHK:	CALL GTNVLK		;No messing with NVT table
	IFNSK.
	  SIGDEF(SYN)		;Already locked, request defered processing
	  MOVEI T1,IBWDLY	;Try again a bit later
	  JRST SYNCH4		;Go reset our timer
	ENDIF.
	MOVE T2,PUPPAR+.PPPNV	;Prepare to scan all NVT's
SYNCH1:	PUSH P,T2		;Save static line number
	CALL LCKTTY		;Lock tty, get dynamic data
	 JRST SYNCH3		;Not really there
	SKIPGE T1,TTYPUP(T2)	;NVT assigned?
	IFSKP.
	  CALL PNVIN1		;No, schedule a pass over it
	  JRST SYNCH3		;And join exit code
	ENDIF.
	HRRZ UNIT,T1		;Get a clean unit
	TXNN T1,SYNCNT		;Sync in progress?
	 JRST SYNCH2		;No, don't check the timer
	LOAD T1,PSYNTM		;Get timer
	SOSGE T1		;Decrement timer
	IFSKP.
	 STOR T1,PSYNTM		;Not timed out, restore count
	 JRST SYNCH2		;And exit
	ENDIF.
	SETZ T1,		;If timed out, clear sync count
	STOR T1,PSYNCT
SYNCH2:	CALL PNVIND		;Scan all PNV's at least this often
SYNCH3:	CALL ULKTTY		;Unlock tty
	POP P,T2 		;Restore static line
	AOBJN T2,SYNCH1		;Loop over NVT's
	CALL RLNVLK		;Done, unlock
	MOVEI T1,SYNCHI		;Get sync check interval
SYNCH4:	ADD T1,TODCLK		;Compute time of next check
	MOVEM T1,SYNTIM		;Save it
	RET			;Return to caller
;ASPNVT - Assign Pup NVT
;Takes	UNIT/ Pup unit number
;Returns +1  Failure, T1/ error code
;	 +2  Success, T2/ static line number
;Clobbers T1-T4

ASPNVT:	STKVAR<ASPNLN>		;Declare local storage
ASPPN0:	CALL GTNVLK		;Lock NVT table
	 RETBAD(MONX03)		;Odd...we are the background fork?
	LOKK (DEVLKK,<JRST ASPPN1>)	;Lock device tables
	CALL ASPPNA		;Select next available PNV
	IFNSK.
	  UNLOKK DEVLKK		;Unlock the device lock
	  CALLRET RLNVLK	;Release NVT lock and return, T1/ error code
	ENDIF.
	MOVEM T2,ASPNLN		;Store the tty number
IFE REL6,<
	MOVEI T1,-1		;No owning job
	CALL STCJOB		;Set it
	MOVE T2,ASPNLN		;Get back line number
>;IFE REL6
	CALL LCKTTY		;Lock the tty, get dynamic data in T2
	 BUG.(HLT,ASPNVZ,PUP,SOFT,<PUP - Failure of LCKTTY in ASPNVT>)
IFE REL6,<CALL STPRM		;Set permanent flag>
IFN REL6,<
	SETONE TCJOB,(T2)	;No controlling job
	SETONE TTPRM,(T2)	;Dynamic data is permanent
>;IFN REL6
	MOVX T1,PNVASF		;Set assigned bit, clear reset of ac
	HRRI T1,(UNIT)		;Set up pup port index
	MOVEM T1,TTYPUP(T2)	;Assign NVT
	MOVE T1,ASPNLN		;Get static line number
	MOVEI T1,.TTDES(T1)	;Point port to NVT
	HRROM T1,PUPPSI(UNIT)	; and clear interrupt assignments
	UNLOKK DEVLKK		;Unlock the device lock
	CALL RLNVLK		;Unlock NVT table
	CALL ULKTTY		;Unlock tty
	MOVE T2,ASPNLN		;Get back static line number for caller
	RETSKP			;Success return
;Here when we couldn't get the device lock.  We release the NVT lock and block.

ASPPN1:	CALL RLNVLK		;Unlock NVT table
	MOVEI T1,ATNVTT		;Scheduler test
	MDISMS			;Block for a while
	JRST ASPPN0		;Try again

;ATNVTT - wait for PNV locks
;Called by ASPNVT
	
	RESCD				

ATNVTT:	SKIPGE NVTLCK		;Is NVT lock free?
	 SKIPL DEVL0K		;Is the device lock free?
	  JRST 0(T4)		;No to either, keep on blocking
	   JRST 1(T4)		;Both free, try for the locks

	XSWAPCD
;ASPPNA - select first free PNV
;Assumes caller has the device and NVT locks
;Returns +1 failure, T1/ error code
;	 +2 success, T2/ static line number

ASPPNA:	SAVEP			;CHKDEV clobbers DEV = P4 = PB
	MOVE F1,PUPPAR+.PPPNV	;Use an available Px for aobjn pointer
ASPNA0:	MOVEI T2,(F1)		;Get static terminal number
	CALL STADYN		;Check for dynamic data
	 SKIPE T2		;Skip if there was absolutely nothing there
	  JRST ASPNA1		;Line is in use, look at next one
	MOVEI T1,.TTDES(F1)	;Form terminal designator
	CALLM CHKDEV		;Is device available?
	 JRST ASPNA1		;No.  Still has device assignment.
	HRRZ T2,F1		;Clear left hand bits
	CALLM TTYASC		;Assign the line, build dynamic data block
	 TRNA			;Assignment failed for some reason
	  RETSKP		;Take success return
ASPNA1:	AOBJN F1,ASPNA0		;Loop over all PNV's
	RETBAD(ATNX13)		;"Insufficient system resources (No NVT's)"
;CLPNVT - Close Pup NVT
;Does PUP dependent stuff for deassigning a PNV.
;Assumes NVTLCK and port are locked and UNIT, BSP, IOS setup
;We never wait for any remaining output to be acknowledged.  Consider the
; case where the other host has closed the connection.  No further ACK's
; will be forthcoming and our side of the connection will eventually timeout.
; If a user tries to attach the NVT job away before the timeout occurs, the
; user's job will hang until we timeout.  Hence for a normal termination
; condition, we are content to send an End and wait for an EndReply.
;Takes	T2/ Pointer to dynamic data
;Returns +1 port is dallying, T1/ scheduler test
;	 +2 port deleted, NVT released
;Clobbers T1,T3,T4,PB;  preserves T2

CLPNVT:	SKIPL TTYPUP(T2)	;Currently assigned?
	 RETSKP			;No, succeed
	SKIPL T1,INTDF		;Interruptable?
	 SKIPGE T3,PUPLCK(UNIT)	;Or not port not locked?
	  BUG.(CHK,CLPLCK,PUP,SOFT,<PUP - CLPNVT called OKINT or unlocked>,<<T1,T1>,<T3,T3>>)
	SAVEAC <T2>		;Preserve T2 (dynamic data pointer)
	STKVAR <CLPLIN>		;Declare local storage
	MOVEM T2,CLPLIN		;Remember location of dynamic data
	MOVX T1,HUREQF		;Get flag that we are hanging up
	IORM T1,TTYPUP(T2)	;Make sure it is set for this NVT
	CALL TTCIBF		;Clear TTY input buffer
	CALL TTCOBI		;Clear TTY output buffer
	CALL DOBSPL		;Ensure BSP data, FSM state are updated
	TXNE IOS,BSERRF		;Error? (Net off or state is S.CLOS/S.ABOR)
	 JRST CLPNV1		;Yes, just flush port
	TXNN IOS,BSTIMF  	;Timed out?
	IFSKP.
	  MOVEI T1,E.CLST	;Yes, generate CLOSF(T) event
	  HRROI T3,0		;Need registered code
	  HRROI T4,[ASCIZ/Connection timed out/]
	  JRST CLPNV0		;Go send an Abort 
	ENDIF.
	CHKSTT <OPEN,ENDI>	;Port state ok for BSP output?
	 TRNA			;No, go straight to the close
	SKIPN PB,BSPCOP(BSP)	;Have a current buffer?
	 JRST CLPNV3		;No, skip around this
	MOVE T1,PBOPTR(PB)	;Get pointer to last data
	CALL FRCBSP		;Dump last pup, don't wait for ACK's
CLPNV3:	MOVEI T1,E.CLSN		;Generate CLOSF(N) event
CLPNV0:	CALL PUPFSM		;Activate the FSM (send End or Abort pup)
	TXNN IOS,BSERRF		;Is the port Closed or Aborted now?
	 JRST CLPNV2		;No, must wait for handshake to finish
CLPNV1:	CALL FLSBSQ		;Flush all queues
	CALL DELPRT		;Delete the port
	MOVE T2,CLPLIN		;Recover dynamic data
IFE REL6,<CALL CLRPRM		;Turn off permanent bit>
IFN REL6,<SETZRO TTPRM,(T2)	;Turn off permanent bit> 
	MOVX T1,PNVASF		;Assignment flag (sign bit)
	ANDCAM T1,TTYPUP(T2)	;Release the NVT
	HLLOS TTYPUP(T2)	;Port number of -1 means deleted 
	RETSKP			;Return to caller

CLPNV2:	MOVE T1,[1B0+PNVCLT]	;Scheduler test (line no. filled in by caller)
	RET			;Return to caller
;PNVCLT - test for normal termination of an NVT BSP connection
;Argument is static line number
;Callers are: CLPNVT

	RESCD

PNVCLT:	MOVE T2,T1		;Get static line number
	CALL STADYN		;Translate to dynamic data
	 JRST 1(T4)		;Line no longer there, wakeup
	SKIPL TTYPUP(T2)	;Line still assigned? 
	 JRST 1(T4)		;No, wakeup
	HRRZ T2,TTYPUP(T2)	;Get port number
	MOVE T1,PUPSTS(T2)	;Get status word
	TXNE T1,BSERRF!BSTIMF	;Closed or dally timed out?
	 JRST 1(T4)		;Yes, wakeup
	JRST 0(T4)		;No, keep sleeping

	XSWAPCD
;PNVABT - abort (as opposed to close) an NVT connection
;Routine called when attaching a job away from a PNV 
;We must never block here since our caller owns DEVL0K
;Takes	T2/ dynamic data pointer
;Returns +1 always
;Clobbers T1

XNENT PNVABT,G
	CALL PUPCTY		;Is this a PNV?
	 RET			;No, do nothing
	CALL LCKPN		;Yes, try lock it for our use
	 RET			;Can't, just forget it
	MOVX T1,E.CLST		;Event is Close with prejudice (send Abort)
	HRROI T3,0		;Should be a registered code
	HRROI T4,[ASCIZ/Job was detached away/]
	CALL PUPFSM		;Abort the connection
	CALL ULKPNV		;Unlock the PNV
	RET			;Return to caller
;PNVCOB - Clear TTY output buffers for PUP NVT
;Takes	T2/ dynamic data pointer
;Returns +1 always
;Clobbers T1, T3-T4;  preserves T2, UNIT, BSP, IOS

;It is unclear to me that PNVCOB has ever been a useful routine.  Since
; it has developed a tendency to crash 6.1 systems, I'm gutting the
; routine.  Maybe someday it can be resurrected and made to work in the
; manner it was intended.  -KSL, 25-Mar-85

	XRESCD

XRENT PNVCOB,G
	CALL LCKPNV		;Check and lock NVT
	 RET			;No port attached or can't block, do nothing
	SKIPN PB,BSPCOP(BSP)	;Do we have an output buffer?
	IFSKP.
	  SETZM BSPCOP(BSP)	;Yes, clear pointer to buffer
	  CALL RELPKT		;Flush buffer
	  MOVE T2,0(P)		;Get dynamic data pointer (LCKPNV hack)
	  CALL PNVREL		;And fix up TTY dynamic data
	ENDIF.
	CALL ULKPNV		;Unlock NVT
	RET			;Return to caller

	XSWAPCD
REPEAT 0,<
;PNVCOB - Clear TTY output buffers for Pup NVT
;Takes	T2/ dynamic data pointer
;Returns +1 always
;Clobbers T1, T3-T4;  preserves T2, UNIT, BSP, IOS

XNENT PNVCOB,G
	CALL LCKPNV		;Check and lock NVT
	 RET			;No port attached or can't block, do nothing
	SKIPN PB,BSPCOP(BSP)	;Do we have an output buffer?
	IFSKP.
	  SETZM BSPCOP(BSP)	;Yes, clear pointer to buffer
	  CALL RELPKT		;Flush buffer
	  MOVE T2,0(P)		;Get dynamic data pointer (LCKPNV hack)
	  CALL PNVREL		;And fix up TTY dynamic data
	ENDIF.
	TXNN IOS,BSOUTF		;Sufficient allocation?
	 JRST PNVCO2		;No, forget it
	HRROI T1,0		;Need registered code
	HRROI T2,[ASCIZ/Sync/]
	CALL SNDINT		;Send Interrupt
	IFNSK.
	  CALL ULKPNV		;Can't, undo all locks
	  MDISMS		;Wait until can send Interrupt
	  JRST PNVCOB		;Try again
	ENDIF.
PNVCO1:	MOVEI T1,MK.DAT		;Mark type = 1 
	CALL SNDAMA		;Send AMark
	IFNSK.
	  CALL ULKPNV		;Unlock port
	  MDISMS		;Wait until can send Mark
	  CALL LCKPNV		;Lock everything again
	   RET			;Port went away or can't block, give up
	  JRST PNVCO1		;Try again to send Mark
	ENDIF.
>;REPEAT 0
PNVCO2:	CALL ULKPNV		;Unlock, unwind...
	RET
;PNVDOB - Dismiss until output buffer empty, called from TTDOBE
;Takes	T2/ Pointer to dynamic data
;Returns +1 no retry (connection dead, allocation low, or DOBE% succeeded)
;	 +2 we have blocked and DOBE% should be re-invoked
;Because of the crocking and kludging done to speed up PNV service,
; a PNV in a page wait always has an empty output buffer.  After going
; through PUP specific actions, we attempt to honor any existing page
; wait condition.  There are programs that expect DOBE% to block under
; such conditions.
;Clobbers T1, T3-T4;  preserves T2, UNIT, BSP, IOS

XNENT PNVDOB,G
	CALL LCKPNV		;Check and lock NVT
	 RET			;No port attached, do nothing
	MOVEI T1,MK.TIM		;Mark type = 5 (Timing Mark)
	CALL SNDAMA		;Send AMark
	IFNSK.
	  CALL ULKPNV		;Can't, undo locks
	  RET			;Pretend we succeeded
	ENDIF.
	MOVE T2,0(P)		;Recover dynamic data ptr (hidden by LCKPNV)
	MOVX T1,TMKPNF		;Set timing mark pending flag
	IORM T1,TTYPUP(T2)
	CALL ULKPNV		;Unlock BSP data block
	STKVAR <DOBPRT,DOBLIN>
	MOVE T1,TTYPUP(T2)	;Save port information
	MOVEM T1,DOBPRT
	MOVEM T2,DOBLIN		;Save dynamic data
	CALL DYNSTA		;Convert to static line number
	MOVEI T1,PNVDBT		;Scheduler test is wait for Timing Mark Reply
	HRL T1,T2		;For this line
	EXCH T2,DOBLIN		;Swap static and dynamic data
	CALL ULKTTY		;Allow deallocation of TTY data
	MDISMS			;Wait for Timing Mark Reply
	MOVE T2,DOBLIN		;Get static line number (may be wrong now!)
	CALL STADYN		;Get pointer to dynamic dta
	 RETSKP			;Something wrong, restart DOBE%
	CALL PNVSFG		;Are we in a page hold?
	 MDISMS			;Yes, honor it
	MOVE T2,DOBLIN		;Get back static line number 
	MOVE T3,DOBPRT		;Restore old port info
	CALL LCKTTY		;Try to lock the TTY again
	 RETSKP			;Failed, return to caller for retry
	HRRZS T3		;Isolate old port number
	HRRZ T1,TTYPUP(T2)	;Get present port number
	CAIN T1,(T3)		;Do they match?
	 RET			;Yes, DOBE% done, and TTY still ours
	CALL ULKTTY		;Mismatch, unlock TTY
	RETSKP			;And retry

	ENDSV.
;PNVDBT - Scheduler test for timing mark not outstanding
;Wakes up if the port goes away or gets wedged
;Arg is static line number
;Callers are: PNVDOB

	RESCD

PNVDBT:	MOVE T2,T1		;Get argument into place
	CALL STADYN		;Get dynamic data
	 JRST 1(T4)		;Not there anymore, wakeup
	SKIPL T1,TTYPUP(T2)	;Get status
	 JRST 1(T4)		;What, not active? wake up then
	TXNN T1,HUREQF		;Hanging up?
	 TXNN T1,TMKPNF		;Or Mark no longer pending?
	  JRST 1(T4)		;Yes, wakeup
	HRRZS T1		;Isolate port number
	MOVE T1,PUPSTS(T1)	;Get port status
	TXNE T1,BSTIMF!BSERRF   ;Wakeup if port is wedged
	 JRST 1(T4)
	JRST 0(T4)		;Else keep on blocking 

	XSWAPCD
;PNVCLZ - Hang up Pup NVT line, i.e. close connection, release NVT
;Takes	T2/ Static line number
;Returns +1 to caller on success
;	 +1 to caller of caller on failure, T1/ scheduler test
;	 +2 to caller of caller on success, dynamic data was deassigned
;PNVCLZ is called from TTYDEA in TTYSRV.  If TTYDEA needs to block, it
; returns +1 to its caller with the scheduler test in T1.  However the
; TDCALL macro that invokes PNVCLZ allows only single returns.  Hence if
; we need to block for dallying, we must fake a single return out of TTYDEA.
; This is inelegant, but necessary, as we cannot block in PNVCLZ since we
; are NOINT and own the device lock.
;Clobbers T1, T3-T4;  preserves T2, UNIT, BSP, IOS

XNENT PNVCLZ,G
	CALL PNVCLO		;Call worker routine
	 TRNA			;Must block or no dynamic data
	  RET			;Port deleted, return to caller
	ADJSP P,-1		;*** Fudge stack pointer ***
	JUMPE T1,RSKP		;+2 don't call rest of TTYDEA, no dynamic data
	RET			;+1 caller of TTYDEA must block, T1/ test

;PNVCLO - helper routine for closing a PNV connection

PNVCLO:	SAVEAC <T2>		;Preserve static line number
	CALL LCKTTY		;Lock dynamic data
	 JRST RETZ		;Return +1, T1/ zero, no dynamic data
	CALL LCKPN		;Lock NVT and setup BSP data
	IFNSK.
	  JUMPE T1,PNVCL1	;Jump if no BSP port
	  TXO T1,1B0		;Make sure upper levels do correct thing
	  JRST PNVCL3		;Else unlock TTY and block
	ENDIF.
	CALL CLPNVT		;Close pup side of NVT
	 JRST PNVCL2		;Must dally, go unlock and block
	CALL ULKPNX		;Now closed, port gone
PNVCL1:	CALL ULKTTY		;Unlock TTY
	RETSKP			;Skip return, BSP port is deleted

PNVCL2:	CALL ULKPNV		;Unlock NVT, restore BSP AC's
PNVCL3: PUSH P,T1		;Save T1 which contains our scheduler test
	CALL ULKTTY		;Unlock TTY
	POP P,T1		;Restore T1
	RET			;Return +1, T1/ scheduler test
;PNVIBE - Routine called when TTY input buffer becomes empty (or is cleared)
;Takes	T2/ Pointer to dynamic data
;Returns +1 always
;Clobbers nothing

XNENT PNVIBE,G
	CALL PNVIN1		;Schedule pass over this NVT in case we block
	PUSH P,T1		;Save an ac
	LOAD T1,PTMKCT		;Get count of timing marks pending
	JUMPE T1,PNVIB3		;Jump if none
	SKIPN NSKED		;Make sure not NOSKED
	 CALL LCKPNV		;Check and lock NVT
	  JRST PNVIB3		;No port attached, do nothing
	CHKSTT <OPEN,ENDI>	;Port in good state for output?
	 JRST PNVIB2		;No, do nothing
PNVIB1:	MOVEI T1,MK.TMR		;Mark type = Timing Mark Reply
	CALL SNDAMA		;Try to send AMark
	 JRST PNVIB2		;Insufficient allocation, just exit
	MOVE T2,0(P)		;Recover line number
	LOAD T1,PTMKCT		;Decrement timing mark count
	SOSL T1
	 STOR T1,PTMKCT
	JUMPG T1,PNVIB1		;Repeat if more timing marks pending
PNVIB2:	CALL ULKPNV
PNVIB3:	POP P,T1
	RET    
;LCKPNV - Check and lock PNV and save AC's (for TTY level routines)
;LCKPN - same as LCKPNV, except doesn't block
;Takes	T2/ dynamic data pointer
;Returns +1 failure, T1/ scheduler test, or zero if no PUP connection
;	 +2 success, PNV locked and BSP AC's set up
;If we are called by the PUP background fork, we don't set the lock
; since we have already locked the BSP block to do the TTY processing.
;The +2 return is made at a stack level deeper than the call.
; T2 is saved at 0(P).  Return must be via ULKPNV.
;Warning:  Always invoke ULKPNV with a CALL, never with a CALLRET.
; The later method will cause the stack to become unbalanced.
;Clobbers T1,T3,T4

LCKPNV:	TDZA T4,T4		;Blocking allowed
LCKPN:	SETO T4,		;Can't block if enter here
	EXCH UNIT,0(P)		;Swap UNIT with our return address on stack
	PUSH P,BSP		;Save vulnerable ac's
	PUSH P,IOS
	PUSH P,PB
	PUSH P,T2
	PUSH P,UNIT		;Put return PC on top of stack
LCKPN0:	CALL GTNVNB		;Try for NVT lock, but don't block 
	 JRST LCKPN2		;Not available, go set up scheduler test
	MOVE T1,0(P)		;Fetch caller's PC from top of stack
	MOVEM T1,NVTLCK+2	;Fixup record of NVTLCK locker PC
 	SKIPL UNIT,TTYPUP(T2)	;PNV is assigned?
	 JRST LCKPN4		;No, dead connection
	HRRZS UNIT		;Isolate port number
	MOVE T1,PUPSTS(UNIT)	;Get status bits
	TXNE T1,BSERRF!BSTIMF	;Error or timed out?
	 JRST LCKPN4		;Yes, dead connection
	AOSE PRTLCK		;Try for the port lock
	 JRST LCKPN3		;Release NVT lock and set up test
	CALL LCKBSA		;Attempt to lock port
	IFNSK.
	  UNLOCK(PRTLCK)	;Can't, unlock port table
	  JRST LCKPN3		;Release NVT lock and unwind
	ENDIF.
	UNLOCK(PRTLCK)		;Ok, unlock port table
	RETSKP			;Skip return, NVT locked
;Here if LCKPN/LCKPNV detected a dead connection
;We return +1 with T1/ zero to indicate dead connection

LCKPN4:	SETZ T1,		;Return zero to indicate dead connection
	JRST ULKPN2		;Release NVT lock and return error

;Here if LCKPN/LCKPNV couldn't get lock, but might succeed in the future
;T4 was set up at the entry point and determines our blocking behaviour.

LCKPN3:	CALL RLNVLK		;Release NVT lock
LCKPN2:	PUSH P,T2		;Save dynamic data pointer
	CALL DYNSTA		;Get static line number
	HRLI T1,(T2)		;Set up data for scheduler test
	POP P,T2		;Restore dynamic data ptr
	HRRI T1,LCKPNT		;Set scheduler test
	JUMPN T4,ULKPN3		;Go unwind stack if blocking not allowed
	SKIPN INSKED		;If in scheduler
	 SKIPE NSKED		;Or NOSKED
	  JRST ULKPN3		;Can't block
	MOVE CX,PUPFRK		;Get number of PUP fork
	CAMN CX,FORKX		;Is that us?
	 JRST ULKPN3		;Yes, can't block
	MDISMS			;Low priority dismiss (note that TTY is locked)
	JRST LCKPN0		;Try for the lock again

;LCKPNT - scheduler test to wait for a PNV to be lockable
;Arg is static line number
;Callers are: LCKPN/LCKPNV (PNVCLO, TTYDEA, PNVBUF)

	RESCD

LCKPNT:	SKIPGE NVTLCK		;Is the NVT lock free?
	 SKIPL PRTLCK		;And is the port lock free?
	  JRST 0(T4)		;No to either, keep blocking
	MOVE T2,T1		;Get static line number
	CALL STADYN		;Get pointer to dynamic data
	 JRST 1(T4)		;Wakeup if no dynamic data
	SKIPL T1,TTYPUP(T2)	;Load PNV status word
	 JRST 1(T4)		;Wakeup if PNV not assigned
	HRRZ T1,T1		;Isolate port number
	MOVE T3,PUPSTS(T1)	;Get port status 
	TXNN T3,BSERRF!BSTIMF	;Is port in an error state?
	 SKIPGE PUPLCK(T1)	;Is the port locked?
	  JRST 1(T4)		;Error or unlocked, unblock
	   JRST 0(T4)		;Keep blocking

	XSWAPCD
;ULKPNV - Unlock Pup NVT and restore saved AC's
;UNIT, BSP, IOS setup from previous call to CHKNVT
;Enter at ULKPN2 if port wasn't locked
;Enter at ULKPNX if port has been deleted
;Returns +1, restores T2, UNIT, BSP, IOS

ULKPNX:	ECSKED			;No BSP, can't do ULKBSP, so must ECSKED
	JRST ULKPN2		;Go unlock NVT table 

ULKPNV: CALL ULKBSP		;Unlock port
ULKPN2:	CALL RLNVLK		;Unlock NVT table
ULKPN3:	POP P,UNIT		;Pop off return pc
	POP P,T2		;Restore saved ac's
	POP P,PB
	POP P,IOS
	POP P,BSP
	EXCH UNIT,0(P)		;Restore UNIT, put back pc
	RET    			;Return
;GTNVLK/GTNVNB - lock NVTLCK
;Succeeds if already locked by same FORKX
;We never block if we are the pup background fork, scheduler, NOSKED, etc.
;Enter at GTNVNB if we are never supposed to block
;Returns +1 can't get lock and we are background fork, scheduler, or NOSKED
;	 +2 have lock, are running CSKED
;Clobbers T1,T3

;	NVTLCK+0	lock word, -1 if free
;	NVTLCK+1	FORKX of locking process
;	NVTLCK+2	PC of locking process
;	NVTLCK+3	TODCLK when locked

RS NVTLCK,4			;Declare lock storage

NVTTMO==^D2000			;Lock timeout interval in ms

GTNVNB:	TDZA T3,T3		;Take failure return instead of blocking
GTNVLK:	SETO T3,		;Block if possible
	NOSKED			;No scheduling
	MOVE T1,FORKX		;Get current fork
	AOSE NVTLCK		;Try to get lock
	CAMN T1,NVTLCK+1	;Can't, locked by same fork?
	 JRST GTNVL0		;Have the lock!
	MOVE T1,TODCLK		;Get now
	SUB T1,NVTLCK+3		;Subtract last lock time
	CAIL T1,NVTTMO		;Was it more than just a while ago?
	 JRST GTNVL2		;Yes, we have a problem.
	SOS NVTLCK		;Fix lock count
	OKSKED			;Resume scheduling
	SKIPN INSKED		;In scheduler?
	 SKIPE NSKED		;Or NOSKED?
	  RET			;Yes, we have to give up
	MOVE T1,FORKX		;Get back system fork number
	CAME T1,PUPFRK		;Are we the background fork?
	 JUMPN T3,GTNVL1	;Not background, jump if okay to block
	SIGDEF(NVT,,CX)		;Yes, schedule another pass at the NVT's
	RET			;Don't even *think* about blocking
;here to timeout a lock. We bugchk, grant the lock, and hope for the best

GTNVL2:	MOVE T1,NVTLCK+1	;Get FORKX of locker
	MOVE T2,NVTLCK+2	;Get PC of call to GTNVLK/GTNVNB
	BUG.(CHK,PNVLKX,PUP,SOFT,<PUP - NVT lock timeout>,<<T1,FORKX>,<T2,PC>>)
	MOVE T1,FORKX		;Get our FORKX ready
;	JRST GTNVL0		;Fall through

;here when we have the lock

GTNVL0:	MOVEM T1,NVTLCK+1	;Remember fork handle of locker
	MOVE T1,0(P)		;Get PC of caller from stack
	MOVEM T1,NVTLCK+2	;Remember where we locked
	MOVE T1,TODCLK		;Get now
	MOVEM T1,NVTLCK+3	;Remember when we locked
	CSKED			;Run very fast
	OKSKED			;Resume scheduling
	RETSKP			;Skip return to caller

;here to block waiting for the lock to free up (note strange flow of control)

	JRST GTNVLK		;Execute this instruction when CBLK1 wakes up
GTNVL1:	CBLK1			;Short term block, return .-1

;RLNVLK - unlock NVTLCK
;Returns +1 always

RLNVLK:	SKIPL NVTLCK		;Skip if we would overly decrement
	 SOS NVTLCK		;Else decrement the lock
	SKIPLE CRSKED		;Skip if we had lock, but no CSKED (bug!)
	 ECSKED			;Else leave the critical section
	RET			;Return to caller
SUBTTL Ports, Sockets, and Checksums

;PRTLUK - Lookup local port
;Takes	T1/ net,,host
;	T2/ Socket (right-justified)
;	UNIT/ flags (see below)
;Returns +1 fail, UNIT/ Index of first free slot (PRNFEF set =) full)
;	 +2 found, UNIT/ Index of matching entry
;Non-interrupt-level callers should lock PRTLCK before calling
; if they intend to use UNIT on either return.
;Clobbers T3, T4, UNIT

;Flags in UNIT
PRNFEF==1B0	;No free entries found yet (set by routine)
PRGCMF==1B1	;Doing gc marking (setting free entries to deleted)
PRPCCF==1B2	;Doing port conflict check, ignore non-fully wild ports if
		; user's arg is fully wild

	XRESCD

PRTLUK:	TXO UNIT,PRNFEF		;Note no free entries found yet
	STKVAR <CURPRT>		;Declare local storage
	MOVE T3,[^D2654435769_3] ;Constant relatively prime to 2^32
	MUL T3,T2		;T4 := 32-bit fraction in range [0,1)
	MOVEI T3,NPUPUN		;Normalize to range [0,NPUPUN)
	MULM T3,T4
	MOVNI T3,(T4)		;Save neg index for wraparound
	HRRZM T3,CURPRT		;...
	HRLI T4,-NPUPUN(T4)	;Make AOBJN ptr, here to end
PRTLU1:	CAMN T2,PUPLSK(T4)	;Socket number match this entry?
	 JUMPN T2,PRTLU3	;Yes, go compare net,,host
	SKIPLE T3,PUPLSK(T4)	;No match, free or deleted?
	IFSKP.
	  TXNN UNIT,PRGCMF	;Yes, doing GC marking?
	  IFSKP.
	    JUMPL T3,PRTLU2	;Yes, ignore if deleted
	    SETOM PUPLSK(T4)	;Free, mark deleted
	    AOS LSKNDL		;Bump delete count
	    JRST PRTLU2		;Continue search
	  ENDIF.
	  TXZE UNIT,PRNFEF	;No, already have free/deleted index?
	  HRR UNIT,T4		;No, remember this
	  JUMPL T3,PRTLU2	;Keep searching if deleted entry
	  RET			;Fail if free entry, i.e. not found	
	ENDIF.
PRTLU2:	AOBJN T4,PRTLU1		;Search linearly thru table
	HRLZ T4,CURPRT		;At end, wraparound
	SETZM CURPRT		;Clear count in case get here again
	JUMPL T4,PRTLU1		;Do portion before initial probe
	RET			;Searched whole table, not found

PRTLU3:	MOVE T3,PUPLNH(T4)	;Get net,,host for this port
	XOR T3,T1		;XOR against PRTLUK argument
	JUMPE T3,PRTLU4		;If exact match, go finish up
	MOVE CX,PUPLNH(T4)	;Get set for a wildcard check (ran out of AC's)
	TXNE UNIT,PRPCCF	;Port conflict check?
	 TXNE T1,.LHALF		;Yes, check arg for wild net
	TXNN CX,.LHALF		;Check port for wild net
	 TXZ T3,.LHALF		;Net is wild
	TXNE UNIT,PRPCCF	;Port conflict check?
	 TXNE T1,.RHALF		;Yes, check arg for wild host
	TXNN CX,.RHALF		;Check port for wild host
	 TXZ T3,.RHALF		;Host is wild
	JUMPN T3,PRTLU2		;Jump if mismatch, continue search
PRTLU4:	HRRZ UNIT,T4		;Match, return index
	RETSKP

	XSWAPCD
;ASGPRT - Assign local port
;	T1/ net,,host
;	T2/ Socket (right-justified)
;Returns +1:  Error, no slots available
;	+2:  Port already in use, UNIT/ Pup unit #
;	+3:  Successful, UNIT/ Pup unit #
;+1 return with PRTLCK unlocked, +2 and +3 with PRTLCK locked.
;Clobbers T3, T4, UNIT

ASGPRT:	LOCK(PRTLCK)		;Lock the table
	MOVX UNIT,PRPCCF	;Port conflict check
	CALL PRTLUK		;Lookup local port
	 TRNA			;Not found, see if table full
	  RETSKP		;Found, take single skip return
	JXE UNIT,PRNFEF,ASGPR0	;Table full?
	UNLOCK(PRTLCK)		;Unlock and fail if table full
	RET

ASGPR0:	HRRZS UNIT		;Clear any leftover flags
	CALL INIPRT		;Initialize port
	SKIPE PUPLSK(UNIT)	;Skip if entry is free
	 SOS LSKNDL		;Deleted, decrement delete count
	MOVEM T1,PUPLNH(UNIT)	;Store net,,host
	MOVEM T2,PUPLSK(UNIT)	;Store socket #, assigning port
	JRST SK2RET		;Take success return
;DELPRT - Delete local port
;The port itself must be locked if it is a BSP port
;Allows output queue to empty
;** warning ** this routine blocks (locking PRTLCK)
;Takes	UNIT/ Pup unit number
;Returns +1 always
;Clobbers T1-T4, PB

DELPRT:	LOCK(PRTLCK)		;Lock the table
	SETOM PUPLSK(UNIT)	;Mark entry deleted
	CALL DELTQP		;Delete port from timer queue
	HRRZ T1,UNIT		;Get our port number
	LSH T1,1		;Double it for index (PUPIBQ is doubleword)
	XMOVEI T1,PUPIBQ(T1)	;Input packet buffer queue header
	CALL FSHPBQ		;Flush input queue
	SKIPE T1,PUPFPT(UNIT)	;Have foreign port address table?
	 CALL RELBSP		;Yes, deallocate it
	SETZM PUPFPT(UNIT)	;Make sure pointer is cleared 
	SKIPE T1,PUPBSP(UNIT)	;Have BSP data block?
	 CALL RELBSP		;Yes, deallocate it
	SETZM PUPBSP(UNIT)	;Make sure pointer is cleared
	CALL INIPRT		;Initialize port for cleanliness
	MOVX T2,BSERRF		;But in case someone is blocked
	MOVEM T2,PUPSTS(UNIT)	;Set the error flag to wake them up
	AOS T2,LSKNDL		;Increment number of deleted entries
	SIGPBP(GCS,<CAIL T2,NPUPUN/4>) ;Request GC of table if worthwhile
	UNLOCK(PRTLCK)		;Unlock the table
	RET    
;INIPRT - Initialize local port
;Takes	UNIT/ Pup unit number
;Returns +1 always, after setting the port's entry in all port-
; indexed tables to a virgin state.
;Clobbers T3

INIPRT:	SETZM PUPFPT(UNIT)	;Clear foreign port
	HRRZ T3,UNIT		;Get port number
	LSH T3,1		;Double it for index into PUPIBQ
	XMOVEI T3,PUPIBQ(T3)	;Get queue address
	MOVEM T3,HEAD(T3)	;Set input buffer queue to empty
	MOVEM T3,TAIL(T3)	; ...
	SETZM PUPIBC(UNIT)	;Set queue counts to zero
	SETOM PUPPSI(UNIT)	;Disable PSI stuff
	SETOM PUPLCK(UNIT)	;Port is unlocked
	SETZM PUPSTS(UNIT)	;Clear status word
	SETZM PUPLNH(UNIT)	;No local net and host
	RET    
;SWPPRT - Swap Source and Destination Ports in Pup
;	PB/ Packet buffer pointer
;Returns +1 always
;Clobbers T1,T2

SWPPRT:	
;first exchange the network/host specifications
	LOAD T1,PUPD
	LOAD T2,PUPS
	STOR T1,PUPS
	STOR T2,PUPD

;then exchange the low order socket bits
	LOAD T1,PUPD0
	LOAD T2,PUPS0
	STOR T1,PUPS0
	STOR T2,PUPD0

;lastly, exchange the high order socket bits
	LOAD T1,PUPD1
	LOAD T2,PUPS1
	STOR T1,PUPS1
	STOR T2,PUPD1
	RET    
;GCPLSK - Garbage collect Pup local socket table
;We first set free all deleted entries.  We then lookup each in-use entry
;and delete it if it turns out to not be in use.
;Called from background process
;We lock PRTLCK right away, otherwise we might block while NOSKED....
;Returns +1 always
;Clobbers T1-T4, UNIT, E

	XRESCD			;We go IOPIOFF while NOSKED

GCPLSK:	LOCK(PRTLCK,<RET>)	;Lock the port table
	CALL PILOCK		;Enter interlock coroutine
	MOVSI E,-NPUPUN		;Set up aobjn pointer
GCPLS0:	SKIPGE PUPLSK(E)	;Deleted entry?
	 SETZM PUPLSK(E)	;Yes, set free
	AOBJN E,GCPLS0		;Loop over all entries
	SETZM LSKNDL		;Init count of deleted entries
	MOVSI E,-NPUPUN		;Set up aobjn pointer
GCPLS1:	SKIPG T2,PUPLSK(E)	;In use?
	 JRST GCPLS2		;No, try next port
	MOVE T1,PUPLNH(E)	;Yes, fetch net,,host
	MOVX UNIT,PRGCMF	;Set flag that we are doing GC stuff
	CALL PRTLUK		;Lookup and delete unused ports
	 BUG.(HLT,GCPLSZ,PUP,SOFT,<PUP - Impossible fail return from PRTLU0>)
GCPLS2:	AOBJN E,GCPLS1		;Loop over all entries
	UNLOCK(PRTLCK)		;Unlock table (We are NOSKED, IOPIOF still)
	RET			;Return to caller

	XSWAPCD
;CHKSRC - Check Pup source field
;Takes	PB/ Packet buffer pointer
;	UNIT/ Pup unit number
;Enter at CHKSR1 with T1/ net, T2/ host, T3/ socket to be checked
;Returns +1  Source incorrect
;	 +2  Source correct, i.e. matches foreign port
;Clobbers T1-T4

CHKSRC:	CALL GETPSS		;Get source socket
	MOVE T3,T1		;Want it in T3
	LOAD T1,PUPSN		;Get source net from Pup
	LOAD T2,PUPSH		;Source host
CHKSR1:	SKIPN T4,PUPFPT(UNIT)	;Have foreign port address table?
	 RETSKP			;No, fully wildcard, skip return
	SAVEAC<CX,E>		;Need some AC's
	MOVE CX,0(T4)		;Get length of address table
CHKSR2:	HLRZ E,1(T4)		;Get net from table
	CAIE T1,(E)		;Match?
	 JUMPN E,CHKSR3		;No, fail unless wildcard
	HRRZ E,1(T4)		;Yes, get host from table
	CAIE T2,(E)		;Match?
	 JUMPN E,CHKSR3		;No, fail unless wildcard
	CAME T3,2(T4)		;Yes, check socket
	 SKIPN 2(T4)		;Mismatch, fail unless wildcard
	  RETSKP		;Matched, take skip return
CHKSR3:	ADDI T4,2		;Move pointer along
	SUBI CX,2		;Decrement length of unexamined table
	JUMPG CX,CHKSR2		;Go look if more entries
	RET			;Failure return to caller
;SETCKS - Set Pup checksum
;SETCHK - BSP processing entry point, IOS set up
;Takes	PB/ Packet Buffer pointer
;Returns +1 always
;Clobbers T1-T4

SETCHK:	TXNE IOS,BSNCHK		;No checksumming?
	SKIPA T1,[NILCHK]	;Yes, use nil checksum
SETCKS:	CALL PUPCKS		;Compute checksum
	STOR T1,PUCHK		;Store it
	RET			;Return to caller


;CHKCKS - Check Pup checksum
;Takes	PB/ Packet Buffer pointer
;Returns +1  Checksum incorrect
;	 +2  Checksum correct
;Clobbers T1-T4

CHKCKS:	LOAD T1,PUCHK		;Get checksum
	CAIN T1,NILCHK		;Real checksum?
	 RETSKP			;No, unchecksummed Pup always ok
	CALL PUPCKS		;Recompute checksum
	CAIN T1,NILCHK		;Did we compute a nil checksum?
	 RETSKP			;Yes, pretend packet's checksum is good
	LOAD T2,PUCHK		;Recover transmitted checksum
	CAMN T1,T2		;Match?
	 RETSKP			;Yes, skip return
	RET			;Single return if no match
;UPDCKS - change field in pup header, update checksum if necessary
;Call at interrupt or process level
;Takes	PB/ Packet Buffer pointer
;	T1/ New value of field to be updated
;	T2/ (Local) byte pointer denoting field to be changed
;	      (must be indexed by PB and cannot cross 16-bit boundary)
;	T3/ (Local) byte pointer for checksum
;Returns +1 always, after storing new value as specified and updating the
;	   checksum (if appropriate)
;Clobbers T1-T4

	XRESCD

UPDCKS:	STKVAR <UPDNEW,UPDPTR,UPDSUM>	;Declare local storage
	MOVEM T1,UPDNEW		;Save new value
	MOVEM T2,UPDPTR		;Save byte pointer
	MOVEM T3,UPDSUM		;Save pointer to checksum
	MOVEI T1,(T2)		;Compute 36-bit word offset into packet buffer
	LSH T1,1		;Convert to 16-bit word offset
	LDB T2,[POINT 6,T2,5]	;Get position field (P) of byte pointer 
	CAIGE T2,^D20		;Less than 20 bits after high bit of byte?
	 ADDI T1,1		;Yes, we're the right 16-bit word
	CAIL T1,PBHEAD*2	;Range check
	 CAIL T1,PBCONT*2	;Within pup header?
	  BUG.(CHK,PUPUPD,PUP,SOFT,<PUP - UPDCKS field not in pup header>)
	MOVE T2,T1		;Copy offset into T2
	ROT T2,-1		;Compute 36-bit word offset, set sign if right
	JUMPL T2,.+2		;Which 16-bit word?
	 TLOA T2,(POINT 16,(PB),15) ;Left
	  HRLI T2,(POINT 16,(PB),31) ;Right
	LDB T4,T2		;Fetch old contents
	MOVE T3,UPDNEW		;Get new field value
	DPB T3,UPDPTR		;Set up new field
	LDB T3,UPDSUM		;Get old checksum
	CAIN T3,NILCHK		;Nil?
	RET			;Yes, can quit now
	LDB T3,T2		;Fetch new contents of word
	SUBI T3,(T4)		;Compute 16-bit 1's complement difference
	JUMPGE T3,.+2
	 ADDI T3,177777
	MOVE T4,T1		;Save offset of changed 16-bit word
	LOAD T1,PUPLEN		;Get Pup length in bytes
	ADDI T1,4*PBHEAD-1	;Add overhead bytes
	LSH T1,-1		;Compute 16-bit word offset of Pup checksum
	SUBI T1,(T4)		;Compute difference in offsets
	ANDI T1,17		;Modulo 16
	LSH T3,(T1)		;Shift checksum correction appropriately
	LDB T1,UPDSUM		;Fetch old checksum (already checked for nil)
	ADD T1,T3		;Compute new sum
	CALL CKFOLD		;Fold computed checksum to 16 bits
	CAIN T1,NILCHK		;Check for minus zero
	 SETZ T1,		;Convert to plus zero
	DPB T1,UPDSUM		;Store new checksum
	RET			;Return to caller

	XSWAPCD
;PUPCKS - Compute Pup checksum
;Takes	PB/ Packet Buffer pointer
;	UNIT/ port number
;Returns +1
;	T1/ 16-bit checksum, right-justified
;Clobbers T1-T4

PUPCKS:	SAVEAC <E>		;We use this AC
	CALL GETMOD		;Get data mode for this pup
	CALL @CHKTAB(T2)	;Dispatch to appropriate checksumming routine
	RET			;Return to caller

;Dispatch table for checksumming routines

CHKTAB:	DSP (CHKSIX)		;.PM16
	DSP (CHKETH)		;.PM32
	DSP (CHK36)		;.PM36
	DSP (CHKTXT)		;.PMASC
	DSP (CHKSIX)		;.PM16S
	DSP (CHKNIL)		;.PM9

;Nil checksum
 
CHKNIL:	MOVX T1,NILCHK
	RET
;CHKHDR - compute the checksum for the header
;Takes PB/ pointer to packet buffer
;Returns +1 always, T1/ partial, unnormalized checksum
;Clobbers T1-T4, E

CHKHDR:	MOVEI T4,5		;Just five words
	XMOVEI T1,PBHEAD(PB)	;Start of header words
	MOVEM T1,PBOPTR(PB)	;Use the old byte pointer area
	SETZ T1,		;Accumulate checksum in T1
CHKHD1:	MOVE T2,@PBOPTR(PB)	;Fetch 32 bits, left justified
	LSHC T2,-^D20		;High 16 bits right just in T2, low left in T3
	ADDI T1,(T2)		;Add first word
	LSH T1,1		;Left cycle
	LSH T3,-^D20		;Right justify low 16 bits
	ADDI T1,(T3)		;Add second word
	LSH T1,1		;Left cycle
	TLNE T1,740000		;Time to fold the checksum?
	 CALL CKFOLD		;Yes, do so now
	AOS PBOPTR(PB)		;Point to next word
	SOJG T4,CHKHD1		;Go to next word
	RET
;CKFOLD - Fold 36-bit add-and-left-shift checksum into 16-bit
; ones-complement add-and-left-cycle checksum
;Takes	T1/ 36-bit checksum
;Returns +1 always, T1/ 16-bit checksum, right-justified
;Clobbers T2

	XRESCD			;Called by UPDCKS at interrupt level

CKFOLD:	CAIG T1,NILCHK		;Checksum within range?
	 RET			;Yes, no more folding to do
	LSHC T1,-^D16		;Overflow bits in T1, low 16 in T2
	LSH T2,-^D<36-16>	;Shuffle low 16 into place
	ADDI T1,(T2)		;Fold
	JRST CKFOLD		;Check again

	XSWAPCD
;CHKSIX - Compute checksum of pup in .PM16 or .PM16S data mode
;Takes	PB/ packet buffer pointer
;Return +1 always, T1/ 16-bit checksum
;Clobbers T1-T4, E

CHKSIX:	CALL CHKHDR		;Compute checksum for header, return in T1
	PUSH P,[0]		;Simulate stkvar for speed
	LOAD T4,PUPLEN		;Get pup length in bytes
	SUBI T4,MNPLEN		;Discount header and checksum bytes
	LSH T4,-1		;Compute number of 16-bit words
	ROT T4,-1		;Compute number of 36-bit words
	TLZE T4,(1B0)		;Did the rotate set the sign bit?
	 SETOM 0(P)		;"stkvar" is negative if odd word count
	XMOVEI T1,PBHEAD(PB)	;Address of data
	MOVEM T1,PBOPTR(PB)	;Clobber unused byte pointer
	MOVE E,[XWD NILCHK,NILCHK] ;Mask of significant bits
CHKSI0:	MOVE T2,@PBOPTR(PB)	;Fetch a word
	AND T2,E		;Ensure unused bits are zero
	LSHC T2,-^D18		;Low 16 bits right in T2, high 16 left in T3
	ADDI T1,(T2)		;Add first word
	LSH T1,1		;Left cycle sum
	LSH T3,-^D18		;Right justify high 16 bits
	ADDI T1,(T3)		;Add second word
	LSH T1,1		;Left cycle sum
	TLNE T1,740000		;Time to fold?
	 CALL CKFOLD		;Yes, fold into a 16-bit quantity
	AOS PBOPTR(PB)		;Point to next word in packet
	SOJG T4,CHKSI0		;Loop over header
	SKIPL 0(P)		;Odd number of words?
	 JRST CHKSI1		;No, all done
	HLRZ T2,@PBOPTR(PB)	;Get last word
	ANDI T2,NILCHK		;Mask off garbage bits
	ADDI T1,(T2)		;Add to sum
	LSH T1,1		;Left cycle
CHKSI1:	CALL CKFOLD		;Fold the checksum
	CAIN T1,NILCHK		;Did we arrive at the nil checksum?
	 SETZ T1,		;Yes, use zero instead
	ADJSP P,-1		;Adjust stack
	RET			;Return to caller
;CHKTXT - compute checksum for a pup in the .PMASC data mode
;Takes	PB/ packet buffer pointer
;Return +1 always, T1/ 16-bit checksum
;Clobbers T1-T4, E

CHKTXT:	CALL CHKHDR		;Compute header checksum, result in T1
	LOAD T4,PUPLEN		;Get pup length in bytes
	SUBI T4,MNPLEN		;Discount header and checksum bytes
	JUMPE T4,CHKTX2		;Any data?  If not, go finish up
	ADDI T4,1		;Round up
	LSH T4,-1		;Compute number of 16-bit words
	XMOVEI E,PBCONT(PB)	;Point to start of text

CHKTX1:	TLNE T1,774000		;Time to fold? (can only carry once)
	 CALL CKFOLD		;Yes, do so
	MOVE T2,(E)		;Get the first word
	LSHC T2,-^D29		;Shift out (if we shift in from T3 need ANDI)
	LSH T2,1		;Get a bit
	LSHC T2,7		;Shift in
	ADDI T1,(T2)		;Add
	LSH T1,1		;Left-cycle
	SOJLE T4,CHKTX2

	LSHC T2,7		;Pull byte from remainder in T3
	ANDI T2,177		;Mask ASCII
	LSH T2,1		;Get a bit
	LSHC T2,7		;Shift in
	ADDI T1,(T2)		;Add
	LSH T1,1		;Left-cycle
	SOJLE T4,CHKTX2

	LSHC T2,^D8		;Get final byte and extra bit of first word
	ANDI T2,376		;Mask ASCII shifted over one
	MOVE T3,1(E)		;Get next word to replenish T3
	LSHC T2,7		;Shift in
	ADDI T1,(T2)		;Add
	LSH T1,1		;Left-cycle
	SOJLE T4,CHKTX2

	LSHC T2,7		;Pull next byte from remainder of T3
	ANDI T2,177		;Mask ASCII
	LSH T2,1		;Get a bit
	LSHC T2,7		;Shift in
	ADDI T1,(T2)		;Add
	LSH T1,1		;Left-cycle
	SOJLE T4,CHKTX2

	LSHC T2,7		;Pull next byte from remainder of T3
	ANDI T2,177		;Mask ASCII
	LSH T2,1		;Get a bit
	LSHC T2,7		;Shift in
	ADDI T1,(T2)		;Add
	LSH T1,1		;Left-cycle

	ADDI E,2		;Done with that pair of words
	SOJG T4,CHKTX1		;Go back for the next

CHKTX2:	CALL CKFOLD		;Done, fold checksum again
	CAIN T1,NILCHK		;Did we arrive at the nil checksum?
	 SETZ T1,		;Yes, use zero instead
	RET			;Return to caller
;CHK36 - 36-bit mode checksum
;Takes	PB/ packet buffer pointer
;Return +1 always, T1/ 16-bit checksum
;Clobbers T1-T4, E

CHK36:	CALL CHKHDR		;Compute header checksum, result in T1
	LOAD T4,PUPLEN		;Get pup length in bytes
	SUBI T4,MNPLEN		;Discount header and checksum bytes
	JUMPE T4,CHKET3		;Any data?  If not, go finish up
	ADDI T4,1		;Round up
	LSH T4,-1		;Compute number of 16-bit words
	XMOVEI E,PBCONT(PB)	;Point to start of text
	JRST CHK36E		;Enter loop at start

; This is not the loop top, it is just a convenient place to break
CHK36L:	LSHC T2,4		;Shift in   T2/T3 :  4 /  0
	MOVE T3,1(E)		;Pull word           4 / 36
	LSHC T2,^D12		;More shift         16 / 24
	ANDI T2,177777		;Mask bits
	ADDI T1,(T2)		;Add
	LSH T1,1		;Left-cycle
	LSHC T2,^D16		;Shift in   T2/T3 : 16 /  8
	ANDI T2,177777		;Mask bits
	ADDI T1,(T2)		;Add
	LSH T1,1		;Left-cycle
	SUBI T4,2		;Count off words
	JUMPLE T4,CHK36B	;If done, stop

	LSHC T2,^D8		;Shift in   T2/T3 :  8 /  0
	MOVE T3,2(E)		;Pull word           8 / 36
	LSHC T2,^D8		;Shift in           16 / 28
	ANDI T2,177777		;Mask bits
	ADDI T1,(T2)		;Add
	LSH T1,1		;Left-cycle
	LSHC T2,^D16		;Shift in   T2/T3 : 16 / 12
	ANDI T2,177777		;Mask bits
	ADDI T1,(T2)		;Add
	LSH T1,1		;Left-cycle
	SUBI T4,2		;Count off words
	JUMPLE T4,CHK36B	;If done, finish up

	LSHC T2,^D12		;Shift in   T2/T3 : 12 /  0
	MOVE T3,3(E)		;Pull word          12 / 36
	LSHC T2,4		;Shift in           16 / 32
	ANDI T2,177777		;Mask bits
	ADDI T1,(T2)		;Add
	LSH T1,1		;Left-cycle
	LSHC T2,^D16		;Shift in   T2/T3 : 16 / 16
	ANDI T2,177777		;Mask bits
	ADDI T1,(T2)		;Add
	LSH T1,1		;Left-cycle
	LSH T3,-^D20		;Shift out  T2/T3 : 0  / 16
	ADDI T1,(T3)		;Add
	LSH T1,1		;Left-cycle
	ADDI E,4		;Used 4 more words from the packet
	SUBI T4,2		;Count off
	JUMPLE T4,CHK36B	;If that was all, stop

; This is the real loop top and entry point
CHK36E:	TLNE T1,777600		;Time to fold?
	 CALL CKFOLD		;Yes, do so
	MOVE T2,0(E)		;Pull word  T2/T3 : 36 /  0
	LSHC T2,-^D20		;Shift out          16 / 20
	ADDI T1,(T2)		;Add
	LSH T1,1		;Left-cycle
	LSHC T2,^D16		;Shift in   T2/T3 : 16 /  4
	ANDI T2,177777		;Mask bits
	ADDI T1,(T2)		;Add
	LSH T1,1		;Left-cycle
	SUBI T4,3		;Account for words needed by this word
	JUMPG T4,CHK36L		;If more, go do them

	TLZ T3,37777		;Mask out remaining bits for 4-bit case
CHK36B:	LSH T3,-^D20		;Shift into word, masked
	ADDI T1,(T3)		;Add
	LSH T1,1		;Left-cycle
	JRST CHKET3		;All done
;CHKETH - compute checksum for a pup in the .PM32 data mode
;Takes	PB/ packet buffer pointer
;Return +1 always, T1/ 16-bit checksum
;Clobbers T1-T4,E

CHKETH:	CALL CHKHDR		;Compute checksum for header, return in T1
	LOAD T4,PUPLEN		;Get pup length in bytes
	SUBI T4,MNPLEN		;Discount header and checksum bytes
	JUMPE T4,CHKET3		;Any data?  If not, go finish up
	ADDI T4,1		;Round up
	LSH T4,-1		;Compute number of 16-bit words
	SETZ E,			;Assume an even number of 16-bit words
	ROT T4,-1		;Compute number of 36-bit words
	TLZE T4,(1B0)		;Did the rotate set the sign bit?
	 SETO E,		;Yes, we have an odd number of 16-bit words
	JUMPE T4,CHKET2		;If never through loop, have only one/two bytes
CHKET1:	MOVE T2,@PBOPTR(PB)	;Fetch 32 bits, left justified
	LSHC T2,-^D20		;High 16 bits right just in T2, low left in T3
	ADDI T1,(T2)		;Add first word
	LSH T1,1		;Left cycle
	LSH T3,-^D20		;Right justify low 16 bits
	ADDI T1,(T3)		;Add second word
	LSH T1,1		;Left cycle
	TLNE T1,740000		;Time to fold the checksum?
	 CALL CKFOLD		;Yes, do so now
	AOS PBOPTR(PB)		;Point to next word
	SOJG T4,CHKET1		;Go to next word
	JUMPE E,CHKET3		;Jump if all done
CHKET2:	HLRZ T3,@PBOPTR(PB)	;Get the leftover 16-bit word
	LSH T3,-2		;Shift it into place
	ADDI T1,(T3)		;Add to checksum
	LSH T1,1		;Left-cycle
CHKET3:	CALL CKFOLD		;Fold the checksum
	CAIN T1,NILCHK		;Did we arrive at the nil checksum?
	 SETZ T1,		;Yes, use zero instead
	RET			;Return to caller
SUBTTL Free Storage Management

;STGINI - initialize pup free storage queues
;Called from PUPINI at system startup
;Clobbers T1-T4, PB

STGINI:	MOVE T1,[XWD PUPSEC,PBSTGB]	;Start of our packet buffers
	MOVE T2,[XWD PUPSEC,PBSTGE]	;End of packet buffers
	CALL LCKBUF		;Lock down our buffers
	MOVE PB,[XWD PUPSEC,PBSTGB]	;Pointer to very first buffer

;Now create the pool of small packet buffers 
	SETZM SMPBC		;Initially there are no free buffers
	XMOVEI T1,SMPBQ		;Get address of queue header
	MOVEM T1,HEAD(T1)	;Form empty queue
	MOVEM T1,TAIL(T1)	; ...
	MOVEI T4,NSMPB		;Number of small buffers we are creating
STGIN0:	MOVE T1,[PBSMF+SMPBLN]	;Header word =) small flag,,packet length
	MOVEM T1,PBFLAG(PB)	;Set up the header word
	XMOVEI T1,SMPBQ		;T1/ Queue header
	CALL APPIBQ		;Append packet to the queue
	AOS SMPBC		;Count another available packet
	ADDI PB,SMPBLN		;Increment the pointer
	SOJG T4,STGIN0		;Loop over all packets

;Now do the large packet buffers
	SETZM BGPBC		;Initially there are no free buffers
	XMOVEI T1,BGPBQ		;Get address of queue header
	MOVEM T1,HEAD(T1)	;Form a nil queue pointer
	MOVEM T1,TAIL(T1)	; ...
	MOVEI T4,NBGPB		;Number of large buffers we are creating
STGIN1:	MOVE T1,[PBBGF+BGPBLN]	;Header word =) large flag,,packet length
	MOVEM T1,PBFLAG(PB)	;Set up the header word
	XMOVEI T1,BGPBQ		;T1/ Queue header
	CALL APPIBQ		;Append packet to the queue
	AOS BGPBC		;Count another available packet
	ADDI PB,BGPBLN		;Increment the pointer
	SOJG T4,STGIN1		;Loop over all packets

;Now do the BSP storage
	MOVE PB,[XWD PUPSEC,BSPBEG]	;Set up pointer to start of BSP storage
	SETZM BSPC		;Initially there are no free buffers
	XMOVEI T1,BSPQ		;Get address of queue header
	MOVEM T1,HEAD(T1)	;Form a nil queue pointer
	MOVEM T1,TAIL(T1)	; ...
	MOVEI T4,NBSP		;Number of BSP blocks we are creating
STGIN2:	SETZM PBFLAG(PB)	;Clear flag word
	XMOVEI T1,BSPQ		;T1/ Queue header
	CALL APPIBQ		;Append packet to the queue
	AOS BSPC		;Count another available packet
	ADDI PB,BSPSZ0+BSPSIZ	;Increment the pointer
	SOJG T4,STGIN2		;Loop over all packets
	RET			;Return to caller
;LCKBUF - lock down a region of memory
;Takes	T1/ starting address
;	T2/ ending address
;Returns +1 always
;Clobbers T1-T4

LCKBUF:	TRZ T1,777		;Start on a page boundary
LCKBU0:	PUSH P,T1		;Save starting address
	PUSH P,T2		;And ending address
	CALL MLKMA		;Lock down that page
	POP P,T2		;Restore arguments
	POP P,T1		;...
	ADDI T1,PGSIZ		;Increment to next page
	CAMG T1,T2		;Past last address?
	 JRST LCKBU0		;No, loop
	RET			;Return to caller
;SMGET - get a small packet at interrupt level
;SMGETP - get small packet at process or scheduler level
;Returns +1 failure, no packets on queue
;	 +2 success, PB/ pointer to new packet
;Clobbers T1-T4, PB

	XRESCD

SMGETP:	CALL PILOCK		;Interlock if at process level
SMGET:	SOS SMPBC		;Decrement count of small packets
	XMOVEI T1,SMPBQ		;Queue header
	CALL REMITQ		;Pull a packet off the queue
	 JRST [ SETZM SMPBC	;Queue is empty, correct count
		RET ]		;And take a failure return
	XMOVEI PB,-PBLINK(T2)	;Set up pointer
	MOVE T1,PBFLAG(PB)	;Get flag word
	TXNN T1,PBSMF		;This is a small packet?
	 BUG.(HLT,SMGETX,PUP,SOFT,<PUP - inconsistent small packet queue>)
	RETSKP			;Good return to caller

	XSWAPCD
;BGGET - get a large packet at interrupt level
;BGGETP - get a large packet at process or scheduler level
;Returns +1 failure, no packets on queue
;	 +2 success, PB/ pointer to new packet
;Clobbers T1-T4, PB

	XRESCD

BGGETP:	CALL PILOCK		;Interlock if at scheduler level
BGGET:	SOS BGPBC		;Decrement count of large packets
	XMOVEI T1,BGPBQ		;Queue header
	CALL REMITQ		;Pull a packet off the queue
	 JRST [ SETZM BGPBC	;Queue is empty, correct count
		RET	]	;And give a fail return
	XMOVEI PB,-PBLINK(T2)	;Set up pointer
	MOVE T1,PBFLAG(PB)	;Get flag word
	TXNN T1,PBBGF		;This is a large packet?
	 BUG.(HLT,BGGETX,PUP,SOFT,<PUP - inconsistent big packet queue>)
	RETSKP			;Good return to caller

	XSWAPCD
;ASGPKT - assign packet buffer storage at process or scheduler level
;Ensures that process level code doesn't tie up all the free buffers
;Takes	T1/ number of 36-bit words in packet
;Returns +1 allocation low, T1/ scheduler test
;	 +2 got a packet, PB/ pointer
;Clobbers T1-T4, PB

ASGPKT:	CAIL T1,MNPBLX		;Minimum length
	CAILE T1,MXPBLX		;Maximum length
	 BUG.(HLT,ASGPKX,PUP,SOFT,<PUP - impossible packet size>)
	CAIG T1,SMPBLN		;Small packet?
	 JRST ASGPK0		;Yes, go handle it
	AOS STABPT		;Count a try a large buffer at process level
	MOVE T1,BGPBC		;Get number of available large packets
	CAILE T1,BGPBMN		;Allocation low?
	 CALL BGGETP		;Get a large packet
	  AOSA STABPM		;Allocation low or queue empty, count a miss
	   RETSKP		;Got one, skip return to caller
	HRROI T1,STGALT		;Set up scheduler test in funny manner
	RET			;Single return

;Here to assign a small buffer

ASGPK0:	AOS STASPT		;Count a try at process level
	MOVE T1,SMPBC		;Get number of available small packets
	CAILE T1,SMPBMN		;Allocation low?
	 CALL SMGETP		;Get a small packet
	  AOSA STASPM		;Allocation low or queue empty, count a miss
	   RETSKP		;Got one, skip return to caller
	MOVEI T1,STGALT		;Set up scheduler test
	RET			;Single return to caller
;STGALT - wait for free packets to become available
;T1 is zero if we're watching the queue of small packets

	RESCD

STGALT:	SKIPN PUPON		;Is PUP up?
	 JRST 1(T4)		;No, wakeup
	JUMPN T1,STGAL0		;If watching large queue, jump
	MOVE T1,SMPBC		;Get count of small packets 
	CAIG T1,SMPBMN		;Within range?
	 JRST 0(T4)		;No.
	  JRST 1(T4)		;Yes, wakeup

STGAL0:	MOVE T1,BGPBC		;Get count of large packets
	CAIG T1,BGPBMN		;Within range?
	 JRST 0(T4)		;No.
	  JRST 1(T4)		;Yes, wakeup

	XSWAPCD
;ASGPBI - assign a packet buffer at interrupt level
;Takes	T1/ data bytes in packet, less checksum and encapsulation
;Returns +1 failure, no free storage
;	 +2 success, PB/ buffer pointer

	XRESCD

ASGPBI:
;;	CAILE T1,^D20+^D8	;If very few bytes, use a small buffer
	CAILE T1,^D46		;If very few bytes, use a small buffer.
				; 46. is the number of data bytes in a minimum
				; sized 10MB datagram.
	 JRST ASGPI0		;Else use a full size buffer;
	AOS STASMT		;Count a try for a small buffer
	CALL SMGET		;Try for a small buffer
	 AOSA STASMM		;Nothing there, count it and skip
	  RETSKP		;Return with PB/ pointer
	   RET			;Take failure return

ASGPI0:	AOS STABGT		;Count a try for a large buffer
	CALL BGGET		;Want a full size buffer
	 AOSA STABGM		;Count a miss and skip
	  RETSKP		;Return with PB/ pointer
	   RET			;Take failure return

	XSWAPCD
	
;RELPKT - return packet buffer to free queue
;RELPBI - same routine, called from interrupt level
;RELBUG - same as RELPKT, but preserves temporary AC's
;Takes PB/ pointer to packet buffer
;Returns +1 always
;Clobbers T1-T4

	XRESCD

RELBUG:	SAVET			;Save temporaries (PUPBUG entry point)
RELPKT:	CALL PILOCK		;Enter interlock coroutine
RELPBI:	CAML PB,[XWD PUPSEC,PBSTGB]	;Perform a range check on the pointer
	 CAMLE PB,[XWD PUPSEC,PBSTGE]
	  JRST [ BUG.(CHK,BADPBX,PUP,SOFT,<Bad Pkt Ptr>,<<PB,PTR>,<UNIT,UNIT>>)
		 RET ]		;Bad, bugchk and do nothing
	MOVE T1,PBFLAG(PB)	;Get flag word
	TXNN T1,PBSMF		;A small buffer?
	IFSKP.
	  AOS SMPBC		;Yes, count the addition
	  JRST RELPB0		;Go append it to the queue
	ENDIF.
	TXNE T1,PBBGF		;A large buffer?
	IFSKP.
	  BUG.(HLT,RELPBX,PUP,SOFT,<PUP - bad packet ptr>,<<PB,PB>,<UNIT,U>>)
	  RET			;No, bugchk and do nothing
	ENDIF.
	AOS BGPBC		;Count a large packet being added
IFE REL6,<SKIPA T1,[XWD MSEC1,BGPBQ] ;Header of large packets>
IFN REL6,<SKIPA T1,[XWD XCDSEC,BGPBQ] ;Header of large packets>
RELPB0:	XMOVEI T1,SMPBQ		;Header of small packets
	CALL APPIBQ		;Append buffer
	HRLOI PB,377777		;Set pointer to catch reuse bugs (ILMNRF)
	RET			;Return to caller

	XSWAPCD
;ASGBSP - assign swappable free storage for the BSP levels
;Returns +1 failure
;	 +2 success, T1/ pointer to start of BSP block
;Clobbers T1-T4

ASGBSP:	NOSKED			;Protect integerity of queues
	SOS BSPC		;Decrement count of buffers
	XMOVEI T1,BSPQ		;Queue header
	CALL REMITQ		;Pull a packet off the queue
	 JRST [ SETZM BSPC	;Queue is empty, correct count
		OKSKED		;Reallow scheduling
		RET ]		;And take a failure return
	OKSKED			;Scheduling okay now
	XMOVEI T4,-PBLINK(T2)	;Set up pointer
	SKIPE PBFLAG(T4)	;Sanity check. Flag word is zero.
	 BUG.(HLT,ASGBSX,PUP,SOFT,<PUP - inconsistent BSP buffer queue>)
	MOVEI T1,BSPSIZ		;T1/ Length of data portion
	XMOVEI T2,BSPSZ0(T4)	;T2/ Source address
	SETZM (T2)		;Zero that first word 
	XMOVEI T3,1(T2)		;T3/ Destination address
	CALL XBLTA		;Zero the storage
	XMOVEI T1,BSPSZ0(T4)	;Return pointer offset to data portion
	RETSKP			;Good return to caller
;RELBSP - return BSP storage to free queue
; Note that we prepend buffers to the free queue.  This tends to restrict
;  paging activity to the low end of the buffer area.
;Takes T1/ pointer to BSP block
;Returns +1 always
;Clobbers T1-T4

RELBSP:	XMOVEI T1,-BSPSZ0(T1)	;Adjust pointer to point to header words
	SKIPN PBFLAG(T1)	;Sanity check on flag word, should be zero
	IFSKP.
	  BUG.(CHK,RELBSX,PUP,SOFT,<PUP - bad BSP packet pointer>)
	  RET			;Bugchk and ignore bad storage
	ENDIF.
	XMOVEI T2,PBLINK(T1)	;Address of buffer's link word
	XMOVEI T1,BSPQ		;Queue header
	NOSKED			;Protect queue integrity
	AOS BSPC		;Count the addition
	CALL PREITQ		;Prepend the buffer to the queue
	OKSKED			;Reallow scheduling
	RET			;Return to caller
SUBTTL Queueing Routines

;GETPUP - Get Pup from port input queue
;Call from process level
;Takes	UNIT/ Pup unit number
;Returns +1  Queue empty, T1/ scheduler test
;	 +2  PB/ Packet Buffer pointer, counts updated appropriately
;Clobbers T1-T3

	XRESCD

GETPUP:	CALL PILOCK		;Enter interlock coroutine
	MOVE T1,UNIT		;Get our port number
	LSH T1,1		;Double it for index into PUPIBQ
	XMOVEI T1,PUPIBQ(T1)	;Input queue header for this port
	MOVE T2,(T1)		;Get queue header
IFE REL6,<
	CAML T2,[XWD MSEC1,PUPIBQ]
	CAMLE T2,[XWD MSEC1,PUPIBQ+<2*NPUPUN>+1]
>;IFE REL6
IFN REL6,<
	CAML T2,[XWD XCDSEC,PUPIBQ]
	CAMLE T2,[XWD XCDSEC,PUPIBQ+<2*NPUPUN>+1]
>;IFN REL6
	 TRNA
 	  JRST GETPU0		;Within range of a nil queue header
	CAML T2,[XWD PUPSEC,PBSTGB]
	CAMLE T2,[XWD PUPSEC,PBSTGE]
	 TRNA
 	  JRST GETPU0		;Within range of PUP storage
	BUG.(CHK,GETPUZ,PUP,SOFT,<PUPIBQ messed up>,<<UNIT,UNIT>,<T2,T2>>)
	MOVEM T1,HEAD(T1)	;Complain, and make a nil queue
	MOVEM T1,TAIL(T1)	; ....
GETPU0:	CALL REMITQ		;Pull item from head of list
	 JRST GETPUX		;Queue is empty, go set test
	XMOVEI PB,-PBLINK(T2)	;Set up PB to point to head of packet buffer
	SOSL PUPIBC(UNIT)	;Decrement input count
	 RETSKP			;Good count, return to caller
	BUG.(CHK,DECIQZ,PUP,SOFT,<PUP - Over-decremented Pup input count>)
	SETZM PUPIBC(UNIT)	;Try to repair the damage
	RETSKP			;Skip return

;Here if the queue was empty.  Ensure good count and set blocking test.

GETPUX:	SETZM PUPIBC(UNIT)	;Make sure count is consistent with queue
	MOVE T1,UNIT		;Get port number
	LSH T1,1		;Double for index into PUPIBQ
	HRLI T1,PUPIBQ(T1)	;Set 18-bit address of input queue
	HRRI T1,NEPBQT		;Scheduler test for non-zero input queue
	RET			;Take a fail return

	XSWAPCD
;NEPBQT - Scheduler test for non-empty packet buffer queue
;Argument is address of queue header

	RESCD

NEPBQT:	SKIPN PUPON		;PUP still on?
	 JRST 1(T4)		;No, wakeup so connection will be killed
IFE REL6,<HRLI T1,MSEC1		;Queue headers are in code section>
IFN REL6,<HRLI T1,XCDSEC	;Queue headers are in code section>
	MOVE T2,HEAD(T1)	;Get head item in queue
	CAMN T2,T1		;Self-pointer?
	 JRST 0(T4)		;Yes, queue still empty
	JRST 1(T4)		;Non-empty, wakeup

	XSWAPCD
;PUTPUP - queue a pup for output on the MEIS
;Call from process context only.
;Performs partial Ethernet encapsulation (header bytes).
;Takes	PB/ packet buffer pointer
;	UNIT/ unit number of owning port
;Returns +1 Bad packet, i.e., unable to queue to destination
;	 +2 Success

PUTPUP:	MOVE T1,FORKX		;Get our system fork number
	CAMN T1,PUPFRK		;Background process?
	 AOSA STAPBG		;Yes, count a packet
	  AOS STAPPR		;Else count a hit at process level
	LOAD T1,PUPDN		;Get destination network
	OPSTR <SKIPE>,ROUHST,(T1) ;Is there some host we should route to?
	 LOAD T1,ROUNET,(T1)	;Yes, get network we are routing to
	JE NETADR,(T1),PUTPU0	;Jump if we're no longer on that subnet
 	LOAD T2,PUPDH		;Get destination host
	LOAD T3,PUPDN		;Get destination network
	HRLI T2,400000(T3)	;Form 1B0+subnet,,host for ENCAPS subroutine 
	CALL PUPOUT		;Send packet to physical I/O routines
	 JRST PUTPU1		;Some failure, analyze and recover
	RETSKP			;Success, packet queued for transmission

;Here if we can't send the pup.  We make sure to recover the storage for
; non-BSP pups.  BSP pups that lost will be recovered by when a timeout occurs.

PUTPU0:	MOVEI T1,PUPX21		;"Invalid destination network" error
PUTPU1:	PUSH P,T1		;Save error code for caller
	SKIPN PBLINK(PB)	;Does this packet belong to BSP?
	 CALL RELPKT		;No, release the packet now
	POP P,T1		;Restore error code
	RET			;Return to caller
;FSHPBQ - Flush packet buffer queue
;Call from process level
;Takes	T1/ Address of queue header
;Returns +1 always.
;Clobbers T1-T4, PB

FSHPBQ:	STKVAR <FSHPBS>		;Queue head
	MOVEM T1,FSHPBS		;Save pointer to queue header
FSHPB1:	CALL REMIBQ		;Remove item from head of queue
	 RET			;Queue empty, done
	CALL RELPKT		;Release packet buffer
	MOVE T1,FSHPBS		;Restore queue header pointer
	JRST FSHPB1		;Repeat till queue empty
;APPIBQ - Append packet buffer to input queue at interrupt level
;	PB/ Packet Buffer pointer
;	T1/ Address of queue header
;Returns +1 always
;Clobbers T1-T3

	XRESCD

APPIBQ:	XMOVEI T2,PBLINK(PB)	;Get adr of new PB's link word
	CALLRET APPITQ		;Append item to queue

	XSWAPCD


;REMIBQ - Remove packet buffer from input queue at non-interrupt level,
;	T1/ Address of queue header
;Returns +1 Queue empty
;	 +2 success, PB/ Address of packet buffer
;Clobbers T1-T3

	XRESCD

REMIBQ:	CALL PILOCK		;Enter interlock coroutine
	CALL REMITQ		;Remove item from queue
	 RET			;Empty, single return
	XMOVEI PB,-PBLINK(T2)	;Set up packet buffer pointer
	RETSKP			;Skip return

	XSWAPCD
COMMENT \

Primitive queueing/dequeueing routines:

Local address queueing routines:

All queues are double linked through 36-bit link words.  A queue header is
in the form "tail,,head" where self-pointers denote an empty queue.  A queue
item (e.g. a packet link word) is in the form "predecessor,,successor".  All
pointers point to other queue items or to header words to permit uniform
queue management without special cases.  The caller is responsible for
interlocking queue access for race prevention.

Global address queueing routines:

All queues are double linked through 36-bit link doublewords. A queue header
is in the form:
			head pointer
			tail pointer

where both pointers pointing to the address of the head pointer denote an
empty queue. A queue item (e.g. a packet link doubleword) is in the form:

			successor
			predecssor.
		
The symbols HEAD and TAIL are used to refer to the first and second link
words respectively. All pointers point to other queue items or to header words
to permit uniform queue management without special cases.  The caller is
responsible for interlocking queue access for race prevention.
\
;APPITQ - 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

APPITQ:	MOVE T3,TAIL(T1)	;Get current tail
	MOVEM T2,TAIL(T1)	;Queue tail := new item
	MOVEM T1,HEAD(T2)	;New item's sucessor := queue header
	MOVEM T3,TAIL(T2)	;New item's predecessor := old tail
	MOVEM T2,HEAD(T3)	;Old tail's sucessor := new item
	RET			;Return to caller

	XSWAPCD
;PREITQ - prepend to queue
;Takes	T1/ queue header
;	T2/ queue item
;Returns +1 always
;Clobbers T3

	XRESCD

PREITQ:	MOVE T3,HEAD(T1)	;Get current head
	MOVEM T2,HEAD(T1)	;Queue head := new item
	MOVEM T1,TAIL(T2)	;New item's predecessor := queue head
	MOVEM T3,HEAD(T2)	;New item's successor := old head
	MOVEM T2,TAIL(T3)	;Old head's predecessor := new item
	RET			;Return to caller

	XSWAPCD
;REMITQ - Remove item from head of queue
;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

REMITQ:	MOVE T2,HEAD(T1)	;Get current head
	CAMN T2,T1		;Empty?
	 RET			;Yes, fail
	MOVE T3,HEAD(T2)	;No, get successor
	MOVEM T3,HEAD(T1)	;Queue head := successor
	MOVEM T1,TAIL(T3)	;Successor's predecessor := queue header
	SETZM (T2)		;Clear links in removed item
	RETSKP			;Skip return

	XSWAPCD
;DELITQ - Delete item from queue, i.e. unlink it, wherever it is in queue
;LDELTQ - same as DELITQ but assumes local addresses
;Takes	T1/ Address of item to be deleted
;Returns +1 always, T2/ Address of item that was predecessor to this one
;		    T3/ Address of item that was successor to this one
;Clobbers T2,T3

	XRESCD

DELITQ::MOVE T2,TAIL(T1)	;Get this item's predecessor
	MOVE T3,HEAD(T1)	;Get this item's successor
	MOVEM T2,TAIL(T3)	;Fix links between predecessor
	MOVEM T3,HEAD(T2)	; and successor
	SETZM (T1)		;Clear links in deleted item
	RET    

LDELTQ::HLRZ T2,(T1)		;Get this item's predecessor
	HRRZ T3,(T1)		;Get this item's successor
	HRLM T2,(T3)		;Fix links between predecessor
	HRRM T3,(T2)		; and successor
	SETZM (T1)		;Clear links in deleted item
	RET    

	XSWAPCD
SUBTTL Physical I/O Routines - the MEIS

;BLDIOW, BLDIOR - build an iorb for transmission or reception
;Takes  T1/ address of iorb
;	P1/ NCT pointer
;	PB/ address of a packet buffer containing at least the header bytes
;	UNIT/ pup port index
;Returns +1 with iorb assembled and ready to be queued
;Clobbers T1-T4

	XRESCD

BLDIOW:	TDZA T4,T4		;T4 := 0 if writing
BLDIOR: SETO T4,		;T4 := -1 if reading
	SAVEAC <E>		;Preserve this register
	MOVE E,T1		;we'll use E to reference the iorb
	STOR PB,IRBUF,(E)	;Remember packet buffer address
	STOR UNIT,PUPRT,(PB)	;Remember our pup port number
	SETONE IRHDR,(E)	;We are using 32-bit header mode
	DMOVE T2,[ IRFWRT	;Assume writing
		   PUPWIN ]
	SKIPE T4		;Well?
	 DMOVE T2,[ IRFRED	;No, reading
		    PUPRIN ]
	STOR T2,ISFCN,(E)	;Set iorb function
	HRRZM T3,IRBIVA(E)	;Set done interrupt address
	CALL GETMOD		;Return data mode in T2
	STOR T2,IRPMD,(E)	;Set pup data mode
	JUMPE T4,BLDIO1		;Jump if writing
	MOVE T1,RCILEN		;Get received length
	SUBI T1,^D20		;Discount PUP protocol header bytes
	SUB T1,NTCAPB(P1)	;Subtract hardware encapsulation bytes
	JRST BLDIO2		;Go join common code

BLDIO1:	LOAD T1,PUPLEN		;Get pup length in Ethernet bytes
	SUBI T1,MNPLEN		;Subtract off header and checksum bytes
	SKIPN NTETHR(P1)	;If it's a 3MB interface
	 JRST BLDIO2		;Then don't need to worry about 10MB padding
	MOVNI T3,-MINDAT(T1)	;Compute distance to minimum hdw size
	TRNN T3,1B18		;Skip if packet is larger than minimum
	 CALL PADPUP		;Else pad the byte count for 10MB hardware
BLDIO2:	MOVEM T1,IRBCNT(E)	;Stash count of Ethernet data bytes
	MOVE T3,NTCAPC(P1)	;Get count of encapsulation words (16-bit)
	ADDI T3,^D10		;Add header words for PUP protocol
	STOR T3,IRHLN,(E)	;Set length of pup header in 36 bit words
	CALL WRDCNV		;Convert bytes to words
	MOVE T3,T1		;T3/ total number of 36-bit words
	MOVE T1,E		;T1/ pointer to iorb
	XMOVEI T2,PBPHYS(PB)	;T2/ start of data area in buffer
	ADD T2,NTOFF(P1)	;Adjust for network type
	MOVE T4,NTENCU(P1)	;T4/ CDB,,UDB for the interface
	CALL BLDCCW		;Build CCW list
	RET			;Return with iorb pointer in T1
;PADPUP - insert checksum and padding for 10MB PUP datagrams
;If we get here we are writing a short PUP to the 10MB Ethernet.
;Takes	T1/ data length less checksum and header
;	T2/ MEIS data mode
;	T3/ no. of padding bytes needed
;	PB/ packet buffer pointer
;Returns +1 always, T1 updated for new data length, T2 preserved

PADPUP:	HRRZS T3		;Flush garbage bits on left
	CAIG T3,1		;If one or fewer padding bytes
	RET			;Then the interface will supply the padding
	ADDI T1,(T3)		;Pad out the byte count now
	CAIN T2,.PM36		;36-bit data mode?
	 JRST PADPU0		;Yes, skip useless set up
	LOAD T4,PUPLEN		;Get PUP length in bytes
	ADDI T4,4*PBHEAD-1	;Compute buffer relative 16-bit word offset
	LSH T4,-1		;  of Pup checksum
	ROT T4,-1		;Compute 36-bit word offset
	LOAD T3,PUCHK		;Get PUP checksum ready
PADPU0:	CALL @PADTAB(T2)	;Invoke the appropriate padding routine
	RET			;Return to caller

;Table of padding routines indexed by data mode

PADTAB:	DSP (PAD16)		;.PM16
	DSP (PADETH)		;.PM32
	DSP (PAD36)		;.PM36
	DSP (PADASC)		;.PMASC
	DSP (PAD16)		;.PM16S
	DSP (PAD9)		;.PM9
;.PM16 and .PM16S

PAD16:	JUMPL T4,.+2		;Which halfword?
	 TLOA T4,(POINT 16,(PB),17)	;Left
	 HRLI T4,(POINT 16,(PB),35)	;Right
	DPB T3,T4		;Store checksum in appropriate spot
	RET			;Return to caller

;.PM32

PADETH:	JUMPL T4,.+2		;Which halfword?
	 TLOA T4,(POINT 16,(PB),15)	;Left
	 HRLI T4,(POINT 16,(PB),31)	;Right
	DPB T3,T4		;Store checksum in appropriate spot
	RET			;Return to caller

;.PM36

PAD36:	PUSH P,T1		;Preserve T1
	LOAD T1,PUPLEN		;Get back our data length
	SUBI T1,MNPLEN		;Discount header and checksum
	SETZ T3,		;No encapsulation bytes
	CALL WRDCNV		;Compute number of data words
	ADDI T1,PBCONT		;Add offset of start of data words
	HRLI T1,(POINT 16,(PB),15)	;Always left justified in the word
	LOAD T3,PUCHK		;Get checksum
	DPB T3,T1		;Store checksum
	POP P,T1		;Restore byte count into T1
	RET			;Return to caller

;.PMASC, resquiat in pace

PADASC:	BUG.(INF,PUPASC,PUP,SOFT,<PUP - Cannot pad ASCII mode on 10Mb net>)
	RET

;.PM9

PAD9:	JUMPL T4,.+2		;Jump if checksum is on right side
	TLOA T4,(POINT 9,(PB),0)	;Set up byte pointer into packet
	 HRLI T4,(POINT 9,(PB),18)	;Set up byte pointer into packet
	LDB T2,[POINT 8,T3,27]	;Get first octet of checksum
	IDPB T2,T4		;Store it and advance pointer
	LDB T2,[POINT 8,T3,35]	;Get second octet of checksum
	IDPB T2,T4		;Store it
	MOVEI T2,.PM9		;Reset clobbered value in T2
	RET			;Return to caller
;PUPOUT - queue up a pup for output by the MEIS
;Call from process context
;Takes	T1/ immediate destination subnet number
;	T2/ 1B0+subnet,,host of \ultimate/ destination
;	PB/ pointer to packet buffer
;	UNIT/ pup port number
;returns +1 error queueing the pup
;	 +2 successfully queued
;Clobbers T1-T4

PUPOUT:	SAVEAC<P1>		;We will use this as the NCT pointer
	XMOVEI P1,NCTVT		;Point to the NCT table
PUPOU0:	LOAD P1,NTLNK,(P1)	;Get net in the chain
	JUMPE P1,[RETBAD(PUPX6)] ;Can't get there from here
	SKIPE NTORDY(P1)	;Is interface up?
	 CAME T1,NTSUBN(P1)	;Same subnet?
	  JRST PUPOU0		;No, try again
	MOVE T1,T2		;PUP Protocol address
	XMOVEI T2,PBPHYS(PB)	;Pointer to encapsulation area
	MOVX T3,ET%PUP		;Datagram type is PUP
	CALL ENCAPS		;Encapsulate the datagram
	IFNSK.
	  JUMPE T1,[RETBAD(ETHRX2)] ;Failure, T1/0 means we tried ARP
	  RETBAD(PUPX6)		;Else we have a hard failure.
	ENDIF.
	CALL ASGIRB		;Get iorb pointer in T1
	 RETBAD(MONX01)		;Pup fork tried to block, take error return
	CALL BLDIOW		;Build the IORB, ret pointer in T1
	LOAD T2,PUCHK
	STOR T2,IRCHK,(T1)	;Set up Pup checksum
	SETONE IRTRF,(T1)	;Set flag that we are writing a checksum
	MOVE T2,NTENCU(P1)	;Set up CDB,,UDB for this NCT
	NOSKED			;Turn off scheduling
	CALL PHYSIO		;Pass iorb to PHYMEI routines
	OKSKED			;Resume scheduling
	AOS STAXMT		;Count another pup queued for output
	RETSKP			;Return to caller
;PUPWIN - write done interrupt routine
;Dequeues the iorb and releases the packet buffer if it doesn't belong to BSP.
;No actions taken if the iorb indicates errors - we assume the packet will
; be retransmitted later when the MEIS is feeling better.
;Called at interrupt level from PHYSIO
;Takes T1/ pointer to iorb
;Returns +1 always

	XRESCD

IFE REL6,<PUPWIN: EA.ENT>
IFN REL6,<XRENT PUPWIN>
	PUSH P,PB		;Don't clobber this register
	LOAD PB,IRBUF,(T1)	;Get virtual address of packet buffer
	MOVE T2,IRBSTS(T1)	;Get status flags
	TXNE T2,IS.ERR		;Any error during the transfer?
	 AOS STAXBD		;Yes, just count it
	CALL RELIRB		;Put iorb back on free queue
	SKIPN PBLINK(PB)	;Does this packet belong to BSP?
	 CALL RELPBI		;No, release the packet now
	POP P,PB		;Restore preserved register
	RET			;Return to caller

	XSWAPCD
;PUPRCI - Process a PUP Reception Interrupt
;Called at interrupt level, assumes environment set up by ETHRCI
;Setup:	RCILEN - byte count of packet
;	P1/ pointer to Internet NCT for the interface
;Return: +1 flush the pup, we're done with it
;	 +2 want to read more of the pup, address of iorb in T1
;Clobbers T1-T4; note that Px are preserved by caller (includes PB!)

;The following cells are not STKVAR's for the sake of speed
;We can get away with this since PUPRCI is called only at interrupt level

RS RCIDAT			;Total byte count, less encaps. and checksum

	XRESCD

PUPRCI::SKIPN PUPON		;Is the PUP code on?
	 JRST PKOWAI		;No, see if we are waif collecting 
	SAVEAC<UNIT>		;We will use this AC
	AOS STARCV		;Count the interrupt
	MOVE T1,RCILEN		;Get byte count of packet
	MOVE T2,NTCAPB(P1)	;Get count of encapsulation bytes
	SUBI T1,2(T2)		;Compute total data bytes (subtract checksum)
	MOVEM T1,RCIDAT		;Remember for later use
	CALL ASGPBI		;Go for a buffer
	 RET			;No can do, must flush datagram
	MOVEI T4,^D20+SNFCNT	;Read header bytes and some data
	CAML T4,RCIDAT		;Unless there isn't much data
	 MOVE T4,RCIDAT		;In which case read only what's there
	ADD T4,NTCAPB(P1)	;Add in encapsulation bytes
	XMOVEI T1,PBPHYS(PB)	;Get address of data portion of buffer
	ADD T1,NTOFF(P1)	;Add offset based on network type
	MOVE T2,NTENCU(P1)	;Get CDB,,UDB address
	CALL MEIRHD		;Read some bytes from RAM into buffer
	MOVE T1,RCIDAT		;Get data length seen by hardware
	LOAD T2,PUPLEN		;Get data length claimed by packet (plus chksm)
	SKIPE NTETHR(P1)	;If 10MB interface and the software length...
	 CAIL T2,MNPLEN+MINDAT	;...is less than the minimum hardware length
	  TRNA			;Neither, sanity test is valid
	   JRST PUPRC0		;Then skip the usual sanity test
	SUBI T1,(T2)		;Compute hardware and software count difference
	CAILE T1,1		;Zero or one is good (may have garbage byte)
	 CALL PUPRCX		;Anything else is a bad packet, drop it
PUPRC0:	CAIL T2,MNPLEN		;Check for legal length
	 CAILE T2,MXPLEN	; ...
	  CALL PUPRCX		;Bad protocol length, drop it
	MOVE T1,NTCAPB(P1)	;Get count of encapsulation bytes
	ADDI T1,(T2)		;Calculate byte offset of checksum
	MOVE T2,NTENCU(P1)	;Get CDB,,UDB address
	CALL MEIRTL		;Read last 16-bits of packet (PUP checksum)
	STOR T1,PUCHK		;Store the checksum
	CALL NETDFT		;Default networks, update checksum if necessary
	 CALL PUPRCX		;A bad network number
;PUPRCI (cont'd)

	LOAD T1,PUPDN		;Get destination network
	LOAD T3,NETADR,(T1)	;Get our host address on destination net
	LOAD T2,PUPDH		;Get Pup destination host
	CAIN T2,(T3)		;Pup sent to us?
	IFSKP.
	  AOS STAGAT		;No, count a gateway pup
	  SKIPL PUPPAR+.PPFLG	;Skip if we're a gateway
	   JRST PUPWAI		;Else quietly flush buffer and datagram.
	  MOVEI UNIT,GATPRT	;Set up our special gateway "port"
	ELSE.
	  CALL GETPDS		;Yes, get destination socket in T1
	  MOVE T2,T1		;PRTLUK wants the socket in T2
	  LOAD T1,PUPDH		;Get destination host
	  LOAD T3,PUPDN		;Get destination net
	  HRLI T1,(T3)		;Form net,,host
	  SETZ UNIT,		;No special flags
	  CALL PRTLUK		;Lookup local port, set UNIT
	   JRST PUPWAI		;No port, dump the waif
	ENDIF.
	MOVE T1,PUPIBC(UNIT)	;Get count of packets already queued
	CAIGE T1,MAXQDI		;Within range?
	IFSKP.
	  AOS STAIQL		;No, count a miss
	  JRST RELPBI		;And flush this packet
	ENDIF.
	CALL GETMOD		;Determine pup's data mode
	LOAD T1,PUPLEN		;Get pup length
	SUBI T1,MNPLEN		;Subtract off header and checksum bytes
	JUMPE T1,PUPRC1		;Can always optimize if no data
	CAIN T2,.PM32		;Favorite flavor of data mode?
	 CAILE T1,SNFCNT	;Yes, a small number of bytes?
	  TRNA			;No to either, must make the channel work
	   JRST PUPRC1		;Looks like we can optimize
	CALL ASGIRI		;Get an iorb block, return pointer in T1
	 JRST PUPIOX		;No iorbs, go release buffer and log error
	CALL BLDIOR		;Build an iorb for reception, return ptr in T1
	OPSTR <SKIPN>,PUDFT,(PB)  ;Skip if any fields defaulted by NETDFT
	IFSKP.
	  LOAD T2,PUPDN		;Get possibly defaulted destination net
	  STOR T2,PUDNT,(PB)	;Remember it
	  LOAD T2,PUPDH		;Get possibly defaulted destination host
	  STOR T2,PUDHS,(PB)	;Remember it
	  LOAD T2,PUPSN		;Get possibly defaulted source net
	  STOR T2,PUSRC,(PB)	;Remember it
	ENDIF.
	RETSKP			;Return to caller with the iorb

PUPRC1:	AOS STASHT		;Count a short datagram
	CALLRET PUPNR0		;Go append pup to queue, etc.
				;(we join the code in PUPRIN)
;PUPRCI (cont'd)

;Error returns from PUPRCI
;We count the error returns and drop the interrupt

;Badly formatted pups end up here.
;If we're logging pupbugs, generate a BUGCHK.
;Can back out of this by typing R$G in EDDT

PUPRCX:	HRRZ CX,0(P)		;Get our return PC
	SKIPE SYSIFG		;Don't log if job 0 (esp. syserr) not inited
	 SKIPN PUPBGF		;Must be up for a while and logging pupbugs
	  TRNA			;Either is false, skip the bugchk
	   BUG.(CHK,BADPUP,PUP,SOFT,<PUP - Malformed PUP at PUPRCI>,<<CX,PC>>)
	AOS STAFRM		;Keep count of badly formatted pups
	CALL RELPBI		;Flush the buffer
	ADJSP P,-1		;Fudge stack pointer for sneak return
	RET			;Return to caller's caller (flush packet)

;Waifs are discarded.  First we check with the PKOPR% net monitoring code
; to see if we are interested in junk packets.

PUPWAI:	AOS STAWAI		;No local port is listening
	CALL RELPBI		;Release pup buffer
	CALL PKOWAI		;See if PKOPR% is interested
	 RET			;It isn't, just flush the packet
	RETSKP			;We are reading the packet, T1/ iorb pointer

;If we have no free iorbs, we can't read the packet.  Drop it.

PUPIOX: AOS STAIOB		;No free iorbs
	CALL RELPBI		;Release the buffer
	RET			;Flush the packet
;NETDFT - default network addresses if necessary
;Takes  PB/ pointer to packet buffer
;	P1/ NCT pointer
;Returns +1 one of the network numbers was out of range
;	 +2 net numbers good,
;		with PUPDN, PUPSN, PUPDH, and PUCHK updated if necessary
;Clobbers T1-T4
	
NETDFT:	SETZM PBDFLT(PB)	;No fields defaulted yet
	LOAD T1,PUPDN		;Get pup destination net
	CAILE T1,NPNETS		;Range check
	 RET			;Out of range
	JUMPN T1,NETDF0		;Non-zero, so no need to default
	MOVE T1,NTSUBN(P1)	;T1/ use incoming network number as new value
	MOVE T2,[STRPTR<PUPDN>]	;T2/ pointer to field
	MOVE T3,[STRPTR<PUCHK>] ;T3/ pointer to checksum
	CALL UPDCKS		;Change field, update checksum
	SETONE PUDFT,(PB)	;Set field default flag
NETDF0:	LOAD T1,PUPSN		;Get Pup source net
	CAILE T1,NPNETS		;Range check
	 RET			;Out of range
	JUMPN T1,NETDF1		;Non-zero, so no need to default
	MOVE T1,NTSUBN(P1)	;T1/ use incoming network number as new value
	MOVE T2,[STRPTR<PUPSN>]	;T2/ pointer to field
	MOVE T3,[STRPTR<PUCHK>] ;T3/ pointer to checksum
	CALL UPDCKS		;Change field, update checksum
	SETONE PUDFT,(PB)	;Set field default flag
NETDF1:	LOAD T1,PUPDH		;Get pup destination host
	JUMPN T1,RSKP		;Non-zero, so no need to default
	LOAD T1,PUPDN		;Get back the destination network
	LOAD T1,NETADR,(T1)	;Use our host number on that network
	MOVE T2,[STRPTR<PUPDH>]	;T2/ pointer to field
	MOVE T3,[STRPTR<PUCHK>] ;T3/ pointer to checksum
	CALL UPDCKS		;Change field, update checksum
	SETONE PUDFT,(PB)	;Set field defaulted flag
	RETSKP			;Return to caller
;PUPRIN - process an input done interrupt from PHYSIO
;Call at interrupt level
;Takes	T1/ address of iorb
;Returns +1 always

IFE REL6,<PUPRIN: EA.ENT>
IFN REL6,<XRENT PUPRIN>
	SAVEAC <PB,UNIT>	;Save some PUP registers 
	LOAD PB,IRBUF,(T1)	;Get virtual address of packet buffer
	LOAD UNIT,PUPRT,(PB)	;Get owning port number
	MOVE T2,IRBSTS(T1)	;Get status flags from the iorb
	IFXN. T2,IS.ERR		;Was there a transfer error?
	  AOS STARBD		;Yes, count it
	  CALL RELIRB		;Release iorb
	  JRST RELPBI		;Release packet buffer and return
	ENDIF.
	CALL RELIRB		;Put iorb back on free iorb queue
	LOAD T2,PUDFT,(PB)	;Get setting of default flag
	IFN. T2
	  LOAD T2,PUDNT,(PB)	;Get defaulted destination network
	  STOR T2,PUPDN		;Stash it
	  LOAD T2,PUDHS,(PB)	;Get defaulted destination host
	  STOR T2,PUPDH		;Stash it
	  LOAD T2,PUSRC,(PB)	;Get defaulted source network
	  STOR T2,PUPSN		;Stash it
	ENDIF.
	CAIN UNIT,GATPRT	;Gateway pup?
	 JRST PUPNR0		;Yes, skip port deletion checks
	SKIPG T3,PUPLSK(UNIT)	;Skip if port is still in use
	 JRST RELPBI		;Punt if port was deleted or is now free
	CALL GETPDS		;Get destination socket (we clobber T1, T2)
	CAME T1,T3		;Is the socket still ours?
	 JRST RELPBI		;No, port isn't ours anymore, drop packet
PUPNR0:	MOVE T1,UNIT		;Get unit number
	LSH T1,1		;Double for index into PUPIBQ
	XMOVEI T1,PUPIBQ(T1)	;Get address of input queue header
	CALL APPIBQ		;Append Pup to queue
	AOS PUPIBC(UNIT)	;Count a packet in the queue
	CAIN UNIT,GATPRT	;Was that a gateway pup?
	 JRST [ SIGPBP(GAT)	;Yes, signal background process
	        RET  ]		;And return now
	CALL PUPINT		;Request a PSI from scheduler if necessary
	SKIPN PUPBSP(UNIT)	;BSP port?
	 RET			;No, done now
	MOVX T1,BSWAKF		;Set flag to awaken BSP processor
	IORM T1,PUPSTS(UNIT)
	MOVE T1,TODCLK		;Queue request for background task
	HRRE T2,PUPPSI(UNIT)	;Get BSP linkage/NVT number
	AOJGE T2,PUPNR1		;Process request immediately if NVT
IFN STANSW!REL6,<	;NULJBF is Release 6 and Stanford Release 5.3+
	SKIPN NULJBF		;Is scheduler running the null job?
	 JRST ADDTQI		;No, add port to timer queue
>;IFN STANSW!REL6
IFE REL6,<AOSA PSKED		;Yes, make sure scheduler wakes up>
IFN REL6,<AOSA PSKD1		;Yes, make sure scheduler wakes up>
PUPNR1:	ADDI T1,IBWDLY		;Delayed otherwise
	CALLRET ADDTQI		;Add port to timer queue and return
;PUPINT - set bit table flags for a packet received PSI
;Call at interrupt level
;Takes	UNIT/ port to interrupt
;Clobbers T1-T4

	XRESCD

PUPINT:	HRRE T1,PUPPSI(UNIT)	;Get fork to interrupt
	JUMPL T1,R		;Quit if none
	LOAD T1,RECPSI		;Get PSI channel to interrupt on
	CAIL T1,^D36		;Armed?
	RET			;No, quit
	SKIPL T3,UNIT		;Get the unit number into place 
	 CAIL T3,NPUPUN		;Within range?
	  RET			;No, just punt
	IDIVI T3,^D36		;Calculate word (T3) and bit (T4) offsets
	MOVE T1,BITS(T4)	;Get the bit
	IORM T1,PUPSKD(T3)	;And set it in the correct word
	AOS P7INTC		;Set flag for scheduler
	RET			;Return to caller

	XSWAPCD
;PUPCH7 - perform scheduler level functions for PUP
;Called at scheduler level every short cycle (20 ms clock)
;Takes no arguments
;Returns +1 always
;Clobbers nearly everything

	XRESCD

XRENT PUPCH7,G
	CALL PU7PSI		;Initiate any PSI requests
;;;	CALL PU7BSP		;Do any input processing
;;;	CALL PU7NVT		;Do any NVT processing
;;;	CALL PU7GAT		;Do any gateway processing
	RET			;Return to caller

	XSWAPCD
;PU7PSI - initiate PSI interrupts for queued input
;Called at scheduler level from PUPCH7
;Returns +1 always
;Clobbers T1-T4,E,UNIT

	XRESCD

PU7PSI:	SETZ T1,		;This will be new value for P7INTC
	EXCH T1,P7INTC		;Clear P7INTC, get old value
	JUMPE T1,R		;Quit if nothing to look for
	MOVSI E,-NPUPSK		;Set up bit table word counter 
PU7PS0:	SKIPE T4,PUPSKD(E)	;Get a word to look at
	 JFFO T4,PU7PS1		;Jump if we find a bit set
	  JRST PU7PS2		;No bits set, try next word
PU7PS1:	MOVE T1,BITS(UNIT)	;We have the port number (UNIT = T4+1).
	ANDCAM T1,PUPSKD(E)	;Clear the flag
	MOVEI T1,^D36		;One word worth of offset
	IMULI T1,(E)		;Times number of words
	ADDI UNIT,(T1)		;Add offset onto jffo bit to get port number
	CAILE UNIT,NPUPUN	;Valid port number?
	 JRST PU7PS0		;No, ignore it
	HRRE T2,PUPPSI(UNIT)	;Get fork to interrupt
	JUMPL T2,PU7PS0		;None, go look for more bits
	LOAD T1,RECPSI		;Get PSI channel to interrupt on
	CAIGE T1,^D36		;Armed?
	 CALL PSIRQ		;Yes, initiate an interrupt
	JRST PU7PS0		;Go look at the word again

PU7PS2:	AOBJN E,PU7PS0		;Advance to next word in the bit table
	RET			;All done, return to caller

	XSWAPCD
	TNXEND
	END