-- FormatSA1000andSA4000Impl.mesa
-- Last Edited by: Taft, May 22, 1983 4:47 pm

-- this must be generalized to also do sa1000's.  The general theory is the same, but the interface has to be a little different since SA1000's only do cylinders, and don't know about initial microcode yet.
-- do something about event counts???

DIRECTORY
  DeviceTypes USING [sa1000, sa4000],
  DiskChannel USING [
    Address, Create, Delete, Drive, DiskPageNumber, GetDriveAttributes,
    GetPageAddress, GetPageNumber, Handle, PVHandle],
  Environment USING [Base, first64K, wordsPerPage],
  FormatSA1000andSA4000,
  Inline USING [BITNOT, LowHalf, LongCOPY],
  MicrocodeFile,
  PhysicalVolume USING [], -- exports Handle
  PilotDisk USING [Label, NextLabel, nullLabel],
  Process USING [Yield],
  RandomCard USING [Init, Next],
  ResidentHeap USING [MakeNode],
  SA4000Face USING [
    Command, DeviceHandle, DiskAddress, Initiate, Operation, OperationPtr,
    operationSize, Poll, Recalibrate, Status],
  Space USING [
    Create, defaultWindow, Handle, LongPointer, Map, Pointer, Unmap, mds],
  String USING [AppendString],
  System USING [GreenwichMeanTime],
  Zone USING [Status];

FormatSA1000andSA4000Impl: PROGRAM -- should be a monitor since globals shared
  IMPORTS
    DiskChannel, Inline, MicrocodeFile, PilotDisk, Process, RandomCard, ResidentHeap,
    SA4000Face, Space, String
  EXPORTS FormatSA1000andSA4000, PhysicalVolume
  SHARES DiskChannel = PUBLIC
  BEGIN OPEN Inline;
  
  Handle: PUBLIC TYPE = DiskChannel.PVHandle;
  
  DiskPageNumber: PRIVATE TYPE = FormatSA1000andSA4000.DiskPageNumber;
  RetryLimit:     PRIVATE TYPE = [0..10);
  retryLimit:     PRIVATE RetryLimit = LAST[RetryLimit];
  
  space: PRIVATE Space.Handle = Space.Create[
    size: 1, parent: Space.mds];
  op: PRIVATE SA4000Face.OperationPtr = MakeOperation[];
  
  BadPage: SIGNAL [p: DiskPageNumber] = CODE;
  PassStarting: PUBLIC SIGNAL [pass: CARDINAL, operation: FormatSA1000andSA4000.Operation] = CODE;
  MicrocodeInstallFailure: SIGNAL [m: FormatSA1000andSA4000.FailureType]
    = CODE;
  NotSA1000orSA4000: ERROR = CODE;
  SA1000FormatTrackError, SA1000FormatCylinderError: ERROR = CODE;
  
  Format: PROC [h: Handle, firstPage: DiskPageNumber, count: LONG CARDINAL,
    passes: CARDINAL] =
    BEGIN
    ckData:      LONG POINTER = Space.LongPointer[space];
    cnl:         DiskChannel.Handle;
    label:       PilotDisk.Label;
    formatCount: LONG CARDINAL = AssertSA1000orSA4000[h, firstPage, count];
    SetLabel: PROC [value: CARDINAL] =
      BEGIN
      SetBlock[@label, SIZE[PilotDisk.Label], value];
      label.pad1 ← 0;
      IF label.filePageLo#0 OR label.filePageHi#0 THEN
        label.immutable ← label.temporary ← label.zeroSize ← FALSE;
      END;
    Space.Map[space, Space.defaultWindow];
    cnl ← DiskChannel.Create[h.drive, [LONG[0]]];
    BEGIN ENABLE UNWIND => {Space.Unmap[space]; DiskChannel.Delete[cnl]};
    SetBlock[@label, SIZE[PilotDisk.Label], 52525B];
    FOR countDone: LONG CARDINAL ← 0, countDone + formatCount
      WHILE countDone < count DO
      Transfer[h, cnl, firstPage+countDone, formatCount, w, @label, ckData];
      ENDLOOP;
    [] ← RandomCard.Init[-1];
    FOR pass: CARDINAL IN [0..passes) DO
      labelValue: CARDINAL ← (IF pass MOD 2 = 0 THEN RandomCard.Next[] ELSE BITNOT[labelValue]);
      SIGNAL PassStarting[pass+1, write];
      SetLabel[labelValue];
      FOR i: CARDINAL IN [0..Environment.wordsPerPage) DO
        (ckData+i)↑ ← (IF pass MOD 2 = 0 THEN RandomCard.Next[] ELSE BITNOT[(ckData+i)↑]);
        ENDLOOP;
      Transfer[h, cnl, firstPage, count, vww, @label, ckData];
      SIGNAL PassStarting[pass+1, read];
      SetLabel[labelValue];
      Transfer[h, cnl, firstPage, count, vvv, @label, ckData, 1];
      ENDLOOP;
    END;
    Space.Unmap[space];
    DiskChannel.Delete[cnl];
    END;

  IdentifyInitialMicrocode: PUBLIC PROCEDURE [h: Handle, s: STRING]
    RETURNS [microcodeInstalled: BOOLEAN ← FALSE, time: System.GreenwichMeanTime] =
    BEGIN
    label: PilotDisk.Label ← PilotDisk.nullLabel;
    page: DiskPageNumber;
    cnl: DiskChannel.Handle;
    header: POINTER TO MicrocodeFile.Header;
    [] ← AssertSA1000orSA4000[h];
    cnl ← DiskChannel.Create[h.drive, [LONG[0]]];
    page ← IF DiskChannel.GetDriveAttributes[h.drive].deviceType = DeviceTypes.sa4000
      THEN FormatSA1000andSA4000.SA4000startOfMicrocode
      ELSE FormatSA1000andSA4000.SA1000startOfMicrocode;
    Space.Map[space, Space.defaultWindow];
      BEGIN
      Transfer[h: h, channel: cnl, firstPage: page, count: 1,
        cmd: vvr, label: @label, data: Space.LongPointer[space] !
        BadPage => GOTO error];
      header ← Space.Pointer[space];
      IF header.name.length IN [1..header.name.maxlength] AND
        header.name.maxlength<=MicrocodeFile.maxNameLength AND
	header.name.length<=s.maxlength THEN
	BEGIN
	time ← MicrocodeFile.GMTFromBCPLTime[header.createDate];
	s.length ← 0;
	String.AppendString[s, @header.name];
	microcodeInstalled ← TRUE;
	END;
      EXITS
        error => NULL;
      END;
    Space.Unmap[space];
    DiskChannel.Delete[cnl];
    END;
    
  InstallBootMicrocode: PROC [
    h: Handle, getPage: PROC RETURNS [LONG POINTER]] =
    BEGIN
    cnl: DiskChannel.Handle;
    [] ← AssertSA1000orSA4000[h];
    cnl ← DiskChannel.Create[h.drive, [LONG[0]]];
    Space.Map[space, Space.defaultWindow];
    InstallBootMicrocode1[
      h, cnl, Space.LongPointer[space], getPage
      ! UNWIND => {Space.Unmap[space]; DiskChannel.Delete[cnl]}];
    Space.Unmap[space];
    DiskChannel.Delete[cnl];
    END;
    
  Scan: PROC [h: Handle, firstPage: DiskPageNumber, count: LONG CARDINAL,
    passes: CARDINAL] =
    BEGIN
    label: PilotDisk.Label;
    cnl: DiskChannel.Handle;
    [] ← AssertSA1000orSA4000[h];
    cnl ← DiskChannel.Create[h.drive, [LONG[0]]];
    Space.Map[space, Space.defaultWindow];
    FOR pass: CARDINAL IN [0..passes) DO
      SIGNAL PassStarting[pass+1, read];
      Transfer[
        h, cnl, firstPage, count, vrr, @label, Space.LongPointer[space]
        ! UNWIND => {Space.Unmap[space]; DiskChannel.Delete[cnl]}];
      ENDLOOP;
    Space.Unmap[space];
    DiskChannel.Delete[cnl];
    END;
    
  -- Private Procedures
  AssertSA1000orSA4000: PRIVATE PROC [
    h: Handle, first: DiskPageNumber ← 0, count: LONG CARDINAL ← 0]
    RETURNS [countFormat: LONG CARDINAL] =
    BEGIN
    SELECT DiskChannel.GetDriveAttributes[h.drive].deviceType FROM
      DeviceTypes.sa4000 => RETURN[count];
      DeviceTypes.sa1000 =>
	BEGIN
	p: CARDINAL = FormatSA1000andSA4000.SA1000pagesPerTrack;
	IF (first MOD p) # 0 OR (count MOD p) # 0 THEN
	  ERROR SA1000FormatTrackError;
	RETURN[FormatSA1000andSA4000.SA1000pagesPerTrack]
	END;
      ENDCASE => ERROR NotSA1000orSA4000
    END;
    
  CopyPage: PRIVATE PROC [from, to: LONG POINTER] = INLINE
    {LongCOPY[from: from, to: to, nwords: Environment.wordsPerPage]; };
    
  GetDiskAddress: PRIVATE PROC [h: DiskChannel.Handle, p: DiskPageNumber]
    RETURNS [SA4000Face.DiskAddress] =
    BEGIN
    x: DiskChannel.Address = DiskChannel.GetPageAddress[h, p];
    RETURN[[cylinder: x.cylinder, head: x.head, sector: x.sector]];
    END;
    
  GetPageNumber: PRIVATE PROC [
    d: DiskChannel.Drive, a: SA4000Face.DiskAddress]
    RETURNS [DiskPageNumber] =
    BEGIN
    RETURN[DiskChannel.GetPageNumber[
      d, [cylinder: a.cylinder, head: a.head, sector: a.sector]]]
    END;
    
  -- h has been asserted to be SA4000
  InstallBootMicrocode1: PRIVATE PROC [
    h: Handle, cnl: DiskChannel.Handle, myPage: LONG POINTER,
    getPage: PROC RETURNS [LONG POINTER]] =
    BEGIN
    bits: LONG POINTER;
    device: SA4000Face.DeviceHandle = DiskChannel.GetDriveAttributes[
      h.drive].deviceHandle;
    thisPage, prevPage: DiskPageNumber ← 0;
    stopDa: SA4000Face.DiskAddress = LOOPHOLE[LONG[-1]];
    offset, lastPage: DiskPageNumber;
    GetDA: PROC [p: DiskPageNumber] RETURNS [SA4000Face.DiskAddress] = INLINE
      {RETURN[GetDiskAddress[cnl, p + offset]]};
    IO: PROC [
      command: SA4000Face.Command, thisPage, link: SA4000Face.DiskAddress]
      RETURNS [success: BOOLEAN] =
      BEGIN
      label: PilotDisk.Label ← PilotDisk.nullLabel;
      rCount: RetryLimit ← 0;
      status: SA4000Face.Status;
      op↑ ← [
	  command: command, device: device, clientHeader: thisPage,
          pageCount: 1,
	  labelPtr: @label, dataPtr: myPage, incrementDataPtr: FALSE];
      label.bootChainLink ← LOOPHOLE[link];
      SA4000Face.Initiate[op];
      DO
	SELECT status ← SA4000Face.Poll[op] FROM
	  inProgress => {Process.Yield[]; LOOP; };
	  goodCompletion => EXIT;
	  ENDCASE =>
	    BEGIN
	    SELECT rCount FROM
	      >= retryLimit => RETURN[FALSE];
	      = retryLimit/2 => SA4000Face.Recalibrate[device];
	      ENDCASE => NULL;
	    rCount ← rCount + 1;
	    op.pageCount ← 1;
	    SA4000Face.Initiate[op];
	    END
	ENDLOOP;
      RETURN[IF command = vww THEN IO[vvv, thisPage, link] ELSE TRUE];
      END;
      
    IF DiskChannel.GetDriveAttributes[h.drive].deviceType
      = DeviceTypes.sa4000 THEN
      BEGIN
      offset ← FormatSA1000andSA4000.SA4000startOfMicrocode;
      lastPage ← FormatSA1000andSA4000.SA4000lastPageOfMicrocode
      END
    ELSE
      BEGIN
      offset ← FormatSA1000andSA4000.SA1000startOfMicrocode;
      lastPage ← FormatSA1000andSA4000.SA1000lastPageOfMicrocode;
      END;
    bits ← getPage[];
    IF bits = NIL THEN GOTO emptyFile;
    CopyPage[from: bits, to: myPage];
    DO
      bits ← getPage[];
      DO
	IF thisPage + offset > lastPage THEN GOTO tooBig;
	IF IO[
	  vww, GetDA[thisPage],
	  IF bits = NIL THEN stopDa ELSE GetDA[thisPage + 1]] THEN EXIT;
	IF thisPage = 0 THEN GOTO firstPageBad;
	thisPage ← thisPage + 1;
	ENDLOOP;
      IF thisPage # prevPage + 1 AND thisPage # 0 THEN
	-- have to fix up previous link if we had an error
	BEGIN
	IF ~IO[vvr, GetDA[prevPage], GetDA[prevPage + 1]] THEN GOTO flakey;
	IF ~IO[vww, GetDA[prevPage], GetDA[thisPage]] THEN GOTO flakey;
	END;
      IF bits = NIL THEN EXIT;
      -- we are done and have written a zero link in the last page
      CopyPage[from: bits, to: myPage];
      prevPage ← thisPage;
      thisPage ← thisPage + 1;
      ENDLOOP;
    EXITS
      emptyFile => SIGNAL MicrocodeInstallFailure[emptyFile];
      firstPageBad => SIGNAL MicrocodeInstallFailure[firstPageBad];
      flakey => SIGNAL MicrocodeInstallFailure[flakeyPageFound];
      tooBig => SIGNAL MicrocodeInstallFailure[microcodeTooBig];
    END;
    
  MakeOperation: PRIVATE PROC RETURNS [SA4000Face.OperationPtr] =
    BEGIN
    rp: Environment.Base RELATIVE POINTER TO SA4000Face.Operation;
    NoWay: ERROR = CODE;
    status: Zone.Status;
    [rp, status]
      ← ResidentHeap.MakeNode[n: SA4000Face.operationSize, alignment: a16];
    IF status # okay THEN ERROR NoWay;
    RETURN[@Environment.first64K[rp]]
    END;
    
  SetBlock: PRIVATE PROC [blk: LONG POINTER, length, v: CARDINAL] = INLINE
    {blk↑ ← v; LongCOPY[from: blk, to: blk + 1, nwords: length - 1]; };
    
  -- SA4000-ness has been asserted
  
  Transfer: PRIVATE PROC [
    h: Handle, channel: DiskChannel.Handle, firstPage: DiskPageNumber,
    count: LONG CARDINAL, cmd: SA4000Face.Command,
    label: POINTER TO PilotDisk.Label, data: LONG POINTER,
    retries: RetryLimit ← retryLimit] =
    BEGIN
    device: SA4000Face.DeviceHandle = DiskChannel.GetDriveAttributes[
      h.drive].deviceHandle;
    rCount: RetryLimit ← 0;
    status: SA4000Face.Status;
    runStartAddr: SA4000Face.DiskAddress ← GetDiskAddress[channel, firstPage];
    
    Ooops: PROC RETURNS [notDoneYet: BOOLEAN] =
      BEGIN
      rCount ← IF op.clientHeader = runStartAddr THEN rCount + 1 ELSE 0;
      SELECT TRUE FROM
	rCount >= retries =>
	  BEGIN
	  page: DiskPageNumber = GetPageNumber[h.drive, op.clientHeader];
	  SIGNAL BadPage[page];
	  op.clientHeader ← GetDiskAddress[channel, page + 1];
	  op.pageCount ← op.pageCount - 1;
             PilotDisk.NextLabel[label]; -- Fix up the label to the next page
	  rCount ← 0;
	  END;
	rCount = retryLimit/2 OR (status IN [wrongSector..wrongHead] AND rCount = retries/2) =>
	  SA4000Face.Recalibrate[device];
	ENDCASE => NULL;
      runStartAddr ← op.clientHeader;
      IF (notDoneYet ← op.pageCount > 0) THEN SA4000Face.Initiate[op];
      END;
      
    op↑ ← [
	device: device, clientHeader: runStartAddr, pageCount: LowHalf[count],
	labelPtr: label, dataPtr: data, incrementDataPtr: FALSE,
        command: cmd];
    IF count > LAST[CARDINAL] THEN ERROR;
    SA4000Face.Initiate[op];
    DO
      SELECT status ← SA4000Face.Poll[op] FROM
	inProgress => {Process.Yield[]; LOOP};
	goodCompletion => EXIT;
	ENDCASE => IF Ooops[] THEN LOOP ELSE EXIT;
      ENDLOOP;
    END;
    
  
  END....


LOG
Time: June 1, 1980  1:23 AM	By: Forrest	Action: Changed to use sanitized interface.  Added limit checking on InstallMicrocode.  Trimmed log.
Time: July 28, 1980  8:22 AM	By: Forrest	Action: Retrofited McJones changes for new SA4000face.  Converted to SA1000and4000.
Time: September 30, 1980  5:31 PM	By: Luniewski	Action: Increment File Page numbers in labels after finding a bad page in Transfer. 
17-Jun-82 17:46:14  Taft  ~= => # for Cedar compiler
21-Jun-82 18:21:10  Taft  Add IdentifyInitialMicrocode
May 22, 1983 4:28 pm  Taft  Change Format to do multiple passes with random data.