-- Cedar Remote Debugging: access to world-swap client memory

-- WVMOutLd.mesa

-- Andrew Birrell July 27, 1983 2:33 pm

DIRECTORY
Boot    USING[ DiskFileID, LVBootFiles ],
BootFile   USING[ Entry, Header, maxEntriesPerHeader, maxEntriesPerTrailer, MemorySizeToFileSize, Trailer ],
Booting   USING[ Boot, Bootee, defaultSwitches, nullBootFile, Switches ],
DiskChannel USING[ CompletionHandle, Create, CreateCompletionObject, Delete,
Drive, GetDriveAttributes, GetNextDrive, Handle, IORequest,
InitiateIO, nullDrive, WaitAny],
Device   USING[ Type ],
File    USING[ Capability, Create, Delete, delete, GetSize, MakePermanent, nullID, PageNumber, read, write ],
FileTypes  USING[ tUntypedFile ],
Inline   USING[ BITAND ],
PageMap  USING[ GetF, flagsVacant, Value ],
PilotDisk  USING[ Label, LabelCheckSum ],
PilotSwitches USING[ switches ],
SpecialFile  USING[ MakeBootable, SetDebuggerFiles ],
SpecialSpace USING[ MakeResident, MakeSwappable, realMemorySize ],
SpecialVolume USING[ GetLogicalVolumeBootFiles ],
Space   USING[ CopyIn, CopyOut, Create, defaultWindow, Delete,
GetHandle, Handle, LongPointer, LongPointerFromPage, Map, PageFromLongPointer, PageNumber, virtualMemory,
VMPageNumber],
Volume   USING[ GetType, SystemID ],
WorldVM  USING[ AddressFault, BadWorld ],
WVMPrivate;

WVMOutLd: MONITOR
IMPORTS BootFile, Booting, DiskChannel, File, Inline, PageMap, PilotDisk, PilotSwitches, SpecialFile, SpecialSpace, SpecialVolume, Space, Volume, WorldVM, WVMPrivate
EXPORTS WVMPrivate
SHARES File-- to create a capability inside VerifyFile --, PageMap -- GetF -- =

BEGIN

-- Management of outload files --

debugger: File.Capability;
debuggee: File.Capability;
thisIsADebugger: BOOLFALSE;

SetupFiles: INTERNAL PROC RETURNS[ok: BOOL] =
BEGIN
size: CARDINAL = BootFile.MemorySizeToFileSize[SpecialSpace.realMemorySize]+256;
bootFiles: Boot.LVBootFiles;
changed: BOOLEANFALSE;
IF Volume.GetType[Volume.SystemID[]] # debugger
AND Volume.GetType[Volume.SystemID[]] # debuggerDebugger
THEN RETURN[FALSE]
ELSE ok ← TRUE;
SpecialVolume.GetLogicalVolumeBootFiles[Volume.SystemID[], @bootFiles];
debugger ← VerifyFile[bootFiles[debugger], size, @changed];
debuggee ← VerifyFile[bootFiles[debuggee], size, @changed];
IF changed
THEN BEGIN
SpecialFile.SetDebuggerFiles[debugger: debugger, debuggee: debuggee];
File.MakePermanent[debugger]; File.MakePermanent[debuggee];
END;
thisIsADebugger ← TRUE;
END;

VerifyFile: INTERNAL PROC[old: Boot.DiskFileID, size: CARDINAL,
changed: POINTER TO BOOLEAN]
RETURNS[ new: File.Capability ] =
BEGIN
new ← [fID: old.fID, permissions: File.read+File.write+File.delete];
IF old.fID = File.nullID
OR File.GetSize[new] < size
THEN -- need to create --
BEGIN
IF old.fID # File.nullID THEN File.Delete[new];
new ← File.Create[Volume.SystemID[], size, FileTypes.tUntypedFile];
[] ← SpecialFile.MakeBootable[file: new, firstPage: FIRST[File.PageNumber],
count: size, lastLink: [0]--cf. CoPilot!--];
changed^ ← TRUE;
END;
END;

outLdSpace: Space.Handle = Space.Create[1, Space.virtualMemory];
outLdPage: File.PageNumber ← LAST[File.PageNumber];
outLdAddress: LONG POINTER = Space.LongPointer[outLdSpace];

ReadOutLdPage: INTERNAL PROC[page: File.PageNumber] =
{ IF outLdPage # page
THEN Space.CopyIn[outLdSpace, [debuggee,outLdPage←page]] };

WriteOutLdPage: INTERNAL PROC =
{ Space.CopyOut[outLdSpace, [debuggee,outLdPage]] };

InvalidateOutLdPage: INTERNAL PROC =
{ outLdPage ← LAST[File.PageNumber] };

-- Layout of boot and outload files --

BFMEntryIndex: TYPE = [0..MAX[BootFile.maxEntriesPerHeader,
BootFile.maxEntriesPerTrailer]];

BootFileMapItem: TYPE = RECORD [
link: BFMPointer ← NIL,
lastMemPage: WVMPrivate.PageNumber,
filePageOffset: File.PageNumber,
entryCount: BFMEntryIndex,
entries: LONG POINTER TO ARRAY [0..0) OF BootFile.Entry];

BFMPointer: TYPE = REF BootFileMapItem;

bfmHead: BFMPointer ← NIL;

OpenOutLdFile: INTERNAL PROC =
BEGIN
bfm: BFMPointer;
bfh: LONG POINTER TO BootFile.Header;
entries: LONG POINTER TO ARRAY [0..0) OF BootFile.Entry;
curmax: CARDINAL ← BootFile.maxEntriesPerHeader;
curbase: File.PageNumber ← 0;
OutLdPageSpace: PROC RETURNS[LONG POINTER] =
-- returns pointer into space mapped to outload file map page --
BEGIN
space: Space.Handle = Space.Create[1, Space.virtualMemory];
Space.Map[space, [debuggee, curbase]];
RETURN[Space.LongPointer[space]]
END;
pagesremaining: CARDINAL;
bfmHead ← NIL;
bfh ← OutLdPageSpace[];
entries ← @bfh.entries;
pagesremaining ← bfh.countData;
bfmHead ← bfm ← NEW[BootFileMapItem];
DO bfm.entries ← entries;
bfm.entryCount ← MIN[curmax, pagesremaining];
bfm.lastMemPage ← bfm.entries[bfm.entryCount-1].page;
bfm.filePageOffset ← curbase+1;
pagesremaining ← pagesremaining - bfm.entryCount;
IF pagesremaining = 0 THEN EXIT;
curbase ← bfm.filePageOffset+bfm.entryCount;
curmax ← BootFile.maxEntriesPerTrailer;
entries ← OutLdPageSpace[] + SIZE[BootFile.Trailer];
bfm.link ← NEW[BootFileMapItem];
bfm ← bfm.link;
ENDLOOP;
END;

CloseOutLdFile: INTERNAL PROC =
BEGIN
FOR bfmPtr: BFMPointer ← bfmHead, bfmPtr.link UNTIL bfmPtr = NIL
DO Space.Delete[Space.GetHandle[Space.PageFromLongPointer[bfmPtr.entries]]] ENDLOOP;
bfmHead ← NIL;
END;

SearchOutLdFile: INTERNAL PROCEDURE [mempage: WVMPrivate.PageNumber]
RETURNS[ entry: LONG POINTER TO BootFile.Entry ] =
BEGIN
-- Returns [NIL,NIL] if page isn't in file --
FOR bfm: BFMPointer ← bfmHead, bfm.link UNTIL bfm = NIL
DO IF mempage <= bfm.lastMemPage
THEN BEGIN -- it's in this page of the bootFileMap --
FOR i: BFMEntryIndex IN [0..bfm.entryCount]
DO SELECT LONG[bfm.entries[i].page] FROM
< mempage => NULL;
> mempage => RETURN[NIL];
ENDCASE =>
BEGIN
ReadOutLdPage[bfm.filePageOffset+i];
RETURN[ @bfm.entries[i] ];
END
REPEAT FINISHED => ERROR
ENDLOOP;
END;
REPEAT FINISHED => RETURN[NIL]
ENDLOOP;
END;


-- Transfers to/from outload file --

ReadOtherCore: PUBLIC ENTRY PROC[data: REF WVMPrivate.PageData,
mempage: WVMPrivate.PageNumber]
RETURNS[map: WVMPrivate.MapEntry, ok: BOOLEAN] =
BEGIN
ENABLE UNWIND => NULL;
entry: LONG POINTER TO BootFile.Entry = SearchOutLdFile[mempage];
IF entry = NIL
THEN IF mempage IN [376B..377B]
THEN -- kludge: Germ doesn't write io pages to outload file, so read ours instead --
BEGIN
value: PageMap.Value = PageMap.GetF[mempage];
IF value.flags = PageMap.flagsVacant THEN ERROR WorldVM.AddressFault[mempage];
data^ ← LOOPHOLE[WVMPrivate.PageAddress[mempage],
LONG POINTER TO WVMPrivate.PageData]^;
map ← [flags: [logSE: value.logSingleError,
W: TRUE,
D: FALSE,
R: TRUE],
page: value.realPage];
ok ← TRUE;
END
ELSE RETURN[,FALSE]
ELSE BEGIN
data^ ← LOOPHOLE[outLdAddress, LONG POINTER TO WVMPrivate.PageData]^;
map ← LOOPHOLE[entry.value];
ok ← TRUE;
END;
END;

WriteOtherCore: PUBLIC ENTRY PROC[data: REF WVMPrivate.PageData,
mempage: WVMPrivate.PageNumber,
map: WVMPrivate.MapEntry] =
BEGIN
ENABLE UNWIND => NULL;
entry: LONG POINTER TO BootFile.Entry = SearchOutLdFile[mempage];
IF entry = NIL THEN ERROR WorldVM.AddressFault[mempage];
LOOPHOLE[outLdAddress, LONG POINTER TO WVMPrivate.PageData]^ ← data^;
WriteOutLdPage[];
IF NOT entry.value.flags.writeProtected
THEN entry.value.flags.dirty ← TRUE;
END;



-- Transfers to/from backing store --

VolumeObject: TYPE = RECORD [
link: VHandle,
type: Device.Type,
ordinal: CARDINAL,
handle: DiskChannel.Handle];

completion: DiskChannel.CompletionHandle ←
DiskChannel.CreateCompletionObject[];

VHandle: TYPE = REF VolumeObject;

vHead: VHandle ← NIL;

DiscoverVolumes: INTERNAL PROCEDURE =
BEGIN
drive: DiskChannel.Drive ← DiskChannel.nullDrive;
FOR vh: VHandle ← vHead, vh.link UNTIL vh = NIL
DO DiskChannel.Delete[vHead.handle] ENDLOOP;
vHead ← NIL;
UNTIL (drive ← DiskChannel.GetNextDrive[drive]) = DiskChannel.nullDrive
DO handle: DiskChannel.Handle ←
DiskChannel.Create[drive: drive, completion: completion];
vHead ← NEW[VolumeObject ← [
link: vHead,
type: DiskChannel.GetDriveAttributes[drive].deviceType,
ordinal: DiskChannel.GetDriveAttributes[drive].deviceOrdinal,
handle: DiskChannel.Create[drive: drive, completion: completion]]];
ENDLOOP;
END;

bufferSpace: Space.Handle = Space.Create[1, Space.virtualMemory];
bufferData: LONG POINTER TO WVMPrivate.PageData = Space.LongPointer[bufferSpace];
BadDriveTag: ERROR = CODE;
BadTransfer: ERROR = CODE;

MoveLocalDiskPage: PUBLIC ENTRY PROC[data: REF WVMPrivate.PageData,
direction: WVMPrivate.ChannelDirection,
addr: WVMPrivate.DiskAddress] =
BEGIN
ENABLE UNWIND => NULL;
req: DiskChannel.IORequest;
label: ARRAY [0..SIZE[PilotDisk.Label]+3) OF WORD;
pLabel: POINTER = Inline.BITAND[@label+3,-4];
FOR vh: VHandle ← vHead, vh.link UNTIL vh = NIL
DO IF vh.type # addr.deviceType OR vh.ordinal # addr.deviceOrdinal THEN LOOP;
req.dontIncrement ← FALSE;
req.channel ← vh.handle;
req.diskPage ← addr.diskPage + addr.offset;
req.memoryPage ← Space.VMPageNumber[bufferSpace];
req.count ← 1;
req.label ← pLabel;
req.command ← vrr;
SpecialSpace.MakeResident[bufferSpace];
DiskChannel.InitiateIO[@req];
UNTIL DiskChannel.WaitAny[completion] = @req DO NULL ENDLOOP;
SpecialSpace.MakeSwappable[bufferSpace];
IF req.status # goodCompletion
OR PilotDisk.LabelCheckSum[pLabel, addr.offset] # addr.labelCheck
THEN ERROR BadTransfer[];
IF direction = read THEN { data^ ← bufferData^; EXIT };
IF direction # write THEN ERROR;
bufferData^ ← data^;
req.memoryPage ← Space.VMPageNumber[bufferSpace];
req.command ← vvw;
SpecialSpace.MakeResident[bufferSpace];
DiskChannel.InitiateIO[@req]; -- check the label and write the data
UNTIL DiskChannel.WaitAny[completion] = @req DO NULL ENDLOOP;
SpecialSpace.MakeSwappable[bufferSpace];
IF req.status # goodCompletion THEN ERROR BadTransfer[];
EXIT
REPEAT FINISHED => ERROR BadDriveTag[];
ENDLOOP;
END;


-- Control transfers --

-- Mechanism for handling "inloaded twice". If we're inloaded a second time
-- we cannot proceed, because various pilot caches may not reflect the correct
-- state of the disk(s). In that case, we re-boot our logical volume with a
-- switch indicating that the client outload file should be believed. --


CantGetHere: ERROR = CODE;
CantFindInloadFlag: ERROR = CODE;
mySwitches: Booting.Switches ← PilotSwitches.switches;

WorldSwap: INTERNAL PROC[ to: { install, run },
boot: Booting.Bootee,
switches: Booting.Switches ← Booting.defaultSwitches ] =
BEGIN
-- to=boot => outload then boot physical volume, unless switches.w = down;
-- to=client => outload then inload client.
-- returns after we are inloaded again.
inLoaded: BOOLFALSE; -- "inloaded twice" boolean --
CloseOutLdFile[];
InvalidateOutLdPage[];
IF to = install AND mySwitches.w = down
THEN NULL -- assume client outload is acceptable and interesting --
ELSE Booting.Boot[boot: boot, switches: switches,
outload: IF thisIsADebugger THEN [debugger] ELSE Booting.nullBootFile ];
IF inLoaded
THEN -- inloaded twice! --
BEGIN
mySwitches.w ← down;
Booting.Boot[[logical[]], mySwitches];
ERROR CantGetHere[]
END
ELSE BEGIN
mempage: Space.PageNumber = Space.PageFromLongPointer[@inLoaded];
-- now search "debugger" outload file for that page, then set bool TRUE in outload file --
bfh: LONG POINTER TO BootFile.Header;
entries: LONG POINTER TO ARRAY [0..0) OF BootFile.Entry;
curmax: CARDINAL ← BootFile.maxEntriesPerHeader;
curbase: File.PageNumber ← 0;
pagesremaining: CARDINAL;
Space.CopyIn[outLdSpace, [debugger, curbase]];
bfh ← outLdAddress;
entries ← @bfh.entries;
pagesremaining ← bfh.countData;
DO filePageOffset: File.PageNumber = curbase+1;
entryCount: CARDINAL = MIN[curmax, pagesremaining];
IF mempage <= entries[entryCount-1].page
THEN -- it's in this page of the bootFileMap --
BEGIN
FOR i: BFMEntryIndex IN [0..entryCount]
DO SELECT LONG[entries[i].page] FROM
< mempage => NULL;
> mempage => ERROR CantFindInloadFlag[];
ENDCASE =>
BEGIN
diskInLoaded: LONG POINTER TO BOOL;
Space.CopyIn[outLdSpace, [debugger, filePageOffset+i]];
diskInLoaded ← LOOPHOLE[
outLdAddress + (LONG[@inLoaded]-Space.LongPointerFromPage[mempage])];
diskInLoaded^ ← TRUE;
Space.CopyOut[outLdSpace, [debugger, filePageOffset+i]];
GOTO done;
END
REPEAT FINISHED => ERROR
ENDLOOP;
END;
pagesremaining ← pagesremaining - entryCount;
IF pagesremaining = 0 THEN ERROR CantFindInloadFlag[];
curbase ← filePageOffset+entryCount;
curmax ← BootFile.maxEntriesPerTrailer;
Space.CopyIn[outLdSpace, [debugger, curbase]];
entries ← outLdAddress + SIZE[BootFile.Trailer];
ENDLOOP;
EXITS done => InvalidateOutLdPage[];
END;
OpenOutLdFile[];
END;

LocateOther: PUBLIC ENTRY PROC =
BEGIN
ENABLE UNWIND => NULL;
-- This is called successfully only once per run --
IF NOT SetupFiles[] THEN RETURN WITH ERROR WorldVM.BadWorld[];
DiscoverVolumes[];
Space.Map[bufferSpace];
Space.Map[outLdSpace];
WorldSwap[install, [physical[]]];
END;

GoOther: PUBLIC ENTRY PROC =
{ WorldSwap[run, [inload[[debuggee]]]] };

ReallySwapAndBoot: PUBLIC ENTRY SAFE PROC[boot: Booting.Bootee, switches: Booting.Switches] = TRUSTED
{ WorldSwap[run, boot, switches] };


END.