xref: /kvm-unit-tests/powerpc/timebase.c (revision 28ac3b10d6f982b1d9c2fe629f23d23ec5024b4f)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Test Timebase
4  *
5  * Copyright 2024 Nicholas Piggin, IBM Corp.
6  *
7  * This contains tests of timebase facility, TB, DEC, etc.
8  */
9 #include <libcflat.h>
10 #include <util.h>
11 #include <migrate.h>
12 #include <alloc.h>
13 #include <asm/handlers.h>
14 #include <devicetree.h>
15 #include <asm/hcall.h>
16 #include <asm/processor.h>
17 #include <asm/time.h>
18 #include <asm/barrier.h>
19 
20 static int dec_bits = 0;
21 
cpu_dec_bits(int fdtnode,u64 regval __unused,void * arg __unused)22 static void cpu_dec_bits(int fdtnode, u64 regval __unused, void *arg __unused)
23 {
24 	const struct fdt_property *prop;
25 	int plen;
26 
27 	prop = fdt_get_property(dt_fdt(), fdtnode, "ibm,dec-bits", &plen);
28 	if (!prop) {
29 		dec_bits = 32;
30 		return;
31 	}
32 
33 	/* Sanity check for the property layout (first two bytes are header) */
34 	assert(plen == 4);
35 
36 	/* Check all CPU nodes have the same value of dec-bits */
37 	if (dec_bits)
38 		assert(dec_bits == fdt32_to_cpu(*(uint32_t *)prop->data));
39 	else
40 		dec_bits = fdt32_to_cpu(*(uint32_t *)prop->data);
41 }
42 
43 /* Check amount of CPUs nodes that have the TM flag */
find_dec_bits(void)44 static int find_dec_bits(void)
45 {
46 	int ret;
47 
48 	ret = dt_for_each_cpu_node(cpu_dec_bits, NULL);
49 	if (ret < 0)
50 		return ret;
51 
52 	return dec_bits;
53 }
54 
55 
56 static bool do_migrate = false;
57 static volatile bool got_interrupt;
58 static volatile struct pt_regs recorded_regs;
59 
60 static uint64_t dec_max;
61 static uint64_t dec_min;
62 
test_tb(int argc,char ** argv)63 static void test_tb(int argc, char **argv)
64 {
65 	uint64_t tb;
66 	int i;
67 
68 	tb = get_tb();
69 	report(get_tb() >= tb, "timebase is not going backwards");
70 	if (do_migrate) {
71 		tb = get_tb();
72 		migrate();
73 		report(get_tb() >= tb,
74 		       "timebase is not going backwards over migration");
75 	}
76 
77 	for (i = 0; i < 100; i++) {
78 		if (get_tb() > tb)
79 			break;
80 	}
81 	report(get_tb() > tb, "timebase is incrementing");
82 }
83 
dec_stop_handler(struct pt_regs * regs,void * data)84 static void dec_stop_handler(struct pt_regs *regs, void *data)
85 {
86 	mtspr(SPR_DEC, dec_max);
87 }
88 
dec_handler(struct pt_regs * regs,void * data)89 static void dec_handler(struct pt_regs *regs, void *data)
90 {
91 	got_interrupt = true;
92 	memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
93 	regs->msr &= ~MSR_EE;
94 }
95 
test_dec(int argc,char ** argv)96 static void test_dec(int argc, char **argv)
97 {
98 	uint64_t tb1, tb2, dec;
99 	int i;
100 
101 	handle_exception(0x900, &dec_handler, NULL);
102 
103 	for (i = 0; i < 100; i++) {
104 		tb1 = get_tb();
105 		mtspr(SPR_DEC, dec_max);
106 		dec = mfspr(SPR_DEC);
107 		tb2 = get_tb();
108 		if (tb2 - tb1 < dec_max - dec)
109 			break;
110 	}
111 	/* POWER CPUs can have a slight (few ticks) variation here */
112 	report_kfail(!host_is_tcg, tb2 - tb1 >= dec_max - dec,
113 		     "decrementer remains within TB after mtDEC");
114 
115 	tb1 = get_tb();
116 	mtspr(SPR_DEC, dec_max);
117 	mdelay(1000);
118 	dec = mfspr(SPR_DEC);
119 	tb2 = get_tb();
120 	report(tb2 - tb1 >= dec_max - dec,
121 	       "decrementer remains within TB after 1s");
122 
123 	mtspr(SPR_DEC, dec_max);
124 	local_irq_enable();
125 	local_irq_disable();
126 	if (mfspr(SPR_DEC) <= dec_max) {
127 		report(!got_interrupt,
128 		       "no interrupt on decrementer positive");
129 	}
130 	got_interrupt = false;
131 
132 	mtspr(SPR_DEC, 1);
133 	mdelay(100); /* Give the timer a chance to run */
134 	if (do_migrate)
135 		migrate();
136 	local_irq_enable();
137 	local_irq_disable();
138 	report(got_interrupt, "interrupt on decrementer underflow");
139 	got_interrupt = false;
140 
141 	if (do_migrate)
142 		migrate();
143 	local_irq_enable();
144 	local_irq_disable();
145 	report(got_interrupt, "interrupt on decrementer still underflown");
146 	got_interrupt = false;
147 
148 	mtspr(SPR_DEC, 0);
149 	mdelay(100); /* Give the timer a chance to run */
150 	if (do_migrate)
151 		migrate();
152 	local_irq_enable();
153 	local_irq_disable();
154 	report(got_interrupt, "DEC deal with set to 0");
155 	got_interrupt = false;
156 
157 	/* Test for level-triggered decrementer */
158 	mtspr(SPR_DEC, -1ULL);
159 	if (do_migrate)
160 		migrate();
161 	local_irq_enable();
162 	local_irq_disable();
163 	report(got_interrupt, "interrupt on decrementer write MSB");
164 	got_interrupt = false;
165 
166 	mtspr(SPR_DEC, dec_max);
167 	local_irq_enable();
168 	if (do_migrate)
169 		migrate();
170 	mtspr(SPR_DEC, -1);
171 	local_irq_disable();
172 	report(got_interrupt, "interrupt on decrementer write MSB with irqs on");
173 	got_interrupt = false;
174 
175 	mtspr(SPR_DEC, dec_min + 1);
176 	mdelay(100);
177 	local_irq_enable();
178 	local_irq_disable();
179 	/* TCG does not model this correctly */
180 	report_kfail(host_is_tcg, !got_interrupt,
181 		     "no interrupt after wrap to positive");
182 	got_interrupt = false;
183 
184 	handle_exception(0x900, NULL, NULL);
185 }
186 
test_hdec(int argc,char ** argv)187 static void test_hdec(int argc, char **argv)
188 {
189 	uint64_t tb1, tb2, hdec;
190 
191 	if (!machine_is_powernv()) {
192 		report_skip("test reqiures powernv machine");
193 		return;
194 	}
195 
196 	handle_exception(0x900, &dec_stop_handler, NULL);
197 	handle_exception(0x980, &dec_handler, NULL);
198 
199 	mtspr(SPR_HDEC, dec_max);
200 	mtspr(SPR_LPCR, mfspr(SPR_LPCR) | LPCR_HDICE);
201 
202 	tb1 = get_tb();
203 	mtspr(SPR_HDEC, dec_max);
204 	hdec = mfspr(SPR_HDEC);
205 	tb2 = get_tb();
206 	report(tb2 - tb1 >= dec_max - hdec, "hdecrementer remains within TB");
207 
208 	tb1 = get_tb();
209 	mtspr(SPR_HDEC, dec_max);
210 	mdelay(1000);
211 	hdec = mfspr(SPR_HDEC);
212 	tb2 = get_tb();
213 	report(tb2 - tb1 >= dec_max - hdec, "hdecrementer remains within TB after 1s");
214 
215 	mtspr(SPR_HDEC, dec_max);
216 	local_irq_enable();
217 	local_irq_disable();
218 	if (mfspr(SPR_HDEC) <= dec_max) {
219 		report(!got_interrupt, "no interrupt on decrementer positive");
220 	}
221 	got_interrupt = false;
222 
223 	mtspr(SPR_HDEC, 1);
224 	mdelay(100); /* Give the timer a chance to run */
225 	if (do_migrate)
226 		migrate();
227 	/* HDEC is edge triggered so ensure it still fires */
228 	mtspr(SPR_HDEC, dec_max);
229 	local_irq_enable();
230 	local_irq_disable();
231 	report(got_interrupt, "interrupt on hdecrementer underflow");
232 	got_interrupt = false;
233 
234 	if (do_migrate)
235 		migrate();
236 	local_irq_enable();
237 	local_irq_disable();
238 	report(!got_interrupt, "no interrupt on hdecrementer still underflown");
239 	got_interrupt = false;
240 
241 	mtspr(SPR_HDEC, -1ULL);
242 	if (do_migrate)
243 		migrate();
244 	local_irq_enable();
245 	local_irq_disable();
246 	report(got_interrupt, "no interrupt on hdecrementer underflown write MSB");
247 	got_interrupt = false;
248 
249 	mtspr(SPR_HDEC, 0);
250 	mdelay(100); /* Give the timer a chance to run */
251 	if (do_migrate)
252 		migrate();
253 	/* HDEC is edge triggered so ensure it still fires */
254 	mtspr(SPR_HDEC, dec_max);
255 	local_irq_enable();
256 	local_irq_disable();
257 	report(got_interrupt, "HDEC deal with set to 0");
258 	got_interrupt = false;
259 
260 	mtspr(SPR_HDEC, dec_max);
261 	local_irq_enable();
262 	if (do_migrate)
263 		migrate();
264 	mtspr(SPR_HDEC, -1ULL);
265 	local_irq_disable();
266 	report(got_interrupt, "interrupt on hdecrementer write MSB with irqs on");
267 	got_interrupt = false;
268 
269 	mtspr(SPR_HDEC, dec_max);
270 	got_interrupt = false;
271 	mtspr(SPR_HDEC, dec_min + 1);
272 	if (do_migrate)
273 		migrate();
274 	mdelay(100);
275 	local_irq_enable();
276 	local_irq_disable();
277 	report(got_interrupt, "got interrupt after wrap to positive");
278 	got_interrupt = false;
279 
280 	mtspr(SPR_HDEC, -1ULL);
281 	local_irq_enable();
282 	local_irq_disable();
283 	got_interrupt = false;
284 	mtspr(SPR_HDEC, dec_min + 1000000);
285 	if (do_migrate)
286 		migrate();
287 	mdelay(100);
288 	mtspr(SPR_HDEC, -1ULL);
289 	local_irq_enable();
290 	local_irq_disable();
291 	report(got_interrupt, "edge re-armed after wrap to positive");
292 	got_interrupt = false;
293 
294 	mtspr(SPR_LPCR, mfspr(SPR_LPCR) & ~LPCR_HDICE);
295 
296 	handle_exception(0x900, NULL, NULL);
297 	handle_exception(0x980, NULL, NULL);
298 }
299 
300 struct {
301 	const char *name;
302 	void (*func)(int argc, char **argv);
303 } hctests[] = {
304 	{ "tb", test_tb },
305 	{ "dec", test_dec },
306 	{ "hdec", test_hdec },
307 	{ NULL, NULL }
308 };
309 
main(int argc,char ** argv)310 int main(int argc, char **argv)
311 {
312 	bool all;
313 	int i;
314 
315 	all = argc == 1 || !strcmp(argv[1], "all");
316 
317 	for (i = 1; i < argc; i++) {
318 		if (!strcmp(argv[i], "-w")) {
319 			do_migrate = true;
320 			if (!all && argc == 2)
321 				all = true;
322 		}
323 	}
324 
325 	find_dec_bits();
326 	dec_max = (1ULL << (dec_bits - 1)) - 1;
327 	dec_min = (1ULL << (dec_bits - 1));
328 
329 	if (machine_is_powernv() && dec_bits > 32) {
330 		mtspr(SPR_LPCR, mfspr(SPR_LPCR) | LPCR_LD);
331 	}
332 
333 	report_prefix_push("timebase");
334 
335 	for (i = 0; hctests[i].name != NULL; i++) {
336 		if (all || strcmp(argv[1], hctests[i].name) == 0) {
337 			report_prefix_push(hctests[i].name);
338 			hctests[i].func(argc, argv);
339 			report_prefix_pop();
340 		}
341 	}
342 
343 	report_prefix_pop();
344 
345 	if (machine_is_powernv() && dec_bits > 32) {
346 		mtspr(SPR_LPCR, mfspr(SPR_LPCR) & ~LPCR_LD);
347 	}
348 
349 	return report_summary();
350 }
351