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