]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
soc/tegra: pmc: Fix unsafe generic_handle_irq() call
authorPrathamesh Shete <pshete@nvidia.com>
Thu, 8 Jan 2026 05:01:03 +0000 (05:01 +0000)
committerThierry Reding <treding@nvidia.com>
Sun, 18 Jan 2026 07:48:21 +0000 (08:48 +0100)
Currently, when resuming from system suspend on Tegra platforms,
the following warning is observed:

WARNING: CPU: 0 PID: 14459 at kernel/irq/irqdesc.c:666
Call trace:
 handle_irq_desc+0x20/0x58 (P)
 tegra186_pmc_wake_syscore_resume+0xe4/0x15c
 syscore_resume+0x3c/0xb8
 suspend_devices_and_enter+0x510/0x540
 pm_suspend+0x16c/0x1d8

The warning occurs because generic_handle_irq() is being called from
a non-interrupt context which is considered as unsafe.

Fix this warning by deferring generic_handle_irq() call to an IRQ work
which gets executed in hard IRQ context where generic_handle_irq()
can be called safely.

When PREEMPT_RT kernels are used, regular IRQ work (initialized with
init_irq_work) is deferred to run in per-CPU kthreads in preemptible
context rather than hard IRQ context. Hence, use the IRQ_WORK_INIT_HARD
variant so that with PREEMPT_RT kernels, the IRQ work is processed in
hardirq context instead of being deferred to a thread which is required
for calling generic_handle_irq().

On non-PREEMPT_RT kernels, both init_irq_work() and IRQ_WORK_INIT_HARD()
execute in IRQ context, so this change has no functional impact for
standard kernel configurations.

Signed-off-by: Petlozu Pravareshwar <petlozup@nvidia.com>
Signed-off-by: Prathamesh Shete <pshete@nvidia.com>
Reviewed-by: Jon Hunter <jonathanh@nvidia.com>
Tested-by: Jon Hunter <jonathanh@nvidia.com>
[treding@nvidia.com: miscellaneous cleanups]
Signed-off-by: Thierry Reding <treding@nvidia.com>
drivers/soc/tegra/pmc.c

index f3760a3b3026d618705fc6f61ec3005a12377ffb..407fa840814c33d816aa0f2c4bd8ac7552e66bec 100644 (file)
@@ -28,6 +28,7 @@
 #include <linux/iopoll.h>
 #include <linux/irqdomain.h>
 #include <linux/irq.h>
+#include <linux/irq_work.h>
 #include <linux/kernel.h>
 #include <linux/of_address.h>
 #include <linux/of_clk.h>
@@ -468,6 +469,10 @@ struct tegra_pmc {
        unsigned long *wake_sw_status_map;
        unsigned long *wake_cntrl_level_map;
        struct syscore syscore;
+
+       /* Pending wake IRQ processing */
+       struct irq_work wake_work;
+       u32 *wake_status;
 };
 
 static struct tegra_pmc *pmc = &(struct tegra_pmc) {
@@ -1905,6 +1910,50 @@ static int tegra_pmc_parse_dt(struct tegra_pmc *pmc, struct device_node *np)
        return 0;
 }
 
+/* translate sc7 wake sources back into IRQs to catch edge triggered wakeups */
+static void tegra186_pmc_wake_handler(struct irq_work *work)
+{
+       struct tegra_pmc *pmc = container_of(work, struct tegra_pmc, wake_work);
+       unsigned int i, wake;
+
+       for (i = 0; i < pmc->soc->max_wake_vectors; i++) {
+               unsigned long status = pmc->wake_status[i];
+
+               for_each_set_bit(wake, &status, 32) {
+                       irq_hw_number_t hwirq = wake + (i * 32);
+                       struct irq_desc *desc;
+                       unsigned int irq;
+
+                       irq = irq_find_mapping(pmc->domain, hwirq);
+                       if (!irq) {
+                               dev_warn(pmc->dev,
+                                        "No IRQ found for WAKE#%lu!\n",
+                                        hwirq);
+                               continue;
+                       }
+
+                       dev_dbg(pmc->dev,
+                               "Resume caused by WAKE#%lu mapped to IRQ#%u\n",
+                               hwirq, irq);
+
+                       desc = irq_to_desc(irq);
+                       if (!desc) {
+                               dev_warn(pmc->dev,
+                                        "No descriptor found for IRQ#%u\n",
+                                        irq);
+                               continue;
+                       }
+
+                       if (!desc->action || !desc->action->name)
+                               continue;
+
+                       generic_handle_irq(irq);
+               }
+
+               pmc->wake_status[i] = 0;
+       }
+}
+
 static int tegra_pmc_init(struct tegra_pmc *pmc)
 {
        if (pmc->soc->max_wake_events > 0) {
@@ -1923,6 +1972,18 @@ static int tegra_pmc_init(struct tegra_pmc *pmc)
                pmc->wake_cntrl_level_map = bitmap_zalloc(pmc->soc->max_wake_events, GFP_KERNEL);
                if (!pmc->wake_cntrl_level_map)
                        return -ENOMEM;
+
+               pmc->wake_status = kcalloc(pmc->soc->max_wake_vectors, sizeof(u32), GFP_KERNEL);
+               if (!pmc->wake_status)
+                       return -ENOMEM;
+
+               /*
+                * Initialize IRQ work for processing wake IRQs. Must use
+                * HARD_IRQ variant to run in hard IRQ context on PREEMPT_RT
+                * because we call generic_handle_irq() which requires hard
+                * IRQ context.
+                */
+               pmc->wake_work = IRQ_WORK_INIT_HARD(tegra186_pmc_wake_handler);
        }
 
        if (pmc->soc->init)
@@ -3129,47 +3190,30 @@ static void wke_clear_wake_status(struct tegra_pmc *pmc)
        }
 }
 
-/* translate sc7 wake sources back into IRQs to catch edge triggered wakeups */
-static void tegra186_pmc_process_wake_events(struct tegra_pmc *pmc, unsigned int index,
-                                            unsigned long status)
-{
-       unsigned int wake;
-
-       dev_dbg(pmc->dev, "Wake[%d:%d]  status=%#lx\n", (index * 32) + 31, index * 32, status);
-
-       for_each_set_bit(wake, &status, 32) {
-               irq_hw_number_t hwirq = wake + 32 * index;
-               struct irq_desc *desc;
-               unsigned int irq;
-
-               irq = irq_find_mapping(pmc->domain, hwirq);
-
-               desc = irq_to_desc(irq);
-               if (!desc || !desc->action || !desc->action->name) {
-                       dev_dbg(pmc->dev, "Resume caused by WAKE%ld, IRQ %d\n", hwirq, irq);
-                       continue;
-               }
-
-               dev_dbg(pmc->dev, "Resume caused by WAKE%ld, %s\n", hwirq, desc->action->name);
-               generic_handle_irq(irq);
-       }
-}
-
 static void tegra186_pmc_wake_syscore_resume(void *data)
 {
-       u32 status, mask;
        unsigned int i;
+       u32 mask;
 
        for (i = 0; i < pmc->soc->max_wake_vectors; i++) {
                mask = readl(pmc->wake + WAKE_AOWAKE_TIER2_ROUTING(i));
-               status = readl(pmc->wake + WAKE_AOWAKE_STATUS_R(i)) & mask;
-
-               tegra186_pmc_process_wake_events(pmc, i, status);
+               pmc->wake_status[i] = readl(pmc->wake + WAKE_AOWAKE_STATUS_R(i)) & mask;
        }
+
+       /* Schedule IRQ work to process wake IRQs (if any) */
+       irq_work_queue(&pmc->wake_work);
 }
 
 static int tegra186_pmc_wake_syscore_suspend(void *data)
 {
+       unsigned int i;
+
+       /* Check if there are unhandled wake IRQs */
+       for (i = 0; i < pmc->soc->max_wake_vectors; i++)
+               if (pmc->wake_status[i])
+                       dev_warn(pmc->dev,
+                                "Unhandled wake IRQs pending vector[%u]: 0x%x\n",
+                                i, pmc->wake_status[i]);
        wke_read_sw_wake_status(pmc);
 
        /* flip the wakeup trigger for dual-edge triggered pads