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:
Andriy Gapon 2018-05-25 07:33:20 +00:00
parent 620b779158
commit 279be68bfd
4 changed files with 35 additions and 4 deletions

View File

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

View File

@ -52,6 +52,7 @@ __FBSDID("$FreeBSD$");
#include <sys/timetc.h>
#if defined(__i386__) || defined(__amd64__)
#include <machine/clock.h>
#include <machine/pci_cfgreg.h>
#endif
#include <machine/resource.h>
@ -3040,6 +3041,10 @@ acpi_EnterSleepState(struct acpi_softc *sc, int state)
if (slp_state >= ACPI_SS_SLP_PREP)
AcpiLeaveSleepState(state);
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_enable_fixed_events(sc);
}

View File

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

View File

@ -451,7 +451,7 @@ adj_smp_tsc(void *arg)
}
static int
test_tsc(void)
test_tsc(int adj_max_count)
{
uint64_t *data, *tsc;
u_int i, size, adj;
@ -467,7 +467,7 @@ test_tsc(void)
smp_tsc = 1; /* XXX */
smp_rendezvous(smp_no_rendezvous_barrier, comp_smp_tsc,
smp_no_rendezvous_barrier, data);
if (!smp_tsc && adj < smp_tsc_adjust) {
if (!smp_tsc && adj < adj_max_count) {
adj++;
smp_rendezvous(smp_no_rendezvous_barrier, adj_smp_tsc,
smp_no_rendezvous_barrier, data);
@ -512,7 +512,7 @@ test_tsc(void)
* on uniprocessor kernel.
*/
static int
test_tsc(void)
test_tsc(int adj_max_count __unused)
{
return (0);
@ -579,7 +579,7 @@ init_TSC_tc(void)
* environments, so it is set to a negative quality in those cases.
*/
if (mp_ncpus > 1)
tsc_timecounter.tc_quality = test_tsc();
tsc_timecounter.tc_quality = test_tsc(smp_tsc_adjust);
else if (tsc_is_invariant)
tsc_timecounter.tc_quality = 1000;
max_freq >>= tsc_shift;
@ -615,6 +615,30 @@ init_TSC_tc(void)
}
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
* use this to update CPU accounting in case it got a lower estimate at boot.