-- HelloDevImplDLion.Mesa
-- Contains Hello commands specific to DLion disks
--	Masinter 22 Aug 84 from OthelloDeviceImplD0DLion
--	Glassman  17-Jul-81 16:43:40
--	Linda     10-Mar-82 16:17:10
--	Forrest    7-Jan-82 17:40:00
--	Johnsson   8-Nov-83  9:24:59
-- there is a seperate version at least for dorados.

DIRECTORY
  Device,
  DeviceTypes,
  FormatPilotDisk, FormatPilotDiskExtras,
  OthelloDefs,
  OthelloOps,
  PhysicalVolume,
  String USING [EquivalentStrings],
  Volume;

HelloDevImplDLion: PROGRAM
  IMPORTS
    FormatPilotDisk, OthelloDefs, OthelloOps, PhysicalVolume, String, Volume =
  BEGIN OPEN OthelloDefs, OthelloOps;

  commandProcessor: CommandProcessor ← [DiskCommands];

  DiskCommands: PROC [index: CARDINAL] = {
    SELECT index FROM
      0 => CheckDrive[];
      1 => CreateVolume[];
      2 => Format[];
     -- => IndicateAltoness[];
      3 => FetchInitialMicrocode[];
      ENDCASE => IndexTooLarge};
    
  badTableSize: CARDINAL = 200;
  logicalVolumeOverhead: CARDINAL = 1;
  minLogicalVolumeSize:  CARDINAL = 50;  -- fudge + 1+1+6;
  maxNameLength:         CARDINAL = PhysicalVolume.maxNameLength;

  -- BUG! The following statement is wrong!  physicalVolumeOverhead
  -- should be set based upon device type.  This will be necessary once Pilot
  -- knows how to create bad page tables based upon device types.  For the
  --  time being, we just KNOW that all bad page tables are one page
  --  long, and that as a result there
  --  are always two pages of physical volume overhead.

  physicalVolumeOverhead: CARDINAL = 2;

  sa4000Flavor: PACKED ARRAY [0..8) OF [0..377B] ← ALL[377B];

<<
  IndicateAltoness: PUBLIC PROC = {
    h: PhysicalVolume.Handle;
    t: Device.Type;
    MyNameIs[
      myNameIs: "Reserve Alto Volume"L,
      myHelpIs: "Reserve space for simulated Alto-mode partitions"L];
    IF (t ← GetDriveType[(h ← GetDriveFromUser[])]) # DeviceTypes.sa4000
       AND t # DeviceTypes.sa4008 THEN
      AbortingCommand["Command is only for SA4000's."L]
    ELSE IndicateAltoness1[h]};
>>

  CheckDrive: PROC =
    BEGIN
    badSpots:         CARDINAL;
    badSpotArray:     ARRAY [0..badTableSize) OF PhysicalVolume.PageNumber;
    couldDo:          BOOLEAN;
    h:                PhysicalVolume.Handle;
    p:                PhysicalVolume.ID ← PhysicalVolume.nullID;
    wasOnLine:        BOOLEAN ← FALSE;
    badTableOverflow: BOOLEAN;

    MyNameIs[
      myNameIs: "Check Drive"L, myHelpIs: "Scan drive for unreadable pages"L];
    [couldDo, badSpots, h] ← FormatCheckDrive[@badSpotArray, check];
    IF ~couldDo THEN AbortingCommand["Command can't be done for this device."L]
    ELSE IF badSpots = 0 THEN {WriteLine["No bad pages found."L]; RETURN}
    ELSE IF ~Wizard[] OR ~Yes["\rShall I record these pages in the bad table? "L] THEN RETURN;
    -- See if was on line/put on line
    DO
      p ← PhysicalVolume.GetNext[p];
      IF p = PhysicalVolume.nullID THEN {
        p ← PhysicalVolume.AssertPilotVolume[h]; EXIT}
      ELSE IF h = PhysicalVolume.GetAttributes[p].instance THEN {
        wasOnLine ← TRUE; EXIT};
      ENDLOOP;
    badTableOverflow ← FillBadTable[@badSpotArray, badSpots, p];
    IF ~wasOnLine THEN PhysicalVolume.Offline[p];
    IF badTableOverflow THEN AbortingCommand["Too many bad spots."L]
    ELSE WriteLine["Consider scavenging some volumes."L];
    END;

  Format: PROC =
    BEGIN
    badSpots:         CARDINAL;
    badSpotArray:     ARRAY [0..badTableSize) OF PhysicalVolume.PageNumber;
    couldDo:          BOOLEAN;
    h:                PhysicalVolume.Handle;
    p:                PhysicalVolume.ID;
    badTableOverflow: BOOLEAN;

    IF ~Wizard[] THEN RETURN;
    MyNameIs[myNameIs: "Format"L, myHelpIs: "Format a disk drive"L];
    [couldDo, badSpots, h] ← FormatCheckDrive[@badSpotArray, format];
    IF ~couldDo THEN AbortingCommand["Command can't be done for this device."L];
    FOR i: CARDINAL IN [0..MIN[badSpots, LENGTH[badSpotArray]]) DO
      IF badSpotArray[i] = 0 THEN {WriteLine["Physical page zero bad"L]; RETURN}
      ENDLOOP;
    WriteLine["\rCreating Pilot volume named ""Empty"" to hold bad spot table"L];
    p ← PhysicalVolume.CreatePhysicalVolume[h, "Empty"L];
    badTableOverflow ← FillBadTable[@badSpotArray, badSpots, p];
    PhysicalVolume.Offline[p];
    IF badTableOverflow THEN AbortingCommand["Too many bad spots."L];
    END;

  lvTable: ARRAY [0..10) OF RECORD [
    size: Volume.PageCount,
    type: Volume.Type,
    name: LONG STRING] ← ALL[[0, normal, NIL]];
  newPvName:  LONG STRING ← NIL;

  CreateVolume: PROC  = 
    BEGIN
    h:              PhysicalVolume.Handle;
    badTable:       ARRAY [0..badTableSize) OF PhysicalVolume.PageNumber;
    bad:            CARDINAL ← 0;
    broughtOnLine:  BOOLEAN ← FALSE;
    driveSize:      LONG CARDINAL;
    nSubVols:       CARDINAL;
    lvID:           Volume.ID;
    lvsize:         Volume.PageCount;
    pvID:           PhysicalVolume.ID ← PhysicalVolume.nullID;
    PagesRippedOff: PROC RETURNS [p: LONG CARDINAL] = INLINE {
      --we don't really lose physicalVolumeOverhead
      p ← MinPilotPage[h]; IF p # 0 THEN p ← p - physicalVolumeOverhead};
    volumeFound:    BOOLEAN ← TRUE;

    MyNameIs[
      myNameIs: "Create Physical Volume"L,
      myHelpIs: "Format physical volume into logical volumes (old contents lost)"L];
    h ← GetDriveFromUser[];
    DO
      pvID ← PhysicalVolume.GetNext[pvID];
      IF pvID = PhysicalVolume.nullID THEN {
        volumeFound ← TRUE; -- Yes["Shall I try to find an old bad page Table? "L];
        IF volumeFound THEN {
          broughtOnLine ← TRUE;
          pvID ← PhysicalVolume.AssertPilotVolume[h !
            PhysicalVolume.Error, PhysicalVolume.NeedsScavenging => {
              volumeFound ← broughtOnLine ← FALSE; CONTINUE}]};
        EXIT};
      IF h = PhysicalVolume.GetAttributes[pvID].instance THEN EXIT;
      ENDLOOP;
    IF volumeFound THEN {
      OPEN PhysicalVolume;
      badTable[bad] ← GetNextBadPage[pvID, nullBadPage];
      WHILE badTable[bad] # nullBadPage DO
        badTable[bad + 1] ← GetNextBadPage[pvID, badTable[bad]]; bad ← bad + 1
        ENDLOOP;
      volumeFound ← PhysicalVolume.GetNextLogicalVolume[pvID, Volume.nullID]
                    # Volume.nullID};
    GetName["New physical volume name: "L, @newPvName];
    nSubVols ← ReadShortNumber[
      "Number of logical volumes: "L, 1,
      PhysicalVolume.maxSubvolumesOnPhysicalVolume, 3];
    driveSize ← GetDriveSize[h] -
      (physicalVolumeOverhead + nSubVols*logicalVolumeOverhead
       + PagesRippedOff[]);
    driveSize ← driveSize - ReserveLastCylinderForDiag[h];
    FOR i: CARDINAL IN [0..nSubVols) DO
      OPEN lvTable[i];
      WriteString["Logical volume "L];
      WriteLongNumber[LONG[i]];
      NewLine[];
      DO
        duplicate: BOOLEAN ← FALSE;
        GetName["  Name: "L, @lvTable[i].name];
        FOR j: CARDINAL IN [0..i) WHILE ~duplicate DO
          duplicate ← String.EquivalentStrings[
	    lvTable[i].name, lvTable[j].name] ENDLOOP;
        IF ~duplicate THEN EXIT;
        WriteLine["Name is already in use; please choose another"L];
        ENDLOOP;
      size ← ReadNumber[
        "  Pages: "L, minLogicalVolumeSize,
        driveSize - ((nSubVols - (i + 1))*minLogicalVolumeSize),
        driveSize/(nSubVols - i)];
      driveSize ← driveSize - size;
      type ← GetLvTypeFromUser["  Type: "L, type];
      ENDLOOP;
    IF broughtOnLine THEN PhysicalVolume.Offline[pvID];
    Confirm[IF volumeFound THEN twice ELSE once];
    PhysicalVolume.Offline[pvID ! ANY => CONTINUE];
    pvID ← PhysicalVolume.CreatePhysicalVolume[h, newPvName];
    FOR i: CARDINAL IN [0..bad) DO
      PhysicalVolume.MarkPageBad[pvID, badTable[i]] ENDLOOP;
    FOR i: CARDINAL IN [0..nSubVols) DO
      OPEN lvTable[i];
      lvID ← Volume.Create[pvID, size, name, type, MinPilotPage[h]];
      IF (lvsize ← Volume.GetAttributes[lvID].volumeSize) # size THEN {
        WriteString[name];
        WriteString["'s size decreased (because of bad pages) to "L];
        WriteLongNumber[lvsize];
        NewLine[]};
      ENDLOOP;
    END;

  FetchInitialMicrocode: PROC = {
    h: PhysicalVolume.Handle;
    InstallProc: PROC [getPage: PROC RETURNS [LONG POINTER]] = {
      WriteString["Formatting..."L];
      PhysicalVolume.AssertNotAPilotVolume[h];
      {ENABLE UNWIND => PhysicalVolume.FinishWithNonPilotVolume[h];
      FormatPilotDisk.FormatBootMicrocodeArea[h: h, passes: 1, retries: 0 !
	FormatPilotDisk.BadPage => {
          WriteString["Warning: page "L]; WriteLongNumber[p];
          WriteLine[" is bad (will be skipped). "L]; RESUME}];
      FormatPilotDisk.InstallBootMicrocode[h, getPage]}; -- ENABLE
      PhysicalVolume.FinishWithNonPilotVolume[h]};
    MyNameIs[
      myNameIs: "Initial Microcode Fetch"L,
      myHelpIs: "Fetch and install initial microcode"L];
    h ← GetDriveFromUser[];
    SELECT TRUE FROM
      LOOPHOLE[GetDriveType[h], CARDINAL] IN Device.PilotDisk =>
        BEGIN
	wasOnline: BOOLEAN = ForceOffline[h];
        OthelloDefs.FetchInitialMicrocode[InstallProc
        ! FormatPilotDisk.MicrocodeInstallFailure =>
            SELECT m FROM
              emptyFile       =>
	        AbortingCommand["That remote file is empty!"L];
              firstPageBad    =>
	        AbortingCommand["First microcode page of this disk is bad."L];
              flakeyPageFound => {
	        WriteLine["Intermittent page in microcode area."L]; RESUME};
              microcodeTooBig =>
	        AbortingCommand["Microcode too large."L];
              ENDCASE         =>
	        AbortingCommand["Unknown Install microcode error."L];
          FormatPilotDisk.CantInstallUCodeOnThisDevice => GOTO no];
	IF wasOnline THEN [] ← PhysicalVolume.AssertPilotVolume[h];
        WriteLine["Done"L]
        END;
      ENDCASE => GOTO no;
    EXITS
    no => AbortingCommand["microcode can't be installed on this disk"L]};

  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- support Procs
  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  -- this code depends upon Pilot's bad spot table being smaller than ours, 
  -- and an error being raised when Pilot's table is full.
  FillBadTable: PROC [
    badSpotArray: POINTER TO ARRAY [0..badTableSize) OF PhysicalVolume.PageNumber,
    badSpots: CARDINAL, p: PhysicalVolume.ID]
    RETURNS [badSpotTableFull: BOOLEAN ← FALSE] = {
    FOR i: CARDINAL IN [0..badSpots) DO
      PhysicalVolume.MarkPageBad[p, badSpotArray[i]
      ! PhysicalVolume.Error =>
        IF error = badSpotTableFull THEN {badSpotTableFull ← TRUE; EXIT}]
      ENDLOOP};
    
  ForceOffline: PROC [h: PhysicalVolume.Handle]
    RETURNS [wasOnline: BOOLEAN ← FALSE] = {
    p: PhysicalVolume.ID ← PhysicalVolume.nullID;
    DO
      p ← PhysicalVolume.GetNext[p];
      IF p = PhysicalVolume.nullID THEN EXIT;
      IF h = PhysicalVolume.GetAttributes[p].instance THEN {
	wasOnline ← TRUE; PhysicalVolume.Offline[p]; EXIT};
      ENDLOOP};
    
  FormatCheckDrive: PUBLIC PROC [
    bs: POINTER TO ARRAY [0..badTableSize) OF PhysicalVolume.PageNumber,
    op: {format, check}]
    RETURNS [couldDo: BOOLEAN, badSpots: CARDINAL, h: PhysicalVolume.Handle] = {
    -- leaves pv offline for op = format; restores previous state for op = check
    cbs: ARRAY [0..badTableSize) OF CARDINAL ← ALL[0];
    tooManyMsg: BOOLEAN ← FALSE;
    passes:     CARDINAL ← 1;

    NoteBad: PROC [p: PhysicalVolume.PageNumber] = {
      FOR i: CARDINAL IN [0..badSpots) DO
        IF bs[i] = p THEN {cbs[i] ← cbs[i] + 1; WriteBadSpot[p, cbs[i]]; RETURN}
	ENDLOOP;
      IF badSpots < LENGTH[bs↑] THEN {
        bs[badSpots] ← p; cbs[badSpots] ← cbs[badSpots] + 1;
	badSpots ← badSpots + 1; WriteBadSpot[p, 1]}
      ELSE {
        IF ~tooManyMsg THEN {
	  WriteLine["Too many bad pages"L]; tooManyMsg ← TRUE; column ← 0};
	WriteBadSpot[p, 1]};
      CheckUserAbort[! UNWIND => FormatSummary[]]};

    FormatSummary: PROC = {
      IF badSpots > 0 THEN {
        column ← 0;
        WriteLine["\rSummary of bad pages: badPage(countTimesBad)"L];
        FOR i: CARDINAL IN [0..badSpots) DO
	  WriteBadSpot[bs[i], cbs[i]] ENDLOOP;
        NewLine[]}};

    column: CARDINAL ← 0;
    WriteBadSpot: PROC [p: PhysicalVolume.PageNumber, cnt: CARDINAL] = {
      WriteFixedWidthNumber[p, 8]; WriteChar['(];
      WriteFixedWidthNumber[
	cnt, SELECT passes FROM IN [0..9] => 1, IN [10..99] => 2, ENDCASE => 3];
      WriteChar[')];
      IF (column MOD 5) = 4 THEN {column ← 0; NewLine[]} 
      ELSE column ← column+1};

    badSpots ← 0;
    h ← GetDriveFromUser[];
    SELECT TRUE FROM
      -- dam compiler won't coerce [CARDINAL] into CARDINAL
      LOOPHOLE[GetDriveType[h], CARDINAL] IN Device.PilotDisk => {
        OPEN FPD: FormatPilotDisk;
        pilotStart: PhysicalVolume.PageNumber = MinPilotPage[h];
        retries:    FormatPilotDisk.RetryLimit ← 0;
	              -- number of retries on bad page
        cylSize:    CARDINAL = CylinderSize[h];
	IF op = format THEN {
          passes  ← ReadShortNumber["Number of passes: "L, 1, 200, 10];
          retries ← ReadShortNumber[
            "Number of retries: "L, FPD.noRetries, FPD.retryLimit, FPD.noRetries];
          Confirm[twice];
	  [] ← ForceOffline[h];
	  -- format zero'th cylinder separately
	  PhysicalVolume.AssertNotAPilotVolume[h];
	  {ENABLE UNWIND => PhysicalVolume.FinishWithNonPilotVolume[h];
	  FPD.Format[h, 0, cylSize, passes, retries
          ! FPD.BadPage => {NoteBad[p]; RESUME}];
          -- format rest of disk possibly allowing for alto-type volume
	  -- or other device dependent dreck
	  FPD.Format[h, pilotStart, GetDriveSize[h] - pilotStart, passes, retries
          ! FPD.BadPage => {NoteBad[p]; RESUME}]}; -- ENABLE
	  PhysicalVolume.FinishWithNonPilotVolume[h]}
        ELSE {  -- op=scan
          wasOnline: BOOLEAN;
          Confirm[once];
          wasOnline ← ForceOffline[h];
          -- scan zero'th cylinder seperatly
	  FPD.Scan[h, 0, cylSize
	  ! FPD.BadPage => {NoteBad[p]; RESUME}];
          -- scan rest of disk possibly allowing for alto-type volume
	  -- or other device dependent dreck
	  FPD.Scan[h, pilotStart, GetDriveSize[h] - pilotStart
          ! FPD.BadPage => {NoteBad[p]; RESUME}];
	  IF wasOnline THEN [] ← PhysicalVolume.AssertPilotVolume[h]};
        FormatSummary[];
        RETURN[TRUE, badSpots, h]};
      ENDCASE => RETURN[FALSE, badSpots, h]};

<<
  IndicateAltoness1: PROC [h: PhysicalVolume.Handle] = {
    dn: CARDINAL = GetDriveNumber[h];
    sa4000Flavor[dn] ← 377B;
    IF ~Yes["Reserve space for alto volume? "L] THEN {
      sa4000Flavor[dn] ← 0; RETURN};
    sa4000Flavor[dn] ← ReadShortNumber[
      "Number of Model 44's:"L, 1, LAST[FormatPilotDisk.SA4000Model44Count], 1]};
>>

  MinPilotPage: PROC [h: PhysicalVolume.Handle]
    RETURNS [PhysicalVolume.PageNumber] = {
    OPEN FPD: FormatPilotDisk;
    SELECT GetDriveType[h] FROM
      DeviceTypes.sa1000, DeviceTypes.sa1004 => 
        RETURN[FPD.FirstSA1000PageForPilot];
      DeviceTypes.q2000, DeviceTypes.q2010, DeviceTypes.q2020,
      DeviceTypes.q2030, DeviceTypes.q2040, DeviceTypes.q2080 => 
        RETURN[FPD.FirstQ2000PageForPilot];
      DeviceTypes.sa4000, DeviceTypes.sa4008  =>
        DO
          dn: CARDINAL = GetDriveNumber[h];
          IF sa4000Flavor[dn] IN FPD.SA4000Model44Count THEN
            RETURN[FPD.SA4000FirstPageForPilot[sa4000Flavor[dn]]];
          -- IndicateAltoness1[h]
          ENDLOOP;
      DeviceTypes.t80    => RETURN[FPD.Firstt80PageForPilot];
      DeviceTypes.t300   => RETURN[FPD.Firstt300PageForPilot];
      ENDCASE => RETURN[0]};

  UnknownCylSize: ERROR = CODE;
  CylinderSize: PROC [h: PhysicalVolume.Handle] RETURNS [cylSize: CARDINAL] = {
    OPEN FPD: FormatPilotDisk, FPDx: FormatPilotDiskExtras;
    SELECT GetDriveType[h] FROM
      DeviceTypes.sa1000, DeviceTypes.sa1004 =>
        cylSize ← FPD.SA1004pagesPerCylinder;
      DeviceTypes.q2000 => cylSize ← FPD.Q2040pagesPerCylinder;
      DeviceTypes.q2010 => cylSize ← FPD.Q2010pagesPerCylinder;
      DeviceTypes.q2020 => cylSize ← FPD.Q2020pagesPerCylinder;
      DeviceTypes.q2030 => cylSize ← FPD.Q2030pagesPerCylinder;
      DeviceTypes.q2040 => cylSize ← FPD.Q2040pagesPerCylinder;
      DeviceTypes.q2080 => cylSize ← FPDx.Q2080pagesPerCylinder;
      DeviceTypes.sa4000, DeviceTypes.sa4008 =>
        cylSize ← FPD.SA4008pagesPerCylinder;
      DeviceTypes.t80   => cylSize ← FPD.t80pagesPerCylinder;
      DeviceTypes.t300  => cylSize ← FPD.t300pagesPerCylinder;
      ENDCASE => ERROR UnknownCylSize};

  ReserveLastCylinderForDiag: PROC [h: PhysicalVolume.Handle]
    RETURNS [ripOff: CARDINAL] = {
    ripOff ← CylinderSize[h ! UnknownCylSize => GOTO out];
    IF Wizard[] AND ~Yes["Reserve last cylinder for diagnostics? "L] THEN ripOff ← 0;
    EXITS out => RETURN[0]};
  
  RegisterCommandProc[@commandProcessor];

  END.....


11-Jun-81 10:56:57  Taft  Created file using excerpts from VolumeInitImplA.mesa
17-Jun-81 16:39:45  Glassman	Action: Prompt for passes and retries for formatting disk, summary after all passes done
17-Jul-81 16:44:52 Glassman	Action: Change name of file and print more summary information, OthelloDevice merged into OthelloDefs11-Jun-81 10:56:57  Taft  Created file using excerpts from VolumeInitImplA.mesa
26-Aug-81 18:34:22  Forrest  8.0c build
14-Oct-81 20:17:53  Forrest  othello reorg/add trident stuff/add diag cylinder stuff
13-Nov-81 16:24:41  Forrest  8.0e build
23-Nov-81 19:10:16  Forrest  add stuff for t80, t300
10-Dec-81 16:15:15  Forrest  add Quantum support; fix a couple of awful bugs in install ucode
 7-Jan-82 17:39:36  Forrest  Change for new FormatPilotDisk.CantInstallUCodeOnThisDevice
10-Mar-82 16:17:20  Linda    Formating ==> formatting
22-Aug-84 13:06:35  Masinter  remove Alto format command stuff