[PATCH v7 04/20] xen/riscv: introduce guest riscv,isa string
Oleksii Kurochko <[email protected]> Tue, 4 Aug 2026 17:47:54 +0200
| Newsgroups | gmane.comp.emulators.xen.devel |
|---|---|
| Message-ID | <59b5b69f53ef81d68fd279d9ee0c03f5ffbbc2ae.1785836421.git.oleksii.kurochko@gmail.com> |
Introduce build_guest_isa_str() to generate the riscv,isa string to be passed to the guest via the Device Tree riscv,isa property. Introduce the per-domain guest ISA bitmap, populated during domain creation by calling init_guest_isa(). Introduce struct riscv_isa_ext_entry with a new guest_supported field to filter out ISA extensions that should not be exposed to guests: - f/d/q/v: FPU and vector context save/restore are not yet implemented for guests. - Z*inx are not exposed either: they aren't in riscv_isa_ext[], so they can never be set in riscv_isa and thus never reach a guest, and no current hardware/guest-OS advertises or expects them. Supporting them would be cheaper than F/D/Q (FP values stay in integer registers Xen already context-switches), but is left as future work. - h: Nested virtualisation is not supported. - sstc: Xen owns the supervisor timer; guests must use SBI. - svade: Xen manages hardware A/D bit updates in stage-2 page tables. - svpbmt: Page-based memory types are not yet wired up in stage-2 code. Signed-off-by: Oleksii Kurochko <[email protected]> --- Changes in v7: - build_guest_isa_str(): drop the struct domain argument and work directly on the guest_isa bitmap; make the function static as it has no external users anymore. - build the guest "riscv,isa" string only once, in compute_guest_isa(), and store it in an __ro_after_init pointer, instead of rebuilding it for every domain: all domains are currently given the same guest ISA, so apply here the same reasoning as for the guest_isa bitmap. - panic() on length calculation, allocation or formatting failure, in line with the rest of riscv_fill_hwcap() initialization. - introduce get_guest_isa_str() accessor and expose it in asm/cpufeature.h instead of build_guest_isa_str(). --- Changes in v6: - build_guest_isa_str() now takes a `const struct domain *d` instead of a raw `const unsigned long *isa_bitmap`, to leave room for using more than just the bitmap in the future. - Compute the guest-visible ISA bitmap once at boot, into a new __ro_after_init `guest_isa` bitmap (compute_guest_isa(), called at the end of riscv_fill_hwcap()), instead of re-deriving it from riscv_isa_ext[] on every domain creation in init_guest_isa(). All guests currently get the same extension set, so this avoids repeating identical work per domain; will need revisiting if/when per-domain ISA policy is introduced. - struct arch_domain's `isa` field is now `const unsigned long *isa` instead of an embedded bitmap; init_guest_isa() just points it at the shared `guest_isa` bitmap rather than copying bits into a per-domain array. - Mark riscv_isa_ext[] __initconstrel, since its entries hold name pointers and the need for relocations requires that the compiler emit the data to a writable section. - Make build_guest_isa_str() __init as it is called during make_cpus_node() which is used only (at least, for now) in build time of domain. --- Changes in v5: - Introduce struct riscv_isa_ext_entry with a guest_supported field and RISCV_ISA_EXT_ENTRY(name, guest_supp) macro for riscv_isa_ext[], replacing the ad-hoc guest_unsupp bitmap and init_guest_unsupp(). Every entry now carries an explicit true/false decision, enforced at compile time. - init_guest_isa() builds d->arch.isa by iterating riscv_isa_ext[] directly instead of using bitmap_andnot() against guest_unsupp. - init_guest_isa() changed to void as it can no longer fail. - Drop isa_str from struct arch_domain; the ISA string does not need to persist over the domain lifetime. build_guest_isa_str() is made non-static and declared in cpufeature.h for use when building the guest device tree. - Updated the fix of underflow in build_guest_isa_str(). - Drop unnecessary empty line in cpufeature.h before enum riscv_isa_ext_id. --- Changes in v4: - Add an explicit overflow guard in build_guest_isa_str(): return -ENOSPC when buf is non-NULL and total >= size, to avoid the size - total underflow being passed to snprintf(). - Expand the commit message to explain why Zfinx/Zdinx/Zqinx are not added to guest_unsupp (not in riscv_isa_ext[], so never set in riscv_isa nor exposed to a guest; left as future work) --- Changes in v3: - s/set_bit/__set_bit in init_guest_unsupp() as atomicity isn't needed at init time. - Drop RISCV_GUEST_ISA_STR_MAX; allocate isa_str dynamically with xvmalloc_array(). - Drop "guest" prefix from d->arch.guest_isa and d->arch.guest_isa_str. - Introduce build_guest_isa_str() using snprintf(NULL, 0, ...) to determine the needed buffer size; init_guest_isa() calls it once for sizing and once to fill, keeping both in a single function so they can't go out of sync. - Scope ret inside the loop; initialize total directly from the prefix snprintf(). - Merge "_" separator and extension name into a single snprintf() with "%s%s". - Replace ASSERT with an explicit error check: if the fill call returns a different length, free isa_str and return -EINVAL. --- Changes in v2: - s/guest_unsupp_bmp/guest_unsupp. - Drop guest_isa_str. - Provide init_guest_isa() instead of polluting match_isa_ext(). - Drop xlen. - Add the comment about guest_unsupp. - Update the way how guest_unsupp is init-ed. - Drop __initconst for riscv_isa_ext[] as it is used in init_guest_isa() which isn't marked as __init as it could be used after init stage. --- --- xen/arch/riscv/cpufeature.c | 171 ++++++++++++++++++++---- xen/arch/riscv/domain.c | 2 + xen/arch/riscv/include/asm/cpufeature.h | 5 + xen/arch/riscv/include/asm/domain.h | 3 + 4 files changed, 157 insertions(+), 24 deletions(-) diff --git a/xen/arch/riscv/cpufeature.c b/xen/arch/riscv/cpufeature.c index 92235fdfd5ab..c963c3d556b4 100644 --- a/xen/arch/riscv/cpufeature.c +++ b/xen/arch/riscv/cpufeature.c @@ -14,7 +14,9 @@ #include <xen/errno.h> #include <xen/init.h> #include <xen/lib.h> +#include <xen/sched.h> #include <xen/sections.h> +#include <xen/xvmalloc.h> #include <asm/cpufeature.h> #include <asm/csr.h> @@ -34,9 +36,35 @@ struct riscv_isa_ext_data { .name = #ext_name, \ } +struct riscv_isa_ext_entry { + unsigned int id; + const char *name; + bool guest_supported; +}; + +#define RISCV_ISA_EXT_ENTRY(ext_name, guest_supp) \ +{ \ + .id = RISCV_ISA_EXT_ ## ext_name, \ + .name = #ext_name, \ + .guest_supported = guest_supp, \ +} + /* Host ISA bitmap */ static __ro_after_init DECLARE_BITMAP(riscv_isa, RISCV_ISA_EXT_MAX); +/* + * ISA bitmap handed out to every guest. + * + * All guests are given the same extensions for the time being, so this is + * computed once out of riscv_isa_ext[] and riscv_isa, rather than redoing + * the walk for every domain created. Should per-domain ISA policy ever be + * introduced, this will need to become per-domain again. + */ +static __ro_after_init DECLARE_BITMAP(guest_isa, RISCV_ISA_EXT_MAX); + +/* "riscv,isa" string corresponding to guest_isa, shared by all domains. */ +static char *__ro_after_init guest_isa_str; + static int __init dt_get_cpuid_from_node(const struct dt_device_node *cpu, unsigned long *dt_cpuid) { @@ -120,29 +148,30 @@ static int __init dt_get_cpuid_from_node(const struct dt_device_node *cpu, * and strncmp() is used in match_isa_ext() to compare extension names instead * of strncasecmp(). */ -const struct riscv_isa_ext_data __initconst riscv_isa_ext[] = { - RISCV_ISA_EXT_DATA(i), - RISCV_ISA_EXT_DATA(m), - RISCV_ISA_EXT_DATA(a), - RISCV_ISA_EXT_DATA(f), - RISCV_ISA_EXT_DATA(d), - RISCV_ISA_EXT_DATA(q), - RISCV_ISA_EXT_DATA(c), - RISCV_ISA_EXT_DATA(h), - RISCV_ISA_EXT_DATA(zicntr), - RISCV_ISA_EXT_DATA(zicsr), - RISCV_ISA_EXT_DATA(zifencei), - RISCV_ISA_EXT_DATA(zihintpause), - RISCV_ISA_EXT_DATA(zihpm), - RISCV_ISA_EXT_DATA(zba), - RISCV_ISA_EXT_DATA(zbb), - RISCV_ISA_EXT_DATA(zbs), - RISCV_ISA_EXT_DATA(smaia), - RISCV_ISA_EXT_DATA(smstateen), - RISCV_ISA_EXT_DATA(ssaia), - RISCV_ISA_EXT_DATA(sstc), - RISCV_ISA_EXT_DATA(svade), - RISCV_ISA_EXT_DATA(svpbmt), +static const struct riscv_isa_ext_entry __initconstrel riscv_isa_ext[] = { + RISCV_ISA_EXT_ENTRY(i, true), + RISCV_ISA_EXT_ENTRY(m, true), + RISCV_ISA_EXT_ENTRY(a, true), + RISCV_ISA_EXT_ENTRY(f, false), + RISCV_ISA_EXT_ENTRY(d, false), + RISCV_ISA_EXT_ENTRY(q, false), + RISCV_ISA_EXT_ENTRY(c, true), + RISCV_ISA_EXT_ENTRY(v, false), + RISCV_ISA_EXT_ENTRY(h, false), + RISCV_ISA_EXT_ENTRY(zicntr, true), + RISCV_ISA_EXT_ENTRY(zicsr, true), + RISCV_ISA_EXT_ENTRY(zifencei, true), + RISCV_ISA_EXT_ENTRY(zihintpause, true), + RISCV_ISA_EXT_ENTRY(zihpm, true), + RISCV_ISA_EXT_ENTRY(zba, true), + RISCV_ISA_EXT_ENTRY(zbb, true), + RISCV_ISA_EXT_ENTRY(zbs, true), + RISCV_ISA_EXT_ENTRY(smaia, true), + RISCV_ISA_EXT_ENTRY(smstateen, true), + RISCV_ISA_EXT_ENTRY(ssaia, true), + RISCV_ISA_EXT_ENTRY(sstc, false), + RISCV_ISA_EXT_ENTRY(svade, false), + RISCV_ISA_EXT_ENTRY(svpbmt, false), }; static const struct riscv_isa_ext_data __initconst required_extensions[] = { @@ -181,7 +210,7 @@ static void __init match_isa_ext(const char *name, const char *name_end, for ( unsigned int i = 0; i < riscv_isa_ext_count; i++ ) { - const struct riscv_isa_ext_data *ext = &riscv_isa_ext[i]; + const struct riscv_isa_ext_entry *ext = &riscv_isa_ext[i]; /* * `ext->name` (according to initialization of riscv_isa_ext[] @@ -480,6 +509,98 @@ bool riscv_isa_extension_available(const unsigned long *isa_bitmap, return test_bit(id, isa_bitmap); } +static int __init build_guest_isa_str(char *buf, size_t size) +{ + char *p = buf; + size_t left = size; + int total; + +#if defined(CONFIG_RISCV_32) + total = snprintf(p, left, "rv32"); +#elif defined(CONFIG_RISCV_64) + total = snprintf(p, left, "rv64"); +#else +# error "Unsupported RISC-V bitness" +#endif + + if ( total < 0 ) + return total; + + if ( buf ) + { + if ( (size_t)total >= left ) + return -ENOSPC; + + p += total; + left -= total; + } + + for ( unsigned int i = 0; i < ARRAY_SIZE(riscv_isa_ext); i++ ) + { + const struct riscv_isa_ext_entry *ext = &riscv_isa_ext[i]; + int ret; + + if ( !riscv_isa_extension_available(guest_isa, ext->id) ) + continue; + + ret = snprintf(p, left, "%s%s", + ext->id >= RISCV_ISA_EXT_BASE ? "_" : "", + ext->name); + if ( ret < 0 ) + return ret; + + total += ret; + + if ( buf ) + { + if ( (size_t)ret >= left ) + return -ENOSPC; + + p += ret; + left -= ret; + } + } + + return total; +} + +static void __init compute_guest_isa(void) +{ + int len; + + for ( unsigned int i = 0; i < ARRAY_SIZE(riscv_isa_ext); i++ ) + { + const struct riscv_isa_ext_entry *ext = &riscv_isa_ext[i]; + + if ( ext->guest_supported && + riscv_isa_extension_available(NULL, ext->id) ) + __set_bit(ext->id, guest_isa); + } + + /* + * All domains are given the same guest ISA, so the "riscv,isa" string + * is built only once here and then shared by all of them. + */ + if ( (len = build_guest_isa_str(NULL, 0)) < 0 ) + panic("Failed to calculate guest \"riscv,isa\" length: %d\n", len); + + if ( !(guest_isa_str = xvmalloc_array(char, len + 1)) ) + panic("Failed to allocate guest \"riscv,isa\" string\n"); + + if ( build_guest_isa_str(guest_isa_str, len + 1) != len ) + panic("Failed to build guest \"riscv,isa\" string\n"); +} + +void init_guest_isa(struct domain *d) +{ + d->arch.isa = guest_isa; +} + +const char *get_guest_isa_str(void) +{ + return guest_isa_str; +} + void __init riscv_fill_hwcap(void) { unsigned int i; @@ -527,4 +648,6 @@ void __init riscv_fill_hwcap(void) if ( !all_extns_available ) panic("Look why the extensions above are needed in " "https://xenbits.xenproject.org/docs/unstable/misc/riscv/booting.txt\n"); + + compute_guest_isa(); } diff --git a/xen/arch/riscv/domain.c b/xen/arch/riscv/domain.c index 2819ff4e7c92..c9933147595e 100644 --- a/xen/arch/riscv/domain.c +++ b/xen/arch/riscv/domain.c @@ -308,6 +308,8 @@ int arch_domain_create(struct domain *d, if ( is_idle_domain(d) ) return 0; + init_guest_isa(d); + if ( (rc = p2m_init(d, config)) != 0) goto fail; diff --git a/xen/arch/riscv/include/asm/cpufeature.h b/xen/arch/riscv/include/asm/cpufeature.h index 0c48d57a03bb..2973eb13a513 100644 --- a/xen/arch/riscv/include/asm/cpufeature.h +++ b/xen/arch/riscv/include/asm/cpufeature.h @@ -5,6 +5,7 @@ #ifndef __ASSEMBLER__ #include <xen/stdbool.h> +#include <xen/types.h> /* * These macros represent the logical IDs of each multi-letter RISC-V ISA @@ -44,7 +45,11 @@ enum riscv_isa_ext_id { RISCV_ISA_EXT_MAX }; +struct domain; + void riscv_fill_hwcap(void); +void init_guest_isa(struct domain *d); +const char *get_guest_isa_str(void); bool riscv_isa_extension_available(const unsigned long *isa_bitmap, enum riscv_isa_ext_id id); diff --git a/xen/arch/riscv/include/asm/domain.h b/xen/arch/riscv/include/asm/domain.h index 6044ce0feee0..8ae01a5e4dcc 100644 --- a/xen/arch/riscv/include/asm/domain.h +++ b/xen/arch/riscv/include/asm/domain.h @@ -7,6 +7,7 @@ #include <xen/xmalloc.h> #include <public/hvm/params.h> +#include <asm/cpufeature.h> #include <asm/guest-layout.h> #include <asm/p2m.h> #include <asm/vtimer.h> @@ -94,6 +95,8 @@ struct arch_domain { struct p2m_domain p2m; struct paging_domain paging; + + const unsigned long *isa; }; #include <xen/sched.h> -- 2.55.0