[PATCH v3 2/3] riscv: hwprobe: export the availability of vector to user
Jesse Taube
jtaubepe at redhat.com
Wed Aug 5 10:38:33 PDT 2026
On Fri, Jul 24, 2026 at 8:17 PM Andy Chiu <tchiu at tenstorrent.com> wrote:
>
> 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 at tenstorrent.com>
> ---
> Changelog v2:
> - rebase on top of latest for-next
> - add test_avail to sifive's vector depending extensions
> ---
> Documentation/arch/riscv/hwprobe.rst | 27 ++++++++++++++
> arch/riscv/include/asm/hwprobe.h | 2 +-
> .../asm/vendor_extensions/sifive_hwprobe.h | 6 ++--
> .../asm/vendor_extensions/thead_hwprobe.h | 6 ++--
> arch/riscv/include/uapi/asm/hwprobe.h | 2 ++
> arch/riscv/kernel/sys_hwprobe.c | 36 +++++++++++++------
> arch/riscv/kernel/vdso/hwprobe.c | 25 +++++++++----
> .../kernel/vendor_extensions/sifive_hwprobe.c | 16 ++++++---
> .../kernel/vendor_extensions/thead_hwprobe.c | 9 +++--
> 9 files changed, 100 insertions(+), 29 deletions(-)
>
> diff --git a/Documentation/arch/riscv/hwprobe.rst b/Documentation/arch/riscv/hwprobe.rst
> index d9928641deb9..b9b3cf23c212 100644
> --- a/Documentation/arch/riscv/hwprobe.rst
> +++ b/Documentation/arch/riscv/hwprobe.rst
> @@ -401,3 +401,30 @@ The following keys are defined:
> as defined in version 1.0 of the RISC-V Control-flow Integrity (CFI)
> extensions specification, ratified in commit 302a2d45c243
> ("Update build-pdf.yml") of riscv-cfi.
> +
> + * :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/sifive_hwprobe.h b/arch/riscv/include/asm/vendor_extensions/sifive_hwprobe.h
> index 90a61abd033c..c961c78a7927 100644
> --- a/arch/riscv/include/asm/vendor_extensions/sifive_hwprobe.h
> +++ b/arch/riscv/include/asm/vendor_extensions/sifive_hwprobe.h
> @@ -7,10 +7,12 @@
> #include <uapi/asm/hwprobe.h>
>
> #ifdef CONFIG_RISCV_ISA_VENDOR_EXT_SIFIVE
> -void hwprobe_isa_vendor_ext_sifive_0(struct riscv_hwprobe *pair, const struct cpumask *cpus);
> +void hwprobe_isa_vendor_ext_sifive_0(struct riscv_hwprobe *pair,
> + const struct cpumask *cpus, bool test_avail);
> #else
> static inline void hwprobe_isa_vendor_ext_sifive_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/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 d2bb70fed8b9..5949e3d9146f 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)
> {
> pair->value = 0;
>
> @@ -314,7 +316,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:
> @@ -351,11 +353,11 @@ static void hwprobe_one_pair(struct riscv_hwprobe *pair,
> break;
>
> case RISCV_HWPROBE_KEY_VENDOR_EXT_SIFIVE_0:
> - hwprobe_isa_vendor_ext_sifive_0(pair, cpus);
> + hwprobe_isa_vendor_ext_sifive_0(pair, cpus, test_avail);
> 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);
It might be good to add a warning if `RISCV_HWPROBE_KEY_EXT_ENABLED`
is ever passed to `hwprobe_one_pair` in the future as it should never
be passed here.
> @@ -378,6 +380,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;
> @@ -418,7 +421,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);
> @@ -437,6 +443,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.
By the way this got removed in for-next
("Revert "riscv: hwprobe: Fix stale vDSO data for late-initialized
keys at boot"")
> */
> 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;
NIT: `avail_test` is very similar to `test_avail`, which confused me at first.
I would change `avail_test` to `has_avail_test` or `does_avail_test`
so its more readable.
> + 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/sifive_hwprobe.c b/arch/riscv/kernel/vendor_extensions/sifive_hwprobe.c
> index 1f77f6309763..379ea16bd89f 100644
> --- a/arch/riscv/kernel/vendor_extensions/sifive_hwprobe.c
> +++ b/arch/riscv/kernel/vendor_extensions/sifive_hwprobe.c
> @@ -6,17 +6,23 @@
>
> #include <linux/cpumask.h>
> #include <linux/types.h>
> +#include <asm/vector.h>
>
> #include <uapi/asm/hwprobe.h>
> #include <uapi/asm/vendor/sifive.h>
>
> -void hwprobe_isa_vendor_ext_sifive_0(struct riscv_hwprobe *pair, const struct cpumask *cpus)
> +void hwprobe_isa_vendor_ext_sifive_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_sifive.per_hart_isa_bitmap, {
> - VENDOR_EXT_KEY(XSFVQMACCDOD);
> - VENDOR_EXT_KEY(XSFVQMACCQOQ);
> - VENDOR_EXT_KEY(XSFVFNRCLIPXFQF);
> - VENDOR_EXT_KEY(XSFVFWMACCQQQ);
> + if (report_v) {
> + VENDOR_EXT_KEY(XSFVQMACCDOD);
> + VENDOR_EXT_KEY(XSFVQMACCQOQ);
> + VENDOR_EXT_KEY(XSFVFNRCLIPXFQF);
> + VENDOR_EXT_KEY(XSFVFWMACCQQQ);
> + }
> });
> }
> 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 at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-riscv
>
More information about the linux-riscv
mailing list