xref: /dpdk/lib/eal/linux/eal_timer.c (revision dbdf3d5581caa1de40b5952e41d54b64e39536d1)
1 /* SPDX-License-Identifier: BSD-3-Clause
2  * Copyright(c) 2010-2014 Intel Corporation.
3  * Copyright(c) 2012-2013 6WIND S.A.
4  */
5 
6 #include <stdio.h>
7 #include <stdint.h>
8 #include <inttypes.h>
9 #ifdef RTE_LIBEAL_USE_HPET
10 #include <fcntl.h>
11 #include <sys/mman.h>
12 #include <unistd.h>
13 #endif
14 
15 #include <rte_common.h>
16 #include <rte_cycles.h>
17 #include <rte_thread.h>
18 
19 #include "eal_private.h"
20 
21 enum timer_source eal_timer_source = EAL_TIMER_HPET;
22 
23 #ifdef RTE_LIBEAL_USE_HPET
24 
25 #define DEV_HPET "/dev/hpet"
26 
27 /* Maximum number of counters. */
28 #define HPET_TIMER_NUM 3
29 
30 /* General capabilities register */
31 #define CLK_PERIOD_SHIFT     32 /* Clock period shift. */
32 #define CLK_PERIOD_MASK      0xffffffff00000000ULL /* Clock period mask. */
33 
34 /**
35  * HPET timer registers. From the Intel IA-PC HPET (High Precision Event
36  * Timers) Specification.
37  */
38 struct eal_hpet_regs {
39 	/* Memory-mapped, software visible registers */
40 	uint64_t capabilities;      /**< RO General Capabilities Register. */
41 	uint64_t reserved0;         /**< Reserved for future use. */
42 	uint64_t config;            /**< RW General Configuration Register. */
43 	uint64_t reserved1;         /**< Reserved for future use. */
44 	uint64_t isr;               /**< RW Clear General Interrupt Status. */
45 	uint64_t reserved2[25];     /**< Reserved for future use. */
46 	union {
47 		uint64_t counter;   /**< RW Main Counter Value Register. */
48 		struct {
49 			uint32_t counter_l; /**< RW Main Counter Low. */
50 			uint32_t counter_h; /**< RW Main Counter High. */
51 		};
52 	};
53 	uint64_t reserved3;         /**< Reserved for future use. */
54 	struct {
55 		uint64_t config;    /**< RW Timer Config and Capability Reg. */
56 		uint64_t comp;      /**< RW Timer Comparator Value Register. */
57 		uint64_t fsb;       /**< RW FSB Interrupt Route Register. */
58 		uint64_t reserved4; /**< Reserved for future use. */
59 	} timers[HPET_TIMER_NUM]; /**< Set of HPET timers. */
60 };
61 
62 /* Mmap'd hpet registers */
63 static volatile struct eal_hpet_regs *eal_hpet = NULL;
64 
65 /* Period at which the HPET counter increments in
66  * femtoseconds (10^-15 seconds). */
67 static uint32_t eal_hpet_resolution_fs = 0;
68 
69 /* Frequency of the HPET counter in Hz */
70 static uint64_t eal_hpet_resolution_hz = 0;
71 
72 /* Incremented 4 times during one 32bits hpet full count */
73 static uint32_t eal_hpet_msb;
74 
75 static rte_thread_t msb_inc_thread_id;
76 
77 /*
78  * This function runs on a specific thread to update a global variable
79  * containing used to process MSB of the HPET (unfortunately, we need
80  * this because hpet is 32 bits by default under linux).
81  */
82 static uint32_t
83 hpet_msb_inc(__rte_unused void *arg)
84 {
85 	uint32_t t;
86 
87 	while (1) {
88 		t = (eal_hpet->counter_l >> 30);
89 		if (t != (eal_hpet_msb & 3))
90 			eal_hpet_msb ++;
91 		sleep(10);
92 	}
93 	return 0;
94 }
95 
96 uint64_t
97 rte_get_hpet_hz(void)
98 {
99 	const struct internal_config *internal_conf =
100 		eal_get_internal_configuration();
101 
102 	if (internal_conf->no_hpet)
103 		rte_panic("Error, HPET called, but no HPET present\n");
104 
105 	return eal_hpet_resolution_hz;
106 }
107 
108 uint64_t
109 rte_get_hpet_cycles(void)
110 {
111 	uint32_t t, msb;
112 	uint64_t ret;
113 	const struct internal_config *internal_conf =
114 		eal_get_internal_configuration();
115 
116 	if (internal_conf->no_hpet)
117 		rte_panic("Error, HPET called, but no HPET present\n");
118 
119 	t = eal_hpet->counter_l;
120 	msb = eal_hpet_msb;
121 	ret = (msb + 2 - (t >> 30)) / 4;
122 	ret <<= 32;
123 	ret += t;
124 	return ret;
125 }
126 
127 #endif
128 
129 #ifdef RTE_LIBEAL_USE_HPET
130 /*
131  * Open and mmap /dev/hpet (high precision event timer) that will
132  * provide our time reference.
133  */
134 int
135 rte_eal_hpet_init(int make_default)
136 {
137 	int fd, ret;
138 	struct internal_config *internal_conf =
139 		eal_get_internal_configuration();
140 
141 	if (internal_conf->no_hpet) {
142 		EAL_LOG(NOTICE, "HPET is disabled");
143 		return -1;
144 	}
145 
146 	fd = open(DEV_HPET, O_RDONLY);
147 	if (fd < 0) {
148 		EAL_LOG(ERR, "ERROR: Cannot open "DEV_HPET": %s!",
149 			strerror(errno));
150 		internal_conf->no_hpet = 1;
151 		return -1;
152 	}
153 	eal_hpet = mmap(NULL, 1024, PROT_READ, MAP_SHARED, fd, 0);
154 	if (eal_hpet == MAP_FAILED) {
155 		EAL_LOG(ERR, "ERROR: Cannot mmap "DEV_HPET"!");
156 		close(fd);
157 		internal_conf->no_hpet = 1;
158 		return -1;
159 	}
160 	close(fd);
161 
162 	eal_hpet_resolution_fs = (uint32_t)((eal_hpet->capabilities &
163 					CLK_PERIOD_MASK) >>
164 					CLK_PERIOD_SHIFT);
165 
166 	eal_hpet_resolution_hz = (1000ULL*1000ULL*1000ULL*1000ULL*1000ULL) /
167 		(uint64_t)eal_hpet_resolution_fs;
168 
169 	EAL_LOG(INFO, "HPET frequency is ~%"PRIu64" kHz",
170 			eal_hpet_resolution_hz/1000);
171 
172 	eal_hpet_msb = (eal_hpet->counter_l >> 30);
173 
174 	/* create a thread that will increment a global variable for
175 	 * msb (hpet is 32 bits by default under linux) */
176 	ret = rte_thread_create_internal_control(&msb_inc_thread_id, "hpet-msb",
177 			hpet_msb_inc, NULL);
178 	if (ret != 0) {
179 		EAL_LOG(ERR, "ERROR: Cannot create HPET timer thread!");
180 		internal_conf->no_hpet = 1;
181 		return -1;
182 	}
183 
184 	if (make_default)
185 		eal_timer_source = EAL_TIMER_HPET;
186 	return 0;
187 }
188 #endif
189 
190 /* Check if the kernel deems the arch provided TSC frequency trustworthy. */
191 
192 static bool
193 is_tsc_known_freq(void)
194 {
195 	bool ret = true; /* Assume tsc_known_freq */
196 
197 #if defined(RTE_ARCH_X86)
198 	char line[2048];
199 	FILE *stream;
200 
201 	stream = fopen("/proc/cpuinfo", "r");
202 	if (!stream) {
203 		EAL_LOG(WARNING, "Unable to open /proc/cpuinfo");
204 		return ret;
205 	}
206 
207 	while (fgets(line, sizeof(line), stream)) {
208 		if (strncmp(line, "flags", 5) != 0)
209 			continue;
210 
211 		if (!strstr(line, "tsc_known_freq"))
212 			ret = false;
213 
214 		break;
215 	}
216 
217 	fclose(stream);
218 #endif
219 
220 	return ret;
221 }
222 
223 uint64_t
224 get_tsc_freq(uint64_t arch_hz)
225 {
226 #ifdef CLOCK_MONOTONIC_RAW
227 #define NS_PER_SEC 1E9
228 #define CYC_PER_100KHZ 1E5
229 
230 	struct timespec sleeptime = {.tv_nsec = NS_PER_SEC / 10 }; /* 1/10 second */
231 
232 	struct timespec t_start, t_end;
233 	uint64_t tsc_hz;
234 
235 	if (arch_hz && is_tsc_known_freq())
236 		return arch_hz;
237 
238 	if (clock_gettime(CLOCK_MONOTONIC_RAW, &t_start) == 0) {
239 		uint64_t ns, end, start = rte_rdtsc();
240 		nanosleep(&sleeptime,NULL);
241 		clock_gettime(CLOCK_MONOTONIC_RAW, &t_end);
242 		end = rte_rdtsc();
243 		ns = ((t_end.tv_sec - t_start.tv_sec) * NS_PER_SEC);
244 		ns += (t_end.tv_nsec - t_start.tv_nsec);
245 
246 		double secs = (double)ns/NS_PER_SEC;
247 		tsc_hz = (uint64_t)((end - start)/secs);
248 
249 		if (arch_hz) {
250 			/* Make sure we're within 1% for sanity check */
251 			if (RTE_MAX(arch_hz, tsc_hz) - RTE_MIN(arch_hz, tsc_hz) > arch_hz / 100)
252 				return arch_hz;
253 
254 			EAL_LOG(DEBUG,
255 				"Refined arch frequency %"PRIu64" to measured frequency %"PRIu64,
256 				arch_hz, tsc_hz);
257 		}
258 
259 		/* Round up to 100Khz. 1E5 ~ 100Khz */
260 		return RTE_ALIGN_MUL_NEAR(tsc_hz, CYC_PER_100KHZ);
261 	}
262 #endif
263 	return arch_hz;
264 }
265 
266 int
267 rte_eal_timer_init(void)
268 {
269 
270 	eal_timer_source = EAL_TIMER_TSC;
271 
272 	set_tsc_freq();
273 	return 0;
274 }
275