-- File: DiskControl.mesa
-- Last edited by Levin:  12-Apr-83 12:03:54

DIRECTORY
  AltoFileDefs USING [DirDA, DirFP, DiskShape, FIP, LD],
  DiskDriver USING [busyTail, cbAvailable, completedTail,
    completerToDie, completionsExist, diskInterruptCV,
    freeHead, interruptHandlerToDie, longTermWait, totalErrors],
  DiskIODefs USING [
    CompletionStatus, DISK, DiskError, DiskRequest, InitiateDiskIO,
    NormalCompletionProcedure, XferSpec],
  DiskIOPrivate USING [
    CB, CBPtr, Completer, DiskInterruptHandler, diskInterruptLevel, diskReads,
    diskWrites, FinalizeCompleter, FinalizeInterruptHandler, FreeCB, GetCBs, IOSynch,
    maxOps, nSafetyDCBs, scratchPage, standardDisk, SynchRecord, sysDisk, VirtualDA],
  FrameDefs USING [GlobalFrame, IsBound, MakeCodeResident, UnlockCode],
  LogDefs USING [DisplayNumber, WriteLogEntry],
  ProcessDefs USING [CV, MsecToTicks, Yield],
  SegmentDefs USING [memConfig],
  StringDefs USING [AppendOctal, AppendString],
  VMStorage USING [AllocatePage, FreePage, longTerm];

DiskControl: MONITOR LOCKS synch.LOCK USING synch: DiskIOPrivate.IOSynch
  IMPORTS
    DiskDriver, DiskIODefs, DiskIOPrivate, FrameDefs, LogDefs, ProcessDefs,
    SegmentDefs, StringDefs, VMStorage
  EXPORTS DiskIODefs, DiskIOPrivate
  SHARES DiskDriver =

  BEGIN OPEN DiskIODefs, DiskIOPrivate;

  -- Global Variables --

  diskInterruptProcess, completerProcess: PROCESS;

  -- Miscellaneous Declarations --
 
  diskInterruptTimeoutMS: CARDINAL = 250;

  CBsLost: ERROR = CODE;


  -- Procedures exported to DiskIOPrivate --

  loggingEnabled: PUBLIC BOOLEAN;
  -- indicates whether statistics logging is enabled.

  InitializeDiskIO: PUBLIC PROCEDURE [minOps: CARDINAL] RETURNS [nOps: CARDINAL] =
    -- initializes the storage for CBs and sets up the interrupt handling processes.
    BEGIN
    loggingEnabled ← FrameDefs.IsBound[LogDefs.DisplayNumber];
    sysDisk ← standardDisk;
    maxOps ← minOps + nSafetyDCBs;
    InitializeDiskDriver[];
    InitializeDiskRequestor[];
    InitializeSysDisk[];
    RETURN[maxOps - nSafetyDCBs];
    END;

  FinalizeDiskIO: PUBLIC PROCEDURE =
    -- shuts down the disk driver and releases associated storage.
    BEGIN
    FinalizeDiskDriver[];
    FinalizeDiskRequestor[];
    END;

  ResetDiskShape: PUBLIC PROCEDURE [disk: DISK] = {sysDisk ← disk};

  WriteErrorToLog: PUBLIC PROCEDURE [cb: CBPtr] =
    BEGIN OPEN StringDefs;
    s: STRING ← [30+3*7];
    AppendString[s, "Disk error:  vDA "L];
    AppendOctal[s, VirtualDA[cb.header.diskAddress]];
    AppendString[s, ", op "L]; AppendOctal[s, cb.command];
    AppendString[s, " status "L]; AppendOctal[s, cb.status];
    LogDefs.WriteLogEntry[s];
    END;


  -- Internal Procedures --

  InitializeDiskDriver: PROCEDURE =
    BEGIN
    OPEN DiskDriver, ProcessDefs;
    FrameDefs.MakeCodeResident[FrameDefs.GlobalFrame[DiskInterruptHandler]];
    START DiskDriver;
    IF loggingEnabled THEN
      BEGIN
      totalErrors ← 0;
      LogDefs.DisplayNumber["Disk Errors"L, [short[@totalErrors]]];
      END;
    freeHead ← busyTail ← completedTail ← NIL;
    interruptHandlerToDie ← completerToDie ← FALSE;
    diskInterruptCV.timeout ← MsecToTicks[diskInterruptTimeoutMS];
    CV[diskInterruptLevel] ← @diskInterruptCV;
    longTermWait.timeout ← 0;
    diskInterruptProcess ← FORK DiskInterruptHandler;
    completionsExist.timeout ← 0;
    cbAvailable.timeout ← 0;
    completerProcess ← FORK Completer;
    THROUGH [1..10] DO Yield[] ENDLOOP; -- hack to start interrupt processes
    END;

  InitializeDiskRequestor: PROCEDURE =
    BEGIN
    IF loggingEnabled THEN
      BEGIN
      diskReads ← diskWrites ← 0;
      LogDefs.DisplayNumber["Disk Reads"L, [long[@diskReads]]];
      LogDefs.DisplayNumber["Disk Writes"L, [long[@diskWrites]]];
      END;
    scratchPage ←
      IF SegmentDefs.memConfig.banks = 100000B THEN LOOPHOLE[177040B]  -- consult Taft!
      ELSE VMStorage.AllocatePage[];
    THROUGH [0..maxOps) DO FreeCB[VMStorage.longTerm.NEW[CB]]; ENDLOOP;
    END;

  InitializeSysDisk: PROCEDURE =
    BEGIN
    leader: POINTER TO AltoFileDefs.LD = VMStorage.AllocatePage[];
    xferSpec: ARRAY [0..1) OF XferSpec ← [[leader, AltoFileDefs.DirDA, 0]];
    request: DiskRequest ← [
      firstPage: 0,
      fileID: [1, AltoFileDefs.DirFP.serial],
      firstPagevDA: ,
      pagesToSkip: 0,
      nonXferID: ,
      xfers: DESCRIPTOR[@xferSpec, 1],
      proc: [normal[ProcessCompletion]],
      noRestore: FALSE,
      command: ReadD[]];
    synch: SynchRecord;

    IssueRequestAndWait: ENTRY PROCEDURE [synch: IOSynch] = INLINE
      BEGIN
      DO
	synch.status ← noStatus;
	InitiateDiskIO[@request];
	WHILE synch.status = noStatus DO WAIT synch.lastDone ENDLOOP;
	IF synch.status ~= neverStarted THEN EXIT;
	ENDLOOP;
      END;

    ProcessCompletion: NormalCompletionProcedure =
      BEGIN
      DoNotify: ENTRY PROCEDURE [synch: IOSynch] = INLINE
        {synch.status ← status; NOTIFY synch.lastDone};
      DoNotify[@synch];
      END;

    prop: POINTER TO ARRAY OF AltoFileDefs.FIP;
    IssueRequestAndWait[@synch];
    IF synch.status ~= ok THEN ERROR DiskError[synch.status];
    prop ← LOOPHOLE[leader, POINTER] + leader.propBegin;
    FOR i: CARDINAL ← 0, i + prop[i].length
     UNTIL prop[i].length = 0 OR i >= leader.propLength DO
      IF prop[i].type = AltoFileDefs.DiskShape THEN
	{sysDisk ← LOOPHOLE[@prop[i] + 1, POINTER TO DISK]↑; EXIT};
      ENDLOOP;
    VMStorage.FreePage[leader];
    END;

  FinalizeDiskDriver: PROCEDURE =
    BEGIN
    FinalizeInterruptHandler[];
    JOIN diskInterruptProcess;
    ProcessDefs.CV[diskInterruptLevel] ← NIL;
    FinalizeCompleter[];
    JOIN completerProcess;
    FrameDefs.UnlockCode[FinalizeInterruptHandler];
    END;

  FinalizeDiskRequestor: PROCEDURE =
    BEGIN
    IF SegmentDefs.memConfig.banks ~= 100000B THEN VMStorage.FreePage[scratchPage];
    FOR freedCBs: CARDINAL ← 0, freedCBs + 1 DO
      cb: CBPtr ← GetCBs[n: 1, wait: FALSE];
      IF cb = NIL THEN
	IF freedCBs = maxOps THEN EXIT ELSE ERROR CBsLost
      ELSE VMStorage.longTerm.FREE[@cb];
      ENDLOOP;
    END;

  END.