// PupAlEthb.bcpl - Pup Alto Ethernet driver (level 0), BCPL portion
// Copyright Xerox Corporation 1979, 1980
// Last modified September 26, 1980  3:24 PM by Boggs

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

external
[
// outgoing procedures
SendEtherPacket; EncapsulateEtherPup; SendEtherStats
FeedEther; @EtherInterrupt; EtherFinish

// incoming procedures
StartIO; StartEther; DoubleIncrement
Enqueue; Dequeue; SysErr; MoveBlock
SetTimer; TimerHasExpired; Dismiss
CauseInterrupt; DestroyInterrupt
DisableInterrupts; EnableInterrupts

// incoming statics
pbiIQ; pbiFreeQ; ndbQ; lvUserFinishProc
savedEtherUFP; lenEtherPacket
]

// Note: if pupDebug is true, then code will be compiled which
//  calls DoubleIncrement, which is in PupAl1A.asm -- above level 0!

//----------------------------------------------------------------------------
let EncapsulateEtherPup(pbi, pdh) be
//----------------------------------------------------------------------------
// performs Ethernet dependent encapsulation
// pbi -> a PBI
// pdh is physical destination host for Pup
[
pbi>>EtherPBI.dest = pdh
pbi>>EtherPBI.src = pbi>>PBI.ndb>>NDB.localHost
pbi>>EtherPBI.type = typePup
pbi>>PBI.packetLength = (pbi>>PBI.pup.length+5) rshift 1
]

 
//----------------------------------------------------------------------------
and SendEtherPacket(pbi) be
//----------------------------------------------------------------------------
[
let ndb = pbi>>PBI.ndb
if pbi>>EtherPBI.dest eq @(ndb>>EtherNDB.eHLoc) %
 pbi>>EtherPBI.dest eq 0 then // sending to ourself or bcst
   [  // fake reception of packet
   let ipbi = Dequeue(pbiFreeQ)
   if ipbi ne 0 then
      [
      MoveBlock(ipbi+1, pbi+1,
       (offset EtherPBI.dest/16-1)+pbi>>PBI.packetLength)
      let q = pbiFreeQ
      let pf = ndb>>NDB.pfQ.head
      while pf ne 0 do
         [
         if (pf>>PF.predicate)(ipbi) then
            [ q = pf>>PF.queue; break ]
         pf = pf>>PF.link
         ]
      Enqueue(q, ipbi)
      ]
   ]

// transmit the packet
Enqueue(lv ndb>>EtherNDB.oQ, pbi)
if ndb>>EtherNDB.eOB eq 0 then CauseInterrupt(ndb>>EtherNDB.mask)
compileif pupDebug then
   [ DoubleIncrement(lv ndb>>EtherNDB.packetsSent) ]
]


//----------------------------------------------------------------------------
and SendEtherStats(pbi, ndb) = valof
//----------------------------------------------------------------------------
[
compileif pupDebug then
   [
   MoveBlock(lv pbi>>PBI.pup.words, lv ndb>>EtherNDB.stats, lenEtherStats)
   pbi>>PBI.pup.length = pupOvBytes + lenEtherStats
   ]
resultis pupDebug
]

//----------------------------------------------------------------------------
and EtherInterrupt(ndb) be
//----------------------------------------------------------------------------
// Control comes here when an Ethernet interrupt occurs.
// The contents of ePLoc at the time of the interrupt is in
//  lastEPLoc (zero means that the interrupt was manually initiated).
// If an input done interrupt has occurred, lastEIB holds the pbi
//  just received and lastEELoc holds the ending word count.
// In any event, if eState is nonzero, input has been restarted.
[
// if the interface has posted, dispose of the packet appropriately
let lastEPLoc = ndb>>EtherNDB.lastEPLoc
if lastEPLoc ne 0 then switchon lastEPLoc rshift 8 into
   [
   case 0: // good input microcode status
      [
      let q, lastEIB = pbiFreeQ, ndb>>EtherNDB.lastEIB
      test lastEPLoc eq 377b  // hardware status
         ifso  // good packet, put on input queue
            [
            lastEIB>>PBI.packetLength = lenEtherPacket-ndb>>EtherNDB.lastEELoc
            lastEIB>>PBI.ndb = ndb
            let pf = ndb>>NDB.pfQ.head
            while pf ne 0 do
               [
               if (pf>>PF.predicate)(lastEIB) then
                  [ q = pf>>PF.queue; break ]
               pf = pf>>PF.link
               ]
            compileif pupDebug then
               [ DoubleIncrement(lv ndb>>EtherNDB.packetsRcvd) ]
            ]
         ifnot  // bad packet, throw it away
            [
            compileif pupDebug then
               [ DoubleIncrement(lv ndb>>EtherNDB.numBadRcvStatus) ]
            ]
      Enqueue(q, lastEIB)
      endcase
      ]
   case 1: // good output microcode status
      [
      if lastEPLoc eq #777 then  //hardware status
         [
         Enqueue(ndb>>EtherNDB.eOB>>PBI.queue, ndb>>EtherNDB.eOB)
         ndb>>EtherNDB.eOB, @(ndb>>EtherNDB.eOCLoc) = 0, 0
         compileif pupDebug then
            [
            let load, i = @(ndb>>EtherNDB.eLLoc), -1
               [ load = load rshift 1; i = i+1 ] repeatuntil load eq 0
            DoubleIncrement(lv ndb>>EtherNDB.loadTable↑i)
            endcase
            ]
         ]
      compileif pupDebug then
         [ DoubleIncrement(lv ndb>>EtherNDB.numBadXmtStatus) ]
      endcase
      ]

// EtherInterrupt (cont'd)

   case 3: // load overflow, or software-induced timeout
      [
      if ndb>>EtherNDB.eOB ne 0 then
         Enqueue(ndb>>EtherNDB.eOB>>PBI.queue, ndb>>EtherNDB.eOB)
      ndb>>EtherNDB.eOB, @(ndb>>EtherNDB.eOCLoc) = 0, 0
      compileif pupDebug then
         [ DoubleIncrement(lv ndb>>EtherNDB.loadTable↑16) ]
      ]
   case 2: // input buffer overrun
   case 5: // reset of some kind
      endcase
   case 4: // zero length buffer
   default: // impossible microcode branch
      SysErr(lastEPLoc, ecBadEtherStatus)
   ]

compileif pupDebug then
   [
   if ndb>>EtherNDB.eState eq 0 then
      DoubleIncrement(lv ndb>>EtherNDB.inputOff)
   ]

// set up new input and output buffers if appropriate
if ndb>>EtherNDB.eIB eq 0 then
   [
   ndb>>EtherNDB.eIB = Dequeue(pbiFreeQ)
   if ndb>>EtherNDB.eIB ne 0 then
      [
      @(ndb>>EtherNDB.eIPLoc) = lv ndb>>EtherNDB.eIB>>EtherPBI.dest
      @(ndb>>EtherNDB.eICLoc) = lenEtherPacket
      ]
   ]
if ndb>>EtherNDB.eOB eq 0 then
   [
   ndb>>EtherNDB.eOB = Dequeue(lv ndb>>EtherNDB.oQ)
   if ndb>>EtherNDB.eOB ne 0 then
      [
      @(ndb>>EtherNDB.eOPLoc) = lv ndb>>EtherNDB.eOB>>EtherPBI.dest
      @(ndb>>EtherNDB.eOCLoc) = ndb>>EtherNDB.eOB>>PBI.packetLength
      SetTimer(lv ndb>>EtherNDB.tTimer, 10)  //time out in 100 ms
      ]
   ]

// restart the interface if it is off or output is now ready
StartEther(ndb)
]


//----------------------------------------------------------------------------
// and EtherPupFilter(pbi) =  // hand-coded in PupAlEtha.asm
//----------------------------------------------------------------------------
//   (pbi>>PBI.pup.length+5) rshift 1 eq pbi>>PBI.packetLength &
//    pbi>>EtherPBI.type eq typePup

//----------------------------------------------------------------------------
and FeedEther(ctx) be
//----------------------------------------------------------------------------
// This background process performs two tasks:
// (1) if the interface is turned off (presumably for lack of input
//     buffers) and buffers are now availble, start the receiver.
// (2) if the interface is transmitting and has timed out, reset it
//     and fake a load overflow indication to unhang the software.
[
let ndb = ctx!3
if ndb>>EtherNDB.eState eq 0 & pbiFreeQ!0 ne 0 then
   [ @(ndb>>EtherNDB.ePLoc) = 0; CauseInterrupt(ndb>>EtherNDB.mask) ]
if ndb>>EtherNDB.eState ls 0 & TimerHasExpired(lv ndb>>EtherNDB.tTimer) then
   [
   DisableInterrupts()
   StartIO(ndb>>EtherNDB.resetCmd)  // causes pending interrupt
   @(ndb>>EtherNDB.ePLoc) = 3 lshift 8  // fake a load overflow
   EnableInterrupts()
   ]
Dismiss(4)
] repeat


//----------------------------------------------------------------------------
and EtherFinish(code) be
//----------------------------------------------------------------------------
// Turns off all Ethernet interfaces.
[
if ndbQ ne 0 then
   [
   let ndb = ndbQ!0
   while ndb ne 0 do
      [
      if ndb>>NDB.netType eq netTypeEther then
         [
         @(ndb>>EtherNDB.eBLoc) = 0
         StartIO(ndb>>EtherNDB.resetCmd)
         DestroyInterrupt(ndb>>EtherNDB.mask)
         ]
      ndb = ndb!0
      ]
   ]
@lvUserFinishProc = savedEtherUFP
savedEtherUFP = -1
]