[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <e6e83a4e-5eca-4ddd-b0cb-e78921d1e5a7@nvidia.com>
Date: Fri, 9 Jan 2026 18:36:37 +0000
From: Jon Hunter <jonathanh@...dia.com>
To: Prathamesh Shete <pshete@...dia.com>, thierry.reding@...il.com,
ulf.hansson@...aro.org, chleroy@...nel.org, jirislaby@...nel.org,
haotienh@...dia.com, linux-tegra@...r.kernel.org,
linux-kernel@...r.kernel.org
Cc: petlozup@...dia.com
Subject: Re: [PATCH] soc/tegra: pmc: Fix unsafe generic_handle_irq() call
On 08/01/2026 05:01, Prathamesh Shete wrote:
> 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@...dia.com>
> Signed-off-by: Prathamesh Shete <pshete@...dia.com>
> ---
> drivers/soc/tegra/pmc.c | 106 ++++++++++++++++++++++++++++------------
> 1 file changed, 76 insertions(+), 30 deletions(-)
>
> diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c
> index f3760a3b3026..a0cc276e235a 100644
> --- a/drivers/soc/tegra/pmc.c
> +++ b/drivers/soc/tegra/pmc.c
> @@ -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>
> @@ -336,6 +337,8 @@ struct tegra_wake_event {
> }, \
> }
>
> +#define TEGRA_PMC_MAX_WAKE_VECTORS 4
> +
> struct tegra_pmc_soc {
> unsigned int num_powergates;
> const char *const *powergates;
> @@ -468,6 +471,10 @@ struct tegra_pmc {
> unsigned long *wake_sw_status_map;
> unsigned long *wake_cntrl_level_map;
> struct syscore syscore;
> +
> + /* Pending wake IRQ processing */
> + u32 pending_wake_status[TEGRA_PMC_MAX_WAKE_VECTORS];
> + struct irq_work pending_wake_irq_work;
> };
>
> static struct tegra_pmc *pmc = &(struct tegra_pmc) {
> @@ -1905,8 +1912,59 @@ 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_irq_work_handler(struct irq_work *work)
> +{
> + struct tegra_pmc *pmc = container_of(work, struct tegra_pmc,
> + pending_wake_irq_work);
> + unsigned int i, wake;
> + unsigned long pending_wake_status;
> +
> + for (i = 0; i < pmc->soc->max_wake_vectors; i++) {
> + pending_wake_status = pmc->pending_wake_status[i];
> +
> + for_each_set_bit(wake, &pending_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 %d\n",
> + hwirq, irq);
> +
> + desc = irq_to_desc(irq);
> + if (!desc) {
> + dev_warn(pmc->dev,
> + "No descriptor found for IRQ %d\n",
> + irq);
> + continue;
> + }
> +
> + if (!desc->action || !desc->action->name)
> + continue;
> +
> + generic_handle_irq(irq);
> + }
> +
> + pmc->pending_wake_status[i] = 0;
> + }
> +}
> +
> static int tegra_pmc_init(struct tegra_pmc *pmc)
> {
> + if (pmc->soc->max_wake_vectors > TEGRA_PMC_MAX_WAKE_VECTORS) {
> + dev_err(pmc->dev, "max_wake_vectors (%u) exceeds maximum (%u)\n",
> + pmc->soc->max_wake_vectors, TEGRA_PMC_MAX_WAKE_VECTORS);
> + return -EINVAL;
> + }
> +
> if (pmc->soc->max_wake_events > 0) {
> pmc->wake_type_level_map = bitmap_zalloc(pmc->soc->max_wake_events, GFP_KERNEL);
> if (!pmc->wake_type_level_map)
> @@ -1923,6 +1981,12 @@ 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;
> +
> + /* 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->pending_wake_irq_work = IRQ_WORK_INIT_HARD(tegra186_pmc_wake_irq_work_handler);
> }
>
> if (pmc->soc->init)
> @@ -3129,47 +3193,29 @@ 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;
> + u32 mask;
> unsigned int i;
>
> 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->pending_wake_status[i] = readl(pmc->wake + WAKE_AOWAKE_STATUS_R(i)) & mask;
> }
Missing a newline here.
> + /* Schedule IRQ work to process wake IRQs (if any) */
> + irq_work_queue(&pmc->pending_wake_irq_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->pending_wake_status[i])
> + dev_warn(pmc->dev,
> + "Unhandled wake IRQs pending vector[%u]: 0x%x\n",
> + i, pmc->pending_wake_status[i]);
Missing a newline here.
> wke_read_sw_wake_status(pmc);
>
> /* flip the wakeup trigger for dual-edge triggered pads
Thierry may be able to fix up the missing newlines when applying. With
that ...
Reviewed-by: Jon Hunter <jonathanh@...dia.com>
Tested-by: Jon Hunter <jonathanh@...dia.com>
Thanks!
Jon
--
nvpublic
Powered by blists - more mailing lists