*----------------------------------------------------------------------------
Title[PilotEther.Mc...September 23, 1982  1:49 PM...Taft];
* Ethernet microcode for Pilot, handles "multicast" on input (Swinehart)
*----------------------------------------------------------------------------

*----------------------------------------------------------------------------
* Data structures
*----------------------------------------------------------------------------

* This microcode supports a "multicast" input regimen.  The client can specify from
* 0 to 255 host numbers, using a bit vector.  Packets directed to any of the specified
* hosts will be accepted by the microcode.  It is up to the client to sort out the results.
* The feature is enabled by setting the ICSB.host field to -1.  Then the ICSB.hosts vector
* will be used to decide which packets to accept.  Note that the client can disable the
* acceptance of broadcast packets.  Setting ICSB.host to a valid host returns to the
* conventional single-host mode.  The ICSB.hosts vector should have the proper value
* before the ICSB.host field is set to -1.

* ICSB: LONG POINTER TO InputControllerStatusBlock = LOOPHOLE[177600B];
* InputControllerStatusBlock: TYPE = MACHINE DEPENDENT RECORD [
MC[ICSB.next, 177600];		* next: POINTER TO IOControlBlock,
MC[ICSB.interruptMask, 177601];	* interruptMask: WORD,
MC[ICSB.host, 177602];		* host: CARDINAL, -- if >=0, conventional one-host style
MC[ICSB.missed, 177603];	* missed: CARDINAL,
				* skipOverOCSB: ARRAY [4..30B) OF CARDINAL,
MC[ICSB.hosts, 177630];		* hosts: PACKED ARRAY [0..256) OF BOOL,
				* ... ] -- Other stuff microcode doesn't use

* OCSB: LONG POINTER TO OutputControllerStatusBlock = LOOPHOLE[177610B];
* OutputControllerStatusBlock: TYPE = MACHINE DEPENDENT RECORD [
MC[OCSB.next, 177610];		* next: POINTER TO IOControlBlock,
MC[OCSB.interruptMask, 177611];	* interruptMask: WORD,
MC[OCSB.minPacketSpacing, 177612]; * minPacketSpacing: CARDINAL, -- 32us units
				* ... ] -- Other stuff microcode doesn't use

* IOControlBlock: TYPE = MACHINE DEPENDENT RECORD [
MC[IOCB.next, 0];		* next: POINTER TO IOControlBlock,
MC[IOCB.completion, 1];		* completion: CompletionStatus,
MC[IOCB.wordsUsed, 2];		* wordsUsed: CARDINAL,
MC[IOCB.load, 3];		* load: CARDINAL,
MC[IOCB.length, 4];		* length: CARDINAL,
MC[IOCB.buffer, 5];		* buffer: LONG POINTER]

* CompletionStatus: TYPE = MACHINE DEPENDENT RECORD [
*	microcode: {busy, done, bufferOverflow, loadOverflow, (377B)},
*	hardware: [0..377B]]
* Hardware status for input is status word received through receive fifo;
* for output is status word read from EControl.

* Microcode completion status codes:
MC[CS.done, 400];
MC[CS.bufferOverflow, 1000];
MC[CS.loadOverflow, 1400];

*----------------------------------------------------------------------------
* Emulator Task -- ResetEther instruction.
* Branched to from Mesa emulator to reset Ethernet hardware and tasks.
*----------------------------------------------------------------------------
Set[XTask, IP[EMU]];		* Emulator mode
TopLevel;
DontKnowRBase;

ResetEther:
	T← EControl;
	TIOA← T;
	TaskingOff;			* Smash the hardware off
	T← TurnOffRx, Call[OutputGetsT];
	T← TurnOffTx, Call[OutputGetsT];
	Call[EITInitPC];		* Reset the tasks
	LdTPC← T, Wakeup[EIT];
	Call[EOTInitPC];
	LdTPC← T, Wakeup[EOT];
	T← A0, TaskingOn;
	TIOA← T, IFUJump[0];


*----------------------------------------------------------------------------
* Initialization code for Input Task
*----------------------------------------------------------------------------
Set[XTask, IP[EIT]];

Subroutine;
EITInitPC: T← EIT, CoReturn;
TopLevel;
	
	T← EControl;
	TIOA← T, Block, Branch[EIIdle];	* full TIOA for initialization

*----------------------------------------------------------------------------
* Initialization code for Output Task
*----------------------------------------------------------------------------
Set[XTask, IP[EOT]];

Subroutine;
EOTInitPC: T← EOT, CoReturn;
TopLevel;
	RBase← RBase[EORegs];
	T← 33000C;			* Init random number generator
	RConst← T+(31C);		* Constant (33031B = 13849)
	T← EControl;
	TIOA← T, Block, Branch[EOIdle];	* full TIOA for initialization

*----------------------------------------------------------------------------
* Input Task
*----------------------------------------------------------------------------

Set[XTask, IP[EIT]];		* Non-emulator mode

* Idle state of the Ethernet input task.
* Wake up here when first word of a new packet arrives.
EIIdle: MemBase← IOBR;
	T← ICSB.next;
	Fetch← T, T← ICSB.host;
	PD← MD, RBase← RBase[EIRegs];	* Is there an IOCB?
	EICB← MD, TIOA[EData],
		Branch[EINoCB, ALU=0];	* Branch if no IOCB
	Fetch← T, EITemp2← T← Input,	* Fetch ICSB.host, save first word of packet
		Branch[EIPLZ, IOAtten];	* Branch if zero-length packet

* Address filtering.
	PD← NOT MD, TIOA[EControl];	* (Done upside-down for placement)
	T← A0, Branch[MultiTest, ALU>=0]; * Use multicast method if ICSB.host is negative

* Traditional method: test for single host, broadcast, or promiscuous.
	T← RCY[T, EITemp2, 10], Call[EAdrCk]; * T← destination host, test for zero
	PD← T-MD, Call[EAdrCk];		* Test for destination host = me
	PD← MD, Call[EAdrCk];		* Test for me = 0 (promiscuous)

* Packet not accepted by filter.
* Tell hardware to ignore the rest of this packet.
EIIgn:	T← WaitForBOP;			* Wakeup at start of next packet
EILast:	Output← T, Block, Branch[EIIdle];

Subroutine;
EAdrCk:	Branch[Accept, ALU=0];
	Return;
TopLevel;

* Multicast method: use destination as an index into the ICSB.hosts array.
MultiTest:
	T← RSH[EITemp2, 14];		* T ← Word offset
	T← T+(ICSB.hosts);
	Fetch← T, EITemp1← EITemp2;
	T← MD, EIPtr← ShC;
	EITemp1← T, ShC← EITemp1;	* Shift count ← low 4 bits of destination host
					* (EITemp1[4:7]); R/T select bits get garbage
	PD← ShiftNoMask[EITemp1];	* Left-justify selected bit (from EITemp1 or T)
	ShC← EIPtr, Branch[EIIgn, ALU>=0]; * Reject if selected bit is zero
	Nop;

* Packet accepted by filter.  Set up buffer pointer and count.
Accept: EIPtr← A0, Call[EIOCBSetup];
	T← EITemp2, TIOA[EData], Block;	* Recover first data word, await next

* Input task (cont'd)

* Input main loop.
* Each iteration stores the word previously input from the interface
* and inputs the next word.  This facilitates end-of-packet handling.
* EIPtr has negative of number of words remaining in the buffer.
* IOAtten branches if the data word being read is the end-of-packet status.
	EIPtr← (Store← EIPtr)+1, DBuf← T, Branch[EIEnd, IOAtten];
	T← Input, Block, Branch[.-1, ALU#0];

* Get here when buffer is exactly full (EIPtr=0).
* We know that the word in T is not EOP status since we would have taken
* the IOAtten branch if it were.  If IOAtten is now true, then the word in T
* is the CRC (which we must discard) and the next input is the status word.
* If IOAtten is false, the buffer has overflowed.
	EIPtr← EITemp1;			* Set packet length = buffer length
	T← Input, Branch[EIEnd1, IOAtten]; * Branch if end-of-packet
	T← CS.bufferOverflow, Branch[EIPost];


* Normal end-of-packet exit from main loop.
* One extra word (the CRC) has been stored in the buffer and must not be
* included in the count.  The next input is the status word.
EIEnd:	T← (EITemp1)-1;			* Initial buffer length
	EIPtr← (EIPtr)+T;		* Actual packet length
	T← Input;			* Read status word
EIEnd1:	T← T OR (CS.done);		* Done completion status

* Post input completion status now in T.
EIPost:	EITemp1← ICSB.next;
	Call[EPost];
	Block, Branch[EIIdle];		* Await next packet

* Here if packet length zero, i.e., first word input was end-of-packet status.
* This should happen only if receiver-detected collision reporting is turned
* on, since runt packets are ordinarily filtered out by the phase decoder.
EIPLZ:	EIPtr← A0, Branch[EIEnd1];	* Length← 0, post done status

* Discard packet because IOCB queue is empty.
EINoCB: T← T+1, TIOA[EControl]; 	* Increment ICSB.missed
	Fetch← T;
	EITemp1← MD+1;
	Store← T, DBuf← EITemp1, Branch[EIIgn]; * Ignore rest of packet

*----------------------------------------------------------------------------
* Output Task
*----------------------------------------------------------------------------

Set[XTask, IP[EOT]];

* Idle state of the Ethernet output task.  TIOA=EControl.
* Wake up here at end of previous packet or when poked by software.
EOIdle:	MemBase← IOBR;
	T← OCSB.next;			* Fetch OCSB.next
	T← (Fetch← T)+(Sub[OCSB.minPacketSpacing!, OCSB.next!]C);
	RBase← RBase[EORegs];
	Fetch← T, EOCB← PD← MD;		* Fetch OCSB.minPacketSpacing
	T← (EOCB)+(IOCB.load),
		Branch[EONoCB, ALU=0];	* Go shut off transmitter if no IOCB

* Test and update load
	Fetch← T, EOTemp2← MD;		* Fetch IOCB.load
	EOTemp1← MD+MD+1;		* Load lsh 1 +1
	Store← T, DBuf← EOTemp1, EOTemp1← MD, * Store new load, save current
		Call[Random];		* T← random number
	T← RSH[T, 10];			* Use leftmost 8 bits

* Test for load overflow (old load has sign bit set).
* Then mask countdown with old load and test for nonzero result.
* Note that the countdown clock ticks every 16 us, but we want to count in
* units of 32 us, so we double the count before using it.
	T← ((EOTemp1) AND T) LSH 1, Branch[ELodOv, R<0];
	PD← T-T, Branch[EWaitC, ALU#0];	* Carry← 1; branch if wait required

* On first transmission attempt, enforce minimum inter-packet spacing.
* Note: RTClock counts 32-usec intervals.  EOTemp2 = OCSB.minPacketSpacing
	T← EOTime, RBase← RBase[RTClock]; * Time last transmission ended
	T← (RTClock)-T, RBase← RBase[EORegs]; * Inter-packet spacing
	T← ((EOTemp2)-T-1) LSH 1;	* Compare with min permitted
EWaitC:	EOTemp1← T-1, Branch[EOGo, Carry']; * Start now if exceeded min

* Must wait before retransmitting.  EOTemp1 has wait time -1 in 16-usec units.
* CountDown turns off data wakeups and requests a wakeup at next
* tick of countdown clock.
EWait:	T← CountDown;
	EOTemp1← (EOTemp1)-1, Output← T, Block, Branch[.-1, R>=0];

* Now time to transmit packet.  Set up buffer pointer and count.
EOGo:	EOPtr← T-T-1, Call[EIOCBSetup];
	EOPtr← (Fetch← EOPtr)+1;	* Pre-fetch the first word
	TIOA[EData], Branch[EOEnd, ALU=0];

* Output task (cont'd)

* Output main loop.
* Data transfer is pipelined:  each iteration outputs a data word
* fetched in the previous iteration and starts the fetch of the data for
* the next iteration.  This way, cache misses generally occur while our
* task is blocked, and lower-priority tasks are permitted to run while
* the cache is being loaded.
* EOPtr is the negative of the number of words remaining to be fetched.
* IOAtten branches if a collision, data late, or Fifo PE has aborted output.
	EOPtr← (Fetch← EOPtr)+1, T← MD, Branch[EOAbrt, IOAtten];
	Output← T, Block, Branch[.-1, ALU#0];

* Next-to-last word has been output and last word has been fetched
EOEnd:	T← EOTemp1, Output← MD;		* Output last word
	EOPtr← T, TIOA[EControl];	* Set words used = buffer length
	T← SendEOP;			* Declare end of packet
	Output← T, Block;

* Wake up here only when packet completely sent or collision has occurred.
	EOTemp1← Input, Branch[EOAbr1, IOAtten]; * Branch if aborted, get status
	RBase← RBase[RTClock];		* Remember current time
	T← RTClock, RBase← RBase[EORegs];
	EOTime← T;
EODone:	T← (EOTemp1) AND (377C);	* Just the status bits
	T← T OR (CS.done);		* Done completion status

* Post ending status and reset interface for next packet.
EOPost:	EOTemp1← OCSB.next;
	Call[EPost];			* Post status in T
	T← TurnOffTx, Branch[EORest];	* Turn transmitter off and on again

* Here if collision or other error occurred.
* If a collision, just try again.  If not, post output done.
EOAbrt:	T← (EOTemp1)-1;			* Initial buffer length
	EOPtr← (EOPtr)+T, TIOA[EControl]; * Words actually sent
	EOTemp1← Input;			* Read status

* If not a collision, post output done with whatever status bits we got.
EOAbr1:	PD← (EOTemp1) AND (TxCollision); * Collision?
	T← TurnOffTx, Branch[EODone, ALU=0]; * Branch if not
EORest:	Output← T;			* Turn off to reset collision bit
	T← TurnOnTx, Branch[EOLast];	* Turn on and try again

* Load overflow, post error status
ELodOv:	T← CS.loadOverflow, Branch[EOPost]; * Load overflow post code

* Shut off hardware because IOCB queue is empty.
EONoCB:	T← TurnOffTx, Branch[EOLast];
EOLast:	Output← T, Block, Branch[EOIdle];

*----------------------------------------------------------------------------
* Task-independent Subroutines
*----------------------------------------------------------------------------

*----------------------------------------------------------------------------
EIOCBSetup:	* Set up IOCB pointers and counts
* Enter: ExCB = IOCB being worked on
*	ExPtr = 0 for input, -1 for output
*	MemBase = IOBR
* Exit: ExPtr = negative word count
*	MemBase = ExBR
*	ExBR = base register properly set up for indexing by ExPtr
* Points the base register beyond the end of the buffer -2↑16, and
* addresses the buffer with a negative index in ExPtr.
* Clobbers ExTemp1, T
*----------------------------------------------------------------------------
Subroutine;

	T← (ExCB)+(IOCB.length);
	T← (Fetch← T)+1;		* Fetch IOCB.length
	T← (Fetch← T)+1, ExTemp1← MD;	* Fetch low buffer pointer
	Fetch← T, T← MD,		* Fetch high buffer pointer
		ExPtr, Branch[.+2, R<0]; * Which base register?
	T← T+(ExTemp1), MemBase← EIBR, Branch[.+2]; * Low pointer + length
	T← T+(ExTemp1), MemBase← EOBR;	* Low pointer + length
	T← MD, BRLo← T;
	T← T-1, XorSavedCarry;		* Adjust high for negative indexing
	T← ExTemp1, BRHi← T;
	ExPtr← (0S)-T, Return;		* ExPtr← negative count

*----------------------------------------------------------------------------
EPost:		* Post command completion.
* Enter: T = completion status word
*	ExPtr = actual packet length
*	ExCB = IOCB being worked on
*	ExTemp1 = ICSB or OCSB pointer (IOBR relative)
* Exit:	MemBase = IOBR
* Clobbers T, ExCB, ExTemp1, RBase
*----------------------------------------------------------------------------
Subroutine;

	MemBase← IOBR;
	ExCB← (Fetch← ExCB)+1;		* Fetch IOCB.next
	T← (Store← ExCB)+1, DBuf← T;	* Store IOCB.completion
	Store← T, DBuf← ExPtr, T← MD;	* Store IOCB.wordsUsed
	ExTemp1← (Store← ExTemp1)+1, DBuf← T; * Store xCSB.next
	Fetch← ExTemp1;			* Fetch xCSB.interruptMask
	RBase← RBase[NWW];		* Initiate interrupts
	NWW← (NWW) OR MD, Reschedule, Return;

*----------------------------------------------------------------------------
Random:		* Generate random number and return it in T.
* Must have RBase[RNum] (=EORegs).
* Uses algorithm:
* 	R← (2↑11 + 2↑2 + 2↑0)* R + 13849
*----------------------------------------------------------------------------
Subroutine;
KnowRBase[RNum];

	T← LSH[RNum, 11];		* T← 2↑9 * R
	T← T+(RNum);			* (2↑9 + 2↑0)* R
	T← LSH[T, 2];			* (2↑11 + 2↑2)* R
	T← T+(RNum);			* (2↑11 + 2↑2 + 2↑0)* R
	T← RNum← T+(RConst), Return;	* +13849

TopLevel;