* [PATCH v2 0/1] irqchip/gic-v3: Preserve SPI state across hibernation
@ 2026-10-24 14:47 Zhu Ling
2026-10-24 14:47 ` [PATCH v2 1/1] irqchip/gic-v3: Preserve SPI configuration " Zhu Ling
0 siblings, 1 reply; 2+ messages in thread
From: Zhu Ling @ 2026-10-24 14:47 UTC (permalink / raw)
To: maz
Cc: Zhu Ling, Thomas Gleixner, Radu Rendec, Rafael J . Wysocki,
linux-arm-kernel, linux-pm, linux-kernel
This v2 replaces my earlier genirq/PM proposal. Please do not apply
v1. The new revision restores distributor configuration in the GICv3
driver and leaves generic IRQ PM policy unchanged.
Previous review:
https://lists.openwall.net/linux-kernel/2026/09/08/2938
Test platform: Phytium E2000Q.
Base: 17bb0e2f6c8043f70a693a5f3797471cbed354c8.
Setup: GICv3 SPI 189, level-high, CPU0 only, SATA root/swap and serial
console; disk=shutdown, pm_test=none. Serial captures confirm poweroff,
a new boot and hibernation-image restoration.
An independent module installs either request_irq() or a chained
handler on the same idle SPI. It injects one real GIC pending event
before S4 and another after restoration. It has no PM callbacks or
automatic recovery. GPIOLIB is disabled; no vendor GPIO driver or
PAD configuration participates in the test.
After-S4 results, with separate fresh boots for each registration mode:
hits/injected hw_masked pending rescued
unpatched ordinary 2/2 0 0 0
unpatched chained 1/2 1 1 0
patched chained 2/2 0 0 0
patched ordinary 2/2 0 0 0
In the failing run the descriptor remains started=1, disabled=0 and
sw_masked=0. A separate explicit unmask delivers the queued event
without another injection or handler reinstallation. Neither patched
run needs this rescue; the SPI is already unmasked before injection.
No unsolicited interrupts were observed.
The fix saves SPI/ESPI enable, trigger, priority and routing state in
the image and restores it during syscore resume. It does not rely on
the module being probed by the boot kernel. v2 changes only GICv3,
instead of forcing chained IRQs through generic resume as v1 did.
Validation: one real S4 cycle per patched mode; ARM64 Image/modules;
W=1 GICv3 builds for ARM32 with hibernation and ARM64 without
hibernation or PM; strict checkpatch and clean-base apply checks.
Mock-MMIO ASan/UBSan checks also pass; these are not hardware tests.
Sparse was skipped by Kbuild because the installed checker is too old.
The test kernels also contain the same 8-line USB host enablement,
not included in this patch. Taint is 0 before loading the external
test module and 4096 (O) afterwards. The fixed runs use the same
Image and byte-identical full configurations.
Scope: SPI distributor configuration, not complete GICv3/ITS S4
support. ESPI hardware, PPI/SGI/LPI, deep S3, dedicated disabled-IRQ,
rollback/fault-injection, non-default configurations and repeated S4
have not been validated. No introducing commit or Fixes tag is guessed.
The reproducer and original logs are retained and can be supplied.
v1 used zhuling0805@qq.com; v2 uses zhuling2709@phytium.com.cn.
Zhu Ling (1):
irqchip/gic-v3: Preserve SPI configuration across hibernation
drivers/irqchip/irq-gic-v3.c | 161 +++++++++++++++++++++++++++++++++++
1 file changed, 161 insertions(+)
base-commit: 17bb0e2f6c8043f70a693a5f3797471cbed354c8
^ permalink raw reply [flat|nested] 2+ messages in thread
* [PATCH v2 1/1] irqchip/gic-v3: Preserve SPI configuration across hibernation
2026-10-24 14:47 [PATCH v2 0/1] irqchip/gic-v3: Preserve SPI state across hibernation Zhu Ling
@ 2026-10-24 14:47 ` Zhu Ling
0 siblings, 0 replies; 2+ messages in thread
From: Zhu Ling @ 2026-10-24 14:47 UTC (permalink / raw)
To: maz
Cc: Zhu Ling, Thomas Gleixner, Radu Rendec, Rafael J . Wysocki,
linux-arm-kernel, linux-pm, linux-kernel
A module-installed chained SPI can remain disabled after restoring a
hibernation image. The boot kernel initializes the distributor but
need not load that module. The restored IRQ descriptor still records
the interrupt as started, enabled and unmasked. Chained interrupts
are skipped by suspend_device_irqs(), so resume_device_irqs() does
not re-enable them.
On the Phytium E2000Q platform, an independent module reproduces this
on SPI 189 with GPIOLIB disabled and no vendor GPIO driver. Ordinary
IRQ mode delivers both pending-state injections, before and after
real S4. Chained mode delivers only the first and leaves the second
pending with the hardware enable bit clear. Unmasking alone delivers
that queued interrupt without rebinding the handler.
Preserve SPI/ESPI enables, trigger configuration, priorities and routing
in the GICv3 driver. Allocate the context on PM_HIBERNATION_PREPARE and
capture it in syscore suspend, after device IRQ suspension. Restore
configuration with the SPIs disabled, then restore the saved enables.
This preserves the snapshot mask state without changing generic IRQ
PM policy or reinstalling handlers.
Paired thaw and rollback also restore the saved context. No context is
allocated for S3 or PM_RESTORE_PREPARE in the boot kernel; free it on
PM_POST_HIBERNATION. Leave pending/active state, SGIs/PPIs and ITS/LPI
tables untouched. This addresses distributor SPI configuration, not
complete GICv3 hibernation support.
With this change, both modes deliver the second injection after real
S4 without manual recovery. Each was tested for one cycle on a fresh
boot, using a level-high SPI, CPU0, disk=shutdown and pm_test=none.
Signed-off-by: Zhu Ling <zhuling2709@phytium.com.cn>
---
Changes since v1:
- Replace the genirq force-resume proposal with GICv3 SPI context
save/restore; leave generic IRQ PM policy and interfaces unchanged.
- Add physical-board reproduction and patched-kernel S4 results for
both ordinary and chained registration modes.
Previous review:
https://lists.openwall.net/linux-kernel/2026/09/08/2938
drivers/irqchip/irq-gic-v3.c | 161 +++++++++++++++++++++++++++++++++++
1 file changed, 161 insertions(+)
diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c
index 94873646d46fb..b968b428d0bfd 100644
--- a/drivers/irqchip/irq-gic-v3.c
+++ b/drivers/irqchip/irq-gic-v3.c
@@ -11,15 +11,19 @@
#include <linux/cpu_pm.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
+#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/irqdomain.h>
#include <linux/kernel.h>
#include <linux/kstrtox.h>
+#include <linux/notifier.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/percpu.h>
#include <linux/refcount.h>
#include <linux/slab.h>
+#include <linux/suspend.h>
+#include <linux/syscore_ops.h>
#include <linux/iopoll.h>
#include <linux/irqchip.h>
@@ -1506,6 +1510,162 @@ static void gic_cpu_pm_init(void)
static inline void gic_cpu_pm_init(void) { }
#endif /* CONFIG_CPU_PM */
+#ifdef CONFIG_HIBERNATION
+/* The SPI and ESPI register banks each cover at most 1024 INTIDs. */
+struct gic_dist_context {
+ u32 enabled[1024 / 32];
+ u32 config[1024 / 16];
+ u32 priority[1024 / 4];
+ u64 router[1024];
+};
+
+static struct gic_dist_context *gic_saved_dist;
+static bool gic_dist_saved;
+
+static void gic_save_spis(struct gic_dist_context *ctx, u32 first, u32 nr)
+{
+ struct irq_data d = { };
+ void __iomem *base;
+ u32 i, index, offset;
+
+ for (i = 0; i < nr; i++) {
+ d.hwirq = first + i;
+ base = gic_dist_base_alias(&d);
+
+ offset = convert_offset_index(&d, GICD_IROUTER, &index);
+ ctx->router[index] = readq_relaxed(base + offset + index * 8);
+
+ if (IS_ALIGNED(index, 32)) {
+ offset = convert_offset_index(&d, GICD_ISENABLER, &index);
+ ctx->enabled[index / 32] =
+ readl_relaxed(base + offset + index / 32 * 4);
+ }
+ if (IS_ALIGNED(index, 16)) {
+ offset = convert_offset_index(&d, GICD_ICFGR, &index);
+ ctx->config[index / 16] =
+ readl_relaxed(base + offset + index / 16 * 4);
+ }
+ if (IS_ALIGNED(index, 4)) {
+ offset = convert_offset_index(&d, GICD_IPRIORITYR, &index);
+ ctx->priority[index / 4] =
+ readl_relaxed(base + offset + index / 4 * 4);
+ }
+ }
+}
+
+static void gic_restore_spis(struct gic_dist_context *ctx, u32 first, u32 nr)
+{
+ void __iomem *base = gic_data.dist_base;
+ struct irq_data d = { };
+ u32 i, index, offset;
+
+ /* Configuration and routing must be restored with the SPIs disabled. */
+ for (i = 0; i < nr; i += 32) {
+ d.hwirq = first + i;
+ offset = convert_offset_index(&d, GICD_ICENABLER, &index);
+ writel_relaxed(~0U, base + offset + index / 32 * 4);
+ }
+ gic_dist_wait_for_rwp();
+
+ for (i = 0; i < nr; i++) {
+ d.hwirq = first + i;
+ offset = convert_offset_index(&d, GICD_IROUTER, &index);
+ gic_write_irouter(ctx->router[index], base + offset + index * 8);
+
+ if (IS_ALIGNED(index, 16)) {
+ offset = convert_offset_index(&d, GICD_ICFGR, &index);
+ writel_relaxed(ctx->config[index / 16],
+ base + offset + index / 16 * 4);
+ }
+ if (IS_ALIGNED(index, 4)) {
+ offset = convert_offset_index(&d, GICD_IPRIORITYR, &index);
+ writel_relaxed(ctx->priority[index / 4],
+ base + offset + index / 4 * 4);
+ }
+ }
+
+ /* Order configuration writes before restoring interrupt enables. */
+ wmb();
+ for (i = 0; i < nr; i += 32) {
+ d.hwirq = first + i;
+ offset = convert_offset_index(&d, GICD_ISENABLER, &index);
+ writel_relaxed(ctx->enabled[index / 32],
+ base + offset + index / 32 * 4);
+ }
+}
+
+/*
+ * Capture the distributor after device IRQs have been suspended. The image
+ * retains this context and gic_dist_saved, even though the initial thaw clears
+ * the latter. PM_RESTORE_PREPARE in the boot kernel does not allocate a context.
+ * Paired thaw and rollback also restore the saved configuration; S3 is untouched.
+ */
+static int gic_hibernate_suspend(void *data)
+{
+ if (!gic_saved_dist)
+ return 0;
+
+ gic_save_spis(gic_saved_dist, 32, GIC_LINE_NR - 32);
+ if (GIC_ESPI_NR)
+ gic_save_spis(gic_saved_dist + 1, ESPI_BASE_INTID, GIC_ESPI_NR);
+ gic_dist_saved = true;
+ return 0;
+}
+
+static void gic_hibernate_resume(void *data)
+{
+ if (!gic_dist_saved)
+ return;
+
+ gic_restore_spis(gic_saved_dist, 32, GIC_LINE_NR - 32);
+ if (GIC_ESPI_NR)
+ gic_restore_spis(gic_saved_dist + 1, ESPI_BASE_INTID, GIC_ESPI_NR);
+ gic_dist_saved = false;
+}
+
+static const struct syscore_ops gic_hibernate_syscore_ops = {
+ .suspend = gic_hibernate_suspend,
+ .resume = gic_hibernate_resume,
+};
+
+static struct syscore gic_hibernate_syscore = {
+ .ops = &gic_hibernate_syscore_ops,
+};
+
+static int gic_hibernate_notify(struct notifier_block *nb,
+ unsigned long event, void *unused)
+{
+ switch (event) {
+ case PM_HIBERNATION_PREPARE:
+ gic_saved_dist = kmalloc_objs(*gic_saved_dist, 1 + !!GIC_ESPI_NR);
+ if (!gic_saved_dist)
+ return notifier_from_errno(-ENOMEM);
+ break;
+ case PM_POST_HIBERNATION:
+ kfree(gic_saved_dist);
+ gic_saved_dist = NULL;
+ gic_dist_saved = false;
+ break;
+ }
+ return NOTIFY_OK;
+}
+
+static struct notifier_block gic_hibernate_notifier = {
+ .notifier_call = gic_hibernate_notify,
+};
+
+static void __init gic_hibernate_init(void)
+{
+ if (register_pm_notifier(&gic_hibernate_notifier)) {
+ pr_warn("Failed to register hibernation notifier\n");
+ return;
+ }
+ register_syscore(&gic_hibernate_syscore);
+}
+#else
+static inline void gic_hibernate_init(void) { }
+#endif /* CONFIG_HIBERNATION */
+
static struct irq_chip gic_chip = {
.name = "GICv3",
.irq_mask = gic_mask_irq,
@@ -2053,6 +2213,7 @@ static int __init gic_init_bases(phys_addr_t dist_phys_base,
gic_enable_nmi_support();
gic_smp_init();
gic_cpu_pm_init();
+ gic_hibernate_init();
if (gic_dist_supports_lpis()) {
its_init(handle, &gic_data.rdists, gic_data.domain, dist_prio_irq);
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-10-24 14:47 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-24 14:47 [PATCH v2 0/1] irqchip/gic-v3: Preserve SPI state across hibernation Zhu Ling
2026-10-24 14:47 ` [PATCH v2 1/1] irqchip/gic-v3: Preserve SPI configuration " Zhu Ling
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®