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