From: Mirela Simonovic <[email protected]> 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. Signed-off-by: Mirela Simonovic <[email protected]> Signed-off-by: Saeed Nowshadi <[email protected]> Signed-off-by: Mykyta Poturai <[email protected]> Signed-off-by: Mykola Kvach <[email protected]> --- 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 | 126 +++++++++++++++++++++++++++++++++ xen/arch/arm/gic.c | 29 ++++++++ xen/arch/arm/include/asm/gic.h | 12 ++++ 3 files changed, 167 insertions(+) diff --git a/xen/arch/arm/gic-v2.c b/xen/arch/arm/gic-v2.c index b23e72a3d0..0b2f7b3862 100644 --- a/xen/arch/arm/gic-v2.c +++ b/xen/arch/arm/gic-v2.c @@ -1098,6 +1098,123 @@ static int gicv2_iomem_deny_access(struct domain *d) return iomem_deny_access(d, mfn, mfn + nr); } +#ifdef CONFIG_SYSTEM_SUSPEND + +/* This struct represent 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; + struct irq_block *irqs; + } dist; +}; + +static struct gicv2_context gic_ctx; + +static int gicv2_suspend(void) +{ + unsigned int i, blocks = DIV_ROUND_UP(gicv2_info.nr_lines, 32); + + /* Save GICC configuration */ + gic_ctx.cpu.ctlr = readl_gicc(GICC_CTLR); + gic_ctx.cpu.pmr = readl_gicc(GICC_PMR); + gic_ctx.cpu.bpr = readl_gicc(GICC_BPR); + + /* Save GICD configuration */ + gic_ctx.dist.ctlr = readl_gicd(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); + + irqs->isenabler = readl_gicd(GICD_ISENABLER + off); + 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); + 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(0xffffffffU, GICD_ICENABLER + off); + writel_gicd(irqs->isenabler, GICD_ISENABLER + off); + + writel_gicd(0xffffffffU, 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); + 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) { @@ -1302,6 +1419,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; } @@ -1345,6 +1467,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 8e713aa477..8e8f4ac4c5 100644 --- a/xen/arch/arm/include/asm/gic.h +++ b/xen/arch/arm/include/asm/gic.h @@ -280,6 +280,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, @@ -423,6 +429,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
