-- Filer>FilerTransferImpl.mesa   (August 4, 1982 10:59 am by Levin)

-- Things to consider:
-- 1) Large XWire request (>Transfer.maxConcurrency) will hang forever; should split up as with label requests (must deal with multiple processes however...simply making this module a monitor would seem to introduce deadlocks ...?)
-- 2) Main routines for data and label transfers should be combined. Note that combined routine will be an external procedure and will execute within monitor for label transfers but outside it for data...
-- 3) Current code in Initiate will fail if a page group crosses a physical volume boundary.

DIRECTORY
  DiskChannel USING [
    Address, CompletionHandle, CompletionStatus, Create, CreateCompletionObject,
    Delete, Drive, Handle, InitiateIO, IORequest, Label, PLabel, WaitAny],
  Environment USING [PageCount, PageNumber, wordsPerPage],
  File USING [PageCount, PageNumber],
  FileCache USING [GetFilePtrs, GetPageGroup, ReturnFilePtrs],
  FileInternal USING [Descriptor, FilePtr, PageGroup],
  FilePageTransfer USING [Request],
  FilerException USING [Report],
  FilerPrograms USING [],
  FileTask USING [LabelWait -- ,XWireStart--],
  Inline USING [LowHalf],
  LabelTransfer USING [Operation],
  MStore USING [Promise],
  PilotDisk USING [Label],
  --RemotePageTransfer USING [Initiate, Suggest],
  ResidentMemory USING [Allocate],
  SubVolume USING [Find, Handle, PageNumber, StartIO, IO],
  --System USING [nullNetworkAddress],
  --SystemInternal USING [altoFPSeries, UniversalID],
  Utilities USING [PageFromLongPointer],
  VolumeInternal USING [--altoVolume,-- PageNumber];

FilerTransferImpl: MONITOR
  IMPORTS
    DiskChannel, FileCache, FilerException, FileTask, Inline,
    MStore, ResidentMemory, SubVolume, Utilities
  EXPORTS FilePageTransfer, FilerPrograms, LabelTransfer
  SHARES File =
  BEGIN

Bug: ERROR [type: BugType] = CODE;
BugType: TYPE = {subVolWentAway};

  -- Temporary histogram to record statistics on runs of pages transferred
  logging: BOOLEAN ← FALSE;
  hist: ARRAY [0..16] OF LONG CARDINAL ← ALL[0];
  Log: PRIVATE PROCEDURE [size: CARDINAL] =
    BEGIN
    FOR i: CARDINAL IN [0..16] DO
      IF size = 0 THEN {hist[i] ← hist[i] + 1; EXIT} ELSE size ← size/2;
      ENDLOOP
    END;
  -- hist[j] counts requests of size [2↑(j-1)..(2↑j)-1]

  Initiate: PUBLIC PROCEDURE [req: FilePageTransfer.Request] =
    BEGIN
    groupSize: Environment.PageCount;  -- OK to be a short cardinal since we only do I/O from pages of virtual memory.
    fileFound, groupFound: BOOLEAN;
    fileP: FileInternal.FilePtr;
    --fileD: FileInternal.Descriptor;
    rReq: FilePageTransfer.Request = req;
    group: FileInternal.PageGroup;
    IF req.count = 0 THEN RETURN;
    --IF LOOPHOLE[file.fID, SystemInternal.UniversalID].series =
    --   SystemInternal.altoFPSeries THEN ++ Alto files are special remote files
    --  BEGIN
    --  fileP ← @fileD;
    --  fileD ← FileInternal.Descriptor[file.fID, VolumeInternal.altoVolume, remote[]];
    --  END
    --ELSE
    --  BEGIN
    [fileFound, fileP] ← FileCache.GetFilePtrs[1, req.file.fID];
    IF ~fileFound THEN { FilerException.Report[req]; RETURN };
    --  END;
    WHILE req.count > 0 DO
      -- Break request into page groups and start transfers: 
      WITH fileP SELECT FROM
        local =>
          BEGIN
          svH: SubVolume.Handle;
          svFound: BOOLEAN;
          svPage: SubVolume.PageNumber;
          [groupFound, group] ← FileCache.GetPageGroup[req.file.fID, req.filePage];
          IF ~groupFound THEN
            { FileCache.ReturnFilePtrs[1, fileP]; FilerException.Report[req]; EXIT };
          groupSize ← MIN[req.count, Inline.LowHalf[group.nextFilePage - req.filePage]];
          IF req.promise THEN MStore.Promise[groupSize];  -- tell the SwapOutProcess this page group is on the way.
          IF logging THEN Log[groupSize];
          [svFound, svH] ← SubVolume.Find[fileP.volumeID, group.volumePage];
          IF ~svFound THEN ERROR Bug[subVolWentAway];
          svPage ← group.volumePage - svH.lvPage + (req.filePage - group.filePage);
            BEGIN
            io: SubVolume.IO ← [
              op: req.operation, subVolume: svH, subVolumePage: svPage, filePtr: fileP,
              memPage: req.memoryPage, filePage: req.filePage, pageCount: groupSize];
            SubVolume.StartIO[@io];
            END;
          END;
        remote =>
          BEGIN
          -- groupSize ← 1; ++ yuck! This should be in runs, too
          -- FileTask.XWireStart[req.memoryPage, fileP];
          -- RemotePageTransfer.Suggest[
          --     [req.operation, req.file.fID, req.filePage, req.memoryPage]];
          --   Alto file hack: remove later
          END;
        ENDCASE;
      req.filePage ← req.filePage + groupSize;
      req.memoryPage ← req.memoryPage + groupSize;
      req.count ← req.count - groupSize;
      IF req.count > 0 THEN [] ← FileCache.GetFilePtrs[1, req.file.fID];
      ENDLOOP;
    --WITH fileP SELECT FROM
    --  remote => RemotePageTransfer.Initiate[rReq, System.nullNetworkAddress];
    --  ENDCASE;
    END;
    
  -- Start of monitor implementing LabelTransfer interface
  -- Empty page is source of zeros (sink for garbage) when writing (reading) labels.
  emptyPagePtr: LONG POINTER = ResidentMemory.Allocate[hyperspace, 1];
  emptyPage: Environment.PageNumber = Utilities.PageFromLongPointer[emptyPagePtr];
  completion: DiskChannel.CompletionHandle ← DiskChannel.CreateCompletionObject[];

  LabelOperation: TYPE = RECORD[
      operation: LabelTransfer.Operation,
      fileP: POINTER TO READONLY FileInternal.Descriptor,
      pageGroupPtr: POINTER TO READONLY FileInternal.PageGroup,
      memoryPage: Environment.PageNumber,
      label: POINTER TO PilotDisk.Label];

  ReadLabel: PUBLIC ENTRY PROCEDURE [
    file: FileInternal.Descriptor,
    filePage: File.PageNumber,
    volumePage: VolumeInternal.PageNumber,
    handleErrors: BOOLEAN]
    RETURNS [label: PilotDisk.Label, status: DiskChannel.CompletionStatus] =
    BEGIN
    pageGroup: FileInternal.PageGroup ← [filePage, volumePage, filePage + 1];
    op: LabelOperation;
    op ← [readLabel, @file, @pageGroup, emptyPage, @label];
    status ← Perform[@op, handleErrors].status;
    END;
    
  ReadRootLabel: PUBLIC ENTRY PROCEDURE [
    drive: DiskChannel.Drive,
    rootPage: VolumeInternal.PageNumber]
    RETURNS [label: PilotDisk.Label, status: DiskChannel.CompletionStatus] =
    BEGIN OPEN DiskChannel;
    rootChannel: Handle ← Create[drive, completion];
    -- temporary channel for root page
    request: IORequest;
    pLabel: PLabel;
    labelStorage: ARRAY [0..SIZE[Label] + 3) OF WORD;
    -- space for label plus extra for quad-alignment
    pLabel ←
      LOOPHOLE[((LOOPHOLE[LONG[@labelStorage[0]], LONG INTEGER] + 3)/4)*4];
    --quad-align label
    BEGIN OPEN request;
    -- Can't use constructor because of blasted PRIVATE fields...
    channel ← rootChannel; diskPage ← rootPage; memoryPage ← emptyPage;
    count ← 1; command ← vrr; label ← pLabel; dontIncrement ← TRUE;
    END;
    InitiateIO[@request];
    status ← WaitAny[completion].status;
    Delete[rootChannel];
    label ← LOOPHOLE[pLabel↑];
    END;
    
  WriteLabels: PUBLIC ENTRY PROCEDURE [
    file: FileInternal.Descriptor,
    pageGroup: FileInternal.PageGroup,
    handleErrors: BOOLEAN ← TRUE]
    RETURNS [status: DiskChannel.CompletionStatus] =
    BEGIN OPEN Environment, pageGroup;
    label: PilotDisk.Label;
    op: LabelOperation ← [writeLabel, @file, @pageGroup, emptyPage, @label];
    DataPage: TYPE = LONG POINTER TO ARRAY [0..wordsPerPage) OF WORD;
    LOOPHOLE[emptyPagePtr, DataPage]↑ ← ALL[0];
    status ← Perform[@op, handleErrors].status;
    END;
    
  -- This is an imperfect simulation for passing expectErrorAfterFirstPage
  -- down the old disk channel (lacks recalibrates at the least)
  VerifyLabels: PUBLIC ENTRY PROCEDURE [
    file: FileInternal.Descriptor,
    pageGroup: FileInternal.PageGroup,
    expectErrorAfterFirstPage: BOOLEAN ← FALSE,
    handleErrors: BOOLEAN ← TRUE]
    RETURNS [countValid: File.PageCount, status: DiskChannel.CompletionStatus] =
    BEGIN
    label: PilotDisk.Label;
    op: LabelOperation ← [verifyLabel, @file, @pageGroup, emptyPage, @label];
    THROUGH [0..10) DO
      [countValid, status] ← Perform[@op, handleErrors];
      IF status = goodCompletion OR
        (expectErrorAfterFirstPage AND countValid # 0) THEN EXIT;
      ENDLOOP;
    END;
    
  ReadLabelAndData: PUBLIC ENTRY PROCEDURE [
    file: FileInternal.Descriptor,
    filePage: File.PageNumber,
    volumePage: VolumeInternal.PageNumber, 
    memoryPage: Environment.PageNumber,
    handleErrors: BOOLEAN]
    RETURNS [label: PilotDisk.Label, status: DiskChannel.CompletionStatus] =
    BEGIN
    pageGroup: FileInternal.PageGroup ← [filePage, volumePage, filePage + 1];
    op: LabelOperation ← [readLabelAndData, @file, @pageGroup, memoryPage, @label];
    status ← Perform[@op, handleErrors].status
    END;
    
  WriteLabelAndData: PUBLIC ENTRY PROCEDURE [
    file: FileInternal.Descriptor,
    filePage: File.PageNumber,
    volumePage: VolumeInternal.PageNumber, 
    memoryPage: Environment.PageNumber,
    bootChainLink: DiskChannel.Address,
    handleErrors: BOOLEAN]
    RETURNS[status: DiskChannel.CompletionStatus] =
    BEGIN
    label: PilotDisk.Label;
    pageGroup: FileInternal.PageGroup ← [
      filePage: filePage, volumePage: volumePage, nextFilePage: filePage+1];
    op: LabelOperation ← [writeLabelsAndData, @file, @pageGroup, memoryPage, @label];
    status ← Perform[@op, handleErrors, chained, bootChainLink].status;
    END;
    
  Perform: INTERNAL PROCEDURE [  -- Should be combined with Initiate (above)
      operation: POINTER TO LabelOperation,
      handleErrors: BOOLEAN,
      labelType: {normal, chained} ← normal,
      bootChainLink: DiskChannel.Address ← NULL]
    RETURNS [countValid: File.PageCount, status: DiskChannel.CompletionStatus] =
    BEGIN
    count: File.PageCount ←
      operation.pageGroupPtr.nextFilePage-operation.pageGroupPtr.filePage;
    subVol: SubVolume.Handle;
    subVolumeFound: BOOLEAN;

    countValid ← 0; -- Initialize return values in case no I/O is necessary (count=0)
    status ← goodCompletion;

    IF logging THEN Log[Inline.LowHalf[count]];
    WITH operation.fileP SELECT FROM
      remote => ERROR; -- can't transfer remote labels
      local =>
        BEGIN
        [subVolumeFound, subVol] ← SubVolume.Find[
          operation.fileP.volumeID, operation.pageGroupPtr.volumePage];
        IF ~ subVolumeFound THEN ERROR; -- local volumes must be found
        IF count > 0 THEN
          BEGIN
          io: SubVolume.IO ← [
             op: operation.operation, subVolume: subVol,
             subVolumePage: operation.pageGroupPtr.volumePage,
             memPage: operation.memoryPage, filePtr: operation.fileP,
             filePage: operation.pageGroupPtr.filePage, pageCount: count,
             fixedMemPage: operation.operation IN [readLabel..verifyLabel],
             chained: labelType = chained, link: bootChainLink];
          SubVolume.StartIO[@io];
          [countValid, status] ← FileTask.LabelWait[operation.label, handleErrors];
          END;
        END;
      ENDCASE;
    END;
  
  END.

February 28, 1979  9:28 AM   Redell   Create from FilePageTransferImpl and LabelTransferImpl.

March 21, 1979  12:01 PM   Redell   Convert to Mesa 5.0.

August 13, 1979  3:59 PM   Redell   Minor mods for boot-chain machinery.

September 5, 1979  3:13 PM   McJones   AR1593: Add countInitiated result to Initiate.

September 17, 1979  4:42 PM   Forrest   Change Read Root Label.

October 17, 1979  8:41 PM   Redell   Add histogram.

November 26, 1979  3:49 PM   Gobbel   Initial changes for runs of pages: SubVolume.StartIO takes count arg, always 1 for now.

December 5, 1979  4:24 PM   Gobbel   Runs of pages for real.

January 9, 1980  2:29 PM   Gobbel   Add countValid to VerifyLabels and Perform.

January 30, 1980  3:16 PM   Gobbel   Remove Alto file stuff.

August 14, 1980  9:54 AM   McJones   WriteLabelsAndData=>WriteLabelAndData; FilePageLabel=>PilotDisk.

October 11, 1980  9:30 PM   Forrest   Add expectLabelCheck to VerifyLabels (and hack implementation until we come up with correct solution).

January 14, 1981  9:17 AM   Luniewski   New LabelTransfer interface. 

February 11, 1981  4:47 PM   Knutsen   Initiate must MStore.Promise the pages, and indeed *before* starting the I/O.

August 4, 1982 10:59 am	Levin	Log now has short CARDINAL parameter.