summaryrefslogtreecommitdiffstats
path: root/src/arch/x86/core/rdtsc_timer.c
blob: ed5151503f0d17d1dc0dad854fea8fcb907001de (plain) (blame)
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
/*
 * Copyright (C) 2008 Michael Brown <mbrown@fensystems.co.uk>.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 *
 * You can also choose to distribute this program under the terms of
 * the Unmodified Binary Distribution Licence (as given in the file
 * COPYING.UBDL), provided that you have satisfied its requirements.
 */

FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );

/** @file
 *
 * RDTSC timer
 *
 */

#include <string.h>
#include <errno.h>
#include <ipxe/timer.h>
#include <ipxe/cpuid.h>
#include <ipxe/pit8254.h>

/** Number of microseconds to use for TSC calibration */
#define TSC_CALIBRATE_US 1024

/** TSC increment per microsecond */
static unsigned long tsc_per_us;

/** Minimum resolution for scaled TSC timer */
#define TSC_SCALED_HZ 32

/** TSC scale (expressed as a bit shift)
 *
 * We use this to avoid the need for 64-bit divsion on 32-bit systems.
 */
static unsigned int tsc_scale;

/** Number of timer ticks per scaled TSC increment */
static unsigned long ticks_per_scaled_tsc;

/** Colour for debug messages */
#define colour &tsc_per_us

/**
 * Get raw TSC value
 *
 * @ret tsc		Raw TSC value
 */
static inline __always_inline unsigned long rdtsc_raw ( void ) {
	unsigned long raw;

	__asm__ __volatile__ ( "rdtsc\n\t" : "=a" ( raw ) : : "edx" );
	return raw;
}

/**
 * Get TSC value, shifted to avoid rollover within a realistic timescale
 *
 * @ret tsc		Scaled TSC value
 */
static inline __always_inline unsigned long rdtsc_scaled ( void ) {
	unsigned long scaled;

	__asm__ __volatile__ ( "rdtsc\n\t"
			       "shrdl %b1, %%edx, %%eax\n\t"
			       : "=a" ( scaled ) : "c" ( tsc_scale ) : "edx" );
	return scaled;
}

/**
 * Get current system time in ticks
 *
 * @ret ticks		Current time, in ticks
 */
static unsigned long rdtsc_currticks ( void ) {
	unsigned long scaled;

	scaled = rdtsc_scaled();
	return ( scaled * ticks_per_scaled_tsc );
}

/**
 * Delay for a fixed number of microseconds
 *
 * @v usecs		Number of microseconds for which to delay
 */
static void rdtsc_udelay ( unsigned long usecs ) {
	unsigned long start;
	unsigned long elapsed;
	unsigned long threshold;

	start = rdtsc_raw();
	threshold = ( usecs * tsc_per_us );
	do {
		elapsed = ( rdtsc_raw() - start );
	} while ( elapsed < threshold );
}

/**
 * Probe RDTSC timer
 *
 * @ret rc		Return status code
 */
static int rdtsc_probe ( void ) {
	unsigned long before;
	unsigned long after;
	unsigned long elapsed;
	uint32_t apm;
	uint32_t discard_a;
	uint32_t discard_b;
	uint32_t discard_c;
	int rc;

	/* Check that TSC is invariant */
	if ( ( rc = cpuid_supported ( CPUID_APM ) ) != 0 ) {
		DBGC ( colour, "RDTSC cannot determine APM features: %s\n",
		       strerror ( rc ) );
		return rc;
	}
	cpuid ( CPUID_APM, &discard_a, &discard_b, &discard_c, &apm );
	if ( ! ( apm & CPUID_APM_EDX_TSC_INVARIANT ) ) {
		DBGC ( colour, "RDTSC has non-invariant TSC (%#08x)\n",
		       apm );
		return -ENOTTY;
	}

	/* Calibrate udelay() timer via 8254 PIT */
	before = rdtsc_raw();
	pit8254_udelay ( TSC_CALIBRATE_US );
	after = rdtsc_raw();
	elapsed = ( after - before );
	tsc_per_us = ( elapsed / TSC_CALIBRATE_US );
	if ( ! tsc_per_us ) {
		DBGC ( colour, "RDTSC has zero TSC per microsecond\n" );
		return -EIO;
	}

	/* Calibrate currticks() scaling factor */
	tsc_scale = 31;
	ticks_per_scaled_tsc = ( ( 1UL << tsc_scale ) /
				 ( tsc_per_us * ( 1000000 / TICKS_PER_SEC ) ) );
	while ( ticks_per_scaled_tsc > ( TICKS_PER_SEC / TSC_SCALED_HZ ) ) {
		tsc_scale--;
		ticks_per_scaled_tsc >>= 1;
	}
	DBGC ( colour, "RDTSC has %ld tsc per us, %ld ticks per 2^%d tsc\n",
	       tsc_per_us, ticks_per_scaled_tsc, tsc_scale );
	if ( ! ticks_per_scaled_tsc ) {
		DBGC ( colour, "RDTSC has zero ticks per TSC\n" );
		return -EIO;
	}

	return 0;
}

/** RDTSC timer */
struct timer rdtsc_timer __timer ( TIMER_PREFERRED ) = {
	.name = "rdtsc",
	.probe = rdtsc_probe,
	.currticks = rdtsc_currticks,
	.udelay = rdtsc_udelay,
};