Re: [PATCH v3 2/3] riscv: hwprobe: export the availability of vector to user

Jesse Taube <[email protected]> Wed, 5 Aug 2026 13:38:33 -0400
Newsgroups org.infradead.lists.linux-riscv,org.kernel.vger.linux-doc
Message-ID <CADRr4bccaiDeMF+R94Sd8hetoi84Z0p9Dxin5TCm3iAy8r9phA@mail.gmail.com>
On Fri, Jul 24, 2026 at 8:17 PM Andy Chiu <[email protected]> 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/[email protected]/
> Signed-off-by: Andy Chiu <[email protected]>
> ---
> 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
> [email protected]
> http://lists.infradead.org/mailman/listinfo/linux-riscv
>


_______________________________________________
linux-riscv mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/linux-riscv