|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [PATCH v5 12/22] xen/arm: vsmmuv3: Add support for event queue and global error
From: Rahul Singh <rahul.singh@xxxxxxx>
Event queue is used to send the events to guest when there is an events/
faults. Add support for event queue to send events to guest.
Global error in SMMUv3 hw will be updated in smmu_gerror and
smmu_gerrorn register. Add support for global error registers to send
global error to guest.
Add architectural error handling to the emulated command queue,
updating the global error register and stopping command consumption
on error.
Signed-off-by: Rahul Singh <rahul.singh@xxxxxxx>
Signed-off-by: Milan Djokic <milan_djokic@xxxxxxxx>
---
xen/arch/arm/dom0less-build.c | 50 +++--
xen/drivers/passthrough/arm/smmu-v3.h | 20 ++
xen/drivers/passthrough/arm/vsmmu-v3.c | 246 +++++++++++++++++++++++--
xen/include/public/arch-arm.h | 2 +
4 files changed, 295 insertions(+), 23 deletions(-)
diff --git a/xen/arch/arm/dom0less-build.c b/xen/arch/arm/dom0less-build.c
index b55218d920..6ad2a1b745 100644
--- a/xen/arch/arm/dom0less-build.c
+++ b/xen/arch/arm/dom0less-build.c
@@ -386,34 +386,60 @@ int __init arch_parse_dom0less_node(struct dt_device_node
*node,
if ( !dt_property_read_u32(node, "nr_spis", &d_cfg->arch.nr_spis) )
{
int vpl011_virq = GUEST_VPL011_SPI;
+ int vsmmu_virq = GUEST_VSMMU_SPI;
d_cfg->arch.nr_spis = vgic_def_nr_spis();
/*
- * The VPL011 virq is GUEST_VPL011_SPI, unless direct-map is
- * set, in which case it'll match the hardware.
- *
- * Since the domain is not yet created, we can't use
- * d->arch.vpl011.irq. So the logic to find the vIRQ has to
- * be hardcoded.
- * The logic here shall be consistent with the one in
- * domain_vpl011_init().
+ * The VPL011 and VSMMU interrupts use predefined virtual SPIs,
+ * unless direct-map is set, in which case they match the
+ * corresponding hardware interrupts.
*/
if ( flags & CDF_directmap )
{
+#ifdef CONFIG_ARM_VIRTUAL_IOMMU
+ struct host_iommu *hw_iommu;
+#endif
+ /*
+ * Since the domain is not yet created, we can't use
+ * d->arch.vpl011.irq. So the logic to find the vIRQ has to
+ * be hardcoded.
+ * The logic here shall be consistent with the one in
+ * domain_vpl011_init().
+ */
vpl011_virq = serial_irq(SERHND_DTUART);
if ( vpl011_virq < 0 )
panic("Error getting IRQ number for this serial port %d\n",
SERHND_DTUART);
- }
+#ifdef CONFIG_ARM_VIRTUAL_IOMMU
+ /*
+ * The host SMMU list is already populated during SMMUv3 driver
+ * probe, so we can determine the highest SMMU interrupt ID
+ * from this list.
+ */
+ vsmmu_virq = 0;
+ list_for_each_entry(hw_iommu, &host_iommu_list, entry)
+ {
+ vsmmu_virq = MAX(vsmmu_virq, hw_iommu->irq);
+ }
+#endif
+ }
/*
- * vpl011 uses one emulated SPI. If vpl011 is requested, make
- * sure that we allocate enough SPIs for it.
- */
+ * vpl011 uses one emulated SPI. If vpl011 is requested, make
+ * sure that we allocate enough SPIs for it.
+ */
if ( dt_property_read_bool(node, "vpl011") )
d_cfg->arch.nr_spis = MAX(d_cfg->arch.nr_spis,
vpl011_virq - 32 + 1);
+
+ /*
+ * vsmmu uses one emulated SPI. If vsmmu is enabled, make
+ * sure that we allocate enough SPIs for it.
+ */
+ if ( d_cfg->arch.viommu_type == XEN_DOMCTL_CONFIG_VIOMMU_SMMUV3 )
+ d_cfg->arch.nr_spis = MAX(d_cfg->arch.nr_spis,
+ vsmmu_virq - 32 + 1);
}
else if ( flags & CDF_hardware )
panic("nr_spis cannot be specified for hardware domain\n");
diff --git a/xen/drivers/passthrough/arm/smmu-v3.h
b/xen/drivers/passthrough/arm/smmu-v3.h
index 4a912be740..44ecb5d89c 100644
--- a/xen/drivers/passthrough/arm/smmu-v3.h
+++ b/xen/drivers/passthrough/arm/smmu-v3.h
@@ -358,6 +358,26 @@
#define EVTQ_0_ID GENMASK_ULL(7, 0)
+#define EVT_ID_BAD_STREAMID 0x02
+#define EVT_ID_BAD_STE 0x04
+#define EVT_ID_TRANSLATION_FAULT 0x10
+#define EVT_ID_ADDR_SIZE_FAULT 0x11
+#define EVT_ID_ACCESS_FAULT 0x12
+#define EVT_ID_PERMISSION_FAULT 0x13
+
+#define EVTQ_0_SSV (1UL << 11)
+#define EVTQ_0_SSID GENMASK_ULL(31, 12)
+#define EVTQ_0_SID GENMASK_ULL(63, 32)
+#define EVTQ_1_STAG GENMASK_ULL(15, 0)
+#define EVTQ_1_STALL (1UL << 31)
+#define EVTQ_1_PnU (1UL << 33)
+#define EVTQ_1_InD (1UL << 34)
+#define EVTQ_1_RnW (1UL << 35)
+#define EVTQ_1_S2 (1UL << 39)
+#define EVTQ_1_CLASS GENMASK_ULL(41, 40)
+#define EVTQ_1_TT_READ (1UL << 44)
+#define EVTQ_2_ADDR GENMASK_ULL(63, 0)
+#define EVTQ_3_IPA GENMASK_ULL(51, 12)
/* PRI queue */
#define PRIQ_ENT_SZ_SHIFT 4
#define PRIQ_ENT_DWORDS ((1 << PRIQ_ENT_SZ_SHIFT) >> 3)
diff --git a/xen/drivers/passthrough/arm/vsmmu-v3.c
b/xen/drivers/passthrough/arm/vsmmu-v3.c
index 72f8075e0a..17e83dcb1a 100644
--- a/xen/drivers/passthrough/arm/vsmmu-v3.c
+++ b/xen/drivers/passthrough/arm/vsmmu-v3.c
@@ -86,6 +86,35 @@
#define smmu_get_ste_s1ctxptr(x) FIELD_PREP(STRTAB_STE_0_S1CTXPTR_MASK, \
FIELD_GET(STRTAB_STE_0_S1CTXPTR_MASK, x))
+/* event queue entry */
+struct arm_smmu_evtq_ent {
+ /* Common fields */
+ uint8_t opcode;
+ uint32_t sid;
+
+ /* Event-specific fields */
+ union {
+ struct {
+ uint32_t ssid;
+ bool ssv;
+ } c_bad_ste_streamid;
+
+ struct {
+ bool stall;
+ uint16_t stag;
+ uint32_t ssid;
+ bool ssv;
+ bool s2;
+ uint64_t addr;
+ bool rnw;
+ bool pnu;
+ bool ind;
+ uint8_t class;
+ uint64_t addr2;
+ } f_translation;
+ };
+};
+
/* stage-1 translation configuration */
struct arm_vsmmu_s1_trans_cfg {
paddr_t s1ctxptr;
@@ -116,6 +145,7 @@ struct virt_smmu {
uint32_t strtab_base_cfg;
uint64_t strtab_base;
uint32_t irq_ctrl;
+ uint32_t virq;
uint64_t gerror_irq_cfg0;
uint64_t evtq_irq_cfg0;
struct arm_vsmmu_queue evtq, cmdq;
@@ -161,9 +191,15 @@ static inline bool smmu_get_evtq_enabled(struct virt_smmu
*smmu)
spin_unlock(&smmu->cr0_lock);
return enabled;
-};
+}
/* Queue manipulation functions */
+static bool queue_full(struct arm_vsmmu_queue *q)
+{
+ return Q_IDX(q, q->prod) == Q_IDX(q, q->cons) &&
+ Q_WRP(q, q->prod) != Q_WRP(q, q->cons);
+}
+
static bool queue_empty(struct arm_vsmmu_queue *q)
{
return Q_IDX(q, q->prod) == Q_IDX(q, q->cons) &&
@@ -176,12 +212,141 @@ static void queue_inc_cons(struct arm_vsmmu_queue *q)
q->cons = Q_OVF(q->cons) | Q_WRP(q, cons) | Q_IDX(q, cons);
}
+static void queue_inc_prod(struct arm_vsmmu_queue *q)
+{
+ u32 prod = (Q_WRP(q, q->prod) | Q_IDX(q, q->prod)) + 1;
+ q->prod = Q_OVF(q->prod) | Q_WRP(q, prod) | Q_IDX(q, prod);
+}
+
static void dump_smmu_command(uint64_t *command)
{
gprintk(XENLOG_ERR, "cmd 0x%02llx: %016"PRIx64" %016"PRIx64"\n",
smmu_cmd_get_command(command[0]), command[0], command[1]);
}
+static void arm_vsmmu_update_irq(struct virt_smmu *smmu)
+{
+ bool evt_pending = false;
+ bool gerr_pending = false;
+ uint32_t irq_ctrl;
+
+ spin_lock(&smmu->irq_cfg_lock);
+ irq_ctrl = smmu->irq_ctrl;
+ spin_unlock(&smmu->irq_cfg_lock);
+
+ spin_lock(&smmu->evt_queue_lock);
+ evt_pending = !queue_empty(&smmu->evtq);
+ spin_unlock(&smmu->evt_queue_lock);
+
+ spin_lock(&smmu->gerror_lock);
+ gerr_pending = (smmu->gerror ^ smmu->gerrorn) != 0;
+ spin_unlock(&smmu->gerror_lock);
+
+ vgic_inject_irq(smmu->d, NULL, smmu->virq,
+ ((irq_ctrl & IRQ_CTRL_EVTQ_IRQEN) && evt_pending) ||
+ ((irq_ctrl & IRQ_CTRL_GERROR_IRQEN) && gerr_pending));
+}
+
+static void arm_vsmmu_set_gerror(struct virt_smmu *smmu, uint32_t gerror_err)
+{
+ uint32_t pending, new_gerrors;
+
+ spin_lock(&smmu->gerror_lock);
+
+ pending = smmu->gerror ^ smmu->gerrorn;
+ new_gerrors = ~pending & gerror_err;
+
+ if ( new_gerrors )
+ smmu->gerror ^= new_gerrors;
+
+ spin_unlock(&smmu->gerror_lock);
+
+ arm_vsmmu_update_irq(smmu);
+}
+
+static int arm_vsmmu_write_evtq(struct virt_smmu *smmu, uint64_t *evt)
+{
+ struct arm_vsmmu_queue *q = &smmu->evtq;
+ struct domain *d = smmu->d;
+ paddr_t addr;
+ int ret;
+
+ if ( !smmu_get_evtq_enabled(smmu) )
+ return 0;
+
+ spin_lock(&smmu->evt_queue_lock);
+
+ if ( queue_full(q) )
+ {
+ /*
+ * Toggle OVFLG only if the previous overflow
+ * has already been acknowledged.
+ */
+ if ( Q_OVF(smmu->evtq.prod) ==
+ Q_OVF(smmu->evtq.cons) )
+ smmu->evtq.prod ^= Q_OVERFLOW_FLAG;
+
+ spin_unlock(&smmu->evt_queue_lock);
+ return 0;
+ }
+
+ addr = Q_PROD_ENT(q);
+
+ ret = access_guest_memory_by_gpa(d, addr, evt,
+ sizeof(*evt) * EVTQ_ENT_DWORDS, true);
+ if ( ret )
+ {
+ spin_unlock(&smmu->evt_queue_lock);
+ return ret;
+ }
+
+ queue_inc_prod(q);
+
+ spin_unlock(&smmu->evt_queue_lock);
+
+ arm_vsmmu_update_irq(smmu);
+
+ return 0;
+}
+
+static void arm_vsmmu_send_event(struct virt_smmu *smmu,
+ struct arm_smmu_evtq_ent *ent)
+{
+ uint64_t evt[EVTQ_ENT_DWORDS];
+ int ret;
+
+ memset(evt, 0, 1 << EVTQ_ENT_SZ_SHIFT);
+
+ if ( !smmu_get_evtq_enabled(smmu) )
+ return;
+
+ evt[0] |= FIELD_PREP(EVTQ_0_ID, ent->opcode);
+ evt[0] |= FIELD_PREP(EVTQ_0_SID, ent->sid);
+
+ switch ( ent->opcode )
+ {
+ case EVT_ID_BAD_STREAMID:
+ case EVT_ID_BAD_STE:
+ evt[0] |= FIELD_PREP(EVTQ_0_SSID, ent->c_bad_ste_streamid.ssid);
+ evt[0] |= FIELD_PREP(EVTQ_0_SSV, ent->c_bad_ste_streamid.ssv);
+ break;
+ case EVT_ID_TRANSLATION_FAULT:
+ case EVT_ID_ADDR_SIZE_FAULT:
+ case EVT_ID_ACCESS_FAULT:
+ case EVT_ID_PERMISSION_FAULT:
+ break;
+ default:
+ gdprintk(XENLOG_WARNING, "vSMMUv3: event opcode is bad\n");
+ break;
+ }
+
+ ret = arm_vsmmu_write_evtq(smmu, evt);
+ if ( ret )
+ arm_vsmmu_set_gerror(smmu, GERROR_EVTQ_ABT_ERR);
+
+ return;
+}
+
static int arm_vsmmu_find_ste(struct virt_smmu *smmu, uint32_t sid,
uint64_t *ste)
{
@@ -190,6 +355,14 @@ static int arm_vsmmu_find_ste(struct virt_smmu *smmu,
uint32_t sid,
uint32_t log2size;
int strtab_size_shift;
int ret;
+ struct arm_smmu_evtq_ent ent = {
+ .opcode = EVT_ID_BAD_STREAMID,
+ .sid = sid,
+ .c_bad_ste_streamid = {
+ .ssid = 0,
+ .ssv = false,
+ },
+ };
spin_lock(&smmu->strtab_cfg_lock);
log2size = MIN(FIELD_GET(STRTAB_BASE_CFG_LOG2SIZE, smmu->strtab_base_cfg),
@@ -197,7 +370,10 @@ static int arm_vsmmu_find_ste(struct virt_smmu *smmu,
uint32_t sid,
spin_unlock(&smmu->strtab_cfg_lock);
if ( sid >= (1U << log2size) )
+ {
+ arm_vsmmu_send_event(smmu, &ent);
return -EINVAL;
+ }
spin_lock(&smmu->strtab_cfg_lock);
if ( FIELD_GET(STRTAB_BASE_CFG_FMT, smmu->strtab_base_cfg) ==
@@ -250,6 +426,7 @@ static int arm_vsmmu_find_ste(struct virt_smmu *smmu,
uint32_t sid,
{
gdprintk(XENLOG_ERR, "idx=%d > max_l2_ste=%d\n",
idx, max_l2_ste);
+ arm_vsmmu_send_event(smmu, &ent);
return -EINVAL;
}
addr = l2ptr + idx * sizeof(*ste) * STRTAB_STE_DWORDS;
@@ -278,6 +455,14 @@ static int arm_vsmmu_decode_ste(struct virt_smmu *smmu,
uint32_t sid,
uint64_t *ste)
{
uint64_t val = ste[0];
+ struct arm_smmu_evtq_ent ent = {
+ .opcode = EVT_ID_BAD_STE,
+ .sid = sid,
+ .c_bad_ste_streamid = {
+ .ssid = 0,
+ .ssv = false,
+ },
+ };
if ( !(val & STRTAB_STE_0_V) )
{
@@ -316,6 +501,7 @@ static int arm_vsmmu_decode_ste(struct virt_smmu *smmu,
uint32_t sid,
return 0;
bad_ste:
+ arm_vsmmu_send_event(smmu, &ent);
return -EINVAL;
}
@@ -373,16 +559,19 @@ static int arm_vsmmu_handle_cmds(struct virt_smmu *smmu)
* monopolizing CPU time. Implement commands rescheduling and asynchronous
* execution as outlined in the design.
*/
- while ( !queue_empty(q) )
+ while ( !queue_empty(q) &&
+ ((smmu->gerror ^ smmu->gerrorn) & (GERROR_CMDQ_ERR)) == 0 )
{
- int ret;
-
addr = Q_CONS_ENT(q);
ret = access_guest_memory_by_gpa(d, addr, command,
sizeof(command), false);
- if ( ret ) {
- queue_inc_cons(q);
- return ret;
+ if ( ret )
+ {
+ smmu->cmdq.cons &= ~CMDQ_CONS_ERR;
+ smmu->cmdq.cons |= FIELD_PREP(CMDQ_CONS_ERR,
+ CMDQ_ERR_CERROR_ABT_IDX);
+ arm_vsmmu_set_gerror(smmu, GERROR_CMDQ_ERR);
+ break;
}
switch ( smmu_cmd_get_command(command[0]) )
@@ -406,6 +595,10 @@ static int arm_vsmmu_handle_cmds(struct virt_smmu *smmu)
default:
gdprintk(XENLOG_ERR, "vSMMUv3: unhandled command\n");
dump_smmu_command(command);
+ smmu->cmdq.cons &= ~CMDQ_CONS_ERR;
+ smmu->cmdq.cons |= FIELD_PREP(CMDQ_CONS_ERR,
+ CMDQ_ERR_CERROR_ILL_IDX);
+ arm_vsmmu_set_gerror(smmu, GERROR_CMDQ_ERR);
break;
}
@@ -416,6 +609,15 @@ static int arm_vsmmu_handle_cmds(struct virt_smmu *smmu)
ret);
dump_smmu_command(command);
}
+
+ /*
+ * Stop consuming the command queue if a command error occurred.
+ * CMDQ_CONS continues to point at the offending command until the
+ * error is acknowledged.
+ */
+ if ( (smmu->gerror ^ smmu->gerrorn) & GERROR_CMDQ_ERR )
+ break;
+
queue_inc_cons(q);
}
@@ -454,6 +656,7 @@ static int vsmmuv3_mmio_write(struct vcpu *v, mmio_info_t
*info,
vreg_reg32_update(®32, r, info);
smmu->evtq.cons = reg32;
spin_unlock(&smmu->evt_queue_lock);
+ arm_vsmmu_update_irq(smmu);
break;
default:
printk(XENLOG_G_ERR
@@ -584,6 +787,7 @@ static int vsmmuv3_mmio_write(struct vcpu *v, mmio_info_t
*info,
vreg_reg32_update(®32, r, info);
smmu->irq_ctrl = reg32;
spin_unlock(&smmu->irq_cfg_lock);
+ arm_vsmmu_update_irq(smmu);
break;
case VREG64(ARM_SMMU_GERROR_IRQ_CFG0):
@@ -608,11 +812,20 @@ static int vsmmuv3_mmio_write(struct vcpu *v, mmio_info_t
*info,
case VREG32(ARM_SMMU_GERRORN):
if ( dabt.size != DABT_WORD ) goto bad_width;
+ /*
+ * Acquire cmd_queue_lock before updating GERRORN to serialize
error
+ * acknowledgement with command processing and ensure that repaired
+ * commands are not skipped.
+ */
+ spin_lock(&smmu->cmd_queue_lock);
spin_lock(&smmu->gerror_lock);
reg32 = smmu->gerrorn;
vreg_reg32_update(®32, r, info);
smmu->gerrorn = reg32;
spin_unlock(&smmu->gerror_lock);
+ arm_vsmmu_update_irq(smmu);
+ arm_vsmmu_handle_cmds(smmu);
+ spin_unlock(&smmu->cmd_queue_lock);
break;
default:
@@ -916,9 +1129,10 @@ static const struct mmio_handler_ops vsmmuv3_mmio_handler
= {
.write = vsmmuv3_mmio_write,
};
-static int vsmmuv3_init_single(struct domain *d, paddr_t addr, paddr_t size,
- uint32_t features)
+static int vsmmuv3_init_single(struct domain *d, paddr_t addr,
+ paddr_t size, uint32_t virq, uint32_t features)
{
+ int ret;
struct virt_smmu *smmu;
smmu = xzalloc(struct virt_smmu);
@@ -927,6 +1141,7 @@ static int vsmmuv3_init_single(struct domain *d, paddr_t
addr, paddr_t size,
smmu->d = d;
smmu->features = features;
+ smmu->virq = virq;
smmu->cmdq.ent_size = CMDQ_ENT_DWORDS * DWORDS_BYTES;
smmu->evtq.ent_size = EVTQ_ENT_DWORDS * DWORDS_BYTES;
@@ -939,6 +1154,15 @@ static int vsmmuv3_init_single(struct domain *d, paddr_t
addr, paddr_t size,
spin_lock_init(&smmu->strtab_cfg_lock);
spin_lock_init(&smmu->irq_cfg_lock);
+ ret = vgic_reserve_virq(d, virq);
+ if ( !ret )
+ {
+ printk(XENLOG_G_ERR
+ "vSMMUv3: Failed to reserve domain vIRQ %u\n", virq);
+ xfree(smmu);
+ return -EINVAL;
+ }
+
register_mmio_handler(d, &vsmmuv3_mmio_handler, addr, size, smmu);
/* Register the vIOMMU to be able to clean it up later. */
@@ -990,7 +1214,7 @@ static int domain_vsmmuv3_init(struct domain *d)
{
smmu = (const struct arm_smmu_device *) hw_iommu->iommu;
ret = vsmmuv3_init_single(d, hw_iommu->addr, hw_iommu->size,
- smmu->features);
+ hw_iommu->irq, smmu->features);
if ( ret )
return ret;
}
@@ -1033,7 +1257,7 @@ static int domain_vsmmuv3_init(struct domain *d)
}
ret = vsmmuv3_init_single(d, GUEST_VSMMUV3_BASE, GUEST_VSMMUV3_SIZE,
- features);
+ GUEST_VSMMU_SPI, features);
if ( ret )
return ret;
}
diff --git a/xen/include/public/arch-arm.h b/xen/include/public/arch-arm.h
index 7c76f2afd4..35cdbbb867 100644
--- a/xen/include/public/arch-arm.h
+++ b/xen/include/public/arch-arm.h
@@ -524,6 +524,8 @@ typedef uint64_t xen_callback_t;
#define GUEST_VIRTIO_MMIO_SPI_FIRST 33
#define GUEST_VIRTIO_MMIO_SPI_LAST 43
+#define GUEST_VSMMU_SPI 44
+
/*
* SGI is the preferred delivery mechanism of FF-A pending notifications or
* schedule recveive interrupt. SGIs 8-15 are normally not used by a guest
--
2.43.0
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |