xref: /kvm-unit-tests/riscv/sbi.c (revision 56ca80938026e71d0a2c48cb2a4d41905abed4a2)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * SBI verification
4  *
5  * Copyright (C) 2023, Ventana Micro Systems Inc., Andrew Jones <ajones@ventanamicro.com>
6  */
7 #include <libcflat.h>
8 #include <alloc_page.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <limits.h>
12 #include <vmalloc.h>
13 #include <memregions.h>
14 #include <asm/barrier.h>
15 #include <asm/csr.h>
16 #include <asm/delay.h>
17 #include <asm/io.h>
18 #include <asm/mmu.h>
19 #include <asm/processor.h>
20 #include <asm/sbi.h>
21 #include <asm/smp.h>
22 #include <asm/timer.h>
23 
24 #define	HIGH_ADDR_BOUNDARY	((phys_addr_t)1 << 32)
25 
26 static void help(void)
27 {
28 	puts("Test SBI\n");
29 	puts("An environ must be provided where expected values are given.\n");
30 }
31 
32 static struct sbiret sbi_base(int fid, unsigned long arg0)
33 {
34 	return sbi_ecall(SBI_EXT_BASE, fid, arg0, 0, 0, 0, 0, 0);
35 }
36 
37 static struct sbiret sbi_dbcn_write(unsigned long num_bytes, unsigned long base_addr_lo,
38 				    unsigned long base_addr_hi)
39 {
40 	return sbi_ecall(SBI_EXT_DBCN, SBI_EXT_DBCN_CONSOLE_WRITE,
41 			 num_bytes, base_addr_lo, base_addr_hi, 0, 0, 0);
42 }
43 
44 static struct sbiret sbi_dbcn_write_byte(uint8_t byte)
45 {
46 	return sbi_ecall(SBI_EXT_DBCN, SBI_EXT_DBCN_CONSOLE_WRITE_BYTE, byte, 0, 0, 0, 0, 0);
47 }
48 
49 static void split_phys_addr(phys_addr_t paddr, unsigned long *hi, unsigned long *lo)
50 {
51 	*lo = (unsigned long)paddr;
52 	*hi = 0;
53 	if (__riscv_xlen == 32)
54 		*hi = (unsigned long)(paddr >> 32);
55 }
56 
57 static bool check_addr(phys_addr_t start, phys_addr_t size)
58 {
59 	struct mem_region *r = memregions_find(start);
60 	return r && r->end - start >= size && r->flags == MR_F_UNUSED;
61 }
62 
63 static phys_addr_t get_highest_addr(void)
64 {
65 	phys_addr_t highest_end = 0;
66 	struct mem_region *r;
67 
68 	for (r = mem_regions; r->end; ++r) {
69 		if (r->end > highest_end)
70 			highest_end = r->end;
71 	}
72 
73 	return highest_end - 1;
74 }
75 
76 static bool env_or_skip(const char *env)
77 {
78 	if (!getenv(env)) {
79 		report_skip("missing %s environment variable", env);
80 		return false;
81 	}
82 
83 	return true;
84 }
85 
86 static void gen_report(struct sbiret *ret,
87 		       long expected_error, long expected_value)
88 {
89 	bool check_error = ret->error == expected_error;
90 	bool check_value = ret->value == expected_value;
91 
92 	if (!check_error || !check_value)
93 		report_info("expected (error: %ld, value: %ld), received: (error: %ld, value %ld)",
94 			    expected_error, expected_value, ret->error, ret->value);
95 
96 	report(check_error, "expected sbi.error");
97 	report(check_value, "expected sbi.value");
98 }
99 
100 static void check_base(void)
101 {
102 	struct sbiret ret;
103 	long expected;
104 
105 	report_prefix_push("base");
106 
107 	ret = sbi_base(SBI_EXT_BASE_GET_SPEC_VERSION, 0);
108 
109 	report_prefix_push("spec_version");
110 	if (env_or_skip("SBI_SPEC_VERSION")) {
111 		expected = (long)strtoul(getenv("SBI_SPEC_VERSION"), NULL, 0);
112 		assert_msg(!(expected & BIT(31)), "SBI spec version bit 31 must be zero");
113 		assert_msg(__riscv_xlen == 32 || !(expected >> 32), "SBI spec version bits greater than 31 must be zero");
114 		gen_report(&ret, 0, expected);
115 	}
116 	report_prefix_pop();
117 
118 	ret.value &= 0x7ffffffful;
119 
120 	if (ret.error || ret.value < 2) {
121 		report_skip("SBI spec version 0.2 or higher required");
122 		return;
123 	}
124 
125 	report_prefix_push("impl_id");
126 	if (env_or_skip("SBI_IMPL_ID")) {
127 		expected = (long)strtoul(getenv("SBI_IMPL_ID"), NULL, 0);
128 		ret = sbi_base(SBI_EXT_BASE_GET_IMP_ID, 0);
129 		gen_report(&ret, 0, expected);
130 	}
131 	report_prefix_pop();
132 
133 	report_prefix_push("impl_version");
134 	if (env_or_skip("SBI_IMPL_VERSION")) {
135 		expected = (long)strtoul(getenv("SBI_IMPL_VERSION"), NULL, 0);
136 		ret = sbi_base(SBI_EXT_BASE_GET_IMP_VERSION, 0);
137 		gen_report(&ret, 0, expected);
138 	}
139 	report_prefix_pop();
140 
141 	report_prefix_push("probe_ext");
142 	expected = getenv("SBI_PROBE_EXT") ? (long)strtoul(getenv("SBI_PROBE_EXT"), NULL, 0) : 1;
143 	ret = sbi_base(SBI_EXT_BASE_PROBE_EXT, SBI_EXT_BASE);
144 	gen_report(&ret, 0, expected);
145 	report_prefix_push("unavailable");
146 	ret = sbi_base(SBI_EXT_BASE_PROBE_EXT, 0xb000000);
147 	gen_report(&ret, 0, 0);
148 	report_prefix_popn(2);
149 
150 	report_prefix_push("mvendorid");
151 	if (env_or_skip("MVENDORID")) {
152 		expected = (long)strtoul(getenv("MVENDORID"), NULL, 0);
153 		assert(__riscv_xlen == 32 || !(expected >> 32));
154 		ret = sbi_base(SBI_EXT_BASE_GET_MVENDORID, 0);
155 		gen_report(&ret, 0, expected);
156 	}
157 	report_prefix_pop();
158 
159 	report_prefix_push("marchid");
160 	if (env_or_skip("MARCHID")) {
161 		expected = (long)strtoul(getenv("MARCHID"), NULL, 0);
162 		ret = sbi_base(SBI_EXT_BASE_GET_MARCHID, 0);
163 		gen_report(&ret, 0, expected);
164 	}
165 	report_prefix_pop();
166 
167 	report_prefix_push("mimpid");
168 	if (env_or_skip("MIMPID")) {
169 		expected = (long)strtoul(getenv("MIMPID"), NULL, 0);
170 		ret = sbi_base(SBI_EXT_BASE_GET_MIMPID, 0);
171 		gen_report(&ret, 0, expected);
172 	}
173 	report_prefix_popn(2);
174 }
175 
176 struct timer_info {
177 	bool timer_works;
178 	bool mask_timer_irq;
179 	bool timer_irq_set;
180 	bool timer_irq_cleared;
181 	unsigned long timer_irq_count;
182 };
183 
184 static struct timer_info timer_info;
185 
186 static bool timer_irq_pending(void)
187 {
188 	return csr_read(CSR_SIP) & IP_TIP;
189 }
190 
191 static void timer_irq_handler(struct pt_regs *regs)
192 {
193 	timer_info.timer_works = true;
194 
195 	if (timer_info.timer_irq_count < ULONG_MAX)
196 		++timer_info.timer_irq_count;
197 
198 	if (timer_irq_pending())
199 		timer_info.timer_irq_set = true;
200 
201 	if (timer_info.mask_timer_irq)
202 		timer_irq_disable();
203 	else
204 		sbi_set_timer(ULONG_MAX);
205 
206 	if (!timer_irq_pending())
207 		timer_info.timer_irq_cleared = true;
208 }
209 
210 static void timer_check_set_timer(bool mask_timer_irq)
211 {
212 	struct sbiret ret;
213 	unsigned long begin, end, duration;
214 	const char *mask_test_str = mask_timer_irq ? " for mask irq test" : "";
215 	unsigned long d = getenv("SBI_TIMER_DELAY") ? strtol(getenv("SBI_TIMER_DELAY"), NULL, 0) : 200000;
216 	unsigned long margin = getenv("SBI_TIMER_MARGIN") ? strtol(getenv("SBI_TIMER_MARGIN"), NULL, 0) : 200000;
217 
218 	d = usec_to_cycles(d);
219 	margin = usec_to_cycles(margin);
220 
221 	timer_info = (struct timer_info){ .mask_timer_irq = mask_timer_irq };
222 	begin = timer_get_cycles();
223 	ret = sbi_set_timer(begin + d);
224 
225 	report(!ret.error, "set timer%s", mask_test_str);
226 	if (ret.error)
227 		report_info("set timer%s failed with %ld\n", mask_test_str, ret.error);
228 
229 	while ((end = timer_get_cycles()) <= (begin + d + margin) && !timer_info.timer_works)
230 		cpu_relax();
231 
232 	report(timer_info.timer_works, "timer interrupt received%s", mask_test_str);
233 	report(timer_info.timer_irq_set, "pending timer interrupt bit set in irq handler%s", mask_test_str);
234 
235 	if (!mask_timer_irq) {
236 		report(timer_info.timer_irq_set && timer_info.timer_irq_cleared,
237 		       "pending timer interrupt bit cleared by setting timer to -1");
238 	}
239 
240 	if (timer_info.timer_works) {
241 		duration = end - begin;
242 		report(duration >= d && duration <= (d + margin), "timer delay honored%s", mask_test_str);
243 	}
244 
245 	report(timer_info.timer_irq_count == 1, "timer interrupt received exactly once%s", mask_test_str);
246 }
247 
248 static void check_time(void)
249 {
250 	bool pending;
251 
252 	report_prefix_push("time");
253 
254 	if (!sbi_probe(SBI_EXT_TIME)) {
255 		report_skip("time extension not available");
256 		report_prefix_pop();
257 		return;
258 	}
259 
260 	report_prefix_push("set_timer");
261 
262 	install_irq_handler(IRQ_S_TIMER, timer_irq_handler);
263 	local_irq_enable();
264 	timer_irq_enable();
265 
266 	timer_check_set_timer(false);
267 
268 	if (csr_read(CSR_SIE) & IE_TIE)
269 		timer_check_set_timer(true);
270 	else
271 		report_skip("timer irq enable bit is not writable, skipping mask irq test");
272 
273 	timer_irq_disable();
274 	sbi_set_timer(0);
275 	pending = timer_irq_pending();
276 	report(pending, "timer immediately pending by setting timer to 0");
277 	sbi_set_timer(ULONG_MAX);
278 	if (pending)
279 		report(!timer_irq_pending(), "pending timer cleared while masked");
280 	else
281 		report_skip("timer is not pending, skipping timer cleared while masked test");
282 
283 	local_irq_disable();
284 	install_irq_handler(IRQ_S_TIMER, NULL);
285 
286 	report_prefix_popn(2);
287 }
288 
289 #define DBCN_WRITE_TEST_STRING		"DBCN_WRITE_TEST_STRING\n"
290 #define DBCN_WRITE_BYTE_TEST_BYTE	((u8)'a')
291 
292 static void dbcn_write_test(const char *s, unsigned long num_bytes, bool xfail)
293 {
294 	unsigned long base_addr_lo, base_addr_hi;
295 	phys_addr_t paddr = virt_to_phys((void *)s);
296 	int num_calls = 0;
297 	struct sbiret ret;
298 
299 	split_phys_addr(paddr, &base_addr_hi, &base_addr_lo);
300 
301 	do {
302 		ret = sbi_dbcn_write(num_bytes, base_addr_lo, base_addr_hi);
303 		num_bytes -= ret.value;
304 		paddr += ret.value;
305 		split_phys_addr(paddr, &base_addr_hi, &base_addr_lo);
306 		num_calls++;
307 	} while (num_bytes != 0 && ret.error == SBI_SUCCESS);
308 
309 	report_xfail(xfail, ret.error == SBI_SUCCESS, "write success (error=%ld)", ret.error);
310 	report_info("%d sbi calls made", num_calls);
311 }
312 
313 static void dbcn_high_write_test(const char *s, unsigned long num_bytes,
314 				 phys_addr_t page_addr, size_t page_offset,
315 				 bool highmem_supported)
316 {
317 	int nr_pages = page_offset ? 2 : 1;
318 	void *vaddr;
319 
320 	if (page_addr != PAGE_ALIGN(page_addr) || page_addr + PAGE_SIZE < HIGH_ADDR_BOUNDARY ||
321 	    !check_addr(page_addr, nr_pages * PAGE_SIZE)) {
322 		report_skip("Memory above 4G required");
323 		return;
324 	}
325 
326 	vaddr = alloc_vpages(nr_pages);
327 
328 	for (int i = 0; i < nr_pages; ++i)
329 		install_page(current_pgtable(), page_addr + i * PAGE_SIZE, vaddr + i * PAGE_SIZE);
330 	memcpy(vaddr + page_offset, DBCN_WRITE_TEST_STRING, num_bytes);
331 	dbcn_write_test(vaddr + page_offset, num_bytes, !highmem_supported);
332 }
333 
334 /*
335  * Only the write functionality is tested here. There's no easy way to
336  * non-interactively test SBI_EXT_DBCN_CONSOLE_READ.
337  */
338 static void check_dbcn(void)
339 {
340 	unsigned long num_bytes = strlen(DBCN_WRITE_TEST_STRING);
341 	unsigned long base_addr_lo, base_addr_hi;
342 	bool do_invalid_addr = false;
343 	bool highmem_supported = true;
344 	phys_addr_t paddr;
345 	struct sbiret ret;
346 	const char *tmp;
347 	char *buf;
348 
349 	report_prefix_push("dbcn");
350 
351 	if (!sbi_probe(SBI_EXT_DBCN)) {
352 		report_skip("DBCN extension unavailable");
353 		report_prefix_pop();
354 		return;
355 	}
356 
357 	report_prefix_push("write");
358 
359 	dbcn_write_test(DBCN_WRITE_TEST_STRING, num_bytes, false);
360 
361 	assert(num_bytes < PAGE_SIZE);
362 
363 	report_prefix_push("page boundary");
364 	buf = alloc_pages(1);
365 	memcpy(&buf[PAGE_SIZE - num_bytes / 2], DBCN_WRITE_TEST_STRING, num_bytes);
366 	dbcn_write_test(&buf[PAGE_SIZE - num_bytes / 2], num_bytes, false);
367 	report_prefix_pop();
368 
369 	tmp = getenv("SBI_HIGHMEM_NOT_SUPPORTED");
370 	if (tmp && atol(tmp) != 0)
371 		highmem_supported = false;
372 
373 	report_prefix_push("high boundary");
374 	tmp = getenv("SBI_DBCN_SKIP_HIGH_BOUNDARY");
375 	if (!tmp || atol(tmp) == 0)
376 		dbcn_high_write_test(DBCN_WRITE_TEST_STRING, num_bytes,
377 				     HIGH_ADDR_BOUNDARY - PAGE_SIZE, PAGE_SIZE - num_bytes / 2,
378 				     highmem_supported);
379 	else
380 		report_skip("user disabled");
381 	report_prefix_pop();
382 
383 	report_prefix_push("high page");
384 	tmp = getenv("SBI_DBCN_SKIP_HIGH_PAGE");
385 	if (!tmp || atol(tmp) == 0) {
386 		paddr = HIGH_ADDR_BOUNDARY;
387 		tmp = getenv("HIGH_PAGE");
388 		if (tmp)
389 			paddr = strtoull(tmp, NULL, 0);
390 		dbcn_high_write_test(DBCN_WRITE_TEST_STRING, num_bytes, paddr, 0, highmem_supported);
391 	} else {
392 		report_skip("user disabled");
393 	}
394 	report_prefix_pop();
395 
396 	/* Bytes are read from memory and written to the console */
397 	report_prefix_push("invalid parameter");
398 	tmp = getenv("INVALID_ADDR_AUTO");
399 	if (tmp && atol(tmp) == 1) {
400 		paddr = get_highest_addr() + 1;
401 		do_invalid_addr = true;
402 	} else if (env_or_skip("INVALID_ADDR")) {
403 		paddr = strtoull(getenv("INVALID_ADDR"), NULL, 0);
404 		do_invalid_addr = true;
405 	}
406 
407 	if (do_invalid_addr) {
408 		split_phys_addr(paddr, &base_addr_hi, &base_addr_lo);
409 		ret = sbi_dbcn_write(1, base_addr_lo, base_addr_hi);
410 		report(ret.error == SBI_ERR_INVALID_PARAM, "address (error=%ld)", ret.error);
411 	}
412 	report_prefix_popn(2);
413 	report_prefix_push("write_byte");
414 
415 	puts("DBCN_WRITE_BYTE TEST BYTE: ");
416 	ret = sbi_dbcn_write_byte(DBCN_WRITE_BYTE_TEST_BYTE);
417 	puts("\n");
418 	report(ret.error == SBI_SUCCESS, "write success (error=%ld)", ret.error);
419 	report(ret.value == 0, "expected ret.value (%ld)", ret.value);
420 
421 	puts("DBCN_WRITE_BYTE TEST WORD: "); /* still expect 'a' in the output */
422 	ret = sbi_ecall(SBI_EXT_DBCN, SBI_EXT_DBCN_CONSOLE_WRITE_BYTE, 0x64636261, 0, 0, 0, 0, 0);
423 	puts("\n");
424 	report(ret.error == SBI_SUCCESS, "write success (error=%ld)", ret.error);
425 	report(ret.value == 0, "expected ret.value (%ld)", ret.value);
426 
427 	report_prefix_popn(2);
428 }
429 
430 int main(int argc, char **argv)
431 {
432 	if (argc > 1 && !strcmp(argv[1], "-h")) {
433 		help();
434 		exit(0);
435 	}
436 
437 	report_prefix_push("sbi");
438 	check_base();
439 	check_time();
440 	check_dbcn();
441 
442 	return report_summary();
443 }
444