// AltIOImp.bcpl -- Alto Imp driver for Maxc2
// This version supports 96-bit leaders, NCP, Internet/TCP, Pup
// Last modified December 13, 1982 9:35 AM
get "AltIO.decl"
get "Pup0.decl"
get "AltIOImp.decl"
external
[
// Outgoing procedures
ImpReset; EncapsulateImpPup; SendImpPacket
ImpInputInterrupt; ImpOutputInterrupt; ImpProcess
// Incoming procedures
Enqueue; Dequeue; CauseInterrupt; DisableInterrupts; EnableInterrupts
MemReadRelative; MemWriteRelative; MemReadBlock40A; MemWriteBlock40A
MemReadBlock32A; MemWriteBlock32A; RMWBitAbsolute; SignalMaxc
ConvertNBP; NBBlockTransfer; NBReadWord; NBWriteWord
ImpConvTo32; ImpConvTo36; ImpConvFrom32; ImpConvFrom36
SetTimer; TimerHasExpired; Dismiss; Block
CallSwat; Usc; StartIO; MoveBlock; Zero; Min
// Outgoing statics
impNDB; impMaxcBuf; impInputRequest; impOutputRequest; icb
// Incoming statics
pbiFreeQ; pbiIQ; lenPup
]
static
[
impNDB; impMaxcBuf; icb
impInputRequest = false
impOutputRequest = false
lastStatus
]
//----------------------------------------------------------------------------
let ImpReset() be
//----------------------------------------------------------------------------
// Called when an I/O Reset is done
[
ResetImpInterface(true)
impInputRequest = false
impOutputRequest = false
// Set "power on" bit in status word if an interface seems to be installed.
icb = impNDB>>ImpNDB.icb
impNDB>>ImpNDB.powerOn = icb>>ICB.controlPost ne 0
lastStatus = -1 // force status update in Maxc
]
//----------------------------------------------------------------------------
and ResetImpInterface(dropHostReady) be
//----------------------------------------------------------------------------
// Shuts off the interface and flushes all buffers.
[
IssueControlCommand(icMasterReset)
if dropHostReady then IssueControlCommand(icHostReadyOff)
IssueControlCommand(icLoopBackOff)
UpdateStatus(impNDB>>ImpNDB.icb>>ICB.controlPost)
FlushImpInput()
FlushImpOutput()
impNDB>>ImpNDB.oActive = false
]
// Pup interface procedures
//----------------------------------------------------------------------------
and 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.pupHost = pdh // just remember this for later
]
//----------------------------------------------------------------------------
and SendImpPacket(pbi) be
//----------------------------------------------------------------------------
[
test pbi>>ImpPBI.pupHost eq 0 % // For now, discard broadcast
impNDB>>ImpNDB.hostNotReady ne 0 % // Discard if host not ready
impNDB>>ImpNDB.impNotReady ne 0 // Discard if Imp not ready
ifso
Enqueue(pbi>>PBI.queue, pbi)
ifnot
[
Enqueue(lv impNDB>>ImpNDB.pupOQ, pbi)
unless impNDB>>ImpNDB.oActive do
CauseInterrupt(impNDB>>ImpNDB.icb>>ICB.outputChanMask)
]
]
//----------------------------------------------------------------------------
and ImpProcess() be
//----------------------------------------------------------------------------
// Process that transfers Imp messages to and from Maxc main memory.
// Also watches status and maintains various timers.
[
let maxcWord = vec 2
let maxcNBP = vec 2
let nbp = vec lenNBP
let icb = impNDB>>ImpNDB.icb
let statusTimer, maxcInputTimer = nil, nil
SetTimer(lv statusTimer, 0)
// The main loop of the process
[
Block()
if impNDB>>ImpNDB.pupInputIMBQ.head ne 0 then
[ // Transfer received Pup to a normal Pup PBI and hand it to AltIOPup
let imb = Dequeue(lv impNDB>>ImpNDB.pupInputIMBQ)
// Accept only if source address will fit in a Pup host number
if Usc(imb>>IMB.imp, 64) ls 0 & imb>>IMB.host ls 4 then
[
let host = imb>>IMB.host lshift 6 + imb>>IMB.imp
let pbi = Dequeue(pbiFreeQ)
if pbi ne 0 then
[
pbi>>PBI.packetLength = 2 + (imb>>IMB.pup.length+1) rshift 1
pbi>>ImpPBI.pupHost = host // "encapsulation"
MoveBlock(lv pbi>>PBI.pup, lv imb>>IMB.pup,
pbi>>PBI.packetLength-2)
pbi>>PBI.ndb = impNDB
Enqueue(pbiIQ, pbi)
]
]
Enqueue(lv impNDB>>ImpNDB.freeInputIMBQ, imb)
]
// ImpProcess (cont'd)
if impInputRequest & impNDB>>ImpNDB.hostInputIMBQ.head ne 0 then
[ // Ready to hand received Host-Host protocol message to Maxc
impInputRequest = false
// Read the NBP from XIMPIB and see if Maxc is really ready
MemReadRelative(xIMPIB, maxcNBP)
unless ConvertNBP(maxcNBP, nbp) loop
SetTimer(lv maxcInputTimer, 1500)
// Convert the Imp leader (96 bits) to 36-bit Maxc memory format
let imb = Dequeue(lv impNDB>>ImpNDB.hostInputIMBQ)
let format = MessageFormat(imb)
let message = lv imb>>IMB.message
impMaxcBuf!0 = message!0 // 0-15
impMaxcBuf!1 = message!1 // 16-31
impMaxcBuf!2 = message!2 // 32-35
impMaxcBuf!3 = message!2 lshift 4 + message!3 rshift 12 // 36-51
impMaxcBuf!4 = message!3 lshift 4 + message!4 rshift 12 // 52-67
impMaxcBuf!5 = message!4 lshift 4 // 68-71
impMaxcBuf!6 = message!4 lshift 8 + message!5 rshift 8 // 72-87
impMaxcBuf!7 = message!5 lshift 8 // 88-95
// Low-order 12 bits of last Maxc word get garbage
// Convert Host-Host protocol leader if appropriate, and
// compute parameters for body transfer.
let nMaxcWordsLeader, nMaxcWordsData = nil, nil
switchon format into
[
case mfHost36:
nMaxcWordsData = (16*imb>>IMB.length - size HostLeader +35)/36
docase -1
case mfHost32:
nMaxcWordsData = (16*imb>>IMB.length - size HostLeader +31)/32
docase -1
case -1: // tail of cases mfHost36 and mfHost32
// Convert 40-bit Host-Host leader by discarding first 4 bits
// and putting the other 36 in one Maxc word.
impMaxcBuf!9 = message!6 lshift 4 + message!7 rshift 12
impMaxcBuf!10 = message!7 lshift 4 + message!8 rshift 12
impMaxcBuf!11 = message!8 lshift 4
nMaxcWordsLeader = 4
endcase
case mfRaw32:
nMaxcWordsData = (16*imb>>IMB.length - size ImpLeader +31)/32
nMaxcWordsLeader = 3
endcase
default:
CallSwat("[ImpProcess] Wrong-format message on hostInputIMBQ")
]
if nMaxcWordsLeader+nMaxcWordsData gr maxMaxcWords then
CallSwat("[ImpProcess] Incoming message too large")
// ImpProcess (cont'd)
// Imp input to Maxc (cont'd)
// Transfer leader(s) to Maxc
// Must put message length (Maxc words) in bits 0-15 of word 0 of
// the Maxc buffer (without disturbing the rest of the word)
// and the message itself starting at word 1.
NBReadWord(nbp, 0, maxcWord)
maxcWord!0 = nMaxcWordsLeader+nMaxcWordsData
NBWriteWord(nbp, 0, maxcWord)
NBBlockTransfer(MemWriteBlock40A, nbp, 1, impMaxcBuf,
nMaxcWordsLeader, 3)
// Convert body to appropriate Maxc memory format (if necessary)
// and transfer it to Maxc.
if nMaxcWordsData gr 0 then switchon format into
[
case mfHost36:
ImpConvTo36(impMaxcBuf, message+8, nMaxcWordsData)
NBBlockTransfer(MemWriteBlock40A, nbp, 5, impMaxcBuf,
nMaxcWordsData, 3)
endcase
case mfHost32:
ImpConvTo32(impMaxcBuf, message+8, nMaxcWordsData)
NBBlockTransfer(MemWriteBlock40A, nbp, 5, impMaxcBuf,
nMaxcWordsData, 3)
endcase
case mfRaw32:
NBBlockTransfer(MemWriteBlock32A, nbp, 4, message+6,
nMaxcWordsData, 2)
endcase
]
// Don't need the IMB any more. Free it and restart input if
// it was stopped due to lack of buffers.
Enqueue(lv impNDB>>ImpNDB.freeInputIMBQ, imb)
if impNDB>>ImpNDB.inputIMB eq 0 then
CauseInterrupt(icb>>ICB.inputChanMask)
// Signal Maxc that we have given it an Imp message
MemWriteRelative(xIMPIB, maxcNBP) //set use flag
RMWBitAbsolute(aNVMAX, nmIMPIDN)
SignalMaxc()
]
// ImpProcess (cont'd)
if impOutputRequest & impNDB>>ImpNDB.freeOutputIMBQ.head ne 0 then
[ // Ready to get Host-Host protocol message from Maxc and send it
impOutputRequest = false
// Read the NBP from XIMPOB and see if Maxc is really ready
MemReadRelative(xIMPOB, maxcNBP)
unless ConvertNBP(maxcNBP, nbp) loop
// If host is now down, raise it
if impNDB>>ImpNDB.hostNotReady then
[
IssueControlCommand(icHostReadyOn)
Dismiss(50)
IssueControlCommand(icNoop) // just get updated status
UpdateStatus(icb>>ICB.controlPost)
]
// Copy the overhead word and the leader(s) from Maxc memory.
// Copy enough words to encompass both Imp and Host-Host leaders,
// though the latter may not actually be needed.
// Word 0 has the message length (Maxc words) in bits 0-15.
// The message itself starts in word 1.
NBReadWord(nbp, 0, maxcWord)
let nMaxcWords = maxcWord!0
if Usc(nMaxcWords, maxMaxcWords) gr 0 then
CallSwat("[ImpProcess] Maxc Imp message too large")
NBBlockTransfer(MemReadBlock40A, nbp, 1, impMaxcBuf,
Min(nMaxcWords, 4), 3)
// Convert the Imp leader (96 bits) from 36-bit Maxc memory format.
let imb = Dequeue(lv impNDB>>ImpNDB.freeOutputIMBQ)
let message = lv imb>>IMB.message
message!0 = impMaxcBuf!0 // 0-15
message!1 = impMaxcBuf!1 // 16-31
message!2 = (impMaxcBuf!2 & #170000) + impMaxcBuf!3 rshift 4 // 32-47
message!3 = impMaxcBuf!3 lshift 12 + impMaxcBuf!4 rshift 4 // 48-63
message!4 = impMaxcBuf!4 lshift 12 + (impMaxcBuf!5 & #170000) rshift 4 +
impMaxcBuf!6 rshift 8 // 64-79
message!5 = impMaxcBuf!6 lshift 8 + impMaxcBuf!7 rshift 8 // 80-95
// Discard 12 garbage bits of Maxc data
// Convert the 36-bit Maxc host leader to a 40-bit Host-Host leader
// by prefixing 4 zero bits. Do this whether or not it's
// appropriate to do so, because we don't yet know whether or not
// MessageFormat will need to look at the byte size.
message!6 = impMaxcBuf!9 rshift 4
message!7 = impMaxcBuf!9 lshift 12 + impMaxcBuf!10 rshift 4
message!8 = impMaxcBuf!10 lshift 12 + impMaxcBuf!11 rshift 4
// ImpProcess (cont'd)
// Imp output from Maxc (cont'd)
// Compute parameters for body transfer
let format = MessageFormat(imb)
let nAltoWords, nMaxcWordsData = nil, nil
switchon format into
[
case mfHost36:
nMaxcWordsData = nMaxcWords-4
nAltoWords = (size HostLeader + 36*nMaxcWordsData +15)/16
endcase
case mfHost32:
nMaxcWordsData = nMaxcWords-4
nAltoWords = (size HostLeader + 32*nMaxcWordsData +15)/16
endcase
case mfRaw32: case mfPup:
nMaxcWordsData = nMaxcWords-3
nAltoWords = (size ImpLeader + 32*nMaxcWordsData +15)/16
endcase
]
if nAltoWords gr maxImpMessWords then // Consistency check
CallSwat("[ImpProcess] Outgoing message too large")
// Transfer body from Maxc and
// convert it from the appropriate Maxc memory format (if necessary)
if nMaxcWordsData gr 0 then switchon MessageFormat(imb) into
[
case mfHost36:
NBBlockTransfer(MemReadBlock40A, nbp, 5, impMaxcBuf,
nMaxcWordsData, 3)
ImpConvFrom36(message+8, impMaxcBuf, nMaxcWordsData+1)
endcase
case mfHost32:
NBBlockTransfer(MemReadBlock40A, nbp, 5, impMaxcBuf,
nMaxcWordsData, 3)
ImpConvFrom32(message+8, impMaxcBuf, nMaxcWordsData+1)
endcase
case mfRaw32:
case mfPup: // Someone trying to send a Pup thru IMPDV??
NBBlockTransfer(MemReadBlock32A, nbp, 4, message+6,
nMaxcWordsData, 2)
endcase
]
// Queue message for transmission to Imp
imb>>IMB.length = nAltoWords
Enqueue(lv impNDB>>ImpNDB.hostOutputIMBQ, imb)
unless impNDB>>ImpNDB.oActive do
CauseInterrupt(impNDB>>ImpNDB.icb>>ICB.outputChanMask)
// Signal Maxc that we have taken the Imp message
MemWriteRelative(xIMPOB, maxcNBP) //set use flag
RMWBitAbsolute(aNVMAX, nmIMPODN)
SignalMaxc()
]
// ImpProcess (cont'd)
if TimerHasExpired(lv statusTimer) then
[ // Do once-per-second housekeeping
SetTimer(lv statusTimer, 100)
IssueControlCommand(icNoop) // Noop, just to get updated status
UpdateStatus(icb>>ICB.controlPost)
// If Maxc fails to accept an input Host-Host protocol packet
// within 15 seconds, drop the Host ready line and flush all buffers.
test impNDB>>ImpNDB.hostInputIMBQ.head eq 0
ifso SetTimer(lv maxcInputTimer, 1500)
ifnot if TimerHasExpired(lv maxcInputTimer) then
ResetImpInterface(true)
// If status has changed, update status word in Maxc memory
if impNDB>>ImpNDB.status ne lastStatus then
[
lastStatus = impNDB>>ImpNDB.status
maxcWord!0, maxcWord!1, maxcWord!2 = 0, lastStatus, 0
MemWriteRelative(xIMPSI, maxcWord)
// If Imp has gone off, flap the interface and flush buffers.
// This is to recover from lost interrupts and such.
if impNDB>>ImpNDB.impNotReady then ResetImpInterface(false)
]
// If Imp and Host are now up and there is no input buffer set up,
// attempt to start input.
if impNDB>>ImpNDB.impNotReady eq 0 & impNDB>>ImpNDB.hostNotReady eq 0 &
impNDB>>ImpNDB.inputIMB eq 0 &
impNDB>>ImpNDB.freeInputIMBQ.head ne 0 then
CauseInterrupt(icb>>ICB.inputChanMask)
]
] repeat
]
//----------------------------------------------------------------------------
and MessageFormat(imb) = valof
//----------------------------------------------------------------------------
// Returns the appropriate message format (mfHost36, mfHost32, mfPup) for
// supplied Imp Message Buffer.
[
let type = imb>>IMB.messageType
// Link number may or may not be used for addressing the message,
// depending on message type
manifest [ y = true; n = false ]
let linkAddressed = type gr 9? true,
(table [ y; n; n; n; n; y; n; y; y; y ])!type
// All non-link-addressed messages are handled as 36-bit Host-Host
unless linkAddressed resultis mfHost36
// Link-addressed messages on the Pup link are given to the Pup handler.
// Note that this does not necessarily mean that they are Pups -- further
// discrimination is required.
let link = imb>>IMB.link
if link eq linkPup resultis mfPup
// Not on the Pup link.
// All irregular messages, regular messages on non-NCP links, and
// regular messages to/from fake hosts are handled as raw 32-bit messages.
if type ne 0 % link gr maxLinkNCP %
imb>>IMB.host ge firstFakeHost resultis mfRaw32
// Regular messages on Host-Host protocol links and with byte size 36
// are handled as 36-bit Host-Host, all others as 32-bit Host-Host
resultis imb>>IMB.byteSize eq 36? mfHost36, mfHost32
]
//----------------------------------------------------------------------------
and ImpInputInterrupt() be
//----------------------------------------------------------------------------
[
let icb = impNDB>>ImpNDB.icb
let imb = impNDB>>ImpNDB.inputIMB
if imb ne 0 then
[ // Dispose of incoming message
let queue = lv impNDB>>ImpNDB.freeInputIMBQ
UpdateStatus(icb>>ICB.inputPost)
switchon icb>>ICB.inputPost.microcode into
[
case isDone:
[ // Normal completion
if impNDB>>ImpNDB.iError then // Clear error, discard this message
[ impNDB>>ImpNDB.iError = false; endcase ]
if imb>>IMB.format ne 17B then endcase // Discard if not new format
imb>>IMB.length = icb>>ICB.inputPointer - lv imb>>IMB.message
test MessageFormat(imb) eq mfPup
ifso // Pups must also be type 0 (regular)
if imb>>IMB.messageType eq 0 &
imb>>IMB.length ge (size ImpLeader/16)+pupOvWords &
imb>>IMB.pup.length le imb>>IMB.length lshift 1 &
imb>>IMB.pup.length le lenPup lshift 1 then
queue = lv impNDB>>ImpNDB.pupInputIMBQ
ifnot // Host-host or irregular
queue = lv impNDB>>ImpNDB.hostInputIMBQ
endcase
]
case isInputOverflow:
[ // Input overflow, cause remainder of message to be discarded
impNDB>>ImpNDB.iError = true
endcase
]
default:
CallSwat("[ImpInputInterrupt] Improper microcode status")
]
Enqueue(queue, imb)
imb = 0
]
unless impNDB>>ImpNDB.impNotReady % impNDB>>ImpNDB.hostNotReady do
[ // Try to start up input
imb = Dequeue(lv impNDB>>ImpNDB.freeInputIMBQ)
if imb ne 0 then
[
icb>>ICB.inputPointer = lv imb>>IMB.message
icb>>ICB.inputEnd = imb+lenIMB
icb>>ICB.inputPost = 0
StartIO(impStartInput)
]
]
impNDB>>ImpNDB.inputIMB = imb
]
//----------------------------------------------------------------------------
and FlushImpInput() be
//----------------------------------------------------------------------------
[
[ // repeat
let imb = Dequeue(lv impNDB>>ImpNDB.hostInputIMBQ)
if imb eq 0 break
Enqueue(lv impNDB>>ImpNDB.freeInputIMBQ, imb)
] repeat
if impNDB>>ImpNDB.inputIMB ne 0 then
[
Enqueue(lv impNDB>>ImpNDB.freeInputIMBQ, impNDB>>ImpNDB.inputIMB)
impNDB>>ImpNDB.inputIMB = 0
]
]
//----------------------------------------------------------------------------
and ImpOutputInterrupt() be
//----------------------------------------------------------------------------
[
let icb = impNDB>>ImpNDB.icb
if impNDB>>ImpNDB.oActive then
[ // Dispose of completed output message
unless icb>>ICB.outputPost.microcode eq isDone do
CallSwat("[ImpOutputInterrupt] Improper microcode status")
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.impNotReady then [ FlushImpOutput(); return ]
test impNDB>>ImpNDB.oError
ifso
[ // Error flop was set. Send a Nop
impNDB>>ImpNDB.oError = false
impNDB>>ImpNDB.sendingNop = true
icb>>ICB.outputPointer = table [ // Nop message
17B lshift 8; 4; 0; 0; 0; 0 ]
icb>>ICB.outputEnd = icb>>ICB.outputPointer+6
]
ifnot
test SetupImpOutput()
ifnot return
ifso // IMB pending
[
let imb = impNDB>>ImpNDB.outputIMB
icb>>ICB.outputPointer = lv imb>>IMB.message
icb>>ICB.outputEnd = icb>>ICB.outputPointer+imb>>IMB.length
]
StartIO(impStartOutput)
impNDB>>ImpNDB.oActive = true
]
//----------------------------------------------------------------------------
and ImpOutputDispose() be
//----------------------------------------------------------------------------
[
if impNDB>>ImpNDB.outputIMB ne 0 then
[
Enqueue(lv impNDB>>ImpNDB.freeOutputIMBQ, impNDB>>ImpNDB.outputIMB)
impNDB>>ImpNDB.outputIMB = 0
]
]
//----------------------------------------------------------------------------
and SetupImpOutput() = valof
//----------------------------------------------------------------------------
// Returns true iff ready to send an IMB.
[
if impNDB>>ImpNDB.outputIMB eq 0 then
impNDB>>ImpNDB.outputIMB = Dequeue(lv impNDB>>ImpNDB.hostOutputIMBQ)
if impNDB>>ImpNDB.outputIMB eq 0 & impNDB>>ImpNDB.pupOQ.head ne 0 then
[
let pbi = Dequeue(lv impNDB>>ImpNDB.pupOQ)
let imb = Dequeue(lv impNDB>>ImpNDB.freeOutputIMBQ)
if imb eq 0 then
CallSwat("[SetupImpOutput] Output idle but freeOutputIMBQ empty")
MoveBlock(lv imb>>IMB.pup, lv pbi>>PBI.pup,
(pbi>>PBI.pup.length +1) rshift 1)
Zero(lv imb>>IMB.message, size ImpLeader/16)
imb>>IMB.format = 17B
imb>>IMB.host = pbi>>ImpPBI.pupHost rshift 6
imb>>IMB.imp = pbi>>ImpPBI.pupHost & 77B
imb>>IMB.link = linkPup
imb>>IMB.length = (size ImpLeader/16)+(pbi>>PBI.pup.length +1) rshift 1
impNDB>>ImpNDB.outputIMB = imb
Enqueue(pbi>>PBI.queue, pbi)
]
resultis impNDB>>ImpNDB.outputIMB ne 0
]
//----------------------------------------------------------------------------
and FlushImpOutput() be while SetupImpOutput() do ImpOutputDispose()
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
and UpdateStatus(status) be
//----------------------------------------------------------------------------
// Update software status from hardware.
[
impNDB>>ImpNDB.impNotReady = status<<ImpStatus.impNotReady
impNDB>>ImpNDB.hostNotReady = status<<ImpStatus.hostNotReady
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 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()
]