*----------------------------------------------------------------------------
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;