[PATCH v6 03/13] xen/arm: gic-v3: Implement GICv3 suspend/resume functions

Mykola Kvach posted 13 patches 1 month, 4 weeks ago
[PATCH v6 03/13] xen/arm: gic-v3: Implement GICv3 suspend/resume functions
Posted by Mykola Kvach 1 month, 4 weeks ago
From: Mykola Kvach <mykola_kvach@epam.com>

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: Mykola Kvach <mykola_kvach@epam.com>
---
Changes in V6:
- Drop gicv3_save/restore_state since it is already handled during vCPU
  context switch.
- The comment about systems without SPIs is clarified for readability.
- Error and warning messages related to suspend context allocation are unified
  and now use printk() with XENLOG_ERR for consistency.
- The check for suspend context allocation in gicv3_resume() is removed,
  as it is handled earlier in the suspend path.
- The loop for saving and restoring PPI/SGI priorities is corrected to use
  the proper increment.
- The gicv3_suspend() function now prints an explicit error if ITS suspend
  support is not implemented, and returns ENOSYS in this case.
- The GICD_CTLR_DS bit definition is added to gic_v3_defs.h.
- The comment for GICR_WAKER access is expanded to reference the relevant
  ARM specification section and clarify the RAZ/WI behavior for Non-secure
  accesses.
- Cleanup active and enable registers before restoring.
---
 xen/arch/arm/gic-v3-lpi.c              |   3 +
 xen/arch/arm/gic-v3.c                  | 235 +++++++++++++++++++++++++
 xen/arch/arm/include/asm/gic_v3_defs.h |   1 +
 3 files changed, 239 insertions(+)

diff --git a/xen/arch/arm/gic-v3-lpi.c b/xen/arch/arm/gic-v3-lpi.c
index de5052e5cf..61a6e18303 100644
--- a/xen/arch/arm/gic-v3-lpi.c
+++ b/xen/arch/arm/gic-v3-lpi.c
@@ -391,6 +391,9 @@ static int cpu_callback(struct notifier_block *nfb, unsigned long action,
     switch ( action )
     {
     case CPU_UP_PREPARE:
+        if ( system_state == SYS_STATE_resume )
+            break;
+
         rc = gicv3_lpi_allocate_pendtable(cpu);
         if ( rc )
             printk(XENLOG_ERR "Unable to allocate the pendtable for CPU%lu\n",
diff --git a/xen/arch/arm/gic-v3.c b/xen/arch/arm/gic-v3.c
index cd3e1acf79..9f1be7e905 100644
--- a/xen/arch/arm/gic-v3.c
+++ b/xen/arch/arm/gic-v3.c
@@ -1776,6 +1776,233 @@ static bool gic_dist_supports_lpis(void)
     return (readl_relaxed(GICD + GICD_TYPER) & GICD_TYPE_LPIS);
 }
 
+#ifdef CONFIG_SYSTEM_SUSPEND
+
+/* GICv3 registers to be saved/restored on system suspend/resume */
+struct gicv3_ctx {
+    struct dist_ctx {
+        uint32_t ctlr;
+        /*
+         * This struct represent block of 32 IRQs
+         * TODO: store extended SPI configuration (GICv3.1+)
+         */
+        struct irq_regs {
+            uint32_t icfgr[2];
+            uint32_t ipriorityr[8];
+            uint64_t irouter[32];
+            uint32_t isactiver;
+            uint32_t isenabler;
+        } *irqs;
+    } dist;
+
+    /* have only one rdist structure for last running CPU during suspend */
+    struct redist_ctx {
+        uint32_t ctlr;
+        /* TODO: handle case when we have more than 16 PPIs (GICv3.1+) */
+        uint32_t icfgr[2];
+        uint32_t igroupr;
+        uint32_t ipriorityr[8];
+        uint32_t isactiver;
+        uint32_t isenabler;
+    } rdist;
+
+    struct cpu_ctx {
+        uint32_t ctlr;
+        uint32_t pmr;
+        uint32_t bpr;
+        uint32_t sre_el2;
+        uint32_t grpen;
+    } cpu;
+};
+
+static struct gicv3_ctx gicv3_ctx;
+
+static void __init gicv3_alloc_context(void)
+{
+    uint32_t blocks = DIV_ROUND_UP(gicv3_info.nr_lines, 32);
+
+    /* We don't have ITS support for suspend */
+    if ( gicv3_its_host_has_its() )
+        return;
+
+    /* The spec allows for systems without any SPIs */
+    if ( blocks > 1 )
+    {
+        gicv3_ctx.dist.irqs = xzalloc_array(typeof(*gicv3_ctx.dist.irqs),
+                                            blocks - 1);
+        if ( !gicv3_ctx.dist.irqs )
+            printk(XENLOG_ERR "Failed to allocate memory for GICv3 suspend context\n");
+    }
+}
+
+static void gicv3_disable_redist(void)
+{
+    void __iomem* waker = GICD_RDIST_BASE + GICR_WAKER;
+
+    /*
+     * Avoid infinite loop if Non-secure does not have access to GICR_WAKER.
+     * See Arm IHI 0069H.b, 12.11.42 GICR_WAKER:
+     *     When GICD_CTLR.DS == 0 and an access is Non-secure accesses to this
+     *     register are RAZ/WI.
+     */
+    if ( !(readl_relaxed(GICD + GICD_CTLR) & GICD_CTLR_DS) )
+        return;
+
+    writel_relaxed(readl_relaxed(waker) | GICR_WAKER_ProcessorSleep, waker);
+    while ( (readl_relaxed(waker) & GICR_WAKER_ChildrenAsleep) == 0 );
+}
+
+static int gicv3_suspend(void)
+{
+    unsigned int i;
+    void __iomem *base;
+    typeof(gicv3_ctx.rdist)* rdist = &gicv3_ctx.rdist;
+
+    /* TODO: implement support for ITS */
+    if ( gicv3_its_host_has_its() )
+    {
+        printk(XENLOG_ERR "GICv3: ITS suspend support is not implemented\n");
+        return -ENOSYS;
+    }
+
+    if ( !gicv3_ctx.dist.irqs && gicv3_info.nr_lines > NR_GIC_LOCAL_IRQS )
+    {
+        printk(XENLOG_ERR "GICv3: suspend context is not allocated!\n");
+        return -ENOMEM;
+    }
+
+    /* Save GICC configuration */
+    gicv3_ctx.cpu.ctlr     = READ_SYSREG(ICC_CTLR_EL1);
+    gicv3_ctx.cpu.pmr      = READ_SYSREG(ICC_PMR_EL1);
+    gicv3_ctx.cpu.bpr      = READ_SYSREG(ICC_BPR1_EL1);
+    gicv3_ctx.cpu.sre_el2  = READ_SYSREG(ICC_SRE_EL2);
+    gicv3_ctx.cpu.grpen    = READ_SYSREG(ICC_IGRPEN1_EL1);
+
+    gicv3_disable_interface();
+    gicv3_disable_redist();
+
+    /* Save GICR configuration */
+    gicv3_redist_wait_for_rwp();
+
+    base = GICD_RDIST_SGI_BASE;
+
+    rdist->ctlr = readl_relaxed(base + GICR_CTLR);
+
+    /* Save priority on PPI and SGI interrupts */
+    for ( i = 0; i < NR_GIC_LOCAL_IRQS / 4; i++ )
+        rdist->ipriorityr[i] = readl_relaxed(base + GICR_IPRIORITYR0 + 4 * i);
+
+    rdist->isactiver = readl_relaxed(base + GICR_ISACTIVER0);
+    rdist->isenabler = readl_relaxed(base + GICR_ISENABLER0);
+    rdist->igroupr   = readl_relaxed(base + GICR_IGROUPR0);
+    rdist->icfgr[0]  = readl_relaxed(base + GICR_ICFGR0);
+    rdist->icfgr[1]  = readl_relaxed(base + GICR_ICFGR1);
+
+    /* Save GICD configuration */
+    gicv3_dist_wait_for_rwp();
+    gicv3_ctx.dist.ctlr = readl_relaxed(GICD + GICD_CTLR);
+
+    for ( i = 1; i < DIV_ROUND_UP(gicv3_info.nr_lines, 32); i++ )
+    {
+        typeof(gicv3_ctx.dist.irqs) irqs = gicv3_ctx.dist.irqs + i - 1;
+        unsigned int irq;
+
+        base = GICD + GICD_ICFGR + 8 * i;
+        irqs->icfgr[0] = readl_relaxed(base);
+        irqs->icfgr[1] = readl_relaxed(base + 4);
+
+        base = GICD + GICD_IPRIORITYR + 32 * i;
+        for ( irq = 0; irq < 8; irq++ )
+            irqs->ipriorityr[irq] = readl_relaxed(base + 4 * irq);
+
+        base = GICD + GICD_IROUTER + 32 * i;
+        for ( irq = 0; irq < 32; irq++ )
+            irqs->irouter[irq] = readq_relaxed_non_atomic(base + 8 * irq);
+
+        irqs->isactiver = readl_relaxed(GICD + GICD_ISACTIVER + 4 * i);
+        irqs->isenabler = readl_relaxed(GICD + GICD_ISENABLER + 4 * i);
+    }
+
+    return 0;
+}
+
+static void gicv3_resume(void)
+{
+    unsigned int i;
+    void __iomem *base;
+    typeof(gicv3_ctx.rdist)* rdist = &gicv3_ctx.rdist;
+
+    writel_relaxed(0, GICD + GICD_CTLR);
+
+    for ( i = NR_GIC_LOCAL_IRQS; i < gicv3_info.nr_lines; i += 32 )
+        writel_relaxed(GENMASK(31, 0), GICD + GICD_IGROUPR + (i / 32) * 4);
+
+    for ( i = 1; i < DIV_ROUND_UP(gicv3_info.nr_lines, 32); i++ )
+    {
+        typeof(gicv3_ctx.dist.irqs) irqs = gicv3_ctx.dist.irqs + i - 1;
+        unsigned int irq;
+
+        base = GICD + GICD_ICFGR + 8 * i;
+        writel_relaxed(irqs->icfgr[0], base);
+        writel_relaxed(irqs->icfgr[1], base + 4);
+
+        base = GICD + GICD_IPRIORITYR + 32 * i;
+        for ( irq = 0; irq < 8; irq++ )
+            writel_relaxed(irqs->ipriorityr[irq], base + 4 * irq);
+
+        base = GICD + GICD_IROUTER + 32 * i;
+        for ( irq = 0; irq < 32; irq++ )
+            writeq_relaxed_non_atomic(irqs->irouter[irq], base + 8 * irq);
+
+        writel_relaxed(GENMASK(31, 0), GICD + GICD_ICENABLER + i * 4);
+        writel_relaxed(irqs->isenabler, GICD + GICD_ISENABLER + i * 4);
+
+        writel_relaxed(GENMASK(31, 0), GICD + GICD_ICACTIVER + i * 4);
+        writel_relaxed(irqs->isactiver, GICD + GICD_ISACTIVER + i * 4);
+    }
+
+    writel_relaxed(gicv3_ctx.dist.ctlr, GICD + GICD_CTLR);
+    gicv3_dist_wait_for_rwp();
+
+    /* Restore GICR (Redistributor) configuration */
+    gicv3_enable_redist();
+
+    base = GICD_RDIST_SGI_BASE;
+
+    writel_relaxed(0xffffffff, base + GICR_ICENABLER0);
+    gicv3_redist_wait_for_rwp();
+
+    for ( i = 0; i < NR_GIC_LOCAL_IRQS / 4; i++ )
+        writel_relaxed(rdist->ipriorityr[i], base + GICR_IPRIORITYR0 + i * 4);
+
+    writel_relaxed(rdist->isactiver, base + GICR_ISACTIVER0);
+
+    writel_relaxed(rdist->igroupr,  base + GICR_IGROUPR0);
+    writel_relaxed(rdist->icfgr[0], base + GICR_ICFGR0);
+    writel_relaxed(rdist->icfgr[1], base + GICR_ICFGR1);
+
+    gicv3_redist_wait_for_rwp();
+
+    writel_relaxed(rdist->isenabler, base + GICR_ISENABLER0);
+    writel_relaxed(rdist->ctlr, GICD_RDIST_BASE + GICR_CTLR);
+
+    gicv3_redist_wait_for_rwp();
+
+    WRITE_SYSREG(gicv3_ctx.cpu.sre_el2, ICC_SRE_EL2);
+    isb();
+
+    /* Restore CPU interface (System registers) */
+    WRITE_SYSREG(gicv3_ctx.cpu.pmr,   ICC_PMR_EL1);
+    WRITE_SYSREG(gicv3_ctx.cpu.bpr,   ICC_BPR1_EL1);
+    WRITE_SYSREG(gicv3_ctx.cpu.ctlr,  ICC_CTLR_EL1);
+    WRITE_SYSREG(gicv3_ctx.cpu.grpen, ICC_IGRPEN1_EL1);
+    isb();
+
+    gicv3_hyp_init();
+}
+
+#endif /* CONFIG_SYSTEM_SUSPEND */
+
 /* Set up the GIC */
 static int __init gicv3_init(void)
 {
@@ -1850,6 +2077,10 @@ static int __init gicv3_init(void)
 
     gicv3_hyp_init();
 
+#ifdef CONFIG_SYSTEM_SUSPEND
+    gicv3_alloc_context();
+#endif
+
 out:
     spin_unlock(&gicv3.lock);
 
@@ -1889,6 +2120,10 @@ static const struct gic_hw_operations gicv3_ops = {
 #endif
     .iomem_deny_access   = gicv3_iomem_deny_access,
     .do_LPI              = gicv3_do_LPI,
+#ifdef CONFIG_SYSTEM_SUSPEND
+    .suspend             = gicv3_suspend,
+    .resume              = gicv3_resume,
+#endif
 };
 
 static int __init gicv3_dt_preinit(struct dt_device_node *node, const void *data)
diff --git a/xen/arch/arm/include/asm/gic_v3_defs.h b/xen/arch/arm/include/asm/gic_v3_defs.h
index 2af093e774..7e86309acb 100644
--- a/xen/arch/arm/include/asm/gic_v3_defs.h
+++ b/xen/arch/arm/include/asm/gic_v3_defs.h
@@ -56,6 +56,7 @@
 #define GICD_TYPE_LPIS               (1U << 17)
 
 #define GICD_CTLR_RWP                (1UL << 31)
+#define GICD_CTLR_DS                 (1U << 6)
 #define GICD_CTLR_ARE_NS             (1U << 4)
 #define GICD_CTLR_ENABLE_G1A         (1U << 1)
 #define GICD_CTLR_ENABLE_G1          (1U << 0)
-- 
2.48.1
Re: [PATCH v6 03/13] xen/arm: gic-v3: Implement GICv3 suspend/resume functions
Posted by Oleksandr Tyshchenko 1 month, 4 weeks ago

On 02.09.25 01:10, Mykola Kvach wrote:

Hello Mykola


> From: Mykola Kvach <mykola_kvach@epam.com>
> 
> 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: Mykola Kvach <mykola_kvach@epam.com>
> ---
> Changes in V6:
> - Drop gicv3_save/restore_state since it is already handled during vCPU
>    context switch.
> - The comment about systems without SPIs is clarified for readability.
> - Error and warning messages related to suspend context allocation are unified
>    and now use printk() with XENLOG_ERR for consistency.
> - The check for suspend context allocation in gicv3_resume() is removed,
>    as it is handled earlier in the suspend path.
> - The loop for saving and restoring PPI/SGI priorities is corrected to use
>    the proper increment.
> - The gicv3_suspend() function now prints an explicit error if ITS suspend
>    support is not implemented, and returns ENOSYS in this case.
> - The GICD_CTLR_DS bit definition is added to gic_v3_defs.h.
> - The comment for GICR_WAKER access is expanded to reference the relevant
>    ARM specification section and clarify the RAZ/WI behavior for Non-secure
>    accesses.
> - Cleanup active and enable registers before restoring.
> ---
>   xen/arch/arm/gic-v3-lpi.c              |   3 +
>   xen/arch/arm/gic-v3.c                  | 235 +++++++++++++++++++++++++
>   xen/arch/arm/include/asm/gic_v3_defs.h |   1 +
>   3 files changed, 239 insertions(+)
> 
> diff --git a/xen/arch/arm/gic-v3-lpi.c b/xen/arch/arm/gic-v3-lpi.c
> index de5052e5cf..61a6e18303 100644
> --- a/xen/arch/arm/gic-v3-lpi.c
> +++ b/xen/arch/arm/gic-v3-lpi.c
> @@ -391,6 +391,9 @@ static int cpu_callback(struct notifier_block *nfb, unsigned long action,
>       switch ( action )
>       {
>       case CPU_UP_PREPARE:
> +        if ( system_state == SYS_STATE_resume )
> +            break;
> +
>           rc = gicv3_lpi_allocate_pendtable(cpu);
>           if ( rc )
>               printk(XENLOG_ERR "Unable to allocate the pendtable for CPU%lu\n",
> diff --git a/xen/arch/arm/gic-v3.c b/xen/arch/arm/gic-v3.c
> index cd3e1acf79..9f1be7e905 100644
> --- a/xen/arch/arm/gic-v3.c
> +++ b/xen/arch/arm/gic-v3.c
> @@ -1776,6 +1776,233 @@ static bool gic_dist_supports_lpis(void)
>       return (readl_relaxed(GICD + GICD_TYPER) & GICD_TYPE_LPIS);
>   }
>   
> +#ifdef CONFIG_SYSTEM_SUSPEND
> +
> +/* GICv3 registers to be saved/restored on system suspend/resume */
> +struct gicv3_ctx {
> +    struct dist_ctx {
> +        uint32_t ctlr;
> +        /*
> +         * This struct represent block of 32 IRQs
> +         * TODO: store extended SPI configuration (GICv3.1+)
> +         */
> +        struct irq_regs {
> +            uint32_t icfgr[2];
> +            uint32_t ipriorityr[8];
> +            uint64_t irouter[32];
> +            uint32_t isactiver;
> +            uint32_t isenabler;
> +        } *irqs;
> +    } dist;
> +
> +    /* have only one rdist structure for last running CPU during suspend */
> +    struct redist_ctx {
> +        uint32_t ctlr;
> +        /* TODO: handle case when we have more than 16 PPIs (GICv3.1+) */
> +        uint32_t icfgr[2];
> +        uint32_t igroupr;
> +        uint32_t ipriorityr[8];
> +        uint32_t isactiver;
> +        uint32_t isenabler;
> +    } rdist;
> +
> +    struct cpu_ctx {
> +        uint32_t ctlr;
> +        uint32_t pmr;
> +        uint32_t bpr;
> +        uint32_t sre_el2;
> +        uint32_t grpen;
> +    } cpu;
> +};
> +
> +static struct gicv3_ctx gicv3_ctx;
> +
> +static void __init gicv3_alloc_context(void)
> +{
> +    uint32_t blocks = DIV_ROUND_UP(gicv3_info.nr_lines, 32);
> +
> +    /* We don't have ITS support for suspend */
> +    if ( gicv3_its_host_has_its() )
> +        return;
> +
> +    /* The spec allows for systems without any SPIs */
> +    if ( blocks > 1 )
> +    {
> +        gicv3_ctx.dist.irqs = xzalloc_array(typeof(*gicv3_ctx.dist.irqs),
> +                                            blocks - 1);
> +        if ( !gicv3_ctx.dist.irqs )
> +            printk(XENLOG_ERR "Failed to allocate memory for GICv3 suspend context\n");
> +    }
> +}
> +
> +static void gicv3_disable_redist(void)
> +{
> +    void __iomem* waker = GICD_RDIST_BASE + GICR_WAKER;
> +
> +    /*
> +     * Avoid infinite loop if Non-secure does not have access to GICR_WAKER.
> +     * See Arm IHI 0069H.b, 12.11.42 GICR_WAKER:
> +     *     When GICD_CTLR.DS == 0 and an access is Non-secure accesses to this
> +     *     register are RAZ/WI.
> +     */
> +    if ( !(readl_relaxed(GICD + GICD_CTLR) & GICD_CTLR_DS) )
> +        return;
> +
> +    writel_relaxed(readl_relaxed(waker) | GICR_WAKER_ProcessorSleep, waker);
> +    while ( (readl_relaxed(waker) & GICR_WAKER_ChildrenAsleep) == 0 );
> +}
> +
> +static int gicv3_suspend(void)
> +{
> +    unsigned int i;
> +    void __iomem *base;
> +    typeof(gicv3_ctx.rdist)* rdist = &gicv3_ctx.rdist;
> +
> +    /* TODO: implement support for ITS */
> +    if ( gicv3_its_host_has_its() )
> +    {
> +        printk(XENLOG_ERR "GICv3: ITS suspend support is not implemented\n");
> +        return -ENOSYS;
> +    }
> +
> +    if ( !gicv3_ctx.dist.irqs && gicv3_info.nr_lines > NR_GIC_LOCAL_IRQS )
> +    {
> +        printk(XENLOG_ERR "GICv3: suspend context is not allocated!\n");
> +        return -ENOMEM;
> +    }
> +
> +    /* Save GICC configuration */
> +    gicv3_ctx.cpu.ctlr     = READ_SYSREG(ICC_CTLR_EL1);
> +    gicv3_ctx.cpu.pmr      = READ_SYSREG(ICC_PMR_EL1);
> +    gicv3_ctx.cpu.bpr      = READ_SYSREG(ICC_BPR1_EL1);
> +    gicv3_ctx.cpu.sre_el2  = READ_SYSREG(ICC_SRE_EL2);
> +    gicv3_ctx.cpu.grpen    = READ_SYSREG(ICC_IGRPEN1_EL1);
> +
> +    gicv3_disable_interface();
> +    gicv3_disable_redist();
> +
> +    /* Save GICR configuration */
> +    gicv3_redist_wait_for_rwp();
> +
> +    base = GICD_RDIST_SGI_BASE;
> +
> +    rdist->ctlr = readl_relaxed(base + GICR_CTLR);
> +
> +    /* Save priority on PPI and SGI interrupts */
> +    for ( i = 0; i < NR_GIC_LOCAL_IRQS / 4; i++ )
> +        rdist->ipriorityr[i] = readl_relaxed(base + GICR_IPRIORITYR0 + 4 * i);
> +
> +    rdist->isactiver = readl_relaxed(base + GICR_ISACTIVER0);
> +    rdist->isenabler = readl_relaxed(base + GICR_ISENABLER0);
> +    rdist->igroupr   = readl_relaxed(base + GICR_IGROUPR0);
> +    rdist->icfgr[0]  = readl_relaxed(base + GICR_ICFGR0);

GICR_ICFGR0 is for SGIs, which are always edge-triggered, so I am not 
sure that we need to save it here ...


> +    rdist->icfgr[1]  = readl_relaxed(base + GICR_ICFGR1);
> +
> +    /* Save GICD configuration */
> +    gicv3_dist_wait_for_rwp();
> +    gicv3_ctx.dist.ctlr = readl_relaxed(GICD + GICD_CTLR);
> +
> +    for ( i = 1; i < DIV_ROUND_UP(gicv3_info.nr_lines, 32); i++ )
> +    {
> +        typeof(gicv3_ctx.dist.irqs) irqs = gicv3_ctx.dist.irqs + i - 1;
> +        unsigned int irq;
> +
> +        base = GICD + GICD_ICFGR + 8 * i;
> +        irqs->icfgr[0] = readl_relaxed(base);
> +        irqs->icfgr[1] = readl_relaxed(base + 4);
> +
> +        base = GICD + GICD_IPRIORITYR + 32 * i;
> +        for ( irq = 0; irq < 8; irq++ )
> +            irqs->ipriorityr[irq] = readl_relaxed(base + 4 * irq);
> +
> +        base = GICD + GICD_IROUTER + 32 * i;
> +        for ( irq = 0; irq < 32; irq++ )
> +            irqs->irouter[irq] = readq_relaxed_non_atomic(base + 8 * irq);
> +
> +        irqs->isactiver = readl_relaxed(GICD + GICD_ISACTIVER + 4 * i);
> +        irqs->isenabler = readl_relaxed(GICD + GICD_ISENABLER + 4 * i);
> +    }
> +
> +    return 0;
> +}
> +
> +static void gicv3_resume(void)
> +{
> +    unsigned int i;
> +    void __iomem *base;
> +    typeof(gicv3_ctx.rdist)* rdist = &gicv3_ctx.rdist;
> +
> +    writel_relaxed(0, GICD + GICD_CTLR);
> +
> +    for ( i = NR_GIC_LOCAL_IRQS; i < gicv3_info.nr_lines; i += 32 )
> +        writel_relaxed(GENMASK(31, 0), GICD + GICD_IGROUPR + (i / 32) * 4);
> +
> +    for ( i = 1; i < DIV_ROUND_UP(gicv3_info.nr_lines, 32); i++ )
> +    {
> +        typeof(gicv3_ctx.dist.irqs) irqs = gicv3_ctx.dist.irqs + i - 1;
> +        unsigned int irq;
> +
> +        base = GICD + GICD_ICFGR + 8 * i;
> +        writel_relaxed(irqs->icfgr[0], base);
> +        writel_relaxed(irqs->icfgr[1], base + 4);
> +
> +        base = GICD + GICD_IPRIORITYR + 32 * i;
> +        for ( irq = 0; irq < 8; irq++ )
> +            writel_relaxed(irqs->ipriorityr[irq], base + 4 * irq);
> +
> +        base = GICD + GICD_IROUTER + 32 * i;
> +        for ( irq = 0; irq < 32; irq++ )
> +            writeq_relaxed_non_atomic(irqs->irouter[irq], base + 8 * irq);
> +
> +        writel_relaxed(GENMASK(31, 0), GICD + GICD_ICENABLER + i * 4);
> +        writel_relaxed(irqs->isenabler, GICD + GICD_ISENABLER + i * 4);
> +
> +        writel_relaxed(GENMASK(31, 0), GICD + GICD_ICACTIVER + i * 4);
> +        writel_relaxed(irqs->isactiver, GICD + GICD_ISACTIVER + i * 4);
> +    }
> +
> +    writel_relaxed(gicv3_ctx.dist.ctlr, GICD + GICD_CTLR);
> +    gicv3_dist_wait_for_rwp();
> +
> +    /* Restore GICR (Redistributor) configuration */
> +    gicv3_enable_redist();
> +
> +    base = GICD_RDIST_SGI_BASE;
> +
> +    writel_relaxed(0xffffffff, base + GICR_ICENABLER0);
> +    gicv3_redist_wait_for_rwp();
> +
> +    for ( i = 0; i < NR_GIC_LOCAL_IRQS / 4; i++ )
> +        writel_relaxed(rdist->ipriorityr[i], base + GICR_IPRIORITYR0 + i * 4);
> +
> +    writel_relaxed(rdist->isactiver, base + GICR_ISACTIVER0);
> +
> +    writel_relaxed(rdist->igroupr,  base + GICR_IGROUPR0);
> +    writel_relaxed(rdist->icfgr[0], base + GICR_ICFGR0);

   ... and restore it here.


> +    writel_relaxed(rdist->icfgr[1], base + GICR_ICFGR1);
> +

[snip]
Re: [PATCH v6 03/13] xen/arm: gic-v3: Implement GICv3 suspend/resume functions
Posted by Mykola Kvach 1 month, 4 weeks ago
Hi Oleksandr,

On Tue, Sep 2, 2025 at 7:08 PM Oleksandr Tyshchenko <olekstysh@gmail.com> wrote:
>
>
>
> On 02.09.25 01:10, Mykola Kvach wrote:
>
> Hello Mykola
>
>
> > From: Mykola Kvach <mykola_kvach@epam.com>
> >
> > 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: Mykola Kvach <mykola_kvach@epam.com>
> > ---
> > Changes in V6:
> > - Drop gicv3_save/restore_state since it is already handled during vCPU
> >    context switch.
> > - The comment about systems without SPIs is clarified for readability.
> > - Error and warning messages related to suspend context allocation are unified
> >    and now use printk() with XENLOG_ERR for consistency.
> > - The check for suspend context allocation in gicv3_resume() is removed,
> >    as it is handled earlier in the suspend path.
> > - The loop for saving and restoring PPI/SGI priorities is corrected to use
> >    the proper increment.
> > - The gicv3_suspend() function now prints an explicit error if ITS suspend
> >    support is not implemented, and returns ENOSYS in this case.
> > - The GICD_CTLR_DS bit definition is added to gic_v3_defs.h.
> > - The comment for GICR_WAKER access is expanded to reference the relevant
> >    ARM specification section and clarify the RAZ/WI behavior for Non-secure
> >    accesses.
> > - Cleanup active and enable registers before restoring.
> > ---
> >   xen/arch/arm/gic-v3-lpi.c              |   3 +
> >   xen/arch/arm/gic-v3.c                  | 235 +++++++++++++++++++++++++
> >   xen/arch/arm/include/asm/gic_v3_defs.h |   1 +
> >   3 files changed, 239 insertions(+)
> >
> > diff --git a/xen/arch/arm/gic-v3-lpi.c b/xen/arch/arm/gic-v3-lpi.c
> > index de5052e5cf..61a6e18303 100644
> > --- a/xen/arch/arm/gic-v3-lpi.c
> > +++ b/xen/arch/arm/gic-v3-lpi.c
> > @@ -391,6 +391,9 @@ static int cpu_callback(struct notifier_block *nfb, unsigned long action,
> >       switch ( action )
> >       {
> >       case CPU_UP_PREPARE:
> > +        if ( system_state == SYS_STATE_resume )
> > +            break;
> > +
> >           rc = gicv3_lpi_allocate_pendtable(cpu);
> >           if ( rc )
> >               printk(XENLOG_ERR "Unable to allocate the pendtable for CPU%lu\n",
> > diff --git a/xen/arch/arm/gic-v3.c b/xen/arch/arm/gic-v3.c
> > index cd3e1acf79..9f1be7e905 100644
> > --- a/xen/arch/arm/gic-v3.c
> > +++ b/xen/arch/arm/gic-v3.c
> > @@ -1776,6 +1776,233 @@ static bool gic_dist_supports_lpis(void)
> >       return (readl_relaxed(GICD + GICD_TYPER) & GICD_TYPE_LPIS);
> >   }
> >
> > +#ifdef CONFIG_SYSTEM_SUSPEND
> > +
> > +/* GICv3 registers to be saved/restored on system suspend/resume */
> > +struct gicv3_ctx {
> > +    struct dist_ctx {
> > +        uint32_t ctlr;
> > +        /*
> > +         * This struct represent block of 32 IRQs
> > +         * TODO: store extended SPI configuration (GICv3.1+)
> > +         */
> > +        struct irq_regs {
> > +            uint32_t icfgr[2];
> > +            uint32_t ipriorityr[8];
> > +            uint64_t irouter[32];
> > +            uint32_t isactiver;
> > +            uint32_t isenabler;
> > +        } *irqs;
> > +    } dist;
> > +
> > +    /* have only one rdist structure for last running CPU during suspend */
> > +    struct redist_ctx {
> > +        uint32_t ctlr;
> > +        /* TODO: handle case when we have more than 16 PPIs (GICv3.1+) */
> > +        uint32_t icfgr[2];
> > +        uint32_t igroupr;
> > +        uint32_t ipriorityr[8];
> > +        uint32_t isactiver;
> > +        uint32_t isenabler;
> > +    } rdist;
> > +
> > +    struct cpu_ctx {
> > +        uint32_t ctlr;
> > +        uint32_t pmr;
> > +        uint32_t bpr;
> > +        uint32_t sre_el2;
> > +        uint32_t grpen;
> > +    } cpu;
> > +};
> > +
> > +static struct gicv3_ctx gicv3_ctx;
> > +
> > +static void __init gicv3_alloc_context(void)
> > +{
> > +    uint32_t blocks = DIV_ROUND_UP(gicv3_info.nr_lines, 32);
> > +
> > +    /* We don't have ITS support for suspend */
> > +    if ( gicv3_its_host_has_its() )
> > +        return;
> > +
> > +    /* The spec allows for systems without any SPIs */
> > +    if ( blocks > 1 )
> > +    {
> > +        gicv3_ctx.dist.irqs = xzalloc_array(typeof(*gicv3_ctx.dist.irqs),
> > +                                            blocks - 1);
> > +        if ( !gicv3_ctx.dist.irqs )
> > +            printk(XENLOG_ERR "Failed to allocate memory for GICv3 suspend context\n");
> > +    }
> > +}
> > +
> > +static void gicv3_disable_redist(void)
> > +{
> > +    void __iomem* waker = GICD_RDIST_BASE + GICR_WAKER;
> > +
> > +    /*
> > +     * Avoid infinite loop if Non-secure does not have access to GICR_WAKER.
> > +     * See Arm IHI 0069H.b, 12.11.42 GICR_WAKER:
> > +     *     When GICD_CTLR.DS == 0 and an access is Non-secure accesses to this
> > +     *     register are RAZ/WI.
> > +     */
> > +    if ( !(readl_relaxed(GICD + GICD_CTLR) & GICD_CTLR_DS) )
> > +        return;
> > +
> > +    writel_relaxed(readl_relaxed(waker) | GICR_WAKER_ProcessorSleep, waker);
> > +    while ( (readl_relaxed(waker) & GICR_WAKER_ChildrenAsleep) == 0 );
> > +}
> > +
> > +static int gicv3_suspend(void)
> > +{
> > +    unsigned int i;
> > +    void __iomem *base;
> > +    typeof(gicv3_ctx.rdist)* rdist = &gicv3_ctx.rdist;
> > +
> > +    /* TODO: implement support for ITS */
> > +    if ( gicv3_its_host_has_its() )
> > +    {
> > +        printk(XENLOG_ERR "GICv3: ITS suspend support is not implemented\n");
> > +        return -ENOSYS;
> > +    }
> > +
> > +    if ( !gicv3_ctx.dist.irqs && gicv3_info.nr_lines > NR_GIC_LOCAL_IRQS )
> > +    {
> > +        printk(XENLOG_ERR "GICv3: suspend context is not allocated!\n");
> > +        return -ENOMEM;
> > +    }
> > +
> > +    /* Save GICC configuration */
> > +    gicv3_ctx.cpu.ctlr     = READ_SYSREG(ICC_CTLR_EL1);
> > +    gicv3_ctx.cpu.pmr      = READ_SYSREG(ICC_PMR_EL1);
> > +    gicv3_ctx.cpu.bpr      = READ_SYSREG(ICC_BPR1_EL1);
> > +    gicv3_ctx.cpu.sre_el2  = READ_SYSREG(ICC_SRE_EL2);
> > +    gicv3_ctx.cpu.grpen    = READ_SYSREG(ICC_IGRPEN1_EL1);
> > +
> > +    gicv3_disable_interface();
> > +    gicv3_disable_redist();
> > +
> > +    /* Save GICR configuration */
> > +    gicv3_redist_wait_for_rwp();
> > +
> > +    base = GICD_RDIST_SGI_BASE;
> > +
> > +    rdist->ctlr = readl_relaxed(base + GICR_CTLR);
> > +
> > +    /* Save priority on PPI and SGI interrupts */
> > +    for ( i = 0; i < NR_GIC_LOCAL_IRQS / 4; i++ )
> > +        rdist->ipriorityr[i] = readl_relaxed(base + GICR_IPRIORITYR0 + 4 * i);
> > +
> > +    rdist->isactiver = readl_relaxed(base + GICR_ISACTIVER0);
> > +    rdist->isenabler = readl_relaxed(base + GICR_ISENABLER0);
> > +    rdist->igroupr   = readl_relaxed(base + GICR_IGROUPR0);
> > +    rdist->icfgr[0]  = readl_relaxed(base + GICR_ICFGR0);
>
> GICR_ICFGR0 is for SGIs, which are always edge-triggered, so I am not
> sure that we need to save it here ...

Looks like I didn’t read the spec carefully and only paid attention to:

12.11.7 GICR_ICFGR0, Interrupt Configuration Register 0
Determines whether the corresponding SGI is edge-triggered or level-sensitive.

But a few lines below it states:

but a few lines below

Int_config<x>, bits [2x+1:2x], for x = 15 to 0
    Indicates whether the is level-sensitive or edge-triggered.
        0b00   Corresponding interrupt is level-sensitive.
        0b10   Corresponding interrupt is edge-triggered.

SGIs are always edge-triggered.

>
>
> > +    rdist->icfgr[1]  = readl_relaxed(base + GICR_ICFGR1);
> > +
> > +    /* Save GICD configuration */
> > +    gicv3_dist_wait_for_rwp();
> > +    gicv3_ctx.dist.ctlr = readl_relaxed(GICD + GICD_CTLR);
> > +
> > +    for ( i = 1; i < DIV_ROUND_UP(gicv3_info.nr_lines, 32); i++ )
> > +    {
> > +        typeof(gicv3_ctx.dist.irqs) irqs = gicv3_ctx.dist.irqs + i - 1;
> > +        unsigned int irq;
> > +
> > +        base = GICD + GICD_ICFGR + 8 * i;
> > +        irqs->icfgr[0] = readl_relaxed(base);
> > +        irqs->icfgr[1] = readl_relaxed(base + 4);
> > +
> > +        base = GICD + GICD_IPRIORITYR + 32 * i;
> > +        for ( irq = 0; irq < 8; irq++ )
> > +            irqs->ipriorityr[irq] = readl_relaxed(base + 4 * irq);
> > +
> > +        base = GICD + GICD_IROUTER + 32 * i;
> > +        for ( irq = 0; irq < 32; irq++ )
> > +            irqs->irouter[irq] = readq_relaxed_non_atomic(base + 8 * irq);
> > +
> > +        irqs->isactiver = readl_relaxed(GICD + GICD_ISACTIVER + 4 * i);
> > +        irqs->isenabler = readl_relaxed(GICD + GICD_ISENABLER + 4 * i);
> > +    }
> > +
> > +    return 0;
> > +}
> > +
> > +static void gicv3_resume(void)
> > +{
> > +    unsigned int i;
> > +    void __iomem *base;
> > +    typeof(gicv3_ctx.rdist)* rdist = &gicv3_ctx.rdist;
> > +
> > +    writel_relaxed(0, GICD + GICD_CTLR);
> > +
> > +    for ( i = NR_GIC_LOCAL_IRQS; i < gicv3_info.nr_lines; i += 32 )
> > +        writel_relaxed(GENMASK(31, 0), GICD + GICD_IGROUPR + (i / 32) * 4);
> > +
> > +    for ( i = 1; i < DIV_ROUND_UP(gicv3_info.nr_lines, 32); i++ )
> > +    {
> > +        typeof(gicv3_ctx.dist.irqs) irqs = gicv3_ctx.dist.irqs + i - 1;
> > +        unsigned int irq;
> > +
> > +        base = GICD + GICD_ICFGR + 8 * i;
> > +        writel_relaxed(irqs->icfgr[0], base);
> > +        writel_relaxed(irqs->icfgr[1], base + 4);
> > +
> > +        base = GICD + GICD_IPRIORITYR + 32 * i;
> > +        for ( irq = 0; irq < 8; irq++ )
> > +            writel_relaxed(irqs->ipriorityr[irq], base + 4 * irq);
> > +
> > +        base = GICD + GICD_IROUTER + 32 * i;
> > +        for ( irq = 0; irq < 32; irq++ )
> > +            writeq_relaxed_non_atomic(irqs->irouter[irq], base + 8 * irq);
> > +
> > +        writel_relaxed(GENMASK(31, 0), GICD + GICD_ICENABLER + i * 4);
> > +        writel_relaxed(irqs->isenabler, GICD + GICD_ISENABLER + i * 4);
> > +
> > +        writel_relaxed(GENMASK(31, 0), GICD + GICD_ICACTIVER + i * 4);
> > +        writel_relaxed(irqs->isactiver, GICD + GICD_ISACTIVER + i * 4);
> > +    }
> > +
> > +    writel_relaxed(gicv3_ctx.dist.ctlr, GICD + GICD_CTLR);
> > +    gicv3_dist_wait_for_rwp();
> > +
> > +    /* Restore GICR (Redistributor) configuration */
> > +    gicv3_enable_redist();
> > +
> > +    base = GICD_RDIST_SGI_BASE;
> > +
> > +    writel_relaxed(0xffffffff, base + GICR_ICENABLER0);
> > +    gicv3_redist_wait_for_rwp();
> > +
> > +    for ( i = 0; i < NR_GIC_LOCAL_IRQS / 4; i++ )
> > +        writel_relaxed(rdist->ipriorityr[i], base + GICR_IPRIORITYR0 + i * 4);
> > +
> > +    writel_relaxed(rdist->isactiver, base + GICR_ISACTIVER0);
> > +
> > +    writel_relaxed(rdist->igroupr,  base + GICR_IGROUPR0);
> > +    writel_relaxed(rdist->icfgr[0], base + GICR_ICFGR0);
>
>    ... and restore it here.

Thank you for pointing that out.
I will remove it in the next version of the patch series.

>
>
> > +    writel_relaxed(rdist->icfgr[1], base + GICR_ICFGR1);
> > +
>
> [snip]

Best regards,
Mykola