:TITLE[ENXTask];
%
Fiala 9 March 1982: eliminate OnPage[EtherInitPage], reformat; absorb
ENXDefs; use macro to eliminate all the duplicate register defs;
eliminate pENXNotifyReg/2/3.
HGM, November 29, 1981 10:57 PM, Patch for wakeup confusion
HGM, November 28, 1981 8:02 PM, Make IOCB Chaining Atomic
HGM, November 27, 1981 10:40 PM, Late Collision trap
HGM, November 20, 1981 3:26 PM, Merge into Fiala’s world
HGM, October 27, 1981 4:22 PM, Use last few words in buffer
HGM, October 5, 1981 1:09 AM, Fixup Input end test
HGM, June 24, 1981 1:46 AM, Expand Runt filter to 4 data words
HGM, June 24, 1981 12:22 AM, Patch for wakeup pipeline delay
HGM, June 16, 1981 8:03 PM, Interlock xxIndex @ xxSetup
HGM, May 27, 1981 12:51 PM, Merge dispatch tables
HGM, May 25, 1981 6:49 PM, massive fiddling
HGM, May 21, 1981 8:15 PM, IOATTEN vs Fault task fixes
HGM, May 7, 1981 11:02 PM, subtract off extra byte (anti dribble hardware change)
HGM, April 23, 1981 10:18 PM, Rubiconize and whatever

HGM, From Tom Henning’s version of January 8, 1981 2:52 PM
%
Set[ZeroModFour,And[enxTask,14]];
SetTask[ZeroModFour];

%RM definitions
The ENXReg macro defines a register for task ZeroModFour which will be used
in the program, and the addressing mechanism will automatically displace
references to another group of 20b registers as appropriate for whatever
task is running. In addition, ENXReg defines offset names for each of the enx
tasks for use when referencing the registers from Midas.

The names defined are: ENX#1, ENX1#1, ENX2#1, and ENX3#1, where one of the
last three names is suppressed if coincident with ENX#1.
%
Macro[TSReg,IFE[And[#1,3],0,,RV[enx#2,Add[LShift[And[#1,3],4],#3]]]];
Macro[ENXReg,(
TSReg[enxTask,1#1,#2],
TSReg[enxTask2,2#1,#2],
TSReg[enxTask3,3#1,#2],
RV[enx#1,#2])];

ENXReg[Temp1,0];
*quad temporary registers, also used by NotifyInterrupt
ENXReg[Temp2,1];
*quad temporary registers, also used by NotifyInterrupt
ENXReg[Temp3,2];
*quad temporary registers
ENXReg[Temp4,3];
*quad temporary registers

ENXReg[RcvIndex,4];
*receive buffer displacement, points to next word in buffer
ENXReg[RcvCount,5];
*receive buffer word counter, words left in buffer
ENXReg[RcvPtr,6];
*low base register pointer to receive buffer
ENXReg[RcvPtrHi,7];
*high base register pointer to receive buffer

ENXReg[XmtIndex,10];
*transmit buffer displacement, points to next word in buffer
ENXReg[XmtCount,11];
*transmit buffer word counter, words left in buffer
ENXReg[XmtPtr,12];
*low base register pointer to transmit buffer
ENXReg[XmtPtrHi,13];
*high base register pointer to transmit buffer

ENXReg[CSBPtr,14];
*short pointer to CSB
ENXReg[CSBPtrHi,15];
*high base pointer to CSB, set to zero
ENXReg[Link,16];
*linking register for saving APC&APCTASK
ENXReg[Temp,17];
*temporary register

*Additional overlay R Register definitions

ENXReg[InitialCSB,0];
*CSB pointer passed in this reg from initialize.mc

ENXReg[ICBPtr,2];
*Short pointer to ICB
ENXReg[OCBPtr,2];
*Short pointer to OCB

ENXReg[DestID1,0];
*Input Packet DestinationID, first word
ENXReg[DestID2,1];
*Input Packet DestinationID, second word
ENXReg[DestID3,2];
*Input Packet DestinationID, third word
ENXReg[DestID4,3];
*Input Packet, fourth word

ENXReg[NextICB,4];
*Short pointer to next ICB
ENXReg[HostID1,5];
*First word of HostID -- Sign bit on if promiscious
ENXReg[HostID2,6];
*Second word of HostID
ENXReg[HostID3,7];
*Third word of HostID

ENXReg[XmtMask,10];
*Retransmission mask
ENXReg[XmtIntLo,10];
*Retransmission interval, low bits
ENXReg[XmtIntHi,11];
*Retransmission interval, high bits

ENXReg[ZeroBase,15];
*used to access ICB’s and OCB’s, value is always zero

ENXReg[Completion,17];
*Completion word

*IOCB pointers

MC[ENXIndexNext,0];
*Short Pointer to next ICB or OCB
MC[ENXIndexXmtMask,1];
*Retransmission Mask
MC[ENXIndexCompletion,3];
MC[ENXIndexBuffer,4];
*Long pointer and length (Quadword aligned)

*CSB pointers

MC[ENXIndexOCB,0];
*current OCB pointer
MC[ENXIndexIWake,2];
*Wakeup Bitmask
MC[ENXIndexOWake,1];
*Wakeup Bitmask
MC[ENXIndexICB,4];
*current ICB pointer
MC[ENXIndexNoICBCount,3]; *Count of times when no ICB chain (For test only)
MC[ENXIndexScratch,10];
*Quadword scratch area

*Controller Hardware Input Registers address definitions

Set[ENXDevID,0];
*Device I.D. number
Set[ENXStatus1,1];
*Status1 register
Set[ENXXmtStatus,15];
*Status1 register, sets Mode Latch to Xmt Mode
Set[ENXRcvStatus,11];
*Status1 register, resets Mode Latch to Rcv Mode
Set[ENXStatus2,2];
*Status2 register
Set[ENXReadState,2];
*read State register, via Status2 register
Set[ENXExcessCount,2];
*Excess Count, via Status2 register
Set[ENXInData,3];
*Input data for INPUT operation
Set[ENXIData,Add[LShift[ZeroModFour,4],3]]; *Input data for IOSTORE16 operation

*Controller Hardware Output Registers address definitions

Set[ENXResetState,0];
*General Reset
Set[ENXIState,1];
*InState register
Set[ENXOState,2];
*OutState register
Set[ENXOutData,4];
*Output data for OUTPUT operation
Set[ENXOData,Add[LShift[ZeroModFour,4],4]]; *Output data for IOFETCH16 operation

*Status bit definitions

Set[ENXSIBA,4000];
*Input Bad Alignment
Set[ENXSIORun,2000];
*Input overrun
Set[ENXSIBadPkt,1000];
*Input Bad Packet
Set[ENXSICRC,400];
*Input Bad CRC
Set[ENXSOPAR,200];
*Output Bad Parity
Set[ENXSOURun,100];
*Output Underrun
Set[ENXSOCOLL,40];
*Transmitter-detected collision (Collision)
Set[ENXSOFAULT,20];
*Output Data Fault

MC[ENXISMask,OR[ENXSIBadPkt,ENXSIORun,ENXSICRC,ENXSIBA]];
*Status bits bits
MC[ENXOSMask,OR[ENXSOURun,ENXSOCOLL,ENXSOFAULT,ENXSOPAR]];
*Output Status bits
MC[ENXOCollisionMask,ENXSOCOLL];

*Completion codes

MC[ENXGoodPacket,040000];
MC[ENXErrorZeroBuf,064000];
*On input (length<3) and output (length=0)
MC[ENXPktBufOverrun,061000];
*Input only
MC[ENXErrorCountOV,062000];
*Output only
MC[ENXLateCollision,063000];
*Output only


*State Register command words

MC[ENXResetAll,0];
*Resets InState and OutState registers

*InState Register command words

MC[ENXDisableInput,Or[LShift[1,14],0]];
*Resets: Enable Input
MC[ENXEnableInput,Or[LShift[11,14],0]];
*Sets: Enable Input
MC[ENXSetPurgeMode,Or[LShift[15,14],0]];
*Sets: Enable Input, PurgeMode

*OutState Register command words

MC[ENXSetOutputEOP,340];
*Sets: Enable Output, OutputEOP
MC[ENXEnableOutput,300];
*Sets: Enable Output, Output Buffer
MC[ENXDisableOutput,0];
*Clears: Output state register, Reset Output Buffer
MC[ENXResetBuffer,200];
*Clears: Reset Output Buffer

*Preamble word constant definition

MC[ENXPream,125];
*Ethernet II preamble byte (0101010101010101)
MC[ENXPreamLast,200];
*to set bit for last preamble word (0101010111010101)

*Timer definitions

SetTask[TTask];
Set[TimerRunning,5];
*State 5 is simple timer
Set[TimerIdle,4];
*State 4 is idle
MC[ENXTimerMask,LShift[TimerRunning,14]];
MC[ENXIdleTimer,LShift[TimerIdle,14]];
MC[pENXRandomReg,IP[ClockLo]];
* random number

%
*CSB definitions

00
Short pointer to First OCB
01
Output wakeup bit mask
02
Input wakeup bit mask
03
Packets missed (for debugging)

04
Short pointer to First ICB
05
Host Address (MSB)
06
Host Address
07
Host Address (LSB)

10
Quadword scratch buffer
11
Quadword scratch buffer
12
Quadword scratch buffer
13
Quadword scratch buffer

14
Used by test program
15
Used by test program
16
Used by test program
17
Used by test program

*ICB definitions

00
Link to next ICB
01
Unused
02
Unused
03
Completion word (Ending Status)

04
Bytes used in buffer
05
Length of buffer (number of bytes)
06
Low pointer to buffer
07
High pointer to buffer

*OCB definitions

00
Link to next OCB
01
Retransmission Mask
02
Unused
03
Completion word (Ending Status)

04
Unused
05
Length of buffer (number of bytes)
06
Low pointer to buffer
07
High pointer to buffer
%

SetTask[ZeroModFOur];

******** INITIALIZATION MICROCODE ********

*Microcode is notified at ENXInitLoc (from Initialize) to setup things.

ENXInitSetup:
ENXTemp3 ← IP[enxNotify]C, GoTo[ENXInitMain], At[ENXInitLoc];
ENXTemp3 ← IP[enxNotify2]C, GoTo[ENXInitMain], At[ENXInitLoc2];
ENXTemp3 ← IP[enxNotify3]C, GoTo[ENXInitMain], At[ENXInitLoc3];

ENXInitMain:
T ← (SStkP&NStkP) xor (377C);
StkP ← ENXTemp3, ENXTemp3 ← T, NoRegILockOK;
*Compute value for ENXONotify register, used for notify after timer wakeup.
T ← (CTask&NCIA) and (170000C);
ENXTemp ← T;
ENXTemp ← (ENXTemp) or (LoA[ENXOTimerDoneLoc]);
T ← ENXTemp ← (ENXTemp) or (HiA[ENXOTimerDoneLoc]);
Stack ← T;
StkP ← ENXTemp3;
T ← ENXInitialCSB;
ENXCSBPtr ← T;
LoadPage[ENXPage];
ENXCSBPtrHi ← 0C, GoToP[ENXReset];*Also zeroes ENXZeroBase

*Microcode is notified at ENXReset (from StartIO) to reset things.

OnPage[ENXPage];

ENXReset:
Input[ENXTemp,ENXXmtStatus], At[ENXStartLoc];
ENXTemp ← ENXResetAll;
Output[ENXTemp, ENXResetState];
ENXXmtPtrHi ← 1C; *See comments at ENXOTimerDone
ENXXmtIntLo ← ENXIdleTimer, Call[ENXLoadTimer];
Call[ENXSetupLink];

%Sleep here until awaken by by an Input or Output wake request
microcode is set to Recv Mode whenever microcode is re-initialized

******** INPUT MICROCODE ********

set Purge Mode
We get here for 3 reasons:
1) The filter rejected this packet,
2) We wanted it, but didn’t have an IOCB setup yet, or
3) We just finished processing a packet.
%
ENXIPurge:
ENXTemp ← ENXSetPurgeMode, Call[ENXWriteInState];
*Call[ENXRet]; *Pipeline delay krock
*Idle state of input microcode
*Wait for the next packet to arrive
*or the driver to poke the output hardware
*or the the timer to go off and poke the output hardware
Input[ENXTemp,ENXStatus1], Call[ENXDispatcher];


* ADDRESS RECOGNITION:
*
Check if the packet,
*
is explicitly for this host (dest=us),
*
or is broadcast (dest bit 7 set),
*
or if we are promiscuous (sign bit of word is on).

ENXIBegin:

IOStore4[ENXCSBPtr,ENXIData,ENXIndexScratch!], Call[ENXRet];
PFetch4[ENXCSBPtr,ENXNextICB,ENXIndexICB!], Call[ENXRet]; *Host Address
T ← ENXHostID1;
PFetch4[ENXCSBPtr, ENXDestID1, ENXIndexScratch!], Skip[ALU>=0];
LU ← ENXNextICB, GoTo[ENXIForMe];*sign bit on => we are promiscuous

T ← LCy[ENXDestID1,7];
T ← (LCy[ENXHostID1,7]) xor T, Skip[ALU>=0];
LU ← ENXNextICB, GoTo[ENXIMulticast];*Multicast Mode

T ← ENXDestID2, Skip[ALU=0];
GoTo[ENXIPurge];*First word didn’t match

T ← (ENXHostID2) xor T;
T ← ENXDestID3, Skip[ALU=0];
GoTo[ENXIPurge];*Second word didn’t match

T ← (ENXHostID3) xor T;
LU ← ENXNextICB, Skip[ALU=0];
GoTo[ENXIPurge];*Third word didn’t match

ENXIMulticast:
*take all Multicast for now
ENXIForMe:
GoTo[ENXIRead,ALU#0];

* No buffer for this packet, bump counter for debugging
PFetch1[ENXCSBPtr,ENXTemp,ENXIndexNoICBCount!];
ENXTemp ← (ENXTemp) + 1;
PStore1[ENXCSBPtr,ENXTemp,ENXIndexNoICBCount!], GoTo[ENXIPurge];

ENXIRead:
T ← (ENXNextICB) + (ENXIndexBuffer), Call[ENXRcvSetup];
LU ← (ENXRcvCount) - (22C);
* (4 for end test, 4 for first clump) * 2 for bytes
ENXRcvCount ← (ENXRcvCount)-(20C), Skip[ALU>=0];
T ← ENXCompletion ← ENXErrorZeroBuf, GoTo[ENXIMark];

* Hackery for off-by-one buffer alignment
PStore1[ENXRcvPtr, ENXDestID1, 0];
PStore2[ENXRcvPtr, ENXDestID2, 1], ODDOK;
PStore1[ENXRcvPtr, ENXDestID4, 3], Call[ENXRet];
Input[ENXTemp, ENXInData];
PStore1[ENXRcvPtr, ENXTemp, 4], Call[ENXRet];
ENXRcvCount ← (ENXRcvCount)-(2C);
(ENXRcvIndex) ← (ENXRcvIndex)+(1C);

* Hackery to test Overrun:
*
ENXTemp ← 10000C, Call[ENXRet];
*
ENXTemp ← (ENXTemp)-1, Skip[R<0];
*
GoTo[ENXRet];
*
NOP;

ENXIReLoop:
Call[ENXRet];* set up TPC for data transfer loop

ENXILoop:
ENXRcvCount ← (ENXRcvCount) - (10C), Skip[R>=0];
T ← ENXRcvIndex ← (ENXRcvIndex) + (4C), GoTo[ENXIFull];
T ← ENXRcvIndex ← (ENXRcvIndex) + (4C);
IOStore4[ENXRcvPtr,ENXIData];
ENXRcvCount ← (ENXRcvCount) - (10C), Skip[R>=0];
T ← ENXRcvIndex ← (ENXRcvIndex) + (4C), GoTo[ENXIFull];
T ← ENXRcvIndex ← (ENXRcvIndex) + (4C), GoTo[ENXILoopAttn,IOAtten’];
IOStore4[ENXRcvPtr,ENXIData], Return;

ENXILoopAttn:
Input[ENXTemp,ENXStatus1], Call[ENXDispatcher];
T ← ENXRcvIndex;
IOStore4[ENXRcvPtr,ENXIData], GoTo[ENXIReLoop];

*Finish up the receive packet
*Calculate packet length
ENXIEnd:
Input[ENXTemp1,ENXExcessCount], At[ENXDispTableLoc,1];
T ← LdF[ENXTemp1,13,5], Call[ENXRet];* Get excess byte count
ENXRcvIndex ← LSh[ENXRcvIndex,1];* change words to bytes
ENXRcvIndex ← (ENXRcvIndex) - T;*adjust total byte count

*Without this test, tiny packets look huge because of underflow
*Spec calls for min of 60 bytes (30 words)
LU ← (ENXRcvIndex) - (15C);*8 bytes is 4 data words
ENXRcvIndex ← (ENXRcvIndex) - (5C), Skip[ALU>=0]; * 4 CRC and 1 mumble
GoTo[ENXIPurge];

Input[ENXTemp2, ENXRcvStatus];
T ← ENXTemp2 ← (ENXTemp2) and (ENXISMask);
ENXCompletion ← T, Skip[ALU#0];
ENXCompletion ← ENXGoodPacket, GoTo[ENXIMark];

*Buffer is full. Check for end of packet before posting error.
ENXIFull:
Input[ENXTemp,ENXStatus1], Call[ENXDispatcher];
T ← ENXCompletion ← ENXPktBufOverrun, GoTo[ENXIMark];

ENXIMark:
ENXCompletion ← (ENXCompletion) or T, Call[ENXFetchICB];
T ← (ENXICBPtr) + (ENXIndexCompletion), Call[ENXStoreCompletion];
T ← (ENXICBPtr) + (ENXIndexBuffer), Call[ENXStoreWordsUsed];

T ← ENXIndexIWake, Call[ENXInterrupt];

T ← (ENXICBPtr) + (ENXIndexNext), Call[ENXChainToNextIOCB];
PStore1[ENXCSBPtr,ENXICBPtr,ENXIndexICB!], GoTo[ENXIPurge];
******** OUTPUT MICROCODE ********


ENXSetupLink:
UseCTask;
T ← APC&APCTASK;
ENXLink ← T, GoTo[ENXOIdle];

ENXNoWork:
ENXTemp ← ENXDisableOutput, Call[ENXWriteOutState];
*Call[ENXRet]; * Pipeline delay krock
* Idle state of output microcode.
* on a wakeup, is it a transmit wake or receive wake?
ENXOIdle:
Input[ENXTemp, ENXStatus1], Call[ENXDispatcher];
PFetch1[ENXCSBPtr, ENXOCBPtr, ENXIndexOCB!], GoTo[ENXOCheckNext];

ENXOCheck:
T ← (ENXOCBPtr)+(ENXIndexXmtMask), Skip[ALU#0];
* ALU=0 => no IOCB, no packet to transmit
GoTo[ENXNoWork];

OddPFetch1[ENXZeroBase,ENXXmtMask], Call[ENXRet];
ENXXmtMask ← (LSh[ENXXmtMask,1])+1, Skip[R>=0]; * R<0 => overflow
T ← ENXCompletion ← ENXErrorCountOV, GoTo[ENXOMark];
OddPStore1[ENXZeroBase,ENXXmtMask];

%
We are about to start sending a packet. In order to help hosts with half
duplex interfaces avoid missing packets in the window behind a packet we
just sent, we would like to delay for 1ms or so if we are sending them
another packet. We approximate that by always waiting.
Taft+Dorado do it a bit better by remembering the time that the last packet
finished getting sent. Maybe we should do that too.
%
NOP; *Aviod bypass kludge
ENXOWait:
T ← (ENXXmtMask) xor (1C);*is this the first attemp to transmit?
Skip[ALU#0];
ENXXmtIntLo ← 24C, GoTo[ENXOCountdown];*yes, count 1 msec, 24B is 20D = 1000/50

*Compute countdown interval, Use high resolution clock for random number
T ← GETRSPEC[103] XOR (377C);*Stkp (reads inverted)
ENXTemp ← pENXRandomReg;*point to "random" register
StkP ← ENXTemp, ENXTemp ← T, NoRegILockOK;

*Form new transmission interval mask, check if old has overflowed

T ← CTask;*in case we have 2 boards on the same machine
T ← (LdF[Stack,6,12]) xor T;*Get low 10 bits
StkP ← ENXTemp;*Restore stack-pointer
ENXXmtIntLo ← (RSH[ENXXmtMask,1]) and T;*Mask random number with old value
*ENXXmtIntLo now has (10bit) number of Ethernet ticks to wait.

%
We need a tick size of 512 bit times. The timers on the D0
have a basic tick size of 64 times the clock speed. If the clock is 100ns,
thats 6.4 microsec. 8*6.4 is 51.2. How convient.
8=2↑3, so thats why there is this LSH by 3.
%
ENXOCountdown:
ENXXmtIntLo ← LSH[ENXXmtIntLo,3], Skip[ALU#0];
GoTo[ENXOGo];

*At this point, ENXXmtIntLo contains the number of D0 ticks to wait. (13 bits)
*Unfortunately, that doesn’t fit into a simple timer.
*We process the overflow with software.
*The timer holds 7 bits, but we use only 6 to save an instruction.
ENXXmtPtrHi ← 0C;
T ← (LdF[ENXXmtIntLo,3,7]);
ENXXmtIntHi ← T, Call[ENXRet];
ENXXmtIntLo ← LdF[ENXXmtIntLo,12,6];

ENXOLoadTimer:
ENXXmtIntLo ← LSh[ENXXmtIntLo,4];
ENXXmtIntLo ← (ENXXmtIntLo) or (ENXTimerMask), Call[ENXLoadTimer];
*Call[ENXRet]; * Pipeline delay krock

*Get here if a packet arrives or the timer goes off and enables output
*(or a buggy driver enables output)
ENXODisp:
Input[ENXTemp,ENXStatus1], Call[ENXDispatcher];
*Return here when output enabled by timer

*Glitch Chasing
Input[ENXXmtIntLo, ENXStatus2];
LU ← (ENXXmtIntLo) AND (100000C); * Reset Output’
Skip[ALU=0];
PFetch1[ENXCSBPtr, ENXOCBPtr, ENXIndexOCB!], GoTo[ENXOGo];
*Barf, input got turned off too late to back put of the pipeline
*This happens if the tail of a packet was generating wakeups and
*PktGap gets reset when another packet arrives.
NOP; * Allocation
Call[ENXRet];
GoTo[ENXODisp];
*Glitch Chasing

*
PFetch1[ENXCSBPtr, ENXOCBPtr, ENXIndexOCB!], GoTo[ENXOGo];

*Timer has expired, Timer task notifies at ENXOTimerDoneLoc. Note that we
*can’t smash our TPC, or any temp RM registers, or T because we may be in
*the middle of receiving a packet.

ENXOTimerDone:

*ENXXmtPtrHi=0 indicates we are expecting a Timer to go off.
*There is a race condition when trying to kill timers,
* and/or a buggy driver might wake us up at a bad time.
*Even if we are transferring into the first 64K, it will be non zero.

LU ← ENXXmtPtrHi, At[ENXOTimerDoneLoc];
ENXXmtIntHi ← (ENXXmtIntHi) - 1, Skip[ALU=0];
PleaseTellHGMAboutThis: *This is possible, but very unlikely
BreakPoint, ENXXmtIntHi ← (ENXXmtIntHi) + 1, Return;

*Check if still more time to elapse before start of transmission.
ENXXmtIntLo ← (2000C), Skip[ALU<0]; *2000C is 100C LSH 4
ENXXmtIntLo ← (ENXXmtIntLo) or (ENXTimerMask), GoTo[ENXLoadTimer];
ENXXmtIntLo ← ENXEnableOutput, GoTo[ENXWriteOutStateViaXmtIntLo];

*Load timer from data in ENXXmtIntLo, and disable the output hardware.
*Note: There must be at least 14 cycles between timer instructions.
*This code must be coordinated with Timer.mc and anything else that loads timers.
*Note that we ABORT for a long time after the OUTPUT.

ENXLoadTimer:
ENXXmtPtr ← T;
T ← CTask;*Timer slot is same as task number.
ENXXmtIntLo ← (ENXXmtIntLo) or T;
T ← ENXXmtPtr;
LoadTimer[ENXXmtIntLo];
ENXXmtIntLo ← ENXDisableOutput;
ENXWriteOutStateViaXmtIntLo:
Output[ENXXmtIntLo, ENXOState];
ENXXmtIntLo ← ENXXmtIntLo, GoTo[ENXRet]; * Wakeup Pipeline


*Countdown interval is over
*Start to send words to the hardware. The hardware will start
*the transmission to the Wire as soon as the buffer has
*> 128 words in the hardware buffer, or the OutputEOP bit
*is set (for a packet of less than 127 words)

ENXOGo:
T ← (ENXOCBPtr) + (ENXIndexBuffer), Call[ENXXmtSetup];
*Hackery below sends at least 5 words. (spec calls for 30)
T ← (ENXXmtCount) - (13C);
T ← (ENXOCBPtr) + (ENXIndexXmtMask), Skip[ALU>=0];
T ← ENXCompletion ← ENXErrorZeroBuf, GoTo[ENXOMark]; *Buffer too small

*Send 4 word preamble (hardware doesn’t compute CRC on first 4 words sent)
T ← ENXTemp ← ENXPream;
ENXTemp ← (LSh[ENXTemp,10]) or T;
Output[ENXTemp,ENXOutData], Call[ENXILockENXTemp];
Output[ENXTemp,ENXOutData], Call[ENXILockENXTemp];
Output[ENXTemp,ENXOutData], Call[ENXILockENXTemp];
ENXTemp ← (ENXTemp) or (ENXPreamLast);
Output[ENXTemp,ENXOutData], Call[ENXILockENXTemp];

** Hackery for off-by-one buffer alignment
PFetch1[ENXXmtPtr,ENXTemp,0];
ENXXmtIndex ← (ENXXmtIndex) + 1;
ENXXmtCount ← (ENXXmtCount) - (2C);
LU ← ENXTemp;
Output[ENXTemp,ENXOutData], Call[ENXILockENXTemp];

ENXXmtCount ← (ENXXmtCount) - (11C);
ENXXmtIndex ← (ENXXmtIndex) - (4C);
ENXOReLoop:
Call[ENXRet];* Leaves TPC setup for ENXOLoop
ENXOLoop:
* Do 12 word transfers to experiment with Overruns
*
ENXXmtCount ← (ENXXmtCount) - (10C), Skip[R>=0];
*
ENXXmtCount ← (ENXXmtCount) + (20C), GoTo[ENXOLastXfer];
*
T ← ENXXmtIndex ← (ENXXmtIndex) + (4C);
*
IOFetch4[ENXXmtPtr,ENXOData];
ENXXmtCount ← (ENXXmtCount) - (10C), Skip[R>=0];
ENXXmtCount ← (ENXXmtCount) + (20C), GoTo[ENXOLastXfer];
T ← ENXXmtIndex ← (ENXXmtIndex) + (4C);
IOFetch4[ENXXmtPtr,ENXOData];
ENXXmtCount ← (ENXXmtCount) - (10C), Skip[R>=0];
ENXXmtCount ← (ENXXmtCount) + (20C), GoTo[ENXOLastXfer];
T ← ENXXmtIndex ← (ENXXmtIndex) + (4C), GoTo[ENXOLoopAttn,IOAtten’];
IOFetch4[ENXXmtPtr,ENXOData], Return;

ENXOLoopAttn:
IOFetch4[ENXXmtPtr,ENXOData];
Input[ENXTemp,ENXStatus1], Call[ENXDispatcher];
GoTo[ENXOReLoop];

*Normal exit from Output Loop is here

ENXOLastXfer:
ENXXmtCount ← (ENXXmtCount) xor (377C);
ENXXmtCount ← (ENXXmtCount) and (37C);
ENXXmtCount ← (ENXXmtCount) or (ENXSetOutputEOP);
Output[ENXXmtCount,ENXOState], Call[ENXILockENXXmtCount];
T ← (ENXXmtIndex) ← (ENXXmtIndex) + (4C);
IOFetch4[ENXXmtPtr,ENXOData], IOStrobe, Call[ENXRet];

*wakeup service requested when the packet is fully transmitted
*or a packet arrives.
ENXOAttn:
Input[ENXTemp,ENXStatus1], Call[ENXDispatcher]; * Shouldn’t return
BreakPoint;

ENXOEnd:
Input[ENXTemp,ENXStatus1], At[ENXDispTableLoc,5];
Call[ENXFetchOCB];
*Beware: ENXCompletion is the same as ENXTemp
T ← ENXTemp ← (ENXTemp) and (ENXOSMask);
LU ← (ENXTemp) xor (ENXOCollisionMask), Skip[ALU#0];
ENXCompletion ← ENXGoodPacket, GoTo[ENXOMark];*No errors
LU ← (ENXXmtIndex) - (250C), Skip[ALU=0];
ENXCompletion ← T, GoTo[ENXOMark];*Some error other than collision
*Collision only, check for a late one (hardware debugging)
*250C is 128 for buffer, 512bits=32 wds for collision, 8 slop
T ← (ENXOCBPtr) + (ENXIndexBuffer), Skip[ALU>=0];
GoTo[ENXOFlap];
ENXXmtIndex ← LSh[ENXXmtIndex,1];
OddPStore1[ENXZeroBase,ENXXmtIndex];
T ← ENXCompletion ← ENXLateCollision;

ENXOMark:
ENXCompletion ← (ENXCompletion) or T;
T ← (ENXOCBPtr)+(ENXIndexCompletion), Call[ENXStoreCompletion];
T ← ENXIndexOWake, Call[ENXInterrupt];

ENXONextBuf:
T ← (ENXOCBPtr) + (ENXIndexNext), Call[ENXChainToNextIOCB];
PStore1[ENXCSBPtr,ENXOCBPtr,ENXIndexOCB!];

*Flap output enable to tell hardware that we have finished processing this packet
ENXOFlap:
ENXTemp ← ENXDisableOutput;
Output[ENXTemp,ENXOState];
ENXTemp ← ENXTemp;
ENXTemp ← ENXEnableOutput, Call[ENXWriteOutState];

ENXOCheckNext:
LU ← ENXOCBPtr, GoTo[ENXOCheck];

******** SUBROUTINES ********


ENXDispatcher:
UseCTask;
T ← APCTask&APC;
Dispatch[ENXTemp,14,3];
Disp[ENXDispTable];

ENXDispTable:

*000 Receive Data wakeup from idle state (no Atten)
ENXRet:
Return, At[ENXDispTableLoc,0];

*ENXIEnd has an At[ENXDispTableLoc,1];
*001Receive End-of-packet

*010 Output Packet Done
Input[ENXTemp,ENXXmtStatus], At[ENXDispTableLoc,2];
ENXLink ← T, GoTo[ENXOEnd];

*011 Transmit Data Wake Request
Input[ENXTemp,ENXXmtStatus], At[ENXDispTableLoc,3];
ENXSwitch:
APCTask&APC ← ENXLink, ENXLink ← T, NoRegILockOK, GoTo[ENXRet];


*100 Transmit Data wakeup from idle state or timer wait (no Atten)
Return, At[ENXDispTableLoc,4];

*101 Output Packet Done
*
ENXOEnd has an At[ENXDispTableLoc,5];

*110 Receive End-of-Packet
Input[ENXTemp,ENXRcvStatus], At[ENXDispTableLoc,6];
ENXLink ← T, GoTo[ENXIEnd];

*111 Receive Data Wake Request
Input[ENXTemp,ENXRcvStatus], GoTo[ENXSwitch], At[ENXDispTableLoc,7];


*Notify the driver by generating appropiate interrupts.
*This could be merged with ENXChainToNextIOCB,
* but that would be too long between TASKs.
*
T=ENXIndexIWake notifies receive software with receive bit mask
*
T=ENXIndexOWake notifies transmit software with transmit bit mask

ENXInterrupt:
PFetch1[ENXCSBPtr,ENXTemp];*Fetch wakeup bitmask
LoadPage[NotifyInterruptPage];
T ← ENXTemp, GoToP[NotifyInterrupt];

* Head assumes that this is Atomic.
ENXChainToNextIOCB:
* assumes that ENXICBPtr is the same as ENXOCBPtr
OddPFetch1[ENXZeroBase,ENXOCBPtr];
LU ← ENXOCBPtr, UseCTask, GoTo[ENXRet];


ENXWriteInState:
Output[ENXTemp,ENXIState], GoTo[ENXILockENXTemp];

ENXWriteOutState:
Output[ENXTemp,ENXOState], GoTo[ENXILockENXTemp];

ENXILockENXTemp:
ENXTemp ← ENXTemp, GoTo[ENXRet]; *Dally for Wakeup Pipeline

ENXILockENXXmtCount:
ENXXmtCount ← ENXXmtCount, Return;


* Handy subroutines convient for tasking even if they are only called once.
* BEWARE of bypass problems

ENXStoreCompletion:
OddPStore1[ENXZeroBase, ENXCompletion], GoTo[ENXRet];

ENXStoreWordsUsed:
OddPStore1[ENXZeroBase, ENXRcvIndex], GoTo[ENXRet];

ENXFetchICB:
PFetch1[ENXCSBPtr,ENXICBPtr,ENXIndexICB!], GoTo[ENXRet];

ENXFetchOCB:
PFetch1[ENXCSBPtr,ENXOCBPtr,ENXIndexOCB!], GoTo[ENXRet];

*Get buffer descriptor pointed to by T, fix base pointers
*(ENXxxxPtr,ENXxxxPtrHi) by transforming ENXxxxPtrHi from (0, x) to (x, x+1)
* zero out ENXxxxIndex

ENXXmtSetup:
OddPFetch4[ENXZeroBase,ENXXmtIndex];
ENXXmtIndex ← (ENXXmtIndex) and (0C);
T ← (ENXXmtPtrHi) + 1;
T ← ENXXmtPtrHi ← (LSH[ENXXmtPtrHi,10]) or T;
ENXXmtPtrHi ← (FixVA[ENXXmtPtrHi]) or T, Return;

ENXRcvSetup:
OddPFetch4[ENXZeroBase,ENXRcvIndex];
ENXRcvIndex ← (ENXRcvIndex) and (0C);
T ← (ENXRcvPtrHi)+1;
T ← ENXRcvPtrHi ← (LSH[ENXRcvPtrHi,10]) or T;
ENXRcvPtrHi ← (FixVA[ENXRcvPtrHi]) or T, Return;

:END[ENXTask];