/* CLOCK.C (c) Copyright Jan Jaeger, 2000-2006 */ /* TOD Clock functions */ /* The emulated hardware clock is based on the host clock, adjusted */ /* by means of an offset and a steering rate. */ #include "hstdinc.h" #if !defined(_HENGINE_DLL_) #define _HENGINE_DLL_ #endif #include "hercules.h" #include "opcode.h" #include "inline.h" #if !defined(_CLOCK_C_) #define _CLOCK_C_ #include "clock.h" // static int clock_state = CC_CLOCK_SET; static CSR old; static CSR new; static CSR *current = &new; void csr_reset() { new.start_time = 0; new.base_offset = 0; new.fine_s_rate = 0; new.gross_s_rate = 0; current = &new; old = new; } static U64 universal_tod; static U64 universal_clock(void) /* really: any clock used as a base */ { struct timeval tv; gettimeofday (&tv, NULL); /* Load number of seconds since 00:00:00 01 Jan 1970 */ universal_tod = (U64)tv.tv_sec; universal_tod += SECONDS_IN_SEVENTY_YEARS; /* Convert to microseconds */ universal_tod = (universal_tod * 1000000) + tv.tv_usec; /* Shift left 4 bits so that bits 0-7=TOD Clock Epoch, bits 8-59=TOD Clock bits 0-51, bits 60-63=zero */ universal_tod <<= 4; return universal_tod; } /* The hercules hardware clock, based on the universal clock, but */ /* running at its own speed as optionally set by set_tod_steering() */ /* The hardware clock returns a unique value */ static double hw_steering = 0.0; /* Current TOD clock steering rate */ static U64 hw_episode; /* TOD of start of steering episode */ static S64 hw_offset = 0; /* Current offset between TOD and HW */ // static U64 hw_tod = 0; /* Globally defined in clock.h */ static inline U64 hw_adjust(U64 base_tod) { /* Apply hardware offset, this is the offset achieved by all previous steering episodes */ base_tod += hw_offset; /* Apply the steering offset from the current steering episode */ base_tod += (S64)(base_tod - hw_episode) * hw_steering; /* Ensure that the clock returns a unique value */ if(hw_tod < base_tod) return base_tod; else return hw_tod += 0x10; } U64 hw_clock(void) { U64 temp_tod; /* Get the time of day (GMT) */ temp_tod = universal_clock(); obtain_lock(&sysblk.todlock); /* Ajust speed and ensure uniqueness */ hw_tod = hw_adjust(temp_tod); release_lock(&sysblk.todlock); return hw_tod; } static U64 hw_clock_l(void) { hw_tod = hw_adjust(universal_clock()); return hw_tod; } /* set_tod_steering(double) sets a new steering rate. */ /* When a new steering episode begins, the offset is adjusted, */ /* and the new steering rate takes effect */ void set_tod_steering(double steering) { obtain_lock(&sysblk.todlock); hw_offset = hw_clock_l() - universal_tod; hw_episode = hw_tod; hw_steering = steering; release_lock(&sysblk.todlock); } /* Start a new episode */ static inline void start_new_episode() { hw_offset = hw_tod - universal_tod; hw_episode = hw_tod; new.start_time = hw_episode; hw_steering = ldexp(2,-44) * (S32)(new.fine_s_rate + new.gross_s_rate); current = &new; } /* Prepare for a new episode */ static inline void prepare_new_episode() { if(current == &new) { old = new; current = &old; } } /* Ajust the epoch for all active cpu's in the configuration */ static U64 adjust_epoch_cpu_all(U64 epoch) { int cpu; /* Update the TOD clock of all CPU's in the configuration as we simulate 1 shared TOD clock, and do not support the TOD clock sync check */ for (cpu = 0; cpu < MAX_CPU; cpu++) { obtain_lock(&sysblk.cpulock[cpu]); if (IS_CPU_ONLINE(cpu)) sysblk.regs[cpu]->tod_epoch = epoch; release_lock(&sysblk.cpulock[cpu]); } return epoch; } double get_tod_steering(void) { return hw_steering; } void set_tod_epoch(S64 epoch) { obtain_lock(&sysblk.todlock); csr_reset(); tod_epoch = epoch; release_lock(&sysblk.todlock); adjust_epoch_cpu_all(epoch); } void adjust_tod_epoch(S64 epoch) { obtain_lock(&sysblk.todlock); csr_reset(); tod_epoch += epoch; release_lock(&sysblk.todlock); adjust_epoch_cpu_all(tod_epoch); } void set_tod_clock(U64 tod) { set_tod_epoch(tod - hw_clock()); } S64 get_tod_epoch() { return tod_epoch; } static void set_gross_steering_rate(S32 gsr) { obtain_lock(&sysblk.todlock); prepare_new_episode(); new.gross_s_rate = gsr; release_lock(&sysblk.todlock); } static void set_fine_steering_rate(S32 fsr) { obtain_lock(&sysblk.todlock); prepare_new_episode(); new.fine_s_rate = fsr; release_lock(&sysblk.todlock); } static void set_tod_offset(S64 offset) { obtain_lock(&sysblk.todlock); prepare_new_episode(); new.base_offset = offset; release_lock(&sysblk.todlock); } static void adjust_tod_offset(S64 offset) { obtain_lock(&sysblk.todlock); prepare_new_episode(); new.base_offset = old.base_offset + offset; release_lock(&sysblk.todlock); } /* The cpu timer is internally kept as an offset to the hw_clock() * the cpu timer counts down as the clock approaches the timer epoch */ void set_cpu_timer(REGS *regs, S64 timer) { regs->cpu_timer = (timer >> 8) + hw_clock(); } S64 cpu_timer(REGS *regs) { S64 timer; timer = (regs->cpu_timer - hw_clock()) << 8; return timer; } U64 tod_clock(REGS *regs) { U64 current_tod; obtain_lock(&sysblk.todlock); current_tod = hw_clock_l(); /* If we are in the old episode, and the new episode has arrived then we must take action to start the new episode */ if(current == &old) start_new_episode(); /* Set the clock to the new updated value with offset applied */ current_tod += current->base_offset; tod_value = current_tod; release_lock(&sysblk.todlock); return current_tod + regs->tod_epoch; } #if defined(_FEATURE_INTERVAL_TIMER) #if defined(_FEATURE_ECPSVM) static inline S32 ecps_vtimer(REGS *regs) { return (S32)TOD_TO_ITIMER((S64)(regs->ecps_vtimer - hw_clock())); } static inline void set_ecps_vtimer(REGS *regs, S32 vtimer) { regs->ecps_vtimer = (U64)(hw_clock() + ITIMER_TO_TOD(vtimer)); regs->ecps_oldtmr = vtimer; } #endif /*defined(_FEATURE_ECPSVM)*/ S32 int_timer(REGS *regs) { return (S32)TOD_TO_ITIMER((S64)(regs->int_timer - hw_clock())); } void set_int_timer(REGS *regs, S32 itimer) { regs->int_timer = (U64)(hw_clock() + ITIMER_TO_TOD(itimer)); regs->old_timer = itimer; } int chk_int_timer(REGS *regs) { S32 itimer; int pending = 0; itimer = int_timer(regs); if(itimer < 0 && regs->old_timer >= 0) { ON_IC_ITIMER(regs); pending = 1; } #if defined(_FEATURE_ECPSVM) if(regs->ecps_vtmrpt) { itimer = ecps_vtimer(regs); if(itimer < 0 && regs->ecps_oldtmr >= 0) { ON_IC_ECPSVTIMER(regs); pending = 1; } } #endif /*defined(_FEATURE_ECPSVM)*/ return pending; } #endif /*defined(_FEATURE_INTERVAL_TIMER)*/ /*-------------------------------------------------------------------*/ /* 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. */ /* */ /* update_tod_clock() returns the tod delta, by which the cpu timer */ /* has been adjusted. */ /* */ /*-------------------------------------------------------------------*/ // static U64 tod_value; U64 update_tod_clock(void) { U64 new_clock; obtain_lock(&sysblk.todlock); new_clock = hw_clock_l(); /* If we are in the old episode, and the new episode has arrived then we must take action to start the new episode */ if(current == &old) start_new_episode(); /* Set the clock to the new updated value with offset applied */ new_clock += current->base_offset; tod_value = new_clock; release_lock(&sysblk.todlock); /* Update the timers and check if either a clock related event has become pending */ update_cpu_timer(); return new_clock; } #endif #if defined(FEATURE_INTERVAL_TIMER) void ARCH_DEP(store_int_timer) (REGS *regs) { S32 itimer; FETCH_FW(itimer, regs->psa->inttimer); if(itimer != regs->old_timer) { // ZZ logmsg(D_("Interval timer out of sync, core=%8.8X, internal=%8.8X\n"), itimer, regs->old_timer); set_int_timer(regs, itimer); } else regs->old_timer = itimer = int_timer(regs); STORE_FW(regs->psa->inttimer, itimer); #if defined(FEATURE_ECPSVM) if(regs->ecps_vtmrpt) { FETCH_FW(itimer, regs->ecps_vtmrpt); if(itimer != regs->ecps_oldtmr) { // ZZ logmsg(D_("ECPS vtimer out of sync, core=%8.8X, internal=%8.8X\n"), itimer, regs->ecps_vtimer); set_ecps_vtimer(regs, itimer); } else regs->ecps_oldtmr = itimer = ecps_vtimer(regs); STORE_FW(regs->ecps_vtmrpt, itimer); } #endif /*defined(FEATURE_ECPSVM)*/ chk_int_timer(regs); } void ARCH_DEP(fetch_int_timer) (REGS *regs) { S32 itimer; FETCH_FW(itimer, regs->psa->inttimer); set_int_timer(regs, itimer); #if defined(FEATURE_ECPSVM) if(regs->ecps_vtmrpt) { FETCH_FW(itimer, regs->ecps_vtmrpt); set_ecps_vtimer(regs, itimer); } #endif /*defined(FEATURE_ECPSVM)*/ } #endif #if defined(FEATURE_TOD_CLOCK_STEERING) void ARCH_DEP(set_gross_s_rate) (REGS *regs) { S32 gsr; gsr = ARCH_DEP(vfetch4) (regs->GR(1) & ADDRESS_MAXWRAP(regs), 1, regs); set_gross_steering_rate(gsr); } void ARCH_DEP(set_fine_s_rate) (REGS *regs) { S32 fsr; fsr = ARCH_DEP(vfetch4) (regs->GR(1) & ADDRESS_MAXWRAP(regs), 1, regs); set_fine_steering_rate(fsr); } void ARCH_DEP(set_tod_offset) (REGS *regs) { S64 offset; offset = ARCH_DEP(vfetch8) (regs->GR(1) & ADDRESS_MAXWRAP(regs), 1, regs); set_tod_offset(offset >> 8); } void ARCH_DEP(adjust_tod_offset) (REGS *regs) { S64 offset; offset = ARCH_DEP(vfetch8) (regs->GR(1) & ADDRESS_MAXWRAP(regs), 1, regs); adjust_tod_offset(offset >> 8); } void ARCH_DEP(query_physical_clock) (REGS *regs) { ARCH_DEP(vstore8) (universal_clock() << 8, regs->GR(1) & ADDRESS_MAXWRAP(regs), 1, regs); } void ARCH_DEP(query_steering_information) (REGS *regs) { PTFFQSI qsi; obtain_lock(&sysblk.todlock); STORE_DW(qsi.physclk, universal_clock() << 8); STORE_DW(qsi.oldestart, old.start_time << 8); STORE_DW(qsi.oldebase, old.base_offset << 8); STORE_FW(qsi.oldfsr, old.fine_s_rate ); STORE_FW(qsi.oldgsr, old.gross_s_rate ); STORE_DW(qsi.newestart, new.start_time << 8); STORE_DW(qsi.newebase, new.base_offset << 8); STORE_FW(qsi.newfsr, new.fine_s_rate ); STORE_FW(qsi.newgsr, new.gross_s_rate ); release_lock(&sysblk.todlock); ARCH_DEP(vstorec) (&qsi, sizeof(qsi)-1, regs->GR(1) & ADDRESS_MAXWRAP(regs), 1, regs); } void ARCH_DEP(query_tod_offset) (REGS *regs) { PTFFQTO qto; obtain_lock(&sysblk.todlock); STORE_DW(qto.todoff, (hw_clock_l() - universal_tod) << 8); STORE_DW(qto.physclk, universal_tod << 8); STORE_DW(qto.ltodoff, current->base_offset << 8); STORE_DW(qto.todepoch, regs->tod_epoch << 8); release_lock(&sysblk.todlock); ARCH_DEP(vstorec) (&qto, sizeof(qto)-1, regs->GR(1) & ADDRESS_MAXWRAP(regs), 1, regs); } void ARCH_DEP(query_available_functions) (REGS *regs) { PTFFQAF qaf; STORE_FW(qaf.sb[0] , 0xF0000000); /* Functions 0x00..0x1F */ STORE_FW(qaf.sb[1] , 0x00000000); /* Functions 0x20..0x3F */ STORE_FW(qaf.sb[2] , 0xF0000000); /* Functions 0x40..0x5F */ STORE_FW(qaf.sb[3] , 0x00000000); /* Functions 0x60..0x7F */ ARCH_DEP(vstorec) (&qaf, sizeof(qaf)-1, regs->GR(1) & ADDRESS_MAXWRAP(regs), 1, regs); } #endif /*defined(FEATURE_TOD_CLOCK_STEERING)*/ #if !defined(_GEN_ARCH) #if defined(_ARCHMODE2) #define _GEN_ARCH _ARCHMODE2 #include "clock.c" #endif #if defined(_ARCHMODE3) #undef _GEN_ARCH #define _GEN_ARCH _ARCHMODE3 #include "clock.c" #endif #endif /*!defined(_GEN_ARCH)*/