// PupAlEthInit.bcpl -- Alto Ethernet Initialization
//		Code may be thrown away after use
// companion to PupAlEthb.bcpl and PupAlEtha.asm
// Copyright Xerox Corporation 1979, 1980, 1982

// Last modified September 27, 1982  2:02 PM by Taft

get "Pup0.decl"
get "PupAlEth.decl"

external
[
// outgoing procedures
InitAltoEther; DestroyAltoEther

// incoming procedures
SendEtherPacket; EncapsulateEtherPup; SendEtherStats
EtherFinish; EtherPupFilter
InitializeInterrupt; FindInterruptMask; DestroyInterrupt
InitializeContext; FeedEther
Enqueue; Dequeue; Unqueue; Zero; MoveBlock; Allocate; Free; StartIO; SysErr

// outgoing statics
savedEtherUFP; lenEtherPacket; etherMcLoaded

// incoming statics
ndbQ; pbiIQ; pbiFreeQ; lenPup; etherProlog
lvUserFinishProc
]

static [ savedEtherUFP = -1; lenEtherPacket; etherMcLoaded ]

manifest
[
eISS = 50	//Ether Interrupt Stack Size
eFSS = 40	//Ether Feeder Stack Size

interruptVector = #501

etherOutputCommand = 1
etherInputCommand = 2
etherResetCommand = 3
]

//----------------------------------------------------------------------------
let InitAltoEther(zone, ctxQ, code) be
//----------------------------------------------------------------------------
//zone is where to get storage from
//ctxQ is the context list on which to queue the feeder process
//EtherInterrupt should have a high priority mask bit
//The low two bits of etherMcLoaded are flags.  Bit 15 means the first
// ExtraEther1.mu is loaded, Bit 14 means ExtraEther2.mu is loaded.
[
if code ne 0 then
   if (code & etherMcLoaded) eq 0 return  //no microcode

let loc, sio = nil, nil
switchon code into
   [
   case 0: [ loc = 600b; sio = 0; endcase ]
   case 1: [ loc = 630b; sio = 2; endcase ]
   case 2: [ loc = 642b; sio = 4; endcase ]
   ]
loc!0 = 0	//ePLoc
loc!1 = 0	//eBLoc
let host = StartIO(etherResetCommand lshift sio) & 377b //reset
if loc!0 eq 0 return  //interface didn't respond - must not be one
if host eq 377b then SysErr(nil, ecNoEthernetInterface)
if host eq 0 then SysErr(nil, ecZeroHostAddress)
loc!8 = host	//eHLoc

// standard part of NDB
let ndb = Allocate(zone, lenEtherNDB+eISS)
Zero(ndb, lenEtherNDB+eISS)
ndb>>NDB.localHost = host  //local host number
ndb>>NDB.encapsulatePup = EncapsulateEtherPup
ndb>>NDB.level0Transmit = SendEtherPacket  //send pbi routine
ndb>>NDB.level0Stats = SendEtherStats
ndb>>NDB.destroy = DestroyAltoEther
ndb>>NDB.netType = netTypeEther
ndb>>NDB.deviceNum = code
ndb>>NDB.pupPF.predicate = EtherPupFilter
ndb>>NDB.pupPF.queue = pbiIQ
Enqueue(lv ndb>>NDB.pfQ, lv ndb>>NDB.pupPF)
Enqueue(ndbQ, ndb)

// Ether specific part of NDB
let t = lv ndb>>EtherNDB.ePLoc
for i = 0 to 8 do t!i = loc+i
ndb>>EtherNDB.outCmd = etherOutputCommand lshift sio
ndb>>EtherNDB.inCmd = etherInputCommand lshift sio
ndb>>EtherNDB.resetCmd = etherResetCommand lshift sio
ndb>>EtherNDB.statsType = netTypeEther
ndb>>EtherNDB.statsVersion = etherStatsVersion
ndb>>EtherNDB.zone = zone
ndb>>EtherNDB.ctxQ = ctxQ

// handy constant for driver
lenEtherPacket = lenPup+2

// InitAltoEther (cont'd)

// This rather complex interrupt mechanism works as follows:
//	Control goes through the interrupt vector to asmProlog
//	 which saves ac3, and loads ac3 with the ndb corresponding
//	 to the interrupting interface.  It then calls EtherInterruptEntry.
//	EtherInterruptEntry tries to get the receiver going as fast
//	 as possible, and then jumps through EtherNDB.savedID to the
//	 normal bcpl interrupt handler.
//	The interrupt handler sets up the stack and calls bcplProlog
//	 which calls EtherInterrupt with the ndb as argument.
//	EtherInterrupt finishes processing the interrupt and returns
//	 back into the interrupt handler which dismisses the interrupt
MoveBlock(lv ndb>>EtherNDB.prolog, etherProlog, (size EtherNDB.prolog/16))
ndb>>EtherNDB.ndb = ndb
ndb>>EtherNDB.mask = FindInterruptMask(1)  //high priority
@(ndb>>EtherNDB.eBLoc) = ndb>>EtherNDB.mask
InitializeInterrupt(ndb+lenEtherNDB, eISS, ndb>>EtherNDB.mask,
 lv ndb>>EtherNDB.bcplProlog)
let i = 0  //discover interrupt channel number
while ((1 lshift i) & ndb>>EtherNDB.mask) eq 0 do i = i +1
ndb>>EtherNDB.savedID = interruptVector!i
interruptVector!i = lv ndb>>EtherNDB.asmProlog

// interface housekeeping process
let ctx = InitializeContext(Allocate(zone, eFSS), eFSS, FeedEther, 1)
ctx!3 = ndb
Enqueue(ctxQ, ctx)
ndb>>EtherNDB.feederCtx = ctx

if savedEtherUFP eq -1 then
   [
   savedEtherUFP = @lvUserFinishProc
   @lvUserFinishProc = EtherFinish
   ]
]

//----------------------------------------------------------------------------
and DestroyAltoEther(ndb) be
//----------------------------------------------------------------------------
// Shuts off the Ethernet interface and returns all allocated storage.
[
@(ndb>>EtherNDB.eBLoc) = 0
StartIO(ndb>>EtherNDB.resetCmd)
DestroyInterrupt(ndb>>EtherNDB.mask)
if ndb>>EtherNDB.eIB ne 0 then Enqueue(pbiFreeQ, ndb>>EtherNDB.eIB)
if ndb>>EtherNDB.eOB ne 0 then Enqueue(pbiFreeQ, ndb>>EtherNDB.eOB)
while ndb>>EtherNDB.oQ.head ne 0 do
   Enqueue(pbiFreeQ, Dequeue(lv ndb>>EtherNDB.oQ))
Unqueue(ndb>>EtherNDB.ctxQ, ndb>>EtherNDB.feederCtx)
Free(ndb>>EtherNDB.zone, ndb>>EtherNDB.feederCtx)
Unqueue(ndbQ, ndb)
Free(ndb>>EtherNDB.zone, ndb)

// try to get rid of EtherFinish -- this is possible only if EtherFinish
// was the last userFinishProc queued.
if @lvUserFinishProc eq EtherFinish then
   [ @lvUserFinishProc = savedEtherUFP; savedEtherUFP = -1 ]
]