Trailing-Edge
-
PDP-10 Archives
-
BB-M081Q-SM
-
monitor-sources/niusr.mac
There are 19 other files named niusr.mac in the archive.  Click here to see a list.
; UPD ID= 8568, RIP:<7.MONITOR>NIUSR.MAC.3,  11-Feb-88 11:43:23 by GSCOTT
;TCO 7.1218 - Update copyright date.
; *** Edit 7454 to NIUSR.MAC by RASPUZZI on 16-Apr-87, for SPR #21586
; Prevent GLFNF BUGHLTs, SUMNR1 BUGCHKs and UNBFNF BUGCHKs.
; Make .NION and .NIOFF global.
; *** Edit 7434 to NIUSR.MAC by PRATT on 2-Apr-87, for SPR #21423
; Change several uses of the extended code PSECT macros to be correct. In
; particular, fix OPOPACs and ILLUUOs occurring out of NIURC1. 
; *** Edit 7285 to NIUSR.MAC by MCCOLLUM on 14-Apr-86
; Return global job number in INTOEX 
; Edit 7173 to NIUSR.MAC by PALMIERI on 23-Oct-85 (TCO 6.1.1542)
; Move modules NIUSR and LLMOP to an extended section. This required the
; changing of some global routine names in LLMOP; Therefor the changes to
; MEXEC, JSYSA, and FORK. 
; UPD ID= 2137, SNARK:<6.1.MONITOR>NIUSR.MAC.28,   5-Jun-85 10:07:19 by MCCOLLUM
;TCO 6.1.1406  - Update copyright notice.
; UPD ID= 2031, SNARK:<6.1.MONITOR>NIUSR.MAC.27,  28-May-85 17:35:36 by GROSSMAN
;TCO 6.1.1415 - Fix up multicast returns in .EIRPI, and fix up handling of
;global portal IDs.
; UPD ID= 1903, SNARK:<6.1.MONITOR>NIUSR.MAC.26,   4-May-85 20:13:00 by MCCOLLUM
;TCO 6.1.1238 - Fix more BUG. documentation
; UPD ID= 1821, SNARK:<6.1.MONITOR>NIUSR.MAC.25,  24-Apr-85 19:57:38 by MCCOLLUM
;TCO 6.1.1238 - Fix BUG. documentation
; UPD ID= 1805, SNARK:<6.1.MONITOR>NIUSR.MAC.24,  24-Apr-85 13:06:06 by GROSSMAN
; TCO 6.1.1341 - Add ERJMPs after all PXCTs that occur in NOINT code.
; UPD ID= 1802, SNARK:<6.1.MONITOR>NIUSR.MAC.23,  23-Apr-85 17:41:39 by GROSSMAN
; TCO 6.1.1337 - Move SETNIA from NISRV to here.
; UPD ID= 1800, SNARK:<6.1.MONITOR>NIUSR.MAC.22,  23-Apr-85 17:19:24 by GROSSMAN
; TCO 6.1.1336 - Return Physical and Hardware addresses for .EIRCI function.
; UPD ID= 1657, SNARK:<6.1.MONITOR>NIUSR.MAC.21,  20-Mar-85 09:41:10 by GROSSMAN
; TCO 6.1.1278 - Call GL2LCL in FNDGPR.
; UPD ID= 1652, SNARK:<6.1.MONITOR>NIUSR.MAC.20,  18-Mar-85 16:54:26 by GROSSMAN
; Implement Read Channel List and Read Portal List
; UPD ID= 1535, SNARK:<6.1.MONITOR>NIUSR.MAC.19,  19-Feb-85 13:52:15 by GROSSMAN
; TCO 6.1.1210 - Implement Read Portal Counters.
; UPD ID= 1528, SNARK:<6.1.MONITOR>NIUSR.MAC.18,  18-Feb-85 22:52:28 by GROSSMAN
; TCO 6.1.1203 - Fix code reading bug in blocking xmits.
; UPD ID= 1527, SNARK:<6.1.MONITOR>NIUSR.MAC.17,  18-Feb-85 22:19:19 by GROSSMAN
; TCO 6.1.1202 - Implement Read Channel Counters.
; UPD ID= 1414, SNARK:<6.1.MONITOR>NIUSR.MAC.16,  28-Jan-85 14:42:30 by GROSSMAN
; More TCO 6.1.1157 - Make NIJJIF global.
; UPD ID= 1411, SNARK:<6.1.MONITOR>NIUSR.MAC.15,  28-Jan-85 10:25:54 by GROSSMAN
; TCO 6.1.1157 - Make NIJJIF use NIJFLG instead of NIJJSR.
; UPD ID= 1409, SNARK:<6.1.MONITOR>NIUSR.MAC.14,  28-Jan-85 10:13:11 by GROSSMAN
; Make GXRCOR, RXRCOR, RELCSH, DQBRB and RQBRB swappable.
; UPD ID= 1399, SNARK:<6.1.MONITOR>NIUSR.MAC.13,  23-Jan-85 17:12:27 by GLINDELL
;  Fix PSI handling in NIJJIF
;   1) calculate channel number correctly
;   2) load fork number before calling PSIRQB
; UPD ID= 1370, SNARK:<6.1.MONITOR>NIUSR.MAC.12,  21-Jan-85 11:54:13 by GLINDELL
;  1) Made the following routines resident:
;	.NION, CLOCBK, GXRCOR, RXRCOR, RELCSH, DQBRB and friends
;  2) Changed JSR BUGHLT to BUG. at NIUCL1
; UPD ID= 1365, SNARK:<6.1.MONITOR>NIUSR.MAC.11,  19-Jan-85 16:19:11 by PAETZOLD
;TCO 6.1.1146 - Make .NIOFF resident also.  NIJJIF calls it.
; UPD ID= 1359, SNARK:<6.1.MONITOR>NIUSR.MAC.10,  17-Jan-85 16:35:53 by GROSSMAN
; More TCO 6.1.1140 - Make NIJJIF resident.
; UPD ID= 1354, SNARK:<6.1.MONITOR>NIUSR.MAC.9,  16-Jan-85 17:11:27 by GROSSMAN
; TCO 6.1.1141 - Add transmit and receive memory cache (GXRCOR).
; TCO 6.1.1140 - Add (ugh, bletch!) NIJJIF.
; TCO 6.1.1138 - Add .EIGET and .EIREL.
; UPD ID= 1189, SNARK:<6.1.MONITOR>NIUSR.MAC.8,  11-Dec-84 17:29:19 by GROSSMAN
; More TCO 6.1.1082 - Don't add DLLERB to successful returns.
; UPD ID= 1188, SNARK:<6.1.MONITOR>NIUSR.MAC.7,  11-Dec-84 15:20:27 by GROSSMAN
; TCO 6.1.1082 -
; Return status in BXSTA upon completion of transmits and receives.  Allow
; blocking mode transmits and receives to be interrupted.  Ensure that receive
; buffers are on a writeable page by writing a byte into the buffer before
; using it.
; UPD ID= 1136, SNARK:<6.1.MONITOR>NIUSR.MAC.6,  30-Nov-84 17:26:03 by GROSSMAN
; Lots of development.  Add ENABLE/DISABLE multicast.  Do quota checking on
; xmit and rcv buffers.  Implement blocking mode xmits and rcvs.
; UPD ID= 1081, SNARK:<6.1.MONITOR>NIUSR.MAC.5,  15-Nov-84 14:28:10 by GROSSMAN
; TCO 6.1.1054 - Change symbols to avoid conflict with other monitor modules.
; TCO 6.1.1045 - Create this module.
;	COPYRIGHT (c) DIGITAL EQUIPMENT CORPORATION 1976, 1988.
;	ALL RIGHTS RESERVED.
;
;	THIS SOFTWARE IS FURNISHED UNDER A  LICENSE AND MAY BE USED AND  COPIED
;	ONLY IN  ACCORDANCE  WITH  THE  TERMS OF  SUCH  LICENSE  AND  WITH  THE
;	INCLUSION OF THE ABOVE  COPYRIGHT NOTICE.  THIS  SOFTWARE OR ANY  OTHER
;	COPIES THEREOF MAY NOT BE PROVIDED  OR OTHERWISE MADE AVAILABLE TO  ANY
;	OTHER PERSON.  NO  TITLE TO  AND OWNERSHIP  OF THE  SOFTWARE IS  HEREBY
;	TRANSFERRED.
;
;	THE INFORMATION IN THIS  SOFTWARE IS SUBJECT  TO CHANGE WITHOUT  NOTICE
;	AND SHOULD  NOT  BE CONSTRUED  AS  A COMMITMENT  BY  DIGITAL  EQUIPMENT
;	CORPORATION.
;
;	DIGITAL ASSUMES NO  RESPONSIBILITY FOR  THE USE OR  RELIABILITY OF  ITS
;	SOFTWARE ON EQUIPMENT THAT IS NOT SUPPLIED BY DIGITAL.
;Ethernet user mode interface (NI% JSYS). - Stu Grossman
	SEARCH PROLOG,NIPAR,MONSYM
	TTITLE NIUSR
	XSWAPCD			; [7173] Make this be swappable
	Subttl	Table of Contents
;		     Table of Contents for NIUSR
;
;				  Section		      Page
;
;
;    1. Definitions  . . . . . . . . . . . . . . . . . . . . .   3
;    2. Storage  . . . . . . . . . . . . . . . . . . . . . . .   4
;    3. NI% JSYS . . . . . . . . . . . . . . . . . . . . . . .   5
;    4. .EIOPN - Open a Portal . . . . . . . . . . . . . . . .   6
;    5. .EICLO - Close a portal  . . . . . . . . . . . . . . .   7
;    6. CLOCBK - Callback from NISRV for close complete  . . .   8
;    7. .EIXMT - Transmit Datagrams  . . . . . . . . . . . . .   9
;    8. NIURTQ - Read Transmit Queue . . . . . . . . . . . . .  11
;    9. XMTCBK - Transmit completion callback from NISRV . . .  12
;   10. NIJJIF - NI% JSYS Jiffy Service  . . . . . . . . . . .  13
;   11. .EIRCV - Post Receive buffers  . . . . . . . . . . . .  14
;   12. .EIRRQ - Read Receive Queue  . . . . . . . . . . . . .  16
;   13. RCVCBK - Receive completion callback from NISRV  . . .  17
;   14. .EIEMA & .EIDMA - Enable/Disable a multicast address .  18
;   15. LOKBUF - Lock down user buffers  . . . . . . . . . . .  19
;   16. ULKBUF - Unlock buffers  . . . . . . . . . . . . . . .  20
;   17. DOINT - Process pending PSI's  . . . . . . . . . . . .  21
;   18. USRCBK - Callback routine from NISRV . . . . . . . . .  22
;   19. GETBRB & RETBRB - Get and Return Buffer Request blocks  23
;   20. QUEBRB - Queue up BRB's  . . . . . . . . . . . . . . .  24
;   21. SETTRB - Set EITBA and EIRBA as appropriate  . . . . .  25
;   22. CREPOR - Create a Portal Block . . . . . . . . . . . .  26
;   23. KILPOR - Release all resources associated with a porta  27
;   24. NIJKFK - Reset for the NI% JSYS  . . . . . . . . . . .  28
;   25. ***** Hacks *****  . . . . . . . . . . . . . . . . . .  29
;   26. .EIRCI - Read Channel Information  . . . . . . . . . .  30
;   27. .EISCA - Set Channel Address . . . . . . . . . . . . .  31
;   28. .EISCS - Set Channel State . . . . . . . . . . . . . .  32
;   29. .EIRCC & .EIRPC - Read Channel & Portal Counters . . .  33
;   30. .EIGET and .EIREL - Get and release a KLNI . . . . . .  34
;   31. .EIRCL - Read Channel List . . . . . . . . . . . . . .  35
;   32. .EIRPL - Read Portal List  . . . . . . . . . . . . . .  36
;   33. INTOEX - Convert internal portal ID's to external port  37
;   34. .EIRPI - Read Portal Info  . . . . . . . . . . . . . .  38
;   35. CHKOWN - Check ownership of KLNI . . . . . . . . . . .  39
;   36. FNDPOR - Find the portal database associated with a po  40
;   37. FNDGPR - Find NISRV portal IDs . . . . . . . . . . . .  41
;   38. SMON% JSYS - Set NI address  . . . . . . . . . . . . .  42
;   39. End of NIUSR . . . . . . . . . . . . . . . . . . . . .  43
	SUBTTL Definitions
	EXTERN UPLIST
	DEFAC UN,Q1		; Define UN block pointer
	DEFAC PR,Q2		; Define portal block pointer
	MAXPOR==^D16		; Maximum number of portals
; The portal list structure
	BEGSTR PL
	 HWORD MAX		; Highest portal defined so far
	 HWORD NUM		; Total number of portals defined so far
	 WORD LIS,MAXPOR	; The actual portal list
	ENDSTR
; Queue headers (used for xmit and rcv queues)
	BEGSTR XR
	  WORD QUH		; Pointer to first item
	  WORD QUE		; Pointer to last item
	ENDSTR
; Portal block
	BEGSTR PR
	  WORD NXT		; Pointer to next portal
	  FIELD UPD,6		; User's portal ID
	  FIELD FLG,6		; Flags for this portal
	   BIT CCP		;  Close complete
	   BIT RPS		;  Receive PSI requested
	   BIT TPS		;  Transmit PSI requested
	   BIT SPS		;  Status change PSI requested
	  FIELD LFK,6		; Job wide fork #
	  FIELD FRK,18		; Fork number of owning fork
	  WORD CHK		; Check word
	  FIELD TCH,12		; Transmit completion interrupt channel
	  FIELD RCH,12		; Receive completion interrupt channel
	  FIELD SCH,12		; Status change interrupt channel
	  WORD UNB		; UN block pointer
	  WORD PID		; Monitor's portal ID
	  WORD XQH,XR.LEN	; Transmitted queue header
	  WORD RQH,XR.LEN	; Receive queue header
	  WORD TRQ		; Transmit quota
	  WORD RCQ		; Receive quota
	  WORD TIP		; # of transmit buffers queued up to NISRV
	  WORD RIP		; # of receive buffers queued up to NISRV
	ENDSTR
; Internal buffer descriptor block
	BEGSTR BR
	  WORD NXT		; Pointer to next BR block
	  HWORD BSZ		; Buffer size
	  HWORD PRO		; Protocol type
	  HWORD CP1		; Locked core page 1
	  HWORD CP2		; Locked core page 2
	  WORD BFA,2		; Buffer address (Byte pointer)
	  WORD BID		; Buffer ID
	  WORD STA		; Return status
	  WORD DAD,2		; Destination Ethernet address
	  WORD SAD,2		; Source Ethernet address
	ENDSTR
	BEGSTR PI		; Structure for global portal IDs
	  FILLER 18		;   Use only low order bits
	  FIELD FRK,9		;   Job wide fork number
	  FIELD PID,9		;   Job wide portal ID
	ENDSTR
	FC.POR==1B1		; Function requires a portal
	DLLERB==NIENSP-UNNSP%	; Conversion factor for NISRV to normal
				;  error codes
	SUBTTL Storage
RSI (UPRBAS,<0>)		; Portal list base address
RSI (KNIOWN,<-1>)		; Contains fork # of owning fork
RSI (NIJFLG,<0>)		; Non-0 means scheduler should call NIJJIF
	SUBTTL NI% JSYS
	XNENT (.NI,G)		; [7434] .NI::, and X.NI::
	MCENT			; Enter JSYS context
	SAVEAC <P1,UN,PR>
; Here after taking a PSI
NI0:	MOVX T1,SC%WHL!SC%OPR	; Wheel or operator
	TDNN T1,CAPENB		;  capabilities enabled?
	 ITERR (CAPX1)		; Nope, die horribly
	UMOVE P1,T1		; Get pointer to user's argument block
	ULOAD T3,EIFCN,(P1)	; Get the function code
	SKIPLE T3		; Jump if illegal function code
	CAXLE T3,.EIMAX		; Is the function in range?
	 ITERR (ARGX02)		;  Nope, complain
	MOVE T3,FUNTAB-1(T3)	; Get the table entry
	TXNN T3,FC.POR		; Does he need a Portal ID?
	 JRST (T3)		;  Nope, just dispatch
	NOINT			; No interrupts please
	CALL FNDPOR		; Look for the portal block
	 ITERR			;  Pass the error upwards
	MOVE PR,T1		; Setup the official pointer
	LOAD UN,PRUNB,(PR)	; Get our UN block pointer
	JRST (T3)		; Call the processor
; Dispatch and bits for function codes
	DEFINE XX (code,flags<0>)<TABENT .EI'code,<IFIW!flags!<NIU'code&777777>>> ;[7173]
FUNTAB:	TABBEG 1,.EIMAX,<FOO>
	  XX OPN		; Open a Portal
	  XX CLO,FC.POR		; Close a Portal
	  XX RCV,FC.POR		; Post a Receive Buffer
	  XX RRQ,FC.POR		; Read Receive Queue
	  XX XMT,FC.POR		; Transmit a Datagram
	  XX RTQ,FC.POR		; Read Transmit Queue
	  XX EMA,FC.POR		; Enable a Multicast
	  XX DMA,FC.POR		; Disable a Multicast
	  XX RPL		; Read Portal List
	  XX RCL		; Read Channel List
	  XX RPC		; Read Portal Counters
	  XX RCC		; Read Channel Counters
	  XX RPI		; Read Portal Information
	  XX RCI		; Read Channel Information
	  XX SCS		; Set Channel State
	  XX SCA		; Set Channel Address
	  XX GET		; Obtain ownership of the channel
	  XX REL		; Release ownership of the channel
	TABEND
	SUBTTL	.EIOPN - Open a Portal
NIUOPN:	NOINT			; No PSI's please
	CALL CREPOR		; Create a portal, setup PR and UN
	 ITERR			;  Couldn't get a portal block
; Note well! We are now NOINT.
	ULOAD T1,EICHN,(P1)	; Get the channel number
	 ERJMP OPNER1		;  Pass the error upwards
	STOR T1,UNCHN,(UN)	; Save channel number in the UN block
	ULOAD T1,EIPAD,(P1)	; Get pad flag
	 ERJMP OPNER1		;  Pass the error upwards
	STOR T1,UNPAD,(UN)	; Save it in the UN block
	ULOAD T1,EIPRO,(P1)	; Get the protocol type
	 ERJMP OPNER1		;  Pass the error upwards
	STOR T1,UNPRO,(UN)	; Save the protocol type in the UN block
	ULOAD T1,EITCH,(P1)	; Get transmit completion PSI channel
	 ERJMP OPNER1		;  Pass the error upwards
	STOR T1,PRTCH,(PR)	; Save it in the portal block
	ULOAD T1,EIRCH,(P1)	; Get receive completion PSI channel
	 ERJMP OPNER1		;  Pass the error upwards
	STOR T1,PRRCH,(PR)	; Save it in the portal block
	ULOAD T1,EISCH,(P1)	; Get status change PSI channel
	 ERJMP OPNER1		;  Pass the error upwards
	STOR T1,PRSCH,(PR)	; Save it in the portal block
	LOAD T1,PRUPD,(PR)	; Get the user's portal ID
	USTOR T1,EIPID,(P1)	; Return it to the user
	 ERJMP OPNER1		;  Pass the error upwards
	MOVE T1,FORKX		; Get our system wide fork index
	STOR T1,PRFRK,(PR)	; This fork now owns this portal
	MOVE T1,FORKN		; Get our job wide fork #
	STOR T1,PRLFK,(PR)	; Setup for information gatherers
	OPSTR <XMOVEI T1,>,PRXQH,+XR.QUH(PR) ; Get ptr to xmit q header
	STOR T1,PRXQH,+XR.QUE(PR) ; Make xmt q end point to xmit q hdr
	OPSTR <XMOVEI T1,>,PRRQH,+XR.QUH(PR) ; Get ptr to rcv q header
	STOR T1,PRRQH,+XR.QUE(PR) ; Make rcv q end point to rcv q hdr
	MOVX T1,8		; Transmit and receive quota
	STOR T1,PRTRQ,(PR)	; Setup transmit quota
	STOR T1,PRRCQ,(PR)	; Setup receive quota
	STOR PR,PRCHK,(PR)	; Setup the check word
	STOR PR,UNUID,(UN)	; Install the callback ID for this portal
	XMOVEI T1,USRCBK	; Get the address of the callback routine
	STOR T1,UNCBA,(UN)	; Save it in the UN block
	MOVX T1,NU.OPN		; Function is Open a Portal
	MOVE T2,UN		; Get UN block pointer
	CALL DLLUNI		; Open the portal
	 JRST OPNERR		;  Go process the error
	LOAD T1,UNPID,(UN)	; Get the Portal ID that NISRV gave us
	STOR T1,PRPID,(PR)	; Save it for later reference
	MOVX T1,UNA.UV		; Indicate that all buffers are user mode
	STOR T1,UNADS,(UN)	; Save it in the UN block
	OKINT			; Allow PSI's
	MRETNG			; All done, return success
; Here when LSTERR contains last error
OPNER1:	MOVE T1,LSTERR		; Retreive last error
	SUBI T1,DLLERB		; Correct for later addition
; Here with error code in T1
OPNERR:	SAVEAC P2
	MOVE P2,T1		; Save the error code
	CALL KILPOR		; Kill the portal block
	OKINT			; Allow PSI's
	MOVE T1,P2		; Retreive the error code
	ADDI T1,DLLERB		; Convert it to a real error code
	ITERR			; And return the error to the user
	SUBTTL	.EICLO - Close a portal
NIUCLO:	CALL NIUCLI		; Here from JSYS context
	OKINT			; Ok to interrupt now
	MRETNG
; Here for internal calls to the close function
NIUCLI:	MOVX T1,NU.CLO		; Get function code
	MOVE T2,UN		; Get UN block pointer
	CALL DLLUNI		; Tell NISRV to close the portal
	 JRST NIUCL1		;  Probably a resource error, try again
	
NIUCL2:	MOVE T1,FORKX		; Get our fork #
	STOR PR,FKST2,(T1)	; Setup portal pointer for scheduler test
	MOVX T1,CLOTST		; Get routine address
	MDISMS			; Wait until close has been done
	OPSTR <XMOVEI T1,>,PRXQH,(PR) ; Get xmit queue header
	CALL FREBRL		; Release the entire transmit queue
	OPSTR <XMOVEI T1,>,PRRQH,(PR) ; Get rcv queue header
	CALL FREBRL		; Release the entire receive queue
	CALL RELCSH		; Flush the memory cache
	 TRN			;  Who cares..
	CALLRET KILPOR		; Murder the PR block and such
; Scheduler test to see if anything has closed
	RESCD
	FX=:7
CLOTST:	LOAD T1,FKST2,(FX)	; Get portal block pointer
	JE PRCCP,(T1),RTN	; Non-skip if not closed
	RETSKP			; Give skip return, portal is closed
	XSWAPCD			; [7173]
; Here on failure from NISRV close function
NIUCL1:	CAIE T1,UNRES%		; Resource error?
	 BUG. (HLT,NIJECL,NIUSR,SOFT,<Error closing portal>,<<T1,ERROR>>,<
Cause:	NISRV returned an error when we tried to close a portal.  The error
	code was not UNRES% (Resource error), which is the only one that may
	occur.
Data:	ERROR - The returned error code
>)
	CALL RELCSH		; Try to shake a little loose
	 TRNA			;  No memory in cache, sleep for a while
	  JRST NIUCLI		;   Try the close again
	MOVX T1,^D500		; Try again in 1/2 a second
	DISMS%			; Wait for things to calm down
	JRST NIUCLI		; Try again
; Here to free all BR blocks on a xmit or rcv queue
FREBRL:	SAVEAC <P1,P3>
	MOVE P1,T1		; Put q header into a safe place
FREBR1:	MOVE T1,P1		; Get q header
	CALL DQBRB		; Get the next block
	 JRST RTN		;  All done, return
	MOVE T1,P3		; Get block address
	CALL RXRCOR		; Release the BR block
	JRST FREBR1		; Loop till done
	SUBTTL	CLOCBK - Callback from NISRV for close complete
	XRESCD			; [7173]
CLOCBK:	JUMPE PR,RTN		; Jump if just temp user
	SETONE PRCCP,(PR)	; Indicate that portal is now closed
	RET
	XSWAPCD			; [7173]
	SUBTTL	.EIXMT - Transmit Datagrams
NIUXMT:	SAVEAC <P2,P3>
	SETZ P3,		; Clear BR pointer in case of error
	ULOAD P2,EIBCP,(P1)	; Get pointer to buffer descriptor chain
	 ERJMP [ITERX]		;  Pass error upwards
XMTLOP:	JUMPE P2,XMTDON		; End of chain, we're all done
	CALL GETBRT		; Get a buffer descriptor block
	 ITERR			;  Pass error upwards
	MOVE P3,T1		; Put BR block pointer in a safe place
	ULOAD T1,BXBSZ,(P2)	; Get length of datagram
	 ERJMP XMTER1
	STOR T1,UNBSZ,(UN)	; Put it in the UN block
	STOR T1,BRBSZ,(P3)	; Put it in the BR block
	XCTU [OPSTR <DMOVE T1,>,BXBFA,(P2)] ; Get byte pointer
	 ERJMP XMTER1
	OPSTR <DMOVEM T1,>,UNBFA,(UN) ; Put it in the UN block
	OPSTR <DMOVEM T1,>,BRBFA,(P3) ; Put it in the BR block
	XCTU [OPSTR <DMOVE T1,>,BXDAD,(P2)] ; Get destination address
	 ERJMP XMTER1
	OPSTR <DMOVEM T1,>,UNDAD,(UN) ; Put it in the UN block
	OPSTR <DMOVEM T1,>,BRDAD,(P3) ; Put it in the BR block
	ULOAD T1,BXBID,(P2)	; Get buffer ID
	 ERJMP XMTER1
	STOR T1,BRBID,(P3)	; Save it for later
	STOR P3,UNRID,(UN)	; Make request ID word point to BR block
	CALL LOKBUF		; Lock up the buffer
	MOVX T1,NU.XMT		; Tell NISRV to transmit
	MOVE T2,UN		; Get address of UN block
	CALL DLLUNI		; Transmit the buffer
	 JRST XMTERR		;  Deal with the error
	OPSTR AOS,PRTIP,(PR)	; Increment transmits in progress
	ULOAD P2,BXNXT,(P2)	; Get pointer to next BXB
	 ERJMP [ITERX]		;  Pass error upwards
	JRST XMTLOP		; And process next buffer
; Here when we reach the end of the BXB chain
XMTDON:	ULOAD T1,EIBLK,(P1)	; Get the block bit
	 ERJMP [ITERX]		;  Pass the error upwards
	JUMPN T1,XMTBLK		; Jump if he wants to wait
	OKINT			; Allow interrupts
	MRETNG			; All done!
; Here when an error occurs and BR block needs to be released, but buffer
; is not yet locked.
XMTER1:	SAVEAC P1
	MOVE T1,LSTERR		; Get last error code
	SUBI T1,DLLERB		; Correct for later addition
	MOVE P1,T1		; Get error code into correct AC
	JRST XMTER2		; Release BR block, but don't unlock buffer
; Here when we get an error from NISRV
XMTERR:	SAVEAC P1
	MOVE P1,T1		; Save the NISRV error code
	CALL ULKBUF		; Unlock the buffer
XMTER2:	MOVE T1,P3		; Get pointer to BR block
	CALL RELBRT		; Release the BR block
	USTOR P2,EIBCP,(P1)	; Point at the offending buffer
	 ERJMP [ITERX]		;  I really hate errors within errors...
	OKINT			; We can allow interrupts now
	ADDI P1,DLLERB		; Convert NISRV error to a real error code
	MOVE T1,P1		; Get the error code into the right place
	ITERR			; Pass the error on to the user
; Here if user wants JSYS to block until all buffers are transmitted
XMTBLK:	MOVE T1,FORKX		; Get our fork number
	STOR PR,FKST2,(T1)	; Setup our portal address for sched test
	MOVEI T1,XMTTST		; Get address of scheduler test
	MDISMS			; Dismiss till buffers come back
	MOVE T1,FORKX		; Get our fork #
	MOVE T1,FKINT(T1)	; Get the interrupt bits
	TXNE T1,FKPS1		; Interrupt pending?
	 CALL INTXMT		;  Yes, go process it
	JN PRTIP,(PR),XMTBLK	; Wait some more if buffers didn't get xmitted
	JRST NIURTQ		; All done, return the buffers
	RESCD			; Scheduler tests must be resident
XMTTST:	JN FKPS1,(FX),RSKP	; Wakeup if a PSI is pending
	LOAD T1,FKST2,(FX)	; Get pointer to portal block
	JE PRTIP,(T1),RSKP	; Wake up job if all buffers got xmitted
	RET			; Sleep some more
	XSWAPCD			; [7173]
; Here when fork woke up because of a pending PSI
INTXMT:	OKINT			; Allow the PSI to happen
	NOINT			; Clam up again
;	CALL CHKARG		; Check the arguments again
	RET
	SUBTTL	NIURTQ - Read Transmit Queue
NIURTQ:	SAVEAC <P2,P3>
	ULOAD P2,EIBCP,(P1)	; Get pointer to buffer descriptor chain
	 ERJMP [ITERX]		;  Pass the error upwards
RTQLOP:	OPSTR <XMOVEI T1,>,PRXQH,(PR) ; Get address of transmit q header
	CALL DQBRB		; Dequeue the next buffer request block
	 JRST RTQDON		;  No more buffers, quit now
	JUMPE P2,RTQEOL		; Not enough BXBs, quit now
	
	LOAD T1,BRBSZ,(P3)	; Get datagram size
	USTOR T1,BXBSZ,(P2)	; Put it into the user's block
	 ERJMP RTQERR
	OPSTR <DMOVE T1,>,BRBFA,(P3) ; Get the byte pointer
	XCTU [OPSTR <DMOVEM T1,>,BXBFA,(P2)] ; Put BP into user's block
	 ERJMP RTQERR
	OPSTR <DMOVE T1,>,BRDAD,(P3) ; Get Destination address
	XCTU [OPSTR <DMOVEM T1,>,BXDAD,(P2)] ; Put dest addr into user's block
	 ERJMP RTQERR
	LOAD T1,BRBID,(P3)	; Get user's buffer ID
	USTOR T1,BXBID,(P2)	; Put buffer ID into user's block
	 ERJMP RTQERR
	LOAD T1,BRSTA,(P3)	; Get the return status
	SKIPE T1		; Don't add error offset if successful
	 ADDI T1,DLLERB		;  Convert this to a user error code
	USTOR T1,BXSTA,(P2)	; Put it where the user can find it
	 ERJMP RTQERR
	MOVX T1,1		; Get a bit
	USTOR T1,BXVAL,(P2)	; Set the valid bit in user's buffer descriptor
	 ERJMP RTQERR
	MOVE T1,P3		; Get pointer to BR block
	CALL RELBRT		; Release the BR block
	TSTINT			; Any interrupts pending?
	 JRST DOINT		;  Yes, go process them
	ULOAD P2,BXNXT,(P2)	; Get pointer to next buffer descriptor
	 ERJMP [ITERX]
	JRST RTQLOP		; No, process next buffer
; Here on an error when the BR block needs to be requeued
RTQERR:	OPSTR <XMOVEI T1,>,PRXQH,(PR) ; Get address of transmit q header
	CALL RQBRB		; Requeue the BRB (onto head of xmit queue)
	CALL SETTRB		; Set the TBA and RBA bits as appropriate
	ITERX			; Pass error upwards
; Here when the transmit queue is empty
RTQDON:	CALL SETTRB		; Go set the TBA and RBA bits
	OKINT			; Re-enable interrupts
	SETZ T1,		; Get a 0
	SKIPE P2		; Don't clear bit if no block
	 USTOR T1,BXVAL,(P2)	;  Clear the valid bit in the last block
	MRETNG			; And return success
; Here when user didn't supply enough buffer descriptors
RTQEOL:	OPSTR <XMOVEI T1,>,PRXQH,(PR) ; Get address of transmit q header
	CALL RQBRB		; Requeue the BRB (onto head of xmit queue)
	CALL SETTRB		; Go set the TBA and RBA bits
	OKINT
	MRETNG
	SUBTTL	XMTCBK - Transmit completion callback from NISRV
	XRESCD			; [7173]
XMTCBK:	SAVEAC P3
	LOAD P3,UNRID,(UN)	; Setup BR block pointer
	STOR T3,BRSTA,(P3)	; Store the return status
	OPSTR <XMOVEI T1,>,PRXQH,(PR) ; Get transmit queue header
	CALL QUEBRB		; Put this one on the transmit queue
	 CALL XMTPSI		;  Queue went non-empty, give user a PSI
	OPSTR SOS,PRTIP,(PR)	; Decrement transmits in progress
	CALLRET ULKBUF		; Unlock the buffer
RCVPSI:	LOAD T1,PRRCH,(PR)	; Get the receive completion PSI channel
	CAIL T1,^D36		; Channel number in range?
	 RET			;  No, don't give a PSI
	SETONE PRRPS,(PR)	; Request an interrupt from jiffy level
	SETOM NIJFLG		; Request jiffy service
	RET
XMTPSI:	LOAD T1,PRTCH,(PR)	; Get the transmit completion PSI channel
	CAIL T1,^D36		; Channel number in range?
	 RET			;  No, don't give a PSI
	SETONE PRTPS,(PR)	; Request an interrupt from jiffy level
	SETOM NIJFLG		; Request jiffy service
	RET
	SUBTTL	NIJJIF - NI% JSYS Jiffy Service
	XRENT (NIJJIF,G)	; [7434] NIJJIF:: and XNIJJI:: Run in sched
	SAVEAC PR
	SETZM NIJFLG		; Clear flag that got us here
	CALL .NIOFF		; No interrupts please
	XMOVEI PR,UPRBAS	; Get the head of the chain
NIJJI1:	LOAD PR,PRNXT,(PR)	; Get the next portal
	JUMPE PR,.NION		; Return if all done
	SETZ T1,		; Clear the bit mask
	JE PRRPS,(PR),NIJJI2	; Jump if no receive PSI requested
	SETZRO PRRPS,(PR)	; Clear the request
	LOAD T2,PRRCH,(PR)	; Get the receive PSI channel
	OR T1,BITS(T2)		; And or in the corresponding bit
NIJJI2:	JE PRTPS,(PR),NIJJI3	; Jump if no transmit PSI requested
	SETZRO PRTPS,(PR)	; Clear the request
	LOAD T2,PRTCH,(PR)	; Get the transmit PSI channel
	OR T1,BITS(T2)		; And or in the corresponding bit
NIJJI3:	JE PRSPS,(PR),NIJJI4	; Jump if no status change PSI requested
	SETZRO PRSPS,(PR)	; Clear the request
	LOAD T2,PRSCH,(PR)	; Get the status change PSI channel
	OR T1,BITS(T2)		; And or in the corresponding bit
NIJJI4:	JUMPE T1,NIJJI1		; Jump if no PSI's to be issued
	LOAD T2,PRFRK,(PR)	; Get fork number of fork to wake up
	XCALL (MSEC1,PSIRQB)	; [7173] Request some interrupts
	JRST NIJJI1		; And loop back for more
	XSWAPCD			; [7173] Make us swappable again
	SUBTTL	.EIRCV - Post Receive buffers
NIURCV:	SAVEAC <P2,P3>
	SETZ P3,		; Clear BR pointer in case of error
	ULOAD P2,EIBCP,(P1)	; Get pointer to buffer descriptor chain
	 ERJMP [ITERX]
RCVLOP:	JUMPE P2,RCVDON		; Nope, we're all done
	CALL GETBRR		; Get a buffer descriptor block
	 ITERR			;  Pass error upwards
	MOVE P3,T1		; Put BR block pointer in a safe place
	XCTU [OPSTR <DMOVE T1,>,BXBFA,(P2)] ; Get byte pointer
	 ERJMPR RCVER1
	OPSTR <DMOVEM T1,>,UNBFA,(UN) ; Put it in the UN block
	OPSTR <DMOVEM T1,>,BRBFA,(P3) ; Put it in the BR block
; Note well!  In order to prevent "spurious" user ?Ill mem refs, the pages
; that the buffer occupies must be private and writeable.  We ensure this
; by writing something in the first and last byte of the buffer prior to
; locking down the pages that the buffer occupies.
	XCTBU [IDPB T1,T1]	; Write the first byte
	 ERJMPR RCVER1
	ULOAD T1,BXBSZ,(P2)	; Get length of the buffer
	 ERJMPR RCVER1
	STOR T1,UNBSZ,(UN)	; Put it in the UN block
	STOR T1,BRBSZ,(P3)	; Put it in the BR block
	SUBI T1,1		; Back off by a byte
	OPSTR <ADJBP T1,>,BRBFA,(P3) ; Point to the last legal byte
	XCTBU [IDPB T1,T1]	; Write into the last byte
	 ERJMPR RCVER1
	ULOAD T1,BXBID,(P2)	; Get buffer ID
	 ERJMPR RCVER1
	STOR T1,BRBID,(P3)	; Save it for later
	STOR P3,UNRID,(UN)	; Make request ID word point to BR block
	CALL LOKBUF		; Lock up the buffer
	MOVX T1,NU.RCV		; Tell NISRV to post this buffer
	MOVE T2,UN		; Get address of UN block
	CALL DLLUNI		; Post the buffer
	 JRST RCVERR		;  Deal with the error
	OPSTR AOS,PRRIP,(PR)	; Increment receives in progress
	TSTINT			; Any interrupts pending?
	 JRST DOINT		;  Yes, process them
	ULOAD P2,BXNXT,(P2)	; Get pointer to next BXB
	 ERJMP [ITERX]
	JRST RCVLOP		; And process next buffer
; Here when we reach the end of the BXB chain
RCVDON:	ULOAD T1,EIBLK,(P1)	; Get the block bit
	 ERJMP [ITERX]
	JUMPN T1,RCVBLK		; Jump if user wants to block
	OKINT			; Allow interrupts
	USTOR P2,EIBCP,(P1)	; Point to the last buffer processed
	MRETNG			; All done!
; Here when we get an error from NISRV
RCVERR:	ADDI T1,DLLERB		; Convert NISRV error to a real error code
; Here on other types of errors
RCVER1:	SAVEAC P1
	MOVE P1,T1		; Save the NISRV error code
	CALL ULKBUF		; Unlock the buffer
	MOVE T1,P3		; Get pointer to BR block
	CALL RELBRR		; Release the BR block
	OKINT			; We can allow interrupts now
	USTOR P2,EIBCP,(P1)	; Point at the offending buffer
	MOVE T1,P1		; Get the error code into the right place
	ITERR			; Pass the error on to the user
; Here when user wants to block until at least one buffer has received a
; datagram.
RCVBLK:	MOVE T1,FORKX		; Get our fork number
	STOR PR,FKST2,(T1)	; Setup our portal address for sched test
	MOVEI T1,RCVTST		; Get address of scheduler test
	MDISMS			; Dismiss till a buffer comes back
	MOVE T1,FORKX		; Get our fork #
	MOVE T1,FKINT(T1)	; Get the interrupt bits
	TXNE T1,FKPS1		; Interrupt pending?
	 CALL INTRCV		;  Yes, go process it
	OPSTR SKIPN,XRQUH,+$PRRQH(PR) ; Any buffers yet?
	 JRST RCVBLK		;  Wait some more if no buffers yet
	JRST NIURRQ		; All done, return the buffers
	RESCD			; Scheduler tests must be resident
RCVTST:	JN FKPS1,(FX),RSKP	; Wakeup if a PSI is pending
	LOAD T1,FKST2,(FX)	; Get the PR block address
	OPSTR SKIPN,XRQUH,+$PRRQH(T1) ; Any buffers yet?
	 RET			;  No, sleep some more
	RETSKP			; Yes, wakeup the job
	XSWAPCD			; [7173]
INTRCV:	OKINT
	NOINT
	RET
	SUBTTL	.EIRRQ - Read Receive Queue
NIURRQ:	SAVEAC <P2,P3>
	ULOAD P2,EIBCP,(P1)	; Get pointer to buffer descriptor chain
	 ERJMP [ITERX]		;  Pass the error upwards
RRQLOP:	OPSTR <XMOVEI T1,>,PRRQH,(PR) ; Get address of receive q header
	CALL DQBRB		; Dequeue the next buffer request block
	 JRST RRQDON		;  All done quit now
	JUMPE P2,RRQEOL		; All done, exit
	
	OPSTR <DMOVE T1,>,BRSAD,(P3) ; Get source Ethernet address
	XCTU [OPSTR <DMOVEM T1,>,BXSAD,(P2)] ; Put it in the user's block
	 ERJMP RRQERR
	OPSTR <DMOVE T1,>,BRDAD,(P3) ; Get destination Ethernet address
	XCTU [OPSTR <DMOVEM T1,>,BXDAD,(P2)] ; Put dest addr into user's block
	 ERJMP RRQERR
	LOAD T1,BRPRO,(P3)	; Get protocol type
	USTOR T1,BXPRO,(P2)	; Put it into the user's buffer descriptor
	 ERJMP RRQERR
	LOAD T1,BRBSZ,(P3)	; Get datagram size
	USTOR T1,BXBSZ,(P2)	; Put it into the user's block
	 ERJMP RRQERR
	LOAD T1,BRSTA,(P3)	; Get the return status
	SKIPE T1		; Don't add error offset if successful
	 ADDI T1,DLLERB		;  Convert this to a user error code
	USTOR T1,BXSTA,(P2)	; Put it where the user can find it
	 ERJMP RRQERR
	OPSTR <DMOVE T1,>,BRBFA,(P3) ; Get the byte pointer
	XCTU [OPSTR <DMOVEM T1,>,BXBFA,(P2)] ; Put BP into user's block
	 ERJMP RRQERR
	LOAD T1,BRBID,(P3)	; Get user's buffer ID
	USTOR T1,BXBID,(P2)	; Put buffer ID into user's block
	 ERJMP RRQERR
	MOVX T1,1		; Get a bit
	USTOR T1,BXVAL,(P2)	; Set the valid bit in user's buffer descriptor
	 ERJMP RRQERR
	MOVE T1,P3		; Get pointer to BR block
	CALL RELBRR		; Release the BR block
	TSTINT			; Any interrupts pending?
	 JRST DOINT		;  Yes, go process them
	ULOAD P2,BXNXT,(P2)	; Get pointer to next buffer descriptor
	 ERJMP [ITERX]
	JRST RRQLOP		; No, process next buffer
; Here when the receive queue is empty
RRQDON:	CALL SETTRB		; Go set the TBA and RBA bits
	OKINT			; Re-enable interrupts
	SETZ T1,		; Get a 0
	SKIPE P2		; Don't clear bit if no block
	 USTOR T1,BXVAL,(P2)	;  Clear the valid bit in the last block
	MRETNG			; And return success
; Here when user didn't supply enough buffer descriptors
RRQEOL:	OPSTR <XMOVEI T1,>,PRRQH,(PR) ; Get address of receive q header
	CALL RQBRB		; Requeue the BRB (onto head of rcv queue)
	CALL SETTRB		; Go set the TBA and RBA bits
	OKINT
	MRETNG
; Here when we get an error, and have to requeue the BRB
RRQERR:	OPSTR <XMOVEI T1,>,PRRQH,(PR) ; Get address of receive q header
	CALL RQBRB		; Requeue the BRB (onto head of rcv queue)
	CALL SETTRB		; Go set the TBA and RBA bits
	OKINT
	ITERX			; And pass the error upwards
	SUBTTL	RCVCBK - Receive completion callback from NISRV
	XRESCD			; [7173]
RCVCBK:	SAVEAC P3
	LOAD P3,UNRID,(UN)	; Setup BR block pointer
	STOR T3,BRSTA,(P3)	; Store the return status
	OPSTR <DMOVE T1,>,UNSAD,(UN) ; Get source Ethernet address
	OPSTR <DMOVEM T1,>,BRSAD,(P3) ; Put it in the buffer request block
	OPSTR <DMOVE T1,>,UNDAD,(UN) ; Get destination Ethernet address
	OPSTR <DMOVEM T1,>,BRDAD,(P3) ; Put it in the BR block
	LOAD T1,UNPRO,(UN)	; Get protocol type
	STOR T1,BRPRO,(P3)	; Put protocol type into the BR block
	OPSTR <DMOVE T1,>,UNBFA,(UN) ; Get BP to buffer
	OPSTR <DMOVEM T1,>,BRBFA,(P3) ; Put buffer pointer into BR block
	LOAD T1,UNBSZ,(UN)	; Get length of datagram
	STOR T1,BRBSZ,(P3)	; Put length into buffer request block
	OPSTR <XMOVEI T1,>,PRRQH,(PR) ; Get receive queue header
	CALL QUEBRB		; Put this one on the receive queue
	 CALL RCVPSI		;  Queue went non-empty, give user a PSI
	CALLRET ULKBUF		; Unlock the buffer
	XSWAPCD			; [7173]
	SUBTTL	.EIEMA & .EIDMA - Enable/Disable a multicast address
NIUEMA:	SKIPA T3,[NU.EMA]	; Get enable multicast function code
NIUDMA:	 MOVX T3,NU.DMA		; Get disable multicast function code
	XCTU [OPSTR <DMOVE T1,>,EIAR1,(P1)] ; Fetch address to be enabled
	 ERJMP [ITERX]		; Pass the error upwards
	OPSTR <DMOVEM T1,>,UNDAD,(UN) ; Put in where NISRV can find it
	MOVE T1,T3		; Fetch the appropriate function code
	MOVE T2,UN		; Get pointer to UN block
	CALL DLLUNI		; Set the address
	 JRST NIUEM1		;  Process the error
	MRETNG			; All done!
NIUEM1:	ADDX T1,DLLERB		; Convert NISRV error to user error
	ITERR			; And pass it upwards
	SUBTTL	LOKBUF - Lock down user buffers
; This routine will lock down all memory associated with a user's buffer.
; The resulting locked pages will be stored in the BRCP1 and BRCP2 words
; of the BR block.
;
; Call:	P3/ BR block pointer
;	CALL LOKBUF
;	<Return +1 Always>
LOKBUF:	SETZRO BRCP2,(P3)		; Zero the locked core page numbers
	OPSTR <DMOVE T1,>,BRBFA,(P3)	; Get byte pointer to buffer
	IBP T1				; Point to the real address
	CALL BP2ADR##			; Convert it to an address
	PUSH P,T1			; Save the address
	TXO T1,TWUSR			; Tell PAGEM this is a user mode addr
	XCALL (MSEC1,MLKMA)		; [7173] Lock down the buffer
	STOR T1,BRCP1,(P3)		; Remember the core page number
	LOAD T1,BRBSZ,(P3)		; Get the buffer size
	OPSTR <ADJBP T1,>,BRBFA,(P3)	; Point to the last byte of the buffer
	CALL BP2ADR##			; Convert it to an address
	POP P,T2			; Get back the start address
	XOR T2,T1			; Compute the differences
	TXNN T2,<007777,,777000>	; Start and end on the same page?
	 RET				;  Yes, all done
	TXO T1,TWUSR			; Tell PAGEM this is a user mode addr
	XCALL (MSEC1,MLKMA)		; [7173] Nope, lock down the last page
	STOR T1,BRCP2,(P3)		; Remember the end core page number
	RET				; All done
	SUBTTL	ULKBUF - Unlock buffers
; Unlock the buffers pointed at by the BR block in P3.  May be called at
; interrupt level.
	XRESCD				; [7173] This must be resident
ULKBUF:	OPSTR <SKIPN T1,>,BRCP1,(P3)	; Get the first core page number
	IFSKP.
	  XCALL (MSEC1,MULKCR)		; [7173]  Unlock the page
	ENDIF.
	OPSTR <SKIPN T1,>,BRCP2,(P3) 	; Get the second core page number
	IFSKP.
	  XCALL (MSEC1,MULKCR)		; [7173]  Unlock the second core page
	ENDIF.
	RET				; If zero, we are done
	XSWAPCD				; [7173] Go swappable again
	SUBTTL	DOINT - Process pending PSI's
; This routine will unlock the currently open portal, and enable PSI's so that
; the NI% JSYS can be interrupted.  Immediately after enabling interrupts, this
; routine will disable them and (almost) restart the JSYS.
DOINT:	OKINT				; Take the interrupt
	ULOAD P2,BXNXT,(P2)		; Get pointer to next BXB
	USTOR P2,EIBCP,(P1)		; Update the BXB pointer
	JRST NI0			; Restart the JSYS
	SUBTTL	USRCBK - Callback routine from NISRV
	XRESCD			; [7173] This must be resident
USRCBK:	SAVEAC <PR,UN>
	MOVE UN,T2		; Setup UN block pointer
	LOAD PR,UNUID,(UN)	; Setup portal block pointer
	JRST @CALDSP-1(T1)	; Dispatch on the function code
	DEFINE XX (code)<TABENT NU.'code,<IFIW <code'CBK&777777>>> ; [7173]
CALDSP:	TABBEG 1,NU.MAX,<IFIW <RTN&777777>> ; [7173]
	  XX XMT		; Transmit
	  XX RCV		; Receive
	  XX CLO		; Close
	  XX RCC		; Read Channel Counters
	  XX RPC		; Read Portal Counters
	TABEND
	XSWAPCD			; [7173] Back to swappable again
	SUBTTL	GETBRB & RETBRB - Get and Return Buffer Request blocks
; This routine will obtain a BR block.  The memory will be resident.
; It takes no arguments, and returns a pointer to the memory in T1.
;
; Call:	CALL GETBRB
;	 <+1 failure, couldn't get memory>
;	<+2 success, T1/ pointer to BR block>
GETBRR:	OPSTR <XMOVEI T1,>,PRRCQ,(PR) ; Pointer to receive quota
	 JRST GETBRB		; Join the main code stream
GETBRT:	OPSTR <XMOVEI T1,>,PRTRQ,(PR) ; Pointer to transmit quota
GETBRB:	SOSGE (T1)		; Quota exceeded?
	 JRST GETBRF		;  Yes, process failure
	SAVEAC P1		; Get a good ac
	MOVE P1,T1		; Save pointer in case of error
	MOVX T1,BR.LEN		; Allocate a BR block
	CALL GXRCOR		; Get res memory for this request
	 AOSA (P1)		;  Back up the quota and handle error
	  RETSKP		; Return success
	MOVX T1,MONX05		; No res free space
	RET			; Give error return
GETBRF:	SETZM (T1)		; Normalize the quota
	MOVX T1,NIERTE		; Transmit or receive quota was exceeded
	RET			; All done
GXRCOR:	CALL .NIOFF		; Disable interrupts
	SKIPN T2,TRCQUE		; Any command blocks available?
	 JRST GXRCO3		;  Nope, get more from ASGRES
	XMOVEI T3,TRCQUE	; Setup previous address
GXRCO2:	CAMG T1,-1(T2)		; Is this block big enough?
	 JRST GXRCO1		;  Yes, give it away
	MOVE T3,T2		; Remember previous block
	MOVE T2,0(T2)		; Nope, point to next block
	JUMPN T2,GXRCO2		; And loop till done
GXRCO3:	CALL .NION		; Turn interrupts back on
	PUSH P,T1		; Save Size for a moment
	ADDI T1,1		; Account for size word
	HRLI T1,.RESP3		; Low priority,,length of BR block
	MOVX T2,.RESGP		; Use the general pool
	XCALL (MSEC1,ASGRES)	; [7173] Get some free space
	 JRST PA1		;  Adjust the stack and return
	POP P,(T1)		; Put the size into the block
	ADDI T1,1		; Point to the next word
	RETSKP			; And return success
GXRCO1:	MOVE T1,T2		; Get block address into T1
	MOVE T2,0(T2)		; Get next block address
	MOVEM T2,0(T3)		; Make previous block point to next block
	CALL .NION
	RETSKP			; All done
RXRCOR:	SETZM 0(T1)		; Zero the next block pointer
	MOVE T2,TODCLK		; Get a timestamp
	MOVEM T2,1(T1)		; Stamp the block
	CALL .NIOFF		; No interrupts please
	MOVE T2,TRCQUE		; Get the first block on the queue
	MOVEM T2,0(T1)		; Make this block point to the first
	MOVEM T1,TRCQUE		; Make this block be the new first
	CALL .NION		; Allow interrupts
	RET
RSI (TRCQUE,0)
RELBRR:	OPSTR <XMOVEI T2,>,PRRCQ,(PR) ; Pointer to receive quota
	 JRST RELBRB		; Join the main code stream
RELBRT:	OPSTR <XMOVEI T2,>,PRTRQ,(PR) ; Pointer to transmit quota
RELBRB:	AOS (T2)		; Up the quota
	CALLRET RXRCOR		; Release the memory
NIPIA==DLSCHN
	XRESCD			; [7173]
.NIOFF::CONSO PI,PIPIIP		;[7454] Are we at interrupt level?
	NOSKED			;  No, disable the scheduler
	CHNOFF NIPIA		; Disable the KLNI channel
	RET			; And return to caller
ONRET:
.NION::	CHNON NIPIA		;[7454] Enable the KLNI interrupt channel
	CONSO PI,PIPIIP		; Are we at interrupt level?
	OKSKED			;  No, don't forget to enable the scheduler
	RET
	XSWAPCD			; [7173]
; Here when closing portals, release all blocks from the block cache
RELCSH:	SAVEAC P1
	CALL .NIOFF		; No interrupts please
	SKIPN P1,TRCQUE		; Get the queue head
	 JRST ONRET		;  Q is empty, just return
RELCS1:	MOVE T1,P1		; Get block address into T1
	MOVE P1,0(P1)		; Get next block address
	SUBI T1,1		; Point to true start of block
	XCALL (MSEC1,RELRES)	; [7173] Release the memory
	JUMPN P1,RELCS1		; Loop over all blocks
	SETZM TRCQUE		; Clear the q header
ONRSKP:	CALL .NION		; All done, enable interrupts
	RETSKP			; & give success return
	SUBTTL	QUEBRB - Queue up BRB's
; This routine will queue a buffer request block onto a queue pointed at by
; T1.  This routine should be called with interrupts off.
;
; Call:	MOVX P3,BR block
;	MOVX T1,pointer to queue header
;	CALL QUEBRB
;	 <+1 Queue was empty.  Ie: It went from empty to non-empty.>
;	<+2 Queue was not empty.>
	XRESCD			; [7173]
QUEBRB:	SETZRO BRNXT,(P3)	; Zero pointer to next BR block
	LOAD T2,XRQUE,(T1)	; Get pointer to last item of queue
	STOR P3,BRNXT,(T2)	; Make last block on q point to this block
	STOR P3,XRQUE,(T1)	; Make q end point to our block
	CAME T1,T2		; Was the queue empty?
	 AOS (P)		;  Nope, give skip return
	RET			; Yes, non-skip return
	XRESCD 			; [7434] 
; Get the first BRB on the queue
DQBRB:	PIOFF			; Keep interrupt level away
	LOAD P3,XRQUH,(T1)	; Get address of first BRB
	JUMPE P3,DQBRB1		; Jump if q is empty
	LOAD T2,BRNXT,(P3)	; Get address of next BR block
	STOR T2,XRQUH,(T1)	; Make it be the new first
	OPSTR <XMOVEI T3,>,XRQUH,(T1) ; Get address of queue header
	SKIPN T2		; Is the queue now empty?
	 STOR T3,XRQUE,(T1)	;  Yes, fix the end pointer
	PION			; Turn interrupts back on
	RETSKP			; Give successful return
DQBRB1:	PION			; Turn interrupts back on
	RET			; Give q empty return
; Requeue a BRB by putting it on the head of the queue
RQBRB:	PIOFF			; No interrupts
	LOAD T2,XRQUH,(T1)	; Get first item on queue
	STOR T2,BRNXT,(P3)	; Make our BR block point to the next item
	SKIPN T2		; Was the queue empty?
	 STOR P3,XRQUE,(T1)	;  Yes, make this item be the last
	STOR P3,XRQUH,(T1)	; Make our block be the first
	PION
	RET
	SUBTTL	SETTRB - Set EITBA and EIRBA as appropriate
	XSWAPCD			; [7434] 
; This routine will set the Transmit Buffers Available and Receive Buffers
; Available bits as appropriate.
SETTRB:	SETZ T1,		; Assume no transmits available
	OPSTR SKIPE,PRXQH,+XR.QUH(PR) ; Any transmits available?
	 MOVX T1,1		; Yes, say so
	USTOR T1,EITBA,(P1)	; Tell the user about it
	 ERJMP [ITERX]
	SETZ T1,		; Assume no receives available
	OPSTR SKIPE,PRRQH,+XR.QUH(PR) ; Any receives available?
	 MOVX T1,1		; Yes, say so
	USTOR T1,EIRBA,(P1)	; Tell the user about it
	 ERJMP [ITERX]
	RET
	SUBTTL	CREPOR - Create a Portal Block
; This routine creates a portal.  It allocates a portal block and assigns
; the fork wide portal ID.  It also creates and destroys the portal list
; structure.  The process should be NOINT when calling this routine.
;
; Call:	CALL CREPOR
;	 <+1 Couldn;t allocate memory>
;	<+2 Success, PR points to portal block>
CREPOR:	SAVEAC P1
	SETZB UN,PR		; Indicate no blocks allocated yet
	SKIPE P1,UPLIST		; Is the portal list setup?
	 JRST CREPO1		;  Yes, just create the portal block
	MOVX T2,PL.LEN		; Get length of portal list structure
	CALL GETJSF		; Allocate the memory
	 JRST [MOVX T1,MONX06	;  No swappable free space
	       JRST CREDIE]	;  Couldn't get memory, clean up and go away
	MOVEM T1,UPLIST		; Make the list official
	MOVE P1,T1		; Put list pointer in a safe place
CREPO1:	LOAD T1,PLNUM,(P1)	; Get the number of portals defined so far
	CAIL T1,MAXPOR		; Would we have too many portals?
	 JRST [MOVX T1,NIENRE	;  No room for entry
	       JRST CREDIE]
	MOVX T1,<.RESP3,,PR.LEN> ; Low priority,,length of PR block
	MOVX T2,.RESGP		; Use the general pool
	XCALL (MSEC1,ASGRES)	; [7173] Get some free space
	 JRST [MOVX T1,MONX05	;  No resident free space
	       JRST CREDIE]
	MOVE PR,T1		; Setup pointer to portal block
	MOVX T1,1		; Start with portal id # 1
	OPSTR <XMOVEI T2,>,PLLIS,(P1) ; Get base address of portal list
CREPO3:	SKIPN (T2)		; Is this slot in use?
	 JRST CREPO2		;  Nope, use it
	DADD T1,[EXP 1,1]	; Increment address and count
	CAIG T1,MAXPOR		; Did we run off the end of the portal list?
	 JRST CREPO3		;  Nope, try again
	BUG. (HLT,NIJPMU,NIUSR,SOFT,<Portal List messed up>,<<P1,PRLIST>>,<
Cause:	NIUSR was attempting to create a portal and install it in the portal
	list.  According to PLNUM, there were some free spots in the portal
	list.  An exhaustive search of the list was not able to find
	a free slot.  This is an inconsistency.
Data:	PRLIST - Portal list address
>,CREDIE)
CREPO2:	MOVEM PR,(T2)		; Make portal list point to portal block
	OPSTRM <AOS>,PLNUM,(P1)	; Increment number of portals in use
	OPSTR <CAMLE T1,>,PLMAX,(P1) ; Did we exceed the max?
	 STOR T1,PLMAX,(P1)	;  Yes, setup the new max
	STOR T1,PRUPD,(PR)	; Install the user's PID in the portal block
	MOVX T2,UN.LEN		; Get length of a UN block
	CALL GETJSF		; Get some free space
	 JRST [MOVX T1,MONX06	;  No swappable free space
	       JRST CREDIE]
	MOVE UN,T1		; Setup pointer to NISRV arg block
	STOR UN,PRUNB,(PR)	; Make portal block point to UN block
;Link the Portal block onto the list beginning at UPRBAS
	NOSKED			; No interrupts please
	MOVE T1,UPRBAS		; Get the first item on the list
	STOR T1,PRNXT,(PR)	; Make the new item point to the former 1st
	MOVEM PR,UPRBAS		; Make the new item be the first
	OKSKED
	RETSKP			; And return success
CREDIE:	MOVE P1,T1		; Save error code
	CALL KILPOR		; Kill the portal
	MOVE T1,P1		; Get error code
	RET			; Pass it upwards
	SUBTTL KILPOR - Release all resources associated with a portal
; Here to kill portals.  Portal block pointer is in PR.  This routine will
; release the portal block, it's UN block and deallocate the portal list
; if it's empty.
KILPOR:	JUMPE PR,KILLIS		; If no PR block, try to kill portal list
; Here to remove the portal block from the portal list
	LOAD T1,PRUPD,(PR)	; Get user's Portal ID
	JUMPE T1,KILPO1		; Jump if no PID assigned
; Remove the portal block from the portal list
	ADD T1,UPLIST		; Point to the slot in the portal list
	OPSTR <SETZM>,PLLIS,-1(T1) ; Clear the entry
	OPSTRM <SOS T1,>,PLNUM,+@UPLIST ; Decrement number of portals in use
; Remove the portal block from the system wide portal list
	XMOVEI T1,UPRBAS	; Get the base address
	NOSKED			; No interrupts
KILPO2:	OPSTR <CAMN PR,>,PRNXT,(T1) ; Does this block point to us?
	 JRST KILPO3		;  Yes, splice it out
	LOAD T1,PRNXT,(T1)	; Point to the next portal
	JRST KILPO2		; Loop back
KILPO3:	LOAD T2,PRNXT,(PR)	; Get our next pointer
	STOR T2,PRNXT,(T1)	; Update his next pointer
	SETZRO PRCHK,(PR)	; Zapp the check word to detect bugs
	OKSKED			; Enable interrupts
; Now recompute PLMAX
	MOVE T1,UPLIST		; Get address of portal list
	OPSTR <ADD T1,>,PLMAX,+@UPLIST ; Point to last item in use
KILPO4:	CAMLE T1,UPLIST		; Are we done?
	 SKIPE (T1)		;  No, is this entry blank?
	  TRNA			;   Done or not blank
	   SOJA T1,KILPO4	;    Yes, try again
	SUB T1,UPLIST		; Compute highest used PID
	STOR T1,PLMAX,+@UPLIST	; Save it for posterity
; Here to delete a portal block and it's associeted UN block
KILPO1:	OPSTR <SKIPE T2,>,PRUNB,(PR) ; Get the UN block address
	 CALL GIVJSF		;  Release the UN block
	MOVE T1,PR		; Get pointer to portal block
	XCALL (MSEC1,RELRES)	; [7173] Release the portal block
; Here to delete the portal list if it's empty
KILLIS:	LOAD T2,PLNUM,+@UPLIST	; Get the number of portal blocks defined
	JUMPN T2,RTN		; Just return if some portals are left
	EXCH T2,UPLIST		; Clear pointer, get address of portal list
	CALLRET GIVJSF		; Release the portal list
	SUBTTL	NIJKFK - Reset for the NI% JSYS
; Come here from KFORK% (actually KSELF), RESET% and LGOUT%.  This must be
; called in the fork's context.
	XRENT (NIJKFK,G)	; [7434] NIJKFK:: and XNIJKF:: 
	MOVX T1,0		; Get a channel #
	NOSKED			; No interrupts please
	CALL CHKOW1		; Do we own this KLNI?
	TRNA			;  Nope, skip this
	SETOM KNIOWN		;   Yes, clear ownership
	OKSKED			; Allow interrupts
	SKIPN UPLIST		; Is there a portal table?
	 RET			;  Nope, all done
	SAVEAC <P1,P2,PR,UN>
	NOINT			; No PSI's please
	SKIPN T1,UPLIST		; Get the portal list pointer
	 JRST NIJKF9		;  All done, list sneaked away
	LOAD P1,PLMAX,(T1)	; Get the highest portal defined
	OPSTR <XMOVEI P2,>,PLLIS,(T1) ; Get pointer to list beginning
NIJKF3:	SKIPN PR,(P2)		; Get a portal block
	 JRST NIJKF2		;  No such portal, try next slot
	LOAD UN,PRUNB,(PR)	; Setup UN block pointer
	CALL NIUCLI		; Close the portal
NIJKF2:	ADDI P2,1		; Point to next entry
	SOJG P1,NIJKF3		; Loop over all portals
NIJKF9:	OKINT			; Allow PSI's again
	RET
	SUBTTL ***** Hacks *****
; Hacks of the century go here!
; This is a cover routine for the stupid ASGJFR routine.  This routine
; allocates one more word and increments the address before returning it
; to the caller.  Arguments identical to ASGJFR.
GETJSF:	ADDI T2,1		; Get one more word
	XCALL (MSEC1,ASGJFR)	; [7173] Get the memory
	 RET			;  Sigh, can't get it
	AOJA T1,RSKP		; Point to next word and return success
GIVJSF:	SUBI T2,1		; Point to header word
	XCALLRET (MSEC1,RELJFR)	; [7173] All done
	SUBTTL .EIRCI - Read Channel Information
	XSWAPCD			;[7434]
NIURCI:	STKVAR (<<UNBLOK,UN.LEN>>) ; Allocate a UN block
	ULOAD T1,EICHN,(P1)	; Get the ethernet channel #
	STOR T1,UNCHN,+UNBLOK	; Put the channel number into the UN block
	MOVX T1,NU.RCI		; Get function code
	XMOVEI T2,UNBLOK	; Get UN block address
	CALL DLLUNI		;  Read the channel's info
	 JRST DLLERR		; Go handle a DLL error
	LOAD T1,UNSTA,+UNBLOK	; Read the status word from the UN block
	USTOR T1,EISTA,(P1)	; Store it in the user's block
	OPSTR <DMOVE T1,>,UNCAR,+UNBLOK ; Get the current address
	XCTU [OPSTR <DMOVEM T1,>,EIPHY,(P1)] ; Return it to the user
	OPSTR <DMOVE T1,>,UNHAD,+UNBLOK ; Get the ROM address
	XCTU [OPSTR <DMOVEM T1,>,EIHRD,(P1)] ; Return hardware addr to the user
	MRETNG			; Return success
	ENDSV.			; Terminate the STKVAR
DLLERR:	ADDI T1,DLLERB		; Generate offset for NISRV errors
	ITERR			; Pass the error (in T1) upwards
	SUBTTL .EISCA - Set Channel Address
NIUSCA:	STKVAR (<<UNBLOK,UN.LEN>>) ; Allocate a UN block
	ULOAD T1,EICHN,(P1)	; Get the ethernet channel #
	STOR T1,UNCHN,+UNBLOK	; Put the channel number into the UN block
	XCTU [OPSTR <DMOVE T1,>,EIPHY,(P1)] ; Fetch the new address
	OPSTR <DMOVEM T1,>,UNDAD,+UNBLOK ; Put address into NISRV arg block
	SETZRO UNPTR,+UNBLOK	; Indicate we have an immediate address
	MOVX T1,NU.SCA		; Get function code
	XMOVEI T2,UNBLOK	; Get arg block address
	CALL DLLUNI		; Go tell NISRV to set the address
	 JRST DLLERR		;  Convert the error code and return
	MRETNG			; All done!
	ENDSV.			; Terminate the STKVAR
	SUBTTL .EISCS - Set Channel State
NIUSCS:	CALL CHKOWN		; Do we own this channel?
	 ITERR			;  Nope, pass up the error
	STKVAR (<<UNBLOK,UN.LEN>>)
	ULOAD T1,EICHN,(P1)	; Get the ethernet channel #
	STOR T1,UNCHN,+UNBLOK	; Put the channel number into the UN block
	ULOAD T1,EISST,(P1)	; Get the new state
	STOR T1,UNSST,+UNBLOK	; Save the new state
	MOVX T1,NU.SCS		; Get function code
	XMOVEI T2,UNBLOK	; Get UN block address
	CALL DLLUNI		;  Set the channel's state
	 JRST DLLERR		; Go handle a DLL error
	MRETNG			; Return success
	ENDSV.			; Terminate the STKVAR
	SUBTTL	.EIRCC & .EIRPC - Read Channel & Portal Counters
NIURPC:	TDZA T1,T1		; Indicate reading portal counters
NIURCC:	 SETO T1,		; Indicate reading channel counters
	NOINT			; No PSI's please
	TRVAR (<<UNBLOK,UN.LEN>,<CCBLOK,CC.LEN>,CHNPOR>)
	MOVEM T1,CHNPOR		; Set flag to indicate read channel counters
NIURC1:	SETZRO UNPID,+UNBLOK	; Clear UNPID in case of errors
	SETZRO UNBFA,+UNBLOK	; Clear UNBFA in case of errors
	SETO T1,		; Get protocol type for informational portal
	STOR T1,UNPRO,+UNBLOK	; Put it in the UN block
	XMOVEI T1,USRCBK	; Get the callback address
	STOR T1,UNCBA,+UNBLOK	; Put it in the UN block
	SETZRO UNUID,+UNBLOK	; Setup the user ID
	MOVX T1,NU.OPN		; Get function code (Open a Portal)
	XMOVEI T2,UNBLOK	; Get UN block address
	CALL DLLUNI		; Open the informational portal
	 JRST RCCDI1		;  Die gracefully
	MOVX T1,<.RESP3,,CC.LEN> ; Low priority,, length of counters block
	SKIPN CHNPOR		; Reading portal counters?
	 MOVX T1,<.RESP3,,PC.LEN> ;  Yes, use proper length
	MOVX T2,.RESGP		; Use the general pool
	XCALL (MSEC1,ASGRES)	; [7173] Get some free space
	 JRST [MOVX T1,MONX05	;  Can't, release it
	       JRST RCCDI3]	;  And kill the information portal
	STOR T1,UNBFA,+UNBLOK	; Save the buffer address
	MOVX T1,CC.LEN		; Get the buffer length
	SKIPN CHNPOR		; Reading portal counters?
	 MOVX T1,PC.LEN		;  Yes, use this length instead
	STOR T1,UNBSZ,+UNBLOK	; Put it in the UN block
	ULOAD T1,EICHN,(P1)	; Get the channel #
	 ERJMP RCCDI2		;  Clean up and pass the error up
	STOR T1,UNCHN,+UNBLOK	; Put the channel # into the UN block
	ULOAD T1,EIZRO,(P1)	; Get the zero counters bit
	 ERJMP RCCDI2		;  Clean up and pass the error upwards
	STOR T1,UNZRO,+UNBLOK	; Put the zero counters bit into the UN block
	SKIPE CHNPOR		; Are we reading portal counters?
	IFSKP.			; Yes
	  CALL FNDGPR		;   Look for a global portal ID
	   JRST RCCDI3		;    No good, pass error upwards
	  STOR T1,UNSPI,+UNBLOK	;   Save the object portal ID
	ENDIF.			; End of Read Portal Counters code
	MOVE T1,FORKX		; Get our fork #
	STOR T1,UNRID,+UNBLOK	; Save the fork number for callback
	SETZRO FKST2,(T1)	; Clear completion flag for scheduler test
	MOVX T1,NU.RCC		; Function is read channel counters
	SKIPN CHNPOR		; Read channel counters?
	 MOVX T1,NU.RPC		;  No, read portal counters
	XMOVEI T2,UNBLOK	; Get the UN block address
	CALL DLLUNI		; Read the counters
	 JRST RCCDI1		;  Process the errors
	MOVEI T1,RCCTST		; Get the scheduler test for read counters
	MDISMS			; Dismiss till counters are read
	MOVE T1,FORKX		; Get our fork #
	MOVE T1,FKINT(T1)	; Get the interrupt bits
	TXNN T1,FKPS1		; Interrupt pending?
	IFSKP.
	  CALL RCCRES		;   Clear all resources
	  OKINT			;   Allow interrupts for a moment
	  NOINT			;   Clam it up again
	  JRST NIURC1		;   And restart the JSYS
	ENDIF.
; Now copy all counters from the monitor to the user
	SAVEAC <P2,P3,P4>	; Save a few AC's
	ULOAD P2,EIAR1,(P1)	; Get the counter buffer size
	 ERJMP RCCDI2		;  Die
	SUBI P2,1		; Account for length word
	SKIPLE P2		; Any room?
	IFSKP.
	  MOVX T1,NIEIBS	;   Nope, complain
	  JRST RCCDI3		;   But clean up first
	ENDIF.
	MOVX T1,CTRTLN		; Get the # of counters we know about
	SKIPN CHNPOR		; Reading channel counters?
	 MOVX T1,PRCTLN		;  No, we are reading portal counters
	CAMLE P2,T1		; Is the user's buffer too big?
	 MOVE P2,T1		;  Yes, use the shorter length
	ULOAD P3,EIAR2,(P1)	; Get the addr of the counter buffer
	 ERJMP RCCDI2
	UMOVEM P2,.ECCNT(P3)	; Save the returned length
	 ERJMP RCCDI2
	ADDI P3,1		; Point to the next word of the counters block
	LOAD P4,UNBFA,+UNBLOK	; Get the address of the counter table
	MOVN P2,P2		; Negate the count
	HRLZ P2,P2		; Get count into left half
	HRRI P2,CTRTBL		; Get table address into right half
	SKIPN CHNPOR		; Read channel counters?
	 HRRI P2,PRCTBL		;  No, use table for read portal counters
CTRLOP:	XCT (P2)		; Fetch an item from the counters block
	UMOVEM T1,(P3)		; Put it in the user's table
	 ERJMP RCCDI2
	ADDI P3,1		; Point to the next user loc
	AOBJN P2,CTRLOP		; Loop over the whole counter table
	CALL RCCRES		; Release all the resources
	OKINT			; Make us interruptable again
	MRETNG			; And return success
	RESCD			; Make scheduler test resident
RCCTST:	JN FKPS1,(FX),RSKP	; Wakeup if a PSI is pending
	OPSTR SKIPN,FKST2,(FX)	; Have we gotten the callback yet?
	RET			;  Nope, block some more
	RETSKP			; Yes, wake him up
; Here from NISRV callback level
RCCCBK:				; Callback for Read Channel Counters
RPCCBK:				; Callback for Read Portal Counters
	LOAD T1,UNRID,(UN)	; Get the fork #
	SETONE FKST2,(T1)	; Indicate that the counters have been read
	RET
	XSWAPCD			; [7173]
; Here when we get an error from NISRV
RCCDI1:	ADDI T1,DLLERB		; Convert it to a real error code
RCCDI3:	MOVEM T1,LSTERR		; Make it official
RCCDI2:	CALL RCCRES		; Release all the resources
	ITERX			; Return with error code in LSTERR
; Here to release all resources associated with this function
RCCRES:	OPSTR <SKIPN T1,>,UNBFA,+UNBLOK ; Get the counters buffer address
	IFSKP.
	  XCALL (MSEC1,RELRES)	; [7173] Release the memory
	ENDIF.
	MOVX T1,NU.CLO		; Close the portal
	XMOVEI T2,UNBLOK	; Get the UN block address
	OPSTR SKIPN,UNPID,+UNBLOK ; Did we get a PID?
	IFSKP.
	  CALL DLLUNI		;   Close the portal
	   TRN
	ENDIF.
	RET
	ENDTV.			; Terminate the TRVAR
	DEFINE FOO (field,value)<PRINTX ? CTRTBL out of order field'='value>
	DEFINE XX (field)<
	IFN .-CTRTBL-.EC'field+.ECSLZ,FOO (field,\.EC'field)
	LOAD T1,CC'field,(P4)	;; Load the field
	>
IFN .ECCNT-.EPCNT,<PRINTX ? .ECCNT and .EPCNT are not equal>
CTRTBL:
	XX SLZ		; Seconds since last zeroed
	XX BYR		; Bytes received
	XX BYS		; Bytes sent
	XX DGR		; Datagrams received
	XX DGS		; Datagrams sent
	XX MBR		; Multicast bytes received
	XX MDR		; Multicast datagrams received
	XX DSD		; Datagrams sent, initially deferred
	XX DS1		; Datagrams sent, single collision
	XX DSM		; Datagrams sent multiple collisions
	XX SF		; Send failures
	XX SFM		; Send failure bit mask
	XX RF		; Receive failure
	XX RFM		; Receive failure bit mask
	XX UFD		; Unrecognized frame destination
	XX DOV		; Data overrun
	XX SBU		; System buffer unavailable
	XX UBU		; User buffer unavailable
	CTRTLN==.-CTRTBL
	DEFINE FOO (field,value)<PRINTX ? PRCTBL out of order field'='value>
	DEFINE XX (field)<
	IFN .-PRCTBL-.EP'field+.EPSLZ,FOO (field,\.EP'field)
	LOAD T1,PC'field,(P4)	;; Load the field
	>
PRCTBL:	XX SLZ		; Seconds since last zeroed
	XX BYR		; Bytes received
	XX DGR		; Datagrams received
	XX BYS		; Bytes sent
	XX DGS		; Datagrams sent
	XX UBU		; User buffer unavailable
	PRCTLN==.-PRCTBL
	SUBTTL	.EIGET and .EIREL - Get and release a KLNI
; Test and set the ownership of a KLNI in an uninterruptable manner.
NIUGET:	ULOAD T1,EICHN,(P1)	; Obtain the channel #
	NOSKED			; Do this uninterruptably
	CALL CHKOW1		; Anybody own this channel already?
	 JRST [OKSKED		;  Allow interrupts
	       ITERR]		;  Channel is already in use
	MOVE T2,FORKX		; Get our system wide fork index
	MOVEM T2,KNIOWN		; Make us be the owner
	OKSKED			; Allow interrupts
	MRETNG			; All done!
; Release ownership of the KLNI
NIUREL:	NOINT			; Don't allow PSI's
	CALL CHKOW1		; Do we own this channel?
	 ITERR 			;  Nope, don't allow this
	SETOM KNIOWN		; Clear our ownership
	OKINT			; Allow PSI's
	MRETNG			; Return good
	SUBTTL	.EIRCL - Read Channel List
	NUMLST==50		; Amount of space to allocate for lists
; This macro should be used for all things that want a common TRVAR that
; defines UNBLOK and CLBLOK.
	DEFINE TRSTUF <TRVAR (<<UNBLOK,UN.LEN>,<CLBLOK,NUMLST>>)>
NIURCL:	TRSTUF			; Define common TRVARs
	MOVX T1,NU.RCL		; Get function code
	CALL REDLST		; Go read the list
; Now copy the channel list into the user's buffer
	CALL CPYLST		; Copy the channel list to the user
	MRETNG			; All done!
; Do an NISRV Read Channel List or Read Portal List function
REDLST:	XMOVEI T2,CLBLOK	; Get address to hold the list
	STOR T2,UNBFA,+UNBLOK	; Put it in the arg block
	MOVX T2,NUMLST		; Get length of list
	STOR T2,UNBSZ,+UNBLOK	; Put length into the UN block
	XMOVEI T2,UNBLOK	; Get arg block address
	CALL DLLUNI		; Read the list
	 JRST [ADDI T1,DLLERB	;  Convert to a real error code
	       ITERR]		;  And give error return
	RET			; All done!
; Copy the list in the CLBLOK TRVAR to the user
CPYLST:	ULOAD T1,EIAR1,(P1)	; Get the user's buffer size
	SKIPG T1		; Is the size reasonable?
	 ITERR NIEIBS		;  Nope, invalid buffer size
	ULOAD T3,EIAR2,(P1)	; Get the buffer address
	XMOVEI T2,CLBLOK	; Get address of the list
	OPSTR <CAMLE T1,>,UNBSZ,+UNBLOK ; Is the user's buffer too big?
	 LOAD T1,UNBSZ,+UNBLOK	;  Yes, use the smaller size
	UMOVEM T1,(T3)		; Install the # of items we will return
	ADDI T3,1		; Point to the next item
	XCTBMU [EXTEND T1,[XBLT]] ; Copy the data to the user
	RET			; All done!
	ENDTV.			; End the TRVAR
	SUBTTL	.EIRPL - Read Portal List
NIURPL:	SAVEAC <P2,P3>
	TRSTUF			; Define common TRVARs
	ULOAD T1,EIGBL,(P1)	; Get the global portal ID bit
	SKIPN T1		; Doing global portal ID's?
	IFSKP.			; Yes
	  MOVX T1,NU.RPL	; Tell NISRV to return it's portal list
	  CALL REDLST		; Go read the list
	  XMOVEI P2,CLBLOK	; Get address of the channel list
	  XMOVEI P3,CLBLOK	; Setup list filler AC
	  OPSTR <SKIPN T1,>,UNBSZ,+UNBLOK ; Get number of entries
	   JRST NIURP1		;  Jump if no entries
	  MOVN T1,T1		; Make it negative
	  HRL P2,T1		; Form an AOBJN pointer
	  DO.
	    MOVE T1,(P2)	; Get an NISRV PID
	    CALL INTOEX		; Convert it to an NIUSR PID
	    IFSKP.
	      MOVEM T1,(P3)	; Put back the portal ID
	      ADDI P3,1		; Point to next output
	    ENDIF.
	    AOBJN P2,TOP.	; Loop over the whole mess
	  ENDDO.
	  XMOVEI T1,CLBLOK	; Get base addr of portal list
	  SUB P3,T1		; Compute number of ID's returned
	  STOR P3,UNBSZ,+UNBLOK	; Setup for CPYLST
	ELSE.			; Local portal ID's
	  NOINT			; No interrupts while traversing list
	  SKIPE T1,UPLIST	; Get base of portal list
	  IFSKP.		; No portals defined
	    OKINT		; Allow interrupts
	    SETZRO UNBSZ,+UNBLOK ; No portals IDs returned
	    JRST NIURP1		; Exit gracefully
	  ENDIF.
	  OPSTR <XMOVEI P2,>,PLLIS,(T1) ; Get address of base of portal list
	  LOAD T4,PLMAX,(T1)	; Get number of PIDs to scan
	  XMOVEI P3,CLBLOK	; Get address of output buffer
	  STOR P3,UNBFA,+UNBLOK	; Put it where CPYLST can find it
	  DO.
	    JUMPE T4,ENDLP.	; Jump if no more portals
	    SKIPN T1,(P2)	; Fetch a portal address
	    IFSKP.		; Found a portal
	      LOAD T2,PRUPD,(T1) ; Get the portal ID
	      MOVEM T2,(P3)	; Install this portal ID
	      ADDI P3,1		; Point to next output buffer add	
	    ENDIF.
	    ADDI P2,1		; Point to next portal ID
	    SOJA T4,TOP.	; Loop over all the portals
	  ENDDO.
	  XMOVEI T1,CLBLOK	; Get start address of output buffer
	  SUB P3,T1		; Compute number of portal ID's returned
	  STOR P3,UNBSZ,+UNBLOK	; Put it where CPYLST will find it
	  OKINT
	ENDIF.
NIURP1:	CALL CPYLST		; Copy the list to the user
	MRETNG			; And return success to the user
	ENDTV.			; Terminate the TRVAR
	SUBTTL	INTOEX - Convert internal portal ID's to external portal ID's
; Convert the internal portal ID's to external portal ID's
INTOEX:	TRVAR (<<UNBLOK,UN.LEN>>)
	STKVAR <TEMPAC>		; [7285]
	STOR T1,UNPID,+UNBLOK	; Setup portal ID
	SETZRO UNBSZ,+UNBLOK	; We don't want to know about multicasts
	MOVX T1,NU.RPI		; Read portal info
	XMOVEI T2,UNBLOK	; Get arg block addr
	CALL DLLUNI		; Read the portal's info
	 RET			;  Pass the error upwards
	LOAD T1,UNCBA,+UNBLOK	; Get the callback address
	XMOVEI T2,USRCBK	; Get our callback address
	CAME T1,T2		; Match?
	IFSKP.			; Yes, convert to a real portal ID
	  SETZ T1,		;   Reset the portal ID
	  LOAD T2,UNUID,+UNBLOK	;   Get the NIUSR portal block address
	  NOSKED		;   Keep portals from sneaking away
	  OPSTR <CAME T2,>,PRCHK,(T2) ; Legit portal?
	   JRST ONRET		;   Nope, must have snuck away...
	  LOAD T3,PRUPD,(T2)	;   Get his fork-wide portal ID
	  STOR T3,PIPID,+T1	;   Add that to the global portal ID
	  LOAD T3,PRLFK,(T2)	;   Get the FORKN of the portal
	  STOR T3,PIFRK,+T1	;   Setup the fork #
	  LOAD T3,PRFRK,(T2)	;   Get the FORKX of the portal
	  OKSKED		;   Ok, portal can sneak away now
	  MOVEM T1,TEMPAC	; [7285] Save T1
	  LOAD T1,FKJO%,(T3)	; [7285] Get the job # of this portal
	  CALL LCL2GL		; [7285] Get global job number
	   SETOM T1		; [7285] Failed, use -1
	  HRLZS T1		; [7285] Move to LH
	  HRR T1,TEMPAC		; [7285] Get back fork and portal ID
	ELSE.			; No, just return NISRV's external portal ID
	  LOAD T1,UNEXI,+UNBLOK	;   Fetch the external portal ID
	  HRLI T1,-1		;   Use job # of -1 for monitor portals
	ENDIF.
	RETSKP			; Return success
	ENDTV.			; Terminate the TRVAR
	SUBTTL	.EIRPI - Read Portal Info
NIURPI:	TRSTUF			; Define common TRVARs
	CALL FNDGPR		; Find the NISRV portal he's talking about
	 ITERR			;  Couldn't find it, bomb the user
	STOR T1,UNPID,+UNBLOK	; Put portal ID where NISRV can find it
	XMOVEI T1,CLBLOK	; Get address of multicast list
	STOR T1,UNBFA,+UNBLOK	; Put it into NISRV's arg block
	MOVX T1,NUMLST		; Get length of list
	STOR T1,UNBSZ,+UNBLOK	; Put multicast list length in NISRV arg block
	MOVX T1,NU.RPI		; Get function code for NISRV
	XMOVEI T2,UNBLOK	; Setup arg block address for NISRV
	CALL DLLUNI		; Read some info about this portal
	 JRST DLLERR		;  Pass error to the user
	LOAD T1,UNPRO,+UNBLOK	; Get the protocol type
	USTOR T1,EIPRO,(P1)	; Return protocol type to the user
	LOAD T1,UNPAD,+UNBLOK	; Get the pad flag
	USTOR T1,EIPAD,(P1)	; Return pad flag to the user
	LOAD T1,UNCHN,+UNBLOK	; Get the Ethernet channel number
	USTOR T1,EICHN,(P1)	; Return the channel number to the user
	LOAD T1,UNCBA,+UNBLOK	; Get the callback address
	XMOVEI T2,USRCBK	; Get our callback address
	CAME T1,T2		; Is it an NIUSR portal?
	IFSKP.			; Yes
	  LOAD T1,UNUID,+UNBLOK	; Get the user ID
	  NOSKED		; Keep portal from slipping away
	  OPSTR <CAME T1,>,PRCHK,(T1) ; Is the check word valid?
	  IFSKP.		; Yes
	    LOAD T2,PRTCH,(T1)	; Get the xmit channel PSI
	    STOR T2,EITCH,T3-$EITCH ; Put it into T3
	    LOAD T2,PRRCH,(T1)	; Get the receive channel PSI
	    STOR T2,EIRCH,T3-$EIRCH ; Put it into T3
	    LOAD T2,PRSCH,(T1)	;   Get the status channel PSI
	    STOR T2,EISCH,T3-$EISCH ; Put it into T3
	  ENDIF.
	  OKSKED		; Allow scheduler to run
	ELSE.
	  SETZ T3,		; No PSI's if not NIUSR portal
	ENDIF.
	USTOR T3,EIPSI,(P1)	; Return the PSI's
	XCTU [OPSTR <SKIPG T1,>,EIAR1,(P1)] ; Did user supply a multicast buffer?
	IFSKP.			; Yes.
	  SUBI T1,1		; Account for count word
	  LSH T1,-1		; Compute # of multicasts user has room for
	  HLRZ T2,CLBLOK	; Get number of multicasts returned
	  CAMLE T1,T2		; Did user supply enough space?
	   MOVE T1,T2		;  Yes, user was generous, use monitor's count
	  HRLM T1,CLBLOK	; Save # of multicasts returned to the user
	  LSH T1,1		; Multiply by 2
	  ADDI T1,1		; Add in count word
	  XMOVEI T2,CLBLOK	; Get source address for XBLT
	  ULOAD T3,EIAR2,(P1)	; Get dest addr for XBLT
	  XCTBMU [EXTEND T1,[XBLT]] ; Copy the data to the user
	ENDIF.
	MRETNG			; All done!
	ENDTV.
	SUBTTL CHKOWN - Check ownership of KLNI
; See if the current fork owns the Ethernet channel in EICHN.
;
; Call:	CALL CHKOWN
;	 <Return +1, channel is owned by another fork, T1/ channel #>
;	<Return +2, channel is unowned, or owned by this fork T1/ Channel #>
;
; CHKOW1 - Same as CHKOWN, but T1 contains channel #
CHKOWN:	ULOAD T1,EICHN,(P1)	; Get the channel #
	 ERJMPR RTN		;  Pass the error code upwards
CHKOW1:	SKIPGE KNIOWN		; Anybody own the KLNI?
	 RETSKP			;  Nope, give skip return
	MOVE T2,FORKX		; Get our fork number
	CAME T2,KNIOWN		; Is this fork the owner?
	 RETBAD (NIECIO)	;  Nope, give error return
	RETSKP			; Yes, skip return
	SUBTTL	FNDPOR - Find the portal database associated with a portal ID
; FNDPOR - Find the portal block associated with a local portal ID.
;
; Call:	MOVX P1,previous context address of EI block
;	CALL FNDPOR
;	 <Return +1, error code in T1>
;	<Returns +2, portal block pointer in T1>
;
; Uses only T1 and T2
FNDPOR:	SKIPN T2,UPLIST		; Get the pointer to his portal list
	 RETBAD NIENSP		;  No such portal
	ULOAD T1,EIPID,(P1)	; Fetch his Portal ID
	 ERJMPR RTN		;  Propagate the error
	OPSTR <CAMG T1,>,PLMAX,(T2) ; Is it larger than the largest?
	 SKIPG T1		;  Or .LE. 0?
	  RETBAD NIENSP		;   No such portal
	ADD T2,T1		; Add portal offset to portal list base
	OPSTR <SKIPN T1,>,PLLIS,-1(T2) ; Fetch address of our portal block
	 RETBAD NIENSP		;  No such portal
	OPSTR <CAME T1,>,PRCHK,(T1) ; Should be equal to the PR block address
	 BUG. (HLT,NIJIPB,NIUSR,SOFT,<Illegal Portal Block>,<<T1,JOBPR>>,<
Cause:	NIUSR did not find a proper portal block pointer in the job's portal
	list.
Data:	JOBPPR - Job's portal list address
>,RTN)
	RETSKP
	SUBTTL	FNDGPR - Find NISRV portal IDs
; FNDGPR - Find the NISRV portal ID associated with the possibly global
; portal ID in EIPID.
;
; Call:	MOVX P1,previous context address of EI block
;	CALL FNDGPR
;	 <Return +1, error code in T1>
;	<Returns +2, portal block pointer in T1>
;
FNDGPR:	ULOAD T1,EIGBL,(P1)		; Get the global flag
	 ERJMPR RTN
	JUMPE T1,FNDLOC			; Jump if just local portal ID
	ULOAD T1,EIJOB,(P1)		; Get the job number
	 ERJMPR RTN
	HRRE T1,T1			; Extend the sign
	CAMN T1,[-1]			; Job -1?
	 JRST FNDMON			;  Yes, look through monitor's portals
; Here to find portals in another job or fork
	CALL GL2LCL			; Convert it to an internal job index
	 RET				;  Pass error upwards
	ULOAD T2,EIPID,(P1)		; Get the portal ID
	 ERJMPR RTN
	LOAD T3,PIFRK,+T2		; Get the job relative fork #
	LOAD T2,PIPID,+T2		; Get the fork relative pid #
	SAVEAC P2
	NOSKED				; No interrupts
	MOVE P2,UPRBAS			; Get the head of the portal list
	TRNA
FNDGLP:	 LOAD P2,PRNXT,(P2)		;  Get the next portal
	JUMPE P2,FNDGL1			; Jump when done
	OPSTR <CAME T2,>,PRUPD,(P2)	; Does the local portal ID match?
	 JRST FNDGLP			;  Nope, try next portal
	OPSTR <CAME T3,>,PRLFK,(P2)	; Does the local fork # match?
	 JRST FNDGLP			;  Nope, try next portal
	LOAD T4,PRFRK,(P2)		; Get his fork number
	LOAD T4,FKJO%,(T4)		; Get his job #
	CAME T4,T1			; Does his job # match?
	 JRST FNDGLP			;  Nope, try next portal
	LOAD T1,PRPID,(P2)		; Get his NISRV portal ID
	OKSKED
	RETSKP				; And return success
FNDGL1:	OKSKED				; Allow interrupts
	RETBAD NIENSP			; Give error - No Such Portal
; Here for portals owned by this fork
FNDLOC:	CALL FNDPOR			; Lookup our portal ID
	 RET				;  Pass error upwards
	LOAD T1,PRPID,(T1)		; Get the NISRV portal ID
	RETSKP				; And return success
; Here for monitor based portals
FNDMON:	TRVAR (<<UNBLOK,UN.LEN>>)	; Make room for portal list
	ULOAD T1,EIPID,(P1)		; Get the portal ID
	 ERJMPR RTN
	STOR T1,UNEXI,+UNBLOK		; Setup the external portal ID
	MOVX T1,NU.CXI			; Convert to internal PID
	XMOVEI T2,UNBLOK		; Get arg block address
	CALL DLLUNI			; Convert to a real PID
	 RETBAD (,<ADDI T1,DLLERB>)	;  Portal probably went away
	SETZRO UNBSZ,+UNBLOK		; We don't want the multicasts
	MOVX T1,NU.RPI			; Function is Read Portal Info
	XMOVEI T2,UNBLOK		; Get the arg block address
	CALL DLLUNI			; Get some info on the portal
	 RETBAD (,<ADDI T1,DLLERB>)	;  Portal probably went away
	LOAD T1,UNCBA,+UNBLOK		; Get the callback address
	XMOVEI T2,USRCBK		; Get our callback address
	CAMN T1,T2			; Are they the same?
	 RETBAD (NIENSP)		;  Yes, there are other ways to get
					;  info about this portal.
	LOAD T1,UNPID,+UNBLOK		; Get the NISRV portal ID
	RETSKP				; And return success
	ENDTV.				; Terminate the TRSVR.
	SUBTTL SMON% JSYS - Set NI address
; Call:	MOVX T1,.SFSEA		; Function code is "Set Ethernet Address"
;	MOVX T2,chnnum		; Ethernet interface channel number
;	MOVE T3,[POINT 8,ADDR]	; Byte pointer to 6 byte Ethernet address
;	SMON%			; Set the address
	XRENT (SETNIA,G)	; [7434] SETNIA:: and XSETNI::
	STKVAR (<<EIBLOK,.EIBMX>>) ; Allocate an NI% JSYS arg block
	UMOVE T1,2		; Get Ethernet channel number
	STOR T1,EICHN,+EIBLOK	; Stuff it in the arg block
	SETO T1,		; Get a -1
	XCTU [CAMN T1,3]	; Did user use -1 instead of a byte pointer?
	 JRST SETNI3		;  Yes, go set the DECnet address
	MOVX T4,6		; Fetch six bytes
	OPSTR <XMOVEI T3,>,EIPHY,+EIBLOK ; Setup destination address
	TXO T3,OWGP.(8)		; Make it an 8 bit one word global
	DO.
	  XCTBUU [ILDB T1,3]	; Fetch a byte from the user
	  IDPB T1,T3		; Put it in the UN block
	  SOJG T4,TOP.		; Loop over all six bytes
	ENDDO.
SETNI4:	MOVX T1,.EISCA		; Function is "Set Channel address"
	STOR T1,EIFCN,+EIBLOK	; Put it in the arg block
	XMOVEI T1,+EIBLOK	; Get the arg block address
	NI%			; Set the address
	 ERJMP .+1		;  Ignore errors for now
	RET
; Here to set the DECnet address for the Ethernet channel
SETNI3:	MOVE T1,RTRHIO##	; Get HI-ORD
	MOVE T2,RTRHOM##	; Get it's area number
	LSH T2,^D10		; Put it into place
	IOR T2,RTRADR##		; Get LO-ORD
	DPB T2,[POINT 8,T2,19]	; Swap the bytes
	LSH T2,^D12		; Left justify the swapped address
	OPSTR <DMOVEM T1,>,EIPHY,+EIBLOK ; Put the address into the arg block
	JRST SETNI4		; And finish up
	ENDSV.			; Terminate the STKVAR
	SUBTTL	End of NIUSR
	TNXEND
	END