|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [PATCH v9 02/13] xen/arm: gic-v2: Implement GIC suspend/resume functions
From: Mirela Simonovic <mirela.simonovic@xxxxxxxxxx>
System suspend may lead to a state where GIC would be powered down.
Therefore, Xen should save/restore the context of GIC on suspend/resume.
Note that the context consists of states of registers which are
controlled by the hypervisor. Other GIC registers which are accessible
by guests are saved/restored on context switch.
Transient physical SGI pending state (GICD_CPENDSGIRn/GICD_SPENDSGIRn)
is intentionally excluded. CPU-interface active-priority state is also
not restored across suspend/resume. Xen reaches the final suspend path
at a quiescent point, so there is no active-priority execution context
to replay after resume. Enforce this with a runtime check after
disabling the CPU interface: if any implemented GICC_APRn word is still
non-zero, restore GICC_CTLR and abort suspend with -EBUSY.
This does not apply to distributor active state. With GICv2 EOImode==1,
EOIR only drops the interrupt priority; final deactivation is a separate
step. For guest-routed interrupts, Xen can have already EOIed the physical
IRQ while deactivation is still pending on the vGIC/GICV path. Therefore
GICD_ISACTIVER is preserved as architectural in-flight interrupt state.
Signed-off-by: Mirela Simonovic <mirela.simonovic@xxxxxxxxxx>
Signed-off-by: Saeed Nowshadi <saeed.nowshadi@xxxxxxxxxx>
Signed-off-by: Mykyta Poturai <mykyta_poturai@xxxxxxxx>
Signed-off-by: Mykola Kvach <mykola_kvach@xxxxxxxx>
---
Changes in V9:
- Skip saving/restoring GICD_ITARGETSR0..7 because SGI/PPI target
registers hold no state (read-only on MP, RAZ/WI on UP).
- Add a runtime GICC_APRn quiescence check after disabling the CPU
interface, and restore GICC_CTLR before returning -EBUSY.
Changes in V8:
- disable cpu interface + distributor before suspend
- change 0xffffffff to GENMASK;
- cosmetic changes;
Changes in V7:
- Allocate one contiguous memory block for the GICv2 dist suspend context.
- gicv2_resume() no longer unconditionally re-enables the distributor/CPU
interface; it now writes back the saved CTLR values as-is.
- gicv2_alloc_context() now returns 0 on success and panics on failure,
since suspend context allocation is not recoverable.
---
xen/arch/arm/gic-v2.c | 181 +++++++++++++++++++++++++++++++++
xen/arch/arm/gic.c | 29 ++++++
xen/arch/arm/include/asm/gic.h | 12 +++
3 files changed, 222 insertions(+)
diff --git a/xen/arch/arm/gic-v2.c b/xen/arch/arm/gic-v2.c
index 43a379fdda..1970c4feb9 100644
--- a/xen/arch/arm/gic-v2.c
+++ b/xen/arch/arm/gic-v2.c
@@ -1108,6 +1108,178 @@ static int gicv2_iomem_deny_access(struct domain *d)
return iomem_deny_access(d, mfn, mfn + nr - 1);
}
+#ifdef CONFIG_SYSTEM_SUSPEND
+
+/* This struct represents block of 32 IRQs */
+struct irq_block {
+ uint32_t icfgr[2]; /* 2 registers of 16 IRQs each */
+ uint32_t ipriorityr[8];
+ uint32_t isenabler;
+ uint32_t isactiver;
+ uint32_t itargetsr[8];
+};
+
+/* GICv2 registers to be saved/restored on system suspend/resume */
+struct gicv2_context {
+ /* GICC context */
+ struct cpu_ctx {
+ uint32_t ctlr;
+ uint32_t pmr;
+ uint32_t bpr;
+ } cpu;
+
+ /* GICD context */
+ struct dist_ctx {
+ uint32_t ctlr;
+ /* Includes banked SGI/PPI state for the boot CPU. */
+ struct irq_block *irqs;
+ } dist;
+};
+
+static struct gicv2_context gic_ctx;
+
+#define GICV2_NR_APRS 4
+
+static int gicv2_suspend(void)
+{
+ unsigned int i, blocks = DIV_ROUND_UP(gicv2_info.nr_lines, 32);
+
+ /* Save GICC_CTLR configuration. */
+ gic_ctx.cpu.ctlr = readl_gicc(GICC_CTLR);
+
+ /* Quiesce the GIC CPU interface before suspend. */
+ gicv2_cpu_disable();
+
+ /*
+ * Check the active-priority state for the group Xen drives through the
+ * CPU interface. GICC_CTL_ENABLE enables Group 0 without SecurityExtn and
+ * Group 1 in Xen's Non-secure view with SecurityExtn, and in both cases
+ * the relevant state is visible through GICC_APRn. Only zero/non-zero
+ * matters here, not the implementation-defined bit layout.
+ */
+ for ( i = 0; i < GICV2_NR_APRS; i++ )
+ {
+ uint32_t apr = readl_gicc(GICC_APR + i * 4);
+
+ if ( !apr )
+ continue;
+
+ printk(XENLOG_ERR "GICv2: suspend aborted: GICC_APR%u=%#08x\n", i,
apr);
+ writel_gicc(gic_ctx.cpu.ctlr, GICC_CTLR);
+ return -EBUSY;
+ }
+
+ /* Save GICD configuration */
+ gic_ctx.dist.ctlr = readl_gicd(GICD_CTLR);
+ writel_gicd(0, GICD_CTLR);
+
+ gic_ctx.cpu.pmr = readl_gicc(GICC_PMR);
+ gic_ctx.cpu.bpr = readl_gicc(GICC_BPR);
+
+ for ( i = 0; i < blocks; i++ )
+ {
+ struct irq_block *irqs = gic_ctx.dist.irqs + i;
+ size_t j, off = i * sizeof(irqs->isenabler);
+
+ irqs->isenabler = readl_gicd(GICD_ISENABLER + off);
+
+ /*
+ * Save distributor active state as part of the hypervisor-owned
+ * physical interrupt state. In GICv2 EOImode==1, EOIR only drops the
+ * priority; final deactivation is separate. For guest-routed
+ * interrupts, Xen may have EOIed the physical IRQ while the guest/vGIC
+ * side still owns the deactivate step. Therefore GICD_ISACTIVER can
+ * legitimately remain set even though transient SGI pending state and
+ * CPU-interface active-priority state are expected to be quiesced
here.
+ */
+ irqs->isactiver = readl_gicd(GICD_ISACTIVER + off);
+
+ off = i * sizeof(irqs->ipriorityr);
+ for ( j = 0; j < ARRAY_SIZE(irqs->ipriorityr); j++ )
+ irqs->ipriorityr[j] = readl_gicd(GICD_IPRIORITYR + off + j * 4);
+
+ /*
+ * GICD_ITARGETSR0..7 cover SGIs/PPIs and hold no state to save:
+ * they are read-only on multiprocessor implementations and RAZ/WI
+ * on uniprocessor implementations.
+ */
+ if ( i )
+ {
+ off = i * sizeof(irqs->itargetsr);
+ for ( j = 0; j < ARRAY_SIZE(irqs->itargetsr); j++ )
+ irqs->itargetsr[j] = readl_gicd(GICD_ITARGETSR + off + j * 4);
+ }
+
+ off = i * sizeof(irqs->icfgr);
+ for ( j = 0; j < ARRAY_SIZE(irqs->icfgr); j++ )
+ irqs->icfgr[j] = readl_gicd(GICD_ICFGR + off + j * 4);
+ }
+
+ return 0;
+}
+
+static void gicv2_resume(void)
+{
+ unsigned int i, blocks = DIV_ROUND_UP(gicv2_info.nr_lines, 32);
+
+ gicv2_cpu_disable();
+ /* Disable distributor */
+ writel_gicd(0, GICD_CTLR);
+
+ for ( i = 0; i < blocks; i++ )
+ {
+ struct irq_block *irqs = gic_ctx.dist.irqs + i;
+ size_t j, off = i * sizeof(irqs->isenabler);
+
+ writel_gicd(GENMASK(31, 0), GICD_ICENABLER + off);
+ writel_gicd(irqs->isenabler, GICD_ISENABLER + off);
+
+ writel_gicd(GENMASK(31, 0), GICD_ICACTIVER + off);
+ writel_gicd(irqs->isactiver, GICD_ISACTIVER + off);
+
+ off = i * sizeof(irqs->ipriorityr);
+ for ( j = 0; j < ARRAY_SIZE(irqs->ipriorityr); j++ )
+ writel_gicd(irqs->ipriorityr[j], GICD_IPRIORITYR + off + j * 4);
+
+ /*
+ * GICD_ITARGETSR0..7 cover SGIs/PPIs and hold no state to save:
+ * they are read-only on multiprocessor implementations and RAZ/WI
+ * on uniprocessor implementations.
+ */
+ if ( i )
+ {
+ off = i * sizeof(irqs->itargetsr);
+ for ( j = 0; j < ARRAY_SIZE(irqs->itargetsr); j++ )
+ writel_gicd(irqs->itargetsr[j], GICD_ITARGETSR + off + j * 4);
+ }
+
+ off = i * sizeof(irqs->icfgr);
+ for ( j = 0; j < ARRAY_SIZE(irqs->icfgr); j++ )
+ writel_gicd(irqs->icfgr[j], GICD_ICFGR + off + j * 4);
+ }
+
+ /* Make sure all registers are restored and enable distributor */
+ writel_gicd(gic_ctx.dist.ctlr, GICD_CTLR);
+
+ /* Restore GIC CPU interface configuration */
+ writel_gicc(gic_ctx.cpu.pmr, GICC_PMR);
+ writel_gicc(gic_ctx.cpu.bpr, GICC_BPR);
+
+ /* Enable GIC CPU interface */
+ writel_gicc(gic_ctx.cpu.ctlr, GICC_CTLR);
+}
+
+static void __init gicv2_alloc_context(void)
+{
+ uint32_t blocks = DIV_ROUND_UP(gicv2_info.nr_lines, 32);
+
+ gic_ctx.dist.irqs = xzalloc_array(struct irq_block, blocks);
+ if ( !gic_ctx.dist.irqs )
+ panic("Failed to allocate memory for GICv2 suspend context\n");
+}
+
+#endif /* CONFIG_SYSTEM_SUSPEND */
+
#ifdef CONFIG_ACPI
static unsigned long gicv2_get_hwdom_extra_madt_size(const struct domain *d)
{
@@ -1312,6 +1484,11 @@ static int __init gicv2_init(void)
spin_unlock(&gicv2.lock);
+#ifdef CONFIG_SYSTEM_SUSPEND
+ /* Allocate memory to be used for saving GIC context during the suspend */
+ gicv2_alloc_context();
+#endif /* CONFIG_SYSTEM_SUSPEND */
+
return 0;
}
@@ -1355,6 +1532,10 @@ static const struct gic_hw_operations gicv2_ops = {
.map_hwdom_extra_mappings = gicv2_map_hwdom_extra_mappings,
.iomem_deny_access = gicv2_iomem_deny_access,
.do_LPI = gicv2_do_LPI,
+#ifdef CONFIG_SYSTEM_SUSPEND
+ .suspend = gicv2_suspend,
+ .resume = gicv2_resume,
+#endif /* CONFIG_SYSTEM_SUSPEND */
};
/* Set up the GIC */
diff --git a/xen/arch/arm/gic.c b/xen/arch/arm/gic.c
index ee75258fc3..7727ffed5a 100644
--- a/xen/arch/arm/gic.c
+++ b/xen/arch/arm/gic.c
@@ -432,6 +432,35 @@ int gic_iomem_deny_access(struct domain *d)
return gic_hw_ops->iomem_deny_access(d);
}
+#ifdef CONFIG_SYSTEM_SUSPEND
+
+int gic_suspend(void)
+{
+ /* Must be called by boot CPU#0 with interrupts disabled */
+ ASSERT(!local_irq_is_enabled());
+ ASSERT(!smp_processor_id());
+
+ if ( !gic_hw_ops->suspend || !gic_hw_ops->resume )
+ return -ENOSYS;
+
+ return gic_hw_ops->suspend();
+}
+
+void gic_resume(void)
+{
+ /*
+ * Must be called by boot CPU#0 with interrupts disabled after gic_suspend
+ * has returned successfully.
+ */
+ ASSERT(!local_irq_is_enabled());
+ ASSERT(!smp_processor_id());
+ ASSERT(gic_hw_ops->resume);
+
+ gic_hw_ops->resume();
+}
+
+#endif /* CONFIG_SYSTEM_SUSPEND */
+
static int cpu_gic_callback(struct notifier_block *nfb,
unsigned long action,
void *hcpu)
diff --git a/xen/arch/arm/include/asm/gic.h b/xen/arch/arm/include/asm/gic.h
index ff22dea40d..fbf0d69edd 100644
--- a/xen/arch/arm/include/asm/gic.h
+++ b/xen/arch/arm/include/asm/gic.h
@@ -301,6 +301,12 @@ extern int gicv_setup(struct domain *d);
extern void gic_save_state(struct vcpu *v);
extern void gic_restore_state(struct vcpu *v);
+#ifdef CONFIG_SYSTEM_SUSPEND
+/* Suspend/resume */
+extern int gic_suspend(void);
+extern void gic_resume(void);
+#endif /* CONFIG_SYSTEM_SUSPEND */
+
/* SGI (AKA IPIs) */
enum gic_sgi {
GIC_SGI_EVENT_CHECK,
@@ -444,6 +450,12 @@ struct gic_hw_operations {
int (*iomem_deny_access)(struct domain *d);
/* Handle LPIs, which require special handling */
void (*do_LPI)(unsigned int lpi);
+#ifdef CONFIG_SYSTEM_SUSPEND
+ /* Save GIC configuration due to the system suspend */
+ int (*suspend)(void);
+ /* Restore GIC configuration due to the system resume */
+ void (*resume)(void);
+#endif /* CONFIG_SYSTEM_SUSPEND */
};
extern const struct gic_hw_operations *gic_hw_ops;
--
2.43.0
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |