package com.jopdesign.sys; import com.jopdesign.io.IOFactory; import com.jopdesign.io.SysDevice; /** * Represent the scheduler and thread queue for one CPU. * Created and set in startMisison() * * @author martin * */ class Scheduler implements Runnable { // allocated and set in startMission // ordered by priority int next[]; // next time to change to state running RtThreadImpl[] ref; // references to threads final static int NO_EVENT = 0; final static int EV_FIRED = 1; final static int EV_WAITING = 2; int event[]; // state of an event int cnt; // number of threads int active; // active thread number int tmp; // counter to build the thread list static SysDevice sys = IOFactory.getFactory().getSysDevice(); static Scheduler[] sched = new Scheduler[sys.nrCpu]; static { // create scheduler objects for all cores for (int i=0; i<sys.nrCpu; ++i) { new Scheduler(i); } } Scheduler(int core) { active = 0; // main thread (or idle thread) is first thread cnt = 0; // stays 0 till startMission // next = new int[1]; // ref = new RtThreadImpl[1]; sched[core] = this; } /** * If no thread gets ready within some time the scheduler is * still invoked after IDL_TICK. * TODO: a cross-core SW event is only detected at a scheduler * invocation due to a thread on this core or at idle tick. */ final static int IDL_TICK = 1000000; // use local memory for two values private final static int TIM_VAL_ADDR = 0x1e; private final static int SP_VAL_ADDR = 0x1f; // timer offset to ensure that no timer interrupt happens just // after monitorexit in this method and the new thread // has a minimum time to run. private final static int TIM_OFF = 200; // private final static int TIM_OFF = 20; // private final static int TIM_OFF = 2; // for 100 MHz version 20 or even lower // 2 is minimum /** * This is the scheduler invoked as a plain interrupt handler * from JVMHelp.interrupt(). It gets invoked with interrupts * globally disabled. * * This is the one and only function to switch threads * and should NEVER be called from somewhere else. * * Interrupts (also yield/genInt()) should NEVER occur * before startMission is called (ref and active are set) * */ public void run() { int i, j, k; int diff; Scheduler s; RtThreadImpl th; // we have not called doInit(), which means // we have only one thread => just return // Should actually not happen. if (!RtThreadImpl.initDone) return; // take care to NOT invoke a method with monitorexit // can happen on the write barrier on reference assignment // save stack i = Native.getSP(); th = ref[active]; th.sp = i; Native.int2extMem(Const.STACK_OFF, th.stack, i-Const.STACK_OFF+1); // cnt is i-Const.STACK_OFF+1 // SCHEDULE // cnt should NOT contain idle thread // change this some time k = IDL_TICK; // this is now j = Native.rd(Const.IO_US_CNT); for (i=cnt-1; i>0; --i) { if (event[i] == EV_FIRED) { break; // a pending event found } else if (event[i] == NO_EVENT) { diff = next[i]-j; // check only periodic if (diff < TIM_OFF) { break; // found a ready task } else if (diff < k) { k = diff; // next interrupt time of higher priority thread } } } // i is next ready thread (index into the list) // If none is ready i points to idle task or main thread (fist in the list) active = i; // set next interrupt time to now+(min(diff)) (j, k) // use JVM locals to get time and sp over the stack exchange Native.wrIntMem(j+k, TIM_VAL_ADDR); // restore stack // We cannot use statics in a CMP setting! Native.wrIntMem(ref[i].sp, SP_VAL_ADDR); Native.setVP(Native.rdIntMem(SP_VAL_ADDR)+2); // +2 for sure ??? // +7 locals, take care to use only the first 1+6!! // so +9 should be ok, but it works only with +10 when // using all locals Native.setSP(Native.rdIntMem(SP_VAL_ADDR)+10); // all locals are lost now, even this - reassign them // get this back form the array of Schedulers s = sched[sys.cpuId]; th = s.ref[s.active]; i = Native.rdIntMem(SP_VAL_ADDR); // can't use s1-127 as count, // don't know why I have to store it in a local. Native.ext2intMem(th.stack, Const.STACK_OFF, i-Const.STACK_OFF+1); // cnt is i-Const.STACK_OFF+1 j = Native.rd(Const.IO_US_CNT); // check if next timer value is too early (or already missed) // ack timer interrupt and schedule timer if (Native.rdIntMem(TIM_VAL_ADDR)-j<TIM_OFF) { // set timer to now plus some short time Native.wr(j+TIM_OFF, Const.IO_TIMER); } else { Native.wr(Native.rdIntMem(TIM_VAL_ADDR), Const.IO_TIMER); } Native.setSP(i); // only return after setSP! // WHY should this be true? We need a monitorexit AFTER setSP(). // It compiles to following: // invokestatic #32 <Method void setSP(int)> // aload 5 // monitorexit // goto 283 // ... // 283 return // // for a 'real monitor' we have a big problem: // aload 5 loads the monitor from the OLD stack!!! // // we can't access any 'old' locals now // // a solution: don't use a monitor here! // disable and enable INT 'manual' // and DON'T call a method with synchronized // it would enable the INT on monitorexit Native.wr(1, Const.IO_INT_ENA); } /** * Allocate arrays for the thread list. One more than cnt for * the initial main/Runnable */ void allocArrays() { // change active if a lower priority // thread is before main // tq.active = tq.ref[0].nr; // this was our main thread // cnt one higher for start thread (main or Runnable) ++cnt; ref = new RtThreadImpl[cnt]; next = new int[cnt]; event = new int[cnt]; tmp = cnt-1; } /** * Add main or Runnable to the list as last thread. */ public void addMain() { // thread structure for main and the start Runnables // TODO: do we need to do a startThread for the CMP start Runnable? ref[0] = new RtThreadImpl(RtThreadImpl.NORM_PRIORITY, 0); ref[0].state = RtThreadImpl.READY; // main thread is READY next[0] = 0; ref[0].nr = 0; } }