* [RFC PATCH 2/3] RISC-V: KVM: Preserve guest Vector state across vCPU put/load
2026-10-27 13:13 [RFC PATCH 0/3] RISC-V: KVM: Preserve resident guest Vector state slack up
2026-10-27 13:13 ` [RFC PATCH 1/3] riscv: vector: Add a guest owner invalidation hook slack up
@ 2026-10-27 13:13 ` slack up
2026-10-27 14:11 ` sashiko-bot
2026-10-27 13:13 ` [RFC PATCH 3/3] KVM: riscv: selftests: Add Vector owner regression tests slack up
2 siblings, 1 reply; 7+ messages in thread
From: slack up @ 2026-10-27 13:13 UTC (permalink / raw)
To: Anup Patel, Atish Patra, Palmer Dabbelt, kvm-riscv, linux-riscv
Cc: Paul Walmsley, Albert Ou, Alexandre Ghiti, Paolo Bonzini,
Sean Christopherson, Shuah Khan, kvm, linux-kernel,
linux-kselftest, Xiaochun Yu
From: Xiaochun Yu <yxcnew@outlook.com>
kvm_arch_vcpu_put() saves the guest Vector state and restores the host
register file. A later load on the same hart saves the host again and
requests a full guest restore, even when the host has not used Vector in
between.
Track a per-hart owner with an opaque vCPU identity, an allocation cookie
and a state generation. After saving a dirty guest to memory, leave its
data registers resident when the host has no Vector restore obligation. A
matching load restores only the Vector control CSRs. All mismatches and
generic Vector users fall back to a full restore.
Keep guest memory authoritative by continuing to save at every dirty put.
Advance the generation on saves, resets and SET_ONE_REG. Do so before
copy_from_user() because a fault can leave a modified prefix in the
destination even though the ioctl fails.
Register the generic invalidation hook only when the read-only
vector_owner_fastpath module parameter is enabled. The parameter defaults
to false so the existing full-restore behavior remains the default. Add
tracepoints for restore mode, owner decisions and invalidation reasons.
Development note: OpenAI Codex assisted with source analysis,
implementation drafting, test orchestration and changelog preparation.
Signed-off-by: Xiaochun Yu <yxcnew@outlook.com>
---
arch/riscv/include/asm/kvm_host.h | 4 +
arch/riscv/include/asm/kvm_vcpu_vector.h | 54 ++++-
arch/riscv/include/asm/processor.h | 1 +
arch/riscv/kvm/trace.h | 98 +++++++++
arch/riscv/kvm/vcpu.c | 54 ++++-
arch/riscv/kvm/vcpu_vector.c | 252 ++++++++++++++++++++++-
6 files changed, 439 insertions(+), 24 deletions(-)
diff --git a/arch/riscv/include/asm/kvm_host.h b/arch/riscv/include/asm/kvm_host.h
index e28f058a94d7..a6e2b3097644 100644
--- a/arch/riscv/include/asm/kvm_host.h
+++ b/arch/riscv/include/asm/kvm_host.h
@@ -204,6 +204,10 @@ struct kvm_vcpu_arch {
/* CPU context of Guest VCPU */
struct kvm_cpu_context guest_context;
+ /* Identity and version used by Vector owner tracking. */
+ u64 vector_owner_cookie;
+ u64 vector_state_generation;
+
/* CPU CSR context of Guest VCPU */
struct kvm_vcpu_csr guest_csr;
diff --git a/arch/riscv/include/asm/kvm_vcpu_vector.h b/arch/riscv/include/asm/kvm_vcpu_vector.h
index ebc619ed67dd..7f3eecf4217b 100644
--- a/arch/riscv/include/asm/kvm_vcpu_vector.h
+++ b/arch/riscv/include/asm/kvm_vcpu_vector.h
@@ -26,29 +26,47 @@ static __always_inline void __kvm_riscv_vector_restore(struct kvm_cpu_context *c
__riscv_v_vstate_restore(&context->vector, context->vector.datap);
}
+static __always_inline void __kvm_riscv_vector_restore_csrs(struct kvm_cpu_context *context)
+{
+ riscv_v_enable();
+ __vstate_csr_restore(&context->vector);
+ riscv_v_disable();
+}
+
void kvm_riscv_vcpu_vector_reset(struct kvm_vcpu *vcpu);
void kvm_riscv_vcpu_guest_vector_save(struct kvm_cpu_context *cntx,
unsigned long *isa);
void kvm_riscv_vcpu_guest_vector_restore(struct kvm_cpu_context *cntx,
- unsigned long *isa);
+ unsigned long *isa, bool csr_only);
void kvm_riscv_vcpu_host_vector_save(struct kvm_cpu_context *cntx);
-void kvm_riscv_vcpu_host_vector_restore(struct kvm_cpu_context *cntx);
+void kvm_riscv_vcpu_host_vector_restore(struct kvm_cpu_context *cntx,
+ bool preserve_guest);
int kvm_riscv_vcpu_alloc_vector_context(struct kvm_vcpu *vcpu);
void kvm_riscv_vcpu_free_vector_context(struct kvm_vcpu *vcpu);
void kvm_riscv_register_vctx_callback(void (*func)(void));
void kvm_riscv_unregister_vctx_callback(void);
+bool kvm_riscv_vcpu_vector_owner_enabled(void);
+bool kvm_riscv_vcpu_vector_owner_load(struct kvm_vcpu *vcpu, int cpu);
+bool kvm_riscv_vcpu_vector_owner_put(struct kvm_vcpu *vcpu);
+void kvm_riscv_vcpu_vector_owner_invalidate(enum riscv_v_invalidate_reason reason);
void kvm_riscv_vcpu_flush_vector(void);
static inline void kvm_riscv_v_init(void)
{
- if (has_vector())
+ if (has_vector()) {
kvm_riscv_register_vctx_callback(&kvm_riscv_vcpu_flush_vector);
+ if (kvm_riscv_vcpu_vector_owner_enabled())
+ kvm_riscv_register_vctx_invalidate(&kvm_riscv_vcpu_vector_owner_invalidate);
+ }
}
static inline void kvm_riscv_v_exit(void)
{
- if (has_vector())
+ if (has_vector()) {
+ if (kvm_riscv_vcpu_vector_owner_enabled())
+ kvm_riscv_unregister_vctx_invalidate();
kvm_riscv_unregister_vctx_callback();
+ }
}
#else
@@ -59,6 +77,10 @@ static inline void __kvm_riscv_vector_restore(struct kvm_cpu_context *context)
{
}
+static inline void __kvm_riscv_vector_restore_csrs(struct kvm_cpu_context *context)
+{
+}
+
static inline void kvm_riscv_vcpu_vector_reset(struct kvm_vcpu *vcpu)
{
}
@@ -69,7 +91,7 @@ static inline void kvm_riscv_vcpu_guest_vector_save(struct kvm_cpu_context *cntx
}
static inline void kvm_riscv_vcpu_guest_vector_restore(struct kvm_cpu_context *cntx,
- unsigned long *isa)
+ unsigned long *isa, bool csr_only)
{
}
@@ -77,7 +99,8 @@ static inline void kvm_riscv_vcpu_host_vector_save(struct kvm_cpu_context *cntx)
{
}
-static inline void kvm_riscv_vcpu_host_vector_restore(struct kvm_cpu_context *cntx)
+static inline void
+kvm_riscv_vcpu_host_vector_restore(struct kvm_cpu_context *cntx, bool preserve_guest)
{
}
@@ -90,6 +113,25 @@ static inline void kvm_riscv_vcpu_free_vector_context(struct kvm_vcpu *vcpu)
{
}
+static inline bool kvm_riscv_vcpu_vector_owner_enabled(void)
+{
+ return false;
+}
+
+static inline bool kvm_riscv_vcpu_vector_owner_load(struct kvm_vcpu *vcpu, int cpu)
+{
+ return false;
+}
+
+static inline bool kvm_riscv_vcpu_vector_owner_put(struct kvm_vcpu *vcpu)
+{
+ return false;
+}
+
+static inline void kvm_riscv_vcpu_vector_owner_invalidate(enum riscv_v_invalidate_reason reason)
+{
+}
+
static inline void kvm_riscv_v_init(void)
{
}
diff --git a/arch/riscv/include/asm/processor.h b/arch/riscv/include/asm/processor.h
index 815715c67f94..abda84ca9548 100644
--- a/arch/riscv/include/asm/processor.h
+++ b/arch/riscv/include/asm/processor.h
@@ -105,6 +105,7 @@ struct pt_regs;
#define RISCV_KERNEL_MODE_V 0x00000001
#define RISCV_V_VCPU_NEED_RESTORE 0x00000002
#define RISCV_V_VCPU_CTX 0x00000004
+#define RISCV_V_VCPU_CSR_RESTORE 0x00000008
#define RISCV_PREEMPT_V 0x00000100
#define RISCV_PREEMPT_V_DIRTY 0x80000000
#define RISCV_PREEMPT_V_NEED_RESTORE 0x40000000
diff --git a/arch/riscv/kvm/trace.h b/arch/riscv/kvm/trace.h
index 23a5fd1b686e..c805606c704d 100644
--- a/arch/riscv/kvm/trace.h
+++ b/arch/riscv/kvm/trace.h
@@ -133,6 +133,104 @@ TRACE_EVENT(kvm_vcpu_irq,
__entry->vcpu_id, __entry->irq, __entry->level)
);
+TRACE_EVENT(kvm_riscv_vector_restore_request,
+ TP_PROTO(unsigned long vcpu_id, int cpu, unsigned int source,
+ unsigned long host_vs),
+ TP_ARGS(vcpu_id, cpu, source, host_vs),
+
+ TP_STRUCT__entry(
+ __field(unsigned long, vcpu_id)
+ __field(int, cpu)
+ __field(unsigned int, source)
+ __field(unsigned long, host_vs)
+ ),
+
+ TP_fast_assign(
+ __entry->vcpu_id = vcpu_id;
+ __entry->cpu = cpu;
+ __entry->source = source;
+ __entry->host_vs = host_vs;
+ ),
+
+ TP_printk("VCPU: %lu, CPU: %d, source: %s, host VS: 0x%lx",
+ __entry->vcpu_id, __entry->cpu,
+ __print_symbolic(__entry->source,
+ { 0, "load" }, { 1, "kernel_vector" }),
+ __entry->host_vs)
+);
+
+TRACE_EVENT(kvm_riscv_vector_restore,
+ TP_PROTO(unsigned long vcpu_id, int cpu, bool csr_only),
+ TP_ARGS(vcpu_id, cpu, csr_only),
+
+ TP_STRUCT__entry(
+ __field(unsigned long, vcpu_id)
+ __field(int, cpu)
+ __field(bool, csr_only)
+ ),
+
+ TP_fast_assign(
+ __entry->vcpu_id = vcpu_id;
+ __entry->cpu = cpu;
+ __entry->csr_only = csr_only;
+ ),
+
+ TP_printk("VCPU: %lu, CPU: %d, mode: %s",
+ __entry->vcpu_id, __entry->cpu,
+ __entry->csr_only ? "csr" : "full")
+);
+
+TRACE_EVENT(kvm_riscv_vector_owner,
+ TP_PROTO(unsigned long vcpu_id, int cpu, unsigned int action,
+ bool eligible, unsigned int reason, unsigned long host_vs,
+ unsigned long guest_vs, u64 generation),
+ TP_ARGS(vcpu_id, cpu, action, eligible, reason, host_vs, guest_vs,
+ generation),
+
+ TP_STRUCT__entry(
+ __field(unsigned long, vcpu_id)
+ __field(int, cpu)
+ __field(unsigned int, action)
+ __field(bool, eligible)
+ __field(unsigned int, reason)
+ __field(unsigned long, host_vs)
+ __field(unsigned long, guest_vs)
+ __field(u64, generation)
+ ),
+
+ TP_fast_assign(
+ __entry->vcpu_id = vcpu_id;
+ __entry->cpu = cpu;
+ __entry->action = action;
+ __entry->eligible = eligible;
+ __entry->reason = reason;
+ __entry->host_vs = host_vs;
+ __entry->guest_vs = guest_vs;
+ __entry->generation = generation;
+ ),
+
+ TP_printk("VCPU: %lu, CPU: %d, action: %s, eligible: %d, reason: %s, host VS: 0x%lx, guest VS: 0x%lx, generation: %llu",
+ __entry->vcpu_id, __entry->cpu,
+ __print_symbolic(__entry->action,
+ { 0, "load" }, { 1, "put" },
+ { 2, "invalidate" }),
+ __entry->eligible,
+ __print_symbolic(__entry->reason,
+ { 0, "hit" }, { 1, "empty" },
+ { 2, "other_vcpu" }, { 3, "cookie" },
+ { 4, "generation" }, { 5, "guest_off" },
+ { 6, "host_on" }, { 7, "migration" },
+ { 8, "put_candidate" },
+ { 9, "task_save" },
+ { 10, "task_restore" },
+ { 11, "task_discard" },
+ { 12, "kernel_vector" },
+ { 13, "cpu_state" },
+ { 14, "not_resident" }),
+ __entry->host_vs, __entry->guest_vs,
+ __entry->generation)
+);
+
#endif /* _TRACE_KVM_H */
#undef TRACE_INCLUDE_PATH
diff --git a/arch/riscv/kvm/vcpu.c b/arch/riscv/kvm/vcpu.c
index 7f1636c44d41..a59cac26ca7c 100644
--- a/arch/riscv/kvm/vcpu.c
+++ b/arch/riscv/kvm/vcpu.c
@@ -35,6 +35,7 @@ void kvm_riscv_clear_former_vcpu(void)
* via the fast-path optimization.
*/
__this_cpu_write(kvm_former_vcpu, NULL);
+ kvm_riscv_vcpu_vector_owner_invalidate(RISCV_V_INVALIDATE_CPU_STATE);
}
const struct kvm_stats_desc kvm_vcpu_stats_desc[] = {
@@ -589,6 +590,7 @@ void kvm_arch_vcpu_load(struct kvm_vcpu *vcpu, int cpu)
{
void *nsh;
struct kvm_vcpu_csr *csr = &vcpu->arch.guest_csr;
+ bool vector_owner_hit;
/*
* If VCPU is being reloaded on the same physical CPU and no
@@ -647,9 +649,11 @@ void kvm_arch_vcpu_load(struct kvm_vcpu *vcpu, int cpu)
kvm_riscv_vcpu_guest_fp_restore(&vcpu->arch.guest_context,
vcpu->arch.isa);
get_cpu_vector_context();
- kvm_riscv_vcpu_host_vector_save(&vcpu->arch.host_context);
+ vector_owner_hit = kvm_riscv_vcpu_vector_owner_load(vcpu, cpu);
+ if (!vector_owner_hit)
+ kvm_riscv_vcpu_host_vector_save(&vcpu->arch.host_context);
kvm_riscv_vcpu_guest_vector_restore(&vcpu->arch.guest_context,
- vcpu->arch.isa);
+ vcpu->arch.isa, vector_owner_hit);
put_cpu_vector_context();
kvm_make_request(KVM_REQ_STEAL_UPDATE, vcpu);
@@ -661,6 +665,7 @@ void kvm_arch_vcpu_put(struct kvm_vcpu *vcpu)
{
void *nsh;
struct kvm_vcpu_csr *csr = &vcpu->arch.guest_csr;
+ bool preserve_guest;
vcpu->cpu = -1;
@@ -674,7 +679,9 @@ void kvm_arch_vcpu_put(struct kvm_vcpu *vcpu)
get_cpu_vector_context();
kvm_riscv_vcpu_guest_vector_save(&vcpu->arch.guest_context,
vcpu->arch.isa);
- kvm_riscv_vcpu_host_vector_restore(&vcpu->arch.host_context);
+ preserve_guest = kvm_riscv_vcpu_vector_owner_put(vcpu);
+ kvm_riscv_vcpu_host_vector_restore(&vcpu->arch.host_context,
+ preserve_guest);
put_cpu_vector_context();
if (kvm_riscv_nacl_available()) {
@@ -793,6 +800,12 @@ static __always_inline void kvm_riscv_vcpu_swap_in_host_state(struct kvm_vcpu *v
zicficsr->ssp = csr_swap(CSR_SSP, 0);
}
+enum kvm_riscv_vector_restore_type {
+ KVM_RISCV_VECTOR_RESTORE_NONE,
+ KVM_RISCV_VECTOR_RESTORE_FULL,
+ KVM_RISCV_VECTOR_RESTORE_CSR,
+};
+
/*
* Actually run the vCPU, entering an RCU extended quiescent state (EQS) while
* the vCPU is running.
@@ -800,12 +813,14 @@ static __always_inline void kvm_riscv_vcpu_swap_in_host_state(struct kvm_vcpu *v
* This must be noinstr as instrumentation may make use of RCU, and this is not
* safe during the EQS.
*/
-static void noinstr kvm_riscv_vcpu_enter_exit(struct kvm_vcpu *vcpu,
- struct kvm_cpu_trap *trap)
+static int noinstr kvm_riscv_vcpu_enter_exit(struct kvm_vcpu *vcpu,
+ struct kvm_cpu_trap *trap)
{
void *nsh;
struct kvm_cpu_context *gcntx = &vcpu->arch.guest_context;
struct kvm_cpu_context *hcntx = &vcpu->arch.host_context;
+ int vector_restore = KVM_RISCV_VECTOR_RESTORE_NONE;
+ u32 vector_flags;
/*
* We save trap CSRs (such as SEPC, SCAUSE, STVAL, HTVAL, and
@@ -818,11 +833,21 @@ static void noinstr kvm_riscv_vcpu_enter_exit(struct kvm_vcpu *vcpu,
kvm_riscv_vcpu_swap_in_guest_state(vcpu);
guest_state_enter_irqoff();
- /* sstatus.VS != SR_VS_OFF is guaranteed when NEED_RESTORE is set */
- if (current->thread.riscv_v_flags & RISCV_V_VCPU_NEED_RESTORE) {
- current->thread.riscv_v_flags &= ~RISCV_V_VCPU_NEED_RESTORE;
+ vector_flags = current->thread.riscv_v_flags;
+ /* sstatus.VS != SR_VS_OFF is guaranteed for either restore request. */
+ if (vector_flags & (RISCV_V_VCPU_NEED_RESTORE |
+ RISCV_V_VCPU_CSR_RESTORE)) {
+ current->thread.riscv_v_flags &=
+ ~(RISCV_V_VCPU_NEED_RESTORE |
+ RISCV_V_VCPU_CSR_RESTORE);
current->thread.riscv_v_flags |= RISCV_V_VCPU_CTX;
- __kvm_riscv_vector_restore(gcntx);
+ if (vector_flags & RISCV_V_VCPU_NEED_RESTORE) {
+ __kvm_riscv_vector_restore(gcntx);
+ vector_restore = KVM_RISCV_VECTOR_RESTORE_FULL;
+ } else {
+ __kvm_riscv_vector_restore_csrs(gcntx);
+ vector_restore = KVM_RISCV_VECTOR_RESTORE_CSR;
+ }
gcntx->sstatus = (gcntx->sstatus & ~SR_VS) | SR_VS_CLEAN;
}
@@ -886,6 +911,8 @@ static void noinstr kvm_riscv_vcpu_enter_exit(struct kvm_vcpu *vcpu,
vcpu->arch.last_exit_cpu = vcpu->cpu;
guest_state_exit_irqoff();
kvm_riscv_vcpu_swap_in_host_state(vcpu);
+
+ return vector_restore;
}
int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)
@@ -893,6 +920,7 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)
int ret;
struct kvm_cpu_trap trap;
struct kvm_run *run = vcpu->run;
+ int vector_restore;
if (!vcpu->arch.ran_atleast_once)
kvm_riscv_vcpu_config_ran_once(vcpu);
@@ -1001,7 +1029,7 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)
guest_timing_enter_irqoff();
- kvm_riscv_vcpu_enter_exit(vcpu, &trap);
+ vector_restore = kvm_riscv_vcpu_enter_exit(vcpu, &trap);
vcpu->mode = OUTSIDE_GUEST_MODE;
vcpu->stat.exits++;
@@ -1026,6 +1054,12 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)
local_irq_enable();
+ if (vector_restore == KVM_RISCV_VECTOR_RESTORE_FULL)
+ trace_kvm_riscv_vector_restore(vcpu->vcpu_id,
+ smp_processor_id(), false);
+ else if (vector_restore == KVM_RISCV_VECTOR_RESTORE_CSR)
+ trace_kvm_riscv_vector_restore(vcpu->vcpu_id,
+ smp_processor_id(), true);
trace_kvm_exit(&trap);
preempt_enable();
diff --git a/arch/riscv/kvm/vcpu_vector.c b/arch/riscv/kvm/vcpu_vector.c
index fefc2857f753..c197d3d7b539 100644
--- a/arch/riscv/kvm/vcpu_vector.c
+++ b/arch/riscv/kvm/vcpu_vector.c
@@ -7,9 +7,11 @@
* Greentime Hu <greentime.hu@sifive.com>
*/
+#include <linux/atomic.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/kvm_host.h>
+#include <linux/moduleparam.h>
#include <linux/nospec.h>
#include <linux/uaccess.h>
#include <asm/cpufeature.h>
@@ -17,7 +19,50 @@
#include <asm/kvm_vcpu_vector.h>
#include <asm/vector.h>
+#include "trace.h"
+
#ifdef CONFIG_RISCV_ISA_V
+enum kvm_riscv_vector_owner_reason {
+ KVM_RISCV_VECTOR_OWNER_HIT,
+ KVM_RISCV_VECTOR_OWNER_EMPTY,
+ KVM_RISCV_VECTOR_OWNER_OTHER_VCPU,
+ KVM_RISCV_VECTOR_OWNER_COOKIE,
+ KVM_RISCV_VECTOR_OWNER_GENERATION,
+ KVM_RISCV_VECTOR_OWNER_GUEST_OFF,
+ KVM_RISCV_VECTOR_OWNER_HOST_ON,
+ KVM_RISCV_VECTOR_OWNER_MIGRATION,
+ KVM_RISCV_VECTOR_OWNER_PUT_CANDIDATE,
+ KVM_RISCV_VECTOR_OWNER_TASK_SAVE,
+ KVM_RISCV_VECTOR_OWNER_TASK_RESTORE,
+ KVM_RISCV_VECTOR_OWNER_TASK_DISCARD,
+ KVM_RISCV_VECTOR_OWNER_KERNEL_VECTOR,
+ KVM_RISCV_VECTOR_OWNER_CPU_STATE,
+ KVM_RISCV_VECTOR_OWNER_NOT_RESIDENT,
+};
+
+struct kvm_riscv_vector_owner {
+ /* Opaque comparison token; cookie rejects address reuse after destroy. */
+ unsigned long identity;
+ u64 cookie;
+ u64 generation;
+ unsigned long vcpu_id;
+ unsigned long guest_vs;
+};
+
+static DEFINE_PER_CPU(struct kvm_riscv_vector_owner,
+ kvm_riscv_vector_owner);
+static atomic64_t kvm_riscv_vector_owner_cookie = ATOMIC64_INIT(0);
+static bool kvm_riscv_vector_owner_fastpath;
+module_param_named(vector_owner_fastpath, kvm_riscv_vector_owner_fastpath,
+ bool, 0444);
+MODULE_PARM_DESC(vector_owner_fastpath,
+ "Preserve matched guest Vector registers across vCPU put/load");
+
+bool kvm_riscv_vcpu_vector_owner_enabled(void)
+{
+ return READ_ONCE(kvm_riscv_vector_owner_fastpath);
+}
+
void kvm_riscv_vcpu_vector_reset(struct kvm_vcpu *vcpu)
{
unsigned long *isa = vcpu->arch.isa;
@@ -34,6 +79,8 @@ void kvm_riscv_vcpu_vector_reset(struct kvm_vcpu *vcpu)
} else {
cntx->sstatus |= SR_VS_OFF;
}
+
+ vcpu->arch.vector_state_generation++;
}
static void kvm_riscv_vcpu_vector_clean(struct kvm_cpu_context *cntx)
@@ -46,18 +93,35 @@ void kvm_riscv_vcpu_guest_vector_save(struct kvm_cpu_context *cntx,
unsigned long *isa)
{
if ((cntx->sstatus & SR_VS) == SR_VS_DIRTY) {
- if (riscv_isa_extension_available(isa, V))
+ if (riscv_isa_extension_available(isa, V)) {
+ struct kvm_vcpu *vcpu = container_of(cntx, struct kvm_vcpu,
+ arch.guest_context);
+
__kvm_riscv_vector_save(cntx);
+ vcpu->arch.vector_state_generation++;
+ }
kvm_riscv_vcpu_vector_clean(cntx);
}
}
void kvm_riscv_vcpu_guest_vector_restore(struct kvm_cpu_context *cntx,
- unsigned long *isa)
+ unsigned long *isa, bool csr_only)
{
if ((cntx->sstatus & SR_VS) != SR_VS_OFF) {
- if (riscv_isa_extension_available(isa, V))
- riscv_v_flags_set(riscv_v_flags() | RISCV_V_VCPU_NEED_RESTORE);
+ if (riscv_isa_extension_available(isa, V)) {
+ struct kvm_vcpu *vcpu = container_of(cntx, struct kvm_vcpu,
+ arch.guest_context);
+ unsigned long host_vs = task_pt_regs(current)->status & SR_VS;
+ u32 flags = riscv_v_flags();
+
+ flags &= ~(RISCV_V_VCPU_NEED_RESTORE |
+ RISCV_V_VCPU_CSR_RESTORE);
+ flags |= csr_only ? RISCV_V_VCPU_CSR_RESTORE :
+ RISCV_V_VCPU_NEED_RESTORE;
+ riscv_v_flags_set(flags);
+ trace_kvm_riscv_vector_restore_request(vcpu->vcpu_id,
+ smp_processor_id(), 0, host_vs);
+ }
}
}
@@ -68,11 +132,167 @@ void kvm_riscv_vcpu_host_vector_save(struct kvm_cpu_context *cntx)
__kvm_riscv_vector_save(cntx);
}
-void kvm_riscv_vcpu_host_vector_restore(struct kvm_cpu_context *cntx)
+void kvm_riscv_vcpu_host_vector_restore(struct kvm_cpu_context *cntx,
+ bool preserve_guest)
{
- if (!kvm_riscv_isa_check_host(V))
+ if (!preserve_guest && !kvm_riscv_isa_check_host(V))
__kvm_riscv_vector_restore(cntx);
- riscv_v_flags_set(riscv_v_flags() & ~(RISCV_V_VCPU_CTX | RISCV_V_VCPU_NEED_RESTORE));
+ riscv_v_flags_set(riscv_v_flags() &
+ ~(RISCV_V_VCPU_CTX | RISCV_V_VCPU_NEED_RESTORE |
+ RISCV_V_VCPU_CSR_RESTORE));
+}
+
+bool kvm_riscv_vcpu_vector_owner_load(struct kvm_vcpu *vcpu, int cpu)
+{
+ struct kvm_riscv_vector_owner *owner;
+ unsigned long guest_vs = vcpu->arch.guest_context.sstatus & SR_VS;
+ unsigned long host_vs = task_pt_regs(current)->status & SR_VS;
+ enum kvm_riscv_vector_owner_reason reason;
+ bool eligible = false;
+
+ if (!kvm_riscv_vcpu_vector_owner_enabled())
+ return false;
+
+ owner = this_cpu_ptr(&kvm_riscv_vector_owner);
+ if (guest_vs == SR_VS_OFF) {
+ reason = KVM_RISCV_VECTOR_OWNER_GUEST_OFF;
+ } else if (host_vs != SR_VS_OFF) {
+ reason = KVM_RISCV_VECTOR_OWNER_HOST_ON;
+ } else if (!owner->identity) {
+ reason = vcpu->arch.last_exit_cpu == cpu ?
+ KVM_RISCV_VECTOR_OWNER_EMPTY :
+ KVM_RISCV_VECTOR_OWNER_MIGRATION;
+ } else if (owner->identity != (unsigned long)vcpu) {
+ reason = KVM_RISCV_VECTOR_OWNER_OTHER_VCPU;
+ } else if (owner->cookie != vcpu->arch.vector_owner_cookie) {
+ reason = KVM_RISCV_VECTOR_OWNER_COOKIE;
+ } else if (owner->generation != vcpu->arch.vector_state_generation) {
+ reason = KVM_RISCV_VECTOR_OWNER_GENERATION;
+ } else {
+ reason = KVM_RISCV_VECTOR_OWNER_HIT;
+ eligible = true;
+ }
+
+ trace_kvm_riscv_vector_owner(vcpu->vcpu_id, cpu, 0, eligible,
+ reason, host_vs, guest_vs,
+ vcpu->arch.vector_state_generation);
+
+ /* A load consumes the cached owner; the next put publishes it. */
+ owner->identity = 0;
+ owner->cookie = 0;
+ owner->generation = 0;
+ owner->vcpu_id = 0;
+ owner->guest_vs = 0;
+
+ return eligible;
+}
+
+bool kvm_riscv_vcpu_vector_owner_put(struct kvm_vcpu *vcpu)
+{
+ struct kvm_riscv_vector_owner *owner;
+ unsigned long guest_vs = vcpu->arch.guest_context.sstatus & SR_VS;
+ unsigned long host_vs = task_pt_regs(current)->status & SR_VS;
+ u32 flags = riscv_v_flags();
+ enum kvm_riscv_vector_owner_reason reason;
+ bool eligible;
+
+ if (!kvm_riscv_vcpu_vector_owner_enabled())
+ return false;
+
+ owner = this_cpu_ptr(&kvm_riscv_vector_owner);
+ eligible = guest_vs != SR_VS_OFF && host_vs == SR_VS_OFF &&
+ (flags & RISCV_V_VCPU_CTX) &&
+ !(flags & (RISCV_V_VCPU_NEED_RESTORE |
+ RISCV_V_VCPU_CSR_RESTORE));
+ if (eligible) {
+ owner->identity = (unsigned long)vcpu;
+ owner->cookie = vcpu->arch.vector_owner_cookie;
+ owner->generation = vcpu->arch.vector_state_generation;
+ owner->vcpu_id = vcpu->vcpu_id;
+ owner->guest_vs = guest_vs;
+ reason = KVM_RISCV_VECTOR_OWNER_PUT_CANDIDATE;
+ } else {
+ owner->identity = 0;
+ owner->cookie = 0;
+ owner->generation = 0;
+ owner->vcpu_id = 0;
+ owner->guest_vs = 0;
+ if (guest_vs == SR_VS_OFF)
+ reason = KVM_RISCV_VECTOR_OWNER_GUEST_OFF;
+ else if (host_vs != SR_VS_OFF)
+ reason = KVM_RISCV_VECTOR_OWNER_HOST_ON;
+ else
+ reason = KVM_RISCV_VECTOR_OWNER_NOT_RESIDENT;
+ }
+
+ trace_kvm_riscv_vector_owner(vcpu->vcpu_id, smp_processor_id(), 1,
+ eligible, reason, host_vs, guest_vs,
+ vcpu->arch.vector_state_generation);
+
+ return eligible;
+}
+
+void kvm_riscv_vcpu_vector_owner_invalidate(enum riscv_v_invalidate_reason reason)
+{
+ struct kvm_riscv_vector_owner *owner;
+ struct kvm_vcpu *vcpu;
+ enum kvm_riscv_vector_owner_reason trace_reason;
+ unsigned long host_vs;
+ u32 flags;
+
+ switch (reason) {
+ case RISCV_V_INVALIDATE_TASK_SAVE:
+ trace_reason = KVM_RISCV_VECTOR_OWNER_TASK_SAVE;
+ break;
+ case RISCV_V_INVALIDATE_TASK_RESTORE:
+ trace_reason = KVM_RISCV_VECTOR_OWNER_TASK_RESTORE;
+ break;
+ case RISCV_V_INVALIDATE_TASK_DISCARD:
+ trace_reason = KVM_RISCV_VECTOR_OWNER_TASK_DISCARD;
+ break;
+ case RISCV_V_INVALIDATE_KERNEL_VECTOR:
+ trace_reason = KVM_RISCV_VECTOR_OWNER_KERNEL_VECTOR;
+ break;
+ case RISCV_V_INVALIDATE_CPU_STATE:
+ trace_reason = KVM_RISCV_VECTOR_OWNER_CPU_STATE;
+ break;
+ default:
+ return;
+ }
+
+ preempt_disable();
+ flags = riscv_v_flags();
+ if (flags & RISCV_V_VCPU_CSR_RESTORE) {
+ vcpu = *this_cpu_ptr(kvm_get_running_vcpus());
+ if (!WARN_ON_ONCE(!vcpu)) {
+ flags &= ~RISCV_V_VCPU_CSR_RESTORE;
+ flags |= RISCV_V_VCPU_NEED_RESTORE;
+ riscv_v_flags_set(flags);
+ host_vs = task_pt_regs(current)->status & SR_VS;
+ trace_kvm_riscv_vector_owner(vcpu->vcpu_id,
+ smp_processor_id(), 2, false,
+ trace_reason, host_vs,
+ vcpu->arch.guest_context.sstatus & SR_VS,
+ vcpu->arch.vector_state_generation);
+ }
+ }
+
+ owner = this_cpu_ptr(&kvm_riscv_vector_owner);
+ if (!owner->identity)
+ goto out;
+
+ host_vs = task_pt_regs(current)->status & SR_VS;
+ trace_kvm_riscv_vector_owner(owner->vcpu_id, smp_processor_id(), 2,
+ false, trace_reason, host_vs,
+ owner->guest_vs, owner->generation);
+ owner->identity = 0;
+ owner->cookie = 0;
+ owner->generation = 0;
+ owner->vcpu_id = 0;
+ owner->guest_vs = 0;
+
+out:
+ preempt_enable();
}
int kvm_riscv_vcpu_alloc_vector_context(struct kvm_vcpu *vcpu)
@@ -88,6 +308,10 @@ int kvm_riscv_vcpu_alloc_vector_context(struct kvm_vcpu *vcpu)
return -ENOMEM;
}
+ vcpu->arch.vector_owner_cookie =
+ atomic64_inc_return(&kvm_riscv_vector_owner_cookie);
+ vcpu->arch.vector_state_generation = 0;
+
return 0;
}
@@ -110,8 +334,13 @@ void kvm_riscv_vcpu_flush_vector(void)
kvm_riscv_vcpu_guest_vector_save(&vcpu->arch.guest_context, vcpu->arch.isa);
- if ((vcpu->arch.guest_context.sstatus & SR_VS) != SR_VS_OFF)
+ if ((vcpu->arch.guest_context.sstatus & SR_VS) != SR_VS_OFF) {
+ unsigned long host_vs = task_pt_regs(current)->status & SR_VS;
+
riscv_v_flags_set(riscv_v_flags() | RISCV_V_VCPU_NEED_RESTORE);
+ trace_kvm_riscv_vector_restore_request(vcpu->vcpu_id,
+ smp_processor_id(), 1, host_vs);
+ }
}
#endif
@@ -229,6 +458,13 @@ int kvm_riscv_vcpu_set_reg_vector(struct kvm_vcpu *vcpu,
if (rc)
return rc;
+ /*
+ * copy_from_user() can modify a prefix of the destination before it
+ * reports a fault. Invalidate any cached hardware copy first so a
+ * failed SET_ONE_REG cannot leave newer memory hidden behind a stale
+ * per-hart owner.
+ */
+ vcpu->arch.vector_state_generation++;
if (copy_from_user(reg_addr, uaddr, reg_size))
return -EFAULT;
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [RFC PATCH 3/3] KVM: riscv: selftests: Add Vector owner regression tests
2026-10-27 13:13 [RFC PATCH 0/3] RISC-V: KVM: Preserve resident guest Vector state slack up
2026-10-27 13:13 ` [RFC PATCH 1/3] riscv: vector: Add a guest owner invalidation hook slack up
2026-10-27 13:13 ` [RFC PATCH 2/3] RISC-V: KVM: Preserve guest Vector state across vCPU put/load slack up
@ 2026-10-27 13:13 ` slack up
2026-10-27 14:03 ` sashiko-bot
2 siblings, 1 reply; 7+ messages in thread
From: slack up @ 2026-10-27 13:13 UTC (permalink / raw)
To: Anup Patel, Atish Patra, Palmer Dabbelt, kvm-riscv, linux-riscv
Cc: Paul Walmsley, Albert Ou, Alexandre Ghiti, Paolo Bonzini,
Sean Christopherson, Shuah Khan, kvm, linux-kernel,
linux-kselftest, Xiaochun Yu
From: Xiaochun Yu <yxcnew@outlook.com>
Add focused tests for the correctness boundaries of a resident guest
Vector owner.
The lifetime test destroys a vCPU with resident Vector data before
creating a new vCPU and verifies that the new vCPU cannot inherit it. The
ONE_REG test checks that GET preserves guest data and SET becomes visible
on the next entry. The fault test crosses into a protected page to verify
that a partially completed SET_ONE_REG copy cannot leave stale hardware
state visible to the guest.
Pin each test to one host CPU so the same-hart owner path is deterministic
when the optional optimization is enabled. Size the Vector buffers for
the architectural maximum VLEN so the tests are not limited to the VLEN
implemented by the development platform.
Development note: OpenAI Codex assisted with source analysis, selftest
drafting, test orchestration and changelog preparation.
Signed-off-by: Xiaochun Yu <yxcnew@outlook.com>
---
tools/testing/selftests/kvm/Makefile.kvm | 3 +
.../kvm/riscv/vector_owner_fault_test.c | 147 ++++++++++++++
.../kvm/riscv/vector_owner_lifetime_test.c | 127 ++++++++++++
.../kvm/riscv/vector_owner_onereg_test.c | 191 ++++++++++++++++++
4 files changed, 468 insertions(+)
create mode 100644 tools/testing/selftests/kvm/riscv/vector_owner_fault_test.c
create mode 100644 tools/testing/selftests/kvm/riscv/vector_owner_lifetime_test.c
create mode 100644 tools/testing/selftests/kvm/riscv/vector_owner_onereg_test.c
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 3cb35a6963d5..50a1f247ebc3 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -236,6 +236,9 @@ TEST_GEN_PROGS_s390 += s390/irq_injection
TEST_GEN_PROGS_riscv = $(TEST_GEN_PROGS_COMMON)
TEST_GEN_PROGS_riscv += riscv/sbi_pmu_test
TEST_GEN_PROGS_riscv += riscv/ebreak_test
+TEST_GEN_PROGS_riscv += riscv/vector_owner_lifetime_test
+TEST_GEN_PROGS_riscv += riscv/vector_owner_onereg_test
+TEST_GEN_PROGS_riscv += riscv/vector_owner_fault_test
TEST_GEN_PROGS_riscv += access_tracking_perf_test
TEST_GEN_PROGS_riscv += arch_timer
TEST_GEN_PROGS_riscv += coalesced_io_test
diff --git a/tools/testing/selftests/kvm/riscv/vector_owner_fault_test.c b/tools/testing/selftests/kvm/riscv/vector_owner_fault_test.c
new file mode 100644
index 000000000000..a25dd21db77f
--- /dev/null
+++ b/tools/testing/selftests/kvm/riscv/vector_owner_fault_test.c
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Check that failed Vector SET_ONE_REG copies cannot leave a stale owner. */
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include <asm/ptrace.h>
+
+#include "kvm_syscalls.h"
+#include "kvm_util.h"
+#include "processor.h"
+#include "ucall_common.h"
+
+#define OLD_PATTERN 0x31
+#define NEW_PATTERN 0xa7
+
+static unsigned char source[RISCV_MAX_VLENB] __aligned(64);
+static unsigned char observed[RISCV_MAX_VLENB] __aligned(64);
+
+static void guest_code(void)
+{
+ unsigned long i, vlenb;
+
+ csr_set(CSR_SSTATUS, SR_VS_INITIAL);
+ vlenb = csr_read(CSR_VLENB);
+ GUEST_ASSERT(vlenb && vlenb <= RISCV_MAX_VLENB);
+ for (i = 0; i < vlenb; i++)
+ source[i] = OLD_PATTERN;
+
+ asm volatile(".option push\n"
+ ".option arch, +v\n"
+ "vsetvli zero, %1, e8, m1, ta, ma\n"
+ "vle8.v v0, (%0)\n"
+ ".option pop\n"
+ :
+ : "r"(source), "r"(vlenb)
+ : "memory");
+ GUEST_SYNC(1);
+
+ asm volatile(".option push\n"
+ ".option arch, +v\n"
+ "vsetvli zero, %1, e8, m1, ta, ma\n"
+ "vse8.v v0, (%0)\n"
+ ".option pop\n"
+ :
+ : "r"(observed), "r"(vlenb)
+ : "memory");
+ GUEST_DONE();
+}
+
+static void pin_to_one_cpu(void)
+{
+ cpu_set_t allowed, pinned;
+ int cpu;
+
+ kvm_sched_getaffinity(0, sizeof(allowed), &allowed);
+ for (cpu = 0; cpu < CPU_SETSIZE; cpu++) {
+ if (CPU_ISSET(cpu, &allowed))
+ break;
+ }
+ TEST_ASSERT(cpu < CPU_SETSIZE, "no CPU available");
+ CPU_ZERO(&pinned);
+ CPU_SET(cpu, &pinned);
+ kvm_sched_setaffinity(0, sizeof(pinned), &pinned);
+}
+
+static void run_to(struct kvm_vcpu *vcpu, u64 expected)
+{
+ struct ucall uc;
+ u64 cmd;
+
+ vcpu_run(vcpu);
+ cmd = get_ucall(vcpu, &uc);
+ if (cmd == UCALL_ABORT)
+ REPORT_GUEST_ASSERT(uc);
+ TEST_ASSERT_EQ(cmd, expected);
+ if (expected == UCALL_SYNC)
+ TEST_ASSERT_EQ(uc.args[1], 1);
+}
+
+int main(void)
+{
+ unsigned char expected[RISCV_MAX_VLENB] __aligned(64) = {};
+ struct kvm_vm *vm;
+ struct kvm_vcpu *vcpu;
+ struct kvm_one_reg reg;
+ unsigned char *mapping;
+ unsigned long i, vlenb;
+ u64 vlenb_id, v0_id, size;
+ long page_size;
+ bool changed = false;
+ int ret, saved_errno;
+
+ pin_to_one_cpu();
+ vm = vm_create_with_one_vcpu(&vcpu, guest_code);
+ TEST_REQUIRE(__vcpu_has_isa_ext(vcpu, KVM_RISCV_ISA_EXT_V));
+ vm_init_vector_tables(vm);
+ vcpu_init_vector_tables(vcpu);
+ vlenb_id = KVM_REG_RISCV | KVM_REG_RISCV_VECTOR |
+ KVM_REG_SIZE_ULONG | KVM_REG_RISCV_VECTOR_CSR_REG(vlenb);
+ ret = __vcpu_get_reg(vcpu, vlenb_id, &vlenb);
+ TEST_ASSERT(!ret, "GET vlenb failed: %d", ret);
+ TEST_ASSERT(vlenb && vlenb <= RISCV_MAX_VLENB &&
+ !(vlenb & (vlenb - 1)), "bad vlenb: %lu", vlenb);
+ size = (u64)__builtin_ctzl(vlenb) << KVM_REG_SIZE_SHIFT;
+ v0_id = KVM_REG_RISCV | KVM_REG_RISCV_VECTOR | size |
+ KVM_REG_RISCV_VECTOR_REG(0);
+
+ page_size = sysconf(_SC_PAGESIZE);
+ TEST_ASSERT(page_size > 0 && vlenb <= 2 * page_size, "bad page size");
+ mapping = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ TEST_ASSERT(mapping != MAP_FAILED, "mmap failed: %d", errno);
+ memset(mapping, NEW_PATTERN, page_size);
+ ret = mprotect(mapping + page_size, page_size, PROT_NONE);
+ TEST_ASSERT(!ret, "mprotect failed: %d", errno);
+ reg.id = v0_id;
+ reg.addr = (u64)(mapping + page_size - vlenb / 2);
+
+ run_to(vcpu, UCALL_SYNC);
+ ret = __vcpu_ioctl(vcpu, KVM_SET_ONE_REG, ®);
+ saved_errno = errno;
+ TEST_ASSERT(ret == -1 && saved_errno == EFAULT,
+ "SET should fault: ret=%d errno=%d", ret, saved_errno);
+ ret = __vcpu_get_reg(vcpu, v0_id, expected);
+ TEST_ASSERT(!ret, "GET v0 after failed SET: %d", ret);
+
+ /* Compare with memory, without assuming an exact partial-copy length. */
+ run_to(vcpu, UCALL_DONE);
+ sync_global_from_guest(vm, observed);
+ for (i = 0; i < vlenb; i++)
+ changed |= expected[i] != OLD_PATTERN;
+ TEST_ASSERT(changed, "failed copy did not exercise a memory update");
+ for (i = 0; i < vlenb; i++)
+ TEST_ASSERT(observed[i] == expected[i],
+ "partial fault byte %lu: guest=%#x memory=%#x",
+ i, observed[i], expected[i]);
+
+ pr_info("Vector SET_ONE_REG partial fault test passed (vlenb=%lu)\n",
+ vlenb);
+ kvm_vm_free(vm);
+ ret = munmap(mapping, 2 * page_size);
+ TEST_ASSERT(!ret, "munmap failed: %d", errno);
+ return 0;
+}
diff --git a/tools/testing/selftests/kvm/riscv/vector_owner_lifetime_test.c b/tools/testing/selftests/kvm/riscv/vector_owner_lifetime_test.c
new file mode 100644
index 000000000000..c7b057b27cd0
--- /dev/null
+++ b/tools/testing/selftests/kvm/riscv/vector_owner_lifetime_test.c
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Validate that a stale per-hart Vector owner from a destroyed vCPU cannot
+ * be treated as the owner of a different vCPU.
+ */
+#include <stdio.h>
+
+#include <asm/ptrace.h>
+
+#include "kvm_syscalls.h"
+#include "kvm_util.h"
+#include "processor.h"
+#include "ucall_common.h"
+
+#define OLD_PATTERN 0x5d
+
+static unsigned char old_source[RISCV_MAX_VLENB] __aligned(64);
+static unsigned char new_observed[RISCV_MAX_VLENB] __aligned(64);
+
+static void vector_load(const void *source, unsigned long vlenb)
+{
+ asm volatile(".option push\n"
+ ".option arch, +v\n"
+ "vsetvli zero, %1, e8, m1, ta, ma\n"
+ "vle8.v v0, (%0)\n"
+ ".option pop\n"
+ :
+ : "r"(source), "r"(vlenb)
+ : "memory");
+}
+
+static void vector_store(void *destination, unsigned long vlenb)
+{
+ asm volatile(".option push\n"
+ ".option arch, +v\n"
+ "vsetvli zero, %1, e8, m1, ta, ma\n"
+ "vse8.v v0, (%0)\n"
+ ".option pop\n"
+ :
+ : "r"(destination), "r"(vlenb)
+ : "memory");
+}
+
+static void old_guest(void)
+{
+ unsigned long i, vlenb;
+
+ csr_set(CSR_SSTATUS, SR_VS_INITIAL);
+ vlenb = csr_read(CSR_VLENB);
+ GUEST_ASSERT(vlenb && vlenb <= RISCV_MAX_VLENB);
+ for (i = 0; i < vlenb; i++)
+ old_source[i] = OLD_PATTERN;
+ vector_load(old_source, vlenb);
+ GUEST_DONE();
+}
+
+static void new_guest(void)
+{
+ unsigned long i, vlenb;
+
+ csr_set(CSR_SSTATUS, SR_VS_INITIAL);
+ vlenb = csr_read(CSR_VLENB);
+ GUEST_ASSERT(vlenb && vlenb <= RISCV_MAX_VLENB);
+ vector_store(new_observed, vlenb);
+ for (i = 0; i < vlenb; i++)
+ GUEST_ASSERT_EQ(new_observed[i], 0);
+ GUEST_DONE();
+}
+
+static void pin_to_one_cpu(void)
+{
+ cpu_set_t allowed, pinned;
+ int cpu;
+
+ kvm_sched_getaffinity(0, sizeof(allowed), &allowed);
+ for (cpu = 0; cpu < CPU_SETSIZE; cpu++) {
+ if (CPU_ISSET(cpu, &allowed))
+ break;
+ }
+ TEST_ASSERT(cpu < CPU_SETSIZE, "no CPU available");
+
+ CPU_ZERO(&pinned);
+ CPU_SET(cpu, &pinned);
+ kvm_sched_setaffinity(0, sizeof(pinned), &pinned);
+}
+
+static void run_to_done(struct kvm_vcpu *vcpu)
+{
+ struct ucall uc;
+ u64 cmd;
+
+ vcpu_run(vcpu);
+ cmd = get_ucall(vcpu, &uc);
+ if (cmd == UCALL_ABORT)
+ REPORT_GUEST_ASSERT(uc);
+ TEST_ASSERT_EQ(cmd, UCALL_DONE);
+}
+
+static struct kvm_vm *create_vector_vm(struct kvm_vcpu **vcpu,
+ void (*guest_code)(void))
+{
+ struct kvm_vm *vm;
+
+ vm = vm_create_with_one_vcpu(vcpu, guest_code);
+ TEST_REQUIRE(__vcpu_has_isa_ext(*vcpu, KVM_RISCV_ISA_EXT_V));
+ vm_init_vector_tables(vm);
+ vcpu_init_vector_tables(*vcpu);
+ return vm;
+}
+
+int main(void)
+{
+ struct kvm_vm *old_vm, *new_vm;
+ struct kvm_vcpu *old_vcpu, *new_vcpu;
+
+ pin_to_one_cpu();
+ old_vm = create_vector_vm(&old_vcpu, old_guest);
+ run_to_done(old_vcpu);
+ kvm_vm_free(old_vm);
+
+ new_vm = create_vector_vm(&new_vcpu, new_guest);
+ run_to_done(new_vcpu);
+ kvm_vm_free(new_vm);
+
+ pr_info("Vector owner lifetime test passed\n");
+ return 0;
+}
diff --git a/tools/testing/selftests/kvm/riscv/vector_owner_onereg_test.c b/tools/testing/selftests/kvm/riscv/vector_owner_onereg_test.c
new file mode 100644
index 000000000000..a1bd83e4808b
--- /dev/null
+++ b/tools/testing/selftests/kvm/riscv/vector_owner_onereg_test.c
@@ -0,0 +1,191 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Validate RISC-V KVM Vector KVM_GET/SET_ONE_REG interactions while the
+ * vCPU repeatedly runs on one physical CPU.
+ */
+#include <stdio.h>
+#include <string.h>
+
+#include <asm/ptrace.h>
+
+#include "kvm_syscalls.h"
+#include "kvm_util.h"
+#include "processor.h"
+#include "ucall_common.h"
+
+#define GUEST_PATTERN 0x31
+#define SET_PATTERN 0xa7
+
+static unsigned char guest_source[RISCV_MAX_VLENB] __aligned(64);
+static unsigned char guest_after_get[RISCV_MAX_VLENB] __aligned(64);
+static unsigned char guest_after_set[RISCV_MAX_VLENB] __aligned(64);
+
+static void guest_vector_load(const void *source, unsigned long vlenb)
+{
+ asm volatile(".option push\n"
+ ".option arch, +v\n"
+ "vsetvli zero, %1, e8, m1, ta, ma\n"
+ "vle8.v v0, (%0)\n"
+ ".option pop\n"
+ :
+ : "r"(source), "r"(vlenb)
+ : "memory");
+}
+
+static void guest_vector_store(void *destination, unsigned long vlenb)
+{
+ asm volatile(".option push\n"
+ ".option arch, +v\n"
+ "vsetvli zero, %1, e8, m1, ta, ma\n"
+ "vse8.v v0, (%0)\n"
+ ".option pop\n"
+ :
+ : "r"(destination), "r"(vlenb)
+ : "memory");
+}
+
+static void guest_expect_pattern(const unsigned char *data,
+ unsigned long vlenb, unsigned char pattern)
+{
+ unsigned long i;
+
+ for (i = 0; i < vlenb; i++)
+ GUEST_ASSERT_EQ(data[i], pattern);
+}
+
+static void guest_code(void)
+{
+ unsigned long i, vlenb;
+
+ csr_set(CSR_SSTATUS, SR_VS_INITIAL);
+ vlenb = csr_read(CSR_VLENB);
+ GUEST_ASSERT(vlenb && vlenb <= RISCV_MAX_VLENB);
+
+ for (i = 0; i < vlenb; i++)
+ guest_source[i] = GUEST_PATTERN;
+
+ guest_vector_load(guest_source, vlenb);
+ GUEST_SYNC(1);
+
+ guest_vector_store(guest_after_get, vlenb);
+ guest_expect_pattern(guest_after_get, vlenb, GUEST_PATTERN);
+ GUEST_SYNC(2);
+
+ guest_vector_store(guest_after_set, vlenb);
+ guest_expect_pattern(guest_after_set, vlenb, SET_PATTERN);
+ GUEST_DONE();
+}
+
+static void pin_to_one_cpu(void)
+{
+ cpu_set_t allowed, pinned;
+ int cpu;
+
+ kvm_sched_getaffinity(0, sizeof(allowed), &allowed);
+ for (cpu = 0; cpu < CPU_SETSIZE; cpu++) {
+ if (CPU_ISSET(cpu, &allowed))
+ break;
+ }
+ TEST_ASSERT(cpu < CPU_SETSIZE, "no CPU available");
+
+ CPU_ZERO(&pinned);
+ CPU_SET(cpu, &pinned);
+ kvm_sched_setaffinity(0, sizeof(pinned), &pinned);
+}
+
+static void run_to_sync(struct kvm_vcpu *vcpu, unsigned long stage)
+{
+ struct ucall uc;
+ u64 cmd;
+
+ vcpu_run(vcpu);
+ cmd = get_ucall(vcpu, &uc);
+ if (cmd == UCALL_ABORT)
+ REPORT_GUEST_ASSERT(uc);
+ TEST_ASSERT_EQ(cmd, UCALL_SYNC);
+ TEST_ASSERT_EQ(uc.args[1], stage);
+}
+
+static void run_to_done(struct kvm_vcpu *vcpu)
+{
+ struct ucall uc;
+ u64 cmd;
+
+ vcpu_run(vcpu);
+ cmd = get_ucall(vcpu, &uc);
+ if (cmd == UCALL_ABORT)
+ REPORT_GUEST_ASSERT(uc);
+ TEST_ASSERT_EQ(cmd, UCALL_DONE);
+}
+
+static int set_one_reg_buffer(struct kvm_vcpu *vcpu, u64 id, void *buffer)
+{
+ struct kvm_one_reg reg = {
+ .id = id,
+ .addr = (u64)buffer,
+ };
+
+ return __vcpu_ioctl(vcpu, KVM_SET_ONE_REG, ®);
+}
+
+static void expect_pattern(const unsigned char *data, unsigned long length,
+ unsigned char pattern, const char *where)
+{
+ unsigned long i;
+
+ for (i = 0; i < length; i++)
+ TEST_ASSERT(data[i] == pattern,
+ "%s byte %lu: got %#x, expected %#x",
+ where, i, data[i], pattern);
+}
+
+int main(void)
+{
+ unsigned char get_buffer[RISCV_MAX_VLENB] __aligned(64) = {};
+ unsigned char set_buffer[RISCV_MAX_VLENB] __aligned(64);
+ struct kvm_vm *vm;
+ struct kvm_vcpu *vcpu;
+ unsigned long vlenb;
+ u64 vlenb_id, v0_id, size;
+ int ret;
+
+ pin_to_one_cpu();
+ vm = vm_create_with_one_vcpu(&vcpu, guest_code);
+ TEST_REQUIRE(__vcpu_has_isa_ext(vcpu, KVM_RISCV_ISA_EXT_V));
+
+ vm_init_vector_tables(vm);
+ vcpu_init_vector_tables(vcpu);
+
+ vlenb_id = KVM_REG_RISCV | KVM_REG_RISCV_VECTOR |
+ KVM_REG_SIZE_ULONG | KVM_REG_RISCV_VECTOR_CSR_REG(vlenb);
+ ret = __vcpu_get_reg(vcpu, vlenb_id, &vlenb);
+ TEST_ASSERT(!ret, "KVM_GET_ONE_REG(vlenb) failed: %d", ret);
+ TEST_ASSERT(vlenb && vlenb <= RISCV_MAX_VLENB &&
+ !(vlenb & (vlenb - 1)), "unsupported vlenb: %lu", vlenb);
+
+ size = (u64)__builtin_ctzl(vlenb) << KVM_REG_SIZE_SHIFT;
+ v0_id = KVM_REG_RISCV | KVM_REG_RISCV_VECTOR | size |
+ KVM_REG_RISCV_VECTOR_REG(0);
+
+ run_to_sync(vcpu, 1);
+ ret = __vcpu_get_reg(vcpu, v0_id, get_buffer);
+ TEST_ASSERT(!ret, "KVM_GET_ONE_REG(v0) failed: %d", ret);
+ expect_pattern(get_buffer, vlenb, GUEST_PATTERN, "GET v0");
+
+ run_to_sync(vcpu, 2);
+ memset(set_buffer, SET_PATTERN, vlenb);
+ ret = set_one_reg_buffer(vcpu, v0_id, set_buffer);
+ TEST_ASSERT(!ret, "KVM_SET_ONE_REG(v0) failed: %d", ret);
+
+ run_to_done(vcpu);
+ sync_global_from_guest(vm, guest_after_get);
+ sync_global_from_guest(vm, guest_after_set);
+ expect_pattern(guest_after_get, vlenb, GUEST_PATTERN,
+ "guest after GET");
+ expect_pattern(guest_after_set, vlenb, SET_PATTERN,
+ "guest after SET");
+
+ pr_info("Vector ONE_REG owner test passed (vlenb=%lu)\n", vlenb);
+ kvm_vm_free(vm);
+ return 0;
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread