// DDTapeIO.bcpl - Dual Density Tape Low Level Driver
//
// Dual Density Tape Low Level I/O Routines
// Last modified by Tim Diebert, March 27, 1981 11:43 AM
// Copyright Xerox, 1981
//


get "DDTapes.d"

external
[
// Procedures from the ALTO Operating System
Allocate
CallSwat
DefaultArgs
Free
StartIO
sysZone
Zero

// Procedures from the User Program
TapeWaitQuiet // Procedure that waits for other microcode tasks to complete

// Procedures from the User Program or the ALTO Context package
Block

// Procedures from the ALTO Timer Package
InitializeTimer
SetTimer
TimerHasExpired
Dismiss

]

static
[
TapeActive = false
TapeOpened
]


//---------------------------------------------------------------------------------
let ActOnDDTape(vtape, function, rbuf, rcnt, wbuf, wcnt, retries, readafterwrite; numargs nu) = valof
//---------------------------------------------------------------------------------
// Performs operations on tape with retry
// errors are reported to vtape>>VDDTCB.errProc
// operator intervention is reported to vtape>>VDDTCB.interventionProc
//
// Returns:
//
controller status on non R/W operations
// byte count on R/W operations
// -1 on Hardware error (timeout)
// -2 on illegal function
// -3 on Not Ready or Offline
// -4 on End of File
// -5 on End of Tape
// -6 on Soft error
// -7 on Other Tape Error
// -8 on File protect error
//
// Calls of interest to the user:
//
vtape>>VDDTCB.errProc on error
//
vtape>>VDDTCB.interventionProc operator intervention
//
[
DefaultArgs(lv nu, 2, 0, 0, 0, 0, 4, false)

let actualstatus = nil
let tempstatus = nil
let errproc = vtape>>VDDTCB.errProc
let interventionproc = vtape>>VDDTCB.interventionProc

// Check to see if function is invalid
if (function ls 0) % (function gr #37) do
[
errproc("*nActOnTape: Call with Illegal Function")
vtape>>VDDTCB.CMDER = 1
resultis -2
]

// Check and correct number of retries (8 max)
if (retries ls 0) % (retries gr 8) then retries = 8

// Get everything set up for retry
vtape>>VDDTCB.retries = retries
vtape>>VDDTCB.regap = retries
vtape>>VDDTCB.lowthresh = false
vtape>>VDDTCB.operation = function

// check for device On-Line
let currentstatus = PerformVDDTCB(vtape, NoOp)
unless currentstatus do
[
vtape>>VDDTCB.HDWERR = 1
resultis -1
] // return -1 on hung controller
unless currentstatus<<DDStatus.ONL & currentstatus<<DDStatus.RDY then
[
interventionproc("*nTape Drive Offline or Not Ready")
vtape>>VDDTCB.CMDER = 1
resultis -3
]


// PERFORM THE REQUESTED FUNCTION

test (function eq NoOp) % (function eq Rewind) % (function eq Unload)
ifso
[
actualstatus = PerformVDDTCB(vtape, function)
resultis actualstatus
]

ifnot
[
if (((function & #37) & #20) eq #20) & currentstatus<<DDStatus.BOT do
[
errproc("*nActOnTape: Tape is at BOT prior to a REV motion command")
vtape>>VDDTCB.CMDER = 1
resultis -2
]
]

[
[
actualstatus = PerformVDDTCB(vtape, function, rbuf, rcnt, wbuf, wcnt, readafterwrite)
unless actualstatus do
[
vtape>>VDDTCB.HDWERR = 1
resultis -1 // return on hung device
]
if ((#7 & actualstatus) ne 0) do
[
if (actualstatus & #1) ne 0 do
[
errproc("*nActOnTape: Microcode detected Command Error")
vtape>>VDDTCB.CMDER = 1
resultis -2 //ilegal function
]

if (actualstatus & #2) ne 0 do
[
interventionproc("*nTape is Write Protected")
vtape>>VDDTCB.CMDER = 1
resultis -8
]

vtape>>VDDTCB.HDWERR = 1
resultis -1
]

// Check to see if the operation has good ending status or the retry count is used up
let breakout = valof
[
if ((TapeErr & actualstatus) eq 0) % (retries eq 0) % FunctionNotRetryable(function) do resultis true
test vtape>>VDDTCB.WRT
ifnot
[
if vtape>>VDDTCB.lowthresh & (vtape>>VDDTCB.retries le 0) do resultis true
]

ifso
[
if (vtape>>VDDTCB.retries le 0) & (vtape>>VDDTCB.regap le 0) do resultis true
]
resultis false
]
if breakout do break


// retry the operation after backing up, ignoring errors
tempstatus = PerformVDDTCB(vtape, (vtape>>VDDTCB.REV ? FwdSpaceRecord, BackSpaceRecord) )
unless tempstatus do
[
vtape>>VDDTCB.HDWERR = 1
resultis -1
]
vtape>>VDDTCB.retries = vtape>>VDDTCB.retries - 1

// Generate a long wrt gap if the bad operation was a write and the retry count was
// gone
test vtape>>VDDTCB.WRT
ifso
[
if (vtape>>VDDTCB.retries le 0) & (vtape>>VDDTCB.regap le 0) then
[ // Set up to retry the operation a little way farther down on the tape
vtape>>VDDTCB.retries = retries
vtape>>VDDTCB.regap = vtape>>VDDTCB.regap - 1
unless PerformVDDTCB(vtape, Erase) do resultis -1 // Write a longer gap
]
]

ifnot
[

// Use the low threshold if the bad operation was a read op and the retry count was gone
if (vtape>>VDDTCB.retries le 0) & (not vtape>>VDDTCB.lowthresh) then
[ // Set up to retry the operation with low threshold set
vtape>>VDDTCB.retries = retries
vtape>>VDDTCB.lowthresh = true
vtape>>VDDTCB.Threshold = 1
]
]
] repeat
]

// Build the ending status
if vtape>>VDDTCB.FMK then resultis -4
if vtape>>VDDTCB.EOT then resultis -5
if vtape>>VDDTCB.SE do
[
errproc(ErrmsgTapeCB(vtape))
resultis -6
]

if (TapeErr & actualstatus) eq 0 then resultis vtape>>VDDTCB.ByteCount

// The ending status was not good so look up the text for the error and call the error handler
errproc(ErrmsgTapeCB(vtape))
resultis -7
]


//---------------------------------------------------------------------------------
and CloseDDTape(vtape) be
//---------------------------------------------------------------------------------
// Releases the storage for vtape and markes the drive as available in TapeOpened
//
[
if (vtape ne 0) do
[
TapeOpened ! (vtape>>VDDTCB.drive) = false
Free(sysZone,vtape)
]
]

//---------------------------------------------------------------------------------
and EraseInches(vtape, inches) = valof
//---------------------------------------------------------------------------------
// This routine erases "inches" amount of tape (three is a typical figure).
// An "inches" value of zero or less is a no-op, and 72 is the maximum. Any number
// greater than 72 is taken as 72. Inches does not default.
// Note: A standard gap is erased in addition to the erase length specified.
//
//
[
if inches ls 0 then resultis false
if inches gr 72 then inches = 72
resultis PerformVDDTCB(vtape, EraseVar, inches * vtape>>VDDTCB.opDensity)
]

//---------------------------------------------------------------------------------
and FileSkipFwd(vtape) = ActOnDDTape(vtape, FwdSpaceFile)
//---------------------------------------------------------------------------------
// Forward Space File passes over tape records until an EOF or EOT is encountered
//

//---------------------------------------------------------------------------------
and FileSkipRev(vtape)=ActOnDDTape(vtape, BackSpaceFile)
//---------------------------------------------------------------------------------
// Reverse Space File passes over tape records in the reverse direction until EOF or BOT

//---------------------------------------------------------------------------------
and OpenDDTape(unit, errproc, interventionproc; numargs nu) = valof
//---------------------------------------------------------------------------------
// Allocates a VDDTCB area for the unit specified, sets up the pointer to the errProc.
//
// Returns:
// 0 if Open was Unsuccessful
// pointer to the VDDTCB if Successful
//
// Calls of interest to the user:
// errproc
//
[
DefaultArgs(lv nu, 0, 0, CallSwat, CallSwat)

// Check for Valid Tape Unit Number
if (unit ls 0) % (unit gr MaxUnitNum ) do
[
errproc("*nIllegal Tape Unit Number")
resultis 0
]

// Allocate some space for the VDDTCB
let vtape = Allocate(sysZone, lVDDTCB, true, true)
unless vtape do
[
errproc("*nSystem Error: Unable to allocate space for VDDTCB")
resultis 0
]
Zero(vtape, lVDDTCB)

// Check to see if unit is already opened.
if TapeOpened ! unit do
[
errproc("*nOpen command issued on tape unit which was already open.")
Free(sysZone, vtape)
resultis 0
]

// Set up areas in the VDDTCB that relate to the drive we just opened.
vtape>>VDDTCB.drive = unit
vtape>>VDDTCB.errProc = errproc
vtape>>VDDTCB.interventionProc = interventionproc
vtape>>VDDTCB.Command = 0
vtape>>VDDTCB.opDensity = PE

// Indicate the drive is now opened in TapeOpened
TapeOpened ! unit = true

// Initalize everything
let stat = PerformVDDTCB(vtape, NoOp)
if vtape>>VDDTCB.NRZI then vtape>>VDDTCB.opDensity = NRZI


resultis vtape
]

//---------------------------------------------------------------------------------
and PerformVDDTCB(vtape, function, readbuf, readcount, writebuf, writecount,
readafterwrite; numargs nu) = valof
//---------------------------------------------------------------------------------
// Sets up and executes a TCB located inside the VDDTCB area to perform the
// requested funtion.
//
// Returns:
// 0 on tape operation timeout
// hardware status if no timeout
//
// Calls of interest to the user:
// vtape>>VDDTCB.errProc on any error execpt an attempt to operate
// on a drive that has not been opened, in which case CallSwat is executed.
//
[
DefaultArgs(lv nu, 2, 0, 0, 0, 0, false)

// Is vtape allocated?
unless vtape do
[
CallSwat("*nTapeIO: PerformVDDTCB called without a VDDTCB being allocated")
resultis 0
]


// Is the drive open?
unless TapeOpened ! (vtape>>VDDTCB.drive) do
[
CallSwat("*nTapeIO: PerformVDDTCB called without a drive being Opened")
resultis 0
]

// Check for reset.
if function eq Reset do
[
Zero(vtape, lDDTCB)
vtape>>VDDTCB.Unit = 0
vtape>>VDDTCB.Command = 0
vtape>>DDTCB.Density = 0 // assume PE mode
if vtape>>VDDTCB.opDensity eq NRZI do
vtape>>DDTCB.Density = 1
vtape>>DDTCB.Opcode = NoOp
@DDTCBStart=vtape
StartIO(TapeResetOp)

// DO the reset
Dismiss(4)
resultis vtape>>DDTCB.Flags
]

// set up the hardware TCB
let opcode = nil
Zero(vtape, lDDTCB)


vtape>>DDTCB.WriteCount = writecount
vtape>>DDTCB.WriteBuffer = writebuf
vtape>>DDTCB.ReadCount = readcount
vtape>>DDTCB.ReadBuffer = readbuf
vtape>>DDTCB.Unit = vtape>>VDDTCB.drive
if vtape>>VDDTCB.lowthresh then vtape>>DDTCB.Threshold = 1
if readafterwrite then vtape>>DDTCB.ReadAfterWrite = 1
vtape>>DDTCB.Density = 0 // assume PE mode
if vtape>>VDDTCB.opDensity eq NRZI do
vtape>>DDTCB.Density = 1

// ERASE functions takes 3rd parameter as write byte count for erase timing
if function eq EraseVar do
vtape>>DDTCB.WriteCount = readbuf


// PerformVDDTCB (cont’d)

// Set up opcode
vtape>>DDTCB.Opcode = (function & #37)

// WAIT FOR ANY PENDING TRIDENT OR TAPE I/O FOR BANDWIDTH’S SAKE
TapeWaitQuiet()
while TapeActive do Block()
TapeActive = true

// Check to see if we will transfer data so we can turn off the display

let DispOffFlag = false
let DCBStartSave = nil

switchon function into
[
case ReadFwd:
case ReadRev:
case ReadRevEdit:
case Write:
case WriteEdit:
case EraseVar:
[
if @DCBStart ne 0 do
[
DispOffFlag = true
DCBStartSave = @DCBStart
@DCBStart = 0
DDTapeVertIntFlag = false
until DDTapeVertIntFlag do Block()
]
]
endcase
]

// start the instruction
@DDTCBStart = vtape

StartIO(TapeNormalOp)

unless function eq Unload do WaitforTapeStatus(vtape, function)

if DispOffFlag do @DCBStart = DCBStartSave


TapeActive = false

// return with the controller status
resultis vtape>>DDTCB.Flags
]


//---------------------------------------------------------------------------------
and RecSkipFwd(vtape) = ActOnDDTape(vtape, FwdSpaceRecord)
//---------------------------------------------------------------------------------
// Skips one record forward

//---------------------------------------------------------------------------------
and RecSkipRev(vtape) = ActOnDDTape(vtape, BackSpaceRecord)
//---------------------------------------------------------------------------------
// Skips one record reverse

//---------------------------------------------------------------------------------
and SetDensity(vtape, density) = valof
//---------------------------------------------------------------------------------
// Sets the tape density.
[


// Check for VDDTCB allocated
unless vtape do
[
CallSwat("*nTapeIO: No VDDTCB allocated")
resultis false
]

let errproc = vtape>>VDDTCB.errProc

// Check for Valid Density Selection
unless (density eq PE) % (density eq NRZI) do
[
errproc("*nSpecified Tape Density Not Supported")
resultis false
]

let flags = PerformVDDTCB(vtape, NoOp)
unless flags do resultis false

let DensitySameFlag = false
test flags<<DDStatus.NRZI
ifso
if (density eq NRZI) do
[
DensitySameFlag = true
]
ifnot
if (density eq PE) do
[
DensitySameFlag = true
]

if flags<<DDStatus.BOT do
[
vtape>>VDDTCB.opDensity = density
resultis true
]

if (not vtape>>VDDTCB.BOT) & DensitySameFlag do resultis true
resultis false
]

//---------------------------------------------------------------------------------
and TapeEOF(vtape) = ActOnDDTape(vtape, WriteEOF)
//---------------------------------------------------------------------------------

//---------------------------------------------------------------------------------
and TapeStatus(vtape) = PerformVDDTCB(vtape, NoOp)
//---------------------------------------------------------------------------------

//---------------------------------------------------------------------------------
and TapeRewind(vtape) = PerformVDDTCB(vtape, Rewind)
//---------------------------------------------------------------------------------
// Rewinds the tape

//---------------------------------------------------------------------------------
and TapeRewindWait(vtape) = valof
//---------------------------------------------------------------------------------
// Rewinds the tape
//
// Returns:
// true if the rewind completed
// false if the rewind timeout
//
// Calls of interest to the user:
// vtape>>VDDTCB.errProc on error (via WaitTapeReady)
//
[
PerformVDDTCB(vtape, Rewind)
let tempstatus = WaitTapeReady(vtape)
resultis tempstatus
]

//---------------------------------------------------------------------------------
and TapeUnload(vtape) be
//---------------------------------------------------------------------------------
[
TapeRewindWait(vtape)
PerformVDDTCB(vtape, Unload)
]

//---------------------------------------------------------------------------------
and WaitTapeReady(vtape) = valof
//---------------------------------------------------------------------------------
// Waits for the drive to report ready
//
// Returns:
// true if drive came ready before the timeout
// false if timeout
//
[
let tempstatus = nil
let Timer = nil
SetTimer(lv Timer, RewindTimeout) // Timer for rewind time 2400’ @ 150 ips

[
Block()
tempstatus = PerformVDDTCB(vtape, NoOp)
] repeatuntil tempstatus<<DDStatus.RDY % TimerHasExpired(lv Timer)

unless tempstatus<<DDStatus.RDY resultis false
resultis true
]

// Local Procedures to help make life easier


//---------------------------------------------------------------------------------
and ErrmsgTapeCB(vtape) = valof
//---------------------------------------------------------------------------------
// formats an error message that summarizes the controller status
//
// Returns:
// text line describing error
//
[


if vtape>>VDDTCB.HE then resultis "*n Ending Status Bad HE"
if vtape>>VDDTCB.SE then resultis "*n Ending Status Bad SE"
if vtape>>VDDTCB.DL then resultis "*n Ending Status Bad DL"
if vtape>>VDDTCB.RDP then resultis "*n Ending Status Bad RDP"
if vtape>>VDDTCB.ICL then resultis "*n Ending Status Bad ICL"
if vtape>>VDDTCB.HDWERR then resultis "*n Ending Status Bad HDWERR"
if vtape>>VDDTCB.WFP then resultis "*n Ending Status Bad WFP"
if vtape>>VDDTCB.CMDER then resultis "*n Ending Status Bad CMDER"


// Error messages in order of precedence
resultis "*n NOT OK"
]

//---------------------------------------------------------------------------------
and FunctionNotRetryable(function) = valof
//---------------------------------------------------------------------------------
// test for not retryable function codes
//
// Returns:
// true if not retryable
//
[

if (function eq BackSpaceFile) % (function eq FwdSpaceFile) % (function eq Erase) % (function eq EraseVar) % (function eq Reset) resultis true

resultis false
]

//---------------------------------------------------------------------------------
and WaitforTapeStatus(vtape, function) be
//---------------------------------------------------------------------------------
// Causes a wait for vtape>>TCB.Flags ne 0 or a {3, 1} sec timeout at {45, 125} IPS
//
// Returns:
// vtape>>TCB.Flags = 0 if Timeout
// vtape>>TCB.Flags = Hardware DDStatus if not Timeout
//
// Calls:
// vtape>>VDDTCB.errProc on Timeout.
//
[

TapeActive = true
vtape>>VDDTCB.timeout = false

// Wait for the required time.
let Timer = nil
let alarmtime = RecordTimeout
if (function eq FwdSpaceFile) % (function eq BackSpaceFile) do alarmtime = FileTimeout
if (function eq NoOp) do alarmtime = NoOpTimeout
SetTimer(lv Timer, alarmtime)
until (vtape>>DDTCB.Flags ne 0) % TimerHasExpired(lv Timer) do Block()

// Done waiting. If the status from the controller is still zeros, the timer expired.
if vtape>>DDTCB.Flags eq 0 do
[
vtape>>VDDTCB.timeout = true
let errproc = vtape>>VDDTCB.errProc
// Timer expired: Reset the microcode and wait for the controller to settle.
vtape>>DDTCB.Opcode = NoOp
@DDTCBStart=vtape
StartIO(TapeResetOp)
Dismiss(4)

// Check to see if operation was a NOP
test (function eq NoOp) % (function eq Reset)
ifso
[ // Timeout on a NOP indicates a hardware problem
errproc("*nTape Controller Timeout on NoOp; Possible Hardware Problem")
vtape>>DDTCB.Flags = 0
]
ifnot
[
test vtape>>DDTCB.BOT
ifso
[ // Timeout on a motion command @ BOT indicates a hardware problem
errproc("*nTape Controller Timeout at BOT; Possible Hardware Problem")
vtape>>DDTCB.Flags = 0
]
ifnot
[ // Timeout on a motion command not @ BOT may indicate a blank tape
errproc("*nTape Controller Timeout; Possible Blank Tape")
vtape>>DDTCB.Flags = 0
]
]
]
TapeActive = false
]