|
root / base / usr / src / uts / i86pc / os / tscc_hpet.c
tscc_hpet.c C 114 lines 3.2 KB
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2020 Joyent, Inc.
 */

#include <sys/tsc.h>
#include <sys/prom_debug.h>
#include <sys/hpet.h>
#include <sys/clock.h>

/*
 * The amount of time (in microseconds) between tsc samples. This is
 * somewhat arbitrary, but seems reasonable. A frequency of 1GHz is
 * 1,000,000,000 ticks / sec (10^9). 100us is 10^(-6) * 10^2 => 10^(-4), so
 * 100us would represent 10^5 (100,000) ticks.
 */
#define	HPET_SAMPLE_INTERVAL_US (100)

/*
 * The same as above, but in nanoseconds (for ease in converting to HPET
 * ticks)
 */
#define	HPET_SAMPLE_INTERVAL_NS (USEC2NSEC(HPET_SAMPLE_INTERVAL_US))

/* The amount of HPET sample ticks to wait */
#define	HPET_SAMPLE_TICKS (HRTIME_TO_HPET_TICKS(HPET_SAMPLE_INTERVAL_NS))

#define	TSC_NUM_SAMPLES 10

static boolean_t
tsc_calibrate_hpet(uint64_t *freqp)
{
	uint64_t hpet_sum = 0;
	uint64_t tsc_sum = 0;
	uint_t i;

	PRM_POINT("Attempting to use HPET for TSC calibration...");

	if (hpet_early_init() != DDI_SUCCESS)
		return (B_FALSE);

	/*
	 * The expansion of HPET_SAMPLE_TICKS (specifically
	 * HRTIME_TO_HPET_TICKS) uses the HPET period to calculate the number
	 * of HPET ticks for the given time period. Therefore, we cannot
	 * set hpet_num_ticks until after the early HPET initialization has
	 * been performed by hpet_early_init() (and the HPET period is known).
	 */
	const uint64_t hpet_num_ticks = HPET_SAMPLE_TICKS;

	for (i = 0; i < TSC_NUM_SAMPLES; i++) {
		uint64_t hpet_now, hpet_end;
		uint64_t tsc_start, tsc_end;

		hpet_now = hpet_read_timer();
		hpet_end = hpet_now + hpet_num_ticks;

		tsc_start = tsc_read();
		while (hpet_now < hpet_end)
			hpet_now = hpet_read_timer();

		tsc_end = tsc_read();

		/*
		 * If our TSC isn't advancing after 100us, we're pretty much
		 * hosed.
		 */
		VERIFY3P(tsc_end, >, tsc_start);

		tsc_sum += tsc_end - tsc_start;

		/*
		 * We likely did not end exactly HPET_SAMPLE_TICKS after
		 * we started, so save the actual amount.
		 */
		hpet_sum += hpet_num_ticks + hpet_now - hpet_end;
	}

	uint64_t hpet_avg = hpet_sum / TSC_NUM_SAMPLES;
	uint64_t tsc_avg = tsc_sum / TSC_NUM_SAMPLES;
	uint64_t hpet_ns = hpet_avg * hpet_info.period / HPET_FEMTO_TO_NANO;

	PRM_POINT("HPET calibration complete");

	*freqp = tsc_avg * NANOSEC / hpet_ns;
	PRM_DEBUG(*freqp);

	return (B_TRUE);
}

/*
 * Reports from the field suggest that HPET calibration is currently producing
 * a substantially greater error than PIT calibration on a wide variety of
 * systems.  We are placing it last in the preference order until that can be
 * resolved.  HPET calibration cannot be disabled completely, as some systems
 * no longer emulate the PIT at all.
 */
static tsc_calibrate_t tsc_calibration_hpet = {
	.tscc_source = "HPET",
	.tscc_preference = 1,
	.tscc_calibrate = tsc_calibrate_hpet,
};
TSC_CALIBRATION_SOURCE(tsc_calibration_hpet);