// TfsInit.bcpl
// Copyright Xerox Corporation 1979, 1980, 1981

//	Last modified April 30, 1981  8:28 PM by Taft

get "Altofilesys.d"
get "Disks.d"
get "Tfs.d"
get "Streams.d"
get "AltoDefs.d"

external
[ 
// TFS procedures defined here
TFSInit
TFSSetDisk
TFSTryDisk
TFSDiskModel

// TFS procedures defined elsewhere
TFSReadBTpage
TFSActOnPages
TFSVirtualDA
TFSRealDA
TFSWritePages
TFSCreateFile
TFSDeletePages
TFSAssignDiskPage
TFSReleaseDiskPage
TFSInitializeCbStorage
TFSDoDiskCommand
TFSGetCb
TFSClose
TFSNonEx
DoRecovery
TFSWaitQuiet
TFSModShift
TFSModShiftA
TFSSilentBoot
TFSCreateDDMgr
OpenDD

// OS
ActOnDiskPages
SetWorkingDir
OpenFile
Closes
Gets
ReadBlock
WriteBlock
Allocate
Free
Zero
MoveBlock
SysErr
StartIO 
DefaultArgs
Idle
Noop

// Statics
TFSLock
TFSLeaveDisplay
OsVersion
AltoVersion
]
manifest RTC=#430		//Real time clock

//----------------------------------------------------------------------------
let TFSInit(zone, initmode, driveNumber, ddMgr, freshDisk; numargs na) = valof
//----------------------------------------------------------------------------
[
DefaultArgs(lv na, -1, 0, 0, 0, 0)
manifest minOsVersion = 16
if OsVersion ls minOsVersion then SysErr(minOsVersion, ecOsVersion)

// Alto II convention is to set location 613 to -1.
// The reason we recompute AltoVersion is to ensure it is correct even
// in a boot file that was created on a different type of machine.
AltoVersion = (table [ #61014; #1401 ])()  // VERS
@#613 = AltoVersion<<VERS.eng gr 1
if AltoVersion<<VERS.eng ge 4 then
   [  // Dolphin or Dorado
   TFSLeaveDisplay = true	// don't need to turn display off
   TFSSilentBoot = Noop		// don't silent boot when finishing
   TFSModShift = TFSModShiftA	// use software ECC correction subroutine
   ]

// separate out the file system number (if T-300)
let fsNumber = driveNumber rshift 8
driveNumber = driveNumber & #377

// First, try to select a disk that does not exist.  If
// selection succeeds, it is because the controller does not
// have a multi-drive multiplexor attached.  Note that the
// important bit in the disk number is 10b, because this will
// cause none of the 8 copies of the selection logic to be
// enabled.  (The drive-select decoder is only 4 bits wide!!)
// 37 is used rather than 17 because 37 is specifically defined
// as a nonexistent drive on the Dorado.
let p = TFSTryDisk(37b)		//See if controller hooked up
if p eq 0 then resultis 0	//No -- no dice
if driveNumber ne 0 & p eq 1 then resultis 0	//Not multi-drive controller
p = TFSTryDisk(driveNumber)	//Now look for exact drive
if p ne 1 then resultis 0	//Not there or not on-line

// Setup structures needed for subsequent accesses
let disk = Allocate(zone, lTFSDSK)
Zero(disk, lTFSDSK)
TFSSetDisk(disk, initmode, driveNumber)
disk>>TFSDSK.zone = zone

// do a restore if the drive appears to be in trouble
if KBLK>>KBLK.DeviceCk % KBLK>>KBLK.SeekInc then DoRecovery(disk, diskRestore)

// set up physical disk shape
let model = DetermineDiskModel(disk)
disk>>TFSDSK.model = model

// Set up file system related parameters.
// Largest possible file system has 2↑16-1 pages.  If the physical disk
// is bigger than this, then multiple file systems must be constructed.
// Each file system (except the last) consists of the largest integral
// number of tracks (nVTracks) such that the file system contains fewer
// than 2↑16 pages, and is offset from the beginning of the disk by an
// integral number of tracks (firstVTrack).
// The correct general computation for this is:
//   nVTracks = (2↑16-1)/(nHeads*nSectors)
//   firstVTrack = fsNumber*nVTracks
// However, this is messy to do in BCPL because it requires unsigned
// division.  Since the Trident T-300 is the only disk large enough
// to require this treatment, it is therefore handled as a special case.
manifest maxT300nVTracks = 383  // (2↑16-1)/(19*9)
if (model ne 300 & fsNumber ne 0) % fsNumber gr 2 then
   [ Free(zone, disk); resultis 0 ]  //no such fs on pack
disk>>TFSDSK.firstVTrack = fsNumber*maxT300nVTracks
disk>>TFSDSK.nVTracks = disk>>TFSDSK.nTracks-disk>>TFSDSK.firstVTrack
if model eq 300 & disk>>TFSDSK.nVTracks gr maxT300nVTracks then
   disk>>TFSDSK.nVTracks = maxT300nVTracks

// TFSInit (cont'd)

// Setup directory fp for this disk
let fidTFSSysdir = table [ #100000 ; 100 ]
let fpTFSSysDir = lv (disk>>TFSDSK.fpTFSSysDirblk)
MoveBlock(lv fpTFSSysDir>>FP.serialNumber, fidTFSSysdir, lSN)
fpTFSSysDir>>FP.version = 1
fpTFSSysDir>>FP.leaderVirtualDa = 1
SetWorkingDir("<SysDir.",fpTFSSysDir,disk)

// Open directory, and setup important fp's for this disk, etc.
unless freshDisk do
   [
   let DAs = vec 2
   DAs!1 = 1
   if ActOnDiskPages(disk, 0, DAs+1, fpTFSSysDir, 0, 0, DCreadnD,
    0, 0, 0, 0, 0, true) ls 0 then
      [ Free(zone, disk); resultis 0 ]  //no SysDir
   let dds = OpenFile("DiskDescriptor", ksTypeReadOnly, 0, 0,
      disk>>TFSDSK.fpDiskDescriptor, 0, zone, 0, disk)
   if dds eq 0 then [ Free(zone, disk); resultis 0 ]  //no DiskDescriptor
   ReadBlock(dds, lv disk>>TFSDSK.kd, lTFSKDHeader)
   if disk>>TFSDSK.version ne TFSKDversion then
      [ Closes(dds); Free(zone, disk); resultis 0 ]  //version incompatible

   // Count free pages in the file system
   for i=1 to TFSwordsPerPage-lTFSKDHeader do Gets(dds)
   let pages=0
   for i=1 to disk>>TFSDSK.diskBTsize do
      [
      let w=Gets(dds)
      test w eq 0
         ifso pages = pages+16
         ifnot if w ne -1 then
            for j=0 to 15 do if ((w rshift j)&1) eq 0 then pages = pages+1
      ]

   // Now fill in free count where it is supposed to be:
   disk>>TFSDSK.freePages = pages
   Closes(dds)
   ]

// set up DD manager, if needed
if initmode ne 0 then
   [
   if ddMgr eq 0 then ddMgr = TFSCreateDDMgr(zone)
   disk>>TFSDSK.ddMgr = ddMgr
   OpenDD(ddMgr, disk)
   ]

// return handle on disk object
resultis disk
]

//----------------------------------------------------------------------------
and TFSTryDisk(drive) = valof
//----------------------------------------------------------------------------
// Try to access disk and return:
//	  0 if controller appears not to work
//	  1 if disk appears to be there
//	  2 if disk appears to be absent
[
while TFSLock ne 0 do Idle()
TFSLock = -1		//lock out all other access
TFSWaitQuiet(false)
let oldDrive = KBLK>>KBLK.drive
if oldDrive uge #17 then oldDrive = 0

let result = TryDisk(drive)

// If selected a nonexistent drive, must leave controller pointing
// at the previously-selected drive, which is assumed to exist.
// (Note that drive 0 must exist or the controller won't work --
// this is a hardware restriction.)
if result eq 2 then TryDisk(oldDrive)

TFSLock = 0
resultis result
]

//----------------------------------------------------------------------------
and TryDisk(drive) = valof
//----------------------------------------------------------------------------
[
StartIO(#20)		//turn off and reset controller

   [ //repeat
   KBLK>>KBLK.ptr = 0
   KBLK>>KBLK.drive = drive%100000b	//Force drive select
   KBLK>>KBLK.Status = -1
   KBLK>>KBLK.track = -1		//Force seek next time
   let timedOut=nil

   // See if unit responds.  Let each StartIO work for 1/20 second.
   // If Status has not been set after last StartIO, timedOut will
   // be true.  The reason for several StartIO's is that we may
   // be trying to select a non-existent disk, which will not
   // provide sector pulses and corresponding wakeups.
   for reTries = 1 to 4 do	//May have to give several StartIO's
      [				// for a non-ex disk
      StartIO(#40)	// Causes microcode to start
      timedOut = false
      let inTime = @RTC
         [
         if KBLK>>KBLK.Status ne -1 then break
         if (@RTC-inTime) gr 1 then timedOut=true
         Idle()
         ] repeatuntil timedOut
      unless timedOut break
      ]

   if timedOut then resultis 0	//Controller not hooked up
   ] repeatuntil KBLK>>KBLK.WrLate eq 0

// Which status bit to test? A drive will select if power is on
// (even if the disk is not spinning): NotSelected is therefore only
// an indication of a non-ex or fully powered down drive. The OnLine
// indication is best, although if the drive is in the middle of
// a restore, it is conceivable that OnLine goes away.
resultis KBLK>>KBLK.NotOnLine eq 0? 1, 2
]

//----------------------------------------------------------------------------
and TFSDiskModel(disk) = disk>>TFSDSK.model
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
and DetermineDiskModel(disk) = valof
//----------------------------------------------------------------------------
// Determines what model disk is attached, puts default disk shape
// parameters in the disk object, and returns the disk model number:
// 	80	Trident T-80
//	300	Trident T-300
//	4004	Shugart SA-4004
//	4008	Shugart SA-4008

[
// The following parallel tables define the disk parameters for the
// disk models currently known about.  Determination of disk model
// is done by selecting heads and seeing what happens -- each model
// disk has a different maximum head number.
// nHeadsT!(i-1) but not nHeadsT!i is a legal head for disk model modelT!i,
// 0 <= i <= maxModel.  nHeadsT must be monotonic!
manifest maxModel = 3
let modelT = table [ 4004; 80; 4008; 300 ]
let nTracksT = table [ 202; 815; 202; 815 ]
let nHeadsT = table [ 4; 5; 8; 19 ]
let nSectorsT = table [ 8; 9; 8; 9 ]

while TFSLock ne 0 do Idle()
TFSLock = -1
TFSWaitQuiet(false)
let modelIndex = 0

   [ // repeat
   let kcb = vec lKCB
   Zero(kcb, lKCB)
   kcb>>KCB.drive = disk>>TFSDSK.driveNumber

   // Issue no real commands -- just select a head 
   kcb>>KCB.head = nHeadsT!modelIndex
   kcb>>KCB.ID = dcbID
   KBLK>>KBLK.ptr = kcb
   while KBLK>>KBLK.ptr ne 0 do Idle()

   // Trident disks raise CylOvfl and Shugart disks raise SecOvfl
   // when an illegal head is selected.
   if KBLK>>KBLK.CylOvfl % KBLK>>KBLK.SecOvfl then break
   unless KBLK>>KBLK.WrLate do modelIndex = modelIndex + 1
   ] repeatuntil modelIndex eq maxModel

TFSLock = 0
DoRecovery(disk, diskReset)  //turn off error indications

// Now set physical disk shape parameters
disk>>TFSDSK.nDisks = 1
disk>>TFSDSK.nTracks = nTracksT!modelIndex
disk>>TFSDSK.nHeads = nHeadsT!modelIndex
disk>>TFSDSK.nSectors = nSectorsT!modelIndex
resultis modelT!modelIndex
]

//----------------------------------------------------------------------------
and TFSSetDisk(disk, initmode, drivenumber) be
//----------------------------------------------------------------------------
// TFSSetDisk sets up constant part of disk structure.
// disk points to the TFSDSK structure for drive drivenumber
[
// Access dispatches
disk>>DSK.ActOnDiskPages = TFSActOnPages
disk>>DSK.WriteDiskPages = TFSWritePages
disk>>DSK.CreateDiskFile = TFSCreateFile
disk>>DSK.DeleteDiskPages = TFSDeletePages
disk>>DSK.AssignDiskPage = TFSAssignDiskPage
disk>>DSK.ReleaseDiskPage = TFSReleaseDiskPage
disk>>DSK.CloseDisk = TFSClose
disk>>DSK.VirtualDiskDA = TFSVirtualDA
disk>>DSK.RealDiskDA = TFSRealDA
disk>>DSK.InitializeDiskCBZ = TFSInitializeCbStorage
disk>>DSK.DoDiskCommand = TFSDoDiskCommand
disk>>DSK.GetDiskCb = TFSGetCb

// Deny access in certain cases
// WriteDiskPages is NOT included because it has several uses that do not
// require modifications to the bit table.
if initmode eq 0 then
   [
   disk>>DSK.CreateDiskFile = TFSNonEx
   disk>>DSK.DeleteDiskPages = TFSNonEx
   disk>>DSK.AssignDiskPage = TFSNonEx
   disk>>DSK.ReleaseDiskPage = TFSNonEx
   ]

// FP's
disk>>DSK.fpSysDir = lv disk>>TFSDSK.fpTFSSysDirblk
disk>>DSK.fpDiskDescriptor = lv disk>>TFSDSK.fpTFSDDblk
disk>>DSK.fpWorkingDir = lv disk>>TFSDSK.fpTFSWDblk

// Working dir
disk>>DSK.nameWorkingDir=lv disk>>TFSDSK.WDNameblk

// Other parameters
disk>>DSK.lnPageSize = TFSlnWordsPerPage
disk>>DSK.driveNumber = drivenumber
disk>>DSK.retryCount = 16
disk>>DSK.totalErrors = 0
disk>>DSK.diskKd = lv disk>>TFSDSK.kd
disk>>DSK.lengthCBZ = lTFSCBZ
disk>>DSK.lengthCB = lCB
disk>>TFSDSK.initmode = initmode
]