// PupAlImp.bcpl -- Alto Imp driver
// Copyright Xerox Corporation 1983

// Last modified August 22, 1983  2:03 PM by Taft

// Note: this code requires the special version of the Alto1822 microcode
// that does scatter/gather.

get "Pup0.decl"
get "PupAlImp.decl"

external
[
// Outgoing procedures
EncapsulateImpPup; SendImpPacket; ImpPupFilter; ResetImpInterface
ImpInputInterrupt; ImpOutputInterrupt; ImpProcess; ImpFinish

// Incoming procedures
Enqueue; Dequeue; CauseInterrupt; DisableInterrupts; EnableInterrupts
SetTimer; TimerHasExpired; Dismiss; Block
CallSwat; StartIO; MoveBlock; Zero

// Outgoing statics
impNDB; savedImpUFP

// Incoming statics
pbiFreeQ; pbiIQ; lenPBI; lvUserFinishProc
]

static
[
impNDB
savedImpUFP = -1
]

// Pup interface procedures

//----------------------------------------------------------------------------
let EncapsulateImpPup(pbi, pdh) be
//----------------------------------------------------------------------------
// Performs Arpanet-dependent encapsulation.
// pbi points at a PBI containing a Pup.
// pdh is physical destination host for Pup.
[
pbi>>ImpPBI.host = pdh rshift 6
pbi>>ImpPBI.imp = pdh & 77B
pbi>>ImpPBI.link = linkPup
pbi>>PBI.packetLength = (pbi>>PBI.pup.length+1) rshift 1
]

//----------------------------------------------------------------------------
and SendImpPacket(pbi) be
//----------------------------------------------------------------------------
// Assumes that the ImpPBI.host, imp, and link fields have been filled in.
// Note: contrary to normal practice, the PBI.packetLength refers only to the
// encapsulated data.  Assumes that it is nonzero.
[
test pbi>>ImpPBI.imp eq 0 %  // For now, discard broadcast
 impNDB>>ImpNDB.status.hostNotReady ne 0 %  // Discard if host not ready
 impNDB>>ImpNDB.status.impNotReady ne 0  // Discard if Imp not ready
   ifso Enqueue(pbi>>PBI.queue, pbi)
   ifnot
      [
      Enqueue(lv impNDB>>ImpNDB.oQ, pbi)
      unless impNDB>>ImpNDB.oActive do
         CauseInterrupt(impNDB>>ImpNDB.icb>>ICB.outputChanMask)
      ]
]

//----------------------------------------------------------------------------
and ImpInputInterrupt() be
//----------------------------------------------------------------------------
[
let icb = impNDB>>ImpNDB.icb
let pbi = impNDB>>ImpNDB.iPBI
if pbi ne 0 then
   [  // Dispose of incoming message
   UpdateStatus(icb>>ICB.inputPost)
   let queue = pbiFreeQ
   let newError = false
   switchon icb>>ICB.inputPost.microcode into
      [
      case isInputOverflow:  // Input buffer full and not end of message
         if icb>>ICB.inputPointer - lv impNDB>>ImpNDB.iLeader ule lenImpLeader then
            [  // end of leader -- set up to read data into PBI
            icb>>ICB.inputPointer = lv pbi>>ImpPBI.data
            icb>>ICB.inputEnd = pbi+lenPBI
            icb>>ICB.inputPost = 0
            StartIO(impStartInput)
            return
            ]

         // Discard rest of message; but process whatever was received anyway,
         // since the overflow may simply be Imp padding.
         newError = true
         // fall through

      case isInputFullAndEnd:  // Input buffer full and end of message simultaneously
         icb>>ICB.inputPointer = icb>>ICB.inputEnd  // microcode gets this wrong
         // fall through

      case isDone:  // Normal completion
         [
         let llen = icb>>ICB.inputPointer - lv impNDB>>ImpNDB.iLeader
         if impNDB>>ImpNDB.iError %  // Discard if previous error
          llen uls lenImpLeader %  // Discard if incomplete leader
          impNDB>>ImpNDB.iLeader.format ne 17B then endcase  // Discard if not new format
         pbi>>PBI.packetLength = llen eq lenImpLeader? 0,
          icb>>ICB.inputPointer - lv pbi>>ImpPBI.data
         pbi>>ImpPBI.host = impNDB>>ImpNDB.iLeader.host  // Compress encapsulation into PBI
         pbi>>ImpPBI.imp = impNDB>>ImpNDB.iLeader.imp
         pbi>>ImpPBI.link = impNDB>>ImpNDB.iLeader.link
         switchon impNDB>>ImpNDB.iLeader.messageType into
            [
            case 0:  // regular message
               [
               pbi>>PBI.ndb = impNDB
               let pf = impNDB>>NDB.pfQ.head
               while pf ne 0 do
                  [
                  if (pf>>PF.predicate)(pbi) then [ queue = pf>>PF.queue; break ]
                  pf = pf>>PF.link
                  ]
               endcase
               ]

            case 4:  // nop -- contains local host address
               impNDB>>NDB.localHost = (pbi>>ImpPBI.host & 3) lshift 6 +
                (pbi>>ImpPBI.imp & 77B)
               endcase
            ]
         endcase
         ]

// ImpInputInterrupt (cont'd)

      default:
         CallSwat("[ImpInputInterrupt] Bad microcode status")
      ]

   Enqueue(queue, pbi)
   pbi = 0
   impNDB>>ImpNDB.iError = newError
   ]

unless impNDB>>ImpNDB.status.impNotReady % impNDB>>ImpNDB.status.hostNotReady do
   [  // Try to start up input
   pbi = Dequeue(pbiFreeQ)
   if pbi ne 0 then
      [  // Set up to read leader first
      icb>>ICB.inputPointer = lv impNDB>>ImpNDB.iLeader
      icb>>ICB.inputEnd = icb>>ICB.inputPointer+lenImpLeader
      icb>>ICB.inputPost = 0
      StartIO(impStartInput)
      ]
   ]

impNDB>>ImpNDB.iPBI = pbi
]

//----------------------------------------------------------------------------
and ImpPupFilter(pbi) =
//----------------------------------------------------------------------------
 pbi>>ImpPBI.link eq linkPup &
 (pbi>>ImpPBI.pup.length+1) rshift 1 ule pbi>>PBI.packetLength

//----------------------------------------------------------------------------
and FlushImpInput() be
//----------------------------------------------------------------------------
// Call this only after having reset the interface
[
if impNDB>>ImpNDB.iPBI ne 0 then
   [
   Enqueue(pbiFreeQ, impNDB>>ImpNDB.iPBI)
   impNDB>>ImpNDB.iPBI = 0
   ]
]

//----------------------------------------------------------------------------
and ImpOutputInterrupt() be
//----------------------------------------------------------------------------
[
let icb = impNDB>>ImpNDB.icb
if impNDB>>ImpNDB.oActive then
   [
   unless icb>>ICB.outputPost.microcode eq isDone do
      CallSwat("[ImpOutputInterrupt] Bad microcode status")
   if icb>>ICB.outputPointer eq lv impNDB>>ImpNDB.oLeader + lenImpLeader &
    not impNDB>>ImpNDB.sendingNop then
      [  // end of leader.  Now send data and end of packet
      let pbi = impNDB>>ImpNDB.oPBI
      icb>>ICB.outputPointer = lv pbi>>ImpPBI.data
      icb>>ICB.outputEnd = icb>>ICB.outputPointer+pbi>>PBI.packetLength
      icb>>ICB.outputPost = 0
      IssueControlCommand(icEndPacketOn)
      StartIO(impStartOutput)
      return
      ]

   // Dispose of completed output message
   UpdateStatus(icb>>ICB.outputPost)
   test impNDB>>ImpNDB.sendingNop
      ifso impNDB>>ImpNDB.sendingNop = false
      ifnot unless impNDB>>ImpNDB.oError do ImpOutputDispose()
   ]

impNDB>>ImpNDB.oActive = false

// If the Imp is down, flush all output
if impNDB>>ImpNDB.status.impNotReady then [ FlushImpOutput(); return ]

let leader = lv impNDB>>ImpNDB.oLeader
Zero(leader, lenImpLeader)
leader>>ImpLeader.format = 17B
test impNDB>>ImpNDB.oError
   ifso
      [  // Error flop was set.  Send a Nop
      impNDB>>ImpNDB.oError = false
      impNDB>>ImpNDB.sendingNop = true
      leader>>ImpLeader.messageType = 4
      // Set the host address so that the right thing happens if the Imp
      // interface is looped back.
      leader>>ImpLeader.host = impNDB>>ImpNDB.localHost rshift 6
      leader>>ImpLeader.imp = impNDB>>ImpNDB.localHost & 77B
      IssueControlCommand(icEndPacketOn)  // packet consists of leader only
      ]
   ifnot
      [
      unless SetupImpOutput() return
      let pbi = impNDB>>ImpNDB.oPBI
      leader>>ImpLeader.host = pbi>>ImpPBI.host
      leader>>ImpLeader.imp = pbi>>ImpPBI.imp
      leader>>ImpLeader.link = pbi>>ImpPBI.link
      IssueControlCommand(icEndPacketOff)  // do not end packet after leader
      ]

icb>>ICB.outputPointer = leader
icb>>ICB.outputEnd = leader+lenImpLeader
icb>>ICB.outputPost = 0
StartIO(impStartOutput)
impNDB>>ImpNDB.oActive = true
]

//----------------------------------------------------------------------------
and ImpOutputDispose() be
//----------------------------------------------------------------------------
[
let pbi = impNDB>>ImpNDB.oPBI
if pbi ne 0 then
   [
   Enqueue(pbi>>PBI.queue, pbi)
   impNDB>>ImpNDB.oPBI = 0
   ]
]

//----------------------------------------------------------------------------
and SetupImpOutput() = valof
//----------------------------------------------------------------------------
// Returns true iff ready to send a PBI.
[
if impNDB>>ImpNDB.oPBI eq 0 then
   impNDB>>ImpNDB.oPBI = Dequeue(lv impNDB>>ImpNDB.oQ)
resultis impNDB>>ImpNDB.oPBI ne 0
]

//----------------------------------------------------------------------------
and FlushImpOutput() be while SetupImpOutput() do ImpOutputDispose()
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
and UpdateStatus(status) be
//----------------------------------------------------------------------------
// Update software status from hardware.
[
impNDB>>ImpNDB.status = status
if status<<ImpStatus.impWasDown then
   [  // Imp was down.  Attempt to clear it and record software errors
   IssueControlCommand(icClearImpWasDown)
   impNDB>>ImpNDB.iError = true
   impNDB>>ImpNDB.oError = true
   ]
]

//----------------------------------------------------------------------------
and ResetImpInterface(hostUp) be
//----------------------------------------------------------------------------
// Resets the interface and flushes all buffers.  Leaves the host up if hostUp is true,
// down otherwise.
[
let icb = impNDB>>ImpNDB.icb
IssueControlCommand(icMasterReset)
IssueControlCommand(icLoopBackOff)
test hostUp
  ifso
     [
     IssueControlCommand(icHostReadyOn)
     Dismiss(50)  // wait for relay to close
     IssueControlCommand(icNoop)  // just get updated status
     ]
  ifnot IssueControlCommand(icHostReadyOff)
UpdateStatus(icb>>ICB.controlPost)
FlushImpInput()
FlushImpOutput()
impNDB>>ImpNDB.oActive = false
impNDB>>ImpNDB.iError = true
impNDB>>ImpNDB.oError = true
]

//----------------------------------------------------------------------------
and IssueControlCommand(command) be
//----------------------------------------------------------------------------
// All control commands must be issued via this subroutine, which must
// disable interrupts since the control portion of the ICB is shared
// between input and output.
[
let icb = impNDB>>ImpNDB.icb
DisableInterrupts()
icb>>ICB.controlPost = 0
icb>>ICB.control = command
StartIO(impControlStatus, icb)
EnableInterrupts()
]

//----------------------------------------------------------------------------
and ImpProcess() be  // once-per-second housekeeping
//----------------------------------------------------------------------------
[
let icb = impNDB>>ImpNDB.icb
IssueControlCommand(icNoop)
UpdateStatus(icb>>ICB.controlPost)

// If Imp has gone off, flap the interface and flush buffers.
// This is to recover from lost interrupts and such.
if impNDB>>ImpNDB.status.impNotReady then ResetImpInterface(true)

// If Imp and Host are now up and there is no input buffer set up,
// attempt to start input.
if impNDB>>ImpNDB.iPBI eq 0 & impNDB>>ImpNDB.status.impNotReady eq 0 &
 impNDB>>ImpNDB.status.hostNotReady eq 0 & pbiFreeQ>>Q.head ne 0 then
   CauseInterrupt(icb>>ICB.inputChanMask)  // let ImpInputInterrupt do it

Dismiss(100)
] repeat

//----------------------------------------------------------------------------
and ImpFinish(code) be
//----------------------------------------------------------------------------
[
ResetImpInterface(false)
@lvUserFinishProc = savedImpUFP
savedImpUFP = -1
]