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