-- DiskChannelImpl.mesa (last edited by: Yokota on: December 9, 1980  7:49 PM)

DIRECTORY
  Device USING [nullType, Type],
  DiskChannel USING [
    Address, CompletionHandle, DiskPageCount, DiskPageNumber, Drive, DriveState,
    DriveStatus, Handle, IORequestHandle, nullDrive, nullHandle],
  DiskChannelBackend USING [DriveHandle, DriveID],
  Environment USING [Base, first64K],
  PhysicalVolume USING [nullDeviceIndex],
  PilotDisk USING [Handle],
  Process USING [InitializeCondition, MsecToTicks, SetTimeout],
  ResidentHeap USING [FreeNode, MakeNode],
  RuntimeInternal USING [WorryCallDebugger],
  Zone USING [BlockSize, Status];

DiskChannelImpl: MONITOR
  IMPORTS Process, ResidentHeap, RuntimeInternal
  EXPORTS DiskChannel, DiskChannelBackend
  SHARES DiskChannel, DiskChannelBackend, PilotDisk =
  BEGIN

  -- Miscellaneous
  base: Environment.Base = Environment.first64K;
  ErrorHalt: PROC = { RuntimeInternal.WorryCallDebugger["Error in DiskChannelImpl"L]; };

  driveObjectUnlocked: CONDITION;
  g: GlobalHandle ← MakeLP[SIZE[Globals]];
  GlobalHandle: TYPE = LONG POINTER TO Globals;
  Globals: TYPE = RECORD [
    firstDrive: DiskChannelBackend.DriveHandle, -- head of the linked list of known drives
    channelRestarted: CONDITION]; -- used in the implementation of Restart

  MakeLP: PROC [size: Zone.BlockSize] RETURNS [lp: LONG POINTER] =
    BEGIN
    t: Environment.Base RELATIVE POINTER = MakeRP[size];
    RETURN[@base[t]];
    END;

  MakeRP: PROC [size: Zone.BlockSize]
    RETURNS [rp: Environment.Base RELATIVE POINTER] =
    BEGIN
    status: Zone.Status;
    [node: rp, s: status] ← ResidentHeap.MakeNode[size, a1];
    IF status ~= okay THEN ErrorHalt[];
    END;

  -- Drives

  FindDrive: PROC [drive: DiskChannel.Drive]
    RETURNS [driveH: DiskChannelBackend.DriveHandle] = INLINE {
    RETURN[LOOPHOLE[drive]]};

  shortTime: CONDITION;

  AcquireDriveObject: ENTRY PROCEDURE [drive: DiskChannelBackend.DriveHandle] =
      BEGIN
      WHILE drive.locked DO WAIT driveObjectUnlocked; ENDLOOP;
      drive.locked ← TRUE;
      END;

  ReturnDriveObject: ENTRY PROCEDURE [drive: DiskChannelBackend.DriveHandle] =
      { drive.locked ← FALSE; BROADCAST driveObjectUnlocked; };

  AwaitStateChange: PUBLIC PROC [
    count: CARDINAL, type: Device.Type, index: CARDINAL]
    RETURNS [currentChangeCount: CARDINAL] =
    BEGIN
    DriveOK: PROC [drive: DiskChannelBackend.DriveHandle] RETURNS [BOOLEAN] =
      BEGIN
      IF type = Device.nullType THEN
         RETURN[index = drive.driveOrdinal OR index = PhysicalVolume.nullDeviceIndex]
      ELSE RETURN[
                type = drive.driveID.type
                AND (index = drive.driveOrdinal OR index = PhysicalVolume.nullDeviceIndex)]
      END;
    GetCount: PROC RETURNS [count: CARDINAL] =
      BEGIN
      drive: DiskChannelBackend.DriveHandle ← g.firstDrive;
      nextdrive: DiskChannelBackend.DriveHandle;
      count ← 0;
      WHILE drive ~= NIL DO
         AcquireDriveObject[drive];
         IF DriveOK[drive] THEN
            BEGIN -- we must poke drive to be sure that drive.changeCount is right
            [] ← drive.getStatus[drive];
            count ← count + drive.changeCount;
            END;
         nextdrive ← drive.next;
         ReturnDriveObject[drive];
         drive ← nextdrive;
         ENDLOOP
      END;
    WHILE (currentChangeCount ← GetCount[]) < count DO Wait[] ENDLOOP;
    END;

  Wait: ENTRY PROCEDURE = INLINE { WAIT shortTime };

  GetDriveAttributes: PUBLIC PROC [drive: DiskChannel.Drive]
    RETURNS [
      deviceType: Device.Type, deviceHandle: PilotDisk.Handle,
      deviceOrdinal: CARDINAL, nPages: DiskChannel.DiskPageCount, ready: BOOLEAN,
      state: DiskChannel.DriveState, changeCount: CARDINAL] =
    BEGIN
    driveID: DiskChannelBackend.DriveID;
    handle: DiskChannelBackend.DriveHandle ← FindDrive[drive];
    AcquireDriveObject[handle];
    ready ← handle.getStatus[handle].ready;
    [driveID: driveID, nPages: nPages, driveOrdinal: deviceOrdinal, state: state,
      changeCount: changeCount] ← handle↑;
    deviceType ← driveID.type;
    deviceHandle ← driveID.handle;
    ReturnDriveObject[handle];
    END;

  GetDriveTag: PUBLIC PROC [drive: DiskChannel.Drive] RETURNS [CARDINAL] =
    BEGIN
    handle: DiskChannelBackend.DriveHandle ← FindDrive[drive]; tag: CARDINAL;
    AcquireDriveObject[handle];
    tag ← handle.tag;
    ReturnDriveObject[handle]; RETURN[tag];
    END;

  GetNextDrive: PUBLIC PROC [prev: DiskChannel.Drive]
    RETURNS [DiskChannel.Drive] =
    BEGIN
    GetFirstDrive: ENTRY PROC RETURNS[DiskChannel.Drive] = INLINE
       { RETURN[SealDrive[g.firstDrive]] };
    handle: DiskChannelBackend.DriveHandle ← FindDrive[prev]; next: DiskChannel.Drive;
    IF prev = DiskChannel.nullDrive THEN next ← GetFirstDrive[]
    ELSE {AcquireDriveObject[handle]; next ← SealDrive[FindDrive[prev].next]; ReturnDriveObject[handle];};
    RETURN[next];
    END;

  GetPageNumber: PUBLIC PROC [
    drive: DiskChannel.Drive, page: DiskChannel.Address]
    RETURNS [DiskChannel.DiskPageNumber] =
    BEGIN
    dH: DiskChannelBackend.DriveHandle = FindDrive[drive];
    diskPageNumber: DiskChannel.DiskPageNumber;
    AcquireDriveObject[dH];
    diskPageNumber ← dH.getPageNumber[dH, page];
    ReturnDriveObject[dH]; RETURN[diskPageNumber];
    END;

  SealDrive: PROC [driveID: DiskChannelBackend.DriveHandle]
    RETURNS [DiskChannel.Drive] = INLINE {RETURN[LOOPHOLE[driveID]]};

  SetDriveState: PUBLIC PROC [
    drive: DiskChannel.Drive, changeCount: CARDINAL,
    state: DiskChannel.DriveState] RETURNS [s: DiskChannel.DriveStatus] =
    BEGIN
    handle: DiskChannelBackend.DriveHandle = FindDrive[drive];
    AcquireDriveObject[handle];
    -- poke the drive so that drive.changeCount is current
    [] ← handle.getStatus[handle];
    -- Special case changeCount of 0 for PhysicalVolumeImpl
    IF changeCount ~= 0 THEN
      IF changeCount ~= handle.changeCount THEN RETURN[invalidDrive];
    IF NOT (handle.state = inactive OR state = inactive) THEN
      { ReturnDriveObject[handle]; RETURN[alreadyAsserted]; };
    handle.changeState[handle, state];
    handle.state ← state;
    ReturnDriveObject[handle];
    RETURN[ok]
    END;

  SetDriveTag: PUBLIC PROC [drive: DiskChannel.Drive, tag: CARDINAL] =
    BEGIN
    handle: DiskChannelBackend.DriveHandle = FindDrive[drive];
    AcquireDriveObject[handle];
    handle.tag ← tag;
    ReturnDriveObject[handle];
    END;

  -- Channels

  ChannelHandle: TYPE = Environment.Base RELATIVE POINTER TO ChannelObject;
  ChannelObject: TYPE = RECORD [
    driveHandle: DiskChannelBackend.DriveHandle,
    completionHandle: CompletionHandle, -- used in the implementation of WaitAny
    changeCount: CARDINAL,
    suspendCount: [0..256), -- used in the implementation of Suspend, Restart
    ioCount: [0..256)]; -- used in the implementation of Idle
  Create: PUBLIC ENTRY PROC [
    drive: DiskChannel.Drive, completion: DiskChannel.CompletionHandle]
    RETURNS [DiskChannel.Handle] =
    BEGIN OPEN comp: LOOPHOLE[completion, CompletionHandle];
    chH: ChannelHandle;
    dH: DiskChannelBackend.DriveHandle = FindDrive[drive];
    IF dH = NIL THEN RETURN[LOOPHOLE[DiskChannel.nullHandle]]; -- no such drive
    chH ← MakeRP[SIZE[ChannelObject]];
    base[chH] ← ChannelObject[
      driveHandle: dH, completionHandle: @comp, changeCount: dH.changeCount,
      suspendCount: 0, ioCount: 0];
    RETURN[LOOPHOLE[chH]]
    END;

  Delete: PUBLIC ENTRY PROC [channel: DiskChannel.Handle] =
    { IF ResidentHeap.FreeNode[LOOPHOLE[channel]] # okay THEN ErrorHalt[]};

  GetAttributes: PUBLIC ENTRY PROC [channel: DiskChannel.Handle]
    RETURNS [drive: DiskChannel.Drive] = {RETURN[SealDrive[GetDrive[channel]]]};

  GetPageAddress: PUBLIC PROC [
    channel: DiskChannel.Handle, page: DiskChannel.DiskPageNumber]
    RETURNS [DiskChannel.Address] =
    BEGIN OPEN channelObj: base[LOOPHOLE[channel, ChannelHandle]];
    address: DiskChannel.Address;
    handle: DiskChannelBackend.DriveHandle = channelObj.driveHandle;
    AcquireDriveObject[handle];
    address ← handle.getPageAddress[handle, page];
    ReturnDriveObject[handle];
    RETURN[address];
    END;

  Idle: PUBLIC ENTRY PROC [channel: DiskChannel.Handle] =
    BEGIN OPEN channelObj: base[LOOPHOLE[channel, ChannelHandle]];
    -- Should an error be returned if the change counts do not match?
    IF channelObj.changeCount ~= channelObj.driveHandle.changeCount THEN RETURN;
    channelObj.suspendCount ← channelObj.suspendCount + 1;
    WHILE channelObj.ioCount > 0 DO
      WAIT channelObj.completionHandle.ioComplete; ENDLOOP;
    END;

  InitiateIO: PUBLIC --EXTERNAL--PROC [req: DiskChannel.IORequestHandle] =
    BEGIN
    TouchChannel: ENTRY PROC [channelHandle: ChannelHandle] =
      -- do the monitored parts of setting up an IO request.
      -- INLINE
      BEGIN OPEN channelObj: base[channelHandle];
      WHILE channelObj.suspendCount > 0 DO WAIT g.channelRestarted; ENDLOOP;
      channelObj.ioCount ← channelObj.ioCount + 1;
      END;
    CancelIO: ENTRY PROC [channelHandle: ChannelHandle] = INLINE
      BEGIN OPEN channelObj: base[channelHandle];
      channelObj.ioCount ← channelObj.ioCount - 1;
      END;
    ch: ChannelHandle = LOOPHOLE[req.channel];
    AcquireDriveObject[base[ch].driveHandle];
    TouchChannel[ch];
    IF base[ch].changeCount ~= base[ch].driveHandle.changeCount THEN
      BEGIN
      CancelIO[ch];
      req.status ← invalidChannel;
      NotifyIOComplete[req];
      RETURN;
      END;
    req.countDone ← 0;
    base[ch].driveHandle.requestIO[req];
    ReturnDriveObject[base[ch].driveHandle];
    END;

  Restart: PUBLIC ENTRY PROC [channel: DiskChannel.Handle] =
    BEGIN OPEN channelObj: base[LOOPHOLE[channel, ChannelHandle]];
    IF (channelObj.suspendCount ← channelObj.suspendCount - 1) = 0 THEN
      BROADCAST g.channelRestarted;
    END;

  Suspend: PUBLIC ENTRY PROC [channel: DiskChannel.Handle] =
    BEGIN OPEN channelObj: base[LOOPHOLE[channel, ChannelHandle]];
    channelObj.suspendCount ← channelObj.suspendCount + 1;
    END;

  WaitAny: PUBLIC ENTRY PROC [completion: DiskChannel.CompletionHandle]
    RETURNS [req: DiskChannel.IORequestHandle] =
    BEGIN OPEN LOOPHOLE[completion, CompletionHandle];
    WHILE first = NIL DO WAIT ioComplete; ENDLOOP;
    req ← first;
    first ← req.next;
    END;

  -- Completions

  CompletionHandle: TYPE = LONG POINTER TO CompletionObject;
  CompletionObject: TYPE = RECORD [
    first, last: DiskChannel.IORequestHandle, ioComplete: CONDITION];
  ioCompletePrototype: CONDITION; -- do not ever wait on this
  CreateCompletionObject: PUBLIC PROC RETURNS [DiskChannel.CompletionHandle] =
    BEGIN
    Crock: TYPE = ARRAY [0..SIZE[CONDITION]) OF WORD;
    cH: CompletionHandle ← MakeLP[SIZE[CompletionObject]];
    cH.first ← NIL; -- cH.ioComplete ← ioCompletePrototype;
    LOOPHOLE[@cH.ioComplete, LONG POINTER TO Crock]↑ ←
      LOOPHOLE[ioCompletePrototype];
    RETURN[LOOPHOLE[cH]]
    END;

  -- Backend

  GetDrive: PUBLIC PROC [channel: DiskChannel.Handle]
    RETURNS [DiskChannelBackend.DriveHandle] = -- INLINE
    BEGIN OPEN channelObj: base[LOOPHOLE[channel, ChannelHandle]];
    RETURN[channelObj.driveHandle]
    END;

  NotifyIOComplete: PUBLIC ENTRY PROC [req: DiskChannel.IORequestHandle] =
    -- used by driver to indicate some IO transaction has completed.
    BEGIN
    OPEN channelObj: base[LOOPHOLE[req.channel, ChannelHandle]],
      channelObj.completionHandle;
    channelObj.ioCount ← channelObj.ioCount - 1;
    req.next ← NIL;
    IF first = NIL THEN first ← req ELSE last.next ← req;
    last ← req;
    BROADCAST ioComplete;
    END;

  RegisterDrive: PUBLIC ENTRY PROC [drive: DiskChannelBackend.DriveHandle] =
    -- Add new drive to end of list, to preserve enumeration order defined by faces
    BEGIN
    prevDrive: DiskChannelBackend.DriveHandle;
    drive.driveOrdinal ← 0; -- in case first of type
    drive.next ← NIL;
    drive.locked ← FALSE;
    IF g.firstDrive = NIL THEN g.firstDrive ← drive
    ELSE
      BEGIN
      FOR prevDrive ← g.firstDrive, prevDrive.next WHILE prevDrive.next ~= NIL DO
            ENDLOOP;
      prevDrive.next ← drive;
      IF prevDrive.driveID.type = drive.driveID.type THEN
         drive.driveOrdinal ← prevDrive.driveOrdinal + 1;
      END;
    END;

  -- Initialization

  g.firstDrive ← NIL;
  Process.SetTimeout[@ioCompletePrototype, Process.MsecToTicks[1000]];
  Process.InitializeCondition[@g.channelRestarted, Process.MsecToTicks[1000]];
  Process.InitializeCondition[@shortTime, Process.MsecToTicks[5000]];
  Process.InitializeCondition[@driveObjectUnlocked, Process.MsecToTicks[30000]];
  END.

LOG
Time: January 11, 1979  3:28 PM   By: Horsley   Action: Create file
Time: August 14, 1979  11:25 PM   By: Redell   Action: New DiskChannel
Time: August 16, 1979  11:04 PM   By: Redell   Action: Add drive tags
Time: October 19, 1979  9:11 AM   By: McJones   Action: Avoid calling InitializeCondition in CreateCompletionObject
Time: November 29, 1979  3:21 PM   By: Gobbel   Action: New DiskChannel interface: initialize countDone field of IORequest
Time: January 31, 1980  4:53 PM   By: McJones   Action: New DiskChannel; preserve order of drive registration
Time: June 11, 1980  3:24 PM   By: Luniewski   Action: Changes for DiskChannel.Drive's being LONG POINTER's to DriveObject's.  Implement AwaitStateChange, SetDriveState.  Make changes to the channel implementation to account for removable volumes.
Time: June 23, 1980  4:16 PM   By: McJones   Action: OISDisk=>PilotDisk
Time: July 19, 1980  3:09 PM   By: Jose   Action: Changes to SetDriveState to fix deadlock, handle change of state.
Time: July 30, 1980  2:15 PM   By: Luniewski   Action: Modify SetDriveState to return status.
Time: August 7, 1980  9:27 AM   By: Luniewski   Action: Add support for stateChangeLock in driveObject.
Time: September 16, 1980  11:59 PM   By: Jose   Action: Get ready before changeCount in GetDriveAttributes, change stateChangeLock to stateChangeLocked.
Time: October 3, 1980  12:07 PM   By: Luniewski   Action: AwaitStateChange: access all drives 
Time: December 9, 1980  7:49 PM   By: Yokota   Action: Several ENTRY procs are converted to procs and Monitor lock is done by "locked" of each DriveObject.