Files
org-hyperion-cules/timer.c
Greg Smith 5a62321ddd 19 Apr 2003 Shared devices - Greg Smith
git-svn-id: file:///home/jj/hercules.svn/trunk@1335 956126f8-22a0-4046-8f4a-272fa8102e63
2003-04-19 07:41:59 +00:00

413 lines
15 KiB
C

/* TIMER.C */
/* z/Architecture support - (c) Copyright Jan Jaeger, 1999-2003 */
#include "hercules.h"
#include "opcode.h"
#include "feat390.h"
#include "feat370.h"
int ecpsvm_testvtimer(REGS *,int);
void check_timer_event(void);
/*-------------------------------------------------------------------*/
/* Update TOD clock */
/* */
/* This function updates the TOD clock. */
/* */
/* This function is called by timer_update_thread and by cpu_thread */
/* instructions that manipulate any of the timer related entities */
/* (clock comparator, cpu timer and interval timer). */
/* */
/* Internal function `check_timer_event' is called which will signal */
/* any timer related interrupts to the appropriate cpu_thread. */
/* */
/* Callers *must* own the todlock and *must not* own the intlock. */
/* */
/*-------------------------------------------------------------------*/
void update_TOD_clock(void)
{
struct timeval tv; /* Current time */
U64 dreg; /* Double register work area */
/* Get current time */
gettimeofday (&tv, NULL);
ADJUST_TOD (tv, sysblk.lasttod);
/* Load number of seconds since 00:00:00 01 Jan 1970 */
dreg = (U64)tv.tv_sec;
/* Convert to microseconds */
dreg = dreg * 1000000 + tv.tv_usec;
#ifdef OPTION_TODCLOCK_DRAG_FACTOR
if (sysblk.toddrag > 1)
dreg = sysblk.todclock_init +
(dreg - sysblk.todclock_init) / sysblk.toddrag;
#endif /*OPTION_TODCLOCK_DRAG_FACTOR*/
/* Shift left 4 bits so that bits 0-7=TOD Clock Epoch,
bits 8-59=TOD Clock bits 0-51, bits 60-63=zero */
dreg <<= 4;
/* Ensure that the clock does not go backwards and always
returns a unique value in the microsecond range */
if( dreg > sysblk.todclk)
sysblk.todclk = dreg;
else sysblk.todclk += 16;
/* Get the difference between the last TOD saved and this one */
sysblk.todclock_diff = (sysblk.todclock_prev == 0 ? 0 :
sysblk.todclk - sysblk.todclock_prev);
/* Save the old TOD clock value */
sysblk.todclock_prev = sysblk.todclk;
/* Check if timer event has occurred */
check_timer_event();
}
/*-------------------------------------------------------------------*/
/* 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 */
/* */
/*-------------------------------------------------------------------*/
void check_timer_event(void)
{
int cpu; /* CPU counter */
REGS *regs; /* -> CPU register context */
PSA_3XX *psa; /* -> Prefixed storage area */
S32 itimer; /* Interval timer value */
S32 olditimer; /* Previous interval timer */
#if defined(OPTION_MIPS_COUNTING) && ( defined(_FEATURE_SIE) || defined(_FEATURE_ECPSVM) )
int itimer_diff; /* TOD difference in TU */
#endif
U32 intmask = 0; /* Interrupt CPU mask */
/* Access the diffent register contexts with the intlock held */
obtain_lock (&sysblk.intlock);
/* Check for [1] clock comparator, [2] cpu timer, and
[3] interval timer interrupts for each CPU.
Note that we do *not* check for the macro
_FEATURE_CPU_RECONFIG because we are not running
in an ARCH_DEP context */
for (cpu = 0; cpu < MAX_CPU_ENGINES; cpu++)
{
/* Ignore this CPU if it is not started */
if((sysblk.regs[cpu].cpumask & sysblk.started_mask) == 0)
continue;
/* Point to the CPU register context */
regs = sysblk.regs + cpu;
/*-------------------------------------------*
* [1] Check for clock comparator interrupt *
*-------------------------------------------*/
if ((sysblk.todclk + regs->todoffset) > regs->clkc)
{
if (!IS_IC_CLKC(regs))
{
ON_IC_CLKC(regs);
intmask |= regs->cpumask;
}
}
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((sysblk.todclk + regs->guestregs->todoffset) > regs->guestregs->clkc)
{
ON_IC_CLKC(regs->guestregs);
intmask |= regs->cpumask;
}
else
OFF_IC_CLKC(regs->guestregs);
}
#endif /*defined(_FEATURE_SIE)*/
/*-------------------------------------------*
* [2] Decrement the CPU timer for each CPU *
*-------------------------------------------*/
(S64)regs->ptimer -= (S64)sysblk.todclock_diff << 8;
/* Set interrupt flag if the CPU timer is negative */
if ((S64)regs->ptimer < 0)
{
if (!IS_IC_PTIMER(regs))
{
ON_IC_PTIMER(regs);
intmask |= regs->cpumask;
}
}
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)
{
/* Decrement the guest CPU timer */
(S64)regs->guestregs->ptimer -= (S64)sysblk.todclock_diff << 8;
/* Set interrupt flag if the CPU timer is negative */
if ((S64)regs->guestregs->ptimer < 0)
{
ON_IC_PTIMER(regs->guestregs);
intmask |= regs->cpumask;
}
else
OFF_IC_PTIMER(regs->guestregs);
}
#endif /*defined(_FEATURE_SIE)*/
/*-------------------------------------------*
* [3] Check for interval timer interrupt *
*-------------------------------------------*/
#if defined(OPTION_MIPS_COUNTING) && ( defined(_FEATURE_SIE) || defined(_FEATURE_ECPSVM) )
/* Calculate diff in interval timer units */
itimer_diff = (int)((3*sysblk.todclock_diff)/625);
#endif
if(regs->arch_mode == ARCH_370)
{
/* Point to PSA in main storage */
psa = (PSA_3XX*)(regs->mainstor + regs->PX);
/* Decrement the location 80 timer */
FETCH_FW(itimer,psa->inttimer);
olditimer = itimer;
/* The interval timer is decremented as though bit 23 is
decremented by one every 1/300 of a second. This comes
out to subtracting 768 (X'300') every 1/100 of a second.
76800/CLK_TCK comes out to 768 on Intel versions of
Linux, where the clock ticks every 1/100 second; it
comes out to 75 on the Alpha, with its 1024/second
tick interval. See 370 POO page 4-29. (ESA doesn't
even have an interval timer.) */
#if defined(OPTION_MIPS_COUNTING) && ( defined(_FEATURE_SIE) || defined(_FEATURE_ECPSVM) )
itimer -= itimer_diff;
#else
itimer -= 76800 / CLK_TCK;
#endif
STORE_FW(psa->inttimer,itimer);
/* Set interrupt flag and interval timer interrupt pending
if the interval timer went from positive to negative */
if (itimer < 0 && olditimer >= 0)
{
#if defined(_FEATURE_ECPSVM)
regs->rtimerint=1; /* To resolve concurrent V/R Int Timer Ints */
#endif
ON_IC_ITIMER(regs);
intmask |= regs->cpumask;
}
#if defined(_FEATURE_ECPSVM)
#if defined(OPTION_MIPS_COUNTING)
if(ecpsvm_testvtimer(regs,itimer_diff)==0)
#else /* OPTION_MIPS_COUNTING */
if(ecpsvm_testvtimer(regs,76800 / CLK_TCK)==0)
#endif /* OPTION_MIPS_COUNTING */
{
ON_IC_ITIMER(regs);
intmask |= regs->cpumask;
}
#endif /* _FEATURE_ECPSVM */
} /*if(regs->arch_mode == ARCH_370)*/
#if defined(_FEATURE_SIE)
/* When running under SIE also update the SIE copy */
if(regs->sie_active)
{
if((regs->guestregs->siebk->m & SIE_M_370)
&& !(regs->guestregs->siebk->m & SIE_M_ITMOF))
{
/* Decrement the location 80 timer */
FETCH_FW(itimer,regs->guestregs->sie_psa->inttimer);
olditimer = itimer;
#if defined(OPTION_MIPS_COUNTING)
itimer -= itimer_diff;
#else
itimer -= 76800 / CLK_TCK;
#endif
STORE_FW(regs->guestregs->sie_psa->inttimer,itimer);
/* Set interrupt flag and interval timer interrupt pending
if the interval timer went from positive to negative */
if (itimer < 0 && olditimer >= 0)
{
ON_IC_ITIMER(regs->guestregs);
intmask |= regs->cpumask;
}
}
}
#endif /*defined(_FEATURE_SIE)*/
} /* end for(cpu) */
/* If a timer interrupt condition was detected for any CPU
then wake up those CPUs if they are waiting */
WAKEUP_WAITING_CPUS (intmask, CPUSTATE_STARTED);
release_lock(&sysblk.intlock);
} /* end function check_timer_event */
/*-------------------------------------------------------------------*/
/* TOD clock and timer thread */
/* */
/* This function runs as a separate thread. It wakes up every */
/* x milliseconds, 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 msecctr = 0; /* Millisecond counter */
int cpu; /* CPU counter */
REGS *regs; /* -> CPU register context */
U64 prev = 0; /* Previous TOD clock value */
U64 diff; /* Difference between new and
previous TOD clock values */
U64 waittime; /* CPU wait time in interval */
U64 now = 0; /* Current time of day (us) */
U64 then; /* Previous time of day (us) */
int interval; /* Interval (us) */
#endif /*OPTION_MIPS_COUNTING*/
struct timeval tv; /* Structure for gettimeofday
and select function calls */
UNREFERENCED(argp);
/* Display thread started message on control panel */
logmsg (_("HHCCP012I Timer thread started: tid="TIDPAT", pid=%d\n"),
thread_id(), getpid());
#ifdef OPTION_TODCLOCK_DRAG_FACTOR
/* Get current time */
gettimeofday (&tv, NULL);
/* Load number of seconds since 00:00:00 01 Jan 1970 */
sysblk.todclock_init = (U64)tv.tv_sec;
/* Convert to microseconds */
sysblk.todclock_init = sysblk.todclock_init * 1000000 + tv.tv_usec;
#endif /*OPTION_TODCLOCK_DRAG_FACTOR*/
while (sysblk.numcpu)
{
/* Obtain the TOD lock */
obtain_lock (&sysblk.todlock);
/* Update TOD clock */
update_TOD_clock();
#ifdef OPTION_MIPS_COUNTING
/* Calculate MIPS rate and percentage CPU busy */
/* Get the difference between the last TOD saved and this one */
diff = (prev == 0 ? 0 : sysblk.todclk - prev);
/* Save the old TOD clock value */
prev = sysblk.todclk;
/* Shift the epoch out of the difference for the CPU timer */
diff <<= 8;
msecctr += (int)(diff/4096000);
if (msecctr > 999)
{
/* Access the diffent register contexts with the intlock held */
obtain_lock(&sysblk.intlock);
/* Get current time, we may have had to wait for the intlock */
then = now;
gettimeofday (&tv, NULL);
now = (U64)tv.tv_sec;
now = now * 1000000 + tv.tv_usec;
interval = (int)(now - then);
#if defined(OPTION_SHARED_DEVICES)
sysblk.shrdrate = sysblk.shrdcount;
sysblk.shrdcount = 0;
#endif
for (cpu = 0; cpu < MAX_CPU_ENGINES; cpu++)
{
/* Point to the CPU register context */
regs = sysblk.regs + cpu;
/* 0% if no cpu thread or first time thru */
if (regs->cputid == 0 || then == 0 || regs->waittod == 0)
{
regs->mipsrate = regs->siosrate = 0;
regs->cpupct = 0.0;
continue;
}
/* Calculate instructions/millisecond for this CPU */
regs->mipsrate =
((regs->instcount - regs->prevcount)*1000) / interval;
regs->siosrate = regs->siocount;
/* Save the instruction counter */
regs->prevcount = regs->instcount;
regs->siocount = 0;
/* Calculate CPU busy percentage */
waittime = regs->waittime;
if ((sysblk.waitmask & regs->cpumask) != 0)
waittime += now - regs->waittod;
regs->cpupct = ((interval - waittime)*1.0) / (interval*1.0);
/* Reset the wait values */
regs->waittime = 0;
regs->waittod = now;
} /* end for(cpu) */
/* Reset the millisecond counter */
msecctr = 0;
/* Release the intlock */
release_lock(&sysblk.intlock);
} /* end if(msecctr) */
#endif /*OPTION_MIPS_COUNTING*/
/* Release the TOD lock */
release_lock (&sysblk.todlock);
/* Sleep for one system clock tick by specifying a one-microsecond
delay, which will get stretched out to the next clock tick */
tv.tv_sec = 0;
tv.tv_usec = 1;
select (0, NULL, NULL, NULL, &tv);
} /* end while */
logmsg (_("HHCCP013I Timer thread ended\n"));
return NULL;
} /* end function timer_update_thread */