[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [PATCH v2 12/39] xen/riscv: implement vCPU context switching


  • To: Oleksii Kurochko <oleksii.kurochko@xxxxxxxxx>
  • From: Jan Beulich <jbeulich@xxxxxxxx>
  • Date: Mon, 7 Sep 2026 10:17:40 +0200
  • Authentication-results: eu.smtp.expurgate.cloud; dkim=pass header.s=google header.d=suse.com header.i="@suse.com" header.h="Content-Transfer-Encoding:Content-Type:In-Reply-To:Autocrypt:From:Content-Language:References:Cc:To:Subject:User-Agent:MIME-Version:Date:Message-ID"
  • Autocrypt: addr=jbeulich@xxxxxxxx; keydata= xsDiBFk3nEQRBADAEaSw6zC/EJkiwGPXbWtPxl2xCdSoeepS07jW8UgcHNurfHvUzogEq5xk hu507c3BarVjyWCJOylMNR98Yd8VqD9UfmX0Hb8/BrA+Hl6/DB/eqGptrf4BSRwcZQM32aZK 7Pj2XbGWIUrZrd70x1eAP9QE3P79Y2oLrsCgbZJfEwCgvz9JjGmQqQkRiTVzlZVCJYcyGGsD /0tbFCzD2h20ahe8rC1gbb3K3qk+LpBtvjBu1RY9drYk0NymiGbJWZgab6t1jM7sk2vuf0Py O9Hf9XBmK0uE9IgMaiCpc32XV9oASz6UJebwkX+zF2jG5I1BfnO9g7KlotcA/v5ClMjgo6Gl MDY4HxoSRu3i1cqqSDtVlt+AOVBJBACrZcnHAUSuCXBPy0jOlBhxPqRWv6ND4c9PH1xjQ3NP nxJuMBS8rnNg22uyfAgmBKNLpLgAGVRMZGaGoJObGf72s6TeIqKJo/LtggAS9qAUiuKVnygo 3wjfkS9A3DRO+SpU7JqWdsveeIQyeyEJ/8PTowmSQLakF+3fote9ybzd880fSmFuIEJldWxp Y2ggPGpiZXVsaWNoQHN1c2UuY29tPsJgBBMRAgAgBQJZN5xEAhsDBgsJCAcDAgQVAggDBBYC AwECHgECF4AACgkQoDSui/t3IH4J+wCfQ5jHdEjCRHj23O/5ttg9r9OIruwAn3103WUITZee e7Sbg12UgcQ5lv7SzsFNBFk3nEQQCACCuTjCjFOUdi5Nm244F+78kLghRcin/awv+IrTcIWF hUpSs1Y91iQQ7KItirz5uwCPlwejSJDQJLIS+QtJHaXDXeV6NI0Uef1hP20+y8qydDiVkv6l IreXjTb7DvksRgJNvCkWtYnlS3mYvQ9NzS9PhyALWbXnH6sIJd2O9lKS1Mrfq+y0IXCP10eS FFGg+Av3IQeFatkJAyju0PPthyTqxSI4lZYuJVPknzgaeuJv/2NccrPvmeDg6Coe7ZIeQ8Yj t0ARxu2xytAkkLCel1Lz1WLmwLstV30g80nkgZf/wr+/BXJW/oIvRlonUkxv+IbBM3dX2OV8 AmRv1ySWPTP7AAMFB/9PQK/VtlNUJvg8GXj9ootzrteGfVZVVT4XBJkfwBcpC/XcPzldjv+3 HYudvpdNK3lLujXeA5fLOH+Z/G9WBc5pFVSMocI71I8bT8lIAzreg0WvkWg5V2WZsUMlnDL9 mpwIGFhlbM3gfDMs7MPMu8YQRFVdUvtSpaAs8OFfGQ0ia3LGZcjA6Ik2+xcqscEJzNH+qh8V m5jjp28yZgaqTaRbg3M/+MTbMpicpZuqF4rnB0AQD12/3BNWDR6bmh+EkYSMcEIpQmBM51qM EKYTQGybRCjpnKHGOxG0rfFY1085mBDZCH5Kx0cl0HVJuQKC+dV2ZY5AqjcKwAxpE75MLFkr wkkEGBECAAkFAlk3nEQCGwwACgkQoDSui/t3IH7nnwCfcJWUDUFKdCsBH/E5d+0ZnMQi+G0A nAuWpQkjM1ASeQwSHEeAWPgskBQL
  • Cc: xen-devel@xxxxxxxxxxxxxxxxxxxx, Romain Caritey <Romain.Caritey@xxxxxxxxxxxxx>, Zheng Zhang <zhangzheng@xxxxxxxxxxx>, Alistair Francis <alistair.francis@xxxxxxx>, Connor Davis <connojdavis@xxxxxxxxx>, Andrew Cooper <andrew.cooper3@xxxxxxxxxx>, Anthony PERARD <anthony.perard@xxxxxxxxxx>, Michal Orzel <michal.orzel@xxxxxxx>, Julien Grall <julien@xxxxxxx>, Roger Pau Monné <roger@xxxxxxxxxxxxxx>, Stefano Stabellini <sstabellini@xxxxxxxxxx>, Baptiste Le Duc <baptiste.le-duc@xxxxxxxxxx>
  • Delivery-date: Mon, 07 Sep 2026 08:17:46 +0000
  • List-id: Xen developer discussion <xen-devel.lists.xenproject.org>

On 04.09.2026 16:55, Oleksii Kurochko wrote:
> 
> 
> On 9/4/26 10:26 AM, Baptiste Le Duc wrote:
>> As I understand it, a generation wrap doesn't retire a single VMID, it
>> resets next_vmid to 1, which makes every VMID in 1..max_vmid reusable
>> again in the new generation. We do a full (local) flush at that point to
>> avoid two different vCPUs ending up with the same VMID valid at once,
>> across generations.
>>
>> If this is correct, doing a full flush there also throws away entries
>> for the current vCPU that a local HFENCE.GVMA(vmid) per retired VMID
> 
> We are doing flushed for the pCPU on which a vCPU is ran.
> 
>> could have preserved. A local-flush-per-VMID approach could also
>> reduce how often we need a full flush at all.
>>
>> Is there a reason we don't do local flushing instead? I see x86 and KVM
>> use the same flush-all design on wrap, so I assume there's a reason I'm
>> missing, I'd like to understand it.
> 
> What do you mean here by "local flushing instead"? We are doing local flush:
> 
>      if ( unlikely(need_flush) )
>          local_hfence_gvma_all();
> 
> Do you mean why we don't do hfence_gvma only for specific VMID?
> 
> A vCPU's VMID is valid only while vmid->generation == data->generation 
> (vmid.c:141). Bumping the generation invalidates every vCPU's VMID on 
> this hart simultaneously, so every G-stage entry in the TLB (whatever 
> number it is tagged with) belongs to a (vcpu, vmid) binding that can 
> never be consulted again. Each of those vCPUs will be handed a fresh 
> number on its next vmenter before it can run.
> 
> That includes the current vCPU, which is the case you're worried about. 
> At the wrap it is being assigned VMID 1, not its previous number, so its 
> old entries are unreachable regardless of whether we flush them. 
> hfence.gvma per retired VMID would preserve them physically but not 
> usefully [A concrete example. vCPU A is running on the hart with 
> VMID=100 in generation G; the TLB holds G-stage entries tagged VMID=100. 
> A wrap occurs: the generation becomes G+1, next_vmid is reset to 1, and 
> A is assigned VMID=1 (vmid.c:154). From that moment on, the hardware 
> looks up translations for A under the tag VMID=1. The entries tagged 100 
> will no longer match anything: A isn't 100 any more, and no one else 
> will be handed 100 until the next wrap.
> So A loses its warm entries not because we did an hfence.gvma, but 
> because it was renumbered. The flush has nothing to do with it. It 
> merely discards what has already become unreachable.]; they'd just 
> occupy TLB capacity until natural eviction. Preserving them would 
> require a different allocator that keeps a vCPU's number stable across a 
> rollover (Linux/KVM-arm64 style, with an active/reserved set pinning 
> live ASIDs), not a different flush granularity.
> 
> So x86's hvm_asid_handle_vmenter() and KVM's equivalent aren't doing 
> this out of inertia — with a round-robin generation allocator, the full 
> flush is free of useful collateral damage and strictly cheaper than the 
> alternative. Preserving entries across a rollover is a real 
> optimisation, but it's an allocator change, and IMO worth doing only if 
> profiling shows the wrap flush matters.
> 
>>
>>> H/VS CSRs, virtual timer and P2M context, and __context_switch() in 
>>> assembly,
>>> which switches Xen's own callee-saved state (and thereby the stack) from
>>> prev to next. Virtual interrupt controller context switch will be
>>> introduced later.
>>>
>>> Add offsets of struct arch_vcpu's xen_saved_context to asm-offsets.c for
>>> use by __context_switch().
>>>
>>> henvcfg and htimedelta are 64-bit on both RV32 and RV64, so store them as
>>> uint64_t and use csr_{read,write}64() instead of open-coding accesses to
>>> the high halves.
>>>
>>> A hart which drops out of a domain's dirty_cpumask stops being a target
>>> of p2m_tlb_flush() while its TLB may still hold G-stage translations of
>>> that domain, and neither the vCPU which just ran nor any other vCPU of
>>> that domain which ran there earlier has had its VMID invalidated. Move
>>> the hart to a new VMID generation at that point: a VMID number is never
>>> re-used until a full local flush has happened, hence none of those
>>> translations can be reached again.
>>>
>>> Claim the VMID in p2m_ctxt_switch_to() rather than at the next guest
>>> entry. VMIDs are a per-hart resource, so the (generation, vmid) pair a
>>> migrating vCPU brings from another hart is meaningless here and may even
>>> match this hart's current generation, leaving the vCPU under a VMID owned
>>> by another domain. ctxt_switch_to() invalidates that pair, but claiming a
>>> replacement only on guest entry is too late: p2m_ctxt_switch_to() has by
>>> then already made HGATP live, and speculation can populate G-stage entries
>>> of the incoming domain under the stale VMID. The local flush for a wrapped
>>> generation moves along with the claim.
>>>
>>> That leaves p2m_handle_vmenter() with nothing to do, so drop it together
>>> with its call from check_for_pcpu_work(). A VMID can only be invalidated
>>> while its vCPU isn't running: vmid_flush_vcpu() is called for the vCPU
>>> being switched in, and vmid_flush_hart() runs either from schedule_tail(),
>>> ahead of ctxt_switch_to(), or from the wrap path of vmid_handle_vmenter()
>>> itself. A P2M change on another hart doesn't invalidate it either, as
>>> p2m_tlb_flush() drops the stale entries directly with
>>> sbi_remote_hfence_gvma() instead of retiring the VMIDs which tag them. A
>>> guest therefore always runs under the VMID claimed on its way in, and
>>> there is nothing left for a guest entry hook to notice.
>>>
>>> p2m_handle_vmenter() also skipped the HGATP write when the VMID it claimed
>>> was unchanged. That isn't carried over: HGATP holds the G-stage root as
>>> well, and skipping the write is only correct where that root is already
>>> the incoming domain's. On the guest entry path it is, on the context
>>> switch path it is not.
>>>
>>> While at it, fix the inclusion order of headers in asm-offsets.c: Xen's
>>> headers go first, then arch specific ones.
>> This could have a dedicated patch no?
> 
> It could but considering that it is pretty small fix I think it could be 
> part of this patch. If you are insisting on moving that to separate 
> patch I will happy to do that.
> 
>>> Signed-off-by: Oleksii Kurochko <oleksii.kurochko@xxxxxxxxx>
>>>
>>> diff --git a/xen/arch/riscv/domain.c b/xen/arch/riscv/domain.c
>>> index ec327a5e8a..91a46d630f 100644
>>> --- a/xen/arch/riscv/domain.c
>>> +++ b/xen/arch/riscv/domain.c
>>> @@ -11,9 +11,11 @@
>>>   #include <asm/bitops.h>
>>>   #include <asm/cpufeature.h>
>>>   #include <asm/csr.h>
>>> +#include <asm/current.h>
>>>   #include <asm/intc.h>
>>>   #include <asm/mmio.h>
>>>   #include <asm/riscv_encoding.h>
>>> +#include <asm/vmid.h>
>>>   #include <asm/vtimer.h>
>>>   
>>>   struct csr_masks {
>>> @@ -158,6 +160,8 @@ int arch_vcpu_create(struct vcpu *v)
>>>       if ( is_idle_vcpu(v) )
>>>           return 0;
>>>   
>>> +    v->arch.last_cpu = NR_CPUS;
>>> +
>>>       vcpu_csr_init(v);
>>>   
>>>       if ( (rc = vcpu_vtimer_init(v)) )
>>> @@ -329,6 +333,169 @@ int arch_domain_create(struct domain *d,
>>>       return rc;
>>>   }
>>>   
>>> +static void save_csr_regs(struct vcpu *vcpu)
>>> +{
>>> +    /*
>>> +     * There is no need to save these CSRs as only hypervisor writes them 
>>> in
>>> +     * restore_csr_regs() and guest can't access them so they shouldn't be
>>> +     * stored here. Keep them commented here just for symmetry with the
>>> +     * restore CSRs register part.
>>> +     *
>>> +     * vcpu->arch.hedeleg = csr_read(CSR_HEDELEG);
>>> +     * vcpu->arch.hideleg = csr_read(CSR_HIDELEG);
>>> +     * vcpu->arch.henvcfg = csr_read64(CSR_HENVCFG);
>>> +     * vcpu->arch.hcounteren = csr_read(CSR_HCOUNTEREN);
>>> +     * vcpu->arch.htimedelta = csr_read64(CSR_HTIMEDELTA);
>>> +     *
>>> +     * if ( riscv_isa_extension_available(NULL, RISCV_ISA_EXT_smstateen) )
>>> +     *     vcpu->arch.hstateen0 = csr_read(CSR_HSTATEEN0);
>>> +     */
>>> +
>>> +    vcpu->arch.hvip = csr_read(CSR_HVIP);
>>> +
>>> +    vcpu->arch.vsstatus = csr_read(CSR_VSSTATUS);
>>> +    vcpu->arch.vsie = csr_read(CSR_VSIE);
>>
>>
>>> +    vcpu->arch.vstvec = csr_read(CSR_VSTVEC);
>>> +    vcpu->arch.vsscratch = csr_read(CSR_VSSCRATCH);
>>> +    vcpu->arch.vscause = csr_read(CSR_VSCAUSE);
>>> +    vcpu->arch.vstval = csr_read(CSR_VSTVAL);
>>> +    vcpu->arch.vsepc = csr_read(CSR_VSEPC);
>>> +}
>>> +
>>> +static void restore_csr_regs(struct vcpu *vcpu)
>>> +{
>>> +    csr_write(CSR_HEDELEG, vcpu->arch.hedeleg);
>>> +    csr_write(CSR_HIDELEG, vcpu->arch.hideleg);
>>> +    csr_write(CSR_HVIP, vcpu->arch.hvip);
>>> +    csr_write64(CSR_HENVCFG, vcpu->arch.henvcfg);
>>> +    csr_write(CSR_HCOUNTEREN, vcpu->arch.hcounteren);
>>> +    csr_write64(CSR_HTIMEDELTA, vcpu->arch.htimedelta);
>>> +
>>> +    if ( riscv_isa_extension_available(NULL, RISCV_ISA_EXT_smstateen) )
>>> +        csr_write(CSR_HSTATEEN0, vcpu->arch.hstateen0);
>>> +
>>> +    csr_write(CSR_VSSTATUS, vcpu->arch.vsstatus);
>>> +    csr_write(CSR_VSIE, vcpu->arch.vsie);
>>
>>
>>> +    csr_write(CSR_VSTVEC, vcpu->arch.vstvec);
>>> +    csr_write(CSR_VSSCRATCH, vcpu->arch.vsscratch);
>>> +    csr_write(CSR_VSCAUSE, vcpu->arch.vscause);
>>> +    csr_write(CSR_VSTVAL, vcpu->arch.vstval);
>>> +    csr_write(CSR_VSEPC, vcpu->arch.vsepc);
>>> +}
>>> +
>>> +static void ctxt_switch_from(struct vcpu *p)
>> Is it expected to have diverse names for the vcpu arg? Above it's vcpu,
>> here it's p (I assume it's for `previous` but I think the _from alone is
>> enough to understand) and below it's n. Shouldn't be better to keep the same 
>> name?
> 
> Above should be used n.

Why would that be? n in such contexts stands for "next", while here
it can only be "previous".

Jan



 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.