From 6c1f920589b7b30ac8ebd2e81ac86dd5175c539f Mon Sep 17 00:00:00 2001 From: Brent <1550934+brentkearney@users.noreply.github.com> Date: Wed, 2 Sep 2026 22:10:00 -0600 Subject: [PATCH] soc: apple: mailbox: balance the send-empty IRQ enable when the wait fails apple_mbox_send() enables irq_send_empty and waits for the coprocessor to drain the A2I FIFO. Only the interrupt handler disables that interrupt again, so when the wait does not complete -- the FIFO stays full for APPLE_MBOX_TX_TIMEOUT, or the wait is interrupted by a signal -- the interrupt is left enabled and its disable depth is off by one. The next send that finds the FIFO full then calls enable_irq() on an already-enabled interrupt, which warns and underflows the depth counter: Unbalanced enable for IRQ 57 WARNING: kernel/irq/manage.c:774 at __enable_irq+0x4c/0x80, CPU#0 Call trace: __enable_irq+0x4c/0x80 (P) enable_irq+0x74/0xe4 apple_mbox_send+0xb4/0x1b8 Observed on an M1 Pro (apple,t6000) after SEPOS stopped draining its mailbox: of eight consecutive sends that timed out, the last seven each warned with a full backtrace. Track whether the interrupt is enabled in struct apple_mbox under tx_lock and disable it on the failure paths. The flag also settles the race with the handler running just after the wait expired: whichever side takes tx_lock first performs the single disable, and the stale completion is cleared by the reinit_completion() before the next wait. Signed-off-by: Brent <1550934+brentkearney@users.noreply.github.com> --- drivers/soc/apple/mailbox.c | 27 ++++++++++++++++++++++----- include/linux/soc/apple/mailbox.h | 8 ++++++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/drivers/soc/apple/mailbox.c b/drivers/soc/apple/mailbox.c index 330015eea2ebe4..6f7b964a161b53 100644 --- a/drivers/soc/apple/mailbox.c +++ b/drivers/soc/apple/mailbox.c @@ -137,17 +137,33 @@ int apple_mbox_send(struct apple_mbox *mbox, const struct apple_mbox_msg msg, writel_relaxed(mbox->hw->irq_bit_send_empty, mbox->regs + mbox->hw->irq_ack); } - enable_irq(mbox->irq_send_empty); + if (!mbox->tx_irq_enabled) { + enable_irq(mbox->irq_send_empty); + mbox->tx_irq_enabled = true; + } reinit_completion(&mbox->tx_empty); spin_unlock_irqrestore(&mbox->tx_lock, flags); t = wait_for_completion_interruptible_timeout( &mbox->tx_empty, msecs_to_jiffies(APPLE_MBOX_TX_TIMEOUT)); - if (t < 0) - return t; - else if (t == 0) - return -ETIMEDOUT; + if (t <= 0) { + /* + * The interrupt handler disables the interrupt + * when it completes tx_empty. If it never ran, + * undo the enable above here: otherwise the next + * send that finds the FIFO full warns about an + * unbalanced enable and underflows the interrupt's + * disable depth. + */ + spin_lock_irqsave(&mbox->tx_lock, flags); + if (mbox->tx_irq_enabled) { + disable_irq_nosync(mbox->irq_send_empty); + mbox->tx_irq_enabled = false; + } + spin_unlock_irqrestore(&mbox->tx_lock, flags); + return t < 0 ? t : -ETIMEDOUT; + } spin_lock_irqsave(&mbox->tx_lock, flags); mbox_ctrl = readl_relaxed(mbox->regs + mbox->hw->a2i_control); @@ -176,6 +192,7 @@ static irqreturn_t apple_mbox_send_empty_irq(int irq, void *data) */ spin_lock(&mbox->tx_lock); disable_irq_nosync(mbox->irq_send_empty); + mbox->tx_irq_enabled = false; complete(&mbox->tx_empty); spin_unlock(&mbox->tx_lock); diff --git a/include/linux/soc/apple/mailbox.h b/include/linux/soc/apple/mailbox.h index f73a8913da9510..6306a73e34efbc 100644 --- a/include/linux/soc/apple/mailbox.h +++ b/include/linux/soc/apple/mailbox.h @@ -31,6 +31,14 @@ struct apple_mbox { struct completion tx_empty; + /* + * Whether irq_send_empty is enabled at the interrupt controller. + * Guarded by tx_lock. Keeps the enable in apple_mbox_send balanced + * against the disable in apple_mbox_send_empty_irq when the wait for + * the FIFO to drain fails. + */ + bool tx_irq_enabled; + /** Receive callback for incoming messages */ void (*rx)(struct apple_mbox *mbox, struct apple_mbox_msg msg, void *cookie); void *cookie;