*----------------------------------------------------------- Title[AltoMesaProcess.mc...January 22, 1982 6:04 PM...Taft]; * Process/monitor opcodes, interrupt handler, and process scheduler. IfE[AltoMode, 0, ER[Alto-only.module--will.not.work.with.Pilot, 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 FetchCP&CPF Fetch current process and current process's flags SetReadyQBR Set up RQBR to point to the Ready Queue EnterFailed Put current process on ML queue ExitMonitor Unlock monitor and awaken process at head of ML queue 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 MesaIFUMapFault (etc.) IFU exception traps 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. * Following two registers must be IFU-addressable and mutually xor 1. BR[MLBR, 34]; * Monitor lock BR[CVBR, 35]; * Condition variable BR[RQBR, IP[ScratchBR]]; * Ready queue * R register usage. * 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 * 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]; * Ticks: TYPE = CARDINAL; * ProcessHandle: TYPE = LONG POINTER [0..77777B] TO PSB; * Queue: TYPE = LONG POINTER [0..77777B] TO PSB; * QueueHandle: TYPE = LONG POINTER TO Queue; * Note that the process/monitor opcodes actually accept either short or long * QueueHandles, and must distinguish among them by testing the stack depth. * PSB: TYPE = MACHINE DEPENDENT RECORD [ MSC[PSB.link, 0]; * Link: ProcessHandle, MSC[PSB.cleanUp, 1]; * cleanUp: ProcessHandle, MSC[PSB.timeout, 2]; * timeout: Ticks, MSC[PSB.flags, 3]; * flags: PsbFlags, MSC[PSB.frame, 4]; * frame: LocalFrameHandle ]; MC[SizePSB, 5]; * PsbFlags: TYPE = MACHINE DEPENDENT RECORD [ MC[PSBF.enterFailed, 100000]; * enterFailed: BOOLEAN, MC[PSBF.detached, 40000]; * detached: BOOLEAN, * fill: [0..37B], MC[PSBF.state, 600]; * state: {ready, taken, dead, alive}, * unused: BOOLEAN, MC[PSBF.abortPending, 40]; * abortPending: BOOLEAN, * unused: BOOLEAN, MC[PSBF.waitingOnCV, 10]; * waitingOnCV: BOOLEAN, MC[PSBF.priority, 7]; * priority: Priority ] * Condition: TYPE = MACHINE DEPENDENT RECORD [ * wakeupWaiting: {no, yes}, * queue: Queue ] * MonitorLock: TYPE = MACHINE DEPENDENT RECORD [ * lock: {locked, unlocked}, * queue: Queue ] * StateVector: TYPE = MACHINE DEPENDENT RECORD [ MC[SV.stack, 0]; * stack: ARRAY[0..7] OF UNSPECIFIED, MC[SV.stkp, 10]; * breakbyte, stkp: BYTE, MC[SV.dst, 11]; * dst: ControlLink, MC[SV.src, 12]; * src: ControlLink ]; * Microcode knows that SIZE[StateVector] = 13B !! * Fixed memory locations MC[CurrentPSB, 21]; MC[ReadyList, 22]; * MC[CurrentState, 23]; * Defined in DMesaDefs.mc MC[NakedCVArray, 40]; * Array of naked CVs (thru 57) Set[FirstPSBLoc, 1075]; * Pointer to first PSB Set[LastPSBLoc, 1076]; * Pointer to last PSB Set[FirstSVLoc, 1077]; * Pointer to first StateVector * Other constants -- unpublished but apparently widely known!! MC[TimerLoc, 344]; * Address of process timer cell * TimerLoc+1 counts timer interrupts per tick MC[TimerChanMask, 20]; * Timer interrupt channel *----------------------------------------------------------- IFUR[ME, 1, MLBR, N[2]]; * MonitorEnter[pMonitorLock]; *----------------------------------------------------------- :IfMEP; StkP-1, Branch[.+2]; Stack&-1_ MD, Branch[.+1]; StkP+1, Call[Pop1ShortOrLong]; * MLBR_ pMonitorLock :Else; Call[Pop1ShortOrLong]; * MLBR_ pMonitorLock :EndIf; * ml _ Fetch[m]^; * IF ml.lock = unlocked THEN * BEGIN ml.lock _ locked; Store[m]^ _ ml; Push[TRUE]; END * ELSE BEGIN EnterFailed[m]; ReSchedule[TRUE]; END; T_ Fetch_ RTemp0, Call[TestMonitorLock]; * RTemp0 = 0 here Stack+1_ (Store_ T)+1, DBuf_ Q, IFUNext0; *----------------------------------------------------------- IFUR[MRE, 1, CVBR, N[4]]; * MonitorReEnter[pMonitorLock, pCondition]; *----------------------------------------------------------- :IfMEP; StkP-1, Branch[.+2]; Stack&-1_ MD, Branch[.+1]; StkP+1, Call[Pop2ShortOrLong]; * CVBR_ pCondition, MLBR_ pMonitorLock :Else; Call[Pop2ShortOrLong]; * CVBR_ pCondition, MLBR_ pMonitorLock :EndIf; * ml _ Fetch[m]^; * IF ml.lock = unlocked THEN BEGIN ... ... END * ELSE EnterFailed[m]; ReSchedule[TRUE]; Fetch_ RTemp0, Call[TestMonitorLock]; * RTemp0 = 0 here * : * CleanUpCVQueue[c]; ml.lock _ locked; Store[m]^ _ ml; T_ A0, MemBase_ CVBR, Call[CleanUpCVQueue]; T_ A0, MemBase_ MLBR; Store_ T, DBuf_ Q; * Store[@cp.cleanUp]^ _ NIL; cpf _ Fetch[@cp.flags]; * if cpf.abortPending THEN Trap[sProcessTrap] ELSE Push[TRUE]; RTemp0_ A0, MemBase_ PDA, Call[FetchCP&CPF]; T_ (Process)+1; * @cp.cleanUp PD_ (RTemp1) AND (PSBF.abortPending); Store_ T, T_ DBuf_ RTemp0, Branch[.+2, ALU#0]; Stack+1_ T+1, IFUNext0; * Push[TRUE] and exit T_ sProcessTrap, Branch[SavePCAndTrap]; *----------------------------------------------------------- TestMonitorLock: * Test and set monitor lock; shared by ME and MRE. * Enter: monitor lock fetched * Exit: if monitor is locked, goes to EnterFailed; otherwise: * RTemp1 = Q = monitor lock value, with lock changed to "locked". *----------------------------------------------------------- Subroutine; RTemp1_ MD; RTemp1_ (RTemp1) AND (77777C), Branch[EnterFailed, R>=0]; Q_ RTemp1, Return; TopLevel; *----------------------------------------------------------- IFUR[MXD, 1, MLBR, N[2]]; * MonitorExitAndDepart[pMonitorLock]; *----------------------------------------------------------- :IfMEP; StkP-1, Branch[.+2]; Stack&-1_ MD, Branch[.+1]; StkP+1, Call[Pop1ShortOrLong]; * MLBR_ pMonitorLock :Else; Call[Pop1ShortOrLong]; * MLBR_ pMonitorLock :EndIf; * ExitMonitor[m]; Reschedule[TRUE]; T_ A0, Call[ExitMonitor]; * Returns with T=0, RTemp1=ml RTemp1_ (Store_ T)+1, DBuf_ RTemp1, Branch[MesaReschedule1]; * IL=1 *----------------------------------------------------------- IFUR[MXW, 1, CVBR, N[4]]; * MonitorExitAndWait * [pMonitorLock, pCondition, ticks]; *----------------------------------------------------------- T_ Stack&-1, Branch[MXWM2]; :IfMEP; T_ Stack&-1_ MD, Branch[.+1]; :EndIf; MXWM2: RTemp3_ T, Call[Pop2ShortOrLong]; * RTemp3_ ticks, * CVBR_ pCondition, MLBR_ pMonitorLock * [] _ Fetch[m]^; CleanUpCVQueue[c]; ExitMonitor[m]; T_ Fetch_ RTemp0, FlipMemBase, Call[CleanUpCVQueue]; T_ A0, MemBase_ MLBR, Call[ExitMonitor]; Store_ T, DBuf_ RTemp1; * cp _ Fetch[currentPSB]^; cpf _ Fetch[@cp.flags]^; * IF ~cpf.abortPending THEN ... MemBase_ PDA, Call[FetchCP&CPF]; PD_ (RTemp1) AND (PSBF.abortPending); RTemp0_ A0, MemBase_ CVBR, Branch[.+2, ALU=0]; RTemp1_ ID, Branch[MesaReschedule1]; * BEGIN cv _ Fetch[c]^; * IF cv.wakeupWaiting = yes THEN * BEGIN cv.wakeupWaiting _ no; Store[c]^ _ cv; END ... Fetch_ RTemp0, RTemp0_ T; * RTemp0_ @cp.flags RTemp2_ MD, T_ PSBF.waitingOnCV; RTemp2_ (RTemp2) AND (77777C), Branch[.+2, R>=0]; Store_ 0S, DBuf_ RTemp2, Branch[MesaReschedule]; * ... ELSE BEGIN * cpf.waitingOnCV _ TRUE; Store[@cp.flags]^ _ cpf; * IF ticks#0 THEN * BEGIN ticks _ currentTime+ticks; IF ticks=0 THEN ticks_1; END; * Store[@cp.timeout]^ _ ticks; * Requeue[src: ready, dst: c, process: cp]; * END; T_ (RTemp1) OR T, MemBase_ PDA; Store_ RTemp0, DBuf_ T; SQBRReg_ BRConst[RQBR], Call[SetReadyQBR]; * src = ReadyQ MemBase_ PDA; DQBRReg_ BRConst[CVBR], Call[Requeue]; * dst = CV T_ TimerLoc; Fetch_ T, PD_ RTemp3; RTemp3_ (RTemp3)+MD, Branch[ZeroTimeout, ALU=0]; T_ (PSB.timeout)+(Process), Branch[.+2, ALU#0]; RTemp3_ (RTemp3)+1; Store_ T, DBuf_ RTemp3; * If supplied ticks = 0, don't need to store timeout at all, since * Requeue zeroed it. ZeroTimeout: RTemp1_ ID, Branch[MesaReschedule1]; *----------------------------------------------------------- IFUR[NOTIFY, 1, CVBR, N[2]]; * Notify[pCondition]; *----------------------------------------------------------- :IfMEP; StkP-1, Branch[.+2]; Stack&-1_ MD, Branch[.+1]; :EndIf; RTemp3_ A0, Branch[BCASTM1]; *----------------------------------------------------------- IFUR[BCAST, 1, CVBR, N[2]]; * Broadcast[pCondition]; *----------------------------------------------------------- :IfMEP; StkP-1, Branch[.+2]; Stack&-1_ MD, Branch[.+1]; :EndIf; RTemp3_ 77777C, Branch[BCASTM1]; :IfMEP; BCASTM1: StkP+1, Call[Pop1ShortOrLong]; * CVBR_ pCondition :Else; BCASTM1: Call[Pop1ShortOrLong]; * CVBR_ pCondition :EndIf; * This code is shared by NOTIFY and BCAST. * RTemp3 has the appropriate mask for testing whether to follow the chain * past the first PSB: 0 if NOTIFY, 77777 if BCAST. * CleanUpCVQueue[c]; * DO cv _ Fetch[c]^; IF cv.queue=NIL THEN EXIT; WakeHead[c]; ENDLOOP; * Reschedule[TRUE]; T_ A0, Call[CleanUpCVQueue]; * Returns ALU = CV.queue BroadcastLoop: Branch[.+2, ALU#0]; RTemp1_ ID, Branch[MesaReschedule1]; Nop; * Placement Call[WakeHead]; T_ A0, MemBase_ CVBR; Fetch_ T, T_ RTemp3; Process_ T AND MD, Branch[BroadcastLoop]; *----------------------------------------------------------- IFUR[REQUEUE, 1, CVBR, N[4]]; * Requeue * [sourceQueueHandle, destQueueHandle, processHandle]; *----------------------------------------------------------- T_ Stack&-1, Branch[REQUEUEM2]; * PSB handle :IfMEP; T_ Stack&-1_ MD, Branch[.+1]; :EndIf; REQUEUEM2: Process_ T, Call[Pop2ShortOrLong]; * CVBR_ destQ, MLBR_ sourceQ SQBRReg_ T-T-1, Branch[.+2, ALU=0]; * Branch if sq=NIL SQBRReg_ BRConst[MLBR]; * sourceQueueHandle in MLBR DQBRReg_ BRConst[CVBR]; * destQueueHandle in CVBR 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 * ID = 4 * Exit: RTemp0 = 0 * MemBase = entering MemBase xor 1 * Both BRs loaded * ID advanced * T = ALU = 0 iff second pointer is NIL * DQBRReg >= 0 (i.e., Requeue flag initialized to 0, if relevant) * Clobbers RTemp0, RTemp1, DQBRReg *----------------------------------------------------------- RTemp1_ Link, Call[Pop1ShortOrLong]; Link_ RTemp1; Subroutine; PD_ DQBRReg, FlipMemBase, Branch[Pop1Tail]; *----------------------------------------------------------- Pop1ShortOrLong: * Pop one short or long pointer and load base register. * Enter: MemBase = BR to be loaded * ID = 2 * Exit: RTemp0 = 0 * MemBase unchanged * BR loaded * ID advanced * T = ALU = 0 iff pointer is NIL * DQBRReg >= 0 (i.e., Requeue flag initialized to 0, if relevant) * Clobbers RTemp0, DQBRReg *----------------------------------------------------------- Subroutine; DQBRReg_ TIOA&StkP; DQBRReg_ (ID) AND (DQBRReg); Pop1Tail: RTemp0_ T_ A0, Branch[.+2, ALU#0]; * Short pointer, lengthen it with high part of MDS. BRHi_ MDSHi, Branch[.+2]; * Long pointer, pop high part off stack. T_ BRHi_ Stack&-1; T_ (BRLo_ Stack&-1) OR T, Return; *----------------------------------------------------------- FetchCP&CPF: * Fetches current process and current process's flags. * Enter: MemBase = PDA * Exit: Process = processHandle for current PSB * RTemp1 = processHandle^.flags * T = pointer to processHandle^.flags *----------------------------------------------------------- Subroutine; T_ CurrentPSB; Fetch_ T; Process_ MD, T_ (PSB.flags)+MD; Fetch_ T; RTemp1_ MD, Return; *----------------------------------------------------------- SetReadyQBR: * Sets up RQBR to point to the Ready queue * Exit: MemBase = RQBR * BR loaded * RTemp0 = -1 *----------------------------------------------------------- Subroutine; MemBase_ RQBR; BRHi_ PDAHi; RTemp0_ ReadyList; RTemp0_ (BRLo_ RTemp0)-(RTemp0)-1, Return; *----------------------------------------------------------- EnterFailed: * Enter: MLBR points to monitor lock * Exit: Does not return but rather goes directly to MesaReschedule *----------------------------------------------------------- TopLevel; DQBRReg_ BRConst[MLBR]; * dst = ML SQBRReg_ BRConst[RQBR], Call[SetReadyQBR]; * src = ReadyQ * cp _ Fetch[currentPSB]^; cpf _ Fetch[@cp.flags]^; MemBase_ PDA, Call[FetchCP&CPF]; * cpf.enterFailed _ TRUE; Store[@cp.flags]^ _ cpf; * Requeue[src: ready, dst: m, process: cp]; * Here RTemp1 has cpf and T has @cp.flags. RTemp1_ (RTemp1) OR (PSBF.enterFailed); Store_ T, DBuf_ RTemp1, Branch[Requeue&Resched]; *----------------------------------------------------------- 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 * Exit: MemBase = MLBR * T = 0 * RTemp1 = adjusted MonitorLock value (lock = unlocked) * Clobbers T, Q, RTemp0, RTemp1, RTemp2, Process *----------------------------------------------------------- Subroutine; * ml _ Fetch[m]^; p _ ml.queue; * IF p#NIL THEN ... Fetch_ T, SQBRReg_ BRConst[MLBR]; * src = ML T_ Process_ PD_ MD; Q_ Link, Branch[.+2, ALU#0]; RTemp1_ 100000C, Return; * Queue empty, exit quickly * BEGIN p _ Fetch[@p.link]^; Requeue[src: m, dst: ready, process: p]; END; TopLevel; MemBase_ PDA; Fetch_ T, DQBRReg_ BRConst[RQBR], Call[SetReadyQBR]; * dst = ReadyQ Process_ MD, MemBase_ PDA, Call[Requeue]; * ml _ Fetch[m]^; ml.lock _ unlocked; Store[m]^ _ ml; T_ A0, MemBase_ MLBR; RTemp1_ 100000C, Fetch_ T; Link_ Q; Subroutine; RTemp1_ (RTemp1) OR MD, Return; *----------------------------------------------------------- WakeHead: * Wakes the process at the head of the CV queue (there must be one). * Enter: CVBR = QueueHandle for CV * Process = CV.queue (note that WW bit must be stripped off) * Exit: MemBase = PDA * Clobbers T, RTemp0, RTemp1, RTemp2, Process, SQBRReg, DQBRReg *----------------------------------------------------------- Subroutine; RTemp1_ Link; TopLevel; DQBRReg_ BRConst[RQBR], Call[SetReadyQBR]; * dst = ReadyQ T_ Process, MemBase_ PDA; * p _ Fetch[@p.link]^; pf _ Fetch[@p.flags]^; Fetch_ T, SQBRReg_ BRConst[CVBR]; * src = CV Process_ MD, T_ (PSB.flags)+MD; Fetch_ T, RTemp0_ Not[PSBF.waitingOnCV!]C; * pf.waitingOnCV _ FALSE; Store[@p.flags]^ _ pf; RTemp0_ (RTemp0) AND MD; RTemp0_ Store_ T, DBuf_ RTemp0; * RTemp0#0 for Requeue * Requeue[src: c, dst: ready, process: p]; Link_ RTemp1, Branch[Requeue]; * Link_ overrides implied Call *----------------------------------------------------------- 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 = ProcessHandle * MemBase = PDA * Exit: MemBase = PDA * Clobbers T, RTemp0, RTemp1, RTemp2 *----------------------------------------------------------- Subroutine; * Here we compute "pp", the predecessor of process, and put it in RTemp1. * IF Fetch[@process.link]^ = process THEN pp _ NIL ... T_ Fetch_ Process, Global; RTemp1_ T#MD; RTemp0_ T+1, T_ MD, Branch[RequeueGotPP, ALU=0]; * ... ELSE BEGIN pp _ IF sq=NIL THEN process ELSE Fetch[sq]^; * Keep pp in MD for a while, and save process.link in T. * Note that we actually start with Fetch[@process.link]^ rather than process, * but that's ok since we already checked for Fetch[@process.link]^ = process. MemBase_ SQBRReg, Branch[.+2, R odd]; * Branch if sq=NIL Fetch_ 0S; * Search for predecessor of Process in ring. MD is where to start. * WHILE Fetch[@pp.link]^ # process DO pp _ Fetch[@pp.link]^; ENDLOOP; MemBase_ PDA; RTemp1_ Fetch_ MD; PD_ (Process)#MD; Branch[.-2, ALU#0]; * Now RTemp1 has predecessor of process. Bypass process in queue structure. * Store[@pp.link]^ _ Fetch[@process.link]^; END; Store_ RTemp1, DBuf_ T; * RTemp1 = predecessor of process, or 0 if wasn't one. T = process.link. * RTemp0 = @process.cleanUp. * IF sq=NIL THEN Store[@process.cleanUp]^ _ Fetch[@process.link]^ * ELSE IF Fetch[sq]^ = process THEN Store[sq]^ _ pp; RequeueGotPP: MemBase_ SQBRReg, Branch[.+2, R even]; * Branch if sq#NIL MemBase_ PDA, Branch[StorePPOrCleanUp]; RTemp0_ Fetch_ 0S; * Fetch[sq] PD_ (Process)#MD; T_ RTemp1, Branch[.+2, ALU#0]; StorePPOrCleanUp: Store_ RTemp0, DBuf_ T; * Store[sq]^ _ pp * Requeue (cont'd) * Now begin to insert process into dq. First, test for the easy case * in which dq is initially empty. * While we are at it, zero process.timer so as to disable timeouts. * (This is logically a part of WakeHead, but it's easier to do here.) * Store[@process.timeout]^ _ Ticks[0]; * pp _ Fetch[dq]^; IF pp=NIL THEN ... T_ A0, MemBase_ DQBRReg; RTemp0_ Fetch_ T; * Fetch[dq]^ T_ (PSB.timeout)+(Process); RTemp1_ PD_ MD, MemBase_ PDA; T_ (Store_ T)+1, DBuf_ RTemp0, * Know @PSB.flags = @PSB.timeout+1 Branch[CheckDQTail, ALU#0]; * dq is empty. Simply put process onto dq and link it to itself. * ... BEGIN Store[@process.link]^ _ process; Store[dq]^ _ process; END T_ A0, MemBase_ DQBRReg; Store_ T, T_ DBuf_ Process; RTemp0_ T, MemBase_ PDA, Branch[RequeueFinish]; * dq 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; T points to process^.flags. * ... ELSE BEGIN pri _ Fetch[@process.flags]^.priority; * IF pri <= Fetch[@pp.flags]^.priority THEN ... CheckDQTail: Fetch_ T; T_ (PSB.flags)+(RTemp1); Fetch_ T, RTemp2_ MD, T_ PSBF.priority; RTemp2_ (RTemp2) AND T; * RTemp2_ process's priority T_ T AND MD; * T_ pp's priority PD_ (RTemp2)-T-1; * Top of search loop; RTemp1 = pp; ALU<0 if process^.priority <= pp^.priority. * ALU>=0 here always if came from SearchDQ (below). SearchDQLoop: RTemp0_ Fetch_ RTemp1, Branch[SearchDQ, ALU>=0]; * Process lower priority than tail. Just make process be new tail. * ... Store[dq]^ _ process ... T_ A0, MemBase_ DQBRReg; Store_ T, DBuf_ Process, T_ MD; MemBase_ PDA, Branch[RequeueLinkDQ]; * Have to search dq to find proper place to insert process. * Insert after last process whose priority is >= that of process. * ... ELSE DO tp _ Fetch[@pp.link]^; * IF pri <= Fetch[@tp.flags]^.priority then pp _ tp ELSE EXIT; * ENDLOOP; SearchDQ: RTemp1_ MD, T_ (PSB.flags)+MD; Fetch_ T; T_ (Add[PSBF.priority!]S) AND MD; PD_ (RTemp2)-T-1; * Note: set ALU>=0 here -- know ProcessHandles are IN [0..77777B]. T_ RTemp1, Branch[SearchDQLoop, ALU<0]; * Now RTemp0 = pp = the process after which we are to insert; * T = tp = that process's successor. * Store[@process.link]^ _ Fetch[@pp.link]^; * Store[@pp.link]^ _ process; RequeueLinkDQ: T_ Store_ Process, DBuf_ T; RequeueFinish: Store_ RTemp0, DBuf_ T, Return; *----------------------------------------------------------- CleanUpCVQueue: * Cleans up CV queue that may have been left in a mess by Requeue. * Enter: MemBase = CVBR * CVBR points to condition variable * T = 0 * Exit: MemBase = CVBR * ALU = Process = CV.queue (as modified by subroutine, if necessary) * Clobbers T, RTemp0 *----------------------------------------------------------- Subroutine; T_ 77777C, Fetch_ T; * Fetch pCondition^ Process_ T AND MD, MemBase_ PDA; * RTemp0_ pCondition^.queue T_ (Process)+1, Branch[.+2, ALU#0]; PD_ Process, MemBase_ CVBR, Return; * Queue is empty, nothing to do Fetch_ T; * Fetch process^.cleanUp PD_ MD; SearchForCVQHead: PD_ MD, Branch[.+2, ALU#0]; * Always branches 2nd time and after PD_ Process, MemBase_ CVBR, Return; * No cleanup queue, nothing to do * Process has a processHandle to check, and ALU = MD = process^.cleanUp. * Follow process^.cleanUp links until we either hit NIL or * find a PSB with a cleanUp link pointing to itself. Process_ MD, PD_ (Process)#MD, Branch[FoundCVQHead, ALU=0]; T_ (Process)+1, Branch[CVQEmpty, ALU=0]; PD_ (Fetch_ T)-T-1, Branch[SearchForCVQHead]; * Fetch cleanUp link * 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. CVQEmpty: T_ Process_ A0, MemBase_ CVBR, 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. * T = new head process +1 here. FoundCVQHead: RTemp0_ T_ T-1; * RTemp0_ tail SearchForCVQTail: Process_ Fetch_ T; * Fetch process^.link T_ MD, PD_ (RTemp0)#MD; Branch[SearchForCVQTail, ALU#0]; * Process now points to the predecessor of RTemp0. T_ A0, MemBase_ CVBR; StoreCVQueue: Store_ T, PD_ DBuf_ Process, Return; * pCondition^ _ Process *----------------------------------------------------------- IFUR[IWDC, 1, L]; * Increment Wakeup Disable Counter: wdc _ wdc+1; *----------------------------------------------------------- WDC_ (WDC)+1, RBase_ RBase[NWW], Branch[IWDCM1]; :IfMEP; Stack_ MD, Branch[.-1]; StkP+1, Branch[.-2]; :EndIf; IWDCM1: NWW _ (NWW) OR (100000C), IFUNext0; *----------------------------------------------------------- IFUR[DWDC, 1, L]; * Decrement Wakeup Disable Counter: wdc _ wdc-1; * Note: one instruction must be executed after DWDC before an * interrupt is permitted to occur. (This is accomplished by the * NoReschedule .. Reschedule in UpdateNWW.) *----------------------------------------------------------- :IfMEP; Branch[DWDCM1]; Stack_ MD, Branch[DWDCM1]; StkP+1, Branch[DWDCM1]; :EndIf; DWDCM1: * Also get here from WRM WDC_ (WDC)-1, RBase_ RBase[NWW], Call[UpdateNWW]; IFUNext0; *----------------------------------------------------------- UpdateNWW: * Subroutine to update NWW to reflect new state of WDC and initiate * a Reschedule request if an interrupt is pending. * Entry conditions: ALU=WDC, RBase=RBase[NWW] * Exit conditions: RBase=RBase[NWW]. *----------------------------------------------------------- Subroutine; KnowRBase[NWW]; NWW_ (NWW) AND NOT (100000C), Branch[DisableNWW, ALU#0]; PD_ NWW, NoReschedule, Branch[.+3, Reschedule']; * 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. T_ (ID)-(PCX')-1; PD_ NWW, PCF_ T; * 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. Branch[.+2, ALU=0]; Reschedule; Return; * Unnecessary to worry about possibility of Reschedule trap when disabling * interrupts, since MesaReschedTrap ignores traps when interrupts are disabled. DisableNWW: NWW_ (NWW) OR (100000C), Return; TopLevel; *----------------------------------------------------------- * 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[NWW], Branch[MesaResc1]; :IfMEP; At[MesaTrapBase, 15], Stack_ MD, RBase_ RBase[NWW], Branch[MesaResc1]; At[MesaTrapBase, 16], StkP+1, RBase_ RBase[NWW], Branch[MesaResc1]; :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. * Invariant: NWW<0 iff WDC#0; therefore, need not check WDC explicitly. MesaResc1: NoReschedule; T_ NOT (Q_ PCX'); * Q#0 => normal emulation T_ NWW, PCF_ T; * NWW>0 means interrupt can take Branch[MesaInterrupt, ALU>0]; * No interrupt is pending. Just resume execution. IFUNext0; *----------------------------------------------------------- MesaInterrupt: * Enter: RBase=AEmRegs * T=NWW>0 (i.e., interrupts enabled and at least one request pending) * Q#0 if interrupted from normal emulation; Q=0 if from idle loop. * WDC=0 *----------------------------------------------------------- * Turn off the wakeups pending on channels we are about to process. * Make a special test for an interrupt occurring on the timer channel * (60 hz), and do CheckForTimeouts when one occurs. * As a side-effect, reset the "requeue performed" flag in DQBRReg. NWW_ (NWW) AND NOT T, RBase_ RBase[RTemp0]; DQBRReg_ T AND (TimerChanMask); WDC_ T, MemBase_ PDA, Branch[CheckForTimeouts, ALU#0]; * For each interrupt that is pending, check the naked CV pointed to by * the appropriate entry in NakedCVArray (these are PDA-relative pointers). * Discard any interrupt directed to a NakedCVArray entry that is NIL. * RTemp3 keeps the NakedCVArray pointer, and WDC is used to shift the * interrupt request bits. CheckNakedCVs: RTemp3_ NakedCVArray; CheckNakedCVLoop: PD_ WDC, MemBase_ PDA; RTemp3_ (Fetch_ RTemp3)+1, Branch[NoMoreInts, ALU=0]; WDC_ (WDC) RSH 1, Branch[.-1, R even]; T_ PD_ MD, MemBase_ CVBR; BRLo_ T, Branch[CheckNakedCVLoop, ALU=0]; * Have an interrupt for a non-NIL NakedCVArray entry. * If a process is waiting on the CV pointed to by the entry, wake it up; * if no process is waiting, set the wakeupWaiting bit in the CV. BRHi_ PDAhi; T_ A0, Call[CleanUpCVQueue]; T_ A0, Branch[.+2, ALU#0]; Store_ T, DBuf_ 100000C, Branch[CheckNakedCVLoop]; Nop; * Placement Call[WakeHead]; Branch[CheckNakedCVLoop]; * All interrupt requests have been processed, and WDC=0 again. * If we were interrupted from normal emulation (Q#0), do a Reschedule iff * the interrupt actually caused any processes to be requeued. * If we were interrupted from the idle loop (Q=0), go reconsider the ReadyQ. NoMoreInts: PD_ Q; 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 T_ Add[TimerLoc!, 1]C; * Count up timer interrupts/tick Fetch_ T, Process_ And[FirstPSBLoc, 177400]C; RTemp0_ MD+1; Process_ (Process) OR (And[FirstPSBLoc, 377]C), Branch[.+2, ALU>=0]; Store_ T, DBuf_ RTemp0, Branch[CheckNakedCVs]; * Not this time * Fetch PSB array bounds and update timer T_ (Store_ T)-1, DBuf_ -3C; * Reset interrupts/tick count Fetch_ T; RTemp0_ MD+1; * RTemp0_ updated current time Store_ T, DBuf_ RTemp0; T_ (Fetch_ Process)+1; Process_ MD, Fetch_ T; * Process_ first PSB RTemp3_ MD; * RTemp3_ last PSB * For each process, if PSB.timeout#0 (i.e., enabled to timeout) and * PSB.timeout=currentTime, then put the process back on the readyQ. * FOR p _ Fetch[firstPSB]^, p+SIZE[PSB] UNTIL p=Fetch[lastPSB]^ DO * time _ Fetch[@p.timeout]^; * IF time#Ticks[0] AND time=currentTime THEN ... CheckTimerLoop: T_ (PSB.timeout)+(Process); T_ (Fetch_ T)+(Sub[SizePSB!, PSB.timeout!]C); * T_ next process PD_ MD; PD_ (RTemp0)-MD, Branch[CanTimeout, ALU#0]; PD_ (RTemp3)-T; * Beyond last PSB? NoTimeout: Process_ T, Branch[CheckTimerLoop, ALU>=0]; Branch[CheckNakedCVs]; CanTimeout: PD_ (RTemp3)-T, Branch[NoTimeout, ALU#0]; * pf _ Fetch[@process.flags]^; pf.waitingOnCV _ FALSE; * Store[@process.flags]^ _ pf; * Requeue[src: NIL, dst: ready, process: process]; DQBRReg_ BRConst[RQBR], Call[SetReadyQBR]; * dst = ReadyQ SQBRReg_ T-T-1, MemBase_ PDA, Call[Requeue]; * src = NIL T_ (PSB.flags)+(Process); Fetch_ T, RTemp0_ Not[PSBF.waitingOnCV!]C; RTemp0_ (RTemp0) AND MD; * Will check the current process's timer again, but Requeue zeroed it * so no matter. RTemp1_ TimerLoc; * Re-fetch currentTime Fetch_ RTemp1; Store_ T, DBuf_ RTemp0, RTemp0_ MD, Branch[CheckTimerLoop]; *----------------------------------------------------------- Requeue&Resched: * Do a Requeue followed by a Reschedule. * Arguments as for Requeue. *----------------------------------------------------------- MemBase_ PDA, Call[Requeue]; *----------------------------------------------------------- MesaReschedule: * 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; = IL if came from * process/monitor opcode. * IF ~reschedulePending THEN RETURN; MesaReschedule1: MemBase_ PDA, DQBRReg, Branch[.+2, R<0]; IFUNext0; * No Requeue, resume normal execution * Save state of current process. * cs _ Fetch[currentState]^; SaveState[cs]; * StoreMDS[@LocalBase[L].pc]^ _ PC; * cp _ Fetch[currentPSB]^; Store[@cp.frame]^ _ L; T_ CurrentState; Fetch_ T, RTemp2_ CurrentPSB; RTemp1_ (RTemp1)-(PCX')-1, Call[SavePCInFrame]; * PCX or PCX+IL DLink_ T, MemBase_ PDA; * DLink_ L RTemp0_ MD, Call[SaveState]; * RTemp0 = where to save the state Fetch_ RTemp2; * CurrentPSB^ Store_ T, DBuf_ SLink, T_ MD; * Finish saving state T_ (PSB.frame)+T; Store_ T, DBuf_ DLink; * CurrentPSB^.frame _ L * Check ready queue for new process to run * IF Fetch[ready]^ = NIL THEN ... IdleReschedule: T_ ReadyList; Fetch_ T, RTemp0_ And[FirstSVLoc, 177400]C; Process_ PD_ Q_ MD; PD_ WDC, Branch[SetupNewProcess, ALU#0]; * Ready queue is empty. * 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. * ... BEGIN IF wdc#0 THEN WakeError[]; * DO IF wp#0 THEN RETURN; ENDLOOP; END * Note that RETURN with wp#0 implies that CheckForInterrupts will happen * before any additional instructions can be executed. But we can't implement * it that way on the Dorado; therefore, branch directly to MesaInterrupt. T_ sWakeupError, Branch[.+2, ALU=0]; Branch[MTrap]; * It's an error to be idle with WDC#0 PD_ A0, RBase_ RBase[NWW]; PD_ NWW, NoReschedule, Branch[., ALU=0]; T_ NWW, Branch[MesaInterrupt]; * Q=0 => will return to IdleReschedule * MesaReschedule (cont'd) KnowRBase[RTemp0]; * Have a process ready to run. * Process = tail of ready list. Set up to run head process. * np _ Fetch[@np.link]^; pf _ Fetch[@np.flags]^; SetupNewProcess: Fetch_ Process; Process_ MD, T_ (PSB.flags)+MD; RTemp2_ Fetch_ T; * state _ Fetch[FirstSV]^ + SIZE[StateVector]*pf.priority. * This code knows that SIZE[StateVector] = 13B, and does the multiply * by the shift-and-add method. RTemp0_ (RTemp0) OR (And[FirstSVLoc, 377]C); RTemp1_ MD, T_ (Add[PSBF.priority!]S) AND MD; Fetch_ RTemp0, Q_ T; T_ LSH[T, 3]; T_ T+Q, Q LSH 1; T_ T+Q; RTemp0_ T+MD; * If we are resuming a process waiting on a monitor lock, store FALSE * on its stack before resuming it. * Process = np, RTemp1 = flags, RTemp0 = state, RTemp2 = @np.flags. * IF pf.enterFailed THEN BEGIN * Store[@state.stkp]^ _ 1; Store[@state.stack[0]]^ _ FALSE; * pf.enterFailed _ FALSE; Store[@np.flags]^ _ pf; END; T_ (RTemp1) AND (77777C), Branch[NotWaitingML, R>=0]; Store_ RTemp2, DBuf_ T, T_ A0; * Store flags Store_ RTemp0, DBuf_ T; * Store stack[0] T_ (RTemp0)+(SV.stkp); Store_ T, DBuf_ 1C; * Store stkp * Now set up the state for the new process. * Process = np, RTemp0 = state. * Store[@state.dst]^ _ Fetch[@np.frame]^; * Store[currentPSB]^ _ np; Store[currentState]^ _ state; * LoadState[state]; NotWaitingML: T_ (PSB.frame)+(Process); Fetch_ T, T_ CurrentPSB; Store_ T, DBuf_ Process; * Store currentPSB T_ (RTemp0)+(SV.dst); Store_ T, DBuf_ MD, XferFlags_ A0; * Store state.dst T_ CurrentState; Store_ T, DBuf_ RTemp0, Branch[LoadState]; * Store currentState (1552)