/* TIMER.C (c) Copyright Roger Bowler, 1999-2012 */ /* Timer support functions */ /* */ /* Released under "The Q Public License Version 1" */ /* (http://www.hercules-390.org/herclic.html) as modifications to */ /* Hercules. */ /* z/Architecture support - (c) Copyright Jan Jaeger, 1999-2012 */ #include "hstdinc.h" #define _HENGINE_DLL_ #include "hercules.h" #include "opcode.h" #include "feat390.h" #include "feat370.h" /*-------------------------------------------------------------------*/ /* Check for timer event */ /* */ /* Checks for the following interrupts: */ /* [1] Clock comparator */ /* [2] CPU timer */ /* [3] Interval timer */ /* CPUs with an outstanding interrupt are signalled */ /* */ /* tod_delta is in hercules internal clock format (>> 8) */ /*-------------------------------------------------------------------*/ void update_cpu_timer(void) { int cpu; /* CPU counter */ REGS *regs; /* -> CPU register context */ CPU_BITMAP intmask = 0; /* Interrupt CPU mask */ #if defined(OPTION_MIPS_COUNTING) /* If no CPUs are available, just return (device server mode) */ if (!sysblk.hicpu) return; #endif /*defined(OPTION_MIPS_COUNTING)*/ /* Access the diffent register contexts with the intlock held */ OBTAIN_INTLOCK(NULL); /* Check for [1] clock comparator, [2] cpu timer, and * [3] interval timer interrupts for each CPU. */ for (cpu = 0; cpu < sysblk.hicpu; cpu++) { /* Ignore this CPU if it is not started */ if (!IS_CPU_ONLINE(cpu) || CPUSTATE_STOPPED == sysblk.regs[cpu]->cpustate) continue; /* Point to the CPU register context */ regs = sysblk.regs[cpu]; /*-------------------------------------------* * [1] Check for clock comparator interrupt * *-------------------------------------------*/ if (TOD_CLOCK(regs) > regs->clkc) { if (!IS_IC_CLKC(regs)) { ON_IC_CLKC(regs); intmask |= regs->cpubit; } } else if (IS_IC_CLKC(regs)) OFF_IC_CLKC(regs); #if defined(_FEATURE_SIE) /* If running under SIE also check the SIE copy */ if(regs->sie_active) { /* Signal clock comparator interrupt if needed */ if(TOD_CLOCK(regs->guestregs) > regs->guestregs->clkc) { ON_IC_CLKC(regs->guestregs); intmask |= regs->cpubit; } else OFF_IC_CLKC(regs->guestregs); } #endif /*defined(_FEATURE_SIE)*/ /*-------------------------------------------* * [2] Decrement the CPU timer for each CPU * *-------------------------------------------*/ /* If LPAR mode and not waiting, or in BASIC mode, decrement * the CPU timer and update the CPU timer interrupt state, if. * necessary. */ if (!WAITSTATE(®s->psw) || !sysblk.lparmode) { /* Set interrupt flag if the CPU timer is negative */ if (cpu_timer(regs) < 0) { if (!IS_IC_PTIMER(regs)) { ON_IC_PTIMER(regs); intmask |= regs->cpubit; } } else if(IS_IC_PTIMER(regs)) OFF_IC_PTIMER(regs); } #if defined(_FEATURE_SIE) /* When running under SIE also update the SIE copy */ if(regs->sie_active) { /* Set interrupt flag if the CPU timer is negative */ if (cpu_timer_SIE(regs) < 0) { ON_IC_PTIMER(regs->guestregs); intmask |= regs->cpubit; } else OFF_IC_PTIMER(regs->guestregs); } #endif /*defined(_FEATURE_SIE)*/ #if defined(_FEATURE_INTERVAL_TIMER) /*-------------------------------------------* * [3] Check for interval timer interrupt * *-------------------------------------------*/ if(regs->arch_mode == ARCH_370) { if( chk_int_timer(regs) ) intmask |= regs->cpubit; } #if defined(_FEATURE_SIE) /* When running under SIE also update the SIE copy */ if(regs->sie_active) { if(SIE_STATB(regs->guestregs, M, 370) && SIE_STATNB(regs->guestregs, M, ITMOF)) { if( chk_int_timer(regs->guestregs) ) intmask |= regs->cpubit; } } #endif /*defined(_FEATURE_SIE)*/ #endif /*defined(_FEATURE_INTERVAL_TIMER)*/ } /* end for(cpu) */ /* If a timer interrupt condition was detected for any CPU then wake up those CPUs if they are waiting */ WAKEUP_CPUS_MASK (intmask); RELEASE_INTLOCK(NULL); } /* end function check_timer_event */ /*-------------------------------------------------------------------*/ /* TOD clock and timer thread */ /* */ /* This function runs as a separate thread. It wakes up every */ /* 1 microsecond, updates the TOD clock, and decrements the */ /* CPU timer for each CPU. If any CPU timer goes negative, or */ /* if the TOD clock exceeds the clock comparator for any CPU, */ /* it signals any waiting CPUs to wake up and process interrupts. */ /*-------------------------------------------------------------------*/ void *timer_update_thread (void *argp) { #ifdef OPTION_MIPS_COUNTING int i; /* Loop index */ REGS *regs; /* -> REGS */ U64 mipsrate; /* Calculated MIPS rate */ U64 siosrate; /* Calculated SIO rate */ U64 total_mips; /* Total MIPS rate */ U64 total_sios; /* Total SIO rate */ /* Clock times use the top 64-bits of the ETOD clock */ U64 now; /* Current time of day */ U64 then; /* Previous time of day */ U64 diff; /* Interval */ U64 halfdiff; /* One-half interval */ U64 waittime; /* Wait time */ const U64 period = ETOD_SEC; /* MIPS calculation period */ #define diffrate(_x,_y) \ ((((_x) * (_y)) + halfdiff) / diff) #endif /*OPTION_MIPS_COUNTING*/ UNREFERENCED(argp); /* Set timer thread priority */ set_thread_priority(0, sysblk.todprio); /* Display thread started message on control panel */ WRMSG (HHC00100, "I", thread_id(), get_thread_priority(0), "Timer"); SET_THREAD_NAME_ID(-1, "CPU Timer"); #ifdef OPTION_MIPS_COUNTING then = host_tod(); while (!sysblk.shutdown) { /* Update TOD clock and save TOD clock value */ now = update_tod_clock(); diff = now - then; if (diff >= period) /* Period expired? */ { halfdiff = diff / 2; /* One-half interval for rounding */ then = now; total_mips = total_sios = 0; #if defined(OPTION_SHARED_DEVICES) total_sios = sysblk.shrdcount; sysblk.shrdcount = 0; #endif for (i = 0; i < sysblk.hicpu; i++) { obtain_lock (&sysblk.cpulock[i]); if (!IS_CPU_ONLINE(i)) { release_lock(&sysblk.cpulock[i]); continue; } regs = sysblk.regs[i]; /* 0% if CPU is STOPPED */ if (regs->cpustate == CPUSTATE_STOPPED) { regs->mipsrate = regs->siosrate = regs->cpupct = 0; release_lock(&sysblk.cpulock[i]); continue; } /* Calculate instructions per second */ mipsrate = regs->instcount; regs->instcount = 0; regs->prevcount += mipsrate; mipsrate = diffrate(mipsrate, period); regs->mipsrate = mipsrate; total_mips += mipsrate; /* Calculate SIOs per second */ siosrate = regs->siocount; regs->siocount = 0; regs->siototal += siosrate; siosrate = diffrate(siosrate, period); regs->siosrate = siosrate; total_sios += siosrate; /* Calculate CPU busy percentage */ waittime = regs->waittime; regs->waittime = 0; if (regs->waittod) { waittime += now - regs->waittod; regs->waittod = now; } regs->cpupct = min((diff > waittime) ? diffrate(diff - waittime, 100) : 0, 100); release_lock(&sysblk.cpulock[i]); } /* end for(cpu) */ /* Total for ALL CPUs together */ sysblk.mipsrate = total_mips; sysblk.siosrate = total_sios; update_maxrates_hwm(); // (update high-water-mark values) } /* end if(diff >= period) */ #else /* ! OPTION_MIPS_COUNTING */ while (sysblk.cpus) { /* Update TOD clock */ update_tod_clock(); #endif /*OPTION_MIPS_COUNTING*/ /* Sleep for another timer update interval... */ usleep ( sysblk.timerint ); } /* end while */ WRMSG (HHC00101, "I", thread_id(), get_thread_priority(0), "Timer"); sysblk.todtid = 0; return NULL; } /* end function timer_update_thread */ LOCK caplock; COND capcond; static void capping_manager_shutdown(void * unused) { UNREFERENCED(unused); if(sysblk.capvalue) { sysblk.capvalue = 0; obtain_lock(&caplock); timed_wait_condition_relative_usecs(&capcond,&caplock,2*1000*1000,NULL); release_lock(&caplock); } } /*-------------------------------------------------------------------*/ /* Capping manager thread */ /* */ /* This function runs as a separate thread. It is started when a */ /* value is given on the CAPPING statement within the config file. */ /* It checks every 1/100 second if there are too many CP */ /* instructions executed. In that case the CPs are stopped. Then */ /* the manager counts if the CPs are stopped long enough before */ /* waking them up. Capping does only apply to CP, not specialty */ /* engines. Those engines are untouched by the capping manager. */ /*-------------------------------------------------------------------*/ void *capping_manager_thread (void *unused) { U64 diff; /* Time passed during interval */ U64 now; /* Current time */ U64 then = 0; /* Previous interval time */ int cpu; /* cpu index */ U32 allowed; /* Max allowed insts during interval */ U64 instcnt[MAX_CPU_ENGINES]; /* Number of CP insts executed */ U64 prevcnt[MAX_CPU_ENGINES]; /* Inst CP count on previous interval */ U32 prevcap = 0; /* Previous cappling value */ U32 iactual; /* Actual instruction count */ U32 irate[MAX_CPU_ENGINES]; /* Actual instruction rate */ int numcap = 1; /* Number of CPU's being capped */ UNREFERENCED(unused); initialize_lock (&caplock); initialize_condition(&capcond); hdl_adsc("capping_manager_shutdown",capping_manager_shutdown, NULL); /* Display thread started message on control panel */ WRMSG(HHC00100, "I", thread_id(), get_thread_priority(0), "Capping manager"); /* Initialize interrupt wait locks */ for(cpu = 0; cpu < sysblk.maxcpu; cpu++) initialize_lock(&sysblk.caplock[cpu]); /* Check for as long as capping is active */ while(sysblk.capvalue) { if(sysblk.capvalue != prevcap) { prevcap = sysblk.capvalue; WRMSG(HHC00877, "I", sysblk.capvalue); /* Lets get started */ then = host_tod(); for(cpu = 0; cpu < MAX_CPU_ENGINES; cpu++) { prevcnt[cpu] = 0xFFFFFFFFFFFFFFFFULL; irate[cpu] = 0; } } /* Sleep for 1/100 of a second */ usleep(10000); if ( sysblk.capvalue == 0 ) break; now = host_tod(); /* Count the number of CPs to be capped */ for(numcap = cpu = 0; cpu < sysblk.hicpu; cpu++) if(IS_CPU_ONLINE(cpu) && sysblk.ptyp[cpu] == SCCB_PTYP_CP && sysblk.regs[cpu]->cpustate == CPUSTATE_STARTED) numcap++; /* Continue if no CPU's to be capped */ if(!numcap) continue; /* Actual elapsed time of our approximate 1/100 of a second wait */ diff = now - then; /* Calculate the allowed amount of executed instructions for this interval */ allowed = sysblk.capvalue * diff; allowed /= numcap; /* Calculate the number of executed instructions */ for(cpu = 0; cpu < sysblk.hicpu; cpu++) { if(!IS_CPU_ONLINE(cpu) || sysblk.ptyp[cpu] != SCCB_PTYP_CP || sysblk.regs[cpu]->cpustate != CPUSTATE_STARTED) break; instcnt[cpu] = sysblk.regs[cpu]->prevcount + sysblk.regs[cpu]->instcount; /* Check for CP reset */ if(prevcnt[cpu] > instcnt[cpu]) prevcnt[cpu] = instcnt[cpu]; /* Actual number of instructions executed in interval */ iactual = instcnt[cpu] - prevcnt[cpu]; /* Calculate floating average rate over the past second */ irate[cpu] = (irate[cpu] - ((irate[cpu] + 64) >> 7)) + ((iactual + 64) >> 7); /* Wakeup the capped CP if rate not exceeded */ if(sysblk.caplocked[cpu] && (allowed > irate[cpu])) { sysblk.caplocked[cpu] = 0; release_lock(&sysblk.caplock[cpu]); } else /* We will never unlock and relock in the same interval, this to ensure we do not starve a CP */ /* Cap if the rate has exceeded the allowed rate */ if(!sysblk.caplocked[cpu] && (allowed < irate[cpu])) { /* Cap the CP */ obtain_lock(&sysblk.caplock[cpu]); sysblk.caplocked[cpu] = 1; } prevcnt[cpu] = instcnt[cpu]; then = now; } } /* Uncap all before exit */ for(cpu = 0; cpu < sysblk.maxcpu; cpu++) if(sysblk.caplocked[cpu]) { sysblk.caplocked[cpu] = 0; release_lock(&sysblk.caplock[cpu]); } signal_condition(&capcond); if ( !sysblk.shutdown ) hdl_rmsc(capping_manager_shutdown, NULL); sysblk.captid = 0; WRMSG(HHC00101, "I", thread_id(), get_thread_priority(0), "Capping manager"); return(NULL); }