[PATCH v7] KVM: riscv: Skip CSR restore if VCPU is reloaded on the same core

Anup Patel anup at brainfault.org
Thu Apr 2 06:28:01 PDT 2026


On Fri, Feb 27, 2026 at 5:40 PM Jinyu Tang <tjytimi at 163.com> wrote:
>
> Currently, kvm_arch_vcpu_load() unconditionally restores guest CSRs,
> HGATP, and AIA state. However, when a VCPU is loaded back on the same
> physical CPU, and no other KVM VCPU has run on this CPU since it was
> last put, the hardware CSRs and AIA registers are still valid.
>
> This patch optimizes the vcpu_load path by skipping the expensive CSR
> and AIA writes if all the following conditions are met:
> 1. It is being reloaded on the same CPU (vcpu->arch.last_exit_cpu == cpu).
> 2. The CSRs are not dirty (!vcpu->arch.csr_dirty).
> 3. No other VCPU used this CPU (vcpu == __this_cpu_read(kvm_former_vcpu)).
>
> To ensure this fast-path doesn't break corner cases:
> - Live migration and VCPU reset are naturally safe. KVM initializes
>   last_exit_cpu to -1, which guarantees the fast-path won't trigger.
> - The 'csr_dirty' flag tracks runtime userspace interventions. If
>   userspace modifies guest configurations (e.g., hedeleg via
>   KVM_SET_GUEST_DEBUG, or CSRs including AIA via KVM_SET_ONE_REG),
>   the flag is set to skip the fast path.
>
> With the 'csr_dirty' safeguard proven effective, it is safe to
> include kvm_riscv_vcpu_aia_load() inside the skip logic now.
>
> Signed-off-by: Jinyu Tang <tjytimi at 163.com>

Queued this patch for Linux-7.1

Thanks,
Anup

> ---
>  v6 -> v7:
>  - Moved kvm_riscv_vcpu_aia_load() into the fast-path skip logic, as
>    suggested by Radim Krčmář.
>  - Verified the fix for the IMSIC instability issue reported in v3.
>    Testing was conducted on QEMU 10.0.2 with explicitly enabled AIA
>    (`-machine virt,aia=aplic-imsic`). The guest boots successfully
>    using virtio-mmio devices like virtio-blk and virtio-net.
>
>  v5 -> v6:
>  As suggested by Andrew Jones, checking 'last_exit_cpu' first (most
>  likely to fail on busy hosts) and placing the expensive
>  __this_cpu_read() last, skipping __this_cpu_write() in kvm_arch_vcpu_put()
>  if kvm_former_vcpu is already set to the current VCPU.
>
>  v4 -> v5:
>  - Dropped the 'vcpu->scheduled_out' check as Andrew Jones pointed out,
>    relying on 'last_exit_cpu', 'former_vcpu', and '!csr_dirty'
>    is sufficient and safe. This expands the optimization to cover many
>    userspace exits (e.g., MMIO) as well.
>  - Added a block comment in kvm_arch_vcpu_load() to warn future
>    developers about maintaining the 'csr_dirty' dependency, as Andrew's
>    suggestion to reduce fragility.
>  - Removed unnecessary single-line comments and fixed indentation nits.
>
>  v3 -> v4:
>  - Addressed Anup Patel's review regarding hardware state inconsistency.
>  - Introduced 'csr_dirty' flag to track dynamic userspace CSR/CONFIG
>    modifications (KVM_SET_ONE_REG, KVM_SET_GUEST_DEBUG), forcing a full
>    restore when debugging or modifying states at userspace.
>  - Kept kvm_riscv_vcpu_aia_load() out of the skip block to resolve IMSIC
>    VS-file instability.
>
>  v2 -> v3:
>  v2 was missing a critical check because I generated the patch from my
>  wrong (experimental) branch. This is fixed in v3. Sorry for my trouble.
>
>  v1 -> v2:
>  Apply the logic to aia csr load. Thanks for Andrew Jones's advice.
> ---
>  arch/riscv/include/asm/kvm_host.h |  3 +++
>  arch/riscv/kvm/vcpu.c             | 24 ++++++++++++++++++++++--
>  arch/riscv/kvm/vcpu_onereg.c      |  2 ++
>  3 files changed, 27 insertions(+), 2 deletions(-)
>
> diff --git a/arch/riscv/include/asm/kvm_host.h b/arch/riscv/include/asm/kvm_host.h
> index 24585304c..7ee47b83c 100644
> --- a/arch/riscv/include/asm/kvm_host.h
> +++ b/arch/riscv/include/asm/kvm_host.h
> @@ -273,6 +273,9 @@ struct kvm_vcpu_arch {
>         /* 'static' configurations which are set only once */
>         struct kvm_vcpu_config cfg;
>
> +       /* Indicates modified guest CSRs */
> +       bool csr_dirty;
> +
>         /* SBI steal-time accounting */
>         struct {
>                 gpa_t shmem;
> diff --git a/arch/riscv/kvm/vcpu.c b/arch/riscv/kvm/vcpu.c
> index a55a95da5..2e4dfff07 100644
> --- a/arch/riscv/kvm/vcpu.c
> +++ b/arch/riscv/kvm/vcpu.c
> @@ -24,6 +24,8 @@
>  #define CREATE_TRACE_POINTS
>  #include "trace.h"
>
> +static DEFINE_PER_CPU(struct kvm_vcpu *, kvm_former_vcpu);
> +
>  const struct _kvm_stats_desc kvm_vcpu_stats_desc[] = {
>         KVM_GENERIC_VCPU_STATS(),
>         STATS_DESC_COUNTER(VCPU, ecall_exit_stat),
> @@ -537,6 +539,8 @@ int kvm_arch_vcpu_ioctl_set_guest_debug(struct kvm_vcpu *vcpu,
>                 vcpu->arch.cfg.hedeleg |= BIT(EXC_BREAKPOINT);
>         }
>
> +       vcpu->arch.csr_dirty = true;
> +
>         return 0;
>  }
>
> @@ -581,6 +585,21 @@ void kvm_arch_vcpu_load(struct kvm_vcpu *vcpu, int cpu)
>         struct kvm_vcpu_csr *csr = &vcpu->arch.guest_csr;
>         struct kvm_vcpu_config *cfg = &vcpu->arch.cfg;
>
> +       /*
> +        * If VCPU is being reloaded on the same physical CPU and no
> +        * other KVM VCPU has run on this CPU since it was last put,
> +        * we can skip the expensive CSR and HGATP writes.
> +        *
> +        * Note: If a new CSR is added to this fast-path skip block,
> +        * make sure that 'csr_dirty' is set to true in any
> +        * ioctl (e.g., KVM_SET_ONE_REG) that modifies it.
> +        */
> +       if (vcpu != __this_cpu_read(kvm_former_vcpu))
> +               __this_cpu_write(kvm_former_vcpu, vcpu);
> +       else if (vcpu->arch.last_exit_cpu == cpu && !vcpu->arch.csr_dirty)
> +               goto csr_restore_done;
> +
> +       vcpu->arch.csr_dirty = false;
>         if (kvm_riscv_nacl_sync_csr_available()) {
>                 nsh = nacl_shmem();
>                 nacl_csr_write(nsh, CSR_VSSTATUS, csr->vsstatus);
> @@ -624,6 +643,9 @@ void kvm_arch_vcpu_load(struct kvm_vcpu *vcpu, int cpu)
>
>         kvm_riscv_mmu_update_hgatp(vcpu);
>
> +       kvm_riscv_vcpu_aia_load(vcpu, cpu);
> +
> +csr_restore_done:
>         kvm_riscv_vcpu_timer_restore(vcpu);
>
>         kvm_riscv_vcpu_host_fp_save(&vcpu->arch.host_context);
> @@ -633,8 +655,6 @@ void kvm_arch_vcpu_load(struct kvm_vcpu *vcpu, int cpu)
>         kvm_riscv_vcpu_guest_vector_restore(&vcpu->arch.guest_context,
>                                             vcpu->arch.isa);
>
> -       kvm_riscv_vcpu_aia_load(vcpu, cpu);
> -
>         kvm_make_request(KVM_REQ_STEAL_UPDATE, vcpu);
>
>         vcpu->cpu = cpu;
> diff --git a/arch/riscv/kvm/vcpu_onereg.c b/arch/riscv/kvm/vcpu_onereg.c
> index e7ab6cb00..fc08bf833 100644
> --- a/arch/riscv/kvm/vcpu_onereg.c
> +++ b/arch/riscv/kvm/vcpu_onereg.c
> @@ -652,6 +652,8 @@ static int kvm_riscv_vcpu_set_reg_csr(struct kvm_vcpu *vcpu,
>         if (rc)
>                 return rc;
>
> +       vcpu->arch.csr_dirty = true;
> +
>         return 0;
>  }
>
> --
> 2.43.0
>



More information about the linux-riscv mailing list