Google
 

Trailing-Edge - PDP-10 Archives - bb-m080z-sm - monitor-sources/niusr.mac
There are 19 other files named niusr.mac in the archive. Click here to see a list.
; Edit= 9009 to NIUSR.MAC on 8-Nov-88 by LOMARTIRE
;Merge Production changes to BUG text
; Edit= 8929 to NIUSR.MAC on 23-Aug-88 by LOMARTIRE
;Improve BUG. documentation
; 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 inconsistent.

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