re-synchronize TSC-s on SMP systems after resume, if necessary

The TSC-s are checked and synchronized only if they were good
originally.  That is, invariant, synchronized, etc.

This is necessary on an AMD-based system where after a wakeup from STR I
see that BSP clock differs from AP clocks by a count that roughly
corresponds to one second.  The APs are in sync with each other.  Not
sure if this is a hardware quirk or a firmware bug.

This is what I see after a resume with this change:
    SMP: passed TSC synchronization test after adjustment
    acpi_timer0: restoring timecounter, ACPI-fast -> TSC-low

Reviewed by:	kib
MFC after:	3 weeks
Differential Revision: https://reviews.freebsd.org/D15551
This commit is contained in:
avg 2018-05-25 07:33:20 +00:00
parent 80919a9e8a
commit 546f863d51
4 changed files with 35 additions and 4 deletions

View File

@ -34,6 +34,7 @@ void clock_init(void);
void startrtclock(void); void startrtclock(void);
void init_TSC(void); void init_TSC(void);
void resume_TSC(void);
#define HAS_TIMER_SPKR 1 #define HAS_TIMER_SPKR 1
int timer_spkr_acquire(void); int timer_spkr_acquire(void);

View File

@ -52,6 +52,7 @@ __FBSDID("$FreeBSD$");
#include <sys/timetc.h> #include <sys/timetc.h>
#if defined(__i386__) || defined(__amd64__) #if defined(__i386__) || defined(__amd64__)
#include <machine/clock.h>
#include <machine/pci_cfgreg.h> #include <machine/pci_cfgreg.h>
#endif #endif
#include <machine/resource.h> #include <machine/resource.h>
@ -3040,6 +3041,10 @@ backout:
if (slp_state >= ACPI_SS_SLP_PREP) if (slp_state >= ACPI_SS_SLP_PREP)
AcpiLeaveSleepState(state); AcpiLeaveSleepState(state);
if (slp_state >= ACPI_SS_SLEPT) { if (slp_state >= ACPI_SS_SLEPT) {
#if defined(__i386__) || defined(__amd64__)
/* NB: we are still using ACPI timecounter at this point. */
resume_TSC();
#endif
acpi_resync_clock(sc); acpi_resync_clock(sc);
acpi_enable_fixed_events(sc); acpi_enable_fixed_events(sc);
} }

View File

@ -32,6 +32,7 @@ void clock_init(void);
void startrtclock(void); void startrtclock(void);
void timer_restore(void); void timer_restore(void);
void init_TSC(void); void init_TSC(void);
void resume_TSC(void);
#define HAS_TIMER_SPKR 1 #define HAS_TIMER_SPKR 1
int timer_spkr_acquire(void); int timer_spkr_acquire(void);

View File

@ -451,7 +451,7 @@ adj_smp_tsc(void *arg)
} }
static int static int
test_tsc(void) test_tsc(int adj_max_count)
{ {
uint64_t *data, *tsc; uint64_t *data, *tsc;
u_int i, size, adj; u_int i, size, adj;
@ -467,7 +467,7 @@ retry:
smp_tsc = 1; /* XXX */ smp_tsc = 1; /* XXX */
smp_rendezvous(smp_no_rendezvous_barrier, comp_smp_tsc, smp_rendezvous(smp_no_rendezvous_barrier, comp_smp_tsc,
smp_no_rendezvous_barrier, data); smp_no_rendezvous_barrier, data);
if (!smp_tsc && adj < smp_tsc_adjust) { if (!smp_tsc && adj < adj_max_count) {
adj++; adj++;
smp_rendezvous(smp_no_rendezvous_barrier, adj_smp_tsc, smp_rendezvous(smp_no_rendezvous_barrier, adj_smp_tsc,
smp_no_rendezvous_barrier, data); smp_no_rendezvous_barrier, data);
@ -512,7 +512,7 @@ retry:
* on uniprocessor kernel. * on uniprocessor kernel.
*/ */
static int static int
test_tsc(void) test_tsc(int adj_max_count __unused)
{ {
return (0); return (0);
@ -579,7 +579,7 @@ init_TSC_tc(void)
* environments, so it is set to a negative quality in those cases. * environments, so it is set to a negative quality in those cases.
*/ */
if (mp_ncpus > 1) if (mp_ncpus > 1)
tsc_timecounter.tc_quality = test_tsc(); tsc_timecounter.tc_quality = test_tsc(smp_tsc_adjust);
else if (tsc_is_invariant) else if (tsc_is_invariant)
tsc_timecounter.tc_quality = 1000; tsc_timecounter.tc_quality = 1000;
max_freq >>= tsc_shift; max_freq >>= tsc_shift;
@ -615,6 +615,30 @@ init:
} }
SYSINIT(tsc_tc, SI_SUB_SMP, SI_ORDER_ANY, init_TSC_tc, NULL); SYSINIT(tsc_tc, SI_SUB_SMP, SI_ORDER_ANY, init_TSC_tc, NULL);
void
resume_TSC(void)
{
int quality;
/* If TSC was not good on boot, it is unlikely to become good now. */
if (tsc_timecounter.tc_quality < 0)
return;
/* Nothing to do with UP. */
if (mp_ncpus < 2)
return;
/*
* If TSC was good, a single synchronization should be enough,
* but honour smp_tsc_adjust if it's set.
*/
quality = test_tsc(MAX(smp_tsc_adjust, 1));
if (quality != tsc_timecounter.tc_quality) {
printf("TSC timecounter quality changed: %d -> %d\n",
tsc_timecounter.tc_quality, quality);
tsc_timecounter.tc_quality = quality;
}
}
/* /*
* When cpufreq levels change, find out about the (new) max frequency. We * When cpufreq levels change, find out about the (new) max frequency. We
* use this to update CPU accounting in case it got a lower estimate at boot. * use this to update CPU accounting in case it got a lower estimate at boot.