Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

irqchip/riscv-aplic: Preserve APLIC states across suspend/resume

The APLIC states might be reset when the platform enters a low power
state, but the register states are not being preserved and restored,
which prevents interrupt delivery after the platform resumes.
Solve this by adding a syscore ops and a power management notifier to
preserve and restore the APLIC states on suspend and resume.

[ tglx: Folded the build fix provided by Geert ]

Signed-off-by: Nick Hu <nick.hu@sifive.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Reviewed-by: Yong-Xuan Wang <yongxuan.wang@sifive.com>
Reviewed-by: Cyan Yang <cyan.yang@sifive.com>
Reviewed-by: Nutty Liu <liujingqi@lanxincomputing.com>
Reviewed-by: Anup Patel <anup@brainfault.org>
Link: https://patch.msgid.link/20251202-preserve-aplic-imsic-v3-2-1844fbf1fe92@sifive.com

authored by

Nick Hu and committed by
Thomas Gleixner
95a8ddde f48b4bd0

+198 -1
+10
drivers/irqchip/irq-riscv-aplic-direct.c
··· 8 8 #include <linux/bitfield.h> 9 9 #include <linux/bitops.h> 10 10 #include <linux/cpu.h> 11 + #include <linux/cpumask.h> 11 12 #include <linux/interrupt.h> 12 13 #include <linux/irqchip.h> 13 14 #include <linux/irqchip/chained_irq.h> ··· 170 169 171 170 /* Delivery must be set to 1 for interrupt triggering */ 172 171 writel(de, idc->regs + APLIC_IDC_IDELIVERY); 172 + } 173 + 174 + void aplic_direct_restore_states(struct aplic_priv *priv) 175 + { 176 + struct aplic_direct *direct = container_of(priv, struct aplic_direct, priv); 177 + int cpu; 178 + 179 + for_each_cpu(cpu, &direct->lmask) 180 + aplic_idc_set_delivery(per_cpu_ptr(&aplic_idcs, cpu), true); 173 181 } 174 182 175 183 static int aplic_direct_dying_cpu(unsigned int cpu)
+169 -1
drivers/irqchip/irq-riscv-aplic-main.c
··· 12 12 #include <linux/of.h> 13 13 #include <linux/of_irq.h> 14 14 #include <linux/platform_device.h> 15 + #include <linux/pm_domain.h> 16 + #include <linux/pm_runtime.h> 15 17 #include <linux/printk.h> 18 + #include <linux/syscore_ops.h> 16 19 17 20 #include "irq-riscv-aplic-main.h" 21 + 22 + static LIST_HEAD(aplics); 23 + 24 + static void aplic_restore_states(struct aplic_priv *priv) 25 + { 26 + struct aplic_saved_regs *saved_regs = &priv->saved_hw_regs; 27 + struct aplic_src_ctrl *srcs; 28 + void __iomem *regs; 29 + u32 nr_irqs, i; 30 + 31 + regs = priv->regs; 32 + writel(saved_regs->domaincfg, regs + APLIC_DOMAINCFG); 33 + #ifdef CONFIG_RISCV_M_MODE 34 + writel(saved_regs->msiaddr, regs + APLIC_xMSICFGADDR); 35 + writel(saved_regs->msiaddrh, regs + APLIC_xMSICFGADDRH); 36 + #endif 37 + /* 38 + * The sourcecfg[i] has to be restored prior to the target[i], interrupt-pending and 39 + * interrupt-enable bits. The AIA specification states that "Whenever interrupt source i is 40 + * inactive in an interrupt domain, the corresponding interrupt-pending and interrupt-enable 41 + * bits within the domain are read-only zeros, and register target[i] is also read-only 42 + * zero." 43 + */ 44 + nr_irqs = priv->nr_irqs; 45 + for (i = 0; i < nr_irqs; i++) { 46 + srcs = &priv->saved_hw_regs.srcs[i]; 47 + writel(srcs->sourcecfg, regs + APLIC_SOURCECFG_BASE + i * sizeof(u32)); 48 + writel(srcs->target, regs + APLIC_TARGET_BASE + i * sizeof(u32)); 49 + } 50 + 51 + for (i = 0; i <= nr_irqs; i += 32) { 52 + srcs = &priv->saved_hw_regs.srcs[i]; 53 + writel(-1U, regs + APLIC_CLRIE_BASE + (i / 32) * sizeof(u32)); 54 + writel(srcs->ie, regs + APLIC_SETIE_BASE + (i / 32) * sizeof(u32)); 55 + 56 + /* Re-trigger the interrupts if it forwards interrupts to target harts by MSIs */ 57 + if (!priv->nr_idcs) 58 + writel(readl(regs + APLIC_CLRIP_BASE + (i / 32) * sizeof(u32)), 59 + regs + APLIC_SETIP_BASE + (i / 32) * sizeof(u32)); 60 + } 61 + 62 + if (priv->nr_idcs) 63 + aplic_direct_restore_states(priv); 64 + } 65 + 66 + static void aplic_save_states(struct aplic_priv *priv) 67 + { 68 + struct aplic_src_ctrl *srcs; 69 + void __iomem *regs; 70 + u32 i, nr_irqs; 71 + 72 + regs = priv->regs; 73 + nr_irqs = priv->nr_irqs; 74 + /* The valid interrupt source IDs range from 1 to N, where N is priv->nr_irqs */ 75 + for (i = 0; i < nr_irqs; i++) { 76 + srcs = &priv->saved_hw_regs.srcs[i]; 77 + srcs->target = readl(regs + APLIC_TARGET_BASE + i * sizeof(u32)); 78 + 79 + if (i % 32) 80 + continue; 81 + 82 + srcs->ie = readl(regs + APLIC_SETIE_BASE + (i / 32) * sizeof(u32)); 83 + } 84 + 85 + /* Save the nr_irqs bit if needed */ 86 + if (!(nr_irqs % 32)) { 87 + srcs = &priv->saved_hw_regs.srcs[nr_irqs]; 88 + srcs->ie = readl(regs + APLIC_SETIE_BASE + (nr_irqs / 32) * sizeof(u32)); 89 + } 90 + } 91 + 92 + static int aplic_syscore_suspend(void *data) 93 + { 94 + struct aplic_priv *priv; 95 + 96 + list_for_each_entry(priv, &aplics, head) 97 + aplic_save_states(priv); 98 + 99 + return 0; 100 + } 101 + 102 + static void aplic_syscore_resume(void *data) 103 + { 104 + struct aplic_priv *priv; 105 + 106 + list_for_each_entry(priv, &aplics, head) 107 + aplic_restore_states(priv); 108 + } 109 + 110 + static struct syscore_ops aplic_syscore_ops = { 111 + .suspend = aplic_syscore_suspend, 112 + .resume = aplic_syscore_resume, 113 + }; 114 + 115 + static struct syscore aplic_syscore = { 116 + .ops = &aplic_syscore_ops, 117 + }; 118 + 119 + static int aplic_pm_notifier(struct notifier_block *nb, unsigned long action, void *data) 120 + { 121 + struct aplic_priv *priv = container_of(nb, struct aplic_priv, genpd_nb); 122 + 123 + switch (action) { 124 + case GENPD_NOTIFY_PRE_OFF: 125 + aplic_save_states(priv); 126 + break; 127 + case GENPD_NOTIFY_ON: 128 + aplic_restore_states(priv); 129 + break; 130 + default: 131 + break; 132 + } 133 + 134 + return 0; 135 + } 136 + 137 + static void aplic_pm_remove(void *data) 138 + { 139 + struct aplic_priv *priv = data; 140 + struct device *dev = priv->dev; 141 + 142 + list_del(&priv->head); 143 + if (dev->pm_domain) 144 + dev_pm_genpd_remove_notifier(dev); 145 + } 146 + 147 + static int aplic_pm_add(struct device *dev, struct aplic_priv *priv) 148 + { 149 + struct aplic_src_ctrl *srcs; 150 + int ret; 151 + 152 + srcs = devm_kzalloc(dev, (priv->nr_irqs + 1) * sizeof(*srcs), GFP_KERNEL); 153 + if (!srcs) 154 + return -ENOMEM; 155 + 156 + priv->saved_hw_regs.srcs = srcs; 157 + list_add(&priv->head, &aplics); 158 + if (dev->pm_domain) { 159 + priv->genpd_nb.notifier_call = aplic_pm_notifier; 160 + ret = dev_pm_genpd_add_notifier(dev, &priv->genpd_nb); 161 + if (ret) 162 + goto remove_head; 163 + 164 + ret = devm_pm_runtime_enable(dev); 165 + if (ret) 166 + goto remove_notifier; 167 + } 168 + 169 + return devm_add_action_or_reset(dev, aplic_pm_remove, priv); 170 + 171 + remove_notifier: 172 + dev_pm_genpd_remove_notifier(dev); 173 + remove_head: 174 + list_del(&priv->head); 175 + return ret; 176 + } 18 177 19 178 void aplic_irq_unmask(struct irq_data *d) 20 179 { ··· 219 60 sourcecfg += (d->hwirq - 1) * sizeof(u32); 220 61 writel(val, sourcecfg); 221 62 63 + priv->saved_hw_regs.srcs[d->hwirq - 1].sourcecfg = val; 64 + 222 65 return 0; 223 66 } 224 67 ··· 243 82 244 83 void aplic_init_hw_global(struct aplic_priv *priv, bool msi_mode) 245 84 { 85 + struct aplic_saved_regs *saved_regs = &priv->saved_hw_regs; 246 86 u32 val; 247 87 #ifdef CONFIG_RISCV_M_MODE 248 88 u32 valh; ··· 257 95 valh |= FIELD_PREP(APLIC_xMSICFGADDRH_HHXS, priv->msicfg.hhxs); 258 96 writel(val, priv->regs + APLIC_xMSICFGADDR); 259 97 writel(valh, priv->regs + APLIC_xMSICFGADDRH); 98 + saved_regs->msiaddr = val; 99 + saved_regs->msiaddrh = valh; 260 100 } 261 101 #endif 262 102 ··· 270 106 writel(val, priv->regs + APLIC_DOMAINCFG); 271 107 if (readl(priv->regs + APLIC_DOMAINCFG) != val) 272 108 dev_warn(priv->dev, "unable to write 0x%x in domaincfg\n", val); 109 + 110 + saved_regs->domaincfg = val; 273 111 } 274 112 275 113 static void aplic_init_hw_irqs(struct aplic_priv *priv) ··· 342 176 /* Setup initial state APLIC interrupts */ 343 177 aplic_init_hw_irqs(priv); 344 178 345 - return 0; 179 + return aplic_pm_add(dev, priv); 346 180 } 347 181 348 182 static int aplic_probe(struct platform_device *pdev) ··· 375 209 if (rc) 376 210 dev_err_probe(dev, rc, "failed to setup APLIC in %s mode\n", 377 211 msi_mode ? "MSI" : "direct"); 212 + else 213 + register_syscore(&aplic_syscore); 378 214 379 215 #ifdef CONFIG_ACPI 380 216 if (!acpi_disabled)
+19
drivers/irqchip/irq-riscv-aplic-main.h
··· 23 23 u32 lhxw; 24 24 }; 25 25 26 + struct aplic_src_ctrl { 27 + u32 sourcecfg; 28 + u32 target; 29 + u32 ie; 30 + }; 31 + 32 + struct aplic_saved_regs { 33 + u32 domaincfg; 34 + #ifdef CONFIG_RISCV_M_MODE 35 + u32 msiaddr; 36 + u32 msiaddrh; 37 + #endif 38 + struct aplic_src_ctrl *srcs; 39 + }; 40 + 26 41 struct aplic_priv { 42 + struct list_head head; 43 + struct notifier_block genpd_nb; 44 + struct aplic_saved_regs saved_hw_regs; 27 45 struct device *dev; 28 46 u32 gsi_base; 29 47 u32 nr_irqs; ··· 58 40 unsigned long *hwirq, unsigned int *type); 59 41 void aplic_init_hw_global(struct aplic_priv *priv, bool msi_mode); 60 42 int aplic_setup_priv(struct aplic_priv *priv, struct device *dev, void __iomem *regs); 43 + void aplic_direct_restore_states(struct aplic_priv *priv); 61 44 int aplic_direct_setup(struct device *dev, void __iomem *regs); 62 45 #ifdef CONFIG_RISCV_APLIC_MSI 63 46 int aplic_msi_setup(struct device *dev, void __iomem *regs);