*----------------------------------------------------------- Title[CpaProcess.mc...May 23, 1986 10:49:38 am PDT...Willie-Sue]; * Process/monitor opcodes, interrupt handler, and process scheduler. IfE[AltoMode, 0, , ER[Pilot-only.module--will.not.work.with.Alto, 1]]; *----------------------------------------------------------- % CONTENTS, by order of occurence ME Monitor Enter MRE Monitor Reenter MXD Monitor Exit and Depart MXW Monitor Exit and Wait NOTIFY Notify BCAST Broadcast REQUEUE Requeue Pop2ShortOrLong Pop 2 short or long pointers and load base registers Pop1ShortOrLong Pop 1 short or long pointer and load base register FetchCPFlags Fetch current process's flags FetchCPLink Fetch current process's link EnterFailed Put current process on ML queue ExitMonitor Unlock monitor and awaken process at head of ML queue NotifyWakeup Naked Notify of CV (awaken process or set wakeup waiting bit) WakeHead Awaken process at head of CV queue Requeue Move a process from a source queue to a destination queue CleanUpCVQueue Clean up CV queue that may have been left in a mess by Requeue IWDC Increment WDC, disable interrupts DWDC Decrement WDC, reenable interrupts as appropriate MesaFault Suspend current process and notify fault handler MesaReschedTrap Mesa Reschedule trap MesaInterrupt Notify naked CVs according to wakeups pending in NWW CheckForTimeouts Scan PSBs and awaken any that have timed out MesaReschedule Suspend current process and begin new one from Ready Queue % TopLevel; KnowRBase[RTemp0]; *----------------------------------------------------------- * Base registers. *----------------------------------------------------------- * PDA * Process data area -- permanently assigned * Following two registers must be IFU-addressable and mutually xor 1. BR[MLBR, 34]; * Monitor lock BR[CVBR, 35]; * Condition variable BR[RQBR, IP[PDA]]; * Ready queue -- know it is word 0 of the PDA *----------------------------------------------------------- * R register usage. *----------------------------------------------------------- * Permanently assigned: * CurrentPSB PsbHandle of Current PSB (accessible via RR/WR) * Note: PrincOps says this should be a PsbIndex, but * the D0 implementation says otherwise! * CurrentTime Current time, in ~50 ms ticks * TickCount Counter to divide down vertical field interrupts to make ticks * RTemp0-3 are used for general scratch purposes. * The remaining RTemps are assigned for specific purposes: RME[Process, RTemp4]; * -> PSB being worked on RME[DQBRReg, RTemp5]; * MemBase value for Requeue destination queue * B0=1 => Requeue has been done RME[SQBRReg, RTemp6]; * MemBase value for Requeue source queue * B15=1 => source queue is NIL RME[IndexMask, XferFlags]; * Constant mask for extracting Q.tail words * BRConst[XX] generates a constant to select base register XX, and with * bit 0 = 1 to set the Requeue flag in DQBRReg (if relevant). M[BRConst, Add[100000, LShift[IP[#1], 10]]C]; *----------------------------------------------------------- * Process/monitor data structures *----------------------------------------------------------- * Priority: TYPE = [0..7]; * Note: QueueHandles are either short or long pointers; the process/monitor * opcodes must distinguish between them by testing the stack depth. * QueueHandle: LONG POINTER TO Queue; * Note: a Queue has the same structure as the words containing * CV.tail, ML.tail, PSBL.next, and PSBF.cleanup. * Queue: TYPE = MACHINE DEPENDENT RECORD [ * reserved: [0..17B] _ 0, MC[Q.tail, 7774]; * tail: PsbIndex, * reserved: [0..3]; MC[shcQ.tail, 44]; * ShC for making Q.tail: LMask = 4, RMask = 2 * PsbHandle: TYPE = POINTER TO PSB; -- PDA-relative * PsbIndex: TYPE = [0..1024); * Invariant: for any given PSB, PsbHandle = 4*PsbIndex. * PSB: TYPE = MACHINE DEPENDENT RECORD [ MC[PSB.link, 0]; * Link: PsbLink, MC[PSB.flags, 1]; * flags: PsbFlags, MC[PSB.context, 2]; * context: POINTER, -- to Frame or StateVector MC[PSB.timeout, 3]; * timeout: CARDINAL ]; MC[sizePSB, 4]; * PsbLink: TYPE = MACHINE DEPENDENT RECORD [ MC[PSBL.failed, 100000]; * failed: BOOLEAN, -- monitor enter failed MC[PSBL.priority, 70000]; * priority: Priority, MC[PSBL.next, 7774]; * next: PsbIndex, * reserved: BIT _ 0, MC[PSBL.vector, 1]; * vector: BOOLEAN ]; -- context is StateVector * PsbFlags: TYPE = MACHINE DEPENDENT RECORD [ * available: [0..17B], -- software use MC[PSBF.cleanup, 7774]; * cleanup: PsbIndex, -- cleanup link MC[PSBF.waiting, 2]; * waiting: BOOLEAN, -- waiting on CV MC[PSBF.abort, 1]; * abort: BOOLEAN ]; -- abort at next wait * Condition: TYPE = MACHINE DEPENDENT RECORD [ * reserved: [0..17B] _ 0, MC[CV.tail, 7774], * tail: PsbIndex, MC[CV.abortable, 2]; * abortable: BOOLEAN, MC[CV.wakeup, 1]; * wakeup: BOOLEAN ]; -- wakeup waiting * MonitorLock: TYPE = MACHINE DEPENDENT RECORD [ * reserved: [0..17B] _ 0, MC[ML.tail, 7774]; * tail: PsbIndex, * available: BIT, -- currently unused MC[ML.locked, 1]; * locked: BOOLEAN ]; * StateVector: TYPE = MACHINE DEPENDENT RECORD [ MC[SV.stack, 0]; * stack: ARRAY[0..7] OF UNSPECIFIED, MC[SV.stkp, 16]; * breakbyte, stkp: BYTE, MC[SV.frame, 17]; * frame: ControlLink, MC[SV.data, 20]; * data: ARRAY[0..1] OF UNSPECIFIED ]; * FaultIndex: TYPE = [0..7]; * Process Data Area (PDA) format, relative to PDA base register * PDA: TYPE = MACHINE DEPENDENT RECORD [ * block: ARRAY [0..0) OF PSB, -- zero length, * -- enables PDA-relative addressing of PSBs MSC[PDA.ready, 0]; * ready: Queue, MSC[PDA.count, 1]; * count: CARDINAL, -- number of PSBs in PDA * MSC[PDA.timeout, 2]; * timeout: POINTER TO TimeoutVector, -- unused * available: ARRAY [3..7] OF UNSPECIFIED, MC[PDA.state, 10]; * state: ARRAY Priority OF POINTER TO StateVector, MC[PDA.interrupt, 20]; * interrupt: ARRAY [0..15] OF RECORD [ * Condition, UNSPECIFIED], MC[PDA.fault, 60]; * fault: ARRAY FaultIndex OF RECORD [ * Queue, Condition], MC[PDA.psbs, 100]; * psbs: ARRAY [0..count) OF PSB ]; * Other stuff * MC[TimerChanMask, 100000]; * This channel is used to run the 60-Hz timer * Must be sign bit *----------------------------------------------------------- IFUR[ME, 1, MLBR]; * MonitorEnter[m]; *----------------------------------------------------------- :IfMEP; StkP-1, Branch[.+2]; Stack&-1_ MD, Branch[.+1]; StkP+1, Call[Pop1ShortOrLong]; * MLBR_ m :Else; Call[Pop1ShortOrLong]; * MLBR_ m :EndIf; * ml _ Fetch[m]^; IF ~ml.locked THEN * BEGIN ml.locked _ TRUE; Store[m]^ _ ml; Push[TRUE]; END * ELSE EnterFailed[m]; Call[MeStats]; Fetch_ 0S, Call[TestMonitorLock]; LockMonitorAndExit: <> Stack_ (Store_ 0S)+1, DBuf_ Q; Call[MesStats]; IFUNext0; *----------------------------------------------------------- IFUR[MRE, 1, CVBR]; * MonitorReEnter[m, c]; *----------------------------------------------------------- :IfMEP; StkP-1, Branch[.+2]; Stack&-1_ MD, Branch[.+1]; StkP+1, Call[Pop2ShortOrLong]; * CVBR_ c, MLBR_ m :Else; Call[Pop2ShortOrLong]; * CVBR_ c, MLBR_ m :EndIf; * ml _ Fetch[m]^; * IF ml.locked THEN EnterFailed[m] ELSE ... Call[MreStats]; Fetch_ 0S, Call[TestMonitorLock]; * BEGIN CleanUpCVQueue[c]; * flags _ Fetch[@PDA.block[PSB].flags]^; flags.cleanup _ PsbNull; * Store[@PDA.block[PSB].flags]^ _ flags; T_ A0, MemBase_ CVBR, Call[CleanUpCVQueue]; T_ (CurrentPSB)+1, MemBase_ PDA; Fetch_ T, RTemp2_ NOT (IndexMask); RTemp2_ (RTemp2) AND MD; Store_ T, DBuf_ RTemp2, Branch[MRENoAbort, R even]; * abort is bit 15 * IF flags.abort THEN * BEGIN cond _ Fetch[c]^; IF cond.abortable THEN ProcessTrap[]; END; * ml.locked _ TRUE; Store[m]^ _ ml; Push[TRUE]; END; * RTemp0 contains cond (left over from CleanUpCVQueue). PD_ (RTemp0) AND (CV.abortable); Branch[.+2, ALU=0]; T_ sProcessTrap, Branch[SavePCAndTrap]; Nop; MRENoAbort: <> MemBase_ MLBR; Stack_ (Store_ 0S)+1, DBuf_ Q; Call[MresStats]; IFUNext0; *----------------------------------------------------------- TestMonitorLock: * Test and set monitor lock; shared by ME and MRE. * Enter: MD = monitor lock * Exit: if monitor is locked, goes to EnterFailed; otherwise: * RTemp1 = Q = monitor lock value, with ML.locked = TRUE. * StkP incremented (in preparation for pushing TRUE result) * Clobbers T *----------------------------------------------------------- Subroutine; T_ A0, RTemp1_ MD; T_ RTemp1_ (Store_ T)+(DBuf_ RTemp1)+1, * Dirty monitor, set ML.locked Branch[EnterFailed, R odd]; * Branch if already locked Q_ T, StkP+1, Return; *----------------------------------------------------------- IFUR[MXD, 1, MLBR]; * MonitorExitAndDepart[m]; *----------------------------------------------------------- :IfMEP; StkP-1, Branch[.+2]; Stack&-1_ MD, Branch[.+1]; StkP+1, Call[Pop1ShortOrLong]; * MLBR_ m :Else; Call[Pop1ShortOrLong]; * MLBR_ m :EndIf; * IF ExitMonitor[m] THEN Reschedule[]; T_ A0, Call[ExitMonitor]; <> RTemp1_ ID; Call[MxdStats]; Branch[MesaReschedule1]; * IL=1 *----------------------------------------------------------- IFUR[MXW, 1, CVBR]; * MonitorExitAndWait[m, c, t]; *----------------------------------------------------------- T_ Stack&-1, Branch[MXWM2]; :IfMEP; T_ Stack&-1_ MD, Branch[.+1]; :EndIf; MXWM2: RTemp3_ T, Call[Pop2ShortOrLong]; * RTemp3_ t, CVBR_ c, MLBR_ m * CleanUpCVQueue[c]; ExitMonitor[m]; T_ A0, MemBase_ CVBR, Call[CleanUpCVQueue]; T_ A0, MemBase_ MLBR, Call[ExitMonitor]; * flags _ Fetch[@PDA.block[PSB].flags]^; cond _ Fetch[c]^; * IF ~(flags.abort AND cond.abortable) THEN ... MemBase_ PDA, Call[FetchCPFlags]; * Returns RTemp1=flags, T=@PSB.flags RTemp2_ T, MemBase_ CVBR; * Save @PSB.flags PD_ Fetch_ 0S, RTemp1, Branch[.+2, R even]; * Fetch[c], test flags.abort PD_ (Add[CV.abortable!]S) AND MD; T_ A0, RTemp0_ MD, * RTemp0_ cond Branch[.+2, ALU=0]; * Branch if PSB not aborting or CV not abortable RTemp1_ ID, Branch[MesaReschedule1]; * Aborting, don't wait * IF cond.wakeup THEN * BEGIN cond.wakeupWaiting _ FALSE; Store[c]^ _ cond; END ... <> <> <> RTemp0_ (RTemp0) AND NOT (CV.wakeup), Branch[mxw3, R even]; * cond.wakeup is bit 15 Store_ T, DBuf_ RTemp0; Call[MxwStats]; Branch[MesaReschedule]; mxw3: * ... ELSE BEGIN * Store[@PDA.block[PSB].timeout]^ _ IF t=0 THEN 0 ELSE MAX[1, ticks+t]; * flags.waiting _ TRUE; Store[@PDA.block[PSB].flags]^ _ flags; * Requeue[src: @PDA.ready, dst: c, psb: PSB]; END; T_ RTemp3, MemBase_ PDA; * RTemp3 = time to wait (0 = infinity) T_ (CurrentTime)+T, Branch[.+3, ALU=0]; PD_ T-1; * Generates carry unless T=0 RTemp3_ T+1, XorSavedCarry; T_ (RTemp1) OR (PSBF.waiting); Store_ RTemp2, DBuf_ T; * Store updated flags T_ (CurrentPSB)+(PSB.timeout); DQBRReg_ BRConst[CVBR]; * dst = c SQBRReg_ BRConst[RQBR]; * src = ready Store_ T, DBuf_ RTemp3, Branch[Requeue&Resched]; *----------------------------------------------------------- IFUR[NOTIFY, 1, CVBR, N[1]]; * Notify[c]; IFUR[BCAST, 1, CVBR, N[0]]; * Broadcast[c]; *----------------------------------------------------------- :IfMEP; StkP-1, Branch[.+2]; Stack&-1_ MD, Branch[.+1]; RTemp3_ ID-1, StkP+1, Call[Pop1ShortOrLong]; * CVBR_ c :Else; RTemp3_ ID-1, Call[Pop1ShortOrLong]; * CVBR_ c :EndIf; * This code is shared by NOTIFY and BCAST. * RTemp3 = 0 if NOTIFY, -1 if BCAST. * CleanUpCVQueue[c]; * WHILE (cond _ Fetch[c]).tail # PsbNull DO * WakeHead[c]; requeue _ TRUE; ENDLOOP; * IF requeue THEN Reschedule[]; * Note that we actually call CleanUpCVQueue every iteration. * This is ok since it is an idempotent operation. <> Branch[bcs, R < 0], RTemp3; Nop; Call[NotifyStats]; Branch[BroadcastLoop]; bcs: Call[BCastStats]; Nop; BroadcastLoop: T_ A0, Call[CleanUpCVQueue]; * Returns Process = ALU = c.tail RTemp3_ (RTemp3)+1, Branch[.+2, ALU=0]; RTemp3_ (RTemp3)-1, Call[WakeHead]; MemBase_ CVBR, RTemp3, Branch[BroadcastLoop, R<0]; RTemp1_ ID, Branch[MesaReschedule1]; *----------------------------------------------------------- IFUR[REQUEUE, 1, CVBR]; * Requeue[sq, dq, psb]; *----------------------------------------------------------- T_ Stack&-1, Branch[REQUEUEM2]; * PSB handle :IfMEP; T_ Stack&-1_ MD, Branch[.+1]; :EndIf; REQUEUEM2: Process_ T, Call[Pop2ShortOrLong]; * CVBR_ dq, MLBR_ sq * Dirty both queues so that if a write-protect fault is to occur, * it will occur now. SQBRReg_ T-T-1, Branch[ReQSQNil, ALU=0]; * Branch if sq=NIL T_ Fetch_ 0S; SQBRReg_ BRConst[MLBR]; * sq in MLBR Store_ T, DBuf_ MD, FlipMemBase, Branch[.+2]; ReQSQNil: T_ A0, FlipMemBase; Fetch_ T, DQBRReg_ BRConst[CVBR]; * dq in CVBR <> <<>> Store_ T, DBuf_ MD; Call[ReqStats]; Branch[Requeue&Resched]; *----------------------------------------------------------- Pop2ShortOrLong: * Pop two short or long pointers and load base registers. * Enter: MemBase, MemBase xor 1 = first and second BRs to be loaded * TIOA=0 * Exit: MemBase = entering MemBase xor 1 * Both BRs loaded * T = ALU = 0 iff second pointer is NIL * DQBRReg >= 0 (i.e., Requeue flag initialized to 0, if relevant) * IndexMask initialized * Clobbers RTemp0, DQBRReg *----------------------------------------------------------- Subroutine; DQBRReg_ TIOA&StkP; * Assume emulator's TIOA=0 ! DQBRReg_ (DQBRReg)-(2C); IndexMask_ And[Q.tail!, 177400]C, Branch[.+2, ALU=0]; * StkP=4: long pointer, pop high part off stack. T_ A0, BRHi_ Stack&-1, Branch[.+2]; * StkP=2: short pointer, lengthen it with high part of MDS. T_ (BRHi_ MDSHi)-(MDSHi)-1; BRLo_ Stack&-1; * Now ready to pop second pointer. * DQBRReg=2 and T=0 if first pointer was long; DQBRReg=0 and T=-1 if short. * Adjust DQBRReg to 1 in latter case. DQBRReg_ (DQBRReg)-T, FlipMemBase, Branch[Pop1Tail]; *----------------------------------------------------------- Pop1ShortOrLong: * Pop one short or long pointer and load base register. * Enter: MemBase = BR to be loaded * TIOA=0 * Exit: MemBase unchanged * BR loaded * T = ALU = 0 iff pointer is NIL * DQBRReg >= 0 (i.e., Requeue flag initialized to 0, if relevant) * IndexMask initialized * Clobbers RTemp0, DQBRReg *----------------------------------------------------------- Subroutine; IndexMask_ And[Q.tail!, 177400]C; DQBRReg_ TIOA&StkP; * Assume emulator's TIOA=0 ! Pop1Tail: PD_ (DQBRReg)-(2C); IndexMask_ (IndexMask) OR (And[Q.tail!, 377]C), Branch[.+2, ALU#0]; * StkP=2: long pointer, pop high part off stack. T_ BRHi_ Stack&-1, Branch[.+2]; * StkP=1: short pointer, lengthen it with high part of MDS. T_ A0, BRHi_ MDSHi, Branch[Pop1StackErr, Carry]; T_ (BRLo_ Stack&-1) OR T, Return; * Opcode was not called with minimal stack; generate stack error trap. TopLevel; Pop1StackErr: Branch[StackError]; *----------------------------------------------------------- FetchCPFlags: * Fetches current process's flags. * Enter: MemBase = PDA * CurrentPSB set up * Exit: RTemp1 = CurrentPSB^.flags * T = pointer to CurrentPSB^.flags * Process = CurrentPSB *----------------------------------------------------------- Subroutine; T_ (CurrentPSB)+1; * Know PSB.flags is word 1 Process_ (Fetch_ T)-1, Branch[.+2]; *----------------------------------------------------------- FetchCPLink: * Fetches current process's link * Enter: MemBase = PDA * CurrentPSB set up * Exit: RTemp1 = CurrentPSB^.link * T = pointer to CurrentPSB^.link * Process = CurrentPSB *----------------------------------------------------------- Subroutine; T_ Process_ Fetch_ CurrentPSB; * Know that link is word 0 RTemp1_ MD, Return; *----------------------------------------------------------- EnterFailed: * Enter: MLBR points to monitor lock * Exit: Does not return but rather goes directly to MesaReschedule *----------------------------------------------------------- TopLevel; * link _ Fetch[@PDA.block[PSB].link]^; link.failed _ TRUE; * Store[@PDA.block[PSB].link]^ _ link; * Requeue[src: @PSB.ready, dst: m, psb: PSB]; MemBase_ PDA, Call[FetchCPLink]; * RTemp1_ PSB.link, T_ @PSB.link RTemp1_ (RTemp1) OR (PSBL.failed); Store_ T, DBuf_ RTemp1; DQBRReg_ BRConst[MLBR]; * dst = ML SQBRReg_ BRConst[RQBR]; * src = ReadyQ Process_ T, Branch[Requeue&Resched]; * T = CurrentPSB *----------------------------------------------------------- ExitMonitor: * Unlocks monitor, and moves process at head of monitor lock queue to * ready queue, if there is one. * Enter: T = 0 * MemBase = MLBR * MLBR = QueueHandle for MonitorLock * Clobbers T, RTemp0, RTemp1, RTemp2, Process, MemBase *----------------------------------------------------------- Subroutine; * ml _ Fetch[m]^; ml.locked _ FALSE; Store[m]^ _ ml; * IF ml.tail # PsbNull THEN ... Fetch_ T; RTemp1_ MD, T_ (IndexMask) AND MD; RTemp1_ (RTemp1) AND NOT (ML.locked), Branch[.+2, ALU#0]; Store_ T, DBuf_ RTemp1, Return; * BEGIN link _ Fetch[@PDA.block[mon.tail].link]^; * Requeue[src: m, dst: @PDA.ready, psb: link.next]; END; Store_ 0S, DBuf_ RTemp1; MemBase_ PDA; Fetch_ T, DQBRReg_ BRConst[RQBR]; * dst = ReadyQ SQBRReg_ BRConst[MLBR]; * src = ML T_ (IndexMask) AND MD; Process_ Fetch_ T, Branch[Requeue1]; *----------------------------------------------------------- NotifyWakeup: * Wakes the process at the head of the CV queue if there is one; * sets the wakeup waiting bit in the CV if there isn't. * Enter: CVBR = pointer to CV * IndexMask initialized * Exit: ALU#0 (for the benefit of MesaInterrupt) * Clobbers T, RTemp0, RTemp1, RTemp2, Process, SQBRReg, DQBRReg, MemBase *----------------------------------------------------------- Subroutine; RTemp2_ Link; TopLevel; * CleanupCondition[c]; * IF (cond _ Fetch[c]^).tail = PsbNull THEN * BEGIN cond.wakeup _ TRUE; Store[c]^ _ cond; END * ELSE WakeHead[c]; T_ A0, MemBase_ CVBR, Call[CleanUpCVQueue]; T_ (RTemp0) OR (CV.wakeup), Branch[.+2, ALU=0]; Link_ RTemp2, Branch[WakeHead]; * CV queue non-empty, awaken head Link_ RTemp2; * CV queue empty, set wakeup waiting Subroutine; Store_ 0S, PD_ DBuf_ T, Return; *----------------------------------------------------------- WakeHead: * Wakes the process at the head of the CV queue (there must be one). * Enter: CVBR = pointer to CV * Process = CV.tail * IndexMask initialized * Exit: MemBase = PDA * ALU#0 (for the benefit of MesaInterrupt) * Clobbers T, RTemp0, RTemp1, RTemp2, Process, SQBRReg, DQBRReg *----------------------------------------------------------- Subroutine; T_ Process, MemBase_ PDA; * link _ Fetch[@PDA.block[cond.tail].link]^; Fetch_ T, * PSB.link is word 0 DQBRReg_ BRConst[RQBR]; * dst = ReadyQ Process_ (IndexMask) AND MD; * flags _ Fetch[@PDA.block[link.next].flags]^; flags.waiting _ FALSE; * Store[@PDA.block[link.next].flags]^ _ flags; * Store[@PDA.block[link.next].timeout]^ _ 0; T_ (Process)+(PSB.flags); RTemp0_ (Fetch_ T)+(Sub[PSB.timeout!, PSB.flags!]C); Store_ RTemp0, DBuf_ 0C, RTemp0_ MD; RTemp0_ (RTemp0) AND NOT (PSBF.waiting); Store_ T, DBuf_ RTemp0; * Requeue[src: c, dst: ready, psb: link.next]; SQBRReg_ BRConst[CVBR]; * src = CV Fetch_ Process, Branch[Requeue1]; *----------------------------------------------------------- Requeue: * Moves a process from a source queue to a destination queue. * Enter: SQBRReg = MemBase value for BR containing source QueueHandle; * bit 15 = 1 iff source QueueHandle is NIL, meaning the caller * doesn't know what queue the process is on. * DQBRReg = MemBase value for BR containing destination QueueHandle * Process = PSB to be requeued * MemBase = PDA * IndexMask initialized * Exit: MemBase = PDA * ALU#0 (for benefit of MesaInterrupt) * Clobbers T, RTemp0, RTemp1, RTemp2, ShC *----------------------------------------------------------- Subroutine; * Here we compute "prev", the predecessor of process, and put it in RTemp1. * If psb.link points to itself, queue will be empty after psb is removed. * IF src#NIL THEN queue _ Fetch[src]^; * link _ Fetch[@PDA.block[psb].link]^; * IF link.next = psb THEN prev _ PsbNull ... Fetch_ Process, Global; Requeue1: RTemp1_ shcQ.tail; * Set shifter for Q.tail masking RTemp0_ T_ (IndexMask) AND MD; RTemp2_ MD, PD_ (Process)#T; * RTemp2 = link, will be used later RTemp1_ A0, ShC_ RTemp1, Branch[RequeueGotPrev, ALU=0]; * Search for predecessor. Start at psb if sq is NIL, else at sq.tail. * ELSE BEGIN prev _ IF sq=NIL THEN psb ELSE queue.tail; * DO temp _ Fetch[@PDA.block[prev].link]^; IF temp.next = psb THEN EXIT; * prev _ temp.next; ENDLOOP; * temp.next _ link.next; Store[@PDA.block[prev].link]^ _ temp; END; MemBase_ SQBRReg, Branch[.+2, R odd]; * Branch if src=NIL Fetch_ 0S; * queue _ Fetch[src]^ T_ (IndexMask) AND MD, MemBase_ PDA; * T = psb.next or queue.tail RequeueSearchSQ: RTemp1_ Fetch_ T; * T = potential predecessor T_ (IndexMask) AND MD; PD_ (Process)#T; Branch[RequeueSearchSQ, ALU#0]; * Now RTemp1 has predecessor of process, and MD has its link word. * Bypass process in queue structure. T_ ShMDBothMasks[RTemp0]; * Insert RTemp0 = psb^.link.next Store_ RTemp1, DBuf_ T; * RTemp1 = predecessor of process, or 0 if there wasn't one. * RTemp0 = psb^.link.next. * IF src=NIL THEN * BEGIN flags _ Fetch[@PDA.block[psb].flags]^; flags.cleanup _ link.next; * Store[@PDA.block[psb].flags]^ _ flags; END; * ELSE IF queue.tail = psb THEN * BEGIN queue.tail _ prev; Store[src]^ _ queue; END; RequeueGotPrev: T_ A0, MemBase_ SQBRReg, Branch[.+3, R even]; * Branch if src#NIL T_ (Process)+1, MemBase_ PDA; * Know flags is word 1 RTemp1_ Fetch_ T, Branch[StoreQueueOrCleanup]; Fetch_ T, RTemp0_ RTemp1; * Fetch[src]^; RTemp0_ prev T_ (IndexMask) AND (MD); PD_ (Process)#T; RTemp1_ A0, Branch[BeginInsertDQ, ALU#0]; * In the following multi-purpose code, either: * MemBase = PDA, RTemp1 = @psb.flags, MD = psb.flags, and RTemp0 = link.next; * or: MemBase = SQBR, RTemp1 = 0, MD = src^, and RTemp0 = prev. StoreQueueOrCleanup: T_ ShMDBothMasks[RTemp0]; * Insert into Q.tail or flags.next Store_ RTemp1, DBuf_ T; * Now begin to insert process into dst queue. First, test for the easy case * in which the dst queue is initially empty. RTemp2 still contains psb^.link. * queue _ Fetch[dst]^; link _ Fetch[@PDA.block[psb].link]^; * IF queue.tail = PsbNull THEN * BEGIN link.next _ psb; Store[@PDA.block[psb].link]^ _ link; * queue.tail _ psb; Store[dst]^ _ queue; END; BeginInsertDQ: T_ A0, MemBase_ DQBRReg; RTemp0_ Fetch_ T; * Fetch[dst]^ RTemp1_ (IndexMask) AND MD; T_ ShMDBothMasks[Process], Branch[CheckDQTail, ALU#0]; Store_ RTemp0, DBuf_ T; * Queue empty, make it point to psb T_ Process, MemBase_ PDA, * Link psb to itself Branch[RequeueStorePSBLink]; * dst is non-empty. Begin by testing whether process's priority is <= * the priority of the current tail process, and if so just append process. * RTemp1 points to current tail process; RTemp2 = psb^.link. * ELSE BEGIN prev _ queue.tail; temp _ Fetch[@PDA.block[prev].link]^; * IF temp.priority >= link.priority THEN * BEGIN queue.tail _ psb; Store[dst]^ _ queue; END; CheckDQTail: RTemp0_ T, MemBase_ PDA; * In case psb becomes new tail (below) Fetch_ RTemp1, T_ PSBL.priority; RTemp2_ (RTemp2) AND T; * Extract psb^.link.priority T_ T AND MD; * Extract prev^.link.priority PD_ T-(RTemp2); T_ (IndexMask) AND MD, Branch[SearchDQLoop, ALU<0]; * Process priority <= tail priority. Just make process be new tail. T_ A0, MemBase_ DQBRReg; Store_ T, DBuf_ RTemp0; * Make queue point to psb MemBase_ PDA, Branch[RequeueStorePrevLink]; * Link it into the queue * Must search queue to find proper place to insert process. * Find prev = last process whose priority is >= that of psb. * At this point, RTemp1 points to tail process, and MD = its link word, * which is a pointer to the head process. T = temp.next. * ELSE DO next _ Fetch[@PDA.block[temp.next].link]^; * IF link.priority > next.priority THEN EXIT; * prev _ temp.next; temp _ next; ENDLOOP; SearchDQLoop: RTemp0_ Fetch_ T; T_ PSBL.priority; T_ T AND MD; PD_ (RTemp2)-T-1; T_ (IndexMask) AND MD, Branch[RequeueStorePrevLink, ALU>=0]; RTemp1_ RTemp0, Branch[SearchDQLoop]; * Now RTemp1 = prev = the process after which we are to insert. * link.next _ temp.next; Store[@PDA.block[psb].link]^ _ link; * temp.next _ psb; Store[@PDA.block[prev].link]^ _ temp; END; RequeueStorePrevLink: Fetch_ RTemp1; * Insert Process into prev^.link.next T_ ShMDBothMasks[Process]; Store_ RTemp1, DBuf_ T, T_ MD; * T_ former prev^.link * Insert T into psb^.link.next and return. RequeueStorePSBLink: RTemp0_ T, Fetch_ Process; * Know link is word 0 T_ ShMDBothMasks[RTemp0]; PD_ Store_ Process, DBuf_ T, Return; * ALU#0 *----------------------------------------------------------- CleanUpCVQueue: * Cleans up CV queue that may have been left in a mess by Requeue. * Enter: MemBase = CVBR * CVBR points to condition variable c * T = 0 * IndexMask initialized * Exit: MemBase = CVBR * ALU = Process = c^.tail (as modified by subroutine, if necessary) * RTemp0 = c^ (modified, if necessary; tail field may not be valid) * Clobbers T, RTemp0, RTemp1 *----------------------------------------------------------- Subroutine; * If CV queue is empty then do nothing. * cond _ Fetch[c]^; psb _ cond.tail; IF psb # PsbNull THEN ... T_ IndexMask, Fetch_ T, Global; * Force write-protect fault to occur here if it's going to occur at all. Store_ 0S, Process_ DBuf_ MD; Process_ T_ (Process) AND T, MemBase_ PDA; T_ T+1, RTemp0_ MD, Branch[.+2, ALU#0]; * RTemp0_ cond CleanupCVReturn: PD_ Process, MemBase_ CVBR, Return; * Queue is empty, nothing to do * If head PSB's cleanup link is nil then do nothing. * BEGIN flags _ Fetch[@PDA.block[psb].flags]^; * IF flags.cleanup # PsbNull THEN ... T_ IndexMask, Fetch_ T; * psb^.flags -- know flags are word 1 RTemp0_ (RTemp0) AND NOT T; * cond.tail _ PsbNull (for later) PD_ (IndexMask) AND MD; SearchForCVQHead: T_ (IndexMask) AND MD, Branch[.+2, ALU#0]; * T_ psb^.flags.cleanup PD_ Process, MemBase_ CVBR, Return; * No cleanup queue, nothing to do * Process has a psb to check, and ALU = T = psb^.flags.cleanup. * Follow cleanup links until we either hit NIL or * find a PSB with a cleanup link pointing to itself. * BEGIN DO IF flags.cleanup = psb then * BEGIN cond.wakeup _ FALSE; cond.tail _ PsbNull; Store[c]^ _ cond; * RETURN; END; * psb _ flags.cleanup; flags _ Fetch[@PDA.block[psb].flags]^; * IF flags.cleanup = PsbNull THEN EXIT; * ENDLOOP; PD_ (Process)#T, Branch[FoundCVQHead, ALU=0]; T_ T+1, Branch[MakeCVQEmpty, ALU=0]; Process_ (Fetch_ T)-1, Branch[SearchForCVQHead]; * ALU#0 guaranteed * Found a PSB with a cleanup link pointing to itself. This can happen * if a PSB is Requeued when it is the only PSB on the CV queue. * Reset the CV queue to empty. MakeCVQEmpty: RTemp0_ (RTemp0) AND NOT (CV.wakeup); Process_ T_ A0, Branch[StoreCVQueue]; * Found a PSB with a NIL cleanUp link, and not the first in the cleanUp queue. * Put it at the head of the CV queue. Since queues are circular and rooted * through their tail, this requires chasing down the queue to find the tail. * head _ psb; * DO link _ Fetch[@PDA.block[psb].link]^; IF link.next = head THEN EXIT; * psb _ link.next; ENDLOOP; FoundCVQHead: RTemp1_ T_ Fetch_ Process; * Remember head, fetch psb^.link SearchForCVQTail: Process_ T; T_ (IndexMask) AND MD; * T_ psb^.link.next PD_ (RTemp1)#T; Fetch_ T, Branch[SearchForCVQTail, ALU#0]; * Process = new tail PSB; RTemp0 = cond with tail field zeroed out. * cond.tail _ psb; Store[c]^ _ cond; END; END; T_ Process; StoreCVQueue: RTemp0_ (RTemp0) OR T, MemBase_ CVBR; Store_ 0S, DBuf_ RTemp0, Branch[CleanupCVReturn]; *----------------------------------------------------------- IFUR[IWDC, 1, L]; * Increment Wakeup Disable Counter: wdc _ wdc+1; *----------------------------------------------------------- T_ (WDC)+1, Branch[CheckValidWDC]; :IfMEP; Stack_ MD, T_ (WDC)+1, Branch[CheckValidWDC]; StkP+1, Branch[.-2]; :EndIf; *----------------------------------------------------------- IFUR[DWDC, 1, L]; * Decrement Wakeup Disable Counter: wdc _ wdc-1; * Note: one instruction must be executed after DWDC before an interrupt may occur. *----------------------------------------------------------- PD_ NWW, NoReschedule, DblBranch[DWDCRescPend, DWDCNoRescPend, Reschedule]; :IfMEP; Stack_ MD, Branch[.-1]; StkP+1, Branch[.-2]; :EndIf; * If a Reschedule was already pending, we must restart the IFU in order to * prevent a reschedule trap from occurring on the next IFUJump, even though * we have already done a NoReschedule. DWDCRescPend: T_ (ID)-(PCX')-1; PD_ NWW, PCF_ T; DWDCNoRescPend: T_ (WDC)-1, Branch[.+2, ALU=0]; * Any interrupt requests in? * Actually, should Reschedule only if WDC=0 now, but incrementing WDC * more than once is uncommon, and MesaReschedTrap will sort this out anyway. * Note that Reschedule doesn't take effect until the second successful IFUJump. PD_ T, Reschedule; CheckValidWDC: Branch[.+2, ALU<0]; WDC_ T, IFUNext0; * Normal exit T_ sWakeupError, Branch[SavePCAndTrap]; * Over-incremented/decremented *----------------------------------------------------------- MesaFault: * Suspend current process and invoke fault handler * Enter: T = FaultVector offset = 2*FaultIndex for fault handler * FaultParam0, FaultParam1 = fault parameters, if any * Exit: Puts current process on fault queue, awakens handler, and * performs a Reschedule. *----------------------------------------------------------- TopLevel; Q_ XferFlags, * Preserve XferFlags (=IndexMask) Branch[.+2, R<0]; * Branch if xf.invalidContext RestoreStkP; * Requeue[dst: @PDA.fault[fi].queue, src: @PDA.ready, psb: PSB]; MemBase_ MLBR; RTemp0_ T+(PDA.fault); RTemp1_ PDAHi; RTemp0_ (BRLo_ RTemp0)+1; * MLBR_ @PDA.fault[fi].queue BRHi_ RTemp1; T_ CurrentPSB, MemBase_ CVBR; * Process_ PSB to requeue BRLo_ RTemp0; * CVBR_ @PDA.fault[fi].condition BRHi_ RTemp1; DQBRReg_ BRConst[MLBR]; * Destination is fault queue SQBRReg_ BRConst[RQBR]; * Source is ready queue IndexMask_ And[Q.tail!, 177400]C; IndexMask_ (IndexMask) OR (And[Q.tail!, 377]C); Process_ T, MemBase_ PDA, Call[Requeue]; * Move CurrentPSB to fault queue * NotifyWakeup[@PDA.fault[fi].condition]; * PC _ savedPC; -- done by hardware * SP _ savedSP; -- done by RestoreStkP (above) * Reschedule[preemption: TRUE]; Call[NotifyWakeup]; * CVBR points to CV already XferFlags_ Q, Branch[NoMoreInts]; * Recover XferFlags, go preempt current process *----------------------------------------------------------- * These are exception entries for the Mesa instruction set *----------------------------------------------------------- Set[MesaTrapBase, Sub[300, LShift[MesaInsSet, 6]]]; DontKnowRBase; TopLevel; MesaIFUNotReady: At[MesaTrapBase, 34], IFUNext0; :IfMEP; At[MesaTrapBase, 35], T_ Stack&-1_ MD, IFUNext2; At[MesaTrapBase, 36], IFUNext2; :EndIf; *----------------------------------------------------------- * Reschedule trap and interrupt processing *----------------------------------------------------------- MesaReschedTrap: At[MesaTrapBase, 14], RBase_ RBase[WDC], Branch[MesaResched1]; :IfMEP; At[MesaTrapBase, 15], Stack_ MD, RBase_ RBase[WDC], Branch[MesaResched1]; At[MesaTrapBase, 16], StkP+1, RBase_ RBase[WDC], Branch[MesaResched1]; :EndIf; * Immediately reset the Reschedule trap condition and restart the IFU, so * that if we subsequently decide there is nothing to do (either here or * later), we can simply exit with an IFUNext0. MesaResched1: PD_ WDC, NoReschedule; T_ NOT (PCX'), Branch[MesaIntDisabled, ALU#0]; T_ NWW, PCF_ T; * NWW#0 means interrupt pending MesaReschedTest: NWW_ (NWW) AND NOT T, * Clear interrupts we will process Branch[MesaInterrupt, ALU#0]; IFUNext0; * No interrupt, resume execution MesaIntDisabled: T_ A0, PCF_ T, Branch[MesaReschedTest]; *----------------------------------------------------------- MesaInterrupt: * Enter: T = interrupt request mask * CurrentPSB#0 if interrupted from normal emulation; CurrentPSB=0 if from idle loop. * WDC=0 *----------------------------------------------------------- * Make a special test for an interrupt occurring on the timer channel * (60 hz), and do CheckForTimeouts when one occurs. IndexMask_ And[Q.tail!, 177400]C; IndexMask_ (IndexMask) OR (And[Q.tail!, 377]C); WDC_ T; DQBRReg_ A0, MemBase_ PDA, Branch[CheckForTimeouts, ALU<0]; * For each interrupt that is pending, check the naked CV in the corresponding * slot in the PDA.interrupt array. * RTemp3 keeps the PDA-relative pointer, and WDC is used to shift the * interrupt request bits. * WW: ARRAY InterruptLevel OF BIT; * FOR level DECREASING IN InterruptLevel DO * IF WW[level]#0 THEN * requeue _ requeue OR NotifyWakeup[@PDA.interrupt[level].condition]; CheckNakedCVs: RTemp3_ Add[PDA.interrupt!, 40]C, Branch[CheckNakedCVLoop]; NakedCVWakeup: BRLo_ RTemp3, Call[NotifyWakeup]; * Returns with ALU#0 CheckNakedCVLoop: RTemp3_ (RTemp3)-(2C), Branch[NoMoreInts, ALU=0]; WDC_ (WDC) RSH 1, Branch[.-1, R even]; MemBase_ CVBR; * Interrupt for this CV T_ PDAhi; BRHi_ T, Branch[NakedCVWakeup]; * All interrupt requests have been processed, and WDC=0 again. * If we were interrupted from normal emulation (CurrentPSB#0), do a Reschedule iff * the interrupt actually caused any processes to be requeued. * If we were interrupted from the idle loop (CurrentPSB=0), go reconsider the ReadyQ. NoMoreInts: PD_ CurrentPSB, MemBase_ PDA; RTemp1_ A0, DblBranch[MesaReschedule1, IdleReschedule, ALU#0]; *----------------------------------------------------------- CheckForTimeouts: * This is invoked every time an interrupt is requested on the * 60 hz timer channel. This microcode "knows" which channel the * Mesa system enables for that purpose! * Enter: MemBase = PDA * Exit: MemBase = PDA * Clobbers T, RTemp0-3, Process, DQBRReg, SQBRReg *----------------------------------------------------------- * Scan PSBs only every third tick RBase_ RBase[TickCount]; TickCount_ (TickCount)-1, RBase_ RBase[RTemp0]; T_ Fetch_ PDA.count, Branch[.+2, ALU=0]; Branch[CheckNakedCVs]; * count _ Fetch[@PDA.count]^; * FOR psb IN [StartPsb..StartPsb+count) DO * timeout _ Fetch[@PDA.block[psb].timeout]^; * IF timeout#0 AND timeout=CurrentTime THEN * BEGIN flags _ Fetch[@PSB.block[psb].flags]^; * flags.waiting _ FALSE; Store[@PDA.block[psb].flags]^ _ flags; * Store[@PDA.block[psb].timeout]^ _ 0; * Requeue[src: 0, dst: @PDA.ready, psb:psb]; * END; ENDLOOP TickCount_ T+T+1; * = 3 (know PDA.count=1) CurrentTime_ (CurrentTime)+1; T_ Add[PDA.psbs!, PSB.timeout!]C, * Timeout word of first PSB RTemp3_ MD; * Use this to count PSBs * T points to PSB timeout word CheckTimerLoop: Process_ (Fetch_ T)-(PSB.timeout); PD_ MD; PD_ (CurrentTime)-MD, Branch[CanTimeout, ALU#0]; RTemp3_ (RTemp3)-1; NoTimeout: T_ (Process)+(Add[PSB.timeout!, sizePSB!]C), Branch[CheckTimerLoop, ALU#0]; Branch[CheckNakedCVs]; CanTimeout: RTemp3_ (RTemp3)-1, Branch[NoTimeout, ALU#0]; * This process has timed out. T = @psb.timeout RTemp0_ T+(Sub[PSB.flags!, PSB.timeout!]C); Fetch_ RTemp0; * Fetch psb.flags Store_ T, DBuf_ 0C, T_ MD; * psb.timeout _ 0 T_ T AND (Not[PSBF.waiting!]C); Store_ RTemp0, DBuf_ T; DQBRReg_ BRConst[RQBR]; * dst = ReadyQ SQBRReg_ T-T-1, Call[Requeue]; * src = NIL PD_ RTemp3, Branch[NoTimeout]; *----------------------------------------------------------- Requeue&Resched: * Do a Requeue followed by a Reschedule. * Arguments as for Requeue. *----------------------------------------------------------- MemBase_ PDA, Call[Requeue]; *----------------------------------------------------------- MesaReschedule: * PrincOps Reschedule operation * Also includes SaveProcess, LoadProcess, AllocState, FreeState, and * part of Fault, suitably flattened out into in-line code. * Process/monitor opcodes normally branch here when they are done. * Can get here at the end of interrupt processing also. * Enter: DQBRReg[0] = 1 iff a Requeue has been done. * Exit: Resumes execution of this or some other process. *----------------------------------------------------------- TopLevel; RTemp1_ ID; * RTemp1_ IL * Here RTemp1 = 0 if came from MesaInterrupt or MesaFault; = IL if came from * process/monitor opcode. Note that IL = 1 for all process/monitor opcodes; * therefore "RTemp1 even" is synonymous with "preemption". * IF ~reschedulePending THEN RETURN; MesaReschedule1: MemBase_ PDA, DQBRReg, Branch[.+2, R<0]; IFUNext0; * No Requeue, resume normal execution *----------------------------------------------------------- * Save state of current process. * Can get here due to a Fault, after requeueing CurrentPSB onto a fault queue. *----------------------------------------------------------- * link _ Fetch[@PDA.block[PSB].link]^; * IF ValidContext[] THEN StoreMDS[@LocalBase[L].pc]^ _ PC; * IF (link.vector _ preemption) THEN * BEGIN state _ AllocState[link.priority]; SaveStack[state]; * Store[@state.frame]^ _ L; Store[@PDA.block[PSB].context]^ _ state; END * ELSE Store[@PDA.block[psb].context]^ _ L; * Store[@PDA.block[PSB].link]^ _ link; * Validity of context is indicated by XferFlags[xf.invalidContext], which * is checked by SavePCInFrame. Call[SwitchStats]; Fetch_ CurrentPSB; * Know PSB.link is word 0 T_ MD, RTemp1_ (RTemp1)-(PCX')-1, * PCX or PCX+IL Branch[NotPreemption, R odd]; * Here to preempt current process (possibly due to a fault). * AllocState: need to allocate a StateVector. * state _ Fetch[@PDA.state[priority]]^; IF state=NIL THEN ERROR; * Store[@PDA.state[priority]]^ _ Fetch[state]^; * Careful not to clobber RTemp3 (=FaultParam1) until it is stored. * Must call SaveState before removing StateVector from list, because SaveState * might generate a StackError trap (during interrupts only, never during faults). RTemp2_ T OR (PSBL.vector); RTemp4_ LDF[T, 3, 14]; * Extract link.priority RTemp4_ (RTemp4)+(PDA.state); Fetch_ RTemp4, * Fetch head of state list Call[SavePCInFrame]; * Returns L in T RTemp0_ PD_ MD, MemBase_ PDA; * RTemp0_ head StateVector DLink_ T, Branch[NoStateVector, ALU=0]; * Branch if list empty Fetch_ RTemp0; * Fetch successor Call[SaveState]; * Save stack, StkP, DLink; doesn't clobber MD Store_ RTemp4, DBuf_ MD; * Store successor as head * The following is really part of FaultTwo rather than SaveProcess: * Store[@state.data[0]]^ _ LowHalf[parameter]; -- = FaultParam0 * Store[@state.data[1]]^ _ HighHalf[parameter]; -- = FaultParam1 T_ (Store_ T)+1, DBuf_ FaultParam0; Store_ T, DBuf_ FaultParam1, Branch[EndSaveProcess]; * Use RTemp0 = state as context NoStateVector: Breakpoint, Branch[.]; * No state vector for preempted process * Here if preemption not in progress: just save PC and use L as context. NotPreemption: RTemp2_ T AND NOT (PSBL.vector), Call[SavePCInFrame]; * Returns L in T RTemp0_ T, MemBase_ PDA; * Together again here. * Now RTemp0 = context (frame or state vector); RTemp2 = updated PSB.link. EndSaveProcess: T_ CurrentPSB; Store_ T, DBuf_ RTemp2; T_ T+(PSB.context); Store_ T, DBuf_ RTemp0; *----------------------------------------------------------- * Check ready queue for new process to run. * Can get here after saving current process (above), or after * an interrupt from BusyWait (below). * A process is runnable if either it already has a StateVector (i.e., the * process was preempted) or a free StateVector is available at the * process's priority level. *----------------------------------------------------------- * queue _ Fetch[@PDA.ready]^; IF queue.tail = PsbNull THEN GOTO BusyWait; * link _ Fetch[@PDA.block[queue.tail].link]^; * DO psb _ link.next; link _ Fetch[@PDA.block[psb].link]^; * IF link.vector OR ~EmptyState[link.priority] THEN EXIT; * IF psb = queue.tail THEN GOTO BusyWait; * ENDLOOP; * EXITS BusyWait => * BEGIN IF WDC#0 THEN RescheduleError[]; running _ FALSE; RETURN; END; IdleReschedule: Fetch_ PDA.ready; * Fetch queue IndexMask_ And[Q.tail!, 177400]C; IndexMask_ (IndexMask) OR (And[Q.tail!, 377]C), T_ MD; RTemp0_ Fetch_ T; * Fetch link word of tail PSB T_ (IndexMask) AND MD, FreezeBC; FindRunnableProcess: CurrentPSB_ Fetch_ T, Q_ T, * Fetch link word of next PSB Branch[BusyWait, ALU=0]; * Branch if queue empty or hit tail RTemp2_ MD; T_ LDF[RTemp2, 3, 14]; * T_ link.priority RTemp2_ T+(PDA.state), * See if StateVector is available Branch[LoadPreempted, R odd]; * Branch if link.vector=TRUE Fetch_ RTemp2, RTemp2_ T_ B_ MD; T_ (IndexMask) AND T; * T_ next PSB PD_ MD; PD_ (RTemp0)-Q, * See if CurrentPSB = queue.tail DblBranch[LoadNonPreempted, FindRunnableProcess, ALU#0]; * Ready queue is empty, or there are no runnable processes. * The only way out of this state is to get a naked notify (or a timeout) * on a CV that some process is waiting on. Therefore, spin here until * an interrupt request is raised. (This is our implementation of setting * the "running" flag to FALSE and returning.) BusyWait: PD_ WDC; T_ sWakeupError, Branch[.+2, ALU=0]; Branch[MTrap]; * It's an error to be idle with WDC#0 CurrentPSB_ A0; * Indicate we are idle T_ NWW, NoReschedule, Branch[., ALU=0]; NWW_ (NWW) AND NOT T, * Clear interrupts about to be caused Branch[MesaInterrupt]; * CurrentPSB=0 => will return to IdleReschedule *----------------------------------------------------------- * Run preempted process. * CurrentPSB = process to run; RTemp2 = @PDA.state[link.priority]. *----------------------------------------------------------- * state _ Fetch[@PDA.block[PSB].context]^ * LoadStack[state]; L _ Fetch[@state.frame]; * FreeState[link.priority, state]; LoadPreempted: T_ (CurrentPSB)+(PSB.context); Fetch_ T, * PSB.context = StateVector XferFlags_ xf.invalidContext; * xf.free_ 0, xf.push_ 0 RTemp0_ MD, Call[LoadStack]; * Load stack, StkP, DLink * FreeState: * Store[state]^ _ Fetch[@PDA.state[priority]]^; * Store[@PDA.state[priority]]^ _ state; T_ Fetch_ RTemp2; Store_ T, DBuf_ RTemp0, T_ MD; Store_ RTemp0, DBuf_ T, Branch[XferToNewProcess]; *----------------------------------------------------------- * Run non-preempted process. * CurrentPSB = Q = process to run; RTemp2 = PSB.link. *----------------------------------------------------------- * L _ Fetch[@PDA.block[PSB].context]; * IF link.failed THEN * BEGIN Push[FALSE]; link.failed _ FALSE; * Store[@PDA.block[psb].link]^ _ link; END; LoadNonPreempted: T_ (CurrentPSB)+(PSB.context); Fetch_ T, * PSB.context = frame XferFlags_ xf.invalidContext; * xf.free_ 0, xf.push_ 0 DLink_ MD, T_ RTemp2, Branch[XferToNewProcess, R>=0]; T_ T AND NOT (PSBL.failed), StkP+1; Store_ Q, DBuf_ T, Stack_ A0; * Store updated link; Push[FALSE] * Preempted and non-preempted cases together here. DLink = new local frame. * Set the new local frame now so that if a fault occurs during the * subsequent Xfer, the correct context is saved. XferToNewProcess: MemBase_ L; BRLo_ DLink; SLink_ A0, MemBase_ MDS, Branch[Xfer];