-- DiskHeadDorado.mesa
-- Last Edited by: Taft, February 24, 1983 5:26 pm

DIRECTORY
  DeviceCleanup USING [Await, Item, Reason],
  DoradoInputOutput USING [IOAddress, DMuxAddr, Output, ResetDisk, RWMufMan],
  Environment USING [Base],
  HeadStartChain USING [Start],
  Inline USING [BITAND, BITOR, LowHalf],
  PilotDisk USING [Handle, Label],
  SA4000Face;

DiskHeadDorado: PROGRAM
  IMPORTS DeviceCleanup, DoradoInputOutput, RemainingHeads: HeadStartChain, Inline
  EXPORTS HeadStartChain, SA4000Face
  SHARES SA4000Face =
  BEGIN OPEN SA4000Face;

  -- Data structures shared with disk microcode

  CSBPtr: TYPE = LONG POINTER TO CSB;
  CSB: TYPE = MACHINE DEPENDENT RECORD [
    head (0): IOCBQueueHead,
    interruptMask (1): WORD,
    drive (2): Drive, -- drive currently selected
    cylinder (3): CARDINAL, -- negative => need to restore disk
    -- Remainder of CSB is not used by the microcode 
    tail (4): IOCBLongPtr];

  IOCBQueueHead: TYPE = MACHINE DEPENDENT RECORD [
    SELECT OVERLAID * FROM
      untagged => [iocb: IOCBShortPtr],
      tagged => [highBits: [0..77777B], tag: {idle, active}],
      ENDCASE];

  nilIOCBQueueHead: IOCBQueueHead = [untagged[iocb: nilIOCBShortPtr]];

  -- IOCB must be odd word aligned
  IOCBShortPtr: TYPE = Environment.Base RELATIVE POINTER TO IOCB;
  IOCBLongPtr: TYPE = LONG POINTER TO IOCB;
  IOCB: TYPE = MACHINE DEPENDENT RECORD [
    next (0B): IOCBShortPtr,
    seal (1B): CARDINAL,
    drive (2B): Drive,
    pageCount (3B): CARDINAL,
    command (4B): DiskCommand,
    diskAddress (5B): DiskAddress,
    diskHeader (7B): DiskAddress,
    headerPtr (11B): LONG POINTER TO DiskAddress,
    headerECC (13B): LONG CARDINAL,
    headerStatus (15B): DiskStatus,
    labelPtr (16B): LONG POINTER TO PilotDisk.Label,
    labelECC (20B): LONG CARDINAL,
    labelStatus (22B): DiskStatus,
    dataPtr (23B): LONG POINTER,
    dataECC (25B): LONG CARDINAL,
    dataStatus (27B): DiskStatus,
    diskLabel (30B): PilotDisk.Label];

  nilIOCBShortPtr: IOCBShortPtr = LOOPHOLE[0];

  DoradoOperationPtr: TYPE = LONG POINTER TO DoradoOperation;
  DoradoOperation: TYPE = MACHINE DEPENDENT RECORD [
    operation (0): Operation,
    -- Guarantee required odd word alignment, since Operation is 16-word aligned
    iocb (15B): IOCB];

  DiskStatus: TYPE = MACHINE DEPENDENT RECORD [
    seekInc (0: 0..0): BOOLEAN,
    headOvfl (0: 1..1): BOOLEAN,
    devCheck (0: 2..2): BOOLEAN,
    notSelected (0: 3..3): BOOLEAN,
    notOnLine (0: 4..4): BOOLEAN,
    notReady (0: 5..5): BOOLEAN,
    sectorOvfl (0: 6..6): BOOLEAN,
    fifoUnderflow (0: 7..7): BOOLEAN,
    fifoOverflow (0: 8..8): BOOLEAN,
    checkErr (0: 9..9): BOOLEAN,
    readOnly (0: 10..10): BOOLEAN,
    cylOffset (0: 11..11): BOOLEAN,
    iobParityErr (0: 12..12): BOOLEAN,
    fifoParityErr (0: 13..13): BOOLEAN,
    eccErr (0: 14..14): BOOLEAN,
    sectorSearchErr (0: 15..15): BOOLEAN];

  nullDiskStatus: DiskStatus = LOOPHOLE[0];
  errorStatusMask: DiskStatus = [
    -- These bits constitute errors:
    seekInc: TRUE, headOvfl: TRUE, devCheck: TRUE, notSelected: TRUE, notOnLine: TRUE,
    notReady: TRUE, sectorOvfl: TRUE, fifoUnderflow: TRUE, fifoOverflow: TRUE,
    checkErr: TRUE, iobParityErr: TRUE, fifoParityErr: TRUE, eccErr: TRUE,
    sectorSearchErr: TRUE,
    -- These are just status bits that do not necessarily constitute errors:
    readOnly: FALSE, cylOffset: FALSE];

  sealValid: CARDINAL = 125377B;
  sealNil: CARDINAL = 0;

  cylinderRestore: CARDINAL = 177777B;
  cylinderUnknown: CARDINAL = 77777B;


  -- Disk hardware definitions

  -- Output to DiskControl register, and used as diskCommand in IOCBs
  diskControl: DoradoInputOutput.IOAddress = 10B;
  DiskCommand: TYPE = MACHINE DEPENDENT RECORD [
    incrementDataPtr (0: 0..0): BOOLEAN ← FALSE, -- in IOCB only; not defined in hardware
    unused1 (0: 1..4): Filler,
    clearEnableRun (0: 5..5): BOOLEAN ← FALSE,
    debugMode (0: 6..6): BOOLEAN ← FALSE,
    blockTilIndex (0: 7..7): BOOLEAN ← FALSE,
    header (0: 8..9): Action ← none,
    label (0: 10..11): Action ← none,
    data (0: 12..13): Action ← none,
    unused2 (0: 14..15): Action ← none];
  Action: TYPE = MACHINE DEPENDENT {none, write, check, read};

  -- Output to DiskMuff register
  diskMuff: DoradoInputOutput.IOAddress = 11B;
  MuffCommand: TYPE = MACHINE DEPENDENT RECORD [
    unused (0: 0..1): Filler,
    clearCompareErr (0: 2..2): BOOLEAN ← FALSE,
    setChecksumErr (0: 3..3): BOOLEAN ← FALSE,
    clearIndexTW (0: 4..4): BOOLEAN ← FALSE,
    clearSectorTW (0: 5..5): BOOLEAN ← FALSE,
    clearSeekTagTW (0: 6..6): BOOLEAN ← FALSE,
    clearErrors (0: 7..7): BOOLEAN ← FALSE,
    muffAddr (0: 8..15): MufflerAddress ← tempSense];

  -- Input from DiskMuff register
  MuffInput: TYPE = MACHINE DEPENDENT RECORD [
    unused (0: 0..14): Filler,
    bit (0: 15..15): BOOLEAN];

  -- Output to DiskTag register
  diskTag: DoradoInputOutput.IOAddress = 14B;
  TagCommand: TYPE = MACHINE DEPENDENT RECORD [
    driveTag (0: 0..0): BOOLEAN ← FALSE,
    cylinderTag (0: 1..1): BOOLEAN ← FALSE,
    headTag (0: 2..2): BOOLEAN ← FALSE,
    controlTag (0: 3..3): BOOLEAN ← FALSE,
    bus (0: 4..15): SELECT OVERLAID * FROM
      drive => [
        unused (0: 4..5): Filler,
        subSectorCount (0: 6..9): [0..17B] ← 0, -- sub-sectors/sector - 1
        loadSubSector (0: 10..10): BOOLEAN ← FALSE,
        select (0: 11..11): BOOLEAN ← FALSE,
        driveNumber (0: 12..15): Drive],
      cylinder => [
        cylinder (0: 4..15): [0..7777B]],
      head => [
        unused (0: 4..7): Filler,
        offset (0: 8..8): BOOLEAN ← FALSE,
        offsetDirection (0: 9..9): {out(0), in(1)} ← out,
        headNumber (0: 10..15): [0..77B]],
      control => [
        syncPattern (0: 4..4): {s201, s001} ← s201, -- s001 is Alto-compatible
        unused (0: 5..5): Filler,
        strobeLate (0: 6..6): BOOLEAN ← FALSE,
        strobeEarly (0: 7..7): BOOLEAN ← FALSE,
        write (0: 8..8): BOOLEAN ← FALSE,
        read (0: 9..9): BOOLEAN ← FALSE,
        addressMark (0: 10..10): BOOLEAN ← FALSE,
        headReset (0: 11..11): BOOLEAN ← FALSE,
        deviceCheckReset (0: 12..12): BOOLEAN ← FALSE,
        headSelect (0: 13..13): BOOLEAN ← FALSE,
        rezero (0: 14..14): BOOLEAN ← FALSE,
        headAdvance (0: 15..15): BOOLEAN ← FALSE],
      ENDCASE];

  -- Muffler addresses for status bits (DskEth-relative)
  MufflerAddress: TYPE = MACHINE DEPENDENT {
    tempSense(0), indexTW(1), sectorTW(2), seekTagTW(3), rdFifoTW(4), wrFifoTW(5),
    readData(6), writeData(7), enableRun(10B), debugMode(11B), notRdOnlyBlock(12B),
    notWriteBlock(13B), notCheckBlock(14B), active(15B), select0(16B), select1(17B),
    seekInc(20B), headOvfl(21B), devCheck(22B), notSelected(23B), notOnLine(24B),
    notReady(25B), sectorOvfl(26B), fifoUnderflow(27B), fifoOverflow(30B),
    readDataErr(31B), readOnly(32B), cylOffset(33B), iobParityErr(34B),
    fifoParityErr(35B), writeError(36B), readError(37B),
    -- others of interest only to Midas
    (177B)};
  
  diskDMuxAddr: [0..7777B] = 2000B;  -- DMux address of first disk muffler

  Filler: TYPE = [0..1] ← 0;


  -- Data structures private to the software

  DiskHandle: TYPE = MACHINE DEPENDENT RECORD [
    unused (0: 0..7): Filler,
    drive (0: 8..15): Drive];
  nullDiskHandle: DiskHandle = LOOPHOLE [177777B];

  Drive: TYPE = [0..drives);
  drives: CARDINAL = 16; -- maximum number of drives per controller

  -- The "system" drive is drive 0, which is always assumed to be a T-80 that is
  -- addressed in a funny way.  t80 and t300 refer to drives other than drive 0,
  -- which are addressed in the normal way.
  Model: TYPE = {nonexistent, system, t80, t300};

  modelCylinders: ARRAY Model OF CARDINAL = [
    nonexistent: 0, system: 815*5, t80: 815, t300: 815];
  modelHeads: ARRAY Model OF CARDINAL = [
    nonexistent: 0, system: 1, t80: 5, t300: 19];
  modelSectors: ARRAY Model OF CARDINAL = [
    nonexistent: 0, system: 28, t80: 28, t300: 28];


  -- Public variables

  globalStateSize: PUBLIC CARDINAL ← 0;
  -- an IOCB for label fixup is not required for the Dorado implementation.
  nullDeviceHandle: PUBLIC DeviceHandle ← DeviceFromDiskHandle[nullDiskHandle];
  operationSize: PUBLIC CARDINAL ← SIZE[DoradoOperation];


  -- Private variables

  csb: CSBPtr = LOOPHOLE[LONG[177520B]];
  totalErrors: CARDINAL ← 0; -- = total errors reported by Poll
  modelTable: ARRAY [0..drives) OF Model ← ALL [nonexistent];


  -- Public procedures

  -- Assumes Initialize is called before GetDeviceAttributes
  GetDeviceAttributes: PUBLIC PROCEDURE [device: DeviceHandle]
    RETURNS [cylinders, movingHeads, fixedHeads, sectorsPerTrack: CARDINAL] =
    BEGIN
    diskHandle: DiskHandle = DiskFromDeviceHandle[device];
    model: Model = modelTable[diskHandle.drive];
    RETURN [
     cylinders: modelCylinders[model], movingHeads: modelHeads[model],
     fixedHeads: 0, sectorsPerTrack: modelSectors[model]];
    END;

  -- Assumes Initialize is called before GetNextDevice
  GetNextDevice: PUBLIC PROCEDURE [device: DeviceHandle] RETURNS [DeviceHandle] =
    BEGIN
    disk: DiskHandle = DiskFromDeviceHandle[device];
    IF disk=nullDiskHandle THEN  -- drive 0 presumed to exist always
      RETURN [DeviceFromDiskHandle[[drive: 0]]];
    FOR drive: Drive IN (disk.drive..drives) DO
      IF modelTable[drive]#nonexistent THEN RETURN [DeviceFromDiskHandle[[drive: drive]]];
      ENDLOOP;
    RETURN [nullDeviceHandle];
    END;

  -- Assumes that the disk hardware and microcode are quiescent
  Initialize: PUBLIC PROCEDURE [t: WORD, globalState: GlobalStatePtr] =
    BEGIN OPEN DoradoInputOutput;
    ManualStrobe: PROCEDURE [tag, data: TagCommand] =
      BEGIN
      Output[data, diskTag];
      Output[Inline.BITOR[tag, data], diskTag];
      Output[data, diskTag];
      -- No need to wait: drive is advertised to return status within 200 ns
      -- of its being selected.
      END;
    AutoStrobe: PROCEDURE [data: TagCommand] =
      BEGIN
      Output[data, diskTag];
      -- Need to wait at least 1.2 microseconds for strobe sequence to complete,
      -- plus an unspecified amount of time (probably less than a microsecond)
      -- for the drive to return up-to-date status.
      THROUGH [0..10) DO NULL; ENDLOOP;
      END;
    ReadMuffler: PROCEDURE [addr: MufflerAddress] RETURNS [status: BOOLEAN] =
      BEGIN
      -- Reads through system DMux rather than through DskEth muffler interface, because
      -- it's impossible for the disk task to dismiss wakeups without clobbering the
      -- muffler address.
      RETURN [
        DoradoInputOutput.RWMufMan[
          [useDMD: FALSE, dMuxAddr: DMuxFromMufAddr[addr]]].dMuxData#0];
      END;
    ClassifyDrive: PROCEDURE [drive: Drive] RETURNS [model: Model] =
      BEGIN
      -- Select the drive.  If it doesn't become selected then it doesn't exist.
      ManualStrobe[tag: [driveTag: TRUE, bus: drive[driveNumber: 0]],
        data: [bus: drive[select: TRUE, driveNumber: drive]]];
      IF ReadMuffler[notSelected] THEN RETURN [nonexistent]
      ELSE BEGIN
        -- Try to select a head which does not exist on a T80.
        -- This causes a headOvfl error on a T80, not on a T300
        AutoStrobe[[controlTag: TRUE, bus: control[deviceCheckReset: TRUE]]];
        AutoStrobe[[headTag: TRUE, bus: head[headNumber: modelHeads[t80]]]];
        model ← IF ReadMuffler[headOvfl] THEN t80 ELSE t300;
        AutoStrobe[[controlTag: TRUE, bus: control[deviceCheckReset: TRUE]]];
        END;
      END;
    lastDrive: Drive;
    ResetDisk[disable]; -- disable disk task so it won't run and confuse matters
    csb↑ ← [
      head: nilIOCBQueueHead, interruptMask: t, drive: 0,
      cylinder: cylinderUnknown, tail: NIL];
    modelTable[0] ← system;  -- drive 0 presumed to exist always
    -- Drives 0-2 are connected directly to the controller, and drive 3 also if
    -- no multiplexor is present.
    -- If a multiplexor is present, it is connected in place of drive 3.
    -- Drive 17B is presumed not to exist.  Try selecting drive 17B.  If this succeeds
    -- then what really happened is that no multiplexor is present and drive 3
    -- got selected instead.  If this fails then either there is a multiplexor
    -- or there is no multiplexor and no drive 3 is present; it is safe to test each
    -- of drives 4-16 since in this case they will select only if there is a multiplexor.
    lastDrive ← (IF ClassifyDrive[LAST[Drive]]=nonexistent THEN LAST[Drive]-1 ELSE 3);
    FOR drive: Drive IN [1..lastDrive] DO
      modelTable[drive] ← ClassifyDrive[drive];
      ENDLOOP;
    ResetDisk[normal]; -- put disk microcode back in normal state
    END;

  InitializeCleanup: PUBLIC PROCEDURE =
    BEGIN OPEN DeviceCleanup;
    item: Item;
    reason: Reason;
    savedCSB: CSB;
    DO
      reason ← Await[@item];
      SELECT reason FROM
	turnOff, kill =>
	  BEGIN
	  UNTIL csb.head.tag = idle DO ENDLOOP;
	  savedCSB ← csb↑;
	  csb.head.iocb ← nilIOCBShortPtr;
	  END;
	turnOn =>
	  BEGIN
	  csb↑ ← savedCSB;
	  csb.cylinder ← cylinderUnknown;
	  END;
	ENDCASE
      ENDLOOP
    END;

  Initiate: PUBLIC PROCEDURE [operationPtr: OperationPtr] =
    BEGIN
    doradoOperationPtr: DoradoOperationPtr = LOOPHOLE[operationPtr];
    iocb: IOCBLongPtr = @doradoOperationPtr.iocb;
    iocbShort: IOCBShortPtr = LOOPHOLE[Inline.LowHalf[iocb]];
    diskCommand: DiskCommand ← SELECT operationPtr.command FROM
      vv => [incrementDataPtr: FALSE, header: check, label: check, data: none],
      vvr => [incrementDataPtr: FALSE, header: check, label: check, data: read],
      vvw => [incrementDataPtr: FALSE, header: check, label: check, data: write],
      vvv => [incrementDataPtr: FALSE, header: check, label: check, data: check],
      vw => [incrementDataPtr: FALSE, header: check, label: write, data: none],
      vww => [incrementDataPtr: FALSE, header: check, label: write, data: write],
      vr => [incrementDataPtr: FALSE, header: check, label: read, data: none],
      vrr => [incrementDataPtr: FALSE, header: check, label: read, data: read],
      vrw => [incrementDataPtr: FALSE, header: check, label: read, data: write],
      vrv => [incrementDataPtr: FALSE, header: check, label: read, data: check],
      rv => [incrementDataPtr: FALSE, header: read, label: check, data: none],
      rvr => [incrementDataPtr: FALSE, header: read, label: check, data: read],
      rvw => [incrementDataPtr: FALSE, header: read, label: check, data: write],
      rvv => [incrementDataPtr: FALSE, header: read, label: check, data: check],
      rw => [incrementDataPtr: FALSE, header: read, label: write, data: none],
      rww => [incrementDataPtr: FALSE, header: read, label: write, data: write],
      rr => [incrementDataPtr: FALSE, header: read, label: read, data: none],
      rrr => [incrementDataPtr: FALSE, header: read, label: read, data: read],
      rrw => [incrementDataPtr: FALSE, header: read, label: read, data: write],
      rrv => [incrementDataPtr: FALSE, header: read, label: read, data: check],
      w => [incrementDataPtr: FALSE, header: write, label: none, data: none],
      ENDCASE => ERROR;
    diskCommand.incrementDataPtr ← operationPtr.incrementDataPtr;

    iocb↑ ← [
      next: nilIOCBShortPtr, seal: sealValid,
      drive: DiskFromDeviceHandle[operationPtr.device].drive,
      pageCount: operationPtr.pageCount, command: diskCommand,
      diskAddress: operationPtr.clientHeader, diskHeader: operationPtr.clientHeader,
      headerPtr: @iocb.diskHeader, headerECC: 0, headerStatus: nullDiskStatus,
      labelPtr: IF diskCommand.label=read THEN operationPtr.labelPtr ELSE @iocb.diskLabel,
      labelECC: 0, labelStatus: nullDiskStatus,
      dataPtr: operationPtr.dataPtr, dataECC: 0, dataStatus: nullDiskStatus,
      diskLabel: operationPtr.labelPtr↑];

    -- Chain this IOCB onto CSB for processing by microcode.
    IF csb.head.iocb # nilIOCBShortPtr THEN csb.tail.next ← iocbShort;
    IF csb.head.iocb = nilIOCBShortPtr AND iocb.seal = sealValid THEN
      csb.head.iocb ← iocbShort;
    csb.tail ← iocb;
    END;

  Poll: PUBLIC PROCEDURE [operationPtr: OperationPtr] RETURNS [status: Status] =
    BEGIN
    doradoOperationPtr: DoradoOperationPtr = LOOPHOLE[operationPtr];
    iocb: IOCBLongPtr = @doradoOperationPtr.iocb;
    iocbShort: IOCBShortPtr = LOOPHOLE[Inline.LowHalf[iocb]];

    -- Capture in-progress/done state here and use it for all subsequent decisions.
    -- If the command finishes during the code below, we will still report inProgress
    -- to the client, who will Poll again and get the real ending state.
    status ← IF iocb.seal=sealValid THEN inProgress ELSE goodCompletion;

    -- Copy things back to client that he might want to monitor while the command
    -- is still in progress.
    operationPtr.clientHeader ← iocb.diskAddress;
    operationPtr.pageCount ← iocb.pageCount;

    IF status#inProgress THEN
      BEGIN
      -- Command has completed: successfully if pageCount=0, unsuccessfully otherwise.
      IF iocb.pageCount=0 THEN
        BEGIN
        IF iocb.diskAddress.head = modelHeads[modelTable[iocb.drive]] THEN
          -- Successful transfer happened to end at a cylinder boundary.
          -- Increment disk address to next cylinder.
          BEGIN
          iocb.diskAddress.head ← 0;
          iocb.diskAddress.cylinder ← iocb.diskAddress.cylinder+1;
          END
        END
      ELSE BEGIN
        -- Error occurred.  Must carefully examine DiskStatus for each block to determine
        -- what happened and to decide what Status to report to the client.
        combinedStatus: DiskStatus = Inline.BITOR[iocb.headerStatus,
          Inline.BITOR[iocb.labelStatus, iocb.dataStatus]];
        SELECT TRUE FROM
          -- First check for problems that might have caused the operation to
          -- malfunction and generated other errors as secondary effects.
          combinedStatus.notSelected OR combinedStatus.notOnLine OR
            combinedStatus.notReady => status ← notReady;
          combinedStatus.seekInc => status ← seekTimeout;
          combinedStatus.sectorSearchErr => status ← sectorTimeout;

          -- Check for head overflow: means transfer crossed a cylinder boundary.
          combinedStatus.headOvfl =>
            IF iocb.diskAddress.head = modelHeads[modelTable[iocb.drive]] THEN
              BEGIN
              iocb.diskAddress.head ← 0;
              iocb.diskAddress.cylinder ← iocb.diskAddress.cylinder+1;
              GOTO restart;
              END
            ELSE status ← hardwareError;
          -- Look for header problems:
          iocb.headerStatus.checkErr =>
            -- Header check error: must discriminate possible causes
            status ← SELECT TRUE FROM
              iocb.diskHeader.cylinder#iocb.diskAddress.cylinder => wrongCylinder,
              iocb.diskHeader.head#iocb.diskAddress.head => wrongHead,
              ENDCASE => wrongSector;
          -- Would like to distinguish other header errors (e.g., header checksum),
          -- but there is no Status value with which to report them!!
          Inline.BITAND[iocb.headerStatus, errorStatusMask]#0 => status ← hardwareError;

          -- Look for label problems:
          iocb.labelStatus.sectorOvfl OR iocb.labelStatus.eccErr => status ← labelError;
          iocb.labelStatus.checkErr => status ← labelCheck;
          Inline.BITAND[iocb.labelStatus, errorStatusMask]#0 => status ← hardwareError;

          -- Look for data problems:
          iocb.dataStatus.sectorOvfl OR iocb.dataStatus.eccErr => status ← dataError;
          ENDCASE => status ← hardwareError;

        -- Microcode has been deferring ever since the error occurred.
        -- Now zap all deferred operations and allow new ones to be Initiated.
        csb.head.iocb ← nilIOCBShortPtr;
        totalErrors ← totalErrors+1;
        EXITS
          restart =>
            BEGIN
            iocb.headerStatus ← iocb.labelStatus ← iocb.dataStatus ← nullDiskStatus;
            iocb.seal ← sealValid;
            csb.head.iocb ← iocbShort;
            status ← inProgress;
            END;
        END;

      -- Copy updated information back to client.
      -- We already did clientHeader and pageCount.
      -- Note: if reading label, it was read directly to client so don't clobber it here.
      IF iocb.command.label#read THEN operationPtr.labelPtr↑ ← iocb.diskLabel;
      operationPtr.dataPtr ← iocb.dataPtr;
      operationPtr.diskHeader ← iocb.diskHeader;
      END;
    END;

  Recalibrate: PUBLIC PROCEDURE [device: DeviceHandle] =
    BEGIN csb.cylinder ← cylinderRestore; END;

  Reset: PUBLIC PROCEDURE [device: DeviceHandle] =
    BEGIN
    -- WHAT SHOULD THIS DO?
    END;

  Start: PUBLIC PROCEDURE = -- exported to HeadStartChain
    BEGIN RemainingHeads.Start[]; END;


  -- Private procedures

  DiskFromDeviceHandle: PROCEDURE [device: DeviceHandle] RETURNS [DiskHandle] = INLINE
    BEGIN RETURN[LOOPHOLE[device]]; END;

  DeviceFromDiskHandle: PROCEDURE [disk: DiskHandle] RETURNS [DeviceHandle] = INLINE
    BEGIN RETURN[LOOPHOLE[disk]]; END;

  DMuxFromMufAddr: PROCEDURE [mufAdr: MufflerAddress]
    RETURNS [DoradoInputOutput.DMuxAddr] = INLINE
    BEGIN RETURN[diskDMuxAddr+LOOPHOLE[mufAdr, CARDINAL]]; END;

  END.
LOG
Time: August 8, 1979  1:09 AM	By: Redell	Action: Add log to file from Jarvis
Time: August 17, 1979  1:27 PM	By: Gobbel	Action: Pilot formatting
Time: August 22, 1979  12:40 PM	By: Gobbel	Action: Bring up to date with revised device face
Time: October 2, 1979  11:38 PM	By: Redell	Action: Further cleanup, esp for bootchains (partial label verification)
Time: October 8, 1979  9:22 AM	By: McJones	Action: Restart controller bug, add hardwareState
Time: October 10, 1979  3:53 PM	By: McJones	Action: INLINE procedures for speed; labelCheck is cue to call StartLabelFix
Time: October 30, 1979  11:04 AM	By: McJones	AR2643: Label fix didn't reset iocb.label from user's label
Time: November 1, 1979  1:20 PM	By: Frandeen	Action: Fix recalibrate after header error; bug in SynchronizeWithController
Time: November 7, 1979  1:17 PM	By: McJones	AR2739: Label fix up confused by spurious labelCheck status returned by read label command on sector with missing (label?) synch byte
Time: November 9, 1979  8:59 AM	By: Frandeen	Action: Implement runs of pages. Change interface to Poll. Change LabelFix procedures. Change interface to StartB since we no longer need an extra IOCB for label fixup.
Time: December 13, 1979  11:29 AM	By: Gobbel	Action: Change name from SA4000D0Head to SA4000HeadD0
Time: January 30, 1980  12:48 PM	By: McJones	Action: Update to match new face; add StartChain logic
Time: June 25, 1980  10:34 AM	By: McJones	Action: 48-bit processor ids
Time: July 24, 1980  11:23 AM	By: McJones	Action: Label fixup must update iocb.operation.clientHeader
Time: December 6, 1980  2:56 PM  Taft  Convert for Dorado
Time: February 13, 1983 1:24 pm  Taft  Add support for multiple drives and T-300s