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