Add a new selftest under hwprobe/ to verify Zicbop extension behavior.
The test checks: - That hwprobe correctly reports Zicbop presence and block size. - That prefetch instructions execute without exception on valid and NULL addresses when Zicbop is present. - That prefetch.{i,r,w} do not trigger SIGILL even when Zicbop is absent, since Zicbop instructions are defined as hints.
The test is based on cbo.c but adapted for Zicbop prefetch instructions.
Signed-off-by: Yao Zihong zihong.plct@isrc.iscas.ac.cn --- .../testing/selftests/riscv/hwprobe/Makefile | 5 +- .../selftests/riscv/hwprobe/prefetch.c | 236 ++++++++++++++++++ 2 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/riscv/hwprobe/prefetch.c
diff --git a/tools/testing/selftests/riscv/hwprobe/Makefile b/tools/testing/selftests/riscv/hwprobe/Makefile index cec81610a5f2..3c8b8ba7629c 100644 --- a/tools/testing/selftests/riscv/hwprobe/Makefile +++ b/tools/testing/selftests/riscv/hwprobe/Makefile @@ -4,7 +4,7 @@
CFLAGS += -I$(top_srcdir)/tools/include
-TEST_GEN_PROGS := hwprobe cbo which-cpus +TEST_GEN_PROGS := hwprobe cbo which-cpus prefetch
include ../../lib.mk
@@ -16,3 +16,6 @@ $(OUTPUT)/cbo: cbo.c sys_hwprobe.S
$(OUTPUT)/which-cpus: which-cpus.c sys_hwprobe.S $(CC) -static -o$@ $(CFLAGS) $(LDFLAGS) $^ + +$(OUTPUT)/prefetch: prefetch.c sys_hwprobe.S + $(CC) -static -o$@ $(CFLAGS) $(LDFLAGS) $^ diff --git a/tools/testing/selftests/riscv/hwprobe/prefetch.c b/tools/testing/selftests/riscv/hwprobe/prefetch.c new file mode 100644 index 000000000000..d9ea048325fb --- /dev/null +++ b/tools/testing/selftests/riscv/hwprobe/prefetch.c @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023 Ventana Micro Systems Inc. + * Copyright (c) 2025 PLCT Lab, ISCAS + * + * Based on tools/testing/selftests/riscv/hwprobe/cbo.c with modifications + * for Zicbop prefetch testing. + * + * Run with 'taskset -c <cpu-list> prefetch' to only execute hwprobe on a + * subset of cpus, as well as only executing the tests on those cpus. + */ +#define _GNU_SOURCE +#include <stdbool.h> +#include <stdint.h> +#include <string.h> +#include <sched.h> +#include <signal.h> +#include <assert.h> +#include <linux/compiler.h> +#include <linux/kernel.h> +#include <asm/ucontext.h> + +#include "hwprobe.h" +#include "../../kselftest.h" + +#define MK_PREFETCH(fn) \ + le32_bswap(0 << 25 | (uint32_t)(fn) << 20 | 10 << 15 | 6 << 12 | 0 << 7 | 19) + +static char mem[4096] __aligned(4096) = { [0 ... 4095] = 0xa5 }; + +static bool illegal; + +static void sigill_handler(int sig, siginfo_t *info, void *context) +{ + unsigned long *regs = (unsigned long *)&((ucontext_t *)context)->uc_mcontext; + uint32_t insn = *(uint32_t *)regs[0]; + + assert(insn == MK_PREFETCH(regs[11])); + + illegal = true; + regs[0] += 4; +} + +#define prefetch_insn(base, fn) \ +({ \ + asm volatile( \ + "mv a0, %0\n" \ + "li a1, %1\n" \ + ".4byte %2\n" \ + : : "r" (base), "i" (fn), "i" (MK_PREFETCH(fn)) : "a0", "a1", "memory");\ +}) + +static void prefetch_i(char *base) { prefetch_insn(base, 0); } + +static void prefetch_r(char *base) { prefetch_insn(base, 1); } + +static void prefetch_w(char *base) { prefetch_insn(base, 3); } + +static bool is_power_of_2(__u64 n) +{ + return n != 0 && (n & (n - 1)) == 0; +} + +static void test_no_zicbop(void *arg) +{ + // Zicbop prefetch.* are HINT instructions. + ksft_print_msg("Testing Zicbop instructions\n"); + + illegal = false; + prefetch_i(&mem[0]); + ksft_test_result(!illegal, "No prefetch.i\n"); + + illegal = false; + prefetch_r(&mem[0]); + ksft_test_result(!illegal, "No prefetch.r\n"); + + illegal = false; + prefetch_w(&mem[0]); + ksft_test_result(!illegal, "No prefetch.w\n"); +} + +static void test_zicbop(void *arg) +{ + struct riscv_hwprobe pair = { + .key = RISCV_HWPROBE_KEY_ZICBOP_BLOCK_SIZE, + }; + cpu_set_t *cpus = (cpu_set_t *)arg; + __u64 block_size; + long rc; + + rc = riscv_hwprobe(&pair, 1, sizeof(cpu_set_t), (unsigned long *)cpus, 0); + block_size = pair.value; + ksft_test_result(rc == 0 && pair.key == RISCV_HWPROBE_KEY_ZICBOP_BLOCK_SIZE && + is_power_of_2(block_size), "Zicbop block size\n"); + ksft_print_msg("Zicbop block size: %llu\n", block_size); + + illegal = false; + prefetch_i(&mem[0]); + prefetch_r(&mem[0]); + prefetch_w(&mem[0]); + ksft_test_result(!illegal, "Zicbop prefetch.* on valid address\n"); + + illegal = false; + prefetch_i(NULL); + prefetch_r(NULL); + prefetch_w(NULL); + ksft_test_result(!illegal, "Zicbop prefetch.* on NULL\n"); +} + +static void check_no_zicbop_cpus(cpu_set_t *cpus) +{ + struct riscv_hwprobe pair = { + .key = RISCV_HWPROBE_KEY_IMA_EXT_0, + }; + cpu_set_t one_cpu; + int i = 0, c = 0; + long rc; + + while (i++ < CPU_COUNT(cpus)) { + while (!CPU_ISSET(c, cpus)) + ++c; + + CPU_ZERO(&one_cpu); + CPU_SET(c, &one_cpu); + + rc = riscv_hwprobe(&pair, 1, sizeof(cpu_set_t), (unsigned long *)&one_cpu, 0); + assert(rc == 0 && pair.key == RISCV_HWPROBE_KEY_IMA_EXT_0); + + if (pair.value & RISCV_HWPROBE_EXT_ZICBOP) + ksft_exit_fail_msg("zicbop is only present on a subset of harts.\n" + "Use taskset to select a set of harts where zicbop\n" + "presence (present or not) is consistent for each hart\n"); + ++c; + } +} + +enum { + TEST_ZICBOP, + TEST_NO_ZICBOP, +}; + +enum { + HANDLER_SIGILL, + HANDLER_SIGSEGV, + HANDLER_SIGBUS, +}; + +static struct test_info { + bool enabled; + unsigned int nr_tests; + void (*test_fn)(void *arg); +} tests[] = { + [TEST_ZICBOP] = { .nr_tests = 3, test_zicbop }, + [TEST_NO_ZICBOP] = { .nr_tests = 3, test_no_zicbop }, +}; + +static struct sighandler_info { + const char *flag; + int sig; +} handlers[] = { + [HANDLER_SIGILL] = { .flag = "--sigill", .sig = SIGILL }, + [HANDLER_SIGSEGV] = { .flag = "--sigsegv", .sig = SIGSEGV }, + [HANDLER_SIGBUS] = { .flag = "--sigbus", .sig = SIGBUS }, +}; + +static bool search_flag(int argc, char **argv, const char *flag) +{ + int i; + + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], flag)) + return true; + } + return false; +} + +static void install_sigaction(int argc, char **argv) +{ + int i, rc; + struct sigaction act = { + .sa_sigaction = &sigill_handler, + .sa_flags = SA_SIGINFO, + }; + + for (i = 0; i < ARRAY_SIZE(handlers); ++i) { + if (search_flag(argc, argv, handlers[i].flag)) { + rc = sigaction(handlers[i].sig, &act, NULL); + assert(rc == 0); + } + } + + if (search_flag(argc, argv, handlers[HANDLER_SIGILL].flag)) + tests[TEST_NO_ZICBOP].enabled = true; +} + +int main(int argc, char **argv) +{ + struct riscv_hwprobe pair; + unsigned int plan = 0; + cpu_set_t cpus; + long rc; + int i; + + install_sigaction(argc, argv); + + rc = sched_getaffinity(0, sizeof(cpu_set_t), &cpus); + assert(rc == 0); + + ksft_print_header(); + + pair.key = RISCV_HWPROBE_KEY_IMA_EXT_0; + rc = riscv_hwprobe(&pair, 1, sizeof(cpu_set_t), (unsigned long *)&cpus, 0); + if (rc < 0) + ksft_exit_fail_msg("hwprobe() failed with %ld\n", rc); + assert(rc == 0 && pair.key == RISCV_HWPROBE_KEY_IMA_EXT_0); + + if (pair.value & RISCV_HWPROBE_EXT_ZICBOP) + tests[TEST_ZICBOP].enabled = true; + else + check_no_zicbop_cpus(&cpus); + + for (i = 0; i < ARRAY_SIZE(tests); ++i) + plan += tests[i].enabled ? tests[i].nr_tests : 0; + + if (plan == 0) + ksft_print_msg("No tests enabled.\n"); + else + ksft_set_plan(plan); + + for (i = 0; i < ARRAY_SIZE(tests); ++i) { + if (tests[i].enabled) + tests[i].test_fn(&cpus); + } + + ksft_finished(); +}
On Thu, Oct 09, 2025 at 09:41:54PM +0800, Yao Zihong wrote:
Add a new selftest under hwprobe/ to verify Zicbop extension behavior.
The test checks:
- That hwprobe correctly reports Zicbop presence and block size.
- That prefetch instructions execute without exception on valid and NULL addresses when Zicbop is present.
- That prefetch.{i,r,w} do not trigger SIGILL even when Zicbop is absent, since Zicbop instructions are defined as hints.
The test is based on cbo.c but adapted for Zicbop prefetch instructions.
Signed-off-by: Yao Zihong zihong.plct@isrc.iscas.ac.cn
.../testing/selftests/riscv/hwprobe/Makefile | 5 +- .../selftests/riscv/hwprobe/prefetch.c | 236 ++++++++++++++++++ 2 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/riscv/hwprobe/prefetch.c
diff --git a/tools/testing/selftests/riscv/hwprobe/Makefile b/tools/testing/selftests/riscv/hwprobe/Makefile index cec81610a5f2..3c8b8ba7629c 100644 --- a/tools/testing/selftests/riscv/hwprobe/Makefile +++ b/tools/testing/selftests/riscv/hwprobe/Makefile @@ -4,7 +4,7 @@ CFLAGS += -I$(top_srcdir)/tools/include -TEST_GEN_PROGS := hwprobe cbo which-cpus +TEST_GEN_PROGS := hwprobe cbo which-cpus prefetch include ../../lib.mk @@ -16,3 +16,6 @@ $(OUTPUT)/cbo: cbo.c sys_hwprobe.S $(OUTPUT)/which-cpus: which-cpus.c sys_hwprobe.S $(CC) -static -o$@ $(CFLAGS) $(LDFLAGS) $^
+$(OUTPUT)/prefetch: prefetch.c sys_hwprobe.S
- $(CC) -static -o$@ $(CFLAGS) $(LDFLAGS) $^
diff --git a/tools/testing/selftests/riscv/hwprobe/prefetch.c b/tools/testing/selftests/riscv/hwprobe/prefetch.c new file mode 100644 index 000000000000..d9ea048325fb --- /dev/null +++ b/tools/testing/selftests/riscv/hwprobe/prefetch.c @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: GPL-2.0-only +/*
- Copyright (c) 2023 Ventana Micro Systems Inc.
- Copyright (c) 2025 PLCT Lab, ISCAS
- Based on tools/testing/selftests/riscv/hwprobe/cbo.c with modifications
- for Zicbop prefetch testing.
- Run with 'taskset -c <cpu-list> prefetch' to only execute hwprobe on a
- subset of cpus, as well as only executing the tests on those cpus.
- */
+#define _GNU_SOURCE +#include <stdbool.h> +#include <stdint.h> +#include <string.h> +#include <sched.h> +#include <signal.h> +#include <assert.h> +#include <linux/compiler.h> +#include <linux/kernel.h> +#include <asm/ucontext.h>
+#include "hwprobe.h" +#include "../../kselftest.h"
+#define MK_PREFETCH(fn) \
- le32_bswap(0 << 25 | (uint32_t)(fn) << 20 | 10 << 15 | 6 << 12 | 0 << 7 | 19)
+static char mem[4096] __aligned(4096) = { [0 ... 4095] = 0xa5 };
+static bool illegal;
+static void sigill_handler(int sig, siginfo_t *info, void *context) +{
- unsigned long *regs = (unsigned long *)&((ucontext_t *)context)->uc_mcontext;
- uint32_t insn = *(uint32_t *)regs[0];
- assert(insn == MK_PREFETCH(regs[11]));
- illegal = true;
- regs[0] += 4;
+}
+#define prefetch_insn(base, fn) \ +({ \
- asm volatile( \
- "mv a0, %0\n" \
- "li a1, %1\n" \
- ".4byte %2\n" \
- : : "r" (base), "i" (fn), "i" (MK_PREFETCH(fn)) : "a0", "a1", "memory");\
+})
+static void prefetch_i(char *base) { prefetch_insn(base, 0); }
+static void prefetch_r(char *base) { prefetch_insn(base, 1); }
+static void prefetch_w(char *base) { prefetch_insn(base, 3); }
Please remove the unnecessary blank lines between function definitions.
+static bool is_power_of_2(__u64 n) +{
- return n != 0 && (n & (n - 1)) == 0;
+}
+static void test_no_zicbop(void *arg) +{
- // Zicbop prefetch.* are HINT instructions.
No C++ comments. Run checkpatch.
- ksft_print_msg("Testing Zicbop instructions\n");
- illegal = false;
- prefetch_i(&mem[0]);
- ksft_test_result(!illegal, "No prefetch.i\n");
- illegal = false;
- prefetch_r(&mem[0]);
- ksft_test_result(!illegal, "No prefetch.r\n");
- illegal = false;
- prefetch_w(&mem[0]);
- ksft_test_result(!illegal, "No prefetch.w\n");
+}
+static void test_zicbop(void *arg) +{
- struct riscv_hwprobe pair = {
.key = RISCV_HWPROBE_KEY_ZICBOP_BLOCK_SIZE,- };
- cpu_set_t *cpus = (cpu_set_t *)arg;
- __u64 block_size;
- long rc;
- rc = riscv_hwprobe(&pair, 1, sizeof(cpu_set_t), (unsigned long *)cpus, 0);
- block_size = pair.value;
- ksft_test_result(rc == 0 && pair.key == RISCV_HWPROBE_KEY_ZICBOP_BLOCK_SIZE &&
is_power_of_2(block_size), "Zicbop block size\n");- ksft_print_msg("Zicbop block size: %llu\n", block_size);
- illegal = false;
- prefetch_i(&mem[0]);
- prefetch_r(&mem[0]);
- prefetch_w(&mem[0]);
- ksft_test_result(!illegal, "Zicbop prefetch.* on valid address\n");
- illegal = false;
- prefetch_i(NULL);
- prefetch_r(NULL);
- prefetch_w(NULL);
- ksft_test_result(!illegal, "Zicbop prefetch.* on NULL\n");
+}
+static void check_no_zicbop_cpus(cpu_set_t *cpus) +{
- struct riscv_hwprobe pair = {
.key = RISCV_HWPROBE_KEY_IMA_EXT_0,- };
- cpu_set_t one_cpu;
- int i = 0, c = 0;
- long rc;
- while (i++ < CPU_COUNT(cpus)) {
while (!CPU_ISSET(c, cpus))++c;CPU_ZERO(&one_cpu);CPU_SET(c, &one_cpu);rc = riscv_hwprobe(&pair, 1, sizeof(cpu_set_t), (unsigned long *)&one_cpu, 0);assert(rc == 0 && pair.key == RISCV_HWPROBE_KEY_IMA_EXT_0);if (pair.value & RISCV_HWPROBE_EXT_ZICBOP)ksft_exit_fail_msg("zicbop is only present on a subset of harts.\n""Use taskset to select a set of harts where zicbop\n""presence (present or not) is consistent for each hart\n");++c;- }
+}
+enum {
- TEST_ZICBOP,
- TEST_NO_ZICBOP,
+};
+enum {
- HANDLER_SIGILL,
- HANDLER_SIGSEGV,
- HANDLER_SIGBUS,
+};
Why create this enum?
+static struct test_info {
- bool enabled;
- unsigned int nr_tests;
- void (*test_fn)(void *arg);
+} tests[] = {
- [TEST_ZICBOP] = { .nr_tests = 3, test_zicbop },
- [TEST_NO_ZICBOP] = { .nr_tests = 3, test_no_zicbop },
+};
+static struct sighandler_info {
- const char *flag;
- int sig;
+} handlers[] = {
- [HANDLER_SIGILL] = { .flag = "--sigill", .sig = SIGILL },
- [HANDLER_SIGSEGV] = { .flag = "--sigsegv", .sig = SIGSEGV },
- [HANDLER_SIGBUS] = { .flag = "--sigbus", .sig = SIGBUS },
+};
+static bool search_flag(int argc, char **argv, const char *flag) +{
- int i;
- for (i = 1; i < argc; i++) {
if (!strcmp(argv[i], flag))return true;- }
- return false;
+}
Instead of this search function just use getopt()
+static void install_sigaction(int argc, char **argv) +{
- int i, rc;
- struct sigaction act = {
.sa_sigaction = &sigill_handler,.sa_flags = SA_SIGINFO,- };
- for (i = 0; i < ARRAY_SIZE(handlers); ++i) {
if (search_flag(argc, argv, handlers[i].flag)) {rc = sigaction(handlers[i].sig, &act, NULL);assert(rc == 0);}- }
- if (search_flag(argc, argv, handlers[HANDLER_SIGILL].flag))
tests[TEST_NO_ZICBOP].enabled = true;+}
+int main(int argc, char **argv) +{
- struct riscv_hwprobe pair;
- unsigned int plan = 0;
- cpu_set_t cpus;
- long rc;
- int i;
- install_sigaction(argc, argv);
- rc = sched_getaffinity(0, sizeof(cpu_set_t), &cpus);
- assert(rc == 0);
- ksft_print_header();
- pair.key = RISCV_HWPROBE_KEY_IMA_EXT_0;
- rc = riscv_hwprobe(&pair, 1, sizeof(cpu_set_t), (unsigned long *)&cpus, 0);
- if (rc < 0)
ksft_exit_fail_msg("hwprobe() failed with %ld\n", rc);- assert(rc == 0 && pair.key == RISCV_HWPROBE_KEY_IMA_EXT_0);
- if (pair.value & RISCV_HWPROBE_EXT_ZICBOP)
tests[TEST_ZICBOP].enabled = true;- else
check_no_zicbop_cpus(&cpus);- for (i = 0; i < ARRAY_SIZE(tests); ++i)
plan += tests[i].enabled ? tests[i].nr_tests : 0;- if (plan == 0)
ksft_print_msg("No tests enabled.\n");- else
ksft_set_plan(plan);- for (i = 0; i < ARRAY_SIZE(tests); ++i) {
if (tests[i].enabled)tests[i].test_fn(&cpus);- }
- ksft_finished();
+}
2.47.2
There's no reason to duplicate cbo.c. Just parameterize check_no_zicboz_cpus() (and rename it to check_no_zicbo_cpus()) in order to share it with zicbop and then add your new tests.
drew
Thanks for the review, I’ll fix those issues in the next revision.
Also, do you think it’s worth renaming cbo.c to something more generic (like zicbo.c), or should I keep the current name for consistency?
Thanks, Zihong
On Tue, Oct 14, 2025 at 12:16:41AM +0800, Yao Zihong wrote:
Thanks for the review, I’ll fix those issues in the next revision.
Also, do you think it’s worth renaming cbo.c to something more generic (like zicbo.c), or should I keep the current name for consistency?
The cbo (cache-block operations) name represents all the cbo.* instructions and all the prefetch.* instructions, just as section 2.2 "Cache-Block Operations" of the CMO spec introduces all of them under the same heading.
Thanks, drew
linux-kselftest-mirror@lists.linaro.org