@@ -29,16 +29,70 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
29
29
*
30
30
*/
31
31
32
- #include <assert.h>
32
+ #include <string.h>
33
+ #include <errno.h>
33
34
#include <ipxe/timer.h>
35
+ #include <ipxe/cpuid.h>
34
36
#include <ipxe/pit8254.h>
35
37
38
+ /** Number of microseconds to use for TSC calibration */
39
+ #define TSC_CALIBRATE_US 1024
40
+
41
+ /** TSC increment per microsecond */
42
+ static unsigned long tsc_per_us ;
43
+
44
+ /** Minimum resolution for scaled TSC timer */
45
+ #define TSC_SCALED_HZ 32
46
+
47
+ /** TSC scale (expressed as a bit shift)
48
+ *
49
+ * We use this to avoid the need for 64-bit divsion on 32-bit systems.
50
+ */
51
+ static unsigned int tsc_scale ;
52
+
53
+ /** Number of timer ticks per scaled TSC increment */
54
+ static unsigned long ticks_per_scaled_tsc ;
55
+
56
+ /** Colour for debug messages */
57
+ #define colour &tsc_per_us
58
+
59
+ /**
60
+ * Get raw TSC value
61
+ *
62
+ * @ret tsc Raw TSC value
63
+ */
64
+ static inline __always_inline unsigned long rdtsc_raw ( void ) {
65
+ unsigned long raw ;
66
+
67
+ __asm__ __volatile__ ( "rdtsc\n\t" : "=a" ( raw ) : : "edx" );
68
+ return raw ;
69
+ }
70
+
71
+ /**
72
+ * Get TSC value, shifted to avoid rollover within a realistic timescale
73
+ *
74
+ * @ret tsc Scaled TSC value
75
+ */
76
+ static inline __always_inline unsigned long rdtsc_scaled ( void ) {
77
+ unsigned long scaled ;
78
+
79
+ __asm__ __volatile__ ( "rdtsc\n\t"
80
+ "shrdl %b1, %%edx, %%eax\n\t"
81
+ : "=a" ( scaled ) : "c" ( tsc_scale ) : "edx" );
82
+ return scaled ;
83
+ }
84
+
36
85
/**
37
- * Number of TSC ticks per microsecond
86
+ * Get current system time in ticks
38
87
*
39
- * This is calibrated on the first use of the timer.
88
+ * @ret ticks Current time, in ticks
40
89
*/
41
- static unsigned long rdtsc_ticks_per_usec ;
90
+ static unsigned long rdtsc_currticks ( void ) {
91
+ unsigned long scaled ;
92
+
93
+ scaled = rdtsc_scaled ();
94
+ return ( scaled * ticks_per_scaled_tsc );
95
+ }
42
96
43
97
/**
44
98
* Delay for a fixed number of microseconds
@@ -48,47 +102,76 @@ static unsigned long rdtsc_ticks_per_usec;
48
102
static void rdtsc_udelay ( unsigned long usecs ) {
49
103
unsigned long start ;
50
104
unsigned long elapsed ;
105
+ unsigned long threshold ;
51
106
52
- /* Sanity guard, since we may divide by this */
53
- if ( ! usecs )
54
- usecs = 1 ;
55
-
56
- start = currticks ();
57
- if ( rdtsc_ticks_per_usec ) {
58
- /* Already calibrated; busy-wait until done */
59
- do {
60
- elapsed = ( currticks () - start );
61
- } while ( elapsed < ( usecs * rdtsc_ticks_per_usec ) );
62
- } else {
63
- /* Not yet calibrated; use 8254 PIT and calibrate
64
- * based on result.
65
- */
66
- pit8254_udelay ( usecs );
67
- elapsed = ( currticks () - start );
68
- rdtsc_ticks_per_usec = ( elapsed / usecs );
69
- DBG ( "RDTSC timer calibrated: %ld ticks in %ld usecs "
70
- "(%ld MHz)\n" , elapsed , usecs ,
71
- ( rdtsc_ticks_per_usec << TSC_SHIFT ) );
72
- }
107
+ start = rdtsc_raw ();
108
+ threshold = ( usecs * tsc_per_us );
109
+ do {
110
+ elapsed = ( rdtsc_raw () - start );
111
+ } while ( elapsed < threshold );
73
112
}
74
113
75
114
/**
76
- * Get number of ticks per second
115
+ * Probe RDTSC timer
77
116
*
78
- * @ret ticks_per_sec Number of ticks per second
117
+ * @ret rc Return status code
79
118
*/
80
- static unsigned long rdtsc_ticks_per_sec ( void ) {
119
+ static int rdtsc_probe ( void ) {
120
+ unsigned long before ;
121
+ unsigned long after ;
122
+ unsigned long elapsed ;
123
+ uint32_t apm ;
124
+ uint32_t discard_a ;
125
+ uint32_t discard_b ;
126
+ uint32_t discard_c ;
127
+ int rc ;
81
128
82
- /* Calibrate timer, if not already done */
83
- if ( ! rdtsc_ticks_per_usec )
84
- udelay ( 1 );
129
+ /* Check that TSC is invariant */
130
+ if ( ( rc = cpuid_supported ( CPUID_APM ) ) != 0 ) {
131
+ DBGC ( colour , "RDTSC cannot determine APM features: %s\n" ,
132
+ strerror ( rc ) );
133
+ return rc ;
134
+ }
135
+ cpuid ( CPUID_APM , & discard_a , & discard_b , & discard_c , & apm );
136
+ if ( ! ( apm & CPUID_APM_EDX_TSC_INVARIANT ) ) {
137
+ DBGC ( colour , "RDTSC has non-invariant TSC (%#08x)\n" ,
138
+ apm );
139
+ return - ENOTTY ;
140
+ }
85
141
86
- /* Sanity check */
87
- assert ( rdtsc_ticks_per_usec != 0 );
142
+ /* Calibrate udelay() timer via 8254 PIT */
143
+ before = rdtsc_raw ();
144
+ pit8254_udelay ( TSC_CALIBRATE_US );
145
+ after = rdtsc_raw ();
146
+ elapsed = ( after - before );
147
+ tsc_per_us = ( elapsed / TSC_CALIBRATE_US );
148
+ if ( ! tsc_per_us ) {
149
+ DBGC ( colour , "RDTSC has zero TSC per microsecond\n" );
150
+ return - EIO ;
151
+ }
152
+
153
+ /* Calibrate currticks() scaling factor */
154
+ tsc_scale = 31 ;
155
+ ticks_per_scaled_tsc = ( ( 1UL << tsc_scale ) /
156
+ ( tsc_per_us * ( 1000000 / TICKS_PER_SEC ) ) );
157
+ while ( ticks_per_scaled_tsc > ( TICKS_PER_SEC / TSC_SCALED_HZ ) ) {
158
+ tsc_scale -- ;
159
+ ticks_per_scaled_tsc >>= 1 ;
160
+ }
161
+ DBGC ( colour , "RDTSC has %ld tsc per us, %ld ticks per 2^%d tsc\n" ,
162
+ tsc_per_us , ticks_per_scaled_tsc , tsc_scale );
163
+ if ( ! ticks_per_scaled_tsc ) {
164
+ DBGC ( colour , "RDTSC has zero ticks per TSC\n" );
165
+ return - EIO ;
166
+ }
88
167
89
- return ( rdtsc_ticks_per_usec * 1000 * 1000 ) ;
168
+ return 0 ;
90
169
}
91
170
92
- PROVIDE_TIMER ( rdtsc , udelay , rdtsc_udelay );
93
- PROVIDE_TIMER_INLINE ( rdtsc , currticks );
94
- PROVIDE_TIMER ( rdtsc , ticks_per_sec , rdtsc_ticks_per_sec );
171
+ /** RDTSC timer */
172
+ struct timer rdtsc_timer __timer ( TIMER_PREFERRED ) = {
173
+ .name = "rdtsc" ,
174
+ .probe = rdtsc_probe ,
175
+ .currticks = rdtsc_currticks ,
176
+ .udelay = rdtsc_udelay ,
177
+ };
0 commit comments