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