-- BootChannelDisk.mesa  (last edited by: Taft on: June 18, 1981  2:04 PM)

DIRECTORY
  Boot USING [Location, LP],
  BootChannel USING [Create, Operation, Handle, transferCleanup, transferWait],
  DeviceTypes USING [sa4000],
  Environment USING [PageCount, PageNumber],
  Inline USING [DIVMOD, LongDivMod, LongMult, LowHalf],
  PilotDisk USING [Address, GetLabelFilePage, Label, SetLabelFilePage],
  PilotMP USING [cGermDeviceError, cGermLabelCheck, Code],
  SA4000Face USING [
    Command, DeviceHandle, DiskAddress, GetDeviceAttributes, GetNextDevice,
    globalStateSize, Initialize, Initiate, nullDeviceHandle, Operation, operationSize,
    Poll, Recalibrate];

BootChannelDisk: PROGRAM
  IMPORTS Boot, RemainingChannels: BootChannel, Inline, PilotDisk, SA4000Face
  EXPORTS BootChannel
  SHARES PilotDisk =

  -- Implementation of BootChannel for Pilot volume on SA4000.
  -- A single, serially reusable, channel is supported.
  BEGIN OPEN Boot, Environment;

  daNull: PilotDisk.Address = [0, 0];
  label1, label2: PilotDisk.Label; -- HOW SHOULD THIS BE ALIGNED FOR FUTURE DISKS?
  pAllocateNext: LONG POINTER TO UNSPECIFIED; -- allocator for first 64K storage
  pLoc: POINTER TO Location;
  tries: [0..triesMax];
  triesMax: CARDINAL = 8;
  movingHeads, sectorsPerTrack: CARDINAL;

  currentDiskAddress: SA4000Face.DiskAddress;
  currentFilePage: LONG CARDINAL;

  Operation: TYPE = RECORD [
    pOp: LONG POINTER TO SA4000Face.Operation,
    state: {idle, busy}];
  current, alternate: Operation;

  Create: PUBLIC PROCEDURE [
    pLocation: POINTER TO Location, operation: BootChannel.Operation,
    dFirst64KStorage: LONG DESCRIPTOR FOR ARRAY OF WORD]
    RETURNS [BootChannel.Handle] =
    BEGIN
    pAllocateNext ← BASE[dFirst64KStorage]; -- reset first 64K allocator
    SELECT (pLoc ← pLocation).deviceType FROM
      = DeviceTypes.sa4000 =>
	BEGIN
	da: SA4000Face.DiskAddress = LOOPHOLE[pLocation.diskFileID.da];
	device: SA4000Face.DeviceHandle;
	current.pOp ← Allocate[SA4000Face.operationSize];
	current.state ← idle;
	alternate.pOp ← Allocate[SA4000Face.operationSize];
	alternate.state ← idle;
	SA4000Face.Initialize[0, Inline.LowHalf[Allocate[SA4000Face.globalStateSize]]];
	device ← SA4000Face.nullDeviceHandle;
	THROUGH [0..pLocation.deviceOrdinal] DO
	  device ← SA4000Face.GetNextDevice[device] ENDLOOP;
	[movingHeads: movingHeads, sectorsPerTrack: sectorsPerTrack] ←
	  SA4000Face.GetDeviceAttributes[device];
	-- Initialize label
	currentDiskAddress ← da;
	current.pOp.labelPtr ← @label1;
	-- dataPtr, incrementDataPtr irrelevant
	current.pOp.command ← vr; -- read label (and ignore data)
	-- pageCount set in Transfer
	current.pOp.device ← device;
	TransferSA4000[page: NULL, count: 1]; -- fetch type, attribute fields
	TransferSA4000[page: NULL, count: BootChannel.transferWait];
	label1.fileID ← pLoc.diskFileID.fID;
	PilotDisk.SetLabelFilePage[@label1, pLoc.diskFileID.firstPage];
	label1.bootChainLink ← daNull; --- so TransferSA4000 will use pOp.clientHeader
	label2 ← label1;
	currentFilePage ← pLoc.diskFileID.firstPage;
	-- Initialize remaining operation fields
	currentDiskAddress ← da; -- since transfer above incremented it
	-- labelPtr set above
	-- dataPtr set in Transfer
	current.pOp.incrementDataPtr ← TRUE;
	current.pOp.command ← SELECT operation FROM
	  read => vvr,
	  write => vvw,
	  ENDCASE --rawRead-- => vrr;
	-- pageCount set in Transfer
	-- device set above
	alternate.pOp↑ ← current.pOp↑;
	alternate.pOp.labelPtr ← @label2;
	RETURN[TransferSA4000];
	END;
      ENDCASE => -- not anything I implement.  Pass it on.
	RETURN[RemainingChannels.Create[pLocation, operation, dFirst64KStorage]];
    END;

  TransferSA4000: PROCEDURE [page: PageNumber, count: PageCount] =
    BEGIN
    IF current.state=busy THEN Cleanup[];
    SELECT count FROM
      BootChannel.transferWait, BootChannel.transferCleanup =>
        BEGIN
        ExchangeCurAlt[];
        IF current.state=busy THEN Cleanup[];
        END;
      ENDCASE =>
        BEGIN
        current.pOp.dataPtr ← LPFromPage[page];
        current.pOp.pageCount ← count;
        Initiate[];
        END;
    END;

  Initiate: PROCEDURE =
    BEGIN
    current.pOp.clientHeader ← currentDiskAddress;
    PilotDisk.SetLabelFilePage[current.pOp.labelPtr, currentFilePage];
    current.pOp.labelPtr.bootChainLink ← daNull;
    IncrementDiskAddressAndPage[current.pOp.pageCount];
    SA4000Face.Initiate[current.pOp];
    current.state ← busy;
    ExchangeCurAlt[];
    END;

  Cleanup: PROCEDURE =
    BEGIN
    initialCount: CARDINAL ← LAST[CARDINAL];
    DO -- until count pages transferred
      DO -- until status~=inProgress
	SELECT SA4000Face.Poll[current.pOp] FROM
	  inProgress => NULL;
	  goodCompletion => GOTO TransferComplete;
	  labelCheck =>
	    IF current.pOp.labelPtr.bootChainLink~=daNull THEN
              BEGIN -- end of run on disk; chain to next run and resume transfer
              current.pOp.clientHeader ← LOOPHOLE[current.pOp.labelPtr.bootChainLink];
              GOTO RetryOperation;
              END
	    ELSE Error[PilotMP.cGermLabelCheck];
	  ENDCASE -- other error -- =>
	    BEGIN
	    IF current.pOp.pageCount ~= initialCount THEN tries ← triesMax - 1
	    ELSE
	      SELECT tries ← tries - 1 FROM
		0 => Error[PilotMP.cGermDeviceError];
		triesMax/2 => SA4000Face.Recalibrate[current.pOp.device];
		ENDCASE;
	    initialCount ← current.pOp.pageCount;
	    GOTO RetryOperation
	    END;
	REPEAT RetryOperation => NULL;
	ENDLOOP;
      -- At this point we know the disk controller is dormant, since errors cause
      -- all pending transfers to be abandoned.  Therefore it is safe to fool with
      -- the label and the operation.
      currentDiskAddress ← current.pOp.clientHeader;
      currentFilePage ← PilotDisk.GetLabelFilePage[current.pOp.labelPtr];
      Initiate[];
      -- Restart the alternate operation if there is one, since the head has forgotten it.
      IF current.state=busy THEN Initiate[] ELSE ExchangeCurAlt[];
      REPEAT TransferComplete =>
        BEGIN
        current.state ← idle;
        -- If this operation happened to finish at the end of a run of pages, we must
        -- manually follow the bootChainLink.  If the next operation has already been
        -- initiated, it will surely suffer a check error; wait for it to terminate,
        -- and then restart it at the correct place.
        IF current.pOp.labelPtr.bootChainLink # daNull AND
          current.pOp.labelPtr.bootChainLink # LOOPHOLE[current.pOp.clientHeader] THEN
          BEGIN
          currentDiskAddress ← LOOPHOLE[current.pOp.labelPtr.bootChainLink];
          currentFilePage ← PilotDisk.GetLabelFilePage[current.pOp.labelPtr];
          IF alternate.state=busy THEN
            BEGIN
            ExchangeCurAlt[];
            WHILE SA4000Face.Poll[current.pOp] = inProgress DO ENDLOOP;
            Initiate[];
            END;
          END;
        END;
      ENDLOOP;
    END;

  IncrementDiskAddressAndPage: PROCEDURE [count: CARDINAL] = INLINE
    BEGIN
    virtualDA: LONG CARDINAL = count + currentDiskAddress.sector +
      Inline.LongMult[sectorsPerTrack,
        currentDiskAddress.head + movingHeads*currentDiskAddress.cylinder];
    c: CARDINAL;
    [c, currentDiskAddress.sector] ← Inline.LongDivMod[virtualDA, sectorsPerTrack];
    [currentDiskAddress.cylinder, currentDiskAddress.head] ← Inline.DIVMOD[c, movingHeads];
    currentFilePage ← currentFilePage+count;
    END;

  ExchangeCurAlt: PROCEDURE = INLINE
    BEGIN
    t: Operation ← current; current ← alternate; alternate ← t;
    END;

  -- Allocator for 16-word aligned storage in first64K
  Allocate: PROCEDURE [size: CARDINAL] RETURNS [lp: LONG POINTER TO UNSPECIFIED] =
    BEGIN pAllocateNext ← (lp ← pAllocateNext) + LONG[((size + 15)/16)*16] END;

  Error: SIGNAL [PilotMP.Code] = CODE;

  LPFromPage: PROCEDURE [page: PageNumber] RETURNS [LONG POINTER] = INLINE
    BEGIN RETURN[LP[highbits: page/256, lowbits: page*256 --MOD 2**16--]] END;

  END.

(For earlier log entries see Pilot 4.0 archive version.)
June 23, 1980  2:31 PM	McJones	OISDisk,FilePageLabel=>PilotDisk
August 13, 1980  6:02 PM	McJones	Read first label to determine attributes
August 23, 1980  1:21 PM	McJones	Don't report labelCheck at end of run
January 4, 1981  3:03 PM	Taft	Queue up to 2 operations at a time
June 18, 1981  2:04 PM	Taft	Bug in pathological case of boot file chaining