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-- SparcSoftcardOps USING [ InitializeSoftcard, ResetSoftcard ], VolumeFormat USING [ PhysicalRoot, PRCurrentVersion, PRSeal ], GermPrivate USING [DebugMPCode, DebugMPCardinal, GetTeledebugged, TeleDebug ]; BootSwapGerm: PROGRAM IMPORTS Basics, BC: BootChannel, GermPrivate, GermSwap, MesaRuntimeInit, PrincOpsUtils, ProcessorFace, ProcessorFaceExtras, RuntimeError, SparcSoftcardOps EXPORTS BootChannel, GermPrivate SHARES GermSwap ~ { OPEN BootChannel, GermPrivate; 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; IOPage: PageNumber ~ 0FFH; -- special page in lowcore InIORegion: PROC [ p: PageNumber ] RETURNS [ reserved: BOOL _ FALSE ] ~ INLINE { reserved _ ( p IN [ProcessorFaceExtras.dFirst64KPage .. IOPage] ); }; pCountGerm: PUBLIC LONG POINTER TO READONLY CARD16 _ LOOPHOLE[GermSwap.LP[GermSwap.pCountGerm, GermSwap.mdsiGerm]]; pageGerm: PUBLIC PageNumber _ (GermSwap.mdsiGerm * 256) + GermSwap.countSkip; imageMarker: PUBLIC PageNumber ~ pageGerm + pCountGerm^.PRED; pageAfterGerm: PUBLIC PageNumber _ imageMarker.SUCC; 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: BOOL _ FALSE ] ~ 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 ] ~ { 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 ] ~ { 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; 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; backedPageRun: PageCount; 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 { FOR i: PageCount IN [0..pages) DO IF ( IsVacantPage[(p + i)] ) THEN { pv: PrincOps.PageValue ~ VacatePageFlags[lastBackedPage]; PrincOpsUtils.SetPageValue[(p + i), pv]; lastBackedPage _ lastBackedPage.PRED; backedPageRun _ backedPageRun.PRED; }; ENDLOOP; }; CompactAvailableVM: PROC ~ { 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 { pv.state.flags.readonly _ FALSE; PrincOpsUtils.SetPageValue[last, pv]; }; }; ENDCASE => { pv.state.flags.readonly _ FALSE; PrincOpsUtils.SetPageValue[high, valueVacant]; PrincOpsUtils.SetPageValue[last, pv]; }; run _ run.SUCC; -- we have just extended the run by a page { 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 ] ~ { 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]; }; dFirst64KStorage: LONG DESCRIPTOR FOR ARRAY OF WORD; ResetChunkAllocator: PROC ~ INLINE { dFirst64KStorage _ ProcessorFaceExtras.dFirst64KDescriptor; }; chunkGrain: CARD16 ~ 16; AllocateChunk: PUBLIC PROC [ words: CARD16, align: PrincOps.Alignment _ a16 ] RETURNS [ chunk: LONG POINTER _ NIL ] ~ { 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 ] ~ { 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]]; }; 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]; }; sanityChecking: PUBLIC BOOL ¬ FALSE; -- set to TRUE by boot switch? cedar7Debug: BOOL _ FALSE; 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 ] ~ { ProcessorFace.SpecialSetMP[code]; IF ( ( cedar7Debug ) AND ( code # MPCodes.germInLoad ) ) THEN ShowCodeInMP[code]; DebugMPCode[code]; }; Error: PUBLIC PROC [ code: MPCodes.Code ] ~ { GermWorldError[code] }; Mumble: PUBLIC SIGNAL [ code: MPCodes.Code ] ~ CODE; 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.StartFault] ) => MPCodes.germStartFault, ( signal = LOOPHOLE[RuntimeError.StackError] ) => 107, ( signal = LOOPHOLE[RuntimeError.UnboundProcedure] ) => 108, ( signal = LOOPHOLE[RuntimeError.ZeroDivisor] ) => 110, ENDCASE => MPCodes.germERROR; GermWorldError[code]; }; GermWorldError: PUBLIC PROC [ mpCode: MPCodes.Code ] ~ { 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[pc]; localFrame ¬ localFrame.returnlink.frame; -- ReadReturnLink[localFrame].frame IF ( localFrame = PrincOps.NullFrame ) THEN EXIT; ENDLOOP; }; ENDLOOP; }; 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 }; result: BootChannel.Result; handle: BootChannel.Handle; GermSwap.InitializeMDS[]; -- set pMon ShowCodeInMP[MPCodes.germStarting]; { location: BootFile.Location; location.deviceType ¬ Device.nullType; [] ¬ BC.Create[@location, read, NIL]; }; DO { ResetChunkAllocator[]; -- need to double check this one! 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]; }; 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: BOOL _ TRUE ] ~ 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; 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­; 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: BOOL _ FALSE; inLoadMode: BootFile.InLoadMode; nextPage: PageNumber _ 0; 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; pEntryGroup: EntryPointer _ @header.entries[0]; nEntry: CARD16 _ BootFile.maxEntriesPerHeader; pEntry: EntryPointer; count, countData, countGroup, countRemaining: PageCount; 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! }; SparcSoftcardOps.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; DO countGroup _ MIN[nEntry, countRemaining]; 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]; nextPage _ pEntry.page.SUCC; pEntry _ pEntry + SIZE[BootFile.Entry]; -- .SUCC; ENDLOOP; pEntry _ pEntryGroup; count _ countGroup; WHILE ( count # 0 ) DO IF ( InGermOrIORegion[pEntry.page] ) -- InIORegion[pEntry.page] ??? THEN { 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 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]; EXIT; }; ENDCASE; ENDLOOP; result _ BC.Transfer[handle, pageRun, (pageNext - pageRun)]; IF ( result # [ok[]] ) THEN { GOTO EntryGroupProblem }; }; ENDLOOP; result _ BC.SyncTransfer[handle]; IF ( result # [ok[]] ) THEN { GOTO EntryGroupProblem }; 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]; countRemaining _ countRemaining - countGroup; IF ( countRemaining = 0 ) THEN { IF ( result = [ok[]] ) THEN result _ BC.SyncTransfer[handle]; IF ( result # [ok[]] ) THEN { GOTO EntryGroupProblem }; EXIT; }; 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; 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]; FOR i: CARD16 IN [0 .. nEntry) WHILE ( mapPage < countVM ) DO current: PrincOps.PageValue ~ PrincOpsUtils.GetPageValue[mapPage]; 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 { 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 { 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 ] ~ { result: BootChannel.Result; groupsProcessed: CARD16 _ 0; page: PageNumber; 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]; nEntry: CARD16 _ BootFile.maxEntriesPerHeader; pEntry: EntryPointer; count, countData, countGroup, countRemaining: PageCount; pageNext, pageRun: PageNumber; ShowCodeInMP[MPCodes.germOutLoad]; countData _ 0; FOR p: PageNumber _ AdvanceToAvailable[0], AdvanceToAvailable[p.SUCC] WHILE ( p < countVM ) DO IF ( NOT IsVacantPage[p] ) THEN countData _ countData.SUCC; ENDLOOP; 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; page _ 0; FOR countRemaining _ countData, countRemaining - countGroup WHILE ( countRemaining > 0 ) DO { countGroup _ MIN[nEntry, countRemaining]; -- Write map page ProcessorFace.SpecialSetMP[groupsProcessed _ groupsProcessed.SUCC]; 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]; IF ( result # [ok[]] ) THEN { GOTO EntryGroupProblem }; pEntry _ pEntryGroup; count _ countGroup; WHILE ( count # 0 ) DO pageNext _ pageRun _ pEntry.page; DO 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)]; IF ( result # [ok[]] ) THEN { GOTO EntryGroupProblem }; ENDLOOP; result _ BC.SyncTransfer[handle]; IF ( result # [ok[]] ) THEN { GOTO EntryGroupProblem }; trailer.version _ BootFile.currentVersion; -- set version pEntryGroup _ @trailer.entries[0]; nEntry _ BootFile.maxEntriesPerTrailer; entryBuffer _ trailerBuffer; EXITS EntryGroupProblem => { WITH r: result SELECT FROM error => GermWorldError[r.code]; ENDCASE => GermWorldError[895]; }; } ENDLOOP; ReturnPageBuffer[headerBuffer]; ReturnPageBuffer[trailerBuffer]; IF ( cedar ) THEN { 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 mapArray[i] _ PrincOpsUtils.GetPageValue[mapPage]; 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 }; EXITS MapGroupProblem => { 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 ~ { { 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]]; }; MesaRuntimeInit.Start[LOOPHOLE[MesaRuntimeInit.TrapsImpl]]; ProcessorFace.Start[]; ShowCodeInMP[MPCodes.germStarting]; 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 SparcSoftcardOps.InitializeSoftcard[ReclaimPhysicalPage]; { 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]; ProcessorFace.SpecialSetMP[MPCodes.germStarting]; -- let them know we are here Initialize[]; -- never returns }. '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 1, 1988 3:54:09 pm PDT Note: The code in Initialize assumes the machine starts with vm "Compact"ed 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. version number of outload files written with cedarOutLoad Germ VM Geography Number of (image) pages for the germ; Temp(?) type fix for GermSwap definition bug virtual (page) address of the first (image) page of the germ should be engraved in {interface} stone ([mdsiGerm, countSkip] = 3e02H / 37002B) 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 current end of (mapped) germ vm (includes allocated buffer space, doesn't include pageTemp). Think of the region [imageMarker..pageAfterGerm) as the TwilightZone. 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") 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) highest entry in the map; Export to GermPrivate end of (Compacted) resident vm run; state = non-vacant & writable for pages [(lastBackedPage-backedPageRun)..lastBackedPage] number of pages left in the contiguous run grab pages (can only do during inLoad/boot when the world is "Compact") pages are guaranteed to be non-vacant/writable we don't want to lose pages (for the dorado case where we so much real memory) 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) 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. this is just an efficiency hack to not rewrite the same values make writable (don't care about dirty/ref'ed) put in the map at end of run make writable (don't care about dirty/ref'ed) vacate the page we're stealing the backing page from put in the map at end of run check to see if next page is in the same run, or start a new run 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). Small Object Allocation 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. Chunks are allocated from a special region of low vm; look at the implemenation for details (mds=0; within ProcessorFaceExtras.dFirst64KPage) not guaranteed to be smart enough to return storage to the allocater 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 Germ Error Handling display code in panel (for a second if "debug") 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. 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. ( signal = LOOPHOLE[RuntimeError.SendMsgSignal] ) => 106, ( signal = LOOPHOLE[RuntimeError.UNCAUGHT] ) => 109, 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. ShowCardinalInMP[localFrame]; ++ not very useful. Worker routines Backstop Create routine to plug end of chain of BootChannel interfaces. (Possibly invoked by dummy call to initialize BootChannel implementations) after the very first execution, the germ entry point is here Initialize the BootChannels.. allow them to allocate permanent storage: (null device doesn't really create a Channel; just inits) inLoad exits through JumpCall2, outLoad does explicit EXIT, bootPhysicalVolume turns itself into an inLoad frees our frame now get the boot file and go We overwrite the Location we were working on with the new suggested Location, fall through, and try again. whether outload file was written with cedarOutLoad (e.g. by us) basically whether we're doing a boot(load) or a rollback(restore) succ[last page for which the map entry has been fixed] (scoped here for pilot?) a handy-dandy progress number to show to the user as an mpcode will always point to either header.entries[0] or trailer.entries[0] maximum number of entries left in this entry group (we assume bootfile has a full header) the next entry to be processed in this group Read first page (header) and process its specific information, setup pEntryGroup Restore real memory and map from boot file, beginning with header entry group 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) for load/boot we don't mess with this page; it should be fine already! Transfer data for all (non-vacant) pages in entry group find and start transfer of each run detected in this entry group skip loading this particular page from the image. this should never happen because of the way bootfiles/checkpoints are made run thru the Entry Group identifying consecutive vm pages (runs) this page should not be in bootfile! initiate transfer of this run! wait until all the runs make it in before mucking with the flags! Restore map flags/PageState; we "dirty"ed them in transfer! indicate progress to the user (makes him feel happier) Theres no trailer after the last group! Get next Entry Group (from trailer page) All groups are now loaded maximum number of entries left in this buffer IF ( InGermVM[mapPage] ) THEN { mapPage _ mapPage.SUCC; LOOP }; this is just a little performance hack for the Dorado cache! 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) Assumptions: interrupts disabled, all devices quiesced a handy-dandy progress number to show to the user as an mpcode next virtual page to (possibly) outload will always point to either header.entries[0] or trailer.entries[0] maximum number of entries left in this entry group (we assume bootfile has a full header) the next entry to be processed in this group Number of pages to save is total nonvacant minus those in germ Construct header in first map page Write sequence of (map, data) groups Calculate group size indicate progress to the user (makes him feel happier) initiate transfer of header/trailer page! find and transfer one run until end of run found initiate transfer of this run! wait until header/trailer & runs make it out. ReturnPageBuffer[headerBuffer]; ReturnPageBuffer[trailerBuffer]; Cedar Nucleus: write data words from entire page map (double-buffered) IF ( InGermOrIORegion[mapPage] ) THEN { ignore entry when reading it back }; initiate transfer of this page! wait until *the* map page makes it out before shutting down. ReturnPageBuffer[mapBuffer]; Assume called from main body & all SD entries zero Clear fault queues to aid debugging (makes fault cause reschedule error). redisplay, for machines that require the ProcessorHead to be started to see the MP GrabPagesForSoftWindow[]; Subsequent entries to the germ go directly to Run[]: Interrupts are supposed to be disabled by the boot button, but just in case.. Κ#T˜codešœ™KšœH™HKšΟy™Kš)™)Kš$Πky™'Kšœ,Οk™/Kšœ(™(Kšœ0™0K™2K™—šŸ ˜ KšœŸœŸœ6˜MKšœ ŸœM˜^Kšœ Ÿœ…˜“KšœŸœ˜Kšœ Ÿœ1ŸœN˜KšœŸœ˜+KšœŸœΏ˜ΜKšœ ŸœΐŸœ5Ÿœ˜˜KšœŸœ˜˜«KšœŸœ[˜nKšœŸœ(˜AKšœΟc˜KšœŸœ'˜=Kšœ Ÿœ,˜>Kšœ Ÿœ=˜NK˜—KšΟnœG™KK˜š‘ œŸ˜KšŸœ Ÿœˆ˜šKšŸœ˜ KšŸœ ˜KšŸœ˜K™KšœŸœ‰™K™šœI™IKšœ:™:Kšœ”™”Kšœ:™:Kš œŸœŸœŸœ Ÿœ™*Kšœ%™%—K˜Kšœ™™™K˜Kš ‘œŸœŸœŸœ ˜9K˜Kšœ Ÿœ˜%Kšœ Ÿœ˜'K˜Kš œŸœŸœŸœŸœ˜4K˜KšœŸœŸœ˜IKšœŸœŸœŸœ˜FKšœŸœŸœ>˜XK˜Kšœ"Ÿœ˜AKšœ!Ÿœ˜?Kšœ@˜@K˜Kš ‘ œŸœŸœ ŸœŸœ<˜Kš‘œŸœŸœŸœD˜—Kš ‘ œŸœŸœ ŸœŸœ9˜Kš ‘œŸœŸœ ŸœŸœA˜‰Kš‘œŸœŸœŸœ;˜ŽKš‘œŸœ1ŸœF˜‹K˜šœŸœ˜Kšœ9™9—headšΟzΠkz’ ™Kš‘œ ˜5K˜š ‘ œŸœŸœ ŸœŸœŸœ˜PKšœŸœ1˜BKšœ˜K˜—šœ ŸœŸœŸœŸœŸœŸœŸœ Ÿœ*˜sKšœR™R—šœ Ÿœ=˜MKšœ<™˜‰š ‘œŸœŸœ ŸœŸœŸœ!˜yK˜—š ‘ œŸœŸœ Ÿœ Ÿœ˜;šœŸœ& ˜OKšœ ˜5—Kšœ˜K˜—š ‘œŸœŸœ ŸœŸœŸœ˜VšŸœŸœŸ˜KšœŸœ/Ÿœ˜šœŸœ˜ Kšœ-™-—šœ%˜%Kšœ™—K˜—Kšœ˜—šŸœ˜ šœŸœ˜ Kšœ-™-—šœ.˜.K™4—šœ%˜%Kšœ™—K˜——Kšœ Ÿœ *˜:Lš’@™@šœ˜Kšœ+Ÿœ ˜MKšŸœŸœŸœ ˜)Kšœ  .˜;K˜—KšŸœ˜—Kš€˜šŸœ Ÿœ˜šŸœ,ŸœŸœŸ˜MKšŸœŸœŸœ˜4KšŸœ˜—K˜—Kš€˜KšœŸœ˜Kšœ˜Kšœ˜K˜—š ‘ œŸœŸœ ŸœŸœŸœ˜GKšœ7Ÿœ™;KšœKŸœ™Ξš ‘œŸœŸœ ŸœŸœ˜MKšŸœ-Ÿœ˜HKšœŸœ:˜JKšœ˜—Kšœ*˜*Kšœ˜Kšœ˜Kšœ˜——š’™š œŸœŸ œŸœŸœŸœŸœ˜4KšœBŸœ™IKšœP™P—K˜š‘œŸœŸœ˜$Kšœ;˜;Kšœ˜K˜—Kšœ Ÿœ˜K˜š‘ œŸœŸœ Ÿœ$Ÿœ ŸœŸœŸœ˜wKšœ™Kšœ ŸœŸœ˜9KšœŸœ˜$KšœŸœŸœ˜(KšŸœŸœŸœ˜Kš€˜KšŸœŸœ˜.KšœŸœ˜KšœŸ œ˜9Kšœ˜—K˜š ‘ œŸœŸœŸœŸœ˜2K™DKšŸœŸœŸœŸœ˜Kšœ˜K˜—š‘œŸœŸœ Ÿœ$ŸœŸœŸœ˜‡KšœŸœŸœ˜.KšŸœ#Ÿœ˜>KšœŸœ˜+K˜K˜—š ‘œŸœŸœŸœŸœ˜KKšœ(˜(K˜——š’™Kšœ Ÿœ™KšœŸœ™šœŸœŸœ ™9Kšœ™Kšœ Ÿ™Kšœ™K™—K™KšœŸœŸœŸœŸœŸœŸœ ŸœŸœ Ÿœ™‰K™š‘œŸœŸœ™'Kšœ,™,Kš œŸœŸœŸœŸœ™3Kšœ™Kšœ™K™——š’™Kšœ Ÿœ˜Kš œŸœŸœŸœ Ÿœ˜EK˜š‘œŸœŸœ˜#Kšœ,˜,Kšœ˜šŸœŸœŸœŸ˜KšœŸœ˜-KšŸœ˜—K˜K˜—š‘œŸœŸœŸœ˜?šŸœŸœŸœŸ˜šŸœŸœŸœ˜KšœŸœ˜Kšœ˜KšŸœ˜Kšœ˜—KšŸœ˜—Kšœ˜Kšœ˜K˜—š‘œŸœŸœ˜3šŸœŸœŸœŸ˜šŸœŸœ˜KšœŸœ˜KšŸœ˜Kšœ˜—KšŸœ˜—Kšœ˜Kšœ˜——š’™KšœŸœŸœŸœ ˜CKšœ ŸœŸœ˜KšœŸœŸœŸœ ˜:K˜š‘œŸœ Ÿœ Ÿœ˜/KšœŸœ"˜/Kšœ ŸœŸœ8˜^Kšœ Ÿœ&˜8KšŸœ;ŸœŸœ˜KKšœ˜K˜—š‘ œŸœŸœO˜hK˜—š‘œŸœŸœ Ÿœ˜6Kšœ Ÿœ  )˜AKšœ#˜#KšœŸœ ˜%Kšœ˜—K˜š‘œŸœŸœ˜.Kšœ/™/Kšœ!˜!KšŸœŸœ!Ÿœ˜QKšœ˜Kšœ˜K˜—Kš‘œŸœŸœ3˜EK˜Kšœϋ™ϋK˜š‘œŸœŸœŸœ˜4KšœT™TKšœŸœŸœ ™6—K˜š‘ œŸœ˜Kšœ˜Kšœ˜K˜—š‘ œŸœ Ÿœ1˜UšœŸœŸœŸ˜Kšœ˜Kšœ Ÿœ%˜8Kšœ Ÿœ:˜MKšœ Ÿœ%˜8Kšœ Ÿœ%˜8Kšœ Ÿœ&˜9Kšœ Ÿœ#˜6Kšœ Ÿœ&™9Kšœ Ÿœ6˜IKšœ Ÿœ$˜7Kšœ Ÿœ)˜ŸœŸœŸœ?˜ΓKšŸœ#Ÿœ˜.Kšœ˜K˜—š‘œŸœ˜ Kš‘ œŸœŸ œ‘œŸœŸ œŸ œŸœŸœ9˜šKšœ<™K™—šœ/˜/KšœC™C—šœŸœ ˜.K™Y—šœ˜K™,—K˜K˜8K˜š’P™PK˜Kšœ Ÿœ#˜.KšŸœŸœ Ÿœ˜=KšŸœŸœŸœ˜7K˜šŸœ.Ÿœ˜6KšŸœ#Ÿœ)˜RKšœŸœ "˜0K˜K˜—Kšœ" ˜@K˜šŸœ"Ÿ˜,Kšœ!˜!Kšœ Ÿœ @˜UKšŸœ˜—K˜K˜+K˜#K˜.—Lš’M™MšŸ˜šœ Ÿœ˜)Kšœ™—KšœJ™JKšœA™AK˜šŸœŸ˜šŸœŸœŸœŸ˜AKšŸœŸœŸœ˜)KšŸœŸœ˜-KšŸœ˜—šŸœŸœ*˜JK™F—KšœŸœ˜KšœŸœ  ˜1KšŸœ˜—Lš’7™7K˜K˜K˜K˜šŸœŸ˜Kšœ@™@šŸœ# ˜CšŸœ˜Kšœ1™1KšœJ™JKšœ,˜,Kšœ Ÿœ!˜,KšŸœŸœ Ÿœ˜=Kšœ˜KšœŸœ  ˜1KšœŸœ˜KšŸœŸœŸœ˜7KšŸœŸœ˜/Kšœ˜—šŸœ˜Kšœ,˜,šŸ˜Kšœ@™@KšœŸœ˜KšœŸœ˜KšœŸœ  ˜1šŸœŸœŸ˜KšœŸœ˜KšœŸœ "˜Fšœ˜šŸœŸœ˜/Kšœ$™$—KšŸœ˜K˜—KšŸœ˜—KšŸœ˜—šœ Ÿœ1˜—šœ˜K™'—K˜Kšœ.˜.Kšœ/˜/Kšœ '˜@Kš œŸœŸœŸœŸœ3˜eKš œ ŸœŸœŸœŸœ4˜hK˜šœ/˜/KšœC™C—šœŸœ ˜.K™Y—šœ˜K™,—K˜K˜8K™K˜Kšœ"˜"Lš’>™>K˜šŸœ=ŸœŸœŸ˜^KšŸœŸœŸœŸœ˜;KšŸœ˜—Lš’"™"KšœŸœ˜Kšœ˜Kšœ#˜#Kšœ˜KšœŸœ ŸœŸœ˜MK˜K˜!Kšœ˜Lš’$™$K˜ šŸœ9ŸœŸœ˜]Kš’™Kšœ Ÿœ ˜;šœ=Ÿœ˜CKšœ6™6K™—K˜šŸœŸ˜Kšœ! &˜GKšŸœŸœ˜0Kšœ3˜3KšœŸœ  ˜1Kšœ Ÿœ˜KšŸœ˜—šœ Ÿœ"˜-Kšœ)™)—KšŸœŸœŸœ˜7K˜K˜šŸœŸ˜Kš’™K˜!šŸ˜Kšœ™KšœŸœ˜KšœŸœ $˜>KšœŸœ  ˜1KšŸœŸœŸœŸœ˜=KšŸœ˜—šœ Ÿœ1˜Kšœ <˜RKšœ B˜XKšœ9˜9Kšœ™šœ˜Kšœ4™4KšœŸœ Ÿœ ˜SK˜@K˜(Kšœ)Ÿœ˜MK˜&K˜KšŸœŸœ  *˜?Kšœ˜—Kšœ˜K˜——šœ˜KšœM™M—Kšœ2 ˜NKšœ ˜K˜Kšœ˜K˜——…—nΈj