Trailing-Edge
-
PDP-10 Archives
-
bb-bt99q-bb
-
d8eint.mac
There are 10 other files named d8eint.mac in the archive. Click here to see a list.
TITLE D8EINT "FEK" service routine for ANF-to-Ethernet - V014
SUBTTL RDH 6-OCT-87
SEARCH F,S,NETPRM,ETHPRM,MACSYM
$RELOC
$HIGH
;THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE USED
; OR COPIED ONLY IN ACCORDANCE WITH THE TERMS OF SUCH LICENSE.
;
;COPYRIGHT (c) DIGITAL EQUIPMENT CORPORATION 1985,1986,1988.
;ALL RIGHTS RESERVED.
.CPYRT<1985,1988>
XP VD8EINT,014 ;VERSION NUMBER
IFE FTENET,<
PASS2
PRINTX [FTENET is turned off in D8EINT, suppressing assembly]
END
> ;IFE FTENET
ENTRY D8EINT
D8EINT::
Comment \
OVERVIEW
D8EINT is the ANF/Ethernet interface module.
As with D8SINT, D8EINT is a software-only interface between the ANF
network and actual hardware service routine (ETHSER in this case).
Unlike most FEKs however, the "NI" FEK is a hardware-independent in-
terface, and in particular doesn't care which cpu runs the FEK service
routines, or, for that matter, what type of cpu runs them.
At this writing, ETHSER presents only a single "channel" to the user
(e.g., ANF) with no provision for more than one "NI" network. While
D8EINT was written with multiple "channels" in mind, D8EINI in partic-
ular will need work to run in a multiple-channel environment (there
are a lot of unanswered questions about such an environment, like what
it looks like).
D8EINT uses two types of FEKs, so-called "master" and "slave" FEKs.
Each Ethernet channel (ETH-n) has one master FEK associated with it.
The ETH-0 master FEK is hardwired into COMMON along with all the other
"normal" FEKs, FEKs for other Ethernet channels (if such ever occurs)
are generated dynamically at SYSINI time and linked into the FEK chain.
All Ethernet input operations are done via a master FEK and never
via a slave FEK. All multicast NCL traffic (the periodic NODEID as
described below) goes out via a master FEK.
Each master FEK points to a (conceivably-0-length) chain of slave FEKs.
There is one slave FEK for each NCL node on the Ethernet (this is how
the nodes are faked out as "neighbors" of this host node). NCL messages
for a specific node are output via these slave FEKs (but be aware that
the output-done interrupt comes back via the slave's master FEK!!).
RESTRICTIONS
The first pass at NCL on the ethernet implements a restricted NCL
service by treating each other NCL node on the ethernet as an immediate
neighbor (indistinguishable from any other 'point-to-point' communications
link). This means that the number of NCL nodes supportable on a single
ethernet is drastically limited by NGHMAX - the maximum number of
neighbors any node in the net can support. This restriction allows NCL
to directly support the ethernet with only the most minor of protocol
changes (support of a 'broadcast' NODEID message). To properly support
NCL on an ethernet would require a significant, ah, enhancement of the
neighbors-based routing philosophy . . .
Since the KLNI can't deal with 7-bit byte pointers (boy, ever since
the DL-10 passed away it's been a steady down-hill trend!!!!!!!!!!)
NETSER must provide an 8-bit byte buffer. For the moment, it is assumed
that the buffer is contiguous and that there is only one byte count
and pointer for the entire PCB.
ETHERNET-SPECIFIC NCL PROTOCOL
The NODEID message format is extended to include a 'serial number'
(based on the ethernet 'uptime').
As with other FEKs (the NUL FEK excluded of course) a NODEID is the
first message transmitted through a FEK when the FEK comes 'online'.
This NODEID is always a 'broadcast' ethernet message. Periodically
thereafter another NODEID is 'broadcast', with an incremented serial
number (unlike other FEKs where a NODEID is NEVER sent again until
the FEK has gone offline and come back online).
Whenever an ethernet broadcast NODEID is received from an unknown NCL
node (or even a known NCL node not on the ethernet) that node is marked
as a "neighbor" (*) on the ethernet and the serial number is stashed away.
Any subsequent broadcast NODEID is ignored as long as the serial number
monotonically increases. If a broadcast NODEID is received with a serial
number which is smaller, then the NCL logical link is reinitialized.
Other than the serial-numbered NODEID message, the NCL protocol treats
the ethernet just like any other communications line.
(*) = The NCL "neighbor" is given its very own "slave" FEK, linked back
to the master FEK for that Ethernet channel. As far as NETSER is concerned
it looks just like any other "real" FEK.
FEK USAGE
The Ethernet FEKs have several (quite a few, actually) specific entries.
They are:
FEKNIA For a master FEK, FEKNIA contains the ANF protocol "broadcast"
address (same as %NTNIM). For a slave FEK, FEKNIA contains the
remote Ethernet destination address for the ANF node associated
with this FEK. With this setup, and output to a specific node is
queued via that node's FEK, while any output to be "broadcast"
is queued via the master FEK.
FEKNIC For a master FEK, FEKNIC is the Ethernet "channel" ID. Not used
by slave FEKs.
FEKNID For a master FEK, FEKNID is the Ethernet "portal" ID. Not used
by slave FEKs.
FEKNIF FEKNIF contains the address of the FEK's associated master FEK
(a master FEK points to itself).
FEKNIQ For a master FEK, FEKNIQ is the count of PCBs (buffers) queued to
to the Ethernet service layer. Not used by slave FEKs.
FEKNIS For a master FEK, FEKNIS is the current NODEID "serial number",
incremented for each broadcast NODEID. For a slave FEK, FEKNIS
is the last received NODEID serial number from the remote ANF
node.
FEKNIT For a master FEK, FEKNIT contains XWD <int>,<tim> where <int> is
the current (sliding) interval between consecutive broadcast NODEID
messages, and <tim> is the current timer until the next broadcast
NODEID message. For a slave FEK, FEKNIT is the time since the last
received NODEID from the FEK's associated remote node. All values
are in seconds.
USING D8EINT
To utilize ANF on the Ethernet, the user must explicitly define the two
symbols ANFNIP and ANFNIM via MONGEN. If the symbol ANFNIP is not defined,
then D8EINT will not be loaded with the monitor (COMMON FEK definitions).
If ANFNIP is defined, it is the Ethernet protocol type to be used by ANF.
If ANFNIP is 0, then ANF will not use the Ethernet (even though D8EINT is
loaded). This allows the adventurous user to have D8EINT loaded with the
monitor for playtime experiments (by patching the protocol type value
into location %NTNIP in NETSER) without having to rebuild the monitor,
yet still bring the monitor up sans ANF Ethernet. If ANFNIP is non-zero,
it will be used as the Ethernet protocol type for ANF NCL communications.
The protocol type is 16 bits in size. Protocol 60-06 (60006 octal) is
reserved for customer usage, as are multicast addresses AB-00-04-00-xx-xx.
The symbol ANFNIM defines the "xx-xx" part of the multicast address, and
is also 16 bits wide - but **MUST** be defined as a left-justified integer
value (bits 00-15), as opposed to ANFNIP which is a right-justified integer
value (bits 20-35). If ANFNIM is not explicitly defined, it will default
to the ANFNIP value (shifted to left-most 16 bits).
For example, in the MONGEN dialog for "OCTAL SYMBOL,VALUE", include:
ANFNIP,<60006B35> ;ANF Ethernet protocol 60-06
ANFNIM,<60006B15> ;ANF Ethernet broadcast address AB-00-04-00-60-06
\
SUBTTL D8EINT parameters varied and sundry
ND RSBLEN,10 ;LENGTH OF RANDOM SCRATCH BLOCK
;D8EINI -- ONCE-only D8EINT initialization
;
;This routine is called by SYSINI to dynamically build a master FEK for
;each of the Ethernet "channels". The individual "channels" (or FEKs)
;will be set online later (FF.ONC FEK dispatch from SYSINI/FEKINI).
D8EINI::SKIPG %NTNIP## ;GOT AN ANF PROTOCOL TYPE?
POPJ P, ;NOPE, CAN DO NOTHING HERE
SE1ENT ;GET INTO SECTION 1 FOR ETHSER
PUSHJ P,D8EGUN ;GET A UN BLOCK (RETURN IN P4)
POPJ P, ;NO UN? CAN DO NOTHING HERE
MOVEI T1,RSBLEN ;LENGTH OF A RANDOM SCRATCH BLOCK
STOR T1,UNBSZ,(P4) ;SET THAT AS SIZE OF "BUFFER"
XMOVEI T1,D8EBLK ;ADDRESS OF A RANDOM SCRATCH BLOCK
STOR T1,UNBFA,(P4) ;SET AS ADDRESS OF "BUFFER"
MOVX T1,NU.RCL ;FUNCTION: "RETURN CHANNEL LIST"
XMOVEI T2,(P4) ;ADDRESS OF "UN" BLOCK
PUSHJ P,ETHSER## ;ASK ETHSER FOR KNOWN CHANNELS
POPJ P, ;NO? THEN NO ETHERNET SERVICE HERE!
LOAD T1,UNBSZ,(P4) ;GET RETURNED COUNT (OF CHANNELS)
JUMPE T1,CPOPJ## ;IF NO CHANNELS THEN QUIT NOW
CAIE T1,1 ;*** ONLY ALLOW FOR 1 ETHERNET CHANNEL FOR NOW
STOPCD CPOPJ,DEBUG,D8ETME ;++ TOO MANY ETHERNETS
;Loop buildng a "master" FEK for each Ethernet channel (ETH-0, etc.)
PUSHJ P,SAVE4## ;WILL NEED SOME ACS HERE
MOVN P1,T1 ;NEGATIVE COUNT OF ETH CHANNELS
HRLZ P1,P1 ;AOBJN COUNT
D8EIN2: XMOVEI T1,ET0FEK## ;PROTOTYPE (AND HARDWIRED ETH-0) FEK
;RDH TRNN P1,-1 ;IS THIS CHANNEL "0"?
;RDH JRST D8EIN3 ;YES, USE PRE-EXISTING FEK THEN
;RDH MOVEI J,ET0FEK## ;USE ET0FEK AS A PROTOTYPE
;RDH PUSHJ P,D8EFGN ;ALLOCATE AN ETHERNET FEK
;RDH POPJ P, ;CAN'T GO ANY FURTHER HERE
D8EIN3: MOVEM T1,FEKNIF(T1) ;POINT FEK TO ITSELF AS MASTER FEK
MOVSI T2,FK.ETH!FK.ETM;THE "ETHERNET-MASTER-FEK" FLAG(S)
IORM T2,FEKBLK(T1) ;MARK THIS FEK AS AN ETHERNET MASTER
MOVE T2,D8EBLK(P1) ;GET THE ETHSER "CHANNEL" ID
MOVEM T2,FEKNIC(T1) ;AND SAVE IT
DMOVE T2,%NTNIM## ;ANF MULTICAST ADDRESS
DMOVEM T2,FEKNIA(T1) ;SET THAT ADDRESS IN MASTER FEK
MOVSI T2,4 ;INITIAL BROADCAST TIMER INTERVAL
MOVEM T2,FEKNIT(T1) ;SET IT UP
SETZM FEKNIS(T1) ;INITIALIZE NODEID SERIAL NUMBER
;RDH TRNN P1,-1 ;IS THIS THE PROTOTYPE ETH-0 FEK?
;RDH JRST D8EIN5 ;YES, IT'S ALREADY PROPERLY LINKED
;RDH HRRZ T2,ET0FEK##+FEKBLK ;SPLICE THIS NEWLY-CREATED
;RDH HRRM T1,ET0FEK##+FEKBLK ; ETHERNET MASTER FEK INTO THE
;RDH HRRM T2,FEKBLK(T1) ; SYSTEM FEK CHAIN
D8EIN5: AOBJN P1,D8EIN2 ;LOOP FOR ALL ETHERNET "CHANNELS"
POPJ P, ;ALL FEKS BUILT
SUBTTL FEK 'Kontroller' service
;D8EDSP - Kontroller dispatch service from NETSER
;Call is:
;
; MOVEI T1,<FNC>
; MOVE J, <FEK>
; PUSHJ P,D8EDSP
; return always
;
;where <FEK> is the address of the FEK block; and <FNC> is the FEK-level
;function (FF.???) to be performed. See the individual functions for
;the passing of function-specific data.
;
;Return is CPOPJ always.
D8EDSP::SE1ENT ;NEED TO BE IN EXTENDED ADDRESSING
CAIL T1,FF.ONC ;RANGE CHECK THE FUNCTION
CAILE T1,FF.DFK ; CODE AND STOP IF BAD
PUSHJ P,NTDSTP## ;++ ERROR: BAD FUNCTION CODE TO FEK
JRST @D8EDST(T1) ;DISPATCH TO APPROPRIATE ROUTINE
;FEK function dispatch table
D8EDST: IFIW D8EONC ;FF.ONC - 00 - ONCE-ONLY INITIALIZATION
IFIW D8ESEC ;FF.SEC - 01 - ONCE-A-SECOND CODE
IFIW D8ERDD ;FF.RDD - 02 - POST A RECEIVE BUFFER
IFIW D8EWRT ;FF.WRT - 03 - POST A TRANSMIT BUFFER
IFIW D8ECRS ;FF.CRS - 04 - CRASH THE 'FE'
IFIW D8EDWN ;FF.DWN - 05 - ENTRY WHEN 'FE' CRASHES
IFIW D8EUPP ;FF.UP - 06 - ENTRY WHEN 'FE' COMES UP
IFIW D8ESTC ;FF.STC - 07 - STATION CONTROL
IFIW D8ECPS ;FF.CPS - 10 - CPU IS GOING TO SLEEP
IFIW D8ECPW ;FF.CPW - 11 - CPU IS WAKING UP
IFIW D8ECFK ;FF.CFK - 12 - CREATE NEW SLAVE FEK
IFIW D8EDFK ;FF.DFK - 13 - DESTROY OLD SLAVE FEK
;D8EONC -- ONCE-only FEK initialization
;
;D8EINI has already been called and has built the various master FEK
;blocks for each Ethernet "channel". This code is a per-FEK call to
;turn on each individual FEK.
D8EONC: SKIPG %NTNIP## ;ANF PROTOCOL DEFINED?
POPJ P, ;NO, THEN NO ETHERNET SERVICE
PUSHJ P,D8EGUN ;GET A UN BLOCK
POPJ P, ;NONE AVAILABLE?
MOVE T1,FEKNIC(J) ;GET ETHERNET "CHANNEL" ID
STOR T1,UNCHN,(P4) ;STORE IN "UN" BLOCK
STOR J,UNUID,(P4) ;STORE FEK ADDRESS ("USER ID") IN "UN" BLOCK
XMOVEI T1,D8EISR ;GET OUR "CALLBACK" (OR ISR) DISPATCH ADDRESS
STOR T1,UNCBA,(P4) ;STORE IN "UN" BLOCK
MOVE T1,%NTNIP## ;GET ANF ETHERNET PROTOCOL TYPE
STOR T1,UNPRO,(P4) ;STORE IN "UN" BLOCK
SETONE UNPAD,(P4) ;ENABLE "PADDING" OF MESSAGES
MOVEI T1,NU.OPN ;FUNCTION: OPEN PORTAL
XMOVEI T2,(P4) ;ADDRESS OF "UN" BLOCK
PUSHJ P,ETHSER## ;ASK ETHSER TO OPEN A PORTAL FOR US
POPJ P, ;NO???
LOAD T1,UNPID,(P4) ;GET PORTAL ID
MOVEM T1,FEKNID(J) ;SAVE FOR FUTURE CALLS TO ETHSER
PUSHJ P,D8EOOF ;SET FEK ON- OR OFF-LINE
JFCL ;CAN'T FAIL
DMOVE T1,%NTNIM## ;GET ANF ETHERNET MULTICAST ADDRESS
DMOVEM T1,UN.DAD(P4) ;STOR IN "UN" BLOCK
MOVEI T1,NU.EMA ;FUNCTION: ENABLE MULTICAST ADDRESS
XMOVEI T2,(P4) ;ADDRESS OF "UN" BLOCK
PUSHJ P,ETHSER## ;TURN ON MULTICAST ADDRESS RECEPTION
JFCL ;CAN'T FAIL!
POPJ P, ;RETURN WITH FEK ENABLED
;D8ESEC -- Once-a-second FEK service
;
;Here once-per-second for each of the Ethernet FEKs. Perform the broad-
;cast NODEID timing for "master" FEKs.
D8ESEC: SKIPL T1,FEKBLK(J) ;GET THE FEK FLAGS
.CREF FK.ONL ;CREF REFERENCE TO FEK-ONLINE FLAG
POPJ P, ;FEK IS OFFLINE, FORGET IT
TLNN T1,FK.ETM ;THIS IS A MASTER FEK, ISN'T IT?
JRST D8ESE6 ;NO, A SLAVE, CHECK FOR COMMUNICATIONS TIMEOUT
D8ESE1: HRRE T1,FEKNIT(J) ;GET COUNTDOWN TIMER
SOJLE T1,D8ESE3 ;REQUEST NODEID BROADCAST IF EXPIRED
HRRM T1,FEKNIT(J) ;SET NEW TIMER VALUE
PJRST NTFSEC## ;AND DO NORMAL NETSER ONCE/SECOND STUFF
D8ESE3: MOVSI T2,FK.NID ;THE "NODEID SENT" FLAG
TDNN T2,FEKBLK(J) ;HAVE WE SENT A NODEID?
PJRST NTFSEC## ;NOT YET, LEAVE TIMER(S) ALONE
ANDCAM T2,FEKBLK(J) ;YES, REQUEST ANOTHER NODEID BE SENT
HLRZ T1,FEKNIT(J) ;GET INCREMENTAL TIMER VALUE
LSH T1,1 ;SCALE IT UP A BIT,
CAMLE T1,%NTNII## ; BUT PEG IT
MOVE T1,%NTNII## ; AT [NOMINALLY] A ONE-MINUTE INTERVAL
HRLZM T1,FEKNIT(J) ;STORE NEW INTERVAL
PJRST NTFSEC## ;AND DO NORMAL NETSER ONCE/SECOND STUFF
;Here for a slave FEK, make sure we are in "constant" contact with the remote
; node. This must be done on a per-FEK (and associated NODEID) basis since
; we can't rely on normal NCL traffic (in case multiple Ethernet channels
; to the same node ever arise).
D8ESE6: SOSL FEKNIT(J) ;DECREMENT TIMER
POPJ P, ;STILL ALIVE, AFTER ALL THESE . . .
PJRST D8EDFK ;TIMED OUT, KILL OFF THE FEK
;D8ERDD -- Read request (post a receive buffer)
;
;FEKIAD contains a PCB address to be filled with receive data.
;
;This function merely gives the buffer to the "FE" service routine to
;be filled, no actual data is returned by this call.
;
; Note: Unlike other ANF FEKs, this routine actually decouples the input
; PCB and gives it to ETHSER to queue, using FEKNIQ as a count of
; buffers hiding inside ETHSER. See NETSER's NETRDD routine. This
; skulduggery is necessary to keep multiple buffers available such
; that the KLNI [probably] won't run out of buffers and lose input
; messages . . .
D8ERDD: AOSE FEKBSI(J) ;GET THE INPUT INTERLOCK (WE GET HERE FROM
; BOTH UUO AND INTERRUPT LEVEL)
POPJ P, ;IF ALREADY DOING INPUT, EXIT
PUSHJ P,D8EGUN ;GET A UN BLOCK
JRST D8ERD7 ;NONE AVAILABLE, TRY AGAIN LATER
SKIPN T1,FEKIAD(J) ;GOT AN INPUT BUFFER?
JRST D8ERD9 ;NO
MOVE T2,FEKBLK(J) ;GET THE FEK FLAGS
TLNN T2,FK.ETM ;ONLY THE MASTER FEK GETS TO DO INPUT
STOPCD D8ERD9,DEBUG,D8ESFI ;++ SLAVE FEK HAS INPUT BUFFER
MOVE T2,FEKNID(J) ;ETHSER'S PORTAL ID
STOR T2,UNPID,(P4) ;STORE IN "UN" BLOCK
STOR T1,UNRID,(P4) ;STORE PCB ADDRESS IN "UN" BLOCK RID FIELD
;RDH SETZRO UNBSZ,(P4) ;NO BYTE COUNT MEANS MSD-STYLE CALL
;RDH XMOVEI T2,PCBMS1(T1) ;ADDRESS OF MSD PORTION OF PCB
;RDH STOR T2,UNBFA,(P4) ;STORE IN "UN" BLOCK
;RDH SETZM PCBNXX(T1) ;NO SECOND MSD HERE (STUPID KLNI!!!)
MOVE T2,PCBCTR(T1) ;COUNT OF DATA BYTES IN BUFFER
ADDI T2,4 ;ALLOW FOR STUPID CRC BYTES (*!!$!*#!!)
; BUT *NOT* THE OTHER TWO COUNT BYTES
; YEAH, THIS LOOKS REALLY ODD, BUT NETSER
; CAREFULLY ALLOCATES A BUFFER THAT IS
; SECRETLY 6 BYTES LARGER THAN MSGMAX . . .
STOR T2,UNBSZ,(P4) ;STORE IN "UN" BLOCK
HLLZ T2,PCBPTR(T1) ;NOW MUST CONCOCT A TWO-WORD GLOBAL
HRRZ T3,PCBPTR(T1) ; BYTE POINTER FOR ETHSER
TLO T2,(1B12) ; OTHERWISE IT GOES DING DING DING
DMOVEM T2,UN.BFA(P4) ;STORE IN "UN" BLOCK
MOVX T2,UNA.EV ;THE DATA BUFFER IS IN EXEC VIRTUAL MEMORY
STOR T2,UNADS,(P4) ;STORE THAT IN "UN" BLOCK
MOVEI T1,NU.RCV ;FUNCTION: POST RECEIVE BUFFER
XMOVEI T2,(P4) ;ADDRESS OF "UN" BLOCK
PUSHJ P,ETHSER## ;QUEUE UP RECEIVE BUFFER
JRST D8ERD4 ;ERROR? GO CHECK IT OUT
HRRZ T1,FEKIAD(J) ;RETRIEVE ADDRESS OF PCB
HRRZ T2,PCBBLK(T1) ;ADDRESS OF NEXT PCB (MUST BE 0)
CAIE T2,0 ;JUST TO MAKE SURE IS REALLY TRULY 0
PUSHJ P,NTDSTP## ;THIS IS A NO-NO
MOVEM T2,FEKIAD(J) ;INDICATE PCB GIVEN TO ETHSER
AOS FEKNIQ(J) ;COUNT UP BUFFERS QUEUED TO ETHSER
JRST D8ERD9 ;ALL DONE HERE
;Here when ETHSER rejects our attempt to queue up an input buffer
D8ERD4: CAIE T1,UNRES% ;NO RESOURCES TO DEAL WITH BUFFER?
CAIN T1,UNPWS% ;OR PORTAL IN WRONG STATE (IN TRANSISTION)?
JRST D8ERD7 ;YEAH, FLAG TO TRY AGAIN LATER
CAIN T1,UNRAB% ;RECEIVE ABORTED?
JRST D8ERD7 ;YEAH, TRY AGAIN LATER
STOPCD CPOPJ,DEBUG,D8ERFU ;++ "RDD" REQUEST FOULED UP
; NOTE WELL: THIS LEAVES FEKBSI SET!!!!
; EFFECTIVELY "HANGING" THIS FEK . . .
; BUT AT LEAST IT SHOULD CAUSE NO MORE TROUBLE
;No resources for input buffer, try again later
D8ERD7: MOVSI T1,FK.STI ;THE "KICK ME" FEK FLAG
IORM T1,FEKBLK(J) ;ASK FOR CLOCK-DRIVEN SERVICE LATER ON
D8ERD9: SETOM FEKBSI(J) ;INPUT NOW IDLE
POPJ P, ;RETURN
;D8EWRT -- Write request (post a transmit buffer)
;
;FEKOAD contains a PCB address chain to be transmitted (in order of
;entry on the PCB chain); FEKOCT incremented by the caller.
;
;This function merely "queues" the PCB(s) from FEKOAD to the "FE"
;service routine.
D8EWRT: AOSE FEKBSO(J) ;GET THE "OUTPUT BUSY" INTERLOCK
POPJ P, ; IF ALREADY DOING OUTPUT, EXIT NOW
PUSHJ P,D8EGUN ;GET A UN BLOCK
JRST D8EWR7 ;NONE AVAILABLE, TRY AGAIN LATER
D8EWR0: NETOFF ;KEEP THE PCB CHAIN(S) CLEAN
SKIPG FEKOCT(J) ;CHECK IF ANYTHING TO DO
JRST D8EWR8 ;IF NO OUTPUT MSGS, SET OUTPUT IDLE
SOS FEKOCT(J) ;COUNT DOWN PCB'S ON THE QUEUE
HRRZ T1,FEKOAD(J) ;FIRST OUTPUT PCB ON QUEUE
HRRZ T2,PCBBLK(T1) ;NEXT (IF ANY) PCB ON THE CHAIN
HRRZM T2,FEKOAD(J) ;BECOMES NEW FIRST PCB ON OUTPUT QUEUE
NETON ;SAFE AGAIN
HLLZS PCBBLK(T1) ;DELINK THIS PCB FROM THE QUEUE
HRLM J,PCBFEK(T1) ;REMEMBER FROM WHICH FEK CAME THIS PCB
MOVE T4,FEKNIF(J) ;GET MASTER FEK ADDRESS
MOVE T2,FEKNID(T4) ;ETHSER'S PORTAL ID VALUE (FROM MASTER)
STOR T2,UNPID,(P4) ;STORE IN "UN" BLOCK
DMOVE T2,FEKNIA(J) ;REMOTE ETHERNET NODE ADDRESS
; (OR MULTICAST ADDRESS IF MASTER FEK)
DMOVEM T2,UN.DAD(P4) ;STORE IN "UN" BLOCK
STOR T1,UNRID,(P4) ;STORE PCB ADDRESS IN "UN" BLOCK RID FIELD
;RDH SETZRO UNBSZ,(P4) ;NO BYTE COUNT MEANS MSD-STYLE CALL
;RDH XMOVEI T2,PCBMS1(T1) ;ADDRESS OF MSD PORTION OF PCB
;RDH STOR T2,UNBFA,(P4) ;STORE IN "UN" BLOCK
;RDH SETZM PCBNXX(T1) ;NO SECOND MSD HERE (STUPID KLNI!!!)
MOVE T2,PCBCTR(T1) ;COUNT OF DATA BYTES IN BUFFER
STOR T2,UNBSZ,(P4) ;STORE IN "UN" BLOCK
HLLZ T2,PCBPTR(T1) ;NOW MUST CONCOCT A TWO-WORD GLOBAL
HRRZ T3,PCBPTR(T1) ; BYTE POINTER FOR ETHSER
TLO T2,(1B12) ; OTHERWISE IT GOES DING DING DING
DMOVEM T2,UN.BFA(P4) ;STORE IN "UN" BLOCK
MOVX T2,UNA.EV ;THE DATA BUFFER IS IN EXEC VIRTUAL MEMORY
STOR T2,UNADS,(P4) ;STORE THAT IN "UN" BLOCK
MOVEI T1,NU.XMT ;FUNCTION: SEND DATAGRAM
XMOVEI T2,(P4) ;ADDRESS OF "UN" BLOCK
PUSHJ P,ETHSER## ;GIVE BUFFER TO ETHERNET SERVICE
JRST D8EWR4 ;FAILED - GO CHECK IT OUT
JRST D8EWR0 ;FLUSH ALL OUTPUT INTO ETHSER
;Here on ETHSER's rejection of our output request
D8EWR4: LOAD T3,UNRID,(P4) ;OOPS, RETRIEVE ERRANT PCB
NETOFF ;NO INTERRUPTS PLEASE
HRRZ T4,FEKOAD(J) ;GET HEAD OF STILL-TO-BE-OUTPUT PCB CHAIN
HRRZM T3,FEKOAD(J) ;OLD PCB GETS PUT BACK AT THE START
HRRM T4,PCBBLK(T3) ; OF THE FEK'S OUTPUT QUEUE
AOS FEKOCT(J) ;ONE MORE PCB ON THE OUTPUT QUEUE
NETON ;SAFE AGAIN
CAIE T1,UNRES% ;NO RESOURCES?
CAIN T1,UNPWS% ;OR PORTAL IN WRONG STATE (IN TRANSISTION)?
PJRST D8EWR7 ;YES, FLAG TO TRY AGAIN LATER
STOPCD CPOPJ,DEBUG,D8EWFU ;++ "WRT" REQUEST FOULED UP
; NOTE WELL: THIS LEAVES FEKBSO SET!!!!
; EFFECTIVELY "HANGING" THIS FEK . . .
; BUT AT LEAST IT SHOULD CAUSE NO MORE TROUBLE
;Output "blocked", try again later
D8EWR7: MOVSI T1,FK.STO ;THE "KICK ME" FEK FLAG
IORM T1,FEKBLK(J) ;ASK FOR CLOCK-DRIVEN SERVICE LATER ON
JRST D8EWR9 ;SET FEK OUTPUT IDLE AND RETURN
D8EWR8: NETON ;RE-ENABLE INTERRUPTS
D8EWR9: SETOM FEKBSO(J) ;SET OUTPUT IDLE
POPJ P, ;AND RETURN
;D8ECRS -- Request the "FE" to shut down ('crash')
;
;D8ECRS is called by NETSER to forcibly terminate (i.e., shut down
;or crash) the "FE".
;
;The question remains of how we decide to "start it up" again...
D8ECRS: PUSHJ P,NTDSTP## ;++NULL
;D8EDWN -- The "FE" has gone down
;
;This function is not used (rather ETHSER dispatches via D8EISR/NU.RCI)
D8EDWN: PUSHJ P,NTDSTP## ;++NULL
;D8EUPP -- The "FE" is up
;
;This function is not used (rather ETHSER dispatches via D8EISR/NU.RCI)
D8EUPP: PUSHJ P,NTDSTP## ;++NULL
;D8ESTC -- Station control function
;
;This function is not supported (ergo crash if get here)
D8ESTC: PUSHJ P,NTDSTP ;++NULL
;D8ECPS -- Take a nap (cpu is going to sleep)
;
;This function not implemented.
D8ECPS: POPJ P, ;DON'T WORRY ABOUT IT
;D8ECPW -- Time to wake up (cpu awakened)
;
;This function not implemented.
D8ECPW: POPJ P, ;DON'T WORRY ABOUT IT
;D8ECFK -- Create new slave FEK (new Ethernet NCL node)
;
;D8ECFK called when a NODEID has been received (from the master FEK)
;for a new node. Master FEK address in J.
;
;Returns new slave FEK address in T1 (or 0 if couldn't create a new
;FEK block).
D8ECFK: PUSHJ P,D8EFGN ;CREATE A NEW FEK BLOCK
JRST [SETZ T1, ;CAN'T, FLAG FAILURE
POPJ P,] ;AND RETURN NOW
MOVSI T2,FK.NID ;THE "NODEID-SENT" FLAG
IORM T2,FEKBLK(T1) ;ALWAYS SET IN SLAVES (ONLY MASTER DOES NODEIDS)
MOVSI T2,FK.ONL ;THE "ON-LINE" FEK FLAG
TDNE T2,FEKBLK(J) ;IS THE MASTER ON LINE?
IORM T2,FEKBLK(T1) ;YES, THEN SO IS THE SLAVE
HLLZ T2,FEKCST(J) ;GET MASTER COST
HLLM T2,FEKCST(T1) ;SET AS COST TO THAT SLAVE
POPJ P, ;RETURN WITH T1/FEK
;D8EDFK -- Destroy old slave FEK
;
;Called with slave FEK address in J
D8EDFK: NETOFF ;NO INTERRUPTS WHILST DIDDLING FEK CHAIN
MOVE T2,FEKNIF(J) ;START WITH SLAVE'S MASTER FEK ADDRESS
D8EDF1: MOVE T1,T2 ;SAVE PRECEDING FEK ADDRESS
HRRZ T2,FEKBLK(T2) ;LINK TO NEXT FEK IN CHAIN
CAIN T2,0 ;BAD NEWS IF CAN'T FIND SLAVE FEK IN FEK CHAIN
STOPCD D8EDF9,DEBUG,D8EFNC ;++ SLAVE FEK NOT IN FEK CHAIN
CAME T2,J ;IS THIS THE FEK TO BE DELETED?
JRST D8EDF1 ;NO
D8EDF3: HRRZ T3,FEKBLK(T2) ;ADDRESS OF NEXT FEK IN CHAIN
HRRM T3,FEKBLK(T1) ;SPLICE OUT OLD SLAVE FEK
NETON ;SAFE AGAIN
PUSH P,T1 ;SAVE ADDRESS OF PRECEDING FEK
; MOVSI T1,FK.ONL ;THE FEK-IS-ONLINE FLAG
; ANDCAM T1,FEKBLK(J) ;THE FEK IS NO LONGER ONLINE
MOVEI T1,FI.DWN ;THIS FEK IS DOWN FER SUR
PUSHJ P,FEKINT## ;TELL NETSER TO RECOMPUTE TOPOLOGY
MOVEI T1,FEKLEN ;LENGTH OF FEK BLOCK
MOVE T2,J ;ADDRESS OF FEK BLOCK
PUSHJ P,GIVZWD## ;DEALLOCATE FEK BLOCK MEMORY
PJRST JPOPJ## ;RETURN, J/PRECEDING FEK ADDRESS
; (E.G., FOR NETSER/FEK ONCE/SECOND, ETC.)
D8EDF9: NETON ;CLEAR NETSER INTERLOCK
POPJ P, ;AND RETURN
SUBTTL FEK "Interrupt" service
;D8EISR - Interrupt service dispatch from ETHSER
;Call is:
;
; MOVEI T1,<FNC>
; MOVX T2,<UNB>
; MOVX T3,<STS>
; PUSHJ P,D8EISR
; return
;
;Where <FNC> is the ISR (or 'callback' in Etherese) function; <UNB>
;is the ETHSER-supplied 'UN' argument block; and <STS> is the function
;status (if any).
;
;Return is CPOPJ always.
D8EISR: PUSHJ P,SAVQ## ;PROTECT ETHSER'S REGS
PUSHJ P,SAVJW## ;...
CAIL T1,NU.MIN ;RANGE CHECK THE FUNCTION
CAILE T1,NU.MAX ; CODE AND STOP IF BAD
D8EIS0: STOPCD .,STOP,D8EISF ;++ BAD ISR FUNCTION CODE
JRST @D8EIST-NU.MIN(T1) ;DISPATCH ON ISR ('CALLBACK') FUNCTION
;ISR function dispatch table
DEFINE DISP(CODE,ADDR),<
IF1,<IFN <CODE-NU.MIN-<.-D8EIST>>,<PRINTX ?Table D8EIST entry CODE is out of order>>
IFIW ADDR ;CODE
>; END DEFINE DISP
D8EIST: DISP NU.OPN,D8EIS0 ;NU.OPN - 01 - UNDEFINED
DISP NU.CLO,D8ECLO ;NU.CLO - 02 - PORTAL CLOSED
DISP NU.RCV,D8ERCV ;NU.RCV - 03 - DATAGRAM RECEIVED
DISP NU.XMT,D8EXMT ;NU.XMT - 04 - DATAGRAM TRANSMITTED
DISP NU.EMA,D8EIS0 ;NU.EMA - 05 - MULTICAST ADDRESS ENABLED
DISP NU.DMA,D8EIS0 ;NU.DMA - 06 - MULTICAST ADDRESS DISABLED
DISP NU.RCL,D8EIS0 ;NU.RCL - 07 - READ CHANNEL LIST
DISP NU.RCI,D8ERST ;NU.RCI - 10 - CHANNEL STATE CHANGE
DISP NU.RCC,D8EIS0 ;NU.RCC - 11 - READ CHANNEL COUNTERS
DISP NU.SCA,D8ESCA ;NU.SCA - 12 - CHANNEL ADDRESS CHANGED
DISP NU.RPL,D8EIS0 ;NU.RPL - 13 - READ PORTAL LIST
DISP NU.RPI,D8EIS0 ;NU.RPI - 14 - READ PORTAL INFO
DISP NU.RPC,D8EIS0 ;NU.RPC - 15 - READ PORTAL COUNTERS
DISP NU.RKL,D8EIS0 ;NU.RKL - 16 - READ KONTROLLER LIST
DISP NU.RKI,D8EIS0 ;NU.RKI - 17 - READ KONTROLLER INFO
DISP NU.RKC,D8EIS0 ;NU.RKC - 20 - READ KONTROLLER COUNTERS
IF1,<IFN <NU.MAX-NU.MIN+1-<.-D8EIST>>,<PRINTX ?Table D8EIST is missing entries>>
;D8ECLO -- ETHSER 'Portal' closed
;
;This function handles the ISR-level notification of the FE's having
;'CLOSEd' the ethernet 'Portal' (i.e., the FE is shut down).
D8ECLO: PUSHJ P,NTDSTP## ;++ WE DON'T EVER CLOSE THE PORTAL
;D8ERCV -- Message received
;
;D8ERCV is the ISR-level receipient of a successfully-filled (and
;previously "posted" via D8ERDD) receive buffer from the ethernet.
D8ERCV: LOAD T1,UNRID,(T2) ;RETRIEVE PCB ADDRESS
LOAD J,UNUID,(T2) ;AND MASTER FEK ADDRESS
MOVEM T1,FEKIDN(J) ;SET RECEIVED PCB ADDRESS (NI-FEK ONLY)
SOS FEKNIQ(J) ;ONE FEWER PCB QUEUED IN ETHSER
SKIPE T3 ;ANY ERRORS?
TDZA T3,T3 ;YES, SET ZERO BYTE COUNT AND CONTINUE
LOAD T3,UNBSZ,(T2) ;GET RECEIVED BYTE COUNT
MOVEM T3,PCBCTR(T1) ;SAVE IT WITH THE PCB
DMOVE T3,UN.BFA(T2) ;GET ETHSER'S RETURNED BYTE POINTER
TLZE T3,(1B12) ;CLEAR 2-WORD FORMAT FLAG
HRR T3,T4 ;AND MAKE IT A ONE-WORD LOCAL BYTE POINTER
MOVEM T3,PCBPTR(T1) ;SAVE IT WITH THE PCB
; AS OF THIS WRITING, THE KLNI LEAVES THE
; TWO-BYTE COUNT FIELD IN THE DATA BUFFER,
; AND ETHSER INCREMENTS THE POINTER PAST
; THE TWO BYTES, BUT THAT MAY CHANGE SOME-
; DAY, SO WE MUST PICK UP ETHSER'S BYTE
; POINTER RATHER THAN TRUSTING THE ORIGINAL
; PCB BYTE POINTER . . .
DMOVE T3,UN.SAD(T2) ;GET SOURCE ETHERNET NODE ADDRESS
DMOVEM T3,PCBNIA(T1) ;SAVE IT WITH THE PCB
MOVEI T1,FI.IDN ;FUNCTION: INPUT DONE (FEKIDN)
PJRST FEKINT## ;GIVE THE PCB TO NETSER
;D8EXMT -- Message transmitted
;
;D8EXMT is the ISR-level receipient of a successfully-transmitted
;(and previously "queued" via D8EWRT) transmit buffer.
D8EXMT: LOAD T1,UNRID,(T2) ;RETRIEVE ADDRESS OF PCB
HLRZ J,PCBFEK(T1) ;RETRIEVE ORIGINATING FEK'S ADDRESS
MOVEM T1,FEKODN(J) ;SET OUTPUT-DONE PCB ADDRESS
MOVEI T1,FI.ODN ;FUNCTION: OUTPUT-DONE
PJRST FEKINT## ;RETURN BUFFER TO NETSER
;D8ERST -- Ethernet channel ("FE") state change
;
;D8ERST is called by ETHSER whenever the 'Portal' (the logical "FE")
;changes state (i.e., goes online/offline/etc).
D8ERST: LOAD J,UNUID,(T2) ;RETRIEVE ADDRESS OF ASSOCIATED MASTER FEK
PUSHJ P,D8EOOF ;CHECK/SET ON/OFF LINE FEK STATUS
PUSHJ P,NTDSTP## ;CAN'T (WELL, SHOULDN'T) HAPPEN
JUMPE T1,CPOPJ## ;IF NOTHING EXCITING HAPPENED, IGNORE IT
;FEK changed state, tell NETSER as appropriate
D8ERS1: PUSHJ P,SAVE4## ;NEED SOME NETSER-PRESERVED ACS HERE
MOVE P1,J ;SAVE A COPY OF THE MASTER FEK ADDRESS
TDNN T1,FEKBLK(J) ;IS MASTER FEK NOW ONLINE?
JRST D8ERS6 ;OFFLINE, SET ALL SLAVES OFFLINE ALSO
;Master FEK is now online
D8ERS2: MOVSI T2,4 ;INITIAL BROADCAST TIMER INTERVAL
MOVEM T2,FEKNIT(J) ;RESET BROADCAST INTERVAL TIMER
SETZM FEKNIS(J) ;REINITIALIZE NODEID SERIAL NUMBER
POPJ P, ;NOTHING MORE TO DO FOR NOW
;Master FEK is offline, set all slaves offline also
D8ERS6: MOVE J,P1 ;START SEARCHING AT THIS MASTER FEK
D8ERS7: HRRZ J,FEKBLK(J) ;NEXT POTENTIAL SLAVE FEK
JUMPE J,D8ERS8 ;EXIT WHEN NO MORE SLAVE FEKS
CAME P1,FEKNIF(J) ;SLAVE BELONG TO THIS MASTER?
JRST D8ERS7 ;NO, SKIP IT
PUSHJ P,D8EDFK ;DESTROY DEAD SLAVE FEK
; (THIS TELLS NETSER TO BLAST TOPOLOGY)
JRST D8ERS7 ;LOOP FOR ALL SLAVES ON THIS MASTER
D8ERS8: MOVE J,P1 ;RESTORE MASTER FEK ADDRESS
MOVEI T1,FI.DWN ;FUNCTION: FEK OFFLINE
PJRST FEKINT## ;TELL NETSER MASTER FEK IS NOW OFFLINE
;D8ESCA -- Ethernet channel address changed
;
;D8ESCA is called by ETHSER whenever the 'Channel' address associated
;with a 'Portal' changes.
D8ESCA: POPJ P, ;DUH?
SUBTTL Miscelleaneous support routines
;D8EFGN -- Create a new Ethernet FEK
;Call is:
;
; MOVE J,<MFEK>
; PUSHJ P,D8EFGN
; Error return
; Normal return
;
;Where <MFEK> is the address of the "master" (or prototype) FEK from which
;the new FEK is to be modeled (FEKDSP, FEKUNI, FEKNIF).
;
;On error return there was insufficient ANF free core to allocate for
;the FEK block.
;
;On normal return, T1 contains address of newly-created FEK in CLNFEK (i.e.,
;pristine) condition, and linked into the system FEK chain.
D8EFGN: MOVEI T2,FEKLEN ;LENGTH OF ETHERNET-STYLE FEK BLOCK
PUSHJ P,GETZWD## ;GET SOME ANF FREE CORE
POPJ P, ;NO FREE CORE, NO FEK BLOCK
PUSH P,J ;SAVE MASTER FEK INDEX
MOVE J,T1 ;SET ADDRESS OF NEWLY-CREATED FEK
PUSHJ P,NTFONC## ;"ONCE-ONLY" THE FEK
MOVE T1,J ;REPOSITION NEWLY-CREATED FEK ADDRESS
POP P,J ;AND RESTORE MASTER/PROTOTYPE FEK ADDRESS
MOVSI T2,FK.ETH ;THE "ETHERNET-FEK" FLAG
IORM T2,FEKBLK(T1) ;PUT OUR BRAND ON THIS LITTLE FEKKY
MOVE T2,FEKDSP(J) ;PROTOTYPE "DISP" WORD
MOVEM T2,FEKDSP(T1) ;SET FEK DISPATCH SERVICE VECTOR
MOVE T2,FEKUNI(J) ;PROTOTYPE CPU/UNIT WORD
MOVEM T2,FEKUNI(T1) ;SET FEK CPU AND UNIT WORD
SETZM FEKNIA(T1) ;NO ETHERNET REMOTE ADDRESS YET
SETZM FEKNIA+1(T1) ;NO ETHERNET REMOTE ADDRESS YET
SETZM FEKNIC(T1) ;NO ETHERNET CHANNEL ID YET
SETZM FEKNID(T1) ;NO ETHERNET PORTAL ID YET
MOVEM J,FEKNIF(T1) ;SET MASTER/PROTOTYPE FEK ADDRESS
SETZM FEKNIS(T1) ;NO ETHERNET NODEID SERIAL NUMBER
MOVE T2,%NTNIJ## ;ETHERNET SLAVE FEK KEEP-ALIVE TIMER
MOVEM T2,FEKNIT(T1) ;INITIALIZE KEEP-ALIVE FOR THIS FEK
NETOFF ;PREVENT RACES
HRRZ T2,FEKBLK(J) ;ADDRESS OF REST OF FEK CHAIN
HRRM T1,FEKBLK(J) ;SPLICE NEWLY-CREATED FEK INTO
HRRM T2,FEKBLK(T1) ;SYSTEM FEK CHAIN
NETON ;OK TO RACE NOW
JRST CPOPJ1## ;SUCCESS RETURN WITH T1/FEK
;D8EGUN - Get a "UN" block
;Call is:
;
; PUSHJ P,D8EGUN
; Error return
; Normal return
;
;On error return no UN blocks are available (and no network free core
;is available to make a new UN block).
;
;On normal return register P4 contains a UN block address.
;
;D8EGUN co-routines back to its caller, and automatically frees up the
;UN block on caller's exit.
;
;Uses T1.
D8EGUN: PUSH P,P4 ;SAVE CALLER'S P4 FOR LATER RESTORATION
NETOFF ;MUST DO THIS INTERLOCKED
SKIPN P4,D8EUNL ;GOT A FREE UN BLOCK?
JRST D8EGU4 ;NO, MUST ALLOCATE ONE
MOVE T1,(P4) ;YES, ADDRESS OF NEXT UN BLOCK IN FREE LIST
MOVEM T1,D8EUNL ;IT BECOMES THE NEW FIRST FREE
NETON ;SAFE NOW
D8EGU1: SETZM (P4) ;CLEAR FIRST WORD OF UN BLOCK
MOVSI T1,(P4) ;CONCOCT
HRRI T1,1(P4) ; A BLT POINTER TO
BLT T1,UN.LEN-1(P4) ; CLEAR OUT THE UN BLOCK
AOS -1(P) ;"SKIP"
PUSHJ P,@-1(P) ; CO-ROUTINE BACK TO OUR CALLER
CAIA ;NON-SKIP RETURN TO CALLER'S CALLER
AOS -2(P) ;SKIP RETURN TO CALLER'S CALLER
NETOFF ;MUST INTERLOCK AGAIN
EXCH P4,D8EUNL ;PUT THIS UN BLOCK AT HEAD OF CHAIN
MOVEM P4,@D8EUNL ;AND LINK REST OF CHAIN TO NEW FIRST FREE
NETON ;SAFE AGAIN
POP P,P4 ;RESTORE CALLER'S P4
POP P,(P) ;POP JUNK ADDRESS
POPJ P, ;RETURN TO CALLER'S CALLER
;HERE TO ALLOCATE A NEW UN BLOCK
D8EGU4: NETON ;RESTORE INTERRUPTABILITY
PUSHJ P,D8EGUX ;GET A CORE BLOCK
JRST P4POPJ## ;RESTORE P4 AND ABORT
JRST D8EGU1 ;RETURN UN BLOCK TO CALLER
D8EGUX: PUSHJ P,SAVT## ;SAVE CALLER'S TEAS
MOVEI T2,UN.LEN ;LENGTH OF A UN BLOCK
PUSHJ P,GETZWD## ;ASK NETSER FOR A HUNK OF MEMORY
POPJ P, ;NONE AVAILABLE
MOVE P4,T1 ;GOT ONE!
JRST CPOPJ1## ;SUCCESSFUL RETURN (RESTORING THE TEAS)
;D8EOOF - Set FEK ON- or OFF-LINE
;Call is:
;
; MOVX T2,<UN>
; PUSHJ P,D8EOOF
; Error return
; Normal return
;
;Where <UN> is the address of the "UN" block to/from ETHSER.
;
;Error return is not used.
;
;On normal return the FEK block is "ON-" or "OFF-LINE" based on the
;state of the "RUN" bit in the UN status word. T1 contains the FEK
;status-change flag, 0 if no change in FEK status, FK.ONL if the FEK
;changed ON/OFF status.
D8EOOF: MOVSI T1,FK.ONL ;THE FEK "ON-LINE" FLAG
TMNN UNRUN,(T2) ;IS "RUN" BIT SET IN UN STATUS FIELD?
JRST D8EOO3 ;NO, "OFF-LINE"
TDNE T1,FEKBLK(J) ;YES, "ON-LINE", WAS FEK ALREADY ON-LINE?
JRST D8EOO5 ;NO CHANGE, JUST RETURN
IORM T1,FEKBLK(J) ;WAS OFF-LINE, FLAG ON-LINE
JRST CPOPJ1## ;AND TAKE SUCCESSFUL RETURN WITH CHANGE FLAG
D8EOO3: TDNN T1,FEKBLK(J) ;WAS FEK ALREADY OFF-LINE?
JRST D8EOO5 ;NO CHANGE, JUST RETURN
ANDCAM T1,FEKBLK(J) ;WAS ON-LINE, FLAG OFF-LINE
JRST CPOPJ1## ;AND TAKE SUCCESSFUL RETURN WITH CHANGE FLAG
D8EOO5: SETZ T1, ;FLAG NO CHANGE IN FEK STATUS
JRST CPOPJ1## ;AND TAKE SUCCESSFUL RETURN
$LIT
$LOW
D8EUNL: BLOCK 1 ;FIRST FREE "UN" BLOCK
D8EBLK: BLOCK RSBLEN ;RANDOM SCRATCH BLOCK
D8EEND::!END