From 43c6ca839e8b770995cb822f77648a0e754f1b3f Mon Sep 17 00:00:00 2001 From: Joshua Warren <816217+joshuaswarren@users.noreply.github.com> Date: Wed, 11 Nov 2026 14:09:54 -0600 Subject: [PATCH 1/2] soc: apple: mailbox: receive T6050 GPU mailboxes by polling On the 14-inch M5 Max MacBook Pro the GPU ASC interrupt never reaches the AIC, so nothing the GPU firmware sends is received. Add rx_poll_only to struct apple_mbox_hw and set it for both T6050 ASCWrap v6 hardware descriptions. apple_mbox_has_rx_poller() now also returns true for them, and probe sets up the same delayed work item the v8 mailboxes use, so the receive poller drives these mailboxes. Tested on a 14-inch M5 Max MacBook Pro: with this change the GPU firmware (macOS 27.3 build 26C5035r) completes its initdata handshake and the driver registers /dev/dri/card1 and renderD128. Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com> --- drivers/soc/apple/mailbox.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/soc/apple/mailbox.c b/drivers/soc/apple/mailbox.c index f4f674f548c5d..c76fd0a9367ea 100644 --- a/drivers/soc/apple/mailbox.c +++ b/drivers/soc/apple/mailbox.c @@ -225,6 +225,8 @@ struct apple_mbox_hw { unsigned int num_ascwrap_v6_roles; /* T8152 GPU ASCWrap v8: wrapper + iop-vbar + post-office window + PSD. */ bool is_ascwrap_v8; + /* The mailbox interrupt never reaches the AIC: receive by polling. */ + bool rx_poll_only; /* Status word counts RX occupancy and free TX slots; paired transfers. */ bool counted_fifo; bool ap_initializes_mailboxes; @@ -275,7 +277,7 @@ static struct apple_mbox_priv *apple_mbox_to_priv(struct apple_mbox *mbox) /* Only providers allocated by apple_mbox_probe() have a receive poller. */ static bool apple_mbox_has_rx_poller(const struct apple_mbox *mbox) { - return mbox->hw->is_ascwrap_v8; + return mbox->hw->is_ascwrap_v8 || mbox->hw->rx_poll_only; } /* Both ASCWrap generations share the consumer lifecycle API below. */ @@ -1338,6 +1340,10 @@ static int apple_mbox_probe(struct platform_device *pdev) return -EINVAL; if (mbox->hw->counted_fifo && !IS_ENABLED(CONFIG_ARM64)) return -EOPNOTSUPP; + if (mbox->hw->rx_poll_only) { + priv->rx_poll_only = true; + INIT_DELAYED_WORK(&priv->rx_poll_work, apple_mbox_v8_rx_poll_work); + } if (mbox->hw->is_ascwrap_v6) { /* @@ -1702,6 +1708,7 @@ static const struct apple_mbox_hw apple_mbox_t6050_ascwrap_v6_hw = { .i2a_recv0 = APPLE_ASC_MBOX_I2A_RECV0, .i2a_recv1 = APPLE_ASC_MBOX_I2A_RECV1, .has_irq_controls = false, + .rx_poll_only = true, }; static const struct apple_mbox_hw apple_mbox_t6050_j775d_ascwrap_v6_hw = { @@ -1718,6 +1725,7 @@ static const struct apple_mbox_hw apple_mbox_t6050_j775d_ascwrap_v6_hw = { .i2a_recv0 = APPLE_ASC_MBOX_I2A_RECV0, .i2a_recv1 = APPLE_ASC_MBOX_I2A_RECV1, .has_irq_controls = false, + .rx_poll_only = true, }; /* From a23755c12ee9d59b9caedea010780272c705caf7 Mon Sep 17 00:00:00 2001 From: Joshua Warren <816217+joshuaswarren@users.noreply.github.com> Date: Thu, 12 Nov 2026 08:14:26 -0600 Subject: [PATCH 2/2] soc: apple: mailbox: poll T6050 GPU receive quickly while a reply is outstanding The poll-only T6050 GPU mailboxes receive with the delayed work poller, which wakes once per jiffy: 4 ms at HZ=250, against a 0.22 ms GPU frame, so a reply can wait for roughly eighteen frames. After every send on a rx_poll_only mailbox, arm a 25 us hrtimer that runs the same receive step as the jiffy poller (same rx_lock, same PSD and removal checks). Fast-polling continues while messages arrive and for a 1 ms grace window after the last message or send -- about four frames, to catch straggler replies -- then stops and leaves only the slow poller, so unsolicited messages are still received while idle. The recv-not-empty IRQ mode and the ASCWrap v8 mailboxes are untouched: every new code path is gated on hw->rx_poll_only. The kick writes the grace deadline and arms the timer without taking rx_lock: apple_mbox_poll_locked() runs the rx callback with rx_lock held, and a callback that sends would take tx_lock under rx_lock. Neither the RTKit receive callback nor any recv_message_early hook sends today, but the kick no longer depends on that. Both timers are cancelled where the poller already was, on stop and remove. Compile-tested only against the aurora 7.3 tree (arm64, LLVM=1, config with CONFIG_APPLE_MAILBOX=y); no hardware run yet, so the 25 us period and 1 ms grace are unproven on real firmware. Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com> --- drivers/soc/apple/mailbox.c | 86 ++++++++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/drivers/soc/apple/mailbox.c b/drivers/soc/apple/mailbox.c index c76fd0a9367ea..b3fd558cc79cc 100644 --- a/drivers/soc/apple/mailbox.c +++ b/drivers/soc/apple/mailbox.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -209,6 +210,15 @@ static_assert(APPLE_ASCWRAP_V6_MBOX_OFFSET + APPLE_ASC_MBOX_I2A_RECV0 == #define APPLE_MBOX_TX_RECHECK_MS 2 #define APPLE_G17_MBOX_RX_BUDGET 64 #define APPLE_G17_MBOX_ATOMIC_TX_US 1000 +/* + * T6050 poll-only receive: every send arms an hrtimer that polls the receive + * FIFO at this period, kept for this long after the last message or send. A + * GPU reply lands within one 0.22 ms frame; polling every 25 us bounds the + * added receive latency to ~11% of a frame instead of one 4 ms jiffy, while + * idle receive stays on the jiffy poller. + */ +#define APPLE_MBOX_RX_FAST_POLL_US 25 +#define APPLE_MBOX_RX_FAST_GRACE_US 1000 /* One firmware role of an ASCWrap v6 provider, at its fixed CPU-physical windows. */ struct apple_mbox_ascwrap_v6_role_desc { @@ -267,6 +277,9 @@ struct apple_mbox_priv { /* rx_lock: an unpowered-PSD receive skip was logged this activation. */ bool rx_psd_skip_logged; struct delayed_work rx_poll_work; + /* Kick and fast tick write this lock-free; reads use READ_ONCE. */ + ktime_t rx_fast_until; + struct hrtimer rx_fast_timer; }; static struct apple_mbox_priv *apple_mbox_to_priv(struct apple_mbox *mbox) @@ -600,6 +613,65 @@ static struct apple_mbox_msg apple_mbox_read_msg(struct apple_mbox *mbox) return msg; } +static int apple_mbox_poll_locked(struct apple_mbox *mbox); + +/* + * Fast receive tick for rx_poll_only mailboxes (T6050): the same receive + * step apple_mbox_v8_rx_poll_work() runs, under the same rx_lock, with + * apple_mbox_poll_locked() doing the same PSD and removal checks. The v8 + * storm guard does not apply: it guards the recv-not-empty IRQ line, which + * rx_poll_only hardware never enables. Runs in hardirq context. + */ +static enum hrtimer_restart apple_mbox_fast_rx_poll(struct hrtimer *timer) +{ + struct apple_mbox_priv *priv = container_of(timer, struct apple_mbox_priv, + rx_fast_timer); + struct apple_mbox *mbox = &priv->mbox; + unsigned long flags; + ktime_t now; + int consumed = 0; + + if (!READ_ONCE(mbox->active) || READ_ONCE(mbox->removing)) + return HRTIMER_NORESTART; + + spin_lock_irqsave(&mbox->rx_lock, flags); + if (likely(!READ_ONCE(mbox->removing))) + consumed = apple_mbox_poll_locked(mbox); + spin_unlock_irqrestore(&mbox->rx_lock, flags); + + now = ktime_get(); + if (consumed > 0) { + /* Traffic arrived: keep polling as if a send just happened. */ + WRITE_ONCE(priv->rx_fast_until, + ktime_add_us(now, APPLE_MBOX_RX_FAST_GRACE_US)); + } else if (!ktime_before(now, READ_ONCE(priv->rx_fast_until))) { + return HRTIMER_NORESTART; + } + + hrtimer_forward_now(timer, us_to_ktime(APPLE_MBOX_RX_FAST_POLL_US)); + return HRTIMER_RESTART; +} + +/* + * tx_lock held. Lock-free on purpose: apple_mbox_poll_locked() invokes the + * rx callback with rx_lock held, and a callback that sends would then take + * tx_lock under rx_lock. Kick never touches rx_lock, so no ordering exists + * to invert. A kick that loses a race with the timer's final no-restart + * decision leaves the fast poller disarmed once; the next jiffy poll or + * send recovers it. + */ +static void apple_mbox_fast_rx_kick(struct apple_mbox_priv *priv) +{ + WRITE_ONCE(priv->rx_fast_until, + ktime_add_us(ktime_get(), APPLE_MBOX_RX_FAST_GRACE_US)); + /* Publish the deadline before deciding to arm. */ + smp_mb(); + if (!hrtimer_active(&priv->rx_fast_timer)) + hrtimer_start(&priv->rx_fast_timer, + us_to_ktime(APPLE_MBOX_RX_FAST_POLL_US), + HRTIMER_MODE_REL); +} + int apple_mbox_send(struct apple_mbox *mbox, const struct apple_mbox_msg msg, bool atomic) { @@ -722,6 +794,10 @@ int apple_mbox_send(struct apple_mbox *mbox, const struct apple_mbox_msg msg, apple_mbox_write_msg(mbox, msg); + /* The reply may be back well within a jiffy: fast-poll for it. */ + if (mbox->hw->rx_poll_only) + apple_mbox_fast_rx_kick(apple_mbox_to_priv(mbox)); + spin_unlock_irqrestore(&mbox->tx_lock, flags); return 0; @@ -961,6 +1037,9 @@ static void apple_mbox_v8_rx_stop(struct apple_mbox *mbox) if (live) disable_irq(mbox->irq_recv_not_empty); + /* Same cancel-and-drain guarantee for the fast poller as below. */ + if (mbox->hw->rx_poll_only) + hrtimer_cancel(&priv->rx_fast_timer); cancel_delayed_work_sync(&priv->rx_poll_work); } @@ -1124,8 +1203,11 @@ static void apple_mbox_quiesce(void *data) pm_runtime_put_autosuspend(mbox->dev); } /* The v8 poller also stops on the removal fence; make sure it is gone. */ - if (apple_mbox_has_rx_poller(mbox)) + if (apple_mbox_has_rx_poller(mbox)) { + if (mbox->hw->rx_poll_only) + hrtimer_cancel(&apple_mbox_to_priv(mbox)->rx_fast_timer); cancel_delayed_work_sync(&apple_mbox_to_priv(mbox)->rx_poll_work); + } /* Serialize once-only masking with the IRQ and send-error paths. */ spin_lock_irqsave(&mbox->tx_lock, flags); @@ -1342,6 +1424,8 @@ static int apple_mbox_probe(struct platform_device *pdev) return -EOPNOTSUPP; if (mbox->hw->rx_poll_only) { priv->rx_poll_only = true; + hrtimer_setup(&priv->rx_fast_timer, apple_mbox_fast_rx_poll, + CLOCK_MONOTONIC, HRTIMER_MODE_REL); INIT_DELAYED_WORK(&priv->rx_poll_work, apple_mbox_v8_rx_poll_work); }