BootSwapGerm.Mesa
Copyright Ó 1985, 1987, 1988 by Xerox Corporation. All rights reserved.
Taft, February 25, 1983 3:00 pm
Andrew Birrell, December 13, 1983 3:18 pm
Hal Murray, May 24, 1986 5:34:08 pm PDT
Russ Atkinson, February 13, 1985 5:09:49 pm PST
Willie-sue, May 12, 1988 11:32:32 am PDT
Bill Jackson (bj) August 25, 1988 5:57:28 pm PDT
Christophe Cuenod September 12, 1988 9:37:14 am PDT
DIRECTORY
Basics USING [ BITSHIFT, HighByte, LongDiv, LongMult, LowByte, ShortNumber ],
BootChannel USING [ CloseChannel, Create, Operation, Handle, Result, SyncTransfer, Transfer ],
BootFile USING [ Continuation, currentVersion, Entry, Header, InLoadMode, Location, maxEntriesPerHeader, maxEntriesPerTrailer, MDSIndex, Trailer ],
Device USING [ nullType ],
GermSwap USING [ Action, countSkip, Initialize, InitializeMDS, LP, mdsiGerm, pRequest, pCountGerm, pInitialLink, pMon, ReadMDS, ResponseKind ],
MesaRuntimeInit USING [ Start, TrapsImpl ],
MPCodes USING [ Code, cantWorldSwap, germStarting, germOutLoad, germInLoad, germMapIO, germBadBootFile, germBadPhysicalVolume, germControlFault, germDeviceError, germERROR, germFinished, germStartFault ],
PrincOps USING [ Alignment, Base, BytePC, Condition, ControlLink, flagsClean, flagsVacant, FrameHandle, GlobalFrameHandle, first64K, logWordsPerPage, NullFrame, PageCount, PageNumber, PageState, PageValue, PDA, PsbNull, QueueEmpty, RealPageNumber, returnOffset, SD, sError, sErrorList, sReturnError, sReturnErrorList, sSignal, sSignalList, StateVector, sUnnamedError, UnboundLink, wordsPerPage, zRET, zSLB ],
PrincOpsUtils USING [ AddressForPageNumber, ExchangePageFlags, ExchangePageState, Free, GetPageValue, GetReturnFrame, HighHalf, IsBound, LowHalf, SetPageValue, WriteWDC ],
ProcessorFace USING [ GetClockPulses, GetNextAvailableVM, microsecondsPerHundredPulses, SpecialSetMP, Start ],
ProcessorFaceExtras USING [ dFirst64KPage, dFirst64KDescriptor ],
RuntimeError, --using lots--
SparcSoftcardInit USING [ InitializeSoftcard, ResetSoftcard ],
VolumeFormat USING [ PhysicalRoot, PRCurrentVersion, PRSeal ],
GermPrivate USING [DebugMPCode, DebugMPCardinal, GetTeledebugged, TeleDebug ];
Note: The code in Initialize assumes the machine starts with vm "Compact"ed
BootSwapGerm: PROGRAM
IMPORTS Basics, BC: BootChannel, GermPrivate, GermSwap, MesaRuntimeInit, PrincOpsUtils, ProcessorFace, ProcessorFaceExtras, RuntimeError, SparcSoftcardInit
EXPORTS BootChannel, GermPrivate
SHARES GermSwap ~ {
OPEN BootChannel, GermPrivate;
Do NOT save any status between DoOutLoad and DoInLoad relating to the request.
The next time everything in the outside world may have changed.
The booting action defined by the Principles of Operation should include:
1. Put all usable real memory somewhere in virtual memory.
2. Read countGerm pages of a "boot swap germ" into virtual memory beginning at page pageGerm+countSkip (steal real memory from high end to do this).
3. Set GermSwap.pRequest^ to [inLoad, locationOfBootFile].
4. Set WDC>0, NWW=0, MDS=pageGerm, STKP=0.
5. Xfer[dest: GermSwap.pInitialLink].
At present, buffer space allocated for the BootChannel's is reclaimed when the request is complete. Currently only the BootChannelEther allocates buffer space. When non-initial-booting operations via the ether are implemented, we will have to hang onto this buffer space forever.
Noop: PROC ~ MACHINE CODE { 364B, 124B }; -- zMISC, aNOOP
PageCount: TYPE ~ PrincOps.PageCount;
PageNumber: TYPE ~ PrincOps.PageNumber;
EntryPointer: TYPE ~ LONG POINTER TO BootFile.Entry;
valuesPerPage: CARD16 ~ PrincOps.wordsPerPage / SIZE[PrincOps.PageValue];
PageValueArray: TYPE ~ ARRAY [0..valuesPerPage) OF PrincOps.PageValue;
maxGroupSize: CARD16 ~ MAX[BootFile.maxEntriesPerHeader, BootFile.maxEntriesPerTrailer];
stateVacant: PrincOps.PageState ~ LOOPHOLE[PrincOps.flagsVacant];
stateClean: PrincOps.PageState ~ LOOPHOLE[PrincOps.flagsClean];
valueVacant: PrincOps.PageValue ~ [state: stateVacant, real: 0];
VacantFlags: PROC [ value: PrincOps.PageValue ] RETURNS [ vacant: BOOL ] ~ INLINE
{ vacant ← ( value.state.flags = PrincOps.flagsVacant ); };
VacatePageFlags: PROC [ p: PageNumber ] RETURNS [ pv: PrincOps.PageValue ] ~ INLINE
{ pv ← PrincOpsUtils.ExchangePageFlags[ p, PrincOps.flagsVacant] };
IsVacantPage: PROC [ p: PageNumber ] RETURNS [ vacant: BOOL ] ~ INLINE
{ vacant ← VacantFlags[PrincOpsUtils.GetPageValue[p]] };
IsReadOnlyPage: PROC [ p: PageNumber ] RETURNS [ vacant: BOOL ] ~ INLINE
{ vacant ← PrincOpsUtils.GetPageValue[p].state.flags.readonly };
VacatePageState: PROC [ p: PageNumber ] RETURNS [ pv: PrincOps.PageValue ] ~ INLINE
{ pv ← PrincOpsUtils.ExchangePageState[ p, stateVacant] };
SetClean: PROC [ p: PageNumber, r: PrincOps.RealPageNumber ] ~ INLINE
{ PrincOpsUtils.SetPageValue[p, PrincOps.PageValue[stateClean, r]] };
cedarVersion: CARD16 ~ 156;
version number of outload files written with cedarOutLoad
Germ VM Geography
IOPage: PageNumber ~ 0FFH; -- special page in lowcore
InIORegion: PROC [ p: PageNumber ] RETURNS [ reserved: BOOLFALSE ] ~ INLINE {
reserved ← ( p IN [ProcessorFaceExtras.dFirst64KPage .. IOPage] );
};
pCountGerm: PUBLIC LONG POINTER TO READONLY CARD16
LOOPHOLE[GermSwap.LP[GermSwap.pCountGerm, GermSwap.mdsiGerm]];
Number of (image) pages for the germ; Temp(?) type fix for GermSwap definition bug
pageGerm: PUBLIC PageNumber ← (GermSwap.mdsiGerm * 256) + GermSwap.countSkip;
virtual (page) address of the first (image) page of the germ
should be engraved in {interface} stone ([mdsiGerm, countSkip] = 3e02H / 37002B)
imageMarker: PUBLIC PageNumber ~ pageGerm + pCountGerm^.PRED;
handy marker to identify low water mark for allocated pages
The Virtual Memory Map contains pages [0 .. countVM), except (reserved) pages p for which
InGermOrIORegion[p] = TRUE or p # ProcessorFace.GetNextAvailableVM[p].firstPage
pageAfterGerm: PUBLIC PageNumber ← imageMarker.SUCC;
current end of (mapped) germ vm (includes allocated buffer space, doesn't include pageTemp). Think of the region [imageMarker..pageAfterGerm) as the TwilightZone.
ReserveGermVM: PROC [ count: PageCount ] RETURNS [ p: PageNumber ] ~ INLINE
{ p ← pageAfterGerm; pageAfterGerm ← pageAfterGerm + count };
InGermVM: PROC [ p: PageNumber ] RETURNS [ reserved: BOOL ] ~ INLINE
{ reserved ← ( p IN [ pageGerm..pageAfterGerm ) ) };
PagesInGerm: PROC [ ] RETURNS [ count: NAT ← 0 ] ~ INLINE {
count ← (IOPage.SUCC - ProcessorFaceExtras.dFirst64KPage) -- i.e. 376B and 377B
+ (pageAfterGerm - pageGerm); -- current germ VM size
};
InGermOrIORegion: PROC [ p: PageNumber ]
RETURNS [ reserved: BOOLFALSE ] ~ INLINE {
SELECT TRUE FROM
( p IN [0 .. ProcessorFaceExtras.dFirst64KPage) ) => RETURN;
( p IN [IOPage.SUCC .. pageGerm) ) => RETURN;
( p >= pageAfterGerm ) => RETURN;
ENDCASE => NULL;
reserved ← TRUE;
};
AdvanceToNonVacant: PROC [ start: PageNumber ] RETURNS [ p: PageNumber ] ~ {
Next page at or after given page which is not vacant or part of the germ (and valid vm)
For the endcase, we return countVM (rather than some bogus page number or "start")
count: PageCount;
[firstPage: start, count: count] ← ProcessorFace.GetNextAvailableVM[start];
IF ( count = 0 ) THEN { p ← countVM; RETURN };
WHILE ( IsVacantPage[start] OR InGermOrIORegion[start] ) DO
[firstPage: p, count: count] ← ProcessorFace.GetNextAvailableVM[start.SUCC];
IF ( count = 0 ) THEN { p ← countVM; RETURN };
start ← p;
ENDLOOP;
p ← start;
};
AdvanceToAvailable: PROC [ start: PageNumber ] RETURNS [ p: PageNumber ] ~ {
Next page at or after given page which is not vacant or part of the germ (and valid vm)
For the endcase, we return "start" (rather than some bogus page number or countVM)
count: PageCount;
[firstPage: start, count: count] ← ProcessorFace.GetNextAvailableVM[start];
IF ( count = 0 ) THEN { p ← start; RETURN };
WHILE ( InGermOrIORegion[start] ) DO
[firstPage: p, count: count] ← ProcessorFace.GetNextAvailableVM[start.SUCC];
IF ( count = 0 ) THEN { p ← start; RETURN };
start ← p;
ENDLOOP;
p ← start;
};
countVM: PUBLIC PageCount;
highest entry in the map; Export to GermPrivate
FindMapBoundary: PROC RETURNS [ high: PageNumber ] ~ INLINE {
p: PageNumber; count: PageCount;
FOR high ← 0, (p + count) DO -- this is off by one; e.g. [0..high)
[firstPage: p, count: count] ← ProcessorFace.GetNextAvailableVM[high];
IF ( count = 0 ) THEN EXIT;
ENDLOOP;
};
lastBackedPage: PageNumber;
end of (Compacted) resident vm run; state = non-vacant & writable for pages
[(lastBackedPage-backedPageRun)..lastBackedPage]
backedPageRun: PageCount;
number of pages left in the contiguous run
ReclaimPage: PROC [ p: PageNumber ] ~ INLINE {
value: PrincOps.PageValue ~ VacatePageFlags[p]; -- zero page number also?
IF ( VacantFlags[value] ) THEN RETURN;
lastBackedPage ← lastBackedPage.SUCC;
backedPageRun ← backedPageRun.SUCC;
PrincOpsUtils.SetPageValue[lastBackedPage, value];
};
ReclaimPhysicalPage: SAFE PROC [ real: PrincOps.RealPageNumber ] ~ CHECKED {
lastBackedPage ← lastBackedPage.SUCC;
backedPageRun ← backedPageRun.SUCC;
TRUSTED { SetClean[lastBackedPage, real] };
};
FillVMRegion: PROC [ p: PageNumber, pages: PageCount ] ~ INLINE {
grab pages (can only do during inLoad/boot when the world is "Compact")
pages are guaranteed to be non-vacant/writable
FOR i: PageCount IN [0..pages) DO
we don't want to lose pages (for the dorado case where we so much real memory)
IF ( IsVacantPage[(p + i)] ) THEN {
There's a hairy case here where we manage to back into pageAfterGerm because we're using a trivial technique with both ends are chasing towards the middle! - (bj)
pv: PrincOps.PageValue ~ VacatePageFlags[lastBackedPage];
PrincOpsUtils.SetPageValue[(p + i), pv];
lastBackedPage ← lastBackedPage.PRED;
backedPageRun ← backedPageRun.PRED;
};
ENDLOOP;
};
CompactAvailableVM: PROC ~ {
Finds all available real pages in vm (not in germ private areas), and moves them into contiguous runs of available vm. The expectation here is that there's enough pages in bulk vm (there's a hole for the Dorado at page 1) so that you really have all the pages in one run (two for the Dorado). The "run" can be used to allocate storage backware starting from "last" and going backward.
last: PageNumber ← 0; run: PageCount ← 0;
DebugMPCode[222];
FOR high: PageNumber ← AdvanceToNonVacant[0], AdvanceToNonVacant[high.SUCC]
WHILE ( high < countVM) DO
pv: PrincOps.PageValue ← PrincOpsUtils.GetPageValue[high];
SELECT TRUE FROM
( high = last ) => {
IF ( pv.state.flags.readonly ) THEN {
this is just an efficiency hack to not rewrite the same values
pv.state.flags.readonly ← FALSE;
make writable (don't care about dirty/ref'ed)
PrincOpsUtils.SetPageValue[last, pv];
put in the map at end of run
};
};
ENDCASE => {
pv.state.flags.readonly ← FALSE;
make writable (don't care about dirty/ref'ed)
PrincOpsUtils.SetPageValue[high, valueVacant];
vacate the page we're stealing the backing page from
PrincOpsUtils.SetPageValue[last, pv];
put in the map at end of run
};
run ← run.SUCC; -- we have just extended the run by a page
check to see if next page is in the same run, or start a new run
{
next: PageNumber ~ AdvanceToAvailable[last.SUCC]; -- returns arg at end of vm
IF ( next # last.SUCC ) THEN { run ← 0 };
last ← next; -- this could overflow and be equal to countVM
};
ENDLOOP;
DebugMPCode[333];
IF ( debug ) THEN {
FOR p: PageNumber ← last, AdvanceToAvailable[p.SUCC] WHILE ( p < countVM ) DO
IF ( NOT IsVacantPage[p] ) THEN GermWorldError[897];
ENDLOOP;
};
DebugMPCode[444];
lastBackedPage ← last.PRED;
backedPageRun ← run;
};
AllocateMDS: PUBLIC PROC [ count: CARD16 ] RETURNS [ rel: POINTER ] ~ {
Allocates (memory backed - page) storage in the germ's MDS.
AllocateMDS may only be called during at genesis (module initialization) !
VM/real memory configuration must be static during inLoad and outLoad; because of this requirement, spare real memory is not be available since we don't know how to find a free page nor are we neccessarily able to prove that it will remain free (e.g. reload).
PointerFromPage: PROC [ p: PageNumber ] RETURNS [ short: POINTER ] ~ INLINE {
IF ( Basics.HighByte[p] # GermSwap.ReadMDS[] ) THEN GermWorldError[899];
short ← LOOPHOLE[Basics.ShortNumber[bytes[hi: Basics.LowByte[p], lo: 0]]];
};
region: PageNumber ~ ReserveGermVM[count];
FillVMRegion[region, count];
rel ← PointerFromPage[region];
};
Small Object Allocation
dFirst64KStorage: LONG DESCRIPTOR FOR ARRAY OF WORD;
A piece of the first 64K that we know nobody uses, for allocating IOCB's.
When/If the germ moves to the first 64K, this should be put in its global frame.
ResetChunkAllocator: PROC ~ INLINE {
dFirst64KStorage ← ProcessorFaceExtras.dFirst64KDescriptor;
};
chunkGrain: CARD16 ~ 16;
AllocateChunk: PUBLIC PROC [ words: CARD16, align: PrincOps.Alignment ← a16 ]
RETURNS [ chunk: LONG POINTERNIL ] ~ {
Chunks are allocated from a special region of low vm; look at the implemenation for details
(mds=0; within ProcessorFaceExtras.dFirst64KPage)
buckets: CARD16 ~ (words + chunkGrain.PRED) / chunkGrain;
used: CARD16 ~ buckets * chunkGrain;
avail: CARD16 ~ dFirst64KStorage.LENGTH;
IF ( buckets = 0 ) THEN RETURN;
DebugMPCode[buckets];
IF ( used > avail ) THEN GermWorldError[1888];
chunk ← dFirst64KStorage.BASE;
dFirst64KStorage ← DESCRIPTOR[chunk + used, avail - used]
};
ReleaseChunk: PUBLIC PROC [ op: LONG POINTER ] ~ {
not guaranteed to be smart enough to return storage to the allocater
IF ( op = NIL ) THEN RETURN;
};
AllocateBaseChunk: PUBLIC PROC [ words: CARD16, align: PrincOps.Alignment ← a16 ]
RETURNS [ chunk: PrincOps.Base RELATIVE POINTER ] ~ {
p: LONG POINTER ~ AllocateChunk[words, align];
IF ( PrincOpsUtils.HighHalf[p] # 0 ) THEN GermWorldError[888];
chunk ← LOOPHOLE[PrincOpsUtils.LowHalf[p]];
};
ReleaseBaseChunk: PUBLIC PROC [ chunk: PrincOps.Base RELATIVE POINTER ] ~ {
ReleaseChunk[@PrincOps.first64K[chunk]];
};
Softcard Window
swPages: CARD16 ~ 64;
hook: CARD16 ~ xx;
MagicStructure: TYPE ~ RECORD [ -- three words (48 bits)!
region: PageNumber,
swPages: CARD16
];
on the bootfile side:
pMagicStructure: PUBLIC LONG POINTER TO READONLY MagicStructure ←
LOOPHOLE[GermSwap.LP[LOOPHOLE[PrincOps.SD + hook], GermSwap.mdsiGerm]];
GrabPagesForSoftWindow: PROC ~ INLINE {
region: PageNumber ~ ReserveGermVM[swPages];
p: POINTER TO MagicStructure ~ LOOPHOLE[@SD[hook]];
p^ ← [region, swPages];
};
Trival Page Buffer Maintenance
bufsMax: NAT ~ 3;
bufs: ARRAY [0..bufsMax) OF RECORD [ inUse: BOOL, page: PageNumber ];
GrabBuffersForGerm: PROC ~ INLINE {
region: PageNumber ~ ReserveGermVM[bufsMax];
FillVMRegion[region, bufsMax];
FOR i: NAT IN [0..bufsMax) DO
bufs[i] ← [inUse: FALSE, page: (region + i)];
ENDLOOP;
};
BorrowPageBuffer: PROC RETURNS [ p: PageNumber ← 0 ] ~ INLINE {
FOR i: NAT IN [0..bufsMax) DO
IF ( NOT bufs[i].inUse ) THEN {
bufs[i].inUse ← TRUE;
p ← bufs[i].page;
RETURN;
};
ENDLOOP;
GermWorldError[899];
};
ReturnPageBuffer: PROC [ p: PageNumber ] ~ INLINE {
FOR i: NAT IN [0..bufsMax) DO
IF ( p = bufs[i].page ) THEN {
bufs[i].inUse ← FALSE;
RETURN;
};
ENDLOOP;
GermWorldError[899];
};
Germ Error Handling
sanityChecking: PUBLIC BOOL ¬ FALSE; -- set to TRUE by boot switch?
cedar7Debug: BOOLFALSE;
debug: PUBLIC BOOL ¬ FALSE; -- set to TRUE by boot switch?
Spin: PROC [ msDelay: CARD32 ← 500 ] ~ INLINE {
start: CARD32 ~ ProcessorFace.GetClockPulses[];
msPulses: CARD16 ~ Basics.LongDiv[LONG[1000]*100, ProcessorFace.microsecondsPerHundredPulses];
pulseDelay: CARD32 ~ Basics.LongMult[msDelay, msPulses];
WHILE ( (ProcessorFace.GetClockPulses[] - start) < pulseDelay ) DO ENDLOOP;
};
ShowCodeInMP: PUBLIC PROC [ mpCode: MPCodes.Code ] ~
{ ProcessorFace.SpecialSetMP[mpCode]; Spin[1000] };
ShowCardinalInMP: PUBLIC PROC [ cardinal: CARD16 ] ~ {
mpModulus: NAT ~ 1000; -- mp only guaranteed to be three digits.
ShowCodeInMP[cardinal / mpModulus];
ShowCodeInMP[cardinal MOD mpModulus];
};
ShowMP: PUBLIC PROC [ code: MPCodes.Code ] ~ {
display code in panel (for a second if "debug")
ProcessorFace.SpecialSetMP[code];
IF ( ( cedar7Debug ) AND ( code # MPCodes.germInLoad ) )
THEN ShowCodeInMP[code];
DebugMPCode[code];
};
Error: PUBLIC PROC [ code: MPCodes.Code ] ~ { GermWorldError[code] };
We can't set up handlers for Frame, Page, and Write Faults because they require that the ProcessDataArea be already initialized, and it's not until Pilot comes to life. If the germ is not working, try putting a Midas break at the fault handling code.
Mumble: PUBLIC SIGNAL [ code: MPCodes.Code ] ~ CODE;
This used to be "Error". I think there is some Stack mismatching or similar screwup.
All I got in the MP was 821 (germERROR). /HGM, May 86.
UnnamedError: PROC ~ {
SignalHandler[Mumble, 521];
};
SignalHandler: PROC [ signal: SIGNAL [ code: MPCodes.Code ], code: MPCodes.Code ] ~ {
code ← SELECT TRUE FROM
( signal = Mumble )   => code,
( signal = LOOPHOLE[RuntimeError.BoundsFault] )  => 101,
( signal = LOOPHOLE[RuntimeError.ControlFault] ) => MPCodes.germControlFault,
( signal = LOOPHOLE[RuntimeError.DivideCheck] )  => 102,
( signal = LOOPHOLE[RuntimeError.LinkageFault] ) => 103,
( signal = LOOPHOLE[RuntimeError.PointerFault] )  => 104,
( signal = LOOPHOLE[RuntimeError.PortFault] )  => 105,
( signal = LOOPHOLE[RuntimeError.SendMsgSignal] ) => 106,
( signal = LOOPHOLE[RuntimeError.StartFault] ) => MPCodes.germStartFault,
( signal = LOOPHOLE[RuntimeError.StackError] )  => 107,
( signal = LOOPHOLE[RuntimeError.UnboundProcedure] ) => 108,
( signal = LOOPHOLE[RuntimeError.UNCAUGHT] )  => 109,
( signal = LOOPHOLE[RuntimeError.ZeroDivisor] )  => 110,
ENDCASE    => MPCodes.germERROR;
GermWorldError[code];
};
GermWorldError: PUBLIC PROC [ mpCode: MPCodes.Code ] ~ {
Displays mpCode in the maintenance panel.
If in germ debug mode (boot switch), also displays additional state information in the maintenance panel, as follows:
Normal mode: Displays the mpCode passed.
Debug mode: Displays, in sequence
1. The mpCode passed.
2. The code 999
3. For each frame up the stack (limit 5), displays:
3a. globalFrameIndex/1000, globalFrameIndex MOD 1000.
3b. pc/1000, pc MOD 1000.
The mp cycles repeatedly through these values.
NOTE: ALL VALUES DISPLAYED ARE DECIMAL.
DO
localFrame: PrincOps.FrameHandle ¬ PrincOpsUtils.GetReturnFrame[];
ShowCodeInMP[mpCode];
IF ( TRUE ) THEN {-- or debug???
ShowCodeInMP[999];
THROUGH [1..5] DO
gfi: PrincOps.GlobalFrameHandle ~ localFrame.accesslink; -- ReadGlobalLink[localFrame]
pc: PrincOps.BytePC ~ localFrame.pc; -- ReadPC[localFrame];
ShowCardinalInMP[LOOPHOLE[gfi]];
ShowCardinalInMP[localFrame]; ++ not very useful.
ShowCardinalInMP[pc];
localFrame ¬ localFrame.returnlink.frame; -- ReadReturnLink[localFrame].frame
IF ( localFrame = PrincOps.NullFrame ) THEN EXIT;
ENDLOOP;
};
ENDLOOP;
};
Worker routines
Backstop Create routine to plug end of chain of BootChannel interfaces.
(Possibly invoked by dummy call to initialize BootChannel implementations)
Create: PUBLIC PROC [ pLocation: LONG POINTER TO BootFile.Location,
operation: BootChannel.Operation, buffer: LONG POINTER ]
RETURNS [ result: BootChannel.Result, handle: BootChannel.Handle ] ~ {
RETURN[[error[MPCodes.germDeviceError]], NIL];
};
Run: PROC ~ {
JumpCall2: PROC [ arg1, arg2: UNSPECIFIED, Proc: PROC [UNSPECIFIED, UNSPECIFIED] ] ~
MACHINE CODE { PrincOps.zSLB, PrincOps.returnOffset; PrincOps.zRET };
after the very first execution, the germ entry point is here
result: BootChannel.Result; handle: BootChannel.Handle;
GermSwap.InitializeMDS[]; -- set pMon
ShowCodeInMP[MPCodes.germStarting];
{
Initialize the BootChannels.. allow them to allocate permanent storage:
location: BootFile.Location;
location.deviceType ¬ Device.nullType;
[] ¬ BC.Create[@location, read, NIL];
(null device doesn't really create a Channel; just inits)
};
DO
{
ResetChunkAllocator[]; -- need to double check this one!
inLoad exits through JumpCall2,
outLoad does explicit EXIT,
bootPhysicalVolume turns itself into an inLoad
SELECT GermSwap.pRequest.action FROM
inLoad => {
continuation: BootFile.Continuation;
responseKind: GermSwap.ResponseKind;
mdsiOther: BootFile.MDSIndex;
destOther: UNSPECIFIED;
createBuffer: PageNumber ~ BorrowPageBuffer[];
DebugMPCode[200];
[result, handle] ← BC.Create[@GermSwap.pRequest.location, read, PrincOpsUtils.AddressForPageNumber[createBuffer]];
ReturnPageBuffer[createBuffer];
IF ( result # [ok[]] ) THEN GOTO HandleProblem;
[continuation, GermSwap.pMon.pStartListHeader] ← DoInLoad[handle];
WITH continuation SELECT FROM
initial => { -- [mdsi, destination]
responseKind ← initiated;
mdsiOther ← mdsi;
destOther ← destination
};
resumptive=> { -- [mdsi, resumee]
responseKind ← resumed;
mdsiOther ← mdsi;
destOther ← resumee
};
ENDCASE => {
GermWorldError[MPCodes.germBadBootFile];
};
GermSwap.Initialize[mdsiOther]; -- set (pMon and) WriteMDS machinery
GermSwap.pMon.responseKind ← responseKind;
DebugMPCode[201];
DebugMPCode[mdsiOther];
DebugMPCardinal[destOther];
DebugMPCode[999];
ShowCodeInMP[MPCodes.germFinished];
JumpCall2[arg1: mdsiOther, arg2: destOther,
Proc: GermSwap.pMon.CrossMDSCall];
frees our frame
};
outLoad, pilotOutLoad => {
createBuffer: PageNumber ~ BorrowPageBuffer[];
DebugMPCode[599];
[result, handle] ← BC.Create[@GermSwap.pRequest.location, write, PrincOpsUtils.AddressForPageNumber[createBuffer]];
ReturnPageBuffer[createBuffer];
IF ( result # [ok[]] ) THEN GOTO HandleProblem;
DoOutLoad[handle, GermSwap.pMon.inLoadMode, GermSwap.pMon.continuation,
( GermSwap.pRequest.action = outLoad )];
GermSwap.pMon.responseKind ← outLoaded;
EXIT;
};
bootPhysicalVolume => {
ValidPV: PROC [ pvDesc: LONG POINTER TO VolumeFormat.PhysicalRoot ]
RETURNS [ valid: BOOLTRUE ] ~ INLINE {
SELECT TRUE FROM
( pvDesc.seal # VolumeFormat.PRSeal ) =>
{ valid ← FALSE };
( pvDesc.version # VolumeFormat.PRCurrentVersion ) =>
{ valid ← FALSE };
ENDCASE =>
{ NULL };
};
rootPageBuffer: PageNumber ~ BorrowPageBuffer[];
pvDesc: LONG POINTER TO VolumeFormat.PhysicalRoot ~ PrincOpsUtils.AddressForPageNumber[rootPageBuffer];
GermSwap.pRequest.location.diskFileID.firstLink ← LOOPHOLE[LONG[0]]; --kludge!
[result, handle] ← BC.Create[@GermSwap.pRequest.location, rawRead, PrincOpsUtils.AddressForPageNumber[rootPageBuffer]];
IF ( result = [ok[]] ) THEN result ← BC.Transfer[handle, rootPageBuffer, 1];
IF ( result = [ok[]] ) THEN result ← BC.SyncTransfer[handle];
IF ( result = [ok[]] ) THEN result ← BC.CloseChannel[handle];
IF ( result # [ok[]] ) THEN {
ReturnPageBuffer[rootPageBuffer];
GOTO HandleProblem;
};
IF ( NOT ValidPV[pvDesc] )
THEN GermWorldError[MPCodes.germBadPhysicalVolume];
GermSwap.pRequest.location.diskFileID ← pvDesc.bootingInfo[bootFile];
GermSwap.pRequest.action ← inLoad;
now get the boot file and go
ReturnPageBuffer[rootPageBuffer];
};
teledebug => {
kind: { none, pup, xns, idp } ← none;
SELECT TRUE FROM
( PrincOpsUtils.IsBound[LOOPHOLE[GermPrivate.TeleDebug]] ) =>
{ kind ← pup };
( PrincOpsUtils.IsBound[LOOPHOLE[GermPrivate.GetTeledebugged]] ) =>
{ kind ← xns };
ENDCASE =>
{ GermWorldError[MPCodes.cantWorldSwap] };
{
diskBuffer: PageNumber ~ BorrowPageBuffer[];
pLocation: LONG POINTER TO BootFile.Location ~ @GermSwap.pRequest.location;
SELECT kind FROM
pup => GermPrivate.TeleDebug[diskBuffer, dFirst64KStorage];
xns => GermPrivate.GetTeledebugged[pLocation, diskBuffer];
ENDCASE => { NULL };
ReturnPageBuffer[diskBuffer];
};
EXIT; -- otherwise we'd infinitely loop!
};
ENDCASE => GermWorldError[899];
EXITS
HandleProblem => {
WITH r: result SELECT FROM
error => GermWorldError[r.code];
tryOtherLocation => GermSwap.pRequest.location ¬ r.pOtherLocation­;
We overwrite the Location we were working on with the new suggested Location, fall through, and try again.
ENDCASE => GermWorldError[899];
};
};
ShowCodeInMP[MPCodes.germStarting]; -- germAction
ENDLOOP;
ShowCodeInMP[MPCodes.germFinished];
};
DoInLoad: PROC [ handle: BootChannel.Handle ]
RETURNS [ continuation: BootFile.Continuation, pStartListHeader: POINTER ] ~ {
result: BootChannel.Result;
cedar: BOOLFALSE;
whether outload file was written with cedarOutLoad (e.g. by us)
inLoadMode: BootFile.InLoadMode;
basically whether we're doing a boot(load) or a rollback(restore)
nextPage: PageNumber ← 0;
succ[last page for which the map entry has been fixed] (scoped here for pilot?)
headerBuffer: PageNumber ← BorrowPageBuffer[];
trailerBuffer: PageNumber ← BorrowPageBuffer[];
header: LONG POINTER TO BootFile.Header ~ LOOPHOLE[PrincOpsUtils.AddressForPageNumber[headerBuffer]];
trailer: LONG POINTER TO BootFile.Trailer ~ LOOPHOLE[PrincOpsUtils.AddressForPageNumber[trailerBuffer]];
ShowCodeInMP[MPCodes.germInLoad];
{
groupsProcessed: CARD16 ← 0;
a handy-dandy progress number to show to the user as an mpcode
pEntryGroup: EntryPointer ← @header.entries[0];
will always point to either header.entries[0] or trailer.entries[0]
nEntry: CARD16 ← BootFile.maxEntriesPerHeader;
maximum number of entries left in this entry group (we assume bootfile has a full header)
pEntry: EntryPointer;
the next entry to be processed in this group
count, countData, countGroup, countRemaining: PageCount;
Read first page (header) and process its specific information, setup pEntryGroup
result ← BC.Transfer[handle, headerBuffer, 1];
IF ( result = [ok[]] ) THEN result ← BC.SyncTransfer[handle];
IF ( result # [ok[]] ) THEN { GOTO EntryGroupProblem };
IF ( header.version # BootFile.currentVersion ) THEN {
IF ( header.version # cedarVersion )
THEN GermWorldError[MPCodes.germBadBootFile];
cedar ← TRUE; -- checkpoint image written by us!
};
SparcSoftcardInit.ResetSoftcard[]; -- for the cold rollback case!
SELECT (inLoadMode ← header.inLoadMode) FROM
load => { CompactAvailableVM[] };
restore => { NULL }; -- don't Compact; we're putting things back where they came from
ENDCASE;
pStartListHeader ← header.pStartListHeader;
continuation ← header.continuation;
countRemaining ← countData ← header.countData;
Restore real memory and map from boot file, beginning with header entry group
DO
countGroup ← MIN[nEntry, countRemaining];
Calculate group size
Set up map entries as appropriate to be vacant or nonvacant (and writable)
pages not in boot file are set to vacant (don't touch germ pages)
pEntry ← pEntryGroup;
THROUGH [0..countGroup) DO
FOR p: PageNumber ← nextPage, p.SUCC WHILE ( p < pEntry.page ) DO
IF ( InGermOrIORegion[p] ) THEN { LOOP };
IF ( inLoadMode = load ) THEN ReclaimPage[p];
ENDLOOP;
IF ( inLoadMode = restore ) THEN SetClean[pEntry.page, pEntry.value.real];
for load/boot we don't mess with this page; it should be fine already!
nextPage ← pEntry.page.SUCC;
pEntry ← pEntry + SIZE[BootFile.Entry]; -- .SUCC;
ENDLOOP;
Transfer data for all (non-vacant) pages in entry group
pEntry ← pEntryGroup;
count ← countGroup;
WHILE ( count # 0 ) DO
find and start transfer of each run detected in this entry group
IF ( InGermOrIORegion[pEntry.page] ) -- InIORegion[pEntry.page] ???
THEN {
skip loading this particular page from the image.
this should never happen because of the way bootfiles/checkpoints are made
tempBuffer: PageNumber ~ BorrowPageBuffer[];
result ← BC.Transfer[handle, tempBuffer, 1];
IF ( result = [ok[]] ) THEN result ← BC.SyncTransfer[handle];
ReturnPageBuffer[tempBuffer];
pEntry ← pEntry + SIZE[BootFile.Entry]; -- .SUCC;
count ← count.PRED;
IF ( result # [ok[]] ) THEN { GOTO EntryGroupProblem };
IF ( sanityChecking ) THEN GermWorldError[899];
}
ELSE {
pageRun, pageNext: PageNumber ← pEntry.page;
DO
run thru the Entry Group identifying consecutive vm pages (runs)
count ← count.PRED;
pageNext ← pageNext.SUCC;
pEntry ← pEntry + SIZE[BootFile.Entry]; -- .SUCC;
SELECT TRUE FROM
( count = 0 ) => EXIT;
( pEntry.page # pageNext ) => EXIT; -- normally catches IORegion case!
( InIORegion[pageNext] ) => {
IF ( sanityChecking ) THEN GermWorldError[899];
this page should not be in bootfile!
EXIT;
};
ENDCASE;
ENDLOOP;
result ← BC.Transfer[handle, pageRun, (pageNext - pageRun)];
initiate transfer of this run!
IF ( result # [ok[]] ) THEN { GOTO EntryGroupProblem };
};
ENDLOOP;
result ← BC.SyncTransfer[handle];
wait until all the runs make it in before mucking with the flags!
IF ( result # [ok[]] ) THEN { GOTO EntryGroupProblem };
Restore map flags/PageState; we "dirty"ed them in transfer!
pEntry ← pEntryGroup;
THROUGH [0..countGroup) DO
[] ← PrincOpsUtils.ExchangePageState[pEntry.page, pEntry.value.state];
pEntry ← pEntry + SIZE[BootFile.Entry]; -- .SUCC;
ENDLOOP;
ProcessorFace.SpecialSetMP[groupsProcessed ← groupsProcessed.SUCC];
indicate progress to the user (makes him feel happier)
countRemaining ← countRemaining - countGroup;
IF ( countRemaining = 0 ) THEN {
Theres no trailer after the last group!
IF ( result = [ok[]] ) THEN result ← BC.SyncTransfer[handle];
IF ( result # [ok[]] ) THEN { GOTO EntryGroupProblem };
EXIT;
};
Get next Entry Group (from trailer page)
result ← BC.Transfer[handle, trailerBuffer, 1];
IF ( result = [ok[]] ) THEN result ← BC.SyncTransfer[handle];
IF ( result # [ok[]] ) THEN { GOTO EntryGroupProblem };
IF ( trailer.version # BootFile.currentVersion )
THEN GermWorldError[MPCodes.germBadBootFile];
pEntryGroup ← @trailer.entries[0];
nEntry ← BootFile.maxEntriesPerTrailer;
ENDLOOP;
All groups are now loaded
ReturnPageBuffer[headerBuffer];
ReturnPageBuffer[trailerBuffer];
EXITS
EntryGroupProblem => {
ReturnPageBuffer[headerBuffer];
ReturnPageBuffer[trailerBuffer];
WITH r: result SELECT FROM
error => GermWorldError[r.code];
ENDCASE => GermWorldError[897];
};
};
SELECT inLoadMode FROM
load => { NULL };
restore => {
IF ( cedar )
THEN {
mapBuffer: PageNumber ← BorrowPageBuffer[];
mapArray: LONG POINTER TO PageValueArray ~ PrincOpsUtils.AddressForPageNumber[mapBuffer];
mapPage: PageNumber ← 0;
countRemaining: PageCount ← countVM;
ShowCodeInMP[MPCodes.germMapIO];
{
result ← BC.Transfer[handle, mapBuffer, 1];
IF ( result = [ok[]] ) THEN result ← BC.SyncTransfer[handle];
IF ( result # [ok[]] ) THEN { GOTO MapGroupProblem };
WHILE ( mapPage < countVM ) DO
nEntry: CARD16 ~ MIN[countRemaining, valuesPerPage];
maximum number of entries left in this buffer
FOR i: CARD16 IN [0 .. nEntry) WHILE ( mapPage < countVM ) DO
current: PrincOps.PageValue ~ PrincOpsUtils.GetPageValue[mapPage];
IF ( InGermVM[mapPage] ) THEN { mapPage ← mapPage.SUCC; LOOP };
IF ( InGermOrIORegion[mapPage] )
THEN { mapPage ← mapPage.SUCC; LOOP};
SELECT TRUE FROM
VacantFlags[current] => { NULL };
( mapArray[i].state.flags.dirty ) => { NULL };
( current.state.flags.dirty ) => { mapArray[i].state.flags.dirty ← TRUE };
ENDCASE => { NULL };
IF ( mapArray[i] # current ) THEN {
this is just a little performance hack for the Dorado cache!
PrincOpsUtils.SetPageValue[mapPage, mapArray[i]];
};
mapPage ← mapPage.SUCC;
ENDLOOP;
countRemaining ← countRemaining - nEntry;
result ← BC.Transfer[handle, mapBuffer, 1];
IF ( result = [ok[]] ) THEN result ← BC.SyncTransfer[handle];
IF ( result # [ok[]] ) THEN { GOTO MapGroupProblem };
ENDLOOP;
ReturnPageBuffer[mapBuffer];
EXITS
MapGroupProblem => {
ReturnPageBuffer[mapBuffer];
WITH r: result SELECT FROM
error => GermWorldError[r.code];
ENDCASE => GermWorldError[895];
};
};
}
ELSE {
Pilot (is this still viable?) my guess is we need this for world swap debugging, and setting the rest of the pages to vacant might be the proper thing to do - (bj)
nextPage ← AdvanceToAvailable[nextPage]; -- guarantees page is reasonable
WHILE ( nextPage < countVM ) DO
PrincOpsUtils.SetPageValue[nextPage, valueVacant];
nextPage ← AdvanceToAvailable[nextPage.SUCC];
ENDLOOP;
};
};
ENDCASE;
result ← BC.CloseChannel[handle];
WITH r: result SELECT FROM
ok => NULL;
error => GermWorldError[r.code];
ENDCASE => GermWorldError[893];
};
DoOutLoad: PROC [ handle: BootChannel.Handle, inLoadMode: BootFile.InLoadMode,
continuation: BootFile.Continuation, cedar: BOOL ] ~ {
Assumptions: interrupts disabled, all devices quiesced
result: BootChannel.Result;
groupsProcessed: CARD16 ← 0;
a handy-dandy progress number to show to the user as an mpcode
page: PageNumber;
next virtual page to (possibly) outload
headerBuffer: PageNumber ← BorrowPageBuffer[];
trailerBuffer: PageNumber ← BorrowPageBuffer[];
entryBuffer: PageNumber; -- either headerBuffer or trailerBuffer
header: LONG POINTER TO BootFile.Header ~ LOOPHOLE[PrincOpsUtils.AddressForPageNumber[headerBuffer]];
trailer: LONG POINTER TO BootFile.Trailer ~ LOOPHOLE[PrincOpsUtils.AddressForPageNumber[trailerBuffer]];
pEntryGroup: EntryPointer ← @header.entries[0];
will always point to either header.entries[0] or trailer.entries[0]
nEntry: CARD16 ← BootFile.maxEntriesPerHeader;
maximum number of entries left in this entry group (we assume bootfile has a full header)
pEntry: EntryPointer;
the next entry to be processed in this group
count, countData, countGroup, countRemaining: PageCount;
pageNext, pageRun: PageNumber;
ShowCodeInMP[MPCodes.germOutLoad];
Number of pages to save is total nonvacant minus those in germ
countData ← 0;
FOR p: PageNumber ← AdvanceToAvailable[0], AdvanceToAvailable[p.SUCC]
WHILE ( p < countVM ) DO
IF ( NOT IsVacantPage[p] ) THEN countData ← countData.SUCC;
ENDLOOP;
Construct header in first map page
header.pStartListHeader ← NIL;
header.inLoadMode ← inLoadMode;
header.continuation ← continuation;
header.countData ← countData;
header.version ← IF ( cedar ) THEN cedarVersion ELSE BootFile.currentVersion;
pEntryGroup ← @header.entries[0];
entryBuffer ← headerBuffer;
Write sequence of (map, data) groups
page ← 0;
FOR countRemaining ← countData, countRemaining - countGroup
WHILE ( countRemaining > 0 ) DO {
Calculate group size
countGroup ← MIN[nEntry, countRemaining]; -- Write map page
ProcessorFace.SpecialSetMP[groupsProcessed ← groupsProcessed.SUCC];
indicate progress to the user (makes him feel happier)
pEntry ← pEntryGroup;
THROUGH [0..countGroup) DO
page ← AdvanceToNonVacant[page]; -- w/SUCC guaranteed to increase page!
IF ( page >= countVM ) THEN GermWorldError[884];
pEntry^ ← [page, PrincOpsUtils.GetPageValue[page]];
pEntry ← pEntry + SIZE[BootFile.Entry]; -- .SUCC;
page ← page.SUCC;
ENDLOOP;
result ← BC.Transfer[handle, entryBuffer, 1];
initiate transfer of header/trailer page!
IF ( result # [ok[]] ) THEN { GOTO EntryGroupProblem };
pEntry ← pEntryGroup;
count ← countGroup;
WHILE ( count # 0 ) DO
find and transfer one run
pageNext ← pageRun ← pEntry.page;
DO
until end of run found
count ← count.PRED;
pageNext ← pageNext.SUCC; -- (next) page after the current run
pEntry ← pEntry + SIZE[BootFile.Entry]; -- .SUCC;
IF ( ( count = 0 ) OR ( pEntry.page # pageNext ) ) THEN EXIT;
ENDLOOP;
result ← BC.Transfer[handle, pageRun, (pageNext - pageRun)];
initiate transfer of this run!
IF ( result # [ok[]] ) THEN { GOTO EntryGroupProblem };
ENDLOOP;
result ← BC.SyncTransfer[handle];
wait until header/trailer & runs make it out.
IF ( result # [ok[]] ) THEN { GOTO EntryGroupProblem };
trailer.version ← BootFile.currentVersion; -- set version
pEntryGroup ← @trailer.entries[0];
nEntry ← BootFile.maxEntriesPerTrailer;
entryBuffer ← trailerBuffer;
EXITS
EntryGroupProblem => {
ReturnPageBuffer[headerBuffer];
ReturnPageBuffer[trailerBuffer];
WITH r: result SELECT FROM
error => GermWorldError[r.code];
ENDCASE => GermWorldError[895];
};
} ENDLOOP;
ReturnPageBuffer[headerBuffer];
ReturnPageBuffer[trailerBuffer];
IF ( cedar ) THEN {
Cedar Nucleus: write data words from entire page map (double-buffered)
mapBuffer: PageNumber ← BorrowPageBuffer[];
mapArray: LONG POINTER TO PageValueArray ~ PrincOpsUtils.AddressForPageNumber[mapBuffer];
mapPage: PageNumber ← 0;
countRemaining: PageCount ← countVM;
showProgress: CARD16 ← 0;
ShowCodeInMP[MPCodes.germMapIO];
WHILE ( mapPage < countVM ) DO {
nEntry: CARD16 ~ MIN[countRemaining, valuesPerPage];
IF ( debug)
THEN ProcessorFace.SpecialSetMP[showProgress ← showProgress.SUCC];
FOR i: CARD16 IN [0 .. nEntry) WHILE ( mapPage < countVM ) DO
IF ( InGermOrIORegion[mapPage] ) THEN { ignore entry when reading it back };
mapArray[i] ← PrincOpsUtils.GetPageValue[mapPage];
mapPage ← mapPage.SUCC;
ENDLOOP;
countRemaining ← countRemaining - nEntry;
result ← BC.Transfer[handle, mapBuffer, 1];
initiate transfer of this page!
IF ( result = [ok[]] ) THEN result ← BC.SyncTransfer[handle];
wait until *the* map page makes it out before shutting down.
IF ( result # [ok[]] ) THEN { GOTO MapGroupProblem };
EXITS
MapGroupProblem => {
ReturnPageBuffer[mapBuffer];
WITH r: result SELECT FROM
error => GermWorldError[r.code];
ENDCASE => GermWorldError[895];
};
} ENDLOOP;
ReturnPageBuffer[mapBuffer];
};
result ← BC.CloseChannel[handle];
WITH r: result SELECT FROM
ok => NULL;
error => GermWorldError[r.code];
ENDCASE => GermWorldError[893];
};
Initialize: PROC ~ {
Assume called from main body & all SD entries zero
{ OPEN PrincOps;
ConditionEmpty: Condition ~ [tail: PsbNull, abortable: FALSE, wakeup: FALSE];
SD[sSignal] ← SD[sError] ← SD[sReturnError] ← SD[sSignalList] ← SD[sErrorList] ← SD[sReturnErrorList] ← LOOPHOLE[SignalHandler];
SD[sUnnamedError] ← LOOPHOLE[UnnamedError];
PDA.fault ¬ ALL[[QueueEmpty, ConditionEmpty]];
Clear fault queues to aid debugging (makes fault cause reschedule error).
};
MesaRuntimeInit.Start[LOOPHOLE[MesaRuntimeInit.TrapsImpl]];
ProcessorFace.Start[];
ShowCodeInMP[MPCodes.germStarting];
redisplay, for machines that require the ProcessorHead to be started to see the MP
ResetChunkAllocator[]; -- ProcessorHead must be started!
countVM ← FindMapBoundary[]; -- ProcessorHead must be started!
CompactAvailableVM[]; -- moves everything not in IORegion or Germ(code-image only)
GrabBuffersForGerm[]; -- keeps real pages forever; extends the Germ boundary permenently
SparcSoftcardInit.InitializeSoftcard[ReclaimPhysicalPage];
GrabPagesForSoftWindow[];
{
Subsequent entries to the germ go directly to Run[]:
state: RECORD [ a, b: CARD16, sv: PrincOps.StateVector ]; -- a, b: to fix alignment
mainbody: PrincOps.FrameHandle ~ PrincOpsUtils.GetReturnFrame[];
state.sv.instbyte ← state.sv.stkptr ← 0;
state.sv.dest ← GermSwap.pInitialLink^ ← LOOPHOLE[Run, PrincOps.ControlLink];
state.sv.source ← mainbody.returnlink;
PrincOpsUtils.Free[mainbody];
RETURN WITH state.sv -- to Run[], never to return to Initialize
};
};
PrincOpsUtils.WriteWDC[1];
Interrupts are supposed to be disabled by the boot button, but just in case..
ProcessorFace.SpecialSetMP[MPCodes.germStarting]; -- let them know we are here
Initialize[]; -- never returns
}.