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