xref: /kvm-unit-tests/riscv/sbi.c (revision 53cded04370135dee6536f43e9752fa6c5d0d029)
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 <cpumask.h>
10 #include <limits.h>
11 #include <memregions.h>
12 #include <on-cpus.h>
13 #include <rand.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <vmalloc.h>
17 
18 #include <asm/barrier.h>
19 #include <asm/csr.h>
20 #include <asm/delay.h>
21 #include <asm/io.h>
22 #include <asm/mmu.h>
23 #include <asm/processor.h>
24 #include <asm/sbi.h>
25 #include <asm/smp.h>
26 #include <asm/timer.h>
27 
28 #define	HIGH_ADDR_BOUNDARY	((phys_addr_t)1 << 32)
29 
30 static long __labs(long a)
31 {
32 	return __builtin_labs(a);
33 }
34 
35 static void help(void)
36 {
37 	puts("Test SBI\n");
38 	puts("An environ must be provided where expected values are given.\n");
39 }
40 
41 static struct sbiret sbi_base(int fid, unsigned long arg0)
42 {
43 	return sbi_ecall(SBI_EXT_BASE, fid, arg0, 0, 0, 0, 0, 0);
44 }
45 
46 static struct sbiret sbi_dbcn_write(unsigned long num_bytes, unsigned long base_addr_lo,
47 				    unsigned long base_addr_hi)
48 {
49 	return sbi_ecall(SBI_EXT_DBCN, SBI_EXT_DBCN_CONSOLE_WRITE,
50 			 num_bytes, base_addr_lo, base_addr_hi, 0, 0, 0);
51 }
52 
53 static struct sbiret sbi_dbcn_write_byte(uint8_t byte)
54 {
55 	return sbi_ecall(SBI_EXT_DBCN, SBI_EXT_DBCN_CONSOLE_WRITE_BYTE, byte, 0, 0, 0, 0, 0);
56 }
57 
58 static int rand_online_cpu(prng_state *ps)
59 {
60 	int cpu, me = smp_processor_id();
61 
62 	for (;;) {
63 		cpu = prng32(ps) % nr_cpus;
64 		cpu = cpumask_next(cpu - 1, &cpu_present_mask);
65 		if (cpu != nr_cpus && cpu != me && cpu_present(cpu))
66 			break;
67 	}
68 
69 	return cpu;
70 }
71 
72 static void split_phys_addr(phys_addr_t paddr, unsigned long *hi, unsigned long *lo)
73 {
74 	*lo = (unsigned long)paddr;
75 	*hi = 0;
76 	if (__riscv_xlen == 32)
77 		*hi = (unsigned long)(paddr >> 32);
78 }
79 
80 static bool check_addr(phys_addr_t start, phys_addr_t size)
81 {
82 	struct mem_region *r = memregions_find(start);
83 	return r && r->end - start >= size && r->flags == MR_F_UNUSED;
84 }
85 
86 static phys_addr_t get_highest_addr(void)
87 {
88 	phys_addr_t highest_end = 0;
89 	struct mem_region *r;
90 
91 	for (r = mem_regions; r->end; ++r) {
92 		if (r->end > highest_end)
93 			highest_end = r->end;
94 	}
95 
96 	return highest_end - 1;
97 }
98 
99 static bool env_or_skip(const char *env)
100 {
101 	if (!getenv(env)) {
102 		report_skip("missing %s environment variable", env);
103 		return false;
104 	}
105 
106 	return true;
107 }
108 
109 static void gen_report(struct sbiret *ret,
110 		       long expected_error, long expected_value)
111 {
112 	bool check_error = ret->error == expected_error;
113 	bool check_value = ret->value == expected_value;
114 
115 	if (!check_error || !check_value)
116 		report_info("expected (error: %ld, value: %ld), received: (error: %ld, value %ld)",
117 			    expected_error, expected_value, ret->error, ret->value);
118 
119 	report(check_error, "expected sbi.error");
120 	report(check_value, "expected sbi.value");
121 }
122 
123 static void check_base(void)
124 {
125 	struct sbiret ret;
126 	long expected;
127 
128 	report_prefix_push("base");
129 
130 	ret = sbi_base(SBI_EXT_BASE_GET_SPEC_VERSION, 0);
131 
132 	report_prefix_push("spec_version");
133 	if (env_or_skip("SBI_SPEC_VERSION")) {
134 		expected = (long)strtoul(getenv("SBI_SPEC_VERSION"), NULL, 0);
135 		assert_msg(!(expected & BIT(31)), "SBI spec version bit 31 must be zero");
136 		assert_msg(__riscv_xlen == 32 || !(expected >> 32), "SBI spec version bits greater than 31 must be zero");
137 		gen_report(&ret, 0, expected);
138 	}
139 	report_prefix_pop();
140 
141 	ret.value &= 0x7ffffffful;
142 
143 	if (ret.error || ret.value < 2) {
144 		report_skip("SBI spec version 0.2 or higher required");
145 		return;
146 	}
147 
148 	report_prefix_push("impl_id");
149 	if (env_or_skip("SBI_IMPL_ID")) {
150 		expected = (long)strtoul(getenv("SBI_IMPL_ID"), NULL, 0);
151 		ret = sbi_base(SBI_EXT_BASE_GET_IMP_ID, 0);
152 		gen_report(&ret, 0, expected);
153 	}
154 	report_prefix_pop();
155 
156 	report_prefix_push("impl_version");
157 	if (env_or_skip("SBI_IMPL_VERSION")) {
158 		expected = (long)strtoul(getenv("SBI_IMPL_VERSION"), NULL, 0);
159 		ret = sbi_base(SBI_EXT_BASE_GET_IMP_VERSION, 0);
160 		gen_report(&ret, 0, expected);
161 	}
162 	report_prefix_pop();
163 
164 	report_prefix_push("probe_ext");
165 	expected = getenv("SBI_PROBE_EXT") ? (long)strtoul(getenv("SBI_PROBE_EXT"), NULL, 0) : 1;
166 	ret = sbi_base(SBI_EXT_BASE_PROBE_EXT, SBI_EXT_BASE);
167 	gen_report(&ret, 0, expected);
168 	report_prefix_push("unavailable");
169 	ret = sbi_base(SBI_EXT_BASE_PROBE_EXT, 0xb000000);
170 	gen_report(&ret, 0, 0);
171 	report_prefix_popn(2);
172 
173 	report_prefix_push("mvendorid");
174 	if (env_or_skip("MVENDORID")) {
175 		expected = (long)strtoul(getenv("MVENDORID"), NULL, 0);
176 		assert(__riscv_xlen == 32 || !(expected >> 32));
177 		ret = sbi_base(SBI_EXT_BASE_GET_MVENDORID, 0);
178 		gen_report(&ret, 0, expected);
179 	}
180 	report_prefix_pop();
181 
182 	report_prefix_push("marchid");
183 	if (env_or_skip("MARCHID")) {
184 		expected = (long)strtoul(getenv("MARCHID"), NULL, 0);
185 		ret = sbi_base(SBI_EXT_BASE_GET_MARCHID, 0);
186 		gen_report(&ret, 0, expected);
187 	}
188 	report_prefix_pop();
189 
190 	report_prefix_push("mimpid");
191 	if (env_or_skip("MIMPID")) {
192 		expected = (long)strtoul(getenv("MIMPID"), NULL, 0);
193 		ret = sbi_base(SBI_EXT_BASE_GET_MIMPID, 0);
194 		gen_report(&ret, 0, expected);
195 	}
196 	report_prefix_popn(2);
197 }
198 
199 struct timer_info {
200 	bool timer_works;
201 	bool mask_timer_irq;
202 	bool timer_irq_set;
203 	bool timer_irq_cleared;
204 	unsigned long timer_irq_count;
205 };
206 
207 static struct timer_info timer_info;
208 
209 static bool timer_irq_pending(void)
210 {
211 	return csr_read(CSR_SIP) & IP_TIP;
212 }
213 
214 static void timer_irq_handler(struct pt_regs *regs)
215 {
216 	timer_info.timer_works = true;
217 
218 	if (timer_info.timer_irq_count < ULONG_MAX)
219 		++timer_info.timer_irq_count;
220 
221 	if (timer_irq_pending())
222 		timer_info.timer_irq_set = true;
223 
224 	if (timer_info.mask_timer_irq)
225 		timer_irq_disable();
226 	else
227 		sbi_set_timer(ULONG_MAX);
228 
229 	if (!timer_irq_pending())
230 		timer_info.timer_irq_cleared = true;
231 }
232 
233 static void timer_check_set_timer(bool mask_timer_irq)
234 {
235 	struct sbiret ret;
236 	unsigned long begin, end, duration;
237 	const char *mask_test_str = mask_timer_irq ? " for mask irq test" : "";
238 	unsigned long d = getenv("SBI_TIMER_DELAY") ? strtol(getenv("SBI_TIMER_DELAY"), NULL, 0) : 200000;
239 	unsigned long margin = getenv("SBI_TIMER_MARGIN") ? strtol(getenv("SBI_TIMER_MARGIN"), NULL, 0) : 200000;
240 
241 	d = usec_to_cycles(d);
242 	margin = usec_to_cycles(margin);
243 
244 	timer_info = (struct timer_info){ .mask_timer_irq = mask_timer_irq };
245 	begin = timer_get_cycles();
246 	ret = sbi_set_timer(begin + d);
247 
248 	report(!ret.error, "set timer%s", mask_test_str);
249 	if (ret.error)
250 		report_info("set timer%s failed with %ld\n", mask_test_str, ret.error);
251 
252 	while ((end = timer_get_cycles()) <= (begin + d + margin) && !timer_info.timer_works)
253 		cpu_relax();
254 
255 	report(timer_info.timer_works, "timer interrupt received%s", mask_test_str);
256 	report(timer_info.timer_irq_set, "pending timer interrupt bit set in irq handler%s", mask_test_str);
257 
258 	if (!mask_timer_irq) {
259 		report(timer_info.timer_irq_set && timer_info.timer_irq_cleared,
260 		       "pending timer interrupt bit cleared by setting timer to -1");
261 	}
262 
263 	if (timer_info.timer_works) {
264 		duration = end - begin;
265 		report(duration >= d && duration <= (d + margin), "timer delay honored%s", mask_test_str);
266 	}
267 
268 	report(timer_info.timer_irq_count == 1, "timer interrupt received exactly once%s", mask_test_str);
269 }
270 
271 static void check_time(void)
272 {
273 	bool pending;
274 
275 	report_prefix_push("time");
276 
277 	if (!sbi_probe(SBI_EXT_TIME)) {
278 		report_skip("time extension not available");
279 		report_prefix_pop();
280 		return;
281 	}
282 
283 	report_prefix_push("set_timer");
284 
285 	install_irq_handler(IRQ_S_TIMER, timer_irq_handler);
286 	local_irq_enable();
287 	timer_irq_enable();
288 
289 	timer_check_set_timer(false);
290 
291 	if (csr_read(CSR_SIE) & IE_TIE)
292 		timer_check_set_timer(true);
293 	else
294 		report_skip("timer irq enable bit is not writable, skipping mask irq test");
295 
296 	timer_irq_disable();
297 	sbi_set_timer(0);
298 	pending = timer_irq_pending();
299 	report(pending, "timer immediately pending by setting timer to 0");
300 	sbi_set_timer(ULONG_MAX);
301 	if (pending)
302 		report(!timer_irq_pending(), "pending timer cleared while masked");
303 	else
304 		report_skip("timer is not pending, skipping timer cleared while masked test");
305 
306 	local_irq_disable();
307 	install_irq_handler(IRQ_S_TIMER, NULL);
308 
309 	report_prefix_popn(2);
310 }
311 
312 static bool ipi_received[NR_CPUS];
313 static bool ipi_timeout[NR_CPUS];
314 static cpumask_t ipi_done;
315 
316 static void ipi_timeout_handler(struct pt_regs *regs)
317 {
318 	timer_stop();
319 	ipi_timeout[smp_processor_id()] = true;
320 }
321 
322 static void ipi_irq_handler(struct pt_regs *regs)
323 {
324 	ipi_ack();
325 	ipi_received[smp_processor_id()] = true;
326 }
327 
328 static void ipi_hart_wait(void *data)
329 {
330 	unsigned long timeout = (unsigned long)data;
331 	int me = smp_processor_id();
332 
333 	install_irq_handler(IRQ_S_SOFT, ipi_irq_handler);
334 	install_irq_handler(IRQ_S_TIMER, ipi_timeout_handler);
335 	local_ipi_enable();
336 	timer_irq_enable();
337 	local_irq_enable();
338 
339 	timer_start(timeout);
340 	while (!READ_ONCE(ipi_received[me]) && !READ_ONCE(ipi_timeout[me]))
341 		cpu_relax();
342 	local_irq_disable();
343 	timer_stop();
344 	local_ipi_disable();
345 	timer_irq_disable();
346 
347 	cpumask_set_cpu(me, &ipi_done);
348 }
349 
350 static void ipi_hart_check(cpumask_t *mask)
351 {
352 	int cpu;
353 
354 	for_each_cpu(cpu, mask) {
355 		if (ipi_timeout[cpu]) {
356 			const char *rec = ipi_received[cpu] ? "but was still received"
357 							    : "and has still not been received";
358 			report_fail("ipi timed out on cpu%d %s", cpu, rec);
359 		}
360 
361 		ipi_timeout[cpu] = false;
362 		ipi_received[cpu] = false;
363 	}
364 }
365 
366 static void check_ipi(void)
367 {
368 	unsigned long d = getenv("SBI_IPI_TIMEOUT") ? strtol(getenv("SBI_IPI_TIMEOUT"), NULL, 0) : 200000;
369 	int nr_cpus_present = cpumask_weight(&cpu_present_mask);
370 	int me = smp_processor_id();
371 	unsigned long max_hartid = 0;
372 	unsigned long hartid1, hartid2;
373 	cpumask_t ipi_receivers;
374 	static prng_state ps;
375 	struct sbiret ret;
376 	int cpu, cpu2;
377 
378 	ps = prng_init(0xDEADBEEF);
379 
380 	report_prefix_push("ipi");
381 
382 	if (!sbi_probe(SBI_EXT_IPI)) {
383 		report_skip("ipi extension not available");
384 		report_prefix_pop();
385 		return;
386 	}
387 
388 	if (nr_cpus_present < 2) {
389 		report_skip("At least 2 cpus required");
390 		report_prefix_pop();
391 		return;
392 	}
393 
394 	report_prefix_push("random hart");
395 	cpumask_clear(&ipi_done);
396 	cpumask_clear(&ipi_receivers);
397 	cpu = rand_online_cpu(&ps);
398 	cpumask_set_cpu(cpu, &ipi_receivers);
399 	on_cpu_async(cpu, ipi_hart_wait, (void *)d);
400 	ret = sbi_send_ipi_cpu(cpu);
401 	report(ret.error == SBI_SUCCESS, "ipi returned success");
402 	while (!cpumask_equal(&ipi_done, &ipi_receivers))
403 		cpu_relax();
404 	ipi_hart_check(&ipi_receivers);
405 	report_prefix_pop();
406 
407 	report_prefix_push("two in hart_mask");
408 
409 	if (nr_cpus_present < 3) {
410 		report_skip("3 cpus required");
411 		goto end_two;
412 	}
413 
414 	cpu = rand_online_cpu(&ps);
415 	hartid1 = cpus[cpu].hartid;
416 	hartid2 = 0;
417 	for_each_present_cpu(cpu2) {
418 		if (cpu2 == cpu || cpu2 == me)
419 			continue;
420 		hartid2 = cpus[cpu2].hartid;
421 		if (__labs(hartid2 - hartid1) < BITS_PER_LONG)
422 			break;
423 	}
424 	if (cpu2 == nr_cpus) {
425 		report_skip("hartids are too sparse");
426 		goto end_two;
427 	}
428 
429 	cpumask_clear(&ipi_done);
430 	cpumask_clear(&ipi_receivers);
431 	cpumask_set_cpu(cpu, &ipi_receivers);
432 	cpumask_set_cpu(cpu2, &ipi_receivers);
433 	on_cpu_async(cpu, ipi_hart_wait, (void *)d);
434 	on_cpu_async(cpu2, ipi_hart_wait, (void *)d);
435 	ret = sbi_send_ipi((1UL << __labs(hartid2 - hartid1)) | 1UL, hartid1 < hartid2 ? hartid1 : hartid2);
436 	report(ret.error == SBI_SUCCESS, "ipi returned success");
437 	while (!cpumask_equal(&ipi_done, &ipi_receivers))
438 		cpu_relax();
439 	ipi_hart_check(&ipi_receivers);
440 end_two:
441 	report_prefix_pop();
442 
443 	report_prefix_push("broadcast");
444 	cpumask_clear(&ipi_done);
445 	cpumask_copy(&ipi_receivers, &cpu_present_mask);
446 	cpumask_clear_cpu(me, &ipi_receivers);
447 	on_cpumask_async(&ipi_receivers, ipi_hart_wait, (void *)d);
448 	ret = sbi_send_ipi_broadcast();
449 	report(ret.error == SBI_SUCCESS, "ipi returned success");
450 	while (!cpumask_equal(&ipi_done, &ipi_receivers))
451 		cpu_relax();
452 	ipi_hart_check(&ipi_receivers);
453 	report_prefix_pop();
454 
455 	report_prefix_push("invalid parameters");
456 
457 	for_each_present_cpu(cpu) {
458 		if (cpus[cpu].hartid > max_hartid)
459 			max_hartid = cpus[cpu].hartid;
460 	}
461 
462 	/* Try the next higher hartid than the max */
463 	ret = sbi_send_ipi(2, max_hartid);
464 	report_kfail(true, ret.error == SBI_ERR_INVALID_PARAM, "hart_mask got expected error (%ld)", ret.error);
465 	ret = sbi_send_ipi(1, max_hartid + 1);
466 	report_kfail(true, ret.error == SBI_ERR_INVALID_PARAM, "hart_mask_base got expected error (%ld)", ret.error);
467 
468 	report_prefix_pop();
469 
470 	report_prefix_pop();
471 }
472 
473 #define DBCN_WRITE_TEST_STRING		"DBCN_WRITE_TEST_STRING\n"
474 #define DBCN_WRITE_BYTE_TEST_BYTE	((u8)'a')
475 
476 static void dbcn_write_test(const char *s, unsigned long num_bytes, bool xfail)
477 {
478 	unsigned long base_addr_lo, base_addr_hi;
479 	phys_addr_t paddr = virt_to_phys((void *)s);
480 	int num_calls = 0;
481 	struct sbiret ret;
482 
483 	split_phys_addr(paddr, &base_addr_hi, &base_addr_lo);
484 
485 	do {
486 		ret = sbi_dbcn_write(num_bytes, base_addr_lo, base_addr_hi);
487 		num_bytes -= ret.value;
488 		paddr += ret.value;
489 		split_phys_addr(paddr, &base_addr_hi, &base_addr_lo);
490 		num_calls++;
491 	} while (num_bytes != 0 && ret.error == SBI_SUCCESS);
492 
493 	report_xfail(xfail, ret.error == SBI_SUCCESS, "write success (error=%ld)", ret.error);
494 	report_info("%d sbi calls made", num_calls);
495 }
496 
497 static void dbcn_high_write_test(const char *s, unsigned long num_bytes,
498 				 phys_addr_t page_addr, size_t page_offset,
499 				 bool highmem_supported)
500 {
501 	int nr_pages = page_offset ? 2 : 1;
502 	void *vaddr;
503 
504 	if (page_addr != PAGE_ALIGN(page_addr) || page_addr + PAGE_SIZE < HIGH_ADDR_BOUNDARY ||
505 	    !check_addr(page_addr, nr_pages * PAGE_SIZE)) {
506 		report_skip("Memory above 4G required");
507 		return;
508 	}
509 
510 	vaddr = alloc_vpages(nr_pages);
511 
512 	for (int i = 0; i < nr_pages; ++i)
513 		install_page(current_pgtable(), page_addr + i * PAGE_SIZE, vaddr + i * PAGE_SIZE);
514 	memcpy(vaddr + page_offset, DBCN_WRITE_TEST_STRING, num_bytes);
515 	dbcn_write_test(vaddr + page_offset, num_bytes, !highmem_supported);
516 }
517 
518 /*
519  * Only the write functionality is tested here. There's no easy way to
520  * non-interactively test SBI_EXT_DBCN_CONSOLE_READ.
521  */
522 static void check_dbcn(void)
523 {
524 	unsigned long num_bytes = strlen(DBCN_WRITE_TEST_STRING);
525 	unsigned long base_addr_lo, base_addr_hi;
526 	bool do_invalid_addr = false;
527 	bool highmem_supported = true;
528 	phys_addr_t paddr;
529 	struct sbiret ret;
530 	const char *tmp;
531 	char *buf;
532 
533 	report_prefix_push("dbcn");
534 
535 	if (!sbi_probe(SBI_EXT_DBCN)) {
536 		report_skip("DBCN extension unavailable");
537 		report_prefix_pop();
538 		return;
539 	}
540 
541 	report_prefix_push("write");
542 
543 	dbcn_write_test(DBCN_WRITE_TEST_STRING, num_bytes, false);
544 
545 	assert(num_bytes < PAGE_SIZE);
546 
547 	report_prefix_push("page boundary");
548 	buf = alloc_pages(1);
549 	memcpy(&buf[PAGE_SIZE - num_bytes / 2], DBCN_WRITE_TEST_STRING, num_bytes);
550 	dbcn_write_test(&buf[PAGE_SIZE - num_bytes / 2], num_bytes, false);
551 	report_prefix_pop();
552 
553 	tmp = getenv("SBI_HIGHMEM_NOT_SUPPORTED");
554 	if (tmp && atol(tmp) != 0)
555 		highmem_supported = false;
556 
557 	report_prefix_push("high boundary");
558 	tmp = getenv("SBI_DBCN_SKIP_HIGH_BOUNDARY");
559 	if (!tmp || atol(tmp) == 0)
560 		dbcn_high_write_test(DBCN_WRITE_TEST_STRING, num_bytes,
561 				     HIGH_ADDR_BOUNDARY - PAGE_SIZE, PAGE_SIZE - num_bytes / 2,
562 				     highmem_supported);
563 	else
564 		report_skip("user disabled");
565 	report_prefix_pop();
566 
567 	report_prefix_push("high page");
568 	tmp = getenv("SBI_DBCN_SKIP_HIGH_PAGE");
569 	if (!tmp || atol(tmp) == 0) {
570 		paddr = HIGH_ADDR_BOUNDARY;
571 		tmp = getenv("HIGH_PAGE");
572 		if (tmp)
573 			paddr = strtoull(tmp, NULL, 0);
574 		dbcn_high_write_test(DBCN_WRITE_TEST_STRING, num_bytes, paddr, 0, highmem_supported);
575 	} else {
576 		report_skip("user disabled");
577 	}
578 	report_prefix_pop();
579 
580 	/* Bytes are read from memory and written to the console */
581 	report_prefix_push("invalid parameter");
582 	tmp = getenv("INVALID_ADDR_AUTO");
583 	if (tmp && atol(tmp) == 1) {
584 		paddr = get_highest_addr() + 1;
585 		do_invalid_addr = true;
586 	} else if (env_or_skip("INVALID_ADDR")) {
587 		paddr = strtoull(getenv("INVALID_ADDR"), NULL, 0);
588 		do_invalid_addr = true;
589 	}
590 
591 	if (do_invalid_addr) {
592 		split_phys_addr(paddr, &base_addr_hi, &base_addr_lo);
593 		ret = sbi_dbcn_write(1, base_addr_lo, base_addr_hi);
594 		report(ret.error == SBI_ERR_INVALID_PARAM, "address (error=%ld)", ret.error);
595 	}
596 	report_prefix_popn(2);
597 	report_prefix_push("write_byte");
598 
599 	puts("DBCN_WRITE_BYTE TEST BYTE: ");
600 	ret = sbi_dbcn_write_byte(DBCN_WRITE_BYTE_TEST_BYTE);
601 	puts("\n");
602 	report(ret.error == SBI_SUCCESS, "write success (error=%ld)", ret.error);
603 	report(ret.value == 0, "expected ret.value (%ld)", ret.value);
604 
605 	puts("DBCN_WRITE_BYTE TEST WORD: "); /* still expect 'a' in the output */
606 	ret = sbi_ecall(SBI_EXT_DBCN, SBI_EXT_DBCN_CONSOLE_WRITE_BYTE, 0x64636261, 0, 0, 0, 0, 0);
607 	puts("\n");
608 	report(ret.error == SBI_SUCCESS, "write success (error=%ld)", ret.error);
609 	report(ret.value == 0, "expected ret.value (%ld)", ret.value);
610 
611 	report_prefix_popn(2);
612 }
613 
614 int main(int argc, char **argv)
615 {
616 	if (argc > 1 && !strcmp(argv[1], "-h")) {
617 		help();
618 		exit(0);
619 	}
620 
621 	report_prefix_push("sbi");
622 	check_base();
623 	check_time();
624 	check_ipi();
625 	check_dbcn();
626 
627 	return report_summary();
628 }
629