[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [PATCH v3 11/39] xen/riscv: implement vCPU context switching
- To: Baptiste Le Duc <baptiste.le-duc@xxxxxxxxxx>
- From: Oleksii Kurochko <oleksii.kurochko@xxxxxxxxx>
- Date: Wed, 14 Oct 2026 16:05:47 +0200
- Authentication-results: eu.smtp.expurgate.cloud; dkim=pass header.s=20251104 header.d=gmail.com header.i="@gmail.com" header.h="Content-Transfer-Encoding:Content-Type:In-Reply-To:From:Content-Language:References:Cc:To:Subject:User-Agent:MIME-Version:Date:Message-ID"
- 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>, Jan Beulich <jbeulich@xxxxxxxx>, Julien Grall <julien@xxxxxxx>, Roger Pau Monné <roger@xxxxxxxxxxxxxx>, Stefano Stabellini <sstabellini@xxxxxxxxxx>
- Delivery-date: Wed, 14 Oct 2026 14:05:55 +0000
- List-id: Xen developer discussion <xen-devel.lists.xenproject.org>
[...]
diff --git a/xen/arch/riscv/include/asm/csr.h b/xen/arch/riscv/include/asm/csr.h
index a5cb24c863..aad82c6a6d 100644
--- a/xen/arch/riscv/include/asm/csr.h
+++ b/xen/arch/riscv/include/asm/csr.h
@@ -40,6 +40,13 @@
csr_write(csr ## H, v_ >> 32); \
})
+#define csr_read64(csr) \
+({ \
+ uint64_t v_ = csr_read(csr ## H); \
This violates Misra 20.12 rule (docs/misra/rules.rst) as we use macro
parameter `csr` as an operand of ##, while it could be expanded as a
macro (e.g. csr_read64(CSR_HENVCFG)).
To avoid MISRA violation, if i understand it correctly, I will do the
following:
+#define csr_read64(name) \
+({ \
+ uint64_t v_ = csr_read(CSR_ ## name ## H); \
+ \
+ (v_ << 32) | csr_read(CSR_ ## name); \
and ...
+ \
+ (v_ << 32) | csr_read(csr); \
+})
+
/*
* The two halves are read by separate instructions, so a CSR which hardware
* increments can carry from the low half into the high one in between,
@@ -72,6 +79,12 @@
(void)csr ## H; \
csr_read(csr); \
})
+
+#define csr_read64(csr) \
+({ \
+ (void)csr ## H; \
Same as above.
... the following here:
+#define csr_read64(name) csr_read(CSR_ ## name)
+ csr_read(csr); \
+})
#endif
[...]
diff --git a/xen/arch/riscv/p2m.c b/xen/arch/riscv/p2m.c
index de25607247..02e7f6364f 100644
--- a/xen/arch/riscv/p2m.c
+++ b/xen/arch/riscv/p2m.c
@@ -243,6 +243,15 @@ static void p2m_tlb_flush(struct p2m_domain *p2m)
p2m->need_flush = false;
+ /*
+ * Order the p2m updates above against the read of dirty_cpumask below,
+ * pairing with the barrier in p2m_vmid_switch_to(). Either that hart is
+ * seen here and gets an HFENCE.GVMA, or it adds itself to the mask
+ * afterwards, in which case it starts walking this p2m only once the
+ * updates are visible to it.
+ */
+ smp_mb();
+
sbi_remote_hfence_gvma(d->dirty_cpumask, 0, 0);
}
@@ -1504,16 +1513,110 @@ void p2m_ctxt_switch_from(struct vcpu *p)
* VMID, world-switch code should zero vsatp, then swap hgatp, then
* finally write the new vsatp value what will be done in
* p2m_ctxt_switch_to().
- * Note, that also HGATP update could happen in p2m_handle_vmenter().
*/
p->arch.vsatp = csr_swap(CSR_VSATP, 0);
/*
- * Nothing to do with HGATP as it will be update in p2m_ctxt_switch_to()
- * or/and in p2m_handle_vmenter().
+ * Nothing to do with HGATP as it will be updated in
+ * p2m_ctxt_switch_to().
*/
}
+/*
+ * Domain whose p2m this hart's HGATP points at. ctxt_switch_to() bails out
+ * early for the idle vCPU, so HGATP survives a pass through idle and keeps
+ * pointing at the domain which ran here last. That domain, rather than the
+ * one the scheduler switched away from, is what owns this hart's G-stage
+ * translations. The hart also keeps its place in the owner's dirty_cpumask:
+ * p2m_tlb_flush() goes on reaching it, and a domain which idles between two
+ * runs on the same hart keeps its VMIDs.
+ *
+ * TODO: nothing resets hgatp_owner when its domain is destroyed, so a hart
+ * which stays idle from the domain's last run until it is freed is
+ * left with a dangling pointer. Once domain_relinquish_resources()
+ * tears down the p2m, it has to reset hgatp_owner of every hart still
+ * pointing at the domain (cmpxchg() against d), i.e. before the RCU
+ * grace period of domain_destroy() and so before the domain is freed.
+ */
+static DEFINE_PER_CPU(struct domain *, hgatp_owner);
+
+/*
+ * Update this hart's place in the dirty_cpumask of the domains involved and
+ * claim a VMID for n. Must be called before HGATP is pointed at n's p2m, as
+ * it is written with the VMID claimed here.
+ */
+static void p2m_vmid_switch_to(struct vcpu *n)
+{
+ unsigned int cpu = smp_processor_id();
+ struct domain *owner = this_cpu(hgatp_owner);
+ bool need_flush;
+
+ ASSERT(!is_idle_vcpu(n));
+
+ if ( owner != n->domain )
+ {
+ /*
+ * Once this hart drops out of the owner's dirty_cpumask it stops
+ * being a target of p2m_tlb_flush(), while its TLB may still hold
+ * G-stage translations of that domain: none of the vCPUs of that
+ * domain which ran here has had its VMID invalidated. Move the hart
+ * to a new VMID generation so that none of them can be reached
+ * again.
+ *
+ * This has to precede the VMID claim below, so that n gets a VMID of
+ * the new generation.
+ */
+ if ( owner )
+ {
+ vmid_flush_hart();
+
+ cpumask_clear_cpu(cpu, owner->dirty_cpumask);
+ }
+
+ /*
+ * Mark this hart in the incoming domain's dirty_cpumask before HGATP
+ * is pointed at its p2m: from that write on the hart may cache the
+ * p2m's translations, so p2m_tlb_flush() must already reach it.
+ */
+ cpumask_set_cpu(cpu, n->domain->dirty_cpumask);
+
+ /*
+ * Pairs with the barrier in p2m_tlb_flush(). cpumask_set_cpu() is an
+ * unordered AMO on RISC-V, so without this a concurrent flusher could
+ * read the mask without this hart in it while this hart is already
+ * walking the p2m it is about to be pointed at.
+ */
+ smp_mb();
+
+ this_cpu(hgatp_owner) = n->domain;
+ }
+
+ /*
+ * A VMID is meaningful only on the hart whose pool issued it: generations
+ * are per-hart counters which all start at 1 and advance independently,
+ * so the pair a vCPU brings from another hart may match this hart's
+ * generation by coincidence, leaving the vCPU under a VMID which is live
+ * here for someone else.
+ */
So if I understand correctly, we are setting generation to 0 to mark the
vCPU that it is coming from other hart right?
Yes, at least that is the case this code deals with. Strictly speaking,
generation 0 just invalidates the vCPU's VMID, so that a new one is
claimed on the next VM entry; vmid_flush_vcpu() could be used wherever
that is needed, coming from another hart being one such case.
Nit: If it's the case I may simplify the comment to:
/*
* VMIDs and generations are per hart, so a pair issued on another hart
* may match this hart's generation by coincidence. Reset the generation
* to 0, which no hart ever uses, so that a fresh VMID is claimed from
* this hart's pool.
*/
Looks good to me, I'll take it.
+ if ( n->arch.last_cpu != cpu )
+ vmid_flush_vcpu(n);
Nit: I had to read this a couple of times to get why the guest entry is too
late. "makes HGATP live" doesn't say what happens once it is: if it's
because of speculation filling the TLB with G-stage entries tagged with
the VMID in HGATP, like you explained in the commit message then it may
I thought that having "HGATP live" would be clear.
worth spell out:
/*
* Claim the VMID before the caller writes HGATP rather than on guest
* entry: from that write on, speculation may fill the TLB with this
* domain's G-stage entries tagged with that VMID. A stale VMID may
* already have been re-issued to a vCPU of another domain, which would
* then hit these entries.
*/
Indeed, that's clearer. I'll take your wording.
Thanks!
~ Oleksii
|