// BFS
// New Alto basic file system
// Outgoing procedures
external [
InitializeCbStorage; DoDiskCommand; GetCb
ActOnPages; WritePages
Bug
]
// Outgoing statics
external [
exchangeDisks
maxEC
restoreEC
fillInDA
eofDA
// these are defined later
DCdoNothing; DCread; DCwrite; DCwriteLabel; DCseekOnly
]
static [
exchangeDisks=false
maxEC=5
restoreEC=3
fillInDA=#77777
eofDA=0
]
// incoming procedures
external [
Zero; MoveBlock
errhlt; SYSERR
ReturnTo
DefaultArgs1
DisableInterrupts; EnableInterrupts
]
// incoming statics
external [
oneBits
freePageId
]
// declarations for the disk allocation bit table
// the DA structure declaration is also used
// Outgoing procedures
external [
AssignDiskPage; ReleaseDiskPage
RealDA; VirtualDA
]
// Incoming statics
external [
diskBitTable; diskBTsize
]
manifest [
nTracks = 203;
nHeads = 2;
nSectors = 12;
msb=#100000
allOnes=#177777
wordLength=16
]
structure [ wordAddr bit 12; bitAddr bit 4 ]
manifest [ wordsPerPage=#400; charsPerPage=wordsPerPage*2 ]
manifest lFID=3
// disk data structures, transcribed from osstructures
// page 1 locations
manifest [
nextDiskCommand = #521 // pointer to DCB
diskStatus = #522 // @DS
lastDiskAddress = #523 // @DA
sectorInterrupts = #524
]
// disk address
structure DA[
sector bit 4
track bit 9
head bit 1
disk bit 1
restore bit 1
]
// disk header
structure DH[
packId word
diskAddress @DA
]
// disk label. *=set by DoDiskCommand
structure DL[
next word // disk address of next file
// page, or eofDA
previous word // disk address of previous
// file page, or eofDA
blank word
numChars word // between 0 and charsPerPage inclusive.
// ne charsPerPage only on last page
pageNumber word // * leader is page 0, first data
// page is page 1
fileId word lFID // *
]
manifest lDL=size DL/16
// disk status word. See hardware manual for detailed definitions
structure DS[
sector bit 4
done bit 4
seekFailed bit
seekInProgress bit
notReady bit
dataLate bit
noTransfer bit
checksumError bit
finalStatus bit 2
]
// disk command
structure DC[
seal bit 8 // must be #110
headerAction bit 2
labelAction bit 2
dataAction bit 2
seekOnly bit 1
exchangeDisks bit 1 // disk controller inverts
// DCB.diskAddress if this bit
// is set
]
manifest diskCommandSeal=#110
// possible disk actions in command word
manifest [ diskRead = 0; diskCheck = 1; diskWrite = 2]
// disk command block. *=set by DoDiskCommand, $=defaulted by
// DoDiskCommand if 0
structure DCB[
nextCommand word // *
status @DS // set when command is completed
command @DC // *
headerAddress word // * these are memory addresses
labelAddress word // $
dataAddress word // *
normalWakeups word // $
errorWakeups word // $
header @DH =
[
blank word
diskAddress @DA // * if DA argument ne fillInDA
]
]
manifest lDCB=size DCB/16
// *=initialized by InitializeCbStorage; everything else is zeroed
structure CBZ[
length word // *
DAs word
cleanupRoutine word
currentPage word // * set only by InitCbStorage
currentNumChars word
normalWakeups word
errorWakeups word
errorCount word
queueHead word // * address of first entry
queueTail word // * address of last entry + 1
endQueueVec word // * address of first word beyond
queueVec ↑ 0, 1000 word // * contains one more entry than
// there are cb's in the zone
]
manifest [
lCBZ=offset CBZ.queueVec/16+1
CBZqueueHead=offset CBZ.queueHead/16
CBZqueueTail=offset CBZ.queueTail/16
]
// *=initialized by InitializeCbStorage; everything else is zeroed
structure CB[
// the DCB must come first
@DCB // a free CB must have
// status=DSgoodStatus;
// initialization leaves it
// that way
label @DL
truePageNumber word
// 'variable' part of the cb ends here
zone word // *
retry word // *
]
manifest [ lCB=size CB/16; lVarCB=offset CB.zone/16 ]
// normally we want room for 3 cb's in a zone
manifest CBzoneLength=lCBZ+3*(lCB+1)
manifest [
DSerrorBits=msb rshift offset DS.seekFailed +
msb rshift offset DS.notReady +
msb rshift offset DS.dataLate +
msb rshift offset DS.checksumError +
(-(msb rshift (size DS.finalStatus-1)))
rshift offset DS.finalStatus;
DSdoneBits=(-(msb rshift (size DS.done-1)))
rshift offset DS.done;
DSgoodStatusMask=DSerrorBits % DSdoneBits;
// good status means that all the error bits are 0
DSgoodStatus=DSdoneBits
DSfreeStatus=msb rshift (offset DS.done+size DS.done-1)
]
// disk command actions
manifest [
DCheaderx=msb rshift (offset DC.headerAction+size DC.headerAction-1)
DClabelx=msb rshift (offset DC.labelAction+size DC.labelAction-1)
DCdatax=msb rshift (offset DC.dataAction+size DC.dataAction-1)
]
manifest [
DCactionSeal=#321
DCaS=DCactionSeal*(msb rshift (offset DC.seal+size DC.seal-1))
]
// the actions which are needed by callers of bfs are (external)
// statics; the others are manifests
static [
DCdoNothing=#376 // only interpreted by ActOnPages
DCread=diskCheck*(DCheaderx+DClabelx)+diskRead*DCdatax+DCaS
DCwrite=diskCheck*(DCheaderx+DClabelx)+diskWrite*DCdatax+DCaS
DCwriteLabel=diskCheck*DCheaderx+diskWrite*(DClabelx+DCdatax)+DCaS
DCseekOnly=msb rshift offset DC.seekOnly+DCaS
]
manifest [
DCreadLabel=diskCheck*DCheaderx+diskRead*(DClabelx+DCdatax)+DCaS
]
let InitializeCbStorage(zone, length, firstPage, retry,
clearZone) be [
if clearZone then Zero(zone, length)
zone>>CBZ.length=length
zone>>CBZ.currentPage=firstPage
let e=lv zone>>CBZ.queueVec
rv e=0; zone>>CBZ.queueTail=e
e=e+1; zone>>CBZ.queueHead=e
let cb=zone+length
[
cb=cb-lCB; if cb le e break
cb>>CB.zone=zone; cb>>CB.retry=retry
cb>>CB.status=DSfreeStatus
rv e=cb; e=e+1
] repeat
zone>>CBZ.endQueueVec=e
]
and NextCb(zone, pointer)=valof [
let t=zone!pointer; let u=t+1
if u ge zone>>CBZ.endQueueVec then u=lv zone>>CBZ.queueVec
zone!pointer=u; resultis t
]
// Expects command and label to both be zeroed on entry, or
// otherwise appropriately initialized
and DoDiskCommand(cb, CA, DA, fileId, pageNumber, action) be [
let z=cb>>CB.zone
cb>>CB.headerAddress=lv(cb>>CB.header)
let la=cb>>CB.labelAddress
if la eq 0 then [
la=lv(cb>>CB.label)
cb>>CB.labelAddress=la
]
cb>>CB.dataAddress=CA
if cb>>CB.normalWakeups eq 0
then cb>>CB.normalWakeups=z>>CBZ.normalWakeups
if cb>>CB.errorWakeups eq 0
then cb>>CB.errorWakeups=z>>CBZ.errorWakeups
MoveBlock(lv (la>>DL.fileId), fileId, lFID)
la>>DL.pageNumber=pageNumber
cb>>CB.truePageNumber=pageNumber
if DA ne fillInDA then cb>>CB.diskAddress=DA
if action<<DC.seal ne DCactionSeal then SYSERR(action,1000)
cb>>CB.command=action
if exchangeDisks then cb>>CB.command.exchangeDisks=1
cb>>CB.command.seal=diskCommandSeal
// QueueDiskCommand(cb)
DisableInterrupts()
let p=nextDiskCommand-offset CB.nextCommand/16
[ let np=p>>CB.nextCommand; if np eq 0 break; p=np ] repeat
p>>CB.nextCommand=cb
// take care of possible race with disk controller
if rv nextDiskCommand eq 0 then rv nextDiskCommand=cb
EnableInterrupts()
p=NextCb(z, CBZqueueTail)
if rv p ne 0 then Bug(); rv p=cb
]
and GetCb(zone, dontClear; numargs na)=valof [
let t=NextCb(zone, CBZqueueHead)
let cb=rv t; if cb eq 0 then Bug(); rv t=0
[
if (cb>>CB.status & DSdoneBits) ne 0 break
if rv nextDiskCommand eq 0 & (cb>>CB.status &
DSdoneBits) eq 0 then ClearDiskError()
] repeat
// remove seal
cb>>CB.command=0
// this is the test for errors
let s=cb>>CB.status & DSgoodStatusMask
test s eq DSgoodStatus
ifso [
t=zone>>CBZ.cleanupRoutine
if t ne 0 then t(cb)
zone>>CBZ.currentNumChars=cb>>CB.labelAddress>>DL.numChars
unless cb>>CB.diskAddress.restore do
zone>>CBZ.errorCount=0
unless na ge 2 & dontClear do Zero(cb, lVarCB)
resultis cb
]
ifnot if s eq DSfreeStatus then [
Zero(cb, lVarCB); resultis cb
]
// we should discriminate among the various kinds of error
[ if rv nextDiskCommand eq 0 break ] repeat
let ec=zone>>CBZ.errorCount+1; zone>>CBZ.errorCount=ec
if ec ge maxEC then [
// unrecoverable error
// temporary code
SYSERR(cb, 1001)
]
let r=cb>>CB.retry
let DA=cb>>CB.diskAddress
InitializeCbStorage(zone, zone>>CBZ.length,
cb>>CB.truePageNumber, r, false)
if ec ge restoreEC then [
DA<<DA.restore=1
DoDiskCommand(GetCb(zone), 0, DA, 0, 0, DCseekOnly)
]
ReturnTo(r)
]
// this should never be called if there is only one process
and ClearDiskError(zone) be Bug()
// returns the page number of the last page successfully acted
// on.
// CAs (core addresses) and DAs (disk addresses) are vectors
// indexed by page number (e.g. CAs!firstPage)
// the arguments following action are optional; if one of them is
// omitted or 0, the default action is taken
and ActOnPages(CAs, DAs, fileId, firstPage, lastPage, action,
lvNumChars, lastAction, fixedCA, cleanupRoutine; numargs na)=
valof [
let GetNextDA(cb) be [
let t=lv ((cb>>CB.zone>>CBZ.DAs)!(cb>>CB.truePageNumber+1))
if rv t eq fillInDA then rv t=cb>>CB.labelAddress>>DL.next
]
let dummy=nil
DefaultArgs1(lv na, -6, lv dummy, action, 0 ,GetNextDA)
let zone=vec CBzoneLength
InitializeCbStorage(zone, CBzoneLength, firstPage,
Aretry, true)
zone>>CBZ.DAs=DAs
zone>>CBZ.cleanupRoutine=cleanupRoutine
Aretry: [
// Note that each cb is used twice: to hold the DL for
// page i-1, and then to hold the DCB for page i. It isn't
// reused until the command for page i is done, and that is
// guaranteed to be after the DL for page i-1 is no longer
// needed, since everything is done strictly sequentially by
// page number.
let cb=GetCb(zone)
for i=zone>>CBZ.currentPage to lastPage do [
if DAs!i eq eofDA then
[ lastPage = i-1
break;
]
let a=action; if i eq lastPage then a=lastAction
if a eq DCdoNothing then loop
let nextCb=GetCb(zone)
cb>>CB.labelAddress=((DAs!(i+1) eq fillInDA) ?
lv nextCb>>CB.diskAddress, lv nextCb>>CB.label)
DoDiskCommand(cb, (CAs eq 0 ? fixedCA, CAs!i),
DAs!i, fileId, i, a)
cb=nextCb
]
while rv zone>>CBZ.queueHead ne 0 do GetCb(zone)
]
rv lvNumChars=zone>>CBZ.currentNumChars
resultis lastPage
]
// note that DAs!(firstpage-1) will be referenced if DAs!firstPage
// eq fillInDA or eofDA, and that DAs!(lastPage+1) will be set to
// eofDA except when the label of lastPage doesn't need to be
// rewritten
// the arguments following lastPage are optional, as for ActOnPages
and WritePages(CAs, DAs, fileId, firstPage, lastPage, lastAction,
lvNumChars, lastNumChars, fixedCA; numargs na)=valof [
let CheckFreePage(cb) be [
let fid=lv cb>>CB.labelAddress>>DL.fileId
for i=0 to lFID-1 do if fid!i ne freePageId!i then
(cb>>CB.zone>>CBZ.DAs)!(cb>>CB.truePageNumber)=fillInDA
]
let numChars=nil; let firstNewPage=nil
DefaultArgs1(lv na, 5, 0, 0, charsPerPage, 0)
if lastAction eq 0 then lastAction=DCwrite
if lvNumChars eq 0 then lvNumChars=lv numChars
// first proceed as for a read until there are no more
// preallocated pages to write into
test DAs!firstPage eq fillInDA
ifso firstNewPage=firstPage
ifnot [
firstPage=ActOnPages(CAs, DAs, fileId, firstPage,
lastPage, DCwrite, lvNumChars, lastAction,
fixedCA)
if firstPage eq lastPage & (lastAction ne DCwrite %
rv lvNumChars eq lastNumChars) then
resultis lastPage
firstNewPage=firstPage+1
]
// code to assign more pages
// set up eofDA as the page after the end of the file
[
let sink=vec wordsPerPage
for i=firstNewPage to lastPage do
DAs!i=AssignDiskPage(DAs!(i-1))
let j=ActOnPages(0, DAs, freePageId, firstNewPage,
lastPage, DCreadLabel, 0, 0, sink, CheckFreePage)
for i=firstNewPage to lastPage do [
DAs!firstNewPage=DAs!i
if DAs!i ne fillInDA then
firstNewPage=firstNewPage+1
]
if firstNewPage gr lastPage then break
]
repeat
// all the pages have been checked. Write labels and data
[
let zone=vec CBzoneLength;
InitializeCbStorage(zone, CBzoneLength, firstPage,
Wretry, true)
Wretry: for i=zone>>CBZ.currentPage to lastPage do [
let cb=GetCb(zone)
cb>>CB.label.next=((i eq lastPage & lastNumChars ne charsPerPage) % (DAs ! (i+1) eq fillInDA) ? 0,DAs!(i+1))
cb>>CB.label.previous=DAs!(i-1)
cb>>CB.label.numChars=(i eq lastPage ?
lastNumChars, charsPerPage)
DoDiskCommand(cb, (CAs eq 0 ? fixedCA, CAs!i),
DAs!i, fileId, i, DCwriteLabel)
]
while rv zone>>CBZ.queueHead ne 0 do GetCb(zone)
]
resultis lastPage
]
and AssignDiskPage(realPrevDA)=valof [
let base=VirtualDA(realPrevDA)+1
let baseWa=base<<wordAddr; let baseBa=base<<bitAddr
[
for wa=baseWa to diskBTsize-1 do [
let w=diskBitTable!wa
if w ne allOnes then for ba=baseBa to wordLength-1 do
if (w & oneBits!ba) eq 0 then [
diskBitTable!wa=w % oneBits!ba
resultis RealDA(wa*wordLength+ba)
]
baseBa=0
]
if baseWa eq 0 then [ errhlt("Disk full") ] repeat
baseWa=0
]
repeat
]
and ReleaseDiskPage(realDA) be [
let v=VirtualDA(realDA); let wa=v<<wordAddr
diskBitTable!wa=diskBitTable!wa & not oneBits!(v<<bitAddr)
]
and VirtualDA(realDA)=((realDA<<DA.disk*nTracks+realDA<<DA.track)*
nHeads+realDA<<DA.head)*nSectors+realDA<<DA.sector
and RealDA(virtualDA)=valof [
// Div(x) returns virtualDA/x and leaves the remainder in virtualDA
let Div=table [
#55001 // sta 3 savedPC,2
#25004 // lda 1 firstArg,2
#155000 // mov 2 3
#111000 // mov 0 2
#102460 // mkzero 0 0
#61021 // div
#77400 // 77400
#171000 // mov 3 2
#45004 // sta 1 firstArg,2
#35001 // lda 3 savedPC,2
#1401 // jmp 1,3
]
let realDA=0
realDA<<DA.sector=Div(nSectors)
realDA<<DA.head=Div(nHeads)
realDA<<DA.track=Div(nTracks)
realDA<<DA.disk=virtualDA
resultis realDA
]
and Bug(a, b, c) be errhlt("Bug")
// Stuff supplied by bfs
// manifest [ wordsPerPage=#400; charsPerPage=wordsPerPage*2 ]
// manifest [ lFID=3 ]
// outgoing procedures
external [
CreateFile
DeletePages
]
// incoming procedures
external [
SetBlock
]
// incoming statics
external [
lastSN
]
// Miscellaneous declarations
structure STRING[ length byte; body ↑ 1,255 byte ]
structure TIME[ blank word 2 ]
manifest lTIME=size TIME/16
// serial number
structure SN[
[
directory bit 1
random bit 1
part1 bit 14
]
= word1 word
part2 word
= word2 word
]
manifest lSN=size SN/16
// file identifier
structure FID[
version word
serialNumber @SN
]
// file pointer
structure FP[
serialNumber word lSN
version word
blank word
leaderDA word
]
manifest lFP=size FP/16
// leader page
structure LD[
blank word 6
name @STRING
fileId word lFID
created @TIME
read @TIME
written @TIME
]
// the following stuff doesn't have anything to do with disk
// streams. It performs file-related functions which are
// somewhat higher-level than ActOnPages and WritePages
let MakeFileId(fid, filePtr) be [
MoveBlock(lv fid>>FID.serialNumber,
lv filePtr>>FP.serialNumber, lSN)
fid>>FID.version=filePtr>>FP.version
]
and CreateFile(name, filePtr) be [
Zero(filePtr, lFP)
let b=vec wordsPerPage; Zero(b, wordsPerPage)
let t=lastSN>>SN.part2+1; lastSN>>SN.part2=t
if t eq 0 then lastSN>>SN.word1=lastSN>>SN.word1+1
MoveBlock(lv filePtr>>FP.serialNumber, lastSN, 2)
filePtr>>FP.version=1
let fid=vec lFID; MakeFileId(fid, filePtr)
Daytime(lv b>>LD.created)
MoveBlock(lv b>>LD.written, lv b>>LD.created, lTIME)
MoveBlock(lv b>>LD.read, lv b>>LD.created, lTIME)
for i=0 to name>>STRING.length rshift 1 do
(lv b>>LD.name)!i=name!i
let DAs=vec 4; DAs=DAs+1; DAs!(-1)=eofDA; DAs!0=fillInDA
WritePages(0, DAs, fid, 0, 1, 0, 0, 0, b)
filePtr>>FP.leaderDA=DAs!0
]
// delete pages starting at firstDA and continuing to the end of
// the file. CA is a page-size buffer which is zeroed
and DeletePages(CA, firstDA, fileId, firstPage) be
while firstDA ne eofDA do [
manifest biteSize=128; let DAs=vec biteSize
SetBlock(DAs, fillInDA, biteSize+1)
DAs=DAs-firstPage; DAs!firstPage=firstDA
let lastPageFound=ActOnPages(0, DAs, fileId, firstPage,
firstPage+biteSize-1, DCread, 0, 0, CA)
Zero(CA, wordsPerPage)
ActOnPages(0, DAs, freePageId, firstPage,
lastPageFound, DCwriteLabel, 0, 0, CA)
for i=firstPage to lastPageFound do ReleaseDiskPage(DAs!i)
firstPage=lastPageFound+1; firstDA=DAs!firstPage
]
// dummy version of a routine which will someday be part of the
// operating system
and Daytime(v) be [ v!0=0; v!1=0 ]