Merge branch 'riscv/sbi' into 'master'
riscv: Added SBI tests for IPI, SUSP, and HSM
See merge request kvm-unit-tests/kvm-unit-tests!70
diff --git a/lib/cpumask.h b/lib/cpumask.h
index 37d3607..b4cd83a 100644
--- a/lib/cpumask.h
+++ b/lib/cpumask.h
@@ -72,6 +72,19 @@
return !lastmask || !((cpumask_bits(src1)[i] & ~cpumask_bits(src2)[i]) & lastmask);
}
+static inline bool cpumask_equal(const struct cpumask *src1, const struct cpumask *src2)
+{
+ unsigned long lastmask = BIT_MASK(nr_cpus) - 1;
+ int i;
+
+ for (i = 0; i < BIT_WORD(nr_cpus); ++i) {
+ if (cpumask_bits(src1)[i] != cpumask_bits(src2)[i])
+ return false;
+ }
+
+ return !lastmask || (cpumask_bits(src1)[i] & lastmask) == (cpumask_bits(src2)[i] & lastmask);
+}
+
static inline bool cpumask_empty(const cpumask_t *mask)
{
unsigned long lastmask = BIT_MASK(nr_cpus) - 1;
diff --git a/lib/riscv/asm/asm.h b/lib/riscv/asm/asm.h
index 763b28e..107b5bb 100644
--- a/lib/riscv/asm/asm.h
+++ b/lib/riscv/asm/asm.h
@@ -14,6 +14,9 @@
#define REG_S __REG_SEL(sd, sw)
#define SZREG __REG_SEL(8, 4)
+/* ASMARR() may be used with arrays of longs */
+#define ASMARR(reg, idx) ((idx) * SZREG)(reg)
+
#define FP_SIZE 16
#endif /* _ASMRISCV_ASM_H_ */
diff --git a/lib/riscv/asm/sbi.h b/lib/riscv/asm/sbi.h
index 1319439..98a9b09 100644
--- a/lib/riscv/asm/sbi.h
+++ b/lib/riscv/asm/sbi.h
@@ -22,6 +22,7 @@
SBI_EXT_HSM = 0x48534d,
SBI_EXT_SRST = 0x53525354,
SBI_EXT_DBCN = 0x4442434E,
+ SBI_EXT_SUSP = 0x53555350,
};
enum sbi_ext_base_fid {
@@ -87,6 +88,7 @@
struct sbiret sbi_send_ipi(unsigned long hart_mask, unsigned long hart_mask_base);
struct sbiret sbi_send_ipi_cpu(int cpu);
struct sbiret sbi_send_ipi_cpumask(const cpumask_t *mask);
+struct sbiret sbi_send_ipi_broadcast(void);
struct sbiret sbi_set_timer(unsigned long stime_value);
long sbi_probe(int ext);
diff --git a/lib/riscv/sbi.c b/lib/riscv/sbi.c
index 8972e76..02dd338 100644
--- a/lib/riscv/sbi.c
+++ b/lib/riscv/sbi.c
@@ -62,13 +62,18 @@
return sbi_send_ipi(1UL, cpus[cpu].hartid);
}
+struct sbiret sbi_send_ipi_broadcast(void)
+{
+ return sbi_send_ipi(0, -1UL);
+}
+
struct sbiret sbi_send_ipi_cpumask(const cpumask_t *mask)
{
struct sbiret ret;
cpumask_t tmp;
if (cpumask_full(mask))
- return sbi_send_ipi(0, -1UL);
+ return sbi_send_ipi_broadcast();
cpumask_copy(&tmp, mask);
@@ -107,7 +112,7 @@
struct sbiret ret;
ret = sbi_ecall(SBI_EXT_BASE, SBI_EXT_BASE_GET_SPEC_VERSION, 0, 0, 0, 0, 0, 0);
- assert(!ret.error && ret.value >= 2);
+ assert(!ret.error && (ret.value & 0x7ffffffful) >= 2);
ret = sbi_ecall(SBI_EXT_BASE, SBI_EXT_BASE_PROBE_EXT, ext, 0, 0, 0, 0, 0);
assert(!ret.error);
diff --git a/lib/riscv/setjmp.S b/lib/riscv/setjmp.S
new file mode 100644
index 0000000..38b0f1c
--- /dev/null
+++ b/lib/riscv/setjmp.S
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#define __ASSEMBLY__
+#include <asm/asm.h>
+
+.section .text
+
+.balign 4
+.global setjmp
+setjmp:
+ REG_S ra, (0 * SZREG)(a0)
+ REG_S s0, (1 * SZREG)(a0)
+ REG_S s1, (2 * SZREG)(a0)
+ REG_S s2, (3 * SZREG)(a0)
+ REG_S s3, (4 * SZREG)(a0)
+ REG_S s4, (5 * SZREG)(a0)
+ REG_S s5, (6 * SZREG)(a0)
+ REG_S s6, (7 * SZREG)(a0)
+ REG_S s7, (8 * SZREG)(a0)
+ REG_S s8, (9 * SZREG)(a0)
+ REG_S s9, (10 * SZREG)(a0)
+ REG_S s10, (11 * SZREG)(a0)
+ REG_S s11, (12 * SZREG)(a0)
+ REG_S sp, (13 * SZREG)(a0)
+ REG_S gp, (14 * SZREG)(a0)
+ REG_S tp, (15 * SZREG)(a0)
+ li a0, 0
+ ret
+
+.balign 4
+.global longjmp
+longjmp:
+ REG_L ra, (0 * SZREG)(a0)
+ REG_L s0, (1 * SZREG)(a0)
+ REG_L s1, (2 * SZREG)(a0)
+ REG_L s2, (3 * SZREG)(a0)
+ REG_L s3, (4 * SZREG)(a0)
+ REG_L s4, (5 * SZREG)(a0)
+ REG_L s5, (6 * SZREG)(a0)
+ REG_L s6, (7 * SZREG)(a0)
+ REG_L s7, (8 * SZREG)(a0)
+ REG_L s8, (9 * SZREG)(a0)
+ REG_L s9, (10 * SZREG)(a0)
+ REG_L s10, (11 * SZREG)(a0)
+ REG_L s11, (12 * SZREG)(a0)
+ REG_L sp, (13 * SZREG)(a0)
+ REG_L gp, (14 * SZREG)(a0)
+ REG_L tp, (15 * SZREG)(a0)
+ seqz a0, a1
+ add a0, a0, a1
+ ret
diff --git a/lib/setjmp.h b/lib/setjmp.h
index 6afdf66..f878ad8 100644
--- a/lib/setjmp.h
+++ b/lib/setjmp.h
@@ -8,7 +8,11 @@
#define _LIBCFLAT_SETJMP_H_
typedef struct jmp_buf_tag {
+#if defined(__i386__) || defined(__x86_64__)
long int regs[8];
+#elif defined(__riscv)
+ long int regs[16];
+#endif
} jmp_buf[1];
extern int setjmp (struct jmp_buf_tag env[1]);
diff --git a/riscv/Makefile b/riscv/Makefile
index 22fd273..28b0415 100644
--- a/riscv/Makefile
+++ b/riscv/Makefile
@@ -36,6 +36,7 @@
cflatobjs += lib/riscv/mmu.o
cflatobjs += lib/riscv/processor.o
cflatobjs += lib/riscv/sbi.o
+cflatobjs += lib/riscv/setjmp.o
cflatobjs += lib/riscv/setup.o
cflatobjs += lib/riscv/smp.o
cflatobjs += lib/riscv/stack.o
@@ -43,6 +44,7 @@
ifeq ($(ARCH),riscv32)
cflatobjs += lib/ldiv32.o
endif
+cflatobjs += riscv/sbi-asm.o
########################################
@@ -82,7 +84,7 @@
CFLAGS += -std=gnu99
CFLAGS += -ffreestanding
CFLAGS += -O2
-CFLAGS += -I $(SRCDIR)/lib -I $(SRCDIR)/lib/libfdt -I lib
+CFLAGS += -I $(SRCDIR)/lib -I $(SRCDIR)/lib/libfdt -I lib -I $(SRCDIR)/riscv
asm-offsets = lib/riscv/asm-offsets.h
include $(SRCDIR)/scripts/asm-offsets.mak
diff --git a/riscv/sbi-asm.S b/riscv/sbi-asm.S
new file mode 100644
index 0000000..923c2ce
--- /dev/null
+++ b/riscv/sbi-asm.S
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Helper assembly code routines for RISC-V SBI extension tests.
+ *
+ * Copyright (C) 2024, James Raphael Tiovalen <jamestiotio@gmail.com>
+ */
+#define __ASSEMBLY__
+#include <asm/asm.h>
+#include <asm/csr.h>
+
+#include "sbi-tests.h"
+
+.section .text
+
+/*
+ * sbi_hsm_check
+ * a0 and a1 are set by SBI HSM start/suspend
+ * s1 is the address of the results array
+ * Doesn't return.
+ *
+ * This function is only called from HSM start and on resumption
+ * from HSM suspend which means we can do whatever we like with
+ * all registers. So, to avoid complicated register agreements with
+ * other assembly functions called, we just always use the saved
+ * registers for anything that should be maintained across calls.
+ */
+#define HSM_RESULTS_ARRAY s1
+#define HSM_RESULTS_MAP s2
+#define HSM_CPU_INDEX s3
+.balign 4
+sbi_hsm_check:
+ li HSM_RESULTS_MAP, 0
+ REG_L t0, ASMARR(a1, SBI_HSM_MAGIC_IDX)
+ li t1, SBI_HSM_MAGIC
+ bne t0, t1, 1f
+ ori HSM_RESULTS_MAP, HSM_RESULTS_MAP, SBI_HSM_TEST_MAGIC_A1
+1: REG_L t0, ASMARR(a1, SBI_HSM_HARTID_IDX)
+ bne a0, t0, 2f
+ ori HSM_RESULTS_MAP, HSM_RESULTS_MAP, SBI_HSM_TEST_HARTID_A0
+2: csrr t0, CSR_SATP
+ bnez t0, 3f
+ ori HSM_RESULTS_MAP, HSM_RESULTS_MAP, SBI_HSM_TEST_SATP
+3: csrr t0, CSR_SSTATUS
+ andi t0, t0, SR_SIE
+ bnez t0, 4f
+ ori HSM_RESULTS_MAP, HSM_RESULTS_MAP, SBI_HSM_TEST_SIE
+4: call hartid_to_cpu
+ mv HSM_CPU_INDEX, a0
+ li t0, -1
+ bne HSM_CPU_INDEX, t0, 6f
+5: pause
+ j 5b
+6: ori HSM_RESULTS_MAP, HSM_RESULTS_MAP, SBI_HSM_TEST_DONE
+ add t0, HSM_RESULTS_ARRAY, HSM_CPU_INDEX
+ sb HSM_RESULTS_MAP, 0(t0)
+ la t1, sbi_hsm_stop_hart
+ add t1, t1, HSM_CPU_INDEX
+7: lb t0, 0(t1)
+ pause
+ beqz t0, 7b
+ li a7, 0x48534d /* SBI_EXT_HSM */
+ li a6, 1 /* SBI_EXT_HSM_HART_STOP */
+ ecall
+8: pause
+ j 8b
+
+.balign 4
+.global sbi_hsm_check_hart_start
+sbi_hsm_check_hart_start:
+ la HSM_RESULTS_ARRAY, sbi_hsm_hart_start_checks
+ j sbi_hsm_check
+
+.balign 4
+.global sbi_hsm_check_non_retentive_suspend
+sbi_hsm_check_non_retentive_suspend:
+ la HSM_RESULTS_ARRAY, sbi_hsm_non_retentive_hart_suspend_checks
+ j sbi_hsm_check
+
+.balign 4
+restore_csrs:
+ REG_L a1, ASMARR(a0, SBI_CSR_SSTATUS_IDX)
+ csrw CSR_SSTATUS, a1
+ REG_L a1, ASMARR(a0, SBI_CSR_SIE_IDX)
+ csrw CSR_SIE, a1
+ REG_L a1, ASMARR(a0, SBI_CSR_STVEC_IDX)
+ csrw CSR_STVEC, a1
+ REG_L a1, ASMARR(a0, SBI_CSR_SSCRATCH_IDX)
+ csrw CSR_SSCRATCH, a1
+ REG_L a1, ASMARR(a0, SBI_CSR_SATP_IDX)
+ sfence.vma
+ csrw CSR_SATP, a1
+ ret
+
+/*
+ * sbi_susp_resume
+ *
+ * State is as specified by "SUSP System Resume Register State" of the SBI spec
+ * a0 is the hartid
+ * a1 is the opaque parameter (here, it's the context array defined in check_susp())
+ * Doesn't return.
+ */
+#define SUSP_CTX s1
+#define SUSP_RESULTS_MAP s2
+.balign 4
+.global sbi_susp_resume
+sbi_susp_resume:
+ li SUSP_RESULTS_MAP, 0
+ mv SUSP_CTX, a1
+ REG_L t0, ASMARR(SUSP_CTX, SBI_SUSP_MAGIC_IDX)
+ li t1, SBI_SUSP_MAGIC
+ beq t0, t1, 2f
+1: pause
+ j 1b
+2: csrr t0, CSR_SATP
+ bnez t0, 3f
+ ori SUSP_RESULTS_MAP, SUSP_RESULTS_MAP, SBI_SUSP_TEST_SATP
+3: csrr t0, CSR_SSTATUS
+ andi t0, t0, SR_SIE
+ bnez t0, 4f
+ ori SUSP_RESULTS_MAP, SUSP_RESULTS_MAP, SBI_SUSP_TEST_SIE
+4: REG_L t0, ASMARR(SUSP_CTX, SBI_SUSP_HARTID_IDX)
+ bne t0, a0, 5f
+ ori SUSP_RESULTS_MAP, SUSP_RESULTS_MAP, SBI_SUSP_TEST_HARTID
+5: REG_S SUSP_RESULTS_MAP, ASMARR(SUSP_CTX, SBI_SUSP_RESULTS_IDX)
+ REG_L a0, ASMARR(SUSP_CTX, SBI_SUSP_CSRS_IDX)
+ call restore_csrs
+ la a0, sbi_susp_jmp
+ REG_L a1, ASMARR(SUSP_CTX, SBI_SUSP_TESTNUM_IDX)
+ call longjmp
+6: pause /* unreachable */
+ j 6b
diff --git a/riscv/sbi-tests.h b/riscv/sbi-tests.h
new file mode 100644
index 0000000..ce12996
--- /dev/null
+++ b/riscv/sbi-tests.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _RISCV_SBI_TESTS_H_
+#define _RISCV_SBI_TESTS_H_
+
+#define SBI_HSM_TEST_DONE (1 << 0)
+#define SBI_HSM_TEST_MAGIC_A1 (1 << 1)
+#define SBI_HSM_TEST_HARTID_A0 (1 << 2)
+#define SBI_HSM_TEST_SATP (1 << 3)
+#define SBI_HSM_TEST_SIE (1 << 4)
+
+#define SBI_HSM_MAGIC 0x453
+
+#define SBI_HSM_MAGIC_IDX 0
+#define SBI_HSM_HARTID_IDX 1
+#define SBI_HSM_NUM_OF_PARAMS 2
+
+#define SBI_SUSP_MAGIC_IDX 0
+#define SBI_SUSP_CSRS_IDX 1
+#define SBI_SUSP_HARTID_IDX 2
+#define SBI_SUSP_TESTNUM_IDX 3
+#define SBI_SUSP_RESULTS_IDX 4
+
+#define SBI_CSR_SSTATUS_IDX 0
+#define SBI_CSR_SIE_IDX 1
+#define SBI_CSR_STVEC_IDX 2
+#define SBI_CSR_SSCRATCH_IDX 3
+#define SBI_CSR_SATP_IDX 4
+
+#define SBI_SUSP_MAGIC 0x505b
+
+#define SBI_SUSP_TEST_SATP (1 << 0)
+#define SBI_SUSP_TEST_SIE (1 << 1)
+#define SBI_SUSP_TEST_HARTID (1 << 2)
+#define SBI_SUSP_TEST_MASK 7
+
+#endif /* _RISCV_SBI_TESTS_H_ */
diff --git a/riscv/sbi.c b/riscv/sbi.c
index 52434e0..6f4ddaf 100644
--- a/riscv/sbi.c
+++ b/riscv/sbi.c
@@ -6,23 +6,37 @@
*/
#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 <limits.h>
#include <vmalloc.h>
-#include <memregions.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");
@@ -46,6 +60,42 @@
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;
@@ -73,6 +123,13 @@
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)) {
@@ -83,6 +140,22 @@
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)
{
@@ -105,18 +178,23 @@
report_prefix_push("base");
ret = sbi_base(SBI_EXT_BASE_GET_SPEC_VERSION, 0);
- if (ret.error || ret.value < 2) {
- report_skip("SBI spec version 0.2 or higher required");
- return;
- }
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);
@@ -281,6 +359,735 @@
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')
@@ -334,11 +1141,9 @@
{
unsigned long num_bytes = strlen(DBCN_WRITE_TEST_STRING);
unsigned long base_addr_lo, base_addr_hi;
- bool do_invalid_addr = false;
bool highmem_supported = true;
phys_addr_t paddr;
struct sbiret ret;
- const char *tmp;
char *buf;
report_prefix_push("dbcn");
@@ -361,13 +1166,11 @@
dbcn_write_test(&buf[PAGE_SIZE - num_bytes / 2], num_bytes, false);
report_prefix_pop();
- tmp = getenv("SBI_HIGHMEM_NOT_SUPPORTED");
- if (tmp && atol(tmp) != 0)
+ if (env_enabled("SBI_HIGHMEM_NOT_SUPPORTED"))
highmem_supported = false;
report_prefix_push("high boundary");
- tmp = getenv("SBI_DBCN_SKIP_HIGH_BOUNDARY");
- if (!tmp || atol(tmp) == 0)
+ 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);
@@ -376,12 +1179,8 @@
report_prefix_pop();
report_prefix_push("high page");
- tmp = getenv("SBI_DBCN_SKIP_HIGH_PAGE");
- if (!tmp || atol(tmp) == 0) {
- paddr = HIGH_ADDR_BOUNDARY;
- tmp = getenv("HIGH_PAGE");
- if (tmp)
- paddr = strtoull(tmp, NULL, 0);
+ 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");
@@ -390,16 +1189,7 @@
/* Bytes are read from memory and written to the console */
report_prefix_push("invalid parameter");
- tmp = getenv("INVALID_ADDR_AUTO");
- if (tmp && atol(tmp) == 1) {
- paddr = get_highest_addr() + 1;
- do_invalid_addr = true;
- } else if (env_or_skip("INVALID_ADDR")) {
- paddr = strtoull(getenv("INVALID_ADDR"), NULL, 0);
- do_invalid_addr = true;
- }
-
- if (do_invalid_addr) {
+ 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);
@@ -422,6 +1212,231 @@
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")) {
@@ -432,7 +1447,10 @@
report_prefix_push("sbi");
check_base();
check_time();
+ check_ipi();
+ check_hsm();
check_dbcn();
+ check_susp();
return report_summary();
}
diff --git a/riscv/unittests.cfg b/riscv/unittests.cfg
index cbd36bf..2eb760e 100644
--- a/riscv/unittests.cfg
+++ b/riscv/unittests.cfg
@@ -16,4 +16,5 @@
# Set $FIRMWARE_OVERRIDE to /path/to/firmware to select the SBI implementation.
[sbi]
file = sbi.flat
+smp = $MAX_SMP
groups = sbi