* [PATCH 1/2] riscv: hwprobe: export the availability of vector to user
2026-07-23 22:20 [PATCH 0/2] riscv: hwprobe: report per-process extension availability Andy Chiu
@ 2026-07-23 22:20 ` Andy Chiu
2026-07-24 12:53 ` Florian Weimer
2026-07-23 22:20 ` [PATCH 2/2] selftests: riscv: hwprobe: test the RISCV_HWPROBE_KEY_EXT_ENABLED modifier Andy Chiu
1 sibling, 1 reply; 4+ messages in thread
From: Andy Chiu @ 2026-07-23 22:20 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, linux-doc, linux-riscv
Cc: bergner, kito.cheng, Andy Chiu, dfustini, greentime.hu,
Andrew Jones, Nutty Liu, Pincheng Wang, Yao Zihong, Xu Lu,
Aleksa Paunovic, Jingwei Wang, Zong Li, Deepak Gupta,
Clément Léger
Userland IFUNC resolvers use hwprobe to decide whether to dispatch to
vectorized routines. But RISCV_HWPROBE_KEY_IMA_EXT_0 only reports what
is present in hardware, not what the calling process may actually use:
when Vector is disabled for a process via
prctl(PR_RISCV_V_SET_CONTROL, PR_RISCV_V_VSTATE_CTRL_OFF), it is still
reported as present. A resolver that trusts this and runs a vector
instruction crashes with SIGILL.
Add RISCV_HWPROBE_KEY_EXT_ENABLED, a positional modifier key that carries
no value of its own. Within a single request, keys placed after it report
extensions that are both present and enabled for the calling process,
while keys before it keep reporting hardware presence. This masks out V
and its V-dependent sub-extensions when V is disabled for the process, and
lets userland obtain both views in one query:
[ {IMA_EXT_0}, {EXT_ENABLED}, {IMA_EXT_0} ]
present modifier enabled
The enabled view depends on per-process state, so it cannot be served from
the vDSO's process-independent cache; requests carrying the modifier are
deferred to the syscall. Unknown keys are still reported as -1, so the
feature is detectable and existing users are unaffected.
Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220795
Link: https://lore.kernel.org/all/20260107000609.63892-1-andybnac@gmail.com/
Signed-off-by: Andy Chiu <tchiu@tenstorrent.com>
---
Documentation/arch/riscv/hwprobe.rst | 27 +++++++++++++++
arch/riscv/include/asm/hwprobe.h | 2 +-
.../asm/vendor_extensions/thead_hwprobe.h | 6 ++--
arch/riscv/include/uapi/asm/hwprobe.h | 2 ++
arch/riscv/kernel/sys_hwprobe.c | 34 +++++++++++++------
arch/riscv/kernel/vdso/hwprobe.c | 25 ++++++++++----
.../kernel/vendor_extensions/thead_hwprobe.c | 9 +++--
7 files changed, 84 insertions(+), 21 deletions(-)
diff --git a/Documentation/arch/riscv/hwprobe.rst b/Documentation/arch/riscv/hwprobe.rst
index c420a8349bc6..b58b7c734c30 100644
--- a/Documentation/arch/riscv/hwprobe.rst
+++ b/Documentation/arch/riscv/hwprobe.rst
@@ -391,3 +391,30 @@ The following keys are defined:
* :c:macro:`RISCV_HWPROBE_KEY_IMA_EXT_1`: A bitmask containing additional
extensions that are compatible with the
:c:macro:`RISCV_HWPROBE_BASE_BEHAVIOR_IMA`: base system behavior.
+
+* :c:macro:`RISCV_HWPROBE_KEY_EXT_ENABLED`: A modifier key. It reports no
+ value of its own (its value is always 0) and instead changes how the
+ extension-bitmask keys that follow it in the same request are reported.
+ Keys placed before it report the extensions that are present in hardware,
+ as usual. Keys placed after it additionally require each reported extension
+ to be enabled for the calling process, i.e. usable without receiving a
+ SIGILL.
+
+ Currently this applies to Vector. When V has been disabled for the process
+ with ``prctl(PR_RISCV_V_SET_CONTROL, PR_RISCV_V_VSTATE_CTRL_OFF)``,
+ :c:macro:`RISCV_HWPROBE_IMA_V` and the V-dependent sub-extensions are
+ cleared from any :c:macro:`RISCV_HWPROBE_KEY_IMA_EXT_0` that follows the
+ modifier, and :c:macro:`RISCV_HWPROBE_VENDOR_EXT_XTHEADVECTOR` is cleared
+ from any :c:macro:`RISCV_HWPROBE_KEY_VENDOR_EXT_THEAD_0` that follows it,
+ while a preceding key still reports them as present. A single request can
+ therefore return both the hardware-present set and the process-usable
+ set::
+
+ struct riscv_hwprobe pairs[3] = {
+ { .key = RISCV_HWPROBE_KEY_IMA_EXT_0, }, /* present in hardware */
+ { .key = RISCV_HWPROBE_KEY_EXT_ENABLED, }, /* modifier */
+ { .key = RISCV_HWPROBE_KEY_IMA_EXT_0, }, /* present and enabled */
+ };
+
+ The effect is positional: reordering the pairs changes which keys have the
+ enablement filter applied.
diff --git a/arch/riscv/include/asm/hwprobe.h b/arch/riscv/include/asm/hwprobe.h
index 8b9f5e1cf4cb..e8364a3eaeef 100644
--- a/arch/riscv/include/asm/hwprobe.h
+++ b/arch/riscv/include/asm/hwprobe.h
@@ -8,7 +8,7 @@
#include <uapi/asm/hwprobe.h>
-#define RISCV_HWPROBE_MAX_KEY 16
+#define RISCV_HWPROBE_MAX_KEY 17
static inline bool riscv_hwprobe_key_is_valid(__s64 key)
{
diff --git a/arch/riscv/include/asm/vendor_extensions/thead_hwprobe.h b/arch/riscv/include/asm/vendor_extensions/thead_hwprobe.h
index 65a9c5612466..3e1c4271383f 100644
--- a/arch/riscv/include/asm/vendor_extensions/thead_hwprobe.h
+++ b/arch/riscv/include/asm/vendor_extensions/thead_hwprobe.h
@@ -7,10 +7,12 @@
#include <uapi/asm/hwprobe.h>
#ifdef CONFIG_RISCV_ISA_VENDOR_EXT_THEAD
-void hwprobe_isa_vendor_ext_thead_0(struct riscv_hwprobe *pair, const struct cpumask *cpus);
+void hwprobe_isa_vendor_ext_thead_0(struct riscv_hwprobe *pair,
+ const struct cpumask *cpus, bool test_avail);
#else
static inline void hwprobe_isa_vendor_ext_thead_0(struct riscv_hwprobe *pair,
- const struct cpumask *cpus)
+ const struct cpumask *cpus,
+ bool test_avail)
{
pair->value = 0;
}
diff --git a/arch/riscv/include/uapi/asm/hwprobe.h b/arch/riscv/include/uapi/asm/hwprobe.h
index 9139edba0aec..561483172ce5 100644
--- a/arch/riscv/include/uapi/asm/hwprobe.h
+++ b/arch/riscv/include/uapi/asm/hwprobe.h
@@ -116,6 +116,8 @@ struct riscv_hwprobe {
#define RISCV_HWPROBE_KEY_ZICBOP_BLOCK_SIZE 15
#define RISCV_HWPROBE_KEY_IMA_EXT_1 16
#define RISCV_HWPROBE_EXT_ZICFISS (1ULL << 0)
+/* Modifier key working as a signal to the kernel to gather enablement status */
+#define RISCV_HWPROBE_KEY_EXT_ENABLED 17
/* Increase RISCV_HWPROBE_MAX_KEY when adding items. */
diff --git a/arch/riscv/kernel/sys_hwprobe.c b/arch/riscv/kernel/sys_hwprobe.c
index 1659d31fd288..64d598d1a05d 100644
--- a/arch/riscv/kernel/sys_hwprobe.c
+++ b/arch/riscv/kernel/sys_hwprobe.c
@@ -79,8 +79,9 @@ static void hwprobe_arch_id(struct riscv_hwprobe *pair,
}
static void hwprobe_isa_ext0(struct riscv_hwprobe *pair,
- const struct cpumask *cpus)
+ const struct cpumask *cpus, bool report_avail)
{
+ bool report_v;
int cpu;
u64 missing = 0;
@@ -91,7 +92,8 @@ static void hwprobe_isa_ext0(struct riscv_hwprobe *pair,
if (riscv_isa_extension_available(NULL, c))
pair->value |= RISCV_HWPROBE_IMA_C;
- if (has_vector() && riscv_isa_extension_available(NULL, v))
+ report_v = report_avail ? riscv_v_vstate_ctrl_user_allowed() : true;
+ if (has_vector() && riscv_isa_extension_available(NULL, v) && report_v)
pair->value |= RISCV_HWPROBE_IMA_V;
/*
@@ -146,7 +148,7 @@ static void hwprobe_isa_ext0(struct riscv_hwprobe *pair,
* All the following extensions must depend on the kernel
* support of V.
*/
- if (has_vector()) {
+ if (has_vector() && report_v) {
EXT_KEY(isainfo->isa, ZVBB, pair->value, missing);
EXT_KEY(isainfo->isa, ZVBC, pair->value, missing);
EXT_KEY(isainfo->isa, ZVE32F, pair->value, missing);
@@ -215,7 +217,7 @@ static bool hwprobe_ext0_has(const struct cpumask *cpus, u64 ext)
{
struct riscv_hwprobe pair;
- hwprobe_isa_ext0(&pair, cpus);
+ hwprobe_isa_ext0(&pair, cpus, false);
return (pair.value & ext);
}
@@ -293,7 +295,7 @@ static u64 hwprobe_vec_misaligned(const struct cpumask *cpus)
#endif
static void hwprobe_one_pair(struct riscv_hwprobe *pair,
- const struct cpumask *cpus)
+ const struct cpumask *cpus, bool test_avail)
{
switch (pair->key) {
case RISCV_HWPROBE_KEY_MVENDORID:
@@ -312,7 +314,7 @@ static void hwprobe_one_pair(struct riscv_hwprobe *pair,
break;
case RISCV_HWPROBE_KEY_IMA_EXT_0:
- hwprobe_isa_ext0(pair, cpus);
+ hwprobe_isa_ext0(pair, cpus, test_avail);
break;
case RISCV_HWPROBE_KEY_IMA_EXT_1:
@@ -356,7 +358,7 @@ static void hwprobe_one_pair(struct riscv_hwprobe *pair,
break;
case RISCV_HWPROBE_KEY_VENDOR_EXT_THEAD_0:
- hwprobe_isa_vendor_ext_thead_0(pair, cpus);
+ hwprobe_isa_vendor_ext_thead_0(pair, cpus, test_avail);
break;
case RISCV_HWPROBE_KEY_VENDOR_EXT_MIPS_0:
hwprobe_isa_vendor_ext_mips_0(pair, cpus);
@@ -379,6 +381,7 @@ static int hwprobe_get_values(struct riscv_hwprobe __user *pairs,
unsigned long __user *cpus_user,
unsigned int flags)
{
+ bool test_avail = false;
size_t out;
int ret;
cpumask_t cpus;
@@ -419,7 +422,10 @@ static int hwprobe_get_values(struct riscv_hwprobe __user *pairs,
return -EFAULT;
pair.value = 0;
- hwprobe_one_pair(&pair, &cpus);
+ if (pair.key == RISCV_HWPROBE_KEY_EXT_ENABLED)
+ test_avail = true;
+ else
+ hwprobe_one_pair(&pair, &cpus, test_avail);
ret = put_user(pair.key, &pairs->key);
if (ret == 0)
ret = put_user(pair.value, &pairs->value);
@@ -438,6 +444,7 @@ static int hwprobe_get_cpus(struct riscv_hwprobe __user *pairs,
{
cpumask_t cpus, one_cpu;
bool clear_all = false;
+ bool test_avail = false;
size_t i;
int ret;
@@ -476,6 +483,10 @@ static int hwprobe_get_cpus(struct riscv_hwprobe __user *pairs,
if (ret)
return -EFAULT;
}
+ if (pair.key == RISCV_HWPROBE_KEY_EXT_ENABLED) {
+ test_avail = true;
+ continue;
+ }
if (clear_all)
continue;
@@ -485,7 +496,7 @@ static int hwprobe_get_cpus(struct riscv_hwprobe __user *pairs,
for_each_cpu(cpu, &cpus) {
cpumask_set_cpu(cpu, &one_cpu);
- hwprobe_one_pair(&tmp, &one_cpu);
+ hwprobe_one_pair(&tmp, &one_cpu, test_avail);
if (!riscv_hwprobe_pair_cmp(&tmp, &pair))
cpumask_clear_cpu(cpu, &cpus);
@@ -535,8 +546,11 @@ static int complete_hwprobe_vdso_data(void)
* save a syscall in the common case.
*/
for (key = 0; key <= RISCV_HWPROBE_MAX_KEY; key++) {
+ if (key == RISCV_HWPROBE_KEY_EXT_ENABLED)
+ continue;
+
pair.key = key;
- hwprobe_one_pair(&pair, cpu_online_mask);
+ hwprobe_one_pair(&pair, cpu_online_mask, false);
WARN_ON_ONCE(pair.key < 0);
diff --git a/arch/riscv/kernel/vdso/hwprobe.c b/arch/riscv/kernel/vdso/hwprobe.c
index 8f45500d0a6e..f40c21e44ef9 100644
--- a/arch/riscv/kernel/vdso/hwprobe.c
+++ b/arch/riscv/kernel/vdso/hwprobe.c
@@ -14,7 +14,7 @@ extern int riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count,
static int riscv_vdso_get_values(struct riscv_hwprobe *pairs, size_t pair_count,
size_t cpusetsize, unsigned long *cpus,
- unsigned int flags)
+ unsigned int flags, bool avail_test)
{
const struct vdso_arch_data *avd = &vdso_u_arch_data;
bool all_cpus = !cpusetsize && !cpus;
@@ -27,7 +27,8 @@ static int riscv_vdso_get_values(struct riscv_hwprobe *pairs, size_t pair_count,
* homogeneous, then this function can handle requests for arbitrary
* masks.
*/
- if (flags != 0 || (!all_cpus && !avd->homogeneous_cpus) || unlikely(!avd->ready))
+ if (flags != 0 || (!all_cpus && !avd->homogeneous_cpus) ||
+ unlikely(!avd->ready) || avail_test)
return riscv_hwprobe(pairs, pair_count, cpusetsize, cpus, flags);
/* This is something we can handle, fill out the pairs. */
@@ -48,7 +49,7 @@ static int riscv_vdso_get_values(struct riscv_hwprobe *pairs, size_t pair_count,
static int riscv_vdso_get_cpus(struct riscv_hwprobe *pairs, size_t pair_count,
size_t cpusetsize, unsigned long *cpus,
- unsigned int flags)
+ unsigned int flags, bool avail_test)
{
const struct vdso_arch_data *avd = &vdso_u_arch_data;
struct riscv_hwprobe *p = pairs;
@@ -68,7 +69,8 @@ static int riscv_vdso_get_cpus(struct riscv_hwprobe *pairs, size_t pair_count,
}
}
- if (empty_cpus || flags != RISCV_HWPROBE_WHICH_CPUS || !avd->homogeneous_cpus)
+ if (empty_cpus || flags != RISCV_HWPROBE_WHICH_CPUS ||
+ !avd->homogeneous_cpus || avail_test)
return riscv_hwprobe(pairs, pair_count, cpusetsize, cpus, flags);
while (p < end) {
@@ -105,10 +107,21 @@ int __vdso_riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count,
size_t cpusetsize, unsigned long *cpus,
unsigned int flags)
{
+ struct riscv_hwprobe *p = pairs;
+ bool avail_test = false;
+ size_t i;
+
+ for (i = 0; i < pair_count; i++) {
+ if (p[i].key == RISCV_HWPROBE_KEY_EXT_ENABLED) {
+ avail_test = true;
+ break;
+ }
+ }
+
if (flags & RISCV_HWPROBE_WHICH_CPUS)
return riscv_vdso_get_cpus(pairs, pair_count, cpusetsize,
- cpus, flags);
+ cpus, flags, avail_test);
return riscv_vdso_get_values(pairs, pair_count, cpusetsize,
- cpus, flags);
+ cpus, flags, avail_test);
}
diff --git a/arch/riscv/kernel/vendor_extensions/thead_hwprobe.c b/arch/riscv/kernel/vendor_extensions/thead_hwprobe.c
index 2eba34011786..28c58a7123d5 100644
--- a/arch/riscv/kernel/vendor_extensions/thead_hwprobe.c
+++ b/arch/riscv/kernel/vendor_extensions/thead_hwprobe.c
@@ -6,14 +6,19 @@
#include <linux/cpumask.h>
#include <linux/types.h>
+#include <asm/vector.h>
#include <uapi/asm/hwprobe.h>
#include <uapi/asm/vendor/thead.h>
-void hwprobe_isa_vendor_ext_thead_0(struct riscv_hwprobe *pair, const struct cpumask *cpus)
+void hwprobe_isa_vendor_ext_thead_0(struct riscv_hwprobe *pair, const struct cpumask *cpus,
+ bool test_avail)
{
+ bool report_v = test_avail ? riscv_v_vstate_ctrl_user_allowed() : true;
+
VENDOR_EXTENSION_SUPPORTED(pair, cpus,
riscv_isa_vendor_ext_list_thead.per_hart_isa_bitmap, {
- VENDOR_EXT_KEY(XTHEADVECTOR);
+ if (report_v)
+ VENDOR_EXT_KEY(XTHEADVECTOR);
});
}
--
2.43.0
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
^ permalink raw reply related [flat|nested] 4+ messages in thread* [PATCH 2/2] selftests: riscv: hwprobe: test the RISCV_HWPROBE_KEY_EXT_ENABLED modifier
2026-07-23 22:20 [PATCH 0/2] riscv: hwprobe: report per-process extension availability Andy Chiu
2026-07-23 22:20 ` [PATCH 1/2] riscv: hwprobe: export the availability of vector to user Andy Chiu
@ 2026-07-23 22:20 ` Andy Chiu
1 sibling, 0 replies; 4+ messages in thread
From: Andy Chiu @ 2026-07-23 22:20 UTC (permalink / raw)
To: Shuah Khan, Paul Walmsley, Palmer Dabbelt, Albert Ou,
Alexandre Ghiti, linux-kselftest, linux-riscv
Cc: bergner, kito.cheng, Andy Chiu, dfustini, greentime.hu, Zong Li
Add a selftest for the RISCV_HWPROBE_KEY_EXT_ENABLED modifier key, which
reports the extensions that are not only present in hardware but also
enabled for the calling process.
The modifier is positional within a single hwprobe request: keys placed
before it report what is present in hardware, keys placed after it report
what is present and enabled. A single query therefore returns both views:
[ {IMA_EXT_0}, {EXT_ENABLED}, {IMA_EXT_0} ]
present modifier enabled
The test uses this to check that Vector is masked out of the enabled view
once V is disabled for the process, while remaining in the present view.
The disabled-V check depends on user space being allowed to run vector
instructions by default (/proc/sys/abi/riscv_v_default_allow == 1). The
checks that require the modifier or V in hardware are skipped when either
is unavailable.
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Andy Chiu <tchiu@tenstorrent.com>
---
.../selftests/riscv/hwprobe/.gitignore | 2 +
.../testing/selftests/riscv/hwprobe/Makefile | 14 ++-
.../selftests/riscv/hwprobe/ext-enabled.c | 111 ++++++++++++++++++
.../riscv/hwprobe/ext-enabled_nolibc.c | 58 +++++++++
4 files changed, 181 insertions(+), 4 deletions(-)
create mode 100644 tools/testing/selftests/riscv/hwprobe/ext-enabled.c
create mode 100644 tools/testing/selftests/riscv/hwprobe/ext-enabled_nolibc.c
diff --git a/tools/testing/selftests/riscv/hwprobe/.gitignore b/tools/testing/selftests/riscv/hwprobe/.gitignore
index 6e384e80ea1a..ceb4bf86340c 100644
--- a/tools/testing/selftests/riscv/hwprobe/.gitignore
+++ b/tools/testing/selftests/riscv/hwprobe/.gitignore
@@ -1,3 +1,5 @@
hwprobe
cbo
which-cpus
+ext-enabled
+ext-enabled_nolibc
diff --git a/tools/testing/selftests/riscv/hwprobe/Makefile b/tools/testing/selftests/riscv/hwprobe/Makefile
index 71e3f26c541b..55be82758830 100644
--- a/tools/testing/selftests/riscv/hwprobe/Makefile
+++ b/tools/testing/selftests/riscv/hwprobe/Makefile
@@ -2,9 +2,8 @@
# Copyright (C) 2021 ARM Limited
# Originally tools/testing/arm64/abi/Makefile
-CFLAGS += -I$(top_srcdir)/tools/include
-
-TEST_GEN_PROGS := hwprobe cbo which-cpus
+TEST_GEN_PROGS := hwprobe cbo which-cpus ext-enabled
+TEST_GEN_PROGS_EXTENDED := ext-enabled_nolibc
include ../../lib.mk
@@ -12,7 +11,14 @@ $(OUTPUT)/hwprobe: hwprobe.c sys_hwprobe.S
$(CC) -static -o $@ $(CFLAGS) $(LDFLAGS) $^
$(OUTPUT)/cbo: cbo.c sys_hwprobe.S
- $(CC) -static -o $@ $(CFLAGS) $(LDFLAGS) $^
+ $(CC) -static -o $@ $(CFLAGS) -I$(top_srcdir)/tools/include $(LDFLAGS) $^
$(OUTPUT)/which-cpus: which-cpus.c sys_hwprobe.S
$(CC) -static -o $@ $(CFLAGS) $(LDFLAGS) $^
+
+$(OUTPUT)/ext-enabled: ext-enabled.c sys_hwprobe.S
+ $(CC) -static -o $@ $(CFLAGS) $(LDFLAGS) $^
+
+$(OUTPUT)/ext-enabled_nolibc: ext-enabled_nolibc.c
+ $(CC) -nostdlib -static -include ../../../../include/nolibc/nolibc.h \
+ -Wall $(CFLAGS) $(LDFLAGS) $^ -o $@ -lgcc
diff --git a/tools/testing/selftests/riscv/hwprobe/ext-enabled.c b/tools/testing/selftests/riscv/hwprobe/ext-enabled.c
new file mode 100644
index 000000000000..887820911629
--- /dev/null
+++ b/tools/testing/selftests/riscv/hwprobe/ext-enabled.c
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Test the RISCV_HWPROBE_KEY_EXT_ENABLED positional modifier.
+ *
+ * The modifier is a one-way switch within a single hwprobe request: keys
+ * before it report what is present in hardware, keys after it report what is
+ * also enabled for the calling process. A single query returns both views:
+ *
+ * [ {IMA_EXT_0}, {EXT_ENABLED}, {IMA_EXT_0} ]
+ * present modifier enabled
+ *
+ * When V is disabled for the process it must drop out of the enabled view but
+ * stay in the present view. V cannot be turned off for a thread that already
+ * has it on (prctl returns -EPERM); the NEXT control only takes effect across
+ * execve(). So the disabled-V case runs in a forked child that sets NEXT=off
+ * and execs a nolibc worker (built without libc so no vector-optimized libc
+ * routine can SIGILL while V is off). The worker reports back via exit code.
+ */
+#include <unistd.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
+#include "hwprobe.h"
+#include "kselftest.h"
+
+#ifndef PR_RISCV_V_SET_CONTROL
+#define PR_RISCV_V_SET_CONTROL 69
+#define PR_RISCV_V_VSTATE_CTRL_OFF 1
+#endif
+#define VSTATE_CTRL_NEXT_SHIFT 2
+
+#define WORKER "./ext-enabled_nolibc"
+
+/* Verdicts returned by the worker. */
+#define VOFF_OK 0 /* present keeps V, enabled masks it */
+#define VOFF_BUG 1 /* enabled view still reported V */
+#define VOFF_SETUP 2 /* could not disable V / probe failed */
+
+/* Run the canonical present/enabled query; returns the raw syscall result. */
+static long query(__u64 *present, __u64 *enabled, __s64 *mod_key)
+{
+ struct riscv_hwprobe pairs[3] = {
+ { .key = RISCV_HWPROBE_KEY_IMA_EXT_0, },
+ { .key = RISCV_HWPROBE_KEY_EXT_ENABLED, },
+ { .key = RISCV_HWPROBE_KEY_IMA_EXT_0, },
+ };
+ long ret = riscv_hwprobe(pairs, 3, 0, NULL, 0);
+
+ *present = pairs[0].value;
+ *mod_key = pairs[1].key;
+ *enabled = pairs[2].value;
+ return ret;
+}
+
+/* Fork a child that disables V across execve, and return the worker verdict. */
+static int probe_with_v_disabled(void)
+{
+ char * const argv[] = { WORKER, NULL };
+ char * const envp[] = { NULL };
+ int status;
+ pid_t pid = fork();
+
+ if (pid < 0)
+ return VOFF_SETUP;
+ if (pid == 0) {
+ /* Disable V for the next execve of this child. */
+ prctl(PR_RISCV_V_SET_CONTROL,
+ PR_RISCV_V_VSTATE_CTRL_OFF << VSTATE_CTRL_NEXT_SHIFT);
+ execve(WORKER, argv, envp);
+ _exit(VOFF_SETUP); /* execve failed */
+ }
+ if (waitpid(pid, &status, 0) < 0 || !WIFEXITED(status))
+ return VOFF_SETUP;
+ return WEXITSTATUS(status);
+}
+
+/*
+ * This test assumes the user space allow executing vector instruction as a
+ * default behavior. Namely, /proc/sys/abi/riscv_v_default_allow is 1
+ */
+int main(void)
+{
+ __u64 present = 0, enabled = 0;
+ __s64 mod_key = 0;
+ int verdict;
+
+ ksft_print_header();
+ ksft_set_plan(2);
+
+ if (query(&present, &enabled, &mod_key))
+ ksft_exit_fail_msg("hwprobe() failed\n");
+
+ ksft_test_result(mod_key == RISCV_HWPROBE_KEY_EXT_ENABLED,
+ "RISCV_HWPROBE_KEY_EXT_ENABLED is recognized\n");
+
+ /* The remaining checks need both the modifier and V in hardware. */
+ if (mod_key != RISCV_HWPROBE_KEY_EXT_ENABLED ||
+ !(enabled & RISCV_HWPROBE_IMA_V)) {
+ ksft_test_result_skip("V off: present keeps V, enabled masks it\n");
+ ksft_finished();
+ }
+
+ /* V off (only reachable across execve): enabled view masks it. */
+ verdict = probe_with_v_disabled();
+ if (verdict == VOFF_SETUP)
+ ksft_test_result_skip("V off: present keeps V, but could not disable V\n");
+ else
+ ksft_test_result(verdict == VOFF_OK,
+ "V off: present keeps V, enabled masks it\n");
+
+ ksft_finished();
+}
diff --git a/tools/testing/selftests/riscv/hwprobe/ext-enabled_nolibc.c b/tools/testing/selftests/riscv/hwprobe/ext-enabled_nolibc.c
new file mode 100644
index 000000000000..d7f0a3c8314d
--- /dev/null
+++ b/tools/testing/selftests/riscv/hwprobe/ext-enabled_nolibc.c
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * nolibc worker for the RISCV_HWPROBE_KEY_EXT_ENABLED test.
+ *
+ * ext-enabled execs this program with V disabled for the process. It is built
+ * with nolibc on purpose: there is no libc here, so no vector-optimized libc
+ * routine can run and take a SIGILL while V is off. It probes the canonical
+ * [present, modifier, enabled] request and reports a verdict via exit code:
+ *
+ * 0 present keeps V, enabled masks it (correct)
+ * 1 enabled view still reports V (bug)
+ * 2 setup problem (V not off, or hwprobe failed)
+ */
+
+#ifndef __NR_riscv_hwprobe
+#define __NR_riscv_hwprobe 258
+#endif
+
+#ifndef PR_RISCV_V_GET_CONTROL
+#define PR_RISCV_V_GET_CONTROL 70
+#define PR_RISCV_V_VSTATE_CTRL_OFF 1
+#define PR_RISCV_V_VSTATE_CTRL_CUR_MASK 0x3
+#endif
+
+#define RISCV_HWPROBE_KEY_IMA_EXT_0 4
+#define RISCV_HWPROBE_KEY_EXT_ENABLED 17
+#define RISCV_HWPROBE_IMA_V (1 << 2)
+
+struct riscv_hwprobe {
+ long long key;
+ unsigned long long value;
+};
+
+int main(void)
+{
+ struct riscv_hwprobe pairs[3] = {
+ { .key = RISCV_HWPROBE_KEY_IMA_EXT_0 },
+ { .key = RISCV_HWPROBE_KEY_EXT_ENABLED },
+ { .key = RISCV_HWPROBE_KEY_IMA_EXT_0 },
+ };
+ long ctrl;
+
+ /* We must really be running with V turned off. */
+ ctrl = prctl(PR_RISCV_V_GET_CONTROL, 0, 0, 0, 0);
+ if (ctrl < 0 ||
+ (ctrl & PR_RISCV_V_VSTATE_CTRL_CUR_MASK) != PR_RISCV_V_VSTATE_CTRL_OFF)
+ return 2;
+
+ if (syscall(__NR_riscv_hwprobe, pairs, 3, 0, 0, 0))
+ return 2;
+
+ /* Present view must still carry V (it is in hardware). */
+ if (!(pairs[0].value & RISCV_HWPROBE_IMA_V))
+ return 2;
+
+ /* Enabled view must have masked V out. */
+ return (pairs[2].value & RISCV_HWPROBE_IMA_V) ? 1 : 0;
+}
--
2.43.0
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
^ permalink raw reply related [flat|nested] 4+ messages in thread