Commit a9429d5f authored by Palmer Dabbelt's avatar Palmer Dabbelt

Merge patch series "RISC-V: Enable cbo.zero in usermode"

Andrew Jones <ajones@ventanamicro.com> says:

In order for usermode to issue cbo.zero, it needs privilege granted to
issue the extension instruction (patch 2) and to know that the extension
is available and its block size (patch 3). Patch 1 could be separate from
this series (it just fixes up some error messages), patches 4-5 convert
the hwprobe selftest to a statically-linked, TAP test and patch 6 adds a
new hwprobe test for the new information as well as testing CBO
instructions can or cannot be issued as appropriate.

* b4-shazam-merge:
  RISC-V: selftests: Add CBO tests
  RISC-V: selftests: Convert hwprobe test to kselftest API
  RISC-V: selftests: Statically link hwprobe test
  RISC-V: hwprobe: Expose Zicboz extension and its block size
  RISC-V: Enable cbo.zero in usermode
  RISC-V: Make zicbom/zicboz errors consistent

Link: https://lore.kernel.org/r/20230918131518.56803-8-ajones@ventanamicro.comSigned-off-by: default avatarPalmer Dabbelt <palmer@rivosinc.com>
parents 71e11d06 a29e2a48
......@@ -77,6 +77,9 @@ The following keys are defined:
* :c:macro:`RISCV_HWPROBE_EXT_ZBS`: The Zbs extension is supported, as defined
in version 1.0 of the Bit-Manipulation ISA extensions.
* :c:macro:`RISCV_HWPROBE_EXT_ZICBOZ`: The Zicboz extension is supported, as
ratified in commit 3dd606f ("Create cmobase-v1.0.pdf") of riscv-CMOs.
* :c:macro:`RISCV_HWPROBE_KEY_CPUPERF_0`: A bitmask that contains performance
information about the selected set of processors.
......@@ -96,3 +99,6 @@ The following keys are defined:
* :c:macro:`RISCV_HWPROBE_MISALIGNED_UNSUPPORTED`: Misaligned accesses are
not supported at all and will generate a misaligned address fault.
* :c:macro:`RISCV_HWPROBE_KEY_ZICBOZ_BLOCK_SIZE`: An unsigned int which
represents the size of the Zicboz block in bytes.
......@@ -31,5 +31,6 @@ DECLARE_PER_CPU(long, misaligned_access_speed);
extern struct riscv_isainfo hart_isa[NR_CPUS];
void check_unaligned_access(int cpu);
void riscv_user_isa_enable(void);
#endif
......@@ -275,6 +275,7 @@
#define CSR_SIE 0x104
#define CSR_STVEC 0x105
#define CSR_SCOUNTEREN 0x106
#define CSR_SENVCFG 0x10a
#define CSR_SSCRATCH 0x140
#define CSR_SEPC 0x141
#define CSR_SCAUSE 0x142
......
......@@ -70,6 +70,7 @@
#ifndef __ASSEMBLY__
#include <linux/jump_label.h>
#include <asm/cpufeature.h>
unsigned long riscv_get_elf_hwcap(void);
......@@ -137,6 +138,21 @@ riscv_has_extension_unlikely(const unsigned long ext)
return true;
}
static __always_inline bool riscv_cpu_has_extension_likely(int cpu, const unsigned long ext)
{
if (IS_ENABLED(CONFIG_RISCV_ALTERNATIVE) && riscv_has_extension_likely(ext))
return true;
return __riscv_isa_extension_available(hart_isa[cpu].isa, ext);
}
static __always_inline bool riscv_cpu_has_extension_unlikely(int cpu, const unsigned long ext)
{
if (IS_ENABLED(CONFIG_RISCV_ALTERNATIVE) && riscv_has_extension_unlikely(ext))
return true;
return __riscv_isa_extension_available(hart_isa[cpu].isa, ext);
}
#endif
#endif /* _ASM_RISCV_HWCAP_H */
......@@ -8,6 +8,6 @@
#include <uapi/asm/hwprobe.h>
#define RISCV_HWPROBE_MAX_KEY 5
#define RISCV_HWPROBE_MAX_KEY 6
#endif
......@@ -29,6 +29,7 @@ struct riscv_hwprobe {
#define RISCV_HWPROBE_EXT_ZBA (1 << 3)
#define RISCV_HWPROBE_EXT_ZBB (1 << 4)
#define RISCV_HWPROBE_EXT_ZBS (1 << 5)
#define RISCV_HWPROBE_EXT_ZICBOZ (1 << 6)
#define RISCV_HWPROBE_KEY_CPUPERF_0 5
#define RISCV_HWPROBE_MISALIGNED_UNKNOWN (0 << 0)
#define RISCV_HWPROBE_MISALIGNED_EMULATED (1 << 0)
......@@ -36,6 +37,7 @@ struct riscv_hwprobe {
#define RISCV_HWPROBE_MISALIGNED_FAST (3 << 0)
#define RISCV_HWPROBE_MISALIGNED_UNSUPPORTED (4 << 0)
#define RISCV_HWPROBE_MISALIGNED_MASK (7 << 0)
#define RISCV_HWPROBE_KEY_ZICBOZ_BLOCK_SIZE 6
/* Increase RISCV_HWPROBE_MAX_KEY when adding items. */
#endif
......@@ -93,10 +93,10 @@ static bool riscv_isa_extension_check(int id)
return true;
case RISCV_ISA_EXT_ZICBOZ:
if (!riscv_cboz_block_size) {
pr_err("Zicboz detected in ISA string, but no cboz-block-size found\n");
pr_err("Zicboz detected in ISA string, disabling as no cboz-block-size found\n");
return false;
} else if (!is_power_of_2(riscv_cboz_block_size)) {
pr_err("cboz-block-size present, but is not a power-of-2\n");
pr_err("Zicboz disabled as cboz-block-size present, but is not a power-of-2\n");
return false;
}
return true;
......@@ -653,6 +653,12 @@ static int check_unaligned_access_boot_cpu(void)
arch_initcall(check_unaligned_access_boot_cpu);
void riscv_user_isa_enable(void)
{
if (riscv_cpu_has_extension_unlikely(smp_processor_id(), RISCV_ISA_EXT_ZICBOZ))
csr_set(CSR_SENVCFG, ENVCFG_CBZE);
}
#ifdef CONFIG_RISCV_ALTERNATIVE
/*
* Alternative patch sites consider 48 bits when determining when to patch
......
......@@ -26,6 +26,7 @@
#include <asm/acpi.h>
#include <asm/alternative.h>
#include <asm/cacheflush.h>
#include <asm/cpufeature.h>
#include <asm/cpu_ops.h>
#include <asm/early_ioremap.h>
#include <asm/pgtable.h>
......@@ -314,10 +315,13 @@ void __init setup_arch(char **cmdline_p)
riscv_fill_hwcap();
init_rt_signal_env();
apply_boot_alternatives();
if (IS_ENABLED(CONFIG_RISCV_ISA_ZICBOM) &&
riscv_isa_extension_available(NULL, ZICBOM))
riscv_noncoherent_supported();
riscv_set_dma_cache_alignment();
riscv_user_isa_enable();
}
static int __init topology_init(void)
......
......@@ -25,6 +25,8 @@
#include <linux/of.h>
#include <linux/sched/task_stack.h>
#include <linux/sched/mm.h>
#include <asm/cpufeature.h>
#include <asm/cpu_ops.h>
#include <asm/cpufeature.h>
#include <asm/irq.h>
......@@ -253,6 +255,8 @@ asmlinkage __visible void smp_callin(void)
elf_hwcap &= ~COMPAT_HWCAP_ISA_V;
}
riscv_user_isa_enable();
/*
* Remote TLB flushes are ignored while the CPU is offline, so emit
* a local TLB flush right now just in case.
......
......@@ -145,26 +145,38 @@ static void hwprobe_isa_ext0(struct riscv_hwprobe *pair,
for_each_cpu(cpu, cpus) {
struct riscv_isainfo *isainfo = &hart_isa[cpu];
if (riscv_isa_extension_available(isainfo->isa, ZBA))
pair->value |= RISCV_HWPROBE_EXT_ZBA;
else
missing |= RISCV_HWPROBE_EXT_ZBA;
if (riscv_isa_extension_available(isainfo->isa, ZBB))
pair->value |= RISCV_HWPROBE_EXT_ZBB;
else
missing |= RISCV_HWPROBE_EXT_ZBB;
if (riscv_isa_extension_available(isainfo->isa, ZBS))
pair->value |= RISCV_HWPROBE_EXT_ZBS;
else
missing |= RISCV_HWPROBE_EXT_ZBS;
#define EXT_KEY(ext) \
do { \
if (__riscv_isa_extension_available(isainfo->isa, RISCV_ISA_EXT_##ext)) \
pair->value |= RISCV_HWPROBE_EXT_##ext; \
else \
missing |= RISCV_HWPROBE_EXT_##ext; \
} while (false)
/*
* Only use EXT_KEY() for extensions which can be exposed to userspace,
* regardless of the kernel's configuration, as no other checks, besides
* presence in the hart_isa bitmap, are made.
*/
EXT_KEY(ZBA);
EXT_KEY(ZBB);
EXT_KEY(ZBS);
EXT_KEY(ZICBOZ);
#undef EXT_KEY
}
/* Now turn off reporting features if any CPU is missing it. */
pair->value &= ~missing;
}
static bool hwprobe_ext0_has(const struct cpumask *cpus, unsigned long ext)
{
struct riscv_hwprobe pair;
hwprobe_isa_ext0(&pair, cpus);
return (pair.value & ext);
}
static u64 hwprobe_misaligned(const struct cpumask *cpus)
{
int cpu;
......@@ -215,6 +227,12 @@ static void hwprobe_one_pair(struct riscv_hwprobe *pair,
pair->value = hwprobe_misaligned(cpus);
break;
case RISCV_HWPROBE_KEY_ZICBOZ_BLOCK_SIZE:
pair->value = 0;
if (hwprobe_ext0_has(cpus, RISCV_HWPROBE_EXT_ZICBOZ))
pair->value = riscv_cboz_block_size;
break;
/*
* For forward compatibility, unknown keys don't fail the whole
* call, but get their element key set to -1 and value set to 0
......
......@@ -2,9 +2,14 @@
# Copyright (C) 2021 ARM Limited
# Originally tools/testing/arm64/abi/Makefile
TEST_GEN_PROGS := hwprobe
CFLAGS += -I$(top_srcdir)/tools/include
TEST_GEN_PROGS := hwprobe cbo
include ../../lib.mk
$(OUTPUT)/hwprobe: hwprobe.c sys_hwprobe.S
$(CC) -o$@ $(CFLAGS) $(LDFLAGS) $^
$(CC) -static -o$@ $(CFLAGS) $(LDFLAGS) $^
$(OUTPUT)/cbo: cbo.c sys_hwprobe.S
$(CC) -static -o$@ $(CFLAGS) $(LDFLAGS) $^
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2023 Ventana Micro Systems Inc.
*
* Run with 'taskset -c <cpu-list> cbo' 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_CBO(fn) cpu_to_le32((fn) << 20 | 10 << 15 | 2 << 12 | 0 << 7 | 15)
static char mem[4096] __aligned(4096) = { [0 ... 4095] = 0xa5 };
static bool illegal_insn;
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_CBO(regs[11]));
illegal_insn = true;
regs[0] += 4;
}
static void cbo_insn(char *base, int fn)
{
uint32_t insn = MK_CBO(fn);
asm volatile(
"mv a0, %0\n"
"li a1, %1\n"
".4byte %2\n"
: : "r" (base), "i" (fn), "i" (insn) : "a0", "a1", "memory");
}
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 test_no_zicbom(void *arg)
{
ksft_print_msg("Testing Zicbom instructions remain privileged\n");
illegal_insn = false;
cbo_clean(&mem[0]);
ksft_test_result(illegal_insn, "No cbo.clean\n");
illegal_insn = false;
cbo_flush(&mem[0]);
ksft_test_result(illegal_insn, "No cbo.flush\n");
illegal_insn = false;
cbo_inval(&mem[0]);
ksft_test_result(illegal_insn, "No cbo.inval\n");
}
static void test_no_zicboz(void *arg)
{
ksft_print_msg("No Zicboz, testing cbo.zero remains privileged\n");
illegal_insn = false;
cbo_zero(&mem[0]);
ksft_test_result(illegal_insn, "No cbo.zero\n");
}
static bool is_power_of_2(__u64 n)
{
return n != 0 && (n & (n - 1)) == 0;
}
static void test_zicboz(void *arg)
{
struct riscv_hwprobe pair = {
.key = RISCV_HWPROBE_KEY_ZICBOZ_BLOCK_SIZE,
};
cpu_set_t *cpus = (cpu_set_t *)arg;
__u64 block_size;
int i, j;
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_ZICBOZ_BLOCK_SIZE &&
is_power_of_2(block_size), "Zicboz block size\n");
ksft_print_msg("Zicboz block size: %ld\n", block_size);
illegal_insn = false;
cbo_zero(&mem[block_size]);
ksft_test_result(!illegal_insn, "cbo.zero\n");
if (illegal_insn || !is_power_of_2(block_size)) {
ksft_test_result_skip("cbo.zero check\n");
return;
}
assert(block_size <= 1024);
for (i = 0; i < 4096 / block_size; ++i) {
if (i % 2)
cbo_zero(&mem[i * block_size]);
}
for (i = 0; i < 4096 / block_size; ++i) {
char expected = i % 2 ? 0x0 : 0xa5;
for (j = 0; j < block_size; ++j) {
if (mem[i * block_size + j] != expected) {
ksft_test_result_fail("cbo.zero check\n");
ksft_print_msg("cbo.zero check: mem[%d] != 0x%x\n",
i * block_size + j, expected);
return;
}
}
}
ksft_test_result_pass("cbo.zero check\n");
}
static void check_no_zicboz_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_ZICBOZ)
ksft_exit_fail_msg("Zicboz is only present on a subset of harts.\n"
"Use taskset to select a set of harts where Zicboz\n"
"presence (present or not) is consistent for each hart\n");
++c;
}
}
enum {
TEST_ZICBOZ,
TEST_NO_ZICBOZ,
TEST_NO_ZICBOM,
};
static struct test_info {
bool enabled;
unsigned int nr_tests;
void (*test_fn)(void *arg);
} tests[] = {
[TEST_ZICBOZ] = { .nr_tests = 3, test_zicboz },
[TEST_NO_ZICBOZ] = { .nr_tests = 1, test_no_zicboz },
[TEST_NO_ZICBOM] = { .nr_tests = 3, test_no_zicbom },
};
int main(int argc, char **argv)
{
struct sigaction act = {
.sa_sigaction = &sigill_handler,
.sa_flags = SA_SIGINFO,
};
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;
}
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 %d\n", rc);
assert(rc == 0 && pair.key == RISCV_HWPROBE_KEY_IMA_EXT_0);
if (pair.value & RISCV_HWPROBE_EXT_ZICBOZ) {
tests[TEST_ZICBOZ].enabled = true;
tests[TEST_NO_ZICBOZ].enabled = false;
} else {
check_no_zicboz_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();
}
// SPDX-License-Identifier: GPL-2.0-only
#include <stddef.h>
#include <asm/hwprobe.h>
/*
* Rather than relying on having a new enough libc to define this, just do it
* ourselves. This way we don't need to be coupled to a new-enough libc to
* contain the call.
*/
long riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count,
size_t cpu_count, unsigned long *cpus, unsigned int flags);
#include "hwprobe.h"
#include "../../kselftest.h"
int main(int argc, char **argv)
{
......@@ -16,6 +8,9 @@ int main(int argc, char **argv)
unsigned long cpus;
long out;
ksft_print_header();
ksft_set_plan(5);
/* Fake the CPU_SET ops. */
cpus = -1;
......@@ -25,13 +20,16 @@ int main(int argc, char **argv)
*/
for (long i = 0; i < 8; i++)
pairs[i].key = i;
out = riscv_hwprobe(pairs, 8, 1, &cpus, 0);
if (out != 0)
return -1;
ksft_exit_fail_msg("hwprobe() failed with %ld\n", out);
for (long i = 0; i < 4; ++i) {
/* Fail if the kernel claims not to recognize a base key. */
if ((i < 4) && (pairs[i].key != i))
return -2;
ksft_exit_fail_msg("Failed to recognize base key: key != i, "
"key=%ld, i=%ld\n", pairs[i].key, i);
if (pairs[i].key != RISCV_HWPROBE_KEY_BASE_BEHAVIOR)
continue;
......@@ -39,52 +37,30 @@ int main(int argc, char **argv)
if (pairs[i].value & RISCV_HWPROBE_BASE_BEHAVIOR_IMA)
continue;
return -3;
ksft_exit_fail_msg("Unexpected pair: (%ld, %ld)\n", pairs[i].key, pairs[i].value);
}
/*
* This should also work with a NULL CPU set, but should not work
* with an improperly supplied CPU set.
*/
out = riscv_hwprobe(pairs, 8, 0, 0, 0);
if (out != 0)
return -4;
ksft_test_result(out == 0, "NULL CPU set\n");
out = riscv_hwprobe(pairs, 8, 0, &cpus, 0);
if (out == 0)
return -5;
ksft_test_result(out != 0, "Bad CPU set\n");
out = riscv_hwprobe(pairs, 8, 1, 0, 0);
if (out == 0)
return -6;
ksft_test_result(out != 0, "NULL CPU set with non-zero count\n");
/*
* Check that keys work by providing one that we know exists, and
* checking to make sure the resultig pair is what we asked for.
*/
pairs[0].key = RISCV_HWPROBE_KEY_BASE_BEHAVIOR;
out = riscv_hwprobe(pairs, 1, 1, &cpus, 0);
if (out != 0)
return -7;
if (pairs[0].key != RISCV_HWPROBE_KEY_BASE_BEHAVIOR)
return -8;
ksft_test_result(out == 0 && pairs[0].key == RISCV_HWPROBE_KEY_BASE_BEHAVIOR,
"Existing key is maintained\n");
/*
* Check that an unknown key gets overwritten with -1,
* but doesn't block elements after it.
*/
pairs[0].key = 0x5555;
pairs[1].key = 1;
pairs[1].value = 0xAAAA;
out = riscv_hwprobe(pairs, 2, 0, 0, 0);
if (out != 0)
return -9;
if (pairs[0].key != -1)
return -10;
if ((pairs[1].key != 1) || (pairs[1].value == 0xAAAA))
return -11;
ksft_test_result(out == 0 && pairs[0].key == -1 &&
pairs[1].key == 1 && pairs[1].value != 0xAAAA,
"Unknown key overwritten with -1 and doesn't block other elements\n");
return 0;
ksft_finished();
}
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef SELFTEST_RISCV_HWPROBE_H
#define SELFTEST_RISCV_HWPROBE_H
#include <stddef.h>
#include <asm/hwprobe.h>
/*
* Rather than relying on having a new enough libc to define this, just do it
* ourselves. This way we don't need to be coupled to a new-enough libc to
* contain the call.
*/
long riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count,
size_t cpu_count, unsigned long *cpus, unsigned int flags);
#endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment