|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [PATCH v4 38/42] xen/riscv: emulate the vAPLIC genmsi register
So far, a guest's access to genmsi has ended up in the default case of
the vAPLIC emulation and got the domain crashed.
In MSI delivery mode, a write to genmsi makes an APLIC send an extempore
MSI, with EIID as its data, to the hart named by the Hart Index field.
A guest may want to use it when it reconfigures where a target register
points to, though Linux at least doesn't do so at the moment, so an
access to genmsi should be emulated.
The physical genmsi can't be used on the guest's behalf, as it has no
Guest Index field: its MSI always goes to a hart's supervisor-level
interrupt file. So emulate genmsi in software: wait for the MSIs the
h/w APLIC and vAPLIC have already sent to the target vCPU's h/w guest
interrupt file, using Xen's own APLIC-hart barrier, and then make the
identity pending in the file through vcpu_imsic_inject().
The barrier has to be executed by the pCPU owning the file, and reaching
another pCPU through an IPI takes IRQs enabled, while a trap taken from
a guest is handled with them disabled. Enable them for the time of the
barrier: no lock is held at that point, and the CSRs describing the trap
have been read already.
An MSI to a vCPU without a h/w guest interrupt file, e.g. one which
hasn't run yet, is lost.
Busy always reads as zero, as the MSI is sent before the write to genmsi
completes. genmsi takes the value written just before the MSI is sent,
both under a new domain-wide vaplic->lock, so that concurrent writes
take effect in the order their MSIs are sent.
Signed-off-by: Oleksii Kurochko <oleksii.kurochko@xxxxxxxxx>
---
Changes in v4:
- New patch.
---
---
xen/arch/riscv/imsic.c | 40 ++++++++++++++
xen/arch/riscv/include/asm/aplic.h | 4 +-
xen/arch/riscv/include/asm/imsic.h | 2 +
xen/arch/riscv/include/asm/vaplic.h | 6 +++
xen/arch/riscv/vaplic.c | 82 +++++++++++++++++++++++++----
5 files changed, 124 insertions(+), 10 deletions(-)
diff --git a/xen/arch/riscv/imsic.c b/xen/arch/riscv/imsic.c
index e72a710b2d28..e7627dd48041 100644
--- a/xen/arch/riscv/imsic.c
+++ b/xen/arch/riscv/imsic.c
@@ -811,6 +811,46 @@ static void cf_check imsic_aplic_sync(void *data)
imsic_vsfile_local_sync(idata->hgei);
}
+/*
+ * Make sure that all the MSIs the h/w APLIC or vAPLIC has already sent to @v's
+ * IMSIC interrupt file have arrived there.
+ *
+ * The barrier has to be executed by the pCPU owning the file, so for a file
+ * owned by another pCPU an IPI is sent, which has to be done with IRQs
+ * enabled.
+ *
+ * vsfile_lock can't be held across the barrier: the IPI takes IRQs enabled,
+ * while the write side is taken with IRQs disabled, by imsic_update_state()
+ * when @v acquires a file on the very pCPU the IPI goes to, so the two would
+ * wait for each other. Nor is it needed, should @v be migrated meanwhile:
+ * - the migration does the barrier for the old file itself before moving its
+ * state into the new one, and it is done by @v itself before it enters the
+ * guest again;
+ * - MSIs are redirected to the new file only after it has been published,
+ * so whatever reaches the new file was sent after the lookup done here;
+ * - should the file have been handed to another vCPU meanwhile, the wait
+ * touches only the identity Xen reserves in it.
+ */
+void vcpu_imsic_aplic_barrier(const struct vcpu *v)
+{
+ struct vimsic_state *imsic = v->arch.vimsic_state;
+ struct imsic_vsfile_data idata = { };
+ unsigned long flags;
+ unsigned int cpu;
+
+ /* The file and the pCPU owning it have to be read together. */
+ read_lock_irqsave(&imsic->vsfile_lock, flags);
+ cpu = imsic->vsfile_cpu;
+ idata.hgei = imsic->guest_file_id;
+ read_unlock_irqrestore(&imsic->vsfile_lock, flags);
+
+ /* With no interrupt file, there is nothing an MSI can be on its way to. */
+ if ( cpu == CPU_NONE )
+ return;
+
+ imsic_call_on_cpu(cpu, imsic_aplic_sync, &idata);
+}
+
/*
* Make interrupt identity @iid pending in @v's IMSIC interrupt file, as an MSI
* addressed to that file would.
diff --git a/xen/arch/riscv/include/asm/aplic.h
b/xen/arch/riscv/include/asm/aplic.h
index 37584039107b..5b4ae0d91f50 100644
--- a/xen/arch/riscv/include/asm/aplic.h
+++ b/xen/arch/riscv/include/asm/aplic.h
@@ -89,7 +89,9 @@
#define APLIC_SETIPNUM_BE 0x2004
#define APLIC_GENMSI 0x3000
-#define APLIC_GENMSI_BUSY BIT(12, U)
+#define APLIC_GENMSI_HART_IDX GENMASK(31, 18)
+#define APLIC_GENMSI_BUSY BIT(12, U)
+#define APLIC_GENMSI_EIID GENMASK(10, 0)
#define APLIC_TARGET_BASE 0x3004
#define APLIC_TARGET_LAST 0x3ffc
diff --git a/xen/arch/riscv/include/asm/imsic.h
b/xen/arch/riscv/include/asm/imsic.h
index f372761b8853..5fc8f8067fcf 100644
--- a/xen/arch/riscv/include/asm/imsic.h
+++ b/xen/arch/riscv/include/asm/imsic.h
@@ -150,4 +150,6 @@ void cf_check imsic_migrate_vcpu(struct vcpu *v);
int vcpu_imsic_inject(const struct vcpu *v, unsigned int iid);
+void vcpu_imsic_aplic_barrier(const struct vcpu *v);
+
#endif /* RISCV_IMSIC_H */
diff --git a/xen/arch/riscv/include/asm/vaplic.h
b/xen/arch/riscv/include/asm/vaplic.h
index 843b78e80ad8..360ad3021751 100644
--- a/xen/arch/riscv/include/asm/vaplic.h
+++ b/xen/arch/riscv/include/asm/vaplic.h
@@ -33,6 +33,9 @@ struct vaplic_source {
struct vaplic_regs {
uint32_t domaincfg;
+ /* Register to generate an extempore MSI. */
+ uint32_t genmsi;
+
/*
* Indexed by source number. The array holds d->arch.vintc->nr_virqs
* elements; sources[0] is unused as APLIC interrupt sources start from 1.
@@ -44,6 +47,9 @@ struct vaplic {
struct vintc vintc;
struct vaplic_regs regs;
+ /* Protects access to the vAPLIC registers. */
+ spinlock_t lock;
+
paddr_t regs_start;
unsigned int regs_size;
};
diff --git a/xen/arch/riscv/vaplic.c b/xen/arch/riscv/vaplic.c
index b849ea1c97e8..1ab87b8aee1a 100644
--- a/xen/arch/riscv/vaplic.c
+++ b/xen/arch/riscv/vaplic.c
@@ -251,6 +251,69 @@ static uint32_t generate_auth_mask(const struct domain
*currd,
(first_bit % BITS_PER_LONG);
}
+static uint32_t vaplic_genmsi_read(const struct domain *d)
+{
+ /*
+ * Busy always reads as zero, as the MSI is sent before the write to genmsi
+ * completes.
+ *
+ * No lock is needed: a read racing with a write may return the new value
+ * before the MSI has been sent, which looks the same to the guest as an
+ * MSI of an APLIC which hasn't taken effect at the IMSIC yet.
+ */
+ return read_atomic(&to_vaplic(d)->regs.genmsi);
+}
+
+static void vaplic_genmsi_write(const struct domain *d, uint32_t value)
+{
+ struct vaplic *vaplic = to_vaplic(d);
+ const struct vcpu *v;
+ unsigned long flags;
+
+ /*
+ * Hart Index and EIID behave as in a target register, Busy is read-only
+ * and all the other bits are reserved.
+ */
+ value &= APLIC_GENMSI_HART_IDX | APLIC_GENMSI_EIID;
+
+ v = domain_vcpu(d, MASK_EXTR(value, APLIC_GENMSI_HART_IDX));
+ if ( !v )
+ {
+ gdprintk(XENLOG_ERR, "Invalid vCPU id in genmsi\n");
+
+ /* Ignore such writes */
+ return;
+ }
+
+ /*
+ * According to the AIA spec:
+ * All MSIs previously sent from this APLIC to the same hart must be
+ * visible at the hart's IMSIC before the extempore MSI becomes visible
+ * at the hart's IMSIC.
+ *
+ * The physical genmsi can't send an MSI to a guest interrupt file, so
+ * wait for the MSIs already sent to @v's file to arrive there.
+ *
+ * The barrier may need an IPI, which takes IRQs enabled, while a trap
+ * taken from a guest is handled with them disabled. Enable them for the
+ * time of the barrier: no lock is held here, and the CSRs describing the
+ * trap have been read already.
+ */
+ ASSERT(!local_irq_is_enabled());
+ local_irq_enable();
+ vcpu_imsic_aplic_barrier(v);
+ local_irq_disable();
+
+ spin_lock_irqsave(&vaplic->lock, flags);
+
+ write_atomic(&vaplic->regs.genmsi, value);
+
+ if ( vcpu_imsic_inject(v, MASK_EXTR(value, APLIC_GENMSI_EIID)) )
+ gdprintk(XENLOG_DEBUG, "genmsi: %pv has no h/w interrupt file\n", v);
+
+ spin_unlock_irqrestore(&vaplic->lock, flags);
+}
+
static bool vaplic_emulate_load(const struct vcpu *curr, paddr_t addr,
uint32_t *out)
{
@@ -335,15 +398,11 @@ static bool vaplic_emulate_load(const struct vcpu *curr,
paddr_t addr,
return true;
- /*
- * TODO: emulate genmsi. A write to it has to send an extempore MSI to
- * the target vCPU, and a guest typically waits for that MSI to become
- * pending in its interrupt file. Just recording the written value would
- * leave such a guest waiting forever, so until genmsi is emulated, an
- * access to it is treated as an unhandled one, which gets the domain
- * crashed.
- */
case APLIC_GENMSI:
+ *out = vaplic_genmsi_read(currd);
+
+ return true;
+
default:
gdprintk(XENLOG_WARNING, "Unhandled APLIC read at offset %#x\n",
offset);
@@ -472,8 +531,11 @@ static bool vaplic_emulate_store(const struct vcpu *curr,
paddr_t addr,
return true;
- /* TODO: emulate genmsi, see vaplic_emulate_load(). */
case APLIC_GENMSI:
+ vaplic_genmsi_write(currd, value);
+
+ return true;
+
default:
gdprintk(XENLOG_WARNING,
"Unhandled APLIC write at offset %#x (value %#x)\n",
@@ -670,6 +732,8 @@ int domain_vaplic_init(struct domain *d)
for ( i = 0; i < d->arch.vintc->nr_virqs; i++ )
spin_lock_init(&vaplic->regs.sources[i].lock);
+ spin_lock_init(&vaplic->lock);
+
vaplic->regs_start = GUEST_APLIC_S_BASE;
/*
* IDC structures are used only in direct delivery mode, so the control
--
2.56.1
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |