On Tue, Nov 18, 2025 at 12:32:40AM +0800, Yao Zihong wrote:
> Add selftests to cbo.c to verify Zicbop extension behavior, and split
> the previous `--sigill` mode into two options so they can be tested
> independently.
> 
> 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.
> 
> Signed-off-by: Yao Zihong <[email protected]>
> ---
>  tools/testing/selftests/riscv/hwprobe/cbo.c | 168 ++++++++++++++++----
>  1 file changed, 139 insertions(+), 29 deletions(-)
> 
> diff --git a/tools/testing/selftests/riscv/hwprobe/cbo.c 
> b/tools/testing/selftests/riscv/hwprobe/cbo.c
> index 5e96ef785d0d..281ab440e696 100644
> --- a/tools/testing/selftests/riscv/hwprobe/cbo.c
> +++ b/tools/testing/selftests/riscv/hwprobe/cbo.c
> @@ -15,24 +15,31 @@
>  #include <linux/compiler.h>
>  #include <linux/kernel.h>
>  #include <asm/ucontext.h>
> +#include <getopt.h>
>  
>  #include "hwprobe.h"
>  #include "../../kselftest.h"
>  
>  #define MK_CBO(fn) le32_bswap((uint32_t)(fn) << 20 | 10 << 15 | 2 << 12 | 0 
> << 7 | 15)
> +#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_insn;
> +static bool got_fault;
>  
> -static void sigill_handler(int sig, siginfo_t *info, void *context)
> +static void fault_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_CBO(regs[11]));
> +     if (sig == SIGILL)
> +             assert(insn == MK_CBO(regs[11]));
>  
> -     illegal_insn = true;
> +     if (sig == SIGSEGV || sig == SIGBUS)
> +             assert(insn == MK_PREFETCH(regs[11]));
> +
> +     got_fault = true;
>       regs[0] += 4;
>  }
>  
> @@ -45,39 +52,51 @@ static void sigill_handler(int sig, siginfo_t *info, void 
> *context)
>       : : "r" (base), "i" (fn), "i" (MK_CBO(fn)) : "a0", "a1", "memory");     
> \
>  })
>  
> +#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");          
> \
> +})
> +
>  static void cbo_inval(char *base) { cbo_insn(base, 0); }
>  static void cbo_clean(char *base) { cbo_insn(base, 1); }
>  static void cbo_flush(char *base) { cbo_insn(base, 2); }
>  static void cbo_zero(char *base)  { cbo_insn(base, 4); }
> +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 void test_no_cbo_inval(void *arg)
>  {
>       ksft_print_msg("Testing cbo.inval instruction remain privileged\n");
> -     illegal_insn = false;
> +     got_fault = false;
>       cbo_inval(&mem[0]);
> -     ksft_test_result(illegal_insn, "No cbo.inval\n");
> +     ksft_test_result(got_fault, "No cbo.inval\n");
>  }
>  
>  static void test_no_zicbom(void *arg)
>  {
>       ksft_print_msg("Testing Zicbom instructions remain privileged\n");
>  
> -     illegal_insn = false;
> +     got_fault = false;
>       cbo_clean(&mem[0]);
> -     ksft_test_result(illegal_insn, "No cbo.clean\n");
> +     ksft_test_result(got_fault, "No cbo.clean\n");
>  
> -     illegal_insn = false;
> +     got_fault = false;
>       cbo_flush(&mem[0]);
> -     ksft_test_result(illegal_insn, "No cbo.flush\n");
> +     ksft_test_result(got_fault, "No cbo.flush\n");
>  }
>  
>  static void test_no_zicboz(void *arg)
>  {
>       ksft_print_msg("No Zicboz, testing cbo.zero remains privileged\n");
>  
> -     illegal_insn = false;
> +     got_fault = false;
>       cbo_zero(&mem[0]);
> -     ksft_test_result(illegal_insn, "No cbo.zero\n");
> +     ksft_test_result(got_fault, "No cbo.zero\n");
>  }
>  
>  static bool is_power_of_2(__u64 n)
> @@ -85,6 +104,54 @@ static bool is_power_of_2(__u64 n)
>       return n != 0 && (n & (n - 1)) == 0;
>  }
>  
> +static void test_zicbop(void *arg)
> +{
> +     struct riscv_hwprobe pair = {
> +             .key = RISCV_HWPROBE_KEY_ZICBOP_BLOCK_SIZE,
> +     };
> +
> +     struct sigaction act = {
> +             .sa_sigaction = &fault_handler,
> +             .sa_flags = SA_SIGINFO
> +     };
> +
> +     struct sigaction dfl = {
> +             .sa_handler = SIG_DFL
> +     };
> +
> +     cpu_set_t *cpus = (cpu_set_t *)arg;
> +     __u64 block_size;
> +     long rc;
> +
> +     rc = sigaction(SIGSEGV, &act, NULL);
> +     assert(rc == 0);
> +     rc = sigaction(SIGBUS, &act, NULL);
> +     assert(rc == 0);
> +
> +     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);
> +
> +     got_fault = false;
> +     prefetch_i(&mem[0]);
> +     prefetch_r(&mem[0]);
> +     prefetch_w(&mem[0]);
> +     ksft_test_result(!got_fault, "Zicbop prefetch.* on valid address\n");
> +
> +     got_fault = false;
> +     prefetch_i(NULL);
> +     prefetch_r(NULL);
> +     prefetch_w(NULL);
> +     ksft_test_result(!got_fault, "Zicbop prefetch.* on NULL\n");
> +
> +     rc = sigaction(SIGBUS, &dfl, NULL);
> +     assert(rc == 0);
> +     rc = sigaction(SIGSEGV, &dfl, NULL);
> +     assert(rc == 0);
> +}
> +
>  static void test_zicbom(void *arg)
>  {
>       struct riscv_hwprobe pair = {
> @@ -100,13 +167,13 @@ static void test_zicbom(void *arg)
>                        is_power_of_2(block_size), "Zicbom block size\n");
>       ksft_print_msg("Zicbom block size: %llu\n", block_size);
>  
> -     illegal_insn = false;
> +     got_fault = false;
>       cbo_clean(&mem[block_size]);
> -     ksft_test_result(!illegal_insn, "cbo.clean\n");
> +     ksft_test_result(!got_fault, "cbo.clean\n");
>  
> -     illegal_insn = false;
> +     got_fault = false;
>       cbo_flush(&mem[block_size]);
> -     ksft_test_result(!illegal_insn, "cbo.flush\n");
> +     ksft_test_result(!got_fault, "cbo.flush\n");
>  }
>  
>  static void test_zicboz(void *arg)
> @@ -125,11 +192,11 @@ static void test_zicboz(void *arg)
>                        is_power_of_2(block_size), "Zicboz block size\n");
>       ksft_print_msg("Zicboz block size: %llu\n", block_size);
>  
> -     illegal_insn = false;
> +     got_fault = false;
>       cbo_zero(&mem[block_size]);
> -     ksft_test_result(!illegal_insn, "cbo.zero\n");
> +     ksft_test_result(!got_fault, "cbo.zero\n");
>  
> -     if (illegal_insn || !is_power_of_2(block_size)) {
> +     if (got_fault || !is_power_of_2(block_size)) {
>               ksft_test_result_skip("cbo.zero check\n");
>               return;
>       }
> @@ -177,7 +244,19 @@ static void check_no_zicbo_cpus(cpu_set_t *cpus, __u64 
> cbo)
>               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);
>  
> -             cbostr = cbo == RISCV_HWPROBE_EXT_ZICBOZ ? "Zicboz" : "Zicbom";
> +             switch (cbo) {
> +             case RISCV_HWPROBE_EXT_ZICBOZ:
> +                     cbostr = "Zicboz";
> +                     break;
> +             case RISCV_HWPROBE_EXT_ZICBOM:
> +                     cbostr = "Zicbom";
> +                     break;
> +             case RISCV_HWPROBE_EXT_ZICBOP:
> +                     cbostr = "Zicbop";
> +                     break;
> +             default:
> +                     ksft_exit_fail_msg("Internal error: invalid cbo 
> %llu\n", cbo);
> +             }
>  
>               if (pair.value & cbo)
>                       ksft_exit_fail_msg("%s is only present on a subset of 
> harts.\n"
> @@ -194,6 +273,7 @@ enum {
>       TEST_ZICBOM,
>       TEST_NO_ZICBOM,
>       TEST_NO_CBO_INVAL,
> +     TEST_ZICBOP

nit: It's best to add the comma now so if we add another test later we
won't need to change this line.

>  };
>  
>  static struct test_info {
> @@ -206,26 +286,51 @@ static struct test_info {
>       [TEST_ZICBOM]           = { .nr_tests = 3, test_zicbom },
>       [TEST_NO_ZICBOM]        = { .nr_tests = 2, test_no_zicbom },
>       [TEST_NO_CBO_INVAL]     = { .nr_tests = 1, test_no_cbo_inval },
> +     [TEST_ZICBOP]           = { .nr_tests = 3, test_zicbop }
> +};
> +
> +static const struct option long_opts[] = {
> +     {"zicbom-raises-sigill", no_argument, 0, 'm'},
> +     {"zicboz-raises-sigill", no_argument, 0, 'z'},
> +     {0, 0, 0, 0}
>  };
>  
>  int main(int argc, char **argv)
>  {
>       struct sigaction act = {
> -             .sa_sigaction = &sigill_handler,
> +             .sa_sigaction = &fault_handler,
>               .sa_flags = SA_SIGINFO,
>       };
> +

nit: Don't put blank lines between variables (same comment for all
the blanks between the structs above in test_zicbop())

>       struct riscv_hwprobe pair;
>       unsigned int plan = 0;
>       cpu_set_t cpus;
>       long rc;
> -     int i;
> -
> -     if (argc > 1 && !strcmp(argv[1], "--sigill")) {
> -             rc = sigaction(SIGILL, &act, NULL);
> -             assert(rc == 0);
> -             tests[TEST_NO_ZICBOZ].enabled = true;
> -             tests[TEST_NO_ZICBOM].enabled = true;
> -             tests[TEST_NO_CBO_INVAL].enabled = true;
> +     int i, opt, long_index;
> +
> +     long_index = 0;
> +
> +     while ((opt = getopt_long(argc, argv, "mz", long_opts, &long_index)) != 
> -1) {
> +             switch (opt) {
> +             case 'm':
> +                     tests[TEST_NO_ZICBOM].enabled = true;
> +                     tests[TEST_NO_CBO_INVAL].enabled = true;
> +                     rc = sigaction(SIGILL, &act, NULL);
> +                     assert(rc == 0);
> +                     break;
> +             case 'z':
> +                     tests[TEST_NO_ZICBOZ].enabled = true;

Should continue enabling TEST_NO_CBO_INVAL for this too. Since 'm' and 'z'
are almost the same then it should be possible to merge the cases somehow.

> +                     rc = sigaction(SIGILL, &act, NULL);
> +                     assert(rc == 0);
> +                     break;
> +             case '?':
> +                     fprintf(stderr,
> +                             "Usage: %s [--zicbom-raises-sigill|-m] 
> [--zicboz-raises-sigill|-z]\n",
> +                             argv[0]);
> +                     exit(1);
> +             default:
> +                     break;
> +             }
>       }
>  
>       rc = sched_getaffinity(0, sizeof(cpu_set_t), &cpus);
> @@ -253,6 +358,11 @@ int main(int argc, char **argv)
>               check_no_zicbo_cpus(&cpus, RISCV_HWPROBE_EXT_ZICBOM);
>       }
>  
> +     if (pair.value & RISCV_HWPROBE_EXT_ZICBOP)
> +             tests[TEST_ZICBOP].enabled = true;
> +     else
> +             check_no_zicbo_cpus(&cpus, RISCV_HWPROBE_EXT_ZICBOP);
> +
>       for (i = 0; i < ARRAY_SIZE(tests); ++i)
>               plan += tests[i].enabled ? tests[i].nr_tests : 0;
>  
> -- 
> 2.47.2
>

Thanks,
drew

Reply via email to