| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * SBI verification |
| * |
| * Copyright (C) 2023, Ventana Micro Systems Inc., Andrew Jones <ajones@ventanamicro.com> |
| */ |
| #include <libcflat.h> |
| #include <alloc_page.h> |
| #include <cpumask.h> |
| #include <limits.h> |
| #include <memregions.h> |
| #include <on-cpus.h> |
| #include <rand.h> |
| #include <setjmp.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <vmalloc.h> |
| |
| #include <asm/barrier.h> |
| #include <asm/csr.h> |
| #include <asm/delay.h> |
| #include <asm/io.h> |
| #include <asm/mmu.h> |
| #include <asm/page.h> |
| #include <asm/processor.h> |
| #include <asm/sbi.h> |
| #include <asm/setup.h> |
| #include <asm/smp.h> |
| #include <asm/timer.h> |
| |
| #include "sbi-tests.h" |
| |
| #define HIGH_ADDR_BOUNDARY ((phys_addr_t)1 << 32) |
| |
| static long __labs(long a) |
| { |
| return __builtin_labs(a); |
| } |
| |
| static void help(void) |
| { |
| puts("Test SBI\n"); |
| puts("An environ must be provided where expected values are given.\n"); |
| } |
| |
| static struct sbiret sbi_base(int fid, unsigned long arg0) |
| { |
| return sbi_ecall(SBI_EXT_BASE, fid, arg0, 0, 0, 0, 0, 0); |
| } |
| |
| static struct sbiret sbi_dbcn_write(unsigned long num_bytes, unsigned long base_addr_lo, |
| unsigned long base_addr_hi) |
| { |
| return sbi_ecall(SBI_EXT_DBCN, SBI_EXT_DBCN_CONSOLE_WRITE, |
| num_bytes, base_addr_lo, base_addr_hi, 0, 0, 0); |
| } |
| |
| static struct sbiret sbi_dbcn_write_byte(uint8_t byte) |
| { |
| return sbi_ecall(SBI_EXT_DBCN, SBI_EXT_DBCN_CONSOLE_WRITE_BYTE, byte, 0, 0, 0, 0, 0); |
| } |
| |
| static struct sbiret sbi_hart_suspend(uint32_t suspend_type, unsigned long resume_addr, unsigned long opaque) |
| { |
| return sbi_ecall(SBI_EXT_HSM, SBI_EXT_HSM_HART_SUSPEND, suspend_type, resume_addr, opaque, 0, 0, 0); |
| } |
| |
| static struct sbiret sbi_system_suspend(uint32_t sleep_type, unsigned long resume_addr, unsigned long opaque) |
| { |
| return sbi_ecall(SBI_EXT_SUSP, 0, sleep_type, resume_addr, opaque, 0, 0, 0); |
| } |
| |
| static void start_cpu(void *data) |
| { |
| /* nothing to do */ |
| } |
| |
| static void stop_cpu(void *data) |
| { |
| struct sbiret ret = sbi_hart_stop(); |
| assert_msg(0, "cpu%d (hartid = %lx) failed to stop with sbiret.error %ld", |
| smp_processor_id(), current_thread_info()->hartid, ret.error); |
| } |
| |
| static int rand_online_cpu(prng_state *ps) |
| { |
| int cpu, me = smp_processor_id(); |
| |
| for (;;) { |
| cpu = prng32(ps) % nr_cpus; |
| cpu = cpumask_next(cpu - 1, &cpu_present_mask); |
| if (cpu != nr_cpus && cpu != me && cpu_present(cpu)) |
| break; |
| } |
| |
| return cpu; |
| } |
| |
| static void split_phys_addr(phys_addr_t paddr, unsigned long *hi, unsigned long *lo) |
| { |
| *lo = (unsigned long)paddr; |
| *hi = 0; |
| if (__riscv_xlen == 32) |
| *hi = (unsigned long)(paddr >> 32); |
| } |
| |
| static bool check_addr(phys_addr_t start, phys_addr_t size) |
| { |
| struct mem_region *r = memregions_find(start); |
| return r && r->end - start >= size && r->flags == MR_F_UNUSED; |
| } |
| |
| static phys_addr_t get_highest_addr(void) |
| { |
| phys_addr_t highest_end = 0; |
| struct mem_region *r; |
| |
| for (r = mem_regions; r->end; ++r) { |
| if (r->end > highest_end) |
| highest_end = r->end; |
| } |
| |
| return highest_end - 1; |
| } |
| |
| static bool env_enabled(const char *env) |
| { |
| char *s = getenv(env); |
| |
| return s && (*s == '1' || *s == 'y' || *s == 'Y'); |
| } |
| |
| static bool env_or_skip(const char *env) |
| { |
| if (!getenv(env)) { |
| report_skip("missing %s environment variable", env); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool get_invalid_addr(phys_addr_t *paddr, bool allow_default) |
| { |
| if (env_enabled("INVALID_ADDR_AUTO")) { |
| *paddr = get_highest_addr() + 1; |
| return true; |
| } else if (allow_default && !getenv("INVALID_ADDR")) { |
| *paddr = -1ul; |
| return true; |
| } else if (env_or_skip("INVALID_ADDR")) { |
| *paddr = strtoull(getenv("INVALID_ADDR"), NULL, 0); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static void gen_report(struct sbiret *ret, |
| long expected_error, long expected_value) |
| { |
| bool check_error = ret->error == expected_error; |
| bool check_value = ret->value == expected_value; |
| |
| if (!check_error || !check_value) |
| report_info("expected (error: %ld, value: %ld), received: (error: %ld, value %ld)", |
| expected_error, expected_value, ret->error, ret->value); |
| |
| report(check_error, "expected sbi.error"); |
| report(check_value, "expected sbi.value"); |
| } |
| |
| static void check_base(void) |
| { |
| struct sbiret ret; |
| long expected; |
| |
| report_prefix_push("base"); |
| |
| ret = sbi_base(SBI_EXT_BASE_GET_SPEC_VERSION, 0); |
| |
| report_prefix_push("spec_version"); |
| if (env_or_skip("SBI_SPEC_VERSION")) { |
| expected = (long)strtoul(getenv("SBI_SPEC_VERSION"), NULL, 0); |
| assert_msg(!(expected & BIT(31)), "SBI spec version bit 31 must be zero"); |
| assert_msg(__riscv_xlen == 32 || !(expected >> 32), "SBI spec version bits greater than 31 must be zero"); |
| gen_report(&ret, 0, expected); |
| } |
| report_prefix_pop(); |
| |
| ret.value &= 0x7ffffffful; |
| |
| if (ret.error || ret.value < 2) { |
| report_skip("SBI spec version 0.2 or higher required"); |
| return; |
| } |
| |
| report_prefix_push("impl_id"); |
| if (env_or_skip("SBI_IMPL_ID")) { |
| expected = (long)strtoul(getenv("SBI_IMPL_ID"), NULL, 0); |
| ret = sbi_base(SBI_EXT_BASE_GET_IMP_ID, 0); |
| gen_report(&ret, 0, expected); |
| } |
| report_prefix_pop(); |
| |
| report_prefix_push("impl_version"); |
| if (env_or_skip("SBI_IMPL_VERSION")) { |
| expected = (long)strtoul(getenv("SBI_IMPL_VERSION"), NULL, 0); |
| ret = sbi_base(SBI_EXT_BASE_GET_IMP_VERSION, 0); |
| gen_report(&ret, 0, expected); |
| } |
| report_prefix_pop(); |
| |
| report_prefix_push("probe_ext"); |
| expected = getenv("SBI_PROBE_EXT") ? (long)strtoul(getenv("SBI_PROBE_EXT"), NULL, 0) : 1; |
| ret = sbi_base(SBI_EXT_BASE_PROBE_EXT, SBI_EXT_BASE); |
| gen_report(&ret, 0, expected); |
| report_prefix_push("unavailable"); |
| ret = sbi_base(SBI_EXT_BASE_PROBE_EXT, 0xb000000); |
| gen_report(&ret, 0, 0); |
| report_prefix_popn(2); |
| |
| report_prefix_push("mvendorid"); |
| if (env_or_skip("MVENDORID")) { |
| expected = (long)strtoul(getenv("MVENDORID"), NULL, 0); |
| assert(__riscv_xlen == 32 || !(expected >> 32)); |
| ret = sbi_base(SBI_EXT_BASE_GET_MVENDORID, 0); |
| gen_report(&ret, 0, expected); |
| } |
| report_prefix_pop(); |
| |
| report_prefix_push("marchid"); |
| if (env_or_skip("MARCHID")) { |
| expected = (long)strtoul(getenv("MARCHID"), NULL, 0); |
| ret = sbi_base(SBI_EXT_BASE_GET_MARCHID, 0); |
| gen_report(&ret, 0, expected); |
| } |
| report_prefix_pop(); |
| |
| report_prefix_push("mimpid"); |
| if (env_or_skip("MIMPID")) { |
| expected = (long)strtoul(getenv("MIMPID"), NULL, 0); |
| ret = sbi_base(SBI_EXT_BASE_GET_MIMPID, 0); |
| gen_report(&ret, 0, expected); |
| } |
| report_prefix_popn(2); |
| } |
| |
| struct timer_info { |
| bool timer_works; |
| bool mask_timer_irq; |
| bool timer_irq_set; |
| bool timer_irq_cleared; |
| unsigned long timer_irq_count; |
| }; |
| |
| static struct timer_info timer_info; |
| |
| static bool timer_irq_pending(void) |
| { |
| return csr_read(CSR_SIP) & IP_TIP; |
| } |
| |
| static void timer_irq_handler(struct pt_regs *regs) |
| { |
| timer_info.timer_works = true; |
| |
| if (timer_info.timer_irq_count < ULONG_MAX) |
| ++timer_info.timer_irq_count; |
| |
| if (timer_irq_pending()) |
| timer_info.timer_irq_set = true; |
| |
| if (timer_info.mask_timer_irq) |
| timer_irq_disable(); |
| else |
| sbi_set_timer(ULONG_MAX); |
| |
| if (!timer_irq_pending()) |
| timer_info.timer_irq_cleared = true; |
| } |
| |
| static void timer_check_set_timer(bool mask_timer_irq) |
| { |
| struct sbiret ret; |
| unsigned long begin, end, duration; |
| const char *mask_test_str = mask_timer_irq ? " for mask irq test" : ""; |
| unsigned long d = getenv("SBI_TIMER_DELAY") ? strtol(getenv("SBI_TIMER_DELAY"), NULL, 0) : 200000; |
| unsigned long margin = getenv("SBI_TIMER_MARGIN") ? strtol(getenv("SBI_TIMER_MARGIN"), NULL, 0) : 200000; |
| |
| d = usec_to_cycles(d); |
| margin = usec_to_cycles(margin); |
| |
| timer_info = (struct timer_info){ .mask_timer_irq = mask_timer_irq }; |
| begin = timer_get_cycles(); |
| ret = sbi_set_timer(begin + d); |
| |
| report(!ret.error, "set timer%s", mask_test_str); |
| if (ret.error) |
| report_info("set timer%s failed with %ld\n", mask_test_str, ret.error); |
| |
| while ((end = timer_get_cycles()) <= (begin + d + margin) && !timer_info.timer_works) |
| cpu_relax(); |
| |
| report(timer_info.timer_works, "timer interrupt received%s", mask_test_str); |
| report(timer_info.timer_irq_set, "pending timer interrupt bit set in irq handler%s", mask_test_str); |
| |
| if (!mask_timer_irq) { |
| report(timer_info.timer_irq_set && timer_info.timer_irq_cleared, |
| "pending timer interrupt bit cleared by setting timer to -1"); |
| } |
| |
| if (timer_info.timer_works) { |
| duration = end - begin; |
| report(duration >= d && duration <= (d + margin), "timer delay honored%s", mask_test_str); |
| } |
| |
| report(timer_info.timer_irq_count == 1, "timer interrupt received exactly once%s", mask_test_str); |
| } |
| |
| static void check_time(void) |
| { |
| bool pending; |
| |
| report_prefix_push("time"); |
| |
| if (!sbi_probe(SBI_EXT_TIME)) { |
| report_skip("time extension not available"); |
| report_prefix_pop(); |
| return; |
| } |
| |
| report_prefix_push("set_timer"); |
| |
| install_irq_handler(IRQ_S_TIMER, timer_irq_handler); |
| local_irq_enable(); |
| timer_irq_enable(); |
| |
| timer_check_set_timer(false); |
| |
| if (csr_read(CSR_SIE) & IE_TIE) |
| timer_check_set_timer(true); |
| else |
| report_skip("timer irq enable bit is not writable, skipping mask irq test"); |
| |
| timer_irq_disable(); |
| sbi_set_timer(0); |
| pending = timer_irq_pending(); |
| report(pending, "timer immediately pending by setting timer to 0"); |
| sbi_set_timer(ULONG_MAX); |
| if (pending) |
| report(!timer_irq_pending(), "pending timer cleared while masked"); |
| else |
| report_skip("timer is not pending, skipping timer cleared while masked test"); |
| |
| local_irq_disable(); |
| install_irq_handler(IRQ_S_TIMER, NULL); |
| |
| report_prefix_popn(2); |
| } |
| |
| static bool ipi_received[NR_CPUS]; |
| static bool ipi_timeout[NR_CPUS]; |
| static cpumask_t ipi_done; |
| |
| static void ipi_timeout_handler(struct pt_regs *regs) |
| { |
| timer_stop(); |
| ipi_timeout[smp_processor_id()] = true; |
| } |
| |
| static void ipi_irq_handler(struct pt_regs *regs) |
| { |
| ipi_ack(); |
| ipi_received[smp_processor_id()] = true; |
| } |
| |
| static void ipi_hart_wait(void *data) |
| { |
| unsigned long timeout = (unsigned long)data; |
| int me = smp_processor_id(); |
| |
| install_irq_handler(IRQ_S_SOFT, ipi_irq_handler); |
| install_irq_handler(IRQ_S_TIMER, ipi_timeout_handler); |
| local_ipi_enable(); |
| timer_irq_enable(); |
| local_irq_enable(); |
| |
| timer_start(timeout); |
| while (!READ_ONCE(ipi_received[me]) && !READ_ONCE(ipi_timeout[me])) |
| cpu_relax(); |
| local_irq_disable(); |
| timer_stop(); |
| local_ipi_disable(); |
| timer_irq_disable(); |
| |
| cpumask_set_cpu(me, &ipi_done); |
| } |
| |
| static void ipi_hart_check(cpumask_t *mask) |
| { |
| int cpu; |
| |
| for_each_cpu(cpu, mask) { |
| if (ipi_timeout[cpu]) { |
| const char *rec = ipi_received[cpu] ? "but was still received" |
| : "and has still not been received"; |
| report_fail("ipi timed out on cpu%d %s", cpu, rec); |
| } |
| |
| ipi_timeout[cpu] = false; |
| ipi_received[cpu] = false; |
| } |
| } |
| |
| static void check_ipi(void) |
| { |
| unsigned long d = getenv("SBI_IPI_TIMEOUT") ? strtol(getenv("SBI_IPI_TIMEOUT"), NULL, 0) : 200000; |
| int nr_cpus_present = cpumask_weight(&cpu_present_mask); |
| int me = smp_processor_id(); |
| unsigned long max_hartid = 0; |
| unsigned long hartid1, hartid2; |
| cpumask_t ipi_receivers; |
| static prng_state ps; |
| struct sbiret ret; |
| int cpu, cpu2; |
| |
| ps = prng_init(0xDEADBEEF); |
| |
| report_prefix_push("ipi"); |
| |
| if (!sbi_probe(SBI_EXT_IPI)) { |
| report_skip("ipi extension not available"); |
| report_prefix_pop(); |
| return; |
| } |
| |
| if (nr_cpus_present < 2) { |
| report_skip("At least 2 cpus required"); |
| report_prefix_pop(); |
| return; |
| } |
| |
| report_prefix_push("random hart"); |
| cpumask_clear(&ipi_done); |
| cpumask_clear(&ipi_receivers); |
| cpu = rand_online_cpu(&ps); |
| cpumask_set_cpu(cpu, &ipi_receivers); |
| on_cpu_async(cpu, ipi_hart_wait, (void *)d); |
| ret = sbi_send_ipi_cpu(cpu); |
| report(ret.error == SBI_SUCCESS, "ipi returned success"); |
| while (!cpumask_equal(&ipi_done, &ipi_receivers)) |
| cpu_relax(); |
| ipi_hart_check(&ipi_receivers); |
| report_prefix_pop(); |
| |
| report_prefix_push("two in hart_mask"); |
| |
| if (nr_cpus_present < 3) { |
| report_skip("3 cpus required"); |
| goto end_two; |
| } |
| |
| cpu = rand_online_cpu(&ps); |
| hartid1 = cpus[cpu].hartid; |
| hartid2 = 0; |
| for_each_present_cpu(cpu2) { |
| if (cpu2 == cpu || cpu2 == me) |
| continue; |
| hartid2 = cpus[cpu2].hartid; |
| if (__labs(hartid2 - hartid1) < BITS_PER_LONG) |
| break; |
| } |
| if (cpu2 == nr_cpus) { |
| report_skip("hartids are too sparse"); |
| goto end_two; |
| } |
| |
| cpumask_clear(&ipi_done); |
| cpumask_clear(&ipi_receivers); |
| cpumask_set_cpu(cpu, &ipi_receivers); |
| cpumask_set_cpu(cpu2, &ipi_receivers); |
| on_cpu_async(cpu, ipi_hart_wait, (void *)d); |
| on_cpu_async(cpu2, ipi_hart_wait, (void *)d); |
| ret = sbi_send_ipi((1UL << __labs(hartid2 - hartid1)) | 1UL, hartid1 < hartid2 ? hartid1 : hartid2); |
| report(ret.error == SBI_SUCCESS, "ipi returned success"); |
| while (!cpumask_equal(&ipi_done, &ipi_receivers)) |
| cpu_relax(); |
| ipi_hart_check(&ipi_receivers); |
| end_two: |
| report_prefix_pop(); |
| |
| report_prefix_push("broadcast"); |
| cpumask_clear(&ipi_done); |
| cpumask_copy(&ipi_receivers, &cpu_present_mask); |
| cpumask_clear_cpu(me, &ipi_receivers); |
| on_cpumask_async(&ipi_receivers, ipi_hart_wait, (void *)d); |
| ret = sbi_send_ipi_broadcast(); |
| report(ret.error == SBI_SUCCESS, "ipi returned success"); |
| while (!cpumask_equal(&ipi_done, &ipi_receivers)) |
| cpu_relax(); |
| ipi_hart_check(&ipi_receivers); |
| report_prefix_pop(); |
| |
| report_prefix_push("invalid parameters"); |
| |
| for_each_present_cpu(cpu) { |
| if (cpus[cpu].hartid > max_hartid) |
| max_hartid = cpus[cpu].hartid; |
| } |
| |
| /* Try the next higher hartid than the max */ |
| ret = sbi_send_ipi(2, max_hartid); |
| report_kfail(true, ret.error == SBI_ERR_INVALID_PARAM, "hart_mask got expected error (%ld)", ret.error); |
| ret = sbi_send_ipi(1, max_hartid + 1); |
| report_kfail(true, ret.error == SBI_ERR_INVALID_PARAM, "hart_mask_base got expected error (%ld)", ret.error); |
| |
| report_prefix_pop(); |
| |
| report_prefix_pop(); |
| } |
| |
| unsigned char sbi_hsm_stop_hart[NR_CPUS]; |
| unsigned char sbi_hsm_hart_start_checks[NR_CPUS]; |
| unsigned char sbi_hsm_non_retentive_hart_suspend_checks[NR_CPUS]; |
| |
| static const char * const hart_state_str[] = { |
| [SBI_EXT_HSM_STARTED] = "started", |
| [SBI_EXT_HSM_STOPPED] = "stopped", |
| [SBI_EXT_HSM_SUSPENDED] = "suspended", |
| }; |
| struct hart_state_transition_info { |
| enum sbi_ext_hsm_sid initial_state; |
| enum sbi_ext_hsm_sid intermediate_state; |
| enum sbi_ext_hsm_sid final_state; |
| }; |
| static cpumask_t sbi_hsm_started_hart_checks; |
| static bool sbi_hsm_invalid_hartid_check; |
| static bool sbi_hsm_timer_fired; |
| extern void sbi_hsm_check_hart_start(void); |
| extern void sbi_hsm_check_non_retentive_suspend(void); |
| |
| static void hsm_timer_irq_handler(struct pt_regs *regs) |
| { |
| timer_stop(); |
| sbi_hsm_timer_fired = true; |
| } |
| |
| static void hsm_timer_setup(void) |
| { |
| install_irq_handler(IRQ_S_TIMER, hsm_timer_irq_handler); |
| timer_irq_enable(); |
| } |
| |
| static void hsm_timer_teardown(void) |
| { |
| timer_irq_disable(); |
| install_irq_handler(IRQ_S_TIMER, NULL); |
| } |
| |
| static void hart_check_already_started(void *data) |
| { |
| struct sbiret ret; |
| unsigned long hartid = current_thread_info()->hartid; |
| int me = smp_processor_id(); |
| |
| ret = sbi_hart_start(hartid, virt_to_phys(&start_cpu), 0); |
| |
| if (ret.error == SBI_ERR_ALREADY_AVAILABLE) |
| cpumask_set_cpu(me, &sbi_hsm_started_hart_checks); |
| } |
| |
| static void hart_start_invalid_hartid(void *data) |
| { |
| struct sbiret ret; |
| |
| ret = sbi_hart_start(-1UL, virt_to_phys(&start_cpu), 0); |
| |
| if (ret.error == SBI_ERR_INVALID_PARAM) |
| sbi_hsm_invalid_hartid_check = true; |
| } |
| |
| static void hart_retentive_suspend(void *data) |
| { |
| unsigned long hartid = current_thread_info()->hartid; |
| struct sbiret ret = sbi_hart_suspend(SBI_EXT_HSM_HART_SUSPEND_RETENTIVE, 0, 0); |
| |
| if (ret.error) |
| report_fail("failed to retentive suspend cpu%d (hartid = %lx) (error=%ld)", |
| smp_processor_id(), hartid, ret.error); |
| } |
| |
| static void hart_non_retentive_suspend(void *data) |
| { |
| unsigned long hartid = current_thread_info()->hartid; |
| unsigned long params[] = { |
| [SBI_HSM_MAGIC_IDX] = SBI_HSM_MAGIC, |
| [SBI_HSM_HARTID_IDX] = hartid, |
| }; |
| struct sbiret ret = sbi_hart_suspend(SBI_EXT_HSM_HART_SUSPEND_NON_RETENTIVE, |
| virt_to_phys(&sbi_hsm_check_non_retentive_suspend), |
| virt_to_phys(params)); |
| |
| report_fail("failed to non-retentive suspend cpu%d (hartid = %lx) (error=%ld)", |
| smp_processor_id(), hartid, ret.error); |
| } |
| |
| /* This test function is only being run on RV64 to verify that upper bits of suspend_type are ignored */ |
| static void hart_retentive_suspend_with_msb_set(void *data) |
| { |
| unsigned long hartid = current_thread_info()->hartid; |
| unsigned long suspend_type = SBI_EXT_HSM_HART_SUSPEND_RETENTIVE | (_AC(1, UL) << (__riscv_xlen - 1)); |
| struct sbiret ret = sbi_ecall(SBI_EXT_HSM, SBI_EXT_HSM_HART_SUSPEND, suspend_type, 0, 0, 0, 0, 0); |
| |
| if (ret.error) |
| report_fail("failed to retentive suspend cpu%d (hartid = %lx) with MSB set (error=%ld)", |
| smp_processor_id(), hartid, ret.error); |
| } |
| |
| /* This test function is only being run on RV64 to verify that upper bits of suspend_type are ignored */ |
| static void hart_non_retentive_suspend_with_msb_set(void *data) |
| { |
| unsigned long hartid = current_thread_info()->hartid; |
| unsigned long suspend_type = SBI_EXT_HSM_HART_SUSPEND_NON_RETENTIVE | (_AC(1, UL) << (__riscv_xlen - 1)); |
| unsigned long params[] = { |
| [SBI_HSM_MAGIC_IDX] = SBI_HSM_MAGIC, |
| [SBI_HSM_HARTID_IDX] = hartid, |
| }; |
| |
| struct sbiret ret = sbi_ecall(SBI_EXT_HSM, SBI_EXT_HSM_HART_SUSPEND, suspend_type, |
| virt_to_phys(&sbi_hsm_check_non_retentive_suspend), virt_to_phys(params), |
| 0, 0, 0); |
| |
| report_fail("failed to non-retentive suspend cpu%d (hartid = %lx) with MSB set (error=%ld)", |
| smp_processor_id(), hartid, ret.error); |
| } |
| |
| static bool hart_wait_on_status(unsigned long hartid, enum sbi_ext_hsm_sid status, unsigned long duration) |
| { |
| struct sbiret ret; |
| |
| sbi_hsm_timer_fired = false; |
| timer_start(duration); |
| |
| ret = sbi_hart_get_status(hartid); |
| |
| while (!ret.error && ret.value == status && !sbi_hsm_timer_fired) { |
| cpu_relax(); |
| ret = sbi_hart_get_status(hartid); |
| } |
| |
| timer_stop(); |
| |
| if (sbi_hsm_timer_fired) |
| report_info("timer fired while waiting on status %u for hartid %lx", status, hartid); |
| else if (ret.error) |
| report_fail("got %ld while waiting on status %u for hartid %lx", ret.error, status, hartid); |
| |
| return !sbi_hsm_timer_fired && !ret.error; |
| } |
| |
| static int hart_wait_state_transition(cpumask_t *mask, unsigned long duration, |
| struct hart_state_transition_info *states) |
| { |
| struct sbiret ret; |
| unsigned long hartid; |
| int cpu, count = 0; |
| |
| for_each_cpu(cpu, mask) { |
| hartid = cpus[cpu].hartid; |
| if (!hart_wait_on_status(hartid, states->initial_state, duration)) |
| continue; |
| if (!hart_wait_on_status(hartid, states->intermediate_state, duration)) |
| continue; |
| |
| ret = sbi_hart_get_status(hartid); |
| if (ret.error) |
| report_info("hartid %lx get status failed (error=%ld)", hartid, ret.error); |
| else if (ret.value != states->final_state) |
| report_info("hartid %lx status is not '%s' (ret.value=%ld)", hartid, |
| hart_state_str[states->final_state], ret.value); |
| else |
| count++; |
| } |
| |
| return count; |
| } |
| |
| static void hart_wait_until_idle(cpumask_t *mask, unsigned long duration) |
| { |
| sbi_hsm_timer_fired = false; |
| timer_start(duration); |
| |
| while (!cpumask_subset(mask, &cpu_idle_mask) && !sbi_hsm_timer_fired) |
| cpu_relax(); |
| |
| timer_stop(); |
| |
| if (sbi_hsm_timer_fired) |
| report_info("hsm timer fired before all cpus became idle"); |
| } |
| |
| static void check_hsm(void) |
| { |
| struct sbiret ret; |
| unsigned long hartid; |
| cpumask_t secondary_cpus_mask, mask; |
| struct hart_state_transition_info transition_states; |
| bool ipi_unavailable = false; |
| int cpu, me = smp_processor_id(); |
| int max_cpus = getenv("SBI_MAX_CPUS") ? strtol(getenv("SBI_MAX_CPUS"), NULL, 0) : nr_cpus; |
| unsigned long hsm_timer_duration = getenv("SBI_HSM_TIMER_DURATION") |
| ? strtol(getenv("SBI_HSM_TIMER_DURATION"), NULL, 0) : 200000; |
| unsigned long sbi_hsm_hart_start_params[NR_CPUS * SBI_HSM_NUM_OF_PARAMS]; |
| int count, check; |
| |
| max_cpus = MIN(MIN(max_cpus, nr_cpus), cpumask_weight(&cpu_present_mask)); |
| |
| report_prefix_push("hsm"); |
| |
| if (!sbi_probe(SBI_EXT_HSM)) { |
| report_skip("hsm extension not available"); |
| report_prefix_pop(); |
| return; |
| } |
| |
| report_prefix_push("hart_get_status"); |
| |
| hartid = current_thread_info()->hartid; |
| ret = sbi_hart_get_status(hartid); |
| |
| if (ret.error) { |
| report_fail("failed to get status of current hart (error=%ld)", ret.error); |
| report_prefix_popn(2); |
| return; |
| } else if (ret.value != SBI_EXT_HSM_STARTED) { |
| report_fail("current hart is not started (ret.value=%ld)", ret.value); |
| report_prefix_popn(2); |
| return; |
| } |
| |
| report_pass("status of current hart is started"); |
| |
| report_prefix_pop(); |
| |
| if (max_cpus < 2) { |
| report_skip("no other cpus to run the remaining hsm tests on"); |
| report_prefix_pop(); |
| return; |
| } |
| |
| report_prefix_push("hart_stop"); |
| |
| cpumask_copy(&secondary_cpus_mask, &cpu_present_mask); |
| cpumask_clear_cpu(me, &secondary_cpus_mask); |
| hsm_timer_setup(); |
| local_irq_enable(); |
| |
| /* Assume that previous tests have not cleaned up and stopped the secondary harts */ |
| on_cpumask_async(&secondary_cpus_mask, stop_cpu, NULL); |
| |
| transition_states = (struct hart_state_transition_info) { |
| .initial_state = SBI_EXT_HSM_STARTED, |
| .intermediate_state = SBI_EXT_HSM_STOP_PENDING, |
| .final_state = SBI_EXT_HSM_STOPPED, |
| }; |
| count = hart_wait_state_transition(&secondary_cpus_mask, hsm_timer_duration, &transition_states); |
| |
| report(count == max_cpus - 1, "all secondary harts stopped"); |
| |
| report_prefix_pop(); |
| |
| report_prefix_push("hart_start"); |
| |
| for_each_cpu(cpu, &secondary_cpus_mask) { |
| hartid = cpus[cpu].hartid; |
| sbi_hsm_hart_start_params[cpu * SBI_HSM_NUM_OF_PARAMS + SBI_HSM_MAGIC_IDX] = SBI_HSM_MAGIC; |
| sbi_hsm_hart_start_params[cpu * SBI_HSM_NUM_OF_PARAMS + SBI_HSM_HARTID_IDX] = hartid; |
| |
| ret = sbi_hart_start(hartid, virt_to_phys(&sbi_hsm_check_hart_start), |
| virt_to_phys(&sbi_hsm_hart_start_params[cpu * SBI_HSM_NUM_OF_PARAMS])); |
| if (ret.error) { |
| report_fail("failed to start test on cpu%d (hartid = %lx) (error=%ld)", cpu, hartid, ret.error); |
| continue; |
| } |
| } |
| |
| transition_states = (struct hart_state_transition_info) { |
| .initial_state = SBI_EXT_HSM_STOPPED, |
| .intermediate_state = SBI_EXT_HSM_START_PENDING, |
| .final_state = SBI_EXT_HSM_STARTED, |
| }; |
| count = hart_wait_state_transition(&secondary_cpus_mask, hsm_timer_duration, &transition_states); |
| check = 0; |
| |
| for_each_cpu(cpu, &secondary_cpus_mask) { |
| sbi_hsm_timer_fired = false; |
| timer_start(hsm_timer_duration); |
| |
| while (!(READ_ONCE(sbi_hsm_hart_start_checks[cpu]) & SBI_HSM_TEST_DONE) && !sbi_hsm_timer_fired) |
| cpu_relax(); |
| |
| timer_stop(); |
| |
| if (sbi_hsm_timer_fired) { |
| report_info("hsm timer fired before cpu%d (hartid = %lx) is done with start checks", cpu, hartid); |
| continue; |
| } |
| |
| if (!(sbi_hsm_hart_start_checks[cpu] & SBI_HSM_TEST_SATP)) |
| report_info("satp is not zero for test on cpu%d (hartid = %lx)", cpu, hartid); |
| else if (!(sbi_hsm_hart_start_checks[cpu] & SBI_HSM_TEST_SIE)) |
| report_info("sstatus.SIE is not zero for test on cpu%d (hartid = %lx)", cpu, hartid); |
| else if (!(sbi_hsm_hart_start_checks[cpu] & SBI_HSM_TEST_MAGIC_A1)) |
| report_info("a1 does not start with magic for test on cpu%d (hartid = %lx)", cpu, hartid); |
| else if (!(sbi_hsm_hart_start_checks[cpu] & SBI_HSM_TEST_HARTID_A0)) |
| report_info("a0 is not hartid for test on cpu %d (hartid = %lx)", cpu, hartid); |
| else |
| check++; |
| } |
| |
| report(count == max_cpus - 1, "all secondary harts started"); |
| report(check == max_cpus - 1, "all secondary harts have expected register values after hart start"); |
| |
| report_prefix_pop(); |
| |
| report_prefix_push("hart_stop"); |
| |
| memset(sbi_hsm_stop_hart, 1, sizeof(sbi_hsm_stop_hart)); |
| |
| transition_states = (struct hart_state_transition_info) { |
| .initial_state = SBI_EXT_HSM_STARTED, |
| .intermediate_state = SBI_EXT_HSM_STOP_PENDING, |
| .final_state = SBI_EXT_HSM_STOPPED, |
| }; |
| count = hart_wait_state_transition(&secondary_cpus_mask, hsm_timer_duration, &transition_states); |
| |
| report(count == max_cpus - 1, "all secondary harts stopped"); |
| |
| /* Reset the stop flags so that we can reuse them after suspension tests */ |
| memset(sbi_hsm_stop_hart, 0, sizeof(sbi_hsm_stop_hart)); |
| |
| report_prefix_pop(); |
| |
| report_prefix_push("hart_start"); |
| |
| /* Select just one secondary cpu to run the invalid hartid test */ |
| on_cpu(cpumask_next(-1, &secondary_cpus_mask), hart_start_invalid_hartid, NULL); |
| |
| report(sbi_hsm_invalid_hartid_check, "secondary hart refuse to start with invalid hartid"); |
| |
| on_cpumask_async(&secondary_cpus_mask, hart_check_already_started, NULL); |
| |
| transition_states = (struct hart_state_transition_info) { |
| .initial_state = SBI_EXT_HSM_STOPPED, |
| .intermediate_state = SBI_EXT_HSM_START_PENDING, |
| .final_state = SBI_EXT_HSM_STARTED, |
| }; |
| count = hart_wait_state_transition(&secondary_cpus_mask, hsm_timer_duration, &transition_states); |
| |
| report(count == max_cpus - 1, "all secondary harts started"); |
| |
| hart_wait_until_idle(&secondary_cpus_mask, hsm_timer_duration); |
| |
| report(cpumask_weight(&sbi_hsm_started_hart_checks) == max_cpus - 1, |
| "all secondary harts are already started"); |
| |
| report_prefix_pop(); |
| |
| report_prefix_push("hart_suspend"); |
| |
| if (!sbi_probe(SBI_EXT_IPI)) { |
| report_skip("skipping suspension tests since ipi extension is unavailable"); |
| report_prefix_pop(); |
| ipi_unavailable = true; |
| goto sbi_hsm_hart_stop_tests; |
| } |
| |
| on_cpumask_async(&secondary_cpus_mask, hart_retentive_suspend, NULL); |
| |
| transition_states = (struct hart_state_transition_info) { |
| .initial_state = SBI_EXT_HSM_STARTED, |
| .intermediate_state = SBI_EXT_HSM_SUSPEND_PENDING, |
| .final_state = SBI_EXT_HSM_SUSPENDED, |
| }; |
| count = hart_wait_state_transition(&secondary_cpus_mask, hsm_timer_duration, &transition_states); |
| |
| report(count == max_cpus - 1, "all secondary harts retentive suspended"); |
| |
| /* Ignore the return value since we check the status of each hart anyway */ |
| sbi_send_ipi_cpumask(&secondary_cpus_mask); |
| |
| transition_states = (struct hart_state_transition_info) { |
| .initial_state = SBI_EXT_HSM_SUSPENDED, |
| .intermediate_state = SBI_EXT_HSM_RESUME_PENDING, |
| .final_state = SBI_EXT_HSM_STARTED, |
| }; |
| count = hart_wait_state_transition(&secondary_cpus_mask, hsm_timer_duration, &transition_states); |
| |
| report(count == max_cpus - 1, "all secondary harts retentive resumed"); |
| |
| hart_wait_until_idle(&secondary_cpus_mask, hsm_timer_duration); |
| |
| on_cpumask_async(&secondary_cpus_mask, hart_non_retentive_suspend, NULL); |
| |
| transition_states = (struct hart_state_transition_info) { |
| .initial_state = SBI_EXT_HSM_STARTED, |
| .intermediate_state = SBI_EXT_HSM_SUSPEND_PENDING, |
| .final_state = SBI_EXT_HSM_SUSPENDED, |
| }; |
| count = hart_wait_state_transition(&secondary_cpus_mask, hsm_timer_duration, &transition_states); |
| |
| report(count == max_cpus - 1, "all secondary harts non-retentive suspended"); |
| |
| /* Ignore the return value since we check the status of each hart anyway */ |
| sbi_send_ipi_cpumask(&secondary_cpus_mask); |
| |
| transition_states = (struct hart_state_transition_info) { |
| .initial_state = SBI_EXT_HSM_SUSPENDED, |
| .intermediate_state = SBI_EXT_HSM_RESUME_PENDING, |
| .final_state = SBI_EXT_HSM_STARTED, |
| }; |
| count = hart_wait_state_transition(&secondary_cpus_mask, hsm_timer_duration, &transition_states); |
| check = 0; |
| |
| for_each_cpu(cpu, &secondary_cpus_mask) { |
| sbi_hsm_timer_fired = false; |
| timer_start(hsm_timer_duration); |
| |
| while (!(READ_ONCE(sbi_hsm_non_retentive_hart_suspend_checks[cpu]) & SBI_HSM_TEST_DONE) && !sbi_hsm_timer_fired) |
| cpu_relax(); |
| |
| timer_stop(); |
| |
| if (sbi_hsm_timer_fired) { |
| report_info("hsm timer fired before hart %ld is done with non-retentive resume checks", hartid); |
| continue; |
| } |
| |
| if (!(sbi_hsm_non_retentive_hart_suspend_checks[cpu] & SBI_HSM_TEST_SATP)) |
| report_info("satp is not zero for test on cpu%d (hartid = %lx)", cpu, hartid); |
| else if (!(sbi_hsm_non_retentive_hart_suspend_checks[cpu] & SBI_HSM_TEST_SIE)) |
| report_info("sstatus.SIE is not zero for test on cpu%d (hartid = %lx)", cpu, hartid); |
| else if (!(sbi_hsm_non_retentive_hart_suspend_checks[cpu] & SBI_HSM_TEST_MAGIC_A1)) |
| report_info("a1 does not start with magic for test on cpu%d (hartid = %lx)", cpu, hartid); |
| else if (!(sbi_hsm_non_retentive_hart_suspend_checks[cpu] & SBI_HSM_TEST_HARTID_A0)) |
| report_info("a0 is not hartid for test on cpu%d (hartid = %lx)", cpu, hartid); |
| else |
| check++; |
| } |
| |
| report(count == max_cpus - 1, "all secondary harts non-retentive resumed"); |
| report(check == max_cpus - 1, "all secondary harts have expected register values after non-retentive resume"); |
| |
| report_prefix_pop(); |
| |
| sbi_hsm_hart_stop_tests: |
| report_prefix_push("hart_stop"); |
| |
| if (ipi_unavailable) |
| on_cpumask_async(&secondary_cpus_mask, stop_cpu, NULL); |
| else |
| memset(sbi_hsm_stop_hart, 1, sizeof(sbi_hsm_stop_hart)); |
| |
| transition_states = (struct hart_state_transition_info) { |
| .initial_state = SBI_EXT_HSM_STARTED, |
| .intermediate_state = SBI_EXT_HSM_STOP_PENDING, |
| .final_state = SBI_EXT_HSM_STOPPED, |
| }; |
| count = hart_wait_state_transition(&secondary_cpus_mask, hsm_timer_duration, &transition_states); |
| |
| report(count == max_cpus - 1, "all secondary harts stopped"); |
| |
| report_prefix_pop(); |
| |
| if (__riscv_xlen == 32 || ipi_unavailable) { |
| local_irq_disable(); |
| hsm_timer_teardown(); |
| report_prefix_pop(); |
| return; |
| } |
| |
| report_prefix_push("hart_suspend"); |
| |
| /* Select just one secondary cpu to run suspension tests with MSB of suspend type being set */ |
| cpu = cpumask_next(-1, &secondary_cpus_mask); |
| hartid = cpus[cpu].hartid; |
| cpumask_clear(&mask); |
| cpumask_set_cpu(cpu, &mask); |
| |
| /* Boot up the secondary cpu and let it proceed to the idle loop */ |
| on_cpu(cpu, start_cpu, NULL); |
| |
| on_cpu_async(cpu, hart_retentive_suspend_with_msb_set, NULL); |
| |
| transition_states = (struct hart_state_transition_info) { |
| .initial_state = SBI_EXT_HSM_STARTED, |
| .intermediate_state = SBI_EXT_HSM_SUSPEND_PENDING, |
| .final_state = SBI_EXT_HSM_SUSPENDED, |
| }; |
| count = hart_wait_state_transition(&mask, hsm_timer_duration, &transition_states); |
| |
| report(count, "secondary hart retentive suspended with MSB set"); |
| |
| /* Ignore the return value since we manually validate the status of the hart anyway */ |
| sbi_send_ipi_cpu(cpu); |
| |
| transition_states = (struct hart_state_transition_info) { |
| .initial_state = SBI_EXT_HSM_SUSPENDED, |
| .intermediate_state = SBI_EXT_HSM_RESUME_PENDING, |
| .final_state = SBI_EXT_HSM_STARTED, |
| }; |
| count = hart_wait_state_transition(&mask, hsm_timer_duration, &transition_states); |
| |
| report(count, "secondary hart retentive resumed with MSB set"); |
| |
| /* Reset these flags so that we can reuse them for the non-retentive suspension test */ |
| sbi_hsm_stop_hart[cpu] = 0; |
| sbi_hsm_non_retentive_hart_suspend_checks[cpu] = 0; |
| |
| on_cpu_async(cpu, hart_non_retentive_suspend_with_msb_set, NULL); |
| |
| transition_states = (struct hart_state_transition_info) { |
| .initial_state = SBI_EXT_HSM_STARTED, |
| .intermediate_state = SBI_EXT_HSM_SUSPEND_PENDING, |
| .final_state = SBI_EXT_HSM_SUSPENDED, |
| }; |
| count = hart_wait_state_transition(&mask, hsm_timer_duration, &transition_states); |
| |
| report(count, "secondary hart non-retentive suspended with MSB set"); |
| |
| /* Ignore the return value since we manually validate the status of the hart anyway */ |
| sbi_send_ipi_cpu(cpu); |
| |
| transition_states = (struct hart_state_transition_info) { |
| .initial_state = SBI_EXT_HSM_SUSPENDED, |
| .intermediate_state = SBI_EXT_HSM_RESUME_PENDING, |
| .final_state = SBI_EXT_HSM_STARTED, |
| }; |
| count = hart_wait_state_transition(&mask, hsm_timer_duration, &transition_states); |
| check = 0; |
| |
| if (count) { |
| sbi_hsm_timer_fired = false; |
| timer_start(hsm_timer_duration); |
| |
| while (!(READ_ONCE(sbi_hsm_non_retentive_hart_suspend_checks[cpu]) & SBI_HSM_TEST_DONE) && !sbi_hsm_timer_fired) |
| cpu_relax(); |
| |
| timer_stop(); |
| |
| if (sbi_hsm_timer_fired) { |
| report_info("hsm timer fired before cpu%d (hartid = %lx) is done with non-retentive resume checks", cpu, hartid); |
| } else { |
| if (!(sbi_hsm_non_retentive_hart_suspend_checks[cpu] & SBI_HSM_TEST_SATP)) |
| report_info("satp is not zero for test on cpu%d (hartid = %lx)", cpu, hartid); |
| else if (!(sbi_hsm_non_retentive_hart_suspend_checks[cpu] & SBI_HSM_TEST_SIE)) |
| report_info("sstatus.SIE is not zero for test on cpu%d (hartid = %lx)", cpu, hartid); |
| else if (!(sbi_hsm_non_retentive_hart_suspend_checks[cpu] & SBI_HSM_TEST_MAGIC_A1)) |
| report_info("a1 does not start with magic for test on cpu%d (hartid = %lx)", cpu, hartid); |
| else if (!(sbi_hsm_non_retentive_hart_suspend_checks[cpu] & SBI_HSM_TEST_HARTID_A0)) |
| report_info("a0 is not hartid for test on cpu%d (hartid = %lx)", cpu, hartid); |
| else |
| check = 1; |
| } |
| } |
| |
| report(count, "secondary hart non-retentive resumed with MSB set"); |
| report(check, "secondary hart has expected register values after non-retentive resume with MSB set"); |
| |
| report_prefix_pop(); |
| |
| report_prefix_push("hart_stop"); |
| |
| sbi_hsm_stop_hart[cpu] = 1; |
| |
| transition_states = (struct hart_state_transition_info) { |
| .initial_state = SBI_EXT_HSM_STARTED, |
| .intermediate_state = SBI_EXT_HSM_STOP_PENDING, |
| .final_state = SBI_EXT_HSM_STOPPED, |
| }; |
| count = hart_wait_state_transition(&mask, hsm_timer_duration, &transition_states); |
| |
| report(count, "secondary hart stopped after suspension tests with MSB set"); |
| |
| local_irq_disable(); |
| hsm_timer_teardown(); |
| report_prefix_popn(2); |
| } |
| |
| #define DBCN_WRITE_TEST_STRING "DBCN_WRITE_TEST_STRING\n" |
| #define DBCN_WRITE_BYTE_TEST_BYTE ((u8)'a') |
| |
| static void dbcn_write_test(const char *s, unsigned long num_bytes, bool xfail) |
| { |
| unsigned long base_addr_lo, base_addr_hi; |
| phys_addr_t paddr = virt_to_phys((void *)s); |
| int num_calls = 0; |
| struct sbiret ret; |
| |
| split_phys_addr(paddr, &base_addr_hi, &base_addr_lo); |
| |
| do { |
| ret = sbi_dbcn_write(num_bytes, base_addr_lo, base_addr_hi); |
| num_bytes -= ret.value; |
| paddr += ret.value; |
| split_phys_addr(paddr, &base_addr_hi, &base_addr_lo); |
| num_calls++; |
| } while (num_bytes != 0 && ret.error == SBI_SUCCESS); |
| |
| report_xfail(xfail, ret.error == SBI_SUCCESS, "write success (error=%ld)", ret.error); |
| report_info("%d sbi calls made", num_calls); |
| } |
| |
| static void dbcn_high_write_test(const char *s, unsigned long num_bytes, |
| phys_addr_t page_addr, size_t page_offset, |
| bool highmem_supported) |
| { |
| int nr_pages = page_offset ? 2 : 1; |
| void *vaddr; |
| |
| if (page_addr != PAGE_ALIGN(page_addr) || page_addr + PAGE_SIZE < HIGH_ADDR_BOUNDARY || |
| !check_addr(page_addr, nr_pages * PAGE_SIZE)) { |
| report_skip("Memory above 4G required"); |
| return; |
| } |
| |
| vaddr = alloc_vpages(nr_pages); |
| |
| for (int i = 0; i < nr_pages; ++i) |
| install_page(current_pgtable(), page_addr + i * PAGE_SIZE, vaddr + i * PAGE_SIZE); |
| memcpy(vaddr + page_offset, DBCN_WRITE_TEST_STRING, num_bytes); |
| dbcn_write_test(vaddr + page_offset, num_bytes, !highmem_supported); |
| } |
| |
| /* |
| * Only the write functionality is tested here. There's no easy way to |
| * non-interactively test SBI_EXT_DBCN_CONSOLE_READ. |
| */ |
| static void check_dbcn(void) |
| { |
| unsigned long num_bytes = strlen(DBCN_WRITE_TEST_STRING); |
| unsigned long base_addr_lo, base_addr_hi; |
| bool highmem_supported = true; |
| phys_addr_t paddr; |
| struct sbiret ret; |
| char *buf; |
| |
| report_prefix_push("dbcn"); |
| |
| if (!sbi_probe(SBI_EXT_DBCN)) { |
| report_skip("DBCN extension unavailable"); |
| report_prefix_pop(); |
| return; |
| } |
| |
| report_prefix_push("write"); |
| |
| dbcn_write_test(DBCN_WRITE_TEST_STRING, num_bytes, false); |
| |
| assert(num_bytes < PAGE_SIZE); |
| |
| report_prefix_push("page boundary"); |
| buf = alloc_pages(1); |
| memcpy(&buf[PAGE_SIZE - num_bytes / 2], DBCN_WRITE_TEST_STRING, num_bytes); |
| dbcn_write_test(&buf[PAGE_SIZE - num_bytes / 2], num_bytes, false); |
| report_prefix_pop(); |
| |
| if (env_enabled("SBI_HIGHMEM_NOT_SUPPORTED")) |
| highmem_supported = false; |
| |
| report_prefix_push("high boundary"); |
| if (!env_enabled("SBI_DBCN_SKIP_HIGH_BOUNDARY")) |
| dbcn_high_write_test(DBCN_WRITE_TEST_STRING, num_bytes, |
| HIGH_ADDR_BOUNDARY - PAGE_SIZE, PAGE_SIZE - num_bytes / 2, |
| highmem_supported); |
| else |
| report_skip("user disabled"); |
| report_prefix_pop(); |
| |
| report_prefix_push("high page"); |
| if (!env_enabled("SBI_DBCN_SKIP_HIGH_PAGE")) { |
| paddr = getenv("HIGH_PAGE") ? strtoull(getenv("HIGH_PAGE"), NULL, 0) : HIGH_ADDR_BOUNDARY; |
| dbcn_high_write_test(DBCN_WRITE_TEST_STRING, num_bytes, paddr, 0, highmem_supported); |
| } else { |
| report_skip("user disabled"); |
| } |
| report_prefix_pop(); |
| |
| /* Bytes are read from memory and written to the console */ |
| report_prefix_push("invalid parameter"); |
| if (get_invalid_addr(&paddr, false)) { |
| split_phys_addr(paddr, &base_addr_hi, &base_addr_lo); |
| ret = sbi_dbcn_write(1, base_addr_lo, base_addr_hi); |
| report(ret.error == SBI_ERR_INVALID_PARAM, "address (error=%ld)", ret.error); |
| } |
| report_prefix_popn(2); |
| report_prefix_push("write_byte"); |
| |
| puts("DBCN_WRITE_BYTE TEST BYTE: "); |
| ret = sbi_dbcn_write_byte(DBCN_WRITE_BYTE_TEST_BYTE); |
| puts("\n"); |
| report(ret.error == SBI_SUCCESS, "write success (error=%ld)", ret.error); |
| report(ret.value == 0, "expected ret.value (%ld)", ret.value); |
| |
| puts("DBCN_WRITE_BYTE TEST WORD: "); /* still expect 'a' in the output */ |
| ret = sbi_ecall(SBI_EXT_DBCN, SBI_EXT_DBCN_CONSOLE_WRITE_BYTE, 0x64636261, 0, 0, 0, 0, 0); |
| puts("\n"); |
| report(ret.error == SBI_SUCCESS, "write success (error=%ld)", ret.error); |
| report(ret.value == 0, "expected ret.value (%ld)", ret.value); |
| |
| report_prefix_popn(2); |
| } |
| |
| void sbi_susp_resume(unsigned long hartid, unsigned long opaque); |
| jmp_buf sbi_susp_jmp; |
| |
| struct susp_params { |
| unsigned long sleep_type; |
| unsigned long resume_addr; |
| unsigned long opaque; |
| bool returns; |
| struct sbiret ret; |
| }; |
| |
| static bool susp_basic_prep(unsigned long ctx[], struct susp_params *params) |
| { |
| int cpu, me = smp_processor_id(); |
| struct sbiret ret; |
| cpumask_t mask; |
| |
| memset(params, 0, sizeof(*params)); |
| params->sleep_type = 0; /* suspend-to-ram */ |
| params->resume_addr = virt_to_phys(sbi_susp_resume); |
| params->opaque = virt_to_phys(ctx); |
| params->returns = false; |
| |
| cpumask_copy(&mask, &cpu_present_mask); |
| cpumask_clear_cpu(me, &mask); |
| on_cpumask_async(&mask, stop_cpu, NULL); |
| |
| /* Wait up to 1s for all harts to stop */ |
| for (int i = 0; i < 100; i++) { |
| int count = 1; |
| |
| udelay(10000); |
| |
| for_each_present_cpu(cpu) { |
| if (cpu == me) |
| continue; |
| ret = sbi_hart_get_status(cpus[cpu].hartid); |
| if (!ret.error && ret.value == SBI_EXT_HSM_STOPPED) |
| ++count; |
| } |
| if (count == cpumask_weight(&cpu_present_mask)) |
| break; |
| } |
| |
| for_each_present_cpu(cpu) { |
| ret = sbi_hart_get_status(cpus[cpu].hartid); |
| if (cpu == me) { |
| assert_msg(!ret.error && ret.value == SBI_EXT_HSM_STARTED, |
| "cpu%d is not started", cpu); |
| } else { |
| assert_msg(!ret.error && ret.value == SBI_EXT_HSM_STOPPED, |
| "cpu%d is not stopped", cpu); |
| } |
| } |
| |
| return true; |
| } |
| |
| static void susp_basic_check(unsigned long ctx[], struct susp_params *params) |
| { |
| if (ctx[SBI_SUSP_RESULTS_IDX] == SBI_SUSP_TEST_MASK) { |
| report_pass("suspend and resume"); |
| } else { |
| if (!(ctx[SBI_SUSP_RESULTS_IDX] & SBI_SUSP_TEST_SATP)) |
| report_fail("SATP set to zero on resume"); |
| if (!(ctx[SBI_SUSP_RESULTS_IDX] & SBI_SUSP_TEST_SIE)) |
| report_fail("sstatus.SIE clear on resume"); |
| if (!(ctx[SBI_SUSP_RESULTS_IDX] & SBI_SUSP_TEST_HARTID)) |
| report_fail("a0 is hartid on resume"); |
| } |
| } |
| |
| static bool susp_type_prep(unsigned long ctx[], struct susp_params *params) |
| { |
| bool r; |
| |
| r = susp_basic_prep(ctx, params); |
| assert(r); |
| params->sleep_type = 1; |
| params->returns = true; |
| params->ret.error = SBI_ERR_INVALID_PARAM; |
| |
| return true; |
| } |
| |
| static bool susp_badaddr_prep(unsigned long ctx[], struct susp_params *params) |
| { |
| phys_addr_t badaddr; |
| bool r; |
| |
| if (!get_invalid_addr(&badaddr, false)) |
| return false; |
| |
| r = susp_basic_prep(ctx, params); |
| assert(r); |
| params->resume_addr = badaddr; |
| params->returns = true; |
| params->ret.error = SBI_ERR_INVALID_ADDRESS; |
| |
| return true; |
| } |
| |
| static bool susp_one_prep(unsigned long ctx[], struct susp_params *params) |
| { |
| int started = 0, cpu, me = smp_processor_id(); |
| struct sbiret ret; |
| bool r; |
| |
| if (cpumask_weight(&cpu_present_mask) < 2) { |
| report_skip("At least 2 cpus required"); |
| return false; |
| } |
| |
| r = susp_basic_prep(ctx, params); |
| assert(r); |
| params->returns = true; |
| params->ret.error = SBI_ERR_DENIED; |
| |
| for_each_present_cpu(cpu) { |
| if (cpu == me) |
| continue; |
| break; |
| } |
| |
| on_cpu(cpu, start_cpu, NULL); |
| |
| for_each_present_cpu(cpu) { |
| ret = sbi_hart_get_status(cpus[cpu].hartid); |
| assert_msg(!ret.error, "HSM get status failed for cpu%d", cpu); |
| if (ret.value == SBI_EXT_HSM_STARTED) |
| started++; |
| } |
| |
| assert(started == 2); |
| |
| return true; |
| } |
| |
| static void check_susp(void) |
| { |
| unsigned long csrs[] = { |
| [SBI_CSR_SSTATUS_IDX] = csr_read(CSR_SSTATUS), |
| [SBI_CSR_SIE_IDX] = csr_read(CSR_SIE), |
| [SBI_CSR_STVEC_IDX] = csr_read(CSR_STVEC), |
| [SBI_CSR_SSCRATCH_IDX] = csr_read(CSR_SSCRATCH), |
| [SBI_CSR_SATP_IDX] = csr_read(CSR_SATP), |
| }; |
| unsigned long ctx[] = { |
| [SBI_SUSP_MAGIC_IDX] = SBI_SUSP_MAGIC, |
| [SBI_SUSP_CSRS_IDX] = (unsigned long)csrs, |
| [SBI_SUSP_HARTID_IDX] = current_thread_info()->hartid, |
| [SBI_SUSP_TESTNUM_IDX] = 0, |
| [SBI_SUSP_RESULTS_IDX] = 0, |
| }; |
| enum { |
| #define SUSP_FIRST_TESTNUM 1 |
| SUSP_BASIC = SUSP_FIRST_TESTNUM, |
| SUSP_TYPE, |
| SUSP_BAD_ADDR, |
| SUSP_ONE_ONLINE, |
| NR_SUSP_TESTS, |
| }; |
| struct susp_test { |
| const char *name; |
| bool (*prep)(unsigned long ctx[], struct susp_params *params); |
| void (*check)(unsigned long ctx[], struct susp_params *params); |
| } susp_tests[] = { |
| [SUSP_BASIC] = { "basic", susp_basic_prep, susp_basic_check, }, |
| [SUSP_TYPE] = { "sleep_type", susp_type_prep, }, |
| [SUSP_BAD_ADDR] = { "bad addr", susp_badaddr_prep, }, |
| [SUSP_ONE_ONLINE] = { "one cpu online", susp_one_prep, }, |
| }; |
| struct susp_params params; |
| struct sbiret ret; |
| int testnum, i; |
| |
| local_irq_disable(); |
| timer_stop(); |
| |
| report_prefix_push("susp"); |
| |
| ret = sbi_ecall(SBI_EXT_SUSP, 1, 0, 0, 0, 0, 0, 0); |
| report(ret.error == SBI_ERR_NOT_SUPPORTED, "funcid != 0 not supported"); |
| |
| for (i = SUSP_FIRST_TESTNUM; i < NR_SUSP_TESTS; i++) { |
| report_prefix_push(susp_tests[i].name); |
| |
| ctx[SBI_SUSP_TESTNUM_IDX] = i; |
| ctx[SBI_SUSP_RESULTS_IDX] = 0; |
| |
| assert(susp_tests[i].prep); |
| if (!susp_tests[i].prep(ctx, ¶ms)) { |
| report_prefix_pop(); |
| continue; |
| } |
| |
| if ((testnum = setjmp(sbi_susp_jmp)) == 0) { |
| ret = sbi_system_suspend(params.sleep_type, params.resume_addr, params.opaque); |
| |
| if (!params.returns && ret.error == SBI_ERR_NOT_SUPPORTED) { |
| report_skip("SUSP not supported?"); |
| report_prefix_popn(2); |
| return; |
| } else if (!params.returns) { |
| report_fail("unexpected return with error: %ld, value: %ld", ret.error, ret.value); |
| } else { |
| report(ret.error == params.ret.error, "expected sbi.error"); |
| if (ret.error != params.ret.error) |
| report_info("expected error %ld, received %ld", params.ret.error, ret.error); |
| } |
| |
| report_prefix_pop(); |
| continue; |
| } |
| assert(testnum == i); |
| |
| if (susp_tests[i].check) |
| susp_tests[i].check(ctx, ¶ms); |
| |
| report_prefix_pop(); |
| } |
| |
| report_prefix_pop(); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| if (argc > 1 && !strcmp(argv[1], "-h")) { |
| help(); |
| exit(0); |
| } |
| |
| report_prefix_push("sbi"); |
| check_base(); |
| check_time(); |
| check_ipi(); |
| check_hsm(); |
| check_dbcn(); |
| check_susp(); |
| |
| return report_summary(); |
| } |